xmonad-0.11/0000755000000000000000000000000012070436203011104 5ustar0000000000000000xmonad-0.11/STYLE0000644000000000000000000000135612070436203011734 0ustar0000000000000000 == Coding guidelines for contributing to == xmonad and the xmonad contributed extensions * Comment every top level function (particularly exported functions), and provide a type signature; use Haddock syntax in the comments. * Follow the coding style of the other modules. * Code should be compilable with -Wall -Werror. There should be no warnings. * Partial functions should be avoided: the window manager should not crash, so do not call `error` or `undefined` * Tabs are illegal. Use 4 spaces for indenting. * Any pure function added to the core should have QuickCheck properties precisely defining its behavior. * New modules should identify the author, and be submitted under the same license as xmonad (BSD3 license or freer). xmonad-0.11/XMonad.hs0000644000000000000000000000215212070436203012626 0ustar0000000000000000-------------------------------------------------------------------- -- | -- Module : XMonad -- Copyright : (c) Don Stewart -- License : BSD3 -- -- Maintainer: Don Stewart -- Stability : provisional -- Portability: -- -------------------------------------------------------------------- -- -- Useful exports for configuration files. module XMonad ( module XMonad.Main, module XMonad.Core, module XMonad.Config, module XMonad.Layout, module XMonad.ManageHook, module XMonad.Operations, module Graphics.X11, module Graphics.X11.Xlib.Extras, (.|.), MonadState(..), gets, modify, MonadReader(..), asks, MonadIO(..) ) where -- core modules import XMonad.Main import XMonad.Core import XMonad.Config import XMonad.Layout import XMonad.ManageHook import XMonad.Operations -- import XMonad.StackSet -- conflicts with 'workspaces' defined in XMonad.hs -- modules needed to get basic configuration working import Data.Bits import Graphics.X11 hiding (refreshKeyboardMapping) import Graphics.X11.Xlib.Extras import Control.Monad.State import Control.Monad.Reader xmonad-0.11/README0000644000000000000000000001105612070436203011767 0ustar0000000000000000 xmonad : a tiling window manager http://xmonad.org xmonad is a tiling window manager for X. Windows are arranged automatically to tile the screen without gaps or overlap, maximising screen use. Window manager features are accessible from the keyboard: a mouse is optional. xmonad is written, configured and extensible in Haskell. Custom layout algorithms, key bindings and other extensions may be written by the user in config files. Layouts are applied dynamically, and different layouts may be used on each workspace. Xinerama is fully supported, allowing windows to be tiled on several physical screens. Quick start: Obtain the dependent libraries, then build with: runhaskell Setup.lhs configure --user --prefix=$HOME runhaskell Setup.lhs build runhaskell Setup.lhs install --user For the full story, read on. Building: Building is quite straightforward, and requires a basic Haskell toolchain. On many systems xmonad is available as a binary package in your package system (e.g. on Debian or Gentoo). If at all possible, use this in preference to a source build, as the dependency resolution will be simpler. We'll now walk through the complete list of toolchain dependencies. * GHC: the Glasgow Haskell Compiler You first need a Haskell compiler. Your distribution's package system will have binaries of GHC (the Glasgow Haskell Compiler), the compiler we use, so install that first. If your operating system's package system doesn't provide a binary version of GHC, you can find them here: http://haskell.org/ghc For example, in Debian you would install GHC with: apt-get install ghc6 It shouldn't be necessary to compile GHC from source -- every common system has a pre-build binary version. * X11 libraries: Since you're building an X application, you'll need the C X11 library headers. On many platforms, these come pre-installed. For others, such as Debian, you can get them from your package manager: apt-get install libx11-dev Typically you need: libXinerama libXext libX11 * Cabal xmonad requires a recent version of Cabal, >= 1.2.0. If you're using GHC 6.8, then it comes bundled with the right version. If you're using GHC 6.6.x, you'll need to build and install Cabal from hackage first: http://hackage.haskell.org/package/Cabal You can check which version you have with the command: $ ghc-pkg list Cabal Cabal-1.2.2.0 * Haskell libraries: mtl, unix, X11 Finally, you need the Haskell libraries xmonad depends on. Since you've a working GHC installation now, most of these will be provided. To check whether you've got a package run 'ghc-pkg list some_package_name'. You will need the following packages: mtl http://hackage.haskell.org/package/mtl unix http://hackage.haskell.org/package/unix X11 http://hackage.haskell.org/package/X11 * Build xmonad: Once you've got all the dependencies in place (which should be straightforward), build xmonad: runhaskell Setup.lhs configure --user --prefix=$HOME runhaskell Setup.lhs build runhaskell Setup.lhs install --user And you're done! ------------------------------------------------------------------------ Running xmonad: Add: $HOME/bin/xmonad to the last line of your .xsession or .xinitrc file. ------------------------------------------------------------------------ Configuring: See the CONFIG document ------------------------------------------------------------------------ XMonadContrib There are many extensions to xmonad available in the XMonadContrib (xmc) library. Examples include an ion3-like tabbed layout, a prompt/program launcher, and various other useful modules. XMonadContrib is available at: latest release: http://hackage.haskell.org/package/xmonad-contrib darcs version: darcs get http://code.haskell.org/XMonadContrib ------------------------------------------------------------------------ Other useful programs: A nicer xterm replacement, that supports resizing better: urxvt http://software.schmorp.de/pkg/rxvt-unicode.html For custom status bars: dzen http://gotmor.googlepages.com/dzen xmobar http://hackage.haskell.org/package/xmobar For a program dispatch menu: dmenu http://www.suckless.org/download/ gmrun (in your package system) Authors: Spencer Janssen Don Stewart Jason Creighton xmonad-0.11/TODO0000644000000000000000000000144612070436203011601 0ustar0000000000000000 - Write down invariants for the window life cycle, especially: - When are borders set? Prove that the current handling is sufficient. - current floating layer handling is nonoptimal. FocusUp should raise, for example - Issues still with stacking order. = Release management = * configuration documentation * generate haddocks for core and XMC, upload to xmonad.org * generate manpage, generate html manpage * double check README build instructions * test core with 6.6 and 6.8 * bump xmonad.cabal version and X11 version * upload X11 and xmonad to Hackage * update links to hackage in download.html * update #xmonad topic * check examples/text in user-facing Config.hs * check tour.html and intro.html are up to date, and mention all core bindings * confirm template config is type correct xmonad-0.11/Setup.lhs0000644000000000000000000000011412070436203012710 0ustar0000000000000000#!/usr/bin/env runhaskell > import Distribution.Simple > main = defaultMain xmonad-0.11/xmonad.cabal0000644000000000000000000000652512070436203013366 0ustar0000000000000000name: xmonad version: 0.11 homepage: http://xmonad.org synopsis: A tiling window manager description: xmonad is a tiling window manager for X. Windows are arranged automatically to tile the screen without gaps or overlap, maximising screen use. All features of the window manager are accessible from the keyboard: a mouse is strictly optional. xmonad is written and extensible in Haskell. Custom layout algorithms, and other extensions, may be written by the user in config files. Layouts are applied dynamically, and different layouts may be used on each workspace. Xinerama is fully supported, allowing windows to be tiled on several screens. category: System license: BSD3 license-file: LICENSE author: Spencer Janssen maintainer: xmonad@haskell.org extra-source-files: README TODO CONFIG STYLE tests/loc.hs tests/Properties.hs man/xmonad.1.markdown man/xmonad.1 man/xmonad.1.html util/GenerateManpage.hs cabal-version: >= 1.6 bug-reports: http://code.google.com/p/xmonad/issues/list build-type: Simple tested-with: GHC==7.6.1, GHC==7.4.1, GHC==7.2.1, GHC==6.12.3, GHC==6.10.4 data-files: man/xmonad.hs, man/xmonad.1, man/xmonad.1.html source-repository head type: darcs location: http://code.haskell.org/xmonad flag small_base description: Choose the new smaller, split-up base package. flag testing description: Testing mode, only build minimal components default: False library exposed-modules: XMonad XMonad.Main XMonad.Core XMonad.Config XMonad.Layout XMonad.ManageHook XMonad.Operations XMonad.StackSet if flag(small_base) build-depends: base < 5 && >=3, containers, directory, process, filepath, extensible-exceptions else build-depends: base < 3 build-depends: X11>=1.5 && < 1.7, mtl, unix, utf8-string >= 0.3 && < 0.4 if true ghc-options: -funbox-strict-fields -Wall if impl(ghc >= 6.12.1) ghc-options: -fno-warn-unused-do-bind ghc-prof-options: -prof -auto-all extensions: CPP if flag(testing) buildable: False executable xmonad main-is: Main.hs other-modules: XMonad XMonad.Main XMonad.Core XMonad.Config XMonad.Layout XMonad.ManageHook XMonad.Operations XMonad.StackSet if true ghc-options: -funbox-strict-fields -Wall if impl(ghc >= 6.12.1) ghc-options: -fno-warn-unused-do-bind ghc-prof-options: -prof -auto-all extensions: CPP if flag(testing) cpp-options: -DTESTING hs-source-dirs: . tests/ build-depends: QuickCheck < 2 ghc-options: -Werror if flag(testing) && flag(small_base) build-depends: filepath, process, directory, mtl, unix, X11, base, containers, random, extensible-exceptions xmonad-0.11/LICENSE0000644000000000000000000000273312070436203012116 0ustar0000000000000000Copyright (c) 2007,2008 Spencer Janssen Copyright (c) 2007,2008 Don Stewart All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the author nor the names of his contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. xmonad-0.11/CONFIG0000644000000000000000000000424712070436203012003 0ustar0000000000000000== Configuring xmonad == xmonad is configured by creating and editing the file: ~/.xmonad/xmonad.hs xmonad then uses settings from this file as arguments to the window manager, on startup. For a complete example of possible settings, see the file: man/xmonad.hs Further examples are on the website, wiki and extension documentation. http://haskell.org/haskellwiki/Xmonad == A simple example == Here is a basic example, which overrides the default border width, default terminal, and some colours. This text goes in the file $HOME/.xmonad/xmonad.hs : import XMonad main = xmonad $ defaultConfig { borderWidth = 2 , terminal = "urxvt" , normalBorderColor = "#cccccc" , focusedBorderColor = "#cd8b00" } You can find the defaults in the file: XMonad/Config.hs == Checking your xmonad.hs is correct == Place this text in ~/.xmonad/xmonad.hs, and then check that it is syntactically and type correct by loading it in the Haskell interpreter: $ ghci ~/.xmonad/xmonad.hs GHCi, version 6.8.1: http://www.haskell.org/ghc/ :? for help Loading package base ... linking ... done. Ok, modules loaded: Main. Prelude Main> :t main main :: IO () Ok, looks good. == Loading your configuration == To have xmonad start using your settings, type 'mod-q'. xmonad will then load this new file, and run it. If it is unable to, the defaults are used. To load successfully, both 'xmonad' and 'ghc' must be in your $PATH environment variable. If GHC isn't in your path, for some reason, you can compile the xmonad.hs file yourself: $ cd ~/.xmonad $ ghc --make xmonad.hs $ ls xmonad xmonad.hi xmonad.hs xmonad.o When you hit mod-q, this newly compiled xmonad will be used. == Where are the defaults? == The default configuration values are defined in the source file: XMonad/Config.hs the XConfig data structure itself is defined in: XMonad/Core.hs == Extensions == Since the xmonad.hs file is just another Haskell module, you may import and use any Haskell code or libraries you wish. For example, you can use things from the xmonad-contrib library, or other code you write yourself. xmonad-0.11/Main.hs0000644000000000000000000000705512070436203012333 0ustar0000000000000000---------------------------------------------------------------------------- -- | -- Module : Main -- Copyright : (c) Spencer Janssen 2007 -- License : BSD3-style (see LICENSE) -- -- Maintainer : sjanssen@cse.unl.edu -- Stability : unstable -- Portability : not portable, uses mtl, X11, posix -- -- xmonad, a minimalist, tiling window manager for X11 -- ----------------------------------------------------------------------------- module Main (main) where import XMonad import Control.Monad (unless) import System.Info import System.Environment import System.Posix.Process (executeFile) import System.Exit (exitFailure) import Paths_xmonad (version) import Data.Version (showVersion) import Graphics.X11.Xinerama (compiledWithXinerama) #ifdef TESTING import qualified Properties #endif -- | The entry point into xmonad. Attempts to compile any custom main -- for xmonad, and if it doesn't find one, just launches the default. main :: IO () main = do installSignalHandlers -- important to ignore SIGCHLD to avoid zombies args <- getArgs let launch = catchIO buildLaunch >> xmonad defaultConfig case args of [] -> launch ("--resume":_) -> launch ["--help"] -> usage ["--recompile"] -> recompile True >>= flip unless exitFailure ["--replace"] -> launch ["--restart"] -> sendRestart >> return () ["--version"] -> putStrLn $ unwords shortVersion ["--verbose-version"] -> putStrLn . unwords $ shortVersion ++ longVersion #ifdef TESTING ("--run-tests":_) -> Properties.main #endif _ -> fail "unrecognized flags" where shortVersion = ["xmonad", showVersion version] longVersion = [ "compiled by", compilerName, showVersion compilerVersion , "for", arch ++ "-" ++ os , "\nXinerama:", show compiledWithXinerama ] usage :: IO () usage = do self <- getProgName putStr . unlines $ concat ["Usage: ", self, " [OPTION]"] : "Options:" : " --help Print this message" : " --version Print the version number" : " --recompile Recompile your ~/.xmonad/xmonad.hs" : " --replace Replace the running window manager with xmonad" : " --restart Request a running xmonad process to restart" : #ifdef TESTING " --run-tests Run the test suite" : #endif [] -- | Build "~\/.xmonad\/xmonad.hs" with ghc, then execute it. If there are no -- errors, this function does not return. An exception is raised in any of -- these cases: -- -- * ghc missing -- -- * both "~\/.xmonad\/xmonad.hs" and "~\/.xmonad\/xmonad-$arch-$os" missing -- -- * xmonad.hs fails to compile -- -- ** wrong ghc in path (fails to compile) -- -- ** type error, syntax error, .. -- -- * Missing XMonad\/XMonadContrib modules due to ghc upgrade -- buildLaunch :: IO () buildLaunch = do recompile False dir <- getXMonadDir args <- getArgs executeFile (dir ++ "/xmonad-"++arch++"-"++os) False args Nothing return () sendRestart :: IO () sendRestart = do dpy <- openDisplay "" rw <- rootWindow dpy $ defaultScreen dpy xmonad_restart <- internAtom dpy "XMONAD_RESTART" False allocaXEvent $ \e -> do setEventType e clientMessage setClientMessageEvent e rw xmonad_restart 32 0 currentTime sendEvent dpy rw False structureNotifyMask e sync dpy False xmonad-0.11/man/0000755000000000000000000000000012070436203011657 5ustar0000000000000000xmonad-0.11/man/xmonad.1.html0000644000000000000000000002133212070436203014173 0ustar0000000000000000

