{-# LANGUAGE NamedFieldPuns #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  XMonad.Hooks.WindowSwallowing
-- Description :  Temporarily hide parent windows when opening other programs.
-- Copyright   :  (c) 2020 Leon Kowarschick
-- License     :  BSD3-style (see LICENSE)
--
-- Maintainer  :  Leon Kowarschick. <thereal.elkowar@gmail.com>
-- Stability   :  unstable
-- Portability :  unportable
--
-- Provides a handleEventHook that implements window swallowing.
--
-- If you open a GUI-window (i.e. feh) from the terminal,
-- the terminal will normally still be shown on screen, unnecessarily
-- taking up space on the screen.
-- With window swallowing, can detect that you opened a window from within another
-- window, and allows you "swallow" that parent window for the time the new
-- window is running.
--
-- __NOTE__: This module depends on @pstree@ to analyze the process hierarchy, so make
-- sure that is on your @$PATH@.
--
-- __NOTE__ that this does not always work perfectly:
--
-- - Because window swallowing needs to check the process hierarchy, it requires
--   both the child and the parent to be distinct processes. This means that
--   applications which implement instance sharing cannot be supported by window swallowing.
--   Most notably, this excludes some terminal emulators as well as tmux
--   from functioning as the parent process. It also excludes a good amount of
--   child programs, because many graphical applications do implement instance sharing.
--   For example, window swallowing will probably not work with your browser.
--
-- - To check the process hierarchy, we need to be able to get the process ID
--   by looking at the window. This requires the @_NET_WM_PID@ X-property to be set.
--   If any application you want to use this with does not provide the @_NET_WM_PID@,
--   there is not much you can do except for reaching out to the author of that
--   application and asking them to set that property.
-----------------------------------------------------------------------------
module XMonad.Hooks.WindowSwallowing
  ( -- * Usage
    -- $usage
    swallowEventHook, swallowEventHookSub
  )
where
import           XMonad
import           XMonad.Prelude
import qualified XMonad.StackSet               as W
import           XMonad.Layout.SubLayouts
import qualified XMonad.Util.ExtensibleState   as XS
import           XMonad.Util.WindowProperties
import           XMonad.Util.Run                ( runProcessWithInput )
import qualified Data.Map.Strict               as M

-- $usage
-- You can use this module by including  the following in your @~\/.xmonad/xmonad.hs@:
--
-- > import XMonad.Hooks.WindowSwallowing
--
-- and using 'swallowEventHook' somewhere in your 'handleEventHook', for example:
--
-- > 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,
-- see "XMonad.Doc.Extending".

-- | Run @action@ iff both parent- and child queries match and the child
-- is a child by PID.
--
-- A 'MapRequestEvent' 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.
handleMapRequestEvent :: Query Bool -> Query Bool -> Window -> (Window -> X ()) -> X ()
handleMapRequestEvent :: Query Bool -> Query Bool -> Window -> (Window -> X ()) -> X ()
handleMapRequestEvent Query Bool
parentQ Query Bool
childQ Window
childWindow Window -> X ()
action =
  -- 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
  -- the currently focused window.
  (Window -> X ()) -> X ()
withFocused ((Window -> X ()) -> X ()) -> (Window -> X ()) -> X ()
forall a b. (a -> b) -> a -> b
$ \Window
parentWindow -> do
    -- First verify that both windows match the given queries
    Bool
parentMatches <- Query Bool -> Window -> X Bool
forall a. Query a -> Window -> X a
runQuery Query Bool
parentQ Window
parentWindow
    Bool
childMatches  <- Query Bool -> Window -> X Bool
forall a. Query a -> Window -> X a
runQuery Query Bool
childQ Window
childWindow
    Bool -> X () -> X ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Bool
parentMatches Bool -> Bool -> Bool
&& Bool
childMatches) (X () -> X ()) -> X () -> X ()
forall a b. (a -> b) -> a -> b
$ do
      -- read the windows _NET_WM_PID properties
      Maybe [CLong]
childWindowPid  <- String -> Window -> X (Maybe [CLong])
getProp32s String
"_NET_WM_PID" Window
childWindow
      Maybe [CLong]
