From cf975d082eb34e9b52d1556253be785690efbec8 Mon Sep 17 00:00:00 2001
From: Illia Bobyr <illia.bobyr@gmail.com>
Date: Fri, 28 Oct 2022 22:15:17 -0700
Subject: [PATCH] EwmhDesktops: _NET_CLIENT_LIST_STACKING: In focus order

Order workspaces based on the visibility, before collecting windows.
`_NET_CLIENT_LIST_STACKING` is supposed to be in the focus order.
---
 CHANGES.md                   |  8 ++++++++
 XMonad/Hooks/EwmhDesktops.hs | 31 +++++++++++++++++++++++++++----
 2 files changed, 35 insertions(+), 4 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 83cc8006..73ed7719 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -55,6 +55,14 @@
     - Deprecated the entire module, use `XMonad.Actions.WithAll`
       instead.
 
+  * `XMonad.Hooks.EwmhDesktops`
+
+    - `_NET_CLIENT_LIST_STACKING` puts windows in the current workspace at the
+      top in bottom-to-top order, followed by visible workspaces, followed by
+      invisible workspaces.  Within visible and invisible groups, workspaces are
+      ordered lexicographically, as before.  Currently focused window will
+      always be the topmost, meaning the last in the list.
+
 ### New Modules
 
 * `XMonad.Hooks.OnPropertyChange`:
diff --git a/XMonad/Hooks/EwmhDesktops.hs b/XMonad/Hooks/EwmhDesktops.hs
index fd1230e0..584684e8 100644
--- a/XMonad/Hooks/EwmhDesktops.hs
+++ b/XMonad/Hooks/EwmhDesktops.hs
@@ -320,10 +320,33 @@ ewmhDesktopsLogHook' EwmhDesktopsConfig{workspaceSort, workspaceRename} = withWi
     let clientList = nub . concatMap (W.integrate' . W.stack) $ ws
     whenChanged (ClientList clientList) $ setClientList clientList
 
-    -- Set stacking client list which should have bottom-to-top
-    -- stacking order, i.e. focused window should be last
-    let clientListStacking = nub . concatMap (maybe [] (\(W.Stack x l r) -> reverse l ++ r ++ [x]) . W.stack) $ ws
-    whenChanged (ClientListStacking clientListStacking) $ setClientListStacking clientListStacking
+    -- @ws@ is sorted in the "workspace order", which, by default, is
+    -- the lexicographical sorting on @WorkspaceId@.
+    -- @_NET_CLIENT_LIST_STACKING@ is expected to be in the "bottom-to-top
+    -- stacking order".  It is unclear what that would mean for windows on
+    -- invisible workspaces, but it seems reasonable to assume that windows on
+    -- the current workspace should be "at the top".  With the focused window to
+    -- be the top most, meaning the last.
+    --
+    -- There has been a number of discussions on the order of windows within a
+    -- workspace.  See:
+    --
+    --   https://github.com/xmonad/xmonad-contrib/issues/567
+    --   https://github.com/xmonad/xmonad-contrib/pull/568
+    --   https://github.com/xmonad/xmonad-contrib/pull/772
+    let clientListStacking =
+          let wsInFocusOrder = W.hidden s
+                               ++ (map W.workspace . W.visible) s
+                               ++ [W.workspace $ W.current s]
+              stackWindows (W.Stack cur up down) = reverse up ++ down ++ [cur]
+              workspaceWindows = maybe [] stackWindows . W.stack
+              -- In case a window is a member of multiple workspaces, we keep
+              -- only the last occurrence in the list.  One that is closer to
+              -- the top in the focus order.
+              uniqueKeepLast = reverse . nub . reverse
+           in uniqueKeepLast $ concatMap workspaceWindows wsInFocusOrder
+    whenChanged (ClientListStacking clientListStacking) $
+      setClientListStacking clientListStacking
 
     -- Set current desktop number
     let current = W.currentTag s `elemIndex` map W.tag ws