Skip to content

Commit 4da00d2

Browse files
authored
Merge pull request #968 from TheMC47/ewmh-handle-above-below
EWMH: support _NET_WM_STATE_{ABOVE,BELOW}
2 parents 415797f + 10bf8a2 commit 4da00d2

File tree

2 files changed

+86
-5
lines changed

2 files changed

+86
-5
lines changed

CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@
6767
when setting the **_NET_DESKTOP_VIEWPORT**. This can be done using
6868
the `setEwmhHiddenWorkspaceToScreenMapping`.
6969

70+
- Added support for **_NET_WM_STATE_{ABOVE,BELOW}** to place windows
71+
correctly.
72+
7073
* `XMonad.Layout.IndependentScreens`
7174

7275
- Added `focusWorkspace` for focusing workspaces on the screen that they

XMonad/Hooks/EwmhDesktops.hs

Lines changed: 83 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ module XMonad.Hooks.EwmhDesktops (
5656

5757
-- $customHiddenWorkspaceMapper
5858
setEwmhHiddenWorkspaceToScreenMapping,
59+
-- ** @_NET_WM_STATE_{ABOVE,BELOW}@
60+
-- $customManageAboveBelowState
61+
enableEwmhManageAboveBelowState,
5962

6063
-- * Standalone hooks (deprecated)
6164
ewmhDesktopsStartup,
@@ -104,7 +107,8 @@ import qualified XMonad.Util.ExtensibleState as XS
104107
ewmh :: XConfig a -> XConfig a
105108
ewmh c = c { startupHook = ewmhDesktopsStartup <> startupHook c
106109
, handleEventHook = ewmhDesktopsEventHook <> handleEventHook c
107-
, logHook = ewmhDesktopsLogHook <> logHook c }
110+
, logHook = ewmhDesktopsLogHook <> logHook c
111+
, manageHook = ewmhDesktopsManageHook' <> manageHook c }
108112

109113

110114
-- $customization
@@ -127,6 +131,8 @@ data EwmhDesktopsConfig =
127131
-- ^ manage @_NET_DESKTOP_VIEWPORT@?
128132
, hiddenWorkspaceToScreen :: WindowSet -> WindowSpace -> WindowScreen
129133
-- ^ map hidden workspaces to screens for @_NET_DESKTOP_VIEWPORT@
134+
, handleAboveBelowState :: Bool
135+
-- ^ handle @_NET_WM_STATE_ABOVE@ and @_NET_WM_STATE_BELOW@?
130136
}
131137

132138
instance Default EwmhDesktopsConfig where
@@ -139,6 +145,7 @@ instance Default EwmhDesktopsConfig where
139145
, manageDesktopViewport = True
140146
-- Hidden workspaces are mapped to the current screen by default.
141147
, hiddenWorkspaceToScreen = \winset _ -> W.current winset
148+
, handleAboveBelowState = False
142149
}
143150

144151

