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: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/3162>
This commit is contained in:
Carlos Garnacho 2024-01-30 13:53:03 +01:00
parent 64d300b525
commit 7db0e01b24

View File

@ -1244,6 +1244,7 @@ export const Keyboard = GObject.registerClass({
_onFocusPositionChanged(focusTracker) { _onFocusPositionChanged(focusTracker) {
let rect = focusTracker.getCurrentRect(); let rect = focusTracker.getCurrentRect();
this.setCursorLocation(focusTracker.currentWindow, rect.x, rect.y, rect.width, rect.height); this.setCursorLocation(focusTracker.currentWindow, rect.x, rect.y, rect.width, rect.height);
this._updateLevelFromHints();
} }
_onDestroy() { _onDestroy() {
@ -1305,6 +1306,7 @@ export const Keyboard = GObject.registerClass({
'group-changed', this._onGroupChanged.bind(this), 'group-changed', this._onGroupChanged.bind(this),
'panel-state', this._onKeyboardStateChanged.bind(this), 'panel-state', this._onKeyboardStateChanged.bind(this),
'purpose-changed', this._onPurposeChanged.bind(this), 'purpose-changed', this._onPurposeChanged.bind(this),
'content-hints-changed', this._onContentHintsChanged.bind(this),
this); this);
global.stage.connectObject('notify::key-focus', global.stage.connectObject('notify::key-focus',
this._onKeyFocusChanged.bind(this), this); this._onKeyFocusChanged.bind(this), this);
@ -1317,6 +1319,60 @@ export const Keyboard = GObject.registerClass({
this._updateKeys(); 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() { _onKeyFocusChanged() {
let focus = global.stage.key_focus; let focus = global.stage.key_focus;
@ -1443,8 +1499,12 @@ export const Keyboard = GObject.registerClass({
this._keyboardController.commit(str, this._modifiers).then(() => { this._keyboardController.commit(str, this._modifiers).then(() => {
this._disableAllModifiers(); this._disableAllModifiers();
if (layout.mode === 'default' || if (layout.mode === 'default' ||
(layout.mode === 'latched' && !this._latched)) (layout.mode === 'latched' && !this._latched)) {
this._setActiveLevel('default'); if (this._contentHint !== 0)
this._updateLevelFromHints();
else
this._setActiveLevel('default');
}
}).catch(console.error); }).catch(console.error);
}); });
} }
@ -1925,8 +1985,8 @@ class KeyboardController extends Signals.EventEmitter {
this._currentSource = this._inputSourceManager.currentSource; this._currentSource = this._inputSourceManager.currentSource;
Main.inputMethod.connectObject( Main.inputMethod.connectObject(
'notify::content-purpose', this._onContentPurposeHintsChanged.bind(this), 'notify::content-purpose', this._onPurposeHintsChanged.bind(this),
'notify::content-hints', this._onContentPurposeHintsChanged.bind(this), 'notify::content-hints', this._onContentHintsChanged.bind(this),
'input-panel-state', (o, state) => this.emit('panel-state', state), 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'); this.emit('group-changed');
} }
_onContentPurposeHintsChanged(method) { _onPurposeHintsChanged(method) {
const purpose = method.content_purpose; const purpose = method.content_purpose;
this._purpose = purpose; this._purpose = purpose;
this.emit('purpose-changed', purpose); this.emit('purpose-changed', purpose);
} }
_onContentHintsChanged(method) {
const contentHints = method.content_hints;
this._contentHints = contentHints;
this.emit('content-hints-changed', contentHints);
}
getCurrentGroup() { getCurrentGroup() {
// Special case for Korean, if Hangul mode is disabled, use the 'us' keymap // Special case for Korean, if Hangul mode is disabled, use the 'us' keymap
if (this._currentSource.id === 'hangul') { if (this._currentSource.id === 'hangul') {