Xmonad/Config archive/Brent Yorgey's darcs xmonad.hs: Difference between revisions

From HaskellWiki
(update to my latest xmonad.hs)
(update my xmonad.hs)
 
(One intermediate revision by one other user not shown)
Line 19: Line 19:
</pre>
</pre>


My current config file, which works with the latest development xmonad and xmonad-contrib, annotated to show which extensions are being used where:
My current config file, which works with the latest development xmonad and xmonad-contrib (and probably with 0.10), annotated to show which extensions are being used where:


<haskell>
<haskell>
Line 26: Line 26:
import qualified XMonad.StackSet as W  -- (0a) window stack manipulation
import qualified XMonad.StackSet as W  -- (0a) window stack manipulation
import qualified Data.Map as M        -- (0b) map creation
import qualified Data.Map as M        -- (0b) map creation
import Data.List (isPrefixOf, (\\))
import Data.List ((\\), find)
import Data.Maybe (isNothing, isJust, catMaybes)
import Data.Maybe (isJust, catMaybes)
import Data.Monoid


import System.Posix.Unistd
import System.Posix.Unistd
import Control.Concurrent (threadDelay)
import XMonad.Layout.MagicFocus    -- (0c)


-- Hooks -----------------------------------------------------
-- Hooks -----------------------------------------------------
Line 48: Line 52:
import XMonad.Layout.Grid          -- (6)  grid layout
import XMonad.Layout.Grid          -- (6)  grid layout
import XMonad.Layout.TwoPane
import XMonad.Layout.TwoPane
import XMonad.Layout.Accordion
import XMonad.Layout.NoBorders    -- (7)  get rid of borders sometimes
import XMonad.Layout.NoBorders    -- (7)  get rid of borders sometimes
                                   -- (8)  navigate between windows
                                   -- (8)  navigate between windows
Line 58: Line 63:
import XMonad.Layout.MultiToggle  -- (14) apply layout modifiers dynamically
import XMonad.Layout.MultiToggle  -- (14) apply layout modifiers dynamically
import XMonad.Layout.MultiToggle.Instances
import XMonad.Layout.MultiToggle.Instances
                                 
                                   -- (15) ability to magnify the focused
                                   -- (15) ability to magnify the focused
                                   --      window
                                   --      window
import qualified XMonad.Layout.Magnifier as Mag
import qualified XMonad.Layout.Magnifier as Mag


import XMonad.Layout.Gaps
import XMonad.Layout.Gaps         -- (15a) add manual gaps around the sides
                                  --      of the screen useful for things
                                  --      like screencasts or projectors
                                  --      which cut off part of the screen
 
import XMonad.Layout.Combo        -- (15b) combine layouts


-- Actions ---------------------------------------------------
-- Actions ---------------------------------------------------
Line 68: Line 79:
import XMonad.Actions.CycleWS      -- (16) general workspace-switching
import XMonad.Actions.CycleWS      -- (16) general workspace-switching
                                   --      goodness
                                   --      goodness
import XMonad.Actions.CycleRecentWS
import XMonad.Actions.CycleRecentWS -- (17) cycle between workspaces
                                  -- (17) more flexible window resizing
                                    --      in most-recently-used order
import qualified XMonad.Actions.FlexibleManipulate as Flex
import XMonad.Actions.Warp        -- (18) warp the mouse pointer
import XMonad.Actions.Warp        -- (18) warp the mouse pointer
import XMonad.Actions.Submap      -- (19) create keybinding submaps
import XMonad.Actions.Submap      -- (19) create keybinding submaps
import XMonad.Actions.Search       -- (20) some predefined web searches
import XMonad.Actions.Search hiding (Query, images)
import XMonad.Actions.WindowGo     -- (21) runOrRaise
                                  -- (20) some predefined web searches
import XMonad.Actions.UpdatePointer -- (22) auto-warp the pointer to the LR
-- import XMonad.Actions.WindowGo -- (21) runOrRaise
                                    --     corner of the focused window
import XMonad.Actions.WithAll      -- (22) do something with all windows on a workspace
import XMonad.Actions.WithAll
import XMonad.Actions.SpawnOn      -- (22a) start programs on a particular WS
import XMonad.Actions.SpawnOn
 
import XMonad.Actions.TopicSpace  -- (22b) set a "topic" for each workspace
import XMonad.Actions.DynamicWorkspaces 
                                  -- (22c)


import XMonad.Actions.TopicSpace
import XMonad.Actions.DynamicWorkspaceGroups
import XMonad.Actions.DynamicWorkspaces
                                  -- (22d)
 
import qualified XMonad.Actions.DynamicWorkspaceOrder as DO
                                  -- (22e)
 
import XMonad.Actions.FloatKeys    -- (22f)


-- Prompts ---------------------------------------------------
-- Prompts ---------------------------------------------------
Line 93: Line 111:
                                     --      prompts than those in
                                     --      prompts than those in
                                     --      XMonad.Prompt.Search
                                     --      XMonad.Prompt.Search
import XMonad.Prompt.Workspace
import XMonad.Prompt.Workspace     -- (27) prompt for a workspace


-- Utilities -------------------------------------------------
-- Utilities -------------------------------------------------
Line 104: Line 122:


import Control.Monad (when)
import Control.Monad (when)
                                                                 -- (31)
                                                                 -- (31)
main :: IO ()
main = do h <- spawnPipe "dzen2 -ta r -fg '#a8a3f7' -bg '#3f3c6d' -e 'onstart=lower'"
main = do h <- spawnPipe "dzen2 -ta r -fg '#a8a3f7' -bg '#3f3c6d' -e 'onstart=lower'"
           host <- getHost
           host <- getHost
           checkTopicConfig myTopicNames myTopicConfig
           checkTopicConfig (myTopicNames host) (myTopicConfig host) -- (22b)
           xmonad $ byorgeyConfig h host                        -- (0)
           xmonad $ byorgeyConfig h host                        -- (0)


Line 131: Line 149:
         borderWidth        = 2
         borderWidth        = 2
       , terminal          = myTerminal
       , terminal          = myTerminal
       , workspaces        = myTopicNames
       , workspaces        = myTopicNames host
       , modMask            = if host == Laptop False
       , modMask            = if host == Laptop False
                                 then modMask defaultConfig
                                 then modMask defaultConfig
Line 151: Line 169:
                                           (myKeys h host)
                                           (myKeys h host)


                                              -- (0c), and see below
      , handleEventHook    = followOnlyIf (queryFocused whenToFollow)
       }
       }
       `additionalKeysP` (myKeys h host)                        -- (29)
       `additionalKeysP` (myKeys h host)                        -- (29)
Line 162: Line 182:
     { args = ["-bg", "yellow", "-fg", "black"] }
     { args = ["-bg", "yellow", "-fg", "black"] }


