js-gnome-extensions
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseGNOME Shell Extensions
GNOME Shell 扩展
Build extensions for GNOME Shell 45+ using GJS with ESModules.
使用支持ESModules的GJS为GNOME Shell 45+版本构建扩展。
Key Resources
核心资源
- Public APIs (GJS Docs): https://gjs-docs.gnome.org/
- GNOME Shell UI Source: https://gitlab.gnome.org/GNOME/gnome-shell/-/tree/main/js/ui
- Extensions Guide: https://gjs.guide/extensions/
- Review Guidelines: https://gjs.guide/extensions/review-guidelines/review-guidelines.html
Architecture Overview
架构概述
Extensions run inside the process using Clutter/St toolkits (not GTK).
Preferences run in a separate GTK4/Adwaita process.
gnome-shellLibrary stack (bottom-up):
- Clutter — Actor-based toolkit, layout managers, animations
- St — Shell Toolkit: buttons, icons, labels, entries, scroll views (CSS-styleable)
- Meta (Mutter) — displays, workspaces, windows, keybindings
- Shell — object, app tracking, utilities
global - js/ui/ — GNOME Shell JS modules (Main, Panel, PopupMenu, QuickSettings, etc.)
Import conventions in :
extension.jsjs
import 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";Import conventions in :
prefs.jsjs
import 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";Critical rule: Never import GTK/Gdk/Adw in . Never import Clutter/Meta/St/Shell in .
extension.jsprefs.js扩展运行在进程内,使用Clutter/St工具包(而非GTK)。偏好设置则在独立的GTK4/Adwaita进程中运行。
gnome-shell库栈(从底层到上层):
- Clutter — 基于Actor的工具包,包含布局管理器、动画功能
- St — Shell工具包:按钮、图标、标签、输入框、滚动视图(支持CSS样式)
- Meta(Mutter) — 显示器、工作区、窗口、快捷键绑定
- Shell — 对象、应用追踪、工具函数
global - js/ui/ — GNOME Shell JS模块(Main、Panel、PopupMenu、QuickSettings等)
在中的导入约定:
extension.jsjs
import 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.jsjs
import 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";重要规则:永远不要在中导入GTK/Gdk/Adw。永远不要在中导入Clutter/Meta/St/Shell。
extension.jsprefs.jsExtension Lifecycle
扩展生命周期
Required Files
必备文件
- — UUID, name, description, shell-version, url
metadata.json - — Default export: subclass of
extension.jsExtension
- — 包含UUID、名称、描述、shell版本、网址
metadata.json - — 默认导出:
extension.js的子类Extension
Optional Files
可选文件
- — Subclass of
prefs.js(GTK4/Adwaita)ExtensionPreferences - — CSS for St widgets in gnome-shell (not prefs)
stylesheet.css - — GSettings schema XML + compiled binary
schemas/ - — Gettext translation .mo files
locale/
- —
prefs.js的子类(基于GTK4/Adwaita)ExtensionPreferences - — 用于gnome-shell中St组件的CSS样式(不适用于偏好设置界面)
stylesheet.css - — GSettings模式XML文件及编译后的二进制文件
schemas/ - — Gettext翻译.mo文件
locale/
enable()/disable() Contract
enable()/disable() 契约
js
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
}
}Rules (enforced by extension review):
- Do NOT create GObject instances or connect signals in
constructor() - may only set up static data (RegExp, Map, etc.) and call
constructor()super(metadata) - Everything created in MUST be cleaned up in
enable()disable() - is called on lock screen (unless
disable()includessession-modes)unlock-dialog
js
import { Extension } from "resource:///org/gnome/shell/extensions/extension.js";
export default class MyExtension extends Extension {
enable() {
// 创建对象、连接信号、添加UI
}
disable() {
// 必须撤销enable()中完成的所有操作:
// - 销毁所有创建的组件
// - 断开所有信号连接
// - 移除所有GLib.timeout/idle源
// - 清空所有引用
}
}规则(扩展审核强制执行):
- 不要在中创建GObject实例或连接信号
constructor() - 仅可设置静态数据(正则表达式、Map等)并调用
constructor()super(metadata) - 在中创建的所有内容必须在
enable()中清理disable() - 锁屏时会调用(除非
disable()包含session-modes)unlock-dialog
Reference Files
参考文件
Detailed documentation is split into reference files. Read the appropriate file based on the task:
- references/review-guidelines.md — Complete review rules for extensions.gnome.org submission. Read before submitting or when reviewing extension code for compliance.
- references/ui-patterns.md — Panel indicators, popup menus, quick settings (toggles, sliders, menus), dialogs, notifications, and search providers with complete code examples. Read when building any UI component.
- references/preferences.md — GSettings schemas, prefs.js with GTK4/Adwaita, settings binding. Read when implementing extension preferences.
- references/development.md — Getting started, testing, debugging, translations, InjectionManager, and packaging. Read when setting up a new extension or debugging.
- references/porting-guide.md — Breaking changes for GNOME Shell 45–49. Read when porting an extension to a newer version.
详细文档分为多个参考文件。请根据任务阅读对应的文件:
- references/review-guidelines.md — 提交至extensions.gnome.org的完整审核规则。提交前或审核扩展代码合规性时阅读。
- references/ui-patterns.md — 面板指示器、弹出菜单、快速设置(开关、滑块、菜单)、对话框、通知和搜索提供程序的完整代码示例。构建任何UI组件时阅读。
- references/preferences.md — GSettings模式、基于GTK4/Adwaita的prefs.js、设置绑定。实现扩展偏好设置时阅读。
- references/development.md — 入门指南、测试、调试、翻译、InjectionManager和打包。设置新扩展或调试时阅读。
- references/porting-guide.md — GNOME Shell 45–49版本的破坏性变更。将扩展移植到新版本时阅读。
Quick Start: Panel Indicator Extension
快速入门:面板指示器扩展
Minimal working extension with a panel icon:
带面板图标的最简可用扩展:
metadata.json
metadata.jsonmetadata.json
metadata.jsonjson
{
"uuid": "my-extension@example.com",
"name": "My Extension",
"description": "Does something useful",
"shell-version": ["47", "48", "49"],
"url": "https://github.com/user/my-extension"
}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.js
extension.jsextension.js
extension.jsjs
import 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;
}
}js
import 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;
}
}Install & Test
安装与测试
sh
undefinedsh
undefinedCreate extension directory
创建扩展目录
mkdir -p ~/.local/share/gnome-shell/extensions/my-extension@example.com
mkdir -p ~/.local/share/gnome-shell/extensions/my-extension@example.com
Copy files there, then:
将文件复制到该目录,然后执行:
Wayland: run nested session
Wayland:运行嵌套会话
dbus-run-session gnome-shell --devkit --wayland # GNOME 49+
dbus-run-session gnome-shell --nested --wayland # GNOME 48 and earlier
dbus-run-session gnome-shell --devkit --wayland # GNOME 49+
dbus-run-session gnome-shell --nested --wayland # GNOME 48及更早版本
Enable extension in nested session
在嵌套会话中启用扩展
gnome-extensions enable my-extension@example.com
gnome-extensions enable my-extension@example.com
Watch logs
查看日志
journalctl -f -o cat /usr/bin/gnome-shell
undefinedjournalctl -f -o cat /usr/bin/gnome-shell
undefinedCommon Patterns Cheatsheet
常见模式速查表
Connect a signal (and clean up)
连接信号(并清理)
js
enable() {
this._handlerId = someObject.connect('some-signal', () => { /* ... */ });
}
disable() {
if (this._handlerId) {
someObject.disconnect(this._handlerId);
this._handlerId = null;
}
}js
enable() {
this._handlerId = someObject.connect('some-signal', () => { /* ... */ });
}
disable() {
if (this._handlerId) {
someObject.disconnect(this._handlerId);
this._handlerId = null;
}
}Add a timeout (and clean up)
添加定时器(并清理)
js
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;
}
}js
enable() {
this._timeoutId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 5, () => {
// 执行任务
return GLib.SOURCE_CONTINUE; // 或GLib.SOURCE_REMOVE
});
}
disable() {
if (this._timeoutId) {
GLib.Source.remove(this._timeoutId);
this._timeoutId = null;
}
}Use GSettings
使用GSettings
js
enable() {
this._settings = this.getSettings(); // uses metadata settings-schema
this._settings.bind('show-indicator', this._indicator, 'visible',
Gio.SettingsBindFlags.DEFAULT);
}
disable() {
this._settings = null;
}js
enable() {
this._settings = this.getSettings(); // 使用metadata中的settings-schema
this._settings.bind('show-indicator', this._indicator, 'visible',
Gio.SettingsBindFlags.DEFAULT);
}
disable() {
this._settings = null;
}Override a method (InjectionManager)
重写方法(InjectionManager)
js
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;
}
}js
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;
}
}Packaging for Submission
提交打包
sh
cd ~/.local/share/gnome-shell/extensions/my-extension@example.com
gnome-extensions pack --podir=po --extra-source=utils.js .sh
cd ~/.local/share/gnome-shell/extensions/my-extension@example.com
gnome-extensions pack --podir=po --extra-source=utils.js .GNOME 49+: upload directly
GNOME 49+:直接上传
gnome-extensions upload --accept-tos
undefinedgnome-extensions upload --accept-tos
undefined