parentWindowPid <- String -> Window -> X (Maybe [CLong])
getProp32s String
"_NET_WM_PID" Window
parentWindow
      case (Maybe [CLong]
parentWindowPid, Maybe [CLong]
childWindowPid) of
        (Just (CLong
parentPid : [CLong]
_), Just (CLong
childPid : [CLong]
_)) -> do
          -- check if the new window is a child process of the last focused window
          -- using the process ids.
          Bool
isChild <- IO Bool -> X Bool
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO Bool -> X Bool) -> IO Bool -> X Bool
forall a b. (a -> b) -> a -> b
$ CLong -> Int
forall a b. (Integral a, Num b) => a -> b
fi CLong
childPid Int -> Int -> IO Bool
`isChildOf` CLong -> Int
forall a b. (Integral a, Num b) => a -> b
fi CLong
parentPid
          Bool -> X () -> X ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when Bool
isChild (X () -> X ()) -> X () -> X ()
forall a b. (a -> b) -> a -> b
$ do
            Window -> X ()
action Window
parentWindow
        (Maybe [CLong], Maybe [CLong])
_ -> () -> X ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
      () -> X ()
forall (m :: * -> *) a. Monad m => a -> m a
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 :: Query Bool -> Query Bool -> Event -> X All
swallowEventHookSub Query Bool
parentQ Query Bool
childQ Event
event =
  Bool -> All
All Bool
True All -> X () -> X All
forall (f :: * -> *) a b. Functor f => a -> f b -> f a
<$ case Event
event of
    MapRequestEvent{ev_window :: Event -> Window
ev_window=Window
childWindow} ->
      Query Bool -> Query Bool -> Window -> (Window -> X ()) -> X ()
handleMapRequestEvent Query Bool
parentQ Query Bool
childQ Window
childWindow ((Window -> X ()) -> X ()) -> (Window -> X ()) -> X ()
forall a b. (a -> b) -> a -> b
$ \Window
parentWindow -> do
        Window -> X ()
manage Window
childWindow
        GroupMsg Window -> X ()
forall a. Message a => a -> X ()
sendMessage (Window -> Window -> GroupMsg Window
forall a. a -> a -> GroupMsg a
Merge Window
parentWindow Window
childWindow)
    Event
_ -> () -> X ()
forall (f :: * -> *) a. Applicative f => a -> f a
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 :: Query Bool -> Query Bool -> Event -> X All
swallowEventHook Query Bool
parentQ Query Bool
childQ Event
event = do
  case Event
event of
    MapRequestEvent{ev_window :: Event -> Window
ev_window=Window
childWindow} ->
      Query Bool -> Query Bool -> Window -> (Window -> X ()) -> X ()
handleMapRequestEvent Query Bool
parentQ Query Bool
childQ Window
childWindow ((Window -> X ()) -> X ()) -> (Window -> X ()) -> X ()
forall a b. (a -> b) -> a -> b
$ \Window
parentWindow -> do
        -- 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,
        -- such that it shows up at the same position, with the same dimensions.
        (WindowSet -> WindowSet) -> X ()
windows
          ( (Stack Window -> Stack Window) -> WindowSet -> WindowSet
forall a i l s sd.
(Stack a -> Stack a) -> StackSet i l a s sd -> StackSet i l a s sd
W.modify' (\Stack Window
x -> Stack Window
x { focus :: Window
W.focus = Window
childWindow })
          (WindowSet -> WindowSet)
-> (WindowSet -> WindowSet) -> WindowSet -> WindowSet
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Window -> Window -> WindowSet -> WindowSet
forall a i l s sd.
Ord a =>
a -> a -> StackSet i l a s sd -> StackSet i l a s sd
moveFloatingState Window
parentWindow Window
childWindow
          )
        (SwallowingState -> SwallowingState) -> X ()
forall a (m :: * -> *).
(ExtensionClass a, XLike m) =>
(a -> a) -> m ()
XS.modify (Window -> Window -> SwallowingState -> SwallowingState
addSwallowedParent Window
parentWindow Window
childWindow)

    -- This is called in many circumstances, most notably for us:
    -- right before a window gets closed. We store the current
    -- state of the window stack here, such that we know where the
    -- child window was on the screen when restoring the swallowed parent process.
    ConfigureEvent{} -> (WindowSet -> X ()) -> X ()
