xmonad-0.17.2/0000755000000000000000000000000007346545000011260 5ustar0000000000000000xmonad-0.17.2/CHANGES.md0000644000000000000000000002365607346545000012666 0ustar0000000000000000# Change Log / Release Notes ## 0.17.2 (April 2, 2023) ### Bug Fixes * Fixed the build with GHC 9.6. ## 0.17.1 (September 3, 2022) ### Enhancements * Added custom cursor shapes for resizing and moving windows. * Exported `cacheNumlockMask` and `mkGrabs` from `XMonad.Operations`. ### Bug Fixes * Fixed border color of windows with alpha channel. Now all windows have the same opaque border color. * Change the main loop to try to avoid [GHC bug 21708] on systems running GHC 9.2 up to version 9.2.3. The issue has been fixed in [GHC 9.2.4] and all later releases. [GHC bug 21708]: https://gitlab.haskell.org/ghc/ghc/-/issues/21708 [GHC 9.2.4]: https://discourse.haskell.org/t/ghc-9-2-4-released/4851 ## 0.17.0 (October 27, 2021) ### Enhancements * Migrated `X.L.LayoutCombinators.(|||)` into `XMonad.Layout`, providing the ability to directly jump to a layout with the `JumpToLayout` message. * Recompilation now detects `stack.yaml` (can be a symlink) alongside `xmonad.hs` and switches to using `stack ghc`. We also updated INSTALL.md with instructions for cabal-install that lead to correct recompilation. Deprecation warnings during recompilation are no longer suppressed to make it easier for us to clean up the codebase. These can still be suppressed manually using an `OPTIONS_GHC` pragma with `-Wno-deprecations`. * Improve handling of XDG directories. 1. If all three of xmonad's environment variables (`XMONAD_DATA_DIR,` `XMONAD_CONFIG_DIR`, and `XMONAD_CACHE_DIR`) are set, use them. 2. If there is a build script called `build` (see [these build scripts] for usage examples) or configuration `xmonad.hs` in `~/.xmonad`, set all three directories to `~/.xmonad`. 3. Otherwise, use the `xmonad` directory in `XDG_DATA_HOME`, `XDG_CONFIG_HOME`, and `XDG_CACHE_HOME` (or their respective fallbacks). These directories are created if necessary. In the cases of 1. and 3., the build script or executable is expected to be in the config dir. Additionally, the xmonad config binary and intermediate object files were moved to the cache directory (only relevant if using XDG or `XMONAD_CACHE_DIR`). * Added `Foldable`, `Functor`, and `Traversable` instances for `Stack`. * Added `Typeable layout` constraint to `LayoutClass`, making it possible to cast `Layout` back into a concrete type and extract current layout state from it. * Export constructor for `Choose` and `CLR` from `Module.Layout` to allow pattern-matching on the left and right sub-layouts of `Choose l r a`. * Added `withUnfocused` function to `XMonad.Operations`, allowing for `X` operations to be applied to unfocused windows. * Added `willFloat` function to `XMonad.ManageHooks` to detect whether the (about to be) managed window will be a floating window or not [these build scripts]: https://github.com/xmonad/xmonad-testing/tree/master/build-scripts ### Bug Fixes * Fixed a bug when using multiple screens with different dimensions, causing some floating windows to be smaller/larger than the size they requested. * Compatibility with GHC 9.0 * Fixed dunst notifications being obscured when moving floats. https://github.com/xmonad/xmonad/issues/208 ### Breaking Changes * Made `(<&&>)` and `(<||>)` non-strict in their right operand; i.e., these operators now implement short-circuit evaluation so the right operand is evaluated only if the left operand does not suffice to determine the result. * Change `ScreenDetail` to a newtype and make `RationalRect` strict in its contents. * Added the `extensibleConf` field to `XConfig` which makes it easier for contrib modules to have composable configuration (custom hooks, …). * `util/GenerateManpage.hs` is no longer distributed in the tarball. Instead, the manpage source is regenerated and manpage rebuilt automatically in CI. * `DestroyWindowEvent` is now broadcasted to layouts to let them know window-specific resources can be discarded. ## 0.15 (September 30, 2018) * Reimplement `sendMessage` to deal properly with windowset changes made during handling. * Add new library functions `windowBracket` and `modifyWindowSet` to `XMonad.Operations`. ## 0.14.2 (August 21, 2018) ### Bug Fixes * Add the sample configuration file xmonad.hs again to the release tarball. [https://github.com/xmonad/xmonad/issues/181] ## 0.14.1 (August 20, 2018) ### Breaking Changes * The cabal build no longer installs xmonad.hs, xmonad.1, and xmonad.1.html as data files. The location cabal picks for chose files isn't useful as standard tools like man(1) won't find them there. Instead, we rely on distributors to pick up the files from the source tarball during the build and to install them into proper locations where their users expect them. [https://github.com/xmonad/xmonad/pull/127] ### Bug Fixes * Add support for GHC 8.6.x by providing an instance for 'MonadFail X'. A side effect of that change is that our code no longer compiles with GHC versions prior to 8.0.x. We could work around that, no doubt, but the resulting code would require CPP and Cabal flags and whatnot. It feels more reasonable to just require a moderately recent compiler instead of going through all that trouble. * xmonad no longer always recompile on startup. Now it only does so if the executable does not have the name that would be used for the compilation output. The purpose of recompiling and executing the results in this case is so that the `xmonad` executable in the package can be used with custom configurations. ### Enhancements * Whenever xmonad recompiles, it now explains how it is attempting to recompile, by outputting logs to stderr. If you are using xmonad as a custom X session, then this will end up in a `.xsession-errors` file. ## 0.14 (July 30, 2018) ### Bug Fixes * The state file that xmonad uses while restarting itself is now removed after it is processed. This fixes a bug that manifested in several different ways: - Names of old workspaces would be resurrected after a restart - Screen sizes would be wrong after changing monitor configuration (#90) - `spawnOnce` stopped working (xmonad/xmonad-contrib#155) - Focus did not follow when moving between workspaces (#87) - etc. * Recover old behavior (in 0.12) when `focusFollowsMouse == True`: the focus follows when the mouse enters another workspace but not moving into any window. * Compiles with GHC 8.4.1 * Restored compatability with GHC version prior to 8.0.1 by removing the dependency on directory version 1.2.3. ## 0.13 (February 10, 2017) ### Breaking Changes * When restarting xmonad, resume state is no longer passed to the next process via the command line. Instead, a temporary state file is created and xmonad's state is serialized to that file. When upgrading to 0.13 from a previous version, the `--resume` command line option will automatically migrate to a state file. This fixes issue #12. ### Enhancements * You can now control which directory xmonad uses for finding your configuration file and which one is used for storing the compiled version of your configuration. In order of preference: 1. New environment variables. If you want to use these ensure you set the correct environment variable and also create the directory it references: - `XMONAD_CONFIG_DIR` - `XMONAD_CACHE_DIR` - `XMONAD_DATA_DIR` 2. The `~/.xmonad` directory. 3. XDG Base Directory Specification directories, if they exist: - `XDG_CONFIG_HOME/xmonad` - `XDG_CACHE_HOME/xmonad` - `XDG_DATA_HOME/xmonad` If none of these directories exist then one will be created using the following logic: If the relevant environment variable mentioned in step (1) above is set, the referent directory will be created and used. Otherwise `~/.xmonad` will be created and used. This fixes a few issues, notably #7 and #56. * A custom build script can be used when xmonad is given the `--recompile` command line option. If an executable named `build` exists in the xmonad configuration directory it will be called instead of `ghc`. It takes one argument, the name of the executable binary it must produce. This fixes #8. (One of two possible custom build solutions. See the next entry for another solution.) * For users who build their xmonad configuration using tools such as cabal or stack, there is another option for executing xmonad. Instead of running the `xmonad` executable directly, arrange to have your login manager run your configuration binary instead. Then, in your binary, use the new `launch` command instead of `xmonad`. This will keep xmonad from using its configuration file checking/compiling code and directly start the window manager without `exec`ing any other binary. See the documentation for the `launch` function in `XMonad.Main` for more details. Fixes #8. (Second way to have a custom build environment for XMonad. See previous entry for another solution.) ## 0.12 (December 14, 2015) * Compiles with GHC 7.10.2, 7.8.4, and 7.6.3 * Use of [data-default][] allows using `def` where previously you had to write `defaultConfig`, `defaultXPConfig`, etc. * The [setlocale][] package is now used instead of a binding shipped with xmonad proper allowing the use of `Main.hs` instead of `Main.hsc` * No longer encodes paths for `spawnPID` * The default `manageHook` no longer floats Gimp windows * Doesn't crash when there are fewer workspaces than screens * `Query` is now an instance of `Applicative` * Various improvements to the example configuration file [data-default]: http://hackage.haskell.org/package/data-default [setlocale]: https://hackage.haskell.org/package/setlocale xmonad-0.17.2/CONTRIBUTING.md0000644000000000000000000001266707346545000013525 0ustar0000000000000000# Contributing to xmonad and xmonad-contrib ## Before Creating a GitHub Issue New issue submissions should adhere to the following guidelines: * Does your issue have to do with [xmonad][], [xmonad-contrib][], or maybe even with the [X11][] library? Please submit your issue to the **correct** GitHub repository. * To help you figure out which repository to submit your issue to, and to help us resolve the problem you are having, create the smallest configuration file you can that reproduces the problem. You may find that the [xmonad-testing][] repository is helpful in reproducing the problem with a smaller configuration file. Once you've done that please include the configuration file with your GitHub issue. * If possible, use the [xmonad-testing][] repository to test your configuration with the bleeding-edge development version of xmonad and xmonad-contrib. We might have already fixed your problem. ## Contributing Changes/Patches Have a change to xmonad that you want included in the next release? Awesome! Here are a few things to keep in mind: * Review the above section about creating GitHub issues. * It's always best to talk with the community before making any nontrivial changes to xmonad. There are a couple of ways you can chat with us: - Join the [`#xmonad` IRC channel] on `irc.libera.chat` or the official [matrix channel], which is linked to IRC. This is the preferred (and fastest!) way to get into contact with us. - Post a message to the [mailing list][ml]. * [XMonad.Doc.Developing][xmonad-doc-developing] is a great resource to get an overview of xmonad. Make sure to also check it if you want more details on the coding style. * Continue reading this document! ## Expediting Reviews and Merges Here are some tips for getting your changes merged into xmonad: * If your changes can go into [xmonad-contrib][] instead of [xmonad][], please do so. We rarely accept new features to xmonad. (Not that we don't accept changes to xmonad, just that we prefer changes to xmonad-contrib instead.) * Change the fewest files as possible. If it makes sense, submit a completely new module to xmonad-contrib. * Your changes should include relevant entries in the `CHANGES.md` file. Help us communicate changes to the community. * Make sure you test your changes against the most recent commit of [xmonad][] (and [xmonad-contrib][], if you're contributing there). If you're adding a new module or functionality, make sure to add an example in the documentation and in the PR description. * Make sure you run the automated tests. Both [xmonad-contrib][] and [xmonad][] have test-suites that you could run with `stack test` for example. * When committing, try to follow existing practices. For more information on what good commit messages look like, see [How to Write a Git Commit Message][commit-cbeams] and the [Kernel documentation][commit-kernel] about committing logical changes separately. ## Style Guidelines Below are some common style guidelines that all of the core modules follow. Before submitting a pull request, make sure that your code does as well! * 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 module that you are making changes to (`n` spaces for indentation, where to break long type signatures, …). * New code should not introduce any new warnings. If you want to check this yourself before submitting a pull request, there is the `pedantic` flag, which is enforced in our CI. You can enable it by building your changes with `stack build --flag xmonad:pedantic` or `cabal build --flag pedantic`. * Likewise, your code should be free of [hlint] warnings; this is also enforced in our GitHub CI. * Partial functions are to be avoided: the window manager should not crash, so do not call `error` or `undefined`. * 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). ## Keep rocking! xmonad is a passion project created and maintained by the community. We'd love for you to maintain your own contributed modules (approve changes from other contributors, review code, etc.). However, before we'd be comfortable adding you to the [xmonad GitHub organization][xmonad-gh-org] we need to trust that you have sufficient knowledge of Haskell and git; and have a way of chatting with you ([IRC, Matrix, etc.][community]). [hlint]: https://github.com/ndmitchell/hlint [xmonad]: https://github.com/xmonad/xmonad [xmonad-contrib]: https://github.com/xmonad/xmonad-contrib [xmonad-testing]: https://github.com/xmonad/xmonad-testing [x11]: https://github.com/xmonad/X11 [ml]: https://mail.haskell.org/cgi-bin/mailman/listinfo/xmonad [xmonad-doc-developing]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Doc-Developing.html [`#xmonad` IRC channel]: https://web.libera.chat/#xmonad [matrix channel]: https://matrix.to/#/#xmonad:matrix.org [commit-cbeams]: https://cbea.ms/git-commit/ [commit-kernel]: https://www.kernel.org/doc/html/v4.10/process/submitting-patches.html#separate-your-changes [community]: https://xmonad.org/community.html [xmonad-gh-org]: https://github.com/xmonad xmonad-0.17.2/INSTALL.md0000644000000000000000000003111407346545000012710 0ustar0000000000000000# Install XMonad On many systems xmonad is available as a binary package in your distribution (Debian, Ubuntu, Fedora, Arch, Gentoo, …). It's by far the easiest way to get xmonad, although you'll miss out on the latest features and fixes that may not have been released yet. If you do want the latest and greatest, continue reading. Those who install from distro can skip this and go straight to [the XMonad Configuration Tutorial](TUTORIAL.md). - [Dependencies](#dependencies) - [Preparation](#preparation) - [Download XMonad sources](#download-xmonad-sources) - [Build XMonad](#build-xmonad) - [Build using Stack](#build-using-stack) - [Build using cabal-install](#build-using-cabal-install) - [Make XMonad your window manager](#make-xmonad-your-window-manager) - [Custom Build Script](#custom-build-script) ## Dependencies #### Debian, Ubuntu ``` console $ sudo apt install \ > git \ > libx11-dev libxft-dev libxinerama-dev libxrandr-dev libxss-dev ``` #### Fedora ``` console $ sudo dnf install \ > git \ > libX11-devel libXft-devel libXinerama-devel libXrandr-devel libXScrnSaver-devel ``` #### Arch ``` console $ sudo pacman -S \ > git \ > xorg-server xorg-apps xorg-xinit xorg-xmessage \ > libx11 libxft libxinerama libxrandr libxss \ > pkgconf ``` #### Void ``` console $ sudo xbps-install \ > git \ > ncurses-libtinfo-libs ncurses-libtinfo-devel \ > libX11-devel libXft-devel libXinerama-devel libXrandr-devel libXScrnSaver-devel \ > pkg-config ``` ## Preparation We'll use the [XDG] directory specifications here, meaning our configuration will reside within `$XDG_CONFIG_HOME`, which is `~/.config` on most systems. Let's create this directory and move to it: ``` console $ mkdir -p ~/.config/xmonad && cd ~/.config/xmonad ``` If you already have an `xmonad.hs` configuration, you can copy it over now. If not, you can use the defaults: create a file called `xmonad.hs` with the following content: ``` haskell import XMonad main :: IO () main = xmonad def ``` Older versions of xmonad used `~/.xmonad` instead. This is still supported, but XDG is preferred. ## Download XMonad sources Still in `~/.config/xmonad`, clone `xmonad` and `xmonad-contrib` repositories using [git][]: ``` console $ git clone https://github.com/xmonad/xmonad $ git clone https://github.com/xmonad/xmonad-contrib ``` This will give you the latest `HEAD`; if you want you can also check out a tagged release, e.g.: ``` console $ git clone --branch v0.17.2 https://github.com/xmonad/xmonad $ git clone --branch v0.17.1 https://github.com/xmonad/xmonad-contrib ``` (Sources and binaries don't usually go into `~/.config`. In our case, however, it avoids complexities related to Haskell build tools and lets us focus on the important bits of XMonad installation.) ## Build XMonad There are two widely used Haskell build tools: * [Stack][stack] * [cabal-install][cabal-install] We include instructions for both. Unless you already know which one you prefer, use Stack, which is easier. ### Build using Stack #### Install Stack The easiest way to get [stack] is probably via your system's package manager: ``` console $ sudo apt install haskell-stack # Debian, Ubuntu $ sudo dnf install stack # Fedora $ sudo pacman -S stack # Arch ``` If you install stack via this method, it is advisable that you run `stack upgrade` after installation. This will make sure that you are on the most recent version of the program, regardless of which version your distribution actually packages. If your distribution does not package stack, you can also easily install it via the following command (this is the recommended way to install stack via its [documentation][stack]): ``` console $ curl -sSL https://get.haskellstack.org/ | sh ``` Yet another way would be via [ghcup]; this is similar to installers like `rustup`, in case you prefer that. #### Create a New Project Let's create a stack project. Since we're already in the correct directory (`~/.config/xmonad`) with `xmonad` and `xmonad-contrib` subdirectories, starting a new stack project is as simple as running `stack init`. Stack should now inform you that it will use the relevant `stack` and `cabal` files from `xmonad` and `xmonad-contrib` to generate its `stack.yaml` file. At the time of writing, this looks a little bit like this: ``` console $ stack init Looking for .cabal or package.yaml files to use to init the project. Using cabal packages: - xmonad-contrib/ - xmonad/ Selecting the best among 19 snapshots... * Matches https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/17/9.yaml Selected resolver: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/17/9.yaml Initialising configuration using resolver: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/17/9.yaml Total number of user packages considered: 2 Writing configuration to file: stack.yaml All done. ``` If you look into your current directory now, you should see a freshly generated `stack.yaml` file: ``` console $ ls xmonad xmonad-contrib stack.yaml xmonad.hs ``` The meat of that file (comments start with `#`, we've omitted them here) will look a little bit like ``` yaml resolver: url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/17/9.yaml packages: - xmonad - xmonad-contrib ``` With `stack.yaml` alongside `xmonad.hs`, xmonad now knows that it needs to use `stack ghc` instead of just `ghc` when (re)compiling its configuration. If you want to keep xmonad sources and the stack project elsewhere, but still use `xmonad --recompile`, symlink your real `stack.yaml` into the xmonad configuration directory, or [use a custom build script](#custom-build-script). #### Install Everything Installing things is as easy as typing `stack install`. This will install the correct version of GHC, as well as build all of the required packages (`stack build`) and then copy the relevant executables (`xmonad`, in our case) to `~/.local/bin`. Make sure to add that directory to your `$PATH`! The command `which xmonad` should now return that executable. In case it does not, check if you still have xmonad installed via your package manager and uninstall it. If you're getting build failures while building the `X11` package it may be that you don't have the required C libraries installed. See [above](#dependencies). ### Build using cabal-install #### Install cabal-install The easiest way to get [cabal-install] is probably via your system's package manager: ``` console $ sudo apt install cabal-install # Debian, Ubuntu $ sudo dnf install cabal-install # Fedora $ sudo pacman -S cabal-install # Arch ``` If your distribution does not package cabal-install, [ghcup][] is another option. See also . #### Create a New Project Let's create a cabal project. Since we're already in the correct directory (`~/.config/xmonad`) with `xmonad` and `xmonad-contrib` subdirectories, we'll instruct cabal to use them. Create a file named `cabal.project` containing: ``` packages: */*.cabal ``` (If you skip this step, cabal will use the latest releases from [Hackage][] instead.) #### Install Everything You'll need to update the cabal package index, build xmonad and xmonad-contrib libraries and then build the xmonad binary: ``` console $ cabal update $ cabal install --package-env=$HOME/.config/xmonad --lib xmonad xmonad-contrib $ cabal install --package-env=$HOME/.config/xmonad xmonad ``` This will create a GHC environment in `~/.config/xmonad` so that the libraries are available for recompilation of the config file, and also install the xmonad binary to `~/.cabal/bin/xmonad`. Make sure you have that directory in your `$PATH`! If you're getting build failures while building the `X11` package it may be that you don't have the required C libraries installed. See [above](#dependencies). ## Make XMonad your window manager This step varies depending on your distribution and X display manager (if any). #### Debian, Ubuntu `/etc/X11/xinit/xinitrc` runs `/etc/X11/Xsession` which runs `~/.xsession`, so you probably want to put `exec xmonad` there (don't forget the shebang and chmod). (Tested with `startx`, `xdm`, `lightdm`.) By using `~/.xsession`, the distro takes care of stuff like dbus, ssh-agent, X resources, etc. If you want a completely manual X session, use `~/.xinitrc` instead. Or invoke `startx`/`xinit` with an explicit path. Some newer display managers require an entry in `/usr/share/xsessions`. To use your custom `~/.xsession`, put these lines to `/usr/share/xsessions/default.desktop`: ``` [Desktop Entry] Name=Default X session Type=Application Exec=default ``` (Tested with `sddm`.) #### Fedora `/etc/X11/xinit/xinitrc` runs `~/.Xclients`, so you probably want to put `exec xmonad` there (don't forget the shebang and chmod). Like in Debian, this can be overridden by having a completely custom `~/.xinitrc` or passing arguments to `startx`/`xinit`. X display managers (e.g. `lightdm`) usually invoke `/etc/X11/xinit/Xsession` instead, which additionally redirects output to `~/.xsession-errors` and also tries `~/.xsession` before `~/.Xclients`. Newer display managers require an entry in `/usr/share/xsessions`, which is available in the `xorg-x11-xinit-session` package. #### Arch `/etc/X11/xinit/xinitrc` runs `twm`, `xclock` and 3 `xterm`s; users are meant to just copy that to `~/.xinitrc` and [customize](https://wiki.archlinux.org/title/Xinit#xinitrc) it: replace the last few lines with `exec xmonad`. Display managers like `lightdm` have their own `Xsession` script which invokes `~/.xsession`. Other display managers need an entry in `/usr/share/xsessions`, provides one. #### See also * * [FAQ: How can I use xmonad with a display manager? (xdm, kdm, gdm)](https://wiki.haskell.org/Xmonad/Frequently_asked_questions#How_can_I_use_xmonad_with_a_display_manager.3F_.28xdm.2C_kdm.2C_gdm.29) ## Custom Build Script If you need to customize what happens during `xmonad --recompile` (bound to `M-q` by default), perhaps because your xmonad configuration is a whole separate Haskell package, you need to create a so-called `build` file. This is quite literally just a shell script called `build` in your xmonad directory (which is `~/.config/xmonad` for us) that tells xmonad how it should build its executable. A good starting point (this is essentially [what xmonad would do][] without a build file, with the exception that we are invoking `stack ghc` instead of plain `ghc`) would be ``` shell #!/bin/sh exec stack ghc -- \ --make xmonad.hs \ -i \ -ilib \ -fforce-recomp \ -main-is main \ -v0 \ -o "$1" ``` Don't forget to mark the file as `+x`: `chmod +x build`! Some example build scripts for `stack` and `cabal` are provided in the `xmonad-contrib` distribution. You can see those online in the [scripts/build][] directory. You might wish to use these if you have special dependencies for your `xmonad.hs`, especially with cabal as you must use a cabal file and often a `cabal.project` to specify them; `cabal install --lib` above generally isn't enough, and when it is it can be difficult to keep track of when you want to replicate your configuration on another system. #### Don't Recompile on Every Startup By default, xmonad always recompiles itself when a build script is used (because the build script could contain arbitrary code, so a simple check whether the `xmonad.hs` file changed is not enough). If you find that too annoying, then you can use the `xmonad-ARCH` executable that `xmonad --recompile` generates instead of `xmonad` in your startup. For example, instead of writing ``` shell exec xmonad ``` in your `~/.xinitrc`, you would write ``` shell exec $HOME/.cache/xmonad/xmonad-x86_64-linux ``` The `~/.cache` prefix is the `$XDG_CACHE_HOME` directory. Note that if your xmonad configuration resides within `~/.xmonad`, then the executable will also be within that directory and not in `$XDG_CACHE_HOME`. [XDG]: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html [git]: https://git-scm.com/ [stack]: https://docs.haskellstack.org/en/stable/README/ [cabal-install]: https://www.haskell.org/cabal/ [ghcup]: https://www.haskell.org/ghcup/ [what xmonad would do]: https://github.com/xmonad/xmonad/blob/master/src/XMonad/Core.hs#L659-L667 [Hackage]: https://hackage.haskell.org/ [scripts/build]: https://github.com/xmonad/xmonad-contrib/blob/master/scripts/build xmonad-0.17.2/LICENSE0000644000000000000000000000303207346545000012263 0ustar0000000000000000Copyright (c) 2007,2008 Spencer Janssen Copyright (c) 2007,2008 Don Stewart Copyright (c) The Xmonad Community. 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 copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT HOLDER 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.17.2/MAINTAINERS.md0000644000000000000000000001227007346545000013356 0ustar0000000000000000# XMonad Maintainers ## The XMonad Core Team * Brandon S Allbery [GitHub][geekosaur], IRC: `geekosaur`, [GPG][gpg:geekosaur] * Brent Yorgey [GitHub][byorgey], IRC: `byorgey` * Daniel Wagner [GitHub][dmwit], [Twitter][twitter:dmwit], IRC: `dmwit` * Sibi Prabakaran [GitHub][psibi], [Twitter][twitter:psibi], IRC: `sibi` * Tomáš Janoušek [GitHub][liskin], [Twitter][twitter:liskin], IRC: `liskin`, [GPG][gpg:liskin] * Tony Zorman [GitHub][slotThe], IRC: `Solid`, [GPG][gpg:slotThe] [geekosaur]: https://github.com/geekosaur [byorgey]: https://github.com/byorgey [dmwit]: https://github.com/dmwit [psibi]: https://github.com/psibi [liskin]: https://github.com/liskin [slotThe]: https://github.com/slotThe [gpg:geekosaur]: https://github.com/geekosaur.gpg [gpg:liskin]: https://github.com/liskin.gpg [gpg:slotThe]: https://github.com/slotThe.gpg [twitter:dmwit]: https://twitter.com/dmwit13 [twitter:psibi]: https://twitter.com/psibi [twitter:liskin]: https://twitter.com/Liskni_si ## Hall of Fame (past maintainers/developers) * Adam Vogt [GitHub](https://github.com/aavogt) * Peter Simons [GitHub](https://github.com/peti), [Twitter](https://twitter.com/OriginalPeti) * Spencer Janssen [GitHub](https://github.com/spencerjanssen) * Don Stewart [GitHub](https://github.com/donsbot), [Twitter](https://twitter.com/donsbot) * Jason Creighton [GitHub](https://github.com/JasonCreighton) * David Roundy [GitHub](https://github.com/droundy) * Daniel Schoepe [GitHub](https://github.com/dschoepe) * Eric Mertens [GitHub](https://github.com/glguy) * Nicolas Pouillard [GitHub](https://github.com/np) * Roman Cheplyaka [GitHub](https://github.com/UnkindPartition) * Gwern Branwen [GitHub](https://github.com/gwern) * Lukas Mai [GitHub](https://github.com/mauke) * Braden Shepherdson [GitHub](https://github.com/shepheb) * Devin Mullins [GitHub](https://github.com/twifkak) * David Lazar [GitHub](https://github.com/davidlazar) * Peter J. Jones [GitHub](https://github.com/pjones) ## Release Procedures When the time comes to release another version of xmonad and xmonad-contrib: 1. Update the version number in all the `*.cabal` files and let the CI verify that it all builds together. 2. Review documentation files and make sure they are accurate: - [`README.md`](README.md) - [`CHANGES.md`](CHANGES.md) (bump version, set date) - [`INSTALL.md`](INSTALL.md) - [`man/xmonad.1.markdown.in`](man/xmonad.1.markdown.in) - [haddocks](https://xmonad.github.io/xmonad-docs/) If the manpage changes, wait for the CI to rebuild the rendered outputs. 3. Update the website: - Draft a [new release announcement][web-announce]. - Check install instructions, guided tour, keybindings cheat sheet, … 4. Make sure that `tested-with:` covers several recent releases of GHC, that `.github/workflows/haskell-ci.yml` had been updated to test all these GHC versions and that `.github/workflows/stack.yml` tests with several recent revisions of [Stackage][] LTS. 5. Trigger the Haskell-CI workflow and fill in the candidate version number. This will upload a release candidate to Hackage. - https://github.com/xmonad/xmonad/actions/workflows/haskell-ci.yml - https://github.com/xmonad/xmonad-contrib/actions/workflows/haskell-ci.yml Check that everything looks good. If not, push fixes and do another candidate. When everything's ready, create a release on GitHub: - https://github.com/xmonad/xmonad/releases/new - https://github.com/xmonad/xmonad-contrib/releases/new CI will automatically upload the final release to Hackage. See [haskell-ci-hackage.patch][] for details about the Hackage automation. 6. Post announcement to: - [xmonad.org website](https://github.com/xmonad/xmonad-web/tree/gh-pages/news/_posts) - [XMonad mailing list](https://mail.haskell.org/mailman/listinfo/xmonad) - [Haskell Cafe](https://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe) - [Haskell Discourse](https://discourse.haskell.org/) - [Twitter](https://twitter.com/xmonad) - [Reddit](https://www.reddit.com/r/xmonad/) See [old announcements][old-announce] ([even older][older-announce]) for inspiration. 7. Bump version for development (add `.9`) and prepare fresh sections in [`CHANGES.md`](CHANGES.md). [packdeps]: https://hackage.haskell.org/package/packdeps [Stackage]: https://www.stackage.org/ [haskell-ci-hackage.patch]: .github/workflows/haskell-ci-hackage.patch [web-announce]: https://github.com/xmonad/xmonad-web/tree/gh-pages/news/_posts [old-announce]: https://github.com/xmonad/xmonad-web/blob/gh-pages/news/_posts/2021-10-27-xmonad-0-17-0.md [older-announce]: https://github.com/xmonad/xmonad-web/tree/55614349421ebafaef4a47424fcb16efa80ff768 ## Website and Other Accounts * The [xmonad twitter] is tended to by [liskin]. * The [xmonad.org] domain is owned by [eyenx] and the website itself is deployed via GitHub Pages. It can be updated by making a pull request against the [xmonad-web] repository. [eyenx]: https://github.com/eyenx [xmonad-web]: https://github.com/xmonad/xmonad-web/ [xmonad.org]: https://xmonad.org/ [xmonad twitter]: https://twitter.com/xmonad xmonad-0.17.2/Main.hs0000644000000000000000000000101607346545000012476 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 main :: IO () main = xmonad def xmonad-0.17.2/README.md0000644000000000000000000001345507346545000012547 0ustar0000000000000000

