X.C.Prime: add withWorkspaces et al.

This allows easier configuration of workspaces and their keybindings. Required
generalizing the 'Prime' type, so lots of other lines changed in rather trivial
ways.
This commit is contained in:
Devin Mullins
2014-09-25 20:30:34 +00:00
parent cbcd42dc83
commit 74c3f059b0

View File

@@ -60,6 +60,14 @@ keys,
mouseBindings, mouseBindings,
RemovableClass(..), RemovableClass(..),
-- * Modifying the list of workspaces
-- $workspaces
withWorkspaces,
wsNames,
wsKeyspecs,
wsActions,
wsSetName,
-- * Modifying the layoutHook -- * Modifying the layoutHook
-- $layout -- $layout
addLayout, addLayout,
@@ -86,6 +94,7 @@ module Prelude,
-- | These are the building blocks on which the config language is built. -- | These are the building blocks on which the config language is built.
-- Regular people shouldn't need to know about these. -- Regular people shouldn't need to know about these.
Prime, Prime,
Arr,
(>>), (>>),
ifThenElse, ifThenElse,
@@ -96,17 +105,17 @@ ifThenElse,
-- $troubleshooting -- $troubleshooting
) where ) where
import Prelude hiding ((>>)) import Prelude hiding ((>>), mod)
import qualified Prelude as P ((>>=), (>>)) import qualified Prelude as P ((>>=), (>>))
import qualified Data.Map as M
import Data.Monoid (All) import Data.Monoid (All)
import XMonad hiding (xmonad, XConfig(..)) import XMonad hiding (xmonad, XConfig(..))
import XMonad (XConfig(XConfig)) import XMonad (XConfig(XConfig))
import qualified XMonad.StackSet as W
import qualified XMonad as X (xmonad, XConfig(..)) import qualified XMonad as X (xmonad, XConfig(..))
import XMonad.Util.EZConfig (additionalKeysP, additionalMouseBindings, checkKeymap, mkKeymap, removeKeysP, removeMouseBindings) import XMonad.Util.EZConfig (additionalKeysP, additionalMouseBindings, checkKeymap, removeKeysP, removeMouseBindings)
-- $start_here -- $start_here
-- To start with, create a @~\/.xmonad\/xmonad.hs@ that looks like this: -- To start with, create a @~\/.xmonad\/xmonad.hs@ that looks like this:
@@ -133,10 +142,14 @@ import XMonad.Util.EZConfig (additionalKeysP, additionalMouseBindings, checkKeym
-- | A Prime is a function that transforms an XConfig. It's not a monad, but we -- | A Prime is a function that transforms an XConfig. It's not a monad, but we
-- turn on RebindableSyntax so we can abuse the pretty do notation. -- turn on RebindableSyntax so we can abuse the pretty do notation.
type Prime l l' = XConfig l -> IO (XConfig l') type Prime l l' = Arr (XConfig l) (XConfig l')
-- | Composes two Primes using 'Prelude.>>=' from "Prelude". -- | An Arr is a generalization of Prime. Don't reference the type, if you can
(>>) :: Prime l l' -> Prime l' l'' -> Prime l l'' -- avoid it. It might go away in the future.
type Arr x y = x -> IO y
-- | Composes two Arrs using 'Prelude.>>=' from "Prelude".
(>>) :: Arr x y -> Arr y z -> Arr x z
(>>) x y c = (P.>>=) (x c) y (>>) x y c = (P.>>=) (x c) y
-- | Because of RebindableSyntax, this is necessary to enable you to use -- | Because of RebindableSyntax, this is necessary to enable you to use
@@ -173,53 +186,53 @@ nothing = return
class UpdateableClass s x y | s -> x y where class UpdateableClass s x y | s -> x y where
-- | This lets you apply a function to an attribute (i.e. read, modify, write). -- | This lets you apply a function to an attribute (i.e. read, modify, write).
(=.) :: s l -> (x -> y) -> Prime l l (=.) :: s c -> (x -> y) -> Arr c c
class SettableClass s x y | s -> x y where class SettableClass s x y | s -> x y where
-- | This lets you modify an attribute. -- | This lets you modify an attribute.
(=:) :: s l -> y -> Prime l l (=:) :: s c -> y -> Arr c c
-- Undecideable instance. But it's nice to leave open the possibility to write -- Undecideable instance. But it's nice to leave open the possibility to write
-- fields you can't read (e.g. `wmName =: ...`). -- fields you can't read (e.g. `wmName =: ...`).
instance UpdateableClass s x y => SettableClass s x y where instance UpdateableClass s x y => SettableClass s x y where
s =: y = s =. const y s =: y = s =. const y
data Settable x l = Settable (XConfig l -> x) -- getter data Settable x c = Settable (c -> x) -- getter
(x -> XConfig l -> XConfig l) -- setter (x -> c -> c) -- setter
instance UpdateableClass (Settable x) x x where instance UpdateableClass (Settable x) x x where
(Settable g s =. f) c = return $ s (f $ g c) c (Settable g s =. f) c = return $ s (f $ g c) c
-- | Non-focused windows border color. Default: @\"#dddddd\"@ -- | Non-focused windows border color. Default: @\"#dddddd\"@
normalBorderColor :: Settable String l normalBorderColor :: Settable String (XConfig l)
normalBorderColor = Settable X.normalBorderColor (\x c -> c { X.normalBorderColor = x }) normalBorderColor = Settable X.normalBorderColor (\x c -> c { X.normalBorderColor = x })
-- | Focused windows border color. Default: @\"#ff0000\"@ -- | Focused windows border color. Default: @\"#ff0000\"@
focusedBorderColor :: Settable String l focusedBorderColor :: Settable String (XConfig l)
focusedBorderColor = Settable X.focusedBorderColor (\x c -> c { X.focusedBorderColor = x }) focusedBorderColor = Settable X.focusedBorderColor (\x c -> c { X.focusedBorderColor = x })
-- | The preferred terminal application. Default: @\"xterm\"@ -- | The preferred terminal application. Default: @\"xterm\"@
terminal :: Settable String l terminal :: Settable String (XConfig l)
terminal = Settable X.terminal (\x c -> c { X.terminal = x }) terminal = Settable X.terminal (\x c -> c { X.terminal = x })
-- | The mod modifier, as used by key bindings. Default: @mod1Mask@ (which is -- | The mod modifier, as used by key bindings. Default: @mod1Mask@ (which is
-- probably alt on your computer). -- probably alt on your computer).
modMask :: Settable KeyMask l modMask :: Settable KeyMask (XConfig l)
modMask = Settable X.modMask (\x c -> c { X.modMask = x }) modMask = Settable X.modMask (\x c -> c { X.modMask = x })
-- | The border width (in pixels). Default: @1@ -- | The border width (in pixels). Default: @1@
borderWidth :: Settable Dimension l borderWidth :: Settable Dimension (XConfig l)
borderWidth = Settable X.borderWidth (\x c -> c { X.borderWidth = x }) borderWidth = Settable X.borderWidth (\x c -> c { X.borderWidth = x })
-- | Whether window focus follows the mouse cursor on move, or requires a mouse -- | Whether window focus follows the mouse cursor on move, or requires a mouse
-- click. (Mouse? What's that?) Default: @True@ -- click. (Mouse? What's that?) Default: @True@
focusFollowsMouse :: Settable Bool l focusFollowsMouse :: Settable Bool (XConfig l)
focusFollowsMouse = Settable X.focusFollowsMouse (\x c -> c { X.focusFollowsMouse = x }) focusFollowsMouse = Settable X.focusFollowsMouse (\x c -> c { X.focusFollowsMouse = x })
-- | If True, a mouse click on an inactive window focuses it, but the click is -- | If True, a mouse click on an inactive window focuses it, but the click is
-- not passed to the window. If False, the click is also passed to the window. -- not passed to the window. If False, the click is also passed to the window.
-- Default @True@ -- Default @True@
clickJustFocuses :: Settable Bool l clickJustFocuses :: Settable Bool (XConfig l)
clickJustFocuses = Settable X.clickJustFocuses (\x c -> c { X.clickJustFocuses = x }) clickJustFocuses = Settable X.clickJustFocuses (\x c -> c { X.clickJustFocuses = x })
-- $summables -- $summables
@@ -230,12 +243,12 @@ clickJustFocuses = Settable X.clickJustFocuses (\x c -> c { X.clickJustFocuses =
class SummableClass s y | s -> y where class SummableClass s y | s -> y where
-- | This lets you add to an attribute. -- | This lets you add to an attribute.
(=+) :: s l -> y -> Prime l l (=+) :: s c -> y -> Arr c c
infix 0 =+ infix 0 =+
data Summable x y l = Summable (XConfig l -> x) -- getter data Summable x y c = Summable (c -> x) -- getter
(x -> XConfig l -> XConfig l) -- setter (x -> c -> c) -- setter
(x -> y -> x) -- accumulator (x -> y -> x) -- accumulator
instance UpdateableClass (Summable x y) x x where instance UpdateableClass (Summable x y) x x where
(Summable g s _ =. f) c = return $ s (f $ g c) c (Summable g s _ =. f) c = return $ s (f $ g c) c
@@ -255,7 +268,7 @@ instance SummableClass (Summable x y) y where
-- > manageHook =+ (className =? "Vim" --> doF shiftMaster) -- > manageHook =+ (className =? "Vim" --> doF shiftMaster)
-- --
-- Note that operator precedence mandates the parentheses here. -- Note that operator precedence mandates the parentheses here.
manageHook :: Summable ManageHook ManageHook l manageHook :: Summable ManageHook ManageHook (XConfig l)
manageHook = Summable X.manageHook (\x c -> c { X.manageHook = x }) (<+>) manageHook = Summable X.manageHook (\x c -> c { X.manageHook = x }) (<+>)
-- | Custom X event handler. Return @All True@ if the default handler should -- | Custom X event handler. Return @All True@ if the default handler should
@@ -264,7 +277,7 @@ manageHook = Summable X.manageHook (\x c -> c { X.manageHook = x }) (<+>)
-- > import XMonad.Hooks.ServerMode -- > import XMonad.Hooks.ServerMode
-- > ... -- > ...
-- > handleEventHook =+ serverModeEventHook -- > handleEventHook =+ serverModeEventHook
handleEventHook :: Summable (Event -> X All) (Event -> X All) l handleEventHook :: Summable (Event -> X All) (Event -> X All) (XConfig l)
handleEventHook = Summable X.handleEventHook (\x c -> c { X.handleEventHook = x }) (<+>) handleEventHook = Summable X.handleEventHook (\x c -> c { X.handleEventHook = x }) (<+>)
-- | List of workspaces' names. Default: @map show [1 .. 9 :: Int]@. Adding -- | List of workspaces' names. Default: @map show [1 .. 9 :: Int]@. Adding
@@ -273,11 +286,9 @@ handleEventHook = Summable X.handleEventHook (\x c -> c { X.handleEventHook = x
-- > workspaces =+ ["0"] -- > workspaces =+ ["0"]
-- --
-- This is useless unless you also create keybindings for this. -- This is useless unless you also create keybindings for this.
workspaces :: Summable [String] [String] l workspaces :: Summable [String] [String] (XConfig l)
workspaces = Summable X.workspaces (\x c -> c { X.workspaces = x }) (++) workspaces = Summable X.workspaces (\x c -> c { X.workspaces = x }) (++)
-- TODO: Rework the workspaces thing to pair names with keybindings.
-- | The action to perform when the windows set is changed. This happens -- | The action to perform when the windows set is changed. This happens
-- whenever focus change, a window is moved, etc. @logHook =+@ takes an @X ()@ -- whenever focus change, a window is moved, etc. @logHook =+@ takes an @X ()@
-- and appends it via '(>>)'. For instance: -- and appends it via '(>>)'. For instance:
@@ -290,7 +301,7 @@ workspaces = Summable X.workspaces (\x c -> c { X.workspaces = x }) (++)
-- @MonadIO m => m ()@), you'll need to explicitly annotate it, like so: -- @MonadIO m => m ()@), you'll need to explicitly annotate it, like so:
-- --
-- > logHook =+ (io $ putStrLn "Hello, world!" :: X ()) -- > logHook =+ (io $ putStrLn "Hello, world!" :: X ())
logHook :: Summable (X ()) (X ()) l logHook :: Summable (X ()) (X ()) (XConfig l)
logHook = Summable X.logHook (\x c -> c { X.logHook = x }) (P.>>) logHook = Summable X.logHook (\x c -> c { X.logHook = x }) (P.>>)
-- | The action to perform on startup. @startupHook =+@ takes an @X ()@ and -- | The action to perform on startup. @startupHook =+@ takes an @X ()@ and
@@ -303,7 +314,7 @@ logHook = Summable X.logHook (\x c -> c { X.logHook = x }) (P.>>)
-- Note that if your expression is parametrically typed (e.g. of type -- Note that if your expression is parametrically typed (e.g. of type
-- @MonadIO m => m ()@), you'll need to explicitly annotate it, as documented -- @MonadIO m => m ()@), you'll need to explicitly annotate it, as documented
-- in 'logHook'. -- in 'logHook'.
startupHook :: Summable (X ()) (X ()) l startupHook :: Summable (X ()) (X ()) (XConfig l)
startupHook = Summable X.startupHook (\x c -> c { X.startupHook = x }) (P.>>) startupHook = Summable X.startupHook (\x c -> c { X.startupHook = x }) (P.>>)
-- | The client events that xmonad is interested in. This is useful in -- | The client events that xmonad is interested in. This is useful in
@@ -311,14 +322,14 @@ startupHook = Summable X.startupHook (\x c -> c { X.startupHook = x }) (P.>>)
-- enterWindowMask .|. propertyChangeMask@ -- enterWindowMask .|. propertyChangeMask@
-- --
-- > clientMask =+ keyPressMask .|. keyReleaseMask -- > clientMask =+ keyPressMask .|. keyReleaseMask
clientMask :: Summable EventMask EventMask l clientMask :: Summable EventMask EventMask (XConfig l)
clientMask = Summable X.clientMask (\x c -> c { X.clientMask = x }) (.|.) clientMask = Summable X.clientMask (\x c -> c { X.clientMask = x }) (.|.)
-- | The root events that xmonad is interested in. This is useful in -- | The root events that xmonad is interested in. This is useful in
-- combination with handleEventHook. Default: @substructureRedirectMask .|. -- combination with handleEventHook. Default: @substructureRedirectMask .|.
-- substructureNotifyMask .|. enterWindowMask .|. leaveWindowMask .|. -- substructureNotifyMask .|. enterWindowMask .|. leaveWindowMask .|.
-- structureNotifyMask .|. buttonPressMask@ -- structureNotifyMask .|. buttonPressMask@
rootMask :: Summable EventMask EventMask l rootMask :: Summable EventMask EventMask (XConfig l)
rootMask = Summable X.rootMask (\x c -> c { X.rootMask = x }) (.|.) rootMask = Summable X.rootMask (\x c -> c { X.rootMask = x }) (.|.)
-- $removables -- $removables
@@ -327,26 +338,17 @@ rootMask = Summable X.rootMask (\x c -> c { X.rootMask = x }) (.|.)
class RemovableClass r y | r -> y where class RemovableClass r y | r -> y where
-- | This lets you remove from an attribute. -- | This lets you remove from an attribute.
(=-) :: r l -> y -> Prime l l (=-) :: r c -> y -> Arr c c
infix 0 =- infix 0 =-
data Keys (l :: * -> *) = Keys data Keys c = Keys { kAdd :: [(String, X ())] -> c -> c,
kRemove :: [String] -> c -> c }
-- Note that since checkKeymap happens on newKeys, it doesn't check for
-- duplicates between repeated applications. Probably OK. (Especially since
-- overriding defaults is a common behavior.) Also note that there's no
-- reference cycle here. Yay!
instance UpdateableClass Keys (XConfig Layout -> M.Map (ButtonMask, KeySym) (X ())) [(String, X ())] where
(_ =. f) c = return c { X.keys = \c' -> mkKeymap c' newKeys,
X.startupHook = (P.>>) (X.startupHook c) (checkKeymap c newKeys) }
where newKeys = f $ X.keys c
instance SummableClass Keys [(String, X ())] where instance SummableClass Keys [(String, X ())] where
(_ =+ newKeys) c = return (c `additionalKeysP` newKeys) { X.startupHook = (P.>>) (X.startupHook c) (checkKeymap c newKeys) } Keys { kAdd = a } =+ newKeys = return . a newKeys
instance RemovableClass Keys [String] where instance RemovableClass Keys [String] where
(_ =- sadKeys) c = return (c `removeKeysP` sadKeys) Keys { kRemove = r } =- sadKeys = return . r sadKeys
-- | Key bindings to 'X' actions. Default: see @`man xmonad`@. 'keys' -- | Key bindings to 'X' actions. Default: see @`man xmonad`@. 'keys'
-- takes a list of keybindings specified emacs-style, as documented in -- takes a list of keybindings specified emacs-style, as documented in
@@ -355,16 +357,24 @@ instance RemovableClass Keys [String] where
-- --
-- > keys =- ["M-S-c"] -- > keys =- ["M-S-c"]
-- > keys =+ [("M-M1-x", kill)] -- > keys =+ [("M-M1-x", kill)]
keys :: Keys l keys :: Keys (XConfig l)
keys = Keys keys = Keys {
-- Note that since checkKeymap happens on newKeys, it doesn't check for
-- duplicates between repeated applications. Probably OK. (Especially since
-- overriding defaults is a common behavior.) Also note that there's no
-- reference cycle here. Yay!
kAdd = \newKeys c -> (c `additionalKeysP` newKeys) { X.startupHook = (P.>>) (X.startupHook c) (checkKeymap c newKeys) },
kRemove = flip removeKeysP
}
data MouseBindings (l :: * -> *) = MouseBindings data MouseBindings c = MouseBindings { mAdd :: [((ButtonMask, Button), Window -> X ())] -> c -> c,
mRemove :: [(ButtonMask, Button)] -> c -> c }
instance SummableClass MouseBindings [((ButtonMask, Button), Window -> X ())] where instance SummableClass MouseBindings [((ButtonMask, Button), Window -> X ())] where
(_ =+ newBindings) c = return (c `additionalMouseBindings` newBindings) MouseBindings { mAdd = a } =+ newBindings = return . a newBindings
instance RemovableClass MouseBindings [(ButtonMask, Button)] where instance RemovableClass MouseBindings [(ButtonMask, Button)] where
(_ =- sadBindings) c = return (c `removeMouseBindings` sadBindings) MouseBindings { mRemove = r } =- sadBindings = return . r sadBindings
-- | Mouse button bindings to an 'X' actions on a window. Default: see @`man -- | Mouse button bindings to an 'X' actions on a window. Default: see @`man
-- xmonad`@. To make mod-<scrollwheel> switch workspaces: -- xmonad`@. To make mod-<scrollwheel> switch workspaces:
@@ -376,8 +386,93 @@ instance RemovableClass MouseBindings [(ButtonMask, Button)] where
-- --
-- Note that you need to specify the numbered mod-mask e.g. 'mod4Mask' instead -- Note that you need to specify the numbered mod-mask e.g. 'mod4Mask' instead
-- of just 'modMask'. -- of just 'modMask'.
mouseBindings :: MouseBindings l mouseBindings :: MouseBindings (XConfig l)
mouseBindings = MouseBindings mouseBindings = MouseBindings {
mAdd = flip additionalMouseBindings,
mRemove = flip removeMouseBindings
}
-- $workspaces
-- Workspaces can be configured through 'workspaces', but then the 'keys' need
-- to be set, and this can be a bit laborious. 'withWorkspaces' provides a
-- convenient mechanism for common workspace updates.
-- | Configure workspaces through a Prime-like interface. Example:
--
-- > withWorkspaces $ do
-- > wsKeyspecs =+ ["0"]
-- > wsActions =+ [("M-M1-", windows . swapWithCurrent)]
-- > wsSetName 1 "mail"
--
-- This will set 'workspaces' and add the necessary keybindings to 'keys'. Note
-- that it won't remove old keybindings; it's just not that clever.
withWorkspaces :: Arr WorkspaceConfig WorkspaceConfig -> Prime l l
withWorkspaces wsarr xconf = (P.>>=) (wsarr def) $ \wsconf -> wsprime wsconf xconf
where wsprime :: WorkspaceConfig -> Prime l l
wsprime wsconf =
(workspaces =: allNames) >>
(keys =+ [(mod ++ key, action name) | (name, key) <- zip allNames (wsKeyspecs_ wsconf),
(mod, action) <- wsActions_ wsconf])
where allNames = zipWith chooseName (wsNames_ wsconf) (wsKeyspecs_ wsconf)
chooseName name keyspec = if not (null name) then name else keyspec
data WorkspaceConfig = WorkspaceConfig {
wsNames_ :: [String],
wsKeyspecs_ :: [String],
wsActions_ :: [(String, String -> X ())]
}
instance Default WorkspaceConfig where
def = WorkspaceConfig {
wsNames_ = repeat "",
wsKeyspecs_ = map (:[]) ['1'..'9'], -- The hungry monkey eats dots and turns them into numbers.
wsActions_ = [("M-", windows . W.greedyView),
("M-S-", windows . W.shift)]
}
-- | The list of workspace names, like 'workspaces' but with two differences:
--
-- 1. If any entry is the empty string, it'll be replaced with the
-- corresponding entry in 'wsKeyspecs'.
-- 2. The list is truncated to the size of 'wsKeyspecs'.
--
-- The default value is @'repeat' ""@.
--
-- If you'd like to create workspaces without associated keyspecs, you can do
-- that afterwards, outside the 'withWorkspaces' block, with @'workspaces' =+@.
wsNames :: Settable [String] WorkspaceConfig
wsNames = Settable wsNames_ (\x c -> c { wsNames_ = x })
-- | The list of workspace keys. These are combined with the modifiers in
-- 'wsActions' to form the keybindings for navigating to workspaces. Default:
-- @["1","2",...,"9"]@.
wsKeyspecs :: Summable [String] [String] WorkspaceConfig
wsKeyspecs = Summable wsKeyspecs_ (\x c -> c { wsKeyspecs_ = x }) (++)
-- | Mapping from key prefix to command. Its type is @[(String, String ->
-- X())]@. The key prefix may be a modifier such as @\"M-\"@, or a submap
-- prefix such as @\"M-a \"@. The command is a function that takes a workspace
-- name and returns an @X ()@. 'withWorkspaces' creates keybindings for the
-- cartesian product of 'wsKeyspecs' and 'wsActions'.
--
-- Default:
--
-- > [("M-", windows . W.greedyView),
-- > ("M-S-", windows . W.shift)]
wsActions :: Summable [(String, String -> X ())] [(String, String -> X ())] WorkspaceConfig
wsActions = Summable wsActions_ (\x c -> c { wsActions_ = x }) (++)
-- | A convenience for just modifying one entry in 'wsNames', in case you only
-- want a few named workspaces. Example:
--
-- > wsSetName 1 "mail"
-- > wsSetName 2 "web"
wsSetName :: Int -> String -> Arr WorkspaceConfig WorkspaceConfig
wsSetName index newName = wsNames =. (map maybeSet . zip [0..])
where maybeSet (i, oldName) | i == (index - 1) = newName
| otherwise = oldName
-- TODO: Something for screens, too.
-- $layout -- $layout
-- Layouts are special. You can't modify them using the @=:@ or @=.@ operator. -- Layouts are special. You can't modify them using the @=:@ or @=.@ operator.
@@ -460,6 +555,7 @@ applyIO = id -- This is here in case we want to change the Prime type later.
-- > import XMonad.Config.Prime -- > import XMonad.Config.Prime
-- > -- >
-- > import XMonad.Actions.CycleWS (prevWS, nextWS) -- > import XMonad.Actions.CycleWS (prevWS, nextWS)
-- > import XMonad.Actions.SwapWorkspaces (swapWithCurrent)
-- > import XMonad.Actions.WindowNavigation (withWindowNavigation) -- > import XMonad.Actions.WindowNavigation (withWindowNavigation)
-- > import XMonad.Layout.Fullscreen (fullscreenSupport) -- > import XMonad.Layout.Fullscreen (fullscreenSupport)
-- > import XMonad.Layout.NoBorders (smartBorders) -- > import XMonad.Layout.NoBorders (smartBorders)
@@ -474,6 +570,9 @@ applyIO = id -- This is here in case we want to change the Prime type later.
-- > modifyLayout smartBorders -- > modifyLayout smartBorders
-- > apply fullscreenSupport -- > apply fullscreenSupport
-- > applyIO $ withWindowNavigation (xK_w, xK_a, xK_s, xK_d) -- > applyIO $ withWindowNavigation (xK_w, xK_a, xK_s, xK_d)
-- > withWorkspaces $ do
-- > wsKeyspecs =+ ["0"]
-- > wsActions =+ [("M-M1-", windows . swapWithCurrent)]
-- > keys =+ [ -- > keys =+ [
-- > ("M-,", sendMessage $ IncMasterN (-1)), -- > ("M-,", sendMessage $ IncMasterN (-1)),
-- > ("M-.", sendMessage $ IncMasterN 1), -- > ("M-.", sendMessage $ IncMasterN 1),