forall a. (WindowSet -> X a) -> X a
withWindowSet ((WindowSet -> X ()) -> X ()) -> (WindowSet -> X ()) -> X ()
forall a b. (a -> b) -> a -> b
$ \WindowSet
ws -> do
      (SwallowingState -> SwallowingState) -> X ()
forall a (m :: * -> *).
(ExtensionClass a, XLike m) =>
(a -> a) -> m ()
XS.modify ((SwallowingState -> SwallowingState) -> X ())
-> (WindowSet -> SwallowingState -> SwallowingState)
-> WindowSet
-> X ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Maybe (Stack Window) -> SwallowingState -> SwallowingState
setStackBeforeWindowClosing (Maybe (Stack Window) -> SwallowingState -> SwallowingState)
-> (WindowSet -> Maybe (Stack Window))
-> WindowSet
-> SwallowingState
-> SwallowingState
forall b c a. (b -> c) -> (a -> b) -> a -> c
. WindowSet -> Maybe (Stack Window)
forall i l a sid sd. StackSet i l a sid sd -> Maybe (Stack a)
currentStack (WindowSet -> X ()) -> WindowSet -> X ()
forall a b. (a -> b) -> a -> b
$ WindowSet
ws
      (SwallowingState -> SwallowingState) -> X ()
forall a (m :: * -> *).
(ExtensionClass a, XLike m) =>
(a -> a) -> m ()
XS.modify ((SwallowingState -> SwallowingState) -> X ())
-> (WindowSet -> SwallowingState -> SwallowingState)
-> WindowSet
-> X ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Map Window RationalRect -> SwallowingState -> SwallowingState
setFloatingBeforeWindowClosing (Map Window RationalRect -> SwallowingState -> SwallowingState)
-> (WindowSet -> Map Window RationalRect)
-> WindowSet
-> SwallowingState
-> SwallowingState
forall b c a. (b -> c) -> (a -> b) -> a -> c
. WindowSet -> Map Window RationalRect
forall i l a sid sd. StackSet i l a sid sd -> Map a RationalRect
W.floating (WindowSet -> X ()) -> WindowSet -> X ()
forall a b. (a -> b) -> a -> b
$ WindowSet
ws

    -- This is called right after any window closes.
    DestroyWindowEvent { ev_event :: Event -> Window
ev_event = Window
eventId, ev_window :: Event -> Window
ev_window = Window
childWindow } ->
      -- Because DestroyWindowEvent is emitted a lot more often then you think,
      -- this check verifies that the event is /actually/ about closing a window.
      Bool -> X () -> X ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Window
eventId Window -> Window -> Bool
forall a. Eq a => a -> a -> Bool
== Window
childWindow) (X () -> X ()) -> X () -> X ()
forall a b. (a -> b) -> a -> b
$ do
        -- we get some data from the extensible state, most notably we ask for
        -- the \"parent\" window of the now closed window.
        Maybe Window
maybeSwallowedParent <- (SwallowingState -> Maybe Window) -> X (Maybe Window)
forall a (m :: * -> *) b.
(ExtensionClass a, XLike m) =>
(a -> b) -> m b
XS.gets (Window -> SwallowingState -> Maybe Window
getSwallowedParent Window
childWindow)
        Maybe (Stack Window)
maybeOldStack        <- (SwallowingState -> Maybe (Stack Window))
-> X (Maybe (Stack Window))
forall a (m :: * -> *) b.
(ExtensionClass a, XLike m) =>
(a -> b) -> m b
XS.gets SwallowingState -> Maybe (Stack Window)
stackBeforeWindowClosing
        Map Window RationalRect
oldFloating          <- (SwallowingState -> Map Window RationalRect)
-> X (Map Window RationalRect)
forall a (m :: * -> *) b.
(ExtensionClass a, XLike m) =>
(a -> b) -> m b
XS.gets SwallowingState -> Map Window RationalRect
floatingBeforeClosing
        case (Maybe Window
maybeSwallowedParent, Maybe (Stack Window)
maybeOldStack) of
          -- If there actually is a corresponding swallowed parent window for this window,
          -- we will try to restore it.
          -- Because there are some cases where the stack-state is not stored correctly in the ConfigureEvent hook,
          -- we have to first check if the stack-state is valid.
          -- If it is, we can restore the parent exactly where the child window was before being closed.
          -- If the stored stack-state is invalid however, we still restore the window
          -- by just inserting it as the focused window in the stack.
          --
          -- After restoring, we remove the information about the swallowing from the state.
          (Just Window
parent, Maybe (Stack Window)
Nothing) -> do
            (WindowSet -> WindowSet) -> X ()
