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?

Kevin Bowen
  • 19,615
  • 55
  • 79
  • 83
  • Hi hue*10, posted my solution. Please mention if all is clear. – Jacob Vlijm Feb 08 '17 at 18:52
  • I do not use multiple layouts, so I can't test it, but this manpage suggests it is im-config -n. – O8h7w Feb 08 '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 08 '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 08 '17 at 17:35
  • What Desktop Environment are you using? Where can you edit the Shift+Alt shortcut? – O8h7w Feb 08 '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 08 '17 at 17:54
  • @O8h7w: Not related to im-config, which is about input method frameworks for complex input methods such as IBus and Fcitx. – Gunnar Hjalmarsson Feb 08 '17 at 19:29
  • @huehuehuehuehuehuehuehuehuehue May I ask this: do you want to run both commands when any change occurs, or you want to run specific command when you're using specific input method ( like command1 for input2, command2 for input 2) ? – Sergiy Kolodyazhnyy Feb 09 '17 at 01:07
  • @huehuehuehuehuehuehuehuehuehue I've posted an answer. It includes explanation of how language switching works and small script example which monitors when changes to input method occur. I still would like to know the answer to the question I posted earlier, so I could improve my answer. Let me know if there's any additional information you want – Sergiy Kolodyazhnyy Feb 09 '17 at 07:54
  • @huehuehuehuehuehuehuehuehuehue Hi, have you seen the previous two comments I posted ? Have you tried the answer ? Please let me know – Sergiy Kolodyazhnyy Feb 09 '17 at 23:26

2 Answers2

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.

Sergiy Kolodyazhnyy
  • 105,154
  • 20
  • 279
  • 497
  • "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 09 '17 at 08:07
  • @JacobVlijm see the screenshot: http://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 09 '17 at 08: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 09 '17 at 09:08
  • The downvote is for: "which means it's one less external program to run and thus is faster and consumes less resources.", which is plain wrong information. – Jacob Vlijm Feb 09 '17 at 09:59
  • 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 09 '17 at 14:01
  • @Oli I am sorry, but a claim like this, used as an important (if not main) argument, comparing your answer with the other answer, is simply wrong information. See chat. – Jacob Vlijm Feb 09 '17 at 14:06
  • The memory usage will be negligible and more importantly, the pages of memory used by this program will be an excellent candidate to swap out to disk since it isn't doing anything most of the time. – Nathan Osman Feb 09 '17 at 20:09
  • 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 09 '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.

Jacob Vlijm
  • 83,767
  • 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

    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 09 '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 09 '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 09 '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 09 '17 at 22:46
  • Wait, I might have introduced an error in my last edit, will change... – Jacob Vlijm Feb 09 '17 at 22:47
  • @huehuehuehuehuehuehuehuehuehue edited :) I will add a few links tomorrow, but ast.literal_eval() is to interpret the (string) output of the gsettings command as a list, which is safer than using eval(). See https://docs.python.org/3/library/ast.html and https://docs.python.org/3/library/subprocess.html – Jacob Vlijm Feb 09 '17 at 22:52
  • Hey, thanks for that quick answer! Still doesn't seem to be working for me though, the y and z keys are unchanged this time – huehuehuehuehuehuehuehuehuehue Feb 09 '17 at 23:15
  • Hi @huehuehuehuehuehuehuehuehuehue just curious, but did you try? – Jacob Vlijm Feb 11 '17 at 13:15
  • Yeah but it doesn't seem to work for me for some reason, so I have just decided to get used to having y and z switched, it might even make more sense ergonomically tbh.

    The only annoying part is that I get confused when I switch back to the german keyboard to use letters like äüö, which makes it a bit harder to get used to the new layout. Also, for some very weird reason the alias keys="xmodmap -e "keycode 29=z"&&xmodmap -e "keycode 52=y"" isn't working either, but if I switch z and y it works...very strange.

    – huehuehuehuehuehuehuehuehuehue Feb 12 '17 at 22:51
  • @huehuehuehuehuehuehuehuehuehue really strange. It works fine here, but so did the before-last version. How is Serg's answer doing? – Jacob Vlijm Feb 12 '17 at 22:55
  • Yeah, that's not working either for some reason :D – huehuehuehuehuehuehuehuehuehue Feb 13 '17 at 11:46