GNOME shell extension for input methods

Date: 2023/06/19 (initial publish), 2024/02/23 (last update)

Source: en/note-00047.md

Previous Post Top Next Post

TOC

Since I didn’t find GNOME shell extension to provide shortcuts to input method activations, I decided to create one by following examples of existing extensions without rigorous learning. I also worked on quick touchpad control. See my final result at:

I also updated GNOME extensions for better UX.

Simple GNOME shell extension

One can create a skelton of a simple GNOME shell extension as:

$ gnome-shell-extension-tool --create-extension

This created a almost empty GNOME shell extension under ~/.local/share/gnome-shell/extensions/.

After “Log out” and “Log in” to my account, this GNOME shell extension can be seen from “Extensions”. But this was too simple to do anything useful.

Reference examples

Since I had no idea how to set up keybindings for switching input methods, I looked around examples on simple GNOME extensions with “shortcut” as a keyword.

GNOME extension “Shortcuts”

GNOME extension Shortcuts looked like simple enough since all it does is toggling of display when <Super>s is pressed.

But there is no text like super in the source. It looks like imports.ui.main.overview gets a call back to enable toggling of display.

const Main = imports.ui.main;
...
  Main.overview._specialToggle = function (evt) {
    _toggleShortcuts();
  };

gnome-shell source has js/ui/main.js which calls overview.init() then js/ui/overviw.js which has overview.init() calling:

const Main = imports.ui.main;
...
Main.wm.addKeybinding(
    'toggle-overview',
    new Gio.Settings({ schema_id: WindowManager.SHELL_KEYBINDINGS_SCHEMA }),
    Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
    Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW,
    this.toggle.bind(this));

In data/org.gnome.shell.gschema.xml.in from gnome-shell source, I found:

<key name="toggle-overview" type="as">
<default>["&lt;Super&gt;s"]</default>
<summary>Keybinding to open the overview</summary>
<description>
│ │ Keybinding to open the Activities Overview.
</description>
</key>

So basically, this extension uses keybinding defined by gnome-shell.

The schema definition <default>["&lt;Super&gt;s"]</default> used in data/org.gnome.shell.gschema.xml.in is interesting. Also, imports.ui.main.wm.addKeybinding() seems to be the way to bind specific key to a JavaScript code.

I also got idea to use IGNORE_AUTOREPEAT from this.

GNOME extension “More keyboard shortcuts”

More keyboard shortcuts also looked like simple enough since all it does is switching window on the current workspace when <Super>j or <Super>k is pressed.

This source has its own schemas/org.gnome.shell.extensions.more-keyboard-shortcuts.gschema.xml with <default><![CDATA[['<Super>j']]]></default> and <default><![CDATA[['<Super>k']]]></default> for <key type="as" name="switch-window-next-workspace"> and <key type="as" name="switch-window-prev-workspace">.

I suppose this CDATA thing is the same thing as <default>["&lt;Super&gt;j"]</default> and <default>["&lt;Super&gt;k"]</default>.

I also see extension.js has the following which binds “key name” to JavaScript.

function enable() {
    Main.wm.addKeybinding("switch-window-next-workspace",
        settings,
        Meta.KeyBindingFlags.NONE,
        Shell.ActionMode ? Shell.ActionMode.NORMAL : Shell.KeyBindingMode.NORMAL,
        function(display, screen, window, binding) {
            switchWindow(true);
        }
    );
    Main.wm.addKeybinding("switch-window-prev-workspace",
        settings,
        Meta.KeyBindingFlags.NONE,
        Shell.ActionMode ? Shell.ActionMode.NORMAL : Shell.KeyBindingMode.NORMAL,
        function(display, screen, window, binding) {
            switchWindow(false);
        }
    );
}

By replacing switchWindow() with Input Method setting JavaScript function, I think I can get desired extension functionality.

Since this was my first JavaScript program, there were many rough edges.

Initial working GNOME shell extension for switching input methods

I then went to upload a zip file of this newly made extension to GNOME site. It became very interesting learning experiences.

inputmethod-shortcuts (v1: rejected)

JustPerfection gave me feed backs.

OsamuAoki's extension, "Shortcuts to activate input methods", version 1 has a new review:

JustPerfection posted a review on June 25, 2023:

1. Please remove `stylesheet.css` since you are not using it:
    https://gjs.guide/extensions/review-guidelines/review-guidelines.html#don-t-include-unnecessary-files

2. Remove versions &gt;=45 since we don&#x27;t have those versions yet.

3. Use initTranslations() and getSettings() from ExtensionUtils instead of creating your own custom functions (remove convenience.js after that):
    https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/misc/extensionUtils.js

