Scroll to code, Scroll to explanation

In the past couple of days I was trying to create my Mac workflow in my Intel PC where I installed Arch recently. There is a strange behavior whenever I launch apps using the keybindings I assigned using GNOME’s keyboard settings. No matter if that application is already running or not, launching with the keybinding triggers another instance. Problem seems to be related to GNOME and if I open the apps using the Super key and then selecting from the menu, it seems the system just focuses on the open instance. This was also what I wanted to do.

So, my target behavior while using keybindings to open apps is the following:

  1. Open the application if it’s not running already
  2. Focus the application, if it’s running

I thought there must be some kind of a setting, since such behavior seems ridiculous to me. After couple of minutes of research, I realised the behavior was really intended to be that way. In fact, a lot of people even wrote their own small programs to overcome this issue.

Fair enough, I thought I could also come up with some kind of a basic bash script and run the apps through it.

wmctrl could be my starting point. It is a small program to interact with window manager to gather information and execute commands like switching to a desktop, raising a window, giving it focus, closing it and so on.

What are we goint to do is basically listing the open windows and control them using their WM_CLASS property in X Window System. Therefore we need to list all open windows first with:

shell

wmctrl -lx

an example output would be:

shell

0x03a00004  2 obsidian.obsidian     capsule-arch 8.2 Strings - haupt - Obsidian v1.9.2
0x00e0000b  0 kitty.kitty           capsule-arch nvim
0x00e0001c  0 kitty.kitty           capsule-arch wmctrl -lx
0x01c0003e  0 Navigator.firefox     capsule-arch GNOME: Focus app if it exists  Mozilla Firefox
0x02600007  0 okular.okular capsule-arch h64f7vgjse3f1.png  Okular

As you might expect, the first column is the WM_CLASS property, which we are going to use for controlling the behavior of our script.

Complete Script

After couple of hours and brushing up my rusty bash skills, I came up with a basic script where I can use inside the keybinding assignment setting in GNOME:

fl.sh

#!/bin/bash

if [ $# -lt 2 ]; then
  exit 1
fi

WM_CLASS="$1"
shift
APP_CMD=("$@")

# Find the window ID matching the WM_CLASS

WIN_ID=$(wmctrl -lx | grep -i "$WM_CLASS" | awk '{print $1}' | head -n1)

if [ -n "$WIN_ID" ]; then
  # Activate the running window
  wmctrl -ia "$WIN_ID"
else
  # Launch the app with possible additional arguments
  "${APP_CMD[@]}" &
fi

After that we need to first make the script executable and use it in our GNOME setting when assigning custom keybindings.

shell

chmod +x fl.sh

and create a GNOME custom shortcut, for Firefox in this case:

Explanation

We have couple of things to understand here. First we check if our script got 2 arguments passed to it if not, we exit with status code 1:

shell

if [ $# -lt 2 ]; then
  exit 1
fi

then we store the arguments to variables and make a small trick in between to kind of reset the arguments list to the beginnig. We first store the first argument into WM_CLASS and then remove it with shift This from StackOverflow helped here. and then collect the remaining arguments with APP_CMD("$@"). So this will let applications to be able to have their own arguments when starting.

shell

WM_CLASS="$1"
shift
APP_CMD=("$@")

Now we can use wmctrl, process the result and store it in WIN_ID:

shell

WIN_ID=$(wmctrl -lx | grep -i "$WM_CLASS" | awk '{print $1}' | head -n1)

Couple of things are happening here too. First we list all the windows with wmctrl -lx. Then we grep the result for WM_CLASS which is our first argument, extract the first column with awk for all windows, and finally get the first one with head -n1.

Rest is straigtforward. We check if a window with that id exists and based on that we either “activate” it again with wmctrl or launch it.

shell

if [ -n "$WIN_ID" ]; then
  # Activate the running window
  wmctrl -ia "$WIN_ID"
else
  # Launch the app with possible additional arguments
  "${APP_CMD[@]}" &
fi

That’s it. Now I can either focus or launch apps when I need to something in GNOME. There’s a catch though, since we are always launching the first window of an application that comes up in the list, we can not somehow focus to other windows. An option to implement would be some kind of switcher in rofi or dmenu to show open windows of that application. I might implement that in some free time just for fun.

← Go Back Reply via E-Mail Copy URL