{-# LANGUAGE FlexibleContexts, FlexibleInstances, FunctionalDependencies, KindSignatures, MultiParamTypeClasses, UndecidableInstances #-} ----------------------------------------------------------------------------- -- | -- Module : XMonad.Config.Prime -- Copyright : Devin Mullins <devin.mullins@gmail.com> -- License : BSD-style (see LICENSE) -- -- Maintainer : Devin Mullins <devin.mullins@gmail.com> -- Stability : unstable -- Portability : unportable -- -- This is a draft of a brand new config syntax for xmonad. It aims to be: -- -- * easier to copy/paste snippets from the docs -- -- * easier to get the gist for what's going on, for you imperative programmers -- -- It's brand new, so it's pretty much guaranteed to break or change syntax. -- But what's the worst that could happen? Xmonad crashes and logs you out? -- It probably won't do that. Give it a try. -- ----------------------------------------------------------------------------- module XMonad.Config.Prime ( -- Note: The identifiers here are listed in the order that makes the most sense -- for a user, while the definitions below are listed in the order that makes -- the most sense for a developer. -- * Start here -- $start_here xmonad, nothing, -- * Attributes you can set -- $settables normalBorderColor, focusedBorderColor, terminal, modMask, borderWidth, focusFollowsMouse, clickJustFocuses, SettableClass(..), UpdateableClass(..), -- * Attributes you can add to -- $summables manageHook, handleEventHook, workspaces, logHook, startupHook, clientMask, rootMask, SummableClass(..), -- * Attributes you can add to or remove from -- $removables keys, mouseBindings, RemovableClass(..), -- * Modifying the list of workspaces -- $workspaces withWorkspaces, wsNames, wsKeys, wsActions, wsSetName, -- * Modifying the screen keybindings -- $screens withScreens, sKeys, sActions, onScreens, -- * Modifying the layoutHook -- $layout addLayout, resetLayout, modifyLayout, -- * Updating the XConfig en masse -- $update startWith, apply, applyIO, -- * The rest of the world -- | Everything you know and love from the core "XMonad" module is available -- for use in your config file, too. module XMonad, -- | (Almost) everything you know and love from the Haskell "Prelude" is -- available for use in your config file. Note that '>>' has been overriden, so -- if you want to create do-blocks for normal monads, you'll need some let -- statements or a separate module. (See the Troubleshooting section.) module Prelude, -- * Core -- | These are the building blocks on which the config language is built. -- Regular people shouldn't need to know about these. Prime, Arr, (>>), ifThenElse, -- * Example config -- $example -- * Troubleshooting -- $troubleshooting ) where import Prelude hiding ((>>), mod) import qualified Prelude as P ((>>=), (>>)) import Data.Monoid (All) import XMonad hiding (xmonad, XConfig(..)) import XMonad (XConfig(XConfig)) import qualified XMonad.StackSet as W import qualified XMonad as X (xmonad, XConfig(..)) import XMonad.Util.EZConfig (additionalKeysP, additionalMouseBindings, checkKeymap, removeKeysP, removeMouseBindings) -- $start_here -- To start with, create a @~\/.xmonad\/xmonad.hs@ that looks like this: -- -- > {-# LANGUAGE RebindableSyntax #-} -- > import XMonad.Config.Prime -- > -- > -- Imports go here. -- > -- > main = xmonad $ do -- > nothing -- > -- Configs go here. -- -- This will give you a default xmonad install, with room to grow. The lines -- starting with double dashes are comments. You may delete them. Note that -- Haskell is a bit precise about indentation. Make sure all the statements in -- your do-block start at the same column, and make sure that any multi-line -- statements are formatted with a hanging indent. (For an example, see the -- 'keys =+' statement in the /Example config/ section, below.) -- -- After changing your config file, restart xmonad with mod-q (where, by -- default, "mod" == "alt"). -- -- The Prime "Monad" -- -- | 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. type Prime l l' = Arr (XConfig l) (XConfig l') -- | An Arr is a generalization of Prime. Don't reference the type, if you can -- 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 -- | Because of RebindableSyntax, this is necessary to enable you to use -- if-then-else expressions. No need to call it directly. ifThenElse :: Bool -> a -> a -> a ifThenElse True a _ = a ifThenElse False _ b = b -- | This is the xmonad main function. It passes 'XMonad.Config.def' (the -- default 'XConfig') into your do-block, takes the modified config out of your -- do-block, and then runs xmonad. -- -- The do-block is a 'Prime'. Advanced readers can skip right to that -- definition. xmonad :: (Default a, Read (l Window), LayoutClass l Window) => (a -> IO (XConfig l)) -> IO () xmonad prime = (P.>>=) (prime def) X.xmonad -- | This doesn't modify the config in any way. It's just here for your initial -- config because Haskell doesn't allow empty do-blocks. Feel free to delete it -- once you've added other stuff. nothing :: Prime l l nothing = return -- $settables -- These are a bunch of attributes that you can set. Syntax looks like this: -- -- > terminal =: "urxvt" -- -- Strings are double quoted, Dimensions are unquoted integers, booleans are -- 'True' or 'False' (case-sensitive), and 'modMask' is usually 'mod1Mask' or -- 'mod4Mask'. class UpdateableClass s x y | s -> x y where -- | This lets you apply a function to an attribute (i.e. read, modify, write). (=.) :: s c -> (x -> y) -> Arr c c class SettableClass s x y | s -> x y where -- | This lets you modify an attribute. (=:) :: s c -> y -> Arr c c -- Undecideable instance. But it's nice to leave open the possibility to write -- fields you can't read (e.g. `wmName =: ...`). instance UpdateableClass s x y => SettableClass s x y where s =: y = s =. const y data Settable x c = Settable (c -> x) -- getter (x -> c -> c) -- setter instance UpdateableClass (Settable x) x x where (Settable g s =. f) c = return $ s (f $ g c) c -- | Non-focused windows border color. Default: @\"#dddddd\"@ normalBorderColor :: Settable String (XConfig l) normalBorderColor = Settable X.normalBorderColor (\x c -> c { X.normalBorderColor = x }) -- | Focused windows border color. Default: @\"#ff0000\"@ focusedBorderColor :: Settable String (XConfig l) focusedBorderColor = Settable X.focusedBorderColor (\x c -> c { X.focusedBorderColor = x }) -- | The preferred terminal application. Default: @\"xterm\"@ terminal :: Settable String (XConfig l) terminal = Settable X.terminal (\x c -> c { X.terminal = x }) -- | The mod modifier, as used by key bindings. Default: @mod1Mask@ (which is -- probably alt on your computer). modMask :: Settable KeyMask (XConfig l) modMask = Settable X.modMask (\x c -> c { X.modMask = x }) -- | The border width (in pixels). Default: @1@ borderWidth :: Settable Dimension (XConfig l) borderWidth = Settable X.borderWidth (\x c -> c { X.borderWidth = x }) -- | Whether window focus follows the mouse cursor on move, or requires a mouse -- click. (Mouse? What's that?) Default: @True@ focusFollowsMouse :: Settable Bool (XConfig l) 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 -- not passed to the window. If False, the click is also passed to the window. -- Default @True@ clickJustFocuses :: Settable Bool (XConfig l) clickJustFocuses = Settable X.clickJustFocuses (\x c -> c { X.clickJustFocuses = x }) -- $summables -- In addition to being able to set these attributes, they have a special -- syntax for being able to add to them. The operator is @=+@ (the plus comes -- /after/ the equals), but each attribute has a different syntax for what -- comes after the operator. class SummableClass s y | s -> y where -- | This lets you add to an attribute. (=+) :: s c -> y -> Arr c c infix 0 =+ data Summable x y c = Summable (c -> x) -- getter (x -> c -> c) -- setter (x -> y -> x) -- accumulator instance UpdateableClass (Summable x y) x x where (Summable g s _ =. f) c = return $ s (f $ g c) c instance SummableClass (Summable x y) y where (Summable g s a =+ y) c = return $ s (g c `a` y) c -- | The action to run when a new window is opened. Default: -- -- > manageHook =: composeAll [className =? "MPlayer" --> doFloat, className =? "Gimp" --> doFloat] -- -- To add more rules to this list, you can say, for instance: -- -- > import XMonad.StackSet -- > ... -- > manageHook =+ (className =? "Emacs" --> doF kill) -- > manageHook =+ (className =? "Vim" --> doF shiftMaster) -- -- Note that operator precedence mandates the parentheses here. manageHook :: Summable ManageHook ManageHook (XConfig l) manageHook = Summable X.manageHook (\x c -> c { X.manageHook = x }) (<+>) -- | Custom X event handler. Return @All True@ if the default handler should -- also be run afterwards. Default does nothing. To add an event handler: -- -- > import XMonad.Hooks.ServerMode -- > ... -- > handleEventHook =+ serverModeEventHook handleEventHook :: Summable (Event -> X All) (Event -> X All) (XConfig l) handleEventHook = Summable X.handleEventHook (\x c -> c { X.handleEventHook = x }) (<+>) -- | List of workspaces' names. Default: @map show [1 .. 9 :: Int]@. Adding -- appends to the end: -- -- > workspaces =+ ["0"] -- -- This is useless unless you also create keybindings for this. workspaces :: Summable [String] [String] (XConfig l) workspaces = Summable X.workspaces (\x c -> c { X.workspaces = x }) (++) -- | The action to perform when the windows set is changed. This happens -- whenever focus change, a window is moved, etc. @logHook =+@ takes an @X ()@ -- and appends it via '(>>)'. For instance: -- -- > import XMonad.Hooks.ICCCMFocus -- > ... -- > logHook =+ takeTopFocus -- -- Note that if your expression is parametrically typed (e.g. of type -- @MonadIO m => m ()@), you'll need to explicitly annotate it, like so: -- -- > logHook =+ (io $ putStrLn "Hello, world!" :: X ()) logHook :: Summable (X ()) (X ()) (XConfig l) logHook = Summable X.logHook (\x c -> c { X.logHook = x }) (P.>>) -- | The action to perform on startup. @startupHook =+@ takes an @X ()@ and -- appends it via '(>>)'. For instance: -- -- > import XMonad.Hooks.SetWMName -- > ... -- > startupHook =+ setWMName "LG3D" -- -- Note that if your expression is parametrically typed (e.g. of type -- @MonadIO m => m ()@), you'll need to explicitly annotate it, as documented -- in 'logHook'. startupHook :: Summable (X ()) (X ()) (XConfig l) startupHook = Summable X.startupHook (\x c -> c { X.startupHook = x }) (P.>>) -- | The client events that xmonad is interested in. This is useful in -- combination with handleEventHook. Default: @structureNotifyMask .|. -- enterWindowMask .|. propertyChangeMask@ -- -- > clientMask =+ keyPressMask .|. keyReleaseMask clientMask :: Summable EventMask EventMask (XConfig l) clientMask = Summable X.clientMask (\x c -> c { X.clientMask = x }) (.|.) -- | The root events that xmonad is interested in. This is useful in -- combination with handleEventHook. Default: @substructureRedirectMask .|. -- substructureNotifyMask .|. enterWindowMask .|. leaveWindowMask .|. -- structureNotifyMask .|. buttonPressMask@ rootMask :: Summable EventMask EventMask (XConfig l) rootMask = Summable X.rootMask (\x c -> c { X.rootMask = x }) (.|.) -- $removables -- The following support the the @=+@ for adding items and the @=-@ operator -- for removing items. class RemovableClass r y | r -> y where -- | This lets you remove from an attribute. (=-) :: r c -> y -> Arr c c infix 0 =- data Keys c = Keys { kAdd :: [(String, X ())] -> c -> c, kRemove :: [String] -> c -> c } instance SummableClass Keys [(String, X ())] where Keys { kAdd = a } =+ newKeys = return . a newKeys instance RemovableClass Keys [String] where Keys { kRemove = r } =- sadKeys = return . r sadKeys -- | Key bindings to 'X' actions. Default: see @`man xmonad`@. 'keys' -- takes a list of keybindings specified emacs-style, as documented in -- 'XMonad.Util.EZConfig.mkKeyMap'. For example, to change the "kill window" -- key: -- -- > keys =- ["M-S-c"] -- > keys =+ [("M-M1-x", kill)] keys :: Keys (XConfig l) 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 c = MouseBindings { mAdd :: [((ButtonMask, Button), Window -> X ())] -> c -> c, mRemove :: [(ButtonMask, Button)] -> c -> c } instance SummableClass MouseBindings [((ButtonMask, Button), Window -> X ())] where MouseBindings { mAdd = a } =+ newBindings = return . a newBindings instance RemovableClass MouseBindings [(ButtonMask, Button)] where MouseBindings { mRemove = r } =- sadBindings = return . r sadBindings -- | Mouse button bindings to an 'X' actions on a window. Default: see @`man -- xmonad`@. To make mod-<scrollwheel> switch workspaces: -- -- > import XMonad.Actions.CycleWS (nextWS, prevWS) -- > ... -- > mouseBindings =+ [((mod4Mask, button4), const prevWS), -- > ((mod4Mask, button5), const nextWS)] -- -- Note that you need to specify the numbered mod-mask e.g. 'mod4Mask' instead -- of just 'modMask'. mouseBindings :: MouseBindings (XConfig l) 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 -- > wsKeys =+ ["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 (wsKeys_ wsconf), (mod, action) <- wsActions_ wsconf]) where allNames = zipWith chooseName (wsNames_ wsconf) (wsKeys_ wsconf) chooseName name keyspec = if not (null name) then name else keyspec data WorkspaceConfig = WorkspaceConfig { wsNames_ :: [String], wsKeys_ :: [String], wsActions_ :: [(String, String -> X ())] } instance Default WorkspaceConfig where def = WorkspaceConfig { wsNames_ = repeat "", wsKeys_ = 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 'wsKeys'. -- 2. The list is truncated to the size of 'wsKeys'. -- -- 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"]@. wsKeys :: Summable [String] [String] WorkspaceConfig wsKeys = Summable wsKeys_ (\x c -> c { wsKeys_ = 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 \"@, or both, as in @\"M-a M-\"@. The command is a -- function that takes a workspace name and returns an @X ()@. 'withWorkspaces' -- creates keybindings for the cartesian product of 'wsKeys' 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 -- $screens -- 'withScreens' provides a convenient mechanism to set keybindings for moving -- between screens, much like 'withWorkspaces'. -- | Configure screen keys through a Prime-like interface: -- -- > withScreens $ do -- > sKeys =: ["e", "r"] -- -- This will add the necessary keybindings to 'keys'. Note that it won't remove -- old keybindings; it's just not that clever. withScreens :: Arr ScreenConfig ScreenConfig -> Prime l l withScreens sarr xconf = (P.>>=) (sarr def) $ \sconf -> sprime sconf xconf where sprime :: ScreenConfig -> Prime l l sprime sconf = (keys =+ [(mod ++ key, action sid) | (sid, key) <- zip [0..] (sKeys_ sconf), (mod, action) <- sActions_ sconf]) data ScreenConfig = ScreenConfig { sKeys_ :: [String], sActions_ :: [(String, ScreenId -> X ())] } instance Default ScreenConfig where def = ScreenConfig { sKeys_ = ["w", "e", "r"], sActions_ = [("M-", windows . onScreens W.view), ("M-S-", windows . onScreens W.shift)] } -- | The list of screen keys. These are combined with the modifiers in -- 'sActions' to form the keybindings for navigating to workspaces. Default: -- @["w","e","r"]@. sKeys :: Summable [String] [String] ScreenConfig sKeys = Summable sKeys_ (\x c -> c { sKeys_ = x }) (++) -- | Mapping from key prefix to command. Its type is @[(String, ScreenId -> -- X())]@. Works the same as 'wsActions' except for a different function type. -- -- Default: -- -- > [("M-", windows . onScreens W.view), -- > ("M-S-", windows . onScreens W.shift)] sActions :: Summable [(String, ScreenId -> X ())] [(String, ScreenId -> X ())] ScreenConfig sActions = Summable sActions_ (\x c -> c { sActions_ = x }) (++) -- | Converts a stackset transformer parameterized on the workspace type into one -- parameterized on the screen type. For example, you can use @onScreens W.view -- 0@ to navigate to the workspace on the 0th screen. If the screen id is not -- recognized, the returned transformer acts as an identity function. onScreens :: Eq s => (i -> W.StackSet i l a s sd -> W.StackSet i l a s sd) -> s -> W.StackSet i l a s sd -> W.StackSet i l a s sd onScreens f sc ws = maybe id f (W.lookupWorkspace sc ws) ws -- $layout -- Layouts are special. You can't modify them using the @=:@ or @=.@ operator. -- You need to use the following functions. -- | Add a layout to the list of layouts choosable with mod-space. For instance: -- -- > import XMonad.Layout.Tabbed -- > ... -- > addLayout simpleTabbed addLayout :: (LayoutClass l Window, LayoutClass r Window) => r Window -> Prime l (Choose l r) addLayout r c = return c { X.layoutHook = X.layoutHook c ||| r } -- | Reset the layoutHook from scratch. For instance, to get rid of the wide -- layout: -- -- > resetLayout $ Tall 1 (3/100) (1/2) ||| Full -- -- (The dollar is like an auto-closing parenthesis, so all the stuff to the -- right of it is treated like an argument to resetLayout.) resetLayout :: (LayoutClass r Window) => r Window -> Prime l r resetLayout r c = return c { X.layoutHook = r } -- | Modify your 'layoutHook' with some wrapper function. You probably want to call -- this after you're done calling 'addLayout'. Example: -- -- > import XMonad.Layout.NoBorders -- > ... -- > modifyLayout smartBorders modifyLayout :: (LayoutClass r Window) => (l Window -> r Window) -> Prime l r modifyLayout f c = return c { X.layoutHook = f $ X.layoutHook c } -- $update -- Finally, there are a few contrib modules that bundle multiple attribute -- updates together. There are three types: 1) wholesale replacements for the -- default config, 2) pure functions on the config, and 3) IO actions on the -- config. The syntax for each is different. Examples: -- -- 1) To start with a 'XMonad.Config.Gnome.gnomeConfig' instead of the default, -- we use 'startWith': -- -- > import XMonad.Config.Gnome -- > ... -- > startWith gnomeConfig -- -- 2) 'XMonad.Hooks.UrgencyHook.withUrgencyHook' is a pure function, so we need -- to use 'apply': -- -- > import XMonad.Hooks.UrgencyHook -- > ... -- > apply $ withUrgencyHook dzenUrgencyHook -- -- 3) 'XMonad.Hooks.DynamicLog.xmobar' returns an @IO (XConfig l)@, so we need -- to use 'applyIO': -- -- > import XMonad.Hooks.DynamicLog -- > ... -- > applyIO xmobar -- | Replace the current 'XConfig' with the given one. If you use this, you -- probably want it to be the first line of your config. startWith :: XConfig l' -> Prime l l' startWith = const . return -- | Turns a pure function on 'XConfig' into a 'Prime'. apply :: (XConfig l -> XConfig l') -> Prime l l' apply f = return . f -- | Turns an IO function on 'XConfig' into a 'Prime'. applyIO :: (XConfig l -> IO (XConfig l')) -> Prime l l' applyIO = id -- This is here in case we want to change the Prime type later. -- $example -- As an example, I've included below a subset of my current config. Note that -- my import statements specify individual identifiers in parentheticals. -- That's optional. The default is to import the entire module. I just find it -- helpful to remind me where things came from. -- -- > {-# LANGUAGE RebindableSyntax #-} -- > import XMonad.Config.Prime -- > -- > import XMonad.Actions.CycleWS (prevWS, nextWS) -- > import XMonad.Actions.SwapWorkspaces (swapWithCurrent) -- > import XMonad.Actions.WindowNavigation (withWindowNavigation) -- > import XMonad.Layout.Fullscreen (fullscreenSupport) -- > import XMonad.Layout.NoBorders (smartBorders) -- > import XMonad.Layout.Tabbed (simpleTabbed) -- > -- > main = xmonad $ do -- > modMask =: mod4Mask -- > normalBorderColor =: "#222222" -- > terminal =: "urxvt" -- > focusFollowsMouse =: False -- > resetLayout $ Tall 1 (3/100) (1/2) ||| simpleTabbed -- > modifyLayout smartBorders -- > apply fullscreenSupport -- > applyIO $ withWindowNavigation (xK_w, xK_a, xK_s, xK_d) -- > withWorkspaces $ do -- > wsKeys =+ ["0"] -- > wsActions =+ [("M-M1-", windows . swapWithCurrent)] -- > keys =+ [ -- > ("M-,", sendMessage $ IncMasterN (-1)), -- > ("M-.", sendMessage $ IncMasterN 1), -- > ("M-M1-d", spawn "date | dzen2 -fg '#eeeeee' -p 2"), -- > ("C-S-q", return ()), -- > ("<XF86AudioLowerVolume>", spawn "amixer set Master 5%-"), -- > ("<XF86AudioRaiseVolume>", spawn "amixer set Master 5%+"), -- > ("M-M1-x", kill), -- > ("M-i", prevWS), -- > ("M-o", nextWS) -- > ] -- $troubleshooting -- === Only the last line of my config seems to take effect. What gives? -- You're missing the @{-\# LANGUAGE RebindableSyntax \#-}@ line at the top. -- -- === How do I do use normal monads like 'X' or 'IO'? -- Here are a couple of ways: -- -- > import qualified Prelude as P -- > ... -- > test1, test2 :: X () -- > test1 = spawn "echo Hi" P.>> spawn "echo Bye" -- > test2 = do spawn "echo Hi" -- > spawn "echo Bye" -- > where (>>) = (P.>>) -- -- === How do I use the old keyboard syntax? -- You can use 'apply' and supply your own Haskell function. For instance: -- -- > apply $ flip additionalKeys $ [((mod1Mask, xK_z), spawn "date | dzen2 -fg '#eeeeee' -p 2")] -- -- === How do I run a command before xmonad starts (like 'spawnPipe')? -- If you're using it for a status bar, see if 'XMonad.Hooks.DynamicLog.dzen' -- or 'XMonad.Hooks.DynamicLog.xmobar' does what you want. If so, you can apply -- it with 'applyIO'. -- -- If not, you can write your own @XConfig l -> IO (XConfig l)@ and apply it -- with 'applyIO'. When writing this function, see the above tip about using -- normal monads. -- -- Alternatively, you could do something like this this: -- -- > import qualified Prelude as P (>>) -- > -- > main = -- > openFile ".xmonad.log" AppendMode >>= \log -> -- > hSetBuffering log LineBuffering P.>> -- > (xmonad $ do -- > nothing -- Prime config here. -- > )