XMonad logo

Hackage License Made in Haskell
Stack Cabal Nix
GitHub Sponsors Open Collective
Chat on #xmonad@irc.libera.chat Chat on #xmonad:matrix.org

# xmonad **A tiling window manager for X11.** [XMonad][web:xmonad] is a tiling window manager for X11. 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. This repository contains the [xmonad][hackage:xmonad] package, a minimal, stable, yet extensible core. It is accompanied by [xmonad-contrib][gh:xmonad-contrib], a library of hundreds of additional community-maintained tiling algorithms and extension modules. The two combined make for a powerful X11 window-manager with endless customization possibilities. They are, quite literally, libraries for creating your own window manager. ## Installation For installation and configuration instructions, please see: * [downloading and installing xmonad][web:download] * [installing latest xmonad snapshot from git][web:install] * [configuring xmonad][web:tutorial] If you run into any trouble, consult our [documentation][web:documentation] or ask the [community][web:community] for help. ## Contributing We welcome all forms of contributions: * [bug reports and feature ideas][gh:xmonad:issues] (also to [xmonad-contrib][gh:xmonad-contrib:issues]) * [bug fixes, new features, new extensions][gh:xmonad:pulls] (usually to [xmonad-contrib][gh:xmonad-contrib:pulls]) * documentation fixes and improvements: [xmonad][gh:xmonad], [xmonad-contrib][gh:xmonad-contrib], [xmonad-web][gh:xmonad-web] * helping others in the [community][web:community] * financial support: [GitHub Sponsors][gh:xmonad:sponsors], [Open Collective][opencollective:xmonad] Please do read the [CONTRIBUTING][gh:xmonad:contributing] document for more information about bug reporting and code contributions. For a brief overview of the architecture and code conventions, see the [documentation for the `XMonad.Doc.Developing` module][doc:developing]. If in doubt, [talk to us][web:community]. ## Authors Started in 2007 by [Spencer Janssen][gh:spencerjanssen], [Don Stewart][gh:donsbot] and [Jason Creighton][gh:JasonCreighton], the [XMonad][web:xmonad] project lives on thanks to [new generations of maintainers][gh:xmonad:maintainers] and [dozens of contributors][gh:xmonad:contributors]. [gh:spencerjanssen]: https://github.com/spencerjanssen [gh:donsbot]: https://github.com/donsbot [gh:JasonCreighton]: https://github.com/JasonCreighton [doc:developing]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Doc-Developing.html [gh:xmonad-contrib:issues]: https://github.com/xmonad/xmonad-contrib/issues [gh:xmonad-contrib:pulls]: https://github.com/xmonad/xmonad-contrib/pulls [gh:xmonad-contrib]: https://github.com/xmonad/xmonad-contrib [gh:xmonad-web]: https://github.com/xmonad/xmonad-web [gh:xmonad:contributing]: https://github.com/xmonad/xmonad/blob/master/CONTRIBUTING.md [gh:xmonad:contributors]: https://github.com/xmonad/xmonad/graphs/contributors [gh:xmonad:issues]: https://github.com/xmonad/xmonad/issues [gh:xmonad:maintainers]: https://github.com/xmonad/xmonad/blob/master/MAINTAINERS.md [gh:xmonad:pulls]: https://github.com/xmonad/xmonad/pulls [gh:xmonad:sponsors]: https://github.com/sponsors/xmonad [gh:xmonad]: https://github.com/xmonad/xmonad [hackage:xmonad]: https://hackage.haskell.org/package/xmonad [opencollective:xmonad]: https://opencollective.com/xmonad [web:community]: https://xmonad.org/community.html [web:documentation]: https://xmonad.org/documentation.html [web:download]: https://xmonad.org/download.html [web:install]: https://xmonad.org/INSTALL.html [web:tutorial]: https://xmonad.org/TUTORIAL.html [web:xmonad]: https://xmonad.org/ xmonad-0.17.2/Setup.lhs0000644000000000000000000000011407346545000013064 0ustar0000000000000000#!/usr/bin/env runhaskell > import Distribution.Simple > main = defaultMain xmonad-0.17.2/TUTORIAL.md0000644000000000000000000014277307346545000013063 0ustar0000000000000000# XMonad Configuration Tutorial We're going to take you, step-by-step, through the process of configuring xmonad, setting up [xmobar] as a status bar, configuring [trayer-srg] as a tray, and making it all play nicely together. We assume that you have read the [xmonad guided tour] already. It is a bit dated at this point but, because xmonad is stable, the guide should still give you a good overview of the most basic functionality. Before we begin, here are two screenshots of the kind of desktop we will be creating together. In particular, a useful layout we'll conjure into being—the three columns layout from [XMonad.Layout.ThreeColumns] with the ability to magnify stack windows via [XMonad.Layout.Magnifier]:

blank desktop blank desktop

