Skip to content

fix managed windows drawing above unmanaged ones (e.g. punching through screen lockers)#233

Open
Nabile-Rahmani wants to merge 8 commits into
JLErvin:masterfrom
Nabile-Rahmani:fix-xscreensaver
Open

fix managed windows drawing above unmanaged ones (e.g. punching through screen lockers)#233
Nabile-Rahmani wants to merge 8 commits into
JLErvin:masterfrom
Nabile-Rahmani:fix-xscreensaver

Conversation

@Nabile-Rahmani

@Nabile-Rahmani Nabile-Rahmani commented Mar 20, 2026

Copy link
Copy Markdown
Contributor

Always prepend an oldest fake window managed by berry to inherit its lower stacking order to ensure the WM doesn't draw above unmanaged windows.

Fixes #231 (https://www.jwz.org/xscreensaver/faq.html#popup-windows)

Depends on #229

@a0405u

a0405u commented Mar 22, 2026

Copy link
Copy Markdown
Contributor

Oh, this is awesome! I've noticed this behavior but haven't got my hands on it! I use screentime manager that relies on unmanaged windows, and sometimes new windows appear on top of it. I thought that it might be the issue with my app, not the wm. Thank you!

Comment thread wm.c
return;

Window wins[count + 1];
wins[i++] = check; // Always prepend an oldest fake window managed by berry to inherit its lower stacking order to ensure the WM doesn't draw above unmanaged windows. Fixes things like punching through screen lockers (https://www.jwz.org/xscreensaver/faq.html#popup-windows).

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't dug into the "why" much, but the changes in this PR sadly break the functionality of the first opened window for me. The first window I open has its decorations draw above the window itself.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, I also noticed that the first window in a workspace always draws in front of any newer one until you click back and forth between the first and latter ones.

Revert 8327b67 and it'll seemingly fix it !

We may want to review #227 first.

@Nabile-Rahmani Nabile-Rahmani Mar 25, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved in 30016c3

Only fixed when decorate is false, my bad.

@Nabile-Rahmani

Nabile-Rahmani commented Mar 23, 2026

Copy link
Copy Markdown
Contributor Author

Oh, this is awesome! I've noticed this behavior but haven't got my hands on it! I use screentime manager that relies on unmanaged windows, and sometimes new windows appear on top of it. I thought that it might be the issue with my app, not the wm. Thank you!

Haha, I should be the one thanking you, you provided me 99% of the fix ! Sometimes the stars do align :D

... But unfortunately I've run into berry crashes and Xorg hangs introduced by 529c988 and I'm scratching my head.

Did you also run into them ?


BTW, polybar users need to add wm-restack = bottom to their config so bars always draw below managed windows.

@a0405u

a0405u commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

Oh, this is awesome! I've noticed this behavior but haven't got my hands on it! I use screentime manager that relies on unmanaged windows, and sometimes new windows appear on top of it. I thought that it might be the issue with my app, not the wm. Thank you!

Haha, I should be the one thanking you, you provided me 99% of the fix ! Sometimes the stars do align :D

... But unfortunately I've run into berry crashes and Xorg hangs introduced by 529c988 and I'm scratching my head.

Did you also run into them ?


BTW, polybar users need to add wm-restack = bottom to their config so bars always draw below managed windows.

Hmm, I have not noticed any crashes or hangs, and I haven't noticed the issue with the first opened window decorations. For me it worked flawlessly. I use berry with decorations turned off, this might be the reason.

UPD: I actually use berry with decorations, check the next answer!

@a0405u

a0405u commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

Oh, this is awesome! I've noticed this behavior but haven't got my hands on it! I use screentime manager that relies on unmanaged windows, and sometimes new windows appear on top of it. I thought that it might be the issue with my app, not the wm. Thank you!

Haha, I should be the one thanking you, you provided me 99% of the fix ! Sometimes the stars do align :D

... But unfortunately I've run into berry crashes and Xorg hangs introduced by 529c988 and I'm scratching my head.

Did you also run into them ?

BTW, polybar users need to add wm-restack = bottom to their config so bars always draw below managed windows.

I think you should try #229, it changes restack and raise functionality. I was testing merge of #223 #226 #227 #228 #229 this whole time. There were hangs that I've noticed. I'm not sure if they were introduced during #229 or #227, but they are solved in #229.

Tell me, if it solves hangs or the issue with the first window for you!

@Nabile-Rahmani

Nabile-Rahmani commented Mar 23, 2026

Copy link
Copy Markdown
Contributor Author

Thanks, I'll check it out.

Here's where it crashed just in case:

