Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

No longer working with Spotify 1.1.55 #8

Closed
Nephiel opened this issue Mar 29, 2021 · 28 comments · Fixed by #9
Closed

No longer working with Spotify 1.1.55 #8

Nephiel opened this issue Mar 29, 2021 · 28 comments · Fixed by #9

Comments

@Nephiel
Copy link

Nephiel commented Mar 29, 2021

After updating Spotify to latest 1.1.55.498.gf9a83c60, spotifywm is no longer working.

Output looks OK:

[spotifywm] attached to spotify
[spotifywm] attached to spotify
[spotifywm] attached to spotify

[spotifywm] attached to spotify
[spotifywm] spotify window found

But the window manager no longer applies any settings to the Spotify window on startup.

This same setup was working on previous versions of spotify-client, with nothing else changed.

@gabrieleavi
Copy link

Experiencing the same problem. I installed today bspwm and spotify doesn't follow the rules that I put in the bspwmrc.
Same output for me.
[spotifywm] attached to spotify
[spotifywm] attached to spotify

[spotifywm] attached to spotify
[spotifywm] spotify window found

In the bspwmrc I put:
bspc rule -a Spotify desktop='^3'

@onixtsm
Copy link

onixtsm commented Mar 30, 2021

Same stuff on DWM

@afreakk
Copy link

afreakk commented Apr 5, 2021

same on xmonad

@dasJ
Copy link
Owner

dasJ commented Apr 6, 2021

Same on sway

@kylekrol
Copy link

kylekrol commented Apr 7, 2021

I'm not too terribly familiar with how X works but I compiled a shared library very similiar to spotifywm that intercepts all of the windows creation and mapping functions I could find:

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif /* _GNU_SOURCE */

#include <X11/Xlib.h>
#include <X11/Xutil.h>

#include <assert.h>
#include <dlfcn.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

// Provided by glibc
extern char *program_invocation_short_name;

static void *dllXCreateWindow = NULL;
static void *dllXCreateSimpleWindow = NULL;
static void *dllXMapRaised = NULL;
static void *dllXMapSubwindows = NULL;
static void *dllXMapWindow = NULL;

static void SetClassHint(Display *display, Window w, char *name, char *cls)
{
    XClassHint *hint = XAllocClassHint();
    if (hint)
    {
        hint->res_name  = name;
        hint->res_class = cls;
        if (!XSetClassHint(display, w, hint))
        {
            fprintf(stderr, "[spotifywm] Failed to set class hint for window %ld\n", w);
        }

        XFree(hint);
    }
    else
    {
        fprintf(stderr, "[spotifywm] Failed to allocate class hints\n");
    }
}

Window XCreateWindow(Display *display, Window parent, int x, int y, unsigned int width, unsigned int height, unsigned int border_width, int depth, unsigned int class, Visual *visual, unsigned long valuemask, XSetWindowAttributes *attributes)
{
    Window w = ((int (*)(Display *, Window, int, int, unsigned int, unsigned int, unsigned int, int, unsigned int, Visual *, unsigned long, XSetWindowAttributes *)) dllXCreateWindow)(display, parent, x, y, width, height, border_width, depth, class, visual, valuemask, attributes);
    fprintf(stdout, "[spotifywm] 0x%lx = XCreateWindow(parent=0x%lx)\n", w, parent);
    return w;
}

Window XCreateSimpleWindow(Display *display, Window parent, int x, int y, unsigned int width, unsigned int height, unsigned int border_width, unsigned long border, unsigned long background)
{
    Window w = ((Window (*)(Display *, Window, int, int, unsigned int, unsigned int, unsigned int, unsigned long, unsigned long)) dllXCreateSimpleWindow)(display, parent, x, y, width, height, border_width, border, background);
    fprintf(stdout, "[spotifywm] 0x%lx = XCreateSimpleWindow(parent=0x%lx)\n", w, parent);
    return w;
}

int XMapRaised(Display *display, Window w)
{
    fprintf(stdout, "[spotifywm] XMapRaised(window=0x%lx)\n", w);
    return ((int (*)(Display *, Window)) dllXMapRaised)(display, w);
}

int XMapSubwindows(Display *display, Window w)
{
    fprintf(stdout, "[spotifywm] XMapSubwindows(window=0x%lx)\n", w);
    return ((int (*)(Display *, Window)) dllXMapSubwindows)(display, w);
}

int XMapWindow(Display *display, Window w)
{
    fprintf(stdout, "[spotifywm] XMapWindow(window=0x%lx)\n", w);

    SetClassHint(display, w, "spotify", "Spotify");
    return ((int (*)(Display *, Window)) dllXMapWindow)(display, w);
}

