js-gnome-extensions

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

GNOME Shell Extensions

GNOME Shell 扩展

Build extensions for GNOME Shell 45+ using GJS with ESModules.
使用支持ESModules的GJS为GNOME Shell 45+版本构建扩展。

Key Resources

核心资源

Architecture Overview

架构概述

Extensions run inside the
gnome-shell
process using Clutter/St toolkits (not GTK). Preferences run in a separate GTK4/Adwaita process.
Library 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
    global
    object, app tracking, utilities
  • js/ui/ — GNOME Shell JS modules (Main, Panel, PopupMenu, QuickSettings, etc.)
Import conventions in
extension.js
:
js
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.js
:
js
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
extension.js
. Never import Clutter/Meta/St/Shell in
prefs.js
.
扩展运行在
gnome-shell
进程内,使用Clutter/St工具包(而非GTK)。偏好设置则在独立的GTK4/Adwaita进程中运行。
库栈(从底层到上层):
  • Clutter — 基于Actor的工具包,包含布局管理器、动画功能
  • St — Shell工具包:按钮、图标、标签、输入框、滚动视图(支持CSS样式)
  • Meta(Mutter) — 显示器、工作区、窗口、快捷键绑定
  • Shell
    global
    对象、应用追踪、工具函数
  • js/ui/ — GNOME Shell JS模块(Main、Panel、PopupMenu、QuickSettings等)
extension.js
中的导入约定:
js
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.js
中的导入约定:
js
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";
重要规则:永远不要在
extension.js
中导入GTK/Gdk/Adw。永远不要在
prefs.js
中导入Clutter/Meta/St/Shell。

Extension Lifecycle

扩展生命周期

Required Files

必备文件

  1. metadata.json
    — UUID, name, description, shell-version, url
  2. extension.js
    — Default export: subclass of
    Extension
  1. metadata.json
    — 包含UUID、名称、描述、shell版本、网址
  2. extension.js
    — 默认导出:
    Extension
    的子类

Optional Files

可选文件

  • prefs.js
    — Subclass of
    ExtensionPreferences
    (GTK4/Adwaita)
  • stylesheet.css
    — CSS for St widgets in gnome-shell (not prefs)
  • schemas/
    — GSettings schema XML + compiled binary
  • locale/
    — Gettext translation .mo files
  • prefs.js
    ExtensionPreferences
    的子类(基于GTK4/Adwaita)
  • stylesheet.css
    — 用于gnome-shell中St组件的CSS样式(不适用于偏好设置界面)
  • schemas/
    — GSettings模式XML文件及编译后的二进制文件
  • locale/
    — Gettext翻译.mo文件

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()
  • constructor()
    may only set up static data (RegExp, Map, etc.) and call
    super(metadata)
  • Everything created in
    enable()
    MUST be cleaned up in
    disable()
  • disable()
    is called on lock screen (unless
    session-modes
    includes
    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源
    // - 清空所有引用
  }
}
规则(扩展审核强制执行):
  • 不要在
    constructor()
    中创建GObject实例或连接信号
  • constructor()
    仅可设置静态数据(正则表达式、Map等)并调用
    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.json

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"
}
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.js

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;
  }
}
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
undefined
sh
undefined

Create 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
undefined
journalctl -f -o cat /usr/bin/gnome-shell
undefined

Common 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
undefined
gnome-extensions upload --accept-tos
undefined