Repository: qtile/qtile

Issue:
`plank` window not kept above
### The issue:

Following discussion at #4049. Would be interesting to check if related to #1145. Going to try  `keep_above` with `tint2`, although I think this is a `qtile` issue. Also gonna try my hand at a `bisect`.

### Required:

- [X] I have searched past issues to see if this bug has already been reported.


Discussion:
I'm not sure I'd bother with a bisect. I suspect there's just a bug in our implementation of layering.

NB by default, floating windows will have `kept_above` set to `True` so `plank` wouldn't necessary stay above these. That being said, I don't think the implementation is quite right as normal, tiled windows seem to open above it which shouldn't happen.
Whoops, I only used bisect once, when I had multiple possible bad commits, but now the situation is too simple, my bad.
I'll need to look at my code again but I have an idea what the issue is here: `plank` is a `Static` window which means it has no group attached to it. This may interfere with some of the logic as currently written.
Ignore that. Your original suggestion (in the Discussion) was correct - the issue is that `bring_to_front` ignores these rules.

EDIT: Ha - there is an issue with static windows too...

Resolving patch:
diff --git a/docs/conf.py b/docs/conf.py
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -103,7 +103,6 @@ def __dir__(self):
     "sphinx.ext.graphviz",
     "sphinx.ext.todo",
     "sphinx.ext.viewcode",
-    "sphinxcontrib.seqdiag",
     "numpydoc",
     "sphinx_qtile",
 ]
diff --git a/libqtile/backend/base.py b/libqtile/backend/base.py
--- a/libqtile/backend/base.py
+++ b/libqtile/backend/base.py
@@ -482,7 +482,13 @@ def disable_fullscreen(self) -> None:
     @abstractmethod
     @expose_command()
     def bring_to_front(self) -> None:
-        """Bring the window to the front"""
+        """
+        Bring the window to the front.
+
+        In X11, `bring_to_front` ignores all other layering rules and brings the
+        window to the very front. When that window loses focus, it will be stacked
+        again according the appropriate rules.
+        """
 
     @abstractmethod
     @expose_command()
diff --git a/libqtile/backend/x11/window.py b/libqtile/backend/x11/window.py
--- a/libqtile/backend/x11/window.py
+++ b/libqtile/backend/x11/window.py
@@ -995,9 +995,12 @@ def change_layer(self, up=True, top_bottom=False):
         if len(self.qtile.windows_map) < 2:
             return
 
-        if self.group is None:
+        if self.group is None and not isinstance(self, Static):
             return
 
+        # Use the window's group or current group if this isn't set (e.g. Static windows)
+        group = self.group or self.qtile.current_group
+
         parent = self.window.get_wm_transient_for()
         if parent is not None and not up:
             return
@@ -1013,9 +1016,14 @@ def change_layer(self, up=True, top_bottom=False):
         if self.wid not in stack or len(stack) < 2:
             return
 
-        group_windows = self.group.windows
-        if self.group.screen is not None:
-            group_bars = [gap for gap in self.group.screen.gaps if isinstance(gap, bar.Bar)]
+        # Get all windows for the group and add Static windows to ensure these are included
+        # in the stacking
+        group_windows = group.windows.copy()
+        statics = [win for win in self.qtile.windows_map.values() if isinstance(win, Static)]
+        group_windows.extend(statics)
+
+        if group.screen is not None:
+            group_bars = [gap for gap in group.screen.gaps if isinstance(gap, bar.Bar)]
         else:
             group_bars = []
 
@@ -1051,6 +1059,10 @@ def change_layer(self, up=True, top_bottom=False):
 
         # If we're forcing to top or bottom of current layer...
         elif top_bottom:
+            # If there are no other windows in the same layer then there's nothing to do
+            if not same:
+                return
+
             if up:
                 sibling = same[-1]
                 above = True
@@ -1278,11 +1290,6 @@ def focus(self, warp: bool = True) -> None:
         if self.group:
             self.group.current_window = self
 
-        # See https://github.com/qtile/qtile/pull/3409#discussion_r1117952134 for discussion
-        # on mypy error here
-        if self.fullscreen and not self.previous_layer[4]:  # type: ignore
-            self.change_layer()
-
         # Check if we need to restack a previously focused fullscreen window
         self.qtile.core.check_stacking(self)
 
@@ -2241,3 +2248,13 @@ def set_position(self, x, y):
             if self._is_in_window(curx, cury, window):
                 self.group.layout.swap(self, window)
                 return
+
+    @expose_command
+    def focus(self, warp: bool = True) -> None:
+        """Focus the window."""
+        _Window.focus(self, warp)
+
+        # Focusing a fullscreen window puts it into a different layer
+        # priority group. If it's not there already, we need to move it.
+        if self.fullscreen and not self.previous_layer[4]:
+            self.change_layer()


Test patch: