From dd8c36036dd1ec0a854a35e49cb890c60596bc3c Mon Sep 17 00:00:00 2001 From: Don Stewart Date: Mon, 5 Nov 2007 03:17:05 +0100 Subject: EventLoop -> Core, DefaultConfig -> Config darcs-hash:20071105021705-cba2c-fc7ffc11ffa9a5397cc40a5dba530ca620018f25 --- Main.hs | 18 ++-- XMonad/Config.hs | 281 ++++++++++++++++++++++++++++++++++++++++++++++++ XMonad/Core.hs | 277 +++++++++++++++++++++++++++++++++++++++++++++++ XMonad/DefaultConfig.hs | 281 ------------------------------------------------ XMonad/EventLoop.hs | 277 ----------------------------------------------- xmonad.cabal | 6 +- 6 files changed, 571 insertions(+), 569 deletions(-) create mode 100644 XMonad/Config.hs create mode 100644 XMonad/Core.hs delete mode 100644 XMonad/DefaultConfig.hs delete mode 100644 XMonad/EventLoop.hs diff --git a/Main.hs b/Main.hs index d41823e..0a24dba 100644 --- a/Main.hs +++ b/Main.hs @@ -14,8 +14,8 @@ module Main (main) where -import XMonad.EventLoop (makeMain) -import XMonad.DefaultConfig (defaultConfig) +import XMonad.Core +import XMonad.Config import Control.Exception (handle) import System.IO @@ -25,6 +25,14 @@ import System.Environment import System.Exit import System.Posix.Process (executeFile) +-- | The entry point into xmonad. Attempts to compile any custom main +-- for xmonad, and if it doesn't find one, just launches the default. +main :: IO () +main = do + handle (hPrint stderr) buildLaunch + -- if buildLaunch returns, execute the trusted core + makeMain defaultConfig + -- | Build "~/.xmonad/Main.hs" with ghc, then execute it. If there are no -- errors, this function does not return. An exception is raised in any of -- these cases: @@ -43,9 +51,3 @@ buildLaunch = do args <- getArgs executeFile (dir ++ "/Main") False args Nothing return () - -main :: IO () -main = do - handle (hPrint stderr) buildLaunch - -- if buildLaunch returns, execute the trusted core - makeMain defaultConfig diff --git a/XMonad/Config.hs b/XMonad/Config.hs new file mode 100644 index 0000000..5021328 --- /dev/null +++ b/XMonad/Config.hs @@ -0,0 +1,281 @@ +{-# OPTIONS -fno-warn-missing-signatures #-} +----------------------------------------------------------------------------- +-- | +-- Module : XMonad.Config +-- Copyright : (c) Spencer Janssen 2007 +-- License : BSD3-style (see LICENSE) +-- +-- Maintainer : dons@galois.com +-- Stability : stable +-- Portability : portable +-- +-- This module specifies the default configuration values for xmonad. +-- Users will typically use record syntax to override particular fields +-- they disagree with, in the defaultConfig structure. +-- +------------------------------------------------------------------------ + +module XMonad.Config (defaultConfig) where + +-- +-- Useful imports +-- +import XMonad hiding + (workspaces,manageHook,numlockMask,keys,logHook,borderWidth,mouseBindings + ,defaultGaps,layoutHook,modMask,terminal,normalBorderColor,focusedBorderColor) +import qualified XMonad + (workspaces,manageHook,numlockMask,keys,logHook,borderWidth,mouseBindings + ,defaultGaps,layoutHook,modMask,terminal,normalBorderColor,focusedBorderColor) + +import XMonad.Layouts +import XMonad.Operations +import qualified XMonad.StackSet as W +import Data.Ratio +import Data.Bits ((.|.)) +import qualified Data.Map as M +import System.Exit +import Graphics.X11.Xlib + +-- % Extension-provided imports + +-- | The default number of workspaces (virtual screens) and their names. +-- By default we use numeric strings, but any string may be used as a +-- workspace name. The number of workspaces is determined by the length +-- of this list. +-- +-- A tagging example: +-- +-- > workspaces = ["web", "irc", "code" ] ++ map show [4..9] +-- +workspaces :: [WorkspaceId] +workspaces = map show [1 .. 9 :: Int] + +-- | modMask lets you specify which modkey you want to use. The default +-- is mod1Mask ("left alt"). You may also consider using mod3Mask +-- ("right alt"), which does not conflict with emacs keybindings. The +-- "windows key" is usually mod4Mask. +-- +defaultModMask :: KeyMask +defaultModMask = mod1Mask + +-- | The mask for the numlock key. Numlock status is "masked" from the +-- current modifier status, so the keybindings will work with numlock on or +-- off. You may need to change this on some systems. +-- +-- You can find the numlock modifier by running "xmodmap" and looking for a +-- modifier with Num_Lock bound to it: +-- +-- > $ xmodmap | grep Num +-- > mod2 Num_Lock (0x4d) +-- +-- Set numlockMask = 0 if you don't have a numlock key, or want to treat +-- numlock status separately. +-- +numlockMask :: KeyMask +numlockMask = mod2Mask + +-- | Width of the window border in pixels. +-- +borderWidth :: Dimension +borderWidth = 1 + +-- | Border colors for unfocused and focused windows, respectively. +-- +normalBorderColor, focusedBorderColor :: String +normalBorderColor = "#dddddd" +focusedBorderColor = "#ff0000" + +-- | Default offset of drawable screen boundaries from each physical +-- screen. Anything non-zero here will leave a gap of that many pixels +-- on the given edge, on the that screen. A useful gap at top of screen +-- for a menu bar (e.g. 15) +-- +-- An example, to set a top gap on monitor 1, and a gap on the bottom of +-- monitor 2, you'd use a list of geometries like so: +-- +-- > defaultGaps = [(18,0,0,0),(0,18,0,0)] -- 2 gaps on 2 monitors +-- +-- Fields are: top, bottom, left, right. +-- +defaultGaps :: [(Int,Int,Int,Int)] +defaultGaps = [(0,0,0,0)] -- 15 for default dzen font + +------------------------------------------------------------------------ +-- Window rules + +-- | Execute arbitrary actions and WindowSet manipulations when managing +-- a new window. You can use this to, for example, always float a +-- particular program, or have a client always appear on a particular +-- workspace. +-- +-- To find the property name associated with a program, use +-- xprop | grep WM_CLASS +-- and click on the client you're interested in. +-- +manageHook :: Window -- ^ the new window to manage + -> String -- ^ window title + -> String -- ^ window resource name + -> String -- ^ window resource class + -> X (WindowSet -> WindowSet) + +-- Always float various programs: +manageHook w _ _ c | c `elem` floats = fmap (W.float w . snd) (floatLocation w) + where floats = ["MPlayer", "Gimp"] + +-- Desktop panels and dock apps should be ignored by xmonad: +manageHook w _ n _ | n `elem` ignore = reveal w >> return (W.delete w) + where ignore = ["gnome-panel", "desktop_window", "kicker", "kdesktop"] + +-- Automatically send Firefox windows to the "web" workspace: +-- If a workspace named "web" doesn't exist, the window will appear on the +-- current workspace. +manageHook _ _ "Gecko" _ = return $ W.shift "web" + +-- The default rule: return the WindowSet unmodified. You typically do not +-- want to modify this line. +manageHook _ _ _ _ = return id + +------------------------------------------------------------------------ +-- Logging + +-- | Perform an arbitrary action on each internal state change or X event. +-- Examples include: +-- * do nothing +-- * log the state to stdout +-- +-- See the 'DynamicLog' extension for examples. +-- +logHook :: X () +logHook = return () + +------------------------------------------------------------------------ +-- Extensible layouts +-- +-- You can specify and transform your layouts by modifying these values. +-- If you change layout bindings be sure to use 'mod-shift-space' after +-- restarting (with 'mod-q') to reset your layout state to the new +-- defaults, as xmonad preserves your old layout settings by default. +-- + +-- | The available layouts. Note that each layout is separated by |||, which +-- denotes layout choice. +layout = tiled ||| Mirror tiled ||| Full + -- Add extra layouts you want to use here: + -- % Extension-provided layouts + where + -- default tiling algorithm partitions the screen into two panes + tiled = Tall nmaster delta ratio + + -- The default number of windows in the master pane + nmaster = 1 + + -- Default proportion of screen occupied by master pane + ratio = 1%2 + + -- Percent of screen to increment by when resizing panes + delta = 3%100 + +------------------------------------------------------------------------ +-- Key bindings: + +-- | The preferred terminal program, which is used in a binding below and by +-- certain contrib modules. +terminal :: String +terminal = "xterm" + +-- | The xmonad key bindings. Add, modify or remove key bindings here. +-- +-- (The comment formatting character is used when generating the manpage) +-- +keys :: XConfig -> M.Map (KeyMask, KeySym) (X ()) +keys conf@(XConfig {XMonad.modMask = modMask}) = M.fromList $ + -- launching and killing programs + [ ((modMask .|. shiftMask, xK_Return), spawn $ XMonad.terminal conf) -- %! Launch terminal + , ((modMask, xK_p ), spawn "exe=`dmenu_path | dmenu` && eval \"exec $exe\"") -- %! Launch dmenu + , ((modMask .|. shiftMask, xK_p ), spawn "gmrun") -- %! Launch gmrun + , ((modMask .|. shiftMask, xK_c ), kill) -- %! Close the focused window + + , ((modMask, xK_space ), sendMessage NextLayout) -- %! Rotate through the available layout algorithms + , ((modMask .|. shiftMask, xK_space ), setLayout $ XMonad.layoutHook conf) -- %! Reset the layouts on the current workspace to default + + , ((modMask, xK_n ), refresh) -- %! Resize viewed windows to the correct size + + -- move focus up or down the window stack + , ((modMask, xK_Tab ), windows W.focusDown) -- %! Move focus to the next window + , ((modMask, xK_j ), windows W.focusDown) -- %! Move focus to the next window + , ((modMask, xK_k ), windows W.focusUp ) -- %! Move focus to the previous window + , ((modMask, xK_m ), windows W.focusMaster ) -- %! Move focus to the master window + + -- modifying the window order + , ((modMask, xK_Return), windows W.swapMaster) -- %! Swap the focused window and the master window + , ((modMask .|. shiftMask, xK_j ), windows W.swapDown ) -- %! Swap the focused window with the next window + , ((modMask .|. shiftMask, xK_k ), windows W.swapUp ) -- %! Swap the focused window with the previous window + + -- resizing the master/slave ratio + , ((modMask, xK_h ), sendMessage Shrink) -- %! Shrink the master area + , ((modMask, xK_l ), sendMessage Expand) -- %! Expand the master area + + -- floating layer support + , ((modMask, xK_t ), withFocused $ windows . W.sink) -- %! Push window back into tiling + + -- increase or decrease number of windows in the master area + , ((modMask , xK_comma ), sendMessage (IncMasterN 1)) -- %! Increment the number of windows in the master area + , ((modMask , xK_period), sendMessage (IncMasterN (-1))) -- %! Deincrement the number of windows in the master area + + -- toggle the status bar gap + , ((modMask , xK_b ), modifyGap (\i n -> let x = (XMonad.defaultGaps conf ++ repeat (0,0,0,0)) !! i in if n == x then (0,0,0,0) else x)) -- %! Toggle the status bar gap + + -- quit, or restart + , ((modMask .|. shiftMask, xK_q ), io (exitWith ExitSuccess)) -- %! Quit xmonad + , ((modMask , xK_q ), broadcastMessage ReleaseResources >> restart (Just "xmonad") True) -- %! Restart xmonad + + -- % Extension-provided key bindings + ] + ++ + -- mod-[1..9] %! Switch to workspace N + -- mod-shift-[1..9] %! Move client to workspace N + [((m .|. modMask, k), windows $ f i) + | (i, k) <- zip (XMonad.workspaces conf) [xK_1 .. xK_9] + , (f, m) <- [(W.greedyView, 0), (W.shift, shiftMask)]] + ++ + -- mod-{w,e,r} %! Switch to physical/Xinerama screens 1, 2, or 3 + -- mod-shift-{w,e,r} %! Move client to screen 1, 2, or 3 + [((m .|. modMask, key), screenWorkspace sc >>= flip whenJust (windows . f)) + | (key, sc) <- zip [xK_w, xK_e, xK_r] [0..] + , (f, m) <- [(W.view, 0), (W.shift, shiftMask)]] + + -- % Extension-provided key bindings lists + +-- | Mouse bindings: default actions bound to mouse events +-- +mouseBindings :: XConfig -> M.Map (KeyMask, Button) (Window -> X ()) +mouseBindings (XConfig {XMonad.modMask = modMask}) = M.fromList $ + -- mod-button1 %! Set the window to floating mode and move by dragging + [ ((modMask, button1), (\w -> focus w >> mouseMoveWindow w)) + -- mod-button2 %! Raise the window to the top of the stack + , ((modMask, button2), (\w -> focus w >> windows W.swapMaster)) + -- mod-button3 %! Set the window to floating mode and resize by dragging + , ((modMask, button3), (\w -> focus w >> mouseResizeWindow w)) + -- you may also bind events to the mouse scroll wheel (button4 and button5) + + -- % Extension-provided mouse bindings + ] + +-- % Extension-provided definitions + +-- | And, finally, the default set of configuration values itself +defaultConfig :: XConfig +defaultConfig = XConfig + { XMonad.borderWidth = borderWidth + , XMonad.workspaces = workspaces + , XMonad.defaultGaps = defaultGaps + , XMonad.layoutHook = Layout layout + , XMonad.terminal = terminal + , XMonad.normalBorderColor = normalBorderColor + , XMonad.focusedBorderColor = focusedBorderColor + , XMonad.numlockMask = numlockMask + , XMonad.modMask = defaultModMask + , XMonad.keys = keys + , XMonad.logHook = logHook + , XMonad.mouseBindings = mouseBindings + , XMonad.manageHook = manageHook } diff --git a/XMonad/Core.hs b/XMonad/Core.hs new file mode 100644 index 0000000..ff64457 --- /dev/null +++ b/XMonad/Core.hs @@ -0,0 +1,277 @@ +{-# LANGUAGE MultiParamTypeClasses #-} +---------------------------------------------------------------------------- +-- | +-- Module : Core.hs +-- Copyright : (c) Spencer Janssen 2007 +-- License : BSD3-style (see LICENSE) +-- +-- Maintainer : sjanssen@cse.unl.edu +-- Stability : unstable +-- Portability : not portable, uses mtl, X11, posix +-- +-- xmonad, a minimalist, tiling window manager for X11 +-- +----------------------------------------------------------------------------- + +module XMonad.Core (makeMain) where + +import Data.Bits +import qualified Data.Map as M +import qualified Data.Set as S +import Control.Monad.Reader +import Control.Monad.State +import Data.Maybe (fromMaybe) + +import System.Environment (getArgs) + +import Graphics.X11.Xlib hiding (refreshKeyboardMapping) +import Graphics.X11.Xlib.Extras +import Graphics.X11.Xinerama (getScreenInfo) + +import XMonad +import XMonad.StackSet (new, floating, member) +import qualified XMonad.StackSet as W +import XMonad.Operations + +import System.IO + +-- | +-- The main entry point +-- +makeMain :: XConfig -> IO () +makeMain xmc = do + dpy <- openDisplay "" + let dflt = defaultScreen dpy + + rootw <- rootWindow dpy dflt + xinesc <- getScreenInfo dpy + nbc <- initColor dpy $ normalBorderColor xmc + fbc <- initColor dpy $ focusedBorderColor xmc + hSetBuffering stdout NoBuffering + args <- getArgs + + let layout = layoutHook xmc + lreads = readsLayout layout + initialWinset = new layout (workspaces xmc) $ zipWith SD xinesc gaps + + maybeRead reads' s = case reads' s of + [(x, "")] -> Just x + _ -> Nothing + + winset = fromMaybe initialWinset $ do + ("--resume" : s : _) <- return args + ws <- maybeRead reads s + return . W.ensureTags layout (workspaces xmc) + $ W.mapLayout (fromMaybe layout . maybeRead lreads) ws + + gaps = take (length xinesc) $ defaultGaps xmc ++ repeat (0,0,0,0) + + cf = XConf + { display = dpy + , config = xmc + , theRoot = rootw + , normalBorder = nbc + , focusedBorder = fbc + , keyActions = keys xmc xmc + , buttonActions = mouseBindings xmc xmc } + st = XState + { windowset = initialWinset + , mapped = S.empty + , waitingUnmap = M.empty + , dragging = Nothing } + + xSetErrorHandler -- in C, I'm too lazy to write the binding: dons + + -- setup initial X environment + sync dpy False + selectInput dpy rootw $ substructureRedirectMask .|. substructureNotifyMask + .|. enterWindowMask .|. leaveWindowMask .|. structureNotifyMask + + allocaXEvent $ \e -> + runX cf st $ do + + grabKeys + grabButtons + + io $ sync dpy False + + -- bootstrap the windowset, Operations.windows will identify all + -- the windows in winset as new and set initial properties for + -- those windows + windows (const winset) + + -- scan for all top-level windows, add the unmanaged ones to the + -- windowset + ws <- io $ scan dpy rootw + mapM_ manage ws + + -- main loop, for all you HOF/recursion fans out there. + forever_ $ handle =<< io (nextEvent dpy e >> getEvent e) + + return () + where forever_ a = a >> forever_ a + + +-- --------------------------------------------------------------------- +-- | Event handler. Map X events onto calls into Operations.hs, which +-- modify our internal model of the window manager state. +-- +-- Events dwm handles that we don't: +-- +-- [ButtonPress] = buttonpress, +-- [Expose] = expose, +-- [PropertyNotify] = propertynotify, +-- +handle :: Event -> X () + +-- run window manager command +handle (KeyEvent {ev_event_type = t, ev_state = m, ev_keycode = code}) + | t == keyPress = withDisplay $ \dpy -> do + s <- io $ keycodeToKeysym dpy code 0 + mClean <- cleanMask m + ks <- asks keyActions + userCode $ whenJust (M.lookup (mClean, s) ks) id + +-- manage a new window +handle (MapRequestEvent {ev_window = w}) = withDisplay $ \dpy -> do + wa <- io $ getWindowAttributes dpy w -- ignore override windows + -- need to ignore mapping requests by managed windows not on the current workspace + managed <- isClient w + when (not (wa_override_redirect wa) && not managed) $ do manage w + +-- window destroyed, unmanage it +-- window gone, unmanage it +handle (DestroyWindowEvent {ev_window = w}) = whenX (isClient w) $ unmanage w + +-- We track expected unmap events in waitingUnmap. We ignore this event unless +-- it is synthetic or we are not expecting an unmap notification from a window. +handle (UnmapEvent {ev_window = w, ev_send_event = synthetic}) = whenX (isClient w) $ do + e <- gets (fromMaybe 0 . M.lookup w . waitingUnmap) + if (synthetic || e == 0) + then unmanage w + else modify (\s -> s { waitingUnmap = M.adjust pred w (waitingUnmap s) }) + +-- set keyboard mapping +handle e@(MappingNotifyEvent {}) = do + io $ refreshKeyboardMapping e + when (ev_request e == mappingKeyboard) grabKeys + +-- handle button release, which may finish dragging. +handle e@(ButtonEvent {ev_event_type = t}) + | t == buttonRelease = do + drag <- gets dragging + case drag of + -- we're done dragging and have released the mouse: + Just (_,f) -> modify (\s -> s { dragging = Nothing }) >> f + Nothing -> broadcastMessage e + +-- handle motionNotify event, which may mean we are dragging. +handle e@(MotionEvent {ev_event_type = _t, ev_x = x, ev_y = y}) = do + drag <- gets dragging + case drag of + Just (d,_) -> d (fromIntegral x) (fromIntegral y) -- we're dragging + Nothing -> broadcastMessage e + +-- click on an unfocused window, makes it focused on this workspace +handle e@(ButtonEvent {ev_window = w,ev_event_type = t,ev_button = b }) + | t == buttonPress = do + -- If it's the root window, then it's something we + -- grabbed in grabButtons. Otherwise, it's click-to-focus. + isr <- isRoot w + m <- cleanMask $ ev_state e + ba <- asks buttonActions + if isr then userCode $ whenJust (M.lookup (m, b) ba) ($ ev_subwindow e) + else focus w + sendMessage e -- Always send button events. + +-- entered a normal window, makes this focused. +handle e@(CrossingEvent {ev_window = w, ev_event_type = t}) + | t == enterNotify && ev_mode e == notifyNormal + && ev_detail e /= notifyInferior = focus w + +-- left a window, check if we need to focus root +handle e@(CrossingEvent {ev_event_type = t}) + | t == leaveNotify + = do rootw <- asks theRoot + when (ev_window e == rootw && not (ev_same_screen e)) $ setFocusX rootw + +-- configure a window +handle e@(ConfigureRequestEvent {ev_window = w}) = withDisplay $ \dpy -> do + ws <- gets windowset + wa <- io $ getWindowAttributes dpy w + + bw <- asks (borderWidth . config) + + if M.member w (floating ws) + || not (member w ws) + then do io $ configureWindow dpy w (ev_value_mask e) $ WindowChanges + { wc_x = ev_x e + , wc_y = ev_y e + , wc_width = ev_width e + , wc_height = ev_height e + , wc_border_width = fromIntegral bw + , wc_sibling = ev_above e + , wc_stack_mode = ev_detail e } + when (member w ws) (float w) + else io $ allocaXEvent $ \ev -> do + setEventType ev configureNotify + setConfigureEvent ev w w + (wa_x wa) (wa_y wa) (wa_width wa) + (wa_height wa) (ev_border_width e) none (wa_override_redirect wa) + sendEvent dpy w False 0 ev + io $ sync dpy False + +-- configuration changes in the root may mean display settings have changed +handle (ConfigureEvent {ev_window = w}) = whenX (isRoot w) rescreen + +-- property notify +handle PropertyEvent { ev_event_type = t, ev_atom = a } + | t == propertyNotify && a == wM_NAME = userCode =<< asks (logHook . config) + +handle e = broadcastMessage e -- trace (eventName e) -- ignoring + + +-- --------------------------------------------------------------------- +-- IO stuff. Doesn't require any X state +-- Most of these things run only on startup (bar grabkeys) + +-- | scan for any new windows to manage. If they're already managed, +-- this should be idempotent. +scan :: Display -> Window -> IO [Window] +scan dpy rootw = do + (_, _, ws) <- queryTree dpy rootw + filterM ok ws + -- TODO: scan for windows that are either 'IsViewable' or where WM_STATE == + -- Iconic + where ok w = do wa <- getWindowAttributes dpy w + a <- internAtom dpy "WM_STATE" False + p <- getWindowProperty32 dpy a w + let ic = case p of + Just (3:_) -> True -- 3 for iconified + _ -> False + return $ not (wa_override_redirect wa) + && (wa_map_state wa == waIsViewable || ic) + +-- | Grab the keys back +grabKeys :: X () +grabKeys = do + XConf { display = dpy, theRoot = rootw } <- ask + let grab kc m = io $ grabKey dpy kc m rootw True grabModeAsync grabModeAsync + io $ ungrabKey dpy anyKey anyModifier rootw + ks <- asks keyActions + forM_ (M.keys ks) $ \(mask,sym) -> do + kc <- io $ keysymToKeycode dpy sym + -- "If the specified KeySym is not defined for any KeyCode, + -- XKeysymToKeycode() returns zero." + when (kc /= '\0') $ mapM_ (grab kc . (mask .|.)) =<< extraModifiers + +-- | XXX comment me +grabButtons :: X () +grabButtons = do + XConf { display = dpy, theRoot = rootw } <- ask + let grab button mask = io $ grabButton dpy button mask rootw False buttonPressMask + grabModeAsync grabModeSync none none + io $ ungrabButton dpy anyButton anyModifier rootw + ems <- extraModifiers + ba <- asks buttonActions + mapM_ (\(m,b) -> mapM_ (grab b . (m .|.)) ems) (M.keys $ ba) diff --git a/XMonad/DefaultConfig.hs b/XMonad/DefaultConfig.hs deleted file mode 100644 index a13d68a..0000000 --- a/XMonad/DefaultConfig.hs +++ /dev/null @@ -1,281 +0,0 @@ -{-# OPTIONS -fno-warn-missing-signatures #-} ------------------------------------------------------------------------------ --- | --- Module : DefaultConfig.hs --- Copyright : (c) Spencer Janssen 2007 --- License : BSD3-style (see LICENSE) --- --- Maintainer : dons@galois.com --- Stability : stable --- Portability : portable --- --- This module specifies the default configuration values for xmonad. --- Users will typically use record syntax to override particular fields --- they disagree with, in the defaultConfig structure. --- ------------------------------------------------------------------------- - -module XMonad.DefaultConfig (defaultConfig) where - --- --- Useful imports --- -import XMonad hiding - (workspaces,manageHook,numlockMask,keys,logHook,borderWidth,mouseBindings - ,defaultGaps,layoutHook,modMask,terminal,normalBorderColor,focusedBorderColor) -import qualified XMonad - (workspaces,manageHook,numlockMask,keys,logHook,borderWidth,mouseBindings - ,defaultGaps,layoutHook,modMask,terminal,normalBorderColor,focusedBorderColor) - -import XMonad.Layouts -import XMonad.Operations -import qualified XMonad.StackSet as W -import Data.Ratio -import Data.Bits ((.|.)) -import qualified Data.Map as M -import System.Exit -import Graphics.X11.Xlib - --- % Extension-provided imports - --- | The default number of workspaces (virtual screens) and their names. --- By default we use numeric strings, but any string may be used as a --- workspace name. The number of workspaces is determined by the length --- of this list. --- --- A tagging example: --- --- > workspaces = ["web", "irc", "code" ] ++ map show [4..9] --- -workspaces :: [WorkspaceId] -workspaces = map show [1 .. 9 :: Int] - --- | modMask lets you specify which modkey you want to use. The default --- is mod1Mask ("left alt"). You may also consider using mod3Mask --- ("right alt"), which does not conflict with emacs keybindings. The --- "windows key" is usually mod4Mask. --- -defaultModMask :: KeyMask -defaultModMask = mod1Mask - --- | The mask for the numlock key. Numlock status is "masked" from the --- current modifier status, so the keybindings will work with numlock on or --- off. You may need to change this on some systems. --- --- You can find the numlock modifier by running "xmodmap" and looking for a --- modifier with Num_Lock bound to it: --- --- > $ xmodmap | grep Num --- > mod2 Num_Lock (0x4d) --- --- Set numlockMask = 0 if you don't have a numlock key, or want to treat --- numlock status separately. --- -numlockMask :: KeyMask -numlockMask = mod2Mask - --- | Width of the window border in pixels. --- -borderWidth :: Dimension -borderWidth = 1 - --- | Border colors for unfocused and focused windows, respectively. --- -normalBorderColor, focusedBorderColor :: String -normalBorderColor = "#dddddd" -focusedBorderColor = "#ff0000" - --- | Default offset of drawable screen boundaries from each physical --- screen. Anything non-zero here will leave a gap of that many pixels --- on the given edge, on the that screen. A useful gap at top of screen --- for a menu bar (e.g. 15) --- --- An example, to set a top gap on monitor 1, and a gap on the bottom of --- monitor 2, you'd use a list of geometries like so: --- --- > defaultGaps = [(18,0,0,0),(0,18,0,0)] -- 2 gaps on 2 monitors --- --- Fields are: top, bottom, left, right. --- -defaultGaps :: [(Int,Int,Int,Int)] -defaultGaps = [(0,0,0,0)] -- 15 for default dzen font - ------------------------------------------------------------------------- --- Window rules - --- | Execute arbitrary actions and WindowSet manipulations when managing --- a new window. You can use this to, for example, always float a --- particular program, or have a client always appear on a particular --- workspace. --- --- To find the property name associated with a program, use --- xprop | grep WM_CLASS --- and click on the client you're interested in. --- -manageHook :: Window -- ^ the new window to manage - -> String -- ^ window title - -> String -- ^ window resource name - -> String -- ^ window resource class - -> X (WindowSet -> WindowSet) - --- Always float various programs: -manageHook w _ _ c | c `elem` floats = fmap (W.float w . snd) (floatLocation w) - where floats = ["MPlayer", "Gimp"] - --- Desktop panels and dock apps should be ignored by xmonad: -manageHook w _ n _ | n `elem` ignore = reveal w >> return (W.delete w) - where ignore = ["gnome-panel", "desktop_window", "kicker", "kdesktop"] - --- Automatically send Firefox windows to the "web" workspace: --- If a workspace named "web" doesn't exist, the window will appear on the --- current workspace. -manageHook _ _ "Gecko" _ = return $ W.shift "web" - --- The default rule: return the WindowSet unmodified. You typically do not --- want to modify this line. -manageHook _ _ _ _ = return id - ------------------------------------------------------------------------- --- Logging - --- | Perform an arbitrary action on each internal state change or X event. --- Examples include: --- * do nothing --- * log the state to stdout --- --- See the 'DynamicLog' extension for examples. --- -logHook :: X () -logHook = return () - ------------------------------------------------------------------------- --- Extensible layouts --- --- You can specify and transform your layouts by modifying these values. --- If you change layout bindings be sure to use 'mod-shift-space' after --- restarting (with 'mod-q') to reset your layout state to the new --- defaults, as xmonad preserves your old layout settings by default. --- - --- | The available layouts. Note that each layout is separated by |||, which --- denotes layout choice. -layout = tiled ||| Mirror tiled ||| Full - -- Add extra layouts you want to use here: - -- % Extension-provided layouts - where - -- default tiling algorithm partitions the screen into two panes - tiled = Tall nmaster delta ratio - - -- The default number of windows in the master pane - nmaster = 1 - - -- Default proportion of screen occupied by master pane - ratio = 1%2 - - -- Percent of screen to increment by when resizing panes - delta = 3%100 - ------------------------------------------------------------------------- --- Key bindings: - --- | The preferred terminal program, which is used in a binding below and by --- certain contrib modules. -terminal :: String -terminal = "xterm" - --- | The xmonad key bindings. Add, modify or remove key bindings here. --- --- (The comment formatting character is used when generating the manpage) --- -keys :: XConfig -> M.Map (KeyMask, KeySym) (X ()) -keys conf@(XConfig {XMonad.modMask = modMask}) = M.fromList $ - -- launching and killing programs - [ ((modMask .|. shiftMask, xK_Return), spawn $ XMonad.terminal conf) -- %! Launch terminal - , ((modMask, xK_p ), spawn "exe=`dmenu_path | dmenu` && eval \"exec $exe\"") -- %! Launch dmenu - , ((modMask .|. shiftMask, xK_p ), spawn "gmrun") -- %! Launch gmrun - , ((modMask .|. shiftMask, xK_c ), kill) -- %! Close the focused window - - , ((modMask, xK_space ), sendMessage NextLayout) -- %! Rotate through the available layout algorithms - , ((modMask .|. shiftMask, xK_space ), setLayout $ XMonad.layoutHook conf) -- %! Reset the layouts on the current workspace to default - - , ((modMask, xK_n ), refresh) -- %! Resize viewed windows to the correct size - - -- move focus up or down the window stack - , ((modMask, xK_Tab ), windows W.focusDown) -- %! Move focus to the next window - , ((modMask, xK_j ), windows W.focusDown) -- %! Move focus to the next window - , ((modMask, xK_k ), windows W.focusUp ) -- %! Move focus to the previous window - , ((modMask, xK_m ), windows W.focusMaster ) -- %! Move focus to the master window - - -- modifying the window order - , ((modMask, xK_Return), windows W.swapMaster) -- %! Swap the focused window and the master window - , ((modMask .|. shiftMask, xK_j ), windows W.swapDown ) -- %! Swap the focused window with the next window - , ((modMask .|. shiftMask, xK_k ), windows W.swapUp ) -- %! Swap the focused window with the previous window - - -- resizing the master/slave ratio - , ((modMask, xK_h ), sendMessage Shrink) -- %! Shrink the master area - , ((modMask, xK_l ), sendMessage Expand) -- %! Expand the master area - - -- floating layer support - , ((modMask, xK_t ), withFocused $ windows . W.sink) -- %! Push window back into tiling - - -- increase or decrease number of windows in the master area - , ((modMask , xK_comma ), sendMessage (IncMasterN 1)) -- %! Increment the number of windows in the master area - , ((modMask , xK_period), sendMessage (IncMasterN (-1))) -- %! Deincrement the number of windows in the master area - - -- toggle the status bar gap - , ((modMask , xK_b ), modifyGap (\i n -> let x = (XMonad.defaultGaps conf ++ repeat (0,0,0,0)) !! i in if n == x then (0,0,0,0) else x)) -- %! Toggle the status bar gap - - -- quit, or restart - , ((modMask .|. shiftMask, xK_q ), io (exitWith ExitSuccess)) -- %! Quit xmonad - , ((modMask , xK_q ), broadcastMessage ReleaseResources >> restart (Just "xmonad") True) -- %! Restart xmonad - - -- % Extension-provided key bindings - ] - ++ - -- mod-[1..9] %! Switch to workspace N - -- mod-shift-[1..9] %! Move client to workspace N - [((m .|. modMask, k), windows $ f i) - | (i, k) <- zip (XMonad.workspaces conf) [xK_1 .. xK_9] - , (f, m) <- [(W.greedyView, 0), (W.shift, shiftMask)]] - ++ - -- mod-{w,e,r} %! Switch to physical/Xinerama screens 1, 2, or 3 - -- mod-shift-{w,e,r} %! Move client to screen 1, 2, or 3 - [((m .|. modMask, key), screenWorkspace sc >>= flip whenJust (windows . f)) - | (key, sc) <- zip [xK_w, xK_e, xK_r] [0..] - , (f, m) <- [(W.view, 0), (W.shift, shiftMask)]] - - -- % Extension-provided key bindings lists - --- | Mouse bindings: default actions bound to mouse events --- -mouseBindings :: XConfig -> M.Map (KeyMask, Button) (Window -> X ()) -mouseBindings (XConfig {XMonad.modMask = modMask}) = M.fromList $ - -- mod-button1 %! Set the window to floating mode and move by dragging - [ ((modMask, button1), (\w -> focus w >> mouseMoveWindow w)) - -- mod-button2 %! Raise the window to the top of the stack - , ((modMask, button2), (\w -> focus w >> windows W.swapMaster)) - -- mod-button3 %! Set the window to floating mode and resize by dragging - , ((modMask, button3), (\w -> focus w >> mouseResizeWindow w)) - -- you may also bind events to the mouse scroll wheel (button4 and button5) - - -- % Extension-provided mouse bindings - ] - --- % Extension-provided definitions - --- | And, finally, the default set of configuration values itself -defaultConfig :: XConfig -defaultConfig = XConfig - { XMonad.borderWidth = borderWidth - , XMonad.workspaces = workspaces - , XMonad.defaultGaps = defaultGaps - , XMonad.layoutHook = Layout layout - , XMonad.terminal = terminal - , XMonad.normalBorderColor = normalBorderColor - , XMonad.focusedBorderColor = focusedBorderColor - , XMonad.numlockMask = numlockMask - , XMonad.modMask = defaultModMask - , XMonad.keys = keys - , XMonad.logHook = logHook - , XMonad.mouseBindings = mouseBindings - , XMonad.manageHook = manageHook } diff --git a/XMonad/EventLoop.hs b/XMonad/EventLoop.hs deleted file mode 100644 index eba8e9d..0000000 --- a/XMonad/EventLoop.hs +++ /dev/null @@ -1,277 +0,0 @@ -{-# LANGUAGE MultiParamTypeClasses #-} ----------------------------------------------------------------------------- --- | --- Module : EventLoop.hs --- Copyright : (c) Spencer Janssen 2007 --- License : BSD3-style (see LICENSE) --- --- Maintainer : sjanssen@cse.unl.edu --- Stability : unstable --- Portability : not portable, uses mtl, X11, posix --- --- xmonad, a minimalist, tiling window manager for X11 --- ------------------------------------------------------------------------------ - -module XMonad.EventLoop (makeMain) where - -import Data.Bits -import qualified Data.Map as M -import qualified Data.Set as S -import Control.Monad.Reader -import Control.Monad.State -import Data.Maybe (fromMaybe) - -import System.Environment (getArgs) - -import Graphics.X11.Xlib hiding (refreshKeyboardMapping) -import Graphics.X11.Xlib.Extras -import Graphics.X11.Xinerama (getScreenInfo) - -import XMonad -import XMonad.StackSet (new, floating, member) -import qualified XMonad.StackSet as W -import XMonad.Operations - -import System.IO - --- | --- The main entry point --- -makeMain :: XConfig -> IO () -makeMain xmc = do - dpy <- openDisplay "" - let dflt = defaultScreen dpy - - rootw <- rootWindow dpy dflt - xinesc <- getScreenInfo dpy - nbc <- initColor dpy $ normalBorderColor xmc - fbc <- initColor dpy $ focusedBorderColor xmc - hSetBuffering stdout NoBuffering - args <- getArgs - - let layout = layoutHook xmc - lreads = readsLayout layout - initialWinset = new layout (workspaces xmc) $ zipWith SD xinesc gaps - - maybeRead reads' s = case reads' s of - [(x, "")] -> Just x - _ -> Nothing - - winset = fromMaybe initialWinset $ do - ("--resume" : s : _) <- return args - ws <- maybeRead reads s - return . W.ensureTags layout (workspaces xmc) - $ W.mapLayout (fromMaybe layout . maybeRead lreads) ws - - gaps = take (length xinesc) $ defaultGaps xmc ++ repeat (0,0,0,0) - - cf = XConf - { display = dpy - , config = xmc - , theRoot = rootw - , normalBorder = nbc - , focusedBorder = fbc - , keyActions = keys xmc xmc - , buttonActions = mouseBindings xmc xmc } - st = XState - { windowset = initialWinset - , mapped = S.empty - , waitingUnmap = M.empty - , dragging = Nothing } - - xSetErrorHandler -- in C, I'm too lazy to write the binding: dons - - -- setup initial X environment - sync dpy False - selectInput dpy rootw $ substructureRedirectMask .|. substructureNotifyMask - .|. enterWindowMask .|. leaveWindowMask .|. structureNotifyMask - - allocaXEvent $ \e -> - runX cf st $ do - - grabKeys - grabButtons - - io $ sync dpy False - - -- bootstrap the windowset, Operations.windows will identify all - -- the windows in winset as new and set initial properties for - -- those windows - windows (const winset) - - -- scan for all top-level windows, add the unmanaged ones to the - -- windowset - ws <- io $ scan dpy rootw - mapM_ manage ws - - -- main loop, for all you HOF/recursion fans out there. - forever_ $ handle =<< io (nextEvent dpy e >> getEvent e) - - return () - where forever_ a = a >> forever_ a - - --- --------------------------------------------------------------------- --- | Event handler. Map X events onto calls into Operations.hs, which --- modify our internal model of the window manager state. --- --- Events dwm handles that we don't: --- --- [ButtonPress] = buttonpress, --- [Expose] = expose, --- [PropertyNotify] = propertynotify, --- -handle :: Event -> X () - --- run window manager command -handle (KeyEvent {ev_event_type = t, ev_state = m, ev_keycode = code}) - | t == keyPress = withDisplay $ \dpy -> do - s <- io $ keycodeToKeysym dpy code 0 - mClean <- cleanMask m - ks <- asks keyActions - userCode $ whenJust (M.lookup (mClean, s) ks) id - --- manage a new window -handle (MapRequestEvent {ev_window = w}) = withDisplay $ \dpy -> do - wa <- io $ getWindowAttributes dpy w -- ignore override windows - -- need to ignore mapping requests by managed windows not on the current workspace - managed <- isClient w - when (not (wa_override_redirect wa) && not managed) $ do manage w - --- window destroyed, unmanage it --- window gone, unmanage it -handle (DestroyWindowEvent {ev_window = w}) = whenX (isClient w) $ unmanage w - --- We track expected unmap events in waitingUnmap. We ignore this event unless --- it is synthetic or we are not expecting an unmap notification from a window. -handle (UnmapEvent {ev_window = w, ev_send_event = synthetic}) = whenX (isClient w) $ do - e <- gets (fromMaybe 0 . M.lookup w . waitingUnmap) - if (synthetic || e == 0) - then unmanage w - else modify (\s -> s { waitingUnmap = M.adjust pred w (waitingUnmap s) }) - --- set keyboard mapping -handle e@(MappingNotifyEvent {}) = do - io $ refreshKeyboardMapping e - when (ev_request e == mappingKeyboard) grabKeys - --- handle button release, which may finish dragging. -handle e@(ButtonEvent {ev_event_type = t}) - | t == buttonRelease = do - drag <- gets dragging - case drag of - -- we're done dragging and have released the mouse: - Just (_,f) -> modify (\s -> s { dragging = Nothing }) >> f - Nothing -> broadcastMessage e - --- handle motionNotify event, which may mean we are dragging. -handle e@(MotionEvent {ev_event_type = _t, ev_x = x, ev_y = y}) = do - drag <- gets dragging - case drag of - Just (d,_) -> d (fromIntegral x) (fromIntegral y) -- we're dragging - Nothing -> broadcastMessage e - --- click on an unfocused window, makes it focused on this workspace -handle e@(ButtonEvent {ev_window = w,ev_event_type = t,ev_button = b }) - | t == buttonPress = do - -- If it's the root window, then it's something we - -- grabbed in grabButtons. Otherwise, it's click-to-focus. - isr <- isRoot w - m <- cleanMask $ ev_state e - ba <- asks buttonActions - if isr then userCode $ whenJust (M.lookup (m, b) ba) ($ ev_subwindow e) - else focus w - sendMessage e -- Always send button events. - --- entered a normal window, makes this focused. -handle e@(CrossingEvent {ev_window = w, ev_event_type = t}) - | t == enterNotify && ev_mode e == notifyNormal - && ev_detail e /= notifyInferior = focus w - --- left a window, check if we need to focus root -handle e@(CrossingEvent {ev_event_type = t}) - | t == leaveNotify - = do rootw <- asks theRoot - when (ev_window e == rootw && not (ev_same_screen e)) $ setFocusX rootw - --- configure a window -handle e@(ConfigureRequestEvent {ev_window = w}) = withDisplay $ \dpy -> do - ws <- gets windowset - wa <- io $ getWindowAttributes dpy w - - bw <- asks (borderWidth . config) - - if M.member w (floating ws) - || not (member w ws) - then do io $ configureWindow dpy w (ev_value_mask e) $ WindowChanges - { wc_x = ev_x e - , wc_y = ev_y e - , wc_width = ev_width e - , wc_height = ev_height e - , wc_border_width = fromIntegral bw - , wc_sibling = ev_above e - , wc_stack_mode = ev_detail e } - when (member w ws) (float w) - else io $ allocaXEvent $ \ev -> do - setEventType ev configureNotify - setConfigureEvent ev w w - (wa_x wa) (wa_y wa) (wa_width wa) - (wa_height wa) (ev_border_width e) none (wa_override_redirect wa) - sendEvent dpy w False 0 ev - io $ sync dpy False - --- configuration changes in the root may mean display settings have changed -handle (ConfigureEvent {ev_window = w}) = whenX (isRoot w) rescreen - --- property notify -handle PropertyEvent { ev_event_type = t, ev_atom = a } - | t == propertyNotify && a == wM_NAME = userCode =<< asks (logHook . config) - -handle e = broadcastMessage e -- trace (eventName e) -- ignoring - - --- --------------------------------------------------------------------- --- IO stuff. Doesn't require any X state --- Most of these things run only on startup (bar grabkeys) - --- | scan for any new windows to manage. If they're already managed, --- this should be idempotent. -scan :: Display -> Window -> IO [Window] -scan dpy rootw = do - (_, _, ws) <- queryTree dpy rootw - filterM ok ws - -- TODO: scan for windows that are either 'IsViewable' or where WM_STATE == - -- Iconic - where ok w = do wa <- getWindowAttributes dpy w - a <- internAtom dpy "WM_STATE" False - p <- getWindowProperty32 dpy a w - let ic = case p of - Just (3:_) -> True -- 3 for iconified - _ -> False - return $ not (wa_override_redirect wa) - && (wa_map_state wa == waIsViewable || ic) - --- | Grab the keys back -grabKeys :: X () -grabKeys = do - XConf { display = dpy, theRoot = rootw } <- ask - let grab kc m = io $ grabKey dpy kc m rootw True grabModeAsync grabModeAsync - io $ ungrabKey dpy anyKey anyModifier rootw - ks <- asks keyActions - forM_ (M.keys ks) $ \(mask,sym) -> do - kc <- io $ keysymToKeycode dpy sym - -- "If the specified KeySym is not defined for any KeyCode, - -- XKeysymToKeycode() returns zero." - when (kc /= '\0') $ mapM_ (grab kc . (mask .|.)) =<< extraModifiers - --- | XXX comment me -grabButtons :: X () -grabButtons = do - XConf { display = dpy, theRoot = rootw } <- ask - let grab button mask = io $ grabButton dpy button mask rootw False buttonPressMask - grabModeAsync grabModeSync none none - io $ ungrabButton dpy anyButton anyModifier rootw - ems <- extraModifiers - ba <- asks buttonActions - mapM_ (\(m,b) -> mapM_ (grab b . (m .|.)) ems) (M.keys $ ba) diff --git a/xmonad.cabal b/xmonad.cabal index 2d34e8d..062dc58 100644 --- a/xmonad.cabal +++ b/xmonad.cabal @@ -21,15 +21,15 @@ build-depends: base>=2.0, mtl, unix, X11==1.3.0 extra-source-files: README TODO tests/loc.hs tests/Properties.hs man/xmonad.1.in util/GenerateManpage.hs man/xmonad.1 man/xmonad.html exposed-modules: XMonad - XMonad.DefaultConfig - XMonad.EventLoop + XMonad.Config + XMonad.Core XMonad.Layouts XMonad.Operations XMonad.StackSet executable: xmonad main-is: Main.hs -other-modules: XMonad.EventLoop XMonad.Layouts XMonad.Operations XMonad.StackSet XMonad +other-modules: XMonad.Core XMonad.Layouts XMonad.Operations XMonad.StackSet XMonad ghc-options: -funbox-strict-fields -O2 -fasm -Wall -optl-Wl,-s ghc-prof-options: -prof -auto-all extensions: GeneralizedNewtypeDeriving -- cgit v1.2.3