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/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 ------------------------------------------------ 7 files changed, 2743 deletions(-) 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 (limited to 'XMonad') 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 -- cgit v1.2.3