windows (Window -> WindowSet -> WindowSet
forall a i l sid sd.
a -> StackSet i l a sid sd -> StackSet i l a sid sd
insertIntoStack Window
parent)
            Window -> X ()
deleteState Window
childWindow
          (Just Window
parent, Just Stack Window
oldStack) -> do
            Bool
stackStoredCorrectly <- do
              Maybe (Stack Window)
curStack <- (WindowSet -> X (Maybe (Stack Window))) -> X (Maybe (Stack Window))
forall a. (WindowSet -> X a) -> X a
withWindowSet (Maybe (Stack Window) -> X (Maybe (Stack Window))
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe (Stack Window) -> X (Maybe (Stack Window)))
-> (WindowSet -> Maybe (Stack Window))
-> WindowSet
-> X (Maybe (Stack Window))
forall b c a. (b -> c) -> (a -> b) -> a -> c
. WindowSet -> Maybe (Stack Window)
forall i l a sid sd. StackSet i l a sid sd -> Maybe (Stack a)
currentStack)
              let oldLen :: Int
oldLen = [Window] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length (Stack Window -> [Window]
forall a. Stack a -> [a]
W.integrate Stack Window
oldStack)
              let curLen :: Int
curLen = [Window] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length (Maybe (Stack Window) -> [Window]
forall a. Maybe (Stack a) -> [a]
W.integrate' Maybe (Stack Window)
curStack)
              Bool -> X Bool
forall (m :: * -> *) a. Monad m => a -> m a
return (Int
oldLen Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1 Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
curLen Bool -> Bool -> Bool
&& Window
childWindow Window -> Window -> Bool
forall a. Eq a => a -> a -> Bool
== Stack Window -> Window
forall a. Stack a -> a
W.focus Stack Window
oldStack)

            if Bool
stackStoredCorrectly
              then (WindowSet -> WindowSet) -> X ()
windows
                (\WindowSet
ws ->
                  (Maybe (Stack Window) -> Maybe (Stack Window))
-> WindowSet -> WindowSet
forall a i l sid sd.
(Maybe (Stack a) -> Maybe (Stack a))
-> StackSet i l a sid sd -> StackSet i l a sid sd
updateCurrentStack
                      (Maybe (Stack Window)
-> Maybe (Stack Window) -> Maybe (Stack Window)
forall a b. a -> b -> a
const (Maybe (Stack Window)
 -> Maybe (Stack Window) -> Maybe (Stack Window))
-> Maybe (Stack Window)
-> Maybe (Stack Window)
-> Maybe (Stack Window)
forall a b. (a -> b) -> a -> b
$ Stack Window -> Maybe (Stack Window)
forall a. a -> Maybe a
Just (Stack Window -> Maybe (Stack Window))
-> Stack Window -> Maybe (Stack Window)
forall a b. (a -> b) -> a -> b
$ Stack Window
oldStack { focus :: Window
W.focus = Window
parent })
                    (WindowSet -> WindowSet) -> WindowSet -> WindowSet
forall a b. (a -> b) -> a -> b
$ Window -> Window -> WindowSet -> WindowSet
forall a i l s sd.
Ord a =>
a -> a -> StackSet i l a s sd -> StackSet i l a s sd
moveFloatingState Window
childWindow Window
parent
                    (WindowSet -> WindowSet) -> WindowSet -> WindowSet
forall a b. (a -> b) -> a -> b
$ WindowSet
ws { floating :: Map Window RationalRect
W.floating = Map Window RationalRect
oldFloating }
                )
              else (WindowSet -> WindowSet) -> X ()
windows (Window -> WindowSet -> WindowSet
forall a i l sid sd.
a -> StackSet i l a sid sd -> StackSet i l a sid sd
insertIntoStack Window
parent)
            Window -> X ()
