XMonad.Hooks.Focus

xmonad-contrib-0.18.1: Community-maintained extensions for xmonad

Copyright | sgf-dma 2016
Maintainer | sgf.dma@gmail.com
Safe Haskell | None
Language | Haskell2010
Extensions | DerivingStrategies DerivingVia FlexibleContexts ConstrainedClassMethods MultiParamTypeClasses GeneralizedNewtypeDeriving

XMonad.Hooks.Focus

Contents

- FocusQuery.
- Lifting into FocusQuery.
- Commonly used actions for modifying focus.
- Running FocusQuery.
- Example configurations.

Description

Extends XMonad.ManageHook EDSL to work on focused windows and current workspace.

Synopsis

- data Focus = Focus {
  - newWorkspace :: WorkspaceId
  - focusedWindow :: Maybe Window
  - currentWorkspace :: WorkspaceId
  }
- newtype FocusLock = FocusLock {
  - getFocusLock :: Bool
  }
- toggleLock :: X ()
- focusLockOn :: X ()
- focusLockOff :: X ()
- data FocusQuery a
- runFocusQuery :: FocusQuery a -> Focus -> Query a
- type FocusHook = FocusQuery (Endo WindowSet)
- liftQuery :: Query a -> FocusQuery a
- new :: Query a -> FocusQuery a
- focused :: Query Bool -> FocusQuery Bool
- focused' :: Monoid a => Query a -> FocusQuery a
- focusedOn :: WorkspaceId -> Query Bool -> FocusQuery Bool
- focusedOn' :: Monoid a => WorkspaceId -> Query a -> FocusQuery a
- focusedCur :: Query Bool -> FocusQuery Bool
- focusedCur' :: Monoid a => Query a -> FocusQuery a
- newOn :: WorkspaceId -> FocusQuery Bool
- newOnCur :: FocusQuery Bool
- unlessFocusLock :: Monoid a => Query a -> Query a
- keepFocus :: FocusHook
- switchFocus :: FocusHook
- keepWorkspace :: FocusHook
- switchWorkspace :: FocusHook
- manageFocus :: FocusHook -> ManageHook
- activateSwitchWs :: ManageHook
- activateOnCurrentWs :: ManageHook
- activateOnCurrentKeepFocus :: ManageHook

# Documentation

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.

data Focus #

Information about current workspace and focus.

Constructors

Focus |
Fields newWorkspace :: WorkspaceId Workspace, where new window appears. focusedWindow :: Maybe Window Focused window on workspace, where new window appears. currentWorkspace :: WorkspaceId Current workspace.

#### Instances

Instances details

Default Focus # |
Instance details Defined in XMonad.Hooks.Focus Methods def :: Focus #
Show Focus # |
Instance details Defined in XMonad.Hooks.Focus Methods showsPrec :: Int -> Focus -> ShowS # show :: Focus -> String # showList :: [Focus] -> ShowS #
MonadReader Focus FocusQuery # |
Instance details Defined in XMonad.Hooks.Focus Methods ask :: FocusQuery Focus # local :: (Focus -> Focus) -> FocusQuery a -> FocusQuery a # reader :: (Focus -> a) -> FocusQuery a #

newtype FocusLock #

Constructors

FocusLock |
Fields getFocusLock :: Bool

#### Instances

Instances details

Show FocusLock # |
Instance details Defined in XMonad.Hooks.Focus Methods showsPrec :: Int -> FocusLock -> ShowS # show :: FocusLock -> String # showList :: [FocusLock] -> ShowS #
ExtensionClass FocusLock # |
Instance details Defined in XMonad.Hooks.Focus Methods initialValue :: FocusLock # extensionType :: FocusLock -> StateExtension #

toggleLock :: X () #

Toggle stored focus lock state.

focusLockOn :: X () #

Lock focus.

focusLockOff :: X () #

Unlock focus.

data FocusQuery a #

Monad on top of `Query` providing additional information about new window.

#### Instances

Instances details

