@@ -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
104107ewmh :: XConfig a -> XConfig a
105108ewmh 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
132138instance 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)
322329disableEwmhManageDesktopViewport :: XConfig l -> XConfig l
323330disableEwmhManageDesktopViewport = 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." #-}
356422ewmhDesktopsStartup :: 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 ()
369435ewmhDesktopsLogHookCustom 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 []
424496whenChanged :: (Eq a , ExtensionClass a ) => a -> X () -> X ()
425497whenChanged = 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+
427505ewmhDesktopsLogHook' :: EwmhDesktopsConfig -> X ()
428506ewmhDesktopsLogHook' EwmhDesktopsConfig {workspaceSort, workspaceRename, manageDesktopViewport, hiddenWorkspaceToScreen} = withWindowSet $ \ s -> do
429507 sort' <- workspaceSort
@@ -517,8 +595,8 @@ mkViewPorts winset hiddenWorkspaceMapper = setDesktopViewport . concat . mapMayb
517595
518596ewmhDesktopsEventHook' :: Event -> EwmhDesktopsConfig -> X All
519597ewmhDesktopsEventHook'
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
558636ewmhDesktopsEventHook' _ _ = mempty
559637
0 commit comments