xmonad 0.15 → 0.18.1
raw patch · 39 files changed
Files
- CHANGES.md +158/−2
- CONFIG +0/−82
- CONTRIBUTING.md +131/−0
- INSTALL.md +402/−0
- LICENSE +21/−24
- MAINTAINERS.md +146/−0
- README.md +81/−106
- STYLE +0/−22
- TUTORIAL.md +1330/−0
- man/xmonad.1 +99/−147
- man/xmonad.1.html +381/−135
- man/xmonad.1.markdown +106/−7
- man/xmonad.hs +9/−8
- src/XMonad/Config.hs +6/−3
- src/XMonad/Core.hs +431/−258
- src/XMonad/Layout.hs +83/−35
- src/XMonad/Main.hs +57/−116
- src/XMonad/ManageHook.hs +20/−14
- src/XMonad/Operations.hs +302/−114
- src/XMonad/StackSet.hs +68/−37
- tests/Instances.hs +5/−5
- tests/Properties.hs +9/−5
- tests/Properties/Delete.hs +1/−1
- tests/Properties/Failure.hs +1/−1
- tests/Properties/Focus.hs +4/−4
- tests/Properties/GreedyView.hs +1/−1
- tests/Properties/Insert.hs +1/−1
- tests/Properties/Layout/Full.hs +1/−1
- tests/Properties/Layout/Tall.hs +34/−10
- tests/Properties/Screen.hs +4/−5
- tests/Properties/Shift.hs +5/−5
- tests/Properties/Stack.hs +30/−4
- tests/Properties/StackSet.hs +2/−2
- tests/Properties/Swap.hs +7/−7
- tests/Properties/View.hs +1/−1
- tests/Utils.hs +2/−2
- tests/loc.hs +0/−14
- util/GenerateManpage.hs +0/−92
- xmonad.cabal +56/−35
CHANGES.md view
@@ -1,7 +1,163 @@ # Change Log / Release Notes -## unknown (unknown)+## 0.18.1 (March 7, 2026) +### Breaking Changes++ * Use `cabal` for `--recompile` if there is a `.cabal` file in the config+ directory and none of `build`, `stack.yaml`, `flake.nix`, nor `default.nix`+ exist.++### Enhancements++### Bug Fixes++### Other++PR #404 (see last change in 0.17.1) has been reverted, because the affected+compilers are (hopefully) no longer being used.++All 9.0 releases of GHC, plus 9.2.1 and 9.2.2 have the join point bug.+Note that 9.0.x is known to also have GC issues and is officially deprecated,+and the only 9.2 release that should be used is 9.2.8. Additionally, GHC HQ+doesn't support releases before 9.6.6.++## 0.18.0 (February 3, 2024)++### Breaking Changes++* Dropped support for GHC 8.4.++### Enhancements++* Exported `sendRestart` and `sendReplace` from `XMonad.Operations`.++* Exported `buildLaunch` from `XMonad.Main`.++* `Tall` does not draw windows with zero area.++* `XMonad.Operations.floatLocation` now applies size hints. This means windows+ will snap to these hints as soon as they're floated (mouse move, keybinding).+ Previously that only happened on mouse resize.++* Recompilation now detects `flake.nix` and `default.nix` (can be a+ symlink) and switches to using `nix build` as appropriate.++* Added `unGrab` to `XMonad.Operations`; this releases XMonad's passive+ keyboard grab, so other applications (like `scrot`) can do their+ thing.++### Bug Fixes++* Duplicated floats (e.g. from X.A.CopyToAll) no longer escape to inactive+ screens.++## 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`.++ * Added `willFloat` function to `XMonad.ManageHooks` to detect whether the+ (about to be) managed window will be a floating window or not.++### 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.++[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@@ -69,7 +225,7 @@ * Compiles with GHC 8.4.1 - * Restored compatability with GHC version prior to 8.0.1 by removing the+ * Restored compatibility with GHC version prior to 8.0.1 by removing the dependency on directory version 1.2.3.
− CONFIG
@@ -1,82 +0,0 @@-== Configuring xmonad ==--xmonad is configured by creating and editing the file:-- ~/.xmonad/xmonad.hs--xmonad then uses settings from this file as arguments to the window manager,-on startup. For a complete example of possible settings, see the file:-- man/xmonad.hs--Further examples are on the website, wiki and extension documentation.-- http://haskell.org/haskellwiki/Xmonad--== A simple example ==--Here is a basic example, which overrides the default border width,-default terminal, and some colours. This text goes in the file-$HOME/.xmonad/xmonad.hs :-- import XMonad-- main = xmonad $ def- { borderWidth = 2- , terminal = "urxvt"- , normalBorderColor = "#cccccc"- , focusedBorderColor = "#cd8b00" }--You can find the defaults in the file:-- XMonad/Config.hs--== Checking your xmonad.hs is correct ==--Place this text in ~/.xmonad/xmonad.hs, and then check that it is-syntactically and type correct by loading it in the Haskell-interpreter:-- $ ghci ~/.xmonad/xmonad.hs- GHCi, version 6.8.1: http://www.haskell.org/ghc/ :? for help- Loading package base ... linking ... done.- Ok, modules loaded: Main.-- Prelude Main> :t main- main :: IO ()--Ok, looks good.--== Loading your configuration ==--To have xmonad start using your settings, type 'mod-q'. xmonad will-then load this new file, and run it. If it is unable to, the defaults-are used.--To load successfully, both 'xmonad' and 'ghc' must be in your $PATH-environment variable. If GHC isn't in your path, for some reason, you-can compile the xmonad.hs file yourself:-- $ cd ~/.xmonad- $ ghc --make xmonad.hs- $ ls- xmonad xmonad.hi xmonad.hs xmonad.o--When you hit mod-q, this newly compiled xmonad will be used.--== Where are the defaults? ==--The default configuration values are defined in the source file:-- XMonad/Config.hs--the XConfig data structure itself is defined in:-- XMonad/Core.hs--== Extensions ==--Since the xmonad.hs file is just another Haskell module, you may import-and use any Haskell code or libraries you wish. For example, you can use-things from the xmonad-contrib library, or other code you write-yourself.
+ CONTRIBUTING.md view
@@ -0,0 +1,131 @@+# 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
+ INSTALL.md view
@@ -0,0 +1,402 @@+# 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).++<!-- https://github.com/frnmst/md-toc -->+<!-- regenerate via: md_toc -s1 -p github INSTALL.md -->+<!--TOC-->++- [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)++<!--TOC-->++## 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++Probably one of the best ways to get [stack] is to use [GHCup], which is the main Haskell installer according to language's official [website][GHCup] and community [survey]. GHCup is [widely available] and is considered less error prone than other installation options.++You can also use 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+```++#### 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++Probably one of the best ways to get [cabal-install] is to use [GHCup], which is the main Haskell installer according to language's official [website][GHCup] and community [survey]. GHCup is [widely available] and is considered less error prone than other installation options.++You can also use 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+```++See also <https://www.haskell.org/cabal/#install-upgrade>.++#### Create a New Project++If you want to use `xmonad` or `xmonad-contrib` from git, you will need a+`cabal.project` file. If you want to use both from [Hackage][], you should+skip this step.++Create a file named `cabal.project` containing:++```+packages: */*.cabal+```++(If you do this step without using [git] checkouts, you will get an error from+cabal in the next step. Simply remove `cabal.project` and try again.)++#### 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 base 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 `~/.local/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`, <https://aur.archlinux.org/packages/xinit-xsession/>+provides one.++#### See also++* <https://xmonad.org/documentation.html#in-your-environment>+* [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/+[survey]: https://taylor.fausak.me/2022/11/18/haskell-survey-results/+[widely available]: https://www.haskell.org/ghcup/install/#supported-platforms+[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
LICENSE view
@@ -1,31 +1,28 @@ Copyright (c) 2007,2008 Spencer Janssen Copyright (c) 2007,2008 Don Stewart--All rights reserved.+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:+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.+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.+2. Redistributions in binary form must reproduce the above copyright notice,+this list of conditions and the following disclaimer in the documentation+and/or other materials provided with the distribution. -3. Neither the name of the author nor the names of his contributors- may be used to endorse or promote products derived from this software- without specific prior written permission.+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 AUTHORS ``AS IS'' AND ANY EXPRESS OR-IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE-DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR-ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL-DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS-OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)-HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,-STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN-ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE-POSSIBILITY OF SUCH DAMAGE.+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.
+ MAINTAINERS.md view
@@ -0,0 +1,146 @@+# 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. Trigger xmonad-docs build to generate and persist docs for the just+ released version:++ - https://github.com/xmonad/xmonad-docs/actions/workflows/stack.yml++ 8. 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
README.md view
@@ -1,8 +1,27 @@-# xmonad: A Tiling Window Manager+<p align="center">+ <a href="https://xmonad.org/"><img alt="XMonad logo" src="https://xmonad.org/images/logo-wrapped.svg" height=150></a>+</p>+<p align="center">+ <a href="https://hackage.haskell.org/package/xmonad"><img alt="Hackage" src="https://img.shields.io/hackage/v/xmonad?logo=haskell"></a>+ <a href="https://github.com/xmonad/xmonad/blob/readme/LICENSE"><img alt="License" src="https://img.shields.io/github/license/xmonad/xmonad"></a>+ <a href="https://haskell.org/"><img alt="Made in Haskell" src="https://img.shields.io/badge/Made%20in-Haskell-%235e5086?logo=haskell"></a>+ <br>+ <a href="https://github.com/xmonad/xmonad/actions/workflows/stack.yml"><img alt="Stack" src="https://img.shields.io/github/actions/workflow/status/xmonad/xmonad/stack.yml?label=Stack&logo=githubactions&logoColor=white"></a>+ <a href="https://github.com/xmonad/xmonad/actions/workflows/haskell-ci.yml"><img alt="Cabal" src="https://img.shields.io/github/actions/workflow/status/xmonad/xmonad/haskell-ci.yml?label=Cabal&logo=githubactions&logoColor=white"></a>+ <a href="https://github.com/xmonad/xmonad/actions/workflows/nix.yml"><img alt="Nix" src="https://img.shields.io/github/actions/workflow/status/xmonad/xmonad/nix.yml?label=Nix&logo=githubactions&logoColor=white"></a>+ <br>+ <a href="https://github.com/sponsors/xmonad"><img alt="GitHub Sponsors" src="https://img.shields.io/github/sponsors/xmonad?label=GitHub%20Sponsors&logo=githubsponsors"></a>+ <a href="https://opencollective.com/xmonad"><img alt="Open Collective" src="https://img.shields.io/opencollective/all/xmonad?label=Open%20Collective&logo=opencollective"></a>+ <br>+ <a href="https://web.libera.chat/#xmonad"><img alt="Chat on #xmonad@irc.libera.chat" src="https://img.shields.io/badge/%23%20chat-on%20libera-brightgreen"></a>+ <a href="https://matrix.to/#/#xmonad:matrix.org"><img alt="Chat on #xmonad:matrix.org" src="https://img.shields.io/matrix/xmonad:matrix.org?logo=matrix"></a>+</p> -[](https://travis-ci.org/xmonad/xmonad)+# xmonad -[xmonad][] is a tiling window manager for X. Windows are arranged+**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@@ -12,118 +31,74 @@ workspace. Xinerama is fully supported, allowing windows to be tiled on several physical screens. -## Quick Start-- * From hackage:-- cabal update- cabal install xmonad xmonad-contrib-- * Alternatively, build from source using the following repositories:-- - <https://github.com/xmonad/xmonad>-- - <https://github.com/xmonad/xmonad-contrib>--For the full story, read on.--## Building--Building is quite straightforward, and requires a basic Haskell toolchain.-On many systems xmonad is available as a binary package in your-package system (e.g. on Debian or Gentoo). If at all possible, use this-in preference to a source build, as the dependency resolution will be-simpler.--We'll now walk through the complete list of toolchain dependencies.-- * GHC: the Glasgow Haskell Compiler-- You first need a Haskell compiler. Your distribution's package- system will have binaries of GHC (the Glasgow Haskell Compiler),- the compiler we use, so install that first. If your operating- system's package system doesn't provide a binary version of GHC- and the `cabal-install` tool, you can install both using the- [Haskell Platform][platform].-- It shouldn't be necessary to compile GHC from source -- every common- system has a pre-build binary version. However, if you want to- build from source, the following links will be helpful:-- - GHC: <http://haskell.org/ghc/>-- - Cabal: <http://haskell.org/cabal/download.html>-- * X11 libraries:-- Since you're building an X application, you'll need the C X11- library headers. On many platforms, these come pre-installed. For- others, such as Debian, you can get them from your package manager:-- # for xmonad- $ apt-get install libx11-dev libxinerama-dev libxext-dev libxrandr-dev libxss-dev-- # for xmonad-contrib- $ apt-get install libxft-dev--Then build and install with:-- $ cabal install--## Running xmonad--If you built XMonad using `cabal` then add:-- exec $HOME/.cabal/bin/xmonad--to the last line of your `.xsession` or `.xinitrc` file.--## Configuring--See the [CONFIG][] document and the [example configuration file][example-config].--## XMonadContrib--There are many extensions to xmonad available in the XMonadContrib-(xmc) library. Examples include an ion3-like tabbed layout, a-prompt/program launcher, and various other useful modules.-XMonadContrib is available at:-- * Latest release: <http://hackage.haskell.org/package/xmonad-contrib>-- * Git version: <https://github.com/xmonad/xmonad-contrib>--## Other Useful Programs--A nicer xterm replacement, that supports resizing better:-- * urxvt: <http://software.schmorp.de/pkg/rxvt-unicode.html>+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. -For custom status bars:+## Installation - * xmobar: <http://hackage.haskell.org/package/xmobar>+For installation and configuration instructions, please see: - * taffybar: <https://github.com/travitch/taffybar>+ * [downloading and installing xmonad][web:download]+ * [installing latest xmonad snapshot from git][web:install]+ * [configuring xmonad][web:tutorial] - * dzen: <http://gotmor.googlepages.com/dzen>+If you run into any trouble, consult our [documentation][web:documentation] or+ask the [community][web:community] for help. -For a program dispatch menu:+## Contributing - * [XMonad.Prompt.Shell][xmc-prompt-shell]: (from [XMonadContrib][])+We welcome all forms of contributions: - * dmenu: <http://www.suckless.org/download/>+ * [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] - * gmrun: (in your package system)+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 - * Spencer Janssen- * Don Stewart- * Jason Creighton+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]. -[xmonad]: http://xmonad.org-[xmonadcontrib]: https://hackage.haskell.org/package/xmonad-contrib-[xmc-prompt-shell]: https://hackage.haskell.org/package/xmonad-contrib/docs/XMonad-Prompt-Shell.html-[platform]: http://haskell.org/platform/-[example-config]: https://github.com/xmonad/xmonad-testing/blob/master/example-config.hs-[config]: https://github.com/xmonad/xmonad/blob/master/CONFIG+[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/
− STYLE
@@ -1,22 +0,0 @@--== Coding guidelines for contributing to-== xmonad and the xmonad contributed extensions--* Comment every top level function (particularly exported functions), and- provide a type signature; use Haddock syntax in the comments.--* Follow the coding style of the other modules.--* Code should be compilable with -Wall -Werror -fno-warn-unused-do-bind -fwarn-tabs.- There should be no warnings.--* Partial functions should be avoided: the window manager should not- crash, so do not call `error` or `undefined`--* Use 4 spaces for indenting.--* Any pure function added to the core should have QuickCheck properties- precisely defining its behavior.--* New modules should identify the author, and be submitted under- the same license as xmonad (BSD3 license or freer).
+ TUTORIAL.md view
@@ -0,0 +1,1330 @@+# 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]:++<p>+<img alt="blank desktop" src="https://user-images.githubusercontent.com/50166980/111586872-c0eb0e00-87c1-11eb-9129-c2781bbbf227.png" width="550">+<img alt="blank desktop" src="https://user-images.githubusercontent.com/50166980/111586885-c47e9500-87c1-11eb-8e8e-83d4d33c4c8d.png" width="550">+</p>++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 big+warning labels for the respective version bounds, 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.++In particular, it might be a good idea to set a wallpaper beforehand.+Otherwise, when switching workspaces or closing windows, you might start+seeing "shadows" of windows that were there before, unable to interact+with them.++## 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+-- NOTE: Only needed for versions < 0.18.0! For 0.18.0 and up, this is+-- already included in the XMonad import and will give you a warning!+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 were 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+```++and 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 `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.++ Further, the `xmobarProp` function does not exist in older 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). Do note the lack of+ `ewmhFullscreen`, as explained above!++---++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_ occurrences 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", "<weather> <tempC>°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", "<volumestatus>"+ , "--suffix" , "True"+ , "--"+ , "--on", ""+ ]+ , Run Memory ["--template", "Mem: <usedratio>%"] 10+ , Run Swap [] 10+ , Run Date "%a %Y-%m-%d <fc=#8be9fd>%H:%M</fc>" "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+(!) occurrences 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+ . 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+explanatory—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`. If you want to use XMonad with a desktop environment, see+[Basic Desktop Environment Integration] for how to do this. For a+version using XMonad's built in functionality instead, see the [next+section][using-the-startupHook].++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.++<img alt="blank desktop" src="https://user-images.githubusercontent.com/50166980/111529498-84d49080-8762-11eb-9e81-c15dd844b0a9.png" width="660">++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).++### Using the `startupHook`++Instead of the `.xinitrc` file, one can also use XMonad's built in+`startupHook` in order to auto start programs. The+[XMonad.Util.SpawnOnce] library is perfect for this use case, allowing+programs to start only once, and not with every invocation of `M-q`!++This requires but a small change in `myConfig`:++``` haskell+-- import XMonad.Util.SpawnOnce (spawnOnce)++myConfig = def+ { modMask = mod4Mask -- Rebind Mod to the Super key+ , layoutHook = myLayout -- Use custom layouts+ , startupHook = myStartupHook+ }+ `additionalKeysP`+ [ ("M-S-z", spawn "xscreensaver-command -lock")+ , ("M-C-s", unGrab *> spawn "scrot -s" )+ , ("M-f" , spawn "firefox" )+ ]++myStartupHook :: X ()+myStartupHook = do+ spawnOnce "trayer --edge top --align right --SetDockType true \+ \--SetPartialStrut true --expand true --width 10 \+ \--transparent true --tint 0x5f5f5f --height 18"+ spawnOnce "feh --bg-fill --no-fehbg ~/.wallpapers/haskell-red-noise.png"+ -- … and so on …+```++## 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+-- NOTE: Importing XMonad.Util.Ungrab is only necessary for versions+-- < 0.18.0! For 0.18.0 and up, this is already included in the+-- XMonad import and will generate a warning instead!+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 `<fn>` 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" , "<fn=1>\62020</fn> "+ , "--mediums", "<fn=1>\62018</fn> "+ , "--highs" , "<fn=1>\62016</fn> "+ ...+ ]+ ...+ }+```++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 `"<fn=1>\xf120</fn>"` 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+[INSTALL.md]: INSTALL.md+[PP record]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Hooks-DynamicLog.html#t:PP+[XMonad.Config]: https://github.com/xmonad/xmonad/blob/master/src/XMonad/Config.hs+[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.Hooks.ManageHelpers]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Hooks-ManageHelpers.html+[XMonad.Layout.Magnifier]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Layout-Magnifier.html+[XMonad.Layout.Renamed]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Layout-Renamed.html+[XMonad.Layout.ThreeColumns]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Layout-ThreeColumns.html+[XMonad.ManageHook]: https://xmonad.github.io/xmonad-docs/xmonad/XMonad-ManageHook.html+[XMonad.Util.ClickableWorkspaces]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Util-ClickableWorkspaces.html+[XMonad.Util.EZConfig]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Util-EZConfig.html+[XMonad.Util.Loggers]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Util-Loggers.html+[XMonad.Util.SpawnOnce]: https://hackage.haskell.org/package/xmonad-contrib/docs/XMonad-Util-SpawnOnce.html++[xmobar]: https://codeberg.org/xmobar/xmobar+[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
man/xmonad.1 view
@@ -1,13 +1,27 @@-.\" Automatically generated by Pandoc 2.2.1+.\" Automatically generated by Pandoc 3.1.3 .\"-.TH "XMONAD" "1" "30 September 2018" "Tiling Window Manager" ""+.\" Define V font for inline verbatim, using C font in formats+.\" that render this, and otherwise B font.+.ie "\f[CB]x\f[]"x" \{\+. ftr V B+. ftr VI BI+. ftr VB B+. ftr VBI BI+.\}+.el \{\+. ftr V CR+. ftr VI CI+. ftr VB CB+. ftr VBI CBI+.\}+.TH "XMONAD" "1" "27 October 2021" "Tiling Window Manager" "" .hy .SH Name .PP-xmonad \- Tiling Window Manager+xmonad - Tiling Window Manager .SH Description .PP-\f[I]xmonad\f[] is a minimalist tiling window manager for X, written in+\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.@@ -15,14 +29,14 @@ real estate. All features of the window manager are accessible purely from the keyboard: a mouse is entirely optional.-\f[I]xmonad\f[] is configured in Haskell, and custom layout algorithms+\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[] is predictability: the user should know+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[] provides three layout algorithms: tall, wide-and fullscreen.+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@@ -31,34 +45,34 @@ simultaneous display of a number of screens. .PP By utilizing the expressivity of a modern functional language with a-rich static type system, \f[I]xmonad\f[] provides a complete, featureful-window manager in less than 1200 lines of code, with an emphasis on-correctness and robustness.+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.+type-based automated testing. A benefit of this is that the code is simple to understand, and easy to modify. .SH Usage .PP-\f[I]xmonad\f[] places each window into a \[lq]workspace\[rq].+\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.+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+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.+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.+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}+mod-{w,e,r} switch the focus between screens, while shift-mod-{w,e,r} move the current window to that screen.-When \f[I]xmonad\f[] starts, workspace 1 is on screen 1, workspace 2 is+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.@@ -67,221 +81,159 @@ xmonad has several flags which you may pass to the executable. These flags are: .TP-.B \[en]recompile-Recompiles your configuration in \f[I]~/.xmonad/xmonad.hs\f[]-.RS-.RE+\[en]recompile+Recompiles your \f[I]xmonad.hs\f[R] configuration .TP-.B \[en]restart-Causes the currently running \f[I]xmonad\f[] process to restart-.RS-.RE+\[en]restart+Causes the currently running \f[I]xmonad\f[R] process to restart .TP-.B \[en]replace+\[en]replace Replace the current window manager with xmonad-.RS-.RE .TP-.B \[en]version-Display version of \f[I]xmonad\f[]-.RS-.RE+\[en]version+Display version of \f[I]xmonad\f[R] .TP-.B \[en]verbose\-version-Display detailed version of \f[I]xmonad\f[]-.RS-.RE-.PP-##Default keyboard bindings+\[en]verbose-version+Display detailed version of \f[I]xmonad\f[R]+.SS Default keyboard bindings .TP-.B mod\-shift\-return+mod-shift-return Launch terminal-.RS-.RE .TP-.B mod\-p+mod-p Launch dmenu-.RS-.RE .TP-.B mod\-shift\-p+mod-shift-p Launch gmrun-.RS-.RE .TP-.B mod\-shift\-c+mod-shift-c Close the focused window-.RS-.RE .TP-.B mod\-space+mod-space Rotate through the available layout algorithms-.RS-.RE .TP-.B mod\-shift\-space+mod-shift-space Reset the layouts on the current workspace to default-.RS-.RE .TP-.B mod\-n+mod-n Resize viewed windows to the correct size-.RS-.RE .TP-.B mod\-tab+mod-tab Move focus to the next window-.RS-.RE .TP-.B mod\-shift\-tab+mod-shift-tab Move focus to the previous window-.RS-.RE .TP-.B mod\-j+mod-j Move focus to the next window-.RS-.RE .TP-.B mod\-k+mod-k Move focus to the previous window-.RS-.RE .TP-.B mod\-m+mod-m Move focus to the master window-.RS-.RE .TP-.B mod\-return+mod-return Swap the focused window and the master window-.RS-.RE .TP-.B mod\-shift\-j+mod-shift-j Swap the focused window with the next window-.RS-.RE .TP-.B mod\-shift\-k+mod-shift-k Swap the focused window with the previous window-.RS-.RE .TP-.B mod\-h+mod-h Shrink the master area-.RS-.RE .TP-.B mod\-l+mod-l Expand the master area-.RS-.RE .TP-.B mod\-t+mod-t Push window back into tiling-.RS-.RE .TP-.B mod\-comma+mod-comma Increment the number of windows in the master area-.RS-.RE .TP-.B mod\-period+mod-period Deincrement the number of windows in the master area-.RS-.RE .TP-.B mod\-shift\-q+mod-shift-q Quit xmonad-.RS-.RE .TP-.B mod\-q+mod-q Restart xmonad-.RS-.RE .TP-.B mod\-shift\-slash+mod-shift-slash Run xmessage with a summary of the default keybindings (useful for beginners)-.RS-.RE .TP-.B mod\-question+mod-question Run xmessage with a summary of the default keybindings (useful for beginners)-.RS-.RE .TP-.B mod\-[1..9]+mod-[1..9] Switch to workspace N-.RS-.RE .TP-.B mod\-shift\-[1..9]+mod-shift-[1..9] Move client to workspace N-.RS-.RE .TP-.B mod\-{w,e,r}+mod-{w,e,r} Switch to physical/Xinerama screens 1, 2, or 3-.RS-.RE .TP-.B mod\-shift\-{w,e,r}+mod-shift-{w,e,r} Move client to screen 1, 2, or 3-.RS-.RE .TP-.B mod\-button1+mod-button1 Set the window to floating mode and move by dragging-.RS-.RE .TP-.B mod\-button2+mod-button2 Raise the window to the top of the stack-.RS-.RE .TP-.B mod\-button3+mod-button3 Set the window to floating mode and resize by dragging-.RS-.RE .SH Examples .PP-To use xmonad as your window manager add to your \f[I]~/.xinitrc\f[]-file:+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 ~/.xmonad/xmonad.hs, and then restarted with-mod\-q.+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[V]XMONAD_DATA_DIR,\f[R] \f[V]XMONAD_CONFIG_DIR\f[R], and+\f[V]XMONAD_CACHE_DIR\f[R]; \f[I]xmonad.hs\f[R] is then expected to be+in \f[V]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[V]XDG_CONFIG_HOME\f[R].+Note that, in this case, xmonad will use \f[V]XDG_DATA_HOME\f[R] and+\f[V]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\-+You can find many extensions to the core feature set in the xmonad- contrib package, available through your package manager or from-xmonad.org (http://xmonad.org).+xmonad.org (https://xmonad.org). .SS Modular Configuration .PP-As of \f[I]xmonad\-0.9\f[], any additional Haskell modules may be placed-in \f[I]~/.xmonad/lib/\f[] are available in GHC's searchpath.+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]~/.xmonad/lib/XMonad/Stack/MyAdditions.hs\f[] could contain:+\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\ "function1:\ Not\ implemented\ yet!"-\f[]+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.+module was contained within xmonad or xmonad-contrib. .SH Bugs .PP Probably.
man/xmonad.1.html view
@@ -5,240 +5,486 @@ <meta name="generator" content="pandoc" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" /> <meta name="author" content="" />- <meta name="dcterms.date" content="2018-09-30" />+ <meta name="dcterms.date" content="2021-10-27" /> <title>XMONAD(1) Tiling Window Manager</title>- <style type="text/css">- code{white-space: pre-wrap;}- span.smallcaps{font-variant: small-caps;}- span.underline{text-decoration: underline;}- div.column{display: inline-block; vertical-align: top; width: 50%;}- </style>- <style type="text/css">-a.sourceLine { display: inline-block; line-height: 1.25; }-a.sourceLine { pointer-events: none; color: inherit; text-decoration: inherit; }-a.sourceLine:empty { height: 1.2em; }-.sourceCode { overflow: visible; }-code.sourceCode { white-space: pre; position: relative; }-div.sourceCode { margin: 1em 0; }-pre.sourceCode { margin: 0; }-@media screen {-div.sourceCode { overflow: auto; }-}-@media print {-code.sourceCode { white-space: pre-wrap; }-a.sourceLine { text-indent: -1em; padding-left: 1em; }-}-pre.numberSource a.sourceLine- { position: relative; left: -4em; }-pre.numberSource a.sourceLine::before- { content: attr(data-line-number);- position: relative; left: -1em; text-align: right; vertical-align: baseline;- border: none; pointer-events: all; display: inline-block;- -webkit-touch-callout: none; -webkit-user-select: none;- -khtml-user-select: none; -moz-user-select: none;- -ms-user-select: none; user-select: none;- padding: 0 4px; width: 4em;- color: #aaaaaa;- }-pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }-div.sourceCode- { }-@media screen {-a.sourceLine::before { text-decoration: underline; }-}-code span.al { color: #ff0000; font-weight: bold; } /* Alert */-code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */-code span.at { color: #7d9029; } /* Attribute */-code span.bn { color: #40a070; } /* BaseN */-code span.bu { } /* BuiltIn */-code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */-code span.ch { color: #4070a0; } /* Char */-code span.cn { color: #880000; } /* Constant */-code span.co { color: #60a0b0; font-style: italic; } /* Comment */-code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */-code span.do { color: #ba2121; font-style: italic; } /* Documentation */-code span.dt { color: #902000; } /* DataType */-code span.dv { color: #40a070; } /* DecVal */-code span.er { color: #ff0000; font-weight: bold; } /* Error */-code span.ex { } /* Extension */-code span.fl { color: #40a070; } /* Float */-code span.fu { color: #06287e; } /* Function */-code span.im { } /* Import */-code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */-code span.kw { color: #007020; font-weight: bold; } /* Keyword */-code span.op { color: #666666; } /* Operator */-code span.ot { color: #007020; } /* Other */-code span.pp { color: #bc7a00; } /* Preprocessor */-code span.sc { color: #4070a0; } /* SpecialChar */-code span.ss { color: #bb6688; } /* SpecialString */-code span.st { color: #4070a0; } /* String */-code span.va { color: #19177c; } /* Variable */-code span.vs { color: #4070a0; } /* VerbatimString */-code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */+ <style>+ html {+ color: #1a1a1a;+ background-color: #fdfdfd;+ }+ body {+ margin: 0 auto;+ max-width: 36em;+ padding-left: 50px;+ padding-right: 50px;+ padding-top: 50px;+ padding-bottom: 50px;+ hyphens: auto;+ overflow-wrap: break-word;+ text-rendering: optimizeLegibility;+ font-kerning: normal;+ }+ @media (max-width: 600px) {+ body {+ font-size: 0.9em;+ padding: 12px;+ }+ h1 {+ font-size: 1.8em;+ }+ }+ @media print {+ html {+ background-color: white;+ }+ body {+ background-color: transparent;+ color: black;+ font-size: 12pt;+ }+ p, h2, h3 {+ orphans: 3;+ widows: 3;+ }+ h2, h3, h4 {+ page-break-after: avoid;+ }+ }+ p {+ margin: 1em 0;+ }+ a {+ color: #1a1a1a;+ }+ a:visited {+ color: #1a1a1a;+ }+ img {+ max-width: 100%;+ }+ h1, h2, h3, h4, h5, h6 {+ margin-top: 1.4em;+ }+ h5, h6 {+ font-size: 1em;+ font-style: italic;+ }+ h6 {+ font-weight: normal;+ }+ ol, ul {+ padding-left: 1.7em;+ margin-top: 1em;+ }+ li > ol, li > ul {+ margin-top: 0;+ }+ blockquote {+ margin: 1em 0 1em 1.7em;+ padding-left: 1em;+ border-left: 2px solid #e6e6e6;+ color: #606060;+ }+ code {+ font-family: Menlo, Monaco, Consolas, 'Lucida Console', monospace;+ font-size: 85%;+ margin: 0;+ hyphens: manual;+ }+ pre {+ margin: 1em 0;+ overflow: auto;+ }+ pre code {+ padding: 0;+ overflow: visible;+ overflow-wrap: normal;+ }+ .sourceCode {+ background-color: transparent;+ overflow: visible;+ }+ hr {+ background-color: #1a1a1a;+ border: none;+ height: 1px;+ margin: 1em 0;+ }+ table {+ margin: 1em 0;+ border-collapse: collapse;+ width: 100%;+ overflow-x: auto;+ display: block;+ font-variant-numeric: lining-nums tabular-nums;+ }+ table caption {+ margin-bottom: 0.75em;+ }+ tbody {+ margin-top: 0.5em;+ border-top: 1px solid #1a1a1a;+ border-bottom: 1px solid #1a1a1a;+ }+ th {+ border-top: 1px solid #1a1a1a;+ padding: 0.25em 0.5em 0.25em 0.5em;+ }+ td {+ padding: 0.125em 0.5em 0.25em 0.5em;+ }+ header {+ margin-bottom: 4em;+ text-align: center;+ }+ #TOC li {+ list-style: none;+ }+ #TOC ul {+ padding-left: 1.3em;+ }+ #TOC > ul {+ padding-left: 0;+ }+ #TOC a:not(:hover) {+ text-decoration: none;+ }+ code{white-space: pre-wrap;}+ span.smallcaps{font-variant: small-caps;}+ div.columns{display: flex; gap: min(4vw, 1.5em);}+ div.column{flex: auto; overflow-x: auto;}+ div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}+ /* The extra [class] is a hack that increases specificity enough to+ override a similar rule in reveal.js */+ ul.task-list[class]{list-style: none;}+ ul.task-list li input[type="checkbox"] {+ font-size: inherit;+ width: 0.8em;+ margin: 0 0.8em 0.2em -1.6em;+ vertical-align: middle;+ }+ .display.math{display: block; text-align: center; margin: 0.5rem auto;}+ /* CSS for syntax highlighting */+ pre > code.sourceCode { white-space: pre; position: relative; }+ pre > code.sourceCode > span { line-height: 1.25; }+ pre > code.sourceCode > span:empty { height: 1.2em; }+ .sourceCode { overflow: visible; }+ code.sourceCode > span { color: inherit; text-decoration: inherit; }+ div.sourceCode { margin: 1em 0; }+ pre.sourceCode { margin: 0; }+ @media screen {+ div.sourceCode { overflow: auto; }+ }+ @media print {+ pre > code.sourceCode { white-space: pre-wrap; }+ pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }+ }+ pre.numberSource code+ { counter-reset: source-line 0; }+ pre.numberSource code > span+ { position: relative; left: -4em; counter-increment: source-line; }+ pre.numberSource code > span > a:first-child::before+ { content: counter(source-line);+ position: relative; left: -1em; text-align: right; vertical-align: baseline;+ border: none; display: inline-block;+ -webkit-touch-callout: none; -webkit-user-select: none;+ -khtml-user-select: none; -moz-user-select: none;+ -ms-user-select: none; user-select: none;+ padding: 0 4px; width: 4em;+ color: #aaaaaa;+ }+ pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }+ div.sourceCode+ { }+ @media screen {+ pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }+ }+ code span.al { color: #ff0000; font-weight: bold; } /* Alert */+ code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */+ code span.at { color: #7d9029; } /* Attribute */+ code span.bn { color: #40a070; } /* BaseN */+ code span.bu { color: #008000; } /* BuiltIn */+ code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */+ code span.ch { color: #4070a0; } /* Char */+ code span.cn { color: #880000; } /* Constant */+ code span.co { color: #60a0b0; font-style: italic; } /* Comment */+ code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */+ code span.do { color: #ba2121; font-style: italic; } /* Documentation */+ code span.dt { color: #902000; } /* DataType */+ code span.dv { color: #40a070; } /* DecVal */+ code span.er { color: #ff0000; font-weight: bold; } /* Error */+ code span.ex { } /* Extension */+ code span.fl { color: #40a070; } /* Float */+ code span.fu { color: #06287e; } /* Function */+ code span.im { color: #008000; font-weight: bold; } /* Import */+ code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */+ code span.kw { color: #007020; font-weight: bold; } /* Keyword */+ code span.op { color: #666666; } /* Operator */+ code span.ot { color: #007020; } /* Other */+ code span.pp { color: #bc7a00; } /* Preprocessor */+ code span.sc { color: #4070a0; } /* SpecialChar */+ code span.ss { color: #bb6688; } /* SpecialString */+ code span.st { color: #4070a0; } /* String */+ code span.va { color: #19177c; } /* Variable */+ code span.vs { color: #4070a0; } /* VerbatimString */+ code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */ </style>- <!--[if lt IE 9]>- <script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>- <![endif]--> </head> <body>-<header>+<header id="title-block-header"> <h1 class="title">XMONAD(1) Tiling Window Manager</h1> <p class="author"></p>-<p class="date">30 September 2018</p>+<p class="date">27 October 2021</p> </header>-<nav id="TOC">+<nav id="TOC" role="doc-toc"> <ul>-<li><a href="#name">Name</a></li>-<li><a href="#description">Description</a></li>-<li><a href="#usage">Usage</a><ul>-<li><a href="#flags">Flags</a></li>+<li><a href="#name" id="toc-name">Name</a></li>+<li><a href="#description" id="toc-description">Description</a></li>+<li><a href="#usage" id="toc-usage">Usage</a>+<ul>+<li><a href="#flags" id="toc-flags">Flags</a></li>+<li><a href="#default-keyboard-bindings"+id="toc-default-keyboard-bindings">Default keyboard bindings</a></li> </ul></li>-<li><a href="#examples">Examples</a></li>-<li><a href="#customization">Customization</a><ul>-<li><a href="#modular-configuration">Modular Configuration</a></li>+<li><a href="#examples" id="toc-examples">Examples</a></li>+<li><a href="#customization" id="toc-customization">Customization</a>+<ul>+<li><a href="#modular-configuration"+id="toc-modular-configuration">Modular Configuration</a></li> </ul></li>-<li><a href="#bugs">Bugs</a></li>+<li><a href="#bugs" id="toc-bugs">Bugs</a></li> </ul> </nav> <h1 id="name">Name</h1> <p>xmonad - Tiling Window Manager</p> <h1 id="description">Description</h1>-<p><em>xmonad</em> 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. <em>xmonad</em> is configured in Haskell, and custom layout algorithms may be implemented by the user in config files. A principle of <em>xmonad</em> is predictability: the user should know in advance precisely the window arrangement that will result from any action.</p>-<p>By default, <em>xmonad</em> 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.</p>-<p>By utilizing the expressivity of a modern functional language with a rich static type system, <em>xmonad</em> 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.</p>+<p><em>xmonad</em> 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. <em>xmonad</em> is configured in Haskell, and custom layout+algorithms may be implemented by the user in config files. A principle+of <em>xmonad</em> is predictability: the user should know in advance+precisely the window arrangement that will result from any action.</p>+<p>By default, <em>xmonad</em> 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.</p>+<p>By utilizing the expressivity of a modern functional language with a+rich static type system, <em>xmonad</em> 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.</p> <h1 id="usage">Usage</h1>-<p><em>xmonad</em> 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.</p>-<p>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.</p>-<p>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 <em>xmonad</em> 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.</p>+<p><em>xmonad</em> 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.</p>+<p>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.</p>+<p>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 <em>xmonad</em> 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.</p> <h2 id="flags">Flags</h2>-<p>xmonad has several flags which you may pass to the executable. These flags are:</p>+<p>xmonad has several flags which you may pass to the executable. These+flags are:</p> <dl> <dt>–recompile</dt>-<dd>Recompiles your configuration in <em>~/.xmonad/xmonad.hs</em>+<dd>+Recompiles your <em>xmonad.hs</em> configuration </dd> <dt>–restart</dt>-<dd>Causes the currently running <em>xmonad</em> process to restart+<dd>+Causes the currently running <em>xmonad</em> process to restart </dd> <dt>–replace</dt>-<dd>Replace the current window manager with xmonad+<dd>+Replace the current window manager with xmonad </dd> <dt>–version</dt>-<dd>Display version of <em>xmonad</em>+<dd>+Display version of <em>xmonad</em> </dd> <dt>–verbose-version</dt>-<dd>Display detailed version of <em>xmonad</em>+<dd>+Display detailed version of <em>xmonad</em> </dd> </dl>-<p>##Default keyboard bindings</p>+<h2 id="default-keyboard-bindings">Default keyboard bindings</h2> <dl> <dt>mod-shift-return</dt>-<dd>Launch terminal+<dd>+Launch terminal </dd> <dt>mod-p</dt>-<dd>Launch dmenu+<dd>+Launch dmenu </dd> <dt>mod-shift-p</dt>-<dd>Launch gmrun+<dd>+Launch gmrun </dd> <dt>mod-shift-c</dt>-<dd>Close the focused window+<dd>+Close the focused window </dd> <dt>mod-space</dt>-<dd>Rotate through the available layout algorithms+<dd>+Rotate through the available layout algorithms </dd> <dt>mod-shift-space</dt>-<dd>Reset the layouts on the current workspace to default+<dd>+Reset the layouts on the current workspace to default </dd> <dt>mod-n</dt>-<dd>Resize viewed windows to the correct size+<dd>+Resize viewed windows to the correct size </dd> <dt>mod-tab</dt>-<dd>Move focus to the next window+<dd>+Move focus to the next window </dd> <dt>mod-shift-tab</dt>-<dd>Move focus to the previous window+<dd>+Move focus to the previous window </dd> <dt>mod-j</dt>-<dd>Move focus to the next window+<dd>+Move focus to the next window </dd> <dt>mod-k</dt>-<dd>Move focus to the previous window+<dd>+Move focus to the previous window </dd> <dt>mod-m</dt>-<dd>Move focus to the master window+<dd>+Move focus to the master window </dd> <dt>mod-return</dt>-<dd>Swap the focused window and the master window+<dd>+Swap the focused window and the master window </dd> <dt>mod-shift-j</dt>-<dd>Swap the focused window with the next window+<dd>+Swap the focused window with the next window </dd> <dt>mod-shift-k</dt>-<dd>Swap the focused window with the previous window+<dd>+Swap the focused window with the previous window </dd> <dt>mod-h</dt>-<dd>Shrink the master area+<dd>+Shrink the master area </dd> <dt>mod-l</dt>-<dd>Expand the master area+<dd>+Expand the master area </dd> <dt>mod-t</dt>-<dd>Push window back into tiling+<dd>+Push window back into tiling </dd> <dt>mod-comma</dt>-<dd>Increment the number of windows in the master area+<dd>+Increment the number of windows in the master area </dd> <dt>mod-period</dt>-<dd>Deincrement the number of windows in the master area+<dd>+Deincrement the number of windows in the master area </dd> <dt>mod-shift-q</dt>-<dd>Quit xmonad+<dd>+Quit xmonad </dd> <dt>mod-q</dt>-<dd>Restart xmonad+<dd>+Restart xmonad </dd> <dt>mod-shift-slash</dt>-<dd>Run xmessage with a summary of the default keybindings (useful for beginners)+<dd>+Run xmessage with a summary of the default keybindings (useful for+beginners) </dd> <dt>mod-question</dt>-<dd>Run xmessage with a summary of the default keybindings (useful for beginners)+<dd>+Run xmessage with a summary of the default keybindings (useful for+beginners) </dd> <dt>mod-[1..9]</dt>-<dd>Switch to workspace N+<dd>+Switch to workspace N </dd> <dt>mod-shift-[1..9]</dt>-<dd>Move client to workspace N+<dd>+Move client to workspace N </dd> <dt>mod-{w,e,r}</dt>-<dd>Switch to physical/Xinerama screens 1, 2, or 3+<dd>+Switch to physical/Xinerama screens 1, 2, or 3 </dd> <dt>mod-shift-{w,e,r}</dt>-<dd>Move client to screen 1, 2, or 3+<dd>+Move client to screen 1, 2, or 3 </dd> <dt>mod-button1</dt>-<dd>Set the window to floating mode and move by dragging+<dd>+Set the window to floating mode and move by dragging </dd> <dt>mod-button2</dt>-<dd>Raise the window to the top of the stack+<dd>+Raise the window to the top of the stack </dd> <dt>mod-button3</dt>-<dd>Set the window to floating mode and resize by dragging+<dd>+Set the window to floating mode and resize by dragging </dd> </dl> <h1 id="examples">Examples</h1>-<p>To use xmonad as your window manager add to your <em>~/.xinitrc</em> file:</p>+<p>To use xmonad as your window manager add to your <em>~/.xinitrc</em>+file:</p> <blockquote> <p>exec xmonad</p> </blockquote> <h1 id="customization">Customization</h1>-<p>xmonad is customized in ~/.xmonad/xmonad.hs, and then restarted with mod-q.</p>-<p>You can find many extensions to the core feature set in the xmonad- contrib package, available through your package manager or from <a href="http://xmonad.org">xmonad.org</a>.</p>+<p>xmonad is customized in your <em>xmonad.hs</em>, and then restarted+with mod-q. You can choose where your configuration file lives by</p>+<ol type="1">+<li>Setting <code>XMONAD_DATA_DIR,</code>+<code>XMONAD_CONFIG_DIR</code>, and <code>XMONAD_CACHE_DIR</code>;+<em>xmonad.hs</em> is then expected to be in+<code>XMONAD_CONFIG_DIR</code>.</li>+<li>Creating <em>xmonad.hs</em> in <em>~/.xmonad</em>.</li>+<li>Creating <em>xmonad.hs</em> in <code>XDG_CONFIG_HOME</code>. Note+that, in this case, xmonad will use <code>XDG_DATA_HOME</code> and+<code>XDG_CACHE_HOME</code> for its data and cache directory+respectively.</li>+</ol>+<p>You can find many extensions to the core feature set in the xmonad-+contrib package, available through your package manager or from <a+href="https://xmonad.org">xmonad.org</a>.</p> <h2 id="modular-configuration">Modular Configuration</h2>-<p>As of <em>xmonad-0.9</em>, any additional Haskell modules may be placed in <em>~/.xmonad/lib/</em> are available in GHC’s searchpath. Hierarchical modules are supported: for example, the file <em>~/.xmonad/lib/XMonad/Stack/MyAdditions.hs</em> could contain:</p>-<div class="sourceCode" id="cb1"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb1-1" data-line-number="1"><span class="kw">module</span> <span class="dt">XMonad.Stack.MyAdditions</span> (function1) <span class="kw">where</span></a>-<a class="sourceLine" id="cb1-2" data-line-number="2"> function1 <span class="fu">=</span> error <span class="st">"function1: Not implemented yet!"</span></a></code></pre></div>-<p>Your xmonad.hs may then import XMonad.Stack.MyAdditions as if that module was contained within xmonad or xmonad-contrib.</p>+<p>As of <em>xmonad-0.9</em>, any additional Haskell modules may be+placed in <em>~/.xmonad/lib/</em> are available in GHC’s searchpath.+Hierarchical modules are supported: for example, the file+<em>~/.xmonad/lib/XMonad/Stack/MyAdditions.hs</em> could contain:</p>+<div class="sourceCode" id="cb1"><pre+class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="kw">module</span> <span class="dt">XMonad.Stack.MyAdditions</span> (function1) <span class="kw">where</span></span>+<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a> function1 <span class="ot">=</span> <span class="fu">error</span> <span class="st">"function1: Not implemented yet!"</span></span></code></pre></div>+<p>Your xmonad.hs may then import XMonad.Stack.MyAdditions as if that+module was contained within xmonad or xmonad-contrib.</p> <h1 id="bugs">Bugs</h1>-<p>Probably. If you find any, please report them to the <a href="https://github.com/xmonad/xmonad/issues">bugtracker</a></p>+<p>Probably. If you find any, please report them to the <a+href="https://github.com/xmonad/xmonad/issues">bugtracker</a></p> </body> </html>
man/xmonad.1.markdown view
@@ -1,6 +1,6 @@ % XMONAD(1) Tiling Window Manager %-% 30 September 2018+% 27 October 2021 # Name @@ -58,7 +58,7 @@ These flags are: --recompile-: Recompiles your configuration in _~/.xmonad/xmonad.hs_+: Recompiles your _xmonad.hs_ configuration --restart : Causes the currently running _xmonad_ process to restart@@ -72,10 +72,101 @@ --verbose-version : Display detailed version of _xmonad_ -##Default keyboard bindings+## Default keyboard bindings -___KEYBINDINGS___+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:@@ -83,9 +174,17 @@ > exec xmonad # Customization-xmonad is customized in ~/.xmonad/xmonad.hs, and then restarted-with mod-q.+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].@@ -107,5 +206,5 @@ # Bugs Probably. If you find any, please report them to the [bugtracker] -[xmonad.org]: http://xmonad.org+[xmonad.org]: https://xmonad.org [bugtracker]: https://github.com/xmonad/xmonad/issues
man/xmonad.hs view
@@ -123,13 +123,13 @@ -- , ((modm , xK_b ), sendMessage ToggleStruts) -- Quit xmonad- , ((modm .|. shiftMask, xK_q ), io (exitWith ExitSuccess))+ , ((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 ), spawn ("echo \"" ++ help ++ "\" | xmessage -file -"))+ , ((modm .|. shiftMask, xK_slash ), xmessage help) ] ++ @@ -154,18 +154,18 @@ ------------------------------------------------------------------------ -- Mouse bindings: default actions bound to mouse events ---myMouseBindings (XConfig {XMonad.modMask = modm}) = M.fromList $+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))+ [ ((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))+ , ((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))+ , ((modm, button3), \w -> focus w >> mouseResizeWindow w+ >> windows W.shiftMaster) -- you may also bind events to the mouse scroll wheel (button4 and button5) ]@@ -293,6 +293,7 @@ "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",
src/XMonad/Config.hs view
@@ -1,5 +1,6 @@ {-# OPTIONS_GHC -fno-warn-missing-signatures -fno-warn-orphans #-} {-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-} ----------------------------------------------------------------------------- -- | -- Module : XMonad.Config@@ -39,7 +40,7 @@ import XMonad.ManageHook import qualified XMonad.StackSet as W import Data.Bits ((.|.))-import Data.Default+import Data.Default.Class import Data.Monoid import qualified Data.Map as M import System.Exit@@ -218,7 +219,7 @@ , ((modMask , xK_period), sendMessage (IncMasterN (-1))) -- %! Deincrement the number of windows in the master area -- quit, or restart- , ((modMask .|. shiftMask, xK_q ), io (exitWith ExitSuccess)) -- %! Quit xmonad+ , ((modMask .|. 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)@@ -239,7 +240,7 @@ , (f, m) <- [(W.view, 0), (W.shift, shiftMask)]] where helpCommand :: X ()- helpCommand = spawn ("echo " ++ show help ++ " | xmessage -file -")+ helpCommand = xmessage help -- | Mouse bindings: default actions bound to mouse events mouseBindings :: XConfig Layout -> M.Map (KeyMask, Button) (Window -> X ())@@ -277,6 +278,7 @@ , 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@@ -296,6 +298,7 @@ "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",
src/XMonad/Core.hs view
@@ -1,5 +1,14 @@-{-# LANGUAGE ExistentialQuantification, FlexibleInstances, GeneralizedNewtypeDeriving,- MultiParamTypeClasses, TypeSynonymInstances, DeriveDataTypeable #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE ViewPatterns #-} ----------------------------------------------------------------------------- -- |@@ -22,26 +31,33 @@ XConf(..), XConfig(..), LayoutClass(..), Layout(..), readsLayout, Typeable, Message, SomeMessage(..), fromMessage, LayoutMessages(..),- StateExtension(..), ExtensionClass(..),+ StateExtension(..), ExtensionClass(..), ConfExtension(..), runX, catchX, userCode, userCodeDef, io, catchIO, installSignalHandlers, uninstallSignalHandlers, withDisplay, withWindowSet, isRoot, runOnWorkspaces,- getAtom, spawn, spawnPID, xfork, recompile, trace, whenJust, whenX,- getXMonadDir, getXMonadCacheDir, getXMonadDataDir, stateFileName,+ getAtom, spawn, spawnPID, xfork, xmessage, recompile, trace, whenJust, whenX, ifM,+ getXMonadDir, getXMonadCacheDir, getXMonadDataDir, stateFileName, binFileName, atom_WM_STATE, atom_WM_PROTOCOLS, atom_WM_DELETE_WINDOW, atom_WM_TAKE_FOCUS, withWindowAttributes,- ManageHook, Query(..), runQuery+ ManageHook, Query(..), runQuery, Directories'(..), Directories, getDirectories, ) where import XMonad.StackSet hiding (modify) import Prelude-import Control.Exception.Extensible (fromException, try, bracket, throw, finally, SomeException(..))-import qualified Control.Exception.Extensible as E-import Control.Applicative(Applicative, pure, (<$>), (<*>))+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, void, when)+import Data.Char (isSpace) import Data.Semigroup-import Data.Default+import Data.Traversable (for)+import Data.Time.Clock (UTCTime)+import Data.Default.Class+import System.Environment (lookupEnv)+import Data.List (isInfixOf, intercalate, (\\)) import System.FilePath import System.IO import System.Info@@ -56,10 +72,8 @@ import Graphics.X11.Xlib import Graphics.X11.Xlib.Extras (getWindowAttributes, WindowAttributes, Event) import Data.Typeable-import Data.List ((\\)) import Data.Maybe (isJust,fromMaybe)-import Data.Monoid hiding ((<>))-import System.Environment (lookupEnv)+import Data.Monoid (Ap(..)) import qualified Data.Map as M import qualified Data.Set as S@@ -93,8 +107,8 @@ , mousePosition :: !(Maybe (Position, Position)) -- ^ position of the mouse according to -- the event currently being processed- , currentEvent :: !(Maybe Event)- -- ^ event currently being processed+ , currentEvent :: !(Maybe Event) -- ^ event currently being processed+ , directories :: !Directories -- ^ directories to use } -- todo, better name@@ -122,6 +136,11 @@ , 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. } @@ -135,7 +154,8 @@ newtype ScreenId = S Int deriving (Eq,Ord,Show,Read,Enum,Num,Integral,Real) -- | The 'Rectangle' with screen dimensions-data ScreenDetail = SD { screenRect :: !Rectangle } deriving (Eq,Show, Read)+newtype ScreenDetail = SD { screenRect :: Rectangle }+ deriving (Eq,Show, Read) ------------------------------------------------------------------------ @@ -148,18 +168,8 @@ -- instantiated on 'XConf' and 'XState' automatically. -- newtype X a = X (ReaderT XConf (StateT XState IO) a)- deriving (Functor, Monad, MonadFail, MonadIO, MonadState XState, MonadReader XConf, Typeable)--instance Applicative X where- pure = return- (<*>) = ap--instance Semigroup a => Semigroup (X a) where- (<>) = liftM2 (<>)--instance (Monoid a) => Monoid (X a) where- mempty = return mempty- mappend = liftM2 mappend+ deriving (Functor, Applicative, Monad, MonadFail, MonadIO, MonadState XState, MonadReader XConf)+ deriving (Semigroup, Monoid) via Ap X a instance Default a => Default (X a) where def = return def@@ -167,16 +177,10 @@ type ManageHook = Query (Endo WindowSet) newtype Query a = Query (ReaderT Window X a) deriving (Functor, Applicative, Monad, MonadReader Window, MonadIO)+ deriving (Semigroup, Monoid) via Ap Query a runQuery :: Query a -> Window -> X a-runQuery (Query m) w = runReaderT m w--instance Semigroup a => Semigroup (Query a) where- (<>) = liftM2 (<>)--instance Monoid a => Monoid (Query a) where- mempty = return mempty- mappend = liftM2 mappend+runQuery (Query m) = runReaderT m instance Default a => Default (Query a) where def = return def@@ -193,7 +197,7 @@ st <- get c <- ask (a, s') <- io $ runX c st job `E.catch` \e -> case fromException e of- Just x -> throw e `const` (x `asTypeOf` ExitSuccess)+ Just (_ :: ExitCode) -> throw e _ -> do hPrint stderr e; runX c st errcase put s' return a@@ -201,12 +205,12 @@ -- | Execute the argument, catching all exceptions. Either this function or -- 'catchX' should be used at all callsites of user customized code. userCode :: X a -> X (Maybe a)-userCode a = catchX (Just `liftM` a) (return Nothing)+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 `liftM` userCode a+userCodeDef defValue a = fromMaybe defValue <$> userCode a -- --------------------------------------------------------------------- -- Convenient wrappers to state@@ -227,7 +231,7 @@ -- | True if the given window is the root window isRoot :: Window -> X Bool-isRoot w = (w==) <$> asks theRoot+isRoot w = asks $ (w ==) . theRoot -- | Wrapper for the common case of atom internment getAtom :: String -> X Atom@@ -255,14 +259,18 @@ -- | Every layout must be an instance of 'LayoutClass', which defines -- the basic layout operations along with a sensible default for each. ----- Minimal complete definition:+-- 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'), and+-- * 'runLayout' || (('doLayout' || 'pureLayout') && 'emptyLayout') -- -- * 'handleMessage' || 'pureMessage' ----- You should also strongly consider implementing 'description',--- although it is not required.+-- * 'description' -- -- Note that any code which /uses/ 'LayoutClass' methods should only -- ever call 'runLayout', 'handleMessage', and 'description'! In@@ -271,7 +279,7 @@ -- 'runLayout', 'handleMessage', and so on. This ensures that the -- proper methods will be used, regardless of the particular methods -- that any 'LayoutClass' instance chooses to define.-class Show (layout a) => LayoutClass layout a where+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@@ -373,12 +381,12 @@ -- layouts) should consider handling. data LayoutMessages = Hide -- ^ sent when a layout becomes non-visible | ReleaseResources -- ^ sent when xmonad is exiting or restarting- deriving (Typeable, Eq)+ deriving Eq instance Message LayoutMessages -- ------------------------------------------------------------------------ Extensible state+-- Extensible state/config -- -- | Every module must make the data it wants to store@@ -386,6 +394,7 @@ -- -- 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@@ -404,10 +413,17 @@ | 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+-- General utilities++-- | 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++-- | Lift an 'IO' action into the 'X' monad io :: MonadIO m => IO a -> m a io = liftIO @@ -421,7 +437,7 @@ -- -- Note this function assumes your locale uses utf8. spawn :: MonadIO m => String -> m ()-spawn x = spawnPID x >> return ()+spawn x = void $ spawnPID x -- | Like 'spawn', but returns the 'ProcessID' of the launched application spawnPID :: MonadIO m => String -> m ProcessID@@ -435,10 +451,25 @@ 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 ()@@ -449,138 +480,362 @@ $ current ws : visible ws modify $ \s -> s { windowset = ws { current = c, visible = v, hidden = h } } --- | Return the path to the xmonad configuration directory. 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.------ Several directories are considered. In order of--- preference:+-- | All the directories that xmonad will use. They will be used for+-- the following purposes: ----- 1. The directory specified in the @XMONAD_CONFIG_DIR@ environment variable.--- 2. The @~\/.xmonad@ directory.--- 3. The @XDG_CONFIG_HOME/xmonad@ directory.+-- * @dataDir@: This directory is used by XMonad to store data files+-- such as the run-time state file. ----- The first directory that exists will be used. If none of the--- directories exist then (1) will be used if it is set, otherwise (2)--- will be used. Either way, a directory will be created if necessary.-getXMonadDir :: MonadIO m => m String-getXMonadDir =- findFirstDirWithEnv "XMONAD_CONFIG_DIR"- [ getAppUserDataDirectory "xmonad"- , getXDGDirectory XDGConfig "xmonad"- ]---- | Return the path to the xmonad cache directory. This directory is--- used to store temporary files that can easily be recreated. For--- example, the XPrompt history 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. ----- Several directories are considered. In order of preference:+-- * @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. ----- 1. The directory specified in the @XMONAD_CACHE_DIR@ environment variable.--- 2. The @~\/.xmonad@ directory.--- 3. The @XDG_CACHE_HOME/xmonad@ directory.+-- For how these directories are chosen, see 'getDirectories'. ----- The first directory that exists will be used. If none of the--- directories exist then (1) will be used if it is set, otherwise (2)--- will be used. Either way, a directory will be created if necessary.-getXMonadCacheDir :: MonadIO m => m String-getXMonadCacheDir =- findFirstDirWithEnv "XMONAD_CACHE_DIR"- [ getAppUserDataDirectory "xmonad"- , getXDGDirectory XDGCache "xmonad"- ]+data Directories' a = Directories+ { dataDir :: !a+ , cfgDir :: !a+ , cacheDir :: !a+ }+ deriving (Show, Functor, Foldable, Traversable) --- | Return the path to the xmonad data directory. This directory is--- used by XMonad to store data files such as the run-time state file--- and the configuration binary generated by GHC.+-- | 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: ----- Several directories are considered. In order of preference:+-- 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. ----- 1. The directory specified in the @XMONAD_DATA_DIR@ environment variable.--- 2. The @~\/.xmonad@ directory.--- 3. The @XDG_DATA_HOME/xmonad@ directory.+-- The xmonad configuration file (or the build script, if present) is+-- always assumed to be in @cfgDir@. ----- The first directory that exists will be used. If none of the--- directories exist then (1) will be used if it is set, otherwise (2)--- will be used. Either way, a directory will be created if necessary.-getXMonadDataDir :: MonadIO m => m String-getXMonadDataDir =- findFirstDirWithEnv "XMONAD_DATA_DIR"- [ getAppUserDataDirectory "xmonad"- , getXDGDirectory XDGData "xmonad"- ]+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 --- | Helper function that will find the first existing directory and--- return its path. If none of the directories can be found, create--- and return the first from the list. If the list is empty this--- function returns the historical @~\/.xmonad@ directory.-findFirstDirOf :: MonadIO m => [IO FilePath] -> m FilePath-findFirstDirOf [] = findFirstDirOf [getAppUserDataDirectory "xmonad"]-findFirstDirOf possibles = do- found <- go possibles+ -- | 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" - case found of- Just path -> return path- Nothing -> do- primary <- io (head possibles)- io (createDirectoryIfMissing True primary)- return primary+ -- 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, nixFlakeFileName, nixDefaultFileName :: Directories -> FilePath+buildScriptFileName Directories{ cfgDir } = cfgDir </> "build"+stackYamlFileName Directories{ cfgDir } = cfgDir </> "stack.yaml"+nixFlakeFileName Directories{ cfgDir } = cfgDir </> "flake.nix"+nixDefaultFileName Directories{ cfgDir } = cfgDir </> "default.nix"++-- | Compilation method for xmonad configuration.+data Compile+ = CompileGhc+ | CompileCabal+ | CompileStackGhc FilePath+ | CompileNixFlake+ | CompileNixDefault+ | 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 <|> tryNixFlake <|> tryNixDefault <|> tryCabal <|> useGhc where- go [] = return Nothing- go (x:xs) = do- dir <- io x- exists <- io (doesDirectoryExist dir)- if exists then return (Just dir) else go xs+ buildScript = buildScriptFileName dirs+ stackYaml = stackYamlFileName dirs+ flakeNix = nixFlakeFileName dirs+ defaultNix = nixDefaultFileName dirs --- | Simple wrapper around @findFirstDirOf@ that allows the primary--- path to be specified by an environment variable.-findFirstDirWithEnv :: MonadIO m => String -> [IO FilePath] -> m FilePath-findFirstDirWithEnv envName paths = do- envPath' <- io (getEnv envName)+ 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 - case envPath' of- Nothing -> findFirstDirOf paths- Just envPath -> findFirstDirOf (return envPath:paths)+ tryNixFlake = do+ guard =<< doesFileExist flakeNix+ canonNixFlake <- canonicalizePath flakeNix+ trace $ "XMonad will use nix flake at " <> show canonNixFlake <> " to recompile"+ pure CompileNixFlake --- | Helper function to retrieve the various XDG directories.--- This has been based on the implementation shipped with GHC version 8.0.1 or--- higher. Put here to preserve compatibility with older GHC versions.-getXDGDirectory :: XDGDirectory -> FilePath -> IO FilePath-getXDGDirectory xdgDir suffix =- normalise . (</> suffix) <$>- case xdgDir of- XDGData -> get "XDG_DATA_HOME" ".local/share"- XDGConfig -> get "XDG_CONFIG_HOME" ".config"- XDGCache -> get "XDG_CACHE_HOME" ".cache"+ tryNixDefault = do+ guard =<< doesFileExist defaultNix+ canonNixDefault <- canonicalizePath defaultNix+ trace $ "XMonad will use nix file at " <> show canonNixDefault <> " to recompile"+ pure CompileNixDefault++ tryStack = do+ guard =<< doesFileExist stackYaml+ canonStackYaml <- canonicalizePath stackYaml+ trace $ "XMonad will use stack ghc --stack-yaml " <> show canonStackYaml <> " to recompile."+ pure $ CompileStackGhc canonStackYaml++ tryCabal = let cwd = cfgDir dirs in listCabalFiles cwd >>= \ case+ [] -> do+ empty+ [name] -> do+ trace $ "XMonad will use " <> show name <> " to recompile."+ pure CompileCabal+ _ : _ : _ -> do+ trace $ "XMonad will not use cabal, because there are multiple cabal files in " <> show cwd <> "."+ empty++ useGhc = do+ trace $ "XMonad will use ghc to recompile, because none of "+ <> intercalate ", "+ [ show buildScript+ , show stackYaml+ , show flakeNix+ , show defaultNix+ ] <> " nor a suitable .cabal file exist."+ pure CompileGhc++listCabalFiles :: FilePath -> IO [FilePath]+listCabalFiles dir = map (dir </>) . Prelude.filter isCabalFile <$> listFiles dir++isCabalFile :: FilePath -> Bool+isCabalFile file = case splitExtension file of+ (name, ".cabal") -> not (null name)+ _ -> False++listFiles :: FilePath -> IO [FilePath]+listFiles dir = getDirectoryContents dir >>= filterM (doesFileExist . (dir </>))++-- | Determine whether or not the file found at the provided filepath is executable.+isExecutable :: FilePath -> IO Bool+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- get name fallback = do- env <- lookupEnv name- case env of- Nothing -> fallback'- Just path- | isRelative path -> fallback'- | otherwise -> return path- where- fallback' = (</> fallback) <$> getHomeDirectory-data XDGDirectory = XDGData | XDGConfig | XDGCache+ 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 _ CompileCabal = return True+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 CompileNixFlake{} = True <$ trace "XMonad recompiling because flake recompilation is being used."+shouldCompile _dirs CompileNixDefault{} = True <$ trace "XMonad recompiling because nix recompilation is being used."+shouldCompile _dirs CompileScript{} =+ True <$ trace "XMonad recompiling because a custom build script is being used." --- | Get the name of the file used to store the xmonad window state.-stateFileName :: (Functor m, MonadIO m) => m FilePath-stateFileName = (</> "xmonad.state") <$> getXMonadDataDir+getModTime :: FilePath -> IO (Maybe UTCTime)+getModTime f = E.catch (Just <$> getModificationTime f) (\(SomeException _) -> return Nothing) --- | 'recompile force', recompile the xmonad configuration file when--- any of the following apply:+-- | Compile the configuration.+compile :: Directories -> Compile -> IO ExitCode+compile dirs method =+ bracket_ uninstallSignalHandlers installSignalHandlers $+ withFile (errFileName dirs) WriteMode $ \err -> do+ let run = runProc err+ case method of+ CompileGhc -> do+ ghc <- fromMaybe "ghc" <$> lookupEnv "XMONAD_GHC"+ run ghc ghcArgs+ CompileCabal -> run "cabal" ["build"] .&&. copyBinary+ where+ copyBinary :: IO ExitCode+ copyBinary = readProc err "cabal" ["-v0", "list-bin", "."] >>= \ case+ Left status -> return status+ Right (trim -> path) -> copyBinaryFrom path+ CompileStackGhc stackYaml ->+ run "stack" ["build", "--silent", "--stack-yaml", stackYaml] .&&.+ run "stack" ("ghc" : "--stack-yaml" : stackYaml : "--" : ghcArgs)+ CompileNixFlake ->+ run "nix" ["build"] >>= andCopyFromResultDir+ CompileNixDefault ->+ run "nix-build" [] >>= andCopyFromResultDir+ CompileScript script ->+ run script [binFileName dirs]+ where+ cwd :: FilePath+ cwd = cfgDir dirs++ ghcArgs :: [String]+ ghcArgs = [ "--make"+ , "xmonad.hs"+ , "-i" -- only look in @lib@+ , "-ilib"+ , "-fforce-recomp"+ , "-main-is", "main"+ , "-v0"+ , "-outputdir", buildDirName dirs+ , "-o", binFileName dirs+ ]++ andCopyFromResultDir :: ExitCode -> IO ExitCode+ andCopyFromResultDir exitCode = do+ if exitCode == ExitSuccess then copyFromResultDir else return exitCode++ findM :: (Monad m, Foldable t) => (a -> m Bool) -> t a -> m (Maybe a)+ findM p = foldr (\x -> ifM (p x) (pure $ Just x)) (pure Nothing)++ catchAny :: IO a -> (SomeException -> IO a) -> IO a+ catchAny = E.catch++ copyFromResultDir :: IO ExitCode+ copyFromResultDir = do+ let binaryDirectory = cfgDir dirs </> "result" </> "bin"+ binFiles <- map (binaryDirectory </>) <$> catchAny (listDirectory binaryDirectory) (\_ -> return [])+ mfilepath <- findM isExecutable binFiles+ case mfilepath of+ Just filepath -> copyBinaryFrom filepath+ Nothing -> return $ ExitFailure 1++ copyBinaryFrom :: FilePath -> IO ExitCode+ copyBinaryFrom filepath = copyFile filepath (binFileName dirs) >> return ExitSuccess++ -- waitForProcess =<< System.Process.runProcess, but without closing the err handle+ runProc :: Handle -> String -> [String] -> IO ExitCode+ runProc err exe args = do+ (Nothing, Nothing, Nothing, h) <- createProcess_ "runProc" =<< mkProc err exe args+ waitForProcess h++ readProc :: Handle -> String -> [String] -> IO (Either ExitCode String)+ readProc err exe args = do+ spec <- mkProc err exe args+ (Nothing, Just out, Nothing, h) <- createProcess_ "readProc" spec{ std_out = CreatePipe }+ result <- hGetContents out+ hPutStr err result >> hFlush err+ waitForProcess h >>= \ case+ ExitSuccess -> return $ Right result+ status -> return $ Left status++ mkProc :: Handle -> FilePath -> [FilePath] -> IO CreateProcess+ mkProc err exe args = do+ hPutStrLn err $ unwords $ "$" : exe : args+ hFlush err+ return (proc exe args){ cwd = Just cwd, std_err = UseHandle err }++ (.&&.) :: Monad m => m ExitCode -> m ExitCode -> m ExitCode+ 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'+-- * force is 'True' ----- * the xmonad executable does not exist+-- * 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).+-- * 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. --@@ -590,106 +845,21 @@ -- -- 'False' is returned if there are compilation errors. ---recompile :: MonadIO m => Bool -> m Bool-recompile force = io $ do- cfgdir <- getXMonadDir- datadir <- getXMonadDataDir- let binn = "xmonad-"++arch++"-"++os- bin = datadir </> binn- err = datadir </> "xmonad.errors"- src = cfgdir </> "xmonad.hs"- lib = cfgdir </> "lib"- buildscript = cfgdir </> "build"-- libTs <- mapM getModTime . Prelude.filter isSource =<< allFiles lib- srcT <- getModTime src- binT <- getModTime bin-- useBuildscript <- do- exists <- doesFileExist buildscript- if exists- then do- isExe <- isExecutable buildscript- if isExe- then do- trace $ "XMonad will use build script at " ++ show buildscript ++ " to recompile."- return True- else do- trace $ unlines- [ "XMonad will not use build script, because " ++ show buildscript ++ " is not executable."- , "Suggested resolution to use it: chmod u+x " ++ show buildscript- ]- return False- else do- trace $- "XMonad will use ghc to recompile, because " ++ show buildscript ++ " does not exist."- return False-- shouldRecompile <-- if useBuildscript || force- then return True- else if any (binT <) (srcT : libTs)- then do- trace "XMonad doing recompile because some files have changed."- return True- else do- 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."- return False-- if shouldRecompile+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- -- temporarily disable SIGCHLD ignoring:- uninstallSignalHandlers- status <- bracket (openFile err WriteMode) hClose $ \errHandle ->- waitForProcess =<< if useBuildscript- then compileScript bin cfgdir buildscript errHandle- else compileGHC bin cfgdir errHandle-- -- re-enable SIGCHLD:- installSignalHandlers-- -- now, if it fails, run xmessage to let the user know:+ status <- compile dirs method if status == ExitSuccess- then trace "XMonad recompilation process exited with success!"- else do- ghcErr <- readFile err- let msg = unlines $- ["Error detected while loading xmonad configuration file: " ++ src]- ++ lines (if null ghcErr then show status else ghcErr)- ++ ["","Please check the file for errors."]- -- nb, the ordering of printing, then forking, is crucial due to- -- lazy evaluation- hPutStrLn stderr msg- forkProcess $ executeFile "xmessage" True ["-default", "okay", replaceUnicode msg] Nothing- return ()- return (status == ExitSuccess)- else return True- where getModTime f = E.catch (Just <$> getModificationTime f) (\(SomeException _) -> return Nothing)- isSource = flip elem [".hs",".lhs",".hsc"] . takeExtension- isExecutable f = E.catch (executable <$> getPermissions f) (\(SomeException _) -> return False)- 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- -- Replace some of the unicode symbols GHC uses in its output- replaceUnicode = map $ \c -> case c of- '\8226' -> '*' -- •- '\8216' -> '`' -- ‘- '\8217' -> '`' -- ’- _ -> c- compileGHC bin dir errHandle =- runProcess "ghc" ["--make"- , "xmonad.hs"- , "-i"- , "-ilib"- , "-fforce-recomp"- , "-main-is", "main"- , "-v0"- , "-o", bin- ] (Just dir) Nothing Nothing Nothing (Just errHandle)- compileScript bin dir script errHandle =- runProcess script [bin] (Just dir) Nothing Nothing Nothing (Just errHandle)+ 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 ()@@ -721,3 +891,6 @@ installHandler openEndedPipe Default Nothing installHandler sigCHLD Default Nothing return ()++trim :: String -> String+trim = reverse . dropWhile isSpace . reverse . dropWhile isSpace
src/XMonad/Layout.hs view
@@ -1,4 +1,6 @@-{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, PatternGuards, TypeSynonymInstances, DeriveDataTypeable #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE PatternGuards #-} -- -------------------------------------------------------------------------- -- |@@ -8,7 +10,7 @@ -- -- Maintainer : spencerjanssen@gmail.com -- Stability : unstable--- Portability : not portable, Typeable deriving, mtl, posix+-- Portability : not portable, mtl, posix -- -- The collection of core layouts. --@@ -16,7 +18,7 @@ module XMonad.Layout ( Full(..), Tall(..), Mirror(..),- Resize(..), IncMasterN(..), Choose, (|||), ChangeLayout(..),+ Resize(..), IncMasterN(..), Choose(..), (|||), CLR(..), ChangeLayout(..), JumpToLayout(..), mirrorRect, splitVertically, splitHorizontally, splitHorizontallyBy, splitVerticallyBy, @@ -27,6 +29,7 @@ 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@@ -35,10 +38,10 @@ ------------------------------------------------------------------------ -- | Change the size of the master pane.-data Resize = Shrink | Expand deriving Typeable+data Resize = Shrink | Expand -- | Increase the number of clients in the master pane.-data IncMasterN = IncMasterN !Int deriving Typeable+newtype IncMasterN = IncMasterN Int instance Message Resize instance Message IncMasterN@@ -59,9 +62,13 @@ -- 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+ pureLayout (Tall nmaster _ frac) r s+ | frac == 0 = drop nmaster layout+ | frac == 1 = take nmaster layout+ | otherwise = layout where ws = W.integrate s rs = tile frac r nmaster (length ws)+ layout = zip ws rs pureMessage (Tall nmaster delta frac) m = msum [fmap resize (fromMessage m)@@ -77,7 +84,7 @@ -- algorithm. -- -- The screen is divided into two panes. All clients are--- then partioned between these two panes. One pane, the master, by+-- 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@@ -131,23 +138,53 @@ -- LayoutClass selection manager -- Layouts that transition between other layouts --- | Messages to change the current layout.-data ChangeLayout = FirstLayout | NextLayout deriving (Eq, Show, Typeable)+-- | 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 L+(|||) = Choose CL infixr 5 ||| -- | A layout that allows users to switch between various layout options.-data Choose l r a = Choose LR (l a) (r a) deriving (Read, Show)+data Choose l r a = Choose CLR (l a) (r a) deriving (Read, Show) --- | Are we on the left or right sub-layout?-data LR = L | R deriving (Read, Show, Eq)+-- | Choose the current sub-layout (left or right) in 'Choose'.+data CLR = CL | CR deriving (Read, Show, Eq) -data NextNoWrap = NextNoWrap deriving (Eq, Show, Typeable)+data NextNoWrap = NextNoWrap deriving (Eq, Show) instance Message NextNoWrap -- | A small wrapper around handleMessage, as it is tedious to write@@ -159,26 +196,26 @@ -- new structure if any fields have changed, and performs any necessary cleanup -- on newly non-visible layouts. choose :: (LayoutClass l a, LayoutClass r a)- => Choose l r a-> LR -> Maybe (l a) -> Maybe (r a) -> X (Maybe (Choose l r a))+ => Choose 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- (L, R) -> (hide l' , return r')- (R, L) -> (return l', hide r' )- (_, _) -> (return l', return r')- f (x,y) = fmap Just $ liftM2 (Choose d') x y- hide x = fmap (fromMaybe x) $ handle x Hide+ (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 L l r) ms) =- fmap (second . fmap $ flip (Choose L) r) . runLayout (W.Workspace i l ms)- runLayout (W.Workspace i (Choose R l r) ms) =- fmap (second . fmap $ Choose R l) . runLayout (W.Workspace i r ms)+ 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 L l _) = description l- description (Choose R _ r) = description r+ description (Choose CL l _) = description l+ description (Choose CR _ r) = description r handleMessage lr m | Just NextLayout <- fromMessage m = do mlr' <- handle lr NextNoWrap@@ -186,25 +223,36 @@ handleMessage c@(Choose d l r) m | Just NextNoWrap <- fromMessage m = case d of- L -> do+ CL -> do ml <- handle l NextNoWrap case ml of- Just _ -> choose c L ml Nothing- Nothing -> choose c R Nothing =<< handle r FirstLayout+ Just _ -> choose c CL ml Nothing+ Nothing -> choose c CR Nothing =<< handle r FirstLayout - R -> choose c R Nothing =<< handle r NextNoWrap+ CR -> choose c CR Nothing =<< handle r NextNoWrap handleMessage c@(Choose _ l _) m | Just FirstLayout <- fromMessage m =- flip (choose c L) Nothing =<< handle l FirstLayout+ 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- L -> handleMessage l m- R -> return Nothing+ CL -> handleMessage l m+ CR -> return Nothing mr' <- case d of- L -> return Nothing- R -> handleMessage r m+ CL -> return Nothing+ CR -> handleMessage r m choose c d ml' mr'
src/XMonad/Main.hs view
@@ -1,4 +1,6 @@-{-# LANGUAGE MultiParamTypeClasses, FlexibleContexts #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE MultiParamTypeClasses #-}+ ---------------------------------------------------------------------------- -- | -- Module : XMonad.Main@@ -13,18 +15,19 @@ -- ----------------------------------------------------------------------------- -module XMonad.Main (xmonad, launch) where+module XMonad.Main (xmonad, buildLaunch, launch) where import System.Locale.SetLocale-import qualified Control.Exception.Extensible as E+import qualified Control.Exception as E import Data.Bits import Data.List ((\\))-import Data.Function+import Data.Foldable (traverse_) import qualified Data.Map as M import qualified Data.Set as S import Control.Monad.Reader import Control.Monad.State-import Data.Maybe (fromMaybe)+import Control.Monad (filterM, guard, unless, void, when, forever)+import Data.Maybe (fromMaybe, isJust) import Data.Monoid (getAll) import Graphics.X11.Xlib hiding (refreshKeyboardMapping)@@ -39,7 +42,7 @@ import System.IO import System.Directory import System.Info-import System.Environment+import System.Environment (getArgs, getProgName, withArgs) import System.Posix.Process (executeFile) import System.Exit (exitFailure) import System.FilePath@@ -48,6 +51,7 @@ import Data.Version (showVersion) import Graphics.X11.Xinerama (compiledWithXinerama)+import Graphics.X11.Xrandr (xrrQueryExtension, xrrUpdateConfiguration) ------------------------------------------------------------------------ @@ -59,17 +63,17 @@ xmonad conf = do installSignalHandlers -- important to ignore SIGCHLD to avoid zombies + dirs <- getDirectories let launch' args = do- catchIO buildLaunch- conf' @ XConfig { layoutHook = Layout l }+ catchIO (buildLaunch dirs)+ conf'@XConfig { layoutHook = Layout l } <- handleExtraArgs conf args conf{ layoutHook = Layout (layoutHook conf) }- withArgs [] $ launch (conf' { layoutHook = l })+ withArgs [] $ launch (conf' { layoutHook = l }) dirs args <- getArgs case args of- ("--resume": ws : xs : args') -> migrateState ws xs >> launch' args' ["--help"] -> usage- ["--recompile"] -> recompile True >>= flip unless exitFailure+ ["--recompile"] -> recompile dirs True >>= flip unless exitFailure ["--restart"] -> sendRestart ["--version"] -> putStrLn $ unwords shortVersion ["--verbose-version"] -> putStrLn . unwords $ shortVersion ++ longVersion@@ -86,14 +90,14 @@ usage = do self <- getProgName putStr . unlines $- concat ["Usage: ", self, " [OPTION]"] :- "Options:" :- " --help Print this message" :- " --version Print the version number" :- " --recompile Recompile your ~/.xmonad/xmonad.hs" :- " --replace Replace the running window manager with xmonad" :- " --restart Request a running xmonad process to restart" :- []+ [ "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@@ -111,40 +115,21 @@ -- -- * Missing XMonad\/XMonadContrib modules due to ghc upgrade ---buildLaunch :: IO ()-buildLaunch = do+buildLaunch :: Directories -> IO ()+buildLaunch dirs = do whoami <- getProgName- let compiledConfig = "xmonad-"++arch++"-"++os+ let bin = binFileName dirs+ let compiledConfig = takeFileName bin unless (whoami == compiledConfig) $ do trace $ concat- [ "XMonad is recompiling and replacing itself another XMonad process because the current process is called "+ [ "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 False- dir <- getXMonadDataDir+ recompile dirs False args <- getArgs- executeFile (dir </> compiledConfig) 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 0 currentTime- 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+ executeFile bin False args Nothing -- | Entry point into xmonad for custom builds. --@@ -166,8 +151,8 @@ -- 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 -> IO ()-launch initxmc = do+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@@ -192,12 +177,12 @@ xinesc <- getCleanedScreenInfo dpy - nbc <- do v <- initColor dpy $ normalBorderColor xmc- ~(Just nbc_) <- initColor dpy $ normalBorderColor Default.def+ 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+ Just fbc_ <- initColor dpy $ focusedBorderColor Default.def return (fromMaybe fbc_ v) hSetBuffering stdout NoBuffering@@ -216,7 +201,9 @@ , buttonActions = mouseBindings xmc xmc , mouseFocused = False , mousePosition = Nothing- , currentEvent = Nothing }+ , currentEvent = Nothing+ , directories = drs+ } st = XState { windowset = initialWinset@@ -231,7 +218,7 @@ runX cf st $ do -- check for serialized state in a file. serializedSt <- do- path <- stateFileName+ path <- asks $ stateFileName . directories exists <- io (doesFileExist path) if exists then readStateFile initxmc else return Nothing @@ -239,7 +226,7 @@ let extst = maybe M.empty extensibleState serializedSt modify (\s -> s {extensibleState = extst}) - setNumlockMask+ cacheNumlockMask grabKeys grabButtons @@ -260,8 +247,11 @@ userCode $ startupHook initxmc + rrData <- io $ xrrQueryExtension dpy+ let rrUpdate = when (isJust rrData) . void . xrrUpdateConfiguration+ -- main loop, for all you HOF/recursion fans out there.- forever $ prehandle =<< io (nextEvent dpy e >> getEvent e)+ forever $ prehandle =<< io (nextEvent dpy e >> rrUpdate e >> getEvent e) return () where@@ -310,16 +300,21 @@ -- window destroyed, unmanage it -- window gone, unmanage it-handle (DestroyWindowEvent {ev_window = w}) = whenX (isClient w) $ do+-- 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)+ if synthetic || e == 0 then unmanage w else modify (\s -> s { waitingUnmap = M.update mpred w (waitingUnmap s) }) where mpred 1 = Nothing@@ -329,7 +324,7 @@ handle e@(MappingNotifyEvent {}) = do io $ refreshKeyboardMapping e when (ev_request e `elem` [mappingKeyboard, mappingModifier]) $ do- setNumlockMask+ cacheNumlockMask grabKeys -- handle button release, which may finish dragging.@@ -417,7 +412,7 @@ handle e@ClientMessageEvent { ev_message_type = mt } = do a <- getAtom "XMONAD_RESTART"- if (mt == a)+ if mt == a then restart "xmonad" True else broadcastMessage e @@ -448,35 +443,14 @@ skip :: E.SomeException -> IO Bool skip _ = return False -setNumlockMask :: X ()-setNumlockMask = do- dpy <- asks display- ms <- io $ getModifierMapping dpy- xs <- sequence [ do- ks <- io $ keycodeToKeysym dpy kc 0- if ks == xK_Num_Lock- then return (setBit 0 (fromIntegral m))- else return (0 :: KeyMask)- | (m, kcs) <- ms, kc <- kcs, kc /= 0]- modify (\s -> s { numberlockMask = foldr (.|.) 0 xs })- -- | Grab the keys back grabKeys :: X () grabKeys = do XConf { display = dpy, theRoot = rootw } <- ask- let grab kc m = io $ grabKey dpy kc m rootw True grabModeAsync grabModeAsync- (minCode, maxCode) = displayKeycodes dpy- allCodes = [fromIntegral minCode .. fromIntegral maxCode] io $ ungrabKey dpy anyKey anyModifier rootw- ks <- asks keyActions- -- build a map from keysyms to lists of keysyms (doing what- -- XGetKeyboardMapping would do if the X11 package bound it)- syms <- forM allCodes $ \code -> io (keycodeToKeysym dpy code 0)- let keysymMap = M.fromListWith (++) (zip syms [[code] | code <- allCodes])- keysymToKeycodes sym = M.findWithDefault [] sym keysymMap- forM_ (M.keys ks) $ \(mask,sym) ->- forM_ (keysymToKeycodes sym) $ \kc ->- mapM_ (grab kc . (mask .|.)) =<< extraModifiers+ 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 ()@@ -487,37 +461,4 @@ 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+ mapM_ (\(m,b) -> mapM_ (grab b . (m .|.)) ems) (M.keys ba)
src/XMonad/ManageHook.hs view
@@ -1,5 +1,3 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}- ----------------------------------------------------------------------------- -- | -- Module : XMonad.ManageHook@@ -8,7 +6,6 @@ -- -- Maintainer : spencerjanssen@gmail.com -- Stability : unstable--- Portability : not portable, uses cunning newtype deriving -- -- An EDSL for ManageHooks --@@ -21,13 +18,13 @@ import XMonad.Core import Graphics.X11.Xlib.Extras import Graphics.X11.Xlib (Display, Window, internAtom, wM_NAME)-import Control.Exception.Extensible (bracket, SomeException(..))-import qualified Control.Exception.Extensible as E+import Control.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)+import XMonad.Operations (floatLocation, reveal, isFixedSizeOrTransient) -- | Lift an 'X' action to a 'Query'. liftX :: X a -> Query a@@ -61,13 +58,14 @@ -- | '&&' lifted to a 'Monad'. (<&&>) :: Monad m => m Bool -> m Bool -> m Bool-(<&&>) = liftM2 (&&)+x <&&> y = ifM x y (pure False) -- | '||' lifted to a 'Monad'. (<||>) :: Monad m => m Bool -> m Bool -> m Bool-(<||>) = liftM2 (||)+x <||> y = ifM x (pure True) y --- | Return the window title.+-- | Return the window title; i.e., the string returned by @_NET_WM_NAME@,+-- or failing that, the string returned by @WM_NAME@. title :: Query String title = ask >>= \w -> liftX $ do d <- asks display@@ -76,10 +74,11 @@ (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+ return $ fromMaybe "" $ listToMaybe l io $ bracket getProp (xFree . tp_value) extract `E.catch` \(SomeException _) -> return "" --- | Return the application name.+-- | 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) @@ -87,20 +86,27 @@ resource :: Query String resource = appName --- | Return the resource class.+-- | 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.+-- identified by name. Works for ASCII strings only. For the properties+-- @_NET_WM_NAME@/@WM_NAME@ and @WM_CLASS@ the specialised variants 'title'+-- and 'appName'/'className' are preferred. stringProperty :: String -> Query String-stringProperty p = ask >>= (\w -> liftX $ withDisplay $ \d -> fmap (fromMaybe "") $ getStringProperty d w p)+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)
src/XMonad/Operations.hs view
@@ -1,5 +1,10 @@-{-# OPTIONS_GHC -fno-warn-orphans #-}-{-# LANGUAGE FlexibleContexts, FlexibleInstances, MultiParamTypeClasses, PatternGuards, TypeSynonymInstances #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE PatternGuards #-}+{-# LANGUAGE ScopedTypeVariables #-}+ -- -------------------------------------------------------------------------- -- | -- Module : XMonad.Operations@@ -8,14 +13,50 @@ -- -- Maintainer : dons@cse.unsw.edu.au -- Stability : unstable--- Portability : not portable, Typeable deriving, mtl, posix+-- Portability : not portable, mtl, posix ----- Operations.+-- Operations. A module for functions that don't cleanly fit anywhere else. -- ----------------------------------------------------------------------------- -module XMonad.Operations where+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, unGrab,++ -- * Messages+ sendMessage, broadcastMessage, sendMessageWithNoRefresh,+ sendRestart, sendReplace,++ -- * 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@@ -23,17 +64,18 @@ import Data.Maybe import Data.Monoid (Endo(..),Any(..)) import Data.List (nub, (\\), find)-import Data.Bits ((.|.), (.&.), complement, testBit)+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.Applicative((<$>), (<*>)) import Control.Arrow (second)-import Control.Monad (void)+import Control.Monad.Fix (fix) import Control.Monad.Reader import Control.Monad.State-import qualified Control.Exception.Extensible as C+import Control.Monad (forM, forM_, guard, join, unless, void, when)+import qualified Control.Exception as C import System.IO import System.Directory@@ -43,9 +85,20 @@ import Graphics.X11.Xlib.Extras -- ------------------------------------------------------------------------ | -- Window manager operations--- manage. Add a new window to be managed in the current workspace.++-- | 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@@ -53,10 +106,8 @@ -- manage :: Window -> X () manage w = whenX (not <$> isClient w) $ withDisplay $ \d -> do- sh <- io $ getWMNormalHints d w - let isFixedSize = sh_min_size sh /= Nothing && sh_min_size sh == sh_max_size sh- isTransient <- isJust <$> io (getTransientForHint d w)+ shouldFloat <- isFixedSizeOrTransient d w rr <- snd `fmap` floatLocation w -- ensure that float windows don't go over the edge of the screen@@ -64,15 +115,15 @@ = W.RationalRect (0.5 - wid/2) (0.5 - h/2) wid h adjust r = r - f ws | isFixedSize || isTransient = W.float w (adjust rr) . W.insertUp w . W.view i $ ws- | otherwise = W.insertUp w ws+ 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) --- | unmanage. A window no longer exists, remove it from the window+-- | A window no longer exists; remove it from the window -- list, on whatever workspace it is. -- unmanage :: Window -> X ()@@ -92,9 +143,9 @@ io $ if wmdelt `elem` protocols then allocaXEvent $ \ev -> do setEventType ev clientMessage- setClientMessageEvent ev w wmprot 32 wmdelt 0+ setClientMessageEvent ev w wmprot 32 wmdelt currentTime sendEvent d w False noEventMask ev- else killClient d w >> return ()+ else void (killClient d w) -- | Kill the currently focused client. kill :: X ()@@ -103,7 +154,7 @@ -- --------------------------------------------------------------------- -- Managing windows --- | windows. Modify the current window list with a pure function, and refresh+-- | Modify the current window list with a pure function, and refresh windows :: (WindowSet -> WindowSet) -> X () windows f = do XState { windowset = old } <- get@@ -145,7 +196,8 @@ let m = W.floating ws flt = [(fw, scaleRationalRect viewrect r)- | fw <- filter (flip M.member m) (W.index this)+ | fw <- filter (`M.member` m) (W.index this)+ , fw `notElem` vis , Just r <- [M.lookup fw m]] vs = flt ++ rs @@ -171,7 +223,7 @@ -- all windows that are no longer in the windowset are marked as -- withdrawn, it is important to do this after the above, otherwise 'hide' -- will overwrite withdrawnState with iconicState- mapM_ (flip setWMState withdrawnState) (W.allWindows old \\ W.allWindows ws)+ mapM_ (`setWMState` withdrawnState) (W.allWindows old \\ W.allWindows ws) isMouseFocused <- asks mouseFocused unless isMouseFocused $ clearEvents enterWindowMask@@ -187,12 +239,14 @@ windowBracket p action = withWindowSet $ \old -> do a <- action when (p a) . withWindowSet $ \new -> do- modifyWindowSet $ \_ -> old- windows $ \_ -> new+ modifyWindowSet $ const old+ windows $ const new return a --- | A version of @windowBracket@ that discards the return value, and handles an--- @X@ action reporting its need for refresh via @Any@.+-- | 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 @@ -202,26 +256,25 @@ = Rectangle (sx + scale sw rx) (sy + scale sh ry) (scale sw rw) (scale sh rh) where scale s r = floor (toRational s * r) --- | setWMState. set the WM_STATE property+-- | 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 fallback to the color in @Pixel@.+-- | 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 <- color_pixel . fst <$> allocNamedColor dpy (wa_colormap wa) color+ pixel <- setPixelSolid . color_pixel . fst <$> allocNamedColor dpy (wa_colormap wa) color setWindowBorder dpy w pixel where fallback :: C.SomeException -> IO ()- fallback e = do hPrint stderr e >> hFlush stderr- setWindowBorder dpy w basic+ fallback _ = setWindowBorder dpy w basic --- | hide. Hide a window by unmapping it, and setting Iconified.+-- | 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@@ -234,15 +287,15 @@ modify (\s -> s { waitingUnmap = M.insertWith (+) w 1 (waitingUnmap s) , mapped = S.delete w (mapped s) }) --- | reveal. Show a window by mapping it and setting Normal--- this is harmless if the window was already visible+-- | 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+-- | 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@@ -253,7 +306,7 @@ -- required by the border setting in 'windows' io $ setWindowBorder d w nb --- | refresh. Render the currently visible workspaces, as determined by+-- | 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@@ -262,7 +315,7 @@ refresh :: X () refresh = windows id --- | clearEvents. Remove all events of a given type from the event queue.+-- | Remove all events of a given type from the event queue. clearEvents :: EventMask -> X () clearEvents mask = withDisplay $ \d -> io $ do sync d False@@ -270,8 +323,8 @@ more <- checkMaskEvent d mask p when more again -- beautiful --- | tileWindow. Moves and resizes w such that it fits inside the given--- rectangle, including its border.+-- | 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@@ -298,27 +351,28 @@ nubScreens :: [Rectangle] -> [Rectangle] nubScreens xs = nub . filter (\x -> not $ any (x `containedIn`) xs) $ xs --- | Cleans the list of screens according to the rules documented for+-- | Clean the list of screens according to the rules documented for -- nubScreens. getCleanedScreenInfo :: MonadIO m => Display -> m [Rectangle] getCleanedScreenInfo = io . fmap nubScreens . getScreenInfo --- | rescreen. The screen configuration may have changed (due to--- xrandr), update the state and refresh the screen, and reset the gap.+-- | The screen configuration may have changed (due to -- xrandr),+-- update the state and refresh the screen, and reset the gap. rescreen :: X ()-rescreen = do- xinesc <- withDisplay getCleanedScreenInfo-- windows $ \ws@(W.StackSet { W.current = v, W.visible = vs, W.hidden = hs }) ->- let (xs, ys) = splitAt (length xinesc) $ map W.workspace (v:vs) ++ hs- (a:as) = zipWith3 W.Screen xs [0..] $ map SD xinesc- in ws { W.current = a- , W.visible = as- , W.hidden = ys }+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 } -- --------------------------------------------------------------------- --- | setButtonGrab. Tell whether or not to intercept clicks on a given window+-- | 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)@@ -373,7 +427,7 @@ currevt <- asks currentEvent let inputHintSet = wmh_flags hints `testBit` inputHintBit - when ((inputHintSet && wmh_input hints) || (not inputHintSet)) $+ when (inputHintSet && wmh_input hints || not inputHintSet) $ io $ do setInputFocus dpy w revertToPointerRoot 0 when (wmtf `elem` protocols) $ io $ allocaXEvent $ \ev -> do@@ -381,12 +435,68 @@ 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+ 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+ ]++-- | Release XMonad's keyboard grab, so other grabbers can do their thing.+--+-- Start a keyboard action with this if it is going to run something+-- that needs to do a keyboard, pointer, or server grab. For example,+--+-- > , ((modm .|. controlMask, xK_p), unGrab >> spawn "scrot")+--+-- (Other examples are certain screen lockers and "gksu".)+-- This avoids needing to insert a pause/sleep before running the+-- command.+--+-- XMonad retains the keyboard grab during key actions because if they+-- use a submap, they need the keyboard to be grabbed, and if they had+-- to assert their own grab then the asynchronous nature of X11 allows+-- race conditions between XMonad, other clients, and the X server that+-- would cause keys to sometimes be "leaked" to the focused window.+unGrab :: X ()+unGrab = withDisplay $ \d -> io $ do+ ungrabKeyboard d currentTime+ ungrabPointer d currentTime+ sync d False+ ------------------------------------------------------------------------ -- Message handling @@ -394,7 +504,7 @@ -- layout the windows, in which case changes are handled through a refresh. sendMessage :: Message a => a -> X () sendMessage a = windowBracket_ $ do- w <- W.workspace . W.current <$> gets windowset+ 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)@@ -405,33 +515,91 @@ -- | Send a message to all layouts, without refreshing. broadcastMessage :: Message a => a -> X () broadcastMessage a = withWindowSet $ \ws -> do- let c = W.workspace . W.current $ ws- v = map W.workspace . W.visible $ ws- h = W.hidden ws- mapM_ (sendMessageWithNoRefresh a) (c : v ++ h)+ -- 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 -> W.Workspace WorkspaceId (Layout Window) Window -> X ()+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+-- | 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+-- | 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+ 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 } } }+ windows $ const $ ss{ W.current = c{ W.workspace = ws{ W.layout = l } } } +-- | Signal xmonad to restart itself.+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++-- | Signal compliant window managers to exit.+sendReplace :: IO ()+sendReplace = do+ dpy <- openDisplay ""+ let dflt = defaultScreen dpy+ rootw <- rootWindow dpy dflt+ replace dpy dflt rootw++-- | Signal 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+ ------------------------------------------------------------------------ -- Utilities --- | Return workspace visible on screen 'sc', or 'Nothing'.+-- | Return workspace visible on screen @sc@, or 'Nothing'. screenWorkspace :: ScreenId -> X (Maybe WorkspaceId) screenWorkspace sc = withWindowSet $ return . W.lookupWorkspace sc @@ -439,7 +607,14 @@ withFocused :: (Window -> X ()) -> X () withFocused f = withWindowSet $ \w -> whenJust (W.peek w) f --- | 'True' if window is under management by us+-- | 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 @@ -450,16 +625,20 @@ nlm <- gets numberlockMask return [0, nlm, lockMask, nlm .|. lockMask ] --- | Strip numlock\/capslock from a mask+-- | Strip numlock\/capslock from a mask. cleanMask :: KeyMask -> X KeyMask cleanMask km = do nlm <- gets numberlockMask return (complement (nlm .|. lockMask) .&. km) --- | Get the 'Pixel' value for a named color+-- | 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 . color_pixel . fst) <$> allocNamedColor dpy colormap c+ Just . setPixelSolid . color_pixel . fst <$> allocNamedColor dpy colormap c where colormap = defaultColormap dpy (defaultScreen dpy) ------------------------------------------------------------------------@@ -479,9 +658,9 @@ maybeShow _ = Nothing wsData = W.mapLayout show . windowset- extState = catMaybes . map maybeShow . M.toList . extensibleState+ extState = mapMaybe maybeShow . M.toList . extensibleState - path <- stateFileName+ path <- asks $ stateFileName . directories stateData <- gets (\s -> StateFile (wsData s) (extState s)) catchIO (writeFile path $ show stateData) @@ -489,7 +668,7 @@ -- 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 <- stateFileName+ 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.@@ -522,23 +701,8 @@ readStrict :: Handle -> IO String readStrict h = hGetContents h >>= \s -> length s `seq` return s --- | Migrate state from a previously running xmonad instance that used--- the older @--resume@ technique.-{-# DEPRECATED migrateState "will be removed some point in the future." #-}-migrateState :: (Functor m, MonadIO m) => String -> String -> m ()-migrateState ws xs = do- io (putStrLn "WARNING: --resume is no longer supported.")- whenJust stateData $ \s -> do- path <- stateFileName- catchIO (writeFile path $ show s)- where- stateData = StateFile <$> maybeRead ws <*> maybeRead xs- maybeRead s = case reads s of- [(x, "")] -> Just x- _ -> Nothing---- | @restart name resume@. Attempt to restart xmonad by executing the program--- @name@. If @resume@ is 'True', restart with the current window state.+-- | @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@@ -548,33 +712,49 @@ catchIO (executeFile prog True [] Nothing) --------------------------------------------------------------------------- | Floating layer support+-- Floating layer support -- | Given a window, find the screen it is located on, and compute--- the geometry of that window wrt. that screen.+-- 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 <- W.current <$> gets windowset+ sc <- gets $ W.current . windowset return (W.screen sc, W.RationalRect 0 0 1 1) - where fi x = fromIntegral x- go = withDisplay $ \d -> do+ where go = withDisplay $ \d -> do ws <- gets windowset+ sh <- io $ getWMNormalHints d w wa <- io $ getWindowAttributes d w let bw = (fromIntegral . wa_border_width) wa- sc <- fromMaybe (W.current ws) <$> pointScreen (fi $ wa_x wa) (fi $ wa_y wa)+ point_sc <- pointScreen (fi $ wa_x wa) (fi $ wa_y wa)+ managed <- isClient w - let sr = screenRect . W.screenDetail $ sc- rr = W.RationalRect ((fi (wa_x wa) - fi (rect_x sr)) % fi (rect_width sr))- ((fi (wa_y wa) - fi (rect_y sr)) % fi (rect_height sr))- (fi (wa_width wa + bw*2) % fi (rect_width sr))- (fi (wa_height wa + bw*2) % fi (rect_height sr))+ -- 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, height) = applySizeHintsContents sh (wa_width wa, wa_height wa)+ rwidth = fi (width + bw*2) % fi (rect_width sr)+ rheight = fi (height + 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 rwidth rheight+ else W.RationalRect (0.5 - rwidth/2) (0.5 - rheight/2) rwidth rheight 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))@@ -605,14 +785,20 @@ -- | Accumulate mouse motion events mouseDrag :: (Position -> Position -> X ()) -> X () -> X ()-mouseDrag f done = do+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 $ grabPointer d root False (buttonReleaseMask .|. pointerMotionMask)- grabModeAsync grabModeAsync none none currentTime+ 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@@ -623,39 +809,41 @@ clearEvents pointerMotionMask return z --- | drag the window under the cursor with the mouse while it is dragged+-- | Drag the window under the cursor with the mouse while it is dragged. mouseMoveWindow :: Window -> X () mouseMoveWindow w = whenX (isClient w) $ withDisplay $ \d -> do- io $ raiseWindow d w wa <- io $ getWindowAttributes d w (_, _, _, ox', oy', _, _, _) <- io $ queryPointer d w let ox = fromIntegral ox' oy = fromIntegral oy'- mouseDrag (\ex ey -> do+ 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+-- | Resize the window under the cursor with the mouse while it is dragged. mouseResizeWindow :: Window -> X () mouseResizeWindow w = whenX (isClient w) $ withDisplay $ \d -> do- io $ raiseWindow d w wa <- io $ getWindowAttributes d w sh <- io $ getWMNormalHints d w io $ warpPointer d none w 0 0 0 0 (fromIntegral (wa_width wa)) (fromIntegral (wa_height wa))- mouseDrag (\ex ey -> do+ 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+-- 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@@ -665,7 +853,7 @@ sh <- getWMNormalHints d w wa <- C.try $ getWindowAttributes d w case wa of- Left err -> const (return id) (err :: C.SomeException)+ Left (_ :: C.SomeException) -> return id Right wa' -> let bw = fromIntegral $ wa_border_width wa' in return $ applySizeHints bw sh@@ -683,7 +871,7 @@ applySizeHintsContents sh (w, h) = applySizeHints' sh (fromIntegral $ max 1 w, fromIntegral $ max 1 h) --- | XXX comment me+-- | Use X11 size hints to scale a pair of dimensions. applySizeHints' :: SizeHints -> D -> D applySizeHints' sh = maybe id applyMaxSizeHint (sh_max_size sh)
src/XMonad/StackSet.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE PatternGuards #-}+{-# LANGUAGE DeriveFunctor #-} ----------------------------------------------------------------------------- -- |@@ -52,9 +53,13 @@ ) 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@@ -85,25 +90,27 @@ -- continuation reified as a data structure. -- -- The Zipper lets us replace an item deep in a complex data--- structure, e.g., a tree or a term, without an mutation. The+-- 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. ----- Oleg Kiselyov, 27 Apr 2005, haskell\@, "Zipper as a delimited continuation"+-- <https://mail.haskell.org/pipermail/haskell/2005-April/015769.html Oleg Kiselyov, 27 Apr 2005, haskell\@, "Zipper as a delimited continuation"> -- -- We use the zipper to keep track of the focused workspace and the -- focused window on each workspace, allowing us to have correct focus -- by construction. We closely follow Huet's original implementation: ----- G. Huet, /Functional Pearl: The Zipper/,--- 1997, J. Functional Programming 75(5):549-554.--- and:--- R. Hinze and J. Jeuring, /Functional Pearl: The Web/.+-- <https://www.st.cs.uni-saarland.de/edu/seminare/2005/advanced-fp/docs/huet-zipper.pdf G. Huet, Functional Pearl: The Zipper; 1997, J. Functional Programming 75(5):549–554> ----- and Conor McBride's zipper differentiation paper.--- Another good reference is:+-- and ----- The Zipper, Haskell wikibook+-- <https://dspace.library.uu.nl/handle/1874/2532 R. Hinze and J. Jeuring, Functional Pearl: Weaving a Web>+--+-- and+--+-- <http://strictlypositive.org/diff.pdf Conor McBride, The Derivative of a Regular Type is its Type of One-Hole Contexts>.+--+-- Another good reference is: <https://wiki.haskell.org/Zipper The Zipper, Haskell wikibook> -- $xinerama -- Xinerama in X11 lets us view multiple virtual workspaces@@ -151,7 +158,7 @@ deriving (Show, Read, Eq) -- | A structure for window geometries-data RationalRect = RationalRect Rational Rational Rational Rational+data RationalRect = RationalRect !Rational !Rational !Rational !Rational deriving (Show, Read, Eq) -- |@@ -175,9 +182,20 @@ 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)+ 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@@ -194,10 +212,11 @@ -- Xinerama: Virtual workspaces are assigned to physical screens, starting at 0. -- new :: (Integral s) => l -> [i] -> [sd] -> StackSet i l a s sd-new l wids m | not (null wids) && length m <= length wids && not (null m)- = StackSet cur visi unseen M.empty- where (seen,unseen) = L.splitAt (length m) $ map (\i -> Workspace i l Nothing) wids- (cur:visi) = [ Screen i s sd | (i, s, sd) <- zip3 seen [0..] m ]+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" @@ -224,7 +243,7 @@ | otherwise = s -- not a member of the stackset - where equating f = \x y -> f x == f y+ where equating f x y = f x == f y -- 'Catch'ing this might be hard. Relies on monotonically increasing -- workspace tags defined in 'new'@@ -297,7 +316,7 @@ integrate (Stack x l r) = reverse l ++ x : r -- |--- /O(n)/ Flatten a possibly empty stack into a list.+-- /O(n)/. Flatten a possibly empty stack into a list. integrate' :: Maybe (Stack a) -> [a] integrate' = maybe [] integrate @@ -329,32 +348,44 @@ index :: StackSet i l a s sd -> [a] index = with [] integrate --- |--- /O(1), O(w) on the wrapping case/.------ focusUp, focusDown. Move the window focus up or down the stack,--- wrapping if we reach the end. The wrapping should model a 'cycle'--- on the current stack. The 'master' window, and window order,+-- | /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.------ swapUp, swapDown, swap the neighbour in the stack ordering, wrapping--- if we reach the end. Again the wrapping model should 'cycle' on--- the current stack.----focusUp, focusDown, swapUp, swapDown :: StackSet i l a s sd -> StackSet i l a s sd+focusUp :: 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) --- | Variants of 'focusUp' and 'focusDown' that work on a+-- | A variant of 'focusUp' with the same asymptotics that works on a -- 'Stack' rather than an entire 'StackSet'.-focusUp', focusDown' :: Stack a -> Stack a+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) = reverse (t:rs)-focusDown' = reverseStack . focusUp' . reverseStack+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) []@@ -508,8 +539,8 @@ -- Focus stays with the item moved. swapMaster :: StackSet i l a s sd -> StackSet i l a s sd swapMaster = modify' $ \c -> case c of- Stack _ [] _ -> c -- already master.- Stack t ls rs -> Stack t [] (xs ++ x : rs) where (x:xs) = reverse ls+ 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. @@ -525,8 +556,8 @@ -- | /O(s)/. Set focus to the master window. focusMaster :: StackSet i l a s sd -> StackSet i l a s sd focusMaster = modify' $ \c -> case c of- Stack _ [] _ -> c- Stack t ls rs -> Stack x [] (xs ++ t : rs) where (x:xs) = reverse ls+ Stack _ [] _ -> c+ Stack t (l:ls) rs -> Stack x [] (xs ++ t : rs) where (x :| xs) = NE.reverse (l :| ls) -- -- ---------------------------------------------------------------------
tests/Instances.hs view
@@ -36,7 +36,7 @@ -- Pick a random window "number" in each workspace, to give focus. focus <- sequence [ if null windows then return Nothing- else liftM Just $ choose (0, length windows - 1)+ else Just <$> choose (0, length windows - 1) | windows <- wsWindows ] let tags = [1 .. fromIntegral numWs]@@ -80,7 +80,7 @@ instance Arbitrary NonEmptyWindowsStackSet where arbitrary =- NonEmptyWindowsStackSet `fmap` (arbitrary `suchThat` (not . null . allWindows))+ NonEmptyWindowsStackSet <$> (arbitrary `suchThat` (not . null . allWindows)) instance Arbitrary Rectangle where arbitrary = Rectangle <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary@@ -99,7 +99,7 @@ deriving ( Eq, Ord, Show, Read ) instance (Eq a, Arbitrary a) => Arbitrary (NonEmptyNubList a) where- arbitrary = NonEmptyNubList `fmap` ((liftM nub arbitrary) `suchThat` (not . null))+ arbitrary = NonEmptyNubList <$> ((nub <$> arbitrary) `suchThat` (not . null)) @@ -116,7 +116,7 @@ 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)+ idx <- choose (0, length ts - 1) return $ ts!!idx -- | Pull out an arbitrary window from a StackSet that is guaranteed to have a@@ -136,5 +136,5 @@ 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)+ idx <- choose (0, length ws - 1) return $ ws!!idx
tests/Properties.hs view
@@ -1,6 +1,6 @@ import Test.QuickCheck --- Our QC instances and properties.+-- Our QC instances and properties: import Instances import Properties.Delete import Properties.Failure@@ -166,11 +166,16 @@ -- tall layout ,("tile 1 window fullsize", property prop_tile_fullscreen)+ ,("tile max ratio", property prop_tile_max_ratio)+ ,("tile min ratio", property prop_tile_min_ratio) ,("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)+ ,("pure layout tall", property prop_purelayout_tall)+ {- Following two test cases should be automatically generated by QuickCheck ideally, but it fails. -}+ ,("pure layout tall: ratio = 0", property (\n d rect -> prop_purelayout_tall n d 0 rect))+ ,("pure layout tall: ratio = 1", property (\n d rect -> prop_purelayout_tall n d 1 rect)) ,("send shrink tall", property prop_shrink_tall) ,("send expand tall", property prop_expand_tall) ,("send incmaster tall", property prop_incmaster_tall)@@ -196,6 +201,5 @@ ,("pointWithin", property prop_point_within) ,("pointWithin mirror", property prop_point_within_mirror) - ]--+ ] <>+ prop_laws_Stack
tests/Properties/Delete.hs view
@@ -64,7 +64,7 @@ -- last one in the stack. `suchThat` \(x' :: T) -> let currWins = index x'- in length (currWins) >= 2 && peek x' /= Just (last currWins)+ 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)
tests/Properties/Failure.hs view
@@ -2,7 +2,7 @@ import XMonad.StackSet hiding (filter) -import qualified Control.Exception.Extensible as C+import qualified Control.Exception as C import System.IO.Unsafe import Data.List (isPrefixOf)
tests/Properties/Focus.hs view
@@ -32,8 +32,8 @@ 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+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)@@ -47,9 +47,9 @@ 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+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+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
tests/Properties/GreedyView.hs view
@@ -35,7 +35,7 @@ -- greedyView is idempotent prop_greedyView_idem (x :: T) = do n <- arbitraryTag x- return $ greedyView n (greedyView n x) == (greedyView n 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
tests/Properties/Insert.hs view
@@ -46,7 +46,7 @@ -- inserting n elements increases current stack size by n prop_size_insert is (EmptyStackSet x) =- size (foldr insertUp x ws ) == (length ws)+ size (foldr insertUp x ws) == length ws where ws = nub is size = length . index
tests/Properties/Layout/Full.hs view
@@ -29,6 +29,6 @@ -- what happens when we send an IncMaster message to Full --- Nothing prop_sendmsg_full (NonNegative k) =- isNothing (Full `pureMessage` (SomeMessage (IncMasterN k)))+ isNothing (Full `pureMessage` SomeMessage (IncMasterN k)) prop_desc_full = description Full == show Full
tests/Properties/Layout/Tall.hs view
@@ -11,8 +11,9 @@ import Graphics.X11.Xlib.Types (Rectangle(..)) -import Data.Maybe+import Control.Applicative import Data.List (sort)+import Data.Maybe import Data.Ratio ------------------------------------------------------------------------@@ -27,14 +28,30 @@ where _ = rect :: Rectangle pct = 3 % 100 +-- with a ratio of 1, no stack windows are drawn of there is at least+-- one master window around.+prop_tile_max_ratio = extremeRatio 1 drop++-- with a ratio of 0, no master windows are drawn at all if there are+-- any stack windows around.+prop_tile_min_ratio = extremeRatio 0 take++extremeRatio amount getRects rect = do+ w@(NonNegative windows) <- arbitrary `suchThat` (> NonNegative 0)+ NonNegative nmaster <- arbitrary `suchThat` (< w)+ let tiled = tile amount rect nmaster windows+ pure $ if nmaster == 0+ then prop_tile_non_overlap rect windows nmaster+ else all ((== 0) . rect_width) $ getRects nmaster tiled+ -- splitting horizontally yields sensible results prop_split_horizontal (NonNegative n) x =- (noOverflows (+) (rect_x x) (rect_width x)) ==>+ noOverflows (+) (rect_x x) (rect_width x) ==> sum (map rect_width xs) == rect_width x &&- all (== rect_height x) (map rect_height xs)+ all (\s -> rect_height s == rect_height x) xs &&- (map rect_x xs) == (sort $ map rect_x xs)+ map rect_x xs == sort (map rect_x xs) where xs = splitHorizontally n x@@ -49,13 +66,20 @@ -- pureLayout works.-prop_purelayout_tall n r1 r2 rect = do+prop_purelayout_tall n d r rect = do x <- (arbitrary :: Gen T) `suchThat` (isJust . peek)- let layout = Tall n r1 r2+ let layout = Tall n d r st = fromJust . stack . workspace . current $ x ts = pureLayout layout rect st+ ntotal = length (index x) return $- length ts == length (index x)+ (if r == 0 then+ -- (<=) for Bool is the logical implication+ (0 <= n && n <= ntotal) <= (length ts == ntotal - n)+ else if r == 1 then+ (0 <= n && n <= ntotal) <= (length ts == n)+ else+ length ts == ntotal) && noOverlaps (map snd ts) &&@@ -72,7 +96,7 @@ -- remaining fraction should shrink where l1 = Tall n delta frac- Just l2@(Tall n' delta' frac') = l1 `pureMessage` (SomeMessage Shrink)+ Just l2@(Tall n' delta' frac') = l1 `pureMessage` SomeMessage Shrink -- pureMessage :: layout a -> SomeMessage -> Maybe (layout a) @@ -93,7 +117,7 @@ where frac = min 1 (n1 % d1) l1 = Tall n delta frac- Just l2@(Tall n' delta' frac') = l1 `pureMessage` (SomeMessage Expand)+ 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@@ -102,7 +126,7 @@ delta == delta' && frac == frac' && n' == n + k where l1 = Tall n delta frac- Just l2@(Tall n' delta' frac') = l1 `pureMessage` (SomeMessage (IncMasterN k))+ Just l2@(Tall n' delta' frac') = l1 `pureMessage` SomeMessage (IncMasterN k) -- pureMessage :: layout a -> SomeMessage -> Maybe (layout a)
tests/Properties/Screen.hs view
@@ -52,15 +52,14 @@ -- 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 v = applyAspectHint ((x, y+a), (x+b, y)) v- in and [ noOverflows (*) x (y+a), noOverflows (*) (x+b) y ]+ 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)- mul a b = toInteger (a*b) /= toInteger a * toInteger b -prop_point_within r @ (Rectangle x y w h) =+prop_point_within r@(Rectangle x y w h) = forAll ((,) <$> choose (0, fromIntegral w - 1) <*> choose (0, fromIntegral h - 1)) $
tests/Properties/Shift.hs view
@@ -27,7 +27,7 @@ -- shiftMaster -- focus/local/idempotent same as swapMaster:-prop_shift_master_focus (x :: T) = peek x == (peek $ shiftMaster x)+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:@@ -45,7 +45,7 @@ Nothing -> return True Just w -> return $ shiftWin n w x == shift n x --- shiftWin on a non-existant window is identity+-- shiftWin on a non-existent window is identity prop_shift_win_indentity (x :: T) = do n <- arbitraryTag x w <- arbitrary `suchThat` \w' -> not (w' `member` x)@@ -57,14 +57,14 @@ 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 && length(otherWindows) >= 1+ 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)+ idx <- choose (0, length otherWindows - 1) let w = otherWindows !! idx- return $ (current $ x) == (current $ shiftWin n w x)+ return $ current x == current (shiftWin n w x)
tests/Properties/Stack.hs view
@@ -1,4 +1,6 @@ {-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+ module Properties.Stack where import Test.QuickCheck@@ -9,12 +11,18 @@ 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 -> length (index x) == 0+ Nothing -> null (index x) Just it -> length (index x) == length (focus it : up it ++ down it) @@ -33,7 +41,7 @@ -- 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)+ idx <- choose (0, length ws - 1) return $ member (ws!!idx) x @@ -46,6 +54,24 @@ -- 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 differentiate xs == Nothing- else (differentiate xs) == Just (Stack (head xs) [] (tail 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 ]
tests/Properties/StackSet.hs view
@@ -58,7 +58,7 @@ -- inBounds = and [ w >=0 && w < size s | (w,sc) <- M.assocs (screens s) ] monotonic [] = True-monotonic (x:[]) = True+monotonic [x] = True monotonic (x:y:zs) | x == y-1 = monotonic (y:zs) | otherwise = False @@ -126,7 +126,7 @@ 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) = member i x == False+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) =
tests/Properties/Swap.hs view
@@ -11,8 +11,8 @@ -- 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+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...@@ -26,12 +26,12 @@ -} -- swap doesn't change focus-prop_swap_master_focus (x :: T) = peek x == (peek $ swapMaster x)+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)+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)@@ -39,9 +39,9 @@ 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+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+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
tests/Properties/View.hs view
@@ -37,7 +37,7 @@ -- view is idempotent prop_view_idem (x :: T) = do n <- arbitraryTag x- return $ view n (view n x) == (view n 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
tests/Utils.hs view
@@ -12,8 +12,8 @@ -- 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+ f a b = tag (workspace a) `compare` tag (workspace b)+ g a b = tag a `compare` tag b noOverlaps [] = True
− tests/loc.hs
@@ -1,14 +0,0 @@-import Control.Monad-import System.Exit--main = do foo <- getContents- let actual_loc = filter (not.null) $ filter isntcomment $- map (dropWhile (==' ')) $ lines foo- loc = length actual_loc- print loc- -- uncomment the following to check for mistakes in isntcomment- -- print actual_loc--isntcomment ('-':'-':_) = False-isntcomment ('{':'-':_) = False -- pragmas-isntcomment _ = True
− util/GenerateManpage.hs
@@ -1,92 +0,0 @@-{-# LANGUAGE FlexibleContexts #-}---- Generates a in-memory version of "man/xmonad.1.markdown" that has the list--- of known key-bindings is inserted automatically from "Config.hs". That--- document is then rendered with Pandoc as "man/xmonad.1" and--- "man/xmonad.1.html".------ Unlike the rest of xmonad, this file is released under the GNU General--- Public License version 2 or later.--import Control.Monad.IO.Class (liftIO)-import Data.Char-import Data.List-import qualified Data.Text as T-import qualified Data.Text.IO as TIO-import Text.Pandoc-import Text.Regex.Posix--main :: IO ()-main = do- keybindings <- guessBindings-- markdownSource <- readFile "./man/xmonad.1.markdown"-- runIOorExplode $ do- parsed <- readMarkdown (def { readerStandalone = True, readerExtensions = pandocExtensions })- . T.pack- . unlines- . replace "___KEYBINDINGS___" keybindings- . lines- $ markdownSource-- manTemplate <- getDefaultTemplate "man"- manBody <- writeMan def { writerTemplate = Just manTemplate } parsed- liftIO $ TIO.writeFile "./man/xmonad.1" $ manBody- liftIO $ putStrLn "Documentation created: man/xmonad.1"-- htmltemplate <- getDefaultTemplate "html"- htmlBody <- writeHtml5String def- { writerTemplate = Just htmltemplate- , writerTableOfContents = True }- parsed- liftIO $ TIO.writeFile "./man/xmonad.1.html" htmlBody- liftIO $ putStrLn "Documentation created: man/xmonad.1.html"---- | The format for the docstrings in "Config.hs" takes the following form:------ @--- -- mod-x %! Frob the whatsit--- @------ "Frob the whatsit" will be used as the description for keybinding "mod-x".----- If the name of the key binding is omitted, the function tries to guess it--- from the rest of the line. For example:------ @--- [ ((modMask .|. shiftMask, xK_Return), spawn "xterm") -- %! Launch an xterm--- @------ Here, "mod-shift-return" will be used as the key binding name.--guessBindings :: IO String-guessBindings = do- buf <- readFile "./src/XMonad/Config.hs"- return (intercalate "\n\n" (map markdownDefn (allBindings buf)))--allBindings :: String -> [(String, String)]-allBindings xs = map (binding . map trim) (xs =~ "(.*)--(.*)%!(.*)")--binding :: [String] -> (String, String)-binding [ _, bindingLine, "", desc ] = (guessKeys bindingLine, desc)-binding [ _, _, keyCombo, desc ] = (keyCombo, desc)-binding x = error ("binding: called with unexpected argument " ++ show x)--guessKeys :: String -> String-guessKeys line =- case keys of- [key] -> concat $ intersperse "-" (modifiers ++ [map toLower key])- _ -> error ("guessKeys: unexpected number of keys " ++ show keys)- where- modifiers = map (!!1) (line =~ "(mod|shift|control)Mask")- (_, _, _, keys) = line =~ "xK_([_[:alnum:]]+)" :: (String, String, String, [String])---- FIXME: What escaping should we be doing on these strings?-markdownDefn :: (String, String) -> String-markdownDefn (key, desc) = key ++ "\n: " ++ desc--replace :: Eq a => a -> a -> [a] -> [a]-replace x y = map (\a -> if a == x then y else a)--trim :: String -> String-trim = reverse . dropWhile isSpace . reverse . dropWhile isSpace
xmonad.cabal view
@@ -1,5 +1,5 @@ name: xmonad-version: 0.15+version: 0.18.1 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@@ -25,42 +25,35 @@ 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+ Trevor Elliott, Wouter Swierstra, Conrad Irwin, Tim Thelion, Tony Zorman maintainer: xmonad@haskell.org-tested-with: GHC == 8.0.2, GHC == 8.2.2, GHC == 8.4.3, GHC == 8.6.1+tested-with: GHC == 8.6.5 || == 8.8.4 || == 8.10.7 || == 9.0.2 || == 9.2.8 || == 9.4.8 || == 9.6.7 || == 9.8.4 || == 9.10.3 || == 9.12.2 || == 9.14.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- CONFIG- STYLE- tests/*.hs- tests/Properties/*.hs- tests/Properties/Layout/*.hs+ CONTRIBUTING.md+ INSTALL.md+ MAINTAINERS.md+ TUTORIAL.md man/xmonad.1.markdown man/xmonad.1 man/xmonad.1.html man/xmonad.hs- util/GenerateManpage.hs util/hpcReport.sh-cabal-version: >= 1.8+cabal-version: 1.12 source-repository head type: git location: https://github.com/xmonad/xmonad -flag testing+flag pedantic+ description: Be pedantic (-Werror and the like) default: False manual: True- description: Testing mode, only build minimal components -flag generatemanpage- default: False- manual: True- description: Build the tool for generating the man page- library exposed-modules: XMonad XMonad.Config@@ -72,36 +65,46 @@ XMonad.StackSet other-modules: Paths_xmonad hs-source-dirs: src- build-depends: base >= 4.9 && < 5- , X11 >= 1.8 && < 1.10+ build-depends: base >= 4.12 && < 5+ , X11 >= 1.10 && < 1.11 , containers- , data-default+ , data-default-class , directory- , extensible-exceptions , filepath , mtl , process , setlocale+ , time+ , transformers >= 0.3 , unix- , utf8-string >= 0.3 && < 1.1- ghc-options: -funbox-strict-fields -Wall -fno-warn-unused-do-bind+ ghc-options: -funbox-strict-fields -Wall -Wno-unused-do-bind -Wincomplete-patterns -Wincomplete-uni-patterns+ if impl(ghc > 8.8.4)+ ghc-options: -Wunused-packages+ default-language: Haskell2010 - if flag(testing)- buildable: False+ -- 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+ if impl(ghc >= 9.10)+ ghc-options: -Wno-incomplete-record-selectors+ executable xmonad main-is: Main.hs- build-depends: base, X11, mtl, unix, xmonad- ghc-options: -Wall -fno-warn-unused-do-bind+ build-depends: base, xmonad+ ghc-options: -Wall -Wno-unused-do-bind+ default-language: Haskell2010 -executable generatemanpage- main-is: GenerateManpage.hs- hs-source-dirs: util+ -- Keep this in sync with the oldest version in 'tested-with'+ if impl(ghc > 8.6.5)+ ghc-options: -Wno-unused-imports - if flag(generatemanpage)- build-depends: base, pandoc >= 2, regex-posix, text- else- buildable: False+ if flag(pedantic)+ ghc-options: -Werror+ if impl(ghc >= 9.10)+ ghc-options: -Wno-incomplete-record-selectors test-suite properties type: exitcode-stdio-1.0@@ -124,4 +127,22 @@ Properties.Workspace Utils hs-source-dirs: tests- build-depends: base, QuickCheck >= 2, X11, containers, extensible-exceptions, xmonad+ build-depends: base+ , QuickCheck >= 2+ , quickcheck-classes >= 0.4.3+ , X11+ , containers+ , xmonad+ default-language: Haskell2010++ ghc-options: -Wno-incomplete-patterns -Wno-incomplete-uni-patterns+ if impl(ghc > 8.8.4)+ ghc-options: -Wno-unused-packages++ if impl(ghc > 9.8)+ ghc-options: -Wno-x-partial++ if flag(pedantic)+ ghc-options: -Werror+ if impl(ghc >= 9.10)+ ghc-options: -Wno-incomplete-record-selectors