rofi: Change Window Location

This is the fourth in a series of several posts on how to do way more than you really need to with rofi. It's a neat little tool that does so many cool things. I don't have a set number of posts, and I don't have a set goal. I just want to share something I find useful.

This post looks at changing rofi's window location. It also introduces some rofidmenu usage to handle input and ends with a introduction to scriptmodi.

Assumptions

I'm running Fedora 27. Most of the instructions are based on that OS. This will translate fairly well to other RHEL derivatives. The Debian ecosystem should also work fairly well, albeit with totally different package names. This probably won't work at all on Windows, and I have no intention of fixing that.

You're going to need a newer version of rofi, >=1.4. I'm currently running this:

Code

Window Location

By default, rofi launches dead-center of the owning screen.

$ rofi -show runshould be in the center

There's a config option, location, that allows us to change that position. We can instead place the launcher on any of the cardinals, any of the ordinals, or dead center. The locations follow a pattern like this:

Scripted

However, manually running sed every time isn't that fun. We should write something.

Basic CLI Location Changer

The first thing we'll need to do is detect the current location for comparison. Once again, awk is very useful. We'll need to remove comment characters, if the option isn't set yet, and we'll want to strip semicolons to make grabbing easier.

Next we'll need to enumerate the directions. I spent a massive amount of time thinking about this last night, and I haven't been able to come up with anything more clever than some half-hearted expansion and associative arrays. It's a very interesting problem, and I'll probably come back to it.

directions

1
2
3
4
5
6
7

DIRECTIONS=(c n{w,,e} e s{e,,w} w)declare -A DIRECTION_INDICES

for index in "${!DIRECTIONS[@]}";dokey="${DIRECTIONS[$index]}" DIRECTION_INDICES[$key]=$indexdone

This will allow us to find the direction with an index via DIRECTIONS or the index with a direction via DIRECTION_INDICES.

Somehow we've got to pass a location to the script. argv never hurt anyone, so we'll go that route. However, if there's one thing you should never do, it's trust your users. We'll need to sanitize and munge the input. Once again, awk is a great tool.

directions

1
2
3
4

DESIRED_LOCATION_KEY=$(echo"$1"\| awk '...')

The first thing we should do is ensure the string contains only the things we're interested in.

parse-location-input

1
2
3
4

{input=tolower($1);input=gensub(/[^a-z]/,"","g",input);...

With a clean input, we should look for easy strings. [ns]o[ru]th leads six of the compass points, so stripping those is a good idea. awk's regex is fairly limited, but we can run basic capture groups via match. If input begins with [ns], we'll snag it and clean input before moving on. If it doesn't, we'll set result to the empty string to make combos easier.

It would also be useful if the user knew which location was currently active. We can modify the DIRECTION_INDICESfor loop to do just that. On a related note, it would also be much nicer for the active option to be the first in the list in case the user changes their mind quickly. We can accomplish that with a simple swap.

While we're building a GUI (sorta), we don't want to remove the CLI. The goal is to build something that works together in tandem. If the script is called with an argument, we'll try to parse it. Otherwise, we'll launch rofi.

directions-gui

1
2
3
4
5

if[[ -n "$1"]];then# same logic from aboveelse# new rofi logicfi

The first thing we have to do is print the array (I use printf; I can never get echo to do what I want). rofi will then consume that (via /dev/stdout) to construct its GUI list. I've added a few style things that you can ignore. You really only need to pipe something into rofi -dmenu; everything else is just window-dressing.

Location Changer modi

Taking what we've learned, we should be able to build a scriptmodi capable of updating the window location. Essentially, a scriptmodi is a never-ending pipe. rofi launches the script, the user interacts, and the script finishes. Its output is then piped back into the original script to run again. It will run until an external close action (e.g. Esc) is fired or the script sends nothing out on /dev/stdout.

Create a scriptmodi

Like before, we'll want to start with a list of options. I wanted to include an exit option this time around. We'll also need to parse the current location for comparison.

This works quite well. As the user interacts, the config gets updated. It does what it says on the tin. Like this:

On the surface, that looks awesome. However, if you look closely, the location is dead center but rofi is reporting East is active. This presents a very interesting problem with scriptmodi. Because they're pipes, rofi isn't reloading each time. The modi can't call rofi again, because it's already running. More importantly, even if it could, it's going to lose the original command, which could contain extra configuration.

Process-Spawning modi

I spent a decent chunk of time beating my head against this, and then I realized that rofi stores its pid. We can access the pid file via the config, which in turn gives us access to all the information we need. Before I get to the exciting stuff, though, it's important to mention safety. It's a really good idea to limit your process count (somehow) in case you create a runaway script. Speaking from experience, it could be half an hour before you can free up enough memory to switch to another tty and kill everything.

Why exactly do we need the pid? It's so we can duplicate the currently running script with all its arguments.

$ ps --no-headers -o command -p $(cat "$ROFI_PID")rofi -show drun

The command by itself isn't going to do us very much good. Attempting to run rofi from inside a scriptmodi hits the process lock. (I supposed we could unlock it, but that's a whole new can of bugs to crush.) Happily enough, we can dump the command out to another script and execute in the background to refresh rofi.