How to initiate automatic execution of commands upon keyboard layout switch?

Solution 1:

How language switching is implemented on Ubuntu

By default, Ubuntu's input source is set in a database called gsettings. Each setting has a specific schema associated with it. In particular, language switching is associated with org.gnome.desktop.input-sources schema. That's the one we're interested in for all modern Ubuntu versions.

More specifically, we're interested in its key current. When you change language via GUI, that key gets modified. There's many things you can do with it, from switching input language in command-line to defining shortcuts for each input method, even setting input method for each individual app can be done. One way to do so is via gsettings command or dconf command. These are external programs which live in your /usr/bin/ folder (this is the same method that Jacob's answer uses). Another way, is via specific set of utilities ( API's actually ) for Python programming language. This is the method I will show in my answer.

It should be noted that gsettings doesn't always work. If you are using a non-standard input method, such as fcitx for example, that might not be true. In fact sogou-pinyin ( Chinese input method ) uses something else known as dbus, so approach with gsettings won't work. But for simple case where you have default Ubuntu, gsettings database is sufficient.

Detecting input method change

The way Jacob does it is via single run of external gsettings command and modifying shortcuts, such that each time you click the shortcut, the program runs. There is another approach, via already existing Gio API. This type of API would be used when you develop a proper desktop application for Ubuntu or other system that uses GNOME-related desktop. The script below illustrates the approach with Gio API.

#!/usr/bin/env python
from __future__ import print_function
from gi.repository import Gio, GObject
import subprocess

def on_changed(settings, key):
  # This will run if specific key of a schema changed
  print("Language changed")
  # Do something else here, for example call external program
  subprocess.call(['xmodmap','-e', 'keycode 52=y'])
  subprocess.call(['xmodmap','-e', 'keycode 29=z'])

def main():
  # We're focusing on this specific schema
  settings = Gio.Settings("org.gnome.desktop.input-sources")
  # Once the "current" key changes, on-changed function will run
  settings.connect("changed::current", on_changed)
  loop = GObject.MainLoop()
  loop.run()

if __name__ == "__main__":
  main()

There are distinct advantages to this approach over interfering with the shortcuts:

  • You don't have to edit shortcuts. I won't overplay this but the setup for this answer is just making the script automatically start up when you login .
  • Because it uses the GIO event API, we aren't honking on gsettings twice every time we change language. We let the language change happen as it would have always have happened. This makes it faster and less resource intensive when changing languages.
  • Because it's always listening, we're never left waiting for Python to put its trousers on and load a script. It loads once, at login and then it's ready for changes.
  • Because it uses the API, anything else that changes the input language (menu icons, gsettings) will fire the event and therefore this script.

There are some concerns in the comments from the other poster but while this is a persistent script also works in its favour. The meagre resources it does consume vastly outweigh the fact it'll eat a fraction of a percent of RAM.

Solution 2:

A file being executed?

I am afraid switching sources is a built in function of Unity, which is not available as a cli option from outside. However, that does not mean we have no options to achieve exactly what you want.

Scripted solution to combine changing layout with your command

Replacing the original shortcut

Input sources can be fetched by the command:

gsettings get org.gnome.desktop.input-sources sources

Output looks like:

[('xkb', 'us+intl'), ('xkb', 'us'), ('xkb', 'nl'), ('xkb', 'be')]

The layout can be set with the command:

gsettings set org.gnome.desktop.input-sources current <index> 

This way, we can replace the keyboard shortcut by a scripted version to switch to the next language and run your command at the same time.

The script

#!/usr/bin/env python3
import subprocess
import ast
import sys

arg = sys.argv[1]

key = "org.gnome.desktop.input-sources"

def get(cmd): return subprocess.check_output(cmd).decode("utf-8")

def run(cmd): subprocess.call(["/bin/bash", "-c", cmd])

langs = len(ast.literal_eval(get(["gsettings", "get", key, "sources"])))
currlang = int(get(["gsettings", "get", key, "current"]).split()[-1].strip())

if arg == "+":
    next_lang = currlang+1 if currlang < langs-1 else 0
elif arg == "-":
    next_lang = currlang-1 if currlang > 0 else langs-1

for cmd in [
    "gsettings set "+key+" current "+str(next_lang),
    'xmodmap -e "keycode 52=y"',
    'xmodmap -e "keycode 29=z"',
    ]: run(cmd)

Set up

  1. Copy the script into an empty file, save it as change_lang.py
  2. Test- run the script by the command:

    python3 /path/to/change_lang.py +
    

    and optionally:

    python3 /path/to/change_lang.py -
    

    Your layout should change and your commands should run.

  3. If all works fine, open System Settings, go to "Keyboard" > "Shortcuts" > "Typing". Click on the shortcut to switch source (on the right, to set the shortcut), and hit backspace to disable the shortcut.

    enter image description here

  4. Now add the same shortcut to custom shortcuts: choose: System Settings > "Keyboard" > "Shortcuts" > "Custom Shortcuts". Click the "+" and add the command:

    python3 /path/to/change_lang.py +
    

    To the shortcut you just disabled in step 2.

    Additionally you can set a shortcut to move the other way around (set previous source from the list):

    python3 /path/to/change_lang.py -
    

That's it. Now changing language is combined with the command you want to run along.

What it does

  • Whenever your shortcut is pressed, The script is called, looking what is the current language, by the command:

    gsettings get org.gnome.desktop.input-sources current
    
  • The script subsequently moves to the next one, or the previous, depending on the argument, by the command:

    gsettings set org.gnome.desktop.input-sources current <index>
    

Resources

Using gsettings is extremely low on juice, and by combining the command to set the language with your commands, the script only runs when changing language.

I believe that is the most elegant way and, although you won't notice in your electricity bill, on long term the lowest on resources. The choice between a constantly running process, using the API, or a run-when-called option is a matter of taste however.

Note also that the script first changes language and then runs the other commands. There is no noticeable difference in response in the two answers whatsoever.