deleteState Window
childWindow
          (Maybe Window, Maybe (Stack Window))
_ -> () -> X ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    Event
_ -> () -> X ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  All -> X All
forall (m :: * -> *) a. Monad m => a -> m a
return (All -> X All) -> All -> X All
forall a b. (a -> b) -> a -> b
$ Bool -> All
All Bool
True
 where
  deleteState :: Window -> X ()
  deleteState :: Window -> X ()
deleteState Window
childWindow = do
    (SwallowingState -> SwallowingState) -> X ()
forall a (m :: * -> *).
(ExtensionClass a, XLike m) =>
(a -> a) -> m ()
XS.modify ((SwallowingState -> SwallowingState) -> X ())
-> (SwallowingState -> SwallowingState) -> X ()
forall a b. (a -> b) -> a -> b
$ Window -> SwallowingState -> SwallowingState
removeSwallowed Window
childWindow
    (SwallowingState -> SwallowingState) -> X ()
forall a (m :: * -> *).
(ExtensionClass a, XLike m) =>
(a -> a) -> m ()
XS.modify ((SwallowingState -> SwallowingState) -> X ())
-> (SwallowingState -> SwallowingState) -> X ()
forall a b. (a -> b) -> a -> b
$ Maybe (Stack Window) -> SwallowingState -> SwallowingState
setStackBeforeWindowClosing Maybe (Stack Window)
forall a. Maybe a
Nothing

-- | 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 -> StackSet i l a sid sd -> StackSet i l a sid sd
insertIntoStack a
win = Maybe (Stack a)
-> (Stack a -> Maybe (Stack a))
-> StackSet i l a sid sd
-> StackSet i l a sid sd
forall a i l s sd.
Maybe (Stack a)
-> (Stack a -> Maybe (Stack a))
-> StackSet i l a s sd
-> StackSet i l a s sd
W.modify
  (Stack a -> Maybe (Stack a)
forall a. a -> Maybe a
Just (Stack a -> Maybe (Stack a)) -> Stack a -> Maybe (Stack a)
forall a b. (a -> b) -> a -> b
$ a -> [a] -> [a] -> Stack a
forall a. a -> [a] -> [a] -> Stack a
W.Stack a
win [] [])
  (\Stack a
s -> Stack a -> Maybe (Stack a)
forall a. a -> Maybe a
Just (Stack a -> Maybe (Stack a)) -> Stack a -> Maybe (Stack a)
forall a b. (a -> b) -> a -> b
$ Stack a
s { focus :: a
W.focus = a
win, down :: [a]
W.down = Stack a -> a
forall a. Stack a -> a
W.focus Stack a
s a -> [a] -> [a]
forall a. a -> [a] -> [a]
: Stack a -> [a]
forall a. Stack a -> [a]
W.down Stack a
s })

-- | run a pure transformation on the Stack of the currently focused workspace.
updateCurrentStack
  :: (Maybe (W.Stack a) -> Maybe (W.Stack a))
  -> W.StackSet i l a sid sd
  -> W.StackSet i l a sid sd
updateCurrentStack :: (Maybe (Stack a) -> Maybe (Stack a))
-> StackSet i l a sid sd -> StackSet i l a sid sd
updateCurrentStack Maybe (Stack a) -> Maybe (Stack a)
f = Maybe (Stack a)
-> (Stack a -> Maybe (Stack a))
-> StackSet i l a sid sd
-> StackSet i l a sid sd
forall a i l s sd.
Maybe (Stack a)
-> (Stack a -> Maybe (Stack a))
-> StackSet i l a s sd
-> StackSet i l a s sd
W.modify (Maybe (Stack a) -> Maybe (Stack a)
f Maybe (Stack a)
forall a. Maybe a
Nothing) (Maybe (Stack a) -> Maybe (Stack a)
f (Maybe (Stack a) -> Maybe (Stack a))
-> (Stack a -> Maybe (Stack a)) -> Stack a -> Maybe (Stack a)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Stack a -> Maybe (Stack a)
forall a. a -> Maybe a
Just)