void spotifywm_init(void) __attribute__((constructor));
void spotifywm_init(void)
{
    if (strcmp(program_invocation_short_name, "spotify"))
    {
        fprintf(stderr, "[spotifywm] Program invocation didn't match 'spotify': %s\n",
                program_invocation_short_name);
    }

    dllXCreateWindow = dlsym(RTLD_NEXT, "XCreateWindow");
    dllXCreateSimpleWindow = dlsym(RTLD_NEXT, "XCreateSimpleWindow");
    dllXMapRaised = dlsym(RTLD_NEXT, "XMapRaised");
    dllXMapSubwindows = dlsym(RTLD_NEXT, "XMapSubwindows");
    dllXMapWindow = dlsym(RTLD_NEXT, "XMapWindow");

    assert(dllXCreateWindow);
    assert(dllXCreateSimpleWindow);
    assert(dllXMapRaised);
    assert(dllXMapSubwindows);
    assert(dllXMapWindow);
}

and is compiled with:

gcc -Wall -Wextra -O2 -shared -fPIC -static-libgcc -lX11 -o spotifywm.so spotifywm.c

Long story short, booting spotify outputs the following:

/opt/spotify/spotify: /usr/lib/libcurl-gnutls.so.4: no version information available (required by /opt/spotify/spotify)
/opt/spotify/spotify: /usr/lib/libcurl-gnutls.so.4: no version information available (required by /opt/spotify/spotify)
/opt/spotify/spotify: /usr/lib/libcurl-gnutls.so.4: no version information available (required by /opt/spotify/spotify)
[spotifywm] 0x6a00001 = XCreateWindow(parent=0x79b)
/proc/self/exe: /usr/lib/libcurl-gnutls.so.4: no version information available (required by /proc/self/exe)
[spotifywm] Program invocation didn't match 'spotify': exe
[spotifywm] 0x600000a = XCreateWindow(parent=0x5e00008)
[spotifywm] XMapWindow(window=0x600000a)
[spotifywm] 0x6000012 = XCreateWindow(parent=0x5e00008)
[spotifywm] XMapWindow(window=0x6000012)

showing the creation of three windows and the mapping of two. The issue seems to be though that the parent window for the latter two create calls is somehow owned by Spotify according to xlsw -r:

0x05E00007  ---  Spotify/spotify  Spotify Premium
  0x05E00008  --o  NA           NA
    0x06000012  ---  Spotify/spotify  NA