@@ -322,6 +329,65 @@ setEwmhFullscreenHooks f uf = XC.modifyDef $ \c -> c{ fullscreenHooks = (f, uf)
322329
disableEwmhManageDesktopViewport :: XConfig l -> XConfig l
323330
disableEwmhManageDesktopViewport = XC.modifyDef $ \c -> c{ manageDesktopViewport = False }
324331

332+
-- $customManageAboveBelowState
333+
-- Some applications use the @_NET_WM_STATE_ABOVE@ and @_NET_WM_STATE_BELOW@
334+
-- states to request being kept above or below other windows. By default, xmonad
335+
-- does not handle these states. To enable handling of these states, you can use
336+
-- the following hook:
337+
--
338+
-- > main = xmonad $ … . enableEwmhManageAboveBelowState . ewmh . … $ def{…}
339+
--
340+
-- This will make xmonad respond to requests to set these states by calling
341+
-- lowerWindow and raiseWindow respectively.
342+
enableEwmhManageAboveBelowState :: XConfig l -> XConfig l
343+
enableEwmhManageAboveBelowState = XC.modifyDef (\c -> c{handleAboveBelowState = True})
344+
345+
aboveBelowManageHook :: ManageHook
346+
aboveBelowManageHook =
347+
((isEnabled <&&> isInProperty "_NET_WM_STATE" "_NET_WM_STATE_BELOW") --> doLower)
348+
<> ((isEnabled <&&> isInProperty "_NET_WM_STATE" "_NET_WM_STATE_ABOVE") --> doRaise)
349+
where
350+
isEnabled = liftX (XC.withDef (pure . handleAboveBelowState))
351+
352+
aboveBelowEventHook :: Event -> X ()
353+
aboveBelowEventHook
354+
ClientMessageEvent{ev_event_display = dpy, ev_window = w, ev_message_type = typ, ev_data = action : dats} =
355+
do
356+
wmstate <- getAtom "_NET_WM_STATE"
357+
above <- getAtom "_NET_WM_STATE_ABOVE"
358+
below <- getAtom "_NET_WM_STATE_BELOW"
359+
360+
wstate <- fromMaybe [] <$> getProp32 wmstate w
361+
362+
let isAbove = fi above `elem` wstate
363+
isBelow = fi below `elem` wstate
364+
chWstate f = io $ changeProperty32 dpy w wmstate aTOM propModeReplace (f wstate)
365+
raise = chWstate (filter (/= fi below) . (fi above :)) >> io (raiseWindow dpy w)
366+
lower = chWstate (filter (/= fi above) . (fi below :)) >> io (lowerWindow dpy w)
367+
clear st = chWstate (filter (/= fi st))
368+
when (typ == wmstate) $
369+
if
370+
-- remove
371+
| action == 0 -> do
372+
when (fi above `elem` dats && isAbove) $ clear above
373+
when (fi below `elem` dats && isBelow) $ clear below
374+
-- add
375+
| action == 1 -> do
376+
when (fi above `elem` dats && not isAbove) raise
377+
when (fi below `elem` dats && not isBelow) lower
378+
-- toggle
379+
| action == 2 -> do
380+
when (fi above `elem` dats) $
381+
if isAbove
382+
then clear above
383+
else raise
384+
when (fi below `elem` dats) $
385+
if isBelow
386+
then clear below
387+
else lower
388+
| otherwise -> trace ("Bad _NET_WM_STATE with data =" <> show action)
389+
mempty
390+
aboveBelowEventHook _ = mempty
325391

326392
-- $customHiddenWorkspaceMapper
327393
--
@@ -354,7 +420,7 @@ setEwmhHiddenWorkspaceToScreenMapping mapper = XC.modifyDef $ \c -> c{ hiddenWor
354420
-- | Initializes EwmhDesktops and advertises EWMH support to the X server.
355421
{-# DEPRECATED ewmhDesktopsStartup "Use ewmh instead." #-}
356422
ewmhDesktopsStartup :: X ()
357-
ewmhDesktopsStartup = setSupported
423+
ewmhDesktopsStartup = setSupported >> XC.withDef ewmhDesktopsStartupHook'
358424

359425
-- | Notifies pagers and window lists, such as those in the gnome-panel of the
360426
-- current state of workspaces and windows.
@@ -369,6 +435,12 @@ ewmhDesktopsLogHookCustom :: WorkspaceSort -> X ()
369435
ewmhDesktopsLogHookCustom f =
370436
ewmhDesktopsLogHook' def{ workspaceSort = (f .) <$> workspaceSort def }
371437

438+
-- | Manage hook that EWMH extensions can hook into. Should be named
439+
-- ewmhDesktopsManageHook for consistency with ewmhDesktopsLogHook for example,
440+
-- but that name is taken.
441+
ewmhDesktopsManageHook' :: ManageHook
442+
ewmhDesktopsManageHook' = aboveBelowManageHook
443+
372444
-- | Intercepts messages from pagers and similar applications and reacts on them.
373445
--
374446
-- Currently supports:
@@ -424,6 +496,12 @@ instance ExtensionClass MonitorTags where initialValue = MonitorTags []
424496
whenChanged :: (Eq a, ExtensionClass a) => a -> X () -> X ()
425497
whenChanged = whenX . XS.modified . const
426498

499+
ewmhDesktopsStartupHook' :: EwmhDesktopsConfig -> X ()
500+
ewmhDesktopsStartupHook' EwmhDesktopsConfig{handleAboveBelowState} =
501+
when
502+
handleAboveBelowState
503+
(addSupported ["_NET_WM_STATE", "_NET_WM_STATE_ABOVE", "_NET_WM_STATE_BELOW"])
504+
427505
ewmhDesktopsLogHook' :: EwmhDesktopsConfig -> X ()
428506
ewmhDesktopsLogHook' EwmhDesktopsConfig{workspaceSort, workspaceRename, manageDesktopViewport, hiddenWorkspaceToScreen} = withWindowSet $ \s -> do
429507
sort' <- workspaceSort
@@ -517,8 +595,8 @@ mkViewPorts winset hiddenWorkspaceMapper = setDesktopViewport . concat . mapMayb
517595

518596
ewmhDesktopsEventHook' :: Event -> EwmhDesktopsConfig -> X All
519597
ewmhDesktopsEventHook'
520-
ClientMessageEvent{ev_window = w, ev_message_type = mt, ev_data = d}
521-
EwmhDesktopsConfig{workspaceSort, activateHook, switchDesktopHook} =
598+
e@ClientMessageEvent{ev_window = w, ev_message_type = mt, ev_data = d}
599+
EwmhDesktopsConfig{workspaceSort, activateHook, switchDesktopHook, handleAboveBelowState} =
522600
withWindowSet $ \s -> do
523601
sort' <- workspaceSort
524602
let ws = sort' $ W.workspaces s
@@ -553,7 +631,7 @@ ewmhDesktopsEventHook'
553631
-- The Message is unknown to us, but that is ok, not all are meant
554632
-- to be handled by the window manager
555633
mempty
556-
634+
when handleAboveBelowState (aboveBelowEventHook e)
557635
mempty
558636
ewmhDesktopsEventHook' _ _ = mempty
559637

0 commit comments

Comments
 (0)