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?