2

I am currently starting to set up stumpwm, and I would like to assign a specific window to a particular group. So far I have this:

(define-frame-preference "Spotify"
    (0 t t :class "Spotify")
    )

So essentially, I would expect that that would set the windows with the class Spotify to the group Spotify, this however does not happen.

Can anybody help me on this? Thank you!

  • Can't test right now, but you may want to check what properties the window actually has, using "xprop" (execute xprop, click on window, properties are printed in shell); or, from the repl, call `(stumpwm:group-windows (stumpwm:current-group))` to list all windows, then inspect (call Lisp's `inspect` or `describe`) the window objects you want from that list. If all else fails, increase `stumpwm::*debug-level*` to have more debugging informations. – coredump May 10 '20 at 20:44
  • So here is the thing. I did this successfully for Emacs and Thunderbird, but for some reason I can't match Spotify. I open Spotify, then check the properties using C-z I, and the class is Spotify, but for some reason it does not match. Also I have created the groups beforehand. Any idea? –  May 11 '20 at 09:23
  • It appears looking for "spotify wm_class" brings many issues with other WM toos. According to [the ArchWiki](https://wiki.archlinux.org/index.php/Spotify#Not_respecting_window_manager_rules), Spotify doesn't set the WM_CLASS property before mapping the window, which is against the ICCCM specifications. You might have better chances by establishing a hook (`add-hook`) on `*new-window-hook*`, and/or matching other properties like the name? Or extend stumpwm so that when properties like `:wm_class` property is set, you execute some user code (update-window-properties in events.lisp). – coredump May 11 '20 at 09:41
  • So when you say a hook you mean, a hook that when I start spotify it will move the newly created window to the specific environment right? I might as well just create a command that, launches spotify and then moves it to the desired group. What would be the syntax for this? Thanks for the help, I would most likely be stuck here for a while thinking it is stumpwm problem –  May 11 '20 at 09:49

2 Answers2

1

So it seems like, as pointed out by coredump, the are issues in the way the Spotify window is defined. As an alternative, there are fortunately plenty of ways to control spotify via Third Party Clients (ArchWiki)

Personally, I found that you can control spotify via Ivy on Emacs thanks to this project Ivy Spotify and this will probably be what I will use.

1

The relationship between X11 windows and Linux processes is thin: things are asynchronous, you start a process and some time later zero, one or more windows are created. You have to work with callbacks, there is no easy way to create a process and synchronously have all its windows in return.

Some processes are nice enough to set the _NET_WM_PID property on windows (it looks like the "Spotify" application does it). You can retrieve this property as follows:

(first (xlib:get-property (window-xwin w) :_net_wm_pid))

Placement rules cannot help here, given how Spotify fails to set the class property early enough (see comments and other answer). But you can use a custom hook:

STUMPWM-USER> (let ((out *standard-output*))
                (push (lambda (&rest args) (print args out))
                      *new-window-hook*))
(#<CLOSURE (LAMBDA (&REST ARGS)) {101A92388B}>)

Notice how I first evaluate *standard-output* to bind it lexically to out, so that the function can use it as a stream when printing informations. This is because the hook might be run in another thread, where the dynamic binding of the standard output might not be the one I want here (this ensures debugging in done in the Slime REPL, in my case).

When I start for example xclock, the following is printed in the REPL:

(#S(TILE-WINDOW "xclock" #x380000A)) 

So I can change the hook so that instead if does other things. This is a bit experimental but for example, you can temporarily modify the *new-window-hook* to react on a particular window event:

(in-package :stumpwm-user)

(let ((process (sb-ext:run-program "xclock" () :search t :wait nil))
      (hook))
  (sb-ext:process-kill process sb-unix:sigstop)
  (flet ((hook (w)
           (when (find
                  (sb-ext:process-pid process)
                  (xlib:get-property (window-xwin w) :_net_wm_pid))
             (move-window-to-group w (add-group (current-screen) "XCLOCK"))
             (setf *new-window-hook* (remove hook *new-window-hook*)))))
    (setf hook #'hook)
    (push #'hook *new-window-hook*))
  (sb-ext:process-kill process sb-unix:sigcont))

Basically: create a process, stop it to minimize race conditions, define a hook that checks if the PID associated in the client matches the one of the process, execute some rules, then remove the hook from the list of hooks. This is fragile, since if the hook is never run, it stays in the list, and in case of errors, it also stays in the list. At the end of the expression, the hook is added and the process resumes execution.

coredump
  • 37,664
  • 5
  • 43
  • 77
  • This looks great, but definitely more complex than I though. I think I will try those third party Spotify clients first (The one with Ivy on Emacs specially). Still, thanks a lot for the detailed answer! I will accept it because it directly answers my question. Thank you! –  May 11 '20 at 14:37