So let's get started! ## Preliminaries First you'll want to install xmonad. You can either do this with your system's package manager or via `stack`/`cabal`. The latter may be necessary if your distribution does not package `xmonad` and `xmonad-contrib` (or packages a version that's very old) or you want to use the git versions instead of a tagged release. You can find instructions for how to do this in [INSTALL.md]. One more word of warning: if you are on a distribution that installs every Haskell package dynamically linked—thus essentially breaking Haskell—(Arch Linux being a prominent example) then we would highly recommend installing via `stack` or `cabal` as instructed in [INSTALL.md]. If you still want to install xmonad via your system's package manager, you will need to `xmonad --recompile` _every time_ a Haskell dependency is updated—else xmonad may fail to start when you want to log in! We're going to assume xmonad version `0.17.0` and xmonad-contrib version `0.17.0` here, though most of these steps should work with older versions as well. When we get to the relevant parts, will point you to alternatives that work with at least xmonad version `0.15` and xmonad-contrib version `0.16`. This will usually be accompanied by a big "_IF YOU ARE ON A VERSION `< 0.17.0`_", so don't worry about missing it! Throughout the tutorial we will use, for keybindings, a syntax very akin to the [GNU Emacs conventions] for the same thing—so `C-x` means "hold down the control key and then press the `x` key". One exception is that in our case `M` will not necessarily mean Alt (also called `Meta`), but "your modifier key"; this is Alt by default, although many people map it to Super instead (I will show you how to do this below). This guide should work for any GNU/Linux distribution and even for BSD folks. Because Debian-based distributions are still rather popular, we will give you the `apt` commands when it comes to installing software. If you use another distribution, just substitute the appropriate commands for your system. To install xmonad, as well as some utilities, via `apt` you can just run ``` console $ apt-get install xmonad libghc-xmonad-contrib-dev libghc-xmonad-dev suckless-tools ``` This installs xmonad itself, everything you need to configure it, and `suckless-tools`, which provides the application launcher `dmenu`. This program is used as the default application launcher on `M-p`. If you are installing xmonad with `stack` or `cabal`, remember to _not_ install `xmonad` and its libraries here! For the remainder of this document, we will assume that you are running a live xmonad session in some capacity. If you have set up your `~/.xinitrc` as directed in the xmonad guided tour, you should be good to go! If not, just smack an `exec xmonad` at the bottom of that file. ## Installing Xmobar What we need to do now—provided we want to use a bar at all—is to install [xmobar]. If you visit [xmobar's `Installation` section] you will find everything from how to install it with your system's package manager all the way to how to compile it yourself. We will show you how we make these programs talk to each other a bit later on. For now, let's start to explore how we can customize this window manager of ours! ## Customizing XMonad Xmonad's configuration file is written in [Haskell]—but don't worry, we won't assume that you know the language for the purposes of this tutorial. The configuration file can either reside within `$XDG_CONFIG_HOME/xmonad`, `~/.xmonad`, or `$XMONAD_CONFIG_DIR`; see `man 1 xmonad` for further details (the likes of `$XDG_CONFIG_HOME` is called a [shell variable]). Let's use `$XDG_CONFIG_HOME/xmonad` for the purposes of this tutorial, which resolves to `~/.config/xmonad` on most systems—the `~/.config` directory is also the place where things will default to should `$XDG_CONFIG_HOME` not be set. First, we need to create `~/.config/xmonad` and, in this directory, a file called `xmonad.hs`. We'll start off with importing some of the utility modules we will use. At the very top of the file, write ``` haskell import XMonad import XMonad.Util.EZConfig import XMonad.Util.Ungrab ``` All of these imports are _unqualified_, meaning we are importing all of the functions in the respective modules. For configuration files this is what most people want. If you prefer to import things explicitly you can do so by adding the necessary function to the `import` statement in parentheses. For example ``` haskell import XMonad.Util.EZConfig (additionalKeysP) ``` For the purposes of this tutorial, we will be importing everything coming from xmonad directly unqualified. Next, a basic configuration—which is the same as the default config—is this: ``` haskell main :: IO () main = xmonad def ``` In case you're interested in what this default configuration actually looks like, you can find it under [XMonad.Config]. Do note that it is _not_ advised to copy that file and use it as the basis of your configuration, as you won't notice when a default changes! You should be able to save the above file, with the import lines plus the other two and then press `M-q` to load it up. Another way to validate your `xmonad.hs` is to simply run `xmonad --recompile` in a terminal. You'll see errors (in an `xmessage` popup, so make sure that is installed on your system) if it's bad, and nothing if it's good. It's not the end of the world if you restart xmonad and get errors, as you will still be on your old, working, configuration and have all the time in the world to fix your errors before trying again! Let's add a few additional things. The default modifier key is Alt, which is also used in Emacs. Sometimes Emacs and xmonad want to use the same key for different actions. Rather than remap every common key, many people just change Mod to be the Super key—the one between Ctrl and Alt on most keyboards. We can do this by changing the above `main` function in the following way: ``` haskell main :: IO () main = xmonad $ def { modMask = mod4Mask -- Rebind Mod to the Super key } ``` The two dashes signify a comment to the end of the line. Notice the curly braces; these stand for a [record update] in Haskell (records are sometimes called "structs" in C-like languages). What it means is "take `def` and change its `modMask` field to the thing **I** want". Taking a record that already has some defaults set and modifying only the fields one cares about is a pattern that is often used within xmonad, so it's worth pausing for a bit here to really take this new syntax in. Don't mind the dollar sign too much; it essentially serves to split apart the `xmonad` function and the `def { .. }` record update visually. Haskell people really don't like writing parentheses, so they sometimes use a dollar sign instead. For us this is particularly nice, because now we don't have to awkwardly write ``` haskell main :: IO () main = xmonad (def { modMask = mod4Mask -- Rebind Mod to the Super key }) ``` This will be especially handy when adding more combinators; something we will talk about later on. The dollar sign is superfluous in this example, but that will change soon enough so it's worth introducing it here as well. What if we wanted to add other keybindings? Say you also want to bind `M-S-z` to lock your screen with the screensaver, `M-C-s` to take a snapshot of one window, and `M-f` to spawn Firefox. This can be achieved with the `additionalKeysP` function from the [XMonad.Util.EZConfig] module—luckily we already have it imported! Our config file, starting with `main`, now looks like: ``` haskell main :: IO () main = xmonad $ def { modMask = mod4Mask -- Rebind Mod to the Super key } `additionalKeysP` [ ("M-S-z", spawn "xscreensaver-command -lock") , ("M-C-s", unGrab *> spawn "scrot -s" ) , ("M-f" , spawn "firefox" ) ] ``` That syntax look familiar? You can find the names for special keys, like `Print` or the `F`-keys, in the [XMonad.Util.EZConfig] documentation. We will cover setting up the screensaver later in this tutorial. The `unGrab` before running the `scrot -s` command tells xmonad to release its keyboard grab before `scrot -s` tries to grab the keyboard itself. The little `*>` operator essentially just sequences two functions, i.e. `f *> g` says > first do `f` and then, discarding any result that `f` may have given > me, do `g`. Do note that you may need to install `scrot` if you don't have it on your system already. What if we wanted to augment our xmonad experience just a little more? We already have `xmonad-contrib`, which means endless possibilities! Say we want to add a three column layout to our layouts and also magnify focused stack windows, making it more useful on smaller screens. We start by visiting the documentation for [XMonad.Layout.ThreeColumns]. The very first sentence of the `Usage` section tells us exactly what we want to start with: > You can use this module with the following in your `~/.xmonad/xmonad.hs`: > > `import XMonad.Layout.ThreeColumns` Ignoring the part about where exactly our `xmonad.hs` is (we have opted to put it into `~/.config/xmonad/xmonad.hs`, remember?) we can follow that documentation word for word. Let's add ``` haskell import XMonad.Layout.ThreeColumns ``` to the top of our configuration file. Most modules have a lot of accompanying text and usage examples in them—so while the type signatures may seem scary, don't be afraid to look up the [xmonad-contrib documentation] on Hackage! Next we just need to tell xmonad that we want to use that particular layout. To do this, there is the `layoutHook`. Let's use the default layout as a base: ``` haskell myLayout = tiled ||| Mirror tiled ||| Full where tiled = Tall nmaster delta ratio nmaster = 1 -- Default number of windows in the master pane ratio = 1/2 -- Default proportion of screen occupied by master pane delta = 3/100 -- Percent of screen to increment by when resizing panes ``` The so-called `where`-clause above simply consists of local declarations that might clutter things up where they all declared at the top-level like this ``` haskell myLayout = Tall 1 (3/100) (1/2) ||| Mirror (Tall 1 (3/100) (1/2)) ||| Full ``` It also gives us the chance of documenting what the individual numbers mean! Now we can add the layout according to the [XMonad.Layout.ThreeColumns] documentation. At this point, we would encourage you to try this yourself with just the docs guiding you. If you can't do it, don't worry; it'll come with time! We can, for example, add the additional layout like this: ``` haskell myLayout = tiled ||| Mirror tiled ||| Full ||| ThreeColMid 1 (3/100) (1/2) where tiled = Tall nmaster delta ratio nmaster = 1 -- Default number of windows in the master pane ratio = 1/2 -- Default proportion of screen occupied by master pane delta = 3/100 -- Percent of screen to increment by when resizing panes ``` or even, because the numbers happen to line up, like this: ``` haskell myLayout = tiled ||| Mirror tiled ||| Full ||| threeCol where threeCol = ThreeColMid nmaster delta ratio tiled = Tall nmaster delta ratio nmaster = 1 -- Default number of windows in the master pane ratio = 1/2 -- Default proportion of screen occupied by master pane delta = 3/100 -- Percent of screen to increment by when resizing panes ``` Now we just need to tell xmonad that we want to use this modified `layoutHook` instead of the default. Again, try to reason this out for yourself by just looking at the documentation. Ready? Here we go: ``` haskell main :: IO () main = xmonad $ def { modMask = mod4Mask -- Rebind Mod to the Super key , layoutHook = myLayout -- Use custom layouts } `additionalKeysP` [ ("M-S-z", spawn "xscreensaver-command -lock") , ("M-C-s", unGrab *> spawn "scrot -s" ) , ("M-f" , spawn "firefox" ) ] ``` But we also wanted to add magnification, right? Luckily for us, there's a module for that as well! It's called [XMonad.Layout.Magnifier]. Again, take a look at the documentation before reading on—see if you can reason out what to do by yourself. Let's pick the `magnifiercz'` modifier from the library; it magnifies a window by a given amount, but only if it's a stack window. Add it to your three column layout thusly: ``` haskell myLayout = tiled ||| Mirror tiled ||| Full ||| threeCol where threeCol = magnifiercz' 1.3 $ ThreeColMid nmaster delta ratio tiled = Tall nmaster delta ratio nmaster = 1 -- Default number of windows in the master pane ratio = 1/2 -- Default proportion of screen occupied by master pane delta = 3/100 -- Percent of screen to increment by when resizing panes ``` Don't forget to import the module! You can think of the `$` here as putting everything into parentheses from the dollar to the end of the line. If you don't like that you can also write ``` haskell threeCol = magnifiercz' 1.3 (ThreeColMid nmaster delta ratio) ``` instead. That's it! Now we have a perfectly functioning three column layout with a magnified stack. If you compare this with the starting screenshots, you will see that that's exactly the behaviour we wanted to achieve! A last thing that's worth knowing about are so-called "combinators"—at least we call them that, we can't tell you what to do. These are functions that compose with the `xmonad` function and add a lot of hooks and other things for you (trying to achieve a specific goal), so you don't have to do all the manual work yourself. For example, xmonad—by default—is only [ICCCM] compliant. Nowadays, however, a lot of programs (including many compositors) expect the window manager to _also_ be [EWMH] compliant. So let's save ourselves a lot of future trouble and add that to xmonad straight away! This functionality is to be found in the [XMonad.Hooks.EwmhDesktops] module, so let's import it: ``` haskell import XMonad.Hooks.EwmhDesktops ``` We might also consider using the `ewmhFullscreen` combinator. By default, a "fullscreened" application is still bound by its window dimensions; this means that if the window occupies half of the screen before it was fullscreened, it will also do so afterwards. Some people really like this behaviour, as applications thinking they're in fullscreen mode tend to remove a lot of clutter (looking at you, Firefox). However, because a lot of people explicitly do not want this effect (and some applications, like chromium, will misbehave and need some [Hacks] to make this work), we will also add the relevant function to get "proper" fullscreen behaviour here. _IF YOU ARE ON A VERSION `< 0.17.0`_: The `ewmhFullscreen` function does not exist in these versions. Instead of it, you can try to add `fullscreenEventHook` to your `handleEventHook` to achieve similar functionality (how to do this is explained in the documentation of [XMonad.Hooks.EwmhDesktops]). To use the two combinators, we compose them with the `xmonad` function in the following way: ``` haskell main :: IO () main = xmonad $ ewmhFullscreen $ ewmh $ def { modMask = mod4Mask -- Rebind Mod to the Super key , layoutHook = myLayout -- Use custom layouts } `additionalKeysP` [ ("M-S-z", spawn "xscreensaver-command -lock") , ("M-C-s", unGrab *> spawn "scrot -s" ) , ("M-f" , spawn "firefox" ) ] ``` Do mind the order of the two combinators—by a particularly awkward set of circumstances, they do not commute! This `main` function is getting pretty crowded now, so let's refactor it a little bit. A good way to do this is to split the config part into one function and the "main and all the combinators" part into another. Let's call the config part `myConfig` for... obvious reasons. It would look like this: ``` haskell main :: IO () main = xmonad $ ewmhFullscreen $ ewmh $ myConfig myConfig = def { modMask = mod4Mask -- Rebind Mod to the Super key , layoutHook = myLayout -- Use custom layouts } `additionalKeysP` [ ("M-S-z", spawn "xscreensaver-command -lock") , ("M-C-s", unGrab *> spawn "scrot -s" ) , ("M-f" , spawn "firefox" ) ] ``` Much better! ## Make XMonad and Xmobar Talk to Each Other Onto the main dish. First, we have to import the necessary modules. Add the following to your list of imports: ``` haskell import XMonad.Hooks.DynamicLog import XMonad.Hooks.StatusBar import XMonad.Hooks.StatusBar.PP ``` _IF YOU ARE ON A VERSION `< 0.17.0`_: The `XMonad.Hooks.StatusBar` and `XMonad.Hooks.StatusBar.PP` modules don't exist yet. You can find everything you need in the `XMonad.Hooks.DynamicLog` module, so remove these two imports. Replace your `main` function above with: ``` haskell main :: IO () main = xmonad $ ewmhFullscreen $ ewmh $ xmobarProp $ myConfig ``` _IF YOU ARE ON A VERSION `< 0.17.0`_: The `xmobarProp` function does not exist in these versions. Instead of it, use `xmobar` via `main = xmonad . ewmh =<< xmobar myConfig` and carefully read the part about pipes later on (`xmobar` uses pipes to make xmobar talk to xmonad). As a quick side-note, we could have also written ``` haskell main :: IO () main = xmonad . ewmhFullscreen . ewmh . xmobarProp $ myConfig ``` Notice how `$` became `.`! The dot operator `(.)` in Haskell means function composition and is read from right to left. What this means in this specific case is essentially the following: > take the four functions `xmonad`, `ewmhFullscreen`, `ewmh`, and > `xmobarProp` and give me the big new function > `xmonad . ewmhFullscreen . ewmh . xmobarProp` that first executes > `xmobarProp`, then `ewmh`, then `ewmhFullscreen`, and finally > `xmonad`. Then give it `myConfig` as its argument so it can do its > thing. This should strike you as nothing more than a syntactical quirk, at least in this case. And indeed, since `($)` is just function application there is a very nice relationship between `(.)` and `($)`. This may be more obvious if we write everything with parentheses and apply the `(.)` operator (because we do have an argument): ``` haskell -- ($) version main = xmonad $ ewmhFullscreen $ ewmh $ xmobarProp $ myConfig -- ($) version with parentheses main = xmonad (ewmhFullscreen (ewmh (xmobarProp (myConfig)))) -- (.) version with parentheses main = (xmonad . ewmhFullscreen . ewmh . xmobarProp) (myConfig) -- xmobarProp applied main = (xmonad . ewmhFullscreen . ewmh) (xmobarProp (myConfig)) -- ewmh applied main = (xmonad . ewmhFullscreen) (ewmh (xmobarProp (myConfig))) -- xmonad and ewmhFullscreen applied main = (xmonad (ewmhFullscreen (ewmh (xmobarProp (myConfig)))) ``` It's the same! This is special to the interplay with `(.)` and `($)` though; if you're on an older version of xmonad and xmonad-contrib and use `xmobar` instead of `xmobarProp`, then you _have_ to write ``` haskell main = xmonad . ewmhFullscreen . ewmh =<< xmobar myConfig ``` and this is _not_ equivalent to ``` haskell main = xmonad (ewmhFullscreen (ewmh =<< xmobar myConfig)) ``` Consult a Haskell book of your choice for why this is the case. Back to our actual goal: customizing xmonad. What the code we've written does is take our tweaked default configuration `myConfig` and add the support we need to make xmobar our status bar. Do note that you will also need to add the `XMonadLog` plugin to your xmobar configuration; we will do this together below, so don't sweat it for now. To understand why this is necessary, let's talk a little bit about how xmonad and xmobar fit together. You can make them talk to each other in several different ways. By default, xmobar accepts input on its stdin, which it can display at an arbitrary position on the screen. We want xmonad to send xmobar the stuff that you can see at the upper left of the starting screenshots: information about available workspaces, current layout, and open windows. Naïvely, we can achieve this by spawning a pipe and letting xmonad feed the relevant information to that pipe. The problem with that approach is that when the pipe is not being read and gets full, xmonad will freeze! It is thus much better to switch over to property based logging, where we are writing to an X11 property and having xmobar read that; no danger when things are not being read! For this reason we have to use `XMonadLog` instead of `StdinReader` in our xmobar. There's also an `UnsafeXMonadLog` available, should you want to send actions to xmobar (this is useful, for example, for [XMonad.Util.ClickableWorkspaces], which is a new feature in `0.17.0`). _IF YOU ARE ON A VERSION `< 0.17.0`_: As discussed above, the `xmobar` function uses pipes, so you actually do want to use the `StdinReader`. Simply replace _all_ occurences of `XMonadLog` with `StdinReader` below (don't forget the template!) ## Configuring Xmobar Now, before this will work, we have to configure xmobar. Here's a nice starting point. Be aware that, while Haskell syntax highlighting is used here to make this pretty, xmobar's config is _not_ a Haskell file and thus can't execute arbitrary code—at least not by default. If you do want to configure xmobar in Haskell there is a note about that at the end of this section. ``` haskell Config { overrideRedirect = False , font = "xft:iosevka-9" , bgColor = "#5f5f5f" , fgColor = "#f8f8f2" , position = TopW L 90 , commands = [ Run Weather "EGPF" [ "--template", " °C" , "-L", "0" , "-H", "25" , "--low" , "lightblue" , "--normal", "#f8f8f2" , "--high" , "red" ] 36000 , Run Cpu [ "-L", "3" , "-H", "50" , "--high" , "red" , "--normal", "green" ] 10 , Run Alsa "default" "Master" [ "--template", "" , "--suffix" , "True" , "--" , "--on", "" ] , Run Memory ["--template", "Mem: %"] 10 , Run Swap [] 10 , Run Date "%a %Y-%m-%d %H:%M" "date" 10 , Run XMonadLog ] , sepChar = "%" , alignSep = "}{" , template = "%XMonadLog% }{ %alsa:default:Master% | %cpu% | %memory% * %swap% | %EGPF% | %date% " } ``` First, we set the font to use for the bar, as well as the colors. The position options are documented well in xmobar's [quick-start.org]. The particular option of `TopW L 90` says to put the bar in the upper left of the screen, and make it consume 90% of the width of the screen (we need to leave a little bit of space for `trayer-srg`). If you're up for it—and this really requires more shell-scripting than Haskell knowledge—you can also try to seamlessly embed trayer into xmobar by using [trayer-padding-icon.sh] and following the advice given in that thread. In the commands list you, well, define commands. Commands are the pieces that generate the content to be displayed in your bar. These will later be combined together in the `template`. Here, we have defined a weather widget, a CPU widget, memory and swap widgets, a date, a volume indicator, and of course the data from xmonad via `XMonadLog`. The `EGPF` in the weather command is a particular station. Replace both (!) occurences of it with your choice of ICAO weather stations. For a list of ICAO codes you can visit the relevant [Wikipedia page]. You can of course monitor more than one if you like; see xmobar's [weather monitor] documentation for further details. The `template` then combines everything together. The `alignSep` variable controls the alignment of all of the monitors. Stuff to be left-justified goes before the `}` character, things to be centered after it, and things to be right justified after `{`. We have nothing centered so there is nothing in-between them. Save the file to `~/.xmobarrc`. Now press `M-q` to reload xmonad; you should now see xmobar with your new configuration! Please note that, at this point, the config _has_ to reside in `~/.xmobarrc`. We will, however, discuss how to change this soon. It is also possible to completely configure xmobar in Haskell, just like xmonad. If you want to know more about that, you can check out the [xmobar.hs] example in the official documentation. For a more complicated example, you can also check out [jao's xmobar.hs] (he's the current maintainer of xmobar). ## Changing What XMonad Sends to Xmobar Now that the xmobar side of the picture looks nice, what about the stuff that xmonad sends to xmobar? It would be nice to visually match these two. Sadly, this is not quite possible with our `xmobarProp` function; however, looking at the implementation of the function (or, indeed, the top-level documentation of the module!) should give us some ideas for how to proceed: ``` haskell xmobarProp config = withEasySB (statusBarProp "xmobar" (pure xmobarPP)) toggleStrutsKey config ``` This means that `xmobarProp` just calls the functions `withEasySB` and `statusBarProp` with some arguments; crucially for us, notice the `xmobarPP`. In this context "PP" stands for "pretty-printer"—exactly what we want to modify! Let's copy the implementation over into our main function: ``` haskell main :: IO () main = xmonad . ewmhFullscreen . ewmh . withEasySB (statusBarProp "xmobar" (pure def)) defToggleStrutsKey $ myConfig ``` _IF YOU ARE ON A VERSION `< 0.17.0`_: `xmobar` has a similar definition, relying on `statusBar` alone: `xmobar = statusBar "xmobar" xmobarPP toggleStrutsKey`. Sadly, the `defToggleStrutsKey` function is not yet exported, so you will have to define it yourself: ``` haskell main :: IO () main = xmonad . ewmhFullscreen . ewmh =<< statusBar "xmobar" def toggleStrutsKey myConfig where toggleStrutsKey :: XConfig Layout -> (KeyMask, KeySym) toggleStrutsKey XConfig{ modMask = m } = (m, xK_b) ``` The `defToggleStrutsKey` here is just the key with which you can toggle the bar; it is bound to `M-b`. If you want to change this, you can also define your own: ``` haskell main :: IO () main = xmonad . ewmhFullscreen . ewmh . withEasySB (statusBarProp "xmobar" (pure def)) toggleStrutsKey $ myConfig where toggleStrutsKey :: XConfig Layout -> (KeyMask, KeySym) toggleStrutsKey XConfig{ modMask = m } = (m, xK_b) ``` Feel free to change the binding by modifying the `(m, xK_b)` tuple to your liking. If you want your xmobar configuration file to reside somewhere else than `~/.xmobarrc`, you can now simply give the file to xmobar as a positional argument! For example: ``` haskell main :: IO () main = xmonad . ewmhFullscreen . ewmh . withEasySB (statusBarProp "xmobar ~/.config/xmobar/xmobarrc" (pure def)) defToggleStrutsKey $ myConfig ``` Back to controlling what exactly we send to xmobar. The `def` pretty-printer just gives us the same result that the internal `xmobarPP` would have given us. Let's try to build something on top of this. To prepare, we can first create a new function `myXmobarPP` with the default configuration: ``` haskell myXmobarPP :: PP myXmobarPP = def ``` and then plug that into our main function: ``` haskell main :: IO () main = xmonad . ewmhFullscreen . ewmh . withEasySB (statusBarProp "xmobar" (pure myXmobarPP)) defToggleStrutsKey $ myConfig ``` As before, we now change things by modifying that `def` record until we find something that we like. First, for some functionality that we need further down we need to import one more module: ``` haskell import XMonad.Util.Loggers ``` Now we are finally ready to make things pretty. There are _a lot_ of options for the [PP record]; I'd advise you to read through all of them now, so you don't get lost! ``` haskell myXmobarPP :: PP myXmobarPP = def { ppSep = magenta " • " , ppTitleSanitize = xmobarStrip , ppCurrent = wrap " " "" . xmobarBorder "Top" "#8be9fd" 2 , ppHidden = white . wrap " " "" , ppHiddenNoWindows = lowWhite . wrap " " "" , ppUrgent = red . wrap (yellow "!") (yellow "!") , ppOrder = \[ws, l, _, wins] -> [ws, l, wins] , ppExtras = [logTitles formatFocused formatUnfocused] } where formatFocused = wrap (white "[") (white "]") . magenta . ppWindow formatUnfocused = wrap (lowWhite "[") (lowWhite "]") . blue . ppWindow -- | Windows should have *some* title, which should not not exceed a -- sane length. ppWindow :: String -> String ppWindow = xmobarRaw . (\w -> if null w then "untitled" else w) . shorten 30 blue, lowWhite, magenta, red, white, yellow :: String -> String magenta = xmobarColor "#ff79c6" "" blue = xmobarColor "#bd93f9" "" white = xmobarColor "#f8f8f2" "" yellow = xmobarColor "#f1fa8c" "" red = xmobarColor "#ff5555" "" lowWhite = xmobarColor "#bbbbbb" "" ``` _IF YOU ARE ON A VERSION `< 0.17`_: Both `logTitles` and `xmobarBorder` are not available yet, so you will have to remove them. As an alternative to `xmobarBorder`, a common way to "mark" the currently focused workspace is by using brackets; you can try something like `ppCurrent = wrap (blue "[") (blue "]")` and see if you like it. Also read the bit about `ppOrder` further down! That's a lot! But don't worry, take a deep breath and remind yourself of what you read above in the documentation of the [PP record]. Even if you haven't read the documentation yet, most of the fields should be pretty self-explanatory; `ppTitle` formats the title of the currently focused window, `ppCurrent` formats the currently focused workspace, `ppHidden` is for the hidden workspaces that have windows on them, etc. The rest is just deciding on some pretty colours and formatting things just how we like it. An important thing to talk about may be `ppOrder`. Quoting from its documentation: > By default, this function receives a list with three formatted > strings, representing the workspaces, the layout, and the current > window title, respectively. If you have specified any extra loggers > in ppExtras, their output will also be appended to the list. So the first three argument of the list (`ws`, `l`, and `_` in our case, where the `_` just means we want to ignore that argument and not give it a name) are the workspaces to show, the currently active layout, and the title of the focused window. The last element—`wins`—is what we gave to `ppExtras`; if you added more loggers to that then you would have to add more items to the list, like this: ``` haskell ppOrder = \[ws, l, _, wins, more, more2] -> [ws, l, wins, more, more2] ``` However, many people want to show _all_ window titles on the currently focused workspace instead. For that, one can use `logTitles` from [XMonad.Util.Loggers] (remember that module we just imported?). However, `logTitles` logs _all_ titles. Naturally, we don't want to show the focused window twice and so we suppress it here by ignoring the third argument of `ppOrder` and not returning it. The functions `formatFocused` and `formatUnfocused` should be relatively self explanitory—they decide how to format the focused resp. unfocused windows. By the way, the `\ ... ->` syntax in there is Haskell's way to express a [lambda abstraction] (or anonymous function, as some languages call it). All of the arguments of the function come directly after the `\` and before the `->`; in our case, this is a list with exactly four elements in it. Basically, it's a nice way to write a function inline and not having to define it inside e.g. a `where` clause. The above could have also be written as ``` haskell myXmobarPP :: PP myXmobarPP = def { -- stuff here , ppOrder = myOrder -- more stuff here } where myOrder [ws, l, _, wins] = [ws, l, wins] -- more stuff here ``` If you're unsure of the number of elements that your `ppOrder` will take, you can also specify the list like this: ``` haskell ppOrder = \(ws : l : _ : wins : _) -> [ws, l, wins] ``` This says that it is a list of _at least_ four elements (`ws`, `l`, the unnamed argument, and `wins`), but that afterwards everything is possible. This config is really quite complicated. If this is too much for you, you can also really just start with the blank ``` haskell myXmobarPP :: PP myXmobarPP = def ``` then add something, reload xmonad, see how things change and whether you like them. If not, remove that part and try something else. If you do, try to understand how that particular piece of code works. You'll have something approaching the above that you fully understand in no time! ## Configuring Related Utilities So now you've got a status bar and xmonad. We still need a few more things: a screensaver, a tray for our apps that have tray icons, a way to set our desktop background, and the like. For this, we will need a few pieces of software. ``` shell apt-get install trayer xscreensaver ``` If you want a network applet, something to set your desktop background, and a power-manager: ``` shell apt-get install nm-applet feh xfce4-power-manager ``` First, configure xscreensaver how you like it with the `xscreensaver-demo` command. Now, we will set these things up in `~/.xinitrc` (we could also do most of this in xmonad's `startupHook`, but `~/.xinitrc` is perhaps more standard). If you want to use xmonad with a desktop environment, see [Basic Desktop Environment Integration] for how to do this. Your `~/.xinitrc` may wind up looking like this: ``` shell #!/bin/sh # [... default stuff that your distro may throw in here ...] # # Set up an icon tray trayer --edge top --align right --SetDockType true --SetPartialStrut true \ --expand true --width 10 --transparent true --tint 0x5f5f5f --height 18 & # Set the default X cursor to the usual pointer xsetroot -cursor_name left_ptr # Set a nice background feh --bg-fill --no-fehbg ~/.wallpapers/haskell-red-noise.png # Fire up screensaver xscreensaver -no-splash & # Power Management xfce4-power-manager & if [ -x /usr/bin/nm-applet ] ; then nm-applet --sm-disable & fi exec xmonad ``` Notice the call to `trayer` above. The options tell it to go on the top right, with a default width of 10% of the screen (to nicely match up with xmobar, which we set to a width of 90% of the screen). We give it a color and a height. Then we fire up the rest of the programs that interest us. Finally, we start xmonad. blank desktop Mission accomplished! Of course substitute the wallpaper for one of your own. If you like the one used above, you can find it [here](https://i.imgur.com/9MQHuZx.png). ## Final Touches There may be some programs that you don't want xmonad to tile. The classic example here is the [GNU Image Manipulation Program]; it pops up all sorts of new windows all the time, and they work best at defined sizes. It makes sense for xmonad to float these kinds of windows by default. This kind of behaviour can be achieved via the `manageHook`, which runs when windows are created. There are several functions to help you match on a certain window in [XMonad.ManageHook]. For example, suppose we'd want to match on the class name of the application. With the application open, open another terminal and invoke the `xprop` command. Then click on the application that you would like to know the properties of. In our case you should see (among other things) ``` shell WM_CLASS(STRING) = "gimp", "Gimp" ``` The second string in `WM_CLASS` is the class name, which we can access with `className` from [XMonad.ManageHook]. The first one is usually called the instance name and is matched-on via `appName` from the same module. Let's use the class name for now. We can tell all windows with that class name to float by defining the following manageHook: ``` haskell myManageHook = (className =? "Gimp" --> doFloat) ``` Say we also want to float all dialog windows. This is easy with the `isDialog` function from [XMonad.Hooks.ManageHelpers] (which you should import) and a little modification to the `myManageHook` function: ``` haskell myManageHook :: ManageHook myManageHook = composeAll [ className =? "Gimp" --> doFloat , isDialog --> doFloat ] ``` Now we just need to tell xmonad to actually use our manageHook. This is as easy as overriding the `manageHook` field in `myConfig`. You can do it like this: ``` haskell myConfig = def { modMask = mod4Mask -- Rebind Mod to the Super key , layoutHook = myLayout -- Use custom layouts , manageHook = myManageHook -- Match on certain windows } `additionalKeysP` [ ("M-S-z", spawn "xscreensaver-command -lock") , ("M-C-s", unGrab *> spawn "scrot -s" ) , ("M-f" , spawn "firefox" ) ] ``` ## The Whole Thing The full `~/.config/xmonad/xmonad.hs`, in all its glory, now looks like this: ``` haskell import XMonad import XMonad.Hooks.DynamicLog import XMonad.Hooks.ManageDocks import XMonad.Hooks.ManageHelpers import XMonad.Hooks.StatusBar import XMonad.Hooks.StatusBar.PP import XMonad.Util.EZConfig import XMonad.Util.Loggers import XMonad.Util.Ungrab import XMonad.Layout.Magnifier import XMonad.Layout.ThreeColumns import XMonad.Hooks.EwmhDesktops main :: IO () main = xmonad . ewmhFullscreen . ewmh . withEasySB (statusBarProp "xmobar" (pure myXmobarPP)) defToggleStrutsKey $ myConfig myConfig = def { modMask = mod4Mask -- Rebind Mod to the Super key , layoutHook = myLayout -- Use custom layouts , manageHook = myManageHook -- Match on certain windows } `additionalKeysP` [ ("M-S-z", spawn "xscreensaver-command -lock") , ("M-C-s", unGrab *> spawn "scrot -s" ) , ("M-f" , spawn "firefox" ) ] myManageHook :: ManageHook myManageHook = composeAll [ className =? "Gimp" --> doFloat , isDialog --> doFloat ] myLayout = tiled ||| Mirror tiled ||| Full ||| threeCol where threeCol = magnifiercz' 1.3 $ ThreeColMid nmaster delta ratio tiled = Tall nmaster delta ratio nmaster = 1 -- Default number of windows in the master pane ratio = 1/2 -- Default proportion of screen occupied by master pane delta = 3/100 -- Percent of screen to increment by when resizing panes myXmobarPP :: PP myXmobarPP = def { ppSep = magenta " • " , ppTitleSanitize = xmobarStrip , ppCurrent = wrap " " "" . xmobarBorder "Top" "#8be9fd" 2 , ppHidden = white . wrap " " "" , ppHiddenNoWindows = lowWhite . wrap " " "" , ppUrgent = red . wrap (yellow "!") (yellow "!") , ppOrder = \[ws, l, _, wins] -> [ws, l, wins] , ppExtras = [logTitles formatFocused formatUnfocused] } where formatFocused = wrap (white "[") (white "]") . magenta . ppWindow formatUnfocused = wrap (lowWhite "[") (lowWhite "]") . blue . ppWindow -- | Windows should have *some* title, which should not not exceed a -- sane length. ppWindow :: String -> String ppWindow = xmobarRaw . (\w -> if null w then "untitled" else w) . shorten 30 blue, lowWhite, magenta, red, white, yellow :: String -> String magenta = xmobarColor "#ff79c6" "" blue = xmobarColor "#bd93f9" "" white = xmobarColor "#f8f8f2" "" yellow = xmobarColor "#f1fa8c" "" red = xmobarColor "#ff5555" "" lowWhite = xmobarColor "#bbbbbb" "" ``` ## Further Customizations The following section mostly consists of extra bits—feel free to skip this and jump directly to [Get in Touch](#get-in-touch). We've covered a lot of ground here and sometimes it's really better to let things settle a bit before going further; much more so if you're happy with how things are looking and feeling right now! ### Beautifying Xmobar A usual starting point for beautifying xmobar is either to use `xpm` icons, or a font like `font-awesome` to add icons to the rest of the text. We will show you how to do the latter. Xmobar, thankfully, makes this very easy; just put a font down under `additionalFonts` and wrap your Icons with `` tags and the respective index of the font (starting from `1`). As an example, consider how we would extend our configuration above with this new functionality: ``` haskell Config { overrideRedirect = False , font = "xft:iosevka-9" , additionalFonts = ["xft:FontAwesome-9"] ... , Run Battery [ ... , "--lows" , "\62020 " , "--mediums", "\62018 " , "--highs" , "\62016 " ... ] ... } ``` For an explanation of the battery commands used above, see xmobar's [battery] documentation. You can also specify workspaces in the same way and feed them to xmobar via the property (e.g. have `"\xf120"` as one of your workspace names). As an example how this would look like in a real configuration, you can look at [Liskin's old][liskin-xmobarrc-old], [Liskin's current][liskin-xmobarrc], [slotThe's][slotThe-xmobarrc], or [TheMC47's][TheMC47-xmobarrc] xmobar configuration. Do note that the last three are Haskell-based and thus may be a little hard to understand for newcomers. [liskin-xmobarrc-old]: https://github.com/liskin/dotfiles/blob/75dfc057c33480ee9d3300d4d02fb79a986ef3a5/.xmobarrc [liskin-xmobarrc]: https://github.com/liskin/dotfiles/blob/home/.xmonad/xmobar.hs [TheMC47-xmobarrc]: https://github.com/TheMC47/dotfiles/tree/master/xmobar/xmobarrc [slotThe-xmobarrc]: https://gitlab.com/slotThe/dotfiles/-/blob/master/xmobar/.config/xmobarrc/src/xmobarrc.hs ### Renaming Layouts `Magnifier NoMaster ThreeCol` is quite a mouthful to show in your bar, right? Thankfully there is the nifty [XMonad.Layout.Renamed], which makes renaming layouts easy! We will focus on the `Replace` constructor here, as a lot of people will find that that's all they need. To use it we again follow the documentation (try it yourself!)—import the module and then change `myLayout` like this: ``` haskell myLayout = tiled ||| Mirror tiled ||| Full ||| threeCol where threeCol = renamed [Replace "ThreeCol"] $ magnifiercz' 1.3 $ ThreeColMid nmaster delta ratio tiled = Tall nmaster delta ratio nmaster = 1 -- Default number of windows in the master pane ratio = 1/2 -- Default proportion of screen occupied by master pane delta = 3/100 -- Percent of screen to increment by when resizing panes ``` The new line `renamed [Replace "ThreeCol"]` tells the layout to throw its current name away and use `ThreeCol` instead. After reloading xmonad, you should now see this shorter name in your bar. The line breaks here are just cosmetic, by the way; if you want you can write everything in one line: ``` haskell threeCol = renamed [Replace "ThreeCol"] $ magnifiercz' 1.3 $ ThreeColMid nmaster delta ratio ``` ## Get in Touch The `irc.libera.chat/#xmonad` channel is very friendly and helpful. It is possible that people will not immediately answer—we do have lives as well, after all :). Eventually though, people will usually chime in if they have something helpful to say; sometimes this takes 10 minutes, other times it may well take 10 hours. If you don't have an IRC client ready to go, the easiest way to join is via [webchat]—just jot down a username and you should be good to go! There is a [log] of the channel available, in case you do have to disconnect at some point, so don't worry about missing any messages. If you're not a fan of real-time interactions, you can also post to the [xmonad mailing list] or the [xmonad subreddit]. ## Trouble? Check `~/.xsession-errors` or your distribution's equivalent first. If you're using a distribution that does not log into a file automatically, you will have to set this up manually. For example, you could put something like ``` shell if [[ ! $DISPLAY ]]; then exec startx >& ~/.xsession-errors fi ``` into your `~/.profile` file to explicitly log everything into `~/.xsession-errors`. If you can't figure out what's wrong, don't hesitate to [get in touch](#get-in-touch)! ## Closing Thoughts That was quite a ride! Don't worry if you didn't understand everything perfectly, these things take time. You can re-read parts of this guide as often as you need to and—with the risk of sounding like a broken record—if you can't figure something out really do not be afraid to [get in touch](#get-in-touch). If you want to see a few more complicated examples of other peoples xmonad configurations, look no further! Below are (in alphabetical order) the configurations of a few of xmonad's maintainers. Just keep in mind that these setups are very customized and perhaps a little bit hard to replicate (some may rely on features only available in personal forks or git), may or may not be documented, and most aren't very pretty either :) - [byorgey](https://github.com/byorgey/dotfiles) - [geekosaur](https://github.com/geekosaur/xmonad.hs/tree/pyanfar) - [liskin](https://github.com/liskin/dotfiles/tree/home/.xmonad) - [psibi](https://github.com/psibi/dotfiles/tree/master/xmonad) - [slotThe](https://gitlab.com/slotThe/dotfiles/-/tree/master/xmonad/.config/xmonad) - [TheMC47](https://github.com/TheMC47/dotfiles/tree/master/xmonad/.xmonad) [log]: https://ircbrowse.tomsmeding.com/browse/lcxmonad [EWMH]: https://specifications.freedesktop.org/wm-spec/wm-spec-1.3.html [ICCCM]: https://tronche.com/gui/x/icccm/ [webchat]: https://web.libera.chat/#xmonad [about xmonad]: https://xmonad.org/about.html [shell variable]: https://www.shellscript.sh/variables1.html [xmonad-testing]: https://github.com/xmonad/xmonad-testing [xmonad subreddit]: https://old.reddit.com/r/xmonad/ [xmonad guided tour]: https://xmonad.org/tour.html [xmonad mailing list]: https://mail.haskell.org/mailman/listinfo/xmonad [xmonad's GitHub page]: https://github.com/xmonad/xmonad [trayer-padding-icon.sh]: https://codeberg.org/xmobar/xmobar/issues/239#issuecomment-537931 [xmonad-contrib documentation]: https://hackage.haskell.org/package/xmonad-contrib [GNU Image Manipulation Program]: https://www.gimp.org/ [Basic Desktop Environment Integration]: https://wiki.haskell.org/Xmonad/Basic_Desktop_Environment_Integration [Hacks]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Util-Hacks.html [PP record]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Hooks-DynamicLog.html#t:PP [INSTALL.md]: INSTALL.md [XMonad.Config]: https://github.com/xmonad/xmonad/blob/master/src/XMonad/Config.hs [XMonad.ManageHook]: https://xmonad.github.io/xmonad-docs/xmonad/XMonad-ManageHook.html [XMonad.Util.Loggers]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Util-Loggers.html [XMonad.Util.EZConfig]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Util-EZConfig.html [XMonad.Layout.Renamed]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Layout-Renamed.html [XMonad.Layout.Magnifier]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Layout-Magnifier.html [XMonad.Doc.Contributing]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Doc-Configuring.html [XMonad.Hooks.EwmhDesktops]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Hooks-EwmhDesktops.html [XMonad.Layout.ThreeColumns]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Layout-ThreeColumns.html [XMonad.Hooks.ManageHelpers]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Hooks-ManageHelpers.html [XMonad.Util.ClickableWorkspaces]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Util-ClickableWorkspaces.html [xmobar]: https://xmobar.org/ [battery]: https://codeberg.org/xmobar/xmobar/src/branch/master/doc/plugins.org#batteryp-dirs-args-refreshrate [xmobar.hs]: https://codeberg.org/xmobar/xmobar/src/branch/master/etc/xmobar.hs [Wikipedia page]: https://en.wikipedia.org/wiki/ICAO_airport_code#Prefixes [quick-start.org]: https://codeberg.org/xmobar/xmobar/src/branch/master/doc/quick-start.org#configuration-options [jao's xmobar.hs]: https://codeberg.org/jao/xmobar-config [weather monitor]: https://codeberg.org/xmobar/xmobar/src/branch/master/doc/plugins.org#weather-monitors [xmobar's `Installation` section]: https://codeberg.org/xmobar/xmobar#installation [Haskell]: https://www.haskell.org/ [trayer-srg]: https://github.com/sargon/trayer-srg [record update]: http://learnyouahaskell.com/making-our-own-types-and-typeclasses [lambda abstraction]: https://wiki.haskell.org/Lambda_abstraction [GNU Emacs conventions]: https://www.gnu.org/software/emacs/manual/html_node/elisp/Key-Sequences.html#Key-Sequences xmonad-0.17.2/man/0000755000000000000000000000000007346545000012033 5ustar0000000000000000xmonad-0.17.2/man/xmonad.10000644000000000000000000001430007346545000013401 0ustar0000000000000000.\" Automatically generated by Pandoc 2.9.2.1 .\" .TH "XMONAD" "1" "27 October 2021" "Tiling Window Manager" "" .hy .SH Name .PP xmonad - Tiling Window Manager .SH Description .PP \f[I]xmonad\f[R] 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[R] is configured in Haskell, and custom layout algorithms may be implemented by the user in config files. A principle of \f[I]xmonad\f[R] 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[R] 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[R] 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[R] places each window into a \[lq]workspace\[rq]. 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[R] 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 \[en]recompile Recompiles your \f[I]xmonad.hs\f[R] configuration .TP \[en]restart Causes the currently running \f[I]xmonad\f[R] process to restart .TP \[en]replace Replace the current window manager with xmonad .TP \[en]version Display version of \f[I]xmonad\f[R] .TP \[en]verbose-version Display detailed version of \f[I]xmonad\f[R] .SS Default keyboard bindings .TP mod-shift-return Launch terminal .TP mod-p Launch dmenu .TP mod-shift-p Launch gmrun .TP mod-shift-c Close the focused window .TP mod-space Rotate through the available layout algorithms .TP mod-shift-space Reset the layouts on the current workspace to default .TP mod-n Resize viewed windows to the correct size .TP mod-tab Move focus to the next window .TP mod-shift-tab Move focus to the previous window .TP mod-j Move focus to the next window .TP mod-k Move focus to the previous window .TP mod-m Move focus to the master window .TP mod-return Swap the focused window and the master window .TP mod-shift-j Swap the focused window with the next window .TP mod-shift-k Swap the focused window with the previous window .TP mod-h Shrink the master area .TP mod-l Expand the master area .TP mod-t Push window back into tiling .TP mod-comma Increment the number of windows in the master area .TP mod-period Deincrement the number of windows in the master area .TP mod-shift-q Quit xmonad .TP mod-q Restart xmonad .TP mod-shift-slash Run xmessage with a summary of the default keybindings (useful for beginners) .TP mod-question Run xmessage with a summary of the default keybindings (useful for beginners) .TP mod-[1..9] Switch to workspace N .TP mod-shift-[1..9] Move client to workspace N .TP mod-{w,e,r} Switch to physical/Xinerama screens 1, 2, or 3 .TP mod-shift-{w,e,r} Move client to screen 1, 2, or 3 .TP mod-button1 Set the window to floating mode and move by dragging .TP mod-button2 Raise the window to the top of the stack .TP mod-button3 Set the window to floating mode and resize by dragging .SH Examples .PP To use xmonad as your window manager add to your \f[I]\[ti]/.xinitrc\f[R] file: .RS .PP exec xmonad .RE .SH Customization .PP xmonad is customized in your \f[I]xmonad.hs\f[R], and then restarted with mod-q. You can choose where your configuration file lives by .IP "1." 3 Setting \f[C]XMONAD_DATA_DIR,\f[R] \f[C]XMONAD_CONFIG_DIR\f[R], and \f[C]XMONAD_CACHE_DIR\f[R]; \f[I]xmonad.hs\f[R] is then expected to be in \f[C]XMONAD_CONFIG_DIR\f[R]. .IP "2." 3 Creating \f[I]xmonad.hs\f[R] in \f[I]\[ti]/.xmonad\f[R]. .IP "3." 3 Creating \f[I]xmonad.hs\f[R] in \f[C]XDG_CONFIG_HOME\f[R]. Note that, in this case, xmonad will use \f[C]XDG_DATA_HOME\f[R] and \f[C]XDG_CACHE_HOME\f[R] for its data and cache directory respectively. .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 (https://xmonad.org). .SS Modular Configuration .PP As of \f[I]xmonad-0.9\f[R], any additional Haskell modules may be placed in \f[I]\[ti]/.xmonad/lib/\f[R] are available in GHC\[cq]s searchpath. Hierarchical modules are supported: for example, the file \f[I]\[ti]/.xmonad/lib/XMonad/Stack/MyAdditions.hs\f[R] could contain: .IP .nf \f[C] module XMonad.Stack.MyAdditions (function1) where function1 = error \[dq]function1: Not implemented yet!\[dq] \f[R] .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 (https://github.com/xmonad/xmonad/issues) xmonad-0.17.2/man/xmonad.1.html0000644000000000000000000002733407346545000014357 0ustar0000000000000000 XMONAD(1) Tiling Window Manager

