keyboard: Refactor 'commit' action handling

Move the complex parts of this mechanism from the Keyboard object
to the KeyboardController object, replaced by code that is easier to
follow.

Also, keyval guessing is deferred to a later point in the commit
procedure so so it does not happen for a single character only,
this way we can send multi-character input through the IM, which
is necessary for some OSK layouts.

Closes: https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/7190
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/3162>
This commit is contained in:
Carlos Garnacho 2024-01-30 11:08:52 +01:00
parent f0b18c9ccc
commit 741355e2cd

View File

@ -224,7 +224,7 @@ const Key = GObject.registerClass({
'pressed': {}, 'pressed': {},
'released': {}, 'released': {},
'keyval': {param_types: [GObject.TYPE_UINT]}, 'keyval': {param_types: [GObject.TYPE_UINT]},
'commit': {param_types: [GObject.TYPE_UINT, GObject.TYPE_STRING]}, 'commit': {param_types: [GObject.TYPE_STRING]},
}, },
}, class Key extends St.BoxLayout { }, class Key extends St.BoxLayout {
_init(params, extendedKeys = []) { _init(params, extendedKeys = []) {
@ -281,11 +281,6 @@ const Key = GObject.registerClass({
this.keyButton._extendedKeys = this._extendedKeyboard; this.keyButton._extendedKeys = this._extendedKeyboard;
} }
_getKeyvalFromString(string) {
let unicode = string?.length ? string.charCodeAt(0) : undefined;
return Clutter.unicode_to_keysym(unicode);
}
_press(button) { _press(button) {
if (button === this.keyButton) { if (button === this.keyButton) {
this._pressTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, this._pressTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT,
@ -320,12 +315,10 @@ const Key = GObject.registerClass({
if (this._pressed) { if (this._pressed) {
if (this._keyval && button === this.keyButton) if (this._keyval && button === this.keyButton)
this.emit('keyval', this._keyval); this.emit('keyval', this._keyval);
else if (commitString) { else if (commitString)
const keyval = this._getKeyvalFromString(commitString); this.emit('commit', commitString);
this.emit('commit', keyval, commitString); else
} else {
console.error('Need keyval or commitString'); console.error('Need keyval or commitString');
}
} }
this.emit('released'); this.emit('released');
@ -805,7 +798,7 @@ const EmojiPager = GObject.registerClass({
key.connect('pressed', () => { key.connect('pressed', () => {
this._currentKey = key; this._currentKey = key;
}); });
key.connect('commit', (actor, keyval, str) => { key.connect('commit', (actor, str) => {
if (this._currentKey !== key) if (this._currentKey !== key)
return; return;
this._currentKey = null; this._currentKey = null;
@ -1266,6 +1259,7 @@ export const Keyboard = GObject.registerClass({
this._clearShowIdle(); this._clearShowIdle();
this._keyboardController.oskCompletion = false;
this._keyboardController.destroy(); this._keyboardController.destroy();
Main.layoutManager.untrackChrome(this); Main.layoutManager.untrackChrome(this);
@ -1276,8 +1270,6 @@ export const Keyboard = GObject.registerClass({
this._languagePopup.destroy(); this._languagePopup.destroy();
this._languagePopup = null; this._languagePopup = null;
} }
IBusManager.getIBusManager().setCompletionEnabled(false, () => Main.inputMethod.update());
} }
_setupKeyboard() { _setupKeyboard() {
@ -1301,7 +1293,7 @@ export const Keyboard = GObject.registerClass({
this._emojiSelection.connect('toggle', this._toggleEmoji.bind(this)); this._emojiSelection.connect('toggle', this._toggleEmoji.bind(this));
this._emojiSelection.connect('close-request', () => this.close(true)); this._emojiSelection.connect('close-request', () => this.close(true));
this._emojiSelection.connect('emoji-selected', (selection, emoji) => { this._emojiSelection.connect('emoji-selected', (selection, emoji) => {
this._keyboardController.commitString(emoji); this._keyboardController.commit(emoji).catch(console.error);
}); });
this._emojiSelection.hide(); this._emojiSelection.hide();
@ -1447,12 +1439,13 @@ export const Keyboard = GObject.registerClass({
} }
if (key.action !== 'modifier') { if (key.action !== 'modifier') {
button.connect('commit', (_actor, keyval, str) => { button.connect('commit', (_actor, str) => {
this._commitAction(keyval, str).then(() => { this._keyboardController.commit(str, this._modifiers).then(() => {
this._disableAllModifiers();
if (layout.mode === 'default' || if (layout.mode === 'default' ||
(layout.mode === 'latched' && !this._latched)) (layout.mode === 'latched' && !this._latched))
this._setActiveLevel('default'); this._setActiveLevel('default');
}); }).catch(console.error);
}); });
} }
@ -1510,30 +1503,6 @@ export const Keyboard = GObject.registerClass({
} }
} }
async _commitAction(keyval, str) {
if (this._modifiers.size === 0 && str !== '' &&
keyval && this._oskCompletionEnabled) {
if (await Main.inputMethod.handleVirtualKey(keyval))
return;
}
if (str === '' || !Main.inputMethod.currentFocus ||
(keyval && this._oskCompletionEnabled) ||
this._modifiers.size > 0 ||
!this._keyboardController.commitString(str, true)) {
if (keyval !== 0) {
this._forwardModifiers(this._modifiers, Clutter.EventType.KEY_PRESS);
this._keyboardController.keyvalPress(keyval);
GLib.timeout_add(GLib.PRIORITY_DEFAULT, KEY_RELEASE_TIMEOUT, () => {
this._keyboardController.keyvalRelease(keyval);
this._forwardModifiers(this._modifiers, Clutter.EventType.KEY_RELEASE);
this._disableAllModifiers();
return GLib.SOURCE_REMOVE;
});
}
}
}
_setLatched(latched) { _setLatched(latched) {
this._latched = latched; this._latched = latched;
this._setCurrentLevelLatched(this._currentPage, this._latched); this._setCurrentLevelLatched(this._currentPage, this._latched);
@ -1554,15 +1523,6 @@ export const Keyboard = GObject.registerClass({
this._setModifierEnabled(keyval, !isActive); this._setModifierEnabled(keyval, !isActive);
} }
_forwardModifiers(modifiers, type) {
for (const keyval of modifiers) {
if (type === Clutter.EventType.KEY_PRESS)
this._keyboardController.keyvalPress(keyval);
else if (type === Clutter.EventType.KEY_RELEASE)
this._keyboardController.keyvalRelease(keyval);
}
}
_disableAllModifiers() { _disableAllModifiers() {
for (const keyval of this._modifiers) for (const keyval of this._modifiers)
this._setModifierEnabled(keyval, false); this._setModifierEnabled(keyval, false);
@ -1699,8 +1659,7 @@ export const Keyboard = GObject.registerClass({
return; return;
} }
this._oskCompletionEnabled = this._keyboardController.oskCompletion = true;
IBusManager.getIBusManager().setCompletionEnabled(true, () => Main.inputMethod.update());
this._clearKeyboardRestTimer(); this._clearKeyboardRestTimer();
if (immediate) { if (immediate) {
@ -1735,8 +1694,7 @@ export const Keyboard = GObject.registerClass({
if (!this._keyboardVisible) if (!this._keyboardVisible)
return; return;
IBusManager.getIBusManager().setCompletionEnabled(false, () => Main.inputMethod.update()); this._keyboardController.oskCompletion = false;
this._oskCompletionEnabled = false;
this._clearKeyboardRestTimer(); this._clearKeyboardRestTimer();
if (immediate) { if (immediate) {
@ -2014,15 +1972,72 @@ class KeyboardController extends Signals.EventEmitter {
return this._currentSource.xkbId; return this._currentSource.xkbId;
} }
commitString(string, fromKey) { _forwardModifiers(modifiers, type) {
if (string == null) for (const keyval of modifiers) {
return false; if (type === Clutter.EventType.KEY_PRESS)
/* Let ibus methods fall through keyval emission */ this.keyvalPress(keyval);
if (fromKey && this._currentSource.type === InputSourceManager.INPUT_SOURCE_TYPE_IBUS) else if (type === Clutter.EventType.KEY_RELEASE)
return false; this.keyvalRelease(keyval);
}
}
Main.inputMethod.commit(string); _getKeyvalsFromString(string) {
return true; const keyvals = [];
for (const unicode of string) {
const keyval = Clutter.unicode_to_keysym(unicode.codePointAt(0));
// If the unicode character is unknown, try to avoid keyvals at all
if (keyval === (unicode || 0x01000000))
return [];
keyvals.push(keyval);
}
return keyvals;
}
async commit(str, modifiers) {
const keyvals = this._getKeyvalsFromString(str);
// If there is no IM focus (e.g. with X11 clients), or modifiers
// are in use, send raw key events.
if (!Main.inputMethod.currentFocus || modifiers?.size > 0) {
if (modifiers)
this._forwardModifiers(modifiers, Clutter.EventType.KEY_PRESS);
for (const keyval of keyvals) {
this.keyvalPress(keyval);
this.keyvalRelease(keyval);
}
if (modifiers)
this._forwardModifiers(modifiers, Clutter.EventType.KEY_RELEASE);
return;
}
// If OSK completion is enabled, or there is an active source requiring
// IBus to receive input, prefer to feed the events directly to the IM
if (this._oskCompletionEnabled ||
this._currentSource.type === InputSourceManager.INPUT_SOURCE_TYPE_IBUS) {
for (const keyval of keyvals) {
// eslint-disable-next-line no-await-in-loop
if (!await Main.inputMethod.handleVirtualKey(keyval)) {
this.keyvalPress(keyval);
this.keyvalRelease(keyval);
}
}
return;
}
Main.inputMethod.commit(str);
}
set oskCompletion(enabled) {
if (this._oskCompletionEnabled === enabled)
return;
this._oskCompletionEnabled =
IBusManager.getIBusManager().setCompletionEnabled(enabled, () => Main.inputMethod.update());
} }
keyvalPress(keyval) { keyvalPress(keyval) {