and doesn't have it's name or class set prior to being mapped (and we don't see a mapping call being intercepted for that window ID). I've tried setting the class hint of the parent windows in the latter two mapping calls however it seems like that's too late as my bspwm rule still doesn't handle the spotify window properly.

Hoping someone more familiar with X may know how else the 0x5e00008 window in the above example could be getting created and/or mapped.

EDIT: Also potentially worth adding that xprop gives the Spotify window's id as 0x5e00008 as well, not one the the windows we see being mapped above.

@OleStrohm
Copy link

By changing the code in XMapWindow to say "notspotify", I get:

0x01600007  ---  Spotify/spotify  Spotify Premium
  0x01600008  --o  NA           NA
    0x01800008  ---  Notspotify/notspotify  NA

And with awesomewm I can reload my window manager, which then reapplies all the rules and correctly send spotify to my desired tag.

That shows that the rules query 0x01600007, even though the second line is the window id from xprop (in my case it is 0x01600008), and the only window we seem to be able to affect is the bottom one.

@TiZ-HugLife
Copy link

Same on XFWM4 with devilspie2. Devilspie2 sees both the window title and the app name as "Untitled window", so here's what I'm doing to work around it. The idea is that if both values are set to that and the spotify process is less than three seconds old, it's reasonable to assume that the window belongs to Spotify. It could be reasonable to presume that Spotify is the only application on a given Linux desktop that would misbehave so flagrantly, but better safe than sorry.

if win_name == "Untitled window" and app_name == "Untitled window" then
    -- Let's find out if Spotify just started.
    handle = io.popen("pgrep -o spotify")
    proc = "/proc/"..handle:read()
    handle:close()
    handle = io.popen("stat -c%X "..proc)
    stat = handle:read()
    handle:close()
    if (os.time() - stat) < 3 then
        debug_print("Spotify just started; this window likely belongs to it.")
        set_class("Spotify")  -- Custom function to change the class for the rest of the scripts.
    end
end

@andrevandal
Copy link

hey, any update?

thx

@msdrigg
Copy link

msdrigg commented Nov 9, 2021

I made an update to the tiler for pop-os that detects WM_CLASS change and then re-tiles. This same method could be used to fix other tiling wms

@andrevandal
Copy link

@msdrigg cool, how did you do? I'm using pop-os too, and it's too boring. :(

@msdrigg
Copy link

msdrigg commented Nov 11, 2021

@andrevandal I submitted a pr for pop-shell here pop-os/shell#1174. You can install from github to have these changes on your system, but its not going to be available on a release for awhile.

There are some additional issues with the latest spotify flatpak, so if you do upgrade, make sure you are using the spotify snap or the regular deb.

@BachoSeven
Copy link

BachoSeven commented Feb 20, 2022

Same on XFWM4 with devilspie2. Devilspie2 sees both the window title and the app name as "Untitled window", so here's what I'm doing to work around it. The idea is that if both values are set to that and the spotify process is less than three seconds old, it's reasonable to assume that the window belongs to Spotify. It could be reasonable to presume that Spotify is the only application on a given Linux desktop that would misbehave so flagrantly, but better safe than sorry.

if win_name == "Untitled window" and app_name == "Untitled window" then
    -- Let's find out if Spotify just started.
    handle = io.popen("pgrep -o spotify")
    proc = "/proc/"..handle:read()
    handle:close()
    handle = io.popen("stat -c%X "..proc)
    stat = handle:read()
    handle:close()
    if (os.time() - stat) < 3 then
        debug_print("Spotify just started; this window likely belongs to it.")
        set_class("Spotify")  -- Custom function to change the class for the rest of the scripts.
    end
end

Not at all an elegant solution, but I hacked a similar workaround for dwm:

	/* rule matching */
	c->isfloating = 0;
	c->tags = 0;
	XGetClassHint(dpy, c->win, &ch);
	if (0 == system("[ $(ps -o etimes= -p $(pgrep -o spotify)) -lt 3 ]")) {
		class    = ch.res_class ? ch.res_class : "Spotify";
	} else {
		class    = ch.res_class ? ch.res_class : broken;
	}
	instance = ch.res_name  ? ch.res_name  : broken;

In case I ever update it, here it is inside my build: https://github.com/BachoSeven/dwm/blob/master/dwm.c#L404

P.S. I found another window which has an initially broken class: Chromium's Task Manager, the one brought up by Shift+Tab.

@mohad12211
Copy link

interesting, where exactly do I put this code in dwm? @BachoSeven

@BachoSeven
Copy link

BachoSeven commented Feb 21, 2022

@mohad12211 You have to edit the rule matching code, which is inside the applyrules() function in dwm.c.

@apprehensions
Copy link

@BachoSeven thanks for the workaround, but it doesn't work unfortunately. atleast on my system it doesn't.

@BachoSeven
Copy link

@wael444 how exactly? did you change the code, recompile, add a rule for the Spotify class in config.h, and then restart dwm?

@apprehensions
Copy link

did you change the code, recompile

i added the snippet. and reloaded

add a rule for the Spotify class in config.h

i only checked the class name using xprop

however the rule applies to it anyway? i kinda expected WM_CLASS to change as well but in dwm it follows the rule specified. i it works...? i think.

@BachoSeven
Copy link

To clarify: what I did is make it so any window which both spawns 3 seconds after a spotify binary has started, and also has a broken class, gets its class renamed to Spotify. By "class" in this context I mean the second string of the WM_CLASS array returned by the xprop utility, which you can reference in dwm's config.h by using the first column of the rules[] array.

@samyakbardiya
Copy link

hey @BachoSeven, when I do xprop I am getting:

instance: "spotify"
class: "Spotify"
title: "Spotify"

when I reload my dwm, Spotify gets into the designated tag, but when I try to open Spotify, it opens in the present tag only.

ig your patch would not benefit me as I am getting the correct xprop class, am I right ??

and any idea how to fix this ??

@mohad12211
Copy link

mohad12211 commented Mar 20, 2022

are you sure that you applied the patch correctly?
can you post the lines of code between /* rule matching */ and the for loop? @samyak039

@BachoSeven
Copy link

@samyak039 regarding your last observation, the point is not that the xprop class is wrong when you check it, but that it is immediately after the application starts, which is the reason why the workaround is needed.

@samyakbardiya
Copy link

samyakbardiya commented Mar 21, 2022

thanks @BachoSeven for explaining.

@mohad12211 afterwards I applied the patch, but Spotify is still not obeying the tag rule.

I added the patch mentioned by BachoSeven, in dwm.c

	/* rule matching */
	c->isfloating = 0;
	c->tags = 0;
	XGetClassHint(dpy, c->win, &ch);
	if (0 == system("[ $(ps -o etimes= -p $(pgrep -o spotify)) -lt 3 ]")) {
		class    = ch.res_class ? ch.res_class : "Spotify";
	} else {
		class    = ch.res_class ? ch.res_class : broken;
	}
	instance = ch.res_name  ? ch.res_name  : broken;

and have also added this in my config.h:

    /* class                instance  title      tags mask  isfloating  isfakefullscreen  monitor */
    { "Spotify",            NULL,     NULL,      1 << 7,        0,            1,            -1 },

but still the Spotify is opening in the current tag.

@BachoSeven
Copy link

@samyak039 Not sure I can help then, however I am also using spotifywm, no idea if that's helping tho.

@apprehensions
Copy link

my only workaround at the moment is this script:

spotify &
sleep 0.5
xdotool set_window --classname Spotify --class spotify --name Spotify $(wmctrl -l | awk 'END{print $1}')

@alvarotroya
Copy link

alvarotroya commented Jun 10, 2022

If it helps anyone, this is working for me in dwm out-of-the box (after adding the rule of course).

    {"Spotify", NULL, NULL, 1 << 3, 0, -1},

Never mind, I am running an older version of Spotify, 1.1.10

@apprehensions
Copy link

only after adding the rule and the change in dwm.c? or just the rule?

in theory this shouldn't be possible.

@alvarotroya
Copy link

Sorry for the confusion, I'm running an older version of Spotify.

@apprehensions
Copy link

No!! don't update!! give us the old version!!!!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.