From 9ac3bfc633c960f5b6aa415e70007b02fcf644dd Mon Sep 17 00:00:00 2001 From: Adam Vogt Date: Fri, 2 May 2014 19:51:59 +0200 Subject: move library part to src/ Ignore-this: 39633e17915844643f12a3dd25288e81 darcs-hash:20140502175159-1499c-fa8e1239e9c2a843e2272e7173374245682a1314.gz --- XMonad.hs | 47 ---- XMonad/Config.hs | 330 -------------------------- XMonad/Core.hs | 530 ------------------------------------------ XMonad/Layout.hs | 210 ----------------- XMonad/Main.hsc | 410 --------------------------------- XMonad/ManageHook.hs | 119 ---------- XMonad/Operations.hs | 586 ----------------------------------------------- XMonad/StackSet.hs | 558 -------------------------------------------- src/XMonad.hs | 47 ++++ src/XMonad/Config.hs | 330 ++++++++++++++++++++++++++ src/XMonad/Core.hs | 530 ++++++++++++++++++++++++++++++++++++++++++ src/XMonad/Layout.hs | 210 +++++++++++++++++ src/XMonad/Main.hsc | 410 +++++++++++++++++++++++++++++++++ src/XMonad/ManageHook.hs | 119 ++++++++++ src/XMonad/Operations.hs | 586 +++++++++++++++++++++++++++++++++++++++++++++++ src/XMonad/StackSet.hs | 558 ++++++++++++++++++++++++++++++++++++++++++++ xmonad.cabal | 23 +- 17 files changed, 2799 insertions(+), 2804 deletions(-) delete mode 100644 XMonad.hs delete mode 100644 XMonad/Config.hs delete mode 100644 XMonad/Core.hs delete mode 100644 XMonad/Layout.hs delete mode 100644 XMonad/Main.hsc delete mode 100644 XMonad/ManageHook.hs delete mode 100644 XMonad/Operations.hs delete mode 100644 XMonad/StackSet.hs create mode 100644 src/XMonad.hs create mode 100644 src/XMonad/Config.hs create mode 100644 src/XMonad/Core.hs create mode 100644 src/XMonad/Layout.hs create mode 100644 src/XMonad/Main.hsc create mode 100644 src/XMonad/ManageHook.hs create mode 100644 src/XMonad/Operations.hs create mode 100644 src/XMonad/StackSet.hs diff --git a/XMonad.hs b/XMonad.hs deleted file mode 100644 index c1fc5dc..0000000 --- a/XMonad.hs +++ /dev/null @@ -1,47 +0,0 @@ --------------------------------------------------------------------- --- | --- Module : XMonad --- Copyright : (c) Don Stewart --- License : BSD3 --- --- Maintainer: Don Stewart --- Stability : provisional --- Portability: --- --------------------------------------------------------------------- --- --- Useful exports for configuration files. - -module XMonad ( - - module XMonad.Main, - module XMonad.Core, - module XMonad.Config, - module XMonad.Layout, - module XMonad.ManageHook, - module XMonad.Operations, - module Graphics.X11, - module Graphics.X11.Xlib.Extras, - (.|.), - MonadState(..), gets, modify, - MonadReader(..), asks, - MonadIO(..) - - ) where - --- core modules -import XMonad.Main -import XMonad.Core -import XMonad.Config -import XMonad.Layout -import XMonad.ManageHook -import XMonad.Operations --- import XMonad.StackSet -- conflicts with 'workspaces' defined in XMonad.hs - --- modules needed to get basic configuration working -import Data.Bits -import Graphics.X11 hiding (refreshKeyboardMapping) -import Graphics.X11.Xlib.Extras - -import Control.Monad.State -import Control.Monad.Reader diff --git a/XMonad/Config.hs b/XMonad/Config.hs deleted file mode 100644 index 9aaab8f..0000000 --- a/XMonad/Config.hs +++ /dev/null @@ -1,330 +0,0 @@ -{-# OPTIONS -fno-warn-missing-signatures -fno-warn-orphans #-} -{-# LANGUAGE TypeFamilies #-} ------------------------------------------------------------------------------ --- | --- 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. --- --- DO NOT MODIFY THIS FILE! It won't work. You may configure xmonad --- by providing your own @~\/.xmonad\/xmonad.hs@ that overrides --- specific fields in the default config, 'def'. For a starting point, you can --- copy the @xmonad.hs@ found in the @man@ directory, or look at --- examples on the xmonad wiki. --- ------------------------------------------------------------------------- - -module XMonad.Config (defaultConfig, Default(..)) where - --- --- Useful imports --- -import XMonad.Core as XMonad hiding - (workspaces,manageHook,keys,logHook,startupHook,borderWidth,mouseBindings - ,layoutHook,modMask,terminal,normalBorderColor,focusedBorderColor,focusFollowsMouse - ,handleEventHook,clickJustFocuses,rootMask,clientMask) -import qualified XMonad.Core as XMonad - (workspaces,manageHook,keys,logHook,startupHook,borderWidth,mouseBindings - ,layoutHook,modMask,terminal,normalBorderColor,focusedBorderColor,focusFollowsMouse - ,handleEventHook,clickJustFocuses,rootMask,clientMask) - -import XMonad.Layout -import XMonad.Operations -import XMonad.ManageHook -import qualified XMonad.StackSet as W -import Data.Bits ((.|.)) -import Data.Default -import Data.Monoid -import qualified Data.Map as M -import System.Exit -import Graphics.X11.Xlib -import Graphics.X11.Xlib.Extras - --- | 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 - --- | Width of the window border in pixels. --- -borderWidth :: Dimension -borderWidth = 1 - --- | Border colors for unfocused and focused windows, respectively. --- -normalBorderColor, focusedBorderColor :: String -normalBorderColor = "gray" -- "#dddddd" -focusedBorderColor = "red" -- "#ff0000" don't use hex, not <24 bit safe - ------------------------------------------------------------------------- --- 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 :: ManageHook -manageHook = composeAll - [ className =? "MPlayer" --> doFloat - , className =? "Gimp" --> doFloat ] - ------------------------------------------------------------------------- --- 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 () - ------------------------------------------------------------------------- --- Event handling - --- | Defines a custom handler function for X Events. The function should --- return (All True) if the default handler is to be run afterwards. --- To combine event hooks, use mappend or mconcat from Data.Monoid. -handleEventHook :: Event -> X All -handleEventHook _ = return (All True) - --- | Perform an arbitrary action at xmonad startup. -startupHook :: X () -startupHook = 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 - 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 - ------------------------------------------------------------------------- --- Event Masks: - --- | The client events that xmonad is interested in -clientMask :: EventMask -clientMask = structureNotifyMask .|. enterWindowMask .|. propertyChangeMask - --- | The root events that xmonad is interested in -rootMask :: EventMask -rootMask = substructureRedirectMask .|. substructureNotifyMask - .|. enterWindowMask .|. leaveWindowMask .|. structureNotifyMask - .|. buttonPressMask - ------------------------------------------------------------------------- --- Key bindings: - --- | The preferred terminal program, which is used in a binding below and by --- certain contrib modules. -terminal :: String -terminal = "xterm" - --- | Whether focus follows the mouse pointer. -focusFollowsMouse :: Bool -focusFollowsMouse = True - --- | Whether a mouse click select the focus or is just passed to the window -clickJustFocuses :: Bool -clickJustFocuses = True - - --- | The xmonad key bindings. Add, modify or remove key bindings here. --- --- (The comment formatting character is used when generating the manpage) --- -keys :: XConfig Layout -> 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 "dmenu_run") -- %! 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 .|. shiftMask, xK_Tab ), windows W.focusUp ) -- %! Move focus to the previous 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 - - -- quit, or restart - , ((modMask .|. shiftMask, xK_q ), io (exitWith ExitSuccess)) -- %! Quit xmonad - , ((modMask , xK_q ), spawn "if type xmonad; then xmonad --recompile && xmonad --restart; else xmessage xmonad not in \\$PATH: \"$PATH\"; fi") -- %! Restart xmonad - - , ((modMask .|. shiftMask, xK_slash ), spawn ("echo \"" ++ help ++ "\" | xmessage -file -")) -- %! Run xmessage with a summary of the default keybindings (useful for beginners) - -- repeat the binding for non-American layout keyboards - , ((modMask , xK_question), spawn ("echo \"" ++ help ++ "\" | xmessage -file -")) - ] - ++ - -- 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)]] - --- | Mouse bindings: default actions bound to mouse events -mouseBindings :: XConfig Layout -> 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 - >> windows W.shiftMaster) - -- mod-button2 %! Raise the window to the top of the stack - , ((modMask, button2), windows . (W.shiftMaster .) . W.focusWindow) - -- mod-button3 %! Set the window to floating mode and resize by dragging - , ((modMask, button3), \w -> focus w >> mouseResizeWindow w - >> windows W.shiftMaster) - -- you may also bind events to the mouse scroll wheel (button4 and button5) - ] - -instance (a ~ Choose Tall (Choose (Mirror Tall) Full)) => Default (XConfig a) where - def = XConfig - { XMonad.borderWidth = borderWidth - , XMonad.workspaces = workspaces - , XMonad.layoutHook = layout - , XMonad.terminal = terminal - , XMonad.normalBorderColor = normalBorderColor - , XMonad.focusedBorderColor = focusedBorderColor - , XMonad.modMask = defaultModMask - , XMonad.keys = keys - , XMonad.logHook = logHook - , XMonad.startupHook = startupHook - , XMonad.mouseBindings = mouseBindings - , XMonad.manageHook = manageHook - , XMonad.handleEventHook = handleEventHook - , XMonad.focusFollowsMouse = focusFollowsMouse - , XMonad.clickJustFocuses = clickJustFocuses - , XMonad.clientMask = clientMask - , XMonad.rootMask = rootMask - } - --- | The default set of configuration values itself -{-# DEPRECATED defaultConfig "Use def (from Data.Default, and re-exported by XMonad and XMonad.Config) instead." #-} -defaultConfig :: XConfig (Choose Tall (Choose (Mirror Tall) Full)) -defaultConfig = def - --- | Finally, a copy of the default bindings in simple textual tabular format. -help :: String -help = unlines ["The default modifier key is 'alt'. Default keybindings:", - "", - "-- launching and killing programs", - "mod-Shift-Enter Launch xterminal", - "mod-p Launch dmenu", - "mod-Shift-p Launch gmrun", - "mod-Shift-c Close/kill the focused window", - "mod-Space Rotate through the available layout algorithms", - "mod-Shift-Space Reset the layouts on the current workSpace to default", - "mod-n Resize/refresh viewed windows to the correct size", - "", - "-- move focus up or down the window stack", - "mod-Tab Move focus to the next window", - "mod-Shift-Tab Move focus to the previous window", - "mod-j Move focus to the next window", - "mod-k Move focus to the previous window", - "mod-m Move focus to the master window", - "", - "-- modifying the window order", - "mod-Return Swap the focused window and the master window", - "mod-Shift-j Swap the focused window with the next window", - "mod-Shift-k Swap the focused window with the previous window", - "", - "-- resizing the master/slave ratio", - "mod-h Shrink the master area", - "mod-l Expand the master area", - "", - "-- floating layer support", - "mod-t Push window back into tiling; unfloat and re-tile it", - "", - "-- increase or decrease number of windows in the master area", - "mod-comma (mod-,) Increment the number of windows in the master area", - "mod-period (mod-.) Deincrement the number of windows in the master area", - "", - "-- quit, or restart", - "mod-Shift-q Quit xmonad", - "mod-q Restart xmonad", - "mod-[1..9] Switch to workSpace N", - "", - "-- Workspaces & screens", - "mod-Shift-[1..9] Move client to workspace N", - "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", - "", - "-- Mouse bindings: default actions bound to mouse events", - "mod-button1 Set the window to floating mode and move by dragging", - "mod-button2 Raise the window to the top of the stack", - "mod-button3 Set the window to floating mode and resize by dragging"] \ No newline at end of file diff --git a/XMonad/Core.hs b/XMonad/Core.hs deleted file mode 100644 index cef4f81..0000000 --- a/XMonad/Core.hs +++ /dev/null @@ -1,530 +0,0 @@ -{-# LANGUAGE ExistentialQuantification, FlexibleInstances, GeneralizedNewtypeDeriving, - MultiParamTypeClasses, TypeSynonymInstances, CPP, DeriveDataTypeable #-} - ------------------------------------------------------------------------------ --- | --- Module : XMonad.Core --- Copyright : (c) Spencer Janssen 2007 --- License : BSD3-style (see LICENSE) --- --- Maintainer : spencerjanssen@gmail.com --- Stability : unstable --- Portability : not portable, uses cunning newtype deriving --- --- The 'X' monad, a state monad transformer over 'IO', for the window --- manager state, and support routines. --- ------------------------------------------------------------------------------ - -module XMonad.Core ( - X, WindowSet, WindowSpace, WorkspaceId, - ScreenId(..), ScreenDetail(..), XState(..), - XConf(..), XConfig(..), LayoutClass(..), - Layout(..), readsLayout, Typeable, Message, - SomeMessage(..), fromMessage, LayoutMessages(..), - StateExtension(..), ExtensionClass(..), - runX, catchX, userCode, userCodeDef, io, catchIO, installSignalHandlers, uninstallSignalHandlers, - withDisplay, withWindowSet, isRoot, runOnWorkspaces, - getAtom, spawn, spawnPID, xfork, getXMonadDir, recompile, trace, whenJust, whenX, - atom_WM_STATE, atom_WM_PROTOCOLS, atom_WM_DELETE_WINDOW, atom_WM_TAKE_FOCUS, ManageHook, Query(..), runQuery - ) where - -import XMonad.StackSet hiding (modify) - -import Prelude -import Codec.Binary.UTF8.String (encodeString) -import Control.Exception.Extensible (fromException, try, bracket, throw, finally, SomeException(..)) -import qualified Control.Exception.Extensible as E -import Control.Applicative -import Control.Monad.State -import Control.Monad.Reader -import Data.Default -import System.FilePath -import System.IO -import System.Info -import System.Posix.Process (executeFile, forkProcess, getAnyProcessStatus, createSession) -import System.Posix.Signals -import System.Posix.IO -import System.Posix.Types (ProcessID) -import System.Process -import System.Directory -import System.Exit -import Graphics.X11.Xlib -import Graphics.X11.Xlib.Extras (Event) -import Data.Typeable -import Data.List ((\\)) -import Data.Maybe (isJust,fromMaybe) -import Data.Monoid - -import qualified Data.Map as M -import qualified Data.Set as S - --- | XState, the (mutable) window manager state. -data XState = XState - { windowset :: !WindowSet -- ^ workspace list - , mapped :: !(S.Set Window) -- ^ the Set of mapped windows - , waitingUnmap :: !(M.Map Window Int) -- ^ the number of expected UnmapEvents - , dragging :: !(Maybe (Position -> Position -> X (), X ())) - , numberlockMask :: !KeyMask -- ^ The numlock modifier - , extensibleState :: !(M.Map String (Either String StateExtension)) - -- ^ stores custom state information. - -- - -- The module "XMonad.Utils.ExtensibleState" in xmonad-contrib - -- provides additional information and a simple interface for using this. - } - --- | XConf, the (read-only) window manager configuration. -data XConf = XConf - { display :: Display -- ^ the X11 display - , config :: !(XConfig Layout) -- ^ initial user configuration - , theRoot :: !Window -- ^ the root window - , normalBorder :: !Pixel -- ^ border color of unfocused windows - , focusedBorder :: !Pixel -- ^ border color of the focused window - , keyActions :: !(M.Map (KeyMask, KeySym) (X ())) - -- ^ a mapping of key presses to actions - , buttonActions :: !(M.Map (KeyMask, Button) (Window -> X ())) - -- ^ a mapping of button presses to actions - , mouseFocused :: !Bool -- ^ was refocus caused by mouse action? - , mousePosition :: !(Maybe (Position, Position)) - -- ^ position of the mouse according to - -- the event currently being processed - , currentEvent :: !(Maybe Event) - -- ^ event currently being processed - } - --- todo, better name -data XConfig l = XConfig - { normalBorderColor :: !String -- ^ Non focused windows border color. Default: \"#dddddd\" - , focusedBorderColor :: !String -- ^ Focused windows border color. Default: \"#ff0000\" - , terminal :: !String -- ^ The preferred terminal application. Default: \"xterm\" - , layoutHook :: !(l Window) -- ^ The available layouts - , manageHook :: !ManageHook -- ^ The action to run when a new window is opened - , handleEventHook :: !(Event -> X All) -- ^ Handle an X event, returns (All True) if the default handler - -- should also be run afterwards. mappend should be used for combining - -- event hooks in most cases. - , workspaces :: ![String] -- ^ The list of workspaces' names - , modMask :: !KeyMask -- ^ the mod modifier - , keys :: !(XConfig Layout -> M.Map (ButtonMask,KeySym) (X ())) - -- ^ The key binding: a map from key presses and actions - , mouseBindings :: !(XConfig Layout -> M.Map (ButtonMask, Button) (Window -> X ())) - -- ^ The mouse bindings - , borderWidth :: !Dimension -- ^ The border width - , logHook :: !(X ()) -- ^ The action to perform when the windows set is changed - , startupHook :: !(X ()) -- ^ The action to perform on startup - , focusFollowsMouse :: !Bool -- ^ Whether window entry events can change focus - , clickJustFocuses :: !Bool -- ^ False to make a click which changes focus to be additionally passed to the window - , clientMask :: !EventMask -- ^ The client events that xmonad is interested in - , rootMask :: !EventMask -- ^ The root events that xmonad is interested in - } - - -type WindowSet = StackSet WorkspaceId (Layout Window) Window ScreenId ScreenDetail -type WindowSpace = Workspace WorkspaceId (Layout Window) Window - --- | Virtual workspace indices -type WorkspaceId = String - --- | Physical screen indices -newtype ScreenId = S Int deriving (Eq,Ord,Show,Read,Enum,Num,Integral,Real) - --- | The 'Rectangle' with screen dimensions -data ScreenDetail = SD { screenRect :: !Rectangle } deriving (Eq,Show, Read) - ------------------------------------------------------------------------- - --- | The X monad, 'ReaderT' and 'StateT' transformers over 'IO' --- encapsulating the window manager configuration and state, --- respectively. --- --- Dynamic components may be retrieved with 'get', static components --- with 'ask'. With newtype deriving we get readers and state monads --- instantiated on 'XConf' and 'XState' automatically. --- -newtype X a = X (ReaderT XConf (StateT XState IO) a) - deriving (Functor, Monad, MonadIO, MonadState XState, MonadReader XConf, Typeable) - -instance Applicative X where - pure = return - (<*>) = ap - -instance (Monoid a) => Monoid (X a) where - mempty = return mempty - mappend = liftM2 mappend - -instance Default a => Default (X a) where - def = return def - -type ManageHook = Query (Endo WindowSet) -newtype Query a = Query (ReaderT Window X a) - deriving (Functor, Applicative, Monad, MonadReader Window, MonadIO) - -runQuery :: Query a -> Window -> X a -runQuery (Query m) w = runReaderT m w - -instance Monoid a => Monoid (Query a) where - mempty = return mempty - mappend = liftM2 mappend - -instance Default a => Default (Query a) where - def = return def - --- | Run the 'X' monad, given a chunk of 'X' monad code, and an initial state --- Return the result, and final state -runX :: XConf -> XState -> X a -> IO (a, XState) -runX c st (X a) = runStateT (runReaderT a c) st - --- | Run in the 'X' monad, and in case of exception, and catch it and log it --- to stderr, and run the error case. -catchX :: X a -> X a -> X a -catchX job errcase = do - st <- get - c <- ask - (a, s') <- io $ runX c st job `E.catch` \e -> case fromException e of - Just x -> throw e `const` (x `asTypeOf` ExitSuccess) - _ -> do hPrint stderr e; runX c st errcase - put s' - return a - --- | Execute the argument, catching all exceptions. Either this function or --- 'catchX' should be used at all callsites of user customized code. -userCode :: X a -> X (Maybe a) -userCode a = catchX (Just `liftM` a) (return Nothing) - --- | Same as userCode but with a default argument to return instead of using --- Maybe, provided for convenience. -userCodeDef :: a -> X a -> X a -userCodeDef defValue a = fromMaybe defValue `liftM` userCode a - --- --------------------------------------------------------------------- --- Convenient wrappers to state - --- | Run a monad action with the current display settings -withDisplay :: (Display -> X a) -> X a -withDisplay f = asks display >>= f - --- | Run a monadic action with the current stack set -withWindowSet :: (WindowSet -> X a) -> X a -withWindowSet f = gets windowset >>= f - --- | True if the given window is the root window -isRoot :: Window -> X Bool -isRoot w = (w==) <$> asks theRoot - --- | Wrapper for the common case of atom internment -getAtom :: String -> X Atom -getAtom str = withDisplay $ \dpy -> io $ internAtom dpy str False - --- | Common non-predefined atoms -atom_WM_PROTOCOLS, atom_WM_DELETE_WINDOW, atom_WM_STATE, atom_WM_TAKE_FOCUS :: X Atom -atom_WM_PROTOCOLS = getAtom "WM_PROTOCOLS" -atom_WM_DELETE_WINDOW = getAtom "WM_DELETE_WINDOW" -atom_WM_STATE = getAtom "WM_STATE" -atom_WM_TAKE_FOCUS = getAtom "WM_TAKE_FOCUS" - ------------------------------------------------------------------------- --- LayoutClass handling. See particular instances in Operations.hs - --- | An existential type that can hold any object that is in 'Read' --- and 'LayoutClass'. -data Layout a = forall l. (LayoutClass l a, Read (l a)) => Layout (l a) - --- | Using the 'Layout' as a witness, parse existentially wrapped windows --- from a 'String'. -readsLayout :: Layout a -> String -> [(Layout a, String)] -readsLayout (Layout l) s = [(Layout (asTypeOf x l), rs) | (x, rs) <- reads s] - --- | Every layout must be an instance of 'LayoutClass', which defines --- the basic layout operations along with a sensible default for each. --- --- Minimal complete definition: --- --- * 'runLayout' || (('doLayout' || 'pureLayout') && 'emptyLayout'), and --- --- * 'handleMessage' || 'pureMessage' --- --- You should also strongly consider implementing 'description', --- although it is not required. --- --- Note that any code which /uses/ 'LayoutClass' methods should only --- ever call 'runLayout', 'handleMessage', and 'description'! In --- other words, the only calls to 'doLayout', 'pureMessage', and other --- such methods should be from the default implementations of --- 'runLayout', 'handleMessage', and so on. This ensures that the --- proper methods will be used, regardless of the particular methods --- that any 'LayoutClass' instance chooses to define. -class Show (layout a) => LayoutClass layout a where - - -- | By default, 'runLayout' calls 'doLayout' if there are any - -- windows to be laid out, and 'emptyLayout' otherwise. Most - -- instances of 'LayoutClass' probably do not need to implement - -- 'runLayout'; it is only useful for layouts which wish to make - -- use of more of the 'Workspace' information (for example, - -- "XMonad.Layout.PerWorkspace"). - runLayout :: Workspace WorkspaceId (layout a) a - -> Rectangle - -> X ([(a, Rectangle)], Maybe (layout a)) - runLayout (Workspace _ l ms) r = maybe (emptyLayout l r) (doLayout l r) ms - - -- | Given a 'Rectangle' in which to place the windows, and a 'Stack' - -- of windows, return a list of windows and their corresponding - -- Rectangles. If an element is not given a Rectangle by - -- 'doLayout', then it is not shown on screen. The order of - -- windows in this list should be the desired stacking order. - -- - -- Also possibly return a modified layout (by returning @Just - -- newLayout@), if this layout needs to be modified (e.g. if it - -- keeps track of some sort of state). Return @Nothing@ if the - -- layout does not need to be modified. - -- - -- Layouts which do not need access to the 'X' monad ('IO', window - -- manager state, or configuration) and do not keep track of their - -- own state should implement 'pureLayout' instead of 'doLayout'. - doLayout :: layout a -> Rectangle -> Stack a - -> X ([(a, Rectangle)], Maybe (layout a)) - doLayout l r s = return (pureLayout l r s, Nothing) - - -- | This is a pure version of 'doLayout', for cases where we - -- don't need access to the 'X' monad to determine how to lay out - -- the windows, and we don't need to modify the layout itself. - pureLayout :: layout a -> Rectangle -> Stack a -> [(a, Rectangle)] - pureLayout _ r s = [(focus s, r)] - - -- | 'emptyLayout' is called when there are no windows. - emptyLayout :: layout a -> Rectangle -> X ([(a, Rectangle)], Maybe (layout a)) - emptyLayout _ _ = return ([], Nothing) - - -- | 'handleMessage' performs message handling. If - -- 'handleMessage' returns @Nothing@, then the layout did not - -- respond to the message and the screen is not refreshed. - -- Otherwise, 'handleMessage' returns an updated layout and the - -- screen is refreshed. - -- - -- Layouts which do not need access to the 'X' monad to decide how - -- to handle messages should implement 'pureMessage' instead of - -- 'handleMessage' (this restricts the risk of error, and makes - -- testing much easier). - handleMessage :: layout a -> SomeMessage -> X (Maybe (layout a)) - handleMessage l = return . pureMessage l - - -- | Respond to a message by (possibly) changing our layout, but - -- taking no other action. If the layout changes, the screen will - -- be refreshed. - pureMessage :: layout a -> SomeMessage -> Maybe (layout a) - pureMessage _ _ = Nothing - - -- | This should be a human-readable string that is used when - -- selecting layouts by name. The default implementation is - -- 'show', which is in some cases a poor default. - description :: layout a -> String - description = show - -instance LayoutClass Layout Window where - runLayout (Workspace i (Layout l) ms) r = fmap (fmap Layout) `fmap` runLayout (Workspace i l ms) r - doLayout (Layout l) r s = fmap (fmap Layout) `fmap` doLayout l r s - emptyLayout (Layout l) r = fmap (fmap Layout) `fmap` emptyLayout l r - handleMessage (Layout l) = fmap (fmap Layout) . handleMessage l - description (Layout l) = description l - -instance Show (Layout a) where show (Layout l) = show l - --- | Based on ideas in /An Extensible Dynamically-Typed Hierarchy of --- Exceptions/, Simon Marlow, 2006. Use extensible messages to the --- 'handleMessage' handler. --- --- User-extensible messages must be a member of this class. --- -class Typeable a => Message a - --- | --- A wrapped value of some type in the 'Message' class. --- -data SomeMessage = forall a. Message a => SomeMessage a - --- | --- And now, unwrap a given, unknown 'Message' type, performing a (dynamic) --- type check on the result. --- -fromMessage :: Message m => SomeMessage -> Maybe m -fromMessage (SomeMessage m) = cast m - --- X Events are valid Messages. -instance Message Event - --- | 'LayoutMessages' are core messages that all layouts (especially stateful --- layouts) should consider handling. -data LayoutMessages = Hide -- ^ sent when a layout becomes non-visible - | ReleaseResources -- ^ sent when xmonad is exiting or restarting - deriving (Typeable, Eq) - -instance Message LayoutMessages - --- --------------------------------------------------------------------- --- Extensible state --- - --- | Every module must make the data it wants to store --- an instance of this class. --- --- Minimal complete definition: initialValue -class Typeable a => ExtensionClass a where - -- | Defines an initial value for the state extension - initialValue :: a - -- | Specifies whether the state extension should be - -- persistent. Setting this method to 'PersistentExtension' - -- will make the stored data survive restarts, but - -- requires a to be an instance of Read and Show. - -- - -- It defaults to 'StateExtension', i.e. no persistence. - extensionType :: a -> StateExtension - extensionType = StateExtension - --- | Existential type to store a state extension. -data StateExtension = - forall a. ExtensionClass a => StateExtension a - -- ^ Non-persistent state extension - | forall a. (Read a, Show a, ExtensionClass a) => PersistentExtension a - -- ^ Persistent extension - --- --------------------------------------------------------------------- --- | General utilities --- --- Lift an 'IO' action into the 'X' monad -io :: MonadIO m => IO a -> m a -io = liftIO - --- | Lift an 'IO' action into the 'X' monad. If the action results in an 'IO' --- exception, log the exception to stderr and continue normal execution. -catchIO :: MonadIO m => IO () -> m () -catchIO f = io (f `E.catch` \(SomeException e) -> hPrint stderr e >> hFlush stderr) - --- | spawn. Launch an external application. Specifically, it double-forks and --- runs the 'String' you pass as a command to \/bin\/sh. --- --- Note this function assumes your locale uses utf8. -spawn :: MonadIO m => String -> m () -spawn x = spawnPID x >> return () - --- | Like 'spawn', but returns the 'ProcessID' of the launched application -spawnPID :: MonadIO m => String -> m ProcessID -spawnPID x = xfork $ executeFile "/bin/sh" False ["-c", encodeString x] Nothing - --- | A replacement for 'forkProcess' which resets default signal handlers. -xfork :: MonadIO m => IO () -> m ProcessID -xfork x = io . forkProcess . finally nullStdin $ do - uninstallSignalHandlers - createSession - x - where - nullStdin = do - fd <- openFd "/dev/null" ReadOnly Nothing defaultFileFlags - dupTo fd stdInput - closeFd fd - --- | This is basically a map function, running a function in the 'X' monad on --- each workspace with the output of that function being the modified workspace. -runOnWorkspaces :: (WindowSpace -> X WindowSpace) -> X () -runOnWorkspaces job = do - ws <- gets windowset - h <- mapM job $ hidden ws - c:v <- mapM (\s -> (\w -> s { workspace = w}) <$> job (workspace s)) - $ current ws : visible ws - modify $ \s -> s { windowset = ws { current = c, visible = v, hidden = h } } - --- | Return the path to @~\/.xmonad@. -getXMonadDir :: MonadIO m => m String -getXMonadDir = io $ getAppUserDataDirectory "xmonad" - --- | 'recompile force', recompile @~\/.xmonad\/xmonad.hs@ when any of the --- following apply: --- --- * force is 'True' --- --- * the xmonad executable does not exist --- --- * the xmonad executable is older than xmonad.hs or any file in --- ~\/.xmonad\/lib --- --- The -i flag is used to restrict recompilation to the xmonad.hs file only, --- and any files in the ~\/.xmonad\/lib directory. --- --- Compilation errors (if any) are logged to ~\/.xmonad\/xmonad.errors. If --- GHC indicates failure with a non-zero exit code, an xmessage displaying --- that file is spawned. --- --- 'False' is returned if there are compilation errors. --- -recompile :: MonadIO m => Bool -> m Bool -recompile force = io $ do - dir <- getXMonadDir - let binn = "xmonad-"++arch++"-"++os - bin = dir binn - base = dir "xmonad" - err = base ++ ".errors" - src = base ++ ".hs" - lib = dir "lib" - libTs <- mapM getModTime . Prelude.filter isSource =<< allFiles lib - srcT <- getModTime src - binT <- getModTime bin - if force || any (binT <) (srcT : libTs) - then do - -- temporarily disable SIGCHLD ignoring: - uninstallSignalHandlers - status <- bracket (openFile err WriteMode) hClose $ \h -> - waitForProcess =<< runProcess "ghc" ["--make", "xmonad.hs", "-i", "-ilib", "-fforce-recomp", "-main-is", "main", "-v0", "-o",binn] (Just dir) - Nothing Nothing Nothing (Just h) - - -- re-enable SIGCHLD: - installSignalHandlers - - -- now, if it fails, run xmessage to let the user know: - when (status /= ExitSuccess) $ do - ghcErr <- readFile err - let msg = unlines $ - ["Error detected while loading xmonad configuration file: " ++ src] - ++ lines (if null ghcErr then show status else ghcErr) - ++ ["","Please check the file for errors."] - -- nb, the ordering of printing, then forking, is crucial due to - -- lazy evaluation - hPutStrLn stderr msg - forkProcess $ executeFile "xmessage" True ["-default", "okay", msg] Nothing - return () - return (status == ExitSuccess) - else return True - where getModTime f = E.catch (Just <$> getModificationTime f) (\(SomeException _) -> return Nothing) - isSource = flip elem [".hs",".lhs",".hsc"] . takeExtension - allFiles t = do - let prep = map (t) . Prelude.filter (`notElem` [".",".."]) - cs <- prep <$> E.catch (getDirectoryContents t) (\(SomeException _) -> return []) - ds <- filterM doesDirectoryExist cs - concat . ((cs \\ ds):) <$> mapM allFiles ds - --- | Conditionally run an action, using a @Maybe a@ to decide. -whenJust :: Monad m => Maybe a -> (a -> m ()) -> m () -whenJust mg f = maybe (return ()) f mg - --- | Conditionally run an action, using a 'X' event to decide -whenX :: X Bool -> X () -> X () -whenX a f = a >>= \b -> when b f - --- | A 'trace' for the 'X' monad. Logs a string to stderr. The result may --- be found in your .xsession-errors file -trace :: MonadIO m => String -> m () -trace = io . hPutStrLn stderr - --- | Ignore SIGPIPE to avoid termination when a pipe is full, and SIGCHLD to --- avoid zombie processes, and clean up any extant zombie processes. -installSignalHandlers :: MonadIO m => m () -installSignalHandlers = io $ do - installHandler openEndedPipe Ignore Nothing - installHandler sigCHLD Ignore Nothing - (try :: IO a -> IO (Either SomeException a)) - $ fix $ \more -> do - x <- getAnyProcessStatus False False - when (isJust x) more - return () - -uninstallSignalHandlers :: MonadIO m => m () -uninstallSignalHandlers = io $ do - installHandler openEndedPipe Default Nothing - installHandler sigCHLD Default Nothing - return () diff --git a/XMonad/Layout.hs b/XMonad/Layout.hs deleted file mode 100644 index 8eff488..0000000 --- a/XMonad/Layout.hs +++ /dev/null @@ -1,210 +0,0 @@ -{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, PatternGuards, TypeSynonymInstances, DeriveDataTypeable #-} - --- -------------------------------------------------------------------------- --- | --- Module : XMonad.Layout --- Copyright : (c) Spencer Janssen 2007 --- License : BSD3-style (see LICENSE) --- --- Maintainer : spencerjanssen@gmail.com --- Stability : unstable --- Portability : not portable, Typeable deriving, mtl, posix --- --- The collection of core layouts. --- ------------------------------------------------------------------------------ - -module XMonad.Layout ( - Full(..), Tall(..), Mirror(..), - Resize(..), IncMasterN(..), Choose, (|||), ChangeLayout(..), - mirrorRect, splitVertically, - splitHorizontally, splitHorizontallyBy, splitVerticallyBy, - - tile - - ) where - -import XMonad.Core - -import Graphics.X11 (Rectangle(..)) -import qualified XMonad.StackSet as W -import Control.Arrow ((***), second) -import Control.Monad -import Data.Maybe (fromMaybe) - ------------------------------------------------------------------------- - --- | Change the size of the master pane. -data Resize = Shrink | Expand deriving Typeable - --- | Increase the number of clients in the master pane. -data IncMasterN = IncMasterN !Int deriving Typeable - -instance Message Resize -instance Message IncMasterN - --- | Simple fullscreen mode. Renders the focused window fullscreen. -data Full a = Full deriving (Show, Read) - -instance LayoutClass Full a - --- | The builtin tiling mode of xmonad. Supports 'Shrink', 'Expand' and --- 'IncMasterN'. -data Tall a = Tall { tallNMaster :: !Int -- ^ The default number of windows in the master pane (default: 1) - , tallRatioIncrement :: !Rational -- ^ Percent of screen to increment by when resizing panes (default: 3/100) - , tallRatio :: !Rational -- ^ Default proportion of screen occupied by master pane (default: 1/2) - } - deriving (Show, Read) - -- TODO should be capped [0..1] .. - --- a nice pure layout, lots of properties for the layout, and its messages, in Properties.hs -instance LayoutClass Tall a where - pureLayout (Tall nmaster _ frac) r s = zip ws rs - where ws = W.integrate s - rs = tile frac r nmaster (length ws) - - pureMessage (Tall nmaster delta frac) m = - msum [fmap resize (fromMessage m) - ,fmap incmastern (fromMessage m)] - - where resize Shrink = Tall nmaster delta (max 0 $ frac-delta) - resize Expand = Tall nmaster delta (min 1 $ frac+delta) - incmastern (IncMasterN d) = Tall (max 0 (nmaster+d)) delta frac - - description _ = "Tall" - --- | Compute the positions for windows using the default two-pane tiling --- algorithm. --- --- The screen is divided into two panes. All clients are --- then partioned between these two panes. One pane, the master, by --- convention has the least number of windows in it. -tile - :: Rational -- ^ @frac@, what proportion of the screen to devote to the master area - -> Rectangle -- ^ @r@, the rectangle representing the screen - -> Int -- ^ @nmaster@, the number of windows in the master pane - -> Int -- ^ @n@, the total number of windows to tile - -> [Rectangle] -tile f r nmaster n = if n <= nmaster || nmaster == 0 - then splitVertically n r - else splitVertically nmaster r1 ++ splitVertically (n-nmaster) r2 -- two columns - where (r1,r2) = splitHorizontallyBy f r - --- --- Divide the screen vertically into n subrectangles --- -splitVertically, splitHorizontally :: Int -> Rectangle -> [Rectangle] -splitVertically n r | n < 2 = [r] -splitVertically n (Rectangle sx sy sw sh) = Rectangle sx sy sw smallh : - splitVertically (n-1) (Rectangle sx (sy+fromIntegral smallh) sw (sh-smallh)) - where smallh = sh `div` fromIntegral n --hmm, this is a fold or map. - --- Not used in the core, but exported -splitHorizontally n = map mirrorRect . splitVertically n . mirrorRect - --- Divide the screen into two rectangles, using a rational to specify the ratio -splitHorizontallyBy, splitVerticallyBy :: RealFrac r => r -> Rectangle -> (Rectangle, Rectangle) -splitHorizontallyBy f (Rectangle sx sy sw sh) = - ( Rectangle sx sy leftw sh - , Rectangle (sx + fromIntegral leftw) sy (sw-fromIntegral leftw) sh) - where leftw = floor $ fromIntegral sw * f - --- Not used in the core, but exported -splitVerticallyBy f = (mirrorRect *** mirrorRect) . splitHorizontallyBy f . mirrorRect - ------------------------------------------------------------------------- - --- | Mirror a layout, compute its 90 degree rotated form. -newtype Mirror l a = Mirror (l a) deriving (Show, Read) - -instance LayoutClass l a => LayoutClass (Mirror l) a where - runLayout (W.Workspace i (Mirror l) ms) r = (map (second mirrorRect) *** fmap Mirror) - `fmap` runLayout (W.Workspace i l ms) (mirrorRect r) - handleMessage (Mirror l) = fmap (fmap Mirror) . handleMessage l - description (Mirror l) = "Mirror "++ description l - --- | Mirror a rectangle. -mirrorRect :: Rectangle -> Rectangle -mirrorRect (Rectangle rx ry rw rh) = Rectangle ry rx rh rw - ------------------------------------------------------------------------- --- LayoutClass selection manager --- Layouts that transition between other layouts - --- | Messages to change the current layout. -data ChangeLayout = FirstLayout | NextLayout deriving (Eq, Show, Typeable) - -instance Message ChangeLayout - --- | The layout choice combinator -(|||) :: (LayoutClass l a, LayoutClass r a) => l a -> r a -> Choose l r a -(|||) = Choose L -infixr 5 ||| - --- | A layout that allows users to switch between various layout options. -data Choose l r a = Choose LR (l a) (r a) deriving (Read, Show) - --- | Are we on the left or right sub-layout? -data LR = L | R deriving (Read, Show, Eq) - -data NextNoWrap = NextNoWrap deriving (Eq, Show, Typeable) -instance Message NextNoWrap - --- | A small wrapper around handleMessage, as it is tedious to write --- SomeMessage repeatedly. -handle :: (LayoutClass l a, Message m) => l a -> m -> X (Maybe (l a)) -handle l m = handleMessage l (SomeMessage m) - --- | A smart constructor that takes some potential modifications, returns a --- new structure if any fields have changed, and performs any necessary cleanup --- on newly non-visible layouts. -choose :: (LayoutClass l a, LayoutClass r a) - => Choose l r a-> LR -> Maybe (l a) -> Maybe (r a) -> X (Maybe (Choose l r a)) -choose (Choose d _ _) d' Nothing Nothing | d == d' = return Nothing -choose (Choose d l r) d' ml mr = f lr - where - (l', r') = (fromMaybe l ml, fromMaybe r mr) - lr = case (d, d') of - (L, R) -> (hide l' , return r') - (R, L) -> (return l', hide r' ) - (_, _) -> (return l', return r') - f (x,y) = fmap Just $ liftM2 (Choose d') x y - hide x = fmap (fromMaybe x) $ handle x Hide - -instance (LayoutClass l a, LayoutClass r a) => LayoutClass (Choose l r) a where - runLayout (W.Workspace i (Choose L l r) ms) = - fmap (second . fmap $ flip (Choose L) r) . runLayout (W.Workspace i l ms) - runLayout (W.Workspace i (Choose R l r) ms) = - fmap (second . fmap $ Choose R l) . runLayout (W.Workspace i r ms) - - description (Choose L l _) = description l - description (Choose R _ r) = description r - - handleMessage lr m | Just NextLayout <- fromMessage m = do - mlr' <- handle lr NextNoWrap - maybe (handle lr FirstLayout) (return . Just) mlr' - - handleMessage c@(Choose d l r) m | Just NextNoWrap <- fromMessage m = - case d of - L -> do - ml <- handle l NextNoWrap - case ml of - Just _ -> choose c L ml Nothing - Nothing -> choose c R Nothing =<< handle r FirstLayout - - R -> choose c R Nothing =<< handle r NextNoWrap - - handleMessage c@(Choose _ l _) m | Just FirstLayout <- fromMessage m = - flip (choose c L) Nothing =<< handle l FirstLayout - - handleMessage c@(Choose d l r) m | Just ReleaseResources <- fromMessage m = - join $ liftM2 (choose c d) (handle l ReleaseResources) (handle r ReleaseResources) - - handleMessage c@(Choose d l r) m = do - ml' <- case d of - L -> handleMessage l m - R -> return Nothing - mr' <- case d of - L -> return Nothing - R -> handleMessage r m - choose c d ml' mr' diff --git a/XMonad/Main.hsc b/XMonad/Main.hsc deleted file mode 100644 index 5d59042..0000000 --- a/XMonad/Main.hsc +++ /dev/null @@ -1,410 +0,0 @@ -{-# LANGUAGE MultiParamTypeClasses, FlexibleContexts, ForeignFunctionInterface #-} ----------------------------------------------------------------------------- --- | --- Module : XMonad.Main --- Copyright : (c) Spencer Janssen 2007 --- License : BSD3-style (see LICENSE) --- --- Maintainer : spencerjanssen@gmail.com --- Stability : unstable --- Portability : not portable, uses mtl, X11, posix --- --- xmonad, a minimalist, tiling window manager for X11 --- ------------------------------------------------------------------------------ - -module XMonad.Main (xmonad) where - -import Control.Arrow (second) -import Data.Bits -import Data.List ((\\)) -import Data.Function -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 Data.Monoid (getAll) - -import Foreign.C -import Foreign.Ptr - -import System.Environment (getArgs) - -import Graphics.X11.Xlib hiding (refreshKeyboardMapping) -import Graphics.X11.Xlib.Extras - -import XMonad.Core -import qualified XMonad.Config as Default -import XMonad.StackSet (new, floating, member) -import qualified XMonad.StackSet as W -import XMonad.Operations - -import System.IO - ------------------------------------------------------------------------- --- Locale support - -#include - -foreign import ccall unsafe "locale.h setlocale" - c_setlocale :: CInt -> Ptr CChar -> IO (Ptr CChar) - ------------------------------------------------------------------------- - --- | --- The main entry point --- -xmonad :: (LayoutClass l Window, Read (l Window)) => XConfig l -> IO () -xmonad initxmc = do - -- setup locale information from environment - withCString "" $ c_setlocale (#const LC_ALL) - -- ignore SIGPIPE and SIGCHLD - installSignalHandlers - -- First, wrap the layout in an existential, to keep things pretty: - let xmc = initxmc { layoutHook = Layout $ layoutHook initxmc } - dpy <- openDisplay "" - let dflt = defaultScreen dpy - - rootw <- rootWindow dpy dflt - - args <- getArgs - - when ("--replace" `elem` args) $ replace dpy dflt rootw - - -- If another WM is running, a BadAccess error will be returned. The - -- default error handler will write the exception to stderr and exit with - -- an error. - selectInput dpy rootw $ rootMask initxmc - - sync dpy False -- sync to ensure all outstanding errors are delivered - - -- turn off the default handler in favor of one that ignores all errors - -- (ugly, I know) - xSetErrorHandler -- in C, I'm too lazy to write the binding: dons - - xinesc <- getCleanedScreenInfo dpy - nbc <- do v <- initColor dpy $ normalBorderColor xmc - ~(Just nbc_) <- initColor dpy $ normalBorderColor Default.def - return (fromMaybe nbc_ v) - - fbc <- do v <- initColor dpy $ focusedBorderColor xmc - ~(Just fbc_) <- initColor dpy $ focusedBorderColor Default.def - return (fromMaybe fbc_ v) - - hSetBuffering stdout NoBuffering - - let layout = layoutHook xmc - lreads = readsLayout layout - initialWinset = new layout (workspaces xmc) $ map SD xinesc - 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 - extState = fromMaybe M.empty $ do - ("--resume" : _ : dyns : _) <- return args - vals <- maybeRead reads dyns - return . M.fromList . map (second Left) $ vals - - cf = XConf - { display = dpy - , config = xmc - , theRoot = rootw - , normalBorder = nbc - , focusedBorder = fbc - , keyActions = keys xmc xmc - , buttonActions = mouseBindings xmc xmc - , mouseFocused = False - , mousePosition = Nothing - , currentEvent = Nothing } - - st = XState - { windowset = initialWinset - , numberlockMask = 0 - , mapped = S.empty - , waitingUnmap = M.empty - , dragging = Nothing - , extensibleState = extState - } - allocaXEvent $ \e -> - runX cf st $ do - - setNumlockMask - grabKeys - grabButtons - - io $ sync dpy False - - ws <- io $ scan dpy rootw - - -- bootstrap the windowset, Operations.windows will identify all - -- the windows in winset as new and set initial properties for - -- those windows. Remove all windows that are no longer top-level - -- children of the root, they may have disappeared since - -- restarting. - windows . const . foldr W.delete winset $ W.allWindows winset \\ ws - - -- manage the as-yet-unmanaged windows - mapM_ manage (ws \\ W.allWindows winset) - - userCode $ startupHook initxmc - - -- main loop, for all you HOF/recursion fans out there. - forever $ prehandle =<< io (nextEvent dpy e >> getEvent e) - - return () - where - -- if the event gives us the position of the pointer, set mousePosition - prehandle e = let mouse = do guard (ev_event_type e `elem` evs) - return (fromIntegral (ev_x_root e) - ,fromIntegral (ev_y_root e)) - in local (\c -> c { mousePosition = mouse, currentEvent = Just e }) (handleWithHook e) - evs = [ keyPress, keyRelease, enterNotify, leaveNotify - , buttonPress, buttonRelease] - - --- | Runs handleEventHook from the configuration and runs the default handler --- function if it returned True. -handleWithHook :: Event -> X () -handleWithHook e = do - evHook <- asks (handleEventHook . config) - whenX (userCodeDef True $ getAll `fmap` evHook e) (handle e) - --- --------------------------------------------------------------------- --- | 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 - userCodeDef () $ 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) $ do - unmanage w - modify (\s -> s { mapped = S.delete w (mapped s) - , waitingUnmap = M.delete w (waitingUnmap s)}) - --- 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.update mpred w (waitingUnmap s) }) - where mpred 1 = Nothing - mpred n = Just $ pred n - --- set keyboard mapping -handle e@(MappingNotifyEvent {}) = do - io $ refreshKeyboardMapping e - when (ev_request e `elem` [mappingKeyboard, mappingModifier]) $ do - setNumlockMask - 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. - dpy <- asks display - isr <- isRoot w - m <- cleanMask $ ev_state e - mact <- asks (M.lookup (m, b) . buttonActions) - case mact of - Just act | isr -> act $ ev_subwindow e - _ -> do - focus w - ctf <- asks (clickJustFocuses . config) - unless ctf $ io (allowEvents dpy replayPointer currentTime) - broadcastMessage e -- Always send button events. - --- entered a normal window: focus it if focusFollowsMouse is set to --- True in the user's config. -handle e@(CrossingEvent {ev_window = w, ev_event_type = t}) - | t == enterNotify && ev_mode e == notifyNormal - = whenX (asks $ focusFollowsMouse . config) (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 event@(PropertyEvent { ev_event_type = t, ev_atom = a }) - | t == propertyNotify && a == wM_NAME = asks (logHook . config) >>= userCodeDef () >> - broadcastMessage event - -handle e@ClientMessageEvent { ev_message_type = mt } = do - a <- getAtom "XMONAD_RESTART" - if (mt == a) - then restart "xmonad" True - else broadcastMessage e - -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) - -setNumlockMask :: X () -setNumlockMask = do - dpy <- asks display - ms <- io $ getModifierMapping dpy - xs <- sequence [ do - ks <- io $ keycodeToKeysym dpy kc 0 - if ks == xK_Num_Lock - then return (setBit 0 (fromIntegral m)) - else return (0 :: KeyMask) - | (m, kcs) <- ms, kc <- kcs, kc /= 0] - modify (\s -> s { numberlockMask = foldr (.|.) 0 xs }) - --- | 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 - (minCode, maxCode) = displayKeycodes dpy - allCodes = [fromIntegral minCode .. fromIntegral maxCode] - io $ ungrabKey dpy anyKey anyModifier rootw - ks <- asks keyActions - -- build a map from keysyms to lists of keysyms (doing what - -- XGetKeyboardMapping would do if the X11 package bound it) - syms <- forM allCodes $ \code -> io (keycodeToKeysym dpy code 0) - let keysymMap = M.fromListWith (++) (zip syms [[code] | code <- allCodes]) - keysymToKeycodes sym = M.findWithDefault [] sym keysymMap - forM_ (M.keys ks) $ \(mask,sym) -> - forM_ (keysymToKeycodes sym) $ \kc -> - 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) - --- | @replace@ to signals compliant window managers to exit. -replace :: Display -> ScreenNumber -> Window -> IO () -replace dpy dflt rootw = do - -- check for other WM - wmSnAtom <- internAtom dpy ("WM_S" ++ show dflt) False - currentWmSnOwner <- xGetSelectionOwner dpy wmSnAtom - when (currentWmSnOwner /= 0) $ do - -- prepare to receive destroyNotify for old WM - selectInput dpy currentWmSnOwner structureNotifyMask - - -- create off-screen window - netWmSnOwner <- allocaSetWindowAttributes $ \attributes -> do - set_override_redirect attributes True - set_event_mask attributes propertyChangeMask - let screen = defaultScreenOfDisplay dpy - visual = defaultVisualOfScreen screen - attrmask = cWOverrideRedirect .|. cWEventMask - createWindow dpy rootw (-100) (-100) 1 1 0 copyFromParent copyFromParent visual attrmask attributes - - -- try to acquire wmSnAtom, this should signal the old WM to terminate - xSetSelectionOwner dpy wmSnAtom netWmSnOwner currentTime - - -- SKIPPED: check if we acquired the selection - -- SKIPPED: send client message indicating that we are now the WM - - -- wait for old WM to go away - fix $ \again -> do - evt <- allocaXEvent $ \event -> do - windowEvent dpy currentWmSnOwner structureNotifyMask event - get_EventType event - - when (evt /= destroyNotify) again diff --git a/XMonad/ManageHook.hs b/XMonad/ManageHook.hs deleted file mode 100644 index aa5ae32..0000000 --- a/XMonad/ManageHook.hs +++ /dev/null @@ -1,119 +0,0 @@ -{-# LANGUAGE GeneralizedNewtypeDeriving #-} - ------------------------------------------------------------------------------ --- | --- Module : XMonad.ManageHook --- Copyright : (c) Spencer Janssen 2007 --- License : BSD3-style (see LICENSE) --- --- Maintainer : spencerjanssen@gmail.com --- Stability : unstable --- Portability : not portable, uses cunning newtype deriving --- --- An EDSL for ManageHooks --- ------------------------------------------------------------------------------ - --- XXX examples required - -module XMonad.ManageHook where - -import XMonad.Core -import Graphics.X11.Xlib.Extras -import Graphics.X11.Xlib (Display, Window, internAtom, wM_NAME) -import Control.Exception.Extensible (bracket, SomeException(..)) -import qualified Control.Exception.Extensible as E -import Control.Monad.Reader -import Data.Maybe -import Data.Monoid -import qualified XMonad.StackSet as W -import XMonad.Operations (floatLocation, reveal) - --- | Lift an 'X' action to a 'Query'. -liftX :: X a -> Query a -liftX = Query . lift - --- | The identity hook that returns the WindowSet unchanged. -idHook :: Monoid m => m -idHook = mempty - --- | Infix 'mappend'. Compose two 'ManageHook' from right to left. -(<+>) :: Monoid m => m -> m -> m -(<+>) = mappend - --- | Compose the list of 'ManageHook's. -composeAll :: Monoid m => [m] -> m -composeAll = mconcat - -infix 0 --> - --- | @p --> x@. If @p@ returns 'True', execute the 'ManageHook'. --- --- > (-->) :: Monoid m => Query Bool -> Query m -> Query m -- a simpler type -(-->) :: (Monad m, Monoid a) => m Bool -> m a -> m a -p --> f = p >>= \b -> if b then f else return mempty - --- | @q =? x@. if the result of @q@ equals @x@, return 'True'. -(=?) :: Eq a => Query a -> a -> Query Bool -q =? x = fmap (== x) q - -infixr 3 <&&>, <||> - --- | '&&' lifted to a 'Monad'. -(<&&>) :: Monad m => m Bool -> m Bool -> m Bool -(<&&>) = liftM2 (&&) - --- | '||' lifted to a 'Monad'. -(<||>) :: Monad m => m Bool -> m Bool -> m Bool -(<||>) = liftM2 (||) - --- | Return the window title. -title :: Query String -title = ask >>= \w -> liftX $ do - d <- asks display - let - getProp = - (internAtom d "_NET_WM_NAME" False >>= getTextProperty d w) - `E.catch` \(SomeException _) -> getTextProperty d w wM_NAME - extract prop = do l <- wcTextPropertyToTextList d prop - return $ if null l then "" else head l - io $ bracket getProp (xFree . tp_value) extract `E.catch` \(SomeException _) -> return "" - --- | Return the application name. -appName :: Query String -appName = ask >>= (\w -> liftX $ withDisplay $ \d -> fmap resName $ io $ getClassHint d w) - --- | Backwards compatible alias for 'appName'. -resource :: Query String -resource = appName - --- | Return the resource class. -className :: Query String -className = ask >>= (\w -> liftX $ withDisplay $ \d -> fmap resClass $ io $ getClassHint d w) - --- | A query that can return an arbitrary X property of type 'String', --- identified by name. -stringProperty :: String -> Query String -stringProperty p = ask >>= (\w -> liftX $ withDisplay $ \d -> fmap (fromMaybe "") $ getStringProperty d w p) - -getStringProperty :: Display -> Window -> String -> X (Maybe String) -getStringProperty d w p = do - a <- getAtom p - md <- io $ getWindowProperty8 d a w - return $ fmap (map (toEnum . fromIntegral)) md - --- | Modify the 'WindowSet' with a pure function. -doF :: (s -> s) -> Query (Endo s) -doF = return . Endo - --- | Move the window to the floating layer. -doFloat :: ManageHook -doFloat = ask >>= \w -> doF . W.float w . snd =<< liftX (floatLocation w) - --- | Map the window and remove it from the 'WindowSet'. -doIgnore :: ManageHook -doIgnore = ask >>= \w -> liftX (reveal w) >> doF (W.delete w) - --- | Move the window to a given workspace -doShift :: WorkspaceId -> ManageHook -doShift i = doF . W.shiftWin i =<< ask diff --git a/XMonad/Operations.hs b/XMonad/Operations.hs deleted file mode 100644 index 41fbed0..0000000 --- a/XMonad/Operations.hs +++ /dev/null @@ -1,586 +0,0 @@ -{-# OPTIONS_GHC -fno-warn-orphans #-} -{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, PatternGuards, TypeSynonymInstances #-} - --- -------------------------------------------------------------------------- --- | --- Module : XMonad.Operations --- Copyright : (c) Spencer Janssen 2007 --- License : BSD3-style (see LICENSE) --- --- Maintainer : dons@cse.unsw.edu.au --- Stability : unstable --- Portability : not portable, Typeable deriving, mtl, posix --- --- Operations. --- ------------------------------------------------------------------------------ - -module XMonad.Operations where - -import XMonad.Core -import XMonad.Layout (Full(..)) -import qualified XMonad.StackSet as W - -import Data.Maybe -import Data.Monoid (Endo(..)) -import Data.List (nub, (\\), find) -import Data.Bits ((.|.), (.&.), complement, testBit) -import Data.Ratio -import qualified Data.Map as M -import qualified Data.Set as S - -import Control.Applicative -import Control.Monad.Reader -import Control.Monad.State -import qualified Control.Exception.Extensible as C - -import System.Posix.Process (executeFile) -import Graphics.X11.Xlib -import Graphics.X11.Xinerama (getScreenInfo) -import Graphics.X11.Xlib.Extras - --- --------------------------------------------------------------------- --- | --- Window manager operations --- manage. Add a new window to be managed in the current workspace. --- Bring it into focus. --- --- Whether the window is already managed, or not, it is mapped, has its --- border set, and its event mask set. --- -manage :: Window -> X () -manage w = whenX (not <$> isClient w) $ withDisplay $ \d -> do - sh <- io $ getWMNormalHints d w - - let isFixedSize = sh_min_size sh /= Nothing && sh_min_size sh == sh_max_size sh - isTransient <- isJust <$> io (getTransientForHint d w) - - rr <- snd `fmap` floatLocation w - -- ensure that float windows don't go over the edge of the screen - let adjust (W.RationalRect x y wid h) | x + wid > 1 || y + h > 1 || x < 0 || y < 0 - = W.RationalRect (0.5 - wid/2) (0.5 - h/2) wid h - adjust r = r - - f ws | isFixedSize || isTransient = W.float w (adjust rr) . W.insertUp w . W.view i $ ws - | otherwise = W.insertUp w ws - where i = W.tag $ W.workspace $ W.current ws - - mh <- asks (manageHook . config) - g <- appEndo <$> userCodeDef (Endo id) (runQuery mh w) - windows (g . f) - --- | unmanage. A window no longer exists, remove it from the window --- list, on whatever workspace it is. --- -unmanage :: Window -> X () -unmanage = windows . W.delete - --- | Kill the specified window. If we do kill it, we'll get a --- delete notify back from X. --- --- There are two ways to delete a window. Either just kill it, or if it --- supports the delete protocol, send a delete event (e.g. firefox) --- -killWindow :: Window -> X () -killWindow w = withDisplay $ \d -> do - wmdelt <- atom_WM_DELETE_WINDOW ; wmprot <- atom_WM_PROTOCOLS - - protocols <- io $ getWMProtocols d w - io $ if wmdelt `elem` protocols - then allocaXEvent $ \ev -> do - setEventType ev clientMessage - setClientMessageEvent ev w wmprot 32 wmdelt 0 - sendEvent d w False noEventMask ev - else killClient d w >> return () - --- | Kill the currently focused client. -kill :: X () -kill = withFocused killWindow - --- --------------------------------------------------------------------- --- Managing windows - --- | windows. Modify the current window list with a pure function, and refresh -windows :: (WindowSet -> WindowSet) -> X () -windows f = do - XState { windowset = old } <- get - let oldvisible = concatMap (W.integrate' . W.stack . W.workspace) $ W.current old : W.visible old - newwindows = W.allWindows ws \\ W.allWindows old - ws = f old - XConf { display = d , normalBorder = nbc, focusedBorder = fbc } <- ask - - mapM_ setInitialProperties newwindows - - whenJust (W.peek old) $ \otherw -> io $ setWindowBorder d otherw nbc - modify (\s -> s { windowset = ws }) - - -- notify non visibility - let tags_oldvisible = map (W.tag . W.workspace) $ W.current old : W.visible old - gottenhidden = filter (flip elem tags_oldvisible . W.tag) $ W.hidden ws - mapM_ (sendMessageWithNoRefresh Hide) gottenhidden - - -- for each workspace, layout the currently visible workspaces - let allscreens = W.screens ws - summed_visible = scanl (++) [] $ map (W.integrate' . W.stack . W.workspace) allscreens - rects <- fmap concat $ forM (zip allscreens summed_visible) $ \ (w, vis) -> do - let wsp = W.workspace w - this = W.view n ws - n = W.tag wsp - tiled = (W.stack . W.workspace . W.current $ this) - >>= W.filter (`M.notMember` W.floating ws) - >>= W.filter (`notElem` vis) - viewrect = screenRect $ W.screenDetail w - - -- just the tiled windows: - -- now tile the windows on this workspace, modified by the gap - (rs, ml') <- runLayout wsp { W.stack = tiled } viewrect `catchX` - runLayout wsp { W.stack = tiled, W.layout = Layout Full } viewrect - updateLayout n ml' - - let m = W.floating ws - flt = [(fw, scaleRationalRect viewrect r) - | fw <- filter (flip M.member m) (W.index this) - , Just r <- [M.lookup fw m]] - vs = flt ++ rs - - io $ restackWindows d (map fst vs) - -- return the visible windows for this workspace: - return vs - - let visible = map fst rects - - mapM_ (uncurry tileWindow) rects - - whenJust (W.peek ws) $ \w -> io $ setWindowBorder d w fbc - - mapM_ reveal visible - setTopFocus - - -- hide every window that was potentially visible before, but is not - -- given a position by a layout now. - mapM_ hide (nub (oldvisible ++ newwindows) \\ visible) - - -- all windows that are no longer in the windowset are marked as - -- withdrawn, it is important to do this after the above, otherwise 'hide' - -- will overwrite withdrawnState with iconicState - mapM_ (flip setWMState withdrawnState) (W.allWindows old \\ W.allWindows ws) - - isMouseFocused <- asks mouseFocused - unless isMouseFocused $ clearEvents enterWindowMask - asks (logHook . config) >>= userCodeDef () - --- | Produce the actual rectangle from a screen and a ratio on that screen. -scaleRationalRect :: Rectangle -> W.RationalRect -> Rectangle -scaleRationalRect (Rectangle sx sy sw sh) (W.RationalRect rx ry rw rh) - = Rectangle (sx + scale sw rx) (sy + scale sh ry) (scale sw rw) (scale sh rh) - where scale s r = floor (toRational s * r) - --- | setWMState. set the WM_STATE property -setWMState :: Window -> Int -> X () -setWMState w v = withDisplay $ \dpy -> do - a <- atom_WM_STATE - io $ changeProperty32 dpy w a a propModeReplace [fromIntegral v, fromIntegral none] - --- | hide. Hide a window by unmapping it, and setting Iconified. -hide :: Window -> X () -hide w = whenX (gets (S.member w . mapped)) $ withDisplay $ \d -> do - cMask <- asks $ clientMask . config - io $ do selectInput d w (cMask .&. complement structureNotifyMask) - unmapWindow d w - selectInput d w cMask - setWMState w iconicState - -- this part is key: we increment the waitingUnmap counter to distinguish - -- between client and xmonad initiated unmaps. - modify (\s -> s { waitingUnmap = M.insertWith (+) w 1 (waitingUnmap s) - , mapped = S.delete w (mapped s) }) - --- | reveal. Show a window by mapping it and setting Normal --- this is harmless if the window was already visible -reveal :: Window -> X () -reveal w = withDisplay $ \d -> do - setWMState w normalState - io $ mapWindow d w - whenX (isClient w) $ modify (\s -> s { mapped = S.insert w (mapped s) }) - --- | Set some properties when we initially gain control of a window -setInitialProperties :: Window -> X () -setInitialProperties w = asks normalBorder >>= \nb -> withDisplay $ \d -> do - setWMState w iconicState - asks (clientMask . config) >>= io . selectInput d w - bw <- asks (borderWidth . config) - io $ setWindowBorderWidth d w bw - -- we must initially set the color of new windows, to maintain invariants - -- required by the border setting in 'windows' - io $ setWindowBorder d w nb - --- | refresh. Render the currently visible workspaces, as determined by --- the 'StackSet'. Also, set focus to the focused window. --- --- This is our 'view' operation (MVC), in that it pretty prints our model --- with X calls. --- -refresh :: X () -refresh = windows id - --- | clearEvents. Remove all events of a given type from the event queue. -clearEvents :: EventMask -> X () -clearEvents mask = withDisplay $ \d -> io $ do - sync d False - allocaXEvent $ \p -> fix $ \again -> do - more <- checkMaskEvent d mask p - when more again -- beautiful - --- | tileWindow. Moves and resizes w such that it fits inside the given --- rectangle, including its border. -tileWindow :: Window -> Rectangle -> X () -tileWindow w r = withDisplay $ \d -> do - bw <- (fromIntegral . wa_border_width) <$> io (getWindowAttributes d w) - -- give all windows at least 1x1 pixels - let least x | x <= bw*2 = 1 - | otherwise = x - bw*2 - io $ moveResizeWindow d w (rect_x r) (rect_y r) - (least $ rect_width r) (least $ rect_height r) - --- --------------------------------------------------------------------- - --- | Returns 'True' if the first rectangle is contained within, but not equal --- to the second. -containedIn :: Rectangle -> Rectangle -> Bool -containedIn r1@(Rectangle x1 y1 w1 h1) r2@(Rectangle x2 y2 w2 h2) - = and [ r1 /= r2 - , x1 >= x2 - , y1 >= y2 - , fromIntegral x1 + w1 <= fromIntegral x2 + w2 - , fromIntegral y1 + h1 <= fromIntegral y2 + h2 ] - --- | Given a list of screens, remove all duplicated screens and screens that --- are entirely contained within another. -nubScreens :: [Rectangle] -> [Rectangle] -nubScreens xs = nub . filter (\x -> not $ any (x `containedIn`) xs) $ xs - --- | Cleans the list of screens according to the rules documented for --- nubScreens. -getCleanedScreenInfo :: MonadIO m => Display -> m [Rectangle] -getCleanedScreenInfo = io . fmap nubScreens . getScreenInfo - --- | rescreen. The screen configuration may have changed (due to --- xrandr), update the state and refresh the screen, and reset the gap. -rescreen :: X () -rescreen = do - xinesc <- withDisplay getCleanedScreenInfo - - windows $ \ws@(W.StackSet { W.current = v, W.visible = vs, W.hidden = hs }) -> - let (xs, ys) = splitAt (length xinesc) $ map W.workspace (v:vs) ++ hs - (a:as) = zipWith3 W.Screen xs [0..] $ map SD xinesc - in ws { W.current = a - , W.visible = as - , W.hidden = ys } - --- --------------------------------------------------------------------- - --- | setButtonGrab. Tell whether or not to intercept clicks on a given window -setButtonGrab :: Bool -> Window -> X () -setButtonGrab grab w = do - pointerMode <- asks $ \c -> if clickJustFocuses (config c) - then grabModeAsync - else grabModeSync - withDisplay $ \d -> io $ if grab - then forM_ [button1, button2, button3] $ \b -> - grabButton d b anyModifier w False buttonPressMask - pointerMode grabModeSync none none - else ungrabButton d anyButton anyModifier w - --- --------------------------------------------------------------------- --- Setting keyboard focus - --- | Set the focus to the window on top of the stack, or root -setTopFocus :: X () -setTopFocus = withWindowSet $ maybe (setFocusX =<< asks theRoot) setFocusX . W.peek - --- | Set focus explicitly to window 'w' if it is managed by us, or root. --- This happens if X notices we've moved the mouse (and perhaps moved --- the mouse to a new screen). -focus :: Window -> X () -focus w = local (\c -> c { mouseFocused = True }) $ withWindowSet $ \s -> do - let stag = W.tag . W.workspace - curr = stag $ W.current s - mnew <- maybe (return Nothing) (fmap (fmap stag) . uncurry pointScreen) - =<< asks mousePosition - root <- asks theRoot - case () of - _ | W.member w s && W.peek s /= Just w -> windows (W.focusWindow w) - | Just new <- mnew, w == root && curr /= new - -> windows (W.view new) - | otherwise -> return () - --- | Call X to set the keyboard focus details. -setFocusX :: Window -> X () -setFocusX w = withWindowSet $ \ws -> do - dpy <- asks display - - -- clear mouse button grab and border on other windows - forM_ (W.current ws : W.visible ws) $ \wk -> - forM_ (W.index (W.view (W.tag (W.workspace wk)) ws)) $ \otherw -> - setButtonGrab True otherw - - -- If we ungrab buttons on the root window, we lose our mouse bindings. - whenX (not <$> isRoot w) $ setButtonGrab False w - - hints <- io $ getWMHints dpy w - protocols <- io $ getWMProtocols dpy w - wmprot <- atom_WM_PROTOCOLS - wmtf <- atom_WM_TAKE_FOCUS - currevt <- asks currentEvent - let inputHintSet = wmh_flags hints `testBit` inputHintBit - - when ((inputHintSet && wmh_input hints) || (not inputHintSet)) $ - io $ do setInputFocus dpy w revertToPointerRoot 0 - when (wmtf `elem` protocols) $ - io $ allocaXEvent $ \ev -> do - setEventType ev clientMessage - setClientMessageEvent ev w wmprot 32 wmtf $ maybe currentTime event_time currevt - sendEvent dpy w False noEventMask ev - where event_time ev = - if (ev_event_type ev) `elem` timedEvents then - ev_time ev - else - currentTime - timedEvents = [ keyPress, keyRelease, buttonPress, buttonRelease, enterNotify, leaveNotify, selectionRequest ] - ------------------------------------------------------------------------- --- Message handling - --- | Throw a message to the current 'LayoutClass' possibly modifying how we --- layout the windows, then refresh. -sendMessage :: Message a => a -> X () -sendMessage a = do - w <- W.workspace . W.current <$> gets windowset - ml' <- handleMessage (W.layout w) (SomeMessage a) `catchX` return Nothing - whenJust ml' $ \l' -> - windows $ \ws -> ws { W.current = (W.current ws) - { W.workspace = (W.workspace $ W.current ws) - { W.layout = l' }}} - --- | Send a message to all layouts, without refreshing. -broadcastMessage :: Message a => a -> X () -broadcastMessage a = withWindowSet $ \ws -> do - let c = W.workspace . W.current $ ws - v = map W.workspace . W.visible $ ws - h = W.hidden ws - mapM_ (sendMessageWithNoRefresh a) (c : v ++ h) - --- | Send a message to a layout, without refreshing. -sendMessageWithNoRefresh :: Message a => a -> W.Workspace WorkspaceId (Layout Window) Window -> X () -sendMessageWithNoRefresh a w = - handleMessage (W.layout w) (SomeMessage a) `catchX` return Nothing >>= - updateLayout (W.tag w) - --- | Update the layout field of a workspace -updateLayout :: WorkspaceId -> Maybe (Layout Window) -> X () -updateLayout i ml = whenJust ml $ \l -> - runOnWorkspaces $ \ww -> return $ if W.tag ww == i then ww { W.layout = l} else ww - --- | Set the layout of the currently viewed workspace -setLayout :: Layout Window -> X () -setLayout l = do - ss@(W.StackSet { W.current = c@(W.Screen { W.workspace = ws })}) <- gets windowset - handleMessage (W.layout ws) (SomeMessage ReleaseResources) - windows $ const $ ss {W.current = c { W.workspace = ws { W.layout = l } } } - ------------------------------------------------------------------------- --- Utilities - --- | Return workspace visible on screen 'sc', or 'Nothing'. -screenWorkspace :: ScreenId -> X (Maybe WorkspaceId) -screenWorkspace sc = withWindowSet $ return . W.lookupWorkspace sc - --- | Apply an 'X' operation to the currently focused window, if there is one. -withFocused :: (Window -> X ()) -> X () -withFocused f = withWindowSet $ \w -> whenJust (W.peek w) f - --- | 'True' if window is under management by us -isClient :: Window -> X Bool -isClient w = withWindowSet $ return . W.member w - --- | Combinations of extra modifier masks we need to grab keys\/buttons for. --- (numlock and capslock) -extraModifiers :: X [KeyMask] -extraModifiers = do - nlm <- gets numberlockMask - return [0, nlm, lockMask, nlm .|. lockMask ] - --- | Strip numlock\/capslock from a mask -cleanMask :: KeyMask -> X KeyMask -cleanMask km = do - nlm <- gets numberlockMask - return (complement (nlm .|. lockMask) .&. km) - --- | Get the 'Pixel' value for a named color -initColor :: Display -> String -> IO (Maybe Pixel) -initColor dpy c = C.handle (\(C.SomeException _) -> return Nothing) $ - (Just . color_pixel . fst) <$> allocNamedColor dpy colormap c - where colormap = defaultColormap dpy (defaultScreen dpy) - ------------------------------------------------------------------------- - --- | @restart name resume@. Attempt to restart xmonad by executing the program --- @name@. If @resume@ is 'True', restart with the current window state. --- When executing another window manager, @resume@ should be 'False'. -restart :: String -> Bool -> X () -restart prog resume = do - broadcastMessage ReleaseResources - io . flush =<< asks display - let wsData = show . W.mapLayout show . windowset - maybeShow (t, Right (PersistentExtension ext)) = Just (t, show ext) - maybeShow (t, Left str) = Just (t, str) - maybeShow _ = Nothing - extState = return . show . catMaybes . map maybeShow . M.toList . extensibleState - args <- if resume then gets (\s -> "--resume":wsData s:extState s) else return [] - catchIO (executeFile prog True args Nothing) - ------------------------------------------------------------------------- --- | Floating layer support - --- | Given a window, find the screen it is located on, and compute --- the geometry of that window wrt. that screen. -floatLocation :: Window -> X (ScreenId, W.RationalRect) -floatLocation w = withDisplay $ \d -> do - ws <- gets windowset - wa <- io $ getWindowAttributes d w - let bw = (fromIntegral . wa_border_width) wa - sc <- fromMaybe (W.current ws) <$> pointScreen (fi $ wa_x wa) (fi $ wa_y wa) - - let sr = screenRect . W.screenDetail $ sc - rr = W.RationalRect ((fi (wa_x wa) - fi (rect_x sr)) % fi (rect_width sr)) - ((fi (wa_y wa) - fi (rect_y sr)) % fi (rect_height sr)) - (fi (wa_width wa + bw*2) % fi (rect_width sr)) - (fi (wa_height wa + bw*2) % fi (rect_height sr)) - - return (W.screen sc, rr) - where fi x = fromIntegral x - --- | Given a point, determine the screen (if any) that contains it. -pointScreen :: Position -> Position - -> X (Maybe (W.Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail)) -pointScreen x y = withWindowSet $ return . find p . W.screens - where p = pointWithin x y . screenRect . W.screenDetail - --- | @pointWithin x y r@ returns 'True' if the @(x, y)@ co-ordinate is within --- @r@. -pointWithin :: Position -> Position -> Rectangle -> Bool -pointWithin x y r = x >= rect_x r && - x < rect_x r + fromIntegral (rect_width r) && - y >= rect_y r && - y < rect_y r + fromIntegral (rect_height r) - --- | Make a tiled window floating, using its suggested rectangle -float :: Window -> X () -float w = do - (sc, rr) <- floatLocation w - windows $ \ws -> W.float w rr . fromMaybe ws $ do - i <- W.findTag w ws - guard $ i `elem` map (W.tag . W.workspace) (W.screens ws) - f <- W.peek ws - sw <- W.lookupWorkspace sc ws - return (W.focusWindow f . W.shiftWin sw w $ ws) - --- --------------------------------------------------------------------- --- Mouse handling - --- | Accumulate mouse motion events -mouseDrag :: (Position -> Position -> X ()) -> X () -> X () -mouseDrag f done = do - drag <- gets dragging - case drag of - Just _ -> return () -- error case? we're already dragging - Nothing -> do - XConf { theRoot = root, display = d } <- ask - io $ grabPointer d root False (buttonReleaseMask .|. pointerMotionMask) - grabModeAsync grabModeAsync none none currentTime - modify $ \s -> s { dragging = Just (motion, cleanup) } - where - cleanup = do - withDisplay $ io . flip ungrabPointer currentTime - modify $ \s -> s { dragging = Nothing } - done - motion x y = do z <- f x y - clearEvents pointerMotionMask - return z - --- | XXX comment me -mouseMoveWindow :: Window -> X () -mouseMoveWindow w = whenX (isClient w) $ withDisplay $ \d -> do - io $ raiseWindow d w - wa <- io $ getWindowAttributes d w - (_, _, _, ox', oy', _, _, _) <- io $ queryPointer d w - let ox = fromIntegral ox' - oy = fromIntegral oy' - mouseDrag (\ex ey -> io $ moveWindow d w (fromIntegral (fromIntegral (wa_x wa) + (ex - ox))) - (fromIntegral (fromIntegral (wa_y wa) + (ey - oy)))) - (float w) - --- | XXX comment me -mouseResizeWindow :: Window -> X () -mouseResizeWindow w = whenX (isClient w) $ withDisplay $ \d -> do - io $ raiseWindow d w - wa <- io $ getWindowAttributes d w - sh <- io $ getWMNormalHints d w - io $ warpPointer d none w 0 0 0 0 (fromIntegral (wa_width wa)) (fromIntegral (wa_height wa)) - mouseDrag (\ex ey -> - io $ resizeWindow d w `uncurry` - applySizeHintsContents sh (ex - fromIntegral (wa_x wa), - ey - fromIntegral (wa_y wa))) - (float w) - --- --------------------------------------------------------------------- --- | Support for window size hints - -type D = (Dimension, Dimension) - --- | Given a window, build an adjuster function that will reduce the given --- dimensions according to the window's border width and size hints. -mkAdjust :: Window -> X (D -> D) -mkAdjust w = withDisplay $ \d -> liftIO $ do - sh <- getWMNormalHints d w - bw <- fmap (fromIntegral . wa_border_width) $ getWindowAttributes d w - return $ applySizeHints bw sh - --- | Reduce the dimensions if needed to comply to the given SizeHints, taking --- window borders into account. -applySizeHints :: Integral a => Dimension -> SizeHints -> (a, a) -> D -applySizeHints bw sh = - tmap (+ 2 * bw) . applySizeHintsContents sh . tmap (subtract $ 2 * fromIntegral bw) - where - tmap f (x, y) = (f x, f y) - --- | Reduce the dimensions if needed to comply to the given SizeHints. -applySizeHintsContents :: Integral a => SizeHints -> (a, a) -> D -applySizeHintsContents sh (w, h) = - applySizeHints' sh (fromIntegral $ max 1 w, fromIntegral $ max 1 h) - --- | XXX comment me -applySizeHints' :: SizeHints -> D -> D -applySizeHints' sh = - maybe id applyMaxSizeHint (sh_max_size sh) - . maybe id (\(bw, bh) (w, h) -> (w+bw, h+bh)) (sh_base_size sh) - . maybe id applyResizeIncHint (sh_resize_inc sh) - . maybe id applyAspectHint (sh_aspect sh) - . maybe id (\(bw,bh) (w,h) -> (w-bw, h-bh)) (sh_base_size sh) - --- | Reduce the dimensions so their aspect ratio falls between the two given aspect ratios. -applyAspectHint :: (D, D) -> D -> D -applyAspectHint ((minx, miny), (maxx, maxy)) x@(w,h) - | or [minx < 1, miny < 1, maxx < 1, maxy < 1] = x - | w * maxy > h * maxx = (h * maxx `div` maxy, h) - | w * miny < h * minx = (w, w * miny `div` minx) - | otherwise = x - --- | Reduce the dimensions so they are a multiple of the size increments. -applyResizeIncHint :: D -> D -> D -applyResizeIncHint (iw,ih) x@(w,h) = - if iw > 0 && ih > 0 then (w - w `mod` iw, h - h `mod` ih) else x - --- | Reduce the dimensions if they exceed the given maximum dimensions. -applyMaxSizeHint :: D -> D -> D -applyMaxSizeHint (mw,mh) x@(w,h) = - if mw > 0 && mh > 0 then (min w mw,min h mh) else x diff --git a/XMonad/StackSet.hs b/XMonad/StackSet.hs deleted file mode 100644 index a7e9f6b..0000000 --- a/XMonad/StackSet.hs +++ /dev/null @@ -1,558 +0,0 @@ -{-# LANGUAGE PatternGuards #-} - ------------------------------------------------------------------------------ --- | --- Module : XMonad.StackSet --- Copyright : (c) Don Stewart 2007 --- License : BSD3-style (see LICENSE) --- --- Maintainer : dons@galois.com --- Stability : experimental --- Portability : portable, Haskell 98 --- - -module XMonad.StackSet ( - -- * Introduction - -- $intro - - -- ** The Zipper - -- $zipper - - -- ** Xinerama support - -- $xinerama - - -- ** Master and Focus - -- $focus - - StackSet(..), Workspace(..), Screen(..), Stack(..), RationalRect(..), - -- * Construction - -- $construction - new, view, greedyView, - -- * Xinerama operations - -- $xinerama - lookupWorkspace, - screens, workspaces, allWindows, currentTag, - -- * Operations on the current stack - -- $stackOperations - peek, index, integrate, integrate', differentiate, - focusUp, focusDown, focusUp', focusDown', focusMaster, focusWindow, - tagMember, renameTag, ensureTags, member, findTag, mapWorkspace, mapLayout, - -- * Modifying the stackset - -- $modifyStackset - insertUp, delete, delete', filter, - -- * Setting the master window - -- $settingMW - swapUp, swapDown, swapMaster, shiftMaster, modify, modify', float, sink, -- needed by users - -- * Composite operations - -- $composite - shift, shiftWin, - - -- for testing - abort - ) where - -import Prelude hiding (filter) -import Data.Maybe (listToMaybe,isJust,fromMaybe) -import qualified Data.List as L (deleteBy,find,splitAt,filter,nub) -import Data.List ( (\\) ) -import qualified Data.Map as M (Map,insert,delete,empty) - --- $intro --- --- The 'StackSet' data type encodes a window manager abstraction. The --- window manager is a set of virtual workspaces. On each workspace is a --- stack of windows. A given workspace is always current, and a given --- window on each workspace has focus. The focused window on the current --- workspace is the one which will take user input. It can be visualised --- as follows: --- --- > Workspace { 0*} { 1 } { 2 } { 3 } { 4 } --- > --- > Windows [1 [] [3* [6*] [] --- > ,2*] ,4 --- > ,5] --- --- Note that workspaces are indexed from 0, windows are numbered --- uniquely. A '*' indicates the window on each workspace that has --- focus, and which workspace is current. - --- $zipper --- --- We encode all the focus tracking directly in the data structure, with a 'zipper': --- --- A Zipper is essentially an `updateable' and yet pure functional --- cursor into a data structure. Zipper is also a delimited --- continuation reified as a data structure. --- --- The Zipper lets us replace an item deep in a complex data --- structure, e.g., a tree or a term, without an mutation. The --- resulting data structure will share as much of its components with --- the old structure as possible. --- --- Oleg Kiselyov, 27 Apr 2005, haskell\@, "Zipper as a delimited continuation" --- --- We use the zipper to keep track of the focused workspace and the --- focused window on each workspace, allowing us to have correct focus --- by construction. We closely follow Huet's original implementation: --- --- G. Huet, /Functional Pearl: The Zipper/, --- 1997, J. Functional Programming 75(5):549-554. --- and: --- R. Hinze and J. Jeuring, /Functional Pearl: The Web/. --- --- and Conor McBride's zipper differentiation paper. --- Another good reference is: --- --- The Zipper, Haskell wikibook - --- $xinerama --- Xinerama in X11 lets us view multiple virtual workspaces --- simultaneously. While only one will ever be in focus (i.e. will --- receive keyboard events), other workspaces may be passively --- viewable. We thus need to track which virtual workspaces are --- associated (viewed) on which physical screens. To keep track of --- this, 'StackSet' keeps separate lists of visible but non-focused --- workspaces, and non-visible workspaces. - --- $focus --- --- Each stack tracks a focused item, and for tiling purposes also tracks --- a 'master' position. The connection between 'master' and 'focus' --- needs to be well defined, particularly in relation to 'insert' and --- 'delete'. --- - ------------------------------------------------------------------------- --- | --- A cursor into a non-empty list of workspaces. --- --- We puncture the workspace list, producing a hole in the structure --- used to track the currently focused workspace. The two other lists --- that are produced are used to track those workspaces visible as --- Xinerama screens, and those workspaces not visible anywhere. - -data StackSet i l a sid sd = - StackSet { current :: !(Screen i l a sid sd) -- ^ currently focused workspace - , visible :: [Screen i l a sid sd] -- ^ non-focused workspaces, visible in xinerama - , hidden :: [Workspace i l a] -- ^ workspaces not visible anywhere - , floating :: M.Map a RationalRect -- ^ floating windows - } deriving (Show, Read, Eq) - --- | Visible workspaces, and their Xinerama screens. -data Screen i l a sid sd = Screen { workspace :: !(Workspace i l a) - , screen :: !sid - , screenDetail :: !sd } - deriving (Show, Read, Eq) - --- | --- A workspace is just a tag, a layout, and a stack. --- -data Workspace i l a = Workspace { tag :: !i, layout :: l, stack :: Maybe (Stack a) } - deriving (Show, Read, Eq) - --- | A structure for window geometries -data RationalRect = RationalRect Rational Rational Rational Rational - deriving (Show, Read, Eq) - --- | --- A stack is a cursor onto a window list. --- The data structure tracks focus by construction, and --- the master window is by convention the top-most item. --- Focus operations will not reorder the list that results from --- flattening the cursor. The structure can be envisaged as: --- --- > +-- master: < '7' > --- > up | [ '2' ] --- > +--------- [ '3' ] --- > focus: < '4' > --- > dn +----------- [ '8' ] --- --- A 'Stack' can be viewed as a list with a hole punched in it to make --- the focused position. Under the zipper\/calculus view of such --- structures, it is the differentiation of a [a], and integrating it --- back has a natural implementation used in 'index'. --- -data Stack a = Stack { focus :: !a -- focused thing in this set - , up :: [a] -- clowns to the left - , down :: [a] } -- jokers to the right - deriving (Show, Read, Eq) - - --- | this function indicates to catch that an error is expected -abort :: String -> a -abort x = error $ "xmonad: StackSet: " ++ x - --- --------------------------------------------------------------------- --- $construction - --- | /O(n)/. Create a new stackset, of empty stacks, with given tags, --- with physical screens whose descriptions are given by 'm'. The --- number of physical screens (@length 'm'@) should be less than or --- equal to the number of workspace tags. The first workspace in the --- list will be current. --- --- Xinerama: Virtual workspaces are assigned to physical screens, starting at 0. --- -new :: (Integral s) => l -> [i] -> [sd] -> StackSet i l a s sd -new l wids m | not (null wids) && length m <= length wids && not (null m) - = StackSet cur visi unseen M.empty - where (seen,unseen) = L.splitAt (length m) $ map (\i -> Workspace i l Nothing) wids - (cur:visi) = [ Screen i s sd | (i, s, sd) <- zip3 seen [0..] m ] - -- now zip up visibles with their screen id -new _ _ _ = abort "non-positive argument to StackSet.new" - --- | --- /O(w)/. Set focus to the workspace with index \'i\'. --- If the index is out of range, return the original 'StackSet'. --- --- Xinerama: If the workspace is not visible on any Xinerama screen, it --- becomes the current screen. If it is in the visible list, it becomes --- current. - -view :: (Eq s, Eq i) => i -> StackSet i l a s sd -> StackSet i l a s sd -view i s - | i == currentTag s = s -- current - - | Just x <- L.find ((i==).tag.workspace) (visible s) - -- if it is visible, it is just raised - = s { current = x, visible = current s : L.deleteBy (equating screen) x (visible s) } - - | Just x <- L.find ((i==).tag) (hidden s) -- must be hidden then - -- if it was hidden, it is raised on the xine screen currently used - = s { current = (current s) { workspace = x } - , hidden = workspace (current s) : L.deleteBy (equating tag) x (hidden s) } - - | otherwise = s -- not a member of the stackset - - where equating f = \x y -> f x == f y - - -- 'Catch'ing this might be hard. Relies on monotonically increasing - -- workspace tags defined in 'new' - -- - -- and now tags are not monotonic, what happens here? - --- | --- Set focus to the given workspace. If that workspace does not exist --- in the stackset, the original workspace is returned. If that workspace is --- 'hidden', then display that workspace on the current screen, and move the --- current workspace to 'hidden'. If that workspace is 'visible' on another --- screen, the workspaces of the current screen and the other screen are --- swapped. - -greedyView :: (Eq s, Eq i) => i -> StackSet i l a s sd -> StackSet i l a s sd -greedyView w ws - | any wTag (hidden ws) = view w ws - | (Just s) <- L.find (wTag . workspace) (visible ws) - = ws { current = (current ws) { workspace = workspace s } - , visible = s { workspace = workspace (current ws) } - : L.filter (not . wTag . workspace) (visible ws) } - | otherwise = ws - where wTag = (w == ) . tag - --- --------------------------------------------------------------------- --- $xinerama - --- | Find the tag of the workspace visible on Xinerama screen 'sc'. --- 'Nothing' if screen is out of bounds. -lookupWorkspace :: Eq s => s -> StackSet i l a s sd -> Maybe i -lookupWorkspace sc w = listToMaybe [ tag i | Screen i s _ <- current w : visible w, s == sc ] - --- --------------------------------------------------------------------- --- $stackOperations - --- | --- The 'with' function takes a default value, a function, and a --- StackSet. If the current stack is Nothing, 'with' returns the --- default value. Otherwise, it applies the function to the stack, --- returning the result. It is like 'maybe' for the focused workspace. --- -with :: b -> (Stack a -> b) -> StackSet i l a s sd -> b -with dflt f = maybe dflt f . stack . workspace . current - --- | --- Apply a function, and a default value for 'Nothing', to modify the current stack. --- -modify :: Maybe (Stack a) -> (Stack a -> Maybe (Stack a)) -> StackSet i l a s sd -> StackSet i l a s sd -modify d f s = s { current = (current s) - { workspace = (workspace (current s)) { stack = with d f s }}} - --- | --- Apply a function to modify the current stack if it isn't empty, and we don't --- want to empty it. --- -modify' :: (Stack a -> Stack a) -> StackSet i l a s sd -> StackSet i l a s sd -modify' f = modify Nothing (Just . f) - --- | --- /O(1)/. Extract the focused element of the current stack. --- Return 'Just' that element, or 'Nothing' for an empty stack. --- -peek :: StackSet i l a s sd -> Maybe a -peek = with Nothing (return . focus) - --- | --- /O(n)/. Flatten a 'Stack' into a list. --- -integrate :: Stack a -> [a] -integrate (Stack x l r) = reverse l ++ x : r - --- | --- /O(n)/ Flatten a possibly empty stack into a list. -integrate' :: Maybe (Stack a) -> [a] -integrate' = maybe [] integrate - --- | --- /O(n)/. Turn a list into a possibly empty stack (i.e., a zipper): --- the first element of the list is current, and the rest of the list --- is down. -differentiate :: [a] -> Maybe (Stack a) -differentiate [] = Nothing -differentiate (x:xs) = Just $ Stack x [] xs - --- | --- /O(n)/. 'filter p s' returns the elements of 's' such that 'p' evaluates to --- 'True'. Order is preserved, and focus moves as described for 'delete'. --- -filter :: (a -> Bool) -> Stack a -> Maybe (Stack a) -filter p (Stack f ls rs) = case L.filter p (f:rs) of - f':rs' -> Just $ Stack f' (L.filter p ls) rs' -- maybe move focus down - [] -> case L.filter p ls of -- filter back up - f':ls' -> Just $ Stack f' ls' [] -- else up - [] -> Nothing - --- | --- /O(s)/. Extract the stack on the current workspace, as a list. --- The order of the stack is determined by the master window -- it will be --- the head of the list. The implementation is given by the natural --- integration of a one-hole list cursor, back to a list. --- -index :: StackSet i l a s sd -> [a] -index = with [] integrate - --- | --- /O(1), O(w) on the wrapping case/. --- --- focusUp, focusDown. Move the window focus up or down the stack, --- wrapping if we reach the end. The wrapping should model a 'cycle' --- on the current stack. The 'master' window, and window order, --- are unaffected by movement of focus. --- --- swapUp, swapDown, swap the neighbour in the stack ordering, wrapping --- if we reach the end. Again the wrapping model should 'cycle' on --- the current stack. --- -focusUp, focusDown, swapUp, swapDown :: StackSet i l a s sd -> StackSet i l a s sd -focusUp = modify' focusUp' -focusDown = modify' focusDown' - -swapUp = modify' swapUp' -swapDown = modify' (reverseStack . swapUp' . reverseStack) - --- | Variants of 'focusUp' and 'focusDown' that work on a --- 'Stack' rather than an entire 'StackSet'. -focusUp', focusDown' :: Stack a -> Stack a -focusUp' (Stack t (l:ls) rs) = Stack l ls (t:rs) -focusUp' (Stack t [] rs) = Stack x xs [] where (x:xs) = reverse (t:rs) -focusDown' = reverseStack . focusUp' . reverseStack - -swapUp' :: Stack a -> Stack a -swapUp' (Stack t (l:ls) rs) = Stack t ls (l:rs) -swapUp' (Stack t [] rs) = Stack t (reverse rs) [] - --- | reverse a stack: up becomes down and down becomes up. -reverseStack :: Stack a -> Stack a -reverseStack (Stack t ls rs) = Stack t rs ls - --- --- | /O(1) on current window, O(n) in general/. Focus the window 'w', --- and set its workspace as current. --- -focusWindow :: (Eq s, Eq a, Eq i) => a -> StackSet i l a s sd -> StackSet i l a s sd -focusWindow w s | Just w == peek s = s - | otherwise = fromMaybe s $ do - n <- findTag w s - return $ until ((Just w ==) . peek) focusUp (view n s) - --- | Get a list of all screens in the 'StackSet'. -screens :: StackSet i l a s sd -> [Screen i l a s sd] -screens s = current s : visible s - --- | Get a list of all workspaces in the 'StackSet'. -workspaces :: StackSet i l a s sd -> [Workspace i l a] -workspaces s = workspace (current s) : map workspace (visible s) ++ hidden s - --- | Get a list of all windows in the 'StackSet' in no particular order -allWindows :: Eq a => StackSet i l a s sd -> [a] -allWindows = L.nub . concatMap (integrate' . stack) . workspaces - --- | Get the tag of the currently focused workspace. -currentTag :: StackSet i l a s sd -> i -currentTag = tag . workspace . current - --- | Is the given tag present in the 'StackSet'? -tagMember :: Eq i => i -> StackSet i l a s sd -> Bool -tagMember t = elem t . map tag . workspaces - --- | Rename a given tag if present in the 'StackSet'. -renameTag :: Eq i => i -> i -> StackSet i l a s sd -> StackSet i l a s sd -renameTag o n = mapWorkspace rename - where rename w = if tag w == o then w { tag = n } else w - --- | Ensure that a given set of workspace tags is present by renaming --- existing workspaces and\/or creating new hidden workspaces as --- necessary. -ensureTags :: Eq i => l -> [i] -> StackSet i l a s sd -> StackSet i l a s sd -ensureTags l allt st = et allt (map tag (workspaces st) \\ allt) st - where et [] _ s = s - et (i:is) rn s | i `tagMember` s = et is rn s - et (i:is) [] s = et is [] (s { hidden = Workspace i l Nothing : hidden s }) - et (i:is) (r:rs) s = et is rs $ renameTag r i s - --- | Map a function on all the workspaces in the 'StackSet'. -mapWorkspace :: (Workspace i l a -> Workspace i l a) -> StackSet i l a s sd -> StackSet i l a s sd -mapWorkspace f s = s { current = updScr (current s) - , visible = map updScr (visible s) - , hidden = map f (hidden s) } - where updScr scr = scr { workspace = f (workspace scr) } - --- | Map a function on all the layouts in the 'StackSet'. -mapLayout :: (l -> l') -> StackSet i l a s sd -> StackSet i l' a s sd -mapLayout f (StackSet v vs hs m) = StackSet (fScreen v) (map fScreen vs) (map fWorkspace hs) m - where - fScreen (Screen ws s sd) = Screen (fWorkspace ws) s sd - fWorkspace (Workspace t l s) = Workspace t (f l) s - --- | /O(n)/. Is a window in the 'StackSet'? -member :: Eq a => a -> StackSet i l a s sd -> Bool -member a s = isJust (findTag a s) - --- | /O(1) on current window, O(n) in general/. --- Return 'Just' the workspace tag of the given window, or 'Nothing' --- if the window is not in the 'StackSet'. -findTag :: Eq a => a -> StackSet i l a s sd -> Maybe i -findTag a s = listToMaybe - [ tag w | w <- workspaces s, has a (stack w) ] - where has _ Nothing = False - has x (Just (Stack t l r)) = x `elem` (t : l ++ r) - --- --------------------------------------------------------------------- --- $modifyStackset - --- | --- /O(n)/. (Complexity due to duplicate check). Insert a new element --- into the stack, above the currently focused element. The new --- element is given focus; the previously focused element is moved --- down. --- --- If the element is already in the stackset, the original stackset is --- returned unmodified. --- --- Semantics in Huet's paper is that insert doesn't move the cursor. --- However, we choose to insert above, and move the focus. --- -insertUp :: Eq a => a -> StackSet i l a s sd -> StackSet i l a s sd -insertUp a s = if member a s then s else insert - where insert = modify (Just $ Stack a [] []) (\(Stack t l r) -> Just $ Stack a l (t:r)) s - --- insertDown :: a -> StackSet i l a s sd -> StackSet i l a s sd --- insertDown a = modify (Stack a [] []) $ \(Stack t l r) -> Stack a (t:l) r --- Old semantics, from Huet. --- > w { down = a : down w } - --- | --- /O(1) on current window, O(n) in general/. Delete window 'w' if it exists. --- There are 4 cases to consider: --- --- * delete on an 'Nothing' workspace leaves it Nothing --- --- * otherwise, try to move focus to the down --- --- * otherwise, try to move focus to the up --- --- * otherwise, you've got an empty workspace, becomes 'Nothing' --- --- Behaviour with respect to the master: --- --- * deleting the master window resets it to the newly focused window --- --- * otherwise, delete doesn't affect the master. --- -delete :: (Ord a, Eq s) => a -> StackSet i l a s sd -> StackSet i l a s sd -delete w = sink w . delete' w - --- | Only temporarily remove the window from the stack, thereby not destroying special --- information saved in the 'Stackset' -delete' :: (Eq a, Eq s) => a -> StackSet i l a s sd -> StackSet i l a s sd -delete' w s = s { current = removeFromScreen (current s) - , visible = map removeFromScreen (visible s) - , hidden = map removeFromWorkspace (hidden s) } - where removeFromWorkspace ws = ws { stack = stack ws >>= filter (/=w) } - removeFromScreen scr = scr { workspace = removeFromWorkspace (workspace scr) } - ------------------------------------------------------------------------- - --- | Given a window, and its preferred rectangle, set it as floating --- A floating window should already be managed by the 'StackSet'. -float :: Ord a => a -> RationalRect -> StackSet i l a s sd -> StackSet i l a s sd -float w r s = s { floating = M.insert w r (floating s) } - --- | Clear the floating status of a window -sink :: Ord a => a -> StackSet i l a s sd -> StackSet i l a s sd -sink w s = s { floating = M.delete w (floating s) } - ------------------------------------------------------------------------- --- $settingMW - --- | /O(s)/. Set the master window to the focused window. --- The old master window is swapped in the tiling order with the focused window. --- Focus stays with the item moved. -swapMaster :: StackSet i l a s sd -> StackSet i l a s sd -swapMaster = modify' $ \c -> case c of - Stack _ [] _ -> c -- already master. - Stack t ls rs -> Stack t [] (xs ++ x : rs) where (x:xs) = reverse ls - --- natural! keep focus, move current to the top, move top to current. - --- | /O(s)/. Set the master window to the focused window. --- The other windows are kept in order and shifted down on the stack, as if you --- just hit mod-shift-k a bunch of times. --- Focus stays with the item moved. -shiftMaster :: StackSet i l a s sd -> StackSet i l a s sd -shiftMaster = modify' $ \c -> case c of - Stack _ [] _ -> c -- already master. - Stack t ls rs -> Stack t [] (reverse ls ++ rs) - --- | /O(s)/. Set focus to the master window. -focusMaster :: StackSet i l a s sd -> StackSet i l a s sd -focusMaster = modify' $ \c -> case c of - Stack _ [] _ -> c - Stack t ls rs -> Stack x [] (xs ++ t : rs) where (x:xs) = reverse ls - --- --- --------------------------------------------------------------------- --- $composite - --- | /O(w)/. shift. Move the focused element of the current stack to stack --- 'n', leaving it as the focused element on that stack. The item is --- inserted above the currently focused element on that workspace. --- The actual focused workspace doesn't change. If there is no --- element on the current stack, the original stackSet is returned. --- -shift :: (Ord a, Eq s, Eq i) => i -> StackSet i l a s sd -> StackSet i l a s sd -shift n s = maybe s (\w -> shiftWin n w s) (peek s) - --- | /O(n)/. shiftWin. Searches for the specified window 'w' on all workspaces --- of the stackSet and moves it to stack 'n', leaving it as the focused --- element on that stack. The item is inserted above the currently --- focused element on that workspace. --- The actual focused workspace doesn't change. If the window is not --- found in the stackSet, the original stackSet is returned. -shiftWin :: (Ord a, Eq a, Eq s, Eq i) => i -> a -> StackSet i l a s sd -> StackSet i l a s sd -shiftWin n w s = case findTag w s of - Just from | n `tagMember` s && n /= from -> go from s - _ -> s - where go from = onWorkspace n (insertUp w) . onWorkspace from (delete' w) - -onWorkspace :: (Eq i, Eq s) => i -> (StackSet i l a s sd -> StackSet i l a s sd) - -> (StackSet i l a s sd -> StackSet i l a s sd) -onWorkspace n f s = view (currentTag s) . f . view n $ s diff --git a/src/XMonad.hs b/src/XMonad.hs new file mode 100644 index 0000000..c1fc5dc --- /dev/null +++ b/src/XMonad.hs @@ -0,0 +1,47 @@ +-------------------------------------------------------------------- +-- | +-- Module : XMonad +-- Copyright : (c) Don Stewart +-- License : BSD3 +-- +-- Maintainer: Don Stewart +-- Stability : provisional +-- Portability: +-- +-------------------------------------------------------------------- +-- +-- Useful exports for configuration files. + +module XMonad ( + + module XMonad.Main, + module XMonad.Core, + module XMonad.Config, + module XMonad.Layout, + module XMonad.ManageHook, + module XMonad.Operations, + module Graphics.X11, + module Graphics.X11.Xlib.Extras, + (.|.), + MonadState(..), gets, modify, + MonadReader(..), asks, + MonadIO(..) + + ) where + +-- core modules +import XMonad.Main +import XMonad.Core +import XMonad.Config +import XMonad.Layout +import XMonad.ManageHook +import XMonad.Operations +-- import XMonad.StackSet -- conflicts with 'workspaces' defined in XMonad.hs + +-- modules needed to get basic configuration working +import Data.Bits +import Graphics.X11 hiding (refreshKeyboardMapping) +import Graphics.X11.Xlib.Extras + +import Control.Monad.State +import Control.Monad.Reader diff --git a/src/XMonad/Config.hs b/src/XMonad/Config.hs new file mode 100644 index 0000000..9aaab8f --- /dev/null +++ b/src/XMonad/Config.hs @@ -0,0 +1,330 @@ +{-# OPTIONS -fno-warn-missing-signatures -fno-warn-orphans #-} +{-# LANGUAGE TypeFamilies #-} +----------------------------------------------------------------------------- +-- | +-- 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. +-- +-- DO NOT MODIFY THIS FILE! It won't work. You may configure xmonad +-- by providing your own @~\/.xmonad\/xmonad.hs@ that overrides +-- specific fields in the default config, 'def'. For a starting point, you can +-- copy the @xmonad.hs@ found in the @man@ directory, or look at +-- examples on the xmonad wiki. +-- +------------------------------------------------------------------------ + +module XMonad.Config (defaultConfig, Default(..)) where + +-- +-- Useful imports +-- +import XMonad.Core as XMonad hiding + (workspaces,manageHook,keys,logHook,startupHook,borderWidth,mouseBindings + ,layoutHook,modMask,terminal,normalBorderColor,focusedBorderColor,focusFollowsMouse + ,handleEventHook,clickJustFocuses,rootMask,clientMask) +import qualified XMonad.Core as XMonad + (workspaces,manageHook,keys,logHook,startupHook,borderWidth,mouseBindings + ,layoutHook,modMask,terminal,normalBorderColor,focusedBorderColor,focusFollowsMouse + ,handleEventHook,clickJustFocuses,rootMask,clientMask) + +import XMonad.Layout +import XMonad.Operations +import XMonad.ManageHook +import qualified XMonad.StackSet as W +import Data.Bits ((.|.)) +import Data.Default +import Data.Monoid +import qualified Data.Map as M +import System.Exit +import Graphics.X11.Xlib +import Graphics.X11.Xlib.Extras + +-- | 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 + +-- | Width of the window border in pixels. +-- +borderWidth :: Dimension +borderWidth = 1 + +-- | Border colors for unfocused and focused windows, respectively. +-- +normalBorderColor, focusedBorderColor :: String +normalBorderColor = "gray" -- "#dddddd" +focusedBorderColor = "red" -- "#ff0000" don't use hex, not <24 bit safe + +------------------------------------------------------------------------ +-- 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 :: ManageHook +manageHook = composeAll + [ className =? "MPlayer" --> doFloat + , className =? "Gimp" --> doFloat ] + +------------------------------------------------------------------------ +-- 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 () + +------------------------------------------------------------------------ +-- Event handling + +-- | Defines a custom handler function for X Events. The function should +-- return (All True) if the default handler is to be run afterwards. +-- To combine event hooks, use mappend or mconcat from Data.Monoid. +handleEventHook :: Event -> X All +handleEventHook _ = return (All True) + +-- | Perform an arbitrary action at xmonad startup. +startupHook :: X () +startupHook = 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 + 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 + +------------------------------------------------------------------------ +-- Event Masks: + +-- | The client events that xmonad is interested in +clientMask :: EventMask +clientMask = structureNotifyMask .|. enterWindowMask .|. propertyChangeMask + +-- | The root events that xmonad is interested in +rootMask :: EventMask +rootMask = substructureRedirectMask .|. substructureNotifyMask + .|. enterWindowMask .|. leaveWindowMask .|. structureNotifyMask + .|. buttonPressMask + +------------------------------------------------------------------------ +-- Key bindings: + +-- | The preferred terminal program, which is used in a binding below and by +-- certain contrib modules. +terminal :: String +terminal = "xterm" + +-- | Whether focus follows the mouse pointer. +focusFollowsMouse :: Bool +focusFollowsMouse = True + +-- | Whether a mouse click select the focus or is just passed to the window +clickJustFocuses :: Bool +clickJustFocuses = True + + +-- | The xmonad key bindings. Add, modify or remove key bindings here. +-- +-- (The comment formatting character is used when generating the manpage) +-- +keys :: XConfig Layout -> 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 "dmenu_run") -- %! 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 .|. shiftMask, xK_Tab ), windows W.focusUp ) -- %! Move focus to the previous 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 + + -- quit, or restart + , ((modMask .|. shiftMask, xK_q ), io (exitWith ExitSuccess)) -- %! Quit xmonad + , ((modMask , xK_q ), spawn "if type xmonad; then xmonad --recompile && xmonad --restart; else xmessage xmonad not in \\$PATH: \"$PATH\"; fi") -- %! Restart xmonad + + , ((modMask .|. shiftMask, xK_slash ), spawn ("echo \"" ++ help ++ "\" | xmessage -file -")) -- %! Run xmessage with a summary of the default keybindings (useful for beginners) + -- repeat the binding for non-American layout keyboards + , ((modMask , xK_question), spawn ("echo \"" ++ help ++ "\" | xmessage -file -")) + ] + ++ + -- 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)]] + +-- | Mouse bindings: default actions bound to mouse events +mouseBindings :: XConfig Layout -> 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 + >> windows W.shiftMaster) + -- mod-button2 %! Raise the window to the top of the stack + , ((modMask, button2), windows . (W.shiftMaster .) . W.focusWindow) + -- mod-button3 %! Set the window to floating mode and resize by dragging + , ((modMask, button3), \w -> focus w >> mouseResizeWindow w + >> windows W.shiftMaster) + -- you may also bind events to the mouse scroll wheel (button4 and button5) + ] + +instance (a ~ Choose Tall (Choose (Mirror Tall) Full)) => Default (XConfig a) where + def = XConfig + { XMonad.borderWidth = borderWidth + , XMonad.workspaces = workspaces + , XMonad.layoutHook = layout + , XMonad.terminal = terminal + , XMonad.normalBorderColor = normalBorderColor + , XMonad.focusedBorderColor = focusedBorderColor + , XMonad.modMask = defaultModMask + , XMonad.keys = keys + , XMonad.logHook = logHook + , XMonad.startupHook = startupHook + , XMonad.mouseBindings = mouseBindings + , XMonad.manageHook = manageHook + , XMonad.handleEventHook = handleEventHook + , XMonad.focusFollowsMouse = focusFollowsMouse + , XMonad.clickJustFocuses = clickJustFocuses + , XMonad.clientMask = clientMask + , XMonad.rootMask = rootMask + } + +-- | The default set of configuration values itself +{-# DEPRECATED defaultConfig "Use def (from Data.Default, and re-exported by XMonad and XMonad.Config) instead." #-} +defaultConfig :: XConfig (Choose Tall (Choose (Mirror Tall) Full)) +defaultConfig = def + +-- | Finally, a copy of the default bindings in simple textual tabular format. +help :: String +help = unlines ["The default modifier key is 'alt'. Default keybindings:", + "", + "-- launching and killing programs", + "mod-Shift-Enter Launch xterminal", + "mod-p Launch dmenu", + "mod-Shift-p Launch gmrun", + "mod-Shift-c Close/kill the focused window", + "mod-Space Rotate through the available layout algorithms", + "mod-Shift-Space Reset the layouts on the current workSpace to default", + "mod-n Resize/refresh viewed windows to the correct size", + "", + "-- move focus up or down the window stack", + "mod-Tab Move focus to the next window", + "mod-Shift-Tab Move focus to the previous window", + "mod-j Move focus to the next window", + "mod-k Move focus to the previous window", + "mod-m Move focus to the master window", + "", + "-- modifying the window order", + "mod-Return Swap the focused window and the master window", + "mod-Shift-j Swap the focused window with the next window", + "mod-Shift-k Swap the focused window with the previous window", + "", + "-- resizing the master/slave ratio", + "mod-h Shrink the master area", + "mod-l Expand the master area", + "", + "-- floating layer support", + "mod-t Push window back into tiling; unfloat and re-tile it", + "", + "-- increase or decrease number of windows in the master area", + "mod-comma (mod-,) Increment the number of windows in the master area", + "mod-period (mod-.) Deincrement the number of windows in the master area", + "", + "-- quit, or restart", + "mod-Shift-q Quit xmonad", + "mod-q Restart xmonad", + "mod-[1..9] Switch to workSpace N", + "", + "-- Workspaces & screens", + "mod-Shift-[1..9] Move client to workspace N", + "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", + "", + "-- Mouse bindings: default actions bound to mouse events", + "mod-button1 Set the window to floating mode and move by dragging", + "mod-button2 Raise the window to the top of the stack", + "mod-button3 Set the window to floating mode and resize by dragging"] \ No newline at end of file diff --git a/src/XMonad/Core.hs b/src/XMonad/Core.hs new file mode 100644 index 0000000..cef4f81 --- /dev/null +++ b/src/XMonad/Core.hs @@ -0,0 +1,530 @@ +{-# LANGUAGE ExistentialQuantification, FlexibleInstances, GeneralizedNewtypeDeriving, + MultiParamTypeClasses, TypeSynonymInstances, CPP, DeriveDataTypeable #-} + +----------------------------------------------------------------------------- +-- | +-- Module : XMonad.Core +-- Copyright : (c) Spencer Janssen 2007 +-- License : BSD3-style (see LICENSE) +-- +-- Maintainer : spencerjanssen@gmail.com +-- Stability : unstable +-- Portability : not portable, uses cunning newtype deriving +-- +-- The 'X' monad, a state monad transformer over 'IO', for the window +-- manager state, and support routines. +-- +----------------------------------------------------------------------------- + +module XMonad.Core ( + X, WindowSet, WindowSpace, WorkspaceId, + ScreenId(..), ScreenDetail(..), XState(..), + XConf(..), XConfig(..), LayoutClass(..), + Layout(..), readsLayout, Typeable, Message, + SomeMessage(..), fromMessage, LayoutMessages(..), + StateExtension(..), ExtensionClass(..), + runX, catchX, userCode, userCodeDef, io, catchIO, installSignalHandlers, uninstallSignalHandlers, + withDisplay, withWindowSet, isRoot, runOnWorkspaces, + getAtom, spawn, spawnPID, xfork, getXMonadDir, recompile, trace, whenJust, whenX, + atom_WM_STATE, atom_WM_PROTOCOLS, atom_WM_DELETE_WINDOW, atom_WM_TAKE_FOCUS, ManageHook, Query(..), runQuery + ) where + +import XMonad.StackSet hiding (modify) + +import Prelude +import Codec.Binary.UTF8.String (encodeString) +import Control.Exception.Extensible (fromException, try, bracket, throw, finally, SomeException(..)) +import qualified Control.Exception.Extensible as E +import Control.Applicative +import Control.Monad.State +import Control.Monad.Reader +import Data.Default +import System.FilePath +import System.IO +import System.Info +import System.Posix.Process (executeFile, forkProcess, getAnyProcessStatus, createSession) +import System.Posix.Signals +import System.Posix.IO +import System.Posix.Types (ProcessID) +import System.Process +import System.Directory +import System.Exit +import Graphics.X11.Xlib +import Graphics.X11.Xlib.Extras (Event) +import Data.Typeable +import Data.List ((\\)) +import Data.Maybe (isJust,fromMaybe) +import Data.Monoid + +import qualified Data.Map as M +import qualified Data.Set as S + +-- | XState, the (mutable) window manager state. +data XState = XState + { windowset :: !WindowSet -- ^ workspace list + , mapped :: !(S.Set Window) -- ^ the Set of mapped windows + , waitingUnmap :: !(M.Map Window Int) -- ^ the number of expected UnmapEvents + , dragging :: !(Maybe (Position -> Position -> X (), X ())) + , numberlockMask :: !KeyMask -- ^ The numlock modifier + , extensibleState :: !(M.Map String (Either String StateExtension)) + -- ^ stores custom state information. + -- + -- The module "XMonad.Utils.ExtensibleState" in xmonad-contrib + -- provides additional information and a simple interface for using this. + } + +-- | XConf, the (read-only) window manager configuration. +data XConf = XConf + { display :: Display -- ^ the X11 display + , config :: !(XConfig Layout) -- ^ initial user configuration + , theRoot :: !Window -- ^ the root window + , normalBorder :: !Pixel -- ^ border color of unfocused windows + , focusedBorder :: !Pixel -- ^ border color of the focused window + , keyActions :: !(M.Map (KeyMask, KeySym) (X ())) + -- ^ a mapping of key presses to actions + , buttonActions :: !(M.Map (KeyMask, Button) (Window -> X ())) + -- ^ a mapping of button presses to actions + , mouseFocused :: !Bool -- ^ was refocus caused by mouse action? + , mousePosition :: !(Maybe (Position, Position)) + -- ^ position of the mouse according to + -- the event currently being processed + , currentEvent :: !(Maybe Event) + -- ^ event currently being processed + } + +-- todo, better name +data XConfig l = XConfig + { normalBorderColor :: !String -- ^ Non focused windows border color. Default: \"#dddddd\" + , focusedBorderColor :: !String -- ^ Focused windows border color. Default: \"#ff0000\" + , terminal :: !String -- ^ The preferred terminal application. Default: \"xterm\" + , layoutHook :: !(l Window) -- ^ The available layouts + , manageHook :: !ManageHook -- ^ The action to run when a new window is opened + , handleEventHook :: !(Event -> X All) -- ^ Handle an X event, returns (All True) if the default handler + -- should also be run afterwards. mappend should be used for combining + -- event hooks in most cases. + , workspaces :: ![String] -- ^ The list of workspaces' names + , modMask :: !KeyMask -- ^ the mod modifier + , keys :: !(XConfig Layout -> M.Map (ButtonMask,KeySym) (X ())) + -- ^ The key binding: a map from key presses and actions + , mouseBindings :: !(XConfig Layout -> M.Map (ButtonMask, Button) (Window -> X ())) + -- ^ The mouse bindings + , borderWidth :: !Dimension -- ^ The border width + , logHook :: !(X ()) -- ^ The action to perform when the windows set is changed + , startupHook :: !(X ()) -- ^ The action to perform on startup + , focusFollowsMouse :: !Bool -- ^ Whether window entry events can change focus + , clickJustFocuses :: !Bool -- ^ False to make a click which changes focus to be additionally passed to the window + , clientMask :: !EventMask -- ^ The client events that xmonad is interested in + , rootMask :: !EventMask -- ^ The root events that xmonad is interested in + } + + +type WindowSet = StackSet WorkspaceId (Layout Window) Window ScreenId ScreenDetail +type WindowSpace = Workspace WorkspaceId (Layout Window) Window + +-- | Virtual workspace indices +type WorkspaceId = String + +-- | Physical screen indices +newtype ScreenId = S Int deriving (Eq,Ord,Show,Read,Enum,Num,Integral,Real) + +-- | The 'Rectangle' with screen dimensions +data ScreenDetail = SD { screenRect :: !Rectangle } deriving (Eq,Show, Read) + +------------------------------------------------------------------------ + +-- | The X monad, 'ReaderT' and 'StateT' transformers over 'IO' +-- encapsulating the window manager configuration and state, +-- respectively. +-- +-- Dynamic components may be retrieved with 'get', static components +-- with 'ask'. With newtype deriving we get readers and state monads +-- instantiated on 'XConf' and 'XState' automatically. +-- +newtype X a = X (ReaderT XConf (StateT XState IO) a) + deriving (Functor, Monad, MonadIO, MonadState XState, MonadReader XConf, Typeable) + +instance Applicative X where + pure = return + (<*>) = ap + +instance (Monoid a) => Monoid (X a) where + mempty = return mempty + mappend = liftM2 mappend + +instance Default a => Default (X a) where + def = return def + +type ManageHook = Query (Endo WindowSet) +newtype Query a = Query (ReaderT Window X a) + deriving (Functor, Applicative, Monad, MonadReader Window, MonadIO) + +runQuery :: Query a -> Window -> X a +runQuery (Query m) w = runReaderT m w + +instance Monoid a => Monoid (Query a) where + mempty = return mempty + mappend = liftM2 mappend + +instance Default a => Default (Query a) where + def = return def + +-- | Run the 'X' monad, given a chunk of 'X' monad code, and an initial state +-- Return the result, and final state +runX :: XConf -> XState -> X a -> IO (a, XState) +runX c st (X a) = runStateT (runReaderT a c) st + +-- | Run in the 'X' monad, and in case of exception, and catch it and log it +-- to stderr, and run the error case. +catchX :: X a -> X a -> X a +catchX job errcase = do + st <- get + c <- ask + (a, s') <- io $ runX c st job `E.catch` \e -> case fromException e of + Just x -> throw e `const` (x `asTypeOf` ExitSuccess) + _ -> do hPrint stderr e; runX c st errcase + put s' + return a + +-- | Execute the argument, catching all exceptions. Either this function or +-- 'catchX' should be used at all callsites of user customized code. +userCode :: X a -> X (Maybe a) +userCode a = catchX (Just `liftM` a) (return Nothing) + +-- | Same as userCode but with a default argument to return instead of using +-- Maybe, provided for convenience. +userCodeDef :: a -> X a -> X a +userCodeDef defValue a = fromMaybe defValue `liftM` userCode a + +-- --------------------------------------------------------------------- +-- Convenient wrappers to state + +-- | Run a monad action with the current display settings +withDisplay :: (Display -> X a) -> X a +withDisplay f = asks display >>= f + +-- | Run a monadic action with the current stack set +withWindowSet :: (WindowSet -> X a) -> X a +withWindowSet f = gets windowset >>= f + +-- | True if the given window is the root window +isRoot :: Window -> X Bool +isRoot w = (w==) <$> asks theRoot + +-- | Wrapper for the common case of atom internment +getAtom :: String -> X Atom +getAtom str = withDisplay $ \dpy -> io $ internAtom dpy str False + +-- | Common non-predefined atoms +atom_WM_PROTOCOLS, atom_WM_DELETE_WINDOW, atom_WM_STATE, atom_WM_TAKE_FOCUS :: X Atom +atom_WM_PROTOCOLS = getAtom "WM_PROTOCOLS" +atom_WM_DELETE_WINDOW = getAtom "WM_DELETE_WINDOW" +atom_WM_STATE = getAtom "WM_STATE" +atom_WM_TAKE_FOCUS = getAtom "WM_TAKE_FOCUS" + +------------------------------------------------------------------------ +-- LayoutClass handling. See particular instances in Operations.hs + +-- | An existential type that can hold any object that is in 'Read' +-- and 'LayoutClass'. +data Layout a = forall l. (LayoutClass l a, Read (l a)) => Layout (l a) + +-- | Using the 'Layout' as a witness, parse existentially wrapped windows +-- from a 'String'. +readsLayout :: Layout a -> String -> [(Layout a, String)] +readsLayout (Layout l) s = [(Layout (asTypeOf x l), rs) | (x, rs) <- reads s] + +-- | Every layout must be an instance of 'LayoutClass', which defines +-- the basic layout operations along with a sensible default for each. +-- +-- Minimal complete definition: +-- +-- * 'runLayout' || (('doLayout' || 'pureLayout') && 'emptyLayout'), and +-- +-- * 'handleMessage' || 'pureMessage' +-- +-- You should also strongly consider implementing 'description', +-- although it is not required. +-- +-- Note that any code which /uses/ 'LayoutClass' methods should only +-- ever call 'runLayout', 'handleMessage', and 'description'! In +-- other words, the only calls to 'doLayout', 'pureMessage', and other +-- such methods should be from the default implementations of +-- 'runLayout', 'handleMessage', and so on. This ensures that the +-- proper methods will be used, regardless of the particular methods +-- that any 'LayoutClass' instance chooses to define. +class Show (layout a) => LayoutClass layout a where + + -- | By default, 'runLayout' calls 'doLayout' if there are any + -- windows to be laid out, and 'emptyLayout' otherwise. Most + -- instances of 'LayoutClass' probably do not need to implement + -- 'runLayout'; it is only useful for layouts which wish to make + -- use of more of the 'Workspace' information (for example, + -- "XMonad.Layout.PerWorkspace"). + runLayout :: Workspace WorkspaceId (layout a) a + -> Rectangle + -> X ([(a, Rectangle)], Maybe (layout a)) + runLayout (Workspace _ l ms) r = maybe (emptyLayout l r) (doLayout l r) ms + + -- | Given a 'Rectangle' in which to place the windows, and a 'Stack' + -- of windows, return a list of windows and their corresponding + -- Rectangles. If an element is not given a Rectangle by + -- 'doLayout', then it is not shown on screen. The order of + -- windows in this list should be the desired stacking order. + -- + -- Also possibly return a modified layout (by returning @Just + -- newLayout@), if this layout needs to be modified (e.g. if it + -- keeps track of some sort of state). Return @Nothing@ if the + -- layout does not need to be modified. + -- + -- Layouts which do not need access to the 'X' monad ('IO', window + -- manager state, or configuration) and do not keep track of their + -- own state should implement 'pureLayout' instead of 'doLayout'. + doLayout :: layout a -> Rectangle -> Stack a + -> X ([(a, Rectangle)], Maybe (layout a)) + doLayout l r s = return (pureLayout l r s, Nothing) + + -- | This is a pure version of 'doLayout', for cases where we + -- don't need access to the 'X' monad to determine how to lay out + -- the windows, and we don't need to modify the layout itself. + pureLayout :: layout a -> Rectangle -> Stack a -> [(a, Rectangle)] + pureLayout _ r s = [(focus s, r)] + + -- | 'emptyLayout' is called when there are no windows. + emptyLayout :: layout a -> Rectangle -> X ([(a, Rectangle)], Maybe (layout a)) + emptyLayout _ _ = return ([], Nothing) + + -- | 'handleMessage' performs message handling. If + -- 'handleMessage' returns @Nothing@, then the layout did not + -- respond to the message and the screen is not refreshed. + -- Otherwise, 'handleMessage' returns an updated layout and the + -- screen is refreshed. + -- + -- Layouts which do not need access to the 'X' monad to decide how + -- to handle messages should implement 'pureMessage' instead of + -- 'handleMessage' (this restricts the risk of error, and makes + -- testing much easier). + handleMessage :: layout a -> SomeMessage -> X (Maybe (layout a)) + handleMessage l = return . pureMessage l + + -- | Respond to a message by (possibly) changing our layout, but + -- taking no other action. If the layout changes, the screen will + -- be refreshed. + pureMessage :: layout a -> SomeMessage -> Maybe (layout a) + pureMessage _ _ = Nothing + + -- | This should be a human-readable string that is used when + -- selecting layouts by name. The default implementation is + -- 'show', which is in some cases a poor default. + description :: layout a -> String + description = show + +instance LayoutClass Layout Window where + runLayout (Workspace i (Layout l) ms) r = fmap (fmap Layout) `fmap` runLayout (Workspace i l ms) r + doLayout (Layout l) r s = fmap (fmap Layout) `fmap` doLayout l r s + emptyLayout (Layout l) r = fmap (fmap Layout) `fmap` emptyLayout l r + handleMessage (Layout l) = fmap (fmap Layout) . handleMessage l + description (Layout l) = description l + +instance Show (Layout a) where show (Layout l) = show l + +-- | Based on ideas in /An Extensible Dynamically-Typed Hierarchy of +-- Exceptions/, Simon Marlow, 2006. Use extensible messages to the +-- 'handleMessage' handler. +-- +-- User-extensible messages must be a member of this class. +-- +class Typeable a => Message a + +-- | +-- A wrapped value of some type in the 'Message' class. +-- +data SomeMessage = forall a. Message a => SomeMessage a + +-- | +-- And now, unwrap a given, unknown 'Message' type, performing a (dynamic) +-- type check on the result. +-- +fromMessage :: Message m => SomeMessage -> Maybe m +fromMessage (SomeMessage m) = cast m + +-- X Events are valid Messages. +instance Message Event + +-- | 'LayoutMessages' are core messages that all layouts (especially stateful +-- layouts) should consider handling. +data LayoutMessages = Hide -- ^ sent when a layout becomes non-visible + | ReleaseResources -- ^ sent when xmonad is exiting or restarting + deriving (Typeable, Eq) + +instance Message LayoutMessages + +-- --------------------------------------------------------------------- +-- Extensible state +-- + +-- | Every module must make the data it wants to store +-- an instance of this class. +-- +-- Minimal complete definition: initialValue +class Typeable a => ExtensionClass a where + -- | Defines an initial value for the state extension + initialValue :: a + -- | Specifies whether the state extension should be + -- persistent. Setting this method to 'PersistentExtension' + -- will make the stored data survive restarts, but + -- requires a to be an instance of Read and Show. + -- + -- It defaults to 'StateExtension', i.e. no persistence. + extensionType :: a -> StateExtension + extensionType = StateExtension + +-- | Existential type to store a state extension. +data StateExtension = + forall a. ExtensionClass a => StateExtension a + -- ^ Non-persistent state extension + | forall a. (Read a, Show a, ExtensionClass a) => PersistentExtension a + -- ^ Persistent extension + +-- --------------------------------------------------------------------- +-- | General utilities +-- +-- Lift an 'IO' action into the 'X' monad +io :: MonadIO m => IO a -> m a +io = liftIO + +-- | Lift an 'IO' action into the 'X' monad. If the action results in an 'IO' +-- exception, log the exception to stderr and continue normal execution. +catchIO :: MonadIO m => IO () -> m () +catchIO f = io (f `E.catch` \(SomeException e) -> hPrint stderr e >> hFlush stderr) + +-- | spawn. Launch an external application. Specifically, it double-forks and +-- runs the 'String' you pass as a command to \/bin\/sh. +-- +-- Note this function assumes your locale uses utf8. +spawn :: MonadIO m => String -> m () +spawn x = spawnPID x >> return () + +-- | Like 'spawn', but returns the 'ProcessID' of the launched application +spawnPID :: MonadIO m => String -> m ProcessID +spawnPID x = xfork $ executeFile "/bin/sh" False ["-c", encodeString x] Nothing + +-- | A replacement for 'forkProcess' which resets default signal handlers. +xfork :: MonadIO m => IO () -> m ProcessID +xfork x = io . forkProcess . finally nullStdin $ do + uninstallSignalHandlers + createSession + x + where + nullStdin = do + fd <- openFd "/dev/null" ReadOnly Nothing defaultFileFlags + dupTo fd stdInput + closeFd fd + +-- | This is basically a map function, running a function in the 'X' monad on +-- each workspace with the output of that function being the modified workspace. +runOnWorkspaces :: (WindowSpace -> X WindowSpace) -> X () +runOnWorkspaces job = do + ws <- gets windowset + h <- mapM job $ hidden ws + c:v <- mapM (\s -> (\w -> s { workspace = w}) <$> job (workspace s)) + $ current ws : visible ws + modify $ \s -> s { windowset = ws { current = c, visible = v, hidden = h } } + +-- | Return the path to @~\/.xmonad@. +getXMonadDir :: MonadIO m => m String +getXMonadDir = io $ getAppUserDataDirectory "xmonad" + +-- | 'recompile force', recompile @~\/.xmonad\/xmonad.hs@ when any of the +-- following apply: +-- +-- * force is 'True' +-- +-- * the xmonad executable does not exist +-- +-- * the xmonad executable is older than xmonad.hs or any file in +-- ~\/.xmonad\/lib +-- +-- The -i flag is used to restrict recompilation to the xmonad.hs file only, +-- and any files in the ~\/.xmonad\/lib directory. +-- +-- Compilation errors (if any) are logged to ~\/.xmonad\/xmonad.errors. If +-- GHC indicates failure with a non-zero exit code, an xmessage displaying +-- that file is spawned. +-- +-- 'False' is returned if there are compilation errors. +-- +recompile :: MonadIO m => Bool -> m Bool +recompile force = io $ do + dir <- getXMonadDir + let binn = "xmonad-"++arch++"-"++os + bin = dir binn + base = dir "xmonad" + err = base ++ ".errors" + src = base ++ ".hs" + lib = dir "lib" + libTs <- mapM getModTime . Prelude.filter isSource =<< allFiles lib + srcT <- getModTime src + binT <- getModTime bin + if force || any (binT <) (srcT : libTs) + then do + -- temporarily disable SIGCHLD ignoring: + uninstallSignalHandlers + status <- bracket (openFile err WriteMode) hClose $ \h -> + waitForProcess =<< runProcess "ghc" ["--make", "xmonad.hs", "-i", "-ilib", "-fforce-recomp", "-main-is", "main", "-v0", "-o",binn] (Just dir) + Nothing Nothing Nothing (Just h) + + -- re-enable SIGCHLD: + installSignalHandlers + + -- now, if it fails, run xmessage to let the user know: + when (status /= ExitSuccess) $ do + ghcErr <- readFile err + let msg = unlines $ + ["Error detected while loading xmonad configuration file: " ++ src] + ++ lines (if null ghcErr then show status else ghcErr) + ++ ["","Please check the file for errors."] + -- nb, the ordering of printing, then forking, is crucial due to + -- lazy evaluation + hPutStrLn stderr msg + forkProcess $ executeFile "xmessage" True ["-default", "okay", msg] Nothing + return () + return (status == ExitSuccess) + else return True + where getModTime f = E.catch (Just <$> getModificationTime f) (\(SomeException _) -> return Nothing) + isSource = flip elem [".hs",".lhs",".hsc"] . takeExtension + allFiles t = do + let prep = map (t) . Prelude.filter (`notElem` [".",".."]) + cs <- prep <$> E.catch (getDirectoryContents t) (\(SomeException _) -> return []) + ds <- filterM doesDirectoryExist cs + concat . ((cs \\ ds):) <$> mapM allFiles ds + +-- | Conditionally run an action, using a @Maybe a@ to decide. +whenJust :: Monad m => Maybe a -> (a -> m ()) -> m () +whenJust mg f = maybe (return ()) f mg + +-- | Conditionally run an action, using a 'X' event to decide +whenX :: X Bool -> X () -> X () +whenX a f = a >>= \b -> when b f + +-- | A 'trace' for the 'X' monad. Logs a string to stderr. The result may +-- be found in your .xsession-errors file +trace :: MonadIO m => String -> m () +trace = io . hPutStrLn stderr + +-- | Ignore SIGPIPE to avoid termination when a pipe is full, and SIGCHLD to +-- avoid zombie processes, and clean up any extant zombie processes. +installSignalHandlers :: MonadIO m => m () +installSignalHandlers = io $ do + installHandler openEndedPipe Ignore Nothing + installHandler sigCHLD Ignore Nothing + (try :: IO a -> IO (Either SomeException a)) + $ fix $ \more -> do + x <- getAnyProcessStatus False False + when (isJust x) more + return () + +uninstallSignalHandlers :: MonadIO m => m () +uninstallSignalHandlers = io $ do + installHandler openEndedPipe Default Nothing + installHandler sigCHLD Default Nothing + return () diff --git a/src/XMonad/Layout.hs b/src/XMonad/Layout.hs new file mode 100644 index 0000000..8eff488 --- /dev/null +++ b/src/XMonad/Layout.hs @@ -0,0 +1,210 @@ +{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, PatternGuards, TypeSynonymInstances, DeriveDataTypeable #-} + +-- -------------------------------------------------------------------------- +-- | +-- Module : XMonad.Layout +-- Copyright : (c) Spencer Janssen 2007 +-- License : BSD3-style (see LICENSE) +-- +-- Maintainer : spencerjanssen@gmail.com +-- Stability : unstable +-- Portability : not portable, Typeable deriving, mtl, posix +-- +-- The collection of core layouts. +-- +----------------------------------------------------------------------------- + +module XMonad.Layout ( + Full(..), Tall(..), Mirror(..), + Resize(..), IncMasterN(..), Choose, (|||), ChangeLayout(..), + mirrorRect, splitVertically, + splitHorizontally, splitHorizontallyBy, splitVerticallyBy, + + tile + + ) where + +import XMonad.Core + +import Graphics.X11 (Rectangle(..)) +import qualified XMonad.StackSet as W +import Control.Arrow ((***), second) +import Control.Monad +import Data.Maybe (fromMaybe) + +------------------------------------------------------------------------ + +-- | Change the size of the master pane. +data Resize = Shrink | Expand deriving Typeable + +-- | Increase the number of clients in the master pane. +data IncMasterN = IncMasterN !Int deriving Typeable + +instance Message Resize +instance Message IncMasterN + +-- | Simple fullscreen mode. Renders the focused window fullscreen. +data Full a = Full deriving (Show, Read) + +instance LayoutClass Full a + +-- | The builtin tiling mode of xmonad. Supports 'Shrink', 'Expand' and +-- 'IncMasterN'. +data Tall a = Tall { tallNMaster :: !Int -- ^ The default number of windows in the master pane (default: 1) + , tallRatioIncrement :: !Rational -- ^ Percent of screen to increment by when resizing panes (default: 3/100) + , tallRatio :: !Rational -- ^ Default proportion of screen occupied by master pane (default: 1/2) + } + deriving (Show, Read) + -- TODO should be capped [0..1] .. + +-- a nice pure layout, lots of properties for the layout, and its messages, in Properties.hs +instance LayoutClass Tall a where + pureLayout (Tall nmaster _ frac) r s = zip ws rs + where ws = W.integrate s + rs = tile frac r nmaster (length ws) + + pureMessage (Tall nmaster delta frac) m = + msum [fmap resize (fromMessage m) + ,fmap incmastern (fromMessage m)] + + where resize Shrink = Tall nmaster delta (max 0 $ frac-delta) + resize Expand = Tall nmaster delta (min 1 $ frac+delta) + incmastern (IncMasterN d) = Tall (max 0 (nmaster+d)) delta frac + + description _ = "Tall" + +-- | Compute the positions for windows using the default two-pane tiling +-- algorithm. +-- +-- The screen is divided into two panes. All clients are +-- then partioned between these two panes. One pane, the master, by +-- convention has the least number of windows in it. +tile + :: Rational -- ^ @frac@, what proportion of the screen to devote to the master area + -> Rectangle -- ^ @r@, the rectangle representing the screen + -> Int -- ^ @nmaster@, the number of windows in the master pane + -> Int -- ^ @n@, the total number of windows to tile + -> [Rectangle] +tile f r nmaster n = if n <= nmaster || nmaster == 0 + then splitVertically n r + else splitVertically nmaster r1 ++ splitVertically (n-nmaster) r2 -- two columns + where (r1,r2) = splitHorizontallyBy f r + +-- +-- Divide the screen vertically into n subrectangles +-- +splitVertically, splitHorizontally :: Int -> Rectangle -> [Rectangle] +splitVertically n r | n < 2 = [r] +splitVertically n (Rectangle sx sy sw sh) = Rectangle sx sy sw smallh : + splitVertically (n-1) (Rectangle sx (sy+fromIntegral smallh) sw (sh-smallh)) + where smallh = sh `div` fromIntegral n --hmm, this is a fold or map. + +-- Not used in the core, but exported +splitHorizontally n = map mirrorRect . splitVertically n . mirrorRect + +-- Divide the screen into two rectangles, using a rational to specify the ratio +splitHorizontallyBy, splitVerticallyBy :: RealFrac r => r -> Rectangle -> (Rectangle, Rectangle) +splitHorizontallyBy f (Rectangle sx sy sw sh) = + ( Rectangle sx sy leftw sh + , Rectangle (sx + fromIntegral leftw) sy (sw-fromIntegral leftw) sh) + where leftw = floor $ fromIntegral sw * f + +-- Not used in the core, but exported +splitVerticallyBy f = (mirrorRect *** mirrorRect) . splitHorizontallyBy f . mirrorRect + +------------------------------------------------------------------------ + +-- | Mirror a layout, compute its 90 degree rotated form. +newtype Mirror l a = Mirror (l a) deriving (Show, Read) + +instance LayoutClass l a => LayoutClass (Mirror l) a where + runLayout (W.Workspace i (Mirror l) ms) r = (map (second mirrorRect) *** fmap Mirror) + `fmap` runLayout (W.Workspace i l ms) (mirrorRect r) + handleMessage (Mirror l) = fmap (fmap Mirror) . handleMessage l + description (Mirror l) = "Mirror "++ description l + +-- | Mirror a rectangle. +mirrorRect :: Rectangle -> Rectangle +mirrorRect (Rectangle rx ry rw rh) = Rectangle ry rx rh rw + +------------------------------------------------------------------------ +-- LayoutClass selection manager +-- Layouts that transition between other layouts + +-- | Messages to change the current layout. +data ChangeLayout = FirstLayout | NextLayout deriving (Eq, Show, Typeable) + +instance Message ChangeLayout + +-- | The layout choice combinator +(|||) :: (LayoutClass l a, LayoutClass r a) => l a -> r a -> Choose l r a +(|||) = Choose L +infixr 5 ||| + +-- | A layout that allows users to switch between various layout options. +data Choose l r a = Choose LR (l a) (r a) deriving (Read, Show) + +-- | Are we on the left or right sub-layout? +data LR = L | R deriving (Read, Show, Eq) + +data NextNoWrap = NextNoWrap deriving (Eq, Show, Typeable) +instance Message NextNoWrap + +-- | A small wrapper around handleMessage, as it is tedious to write +-- SomeMessage repeatedly. +handle :: (LayoutClass l a, Message m) => l a -> m -> X (Maybe (l a)) +handle l m = handleMessage l (SomeMessage m) + +-- | A smart constructor that takes some potential modifications, returns a +-- new structure if any fields have changed, and performs any necessary cleanup +-- on newly non-visible layouts. +choose :: (LayoutClass l a, LayoutClass r a) + => Choose l r a-> LR -> Maybe (l a) -> Maybe (r a) -> X (Maybe (Choose l r a)) +choose (Choose d _ _) d' Nothing Nothing | d == d' = return Nothing +choose (Choose d l r) d' ml mr = f lr + where + (l', r') = (fromMaybe l ml, fromMaybe r mr) + lr = case (d, d') of + (L, R) -> (hide l' , return r') + (R, L) -> (return l', hide r' ) + (_, _) -> (return l', return r') + f (x,y) = fmap Just $ liftM2 (Choose d') x y + hide x = fmap (fromMaybe x) $ handle x Hide + +instance (LayoutClass l a, LayoutClass r a) => LayoutClass (Choose l r) a where + runLayout (W.Workspace i (Choose L l r) ms) = + fmap (second . fmap $ flip (Choose L) r) . runLayout (W.Workspace i l ms) + runLayout (W.Workspace i (Choose R l r) ms) = + fmap (second . fmap $ Choose R l) . runLayout (W.Workspace i r ms) + + description (Choose L l _) = description l + description (Choose R _ r) = description r + + handleMessage lr m | Just NextLayout <- fromMessage m = do + mlr' <- handle lr NextNoWrap + maybe (handle lr FirstLayout) (return . Just) mlr' + + handleMessage c@(Choose d l r) m | Just NextNoWrap <- fromMessage m = + case d of + L -> do + ml <- handle l NextNoWrap + case ml of + Just _ -> choose c L ml Nothing + Nothing -> choose c R Nothing =<< handle r FirstLayout + + R -> choose c R Nothing =<< handle r NextNoWrap + + handleMessage c@(Choose _ l _) m | Just FirstLayout <- fromMessage m = + flip (choose c L) Nothing =<< handle l FirstLayout + + handleMessage c@(Choose d l r) m | Just ReleaseResources <- fromMessage m = + join $ liftM2 (choose c d) (handle l ReleaseResources) (handle r ReleaseResources) + + handleMessage c@(Choose d l r) m = do + ml' <- case d of + L -> handleMessage l m + R -> return Nothing + mr' <- case d of + L -> return Nothing + R -> handleMessage r m + choose c d ml' mr' diff --git a/src/XMonad/Main.hsc b/src/XMonad/Main.hsc new file mode 100644 index 0000000..5d59042 --- /dev/null +++ b/src/XMonad/Main.hsc @@ -0,0 +1,410 @@ +{-# LANGUAGE MultiParamTypeClasses, FlexibleContexts, ForeignFunctionInterface #-} +---------------------------------------------------------------------------- +-- | +-- Module : XMonad.Main +-- Copyright : (c) Spencer Janssen 2007 +-- License : BSD3-style (see LICENSE) +-- +-- Maintainer : spencerjanssen@gmail.com +-- Stability : unstable +-- Portability : not portable, uses mtl, X11, posix +-- +-- xmonad, a minimalist, tiling window manager for X11 +-- +----------------------------------------------------------------------------- + +module XMonad.Main (xmonad) where + +import Control.Arrow (second) +import Data.Bits +import Data.List ((\\)) +import Data.Function +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 Data.Monoid (getAll) + +import Foreign.C +import Foreign.Ptr + +import System.Environment (getArgs) + +import Graphics.X11.Xlib hiding (refreshKeyboardMapping) +import Graphics.X11.Xlib.Extras + +import XMonad.Core +import qualified XMonad.Config as Default +import XMonad.StackSet (new, floating, member) +import qualified XMonad.StackSet as W +import XMonad.Operations + +import System.IO + +------------------------------------------------------------------------ +-- Locale support + +#include + +foreign import ccall unsafe "locale.h setlocale" + c_setlocale :: CInt -> Ptr CChar -> IO (Ptr CChar) + +------------------------------------------------------------------------ + +-- | +-- The main entry point +-- +xmonad :: (LayoutClass l Window, Read (l Window)) => XConfig l -> IO () +xmonad initxmc = do + -- setup locale information from environment + withCString "" $ c_setlocale (#const LC_ALL) + -- ignore SIGPIPE and SIGCHLD + installSignalHandlers + -- First, wrap the layout in an existential, to keep things pretty: + let xmc = initxmc { layoutHook = Layout $ layoutHook initxmc } + dpy <- openDisplay "" + let dflt = defaultScreen dpy + + rootw <- rootWindow dpy dflt + + args <- getArgs + + when ("--replace" `elem` args) $ replace dpy dflt rootw + + -- If another WM is running, a BadAccess error will be returned. The + -- default error handler will write the exception to stderr and exit with + -- an error. + selectInput dpy rootw $ rootMask initxmc + + sync dpy False -- sync to ensure all outstanding errors are delivered + + -- turn off the default handler in favor of one that ignores all errors + -- (ugly, I know) + xSetErrorHandler -- in C, I'm too lazy to write the binding: dons + + xinesc <- getCleanedScreenInfo dpy + nbc <- do v <- initColor dpy $ normalBorderColor xmc + ~(Just nbc_) <- initColor dpy $ normalBorderColor Default.def + return (fromMaybe nbc_ v) + + fbc <- do v <- initColor dpy $ focusedBorderColor xmc + ~(Just fbc_) <- initColor dpy $ focusedBorderColor Default.def + return (fromMaybe fbc_ v) + + hSetBuffering stdout NoBuffering + + let layout = layoutHook xmc + lreads = readsLayout layout + initialWinset = new layout (workspaces xmc) $ map SD xinesc + 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 + extState = fromMaybe M.empty $ do + ("--resume" : _ : dyns : _) <- return args + vals <- maybeRead reads dyns + return . M.fromList . map (second Left) $ vals + + cf = XConf + { display = dpy + , config = xmc + , theRoot = rootw + , normalBorder = nbc + , focusedBorder = fbc + , keyActions = keys xmc xmc + , buttonActions = mouseBindings xmc xmc + , mouseFocused = False + , mousePosition = Nothing + , currentEvent = Nothing } + + st = XState + { windowset = initialWinset + , numberlockMask = 0 + , mapped = S.empty + , waitingUnmap = M.empty + , dragging = Nothing + , extensibleState = extState + } + allocaXEvent $ \e -> + runX cf st $ do + + setNumlockMask + grabKeys + grabButtons + + io $ sync dpy False + + ws <- io $ scan dpy rootw + + -- bootstrap the windowset, Operations.windows will identify all + -- the windows in winset as new and set initial properties for + -- those windows. Remove all windows that are no longer top-level + -- children of the root, they may have disappeared since + -- restarting. + windows . const . foldr W.delete winset $ W.allWindows winset \\ ws + + -- manage the as-yet-unmanaged windows + mapM_ manage (ws \\ W.allWindows winset) + + userCode $ startupHook initxmc + + -- main loop, for all you HOF/recursion fans out there. + forever $ prehandle =<< io (nextEvent dpy e >> getEvent e) + + return () + where + -- if the event gives us the position of the pointer, set mousePosition + prehandle e = let mouse = do guard (ev_event_type e `elem` evs) + return (fromIntegral (ev_x_root e) + ,fromIntegral (ev_y_root e)) + in local (\c -> c { mousePosition = mouse, currentEvent = Just e }) (handleWithHook e) + evs = [ keyPress, keyRelease, enterNotify, leaveNotify + , buttonPress, buttonRelease] + + +-- | Runs handleEventHook from the configuration and runs the default handler +-- function if it returned True. +handleWithHook :: Event -> X () +handleWithHook e = do + evHook <- asks (handleEventHook . config) + whenX (userCodeDef True $ getAll `fmap` evHook e) (handle e) + +-- --------------------------------------------------------------------- +-- | 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 + userCodeDef () $ 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) $ do + unmanage w + modify (\s -> s { mapped = S.delete w (mapped s) + , waitingUnmap = M.delete w (waitingUnmap s)}) + +-- 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.update mpred w (waitingUnmap s) }) + where mpred 1 = Nothing + mpred n = Just $ pred n + +-- set keyboard mapping +handle e@(MappingNotifyEvent {}) = do + io $ refreshKeyboardMapping e + when (ev_request e `elem` [mappingKeyboard, mappingModifier]) $ do + setNumlockMask + 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. + dpy <- asks display + isr <- isRoot w + m <- cleanMask $ ev_state e + mact <- asks (M.lookup (m, b) . buttonActions) + case mact of + Just act | isr -> act $ ev_subwindow e + _ -> do + focus w + ctf <- asks (clickJustFocuses . config) + unless ctf $ io (allowEvents dpy replayPointer currentTime) + broadcastMessage e -- Always send button events. + +-- entered a normal window: focus it if focusFollowsMouse is set to +-- True in the user's config. +handle e@(CrossingEvent {ev_window = w, ev_event_type = t}) + | t == enterNotify && ev_mode e == notifyNormal + = whenX (asks $ focusFollowsMouse . config) (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 event@(PropertyEvent { ev_event_type = t, ev_atom = a }) + | t == propertyNotify && a == wM_NAME = asks (logHook . config) >>= userCodeDef () >> + broadcastMessage event + +handle e@ClientMessageEvent { ev_message_type = mt } = do + a <- getAtom "XMONAD_RESTART" + if (mt == a) + then restart "xmonad" True + else broadcastMessage e + +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) + +setNumlockMask :: X () +setNumlockMask = do + dpy <- asks display + ms <- io $ getModifierMapping dpy + xs <- sequence [ do + ks <- io $ keycodeToKeysym dpy kc 0 + if ks == xK_Num_Lock + then return (setBit 0 (fromIntegral m)) + else return (0 :: KeyMask) + | (m, kcs) <- ms, kc <- kcs, kc /= 0] + modify (\s -> s { numberlockMask = foldr (.|.) 0 xs }) + +-- | 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 + (minCode, maxCode) = displayKeycodes dpy + allCodes = [fromIntegral minCode .. fromIntegral maxCode] + io $ ungrabKey dpy anyKey anyModifier rootw + ks <- asks keyActions + -- build a map from keysyms to lists of keysyms (doing what + -- XGetKeyboardMapping would do if the X11 package bound it) + syms <- forM allCodes $ \code -> io (keycodeToKeysym dpy code 0) + let keysymMap = M.fromListWith (++) (zip syms [[code] | code <- allCodes]) + keysymToKeycodes sym = M.findWithDefault [] sym keysymMap + forM_ (M.keys ks) $ \(mask,sym) -> + forM_ (keysymToKeycodes sym) $ \kc -> + 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) + +-- | @replace@ to signals compliant window managers to exit. +replace :: Display -> ScreenNumber -> Window -> IO () +replace dpy dflt rootw = do + -- check for other WM + wmSnAtom <- internAtom dpy ("WM_S" ++ show dflt) False + currentWmSnOwner <- xGetSelectionOwner dpy wmSnAtom + when (currentWmSnOwner /= 0) $ do + -- prepare to receive destroyNotify for old WM + selectInput dpy currentWmSnOwner structureNotifyMask + + -- create off-screen window + netWmSnOwner <- allocaSetWindowAttributes $ \attributes -> do + set_override_redirect attributes True + set_event_mask attributes propertyChangeMask + let screen = defaultScreenOfDisplay dpy + visual = defaultVisualOfScreen screen + attrmask = cWOverrideRedirect .|. cWEventMask + createWindow dpy rootw (-100) (-100) 1 1 0 copyFromParent copyFromParent visual attrmask attributes + + -- try to acquire wmSnAtom, this should signal the old WM to terminate + xSetSelectionOwner dpy wmSnAtom netWmSnOwner currentTime + + -- SKIPPED: check if we acquired the selection + -- SKIPPED: send client message indicating that we are now the WM + + -- wait for old WM to go away + fix $ \again -> do + evt <- allocaXEvent $ \event -> do + windowEvent dpy currentWmSnOwner structureNotifyMask event + get_EventType event + + when (evt /= destroyNotify) again diff --git a/src/XMonad/ManageHook.hs b/src/XMonad/ManageHook.hs new file mode 100644 index 0000000..aa5ae32 --- /dev/null +++ b/src/XMonad/ManageHook.hs @@ -0,0 +1,119 @@ +{-# LANGUAGE GeneralizedNewtypeDeriving #-} + +----------------------------------------------------------------------------- +-- | +-- Module : XMonad.ManageHook +-- Copyright : (c) Spencer Janssen 2007 +-- License : BSD3-style (see LICENSE) +-- +-- Maintainer : spencerjanssen@gmail.com +-- Stability : unstable +-- Portability : not portable, uses cunning newtype deriving +-- +-- An EDSL for ManageHooks +-- +----------------------------------------------------------------------------- + +-- XXX examples required + +module XMonad.ManageHook where + +import XMonad.Core +import Graphics.X11.Xlib.Extras +import Graphics.X11.Xlib (Display, Window, internAtom, wM_NAME) +import Control.Exception.Extensible (bracket, SomeException(..)) +import qualified Control.Exception.Extensible as E +import Control.Monad.Reader +import Data.Maybe +import Data.Monoid +import qualified XMonad.StackSet as W +import XMonad.Operations (floatLocation, reveal) + +-- | Lift an 'X' action to a 'Query'. +liftX :: X a -> Query a +liftX = Query . lift + +-- | The identity hook that returns the WindowSet unchanged. +idHook :: Monoid m => m +idHook = mempty + +-- | Infix 'mappend'. Compose two 'ManageHook' from right to left. +(<+>) :: Monoid m => m -> m -> m +(<+>) = mappend + +-- | Compose the list of 'ManageHook's. +composeAll :: Monoid m => [m] -> m +composeAll = mconcat + +infix 0 --> + +-- | @p --> x@. If @p@ returns 'True', execute the 'ManageHook'. +-- +-- > (-->) :: Monoid m => Query Bool -> Query m -> Query m -- a simpler type +(-->) :: (Monad m, Monoid a) => m Bool -> m a -> m a +p --> f = p >>= \b -> if b then f else return mempty + +-- | @q =? x@. if the result of @q@ equals @x@, return 'True'. +(=?) :: Eq a => Query a -> a -> Query Bool +q =? x = fmap (== x) q + +infixr 3 <&&>, <||> + +-- | '&&' lifted to a 'Monad'. +(<&&>) :: Monad m => m Bool -> m Bool -> m Bool +(<&&>) = liftM2 (&&) + +-- | '||' lifted to a 'Monad'. +(<||>) :: Monad m => m Bool -> m Bool -> m Bool +(<||>) = liftM2 (||) + +-- | Return the window title. +title :: Query String +title = ask >>= \w -> liftX $ do + d <- asks display + let + getProp = + (internAtom d "_NET_WM_NAME" False >>= getTextProperty d w) + `E.catch` \(SomeException _) -> getTextProperty d w wM_NAME + extract prop = do l <- wcTextPropertyToTextList d prop + return $ if null l then "" else head l + io $ bracket getProp (xFree . tp_value) extract `E.catch` \(SomeException _) -> return "" + +-- | Return the application name. +appName :: Query String +appName = ask >>= (\w -> liftX $ withDisplay $ \d -> fmap resName $ io $ getClassHint d w) + +-- | Backwards compatible alias for 'appName'. +resource :: Query String +resource = appName + +-- | Return the resource class. +className :: Query String +className = ask >>= (\w -> liftX $ withDisplay $ \d -> fmap resClass $ io $ getClassHint d w) + +-- | A query that can return an arbitrary X property of type 'String', +-- identified by name. +stringProperty :: String -> Query String +stringProperty p = ask >>= (\w -> liftX $ withDisplay $ \d -> fmap (fromMaybe "") $ getStringProperty d w p) + +getStringProperty :: Display -> Window -> String -> X (Maybe String) +getStringProperty d w p = do + a <- getAtom p + md <- io $ getWindowProperty8 d a w + return $ fmap (map (toEnum . fromIntegral)) md + +-- | Modify the 'WindowSet' with a pure function. +doF :: (s -> s) -> Query (Endo s) +doF = return . Endo + +-- | Move the window to the floating layer. +doFloat :: ManageHook +doFloat = ask >>= \w -> doF . W.float w . snd =<< liftX (floatLocation w) + +-- | Map the window and remove it from the 'WindowSet'. +doIgnore :: ManageHook +doIgnore = ask >>= \w -> liftX (reveal w) >> doF (W.delete w) + +-- | Move the window to a given workspace +doShift :: WorkspaceId -> ManageHook +doShift i = doF . W.shiftWin i =<< ask diff --git a/src/XMonad/Operations.hs b/src/XMonad/Operations.hs new file mode 100644 index 0000000..41fbed0 --- /dev/null +++ b/src/XMonad/Operations.hs @@ -0,0 +1,586 @@ +{-# OPTIONS_GHC -fno-warn-orphans #-} +{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, PatternGuards, TypeSynonymInstances #-} + +-- -------------------------------------------------------------------------- +-- | +-- Module : XMonad.Operations +-- Copyright : (c) Spencer Janssen 2007 +-- License : BSD3-style (see LICENSE) +-- +-- Maintainer : dons@cse.unsw.edu.au +-- Stability : unstable +-- Portability : not portable, Typeable deriving, mtl, posix +-- +-- Operations. +-- +----------------------------------------------------------------------------- + +module XMonad.Operations where + +import XMonad.Core +import XMonad.Layout (Full(..)) +import qualified XMonad.StackSet as W + +import Data.Maybe +import Data.Monoid (Endo(..)) +import Data.List (nub, (\\), find) +import Data.Bits ((.|.), (.&.), complement, testBit) +import Data.Ratio +import qualified Data.Map as M +import qualified Data.Set as S + +import Control.Applicative +import Control.Monad.Reader +import Control.Monad.State +import qualified Control.Exception.Extensible as C + +import System.Posix.Process (executeFile) +import Graphics.X11.Xlib +import Graphics.X11.Xinerama (getScreenInfo) +import Graphics.X11.Xlib.Extras + +-- --------------------------------------------------------------------- +-- | +-- Window manager operations +-- manage. Add a new window to be managed in the current workspace. +-- Bring it into focus. +-- +-- Whether the window is already managed, or not, it is mapped, has its +-- border set, and its event mask set. +-- +manage :: Window -> X () +manage w = whenX (not <$> isClient w) $ withDisplay $ \d -> do + sh <- io $ getWMNormalHints d w + + let isFixedSize = sh_min_size sh /= Nothing && sh_min_size sh == sh_max_size sh + isTransient <- isJust <$> io (getTransientForHint d w) + + rr <- snd `fmap` floatLocation w + -- ensure that float windows don't go over the edge of the screen + let adjust (W.RationalRect x y wid h) | x + wid > 1 || y + h > 1 || x < 0 || y < 0 + = W.RationalRect (0.5 - wid/2) (0.5 - h/2) wid h + adjust r = r + + f ws | isFixedSize || isTransient = W.float w (adjust rr) . W.insertUp w . W.view i $ ws + | otherwise = W.insertUp w ws + where i = W.tag $ W.workspace $ W.current ws + + mh <- asks (manageHook . config) + g <- appEndo <$> userCodeDef (Endo id) (runQuery mh w) + windows (g . f) + +-- | unmanage. A window no longer exists, remove it from the window +-- list, on whatever workspace it is. +-- +unmanage :: Window -> X () +unmanage = windows . W.delete + +-- | Kill the specified window. If we do kill it, we'll get a +-- delete notify back from X. +-- +-- There are two ways to delete a window. Either just kill it, or if it +-- supports the delete protocol, send a delete event (e.g. firefox) +-- +killWindow :: Window -> X () +killWindow w = withDisplay $ \d -> do + wmdelt <- atom_WM_DELETE_WINDOW ; wmprot <- atom_WM_PROTOCOLS + + protocols <- io $ getWMProtocols d w + io $ if wmdelt `elem` protocols + then allocaXEvent $ \ev -> do + setEventType ev clientMessage + setClientMessageEvent ev w wmprot 32 wmdelt 0 + sendEvent d w False noEventMask ev + else killClient d w >> return () + +-- | Kill the currently focused client. +kill :: X () +kill = withFocused killWindow + +-- --------------------------------------------------------------------- +-- Managing windows + +-- | windows. Modify the current window list with a pure function, and refresh +windows :: (WindowSet -> WindowSet) -> X () +windows f = do + XState { windowset = old } <- get + let oldvisible = concatMap (W.integrate' . W.stack . W.workspace) $ W.current old : W.visible old + newwindows = W.allWindows ws \\ W.allWindows old + ws = f old + XConf { display = d , normalBorder = nbc, focusedBorder = fbc } <- ask + + mapM_ setInitialProperties newwindows + + whenJust (W.peek old) $ \otherw -> io $ setWindowBorder d otherw nbc + modify (\s -> s { windowset = ws }) + + -- notify non visibility + let tags_oldvisible = map (W.tag . W.workspace) $ W.current old : W.visible old + gottenhidden = filter (flip elem tags_oldvisible . W.tag) $ W.hidden ws + mapM_ (sendMessageWithNoRefresh Hide) gottenhidden + + -- for each workspace, layout the currently visible workspaces + let allscreens = W.screens ws + summed_visible = scanl (++) [] $ map (W.integrate' . W.stack . W.workspace) allscreens + rects <- fmap concat $ forM (zip allscreens summed_visible) $ \ (w, vis) -> do + let wsp = W.workspace w + this = W.view n ws + n = W.tag wsp + tiled = (W.stack . W.workspace . W.current $ this) + >>= W.filter (`M.notMember` W.floating ws) + >>= W.filter (`notElem` vis) + viewrect = screenRect $ W.screenDetail w + + -- just the tiled windows: + -- now tile the windows on this workspace, modified by the gap + (rs, ml') <- runLayout wsp { W.stack = tiled } viewrect `catchX` + runLayout wsp { W.stack = tiled, W.layout = Layout Full } viewrect + updateLayout n ml' + + let m = W.floating ws + flt = [(fw, scaleRationalRect viewrect r) + | fw <- filter (flip M.member m) (W.index this) + , Just r <- [M.lookup fw m]] + vs = flt ++ rs + + io $ restackWindows d (map fst vs) + -- return the visible windows for this workspace: + return vs + + let visible = map fst rects + + mapM_ (uncurry tileWindow) rects + + whenJust (W.peek ws) $ \w -> io $ setWindowBorder d w fbc + + mapM_ reveal visible + setTopFocus + + -- hide every window that was potentially visible before, but is not + -- given a position by a layout now. + mapM_ hide (nub (oldvisible ++ newwindows) \\ visible) + + -- all windows that are no longer in the windowset are marked as + -- withdrawn, it is important to do this after the above, otherwise 'hide' + -- will overwrite withdrawnState with iconicState + mapM_ (flip setWMState withdrawnState) (W.allWindows old \\ W.allWindows ws) + + isMouseFocused <- asks mouseFocused + unless isMouseFocused $ clearEvents enterWindowMask + asks (logHook . config) >>= userCodeDef () + +-- | Produce the actual rectangle from a screen and a ratio on that screen. +scaleRationalRect :: Rectangle -> W.RationalRect -> Rectangle +scaleRationalRect (Rectangle sx sy sw sh) (W.RationalRect rx ry rw rh) + = Rectangle (sx + scale sw rx) (sy + scale sh ry) (scale sw rw) (scale sh rh) + where scale s r = floor (toRational s * r) + +-- | setWMState. set the WM_STATE property +setWMState :: Window -> Int -> X () +setWMState w v = withDisplay $ \dpy -> do + a <- atom_WM_STATE + io $ changeProperty32 dpy w a a propModeReplace [fromIntegral v, fromIntegral none] + +-- | hide. Hide a window by unmapping it, and setting Iconified. +hide :: Window -> X () +hide w = whenX (gets (S.member w . mapped)) $ withDisplay $ \d -> do + cMask <- asks $ clientMask . config + io $ do selectInput d w (cMask .&. complement structureNotifyMask) + unmapWindow d w + selectInput d w cMask + setWMState w iconicState + -- this part is key: we increment the waitingUnmap counter to distinguish + -- between client and xmonad initiated unmaps. + modify (\s -> s { waitingUnmap = M.insertWith (+) w 1 (waitingUnmap s) + , mapped = S.delete w (mapped s) }) + +-- | reveal. Show a window by mapping it and setting Normal +-- this is harmless if the window was already visible +reveal :: Window -> X () +reveal w = withDisplay $ \d -> do + setWMState w normalState + io $ mapWindow d w + whenX (isClient w) $ modify (\s -> s { mapped = S.insert w (mapped s) }) + +-- | Set some properties when we initially gain control of a window +setInitialProperties :: Window -> X () +setInitialProperties w = asks normalBorder >>= \nb -> withDisplay $ \d -> do + setWMState w iconicState + asks (clientMask . config) >>= io . selectInput d w + bw <- asks (borderWidth . config) + io $ setWindowBorderWidth d w bw + -- we must initially set the color of new windows, to maintain invariants + -- required by the border setting in 'windows' + io $ setWindowBorder d w nb + +-- | refresh. Render the currently visible workspaces, as determined by +-- the 'StackSet'. Also, set focus to the focused window. +-- +-- This is our 'view' operation (MVC), in that it pretty prints our model +-- with X calls. +-- +refresh :: X () +refresh = windows id + +-- | clearEvents. Remove all events of a given type from the event queue. +clearEvents :: EventMask -> X () +clearEvents mask = withDisplay $ \d -> io $ do + sync d False + allocaXEvent $ \p -> fix $ \again -> do + more <- checkMaskEvent d mask p + when more again -- beautiful + +-- | tileWindow. Moves and resizes w such that it fits inside the given +-- rectangle, including its border. +tileWindow :: Window -> Rectangle -> X () +tileWindow w r = withDisplay $ \d -> do + bw <- (fromIntegral . wa_border_width) <$> io (getWindowAttributes d w) + -- give all windows at least 1x1 pixels + let least x | x <= bw*2 = 1 + | otherwise = x - bw*2 + io $ moveResizeWindow d w (rect_x r) (rect_y r) + (least $ rect_width r) (least $ rect_height r) + +-- --------------------------------------------------------------------- + +-- | Returns 'True' if the first rectangle is contained within, but not equal +-- to the second. +containedIn :: Rectangle -> Rectangle -> Bool +containedIn r1@(Rectangle x1 y1 w1 h1) r2@(Rectangle x2 y2 w2 h2) + = and [ r1 /= r2 + , x1 >= x2 + , y1 >= y2 + , fromIntegral x1 + w1 <= fromIntegral x2 + w2 + , fromIntegral y1 + h1 <= fromIntegral y2 + h2 ] + +-- | Given a list of screens, remove all duplicated screens and screens that +-- are entirely contained within another. +nubScreens :: [Rectangle] -> [Rectangle] +nubScreens xs = nub . filter (\x -> not $ any (x `containedIn`) xs) $ xs + +-- | Cleans the list of screens according to the rules documented for +-- nubScreens. +getCleanedScreenInfo :: MonadIO m => Display -> m [Rectangle] +getCleanedScreenInfo = io . fmap nubScreens . getScreenInfo + +-- | rescreen. The screen configuration may have changed (due to +-- xrandr), update the state and refresh the screen, and reset the gap. +rescreen :: X () +rescreen = do + xinesc <- withDisplay getCleanedScreenInfo + + windows $ \ws@(W.StackSet { W.current = v, W.visible = vs, W.hidden = hs }) -> + let (xs, ys) = splitAt (length xinesc) $ map W.workspace (v:vs) ++ hs + (a:as) = zipWith3 W.Screen xs [0..] $ map SD xinesc + in ws { W.current = a + , W.visible = as + , W.hidden = ys } + +-- --------------------------------------------------------------------- + +-- | setButtonGrab. Tell whether or not to intercept clicks on a given window +setButtonGrab :: Bool -> Window -> X () +setButtonGrab grab w = do + pointerMode <- asks $ \c -> if clickJustFocuses (config c) + then grabModeAsync + else grabModeSync + withDisplay $ \d -> io $ if grab + then forM_ [button1, button2, button3] $ \b -> + grabButton d b anyModifier w False buttonPressMask + pointerMode grabModeSync none none + else ungrabButton d anyButton anyModifier w + +-- --------------------------------------------------------------------- +-- Setting keyboard focus + +-- | Set the focus to the window on top of the stack, or root +setTopFocus :: X () +setTopFocus = withWindowSet $ maybe (setFocusX =<< asks theRoot) setFocusX . W.peek + +-- | Set focus explicitly to window 'w' if it is managed by us, or root. +-- This happens if X notices we've moved the mouse (and perhaps moved +-- the mouse to a new screen). +focus :: Window -> X () +focus w = local (\c -> c { mouseFocused = True }) $ withWindowSet $ \s -> do + let stag = W.tag . W.workspace + curr = stag $ W.current s + mnew <- maybe (return Nothing) (fmap (fmap stag) . uncurry pointScreen) + =<< asks mousePosition + root <- asks theRoot + case () of + _ | W.member w s && W.peek s /= Just w -> windows (W.focusWindow w) + | Just new <- mnew, w == root && curr /= new + -> windows (W.view new) + | otherwise -> return () + +-- | Call X to set the keyboard focus details. +setFocusX :: Window -> X () +setFocusX w = withWindowSet $ \ws -> do + dpy <- asks display + + -- clear mouse button grab and border on other windows + forM_ (W.current ws : W.visible ws) $ \wk -> + forM_ (W.index (W.view (W.tag (W.workspace wk)) ws)) $ \otherw -> + setButtonGrab True otherw + + -- If we ungrab buttons on the root window, we lose our mouse bindings. + whenX (not <$> isRoot w) $ setButtonGrab False w + + hints <- io $ getWMHints dpy w + protocols <- io $ getWMProtocols dpy w + wmprot <- atom_WM_PROTOCOLS + wmtf <- atom_WM_TAKE_FOCUS + currevt <- asks currentEvent + let inputHintSet = wmh_flags hints `testBit` inputHintBit + + when ((inputHintSet && wmh_input hints) || (not inputHintSet)) $ + io $ do setInputFocus dpy w revertToPointerRoot 0 + when (wmtf `elem` protocols) $ + io $ allocaXEvent $ \ev -> do + setEventType ev clientMessage + setClientMessageEvent ev w wmprot 32 wmtf $ maybe currentTime event_time currevt + sendEvent dpy w False noEventMask ev + where event_time ev = + if (ev_event_type ev) `elem` timedEvents then + ev_time ev + else + currentTime + timedEvents = [ keyPress, keyRelease, buttonPress, buttonRelease, enterNotify, leaveNotify, selectionRequest ] + +------------------------------------------------------------------------ +-- Message handling + +-- | Throw a message to the current 'LayoutClass' possibly modifying how we +-- layout the windows, then refresh. +sendMessage :: Message a => a -> X () +sendMessage a = do + w <- W.workspace . W.current <$> gets windowset + ml' <- handleMessage (W.layout w) (SomeMessage a) `catchX` return Nothing + whenJust ml' $ \l' -> + windows $ \ws -> ws { W.current = (W.current ws) + { W.workspace = (W.workspace $ W.current ws) + { W.layout = l' }}} + +-- | Send a message to all layouts, without refreshing. +broadcastMessage :: Message a => a -> X () +broadcastMessage a = withWindowSet $ \ws -> do + let c = W.workspace . W.current $ ws + v = map W.workspace . W.visible $ ws + h = W.hidden ws + mapM_ (sendMessageWithNoRefresh a) (c : v ++ h) + +-- | Send a message to a layout, without refreshing. +sendMessageWithNoRefresh :: Message a => a -> W.Workspace WorkspaceId (Layout Window) Window -> X () +sendMessageWithNoRefresh a w = + handleMessage (W.layout w) (SomeMessage a) `catchX` return Nothing >>= + updateLayout (W.tag w) + +-- | Update the layout field of a workspace +updateLayout :: WorkspaceId -> Maybe (Layout Window) -> X () +updateLayout i ml = whenJust ml $ \l -> + runOnWorkspaces $ \ww -> return $ if W.tag ww == i then ww { W.layout = l} else ww + +-- | Set the layout of the currently viewed workspace +setLayout :: Layout Window -> X () +setLayout l = do + ss@(W.StackSet { W.current = c@(W.Screen { W.workspace = ws })}) <- gets windowset + handleMessage (W.layout ws) (SomeMessage ReleaseResources) + windows $ const $ ss {W.current = c { W.workspace = ws { W.layout = l } } } + +------------------------------------------------------------------------ +-- Utilities + +-- | Return workspace visible on screen 'sc', or 'Nothing'. +screenWorkspace :: ScreenId -> X (Maybe WorkspaceId) +screenWorkspace sc = withWindowSet $ return . W.lookupWorkspace sc + +-- | Apply an 'X' operation to the currently focused window, if there is one. +withFocused :: (Window -> X ()) -> X () +withFocused f = withWindowSet $ \w -> whenJust (W.peek w) f + +-- | 'True' if window is under management by us +isClient :: Window -> X Bool +isClient w = withWindowSet $ return . W.member w + +-- | Combinations of extra modifier masks we need to grab keys\/buttons for. +-- (numlock and capslock) +extraModifiers :: X [KeyMask] +extraModifiers = do + nlm <- gets numberlockMask + return [0, nlm, lockMask, nlm .|. lockMask ] + +-- | Strip numlock\/capslock from a mask +cleanMask :: KeyMask -> X KeyMask +cleanMask km = do + nlm <- gets numberlockMask + return (complement (nlm .|. lockMask) .&. km) + +-- | Get the 'Pixel' value for a named color +initColor :: Display -> String -> IO (Maybe Pixel) +initColor dpy c = C.handle (\(C.SomeException _) -> return Nothing) $ + (Just . color_pixel . fst) <$> allocNamedColor dpy colormap c + where colormap = defaultColormap dpy (defaultScreen dpy) + +------------------------------------------------------------------------ + +-- | @restart name resume@. Attempt to restart xmonad by executing the program +-- @name@. If @resume@ is 'True', restart with the current window state. +-- When executing another window manager, @resume@ should be 'False'. +restart :: String -> Bool -> X () +restart prog resume = do + broadcastMessage ReleaseResources + io . flush =<< asks display + let wsData = show . W.mapLayout show . windowset + maybeShow (t, Right (PersistentExtension ext)) = Just (t, show ext) + maybeShow (t, Left str) = Just (t, str) + maybeShow _ = Nothing + extState = return . show . catMaybes . map maybeShow . M.toList . extensibleState + args <- if resume then gets (\s -> "--resume":wsData s:extState s) else return [] + catchIO (executeFile prog True args Nothing) + +------------------------------------------------------------------------ +-- | Floating layer support + +-- | Given a window, find the screen it is located on, and compute +-- the geometry of that window wrt. that screen. +floatLocation :: Window -> X (ScreenId, W.RationalRect) +floatLocation w = withDisplay $ \d -> do + ws <- gets windowset + wa <- io $ getWindowAttributes d w + let bw = (fromIntegral . wa_border_width) wa + sc <- fromMaybe (W.current ws) <$> pointScreen (fi $ wa_x wa) (fi $ wa_y wa) + + let sr = screenRect . W.screenDetail $ sc + rr = W.RationalRect ((fi (wa_x wa) - fi (rect_x sr)) % fi (rect_width sr)) + ((fi (wa_y wa) - fi (rect_y sr)) % fi (rect_height sr)) + (fi (wa_width wa + bw*2) % fi (rect_width sr)) + (fi (wa_height wa + bw*2) % fi (rect_height sr)) + + return (W.screen sc, rr) + where fi x = fromIntegral x + +-- | Given a point, determine the screen (if any) that contains it. +pointScreen :: Position -> Position + -> X (Maybe (W.Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail)) +pointScreen x y = withWindowSet $ return . find p . W.screens + where p = pointWithin x y . screenRect . W.screenDetail + +-- | @pointWithin x y r@ returns 'True' if the @(x, y)@ co-ordinate is within +-- @r@. +pointWithin :: Position -> Position -> Rectangle -> Bool +pointWithin x y r = x >= rect_x r && + x < rect_x r + fromIntegral (rect_width r) && + y >= rect_y r && + y < rect_y r + fromIntegral (rect_height r) + +-- | Make a tiled window floating, using its suggested rectangle +float :: Window -> X () +float w = do + (sc, rr) <- floatLocation w + windows $ \ws -> W.float w rr . fromMaybe ws $ do + i <- W.findTag w ws + guard $ i `elem` map (W.tag . W.workspace) (W.screens ws) + f <- W.peek ws + sw <- W.lookupWorkspace sc ws + return (W.focusWindow f . W.shiftWin sw w $ ws) + +-- --------------------------------------------------------------------- +-- Mouse handling + +-- | Accumulate mouse motion events +mouseDrag :: (Position -> Position -> X ()) -> X () -> X () +mouseDrag f done = do + drag <- gets dragging + case drag of + Just _ -> return () -- error case? we're already dragging + Nothing -> do + XConf { theRoot = root, display = d } <- ask + io $ grabPointer d root False (buttonReleaseMask .|. pointerMotionMask) + grabModeAsync grabModeAsync none none currentTime + modify $ \s -> s { dragging = Just (motion, cleanup) } + where + cleanup = do + withDisplay $ io . flip ungrabPointer currentTime + modify $ \s -> s { dragging = Nothing } + done + motion x y = do z <- f x y + clearEvents pointerMotionMask + return z + +-- | XXX comment me +mouseMoveWindow :: Window -> X () +mouseMoveWindow w = whenX (isClient w) $ withDisplay $ \d -> do + io $ raiseWindow d w + wa <- io $ getWindowAttributes d w + (_, _, _, ox', oy', _, _, _) <- io $ queryPointer d w + let ox = fromIntegral ox' + oy = fromIntegral oy' + mouseDrag (\ex ey -> io $ moveWindow d w (fromIntegral (fromIntegral (wa_x wa) + (ex - ox))) + (fromIntegral (fromIntegral (wa_y wa) + (ey - oy)))) + (float w) + +-- | XXX comment me +mouseResizeWindow :: Window -> X () +mouseResizeWindow w = whenX (isClient w) $ withDisplay $ \d -> do + io $ raiseWindow d w + wa <- io $ getWindowAttributes d w + sh <- io $ getWMNormalHints d w + io $ warpPointer d none w 0 0 0 0 (fromIntegral (wa_width wa)) (fromIntegral (wa_height wa)) + mouseDrag (\ex ey -> + io $ resizeWindow d w `uncurry` + applySizeHintsContents sh (ex - fromIntegral (wa_x wa), + ey - fromIntegral (wa_y wa))) + (float w) + +-- --------------------------------------------------------------------- +-- | Support for window size hints + +type D = (Dimension, Dimension) + +-- | Given a window, build an adjuster function that will reduce the given +-- dimensions according to the window's border width and size hints. +mkAdjust :: Window -> X (D -> D) +mkAdjust w = withDisplay $ \d -> liftIO $ do + sh <- getWMNormalHints d w + bw <- fmap (fromIntegral . wa_border_width) $ getWindowAttributes d w + return $ applySizeHints bw sh + +-- | Reduce the dimensions if needed to comply to the given SizeHints, taking +-- window borders into account. +applySizeHints :: Integral a => Dimension -> SizeHints -> (a, a) -> D +applySizeHints bw sh = + tmap (+ 2 * bw) . applySizeHintsContents sh . tmap (subtract $ 2 * fromIntegral bw) + where + tmap f (x, y) = (f x, f y) + +-- | Reduce the dimensions if needed to comply to the given SizeHints. +applySizeHintsContents :: Integral a => SizeHints -> (a, a) -> D +applySizeHintsContents sh (w, h) = + applySizeHints' sh (fromIntegral $ max 1 w, fromIntegral $ max 1 h) + +-- | XXX comment me +applySizeHints' :: SizeHints -> D -> D +applySizeHints' sh = + maybe id applyMaxSizeHint (sh_max_size sh) + . maybe id (\(bw, bh) (w, h) -> (w+bw, h+bh)) (sh_base_size sh) + . maybe id applyResizeIncHint (sh_resize_inc sh) + . maybe id applyAspectHint (sh_aspect sh) + . maybe id (\(bw,bh) (w,h) -> (w-bw, h-bh)) (sh_base_size sh) + +-- | Reduce the dimensions so their aspect ratio falls between the two given aspect ratios. +applyAspectHint :: (D, D) -> D -> D +applyAspectHint ((minx, miny), (maxx, maxy)) x@(w,h) + | or [minx < 1, miny < 1, maxx < 1, maxy < 1] = x + | w * maxy > h * maxx = (h * maxx `div` maxy, h) + | w * miny < h * minx = (w, w * miny `div` minx) + | otherwise = x + +-- | Reduce the dimensions so they are a multiple of the size increments. +applyResizeIncHint :: D -> D -> D +applyResizeIncHint (iw,ih) x@(w,h) = + if iw > 0 && ih > 0 then (w - w `mod` iw, h - h `mod` ih) else x + +-- | Reduce the dimensions if they exceed the given maximum dimensions. +applyMaxSizeHint :: D -> D -> D +applyMaxSizeHint (mw,mh) x@(w,h) = + if mw > 0 && mh > 0 then (min w mw,min h mh) else x diff --git a/src/XMonad/StackSet.hs b/src/XMonad/StackSet.hs new file mode 100644 index 0000000..a7e9f6b --- /dev/null +++ b/src/XMonad/StackSet.hs @@ -0,0 +1,558 @@ +{-# LANGUAGE PatternGuards #-} + +----------------------------------------------------------------------------- +-- | +-- Module : XMonad.StackSet +-- Copyright : (c) Don Stewart 2007 +-- License : BSD3-style (see LICENSE) +-- +-- Maintainer : dons@galois.com +-- Stability : experimental +-- Portability : portable, Haskell 98 +-- + +module XMonad.StackSet ( + -- * Introduction + -- $intro + + -- ** The Zipper + -- $zipper + + -- ** Xinerama support + -- $xinerama + + -- ** Master and Focus + -- $focus + + StackSet(..), Workspace(..), Screen(..), Stack(..), RationalRect(..), + -- * Construction + -- $construction + new, view, greedyView, + -- * Xinerama operations + -- $xinerama + lookupWorkspace, + screens, workspaces, allWindows, currentTag, + -- * Operations on the current stack + -- $stackOperations + peek, index, integrate, integrate', differentiate, + focusUp, focusDown, focusUp', focusDown', focusMaster, focusWindow, + tagMember, renameTag, ensureTags, member, findTag, mapWorkspace, mapLayout, + -- * Modifying the stackset + -- $modifyStackset + insertUp, delete, delete', filter, + -- * Setting the master window + -- $settingMW + swapUp, swapDown, swapMaster, shiftMaster, modify, modify', float, sink, -- needed by users + -- * Composite operations + -- $composite + shift, shiftWin, + + -- for testing + abort + ) where + +import Prelude hiding (filter) +import Data.Maybe (listToMaybe,isJust,fromMaybe) +import qualified Data.List as L (deleteBy,find,splitAt,filter,nub) +import Data.List ( (\\) ) +import qualified Data.Map as M (Map,insert,delete,empty) + +-- $intro +-- +-- The 'StackSet' data type encodes a window manager abstraction. The +-- window manager is a set of virtual workspaces. On each workspace is a +-- stack of windows. A given workspace is always current, and a given +-- window on each workspace has focus. The focused window on the current +-- workspace is the one which will take user input. It can be visualised +-- as follows: +-- +-- > Workspace { 0*} { 1 } { 2 } { 3 } { 4 } +-- > +-- > Windows [1 [] [3* [6*] [] +-- > ,2*] ,4 +-- > ,5] +-- +-- Note that workspaces are indexed from 0, windows are numbered +-- uniquely. A '*' indicates the window on each workspace that has +-- focus, and which workspace is current. + +-- $zipper +-- +-- We encode all the focus tracking directly in the data structure, with a 'zipper': +-- +-- A Zipper is essentially an `updateable' and yet pure functional +-- cursor into a data structure. Zipper is also a delimited +-- continuation reified as a data structure. +-- +-- The Zipper lets us replace an item deep in a complex data +-- structure, e.g., a tree or a term, without an mutation. The +-- resulting data structure will share as much of its components with +-- the old structure as possible. +-- +-- Oleg Kiselyov, 27 Apr 2005, haskell\@, "Zipper as a delimited continuation" +-- +-- We use the zipper to keep track of the focused workspace and the +-- focused window on each workspace, allowing us to have correct focus +-- by construction. We closely follow Huet's original implementation: +-- +-- G. Huet, /Functional Pearl: The Zipper/, +-- 1997, J. Functional Programming 75(5):549-554. +-- and: +-- R. Hinze and J. Jeuring, /Functional Pearl: The Web/. +-- +-- and Conor McBride's zipper differentiation paper. +-- Another good reference is: +-- +-- The Zipper, Haskell wikibook + +-- $xinerama +-- Xinerama in X11 lets us view multiple virtual workspaces +-- simultaneously. While only one will ever be in focus (i.e. will +-- receive keyboard events), other workspaces may be passively +-- viewable. We thus need to track which virtual workspaces are +-- associated (viewed) on which physical screens. To keep track of +-- this, 'StackSet' keeps separate lists of visible but non-focused +-- workspaces, and non-visible workspaces. + +-- $focus +-- +-- Each stack tracks a focused item, and for tiling purposes also tracks +-- a 'master' position. The connection between 'master' and 'focus' +-- needs to be well defined, particularly in relation to 'insert' and +-- 'delete'. +-- + +------------------------------------------------------------------------ +-- | +-- A cursor into a non-empty list of workspaces. +-- +-- We puncture the workspace list, producing a hole in the structure +-- used to track the currently focused workspace. The two other lists +-- that are produced are used to track those workspaces visible as +-- Xinerama screens, and those workspaces not visible anywhere. + +data StackSet i l a sid sd = + StackSet { current :: !(Screen i l a sid sd) -- ^ currently focused workspace + , visible :: [Screen i l a sid sd] -- ^ non-focused workspaces, visible in xinerama + , hidden :: [Workspace i l a] -- ^ workspaces not visible anywhere + , floating :: M.Map a RationalRect -- ^ floating windows + } deriving (Show, Read, Eq) + +-- | Visible workspaces, and their Xinerama screens. +data Screen i l a sid sd = Screen { workspace :: !(Workspace i l a) + , screen :: !sid + , screenDetail :: !sd } + deriving (Show, Read, Eq) + +-- | +-- A workspace is just a tag, a layout, and a stack. +-- +data Workspace i l a = Workspace { tag :: !i, layout :: l, stack :: Maybe (Stack a) } + deriving (Show, Read, Eq) + +-- | A structure for window geometries +data RationalRect = RationalRect Rational Rational Rational Rational + deriving (Show, Read, Eq) + +-- | +-- A stack is a cursor onto a window list. +-- The data structure tracks focus by construction, and +-- the master window is by convention the top-most item. +-- Focus operations will not reorder the list that results from +-- flattening the cursor. The structure can be envisaged as: +-- +-- > +-- master: < '7' > +-- > up | [ '2' ] +-- > +--------- [ '3' ] +-- > focus: < '4' > +-- > dn +----------- [ '8' ] +-- +-- A 'Stack' can be viewed as a list with a hole punched in it to make +-- the focused position. Under the zipper\/calculus view of such +-- structures, it is the differentiation of a [a], and integrating it +-- back has a natural implementation used in 'index'. +-- +data Stack a = Stack { focus :: !a -- focused thing in this set + , up :: [a] -- clowns to the left + , down :: [a] } -- jokers to the right + deriving (Show, Read, Eq) + + +-- | this function indicates to catch that an error is expected +abort :: String -> a +abort x = error $ "xmonad: StackSet: " ++ x + +-- --------------------------------------------------------------------- +-- $construction + +-- | /O(n)/. Create a new stackset, of empty stacks, with given tags, +-- with physical screens whose descriptions are given by 'm'. The +-- number of physical screens (@length 'm'@) should be less than or +-- equal to the number of workspace tags. The first workspace in the +-- list will be current. +-- +-- Xinerama: Virtual workspaces are assigned to physical screens, starting at 0. +-- +new :: (Integral s) => l -> [i] -> [sd] -> StackSet i l a s sd +new l wids m | not (null wids) && length m <= length wids && not (null m) + = StackSet cur visi unseen M.empty + where (seen,unseen) = L.splitAt (length m) $ map (\i -> Workspace i l Nothing) wids + (cur:visi) = [ Screen i s sd | (i, s, sd) <- zip3 seen [0..] m ] + -- now zip up visibles with their screen id +new _ _ _ = abort "non-positive argument to StackSet.new" + +-- | +-- /O(w)/. Set focus to the workspace with index \'i\'. +-- If the index is out of range, return the original 'StackSet'. +-- +-- Xinerama: If the workspace is not visible on any Xinerama screen, it +-- becomes the current screen. If it is in the visible list, it becomes +-- current. + +view :: (Eq s, Eq i) => i -> StackSet i l a s sd -> StackSet i l a s sd +view i s + | i == currentTag s = s -- current + + | Just x <- L.find ((i==).tag.workspace) (visible s) + -- if it is visible, it is just raised + = s { current = x, visible = current s : L.deleteBy (equating screen) x (visible s) } + + | Just x <- L.find ((i==).tag) (hidden s) -- must be hidden then + -- if it was hidden, it is raised on the xine screen currently used + = s { current = (current s) { workspace = x } + , hidden = workspace (current s) : L.deleteBy (equating tag) x (hidden s) } + + | otherwise = s -- not a member of the stackset + + where equating f = \x y -> f x == f y + + -- 'Catch'ing this might be hard. Relies on monotonically increasing + -- workspace tags defined in 'new' + -- + -- and now tags are not monotonic, what happens here? + +-- | +-- Set focus to the given workspace. If that workspace does not exist +-- in the stackset, the original workspace is returned. If that workspace is +-- 'hidden', then display that workspace on the current screen, and move the +-- current workspace to 'hidden'. If that workspace is 'visible' on another +-- screen, the workspaces of the current screen and the other screen are +-- swapped. + +greedyView :: (Eq s, Eq i) => i -> StackSet i l a s sd -> StackSet i l a s sd +greedyView w ws + | any wTag (hidden ws) = view w ws + | (Just s) <- L.find (wTag . workspace) (visible ws) + = ws { current = (current ws) { workspace = workspace s } + , visible = s { workspace = workspace (current ws) } + : L.filter (not . wTag . workspace) (visible ws) } + | otherwise = ws + where wTag = (w == ) . tag + +-- --------------------------------------------------------------------- +-- $xinerama + +-- | Find the tag of the workspace visible on Xinerama screen 'sc'. +-- 'Nothing' if screen is out of bounds. +lookupWorkspace :: Eq s => s -> StackSet i l a s sd -> Maybe i +lookupWorkspace sc w = listToMaybe [ tag i | Screen i s _ <- current w : visible w, s == sc ] + +-- --------------------------------------------------------------------- +-- $stackOperations + +-- | +-- The 'with' function takes a default value, a function, and a +-- StackSet. If the current stack is Nothing, 'with' returns the +-- default value. Otherwise, it applies the function to the stack, +-- returning the result. It is like 'maybe' for the focused workspace. +-- +with :: b -> (Stack a -> b) -> StackSet i l a s sd -> b +with dflt f = maybe dflt f . stack . workspace . current + +-- | +-- Apply a function, and a default value for 'Nothing', to modify the current stack. +-- +modify :: Maybe (Stack a) -> (Stack a -> Maybe (Stack a)) -> StackSet i l a s sd -> StackSet i l a s sd +modify d f s = s { current = (current s) + { workspace = (workspace (current s)) { stack = with d f s }}} + +-- | +-- Apply a function to modify the current stack if it isn't empty, and we don't +-- want to empty it. +-- +modify' :: (Stack a -> Stack a) -> StackSet i l a s sd -> StackSet i l a s sd +modify' f = modify Nothing (Just . f) + +-- | +-- /O(1)/. Extract the focused element of the current stack. +-- Return 'Just' that element, or 'Nothing' for an empty stack. +-- +peek :: StackSet i l a s sd -> Maybe a +peek = with Nothing (return . focus) + +-- | +-- /O(n)/. Flatten a 'Stack' into a list. +-- +integrate :: Stack a -> [a] +integrate (Stack x l r) = reverse l ++ x : r + +-- | +-- /O(n)/ Flatten a possibly empty stack into a list. +integrate' :: Maybe (Stack a) -> [a] +integrate' = maybe [] integrate + +-- | +-- /O(n)/. Turn a list into a possibly empty stack (i.e., a zipper): +-- the first element of the list is current, and the rest of the list +-- is down. +differentiate :: [a] -> Maybe (Stack a) +differentiate [] = Nothing +differentiate (x:xs) = Just $ Stack x [] xs + +-- | +-- /O(n)/. 'filter p s' returns the elements of 's' such that 'p' evaluates to +-- 'True'. Order is preserved, and focus moves as described for 'delete'. +-- +filter :: (a -> Bool) -> Stack a -> Maybe (Stack a) +filter p (Stack f ls rs) = case L.filter p (f:rs) of + f':rs' -> Just $ Stack f' (L.filter p ls) rs' -- maybe move focus down + [] -> case L.filter p ls of -- filter back up + f':ls' -> Just $ Stack f' ls' [] -- else up + [] -> Nothing + +-- | +-- /O(s)/. Extract the stack on the current workspace, as a list. +-- The order of the stack is determined by the master window -- it will be +-- the head of the list. The implementation is given by the natural +-- integration of a one-hole list cursor, back to a list. +-- +index :: StackSet i l a s sd -> [a] +index = with [] integrate + +-- | +-- /O(1), O(w) on the wrapping case/. +-- +-- focusUp, focusDown. Move the window focus up or down the stack, +-- wrapping if we reach the end. The wrapping should model a 'cycle' +-- on the current stack. The 'master' window, and window order, +-- are unaffected by movement of focus. +-- +-- swapUp, swapDown, swap the neighbour in the stack ordering, wrapping +-- if we reach the end. Again the wrapping model should 'cycle' on +-- the current stack. +-- +focusUp, focusDown, swapUp, swapDown :: StackSet i l a s sd -> StackSet i l a s sd +focusUp = modify' focusUp' +focusDown = modify' focusDown' + +swapUp = modify' swapUp' +swapDown = modify' (reverseStack . swapUp' . reverseStack) + +-- | Variants of 'focusUp' and 'focusDown' that work on a +-- 'Stack' rather than an entire 'StackSet'. +focusUp', focusDown' :: Stack a -> Stack a +focusUp' (Stack t (l:ls) rs) = Stack l ls (t:rs) +focusUp' (Stack t [] rs) = Stack x xs [] where (x:xs) = reverse (t:rs) +focusDown' = reverseStack . focusUp' . reverseStack + +swapUp' :: Stack a -> Stack a +swapUp' (Stack t (l:ls) rs) = Stack t ls (l:rs) +swapUp' (Stack t [] rs) = Stack t (reverse rs) [] + +-- | reverse a stack: up becomes down and down becomes up. +reverseStack :: Stack a -> Stack a +reverseStack (Stack t ls rs) = Stack t rs ls + +-- +-- | /O(1) on current window, O(n) in general/. Focus the window 'w', +-- and set its workspace as current. +-- +focusWindow :: (Eq s, Eq a, Eq i) => a -> StackSet i l a s sd -> StackSet i l a s sd +focusWindow w s | Just w == peek s = s + | otherwise = fromMaybe s $ do + n <- findTag w s + return $ until ((Just w ==) . peek) focusUp (view n s) + +-- | Get a list of all screens in the 'StackSet'. +screens :: StackSet i l a s sd -> [Screen i l a s sd] +screens s = current s : visible s + +-- | Get a list of all workspaces in the 'StackSet'. +workspaces :: StackSet i l a s sd -> [Workspace i l a] +workspaces s = workspace (current s) : map workspace (visible s) ++ hidden s + +-- | Get a list of all windows in the 'StackSet' in no particular order +allWindows :: Eq a => StackSet i l a s sd -> [a] +allWindows = L.nub . concatMap (integrate' . stack) . workspaces + +-- | Get the tag of the currently focused workspace. +currentTag :: StackSet i l a s sd -> i +currentTag = tag . workspace . current + +-- | Is the given tag present in the 'StackSet'? +tagMember :: Eq i => i -> StackSet i l a s sd -> Bool +tagMember t = elem t . map tag . workspaces + +-- | Rename a given tag if present in the 'StackSet'. +renameTag :: Eq i => i -> i -> StackSet i l a s sd -> StackSet i l a s sd +renameTag o n = mapWorkspace rename + where rename w = if tag w == o then w { tag = n } else w + +-- | Ensure that a given set of workspace tags is present by renaming +-- existing workspaces and\/or creating new hidden workspaces as +-- necessary. +ensureTags :: Eq i => l -> [i] -> StackSet i l a s sd -> StackSet i l a s sd +ensureTags l allt st = et allt (map tag (workspaces st) \\ allt) st + where et [] _ s = s + et (i:is) rn s | i `tagMember` s = et is rn s + et (i:is) [] s = et is [] (s { hidden = Workspace i l Nothing : hidden s }) + et (i:is) (r:rs) s = et is rs $ renameTag r i s + +-- | Map a function on all the workspaces in the 'StackSet'. +mapWorkspace :: (Workspace i l a -> Workspace i l a) -> StackSet i l a s sd -> StackSet i l a s sd +mapWorkspace f s = s { current = updScr (current s) + , visible = map updScr (visible s) + , hidden = map f (hidden s) } + where updScr scr = scr { workspace = f (workspace scr) } + +-- | Map a function on all the layouts in the 'StackSet'. +mapLayout :: (l -> l') -> StackSet i l a s sd -> StackSet i l' a s sd +mapLayout f (StackSet v vs hs m) = StackSet (fScreen v) (map fScreen vs) (map fWorkspace hs) m + where + fScreen (Screen ws s sd) = Screen (fWorkspace ws) s sd + fWorkspace (Workspace t l s) = Workspace t (f l) s + +-- | /O(n)/. Is a window in the 'StackSet'? +member :: Eq a => a -> StackSet i l a s sd -> Bool +member a s = isJust (findTag a s) + +-- | /O(1) on current window, O(n) in general/. +-- Return 'Just' the workspace tag of the given window, or 'Nothing' +-- if the window is not in the 'StackSet'. +findTag :: Eq a => a -> StackSet i l a s sd -> Maybe i +findTag a s = listToMaybe + [ tag w | w <- workspaces s, has a (stack w) ] + where has _ Nothing = False + has x (Just (Stack t l r)) = x `elem` (t : l ++ r) + +-- --------------------------------------------------------------------- +-- $modifyStackset + +-- | +-- /O(n)/. (Complexity due to duplicate check). Insert a new element +-- into the stack, above the currently focused element. The new +-- element is given focus; the previously focused element is moved +-- down. +-- +-- If the element is already in the stackset, the original stackset is +-- returned unmodified. +-- +-- Semantics in Huet's paper is that insert doesn't move the cursor. +-- However, we choose to insert above, and move the focus. +-- +insertUp :: Eq a => a -> StackSet i l a s sd -> StackSet i l a s sd +insertUp a s = if member a s then s else insert + where insert = modify (Just $ Stack a [] []) (\(Stack t l r) -> Just $ Stack a l (t:r)) s + +-- insertDown :: a -> StackSet i l a s sd -> StackSet i l a s sd +-- insertDown a = modify (Stack a [] []) $ \(Stack t l r) -> Stack a (t:l) r +-- Old semantics, from Huet. +-- > w { down = a : down w } + +-- | +-- /O(1) on current window, O(n) in general/. Delete window 'w' if it exists. +-- There are 4 cases to consider: +-- +-- * delete on an 'Nothing' workspace leaves it Nothing +-- +-- * otherwise, try to move focus to the down +-- +-- * otherwise, try to move focus to the up +-- +-- * otherwise, you've got an empty workspace, becomes 'Nothing' +-- +-- Behaviour with respect to the master: +-- +-- * deleting the master window resets it to the newly focused window +-- +-- * otherwise, delete doesn't affect the master. +-- +delete :: (Ord a, Eq s) => a -> StackSet i l a s sd -> StackSet i l a s sd +delete w = sink w . delete' w + +-- | Only temporarily remove the window from the stack, thereby not destroying special +-- information saved in the 'Stackset' +delete' :: (Eq a, Eq s) => a -> StackSet i l a s sd -> StackSet i l a s sd +delete' w s = s { current = removeFromScreen (current s) + , visible = map removeFromScreen (visible s) + , hidden = map removeFromWorkspace (hidden s) } + where removeFromWorkspace ws = ws { stack = stack ws >>= filter (/=w) } + removeFromScreen scr = scr { workspace = removeFromWorkspace (workspace scr) } + +------------------------------------------------------------------------ + +-- | Given a window, and its preferred rectangle, set it as floating +-- A floating window should already be managed by the 'StackSet'. +float :: Ord a => a -> RationalRect -> StackSet i l a s sd -> StackSet i l a s sd +float w r s = s { floating = M.insert w r (floating s) } + +-- | Clear the floating status of a window +sink :: Ord a => a -> StackSet i l a s sd -> StackSet i l a s sd +sink w s = s { floating = M.delete w (floating s) } + +------------------------------------------------------------------------ +-- $settingMW + +-- | /O(s)/. Set the master window to the focused window. +-- The old master window is swapped in the tiling order with the focused window. +-- Focus stays with the item moved. +swapMaster :: StackSet i l a s sd -> StackSet i l a s sd +swapMaster = modify' $ \c -> case c of + Stack _ [] _ -> c -- already master. + Stack t ls rs -> Stack t [] (xs ++ x : rs) where (x:xs) = reverse ls + +-- natural! keep focus, move current to the top, move top to current. + +-- | /O(s)/. Set the master window to the focused window. +-- The other windows are kept in order and shifted down on the stack, as if you +-- just hit mod-shift-k a bunch of times. +-- Focus stays with the item moved. +shiftMaster :: StackSet i l a s sd -> StackSet i l a s sd +shiftMaster = modify' $ \c -> case c of + Stack _ [] _ -> c -- already master. + Stack t ls rs -> Stack t [] (reverse ls ++ rs) + +-- | /O(s)/. Set focus to the master window. +focusMaster :: StackSet i l a s sd -> StackSet i l a s sd +focusMaster = modify' $ \c -> case c of + Stack _ [] _ -> c + Stack t ls rs -> Stack x [] (xs ++ t : rs) where (x:xs) = reverse ls + +-- +-- --------------------------------------------------------------------- +-- $composite + +-- | /O(w)/. shift. Move the focused element of the current stack to stack +-- 'n', leaving it as the focused element on that stack. The item is +-- inserted above the currently focused element on that workspace. +-- The actual focused workspace doesn't change. If there is no +-- element on the current stack, the original stackSet is returned. +-- +shift :: (Ord a, Eq s, Eq i) => i -> StackSet i l a s sd -> StackSet i l a s sd +shift n s = maybe s (\w -> shiftWin n w s) (peek s) + +-- | /O(n)/. shiftWin. Searches for the specified window 'w' on all workspaces +-- of the stackSet and moves it to stack 'n', leaving it as the focused +-- element on that stack. The item is inserted above the currently +-- focused element on that workspace. +-- The actual focused workspace doesn't change. If the window is not +-- found in the stackSet, the original stackSet is returned. +shiftWin :: (Ord a, Eq a, Eq s, Eq i) => i -> a -> StackSet i l a s sd -> StackSet i l a s sd +shiftWin n w s = case findTag w s of + Just from | n `tagMember` s && n /= from -> go from s + _ -> s + where go from = onWorkspace n (insertUp w) . onWorkspace from (delete' w) + +onWorkspace :: (Eq i, Eq s) => i -> (StackSet i l a s sd -> StackSet i l a s sd) + -> (StackSet i l a s sd -> StackSet i l a s sd) +onWorkspace n f s = view (currentTag s) . f . view n $ s diff --git a/xmonad.cabal b/xmonad.cabal index 627361c..5b7dc7a 100644 --- a/xmonad.cabal +++ b/xmonad.cabal @@ -20,7 +20,7 @@ maintainer: xmonad@haskell.org extra-source-files: README TODO CONFIG STYLE tests/loc.hs tests/Properties.hs man/xmonad.1.markdown man/xmonad.1 man/xmonad.1.html util/GenerateManpage.hs -cabal-version: >= 1.6 +cabal-version: >= 1.8 bug-reports: http://code.google.com/p/xmonad/issues/list build-type: Simple @@ -44,6 +44,7 @@ flag testing default: False library + hs-source-dirs: src exposed-modules: XMonad XMonad.Main XMonad.Core @@ -74,19 +75,13 @@ library buildable: False executable xmonad - main-is: Main.hs - other-modules: XMonad - XMonad.Main - XMonad.Core - XMonad.Config - XMonad.Layout - XMonad.ManageHook - XMonad.Operations - XMonad.StackSet - - if true - ghc-options: -funbox-strict-fields -Wall - + main-is: Main.hs + build-depends: base, + X11, + unix, + mtl, + xmonad + ghc-options: -Wall if impl(ghc >= 6.12.1) ghc-options: -fno-warn-unused-do-bind -- cgit v1.2.3