─── Source ──────────────────────────────────────────────────────────────────────────
 582  static struct client*
 583  get_client_from_window(Window w)
 584  {
 585      for (int i = 0; i < WORKSPACE_NUMBER; i++) {
 586          for (struct client *tmp = c_list[i]; tmp != NULL; tmp = tmp->next) {
 587              if (tmp->window == w)
 588                  return tmp;
 589              else if (tmp->decorated && tmp->dec == w)
 590                  return tmp;
 591          }
─── Stack ───────────────────────────────────────────────────────────────────────────
[0] from 0x0000000000403b55 in get_client_from_window+51 at wm.c:587
[1] from 0x0000000000404627 in handle_property_notify+85 at wm.c:769
[2] from 0x000000000040826c in run+247 at wm.c:1956
[3] from 0x000000000040a993 in main+748 at wm.c:2643
─── Threads ─────────────────────────────────────────────────────────────────────────
[1] id 436482 from 0x0000000000403b55 in get_client_from_window+51 at wm.c:587
─── Variables ───────────────────────────────────────────────────────────────────────
arg w = 79691789
loc tmp = 0xe96a: Cannot access memory at address 0xe96a, i = 0
─────────────────────────────────────────────────────────────────────────────────────
>>> p c_list[i]->next
$4 = (struct client *) 0xe96a

And the Xorg hang was spending its time in memmove.

@Nabile-Rahmani

Nabile-Rahmani commented Mar 23, 2026

Copy link
Copy Markdown
Contributor Author

I think you should try #229, it changes restack and raise functionality. I was testing merge of #223 #226 #227 #228 #229 this whole time. There were hangs that I've noticed. I'm not sure if they were introduced during #229 or #227, but they are solved in #229.

Tell me, if it solves hangs or the issue with the first window for you!

Ok, everything seems to be working well so far ! Great job.

I'll rebase #229 onto this.

... I spoke too soon: while it fixes the crash and first window stacking, there's still a hang shared between berry & Xorg preventing me from quitting berry.

berry is aggressively looping while handling IPCQuit (it doesn't exit refresh_config+272 at wm.c:1939):

─── Stack ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
[0] from 0x00007f438cdaf5f4 in poll
[1] from 0x00007f438cc90d6a in _xcb_conn_wait
[2] from 0x00007f438cc92ddf in wait_for_reply
[3] from 0x00007f438cc92f50 in xcb_wait_for_reply64
[4] from 0x00007f438d023b0c in _XReply
[5] from 0x00007f438d00903e in XGetWindowProperty
[6] from 0x0000000000406918 in client_window_is_below+108 at wm.c:1611
[7] from 0x000000000040787d in client_raise+104 at wm.c:1816
[8] from 0x0000000000407f37 in refresh_config+272 at wm.c:1939
[9] from 0x00000000004055b1 in ipc_config+1095 at wm.c:1269
[10] from 0x0000000000403c83 in handle_client_message+219 at wm.c:616
[11] from 0x0000000000408479 in run+247 at wm.c:2016
[12] from 0x000000000040ac0f in main+748 at wm.c:2710
berry: Moving client main window to 1427x63
berry: Updating client status (window_id=79691787)...
berry: Resizing client main window to 1171x699
berry: Updating client status (window_id=79691787)...
berry: Raising Client...
berry: Restacking...
berry: Refreshing client
berry: Moving client main window to 1425x786
berry: Updating client status (window_id=79691784)...
berry: Resizing client main window to 1124x641
berry: Updating client status (window_id=79691784)...
berry: Moving client main window to 1425x786
berry: Updating client status (window_id=79691784)...
berry: Resizing client main window to 1124x641
berry: Updating client status (window_id=79691784)...
berry: Raising Client...
berry: Restacking...
berry: Refreshing client
berry: Moving client main window to 1427x63
berry: Updating client status (window_id=79691787)...
berry: Resizing client main window to 1171x699
berry: Updating client status (window_id=79691787)...
berry: Moving client main window to 1427x63
berry: Updating client status (window_id=79691787)...
berry: Resizing client main window to 1171x699
berry: Updating client status (window_id=79691787)...
berry: Raising Client...
berry: Restacking...
berry: Refreshing client
berry: Moving client main window to 1425x786
berry: Updating client status (window_id=79691784)...
berry: Resizing client main window to 1124x641
berry: Updating client status (window_id=79691784)...
berry: Moving client main window to 1425x786
berry: Updating client status (window_id=79691784)...
berry: Resizing client main window to 1124x641
berry: Updating client status (window_id=79691784)...
berry: Raising Client...

The client linked list is somehow infinitely looping despite the next pointer being null, and the workspace loop counter never increments, because client_raise reassigns c->next to a valid window.

@a0405u

a0405u commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

How do I achieve that hang? If I use berryc quit, everything works without issues. I haven't noticed such behavior while testing everything and continuously closing berry. Are you testing #229 only or #233 on top of #229? Might it be the issue with your patch?

Here is the log, from the side of the berry everything seems ok:

berry: Shutting down window manager
berry: Deleting client on workspace 2
berry: Deleting client on workspace 2
berry: Deleting client on workspace 2
berry: Deleting client on workspace 2
berry: Closing display...
The server closed the connection.
polybar|error: X connection error, terminating... (what: Socket, pipe or stream error)
polybar|notice:  Termination signal received, shutting down...
X connection to :0 broken (explicit kill or server shutdown).
XIO:  fatal IO error 25 (Inappropriate ioctl for device) on X server ":0"
      after 1370 requests (807 known processed) with 0 events remaining.
X connection to :0 broken (explicit kill or server shutdown).

@Nabile-Rahmani

Nabile-Rahmani commented Mar 23, 2026

Copy link
Copy Markdown
Contributor Author

How do I achieve that hang? If I use berryc quit, everything works without issues. I haven't noticed such behavior while testing everything and continuously closing berry. Are you testing #229 only or #233 on top of #229? Might it be the issue with your patch?

Testing only #229:

* 779ed4a (HEAD, a0405u/feat/always-above) Fixed hang on client_raise
* 6a31777 Added always above feature
* 8327b67 (origin/fix/restack-inconsistency, a0405u/fix/restack-inconsistency) Removed redundant client_raise in client_show which caused issues with updated restack method
* 529c988 Fixed workspace stacking for future always on top support
* b9c3ee8 (origin/master, origin/HEAD, a0405u/master, master) fix: correct spelling errors throughout codebase (#225)

You only need two windows in the list and they'll raise each other for ever.

We should either iterate an immutable copy of that list or ignore the windows we already raised so we can exit that loop, right ?

Do you want me to look anything up while I still have that debugging process open ? Not sure how easily I can reproduce it.

@a0405u

a0405u commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

Truly, I was able to reproduce it with two windows open on quit! I'll try to look deeper into this problem! It would be nice if you'll give me more details, I'm not good at debugging, but I'll look into the code itself.

@Nabile-Rahmani

Copy link
Copy Markdown
Contributor Author

Only immutable data structures can safely be iterated.

Because client_raise mutates the iterator, any other loop inside berry that uses this function runs the same risk of infinitely looping.

I'm not sure if there's a better way than to copy the client list into an array.

Here's a video of what the code does if it helps at all (look at Source, Variables & Expressions).

@a0405u

a0405u commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

I'm not sure that something can theoretically change the list while it's being iterated. Berry is not a parallel program, there is no way... Or am I not understanding you correctly? If that's what you mean then the problem might be in event handling loop, not in the client_raise.

We are not getting "Shutting down window manager" for some reason, we're stuck in the event handling loop and can't even handle a single run() while cycle.

@Nabile-Rahmani

Nabile-Rahmani commented Mar 23, 2026

Copy link
Copy Markdown
Contributor Author

See https://github.com/JLErvin/berry/pull/229/changes#diff-7d379c8c94cabc039228fa801df15583038d6326c58cb7dd0543185bd356a285R1845-R1853

client_raise is mutating our client's next pointer as it's switching places, undoing the null that's supposed to terminate the list ;)

@a0405u

a0405u commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

See https://github.com/JLErvin/berry/pull/229/changes#diff-7d379c8c94cabc039228fa801df15583038d6326c58cb7dd0543185bd356a285R1845-R1853

client_raise is mutating our client's next pointer as it's switching places, undoing the null that's supposed to terminate the list ;)

See https://github.com/JLErvin/berry/pull/229/changes#diff-7d379c8c94cabc039228fa801df15583038d6326c58cb7dd0543185bd356a285R1845-R1853

client_raise is mutating our client's next pointer as it's switching places, undoing the null that's supposed to terminate the list ;)

Null at the end of the list is set by the previous code block. I don't see any issues here. In what situation should we lose Null?

Oh, I see it...

@Nabile-Rahmani

Copy link
Copy Markdown
Contributor Author

Yup, in the block I pointed out (c->next above):

null.mp4

@Nabile-Rahmani

Copy link
Copy Markdown
Contributor Author

Fixed it, even though it feels dirty iterating 3 lists.

I'm gonna clean that up and push it, and also check other client_raise callers.

@a0405u

a0405u commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

I don't think your fix is correct. Don't push it.

We're not losing Null, we're moving items in the list continuously while iterating over it outside of client_raise(). The issue is in refresh_config() we're iterating over a list of clients from top to bottom and raising each one of them. The behavior of client_rise() is correct, it's raising clients, but since we've risen the client we start from the top of the list in the refresh_config(). We could copy the list of clients in the refresh_config(), but why do we need to rise each client from top to bottom? The issue is with refresh_config(), it works the same way as switch_ws() previously. It raises each client in the wrong order. I fixed behavior of the switch_ws() previously. Now we just need to remove client_rise in the refresh_config().

@Nabile-Rahmani

Nabile-Rahmani commented Mar 23, 2026

Copy link
Copy Markdown
Contributor Author

Yup, my fix was in refresh_config, but if we can remove client_raise from it altogether that'd be ideal.

@a0405u

a0405u commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

Yes! We just should not use client_rise() this way. The issue is with that. We should not use the function that changes the position of the item in the list we're currently iterating (that's the main point of this function actually). And those client_rise() calls inside refresh_config() are not doing anything except turning the window order upside down, we're not supposed to do that in the first place. I think it's a leftover of the previous client_rise() and client_move_to_front() implementation.

@a0405u

a0405u commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

I'll push the fix to the #229!

@Nabile-Rahmani

Copy link
Copy Markdown
Contributor Author

Perfect ! That's one case where Chesterton's fence doesn't apply 😆

@Nabile-Rahmani

Copy link
Copy Markdown
Contributor Author

@JLErvin it should be good to give this another spin now ! Hopefully berry is as stable as ever.

I'm stress testing this on my own.

@a0405u

a0405u commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

@Nabile-Rahmani @JLErvin I updated the #229 fix after some shower thoughts which confirmed that we need to call a restack_ws() at the end of refresh_config(). Otherwise decorations are displayed on top of the windows until the next restack_ws() call, because we recreate them.

@Nabile-Rahmani

Copy link
Copy Markdown
Contributor Author

Thanks for the heads up.

Quick note: do you have to restack all workspaces or can you get away with only restacking the current workspace ?

@a0405u

a0405u commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

I think it's just safer to restack all of them. I tested restacking only the current one and it works unreliably for some reason.

…gh screen lockers)

Always prepend an oldest fake window managed by berry to inherit its lower stacking order to ensure the WM doesn't draw above unmanaged windows.

Fixes JLErvin#231 (https://www.jwz.org/xscreensaver/faq.html#popup-windows)

Depends on JLErvin#229
@Nabile-Rahmani

Copy link
Copy Markdown
Contributor Author

The first window always being on top problem reappeared.

Repro in an empty workspace, spawn a window, snap it left, and spawn/move new windows around. :/

@a0405u

a0405u commented Mar 23, 2026

Copy link
Copy Markdown
Contributor

I wasn't able to reproduce your problem on my branch of merged #223 #226 #227 #228 #229! Could you check xprop of your window? Might it be always above? I have not applied your patch by the way!

https://files.catbox.moe/e4r4ph.mp4

@Nabile-Rahmani

Nabile-Rahmani commented Mar 24, 2026

Copy link
Copy Markdown
Contributor Author

Ok, now the issue is in my commit.

manage_new_window() → client_manage_focus() → manage_xsend_icccm(c, wm_atom[WMTakeFocus]) → XGetWMProtocols(): window 2 spawns in front of window 1 and is unfocused

manage_new_window() → client_manage_focus() → client_raise() → restack_ws() → XRestackWindow(): OK, the window list properly has [check, 2, 1]

manage_new_window() → client_manage_focus() → ewmh_set_focus(): OK

  • manage_new_window() → client_manage_focus() → manage_xsend_icccm(c, wm_atom[WMTakeFocus]) → XGetWMProtocols(): window 2 takes focus but moved behind window 1 !

BTW, manage_xsend_icccm(c, wm_atom[WMTakeFocus])always returns false (take focus protocol doesn't exist ?), so the event doesn't get sent. does it matter ?

Right after manage_new_window, there's another handle_client_message() → client_manage_focus() but it doesn't change the stack.

xprop on the root window tells me _NET_ACTIVE_WINDOW is window 2, and window 2 is focused, but still draws behind window 1 despite XRestackWindows getting the right order. ???

_NET_CLIENT_LIST also matches what gets passed to XRestackWindows, and window 1 is always the last in the list.

Restacking works between any window except the first until it gets its focus back.

I don't know if berry's check/nofocus windows have any special property that doesn't play well with that first window. It's so strange.

I checked the return value of XRestackWindows just in case, and it always returns 1, with or without prepending the check window, and even when the bug is gone.

For each window in the window array that is not a child of the specified window, a BadMatch error results.

Should all managed windows be children of the check window for restacking to work properly ?

Restacking works between any window except the first until it gets its focus back, so let's just make it happy and forget about this [nonsense](JLErvin#233 (comment)).
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 this pull request may close these issues.

New windows spawn on top of XScreenSaver

3 participants