6

I want to execute the following commands:

xmodmap -e "keycode 52=y"
xmodmap -e "keycode 29=z"

whenever I switch my keyboard layout (Shift+Alt). Is there any way to do that?

10
  • I do not use multiple layouts, so I can't test it, but this manpage suggests it is im-config -n. – O8h7w Feb 8 '17 at 14:26
  • Doesn't seem to be it. Also, when I try to create an alias in the ~./bash file like that: alias keys="xmodmap -e "keycode 52=y"&&xmodmap -e "keycode 29=z"" it gives me this error: xmodmap: commandline:1: bad keycode input line xmodmap: 1 error encountered, aborting. But when I type the commands in regularly it works fine. – huehuehuehuehuehuehuehuehuehue Feb 8 '17 at 16:37
  • That error is just because zou need to escape the quote characters within the string, like so alias keys="xmodmap -e \"keycode 52=y\"&&xmodmap -e \"keycode 29=z\"". And now mz kezs are crayz :D – O8h7w Feb 8 '17 at 17:35
  • What Desktop Environment are you using? Where can you edit the Shift+Alt shortcut? – O8h7w Feb 8 '17 at 17:41
  • You can also check for clues in the output of tail /var/log/Xorg.0.log directly after doing a switch. – O8h7w Feb 8 '17 at 17:54
10

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.

8
  • "consumes less resources" The opposite is true. This runs a loop, while a scripted solution like the other answer only runs when called. Furthermore, handling gsettings is extremely low on juice. – Jacob Vlijm Feb 9 '17 at 8:07
  • @JacobVlijm see the screenshot: imgur.com/a/U8wSx The process uses almost no CPU ( 0.0 percent ) and tiny amount of memory. Gio is an API specifically build for handling desktop events, which gsettings command also uses, just in C language. Multiple professional projects for Linux desktop utilize it, from Red Hat to Ubuntu, and it is optimized for performance. – Sergiy Kolodyazhnyy Feb 9 '17 at 8:26
  • 1
    NOTE: Downvote undeserved. Use of specifically designed API , which is optimized for performance, is still better than calling an external process. My answer provides a working solution to what OP asked and doesn't require OP altering their system in any way. – Sergiy Kolodyazhnyy Feb 9 '17 at 9:08
  • 3
    FWIW, I prefer this approach. It won't fit every situation but I can certainly see why hooking onto the status change event (rather than supplanting a shortcut). I'm not sure I'd make performance arguments here because we're literally talking about hairs, and ongoing processes do take up RAM, but I believe this would be more responsive if events come through to GIO in near-realtime as most event loops do. It's pretty theoretically either way. A downvote on a comment about performance here is pretty harsh. – Oli Feb 9 '17 at 14:01
  • 2
    The bottom line is this: "consumes less resources" is subjective. We are comparing apples and oranges here. Jacob's solution uses more CPU and disk I/O at the expense of memory. Serg's uses more memory at the expense of CPU and disk I/O. – Nathan Osman Feb 9 '17 at 20:14
8

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.

11
  • Holy shit you are literally insane. Thank you so much for that unbelievably thorough, I had no idea the Ubuntu community was this amazing! However, I do get an error when I try to execute that script: hue@hue-Lenovo-G580:~$ python3 ?/change_lang.py Traceback (most recent call last): File "?/change_lang.py", line 6, in <module> arg = sys.argv[1] IndexError: list index out of range I'm still relatively new to linux so I'm not exactly sure what to do now. Can you help me? – huehuehuehuehuehuehuehuehuehue Feb 9 '17 at 18:43
  • @huehuehuehuehuehuehuehuehuehue you are forgetting to set the argument (either + or -, for moving to next- or previous layout). The command should bet either python3 /path/to/change_lang.py + or python3 /path/to/change_lang.py - (mind the + and -). – Jacob Vlijm Feb 9 '17 at 20:10
  • Okay, that was admittedly a pretty stupid mistake. However, there still seem to be a few problems, the script always seems to be ignoring the first argument in the last for loop, in this case ['xmodmap', '-e', 'keycode 52=y'], seems to be getting ignored and I both the y and z key only type z for me. I tried around a bit but don't really see why it won't work.. – huehuehuehuehuehuehuehuehuehue Feb 9 '17 at 22:40
  • Also, I learned a ton about how to work with modules like sys that have functions such as argv to interact with shell inputs, really fascinating, However I don't really understand what the ast and subprocess module do in your code, do you maybe know some ressources where I can acquire this kind of important knowledge about python efficiently? – huehuehuehuehuehuehuehuehuehue Feb 9 '17 at 22:46
  • Wait, I might have introduced an error in my last edit, will change... – Jacob Vlijm Feb 9 '17 at 22:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.