X.H.WindowSwallowing: Implement SubLayout window "swallowing"

This implements window swallowing on top of SubLayouts; the matched
windows are simply tabbed together instead of one actually being
swallowed.  This provides an improved experience for people using
SubLayouts, as the parent window is still accessible.

Done as part of ZuriHac 2021.

Related: https://github.com/xmonad/xmonad-contrib/issues/416#issuecomment-777400194
This commit is contained in:
4caraml 2021-06-18 23:31:51 +02:00 committed by slotThe
parent 722967cb12
commit bbd972012e
3 changed files with 63 additions and 41 deletions

View File

@ -192,7 +192,7 @@
* `XMonad.Hooks.WindowSwallowing` * `XMonad.Hooks.WindowSwallowing`
A handleEventHook that implements window swallowing: HandleEventHooks that implement window swallowing or sublayouting:
Hide parent windows like terminals when opening other programs (like image viewers) from within them, Hide parent windows like terminals when opening other programs (like image viewers) from within them,
restoring them once the child application closes. restoring them once the child application closes.

View File

@ -598,7 +598,7 @@ Here is a list of the modules found in @XMonad.Hooks@:
Keeps track of workspace viewing order. Keeps track of workspace viewing order.
* "XMonad.Hooks.WindowSwallowing" * "XMonad.Hooks.WindowSwallowing"
A handleEventHook that implements window swallowing: handleEventHooks that implement window swallowing or sublayouting:
Hide parent windows like terminals when opening other programs (like image viewers) from within them, Hide parent windows like terminals when opening other programs (like image viewers) from within them,
restoring them once the child application closes. restoring them once the child application closes.

View File

