mirror of
https://github.com/xmonad/xmonad-contrib.git
synced 2025-05-19 03:20:21 -07:00
The links were broken due to: 1. Incorrect quotes (' instead of " for module links and occasionally vice-versa). 2. Changes in the name of the "target" module not reflected in the "source" docs. 3. Typos to begin with. 4. Use of `<foo>` in the docs is rendered as just `foo` with a link to `/foo`. 5. Similarly for `"Foo"` if it starts with a capital letter (and hence could be a module). 6. Markup inside `@` code blocks still being applied. e.g. `@M-<arrow-keys>@` is rendered as `M-arrow-keys` with a spurious hyperlink from arrow-keys to `/arrow-keys`, which is confusing. Three links from XMonad.Util.Run have been removed outright, since they're no longer examples of the usage of 'runProcessWithInput'. WmiiActions has been gone since 2008, while XMonad.Prompt.Directory and XMonad.Layout.WorkspaceDir haven't been using 'runProcessWithInput' since 2020 and 2012, respectively. In some cases the `<foo>` were surrounded with @, especially in the case of key definitions, for consistency. (This wasn't done everywhere, because it looks ugly in the source.) MoreManageHelpers has never been in xmonad-contrib. ManageHelpers seems to fill the expected role. In the case of the module description for X.H.ManageDebug the quotes were simply removed because none of the likely options to make the link work were successful.
560 lines
21 KiB
Haskell
560 lines
21 KiB
Haskell
{-# LANGUAGE FlexibleContexts #-}
|
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
|
{-# OPTIONS_HADDOCK show-extensions #-}
|
|
|
|
-- |
|
|
-- Module: XMonad.Hooks.Focus
|
|
-- Description: Extends ManageHook EDSL to work on focused windows and current workspace.
|
|
-- Copyright: sgf-dma, 2016
|
|
-- Maintainer: sgf.dma@gmail.com
|
|
--
|
|
-- Extends "XMonad.ManageHook" EDSL to work on focused windows and current
|
|
-- workspace.
|
|
--
|
|
|
|
module XMonad.Hooks.Focus
|
|
(
|
|
-- $main
|
|
|
|
-- * FocusQuery.
|
|
--
|
|
-- $focusquery
|
|
Focus (..)
|
|
, FocusLock (..)
|
|
, toggleLock
|
|
, focusLockOn
|
|
, focusLockOff
|
|
, FocusQuery
|
|
, runFocusQuery
|
|
, FocusHook
|
|
|
|
-- * Lifting into FocusQuery.
|
|
--
|
|
-- $lift
|
|
, liftQuery
|
|
, new
|
|
, focused
|
|
, focused'
|
|
, focusedOn
|
|
, focusedOn'
|
|
, focusedCur
|
|
, focusedCur'
|
|
, newOn
|
|
, newOnCur
|
|
, unlessFocusLock
|
|
|
|
-- * Commonly used actions for modifying focus.
|
|
--
|
|
-- $common
|
|
, keepFocus
|
|
, switchFocus
|
|
, keepWorkspace
|
|
, switchWorkspace
|
|
|
|
-- * Running FocusQuery.
|
|
--
|
|
-- $running
|
|
, manageFocus
|
|
|
|
-- * Example configurations.
|
|
--
|
|
-- $examples
|
|
, activateSwitchWs
|
|
, activateOnCurrentWs
|
|
, activateOnCurrentKeepFocus
|
|
)
|
|
where
|
|
|
|
import Control.Arrow ((&&&))
|
|
import Control.Monad.Reader
|
|
|
|
import XMonad
|
|
import XMonad.Prelude
|
|
import qualified XMonad.StackSet as W
|
|
import qualified XMonad.Util.ExtensibleState as XS
|
|
import XMonad.Hooks.ManageHelpers (currentWs)
|
|
|
|
|
|
-- $main
|
|
--
|
|
-- This module provides monad on top of Query monad providing additional
|
|
-- information about new window:
|
|
--
|
|
-- - workspace, where new window will appear;
|
|
-- - focused window on workspace, where new window will appear;
|
|
-- - current workspace;
|
|
--
|
|
-- And a property in extensible state:
|
|
--
|
|
-- - is focus lock enabled? Focus lock instructs all library's 'FocusHook'
|
|
-- functions to not move focus or switch workspace.
|
|
--
|
|
-- Lifting operations for standard 'ManageHook' EDSL combinators into
|
|
-- 'FocusQuery' monad allowing to run these combinators on focused window and
|
|
-- common actions for keeping focus and\/or workspace, switching focus and\/or
|
|
-- workspace are also provided.
|
|
--
|
|
-- == Quick start.
|
|
--
|
|
-- I may use one of predefined configurations.
|
|
--
|
|
-- 1. The default window activation behavior (switch to workspace with
|
|
-- activated window and switch focus to it) expressed using this module:
|
|
--
|
|
-- > import XMonad
|
|
-- >
|
|
-- > import XMonad.Hooks.EwmhDesktops
|
|
-- > import XMonad.Hooks.Focus
|
|
-- >
|
|
-- > main :: IO ()
|
|
-- > main = do
|
|
-- > let ah :: ManageHook
|
|
-- > ah = activateSwitchWs
|
|
-- > xcf = setEwmhActivateHook ah
|
|
-- > . ewmh $ def{ modMask = mod4Mask }
|
|
-- > xmonad xcf
|
|
--
|
|
-- 2. Or i may move activated window to current workspace and switch focus to
|
|
-- it:
|
|
--
|
|
-- > let ah :: ManageHook
|
|
-- > ah = activateOnCurrentWs
|
|
--
|
|
-- 3. Or move activated window to current workspace, but keep focus unchanged:
|
|
--
|
|
-- > let ah :: ManageHook
|
|
-- > ah = activateOnCurrentKeepFocus
|
|
--
|
|
-- 4. I may use regular 'ManageHook' combinators for filtering, which windows
|
|
-- may activate. E.g. activate all windows, except firefox:
|
|
--
|
|
-- > let ah :: ManageHook
|
|
-- > ah = not <$> (className =? "Firefox" <||> className =? "Firefox-esr" <||> className =? "Iceweasel")
|
|
-- > --> activateSwitchWs
|
|
--
|
|
-- 5. Or even use 'FocusHook' combinators. E.g. activate all windows, unless
|
|
-- xterm is focused on /current/ workspace:
|
|
--
|
|
-- > let ah :: ManageHook
|
|
-- > ah = manageFocus (not <$> focusedCur (className =? "XTerm")
|
|
-- > --> liftQuery activateSwitchWs)
|
|
--
|
|
-- or activate all windows, unless focused window on the workspace,
|
|
-- /where activated window is/, is not a xterm:
|
|
--
|
|
-- > let ah :: ManageHook
|
|
-- > ah = manageFocus (not <$> focused (className =? "XTerm")
|
|
-- > --> liftQuery activateSwitchWs)
|
|
--
|
|
-- == Defining FocusHook.
|
|
--
|
|
-- I may define my own 'FocusHook' like:
|
|
--
|
|
-- > activateFocusHook :: FocusHook
|
|
-- > activateFocusHook = composeAll
|
|
-- > -- If 'gmrun' is focused on workspace, on which
|
|
-- > -- /activated window/ is, keep focus unchanged. But i
|
|
-- > -- may still switch workspace (thus, i use 'composeAll').
|
|
-- > -- See 'keepFocus' properties in the docs below.
|
|
-- > [ focused (className =? "Gmrun")
|
|
-- > --> keepFocus
|
|
-- > -- Default behavior for activated windows: switch
|
|
-- > -- workspace and focus.
|
|
-- > , return True --> switchWorkspace <> switchFocus
|
|
-- > ]
|
|
-- >
|
|
-- > newFocusHook :: FocusHook
|
|
-- > newFocusHook = composeOne
|
|
-- > -- Always switch focus to 'gmrun'.
|
|
-- > [ new (className =? "Gmrun") -?> switchFocus
|
|
-- > -- And always keep focus on 'gmrun'. Note, that
|
|
-- > -- another 'gmrun' will steal focus from already
|
|
-- > -- running one.
|
|
-- > , focused (className =? "Gmrun") -?> keepFocus
|
|
-- > -- If firefox dialog prompt (e.g. master password
|
|
-- > -- prompt) is focused on current workspace and new
|
|
-- > -- window appears here too, keep focus unchanged
|
|
-- > -- (note, used predicates: @newOnCur <&&> focused@ is
|
|
-- > -- the same as @newOnCur <&&> focusedCur@, but is
|
|
-- > -- /not/ the same as just 'focusedCur' )
|
|
-- > , newOnCur <&&> focused
|
|
-- > ((className =? "Firefox" <||> className =? "Firefox-esr" <||> className =? "Iceweasel") <&&> isDialog)
|
|
-- > -?> keepFocus
|
|
-- > -- Default behavior for new windows: switch focus.
|
|
-- > , return True -?> switchFocus
|
|
-- > ]
|
|
--
|
|
-- And then use it:
|
|
--
|
|
-- > import XMonad
|
|
-- > import XMonad.Util.EZConfig
|
|
-- >
|
|
-- > import XMonad.Hooks.EwmhDesktops
|
|
-- > import XMonad.Hooks.ManageHelpers
|
|
-- > import XMonad.Hooks.Focus
|
|
-- >
|
|
-- >
|
|
-- > main :: IO ()
|
|
-- > main = do
|
|
-- > let newFh :: ManageHook
|
|
-- > newFh = manageFocus newFocusHook
|
|
-- > acFh :: ManageHook
|
|
-- > acFh = manageFocus activateFocusHook
|
|
-- > xcf = setEwmhActivateHook acFh
|
|
-- > . ewmh $ def
|
|
-- > { manageHook = newFh <> manageHook def
|
|
-- > , modMask = mod4Mask
|
|
-- > }
|
|
-- > `additionalKeys` [((mod4Mask, xK_v), toggleLock)]
|
|
-- > xmonad xcf
|
|
--
|
|
-- Note:
|
|
--
|
|
-- - /mod4Mask+v/ key toggles focus lock (when enabled, neither focus nor
|
|
-- workspace won't be switched).
|
|
-- - I need "XMonad.Hooks.EwmhDesktops" module for enabling window
|
|
-- activation.
|
|
-- - 'FocusHook' in 'manageHook' will be called /only/ for new windows.
|
|
-- - 'FocusHook' in 'setEwmhActivateHook' will be called /only/ for activated windows.
|
|
--
|
|
-- Alternatively, i may construct a single 'FocusHook' for both new and
|
|
-- activated windows and then just add it to both 'manageHook' and 'setEwmhActivateHook':
|
|
--
|
|
-- > let fh :: Bool -> ManageHook
|
|
-- > fh activated = manageFocus $ composeOne
|
|
-- > [ pure activated -?> activateFocusHook
|
|
-- > , pure True -?> newFocusHook
|
|
-- > ]
|
|
-- > xcf = setEwmhActivateHook (fh True)
|
|
-- > . ewmh $ def
|
|
-- > { manageHook = fh False <> manageHook def
|
|
-- > , modMask = mod4Mask
|
|
-- > }
|
|
-- > `additionalKeys` [((mod4Mask, xK_v), toggleLock)]
|
|
--
|
|
-- And more technical notes:
|
|
--
|
|
-- - 'FocusHook' will run /many/ times, so it usually should not keep state
|
|
-- or save results. Precisely, it may do anything, but it must be idempotent
|
|
-- to operate properly.
|
|
-- - 'FocusHook' will see new window at workspace, where functions on the
|
|
-- /right/ from it in 'ManageHook' monoid place it. In other words, in
|
|
-- @(Endo WindowSet)@ monoid i may see changes only from functions applied
|
|
-- /before/ (more to the right in function composition). Thus, it's better to
|
|
-- add 'FocusHook' the last.
|
|
-- - 'FocusHook' functions won't see window shift to another workspace made
|
|
-- by function from 'FocusHook' itself: new window workspace is determined
|
|
-- /before/ running 'FocusHook' and even if later one of 'FocusHook'
|
|
-- functions moves window to another workspace, predicates ('focused',
|
|
-- 'newOn', etc) will still think new window is at workspace it was before.
|
|
-- This can be worked around by splitting 'FocusHook' into several different
|
|
-- values and evaluating each one separately, like:
|
|
--
|
|
-- > (FH2 -- manageFocus --> MH2) <> (FH1 -- manageFocus --> MH1) <> ..
|
|
--
|
|
-- E.g.
|
|
--
|
|
-- > manageFocus FH2 <> manageFocus FH1 <> ..
|
|
--
|
|
-- now @FH2@ will see window shift made by @FH1@.
|
|
--
|
|
-- Another interesting example is moving all activated windows to current
|
|
-- workspace by default, and applying 'FocusHook' after:
|
|
--
|
|
-- > import XMonad
|
|
-- > import XMonad.Util.EZConfig
|
|
-- >
|
|
-- > import XMonad.Hooks.EwmhDesktops
|
|
-- > import XMonad.Hooks.ManageHelpers
|
|
-- > import XMonad.Hooks.Focus
|
|
-- >
|
|
-- > main :: IO ()
|
|
-- > main = do
|
|
-- > let fh :: Bool -> ManageHook
|
|
-- > fh activated = manageFocus $ composeOne
|
|
-- > [ pure activated -?> (newOnCur --> keepFocus)
|
|
-- > , pure True -?> newFocusHook
|
|
-- > ]
|
|
-- > xcf = setEwmhActivateHook (fh True <> activateOnCurrentWs)
|
|
-- > . ewmh $ def
|
|
-- > { manageHook = fh False <> manageHook def
|
|
-- > , modMask = mod4Mask
|
|
-- > }
|
|
-- > `additionalKeys` [((mod4Mask, xK_v), toggleLock)]
|
|
-- > xmonad xcf
|
|
-- >
|
|
-- > newFocusHook :: FocusHook
|
|
-- > newFocusHook = composeOne
|
|
-- > -- Always switch focus to 'gmrun'.
|
|
-- > [ new (className =? "Gmrun") -?> switchFocus
|
|
-- > -- And always keep focus on 'gmrun'. Note, that
|
|
-- > -- another 'gmrun' will steal focus from already
|
|
-- > -- running one.
|
|
-- > , focused (className =? "Gmrun") -?> keepFocus
|
|
-- > -- If firefox dialog prompt (e.g. master password
|
|
-- > -- prompt) is focused on current workspace and new
|
|
-- > -- window appears here too, keep focus unchanged
|
|
-- > -- (note, used predicates: @newOnCur <&&> focused@ is
|
|
-- > -- the same as @newOnCur <&&> focusedCur@, but is
|
|
-- > -- /not/ the same as just 'focusedCur' )
|
|
-- > , newOnCur <&&> focused
|
|
-- > ((className =? "Firefox" <||> className =? "Firefox-esr" <||> className =? "Iceweasel") <&&> isDialog)
|
|
-- > -?> keepFocus
|
|
-- > -- Default behavior for new windows: switch focus.
|
|
-- > , return True -?> switchFocus
|
|
-- > ]
|
|
--
|
|
-- Note here:
|
|
--
|
|
-- - i keep focus, when activated window appears on current workspace, in
|
|
-- this example.
|
|
-- - when @pure activated -?> (newOnCur --> keepFocus)@ runs, activated
|
|
-- window will be /already/ on current workspace, thus, if i do not want to
|
|
-- move some activated windows, i should filter them out before applying
|
|
-- @activateOnCurrentWs@ 'FocusHook'.
|
|
|
|
|
|
-- FocusQuery.
|
|
-- $focusquery
|
|
|
|
-- | Information about current workspace and focus.
|
|
data Focus = Focus
|
|
{ -- | Workspace, where new window appears.
|
|
newWorkspace :: WorkspaceId
|
|
-- | Focused window on workspace, where new window
|
|
-- appears.
|
|
, focusedWindow :: Maybe Window
|
|
-- | Current workspace.
|
|
, currentWorkspace :: WorkspaceId
|
|
}
|
|
deriving (Show)
|
|
instance Default Focus where
|
|
def = Focus
|
|
{ focusedWindow = Nothing
|
|
, newWorkspace = ""
|
|
, currentWorkspace = ""
|
|
}
|
|
|
|
newtype FocusLock = FocusLock {getFocusLock :: Bool}
|
|
deriving (Show)
|
|
instance ExtensionClass FocusLock where
|
|
initialValue = FocusLock False
|
|
|
|
-- | Toggle stored focus lock state.
|
|
toggleLock :: X ()
|
|
toggleLock = XS.modify (\(FocusLock b) -> FocusLock (not b))
|
|
|
|
-- | Lock focus.
|
|
focusLockOn :: X ()
|
|
focusLockOn = XS.modify (const (FocusLock True))
|
|
|
|
-- | Unlock focus.
|
|
focusLockOff :: X ()
|
|
focusLockOff = XS.modify (const (FocusLock False))
|
|
|
|
-- | Monad on top of 'Query' providing additional information about new
|
|
-- window.
|
|
newtype FocusQuery a = FocusQuery (ReaderT Focus Query a)
|
|
instance Functor FocusQuery where
|
|
fmap f (FocusQuery x) = FocusQuery (fmap f x)
|
|
instance Applicative FocusQuery where
|
|
pure x = FocusQuery (pure x)
|
|
(FocusQuery f) <*> (FocusQuery mx) = FocusQuery (f <*> mx)
|
|
instance Monad FocusQuery where
|
|
(FocusQuery mx) >>= f = FocusQuery $ mx >>= \x ->
|
|
let FocusQuery y = f x in y
|
|
instance MonadReader Focus FocusQuery where
|
|
ask = FocusQuery ask
|
|
local f (FocusQuery mx) = FocusQuery (local f mx)
|
|
instance MonadIO FocusQuery where
|
|
liftIO mx = FocusQuery (liftIO mx)
|
|
instance Semigroup a => Semigroup (FocusQuery a) where
|
|
(<>) = liftM2 (<>)
|
|
instance Monoid a => Monoid (FocusQuery a) where
|
|
mempty = return mempty
|
|
|
|
runFocusQuery :: FocusQuery a -> Focus -> Query a
|
|
runFocusQuery (FocusQuery m) = runReaderT m
|
|
|
|
type FocusHook = FocusQuery (Endo WindowSet)
|
|
|
|
|
|
-- Lifting into 'FocusQuery'.
|
|
-- $lift
|
|
|
|
-- | Lift 'Query' into 'FocusQuery' monad. The same as 'new'.
|
|
liftQuery :: Query a -> FocusQuery a
|
|
liftQuery = FocusQuery . lift
|
|
|
|
-- | Run 'Query' on new window.
|
|
new :: Query a -> FocusQuery a
|
|
new = liftQuery
|
|
|
|
-- | Run 'Query' on focused window on workspace, where new window appears. If
|
|
-- there is no focused window, return 'False'.
|
|
focused :: Query Bool -> FocusQuery Bool
|
|
focused m = getAny <$> focused' (Any <$> m)
|
|
-- | More general version of 'focused'.
|
|
focused' :: Monoid a => Query a -> FocusQuery a
|
|
focused' m = do
|
|
mw <- asks focusedWindow
|
|
liftQuery (maybe mempty (flip local m . const) mw)
|
|
|
|
-- | Run 'Query' on window focused at particular workspace. If there is no
|
|
-- focused window, return 'False'.
|
|
focusedOn :: WorkspaceId -> Query Bool -> FocusQuery Bool
|
|
focusedOn i m = getAny <$> focusedOn' i (Any <$> m)
|
|
-- | More general version of 'focusedOn'.
|
|
focusedOn' :: Monoid a => WorkspaceId -> Query a -> FocusQuery a
|
|
focusedOn' i m = liftQuery $ do
|
|
mw <- liftX $ withWindowSet (return . W.peek . W.view i)
|
|
maybe mempty (flip local m . const) mw
|
|
|
|
-- | Run 'Query' on focused window on current workspace. If there is no
|
|
-- focused window, return 'False'. Note,
|
|
--
|
|
-- > focused <&&> newOnCur != focusedCur
|
|
--
|
|
-- The first will affect only new or activated window appearing on current
|
|
-- workspace, while the last will affect any window: focus even for windows
|
|
-- appearing on other workpsaces will depend on focus on /current/ workspace.
|
|
focusedCur :: Query Bool -> FocusQuery Bool
|
|
focusedCur m = getAny <$> focusedCur' (Any <$> m)
|
|
-- | More general version of 'focusedCur'.
|
|
focusedCur' :: Monoid a => Query a -> FocusQuery a
|
|
focusedCur' m = asks currentWorkspace >>= \i -> focusedOn' i m
|
|
|
|
-- | Does new window appear at particular workspace?
|
|
newOn :: WorkspaceId -> FocusQuery Bool
|
|
newOn i = asks ((i ==) . newWorkspace)
|
|
-- | Does new window appear at current workspace?
|
|
newOnCur :: FocusQuery Bool
|
|
newOnCur = asks currentWorkspace >>= newOn
|
|
|
|
-- | Execute 'Query', unless focus is locked.
|
|
unlessFocusLock :: Monoid a => Query a -> Query a
|
|
unlessFocusLock m = do
|
|
FocusLock b <- liftX XS.get
|
|
when' (not b) m
|
|
|
|
-- Commonly used actions for modifying focus.
|
|
--
|
|
-- $common
|
|
-- Operations in each pair 'keepFocus' and 'switchFocus', 'keepWorkspace' and
|
|
-- 'switchWorkspace' overwrite each other (the letftmost will determine what
|
|
-- happened):
|
|
--
|
|
-- prop> keepFocus <> switchFocus = keepFocus
|
|
-- prop> switchFocus <> keepFocus = switchFocus
|
|
-- prop> keepWorkspace <> switchWorkspace = keepWorkspace
|
|
-- prop> switchWorkspace <> keepWorkspace = switchWorkspace
|
|
--
|
|
-- and operations from different pairs are commutative:
|
|
--
|
|
-- prop> keepFocus <> switchWorkspace = switchWorkspace <> keepFocus
|
|
-- prop> switchFocus <> switchWorkspace = switchWorkspace <> switchFocus
|
|
--
|
|
-- etc.
|
|
|
|
-- | Keep focus on workspace (may not be current), where new window appears.
|
|
-- Workspace will not be switched. This operation is idempotent and
|
|
-- effectively returns focus to window focused on that workspace before
|
|
-- applying @(Endo WindowSet)@ function.
|
|
keepFocus :: FocusHook
|
|
keepFocus = focused' $ ask >>= \w -> doF $ \ws ->
|
|
W.view (W.currentTag ws) . W.focusWindow w $ ws
|
|
|
|
-- | Switch focus to new window on workspace (may not be current), where new
|
|
-- window appears. Workspace will not be switched. This operation is
|
|
-- idempotent.
|
|
switchFocus :: FocusHook
|
|
switchFocus = do
|
|
FocusLock b <- liftQuery . liftX $ XS.get
|
|
if b
|
|
-- When focus lock is enabled, call 'keepFocus' explicitly (still no
|
|
-- 'keepWorkspace') to overwrite default behavior.
|
|
then keepFocus
|
|
else new $ ask >>= \w -> doF $ \ws ->
|
|
W.view (W.currentTag ws) . W.focusWindow w $ ws
|
|
|
|
-- | Keep current workspace. Focus will not be changed at either current or
|
|
-- new window's workspace. This operation is idempotent and effectively
|
|
-- switches to workspace, which was current before applying @(Endo WindowSet)@
|
|
-- function.
|
|
keepWorkspace :: FocusHook
|
|
keepWorkspace = do
|
|
ws <- asks currentWorkspace
|
|
liftQuery . doF $ W.view ws
|
|
|
|
-- | Switch workspace to one, where new window appears. Focus will not be
|
|
-- changed at either current or new window's workspace. This operation is
|
|
-- idempotent.
|
|
switchWorkspace :: FocusHook
|
|
switchWorkspace = do
|
|
FocusLock b <- liftQuery . liftX $ XS.get
|
|
if b
|
|
-- When focus lock is enabled, call 'keepWorkspace' explicitly (still no
|
|
-- 'keepFocus') to overwrite default behavior.
|
|
then keepWorkspace
|
|
else do
|
|
ws <- asks newWorkspace
|
|
liftQuery . doF $ W.view ws
|
|
|
|
-- Running FocusQuery.
|
|
-- $running
|
|
|
|
-- | I don't know at which workspace new window will appear until @(Endo
|
|
-- WindowSet)@ function from 'windows' in "XMonad.Operations" actually run,
|
|
-- but in @(Endo WindowSet)@ function i can't already execute monadic actions,
|
|
-- because it's pure. So, i compute result for every workspace here and just
|
|
-- use it later in @(Endo WindowSet)@ function. Note, though, that this will
|
|
-- execute monadic actions many times, and therefore assume, that result of
|
|
-- 'FocusHook' does not depend on the number of times it was executed.
|
|
manageFocus :: FocusHook -> ManageHook
|
|
manageFocus m = do
|
|
fws <- liftX . withWindowSet $ return
|
|
. map (W.tag &&& fmap W.focus . W.stack) . W.workspaces
|
|
ct <- currentWs
|
|
let r = def {currentWorkspace = ct}
|
|
hs <- forM fws $ \(i, mw) -> do
|
|
f <- runFocusQuery m (r {focusedWindow = mw, newWorkspace = i})
|
|
return (i, f)
|
|
reader (selectHook hs) >>= doF
|
|
where
|
|
-- | Select and apply @(Endo WindowSet)@ function depending on which
|
|
-- workspace new window appeared now.
|
|
selectHook :: [(WorkspaceId, Endo WindowSet)] -> Window -> WindowSet -> WindowSet
|
|
selectHook cfs nw ws = fromMaybe ws $ do
|
|
i <- W.findTag nw ws
|
|
f <- lookup i cfs
|
|
return (appEndo f ws)
|
|
|
|
when' :: (Monad m, Monoid a) => Bool -> m a -> m a
|
|
when' b mx
|
|
| b = mx
|
|
| otherwise = return mempty
|
|
|
|
-- Exmaple configurations.
|
|
-- $examples
|
|
|
|
-- | Default EWMH window activation behavior: switch to workspace with
|
|
-- activated window and switch focus to it. Not to be used in a 'manageHook'.
|
|
activateSwitchWs :: ManageHook
|
|
activateSwitchWs = manageFocus (switchWorkspace <> switchFocus)
|
|
|
|
-- | Move activated window to current workspace. Not to be used in a 'manageHook'.
|
|
activateOnCurrent' :: ManageHook
|
|
activateOnCurrent' = currentWs >>= unlessFocusLock . doShift
|
|
|
|
-- | Move activated window to current workspace and switch focus to it. Note,
|
|
-- that i need to explicitly call 'switchFocus' here, because otherwise, when
|
|
-- activated window is /already/ on current workspace, focus won't be
|
|
-- switched. Not to be used in a 'manageHook'.
|
|
activateOnCurrentWs :: ManageHook
|
|
activateOnCurrentWs = manageFocus (newOnCur --> switchFocus) <> activateOnCurrent'
|
|
|
|
-- | Move activated window to current workspace, but keep focus unchanged.
|
|
-- Not to be used in a 'manageHook'.
|
|
activateOnCurrentKeepFocus :: ManageHook
|
|
activateOnCurrentKeepFocus = manageFocus (newOnCur --> keepFocus) <> activateOnCurrent'
|