4. That&#x27;s too much for init (line 42 extension.js):
    https://gjs.guide/extensions/review-guidelines/review-guidelines.html#only-use-init-for-initialization

    Move that line to enable and null that out in disable:
    https://gjs.guide/extensions/review-guidelines/review-guidelines.html#destroy-all-objects

5. Remove all the key bindings on disable (line 118-149 extension.js).

If you need any help with your extension you can ask us on:
- [GNOME Matrix Channel](https://matrix.to/#/#extensions:gnome.org)
- IRC Bridge: irc://irc.gimpnet.org/shell-extensions

Please use the review page to follow up:

https://extensions.gnome.org/review/42618

This was very helpful. I thought I fixed them all but (I forgot to erase old zip in the source tree.) I uploaded again.

inputmethod-shortcuts (v2: rejected)

OsamuAoki's extension, "Shortcuts to activate input methods", version 2 has a new review:

JustPerfection posted a review on June 25, 2023:

1. You forgot to remove `stylesheet.css` and `convenience.js`.
2. As I said before, you should null out `settings` in disable.
3. I recommend to Import in top of the code instead of inside enable (line 51, 60, 69 and 78 extension.js).

Please use the review page to follow up:

https://extensions.gnome.org/review/42621
OsamuAoki's extension, "Shortcuts to activate input methods", version 2 has a new review:

JustPerfection posted a review on June 25, 2023:

1. You forgot to remove `stylesheet.css` and `convenience.js`.
2. As I said before, you should null out `settings` in disable.
3. I recommend to Import in top of the code instead of inside enable (line 51, 60, 69 and 78 extension.js).

Please use the review page to follow up:

https://extensions.gnome.org/review/42621

Very kind words on careless newbie.

inputmethod-shortcuts (v3: rejected)

I missed issue 2 pointed out for version 2. So I uploaded another one before getting official rejection.

inputmethod-shortcuts (v4: accepted)

This got accepted. (I saw some minor issues and fixed them in git repo, later.)

Further improvements of GNOME extension inputmethod-shortcuts (v5 … v15)

I explored more refactoring of code and feature additions to support shortcut configuration.

Use of ES6 class-syntax

Both ES5 prototype-syntax and ES6 class-syntax offer OOP features, but ES6 class-syntax is less confusing for me. So I decided to rewite inputmethod-shortcuts using ES6 class-syntax. – done

Use for-loop

Repeating codes looks stupid. So I added for-loop. – done

Add prefs.js

Make shortcut key choice configurable. – done

Get IM settings

Use Gio -> Settings – done

So IM names are displayed and their shortcuts are configurable up to 10.

prefs.js

This UI will be updated later as below with markups.

Control touchpad enabled / disabled

Since the following shell code lines turn on/off touchpad:

$ gsettings set org.gnome.desktop.peripherals.touchpad send-events enabled
$ gsettings set org.gnome.desktop.peripherals.touchpad send-events disabled

I tried the same thing in gjs-console:

gjs> Touchpad = new imports.gi.Gio.Settings({ schema_id: 'org.gnome.desktop.peripherals.touchpad' })
[object instance wrapper GIName:Gio.Settings jsobj@0x36b95d5f5b8 native@0x7fee6000fc60]
gjs> Touchpad.get_value("send-events")
[object variant of type "s"]
gjs> Touchpad.get_string("send-events")
"disabled"
gjs> Touchpad.set_string("send-events", "enabled")
true
gjs> Touchpad.set_string("send-events", "disabled")
true

I refactored prefs.js and added touchpad control shortcuts.

NOTE: Media key for touchpad seems to cause pop-up icons displayed without changes to the touchpad state. (schema=org.gnome.settings-daemon.plugins.media-keys, key=touchpad-on)

Input Method Shortcuts (example screenshot)

Here is the updated UI with markups.

InputMethod Shortcuts

Here, I disabled <Super>Space and <Super><Shift>Space usages in “Settings” -> “Keyboard” -> “Keyboard shortcuts” -> “Typing” before setting up as above.

Please note that, if an ibus can offer its internal shortcuts to activate input method engine (IME) and to deactivate IME by using direct input mode just with xkb for latin character set, switching between IME and direct input mode within ibus is quicker than using Desktop based input method switching functionality handled by this extension.

Touchpad Shortcuts (example screenshot)

Touchpad Shortcuts

Please note that you don’t need to set all shortcut bindings.

Operation Preference (example screenshot)

Operation Preference

xkb=ru interference fix

Normal ibus-based input methods break when they are invoked after using xkb=ru. In order to reduce shortcut key presses, I added feature to go through the primary xkb before invoking any ibus.

Add test-keys for code verification

I created dummy extension to test code snippets to debug code

Use keyvalIsForbidden(keyval)

Force to avoid cursor and return keys.

Fix constant calling

-    if ((mask === 0 || mask === Gdk.SHIFT_MASK) && keycode !== 0) {
+    if ((mask === 0 || mask === Gdk.ModifierType.SHIFT_MASK) && keycode !== 0) {

Use only non-SHIFT BackSpace to delete

-            if (keyval === Gdk.KEY_BackSpace) {
+            if (mask === 0 && keyval === Gdk.KEY_BackSpace) {

Modifier-combos as shortcut

There are some odd key code:

So adding feature to register Modifier-combos as shortcut was a bit tricky.

prefs-im-alt.js

(Including Alt and AltGr in combo was not the best idea since their keysym isn’t stable.)

So I use now:

prefs-im-alt2.js

Here, I reactivated original GNOME functionality of using <Super>space to rotate input methods.

GNOME 45

Code update for GNOME 45 was donated by Álan Crístoffer.

References

Learning JavaScript for GJS

When I started to write GJS JavaScript code for this extension, I had no idea on JavaScript except it is a interpreter with C/C++-like syntax. Here is a list of reference documents I decided to read to understand JavaScript and its use for GNOME.

JavaScript versions were confusing to me. Major versions are:

I also find a nice summary of generic style equivalences of ES5 and ES6 at w3schools.com site for JavaScript.

History and migration: GTK3/GTK4/Libadwaita/GNOME

Following references helped me to orient myself what is going on.

Since Debian bookworm/12 is GNOME 43, it seems good idea to use GTK4 with Libadwaita.

Also, any new applications which are missing on host system, such as newer Shotwell (0.32.1) which supports HEIF and Cambalach for GTK4 GUI design, are best installed via flatpak.

Tip: Checking systemd log

I can see all gnome-shell log including DEBUG output from the last system boot as:

$ journalctl -p 7 -b -g gnome-shell

Special Character Entry

Options

Conclusion: Use “US, intl., with AltGr dead keys”

Rationale:

There are 2 ways to enter special non-ASCII characters under en_US.UTF-8.

The meaning of AltGr is discussed on stackexchange in details.

Input method key bindings

Here is a summary of IM key bindings

Mode ATOK MS-IME Command
Composition C-h / BS C-h / BS Backspace
Composition C-g / DEL C-g / DEL Delete
Composition C-@ / F10 C-t / F10 Conv. to half-alnum
Composition C-p / F9 C-p / F9 Conv. to full-alnum
Composition C-o / F8 C-o / F8 Conv. to half
Composition C-i / F7 C-i / F7 Conv. to full-kata.
Composition C-u / F6 C-u / F6 Conv. to full-hira.
Composition C-k / Lft C-k / Lft / C-s Left
Composition C-l / Rgt C-l / Rgt / C-d Right
Composition C-lft / Home C-a / Hom / C-e Home
Composition C-rgt / End C-f / End End
Conversion C-h / BS C-h / BS Cancel
Conversion C-g / DEL Cancel
Conversion C-@ / F10 C-t / F10 Conv. to half-alnum
Conversion C-p / F9 C-p / F9 Conv. to full-alnum
Conversion C-o / F8 C-o / F8 Conv. to half
Conversion C-i / F7 C-i / F7 Conv. to full-kata.
Conversion C-u / F6 C-u / F6 Conv. to full-hira.
Conversion C-k / Lft C-k / S-Lft Shrink segment
Conversion C-l / Rgt C-l / S-Rgt Expand segment
Conversion / S-Lft C-s / Lft Move segment focus left
Conversion / S-Rgt C-d / Rgt Move segment focus right
Conversion Home / C-Lft C-a / Hom Move segment focus first
Conversion End / C-Rgt C-f / End Move segment focus last
Conversion C-m / Enter C-m / Enter Commit
Conversion C-n C-n Commit only first segment
Conversion SPACE SPACE Select next cand.
Conversion C-e / S-TAB C-e / S-TAB Select prev. cand.

In short, these are almost the same.

Actions for arrow-keys and their shifted ones are swapped. Old ATOK users like me need to get used to MS-IME style now. I set Mozc with MS-IME binding.

Please note almost all control key combination may not work as intended since they may be consumed by OS or applications such as vi.

Please also see Keyboard shortcut customization (IM) to accommodate US-keyboard.

NOTE: I gave up to match key bindings of IM to ones of BASH.

BASH Cursor vi Move
C-a Home beginning-of-line
C-e End end-of-line
C-f Right forward-char
C-b Left backward-char
C-p Up previous-history
C-n Down next-history
C-h Backspace delete-backward-char
C-d Del delete-char
Previous Post Top Next Post