{-# LANGUAGE LambdaCase #-}
-----------------------------------------------------------------------------
-- |
-- Module       : XMonad.Hooks.ManageHelpers
-- Description  : Helper functions to be used in manageHook.
-- Copyright    : (c) Lukas Mai
-- License      : BSD
--
-- Maintainer   : Lukas Mai <l.mai@web.de>
-- Stability    : unstable
-- Portability  : unportable
--
-- This module provides helper functions to be used in @manageHook@. Here's
-- how you might use this:
--
-- > import XMonad.Hooks.ManageHelpers
-- > main =
-- >     xmonad def{
-- >         ...
-- >         manageHook = composeOne [
-- >             isKDETrayWindow -?> doIgnore,
-- >             transience,
-- >             isFullscreen -?> doFullFloat,
-- >             resource =? "stalonetray" -?> doIgnore
-- >         ],
-- >         ...
-- >     }
--
-- Here's how you can define more helpers like the ones from this module:
--
-- > -- some function you want to transform into an infix operator
-- > f :: a -> b -> Bool
-- >
-- > -- a new helper
-- > q ***? x = fmap (\a -> f a x) q   -- or: (\b -> f x b)
-- > -- or
-- > q ***? x = fmap (`f` x) q         -- or: (x `f`)
--
-- Any existing operator can be "lifted" in the same way:
--
-- > q ++? x = fmap (++ x) q

module XMonad.Hooks.ManageHelpers (
    Side(..),
    composeOne,
    (-?>), (/=?), (^?), (~?), ($?), (<==?), (</=?), (-->>), (-?>>),
    currentWs,
    windowTag,
    isInProperty,
    isKDETrayWindow,
    isFullscreen,
    isMinimized,
    isDialog,
    isNotification,
    pid,
    desktop,
    transientTo,
    maybeToDefinite,
    MaybeManageHook,
    transience,
    transience',
    clientLeader,
    sameBy,
    shiftToSame,
    shiftToSame',
    doRectFloat,
    doFullFloat,
    doCenterFloat,
    doSideFloat,
    doFloatAt,
    doFloatDep,
    doHideIgnore,
    doSink,
    doLower,
    doRaise,
    doFocus,
    Match,
) where

import XMonad
import XMonad.Prelude
import qualified XMonad.StackSet as W
import XMonad.Util.WindowProperties (getProp32s)

import System.Posix (ProcessID)

-- | Denotes a side of a screen. @S@ stands for South, @NE@ for Northeast
-- etc. @C@ stands for Center.
data Side = SC | NC | CE | CW | SE | SW | NE | NW | C
    deriving (ReadPrec [Side]
ReadPrec Side
Int -> ReadS Side
ReadS [Side]
(Int -> ReadS Side)
-> ReadS [Side] -> ReadPrec Side -> ReadPrec [Side] -> Read Side
forall a.
(Int -> ReadS a)
-> ReadS [a] -> ReadPrec a -> ReadPrec [a] -> Read a
$creadsPrec :: Int -> ReadS Side
readsPrec :: Int -> ReadS Side
$creadList :: ReadS [Side]
readList :: ReadS [Side]
$creadPrec :: ReadPrec Side
readPrec :: ReadPrec Side
$creadListPrec :: ReadPrec [Side]
readListPrec :: ReadPrec [Side]
Read, Int -> Side -> ShowS
[Side] -> ShowS
Side -> WorkspaceId
(Int -> Side -> ShowS)
-> (Side -> WorkspaceId) -> ([Side] -> ShowS) -> Show Side
forall a.
(Int -> a -> ShowS)
-> (a -> WorkspaceId) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Side -> ShowS
showsPrec :: Int -> Side -> ShowS
$cshow :: Side -> WorkspaceId
show :: Side -> WorkspaceId
$cshowList :: [Side] -> ShowS
showList :: [Side] -> ShowS
Show, Side -> Side -> Bool
(Side -> Side -> Bool) -> (Side -> Side -> Bool) -> Eq Side
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Side -> Side -> Bool
== :: Side -> Side -> Bool
$c/= :: Side -> Side -> Bool
/= :: Side -> Side -> Bool
Eq)

-- | A ManageHook that may or may not have been executed; the outcome is embedded in the Maybe
type MaybeManageHook = Query (Maybe (Endo WindowSet))
-- | A grouping type, which can hold the outcome of a predicate Query.
-- This is analogous to group types in regular expressions.
-- TODO: create a better API for aggregating multiple Matches logically
data Match a = Match Bool a

-- | An alternative 'ManageHook' composer. Unlike 'composeAll' it stops as soon as
-- a candidate returns a 'Just' value, effectively running only the first match
-- (whereas 'composeAll' continues and executes all matching rules).
composeOne :: (Monoid a, Monad m) => [m (Maybe a)] -> m a
composeOne :: forall a (m :: * -> *). (Monoid a, Monad m) => [m (Maybe a)] -> m a
composeOne = (m (Maybe a) -> m a -> m a) -> m a -> [m (Maybe a)] -> m a
forall a b. (a -> b -> b) -> b -> [a] -> b
forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr m (Maybe a) -> m a -> m a
forall {m :: * -> *} {b}. Monad m => m (Maybe b) -> m b -> m b
try (a -> m a
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return a
forall a. Monoid a => a
mempty)
    where
    try :: m (Maybe b) -> m b -> m b
try m (Maybe b)
q m b
z = do
        Maybe b
x <- m (Maybe b)
q
        m b -> (b -> m b) -> Maybe b -> m b
forall b a. b -> (a -> b) -> Maybe a -> b
maybe m b
z b -> m b
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe b
x

infixr 0 -?>, -->>, -?>>

-- | q \/=? x. if the result of q equals x, return False
(/=?) :: (Eq a, Functor m) => m a -> a -> m Bool
m a
q /=? :: forall a (m :: * -> *). (Eq a, Functor m) => m a -> a -> m Bool
/=? a
x = (a -> Bool) -> m a -> m Bool
forall a b. (a -> b) -> m a -> m b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (a -> a -> Bool
forall a. Eq a => a -> a -> Bool
/= a
x) m a
q

-- | q ^? x. if the result of @x `isPrefixOf` q@, return True
(^?) :: (Eq a, Functor m) => m [a] -> [a] -> m Bool
m [a]
q ^? :: forall a (m :: * -> *). (Eq a, Functor m) => m [a] -> [a] -> m Bool
^? [a]
x = ([a] -> Bool) -> m [a] -> m Bool
forall a b. (a -> b) -> m a -> m b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ([a]
x [a] -> [a] -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf`) m [a]
q

-- | q ~? x. if the result of @x `isInfixOf` q@, return True
(~?) :: (Eq a, Functor m) => m [a] -> [a] -> m Bool
m [a]
q ~? :: forall a (m :: * -> *). (Eq a, Functor m) => m [a] -> [a] -> m Bool
~? [a]
x = ([a] -> Bool) -> m [a] -> m Bool
forall a b. (a -> b) -> m a -> m b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ([a]
x [a] -> [a] -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf`) m [a]
q

-- | q $? x. if the result of @x `isSuffixOf` q@, return True
($?) :: (Eq a, Functor m) => m [a] -> [a] -> m Bool
m [a]
q $? :: forall a (m :: * -> *). (Eq a, Functor m) => m [a] -> [a] -> m Bool
$? [a]
x = ([a] -> Bool) -> m [a] -> m Bool
forall a b. (a -> b) -> m a -> m b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ([a]
x [a] -> [a] -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isSuffixOf`) m [a]
q

-- | q <==? x. if the result of q equals x, return True grouped with q
(<==?) :: (Eq a, Functor m) => m a -> a -> m (Match a)
m a
q <==? :: forall a (m :: * -> *).
(Eq a, Functor m) =>
m a -> a -> m (Match a)
<==? a
x = (a -> Match a) -> m a -> m (Match a)
forall a b. (a -> b) -> m a -> m b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (a -> a -> Match a
forall {a}. Eq a => a -> a -> Match a
`eq` a
x) m a
q
    where
    eq :: a -> a -> Match a
eq a
q' a
x' = Bool -> a -> Match a
forall a. Bool -> a -> Match a
Match (a
q' a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
x') a
q'

-- | q <\/=? x. if the result of q notequals x, return True grouped with q
(</=?) :: (Eq a, Functor m) => m a -> a -> m (Match a)
m a
q </=? :: forall a (m :: * -> *).
(Eq a, Functor m) =>
m a -> a -> m (Match a)
</=? a
x = (a -> Match a) -> m a -> m (Match a)
forall a b. (a -> b) -> m a -> m b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (a -> a -> Match a
forall {a}. Eq a => a -> a -> Match a
`neq` a
x) m a
q
    where
    neq :: a -> a -> Match a
neq a
q' a
x' = Bool -> a -> Match a
forall a. Bool -> a -> Match a
Match (a
q' a -> a -> Bool
forall a. Eq a => a -> a -> Bool
/= a
x') a
q'

-- | A helper operator for use in 'composeOne'. It takes a condition and an action;
-- if the condition fails, it returns 'Nothing' from the 'Query' so 'composeOne' will
-- go on and try the next rule.
(-?>) :: (Functor m, Monad m) => m Bool -> m a -> m (Maybe a)
m Bool
p -?> :: forall (m :: * -> *) a.
(Functor m, Monad m) =>
m Bool -> m a -> m (Maybe a)
-?> m a
f = do
    Bool
x <- m Bool
p
    if Bool
x then (a -> Maybe a) -> m a -> m (Maybe a)
forall a b. (a -> b) -> m a -> m b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap a -> Maybe a
forall a. a -> Maybe a
Just m a
f else Maybe a -> m (Maybe a)
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe a
forall a. Maybe a
Nothing

-- | A helper operator for use in 'composeAll'. It takes a condition and a function taking a grouped datum to action.  If 'p' is true, it executes the resulting action.
(-->>) :: (Monoid b, Monad m) => m (Match a) -> (a -> m b) -> m b
m (Match a)
p -->> :: forall b (m :: * -> *) a.
(Monoid b, Monad m) =>
m (Match a) -> (a -> m b) -> m b
-->> a -> m b
f = do
    Match Bool
b a
m <- m (Match a)
p
    if Bool
b then a -> m b
f a
m else b -> m b
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return b
forall a. Monoid a => a
mempty

-- | A helper operator for use in 'composeOne'.  It takes a condition and a function taking a groupdatum to action.  If 'p' is true, it executes the resulting action.  If it fails, it returns 'Nothing' from the 'Query' so 'composeOne' will go on and try the next rule.
(-?>>) :: (Functor m, Monad m) => m (Match a) -> (a -> m b) -> m (Maybe b)
m (Match a)
p -?>> :: forall (m :: * -> *) a b.
(Functor m, Monad m) =>
m (Match a) -> (a -> m b) -> m (Maybe b)
-?>> a -> m b
f = do
    Match Bool
b a
m <- m (Match a)
p
    if Bool
b then (b -> Maybe b) -> m b -> m (Maybe b)
forall a b. (a -> b) -> m a -> m b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap  b -> Maybe b
forall a. a -> Maybe a
Just (a -> m b
f a
m) else Maybe b -> m (Maybe b)
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe b
forall a. Maybe a
Nothing

-- | Return the current workspace
currentWs :: Query WorkspaceId
currentWs :: Query WorkspaceId
currentWs = X WorkspaceId -> Query WorkspaceId
forall a. X a -> Query a
liftX ((WindowSet -> X WorkspaceId) -> X WorkspaceId
forall a. (WindowSet -> X a) -> X a
withWindowSet ((WindowSet -> X WorkspaceId) -> X WorkspaceId)
-> (WindowSet -> X WorkspaceId) -> X WorkspaceId
forall a b. (a -> b) -> a -> b
$ WorkspaceId -> X WorkspaceId
forall a. a -> X a
forall (m :: * -> *) a. Monad m => a -> m a
return (WorkspaceId -> X WorkspaceId)
-> (WindowSet -> WorkspaceId) -> WindowSet -> X WorkspaceId
forall b c a. (b -> c) -> (a -> b) -> a -> c
. WindowSet -> WorkspaceId
forall i l a s sd. StackSet i l a s sd -> i
W.currentTag)

-- | Return the workspace tag of a window, if already managed
windowTag :: Query (Maybe WorkspaceId)
windowTag :: Query (Maybe WorkspaceId)
windowTag = Query Window
forall r (m :: * -> *). MonadReader r m => m r
ask Query Window
-> (Window -> Query (Maybe WorkspaceId))
-> Query (Maybe WorkspaceId)
forall a b. Query a -> (a -> Query b) -> Query b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \Window
w -> X (Maybe WorkspaceId) -> Query (Maybe WorkspaceId)
forall a. X a -> Query a
liftX (X (Maybe WorkspaceId) -> Query (Maybe WorkspaceId))
-> X (Maybe WorkspaceId) -> Query (Maybe WorkspaceId)
forall a b. (a -> b) -> a -> b
$ (WindowSet -> X (Maybe WorkspaceId)) -> X (Maybe WorkspaceId)
forall a. (WindowSet -> X a) -> X a
withWindowSet ((WindowSet -> X (Maybe WorkspaceId)) -> X (Maybe WorkspaceId))
-> (WindowSet -> X (Maybe WorkspaceId)) -> X (Maybe WorkspaceId)
forall a b. (a -> b) -> a -> b
$ Maybe WorkspaceId -> X (Maybe WorkspaceId)
forall a. a -> X a
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe WorkspaceId -> X (Maybe WorkspaceId))
-> (WindowSet -> Maybe WorkspaceId)
-> WindowSet
-> X (Maybe WorkspaceId)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Window -> WindowSet -> Maybe WorkspaceId
forall a i l s sd. Eq a => a -> StackSet i l a s sd -> Maybe i
W.findTag Window
w

-- | A predicate to check whether a window is a KDE system tray icon.
isKDETrayWindow :: Query Bool
isKDETrayWindow :: Query Bool
isKDETrayWindow = Query Window
forall r (m :: * -> *). MonadReader r m => m r
ask Query Window -> (Window -> Query Bool) -> Query Bool
forall a b. Query a -> (a -> Query b) -> Query b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \Window
w -> X Bool -> Query Bool
forall a. X a -> Query a
liftX (X Bool -> Query Bool) -> X Bool -> Query Bool
forall a b. (a -> b) -> a -> b
$ do
    Maybe [CLong]
r <- WorkspaceId -> Window -> X (Maybe [CLong])
getProp32s WorkspaceId
"_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR" Window
w
    Bool -> X Bool
forall a. a -> X a
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> X Bool) -> Bool -> X Bool
forall a b. (a -> b) -> a -> b
$ case Maybe [CLong]
r of
        Just [CLong
_] -> Bool
True
        Maybe [CLong]
_ -> Bool
False

-- | Helper to check if a window property contains certain value.
isInProperty :: String -> String -> Query Bool
isInProperty :: WorkspaceId -> WorkspaceId -> Query Bool
isInProperty WorkspaceId
p WorkspaceId
v = Query Window
forall r (m :: * -> *). MonadReader r m => m r
ask Query Window -> (Window -> Query Bool) -> Query Bool
forall a b. Query a -> (a -> Query b) -> Query b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \Window
w -> X Bool -> Query Bool
forall a. X a -> Query a
liftX (X Bool -> Query Bool) -> X Bool -> Query Bool
forall a b. (a -> b) -> a -> b
$ do
    Window
va <- WorkspaceId -> X Window
getAtom WorkspaceId
v
    Maybe [CLong]
r <- WorkspaceId -> Window -> X (Maybe [CLong])
getProp32s WorkspaceId
p Window
w
    Bool -> X Bool
forall a. a -> X a
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> X Bool) -> Bool -> X Bool
forall a b. (a -> b) -> a -> b
$ case Maybe [CLong]
r of
        Just [CLong]
xs -> Window -> CLong
forall a b. (Integral a, Num b) => a -> b
fromIntegral Window
va CLong -> [CLong] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [CLong]
xs
        Maybe [CLong]
_ -> Bool
False

-- | A predicate to check whether a window wants to fill the whole screen.
-- See also 'doFullFloat'.
isFullscreen :: Query Bool
isFullscreen :: Query Bool
isFullscreen = WorkspaceId -> WorkspaceId -> Query Bool
isInProperty WorkspaceId
"_NET_WM_STATE" WorkspaceId
"_NET_WM_STATE_FULLSCREEN"

-- | A predicate to check whether a window is hidden (minimized).
-- See also "XMonad.Actions.Minimize".
isMinimized :: Query Bool
isMinimized :: Query Bool
isMinimized = WorkspaceId -> WorkspaceId -> Query Bool
isInProperty WorkspaceId
"_NET_WM_STATE" WorkspaceId
"_NET_WM_STATE_HIDDEN"

-- | A predicate to check whether a window is a dialog.
--
-- See <https://specifications.freedesktop.org/wm-spec/wm-spec-1.5.html#idm46485863906176>.
isDialog :: Query Bool
isDialog :: Query Bool
isDialog = WorkspaceId -> WorkspaceId -> Query Bool
isInProperty WorkspaceId
"_NET_WM_WINDOW_TYPE" WorkspaceId
"_NET_WM_WINDOW_TYPE_DIALOG"

-- | A predicate to check whether a window is a notification.
--
-- See <https://specifications.freedesktop.org/wm-spec/wm-spec-1.5.html#idm46485863906176>.
isNotification :: Query Bool
isNotification :: Query Bool
isNotification =
  WorkspaceId -> WorkspaceId -> Query Bool
isInProperty WorkspaceId
"_NET_WM_WINDOW_TYPE" WorkspaceId
"_NET_WM_WINDOW_TYPE_NOTIFICATION"

-- | This function returns 'Just' the @_NET_WM_PID@ property for a
-- particular window if set, 'Nothing' otherwise.
--
-- See <https://specifications.freedesktop.org/wm-spec/wm-spec-1.5.html#idm45623487788432>.
pid :: Query (Maybe ProcessID)
pid :: Query (Maybe ProcessID)
pid = Query Window
forall r (m :: * -> *). MonadReader r m => m r
ask Query Window
-> (Window -> Query (Maybe ProcessID)) -> Query (Maybe ProcessID)
forall a b. Query a -> (a -> Query b) -> Query b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \Window
w -> X (Maybe ProcessID) -> Query (Maybe ProcessID)
forall a. X a -> Query a
liftX (X (Maybe ProcessID) -> Query (Maybe ProcessID))
-> X (Maybe ProcessID) -> Query (Maybe ProcessID)
forall a b. (a -> b) -> a -> b
$ WorkspaceId -> Window -> X (Maybe [CLong])
getProp32s WorkspaceId
"_NET_WM_PID" Window
w X (Maybe [CLong])
-> (Maybe [CLong] -> Maybe ProcessID) -> X (Maybe ProcessID)
forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
<&> \case
    Just [CLong
x] -> ProcessID -> Maybe ProcessID
forall a. a -> Maybe a
Just (CLong -> ProcessID
forall a b. (Integral a, Num b) => a -> b
fromIntegral CLong
x)
    Maybe [CLong]
_        -> Maybe ProcessID
forall a. Maybe a
Nothing

-- | This function returns 'Just' the @_NET_WM_DESKTOP@ property for a
-- particular window if set, 'Nothing' otherwise.
--
-- See <https://specifications.freedesktop.org/wm-spec/wm-spec-1.5.html#idm46181547492704>.
desktop :: Query (Maybe Int)
desktop :: Query (Maybe Int)
desktop = Query Window
forall r (m :: * -> *). MonadReader r m => m r
ask Query Window -> (Window -> Query (Maybe Int)) -> Query (Maybe Int)
forall a b. Query a -> (a -> Query b) -> Query b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \Window
w -> X (Maybe Int) -> Query (Maybe Int)
forall a. X a -> Query a
liftX (X (Maybe Int) -> Query (Maybe Int))
-> X (Maybe Int) -> Query (Maybe Int)
forall a b. (a -> b) -> a -> b
$ WorkspaceId -> Window -> X (Maybe [CLong])
getProp32s WorkspaceId
"_NET_WM_DESKTOP" Window
w X (Maybe [CLong]) -> (Maybe [CLong] -> Maybe Int) -> X (Maybe Int)
forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
<&> \case
    Just [CLong
x] -> Int -> Maybe Int
forall a. a -> Maybe a
Just (CLong -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral CLong
x)
    Maybe [CLong]
_        -> Maybe Int
forall a. Maybe a
Nothing

-- | A predicate to check whether a window is Transient.
-- It holds the result which might be the window it is transient to
-- or it might be 'Nothing'.
transientTo :: Query (Maybe Window)
transientTo :: Query (Maybe Window)
transientTo = do
    Window
w <- Query Window
forall r (m :: * -> *). MonadReader r m => m r
ask
    Display
d <- (X Display -> Query Display
forall a. X a -> Query a
liftX (X Display -> Query Display)
-> ((XConf -> Display) -> X Display)
-> (XConf -> Display)
-> Query Display
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (XConf -> Display) -> X Display
forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
asks) XConf -> Display
display
    IO (Maybe Window) -> Query (Maybe Window)
forall a. IO a -> Query a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (Maybe Window) -> Query (Maybe Window))
-> IO (Maybe Window) -> Query (Maybe Window)
forall a b. (a -> b) -> a -> b
$ Display -> Window -> IO (Maybe Window)
getTransientForHint Display
d Window
w

-- | A convenience 'MaybeManageHook' that will check to see if a window
-- is transient, and then move it to its parent.
transience :: MaybeManageHook
transience :: MaybeManageHook
transience = Query (Maybe Window)
transientTo Query (Maybe Window)
-> Maybe Window -> Query (Match (Maybe Window))
forall a (m :: * -> *).
(Eq a, Functor m) =>
m a -> a -> m (Match a)
</=? Maybe Window
forall a. Maybe a
Nothing Query (Match (Maybe Window))
-> (Maybe Window -> Query (Endo WindowSet)) -> MaybeManageHook
forall (m :: * -> *) a b.
(Functor m, Monad m) =>
m (Match a) -> (a -> m b) -> m (Maybe b)
-?>> Query (Endo WindowSet)
-> (Window -> Query (Endo WindowSet))
-> Maybe Window
-> Query (Endo WindowSet)
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Query (Endo WindowSet)
forall a. Monoid a => a
idHook Window -> Query (Endo WindowSet)
doShiftTo

-- | 'transience' set to a 'ManageHook'
transience' :: ManageHook
transience' :: Query (Endo WindowSet)
transience' = MaybeManageHook -> Query (Endo WindowSet)
forall a (m :: * -> *). (Monoid a, Functor m) => m (Maybe a) -> m a
maybeToDefinite MaybeManageHook
transience

-- | This function returns 'Just' the @WM_CLIENT_LEADER@ property for a
-- particular window if set, 'Nothing' otherwise. Note that, generally,
-- the window ID returned from this property (by firefox, for example)
-- corresponds to an unmapped or unmanaged dummy window. For this to be
-- useful in most cases, it should be used together with 'sameBy'.
--
-- See <https://tronche.com/gui/x/icccm/sec-5.html>.
clientLeader :: Query (Maybe Window)
clientLeader :: Query (Maybe Window)
clientLeader = Query Window
forall r (m :: * -> *). MonadReader r m => m r
ask Query Window
-> (Window -> Query (Maybe Window)) -> Query (Maybe Window)
forall a b. Query a -> (a -> Query b) -> Query b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \Window
w -> X (Maybe Window) -> Query (Maybe Window)
forall a. X a -> Query a
liftX (X (Maybe Window) -> Query (Maybe Window))
-> X (Maybe Window) -> Query (Maybe Window)
forall a b. (a -> b) -> a -> b
$ WorkspaceId -> Window -> X (Maybe [CLong])
getProp32s WorkspaceId
"WM_CLIENT_LEADER" Window
w X (Maybe [CLong])
-> (Maybe [CLong] -> Maybe Window) -> X (Maybe Window)
forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
<&> \case
    Just [CLong
x] -> Window -> Maybe Window
forall a. a -> Maybe a
Just (CLong -> Window
forall a b. (Integral a, Num b) => a -> b
fromIntegral CLong
x)
    Maybe [CLong]
_        -> Maybe Window
forall a. Maybe a
Nothing

-- | For a given window, 'sameBy' returns all windows that have a matching
-- property (e.g. those obtained from Queries of 'clientLeader' and 'pid').
sameBy :: Eq prop => Query (Maybe prop) -> Query [Window]
sameBy :: forall prop. Eq prop => Query (Maybe prop) -> Query [Window]
sameBy Query (Maybe prop)
prop = Query (Maybe prop)
prop Query (Maybe prop)
-> (Maybe prop -> Query [Window]) -> Query [Window]
forall a b. Query a -> (a -> Query b) -> Query b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
    Maybe prop
Nothing -> [Window] -> Query [Window]
forall a. a -> Query a
forall (f :: * -> *) a. Applicative f => a -> f a
pure []
    Maybe prop
propVal -> Query Window
forall r (m :: * -> *). MonadReader r m => m r
ask Query Window -> (Window -> Query [Window]) -> Query [Window]
forall a b. Query a -> (a -> Query b) -> Query b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \Window
w -> X [Window] -> Query [Window]
forall a. X a -> Query a
liftX (X [Window] -> Query [Window])
-> ((WindowSet -> X [Window]) -> X [Window])
-> (WindowSet -> X [Window])
-> Query [Window]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (WindowSet -> X [Window]) -> X [Window]
forall a. (WindowSet -> X a) -> X a
withWindowSet ((WindowSet -> X [Window]) -> Query [Window])
-> (WindowSet -> X [Window]) -> Query [Window]
forall a b. (a -> b) -> a -> b
$ \WindowSet
s ->
        (Window -> X Bool) -> [Window] -> X [Window]
forall (m :: * -> *) a.
Applicative m =>
(a -> m Bool) -> [a] -> m [a]
filterM ((Maybe prop -> Bool) -> X (Maybe prop) -> X Bool
forall a b. (a -> b) -> X a -> X b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Maybe prop
propVal Maybe prop -> Maybe prop -> Bool
forall a. Eq a => a -> a -> Bool
==) (X (Maybe prop) -> X Bool)
-> (Window -> X (Maybe prop)) -> Window -> X Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Query (Maybe prop) -> Window -> X (Maybe prop)
forall a. Query a -> Window -> X a
runQuery Query (Maybe prop)
prop) (WindowSet -> [Window]
forall a i l s sd. Eq a => StackSet i l a s sd -> [a]
W.allWindows WindowSet
s [Window] -> [Window] -> [Window]
forall a. Eq a => [a] -> [a] -> [a]
\\ [Window
w])

-- | 'MaybeManageHook' that moves the window to the same workspace as the
-- first other window that has the same value of a given 'Query'. Useful
-- Queries for this include 'clientLeader' and 'pid'.
shiftToSame :: Eq prop => Query (Maybe prop) -> MaybeManageHook
shiftToSame :: forall prop. Eq prop => Query (Maybe prop) -> MaybeManageHook
shiftToSame Query (Maybe prop)
prop = Query (Maybe prop) -> Query [Window]
forall prop. Eq prop => Query (Maybe prop) -> Query [Window]
sameBy Query (Maybe prop)
prop Query [Window] -> [Window] -> Query (Match [Window])
forall a (m :: * -> *).
(Eq a, Functor m) =>
m a -> a -> m (Match a)
</=? [] Query (Match [Window])
-> ([Window] -> Query (Endo WindowSet)) -> MaybeManageHook
forall (m :: * -> *) a b.
(Functor m, Monad m) =>
m (Match a) -> (a -> m b) -> m (Maybe b)
-?>> Query (Endo WindowSet)
-> (Window -> Query (Endo WindowSet))
-> Maybe Window
-> Query (Endo WindowSet)
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Query (Endo WindowSet)
forall a. Monoid a => a
idHook Window -> Query (Endo WindowSet)
doShiftTo (Maybe Window -> Query (Endo WindowSet))
-> ([Window] -> Maybe Window) -> [Window] -> Query (Endo WindowSet)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Window] -> Maybe Window
forall a. [a] -> Maybe a
listToMaybe

-- | 'shiftToSame' set to a 'ManageHook'
shiftToSame' :: Eq prop => Query (Maybe prop) -> ManageHook
shiftToSame' :: forall prop.
Eq prop =>
Query (Maybe prop) -> Query (Endo WindowSet)
shiftToSame' = MaybeManageHook -> Query (Endo WindowSet)
forall a (m :: * -> *). (Monoid a, Functor m) => m (Maybe a) -> m a
maybeToDefinite (MaybeManageHook -> Query (Endo WindowSet))
-> (Query (Maybe prop) -> MaybeManageHook)
-> Query (Maybe prop)
-> Query (Endo WindowSet)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Query (Maybe prop) -> MaybeManageHook
forall prop. Eq prop => Query (Maybe prop) -> MaybeManageHook
shiftToSame

-- | converts 'MaybeManageHook's to 'ManageHook's
maybeToDefinite :: (Monoid a, Functor m) => m (Maybe a) -> m a
maybeToDefinite :: forall a (m :: * -> *). (Monoid a, Functor m) => m (Maybe a) -> m a
maybeToDefinite = (Maybe a -> a) -> m (Maybe a) -> m a
forall a b. (a -> b) -> m a -> m b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (a -> Maybe a -> a
forall a. a -> Maybe a -> a
fromMaybe a
forall a. Monoid a => a
mempty)

-- | Move the window to the same workspace as another window.
doShiftTo :: Window -> ManageHook
doShiftTo :: Window -> Query (Endo WindowSet)
doShiftTo Window
target = (WindowSet -> WindowSet) -> Query (Endo WindowSet)
forall s. (s -> s) -> Query (Endo s)
doF ((WindowSet -> WindowSet) -> Query (Endo WindowSet))
-> (Window -> WindowSet -> WindowSet)
-> Window
-> Query (Endo WindowSet)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Window -> WindowSet -> WindowSet
forall {s} {i} {l} {sd}.
(Eq s, Eq i) =>
Window -> StackSet i l Window s sd -> StackSet i l Window s sd
shiftTo (Window -> Query (Endo WindowSet))
-> Query Window -> Query (Endo WindowSet)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< Query Window
forall r (m :: * -> *). MonadReader r m => m r
ask
  where shiftTo :: Window -> StackSet i l Window s sd -> StackSet i l Window s sd
shiftTo Window
w StackSet i l Window s sd
s = StackSet i l Window s sd
-> (i -> StackSet i l Window s sd)
-> Maybe i
-> StackSet i l Window s sd
forall b a. b -> (a -> b) -> Maybe a -> b
maybe StackSet i l Window s sd
s (\i
t -> i -> Window -> StackSet i l Window s sd -> StackSet i l Window s sd
forall a s i l sd.
(Ord a, Eq s, Eq i) =>
i -> a -> StackSet i l a s sd -> StackSet i l a s sd
W.shiftWin i
t Window
w StackSet i l Window s sd
s) (Window -> StackSet i l Window s sd -> Maybe i
forall a i l s sd. Eq a => a -> StackSet i l a s sd -> Maybe i
W.findTag Window
target StackSet i l Window s sd
s)

-- | Floats the new window in the given rectangle.
doRectFloat :: W.RationalRect  -- ^ The rectangle to float the window in. 0 to 1; x, y, w, h.
            -> ManageHook
doRectFloat :: RationalRect -> Query (Endo WindowSet)
doRectFloat RationalRect
r = Query Window
forall r (m :: * -> *). MonadReader r m => m r
ask Query Window
-> (Window -> Query (Endo WindowSet)) -> Query (Endo WindowSet)
forall a b. Query a -> (a -> Query b) -> Query b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \Window
w -> (WindowSet -> WindowSet) -> Query (Endo WindowSet)
forall s. (s -> s) -> Query (Endo s)
doF (Window -> RationalRect -> WindowSet -> WindowSet
forall a i l s sd.
Ord a =>
a -> RationalRect -> StackSet i l a s sd -> StackSet i l a s sd
W.float Window
w RationalRect
r)

-- | Floats the window and makes it use the whole screen. Equivalent to
-- @'doRectFloat' $ 'W.RationalRect' 0 0 1 1@.
doFullFloat :: ManageHook
doFullFloat :: Query (Endo WindowSet)
doFullFloat = RationalRect -> Query (Endo WindowSet)
doRectFloat (RationalRect -> Query (Endo WindowSet))
-> RationalRect -> Query (Endo WindowSet)
forall a b. (a -> b) -> a -> b
$ Rational -> Rational -> Rational -> Rational -> RationalRect
W.RationalRect Rational
0 Rational
0 Rational
1 Rational
1

-- | Floats a new window using a rectangle computed as a function of
--   the rectangle that it would have used by default.
doFloatDep :: (W.RationalRect -> W.RationalRect) -> ManageHook
doFloatDep :: (RationalRect -> RationalRect) -> Query (Endo WindowSet)
doFloatDep RationalRect -> RationalRect
move = Query Window
forall r (m :: * -> *). MonadReader r m => m r
ask Query Window
-> (Window -> Query (Endo WindowSet)) -> Query (Endo WindowSet)
forall a b. Query a -> (a -> Query b) -> Query b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \Window
w -> (WindowSet -> WindowSet) -> Query (Endo WindowSet)
forall s. (s -> s) -> Query (Endo s)
doF ((WindowSet -> WindowSet) -> Query (Endo WindowSet))
-> ((ScreenId, RationalRect) -> WindowSet -> WindowSet)
-> (ScreenId, RationalRect)
-> Query (Endo WindowSet)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Window -> RationalRect -> WindowSet -> WindowSet
forall a i l s sd.
Ord a =>
a -> RationalRect -> StackSet i l a s sd -> StackSet i l a s sd
W.float Window
w (RationalRect -> WindowSet -> WindowSet)
-> ((ScreenId, RationalRect) -> RationalRect)
-> (ScreenId, RationalRect)
-> WindowSet
-> WindowSet
forall b c a. (b -> c) -> (a -> b) -> a -> c
. RationalRect -> RationalRect
move (RationalRect -> RationalRect)
-> ((ScreenId, RationalRect) -> RationalRect)
-> (ScreenId, RationalRect)
-> RationalRect
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (ScreenId, RationalRect) -> RationalRect
forall a b. (a, b) -> b
snd ((ScreenId, RationalRect) -> Query (Endo WindowSet))
-> Query (ScreenId, RationalRect) -> Query (Endo WindowSet)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< X (ScreenId, RationalRect) -> Query (ScreenId, RationalRect)
forall a. X a -> Query a
liftX (Window -> X (ScreenId, RationalRect)
floatLocation Window
w)

-- | Floats a new window with its original size, and its top left
--   corner at a specific point on the screen (both coordinates should
--   be in the range 0 to 1).
doFloatAt :: Rational -> Rational -> ManageHook
doFloatAt :: Rational -> Rational -> Query (Endo WindowSet)
doFloatAt Rational
x Rational
y = (RationalRect -> RationalRect) -> Query (Endo WindowSet)
doFloatDep RationalRect -> RationalRect
move
  where
    move :: RationalRect -> RationalRect
move (W.RationalRect Rational
_ Rational
_ Rational
w Rational
h) = Rational -> Rational -> Rational -> Rational -> RationalRect
W.RationalRect Rational
x Rational
y Rational
w Rational
h

-- | Floats a new window with its original size on the specified side of a
-- screen
doSideFloat :: Side -> ManageHook
doSideFloat :: Side -> Query (Endo WindowSet)
doSideFloat Side
side = (RationalRect -> RationalRect) -> Query (Endo WindowSet)
doFloatDep RationalRect -> RationalRect
move
  where
    move :: RationalRect -> RationalRect
move (W.RationalRect Rational
_ Rational
_ Rational
w Rational
h) = Rational -> Rational -> Rational -> Rational -> RationalRect
W.RationalRect Rational
cx Rational
cy Rational
w Rational
h
      where cx :: Rational
cx
              | Side
side Side -> [Side] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Side
SC,Side
C ,Side
NC] = (Rational
1Rational -> Rational -> Rational
forall a. Num a => a -> a -> a
-Rational
w)Rational -> Rational -> Rational
forall a. Fractional a => a -> a -> a
/Rational
2
              | Side
side Side -> [Side] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Side
SW,Side
CW,Side
NW] = Rational
0
              | Bool
otherwise = {- side `elem` [SE,CE,NE] -} Rational
1Rational -> Rational -> Rational
forall a. Num a => a -> a -> a
-Rational
w
            cy :: Rational
cy
              | Side
side Side -> [Side] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Side
CE,Side
C ,Side
CW] = (Rational
1Rational -> Rational -> Rational
forall a. Num a => a -> a -> a
-Rational
h)Rational -> Rational -> Rational
forall a. Fractional a => a -> a -> a
/Rational
2
              | Side
side Side -> [Side] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Side
NE,Side
NC,Side
NW] = Rational
0
              | Bool
otherwise = {- side `elem` [SE,SC,SW] -} Rational
1Rational -> Rational -> Rational
forall a. Num a => a -> a -> a
-Rational
h

-- | Floats a new window with its original size, but centered.
doCenterFloat :: ManageHook
doCenterFloat :: Query (Endo WindowSet)
doCenterFloat = Side -> Query (Endo WindowSet)
doSideFloat Side
C

-- | Hides window and ignores it.
doHideIgnore :: ManageHook
doHideIgnore :: Query (Endo WindowSet)
doHideIgnore = Query Window
forall r (m :: * -> *). MonadReader r m => m r
ask Query Window
-> (Window -> Query (Endo WindowSet)) -> Query (Endo WindowSet)
forall a b. Query a -> (a -> Query b) -> Query b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \Window
w -> X () -> Query ()
forall a. X a -> Query a
liftX (Window -> X ()
hide Window
w) Query () -> Query (Endo WindowSet) -> Query (Endo WindowSet)
forall a b. Query a -> Query b -> Query b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> (WindowSet -> WindowSet) -> Query (Endo WindowSet)
forall s. (s -> s) -> Query (Endo s)
doF (Window -> WindowSet -> WindowSet
forall a i l s sd.
Ord a =>
a -> StackSet i l a s sd -> StackSet i l a s sd
W.delete Window
w)

-- | Sinks a window
doSink :: ManageHook
doSink :: Query (Endo WindowSet)
doSink = (WindowSet -> WindowSet) -> Query (Endo WindowSet)
forall s. (s -> s) -> Query (Endo s)
doF ((WindowSet -> WindowSet) -> Query (Endo WindowSet))
-> (Window -> WindowSet -> WindowSet)
-> Window
-> Query (Endo WindowSet)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Window -> WindowSet -> WindowSet
forall a i l s sd.
Ord a =>
a -> StackSet i l a s sd -> StackSet i l a s sd
W.sink (Window -> Query (Endo WindowSet))
-> Query Window -> Query (Endo WindowSet)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< Query Window
forall r (m :: * -> *). MonadReader r m => m r
ask

-- | Lower an unmanaged window. Useful together with 'doIgnore' to lower
-- special windows that for some reason don't do it themselves.
doLower :: ManageHook
doLower :: Query (Endo WindowSet)
doLower = Query Window
forall r (m :: * -> *). MonadReader r m => m r
ask Query Window
-> (Window -> Query (Endo WindowSet)) -> Query (Endo WindowSet)
forall a b. Query a -> (a -> Query b) -> Query b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \Window
w -> X (Endo WindowSet) -> Query (Endo WindowSet)
forall a. X a -> Query a
liftX (X (Endo WindowSet) -> Query (Endo WindowSet))
-> X (Endo WindowSet) -> Query (Endo WindowSet)
forall a b. (a -> b) -> a -> b
$ (Display -> X (Endo WindowSet)) -> X (Endo WindowSet)
forall a. (Display -> X a) -> X a
withDisplay ((Display -> X (Endo WindowSet)) -> X (Endo WindowSet))
-> (Display -> X (Endo WindowSet)) -> X (Endo WindowSet)
forall a b. (a -> b) -> a -> b
$ \Display
dpy -> IO () -> X ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
io (Display -> Window -> IO ()
lowerWindow Display
dpy Window
w) X () -> X (Endo WindowSet) -> X (Endo WindowSet)
forall a b. X a -> X b -> X b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> X (Endo WindowSet)
forall a. Monoid a => a
mempty

-- | Raise an unmanaged window. Useful together with 'doIgnore' to raise
-- special windows that for some reason don't do it themselves.
doRaise :: ManageHook
doRaise :: Query (Endo WindowSet)
doRaise = Query Window
forall r (m :: * -> *). MonadReader r m => m r
ask Query Window
-> (Window -> Query (Endo WindowSet)) -> Query (Endo WindowSet)
forall a b. Query a -> (a -> Query b) -> Query b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \Window
w -> X (Endo WindowSet) -> Query (Endo WindowSet)
forall a. X a -> Query a
liftX (X (Endo WindowSet) -> Query (Endo WindowSet))
-> X (Endo WindowSet) -> Query (Endo WindowSet)
forall a b. (a -> b) -> a -> b
$ (Display -> X (Endo WindowSet)) -> X (Endo WindowSet)
forall a. (Display -> X a) -> X a
withDisplay ((Display -> X (Endo WindowSet)) -> X (Endo WindowSet))
-> (Display -> X (Endo WindowSet)) -> X (Endo WindowSet)
forall a b. (a -> b) -> a -> b
$ \Display
dpy -> IO () -> X ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
io (Display -> Window -> IO ()
raiseWindow Display
dpy Window
w) X () -> X (Endo WindowSet) -> X (Endo WindowSet)
forall a b. X a -> X b -> X b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> X (Endo WindowSet)
forall a. Monoid a => a
mempty

-- | Focus a window (useful in 'XMonad.Hooks.EwmhDesktops.setActivateHook').
doFocus :: ManageHook
doFocus :: Query (Endo WindowSet)
doFocus = (WindowSet -> WindowSet) -> Query (Endo WindowSet)
forall s. (s -> s) -> Query (Endo s)
doF ((WindowSet -> WindowSet) -> Query (Endo WindowSet))
-> (Window -> WindowSet -> WindowSet)
-> Window
-> Query (Endo WindowSet)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Window -> WindowSet -> WindowSet
forall s a i l sd.
(Eq s, Eq a, Eq i) =>
a -> StackSet i l a s sd -> StackSet i l a s sd
W.focusWindow (Window -> Query (Endo WindowSet))
-> Query Window -> Query (Endo WindowSet)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< Query Window
forall r (m :: * -> *). MonadReader r m => m r
ask