linux-presets/gui/gnome/autocustom-gnome-macos/res/extensions/gsconnect@andyholmes.github.io/preferences/keybindings.js

315 lines
9.0 KiB
JavaScript
Executable File

// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect
//
// SPDX-License-Identifier: GPL-2.0-or-later
import Gdk from 'gi://Gdk';
import Gio from 'gi://Gio';
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import Gtk from 'gi://Gtk';
/*
* A list of modifier keysyms we ignore
*/
const _MODIFIERS = [
Gdk.KEY_Alt_L,
Gdk.KEY_Alt_R,
Gdk.KEY_Caps_Lock,
Gdk.KEY_Control_L,
Gdk.KEY_Control_R,
Gdk.KEY_Meta_L,
Gdk.KEY_Meta_R,
Gdk.KEY_Num_Lock,
Gdk.KEY_Shift_L,
Gdk.KEY_Shift_R,
Gdk.KEY_Super_L,
Gdk.KEY_Super_R,
];
/**
* Response enum for ShortcutChooserDialog
*/
export const ResponseType = {
CANCEL: Gtk.ResponseType.CANCEL,
SET: Gtk.ResponseType.APPLY,
UNSET: 2,
};
/**
* A simplified version of the shortcut editor from GNOME Control Center
*/
export const ShortcutChooserDialog = GObject.registerClass({
GTypeName: 'GSConnectPreferencesShortcutEditor',
Template: 'resource:///org/gnome/Shell/Extensions/GSConnect/ui/preferences-shortcut-editor.ui',
Children: [
'cancel-button', 'set-button',
'stack', 'summary-label',
'shortcut-label', 'conflict-label',
],
}, class ShortcutChooserDialog extends Gtk.Dialog {
_init(params) {
super._init({
transient_for: Gio.Application.get_default().get_active_window(),
use_header_bar: true,
});
this._seat = Gdk.Display.get_default().get_default_seat();
// Current accelerator or %null
this.accelerator = params.accelerator;
// TRANSLATORS: Summary of a keyboard shortcut function
// Example: Enter a new shortcut to change Messaging
this.summary = _('Enter a new shortcut to change <b>%s</b>').format(
params.summary
);
}
get accelerator() {
return this.shortcut_label.accelerator;
}
set accelerator(value) {
this.shortcut_label.accelerator = value;
}
get summary() {
return this.summary_label.label;
}
set summary(value) {
this.summary_label.label = value;
}
vfunc_key_press_event(event) {
let keyvalLower = Gdk.keyval_to_lower(event.keyval);
let realMask = event.state & Gtk.accelerator_get_default_mod_mask();
// TODO: Critical: 'WIDGET_REALIZED_FOR_EVENT (widget, event)' failed
if (_MODIFIERS.includes(keyvalLower))
return true;
// Normalize Tab
if (keyvalLower === Gdk.KEY_ISO_Left_Tab)
keyvalLower = Gdk.KEY_Tab;
// Put shift back if it changed the case of the key, not otherwise.
if (keyvalLower !== event.keyval)
realMask |= Gdk.ModifierType.SHIFT_MASK;
// HACK: we don't want to use SysRq as a keybinding (but we do want
// Alt+Print), so we avoid translation from Alt+Print to SysRq
if (keyvalLower === Gdk.KEY_Sys_Req && (realMask & Gdk.ModifierType.MOD1_MASK) !== 0)
keyvalLower = Gdk.KEY_Print;
// A single Escape press cancels the editing
if (realMask === 0 && keyvalLower === Gdk.KEY_Escape) {
this.response(ResponseType.CANCEL);
return false;
}
// Backspace disables the current shortcut
if (realMask === 0 && keyvalLower === Gdk.KEY_BackSpace) {
this.response(ResponseType.UNSET);
return false;
}
// CapsLock isn't supported as a keybinding modifier, so keep it from
// confusing us
realMask &= ~Gdk.ModifierType.LOCK_MASK;
if (keyvalLower !== 0 && realMask !== 0) {
this._ungrab();
// Set the accelerator property/label
this.accelerator = Gtk.accelerator_name(keyvalLower, realMask);
// TRANSLATORS: When a keyboard shortcut is unavailable
// Example: [Ctrl]+[S] is already being used
this.conflict_label.label = _('%s is already being used').format(
Gtk.accelerator_get_label(keyvalLower, realMask)
);
// Show Cancel button and switch to confirm/conflict page
this.cancel_button.visible = true;
this.stack.visible_child_name = 'confirm';
this._check();
}
return true;
}
async _check() {
try {
const available = await checkAccelerator(this.accelerator);
this.set_button.visible = available;
this.conflict_label.visible = !available;
} catch (e) {
logError(e);
this.response(ResponseType.CANCEL);
}
}
_grab() {
const success = this._seat.grab(
this.get_window(),
Gdk.SeatCapabilities.KEYBOARD,
true, // owner_events
null, // cursor
null, // event
null
);
if (success !== Gdk.GrabStatus.SUCCESS)
return this.response(ResponseType.CANCEL);
if (!this._seat.get_keyboard() && !this._seat.get_pointer())
return this.response(ResponseType.CANCEL);
this.grab_add();
}
_ungrab() {
this._seat.ungrab();
this.grab_remove();
}
// Override to use our own ungrab process
response(response_id) {
this.hide();
this._ungrab();
return super.response(response_id);
}
// Override with a non-blocking version of Gtk.Dialog.run()
run() {
this.show();
// Wait a bit before attempting grab
GLib.timeout_add(GLib.PRIORITY_DEFAULT, 100, () => {
this._grab();
return GLib.SOURCE_REMOVE;
});
}
});
/**
* Check the availability of an accelerator using GNOME Shell's DBus interface.
*
* @param {string} accelerator - An accelerator
* @param {number} [modeFlags] - Mode Flags
* @param {number} [grabFlags] - Grab Flags
* @param {boolean} %true if available, %false on error or unavailable
*/
export async function checkAccelerator(accelerator, modeFlags = 0, grabFlags = 0) {
try {
let result = false;
// Try to grab the accelerator
const action = await new Promise((resolve, reject) => {
Gio.DBus.session.call(
'org.gnome.Shell',
'/org/gnome/Shell',
'org.gnome.Shell',
'GrabAccelerator',
new GLib.Variant('(suu)', [accelerator, modeFlags, grabFlags]),
null,
Gio.DBusCallFlags.NONE,
-1,
null,
(connection, res) => {
try {
res = connection.call_finish(res);
resolve(res.deepUnpack()[0]);
} catch (e) {
reject(e);
}
}
);
});
// If successful, use the result of ungrabbing as our return
if (action !== 0) {
result = await new Promise((resolve, reject) => {
Gio.DBus.session.call(
'org.gnome.Shell',
'/org/gnome/Shell',
'org.gnome.Shell',
'UngrabAccelerator',
new GLib.Variant('(u)', [action]),
null,
Gio.DBusCallFlags.NONE,
-1,
null,
(connection, res) => {
try {
res = connection.call_finish(res);
resolve(res.deepUnpack()[0]);
} catch (e) {
reject(e);
}
}
);
});
}
return result;
} catch (e) {
logError(e);
return false;
}
}
/**
* Show a dialog to get a keyboard shortcut from a user.
*
* @param {string} summary - A description of the keybinding's function
* @param {string} accelerator - An accelerator as taken by Gtk.ShortcutLabel
* @return {string} An accelerator or %null if it should be unset.
*/
export async function getAccelerator(summary, accelerator = null) {
try {
const dialog = new ShortcutChooserDialog({
summary: summary,
accelerator: accelerator,
});
accelerator = await new Promise((resolve, reject) => {
dialog.connect('response', (dialog, response) => {
switch (response) {
case ResponseType.SET:
accelerator = dialog.accelerator;
break;
case ResponseType.UNSET:
accelerator = null;
break;
case ResponseType.CANCEL:
// leave the accelerator as passed in
break;
}
dialog.destroy();
resolve(accelerator);
});
dialog.run();
});
return accelerator;
} catch (e) {
logError(e);
return accelerator;
}
}