Emulating Key Chain Keyboard Shortcuts in XFCE

I was messing with OpenBox (the windows manager) and came across this article. Key chain shortcuts allow you to press a key combination which then opens up another set of possible keys to press in order to complete the shortcut. It essentially layers the number of actions you can take. This can become very useful in Qubes OS as we have many qubes to launch applications from. For example, if you want to have your file manager mapped to the ‘f’ key, but want that same shortcut for all your qubes, you can assign Ctrl+Alt+p then ‘f’ to open the personal domain’s file manager and Ctrl+Alt+w then ‘f’ for the work domain.
I modified a short python script for dom0 which I took from here:

#!/usr/bin/python
import subprocess, tty, sys, os, termios

filedescriptors = termios.tcgetattr(sys.stdin)
tty.setcbreak(sys.stdin)
vm = 'work'
x = 0
while 1:
    x=sys.stdin.read(1)[0]
    devnull = open(os.devnull, 'wb')

    if x == 'q':
        quit()
    #CommonSettings
    elif x == 'i':
        subprocess.Popen(['nohup', 'qvm-run', vm, 'qvm-run-vm'], stdout=devnull, stderr=devnull)
        quit()
    elif x == 'h':
        subprocess.Popen(['nohup', 'qvm-run', vm, 'sudo shutdown now'], stdout=devnull, stderr=devnull)
    elif x == 'f':
        subprocess.Popen(['nohup', 'qvm-run', vm, 'thunar'], stdout=devnull, stderr=devnull)
    elif x == 't':
        subprocess.Popen(['nohup', 'qvm-run', vm, 'qubes-run-terminal'], stdout=devnull, stderr=devnull)
    elif x == 'a':
        subprocess.Popen(['nohup', 'qvm-run', vm, 'xfce4-appfinder'], stdout=devnull, stderr=devnull)
    elif x == '~':
        subprocess.Popen(['nohup', 'qvm-run', vm, 'xfce4-terminal --drop-down'], stdout=devnull, stderr=devnull)
    elif x == 's':
        subprocess.Popen(['nohup', 'qvm-run', vm, 'xfce4-settings-manager'], stdout=devnull, stderr=devnull)
    elif x == 'F':
        subprocess.Popen(['nohup', 'qvm-run', vm, 'flatpak run com.github.tchx84.Flatseal'], stdout=devnull, stderr=devnull)

termios.tcsetattr(sys.stdin, termios.TCSADRAIN, filedescriptors)

Change the vm variable to the qube in which the applications will launch (make copies of the script for other qubes). Then you will need to hookup your python script(s) to the dom0’s keyboard shortcut settings. It needs to open in a terminal for some reason, so set your launch command to ‘xfce4-terminal -x [path to your script].py’. You might also want to put your script in a place that your $PATH variable includes, like .local/bin (mkdir -p ~/.local/bin if it doesn’t exist) so that you won’t have to type in the whole path.
Theoretically this layering can go on indefinitely. If you want to specify an additional setting/option/action for an application to take you can use

elif x == 'b':
    subprocess.Popen(['nohup', 'xfce4-terminal', '-x', 'your-python-script'])

You’ll need gnome-terminal if your qubes use the regular template. The new python script will store the additional options following the same logic as the first script (I wonder if there’s a way to do this in one file):

elif x == 'a':
    subprocess.Popen(['nohup', 'qvm-run', vm, 'firefox -P Arkenfox --no-remote'], stdout=devnull, stderr=devnull)
elif x == 's':
    subprocess.Popen(['nohup', 'qvm-run', vm, 'firefox -P School --no-remote'], stdout=devnull, stderr=devnull)

This gives the following shortcuts: pressing Ctrl+Alt+w selects the work qube, then b specifies the browser application, and finally s opens a firefox profile dedicated for school; and pressing Ctrl+Alt+w → b → a opens up firefox in Arkenfox mode. A new terminal will open for each step down the ladder of layers.

Limitations

Currently I don’t know how to get python to read non-alphanumeric keys. I would also like to implement a menu for each terminal listing the shortcut options available. Maybe redesign the script to store commands in a list?
Here are some sources if you want to find out more about getting input from the keyboard with python (curses and termios modules are in dom0 by default):
https://stackoverflow.com/questions/24072790/how-to-detect-key-presses
https://code.activestate.com/recipes/577977-get-single-keypress/
https://stackoverflow.com/questions/10693256/how-to-accept-keypress-in-command-line-python

Edit: I forgot to add quit() to each elif code block section. This seems to mean that the last line is useless?

1 Like

I’ve improved upon my little script since upgrading to Qubes 4.1. I’m still sticking with XFCE, but I imagine gnome-terminal will be able to perform equivalently with the correct options.
The setup process is the same as the original post, but I add --geometry 100x50+25+25 to get a better window size.
I switched to using a dictionary for launch commands rather than if statements, which is super helpful because now you just need to type in the shortcut key and then the command you want to run. If you want to specify each element of the subprocess function use a list.
I added a print statement to show what options are available in the terminal that pops up.

#!/usr/bin/python3 
import subprocess, tty, sys, os, termios 
 
filedescriptors = termios.tcgetattr(sys.stdin) 
tty.setcbreak(sys.stdin) 
vm = 'work' 
shortcuts = { 
        'i': 'qvm-run-vm', 
        'h': 'sudo shutdown now', 
        'f': 'thunar', 
        't': 'qubes-run-terminal', 
        'c': 'galculator', 
        'a': 'xfce4-appfinder', 
        '~': 'xfce4-terminal --drop-down', 
        'e': 'mousepad', 
        'd': 'xfce4-dict', 
        's': 'xfce4-settings-manager', 
        'g': 'gnome-disks', 
        'v': 'parole', 
        'p': 'pragha', 
        'k': 'seahorse', 
        'T': 'xfce4-taskmanager', 
        'S': ['nohup', 'qubes-vm-settings', vm] 
} 
for key, command in shortcuts.items(): 
    print(key, ':', command) 
x = 0 
while 1: 
    x=sys.stdin.read(1)[0] 
    devnull = open(os.devnull, 'wb') 
 
    if x == 'q': 
        quit() 
    for key, command in shortcuts.items(): 
        if x == key: 
            if type(command) is list: 
                subprocess.Popen(command, stdout=devnull, stderr=devnull) 
                quit() 
            else: 
                subprocess.Popen(['nohup', 'qvm-run', vm, command], stdout=devnull, stderr=devnull) 
                quit() 

termios.tcsetattr(sys.stdin, termios.TCSADRAIN, filedescriptors)

Personally, this script has transformed my usage of Qubes OS. I hardly use the Qubes menu any longer since these shortcuts are so much faster and due to the redundancy of the same apps on different qubes I only have to remember a few keybindings and follow a chain of keys to launch my applications.