MonadIO FocusQuery # |
Instance details Defined in XMonad.Hooks.Focus Methods liftIO :: IO a -> FocusQuery a #
Applicative FocusQuery # |
Instance details Defined in XMonad.Hooks.Focus Methods pure :: a -> FocusQuery a # (<*>) :: FocusQuery (a -> b) -> FocusQuery a -> FocusQuery b # liftA2 :: (a -> b -> c) -> FocusQuery a -> FocusQuery b -> FocusQuery c # (*>) :: FocusQuery a -> FocusQuery b -> FocusQuery b # (<*) :: FocusQuery a -> FocusQuery b -> FocusQuery a #
Functor FocusQuery # |
Instance details Defined in XMonad.Hooks.Focus Methods fmap :: (a -> b) -> FocusQuery a -> FocusQuery b # (<$) :: a -> FocusQuery b -> FocusQuery a #
Monad FocusQuery # |
Instance details Defined in XMonad.Hooks.Focus Methods (>>=) :: FocusQuery a -> (a -> FocusQuery b) -> FocusQuery b # (>>) :: FocusQuery a -> FocusQuery b -> FocusQuery b # return :: a -> FocusQuery a #
MonadReader Focus FocusQuery # |
Instance details Defined in XMonad.Hooks.Focus Methods ask :: FocusQuery Focus # local :: (Focus -> Focus) -> FocusQuery a -> FocusQuery a # reader :: (Focus -> a) -> FocusQuery a #
Monoid a => Monoid (FocusQuery a) # |
Instance details Defined in XMonad.Hooks.Focus Methods mempty :: FocusQuery a # mappend :: FocusQuery a -> FocusQuery a -> FocusQuery a # mconcat :: [FocusQuery a] -> FocusQuery a #
Semigroup a => Semigroup (FocusQuery a) # |
Instance details Defined in XMonad.Hooks.Focus Methods (<>) :: FocusQuery a -> FocusQuery a -> FocusQuery a # sconcat :: NonEmpty (FocusQuery a) -> FocusQuery a # stimes :: Integral b => b -> FocusQuery a -> FocusQuery a #

runFocusQuery :: FocusQuery a -> Focus -> Query a #

type FocusHook = FocusQuery (Endo WindowSet) #

# Lifting into FocusQuery.

liftQuery :: Query a -> FocusQuery a #

Lift `Query` into `FocusQuery` monad. The same as `new`.

new :: Query a -> FocusQuery a #

Run `Query` on new window.

focused :: Query Bool -> FocusQuery Bool #

Run `Query` on focused window on workspace, where new window appears. If there is no focused window, return `False`.

focused' :: Monoid a => Query a -> FocusQuery a #

More general version of `focused`.

focusedOn :: WorkspaceId -> Query Bool -> FocusQuery Bool #

Run `Query` on window focused at particular workspace. If there is no focused window, return `False`.

focusedOn' :: Monoid a => WorkspaceId -> Query a -> FocusQuery a #

More general version of `focusedOn`.

focusedCur :: Query Bool -> FocusQuery Bool #

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' :: Monoid a => Query a -> FocusQuery a #

More general version of `focusedCur`.

newOn :: WorkspaceId -> FocusQuery Bool #

Does new window appear at particular workspace?

newOnCur :: FocusQuery Bool #

Does new window appear at current workspace?

unlessFocusLock :: Monoid a => Query a -> Query a #

Execute `Query`, unless focus is locked.

# Commonly used actions for modifying focus.

Operations in each pair `keepFocus` and `switchFocus`, `keepWorkspace` and `switchWorkspace` overwrite each other (the letftmost will determine what happened):

    keepFocus       <> switchFocus     = keepFocus

    switchFocus     <> keepFocus       = switchFocus

    keepWorkspace   <> switchWorkspace = keepWorkspace

    switchWorkspace <> keepWorkspace   = switchWorkspace

and operations from different pairs are commutative:

    keepFocus   <> switchWorkspace = switchWorkspace <> keepFocus

    switchFocus <> switchWorkspace = switchWorkspace <> switchFocus

etc.

keepFocus :: FocusHook #

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.

switchFocus :: FocusHook #

Switch focus to new window on workspace (may not be current), where new window appears. Workspace will not be switched. This operation is idempotent.

keepWorkspace :: FocusHook #

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.

switchWorkspace :: FocusHook #

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.

# Running FocusQuery.

manageFocus :: FocusHook -> ManageHook #

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.

# Example configurations.

activateSwitchWs :: ManageHook #

Default EWMH window activation behavior: switch to workspace with activated window and switch focus to it. Not to be used in a `manageHook`.

activateOnCurrentWs :: ManageHook #

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`.

activateOnCurrentKeepFocus :: ManageHook #

Move activated window to current workspace, but keep focus unchanged. Not to be used in a `manageHook`.