#firmware #widgets #gtk #manager #applications #update #devices

pop-os/firmware-manager

针对 system76-firmware 和 fwupd 的固件更新通用框架和 GTK UI,使用 Rust 编写

1 个不稳定版本

0.1.0 2019年8月13日

#1416 in 硬件支持

146 星 & 13 关注者

185KB
3K SLoC

Rust 2.5K SLoC // 0.0% comments · Rust 包仓库 FreeMarker 592 SLoC · Rust 包仓库 C 34 SLoC · Rust 包仓库

固件管理器

Linux 系统中固件管理的遗留问题之一是缺乏对固件管理服务(如 fwupdsystem76-firmware)的图形前端选项。对于 fwupd,可用的唯一解决方案是分发 GNOME 软件,或 KDE Discover;但这对于拥有自己应用中心或前端包管理器的 Linux 发行版来说并不可行。对于 system76-firmware,存在一个官方的 GTK 应用程序,但它仅支持更新 System76 固件,如果它能同时支持更新两个服务的固件会更好。

fwupd 是一个系统服务,它连接到 LVFS 以检查来自多个供应商的多种硬件的固件更新。 system76-firmware 是我们自己的系统服务,它连接到 System76 以检查 System76 硬件的固件更新。

隐私

为了提高隐私性,我们在 Pop!_OS 上禁用了 fwupd 的遥测报告。

为了解决这个问题,我们一直在开发固件管理器项目,该项目将提供给所有Pop!_OS用户以及任何其他发行版的System76硬件客户。它支持从fwupdsystem76-firmware服务检查和更新固件,兼容Wayland,并提供GTK应用程序和库。

Wayland不允许以root身份运行应用程序,因此应用程序必须调用pkexec以提示用户获取运行root背景进程的权限,或者连接到现有的背景服务,前提是具备所需的权限。

在Pop!_OS中,固件管理器将集成到GNOME设置中,在“设备”类别下新增一个名为“固件”的新面板,其中包含GTK小部件库。对于其他Linux发行版以及不使用GNOME的用户,GTK应用程序可作为独立应用程序在其自己的应用程序窗口中提供固件管理器小部件。

虽然我们为Pop!_OS创建了GTK应用程序和小部件库,但核心框架是工具无关的,因此可以使用任何工具包编写固件管理器前端。然而,需要注意的是,由于框架是用Rust编写的,前端需要使用Rust才能与之交互。

GTK应用程序

Ubuntu和其他希望拥有独立桌面应用程序的Linux发行版可以免费使用包含的GTK应用程序。

Screenshot of GTK Application

GNOME设置集成

Pop!_OS将集成一个补丁到GNOME设置中,该补丁将GTK小部件嵌入到“设备”类别下的新“固件”面板中。

Screenshot of GNOME Settings Integration

分发固件管理器

当将固件管理器与GTK前端一起打包时,所需的唯一依赖项是libdbuslibgtklibssllibudev。固件管理器使用DBus与system76-firmwarefwupd守护进程进行通信。这两个都是可选的,不需要安装即可使用或编译项目。固件管理器会初始检查这两个守护进程的存在。如果没有安装守护进程,则不会找到任何固件。如果安装了一个守护进程,那么它将发现由该服务管理的固件,如果系统上找到管理固件。

由于它是用Rust编写的,因此需要Rustc及其Cargo对应物来编译项目。源代码库根目录中的rust-toolchain文件定义了编译器的最低支持版本。我们将始终依赖于Ubuntu最新LTS中打包的Rust版本。您可以在这里检查Ubuntu支持的内容

为了将项目打包以便可以在schroot中离线构建,存在一个make vendor规则,该规则使用官方的cargo-vendor实用程序以本地方式检索所有crate依赖项,然后生成一个tar包,可以在或与源代码包一起分发。然后您可以指示makefile使用 vendored 依赖项构建项目,通过设置VENDOR=1,如下所示:make VENDOR=1 prefix=/usr

如果您的Cargo版本没有cargo-vendor特性,您可以在此处单独安装cargo-vendor

对于不熟悉Rust的人,crate是具有特定功能的源代码模块,可以通过如Crates.io这样的公共注册表大量分发,或者直接通过URL获取。当它们用于构建库或二进制文件的项目时,它们是静态链接的。对于应用程序,Cargo生成一个Cargo.lock文件,该文件指定了每个依赖项的确切版本及其SHA256校验和。这是为了确保拉取项目的人将具有与上游相同的crate依赖项版本。

实现细节

像我们今天所有的项目一样,它是用Rust编写的,并遵循当前的最佳实践。该项目配置为一个工作区,其中核心crate提供用于从多个固件服务发现和管理固件的通用库。既支持fwupd,也支持system76-firmware

核心被用作该工作区两个成员的基础:一个是提供有关固件更新的桌面通知的二进制通知程序;另一个是GTK项目,它既是一个小部件库,也是一个桌面应用程序。

