js-gnome-apps
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseGNOME JavaScript (GJS) Application Development
GNOME JavaScript(GJS)应用开发
Technology Stack
技术栈
| Component | Purpose |
|---|---|
| GJS | JavaScript runtime (SpiderMonkey) with GObject Introspection bindings |
| GTK 4 | UI toolkit |
| Libadwaita (Adw) 1.x | GNOME-specific widgets, adaptive layouts, styling |
| GLib / GIO | Core utilities, async I/O, settings, D-Bus, file operations |
| Meson | Build system |
| Flatpak | App packaging and distribution |
| Blueprint (optional) | Declarative UI markup that compiles to GTK XML |
| 组件 | 用途 |
|---|---|
| GJS | 带有GObject自省绑定的JavaScript运行时(SpiderMonkey) |
| GTK 4 | UI工具包 |
| Libadwaita (Adw) 1.x | GNOME专用组件、自适应布局、样式设置 |
| GLib / GIO | 核心工具、异步I/O、设置、D-Bus、文件操作 |
| Meson | 构建系统 |
| Flatpak | 应用打包与分发 |
| Blueprint(可选) | 可编译为GTK XML的声明式UI标记语言 |
Imports (ES Modules — required for new code)
导入(ES Modules — 新代码必填)
js
// Built-in GJS modules
import Cairo from "cairo";
import Gettext from "gettext";
import System from "system";
// Platform libraries via gi:// URI
import GLib from "gi://GLib";
import GObject from "gi://GObject";
import Gio from "gi://Gio";
// Versioned imports (required when multiple API versions exist)
import Gtk from "gi://Gtk?version=4.0";
import Adw from "gi://Adw?version=1";
// Relative user modules (include .js extension)
import * as Utils from "./lib/utils.js";Keep imports grouped: built-in → platform () → local, separated by blank lines. Use for imported modules.
gi://PascalCasejs
// Built-in GJS modules
import Cairo from "cairo";
import Gettext from "gettext";
import System from "system";
// Platform libraries via gi:// URI
import GLib from "gi://GLib";
import GObject from "gi://GObject";
import Gio from "gi://Gio";
// Versioned imports (required when multiple API versions exist)
import Gtk from "gi://Gtk?version=4.0";
import Adw from "gi://Adw?version=1";
// Relative user modules (include .js extension)
import * as Utils from "./lib/utils.js";导入分组规则:内置模块 → 平台模块()→ 本地模块,各组之间用空行分隔。导入的模块使用命名规范。
gi://PascalCaseGObject Subclassing
GObject子类化
Register every GObject subclass with . Use (not , which is legacy pre-GNOME 42).
GObject.registerClass()constructor()_init()js
const MyWidget = GObject.registerClass(
{
GTypeName: "MyWidget",
Template: "resource:///com/example/MyApp/my-widget.ui",
InternalChildren: ["title_label", "action_button"],
Properties: {
"example-prop": GObject.ParamSpec.string(
"example-prop",
"",
"",
GObject.ParamFlags.READWRITE,
null,
),
},
Signals: {
"item-selected": {
param_types: [GObject.TYPE_STRING],
},
},
},
class MyWidget extends Gtk.Box {
constructor(params = {}) {
super(params);
// Template children: this._title_label, this._action_button
}
get example_prop() {
return this._example_prop ?? null;
}
set example_prop(value) {
if (this.example_prop === value) return;
this._example_prop = value;
this.notify("example-prop");
}
},
);Key rules:
- Property names: in GObject declarations,
kebab-casein JS accessorssnake_case - Always call in setters to emit change notifications
this.notify('prop-name') - is optional unless the type is referenced in UI XML
GTypeName<template class="..."> - maps
InternalChildrenattrs in UI XML →id;this._childId→Childrenthis.childId
使用注册所有GObject子类。使用(而非,后者是GNOME 42之前的遗留写法)。
GObject.registerClass()constructor()_init()js
const MyWidget = GObject.registerClass(
{
GTypeName: "MyWidget",
Template: "resource:///com/example/MyApp/my-widget.ui",
InternalChildren: ["title_label", "action_button"],
Properties: {
"example-prop": GObject.ParamSpec.string(
"example-prop",
"",
"",
GObject.ParamFlags.READWRITE,
null,
),
},
Signals: {
"item-selected": {
param_types: [GObject.TYPE_STRING],
},
},
},
class MyWidget extends Gtk.Box {
constructor(params = {}) {
super(params);
// Template children: this._title_label, this._action_button
}
get example_prop() {
return this._example_prop ?? null;
}
set example_prop(value) {
if (this.example_prop === value) return;
this._example_prop = value;
this.notify("example-prop");
}
},
);核心规则:
- 属性名称:在GObject声明中使用,在JS访问器中使用
kebab-casesnake_case - 在setter中必须调用来触发变更通知
this.notify('prop-name') - 除非在UI XML的中引用该类型,否则
<template class="...">是可选的GTypeName - 将UI XML中的
InternalChildren属性映射为id;this._childId则映射为Childrenthis.childId
Application Entry Point
应用入口
js
import GLib from "gi://GLib";
import GObject from "gi://GObject";
import Gio from "gi://Gio";
import Gtk from "gi://Gtk?version=4.0";
import Adw from "gi://Adw?version=1";
import { MyAppWindow } from "./window.js";
const MyApp = GObject.registerClass(
class MyApp extends Adw.Application {
constructor() {
super({
application_id: "com.example.MyApp",
flags: Gio.ApplicationFlags.DEFAULT_FLAGS,
});
const quitAction = new Gio.SimpleAction({ name: "quit" });
quitAction.connect("activate", () => this.quit());
this.add_action(quitAction);
this.set_accels_for_action("app.quit", ["<Primary>q"]);
}
vfunc_activate() {
let win = this.active_window;
if (!win) win = new MyAppWindow(this);
win.present();
}
},
);
const app = new MyApp();
app.run([System.programInvocationName, ...ARGV]);js
import GLib from "gi://GLib";
import GObject from "gi://GObject";
import Gio from "gi://Gio";
import Gtk from "gi://Gtk?version=4.0";
import Adw from "gi://Adw?version=1";
import { MyAppWindow } from "./window.js";
const MyApp = GObject.registerClass(
class MyApp extends Adw.Application {
constructor() {
super({
application_id: "com.example.MyApp",
flags: Gio.ApplicationFlags.DEFAULT_FLAGS,
});
const quitAction = new Gio.SimpleAction({ name: "quit" });
quitAction.connect("activate", () => this.quit());
this.add_action(quitAction);
this.set_accels_for_action("app.quit", ["<Primary>q"]);
}
vfunc_activate() {
let win = this.active_window;
if (!win) win = new MyAppWindow(this);
win.present();
}
},
);
const app = new MyApp();
app.run([System.programInvocationName, ...ARGV]);Async with Gio._promisify
使用Gio._promisify实现异步
Wrap pairs once at module level for usage:
*_async/*_finishasync/awaitjs
Gio._promisify(
Gio.File.prototype,
"load_contents_async",
"load_contents_finish",
);
const file = Gio.File.new_for_path("/tmp/example.txt");
try {
const [contents] = await file.load_contents_async(null);
const text = new TextDecoder().decode(contents);
} catch (e) {
logError(e, "Failed to load file");
}在模块级别一次性包装方法对,以支持语法:
*_async/*_finishasync/awaitjs
Gio._promisify(
Gio.File.prototype,
"load_contents_async",
"load_contents_finish",
);
const file = Gio.File.new_for_path("/tmp/example.txt");
try {
const [contents] = await file.load_contents_async(null);
const text = new TextDecoder().decode(contents);
} catch (e) {
logError(e, "Failed to load file");
}Code Style (GJS conventions)
代码风格(GJS规范)
- 4-space indentation, single quotes, semicolons
- by default,
constwhen mutation needed, neverletvar - File names: ; directories:
lowerCamelCase.jslowercase - Arrow functions for inline callbacks; for method references
.bind(this) - for public API, never
exportvar
- 使用4空格缩进,单引号,末尾加分号
- 优先使用,需要修改变量时使用
const,禁止使用letvar - 文件名:;目录名:全小写
lowerCamelCase.js - 内联回调使用箭头函数;方法引用使用
.bind(this) - 公开API使用,禁止使用
exportvar
Reference Files
参考文档
Consult these based on the task at hand:
- Project setup & packaging: references/project-setup.md — Meson build, Flatpak manifests, GResource, application ID, desktop files, GSettings schemas, directory layout
- UI design (GTK4 + Libadwaita): references/ui-patterns.md — Widget templates (UI XML), adaptive layouts (breakpoints, split views, view switcher), boxed lists, style classes, header bars, GNOME HIG patterns
- GIO & platform patterns: references/gio-patterns.md — File I/O, GSettings, actions & menus, D-Bus, subprocesses, list models, GVariant
根据任务需求参考以下文档:
- 项目配置与打包:references/project-setup.md — Meson构建、Flatpak清单、GResource、应用ID、桌面文件、GSettings模式、目录结构
- UI设计(GTK4 + Libadwaita):references/ui-patterns.md — 组件模板(UI XML)、自适应布局(断点、拆分视图、视图切换器)、列表框、样式类、标题栏、GNOME HIG设计模式
- GIO与平台模式:references/gio-patterns.md — 文件I/O、GSettings、动作与菜单、D-Bus、子进程、列表模型、GVariant",