# Plan 9 in Linux: Mouse menus 'define expected-reading 17 min 'define created 10 January 2023 'define edited 28 March 2023 [$pagenav] .contents .numbered 1. [url #usefulness-in-linux Usefulness in Linux] 2. [url #going-about-it Going about it] .bulleted - [url #1 \#1] - [url #2 \#2] - [url #3 \#3] - [url #4 \#4] .bulleted - [url #open-files Open files] - [url #send Send] - [url #window-management Window management] 'footnote plan9-vs-rio When I say "Plan 9" in the context of graphical stuff, I usually refer to the default window system [url https://9p.io/magic/man2html/1/rio rio]. Similarly to most UNIX-like OSs, it can be changed, but there doesn't seem to be any others out there*[^[citation ^needed]]*. Let's begin with an introduction to Plan 9 (from Bell Labs): it is an old UNIX-like operating system from the 90s, where[$plan9-vs-rio] everything is pretty much either text, image or some sort of rectangular box. There are no fancy graphical menus of any kind, no title bars, no file managers and nothing utilizes icons, but the window manager is still stacked. 'footnote acme acme actually changes how the three mouse buttons work. Paraphrasing the [url https://9p.io/wiki/plan9/Using_acme/index.html official page], button 2 "executes things" and button 3 gets or searches for things. Everything is done with text, actions in a window can be done by "clicking" any text inside that corresponds to an available action. For example, in the main text editor "acme", you open a file by just pressing[$acme] on a string "New", which for convenience is always available up top: How you manage windows and differentiate between selecting some text and "executing" it is done entirely via a three-button mouse. Each button corresponds to some sort of action, and different application can configure how every button works, which is why I'll only work with the "default" options in the desktop and terminal windows. Every action is started by pressing (and potentially holding) a mouse button, and then releasing it (pressing other buttons before release cancels the action). The left mouse button (button 1), works as expected: you use it to select text. Pressing and holding down the right mouse button (button 3), shows all possible window options: [image ./img/plan9-mouse3-menu.png] For example, if you release it while hovering "New", you cursor will change, and after holding it down again, dragging out will result in a grey rectangle representing the dimensions (and position) of a new terminal window (releasing the button creates that new window). "Opening" an application is always done via a command in the terminal, where the program will [url https://9p.io/wiki/plan9/Using_rio/index.html "take over the window"]. Finally, the middle mouse button (button 2), shows text-control options, which can be executed on selected text: [image ./img/plan9-mouse2-menu.png] 'footnote snarf It's hard to deduce why exactly it was named like this, but the most probable explanation comes from [url https://unix.stackexchange.com/questions/308943/why-does-plan-9-use-snarf-instead-of-copy/309350 unix.stackexchange]: copying might've been seen as a two step action, obtain the contents you want to copy and paste them somewhere. So to copy something you need a duplicate of itself to be created, and either putting it in the "clipboard" isn't considered duplicating, or an optimization makes "the clipboard" store some sort of reference to what you snarfed, but not the exact thing itself. \n On an unrelated note, this might have played a part in the reasoning behind vim using the word "yank" for copy. 'footnote plumber-in-apps Technically, a plumber just passes messages between applications, so you can configure it to not open a URL but do something else with it in a certain application. However, the aforementioned way is how it works most of the time. You'll recognise `[cut]`, `[paste]` and `[snarf]` [$snarf], the latter being just another word for copy. Since every modern window manager supports those in some form, I'll concentrate on the others.\n `[plumb]` is the most interesting: it sends the selection to a "plumber" application, which practically[$plumber-in-apps] tries to open or execute it. For example, a selected URL will open in the browser, but a selected image file will open in an image viewer.\n `[send]` enters the selected text as a command in the current shell. ### Usefulness in Linux Implementing these ideas in Linux sound like a fun sunday project, but that doesn't mean they will be handy. Arguably, when it comes to managing windows, all Linux window managers do it more efficiently than rio. There isn't much to gain with implementing the right-click menu, but I'll still dip into it, for completeness sake. Text actions (plumber and "send") are the exact opposite. A very simple use case would be opening files in the current folder, it could be faster by selecting it and doing a keyboard combination (to send it to plumber) than typing out a command. Alternatively, what if you're looking through some sort of guide or manpage in the terminal, and you see a command listed, that you want to run? No worries, select it and send it! If you're reading text in another language and you don't understand some word, you can open a new browser window with a translation page just by selecting it and clicking the appropriate option. Another idea would be: say you're going through some sort of text document, and you want to email some part of the document to someone. Yet again, select that, press the appropriate menu item or combination, and a proper email window will open with the text inside. While you can do all of those stuff normally, if you do them often enough, the time save could stack up. And everything depends on your configuration, you can make the plumber do as much or as little as you would like, and send could run commands in different shells or terminals depending on context. Most importantly, this works in any application, any time, any where, as long as you can select the text. ### Going about it This could all be done with a C program, but writing the code would take way too much time (if you do it like that, [url /about tell me about it]), so everything should be contained in a couple of shell scripts. Another "constraint" is that I use openbox, an X11 window manager, so my solution will be oriented around tools that work with it. The result should be simple enough that adapting it to your window manager shouldn't take too much effort. Looking around, you would first find a video by Luke Smith, where he creates a small plumber in bash ([url https://www.youtube.com/watch?v=RlMxbQmMz_4 YouTube link], [url https://odysee.com/@Luke:7/plumbing-in-linux-la-plan-9-from-bell:e Odysee link]) and an [url https://tatsumoto-ren.github.io/blog/plumbing-for-language-learners.html expansion of it] by Ren Tatsumoto (surprisingly enough from around 3 months before I started). These posts served as a good base and inspiration. Following their steps, everything will be done via one shell script that does something on the text selection, effectively allowing the menus, plumber and all other possible actions to be managed by that script. The overall life cycle is split into four parts: .numbered 1. Run script 2. Get selection 3. Open menu 4. Execute action #### #1 Since we're emulating Plan 9, I wanted the script to be ran with the mouse, with potentially different parameters depending on button. Ideally, if you quickly press and release a button (button 2 or 3), the click should be sent to the application below the cursor, but if you hold it, the script should be ran. A tool that allows that (mentioned in Ren Tatsumoto's [url https://tatsumoto-ren.github.io/blog/plumbing-for-language-learners.html blog post]) is [url https://github.com/baskerville/sxhkd sxhkd]. Sparing you the details, unfortunately sxhkd cannot pass the mouse press to the application below, if you want something to also happen on key-down ([url https://github.com/baskerville/sxhkd/issues/198 Issue 198], and the workaround in didn't work). This means that the original idea isn't really feasible, but I couldn't really find any other good tool that does what I wanted. Plan B: execute the plumbing script on KeyboardKey + Button, but what key should I choose, especially when we're binding the right click? Ctrl + Button3 is used for opening links, Alt + Button3 is reserved for resizing the current window and binding with the Super (Windows) key causes issues with opening of my application menu. After a lot of research, I found out there is actually a fourth modifier key, called `[Hyper]`, which X11 still supports. It's inception come from 1978 with the [url https://en.wikipedia.org/wiki/Space-cadet_keyboard Space-cadet keyboard], but thanks to the popularity of the [url https://en.wikipedia.org/wiki/Model_M_keyboard Model M], modern keyboards don't include it. All that was left was to assign `[Hyper]` to some key that I don't use, and what better than Caps Lock! There are [url https://en.jveweb.net/archives/2010/11/making-better-use-of-the-caps-lock-key-in-linux.html many ways to do it], but I prefer using [url https://gitlab.freedesktop.org/xorg/app/setxkbmap setxkbmap]: ``` setxkbmap -option "caps:hyper" This line is needs to be ran at start of openbox, so it's placed inside `[~/.config/openbox/autostart]`. Now, finally, our sxhkd config looks something like this (I named the script `[plumb.sh]`): :code # Hyper_L + Middle mouse button mod4 + button2 $HOME/.a/plumb.sh 1 # Hyper_L + Right mouse button mod4 + button3 $HOME/.a/plumb.sh For details about these options, refer to the [url https://github.com/baskerville/sxhkd/blob/master/doc/sxhkd.1.asciidoc manpage]. What you might notice is that on middle mouse button, "1" is passed to the script. Since the window options aren't very useful, I've configured the script to execute a "default" action on Hyper\_L+Button2, but show me a list of all possible on Hyper\_L+Button3. Feel free to change this behaviour. #### #2 Getting the current selection turns out to be easy, X11's selections (clipboard) is split into a primary, secondary and a clipboard, where the primary is the current selection you've done with your mouse, the secondary isn't used, and the clipboard is what you know and love. [url https://www.uninformativ.de/ uninformativ.de] has a great blog post about [url https://www.uninformativ.de/blog/postings/2017-04-02/0/POSTING-en.html X11's "clipboard"], I recommend it, even though it dives more into C than one would like. As for getting it inside our script, [url https://github.com/astrand/xclip xclip] is a perfect choice. In the very beginning, we'll just store the selection in a variable and work from there: ```bash selection="$(xclip -o)" #### #3 With that out of the way, all we now need is an actual menu to appear. Luke Smith and Ren Tatsumoto both used [url https://tools.suckless.org/dmenu/ dmenu], which is keyboard driven. As for a mouse-oriented standalone menu, that could be easily configured, all I could find was my current application menu: [url https://jgmenu.github.io/ jgmenu]. Generally it is meant to serve as an application menu, but with `[--simple]` you can directly echo and pipe to it all menu option and what they do. Menu options are formatted as comma-separated (CSV) text, where each option is on a new line, the first item being the name, and the second, the executed command upon press: ``` Send,echo 1 Resize,echo 2 Move,echo 3 In this case I'm echo-ing a number, rather than doing an action, for readability and formatting sake. The number is parsed by a switch statement, so all actions are in a single place and can be more easily formatted than if they were inlined in the string. Via `[--at-pointer]` the menu can spawn right under the pointer. Unfortunately it always spawns in such a way that the pointer is at the top-left corner, I couldn't find a way to make it appear in a way that the first option is below the pointer. Finally, since I use it as an application menu too, I specify a config file which makes it look more like Plan 9's menu (though I've personally omitted the color scheme, since it doesn't fit my normal theme): ``` 'include ./jgmenurc More info on these options can be found in the [url https://jgmenu.github.io/jgmenu.1.html manpage]. Putting it all together, it looks something like this: ```bash 'snippet 54 54 ./plumb.sh 'snippet 58 59 ./plumb.sh 1) ... ;; 2) ... ;; ... \ *) ... ;; 'snippet 81 $ ./plumb.sh An important aspect of our menu options is that they can change depending on context. For example, if we have a URL we might want the options "Open it in browser" and "Download", which we wouldn't want in case of a .docx file. Or we might want it "ship" it with a lot of possible menu options and let the user decide which ones they need. This is very simple to do, and can be done in many ways. My preferred way is to have a variable `[menu]` to which different options are appended depending on a regular expression or "flag" (variable). To work with regular expressions more easily, I've made a function which returns a status code depending on matching. All in all, it would look something like this: ```bash 'snippet 26 26 ./plumb.sh 'snippet 28 38 ./plumb.sh 'snippet 45 46 ./plumb.sh \ 'snippet 54 54 ./plumb.sh 'snippet 58 59 ./plumb.sh ... 'snippet 81 $ ./plumb.sh #### #4 Finally, we need to execute a selected option, aka put commands in each case of `[\$action]`. ##### Open files Opening a file is very simple, `[xdg-open]` handles opening files by name, with the default program for that file extension. However, the script is ran completely independently from the current program, so when we're given only a part of the file path, we need to figure out the rest. This problem was solved somewhat by Luke Smith, he figured out a way to find (and `[cd]` into) the current directory of the active window (`[pid1]` should be `[pid]`, but you'll see later why I did that change): ```bash pid=$(xprop -id "$(xprop -root | awk '/_NET_ACTIVE_WINDOW\(WINDOW\)/{print $NF}')" | grep -m 1 PID | cut -d " " -f 3) pid1=$(pstree -lpA "$pid" | tail -n 1 | awk -F'---' '{print $NF}' | sed -re 's/[^0-9]//g') cd "$(readlink /proc/"$pid1"/cwd)" Still, one important situation where this doesn't work is tabs, but those are handled differently from application to application. I figured out tabs only for terminal emulators, since that's where text actions can be the most useful. The whole situation turned out to be quite convoluted. We only get the active window, but there is no way to get the active/currently "seen" subprocess of a window. Additionally, pretty much all terminal emulators have different shell, not terminal, processes for the different tabs. Terminal plugins/extensions and special application were considered, but they would take too much effort and/or couldn't work. 'footnote shell-cwd You could make a proper mechanism, which waits for the file to get updated ([url https://github.com/inotify-tools/inotify-tools inotify-wait]), or for there to be a lockfile, but the overall way it works is the same. I couldn't think of anything better, mostly because the plumber script is a completely separate process from the shell you're in. Maybe there is a way for them to communicate between each other, a bit like microservices? In the end, I settled on a hack: the shell would do the heavy lifting. My right-click menu combination would be passed to the terminal (and therefore to the shell), and the shell will simultaneously store it's working directory in a file inside `[/tmp]`, which could then be parse by the script. This isn't ideal, since if the shell is being slow, the script might not wait long enough for the file to get updated, but I couldn't think of anything much better[$shell-cwd] and it does the job. 'footnote escape-sequences The terminal emulator passes keyboard presses to the shell via [url https://en.wikipedia.org/wiki/ANSI_escape_code ANSI escape sequences], which don't support `[Hyper]` bindings. I settled for the next best thing, which is create a different key combination that isn't in use, and make our script "press" that combination upon running. I use zsh, so I implemented it by creating a zsh widget: certain functionality that can be ran by the shell itself and (usually) doesn't need to be executed like a normal program. Examples include key binds, completion, navigation through history, undo and redo. User-defined widgets are just zsh functions, so we create a function that stores the current directory, add it as a widget and bind it to our keyboard combination[$escape-sequences]: ```bash plumb-store-cwd () { pwd > /tmp/plumb-cwd } zle -N plumb-store-cwd bindkey "^[[5;7~" plumb-store-cwd # Ctrl+Alt+Page_Up Finally, in our script, we want to read that file, only if we're inside a terminal: ```bash wname="$(cat /proc/"$pid"/comm)" if [ $wname == "TERMINAL" ]; then # Replace with your terminal emulator name xdotool key Control+Alt+Page_Up # Execute the shell-compatible combination sleep 0.1 # Rework with inotify? cd "$(cat /tmp/plumb-cwd)" fi For the uninitiated, [url https://github.com/jordansissel/xdotool xdotool] is a general X11 tool, which allows (amongst other things) to "execute" key presses via commands. ##### Send Sending the command to the current shell is by far the simplest action. `[xdotool]` supports "typing out" text, so I'll give it the selection and let it do it's thing: ```bash xdotool type --delay 0 "$selection" It could be optimized if there was a way to make the selection be "pasted" into the shell (without being added to the clipboard), since that would mostly eliminate waiting times. Sadly I couldn't figure out how to do anything like that. ##### Window management As promised, I won't dive into it too much, but the easiest way to implement window management actions is via another dirty hack: sending key presses that make the window manager do something. For example, normally resizing is done via Alt+RightClick, so I'll just do this: ```bash xdotool keydown Alt xdotool mousedown 3 xdotool keyup Alt After this is executed, moving the mouse will cause the screen to be resized, the same way if I did the original combination. Pressing and releasing the mouse button will stop resizing (that's why `[keyup Alt]` is done, because otherwise you would need to also press Alt). Though it work, it's kind of clunky and the normal Alt+RightClick is arguably simpler and easier to work with. \n With this our script if finished. You can find the whole thing (including some personal modifications) in my [url https://gitlab.com/Syndamia/dotfiles/-/blob/main/.a/plumb.sh dotfiles]. I'll try to use it as much as I can and I might create a blog post or an appendix on how I feel about it.