Skip to content

Commit

Permalink
Add support for fwupd (#150)
Browse files Browse the repository at this point in the history
* Add D-Bus interface to interact with fwupd

* Get device names

* Add class for devices

* Split into separate views

* Add view for firmware devices

* Add more information to view

* Add function of verify button

* Add function to get releases

* Add page for releases

* Add reference on how to decode device flags

* Check if releases are available for device

* Get releases

* Show list of releases

* Show install duration

* Catch error

* Decode device flags

* Show icon and text for device flags

* Save releases as property of device

* Add update error

* Satisfy linter

* Show release information

* Pretty print file size and time

* Add dialog to install release

* Format release description

* Add todo

* Add support to install upgrade or reinstall

* Add support to install downgrade

* List device only if updatable

* Separate operating system and hardware views

* Add list of available firmware updates

* Download update

* Remove unused code

* Refactor

* Make calls async

* Refactor handle

* Refactor handle

* Refactor handle

* Show feedback during update

* Remove dummy update all button

* Update layout

* Refactor

* Remove unused code

* Update src/Interfaces/FwupdManager.vala

Co-authored-by: Daniel Foré <[email protected]>

* Update src/Plug.vala

Co-authored-by: Daniel Foré <[email protected]>

* Update src/Plug.vala

Co-authored-by: Daniel Foré <[email protected]>

* Update src/Views/FirmwareView.vala

Co-authored-by: Daniel Foré <[email protected]>

* Refactor reference type

* Update src/Widgets/FirmwareUpdateWidget.vala

Co-authored-by: Daniel Foré <[email protected]>

* Update src/Widgets/FirmwareUpdateWidget.vala

Co-authored-by: Daniel Foré <[email protected]>

* Show placeholder when no devices are available

* Show error dialog

* Update src/Views/FirmwareView.vala

Co-authored-by: Cassidy James Blaede <[email protected]>

* Add GetDetails

* Show dialog if manual steps are required

* Set transient_for

* Use listbox placeholder (#167)

* Get rid of page header

* Add icon to list placeholder

* Show dialog to shutdown or reboot if needed

* Refactor

* Provide specifc error text

* Rename to FirmwareUpdateRow

* Be more verbose for shutdown and reboot

* Reduce number of iterations

* Provide a hopefully more understandable text

* Apply suggestions from code review

Co-authored-by: Cassidy James Blaede <[email protected]>

* Update icons

* Show name of device during update

* Update button label

Co-authored-by: Cassidy James Blaede <[email protected]>

* Apply suggestions from code review

Co-authored-by: Daniel Foré <[email protected]>

* Use device icon if possible

* Always provide device on error

* Reload when new device is plugged in

* Reload when device is removed

* Remove unused signal

* Use flags properly

* Refactor get_devices (#168)

* Refactor get_releases

* Refactor install

* Refactor get_details

* Remove unnecessary async

* Use Posix.open

* Refactor namespace

* Refactor warning messages

* Make interface private

* Refactor to use different variables

* Refactor translator comments

* Add cancel button for details view

* Get rid of singleton

* Always download file

* Rename to get_release_details

* Refactor get path of file

* Add TODO

* Close filedescriptor

* Refactor to use property

* Refactor dialog to decide if we want to continue the update

Co-authored-by: Daniel Foré <[email protected]>
Co-authored-by: Cassidy James Blaede <[email protected]>
Co-authored-by: David Hewitt <[email protected]>
  • Loading branch information
4 people committed Jan 25, 2021
1 parent 5ace95e commit 932d4f1
Show file tree
Hide file tree
Showing 11 changed files with 1,087 additions and 1 deletion.
304 changes: 304 additions & 0 deletions src/Interfaces/FwupdManager.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
/*
* Copyright (c) 2020 elementary, Inc. (https://elementary.io)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*
* Authored by: Marius Meisenzahl <[email protected]>
*/

public class About.FwupdManager : Object {
[DBus (name = "org.freedesktop.fwupd")]
private interface FwupdInterface : Object {
public abstract signal void device_added (GLib.HashTable<string, Variant> device);
public abstract signal void device_removed (GLib.HashTable<string, Variant> device);

public abstract async GLib.HashTable<string, Variant>[] get_devices () throws GLib.Error;
public abstract async GLib.HashTable<string, Variant>[] get_releases (string device_id) throws GLib.Error;
public abstract async void install (string id, UnixInputStream handle, GLib.HashTable<string, Variant> options) throws GLib.Error;
public abstract async GLib.HashTable<string, Variant>[] get_details (UnixInputStream handle) throws GLib.Error;
}

private FwupdInterface fwupd;

public signal void on_device_added (Fwupd.Device device);
public signal void on_device_error (Fwupd.Device device, string error);
public signal void on_device_removed (Fwupd.Device device);

public async List<Fwupd.Device> get_devices () {
var devices_list = new List<Fwupd.Device> ();

try {
var result = yield fwupd.get_devices ();
foreach (unowned GLib.HashTable<string, Variant> device in result) {
devices_list.append (yield parse_device (device));
}
} catch (Error e) {
warning ("Could not get devices: %s", e.message);
}

return devices_list;
}

private async List<Fwupd.Release> get_releases (string id) {
var releases_list = new List<Fwupd.Release> ();

try {
var result = yield fwupd.get_releases (id);
foreach (unowned GLib.HashTable<string, Variant> release in result) {
releases_list.append (parse_release (release));
}
} catch (Error e) {
warning ("Could not get releases for “%s”: %s", id, e.message);
}

return releases_list;
}

private async Fwupd.Device parse_device (GLib.HashTable<string, Variant> serialized_device) {
var device = new Fwupd.Device ();

serialized_device.@foreach ((key, val) => {
switch (key) {
case "DeviceId":
device.id = val.get_string ();
break;
case "Flags":
device.flags = (Fwupd.DeviceFlag) val.get_uint64 ();
break;
case "Name":
device.name = val.get_string ();
break;
case "Summary":
device.summary = val.get_string ();
break;
case "Vendor":
device.vendor = val.get_string ();
break;
case "VendorId":
if (device.vendor == null) {
device.vendor = val.get_string ();
}
break;
case "Version":
device.version = val.get_string ();
break;
case "Icon":
var icons = val.get_strv ();
device.icon = icons.length > 0 ? icons[0] : "application-x-firmware";
break;
case "Guid":
device.guids = val.get_strv ();
break;
case "InstallDuration":
device.install_duration = val.get_uint32 ();
break;
case "UpdateError":
device.update_error = val.get_string ();
break;
default:
break;
}
});

if (device.id.length > 0 && device.has_flag (Fwupd.DeviceFlag.UPDATABLE)) {
device.releases = yield get_releases (device.id);
} else {
device.releases = new List<Fwupd.Release> ();
}

return device;
}

private Fwupd.Release parse_release (GLib.HashTable<string, Variant> serialized_release) {
var release = new Fwupd.Release () {
icon = "security-high"
};

serialized_release.@foreach ((key, val) => {
switch (key) {
case "Filename":
release.filename = val.get_string ();
break;
case "Name":
release.name = val.get_string ();
break;
case "Summary":
release.summary = val.get_string ();
break;
case "Version":
release.version = val.get_string ();
break;
case "Description":
release.description = val.get_string ()
.replace ("<p>", "")
.replace ("</p>", "\n\n")
.replace ("<li>", "")
.replace ("</li>", "\n")
.replace ("<ul>", "")
.replace ("</ul>", "\n")
.replace ("<ol>", "") // TODO: add support for ordered lists
.replace ("</ol>", "\n")
.strip ();
break;
case "Protocol":
release.protocol = val.get_string ();
break;
case "RemoteId":
release.remote_id = val.get_string ();
break;
case "AppstreamId":
release.appstream_id = val.get_string ();
break;
case "Checksum":
release.checksum = val.get_string ();
break;
case "Vendor":
release.vendor = val.get_string ();
break;
case "Size":
release.size = val.get_uint64 ();
break;
case "License":
release.license = val.get_string ();
break;
case "TrustFlags":
release.flag = (Fwupd.ReleaseFlag) val.get_uint64 ();
break;
case "InstallDuration":
release.install_duration = val.get_uint32 ();
break;
case "Uri":
release.uri = val.get_string ();
break;
default:
break;
}
});

return release;
}

public async string? download_file (Fwupd.Device device, string uri) {
File server_file = File.new_for_uri (uri);
var path = Path.build_filename (Environment.get_tmp_dir (), server_file.get_basename ());
File local_file = File.new_for_path (path);

bool result;
try {
result = yield server_file.copy_async (local_file, FileCopyFlags.OVERWRITE, Priority.DEFAULT, null, (current_num_bytes, total_num_bytes) => {
// TODO: provide useful information for user
});
} catch (Error e) {
on_device_error (device, "Could not download file: %s".printf (e.message));
return null;
}

if (!result) {
on_device_error (device, "Download of %s was not succesfull".printf (uri));
return null;
}

return path;
}

public async bool install (Fwupd.Device device, string path) {
int fd;

try {
// https://github.com/fwupd/fwupd/blob/c0d4c09a02a40167e9de57f82c0033bb92e24167/libfwupd/fwupd-client.c#L2045
var options = new GLib.HashTable<string, Variant> (str_hash, str_equal);
options.insert ("reason", new Variant.string ("user-action"));
options.insert ("filename", new Variant.string (path));
options.insert ("allow-older", new Variant.boolean (true));
options.insert ("allow-reinstall", new Variant.boolean (true));
options.insert ("no-history", new Variant.boolean (true));

fd = Posix.open (path, Posix.O_RDONLY);
var handle = new UnixInputStream (fd, true);

yield fwupd.install (device.id, handle, options);
} catch (Error e) {
warning ("Could not install release for “%s”: %s", device.id, e.message);
on_device_error (device, device.update_error != null ? device.update_error : e.message);
return false;
} finally {
Posix.close (fd);
}

return true;
}

public async Fwupd.Details get_release_details (Fwupd.Device device, string path) {
var details = new Fwupd.Details ();

int fd;
try {
fd = Posix.open (path, Posix.O_RDONLY);
var handle = new UnixInputStream (fd, true);

var result = yield fwupd.get_details (handle);
foreach (unowned GLib.HashTable<string, Variant> serialized_details in result) {
serialized_details.@foreach ((key, val) => {
if (key == "Release") {
var iter = val.iterator ().next_value ().iterator ();
string details_key;
Variant details_val;
while (iter.next ("{sv}", out details_key, out details_val)) {
if (details_key == "DetachCaption") {
details.caption = details_val.get_string ();
} else if (details_key == "DetachImage") {
details.image = details_val.get_string ();
}
}
}
});
}
} catch (Error e) {
warning ("Could not get details for “%s”: %s", device.id, e.message);
on_device_error (device, device.update_error);
} finally {
Posix.close (fd);
}

if (details.image != null) {
details.image = yield download_file (device, details.image);
}

return details;
}

construct {
try {
fwupd = Bus.get_proxy_sync (BusType.SYSTEM, "org.freedesktop.fwupd", "/");

fwupd.device_added.connect ((serialized_device) => {
parse_device.begin (serialized_device, (obj, res) => {
var device = parse_device.end (res);
on_device_added (device);
});
});

fwupd.device_removed.connect ((serialized_device) => {
parse_device.begin (serialized_device, (obj, res) => {
var device = parse_device.end (res);
on_device_removed (device);
});
});
} catch (Error e) {
warning ("Could not connect to system bus: %s", e.message);
}
}
}
71 changes: 71 additions & 0 deletions src/Interfaces/LoginManager.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright (c) 2021 elementary, Inc. (https://elementary.io)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*
* Authored by: Marius Meisenzahl <[email protected]>
*/

[DBus (name = "org.freedesktop.login1.Manager")]
public interface About.LoginInterface : Object {
public abstract void reboot (bool interactive) throws GLib.Error;
public abstract void power_off (bool interactive) throws GLib.Error;
}

public class About.LoginManager : Object {
private LoginInterface interface;

static LoginManager? instance = null;
public static LoginManager get_instance () {
if (instance == null) {
instance = new LoginManager ();
}

return instance;
}

private LoginManager () {}

construct {
try {
interface = Bus.get_proxy_sync (BusType.SYSTEM, "org.freedesktop.login1", "/org/freedesktop/login1");
} catch (Error e) {
warning ("Could not connect to login interface: %s", e.message);
}
}

public bool shutdown () {
try {
interface.power_off (true);
} catch (Error e) {
warning ("Could not connect to login interface: %s", e.message);
return false;
}

return true;
}

public bool reboot () {
try {
interface.reboot (true);
} catch (Error e) {
warning ("Could not connect to login interface: %s", e.message);
return false;
}

return true;
}
}
Loading

0 comments on commit 932d4f1

Please sign in to comment.