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.BuilderScope
to 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-devel
You 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.ui
Use 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.ui
CSS 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);
});
prefsWidget
is 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_width
anddefault_height
can 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.svg
Just 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);
});
prefsWidget
is the main widget you are returning inbuildPrefsWidget()
function.headerBar
is 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 yourGtkHeaderBar
to 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
GtkLabel
withtitle
class is the window title and the secondGtkLabel
withsubtitle
class is the window sub title. - If you want to change the sub title, you can change the label with
.set_label("New Sub Title")
onsubtitle
object.
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>
menu
is a toplevel element contains at least oneitem
element.submenu
has the same content model asmenu
element.
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);
});
prefsWidget
is the main widget you are returning inbuildPrefsWidget()
function.- We are adding all actions to the window with
GtkWindow.insert_action_group()
function becauseGtkWindow
inheritedGtkWidget
.
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
):
Single
has been used for overview and shows side workspaces partially.Full
means 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.