XMONAD(1) Tiling Window Manager

27 October 2021

Name

xmonad - 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 xmonad.hs configuration
–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-question
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 your xmonad.hs, and then restarted with mod-q. You can choose where your configuration file lives by

  1. Setting XMONAD_DATA_DIR, XMONAD_CONFIG_DIR, and XMONAD_CACHE_DIR; xmonad.hs is then expected to be in XMONAD_CONFIG_DIR.
  2. Creating xmonad.hs in ~/.xmonad.
  3. Creating xmonad.hs in XDG_CONFIG_HOME. Note that, in this case, xmonad will use XDG_DATA_HOME and XDG_CACHE_HOME for its data and cache directory respectively.

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.17.2/man/xmonad.1.markdown0000644000000000000000000001367507346545000015240 0ustar0000000000000000% XMONAD(1) Tiling Window Manager % % 27 October 2021 # Name xmonad - 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 _xmonad.hs_ configuration --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-question : 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 your _xmonad.hs_, and then restarted with mod-q. You can choose where your configuration file lives by 1. Setting `XMONAD_DATA_DIR,` `XMONAD_CONFIG_DIR`, and `XMONAD_CACHE_DIR`; _xmonad.hs_ is then expected to be in `XMONAD_CONFIG_DIR`. 2. Creating _xmonad.hs_ in _~/.xmonad_. 3. Creating _xmonad.hs_ in `XDG_CONFIG_HOME`. Note that, in this case, xmonad will use `XDG_DATA_HOME` and `XDG_CACHE_HOME` for its data and cache directory respectively. 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: ```haskell 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]: https://xmonad.org [bugtracker]: https://github.com/xmonad/xmonad/issues xmonad-0.17.2/man/xmonad.hs0000644000000000000000000002731407346545000013664 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 exitSuccess) -- Restart xmonad , ((modm , xK_q ), spawn "xmonad --recompile; xmonad --restart") -- Run xmessage with a summary of the default keybindings (useful for beginners) , ((modm .|. shiftMask, xK_slash ), xmessage help) ] ++ -- -- 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 = def { -- 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 } -- | 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", "mod-Shift-/ Show this help message with the default keybindings", "", "-- 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.17.2/src/0000755000000000000000000000000007346545000012047 5ustar0000000000000000xmonad-0.17.2/src/XMonad.hs0000644000000000000000000000215207346545000013571 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.17.2/src/XMonad/0000755000000000000000000000000007346545000013235 5ustar0000000000000000xmonad-0.17.2/src/XMonad/Config.hs0000644000000000000000000003434507346545000015007 0ustar0000000000000000{-# OPTIONS_GHC -fno-warn-missing-signatures -fno-warn-orphans #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} ----------------------------------------------------------------------------- -- | -- Module : XMonad.Config -- Copyright : (c) Spencer Janssen 2007 -- License : BSD3-style (see LICENSE) -- -- Maintainer : dons@galois.com -- Stability : stable -- Portability : portable -- -- This module specifies the default configuration values for xmonad. -- -- DO NOT MODIFY THIS FILE! It won't work. You may configure xmonad -- by providing your own @~\/.xmonad\/xmonad.hs@ that overrides -- specific fields in the default config, 'def'. For a starting point, you can -- copy the @xmonad.hs@ found in the @man@ directory, or look at -- examples on the xmonad wiki. -- ------------------------------------------------------------------------ module XMonad.Config (defaultConfig, Default(..)) where -- -- Useful imports -- import XMonad.Core as XMonad hiding (workspaces,manageHook,keys,logHook,startupHook,borderWidth,mouseBindings ,layoutHook,modMask,terminal,normalBorderColor,focusedBorderColor,focusFollowsMouse ,handleEventHook,clickJustFocuses,rootMask,clientMask) import qualified XMonad.Core as XMonad (workspaces,manageHook,keys,logHook,startupHook,borderWidth,mouseBindings ,layoutHook,modMask,terminal,normalBorderColor,focusedBorderColor,focusFollowsMouse ,handleEventHook,clickJustFocuses,rootMask,clientMask) import XMonad.Layout import XMonad.Operations import XMonad.ManageHook import qualified XMonad.StackSet as W import Data.Bits ((.|.)) import Data.Default.Class 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 =? "mplayer2" --> doFloat ] ------------------------------------------------------------------------ -- Logging -- | Perform an arbitrary action on each internal state change or X event. -- Examples include: -- -- * do nothing -- -- * log the state to stdout -- -- See the 'DynamicLog' extension for examples. -- logHook :: X () logHook = return () ------------------------------------------------------------------------ -- Event handling -- | Defines a custom handler function for X Events. The function should -- return (All True) if the default handler is to be run afterwards. -- To combine event hooks, use mappend or mconcat from Data.Monoid. handleEventHook :: Event -> X All handleEventHook _ = return (All True) -- | Perform an arbitrary action at xmonad startup. startupHook :: X () startupHook = return () ------------------------------------------------------------------------ -- Extensible layouts -- -- You can specify and transform your layouts by modifying these values. -- If you change layout bindings be sure to use 'mod-shift-space' after -- restarting (with 'mod-q') to reset your layout state to the new -- defaults, as xmonad preserves your old layout settings by default. -- -- | The available layouts. Note that each layout is separated by |||, which -- denotes layout choice. layout = tiled ||| Mirror tiled ||| Full where -- default tiling algorithm partitions the screen into two panes tiled = Tall nmaster delta ratio -- The default number of windows in the master pane nmaster = 1 -- Default proportion of screen occupied by master pane ratio = 1/2 -- Percent of screen to increment by when resizing panes delta = 3/100 ------------------------------------------------------------------------ -- Event Masks: -- | The client events that xmonad is interested in clientMask :: EventMask clientMask = structureNotifyMask .|. enterWindowMask .|. propertyChangeMask -- | The root events that xmonad is interested in rootMask :: EventMask rootMask = substructureRedirectMask .|. substructureNotifyMask .|. enterWindowMask .|. leaveWindowMask .|. structureNotifyMask .|. buttonPressMask ------------------------------------------------------------------------ -- Key bindings: -- | The preferred terminal program, which is used in a binding below and by -- certain contrib modules. terminal :: String terminal = "xterm" -- | Whether focus follows the mouse pointer. focusFollowsMouse :: Bool focusFollowsMouse = True -- | Whether a mouse click select the focus or is just passed to the window clickJustFocuses :: Bool clickJustFocuses = True -- | The xmonad key bindings. Add, modify or remove key bindings here. -- -- (The comment formatting character is used when generating the manpage) -- keys :: XConfig Layout -> M.Map (KeyMask, KeySym) (X ()) keys conf@(XConfig {XMonad.modMask = modMask}) = M.fromList $ -- launching and killing programs [ ((modMask .|. shiftMask, xK_Return), spawn $ XMonad.terminal conf) -- %! Launch terminal , ((modMask, xK_p ), spawn "dmenu_run") -- %! Launch dmenu , ((modMask .|. shiftMask, xK_p ), spawn "gmrun") -- %! Launch gmrun , ((modMask .|. shiftMask, xK_c ), kill) -- %! Close the focused window , ((modMask, xK_space ), sendMessage NextLayout) -- %! Rotate through the available layout algorithms , ((modMask .|. shiftMask, xK_space ), setLayout $ XMonad.layoutHook conf) -- %! Reset the layouts on the current workspace to default , ((modMask, xK_n ), refresh) -- %! Resize viewed windows to the correct size -- move focus up or down the window stack , ((modMask, xK_Tab ), windows W.focusDown) -- %! Move focus to the next window , ((modMask .|. shiftMask, xK_Tab ), windows W.focusUp ) -- %! Move focus to the previous window , ((modMask, xK_j ), windows W.focusDown) -- %! Move focus to the next window , ((modMask, xK_k ), windows W.focusUp ) -- %! Move focus to the previous window , ((modMask, xK_m ), windows W.focusMaster ) -- %! Move focus to the master window -- modifying the window order , ((modMask, xK_Return), windows W.swapMaster) -- %! Swap the focused window and the master window , ((modMask .|. shiftMask, xK_j ), windows W.swapDown ) -- %! Swap the focused window with the next window , ((modMask .|. shiftMask, xK_k ), windows W.swapUp ) -- %! Swap the focused window with the previous window -- resizing the master/slave ratio , ((modMask, xK_h ), sendMessage Shrink) -- %! Shrink the master area , ((modMask, xK_l ), sendMessage Expand) -- %! Expand the master area -- floating layer support , ((modMask, xK_t ), withFocused $ windows . W.sink) -- %! Push window back into tiling -- increase or decrease number of windows in the master area , ((modMask , xK_comma ), sendMessage (IncMasterN 1)) -- %! Increment the number of windows in the master area , ((modMask , xK_period), sendMessage (IncMasterN (-1))) -- %! Deincrement the number of windows in the master area -- quit, or restart , ((modMask .|. shiftMask, xK_q ), io 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 ), helpCommand) -- %! Run xmessage with a summary of the default keybindings (useful for beginners) -- repeat the binding for non-American layout keyboards , ((modMask , xK_question), helpCommand) -- %! 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 [((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)]] where helpCommand :: X () helpCommand = xmessage help -- | Mouse bindings: default actions bound to mouse events mouseBindings :: XConfig Layout -> M.Map (KeyMask, Button) (Window -> X ()) mouseBindings (XConfig {XMonad.modMask = modMask}) = M.fromList -- mod-button1 %! Set the window to floating mode and move by dragging [ ((modMask, button1), \w -> focus w >> mouseMoveWindow w >> windows W.shiftMaster) -- mod-button2 %! Raise the window to the top of the stack , ((modMask, button2), windows . (W.shiftMaster .) . W.focusWindow) -- mod-button3 %! Set the window to floating mode and resize by dragging , ((modMask, button3), \w -> focus w >> mouseResizeWindow w >> windows W.shiftMaster) -- you may also bind events to the mouse scroll wheel (button4 and button5) ] instance (a ~ Choose Tall (Choose (Mirror Tall) Full)) => Default (XConfig a) where def = XConfig { XMonad.borderWidth = borderWidth , XMonad.workspaces = workspaces , XMonad.layoutHook = layout , XMonad.terminal = terminal , XMonad.normalBorderColor = normalBorderColor , XMonad.focusedBorderColor = focusedBorderColor , XMonad.modMask = defaultModMask , XMonad.keys = keys , XMonad.logHook = logHook , XMonad.startupHook = startupHook , XMonad.mouseBindings = mouseBindings , XMonad.manageHook = manageHook , XMonad.handleEventHook = handleEventHook , XMonad.focusFollowsMouse = focusFollowsMouse , XMonad.clickJustFocuses = clickJustFocuses , XMonad.clientMask = clientMask , XMonad.rootMask = rootMask , XMonad.handleExtraArgs = \ xs theConf -> case xs of [] -> return theConf _ -> fail ("unrecognized flags:" ++ show xs) , XMonad.extensibleConf = M.empty } -- | The default set of configuration values itself {-# DEPRECATED defaultConfig "Use def (from Data.Default, and re-exported by XMonad and XMonad.Config) instead." #-} defaultConfig :: XConfig (Choose Tall (Choose (Mirror Tall) Full)) defaultConfig = def -- | Finally, a copy of the default bindings in simple textual tabular format. help :: String help = unlines ["The default modifier key is 'alt'. Default keybindings:", "", "-- launching and killing programs", "mod-Shift-Enter Launch xterminal", "mod-p Launch dmenu", "mod-Shift-p Launch gmrun", "mod-Shift-c Close/kill the focused window", "mod-Space Rotate through the available layout algorithms", "mod-Shift-Space Reset the layouts on the current workSpace to default", "mod-n Resize/refresh viewed windows to the correct size", "mod-Shift-/ Show this help message with the default keybindings", "", "-- 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", "", "-- Workspaces & screens", "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", "", "-- 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.17.2/src/XMonad/Core.hs0000644000000000000000000010202007346545000014454 0ustar0000000000000000{-# LANGUAGE CPP #-} {-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE ScopedTypeVariables #-} ----------------------------------------------------------------------------- -- | -- 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(..), ConfExtension(..), runX, catchX, userCode, userCodeDef, io, catchIO, installSignalHandlers, uninstallSignalHandlers, withDisplay, withWindowSet, isRoot, runOnWorkspaces, getAtom, spawn, spawnPID, xfork, xmessage, recompile, trace, whenJust, whenX, getXMonadDir, getXMonadCacheDir, getXMonadDataDir, stateFileName, binFileName, atom_WM_STATE, atom_WM_PROTOCOLS, atom_WM_DELETE_WINDOW, atom_WM_TAKE_FOCUS, withWindowAttributes, ManageHook, Query(..), runQuery, Directories'(..), Directories, getDirectories, ) where import XMonad.StackSet hiding (modify) import Prelude import Control.Exception (fromException, try, bracket_, throw, finally, SomeException(..)) import qualified Control.Exception as E import Control.Applicative ((<|>), empty) import Control.Monad.Fail import Control.Monad.Fix (fix) import Control.Monad.State import Control.Monad.Reader import Control.Monad (filterM, guard, liftM2, void, when) import Data.Semigroup import Data.Traversable (for) import Data.Time.Clock (UTCTime) import Data.Default.Class import System.Environment (lookupEnv) import System.FilePath import System.IO import System.Info import System.Posix.Env (getEnv) 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 (getWindowAttributes, WindowAttributes, Event) import Data.Typeable import Data.List (isInfixOf, (\\)) import Data.Maybe (isJust,fromMaybe) 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.Util.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 , directories :: !Directories -- ^ directories to use } -- todo, better name data XConfig l = XConfig { normalBorderColor :: !String -- ^ Non focused windows border color. Default: \"#dddddd\" , focusedBorderColor :: !String -- ^ Focused windows border color. Default: \"#ff0000\" , terminal :: !String -- ^ The preferred terminal application. Default: \"xterm\" , layoutHook :: !(l Window) -- ^ The available layouts , manageHook :: !ManageHook -- ^ The action to run when a new window is opened , handleEventHook :: !(Event -> X All) -- ^ Handle an X event, returns (All True) if the default handler -- should also be run afterwards. mappend should be used for combining -- event hooks in most cases. , workspaces :: ![String] -- ^ The list of workspaces' names , modMask :: !KeyMask -- ^ the mod modifier , keys :: !(XConfig Layout -> M.Map (ButtonMask,KeySym) (X ())) -- ^ The key binding: a map from key presses and actions , mouseBindings :: !(XConfig Layout -> M.Map (ButtonMask, Button) (Window -> X ())) -- ^ The mouse bindings , borderWidth :: !Dimension -- ^ The border width , logHook :: !(X ()) -- ^ The action to perform when the windows set is changed , startupHook :: !(X ()) -- ^ The action to perform on startup , focusFollowsMouse :: !Bool -- ^ Whether window entry events can change focus , clickJustFocuses :: !Bool -- ^ False to make a click which changes focus to be additionally passed to the window , clientMask :: !EventMask -- ^ The client events that xmonad is interested in , rootMask :: !EventMask -- ^ The root events that xmonad is interested in , handleExtraArgs :: !([String] -> XConfig Layout -> IO (XConfig Layout)) -- ^ Modify the configuration, complain about extra arguments etc. with arguments that are not handled by default , extensibleConf :: !(M.Map TypeRep ConfExtension) -- ^ Stores custom config information. -- -- The module "XMonad.Util.ExtensibleConf" in xmonad-contrib -- provides additional information and a simple interface for using this. } 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 newtype 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, Applicative, Monad, MonadFail, MonadIO, MonadState XState, MonadReader XConf) instance Semigroup a => Semigroup (X a) where (<>) = liftM2 (<>) instance (Monoid a) => Monoid (X a) where mempty = pure mempty instance Default a => Default (X a) where def = return def type ManageHook = Query (Endo WindowSet) newtype Query a = Query (ReaderT Window X a) deriving (Functor, Applicative, Monad, MonadReader Window, MonadIO) runQuery :: Query a -> Window -> X a runQuery (Query m) = runReaderT m instance Semigroup a => Semigroup (Query a) where (<>) = liftM2 (<>) instance Monoid a => Monoid (Query a) where mempty = pure mempty instance Default a => Default (Query a) where def = return def -- | Run the 'X' monad, given a chunk of 'X' monad code, and an initial state -- Return the result, and final state runX :: XConf -> XState -> X a -> IO (a, XState) runX c st (X a) = runStateT (runReaderT a c) st -- | Run in the 'X' monad, and in case of exception, and catch it and log it -- to stderr, and run the error case. catchX :: X a -> X a -> X a catchX job errcase = do st <- get c <- ask (a, s') <- io $ runX c st job `E.catch` \e -> case fromException e of Just (_ :: ExitCode) -> throw e _ -> 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 <$> a) (return Nothing) -- | Same as userCode but with a default argument to return instead of using -- Maybe, provided for convenience. userCodeDef :: a -> X a -> X a userCodeDef defValue a = fromMaybe defValue <$> 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 -- | Safely access window attributes. withWindowAttributes :: Display -> Window -> (WindowAttributes -> X ()) -> X () withWindowAttributes dpy win f = do wa <- userCode (io $ getWindowAttributes dpy win) catchX (whenJust wa f) (return ()) -- | True if the given window is the root window isRoot :: Window -> X Bool isRoot w = asks $ (w ==) . 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. -- -- All of the methods have default implementations, so there is no -- minimal complete definition. They do, however, have a dependency -- structure by default; this is something to be aware of should you -- choose to implement one of these methods. Here is how a minimal -- complete definition would look like if we did not provide any default -- implementations: -- -- * 'runLayout' || (('doLayout' || 'pureLayout') && 'emptyLayout') -- -- * 'handleMessage' || 'pureMessage' -- -- * 'description' -- -- 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), Typeable layout) => 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 Eq instance Message LayoutMessages -- --------------------------------------------------------------------- -- Extensible state/config -- -- | 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 {-# MINIMAL initialValue #-} -- | 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 -- | Existential type to store a config extension. data ConfExtension = forall a. Typeable a => ConfExtension a -- --------------------------------------------------------------------- -- | General utilities -- -- Lift an 'IO' action into the 'X' monad io :: MonadIO m => IO a -> m a io = liftIO -- | Lift an 'IO' action into the 'X' monad. If the action results in an 'IO' -- exception, log the exception to stderr and continue normal execution. catchIO :: MonadIO m => IO () -> m () catchIO f = io (f `E.catch` \(SomeException e) -> hPrint stderr e >> hFlush stderr) -- | spawn. Launch an external application. Specifically, it double-forks and -- runs the 'String' you pass as a command to \/bin\/sh. -- -- Note this function assumes your locale uses utf8. spawn :: MonadIO m => String -> m () spawn x = void $ spawnPID x -- | Like 'spawn', but returns the 'ProcessID' of the launched application spawnPID :: MonadIO m => String -> m ProcessID spawnPID x = xfork $ executeFile "/bin/sh" False ["-c", 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 #if MIN_VERSION_unix(2,8,0) fd <- openFd "/dev/null" ReadOnly defaultFileFlags #else fd <- openFd "/dev/null" ReadOnly Nothing defaultFileFlags #endif dupTo fd stdInput closeFd fd -- | Use @xmessage@ to show information to the user. xmessage :: MonadIO m => String -> m () xmessage msg = void . xfork $ do xmessageBin <- fromMaybe "xmessage" <$> liftIO (lookupEnv "XMONAD_XMESSAGE") executeFile xmessageBin True [ "-default", "okay" , "-xrm", "*international:true" , "-xrm", "*fontSet:-*-fixed-medium-r-normal-*-18-*-*-*-*-*-*-*,-*-fixed-*-*-*-*-18-*-*-*-*-*-*-*,-*-*-*-*-*-*-18-*-*-*-*-*-*-*" , msg ] Nothing -- | 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 } } -- | All the directories that xmonad will use. They will be used for -- the following purposes: -- -- * @dataDir@: This directory is used by XMonad to store data files -- such as the run-time state file. -- -- * @cfgDir@: This directory is where user configuration files are -- stored (e.g, the xmonad.hs file). You may also create a @lib@ -- subdirectory in the configuration directory and the default recompile -- command will add it to the GHC include path. -- -- * @cacheDir@: This directory is used to store temporary files that -- can easily be recreated such as the configuration binary and any -- intermediate object files generated by GHC. -- Also, the XPrompt history file goes here. -- -- For how these directories are chosen, see 'getDirectories'. -- data Directories' a = Directories { dataDir :: !a , cfgDir :: !a , cacheDir :: !a } deriving (Show, Functor, Foldable, Traversable) -- | Convenient type alias for the most common case in which one might -- want to use the 'Directories' type. type Directories = Directories' FilePath -- | Build up the 'Dirs' that xmonad will use. They are chosen as -- follows: -- -- 1. If all three of xmonad's environment variables (@XMONAD_DATA_DIR@, -- @XMONAD_CONFIG_DIR@, and @XMONAD_CACHE_DIR@) are set, use them. -- 2. If there is a build script called @build@ or configuration -- @xmonad.hs@ in @~\/.xmonad@, set all three directories to -- @~\/.xmonad@. -- 3. Otherwise, use the @xmonad@ directory in @XDG_DATA_HOME@, -- @XDG_CONFIG_HOME@, and @XDG_CACHE_HOME@ (or their respective -- fallbacks). These directories are created if necessary. -- -- The xmonad configuration file (or the build script, if present) is -- always assumed to be in @cfgDir@. -- getDirectories :: IO Directories getDirectories = xmEnvDirs <|> xmDirs <|> xdgDirs where -- | Check for xmonad's environment variables first xmEnvDirs :: IO Directories xmEnvDirs = do let xmEnvs = Directories{ dataDir = "XMONAD_DATA_DIR" , cfgDir = "XMONAD_CONFIG_DIR" , cacheDir = "XMONAD_CACHE_DIR" } maybe empty pure . sequenceA =<< traverse getEnv xmEnvs -- | Check whether the config file or a build script is in the -- @~\/.xmonad@ directory xmDirs :: IO Directories xmDirs = do xmDir <- getAppUserDataDirectory "xmonad" conf <- doesFileExist $ xmDir "xmonad.hs" build <- doesFileExist $ xmDir "build" -- Place *everything* in ~/.xmonad if yes guard $ conf || build pure Directories{ dataDir = xmDir, cfgDir = xmDir, cacheDir = xmDir } -- | Use XDG directories as a fallback xdgDirs :: IO Directories xdgDirs = for Directories{ dataDir = XdgData, cfgDir = XdgConfig, cacheDir = XdgCache } $ \dir -> do d <- getXdgDirectory dir "xmonad" d <$ createDirectoryIfMissing True d -- | Return the path to the xmonad configuration directory. getXMonadDir :: X String getXMonadDir = asks (cfgDir . directories) {-# DEPRECATED getXMonadDir "Use `asks (cfgDir . directories)' instead." #-} -- | Return the path to the xmonad cache directory. getXMonadCacheDir :: X String getXMonadCacheDir = asks (cacheDir . directories) {-# DEPRECATED getXMonadCacheDir "Use `asks (cacheDir . directories)' instead." #-} -- | Return the path to the xmonad data directory. getXMonadDataDir :: X String getXMonadDataDir = asks (dataDir . directories) {-# DEPRECATED getXMonadDataDir "Use `asks (dataDir . directories)' instead." #-} binFileName, buildDirName :: Directories -> FilePath binFileName Directories{ cacheDir } = cacheDir "xmonad-" <> arch <> "-" <> os buildDirName Directories{ cacheDir } = cacheDir "build-" <> arch <> "-" <> os errFileName, stateFileName :: Directories -> FilePath errFileName Directories{ dataDir } = dataDir "xmonad.errors" stateFileName Directories{ dataDir } = dataDir "xmonad.state" srcFileName, libFileName :: Directories -> FilePath srcFileName Directories{ cfgDir } = cfgDir "xmonad.hs" libFileName Directories{ cfgDir } = cfgDir "lib" buildScriptFileName, stackYamlFileName :: Directories -> FilePath buildScriptFileName Directories{ cfgDir } = cfgDir "build" stackYamlFileName Directories{ cfgDir } = cfgDir "stack.yaml" -- | Compilation method for xmonad configuration. data Compile = CompileGhc | CompileStackGhc FilePath | CompileScript FilePath deriving (Show) -- | Detect compilation method by looking for known file names in xmonad -- configuration directory. detectCompile :: Directories -> IO Compile detectCompile dirs = tryScript <|> tryStack <|> useGhc where buildScript = buildScriptFileName dirs stackYaml = stackYamlFileName dirs tryScript = do guard =<< doesFileExist buildScript isExe <- isExecutable buildScript if isExe then do trace $ "XMonad will use build script at " <> show buildScript <> " to recompile." pure $ CompileScript buildScript else do trace $ "XMonad will not use build script, because " <> show buildScript <> " is not executable." trace $ "Suggested resolution to use it: chmod u+x " <> show buildScript empty tryStack = do guard =<< doesFileExist stackYaml canonStackYaml <- canonicalizePath stackYaml trace $ "XMonad will use stack ghc --stack-yaml " <> show canonStackYaml <> " to recompile." pure $ CompileStackGhc canonStackYaml useGhc = do trace $ "XMonad will use ghc to recompile, because neither " <> show buildScript <> " nor " <> show stackYaml <> " exists." pure CompileGhc isExecutable f = E.catch (executable <$> getPermissions f) (\(SomeException _) -> return False) -- | Should we recompile xmonad configuration? Is it newer than the compiled -- binary? shouldCompile :: Directories -> Compile -> IO Bool shouldCompile dirs CompileGhc = do libTs <- mapM getModTime . Prelude.filter isSource =<< allFiles (libFileName dirs) srcT <- getModTime (srcFileName dirs) binT <- getModTime (binFileName dirs) if any (binT <) (srcT : libTs) then True <$ trace "XMonad recompiling because some files have changed." else False <$ trace "XMonad skipping recompile because it is not forced (e.g. via --recompile), and neither xmonad.hs nor any *.hs / *.lhs / *.hsc files in lib/ have been changed." where isSource = flip elem [".hs",".lhs",".hsc"] . takeExtension allFiles t = do let prep = map (t) . Prelude.filter (`notElem` [".",".."]) cs <- prep <$> E.catch (getDirectoryContents t) (\(SomeException _) -> return []) ds <- filterM doesDirectoryExist cs concat . ((cs \\ ds):) <$> mapM allFiles ds shouldCompile dirs CompileStackGhc{} = do stackYamlT <- getModTime (stackYamlFileName dirs) binT <- getModTime (binFileName dirs) if binT < stackYamlT then True <$ trace "XMonad recompiling because some files have changed." else shouldCompile dirs CompileGhc shouldCompile _dirs CompileScript{} = True <$ trace "XMonad recompiling because a custom build script is being used." getModTime :: FilePath -> IO (Maybe UTCTime) getModTime f = E.catch (Just <$> getModificationTime f) (\(SomeException _) -> return Nothing) -- | Compile the configuration. compile :: Directories -> Compile -> IO ExitCode compile dirs method = bracket_ uninstallSignalHandlers installSignalHandlers $ withFile (errFileName dirs) WriteMode $ \err -> do let run = runProc (cfgDir dirs) err case method of CompileGhc -> do ghc <- fromMaybe "ghc" <$> lookupEnv "XMONAD_GHC" run ghc ghcArgs CompileStackGhc stackYaml -> run "stack" ["build", "--silent", "--stack-yaml", stackYaml] .&&. run "stack" ("ghc" : "--stack-yaml" : stackYaml : "--" : ghcArgs) CompileScript script -> run script [binFileName dirs] where ghcArgs = [ "--make" , "xmonad.hs" , "-i" -- only look in @lib@ , "-ilib" , "-fforce-recomp" , "-main-is", "main" , "-v0" , "-outputdir", buildDirName dirs , "-o", binFileName dirs ] -- waitForProcess =<< System.Process.runProcess, but without closing the err handle runProc cwd err exe args = do hPutStrLn err $ unwords $ "$" : exe : args hFlush err (_, _, _, h) <- createProcess_ "runProc" (proc exe args){ cwd = Just cwd, std_err = UseHandle err } waitForProcess h cmd1 .&&. cmd2 = cmd1 >>= \case ExitSuccess -> cmd2 e -> pure e -- | Check GHC output for deprecation warnings and notify the user if there -- were any. Report success otherwise. checkCompileWarnings :: Directories -> IO () checkCompileWarnings dirs = do ghcErr <- readFile (errFileName dirs) if "-Wdeprecations" `isInfixOf` ghcErr then do let msg = unlines $ ["Deprecations detected while compiling xmonad config: " <> srcFileName dirs] ++ lines ghcErr ++ ["","Please correct them or silence using {-# OPTIONS_GHC -Wno-deprecations #-}."] trace msg xmessage msg else trace "XMonad recompilation process exited with success!" -- | Notify the user that compilation failed and what was wrong. compileFailed :: Directories -> ExitCode -> IO () compileFailed dirs status = do ghcErr <- readFile (errFileName dirs) let msg = unlines $ ["Errors detected while compiling xmonad config: " <> srcFileName dirs] ++ 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 trace msg xmessage msg -- | Recompile the xmonad configuration file 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 -- the @lib@ directory (under the configuration directory) -- -- * custom @build@ script is being used -- -- The -i flag is used to restrict recompilation to the xmonad.hs file only, -- and any files in the aforementioned @lib@ directory. -- -- Compilation errors (if any) are logged to the @xmonad.errors@ file -- in the xmonad data directory. 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 => Directories -> Bool -> m Bool recompile dirs force = io $ do method <- detectCompile dirs willCompile <- if force then True <$ trace "XMonad recompiling (forced)." else shouldCompile dirs method if willCompile then do status <- compile dirs method if status == ExitSuccess then checkCompileWarnings dirs else compileFailed dirs status pure $ status == ExitSuccess else pure True -- | 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.17.2/src/XMonad/Layout.hs0000644000000000000000000002375407346545000015061 0ustar0000000000000000{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE PatternGuards #-} -- -------------------------------------------------------------------------- -- | -- Module : XMonad.Layout -- Copyright : (c) Spencer Janssen 2007 -- License : BSD3-style (see LICENSE) -- -- Maintainer : spencerjanssen@gmail.com -- Stability : unstable -- Portability : not portable, mtl, posix -- -- The collection of core layouts. -- ----------------------------------------------------------------------------- module XMonad.Layout ( Full(..), Tall(..), Mirror(..), Resize(..), IncMasterN(..), Choose(..), (|||), CLR(..), ChangeLayout(..), JumpToLayout(..), mirrorRect, splitVertically, splitHorizontally, splitHorizontallyBy, splitVerticallyBy, tile ) where import XMonad.Core import Graphics.X11 (Rectangle(..)) import Graphics.X11.Xlib.Extras ( Event(DestroyWindowEvent) ) 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 -- | Increase the number of clients in the master pane. newtype IncMasterN = IncMasterN Int 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 partitioned 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. Also see 'JumpToLayout'. data ChangeLayout = FirstLayout | NextLayout deriving (Eq, Show) instance Message ChangeLayout -- | A message to jump to a particular layout, specified by its -- description string. -- -- The argument given to a 'JumpToLayout' message should be the -- @description@ of the layout to be selected. If you use -- "XMonad.Hooks.DynamicLog" from @xmonad-contrib@, this is the name of -- the layout displayed in your status bar. Alternatively, you can use -- GHCi to determine the proper name to use. For example: -- -- > $ ghci -- > GHCi, version 6.8.2: http://www.haskell.org/ghc/ :? for help -- > Loading package base ... linking ... done. -- > :set prompt "> " -- don't show loaded module names -- > > :m +XMonad.Core -- load the xmonad core -- > > :m +XMonad.Layout.Grid -- load whatever module you want to use -- > > description Grid -- find out what it's called -- > "Grid" -- -- As yet another (possibly easier) alternative, you can use the -- "XMonad.Layout.Renamed" module (also in @xmonad-contrib@) to give -- custom names to your layouts, and use those. -- -- For example, if you want to jump directly to the 'Full' layout you -- can do -- -- > , ((modm .|. controlMask, xK_f), sendMessage $ JumpToLayout "Full") -- newtype JumpToLayout = JumpToLayout String instance Message JumpToLayout -- | The layout choice combinator (|||) :: l a -> r a -> Choose l r a (|||) = Choose CL infixr 5 ||| -- | A layout that allows users to switch between various layout options. data Choose l r a = Choose CLR (l a) (r a) deriving (Read, Show) -- | Choose the current sub-layout (left or right) in 'Choose'. data CLR = CL | CR deriving (Read, Show, Eq) data NextNoWrap = NextNoWrap deriving (Eq, Show) 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 -> CLR -> 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 (CL, CR) -> (hide l' , return r') (CR, CL) -> (return l', hide r' ) (_ , _ ) -> (return l', return r') f (x,y) = Just <$> liftM2 (Choose d') x y hide x = fromMaybe x <$> handle x Hide instance (LayoutClass l a, LayoutClass r a) => LayoutClass (Choose l r) a where runLayout (W.Workspace i (Choose CL l r) ms) = fmap (second . fmap $ flip (Choose CL) r) . runLayout (W.Workspace i l ms) runLayout (W.Workspace i (Choose CR l r) ms) = fmap (second . fmap $ Choose CR l) . runLayout (W.Workspace i r ms) description (Choose CL l _) = description l description (Choose CR _ 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 CL -> do ml <- handle l NextNoWrap case ml of Just _ -> choose c CL ml Nothing Nothing -> choose c CR Nothing =<< handle r FirstLayout CR -> choose c CR Nothing =<< handle r NextNoWrap handleMessage c@(Choose _ l _) m | Just FirstLayout <- fromMessage m = flip (choose c CL) 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 | Just e@DestroyWindowEvent{} <- fromMessage m = join $ liftM2 (choose c d) (handle l e) (handle r e) handleMessage c@(Choose d l r) m | Just (JumpToLayout desc) <- fromMessage m = do ml <- handleMessage l m mr <- handleMessage r m let md | desc == description (fromMaybe l ml) = CL | desc == description (fromMaybe r mr) = CR | otherwise = d choose c md ml mr handleMessage c@(Choose d l r) m = do ml' <- case d of CL -> handleMessage l m CR -> return Nothing mr' <- case d of CL -> return Nothing CR -> handleMessage r m choose c d ml' mr' xmonad-0.17.2/src/XMonad/Main.hs0000644000000000000000000004743007346545000014465 0ustar0000000000000000{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE MultiParamTypeClasses #-} ---------------------------------------------------------------------------- -- | -- 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, launch) where import System.Locale.SetLocale import qualified Control.Exception as E import Data.Bits import Data.List ((\\)) import Data.Foldable (traverse_) import Data.Function import qualified Data.Map as M import qualified Data.Set as S import Control.Monad.Reader import Control.Monad.State import Control.Monad (filterM, guard, unless, void, when) import Data.Maybe (fromMaybe, isJust) import Data.Monoid (getAll) 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 import System.Directory import System.Info import System.Environment (getArgs, getProgName, withArgs) import System.Posix.Process (executeFile) import System.Exit (exitFailure) import System.FilePath import Paths_xmonad (version) import Data.Version (showVersion) import Graphics.X11.Xinerama (compiledWithXinerama) import Graphics.X11.Xrandr (xrrQueryExtension, xrrUpdateConfiguration) ------------------------------------------------------------------------ -- | -- | The entry point into xmonad. Attempts to compile any custom main -- for xmonad, and if it doesn't find one, just launches the default. xmonad :: (LayoutClass l Window, Read (l Window)) => XConfig l -> IO () xmonad conf = do installSignalHandlers -- important to ignore SIGCHLD to avoid zombies dirs <- getDirectories let launch' args = do catchIO (buildLaunch dirs) conf'@XConfig { layoutHook = Layout l } <- handleExtraArgs conf args conf{ layoutHook = Layout (layoutHook conf) } withArgs [] $ launch (conf' { layoutHook = l }) dirs args <- getArgs case args of ["--help"] -> usage ["--recompile"] -> recompile dirs True >>= flip unless exitFailure ["--restart"] -> sendRestart ["--version"] -> putStrLn $ unwords shortVersion ["--verbose-version"] -> putStrLn . unwords $ shortVersion ++ longVersion "--replace" : args' -> sendReplace >> launch' args' _ -> launch' args 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 $ [ "Usage: " <> self <> " [OPTION]" , "Options:" , " --help Print this message" , " --version Print the version number" , " --recompile Recompile your xmonad.hs" , " --replace Replace the running window manager with xmonad" , " --restart Request a running xmonad process to restart" ] -- | Build the xmonad configuration file 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 the configuration file and executable are 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 :: Directories -> IO () buildLaunch dirs = do whoami <- getProgName let bin = binFileName dirs let compiledConfig = takeFileName bin unless (whoami == compiledConfig) $ do trace $ concat [ "XMonad is recompiling and replacing itself with another XMonad process because the current process is called " , show whoami , " but the compiled configuration should be called " , show compiledConfig ] recompile dirs False args <- getArgs executeFile bin False args Nothing 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 [] sendEvent dpy rw False structureNotifyMask e sync dpy False -- | a wrapper for 'replace' sendReplace :: IO () sendReplace = do dpy <- openDisplay "" let dflt = defaultScreen dpy rootw <- rootWindow dpy dflt replace dpy dflt rootw -- | Entry point into xmonad for custom builds. -- -- This function isn't meant to be called by the typical xmonad user -- because it: -- -- * Does not process any command line arguments. -- -- * Therefore doesn't know how to restart a running xmonad. -- -- * Does not compile your configuration file since it assumes it's -- actually running from within your compiled configuration. -- -- Unless you know what you are doing, you should probably be using -- the 'xmonad' function instead. -- -- However, if you are using a custom build environment (such as -- stack, cabal, make, etc.) you will likely want to call this -- function instead of 'xmonad'. You probably also want to have a key -- binding to the 'XMonad.Operations.restart` function that restarts -- your custom binary with the resume flag set to @True@. launch :: (LayoutClass l Window, Read (l Window)) => XConfig l -> Directories -> IO () launch initxmc drs = do -- setup locale information from environment setLocale LC_ALL (Just "") -- 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 -- If another WM is running, a BadAccess error will be returned. The -- default error handler will write the exception to stderr and exit with -- an error. selectInput dpy rootw $ rootMask initxmc sync dpy False -- sync to ensure all outstanding errors are delivered -- turn off the default handler in favor of one that ignores all errors -- (ugly, I know) xSetErrorHandler -- in C, I'm too lazy to write the binding: dons xinesc <- getCleanedScreenInfo dpy nbc <- do v <- initColor dpy $ normalBorderColor xmc Just nbc_ <- initColor dpy $ normalBorderColor Default.def return (fromMaybe nbc_ v) fbc <- do v <- initColor dpy $ focusedBorderColor xmc Just fbc_ <- initColor dpy $ focusedBorderColor Default.def return (fromMaybe fbc_ v) hSetBuffering stdout NoBuffering let layout = layoutHook xmc initialWinset = let padToLen n xs = take (max n (length xs)) $ xs ++ repeat "" in new layout (padToLen (length xinesc) (workspaces xmc)) $ map SD xinesc 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 , directories = drs } st = XState { windowset = initialWinset , numberlockMask = 0 , mapped = S.empty , waitingUnmap = M.empty , dragging = Nothing , extensibleState = M.empty } allocaXEvent $ \e -> runX cf st $ do -- check for serialized state in a file. serializedSt <- do path <- asks $ stateFileName . directories exists <- io (doesFileExist path) if exists then readStateFile initxmc else return Nothing -- restore extensibleState if we read it from a file. let extst = maybe M.empty extensibleState serializedSt modify (\s -> s {extensibleState = extst}) cacheNumlockMask 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. let winset = maybe initialWinset windowset serializedSt 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 rrData <- io $ xrrQueryExtension dpy -- main loop, for all you HOF/recursion fans out there. -- forever $ prehandle =<< io (nextEvent dpy e >> rrUpdate e >> getEvent e) -- sadly, 9.2.{1,2,3} join points mishandle the above and trash the heap (see #389) mainLoop dpy e rrData 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] rrUpdate e r = when (isJust r) (void (xrrUpdateConfiguration e)) mainLoop d e r = io (nextEvent d e >> rrUpdate e r >> getEvent e) >>= prehandle >> mainLoop d e r -- | 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 withWindowAttributes dpy w $ \wa -> do -- 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) $ manage w -- window destroyed, unmanage it -- window gone, unmanage it -- broadcast to layouts handle e@(DestroyWindowEvent {ev_window = w}) = do whenX (isClient w) $ do unmanage w modify (\s -> s { mapped = S.delete w (mapped s) , waitingUnmap = M.delete w (waitingUnmap s)}) -- the window is already unmanged, but we broadcast the event to all layouts -- to trigger garbage-collection in case they hold window-specific resources broadcastMessage e -- 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 cacheNumlockMask 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) $ do dpy <- asks display root <- asks theRoot (_, _, w', _, _, _, _, _) <- io $ queryPointer dpy root -- when Xlib cannot find a child that contains the pointer, -- it returns None(0) when (w' == 0 || w == w') (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 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 withWindowAttributes dpy w $ \wa -> 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 (\w -> ok w `E.catch` skip) 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) skip :: E.SomeException -> IO Bool skip _ = return False -- | Grab the keys back grabKeys :: X () grabKeys = do XConf { display = dpy, theRoot = rootw } <- ask io $ ungrabKey dpy anyKey anyModifier rootw let grab :: (KeyMask, KeyCode) -> X () grab (km, kc) = io $ grabKey dpy kc km rootw True grabModeAsync grabModeAsync traverse_ grab =<< mkGrabs =<< asks (M.keys . keyActions) -- | Grab the buttons 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.17.2/src/XMonad/ManageHook.hs0000644000000000000000000001004307346545000015600 0ustar0000000000000000----------------------------------------------------------------------------- -- | -- Module : XMonad.ManageHook -- Copyright : (c) Spencer Janssen 2007 -- License : BSD3-style (see LICENSE) -- -- Maintainer : spencerjanssen@gmail.com -- Stability : unstable -- -- An EDSL for ManageHooks -- ----------------------------------------------------------------------------- -- XXX examples required module XMonad.ManageHook where import XMonad.Core import Graphics.X11.Xlib.Extras import Graphics.X11.Xlib (Display, Window, internAtom, wM_NAME) import Control.Exception (bracket, SomeException(..)) import qualified Control.Exception as E import Control.Monad.Reader import Data.Maybe import Data.Monoid import qualified XMonad.StackSet as W import XMonad.Operations (floatLocation, reveal, isFixedSizeOrTransient) -- | 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 x <&&> y = ifM x y (pure False) -- | '||' lifted to a 'Monad'. (<||>) :: Monad m => m Bool -> m Bool -> m Bool x <||> y = ifM x (pure True) y -- | If-then-else lifted to a 'Monad'. ifM :: Monad m => m Bool -> m a -> m a -> m a ifM mb t f = mb >>= \b -> if b then t else f -- | Return the window title. title :: Query String title = ask >>= \w -> liftX $ do d <- asks display let getProp = (internAtom d "_NET_WM_NAME" False >>= getTextProperty d w) `E.catch` \(SomeException _) -> getTextProperty d w wM_NAME extract prop = do l <- wcTextPropertyToTextList d prop return $ if null l then "" else head l io $ bracket getProp (xFree . tp_value) extract `E.catch` \(SomeException _) -> return "" -- | Return the application name; i.e., the /first/ string returned by -- @WM_CLASS@. 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; i.e., the /second/ string returned by -- @WM_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 -> 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 -- | Return whether the window will be a floating window or not willFloat :: Query Bool willFloat = ask >>= \w -> liftX $ withDisplay $ \d -> isFixedSizeOrTransient d w -- | 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.17.2/src/XMonad/Operations.hs0000644000000000000000000010145407346545000015721 0ustar0000000000000000{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE PatternGuards #-} {-# LANGUAGE ScopedTypeVariables #-} -- -------------------------------------------------------------------------- -- | -- Module : XMonad.Operations -- Copyright : (c) Spencer Janssen 2007 -- License : BSD3-style (see LICENSE) -- -- Maintainer : dons@cse.unsw.edu.au -- Stability : unstable -- Portability : not portable, mtl, posix -- -- Operations. A module for functions that don't cleanly fit anywhere else. -- ----------------------------------------------------------------------------- module XMonad.Operations ( -- * Manage One Window manage, unmanage, killWindow, kill, isClient, setInitialProperties, setWMState, setWindowBorderWithFallback, hide, reveal, tileWindow, setTopFocus, focus, isFixedSizeOrTransient, -- * Manage Windows windows, refresh, rescreen, modifyWindowSet, windowBracket, windowBracket_, clearEvents, getCleanedScreenInfo, withFocused, withUnfocused, -- * Keyboard and Mouse cleanMask, extraModifiers, mouseDrag, mouseMoveWindow, mouseResizeWindow, setButtonGrab, setFocusX, cacheNumlockMask, mkGrabs, -- * Messages sendMessage, broadcastMessage, sendMessageWithNoRefresh, -- * Save and Restore State StateFile (..), writeStateToFile, readStateFile, restart, -- * Floating Layer float, floatLocation, -- * Window Size Hints D, mkAdjust, applySizeHints, applySizeHints', applySizeHintsContents, applyAspectHint, applyResizeIncHint, applyMaxSizeHint, -- * Rectangles containedIn, nubScreens, pointWithin, scaleRationalRect, -- * Other Utilities initColor, pointScreen, screenWorkspace, setLayout, updateLayout, ) where import XMonad.Core import XMonad.Layout (Full(..)) import qualified XMonad.StackSet as W import Data.Maybe import Data.Monoid (Endo(..),Any(..)) import Data.List (nub, (\\), find) import Data.Bits ((.|.), (.&.), complement, setBit, testBit) import Data.Function (on) import Data.Ratio import qualified Data.Map as M import qualified Data.Set as S import Control.Arrow (second) import Control.Monad.Fix (fix) import Control.Monad.Reader import Control.Monad.State import Control.Monad (forM, forM_, guard, join, unless, void, when) import qualified Control.Exception as C import System.IO import System.Directory import System.Posix.Process (executeFile) import Graphics.X11.Xlib import Graphics.X11.Xinerama (getScreenInfo) import Graphics.X11.Xlib.Extras -- --------------------------------------------------------------------- -- Window manager operations -- | Detect whether a window has fixed size or is transient. This check -- can be used to determine whether the window should be floating or not -- isFixedSizeOrTransient :: Display -> Window -> X Bool isFixedSizeOrTransient d w = do sh <- io $ getWMNormalHints d w let isFixedSize = isJust (sh_min_size sh) && sh_min_size sh == sh_max_size sh isTransient <- isJust <$> io (getTransientForHint d w) return (isFixedSize || isTransient) -- | -- 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 shouldFloat <- isFixedSizeOrTransient 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 | shouldFloat = 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) -- | 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 currentTime sendEvent d w False noEventMask ev else void (killClient d w) -- | Kill the currently focused client. kill :: X () kill = withFocused killWindow -- --------------------------------------------------------------------- -- Managing 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 -> do nbs <- asks (normalBorderColor . config) setWindowBorderWithFallback d otherw nbs 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 (`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 -> do fbs <- asks (focusedBorderColor . config) setWindowBorderWithFallback d w fbs 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_ (`setWMState` withdrawnState) (W.allWindows old \\ W.allWindows ws) isMouseFocused <- asks mouseFocused unless isMouseFocused $ clearEvents enterWindowMask asks (logHook . config) >>= userCodeDef () -- | Modify the @WindowSet@ in state with no special handling. modifyWindowSet :: (WindowSet -> WindowSet) -> X () modifyWindowSet f = modify $ \xst -> xst { windowset = f (windowset xst) } -- | Perform an @X@ action and check its return value against a predicate p. -- If p holds, unwind changes to the @WindowSet@ and replay them using @windows@. windowBracket :: (a -> Bool) -> X a -> X a windowBracket p action = withWindowSet $ \old -> do a <- action when (p a) . withWindowSet $ \new -> do modifyWindowSet $ const old windows $ const new return a -- | Perform an @X@ action. If it returns @Any True@, unwind the -- changes to the @WindowSet@ and replay them using @windows@. This is -- a version of @windowBracket@ that discards the return value and -- handles an @X@ action that reports its need for refresh via @Any@. windowBracket_ :: X Any -> X () windowBracket_ = void . windowBracket getAny -- | 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) -- | Set a window's 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] -- | Set the border color using the window's color map, if possible; -- otherwise fall back to the color in @Pixel@. setWindowBorderWithFallback :: Display -> Window -> String -> Pixel -> X () setWindowBorderWithFallback dpy w color basic = io $ C.handle fallback $ do wa <- getWindowAttributes dpy w pixel <- setPixelSolid . color_pixel . fst <$> allocNamedColor dpy (wa_colormap wa) color setWindowBorder dpy w pixel where fallback :: C.SomeException -> IO () fallback _ = setWindowBorder dpy w basic -- | Hide a window by unmapping it and setting Iconified. hide :: Window -> X () hide w = whenX (gets (S.member w . mapped)) $ withDisplay $ \d -> do cMask <- asks $ clientMask . config io $ do selectInput d w (cMask .&. complement structureNotifyMask) unmapWindow d w selectInput d w cMask setWMState w iconicState -- this part is key: we increment the waitingUnmap counter to distinguish -- between client and xmonad initiated unmaps. modify (\s -> s { waitingUnmap = M.insertWith (+) w 1 (waitingUnmap s) , mapped = S.delete w (mapped s) }) -- | Show a window by mapping it and setting Normal. -- This is harmless if the window was already visible. reveal :: Window -> X () reveal w = withDisplay $ \d -> do setWMState w normalState io $ mapWindow d w whenX (isClient w) $ modify (\s -> s { mapped = S.insert w (mapped s) }) -- | Set some properties when we initially gain control of a window. setInitialProperties :: Window -> X () setInitialProperties w = asks normalBorder >>= \nb -> withDisplay $ \d -> do setWMState w iconicState asks (clientMask . config) >>= io . selectInput d w bw <- asks (borderWidth . config) io $ setWindowBorderWidth d w bw -- we must initially set the color of new windows, to maintain invariants -- required by the border setting in 'windows' io $ setWindowBorder d w nb -- | 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 -- | 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 -- | Move and resize @w@ such that it fits inside the given rectangle, -- including its border. tileWindow :: Window -> Rectangle -> X () tileWindow w r = withDisplay $ \d -> withWindowAttributes d w $ \wa -> do -- give all windows at least 1x1 pixels let bw = fromIntegral $ wa_border_width wa 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 -- | Clean the list of screens according to the rules documented for -- nubScreens. getCleanedScreenInfo :: MonadIO m => Display -> m [Rectangle] getCleanedScreenInfo = io . fmap nubScreens . getScreenInfo -- | The screen configuration may have changed (due to -- xrandr), -- update the state and refresh the screen, and reset the gap. rescreen :: X () rescreen = withDisplay getCleanedScreenInfo >>= \case [] -> trace "getCleanedScreenInfo returned []" xinesc:xinescs -> windows $ \ws@W.StackSet{ W.current = v, W.visible = vs, W.hidden = hs } -> let (xs, ys) = splitAt (length xinescs) (map W.workspace vs ++ hs) a = W.Screen (W.workspace v) 0 (SD xinesc) as = zipWith3 W.Screen xs [1..] $ map SD xinescs in ws { W.current = a , W.visible = as , W.hidden = ys } -- --------------------------------------------------------------------- -- | 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 ] cacheNumlockMask :: X () cacheNumlockMask = 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 }) -- | Given a list of keybindings, turn the given 'KeySym's into actual -- 'KeyCode's and prepare them for grabbing. mkGrabs :: [(KeyMask, KeySym)] -> X [(KeyMask, KeyCode)] mkGrabs ks = withDisplay $ \dpy -> do let (minCode, maxCode) = displayKeycodes dpy allCodes = [fromIntegral minCode .. fromIntegral maxCode] -- build a map from keysyms to lists of keysyms (doing what -- XGetKeyboardMapping would do if the X11 package bound it) syms <- forM allCodes $ \code -> io (keycodeToKeysym dpy code 0) let -- keycodeToKeysym returns noSymbol for all unbound keycodes, -- and we don't want to grab those whenever someone accidentally -- uses def :: KeySym keysymMap = M.delete noSymbol $ M.fromListWith (++) (zip syms [[code] | code <- allCodes]) keysymToKeycodes sym = M.findWithDefault [] sym keysymMap extraMods <- extraModifiers pure [ (mask .|. extraMod, keycode) | (mask, sym) <- ks , keycode <- keysymToKeycodes sym , extraMod <- extraMods ] ------------------------------------------------------------------------ -- Message handling -- | Throw a message to the current 'LayoutClass' possibly modifying how we -- layout the windows, in which case changes are handled through a refresh. sendMessage :: Message a => a -> X () sendMessage a = windowBracket_ $ do w <- gets $ W.workspace . W.current . windowset ml' <- handleMessage (W.layout w) (SomeMessage a) `catchX` return Nothing whenJust ml' $ \l' -> modifyWindowSet $ \ws -> ws { W.current = (W.current ws) { W.workspace = (W.workspace $ W.current ws) { W.layout = l' }}} return (Any $ isJust ml') -- | Send a message to all layouts, without refreshing. broadcastMessage :: Message a => a -> X () broadcastMessage a = withWindowSet $ \ws -> do -- this is O(n²), but we can't really fix this as there's code in -- xmonad-contrib that touches the windowset during handleMessage -- (returning Nothing for changes to not get overwritten), so we -- unfortunately need to do this one by one and persist layout states -- of each workspace separately) 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 -> WindowSpace -> 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 -- | Apply an 'X' operation to all unfocused windows on the current workspace, if there are any. withUnfocused :: (Window -> X ()) -> X () withUnfocused f = withWindowSet $ \ws -> whenJust (W.peek ws) $ \w -> let unfocusedWindows = filter (/= w) $ W.index ws in mapM_ f unfocusedWindows -- | Is the window is under management by xmonad? 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) -- | Set the 'Pixel' alpha value to 255. setPixelSolid :: Pixel -> Pixel setPixelSolid p = p .|. 0xff000000 -- | Get the 'Pixel' value for a named color. initColor :: Display -> String -> IO (Maybe Pixel) initColor dpy c = C.handle (\(C.SomeException _) -> return Nothing) $ Just . setPixelSolid . color_pixel . fst <$> allocNamedColor dpy colormap c where colormap = defaultColormap dpy (defaultScreen dpy) ------------------------------------------------------------------------ -- | A type to help serialize xmonad's state to a file. data StateFile = StateFile { sfWins :: W.StackSet WorkspaceId String Window ScreenId ScreenDetail , sfExt :: [(String, String)] } deriving (Show, Read) -- | Write the current window state (and extensible state) to a file -- so that xmonad can resume with that state intact. writeStateToFile :: X () writeStateToFile = do let maybeShow (t, Right (PersistentExtension ext)) = Just (t, show ext) maybeShow (t, Left str) = Just (t, str) maybeShow _ = Nothing wsData = W.mapLayout show . windowset extState = mapMaybe maybeShow . M.toList . extensibleState path <- asks $ stateFileName . directories stateData <- gets (\s -> StateFile (wsData s) (extState s)) catchIO (writeFile path $ show stateData) -- | Read the state of a previous xmonad instance from a file and -- return that state. The state file is removed after reading it. readStateFile :: (LayoutClass l Window, Read (l Window)) => XConfig l -> X (Maybe XState) readStateFile xmc = do path <- asks $ stateFileName . directories -- I'm trying really hard here to make sure we read the entire -- contents of the file before it is removed from the file system. sf' <- userCode . io $ do raw <- withFile path ReadMode readStrict return $! maybeRead reads raw io (removeFile path) return $ do sf <- join sf' let winset = W.ensureTags layout (workspaces xmc) $ W.mapLayout (fromMaybe layout . maybeRead lreads) (sfWins sf) extState = M.fromList . map (second Left) $ sfExt sf return XState { windowset = winset , numberlockMask = 0 , mapped = S.empty , waitingUnmap = M.empty , dragging = Nothing , extensibleState = extState } where layout = Layout (layoutHook xmc) lreads = readsLayout layout maybeRead reads' s = case reads' s of [(x, "")] -> Just x _ -> Nothing readStrict :: Handle -> IO String readStrict h = hGetContents h >>= \s -> length s `seq` return s -- | @restart name resume@ attempts 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 when resume writeStateToFile catchIO (executeFile prog True [] 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 = catchX go $ do -- Fallback solution if `go' fails. Which it might, since it -- calls `getWindowAttributes'. sc <- gets $ W.current . windowset return (W.screen sc, W.RationalRect 0 0 1 1) where go = withDisplay $ \d -> do ws <- gets windowset wa <- io $ getWindowAttributes d w let bw = (fromIntegral . wa_border_width) wa point_sc <- pointScreen (fi $ wa_x wa) (fi $ wa_y wa) managed <- isClient w -- ignore pointScreen for new windows unless it's the current -- screen, otherwise the float's relative size is computed against -- a different screen and the float ends up with the wrong size let sr_eq = (==) `on` fmap (screenRect . W.screenDetail) sc = fromMaybe (W.current ws) $ if managed || point_sc `sr_eq` Just (W.current ws) then point_sc else Nothing sr = screenRect . W.screenDetail $ sc x = (fi (wa_x wa) - fi (rect_x sr)) % fi (rect_width sr) y = (fi (wa_y wa) - fi (rect_y sr)) % fi (rect_height sr) width = fi (wa_width wa + bw*2) % fi (rect_width sr) height = fi (wa_height wa + bw*2) % fi (rect_height sr) -- adjust x/y of unmanaged windows if we ignored or didn't get pointScreen, -- it might be out of bounds otherwise rr = if managed || point_sc `sr_eq` Just sc then W.RationalRect x y width height else W.RationalRect (0.5 - width/2) (0.5 - height/2) width height return (W.screen sc, rr) fi :: (Integral a, Num b) => a -> b fi = fromIntegral -- | 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 = mouseDragCursor Nothing -- | Like 'mouseDrag', but with the ability to specify a custom cursor -- shape. mouseDragCursor :: Maybe Glyph -> (Position -> Position -> X ()) -> X () -> X () mouseDragCursor cursorGlyph 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 $ do cursor <- maybe (pure none) (createFontCursor d) cursorGlyph grabPointer d root False (buttonReleaseMask .|. pointerMotionMask) grabModeAsync grabModeAsync none cursor 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 -- | Drag the window under the cursor with the mouse while it is dragged. mouseMoveWindow :: Window -> X () mouseMoveWindow w = whenX (isClient w) $ withDisplay $ \d -> do wa <- io $ getWindowAttributes d w (_, _, _, ox', oy', _, _, _) <- io $ queryPointer d w let ox = fromIntegral ox' oy = fromIntegral oy' mouseDragCursor (Just xC_fleur) (\ex ey -> do io $ moveWindow d w (fromIntegral (fromIntegral (wa_x wa) + (ex - ox))) (fromIntegral (fromIntegral (wa_y wa) + (ey - oy))) float w ) (float w) -- | Resize the window under the cursor with the mouse while it is dragged. mouseResizeWindow :: Window -> X () mouseResizeWindow w = whenX (isClient w) $ withDisplay $ \d -> do 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)) mouseDragCursor (Just xC_bottom_right_corner) (\ex ey -> do io $ resizeWindow d w `uncurry` applySizeHintsContents sh (ex - fromIntegral (wa_x wa), ey - fromIntegral (wa_y wa)) float w) (float w) -- --------------------------------------------------------------------- -- Support for window size hints -- | An alias for a (width, height) pair 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 wa <- C.try $ getWindowAttributes d w case wa of Left (_ :: C.SomeException) -> return id Right wa' -> let bw = fromIntegral $ wa_border_width wa' in 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) -- | Use X11 size hints to scale a pair of dimensions. 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.17.2/src/XMonad/StackSet.hs0000644000000000000000000005707007346545000015323 0ustar0000000000000000{-# LANGUAGE PatternGuards #-} {-# LANGUAGE DeriveFunctor #-} ----------------------------------------------------------------------------- -- | -- 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 Control.Applicative.Backwards (Backwards (Backwards, forwards)) import Data.Foldable (foldr, toList) import Data.Maybe (listToMaybe,isJust,fromMaybe) import qualified Data.List as L (deleteBy,find,splitAt,filter,nub) import Data.List ( (\\) ) import qualified Data.List.NonEmpty as NE import Data.List.NonEmpty (NonEmpty((:|))) 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 a mutation. The -- resulting data structure will share as much of its components with -- the old structure as possible. -- -- -- -- 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: -- -- -- -- and -- -- -- -- and Conor McBride's zipper differentiation paper. -- Another good reference is: -- $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, Functor) instance Foldable Stack where toList = integrate foldr f z = foldr f z . toList instance Traversable Stack where traverse f s = flip Stack -- 'Backwards' applies the Applicative in reverse order. <$> forwards (traverse (Backwards . f) (up s)) <*> f (focus s) <*> traverse f (down s) -- | 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 (wid:wids) (m:ms) | length ms <= length wids = StackSet cur visi (map ws unseen) M.empty where ws i = Workspace i l Nothing (seen, unseen) = L.splitAt (length ms) wids cur:visi = Screen (ws wid) 0 m : [ Screen (ws i) s sd | (i, s, sd) <- zip3 seen [1..] ms ] -- 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/. Move the window focus up 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. focusUp :: StackSet i l a s sd -> StackSet i l a s sd focusUp = modify' focusUp' -- | /O(1), O(w) on the wrapping case/. Like 'focusUp', but move the -- window focus down the stack. focusDown :: StackSet i l a s sd -> StackSet i l a s sd focusDown = modify' focusDown' -- | /O(1), O(w) on the wrapping case/. Swap the upwards (left) -- neighbour in the stack ordering, wrapping if we reach the end. Much -- like for 'focusUp' and 'focusDown', the wrapping model should 'cycle' -- on the current stack. swapUp :: StackSet i l a s sd -> StackSet i l a s sd swapUp = modify' swapUp' -- | /O(1), O(w) on the wrapping case/. Like 'swapUp', but for swapping -- the downwards (right) neighbour. swapDown :: StackSet i l a s sd -> StackSet i l a s sd swapDown = modify' (reverseStack . swapUp' . reverseStack) -- | A variant of 'focusUp' with the same asymptotics that works on a -- 'Stack' rather than an entire 'StackSet'. focusUp' :: 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) = NE.reverse (t :| rs) -- | A variant of 'focusDown' with the same asymptotics that works on a -- 'Stack' rather than an entire 'StackSet'. focusDown' :: Stack a -> Stack a focusDown' = reverseStack . focusUp' . reverseStack -- | A variant of 'spawUp' with the same asymptotics that works on a -- 'Stack' rather than an entire 'StackSet'. 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) => 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) => 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 (l:ls) rs -> Stack t [] (xs ++ x : rs) where (x :| xs) = NE.reverse (l :| 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 (l:ls) rs -> Stack x [] (xs ++ t : rs) where (x :| xs) = NE.reverse (l :| 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 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 xmonad-0.17.2/tests/0000755000000000000000000000000007346545000012422 5ustar0000000000000000xmonad-0.17.2/tests/Instances.hs0000644000000000000000000001077607346545000014720 0ustar0000000000000000{-# LANGUAGE ScopedTypeVariables #-} module Instances where import Test.QuickCheck import Utils import XMonad.StackSet import Control.Monad import Data.List (nub, genericLength) import Debug.Trace import Graphics.X11 (Rectangle(Rectangle)) import Control.Applicative -- -- 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 -- TODO: Fix this to be a reasonable higher number, Possibly use PositiveSized numWs <- choose (1, 20) -- number of workspaces, there must be at least 1. numScreens <- choose (1, numWs) -- number of physical screens, there must be at least 1 lay <- arbitrary -- pick any layout wsIdxInFocus <- choose (1, numWs) -- pick index of WS to be in focus -- The same screen id's will be present in the list, with high possibility. screens <- replicateM numScreens arbitrary -- Generate a list of "windows" for each workspace. wsWindows <- vector numWs :: Gen [[a]] -- Pick a random window "number" in each workspace, to give focus. focus <- sequence [ if null windows then return Nothing else Just <$> choose (0, length windows - 1) | windows <- wsWindows ] let tags = [1 .. fromIntegral numWs] focusWsWindows = zip focus wsWindows wss = zip tags focusWsWindows -- tmp representation of a workspace (tag, windows) initSs = new lay tags screens return $ view (fromIntegral wsIdxInFocus) $ foldr (\(tag, (focus, windows)) ss -> -- Fold through all generated (tags,windows). -- set workspace active by tag and fold through all -- windows while inserting them. Apply the given number -- of `focusUp` on the resulting StackSet. applyN focus focusUp $ foldr insertUp (view tag ss) windows ) initSs wss -- -- Just generate StackSets with Char elements. -- type Tag = Int type Window = Char type T = StackSet Tag Int Window Int Int 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 newtype NonEmptyWindowsStackSet = NonEmptyWindowsStackSet T deriving Show instance Arbitrary NonEmptyWindowsStackSet where arbitrary = NonEmptyWindowsStackSet <$> (arbitrary `suchThat` (not . null . allWindows)) instance Arbitrary Rectangle where arbitrary = Rectangle <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary newtype SizedPositive = SizedPositive Int deriving (Eq, Ord, Show, Read) instance Arbitrary SizedPositive where arbitrary = sized $ \s -> do x <- choose (1, max 1 s) return $ SizedPositive x newtype NonEmptyNubList a = NonEmptyNubList [a] deriving ( Eq, Ord, Show, Read ) instance (Eq a, Arbitrary a) => Arbitrary (NonEmptyNubList a) where arbitrary = NonEmptyNubList <$> ((nub <$> arbitrary) `suchThat` (not . null)) -- | Pull out an arbitrary tag from the StackSet. This removes the need for the -- precondition "n `tagMember x` in many properties and thus reduces the number -- of discarded tests. -- -- n <- arbitraryTag x -- -- We can do the reverse with a simple `suchThat`: -- -- n <- arbitrary `suchThat` \n' -> not $ n' `tagMember` x arbitraryTag :: T -> Gen Tag arbitraryTag x = do let ts = tags x -- There must be at least 1 workspace, thus at least 1 tag. idx <- choose (0, length ts - 1) return $ ts!!idx -- | Pull out an arbitrary window from a StackSet that is guaranteed to have a -- non empty set of windows. This eliminates the precondition "i `member` x" in -- a few properties. -- -- -- foo (nex :: NonEmptyWindowsStackSet) = do -- let NonEmptyWindowsStackSet x = nex -- w <- arbitraryWindow nex -- return $ ....... -- -- We can do the reverse with a simple `suchThat`: -- -- n <- arbitrary `suchThat` \n' -> not $ n `member` x arbitraryWindow :: NonEmptyWindowsStackSet -> Gen Window arbitraryWindow (NonEmptyWindowsStackSet x) = do let ws = allWindows x -- We know that there are at least 1 window in a NonEmptyWindowsStackSet. idx <- choose (0, length ws - 1) return $ ws!!idx xmonad-0.17.2/tests/Properties.hs0000644000000000000000000002007207346545000015113 0ustar0000000000000000import Test.QuickCheck -- Our QC instances and properties: import Instances import Properties.Delete import Properties.Failure import Properties.Floating import Properties.Focus import Properties.GreedyView import Properties.Insert import Properties.Screen import Properties.Shift import Properties.Stack import Properties.StackSet import Properties.Swap import Properties.View import Properties.Workspace import Properties.Layout.Full import Properties.Layout.Tall import System.Environment import Text.Printf import Control.Monad import Control.Applicative main :: IO () main = do arg <- fmap (drop 1) getArgs let n = if null arg then 100 else read $ head arg args = stdArgs { maxSuccess = n, maxSize = 100 } qc t = do c <- quickCheckWithResult args t case c of Success {} -> return True _ -> return False perform (s, t) = printf "%-35s: " s >> qc t n <- length . filter not <$> mapM perform tests unless (n == 0) (error (show n ++ " test(s) failed")) tests = [("StackSet invariants", property prop_invariant) ,("empty: invariant", property prop_empty_I) ,("empty is empty", property prop_empty) ,("empty / current", property prop_empty_current) ,("empty / member", property prop_member_empty) ,("view : invariant", property prop_view_I) ,("view sets current", property prop_view_current) ,("view idempotent", property prop_view_idem) ,("view reversible", property prop_view_reversible) ,("view is local", property prop_view_local) ,("greedyView : invariant", property prop_greedyView_I) ,("greedyView sets current", property prop_greedyView_current) ,("greedyView is safe", property prop_greedyView_current_id) ,("greedyView idempotent", property prop_greedyView_idem) ,("greedyView reversible", property prop_greedyView_reversible) ,("greedyView is local", property prop_greedyView_local) ,("peek/member", property prop_member_peek) ,("index/length", property prop_index_length) ,("focus left : invariant", property prop_focusUp_I) ,("focus master : invariant", property prop_focusMaster_I) ,("focus right: invariant", property prop_focusDown_I) ,("focusWindow: invariant", property prop_focus_I) ,("focus left/master", property prop_focus_left_master) ,("focus right/master", property prop_focus_right_master) ,("focus master/master", property prop_focus_master_master) ,("focusWindow master", property prop_focusWindow_master) ,("focus left/right", property prop_focus_left) ,("focus right/left", property prop_focus_right) ,("focus all left", property prop_focus_all_l) ,("focus all right", property prop_focus_all_r) ,("focus down is local", property prop_focus_down_local) ,("focus up is local", property prop_focus_up_local) ,("focus master is local", property prop_focus_master_local) ,("focus master idemp", property prop_focusMaster_idem) ,("focusWindow is local", property prop_focusWindow_local) ,("focusWindow works" , property prop_focusWindow_works) ,("focusWindow identity", property prop_focusWindow_identity) ,("findTag", property prop_findIndex) ,("allWindows/member", property prop_allWindowsMember) ,("currentTag", property prop_currentTag) ,("insert: invariant", property prop_insertUp_I) ,("insert/new", property prop_insert_empty) ,("insert is idempotent", property prop_insert_idem) ,("insert is reversible", property prop_insert_delete) ,("insert is local", property prop_insert_local) ,("insert duplicates", property prop_insert_duplicate) ,("insert/peek", property prop_insert_peek) ,("insert/size", property prop_size_insert) ,("delete: invariant", property prop_delete_I) ,("delete/empty", property prop_empty) ,("delete/member", property prop_delete) ,("delete is reversible", property prop_delete_insert) ,("delete is local", property prop_delete_local) ,("delete/focus", property prop_delete_focus) ,("delete last/focus up", property prop_delete_focus_end) ,("delete ~last/focus down", property prop_delete_focus_not_end) ,("filter preserves order", property prop_filter_order) ,("swapLeft", property prop_swap_left) ,("swapRight", property prop_swap_right) ,("swapMaster: invariant", property prop_swap_master_I) ,("swapUp: invariant" , property prop_swap_left_I) ,("swapDown: invariant", property prop_swap_right_I) ,("swapMaster id on focus", property prop_swap_master_focus) ,("swapUp id on focus", property prop_swap_left_focus) ,("swapDown id on focus", property prop_swap_right_focus) ,("swapMaster is idempotent", property prop_swap_master_idempotent) ,("swap all left", property prop_swap_all_l) ,("swap all right", property prop_swap_all_r) ,("swapMaster is local", property prop_swap_master_local) ,("swapUp is local", property prop_swap_left_local) ,("swapDown is local", property prop_swap_right_local) ,("shiftMaster id on focus", property prop_shift_master_focus) ,("shiftMaster is local", property prop_shift_master_local) ,("shiftMaster is idempotent", property prop_shift_master_idempotent) ,("shiftMaster preserves ordering", property prop_shift_master_ordering) ,("shift: invariant" , property prop_shift_I) ,("shift is reversible" , property prop_shift_reversible) ,("shiftWin: invariant" , property prop_shift_win_I) ,("shiftWin is shift on focus", property prop_shift_win_focus) ,("shiftWin fix current" , property prop_shift_win_fix_current) ,("shiftWin identity", property prop_shift_win_indentity) ,("floating is reversible" , property prop_float_reversible) ,("floating sets geometry" , property prop_float_geometry) ,("floats can be deleted", property prop_float_delete) ,("screens includes current", property prop_screens) ,("differentiate works", property prop_differentiate) ,("lookupTagOnScreen", property prop_lookup_current) ,("lookupTagOnVisbleScreen", property prop_lookup_visible) ,("screens works", property prop_screens_works) ,("renaming works", property prop_rename1) ,("ensure works", property prop_ensure) ,("ensure hidden semantics", property prop_ensure_append) ,("mapWorkspace id", property prop_mapWorkspaceId) ,("mapWorkspace inverse", property prop_mapWorkspaceInverse) ,("mapLayout id", property prop_mapLayoutId) ,("mapLayout inverse", property prop_mapLayoutInverse) ,("abort fails", property prop_abort) ,("new fails with abort", property prop_new_abort) ,("point within", property prop_point_within) -- tall layout ,("tile 1 window fullsize", property prop_tile_fullscreen) ,("tiles never overlap", property prop_tile_non_overlap) ,("split horizontal", property prop_split_horizontal) ,("split vertical", property prop_split_vertical) ,("pure layout tall", property prop_purelayout_tall) ,("send shrink tall", property prop_shrink_tall) ,("send expand tall", property prop_expand_tall) ,("send incmaster tall", property prop_incmaster_tall) -- full layout ,("pure layout full", property prop_purelayout_full) ,("send message full", property prop_sendmsg_full) ,("describe full", property prop_desc_full) ,("describe mirror", property prop_desc_mirror) -- resize hints ,("window resize hints: inc", property prop_resize_inc) ,("window resize hints: inc all", property prop_resize_inc_extra) ,("window resize hints: max", property prop_resize_max) ,("window resize hints: max all ", property prop_resize_max_extra) ,("window aspect hints: fits", property prop_aspect_fits) ,("window aspect hints: shrinks ", property prop_aspect_hint_shrink) ,("pointWithin", property prop_point_within) ,("pointWithin mirror", property prop_point_within_mirror) ] <> prop_laws_Stack xmonad-0.17.2/tests/Properties/0000755000000000000000000000000007346545000014556 5ustar0000000000000000xmonad-0.17.2/tests/Properties/Delete.hs0000644000000000000000000000472207346545000016321 0ustar0000000000000000{-# LANGUAGE ScopedTypeVariables #-} module Properties.Delete where import Test.QuickCheck import Instances import Utils import XMonad.StackSet hiding (filter) -- --------------------------------------------------------------------- -- '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 identity, 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 = do -- There should be at least two windows. One in focus, and some to try and -- delete (doesn't have to be windows on the current workspace). We generate -- our own, since we can't rely on NonEmptyWindowsStackSet returning one in -- the argument with at least two windows. x <- arbitrary `suchThat` \x' -> length (allWindows x') >= 2 w <- arbitraryWindow (NonEmptyWindowsStackSet x) -- Make sure we pick a window that is NOT the currently focused `suchThat` \w' -> Just w' /= peek x return $ peek (delete w 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 = do -- Generate a StackSet with at least two windows on the current workspace. x <- arbitrary `suchThat` \(x' :: T) -> length (index x') >= 2 let w = last (index x) y = focusWindow w x -- focus last window in stack return $ peek (delete w y) == peek (focusUp y) -- focus movement in the presence of delete: -- when not in the last item in the stack, focus moves down prop_delete_focus_not_end = do x <- arbitrary -- There must be at least two windows and the current focused is not the -- last one in the stack. `suchThat` \(x' :: T) -> let currWins = index x' in length currWins >= 2 && peek x' /= Just (last currWins) -- This is safe, as we know there are >= 2 windows let Just n = peek x return $ peek (delete n x) == peek (focusDown x) xmonad-0.17.2/tests/Properties/Failure.hs0000644000000000000000000000175107346545000016505 0ustar0000000000000000module Properties.Failure where import XMonad.StackSet hiding (filter) import qualified Control.Exception as C import System.IO.Unsafe import Data.List (isPrefixOf) -- --------------------------------------------------------------------- -- testing for failure and help out hpc -- -- Since base 4.9.0.0 `error` appends a stack trace. The tests below -- use `isPrefixOf` to only test equality on the error message. -- prop_abort :: Int -> Bool prop_abort _ = unsafePerformIO $ C.catch (abort "fail") check where check (C.SomeException e) = return $ "xmonad: StackSet: fail" `isPrefixOf` show e -- new should fail with an abort prop_new_abort :: Int -> Bool prop_new_abort _ = unsafePerformIO $ C.catch f check where f = new undefined{-layout-} [] [] `seq` return False check (C.SomeException e) = return $ "xmonad: StackSet: non-positive argument to StackSet.new" `isPrefixOf` show e -- TODO: Fix this? -- prop_view_should_fail = view {- with some bogus data -} xmonad-0.17.2/tests/Properties/Floating.hs0000644000000000000000000000201007346545000016646 0ustar0000000000000000{-# LANGUAGE ScopedTypeVariables #-} module Properties.Floating where import Test.QuickCheck import Instances import XMonad.StackSet hiding (filter) import qualified Data.Map as M ------------------------------------------------------------------------ -- properties for the floating layer: prop_float_reversible (nex :: NonEmptyWindowsStackSet) = do let NonEmptyWindowsStackSet x = nex w <- arbitraryWindow nex return $ sink w (float w geom x) == x where geom = RationalRect 100 100 100 100 prop_float_geometry (nex :: NonEmptyWindowsStackSet) = do let NonEmptyWindowsStackSet x = nex w <- arbitraryWindow nex let s = float w geom x return $ M.lookup w (floating s) == Just geom where geom = RationalRect 100 100 100 100 prop_float_delete (nex :: NonEmptyWindowsStackSet) = do let NonEmptyWindowsStackSet x = nex w <- arbitraryWindow nex let s = float w geom x t = delete w s return $ not (w `member` t) where geom = RationalRect 100 100 100 100 xmonad-0.17.2/tests/Properties/Focus.hs0000644000000000000000000000513607346545000016176 0ustar0000000000000000{-# LANGUAGE ScopedTypeVariables #-} module Properties.Focus where import Test.QuickCheck import Instances import Utils import XMonad.StackSet hiding (filter) import Data.Maybe (fromJust) -- --------------------------------------------------------------------- -- rotating focus -- -- master/focus -- -- The tiling order, and master window, of a stack is unaffected by focus changes. -- prop_focus_left_master (SizedPositive n) (x::T) = index (applyN (Just n) focusUp x) == index x prop_focus_right_master (SizedPositive n) (x::T) = index (applyN (Just n) focusDown x) == index x prop_focus_master_master (SizedPositive n) (x::T) = index (applyN (Just n) focusMaster x) == index x prop_focusWindow_master (NonNegative n) (x :: T) = case peek x of Nothing -> True Just _ -> let s = index x i = 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 (NonNegative (n :: 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 (NonNegative (n :: 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 (x::T ) = do n <- arbitrary `suchThat` \n' -> not $ n' `member` x return $ focusWindow n x == x xmonad-0.17.2/tests/Properties/GreedyView.hs0000644000000000000000000000271107346545000017165 0ustar0000000000000000{-# LANGUAGE ScopedTypeVariables #-} module Properties.GreedyView where import Test.QuickCheck import Instances import Utils import XMonad.StackSet hiding (filter) import Data.List (sortBy) -- --------------------------------------------------------------------- -- greedyViewing workspaces -- greedyView sets the current workspace to 'n' prop_greedyView_current (x :: T) = do n <- arbitraryTag x return $ currentTag (greedyView n x) == n -- greedyView leaves things unchanged for invalid workspaces prop_greedyView_current_id (x :: T) = do n <- arbitrary `suchThat` \n' -> not $ n' `tagMember` x return $ currentTag (greedyView n x) == currentTag x -- greedyView *only* sets the current workspace, and touches Xinerama. -- no workspace contents will be changed. prop_greedyView_local (x :: T) = do n <- arbitraryTag x return $ workspaces x == workspaces (greedyView n x) where workspaces a = sortBy (\s t -> tag s `compare` tag t) $ workspace (current a) : map workspace (visible a) ++ hidden a -- greedyView is idempotent prop_greedyView_idem (x :: T) = do n <- arbitraryTag x return $ greedyView n (greedyView n x) == greedyView n x -- greedyView is reversible, though shuffles the order of hidden/visible prop_greedyView_reversible (x :: T) = do n <- arbitraryTag x return $ normal (greedyView n' (greedyView n x)) == normal x where n' = currentTag x xmonad-0.17.2/tests/Properties/Insert.hs0000644000000000000000000000340107346545000016354 0ustar0000000000000000{-# LANGUAGE ScopedTypeVariables #-} module Properties.Insert where import Test.QuickCheck import Instances import Utils import XMonad.StackSet hiding (filter) import Data.List (nub) -- --------------------------------------------------------------------- -- '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 (nex :: NonEmptyWindowsStackSet) = do let NonEmptyWindowsStackSet x = nex w <- arbitraryWindow nex return $ insertUp w x == x -- push shouldn't change anything but the current workspace prop_insert_local (x :: T) = do i <- arbitrary `suchThat` \i' -> not $ i' `member` x return $ 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 x = do n <- arbitrary `suchThat` \n -> not $ n `member` x return $ 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 xmonad-0.17.2/tests/Properties/Layout/0000755000000000000000000000000007346545000016033 5ustar0000000000000000xmonad-0.17.2/tests/Properties/Layout/Full.hs0000644000000000000000000000171107346545000017271 0ustar0000000000000000{-# LANGUAGE ScopedTypeVariables #-} module Properties.Layout.Full where import Test.QuickCheck import Instances import XMonad.StackSet hiding (filter) import XMonad.Core import XMonad.Layout import Data.Maybe ------------------------------------------------------------------------ -- Full layout -- pureLayout works for Full prop_purelayout_full rect = do x <- (arbitrary :: Gen T) `suchThat` (isJust . peek) let layout = Full st = fromJust . stack . workspace . current $ x ts = pureLayout layout rect st return $ length ts == 1 -- only one window to view && snd (head ts) == rect -- and sets fullscreen && fst (head ts) == fromJust (peek x) -- and the focused window is shown -- 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 xmonad-0.17.2/tests/Properties/Layout/Tall.hs0000644000000000000000000000716307346545000017272 0ustar0000000000000000{-# LANGUAGE ScopedTypeVariables #-} module Properties.Layout.Tall where import Test.QuickCheck import Instances import Utils import XMonad.StackSet hiding (filter) import XMonad.Core import XMonad.Layout import Graphics.X11.Xlib.Types (Rectangle(..)) import Data.Maybe import Data.List (sort) import Data.Ratio ------------------------------------------------------------------------ -- 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_horizontal (NonNegative n) x = noOverflows (+) (rect_x x) (rect_width x) ==> sum (map rect_width xs) == rect_width x && all (\s -> rect_height s == rect_height x) xs && map rect_x xs == sort (map rect_x xs) where xs = splitHorizontally n x -- splitting vertically yields sensible results prop_split_vertical (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 where (a,b) = splitVerticallyBy r x -- pureLayout works. prop_purelayout_tall n r1 r2 rect = do x <- (arbitrary :: Gen T) `suchThat` (isJust . peek) let layout = Tall n r1 r2 st = fromJust . stack . workspace . current $ x ts = pureLayout layout rect st return $ length ts == length (index x) && noOverlaps (map snd ts) && description layout == "Tall" -- Test message handling of Tall -- what happens when we send a Shrink message to Tall prop_shrink_tall (NonNegative n) (Positive 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) (Positive delta) (NonNegative n1) (Positive 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) (Positive 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) prop_desc_mirror n r1 r2 = description (Mirror $! t) == "Mirror Tall" where t = Tall n r1 r2 xmonad-0.17.2/tests/Properties/Screen.hs0000644000000000000000000000406607346545000016337 0ustar0000000000000000{-# LANGUAGE ScopedTypeVariables #-} module Properties.Screen where import Utils import Test.QuickCheck import Instances import Control.Applicative import XMonad.StackSet hiding (filter) import XMonad.Operations import Graphics.X11.Xlib.Types (Dimension) import Graphics.X11 (Rectangle(Rectangle)) import XMonad.Layout prop_screens (x :: T) = n `elem` screens x where n = current x -- screens makes sense prop_screens_works (x :: T) = screens x == current x : visible x ------------------------------------------------------------------------ -- Hints prop_resize_inc (Positive inc_w,Positive 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 (Positive inc_w,Positive 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) prop_aspect_hint_shrink hint (w,h) = case applyAspectHint hint (w,h) of (w',h') -> w' <= w && h' <= h -- applyAspectHint does nothing when the supplied (x,y) fits -- the desired range prop_aspect_fits = forAll ((,,,) <$> pos <*> pos <*> pos <*> pos) $ \ (x,y,a,b) -> let f = applyAspectHint ((x, y+a), (x+b, y)) in noOverflows (*) x (y+a) && noOverflows (*) (x+b) y ==> f (x,y) == (x,y) where pos = choose (0, 65535) prop_point_within r@(Rectangle x y w h) = forAll ((,) <$> choose (0, fromIntegral w - 1) <*> choose (0, fromIntegral h - 1)) $ \(dx,dy) -> and [ dx > 0, dy > 0, noOverflows (\ a b -> a + abs b) x w, noOverflows (\ a b -> a + abs b) y h ] ==> pointWithin (x+dx) (y+dy) r prop_point_within_mirror r (x,y) = pointWithin x y r == pointWithin y x (mirrorRect r) xmonad-0.17.2/tests/Properties/Shift.hs0000644000000000000000000000476407346545000016202 0ustar0000000000000000{-# LANGUAGE ScopedTypeVariables #-} module Properties.Shift where import Test.QuickCheck import Instances import Utils import XMonad.StackSet hiding (filter) import qualified Data.List as L -- --------------------------------------------------------------------- -- shift -- shift is fully reversible on current window, when focus and master -- are the same. otherwise, master may move. prop_shift_reversible (x :: T) = do i <- arbitraryTag x case peek y of Nothing -> return True Just _ -> return $ normal ((view n . shift n . view i . shift i) y) == normal y where y = swapMaster x n = currentTag 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 (x :: T) = do n <- arbitraryTag x case peek x of Nothing -> return True Just w -> return $ shiftWin n w x == shift n x -- shiftWin on a non-existant window is identity prop_shift_win_indentity (x :: T) = do n <- arbitraryTag x w <- arbitrary `suchThat` \w' -> not (w' `member` x) return $ shiftWin n w x == x -- shiftWin leaves the current screen as it is, if neither n is the tag -- of the current workspace nor w on the current workspace prop_shift_win_fix_current = do x <- arbitrary `suchThat` \(x' :: T) -> -- Invariant, otherWindows are NOT in the current workspace. let otherWindows = allWindows x' L.\\ index x' in length (tags x') >= 2 && not (null otherWindows) -- Sadly we have to construct `otherWindows` again, for the actual StackSet -- that got chosen. let otherWindows = allWindows x L.\\ index x -- We know such tag must exists, due to the precondition n <- arbitraryTag x `suchThat` (/= currentTag x) -- we know length is >= 1, from above precondition idx <- choose (0, length otherWindows - 1) let w = otherWindows !! idx return $ current x == current (shiftWin n w x) xmonad-0.17.2/tests/Properties/Stack.hs0000644000000000000000000000522707346545000016165 0ustar0000000000000000{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} module Properties.Stack where import Test.QuickCheck import Instances import XMonad.StackSet hiding (filter) import qualified XMonad.StackSet as S (filter) import Data.Maybe import Data.Proxy import Test.QuickCheck.Classes ( Laws (lawsTypeclass, lawsProperties), Proxy1 (Proxy1), foldableLaws, traversableLaws, ) -- 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 -> null (index x) Just it -> length (index x) == length (focus it : up it ++ down it) -- 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 (NonEmptyWindowsStackSet x) = do -- Reimplementation of arbitraryWindow, but to make sure that -- implementation doesn't change in the future, and stop using allWindows, -- which is a key component in this test (together with member). let ws = allWindows x -- We know that there are at least 1 window in a NonEmptyWindowsStackSet. idx <- choose (0, length ws - 1) return $ member (ws!!idx) x -- 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)) -- differentiate should return Nothing if the list is empty or Just stack, with -- the first element of the list is current, and the rest of the list is down. prop_differentiate xs = if null xs then isNothing (differentiate xs) else differentiate xs == Just (Stack (head xs) [] (tail xs)) where _ = xs :: [Int] -- Check type class laws of 'Data.Foldable.Foldable' and 'Data.Traversable.Traversable'. newtype TestStack a = TestStack (Stack a) deriving (Eq, Read, Show, Foldable, Functor) instance (Arbitrary a) => Arbitrary (TestStack a) where arbitrary = TestStack <$> (Stack <$> arbitrary <*> arbitrary <*> arbitrary) shrink = traverse shrink instance Traversable TestStack where traverse f (TestStack sx) = fmap TestStack (traverse f sx) prop_laws_Stack = format (foldableLaws p) <> format (traversableLaws p) where p = Proxy :: Proxy TestStack format laws = [ ("Stack: " <> lawsTypeclass laws <> ": " <> name, prop) | (name, prop) <- lawsProperties laws ] xmonad-0.17.2/tests/Properties/StackSet.hs0000644000000000000000000001055207346545000016636 0ustar0000000000000000{-# LANGUAGE ScopedTypeVariables #-} module Properties.StackSet where import Test.QuickCheck import Instances import Utils import XMonad.StackSet hiding (filter) import Data.Maybe import Data.List (nub) -- --------------------------------------------------------------------- -- 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. ------------------------------------------------------------------------ -- 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 -- TODO: Fix this. -- 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 (SizedPositive n) l = forAll (choose (1, fromIntegral n)) $ \m -> forAll (vector m) $ \ms -> invariant $ new l [0..fromIntegral n-1] ms prop_view_I n (x :: T) = invariant $ view n x prop_greedyView_I n (x :: T) = invariant $ greedyView n x prop_focusUp_I (SizedPositive n) (x :: T) = invariant $ applyN (Just n) focusUp x prop_focusMaster_I (SizedPositive n) (x :: T) = invariant $ applyN (Just n) focusMaster x prop_focusDown_I (SizedPositive n) (x :: T) = invariant $ applyN (Just n) focusDown x prop_focus_I (SizedPositive n) (x :: T) = case peek x of Nothing -> True Just _ -> let w = focus . fromJust . stack . workspace . current $ applyN (Just n) focusUp x 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 (SizedPositive n) (x :: T) = invariant $ applyN (Just n) swapUp x prop_swap_right_I (SizedPositive n) (x :: T) = invariant $ applyN (Just n) swapDown x prop_shift_I (x :: T) = do n <- arbitraryTag x return $ invariant $ shift (fromIntegral n) x prop_shift_win_I (nex :: NonEmptyWindowsStackSet) = do let NonEmptyWindowsStackSet x = nex w <- arbitraryWindow nex n <- arbitraryTag x return $ invariant $ shiftWin n w x -- --------------------------------------------------------------------- -- 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 (EmptyStackSet x) = currentTag x == head (tags x) -- no windows will be a member of an empty workspace prop_member_empty i (EmptyStackSet x) = not (member i x) -- 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 xmonad-0.17.2/tests/Properties/Swap.hs0000644000000000000000000000345707346545000016035 0ustar0000000000000000{-# LANGUAGE ScopedTypeVariables #-} module Properties.Swap where import Test.QuickCheck import Instances import Utils import XMonad.StackSet hiding (filter) -- --------------------------------------------------------------------- -- 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 xmonad-0.17.2/tests/Properties/View.hs0000644000000000000000000000257607346545000016036 0ustar0000000000000000{-# LANGUAGE ScopedTypeVariables #-} module Properties.View where import Test.QuickCheck import Instances import Utils import XMonad.StackSet hiding (filter) import Data.List (sortBy) -- --------------------------------------------------------------------- -- viewing workspaces -- view sets the current workspace to 'n' prop_view_current (x :: T) = do n <- arbitraryTag x return $ (tag . workspace . current . view n) x == n -- view *only* sets the current workspace, and touches Xinerama. -- no workspace contents will be changed. prop_view_local (x :: T) = do n <- arbitraryTag x return $ workspaces x == workspaces (view n x) where workspaces a = sortBy (\s t -> tag s `compare` tag t) $ workspace (current a) : map workspace (visible a) ++ hidden a -- TODO: Fix this -- 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) = do n <- arbitraryTag x return $ view n (view n x) == view n x -- view is reversible, though shuffles the order of hidden/visible prop_view_reversible (x :: T) = do n <- arbitraryTag x return $ normal (view n' (view n x)) == normal x where n' = currentTag x xmonad-0.17.2/tests/Properties/Workspace.hs0000644000000000000000000000371107346545000017052 0ustar0000000000000000{-# LANGUAGE ScopedTypeVariables #-} module Properties.Workspace where import Test.QuickCheck import Instances import Utils import XMonad.StackSet hiding (filter) import Data.Maybe -- 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 = do -- make sure we have some xinerama screens. x <- arbitrary `suchThat` \(x' :: T) -> visible x' /= [] let tags = [ tag (workspace y) | y <- visible x ] scr = last [ screen y | y <- visible x ] return $ fromJust (lookupWorkspace scr x) `elem` tags prop_currentTag (x :: T) = currentTag x == tag (workspace (current x)) -- Rename a given tag if present in the StackSet. prop_rename1 (x::T) = do o <- arbitraryTag x n <- arbitrary `suchThat` \n' -> not $ n' `tagMember` x -- Rename o to n let y = renameTag o n x return $ 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 = do n <- arbitrary `suchThat` \n' -> not $ n' `tagMember` x let ts = tags x y = ensureTags l (n:ts) x return $ hidden y /= hidden x -- doesn't append, renames && and [ isNothing (stack z) && layout z == l | z <- hidden y, tag z == n ] 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) xmonad-0.17.2/tests/Utils.hs0000644000000000000000000000267507346545000014070 0ustar0000000000000000{-# LANGUAGE RankNTypes #-} module Utils where import XMonad.StackSet hiding (filter) import Graphics.X11.Xlib.Types (Rectangle(..)) import Data.List (sortBy) -- Useful operation, the non-local workspaces hidden_spaces x = map workspace (visible x) ++ hidden 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 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) applyN :: (Integral n) => Maybe n -> (a -> a) -> a -> a applyN Nothing f v = v applyN (Just 0) f v = v applyN (Just n) f v = applyN (Just $ n-1) f (f v) tags x = map tag $ workspaces x -- | noOverflows op a b is True if @a `op` fromIntegral b@ overflows (or -- otherwise gives the same answer when done using Integer noOverflows :: (Integral b, Integral c) => (forall a. Integral a => a -> a -> a) -> b -> c -> Bool noOverflows op a b = toInteger (a `op` fromIntegral b) == toInteger a `op` toInteger b xmonad-0.17.2/util/0000755000000000000000000000000007346545000012235 5ustar0000000000000000xmonad-0.17.2/util/hpcReport.sh0000644000000000000000000000133707346545000014543 0ustar0000000000000000#!/bin/bash set -e if [[ ! ( -e xmonad.cabal && -e dist/hpc/tix/properties/properties.tix ) ]]; then echo "run in the same dir as xmonad.cabal after having run cabal configure --enable-tests --enable-library-coverage; cabal test " exit 1 fi propsExclude=$(find tests/Properties -name '*.hs' \ | sed -e 's_/_._g' -e 's_.hs$__' -e 's_^tests._--exclude=_' ) hpcFlags=" --hpcdir=dist/hpc/mix/ dist/hpc/tix/properties/properties.tix " if [[ ! (-e dist/hpc/mix/Main.mix) ]]; then mv dist/hpc/mix/properties/* dist/hpc/mix/ mv dist/hpc/mix/xmonad-*/xmonad-*/* dist/hpc/mix/xmonad-*/ fi hpc markup --destdir=dist/hpc $hpcFlags > /dev/null echo "see dist/hpc/hpc_index.html " hpc report $hpcFlags xmonad-0.17.2/xmonad.cabal0000644000000000000000000001241107346545000013531 0ustar0000000000000000name: xmonad version: 0.17.2 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. license: BSD3 license-file: LICENSE author: Spencer Janssen, Don Stewart, Adam Vogt, David Roundy, Jason Creighton, Brent Yorgey, Peter Jones, Peter Simons, Andrea Rossato, Devin Mullins, Lukas Mai, Alec Berryman, Stefan O'Rear, Daniel Wagner, Peter J. Jones, Daniel Schoepe, Karsten Schoelzel, Neil Mitchell, Joachim Breitner, Peter De Wachter, Eric Mertens, Geoff Reedy, Michiel Derhaeg, Philipp Balzarek, Valery V. Vorotyntsev, Alex Tarkovsky, Fabian Beuke, Felix Hirn, Michael Sloan, Tomas Janousek, Vanessa McHale, Nicolas Pouillard, Aaron Denney, Austin Seipp, Benno Fünfstück, Brandon S Allbery, Chris Mears, Christian Thiemann, Clint Adams, Daniel Neri, David Lazar, Ferenc Wagner, Francesco Ariis, Gábor Lipták, Ivan N. Veselov, Ivan Tarasov, Javran Cheng, Jens Petersen, Joey Hess, Jonne Ransijn, Josh Holland, Khudyakov Alexey, Klaus Weidner, Michael G. Sloan, Mikkel Christiansen, Nicolas Dudebout, Ondřej Súkup, Paul Hebble, Shachaf Ben-Kiki, Siim Põder, Tim McIver, Trevor Elliott, Wouter Swierstra, Conrad Irwin, Tim Thelion, Tony Zorman maintainer: xmonad@haskell.org tested-with: GHC == 8.6.5 || == 8.8.4 || == 8.10.7 || == 9.0.2 || == 9.2.7 || == 9.4.4 || == 9.6.1 category: System homepage: http://xmonad.org bug-reports: https://github.com/xmonad/xmonad/issues build-type: Simple extra-source-files: README.md CHANGES.md CONTRIBUTING.md INSTALL.md MAINTAINERS.md TUTORIAL.md man/xmonad.1.markdown man/xmonad.1 man/xmonad.1.html man/xmonad.hs util/hpcReport.sh cabal-version: 1.12 source-repository head type: git location: https://github.com/xmonad/xmonad flag pedantic description: Be pedantic (-Werror and the like) default: False manual: True library exposed-modules: XMonad XMonad.Config XMonad.Core XMonad.Layout XMonad.Main XMonad.ManageHook XMonad.Operations XMonad.StackSet other-modules: Paths_xmonad hs-source-dirs: src build-depends: base >= 4.11 && < 5 , X11 >= 1.10 && < 1.11 , containers , data-default-class , directory , filepath , mtl , process , setlocale , time , transformers >= 0.3 , unix ghc-options: -funbox-strict-fields -Wall -Wno-unused-do-bind default-language: Haskell2010 -- Keep this in sync with the oldest version in 'tested-with' if impl(ghc > 8.6.5) ghc-options: -Wno-unused-imports if flag(pedantic) ghc-options: -Werror executable xmonad main-is: Main.hs build-depends: base, xmonad ghc-options: -Wall -Wno-unused-do-bind default-language: Haskell2010 -- Keep this in sync with the oldest version in 'tested-with' if impl(ghc > 8.6.5) ghc-options: -Wno-unused-imports if flag(pedantic) ghc-options: -Werror test-suite properties type: exitcode-stdio-1.0 main-is: Properties.hs other-modules: Instances Properties.Delete Properties.Failure Properties.Floating Properties.Focus Properties.GreedyView Properties.Insert Properties.Layout.Full Properties.Layout.Tall Properties.Screen Properties.Shift Properties.Stack Properties.StackSet Properties.Swap Properties.View Properties.Workspace Utils hs-source-dirs: tests build-depends: base , QuickCheck >= 2 , quickcheck-classes >= 0.4.3 , X11 , containers , xmonad default-language: Haskell2010 if flag(pedantic) ghc-options: -Werror