xmonad-0.11

Section: xmonad manual (1)
Updated: 31 December 2012


Name

xmonad - a tiling window manager

Description

xmonad is a minimalist tiling window manager for X, written in Haskell. Windows are managed using automatic layout algorithms, which can be dynamically reconfigured. At any time windows are arranged so as to maximize the use of screen real estate. All features of the window manager are accessible purely from the keyboard: a mouse is entirely optional. xmonad is configured in Haskell, and custom layout algorithms may be implemented by the user in config files. A principle of xmonad is predictability: the user should know in advance precisely the window arrangement that will result from any action.

By default, xmonad provides three layout algorithms: tall, wide and fullscreen. In tall or wide mode, windows are tiled and arranged to prevent overlap and maximize screen use. Sets of windows are grouped together on virtual screens, and each screen retains its own layout, which may be reconfigured dynamically. Multiple physical monitors are supported via Xinerama, allowing simultaneous display of a number of screens.

By utilizing the expressivity of a modern functional language with a rich static type system, xmonad provides a complete, featureful window manager in less than 1200 lines of code, with an emphasis on correctness and robustness. Internal properties of the window manager are checked using a combination of static guarantees provided by the type system, and type-based automated testing. A benefit of this is that the code is simple to understand, and easy to modify.

Usage

xmonad places each window into a "workspace". Each workspace can have any number of windows, which you can cycle though with mod-j and mod-k. Windows are either displayed full screen, tiled horizontally, or tiled vertically. You can toggle the layout mode with mod-space, which will cycle through the available modes.

You can switch to workspace N with mod-N. For example, to switch to workspace 5, you would press mod-5. Similarly, you can move the current window to another workspace with mod-shift-N.

When running with multiple monitors (Xinerama), each screen has exactly 1 workspace visible. mod-{w,e,r} switch the focus between screens, while shift-mod-{w,e,r} move the current window to that screen. When xmonad starts, workspace 1 is on screen 1, workspace 2 is on screen 2, etc. When switching workspaces to one that is already visible, the current and visible workspaces are swapped.

Flags

xmonad has several flags which you may pass to the executable. These flags are:

--recompile

Recompiles your configuration in ~/.xmonad/xmonad.hs

--restart

Causes the currently running xmonad process to restart

--replace

Replace the current window manager with xmonad

--version

Display version of xmonad

--verbose-version

Display detailed version of xmonad

Default keyboard bindings

mod-shift-return

Launch terminal

mod-p

Launch dmenu

mod-shift-p

Launch gmrun

mod-shift-c

Close 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 viewed windows to the correct size

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

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

mod-h

Shrink the master area

mod-l

Expand the master area

mod-t

Push window back into tiling

mod-comma

Increment the number of windows in the master area

mod-period

Deincrement the number of windows in the master area

mod-shift-q

Quit xmonad

mod-q

Restart xmonad

mod-shift-slash

Run xmessage with a summary of the default keybindings (useful for beginners)

mod-[1..9]

Switch to workspace N

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

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

Examples

To use xmonad as your window manager add to your ~/.xinitrc file:

exec xmonad

Customization

xmonad is customized in ~/.xmonad/xmonad.hs, and then restarting with mod-q.

You can find many extensions to the core feature set in the xmonad- contrib package, available through your package manager or from xmonad.org.

Modular Configuration

As of xmonad-0.9, any additional Haskell modules may be placed in ~/.xmonad/lib/ are available in GHC's searchpath. Hierarchical modules are supported: for example, the file ~/.xmonad/lib/XMonad/Stack/MyAdditions.hs could contain:

module XMonad.Stack.MyAdditions (function1) where
    function1 = error "function1: Not implemented yet!"

Your xmonad.hs may then import XMonad.Stack.MyAdditions as if that module was contained within xmonad or xmonad-contrib.

Bugs

Probably. If you find any, please report them to the bugtracker