data TopicItem = TI { topicName :: Topic
data TopicItem = TI { topicName :: Topic   -- (22b)
                     , topicDir  :: Dir
                     , topicDir  :: Dir
                     , topicAction :: X ()
                     , topicAction :: X ()
Line 168: Line 188:


-- define some custom topics for use with the TopicSpace module.
-- define some custom topics for use with the TopicSpace module.
myTopics :: [TopicItem]
myTopics :: Host -> [TopicItem]
myTopics = [ TI "web" "" (spawn "firefox")
myTopics host =
          , TI "irc" "" (safeRunInTerm "" "ssh lvn")
  [ TI "web" "" (spawn "firefox")
          , TI "mail" "" (safeRunInTerm "" "ssh enx")
  , TI "irc" "" (ircAction host)
          , TI "read" "papers" spawnShell
  , TI "mail" "" (runInTerm "" "ssh en")
          , TI "write" "" spawnShell
  , ti "read" "papers"
          , TI "org" "notes"
  , ti "write" "writing/blog/stream-comonad"
              (spawn "emacs --name org ~/notes/`date +%Y-%m-%d`.org")
  , TI "org" "notes"
          , TI "draw" "" (spawn "inkscape")
    (spawn "emacs --name org ~/notes/journal.org")
          , TI "xm-conf" ".xmonad"
  , TI "draw" "" (spawn "inkscape")
              (edit "~/.xmonad/xmonad.hs" >>
  , TI "xm-conf" ".xmonad"
              spawnShell)
    (edit "~/.xmonad/xmonad.hs" >>
          , TI "xm-hack" "xmonad/XMonadContrib" spawnShell
    shell)
          , TI "em-conf" "" (edit "~/.emacs")
  , ti "xm-hack" "src/xmonad/XMonadContrib"
          , TI "music" "music" (spawn "rhythmbox")
  , TI "em-conf" "" (edit "~/.emacs")
          , TI "net" "" (spawn "wicd-client -n" >>
  , TI "music" "music" (spawn "rhythmbox")
                          spawnShell)
  , TI "net" "" (spawn "wicd-client -n" >>
          , TI "conf" "" spawnShell
                shell)
          , TI "misc" "" spawnShell
  , ti "conf" ""
          , TI "120" "teaching/120/09fa" spawnShell
  , ti "misc" ""
          , TI "ref" "reference" spawnShell
  , ti "500" "teaching/500/sf"
          , TI "play" "" spawnShell
  , ti "ref" "documents/reference"
          , TI "tex-conf" "texmf/tex" (edit "~/texmf/tex/brent.sty")
  , ti "play" ""
          , TI "mlt" "teaching/mlt" spawnShell
  , TI "tex-conf" "texmf/tex" (edit "~/texmf/tex/brent.sty")
          , TI "MR" "writing/Monad.Reader" spawnShell
  , ti "mlt" "writing/mlt"
          ]
  , ti "MR" "writing/Monad.Reader/issues/Issue19"
  , ti "mc" "teaching/mathcounts"
  , ti "dia" "src/diagrams"
  , ti "dia-doc" "src/diagrams/doc"
  , ti "dia-core" "src/diagrams/core"
  , ti "dia-lib" "src/diagrams/lib"
  , ti "dia-cairo" "src/diagrams/cairo"
  , ti "dia-contrib" "src/diagrams/contrib"
  , ti "sp" "research/species/nsf11"
  , ti "fc"  "src/gparam/fc"
  , ti "geb" "teaching/geb"
  , ti "pweb" "documents/sites/upenn"
  , ti "hask" "teaching/haskell"
  , ti "anki" "local/lib/anki-1.2.8"
  , ti "ghc" "src/ghc-new-tc"
  , ti "CG" "documents/CG"
  , ti "replib" "src/replib"
  , ti "unbound" "src/replib/Unbound"
  , TI "video" "video" (spawn "cinelerra")
  , TI "aop" "learning/aop" (spawnShell host
                            >> spawn "emacs ~/learning/aop/aop.lhs")
  , ti "tc" "writing/typeclassopedia"
  , ti "noah" "documents/noah/schedule"
  ]
  where
    -- Make a default topic item that just spawns a shell.
    ti t d = TI t d shell
    shell = spawnShell host
 
ircAction :: Host -> X ()
ircAction host = case host of
  Laptop _ -> runInTerm "" "ssh byorgey@eniac.seas.upenn.edu"
  Desktop  -> runInTerm "" "screen -dRR"


edit :: String -> X ()
edit :: String -> X ()
edit = spawn . ("em "++)
edit = spawn . ("em "++)


myTopicNames :: [Topic]
myTopicNames :: Host -> [Topic]
myTopicNames = map topicName myTopics
myTopicNames = map topicName . myTopics


myTopicConfig :: TopicConfig
-- (22b)
myTopicConfig = TopicConfig
myTopicConfig :: Host -> TopicConfig
   { topicDirs = M.fromList $ map (\(TI n d _) -> (n,d)) myTopics
myTopicConfig host = defaultTopicConfig
   { topicDirs = M.fromList $ map (\(TI n d _) -> (n,d)) myTopics'
   , defaultTopicAction = const (return ())
   , defaultTopicAction = const (return ())
   , defaultTopic = "web"
   , defaultTopic = "web"
   , maxTopicHistory = 10
   , maxTopicHistory = 10
   , topicActions = M.fromList $ map (\(TI n _ a) -> (n,a)) myTopics
   , topicActions = M.fromList $ map (\(TI n _ a) -> (n,a)) myTopics'
   }
   }
where myTopics' = myTopics host


spawnShell :: X ()
spawnShell :: Host -> X ()
spawnShell = currentTopicDir myTopicConfig >>= spawnShellIn
spawnShell host = currentTopicDir (myTopicConfig host) >>= spawnShellIn


spawnShellIn :: Dir -> X ()
spawnShellIn :: Dir -> X ()
spawnShellIn dir = spawn $ myTerminal ++ " -title urxvt -e sh -c 'cd ''" ++ dir ++ "'' && " ++ myShell ++ "'"
spawnShellIn dir = spawn $ myTerminal ++ " -title urxvt -e sh -c 'cd ''" ++ dir ++ "'' && " ++ myShell ++ "'"


goto :: Topic -> X ()
delay :: X ()
goto = switchTopic myTopicConfig
delay = io (threadDelay 0)  -- I no longer remember what this is for


promptedGoto :: X ()
goto :: Host -> Topic -> X ()
promptedGoto = workspacePrompt myXPConfig goto
goto host t = delay >> switchTopic' W.view (myTopicConfig host) t  -- (22b)


promptedGotoOtherScreen :: X ()
promptedGoto :: Host -> X ()
promptedGotoOtherScreen =
promptedGoto = workspacePrompt myXPConfig . goto    -- (27)
   workspacePrompt myXPConfig $ \ws -> do
 
promptedGotoOtherScreen :: Host -> X ()
promptedGotoOtherScreen host =
   workspacePrompt myXPConfig $ \ws -> do           -- (27)
     nextScreen
     nextScreen
     goto ws
     goto host ws


promptedShift :: X ()
promptedShift :: X ()
promptedShift = workspacePrompt myXPConfig $ windows . W.shift
promptedShift = workspacePrompt myXPConfig $ windows . W.shift -- (27)


-- XXX offset scratchpad windows by a bit --- each one different?
-- XXX offset scratchpad windows by a bit --- each one different?
scratchpadSize = W.RationalRect (1/4) (1/4) (1/2) (1/2)
scratchpadSize = W.RationalRect (1/4) (1/4) (1/2) (1/2)
mySPFloat = customFloating scratchpadSize
mySPFloat = customFloating scratchpadSize
customTerm = "urxvt-custom"


scratchpads =
scratchpads =
   [ NS "term" "urxvt-custom -title term" (title =? "term") mySPFloat
   [ NS "term" (customTerm ++ " -title term") (title =? "term") mySPFloat
   , NS "ghci" "urxvt-custom -e ghci" (title =? "ghci") mySPFloat
  , NS "term2" (customTerm ++ " -title term2") (title =? "term2") mySPFloat
   , NS "sync" "urxvt-custom -e sy" (title =? "sy") mySPFloat
   , NS "ghci" (customTerm ++ " -e ghci") (title =? "ghci") mySPFloat
   , NS "top" "urxvt-custom -e htop" (title =? "htop") mySPFloat
   , NS "sync" (customTerm ++ " -e sy") (title =? "sy") mySPFloat
   , NS "top"   (customTerm ++ " -e htop") (title =? "htop") mySPFloat
   ]
   ]


Line 248: Line 308:
               ]
               ]
               ++ (case host of Laptop _ -> [battery]
               ++ (case host of Laptop _ -> [battery]
                                 _     -> [])
                                 _       -> [])
   , ppOrder  = \(ws:l:t:exs) -> [t,l,ws]++exs                  -- (1)
   , ppOrder  = \(ws:l:t:exs) -> [t,l,ws]++exs                  -- (1)
   , ppOutput = hPutStrLn h                                      -- (1,31)
   , ppOutput = hPutStrLn h                                      -- (1,31)
   , ppTitle  = shorten (case host of Laptop _ -> 45
   , ppTitle  = shorten (case host of Laptop _ -> 45
                                     Desktop  -> 60)
                                     Desktop  -> 60)
   , ppSort  = fmap (namedScratchpadFilterOutWorkspace.) (ppSort byorgeyPP)
   , ppSort  = fmap (namedScratchpadFilterOutWorkspace.) DO.getSortByOrder
   , ppHiddenNoWindows = const ""
   , ppHiddenNoWindows = const ""
   }
   }
