Loading...
Loading...
Build, debug, and maintain GNOME Shell extensions using GJS (GNOME JavaScript). Covers extension anatomy (metadata.json, extension.js, prefs.js, stylesheet.css), ESModule imports, GSettings preferences, popup menus, quick settings, panel indicators, dialogs, notifications, search providers, translations, and session modes. Use when the user wants to: (1) Create a new GNOME Shell extension, (2) Add UI elements like panel buttons, popup menus, quick settings toggles/sliders, or modal dialogs, (3) Implement extension preferences with GTK4/Adwaita, (4) Debug or test an extension, (5) Port an extension to a newer GNOME Shell version (45-49+), (6) Prepare an extension for submission to extensions.gnome.org, (7) Work with GNOME Shell internal APIs (Clutter, St, Meta, Shell, Main).
npx skill4agent add padparadscho/skills js-gnome-extensionsgnome-shellglobalextension.jsimport Clutter from "gi://Clutter";
import GObject from "gi://GObject";
import Gio from "gi://Gio";
import GLib from "gi://GLib";
import Meta from "gi://Meta";
import Shell from "gi://Shell";
import St from "gi://St";
import {
Extension,
gettext as _,
} from "resource:///org/gnome/shell/extensions/extension.js";
import * as Main from "resource:///org/gnome/shell/ui/main.js";
import * as PanelMenu from "resource:///org/gnome/shell/ui/panelMenu.js";
import * as PopupMenu from "resource:///org/gnome/shell/ui/popupMenu.js";
import * as QuickSettings from "resource:///org/gnome/shell/ui/quickSettings.js";prefs.jsimport Gdk from "gi://Gdk?version=4.0";
import Gtk from "gi://Gtk?version=4.0";
import Adw from "gi://Adw";
import Gio from "gi://Gio";
import {
ExtensionPreferences,
gettext as _,
} from "resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js";extension.jsprefs.jsmetadata.jsonextension.jsExtensionprefs.jsExtensionPreferencesstylesheet.cssschemas/locale/import { Extension } from "resource:///org/gnome/shell/extensions/extension.js";
export default class MyExtension extends Extension {
enable() {
// Create objects, connect signals, add UI
}
disable() {
// MUST undo everything done in enable():
// - Destroy all created widgets
// - Disconnect all signals
// - Remove all GLib.timeout/idle sources
// - Null out all references
}
}constructor()constructor()super(metadata)enable()disable()disable()session-modesunlock-dialogmetadata.json{
"uuid": "my-extension@example.com",
"name": "My Extension",
"description": "Does something useful",
"shell-version": ["47", "48", "49"],
"url": "https://github.com/user/my-extension"
}extension.jsimport St from "gi://St";
import { Extension } from "resource:///org/gnome/shell/extensions/extension.js";
import * as Main from "resource:///org/gnome/shell/ui/main.js";
import * as PanelMenu from "resource:///org/gnome/shell/ui/panelMenu.js";
export default class MyExtension extends Extension {
enable() {
this._indicator = new PanelMenu.Button(0.0, this.metadata.name, false);
const icon = new St.Icon({
icon_name: "face-laugh-symbolic",
style_class: "system-status-icon",
});
this._indicator.add_child(icon);
Main.panel.addToStatusArea(this.uuid, this._indicator);
}
disable() {
this._indicator?.destroy();
this._indicator = null;
}
}# Create extension directory
mkdir -p ~/.local/share/gnome-shell/extensions/my-extension@example.com
# Copy files there, then:
# Wayland: run nested session
dbus-run-session gnome-shell --devkit --wayland # GNOME 49+
dbus-run-session gnome-shell --nested --wayland # GNOME 48 and earlier
# Enable extension in nested session
gnome-extensions enable my-extension@example.com
# Watch logs
journalctl -f -o cat /usr/bin/gnome-shellenable() {
this._handlerId = someObject.connect('some-signal', () => { /* ... */ });
}
disable() {
if (this._handlerId) {
someObject.disconnect(this._handlerId);
this._handlerId = null;
}
}enable() {
this._timeoutId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 5, () => {
// do work
return GLib.SOURCE_CONTINUE; // or GLib.SOURCE_REMOVE
});
}
disable() {
if (this._timeoutId) {
GLib.Source.remove(this._timeoutId);
this._timeoutId = null;
}
}enable() {
this._settings = this.getSettings(); // uses metadata settings-schema
this._settings.bind('show-indicator', this._indicator, 'visible',
Gio.SettingsBindFlags.DEFAULT);
}
disable() {
this._settings = null;
}import {
Extension,
InjectionManager,
} from "resource:///org/gnome/shell/extensions/extension.js";
import { Panel } from "resource:///org/gnome/shell/ui/panel.js";
export default class MyExtension extends Extension {
enable() {
this._injectionManager = new InjectionManager();
this._injectionManager.overrideMethod(
Panel.prototype,
"toggleCalendar",
(originalMethod) => {
return function (...args) {
console.debug("Calendar toggled!");
originalMethod.call(this, ...args);
};
},
);
}
disable() {
this._injectionManager.clear();
this._injectionManager = null;
}
}cd ~/.local/share/gnome-shell/extensions/my-extension@example.com
gnome-extensions pack --podir=po --extra-source=utils.js .
# GNOME 49+: upload directly
gnome-extensions upload --accept-tos