currentStack :: W.StackSet i l a sid sd -> Maybe (W.Stack a)
currentStack :: StackSet i l a sid sd -> Maybe (Stack a)
currentStack = Workspace i l a -> Maybe (Stack a)
forall i l a. Workspace i l a -> Maybe (Stack a)
W.stack (Workspace i l a -> Maybe (Stack a))
-> (StackSet i l a sid sd -> Workspace i l a)
-> StackSet i l a sid sd
-> Maybe (Stack a)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Screen i l a sid sd -> Workspace i l a
forall i l a sid sd. Screen i l a sid sd -> Workspace i l a
W.workspace (Screen i l a sid sd -> Workspace i l a)
-> (StackSet i l a sid sd -> Screen i l a sid sd)
-> StackSet i l a sid sd
-> Workspace i l a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. StackSet i l a sid sd -> Screen i l a sid sd
forall i l a sid sd. StackSet i l a sid sd -> Screen i l a sid sd
W.current


-- | move the floating state from one window to another, sinking the original window
moveFloatingState
  :: Ord a
  => a -- ^ window to move from
  -> a -- ^ window to move to
  -> W.StackSet i l a s sd
  -> W.StackSet i l a s sd
moveFloatingState :: a -> a -> StackSet i l a s sd -> StackSet i l a s sd
moveFloatingState a
from a
to StackSet i l a s sd
ws = StackSet i l a s sd
ws
  { floating :: Map a RationalRect
W.floating = a -> Map a RationalRect -> Map a RationalRect
forall k a. Ord k => k -> Map k a -> Map k a
M.delete a
from (Map a RationalRect -> Map a RationalRect)
-> Map a RationalRect -> Map a RationalRect
forall a b. (a -> b) -> a -> b
$ Map a RationalRect
-> (RationalRect -> Map a RationalRect)
-> Maybe RationalRect
-> Map a RationalRect
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (a -> Map a RationalRect -> Map a RationalRect
forall k a. Ord k => k -> Map k a -> Map k a
M.delete a
to (StackSet i l a s sd -> Map a RationalRect
forall i l a sid sd. StackSet i l a sid sd -> Map a RationalRect
W.floating StackSet i l a s sd
ws))
                                       (\RationalRect
r -> a -> RationalRect -> Map a RationalRect -> Map a RationalRect
forall k a. Ord k => k -> a -> Map k a -> Map k a
M.insert a
to RationalRect
r (StackSet i l a s sd -> Map a RationalRect
forall i l a sid sd. StackSet i l a sid sd -> Map a RationalRect
W.floating StackSet i l a s sd
ws))
                                       (a -> Map a RationalRect -> Maybe RationalRect
forall k a. Ord k => k -> Map k a -> Maybe a
M.lookup a
from (StackSet i l a s sd -> Map a RationalRect
forall i l a sid sd. StackSet i l a sid sd -> Map a RationalRect
W.floating StackSet i l a s sd
ws))
  }

-- | 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.
isChildOf
  :: Int -- ^ child PID
  -> Int -- ^ parent PID
  -> IO Bool
isChildOf :: Int -> Int -> IO Bool
isChildOf Int
child Int
parent = do
  String
