Port Extensions to GNOME Shell 40
Metadata
shell-version
To make your extension available of GNOME Shell 40, add "40" to the shell-version field in metadata.json.
The example below indicates the extension supports GNOME Shell 40 and 3.36:
{
"name": "Extension Name",
"description": "Extension Description",
"shell-version": ["3.36", "40"],
"url": "",
"uuid": "example@example"
}There is a difference between GNOME Shell 40 and older versions. While older GNOME Shell versions treating local and stable versions differently, GNOME Shell 40 treats them the same.
It means if your metadata.json is like this:
"shell-version": ["40"],These GNOME Shell versions are also supported in both local and stable versions:
- 40.alpha
- 40.beta
- 40.rc
Extension
TIP
There were no relevant changes to extension.js in GNOME 40.
Preferences
TIP
In GNOME 40 prefs.js must use GTK4.
GNOME Shell 40 now uses GTK4 for extension preferences. This means you need to upgrade your prefs.js UI to GTK4.
It is highly recommended to create your GTK UI with template files (.ui). This structure decouples back-end and front-end code and makes your work easier to upgrade and more maintainable.
If you are not currently using template files, It is easier to move your current UI to a template file and then port it to GTK4. If you are new to template files you can create your template files with Glade (Glade doesn't support GTK4 but you can create your files with Glade and then port them to GTK4. See Convert and Validate Template Files for instructions).
Interface XML for Preferences
GTK supports defining user interfaces with XML. The preferred method is using template widgets to define widget subclasses, but Gtk.Builder may also be used to retrieve objects.
Template Widgets
Using template widgets requires an XML file defining the widget UI and parent class:
<?xml version="1.0" encoding="UTF-8"?>
<interface domain="my-gettext-domain">
<template class="PrefsWidget" parent="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkButton" id="clickButton">
<property name="label" translatable="yes">Click Me!</property>
<signal name="clicked" handler="_onButtonClicked" swapped="no"/>
</object>
</child>
</template>
</interface>The template is then loaded in prefs.js by specifying the template XML in the widget subclass:
const {GObject, Gtk} = imports.gi;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const PrefsWidget = GObject.registerClass({
GTypeName: 'PrefsWidget',
Template: Me.dir.get_child('prefs.ui').get_uri(),
}, class PrefsWidget extends Gtk.Box {
_init(params = {}) {
super._init(params);
}
_onButtonClicked(button) {
button.set_label('Clicked!');
}
});
function init() {
ExtensionUtils.initTranslations('my-gettext-domain');
}
function buildPrefsWidget() {
return new PrefsWidget();
}GtkBuilder
Widgets may also be used by loading the interface XML with Gtk.Builder and retrieving objects by ID:
<object class="GtkButton">
<property name="label" translatable="yes">button</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<signal name="clicked" handler="on_btn_click" swapped="no"/>
</object>In this case you will have to create a custom Gtk.BuilderScope to connect the signals manually:
const {Gtk, GObject} = imports.gi;
const Me = imports.misc.extensionUtils.getCurrentExtension();
const MyBuilderScope = GObject.registerClass({
Implements: [Gtk.BuilderScope],
}, class MyBuilderScope extends GObject.Object {
vfunc_create_closure(builder, handlerName, flags, connectObject) {
if (flags & Gtk.BuilderClosureFlags.SWAPPED)
throw new Error('Unsupported template signal flag "swapped"');
if (typeof this[handlerName] === 'undefined')
throw new Error(`${handlerName} is undefined`);
return this[handlerName].bind(connectObject || this);
}
on_btn_click(connectObject) {
connectObject.set_label("Clicked");
}
});
function init () {}
function buildPrefsWidget () {
let builder = new Gtk.Builder();
builder.set_scope(new MyBuilderScope());
builder.set_translation_domain('gettext-domain');
builder.add_from_file(Me.dir.get_path() + '/prefs.ui');
return builder.get_object('main_widget');
}Tips
- Use a unique class name for the
Gtk.BuilderScopeto avoid conflicting with other extensions. - Define a method for every signal handlers declared in the interface XML.
- Set the scope with
Gtk.Builder.prototype.set_scope()before loading the interface XML.
Convert and Validate Interface XML
WARNING
gtk4-builder-tool can simplify converting GTK3 XML to GTK4 XML, but the process may need some manual intervention.
First, Install gtk4-builder-tool:
sudo dnf install gtk4-develYou can use the validate command to check whether an XML interface file is compatible with GTK4:
gtk4-builder-tool validate ~/Projects/example@gjs.guide/prefs.uiUse the simplify command to covert an XML interface file: You can also convert your file to GTK4:
# Print to stdout
gtk4-builder-tool simplify --3to4 ~/Projects/example@gjs.guide/prefs.ui
# Overwrite the input file
gtk4-builder-tool simplify --3to4 --replace ~/Projects/example@gjs.guide/prefs.uiCSS for Preferences
The style property has been removed from Gtk.Widget and the css-classes property should be used instead. For example, if you are doing this:
let label = new Gtk.Label({
label: 'test',
style: 'color: gold',
});You should replace this usage by defining a CSS class for your widget:
let label = new Gtk.Label({
label: 'test',
css_classes: ['my-label'],
});This CSS class should be defined in a CSS file (e.g. prefs.css):
.my-label {
color: gold;
}The prefs.js file should then create a new Gtk.CssProvider to load the CSS for your preferences:
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);Changes to GTK
GNOME Shell extensions now use GTK4 in prefs.js, so it may be useful to check the current GTK version if supporting older versions.
const {Gtk} = imports.gi;
const gtkVersion = Gtk.get_major_version();
log(`GTK version is ${gtkVersion}`);show_all() and destroy()
In GTK4 all widgets are visible by default (except toplevel windows, popovers and dialogs).
There isn't any need to use show_all() and destroy signal in your widget anymore:
function buildPrefsWidget ()
{
let widget = new MyPrefsWidget();
// widget.show_all();
// widget.connect('destroy', Gtk.main_quit);
return widget;
}Widgets Tree Navigation
On GTK4 you can navigate the widgets tree with these functions:
| Navigate To | Function |
|---|---|
| first | Gtk.Widget.get_first_child() |
| last | Gtk.Widget.get_last_child() |
| next | Gtk.Widget.get_next_sibling() |
| previous | Gtk.Widget.get_prev_sibling() |
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
The margin-left and margin-right properties have been replaced by margin-start and margin-end, which support LTR and RTL languages.
For example, instead of:
<property name="margin-left">5</property>
<property name="margin-right">5</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>You should use:
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>shadow-type
shadow-type no longer exists in GTK4 for GtkFrame, GtkViewport and GtkScrolledWindow.
For example, if you are using shadow-type in GtkFrame:
<object class="GtkFrame">
<property name="can-focus">True</property>
<property name="shadow-type">in</property>
</object>Just remove the shadow-type property:
<object class="GtkFrame">
<property name="can-focus">True</property>
</object>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>Get Prefs Window and Resize it
If you want to see the template structure for prefs window dialog you can see the dbusServices in GNOME Shell source code.
In GNOME 40 you can get the window like this:
prefsWidget.connect('realize', () => {
let window = prefsWidget.get_root();
window.default_width = 700;
window.default_height = 900;
// window.resize(700, 900);
});prefsWidgetis the main widget you are returning inbuildPrefsWidget()function.- Instead of
get_toplevel()you need to useget_root()to have prefs's top level window. resize()doesn't exist in GTK4 anymore.default_widthanddefault_heightcan change the default prefs window size.
Custom Icon Theme
Gtk.IconTheme no longer has get_default(). If you want to add your custom icon theme, use get_for_display() instead.
For example, if you are using myextension-logo-symbolic as your custom icon:
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">myextension-logo-symbolic</property>
<property name="icon-size">1</property>
</object>And you have the symbolic file here:
extension@extensionfolder
└── icons
└── hicolor
└── scalable
└── categories
└── myextension-logo-symbolic.svgJust use add_search_path like this:
const {Gtk, Gdk} = imports.gi;
const Me = imports.misc.extensionUtils.getCurrentExtension();
let iconPath = Me.dir.get_child("icons").get_path();
let iconTheme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default());
iconTheme.add_search_path(iconPath);can-focus property
Unlike GTK3, when you set can-focus property as False for an object, all the descendants no longer can be focused. For example, here GtkToggleButton cannot be focused, because can-focus property on GtkBox is 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>To fix that you need to set can-focus property as True for GtkBox:
<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>Adding to the Containers
In GTK4 we have class specific add functions instead of general add coming from GtkContainer.
These are the alternatives for adding to the containers:
| Gtk Widget | Alternative |
|---|---|
| GtkActionBar | .pack_start() and .pack_end() |
| GtkAspectFrame | .set_child() |
| GtkBox | .prepend() and .append() |
| GtkButton | .set_child() |
| GtkComboBox | .set_child() |
| GtkExpander | .set_child() |
| GtkFixed | .put() |
| GtkFlowBox | .insert() |
| GtkFlowBoxChild | .set_child() |
| GtkFrame | .set_child() |
| GtkGrid | .attach() |
| GtkHeaderBar | .pack_start() and .pack_end() |
| GtkInfoBar | .add_child() |
| GtkListBox | .insert() |
| GtkListBoxRow | .set_child() |
| GtkNotebook | .append_page() |
| GtkOverlay | .set_child() |
| GtkPaned | .set_start_child() and .set_end_child() |
| GtkPopover | .set_child() |
| GtkRevealer | .set_child() |
| GtkSearchBar | .set_child() |
| GtkStack | .add_child() |
| GtkScrolledWindow | .set_child() |
| GtkTextView | .add_child_at_anchor() and .add_overlay() |
| GtkViewport | .set_child() |
| GtkWindow | .set_child() |
For example, if you are doing this with GtkBox:
let hBox = new Gtk.Box();
hBox.set_orientation(Gtk.Orientation.HORIZONTAL);
hBox.pack_start(myLabel, false, false, 0);
hBox.pack_end(myLabel2, false, false, 0);You should use prepend and append instead of pack_start and pack_end:
let hBox = new Gtk.Box();
hBox.set_orientation(Gtk.Orientation.HORIZONTAL);
hBox.prepend(myLabel);
hBox.append(myLabel2);And if you want to remove myLabel from hBox you can use remove:
let hBox = new Gtk.Box();
hBox.set_orientation(Gtk.Orientation.HORIZONTAL);
hBox.prepend(myLabel);
hBox.remove(myLabel1);-gtk-gradient
-gtk-gradient is no longer supported in GTK4. Use the standard CSS gradient instead. For example, if you are doing this:
.my-class {
background-image: -gtk-gradient (
linear,
left top,
right bottom,
from(@yellow),
to(@blue));
}Use linear-gradient instead:
.my-class {
background-image: linear-gradient(to right bottom, yellow, blue);
}GtkHeaderBar
On GTK4, these GtkHeaderBar functions have new names:
| GTK3 | GTK4 |
|---|---|
GtkHeaderBar.set_show_close_button() | GtkHeaderBar.set_show_title_buttons() |
GtkHeaderBar.set_custom_title() | GtkHeaderBar.set_title_widget() |
For example:
prefsWidget.connect('realize', () => {
let window = prefsWidget.get_root();
let headerBar = new Gtk.HeaderBar({show_title_buttons: false});
// use this instead of headerBar.set_show_close_button(true);
headerBar.set_show_title_buttons(true);
window.set_titlebar(headerBar);
});prefsWidgetis the main widget you are returning inbuildPrefsWidget()function.headerBaris the GtkHeaderBar widget you want to have it on window.- The default value for
set_show_title_buttons()istrue. You don't need to use it if you want to have title button. - With
set_titlebar()you can add yourGtkHeaderBarto the window just like before.
GtkHeaderBar sub title
GTK4 removed set_subtitle() function from GtkHeaderBar. If you want to have the old subtitle behavior, you can use title-widget property with title and subtitle class like this:
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<object class="GtkHeaderBar">
<property name="title-widget">
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="vexpand">1</property>
<property name="valign">center</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel">
<property name="label" translatable="yes">Title</property>
<property name="single-line-mode">True</property>
<property name="ellipsize">end</property>
<property name="width-chars">10</property>
<style>
<class name="title"/>
</style>
</object>
</child>
<child>
<object class="GtkLabel" id="subtitle">
<property name="label" translatable="yes">Sub Title</property>
<property name="single-line-mode">True</property>
<property name="ellipsize">end</property>
<property name="width-chars">10</property>
<style>
<class name="subtitle"/>
</style>
</object>
</child>
</object>
</property>
</object>
</interface>- First
GtkLabelwithtitleclass is the window title and the secondGtkLabelwithsubtitleclass is the window sub title. - If you want to change the sub title, you can change the label with
.set_label("New Sub Title")onsubtitleobject.
GtkFileChooserButton
GtkFileChooserButton has been removed and no longer exist. You can use GtkButton and GtkFileChooserNative to have the same behavior.
For example, if your prefs.ui file is like this:
<?xml version="1.0" encoding="UTF-8"?>
<interface domain="my-gettext-domain">
<template class="PrefsWidget" parent="GtkBox">
<property name="orientation">vertical</property>
<child>
<object class="GtkButton" id="file_chooser_button">
<property name="halign">end</property>
<property name="valign">center</property>
<property name="label" translatable="yes">Open</property>
<signal name="clicked" handler="_onBtnClicked" swapped="no"/>
</object>
</child>
</template>
<object class="GtkFileChooserNative" id="file_chooser">
<property name="title" translatable="yes">File Chooser Title</property>
<property name="select-multiple">0</property>
<property name="action">open</property>
<property name="modal">1</property>
<signal name="response" handler="_onFileChooserResponse" swapped="no"/>
</object>
</interface>You can do this in your prefs.js file:
const {GObject, Gtk} = imports.gi;
const Me = imports.misc.extensionUtils.getCurrentExtension();
const PrefsWidget = GObject.registerClass({
GTypeName: 'PrefsWidget',
Template: Me.dir.get_child('prefs.ui').get_uri(),
InternalChildren: [
'file_chooser',
'file_chooser_button',
],
}, class PrefsWidget extends Gtk.Box {
_init(params = {}) {
super._init(params);
}
_onBtnClicked(btn) {
let parent = btn.get_root();
this._file_chooser.set_transient_for(parent);
this._file_chooser.show();
}
_onFileChooserResponse(native, response) {
if (response !== Gtk.ResponseType.ACCEPT) {
return;
}
let fileURI = native.get_file().get_uri();
this._file_chooser_button.set_label(fileURI);
}
});
function init() {}
function buildPrefsWidget() {
return new PrefsWidget();
}GtkPasswordEntry
GTK4 has new entry for password. If you are using the entry for password, you should use GtkPasswordEntry:
<object class="GtkPasswordEntry">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="show-peek-icon">1</property>
<property name="placeholder-text" translatable="yes">Text</property>
</object>show-peek-icon is for toggle button to show the entry content in clear text.
Menu and Sub Menu
GTK4 no longer has GtkMenu, GtkMenuBar and GtkMenuItem. You can use these alternatives with GtkMenuButton:
| GTK3 | GTK4 Alternative |
|---|---|
| GtkMenu | GtkPopoverMenu |
| GtkMenuBar | GtkPopoverMenuBar |
Or you can directly use toplevel menu element.
menu and submenu elements
You can use toplevel menu element with GtkMenuButton:
<?xml version="1.0" encoding="UTF-8"?>
<interface domain="my-gettext-domain">
<menu id="mymenu">
<section>
<attribute name='label' translatable='yes'>Section Header</attribute>
<submenu>
<attribute name='label' translatable='yes'>Others</attribute>
<section>
<item>
<attribute name='label' translatable='yes'>Test</attribute>
<attribute name='action'>mygroup.test</attribute>
</item>
</section>
</submenu>
<item>
<attribute name='label' translatable='yes'>Menu Item</attribute>
<attribute name='action'>mygroup.other</attribute>
</item>
</section>
</menu>
<template class="PrefsWidget" parent="GtkBox">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="vexpand">1</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkMenuButton">
<property name="receives-default">True</property>
<property name="menu-model">mymenu</property>
<property name="icon-name">open-menu-symbolic</property>
</object>
</child>
</object>
</child>
</template>
</interface>menuis a toplevel element contains at least oneitemelement.submenuhas the same content model asmenuelement.
And now on prefs you can connect the signals like this:
prefsWidget.connect('realize', () => {
let window = prefsWidget.get_root();
let actionGroup = new Gio.SimpleActionGroup();
let action = new Gio.SimpleAction({ name: 'other' });
action.connect('activate', () => {
log('Other Clicked');
});
actionGroup.add_action(action);
let action2 = new Gio.SimpleAction({ name: 'test' });
action2.connect('activate', () => {
log('Test Clicked');
});
actionGroup.add_action(action2);
window.insert_action_group('mygroup', actionGroup);
});prefsWidgetis the main widget you are returning inbuildPrefsWidget()function.- We are adding all actions to the window with
GtkWindow.insert_action_group()function becauseGtkWindowinheritedGtkWidget.
GtkPopoverMenu
While GtkMenuButton accepts menu-model you can alternativly create your own GtkPopoverMenu with menu. For example, if your ui file is like this:
<?xml version="1.0" encoding="UTF-8"?>
<interface domain="my-gettext-domain">
<menu id="mymenu">
<item>
<attribute name='label' translatable='yes'>Menu Item</attribute>
</item>
</menu>
<template class="PrefsWidget" parent="GtkBox">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="vexpand">1</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkMenuButton" id="menubtn">
<property name="receives-default">True</property>
<property name="icon-name">open-menu-symbolic</property>
</object>
</child>
</object>
</child>
</template>
</interface>You can create a GtkPopoverMenu with mymenu menu model and assign the GtkPopoverMenu to GtkMenuButton:
const {GObject, Gtk} = imports.gi;
const Me = imports.misc.extensionUtils.getCurrentExtension();
const PrefsWidget = GObject.registerClass({
GTypeName: 'PrefsWidget',
Template: Me.dir.get_child('prefs.ui').get_uri(),
InternalChildren: [
'mymenu',
'menubtn',
],
}, class PrefsWidget extends Gtk.Box {
_init(params = {}) {
super._init(params);
this._popoverMenu = Gtk.PopoverMenu.new_from_model(this._mymenu);
this._menubtn.set_popover(this._popoverMenu);
}
});
function init() {}
function buildPrefsWidget() {
return new PrefsWidget();
}You should use GtkPopover if you want to have other widgets inside your menu.
Icon Size
GTK4 only supports normal and large icon size:
| const | icon-size property value |
|---|---|
| GTK_ICON_SIZE_INHERIT | 0 |
| GTK_ICON_SIZE_NORMAL | 1 |
| GTK_ICON_SIZE_LARGE | 2 |
For example, if you are using normal size for icon-size, it should have 1 as property value:
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="icon-name">system-search-symbolic</property>
<property name="icon-size">1</property>
</object>Also stock-size property of GtkCellRendererPixbuf is now icon-size.
Show and Hide with Animation
.show() and .hide() no longer make the revealing elements appear and disappear with animation. To do that, you need to use these:
| Widget | Animate Show | Animate Hide |
|---|---|---|
| GtkRevealer | GtkRevealer.set_reveal_child(true) | GtkRevealer.set_reveal_child(false) |
| GtkPopover | GtkPopover.popup() | GtkPopover.popdown() |
| GtkInfoBar | GtkInfoBar.set_revealed(true) | GtkInfoBar.set_revealed(false) |
GtkPicture
GtkPicture is a new widget on GTK4. It's better to use GtkImage for icons and GtkPicture for other images.
For example, you can create GtkPicture element in your JavaScript code like this:
const {Gtk} = imports.gi;
const Me = imports.misc.extensionUtils.getCurrentExtension();
let picture = Gtk.Picture.new_for_filename(Me.dir.get_path() + "/image.png");
picture.set_size_request(800, 600);GtkMenuButton
GtkMenuButton no longer supports GtkImage as its icon.
For example, GtkImage will be ignored if you are doing this:
<object class="GtkMenuButton">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="focus-on-click">True</property>
<property name="receives-default">True</property>
<property name="popover">popwidget</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">open-menu-symbolic</property>
<property name="icon-size">1</property>
</object>
</child>
</object>GtkMenuButton's default icon is arrow and that will be shown instead of open-menu-symbolic.
To fix that, you can use icon-name and icon-size properties inside GtkMenuButton:
<object class="GtkMenuButton">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="focus-on-click">True</property>
<property name="receives-default">True</property>
<property name="popover">popwidget</property>
<property name="icon-name">open-menu-symbolic</property>
<property name="icon-size">1</property>
</object>EventControllerKey
add_controller() is a new function in Gtk.Widget and helps you to add controller to the widget.
For example, if your prefs.ui is like this:
<?xml version="1.0" encoding="UTF-8"?>
<interface domain="my-gettext-domain">
<template class="PrefsWidget" parent="GtkBox">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="log_label">
<property name="label">Key Log</property>
</object>
</child>
</template>
<object class="GtkEventControllerKey" id="event_key_controller">
<signal name="key-pressed" handler="_onKeyPressed" swapped="no"/>
</object>
</interface>You can add the GtkEventControllerKey to the prefs window like this:
const {GObject, Gtk, Gdk} = imports.gi;
const Me = imports.misc.extensionUtils.getCurrentExtension();
const PrefsWidget = GObject.registerClass({
GTypeName: 'PrefsWidget',
Template: Me.dir.get_child('prefs.ui').get_uri(),
InternalChildren: [
'log_label',
'event_key_controller',
],
}, class PrefsWidget extends Gtk.Box {
_init(params = {}) {
super._init(params);
this.connect('realize', () => {
let window = this.get_root();
window.add_controller(this._event_key_controller);
});
}
_onKeyPressed(widget, keyval, keycode, state) {
let mask = state & Gtk.accelerator_get_default_mod_mask();
let binding = Gtk.accelerator_name_with_keycode(
null, keyval, keycode, mask);
this._log_label.set_label('Binding is: ' + binding);
return Gdk.EVENT_STOP;
}
});
function init() {}
function buildPrefsWidget() {
return new PrefsWidget();
}Gtk.EventControllerKey.new() no longer accepts any parameters. For example, if you are creating EventControllerKey in your js file, you need to do this to have the same result:
const {Gtk, Gdk} = imports.gi;
prefsWidget.connect('realize', () => {
let window = prefsWidget.get_root();
let evck = Gtk.EventControllerKey.new();
window.add_controller(evck);
evck.connect('key-pressed', (widget, keyval, keycode, state) => {
let mask = state & Gtk.accelerator_get_default_mod_mask();
let binding = Gtk.accelerator_name_with_keycode(
null, keyval, keycode, mask);
log('Binding is: ' + binding);
return Gdk.EVENT_STOP;
});
});prefsWidget is the main widget you are returning in buildPrefsWidget() function.
Gtk.accelerator_parse()
Gtk.accelerator_parse() function in GTK4 returns three elements instead of two. For example, if you are doing this:
let [key, mods] = Gtk.accelerator_parse('<Control>a');The first element is boolean and means whether the parse result is ok:
let [ok, key, mods] = Gtk.accelerator_parse('<Control>a');Gtk.Radiobutton
Gtk.RadioButton no longer exists in GTK4. To have the same behavior you can use GtkToggleButton with group property.
For example:
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">True</property>
<child>
<object class="GtkToggleButton" id="toggle_btn1">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="label" translatable="yes">First</property>
<property name="active">1</property>
</object>
</child>
<child>
<object class="GtkToggleButton" id="toggle_btn2">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="label" translatable="yes">Second</property>
<property name="group">toggle_btn1</property>
</object>
</child>
<style>
<class name="linked"/>
</style>
</object>linked style class makes two toggle buttons attached together.
GNOME Shell
Checking GNOME Shell Version
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');Conditional Imports
Imports have been both added and removed in GNOME Shell 40. If you only want to import when the namespace exist, you can use a try..catch block:
try {
const SearchController = imports.ui.searchController;
} catch(err) {
log("SearchController doesn't exist");
}You can also check GNOME Shell version and then import the namespace:
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");
}Top Panel
GNOME 40 removed popup menu arrows from the top panel. While not recommended, if you want to add an arrow icon the code still exists in ui.popupMenu.arrowIcon().
Overview
Most changes in GNOME Shell 40 are related to the overview. If your extension is doing something with overview elements, you will need to adapt to these changes.
Search Entry
| Type | Where |
|---|---|
| Direct Access | ui.main.overview.searchEntry |
| Created In | ui.overviewControls.ControlsManager |
| Style Class | .search-entry |
| Controller | ui.searchController.SearchController |
ui.viewSelector has been renamed to ui.searchController in GNOME Shell 40. It controls the search entry behavior.
Workspace Thumbnails
Since workspace thumbnails won't be shown in single workspace ui.workspaceThumbnail.ThumbnailsBox has should-show property.
You can change the workspace thumbnails size with ui.workspaceThumbnail.MAX_THUMBNAIL_SCALE.
| Type | Where |
|---|---|
| Created In | ui.overviewControls.ControlsManager |
| Style Class | .workspace-thumbnails as wrapper, workspace-thumbnail-indicator as selected workspace thumbnail |
Workspace View
Workspace View has two fit modes (ui.workspacesView.FitMode):
Singlehas been used for overview and shows side workspaces partially.Fullmeans all the workspaces in one line (like app grid).
| Type | Where |
|---|---|
| Created In | ui.overviewControls.ControlsManager |
| Style Class | .workspaces-view |
Window Preview
ui.windowPreview is the new namespace that is used for window previews in workspaces view.
Window previews no longer have the .window-clone-border style class name for borders but you can still use the .window-caption and .window-close style class names.
| Type | Where |
|---|---|
| Created In | ui.workspace.Workspace |
| Style Class | .window-caption for captions, .window-close for close |
Window Preview Icon
The new window preview icon is part of ui.windowPreview.WindowPreview and will be added to the window preview when each ui.windowPreview.WindowPreview instance is going to be created.
| Type | Where |
|---|---|
| Created In | ui.windowPreview.WindowPreview |
Dash
| Type | Where |
|---|---|
| Direct Access | ui.main.overview.dash |
| Created In | ui.overviewControls.ControlsManager |
| Origin | ui.dash.Dash |
| Style Class | #dash for the wrapper, .dash-background for the background |
| Controller | ui.dash |
#dash style reference no longer points to the wrapper. It is the entire bottom area of the screen and it is transparent. If you want to style the dash background you should use .dash-background instead.
Applications Grid
App grid is under workspaces view and above the dash. Workspaces view goes to the full fit mode when you enter into the app grid (ui.overviewControls.ControlsState.APP_GRID).
| Type | Where |
|---|---|
| Created In | ui.overviewControls.ControlsManager |
| Style Class | .icon-grid, .apps-scroll-view as wrapper |
GJS
TIP
There were no relevant changes to GJS in GNOME 40.