diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:54:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:54:43 +0000 |
commit | e4283f6d48b98e764b988b43bbc86b9d52e6ec94 (patch) | |
tree | c8f7f7a6c2f5faa2942d27cefc6fd46cca492656 /js/misc/inputMethod.js | |
parent | Initial commit. (diff) | |
download | gnome-shell-upstream.tar.xz gnome-shell-upstream.zip |
Adding upstream version 43.9.upstream/43.9upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/misc/inputMethod.js')
-rw-r--r-- | js/misc/inputMethod.js | 386 |
1 files changed, 386 insertions, 0 deletions
diff --git a/js/misc/inputMethod.js b/js/misc/inputMethod.js new file mode 100644 index 0000000..e01eac8 --- /dev/null +++ b/js/misc/inputMethod.js @@ -0,0 +1,386 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- +/* exported InputMethod */ +const { Clutter, GLib, Gio, GObject, IBus } = imports.gi; + +const Keyboard = imports.ui.status.keyboard; +const Main = imports.ui.main; + +Gio._promisify(IBus.Bus.prototype, + 'create_input_context_async', 'create_input_context_async_finish'); +Gio._promisify(IBus.InputContext.prototype, + 'process_key_event_async', 'process_key_event_async_finish'); + +var HIDE_PANEL_TIME = 50; + +const HAVE_REQUIRE_SURROUNDING_TEXT = GObject.signal_lookup('require-surrounding-text', IBus.InputContext); + +var InputMethod = GObject.registerClass({ + Signals: { + 'surrounding-text-set': {}, + 'terminal-mode-changed': {}, + }, +}, class InputMethod extends Clutter.InputMethod { + _init() { + super._init(); + this._hints = 0; + this._purpose = 0; + this._currentFocus = null; + this._preeditStr = ''; + this._preeditPos = 0; + this._preeditAnchor = 0; + this._preeditVisible = false; + this._hidePanelId = 0; + this.terminalMode = false; + this._ibus = IBus.Bus.new_async(); + this._ibus.connect('connected', this._onConnected.bind(this)); + this._ibus.connect('disconnected', this._clear.bind(this)); + this.connect('notify::can-show-preedit', this._updateCapabilities.bind(this)); + + this._inputSourceManager = Keyboard.getInputSourceManager(); + this._sourceChangedId = this._inputSourceManager.connect('current-source-changed', + this._onSourceChanged.bind(this)); + this._currentSource = this._inputSourceManager.currentSource; + + if (this._ibus.is_connected()) + this._onConnected(); + } + + get currentFocus() { + return this._currentFocus; + } + + _updateCapabilities() { + let caps = IBus.Capabilite.PREEDIT_TEXT | IBus.Capabilite.FOCUS | IBus.Capabilite.SURROUNDING_TEXT; + + if (Main.keyboard.visible) + caps |= IBus.Capabilite.OSK; + + if (this._context) + this._context.set_capabilities(caps); + } + + _onSourceChanged() { + this._currentSource = this._inputSourceManager.currentSource; + } + + async _onConnected() { + this._cancellable = new Gio.Cancellable(); + try { + this._context = await this._ibus.create_input_context_async( + 'gnome-shell', -1, this._cancellable); + } catch (e) { + if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { + logError(e); + this._clear(); + } + return; + } + + this._context.set_client_commit_preedit(true); + this._context.connect('commit-text', this._onCommitText.bind(this)); + this._context.connect('delete-surrounding-text', this._onDeleteSurroundingText.bind(this)); + this._context.connect('update-preedit-text-with-mode', this._onUpdatePreeditText.bind(this)); + this._context.connect('show-preedit-text', this._onShowPreeditText.bind(this)); + this._context.connect('hide-preedit-text', this._onHidePreeditText.bind(this)); + this._context.connect('forward-key-event', this._onForwardKeyEvent.bind(this)); + this._context.connect('destroy', this._clear.bind(this)); + + if (HAVE_REQUIRE_SURROUNDING_TEXT) + this._context.connect('require-surrounding-text', this._onRequireSurroundingText.bind(this)); + + Main.keyboard.connectObject('visibility-changed', () => this._updateCapabilities()); + + this._updateCapabilities(); + } + + _clear() { + Main.keyboard.disconnectObject(this); + + if (this._cancellable) { + this._cancellable.cancel(); + this._cancellable = null; + } + + this._context = null; + this._hints = 0; + this._purpose = 0; + this._preeditStr = ''; + this._preeditPos = 0; + this._preeditAnchor = 0; + this._preeditVisible = false; + } + + _emitRequestSurrounding() { + if (this._context.needs_surrounding_text()) + this.emit('request-surrounding'); + } + + _onCommitText(_context, text) { + this.commit(text.get_text()); + } + + _onRequireSurroundingText(_context) { + this.request_surrounding(); + } + + _onDeleteSurroundingText(_context, offset, nchars) { + try { + this.delete_surrounding(offset, nchars); + } catch (e) { + // We may get out of bounds for negative offset on older mutter + this.delete_surrounding(0, nchars + offset); + } + } + + _onUpdatePreeditText(_context, text, pos, visible, mode) { + if (text == null) + return; + + let preedit = text.get_text(); + if (preedit === '') + preedit = null; + + const anchor = pos; + if (visible) + this.set_preedit_text(preedit, pos, anchor, mode); + else if (this._preeditVisible) + this.set_preedit_text(null, pos, anchor, mode); + + this._preeditStr = preedit; + this._preeditPos = pos; + this._preeditAnchor = anchor; + this._preeditVisible = visible; + this._preeditCommitMode = mode; + } + + _onShowPreeditText() { + this._preeditVisible = true; + this.set_preedit_text( + this._preeditStr, this._preeditPos, this._preeditAnchor, + this._preeditCommitMode); + } + + _onHidePreeditText() { + this.set_preedit_text( + null, this._preeditPos, this._preeditAnchor, + this._preeditCommitMode); + this._preeditVisible = false; + } + + _onForwardKeyEvent(_context, keyval, keycode, state) { + let press = (state & IBus.ModifierType.RELEASE_MASK) == 0; + state &= ~IBus.ModifierType.RELEASE_MASK; + + let curEvent = Clutter.get_current_event(); + let time; + if (curEvent) + time = curEvent.get_time(); + else + time = global.display.get_current_time_roundtrip(); + + this.forward_key(keyval, keycode + 8, state & Clutter.ModifierType.MODIFIER_MASK, time, press); + } + + vfunc_focus_in(focus) { + this._currentFocus = focus; + if (this._context) { + this.update(); + this._context.focus_in(); + this._emitRequestSurrounding(); + } + + if (this._hidePanelId) { + GLib.source_remove(this._hidePanelId); + this._hidePanelId = 0; + } + } + + vfunc_focus_out() { + this._currentFocus = null; + if (this._context) { + this._fullReset(); + this._context.focus_out(); + } + + if (this._preeditStr && this._preeditVisible) { + // Unset any preedit text + this.set_preedit_text(null, 0, 0, this._preeditCommitMode); + this._preeditStr = null; + } + + this._hidePanelId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, HIDE_PANEL_TIME, () => { + this.set_input_panel_state(Clutter.InputPanelState.OFF); + this._hidePanelId = 0; + return GLib.SOURCE_REMOVE; + }); + } + + vfunc_reset() { + if (this._context) { + this._context.reset(); + this._emitRequestSurrounding(); + } + + this._surroundingText = null; + this._surroundingTextCursor = null; + this._preeditStr = null; + this._setTerminalMode(false); + } + + vfunc_set_cursor_location(rect) { + if (this._context) { + this._cursorRect = { + x: rect.get_x(), y: rect.get_y(), + width: rect.get_width(), height: rect.get_height(), + }; + this._context.set_cursor_location( + this._cursorRect.x, this._cursorRect.y, + this._cursorRect.width, this._cursorRect.height); + this._emitRequestSurrounding(); + } + } + + vfunc_set_surrounding(text, cursor, anchor) { + this._surroundingText = text; + this._surroundingTextCursor = cursor; + this.emit('surrounding-text-set'); + + if (!this._context || (!text && text !== '')) + return; + + let ibusText = IBus.Text.new_from_string(text); + this._context.set_surrounding_text(ibusText, cursor, anchor); + } + + vfunc_update_content_hints(hints) { + let ibusHints = 0; + if (hints & Clutter.InputContentHintFlags.COMPLETION) + ibusHints |= IBus.InputHints.WORD_COMPLETION; + if (hints & Clutter.InputContentHintFlags.SPELLCHECK) + ibusHints |= IBus.InputHints.SPELLCHECK; + if (hints & Clutter.InputContentHintFlags.AUTO_CAPITALIZATION) + ibusHints |= IBus.InputHints.UPPERCASE_SENTENCES; + if (hints & Clutter.InputContentHintFlags.LOWERCASE) + ibusHints |= IBus.InputHints.LOWERCASE; + if (hints & Clutter.InputContentHintFlags.UPPERCASE) + ibusHints |= IBus.InputHints.UPPERCASE_CHARS; + if (hints & Clutter.InputContentHintFlags.TITLECASE) + ibusHints |= IBus.InputHints.UPPERCASE_WORDS; + + this._hints = ibusHints; + if (this._context) + this._context.set_content_type(this._purpose, this._hints); + } + + vfunc_update_content_purpose(purpose) { + let ibusPurpose = 0; + if (purpose == Clutter.InputContentPurpose.NORMAL) + ibusPurpose = IBus.InputPurpose.FREE_FORM; + else if (purpose == Clutter.InputContentPurpose.ALPHA) + ibusPurpose = IBus.InputPurpose.ALPHA; + else if (purpose == Clutter.InputContentPurpose.DIGITS) + ibusPurpose = IBus.InputPurpose.DIGITS; + else if (purpose == Clutter.InputContentPurpose.NUMBER) + ibusPurpose = IBus.InputPurpose.NUMBER; + else if (purpose == Clutter.InputContentPurpose.PHONE) + ibusPurpose = IBus.InputPurpose.PHONE; + else if (purpose == Clutter.InputContentPurpose.URL) + ibusPurpose = IBus.InputPurpose.URL; + else if (purpose == Clutter.InputContentPurpose.EMAIL) + ibusPurpose = IBus.InputPurpose.EMAIL; + else if (purpose == Clutter.InputContentPurpose.NAME) + ibusPurpose = IBus.InputPurpose.NAME; + else if (purpose == Clutter.InputContentPurpose.PASSWORD) + ibusPurpose = IBus.InputPurpose.PASSWORD; + else if (purpose === Clutter.InputContentPurpose.TERMINAL && + IBus.InputPurpose.TERMINAL) + ibusPurpose = IBus.InputPurpose.TERMINAL; + + this._setTerminalMode( + purpose === Clutter.InputContentPurpose.TERMINAL); + + this._purpose = ibusPurpose; + if (this._context) + this._context.set_content_type(this._purpose, this._hints); + } + + _setTerminalMode(terminalMode) { + if (this.terminalMode !== terminalMode) { + this.terminalMode = terminalMode; + this.emit('terminal-mode-changed'); + } + } + + vfunc_filter_key_event(event) { + if (!this._context) + return false; + if (!this._currentSource) + return false; + + let state = event.get_state(); + if (state & IBus.ModifierType.IGNORED_MASK) + return false; + + if (event.type() == Clutter.EventType.KEY_RELEASE) + state |= IBus.ModifierType.RELEASE_MASK; + + this._context.process_key_event_async( + event.get_key_symbol(), + event.get_key_code() - 8, // Convert XKB keycodes to evcodes + state, -1, this._cancellable, + (context, res) => { + if (context != this._context) + return; + + try { + let retval = context.process_key_event_async_finish(res); + this.notify_key_event(event, retval); + } catch (e) { + if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) + log(`Error processing key on IM: ${e.message}`); + } + }); + return true; + } + + getSurroundingText() { + return [this._surroundingText, this._surroundingTextCursor]; + } + + hasPreedit() { + return this._preeditVisible && this._preeditStr !== '' && this._preeditStr !== null; + } + + async handleVirtualKey(keyval) { + try { + if (!await this._context.process_key_event_async( + keyval, 0, 0, -1, null)) + return false; + + await this._context.process_key_event_async( + keyval, 0, IBus.ModifierType.RELEASE_MASK, -1, null); + return true; + } catch (e) { + return false; + } + } + + _fullReset() { + this._context.set_content_type(0, 0); + this._context.set_cursor_location(0, 0, 0, 0); + this._context.reset(); + } + + update() { + if (!this._context) + return; + this._updateCapabilities(); + this._context.set_content_type(this._purpose, this._hints); + if (this._cursorRect) { + this._context.set_cursor_location( + this._cursorRect.x, this._cursorRect.y, + this._cursorRect.width, this._cursorRect.height); + } + this._emitRequestSurrounding(); + } +}); |