gnome-shell/js/misc/inputMethod.js
Carlos Garnacho d44ffc1158 inputMethod: Use forward_key() method to forward key events
ClutterVirtualInputDevice has the limitation that event flags won't be
made to contain CLUTTER_EVENT_FLAG_INPUT_METHOD, possibly causing feedback
loops.

As the event gets injected up the platform dependent bits, we can avoid
care on not pressing the same key twice, we still expect coherence between
key presses and releases from the IM though.

https://gitlab.gnome.org/GNOME/gnome-shell/issues/531
2018-10-08 16:35:53 +00:00

250 lines
8.9 KiB
JavaScript

// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const Clutter = imports.gi.Clutter;
const IBus = imports.gi.IBus;
const Keyboard = imports.ui.status.keyboard;
const Lang = imports.lang;
const Signals = imports.signals;
var InputMethod = new Lang.Class({
Name: 'InputMethod',
Extends: Clutter.InputMethod,
_init() {
this.parent();
this._hints = 0;
this._purpose = 0;
this._enabled = true;
this._currentFocus = null;
this._preeditStr = '';
this._preeditPos = 0;
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 = 0;
if (this.can_show_preedit)
caps |= IBus.Capabilite.PREEDIT_TEXT;
if (this._currentFocus)
caps |= IBus.Capabilite.FOCUS | IBus.Capabilite.SURROUNDING_TEXT;
else
caps |= IBus.Capabilite.PREEDIT_TEXT | IBus.Capabilite.AUXILIARY_TEXT | IBus.Capabilite.LOOKUP_TABLE | IBus.Capabilite.PROPERTY;
if (this._context)
this._context.set_capabilities(caps);
},
_onSourceChanged() {
this._currentSource = this._inputSourceManager.currentSource;
},
_onConnected() {
this._ibus.create_input_context_async ('gnome-shell', -1, null,
this._setContext.bind(this));
},
_setContext(bus, res) {
this._context = this._ibus.create_input_context_async_finish(res);
this._context.connect('enabled', () => { this._enabled = true });
this._context.connect('disabled', () => { this._enabled = false });
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', 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._updateCapabilities();
},
_clear() {
this._context = null;
this._hints = 0;
this._purpose = 0;
this._enabled = false;
this._preeditStr = ''
this._preeditPos = 0;
},
_emitRequestSurrounding() {
if (this._context.needs_surrounding_text())
this.emit('request-surrounding');
},
_onCommitText(context, text) {
this.commit(text.get_text());
},
_onDeleteSurroundingText(context) {
this.delete_surrounding();
},
_onUpdatePreeditText(context, text, pos, visible) {
if (text == null)
return;
this._preeditStr = text.get_text();
this._preeditPos = pos;
if (visible)
this.set_preedit_text(this._preeditStr, pos);
else
this.set_preedit_text(null, pos);
},
_onShowPreeditText(context) {
this.set_preedit_text(this._preeditStr, this._preeditPos);
},
_onHidePreeditText(context) {
this.set_preedit_text(null, this._preeditPos);
},
_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._context.focus_in();
this._updateCapabilities();
this._emitRequestSurrounding();
}
},
vfunc_focus_out() {
this._currentFocus = null;
if (this._context) {
this._context.focus_out();
this._updateCapabilities();
}
// Unset any preedit text
this.set_preedit_text(null, 0);
},
vfunc_reset() {
if (this._context) {
this._context.reset();
this._emitRequestSurrounding();
}
// Unset any preedit text
this.set_preedit_text(null, 0);
},
vfunc_set_cursor_location(rect) {
if (this._context) {
this._context.set_cursor_location(rect.get_x(), rect.get_y(),
rect.get_width(), rect.get_height());
this._emitRequestSurrounding();
}
},
vfunc_set_surrounding(text, cursor, anchor) {
if (!this._context || !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;
this._purpose = ibusPurpose;
if (this._context)
this._context.set_content_type(this._purpose, this._hints);
},
vfunc_filter_key_event(event) {
if (!this._context || !this._enabled)
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, null,
(context, res) => {
try {
let retval = context.process_key_event_async_finish(res);
this.notify_key_event(event, retval);
} catch (e) {
log('Error processing key on IM: ' + e.message);
}
});
return true;
},
});