@ -40,12 +40,13 @@
module XMonad.Hooks.WindowSwallowing module XMonad.Hooks.WindowSwallowing
( -- * Usage ( -- * Usage
-- $usage -- $usage
swallowEventHook swallowEventHook, swallowEventHookSub
) )
where where
import XMonad import XMonad
import XMonad.Prelude import XMonad.Prelude
import qualified XMonad.StackSet as W import qualified XMonad.StackSet as W
import XMonad.Layout.SubLayouts
import qualified XMonad.Util.ExtensibleState as XS import qualified XMonad.Util.ExtensibleState as XS
import XMonad.Util.WindowProperties import XMonad.Util.WindowProperties
import XMonad.Util.Run ( runProcessWithInput ) import XMonad.Util.Run ( runProcessWithInput )
@ -60,32 +61,27 @@ import qualified Data.Map.Strict as M
-- --
-- > myHandleEventHook = swallowEventHook (className =? "Alacritty" <||> className =? "Termite") (return True) -- > myHandleEventHook = swallowEventHook (className =? "Alacritty" <||> className =? "Termite") (return True)
-- --
-- The variant 'swallowEventHookSub' can be used if a layout from "XMonad.Layouts.SubLayouts" is used;
-- instead of swallowing the window it will merge the child window with the parent. (this does not work with floating windows)
--
-- For more information on editing your handleEventHook and key bindings, -- For more information on editing your handleEventHook and key bindings,
-- see "XMonad.Doc.Extending". -- see "XMonad.Doc.Extending".
-- | Run @action@ iff both parent- and child queries match and the child
-- | handleEventHook that will swallow child windows when they are -- is a child by PID.
-- opened from another window. --
swallowEventHook -- A 'MapRequestEvent' is called right before a window gets opened. We
:: Query Bool -- ^ query the parent window has to match for window swallowing to occur. -- intercept that call to possibly open the window ourselves, swapping
-- Set this to @return True@ to run swallowing for every parent. -- out it's parent processes window for the new window in the stack.
-> Query Bool -- ^ query the child window has to match for window swallowing to occur. handleMapRequestEvent :: Query Bool -> Query Bool -> Window -> (Window -> X ()) -> X ()
-- Set this to @return True@ to run swallowing for every child handleMapRequestEvent parentQ childQ childWindow action =
-> Event -- ^ The event to handle.
-> X All
swallowEventHook parentQueries childQueries event = do
case event of
-- This is called right before a window gets opened. We intercept that
-- call to possibly open the window ourselves, swapping out
-- it's parent processes window for the new window in the stack.
MapRequestEvent { ev_window = childWindow } ->
-- For a window to be opened from within another window, that other window -- For a window to be opened from within another window, that other window
-- must be focused. Thus the parent window that would be swallowed has to be -- must be focused. Thus the parent window that would be swallowed has to be
-- the currently focused window. -- the currently focused window.
withFocused $ \parentWindow -> do withFocused $ \parentWindow -> do
-- First verify that both windows match the given queries -- First verify that both windows match the given queries
parentMatches <- runQuery parentQueries parentWindow parentMatches <- runQuery parentQ parentWindow
childMatches <- runQuery childQueries childWindow childMatches <- runQuery childQ childWindow
when (parentMatches && childMatches) $ do when (parentMatches && childMatches) $ do
-- read the windows _NET_WM_PID properties -- read the windows _NET_WM_PID properties
childWindowPid <- getProp32s "_NET_WM_PID" childWindow childWindowPid <- getProp32s "_NET_WM_PID" childWindow
@ -96,18 +92,48 @@ swallowEventHook parentQueries childQueries event = do
-- using the process ids. -- using the process ids.
isChild <- liftIO $ fi childPid `isChildOf` fi parentPid isChild <- liftIO $ fi childPid `isChildOf` fi parentPid
when isChild $ do when isChild $ do
action parentWindow
_ -> return ()
return ()
-- | handleEventHook that will merge child windows via
-- "XMonad.Layouts.SubLayouts" when they are opened from another window.
swallowEventHookSub
:: Query Bool -- ^ query the parent window has to match for window swallowing to occur.
-- Set this to @return True@ to run swallowing for every parent.
-> Query Bool -- ^ query the child window has to match for window swallowing to occur.
-- Set this to @return True@ to run swallowing for every child
-> Event -- ^ The event to handle.
-> X All
swallowEventHookSub parentQ childQ event =
All True <$ case event of
MapRequestEvent{ev_window=childWindow} ->
handleMapRequestEvent parentQ childQ childWindow $ \parentWindow -> do
manage childWindow
sendMessage (Merge parentWindow childWindow)
_ -> pure ()
-- | handleEventHook that will swallow child windows when they are
-- opened from another window.
swallowEventHook
:: Query Bool -- ^ query the parent window has to match for window swallowing to occur.
-- Set this to @return True@ to run swallowing for every parent.
-> Query Bool -- ^ query the child window has to match for window swallowing to occur.
-- Set this to @return True@ to run swallowing for every child
-> Event -- ^ The event to handle.
-> X All
swallowEventHook parentQ childQ event = do
case event of
MapRequestEvent{ev_window=childWindow} ->
handleMapRequestEvent parentQ childQ childWindow $ \parentWindow -> do
-- We set the newly opened window as the focused window, replacing the parent window. -- We set the newly opened window as the focused window, replacing the parent window.
-- If the parent window was floating, we transfer that data to the child, -- If the parent window was floating, we transfer that data to the child,
-- such that it shows up at the same position, with the same dimensions. -- such that it shows up at the same position, with the same dimensions.
windows windows
( W.modify' (\x -> x { W.focus = childWindow }) ( W.modify' (\x -> x { W.focus = childWindow })
. moveFloatingState parentWindow childWindow . moveFloatingState parentWindow childWindow
) )
XS.modify (addSwallowedParent parentWindow childWindow) XS.modify (addSwallowedParent parentWindow childWindow)
_ -> return ()
return ()
-- This is called in many circumstances, most notably for us: -- This is called in many circumstances, most notably for us:
-- right before a window gets closed. We store the current -- right before a window gets closed. We store the current
@ -159,14 +185,12 @@ swallowEventHook parentQueries childQueries event = do
_ -> return () _ -> return ()
return $ All True return $ All True
-- | insert a window as focused into the current stack, moving the previously focused window down the stack -- | insert a window as focused into the current stack, moving the previously focused window down the stack
insertIntoStack :: a -> W.StackSet i l a sid sd -> W.StackSet i l a sid sd insertIntoStack :: a -> W.StackSet i l a sid sd -> W.StackSet i l a sid sd
insertIntoStack win = W.modify insertIntoStack win = W.modify
(Just $ W.Stack win [] []) (Just $ W.Stack win [] [])
(\s -> Just $ s { W.focus = win, W.down = W.focus s : W.down s }) (\s -> Just $ s { W.focus = win, W.down = W.focus s : W.down s })
-- | run a pure transformation on the Stack of the currently focused workspace. -- | run a pure transformation on the Stack of the currently focused workspace.
updateCurrentStack updateCurrentStack
:: (Maybe (W.Stack a) -> Maybe (W.Stack a)) :: (Maybe (W.Stack a) -> Maybe (W.Stack a))
@ -191,7 +215,6 @@ moveFloatingState from to ws = ws
(M.lookup from (W.floating ws)) (M.lookup from (W.floating ws))
} }
-- | check if a given process is a child of another process. This depends on "pstree" being in the PATH -- | check if a given process is a child of another process. This depends on "pstree" being in the PATH
-- NOTE: this does not work if the child process does any kind of process-sharing. -- NOTE: this does not work if the child process does any kind of process-sharing.
isChildOf isChildOf
@ -202,7 +225,6 @@ isChildOf child parent = do
output <- runProcessWithInput "pstree" ["-T", "-p", show parent] "" output <- runProcessWithInput "pstree" ["-T", "-p", show parent] ""
return $ any (show child `isInfixOf`) $ lines output return $ any (show child `isInfixOf`) $ lines output
data SwallowingState = data SwallowingState =
SwallowingState SwallowingState
{ currentlySwallowed :: M.Map Window Window -- ^ mapping from child window window to the currently swallowed parent window { currentlySwallowed :: M.Map Window Window -- ^ mapping from child window window to the currently swallowed parent window