output <- String -> [String] -> String -> IO String
forall (m :: * -> *).
MonadIO m =>
String -> [String] -> String -> m String
runProcessWithInput String
"pstree" [String
"-T", String
"-p", Int -> String
forall a. Show a => a -> String
show Int
parent] String
""
  Bool -> IO Bool
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> IO Bool) -> Bool -> IO Bool
forall a b. (a -> b) -> a -> b
$ (String -> Bool) -> [String] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Int -> String
forall a. Show a => a -> String
show Int
child String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf`) ([String] -> Bool) -> [String] -> Bool
forall a b. (a -> b) -> a -> b
$ String -> [String]
lines String
output

data SwallowingState =
  SwallowingState
    { SwallowingState -> Map Window Window
currentlySwallowed       :: M.Map Window Window         -- ^ mapping from child window window to the currently swallowed parent window
    , SwallowingState -> Maybe (Stack Window)
stackBeforeWindowClosing :: Maybe (W.Stack Window)      -- ^ current stack state right before DestroyWindowEvent is sent
    , SwallowingState -> Map Window RationalRect
floatingBeforeClosing    :: M.Map Window W.RationalRect -- ^ floating map of the stackset right before DestroyWindowEvent is sent
    } deriving (Int -> SwallowingState -> ShowS
[SwallowingState] -> ShowS
SwallowingState -> String
(Int -> SwallowingState -> ShowS)
-> (SwallowingState -> String)
-> ([SwallowingState] -> ShowS)
-> Show SwallowingState
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [SwallowingState] -> ShowS
$cshowList :: [SwallowingState] -> ShowS
show :: SwallowingState -> String
$cshow :: SwallowingState -> String
showsPrec :: Int -> SwallowingState -> ShowS
$cshowsPrec :: Int -> SwallowingState -> ShowS
Show)

getSwallowedParent :: Window -> SwallowingState -> Maybe Window
getSwallowedParent :: Window -> SwallowingState -> Maybe Window
getSwallowedParent Window
win SwallowingState { Map Window Window
currentlySwallowed :: Map Window Window
currentlySwallowed :: SwallowingState -> Map Window Window
currentlySwallowed } =
  Window -> Map Window Window -> Maybe Window
forall k a. Ord k => k -> Map k a -> Maybe a
M.lookup Window
win Map Window Window
currentlySwallowed

addSwallowedParent :: Window -> Window -> SwallowingState -> SwallowingState
addSwallowedParent :: Window -> Window -> SwallowingState -> SwallowingState
addSwallowedParent Window
parent Window
child s :: SwallowingState
s@SwallowingState { Map Window Window
currentlySwallowed :: Map Window Window
currentlySwallowed :: SwallowingState -> Map Window Window
currentlySwallowed } =
  SwallowingState
s { currentlySwallowed :: Map Window Window
currentlySwallowed = Window -> Window -> Map Window Window -> Map Window Window
forall k a. Ord k => k -> a -> Map k a -> Map k a
M.insert Window
child Window
parent Map Window Window
currentlySwallowed }

removeSwallowed :: Window -> SwallowingState -> SwallowingState
removeSwallowed :: Window -> SwallowingState -> SwallowingState
removeSwallowed Window
child s :: SwallowingState
s@SwallowingState { Map Window Window
currentlySwallowed :: Map Window Window
currentlySwallowed :: SwallowingState -> Map Window Window
currentlySwallowed } =
  SwallowingState
s { currentlySwallowed :: Map Window Window
currentlySwallowed = Window -> Map Window Window -> Map Window Window
forall k a. Ord k => k -> Map k a -> Map k a
M.delete Window
child Map Window Window
currentlySwallowed }

setStackBeforeWindowClosing
  :: Maybe (W.Stack Window) -> SwallowingState -> SwallowingState
setStackBeforeWindowClosing :: Maybe (Stack Window) -> SwallowingState -> SwallowingState
setStackBeforeWindowClosing Maybe (Stack Window)
stack SwallowingState
s = SwallowingState
s { stackBeforeWindowClosing :: Maybe (Stack Window)
stackBeforeWindowClosing = Maybe (Stack Window)
stack }

setFloatingBeforeWindowClosing
  :: M.Map Window W.RationalRect -> SwallowingState -> SwallowingState
setFloatingBeforeWindowClosing :: Map Window RationalRect -> SwallowingState -> SwallowingState
setFloatingBeforeWindowClosing Map Window RationalRect
x SwallowingState
s = SwallowingState
s { floatingBeforeClosing :: Map Window RationalRect
floatingBeforeClosing = Map Window RationalRect
x }

instance ExtensionClass SwallowingState where
  initialValue :: SwallowingState
initialValue = SwallowingState :: Map Window Window
-> Maybe (Stack Window)
-> Map Window RationalRect
-> SwallowingState
SwallowingState { currentlySwallowed :: Map Window Window
currentlySwallowed       = Map Window Window
forall a. Monoid a => a
mempty
                                 , stackBeforeWindowClosing :: Maybe (Stack Window)
stackBeforeWindowClosing = Maybe (Stack Window)
forall a. Maybe a
Nothing
                                 , floatingBeforeClosing :: Map Window RationalRect
floatingBeforeClosing    = Map Window RationalRect
forall a. Monoid a => a
mempty
                                 }