14 KiB
- Mutter state change signal summary (WIP)
- Mutter signal order
- Keybinding system
- GJS
- GObject
- Gnome-shell scene graph and GUI system
- Extension system
- Misc HowTo
Mutter state change signal summary (WIP)
stuck -> unstuck: stuck property change remove from all workspaces add on target workspace
unstuck -> stuck: stuck property change [not-verified] remove from current workspace add on all workspaces
window moved from workspace A -> workspace B: remove from A add on B
window A close: next window receive focus remove A from workspace window-left-monitor (actor is null)
window is created: window-added (actor is null) window-entered-monitor (actor is null) window-created window-focus
window monitor changed: (ws-only-primary) [order not-verified] window-left-monitor [not-verified] window-entered-monitor unstuck -> stuck
window monitor changed: (ws-spans-monitor) window-left-monitor [not-verified] window-entered-monitor
Monitor changes (ws-only-primary) primary -> secondary <=> unstuck -> stuck seconday -> primary <=> stuck -> unstuck
window_added always follows a window_removed except when a window is closed.
window monitor membership determined by majority area (ish)
Mutter signal order
When window A is closed
- The next window, B, receives 'focus' (but the actor of A seems to be gone?)
- Workspace receives 'window-removed'. ('A' seems to have been stripped of signal handlers)
- on screen 'window-left-monitor', actor isn't available
When window A is created
- on workspace "window-added" is run, actor isn't available
- on screen "window-entered-monitor", actor isn't available
- on display "window-created" is run, actor is available
- focus is run if the new window should be focused
Toggle "Always on visible workspace" (scratch windows)
- window-removed on workspace of window
- window-added on all workspaces
Keybinding system
`Main.wm.addKeybinding` is used to register a named keybindable action and it's handler. An numeric id is returned. (this is a thin wrapper around `MetaDisplay.add_keybinding`)
The action should have an entry in the schema underlying the `GSettings` object supplied to `addKeybinding`. This is where the actual keybinding is specified. Multiple bindings can be specified.
<key type="as" name="toggle-scratch-layer">
<default><![CDATA[['<Super>e']]]></default>
<summary>Toggles the floating scratch layer</summary>
</key>
To change a keybinding simply change this value in the gsetting: (mutter will pick up the change automatically.
mySettings.set_strv("toggle-scratch-layer", ["<Super>s"]);
Action names are global. (note that the mutter documentation mostly refers to actions as keybindings)
`Meta.keybindings_set_custom_handler` is used to change a action handler. Despite what the documentation suggests this works for non-builtin actions too.
If the action is a mutter built-in (one of `Meta.KeyBindingAction.*`, setting the custom handler to `null` restores the default handler.
Action handlers fire on key-down.
Mutter itself does not support key-release sensitive bindings, but it's possible to create a Clutter actor in response to a key-down binding, which temporarily take over the keyboard. Clutter can listen for key-up/key-release events.
`MetaDisplay.get_keybinding_action` looks up the action id bound to a specific modifer+keycode. This is mostly useful when handling key events within clutter.
The id -> action-name mapping is not(?) exposed. For builtin actions `Meta.prefs_get_keybinding_action(actionName)` will give the id of actionName.
It's not possible to look up the handler of a action…(?)
A slightly annoying detail about how all this works is that you normally give the handler before you know the action-id. So if the handler need to know the action-id (eg. if it use clutter to implement a mini-mode and want to respond to the same key that triggered the mode) you either have to store a name->id map, or re-assign the handler afterward.
The Keybinding object which is supplied to keyhandler doesn't seem to expose the key used to trigger the action either?
Modifier-only bindings
Simply use the keysym name as if the modifier was a regular key. Don't use angle brackets - those are used for modifiers.
settings.set_strv("my-action", ["Super_L"])
Bind keys without using actions from a schema
From: https://stackoverflow.com/a/42466781/1517969
Meta = imports.gi.Meta;
Main = imports.ui.main;
Shell = imports.gi.Shell;
let action = global.display.grab_accelerator("<super>u");
let name = Meta.external_binding_name_for_action(action);
Main.wm.allowKeybinding(name, Shell.ActionMode.ALL);
global.display.connect(
'accelerator-activated',
function(display, action, deviceId, timestamp){
print('Accelerator Activated: [display={}, action={}, deviceId={}, timestamp={}]',
display, action, deviceId, timestamp)
})
Lookup an keybinding action by a accelerator string
global.display.get_keybinding_action(keycode, mask) is simple to use in clutter event handlers since the keycode and mask is readily available. Outside of clutter is harder:
function devirtualizeMask(gdkVirtualMask) {
const keymap = Gdk.Keymap.get_default();
let [success, rawMask] = keymap.map_virtual_modifiers(gdkVirtualMask);
if (!success)
throw new Error("Couldn't devirtualize mask " + gdkVirtualMask);
return rawMask;
}
function getBoundActionId(keystr) {
let [dontcare, keycodes, mask] =
Gtk.accelerator_parse_with_keycode(keystr);
if(keycodes.length > 1) {
throw new Error("Multiple keycodes " + keycodes + " " + keystr);
}
const rawMask = devirtualizeMask(mask);
return global.display.get_keybinding_action(keycodes[0], rawMask);
}
GJS
import system / module system
`imports.NAME` reflects the directories and javascript files present in `imports.searchPath`. To add a path, simply do `imports.searchPath.push(PATH)`
Environment variable `GJS_PATH` initializes `imports.searchPath`.
The special property `imports.gi` expose gobject-introspectable libraries. Another search path controls which libraries are available: `imports.gi.GIRepository.Repository.get_search_path()` initialized by environment variable `GI_TYPELIB_PATH` (`Repository` is the global instance of GIRepository)
Reloading modules
Modules can't be reloaded, but writing to `imports.myModule.myVariable` works. Eg.
// myModule
var foo = 1;
function printFoo() {
print(foo);
}
After `imports.myModule.foo = 2`, `printFoo` will print 2. All users of the module share the same module object so they will also see the updated variable.
Refering to the current module
Refering to the module being loaded works:
// myModule.js
var currentModule = imports.myModule;
var foo = 1;
currentModule.foo = 2;
print(foo); // prints 2
I don't know if it's possible without knowing the module name.
Creating a standalone importer
This trick is due to gnome-shell
function createImporter (directoryPath) {
const Gio = imports.gi.Gio;
let oldSearchPath = imports.searchPath.slice(); // make a copy
let directory = Gio.file_new_for_path(directoryPath);
try {
imports.searchPath = [ directory.get_parent().get_path() ];
// importing a "subdir" creates a new importer object that doesn't
// affect the global one
return imports[directory.get_basename()];
} finally {
imports.searchPath = oldSearchPath;
}
}
Debugging
Get a stacktrace
`(new Error()).stack`
GObject
The `notify` signal is emited on changes to all GObject properties. Listen to `notify::propery-name` to only receive for changes to ` property-name`. (Reference)
Gnome-shell scene graph and GUI system
NB: some details might differ with the wayland backend.
Gnome shell use Clutter to mange all visible components including the window textures. Basic GUI components are provided by the St (built on top of clutter).
Low level window management and input handling happens through mutter/meta. Gnome-shell is technically a mutter plugin.
Input handling
(Also see /vhad/PaperWM/src/branch/develop/Keybinding%20system)
Input is normally fully handled by X11. This means that even though gnome-shell use clutter (which have input mechanisms) inputs does not normally go through clutter.
Ie. making an actor `reactive` is not enough to capture input reliable.
Input handling can be directed through clutter by using:
Main.layoutManager._trackActor(actor)
This informs mutter[1] that mouse input in the actor's region should be sent through clutter.
Some higher-level interfaces:
Main.pushModal(actor)
The clutter actor will receives all input until `Main.popModal` is called.
Main.layoutManager.trackChrome(actor)
NB: It does not seem to be possible to propagate input captured by a tracked actor to a window actor below.
NB! When a "tracked" actor is stacked below a window actor it will still prevent the window actor from receiving input!
[1] By using `meta_set_stage_input_region` through `global.set_stage_input_region`
`MetaWindow` and `MetaWindowActor`
WIP: display_rect vs frame_rect vs actor.width. Gotchas when placing MetaWindowActors in containers, etc.
Warning: This is a somewhat confusing part of gnome-shell/mutter.
A window is represented by two objects: a `MetaWindow` representing the underlying windowing system object (eg. a X11 window) and a `MetaWindowActor` which basically is the window texture/visible part.
Both of these objects have a geometry (size and position). The meta window geometry determines the input region, while the actor geometry determines the texture. Normally these geometries are kept in sync so the visible and input regions corresponds. It is however possible for these to drift: The thumb of rule is that changes to the meta window geometry is propagated to the actor, but not the other way.
The coordinate system used is thankfully shared :)
The size of the window actor is slightly bigger than the meta window since the actor includes border decorations and window-resize region. The size difference varies with the toolkit used to create the window.
Basic operations
To get the window actor of a meta window: `metaWindow.get_compositor_private()`
To get the meta window of a window actor: `windowActor.meta_window`
The window actor geometry: `windowActor.size, windowActor.position` or `metaWindow.get_buffer_rect`
The meta window geometry: `metaWindow.get_frame_rect()`
Changing the geometry of a window: `metaWindow.move_frame` or `metaWindow.move_resize_frame`
Stacking/"z-index"
The "z-index" in clutter is controlled by the actors position in the scene graph. Ie. the actors are drawn in a depth first manner. So the last child of a parent will be drawn on top of all the other children, and so on.
To my knowledge there is no way to make a actor "break out" of its parent. If sibling A is drawn below another actor X, sibling B will also be drawn below X.
NB: `ClutterActor.z-position` don't control the z-index. It is used to control the perspective of the actors (most relevant for rotated actors).
A complication when using non-window actors inside `global.window_group` is that mutter keep restacking the window actors in a way that destroys the non-window actors z-index. Listening on the `restacked` signal of `global.screen` (`MetaScreen`) and restack the non-window actors in the handler is a workaround that seems to work.
Gotchas
Building `StWidget` detached from the stage are prone to result in the following warning:
st_widget_get_theme_node called on the widget [0x... St...] which is not in the stage.
This is because a lot of actor properties depend on the style of the actor and that can depend on the ancestors of the actor. (`.parent .child { border: 2px; }`)
So any code that try to access eg. height/width (unless these have been explicitly set beforehand) requires that the full style info is present.
Extension system
All extension objects are available using `imports.misc.extensionUtils.extensions[extensionUiid];` where the key is the uuid from the metadata.json file.
The current extension object is usually found like this:
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
The absolute path of the an extension: `Extension.dir.get_path()`
Misc HowTo
Defer an execution of a function
Meta.later_add (assoc: imports.mainloop.timeout_add)
Increase mutter log verbosity
Meta.add_verbose_topic(Meta.DebugTopic.FOCUS)
Meta.remove_verbose_topic(Meta.DebugTopic.FOCUS)
Profiling
Show clutter FPS
Clutter prints the FPS at regular intervals if CLUTTER_SHOW_FPS is set when gnome-shell starts. Where the output ends up depends on how gnome-shell was started. On my system it ends up in the system journal (journalctl)
To turn on off without disrupting flow too much use GLib.setenv("CLUTTER_SHOW_FPS", "1", true) and restart gnome-shell.