9.0 KiB
- Mutter signal order
- Keybinding system
- Stack level
- GJS
- GObject
- Gnome-shell scene graph and GUI system
- Extension system
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)
When window A is created
- on workspace "window-added" is run, actor isn't available
- on display "window-created is run, actor is available
- focus is run if the new window should be focused
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.
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){
log('Accelerator Activated: [display={}, action={}, deviceId={}, timestamp={}]',
display, action, deviceId, timestamp)
})
Stack level
There's not enough room for a maximized window to slide above another maximized window if they move at the same speed:
front
window A -> | ---------------- |
window B | ----|-----------
select window B
front
-----------|---- | window A
window B -> | ---------------- |
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)`
The entries can also(?) be special uri's referring to directories with .gir/.typelib files(?)
Or maybe more likely: `imports.gi.` contains everything that's exposed through .gir/.typelib files.
`GJS_PATH` might be used too.
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. 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 can be directed through clutter by (at least) two means:
Main.pushModal(actor)
The clutter actor will receives all input until `Main.popModal` is called.
Main.layoutManager._trackActor(actor)
Gnome-shell will inform mutter[1] that mouse input in the actor's region should be sent through clutter. This allows the actor to capture input. Ie. setting `reactive` to true is not enough to capture mouse input.
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!
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.
[1] By using `meta_set_stage_input_region` through `global.set_stage_input_region`
`MetaWindow` and `MetaWindowActor`
TODO: display_rect vs frame_rect vs actor.width. Gotchas when placing MetaWindowActors in containers, etc.
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.
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();