xmonad-0.11/man/xmonad.hs0000644000000000000000000002271512070436203013510 0ustar0000000000000000-- -- xmonad example config file. -- -- A template showing all available configuration hooks, -- and how to override the defaults in your own xmonad.hs conf file. -- -- Normally, you'd only override those defaults you care about. -- import XMonad import Data.Monoid import System.Exit import qualified XMonad.StackSet as W import qualified Data.Map as M -- The preferred terminal program, which is used in a binding below and by -- certain contrib modules. -- myTerminal = "xterm" -- Whether focus follows the mouse pointer. myFocusFollowsMouse :: Bool myFocusFollowsMouse = True -- Whether clicking on a window to focus also passes the click to the window myClickJustFocuses :: Bool myClickJustFocuses = False -- Width of the window border in pixels. -- myBorderWidth = 1 -- 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. -- myModMask = mod1Mask -- 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] -- myWorkspaces = ["1","2","3","4","5","6","7","8","9"] -- Border colors for unfocused and focused windows, respectively. -- myNormalBorderColor = "#dddddd" myFocusedBorderColor = "#ff0000" ------------------------------------------------------------------------ -- Key bindings. Add, modify or remove key bindings here. -- myKeys conf@(XConfig {XMonad.modMask = modm}) = M.fromList $ -- launch a terminal [ ((modm .|. shiftMask, xK_Return), spawn $ XMonad.terminal conf) -- launch dmenu , ((modm, xK_p ), spawn "dmenu_run") -- launch gmrun , ((modm .|. shiftMask, xK_p ), spawn "gmrun") -- close focused window , ((modm .|. shiftMask, xK_c ), kill) -- Rotate through the available layout algorithms , ((modm, xK_space ), sendMessage NextLayout) -- Reset the layouts on the current workspace to default , ((modm .|. shiftMask, xK_space ), setLayout $ XMonad.layoutHook conf) -- Resize viewed windows to the correct size , ((modm, xK_n ), refresh) -- Move focus to the next window , ((modm, xK_Tab ), windows W.focusDown) -- Move focus to the next window , ((modm, xK_j ), windows W.focusDown) -- Move focus to the previous window , ((modm, xK_k ), windows W.focusUp ) -- Move focus to the master window , ((modm, xK_m ), windows W.focusMaster ) -- Swap the focused window and the master window , ((modm, xK_Return), windows W.swapMaster) -- Swap the focused window with the next window , ((modm .|. shiftMask, xK_j ), windows W.swapDown ) -- Swap the focused window with the previous window , ((modm .|. shiftMask, xK_k ), windows W.swapUp ) -- Shrink the master area , ((modm, xK_h ), sendMessage Shrink) -- Expand the master area , ((modm, xK_l ), sendMessage Expand) -- Push window back into tiling , ((modm, xK_t ), withFocused $ windows . W.sink) -- Increment the number of windows in the master area , ((modm , xK_comma ), sendMessage (IncMasterN 1)) -- Deincrement the number of windows in the master area , ((modm , xK_period), sendMessage (IncMasterN (-1))) -- Toggle the status bar gap -- Use this binding with avoidStruts from Hooks.ManageDocks. -- See also the statusBar function from Hooks.DynamicLog. -- -- , ((modm , xK_b ), sendMessage ToggleStruts) -- Quit xmonad , ((modm .|. shiftMask, xK_q ), io (exitWith ExitSuccess)) -- Restart xmonad , ((modm , xK_q ), spawn "xmonad --recompile; xmonad --restart") -- Run xmessage with a summary of the default keybindings (useful for beginners) , ((modMask .|. shiftMask, xK_slash ), spawn ("echo \"" ++ help ++ "\" | xmessage -file -")) ] ++ -- -- mod-[1..9], Switch to workspace N -- mod-shift-[1..9], Move client to workspace N -- [((m .|. modm, 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 .|. modm, 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 -- myMouseBindings (XConfig {XMonad.modMask = modm}) = M.fromList $ -- mod-button1, Set the window to floating mode and move by dragging [ ((modm, button1), (\w -> focus w >> mouseMoveWindow w >> windows W.shiftMaster)) -- mod-button2, Raise the window to the top of the stack , ((modm, button2), (\w -> focus w >> windows W.shiftMaster)) -- mod-button3, Set the window to floating mode and resize by dragging , ((modm, button3), (\w -> focus w >> mouseResizeWindow w >> windows W.shiftMaster)) -- you may also bind events to the mouse scroll wheel (button4 and button5) ] ------------------------------------------------------------------------ -- 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. -- myLayout = 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 ------------------------------------------------------------------------ -- 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. -- -- To match on the WM_NAME, you can use 'title' in the same way that -- 'className' and 'resource' are used below. -- myManageHook = composeAll [ className =? "MPlayer" --> doFloat , className =? "Gimp" --> doFloat , resource =? "desktop_window" --> doIgnore , resource =? "kdesktop" --> doIgnore ] ------------------------------------------------------------------------ -- Event handling -- * EwmhDesktops users should change this to ewmhDesktopsEventHook -- -- 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. -- myEventHook = mempty ------------------------------------------------------------------------ -- Status bars and logging -- Perform an arbitrary action on each internal state change or X event. -- See the 'XMonad.Hooks.DynamicLog' extension for examples. -- myLogHook = return () ------------------------------------------------------------------------ -- Startup hook -- Perform an arbitrary action each time xmonad starts or is restarted -- with mod-q. Used by, e.g., XMonad.Layout.PerWorkspace to initialize -- per-workspace layout choices. -- -- By default, do nothing. myStartupHook = return () ------------------------------------------------------------------------ -- Now run xmonad with all the defaults we set up. -- Run xmonad with the settings you specify. No need to modify this. -- main = xmonad defaults -- A structure containing your configuration settings, overriding -- fields in the default config. Any you don't override, will -- use the defaults defined in xmonad/XMonad/Config.hs -- -- No need to modify this. -- defaults = defaultConfig { -- simple stuff terminal = myTerminal, focusFollowsMouse = myFocusFollowsMouse, clickJustFocuses = myClickJustFocuses, borderWidth = myBorderWidth, modMask = myModMask, workspaces = myWorkspaces, normalBorderColor = myNormalBorderColor, focusedBorderColor = myFocusedBorderColor, -- key bindings keys = myKeys, mouseBindings = myMouseBindings, -- hooks, layouts layoutHook = myLayout, manageHook = myManageHook, handleEventHook = myEventHook, logHook = myLogHook, startupHook = myStartupHook } xmonad-0.11/man/xmonad.10000644000000000000000000001364012070436203013233 0ustar0000000000000000.TH xmonad 1 "31 December 2012" xmonad-0.11 "xmonad manual".TH "" "" .SH Name .PP xmonad - a tiling window manager .SH Description .PP \f[I]xmonad\f[] is a minimalist tiling window manager for X, written in Haskell. Windows are managed using automatic layout algorithms, which can be dynamically reconfigured. At any time windows are arranged so as to maximize the use of screen real estate. All features of the window manager are accessible purely from the keyboard: a mouse is entirely optional. \f[I]xmonad\f[] is configured in Haskell, and custom layout algorithms may be implemented by the user in config files. A principle of \f[I]xmonad\f[] is predictability: the user should know in advance precisely the window arrangement that will result from any action. .PP By default, \f[I]xmonad\f[] provides three layout algorithms: tall, wide and fullscreen. In tall or wide mode, windows are tiled and arranged to prevent overlap and maximize screen use. Sets of windows are grouped together on virtual screens, and each screen retains its own layout, which may be reconfigured dynamically. Multiple physical monitors are supported via Xinerama, allowing simultaneous display of a number of screens. .PP By utilizing the expressivity of a modern functional language with a rich static type system, \f[I]xmonad\f[] provides a complete, featureful window manager in less than 1200 lines of code, with an emphasis on correctness and robustness. Internal properties of the window manager are checked using a combination of static guarantees provided by the type system, and type-based automated testing. A benefit of this is that the code is simple to understand, and easy to modify. .SH Usage .PP \f[I]xmonad\f[] places each window into a "workspace". Each workspace can have any number of windows, which you can cycle though with mod-j and mod-k. Windows are either displayed full screen, tiled horizontally, or tiled vertically. You can toggle the layout mode with mod-space, which will cycle through the available modes. .PP You can switch to workspace N with mod-N. For example, to switch to workspace 5, you would press mod-5. Similarly, you can move the current window to another workspace with mod-shift-N. .PP When running with multiple monitors (Xinerama), each screen has exactly 1 workspace visible. mod-{w,e,r} switch the focus between screens, while shift-mod-{w,e,r} move the current window to that screen. When \f[I]xmonad\f[] starts, workspace 1 is on screen 1, workspace 2 is on screen 2, etc. When switching workspaces to one that is already visible, the current and visible workspaces are swapped. .SS Flags .PP xmonad has several flags which you may pass to the executable. These flags are: .TP .B --recompile Recompiles your configuration in \f[I]~/.xmonad/xmonad.hs\f[] .RS .RE .TP .B --restart Causes the currently running \f[I]xmonad\f[] process to restart .RS .RE .TP .B --replace Replace the current window manager with xmonad .RS .RE .TP .B --version Display version of \f[I]xmonad\f[] .RS .RE .TP .B --verbose-version Display detailed version of \f[I]xmonad\f[] .RS .RE .SS Default keyboard bindings .TP .B mod-shift-return Launch terminal .RS .RE .TP .B mod-p Launch dmenu .RS .RE .TP .B mod-shift-p Launch gmrun .RS .RE .TP .B mod-shift-c Close the focused window .RS .RE .TP .B mod-space Rotate through the available layout algorithms .RS .RE .TP .B mod-shift-space Reset the layouts on the current workspace to default .RS .RE .TP .B mod-n Resize viewed windows to the correct size .RS .RE .TP .B mod-tab Move focus to the next window .RS .RE .TP .B mod-shift-tab Move focus to the previous window .RS .RE .TP .B mod-j Move focus to the next window .RS .RE .TP .B mod-k Move focus to the previous window .RS .RE .TP .B mod-m Move focus to the master window .RS .RE .TP .B mod-return Swap the focused window and the master window .RS .RE .TP .B mod-shift-j Swap the focused window with the next window .RS .RE .TP .B mod-shift-k Swap the focused window with the previous window .RS .RE .TP .B mod-h Shrink the master area .RS .RE .TP .B mod-l Expand the master area .RS .RE .TP .B mod-t Push window back into tiling .RS .RE .TP .B mod-comma Increment the number of windows in the master area .RS .RE .TP .B mod-period Deincrement the number of windows in the master area .RS .RE .TP .B mod-shift-q Quit xmonad .RS .RE .TP .B mod-q Restart xmonad .RS .RE .TP .B mod-shift-slash Run xmessage with a summary of the default keybindings (useful for beginners) .RS .RE .TP .B mod-[1..9] Switch to workspace N .RS .RE .TP .B mod-shift-[1..9] Move client to workspace N .RS .RE .TP .B mod-{w,e,r} Switch to physical/Xinerama screens 1, 2, or 3 .RS .RE .TP .B mod-shift-{w,e,r} Move client to screen 1, 2, or 3 .RS .RE .TP .B mod-button1 Set the window to floating mode and move by dragging .RS .RE .TP .B mod-button2 Raise the window to the top of the stack .RS .RE .TP .B mod-button3 Set the window to floating mode and resize by dragging .RS .RE .SH Examples .PP To use xmonad as your window manager add to your \f[I]~/.xinitrc\f[] file: .IP .nf \f[C] exec\ xmonad \f[] .fi .SH Customization .PP xmonad is customized in ~/.xmonad/xmonad.hs, and then restarting with mod-q. .PP You can find many extensions to the core feature set in the xmonad- contrib package, available through your package manager or from xmonad.org (http://xmonad.org). .SS Modular Configuration .PP As of \f[I]xmonad-0.9\f[], any additional Haskell modules may be placed in \f[I]~/.xmonad/lib/\f[] are available in GHC\[aq]s searchpath. Hierarchical modules are supported: for example, the file \f[I]~/.xmonad/lib/XMonad/Stack/MyAdditions.hs\f[] could contain: .IP .nf \f[C] module\ XMonad.Stack.MyAdditions\ (function1)\ where \ \ \ \ function1\ =\ error\ "function1:\ Not\ implemented\ yet!" \f[] .fi .PP Your xmonad.hs may then import XMonad.Stack.MyAdditions as if that module was contained within xmonad or xmonad-contrib. .SH Bugs .PP Probably. If you find any, please report them to the bugtracker (http://code.google.com/p/xmonad/issues/list) xmonad-0.11/man/xmonad.1.markdown0000644000000000000000000000751512070436203015060 0ustar0000000000000000#Name xmonad - a tiling window manager #Description _xmonad_ is a minimalist tiling window manager for X, written in Haskell. Windows are managed using automatic layout algorithms, which can be dynamically reconfigured. At any time windows are arranged so as to maximize the use of screen real estate. All features of the window manager are accessible purely from the keyboard: a mouse is entirely optional. _xmonad_ is configured in Haskell, and custom layout algorithms may be implemented by the user in config files. A principle of _xmonad_ is predictability: the user should know in advance precisely the window arrangement that will result from any action. By default, _xmonad_ provides three layout algorithms: tall, wide and fullscreen. In tall or wide mode, windows are tiled and arranged to prevent overlap and maximize screen use. Sets of windows are grouped together on virtual screens, and each screen retains its own layout, which may be reconfigured dynamically. Multiple physical monitors are supported via Xinerama, allowing simultaneous display of a number of screens. By utilizing the expressivity of a modern functional language with a rich static type system, _xmonad_ provides a complete, featureful window manager in less than 1200 lines of code, with an emphasis on correctness and robustness. Internal properties of the window manager are checked using a combination of static guarantees provided by the type system, and type-based automated testing. A benefit of this is that the code is simple to understand, and easy to modify. #Usage _xmonad_ places each window into a "workspace". Each workspace can have any number of windows, which you can cycle though with mod-j and mod-k. Windows are either displayed full screen, tiled horizontally, or tiled vertically. You can toggle the layout mode with mod-space, which will cycle through the available modes. You can switch to workspace N with mod-N. For example, to switch to workspace 5, you would press mod-5. Similarly, you can move the current window to another workspace with mod-shift-N. When running with multiple monitors (Xinerama), each screen has exactly 1 workspace visible. mod-{w,e,r} switch the focus between screens, while shift-mod-{w,e,r} move the current window to that screen. When _xmonad_ starts, workspace 1 is on screen 1, workspace 2 is on screen 2, etc. When switching workspaces to one that is already visible, the current and visible workspaces are swapped. ##Flags xmonad has several flags which you may pass to the executable. These flags are: --recompile : Recompiles your configuration in _~/.xmonad/xmonad.hs_ --restart : Causes the currently running _xmonad_ process to restart --replace : Replace the current window manager with xmonad --version : Display version of _xmonad_ --verbose-version : Display detailed version of _xmonad_ ##Default keyboard bindings ___KEYBINDINGS___ #Examples To use xmonad as your window manager add to your _~/.xinitrc_ file: > exec xmonad #Customization xmonad is customized in ~/.xmonad/xmonad.hs, and then restarting with mod-q. You can find many extensions to the core feature set in the xmonad- contrib package, available through your package manager or from [xmonad.org]. ##Modular Configuration As of _xmonad-0.9_, any additional Haskell modules may be placed in _~/.xmonad/lib/_ are available in GHC's searchpath. Hierarchical modules are supported: for example, the file _~/.xmonad/lib/XMonad/Stack/MyAdditions.hs_ could contain: > module XMonad.Stack.MyAdditions (function1) where > function1 = error "function1: Not implemented yet!" Your xmonad.hs may then import XMonad.Stack.MyAdditions as if that module was contained within xmonad or xmonad-contrib. #Bugs Probably. If you find any, please report them to the [bugtracker] [xmonad.org]: http://xmonad.org [bugtracker]: http://code.google.com/p/xmonad/issues/list xmonad-0.11/tests/0000755000000000000000000000000012070436203012246 5ustar0000000000000000xmonad-0.11/tests/loc.hs0000644000000000000000000000071412070436203013361 0ustar0000000000000000import Control.Monad import System.Exit main = do foo <- getContents let actual_loc = filter (not.null) $ filter isntcomment $ map (dropWhile (==' ')) $ lines foo loc = length actual_loc print loc -- uncomment the following to check for mistakes in isntcomment -- print actual_loc isntcomment ('-':'-':_) = False isntcomment ('{':'-':_) = False -- pragmas isntcomment _ = True xmonad-0.11/tests/Properties.hs0000644000000000000000000012613712070436203014750 0ustar0000000000000000{-# OPTIONS -fglasgow-exts -w #-} module Properties where import XMonad.StackSet hiding (filter) import XMonad.Layout import XMonad.Core hiding (workspaces,trace) import XMonad.Operations ( applyResizeIncHint, applyMaxSizeHint ) import qualified XMonad.StackSet as S (filter) import Debug.Trace import Data.Word import Graphics.X11.Xlib.Types (Rectangle(..),Position,Dimension) import Data.Ratio import Data.Maybe import System.Environment import Control.Exception (assert) import qualified Control.Exception.Extensible as C import Control.Monad import Test.QuickCheck hiding (promote) import System.IO.Unsafe import System.IO import System.Random hiding (next) import Text.Printf import Data.List (nub,sort,sortBy,group,sort,intersperse,genericLength) import qualified Data.List as L import Data.Char (ord) import Data.Map (keys,elems) import qualified Data.Map as M -- --------------------------------------------------------------------- -- QuickCheck properties for the StackSet -- Some general hints for creating StackSet properties: -- -- * ops that mutate the StackSet are usually local -- * most ops on StackSet should either be trivially reversible, or -- idempotent, or both. -- -- The all important Arbitrary instance for StackSet. -- instance (Integral i, Integral s, Eq a, Arbitrary a, Arbitrary l, Arbitrary sd) => Arbitrary (StackSet i l a s sd) where arbitrary = do sz <- choose (1,10) -- number of workspaces n <- choose (0,sz-1) -- pick one to be in focus sc <- choose (1,sz) -- a number of physical screens lay <- arbitrary -- pick any layout sds <- replicateM sc arbitrary ls <- vector sz -- a vector of sz workspaces -- pick a random item in each stack to focus fs <- sequence [ if null s then return Nothing else liftM Just (choose ((-1),length s-1)) | s <- ls ] return $ fromList (fromIntegral n, sds,fs,ls,lay) -- | fromList. Build a new StackSet from a list of list of elements, -- keeping track of the currently focused workspace, and the total -- number of workspaces. If there are duplicates in the list, the last -- occurence wins. -- -- 'o' random workspace -- 'm' number of physical screens -- 'fs' random focused window on each workspace -- 'xs' list of list of windows -- fromList :: (Integral i, Integral s, Eq a) => (i, [sd], [Maybe Int], [[a]], l) -> StackSet i l a s sd fromList (_,_,_,[],_) = error "Cannot build a StackSet from an empty list" fromList (o,m,fs,xs,l) = let s = view o $ foldr (\(i,ys) s -> foldr insertUp (view i s) ys) (new l [0..genericLength xs-1] m) (zip [0..] xs) in foldr (\f t -> case f of Nothing -> t Just i -> foldr (const focusUp) t [0..i] ) s fs ------------------------------------------------------------------------ -- -- Just generate StackSets with Char elements. -- type T = StackSet (NonNegative Int) Int Char Int Int -- Useful operation, the non-local workspaces hidden_spaces x = map workspace (visible x) ++ hidden x -- Basic data invariants of the StackSet -- -- With the new zipper-based StackSet, tracking focus is no longer an -- issue: the data structure enforces focus by construction. -- -- But we still need to ensure there are no duplicates, and master/and -- the xinerama mapping aren't checked by the data structure at all. -- -- * no element should ever appear more than once in a StackSet -- * the xinerama screen map should be: -- -- keys should always index valid workspaces -- -- monotonically ascending in the elements -- * the current workspace should be a member of the xinerama screens -- invariant (s :: T) = and -- no duplicates [ noDuplicates -- all this xinerama stuff says we don't have the right structure -- , validScreens -- , validWorkspaces -- , inBounds ] where ws = concat [ focus t : up t ++ down t | w <- workspace (current s) : map workspace (visible s) ++ hidden s , t <- maybeToList (stack w)] :: [Char] noDuplicates = nub ws == ws -- validScreens = monotonic . sort . M. . (W.current s : W.visible : W$ s -- validWorkspaces = and [ w `elem` allworkspaces | w <- (M.keys . screens) s ] -- where allworkspaces = map tag $ current s : prev s ++ next s -- inBounds = and [ w >=0 && w < size s | (w,sc) <- M.assocs (screens s) ] monotonic [] = True monotonic (x:[]) = True monotonic (x:y:zs) | x == y-1 = monotonic (y:zs) | otherwise = False prop_invariant = invariant -- and check other ops preserve invariants prop_empty_I (n :: Positive Int) l = forAll (choose (1,fromIntegral n)) $ \m -> forAll (vector m) $ \ms -> invariant $ new l [0..fromIntegral n-1] ms prop_view_I (n :: NonNegative Int) (x :: T) = invariant $ view (fromIntegral n) x prop_greedyView_I (n :: NonNegative Int) (x :: T) = invariant $ greedyView (fromIntegral n) x prop_focusUp_I (n :: NonNegative Int) (x :: T) = invariant $ foldr (const focusUp) x [1..n] prop_focusMaster_I (n :: NonNegative Int) (x :: T) = invariant $ foldr (const focusMaster) x [1..n] prop_focusDown_I (n :: NonNegative Int) (x :: T) = invariant $ foldr (const focusDown) x [1..n] prop_focus_I (n :: NonNegative Int) (x :: T) = case peek x of Nothing -> True Just _ -> let w = focus . fromJust . stack . workspace . current $ foldr (const focusUp) x [1..n] in invariant $ focusWindow w x prop_insertUp_I n (x :: T) = invariant $ insertUp n x prop_delete_I (x :: T) = invariant $ case peek x of Nothing -> x Just i -> delete i x prop_swap_master_I (x :: T) = invariant $ swapMaster x prop_swap_left_I (n :: NonNegative Int) (x :: T) = invariant $ foldr (const swapUp ) x [1..n] prop_swap_right_I (n :: NonNegative Int) (x :: T) = invariant $ foldr (const swapDown) x [1..n] prop_shift_I (n :: NonNegative Int) (x :: T) = n `tagMember` x ==> invariant $ shift (fromIntegral n) x prop_shift_win_I (n :: NonNegative Int) (w :: Char) (x :: T) = n `tagMember` x && w `member` x ==> invariant $ shiftWin (fromIntegral n) w x -- --------------------------------------------------------------------- -- 'new' -- empty StackSets have no windows in them prop_empty (EmptyStackSet x) = all (== Nothing) [ stack w | w <- workspace (current x) : map workspace (visible x) ++ hidden x ] -- empty StackSets always have focus on first workspace prop_empty_current (NonEmptyNubList ns) (NonEmptyNubList sds) l = -- TODO, this is ugly length sds <= length ns ==> tag (workspace $ current x) == head ns where x = new l ns sds :: T -- no windows will be a member of an empty workspace prop_member_empty i (EmptyStackSet x) = member i x == False -- --------------------------------------------------------------------- -- viewing workspaces -- view sets the current workspace to 'n' prop_view_current (x :: T) (n :: NonNegative Int) = i `tagMember` x ==> tag (workspace $ current (view i x)) == i where i = fromIntegral n -- view *only* sets the current workspace, and touches Xinerama. -- no workspace contents will be changed. prop_view_local (x :: T) (n :: NonNegative Int) = i `tagMember` x ==> workspaces x == workspaces (view i x) where workspaces a = sortBy (\s t -> tag s `compare` tag t) $ workspace (current a) : map workspace (visible a) ++ hidden a i = fromIntegral n -- view should result in a visible xinerama screen -- prop_view_xinerama (x :: T) (n :: NonNegative Int) = i `tagMember` x ==> -- M.member i (screens (view i x)) -- where -- i = fromIntegral n -- view is idempotent prop_view_idem (x :: T) (i :: NonNegative Int) = i `tagMember` x ==> view i (view i x) == (view i x) -- view is reversible, though shuffles the order of hidden/visible prop_view_reversible (i :: NonNegative Int) (x :: T) = i `tagMember` x ==> normal (view n (view i x)) == normal x where n = tag (workspace $ current x) -- --------------------------------------------------------------------- -- greedyViewing workspaces -- greedyView sets the current workspace to 'n' prop_greedyView_current (x :: T) (n :: NonNegative Int) = i `tagMember` x ==> tag (workspace $ current (greedyView i x)) == i where i = fromIntegral n -- greedyView leaves things unchanged for invalid workspaces prop_greedyView_current_id (x :: T) (n :: NonNegative Int) = not (i `tagMember` x) ==> tag (workspace $ current (greedyView i x)) == j where i = fromIntegral n j = tag (workspace (current x)) -- greedyView *only* sets the current workspace, and touches Xinerama. -- no workspace contents will be changed. prop_greedyView_local (x :: T) (n :: NonNegative Int) = i `tagMember` x ==> workspaces x == workspaces (greedyView i x) where workspaces a = sortBy (\s t -> tag s `compare` tag t) $ workspace (current a) : map workspace (visible a) ++ hidden a i = fromIntegral n -- greedyView is idempotent prop_greedyView_idem (x :: T) (i :: NonNegative Int) = i `tagMember` x ==> greedyView i (greedyView i x) == (greedyView i x) -- greedyView is reversible, though shuffles the order of hidden/visible prop_greedyView_reversible (i :: NonNegative Int) (x :: T) = i `tagMember` x ==> normal (greedyView n (greedyView i x)) == normal x where n = tag (workspace $ current x) -- normalise workspace list normal s = s { hidden = sortBy g (hidden s), visible = sortBy f (visible s) } where f = \a b -> tag (workspace a) `compare` tag (workspace b) g = \a b -> tag a `compare` tag b -- --------------------------------------------------------------------- -- Xinerama -- every screen should yield a valid workspace -- prop_lookupWorkspace (n :: NonNegative Int) (x :: T) = -- s < M.size (screens x) ==> -- fromJust (lookupWorkspace s x) `elem` (map tag $ current x : prev x ++ next x) -- where -- s = fromIntegral n -- --------------------------------------------------------------------- -- peek/index -- peek either yields nothing on the Empty workspace, or Just a valid window prop_member_peek (x :: T) = case peek x of Nothing -> True {- then we don't know anything -} Just i -> member i x -- --------------------------------------------------------------------- -- index -- the list returned by index should be the same length as the actual -- windows kept in the zipper prop_index_length (x :: T) = case stack . workspace . current $ x of Nothing -> length (index x) == 0 Just it -> length (index x) == length (focus it : up it ++ down it) -- --------------------------------------------------------------------- -- rotating focus -- -- master/focus -- -- The tiling order, and master window, of a stack is unaffected by focus changes. -- prop_focus_left_master (n :: NonNegative Int) (x::T) = index (foldr (const focusUp) x [1..n]) == index x prop_focus_right_master (n :: NonNegative Int) (x::T) = index (foldr (const focusDown) x [1..n]) == index x prop_focus_master_master (n :: NonNegative Int) (x::T) = index (foldr (const focusMaster) x [1..n]) == index x prop_focusWindow_master (n :: NonNegative Int) (x :: T) = case peek x of Nothing -> True Just _ -> let s = index x i = fromIntegral n `mod` length s in index (focusWindow (s !! i) x) == index x -- shifting focus is trivially reversible prop_focus_left (x :: T) = (focusUp (focusDown x)) == x prop_focus_right (x :: T) = (focusDown (focusUp x)) == x -- focus master is idempotent prop_focusMaster_idem (x :: T) = focusMaster x == focusMaster (focusMaster x) -- focusWindow actually leaves the window focused... prop_focusWindow_works (n :: NonNegative Int) (x :: T) = case peek x of Nothing -> True Just _ -> let s = index x i = fromIntegral n `mod` length s in (focus . fromJust . stack . workspace . current) (focusWindow (s !! i) x) == (s !! i) -- rotation through the height of a stack gets us back to the start prop_focus_all_l (x :: T) = (foldr (const focusUp) x [1..n]) == x where n = length (index x) prop_focus_all_r (x :: T) = (foldr (const focusDown) x [1..n]) == x where n = length (index x) -- prop_rotate_all (x :: T) = f (f x) == f x -- f x' = foldr (\_ y -> rotate GT y) x' [1..n] -- focus is local to the current workspace prop_focus_down_local (x :: T) = hidden_spaces (focusDown x) == hidden_spaces x prop_focus_up_local (x :: T) = hidden_spaces (focusUp x) == hidden_spaces x prop_focus_master_local (x :: T) = hidden_spaces (focusMaster x) == hidden_spaces x prop_focusWindow_local (n :: NonNegative Int) (x::T ) = case peek x of Nothing -> True Just _ -> let s = index x i = fromIntegral n `mod` length s in hidden_spaces (focusWindow (s !! i) x) == hidden_spaces x -- On an invalid window, the stackset is unmodified prop_focusWindow_identity (n :: Char) (x::T ) = not (n `member` x) ==> focusWindow n x == x -- --------------------------------------------------------------------- -- member/findTag -- -- For all windows in the stackSet, findTag should identify the -- correct workspace -- prop_findIndex (x :: T) = and [ tag w == fromJust (findTag i x) | w <- workspace (current x) : map workspace (visible x) ++ hidden x , t <- maybeToList (stack w) , i <- focus t : up t ++ down t ] prop_allWindowsMember w (x :: T) = (w `elem` allWindows x) ==> member w x prop_currentTag (x :: T) = currentTag x == tag (workspace (current x)) -- --------------------------------------------------------------------- -- 'insert' -- inserting a item into an empty stackset means that item is now a member prop_insert_empty i (EmptyStackSet x)= member i (insertUp i x) -- insert should be idempotent prop_insert_idem i (x :: T) = insertUp i x == insertUp i (insertUp i x) -- insert when an item is a member should leave the stackset unchanged prop_insert_duplicate i (x :: T) = member i x ==> insertUp i x == x -- push shouldn't change anything but the current workspace prop_insert_local (x :: T) i = not (member i x) ==> hidden_spaces x == hidden_spaces (insertUp i x) -- Inserting a (unique) list of items into an empty stackset should -- result in the last inserted element having focus. prop_insert_peek (EmptyStackSet x) (NonEmptyNubList is) = peek (foldr insertUp x is) == Just (head is) -- insert >> delete is the identity, when i `notElem` . -- Except for the 'master', which is reset on insert and delete. -- prop_insert_delete n x = not (member n x) ==> delete n (insertUp n y) == (y :: T) where y = swapMaster x -- sets the master window to the current focus. -- otherwise, we don't have a rule for where master goes. -- inserting n elements increases current stack size by n prop_size_insert is (EmptyStackSet x) = size (foldr insertUp x ws ) == (length ws) where ws = nub is size = length . index -- --------------------------------------------------------------------- -- 'delete' -- deleting the current item removes it. prop_delete x = case peek x of Nothing -> True Just i -> not (member i (delete i x)) where _ = x :: T -- delete is reversible with 'insert'. -- It is the identiy, except for the 'master', which is reset on insert and delete. -- prop_delete_insert (x :: T) = case peek x of Nothing -> True Just n -> insertUp n (delete n y) == y where y = swapMaster x -- delete should be local prop_delete_local (x :: T) = case peek x of Nothing -> True Just i -> hidden_spaces x == hidden_spaces (delete i x) -- delete should not affect focus unless the focused element is what is being deleted prop_delete_focus n (x :: T) = member n x && Just n /= peek x ==> peek (delete n x) == peek x -- focus movement in the presence of delete: -- when the last window in the stack set is focused, focus moves `up'. -- usual case is that it moves 'down'. prop_delete_focus_end (x :: T) = length (index x) > 1 ==> peek (delete n y) == peek (focusUp y) where n = last (index x) y = focusWindow n x -- focus last window in stack -- focus movement in the presence of delete: -- when not in the last item in the stack, focus moves down prop_delete_focus_not_end (x :: T) = length (index x) > 1 && n /= last (index x) ==> peek (delete n x) == peek (focusDown x) where Just n = peek x -- --------------------------------------------------------------------- -- filter -- preserve order prop_filter_order (x :: T) = case stack $ workspace $ current x of Nothing -> True Just s@(Stack i _ _) -> integrate' (S.filter (/= i) s) == filter (/= i) (integrate' (Just s)) -- --------------------------------------------------------------------- -- swapUp, swapDown, swapMaster: reordiring windows -- swap is trivially reversible prop_swap_left (x :: T) = (swapUp (swapDown x)) == x prop_swap_right (x :: T) = (swapDown (swapUp x)) == x -- TODO swap is reversible -- swap is reversible, but involves moving focus back the window with -- master on it. easy to do with a mouse... {- prop_promote_reversible x b = (not . null . fromMaybe [] . flip index x . current $ x) ==> (raiseFocus y . promote . raiseFocus z . promote) x == x where _ = x :: T dir = if b then LT else GT (Just y) = peek x (Just (z:_)) = flip index x . current $ x -} -- swap doesn't change focus prop_swap_master_focus (x :: T) = peek x == (peek $ swapMaster x) -- = case peek x of -- Nothing -> True -- Just f -> focus (stack (workspace $ current (swap x))) == f prop_swap_left_focus (x :: T) = peek x == (peek $ swapUp x) prop_swap_right_focus (x :: T) = peek x == (peek $ swapDown x) -- swap is local prop_swap_master_local (x :: T) = hidden_spaces x == hidden_spaces (swapMaster x) prop_swap_left_local (x :: T) = hidden_spaces x == hidden_spaces (swapUp x) prop_swap_right_local (x :: T) = hidden_spaces x == hidden_spaces (swapDown x) -- rotation through the height of a stack gets us back to the start prop_swap_all_l (x :: T) = (foldr (const swapUp) x [1..n]) == x where n = length (index x) prop_swap_all_r (x :: T) = (foldr (const swapDown) x [1..n]) == x where n = length (index x) prop_swap_master_idempotent (x :: T) = swapMaster (swapMaster x) == swapMaster x -- --------------------------------------------------------------------- -- shift -- shift is fully reversible on current window, when focus and master -- are the same. otherwise, master may move. prop_shift_reversible i (x :: T) = i `tagMember` x ==> case peek y of Nothing -> True Just _ -> normal ((view n . shift n . view i . shift i) y) == normal y where y = swapMaster x n = tag (workspace $ current y) ------------------------------------------------------------------------ -- shiftMaster -- focus/local/idempotent same as swapMaster: prop_shift_master_focus (x :: T) = peek x == (peek $ shiftMaster x) prop_shift_master_local (x :: T) = hidden_spaces x == hidden_spaces (shiftMaster x) prop_shift_master_idempotent (x :: T) = shiftMaster (shiftMaster x) == shiftMaster x -- ordering is constant modulo the focused window: prop_shift_master_ordering (x :: T) = case peek x of Nothing -> True Just m -> L.delete m (index x) == L.delete m (index $ shiftMaster x) -- --------------------------------------------------------------------- -- shiftWin -- shiftWin on current window is the same as shift prop_shift_win_focus i (x :: T) = i `tagMember` x ==> case peek x of Nothing -> True Just w -> shiftWin i w x == shift i x -- shiftWin on a non-existant window is identity prop_shift_win_indentity i w (x :: T) = i `tagMember` x && not (w `member` x) ==> shiftWin i w x == x -- shiftWin leaves the current screen as it is, if neither i is the tag -- of the current workspace nor w on the current workspace prop_shift_win_fix_current i w (x :: T) = i `tagMember` x && w `member` x && i /= n && findTag w x /= Just n ==> (current $ x) == (current $ shiftWin i w x) where n = tag (workspace $ current x) ------------------------------------------------------------------------ -- properties for the floating layer: prop_float_reversible n (x :: T) = n `member` x ==> sink n (float n geom x) == x where geom = RationalRect 100 100 100 100 prop_float_geometry n (x :: T) = n `member` x ==> let s = float n geom x in M.lookup n (floating s) == Just geom where geom = RationalRect 100 100 100 100 prop_float_delete n (x :: T) = n `member` x ==> let s = float n geom x t = delete n s in not (n `member` t) where geom = RationalRect 100 100 100 100 ------------------------------------------------------------------------ prop_screens (x :: T) = n `elem` screens x where n = current x prop_differentiate xs = if null xs then differentiate xs == Nothing else (differentiate xs) == Just (Stack (head xs) [] (tail xs)) where _ = xs :: [Int] -- looking up the tag of the current workspace should always produce a tag. prop_lookup_current (x :: T) = lookupWorkspace scr x == Just tg where (Screen (Workspace tg _ _) scr _) = current x -- looking at a visible tag prop_lookup_visible (x :: T) = visible x /= [] ==> fromJust (lookupWorkspace scr x) `elem` tags where tags = [ tag (workspace y) | y <- visible x ] scr = last [ screen y | y <- visible x ] -- --------------------------------------------------------------------- -- testing for failure -- and help out hpc prop_abort x = unsafePerformIO $ C.catch (abort "fail") (\(C.SomeException e) -> return $ show e == "xmonad: StackSet: fail" ) where _ = x :: Int -- new should fail with an abort prop_new_abort x = unsafePerformIO $ C.catch f (\(C.SomeException e) -> return $ show e == "xmonad: StackSet: non-positive argument to StackSet.new" ) where f = new undefined{-layout-} [] [] `seq` return False _ = x :: Int -- prop_view_should_fail = view {- with some bogus data -} -- screens makes sense prop_screens_works (x :: T) = screens x == current x : visible x ------------------------------------------------------------------------ -- renaming tags -- | Rename a given tag if present in the StackSet. -- 408 renameTag :: Eq i => i -> i -> StackSet i l a s sd -> StackSet i l a s sd prop_rename1 (x::T) o n = o `tagMember` x && not (n `tagMember` x) ==> let y = renameTag o n x in n `tagMember` y -- | -- Ensure that a given set of workspace tags is present by renaming -- existing workspaces and\/or creating new hidden workspaces as -- necessary. -- prop_ensure (x :: T) l xs = let y = ensureTags l xs x in and [ n `tagMember` y | n <- xs ] -- adding a tag should create a new hidden workspace prop_ensure_append (x :: T) l n = not (n `tagMember` x) ==> (hidden y /= hidden x -- doesn't append, renames && and [ isNothing (stack z) && layout z == l | z <- hidden y, tag z == n ] ) where y = ensureTags l (n:ts) x ts = [ tag z | z <- workspaces x ] prop_mapWorkspaceId (x::T) = x == mapWorkspace id x prop_mapWorkspaceInverse (x::T) = x == mapWorkspace predTag (mapWorkspace succTag x) where predTag w = w { tag = pred $ tag w } succTag w = w { tag = succ $ tag w } prop_mapLayoutId (x::T) = x == mapLayout id x prop_mapLayoutInverse (x::T) = x == mapLayout pred (mapLayout succ x) ------------------------------------------------------------------------ -- The Tall layout -- 1 window should always be tiled fullscreen prop_tile_fullscreen rect = tile pct rect 1 1 == [rect] where pct = 1/2 -- multiple windows prop_tile_non_overlap rect windows nmaster = noOverlaps (tile pct rect nmaster windows) where _ = rect :: Rectangle pct = 3 % 100 -- splitting horizontally yields sensible results prop_split_hoziontal (NonNegative n) x = {- trace (show (rect_x x ,rect_width x ,rect_x x + fromIntegral (rect_width x) ,map rect_x xs)) $ -} sum (map rect_width xs) == rect_width x && all (== rect_height x) (map rect_height xs) && (map rect_x xs) == (sort $ map rect_x xs) where xs = splitHorizontally n x -- splitting horizontally yields sensible results prop_splitVertically (r :: Rational) x = rect_x x == rect_x a && rect_x x == rect_x b && rect_width x == rect_width a && rect_width x == rect_width b {- trace (show (rect_x x ,rect_width x ,rect_x x + fromIntegral (rect_width x) ,map rect_x xs)) $ -} where (a,b) = splitVerticallyBy r x -- pureLayout works. prop_purelayout_tall n r1 r2 rect (t :: T) = isJust (peek t) ==> length ts == length (index t) && noOverlaps (map snd ts) && description layoot == "Tall" where layoot = Tall n r1 r2 st = fromJust . stack . workspace . current $ t ts = pureLayout layoot rect st -- Test message handling of Tall -- what happens when we send a Shrink message to Tall prop_shrink_tall (NonNegative n) (NonZero (NonNegative delta)) (NonNegative frac) = n == n' && delta == delta' -- these state components are unchanged && frac' <= frac && (if frac' < frac then frac' == 0 || frac' == frac - delta else frac == 0 ) -- remaining fraction should shrink where l1 = Tall n delta frac Just l2@(Tall n' delta' frac') = l1 `pureMessage` (SomeMessage Shrink) -- pureMessage :: layout a -> SomeMessage -> Maybe (layout a) -- what happens when we send a Shrink message to Tall prop_expand_tall (NonNegative n) (NonZero (NonNegative delta)) (NonNegative n1) (NonZero (NonNegative d1)) = n == n' && delta == delta' -- these state components are unchanged && frac' >= frac && (if frac' > frac then frac' == 1 || frac' == frac + delta else frac == 1 ) -- remaining fraction should shrink where frac = min 1 (n1 % d1) l1 = Tall n delta frac Just l2@(Tall n' delta' frac') = l1 `pureMessage` (SomeMessage Expand) -- pureMessage :: layout a -> SomeMessage -> Maybe (layout a) -- what happens when we send an IncMaster message to Tall prop_incmaster_tall (NonNegative n) (NonZero (NonNegative delta)) (NonNegative frac) (NonNegative k) = delta == delta' && frac == frac' && n' == n + k where l1 = Tall n delta frac Just l2@(Tall n' delta' frac') = l1 `pureMessage` (SomeMessage (IncMasterN k)) -- pureMessage :: layout a -> SomeMessage -> Maybe (layout a) -- toMessage LT = SomeMessage Shrink -- toMessage EQ = SomeMessage Expand -- toMessage GT = SomeMessage (IncMasterN 1) ------------------------------------------------------------------------ -- Full layout -- pureLayout works for Full prop_purelayout_full rect (t :: T) = isJust (peek t) ==> length ts == 1 -- only one window to view && snd (head ts) == rect -- and sets fullscreen && fst (head ts) == fromJust (peek t) -- and the focused window is shown where layoot = Full st = fromJust . stack . workspace . current $ t ts = pureLayout layoot rect st -- what happens when we send an IncMaster message to Full --- Nothing prop_sendmsg_full (NonNegative k) = isNothing (Full `pureMessage` (SomeMessage (IncMasterN k))) prop_desc_full = description Full == show Full ------------------------------------------------------------------------ prop_desc_mirror n r1 r2 = description (Mirror $! t) == "Mirror Tall" where t = Tall n r1 r2 ------------------------------------------------------------------------ noOverlaps [] = True noOverlaps [_] = True noOverlaps xs = and [ verts a `notOverlap` verts b | a <- xs , b <- filter (a /=) xs ] where verts (Rectangle a b w h) = (a,b,a + fromIntegral w - 1, b + fromIntegral h - 1) notOverlap (left1,bottom1,right1,top1) (left2,bottom2,right2,top2) = (top1 < bottom2 || top2 < bottom1) || (right1 < left2 || right2 < left1) ------------------------------------------------------------------------ -- Aspect ratios prop_resize_inc (NonZero (NonNegative inc_w),NonZero (NonNegative inc_h)) b@(w,h) = w' `mod` inc_w == 0 && h' `mod` inc_h == 0 where (w',h') = applyResizeIncHint a b a = (inc_w,inc_h) prop_resize_inc_extra ((NonNegative inc_w)) b@(w,h) = (w,h) == (w',h') where (w',h') = applyResizeIncHint a b a = (-inc_w,0::Dimension)-- inc_h) prop_resize_max (NonZero (NonNegative inc_w),NonZero (NonNegative inc_h)) b@(w,h) = w' <= inc_w && h' <= inc_h where (w',h') = applyMaxSizeHint a b a = (inc_w,inc_h) prop_resize_max_extra ((NonNegative inc_w)) b@(w,h) = (w,h) == (w',h') where (w',h') = applyMaxSizeHint a b a = (-inc_w,0::Dimension)-- inc_h) ------------------------------------------------------------------------ main :: IO () main = do args <- fmap (drop 1) getArgs let n = if null args then 100 else read (head args) (results, passed) <- liftM unzip $ mapM (\(s,a) -> printf "%-40s: " s >> a n) tests printf "Passed %d tests!\n" (sum passed) when (not . and $ results) $ fail "Not all tests passed!" where tests = [("StackSet invariants" , mytest prop_invariant) ,("empty: invariant" , mytest prop_empty_I) ,("empty is empty" , mytest prop_empty) ,("empty / current" , mytest prop_empty_current) ,("empty / member" , mytest prop_member_empty) ,("view : invariant" , mytest prop_view_I) ,("view sets current" , mytest prop_view_current) ,("view idempotent" , mytest prop_view_idem) ,("view reversible" , mytest prop_view_reversible) -- ,("view / xinerama" , mytest prop_view_xinerama) ,("view is local" , mytest prop_view_local) ,("greedyView : invariant" , mytest prop_greedyView_I) ,("greedyView sets current" , mytest prop_greedyView_current) ,("greedyView is safe " , mytest prop_greedyView_current_id) ,("greedyView idempotent" , mytest prop_greedyView_idem) ,("greedyView reversible" , mytest prop_greedyView_reversible) ,("greedyView is local" , mytest prop_greedyView_local) -- -- ,("valid workspace xinerama", mytest prop_lookupWorkspace) ,("peek/member " , mytest prop_member_peek) ,("index/length" , mytest prop_index_length) ,("focus left : invariant", mytest prop_focusUp_I) ,("focus master : invariant", mytest prop_focusMaster_I) ,("focus right: invariant", mytest prop_focusDown_I) ,("focusWindow: invariant", mytest prop_focus_I) ,("focus left/master" , mytest prop_focus_left_master) ,("focus right/master" , mytest prop_focus_right_master) ,("focus master/master" , mytest prop_focus_master_master) ,("focusWindow master" , mytest prop_focusWindow_master) ,("focus left/right" , mytest prop_focus_left) ,("focus right/left" , mytest prop_focus_right) ,("focus all left " , mytest prop_focus_all_l) ,("focus all right " , mytest prop_focus_all_r) ,("focus down is local" , mytest prop_focus_down_local) ,("focus up is local" , mytest prop_focus_up_local) ,("focus master is local" , mytest prop_focus_master_local) ,("focus master idemp" , mytest prop_focusMaster_idem) ,("focusWindow is local", mytest prop_focusWindow_local) ,("focusWindow works" , mytest prop_focusWindow_works) ,("focusWindow identity", mytest prop_focusWindow_identity) ,("findTag" , mytest prop_findIndex) ,("allWindows/member" , mytest prop_allWindowsMember) ,("currentTag" , mytest prop_currentTag) ,("insert: invariant" , mytest prop_insertUp_I) ,("insert/new" , mytest prop_insert_empty) ,("insert is idempotent", mytest prop_insert_idem) ,("insert is reversible", mytest prop_insert_delete) ,("insert is local" , mytest prop_insert_local) ,("insert duplicates" , mytest prop_insert_duplicate) ,("insert/peek " , mytest prop_insert_peek) ,("insert/size" , mytest prop_size_insert) ,("delete: invariant" , mytest prop_delete_I) ,("delete/empty" , mytest prop_empty) ,("delete/member" , mytest prop_delete) ,("delete is reversible", mytest prop_delete_insert) ,("delete is local" , mytest prop_delete_local) ,("delete/focus" , mytest prop_delete_focus) ,("delete last/focus up", mytest prop_delete_focus_end) ,("delete ~last/focus down", mytest prop_delete_focus_not_end) ,("filter preserves order", mytest prop_filter_order) ,("swapMaster: invariant", mytest prop_swap_master_I) ,("swapUp: invariant" , mytest prop_swap_left_I) ,("swapDown: invariant", mytest prop_swap_right_I) ,("swapMaster id on focus", mytest prop_swap_master_focus) ,("swapUp id on focus", mytest prop_swap_left_focus) ,("swapDown id on focus", mytest prop_swap_right_focus) ,("swapMaster is idempotent", mytest prop_swap_master_idempotent) ,("swap all left " , mytest prop_swap_all_l) ,("swap all right " , mytest prop_swap_all_r) ,("swapMaster is local" , mytest prop_swap_master_local) ,("swapUp is local" , mytest prop_swap_left_local) ,("swapDown is local" , mytest prop_swap_right_local) ,("shiftMaster id on focus", mytest prop_shift_master_focus) ,("shiftMaster is local", mytest prop_shift_master_local) ,("shiftMaster is idempotent", mytest prop_shift_master_idempotent) ,("shiftMaster preserves ordering", mytest prop_shift_master_ordering) ,("shift: invariant" , mytest prop_shift_I) ,("shift is reversible" , mytest prop_shift_reversible) ,("shiftWin: invariant" , mytest prop_shift_win_I) ,("shiftWin is shift on focus" , mytest prop_shift_win_focus) ,("shiftWin fix current" , mytest prop_shift_win_fix_current) ,("floating is reversible" , mytest prop_float_reversible) ,("floating sets geometry" , mytest prop_float_geometry) ,("floats can be deleted", mytest prop_float_delete) ,("screens includes current", mytest prop_screens) ,("differentiate works", mytest prop_differentiate) ,("lookupTagOnScreen", mytest prop_lookup_current) ,("lookupTagOnVisbleScreen", mytest prop_lookup_visible) ,("screens works", mytest prop_screens_works) ,("renaming works", mytest prop_rename1) ,("ensure works", mytest prop_ensure) ,("ensure hidden semantics", mytest prop_ensure_append) ,("mapWorkspace id", mytest prop_mapWorkspaceId) ,("mapWorkspace inverse", mytest prop_mapWorkspaceInverse) ,("mapLayout id", mytest prop_mapLayoutId) ,("mapLayout inverse", mytest prop_mapLayoutInverse) -- testing for failure: ,("abort fails", mytest prop_abort) ,("new fails with abort", mytest prop_new_abort) ,("shiftWin identity", mytest prop_shift_win_indentity) -- tall layout ,("tile 1 window fullsize", mytest prop_tile_fullscreen) ,("tiles never overlap", mytest prop_tile_non_overlap) ,("split hozizontally", mytest prop_split_hoziontal) ,("split verticalBy", mytest prop_splitVertically) ,("pure layout tall", mytest prop_purelayout_tall) ,("send shrink tall", mytest prop_shrink_tall) ,("send expand tall", mytest prop_expand_tall) ,("send incmaster tall", mytest prop_incmaster_tall) -- full layout ,("pure layout full", mytest prop_purelayout_full) ,("send message full", mytest prop_sendmsg_full) ,("describe full", mytest prop_desc_full) ,("describe mirror", mytest prop_desc_mirror) -- resize hints ,("window hints: inc", mytest prop_resize_inc) ,("window hints: inc all", mytest prop_resize_inc_extra) ,("window hints: max", mytest prop_resize_max) ,("window hints: max all ", mytest prop_resize_max_extra) ] ------------------------------------------------------------------------ -- -- QC driver -- debug = False mytest :: Testable a => a -> Int -> IO (Bool, Int) mytest a n = mycheck defaultConfig { configMaxTest=n , configEvery = \n args -> let s = show n in s ++ [ '\b' | _ <- s ] } a -- , configEvery= \n args -> if debug then show n ++ ":\n" ++ unlines args else [] } a mycheck :: Testable a => Config -> a -> IO (Bool, Int) mycheck config a = do rnd <- newStdGen mytests config (evaluate a) rnd 0 0 [] mytests :: Config -> Gen Result -> StdGen -> Int -> Int -> [[String]] -> IO (Bool, Int) mytests config gen rnd0 ntest nfail stamps | ntest == configMaxTest config = done "OK," ntest stamps >> return (True, ntest) | nfail == configMaxFail config = done "Arguments exhausted after" ntest stamps >> return (True, ntest) | otherwise = do putStr (configEvery config ntest (arguments result)) >> hFlush stdout case ok result of Nothing -> mytests config gen rnd1 ntest (nfail+1) stamps Just True -> mytests config gen rnd1 (ntest+1) nfail (stamp result:stamps) Just False -> putStr ( "Falsifiable after " ++ show ntest ++ " tests:\n" ++ unlines (arguments result) ) >> hFlush stdout >> return (False, ntest) where result = generate (configSize config ntest) rnd2 gen (rnd1,rnd2) = split rnd0 done :: String -> Int -> [[String]] -> IO () done mesg ntest stamps = putStr ( mesg ++ " " ++ show ntest ++ " tests" ++ table ) where table = display . map entry . reverse . sort . map pairLength . group . sort . filter (not . null) $ stamps display [] = ".\n" display [x] = " (" ++ x ++ ").\n" display xs = ".\n" ++ unlines (map (++ ".") xs) pairLength xss@(xs:_) = (length xss, xs) entry (n, xs) = percentage n ntest ++ " " ++ concat (intersperse ", " xs) percentage n m = show ((100 * n) `div` m) ++ "%" ------------------------------------------------------------------------ instance Arbitrary Char where arbitrary = choose ('a','z') coarbitrary n = coarbitrary (ord n) instance Random Word8 where randomR = integralRandomR random = randomR (minBound,maxBound) instance Arbitrary Word8 where arbitrary = choose (minBound,maxBound) coarbitrary n = variant (fromIntegral ((fromIntegral n) `rem` 4)) instance Random Word64 where randomR = integralRandomR random = randomR (minBound,maxBound) instance Arbitrary Word64 where arbitrary = choose (minBound,maxBound) coarbitrary n = variant (fromIntegral ((fromIntegral n) `rem` 4)) integralRandomR :: (Integral a, RandomGen g) => (a,a) -> g -> (a,g) integralRandomR (a,b) g = case randomR (fromIntegral a :: Integer, fromIntegral b :: Integer) g of (x,g) -> (fromIntegral x, g) instance Arbitrary Position where arbitrary = do n <- arbitrary :: Gen Word8 return (fromIntegral n) coarbitrary = undefined instance Arbitrary Dimension where arbitrary = do n <- arbitrary :: Gen Word8 return (fromIntegral n) coarbitrary = undefined instance Arbitrary Rectangle where arbitrary = do sx <- arbitrary sy <- arbitrary sw <- arbitrary sh <- arbitrary return $ Rectangle sx sy sw sh coarbitrary = undefined instance Arbitrary Rational where arbitrary = do n <- arbitrary d' <- arbitrary let d = if d' == 0 then 1 else d' return (n % d) coarbitrary = undefined ------------------------------------------------------------------------ -- QC 2 -- from QC2 -- | NonEmpty xs: guarantees that xs is non-empty. newtype NonEmptyList a = NonEmpty [a] deriving ( Eq, Ord, Show, Read ) instance Arbitrary a => Arbitrary (NonEmptyList a) where arbitrary = NonEmpty `fmap` (arbitrary `suchThat` (not . null)) coarbitrary = undefined newtype NonEmptyNubList a = NonEmptyNubList [a] deriving ( Eq, Ord, Show, Read ) instance (Eq a, Arbitrary a) => Arbitrary (NonEmptyNubList a) where arbitrary = NonEmptyNubList `fmap` ((liftM nub arbitrary) `suchThat` (not . null)) coarbitrary = undefined type Positive a = NonZero (NonNegative a) newtype NonZero a = NonZero a deriving ( Eq, Ord, Num, Integral, Real, Enum, Show, Read ) instance (Num a, Ord a, Arbitrary a) => Arbitrary (NonZero a) where arbitrary = fmap NonZero $ arbitrary `suchThat` (/= 0) coarbitrary = undefined newtype NonNegative a = NonNegative a deriving ( Eq, Ord, Num, Integral, Real, Enum, Show, Read ) instance (Num a, Ord a, Arbitrary a) => Arbitrary (NonNegative a) where arbitrary = frequency [ (5, (NonNegative . abs) `fmap` arbitrary) , (1, return 0) ] coarbitrary = undefined newtype EmptyStackSet = EmptyStackSet T deriving Show instance Arbitrary EmptyStackSet where arbitrary = do (NonEmptyNubList ns) <- arbitrary (NonEmptyNubList sds) <- arbitrary l <- arbitrary -- there cannot be more screens than workspaces: return . EmptyStackSet . new l ns $ take (min (length ns) (length sds)) sds coarbitrary = error "coarbitrary EmptyStackSet" -- | Generates a value that satisfies a predicate. suchThat :: Gen a -> (a -> Bool) -> Gen a gen `suchThat` p = do mx <- gen `suchThatMaybe` p case mx of Just x -> return x Nothing -> sized (\n -> resize (n+1) (gen `suchThat` p)) -- | Tries to generate a value that satisfies a predicate. suchThatMaybe :: Gen a -> (a -> Bool) -> Gen (Maybe a) gen `suchThatMaybe` p = sized (try 0 . max 1) where try _ 0 = return Nothing try k n = do x <- resize (2*k+n) gen if p x then return (Just x) else try (k+1) (n-1) xmonad-0.11/util/0000755000000000000000000000000012070436203012061 5ustar0000000000000000xmonad-0.11/util/GenerateManpage.hs0000644000000000000000000000707512070436203015451 0ustar0000000000000000-- Unlike the rest of xmonad, this file is copyright under the terms of the -- GPL. -- -- Generates man/xmonad.1 from man/xmonad.1.in by filling the list of -- keybindings with values scraped from Config.hs -- -- Uses cabal to grab the xmonad version from xmonad.cabal -- -- Uses pandoc to convert the "xmonad.1.markdown" to "xmonad.1" -- -- Format for the docstrings in Config.hs takes the following form: -- -- -- mod-x %! Frob the whatsit -- -- "Frob the whatsit" will be used as the description for keybinding "mod-x" -- -- If the keybinding name is omitted, it will try to guess from the rest of the -- line. For example: -- -- [ ((modMask .|. shiftMask, xK_Return), spawn "xterm") -- %! Launch an xterm -- -- Here, mod-shift-return will be used as the keybinding name. import Control.Monad import Control.Applicative import Text.Regex.Posix import Data.Char import Data.List import Distribution.PackageDescription.Parse import Distribution.Verbosity import Distribution.Package import Distribution.PackageDescription import Text.PrettyPrint.HughesPJ import Distribution.Text import Text.Pandoc -- works with 1.6 releaseDate = "31 December 2012" trim :: String -> String trim = reverse . dropWhile isSpace . reverse . dropWhile isSpace guessKeys line = concat $ intersperse "-" (modifiers ++ [map toLower key]) where modifiers = map (!!1) (line =~ "(mod|shift|control)Mask") (_, _, _, [key]) = line =~ "xK_(\\w+)" :: (String, String, String, [String]) binding :: [String] -> (String, String) binding [ _, bindingLine, "", desc ] = (guessKeys bindingLine, desc) binding [ _, _, keyCombo, desc ] = (keyCombo, desc) allBindings :: String -> [(String, String)] allBindings xs = map (binding . map trim) (xs =~ "(.*)--(.*)%!(.*)") -- FIXME: What escaping should we be doing on these strings? markdownDefn :: (String, String) -> String markdownDefn (key, desc) = key ++ "\n: " ++ desc replace :: Eq a => a -> a -> [a] -> [a] replace x y = map (\a -> if a == x then y else a) -- rawSystem "pandoc" ["--read=markdown","--write=man","man/xmonad.1.markdown"] main = do releaseName <- (show . disp . package . packageDescription) `liftM`readPackageDescription normal "xmonad.cabal" keybindings <- (intercalate "\n\n" . map markdownDefn . allBindings) `liftM` readFile "./XMonad/Config.hs" let manHeader = unwords [".TH xmonad 1","\""++releaseDate++"\"",releaseName,"\"xmonad manual\""] writeOpts = defaultWriterOptions -- { writerLiterateHaskell = True } parsed <- readMarkdown defaultParserState { stateLiterateHaskell = True } . unlines . replace "___KEYBINDINGS___" keybindings . lines <$> readFile "./man/xmonad.1.markdown" Right template <- getDefaultTemplate Nothing "man" writeFile "./man/xmonad.1" . (manHeader ++) . writeMan writeOpts{ writerStandalone = True, writerTemplate = template } $ parsed putStrLn "Documentation created: man/xmonad.1" Right template <- getDefaultTemplate Nothing "html" writeFile "./man/xmonad.1.html" . writeHtmlString writeOpts { writerVariables = [("include-before" ,"

"++releaseName++"

"++ "

Section: xmonad manual (1)
"++ "Updated: "++releaseDate++"

"++ "
")] , writerStandalone = True , writerTemplate = template , writerTableOfContents = True } $ parsed putStrLn "Documentation created: man/xmonad.1.html" xmonad-0.11/XMonad/0000755000000000000000000000000012070436203012272 5ustar0000000000000000xmonad-0.11/XMonad/ManageHook.hs0000644000000000000000000000732412070436203014645 0ustar0000000000000000{-# 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 Prelude hiding (catch) import XMonad.Core import Graphics.X11.Xlib.Extras import Graphics.X11.Xlib (Display, Window, internAtom, wM_NAME) import Control.Exception.Extensible (bracket, catch, SomeException(..)) 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) `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 `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 xmonad-0.11/XMonad/Core.hs0000644000000000000000000005353612070436203013532 0ustar0000000000000000{-# 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 hiding ( catch ) import Codec.Binary.UTF8.String (encodeString) import Control.Exception.Extensible (catch, fromException, try, bracket, throw, finally, SomeException(..)) import Control.Applicative import Control.Monad.State import Control.Monad.Reader 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 } 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 type ManageHook = Query (Endo WindowSet) newtype Query a = Query (ReaderT Window X a) deriving (Functor, 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 -- | 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 `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 def a = fromMaybe def `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 `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", "-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 = 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 <$> 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 () xmonad-0.11/XMonad/Operations.hs0000644000000000000000000005732312070436203014763 0ustar0000000000000000{-# 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 io $ do selectInput d w (clientMask .&. complement structureNotifyMask) unmapWindow d w selectInput d w clientMask 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) }) -- | The client events that xmonad is interested in clientMask :: EventMask clientMask = structureNotifyMask .|. enterWindowMask .|. propertyChangeMask -- | 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 io $ selectInput d w clientMask 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 bw <- fi <$> asks (borderWidth . config) 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 xmonad-0.11/XMonad/Layout.hs0000644000000000000000000002046312070436203014110 0ustar0000000000000000{-# 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' xmonad-0.11/XMonad/Main.hsc0000644000000000000000000003700312070436203013660 0ustar0000000000000000{-# 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 $ substructureRedirectMask .|. substructureNotifyMask .|. enterWindowMask .|. leaveWindowMask .|. structureNotifyMask .|. buttonPressMask 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.defaultConfig return (fromMaybe nbc_ v) fbc <- do v <- initColor dpy $ focusedBorderColor xmc ~(Just fbc_) <- initColor dpy $ focusedBorderColor Default.defaultConfig 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 io $ ungrabKey dpy anyKey anyModifier rootw ks <- asks keyActions forM_ (M.keys ks) $ \(mask,sym) -> do kc <- io $ keysymToKeycode dpy sym -- "If the specified KeySym is not defined for any KeyCode, -- XKeysymToKeycode() returns zero." when (kc /= 0) $ mapM_ (grab kc . (mask .|.)) =<< extraModifiers -- | XXX comment me grabButtons :: X () grabButtons = do XConf { display = dpy, theRoot = rootw } <- ask let grab button mask = io $ grabButton dpy button mask rootw False buttonPressMask grabModeAsync grabModeSync none none io $ ungrabButton dpy anyButton anyModifier rootw ems <- extraModifiers ba <- asks buttonActions mapM_ (\(m,b) -> mapM_ (grab b . (m .|.)) ems) (M.keys $ ba) -- | @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 xmonad-0.11/XMonad/Config.hs0000644000000000000000000003163012070436203014036 0ustar0000000000000000{-# OPTIONS -fno-warn-missing-signatures #-} ----------------------------------------------------------------------------- -- | -- Module : XMonad.Config -- Copyright : (c) Spencer Janssen 2007 -- License : BSD3-style (see LICENSE) -- -- Maintainer : dons@galois.com -- Stability : stable -- Portability : portable -- -- This module specifies the default configuration values for xmonad. -- -- 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 'defaultConfig'. 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) where -- -- Useful imports -- import XMonad.Core as XMonad hiding (workspaces,manageHook,keys,logHook,startupHook,borderWidth,mouseBindings ,layoutHook,modMask,terminal,normalBorderColor,focusedBorderColor,focusFollowsMouse ,handleEventHook,clickJustFocuses) import qualified XMonad.Core as XMonad (workspaces,manageHook,keys,logHook,startupHook,borderWidth,mouseBindings ,layoutHook,modMask,terminal,normalBorderColor,focusedBorderColor,focusFollowsMouse ,handleEventHook,clickJustFocuses) import XMonad.Layout import XMonad.Operations import XMonad.ManageHook import qualified XMonad.StackSet as W import Data.Bits ((.|.)) 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 ------------------------------------------------------------------------ -- 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) ] -- | The default set of configuration values itself defaultConfig = 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 } -- | 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"]xmonad-0.11/XMonad/StackSet.hs0000644000000000000000000005416512070436203014362 0ustar0000000000000000{-# 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