Initial instructions and skills
This commit is contained in:
@@ -0,0 +1,269 @@
|
||||
---
|
||||
name: gjs-gnome-extensions
|
||||
description: "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)."
|
||||
---
|
||||
|
||||
# GNOME Shell Extensions
|
||||
|
||||
Build extensions for GNOME Shell 45+ using GJS with ESModules.
|
||||
|
||||
## 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
|
||||
- **Gnome Shell JS coding style: https://github.com/GNOME/gnome-shell/blob/main/docs/js-coding-style.md
|
||||
|
||||
## 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`.
|
||||
|
||||
## Extension Lifecycle
|
||||
|
||||
### Required Files
|
||||
|
||||
1. **`metadata.json`** — UUID, name, description, shell-version, url
|
||||
2. **`extension.js`** — Default export: subclass of `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
|
||||
|
||||
### enable()/disable() Contract
|
||||
|
||||
~~~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`)
|
||||
- The code complies with the referenced JS coding style, unless it is not javascript or typescript code
|
||||
|
||||
## Reference Files
|
||||
|
||||
Detailed documentation is split into reference files. Read the appropriate file based on the task:
|
||||
|
||||
- **[references/review-guidelines.md](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](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](references/preferences.md)** — GSettings schemas, prefs.js with GTK4/Adwaita, settings binding. Read when implementing extension preferences.
|
||||
- **[references/development.md](references/development.md)** — Getting started, testing, debugging, translations and InjectionManager. Read when setting up a new extension or debugging.
|
||||
- **[references/typescript.md](references/typescript.md)** — Setting up a project to enable using typescript to create the extension. Read when creating an extension project in typescript.
|
||||
- **[references/porting-guide.md](references/porting-guide.md)** — Breaking changes for GNOME Shell 45–49. Read when porting an extension to a newer version.
|
||||
- **[references/js-coding-style.md](references/js-coding-style.md)** — The coding style to always adhere to
|
||||
|
||||
## Quick Start: Panel Indicator Extension
|
||||
|
||||
Minimal working extension with a panel icon:
|
||||
|
||||
### `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"
|
||||
}
|
||||
~~~
|
||||
|
||||
### `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;
|
||||
}
|
||||
}
|
||||
~~~
|
||||
|
||||
### Install & Test
|
||||
|
||||
~~~sh
|
||||
# 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-shell
|
||||
~~~
|
||||
|
||||
## 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;
|
||||
}
|
||||
}
|
||||
~~~
|
||||
|
||||
### 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;
|
||||
}
|
||||
}
|
||||
~~~
|
||||
|
||||
### Use 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;
|
||||
}
|
||||
~~~
|
||||
|
||||
### Override a method (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;
|
||||
}
|
||||
}
|
||||
~~~
|
||||
|
||||
## Packaging for Submission
|
||||
|
||||
~~~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-extensions upload --accept-tos
|
||||
~~~
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,338 @@
|
||||
# JS Coding Style
|
||||
|
||||
Our goal is to have all JavaScript code in GNOME follow a consistent style. In
|
||||
a dynamic language like JavaScript, it is essential to be rigorous about style
|
||||
(and unit tests), or you rapidly end up with a spaghetti-code mess.
|
||||
|
||||
## A quick note
|
||||
|
||||
Life isn't fun if you can't break the rules. If a rule seems unnecessarily
|
||||
restrictive while you're coding, ignore it, and let the patch reviewer decide
|
||||
what to do.
|
||||
|
||||
## Indentation, braces and whitespace
|
||||
|
||||
* Use four-space indents.
|
||||
* Braces are on the same line as their associated statements.
|
||||
* You should only omit braces if *both* sides of the statement are on one line.
|
||||
* One space after the `function` keyword.
|
||||
* No space between the function name in a declaration or a call.
|
||||
* One space before the parens in the `if` statements, or `while`, or `for` loops.
|
||||
|
||||
```javascript
|
||||
function foo(a, b) {
|
||||
let bar;
|
||||
|
||||
if (a > b)
|
||||
bar = do_thing(a);
|
||||
else
|
||||
bar = do_thing(b);
|
||||
|
||||
if (bar === 5) {
|
||||
for (let i = 0; i < 10; i++)
|
||||
print(i);
|
||||
} else {
|
||||
print(20);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Semicolons
|
||||
|
||||
JavaScript allows omitting semicolons at the end of lines, but don't. Always
|
||||
end statements with a semicolon.
|
||||
|
||||
## js2-mode
|
||||
|
||||
If using Emacs, do not use js2-mode. It is outdated and hasn't worked for a
|
||||
while. emacs now has a built-in JavaScript mode, js-mode, based on
|
||||
espresso-mode. It is the de facto emacs mode for JavaScript.
|
||||
|
||||
## File naming and creation
|
||||
|
||||
For JavaScript files, use lowerCamelCase-style names, with a `.js` extension.
|
||||
|
||||
We only use C where gjs/gobject-introspection is not available for the task, or
|
||||
where C would be cleaner. To work around limitations in
|
||||
gjs/gobject-introspection itself, add a new method in `shell-util.[ch]`.
|
||||
|
||||
Like many other GNOME projects, we prefix our C source filenames with the
|
||||
library name followed by a dash, e.g. `shell-app-system.c`. Create a
|
||||
`-private.h` header when you want to share code internally in the
|
||||
library. These headers are not installed, distributed or introspected.
|
||||
|
||||
## Imports
|
||||
|
||||
Use UpperCamelCase when importing modules to distinguish them from ordinary
|
||||
variables, e.g.
|
||||
```javascript
|
||||
import GLib from 'gi://GLib';
|
||||
```
|
||||
Imports should be categorized into one of two places. The top-most import block
|
||||
should contain only "environment imports". These are either modules from
|
||||
gobject-introspection or modules added by gjs itself.
|
||||
|
||||
The second block of imports should contain only "application imports". These
|
||||
are the JS code that is in the gnome-shell codebase,
|
||||
e.g. `'./popupMenu.js'`.
|
||||
|
||||
Each import block should be sorted alphabetically. Don't import modules you
|
||||
don't use.
|
||||
```javascript
|
||||
import GLib from 'gi://GLib';
|
||||
import Gio from 'gi://Gio';
|
||||
import St from 'gi://St';
|
||||
|
||||
import * as Main from './main.js';
|
||||
import * as Params from '../misc/params.js';
|
||||
import * as Util from '../misc/util.js';
|
||||
```
|
||||
The alphabetical ordering should be done independently of the location of the
|
||||
location. Never reference `imports` in actual code.
|
||||
|
||||
## Constants
|
||||
|
||||
We use CONSTANTS_CASE to define constants. All constants should be directly
|
||||
under the imports:
|
||||
```javascript
|
||||
const MY_DBUS_INTERFACE = 'org.my.Interface';
|
||||
```
|
||||
|
||||
## Variable declaration
|
||||
|
||||
Always use either `const` or `let` when defining a variable.
|
||||
```javascript
|
||||
// Iterating over an array
|
||||
for (let i = 0; i < arr.length; ++i) {
|
||||
const item = arr[i];
|
||||
}
|
||||
|
||||
// Iterating over an object's properties
|
||||
for (const prop in someobj) {
|
||||
const val = someobj[prop];
|
||||
}
|
||||
```
|
||||
|
||||
If you use "var" then the variable is added to function scope, not block scope.
|
||||
See [What's new in JavaScript 1.7](https://developer.mozilla.org/en/JavaScript/New_in_JavaScript/1.7#Block_scope_with_let_%28Merge_into_let_Statement%29)
|
||||
|
||||
## Classes
|
||||
|
||||
There are many approaches to classes in JavaScript. We use standard ES6 classes
|
||||
whenever possible, that is when not inheriting from GObjects.
|
||||
```javascript
|
||||
export class IconLabelMenuItem extends PopupMenu.PopupMenuBaseItem {
|
||||
constructor(icon, label) {
|
||||
super({reactive: false});
|
||||
this.actor.add_child(icon);
|
||||
this.actor.add_child(label);
|
||||
}
|
||||
|
||||
open() {
|
||||
log('menu opened!');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For GObject inheritance, we use the GObject.registerClass() function provided
|
||||
by gjs.
|
||||
```javascript
|
||||
export const MyActor = GObject.registerClass(
|
||||
class MyActor extends Clutter.Actor {
|
||||
constructor(params) {
|
||||
super(params);
|
||||
|
||||
this.name = 'MyCustomActor';
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## GObject Introspection
|
||||
|
||||
GObject Introspection is a powerful feature that allows us to have native
|
||||
bindings for almost any library built around GObject. If a library requires
|
||||
you to inherit from a type to use it, you can do so:
|
||||
```javascript
|
||||
export const MyClutterActor = GObject.registerClass(
|
||||
class MyClutterActor extends Clutter.Actor {
|
||||
vfunc_get_preferred_width(forHeight) {
|
||||
return [100, 100];
|
||||
}
|
||||
|
||||
vfunc_get_preferred_height(forWidth) {
|
||||
return [100, 100];
|
||||
}
|
||||
|
||||
vfunc_paint(paintContext) {
|
||||
const framebuffer = paintContext.get_framebuffer();
|
||||
const coglContext = framebuffer.get_context();
|
||||
const alloc = this.get_allocation_box();
|
||||
|
||||
const pipeline = Cogl.Pipeline.new(coglContext);
|
||||
pipeline.set_color4ub(255, 0, 0, 255);
|
||||
|
||||
framebuffer.draw_rectangle(pipeline,
|
||||
alloc.x1, alloc.y1,
|
||||
alloc.x2, alloc.y2);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Translatable strings, `environment.js`
|
||||
|
||||
We use gettext to translate the GNOME Shell into all the languages that GNOME
|
||||
supports. The `gettext` function is aliased globally as `_`, you do not need to
|
||||
explicitly import it. This is done through some magic in the
|
||||
[environment.js](http://git.gnome.org/browse/gnome-shell/tree/js/ui/environment.js)
|
||||
file. If you can't find a method that's used, it's probably either in gjs itself
|
||||
or installed on the global object from the Environment.
|
||||
|
||||
## `actor` (deprecated) and `_delegate`
|
||||
|
||||
gjs allows us to set so-called "expando properties" on introspected objects,
|
||||
allowing us to treat them like any other. Because the Shell was built before
|
||||
you could inherit from GTypes natively in JS, in some cases we have a wrapper
|
||||
class that has a property called `actor` (now deprecated). We call this
|
||||
wrapper class the "delegate".
|
||||
|
||||
We sometimes use expando properties to set a property called `_delegate` on
|
||||
the actor itself:
|
||||
```javascript
|
||||
export const MyActor = GObject.registerClass(
|
||||
class MyActor extends Clutter.Actor {
|
||||
constructor(params) {
|
||||
super(params);
|
||||
this._delegate = this;
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Or using the deprecated `actor`:
|
||||
```javascript
|
||||
export class MyClass {
|
||||
constructor() {
|
||||
this.actor = new St.Button({text: 'This is a button'});
|
||||
this.actor._delegate = this;
|
||||
|
||||
this.actor.connect('clicked', this._onClicked.bind(this));
|
||||
}
|
||||
|
||||
_onClicked(actor) {
|
||||
actor.set_label('You clicked the button!');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The 'delegate' property is important for anything which trying to get the
|
||||
delegate object from an associated actor. For instance, the drag and drop
|
||||
system calls the `handleDragOver` function on the delegate of a "drop target"
|
||||
when the user drags an item over it. If you do not set the `_delegate`
|
||||
property, your actor will not be able to be dropped onto.
|
||||
In case the class is an actor itself, the `_delegate` can be just set to `this`.
|
||||
|
||||
## Functional style
|
||||
|
||||
JavaScript Array objects offer a lot of common functional programming
|
||||
capabilities such as forEach, map, filter and so on. You can use these when
|
||||
they make sense, but please don't have a spaghetti mess of function programming
|
||||
messed in a procedural style. Use your best judgment.
|
||||
|
||||
## Closures
|
||||
|
||||
`this` will not be captured in a closure, it is relative to how the closure is
|
||||
invoked, not to the value of this where the closure is created, because "this"
|
||||
is a keyword with a value passed in at function invocation time, it is not a
|
||||
variable that can be captured in closures.
|
||||
|
||||
All closures should be wrapped with Function.prototype.bind or use arrow
|
||||
notation.
|
||||
```javascript
|
||||
const closure1 = () => this._fnorbate();
|
||||
const closure2 = this._fnorbate.bind(this);
|
||||
```
|
||||
|
||||
A more realistic example would be connecting to a signal on a method of a
|
||||
prototype:
|
||||
```javascript
|
||||
import * as FnorbLib from './fborbLib.js';
|
||||
|
||||
export class MyClass {
|
||||
constructor() {
|
||||
const fnorb = new FnorbLib.Fnorb();
|
||||
fnorb.connect('frobate', this._onFnorbFrobate.bind(this));
|
||||
}
|
||||
|
||||
_onFnorbFrobate(fnorb) {
|
||||
this._updateFnorb();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Object literal syntax
|
||||
|
||||
In JavaScript, these are equivalent:
|
||||
```javascript
|
||||
foo = {'bar': 42};
|
||||
foo = {bar: 42};
|
||||
```
|
||||
|
||||
and so are these:
|
||||
```javascript
|
||||
b = foo['bar'];
|
||||
b = foo.bar;
|
||||
```
|
||||
|
||||
If your usage of an object is like an object, then you're defining "member
|
||||
variables." For member variables, use the no-quotes no-brackets syntax:
|
||||
`{bar: 42}` `foo.bar`.
|
||||
|
||||
If your usage of an object is like a hash table (and thus conceptually the keys
|
||||
can have special chars in them), don't use quotes, but use brackets:
|
||||
`{bar: 42}`, `foo['bar']`.
|
||||
|
||||
## Animations
|
||||
|
||||
Most objects that are animated are actors, and most properties used in animations
|
||||
are animatable, which means they can use implicit animations:
|
||||
|
||||
```javascript
|
||||
moveActor(actor, x, y) {
|
||||
actor.ease({
|
||||
x,
|
||||
y,
|
||||
duration: 500, // ms
|
||||
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
The above is a convenience wrapper around the actual Clutter API, and should generally
|
||||
be preferred over the more verbose:
|
||||
|
||||
```javascript
|
||||
moveActor(actor, x, y) {
|
||||
actor.save_easing_state();
|
||||
|
||||
actor.set_easing_duration(500);
|
||||
actor.set_easing_mode(Clutter.AnimationMode.EASE_OUT_QUAD);
|
||||
actor.set({
|
||||
x,
|
||||
y,
|
||||
});
|
||||
|
||||
actor.restore_easing_state();
|
||||
}
|
||||
```
|
||||
|
||||
There is a similar convenience API around Clutter.PropertyTransition to animate
|
||||
actor (or actor meta) properties that cannot use implicit animations:
|
||||
|
||||
```javascript
|
||||
desaturateActor(actor, desaturate) {
|
||||
const factor = desaturate ? 1.0 : 0.0;
|
||||
actor.ease_property('@effects.desaturate.factor', factor, {
|
||||
duration: 500, // ms
|
||||
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||
});
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,913 @@
|
||||
# [Port Extensions to GNOME Shell 45](#port-extensions-to-gnome-shell-45)
|
||||
|
||||
## [ESM](#esm)
|
||||
|
||||
GNOME Shell 45 moved to ESM (ECMAScript modules). That means you **MUST** use the standard `import` declaration instead of relying on the previous `imports.*` approach.
|
||||
|
||||
If you are not familiar with ESM yet, please read the [MDN guide](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) to learn about modules in general. For GNOME Shell extensions, modules can be split into 3 categories:
|
||||
|
||||
- GI libraries are imported as defaults with a module specifier using a `gi://` URI scheme. So import them with `import SOME_NAME from 'gi://LIBRARY_NAME'`.
|
||||
- GNOME Shell files are imported as [resources](https://gjs-docs.gnome.org/gio20~2.0/gio.resource) with a `resource://` URI scheme. See the examples below.
|
||||
- Your own files are imported with relative paths.
|
||||
|
||||
Here're some examples of importing modules in the old and the new way:
|
||||
|
||||
importing GObject-Introspection generated (gi) libraries
|
||||
~~~js
|
||||
// Before GNOME 45
|
||||
const GLib = imports.gi.GLib;
|
||||
~~~
|
||||
|
||||
~~~js
|
||||
// GNOME 45
|
||||
import GLib from 'gi://GLib';
|
||||
~~~
|
||||
|
||||
importing versioned gi libraries
|
||||
~~~js
|
||||
// Before GNOME 45
|
||||
imports.gi.versions.Soup = '3.0';
|
||||
const Soup = imports.gi.Soup;
|
||||
~~~
|
||||
|
||||
~~~js
|
||||
// GNOME 45
|
||||
import Soup from 'gi://Soup?version=3.0';
|
||||
~~~
|
||||
|
||||
importing native GNOME Shell modules (e.g., from the [ui directory](https://gitlab.gnome.org/GNOME/gnome-shell/-/tree/main/js/ui))
|
||||
~~~js
|
||||
// Before GNOME 45
|
||||
const Main = imports.ui.main;
|
||||
~~~
|
||||
|
||||
~~~js
|
||||
// GNOME 45
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||
~~~
|
||||
|
||||
importing native GNOME Shell modules (e.g., from the [misc directory](https://gitlab.gnome.org/GNOME/gnome-shell/-/tree/main/js/misc))
|
||||
~~~js
|
||||
// Before GNOME 45
|
||||
const Util = imports.misc.util;
|
||||
~~~
|
||||
|
||||
~~~js
|
||||
// GNOME 45
|
||||
import * as Util from 'resource:///org/gnome/shell/misc/util.js';
|
||||
~~~
|
||||
|
||||
importing parts of a module
|
||||
~~~js
|
||||
// Before GNOME 45
|
||||
const {panel, wm} = imports.ui.main;
|
||||
~~~
|
||||
|
||||
~~~js
|
||||
// GNOME 45
|
||||
import {panel, wm} from 'resource:///org/gnome/shell/ui/main.js';
|
||||
~~~
|
||||
|
||||
importing your own modules that are part of your extension's code base
|
||||
~~~js
|
||||
// Before GNOME 45
|
||||
const Me = imports.misc.extensionUtils.getCurrentExtension();
|
||||
const MyModule = Me.imports.MyModule;
|
||||
~~~
|
||||
|
||||
~~~js
|
||||
// GNOME 45
|
||||
import * as MyModule from './MyModule.js';
|
||||
~~~
|
||||
|
||||
#### `extension.js` vs `prefs.js`
|
||||
|
||||
The GNOME Shell resource path to be used while importing from within `prefs.js` is a bit different compared to imports in `extension.js`. In `prefs.js`, resource paths start with `resource:///org/gnome/Shell/Extensions/js/`, while in the `extension.js` case, they start with `resource:///org/gnome/shell/`.
|
||||
|
||||
For example, here's how you'd import the `misc/config` module:
|
||||
|
||||
In `extension.js`:
|
||||
~~~js
|
||||
import * as Config from 'resource:///org/gnome/shell/misc/config.js';
|
||||
~~~
|
||||
|
||||
in `prefs.js` (please note the path is case sensitive):
|
||||
~~~js
|
||||
import * as Config from 'resource:///org/gnome/Shell/Extensions/js/misc/config.js';
|
||||
~~~
|
||||
|
||||
## [Metadata](#metadata)
|
||||
|
||||
### [`shell-version`](#shell-version)
|
||||
|
||||
Since ESM files contain `import` and `export` keywords, your extension modules won't be compatible with older GNOME Shell versions. You should remove the old shell versions and only use `45` in `shell-version` in your `metadata.json`!
|
||||
|
||||
The good news is that [e.g.o](https://extensions.gnome.org/) supports multi versioning - you can still submit multiple packages with different shell versions.
|
||||
|
||||
## [`extensionUtils`](#extensionutils)
|
||||
|
||||
`extensionUtils` no longer contains helper functions extensions usually use. Instead, you can use `Extension`'s and `ExtensionBase`'s properties and methods.
|
||||
|
||||
Additionally, the `extension` module is offering translation functions (`gettext`, `ngettext` and `pgettext`).
|
||||
|
||||
For example, here we use `getSettings()` and `gettext()`:
|
||||
|
||||
~~~js
|
||||
import {Extension, gettext as _} from 'resource:///org/gnome/shell/extensions/extension.js';
|
||||
|
||||
export default class MyTestExtension extends Extension {
|
||||
enable() {
|
||||
this._settings = this.getSettings();
|
||||
console.log(_('This is a translatable text'));
|
||||
}
|
||||
|
||||
disable() {
|
||||
this._settings = null;
|
||||
}
|
||||
}
|
||||
~~~
|
||||
|
||||
If subclassing [`Extension`](./../topics/extension.html#extension) and [`ExtensionPreferences`](./../topics/extension.html#extensionpreferences), you can lookup the extension object from any module by using the static methods:
|
||||
|
||||
~~~js
|
||||
import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js';
|
||||
|
||||
let extensionObject, extensionSettings;
|
||||
|
||||
// Getting the extension object by UUID
|
||||
extensionObject = Extension.lookupByUUID('example@gjs.guide');
|
||||
extensionSettings = extensionObject.getSettings();
|
||||
console.log(extensionObject.metadata);
|
||||
|
||||
// Getting the extension object by URL
|
||||
extensionObject = Extension.lookupByURL(import.meta.url);
|
||||
extensionSettings = extensionObject.getSettings();
|
||||
console.log(extensionObject.metadata);
|
||||
~~~
|
||||
|
||||
The properties and methods you can use:
|
||||
|Property/Method|Output|Description|
|
||||
|---------------|------|-----------|
|
||||
|`initTranslations()`|`null`|Consider this method deprecated. Only specify `gettext-domain` in `metadata.json`. GNOME Shell can automatically initiate the translation for you when it sees the `gettext-domain` key in `metadata.json`.|
|
||||
|`getSettings()`|`Gio.Settings`|Still can read `settings-schema` from `metadata.json`.|
|
||||
|`openPreferences()`|`null`|Opens the preferences window if your extension has one.|
|
||||
|`uuid`|`string`|Extension's UUID value|
|
||||
|`dir`|`Gio.File`|Extension's directory path as an instance of `Gio.File`|
|
||||
|`path`|`string`|Extension's directory path as a string|
|
||||
|`metadata`|`object`|Metadata object built from `metadata.json`|
|
||||
|
||||
## [Extension](#extension)
|
||||
|
||||
`extension.js` **MUST** export a default class containing `enable()` and `disable()` methods:
|
||||
|
||||
~~~js
|
||||
export default class MyTestExtension {
|
||||
enable() {
|
||||
}
|
||||
|
||||
disable() {
|
||||
}
|
||||
}
|
||||
~~~
|
||||
|
||||
## [Preferences](#preferences)
|
||||
|
||||
If your extension is using `prefs.js`, you should export a default class extending `ExtensionPreferences` from the `prefs` module with `fillPreferencesWindow` method.
|
||||
|
||||
All `ExtensionBase`'s properties and methods mentioned before can be used here as well.
|
||||
|
||||
Just like the `extension` module, the `prefs` module is also offering translation functions.
|
||||
~~~js
|
||||
import Adw from 'gi://Adw';
|
||||
|
||||
import {ExtensionPreferences, gettext as _} from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js';
|
||||
|
||||
export default class MyExtensionPreferences extends ExtensionPreferences {
|
||||
fillPreferencesWindow(window) {
|
||||
window._settings = this.getSettings();
|
||||
|
||||
const page = new Adw.PreferencesPage();
|
||||
|
||||
const group = new Adw.PreferencesGroup({
|
||||
title: _('Group Title'),
|
||||
});
|
||||
page.add(group);
|
||||
|
||||
window.add(page);
|
||||
}
|
||||
}
|
||||
~~~
|
||||
|
||||
## [GNOME Shell](#gnome-shell)
|
||||
|
||||
### [`misc.animationUtils`](#misc-animationutils)
|
||||
|
||||
[misc.animationUtils](https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/misc/animationUtils.js) is a new module in GNOME Shell 45 that offers some animation convenience tools.
|
||||
|
||||
Examples include:
|
||||
|
||||
- `wiggle()` - can animate an actor (e.g., `St.Entry`) in X axis
|
||||
- `adjustAnimationTime()` - can change the animation time
|
||||
- [and more...](https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/misc/animationUtils.js)
|
||||
|
||||
### [`Math.clamp()`](#math-clamp)
|
||||
|
||||
GNOME Shell 45 adds [Math.clamp()](https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/8a8539ee6766058b39d0a5c0961a08f76799f4da/js/ui/environment.js#L357) function. You can clamp numbers between some min and max values. This is only available in `extension.js`, and not in `prefs.js`.
|
||||
|
||||
### [Desktop](#desktop)
|
||||
|
||||
#### [`DND`](#dnd)
|
||||
|
||||
`addClickAction()` is a new method for [draggable](https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/a703e192ed4cd5df06f908e733ea4a28481bf95c/js/ui/dnd.js#L890). It allows you to add [click action](https://gjs-docs.gnome.org/clutter12~12/clutter.clickaction) to your draggable actor.
|
||||
|
||||
#### [`MprisPlayer.app`](#mprisplayer-app)
|
||||
|
||||
There is a new `app` property in `MprisPlayer` that gives you [Shell.App](https://gjs-docs.gnome.org/shell12~12/shell.app) or `null`. It can be used to get the current player app.
|
||||
|
||||
#### [`searchController`](#searchcontroller)
|
||||
|
||||
You can now get the `searchController` instance by `/ui/main.js/overview.searchController`.
|
||||
|
||||
#### [`SearchController` Provider](#searchcontroller-provider)
|
||||
|
||||
`SearchController` now offers `addProvider()` and `removeProvider()` so you can add and remove the search provider objects easier.
|
||||
|
||||
### [Top Panel](#top-panel)
|
||||
|
||||
#### [`Panel.toggleQuickSettings()` addition](#panel-togglequicksettings-addition)
|
||||
|
||||
GNOME Shell 45 adds `toggleQuickSettings()` to the panel. You can toggle quick settings menu via `ui.main.panel.toggleQuickSettings()`.
|
||||
|
||||
#### [`Panel.toggleAppMenu()` removal](#panel-toggleappmenu-removal)
|
||||
|
||||
GNOME Shell 45 removs `Panel.toggleAppMenu()` since the keyboard shortcut for app menu has been removed.
|
||||
|
||||
#### [`BackgroundAppMenuItem`](#backgroundappmenuitem)
|
||||
|
||||
`BackgroundAppMenuItem` uses spinner animation that can start spinning when quitting the app represented by the `BackgroundAppMenuItem` instance.
|
||||
|Type|Where|
|
||||
|----|-----|
|
||||
|Created In|`/ui/status/backgroundApps.js/BackgroundAppMenuItem._init()`|
|
||||
|Style Class|`.spinner`|
|
||||
|
||||
#### [`Backlight`](#backlight)
|
||||
|
||||
[ui.status.backlight](https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/status/backlight.js) is a new section in quick settings that allows you to control the keyboard backlight.
|
||||
|Type|Where|
|
||||
|----|-----|
|
||||
|Direct Access|`/ui/main.js/panel.statusArea.quickSettings._backlight`|
|
||||
|Created In|`/ui/panel.js/QuickSettings._init()`|
|
||||
|Style Class|`.keyboard-brightness-item`|
|
||||
|
||||
#### [`Camera`](#camera)
|
||||
|
||||
[ui.status.camera](https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/status/camera.js) is a new indicator to show when user's camera device is in use.
|
||||
|Type|Where|
|
||||
|----|-----|
|
||||
|Direct Access|`/ui/main.js/panel.statusArea.quickSettings._camera`|
|
||||
|Created In|`/ui/panel.js/QuickSettings._init()`|
|
||||
|Style Class|`.privacy-indicator`|
|
||||
|
||||
#### [`WorkspaceIndicators`](#workspaceindicators)
|
||||
|
||||
`ActivitiesButton` no longer has a label. Instead, it uses `WorkspaceIndicators` as its child.
|
||||
|Type|Where|
|
||||
|----|-----|
|
||||
|Implementation Path|`/ui/panel.js/WorkspaceIndicators`|
|
||||
|Direct Access|`/ui/main.js/panel.statusArea.activities.get_first_child()`|
|
||||
|Created In|`/ui/panel.js/ActivitiesButton._init()`|
|
||||
|Style Class|`#panelActivities StBoxLayout`|
|
||||
|
||||
The dots inside `WorkspaceIndicators` are instances of `WorkspaceDot`. `WorkspaceDot` uses `.scaleIn()` and `.scaleOutAndDestroy()` to animate the dots when workspaces are being added or removed:
|
||||
|Type|Where|
|
||||
|----|-----|
|
||||
|Implementation Path|`/ui/panel.js/WorkspaceDot`|
|
||||
|Style Class|`.workspace-dot` (`#panelActivities .workspace-dot`)|
|
||||
|
||||
There is also `/ui/main.js/panel.INACTIVE_WORKSPACE_DOT_SCALE` for inactive workspace dots.
|
||||
|
||||
### [Overview](#overview)
|
||||
|
||||
#### [`MAX_THUMBNAIL_SCALE`](#max-thumbnail-scale)
|
||||
|
||||
`MAX_THUMBNAIL_SCALE` is a `const` and no longer can be changed. `ThumbnailsBox._maxThumbnailScale` is a new property that allows you to change the max thumbnail scale size.
|
||||
|Type|Where|
|
||||
|----|-----|
|
||||
|Implementation Path|`/ui/workspaceThumbnail.js/ThumbnailsBox`|
|
||||
|Direct Access|`/ui/main.js/overview._overview._controls._thumbnailsBox._maxThumbnailScale`|
|
||||
|
||||
### [Clutter and Mutter](#clutter-and-mutter)
|
||||
|
||||
#### [`Clutter.Event`](#clutter-event)
|
||||
|
||||
When using the event objects in vfuncs and signals, use the `Clutter.Event`[getters](https://gjs-docs.gnome.org/clutter13/clutter.event) instead of the fields directly. See merge request [`mutter!3163`](https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3163), which introduces the relevant changes, and merge request [`gnome-shell!2872`](https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2872), which adapts to the changes.
|
||||
|
||||
#### [`Mtk.Rectangle`](#mtk-rectangle)
|
||||
|
||||
`Meta.Rectangle` should be replaced with `Mtk.Rectangle`. See merge request [`mutter!3128`](https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3128) for background information. For compatibility, `Meta.Rectangle` has temporarily been aliased to a function, which returns a `Mtk.Rectangle` (See commit [`gnome-shell@f1317f07`](https://gitlab.gnome.org/GNOME/gnome-shell/-/commit/f1317f07db8da49ad921473c5ccc9b31719b3aee)).
|
||||
|
||||
### [Logging](#logging)
|
||||
|
||||
`log()` is just an alias for `console.log()` now and you no longer can filter `journald` by `GNOME_SHELL_EXTENSION_UUID` and `GNOME_SHELL_EXTENSION_NAME`.
|
||||
|
||||
`console.log()` isn't new in GNOME Shell 45 but if you are still using `log()` for different log levels, you should use `console.*` functions instead:
|
||||
|
||||
- `console.debug()`
|
||||
- `console.error()`
|
||||
- `console.info()`
|
||||
- `console.log()`
|
||||
- `console.warn()`
|
||||
|
||||
## [GJS](#gjs)
|
||||
|
||||
> [!TIP]
|
||||
> There were no relevant changes to GJS in GNOME 45.
|
||||
|
||||
|
||||
# Port Extensions to GNOME Shell 46 [](#port-extensions-to-gnome-shell-46)
|
||||
|
||||
## [Metadata](#metadata)
|
||||
|
||||
> [!TIP]
|
||||
> There were no relevant changes to `metadata.json` in GNOME 46.
|
||||
|
||||
## [Extension](#extension)
|
||||
|
||||
> [!TIP]
|
||||
> There were no relevant changes to `extension.js` in GNOME 46.
|
||||
|
||||
## GNOME [Shell](#gnome-shell)
|
||||
|
||||
### [`appDisplay`](#appdisplay)
|
||||
|
||||
`ui/appDisplay.js/FolderIcon` now uses `overview-tile app-folder` style class instead of `app-well-app app-folder`.
|
||||
|
||||
And the edit icon for `ui/appDisplay.js/AppFolderDialog` folder name entry now uses `icon-button` style class instead of `edit-folder-button`.
|
||||
|
||||
Also `app-well-app` style class renamed to `overview-tile` and `app-well-app-running-dot` renamed to `app-grid-running-dot` for `ui/appDisplay.js/AppIcon`.
|
||||
|
||||
### [`Calendar`](#calendar)
|
||||
|
||||
`ui/calendar.js/Calendar` uses `calendar-day-heading` instead of `calendar-day calendar-day-heading` style class for weekday labels.
|
||||
|
||||
### [`CtrlAtlTab`](#ctrlatltab)
|
||||
|
||||
`ui/ctrlAltTab.js/CtrlAltTabManager` uses `shell-focus-windows-symbolic` icon instead of `focus-windows-symbolic`.
|
||||
|
||||
### [`WorldClocksSection`](#worldclockssection)
|
||||
|
||||
`ui/dateMenu.js/WorldClocksSection`'s header now uses `no-world-clocks` style class name when no world clocks have been set by the user.
|
||||
|
||||
### [`WeatherSection`](#weathersection)
|
||||
|
||||
`ui/dateMenu.js/WeatherSection` now uses `no-location` style class name when the weather location can not be retrieved.
|
||||
|
||||
### [`MessagesIndicator`](#messagesindicator)
|
||||
|
||||
`ui/dateMenu.js/MessagesIndicator` now uses `messages-indicator` style class name.
|
||||
|
||||
### [`Message`](#message)
|
||||
|
||||
Since the message list has been changed on GNOME Shell 46, `ui/messageList.js/Message` has new header (`ui/messageList.js/MessageHeader`) and time label (`ui/messageList.js/TimeLabel`).
|
||||
|
||||
Also `ui/messageList.js/Message` has `datetime` getter and setter for the time label.
|
||||
|
||||
The message box under message header has `message-box` style class name.
|
||||
|
||||
### [`MessageTray`](#messagetray)
|
||||
|
||||
`ui/messageTray.js/NotificationPolicy` has new static `newForApp()` method that can create a new policy for the app.
|
||||
|
||||
`ui/messageTray.js/Notification` has new `iconName` getter and setter.
|
||||
|
||||
The new `ui/messageTray.js/getSystemSource()` allows you to get a system notification source that `Main.notify()` and most Shell notifications use.
|
||||
|
||||
### [`PopupMenu`](#popupmenu)
|
||||
|
||||
`ui/popupMenu.js/Ornament` enum added new `NO_DOT` to have less ambiguous symbols for radio options. This ornament will use `ornament-dot-checked-symbolic` and `ornament-dot-unchecked-symbolic` icons.
|
||||
|
||||
`ui/popupMenu.js/Switch` is using `switch-on-symbolic` and `switch-off-symbolic` icons for on and off.
|
||||
|
||||
### [`Screenshot`](#screenshot)
|
||||
|
||||
Since session mode can disallow the screencast (`sessionMode.allowScreencast`), `ui/screenshot.js/UIMode` enum added new `SCREENSHOT_ONLY`.
|
||||
|
||||
Also, `ui/screenshot.js/ScreenshotUI` has new `screenshot-taken` and `closed` signals.
|
||||
|
||||
### [New Keybindings](#new-keybindings)
|
||||
|
||||
New keybindings added in `ui/windowManager/WindowManager` since GNOME Shell 46 using new key bindings for opening a new instance of pinned applications (`Super + Ctrl + Number`).
|
||||
|
||||
### [`BackgroundAppMenuItem` close icon](#backgroundappmenuitem-close-icon)
|
||||
|
||||
The style class for background app menu item close button changed from `close-button` to `icon-button`.
|
||||
|
||||
### [Keyboard Model Configuration Support](#keyboard-model-configuration-support)
|
||||
|
||||
Since keyboard model configuration support has been added (`xkb-model`), `ui/status/keyboard.js/InputSourceSystemSettings` has new getter and setter for `keyboardModel` and emitting `keyboard-model-changed` when the keyboard mode is getting changed.
|
||||
|
||||
### [`ExtensionState`](#extensionstate)
|
||||
|
||||
`misc/extensionUtils.js/ExtensionState` enum renamed some of the states:
|
||||
OldNew`ENABLED``ACTIVE``DISABLED``INACTIVE``DISABLING``DEACTIVATING``ENABLING``ACTIVATING`
|
||||
|
||||
### [`St.Bin` Expand Properties](#st-bin-expand-properties)
|
||||
|
||||
`St.Bin` and all sub classes like `Button`, `QuickSettingsItem`, ... now only expand according to its expand properties (`x-expand` and `y-expand`), not when the alignment is set to `Clutter.ActorAlign.FILL`.
|
||||
|
||||
### [`St.Button`](#st-button)
|
||||
|
||||
`St.Button` label now defaults to plain text not Pango markup.
|
||||
|
||||
### [`Clutter.cairo` Helpers](#clutter-cairo-helpers)
|
||||
|
||||
GNOME Shell 46 dropped `Clutter.cairo` helpers. If you are using `Clutter.cairo_set_source_color()` in `St.DrawingArea`, `cairo.Context` can use `setSourceColor()` instead (`cr.setSourceColor()`).
|
||||
|
||||
### [`Meta.Barrier.display`](#meta-barrier-display)
|
||||
|
||||
`Meta.Barrier.display` is now deprecated. To get the backend barrier you can use `global.backend` which returns `Meta.Barrier.backend`.
|
||||
|
||||
### [`Shell.BlurEffect`](#shell-blureffect)
|
||||
|
||||
The `sigma` in `Shell.BlurEffect` should be replaced by `radius`. Since the sigma value is `radius / 2.0`, the `radius` value will be `sigma * 2.0`.
|
||||
|
||||
## [GJS](#gjs)
|
||||
|
||||
### [`Clutter.Container`](#clutter-container)
|
||||
|
||||
`Clutter.Container` was removed. To feature test you can do:
|
||||
~~~js
|
||||
if (Clutter.Container === undefined) {
|
||||
console.log('No Clutter Container');
|
||||
}
|
||||
~~~
|
||||
|
||||
[`Clutter.Container.add_actor()`](https://gjs-docs.gnome.org/clutter13~13/clutter.container#method-add_actor) and [`Clutter.Container.remove_actor()`](https://gjs-docs.gnome.org/clutter13~13/clutter.container#method-remove_actor) are deprecated and you should use [`Clutter.Actor.add_child()`](https://gjs-docs.gnome.org/clutter13~13/clutter.actor#method-add_child) and [`Clutter.Actor.remove_child()`](https://gjs-docs.gnome.org/clutter13~13/clutter.actor#method-remove_child) instead.
|
||||
|
||||
So, instead of `actor-added` and `actor-removed` signals you can use `child-added` and `child-removed`.
|
||||
|
||||
### [`Gio.UnixInputStream`](#gio-unixinputstream)
|
||||
|
||||
`Gio.UnixInputStream` is moved to [GioUnix](https://gjs-docs.gnome.org/giounix20~2.0/) and you should use `GioUnix.InputStream` instead.
|
||||
|
||||
|
||||
# [Port Extensions to GNOME Shell 47](#port-extensions-to-gnome-shell-47)
|
||||
|
||||
## [Metadata](#metadata)
|
||||
|
||||
> [!TIP]
|
||||
> There were no relevant changes to `metadata.json` in GNOME 47.
|
||||
|
||||
## [Extension](#extension)
|
||||
|
||||
> [!TIP]
|
||||
> There were no relevant changes to `extension.js` in GNOME 47.
|
||||
|
||||
## [Preferences](#preferences)
|
||||
|
||||
`getPreferencesWidget` and `fillPreferencesWindow` in `prefs.js` are now awaited when the preference window is opened.
|
||||
|
||||
## [GNOME Shell](#gnome-shell)
|
||||
|
||||
### [`GtkNotificationDaemonAppSource`](#gtknotificationdaemonappsource)
|
||||
|
||||
There is a new dbus parameter for the `ui/notificationDaemon.js/GtkNotificationDaemonAppSource.constructor()`.
|
||||
|
||||
Also, there is a new `emitActionInvoked()` method in this class to emit the `ActionInvoked` signal to the dbus.
|
||||
|
||||
### [`overviewControls`](#overviewcontrols)
|
||||
|
||||
`ui/overviewControls.js/ControlsManagerLayout` class no longer uses `_spacing` property. Instead, the `spacing` parameter added to the `_computeWorkspacesBoxForState()` and `_getAppDisplayBoxForState()` methods.
|
||||
|
||||
### [`PopupBaseMenuItem`](#popupbasemenuitem)
|
||||
|
||||
`PopupBaseMenuItem` no longer uses `selected` style class name when the menu item is selected. Instead, it is using `:selected` pseudo class.
|
||||
|
||||
### [`misc/util.js`](#misc-util-js)
|
||||
|
||||
`ui/messageList.js/_fixMarkup()` moved to `misc/util.js/fixMarkup()`.
|
||||
|
||||
### [Accent Color](#accent-color)
|
||||
|
||||
GNOME Shell 47 added the accent color to the Settings. The selected accent color is stored in the `org.gnome.desktop.interface.accent-color`.
|
||||
|
||||
To apply the accent color in `stylesheet.css`, you can use `-st-accent-color` and `-st-accent-fg-color`:
|
||||
|
||||
~~~css
|
||||
#panel {
|
||||
background-color: -st-accent-color;
|
||||
}
|
||||
~~~
|
||||
|
||||
## [GJS](#gjs)
|
||||
|
||||
## [`Clutter.Color`](#clutter-color)
|
||||
|
||||
`Clutter.Color` has been removed from the API. Its functionality was merged into [`Cogl.Color()`](https://gjs-docs.gnome.org/cogl15~15/cogl.color), which should be used instead.
|
||||
|
||||
|
||||
|
||||
# [Port Extensions to GNOME Shell 48](#port-extensions-to-gnome-shell-48)
|
||||
|
||||
## [Metadata](#metadata)
|
||||
|
||||
> [!TIP]
|
||||
> There were no relevant changes to `metadata.json` in GNOME 48.
|
||||
|
||||
## [Extension](#extension)
|
||||
|
||||
> [!TIP]
|
||||
> There were no relevant changes to `extension.js` in GNOME 48.
|
||||
|
||||
## [Preferences](#preferences)
|
||||
|
||||
> [!TIP]
|
||||
> There were no relevant changes to `prefs.js` in GNOME 48.
|
||||
|
||||
## [GNOME Shell](#gnome-shell)
|
||||
|
||||
### [Custom Logger](#custom-logger)
|
||||
|
||||
For convenience, `ExtensionBase` added the new `getLogger()` method to provide a better logging experience for extensions. By using the logger class, you get the extension name as a prefix in the logs.
|
||||
|
||||
These are the supported methods, which you can use in the same way you use the `console` methods:
|
||||
|
||||
- `log()`
|
||||
- `warn()`
|
||||
- `error()`
|
||||
- `info()`
|
||||
- `debug()`
|
||||
- `assert()`
|
||||
- `trace()`
|
||||
- `group()`
|
||||
- `groupEnd()`
|
||||
|
||||
### [`InputSourceManager`](#inputsourcemanager)
|
||||
|
||||
The `/ui/status/keyboard.js/InputSourceManager._switchInputSource()` method now includes an `event` parameter:
|
||||
|Old|New|
|
||||
|---|---|
|
||||
|`_switchInputSource(display, window, binding)`|`_switchInputSource(display, window, event, binding)`|
|
||||
|
||||
### [Keyboard](#keyboard)
|
||||
|
||||
The `/ui/keyboard.js/Key` box pointer style class name changed from `keyboard-subkeys` to `keyboard-subkeys-boxpointer`.
|
||||
|
||||
Also `/ui/keyboard.js/EmojiSelection` key style class name changed from `keyboard-hide-symbolic` to `osk-hide-symbolic`.
|
||||
|
||||
The following icon names also changed for `/ui/keyboard.js/Keyboard`:
|
||||
|Old|New|
|
||||
|---|---|
|
||||
|`keyboard-caps-lock-symbolic`|`osk-caps-lock-symbolic`|
|
||||
|`keyboard-shift-symbolic`|`osk-shift-symbolic`|
|
||||
|
||||
### [`Cogl.SnippetHook`](#cogl-snippethook)
|
||||
|
||||
`Cogl.SnippetHook` is exposed in version 45 and later, use `Cogl.SnippetHook.FRAGMENT` instead of `Shell.SnippetHook.FRAGMENT`.
|
||||
|
||||
### [`MessageTray`](#messagetray)
|
||||
|
||||
`/ui/messageTray.js/Notification` now has a new `removeAction()` method.
|
||||
|
||||
### [`QuickMenuToggle`](#quickmenutoggle)
|
||||
|
||||
`/ui/quickSettings.js/QuickMenuToggle` changed style class names:
|
||||
|Old|New|
|
||||
|---|---|
|
||||
|`quick-menu-toggle`|`quick-toggle-has-menu`|
|
||||
|`quick-toggle-arrow icon-button`|`quick-toggle-menu-button icon-button`|
|
||||
|
||||
The new separator also has the `quick-toggle-separator` style class name.
|
||||
|
||||
### [`WindowManager`](#windowmanager)
|
||||
|
||||
These methods in `/ui/windowManager.js/WindowManager` now include an `event` parameter as their third argument:
|
||||
|
||||
- `_startSwitcher()`
|
||||
- `_startA11ySwitcher()`
|
||||
- `_switchToApplication()`
|
||||
- `_openNewApplicationWindow()`
|
||||
- `_showWorkspaceSwitcher()`
|
||||
|
||||
### [St Widgets Orientation](#st-widgets-orientation)
|
||||
|
||||
The `vertical` property of St widgets is deprecated and will be removed in a future GNOME Shell release (potentially version 50). Replace its usage with the `orientation` property, using [`Clutter.Orientation`](https://gjs-docs.gnome.org/clutter15~15-orientation/) values.
|
||||
|
||||
For example, use `orientation: Clutter.Orientation.VERTICAL` instead of `vertical: true`.
|
||||
|
||||
### [Time Limit and Break Manager](#time-limit-and-break-manager)
|
||||
|
||||
GNOME Shell 48 includes new `/misc/breakManager.js` and `/misc/timeLimitsManager.js` files for break reminders and screen time statistics.
|
||||
|
||||
The main instances of the break manager and time limit manager can be accessed directly in `/ui/main.js/` with:
|
||||
|Variable Name|Type|Description|
|
||||
|`breakManager`|`/misc/breakManager.js/BreakManager`|Tracks active/inactive time and signals break times.|
|
||||
|`breakManagerDispatcher`|`/misc/breakManager.js/BreakDispatcher`|Converts break status to notify a break event.|
|
||||
|`timeLimitsManager`|`/misc/timeLimitsManager.js/TimeLimitsManager`|Tracks active/inactive time and signals daily time limit reached.|
|
||||
|`timeLimitsDispatcher`|`/misc/timeLimitsManager.js/TimeLimitsDispatcher`|Converts time limit status to a notify event.|
|
||||
|
||||
There is also `/ui/shellDBus.js/ScreenTimeDBus` for the screen time D-Bus interface.
|
||||
|
||||
### [Notifications Per App Group](#notifications-per-app-group)
|
||||
|
||||
GNOME Shell 48 introduces grouped notifications, with the following changes:
|
||||
|
||||
- `/ui/calendar.js/NotificationMessage` has been moved to `/ui/messageList.js`.
|
||||
- `/ui/calendar.js/CalendarMessageList` no longer using section list and uses `/ui/messageList.js/MessageView` instead. `MessageView` is using `message-view` style class name.
|
||||
- `/ui/mpris.js/MediaMessage` has been moved to `/ui/messageList.js`.
|
||||
- `/ui/mpris.js/MediaSection` has been renamed to `/ui/mpris.js/MprisSource`.
|
||||
- The maximum number of notifications per source for `/ui/messageTray.js/MAX_NOTIFICATIONS_PER_SOURCE` has been increased from 3 to 10.
|
||||
- The `/ui/messageList.js/MessageListSection` class has been removed and replaced by the `NotificationMessageGroup`. This new class provides `expand()` and `collapse()` methods for controlling the expansion state of the message group. This class emits an `expand-toggle-requested` signal for that.
|
||||
|
||||
These are the style class names using by this class:
|
||||
|name|description|
|
||||
|----|-----------|
|
||||
|`.message-group-header`|Box layout for the message group header.|
|
||||
|`.message-notification-group`|Widget for the group expander layout.|
|
||||
|`.message-group-title`|Label for the message group title.|
|
||||
|`.message-collapse-button`|Button for collapsing the group's messages.|
|
||||
- `/ui/messageList.js/NotificationMessageGroup` uses `/ui/messageList.js/MessageGroupExpanderLayout` to manage its layout.
|
||||
|
||||
## [GJS](#gjs)
|
||||
|
||||
### [`Clutter.Image`](#clutter-image)
|
||||
|
||||
`Clutter.Image` has been removed and you can use [`St.ImageContent`](https://gjs-docs.gnome.org/st15~15/st.imagecontent) instead. There is no need to version check for `St.ImageContent` as it's already available in GNOME Shell 45 and higher.
|
||||
|
||||
`set_bytes()` and `set_data()` in `St.ImageContent` now have a new parameter, and the first parameter is now a `Cogl.Context`. You can get the instance from `global.stage.context.get_backend().get_cogl_context()`.
|
||||
|
||||
### [`Clutter.Stage.get_key_focus()`](#clutter-stage-get-key-focus)
|
||||
|
||||
`Clutter.Stage.get_key_focus()` now always matches the `key_focus` property. If no explicit focus is set, it will return `null`, rather than the `stage` itself.
|
||||
|
||||
### [`Meta`](#meta)
|
||||
|
||||
These `Meta` functions have been renamed and moved to the new namespace:
|
||||
|Old|New|
|
||||
|---|---|
|
||||
|`Meta.disable_unredirect_for_display`|`Meta.Compositor.disable_unredirect`|
|
||||
|`Meta.enable_unredirect_for_display`|`Meta.Compositor.enable_unredirect`|
|
||||
|`Meta.get_top_window_group_for_display`|`Meta.Compositor.get_top_window_group`|
|
||||
|`Meta.get_window_actors`|`Meta.Compositor.get_window_actors`|
|
||||
|`Meta.get_window_group_for_display`|`Meta.Compositor.get_window_group`|
|
||||
|`Meta.CursorTracker.get_for_display()`|`global.backend.get_cursor_tracker()`|
|
||||
|
||||
You can get the `Meta.Compositor` using `global.compositor`.
|
||||
|
||||
Also, these `Meta` functions have been removed, and you need to use `Clutter` instead:
|
||||
|Old|New|
|
||||
|---|---|
|
||||
|`Meta.get_clutter_debug_flags()`|`Clutter.get_debug_flags()`|
|
||||
|`Meta.add_clutter_debug_flags()`|`Clutter.add_debug_flags()`|
|
||||
|`Meta.remove_clutter_debug_flags()`|`Clutter.remove_debug_flags()`|
|
||||
|
||||
|
||||
|
||||
# Port Extensions to GNOME Shell 49 [](#port-extensions-to-gnome-shell-49)
|
||||
|
||||
## [Metadata](#metadata)
|
||||
|
||||
> [!TIP]
|
||||
> There were no relevant changes to `metadata.json` in GNOME 49.
|
||||
|
||||
## [Extension](#extension)
|
||||
|
||||
> [!TIP]
|
||||
> There were no relevant changes to `extension.js` in GNOME 49.
|
||||
|
||||
## [Preferences](#preferences)
|
||||
|
||||
> [!TIP]
|
||||
> There were no relevant changes to `prefs.js` in GNOME 49.
|
||||
|
||||
## [GNOME Shell](#gnome-shell)
|
||||
|
||||
### [Accessibility Toggles](#accessibility-toggles)
|
||||
|
||||
GNOME Shell 49 added some new quick toggles to the `ui/status/accessibility.js`:
|
||||
|
||||
- `HighContrastToggle`
|
||||
- `MagnifierToggle`
|
||||
- `LargeTextToggle`
|
||||
- `ScreenReaderToggle`
|
||||
- `ScreenKeyboardToggle`
|
||||
- `VisualBellToggle`
|
||||
- `StickyKeysToggle`
|
||||
- `SlowKeysToggle`
|
||||
- `BounceKeysToggle`
|
||||
- `MouseKeysToggle`
|
||||
|
||||
For this version, they are only used in the GDM accessibility menu, but they will eventually find their way into quick settings in GNOME Shell 50.
|
||||
|
||||
Since the accessibility button moved to bottom of GDM, `a11y` was removed from the right side of GDM panel.
|
||||
|
||||
Also the accessibility indicator icon name renamed from `org.gnome.Settings-accessibility-symbolic` to `accessibility-menu-symbolic`.
|
||||
|
||||
### [Brightness Manager](#brightness-manager)
|
||||
|
||||
GNOME Shell 49 includes the new `/misc/brightnessManager.js` file for monitor brightness management which was previously handled in gnome-settings-daemon.
|
||||
|
||||
The main instances of the brightness manager can be accessed with `Main.brightnessManager`. The Quick settings brightness slider in `ui/status/brightness.js` was adjusted accordingly and a `org.gnome.Shell.Brightness` D-Bus interface was added in `ui/shellDBus.js`.
|
||||
|
||||
### [Do Not Disturb Toggle](#do-not-disturb-toggle)
|
||||
|
||||
The `DoNotDisturbSwitch` class in `ui/calendar.js` has been removed. Its functionality has been replaced by the new `DoNotDisturbToggle` quick settings toggle button, which you can now find in `ui/status/doNotDisturb.js`.
|
||||
|
||||
Also, message indicator in date menu, now only uses `message-indicator-symbolic` icon name when it is visible.
|
||||
|
||||
### [`WorkspaceSwitcherPopup`](#workspaceswitcherpopup)
|
||||
|
||||
There's a new `MonitorWorkspaceSwitcherPopup` class, in `ui/workspaceSwitcherPopup.js`. The `WorkspaceSwitcherPopup` now uses this new class to create workspace switcher popups for all or primary monitors.
|
||||
|
||||
As a result of this change, the `_redisplay()` method in `WorkspaceSwitcherPopup` has been renamed to `_redisplayAllPopups()`.
|
||||
|
||||
### [`Meta.Rectangle`](#meta-rectangle)
|
||||
|
||||
Since GNOME Shell 45, `Meta.Rectangle` was deprecated. GNOME Shell 49 removed `Meta.Rectangle`, and if you are still using it, it should be replaced with `Mtk.Rectangle`.
|
||||
|
||||
### [Screenshot Notification](#screenshot-notification)
|
||||
|
||||
Screenshot now uses its own notification source. You can get the source with `ui/screenshot.js/getScreenshotNotificationSource()`.
|
||||
|
||||
### [App Menu Button](#app-menu-button)
|
||||
|
||||
GNOME Shell 49 removes the `AppMenuButton` from `js/ui/panel.js`. This button was unused since GNOME Shell 45. So if your extension still relies on that code, you'll need to implement or copy it.
|
||||
|
||||
### [`CaptivePortalHandler`](#captiveportalhandler)
|
||||
|
||||
`CaptivePortalHandler._portalHelperDone()` method in `ui/status/network.js` renamed to `_portalHelperStatusChanged()` and it's getting called when the `StatusChanged` signal is getting emitted from portal helper.
|
||||
|
||||
### [`OsdWindowManager`](#osdwindowmanager)
|
||||
|
||||
To show the OSD on any combination of monitors, `OsdWindowManager` in `ui/osdWindow.js` now uses three show methods:
|
||||
|
||||
- `show()`
|
||||
- `showOne()`
|
||||
- `showAll()`
|
||||
|
||||
You can use `showOne()` method if you still want to use the monitor index.
|
||||
|
||||
### [`ScreenShield`](#screenshield)
|
||||
|
||||
To adapt `Meta.CursorTracker` changes, `ScreenShield` class in `ui/screenShield.js` added a new `_hidePointer()` method to hide the pointer.
|
||||
|
||||
### [`NotificationsBox` players](#notificationsbox-players)
|
||||
|
||||
GNOME Shell 49 added the ability to quickly pause or resume media in unlock dialog. To do that, `NotificationsBox` class in `ui/unlockDialog.js` added two methods: `_addPlayer()` and `_removePlayer()`. Added players are stored in `_players` property.
|
||||
|
||||
### [Debugging](#debugging)
|
||||
|
||||
Since X11 is disabled by default, the nested mode is no longer available.
|
||||
|
||||
To get a nested shell instance you can use development kit:
|
||||
~~~sh
|
||||
dbus-run-session gnome-shell --devkit --wayland
|
||||
~~~
|
||||
|
||||
## [GJS](#gjs)
|
||||
|
||||
### [`Meta`](#meta)
|
||||
|
||||
#### [`Meta.WaylandClient`](#meta-waylandclient)
|
||||
|
||||
`Meta.WaylandClient` was redesigned and now exists for all wayland clients. The previous use case of spawning a subprocess as a wayland client can be achieved using `Meta.WaylandClient.new_subprocess`. Previous `Meta.WaylandClient` functionality can be achieved via `Meta.WaylandClient.owns_window` and new API exposed on `Meta.Window`.
|
||||
|
||||
#### [`Meta.LogicalMonitor` and `Meta.Monitor`](#meta-logicalmonitor-and-meta-monitor)
|
||||
|
||||
`Meta.LogicalMonitor` and `Meta.Monitor` have been added. Their use is rather limited at the moment but they are compatible with monitor related functions on `Meta.Display` via `Meta.LogicalMonitor.get_number`. The monitor related functions on `Meta.Display` will get phased out in future releases. They can be accessed via `meta.MonitorManager.get_logical_monitors`, `meta.MonitorManager.get_monitors`, `Meta.LogicalMonitor.get_monitors` and `Meta.Backend.get_current_logical_monitor`.
|
||||
|
||||
#### [`Meta.Backlight`](#meta-backlight)
|
||||
|
||||
`Meta.Backlight` has been added. The backlights can be accessed via `Meta.Monitor.get_backlight` and replace the backlight handling previously done in gnome-settings-daemon. See the GNOME Shell section for the abstraction over the backlights.
|
||||
|
||||
#### [`Meta.Window`](#meta-window)
|
||||
|
||||
`Meta.Window` removes the `Meta.MaximizeFlags` from the parameters for `maximize()` and `unmaximize()`. Also `get_maximized()` is removed and you can use `is_maximized()` instead. To maximize or unmaximize the window horizontally or vertically, you can call new methods `set_maximize_flags()` and `set_unmaximize_flags()`.
|
||||
|
||||
`Meta.Window.set_type`, `Meta.Window.hide_from_window_list` and `Meta.Window.show_in_window_list` were added which replaces functionality previously existing on `Meta.WaylandClient`.
|
||||
|
||||
#### [`Meta.CursorTracker`](#meta-cursortracker)
|
||||
|
||||
`Meta.CursorTracker.set_pointer_visible()` method has been replaced by `inhibit_cursor_visibility()` and `uninhibit_cursor_visibility()`.
|
||||
|
||||
### [Clutter](#clutter)
|
||||
|
||||
#### [`Clutter.ClickAction()` and `Clutter.TapAction()`](#clutter-clickaction-and-clutter-tapaction)
|
||||
|
||||
`Clutter.ClickAction()` and `Clutter.TapAction()` have been removed. You should now use `Clutter.ClickGesture()` and `Clutter.LongPressGesture()` instead.
|
||||
|
||||
## [Extension Tools](#extension-tools)
|
||||
|
||||
`gnome-extensions` added the `upload` command to ease the upload process to EGO:
|
||||
~~~sh
|
||||
gnome-extensions upload --accept-tos
|
||||
~~~
|
||||
|
||||
You can then enter your EGO username and password. If you are using it in CI, you can use the `--user`, `--password` or `--password-file` options.
|
||||
|
||||
Please take measures to avoid exposing your password. Using the password in a command option risks exposing it in logs, the environment or the filesystem.
|
||||
|
||||
You can read more about the `upload` command in the manual (`man gnome-extensions`).
|
||||
|
||||
|
||||
|
||||
# [Port Extensions to GNOME Shell 50](#port-extensions-to-gnome-shell-50)
|
||||
|
||||
## [Metadata](#metadata)
|
||||
|
||||
> [!TIP]
|
||||
> There were no relevant changes to `metadata.json` in GNOME 50.
|
||||
|
||||
## [Extension](#extension)
|
||||
|
||||
> [!TIP]
|
||||
> There were no relevant changes to `extension.js` in GNOME 50.
|
||||
|
||||
## [Preferences](#preferences)
|
||||
|
||||
> [!TIP]
|
||||
> There were no relevant changes to `prefs.js` in GNOME 50.
|
||||
|
||||
## [GNOME Shell](#gnome-shell)
|
||||
|
||||
### [`easeAsync()`](#easeasync)
|
||||
|
||||
GNOME Shell 50 adds a new `easeAsync()` function which is the async variant of `ease()`. When the transition is completed, the promise is resolved:
|
||||
~~~js
|
||||
await actor.easeAsync({
|
||||
translation_y: 0,
|
||||
duration: 300,
|
||||
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
||||
});
|
||||
~~~
|
||||
|
||||
For transition interruption, the promise is rejected, and you can catch the CANCELLED error.
|
||||
|
||||
If you want to ignore CANCELLED errors but otherwise log them via `logError()`, there is a new `logErrorUnlessCancelled()` helper function in `misc/errorUtils.js`.
|
||||
|
||||
### [Time Limits Manager and Parental Control](#time-limits-manager-and-parental-control)
|
||||
|
||||
When the parental controls session limits are reached, GNOME Shell 50 shows a new widget instead of the unlock prompt.
|
||||
|
||||
`gdm/authPrompt.js` has a new `ParentalControlsShield` class for this widget. `AuthPrompt._parentalControlsShield` holds the instance after calling `AuthPrompt.setAuthBlocked()`.
|
||||
|
||||
`misc/timeLimitsManager.js` holds the `parentalControlsManager` and calls the `TimeLimitsManager._onSessionLimitsChanged()` on `session-limits-changed` signal.
|
||||
|
||||
Also, you can use these properties from `TimeLimitsManager` to check whether the feature is enabled:
|
||||
|
||||
- `parentalControlsSessionLimitsEnabled`
|
||||
- `wellbeingDailyLimitEnabled`
|
||||
|
||||
You can access the instance with `Main.timeLimitsManager`.
|
||||
|
||||
In `parentalControlsManager.js`, you can get the `ParentalControlsManager` instance by `getDefault()` function.
|
||||
|
||||
The `ParentalControlsManager` adds `appFilterEnabled()` and `sessionLimitsEnabled()` to check the status. Additionally, there is `anyParentalControlsEnabled` property to check whether any parental controls are supported and enabled for the current user.
|
||||
|
||||
### [Keyboard Manager](#keyboard-manager)
|
||||
|
||||
`misc/keyboardManager.js` removed `releaseKeyboard()` and `holdKeyboard()` since they don't have any input-method caller.
|
||||
|
||||
### [Input Indicator](#input-indicator)
|
||||
|
||||
`OutputIndicator` in `ui/status/volume.js` adds a new `_updatePrivacyIndicator()` method since the indicator no longer uses `privacy-indicator` style classname when the microphone is muted.
|
||||
|
||||
### [Calendar](#calendar)
|
||||
|
||||
There is a new `org.gnome.desktop.calendar.week-start-day` settings and `ui/calendar.js` adds a new `_getWeekStartDay()` function for that.
|
||||
|
||||
### [Restart](#restart)
|
||||
|
||||
`show-restart-message` and `restart` signals aren't emitted in `global.display` anymore.
|
||||
|
||||
Also, `RunDialog._restart()` method has been removed form `ui/runDialog.js` since restart is only available on X11 and GNOME Shell 50 removed X11 support.
|
||||
|
||||
### [Slider](#slider)
|
||||
|
||||
`Slider` in `ui/slider.js` added `addMark()` and `clearMarks()` helper methods.
|
||||
|
||||
### [`gnome-extensions`](#gnome-extensions)
|
||||
|
||||
The `install` command for `gnome-extensions` has added a `print-uuid` flag. With this flag, the extension UUID is printed on success.
|
||||
|
||||
### [`gnome-shell-test-tool`](#gnome-shell-test-tool)
|
||||
|
||||
`gnome-shell-test-tool` added the `--extension` option to install and enable an extension zip package before starting GNOME Shell:
|
||||
~~~sh
|
||||
gnome-shell-test-tool --extension extension-package.zip tests/testMyExtension.js
|
||||
~~~
|
||||
|
||||
## [GJS](#gjs)
|
||||
|
||||
### [One-shot timeout and idle functions](#one-shot-timeout-and-idle-functions)
|
||||
|
||||
To make the idle and timeouts introspectable, and make them clear that they are only called once, `GLib` added three new one-shot functions:
|
||||
|
||||
- `GLib.idle_add_once()`
|
||||
- `GLib.timeout_add_once()`
|
||||
- `GLib.timeout_add_seconds_once()`
|
||||
|
||||
|
||||
MIT Licensed | GJS, A GNOME Project
|
||||
|
||||
Copyright © 2024 gjs.guide contributors
|
||||
@@ -0,0 +1,240 @@
|
||||
# [Preferences](#preferences)
|
||||
|
||||
> [!WARNING]
|
||||
> This documentation is for GNOME 45 and later. Please see the [Legacy Documentation](./../upgrading/legacy-documentation.html#preferences) for previous versions.
|
||||
|
||||
Creating a preferences window for your extension allows users to configure the appearance and behavior of the extension. It can also contain documentation, a changelog and other information.
|
||||
|
||||
The user interface will be created with [GTK4](https://gjs-docs.gnome.org/gtk40/) and [Adwaita](https://gjs-docs.gnome.org/adw1/), which has many elements specifically for settings and configuration. You may consider looking through the GNOME Human Interface Guidelines, or widget galleries for ideas.
|
||||
|
||||
#### [See Also](#see-also)
|
||||
|
||||
- [GNOME HIG](https://developer.gnome.org/hig)
|
||||
- [GTK4 Widget Gallery](https://docs.gtk.org/gtk4/visual_index.html)
|
||||
- [Adwaita Widget Gallery](https://gnome.pages.gitlab.gnome.org/libadwaita/doc/1-latest/widget-gallery.html)
|
||||
|
||||
## [GSettings](#gsettings)
|
||||
|
||||
[GSettings](https://gjs-docs.gnome.org/gio20/gio.settings) provides a simple, extremely fast API for storing application settings, that can also be used by GNOME Shell extensions.
|
||||
|
||||
### [Creating a Schema](#creating-a-schema)
|
||||
|
||||
> [!TIP]
|
||||
> A GSettings schema ID with the prefix `org.gnome.shell.extensions` and a path with the prefix `/org/gnome/shell/extensions` is the standard for extensions.
|
||||
|
||||
Schema files describe the types and default values of a particular group of settings, using the same type format as [GVariant](https://docs.gtk.org/glib/gvariant-format-strings.html). The basename of the schema file should be the same as the schema ID.
|
||||
|
||||
Start by creating a `schemas/` subdirectory to hold the GSettings schema:
|
||||
|
||||
~~~sh
|
||||
$ mkdir -p ~/.local/share/gnome-shell/extensions/example@gjs.guide/schemas
|
||||
$ cd ~/.local/share/gnome-shell/extensions/example@gjs.guide
|
||||
$ touch schemas/org.gnome.shell.extensions.example.gschema.xml
|
||||
~~~
|
||||
|
||||
Then using your editor, create a schema describing the keys and type of values they can hold:
|
||||
|
||||
~~~xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<schemalist>
|
||||
<schema id="org.gnome.shell.extensions.example" path="/org/gnome/shell/extensions/example/">
|
||||
<key name="show-indicator" type="b">
|
||||
<default>true</default>
|
||||
</key>
|
||||
</schema>
|
||||
</schemalist>
|
||||
~~~
|
||||
|
||||
### [Compiling a Schema](#compiling-a-schema)
|
||||
|
||||
> [!TIP]
|
||||
> As of GNOME 44, settings schemas are compiled automatically for extensions installed with the `gnome-extensions` tool, [GNOME Extensions](https://extensions.gnome.org) website, or a compatible application like [Extension Manager](https://flathub.org/apps/com.mattjakeman.ExtensionManager).
|
||||
|
||||
Before it can be used, a GSettings schema must be compiled. If not using the `gnome-extensions` tool, `glib-compile-schemas` can be used to compile schemas:
|
||||
|
||||
~~~sh
|
||||
$ cd ~/.local/share/gnome-shell/extensions/example@gjs.guide
|
||||
$ glib-compile-schemas schemas/
|
||||
~~~
|
||||
|
||||
## [Integrating Settings](#integrating-settings)
|
||||
|
||||
> [!TIP]
|
||||
> For complex settings, see the [GVariant Guide](./../../guides/glib/gvariant.html) for examples of what data types can be stored with GSettings.
|
||||
|
||||
GSettings is supported by a backend, which allows different processes to share read-write access to the same settings values. This means that while `extension.js` is reading and applying settings, `prefs.js` can be reading and modifying them.
|
||||
|
||||
Usually this is a one-way relationship, but it is possible to change settings from `extension.js` as well, if necessary.
|
||||
|
||||
### [`metadata.json`](#metadata-json)
|
||||
|
||||
The recommended method for defining the schema ID for an extension is by defining the [`settings-schema`](./../overview/anatomy.html#settings-schema) key in `metadata.json`. This allows GNOME Shell to automatically use the correct schema ID when `ExtensionsBase.prototype.getSettings()` is called.
|
||||
|
||||
~~~json
|
||||
{
|
||||
"uuid": "example@gjs.guide",
|
||||
"name": "Example Extension",
|
||||
"description": "An example extension with preferences",
|
||||
"shell-version": [ "45" ],
|
||||
"url": "https://gjs.guide/extensions",
|
||||
"gettext-domain": "example@gjs.guide",
|
||||
"settings-schema": "org.gnome.shell.extensions.example"
|
||||
}
|
||||
~~~
|
||||
|
||||
Otherwise, you should call `ExtensionBase.prototype.getSettings()` with a valid GSettings schema ID.
|
||||
|
||||
<details>
|
||||
<summary>`getSettings()` in `extension.js`</summary>
|
||||
|
||||
~~~js
|
||||
import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js';
|
||||
|
||||
export default class ExampleExtension extends Extension {
|
||||
enable() {
|
||||
this._settings = this.getSettings('org.gnome.shell.extensions.example');
|
||||
}
|
||||
|
||||
disable() {
|
||||
this._settings = null;
|
||||
}
|
||||
}
|
||||
~~~
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>`getSettings()` in `prefs.js`</summary>
|
||||
|
||||
~~~js
|
||||
import {ExtensionPreferences} from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js';
|
||||
|
||||
export default class ExamplePreferences extends ExtensionPreferences {
|
||||
fillPreferencesWindow(window) {
|
||||
window._settings = this.getSettings('org.gnome.shell.extensions.example');
|
||||
}
|
||||
}
|
||||
~~~
|
||||
|
||||
<details>
|
||||
|
||||
### [`extension.js`](#extension-js)
|
||||
|
||||
Methods like [`Gio.Settings.get_boolean()`](https://gjs-docs.gnome.org/gio20/gio.settings#method-get_boolean) are used for native values, while methods like [`Gio.Settings.set_value()`](https://gjs-docs.gnome.org/gio20/gio.settings#method-set_value) can be used to work with `GLib.Variant` values directly.
|
||||
|
||||
Simple types like `Boolean` can be bound directly to a [GObject Property](./../../guides/gobject/basics.html#gobject-properties) with [`Gio.Settings.bind()`](https://gjs-docs.gnome.org/gio20/gio.settings#method-bind). For JavaScript properties and other use cases, you can connect to [`Gio.Settings::changed`](https://gjs-docs.gnome.org/gio20/gio.settings#signal-changed) with the property name as the signal detail (e.g. `changed::show-indicator`).
|
||||
'
|
||||
~~~js
|
||||
import Gio from 'gi://Gio';
|
||||
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';
|
||||
|
||||
export default class ExampleExtension extends Extension {
|
||||
enable() {
|
||||
// Create a panel button
|
||||
this._indicator = new PanelMenu.Button(0.0, this.metadata.name, false);
|
||||
|
||||
// Add an icon
|
||||
const icon = new St.Icon({
|
||||
icon_name: 'face-laugh-symbolic',
|
||||
style_class: 'system-status-icon',
|
||||
});
|
||||
this._indicator.add_child(icon);
|
||||
|
||||
// Add the indicator to the panel
|
||||
Main.panel.addToStatusArea(this.uuid, this._indicator);
|
||||
|
||||
// Add a menu item to open the preferences window
|
||||
this._indicator.menu.addAction(_('Preferences'),
|
||||
() => this.openPreferences());
|
||||
|
||||
// Create a new GSettings object, and bind the "show-indicator"
|
||||
// setting to the "visible" property.
|
||||
this._settings = this.getSettings();
|
||||
this._settings.bind('show-indicator', this._indicator, 'visible',
|
||||
Gio.SettingsBindFlags.DEFAULT);
|
||||
|
||||
// Watch for changes to a specific setting
|
||||
this._settings.connect('changed::show-indicator', (settings, key) => {
|
||||
console.debug(`${key} = ${settings.get_value(key).print(true)}`);
|
||||
});
|
||||
}
|
||||
|
||||
disable() {
|
||||
this._indicator?.destroy();
|
||||
this._indicator = null;
|
||||
this._settings = null;
|
||||
}
|
||||
}
|
||||
~~~
|
||||
|
||||
### [`prefs.js`](#prefs-js)
|
||||
|
||||
> [!TIP]
|
||||
> Extension preferences run in a separate process, without access to code in GNOME Shell, and are written with [GTK4](https://gjs-docs.gnome.org/gtk40/) and [Adwaita](https://gjs-docs.gnome.org/adw1/).
|
||||
|
||||
The [Adwaita Widget Gallery](https://gnome.pages.gitlab.gnome.org/libadwaita/doc/1-latest/widget-gallery.html) has screenshots of the many widgets it includes that make building a preferences dialog easier. You may also use any of the [GNOME APIs](https://gjs-docs.gnome.org) that are compatible with [GTK4](https://gjs-docs.gnome.org/gtk40/) (notable exceptions include `Meta`, `Clutter`, `Shell` and `St`).
|
||||
|
||||
Extensions should implement `fillPreferencesWindow()`, which is passed a new instance of [`Adw.PreferencesWindow`](https://gjs-docs.gnome.org/adw1/adw.preferenceswindow) before it is displayed to the user.
|
||||
|
||||
~~~js
|
||||
import Gio from 'gi://Gio';
|
||||
import Adw from 'gi://Adw';
|
||||
|
||||
import {ExtensionPreferences, gettext as _} from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js';
|
||||
|
||||
export default class ExamplePreferences extends ExtensionPreferences {
|
||||
fillPreferencesWindow(window) {
|
||||
// Create a preferences page, with a single group
|
||||
const page = new Adw.PreferencesPage({
|
||||
title: _('General'),
|
||||
icon_name: 'dialog-information-symbolic',
|
||||
});
|
||||
window.add(page);
|
||||
|
||||
const group = new Adw.PreferencesGroup({
|
||||
title: _('Appearance'),
|
||||
description: _('Configure the appearance of the extension'),
|
||||
});
|
||||
page.add(group);
|
||||
|
||||
// Create a new preferences row
|
||||
const row = new Adw.SwitchRow({
|
||||
title: _('Show Indicator'),
|
||||
subtitle: _('Whether to show the panel indicator'),
|
||||
});
|
||||
group.add(row);
|
||||
|
||||
// Create a settings object and bind the row to the `show-indicator` key
|
||||
window._settings = this.getSettings();
|
||||
window._settings.bind('show-indicator', row, 'active',
|
||||
Gio.SettingsBindFlags.DEFAULT);
|
||||
}
|
||||
}
|
||||
~~~
|
||||
|
||||
## [Testing the Preferences](#testing-the-preferences)
|
||||
|
||||
The preference dialog can be opened with `gnome-extensions prefs`, or any other tool for managing extensions:
|
||||
|
||||

|
||||
|
||||
### [Debugging `prefs.js`](#debugging-prefs-js)
|
||||
|
||||
Because preferences are not run within `gnome-shell` but in a separate process, the logs will appear in the `gjs` process:
|
||||
~~~sh
|
||||
$ journalctl -f -o cat /usr/bin/gjs
|
||||
~~~
|
||||
|
||||
### [Debugging GSettings](#debugging-gsettings)
|
||||
|
||||
To help debug the changes made by `prefs.js` to GSettings values, you can use `dconf` to watch the path for your settings:
|
||||
~~~sh
|
||||
$ dconf watch /org/gnome/shell/extensions/example
|
||||
~~~
|
||||
|
||||
MIT Licensed | GJS, A GNOME Project
|
||||
Copyright © 2024 gjs.guide contributors
|
||||
@@ -0,0 +1,493 @@
|
||||
# [GNOME Shell Extensions Review Guidelines](#gnome-shell-extensions-review-guidelines)
|
||||
|
||||
> [!TIP>
|
||||
> If this is your first time writing an extension, please see the [Getting Started](./../development/creating.html) guide.
|
||||
|
||||
These are the guidelines for developers who would like their extensions distributed on the [extensions.gnome.org](https://extensions.gnome.org). Extensions are reviewed carefully for malicious code, malware and security risks, but not for bugs.
|
||||
|
||||
## [General Guidelines](#general-guidelines)
|
||||
|
||||
There are three basic guidelines for the operation of an extension:
|
||||
|
||||
- Don't create or modify anything before `enable()` is called
|
||||
- Use `enable()` to create objects, connect signals and add main loop sources
|
||||
- Use `disable()` to cleanup anything done in `enable()`
|
||||
|
||||
These general tips will help your extension pass review:
|
||||
|
||||
- Write clean code, with consistent indentation and style
|
||||
- Use modern features like ES6 classes and `async`/`await`
|
||||
- Use a linter to check for logic and syntax errors
|
||||
- Ask other developers for advice on [Matrix](https://matrix.to/#/#extensions:gnome.org)
|
||||
|
||||
## [Rules](#rules)
|
||||
|
||||
### [Only use initialization for static resources](#only-use-initialization-for-static-resources)
|
||||
|
||||
Extensions **MUST NOT** create any objects, connect any signals, add any main loop sources or modify GNOME Shell during initialization.
|
||||
|
||||
In GNOME 45 and later, initialization happens when `extension.js` is imported and the `Extension` class is constructed (e.g. `constructor()` is called).
|
||||
|
||||
<details>
|
||||
<summary>GNOME Shell 45 and later</summary>
|
||||
|
||||
~~~js
|
||||
import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js';
|
||||
|
||||
export default class ExampleExtension extends Extension {
|
||||
constructor(metadata) {
|
||||
super(metadata);
|
||||
|
||||
// DO NOT create objects, connect signals or add main loop sources here
|
||||
}
|
||||
|
||||
enable() {
|
||||
// Create objects, connect signals, create main loop sources, etc.
|
||||
}
|
||||
|
||||
disable() {
|
||||
// Destroy objects, disconnect signals, remove main loop sources, etc.
|
||||
}
|
||||
}
|
||||
~~~
|
||||
|
||||
</details>
|
||||
|
||||
In GNOME 44 and earlier, initialization happens when `extension.js` is imported and the `init()` function is called. When using the `Extension` class pattern, the `constructor()` is also called.
|
||||
|
||||
<details>
|
||||
<summary>GNOME Shell 44 and earlier</summary>
|
||||
|
||||
~~~js
|
||||
const ExtensionUtils = imports.misc.extensionUtils;
|
||||
|
||||
class Extension {
|
||||
constructor() {
|
||||
// DO NOT create objects, connect signals or add main loop sources here
|
||||
}
|
||||
|
||||
enable() {
|
||||
// Create objects, connect signals, create main loop sources, etc.
|
||||
}
|
||||
|
||||
disable() {
|
||||
// Destroy objects, disconnect signals, remove main loop sources, etc.
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
// Initialize translations before returning the extension object
|
||||
ExtensionUtils.initTranslations();
|
||||
|
||||
return new Extension();
|
||||
}
|
||||
~~~
|
||||
|
||||
</details>
|
||||
|
||||
Extensions **MAY** create static data structures and instances of built-in JavaScript objects (such as `Regexp()` and `Map()`), but all dynamically stored memory must be cleared or freed in `disable()` (e.g. `Map.prototype.clear()`). All GObject classes, such as `Gio.Settings` and `St.Widget` are disallowed.
|
||||
|
||||
<details>
|
||||
<summary>Static Objects</summary>
|
||||
|
||||
~~~js
|
||||
import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js';
|
||||
|
||||
// Extensions **MAY** construct built-in JavaScript types in the module scope
|
||||
const FoobarCache = new Map();
|
||||
|
||||
// Extensions **MAY** create static data structures in the module scope
|
||||
const FoobarState = {
|
||||
DEFAULT: 0,
|
||||
CHANGED: 1,
|
||||
};
|
||||
|
||||
class Foobar {
|
||||
state = FoobarState.DEFAULT;
|
||||
}
|
||||
|
||||
let DEFAULT_FOOBAR = null;
|
||||
|
||||
export default class ExampleExtension extends Extension {
|
||||
constructor(metadata) {
|
||||
super(metadata);
|
||||
|
||||
// Extensions **MAY** create and store a reasonable amount of static
|
||||
// data during initialization
|
||||
this._state = {
|
||||
enabled: false,
|
||||
};
|
||||
}
|
||||
|
||||
enable() {
|
||||
// Extensions **MAY** construct instances of classes and assign them
|
||||
// to variables in the module scope
|
||||
if (DEFAULT_FOOBAR === null)
|
||||
DEFAULT_FOOBAR = new Foobar();
|
||||
|
||||
// Extensions **MAY** dynamically store data in the module scope
|
||||
for (let i = 0; i < 10; i++)
|
||||
FoobarCache.set(`${i}`, new Date());
|
||||
|
||||
this._state.enabled = true;
|
||||
}
|
||||
|
||||
disable() {
|
||||
// Extensions **MUST** destroy instances of classes assigned to
|
||||
// variables in the module scope
|
||||
if (DEFAULT_FOOBAR instanceof Foobar)
|
||||
DEFAULT_FOOBAR = null;
|
||||
|
||||
// Extensions **MUST** clear dynamically stored data in the module scope
|
||||
FoobarCache.clear();
|
||||
|
||||
this._state.enabled = false;
|
||||
}
|
||||
}
|
||||
~~~
|
||||
|
||||
</details>
|
||||
|
||||
### [Destroy all objects](#destroy-all-objects)
|
||||
|
||||
Any objects or widgets created by an extension **MUST** be destroyed in `disable()`.
|
||||
|
||||
<details>
|
||||
<summary>GNOME 45 and later</summary>
|
||||
|
||||
~~~js
|
||||
import St from 'gi://St';
|
||||
|
||||
import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js';
|
||||
|
||||
export default class ExampleExtension extends Extension {
|
||||
enable() {
|
||||
this._widget = new St.Widget();
|
||||
}
|
||||
|
||||
disable() {
|
||||
this._widget?.destroy();
|
||||
this._widget = null;
|
||||
}
|
||||
}
|
||||
~~~
|
||||
|
||||
<details>
|
||||
|
||||
<details>
|
||||
<summary>GNOME 44 and earlier</summary>
|
||||
|
||||
~~~js
|
||||
const St = imports.gi.St;
|
||||
|
||||
class Extension {
|
||||
enable() {
|
||||
this._widget = new St.Widget();
|
||||
}
|
||||
|
||||
disable() {
|
||||
if (this._widget) {
|
||||
this._widget.destroy();
|
||||
this._widget = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
return new Extension();
|
||||
}
|
||||
~~~
|
||||
|
||||
</details>
|
||||
|
||||
### [Disconnect all signals](#disconnect-all-signals)
|
||||
|
||||
Any signal connections made by an extension **MUST** be disconnected in `disable()`:
|
||||
|
||||
<details>
|
||||
<summary>GNOME 45 and later</summary>
|
||||
|
||||
~~~js
|
||||
import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js';
|
||||
|
||||
export default class ExampleExtension extends Extension {
|
||||
enable() {
|
||||
this._handlerId = global.settings.connect('changed::favorite-apps', () => {
|
||||
console.log('app favorites changed');
|
||||
});
|
||||
}
|
||||
|
||||
disable() {
|
||||
if (this._handlerId) {
|
||||
global.settings.disconnect(this._handlerId);
|
||||
this._handlerId = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
~~~
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>GNOME 44 and earlier</summary>
|
||||
|
||||
~~~js
|
||||
class Extension {
|
||||
enable() {
|
||||
this._handlerId = global.settings.connect('changed::favorite-apps', () => {
|
||||
console.log('app favorites changed');
|
||||
});
|
||||
}
|
||||
|
||||
disable() {
|
||||
if (this._handlerId) {
|
||||
global.settings.disconnect(this._handlerId);
|
||||
this._handlerId = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
return new Extension();
|
||||
}
|
||||
~~~
|
||||
|
||||
</details>
|
||||
|
||||
### [Remove main loop sources](#remove-main-loop-sources)
|
||||
|
||||
Any main loop sources created **MUST** be removed in `disable()`.
|
||||
|
||||
Note that all sources **MUST** be removed in `disable()`, even if the callback function will eventually return `false` or `GLib.SOURCE_REMOVE`.
|
||||
|
||||
<details>
|
||||
<summary>GNOME 45 and later</summary>
|
||||
|
||||
~~~js
|
||||
import GLib from 'gi://GLib';
|
||||
|
||||
import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js';
|
||||
|
||||
export default class ExampleExtension extends Extension {
|
||||
enable() {
|
||||
this._sourceId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 5, () => {
|
||||
console.log('Source triggered');
|
||||
|
||||
return GLib.SOURCE_CONTINUE;
|
||||
});
|
||||
}
|
||||
|
||||
disable() {
|
||||
if (this._sourceId) {
|
||||
GLib.Source.remove(this._sourceId);
|
||||
this._sourceId = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
~~~
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>GNOME 44 and earlier</summary>
|
||||
|
||||
~~~js
|
||||
const GLib = imports.gi.GLib;
|
||||
|
||||
class Extension {
|
||||
enable() {
|
||||
this._sourceId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 5, () => {
|
||||
console.log('Source triggered');
|
||||
|
||||
return GLib.SOURCE_CONTINUE;
|
||||
});
|
||||
}
|
||||
|
||||
disable() {
|
||||
if (this._sourceId) {
|
||||
GLib.Source.remove(this._sourceId);
|
||||
this._sourceId = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
return new Extension();
|
||||
}
|
||||
~~~
|
||||
|
||||
</details>
|
||||
|
||||
### [Do not use deprecated modules](#do-not-use-deprecated-modules)
|
||||
|
||||
Extensions **MUST NOT** import deprecated modules.
|
||||
|Deprecated|ModuleReplacement|
|
||||
|----------|-----------------|
|
||||
|`ByteArray`|[`TextDecoder`](https://gjs-docs.gnome.org/gjs/encoding.md#textdecoder) and [`TextEncoder`](https://gjs-docs.gnome.org/gjs/encoding.md#textencoder)|
|
||||
|`Lang`|[ES6 Classes](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Classes) and [`Function.prototype.bind()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind)|
|
||||
|`Mainloop`|[`GLib.timeout_add()`](https://gjs-docs.gnome.org/glib20/glib.timeout_add), [`setTimeout()`](https://developer.mozilla.org/docs/Web/API/Window/setTimeout), etc|
|
||||
|
||||
### [Do not import GTK libraries in GNOME Shell](#do-not-import-gtk-libraries-in-gnome-shell)
|
||||
|
||||
Extensions **MUST NOT** import `Gdk`, `Gtk` or `Adw` in the GNOME Shell process.
|
||||
|
||||
These libraries are only for use in the preferences process (`prefs.js`) and will conflict with `Clutter` and other libraries used by GNOME Shell.
|
||||
|
||||
### [Do not import GNOME Shell libraries in Preferences](#do-not-import-gnome-shell-libraries-in-preferences)
|
||||
|
||||
Extensions **MUST NOT** import `Clutter`, `Meta`, `St` or `Shell` in the preferences process.
|
||||
|
||||
These libraries are only for use in the GNOME Shell process (`extension.js`) and will conflict with `Gtk` and other libraries.
|
||||
|
||||
### [Avoid interfering with the Extension System](#avoid-interfering-with-the-extension-system)
|
||||
|
||||
Extensions which modify, reload or interact with other extensions or the extension system are generally discouraged.
|
||||
|
||||
While not strictly prohibited, these extensions will be reviewed on a case-by-case basis and may be rejected at the reviewer's discretion.
|
||||
|
||||
### [Code must not be obfuscated](#code-must-not-be-obfuscated)
|
||||
|
||||
Extension code **MUST** be readable and reviewable JavaScript.
|
||||
|
||||
A specific code-style is not enforced, however submitted code must be formatted in a way that can be easily reviewed. The following rules **MUST** be adhered to:
|
||||
|
||||
- JavaScript code must be readable and reasonably structured
|
||||
- JavaScript code must not be minified or obfuscated
|
||||
- TypeScript must be transpiled to well-formatted JavaScript
|
||||
|
||||
### [No excessive logging](#no-excessive-logging)
|
||||
|
||||
Extension **MUST NOT** print excessively to the log. The log should only be used for important messages and errors.
|
||||
|
||||
If a reviewer determines that an extension is writing excessively to the log, the extension will be rejected.
|
||||
|
||||
### [Extensions should not force dispose a GObject](#extensions-should-not-force-dispose-a-gobject)
|
||||
|
||||
Extensions **SHOULD NOT** call [`GObject.Object.run_dispose()`](https://gjs-docs.gnome.org/gobject20/gobject.object#method-run_dispose) unless absolutely necessary.
|
||||
|
||||
If absolutely necessary, any call to this method **MUST** have a comment explaining the real-world situation that makes it a requirement.
|
||||
|
||||
### [Scripts and Binaries](#scripts-and-binaries)
|
||||
|
||||
Use of external scripts and binaries is strongly discouraged. In cases where this is unavoidable for the extension to serve it's purpose, the following rules must be adhered to:
|
||||
|
||||
- Extensions **MUST NOT** include binary executables or libraries
|
||||
- Processes **MUST** be spawned carefully and exit cleanly
|
||||
- Scripts **MUST** be written in GJS, unless absolutely necessary
|
||||
- Scripts must be distributed under an OSI approved license
|
||||
|
||||
Reviewing Python modules, HTML, and web [JavaScript](https://wiki.gnome.org/JavaScript) dependencies is out of scope for extensions.gnome.org. Unless required functionality is only available in another scripting language, scripts must be written in GJS.
|
||||
|
||||
Extensions may install modules from well-known services such as `pip`, `npm` or `yarn` but **MUST** require explicit user action. For example, the extension preferences may include a page which describes the modules to be installed with a button.
|
||||
|
||||
### [Clipboard Access must be declared](#clipboard-access-must-be-declared)
|
||||
|
||||
Extensions that access the clipboard, with or without user interaction, **MUST** declare it in the description.
|
||||
|
||||
An extension **MUST NOT** share clipboard data with a third-party without explicit user interaction (e.g. button click, a user-defined keyboard shortcut).
|
||||
|
||||
An extension **MUST NOT** ship with default keyboard shortcuts for interacting with clipboard data.
|
||||
|
||||
### [Privileged Subprocess must not be user-writable](#privileged-subprocess-must-not-be-user-writable)
|
||||
|
||||
Spawning privileged subprocesses should be avoided at all costs.
|
||||
|
||||
If absolutely necessary, the subprocess **MUST** be run with `pkexec` and **MUST NOT** be an executable or script that can be modified by a user process.
|
||||
|
||||
### [Extensions must be functional](#extensions-must-be-functional)
|
||||
|
||||
Extensions are reviewed, but not always tested for functionality so an extension **MAY** be approved with broken functionality or inoperable preferences window.
|
||||
|
||||
However, if an extension is tested and found to be fundamentally broken it will be rejected. Extensions which serve no purpose or have no functionality will also be rejected.
|
||||
|
||||
### [metadata.json must be well-formed](#metadata-json-must-be-well-formed)
|
||||
|
||||
The `metadata.json` file that ships with every extension should be well-formed and accurately reflect the extension.
|
||||
|Key|Description|
|
||||
|---|-----------|
|
||||
|name|This should not conflict with another extension if possible. If it is a fork of another extension it **MUST** have a unique name to distinguish it.|
|
||||
|uuid|This must be of the form `extension-id@namespace`. `extension-id` and `namespace` **MUST** only contain numbers, letters, period (`.`), underscore (`_`) and dash (`-`). Extensions **MUST NOT** use `gnome.org` as the namespace, but may use a registered web domain or accounts such as `username.github.io` and `username.gmail.com`.|
|
||||
|description|This should be a reasonable length, but may contain a few paragraphs separated with `\n` literals or a bullet point list made with `*` characters.|
|
||||
|version|**Deprecated:** This field is set for internal use by `extensions.gnome.org`.|
|
||||
|shell-version|This **MUST** only contain stable releases and up to one development release. Extensions must not claim to support future GNOME Shell versions. As of GNOME 40, an entry may simply be a major version like `40` to cover the entire release.|
|
||||
|url|This should be a link to a Github or [GitLab](https://wiki.gnome.org/GitLab) repository where users can report problems and learn more about your extension.|
|
||||
|session-modes|This **MUST** be dropped if you are only using `user` mode. The only valid values are `user` and `unlock-dialog`.|
|
||||
|donations|This **MUST** only contain [possible keys](/extensions/overview/anatomy.html#donations) and **MUST** be dropped if you don't use any of the keys.|
|
||||
|
||||
Example:
|
||||
~~~json
|
||||
{
|
||||
"uuid": "color-button@my-account.github.io",
|
||||
"name": "ColorButton",
|
||||
"description": "ColorButton adds a colored button to the panel.\n\nIt is a fork of MonochromeButton.",
|
||||
"shell-version": [ "3.38", "40", "41.alpha" ],
|
||||
"url": "https://github.com/my-account/color-button",
|
||||
"session-modes": [ "unlock-dialog", "user" ]
|
||||
}
|
||||
~~~
|
||||
|
||||
### [Session Modes](#session-modes)
|
||||
|
||||
In rare cases, it is necessary for an extension to continue running while the screen is locked. In order to be approved to use the `unlock-dialog` session mode:
|
||||
|
||||
- It **MUST** be necessary for the extension to operate correctly.
|
||||
- All signals related to keyboard events **MUST** be disconnected in `unlock-dialog` session mode.
|
||||
- The `disable()` function **MUST** have a comment explaining why you are using `unlock-dialog`.
|
||||
|
||||
Extensions **MUST NOT** disable selectively.
|
||||
|
||||
### [GSettings Schemas](#gsettings-schemas)
|
||||
|
||||
For extensions that include a GSettings Schema:
|
||||
|
||||
- The Schema ID **MUST** use `org.gnome.shell.extensions` as a base ID.
|
||||
- The Schema path **MUST** use `/org/gnome/shell/extensions` as a base path.
|
||||
- The Schema XML file **MUST** be included in the extension ZIP file.
|
||||
- The Schema XML filename **MUST** follow pattern of `<schema-id>.gschema.xml`.
|
||||
|
||||
### [Do not use telemetry tools](#do-not-use-telemetry-tools)
|
||||
|
||||
Extensions **MUST NOT** use any telemetry tools to track users and share the user data online.
|
||||
|
||||
## [Legal Restrictions](#legal-restrictions)
|
||||
|
||||
### [Licensing](#licensing)
|
||||
|
||||
GNOME Shell is licensed under the terms of the `GPL-2.0-or-later`, which means that derived works like extensions **MUST** be distributed under compatible terms (eg. `GPL-2.0-or-later`, `GPL-3.0-or-later`).
|
||||
|
||||
While your extension may include code licensed under a permissive license such as BSD/MIT, you are still approving GNOME to distribute it under terms compatible with the `GPL-2.0-or-later`.
|
||||
|
||||
If your extension contains code from another extension it **MUST** include attribution to the original author in the distributed files. Not doing so is a license violation and your extension will be rejected.
|
||||
|
||||
### [Copyrights and Trademarks](#copyrights-and-trademarks)
|
||||
|
||||
Extensions **MUST NOT** include copyrighted or trademarked content without proof of express permission from the owner. Examples include:
|
||||
|
||||
- Brand Names and Phrases
|
||||
- Logos and Artwork
|
||||
- Audio, Video or Multimedia
|
||||
|
||||
## [Recommendations](#recommendations)
|
||||
|
||||
### [Don't include unnecessary files](#don-t-include-unnecessary-files)
|
||||
|
||||
Extension submissions should not include files that are not necessary for it to function. Examples include:
|
||||
|
||||
- build or install scripts
|
||||
- .po and .pot files
|
||||
- unused icons, images or other media
|
||||
|
||||
A reviewer **MAY** decide to reject an extension which includes an unreasonable amount of unnecessary data.
|
||||
|
||||
### [Use a linter](#use-a-linter)
|
||||
|
||||
Using [ESLint](https://eslint.org/) to check your code can catch syntax errors and other mistakes before submission, as well as enforce consistent code style. You can find the ESLint rules used by GNOME Shell [on GitLab](https://gitlab.gnome.org/GNOME/gnome-shell-extensions/tree/main/lint).
|
||||
|
||||
Following a specific code style is not a requirement for approval, but if the codebase of an extension is too messy to properly review it **MAY** be rejected. This includes obfuscators and transpilers used with TypeScript.
|
||||
|
||||
### [UI Design](#ui-design)
|
||||
|
||||
Although not required for approval, it is recommended that extension preferences follow the [GNOME Human Interface Guidelines](https://developer.gnome.org/hig/) to improve consistency with the GNOME desktop.
|
||||
|
||||
MIT Licensed | GJS, A GNOME Project
|
||||
Copyright © 2024 gjs.guide contributors
|
||||
@@ -0,0 +1,275 @@
|
||||
# [TypeScript and LSP](#typescript-and-lsp)
|
||||
|
||||
This page will guide you through creating an extension using TypeScript, which will allow autocompletion to work in your editor. The setup presented here is editor-agnostic and will work on any editor that support the Language Server Protocol (LSP) or has some internal equivalent functionality.
|
||||
|
||||
## [Creating the extension](#creating-the-extension)
|
||||
|
||||
Differently from what was previously done in the [Getting Started](./creating.html) section, the extension will now be created in a directory outside the installation folder. This is mostly because GNOME Shell does not support TypeScript, so we need a build phase to generate JavaScript from our files, and then the generated files will be copied into the installation folder.
|
||||
|
||||
To start, create a folder anywhere on your disk and, inside it, create the following files:
|
||||
|
||||
### [`metadata.json`](#metadata-json)
|
||||
|
||||
~~~json
|
||||
{
|
||||
"name": "My TypeScript Extension",
|
||||
"description": "An extension made with TypeScript",
|
||||
"uuid": "my-extension@example.com",
|
||||
"url": "https://github.com/example/my-extension",
|
||||
"settings-schema": "org.gnome.shell.extensions.my-extension",
|
||||
"shell-version": [
|
||||
"45",
|
||||
"46",
|
||||
"47",
|
||||
"48",
|
||||
"49"
|
||||
]
|
||||
}
|
||||
~~~
|
||||
|
||||
This file does not contain anything different when compared to the one created in the [Getting Started](./creating.html#manual-creation), but is necessary for the extension to work. Note that the specification has a `version` entry which we do not specify, since it is generated automatically by E.G.O (GNOME's online extension management system).
|
||||
|
||||
### [`schemas/org.gnome.shell.extensions.my-extension.gschema.xml`](#schemas-org-gnome-shell-extensions-my-extension-gschema-xml)
|
||||
|
||||
Attention: this file goes inside a `schemas` folder.
|
||||
|
||||
~~~xml
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<schemalist>
|
||||
<schema id="org.gnome.shell.extensions.my-extension" path="/org/gnome/shell/extensions/my-extension/">
|
||||
<key name="padding-inner" type="i">
|
||||
<default>8</default>
|
||||
<summary>Inner padding</summary>
|
||||
<description>Padding between windows</description>
|
||||
</key>
|
||||
<key name="animate" type="b">
|
||||
<default>true</default>
|
||||
<summary>Animation</summary>
|
||||
<description>Whether to animate window movement/resizing</description>
|
||||
</key>
|
||||
</schema>
|
||||
</schemalist>
|
||||
~~~
|
||||
|
||||
This is a schema, as described in the [Preferences](./preferences.html) section. It is not necessary for an extension to work, but will be used in the example to show how automate steps and how to generate the final zip for distribution.
|
||||
|
||||
## [TypeScript setup](#typescript-setup)
|
||||
|
||||
To use TypeScript we need some setup, installing some dependencies and configuring the project to generate files correctly. In this example both the `extensions.js` and `prefs.js` will be generated from their TypeScript counterparts. Create the following files:
|
||||
|
||||
### [`package.json`](#package-json)
|
||||
|
||||
~~~json
|
||||
{
|
||||
"name": "my-extension",
|
||||
"version": "0.0.0",
|
||||
"description": "A TypeScript GNOME Extension",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/example/my-extension.git"
|
||||
},
|
||||
"author": "Álan Crístoffer e Sousa <acristoffers@startmail.com>",
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"bugs": {
|
||||
"url": "https://github.com/example/my-extension/issues"
|
||||
},
|
||||
"homepage": "https://github.com/example/my-extension#readme",
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"build": "tsc"
|
||||
}
|
||||
}
|
||||
~~~
|
||||
|
||||
In this file, it is important to set `"type": "module"`. You can set `version` to whatever you want, as it is not used.
|
||||
|
||||
Now that you have this file in place, you can run the following to install the dependencies:
|
||||
|
||||
~~~sh
|
||||
npm install --save-dev typescript
|
||||
npm install @girs/gjs @girs/gnome-shell
|
||||
~~~
|
||||
|
||||
### [`tsconfig.json`](#tsconfig-json)
|
||||
|
||||
~~~json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"outDir": "./dist",
|
||||
"sourceMap": false,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"target": "ES2023",
|
||||
"lib": [
|
||||
"ES2023"
|
||||
],
|
||||
},
|
||||
"include": [
|
||||
"ambient.d.ts",
|
||||
],
|
||||
"files": [
|
||||
"extension.ts",
|
||||
"prefs.ts"
|
||||
],
|
||||
}
|
||||
~~~
|
||||
|
||||
The TypeScript compiler configuration. Modifying it may break the build process.
|
||||
|
||||
### [`ambient.d.ts`](#ambient-d-ts)
|
||||
|
||||
~~~typescript
|
||||
import "@girs/gjs";
|
||||
import "@girs/gjs/dom";
|
||||
import "@girs/gnome-shell/ambient";
|
||||
import "@girs/gnome-shell/extensions/global";
|
||||
~~~
|
||||
|
||||
This file makes it possible to use the usual import paths in your TypeScript files instead of referencing `@girs/*` directly.
|
||||
|
||||
## [The Extension and Preferences files](#the-extension-and-preferences-files)
|
||||
|
||||
All the support files are in place, so we can finally write our extension's code. Create the following files:
|
||||
|
||||
### [`extension.ts`](#extension-ts)
|
||||
|
||||
~~~typescript
|
||||
import GLib from 'gi://GLib';
|
||||
import Gio from 'gi://Gio';
|
||||
import Meta from 'gi://Meta';
|
||||
import Shell from 'gi://Shell';
|
||||
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||
import { Extension } from 'resource:///org/gnome/shell/extensions/extension.js';
|
||||
|
||||
export default class MyExtension extends Extension {
|
||||
gsettings?: Gio.Settings
|
||||
animationsEnabled: boolean = true
|
||||
|
||||
enable() {
|
||||
this.gsettings = this.getSettings();
|
||||
this.animationsEnabled = this.gsettings.get_boolean('animate') ?? true
|
||||
}
|
||||
|
||||
disable() {
|
||||
this.gsettings = undefined;
|
||||
}
|
||||
}
|
||||
~~~
|
||||
|
||||
Thanks to `@girs`, the TypeScript code is basically what it would be if it were JavaScript + type information. Your LSP server should be able to offer information about the types in this file, like auto-complete and go-to-definition.
|
||||
|
||||
This is also the very minimum necessary to have a working extension: a default-exported class that extends `Extension` containing the methods `enable()` and `disable()`. You should not create constructors/destructors, and instead initialize your extension in `enable()` and finish it in `disable()`. This is because your class my be constructed once and reused internally, resulting in many calls to those methods.
|
||||
|
||||
### [`prefs.ts`](#prefs-ts)
|
||||
|
||||
~~~typescript
|
||||
import Gtk from 'gi://Gtk';
|
||||
import Adw from 'gi://Adw';
|
||||
import Gio from 'gi://Gio';
|
||||
import { ExtensionPreferences, gettext as _ } from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js';
|
||||
|
||||
export default class GnomeRectanglePreferences extends ExtensionPreferences {
|
||||
_settings?: Gio.Settings
|
||||
|
||||
fillPreferencesWindow(window: Adw.PreferencesWindow): Promise<void> {
|
||||
this._settings = this.getSettings();
|
||||
|
||||
const page = new Adw.PreferencesPage({
|
||||
title: _('General'),
|
||||
iconName: 'dialog-information-symbolic',
|
||||
});
|
||||
|
||||
const animationGroup = new Adw.PreferencesGroup({
|
||||
title: _('Animation'),
|
||||
description: _('Configure move/resize animation'),
|
||||
});
|
||||
page.add(animationGroup);
|
||||
|
||||
const animationEnabled = new Adw.SwitchRow({
|
||||
title: _('Enabled'),
|
||||
subtitle: _('Wether to animate windows'),
|
||||
});
|
||||
animationGroup.add(animationEnabled);
|
||||
|
||||
const paddingGroup = new Adw.PreferencesGroup({
|
||||
title: _('Paddings'),
|
||||
description: _('Configure the padding between windows'),
|
||||
});
|
||||
page.add(paddingGroup);
|
||||
|
||||
const paddingInner = new Adw.SpinRow({
|
||||
title: _('Inner'),
|
||||
subtitle: _('Padding between windows'),
|
||||
adjustment: new Gtk.Adjustment({
|
||||
lower: 0,
|
||||
upper: 1000,
|
||||
stepIncrement: 1
|
||||
})
|
||||
});
|
||||
paddingGroup.add(paddingInner);
|
||||
|
||||
window.add(page)
|
||||
|
||||
this._settings!.bind('animate', animationEnabled, 'active', Gio.SettingsBindFlags.DEFAULT);
|
||||
this._settings!.bind('padding-inner', paddingInner, 'value', Gio.SettingsBindFlags.DEFAULT);
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
~~~
|
||||
|
||||
This is also a good example of a minimal preference pane: a default-exported class that extends `ExtensionPreferences` and implements `fillPreferencesWindow(window: Adw.PreferencesWindow)`. There are other methods that can be implemented instead, but this is the easiest to use. It also shows how to populate the window with some widgets which are bound to properties defined in the schema and therefore persisted.
|
||||
|
||||
## [Build and packaging automation](#build-and-packaging-automation)
|
||||
|
||||
Since we now have a build step, it is better to automate it. Create the following file:
|
||||
|
||||
### [`Makefile`](#makefile)
|
||||
|
||||
~~~
|
||||
NAME=my-extension
|
||||
DOMAIN=example.com
|
||||
|
||||
.PHONY: all pack install clean
|
||||
|
||||
all: dist/extension.js
|
||||
|
||||
node_modules/.package-lock.json: package.json
|
||||
npm install
|
||||
|
||||
dist/extension.js dist/prefs.js: node_modules/.package-lock.json *.ts
|
||||
npm run build
|
||||
|
||||
schemas/gschemas.compiled: schemas/org.gnome.shell.extensions.$(NAME).gschema.xml
|
||||
glib-compile-schemas schemas
|
||||
|
||||
$(NAME).zip: dist/extension.js dist/prefs.js schemas/gschemas.compiled
|
||||
@cp -r schemas dist/
|
||||
@cp metadata.json dist/
|
||||
@(cd dist && zip ../$(NAME).zip -9r .)
|
||||
|
||||
pack: $(NAME).zip
|
||||
|
||||
install: $(NAME).zip
|
||||
gnome-extensions install --force $(NAME).zip
|
||||
|
||||
clean:
|
||||
@rm -rf dist node_modules $(NAME).zip
|
||||
~~~
|
||||
|
||||
You can now run `make` to compile your code and generate the files `extension.js` and `prefs.js` inside the `dist` folder. If needed, it will install the dependencies using `npm install`.
|
||||
|
||||
`make pack` will generate a file `my-extension.zip` which you can upload for review. It will compile the code and the schema, if needed, and copy the `schemas` folder and the `metadata.json` file into the `dest` folder before zipping it.
|
||||
|
||||
`make install` will copy the files to the extensions folder. If you logout and back in it should appear in the Extension Manager app.
|
||||
|
||||
Finally, `make clean` removes all generated files.
|
||||
|
||||
|
||||
MIT Licensed | GJS, A GNOME Project
|
||||
|
||||
Copyright © 2024 gjs.guide contributors
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,75 @@
|
||||
---
|
||||
name: gnome-shell-extension-dev
|
||||
description: 'Use when building or modifying a GNOME Shell extension in GJS, creating metadata.json, extension.js, prefs.js, GSettings schemas, nested-shell test flows, GNOME 47-49 compatibility logic, Wayland subprocess renderers, or review-readiness checks.'
|
||||
argument-hint: 'Describe the GNOME extension task, target GNOME versions, and whether it touches shell process, prefs process, renderer subprocess, or packaging.'
|
||||
---
|
||||
|
||||
# GNOME Shell Extension Development
|
||||
|
||||
Use this skill before changing extension lifecycle code, metadata, prefs, schemas, subprocess launch logic, or GNOME-version-sensitive Meta APIs.
|
||||
|
||||
## Core Rules
|
||||
|
||||
1. Keep the GNOME Shell process and the prefs process separate.
|
||||
2. Only mutate shell state inside enable(). Undo all of it in disable().
|
||||
3. Treat GNOME 47, 48, and 49 as distinct compatibility targets when touching Meta or shell internals.
|
||||
4. Keep companion processes explicit, tracked, and cleanly shut down.
|
||||
5. Prefer documented GNOME APIs first, precedent code second.
|
||||
|
||||
## When To Use
|
||||
|
||||
- Creating or editing metadata.json, extension.js, prefs.js, or schemas
|
||||
- Building a shell-side manager that uses Meta, St, Shell, or Clutter
|
||||
- Building a prefs window with GTK4 or libadwaita for an extension
|
||||
- Adding subprocesses, sockets, or monitor-aware logic to a GNOME extension
|
||||
- Checking GNOME 47, 48, or 49 upgrade risks before coding
|
||||
- Preparing a local-first extension scaffold that can later be hardened for review
|
||||
|
||||
## Procedure
|
||||
|
||||
1. Confirm the target UUID, schema ID, and supported shell versions.
|
||||
2. Read the rules in [GNOME extension rules](./references/gnome-extension-rules.md).
|
||||
3. If the change touches version-sensitive APIs, read [GNOME 47-49 notes](./references/gnome-47-49-notes.md).
|
||||
4. Keep shell-process imports limited to GNOME Shell and GI modules that are safe in shell.
|
||||
5. Keep prefs-process imports limited to GTK4, Adwaita, Gio, GLib, and related GTK-side modules.
|
||||
6. For subprocess renderers on Wayland, validate ownership and lifecycle with GNOME-version-aware Meta.WaylandClient handling.
|
||||
7. Before shipping a new lifecycle feature, verify disable() disconnects signals, removes sources, and destroys tracked objects.
|
||||
8. Test with a nested shell where possible before relying on the real session.
|
||||
|
||||
## Shell Process Checklist
|
||||
|
||||
- Use ESModule imports
|
||||
- Do not create GObject instances in the constructor unless they are static and harmless
|
||||
- Connect signals only in enable()
|
||||
- Remove GLib sources in disable()
|
||||
- Destroy or clear objects created in enable()
|
||||
- Keep logging sparse and operational
|
||||
- Do not import Gtk, Gdk, or Adw in the shell process
|
||||
|
||||
## Prefs Process Checklist
|
||||
|
||||
- Use GTK4 and Adwaita only
|
||||
- Do not import Meta, Shell, St, or Clutter
|
||||
- Bind settings through Gio.Settings
|
||||
- Keep async setup compatible with GNOME 47+ awaited prefs methods
|
||||
|
||||
## Renderer Companion Checklist
|
||||
|
||||
- Track each subprocess explicitly
|
||||
- Prefer clean shutdown over process sweeping
|
||||
- Use runtime feature detection for optional GTK or GL features
|
||||
- Keep IPC small, observable, and versioned
|
||||
- Contain renderer crashes so they do not destabilize gnome-shell
|
||||
|
||||
## Deliverables
|
||||
|
||||
- Valid extension metadata and schema
|
||||
- Minimal extension lifecycle that enables and disables cleanly
|
||||
- Minimal prefs window that opens without shell imports
|
||||
- Version-aware subprocess launch path for GNOME 47-49
|
||||
- Short research notes when using undocumented or precedent-driven behavior
|
||||
|
||||
## References
|
||||
|
||||
- [GNOME extension rules](./references/gnome-extension-rules.md)
|
||||
- [GNOME 47-49 notes](./references/gnome-47-49-notes.md)
|
||||
@@ -0,0 +1,28 @@
|
||||
# GNOME 47-49 Notes
|
||||
|
||||
This file captures the version-sensitive details currently relevant to gnome-milkdrop.
|
||||
|
||||
## GNOME 47
|
||||
|
||||
- prefs.js methods getPreferencesWidget() and fillPreferencesWindow() are awaited.
|
||||
- Keep prefs code async-safe and avoid assuming synchronous window construction.
|
||||
|
||||
## GNOME 48
|
||||
|
||||
- ExtensionBase gained getLogger(), which can be adopted later for cleaner logs.
|
||||
- Some compositor helpers moved under global.compositor.
|
||||
- Avoid relying on older Meta helper names without checking current APIs.
|
||||
|
||||
## GNOME 49
|
||||
|
||||
- Nested testing changed to dbus-run-session gnome-shell --devkit --wayland.
|
||||
- Meta.WaylandClient changed. For subprocess ownership, use Meta.WaylandClient.new_subprocess() on GNOME 49.
|
||||
- Meta.Window.get_maximized() was removed. Use Meta.Window.is_maximized().
|
||||
- Meta.Rectangle was removed. Use Mtk.Rectangle if rectangle types are needed later.
|
||||
- There were no material metadata.json, extension.js, or prefs.js format changes specific to GNOME 49.
|
||||
|
||||
## Implications For This Project
|
||||
|
||||
- The renderer launcher must branch between pre-49 and 49 Wayland client APIs.
|
||||
- Any maximize or fullscreen policy code must handle both get_maximized() and is_maximized().
|
||||
- Testing instructions and docs must use the devkit path for GNOME 49.
|
||||
@@ -0,0 +1,37 @@
|
||||
# GNOME Extension Rules
|
||||
|
||||
This file distills the official GNOME extension guidance into the rules that matter for this project.
|
||||
|
||||
## Lifecycle
|
||||
|
||||
- metadata.json and extension.js are the required core files.
|
||||
- GNOME Shell extensions use ESModules on GNOME 45 and later.
|
||||
- The extension constructor is not the place to connect signals, create widgets, or mutate GNOME Shell state.
|
||||
- enable() is where signals, sources, and shell-side objects are created.
|
||||
- disable() must disconnect signals, remove sources, destroy objects, and undo shell mutations.
|
||||
|
||||
## Process Boundaries
|
||||
|
||||
- extension.js runs in the gnome-shell process.
|
||||
- prefs.js runs in a separate GTK process.
|
||||
- Do not import Gtk, Gdk, or Adw in the shell process.
|
||||
- Do not import Clutter, Meta, St, or Shell in prefs.
|
||||
|
||||
## Metadata And Schema
|
||||
|
||||
- Use a review-safe UUID with a namespace under project control.
|
||||
- shell-version must list only supported stable releases.
|
||||
- settings-schema should match a real schema ID under org.gnome.shell.extensions.*.
|
||||
- session-modes should be omitted unless the extension truly needs more than user mode.
|
||||
|
||||
## Review-Sensitive Areas
|
||||
|
||||
- External scripts and binaries are discouraged by GNOME review.
|
||||
- If a companion process is necessary, it must be carefully spawned and cleanly terminated.
|
||||
- Logging should be limited to meaningful operational messages and errors.
|
||||
- Code must stay readable and reviewable.
|
||||
|
||||
## Testing
|
||||
|
||||
- For GNOME 49, nested shell testing uses gnome-shell --devkit --wayland.
|
||||
- Use journalctl and the nested shell first when validating shell-side lifecycle changes.
|
||||
@@ -0,0 +1,17 @@
|
||||
## INSTRUCTION
|
||||
- Generate the gnome-shell 49 extension described under the _EXTENSION DESCRIPTION_ item.
|
||||
- Place all output in a subdirectory named 'generated' in the working directory. Configure the project for typescript.
|
||||
- Place a GPL-3.0 LICENSE file in the generated directory.
|
||||
|
||||
## EXTENSION DESCRIPTION:
|
||||
- Features:
|
||||
- The extension is called 'BLE Unlock' and is owned by daniel@demus.dk.
|
||||
- The extension monitors the signal strength of a bluetooth device in the proximity of the computer and calls functions when the signal strength goes below or above configured thresholds, respectively. Leave the called functions empty.
|
||||
- Include a quick settings item allowing to enable/disable the monitoring functionality and showing some status information including the selected device and current signal strength.
|
||||
- The dB thresholds and device to monitor are configurable. The item must have a submenu allowing setting the thresholds and selecting which device to monitor, so the quick settings item it should list nearby devices and allow selecting one of them, similarly to the the gnome Bluetooth Settings dialog.
|
||||
- The extension runs in user and unlock-dialogue session modes.
|
||||
- Additional deliverables:
|
||||
- Add comments to all non-boilerplate methods.
|
||||
- Generate unit tests for all isolatable codepaths.
|
||||
- Generate integration tests both with mocks and gnome-shell-test-tool as appropriate for the given functionality under test.
|
||||
- Store any informational output related to the project or further instructions into a README.md file.
|
||||
Reference in New Issue
Block a user