Line 267: Line 327:
     [ (m ++ "M-" ++ [k], f i)                                  -- (0)
     [ (m ++ "M-" ++ [k], f i)                                  -- (0)
         | (i, k) <- zip (XMonad.workspaces conf) "1234567890-=[]\\" -- (0)
         | (i, k) <- zip (XMonad.workspaces conf) "1234567890-=[]\\" -- (0)
         , (f, m) <- [ (windows . W.view, "")                    -- (0a)
         , (f, m) <- [ (goto', "")                    -- (0a)
                     , (windows . W.shift, "S-")
                     , (windows . W.shift, "S-")
                     , (\ws -> nextScreen >> (windows . W.view $ ws), "C-")
                     , (\ws -> nextScreen >> (goto' $ ws), "C-")
                     ]
                     ]
     ]
     ]


     ++
     ++
     [ ("M-S-x", spawnShell)                          -- (0)
     [ ("M-S-x", spawnShell host)                          -- (0)
     , ("M-S-b", spawn "urxvt-big")
     , ("M-S-b", spawn "urxvt-big")
     , ("M-g",  promptedGoto)
     , ("M-g",  promptedGoto host)
     , ("M-C-g", promptedGotoOtherScreen)
     , ("M-C-g", promptedGotoOtherScreen host)
     , ("M-S-g", promptedShift)
     , ("M-S-g", promptedShift)
     , ("M-S-C-g", workspacePrompt myXPConfig $ withAll' . W.shiftWin)
     , ("M-S-C-g", workspacePrompt myXPConfig $ \ws ->          -- (27)
                    withAll' (W.shiftWin ws) >> goto host ws)  -- (22)


       -- in conjunction with manageHook, open a small temporary
       -- in conjunction with manageHook, open a small temporary
       -- floating terminal
       -- floating terminal
     , ("M-a s", namedScratchpadAction scratchpads "term")      -- (30)
     , ("M-a s", namedScratchpadAction scratchpads "term")      -- (30)
    , ("M-a d", namedScratchpadAction scratchpads "term2")
     , ("M-a g", namedScratchpadAction scratchpads "ghci")
     , ("M-a g", namedScratchpadAction scratchpads "ghci")
     , ("M-a t", namedScratchpadAction scratchpads "top")
     , ("M-a t", namedScratchpadAction scratchpads "top")
     ]
     ]
    ++
    -- move floating windows with keybindings                  -- (22f)
    [ ("M-a M-<" ++ dir ++ ">", withFocused (keysMoveWindow (dx,dy)))
      | (dir,dx,dy) <- [ ("L", -20, 0)
                      , ("R", 20, 0)
                      , ("U", 0, -20)
                      , ("D", 0, 20) ]
    ]
    -- sync using Unison in a new floating window, but only on my laptop
     ++ (case host of Laptop _ ->
     ++ (case host of Laptop _ ->
                       [("M-a y", namedScratchpadAction scratchpads "sync")]
                       [("M-a y", namedScratchpadAction scratchpads "sync")]
                     _ -> []
                     _ -> []
       )
       )
   
     ++
     ++
     [ ("M-S-a", kill)                                          -- (0)
     [ ("M-S-a", kill)                                          -- (0)
     , ("M-S-C-a", killAll)
     , ("M-S-C-a", killAll)                                      -- (22)


     -- some gap-toggling
     -- some gap-toggling
Line 300: Line 374:
     , ("M-C-p a", sendMessage $ ToggleStruts)                    --  "
     , ("M-C-p a", sendMessage $ ToggleStruts)                    --  "


     , ("M-C-p g", sendMessage $ ToggleGaps)
     , ("M-C-p g", sendMessage $ ToggleGaps)                      -- (15a)
     ]
     ]


     ++
     ++
     [ ("M-C-p " ++ f ++ " <" ++ dk ++ ">", sendMessage $ m d)
     [ ("M-C-p " ++ f ++ " <" ++ dk ++ ">", sendMessage $ m d)    -- (15a)
         | (dk, d) <- [("L",L), ("D",D), ("U",U), ("R",R)]
         | (dk, d) <- [("L",L), ("D",D), ("U",U), ("R",R)]
         , (f, m)  <- [("v", ToggleGap), ("h", IncGap 10), ("f", DecGap 10)]
         , (f, m)  <- [("v", ToggleGap), ("h", IncGap 40), ("f", DecGap 10)]
     ]
     ]


     ++
     ++
     -- rotate workspaces.
     -- rotate workspaces.
    [ ("M-C-<R>",  nextWS )                                   -- (16)
--    [ ("M-C-<R>",  nextWS )                   -- (16)
    , ("M-C-<L>",  prevWS )                                   --  "
--    , ("M-C-<L>",  prevWS )                   --  "
     , ("M-S-<R>",  shiftToNext )                               -- "
     [ ("M-C-<R>",  DO.swapWith Next NonEmptyWS)               -- (22e)
     , ("M-S-<L>",  shiftToPrev )                               -- "
     , ("M-C-<L>",  DO.swapWith Prev NonEmptyWS)               -- "
     , ("M-S-C-<R>", shiftToNext >> nextWS )                     -- "
     , ("M-S-<R>",   DO.shiftTo Next HiddenNonEmptyWS)           -- "
     , ("M-S-C-<L>", shiftToPrev >> prevWS )                     -- "
     , ("M-S-<L>",   DO.shiftTo Prev HiddenNonEmptyWS)           -- "
     , ("M-<R>",    moveTo Next HiddenNonEmptyWS)               -- "
     , ("M-<R>",    delay >> DO.moveTo Next HiddenNonEmptyWS)   -- "
     , ("M-<L>",    moveTo Prev HiddenNonEmptyWS)               -- "
     , ("M-<L>",    delay >> DO.moveTo Prev HiddenNonEmptyWS)   -- "
     , ("M-f",      newCodeWS)                                  -- "
     , ("M-f",      newCodeWS)                                  -- see below


     -- expand/shrink windows
     -- expand/shrink windows
Line 328: Line 402:


     -- switch to previous workspace
     -- switch to previous workspace
     , ("M-z", toggleWS)                                         -- (16)
     , ("M-z", delay >> toggleWS)                               -- (16)
     , ("M-S-z", killAll >> moveTo Prev HiddenNonEmptyWS)
     
    -- cycle workspaces in most-recently-used order
    -- see definition of custom cycleRecentWS' below, and also    (17) 
    , ("M-S-<Tab>", cycleRecentWS' [xK_Super_L, xK_Shift_L] xK_Tab xK_grave)
     
    -- close all windows on current workspace and move to next 
     , ("M-S-z", killAll >> DO.moveTo Prev HiddenNonEmptyWS)    -- (22, 22e)


     -- dynamic workspace bindings
     -- dynamic workspace bindings
     , ("M-n", addWorkspacePrompt myXPConfig)
     , ("M-n", addWorkspacePrompt myXPConfig)                   -- (22c)
     , ("M-C-r", removeWorkspace)
    , ("M-S-n", renameWorkspace myXPConfig)                    -- "
     , ("M-C-S-r", killAll >> removeWorkspace)
     , ("M-C-r", removeWorkspace)                               -- "
     , ("M-C-S-r", killAll >> removeWorkspace)                   --


     -- move between screens
     -- move between screens
Line 349: Line 430:


     -- some programs to start with keybindings.
     -- some programs to start with keybindings.
     , ("M-x f", spawnOn "web" "firefox")
     , ("M-x f", spawnOn "web" "firefox")                        -- (22a)
     , ("M-x o", spawnOn "web" "opera")
     , ("M-x o", spawnOn "web" "opera")                         -- "
     , ("M-x g", spawnOn "draw" "gimp")                          -- (0)
     , ("M-x g", spawnOn "draw" "gimp")                          -- "
     , ("M-x m", spawn "rhythmbox")                              -- (0)
     , ("M-x m", spawn "rhythmbox")                              -- (0)
     , ("M-x t", spawn "xclock -update 1")                      -- (0)
     , ("M-x t", spawn "xclock -update 1")                      -- (0)
     , ("M-x S-g", spawn "javaws ~/playing/go/cgoban.jnlp")      -- (0)
     , ("M-x S-g", spawn "javaws ~/playing/go/cgoban.jnlp")      -- (0)
     , ("M-x n", goto "org")
     , ("M-x n", goto' "org")


     -- configuration.
     -- configuration.
     , ("M-c x", goto "xm-conf")
     , ("M-c x", goto' "xm-conf")
     , ("M-c e", goto "em-conf")
     , ("M-c e", goto' "em-conf")
     , ("M-c t", goto "tex-conf")
     , ("M-c t", goto' "tex-conf")
     ] ++
     ] ++
     (case host of Laptop _ -> [("M-c n", goto "net")]
     (case host of Laptop _ -> [("M-c n", goto' "net")]
                   _        -> [])
                   _        -> [])
     ++
     ++
Line 378: Line 459:
     , ("S-C-<U>", sendMessage $ Swap U)                        --  "
     , ("S-C-<U>", sendMessage $ Swap U)                        --  "
     , ("S-C-<D>", sendMessage $ Swap D)                        --  "
     , ("S-C-<D>", sendMessage $ Swap D)                        --  "
    , ("S-M-C-<R>", sendMessage $ Move R)
    , ("S-M-C-<L>", sendMessage $ Move L)
    , ("S-M-C-<U>", sendMessage $ Move U)
    , ("S-M-C-<D>", sendMessage $ Move D)


     -- switch to urgent window
     -- switch to urgent window
Line 398: Line 483:
       -- shell prompt.
       -- shell prompt.
     , ("M-p s", sshPrompt myXPConfig)                        -- (26)
     , ("M-p s", sshPrompt myXPConfig)                        -- (26)
    , ("M-p e", spawn "exe=`echo | yeganesh -x` && eval \"exec $exe\"")


       -- some searches.
       -- some searches.
Line 407: Line 493:
     , ("<Print>", spawn "scrot")
     , ("<Print>", spawn "scrot")
     , ("C-<Print>", spawn "sleep 0.2; scrot -s")
     , ("C-<Print>", spawn "sleep 0.2; scrot -s")
    , ("M-y n", promptWSGroupAdd myXPConfig "Name this group: ")  -- (22d)
    , ("M-y g", promptWSGroupView myXPConfig "Go to group: ")    -- (22d)
    , ("M-y d", promptWSGroupForget myXPConfig "Forget group: ")  -- (22d)
    -- volume control.
    , ("<XF86AudioMute>", spawn "amixer -q set Master toggle")
    , ("<XF86AudioLowerVolume>", spawn "amixer -q set Master 5%- unmute")
    , ("<XF86AudioRaiseVolume>", spawn "amixer -q set Master 5%+ unmute")
--    , ("<XF86Display>", spawn "sudo pm-suspend")  -- this never worked very well
     ]
     ]
  where goto' = goto host


-- Find the first empty workspace named "code<i>" for <i> some integer,
-- or create a new one
newCodeWS :: X ()
newCodeWS :: X ()
newCodeWS = withWindowSet $ \w -> do
newCodeWS = withWindowSet $ \w -> do
Line 420: Line 520:
                       [(r,_)] -> Just r
                       [(r,_)] -> Just r
                       _      -> Nothing
                       _      -> Nothing
-- modified variant of cycleRecentWS from XMonad.Actions.CycleRecentWS (17)
-- which does not include visible but non-focused workspaces in the cycle
cycleRecentWS' = cycleWindowSets options
where options w = map (W.view `flip` w) (recentTags w)
      recentTags w = map W.tag $ W.hidden w ++ [W.workspace (W.current w)]


-- Perform a search, using the given method, based on a keypress
-- Perform a search, using the given method, based on a keypress
Line 426: Line 532:
         , ((0, xK_w), method wikipedia)                        --  "
         , ((0, xK_w), method wikipedia)                        --  "
         , ((0, xK_h), method hoogle)                            --  "
         , ((0, xK_h), method hoogle)                            --  "
         , ((shiftMask, xK_h), method hackage)
         , ((shiftMask, xK_h), method hackage)                   --  "
         , ((0, xK_s), method scholar)                          --  "
         , ((0, xK_s), method scholar)                          --  "
         , ((0, xK_m), method mathworld)                        --  "
         , ((0, xK_m), method mathworld)                        --  "
Line 433: Line 539:
         , ((0, xK_a), method alpha)                            --  "
         , ((0, xK_a), method alpha)                            --  "
         , ((0, xK_l), method lucky)                            --  "
         , ((0, xK_l), method lucky)                            --  "
         , ((0, xK_i), method images)
         
        -- custom searches (see below) 
         , ((0, xK_i), method images)                           
        , ((0, xK_k), method greek)
         ]
         ]
-- Search Perseus for ancient Greek dictionary entries
greek  = searchEngine "greek"  "http://www.perseus.tufts.edu/hopper/morph?la=greek&l="
-- for some strange reason the image search that comes with the Search module
-- is for google.fr
images = searchEngine "images" "http://www.google.com/search?hl=en&tbm=isch&q="


-- Prompt search: get input from the user via a prompt, then
-- Prompt search: get input from the user via a prompt, then
--  run the search in firefox and automatically switch to the "wb"
--  run the search in firefox and automatically switch to the web
--  workspace
--  workspace
myPromptSearch (SearchEngine _ site)
myPromptSearch (SearchEngine _ site)
Line 472: Line 588:
                 , appName =? "xbuffy-main" --> doFloatAt 0.92 0.66
                 , appName =? "xbuffy-main" --> doFloatAt 0.92 0.66
                 , appName =? "xbuffy-aux"  --> doFloatAt 0.92 0.81
                 , appName =? "xbuffy-aux"  --> doFloatAt 0.92 0.81
                , appName =? "Caml graphics" --> doFloat
                 ]
                 ]
     -- windows to auto-float
     -- windows to auto-float
Line 483: Line 600:
                     , "Floating"
                     , "Floating"
                     , "Game"
                     , "Game"
                    , "Caml graphics"
                     ]
                     ]
           myFloatTitles = ["Bridge Bid", "Pong", "Floating"]
           myFloatTitles = ["Bridge Bid", "Pong", "Floating"]
Line 520: Line 638:
     myTiled |||          -- resizable tall layout
     myTiled |||          -- resizable tall layout
     Mag.magnifier Grid |||                                      -- (15,6)
     Mag.magnifier Grid |||                                      -- (15,6)
     TwoPane (3/100) (1/2)
     TwoPane (3/100) (1/2) |||
    (named "Full|Acc" $ combineTwo myTiled Full Accordion)      -- (15b)


-- use ResizableTall instead of Tall, but still call it "Tall".
-- use ResizableTall instead of Tall, but still call it "Tall".
myTiled = named "Tall" $ ResizableTall 1 0.03 0.5 []            -- (9,5)
myTiled = named "Tall" $ ResizableTall 1 0.03 0.5 []            -- (9,5)
findTag p = find p . map W.tag . W.workspaces
selectWorkspace' :: XPConfig -> X ()
selectWorkspace' conf = workspacePrompt conf $ \w ->
                        do s <- gets windowset
                          case findTag (w `isPrefixOf`) s of
                            Just w' -> windows $ W.greedyView w'
                            Nothing -> return ()
-- Improved version of followOnlyIf from MagicFocus
followOnlyIfQ :: Query Bool -> Event -> X All
followOnlyIfQ cond e@(CrossingEvent {ev_window = w, ev_event_type = t})
    | t == enterNotify && ev_mode e == notifyNormal
    = whenX (runQuery cond w) (focus w) >> return (All False)
followOnlyIfQ _ _ = return $ All True
-- Focus follows mouse only for Gimp windows
whenToFollow :: Query Bool
whenToFollow = (className =? "Gimp")
queryFocused :: Query Bool -> X Bool
queryFocused q = withWindowSet $ maybe (return False) (runQuery q) . W.peek
</haskell>
</haskell>

Latest revision as of 17:07, 4 December 2011

My .xsession file:

gnome-power-manager 
gnome-volume-manager &

xpmroot ~/images/maine-coast.png

xmodmap -e 'clear Lock'

export PATH=$PATH:/home/brent/local/bin
export OOO_FORCE_DESKTOP=gnome
export BROWSER=firefox

eval `ssh-agent`
xterm -e ssh-add

$HOME/local/bin/xmonad

My current config file, which works with the latest development xmonad and xmonad-contrib (and probably with 0.10), annotated to show which extensions are being used where:

import XMonad                          -- (0) core xmonad libraries

import qualified XMonad.StackSet as W  -- (0a) window stack manipulation
import qualified Data.Map as M         -- (0b) map creation
import Data.List ((\\), find)
import Data.Maybe (isJust, catMaybes)
import Data.Monoid

import System.Posix.Unistd
import Control.Concurrent (threadDelay)

import XMonad.Layout.MagicFocus    -- (0c)

-- Hooks -----------------------------------------------------

import XMonad.Hooks.DynamicLog     -- (1)  for dzen status bar
  hiding (pprWindowSet)
import XMonad.Hooks.UrgencyHook    -- (2)  alert me when people use my nick
                                   --      on IRC
import XMonad.Hooks.ManageDocks    -- (3)  automatically avoid covering my
                                   --      status bar with windows
import XMonad.Hooks.ManageHelpers  -- (4)  for doCenterFloat, put floating
                                   --      windows in the middle of the
                                   --      screen

-- Layout ----------------------------------------------------

import XMonad.Layout.ResizableTile -- (5)  resize non-master windows too
import XMonad.Layout.Grid          -- (6)  grid layout
import XMonad.Layout.TwoPane
import XMonad.Layout.Accordion
import XMonad.Layout.NoBorders     -- (7)  get rid of borders sometimes
                                   -- (8)  navigate between windows
import XMonad.Layout.WindowNavigation  --  directionally
import XMonad.Layout.Named         -- (9)  rename some layouts
import XMonad.Layout.PerWorkspace  -- (10) use different layouts on different WSs
import XMonad.Layout.WorkspaceDir  -- (11) set working directory
                                   --      per-workspace
import XMonad.Layout.Reflect       -- (13) ability to reflect layouts
import XMonad.Layout.MultiToggle   -- (14) apply layout modifiers dynamically
import XMonad.Layout.MultiToggle.Instances
                                   
                                   -- (15) ability to magnify the focused
                                   --      window
import qualified XMonad.Layout.Magnifier as Mag

import XMonad.Layout.Gaps          -- (15a) add manual gaps around the sides
                                   --       of the screen useful for things
                                   --       like screencasts or projectors
                                   --       which cut off part of the screen

import XMonad.Layout.Combo         -- (15b) combine layouts

-- Actions ---------------------------------------------------

import XMonad.Actions.CycleWS      -- (16) general workspace-switching
                                   --      goodness
import XMonad.Actions.CycleRecentWS -- (17) cycle between workspaces
                                    --      in most-recently-used order
import XMonad.Actions.Warp         -- (18) warp the mouse pointer
import XMonad.Actions.Submap       -- (19) create keybinding submaps
import XMonad.Actions.Search hiding (Query, images)
                                   -- (20) some predefined web searches
-- import XMonad.Actions.WindowGo  -- (21) runOrRaise
import XMonad.Actions.WithAll      -- (22) do something with all windows on a workspace
import XMonad.Actions.SpawnOn      -- (22a) start programs on a particular WS

import XMonad.Actions.TopicSpace   -- (22b) set a "topic" for each workspace
import XMonad.Actions.DynamicWorkspaces  
                                   -- (22c)

import XMonad.Actions.DynamicWorkspaceGroups 
                                   -- (22d)

import qualified XMonad.Actions.DynamicWorkspaceOrder as DO 
                                   -- (22e)

import XMonad.Actions.FloatKeys    -- (22f)

-- Prompts ---------------------------------------------------

import XMonad.Prompt                -- (23) general prompt stuff.
import XMonad.Prompt.Man            -- (24) man page prompt
import XMonad.Prompt.AppendFile     -- (25) append stuff to my NOTES file
import XMonad.Prompt.Ssh            -- (26) ssh prompt
import XMonad.Prompt.Input          -- (26) generic input prompt, used for
                                    --      making more generic search
                                    --      prompts than those in
                                    --      XMonad.Prompt.Search
import XMonad.Prompt.Workspace      -- (27) prompt for a workspace

-- Utilities -------------------------------------------------

import XMonad.Util.Loggers          -- (28) some extra loggers for my
                                    --      status bar
import XMonad.Util.EZConfig         -- (29) "M-C-x" style keybindings
import XMonad.Util.NamedScratchpad  -- (30) 'scratchpad' terminal
import XMonad.Util.Run              -- (31) for 'spawnPipe', 'hPutStrLn'

import Control.Monad (when)
                                                                -- (31)
main :: IO ()
main = do h <- spawnPipe "dzen2 -ta r -fg '#a8a3f7' -bg '#3f3c6d' -e 'onstart=lower'"
          host <- getHost
          checkTopicConfig (myTopicNames host) (myTopicConfig host) -- (22b)
          xmonad $ byorgeyConfig h host                         -- (0)

data Host = Desktop | Laptop Bool -- ^ Does the laptop have a Windows key?
  deriving (Eq, Read, Show)

getHost :: IO Host
getHost = do
  hostName <- nodeName `fmap` getSystemID
  return $ case hostName of
    "archimedes" -> Laptop True
    "euclid"     -> Laptop False
    "LVN513-12"  -> Desktop
    _            -> Desktop

myTerminal = "urxvt --perl-lib ~/.urxvt -fg lightgrey -bg black +sb"
myShell = "zsh"

byorgeyConfig h host = myUrgencyHook $                         -- (2)
     defaultConfig
       {
         borderWidth        = 2
       , terminal           = myTerminal
       , workspaces         = myTopicNames host
       , modMask            = if host == Laptop False
                                then modMask defaultConfig
                                else mod4Mask

       , normalBorderColor  = "#dddddd"
       , focusedBorderColor = "#0033ff"
                                                                -- (22)
       , logHook            = myDynamicLog h host
       , manageHook         = manageSpawn
                              <+> myManageHook
                              <+> manageHook defaultConfig
       , layoutHook         = myLayoutHook
       , focusFollowsMouse  = False

         -- XXX fixme: comment!                                 -- (29)
       , startupHook        = return () >>
                              checkKeymap (byorgeyConfig h host)
                                          (myKeys h host)

                                              -- (0c), and see below
       , handleEventHook    = followOnlyIf (queryFocused whenToFollow)
       }
       `additionalKeysP` (myKeys h host)                        -- (29)

checkWS :: (String -> Bool) -> String -> X ()
checkWS p w = do cw <- gets (W.currentTag . windowset)
                 when (not $ p cw) $ (windows $ W.greedyView w)

-- have urgent events flash a yellow dzen bar with black text
myUrgencyHook = withUrgencyHook dzenUrgencyHook                 -- (2)
    { args = ["-bg", "yellow", "-fg", "black"] }

data TopicItem = TI { topicName :: Topic   -- (22b)
                    , topicDir  :: Dir
                    , topicAction :: X ()
                    }

-- define some custom topics for use with the TopicSpace module.
myTopics :: Host -> [TopicItem]
myTopics host =
  [ TI "web" "" (spawn "firefox")
  , TI "irc" "" (ircAction host)
  , TI "mail" "" (runInTerm "" "ssh en")
  , ti "read" "papers"
  , ti "write" "writing/blog/stream-comonad"
  , TI "org" "notes"
    (spawn "emacs --name org ~/notes/journal.org")
  , TI "draw" "" (spawn "inkscape")
  , TI "xm-conf" ".xmonad"
    (edit "~/.xmonad/xmonad.hs" >>
     shell)
  , ti "xm-hack" "src/xmonad/XMonadContrib"
  , TI "em-conf" "" (edit "~/.emacs")
  , TI "music" "music" (spawn "rhythmbox")
  , TI "net" "" (spawn "wicd-client -n" >>
                 shell)
  , ti "conf" ""
  , ti "misc" ""
  , ti "500" "teaching/500/sf"
  , ti "ref" "documents/reference"
  , ti "play" ""
  , TI "tex-conf" "texmf/tex" (edit "~/texmf/tex/brent.sty")
  , ti "mlt" "writing/mlt"
  , ti "MR" "writing/Monad.Reader/issues/Issue19"
  , ti "mc" "teaching/mathcounts"
  , ti "dia" "src/diagrams"
  , ti "dia-doc" "src/diagrams/doc"
  , ti "dia-core" "src/diagrams/core"
  , ti "dia-lib" "src/diagrams/lib"
  , ti "dia-cairo" "src/diagrams/cairo"
  , ti "dia-contrib" "src/diagrams/contrib"
  , ti "sp" "research/species/nsf11"
  , ti "fc"  "src/gparam/fc"
  , ti "geb" "teaching/geb"
  , ti "pweb" "documents/sites/upenn"
  , ti "hask" "teaching/haskell"
  , ti "anki" "local/lib/anki-1.2.8"
  , ti "ghc" "src/ghc-new-tc"
  , ti "CG" "documents/CG"
  , ti "replib" "src/replib"
  , ti "unbound" "src/replib/Unbound"
  , TI "video" "video" (spawn "cinelerra")
  , TI "aop" "learning/aop" (spawnShell host
                             >> spawn "emacs ~/learning/aop/aop.lhs")
  , ti "tc" "writing/typeclassopedia"
  , ti "noah" "documents/noah/schedule"
  ]
  where
    -- Make a default topic item that just spawns a shell.
    ti t d = TI t d shell
    shell = spawnShell host

ircAction :: Host -> X ()
ircAction host = case host of
  Laptop _ -> runInTerm "" "ssh byorgey@eniac.seas.upenn.edu"
  Desktop  -> runInTerm "" "screen -dRR"

edit :: String -> X ()
edit = spawn . ("em "++)

myTopicNames :: Host -> [Topic]
myTopicNames = map topicName . myTopics

-- (22b)
myTopicConfig :: Host -> TopicConfig
myTopicConfig host = defaultTopicConfig
  { topicDirs = M.fromList $ map (\(TI n d _) -> (n,d)) myTopics'
  , defaultTopicAction = const (return ())
  , defaultTopic = "web"
  , maxTopicHistory = 10
  , topicActions = M.fromList $ map (\(TI n _ a) -> (n,a)) myTopics'
  }
 where myTopics' = myTopics host

spawnShell :: Host -> X ()
spawnShell host = currentTopicDir (myTopicConfig host) >>= spawnShellIn

spawnShellIn :: Dir -> X ()
spawnShellIn dir = spawn $ myTerminal ++ " -title urxvt -e sh -c 'cd ''" ++ dir ++ "'' && " ++ myShell ++ "'"

delay :: X ()
delay = io (threadDelay 0)  -- I no longer remember what this is for

goto :: Host -> Topic -> X ()
goto host t = delay >> switchTopic' W.view (myTopicConfig host) t  -- (22b)

promptedGoto :: Host -> X ()
promptedGoto = workspacePrompt myXPConfig . goto    -- (27)

promptedGotoOtherScreen :: Host -> X ()
promptedGotoOtherScreen host =
  workspacePrompt myXPConfig $ \ws -> do            -- (27)
    nextScreen
    goto host ws

promptedShift :: X ()
promptedShift = workspacePrompt myXPConfig $ windows . W.shift  -- (27)

-- XXX offset scratchpad windows by a bit --- each one different?
scratchpadSize = W.RationalRect (1/4) (1/4) (1/2) (1/2)
mySPFloat = customFloating scratchpadSize

customTerm = "urxvt-custom"

scratchpads =
  [ NS "term"  (customTerm ++ " -title term") (title =? "term") mySPFloat
  , NS "term2" (customTerm ++ " -title term2") (title =? "term2") mySPFloat
  , NS "ghci"  (customTerm ++ " -e ghci") (title =? "ghci") mySPFloat
  , NS "sync"  (customTerm ++ " -e sy") (title =? "sy") mySPFloat
  , NS "top"   (customTerm ++ " -e htop") (title =? "htop") mySPFloat
  ]

myDynamicLog h host = dynamicLogWithPP $ byorgeyPP              -- (1)
  { ppVisible = dzenColor "blue" "#a8a3f7" . pad
  , ppExtras = [ date "%a %b %d  %I:%M %p"                      -- (1,28)
               , loadAvg                                        -- (28)
               ]
               ++ (case host of Laptop _ -> [battery]
                                _        -> [])
  , ppOrder  = \(ws:l:t:exs) -> [t,l,ws]++exs                   -- (1)
  , ppOutput = hPutStrLn h                                      -- (1,31)
  , ppTitle  = shorten (case host of Laptop _ -> 45
                                     Desktop  -> 60)
  , ppSort   = fmap (namedScratchpadFilterOutWorkspace.) DO.getSortByOrder
  , ppHiddenNoWindows = const ""
  }

-- my custom keybindings.
myKeys h host = myKeymap host (byorgeyConfig h host)

myKeymap host conf =

    -- mod-[1..],       Switch to workspace N
    -- mod-shift-[1..], Move client to workspace N
    -- mod-ctrl-[1..],  Switch to workspace N on other screen
    [ (m ++ "M-" ++ [k], f i)                                   -- (0)
        | (i, k) <- zip (XMonad.workspaces conf) "1234567890-=[]\\" -- (0)
        , (f, m) <- [ (goto', "")                    -- (0a)
                    , (windows . W.shift, "S-")
                    , (\ws -> nextScreen >> (goto' $ ws), "C-")
                    ]
    ]

    ++
    [ ("M-S-x", spawnShell host)                          -- (0)
    , ("M-S-b", spawn "urxvt-big")
    , ("M-g",   promptedGoto host)
    , ("M-C-g", promptedGotoOtherScreen host)
    , ("M-S-g", promptedShift)
    , ("M-S-C-g", workspacePrompt myXPConfig $ \ws ->          -- (27)
                    withAll' (W.shiftWin ws) >> goto host ws)  -- (22)

      -- in conjunction with manageHook, open a small temporary
      -- floating terminal
    , ("M-a s", namedScratchpadAction scratchpads "term")       -- (30)
    , ("M-a d", namedScratchpadAction scratchpads "term2")
    , ("M-a g", namedScratchpadAction scratchpads "ghci")
    , ("M-a t", namedScratchpadAction scratchpads "top")
    ]
    ++

    -- move floating windows with keybindings                   -- (22f)
    [ ("M-a M-<" ++ dir ++ ">", withFocused (keysMoveWindow (dx,dy)))
      | (dir,dx,dy) <- [ ("L", -20, 0)
                       , ("R", 20, 0)
                       , ("U", 0, -20)
                       , ("D", 0, 20) ]
    ]

    -- sync using Unison in a new floating window, but only on my laptop
    ++ (case host of Laptop _ ->
                       [("M-a y", namedScratchpadAction scratchpads "sync")]
                     _ -> []
       )
    
    ++
    [ ("M-S-a", kill)                                           -- (0)
    , ("M-S-C-a", killAll)                                      -- (22)

    -- some gap-toggling
    , ("M-C-p b", sendMessage $ ToggleStrut D)                    -- (3)
    , ("M-C-p t", sendMessage $ ToggleStrut U)                    --  "
    , ("M-C-p a", sendMessage $ ToggleStruts)                     --  "

    , ("M-C-p g", sendMessage $ ToggleGaps)                       -- (15a)
    ]

    ++
    [ ("M-C-p " ++ f ++ " <" ++ dk ++ ">", sendMessage $ m d)     -- (15a)
        | (dk, d) <- [("L",L), ("D",D), ("U",U), ("R",R)]
        , (f, m)  <- [("v", ToggleGap), ("h", IncGap 40), ("f", DecGap 10)]
    ]

    ++
    -- rotate workspaces.
--    [ ("M-C-<R>",   nextWS )                   -- (16)
--    , ("M-C-<L>",   prevWS )                   --  "
    [ ("M-C-<R>",   DO.swapWith Next NonEmptyWS)                -- (22e)
    , ("M-C-<L>",   DO.swapWith Prev NonEmptyWS)                -- "
    , ("M-S-<R>",   DO.shiftTo Next HiddenNonEmptyWS)           -- "
    , ("M-S-<L>",   DO.shiftTo Prev HiddenNonEmptyWS)           -- "
    , ("M-<R>",     delay >> DO.moveTo Next HiddenNonEmptyWS)   -- "
    , ("M-<L>",     delay >> DO.moveTo Prev HiddenNonEmptyWS)   -- "
    , ("M-f",       newCodeWS)                                  -- see below

    -- expand/shrink windows
    , ("M-r k", sendMessage MirrorExpand)                       -- (5)
    , ("M-r j", sendMessage MirrorShrink)                       -- (5)
    , ("M-r h", sendMessage Shrink)                             -- (0)
    , ("M-r l", sendMessage Expand)                             -- (0)

    -- switch to previous workspace
    , ("M-z", delay >> toggleWS)                                -- (16)
      
    -- cycle workspaces in most-recently-used order
    -- see definition of custom cycleRecentWS' below, and also     (17)  
    , ("M-S-<Tab>", cycleRecentWS' [xK_Super_L, xK_Shift_L] xK_Tab xK_grave)
      
    -- close all windows on current workspace and move to next  
    , ("M-S-z", killAll >> DO.moveTo Prev HiddenNonEmptyWS)     -- (22, 22e)

    -- dynamic workspace bindings
    , ("M-n", addWorkspacePrompt myXPConfig)                    -- (22c)
    , ("M-S-n", renameWorkspace myXPConfig)                     -- "
    , ("M-C-r", removeWorkspace)                                -- "
    , ("M-C-S-r", killAll >> removeWorkspace)                   -- 

    -- move between screens
    , ("M-s", nextScreen)
    , ("M-w", swapNextScreen)
    , ("M-e", shiftNextScreen)

      -- lock the screen with xscreensaver
    , ("M-S-l", spawn "xscreensaver-command -lock")             -- (0)

    -- bainsh the pointer
    , ("M-'", banishScreen LowerRight)                          -- (18)
    , ("M-b", warpToWindow (1/2) (1/2))

    -- some programs to start with keybindings.
    , ("M-x f", spawnOn "web" "firefox")                        -- (22a)
    , ("M-x o", spawnOn "web" "opera")                          -- "
    , ("M-x g", spawnOn "draw" "gimp")                          -- "
    , ("M-x m", spawn "rhythmbox")                              -- (0)
    , ("M-x t", spawn "xclock -update 1")                       -- (0)
    , ("M-x S-g", spawn "javaws ~/playing/go/cgoban.jnlp")      -- (0)
    , ("M-x n", goto' "org")

    -- configuration.
    , ("M-c x", goto' "xm-conf")
    , ("M-c e", goto' "em-conf")
    , ("M-c t", goto' "tex-conf")
    ] ++
    (case host of Laptop _ -> [("M-c n", goto' "net")]
                  _        -> [])
    ++
    [ ("M-c v", spawn "urxvt -e alsamixer")                     -- (0)
    , ("M-c k", spawn "xkill")
    , ("M-c M-S-a", killAll)

    -- window navigation keybindings.
    , ("C-<R>", sendMessage $ Go R)                             -- (8)
    , ("C-<L>", sendMessage $ Go L)                             --  "
    , ("C-<U>", sendMessage $ Go U)                             --  "
    , ("C-<D>", sendMessage $ Go D)                             --  "
    , ("S-C-<R>", sendMessage $ Swap R)                         --  "
    , ("S-C-<L>", sendMessage $ Swap L)                         --  "
    , ("S-C-<U>", sendMessage $ Swap U)                         --  "
    , ("S-C-<D>", sendMessage $ Swap D)                         --  "
    , ("S-M-C-<R>", sendMessage $ Move R)
    , ("S-M-C-<L>", sendMessage $ Move L)
    , ("S-M-C-<U>", sendMessage $ Move U)
    , ("S-M-C-<D>", sendMessage $ Move D)

    -- switch to urgent window
    , ("M-u", focusUrgent)

    -- toggles: fullscreen, flip x, flip y, mirror, no borders
    , ("M-C-<Space>", sendMessage $ Toggle NBFULL)              -- (14)
    , ("M-C-x",       sendMessage $ Toggle REFLECTX)            -- (14,13)
    , ("M-C-y",       sendMessage $ Toggle REFLECTY)            -- (14,13)
    , ("M-C-m",       sendMessage $ Toggle MIRROR)              --  "
    , ("M-C-b",       sendMessage $ Toggle NOBORDERS)           --  "

    -- some prompts.
      -- ability to change the working dir for a workspace.
    , ("M-p d", changeDir myXPConfig)                           -- (11)
      -- man page prompt
    , ("M-p m", manPrompt myXPConfig)                           -- (24)
      -- add single lines to my NOTES file from a prompt.       -- (25)
    , ("M-p n", appendFilePrompt myXPConfig "$HOME/NOTES")
      -- shell prompt.
    , ("M-p s", sshPrompt myXPConfig)                         -- (26)
    , ("M-p e", spawn "exe=`echo | yeganesh -x` && eval \"exec $exe\"") 

      -- some searches.
    , ("M-/", submap . mySearchMap $ myPromptSearch)            -- (19,20)
    , ("M-C-/", submap . mySearchMap $ mySelectSearch)          -- (19,20)

    -- some random utilities.
    , ("M-C-c", spawn "dzen-cal")  -- calendar
    , ("<Print>", spawn "scrot")
    , ("C-<Print>", spawn "sleep 0.2; scrot -s")

    , ("M-y n", promptWSGroupAdd myXPConfig "Name this group: ")  -- (22d)
    , ("M-y g", promptWSGroupView myXPConfig "Go to group: ")     -- (22d)
    , ("M-y d", promptWSGroupForget myXPConfig "Forget group: ")  -- (22d)

    -- volume control.
    , ("<XF86AudioMute>", spawn "amixer -q set Master toggle")
    , ("<XF86AudioLowerVolume>", spawn "amixer -q set Master 5%- unmute")
    , ("<XF86AudioRaiseVolume>", spawn "amixer -q set Master 5%+ unmute")

--    , ("<XF86Display>", spawn "sudo pm-suspend")   -- this never worked very well
    ]
  where goto' = goto host

-- Find the first empty workspace named "code<i>" for <i> some integer,
-- or create a new one
newCodeWS :: X ()
newCodeWS = withWindowSet $ \w -> do
  let wss = W.workspaces w
      cws = map W.tag $ filter (\ws -> "code" `isPrefixOf` W.tag ws && isJust (W.stack ws)) wss
      num = head $ [0..] \\ catMaybes (map (readMaybe . drop 4) cws)
      new = "code" ++ show num
  when (not $ new `elem` (map W.tag wss)) $ addWorkspace new
  windows $ W.view new
 where readMaybe s = case reads s of
                       [(r,_)] -> Just r
                       _       -> Nothing

-- modified variant of cycleRecentWS from XMonad.Actions.CycleRecentWS (17)
-- which does not include visible but non-focused workspaces in the cycle
cycleRecentWS' = cycleWindowSets options
 where options w = map (W.view `flip` w) (recentTags w)
       recentTags w = map W.tag $ W.hidden w ++ [W.workspace (W.current w)]

-- Perform a search, using the given method, based on a keypress
mySearchMap method = M.fromList $                               -- (0b)
        [ ((0, xK_g), method google)                            -- (20)
        , ((0, xK_w), method wikipedia)                         --  "
        , ((0, xK_h), method hoogle)                            --  "
        , ((shiftMask, xK_h), method hackage)                   --  "
        , ((0, xK_s), method scholar)                           --  "
        , ((0, xK_m), method mathworld)                         --  "
        , ((0, xK_p), method maps)                              --  "
        , ((0, xK_d), method dictionary)                        --  "
        , ((0, xK_a), method alpha)                             --  "
        , ((0, xK_l), method lucky)                             --  "
          
        -- custom searches (see below)  
        , ((0, xK_i), method images)                            
        , ((0, xK_k), method greek)
        ]

-- Search Perseus for ancient Greek dictionary entries
greek  = searchEngine "greek"  "http://www.perseus.tufts.edu/hopper/morph?la=greek&l="

-- for some strange reason the image search that comes with the Search module
-- is for google.fr
images = searchEngine "images" "http://www.google.com/search?hl=en&tbm=isch&q="

-- Prompt search: get input from the user via a prompt, then
--   run the search in firefox and automatically switch to the web
--   workspace
myPromptSearch (SearchEngine _ site)
  = inputPrompt myXPConfig "Search" ?+ \s ->                    -- (27)
      (search "firefox" site s >> viewWeb)                      -- (0,20)

-- Select search: do a search based on the X selection
mySelectSearch eng = selectSearch eng >> viewWeb                -- (20)

-- Switch to the "web" workspace
viewWeb = windows (W.view "web")                                -- (0,0a)

-- some nice colors for the prompt windows to match the dzen status bar.
myXPConfig = defaultXPConfig                                    -- (23)
    { fgColor = "#a8a3f7"
    , bgColor = "#3f3c6d"
    }

-- Set up a customized manageHook (rules for handling windows on
--   creation)
myManageHook :: ManageHook                                      -- (0)
myManageHook = composeAll $
                   -- auto-float certain windows
                 [ className =? c --> doCenterFloat | c <- myFloats ] -- (4)
                 ++
                 [ fmap (t `isPrefixOf`) title --> doFloat | t <- myFloatTitles ]
                 ++
                   -- send certain windows to certain workspaces
                 [ className =? "Rhythmbox" --> doF (W.shift "music") -- (0,0a)
                   -- unmanage docks such as gnome-panel and dzen
                 , manageDocks                                     -- (3)
                   -- manage the scratchpad terminal window
                 , namedScratchpadManageHook scratchpads           -- (30)
                 , appName =? "xbuffy-main" --> doFloatAt 0.92 0.66
                 , appName =? "xbuffy-aux"  --> doFloatAt 0.92 0.81
                 , appName =? "Caml graphics" --> doFloat
                 ]
    -- windows to auto-float
    where myFloats = [ "Volume"
                     , "XClock"
                     , "Network-admin"
                     , "Xmessage"
                     , "gnome-search-tool"
                     , "Qjackctl.bin"
                     , "Icfp"
                     , "Floating"
                     , "Game"
                     , "Caml graphics"
                     ]
          myFloatTitles = ["Bridge Bid", "Pong", "Floating"]

-- specify a custom layout hook.
myLayoutHook =

    -- automatically avoid overlapping my dzen status bar.
    avoidStrutsOn [U] $                                        -- (3)

    -- make manual gap adjustment possible.
    gaps (zip [U,D,L,R] (repeat 0)) $

    -- start all workspaces in my home directory, with the ability
    -- to switch to a new working dir.                          -- (10,11)
    workspaceDir "~" $

    -- navigate directionally rather than with mod-j/k
    configurableNavigation (navigateColor "#00aa00") $          -- (8)

    -- ability to toggle between fullscreen, reflect x/y, no borders,
    -- and mirrored.
    mkToggle1 NBFULL $                                  -- (14)
    mkToggle1 REFLECTX $                                -- (14,13)
    mkToggle1 REFLECTY $                                -- (14,13)
    mkToggle1 NOBORDERS $                               --  "
    mkToggle1 MIRROR $                                  --  "

    -- borders automatically disappear for fullscreen windows.
    smartBorders $                                              -- (7)

    -- "web" and "irc" start in Full mode and can switch to tiled...
    onWorkspaces ["web","irc"] (Full ||| myTiled) $               -- (10,0)

    -- ...whereas all other workspaces start tall and can switch
    -- to a grid layout with the focused window magnified.
    myTiled |||           -- resizable tall layout
    Mag.magnifier Grid |||                                      -- (15,6)
    TwoPane (3/100) (1/2) |||
    (named "Full|Acc" $ combineTwo myTiled Full Accordion)      -- (15b)

-- use ResizableTall instead of Tall, but still call it "Tall".
myTiled = named "Tall" $ ResizableTall 1 0.03 0.5 []            -- (9,5)


findTag p = find p . map W.tag . W.workspaces

selectWorkspace' :: XPConfig -> X ()
selectWorkspace' conf = workspacePrompt conf $ \w ->
                        do s <- gets windowset
                           case findTag (w `isPrefixOf`) s of
                             Just w' -> windows $ W.greedyView w'
                             Nothing -> return ()

-- Improved version of followOnlyIf from MagicFocus
followOnlyIfQ :: Query Bool -> Event -> X All
followOnlyIfQ cond e@(CrossingEvent {ev_window = w, ev_event_type = t})
    | t == enterNotify && ev_mode e == notifyNormal
    = whenX (runQuery cond w) (focus w) >> return (All False)
followOnlyIfQ _ _ = return $ All True

-- Focus follows mouse only for Gimp windows
whenToFollow :: Query Bool
whenToFollow = (className =? "Gimp")

queryFocused :: Query Bool -> X Bool
queryFocused q = withWindowSet $ maybe (return False) (runQuery q) . W.peek