From 7db0e01b24010296178193f37cd5050a509e0508 Mon Sep 17 00:00:00 2001 From: Carlos Garnacho Date: Tue, 30 Jan 2024 13:53:03 +0100 Subject: [PATCH] keyboard: Forward/handle content hints Forward these from the IM, and handle the ones involving the OSK state: lowercase/uppercase/auto_capitalization/titlecase. The latter two involve peeking at the surrounding text, to figure out if it makes sense to toggle keyboard level. Part-of: --- js/ui/keyboard.js | 76 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 71 insertions(+), 5 deletions(-) diff --git a/js/ui/keyboard.js b/js/ui/keyboard.js index 661c88038..d095c03dc 100644 --- a/js/ui/keyboard.js +++ b/js/ui/keyboard.js @@ -1244,6 +1244,7 @@ export const Keyboard = GObject.registerClass({ _onFocusPositionChanged(focusTracker) { let rect = focusTracker.getCurrentRect(); this.setCursorLocation(focusTracker.currentWindow, rect.x, rect.y, rect.width, rect.height); + this._updateLevelFromHints(); } _onDestroy() { @@ -1305,6 +1306,7 @@ export const Keyboard = GObject.registerClass({ 'group-changed', this._onGroupChanged.bind(this), 'panel-state', this._onKeyboardStateChanged.bind(this), 'purpose-changed', this._onPurposeChanged.bind(this), + 'content-hints-changed', this._onContentHintsChanged.bind(this), this); global.stage.connectObject('notify::key-focus', this._onKeyFocusChanged.bind(this), this); @@ -1317,6 +1319,60 @@ export const Keyboard = GObject.registerClass({ this._updateKeys(); } + _onContentHintsChanged(controller, contentHint) { + this._contentHint = contentHint; + this._updateLevelFromHints(); + } + + _updateLevelFromHints() { + // If the latch is enabled, avoid level changes + if (this._latched) + return; + + if ((this._contentHint & Clutter.InputContentHintFlags.LOWERCASE) !== 0) { + this._setActiveLevel('default'); + return; + } + + if (!this._layers['shift']) + return; + + if ((this._contentHint & Clutter.InputContentHintFlags.UPPERCASE) !== 0) { + this._setActiveLevel('shift'); + } else if (!this._surroundingTextId && + (this._contentHint & + (Clutter.InputContentHintFlags.AUTO_CAPITALIZATION | + Clutter.InputContentHintFlags.TITLECASE)) !== 0) { + this._surroundingTextId = + Main.inputMethod.connect('surrounding-text-set', () => { + const [text, cursor] = Main.inputMethod.getSurroundingText(); + if (!text || cursor === 0) { + // First character in the buffer + this._setActiveLevel('shift'); + return; + } + + const beforeCursor = GLib.utf8_substring(text, 0, cursor); + + if ((this._contentHint & Clutter.InputContentHintFlags.TITLECASE) !== 0) { + if (beforeCursor.charAt(beforeCursor.length - 1) === ' ') + this._setActiveLevel('shift'); + else + this._setActiveLevel('default'); + } else if ((this._contentHint & Clutter.InputContentHintFlags.AUTO_CAPITALIZATION) !== 0) { + if (beforeCursor.charAt(beforeCursor.trimEnd().length - 1) === '.') + this._setActiveLevel('shift'); + else + this._setActiveLevel('default'); + } + + Main.inputMethod.disconnect(this._surroundingTextId); + this._surroundingTextId = 0; + }); + Main.inputMethod.request_surrounding(); + } + } + _onKeyFocusChanged() { let focus = global.stage.key_focus; @@ -1443,8 +1499,12 @@ export const Keyboard = GObject.registerClass({ this._keyboardController.commit(str, this._modifiers).then(() => { this._disableAllModifiers(); if (layout.mode === 'default' || - (layout.mode === 'latched' && !this._latched)) - this._setActiveLevel('default'); + (layout.mode === 'latched' && !this._latched)) { + if (this._contentHint !== 0) + this._updateLevelFromHints(); + else + this._setActiveLevel('default'); + } }).catch(console.error); }); } @@ -1925,8 +1985,8 @@ class KeyboardController extends Signals.EventEmitter { this._currentSource = this._inputSourceManager.currentSource; Main.inputMethod.connectObject( - 'notify::content-purpose', this._onContentPurposeHintsChanged.bind(this), - 'notify::content-hints', this._onContentPurposeHintsChanged.bind(this), + 'notify::content-purpose', this._onPurposeHintsChanged.bind(this), + 'notify::content-hints', this._onContentHintsChanged.bind(this), 'input-panel-state', (o, state) => this.emit('panel-state', state), this); } @@ -1949,12 +2009,18 @@ class KeyboardController extends Signals.EventEmitter { this.emit('group-changed'); } - _onContentPurposeHintsChanged(method) { + _onPurposeHintsChanged(method) { const purpose = method.content_purpose; this._purpose = purpose; this.emit('purpose-changed', purpose); } + _onContentHintsChanged(method) { + const contentHints = method.content_hints; + this._contentHints = contentHints; + this.emit('content-hints-changed', contentHints); + } + getCurrentGroup() { // Special case for Korean, if Hangul mode is disabled, use the 'us' keymap if (this._currentSource.id === 'hangul') {