Port Extensions to GNOME Shell 40

Port Extensions to GNOME Shell 40

2021年4月份新发布的 Fedora Workstation 34 率先引入 GNOME 40.

Arch 也紧随其后发布了 GNOME 40 相关 package .

Ubuntu 21.04 之前传言是会有 GNOME 40, 但是最后临阵退缩了。不过 Ubuntu 相关的开发人员弄了一个 ppa 可以安装测试 (https://www.debugpoint.com/2021/04/gnome-40-ubuntu-21-04/)。

老灯使用 GNOME 40 已经有一段时间了。大部分必备的 extension 都已经升级支持 GNOME 40 了。如果有少量几个必须的不兼容 40 ,但是原作者又没更新怎么办? 只能自己动手了。

官方 port guide

GNOME shell 扩展是基于 js 的, 解析执行靠 gjs , 事实上 gjs 不只支持写扩展,它完全支持写任何 gtk 应用。流行的电子书阅读器 Foliate 就是 基于 js 写的

metadata.json

如果代码层面没有不工作的地方,最简单的 port 就是一句话,修改 metadata.json shell-version 增加 40

{
    "name": "Extension Name",
    "description": "Extension Description",
    "shell-version": ["3.36", "40"],
    "url": "",
    "uuid": "example@example",
    "version": 1
}

检查 GNOME Shell 和 GTK 版本

const Config = imports.misc.config;
const [major] = Config.PACKAGE_VERSION.split('.');
const shellVersion = Number.parseInt(major);

if (shellVersion < 40)
    log('Shell 3.38 or lower');
else
    log('Shell 40 or higher');
const {Gtk} = imports.gi;
const gtkVersion = Gtk.get_major_version();

log(`GTK version is ${gtkVersion}`);

仅在存在时导入命名空间

在GNOME Shell 40 上有一些新命名空间,同时,有一些旧的命名空间已经被删除了。 如果想要导入某个可能存在的命名空间时,则可以使用 try and catch block 语法:

try {
    const SearchController = imports.ui.searchController;
} catch(err) {
    log("SearchController doesn't exist");
}

你还可以检查GNOME shell版本,然后导入对应版本的命名空间:

const Config = imports.misc.config;
const [major] = Config.PACKAGE_VERSION.split('.');
const shellVersion = Number.parseInt(major);

const SearchController = (shellVersion >= 40) ? imports.ui.searchController : null;

if (!SearchController) {
    log("SearchController doesn't exist");
}

顶部面板

GNOME 40 从顶部面板中删除了弹出菜单箭头。 虽然不推荐,但如果你想添加一个箭头图标,代码仍然存在

ui.popupMenu.arrowIcon()https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/40e22eb524556e63efa4285c1a9d8f872a507499/js/ui/popupMenu.js#L34)

Overview (概览) Elements

GNOME Shell 40 中的大多数更改都与概览有关。 如果您的扩展程序正在使用概览元素执行某些操作,您将需要适应这些更改。

这里有点多,直接贴官方链接吧: https://gjs.guide/extensions/upgrading/gnome-shell-40.html#overview-elements

Prefs 偏好设置

这部分改动应该是最大的。

GNOME Shell 40 现在使用 GTK4 作为扩展首选项。 这意味着您需要将 prefs.js UI 升级到 GTK4。

强烈建议使用模板文件 (.ui) 创建 GTK UI。 这种结构将后端和前端代码解耦,使您的工作更易于升级和更易于维护。

如果您当前没有使用模板文件,将您当前的 UI 移动到模板文件,然后将其移植到 GTK4 会更容易。 如果您不熟悉模板文件,您可以使用 Glade 创建您的模板文件(Glade 不支持 GTK4,但您可以使用 Glade 创建您的文件,然后将它们移植到 GTK4。有关说明,请参阅转换和验证模板文件)。

转换 gtk3 模板到 gtk4: https://gjs.guide/extensions/upgrading/gnome-shell-40.html#convert-and-validate-template-files

To check whether the .ui template file is compatible with GTK4 you can do this:

gtk4-builder-tool validate path_to_ui_file

You can also convert your file to GTK4:

gtk4-builder-tool simplify --3to4 path_to_ui_file 

If you want to replace the file with simplified version:

gtk4-builder-tool simplify --3to4 --replace path_to_ui_file

虽然构建器工具simple 可以帮助您将 gtk3 转换为 gtk4,但它无法转换所有内容。 您需要手动更改某些部分。

show_all and destroy

在 GTK4 中,默认情况下所有小部件都是可见的(顶层窗口、弹出窗口和对话框除外)。

不再需要在您的小部件中使用 show_all() 和 destroy 信号:

function buildPrefsWidget ()
{
    let widget = new MyPrefsWidget();
    // widget.show_all();
    // widget.connect('destroy', Gtk.main_quit);
    return widget;
}

Provide css file for prefs

GTK4 上没有任何样式属性。 您应该改用 css_classescss。 例如,如果您正在执行此操作

let label = new Gtk.Label({
    label: 'test',
    style: 'color: gold',
});

You can use the css_classes property:

let label = new Gtk.Label({
    label: 'test',
    css_classes: ['my-label'],
});

Now you need to create a css file (For example, pref.css) for your prefs window:

.my-label {
    color: gold;
}

And load it in your prefs.js file:

const Me = imports.misc.extensionUtils.getCurrentExtension();
const {Gtk, Gdk} = imports.gi;

let provider = new Gtk.CssProvider();

provider.load_from_path(Me.dir.get_path() + '/prefs.css');
Gtk.StyleContext.add_provider_for_display(
    Gdk.Display.get_default(),
    provider,
    Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);

No Packing

You don't need to use packing anymore. It means instead of:

<object class="GtkBox">
    <property name="visible">True</property>
    <property name="can-focus">False</property>
    <child>
        <placeholder/>
    </child>
</object>
<packing>
    <property name="expand">True</property>
    <property name="fill">True</property>
    <property name="position">0</property>
</packing>

You should use:

<object class="GtkBox">
    <property name="visible">True</property>
    <property name="can-focus">False</property>
    <property name="hexpand">1</property>
    <child>
        <placeholder/>
    </child>
</object>
  • For horizontal expand use hexpand.

  • For vertical expand use vexpand.

  • Don't need to use fill property since it will be true by default.

No margin-left and margin-right property

margin-left and margin-right property has been removed from GTK4.

You should use margin-start and margin-end instead.

关于这个改变,老灯感觉很奇怪。没有觉得 margin-start 会比 margin-left 直观。。。

shadow-type

shadow-type no longer exists in GTK4 for GtkFrame, GtkViewport and GtkScrolledWindow.

Just remove the shadow-type property

draw-value property on GtkScale

On GTK4, draw-value property for GtkScale is False by default. Set draw-value as True if you want to have draw value:

<object class="GtkScale">
    <property name="visible">True</property>
    <property name="can-focus">True</property>
    <property name="adjustment">scale_adjustment</property>
    <property name="round-digits">1</property>
    <property name="draw-value">True</property>
</object>

GtkScale 是什么呢? 是一个 用于从范围中选择值的滑块小部件 (slider widget)。

“draw-value” 属性解释: Whether the current value is displayed as a string next to the slider.

can-focus property

GTK4 与 GTK3 不同的是,在 GTK4 里,当您将对象的 can-focus 属性设置为 False 时,所有后代都无法再聚焦。 例如,这里 GtkToggleButton 不能被聚焦,因为 GtkBox 上的 can-focus 属性是 False

<object class="GtkBox">
    <property name="visible">True</property>
    <property name="can-focus">False</property>
    <property name="hexpand">1</property>
    <child>
        <object class="GtkToggleButton">
            <property name="visible">True</property>
            <property name="can-focus">True</property>
        </object>
    </child>
</object>

要解决这个问题,您需要将 GtkToggleButton 的父元素 GtkBoxcan-focus 属性设置为 True

<object class="GtkBox">
    <property name="visible">True</property>
    <property name="can-focus">True</property>
    <property name="hexpand">1</property>
    <child>
        <object class="GtkToggleButton">
            <property name="visible">True</property>
            <property name="can-focus">True</property>
        </object>
    </child>
</object>

这里有非常非常多的改变,如果你需要查看全部的内容,请移步 https://gjs.guide/extensions/upgrading/gnome-shell-40.html#prefs

Refs

https://fedoraproject.org/wiki/Changes/Gnome40

https://blogs.gnome.org/shell-dev/2021/03/20/extensions-rebooted-porting-your-existing-extensions-to-gnome-40/

https://gjs.guide/extensions/upgrading/gnome-shell-40.html

https://developer.gnome.org/gtk4/stable/

https://gjs-docs.gnome.org/gtk40/

https://developer.gnome.org/gtk4/stable/gtk-migrating-3-to-4.html