A Weird Imagination

Move window to current desktop

The problem#

Previously, I wrote a script for opening and immediately focusing xfce4-appfinder. But xfce4-appfinder will notice if it's already open and just assume you want to use the existing window. Even if it's on a different desktop. And therefore attempting to focus it will either do nothing or switch to that desktop, neither of which is desirable.

The solution#

The actual solution I settled on is more of a workaround than a solution: having xfce4-appfinder on a different desktop doesn't really make sense, so I just set it to be on all desktops (specified as the non-existent desktop -1), so it would never be on the wrong desktop:

xdotool set_desktop_for_window "$winid" -1

To actually move the window to the current desktop, replace xdotool windowactivate "$winid" with

desktop="$(xdotool get_desktop)"
win_desktop="$(xdotool get_desktop_for_window "$winid")"
if [[ "$desktop" == "$win_desktop" ]]
then
  xdotool windowactivate "$winid"
else
  xdotool set_desktop_for_window "$winid" -1
  xdotool set_desktop_for_window "$winid" "$desktop" \
          windowactivate "$winid"
fi

Although this assumes that you know the $winid of the window you want to move. If you have just the title it works just as well to use

wmctrl -R "Application Finder"

The details#

Doing the right thing slowly#

This StackOverflow answer suggests using wmctrl, specifically, the -R option which the man page documents as

Move the window to the current desktop, raise the window, and give it focus.

Sounds like exactly what we want. Unfortunately, it's slow1:

$ time wmctrl -R "Application Finder"

real    0m0.115s
user    0m0.009s
sys     0m0.004s

It does support an option -i to take the window ID instead of the title, but that doesn't make a significant speed difference:

time wmctrl -iR "$winid"

real    0m0.106s
user    0m0.004s
sys     0m0.000s

Note that it's only slow relative to operations on window IDs. Getting the window ID from the window title takes about a tenth of second, so if all you have is the window title, then wmctrl is as fast as we could hope for.

Doing the wrong thing quickly#

xdotool has a set_desktop_for_window subcommand that almost does what we want and quickly:

$ desktop="$(time xdotool get_desktop)"

real    0m0.003s
user    0m0.002s
sys     0m0.000s

$ time xdotool set_desktop_for_window "$winid" "$desktop"\
               windowactivate "$winid"

real    0m0.003s
user    0m0.003s
sys     0m0.000s

The window does indeed get moved to the current desktop. The catch is that it often, although not always, then changes the current desktop to the desktop the window was on. This still happens if the commands are separated:

$ xdotool set_desktop_for_window "$winid" "$desktop"
$ xdotool windowactivate "$winid"

Needless to say, this behavior is surprising and undesirable.

Note that just

$ xdotool windowactivate "$winid"

will switch desktops to whatever desktop the window is on, so what must be happening is that the commands get executed in the wrong order somehow.

Placing window on all desktops#

Experimenting, I noticed there was an exception to that rule: if $desktop is -1, which is used to put a window on all desktops, then the desktop change never happens. One thing we can do with this information is what I suggested above of just leaving the window on all desktops. But the other option is to place it on all desktops for a split second while moving it to a different desktop:

xdotool set_desktop_for_window "$winid" -1
xdotool set_desktop_for_window "$winid" "$desktop" \
        windowactivate "$winid"

Since it is on all desktops when the second command executes, focusing the window never involves changing desktops. But it still ends up on the desired (current) desktop after it finishes.

Putting it all together#

As alluded to above, this is only useful if we know the window ID. The script in my previous post assumed the window of interest was the most recently opened window. That makes sense if it really was just opened by the script, but if it's an existing window on another desktop, that may not be the case. So this version of the script has a fallback on the final line of using wmctrl and killing the xprop job if finds an existing window. Since wmctrl is called immediately after xfce4-appfinder, it will almost always execute before xfce4-appfinder has a chance to create a window, so it will only run in the case that the window was already open.

#!/usr/bin/bash
(xprop -spy -root _NET_CLIENT_LIST | stdbuf -oL head -2 |
  while read -r l
  do
    winid="${l/#*, /}"
    if [[ $(xdotool getwindowname "$winid") == \
            "Application Finder" ]]
    then
      desktop="$(xdotool get_desktop)"
      w_desktop="$(xdotool get_desktop_for_window "$winid")"
      if [[ "$desktop" == "$w_desktop" ]]
      then
        xdotool windowactivate "$winid"
      else
        xdotool set_desktop_for_window "$winid" -1
        xdotool set_desktop_for_window "$winid" "$desktop" \
                windowactivate "$winid"
      fi
      ppid=$BASHPID
      ppid=$(ps -o ppid:1= "$ppid")
      pkill -9 -P "$ppid" 
    fi
  done
  ) &
xfce4-appfinder &
wmctrl -R "Application Finder" && pkill -P "$(jobs -p %1)"

  1. Recall that the goal is to run the script between two keypresses, so a tenth of a second is in fact too slow. 

Comments

Have something to add? Post a comment by sending an email to comments@aweirdimagination.net. You may use Markdown for formatting.

There are no comments yet.