项目结构的可视化

* firmware-manager
    * firmware-manager-notify
    * firmware-manager-gtk
        * firmware-manager-gtk-ffi

核心库

firmware-manager库提供扫描固件的功能,以及一个事件循环,通过通道接收和发送事件信号。一个通道接收来自前端的消息,而另一个向前端发送消息。它设计为在后台线程中运行,以防止使用固件管理器的UI在请求处理时阻塞。

此外,预计将使用提供的基于slotmap的实体-组件架构与事件API一起使用。这允许前端为它们的请求分配实体ID,并在响应中接收这些实体ID。通过这样做,前端可以避免复杂运行时引用计数或创建引用循环的需要。前端对其所引用的实体数据拥有专属所有权。

GTK应用程序/库

项目中的firmware-manager-gtk成员提供固件小部件作为库,并将该小部件放入窗口中的应用程序。该成员包含一个C FFI子成员,它构建了一个具有C API和头文件的动态库,可以用于将小部件集成到任何用C编写的GTK应用程序中。

此实现充分利用了slotmap EC,为其分配自己的组件存储来跟踪与设备实体相关的状态,例如分配给实体的小部件以及有关其固件的信息。

包含的GTK应用程序将Rust小部件库静态链接到二进制文件中。

通知二进制文件

firmware-manager-notify成员包含一个systemd用户定时器,以便在登录时执行,然后在设定的间隔内定期再次运行以检查更新。当找到更新时,将显示一个可点击的通知,该通知将打开GNOME设置中的固件面板或独立的桌面应用程序,具体取决于系统上哪个可用。

支持其他前端

尽管该项目将仅附带GTK前端,但任何人都可以将其作为开发用任何其他图形工具包编写的图形界面的基础。核心库中的所有功能都是与GUI无关的,实体-组件架构可以扩展以满足它们的特定需求。如果您为另一个工具包编写了前端并希望将其包含在项目中,请随时提交一个拉取请求!

如何实现前端支持

前端预计将在包含的实体-组件架构中存储有关设备的信息,在 固件管理器 中。发送到固件管理器事件循环的事件需要将实体 ID 一起发送消息。这使得从小部件信号中排除循环引用变得更容易,并能够识别响应指向的是哪个固件。属于特定固件设备的 widgets 只需要通过它们的发送者发送带有附加实体 ID 的消息。

构建说明

本项目使用 Makefile。在构建应用程序时,必须提供 prefix 标志,以便在安装后生成指向目标二进制文件正确路径的桌面条目文件。

make prefix=/usr
sudo make install prefix=/usr

请注意,生成的桌面条目存储在 target 目录中,该目录中还包括在生成后存储的 pkgconfig 文件。如果您需要使用不同的前缀重新生成桌面条目,可以手动调用 desktop 规则。

make desktop prefix=/usr

调试二进制文件

要构建调试二进制文件,请将 DEBUG=1 传递给 make。

make prefix=/usr DEBUG=1
sudo make install DEBUG=1

供应商

要为打包而对项目进行供应商操作,请调用 make vendor。要构建已供应商的项目,请将 VENDOR=1 传递给 makefile。

make vendor
make prefix=/usr VENDOR=1

API 概述

本节提供了有关 API 以及如何从 Rust 或 C 调用它的详细信息。

Rust API

这是主要 API,C API 依据此 API 构建。可以在 GTK 应用程序中找到 Rust API 的实际示例,请参阅 此处

use firmware_manager_gtk::FirmwareWidget;

// Create a new firmware widget
//
// This spawns a background thread which listens for widget events until
// the `Quit` signal is received, which occurs when the firmware widget
// is dropped.
let mut firmware = FirmwareWidget::new();

// Signal the widget's background thread to begin scanning for firmware.
firmware.scan();

// Get the GTK widget from the firmware widget to add into a window.
let widget = firmware.container();

C API

Rust 库还支持 C 接口,在 Makefile 中使用 FFI 规则生成具有 pkg-config 支持的动态 C 库。这集成在 Pop!_OS 的 GNOME 设置中。

make ffi prefix=/usr
sudo make install-ffi prefix=/usr

然后可以导入到 C 代码库中

#include <s76_firmware.h>

// Create a new firmware widget
S76FirmwareWidget *firmware =
    s76_firmware_widget_new ();

// Signal the widget's background thread to begin scanning for firmware.
s76_firmware_widget_scan (firmware);

// Get the GTK widget from the firmware widget to attach it to a container.
GtkWidget *firmware_widget =
    s76_firmware_widget_container (firmware);

// Destroy the widget and signal its background thread to quit.
s76_firmware_widget_destroy (firmware);

Rust 应用程序的 C 实现请参阅 此处,并使用 Meson 构建系统进行演示。

依赖关系

~28–42MB
~720K SLoC