diff --git a/.github/workflows/haskell-ci-hackage.patch b/.github/workflows/haskell-ci-hackage.patch index af35032696..a27d8d654e 100644 --- a/.github/workflows/haskell-ci-hackage.patch +++ b/.github/workflows/haskell-ci-hackage.patch @@ -37,24 +37,26 @@ set in GitHub repository secrets. jobs: linux: name: Haskell-CI - Linux - ${{ matrix.compiler }} -@@ -31,6 +38,7 @@ - compilerVersion: 9.0.2 +@@ -33,6 +40,7 @@ + compilerVersion: 9.8.2 setup-method: ghcup allow-failure: false + upload: true - - compiler: ghc-8.10.7 + - compiler: ghc-9.6.4 compilerKind: ghc - compilerVersion: 8.10.7 -@@ -237,7 +237,7 @@ - ${CABAL} -vnormal check + compilerVersion: 9.6.4 +@@ -257,6 +265,10 @@ - name: haddock run: | -- $CABAL v2-haddock --disable-documentation $ARG_COMPILER --with-haddock $HADDOCK $ARG_TESTS $ARG_BENCH all + $CABAL v2-haddock --disable-documentation $ARG_COMPILER --with-haddock $HADDOCK $ARG_TESTS $ARG_BENCH all ++ - name: haddock for hackage ++ if: matrix.upload ++ run: | + $CABAL v2-haddock $ARG_COMPILER --with-haddock $HADDOCK $ARG_TESTS $ARG_BENCH --haddock-for-hackage --builddir $GITHUB_WORKSPACE/haddock all - name: unconstrained build run: | rm -f cabal.project.local -@@ -248,3 +248,75 @@ +@@ -267,3 +279,75 @@ with: key: ${{ runner.os }}-${{ matrix.compiler }}-${{ github.sha }} path: ~/.cabal/store diff --git a/.github/workflows/haskell-ci.yml b/.github/workflows/haskell-ci.yml index 359a179e9b..6a698cb920 100644 --- a/.github/workflows/haskell-ci.yml +++ b/.github/workflows/haskell-ci.yml @@ -8,9 +8,9 @@ # # For more information, see https://github.com/haskell-CI/haskell-ci # -# version: 0.16.6 +# version: 0.18.1 # -# REGENDATA ("0.16.6",["github","cabal.project"]) +# REGENDATA ("0.18.1",["github","cabal.project"]) # name: Haskell-CI on: @@ -35,14 +35,20 @@ jobs: strategy: matrix: include: - - compiler: ghc-9.6.2 + - compiler: ghc-9.8.2 compilerKind: ghc - compilerVersion: 9.6.2 + compilerVersion: 9.8.2 setup-method: ghcup allow-failure: false - - compiler: ghc-9.4.5 + upload: true + - compiler: ghc-9.6.4 + compilerKind: ghc + compilerVersion: 9.6.4 + setup-method: ghcup + allow-failure: false + - compiler: ghc-9.4.8 compilerKind: ghc - compilerVersion: 9.4.5 + compilerVersion: 9.4.8 setup-method: ghcup allow-failure: false - compiler: ghc-9.2.8 @@ -55,7 +61,6 @@ jobs: compilerVersion: 9.0.2 setup-method: ghcup allow-failure: false - upload: true - compiler: ghc-8.10.7 compilerKind: ghc compilerVersion: 8.10.7 @@ -79,10 +84,10 @@ jobs: apt-get install -y --no-install-recommends gnupg ca-certificates dirmngr curl git software-properties-common libtinfo5 if [ "${{ matrix.setup-method }}" = ghcup ]; then mkdir -p "$HOME/.ghcup/bin" - curl -sL https://downloads.haskell.org/ghcup/0.1.19.2/x86_64-linux-ghcup-0.1.19.2 > "$HOME/.ghcup/bin/ghcup" + curl -sL https://downloads.haskell.org/ghcup/0.1.20.0/x86_64-linux-ghcup-0.1.20.0 > "$HOME/.ghcup/bin/ghcup" chmod a+x "$HOME/.ghcup/bin/ghcup" "$HOME/.ghcup/bin/ghcup" install ghc "$HCVER" || (cat "$HOME"/.ghcup/logs/*.* && false) - "$HOME/.ghcup/bin/ghcup" install cabal 3.10.1.0 || (cat "$HOME"/.ghcup/logs/*.* && false) + "$HOME/.ghcup/bin/ghcup" install cabal 3.10.2.0 || (cat "$HOME"/.ghcup/logs/*.* && false) apt-get update apt-get install -y libx11-dev libxext-dev libxft-dev libxinerama-dev libxrandr-dev libxss-dev else @@ -90,9 +95,9 @@ jobs: apt-get update apt-get install -y "$HCNAME" libx11-dev libxext-dev libxft-dev libxinerama-dev libxrandr-dev libxss-dev mkdir -p "$HOME/.ghcup/bin" - curl -sL https://downloads.haskell.org/ghcup/0.1.19.2/x86_64-linux-ghcup-0.1.19.2 > "$HOME/.ghcup/bin/ghcup" + curl -sL https://downloads.haskell.org/ghcup/0.1.20.0/x86_64-linux-ghcup-0.1.20.0 > "$HOME/.ghcup/bin/ghcup" chmod a+x "$HOME/.ghcup/bin/ghcup" - "$HOME/.ghcup/bin/ghcup" install cabal 3.10.1.0 || (cat "$HOME"/.ghcup/logs/*.* && false) + "$HOME/.ghcup/bin/ghcup" install cabal 3.10.2.0 || (cat "$HOME"/.ghcup/logs/*.* && false) fi env: HCKIND: ${{ matrix.compilerKind }} @@ -106,17 +111,19 @@ jobs: echo "CABAL_CONFIG=$HOME/.cabal/config" >> "$GITHUB_ENV" HCDIR=/opt/$HCKIND/$HCVER if [ "${{ matrix.setup-method }}" = ghcup ]; then - HC=$HOME/.ghcup/bin/$HCKIND-$HCVER + HC=$("$HOME/.ghcup/bin/ghcup" whereis ghc "$HCVER") + HCPKG=$(echo "$HC" | sed 's#ghc$#ghc-pkg#') + HADDOCK=$(echo "$HC" | sed 's#ghc$#haddock#') echo "HC=$HC" >> "$GITHUB_ENV" - echo "HCPKG=$HOME/.ghcup/bin/$HCKIND-pkg-$HCVER" >> "$GITHUB_ENV" - echo "HADDOCK=$HOME/.ghcup/bin/haddock-$HCVER" >> "$GITHUB_ENV" - echo "CABAL=$HOME/.ghcup/bin/cabal-3.10.1.0 -vnormal+nowrap" >> "$GITHUB_ENV" + echo "HCPKG=$HCPKG" >> "$GITHUB_ENV" + echo "HADDOCK=$HADDOCK" >> "$GITHUB_ENV" + echo "CABAL=$HOME/.ghcup/bin/cabal-3.10.2.0 -vnormal+nowrap" >> "$GITHUB_ENV" else HC=$HCDIR/bin/$HCKIND echo "HC=$HC" >> "$GITHUB_ENV" echo "HCPKG=$HCDIR/bin/$HCKIND-pkg" >> "$GITHUB_ENV" echo "HADDOCK=$HCDIR/bin/haddock" >> "$GITHUB_ENV" - echo "CABAL=$HOME/.ghcup/bin/cabal-3.10.1.0 -vnormal+nowrap" >> "$GITHUB_ENV" + echo "CABAL=$HOME/.ghcup/bin/cabal-3.10.2.0 -vnormal+nowrap" >> "$GITHUB_ENV" fi HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\d+)\.(\d+)\.(\d+)(\.(\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))') @@ -244,6 +251,10 @@ jobs: cd ${PKGDIR_xmonad_contrib} || false ${CABAL} -vnormal check - name: haddock + run: | + $CABAL v2-haddock --disable-documentation $ARG_COMPILER --with-haddock $HADDOCK $ARG_TESTS $ARG_BENCH all + - name: haddock for hackage + if: matrix.upload run: | $CABAL v2-haddock $ARG_COMPILER --with-haddock $HADDOCK $ARG_TESTS $ARG_BENCH --haddock-for-hackage --builddir $GITHUB_WORKSPACE/haddock all - name: unconstrained build diff --git a/.github/workflows/hlint.yaml b/.github/workflows/hlint.yaml index 6979895c55..4ae6766485 100644 --- a/.github/workflows/hlint.yaml +++ b/.github/workflows/hlint.yaml @@ -8,15 +8,15 @@ jobs: hlint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: 'Set up HLint' - uses: haskell/actions/hlint-setup@v2 + uses: haskell-actions/hlint-setup@v2 with: version: '3.5' - name: 'Run HLint' - uses: haskell/actions/hlint-run@v2 + uses: haskell-actions/hlint-run@v2 with: path: '["XMonad/", "tests/", "scripts/"]' fail-on: status diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 59898413dc..b87b19479a 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -12,7 +12,7 @@ jobs: contents: read steps: - name: Install Nix - uses: cachix/install-nix-action@v22 + uses: cachix/install-nix-action@v26 with: install_url: https://nixos-nix-install-tests.cachix.org/serve/i6laym9jw3wg9mw6ncyrk6gjx4l34vvx/install install_options: '--tarball-url-prefix https://nixos-nix-install-tests.cachix.org/serve' @@ -20,7 +20,7 @@ jobs: experimental-features = nix-command flakes access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} - name: Clone project - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Build # "nix build" builds with full optimization and includes a profiling # build, so just the build of xmonad-contrib itself takes 3 minutes. diff --git a/.github/workflows/packdeps.yml b/.github/workflows/packdeps.yml index c74a1faea9..60a83794ce 100644 --- a/.github/workflows/packdeps.yml +++ b/.github/workflows/packdeps.yml @@ -13,16 +13,15 @@ jobs: steps: - name: Clone project - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Haskell - uses: haskell/actions/setup@v2 + uses: haskell-actions/setup@v2 with: # packdeps doesn't build with newer as of 2021-10 ghc-version: '8.8' - name: Install packdeps run: | set -ex - echo "$HOME/.cabal/bin" >> $GITHUB_PATH cd # go somewhere without a cabal.project cabal install packdeps - name: Check package bounds (all) @@ -43,10 +42,11 @@ jobs: *.cabal workflow-keepalive: + if: github.event_name == 'schedule' runs-on: ubuntu-latest + permissions: + actions: write steps: - - name: Re-enable workflow - env: + - uses: liskin/gh-workflow-keepalive@v1 + with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh api -X PUT repos/${{ github.repository }}/actions/workflows/packdeps.yml/enable diff --git a/.github/workflows/stack.yml b/.github/workflows/stack.yml index 43e72c822d..8081bd006c 100644 --- a/.github/workflows/stack.yml +++ b/.github/workflows/stack.yml @@ -31,7 +31,7 @@ jobs: steps: - name: Clone project - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install C dependencies run: | @@ -56,13 +56,13 @@ jobs: date +date=1-%Y-%m >> $GITHUB_OUTPUT - name: Cache Haskell package metadata - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.stack/pantry key: stack-pantry-${{ runner.os }}-${{ steps.cache-date.outputs.date }} - name: Cache Haskell dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.stack/* diff --git a/.mailmap b/.mailmap index db23ed451d..606c78c0bc 100644 --- a/.mailmap +++ b/.mailmap @@ -103,6 +103,7 @@ hexago.nl lithis lithis sam-barr -slotThe <50166980+slotThe@users.noreply.github.com> -slotThe +Tony Zorman <50166980+slotThe@users.noreply.github.com> +Tony Zorman +Tony Zorman spoonm diff --git a/CHANGES.md b/CHANGES.md index 8b75d296b3..e062f74066 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,45 @@ ### Breaking Changes + * `XMonad.Hooks.StatusBars` + + - Move status bar functions from the `IO` to the `X` monad to + allow them to look up information from `X`, like the screen + width. Existing configurations may need to use `io` from + `XMonad.Core` or `liftIO` from `Control.Monad.IO.Class` in + order to lift any existing `IO StatusBarConfig` values into + `X StatusBarConfig` values. + +### New Modules + + * `XMonad.Actions.Profiles`. + + - Group workspaces by similarity. Usefull when one has lots + of workspaces and uses only a couple per unit of work. + +### Bug Fixes and Minor Changes + + * Fix build-with-cabal.sh when XDG_CONFIG_HOME is defined. + + * `XMonad.Util.EZConfig` + + - Fixed `checkKeymap` warning that all keybindings are duplicates. + + * `XMonad.Hooks.ManageHelpers` + + - Added `isNotification` predicate to check for windows with + `_NET_WM_WINDOW_TYPE` property of `_NET_WM_WINDOW_TYPE_NOTIFICATION`. + +### Other changes + +## 0.18.0 (February 3, 2024) + +### Breaking Changes + + * Deprecated `XMonad.Layout.Cross` due to bitrot; refer to + `XMonad.Layout.Circle` and `XMonad.Layout.ThreeColumns` for + alternatives. + * Deprecated the `XMonad.Layout.StateFull` module and `XMonad.Layout.TrackFloating.(t|T)rackFloating` in favour of `XMonad.Layout.FocusTracking`. @@ -64,13 +103,19 @@ * `XMonad.Layout.Named` - - Deprecated the entire module, use `XMonad.Layout.Renamed` instead. + - Deprecated the entire module, use `XMonad.Layout.Renamed` (which newly + provides `named` for convenience) instead. * `XMonad.Actions.SinkAll` - Deprecated the entire module, use `XMonad.Actions.WithAll` instead. + * `XMonad.Layout.Circle`: + + - Deprecated the entire module, use the `circle` function from + `XMonad.Layout.CircleEx` instead. + * `XMonad.Hooks.EwmhDesktops` - `_NET_CLIENT_LIST_STACKING` puts windows in the current workspace at the @@ -79,6 +124,19 @@ ordered lexicographically, as before. Currently focused window will always be the topmost, meaning the last in the list. + * `XMonad.Util.NamedScratchpad` + + - Added `nsSingleScratchpadPerWorkspace`—a logHook to allow only one + active scratchpad per workspace. + + * `XMonad.Util.EZConfig` + + - The function `readKeySequence` now returns a non-empty list if it + succeeded. + + * Deprecate `XMonad.Util.Ungrab`; it was moved to `XMonad.Operations` + in core. + ### New Modules * `XMonad.Layout.CenterMainFluid` @@ -117,6 +175,17 @@ There's both an action to be bound to a key, and hooks that plug into `XMonad.Hooks.EwmhDesktops`. + * `XMonad.Layout.CircleEx`: + + - A new window layout, similar to X.L.Circle, but with more + possibilities for customisation. + + * `XMonad.Layout.DecorationEx`: + + - A new, more extensible, mechanism for window decorations, and some + standard types of decorations, including usual bar on top of window, + tabbed decorations and dwm-like decorations. + ### Bug Fixes and Minor Changes * `XMonad.Layout.Magnifier` @@ -207,10 +276,10 @@ * `XMonad.Actions.Search` - - Added `aur`, `flora`, `ncatlab`, `protondb`, `rosettacode`, - `sourcehut`, `steam`, `voidpgks_x86_64`, `voidpgks_x86_64_musl`, - `arXiv`, `clojureDocs`, `cratesIo`, `rustStd`, `noogle`, and - `zbmath` search engines. + - Added `aur`, `flora`, `ncatlab`, `protondb`, `rosettacode`, `sourcehut`, + `steam`, `voidpgks_x86_64`, `voidpgks_x86_64_musl`, `arXiv`, + `clojureDocs`, `cratesIo`, `rustStd`, `noogle`, `nixos`, `homeManager`, + and `zbmath` search engines. * `XMonad.Layout.ResizableThreeColumns` @@ -229,6 +298,10 @@ `XMonad.Actions.ToggleFullFloat` for a float-restoring implementation of fullscreening. + - Added `ewmhDesktops(Maybe)ManageHook` that places windows in their + preferred workspaces. This is useful when restoring a browser session + after a restart. + * `XMonad.Hooks.StatusBar` - Added `startAllStatusBars` to start the configured status bars. @@ -243,6 +316,13 @@ - The `emacsLikeXPKeymap` and `vimLikeXPKeymap` keymaps now treat `C-m` the same as `Return`. + - Added `prevCompletionKey` to `XPConfig`, facilitating the ability + to cycle through the completions backwards. This is bound to + `S-` by default. + + - The `vimLikeXPKeymap` now accepts the prompt upon pressing enter + in normal mode. + * `XMonad.Actions.Prefix` - Added `orIfPrefixed`, a combinator to decide upon an action based @@ -252,7 +332,15 @@ - Enabled prompt completion (from history) in `renameWorkspace`. -### Other changes + * `XMonad.Prompt.Pass` + + - Added `passOTPTypePrompt` to type out one-time-passwords via + `xdotool`. + + * `XMonad.Util.Stack` + + - Added `zipperFocusedAtFirstOf` to differentiate two lists into a + zipper. ## 0.17.1 (September 3, 2022) diff --git a/NIX.md b/NIX.md index 425f3a04a4..efbdfbbe34 100644 --- a/NIX.md +++ b/NIX.md @@ -103,3 +103,4 @@ The `prefix` option is used if you wish to select your haskell packages from within, e.g., unstable overlaid into `pkgs` as `pkgs.unstable`. See the flakes themselves and nix flake documentation for full detail. +Additionally, a semi-walkthrough is available [here](https://tony-zorman.com/posts/xmonad-on-nixos.html). diff --git a/XMonad/Actions/AfterDrag.hs b/XMonad/Actions/AfterDrag.hs index 4b158a7a4f..989f38a3c4 100644 --- a/XMonad/Actions/AfterDrag.hs +++ b/XMonad/Actions/AfterDrag.hs @@ -24,7 +24,7 @@ import XMonad import Data.Time (NominalDiffTime, diffUTCTime, getCurrentTime) -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Actions.AfterDrag -- diff --git a/XMonad/Actions/BluetileCommands.hs b/XMonad/Actions/BluetileCommands.hs index c8c345f7a6..4ed59b7fef 100644 --- a/XMonad/Actions/BluetileCommands.hs +++ b/XMonad/Actions/BluetileCommands.hs @@ -29,7 +29,7 @@ import System.Exit -- $usage -- --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Hooks.ServerMode -- > import XMonad.Actions.BluetileCommands diff --git a/XMonad/Actions/Commands.hs b/XMonad/Actions/Commands.hs index b957a01d99..cf1a1c737e 100644 --- a/XMonad/Actions/Commands.hs +++ b/XMonad/Actions/Commands.hs @@ -37,7 +37,7 @@ import XMonad.Prelude -- $usage -- --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Actions.Commands -- diff --git a/XMonad/Actions/ConstrainedResize.hs b/XMonad/Actions/ConstrainedResize.hs index 456b4e182e..0048be4497 100644 --- a/XMonad/Actions/ConstrainedResize.hs +++ b/XMonad/Actions/ConstrainedResize.hs @@ -26,7 +26,7 @@ import XMonad -- $usage -- --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import qualified XMonad.Actions.ConstrainedResize as Sqr -- diff --git a/XMonad/Actions/CopyWindow.hs b/XMonad/Actions/CopyWindow.hs index b2f37b5372..47b492984b 100644 --- a/XMonad/Actions/CopyWindow.hs +++ b/XMonad/Actions/CopyWindow.hs @@ -37,7 +37,7 @@ import qualified XMonad.StackSet as W -- $usage -- --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@ file: +-- You can use this module with the following in your @xmonad.hs@ file: -- -- > import XMonad.Actions.CopyWindow -- diff --git a/XMonad/Actions/CycleRecentWS.hs b/XMonad/Actions/CycleRecentWS.hs index 7bb417ea0f..880691314a 100644 --- a/XMonad/Actions/CycleRecentWS.hs +++ b/XMonad/Actions/CycleRecentWS.hs @@ -47,7 +47,7 @@ import Data.Function (on) import Control.Monad.State (lift) -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@ file: +-- You can use this module with the following in your @xmonad.hs@ file: -- -- > import XMonad.Actions.CycleRecentWS -- > diff --git a/XMonad/Actions/CycleSelectedLayouts.hs b/XMonad/Actions/CycleSelectedLayouts.hs index cf3db3ff03..ff461a92b5 100644 --- a/XMonad/Actions/CycleSelectedLayouts.hs +++ b/XMonad/Actions/CycleSelectedLayouts.hs @@ -23,7 +23,7 @@ import XMonad.Prelude (elemIndex, fromMaybe) import qualified XMonad.StackSet as S -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad -- > import XMonad.Actions.CycleSelectedLayouts @@ -39,8 +39,9 @@ cycleToNext lst a = do -- | If the current layout is in the list, cycle to the next layout. Otherwise, -- apply the first layout from list. cycleThroughLayouts :: [String] -> X () -cycleThroughLayouts lst = do +cycleThroughLayouts [] = pure () +cycleThroughLayouts lst@(x: _) = do winset <- gets windowset let ld = description . S.layout . S.workspace . S.current $ winset - let newld = fromMaybe (head lst) (cycleToNext lst ld) + let newld = fromMaybe x (cycleToNext lst ld) sendMessage $ JumpToLayout newld diff --git a/XMonad/Actions/CycleWS.hs b/XMonad/Actions/CycleWS.hs index bea471e9e4..fc603f4ae2 100644 --- a/XMonad/Actions/CycleWS.hs +++ b/XMonad/Actions/CycleWS.hs @@ -92,7 +92,7 @@ import XMonad.Util.Types import XMonad.Util.WorkspaceCompare -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@ file: +-- You can use this module with the following in your @xmonad.hs@ file: -- -- > import XMonad.Actions.CycleWS -- > diff --git a/XMonad/Actions/CycleWindows.hs b/XMonad/Actions/CycleWindows.hs index 1d647f217b..d987f79cc9 100644 --- a/XMonad/Actions/CycleWindows.hs +++ b/XMonad/Actions/CycleWindows.hs @@ -50,7 +50,6 @@ module XMonad.Actions.CycleWindows ( -- $pointer -- * Generic list rotations - -- $generic rotUp, rotDown ) where @@ -65,7 +64,7 @@ import Control.Arrow (second) import Control.Monad.Trans (lift) -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@ file: +-- You can use this module with the following in your @xmonad.hs@ file: -- -- > import XMonad.Actions.CycleWindows -- > -- config @@ -223,12 +222,3 @@ rotUnfocused' f s@(W.Stack _ [] _ ) = rotSlaves' f s -- Master h rotUnfocused' f (W.Stack t ls rs) = W.Stack t (reverse revls') rs' -- otherwise where (master :| revls) = NE.reverse (let l:ll = ls in l :| ll) (revls',rs') = splitAt (length ls) (f $ master:revls ++ rs) - --- $generic --- Generic list rotations such that @rotUp [1..4]@ is equivalent to --- @[2,3,4,1]@ and @rotDown [1..4]@ to @[4,1,2,3]@. They both are --- @id@ for null or singleton lists. -rotUp :: [a] -> [a] -rotUp l = drop 1 l ++ take 1 l -rotDown :: [a] -> [a] -rotDown = reverse . rotUp . reverse diff --git a/XMonad/Actions/DeManage.hs b/XMonad/Actions/DeManage.hs index c1cc4c8c59..11fa529ced 100644 --- a/XMonad/Actions/DeManage.hs +++ b/XMonad/Actions/DeManage.hs @@ -39,7 +39,7 @@ import qualified XMonad.StackSet as W import XMonad -- $usage --- To use demanage, add this import to your @~\/.xmonad\/xmonad.hs@: +-- To use demanage, add this import to your @xmonad.hs@: -- -- > import XMonad.Actions.DeManage -- diff --git a/XMonad/Actions/DwmPromote.hs b/XMonad/Actions/DwmPromote.hs index 2193932bd5..bbb03877e4 100644 --- a/XMonad/Actions/DwmPromote.hs +++ b/XMonad/Actions/DwmPromote.hs @@ -31,7 +31,7 @@ import qualified Data.List.NonEmpty as NE -- $usage -- --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Actions.DwmPromote -- diff --git a/XMonad/Actions/DynamicWorkspaceGroups.hs b/XMonad/Actions/DynamicWorkspaceGroups.hs index 2ec3b517a0..14e905e816 100644 --- a/XMonad/Actions/DynamicWorkspaceGroups.hs +++ b/XMonad/Actions/DynamicWorkspaceGroups.hs @@ -51,7 +51,7 @@ import qualified XMonad.Util.ExtensibleState as XS import XMonad.Actions.TopicSpace -- $usage --- You can use this module by importing it into your ~\/.xmonad\/xmonad.hs file: +-- You can use this module by importing it into your @xmonad.hs@ file: -- -- > import XMonad.Actions.DynamicWorkspaceGroups -- diff --git a/XMonad/Actions/DynamicWorkspaceOrder.hs b/XMonad/Actions/DynamicWorkspaceOrder.hs index e0ee5976c9..f937d75e5f 100644 --- a/XMonad/Actions/DynamicWorkspaceOrder.hs +++ b/XMonad/Actions/DynamicWorkspaceOrder.hs @@ -49,7 +49,7 @@ import XMonad.Prelude (fromJust, fromMaybe) import Data.Ord (comparing) -- $usage --- You can use this module by importing it into your ~\/.xmonad\/xmonad.hs file: +-- You can use this module by importing it into your @xmonad.hs@ file: -- -- > import qualified XMonad.Actions.DynamicWorkspaceOrder as DO -- diff --git a/XMonad/Actions/DynamicWorkspaces.hs b/XMonad/Actions/DynamicWorkspaces.hs index 46edcce2d1..b589bfbc54 100644 --- a/XMonad/Actions/DynamicWorkspaces.hs +++ b/XMonad/Actions/DynamicWorkspaces.hs @@ -44,7 +44,7 @@ import qualified Data.Map.Strict as Map import qualified XMonad.Util.ExtensibleState as XS -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@ file: +-- You can use this module with the following in your @xmonad.hs@ file: -- -- > import XMonad.Actions.DynamicWorkspaces -- > import XMonad.Actions.CopyWindow(copy) diff --git a/XMonad/Actions/EasyMotion.hs b/XMonad/Actions/EasyMotion.hs index 8f449f6ca7..a3aa66adb6 100644 --- a/XMonad/Actions/EasyMotion.hs +++ b/XMonad/Actions/EasyMotion.hs @@ -51,7 +51,7 @@ import qualified Data.Map.Strict as M (Map, elems, map, mapWithKey) -- $usage -- -- You can use this module's basic functionality with the following in your --- @~\/.xmonad\/xmonad.hs@: +-- @xmonad.hs@: -- -- > import XMonad.Actions.EasyMotion (selectWindow) -- @@ -387,5 +387,5 @@ handleKeyboard dpy drawFn cancel selected deselected = do _ -> handleKeyboard dpy drawFn cancel (trim fg) (clear bg) >>= retryBackspace where (fg, bg) = partition ((== Just keySym) . listToMaybe . chord) selected - trim = map (\o -> o { chord = tail $ chord o }) + trim = map (\o -> o { chord = drop 1 $ chord o }) clear = map (\o -> o { chord = [] }) diff --git a/XMonad/Actions/FindEmptyWorkspace.hs b/XMonad/Actions/FindEmptyWorkspace.hs index 488060612e..4866b8890b 100644 --- a/XMonad/Actions/FindEmptyWorkspace.hs +++ b/XMonad/Actions/FindEmptyWorkspace.hs @@ -25,7 +25,7 @@ import XMonad.StackSet -- $usage -- --- To use, import this module into your @~\/.xmonad\/xmonad.hs@: +-- To use, import this module into your @xmonad.hs@: -- -- > import XMonad.Actions.FindEmptyWorkspace -- diff --git a/XMonad/Actions/FlexibleManipulate.hs b/XMonad/Actions/FlexibleManipulate.hs index 11d253c3a8..ea16b32431 100644 --- a/XMonad/Actions/FlexibleManipulate.hs +++ b/XMonad/Actions/FlexibleManipulate.hs @@ -29,7 +29,7 @@ import qualified Prelude as P import Prelude (Double, Integer, Ord (..), const, fromIntegral, fst, id, otherwise, round, snd, uncurry, ($)) -- $usage --- First, add this import to your @~\/.xmonad\/xmonad.hs@: +-- First, add this import to your @xmonad.hs@: -- -- > import qualified XMonad.Actions.FlexibleManipulate as Flex -- diff --git a/XMonad/Actions/FlexibleResize.hs b/XMonad/Actions/FlexibleResize.hs index b6aa7848a1..916343dc19 100644 --- a/XMonad/Actions/FlexibleResize.hs +++ b/XMonad/Actions/FlexibleResize.hs @@ -25,7 +25,7 @@ import XMonad.Prelude (fi) import Foreign.C.Types -- $usage --- To use, first import this module into your @~\/.xmonad\/xmonad.hs@ file: +-- To use, first import this module into your @xmonad.hs@ file: -- -- > import qualified XMonad.Actions.FlexibleResize as Flex -- diff --git a/XMonad/Actions/FloatKeys.hs b/XMonad/Actions/FloatKeys.hs index b685b0439f..0c40d1c92a 100644 --- a/XMonad/Actions/FloatKeys.hs +++ b/XMonad/Actions/FloatKeys.hs @@ -30,7 +30,7 @@ import XMonad.Prelude (fi) import XMonad.Util.Types -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Actions.FloatKeys -- diff --git a/XMonad/Actions/FloatSnap.hs b/XMonad/Actions/FloatSnap.hs index ab77c377db..23d0224f9a 100644 --- a/XMonad/Actions/FloatSnap.hs +++ b/XMonad/Actions/FloatSnap.hs @@ -37,7 +37,7 @@ import XMonad.Util.Types (Direction2D(..)) import XMonad.Actions.AfterDrag -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Actions.FloatSnap -- diff --git a/XMonad/Actions/FocusNth.hs b/XMonad/Actions/FocusNth.hs index d6511c32cc..b9e8146584 100644 --- a/XMonad/Actions/FocusNth.hs +++ b/XMonad/Actions/FocusNth.hs @@ -25,7 +25,7 @@ import XMonad.Prelude import XMonad.StackSet -- $usage --- Add the import to your @~\/.xmonad\/xmonad.hs@: +-- Add the import to your @xmonad.hs@: -- -- > import XMonad.Actions.FocusNth -- diff --git a/XMonad/Actions/GridSelect.hs b/XMonad/Actions/GridSelect.hs index 8c80a0c780..8ef7e2f4e2 100644 --- a/XMonad/Actions/GridSelect.hs +++ b/XMonad/Actions/GridSelect.hs @@ -97,10 +97,11 @@ import XMonad.Actions.WindowBringer (bringWindow) import Text.Printf import System.Random (mkStdGen, randomR) import Data.Word (Word8) +import qualified Data.List.NonEmpty as NE -- $usage -- --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Actions.GridSelect -- @@ -302,14 +303,14 @@ diamondLayer n = r = tr ++ map (\(x,y) -> (y,-x)) tr in r ++ map (negate *** negate) r -diamond :: (Enum a, Num a, Eq a) => [(a, a)] -diamond = concatMap diamondLayer [0..] +diamond :: (Enum a, Num a, Eq a) => Stream (a, a) +diamond = fromList $ concatMap diamondLayer [0..] diamondRestrict :: Integer -> Integer -> Integer -> Integer -> [(Integer, Integer)] diamondRestrict x y originX originY = L.filter (\(x',y') -> abs x' <= x && abs y' <= y) . map (\(x', y') -> (x' + fromInteger originX, y' + fromInteger originY)) . - take 1000 $ diamond + takeS 1000 $ diamond findInElementMap :: (Eq a) => a -> [(a, b)] -> Maybe (a, b) findInElementMap pos = find ((== pos) . fst) @@ -658,7 +659,7 @@ gridselect gsconfig elements = originPosX = floor $ (gs_originFractX gsconfig - (1/2)) * 2 * fromIntegral restrictX originPosY = floor $ (gs_originFractY gsconfig - (1/2)) * 2 * fromIntegral restrictY coords = diamondRestrict restrictX restrictY originPosX originPosY - s = TwoDState { td_curpos = head coords, + s = TwoDState { td_curpos = NE.head (notEmpty coords), td_availSlots = coords, td_elements = elements, td_gsconfig = gsconfig, diff --git a/XMonad/Actions/GroupNavigation.hs b/XMonad/Actions/GroupNavigation.hs index ff0410d904..1fe9f581b9 100644 --- a/XMonad/Actions/GroupNavigation.hs +++ b/XMonad/Actions/GroupNavigation.hs @@ -53,7 +53,7 @@ import qualified XMonad.Util.ExtensibleState as XS {- $usage -Import the module into your @~\/.xmonad\/xmonad.hs@: +Import the module into your @xmonad.hs@: > import XMonad.Actions.GroupNavigation @@ -129,7 +129,7 @@ focusNextMatchOrDo qry act = findM (runQuery qry) >=> maybe act (windows . SS.focusWindow) -- Returns the list of windows ordered by workspace as specified in --- ~/.xmonad/xmonad.hs +-- @xmonad.hs@. orderedWindowList :: Direction -> X (Seq Window) orderedWindowList History = fmap (\(HistoryDB w ws) -> maybe ws (ws |>) w) XS.get orderedWindowList dir = withWindowSet $ \ss -> do @@ -145,7 +145,7 @@ orderedWindowList dir = withWindowSet $ \ss -> do dirfun _ = id rotfun wins x = rotate $ rotateTo (== x) wins --- Returns the ordered workspace list as specified in ~/.xmonad/xmonad.hs +-- Returns the ordered workspace list as specified in @xmonad.hs@. orderedWorkspaceList :: WindowSet -> Seq String -> Seq WindowSpace orderedWorkspaceList ss wsids = rotateTo isCurWS wspcs' where diff --git a/XMonad/Actions/LinkWorkspaces.hs b/XMonad/Actions/LinkWorkspaces.hs index a2aa888081..721149b42d 100644 --- a/XMonad/Actions/LinkWorkspaces.hs +++ b/XMonad/Actions/LinkWorkspaces.hs @@ -36,7 +36,7 @@ import qualified Data.Map as M ( insert, delete, Map, lookup, empty, filter ) -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@ file: +-- You can use this module with the following in your @xmonad.hs@ file: -- -- > import XMonad.Actions.LinkWorkspaces -- diff --git a/XMonad/Actions/MessageFeedback.hs b/XMonad/Actions/MessageFeedback.hs index 58df3721b1..39c403d711 100644 --- a/XMonad/Actions/MessageFeedback.hs +++ b/XMonad/Actions/MessageFeedback.hs @@ -47,13 +47,13 @@ module XMonad.Actions.MessageFeedback import XMonad ( Window ) import XMonad.Core ( X(), Message, SomeMessage(..), LayoutClass(..), windowset, catchX, WorkspaceId, Layout, whenJust ) import XMonad.Operations ( updateLayout, windowBracket, modifyWindowSet ) -import XMonad.Prelude ( isJust, liftA2, void ) +import XMonad.Prelude import XMonad.StackSet ( Workspace, current, workspace, layout, tag ) import Control.Monad.State ( gets ) -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Actions.MessageFeedback -- diff --git a/XMonad/Actions/MostRecentlyUsed.hs b/XMonad/Actions/MostRecentlyUsed.hs index c183805e0b..2ac6b6a708 100644 --- a/XMonad/Actions/MostRecentlyUsed.hs +++ b/XMonad/Actions/MostRecentlyUsed.hs @@ -31,14 +31,8 @@ module XMonad.Actions.MostRecentlyUsed ( ) where -- base -import Data.Maybe (fromMaybe) -import Data.List.NonEmpty (NonEmpty(..), nonEmpty) -import Data.Monoid (All(..), Any) -import Data.Foldable (for_) -import Data.Functor (($>)) +import Data.List.NonEmpty (nonEmpty) import Data.IORef (newIORef, readIORef, writeIORef, modifyIORef) -import Control.Applicative (liftA2) -import Control.Monad (when, unless, join) import Control.Monad.IO.Class (MonadIO) -- mtl @@ -68,6 +62,7 @@ import XMonad.Util.PureX (handlingRefresh, curScreenId, curTag, greedyView, view, peek, focusWindow) import XMonad.Util.History (History, origin, event, erase, ledger) import XMonad.Actions.Repeatable (repeatableSt) +import XMonad.Prelude -- }}} @@ -208,20 +203,3 @@ winHistEH ev = All True <$ case ev of where collect w = XS.modify $ \wh@WinHist{hist} -> wh{ hist = erase w hist } -- }}} - --- --< Auxiliary Data Type: Stream >-- {{{ - --- To satisfy the almighty exhaustivity checker. - -data Stream a = !a :~ Stream a -infixr 5 :~ - -(+~) :: [a] -> Stream a -> Stream a -xs +~ s = foldr (:~) s xs -infixr 5 +~ - -cycleS :: NonEmpty a -> Stream a -cycleS (x :| xs) = s where s = x :~ xs +~ s - --- }}} - diff --git a/XMonad/Actions/MouseGestures.hs b/XMonad/Actions/MouseGestures.hs index f57b2b2eac..8cc066ebdf 100644 --- a/XMonad/Actions/MouseGestures.hs +++ b/XMonad/Actions/MouseGestures.hs @@ -32,7 +32,7 @@ import Data.Map (Map) -- $usage -- --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Actions.MouseGestures -- > import qualified XMonad.StackSet as W diff --git a/XMonad/Actions/MouseResize.hs b/XMonad/Actions/MouseResize.hs index c226120200..8278dc2ad2 100644 --- a/XMonad/Actions/MouseResize.hs +++ b/XMonad/Actions/MouseResize.hs @@ -37,7 +37,7 @@ import XMonad.Util.XUtils -- "XMonad.Layout.SimpleFloat" or "XMonad.Layout.DecorationMadness". -- -- You can use this module with the following in your --- @~\/.xmonad\/xmonad.hs@: +-- @xmonad.hs@: -- -- > import XMonad.Actions.MouseResize -- > import XMonad.Layout.WindowArranger diff --git a/XMonad/Actions/Navigation2D.hs b/XMonad/Actions/Navigation2D.hs index 6cb59874f9..e067791fc4 100644 --- a/XMonad/Actions/Navigation2D.hs +++ b/XMonad/Actions/Navigation2D.hs @@ -66,6 +66,7 @@ import qualified XMonad.StackSet as W import qualified XMonad.Util.ExtensibleState as XS import XMonad.Util.EZConfig (additionalKeys, additionalKeysP) import XMonad.Util.Types +import qualified Data.List.NonEmpty as NE -- $usage -- #Usage# @@ -84,7 +85,7 @@ import XMonad.Util.Types -- layers and allows customization of the navigation strategy for the tiled -- layer based on the layout currently in effect. -- --- You can use this module with (a subset of) the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with (a subset of) the following in your @xmonad.hs@: -- -- > import XMonad.Actions.Navigation2D -- @@ -783,8 +784,7 @@ doCenterNavigation dir (cur, rect) winrects -- All the points that coincide with the current center and succeed it -- in the (appropriately ordered) window stack. - onCtr' = L.tail $ L.dropWhile ((cur /=) . fst) onCtr - -- tail should be safe here because cur should be in onCtr + onCtr' = L.drop 1 $ L.dropWhile ((cur /=) . fst) onCtr -- All the points that do not coincide with the current center and which -- lie in the (rotated) right cone. @@ -884,8 +884,8 @@ swap win winset = W.focusWindow cur -- Reconstruct the workspaces' window stacks to reflect the swap. newvisws = zipWith (\ws wns -> ws { W.stack = W.differentiate wns }) visws newwins newscrs = zipWith (\scr ws -> scr { W.workspace = ws }) scrs newvisws - newwinset = winset { W.current = head newscrs - , W.visible = tail newscrs + newwinset = winset { W.current = NE.head (notEmpty newscrs) -- Always at least one screen. + , W.visible = drop 1 newscrs } -- | Calculates the center of a rectangle diff --git a/XMonad/Actions/OnScreen.hs b/XMonad/Actions/OnScreen.hs index dff88dba28..375a510706 100644 --- a/XMonad/Actions/OnScreen.hs +++ b/XMonad/Actions/OnScreen.hs @@ -27,7 +27,7 @@ module XMonad.Actions.OnScreen ( ) where import XMonad -import XMonad.Prelude (fromMaybe, guard) +import XMonad.Prelude (fromMaybe, guard, empty) import XMonad.StackSet hiding (new) @@ -140,16 +140,15 @@ toggleOrView' f i st = fromMaybe (f i st) $ do let st' = hidden st -- make sure we actually have to do something guard $ i == (tag . workspace $ current st) - guard $ not (null st') - -- finally, toggle! - return $ f (tag . head $ st') st - + case st' of + [] -> empty + (h : _) -> return $ f (tag h) st -- finally, toggle! -- $usage -- -- This module provides an easy way to control, what you see on other screens in -- xinerama mode without having to focus them. Put this into your --- @~\/.xmonad\/xmonad.hs@: +-- @xmonad.hs@: -- -- > import XMonad.Actions.OnScreen -- diff --git a/XMonad/Actions/PerLayoutKeys.hs b/XMonad/Actions/PerLayoutKeys.hs index b4b267ddbe..92f18490b4 100644 --- a/XMonad/Actions/PerLayoutKeys.hs +++ b/XMonad/Actions/PerLayoutKeys.hs @@ -25,7 +25,7 @@ import XMonad.StackSet as S -- $usage -- --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Actions.PerLayoutKeys -- diff --git a/XMonad/Actions/PerWindowKeys.hs b/XMonad/Actions/PerWindowKeys.hs index 95128b649f..b556573859 100644 --- a/XMonad/Actions/PerWindowKeys.hs +++ b/XMonad/Actions/PerWindowKeys.hs @@ -24,7 +24,7 @@ import XMonad -- $usage -- --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Actions.PerWindowKeys -- diff --git a/XMonad/Actions/PerWorkspaceKeys.hs b/XMonad/Actions/PerWorkspaceKeys.hs index f3d95081e9..4bd41f3c2c 100644 --- a/XMonad/Actions/PerWorkspaceKeys.hs +++ b/XMonad/Actions/PerWorkspaceKeys.hs @@ -25,7 +25,7 @@ import XMonad.StackSet as S -- $usage -- --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Actions.PerWorkspaceKeys -- diff --git a/XMonad/Actions/PhysicalScreens.hs b/XMonad/Actions/PhysicalScreens.hs index 4c321c9613..da93b021e6 100644 --- a/XMonad/Actions/PhysicalScreens.hs +++ b/XMonad/Actions/PhysicalScreens.hs @@ -46,7 +46,7 @@ To create a screen comparator you can use screenComparatorByRectangle or screenC The default ScreenComparator orders screens by the upper-left-most corner, from top-to-bottom and then left-to-right. -Example usage in your @~\/.xmonad\/xmonad.hs@ file: +Example usage in your @xmonad.hs@ file: > import XMonad.Actions.PhysicalScreens > import Data.Default diff --git a/XMonad/Actions/Plane.hs b/XMonad/Actions/Plane.hs index dd692d80ee..9cdd5a7fcd 100644 --- a/XMonad/Actions/Plane.hs +++ b/XMonad/Actions/Plane.hs @@ -41,13 +41,13 @@ module XMonad.Actions.Plane import Data.Map (Map, fromList) -import XMonad.Prelude +import XMonad.Prelude hiding (fromList) import XMonad import XMonad.StackSet hiding (workspaces) import XMonad.Util.Run -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@ file: +-- You can use this module with the following in your @xmonad.hs@ file: -- -- > import XMonad.Actions.Plane -- > import Data.Map (union) diff --git a/XMonad/Actions/Prefix.hs b/XMonad/Actions/Prefix.hs index 3890416bcb..8ea5afb9a9 100644 --- a/XMonad/Actions/Prefix.hs +++ b/XMonad/Actions/Prefix.hs @@ -41,6 +41,8 @@ import XMonad.Util.ExtensibleState as XS import XMonad.Util.Paste (sendKey) import XMonad.Actions.Submap (submapDefaultWithKey) import XMonad.Util.EZConfig (readKeySequence) +import qualified Data.List.NonEmpty as NE +import Data.List.NonEmpty ((<|)) {- $usage @@ -129,11 +131,11 @@ usePrefixArgument :: LayoutClass l Window -> XConfig l -> XConfig l usePrefixArgument prefix conf = - conf{ keys = M.insert binding (handlePrefixArg [binding]) . keys conf } + conf{ keys = M.insert binding (handlePrefixArg (binding :| [])) . keys conf } where binding = case readKeySequence conf prefix of - Just [key] -> key - _ -> (controlMask, xK_u) + Just (key :| []) -> key + _ -> (controlMask, xK_u) -- | Set Prefix up with default prefix key (C-u). useDefaultPrefixArgument :: LayoutClass l Window @@ -141,7 +143,7 @@ useDefaultPrefixArgument :: LayoutClass l Window -> XConfig l useDefaultPrefixArgument = usePrefixArgument "C-u" -handlePrefixArg :: [(KeyMask, KeySym)] -> X () +handlePrefixArg :: NonEmpty (KeyMask, KeySym) -> X () handlePrefixArg events = do ks <- asks keyActions logger <- asks (logHook . config) @@ -162,12 +164,12 @@ handlePrefixArg events = do Raw _ -> XS.put $ Numeric x Numeric a -> XS.put $ Numeric $ a * 10 + x None -> return () -- should never happen - handlePrefixArg (key:events) + handlePrefixArg (key <| events) else do prefix <- XS.get mapM_ (uncurry sendKey) $ case prefix of - Raw a -> replicate a (head events) ++ [key] - _ -> reverse (key:events) + Raw a -> replicate a (NE.head events) ++ [key] + _ -> reverse (key : toList events) keyToNum = (xK_0, 0) : zip [xK_1 .. xK_9] [1..9] -- | Turn a prefix-aware X action into an X-action. diff --git a/XMonad/Actions/Profiles.hs b/XMonad/Actions/Profiles.hs new file mode 100644 index 0000000000..9d49b0e787 --- /dev/null +++ b/XMonad/Actions/Profiles.hs @@ -0,0 +1,545 @@ +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE DerivingVia #-} + + +-------------------------------------------------------------------------------- +-- | +-- Module : XMonad.Actions.Profiles +-- Description : Group your workspaces by similarity. +-- Copyright : (c) Mislav Zanic +-- License : BSD3-style (see LICENSE) +-- +-- Maintainer : Mislav Zanic +-- Stability : experimental +-- Portability : unportable +-- +-------------------------------------------------------------------------------- + +module XMonad.Actions.Profiles + ( -- * Overview + -- $overview + + -- * Usage + -- $usage + + -- * Types + ProfileId + , Profile(..) + , ProfileConfig(..) + + -- * Hooks + , addProfiles + , addProfilesWithHistory + + -- * Switching profiles + , switchToProfile + + -- * Workspace navigation and keybindings + , wsFilter + , bindOn + + -- * Loggers and pretty printers + , excludeWSPP + , profileLogger + + -- * Prompts + , switchProfilePrompt + , addWSToProfilePrompt + , removeWSFromProfilePrompt + , switchProfileWSPrompt + , shiftProfileWSPrompt + + -- * Utilities + , currentProfile + , profileIds + , previousProfile + , profileHistory + , allProfileWindows + , profileWorkspaces + )where + +-------------------------------------------------------------------------------- +import Data.Map.Strict (Map) +import Data.List +import qualified Data.Map.Strict as Map + +import Control.DeepSeq + +import XMonad +import XMonad.Prelude +import qualified XMonad.StackSet as W + +import XMonad.Actions.CycleWS + +import qualified XMonad.Util.ExtensibleState as XS +import XMonad.Util.Loggers (Logger) +import XMonad.Prompt.Window (XWindowMap) +import XMonad.Actions.WindowBringer (WindowBringerConfig(..)) +import XMonad.Actions.OnScreen (greedyViewOnScreen) +import XMonad.Hooks.Rescreen (addAfterRescreenHook) +import XMonad.Hooks.DynamicLog (PP(ppRename)) +import XMonad.Prompt + +-------------------------------------------------------------------------------- +-- $overview +-- This module allows you to group your workspaces into 'Profile's based on certain similarities. +-- The idea is to expand upon the philosophy set by "XMonad.Actions.TopicSpace" +-- which states that you can look at a topic/workspace as a +-- single unit of work instead of multiple related units of work. +-- This comes in handy if you have lots of workspaces with windows open and need only to +-- work with a few of them at a time. With 'Profile's, you can focus on those few workspaces that +-- require your attention by not displaying, or allowing you to switch to the rest of the workspaces. +-- The best example is having a profile for development and a profile for leisure activities. + +-------------------------------------------------------------------------------- +-- $usage +-- To use @Profiles@ you need to add it to your XMonad configuration +-- and configure your profiles. +-- +-- First you'll need to handle the imports. +-- +-- > import XMonad.Actions.Profiles +-- > import XMonad.Util.EZConfig -- for keybindings +-- > import qualified XMonad.StackSet as W +-- > import qualified XMonad.Actions.DynamicWorkspaceOrder as DO -- for workspace navigation +-- +-- Next you'll need to define your profiles. +-- +-- > myStartingProfile :: ProfileId +-- > myStartingProfile = "Work" +-- > +-- > myProfiles :: [Profile] +-- > myProfiles = +-- > [ Profile { profileId = "Home" +-- > , profileWS = [ "www" +-- > , "rss" +-- > , "vid" +-- > , "vms" +-- > , "writing" +-- > , "notes" +-- > ] +-- > } +-- > , Profile { profileId = "Work" +-- > , profileWS = [ "www" +-- > , "slack" +-- > , "dev" +-- > , "k8s" +-- > , "notes" +-- > ] +-- > } +-- > ] +-- +-- So, while using @Home@ 'Profile', you'll only be able to see, navigate to and +-- do actions with @["www", "rss", "vid", "vms", "writing", "notes"]@ workspaces. +-- +-- You may also need to define some keybindings. Since @M-1@ .. @M-9@ are +-- sensible keybindings for switching workspaces, you'll need to use +-- 'bindOn' to have different keybindings per profile. +-- Here, we'll use "XMonad.Util.EZConfig" syntax: +-- +-- > myKeys :: [(String, X())] +-- > myKeys = +-- > [ ("M-p", switchProfilePrompt xpConfig) +-- > , ("M-g", switchProfileWSPrompt xpConfig) +-- > , ("M1-j", DO.moveTo Next wsFilter) +-- > , ("M1-k", DO.moveTo Prev wsFilter) +-- > ] +-- > <> +-- > [ ("M-" ++ m ++ k, bindOn $ map (\x -> (fst x, f $ snd x)) i) +-- > | (i, k) <- map (\(x:xs) -> (map fst (x:xs), snd x)) $ sortGroupBy snd tupleList +-- > , (f, m) <- [(mby $ windows . W.greedyView, ""), (mby $ windows . W.shift, "S-")] +-- > ] +-- > where +-- > mby f wid = if wid == "" then return () else f wid +-- > sortGroupBy f = groupBy (\ x y -> f x == f y) . sortBy (\x y -> compare (f x) (f y)) +-- > tupleList = concatMap (\p -> zip (map (\wid -> (profileId p, wid)) (profileWS p <> repeat "")) (map show [1..9 :: Int])) myProfiles +-- +-- After that, you'll need to hook @Profiles@ into your XMonad config: +-- +-- > main = xmonad $ addProfiles def { profiles = myProfiles +-- > , startingProfile = myStartingProfile +-- > } +-- > $ def `additionalKeysP` myKeys +-- + +-------------------------------------------------------------------------------- +type ProfileId = String +type ProfileMap = Map ProfileId Profile + +-------------------------------------------------------------------------------- +-- | Profile representation. +data Profile = Profile + { profileId :: !ProfileId -- ^ Profile name. + , profileWS :: ![WorkspaceId] -- ^ A list of workspaces contained within a profile. + } + +-------------------------------------------------------------------------------- +-- | Internal profile state. +data ProfileState = ProfileState + { profilesMap :: !ProfileMap + , current :: !(Maybe Profile) + , previous :: !(Maybe ProfileId) + } + +-------------------------------------------------------------------------------- +-- | User config for profiles. +data ProfileConfig = ProfileConfig + { workspaceExcludes :: ![WorkspaceId] -- ^ A list of workspaces to exclude from the @profileHistoryHook@. + , profiles :: ![Profile] -- ^ A list of user-defined profiles. + , startingProfile :: !ProfileId -- ^ Profile shown on startup. + } + +-------------------------------------------------------------------------------- +instance Default ProfileConfig where + def = ProfileConfig { workspaceExcludes = [] + , profiles = [] + , startingProfile = "" + } + +-------------------------------------------------------------------------------- +instance ExtensionClass ProfileState where + initialValue = ProfileState Map.empty Nothing Nothing + +-------------------------------------------------------------------------------- +-- Internal type for history tracking. +-- Main problem with @XMonad.Hooks.HistoryHook@ is that it isn't profile aware. +-- Because of that, when switching to a previous workspace, you might switch to +-- a workspace +newtype ProfileHistory = ProfileHistory + { history :: Map ProfileId [(ScreenId, WorkspaceId)] + } + deriving (Read, Show) + deriving NFData via Map ProfileId [(Int, WorkspaceId)] + +-------------------------------------------------------------------------------- +instance ExtensionClass ProfileHistory where + extensionType = PersistentExtension + initialValue = ProfileHistory Map.empty + +-------------------------------------------------------------------------------- +newtype ProfilePrompt = ProfilePrompt String + +-------------------------------------------------------------------------------- +instance XPrompt ProfilePrompt where + showXPrompt (ProfilePrompt x) = x + +-------------------------------------------------------------------------------- +defaultProfile :: Profile +defaultProfile = defaultProfile + +-------------------------------------------------------------------------------- +-- | Returns current profile. +currentProfile :: X ProfileId +currentProfile = profileId . fromMaybe defaultProfile . current <$> XS.get + +-------------------------------------------------------------------------------- +-- | Returns previous profile. +previousProfile :: X (Maybe ProfileId) +previousProfile = XS.gets previous + +-------------------------------------------------------------------------------- +-- | Returns the history of viewed workspaces per profile. +profileHistory :: X (Map ProfileId [(ScreenId, WorkspaceId)]) +profileHistory = XS.gets history + +-------------------------------------------------------------------------------- +profileMap :: X ProfileMap +profileMap = XS.gets profilesMap + +-------------------------------------------------------------------------------- +-- | Returns ids of all profiles. +profileIds :: X [ProfileId] +profileIds = Map.keys <$> XS.gets profilesMap + +-------------------------------------------------------------------------------- +currentProfileWorkspaces :: X [WorkspaceId] +currentProfileWorkspaces = XS.gets current <&> profileWS . fromMaybe defaultProfile + +-------------------------------------------------------------------------------- +-- | Hook profiles into XMonad. This function adds a startup hook that +-- sets up ProfileState. Also adds an afterRescreenHook for viewing correct +-- workspaces when adding new screens. +addProfiles :: ProfileConfig -> XConfig a -> XConfig a +addProfiles profConf conf = addAfterRescreenHook hook $ conf + { startupHook = profileStartupHook' <> startupHook conf + } + where + profileStartupHook' :: X() + profileStartupHook' = profilesStartupHook (profiles profConf) (startingProfile profConf) + hook = currentProfile >>= switchWSOnScreens + +-------------------------------------------------------------------------------- +-- | Hooks profiles into XMonad and enables Profile history logging. +addProfilesWithHistory :: ProfileConfig -> XConfig a -> XConfig a +addProfilesWithHistory profConf conf = conf' + { logHook = profileHistoryHookExclude (workspaceExcludes profConf) <> logHook conf + } + where + conf' = addProfiles profConf conf + +-------------------------------------------------------------------------------- +profileHistoryHookExclude :: [WorkspaceId] -> X() +profileHistoryHookExclude ews = do + cur <- gets $ W.current . windowset + vis <- gets $ W.visible . windowset + pws <- currentProfileWorkspaces + p <- currentProfile + + updateHist p $ workspaceScreenPairs $ filterWS pws $ cur:vis + where + workspaceScreenPairs wins = zip (W.screen <$> wins) (W.tag . W.workspace <$> wins) + filterWS pws = filter ((\wid -> (wid `elem` pws) && (wid `notElem` ews)) . W.tag . W.workspace) + +-------------------------------------------------------------------------------- +updateHist :: ProfileId -> [(ScreenId, WorkspaceId)] -> X() +updateHist pid xs = profileWorkspaces pid >>= XS.modify' . update + where + update pws hs = force $ hs { history = doUpdate pws $ history hs } + + doUpdate pws hist = foldl (\acc (sid, wid) -> Map.alter (f pws sid wid) pid acc) hist xs + + f pws sid wid val = case val of + Nothing -> pure [(sid, wid)] + Just hs -> pure $ let new = (sid, wid) in new:filterWS pws new hs + + filterWS :: [WorkspaceId] -> (ScreenId, WorkspaceId) -> [(ScreenId, WorkspaceId)] -> [(ScreenId, WorkspaceId)] + filterWS pws new = filter (\x -> snd x `elem` pws && x /= new) + +-------------------------------------------------------------------------------- +-- | Adds profiles to ProfileState and sets current profile using . + +profilesStartupHook :: [Profile] -> ProfileId -> X () +profilesStartupHook ps pid = XS.modify go >> switchWSOnScreens pid + where + go :: ProfileState -> ProfileState + go s = s {profilesMap = update $ profilesMap s, current = setCurrentProfile $ Map.fromList $ map entry ps} + + update :: ProfileMap -> ProfileMap + update = Map.union (Map.fromList $ map entry ps) + + entry :: Profile -> (ProfileId, Profile) + entry p = (profileId p, p) + + setCurrentProfile :: ProfileMap -> Maybe Profile + setCurrentProfile s = case Map.lookup pid s of + Nothing -> Just $ Profile pid [] + Just pn -> Just pn + +-------------------------------------------------------------------------------- +setPrevious :: ProfileId -> X() +setPrevious name = XS.modify update + where + update ps = ps { previous = doUpdate ps } + doUpdate ps = case Map.lookup name $ profilesMap ps of + Nothing -> previous ps + Just p -> Just $ profileId p + +-------------------------------------------------------------------------------- +setProfile :: ProfileId -> X () +setProfile p = currentProfile >>= setPrevious >> setProfile' p + +-------------------------------------------------------------------------------- +setProfile' :: ProfileId -> X () +setProfile' name = XS.modify update + where + update ps = ps { current = doUpdate ps } + doUpdate ps = case Map.lookup name $ profilesMap ps of + Nothing -> current ps + Just p -> Just p + +-------------------------------------------------------------------------------- +-- | Switch to a profile. +switchToProfile :: ProfileId -> X() +switchToProfile pid = setProfile pid >> switchWSOnScreens pid + +-------------------------------------------------------------------------------- +-- | Returns the workspace ids associated with a profile id. +profileWorkspaces :: ProfileId -> X [WorkspaceId] +profileWorkspaces pid = profileMap >>= findPWs + where + findPWs pm = return . profileWS . fromMaybe defaultProfile $ Map.lookup pid pm + +-------------------------------------------------------------------------------- +-- | Prompt for adding a workspace id to a profile. +addWSToProfilePrompt :: XPConfig -> X() +addWSToProfilePrompt c = do + ps <- profileIds + mkXPrompt (ProfilePrompt "Add ws to profile:") c (mkComplFunFromList' c ps) f + where + f :: String -> X() + f p = do + vis <- gets $ fmap (W.tag . W.workspace) . W.visible . windowset + cur <- gets $ W.tag . W.workspace . W.current . windowset + hid <- gets $ fmap W.tag . W.hidden . windowset + let + arr = cur:(vis <> hid) + in mkXPrompt (ProfilePrompt "Ws to add to profile:") c (mkComplFunFromList' c arr) (`addWSToProfile` p) + +-------------------------------------------------------------------------------- +-- | Prompt for switching profiles. +switchProfilePrompt :: XPConfig -> X() +switchProfilePrompt c = do + ps <- profileIds + mkXPrompt (ProfilePrompt "Profile: ") c (mkComplFunFromList' c ps) switchToProfile + +-------------------------------------------------------------------------------- +-- | Prompt for switching workspaces. +switchProfileWSPrompt :: XPConfig -> X () +switchProfileWSPrompt c = mkPrompt =<< currentProfileWorkspaces + where + mkPrompt pws = mkXPrompt (ProfilePrompt "Switch to workspace:") c (mkComplFunFromList' c pws) mbygoto + mbygoto wid = do + pw <- profileWorkspaces =<< currentProfile + unless (wid `notElem` pw) (windows . W.greedyView $ wid) + +-------------------------------------------------------------------------------- +-- | Prompt for shifting windows to a different workspace. +shiftProfileWSPrompt :: XPConfig -> X () +shiftProfileWSPrompt c = mkPrompt =<< currentProfileWorkspaces + where + mkPrompt pws = mkXPrompt (ProfilePrompt "Send window to workspace:") c (mkComplFunFromList' c pws) mbyshift + mbyshift wid = do + pw <- profileWorkspaces =<< currentProfile + unless (wid `notElem` pw) (windows . W.shift $ wid) + +-------------------------------------------------------------------------------- +addWSToProfile :: WorkspaceId -> ProfileId -> X() +addWSToProfile wid pid = XS.modify go + where + go :: ProfileState -> ProfileState + go ps = ps {profilesMap = update $ profilesMap ps, current = update' $ fromMaybe defaultProfile $ current ps} + + update :: ProfileMap -> ProfileMap + update mp = case Map.lookup pid mp of + Nothing -> mp + Just p -> if wid `elem` profileWS p then mp else Map.adjust f pid mp + + f :: Profile -> Profile + f p = Profile pid (wid : profileWS p) + + update' :: Profile -> Maybe Profile + update' cp = if profileId cp == pid && wid `notElem` profileWS cp then Just (Profile pid $ wid:profileWS cp) else Just cp + +-------------------------------------------------------------------------------- +-- | Prompt for removing a workspace from a profile. +removeWSFromProfilePrompt :: XPConfig -> X() +removeWSFromProfilePrompt c = do + ps <- profileIds + mkXPrompt (ProfilePrompt "Remove ws from profile:") c (mkComplFunFromList' c ps) f + where + f :: String -> X() + f p = do + arr <- profileWorkspaces p + mkXPrompt (ProfilePrompt "Ws to remove from profile:") c (mkComplFunFromList' c arr) $ + \ws -> do + cp <- currentProfile + ws `removeWSFromProfile` p + when (cp == p) $ currentProfile >>= switchWSOnScreens + +-------------------------------------------------------------------------------- +removeWSFromProfile :: WorkspaceId -> ProfileId -> X() +removeWSFromProfile wid pid = XS.modify go + where + go :: ProfileState -> ProfileState + go ps = ps {profilesMap = update $ profilesMap ps, current = update' $ fromMaybe defaultProfile $ current ps} + + update :: ProfileMap -> ProfileMap + update mp = case Map.lookup pid mp of + Nothing -> mp + Just p -> if wid `elem` profileWS p then Map.adjust f pid mp else mp + + f :: Profile -> Profile + f p = Profile pid (delete wid $ profileWS p) + + update' :: Profile -> Maybe Profile + update' cp = if profileId cp == pid && wid `elem` profileWS cp then Just (Profile pid $ delete wid $ profileWS cp) else Just cp + +-------------------------------------------------------------------------------- +-- | Pretty printer for a bar. Prints workspace ids of current profile. +excludeWSPP :: PP -> X PP +excludeWSPP pp = modifyPP <$> currentProfileWorkspaces + where + modifyPP pws = pp { ppRename = ppRename pp . printTag pws } + printTag pws tag = if tag `elem` pws then tag else "" + +-------------------------------------------------------------------------------- +-- | For cycling through workspaces associated with the current. +wsFilter :: WSType +wsFilter = WSIs $ currentProfileWorkspaces >>= (\ws -> return $ (`elem` ws) . W.tag) + +-------------------------------------------------------------------------------- +-- Takes care of placing correct workspaces on their respective screens. +-- It does this by reducing the history of a Profile until it gets an array of length +-- equal to the number of screens with pairs that have unique workspace ids. +switchWSOnScreens :: ProfileId -> X() +switchWSOnScreens pid = do + hist <- profileHistory + vis <- gets $ W.visible . windowset + cur <- gets $ W.current . windowset + pws <- profileMap <&> (profileWS . fromMaybe (Profile pid []) . Map.lookup pid) + case Map.lookup pid hist of + Nothing -> switchScreens $ zip (W.screen <$> (cur:vis)) pws + Just xs -> compareAndSwitch (f (W.screen <$> cur:vis) xs) (cur:vis) pws + where + f :: [ScreenId] -> [(ScreenId, WorkspaceId)] -> [(ScreenId, WorkspaceId)] + f sids = reorderUniq . reorderUniq . reverse . filter ((`elem` sids) . fst) + + reorderUniq :: (Ord k, Ord v) => [(k,v)] -> [(v,k)] + reorderUniq = map (\(x,y) -> (y,x)) . uniq + + uniq :: (Ord k, Ord v) => [(k,v)] -> [(k,v)] + uniq = Map.toList . Map.fromList + + viewWS fview sid wid = windows $ fview sid wid + + switchScreens = mapM_ (uncurry $ viewWS greedyViewOnScreen) + + compareAndSwitch hist wins pws | length hist < length wins = switchScreens $ hist <> populateScreens hist wins pws + | otherwise = switchScreens hist + + populateScreens hist wins pws = zip (filter (`notElem` map fst hist) $ W.screen <$> wins) (filter (`notElem` map snd hist) pws) + +-------------------------------------------------------------------------------- +chooseAction :: (String -> X ()) -> X () +chooseAction f = XS.gets current <&> (profileId . fromMaybe defaultProfile) >>= f + +-------------------------------------------------------------------------------- +-- | Create keybindings per profile. +bindOn :: [(String, X ())] -> X () +bindOn bindings = chooseAction chooser + where + chooser profile = case lookup profile bindings of + Just action -> action + Nothing -> case lookup "" bindings of + Just action -> action + Nothing -> return () + +-------------------------------------------------------------------------------- +-- | Loggs currentProfile and all profiles with hidden workspaces +-- (workspaces that aren't shown on a screen but have windows). +profileLogger :: (String -> String) -> (String -> String) -> Logger +profileLogger formatFocused formatUnfocused = do + hws <- gets $ W.hidden . windowset + p <- currentProfile + hm <- map fst + . filter (\(p', xs) -> any ((`elem` htags hws) . snd) xs || p' == p) + . Map.toList <$> profileHistory + return $ Just $ foldl (\a b -> a ++ " " ++ b) "" $ format p <$> hm + where + format p a = if a == p then formatFocused a else formatUnfocused a + htags wins = W.tag <$> filter (isJust . W.stack) wins + +-------------------------------------------------------------------------------- +-- | @XWindowMap@ of all windows contained in a profile. +allProfileWindows :: XWindowMap +allProfileWindows = allProfileWindows' def + +-------------------------------------------------------------------------------- +allProfileWindows' :: WindowBringerConfig -> XWindowMap +allProfileWindows' WindowBringerConfig{ windowTitler = titler, windowFilter = include } = do + pws <- currentProfileWorkspaces + windowSet <- gets windowset + Map.fromList . concat <$> mapM keyValuePairs (filter ((`elem` pws) . W.tag) $ W.workspaces windowSet) + where keyValuePairs ws = let wins = W.integrate' (W.stack ws) + in mapM (keyValuePair ws) =<< filterM include wins + keyValuePair ws w = (, w) <$> titler ws w diff --git a/XMonad/Actions/Promote.hs b/XMonad/Actions/Promote.hs index 64c6eb2868..30f229200d 100644 --- a/XMonad/Actions/Promote.hs +++ b/XMonad/Actions/Promote.hs @@ -28,7 +28,7 @@ import XMonad.StackSet -- $usage -- --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Actions.Promote -- diff --git a/XMonad/Actions/RotSlaves.hs b/XMonad/Actions/RotSlaves.hs index 3fc458033e..cceda0165e 100644 --- a/XMonad/Actions/RotSlaves.hs +++ b/XMonad/Actions/RotSlaves.hs @@ -17,7 +17,11 @@ module XMonad.Actions.RotSlaves ( -- $usage rotSlaves', rotSlavesUp, rotSlavesDown, - rotAll', rotAllUp, rotAllDown + rotAll', rotAllUp, rotAllDown, + + -- * Generic list rotations + -- $generic + rotUp, rotDown ) where import XMonad @@ -44,8 +48,8 @@ import XMonad.Prelude -- | Rotate the windows in the current stack, excluding the first one -- (master). rotSlavesUp,rotSlavesDown :: X () -rotSlavesUp = windows $ modify' (rotSlaves' (\l -> tail l++[head l])) -rotSlavesDown = windows $ modify' (rotSlaves' (\l -> last l : init l)) +rotSlavesUp = windows $ modify' (rotSlaves' rotUp) +rotSlavesDown = windows $ modify' (rotSlaves' rotDown) -- | The actual rotation, as a pure function on the window stack. rotSlaves' :: ([a] -> [a]) -> Stack a -> Stack a @@ -57,10 +61,19 @@ rotSlaves' f s@(Stack _ ls _ ) = Stack t' (reverse revls') rs' -- otherwise -- | Rotate all the windows in the current stack. rotAllUp,rotAllDown :: X () -rotAllUp = windows $ modify' (rotAll' (\l -> tail l++[head l])) -rotAllDown = windows $ modify' (rotAll' (\l -> last l : init l)) +rotAllUp = windows $ modify' (rotAll' rotUp) +rotAllDown = windows $ modify' (rotAll' rotDown) -- | The actual rotation, as a pure function on the window stack. rotAll' :: ([a] -> [a]) -> Stack a -> Stack a rotAll' f s = Stack r (reverse revls) rs where (revls, notEmpty -> r :| rs) = splitAt (length (up s)) (f (integrate s)) + +-- $generic +-- Generic list rotations such that @rotUp [1..4]@ is equivalent to +-- @[2,3,4,1]@ and @rotDown [1..4]@ to @[4,1,2,3]@. They both are +-- @id@ for null or singleton lists. +rotUp :: [a] -> [a] +rotUp l = drop 1 l ++ take 1 l +rotDown :: [a] -> [a] +rotDown = reverse . rotUp . reverse diff --git a/XMonad/Actions/RotateSome.hs b/XMonad/Actions/RotateSome.hs index dc1c61cc12..3ac196c1ad 100644 --- a/XMonad/Actions/RotateSome.hs +++ b/XMonad/Actions/RotateSome.hs @@ -35,7 +35,7 @@ import XMonad.StackSet (Screen (Screen), Stack (Stack), current, floating, modif import XMonad.Util.Stack (reverseS) {- $usage -You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +You can use this module with the following in your @xmonad.hs@: > import XMonad.Actions.RotateSome diff --git a/XMonad/Actions/Search.hs b/XMonad/Actions/Search.hs index f59e627d37..1fd6b841f4 100644 --- a/XMonad/Actions/Search.hs +++ b/XMonad/Actions/Search.hs @@ -48,6 +48,7 @@ module XMonad.Actions.Search ( -- * Usage github, google, hackage, + homeManager, hoogle, images, imdb, @@ -55,6 +56,7 @@ module XMonad.Actions.Search ( -- * Usage maps, mathworld, ncatlab, + nixos, noogle, openstreetmap, protondb, @@ -151,6 +153,8 @@ import XMonad.Util.XSelection (getSelection) * 'hackage' -- Hackage, the Haskell package database. +* 'homeManager' -- Search Nix's home-manager's options. + * 'hoogle' -- Hoogle, the Haskell libraries API search engine. * 'images' -- Google images. @@ -165,6 +169,8 @@ import XMonad.Util.XSelection (getSelection) * 'ncatlab' -- Higer Algebra, Homotopy and Category Theory Wiki. +* 'nixos' -- Search NixOS packages and options. + * 'noogle' -- 'hoogle'-like Nix API search engine. * 'openstreetmap' -- OpenStreetMap free wiki world map. @@ -316,7 +322,7 @@ searchEngine name site = searchEngineF name (\s -> site ++ escape s) inside of a URL instead of in the end) you can use the alternative 'searchEngineF' function. > searchFunc :: String -> String -> searchFunc s | "wiki:" `isPrefixOf` s = "https://en.wikipedia.org/wiki/" ++ (escape $ tail $ snd $ break (==':') s) +> searchFunc s | "wiki:" `isPrefixOf` s = "https://en.wikipedia.org/wiki/" ++ (escape $ drop 1 $ snd $ break (==':') s) > | "https://" `isPrefixOf` s = s > | otherwise = (use google) s > myNewEngine = searchEngineF "mymulti" searchFunc @@ -334,9 +340,9 @@ searchEngineF = SearchEngine -- The engines. alpha, amazon, arXiv, aur, clojureDocs, codesearch, cratesIo, deb, debbts, debpts, dictionary, duckduckgo, ebay, flora, - github, google, hackage, hoogle, images, imdb, lucky, maps, mathworld, ncatlab, openstreetmap, protondb, rosettacode, rustStd, - scholar, sourcehut, stackage, steam, thesaurus, vocabulary, voidpgks_x86_64, voidpgks_x86_64_musl, wayback, wikipedia, wiktionary, - youtube, zbmath, noogle :: SearchEngine + github, google, hackage, homeManager, hoogle, images, imdb, lucky, maps, mathworld, ncatlab, nixos, noogle, openstreetmap, protondb, + rosettacode, rustStd, scholar, sourcehut, stackage, steam, thesaurus, vocabulary, voidpgks_x86_64, voidpgks_x86_64_musl, wayback, + wikipedia, wiktionary, youtube, zbmath :: SearchEngine alpha = searchEngine "alpha" "https://www.wolframalpha.com/input/?i=" amazon = searchEngine "amazon" "https://www.amazon.com/s/ref=nb_sb_noss_2?url=search-alias%3Daps&field-keywords=" arXiv = searchEngineF "arXiv" (\s -> "https://arxiv.org/search/?query=" <> s <> "&searchtype=all") @@ -354,6 +360,7 @@ flora = searchEngine "flora" "https://flora.pm/search?q=" github = searchEngine "github" "https://github.com/search?q=" google = searchEngine "google" "https://www.google.com/search?q=" hackage = searchEngine "hackage" "https://hackage.haskell.org/package/" +homeManager = searchEngine "homeManager" "https://mipmip.github.io/home-manager-option-search/?query=" hoogle = searchEngine "hoogle" "https://hoogle.haskell.org/?hoogle=" images = searchEngine "images" "https://images.google.fr/images?q=" imdb = searchEngine "imdb" "https://www.imdb.com/find?s=all&q=" @@ -361,6 +368,7 @@ lucky = searchEngine "lucky" "https://www.google.com/search?btnI maps = searchEngine "maps" "https://maps.google.com/maps?q=" mathworld = searchEngine "mathworld" "https://mathworld.wolfram.com/search/?query=" ncatlab = searchEngine "ncatlab" "https://ncatlab.org/nlab/search?query=" +nixos = searchEngine "nixos" "https://search.nixos.org/packages?channel=unstable&from=0&size=200&sort=relevance&type=packages&query=" noogle = searchEngineF "noogle" (\s -> "https://noogle.dev/?search=" <> s <> "&page=1&to=any&from=any") openstreetmap = searchEngine "openstreetmap" "https://www.openstreetmap.org/search?query=" protondb = searchEngine "protondb" "https://www.protondb.com/search?q=" diff --git a/XMonad/Actions/ShowText.hs b/XMonad/Actions/ShowText.hs index 88e6716344..658cd9c98f 100644 --- a/XMonad/Actions/ShowText.hs +++ b/XMonad/Actions/ShowText.hs @@ -1,4 +1,5 @@ {-# LANGUAGE CPP #-} +{-# LANGUAGE MultiWayIf #-} ----------------------------------------------------------------------------- -- | -- Module : XMonad.Actions.ShowText @@ -26,7 +27,7 @@ module XMonad.Actions.ShowText import Data.Map (Map,empty,insert,lookup) import Prelude hiding (lookup) import XMonad -import XMonad.Prelude (All, fi, when) +import XMonad.Prelude (All, fi, listToMaybe) import XMonad.StackSet (current,screen) import XMonad.Util.Font (Align(AlignCenter) , initXMF @@ -41,7 +42,7 @@ import XMonad.Util.XUtils (createNewWindow import qualified XMonad.Util.ExtensibleState as ES -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Actions.ShowText -- @@ -87,8 +88,9 @@ handleTimerEvent :: Event -> X All handleTimerEvent (ClientMessageEvent _ _ _ dis _ mtyp d) = do (ShowText m) <- ES.get :: X ShowText a <- io $ internAtom dis "XMONAD_TIMER" False - when (mtyp == a && not (null d)) - (whenJust (lookup (fromIntegral $ head d) m) deleteWindow) + if | mtyp == a, Just dh <- listToMaybe d -> + whenJust (lookup (fromIntegral dh) m) deleteWindow + | otherwise -> pure () mempty handleTimerEvent _ = mempty diff --git a/XMonad/Actions/Sift.hs b/XMonad/Actions/Sift.hs index d491cb2ca7..7f4fa9f6ea 100644 --- a/XMonad/Actions/Sift.hs +++ b/XMonad/Actions/Sift.hs @@ -28,7 +28,7 @@ import XMonad.StackSet (Stack (Stack), StackSet, modify') import XMonad.Util.Stack (reverseS) -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Actions.Sift -- diff --git a/XMonad/Actions/SimpleDate.hs b/XMonad/Actions/SimpleDate.hs index d248d6ccc7..84daa780f8 100644 --- a/XMonad/Actions/SimpleDate.hs +++ b/XMonad/Actions/SimpleDate.hs @@ -24,7 +24,7 @@ import XMonad.Core import XMonad.Util.Run -- $usage --- To use, import this module into @~\/.xmonad\/xmonad.hs@: +-- To use, import this module into @xmonad.hs@: -- -- > import XMonad.Actions.SimpleDate -- diff --git a/XMonad/Actions/SinkAll.hs b/XMonad/Actions/SinkAll.hs index 62a1fb02e1..7349d04f43 100644 --- a/XMonad/Actions/SinkAll.hs +++ b/XMonad/Actions/SinkAll.hs @@ -23,7 +23,7 @@ import XMonad.Actions.WithAll (sinkAll) -- $usage -- --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Actions.SinkAll -- diff --git a/XMonad/Actions/SpawnOn.hs b/XMonad/Actions/SpawnOn.hs index 584fff4d13..384040f5d2 100644 --- a/XMonad/Actions/SpawnOn.hs +++ b/XMonad/Actions/SpawnOn.hs @@ -43,7 +43,7 @@ import qualified XMonad.Util.ExtensibleState as XS import XMonad.Util.Process (getPPIDChain) -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Actions.SpawnOn -- diff --git a/XMonad/Actions/Submap.hs b/XMonad/Actions/Submap.hs index d5829ae10d..0c6a80d8be 100644 --- a/XMonad/Actions/Submap.hs +++ b/XMonad/Actions/Submap.hs @@ -32,10 +32,7 @@ import XMonad.Util.XUtils {- $usage - - - -First, import this module into your @~\/.xmonad\/xmonad.hs@: +First, import this module into your @xmonad.hs@: > import XMonad.Actions.Submap diff --git a/XMonad/Actions/SwapPromote.hs b/XMonad/Actions/SwapPromote.hs index 7055dbc8db..934f856244 100644 --- a/XMonad/Actions/SwapPromote.hs +++ b/XMonad/Actions/SwapPromote.hs @@ -63,6 +63,7 @@ import qualified XMonad.Util.ExtensibleState as XS import qualified Data.Map as M import qualified Data.Set as S import Control.Arrow +import qualified Data.List.NonEmpty as NE -- $usage @@ -240,8 +241,8 @@ swapApply ignoreFloats swapFunction = do (r,s2) = stackSplit s1 fl' :: ([(Int,Window)],W.Stack Window) (b,s3) = swapFunction pm s2 s4 = stackMerge s3 r - mh = let w = head . W.integrate $ s3 - in const $ w : delete w ch + mh = let w = NE.head . notEmpty . W.integrate $ s3 + in const $ w : delete w ch in (b,Just s4,mh) (x,y,z) = maybe (False,Nothing,id) swapApply' st -- Any floating master windows will be added to the history when 'windows' diff --git a/XMonad/Actions/SwapWorkspaces.hs b/XMonad/Actions/SwapWorkspaces.hs index 3fe5f13511..c2bc1d3288 100644 --- a/XMonad/Actions/SwapWorkspaces.hs +++ b/XMonad/Actions/SwapWorkspaces.hs @@ -30,7 +30,7 @@ import XMonad.Util.WorkspaceCompare -- $usage --- Add this import to your @~\/.xmonad\/xmonad.hs@: +-- Add this import to your @xmonad.hs@: -- -- > import XMonad.Actions.SwapWorkspaces -- diff --git a/XMonad/Actions/TagWindows.hs b/XMonad/Actions/TagWindows.hs index 0e11b9a11e..7730e53f23 100644 --- a/XMonad/Actions/TagWindows.hs +++ b/XMonad/Actions/TagWindows.hs @@ -39,7 +39,7 @@ econst = const . return -- $usage -- --- To use window tags, import this module into your @~\/.xmonad\/xmonad.hs@: +-- To use window tags, import this module into your @xmonad.hs@: -- -- > import XMonad.Actions.TagWindows -- > import XMonad.Prompt -- to use tagPrompt diff --git a/XMonad/Actions/TiledWindowDragging.hs b/XMonad/Actions/TiledWindowDragging.hs index 8df8b907eb..3620eff654 100644 --- a/XMonad/Actions/TiledWindowDragging.hs +++ b/XMonad/Actions/TiledWindowDragging.hs @@ -27,7 +27,7 @@ import qualified XMonad.StackSet as W import XMonad.Layout.DraggingVisualizer -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Actions.TiledWindowDragging -- > import XMonad.Layout.DraggingVisualizer diff --git a/XMonad/Actions/TopicSpace.hs b/XMonad/Actions/TopicSpace.hs index 577e226712..20bdf8bb7d 100644 --- a/XMonad/Actions/TopicSpace.hs +++ b/XMonad/Actions/TopicSpace.hs @@ -108,7 +108,7 @@ import XMonad.Hooks.WorkspaceHistory -- . -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import qualified Data.Map.Strict as M -- > import qualified XMonad.StackSet as W diff --git a/XMonad/Actions/UpdateFocus.hs b/XMonad/Actions/UpdateFocus.hs index b2eef64e4b..2592484d6a 100644 --- a/XMonad/Actions/UpdateFocus.hs +++ b/XMonad/Actions/UpdateFocus.hs @@ -27,7 +27,7 @@ import qualified XMonad.StackSet as W -- $usage -- To make the focus update on mouse movement within an unfocused window, add the --- following to your @~\/.xmonad\/xmonad.hs@: +-- following to your @xmonad.hs@: -- -- > import XMonad.Actions.UpdateFocus -- > xmonad $ def { diff --git a/XMonad/Actions/UpdatePointer.hs b/XMonad/Actions/UpdatePointer.hs index a00473e2e4..9cc30c134e 100644 --- a/XMonad/Actions/UpdatePointer.hs +++ b/XMonad/Actions/UpdatePointer.hs @@ -31,7 +31,7 @@ import XMonad.StackSet (member, peek, screenDetail, current) import Control.Arrow ((&&&), (***)) -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad -- > import XMonad.Actions.UpdatePointer diff --git a/XMonad/Actions/Warp.hs b/XMonad/Actions/Warp.hs index 8b96ef20b8..c459587eff 100644 --- a/XMonad/Actions/Warp.hs +++ b/XMonad/Actions/Warp.hs @@ -28,7 +28,7 @@ import XMonad import XMonad.StackSet as W {- $usage -You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +You can use this module with the following in your @xmonad.hs@: > import XMonad.Actions.Warp diff --git a/XMonad/Actions/WindowBringer.hs b/XMonad/Actions/WindowBringer.hs index a46d060560..27c42a4c72 100644 --- a/XMonad/Actions/WindowBringer.hs +++ b/XMonad/Actions/WindowBringer.hs @@ -36,7 +36,7 @@ import XMonad.Util.NamedWindows (getName, getNameWMClass) -- $usage -- --- Import the module into your @~\/.xmonad\/xmonad.hs@: +-- Import the module into your @xmonad.hs@: -- -- > import XMonad.Actions.WindowBringer -- diff --git a/XMonad/Actions/WindowGo.hs b/XMonad/Actions/WindowGo.hs index 7a1b949df4..53286be918 100644 --- a/XMonad/Actions/WindowGo.hs +++ b/XMonad/Actions/WindowGo.hs @@ -48,9 +48,11 @@ import XMonad.Operations (windows) import XMonad.Prompt.Shell (getBrowser, getEditor) import qualified XMonad.StackSet as W (peek, swapMaster, focusWindow, workspaces, StackSet, Workspace, integrate', tag, stack) import XMonad.Util.Run (safeSpawnProg) +import qualified Data.List.NonEmpty as NE + {- $usage -Import the module into your @~\/.xmonad\/xmonad.hs@: +Import the module into your @xmonad.hs@: > import XMonad.Actions.WindowGo @@ -90,7 +92,10 @@ ifWindows qry f el = withWindowSet $ \wins -> do -- | The same as ifWindows, but applies a ManageHook to the first match -- instead and discards the other matches ifWindow :: Query Bool -> ManageHook -> X () -> X () -ifWindow qry mh = ifWindows qry (windows . appEndo <=< runQuery mh . head) +ifWindow qry mh = ifWindows qry (windows . appEndo <=< runQuery mh . NE.head . notEmpty) +-- ifWindows guarantees that the list given to the function is +-- non-empty. This should really use Data.List.NonEmpty, but, alas, +-- that would be a breaking change. {- | 'action' is an executable to be run via 'safeSpawnProg' (of "XMonad.Util.Run") if the Window cannot be found. Presumably this executable is the same one that you were looking for. @@ -165,7 +170,8 @@ raiseNextMaybeCustomFocus focusFn f qry = flip (ifWindows qry) f $ \ws -> do let (notEmpty -> _ :| (notEmpty -> y :| _)) = dropWhile (/=w) $ cycle ws -- cannot fail to match in windows $ focusFn y - _ -> windows . focusFn . head $ ws + _ -> windows . focusFn . NE.head . notEmpty $ ws + -- ws is non-empty by ifWindows's definition. -- | Given a function which gets us a String, we try to raise a window with that classname, -- or we then interpret that String as a executable name. diff --git a/XMonad/Actions/WindowMenu.hs b/XMonad/Actions/WindowMenu.hs index 70ba1e0538..d11fdbe3a9 100644 --- a/XMonad/Actions/WindowMenu.hs +++ b/XMonad/Actions/WindowMenu.hs @@ -34,7 +34,7 @@ import XMonad.Prelude (fi) -- $usage -- --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Actions.WindowMenu -- diff --git a/XMonad/Actions/WithAll.hs b/XMonad/Actions/WithAll.hs index bb28613cdc..6192451ac2 100644 --- a/XMonad/Actions/WithAll.hs +++ b/XMonad/Actions/WithAll.hs @@ -24,7 +24,7 @@ import XMonad.StackSet -- $usage -- --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Actions.WithAll -- diff --git a/XMonad/Actions/Workscreen.hs b/XMonad/Actions/Workscreen.hs index 85e8912e9d..8bd82d5502 100644 --- a/XMonad/Actions/Workscreen.hs +++ b/XMonad/Actions/Workscreen.hs @@ -43,7 +43,7 @@ import qualified XMonad.Util.ExtensibleState as XS import XMonad.Actions.OnScreen -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Actions.Workscreen -- > myWorkspaces = let myOldWorkspaces = ["adm","work","mail"] @@ -109,5 +109,6 @@ shiftWs a = drop 1 a ++ take 1 a -- @WorkscreenId@. shiftToWorkscreen :: WorkscreenId -> X () shiftToWorkscreen wscrId = do (WorkscreenStorage _ a) <- XS.get - let ws = head . workspaces $ a !! wscrId - windows $ W.shift ws + case workspaces (a !! wscrId) of + [] -> pure () + (w : _) -> windows $ W.shift w diff --git a/XMonad/Actions/WorkspaceCursors.hs b/XMonad/Actions/WorkspaceCursors.hs index 6f3f1693b7..bfb820690c 100644 --- a/XMonad/Actions/WorkspaceCursors.hs +++ b/XMonad/Actions/WorkspaceCursors.hs @@ -50,7 +50,7 @@ import XMonad.Layout.LayoutModifier(ModifiedLayout(..), import XMonad(Message, WorkspaceId, X, XState(windowset), fromMessage, sendMessage, windows, gets) import XMonad.Util.Stack (reverseS) -import XMonad.Prelude (find, fromJust, guard, liftA2, toList, when, (<=<)) +import XMonad.Prelude -- $usage -- @@ -95,10 +95,10 @@ import XMonad.Prelude (find, fromJust, guard, liftA2, toList, when, (<=<)) -- | makeCursors requires a nonempty string, and each sublist must be nonempty makeCursors :: [[String]] -> Cursors String -makeCursors [] = error "Workspace Cursors cannot be empty" -makeCursors a = concat . reverse <$> foldl addDim x xs - where x = end $ map return $ head a - xs = map (map return) $ tail a +makeCursors [] = error "Workspace Cursors cannot be empty" +makeCursors (a : as) = concat . reverse <$> foldl addDim x xs + where x = end $ map return a + xs = map (map return) as -- this could probably be simplified, but this true: -- toList . makeCursors == map (concat . reverse) . sequence . reverse . map (map (:[])) -- the strange order is used because it makes the regular M-1..9 diff --git a/XMonad/Actions/WorkspaceNames.hs b/XMonad/Actions/WorkspaceNames.hs index 97b7c6452a..4ba5fd08b8 100644 --- a/XMonad/Actions/WorkspaceNames.hs +++ b/XMonad/Actions/WorkspaceNames.hs @@ -58,7 +58,7 @@ import XMonad.Util.WorkspaceCompare (getSortByIndex) import qualified Data.Map as M -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@ file: +-- You can use this module with the following in your @xmonad.hs@ file: -- -- > import XMonad.Actions.WorkspaceNames -- diff --git a/XMonad/Config/Arossato.hs b/XMonad/Config/Arossato.hs index c5007df44e..816ca1dc29 100644 --- a/XMonad/Config/Arossato.hs +++ b/XMonad/Config/Arossato.hs @@ -47,7 +47,7 @@ import XMonad.Util.Themes -- $usage -- The simplest way to use this configuration module is to use an --- @~\/.xmonad\/xmonad.hs@ like this: +-- @xmonad.hs@ like this: -- -- > module Main (main) where -- > @@ -64,7 +64,7 @@ import XMonad.Util.Themes -- -- You can use this module also as a starting point for writing your -- own configuration module from scratch. Save it as your --- @~\/.xmonad\/xmonad.hs@ and: +-- @xmonad.hs@ and: -- -- 1. Change the module name from -- diff --git a/XMonad/Config/Azerty.hs b/XMonad/Config/Azerty.hs index 14caaa428a..b26dc780e9 100644 --- a/XMonad/Config/Azerty.hs +++ b/XMonad/Config/Azerty.hs @@ -27,7 +27,7 @@ import qualified XMonad.StackSet as W import qualified Data.Map as M -- $usage --- To use this module, start with the following @~\/.xmonad\/xmonad.hs@: +-- To use this module, start with the following @xmonad.hs@: -- -- > import XMonad -- > import XMonad.Config.Azerty diff --git a/XMonad/Config/Bepo.hs b/XMonad/Config/Bepo.hs index 43416a1919..b6468fb0b7 100644 --- a/XMonad/Config/Bepo.hs +++ b/XMonad/Config/Bepo.hs @@ -26,7 +26,7 @@ import qualified XMonad.StackSet as W import qualified Data.Map as M -- $usage --- To use this module, start with the following @~\/.xmonad\/xmonad.hs@: +-- To use this module, start with the following @xmonad.hs@: -- -- > import XMonad -- > import XMonad.Config.Bepo diff --git a/XMonad/Config/Bluetile.hs b/XMonad/Config/Bluetile.hs index 93086cd8de..fcfbe759f7 100644 --- a/XMonad/Config/Bluetile.hs +++ b/XMonad/Config/Bluetile.hs @@ -65,7 +65,7 @@ import System.Exit import XMonad.Prelude(when) -- $usage --- To use this module, start with the following @~\/.xmonad\/xmonad.hs@: +-- To use this module, start with the following @xmonad.hs@: -- -- > import XMonad -- > import XMonad.Config.Bluetile diff --git a/XMonad/Config/Desktop.hs b/XMonad/Config/Desktop.hs index 70f8eac36c..1d52bd9bf4 100644 --- a/XMonad/Config/Desktop.hs +++ b/XMonad/Config/Desktop.hs @@ -24,7 +24,7 @@ module XMonad.Config.Desktop ( -- specification. Extra xmonad settings unique to specific DE's are -- added by overriding or modifying @desktopConfig@ fields in the -- same way that the default configuration is customized in - -- @~\/.xmonad/xmonad.hs@. + -- @xmonad.hs@. -- -- For more information about EWMH see: -- @@ -72,7 +72,7 @@ import qualified Data.Map as M -- -- -- To configure xmonad for use with a DE or with DE tools like panels --- and pagers, in place of @def@ in your @~\/.xmonad/xmonad.hs@, +-- and pagers, in place of @def@ in your @xmonad.hs@, -- use @desktopConfig@ or one of the other desktop configs from the -- @XMonad.Config@ namespace. The following setup and customization examples -- work the same way for the other desktop configs as for @desktopConfig@. diff --git a/XMonad/Config/Dmwit.hs b/XMonad/Config/Dmwit.hs index 72fa3212bd..768fd41aee 100644 --- a/XMonad/Config/Dmwit.hs +++ b/XMonad/Config/Dmwit.hs @@ -34,7 +34,7 @@ import XMonad.Layout.Grid import XMonad.Layout.IndependentScreens hiding (withScreen) import XMonad.Layout.Magnifier import XMonad.Layout.NoBorders -import XMonad.Prelude +import XMonad.Prelude hiding (fromList) import XMonad.Util.Dzen hiding (x, y) import XMonad.Util.SpawnOnce -- }}} diff --git a/XMonad/Config/Gnome.hs b/XMonad/Config/Gnome.hs index b689ed3278..4603f64066 100644 --- a/XMonad/Config/Gnome.hs +++ b/XMonad/Config/Gnome.hs @@ -32,7 +32,7 @@ import qualified Data.Map as M import System.Environment (getEnvironment) -- $usage --- To use this module, start with the following @~\/.xmonad\/xmonad.hs@: +-- To use this module, start with the following @xmonad.hs@: -- -- > import XMonad -- > import XMonad.Config.Gnome diff --git a/XMonad/Config/Kde.hs b/XMonad/Config/Kde.hs index 0a1b8d94ce..d70bc825e9 100644 --- a/XMonad/Config/Kde.hs +++ b/XMonad/Config/Kde.hs @@ -28,7 +28,7 @@ import XMonad.Config.Desktop import qualified Data.Map as M -- $usage --- To use this module, start with the following @~\/.xmonad\/xmonad.hs@: +-- To use this module, start with the following @xmonad.hs@: -- -- > import XMonad -- > import XMonad.Config.Kde diff --git a/XMonad/Config/LXQt.hs b/XMonad/Config/LXQt.hs index dcd05bfbde..11462de046 100644 --- a/XMonad/Config/LXQt.hs +++ b/XMonad/Config/LXQt.hs @@ -27,7 +27,7 @@ import XMonad.Config.Desktop import qualified Data.Map as M -- $usage --- To use this module, start with the following @~\/.xmonad\/xmonad.hs@: +-- To use this module, start with the following @xmonad.hs@: -- -- > import XMonad -- > import XMonad.Config.LXQt diff --git a/XMonad/Config/Mate.hs b/XMonad/Config/Mate.hs index f0e7de037c..f93e1beae5 100644 --- a/XMonad/Config/Mate.hs +++ b/XMonad/Config/Mate.hs @@ -1,5 +1,7 @@ {-# OPTIONS_GHC -fno-warn-missing-signatures #-} - +-- TODO: Remove when we depend on a version of xmonad that has unGrab. +{-# OPTIONS_GHC -Wno-deprecations #-} +{-# OPTIONS_GHC -Wno-dodgy-imports #-} ----------------------------------------------------------------------------- -- | -- Module : XMonad.Config.Mate @@ -28,18 +30,17 @@ module XMonad.Config.Mate ( desktopLayoutModifiers ) where -import XMonad -import XMonad.Config.Desktop -import XMonad.Util.Run (safeSpawn) -import XMonad.Util.Ungrab -import XMonad.Prelude (toUpper) - +import System.Environment (getEnvironment) import qualified Data.Map as M -import System.Environment (getEnvironment) +import XMonad hiding (unGrab) +import XMonad.Config.Desktop +import XMonad.Prelude (toUpper) +import XMonad.Util.Run (safeSpawn) +import XMonad.Util.Ungrab (unGrab) -- $usage --- To use this module, start with the following @~\/.xmonad\/xmonad.hs@: +-- To use this module, start with the following @xmonad.hs@: -- -- > import XMonad -- > import XMonad.Config.Mate diff --git a/XMonad/Config/Prime.hs b/XMonad/Config/Prime.hs index e2730c109d..7ddf029601 100644 --- a/XMonad/Config/Prime.hs +++ b/XMonad/Config/Prime.hs @@ -126,7 +126,7 @@ import qualified XMonad as X (xmonad, XConfig(..)) import XMonad.Util.EZConfig (additionalKeysP, additionalMouseBindings, checkKeymap, removeKeysP, removeMouseBindings) -- $start_here --- To start with, create a @~\/.xmonad\/xmonad.hs@ that looks like this: +-- To start with, create a @xmonad.hs@ that looks like this: -- -- > {-# LANGUAGE RebindableSyntax #-} -- > import XMonad.Config.Prime diff --git a/XMonad/Config/Xfce.hs b/XMonad/Config/Xfce.hs index 489e314f9e..34e46c205c 100644 --- a/XMonad/Config/Xfce.hs +++ b/XMonad/Config/Xfce.hs @@ -27,7 +27,7 @@ import XMonad.Config.Desktop import qualified Data.Map as M -- $usage --- To use this module, start with the following @~\/.xmonad\/xmonad.hs@: +-- To use this module, start with the following @xmonad.hs@: -- -- > import XMonad -- > import XMonad.Config.Xfce diff --git a/XMonad/Doc/Developing.hs b/XMonad/Doc/Developing.hs index 69e6db30b9..b56b592763 100644 --- a/XMonad/Doc/Developing.hs +++ b/XMonad/Doc/Developing.hs @@ -86,7 +86,7 @@ customize the window manager to fit her needs. Basically, xmonad and the xmonad-contrib libraries let users write their own window manager in just a few lines of code. While -@~\/.xmonad\/xmonad.hs@ at first seems to be simply a configuration +@xmonad.hs@ at first seems to be simply a configuration file, it is actually a complete Haskell program which uses the xmonad and xmonad-contrib libraries to create a custom window manager. @@ -106,13 +106,13 @@ manager you are writing in any way you see fit. xmonad installs a binary, @xmonad@, which must be executed by the Xsession starting script. This binary, whose code can be read in @Main.hs@ of the xmonad source tree, will use 'XMonad.Core.recompile' -to run @ghc@ in order to build a binary from @~\/.xmonad\/xmonad.hs@. +to run @ghc@ in order to build a binary from @xmonad.hs@. If this compilation process fails, for any reason, a default @main@ entry point will be used, which calls the 'XMonad.Main.xmonad' function with a default configuration. Thus, the real @main@ entry point, the one that even the users' custom -window manager application in @~\/.xmonad\/xmonad.hs@ must call, is +window manager application in @xmonad.hs@ must call, is the 'XMonad.Main.xmonad' function. This function takes a configuration as its only argument, whose type ('XMonad.Core.XConfig') is defined in "XMonad.Core". diff --git a/XMonad/Doc/Extending.hs b/XMonad/Doc/Extending.hs index 28e79cd1b5..9360acd660 100644 --- a/XMonad/Doc/Extending.hs +++ b/XMonad/Doc/Extending.hs @@ -500,7 +500,7 @@ with a specific layout combinator: 'XMonad.Layout.|||'. Suppose we want a list with the 'XMonad.Layout.Full', 'XMonad.Layout.Tabbed.tabbed' and 'XMonad.Layout.Accordion.Accordion' layouts. First we import, in our -@~\/.xmonad\/xmonad.hs@, all the needed modules: +@xmonad.hs@, all the needed modules: > import XMonad > @@ -529,7 +529,7 @@ If we want only the tabbed layout without borders, then we may write: > mylayoutHook = Full ||| noBorders (tabbed shrinkText def) ||| Accordion -Our @~\/.xmonad\/xmonad.hs@ will now look like this: +Our @xmonad.hs@ will now look like this: > import XMonad > diff --git a/XMonad/Hooks/CurrentWorkspaceOnTop.hs b/XMonad/Hooks/CurrentWorkspaceOnTop.hs index 2af126e7c9..3b1fbcee2d 100644 --- a/XMonad/Hooks/CurrentWorkspaceOnTop.hs +++ b/XMonad/Hooks/CurrentWorkspaceOnTop.hs @@ -22,14 +22,15 @@ module XMonad.Hooks.CurrentWorkspaceOnTop ( currentWorkspaceOnTop ) where +import qualified Data.List.NonEmpty as NE (nonEmpty) +import qualified Data.Map as M import XMonad +import XMonad.Prelude (NonEmpty ((:|)), when) import qualified XMonad.StackSet as S import qualified XMonad.Util.ExtensibleState as XS -import XMonad.Prelude (unless, when) -import qualified Data.Map as M -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Hooks.CurrentWorkspaceOnTop -- > @@ -63,7 +64,9 @@ currentWorkspaceOnTop = withDisplay $ \d -> do wins = fltWins ++ map fst rs -- order: first all floating windows, then the order the layout returned -- end of reimplementation - unless (null wins) $ do - io $ raiseWindow d (head wins) -- raise first window of current workspace to the very top, - io $ restackWindows d wins -- then use restackWindows to let all other windows from the workspace follow + case NE.nonEmpty wins of + Nothing -> pure () + Just (w :| ws') -> do + io $ raiseWindow d w -- raise first window of current workspace to the very top, + io $ restackWindows d (w : ws') -- then use restackWindows to let all other windows from the workspace follow XS.put(CWOTS curTag) diff --git a/XMonad/Hooks/DebugEvents.hs b/XMonad/Hooks/DebugEvents.hs index f74e223b3d..f200fd1bcb 100644 --- a/XMonad/Hooks/DebugEvents.hs +++ b/XMonad/Hooks/DebugEvents.hs @@ -687,9 +687,7 @@ dumpString = do \s -> if null s then Nothing else let (w,s'') = break (== '\NUL') s - s' = if null s'' - then s'' - else tail s'' + s' = drop 1 s'' in Just (w,s') case ss of [s] -> append $ show s diff --git a/XMonad/Hooks/DynamicLog.hs b/XMonad/Hooks/DynamicLog.hs index 4236a397aa..7613b47e64 100644 --- a/XMonad/Hooks/DynamicLog.hs +++ b/XMonad/Hooks/DynamicLog.hs @@ -76,7 +76,7 @@ import XMonad.Hooks.StatusBar.PP import XMonad.Hooks.StatusBar -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad -- > import XMonad.Hooks.DynamicLog diff --git a/XMonad/Hooks/EwmhDesktops.hs b/XMonad/Hooks/EwmhDesktops.hs index 3529a55e99..cfb1b91dfd 100644 --- a/XMonad/Hooks/EwmhDesktops.hs +++ b/XMonad/Hooks/EwmhDesktops.hs @@ -24,6 +24,8 @@ module XMonad.Hooks.EwmhDesktops ( -- $usage ewmh, ewmhFullscreen, + ewmhDesktopsManageHook, + ewmhDesktopsMaybeManageHook, -- * Customization -- $customization @@ -74,7 +76,7 @@ import qualified XMonad.Util.ExtensibleConf as XC import qualified XMonad.Util.ExtensibleState as XS -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad -- > import XMonad.Hooks.EwmhDesktops @@ -244,7 +246,7 @@ setEwmhActivateHook h = XC.modifyDef $ \c -> c{ activateHook = h } -- $customFullscreen --- When a client sends a @_NET_WM_STATE@ request to add/remove/toggle the +-- When a client sends a @_NET_WM_STATE@ request to add\/remove\/toggle the -- @_NET_WM_STATE_FULLSCREEN@ state, 'ewmhFullscreen' uses a pair of hooks to -- make the window fullscreen and revert its state. The default hooks are -- stateless: windows are fullscreened by turning them into fullscreen floats, @@ -481,6 +483,31 @@ ewmhDesktopsEventHook' mempty ewmhDesktopsEventHook' _ _ = mempty +-- | A 'ManageHook' that shifts windows to the workspace they want to be in. +-- Useful for restoring browser windows to where they were before restart. +-- +-- To only use this for browsers (which might be a good idea, as many apps try +-- to restore their window to their original position, but it's rarely +-- desirable outside of security updates of multi-window apps like a browser), +-- use this: +-- +-- > stringProperty "WM_WINDOW_ROLE" =? "browser" --> ewmhDesktopsManageHook +ewmhDesktopsManageHook :: ManageHook +ewmhDesktopsManageHook = maybeToDefinite ewmhDesktopsMaybeManageHook + +-- | 'ewmhDesktopsManageHook' as a 'MaybeManageHook' for use with +-- 'composeOne'. Returns 'Nothing' if the window didn't indicate any desktop +-- preference, otherwise 'Just' (even if the preferred desktop was out of +-- bounds). +ewmhDesktopsMaybeManageHook :: MaybeManageHook +ewmhDesktopsMaybeManageHook = desktop >>= traverse doShiftI + where + doShiftI :: Int -> ManageHook + doShiftI d = do + sort' <- liftX . XC.withDef $ \EwmhDesktopsConfig{workspaceSort} -> workspaceSort + ws <- liftX . gets $ map W.tag . sort' . W.workspaces . windowset + maybe idHook doShift $ ws !? d + -- | Add EWMH fullscreen functionality to the given config. ewmhFullscreen :: XConfig a -> XConfig a ewmhFullscreen c = c { startupHook = startupHook c <> fullscreenStartup @@ -518,10 +545,10 @@ fullscreenEventHook' chWstate f = io $ changeProperty32 dpy win wmstate aTOM propModeReplace (f wstate) when (managed && typ == wmstate && fi fullsc `elem` dats) $ do - when (action == add || (action == toggle && not isFull)) $ do + when (not isFull && (action == add || action == toggle)) $ do chWstate (fi fullsc:) windows . appEndo =<< runQuery fullscreenHook win - when (action == remove || (action == toggle && isFull)) $ do + when (isFull && (action == remove || action == toggle)) $ do chWstate $ delete (fi fullsc) windows . appEndo =<< runQuery unFullscreenHook win diff --git a/XMonad/Hooks/FadeInactive.hs b/XMonad/Hooks/FadeInactive.hs index 54726d8347..d65e1e715e 100644 --- a/XMonad/Hooks/FadeInactive.hs +++ b/XMonad/Hooks/FadeInactive.hs @@ -32,7 +32,7 @@ import XMonad.Prelude import qualified XMonad.StackSet as W -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad -- > import XMonad.Hooks.FadeInactive diff --git a/XMonad/Hooks/FloatNext.hs b/XMonad/Hooks/FloatNext.hs index e8a72d26e5..13c604523a 100644 --- a/XMonad/Hooks/FloatNext.hs +++ b/XMonad/Hooks/FloatNext.hs @@ -47,7 +47,7 @@ hookName = "__float" -- to automatically send the next spawned window(s) to the floating -- layer. -- --- You can use it by including the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use it by including the following in your @xmonad.hs@: -- -- > import XMonad.Hooks.FloatNext -- diff --git a/XMonad/Hooks/InsertPosition.hs b/XMonad/Hooks/InsertPosition.hs index fc88ffe62d..b6bb4c6b9d 100644 --- a/XMonad/Hooks/InsertPosition.hs +++ b/XMonad/Hooks/InsertPosition.hs @@ -26,7 +26,7 @@ import XMonad.Prelude (Endo (Endo), find) import qualified XMonad.StackSet as W -- $usage --- You can use this module by importing it in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module by importing it in your @xmonad.hs@: -- -- > import XMonad.Hooks.InsertPosition -- @@ -82,5 +82,7 @@ insertDown :: (Eq a) => a -> W.StackSet i l a s sd -> W.StackSet i l a s sd insertDown w = W.swapDown . W.insertUp w focusLast' :: W.Stack a -> W.Stack a -focusLast' st = let ws = W.integrate st - in W.Stack (last ws) (tail $ reverse ws) [] +focusLast' st = + case reverse (W.integrate st) of + [] -> st + (l : ws) -> W.Stack l ws [] diff --git a/XMonad/Hooks/ManageDocks.hs b/XMonad/Hooks/ManageDocks.hs index 5ed0b72f38..1b8f5aa833 100644 --- a/XMonad/Hooks/ManageDocks.hs +++ b/XMonad/Hooks/ManageDocks.hs @@ -49,7 +49,7 @@ import qualified Data.Map as M import qualified XMonad.StackSet as W -- $usage --- To use this module, add the following import to @~\/.xmonad\/xmonad.hs@: +-- To use this module, add the following import to @xmonad.hs@: -- -- > import XMonad.Hooks.ManageDocks -- diff --git a/XMonad/Hooks/ManageHelpers.hs b/XMonad/Hooks/ManageHelpers.hs index 0d2110847e..f1bf958142 100644 --- a/XMonad/Hooks/ManageHelpers.hs +++ b/XMonad/Hooks/ManageHelpers.hs @@ -51,7 +51,9 @@ module XMonad.Hooks.ManageHelpers ( isFullscreen, isMinimized, isDialog, + isNotification, pid, + desktop, transientTo, maybeToDefinite, MaybeManageHook, @@ -190,9 +192,18 @@ isMinimized :: Query Bool isMinimized = isInProperty "_NET_WM_STATE" "_NET_WM_STATE_HIDDEN" -- | A predicate to check whether a window is a dialog. +-- +-- See . isDialog :: Query Bool isDialog = isInProperty "_NET_WM_WINDOW_TYPE" "_NET_WM_WINDOW_TYPE_DIALOG" +-- | A predicate to check whether a window is a notification. +-- +-- See . +isNotification :: Query Bool +isNotification = + isInProperty "_NET_WM_WINDOW_TYPE" "_NET_WM_WINDOW_TYPE_NOTIFICATION" + -- | This function returns 'Just' the @_NET_WM_PID@ property for a -- particular window if set, 'Nothing' otherwise. -- @@ -202,6 +213,15 @@ pid = ask >>= \w -> liftX $ getProp32s "_NET_WM_PID" w <&> \case Just [x] -> Just (fromIntegral x) _ -> Nothing +-- | This function returns 'Just' the @_NET_WM_DESKTOP@ property for a +-- particular window if set, 'Nothing' otherwise. +-- +-- See . +desktop :: Query (Maybe Int) +desktop = ask >>= \w -> liftX $ getProp32s "_NET_WM_DESKTOP" w <&> \case + Just [x] -> Just (fromIntegral x) + _ -> Nothing + -- | A predicate to check whether a window is Transient. -- It holds the result which might be the window it is transient to -- or it might be 'Nothing'. diff --git a/XMonad/Hooks/Minimize.hs b/XMonad/Hooks/Minimize.hs index 75719150d4..df543f66db 100644 --- a/XMonad/Hooks/Minimize.hs +++ b/XMonad/Hooks/Minimize.hs @@ -25,7 +25,7 @@ import XMonad.Actions.Minimize import XMonad.Prelude -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Hooks.Minimize -- > import XMonad.Layout.Minimize @@ -43,10 +43,12 @@ minimizeEventHook ClientMessageEvent{ev_window = w, a_cs <- getAtom "WM_CHANGE_STATE" when (mt == a_aw) $ maximizeWindow w - when (mt == a_cs) $ do - let message = fromIntegral . head $ dt - when (message == normalState) $ maximizeWindow w - when (message == iconicState) $ minimizeWindow w + when (mt == a_cs) $ case listToMaybe dt of + Nothing -> pure () + Just dth -> do + let message = fromIntegral dth + when (message == normalState) $ maximizeWindow w + when (message == iconicState) $ minimizeWindow w return (All True) minimizeEventHook _ = return (All True) diff --git a/XMonad/Hooks/OnPropertyChange.hs b/XMonad/Hooks/OnPropertyChange.hs index ca9a75ced9..87b82cbf24 100644 --- a/XMonad/Hooks/OnPropertyChange.hs +++ b/XMonad/Hooks/OnPropertyChange.hs @@ -39,7 +39,7 @@ import XMonad import XMonad.Prelude -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Hooks.DynamicProperty -- diff --git a/XMonad/Hooks/Place.hs b/XMonad/Hooks/Place.hs index a25b0772c6..78ca2c227e 100644 --- a/XMonad/Hooks/Place.hs +++ b/XMonad/Hooks/Place.hs @@ -51,7 +51,7 @@ import Control.Monad.Trans (lift) -- floating windows at appropriate positions on the screen, as well -- as an 'X' action to manually trigger repositioning. -- --- You can use this module by including the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module by including the following in your @xmonad.hs@: -- -- > import XMonad.Hooks.Place -- @@ -186,21 +186,22 @@ placeHook p = do window <- ask -- spawned. Each of them also needs an associated screen -- rectangle; for hidden workspaces, we use the current -- workspace's screen. - let infos = filter ((window `elem`) . stackContents . S.stack . fst) + let infos = find ((window `elem`) . stackContents . S.stack . fst) $ [screenInfo $ S.current theWS] ++ map screenInfo (S.visible theWS) ++ map (, currentRect) (S.hidden theWS) - guard(not $ null infos) - - let (workspace, screen) = head infos - rs = mapMaybe (`M.lookup` allRs) - $ organizeClients workspace window floats - r' = purePlaceWindow p screen rs pointer r - newRect = r2rr screen r' - newFloats = M.insert window newRect (S.floating theWS) - - return $ theWS { S.floating = newFloats } + case infos of + Nothing -> empty + Just info -> do + let (workspace, screen) = info + rs = mapMaybe (`M.lookup` allRs) + $ organizeClients workspace window floats + r' = purePlaceWindow p screen rs pointer r + newRect = r2rr screen r' + newFloats = M.insert window newRect (S.floating theWS) + + return $ theWS { S.floating = newFloats } placeWindow :: Placement -> Window diff --git a/XMonad/Hooks/PositionStoreHooks.hs b/XMonad/Hooks/PositionStoreHooks.hs index 1697636e50..42a9722b29 100644 --- a/XMonad/Hooks/PositionStoreHooks.hs +++ b/XMonad/Hooks/PositionStoreHooks.hs @@ -45,7 +45,7 @@ import System.Random(randomRIO) import qualified Data.Set as S -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Hooks.PositionStoreHooks -- diff --git a/XMonad/Hooks/Rescreen.hs b/XMonad/Hooks/Rescreen.hs index b2cb015ba2..85216390f6 100644 --- a/XMonad/Hooks/Rescreen.hs +++ b/XMonad/Hooks/Rescreen.hs @@ -32,7 +32,7 @@ import qualified XMonad.Util.ExtensibleConf as XC -- ('XMonad.Hooks.StatusBar.dynamicSBs' uses this module internally), as well -- as to actually invoke xrandr or autorandr when an output is (dis)connected. -- --- To use this, include the following in your @~\/.xmonad\/xmonad.hs@: +-- To use this, include the following in your @xmonad.hs@: -- -- > import XMonad.Hooks.Rescreen -- diff --git a/XMonad/Hooks/ServerMode.hs b/XMonad/Hooks/ServerMode.hs index 7a03a3550d..d6b4abf78b 100644 --- a/XMonad/Hooks/ServerMode.hs +++ b/XMonad/Hooks/ServerMode.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE MultiWayIf #-} ----------------------------------------------------------------------------- -- | -- Module : XMonad.Hooks.ServerMode @@ -35,7 +36,7 @@ import XMonad.Actions.Commands -- $usage -- You can use this module with the following in your --- @~\/.xmonad\/xmonad.hs@: +-- @xmonad.hs@: -- -- > import XMonad.Hooks.ServerMode -- @@ -89,13 +90,14 @@ serverModeEventHookCmd' cmdAction = serverModeEventHookF "XMONAD_COMMAND" (mapM_ -- serverModeEventHookF :: String -> (String -> X ()) -> Event -> X All serverModeEventHookF key func ClientMessageEvent {ev_message_type = mt, ev_data = dt} = do - d <- asks display - atm <- io $ internAtom d key False - when (mt == atm && dt /= []) $ do - let atom = fromIntegral (head dt) + d <- asks display + atm <- io $ internAtom d key False + if | mt == atm, Just dth <- listToMaybe dt -> do + let atom = fromIntegral dth cmd <- io $ getAtomName d atom case cmd of - Just command -> func command - Nothing -> io $ hPutStrLn stderr ("Couldn't retrieve atom " ++ show atom) - return (All True) + Just command -> func command + Nothing -> io $ hPutStrLn stderr ("Couldn't retrieve atom " ++ show atom) + | otherwise -> pure () + return (All True) serverModeEventHookF _ _ _ = return (All True) diff --git a/XMonad/Hooks/SetWMName.hs b/XMonad/Hooks/SetWMName.hs index 89b074eead..bbf922b1c3 100644 --- a/XMonad/Hooks/SetWMName.hs +++ b/XMonad/Hooks/SetWMName.hs @@ -15,7 +15,7 @@ -- May be useful for making Java GUI programs work, just set WM name to \"LG3D\" -- and use Java 1.6u1 (1.6.0_01-ea-b03 works for me) or later. -- --- To your @~\/.xmonad\/xmonad.hs@ file, add the following line: +-- To your @xmonad.hs@ file, add the following line: -- -- > import XMonad.Hooks.SetWMName -- diff --git a/XMonad/Hooks/ShowWName.hs b/XMonad/Hooks/ShowWName.hs index dc5b31b18f..74648f08da 100644 --- a/XMonad/Hooks/ShowWName.hs +++ b/XMonad/Hooks/ShowWName.hs @@ -33,7 +33,7 @@ import Control.Concurrent (threadDelay) {- $usage You can use this module with the following in your -@~\/.xmonad\/xmonad.hs@: +@xmonad.hs@: > import XMonad.Hooks.ShowWName > diff --git a/XMonad/Hooks/StatusBar.hs b/XMonad/Hooks/StatusBar.hs index d162326860..ac419cce13 100644 --- a/XMonad/Hooks/StatusBar.hs +++ b/XMonad/Hooks/StatusBar.hs @@ -82,7 +82,7 @@ import XMonad.Hooks.StatusBar.PP import qualified XMonad.StackSet as W -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad -- > import XMonad.Hooks.StatusBar @@ -452,7 +452,7 @@ instance ExtensionClass ActiveSBs where -- 'avoidStruts', check 'dynamicEasySBs'. -- -- Heavily inspired by "XMonad.Hooks.DynamicBars" -dynamicSBs :: (ScreenId -> IO StatusBarConfig) -> XConfig l -> XConfig l +dynamicSBs :: (ScreenId -> X StatusBarConfig) -> XConfig l -> XConfig l dynamicSBs f conf = addAfterRescreenHook (updateSBs f) $ conf { startupHook = startupHook conf >> killAllStatusBars >> updateSBs f , logHook = logHook conf >> logSBs @@ -462,7 +462,7 @@ dynamicSBs f conf = addAfterRescreenHook (updateSBs f) $ conf -- resulting config and adds 'avoidStruts' to the -- layout. dynamicEasySBs :: LayoutClass l Window - => (ScreenId -> IO StatusBarConfig) + => (ScreenId -> X StatusBarConfig) -> XConfig l -> XConfig (ModifiedLayout AvoidStruts l) dynamicEasySBs f conf = @@ -471,7 +471,7 @@ dynamicEasySBs f conf = -- | Given the function to create status bars, update -- the status bars by killing those that shouldn't be -- visible anymore and creates any missing status bars -updateSBs :: (ScreenId -> IO StatusBarConfig) -> X () +updateSBs :: (ScreenId -> X StatusBarConfig) -> X () updateSBs f = do actualScreens <- withWindowSet $ return . map W.screen . W.screens (toKeep, toKill) <- @@ -480,7 +480,7 @@ updateSBs f = do cleanSBs (map snd toKill) -- Create new status bars if needed let missing = actualScreens \\ map fst toKeep - added <- io $ traverse (\s -> (s,) <$> f s) missing + added <- traverse (\s -> (s,) <$> f s) missing traverse_ (sbStartupHook . snd) added XS.put (ASB (toKeep ++ added)) diff --git a/XMonad/Hooks/StatusBar/PP.hs b/XMonad/Hooks/StatusBar/PP.hs index 9b7f9fa660..1a172f4b9a 100644 --- a/XMonad/Hooks/StatusBar/PP.hs +++ b/XMonad/Hooks/StatusBar/PP.hs @@ -57,6 +57,7 @@ module XMonad.Hooks.StatusBar.PP ( import Control.Monad.Reader import Control.DeepSeq +import qualified Data.List.NonEmpty as NE import XMonad import XMonad.Prelude @@ -401,7 +402,7 @@ dzenStrip = strip [] where strip keep x | null x = keep | "^^" `isPrefixOf` x = strip (keep ++ "^") (drop 2 x) - | '^' == head x = strip keep (drop 1 . dropWhile (/= ')') $ x) + | "^" `isPrefixOf` x = strip keep (drop 1 . dropWhile (/= ')') $ x) | otherwise = let (good,x') = span (/= '^') x in strip (keep ++ good) x' @@ -463,8 +464,12 @@ xmobarStrip :: String -> String xmobarStrip = converge (xmobarStripTags ["fc","icon","action"]) converge :: (Eq a) => (a -> a) -> a -> a -converge f a = let xs = iterate f a - in fst $ head $ dropWhile (uncurry (/=)) $ zip xs $ tail xs +converge f a + = fst . NE.head . notEmpty -- If this function terminates, we will find a match. + . dropWhile (uncurry (/=)) + . zip xs + $ drop 1 xs + where xs = iterate f a xmobarStripTags :: [String] -- ^ tags -> String -> String -- ^ with all \...\ removed diff --git a/XMonad/Hooks/StatusBar/WorkspaceScreen.hs b/XMonad/Hooks/StatusBar/WorkspaceScreen.hs index eaa40a1c35..cf765d9e86 100644 --- a/XMonad/Hooks/StatusBar/WorkspaceScreen.hs +++ b/XMonad/Hooks/StatusBar/WorkspaceScreen.hs @@ -34,7 +34,7 @@ import XMonad.Prelude import qualified XMonad.StackSet as W {- $usage - You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: + You can use this module with the following in your @xmonad.hs@: > import XMonad > import XMonad.Hooks.StatusBar diff --git a/XMonad/Hooks/ToggleHook.hs b/XMonad/Hooks/ToggleHook.hs index 7eae7da928..c25fc63552 100644 --- a/XMonad/Hooks/ToggleHook.hs +++ b/XMonad/Hooks/ToggleHook.hs @@ -77,7 +77,7 @@ modify' n f = XS.modify (HookState . setter . hooks) -- This module provides actions (that can be set as keybindings) -- to be able to cause hooks to be occur on a conditional basis. -- --- You can use it by including the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use it by including the following in your @xmonad.hs@: -- -- > import XMonad.Hooks.ToggleHook -- diff --git a/XMonad/Hooks/WallpaperSetter.hs b/XMonad/Hooks/WallpaperSetter.hs index 12d6132a7a..4e49be0562 100644 --- a/XMonad/Hooks/WallpaperSetter.hs +++ b/XMonad/Hooks/WallpaperSetter.hs @@ -140,7 +140,7 @@ getPicPath conf (WallpaperDir dir) = do direxists <- doesDirectoryExist $ wallpaperBaseDir conf dir if direxists then do files <- getDirectoryContents $ wallpaperBaseDir conf dir - let files' = filter ((/='.').head) files + let files' = filter (not . ("." `isPrefixOf`)) files file <- pickFrom files' return $ Just $ wallpaperBaseDir conf dir file else return Nothing diff --git a/XMonad/Hooks/WindowSwallowing.hs b/XMonad/Hooks/WindowSwallowing.hs index 66fa1b5ac6..895c704124 100644 --- a/XMonad/Hooks/WindowSwallowing.hs +++ b/XMonad/Hooks/WindowSwallowing.hs @@ -54,7 +54,7 @@ import qualified Data.Map.Strict as M import System.Posix.Types ( ProcessID ) -- $usage --- You can use this module by including the following in your @~\/.xmonad/xmonad.hs@: +-- You can use this module by including the following in your @xmonad.hs@: -- -- > import XMonad.Hooks.WindowSwallowing -- diff --git a/XMonad/Hooks/WorkspaceByPos.hs b/XMonad/Hooks/WorkspaceByPos.hs index e073af63fd..c384f8ae3d 100644 --- a/XMonad/Hooks/WorkspaceByPos.hs +++ b/XMonad/Hooks/WorkspaceByPos.hs @@ -30,7 +30,7 @@ import Control.Monad.Except (runExceptT, throwError) import Control.Monad.Trans (lift) -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Hooks.WorkspaceByPos -- > diff --git a/XMonad/Hooks/WorkspaceHistory.hs b/XMonad/Hooks/WorkspaceHistory.hs index 3af6e17d2d..170145e7b2 100644 --- a/XMonad/Hooks/WorkspaceHistory.hs +++ b/XMonad/Hooks/WorkspaceHistory.hs @@ -34,12 +34,12 @@ import Control.DeepSeq import Prelude import XMonad import XMonad.StackSet hiding (delete, filter, new) -import XMonad.Prelude (delete, find, foldl', groupBy, nub, sortBy) +import XMonad.Prelude (delete, find, foldl', groupBy, nub, sortBy, listToMaybe) import qualified XMonad.Util.ExtensibleState as XS -- $usage -- To record the order in which you view workspaces, you can use this --- module with the following in your @~\/.xmonad\/xmonad.hs@: +-- module with the following in your @xmonad.hs@: -- -- > import XMonad.Hooks.WorkspaceHistory (workspaceHistoryHook) -- @@ -90,7 +90,7 @@ workspaceHistoryWithScreen = XS.gets history workspaceHistoryByScreen :: X [(ScreenId, [WorkspaceId])] workspaceHistoryByScreen = - map (\wss -> (fst $ head wss, map snd wss)) . + map (\wss -> (maybe 0 fst (listToMaybe wss), map snd wss)) . groupBy (\a b -> fst a == fst b) . sortBy (\a b -> compare (fst a) $ fst b)<$> workspaceHistoryWithScreen diff --git a/XMonad/Hooks/XPropManage.hs b/XMonad/Hooks/XPropManage.hs index 41d0986a2e..bb42a0b572 100644 --- a/XMonad/Hooks/XPropManage.hs +++ b/XMonad/Hooks/XPropManage.hs @@ -26,7 +26,7 @@ import XMonad import XMonad.Prelude (Endo (..), chr) -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Hooks.XPropManage -- > import qualified XMonad.StackSet as W diff --git a/XMonad/Layout/Accordion.hs b/XMonad/Layout/Accordion.hs index 76dfa33d10..4a87c5da3b 100644 --- a/XMonad/Layout/Accordion.hs +++ b/XMonad/Layout/Accordion.hs @@ -25,7 +25,7 @@ import qualified XMonad.StackSet as W import Data.Ratio -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.Accordion -- diff --git a/XMonad/Layout/AutoMaster.hs b/XMonad/Layout/AutoMaster.hs index 24d2998aec..b42fa698d0 100644 --- a/XMonad/Layout/AutoMaster.hs +++ b/XMonad/Layout/AutoMaster.hs @@ -35,7 +35,7 @@ import Control.Arrow (first) -- in one row, in slave area underlying layout is run. Size of slave area -- automatically increases when number of slave windows is increasing. -- --- You can use this module by adding folowing in your @xmonad.hs@: +-- You can use this module by adding following in your @xmonad.hs@: -- -- > import XMonad.Layout.AutoMaster -- diff --git a/XMonad/Layout/AvoidFloats.hs b/XMonad/Layout/AvoidFloats.hs index b9e88f123b..8de2c0fde6 100644 --- a/XMonad/Layout/AvoidFloats.hs +++ b/XMonad/Layout/AvoidFloats.hs @@ -35,7 +35,7 @@ import qualified Data.Map as M import qualified Data.Set as S -- $usage --- You can use this module with the following in your ~\/.xmonad\/xmonad.hs file: +-- You can use this module with the following in your @xmonad.hs@ file: -- -- > import XMonad.Layout.AvoidFloats -- diff --git a/XMonad/Layout/BinarySpacePartition.hs b/XMonad/Layout/BinarySpacePartition.hs index 1966ccd2af..83f6589fad 100644 --- a/XMonad/Layout/BinarySpacePartition.hs +++ b/XMonad/Layout/BinarySpacePartition.hs @@ -52,7 +52,7 @@ import qualified Data.Set as S import Data.Ratio ((%)) -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.BinarySpacePartition -- diff --git a/XMonad/Layout/BorderResize.hs b/XMonad/Layout/BorderResize.hs index 44595b0f7d..f697bfe309 100644 --- a/XMonad/Layout/BorderResize.hs +++ b/XMonad/Layout/BorderResize.hs @@ -38,7 +38,7 @@ import qualified Data.Map as M -- $usage -- You can use this module with the following in your --- @~\/.xmonad\/xmonad.hs@: +-- @xmonad.hs@: -- -- > import XMonad.Layout.BorderResize -- > myLayout = borderResize (... layout setup that reacts to SetGeometry ...) diff --git a/XMonad/Layout/BoringWindows.hs b/XMonad/Layout/BoringWindows.hs index 8effd2e9a8..f1816ead0e 100644 --- a/XMonad/Layout/BoringWindows.hs +++ b/XMonad/Layout/BoringWindows.hs @@ -45,7 +45,7 @@ import qualified XMonad.StackSet as W -- $usage -- You can use this module with the following in your --- @~\/.xmonad\/xmonad.hs@: +-- @xmonad.hs@: -- -- > import XMonad.Layout.BoringWindows -- diff --git a/XMonad/Layout/ButtonDecoration.hs b/XMonad/Layout/ButtonDecoration.hs index 31f73965b6..1c1cbbc429 100644 --- a/XMonad/Layout/ButtonDecoration.hs +++ b/XMonad/Layout/ButtonDecoration.hs @@ -33,7 +33,7 @@ import XMonad.Layout.DecorationAddons -- $usage -- You can use this module with the following in your --- @~\/.xmonad\/xmonad.hs@: +-- @xmonad.hs@: -- -- > import XMonad.Layout.DecorationAddons -- > import XMonad.Layout.ButtonDecoration diff --git a/XMonad/Layout/CenteredIfSingle.hs b/XMonad/Layout/CenteredIfSingle.hs index 07d24ee009..7f4c761ff8 100644 --- a/XMonad/Layout/CenteredIfSingle.hs +++ b/XMonad/Layout/CenteredIfSingle.hs @@ -30,7 +30,7 @@ import XMonad.Layout.LayoutModifier import XMonad.Prelude (fi) -- $usage --- You can use this module by including the following in your @~\/.xmonad/xmonad.hs@: +-- You can use this module by including the following in your @xmonad.hs@: -- -- > import XMonad.Layout.CenteredIfSingle -- diff --git a/XMonad/Layout/CenteredMaster.hs b/XMonad/Layout/CenteredMaster.hs index 819cb0324d..b71a0e484b 100644 --- a/XMonad/Layout/CenteredMaster.hs +++ b/XMonad/Layout/CenteredMaster.hs @@ -38,7 +38,7 @@ import Control.Arrow (first) -- All other windows in background are managed by base layout. -- topRightMaster is like centerMaster, but places master window in top right corner instead of center. -- --- Yo can use this module by adding folowing in your @xmonad.hs@: +-- You can use this module by adding following in your @xmonad.hs@: -- -- > import XMonad.Layout.CenteredMaster -- @@ -80,11 +80,9 @@ applyPosition :: (LayoutClass l a, Eq a) => applyPosition pos wksp rect = do let stack = W.stack wksp let ws = W.integrate' stack - if null ws then - runLayout wksp rect - else do - let firstW = head ws - let other = tail ws + case ws of + [] -> runLayout wksp rect + (firstW : other) -> do let filtStack = stack >>= W.filter (firstW /=) wrs <- runLayout (wksp {W.stack = filtStack}) rect return $ first ((firstW, place pos other rect) :) wrs diff --git a/XMonad/Layout/Circle.hs b/XMonad/Layout/Circle.hs index 29dfb1b754..d33cfb9aee 100644 --- a/XMonad/Layout/Circle.hs +++ b/XMonad/Layout/Circle.hs @@ -1,4 +1,5 @@ {-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-} +{-# LANGUAGE PatternSynonyms #-} ----------------------------------------------------------------------------- -- | @@ -15,18 +16,17 @@ -- ----------------------------------------------------------------------------- -module XMonad.Layout.Circle ( - -- * Usage - -- $usage - Circle (..) - ) where -- actually it's an ellipse +module XMonad.Layout.Circle {-# DEPRECATED "Use XMonad.Layout.CircleEx instead" #-} + ( -- * Usage + -- $usage + pattern Circle + ) where -- actually it's an ellipse -import XMonad.Prelude -import XMonad -import XMonad.StackSet (integrate, peek) +import GHC.Real (Ratio(..)) +import XMonad.Layout.CircleEx -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.Circle -- @@ -39,37 +39,6 @@ import XMonad.StackSet (integrate, peek) -- and -- "XMonad.Doc.Extending#Editing_the_layout_hook". -data Circle a = Circle deriving ( Read, Show ) +pattern Circle :: CircleEx a +pattern Circle = CircleEx 1 (70 :% 99) (2 :% 5) 1 0 -instance LayoutClass Circle Window where - doLayout Circle r s = do layout <- raiseFocus $ circleLayout r $ integrate s - return (layout, Nothing) - -circleLayout :: Rectangle -> [a] -> [(a, Rectangle)] -circleLayout _ [] = [] -circleLayout r (w:ws) = master : rest - where master = (w, center r) - rest = zip ws $ map (satellite r) [0, pi * 2 / fromIntegral (length ws) ..] - -raiseFocus :: [(Window, Rectangle)] -> X [(Window, Rectangle)] -raiseFocus xs = do focused <- withWindowSet (return . peek) - return $ case find ((== focused) . Just . fst) xs of - Just x -> x : delete x xs - Nothing -> xs - -center :: Rectangle -> Rectangle -center (Rectangle sx sy sw sh) = Rectangle x y w h - where s = sqrt 2 :: Double - w = round (fromIntegral sw / s) - h = round (fromIntegral sh / s) - x = sx + fromIntegral (sw - w) `div` 2 - y = sy + fromIntegral (sh - h) `div` 2 - -satellite :: Rectangle -> Double -> Rectangle -satellite (Rectangle sx sy sw sh) a = Rectangle (sx + round (rx + rx * cos a)) - (sy + round (ry + ry * sin a)) - w h - where rx = fromIntegral (sw - w) / 2 - ry = fromIntegral (sh - h) / 2 - w = sw * 10 `div` 25 - h = sh * 10 `div` 25 diff --git a/XMonad/Layout/CircleEx.hs b/XMonad/Layout/CircleEx.hs new file mode 100644 index 0000000000..34bb69bbca --- /dev/null +++ b/XMonad/Layout/CircleEx.hs @@ -0,0 +1,189 @@ +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE InstanceSigs #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE RecordWildCards #-} +----------------------------------------------------------------------------- +-- | +-- Module : XMonad.Layout.CircleEx +-- Description : An elliptical, overlapping layout—extended version. +-- Copyright : (c) Peter De Wachter, Ilya V. Portnov +-- License : BSD-style (see LICENSE) +-- +-- Maintainer : Ilya V. Portnov +-- Stability : unstable +-- Portability : unportable +-- +-- Circle is an elliptical, overlapping layout. Original code by Peter De Wachter, +-- extended by Ilya Porntov. +----------------------------------------------------------------------------- + +module XMonad.Layout.CircleEx ( + -- * Usage + -- $usage + CircleEx (..), circle, circleEx, + CircleExMsg (..) + ) + where + +import Data.Ratio + +import XMonad +import XMonad.StackSet (Stack) +import XMonad.Prelude +import qualified XMonad.StackSet as W + +-- $usage +-- +-- The layout puts the first N windows (called master) into the center of +-- screen. All others (called secondary, or stack) are organized in a circle +-- (well, ellipse). When opening a new secondary window, its size will be +-- slightly smaller than that of its predecessor (this is configurable). If +-- the number of master windows is set to zero, all windows will be arranged +-- in a circle. If there is more than one master window, they will be stacked +-- in the center on top of each other. The size of each additional master +-- window will again be slightly smaller than that of the former. +-- +-- Since a picture says more than a thousand words, you see one +-- . +-- +-- You can use this module with the following in your @xmonad.hs@: +-- +-- > import XMonad.Layout.CircleEx +-- +-- Then edit your @layoutHook@ by adding the 'CircleEx' layout: +-- +-- > myCircle = circleEx {cDelta = -3*pi/4} +-- > myLayout = myCircle ||| Full ||| etc.. +-- > main = xmonad def { layoutHook = myLayout } +-- +-- This layout understands standard messages: +-- +-- * 'IncMasterN': increase or decrease the number of master windows. +-- * 'Shrink' and 'Expand': change the size of master windows. +-- +-- More layout-specific messages are also supported, see 'CircleExMsg' below. +-- +-- For more detailed instructions on editing the layoutHook see: +-- "XMonad.Doc.Extending#Editing_the_layout_hook" + +-- | The layout data type. It is recommended to not use the 'CircleEx' data +-- constructor directly, and instead rely on record update syntax; for +-- example: @circleEx {cMasterRatio = 4%5}@. In this way you can avoid nasty +-- surprises if one day additional fields are added to @CircleEx@. +data CircleEx a = CircleEx + { cNMaster :: !Int -- ^ Number of master windows. Default value is 1. + , cMasterRatio :: !Rational -- ^ Size of master window in relation to screen size. + -- Default value is @4%5@. + , cStackRatio :: !Rational -- ^ Size of first secondary window in relation to screen size. + -- Default value is @3%5@. + , cMultiplier :: !Rational -- ^ Coefficient used to calculate the sizes of subsequent secondary + -- windows. The size of the next window is calculated as the + -- size of the previous one multiplied by this value. + -- This value is also used to scale master windows, in case + -- there is more than one. + -- Default value is @5%6@. Set this to 1 if you want all secondary + -- windows to have the same size. + , cDelta :: !Double -- ^ Angle of rotation of the whole circle layout. Usual values + -- are from 0 to 2π, although it will work outside + -- this range as well. Default value of 0 means that the first + -- secondary window will be placed at the right side of screen. + } deriving (Eq, Show, Read) + +-- | Circle layout with default settings: +-- +-- * Number of master windows is set to 1 +-- * @cMasterRatio@ is set to @70/99@, which is nearly @1/sqrt(2)@ +-- * @cStackRatio@ is set to @2/5@ +-- * @cMultiplier@ is set to 1, which means all secondary windows +-- will have the same size +-- +-- This can be used as a drop-in replacement for "XMonad.Layout.Circle". +circle :: CircleEx a +circle = CircleEx 1 (70%99) (2%5) 1 0 + +-- | Another variant of default settings for circle layout: +-- +-- * Number of master windows is set to 1 +-- * @cMasterRatio@ is set to @4/5@ +-- * @cStackRatio@ is set to @3/5@ +-- * @cMultiplier@ is set to @5/6@ +-- +circleEx :: CircleEx a +circleEx = CircleEx 1 (4%5) (3%5) (5%6) 0 + +-- | Specific messages understood by CircleEx layout. +data CircleExMsg + = Rotate !Double -- ^ Rotate secondary windows by specific angle + | IncStackRatio !Rational -- ^ Increase (or decrease, with negative value) sizes of secondary windows + | IncMultiplier !Rational -- ^ Increase 'cMultiplier'. + deriving (Eq, Show, Typeable) + +instance Message CircleExMsg + +instance LayoutClass CircleEx Window where + doLayout :: CircleEx Window -> Rectangle -> Stack Window -> X ([(Window, Rectangle)], Maybe (CircleEx Window)) + doLayout layout rect stack = do + result <- raiseFocus $ circleLayout layout rect $ W.integrate stack + return (result, Nothing) + + pureMessage :: CircleEx Window -> SomeMessage -> Maybe (CircleEx Window) + pureMessage layout m = + msum [changeMasterN <$> fromMessage m, + resize <$> fromMessage m, + specific <$> fromMessage m] + where + deltaSize = 11 % 10 + + resize :: Resize -> CircleEx a + resize Shrink = layout {cMasterRatio = max 0.1 $ min 1.0 $ cMasterRatio layout / deltaSize} + resize Expand = layout {cMasterRatio = max 0.1 $ min 1.0 $ cMasterRatio layout * deltaSize} + + changeMasterN :: IncMasterN -> CircleEx a + changeMasterN (IncMasterN d) = layout {cNMaster = max 0 (cNMaster layout + d)} + + specific :: CircleExMsg -> CircleEx a + specific (Rotate delta) = layout {cDelta = delta + cDelta layout} + specific (IncStackRatio delta) = layout {cStackRatio = max 0.1 $ min 2.0 $ delta + cStackRatio layout} + specific (IncMultiplier delta) = layout {cMultiplier = max 0.1 $ min 2.0 $ delta + cMultiplier layout} + +circleLayout :: CircleEx a -> Rectangle -> [a] -> [(a, Rectangle)] +circleLayout _ _ [] = [] +circleLayout (CircleEx {..}) rectangle wins = + master (take cNMaster wins) ++ rest (drop cNMaster wins) + where + master :: [a] -> [(a, Rectangle)] + master ws = zip ws $ map (placeCenter cMasterRatio cMultiplier rectangle) + [cNMaster-1, cNMaster-2 .. 0] + rest :: [a] -> [(a, Rectangle)] + rest ws = zip ws $ zipWith (placeSatellite cStackRatio cMultiplier rectangle) + (map (+ cDelta) [0, pi*2 / fromIntegral (length ws) ..]) + [0 ..] + + +raiseFocus :: [(Window, Rectangle)] -> X [(Window, Rectangle)] +raiseFocus wrs = do + focused <- withWindowSet (return . W.peek) + return $ case find ((== focused) . Just . fst) wrs of + Just x -> x : delete x wrs + Nothing -> wrs + +placeCenter :: Rational -> Rational -> Rectangle -> Int -> Rectangle +placeCenter ratio multiplier (Rectangle x y width height) n = Rectangle x' y' width' height' + where + m = ratio * multiplier ^ n + width' = round (m * fromIntegral width) + height' = round (m * fromIntegral height) + x' = x + fromIntegral (width - width') `div` 2 + y' = y + fromIntegral (height - height') `div` 2 + +placeSatellite :: Rational -> Rational -> Rectangle -> Double -> Int -> Rectangle +placeSatellite ratio multiplier (Rectangle x y width height) alpha n = + Rectangle x' y' width' height' + where + m = ratio * multiplier ^ n + x' = x + round (rx + rx * cos alpha) + y' = y + round (ry + ry * sin alpha) + rx = fromIntegral (width - width') / 2 + ry = fromIntegral (height - height') / 2 + width' = round (fromIntegral width * m) + height' = round (fromIntegral height * m) diff --git a/XMonad/Layout/Column.hs b/XMonad/Layout/Column.hs index 11efd300d5..5de2054d3c 100644 --- a/XMonad/Layout/Column.hs +++ b/XMonad/Layout/Column.hs @@ -11,7 +11,7 @@ -- Portability : unportable -- -- Provides Column layout that places all windows in one column. Windows --- heights are calculated from equation: H1/H2 = H2/H3 = ... = q, where q is +-- heights are calculated from the equation: H1/H2 = H2/H3 = ... = q, where q is -- given. With Shrink/Expand messages you can change the q value. -- ----------------------------------------------------------------------------- @@ -25,12 +25,12 @@ import XMonad import qualified XMonad.StackSet as W -- $usage --- This module defines layot named Column. It places all windows in one --- column. Windows heights are calculated from equation: H1/H2 = H2/H3 = ... = +-- This module defines layout named Column. It places all windows in one +-- column. Windows heights are calculated from the equation: H1/H2 = H2/H3 = ... = -- q, where `q' is given (thus, windows heights are members of geometric -- progression). With Shrink/Expand messages one can change the `q' value. -- --- You can use this module by adding folowing in your @xmonad.hs@: +-- You can use this module by adding following in your @xmonad.hs@: -- -- > import XMonad.Layout.Column -- diff --git a/XMonad/Layout/Combo.hs b/XMonad/Layout/Combo.hs index e5e41c70c1..2387203f45 100644 --- a/XMonad/Layout/Combo.hs +++ b/XMonad/Layout/Combo.hs @@ -1,5 +1,8 @@ -{-# LANGUAGE FlexibleContexts, FlexibleInstances, MultiParamTypeClasses, - UndecidableInstances, PatternGuards #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE PatternGuards #-} +{-# LANGUAGE UndecidableInstances #-} ----------------------------------------------------------------------------- -- | @@ -24,13 +27,13 @@ module XMonad.Layout.Combo ( ) where import XMonad hiding (focus) -import XMonad.Prelude (delete, fromMaybe, intersect, isJust, (\\)) -import XMonad.StackSet ( integrate', Workspace (..), Stack(..) ) -import XMonad.Layout.WindowNavigation ( MoveWindowToWindow(..) ) -import qualified XMonad.StackSet as W ( differentiate ) +import XMonad.Layout.WindowNavigation (MoveWindowToWindow (..)) +import XMonad.Prelude (delete, fromMaybe, intersect, isJust, (\\), listToMaybe) +import XMonad.StackSet (Stack (..), Workspace (..), integrate') +import XMonad.Util.Stack (zipperFocusedAtFirstOf) -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.Combo -- @@ -88,14 +91,14 @@ instance (LayoutClass l (), LayoutClass l1 a, LayoutClass l2 a, Read a, Show a, handleMessage super (SomeMessage ReleaseResources) return ([(w,rinput)], Just $ C2 [w] [w] super' l1' l2') arrange origws = - do let w2' = case origws `intersect` w2 of [] -> [head origws] + do let w2' = case origws `intersect` w2 of [] -> take 1 origws [x] -> [x] x -> case origws \\ x of [] -> init x _ -> x superstack = Stack { focus=(), up=[], down=[()] } - s1 = differentiate f' (origws \\ w2') - s2 = differentiate f' w2' + s1 = zipperFocusedAtFirstOf f' (origws \\ w2') + s2 = zipperFocusedAtFirstOf f' w2' f' = case s of (Just s') -> focus s':delete (focus s') f Nothing -> f ([((),r1),((),r2)], msuper') <- runLayout (Workspace "" super (Just superstack)) rinput @@ -121,21 +124,13 @@ instance (LayoutClass l (), LayoutClass l1 a, LayoutClass l2 a, Read a, Show a, msuper' <- broadcastPrivate m [super] if isJust msuper' || isJust ml1' || isJust ml2' then return $ Just $ C2 f ws2 - (maybe super head msuper') - (maybe l1 head ml1') - (maybe l2 head ml2') + (fromMaybe super (listToMaybe =<< msuper')) + (fromMaybe l1 (listToMaybe =<< ml1')) + (fromMaybe l2 (listToMaybe =<< ml2')) else return Nothing description (C2 _ _ super l1 l2) = "combining "++ description l1 ++" and "++ description l2 ++" with "++ description super - -differentiate :: Eq q => [q] -> [q] -> Maybe (Stack q) -differentiate (z:zs) xs | z `elem` xs = Just $ Stack { focus=z - , up = reverse $ takeWhile (/=z) xs - , down = tail $ dropWhile (/=z) xs } - | otherwise = differentiate zs xs -differentiate [] xs = W.differentiate xs - broadcastPrivate :: LayoutClass l b => SomeMessage -> [l b] -> X (Maybe [l b]) broadcastPrivate a ol = do nml <- mapM f ol if any isJust nml diff --git a/XMonad/Layout/ComboP.hs b/XMonad/Layout/ComboP.hs index a1631cb37c..7a3f5ea9c3 100644 --- a/XMonad/Layout/ComboP.hs +++ b/XMonad/Layout/ComboP.hs @@ -25,15 +25,16 @@ module XMonad.Layout.ComboP ( Property(..) ) where -import XMonad.Prelude import XMonad hiding (focus) -import XMonad.StackSet ( Workspace (..), Stack(..) ) import XMonad.Layout.WindowNavigation -import XMonad.Util.WindowProperties +import XMonad.Prelude +import XMonad.StackSet ( Workspace (..), Stack(..) ) import qualified XMonad.StackSet as W +import XMonad.Util.Stack (zipperFocusedAtFirstOf) +import XMonad.Util.WindowProperties -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.ComboP -- @@ -99,10 +100,10 @@ instance (LayoutClass l (), LayoutClass l1 Window, LayoutClass l2 Window) => f' = focus s:delete (focus s) f -- list of focused windows, contains 2 elements at most in do matching <- hasProperty prop `filterM` new -- new windows matching predecate - let w1' = w1c ++ matching -- updated first pane windows - w2' = w2c ++ (new \\ matching) -- updated second pane windows - s1 = differentiate f' w1' -- first pane stack - s2 = differentiate f' w2' -- second pane stack + let w1' = w1c ++ matching -- updated first pane windows + w2' = w2c ++ (new \\ matching) -- updated second pane windows + s1 = zipperFocusedAtFirstOf f' w1' -- first pane stack + s2 = zipperFocusedAtFirstOf f' w2' -- second pane stack ([((),r1),((),r2)], msuper') <- runLayout (Workspace "" super superstack) rinput (wrs1, ml1') <- runLayout (Workspace "" l1 s1) r1 (wrs2, ml2') <- runLayout (Workspace "" l2 s2) r2 @@ -177,15 +178,4 @@ forwardIfFocused l w m = do then handleMessage l m else return Nothing --- code from CombineTwo --- given two sets of zs and xs takes the first z from zs that also belongs to xs --- and turns xs into a stack with z being current element. Acts as --- StackSet.differentiate if zs and xs don't intersect -differentiate :: Eq q => [q] -> [q] -> Maybe (Stack q) -differentiate (z:zs) xs | z `elem` xs = Just $ Stack { focus=z - , up = reverse $ takeWhile (/=z) xs - , down = tail $ dropWhile (/=z) xs } - | otherwise = differentiate zs xs -differentiate [] xs = W.differentiate xs - -- vim:ts=4:shiftwidth=4:softtabstop=4:expandtab:foldlevel=20: diff --git a/XMonad/Layout/Cross.hs b/XMonad/Layout/Cross.hs index 261b05d95a..e6c892f170 100644 --- a/XMonad/Layout/Cross.hs +++ b/XMonad/Layout/Cross.hs @@ -12,7 +12,7 @@ -- -- A Cross Layout with the main window in the center. -- -module XMonad.Layout.Cross( +module XMonad.Layout.Cross {-# DEPRECATED "Use XMonad.Layout.Circle or XMonad.Layout.ThreeColumn.ThreeColMid instead" #-} ( -- * Usage -- $usage simpleCross @@ -23,7 +23,7 @@ import XMonad.StackSet( focus, up, down ) import XMonad.Prelude( msum ) -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.Cross -- diff --git a/XMonad/Layout/DecorationEx.hs b/XMonad/Layout/DecorationEx.hs new file mode 100644 index 0000000000..9db42667e4 --- /dev/null +++ b/XMonad/Layout/DecorationEx.hs @@ -0,0 +1,106 @@ + +----------------------------------------------------------------------------- +-- | +-- Module : XMonad.Layout.DecorationEx +-- Description : Advanced window decorations module for XMonad +-- Copyright : (c) 2007 Andrea Rossato, 2009 Jan Vornberger, 2023 Ilya Portnov +-- License : BSD-style (see xmonad/LICENSE) +-- +-- Maintainer : portnov84@rambler.ru +-- Stability : unstable +-- Portability : unportable +-- +-- This set of modules contains a set of type classes and their implementations +-- which define a flexible and extensible mechanism of window decorations. +-- +-- <> +-- Click +-- for a larger version. +-- +-- Within this mechanism, there are the following entities which define +-- how decorations will look and work: +-- +-- * Main object is @DecorationEx@ layout modifier. It is from where everything +-- starts. It creates, shows and hides decoration windows (rectangles) when +-- needed. It is parameterized with decoration geometry, decoration engine and +-- theme. It calls these components to do their parts of the work. +-- * @DecorationGeometry@ defines where decoration rectangles should be placed. +-- For example, standard horizontal bar above each window; or tab bar. +-- * @DecorationEngine@ defines how decorations look and how they react on clicks. +-- Different implementations of the decoration engine can use different APIs +-- to draw decorations. Within this package, there is one implementation +-- (@TextDecoration@), which uses plain Xlib calls, and displays decoration +-- widgets with text fragments, like @[X]@ or @[_]@. Other engines can, for +-- example, use the Cairo library to draw nice gradients and image-based widgets. +-- * A Decoration widget is an element placed on a window decoration. It defines how +-- it looks and how it responds to clicks. Examples include usual window +-- buttons (minimize, maximize, close), window icon, window title. +-- * A Decoration theme defines colors and fonts for the decoration engine. It also +-- contains a list of decoration widgets and says where to place them (at the +-- left, at the right or in the center). +-- +-- This mechanism makes major use of parameterized data types and type families, +-- in order to make it possible to define different types of decorations, and +-- easily combine different aspects of decorations. For example, each decoration +-- engine can be combined with each decoration geometry. +----------------------------------------------------------------------------- + +module XMonad.Layout.DecorationEx ( + -- * Usage: + -- $usage + + -- * Standard decoration settings + decorationEx, + textDecoration, textTabbed, dwmStyleDeco, + -- * Decoration-related types + TextDecoration (..), DefaultGeometry (..), + TabbedGeometry (..), DwmGeometry (..), + DecorationEx, + -- * Theme types + BoxBorders (..), BorderColors, + SimpleStyle (..), GenericTheme (..), + ThemeEx, + -- * Widget types + StandardCommand (..), GenericWidget (..), + StandardWidget, + -- * Utility functions for themes + themeEx, borderColor, shadowBorder, + -- * Convinience re-exports + Shrinker (..), shrinkText, + -- * Standard widgets + titleW, toggleStickyW, minimizeW, + maximizeW, closeW, dwmpromoteW, + moveToNextGroupW, moveToPrevGroupW + ) where + +import XMonad.Layout.Decoration +import XMonad.Layout.DecorationEx.Common +import XMonad.Layout.DecorationEx.Widgets +import XMonad.Layout.DecorationEx.Geometry +import XMonad.Layout.DecorationEx.LayoutModifier +import XMonad.Layout.DecorationEx.TextEngine +import XMonad.Layout.DecorationEx.TabbedGeometry +import XMonad.Layout.DecorationEx.DwmGeometry + +-- $usage +-- +-- You can use this module with the following in your +-- @xmonad.hs@: +-- +-- > import XMonad.Layout.DecorationEx +-- Then edit your @layoutHook@ by adding the DwmStyle decoration to +-- your layout: +-- +-- > myTheme = ThemeEx {...} +-- > myL = textDecoration shrinkText myTheme (layoutHook def) +-- > main = xmonad def { layoutHook = myL } +-- +-- For more detailed instructions on editing the layoutHook see: +-- +-- "XMonad.Doc.Extending#Editing_the_layout_hook" +-- +-- This module exports only some definitions from it's submodules, +-- most likely to be used from user configurations. To define +-- your own decoration types you will likely have to import specific +-- submodules. + diff --git a/XMonad/Layout/DecorationEx/Common.hs b/XMonad/Layout/DecorationEx/Common.hs new file mode 100644 index 0000000000..ba472c648c --- /dev/null +++ b/XMonad/Layout/DecorationEx/Common.hs @@ -0,0 +1,272 @@ +{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE UndecidableInstances #-} + +----------------------------------------------------------------------------- +-- | +-- Module : XMonad.Layout.DecorationEx.Common +-- Description : Declaration of types used by DecorationEx module, +-- and commonly used utility functions. +-- Copyright : (c) 2007 Andrea Rossato, 2009 Jan Vornberger, 2023 Ilya Portnov +-- License : BSD-style (see xmonad/LICENSE) +-- +-- Maintainer : portnov84@rambler.ru +-- Stability : unstable +-- Portability : unportable +-- +-- This module exposes a number of types which are used by other sub-modules +-- of "XMonad.Layout.DecorationEx" module. +----------------------------------------------------------------------------- + +module XMonad.Layout.DecorationEx.Common ( + -- * Common types + WindowDecoration (..) + , WindowCommand (..) + , DecorationWidget (..) + , WidgetPlace (..) + , WidgetLayout (..) + , HasWidgets (..) + , ClickHandler (..) + , ThemeAttributes (..) + , XPaintingContext + , BoxBorders (..) + , BorderColors + , ThemeStyleType (..) + , SimpleStyle (..) + , GenericTheme (..) + , ThemeEx + -- * Utilities + , widgetLayout + , windowStyleType + , genericWindowStyle + , themeEx + , borderColor + , shadowBorder + ) where + +import qualified Data.Map as M +import Data.Bits (testBit) + +import XMonad +import qualified XMonad.StackSet as W +import XMonad.Hooks.UrgencyHook +import qualified XMonad.Layout.Decoration as D + +-- | Information about decoration of one window +data WindowDecoration = WindowDecoration { + wdOrigWindow :: !Window -- ^ Original window (one being decorated) + , wdOrigWinRect :: !Rectangle -- ^ Rectangle of original window + , wdDecoWindow :: !(Maybe Window) -- ^ Decoration window, or Nothing if this window should not be decorated + , wdDecoRect :: !(Maybe Rectangle) -- ^ Rectangle for decoration window + , wdWidgets :: ![WidgetPlace] -- ^ Places for widgets + } + +-- | Type class for window commands (such as maximize or close window) +class (Read cmd, Show cmd) => WindowCommand cmd where + -- | Execute the command + executeWindowCommand :: cmd -> Window -> X Bool + + -- | Is the command currently in `checked' state. + -- For example, for 'sticky' command, check if the + -- window is currently sticky. + isCommandChecked :: cmd -> Window -> X Bool + +-- | Type class for decoration widgets +class (WindowCommand (WidgetCommand widget), Read widget, Show widget) + => DecorationWidget widget where + -- | Type of window commands which this type of widgets can execute + type WidgetCommand widget + + -- | Get window command which is associated with this widget. + widgetCommand :: widget -> Int -> WidgetCommand widget + + -- | Check if the widget is shrinkable, i.e. if it's width + -- can be reduced if there is not enough place in the decoration. + isShrinkable :: widget -> Bool + +-- | Layout of widgets +data WidgetLayout a = WidgetLayout { + wlLeft :: ![a] -- ^ Widgets that should be aligned to the left side of decoration + , wlCenter :: ![a] -- ^ Widgets that should be in the center of decoration + , wlRight :: ![a] -- ^ Widgets taht should be aligned to the right side of decoration + } + +-- | Data type describing where the decoration widget (e.g. window button) +-- should be placed. +-- All coordinates are relative to decoration rectangle. +data WidgetPlace = WidgetPlace { + wpTextYPosition :: !Position -- ^ Y position of text base line + -- (for widgets like window title or text-based buttons) + , wpRectangle :: !Rectangle -- ^ Rectangle where to place the widget + } + deriving (Show) + +-- | Generic data type which is used to +-- describe characteristics of rectangle borders. +data BoxBorders a = BoxBorders { + bxTop :: !a + , bxRight :: !a + , bxBottom :: !a + , bxLeft :: !a + } deriving (Eq, Read, Show) + +-- | Convinience data type describing colors of decoration rectangle borders. +type BorderColors = BoxBorders String + +-- | Data type describing look of window decoration +-- in particular state (active or inactive) +data SimpleStyle = SimpleStyle { + sBgColor :: !String -- ^ Decoration background color + , sTextColor :: !String -- ^ Text (foreground) color + , sTextBgColor :: !String -- ^ Text background color + , sDecoBorderWidth :: !Dimension -- ^ Width of border of decoration rectangle. Set to 0 to disable the border. + , sDecorationBorders :: !BorderColors -- ^ Colors of borders of decoration rectangle. + } + deriving (Show, Read) + +-- | Type class for themes, which claims that +-- the theme contains the list of widgets and their alignments. +class HasWidgets theme widget where + themeWidgets :: theme widget -> WidgetLayout widget + +-- | Type class for themes, which claims that +-- the theme can describe how the decoration should respond +-- to clicks on decoration itself (between widgets). +class ClickHandler theme widget where + -- | This is called when the user clicks on the decoration rectangle + -- (not on one of widgets). + onDecorationClick :: theme widget + -> Int -- ^ Mouse button number + -> Maybe (WidgetCommand widget) + + -- | Determine if it is possible to drag window by it's decoration + -- with mouse button. + isDraggingEnabled :: theme widget + -> Int -- ^ Mouse button number + -> Bool + +-- | Type class for themes, which claims that the theme +-- is responsible for determining looks of decoration. +class (Read theme, Show theme) => ThemeAttributes theme where + -- | Type which describes looks of decoration in one + -- of window states (active, inactive, urgent, etc). + type Style theme + + -- | Select style based on window state. + selectWindowStyle :: theme -> Window -> X (Style theme) + + -- | Define padding between decoration rectangle and widgets. + widgetsPadding :: theme -> BoxBorders Dimension + + -- | Initial background color of decoration rectangle. + -- When decoration widget is created, it is initially filled + -- with this color. + defaultBgColor :: theme -> String + + -- | Font name defined in the theme. + themeFontName :: theme -> String + +-- | Generic Theme data type. This is used +-- by @TextEngine@ and can be used by other relatively +-- simple decoration engines. +data GenericTheme style widget = GenericTheme { + exActive :: !style -- ^ Decoration style for active (focused) windows + , exInactive :: !style -- ^ Decoration style for inactive (unfocused) windows + , exUrgent :: !style -- ^ Decoration style for urgent windows + , exPadding :: !(BoxBorders Dimension) -- ^ Padding between decoration rectangle and widgets + , exFontName :: !String -- ^ Font name + , exOnDecoClick :: !(M.Map Int (WidgetCommand widget)) -- ^ Correspondence between mouse button number and window command. + , exDragWindowButtons :: ![Int] -- ^ For which mouse buttons dragging is enabled + , exWidgetsLeft :: ![widget] -- ^ Widgets that should appear at the left of decoration rectangle (listed left to right) + , exWidgetsCenter :: ![widget] -- ^ Widgets that should appear in the center of decoration rectangle (listed left to right) + , exWidgetsRight :: ![widget] -- ^ Widgets that should appear at the right of decoration rectangle (listed left to right) + } + +deriving instance (Show widget, Show (WidgetCommand widget), Show style) => Show (GenericTheme style widget) +deriving instance (Read widget, Read (WidgetCommand widget), Read style) => Read (GenericTheme style widget) + +-- | Convience type for themes used by @TextDecoration@. +type ThemeEx widget = GenericTheme SimpleStyle widget + +instance HasWidgets (GenericTheme style) widget where + themeWidgets theme = WidgetLayout (exWidgetsLeft theme) (exWidgetsCenter theme) (exWidgetsRight theme) + +-- | Supported states of windows (on which looks of decorations can depend). +data ThemeStyleType = ActiveWindow | UrgentWindow | InactiveWindow + deriving (Eq, Show, Read) + +-- | Utility function to convert WidgetLayout to plain list of widgets. +widgetLayout :: WidgetLayout widget -> [widget] +widgetLayout ws = wlLeft ws ++ wlCenter ws ++ wlRight ws + +-- | Painting context for decoration engines based on plain X11 calls. +type XPaintingContext = (Display, Pixmap, GC) + +instance (Show widget, Read widget, Read (WidgetCommand widget), Show (WidgetCommand widget)) + => ThemeAttributes (ThemeEx widget) where + type Style (ThemeEx widget) = SimpleStyle + selectWindowStyle theme w = genericWindowStyle w theme + defaultBgColor t = sBgColor $ exInactive t + widgetsPadding = exPadding + themeFontName = exFontName + +instance ClickHandler (GenericTheme SimpleStyle) widget where + onDecorationClick theme button = M.lookup button (exOnDecoClick theme) + isDraggingEnabled theme button = button `elem` exDragWindowButtons theme + +-- | Generic utility function to select style from @GenericTheme@ +-- based on current state of the window. +genericWindowStyle :: Window -> GenericTheme style widget -> X style +genericWindowStyle win theme = do + styleType <- windowStyleType win + return $ case styleType of + ActiveWindow -> exActive theme + InactiveWindow -> exInactive theme + UrgentWindow -> exUrgent theme + +-- | Detect type of style to be used from current state of the window. +windowStyleType :: Window -> X ThemeStyleType +windowStyleType win = do + mbFocused <- W.peek <$> gets windowset + isWmStateUrgent <- (win `elem`) <$> readUrgents + isUrgencyBitSet <- withDisplay $ \dpy -> do + hints <- io $ getWMHints dpy win + return $ wmh_flags hints `testBit` urgencyHintBit + if isWmStateUrgent || isUrgencyBitSet + then return UrgentWindow + else return $ + case mbFocused of + Nothing -> InactiveWindow + Just focused + | focused == win -> ActiveWindow + | otherwise -> InactiveWindow + +-- | Convert Theme type from "XMonad.Layout.Decoration" to +-- theme type used by "XMonad.Layout.DecorationEx.TextEngine". +themeEx :: Default (WidgetCommand widget) => D.Theme -> ThemeEx widget +themeEx t = + GenericTheme { + exActive = SimpleStyle (D.activeColor t) (D.activeTextColor t) (D.activeColor t) (D.activeBorderWidth t) (borderColor $ D.activeColor t) + , exInactive = SimpleStyle (D.inactiveColor t) (D.inactiveTextColor t) (D.inactiveColor t) (D.inactiveBorderWidth t) (borderColor $ D.inactiveColor t) + , exUrgent = SimpleStyle (D.urgentColor t) (D.urgentTextColor t) (D.urgentColor t) (D.urgentBorderWidth t) (borderColor $ D.urgentColor t) + , exPadding = BoxBorders 0 4 0 4 + , exFontName = D.fontName t + , exOnDecoClick = M.fromList [(1, def)] + , exDragWindowButtons = [1] + , exWidgetsLeft = [] + , exWidgetsCenter = [] + , exWidgetsRight = [] + } + +instance Default (WidgetCommand widget) => Default (ThemeEx widget) where + def = themeEx (def :: D.Theme) + +borderColor :: String -> BorderColors +borderColor c = BoxBorders c c c c + +shadowBorder :: String -> String -> BorderColors +shadowBorder highlight shadow = BoxBorders highlight shadow shadow highlight + diff --git a/XMonad/Layout/DecorationEx/DwmGeometry.hs b/XMonad/Layout/DecorationEx/DwmGeometry.hs new file mode 100644 index 0000000000..eedd968f0e --- /dev/null +++ b/XMonad/Layout/DecorationEx/DwmGeometry.hs @@ -0,0 +1,106 @@ +{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE RecordWildCards #-} + +----------------------------------------------------------------------------- +-- | +-- Module : XMonad.Layout.DecorationEx.DwmGeometry +-- Description : DWM-style window decoration geometry +-- Copyright : (c) 2007 Andrea Rossato, 2023 Ilya Portnov +-- License : BSD-style (see xmonad/LICENSE) +-- +-- Maintainer : portnov84@rambler.ru +-- Stability : unstable +-- Portability : unportable +-- +-- This defines window decorations which are shown as a bar of fixed width +-- on top of window. +----------------------------------------------------------------------------- + +module XMonad.Layout.DecorationEx.DwmGeometry ( + -- * Usage: + -- $usage + DwmGeometry (..), + dwmStyleDeco, dwmStyleDecoEx + ) where + +import XMonad +import XMonad.Prelude +import qualified XMonad.StackSet as W +import XMonad.Layout.LayoutModifier +import qualified XMonad.Layout.Decoration as D + +import XMonad.Layout.DecorationEx.LayoutModifier +import XMonad.Layout.DecorationEx.Common +import XMonad.Layout.DecorationEx.Geometry +import XMonad.Layout.DecorationEx.Widgets +import XMonad.Layout.DecorationEx.TextEngine + +-- $usage +-- You can use this module with the following in your +-- @xmonad.hs@: +-- +-- > import XMonad.Layout.DecorationEx.DwmStyle +-- Then edit your @layoutHook@ by adding the DwmStyle decoration to +-- your layout: +-- +-- > myL = dwmStyleDeco shrinkText (layoutHook def) +-- > main = xmonad def { layoutHook = myL } +-- +-- For more detailed instructions on editing the layoutHook see: +-- +-- "XMonad.Doc.Extending#Editing_the_layout_hook" + +-- | Decoration geometry data type +data DwmGeometry a = DwmGeometry { + dwmShowForFocused :: !Bool -- ^ Whether to show decorations on focused windows + , dwmHorizontalPosition :: !Rational -- ^ Horizontal position of decoration rectangle. + -- 0 means place it at left corner, 1 - place it at + -- right corner, @1%2@ - place it at center. + , dwmDecoHeight :: !Dimension -- ^ Height of decoration rectangle + , dwmDecoWidth :: !Dimension -- ^ Width of decoration rectangle + } + deriving (Show, Read) + +instance Default (DwmGeometry a) where + def = DwmGeometry False 1 20 200 + +instance DecorationGeometry DwmGeometry Window where + describeGeometry _ = "DwmStyle" + + pureDecoration (DwmGeometry {..}) _ stack _ (w, Rectangle x y windowWidth _) = + let width = min windowWidth dwmDecoWidth + halfWidth = width `div` 2 + minCenterX = x + fi halfWidth + maxCenterX = x + fi windowWidth - fromIntegral halfWidth + centerX = round ((1 - dwmHorizontalPosition)*fi minCenterX + dwmHorizontalPosition*fi maxCenterX) :: Position + decoX = centerX - fi halfWidth + focusedWindow = W.focus stack + isFocused = focusedWindow == w + in if (not dwmShowForFocused && isFocused) || not (D.isInStack stack w) + then Nothing + else Just $ Rectangle decoX y width dwmDecoHeight + + shrinkWindow _ _ windowRect = windowRect + +-- | Add a decoration to window layout. Widgets are indicated with text fragments using TextDecoration; +-- decoration placement can be adjusted. +dwmStyleDecoEx :: D.Shrinker shrinker + => shrinker -- ^ Strings shrinker, for example @shrinkText@ + -> DwmGeometry Window + -> ThemeEx StandardWidget -- ^ Decoration theme (font, colors, widgets, etc) + -> l Window -- ^ Layout to be decorated + -> ModifiedLayout (DecorationEx TextDecoration StandardWidget DwmGeometry shrinker) l Window +dwmStyleDecoEx shrinker geom theme = decorationEx shrinker theme TextDecoration geom + +-- | Add a decoration to window layout. Widgets are indicated with text fragments using TextDecoration; +-- decoration placement is similar to DWM. +dwmStyleDeco :: D.Shrinker shrinker + => shrinker -- ^ Strings shrinker, for example @shrinkText@ + -> ThemeEx StandardWidget -- ^ Decoration theme (font, colors, widgets, etc) + -> l Window -- ^ Layout to be decorated + -> ModifiedLayout (DecorationEx TextDecoration StandardWidget DwmGeometry shrinker) l Window +dwmStyleDeco shrinker = dwmStyleDecoEx shrinker def + diff --git a/XMonad/Layout/DecorationEx/Engine.hs b/XMonad/Layout/DecorationEx/Engine.hs new file mode 100644 index 0000000000..9810d45ad4 --- /dev/null +++ b/XMonad/Layout/DecorationEx/Engine.hs @@ -0,0 +1,511 @@ +{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, ViewPatterns #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE DefaultSignatures #-} + +----------------------------------------------------------------------------- +-- | +-- Module : XMonad.Layout.DecorationEx.Engine +-- Description : Type class and its default implementation for window decoration engines. +-- Copyright : (c) 2007 Andrea Rossato, 2009 Jan Vornberger, 2023 Ilya Portnov +-- License : BSD-style (see xmonad/LICENSE) +-- +-- Maintainer : portnov84@rambler.ru +-- Stability : unstable +-- Portability : unportable +-- +-- This module defines @DecorationEngine@ type class, and default implementation for it. +----------------------------------------------------------------------------- + +module XMonad.Layout.DecorationEx.Engine ( + -- * DecorationEngine class + DecorationEngine (..), + -- * Auxiliary data types + DrawData (..), + DecorationLayoutState (..), + -- * Re-exports from X.L.Decoration + Shrinker (..), shrinkText, + -- * Utility functions + mkDrawData, + paintDecorationSimple + ) where + +import Control.Monad +import Data.Kind +import Foreign.C.Types (CInt) + +import XMonad +import XMonad.Prelude +import qualified XMonad.StackSet as W +import XMonad.Layout.Decoration (Shrinker (..), shrinkWhile, shrinkText) +import XMonad.Layout.DraggingVisualizer (DraggingVisualizerMsg (..)) +import XMonad.Layout.DecorationAddons (handleScreenCrossing) +import XMonad.Util.Font +import XMonad.Util.NamedWindows (getName) + +import XMonad.Layout.DecorationEx.Common + +-- | Auxiliary type for data which are passed from +-- decoration layout modifier to decoration engine. +data DrawData engine widget = DrawData { + ddEngineState :: !(DecorationEngineState engine) -- ^ Decoration engine state + , ddStyle :: !(Style (Theme engine widget)) -- ^ Graphics style of the decoration. This defines colors, fonts etc + -- which are to be used for this particular window in it's current state. + , ddOrigWindow :: !Window -- ^ Original window to be decorated + , ddWindowTitle :: !String -- ^ Original window title (not shrinked yet) + , ddDecoRect :: !Rectangle -- ^ Decoration rectangle + , ddWidgets :: !(WidgetLayout widget) -- ^ Widgets to be placed on decoration + , ddWidgetPlaces :: !(WidgetLayout WidgetPlace) -- ^ Places where widgets must be shown + } + +-- | State of decoration engine +data DecorationLayoutState engine = DecorationLayoutState { + dsStyleState :: !(DecorationEngineState engine) -- ^ Engine-specific state + , dsDecorations :: ![WindowDecoration] -- ^ Mapping between decoration windows and original windows + } + +-- | Decoration engines type class. +-- Decoration engine is responsible for drawing something inside decoration rectangle. +-- It is also responsible for handling X11 events (such as clicks) which happen +-- within decoration rectangle. +-- Decoration rectangles are defined by DecorationGeometry implementation. +class (Read (engine widget a), Show (engine widget a), + Eq a, + DecorationWidget widget, + HasWidgets (Theme engine) widget, + ClickHandler (Theme engine) widget, + ThemeAttributes (Theme engine widget)) + => DecorationEngine engine widget a where + + -- | Type of themes used by decoration engine. + -- This type must be parameterized over a widget type, + -- because a theme will contain a list of widgets. + type Theme engine :: Type -> Type + + -- | Type of data used by engine as a context during painting; + -- for plain X11-based implementation this is Display, Pixmap + -- and GC. + type DecorationPaintingContext engine + + -- | Type of state used by the decoration engine. + -- This can contain some resources that should be initialized + -- and released at time, such as X11 fonts. + type DecorationEngineState engine + + -- | Give a name to decoration engine. + describeEngine :: engine widget a -> String + + -- | Initialize state of the engine. + initializeState :: engine widget a -- ^ Decoration engine instance + -> geom a -- ^ Decoration geometry instance + -> Theme engine widget -- ^ Theme to be used + -> X (DecorationEngineState engine) + + -- | Release resources held in engine state. + releaseStateResources :: engine widget a -- ^ Decoration engine instance + -> DecorationEngineState engine -- ^ Engine state + -> X () + + -- | Calculate place which will be occupied by one widget. + -- NB: X coordinate of the returned rectangle will be ignored, because + -- the rectangle will be moved to the right or to the left for proper alignment + -- of widgets. + calcWidgetPlace :: engine widget a -- ^ Decoration engine instance + -> DrawData engine widget -- ^ Information about window and decoration + -> widget -- ^ Widget to be placed + -> X WidgetPlace + + -- | Place widgets along the decoration bar. + placeWidgets :: Shrinker shrinker + => engine widget a -- ^ Decoration engine instance + -> Theme engine widget -- ^ Theme to be used + -> shrinker -- ^ Strings shrinker + -> DecorationEngineState engine -- ^ Current state of the engine + -> Rectangle -- ^ Decoration rectangle + -> Window -- ^ Original window to be decorated + -> WidgetLayout widget -- ^ Widgets layout + -> X (WidgetLayout WidgetPlace) + placeWidgets engine theme _ decoStyle decoRect window wlayout = do + let leftWidgets = wlLeft wlayout + rightWidgets = wlRight wlayout + centerWidgets = wlCenter wlayout + + dd <- mkDrawData engine theme decoStyle window decoRect + let paddedDecoRect = pad (widgetsPadding theme) (ddDecoRect dd) + paddedDd = dd {ddDecoRect = paddedDecoRect} + rightRects <- alignRight engine paddedDd rightWidgets + leftRects <- alignLeft engine paddedDd leftWidgets + let wantedLeftWidgetsWidth = sum $ map (rect_width . wpRectangle) leftRects + wantedRightWidgetsWidth = sum $ map (rect_width . wpRectangle) rightRects + hasShrinkableOnLeft = any isShrinkable leftWidgets + hasShrinkableOnRight = any isShrinkable rightWidgets + decoWidth = rect_width decoRect + (leftWidgetsWidth, rightWidgetsWidth) + | hasShrinkableOnLeft = + (min (decoWidth - wantedRightWidgetsWidth) wantedLeftWidgetsWidth, + wantedRightWidgetsWidth) + | hasShrinkableOnRight = + (wantedLeftWidgetsWidth, + min (decoWidth - wantedLeftWidgetsWidth) wantedRightWidgetsWidth) + | otherwise = (wantedLeftWidgetsWidth, wantedRightWidgetsWidth) + ddForCenter = paddedDd {ddDecoRect = padCenter leftWidgetsWidth rightWidgetsWidth paddedDecoRect} + centerRects <- alignCenter engine ddForCenter centerWidgets + let shrinkedLeftRects = packLeft (rect_x paddedDecoRect) $ shrinkPlaces leftWidgetsWidth $ zip leftRects (map isShrinkable leftWidgets) + shrinkedRightRects = packRight (rect_width paddedDecoRect) $ shrinkPlaces rightWidgetsWidth $ zip rightRects (map isShrinkable rightWidgets) + return $ WidgetLayout shrinkedLeftRects centerRects shrinkedRightRects + where + shrinkPlaces targetWidth ps = + let nShrinkable = length (filter snd ps) + totalUnshrinkedWidth = sum $ map (rect_width . wpRectangle . fst) $ filter (not . snd) ps + shrinkedWidth = (targetWidth - totalUnshrinkedWidth) `div` fi nShrinkable + + resetX place = place {wpRectangle = (wpRectangle place) {rect_x = 0}} + + adjust (place, True) = resetX $ place {wpRectangle = (wpRectangle place) {rect_width = shrinkedWidth}} + adjust (place, False) = resetX place + in map adjust ps + + pad p (Rectangle _ _ w h) = + Rectangle (fi (bxLeft p)) (fi (bxTop p)) + (w - bxLeft p - bxRight p) + (h - bxTop p - bxBottom p) + + padCenter left right (Rectangle x y w h) = + Rectangle (x + fi left) y + (w - left - right) h + + -- | Shrink window title so that it would fit in decoration. + getShrinkedWindowName :: Shrinker shrinker + => engine widget a -- ^ Decoration engine instance + -> shrinker -- ^ Strings shrinker + -> DecorationEngineState engine -- ^ State of decoration engine + -> String -- ^ Original window title + -> Dimension -- ^ Width of rectangle in which the title should fit + -> Dimension -- ^ Height of rectangle in which the title should fit + -> X String + + default getShrinkedWindowName :: (Shrinker shrinker, DecorationEngineState engine ~ XMonadFont) + => engine widget a -> shrinker -> DecorationEngineState engine -> String -> Dimension -> Dimension -> X String + getShrinkedWindowName _ shrinker font name wh _ = do + let s = shrinkIt shrinker + dpy <- asks display + shrinkWhile s (\n -> do size <- io $ textWidthXMF dpy font n + return $ size > fromIntegral wh) name + + -- | Mask of X11 events on which the decoration engine should do something. + -- @exposureMask@ should be included here so that decoration engine could + -- repaint decorations when they are shown on screen. + -- @buttonPressMask@ should be included so that decoration engine could + -- response to mouse clicks. + -- Other events can be added to custom implementations of DecorationEngine. + decorationXEventMask :: engine widget a -> EventMask + decorationXEventMask _ = exposureMask .|. buttonPressMask + + -- | List of X11 window property atoms of original (client) windows, + -- change of which should trigger repainting of decoration. + -- For example, if @WM_NAME@ changes it means that we have to redraw + -- window title. + propsToRepaintDecoration :: engine widget a -> X [Atom] + propsToRepaintDecoration _ = + mapM getAtom ["WM_NAME", "_NET_WM_NAME", "WM_STATE", "WM_HINTS"] + + -- | Generic event handler, which recieves X11 events on decoration + -- window. + -- Default implementation handles mouse clicks and drags. + decorationEventHookEx :: Shrinker shrinker + => engine widget a + -> Theme engine widget + -> DecorationLayoutState engine + -> shrinker + -> Event + -> X () + decorationEventHookEx = handleMouseFocusDrag + + -- | Event handler for clicks on decoration window. + -- This is called from default implementation of "decorationEventHookEx". + -- This should return True, if the click was handled (something happened + -- because of that click). If this returns False, the click can be considered + -- as a beginning of mouse drag. + handleDecorationClick :: engine widget a -- ^ Decoration engine instance + -> Theme engine widget -- ^ Decoration theme + -> Rectangle -- ^ Decoration rectangle + -> [Rectangle] -- ^ Rectangles where widgets are placed + -> Window -- ^ Original (client) window + -> Int -- ^ Mouse click X coordinate + -> Int -- ^ Mouse click Y coordinate + -> Int -- ^ Mouse button number + -> X Bool + handleDecorationClick = decorationHandler + + -- | Event handler which is called during mouse dragging. + -- This is called from default implementation of "decorationEventHookEx". + decorationWhileDraggingHook :: engine widget a -- ^ Decoration engine instance + -> CInt -- ^ Event X coordinate + -> CInt -- ^ Event Y coordinate + -> (Window, Rectangle) -- ^ Original window and it's rectangle + -> Position -- ^ X coordinate of new pointer position during dragging + -> Position -- ^ Y coordinate of new pointer position during dragging + -> X () + decorationWhileDraggingHook _ = handleDraggingInProgress + + -- | This hoook is called after a window has been dragged using the decoration. + -- This is called from default implementation of "decorationEventHookEx". + decorationAfterDraggingHook :: engine widget a -- ^ Decoration engine instance + -> (Window, Rectangle) -- ^ Original window and its rectangle + -> Window -- ^ Decoration window + -> X () + decorationAfterDraggingHook _ds (w, _r) decoWin = do + focus w + hasCrossed <- handleScreenCrossing w decoWin + unless hasCrossed $ do + sendMessage DraggingStopped + performWindowSwitching w + + -- | Draw everything required on the decoration window. + -- This method should draw background (flat or gradient or whatever), + -- borders, and call @paintWidget@ method to draw window widgets + -- (buttons and title). + paintDecoration :: Shrinker shrinker + => engine widget a -- ^ Decoration engine instance + -> a -- ^ Decoration window + -> Dimension -- ^ Decoration window width + -> Dimension -- ^ Decoration window height + -> shrinker -- ^ Strings shrinker instance + -> DrawData engine widget -- ^ Details about what to draw + -> Bool -- ^ True when this method is called during Expose event + -> X () + + -- | Paint one widget on the decoration window. + paintWidget :: Shrinker shrinker + => engine widget a -- ^ Decoration engine instance + -> DecorationPaintingContext engine -- ^ Decoration painting context + -> WidgetPlace -- ^ Place (rectangle) where the widget should be drawn + -> shrinker -- ^ Strings shrinker instance + -> DrawData engine widget -- ^ Details about window decoration + -> widget -- ^ Widget to be drawn + -> Bool -- ^ True when this method is called during Expose event + -> X () + +handleDraggingInProgress :: CInt -> CInt -> (Window, Rectangle) -> Position -> Position -> X () +handleDraggingInProgress ex ey (mainw, r) x y = do + let rect = Rectangle (x - (fi ex - rect_x r)) + (y - (fi ey - rect_y r)) + (rect_width r) + (rect_height r) + sendMessage $ DraggingWindow mainw rect + +performWindowSwitching :: Window -> X () +performWindowSwitching win = + withDisplay $ \d -> do + root <- asks theRoot + (_, _, selWin, _, _, _, _, _) <- io $ queryPointer d root + ws <- gets windowset + let allWindows = W.index ws + -- do a little double check to be sure + when ((win `elem` allWindows) && (selWin `elem` allWindows)) $ do + let allWindowsSwitched = map (switchEntries win selWin) allWindows + let (ls, notEmpty -> t :| rs) = break (win ==) allWindowsSwitched + let newStack = W.Stack t (reverse ls) rs + windows $ W.modify' $ const newStack + where + switchEntries a b x + | x == a = b + | x == b = a + | otherwise = x + +ignoreX :: WidgetPlace -> WidgetPlace +ignoreX place = place {wpRectangle = (wpRectangle place) {rect_x = 0}} + +alignLeft :: forall engine widget a. DecorationEngine engine widget a => engine widget a -> DrawData engine widget -> [widget] -> X [WidgetPlace] +alignLeft engine dd widgets = do + places <- mapM (calcWidgetPlace engine dd) widgets + return $ packLeft (rect_x $ ddDecoRect dd) $ map ignoreX places + +packLeft :: Position -> [WidgetPlace] -> [WidgetPlace] +packLeft _ [] = [] +packLeft x0 (place : places) = + let rect = wpRectangle place + x' = x0 + rect_x rect + rect' = rect {rect_x = x'} + place' = place {wpRectangle = rect'} + in place' : packLeft (x' + fi (rect_width rect)) places + +alignRight :: forall engine widget a. DecorationEngine engine widget a => engine widget a -> DrawData engine widget -> [widget] -> X [WidgetPlace] +alignRight engine dd widgets = do + places <- mapM (calcWidgetPlace engine dd) widgets + return $ packRight (rect_width $ ddDecoRect dd) $ map ignoreX places + +packRight :: Dimension -> [WidgetPlace] -> [WidgetPlace] +packRight x0 places = reverse $ go x0 places + where + go _ [] = [] + go x (place : rest) = + let rect = wpRectangle place + x' = x - rect_width rect + rect' = rect {rect_x = fi x'} + place' = place {wpRectangle = rect'} + in place' : go x' rest + +alignCenter :: forall engine widget a. DecorationEngine engine widget a => engine widget a -> DrawData engine widget -> [widget] -> X [WidgetPlace] +alignCenter engine dd widgets = do + places <- alignLeft engine dd widgets + let totalWidth = sum $ map (rect_width . wpRectangle) places + availableWidth = fi (rect_width (ddDecoRect dd)) :: Position + x0 = max 0 $ (availableWidth - fi totalWidth) `div` 2 + places' = map (shift x0) places + return $ pack (fi availableWidth) places' + where + shift x0 place = + let rect = wpRectangle place + rect' = rect {rect_x = rect_x rect + fi x0} + in place {wpRectangle = rect'} + + pack _ [] = [] + pack available (place : places) = + let rect = wpRectangle place + placeWidth = rect_width rect + widthToUse = min available placeWidth + remaining = available - widthToUse + rect' = rect {rect_width = widthToUse} + place' = place {wpRectangle = rect'} + in place' : pack remaining places + +-- | Build an instance of 'DrawData' type. +mkDrawData :: (DecorationEngine engine widget a, ThemeAttributes (Theme engine widget), HasWidgets (Theme engine) widget) + => engine widget a + -> Theme engine widget -- ^ Decoration theme + -> DecorationEngineState engine -- ^ State of decoration engine + -> Window -- ^ Original window (to be decorated) + -> Rectangle -- ^ Decoration rectangle + -> X (DrawData engine widget) +mkDrawData _ theme decoState origWindow decoRect = do + -- xmonad-contrib #809 + -- qutebrowser will happily shovel a 389K multiline string into @_NET_WM_NAME@ + -- and the 'defaultShrinker' (a) doesn't handle multiline strings well (b) is + -- quadratic due to using 'init' + name <- fmap (take 2048 . takeWhile (/= '\n') . show) (getName origWindow) + style <- selectWindowStyle theme origWindow + return $ DrawData { + ddEngineState = decoState, + ddStyle = style, + ddOrigWindow = origWindow, + ddWindowTitle = name, + ddDecoRect = decoRect, + ddWidgets = themeWidgets theme, + ddWidgetPlaces = WidgetLayout [] [] [] + } + +-- | Mouse focus and mouse drag are handled by the same function, this +-- way we can start dragging unfocused windows too. +handleMouseFocusDrag :: (DecorationEngine engine widget a, Shrinker shrinker) => engine widget a -> Theme engine widget -> DecorationLayoutState engine -> shrinker -> Event -> X () +handleMouseFocusDrag ds theme (DecorationLayoutState {dsDecorations}) _ (ButtonEvent {ev_window, ev_x_root, ev_y_root, ev_event_type, ev_button}) + | ev_event_type == buttonPress + , Just (WindowDecoration {..}) <- findDecoDataByDecoWindow ev_window dsDecorations = do + let decoRect@(Rectangle dx dy _ _) = fromJust wdDecoRect + x = fi $ ev_x_root - fi dx + y = fi $ ev_y_root - fi dy + button = fi ev_button + dealtWith <- handleDecorationClick ds theme decoRect (map wpRectangle wdWidgets) wdOrigWindow x y button + unless dealtWith $ when (isDraggingEnabled theme button) $ + mouseDrag (\dragX dragY -> focus wdOrigWindow >> decorationWhileDraggingHook ds ev_x_root ev_y_root (wdOrigWindow, wdOrigWinRect) dragX dragY) + (decorationAfterDraggingHook ds (wdOrigWindow, wdOrigWinRect) ev_window) +handleMouseFocusDrag _ _ _ _ _ = return () + +-- | Given a window and the state, if a matching decoration is in the +-- state return it with its ('Maybe') 'Rectangle'. +findDecoDataByDecoWindow :: Window -> [WindowDecoration] -> Maybe WindowDecoration +findDecoDataByDecoWindow decoWin = find (\dd -> wdDecoWindow dd == Just decoWin) + +decorationHandler :: forall engine widget a. + (DecorationEngine engine widget a, + ClickHandler (Theme engine) widget) + => engine widget a + -> Theme engine widget + -> Rectangle + -> [Rectangle] + -> Window + -> Int + -> Int + -> Int + -> X Bool +decorationHandler _ theme _ widgetPlaces window x y button = do + widgetDone <- go $ zip (widgetLayout $ themeWidgets theme) widgetPlaces + if widgetDone + then return True + else case onDecorationClick theme button of + Just cmd -> do + executeWindowCommand cmd window + Nothing -> return False + where + go :: [(widget, Rectangle)] -> X Bool + go [] = return False + go ((w, rect) : rest) = do + if pointWithin (fi x) (fi y) rect + then do + executeWindowCommand (widgetCommand w button) window + else go rest + +-- | Simple implementation of @paintDecoration@ method. +-- This is used by @TextEngine@ and can be re-used by other decoration +-- engines. +paintDecorationSimple :: forall engine shrinker widget. + (DecorationEngine engine widget Window, + DecorationPaintingContext engine ~ XPaintingContext, + Shrinker shrinker, + Style (Theme engine widget) ~ SimpleStyle) + => engine widget Window + -> Window + -> Dimension + -> Dimension + -> shrinker + -> DrawData engine widget + -> Bool + -> X () +paintDecorationSimple deco win windowWidth windowHeight shrinker dd isExpose = do + dpy <- asks display + let widgets = widgetLayout $ ddWidgets dd + style = ddStyle dd + pixmap <- io $ createPixmap dpy win windowWidth windowHeight (defaultDepthOfScreen $ defaultScreenOfDisplay dpy) + gc <- io $ createGC dpy pixmap + -- draw + io $ setGraphicsExposures dpy gc False + bgColor <- stringToPixel dpy (sBgColor style) + -- we start with the border + let borderWidth = sDecoBorderWidth style + borderColors = sDecorationBorders style + when (borderWidth > 0) $ do + drawLineWith dpy pixmap gc 0 0 windowWidth borderWidth (bxTop borderColors) + drawLineWith dpy pixmap gc 0 0 borderWidth windowHeight (bxLeft borderColors) + drawLineWith dpy pixmap gc 0 (fi (windowHeight - borderWidth)) windowWidth borderWidth (bxBottom borderColors) + drawLineWith dpy pixmap gc (fi (windowWidth - borderWidth)) 0 borderWidth windowHeight (bxRight borderColors) + + -- and now again + io $ setForeground dpy gc bgColor + io $ fillRectangle dpy pixmap gc (fi borderWidth) (fi borderWidth) (windowWidth - (borderWidth * 2)) (windowHeight - (borderWidth * 2)) + + -- paint strings + forM_ (zip widgets $ widgetLayout $ ddWidgetPlaces dd) $ \(widget, place) -> + paintWidget deco (dpy, pixmap, gc) place shrinker dd widget isExpose + + -- debug + -- black <- stringToPixel dpy "black" + -- io $ setForeground dpy gc black + -- forM_ (ddWidgetPlaces dd) $ \(WidgetPlace {wpRectangle = Rectangle x y w h}) -> + -- io $ drawRectangle dpy pixmap gc x y w h + + -- copy the pixmap over the window + io $ copyArea dpy pixmap win gc 0 0 windowWidth windowHeight 0 0 + -- free the pixmap and GC + io $ freePixmap dpy pixmap + io $ freeGC dpy gc + where + drawLineWith dpy pixmap gc x y w h colorName = do + color <- stringToPixel dpy colorName + io $ setForeground dpy gc color + io $ fillRectangle dpy pixmap gc x y w h + diff --git a/XMonad/Layout/DecorationEx/Geometry.hs b/XMonad/Layout/DecorationEx/Geometry.hs new file mode 100644 index 0000000000..ea057036e1 --- /dev/null +++ b/XMonad/Layout/DecorationEx/Geometry.hs @@ -0,0 +1,87 @@ +{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE UndecidableInstances #-} + +----------------------------------------------------------------------------- +-- | +-- Module : XMonad.Layout.DecorationEx.Geometry +-- Description : Type class which is responsible for defining the placement +-- of window decorations +-- Copyright : (c) 2007 Andrea Rossato, 2009 Jan Vornberger, 2023 Ilya Portnov +-- License : BSD-style (see xmonad/LICENSE) +-- +-- Maintainer : portnov84@rambler.ru +-- Stability : unstable +-- Portability : unportable +-- +-- This module defines @DecorationGeometry@ type class, and default implementation for it. +----------------------------------------------------------------------------- + +module XMonad.Layout.DecorationEx.Geometry ( + DecorationGeometry (..), + DefaultGeometry (..) + ) where + +import XMonad +import XMonad.Prelude +import qualified XMonad.StackSet as W +import qualified XMonad.Layout.Decoration as D + +-- | Decoration geometry class. +-- Decoration geometry is responsible for placement of window decorations: whether +-- they should be on the top of the window or on the bottom, should they go for +-- full window width or only be of certain width, etc. +-- This does not know what will be drawn inside decorations. +class (Read (geom a), Show (geom a), + Eq a) + => DecorationGeometry geom a where + + -- | Give a name to decoration geometry implementation. + describeGeometry :: geom a -> String + + -- | Reduce original window size to make space for decoration, if necessary. + shrinkWindow :: geom a -> Rectangle -> Rectangle -> Rectangle + shrinkWindow _ (Rectangle _ _ _ dh) (Rectangle x y w h) = Rectangle x (y + fi dh) w (h - dh) + + -- | The pure version of the main method, 'decorate'. + -- The method should return a rectangle where to place window decoration, + -- or 'Nothing' if this window is not to be decorated. + pureDecoration :: geom a -- ^ Decoration geometry instance + -> Rectangle -- ^ Screen rectangle + -> W.Stack a -- ^ Current stack of windows being displayed + -> [(a,Rectangle)] -- ^ Set of all windows with their corresponding rectangle + -> (a,Rectangle) -- ^ Window being decorated and its rectangle + -> Maybe Rectangle + + -- | The method should return a rectangle where to place window decoration, + -- or 'Nothing' if this window is not to be decorated. + decorateWindow :: geom a -- ^ Decoration geometry instance + -> Rectangle -- ^ Screen rectangle + -> W.Stack a -- ^ Current stack of windows being displayed + -> [(a, Rectangle)] -- ^ Set of all windows with their corresponding rectangle + -> (a, Rectangle) -- ^ Window being decorated and its rectangle + -> X (Maybe Rectangle) + decorateWindow geom r s wrs wr = return $ pureDecoration geom r s wrs wr + +-- | Data type for default implementation of 'DecorationGeometry'. +-- This defines simple decorations: a horizontal bar at the top of each window, +-- running for full width of the window. +newtype DefaultGeometry a = DefaultGeometry { + gDecorationHeight :: Dimension + } + deriving (Read, Show) + +instance Eq a => DecorationGeometry DefaultGeometry a where + describeGeometry _ = "Default" + + pureDecoration (DefaultGeometry {..}) _ s _ (w, Rectangle x y windowWidth windowHeight) = + if D.isInStack s w && (gDecorationHeight < windowHeight) + then Just $ Rectangle x y windowWidth gDecorationHeight + else Nothing + +instance Default (DefaultGeometry a) where + def = DefaultGeometry 20 + diff --git a/XMonad/Layout/DecorationEx/LayoutModifier.hs b/XMonad/Layout/DecorationEx/LayoutModifier.hs new file mode 100644 index 0000000000..b603b8ca05 --- /dev/null +++ b/XMonad/Layout/DecorationEx/LayoutModifier.hs @@ -0,0 +1,322 @@ +{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE UndecidableInstances #-} + +----------------------------------------------------------------------------- +-- | +-- Module : XMonad.Layout.DecorationEx.LayoutModifier +-- Description : Layout modifier which adds decorations to windows. +-- Copyright : (c) 2007 Andrea Rossato, 2009 Jan Vornberger, 2023 Ilya Portnov +-- License : BSD-style (see xmonad/LICENSE) +-- +-- Maintainer : portnov84@rambler.ru +-- Stability : unstable +-- Portability : unportable +-- +-- Layout modifier, which is responsible for creation of decoration rectangles +-- (windows), updating and removing them when needed. It is parameterized by +-- @DecorationGeometry@, which says where decorations should be placed, and by +-- @DecorationEngine@, which says how decorations should look. +----------------------------------------------------------------------------- + +module XMonad.Layout.DecorationEx.LayoutModifier ( + -- * Usage + -- + -- $usage + decorationEx, + DecorationEx + ) where + +import XMonad +import XMonad.Prelude +import qualified XMonad.StackSet as W +import XMonad.Layout.LayoutModifier +import XMonad.Layout.WindowArranger (diff, listFromList) +import XMonad.Util.Invisible +import XMonad.Util.XUtils hiding (paintTextAndIcons) + +import XMonad.Layout.DecorationEx.Common +import XMonad.Layout.DecorationEx.Engine +import XMonad.Layout.DecorationEx.Geometry + +-- $usage +-- +-- This module exports @decorationEx@ function, which is a generic function for +-- adding decorations to your layouts. It can be used to use different +-- decoration geometries and engines in any combination. +-- For most used combinations, there are convenience functions in +-- "XMonad.Layout.DecorationEx.TextEngine", "XMonad.Layout.DecorationEx.TabbedGeometry", +-- and "XMonad.Layout.DecorationEx.DwmGeometry". +-- +-- You can use this module with the following in your +-- @xmonad.hs@: +-- +-- > import XMonad.Layout.DecorationEx.LayoutModifier +-- Then edit your @layoutHook@ by adding the DwmStyle decoration to +-- your layout: +-- +-- > myL = decorationEx shrinkText myTheme myEngine myGeometry (layoutHook def) +-- > where +-- > myGeometry = DefaultGeometry -- or another geometry type +-- > myEngine = TextDecoration -- or another decoration engine +-- > myTheme = GenericTheme {...} -- theme type should correspond to selected engine type +-- > +-- > main = xmonad def { layoutHook = myL } +-- +-- For more detailed instructions on editing the layoutHook see: +-- +-- "XMonad.Doc.Extending#Editing_the_layout_hook" + + +-- | The 'DecorationEx' 'LayoutModifier'. This data type is an instance +-- of the 'LayoutModifier' class. This data type will be passed, +-- together with a layout, to the 'ModifiedLayout' type constructor +-- to modify the layout by adding decorations according to a +-- 'DecorationEngine'. +data DecorationEx engine widget geom shrinker a = + DecorationEx (Invisible Maybe (DecorationLayoutState engine)) shrinker (Theme engine widget) (engine widget a) (geom a) + +deriving instance (Show (Theme engine widget), Show shrinker, Show (engine widget a), Show (geom a)) => Show (DecorationEx engine widget geom shrinker a) +deriving instance (Read (Theme engine widget), Read shrinker, Read (engine widget a), Read (geom a)) => Read (DecorationEx engine widget geom shrinker a) + +-- | The long 'LayoutModifier' instance for the 'DecorationEx' type. +-- +-- In 'redoLayout' we check the state: if there is no state we +-- initialize it. +-- +-- The state is @diff@ed against the list of windows produced by the +-- underlying layout: removed windows get deleted and new ones +-- decorated by 'createDecos', which will call 'decorate' to decide if +-- a window must be given a 'Rectangle', in which case a decoration +-- window will be created. +-- +-- After that we resync the updated state with the windows' list and +-- then we process the resynced stated (as we do with a new state). +-- +-- First we map the decoration windows, we update each decoration to +-- reflect any decorated window's change, and we insert, in the list +-- of windows and rectangles returned by the underlying layout, the +-- decoration for each window. This way xmonad will restack the +-- decorations and their windows accordingly. At the end we remove +-- invisible\/stacked windows. +-- +-- Message handling is quite simple: when needed we release the state +-- component of the 'DecorationEx' 'LayoutModifier'. Otherwise we call +-- 'handleEvent', which will call the appropriate 'DecorationEngine' +-- methods to perform its tasks. +instance (DecorationEngine engine widget Window, DecorationGeometry geom Window, Shrinker shrinker) => LayoutModifier (DecorationEx engine widget geom shrinker) Window where + redoLayout (DecorationEx (I (Just decoState)) shrinker theme engine geom) _ Nothing _ = do + releaseResources engine decoState + return ([], Just $ DecorationEx (I Nothing) shrinker theme engine geom) + redoLayout _ _ Nothing _ = return ([], Nothing) + + redoLayout (DecorationEx invState shrinker theme engine geom) screenRect (Just stack) srcPairs + | I Nothing <- invState = initState theme engine geom shrinker screenRect stack srcPairs >>= processState + | I (Just s) <- invState = do + let decorations = dsDecorations s + (d,a) = curry diff (getOrigWindows decorations) srcWindows + toDel = todel d decorations + toAdd = toadd a srcPairs + deleteDecos toDel + let decosToBeAdded = [WindowDecoration win rect Nothing Nothing [] | (win, rect) <- toAdd] + newDecorations <- resync (dsStyleState s) (decosToBeAdded ++ del_dwrs d decorations) srcPairs + processState (s {dsDecorations = newDecorations}) + + where + srcWindows = map fst srcPairs + + getOrigWindows :: [WindowDecoration] -> [Window] + getOrigWindows = map wdOrigWindow + + del_dwrs :: [Window] -> [WindowDecoration] -> [WindowDecoration] + del_dwrs = listFromList wdOrigWindow notElem + + findDecoWindow :: Int -> [WindowDecoration] -> Maybe Window + findDecoWindow i d = wdDecoWindow $ d !! i + + todel :: [Window] -> [WindowDecoration] -> [WindowDecoration] + todel d = filter (\dd -> wdOrigWindow dd `elem` d) + + toadd :: [Window] -> [(Window, Rectangle)] -> [(Window, Rectangle)] + toadd a = filter (\p -> fst p `elem` a) + + createDecoWindowIfNeeded :: Maybe Window -> Maybe Rectangle -> X (Maybe Window) + createDecoWindowIfNeeded mbDecoWindow mbDecoRect = + case (mbDecoWindow, mbDecoRect) of + (Nothing, Just decoRect) -> do + decoWindow <- createDecoWindow engine theme decoRect + return $ Just decoWindow + _ -> return mbDecoWindow + + resync :: DecorationEngineState engine -> [WindowDecoration] -> [(Window,Rectangle)] -> X [WindowDecoration] + resync _ _ [] = return [] + resync decoState dd ((window,rect):xs) = + case window `elemIndex` getOrigWindows dd of + Just i -> do + mbDecoRect <- decorateWindow geom screenRect stack srcPairs (window,rect) + widgetPlaces <- case mbDecoRect of + Nothing -> return $ WidgetLayout [] [] [] + Just decoRect -> placeWidgets engine theme shrinker decoState decoRect window (themeWidgets theme) + mbDecoWindow <- createDecoWindowIfNeeded (findDecoWindow i dd) mbDecoRect + let newDd = WindowDecoration window rect mbDecoWindow mbDecoRect (widgetLayout widgetPlaces) + restDd <- resync decoState dd xs + return $ newDd : restDd + Nothing -> resync decoState dd xs + + -- We drop any windows that are *precisely* stacked underneath + -- another window: these must be intended to be tabbed! + removeTabbed :: [Rectangle] -> [(Window, Rectangle)] -> [(Window, Rectangle)] + removeTabbed _ [] = [] + removeTabbed rs ((w,r):xs) + | r `elem` rs = removeTabbed rs xs + | otherwise = (w,r) : removeTabbed (r:rs) xs + + insertDwr :: WindowDecoration -> [(Window, Rectangle)] -> [(Window, Rectangle)] + insertDwr dd wrs = + case (wdDecoWindow dd, wdDecoRect dd) of + (Just decoWindow, Just decoRect) -> (decoWindow, decoRect) : (wdOrigWindow dd, shrinkWindow geom decoRect (wdOrigWinRect dd)) : wrs + _ -> (wdOrigWindow dd, wdOrigWinRect dd) : wrs + + dwrs_to_wrs :: [WindowDecoration] -> [(Window, Rectangle)] + dwrs_to_wrs = removeTabbed [] . foldr insertDwr [] + + processState :: DecorationLayoutState engine -> X ([(Window, Rectangle)], Maybe (DecorationEx engine widget geom shrinker Window)) + processState st = do + let decorations = dsDecorations st + showDecos decorations + updateDecos engine shrinker theme (dsStyleState st) decorations + return (dwrs_to_wrs decorations, Just (DecorationEx (I (Just (st {dsDecorations = decorations}))) shrinker theme engine geom)) + + handleMess (DecorationEx (I (Just st)) shrinker theme engine geom) m + | Just Hide <- fromMessage m = do + hideDecos $ dsDecorations st + return Nothing +-- | Just (SetTheme nt) <- fromMessage m = do +-- releaseResources engine st +-- let t' = themeEx nt +-- return $ Just $ DecorationEx (I Nothing) shrinker t' engine + | Just ReleaseResources <- fromMessage m = do + releaseResources engine st + return $ Just $ DecorationEx (I Nothing) shrinker theme engine geom + | Just e <- fromMessage m = do + decorationEventHookEx engine theme st shrinker e + handleEvent engine shrinker theme st e + return Nothing + handleMess _ _ = return Nothing + + modifierDescription (DecorationEx _ _ _ engine geom) = describeEngine engine ++ describeGeometry geom + +-- | By default 'DecorationEx' handles 'PropertyEvent' and 'ExposeEvent' +-- only. +handleEvent :: (Shrinker shrinker, DecorationEngine engine widget Window) => engine widget Window -> shrinker -> Theme engine widget -> DecorationLayoutState engine -> Event -> X () +handleEvent engine shrinker theme (DecorationLayoutState {..}) e + | PropertyEvent {ev_window = w, ev_atom = atom} <- e + , Just i <- w `elemIndex` map wdOrigWindow dsDecorations = do + supportedAtoms <- propsToRepaintDecoration engine + when (atom `elem` supportedAtoms) $ do + -- io $ putStrLn $ "property event on " ++ show w -- ++ ": " ++ fromMaybe "" atomName + updateDeco engine shrinker theme dsStyleState (dsDecorations !! i) False + | ExposeEvent {ev_window = w} <- e + , Just i <- w `elemIndex` mapMaybe wdDecoWindow dsDecorations = do + -- io $ putStrLn $ "expose event on " ++ show w + updateDeco engine shrinker theme dsStyleState (dsDecorations !! i) True +handleEvent _ _ _ _ _ = return () + +-- | Initialize the 'DecorationState' by initializing the font +-- structure and by creating the needed decorations. +initState :: (DecorationEngine engine widget Window, DecorationGeometry geom Window, Shrinker shrinker) + => Theme engine widget + -> engine widget Window + -> geom Window + -> shrinker + -> Rectangle + -> W.Stack Window + -> [(Window,Rectangle)] -> X (DecorationLayoutState engine) +initState theme engine geom shrinker screenRect stack wrs = do + styleState <- initializeState engine geom theme + decorations <- createDecos theme engine geom shrinker styleState screenRect stack wrs wrs + return $ DecorationLayoutState styleState decorations + +-- | Delete windows stored in the state and release the font structure. +releaseResources :: DecorationEngine engine widget Window => engine widget Window -> DecorationLayoutState engine -> X () +releaseResources engine st = do + deleteDecos (dsDecorations st) + releaseStateResources engine (dsStyleState st) + +-- | Create the decoration windows of a list of windows and their +-- rectangles, by calling the 'decorate' method of the +-- 'DecorationStyle' received. +createDecos :: (DecorationEngine engine widget Window, DecorationGeometry geom Window, Shrinker shrinker) + => Theme engine widget + -> engine widget Window + -> geom Window + -> shrinker + -> DecorationEngineState engine + -> Rectangle + -> W.Stack Window + -> [(Window,Rectangle)] -> [(Window,Rectangle)] -> X [WindowDecoration] +createDecos theme engine geom shrinker decoState screenRect stack wrs ((w,r):xs) = do + mbDecoRect <- decorateWindow geom screenRect stack wrs (w,r) + case mbDecoRect of + Just decoRect -> do + decoWindow <- createDecoWindow engine theme decoRect + widgetPlaces <- placeWidgets engine theme shrinker decoState decoRect w (themeWidgets theme) + restDd <- createDecos theme engine geom shrinker decoState screenRect stack wrs xs + let newDd = WindowDecoration w r (Just decoWindow) (Just decoRect) $ widgetLayout widgetPlaces + return $ newDd : restDd + Nothing -> do + restDd <- createDecos theme engine geom shrinker decoState screenRect stack wrs xs + let newDd = WindowDecoration w r Nothing Nothing [] + return $ newDd : restDd +createDecos _ _ _ _ _ _ _ _ [] = return [] + +createDecoWindow :: (DecorationEngine engine widget Window) => engine widget Window -> Theme engine widget -> Rectangle -> X Window +createDecoWindow engine theme rect = do + let mask = Just $ decorationXEventMask engine + w <- createNewWindow rect mask (defaultBgColor theme) True + d <- asks display + io $ setClassHint d w (ClassHint "xmonad-decoration" "xmonad") + return w + +showDecos :: [WindowDecoration] -> X () +showDecos dd = + showWindows $ mapMaybe wdDecoWindow $ filter (isJust . wdDecoRect) dd + +hideDecos :: [WindowDecoration] -> X () +hideDecos = hideWindows . mapMaybe wdDecoWindow + +deleteDecos :: [WindowDecoration] -> X () +deleteDecos = deleteWindows . mapMaybe wdDecoWindow + +updateDecos :: (Shrinker shrinker, DecorationEngine engine widget Window) + => engine widget Window -> shrinker -> Theme engine widget -> DecorationEngineState engine -> [WindowDecoration] -> X () +updateDecos engine shrinker theme decoState = mapM_ (\wd -> updateDeco engine shrinker theme decoState wd False) + +-- | Update a decoration window given a shrinker, a theme, the font +-- structure and the needed 'Rectangle's +updateDeco :: (Shrinker shrinker, DecorationEngine engine widget Window) => engine widget Window -> shrinker -> Theme engine widget -> DecorationEngineState engine -> WindowDecoration -> Bool -> X () +updateDeco engine shrinker theme decoState wd isExpose = + case (wdDecoWindow wd, wdDecoRect wd) of + (Just decoWindow, Just decoRect@(Rectangle _ _ wh ht)) -> do + let origWin = wdOrigWindow wd + drawData <- mkDrawData engine theme decoState origWin decoRect + widgetPlaces <- placeWidgets engine theme shrinker decoState decoRect (wdOrigWindow wd) (themeWidgets theme) + -- io $ print widgetPlaces + paintDecoration engine decoWindow wh ht shrinker (drawData {ddWidgetPlaces = widgetPlaces}) isExpose + (Just decoWindow, Nothing) -> hideWindow decoWindow + _ -> return () + +-- | Apply a DecorationEx modifier to an underlying layout +decorationEx :: (DecorationEngine engine widget a, DecorationGeometry geom a, Shrinker shrinker) + => shrinker -- ^ Strings shrinker, for example @shrinkText@ + -> Theme engine widget -- ^ Decoration theme + -> engine widget a -- ^ Decoration engine instance + -> geom a -- ^ Decoration geometry instance + -> l a -- ^ Underlying layout to be decorated + -> ModifiedLayout (DecorationEx engine widget geom shrinker) l a +decorationEx shrinker theme engine geom = ModifiedLayout (DecorationEx (I Nothing) shrinker theme engine geom) + diff --git a/XMonad/Layout/DecorationEx/TabbedGeometry.hs b/XMonad/Layout/DecorationEx/TabbedGeometry.hs new file mode 100644 index 0000000000..83e640d3c2 --- /dev/null +++ b/XMonad/Layout/DecorationEx/TabbedGeometry.hs @@ -0,0 +1,169 @@ +{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE RecordWildCards #-} + +----------------------------------------------------------------------------- +-- | +-- Module : XMonad.Layout.DecorationEx.TabbedGeometry +-- Description : Tab-based window decoration geometry +-- Copyright : (c) 2007 Andrea Rossato, 2023 Ilya Portnov +-- License : BSD-style (see xmonad/LICENSE) +-- +-- Maintainer : portnov84@rambler.ru +-- Stability : unstable +-- Portability : unportable +-- +-- This module defines window decoration geometry based on tabs. +-- The tabs can follow horizontally and be placed above or below windows; +-- in such case, tabs can occupy full width of the window or be aligned to +-- left or right. Or tabs can go vertically near left or right side of +-- the window. +----------------------------------------------------------------------------- + +module XMonad.Layout.DecorationEx.TabbedGeometry ( + textTabbed, + TabbedGeometry (..), + HorizontalTabPlacement (..), + VerticalTabPlacement (..), + HorizontalTabWidth (..), + HorizontalTabsAlignment (..), + SingleTabMode (..) + ) where + +import XMonad +import qualified XMonad.StackSet as W +import XMonad.Prelude +import XMonad.Layout.Decoration (ModifiedLayout, Shrinker (..)) + +import XMonad.Layout.DecorationEx.LayoutModifier +import XMonad.Layout.DecorationEx.Common +import XMonad.Layout.DecorationEx.Geometry +import XMonad.Layout.DecorationEx.Widgets +import XMonad.Layout.DecorationEx.TextEngine + +-- | Placement of tabs when they go horizontally: +-- should they be placed above or below the window. +data HorizontalTabPlacement = Top | Bottom + deriving (Eq, Read, Show) + +-- | Placement of tabs when they go vertically: +-- should they appear at left or at right side of the window. +data VerticalTabPlacement = TabsAtLeft | TabsAtRight + deriving (Eq, Read, Show) + +-- | Width of tabs when they go horizontally. +data HorizontalTabWidth = + AutoWidth -- ^ Define the width automatically by evenly dividing windows' width + | FixedWidth !Dimension -- ^ Use fixed width of the tab + deriving (Eq, Read, Show) + +-- | Alignment of tabs when they go horizontally. +data HorizontalTabsAlignment = AlignTabsLeft | AlignTabsCenter | AlignTabsRight + deriving (Eq, Read, Show) + +-- | What to do if there is only one tab. +data SingleTabMode = ShowTab | HideTab + deriving (Eq, Read, Show) + +data TabbedGeometry a = + HorizontalTabs { + showIfSingleWindow :: !SingleTabMode -- ^ What to do if there is only one tab + , hTabPlacement :: !HorizontalTabPlacement -- ^ Where to place horizontal tabs + , hTabAlignment :: !HorizontalTabsAlignment -- ^ How to align horizontal tabs (makes sense with fixed width of tabs). + , hTabWidth :: !HorizontalTabWidth -- ^ Width of horizontal tabs + , hTabHeight :: !Dimension -- ^ Height of horizontal tabs + } + | VerticalTabs { + showIfSingleWindow :: !SingleTabMode -- ^ What to do if there is only one tab + , vTabPlacement :: !VerticalTabPlacement -- ^ Where to place vertical tabs + , vTabWidth :: !Dimension -- ^ Width of vertical tabs + , vTabHeight :: !Dimension -- ^ Height of vertical tabs + } + deriving (Show, Read) + +instance Default (TabbedGeometry a) where + def = HorizontalTabs ShowTab Top AlignTabsLeft AutoWidth 20 + +instance DecorationGeometry TabbedGeometry Window where + + describeGeometry _ = "Tabbed" + + pureDecoration tabs _ stack wrs (window, windowRect) = + let Rectangle windowX windowY windowWidth windowHeight = windowRect + -- windows that are mapped onto the same rectangle as current one are considered to + -- be in one tabs group + tabbedWindows = filter (`elem` map fst (filter ((==windowRect) . snd) wrs)) (W.integrate stack) + mbWindowIndex = window `elemIndex` tabbedWindows + numWindows = length tabbedWindows + in if numWindows > 1 || (showIfSingleWindow tabs == ShowTab && numWindows > 0) + then + case tabs of + HorizontalTabs {..} -> + Just $ case hTabPlacement of + Top -> Rectangle decoX windowY effectiveTabWidth hTabHeight + Bottom -> Rectangle decoX (windowY + fi (windowHeight - hTabHeight)) effectiveTabWidth hTabHeight + where + decoX = maybe windowX tabX mbWindowIndex + + -- If there are too many windows or configured tab width + -- is too big, then we have to switch to 'auto' mode. + hTabWidth' = + case hTabWidth of + AutoWidth -> AutoWidth + FixedWidth tabWidth + | tabWidth * fi numWindows > windowWidth -> AutoWidth + | otherwise -> FixedWidth tabWidth + + effectiveTabWidth = + case hTabWidth' of + AutoWidth -> fi $ maybe windowX (\i -> tabX (i+1) - tabX i) mbWindowIndex + FixedWidth tabWidth -> tabWidth + + allTabsWidth = + case hTabWidth' of + AutoWidth -> fi windowWidth + FixedWidth _ -> fi $ min windowWidth $ effectiveTabWidth * max 1 (fi numWindows) + + tabsStartX = + case hTabAlignment of + AlignTabsLeft -> windowX + AlignTabsRight -> windowX + fi windowWidth - allTabsWidth + AlignTabsCenter -> windowX + (fi windowWidth - allTabsWidth) `div` 2 + + -- X coordinate of i'th window in horizontal tabs layout + tabX i = tabsStartX + + case hTabWidth' of + AutoWidth -> fi ((windowWidth * fi i) `div` max 1 (fi numWindows)) + FixedWidth _ -> fi effectiveTabWidth * fi i + + VerticalTabs {..} -> + Just $ case vTabPlacement of + TabsAtLeft -> fixHeightTab windowX + TabsAtRight -> fixHeightTab (windowX + fi (windowWidth - vTabWidth)) + where + fixHeightLoc i = windowY + fi vTabHeight * fi i + fixHeightTab x = Rectangle x + (maybe windowY fixHeightLoc mbWindowIndex) vTabWidth vTabHeight + else Nothing + + shrinkWindow tabs (Rectangle _ _ dw dh) (Rectangle x y w h) = + case tabs of + HorizontalTabs {..} -> + case hTabPlacement of + Top -> Rectangle x (y + fi dh) w (h - dh) + Bottom -> Rectangle x y w (h - dh) + VerticalTabs {..} -> + case vTabPlacement of + TabsAtLeft -> Rectangle (x + fi dw) y (w - dw) h + TabsAtRight -> Rectangle x y (w - dw) h + +-- | Add tabbed decorations (with default settings) with text-based widgets to a layout. +textTabbed :: (Shrinker shrinker) + => shrinker -- ^ Strings shrinker, e.g. @shrinkText@ + -> ThemeEx StandardWidget -- ^ Decoration theme + -> l Window -- ^ Layout to be decorated + -> ModifiedLayout (DecorationEx TextDecoration StandardWidget TabbedGeometry shrinker) l Window +textTabbed shrinker theme = decorationEx shrinker theme TextDecoration def + diff --git a/XMonad/Layout/DecorationEx/TextEngine.hs b/XMonad/Layout/DecorationEx/TextEngine.hs new file mode 100644 index 0000000000..a0ddd4392c --- /dev/null +++ b/XMonad/Layout/DecorationEx/TextEngine.hs @@ -0,0 +1,116 @@ +{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE UndecidableInstances #-} + +----------------------------------------------------------------------------- +-- | +-- Module : XMonad.Layout.DecorationEx.TextEngine +-- Description : Text-based window decoration engine +-- Copyright : (c) 2007 Andrea Rossato, 2009 Jan Vornberger, 2023 Ilya Portnov +-- License : BSD-style (see xmonad/LICENSE) +-- +-- Maintainer : portnov84@rambler.ru +-- Stability : unstable +-- Portability : unportable +-- +-- Window decoration engine, that uses text fragments (like @"[X]"@) to indicate +-- widgets (window buttons). +----------------------------------------------------------------------------- + +module XMonad.Layout.DecorationEx.TextEngine ( + textDecoration, + TextDecoration (..) + ) where + +import XMonad +import XMonad.Prelude +import XMonad.Layout.LayoutModifier +import XMonad.Util.Font + +import XMonad.Layout.DecorationEx.LayoutModifier +import XMonad.Layout.DecorationEx.Common +import XMonad.Layout.DecorationEx.Engine +import XMonad.Layout.DecorationEx.Geometry +import XMonad.Layout.DecorationEx.Widgets + +-- | Decoration engine data type +data TextDecoration widget a = TextDecoration + deriving (Show, Read) + +instance (TextWidget widget, ClickHandler (GenericTheme SimpleStyle) widget) + => DecorationEngine TextDecoration widget Window where + type Theme TextDecoration = GenericTheme SimpleStyle + type DecorationPaintingContext TextDecoration = XPaintingContext + type DecorationEngineState TextDecoration = XMonadFont + + describeEngine _ = "TextDecoration" + + calcWidgetPlace = calcTextWidgetPlace + + paintWidget = paintTextWidget + + paintDecoration = paintDecorationSimple + + initializeState _ _ theme = initXMF (themeFontName theme) + releaseStateResources _ = releaseXMF + +-- | Implementation of @paintWidget@ for decoration engines based on @TextDecoration@. +paintTextWidget :: (TextWidget widget, + Style (Theme engine widget) ~ SimpleStyle, + DecorationPaintingContext engine ~ XPaintingContext, + DecorationEngineState engine ~ XMonadFont, + Shrinker shrinker, + DecorationEngine engine widget Window) + => engine widget Window + -> DecorationPaintingContext engine + -> WidgetPlace + -> shrinker + -> DrawData engine widget + -> widget + -> Bool + -> X () +paintTextWidget engine (dpy, pixmap, gc) place shrinker dd widget _ = do + let style = ddStyle dd + rect = wpRectangle place + x = rect_x rect + y = wpTextYPosition place + str <- widgetString dd widget + str' <- if isShrinkable widget + then getShrinkedWindowName engine shrinker (ddEngineState dd) str (rect_width rect) (rect_height rect) + else return str + printStringXMF dpy pixmap (ddEngineState dd) gc (sTextColor style) (sTextBgColor style) x y str' + +-- | Implementation of @calcWidgetPlace@ for decoration engines based on @TextDecoration@. +calcTextWidgetPlace :: (TextWidget widget, + DecorationEngineState engine ~ XMonadFont, + DecorationEngine engine widget Window) + => engine widget Window + -> DrawData engine widget + -> widget + -> X WidgetPlace +calcTextWidgetPlace _ dd widget = do + str <- widgetString dd widget + let h = rect_height (ddDecoRect dd) + font = ddEngineState dd + withDisplay $ \dpy -> do + width <- fi <$> textWidthXMF dpy (ddEngineState dd) str + (a, d) <- textExtentsXMF font str + let height = a + d + y = fi $ (h - fi height) `div` 2 + y0 = y + fi a + rect = Rectangle 0 y width (fi height) + return $ WidgetPlace y0 rect + +-- | Add decoration to existing layout. Widgets are indicated by text fragments, like @"[+]"@. +-- Geometry is simple: a horizontal panel at the top of each window, going for the full width +-- of the window. +textDecoration :: (Shrinker shrinker) + => shrinker -- ^ String shrinker, for example @shrinkText@ + -> Theme TextDecoration StandardWidget -- ^ Decoration theme (font, colors, widgets, etc) + -> l Window -- ^ Layout to be decorated + -> ModifiedLayout (DecorationEx TextDecoration StandardWidget DefaultGeometry shrinker) l Window +textDecoration shrinker theme = decorationEx shrinker theme TextDecoration def + diff --git a/XMonad/Layout/DecorationEx/Widgets.hs b/XMonad/Layout/DecorationEx/Widgets.hs new file mode 100644 index 0000000000..5bd7a53c13 --- /dev/null +++ b/XMonad/Layout/DecorationEx/Widgets.hs @@ -0,0 +1,208 @@ +{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeFamilies #-} +----------------------------------------------------------------------------- +-- | +-- Module : XMonad.Layout.DecorationEx.Widgets +-- Description : Definitions for decoration widgets (window buttons etc) +-- Copyright : 2023 Ilya Portnov +-- License : BSD-style (see xmonad/LICENSE) +-- +-- Maintainer : portnov84@rambler.ru +-- Stability : unstable +-- Portability : unportable +-- +-- This module contains data types and utilities to deal with decoration +-- widgets. A widget is anything that is displayed on window decoration, +-- and, optionally, can react on clicks. Examples of widgets are usual +-- window buttons (minimize, maximize, close), window icon and window title. +----------------------------------------------------------------------------- + +module XMonad.Layout.DecorationEx.Widgets ( + -- * Data types + StandardCommand (..), + TextWidget (..), + GenericWidget (..), + StandardWidget, + -- * Utility functions + isWidgetChecked, + -- * Presets for standard widgets + titleW, toggleStickyW, minimizeW, + maximizeW, closeW, dwmpromoteW, + moveToNextGroupW,moveToPrevGroupW + ) where + +import XMonad +import qualified XMonad.StackSet as W +import XMonad.Actions.DwmPromote +import qualified XMonad.Actions.CopyWindow as CW +import qualified XMonad.Layout.Groups.Examples as Ex +import XMonad.Layout.Maximize +import XMonad.Actions.Minimize +import XMonad.Actions.WindowMenu + +import XMonad.Layout.DecorationEx.Common +import XMonad.Layout.DecorationEx.Engine + +-- | Standard window commands. +-- +-- One can extend this list by simply doing +-- +-- > data MyWindowCommand = +-- > Std StandardCommand +-- > | SomeFancyCommand +-- +-- > instance WindowCommand MyWindowCommand where ... +-- +-- > type MyWidget = GenericWidget MyWindowCommand +-- +data StandardCommand = + FocusWindow -- ^ Focus the window + | FocusUp -- ^ Move focus to previous window + | FocusDown -- ^ Move focus to following window + | MoveToNextGroup -- ^ Move the window to the next group (see "XMonad.Layout.Groups") + | MoveToPrevGroup -- ^ Move the window to the previous group + | DwmPromote -- ^ Execute @dwmpromote@ (see "XMonad.Actions.DwmPromote") + | ToggleSticky -- ^ Make window sticky or unstick it (see "XMonad.Actions.CopyWindow") + | ToggleMaximize -- ^ Maximize or restore window (see "XMonad.Layout.Maximize") + | Minimize -- ^ Minimize window (see "XMonad.Actions.Minimize") + | CloseWindow -- ^ Close the window + | GridWindowMenu -- ^ Show window menu via "XMonad.Actions.GridSelect" (see "XMonad.Actions.WindowMenu") + deriving (Eq, Show, Read) + +instance Default StandardCommand where + def = FocusWindow + +instance WindowCommand StandardCommand where + executeWindowCommand FocusWindow w = do + focus w + return False + executeWindowCommand FocusUp _ = do + windows W.focusUp + withFocused maximizeWindowAndFocus + return True + executeWindowCommand FocusDown _ = do + windows W.focusDown + withFocused maximizeWindowAndFocus + return True + executeWindowCommand MoveToNextGroup w = do + focus w + Ex.moveToGroupDown False + return True + executeWindowCommand MoveToPrevGroup w = do + focus w + Ex.moveToGroupUp False + return True + executeWindowCommand CloseWindow w = do + killWindow w + return True + executeWindowCommand DwmPromote w = do + focus w + dwmpromote + return True + executeWindowCommand ToggleSticky w = do + focus w + copies <- CW.wsContainingCopies + if null copies + then windows CW.copyToAll + else CW.killAllOtherCopies + return True + executeWindowCommand ToggleMaximize w = do + sendMessage $ maximizeRestore w + focus w + return True + executeWindowCommand Minimize w = do + minimizeWindow w + return True + executeWindowCommand GridWindowMenu w = do + focus w + windowMenu + return True + + isCommandChecked FocusWindow _ = return False + isCommandChecked DwmPromote w = do + withWindowSet $ \ws -> return $ Just w == master ws + where + master ws = + case W.integrate' $ W.stack $ W.workspace $ W.current ws of + [] -> Nothing + (x:_) -> Just x + isCommandChecked ToggleSticky w = do + ws <- gets windowset + let copies = CW.copiesOfOn (Just w) (CW.taggedWindows $ W.hidden ws) + return $ not $ null copies + isCommandChecked _ _ = return False + +-- | Generic data type for decoration widgets. +data GenericWidget cmd = + TitleWidget -- ^ Window title (just text label) + | WindowIcon { swCommand :: !cmd } -- ^ Window icon with some associated command + -- | Other widgets + | GenericWidget { + swCheckedText :: !String -- ^ Text for checked widget state + , swUncheckedText :: !String -- ^ Text for unchecked widget state + , swCommand :: !cmd -- ^ Window command + } + deriving (Show, Read) + +-- | Generic widget type specialized for 'StandardCommand' +type StandardWidget = GenericWidget StandardCommand + +instance (Default cmd, Read cmd, Show cmd, WindowCommand cmd) => DecorationWidget (GenericWidget cmd) where + + type WidgetCommand (GenericWidget cmd) = cmd + + widgetCommand TitleWidget _ = def + widgetCommand w 1 = swCommand w + widgetCommand _ _ = def + + isShrinkable TitleWidget = True + isShrinkable _ = False + +-- | Check if the widget should be displayed in `checked' state. +isWidgetChecked :: DecorationWidget widget => widget -> Window -> X Bool +isWidgetChecked wdt = isCommandChecked (widgetCommand wdt 1) + +-- | Type class for widgets that can be displayed as +-- text fragments by 'TextDecoration' engine. +class DecorationWidget widget => TextWidget widget where + widgetString :: DrawData engine widget -> widget -> X String + +instance TextWidget StandardWidget where + widgetString dd TitleWidget = return $ ddWindowTitle dd + widgetString _ (WindowIcon {}) = return "[*]" + widgetString dd w = do + checked <- isWidgetChecked w (ddOrigWindow dd) + if checked + then return $ swCheckedText w + else return $ swUncheckedText w + +-- | Widget for window title +titleW :: StandardWidget +titleW = TitleWidget + +-- | Widget for ToggleSticky command. +toggleStickyW :: StandardWidget +toggleStickyW = GenericWidget "[S]" "[s]" ToggleSticky + +-- | Widget for Minimize command +minimizeW :: StandardWidget +minimizeW = GenericWidget "" "[_]" Minimize + +-- | Widget for ToggleMaximize command +maximizeW :: StandardWidget +maximizeW = GenericWidget "" "[O]" ToggleMaximize + +-- | Widget for CloseWindow command +closeW :: StandardWidget +closeW = GenericWidget "" "[X]" CloseWindow + +dwmpromoteW :: StandardWidget +dwmpromoteW = GenericWidget "[M]" "[m]" DwmPromote + +moveToNextGroupW :: StandardWidget +moveToNextGroupW = GenericWidget "" "[>]" MoveToNextGroup + +moveToPrevGroupW :: StandardWidget +moveToPrevGroupW = GenericWidget "" "[<]" MoveToPrevGroup + diff --git a/XMonad/Layout/DecorationMadness.hs b/XMonad/Layout/DecorationMadness.hs index d17e621f0f..7ddc15debe 100644 --- a/XMonad/Layout/DecorationMadness.hs +++ b/XMonad/Layout/DecorationMadness.hs @@ -17,7 +17,7 @@ module XMonad.Layout.DecorationMadness ( -- * Usage -- $usage - -- * Decorated layouts based on Circle + -- * Decorated layouts based on CircleEx -- $circle circleSimpleDefault , circleDefault @@ -94,13 +94,13 @@ import XMonad.Layout.SimpleDecoration import XMonad.Layout.TabBarDecoration import XMonad.Layout.Accordion -import XMonad.Layout.Circle +import XMonad.Layout.CircleEx import XMonad.Layout.WindowArranger import XMonad.Layout.SimpleFloat -- $usage -- You can use this module with the following in your --- @~\/.xmonad\/xmonad.hs@: +-- @xmonad.hs@: -- -- > import XMonad.Layout.DecorationMadness -- @@ -132,39 +132,39 @@ import XMonad.Layout.SimpleFloat -- "XMonad.Util.Themes" -- $circle --- Here you will find 'Circle' based decorated layouts. +-- Here you will find 'CircleEx' based decorated layouts. --- | A 'Circle' layout with the xmonad default decoration, default +-- | A 'CircleEx' layout with the xmonad default decoration, default -- theme and default shrinker. -- -- Here you can find a screen shot: -- -- -circleSimpleDefault :: ModifiedLayout (Decoration DefaultDecoration DefaultShrinker) Circle Window -circleSimpleDefault = decoration shrinkText def DefaultDecoration Circle +circleSimpleDefault :: ModifiedLayout (Decoration DefaultDecoration DefaultShrinker) CircleEx Window +circleSimpleDefault = decoration shrinkText def DefaultDecoration circle -- | Similar to 'circleSimpleDefault' but with the possibility of -- setting a custom shrinker and a custom theme. circleDefault :: Shrinker s => s -> Theme - -> ModifiedLayout (Decoration DefaultDecoration s) Circle Window -circleDefault s t = decoration s t DefaultDecoration Circle + -> ModifiedLayout (Decoration DefaultDecoration s) CircleEx Window +circleDefault s t = decoration s t DefaultDecoration circle --- | A 'Circle' layout with the xmonad simple decoration, default +-- | A 'CircleEx' layout with the xmonad simple decoration, default -- theme and default shrinker. -- -- Here you can find a screen shot: -- -- -circleSimpleDeco :: ModifiedLayout (Decoration SimpleDecoration DefaultShrinker) Circle Window -circleSimpleDeco = decoration shrinkText def (Simple True) Circle +circleSimpleDeco :: ModifiedLayout (Decoration SimpleDecoration DefaultShrinker) CircleEx Window +circleSimpleDeco = decoration shrinkText def (Simple True) circle -- | Similar to 'circleSimpleDece' but with the possibility of -- setting a custom shrinker and a custom theme. circleDeco :: Shrinker s => s -> Theme - -> ModifiedLayout (Decoration SimpleDecoration s) Circle Window -circleDeco s t = decoration s t (Simple True) Circle + -> ModifiedLayout (Decoration SimpleDecoration s) CircleEx Window +circleDeco s t = decoration s t (Simple True) circle --- | A 'Circle' layout with the xmonad default decoration, default +-- | A 'CircleEx' layout with the xmonad default decoration, default -- theme and default shrinker, but with the possibility of moving -- windows with the mouse, and resize\/move them with the keyboard. -- @@ -172,17 +172,17 @@ circleDeco s t = decoration s t (Simple True) Circle -- -- circleSimpleDefaultResizable :: ModifiedLayout (Decoration DefaultDecoration DefaultShrinker) - (ModifiedLayout MouseResize (ModifiedLayout WindowArranger Circle)) Window -circleSimpleDefaultResizable = decoration shrinkText def DefaultDecoration (mouseResize $ windowArrange Circle) + (ModifiedLayout MouseResize (ModifiedLayout WindowArranger CircleEx)) Window +circleSimpleDefaultResizable = decoration shrinkText def DefaultDecoration (mouseResize $ windowArrange circle) -- | Similar to 'circleSimpleDefaultResizable' but with the -- possibility of setting a custom shrinker and a custom theme. circleDefaultResizable :: Shrinker s => s -> Theme -> ModifiedLayout (Decoration DefaultDecoration s) - (ModifiedLayout MouseResize (ModifiedLayout WindowArranger Circle)) Window -circleDefaultResizable s t = decoration s t DefaultDecoration (mouseResize $ windowArrange Circle) + (ModifiedLayout MouseResize (ModifiedLayout WindowArranger CircleEx)) Window +circleDefaultResizable s t = decoration s t DefaultDecoration (mouseResize $ windowArrange circle) --- | A 'Circle' layout with the xmonad simple decoration, default +-- | A 'CircleEx' layout with the xmonad simple decoration, default -- theme and default shrinker, but with the possibility of moving -- windows with the mouse, and resize\/move them with the keyboard. -- @@ -190,45 +190,45 @@ circleDefaultResizable s t = decoration s t DefaultDecoration (mouseResize $ win -- -- circleSimpleDecoResizable :: ModifiedLayout (Decoration SimpleDecoration DefaultShrinker) - (ModifiedLayout MouseResize (ModifiedLayout WindowArranger Circle)) Window -circleSimpleDecoResizable = decoration shrinkText def (Simple True) (mouseResize $ windowArrange Circle) + (ModifiedLayout MouseResize (ModifiedLayout WindowArranger CircleEx)) Window +circleSimpleDecoResizable = decoration shrinkText def (Simple True) (mouseResize $ windowArrange circle) -- | Similar to 'circleSimpleDecoResizable' but with the -- possibility of setting a custom shrinker and a custom theme. circleDecoResizable :: Shrinker s => s -> Theme -> ModifiedLayout (Decoration SimpleDecoration s) - (ModifiedLayout MouseResize (ModifiedLayout WindowArranger Circle)) Window -circleDecoResizable s t = decoration s t (Simple True) (mouseResize $ windowArrange Circle) + (ModifiedLayout MouseResize (ModifiedLayout WindowArranger CircleEx)) Window +circleDecoResizable s t = decoration s t (Simple True) (mouseResize $ windowArrange circle) --- | A 'Circle' layout with the xmonad DwmStyle decoration, default +-- | A 'CircleEx' layout with the xmonad DwmStyle decoration, default -- theme and default shrinker. -- -- Here you can find a screen shot: -- -- -circleSimpleDwmStyle :: ModifiedLayout (Decoration DwmStyle DefaultShrinker) Circle Window -circleSimpleDwmStyle = decoration shrinkText def Dwm Circle +circleSimpleDwmStyle :: ModifiedLayout (Decoration DwmStyle DefaultShrinker) CircleEx Window +circleSimpleDwmStyle = decoration shrinkText def Dwm circle -- | Similar to 'circleSimpleDwmStyle' but with the -- possibility of setting a custom shrinker and a custom theme. circleDwmStyle :: Shrinker s => s -> Theme - -> ModifiedLayout (Decoration DwmStyle s) Circle Window -circleDwmStyle s t = decoration s t Dwm Circle + -> ModifiedLayout (Decoration DwmStyle s) CircleEx Window +circleDwmStyle s t = decoration s t Dwm circle --- | A 'Circle' layout with the xmonad tabbed decoration, default +-- | A 'CircleEx' layout with the xmonad tabbed decoration, default -- theme and default shrinker. -- -- Here you can find a screen shot: -- -- -circleSimpleTabbed :: ModifiedLayout (Decoration TabBarDecoration DefaultShrinker) (ModifiedLayout ResizeScreen Circle) Window -circleSimpleTabbed = simpleTabBar Circle +circleSimpleTabbed :: ModifiedLayout (Decoration TabBarDecoration DefaultShrinker) (ModifiedLayout ResizeScreen CircleEx) Window +circleSimpleTabbed = simpleTabBar circle -- | Similar to 'circleSimpleTabbed' but with the -- possibility of setting a custom shrinker and a custom theme. circleTabbed :: Shrinker s => s -> Theme - -> ModifiedLayout (Decoration TabBarDecoration s) (ModifiedLayout ResizeScreen Circle) Window -circleTabbed s t = tabBar s t Top (resizeVertical (fi $ decoHeight t) Circle) + -> ModifiedLayout (Decoration TabBarDecoration s) (ModifiedLayout ResizeScreen CircleEx) Window +circleTabbed s t = tabBar s t Top (resizeVertical (fi $ decoHeight t) circle) -- $accordion diff --git a/XMonad/Layout/Dishes.hs b/XMonad/Layout/Dishes.hs index 47d2c28de8..b808b0856f 100644 --- a/XMonad/Layout/Dishes.hs +++ b/XMonad/Layout/Dishes.hs @@ -27,7 +27,7 @@ import XMonad.StackSet (integrate) import XMonad.Prelude (ap) -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.Dishes -- diff --git a/XMonad/Layout/DragPane.hs b/XMonad/Layout/DragPane.hs index 9c8ec7b3cd..3ea4e0e4a3 100644 --- a/XMonad/Layout/DragPane.hs +++ b/XMonad/Layout/DragPane.hs @@ -35,7 +35,7 @@ import XMonad.Util.Invisible import XMonad.Util.XUtils -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.DragPane -- diff --git a/XMonad/Layout/Drawer.hs b/XMonad/Layout/Drawer.hs index 0a179d6077..16d86c3304 100644 --- a/XMonad/Layout/Drawer.hs +++ b/XMonad/Layout/Drawer.hs @@ -41,7 +41,7 @@ import XMonad.StackSet as S import XMonad.Layout.Reflect -- $usage --- To use this module, add the following import to @~\/.xmonad\/xmonad.hs@: +-- To use this module, add the following import to @xmonad.hs@: -- -- > import XMonad.Layout.Drawer -- diff --git a/XMonad/Layout/Dwindle.hs b/XMonad/Layout/Dwindle.hs index dadf8adf08..222b7721c1 100644 --- a/XMonad/Layout/Dwindle.hs +++ b/XMonad/Layout/Dwindle.hs @@ -159,8 +159,8 @@ squeeze dir ratio rect st = zip wins rects nwins = length wins sizes = take nwins $ unfoldr (\r -> Just (r * ratio, r * ratio)) 1 totals' = 0 : zipWith (+) sizes totals' - totals = tail totals' - splits = zip (tail sizes) totals + totals = drop 1 totals' + splits = zip (drop 1 sizes) totals ratios = reverse $ map (uncurry (/)) splits rects = genRects rect ratios genRects r [] = [r] diff --git a/XMonad/Layout/DwmStyle.hs b/XMonad/Layout/DwmStyle.hs index 5c581eb272..af21aa15ae 100644 --- a/XMonad/Layout/DwmStyle.hs +++ b/XMonad/Layout/DwmStyle.hs @@ -30,7 +30,7 @@ import XMonad.Layout.Decoration -- $usage -- You can use this module with the following in your --- @~\/.xmonad\/xmonad.hs@: +-- @xmonad.hs@: -- -- > import XMonad.Layout.DwmStyle -- diff --git a/XMonad/Layout/FixedAspectRatio.hs b/XMonad/Layout/FixedAspectRatio.hs index 2a2964a076..d7dd62ab40 100644 --- a/XMonad/Layout/FixedAspectRatio.hs +++ b/XMonad/Layout/FixedAspectRatio.hs @@ -38,7 +38,7 @@ import XMonad.Layout.Decoration import XMonad.Layout.LayoutHints -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.FixedAspectRatio -- Then add it to your layout: diff --git a/XMonad/Layout/FixedColumn.hs b/XMonad/Layout/FixedColumn.hs index cd7973a1bb..0b5ca28672 100644 --- a/XMonad/Layout/FixedColumn.hs +++ b/XMonad/Layout/FixedColumn.hs @@ -28,7 +28,7 @@ import XMonad.Prelude import qualified XMonad.StackSet as W -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.FixedColumn -- diff --git a/XMonad/Layout/Gaps.hs b/XMonad/Layout/Gaps.hs index 5b24d43535..49053337bd 100644 --- a/XMonad/Layout/Gaps.hs +++ b/XMonad/Layout/Gaps.hs @@ -44,7 +44,7 @@ import XMonad.Layout.LayoutModifier import XMonad.Util.Types (Direction2D(..)) -- $usage --- You can use this module by importing it into your @~\/.xmonad\/xmonad.hs@ file: +-- You can use this module by importing it into your @xmonad.hs@ file: -- -- > import XMonad.Layout.Gaps -- diff --git a/XMonad/Layout/Grid.hs b/XMonad/Layout/Grid.hs index 3e5f44b780..b18b89d5f0 100644 --- a/XMonad/Layout/Grid.hs +++ b/XMonad/Layout/Grid.hs @@ -25,7 +25,7 @@ import XMonad import XMonad.StackSet -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.Grid -- @@ -61,7 +61,7 @@ arrange aspectRatio (Rectangle rx ry rw rh) st = zip st rectangles mincs = max 1 $ nwins `div` ncols extrs = nwins - ncols * mincs chop :: Int -> Dimension -> [(Position, Dimension)] - chop n m = ((0, m - k * fromIntegral (pred n)) :) . map (, k) . tail . reverse . take n . tail . iterate (subtract k') $ m' + chop n m = ((0, m - k * fromIntegral (pred n)) :) . map (, k) . drop 1 . reverse . take n . drop 1 . iterate (subtract k') $ m' where k :: Dimension k = m `div` fromIntegral n diff --git a/XMonad/Layout/Groups.hs b/XMonad/Layout/Groups.hs index 7eabad31d1..ff3b34f231 100644 --- a/XMonad/Layout/Groups.hs +++ b/XMonad/Layout/Groups.hs @@ -105,8 +105,8 @@ data Uniq = U Integer Integer -- seed. All keys generated with this method will be different -- provided you don't use 'gen' again with a key from the list. -- (if you need to do that, see 'split' instead) -gen :: Uniq -> (Uniq, [Uniq]) -gen (U i1 i2) = (U (i1+1) i2, map (U i1) [i2..]) +gen :: Uniq -> (Uniq, Stream Uniq) +gen (U i1 i2) = (U (i1+1) i2, fmap (U i1) (fromList [i2..])) -- | Split an infinite list into two. I ended up not -- needing this, but let's keep it just in case. @@ -197,16 +197,16 @@ instance Message GroupsMessage modifyGroups :: (Zipper (Group l a) -> Zipper (Group l a)) -> Groups l l2 a -> Groups l l2 a -modifyGroups f g = let (seed', ids) = gen (seed g) - defaultGroups = fromJust $ singletonZ $ G (ID (head ids) $ baseLayout g) emptyZ +modifyGroups f g = let (seed', ident :~ _) = gen (seed g) + defaultGroups = fromJust $ singletonZ $ G (ID ident $ baseLayout g) emptyZ in g { groups = fromMaybe defaultGroups . f . Just $ groups g , seed = seed' } modifyGroupsX :: (Zipper (Group l a) -> X (Zipper (Group l a))) -> Groups l l2 a -> X (Groups l l2 a) modifyGroupsX f g = do - let (seed', ids) = gen (seed g) - defaultGroups = fromJust $ singletonZ $ G (ID (head ids) $ baseLayout g) emptyZ + let (seed', ident :~ _) = gen (seed g) + defaultGroups = fromJust $ singletonZ $ G (ID ident $ baseLayout g) emptyZ g' <- f . Just $ groups g return g { groups = fromMaybe defaultGroups g', seed = seed' } @@ -218,12 +218,12 @@ modifyGroupsX f g = do -- other stack changes as gracefully as possible. readapt :: Eq a => Zipper a -> Groups l l2 a -> Groups l l2 a readapt z g = let mf = getFocusZ z - (seed', ids) = gen $ seed g + (seed', ident :~ _) = gen $ seed g g' = g { seed = seed' } in flip modifyGroups g' $ mapZ_ (onZipper $ removeDeleted z) >>> filterKeepLast (isJust . gZipper) >>> findNewWindows (W.integrate' z) - >>> addWindows (ID (head ids) $ baseLayout g) + >>> addWindows (ID ident $ baseLayout g) >>> focusGroup mf >>> onFocusedZ (onZipper $ focusWindow mf) where filterKeepLast _ Nothing = Nothing @@ -379,10 +379,10 @@ type ModifySpecX = forall l. WithID l Window -- | Apply a ModifySpec. applySpec :: ModifySpec -> Groups l l2 Window -> Maybe (Groups l l2 Window) applySpec f g = - let (seed', ids) = gen $ seed g - g' = flip modifyGroups g $ f (ID (head ids) $ baseLayout g) + let (seed', ident :~ ids) = gen $ seed g -- gen generates an infinite list + g' = flip modifyGroups g $ f (ID ident $ baseLayout g) >>> toTags - >>> foldr (reID g) ((tail ids, []), []) + >>> foldr (reID g) ((ids, []), []) >>> snd >>> fromTags in if groups g == groups g' @@ -391,10 +391,10 @@ applySpec f g = applySpecX :: ModifySpecX -> Groups l l2 Window -> X (Maybe (Groups l l2 Window)) applySpecX f g = do - let (seed', ids) = gen $ seed g - g' <- flip modifyGroupsX g $ f (ID (head ids) $ baseLayout g) + let (seed', ident :~ ids) = gen $ seed g -- gen generates an infinite list + g' <- flip modifyGroupsX g $ f (ID ident $ baseLayout g) >>> fmap toTags - >>> fmap (foldr (reID g) ((tail ids, []), [])) + >>> fmap (foldr (reID g) ((ids, []), [])) >>> fmap snd >>> fmap fromTags return $ if groups g == groups g' @@ -403,14 +403,13 @@ applySpecX f g = do reID :: Groups l l2 Window -> Either (Group l Window) (Group l Window) - -> (([Uniq], [Uniq]), [Either (Group l Window) (Group l Window)]) - -> (([Uniq], [Uniq]), [Either (Group l Window) (Group l Window)]) -reID _ _ (([], _), _) = undefined -- The list of ids is infinite -reID g eg ((id:ids, seen), egs) = if myID `elem` seen - then ((ids, seen), mapE_ (setID id) eg:egs) - else ((id:ids, myID:seen), eg:egs) - where myID = getID $ gLayout $ fromE eg - setID id (G (ID _ _) z) = G (ID id $ baseLayout g) z + -> ((Stream Uniq, [Uniq]), [Either (Group l Window) (Group l Window)]) + -> ((Stream Uniq, [Uniq]), [Either (Group l Window) (Group l Window)]) +reID g eg ((ident :~ ids, seen), egs) + | myID `elem` seen = ((ids, seen), mapE_ (setID ident) eg:egs) + | otherwise = ((ident :~ ids, myID:seen), eg:egs) + where myID = getID $ gLayout $ fromE eg + setID id (G (ID _ _) z) = G (ID id $ baseLayout g) z -- ** Misc. ModifySpecs diff --git a/XMonad/Layout/Groups/Examples.hs b/XMonad/Layout/Groups/Examples.hs index c0acb33344..2a88ef6d5c 100644 --- a/XMonad/Layout/Groups/Examples.hs +++ b/XMonad/Layout/Groups/Examples.hs @@ -76,7 +76,7 @@ import XMonad.Layout.Simplest -- -- > import XMonad.Layout.Groups.Examples -- --- to the top of your @.\/.xmonad\/xmonad.hs@. +-- to the top of your @xmonad.hs@. -- -- For more information on using any of the layouts, jump directly -- to its \"Example\" section. diff --git a/XMonad/Layout/Groups/Helpers.hs b/XMonad/Layout/Groups/Helpers.hs index 808d2c024a..72a8dc438e 100644 --- a/XMonad/Layout/Groups/Helpers.hs +++ b/XMonad/Layout/Groups/Helpers.hs @@ -58,7 +58,7 @@ import qualified Data.Map as M -- -- > import XMonad.Layout.Groups.Helpers -- --- to the top of your @.\/.xmonad\/xmonad.hs@. +-- to the top of your @xmonad.hs@. -- -- "XMonad.Layout.Groups"-based layouts do not have the same notion -- of window ordering as the rest of XMonad. For this reason, the usual @@ -155,7 +155,7 @@ focusHelper :: (Bool -> Bool) -- ^ if you want to focus a floating window, 'id'. -> X () focusHelper f g = withFocused $ \w -> do ws <- getWindows - let (before, tail -> after) = span (/=w) ws + let (before, drop 1 -> after) = span (/=w) ws let toFocus = g $ after ++ before floats <- getFloats case filter (f . flip elem floats) toFocus of diff --git a/XMonad/Layout/Groups/Wmii.hs b/XMonad/Layout/Groups/Wmii.hs index f434777130..709d2bdcb5 100644 --- a/XMonad/Layout/Groups/Wmii.hs +++ b/XMonad/Layout/Groups/Wmii.hs @@ -66,7 +66,7 @@ import XMonad.Layout.Simplest -- -- > import XMonad.Layout.Groups.Wmii -- --- to the top of your @.\/.xmonad\/xmonad.hs@, and adding 'wmii' +-- to the top of your @xmonad.hs@, and adding 'wmii' -- (with a 'Shrinker' and decoration 'Theme' as -- parameters) to your layout hook, for example: -- diff --git a/XMonad/Layout/Hidden.hs b/XMonad/Layout/Hidden.hs index 778fb5805f..175273a62f 100644 --- a/XMonad/Layout/Hidden.hs +++ b/XMonad/Layout/Hidden.hs @@ -37,7 +37,7 @@ import qualified XMonad.StackSet as W -------------------------------------------------------------------------------- -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.Hidden -- @@ -118,7 +118,7 @@ popHiddenWindow = sendMessage . PopSpecificHiddenWindow -------------------------------------------------------------------------------- hideWindowMsg :: HiddenWindows a -> Window -> X (Maybe (HiddenWindows a)) hideWindowMsg (HiddenWindows hidden) win = do - modify (\s -> s { windowset = W.delete' win $ windowset s }) + modifyWindowSet $ W.delete' win return . Just . HiddenWindows $ hidden ++ [win] -------------------------------------------------------------------------------- diff --git a/XMonad/Layout/HintedGrid.hs b/XMonad/Layout/HintedGrid.hs index bb84fb0920..624a958be0 100644 --- a/XMonad/Layout/HintedGrid.hs +++ b/XMonad/Layout/HintedGrid.hs @@ -36,7 +36,7 @@ infixr 9 . (.) = fmap -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.HintedGrid -- diff --git a/XMonad/Layout/HintedTile.hs b/XMonad/Layout/HintedTile.hs index 5214b283ea..c913869607 100644 --- a/XMonad/Layout/HintedTile.hs +++ b/XMonad/Layout/HintedTile.hs @@ -27,7 +27,7 @@ import qualified XMonad.StackSet as W import XMonad.Prelude -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.HintedTile -- diff --git a/XMonad/Layout/IM.hs b/XMonad/Layout/IM.hs index aa56ad8eed..69e8de1fe3 100644 --- a/XMonad/Layout/IM.hs +++ b/XMonad/Layout/IM.hs @@ -39,7 +39,7 @@ import qualified XMonad.StackSet as S import Control.Arrow (first) -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.IM -- > import Data.Ratio ((%)) diff --git a/XMonad/Layout/IfMax.hs b/XMonad/Layout/IfMax.hs index 889efddf0b..e27e804d06 100644 --- a/XMonad/Layout/IfMax.hs +++ b/XMonad/Layout/IfMax.hs @@ -36,7 +36,7 @@ import qualified XMonad.StackSet as W -- IfMax layout will run one layout if number of windows on workspace is as -- maximum N, and else will run another layout. -- --- You can use this module by adding folowing in your @xmonad.hs@: +-- You can use this module by adding following in your @xmonad.hs@: -- -- > import XMonad.Layout.IfMax -- diff --git a/XMonad/Layout/ImageButtonDecoration.hs b/XMonad/Layout/ImageButtonDecoration.hs index 15655809d5..5d14d49a4c 100644 --- a/XMonad/Layout/ImageButtonDecoration.hs +++ b/XMonad/Layout/ImageButtonDecoration.hs @@ -47,7 +47,7 @@ import XMonad.Layout.Maximize -- $usage -- You can use this module with the following in your --- @~\/.xmonad\/xmonad.hs@: +-- @xmonad.hs@: -- -- > import XMonad.Layout.ImageButtonDecoration -- diff --git a/XMonad/Layout/IndependentScreens.hs b/XMonad/Layout/IndependentScreens.hs index 5bf89491fe..49f1101975 100644 --- a/XMonad/Layout/IndependentScreens.hs +++ b/XMonad/Layout/IndependentScreens.hs @@ -42,7 +42,7 @@ import XMonad.Prelude import qualified XMonad.StackSet as W -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.IndependentScreens -- diff --git a/XMonad/Layout/LayoutBuilder.hs b/XMonad/Layout/LayoutBuilder.hs index 2e38e523af..6e98439ec5 100644 --- a/XMonad/Layout/LayoutBuilder.hs +++ b/XMonad/Layout/LayoutBuilder.hs @@ -57,14 +57,16 @@ module XMonad.Layout.LayoutBuilder ( LayoutN, ) where +import Data.Maybe (maybeToList) import XMonad import XMonad.Prelude (foldM, (<|>), isJust, fromMaybe, isNothing, listToMaybe) import qualified XMonad.StackSet as W +import XMonad.Util.Stack (zipperFocusedAtFirstOf) import XMonad.Util.WindowProperties -------------------------------------------------------------------------------- -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.LayoutBuilder -- @@ -452,11 +454,4 @@ calcArea (SubBox xpos ypos width height) rect = -------------------------------------------------------------------------------- differentiate' :: Eq q => Maybe q -> [q] -> Maybe (W.Stack q) -differentiate' _ [] = Nothing -differentiate' Nothing w = W.differentiate w -differentiate' (Just f) w - | f `elem` w = Just W.Stack { W.focus = f - , W.up = reverse $ takeWhile (/=f) w - , W.down = tail $ dropWhile (/=f) w - } - | otherwise = W.differentiate w +differentiate' = zipperFocusedAtFirstOf . maybeToList diff --git a/XMonad/Layout/LayoutCombinators.hs b/XMonad/Layout/LayoutCombinators.hs index 44e1db4472..bd01fd6489 100644 --- a/XMonad/Layout/LayoutCombinators.hs +++ b/XMonad/Layout/LayoutCombinators.hs @@ -51,7 +51,7 @@ import XMonad.Layout.Combo import XMonad.Layout.DragPane -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.LayoutCombinators -- diff --git a/XMonad/Layout/LayoutHints.hs b/XMonad/Layout/LayoutHints.hs index dbd4e7755d..1dcc740b0a 100644 --- a/XMonad/Layout/LayoutHints.hs +++ b/XMonad/Layout/LayoutHints.hs @@ -47,7 +47,7 @@ import Data.Set (Set) import qualified Data.Set as Set -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.LayoutHints -- diff --git a/XMonad/Layout/LayoutScreens.hs b/XMonad/Layout/LayoutScreens.hs index b82356a0d9..922e816135 100644 --- a/XMonad/Layout/LayoutScreens.hs +++ b/XMonad/Layout/LayoutScreens.hs @@ -36,7 +36,7 @@ import qualified XMonad.StackSet as W -- email window at all times, a crude mimic of sticky windows). -- -- You can use this module with the following in your --- @~\/.xmonad\/xmonad.hs@ file: +-- @xmonad.hs@ file: -- -- > import XMonad.Layout.LayoutScreens -- > import XMonad.Layout.TwoPane diff --git a/XMonad/Layout/LimitWindows.hs b/XMonad/Layout/LimitWindows.hs index 3a565a6f48..70b6d3dbba 100644 --- a/XMonad/Layout/LimitWindows.hs +++ b/XMonad/Layout/LimitWindows.hs @@ -44,7 +44,7 @@ import XMonad.Prelude (fromJust, guard, (<=<)) import qualified XMonad.StackSet as W -- $usage --- To use this module, add the following import to @~\/.xmonad\/xmonad.hs@: +-- To use this module, add the following import to @xmonad.hs@: -- -- > import XMonad.Layout.LimitWindows -- diff --git a/XMonad/Layout/MagicFocus.hs b/XMonad/Layout/MagicFocus.hs index d1e2e5da1a..d3952801fc 100644 --- a/XMonad/Layout/MagicFocus.hs +++ b/XMonad/Layout/MagicFocus.hs @@ -34,7 +34,7 @@ import XMonad.Prelude(All(..)) import qualified Data.Map as M -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.MagicFocus -- diff --git a/XMonad/Layout/Magnifier.hs b/XMonad/Layout/Magnifier.hs index 72a5fb02f8..e11b778d3d 100644 --- a/XMonad/Layout/Magnifier.hs +++ b/XMonad/Layout/Magnifier.hs @@ -59,7 +59,7 @@ import XMonad.Layout.LayoutModifier import XMonad.StackSet -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.Magnifier -- diff --git a/XMonad/Layout/Master.hs b/XMonad/Layout/Master.hs index 4eaabb8f16..87236d1714 100644 --- a/XMonad/Layout/Master.hs +++ b/XMonad/Layout/Master.hs @@ -31,7 +31,7 @@ import XMonad.Layout.LayoutModifier import Control.Arrow (first) -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.Master -- diff --git a/XMonad/Layout/Maximize.hs b/XMonad/Layout/Maximize.hs index 1fcb67869a..65562f83b8 100644 --- a/XMonad/Layout/Maximize.hs +++ b/XMonad/Layout/Maximize.hs @@ -31,7 +31,7 @@ import XMonad.Layout.LayoutModifier import XMonad.Prelude ( partition ) -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.Maximize -- diff --git a/XMonad/Layout/MessageControl.hs b/XMonad/Layout/MessageControl.hs index b4f8c1f406..318892ef2b 100644 --- a/XMonad/Layout/MessageControl.hs +++ b/XMonad/Layout/MessageControl.hs @@ -35,7 +35,7 @@ import XMonad.Layout.LayoutModifier (LayoutModifier(..), ModifiedLayout(..)) import Control.Arrow (second) -- $usage --- You can use this module by importing it into your @~\/.xmonad\/xmonad.hs@ file: +-- You can use this module by importing it into your @xmonad.hs@ file: -- -- > import XMonad.Layout.MessageEscape -- diff --git a/XMonad/Layout/Minimize.hs b/XMonad/Layout/Minimize.hs index b7a1d900dc..2129312826 100644 --- a/XMonad/Layout/Minimize.hs +++ b/XMonad/Layout/Minimize.hs @@ -30,7 +30,7 @@ import XMonad.Layout.BoringWindows as BW import qualified XMonad.Util.ExtensibleState as XS -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.Minimize -- diff --git a/XMonad/Layout/Monitor.hs b/XMonad/Layout/Monitor.hs index 59c50b94b5..3f69672994 100644 --- a/XMonad/Layout/Monitor.hs +++ b/XMonad/Layout/Monitor.hs @@ -40,7 +40,7 @@ import XMonad.Hooks.ManageHelpers (doHideIgnore) import XMonad.Hooks.FadeInactive (setOpacity) -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.Monitor -- diff --git a/XMonad/Layout/Mosaic.hs b/XMonad/Layout/Mosaic.hs index 6aa9298ac6..6082dce672 100644 --- a/XMonad/Layout/Mosaic.hs +++ b/XMonad/Layout/Mosaic.hs @@ -41,7 +41,7 @@ import qualified XMonad.StackSet as W import Control.Arrow(second, first) -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.Mosaic -- diff --git a/XMonad/Layout/MosaicAlt.hs b/XMonad/Layout/MosaicAlt.hs index 542a33e002..641049effc 100644 --- a/XMonad/Layout/MosaicAlt.hs +++ b/XMonad/Layout/MosaicAlt.hs @@ -38,7 +38,7 @@ import XMonad.Prelude ( sortBy ) import Data.Ratio -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.MosaicAlt -- > import qualified Data.Map as M diff --git a/XMonad/Layout/MouseResizableTile.hs b/XMonad/Layout/MouseResizableTile.hs index 848facbcc3..679c45ac2a 100644 --- a/XMonad/Layout/MouseResizableTile.hs +++ b/XMonad/Layout/MouseResizableTile.hs @@ -41,7 +41,7 @@ import XMonad.Util.XUtils import Graphics.X11 as X -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.MouseResizableTile -- diff --git a/XMonad/Layout/MultiColumns.hs b/XMonad/Layout/MultiColumns.hs index b32642aad7..f7db877ddf 100644 --- a/XMonad/Layout/MultiColumns.hs +++ b/XMonad/Layout/MultiColumns.hs @@ -29,7 +29,7 @@ import qualified XMonad.StackSet as W import XMonad.Prelude -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.MultiColumns -- @@ -96,8 +96,8 @@ instance LayoutClass MultiCol a where ,fmap incmastern (fromMessage m)] where resize Shrink = l { multiColSize = max (-0.5) $ s-ds } resize Expand = l { multiColSize = min 1 $ s+ds } - incmastern (IncMasterN x) = l { multiColNWin = take a n ++ [newval] ++ tail r } - where newval = max 0 $ head r + x + incmastern (IncMasterN x) = l { multiColNWin = take a n ++ [newval] ++ drop 1 r } + where newval = max 0 $ maybe 0 (x +) (listToMaybe r) r = drop a n n = multiColNWin l ds = multiColDeltaSize l diff --git a/XMonad/Layout/MultiDishes.hs b/XMonad/Layout/MultiDishes.hs index 85043a3672..8b76ad86c6 100644 --- a/XMonad/Layout/MultiDishes.hs +++ b/XMonad/Layout/MultiDishes.hs @@ -27,7 +27,7 @@ import XMonad.StackSet (integrate) import XMonad.Prelude (ap) -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.MultiDishes -- diff --git a/XMonad/Layout/Named.hs b/XMonad/Layout/Named.hs index 9ff20f076c..14a0bef22a 100644 --- a/XMonad/Layout/Named.hs +++ b/XMonad/Layout/Named.hs @@ -27,7 +27,7 @@ import XMonad.Layout.LayoutModifier import XMonad.Layout.Renamed -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.Named -- @@ -44,10 +44,6 @@ import XMonad.Layout.Renamed -- Note that this module has been deprecated and may be removed in a future -- release, please use "XMonad.Layout.Renamed" instead. --- | (Deprecated) Rename a layout. -named :: String -> l a -> ModifiedLayout Rename l a -named s = renamed [Replace s] - -- | (Deprecated) Remove the first word of the name. nameTail :: l a -> ModifiedLayout Rename l a nameTail = renamed [CutWordsLeft 1] diff --git a/XMonad/Layout/NoBorders.hs b/XMonad/Layout/NoBorders.hs index e51b83c7eb..a333bd7145 100644 --- a/XMonad/Layout/NoBorders.hs +++ b/XMonad/Layout/NoBorders.hs @@ -45,7 +45,7 @@ import qualified Data.Map as M -- $usage --- You can use this module with the following in your ~\/.xmonad\/xmonad.hs file: +-- You can use this module with the following in your xmonad.hs file: -- -- > import XMonad.Layout.NoBorders -- diff --git a/XMonad/Layout/NoFrillsDecoration.hs b/XMonad/Layout/NoFrillsDecoration.hs index 25cdea5f78..1cd0d596c0 100644 --- a/XMonad/Layout/NoFrillsDecoration.hs +++ b/XMonad/Layout/NoFrillsDecoration.hs @@ -31,7 +31,7 @@ import XMonad.Layout.SimpleDecoration -- $usage -- You can use this module with the following in your --- @~\/.xmonad\/xmonad.hs@: +-- @xmonad.hs@: -- -- > import XMonad.Layout.NoFrillsDecoration -- diff --git a/XMonad/Layout/OnHost.hs b/XMonad/Layout/OnHost.hs index e9456a5443..883a2ce70e 100644 --- a/XMonad/Layout/OnHost.hs +++ b/XMonad/Layout/OnHost.hs @@ -34,7 +34,7 @@ import Data.Maybe (fromMaybe) import System.Posix.Env (getEnv) -- $usage --- You can use this module by importing it into your ~\/.xmonad\/xmonad.hs file: +-- You can use this module by importing it into your @xmonad.hs@ file: -- -- > import XMonad.Layout.OnHost -- diff --git a/XMonad/Layout/OneBig.hs b/XMonad/Layout/OneBig.hs index cd1d2f3945..c3fb03d13e 100644 --- a/XMonad/Layout/OneBig.hs +++ b/XMonad/Layout/OneBig.hs @@ -55,23 +55,25 @@ oneBigMessage (OneBig cx cy) m = fmap resize (fromMessage m) -- | Main layout function oneBigLayout :: OneBig a -> Rectangle -> W.Stack a -> [(a, Rectangle)] -oneBigLayout (OneBig cx cy) rect stack = [(master,masterRect)] - ++ divideBottom bottomRect bottomWs - ++ divideRight rightRect rightWs - where ws = W.integrate stack - n = length ws - ht (Rectangle _ _ _ hh) = hh - wd (Rectangle _ _ ww _) = ww - h' = round (fromIntegral (ht rect)*cy) - w = wd rect - m = calcBottomWs n w h' - master = head ws - other = tail ws - bottomWs = take m other - rightWs = drop m other - masterRect = cmaster n m cx cy rect - bottomRect = cbottom cy rect - rightRect = cright cx cy rect +oneBigLayout (OneBig cx cy) rect stack = + let ws = W.integrate stack + n = length ws + in case ws of + [] -> [] + (master : other) -> [(master,masterRect)] + ++ divideBottom bottomRect bottomWs + ++ divideRight rightRect rightWs + where + ht (Rectangle _ _ _ hh) = hh + wd (Rectangle _ _ ww _) = ww + h' = round (fromIntegral (ht rect)*cy) + w = wd rect + m = calcBottomWs n w h' + bottomWs = take m other + rightWs = drop m other + masterRect = cmaster n m cx cy rect + bottomRect = cbottom cy rect + rightRect = cright cx cy rect -- | Calculate how many windows must be placed at bottom calcBottomWs :: Int -> Dimension -> Dimension -> Int diff --git a/XMonad/Layout/PerScreen.hs b/XMonad/Layout/PerScreen.hs index b07d7429e4..e2f034c81b 100644 --- a/XMonad/Layout/PerScreen.hs +++ b/XMonad/Layout/PerScreen.hs @@ -29,7 +29,7 @@ import qualified XMonad.StackSet as W import XMonad.Prelude (fromMaybe) -- $usage --- You can use this module by importing it into your ~\/.xmonad\/xmonad.hs file: +-- You can use this module by importing it into your @xmonad.hs@ file: -- -- > import XMonad.Layout.PerScreen -- diff --git a/XMonad/Layout/PerWorkspace.hs b/XMonad/Layout/PerWorkspace.hs index 536a676282..767bcb83a4 100644 --- a/XMonad/Layout/PerWorkspace.hs +++ b/XMonad/Layout/PerWorkspace.hs @@ -29,7 +29,7 @@ import qualified XMonad.StackSet as W import XMonad.Prelude (fromMaybe) -- $usage --- You can use this module by importing it into your ~\/.xmonad\/xmonad.hs file: +-- You can use this module by importing it into your @xmonad.hs@ file: -- -- > import XMonad.Layout.PerWorkspace -- diff --git a/XMonad/Layout/PositionStoreFloat.hs b/XMonad/Layout/PositionStoreFloat.hs index 9c7af1d572..308e83bc7e 100644 --- a/XMonad/Layout/PositionStoreFloat.hs +++ b/XMonad/Layout/PositionStoreFloat.hs @@ -33,7 +33,7 @@ import XMonad.Layout.WindowArranger import XMonad.Prelude (fromMaybe, isJust, nub, when) -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.PositionStoreFloat -- > import XMonad.Layout.NoFrillsDecoration diff --git a/XMonad/Layout/Reflect.hs b/XMonad/Layout/Reflect.hs index 06a52c1107..2819749c85 100644 --- a/XMonad/Layout/Reflect.hs +++ b/XMonad/Layout/Reflect.hs @@ -32,7 +32,7 @@ import XMonad.Layout.LayoutModifier import XMonad.Layout.MultiToggle -- $usage --- You can use this module by importing it into your @~\/.xmonad\/xmonad.hs@ file: +-- You can use this module by importing it into your @xmonad.hs@ file: -- -- > import XMonad.Layout.Reflect -- diff --git a/XMonad/Layout/Renamed.hs b/XMonad/Layout/Renamed.hs index c930840f49..3588e9118a 100644 --- a/XMonad/Layout/Renamed.hs +++ b/XMonad/Layout/Renamed.hs @@ -19,6 +19,7 @@ module XMonad.Layout.Renamed ( -- * Usage -- $usage renamed + , named , Rename(..) ) where import XMonad @@ -29,7 +30,7 @@ import XMonad.Layout.LayoutModifier -- -- > import XMonad.Layout.Renamed -- --- to your @~\/.xmonad\/xmonad.hs@. +-- to your @xmonad.hs@. -- -- You can then use 'renamed' to modify the description of your -- layouts. For example: @@ -40,6 +41,10 @@ import XMonad.Layout.LayoutModifier renamed :: [Rename a] -> l a -> ModifiedLayout Rename l a renamed = ModifiedLayout . Chain +-- | Rename a layout. (Convenience alias for @renamed [Replace s]@.) +named :: String -> l a -> ModifiedLayout Rename l a +named s = renamed [Replace s] + -- | The available renaming operations data Rename a = CutLeft Int -- ^ Remove a number of characters from the left | CutRight Int -- ^ Remove a number of characters from the right diff --git a/XMonad/Layout/ResizableThreeColumns.hs b/XMonad/Layout/ResizableThreeColumns.hs index 90eca4037f..d488ffbb13 100644 --- a/XMonad/Layout/ResizableThreeColumns.hs +++ b/XMonad/Layout/ResizableThreeColumns.hs @@ -31,7 +31,7 @@ import qualified Data.Map as M import Data.Ratio -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.ResizableThreeColumns -- diff --git a/XMonad/Layout/ResizableTile.hs b/XMonad/Layout/ResizableTile.hs index 6b06100cf2..9464f66223 100644 --- a/XMonad/Layout/ResizableTile.hs +++ b/XMonad/Layout/ResizableTile.hs @@ -27,7 +27,7 @@ import qualified XMonad.StackSet as W import qualified Data.Map as M -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.ResizableTile -- diff --git a/XMonad/Layout/ResizeScreen.hs b/XMonad/Layout/ResizeScreen.hs index 0e7984ad8b..c5c73bf1ce 100644 --- a/XMonad/Layout/ResizeScreen.hs +++ b/XMonad/Layout/ResizeScreen.hs @@ -31,7 +31,7 @@ import XMonad.Layout.Decoration -- $usage -- You can use this module by importing it into your --- @~\/.xmonad\/xmonad.hs@ file: +-- @xmonad.hs@ file: -- -- > import XMonad.Layout.ResizeScreen -- diff --git a/XMonad/Layout/Roledex.hs b/XMonad/Layout/Roledex.hs index bb4b065ff2..5b0a1ea44e 100644 --- a/XMonad/Layout/Roledex.hs +++ b/XMonad/Layout/Roledex.hs @@ -27,7 +27,7 @@ import qualified XMonad.StackSet as W import Data.Ratio -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.Roledex -- diff --git a/XMonad/Layout/ShowWName.hs b/XMonad/Layout/ShowWName.hs index 2c4562492d..62432b1968 100644 --- a/XMonad/Layout/ShowWName.hs +++ b/XMonad/Layout/ShowWName.hs @@ -35,7 +35,7 @@ import XMonad.Util.XUtils -- $usage -- You can use this module with the following in your --- @~\/.xmonad\/xmonad.hs@: +-- @xmonad.hs@: -- -- > import XMonad.Layout.ShowWName -- > myLayout = layoutHook def diff --git a/XMonad/Layout/SimpleDecoration.hs b/XMonad/Layout/SimpleDecoration.hs index 6f80ea258d..96e22d6441 100644 --- a/XMonad/Layout/SimpleDecoration.hs +++ b/XMonad/Layout/SimpleDecoration.hs @@ -32,7 +32,7 @@ import XMonad.Layout.Decoration -- $usage -- You can use this module with the following in your --- @~\/.xmonad\/xmonad.hs@: +-- @xmonad.hs@: -- -- > import XMonad.Layout.SimpleDecoration -- diff --git a/XMonad/Layout/SimpleFloat.hs b/XMonad/Layout/SimpleFloat.hs index 004a7d7a2e..4b0ba66176 100644 --- a/XMonad/Layout/SimpleFloat.hs +++ b/XMonad/Layout/SimpleFloat.hs @@ -33,7 +33,7 @@ import XMonad.Layout.WindowArranger -- $usage -- You can use this module with the following in your --- @~\/.xmonad\/xmonad.hs@: +-- @xmonad.hs@: -- -- > import XMonad.Layout.SimpleFloat -- diff --git a/XMonad/Layout/Simplest.hs b/XMonad/Layout/Simplest.hs index 7454573611..1d7a132579 100644 --- a/XMonad/Layout/Simplest.hs +++ b/XMonad/Layout/Simplest.hs @@ -26,7 +26,7 @@ import qualified XMonad.StackSet as S -- $usage -- You can use this module with the following in your --- @~\/.xmonad\/xmonad.hs@: +-- @xmonad.hs@: -- -- > import XMonad.Layout.Simplest -- diff --git a/XMonad/Layout/SimplestFloat.hs b/XMonad/Layout/SimplestFloat.hs index f9e8a15c0e..314a43ff9f 100644 --- a/XMonad/Layout/SimplestFloat.hs +++ b/XMonad/Layout/SimplestFloat.hs @@ -28,7 +28,7 @@ import XMonad.Layout.LayoutModifier -- $usage -- You can use this module with the following in your --- @~\/.xmonad\/xmonad.hs@: +-- @xmonad.hs@: -- -- > import XMonad.Layout.SimplestFloat -- diff --git a/XMonad/Layout/SortedLayout.hs b/XMonad/Layout/SortedLayout.hs index 5e85c1d056..b5fa4a3eef 100644 --- a/XMonad/Layout/SortedLayout.hs +++ b/XMonad/Layout/SortedLayout.hs @@ -33,7 +33,7 @@ import XMonad.Util.WindowProperties -- $usage -- You can use this module with the following in your --- @~\/.xmonad\/xmonad.hs@: +-- @xmonad.hs@: -- -- > import XMonad.Layout.SortedLayout -- diff --git a/XMonad/Layout/Spacing.hs b/XMonad/Layout/Spacing.hs index 08e23643e4..a14e84d413 100644 --- a/XMonad/Layout/Spacing.hs +++ b/XMonad/Layout/Spacing.hs @@ -51,7 +51,7 @@ import XMonad.Actions.MessageFeedback -- $usage --- You can use this module by importing it into your @~\/.xmonad\/xmonad.hs@ +-- You can use this module by importing it into your @xmonad.hs@ -- file: -- -- > import XMonad.Layout.Spacing diff --git a/XMonad/Layout/Spiral.hs b/XMonad/Layout/Spiral.hs index 258b6b0840..6f95c65e67 100644 --- a/XMonad/Layout/Spiral.hs +++ b/XMonad/Layout/Spiral.hs @@ -31,7 +31,7 @@ import XMonad hiding ( Rotation ) import XMonad.StackSet ( integrate ) -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.Spiral -- @@ -45,7 +45,7 @@ import XMonad.StackSet ( integrate ) -- "XMonad.Doc.Extending#Editing_the_layout_hook". fibs :: [Integer] -fibs = 1 : 1 : zipWith (+) fibs (tail fibs) +fibs = 1 : 1 : zipWith (+) fibs (drop 1 fibs) mkRatios :: [Integer] -> [Rational] mkRatios (x1:x2:xs) = (x1 % x2) : mkRatios (x2:xs) @@ -82,7 +82,7 @@ data SpiralWithDir a = SpiralWithDir Direction Rotation Rational instance LayoutClass SpiralWithDir a where pureLayout (SpiralWithDir dir rot scale) sc stack = zip ws rects where ws = integrate stack - ratios = blend scale . reverse . take (length ws - 1) . mkRatios $ tail fibs + ratios = blend scale . reverse . take (length ws - 1) . mkRatios $ drop 1 fibs rects = divideRects (zip ratios dirs) sc dirs = dropWhile (/= dir) $ case rot of CW -> cycle [East .. North] diff --git a/XMonad/Layout/Square.hs b/XMonad/Layout/Square.hs index cf10d7303a..b975ea441c 100644 --- a/XMonad/Layout/Square.hs +++ b/XMonad/Layout/Square.hs @@ -29,7 +29,7 @@ import XMonad import XMonad.StackSet ( integrate ) -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@ file: +-- You can use this module with the following in your @xmonad.hs@ file: -- -- > import XMonad.Layout.Square -- diff --git a/XMonad/Layout/StackTile.hs b/XMonad/Layout/StackTile.hs index 0bc1860596..41124522f0 100644 --- a/XMonad/Layout/StackTile.hs +++ b/XMonad/Layout/StackTile.hs @@ -27,7 +27,7 @@ import qualified XMonad.StackSet as W import XMonad.Prelude -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.StackTile -- diff --git a/XMonad/Layout/Stoppable.hs b/XMonad/Layout/Stoppable.hs index 78d8e9a8f8..74f7c62351 100644 --- a/XMonad/Layout/Stoppable.hs +++ b/XMonad/Layout/Stoppable.hs @@ -58,7 +58,7 @@ import XMonad.Layout.LayoutModifier import System.Posix.Signals -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad -- > import XMonad.Layout.Stoppable diff --git a/XMonad/Layout/SubLayouts.hs b/XMonad/Layout/SubLayouts.hs index 4955611969..d9fa13fd41 100644 --- a/XMonad/Layout/SubLayouts.hs +++ b/XMonad/Layout/SubLayouts.hs @@ -47,8 +47,6 @@ module XMonad.Layout.SubLayouts ( ) where -import XMonad.Layout.Circle () -- so haddock can find the link - import XMonad.Layout.Decoration(Decoration, DefaultShrinker) import XMonad.Layout.LayoutModifier(LayoutModifier(handleMess, modifyLayout, redoLayout), @@ -115,7 +113,7 @@ import qualified Data.Set as S -- -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.SubLayouts -- > import XMonad.Layout.WindowNavigation @@ -184,11 +182,11 @@ import qualified Data.Set as S -- [@outerLayout@] The layout that determines the rectangles given to each -- group. -- --- Ex. The second group is 'Tall', the third is 'Circle', all others are tabbed --- with: +-- Ex. The second group is 'Tall', the third is 'XMonad.Layout.CircleEx.circle', +-- all others are tabbed with: -- -- > myLayout = addTabs shrinkText def --- > $ subLayout [0,1,2] (Simplest ||| Tall 1 0.2 0.5 ||| Circle) +-- > $ subLayout [0,1,2] (Simplest ||| Tall 1 0.2 0.5 ||| circle) -- > $ Tall 1 0.2 0.5 ||| Full subLayout :: [Int] -> subl a -> l a -> ModifiedLayout (Sublayout subl) l a subLayout nextLayout sl = ModifiedLayout (Sublayout (I []) (nextLayout,sl) []) diff --git a/XMonad/Layout/TabBarDecoration.hs b/XMonad/Layout/TabBarDecoration.hs index d94a3807a0..2a2ff6df8e 100644 --- a/XMonad/Layout/TabBarDecoration.hs +++ b/XMonad/Layout/TabBarDecoration.hs @@ -31,7 +31,7 @@ import XMonad.Prompt ( XPPosition (..) ) -- $usage -- You can use this module with the following in your --- @~\/.xmonad\/xmonad.hs@: +-- @xmonad.hs@: -- -- > import XMonad.Layout.TabBarDecoration -- @@ -46,8 +46,8 @@ import XMonad.Prompt ( XPPosition (..) ) -- 'tabBar' will give you the possibility of setting a custom shrinker -- and a custom theme. -- --- The deafult theme can be dynamically change with the xmonad theme --- selector. See "XMonad.Prompt.Theme". For more themse, look at +-- The default theme can be dynamically changed with the xmonad theme +-- selector. See "XMonad.Prompt.Theme". For more themes, look at -- "XMonad.Util.Themes" -- | Add, on the top of the screen, a simple bar of tabs to a given diff --git a/XMonad/Layout/Tabbed.hs b/XMonad/Layout/Tabbed.hs index e9fd78d811..8790cbd842 100644 --- a/XMonad/Layout/Tabbed.hs +++ b/XMonad/Layout/Tabbed.hs @@ -43,7 +43,7 @@ import XMonad.Layout.Simplest ( Simplest(Simplest) ) import XMonad.Util.Types (Direction2D(..)) -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.Tabbed -- diff --git a/XMonad/Layout/TallMastersCombo.hs b/XMonad/Layout/TallMastersCombo.hs index 3a86351047..957ebc446d 100644 --- a/XMonad/Layout/TallMastersCombo.hs +++ b/XMonad/Layout/TallMastersCombo.hs @@ -1,5 +1,9 @@ -- {-# LANGUAGE PatternGuards, FlexibleContexts, FlexibleInstances, TypeSynonymInstances, MultiParamTypeClasses #-} -{-# LANGUAGE PatternGuards, FlexibleContexts, FlexibleInstances, MultiParamTypeClasses #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE PatternGuards #-} + --------------------------------------------------------------------------- -- | -- Module : XMonad.Layout.TallMastersCombo @@ -42,16 +46,17 @@ module XMonad.Layout.TallMastersCombo ( ) where import XMonad hiding (focus, (|||)) -import XMonad.Prelude (delete, find, foldM, fromMaybe, isJust) -import XMonad.StackSet (Workspace(..),integrate',Stack(..)) -import qualified XMonad.StackSet as W import qualified XMonad.Layout as LL -import XMonad.Layout.Simplest (Simplest(..)) import XMonad.Layout.Decoration +import XMonad.Layout.Simplest (Simplest (..)) +import XMonad.Prelude (delete, find, foldM, fromMaybe, isJust, listToMaybe) +import XMonad.StackSet (Stack (..), Workspace (..), integrate') +import qualified XMonad.StackSet as W +import XMonad.Util.Stack (zipperFocusedAtFirstOf) --------------------------------------------------------------------------------- -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.TallMastersCombo -- @@ -244,14 +249,14 @@ instance (GetFocused l1 Window, GetFocused l2 Window) => LayoutClass (TMSCombine return $ mergeSubLayouts mlayout1 mlayout2 (TMSCombineTwo f w1 w2 (not vsp) nmaster delta frac layout1 layout2) True | Just SwapSubMaster <- fromMessage m = -- first get the submaster window - let subMaster = if null w2 then Nothing else Just $ head w2 + let subMaster = listToMaybe w2 in case subMaster of Just mw -> do windows $ W.modify' $ swapWindow mw return Nothing Nothing -> return Nothing | Just FocusSubMaster <- fromMessage m = -- first get the submaster window - let subMaster = if null w2 then Nothing else Just $ head w2 + let subMaster = listToMaybe w2 in case subMaster of Just mw -> do windows $ W.modify' $ focusWindow mw return Nothing @@ -302,19 +307,6 @@ instance (GetFocused l1 Window, GetFocused l2 Window) => LayoutClass (TMSCombine mlayout2 <- handleMessage layout2 m return $ mergeSubLayouts mlayout1 mlayout2 i False - - --- code from CombineTwo --- given two sets of zs and xs takes the first z from zs that also belongs to xs --- and turns xs into a stack with z being current element. Acts as --- StackSet.differentiate if zs and xs don't intersect -differentiate :: Eq q => [q] -> [q] -> Maybe (Stack q) -differentiate (z:zs) xs | z `elem` xs = Just $ Stack { focus=z - , up = reverse $ takeWhile (/=z) xs - , down = tail $ dropWhile (/=z) xs } - | otherwise = differentiate zs xs -differentiate [] xs = W.differentiate xs - -- | Swap a given window with the focused window. swapWindow :: (Eq a) => a -> Stack a -> Stack a swapWindow w (Stack foc upLst downLst) @@ -388,9 +380,9 @@ splitStack f nmaster frac s = Nothing -> f snum = length slst (slst1, slst2) = splitAt nmaster slst - s0 = differentiate f' slst - s1' = differentiate f' slst1 - s2' = differentiate f' slst2 + s0 = zipperFocusedAtFirstOf f' slst + s1' = zipperFocusedAtFirstOf f' slst1 + s2' = zipperFocusedAtFirstOf f' slst2 (s1,s2,frac') | nmaster == 0 = (Nothing,s0,0) | nmaster >= snum = (s0,Nothing,1) | otherwise = (s1',s2',frac) diff --git a/XMonad/Layout/ThreeColumns.hs b/XMonad/Layout/ThreeColumns.hs index 075021ca1a..145c6d3f73 100644 --- a/XMonad/Layout/ThreeColumns.hs +++ b/XMonad/Layout/ThreeColumns.hs @@ -32,7 +32,7 @@ import qualified XMonad.StackSet as W import Data.Ratio -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.ThreeColumns -- diff --git a/XMonad/Layout/ToggleLayouts.hs b/XMonad/Layout/ToggleLayouts.hs index 71417f3767..c097d5d049 100644 --- a/XMonad/Layout/ToggleLayouts.hs +++ b/XMonad/Layout/ToggleLayouts.hs @@ -25,7 +25,7 @@ import XMonad.Prelude (fromMaybe) import XMonad.StackSet (Workspace (..)) -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.ToggleLayouts -- diff --git a/XMonad/Layout/TwoPane.hs b/XMonad/Layout/TwoPane.hs index 67bf902895..fda1d7fecf 100644 --- a/XMonad/Layout/TwoPane.hs +++ b/XMonad/Layout/TwoPane.hs @@ -27,7 +27,7 @@ import XMonad hiding (focus) import XMonad.StackSet ( focus, up, down) -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.TwoPane -- @@ -56,8 +56,8 @@ instance LayoutClass TwoPane a where handleMessage (TwoPane delta split) x = return $ case fromMessage x of - Just Shrink -> Just (TwoPane delta (split - delta)) - Just Expand -> Just (TwoPane delta (split + delta)) + Just Shrink -> Just (TwoPane delta (max 0 (split - delta))) + Just Expand -> Just (TwoPane delta (min 1 (split + delta))) _ -> Nothing description _ = "TwoPane" diff --git a/XMonad/Layout/TwoPanePersistent.hs b/XMonad/Layout/TwoPanePersistent.hs index f58984721c..271ebd9f25 100644 --- a/XMonad/Layout/TwoPanePersistent.hs +++ b/XMonad/Layout/TwoPanePersistent.hs @@ -28,7 +28,7 @@ import XMonad.StackSet (focus, up, down, Stack, Stack(..)) import XMonad hiding (focus) -- $usage --- Import the module in @~\/.xmonad\/xmonad.hs@: +-- Import the module in @xmonad.hs@: -- -- > import XMonad.Layout.TwoPanePersistent -- @@ -58,8 +58,8 @@ instance (Show a, Eq a) => LayoutClass TwoPanePersistent a where pureMessage (TwoPanePersistent w delta split) x = case fromMessage x of - Just Shrink -> Just (TwoPanePersistent w delta (split - delta)) - Just Expand -> Just (TwoPanePersistent w delta (split + delta)) + Just Shrink -> Just (TwoPanePersistent w delta (max 0 (split - delta))) + Just Expand -> Just (TwoPanePersistent w delta (min 1 (split + delta))) _ -> Nothing description _ = "TwoPanePersistent" diff --git a/XMonad/Layout/VoidBorders.hs b/XMonad/Layout/VoidBorders.hs index fa956834ad..1837926bf3 100644 --- a/XMonad/Layout/VoidBorders.hs +++ b/XMonad/Layout/VoidBorders.hs @@ -35,7 +35,7 @@ import XMonad.Layout.LayoutModifier import XMonad.StackSet (integrate) -- $usage --- You can use this module with the following in your ~\/.xmonad/xmonad.hs +-- You can use this module with the following in your @xmonad.hs@ -- file: -- -- > import XMonad.Layout.VoidBorders diff --git a/XMonad/Layout/WindowArranger.hs b/XMonad/Layout/WindowArranger.hs index dc504c8641..9ca9d65d45 100644 --- a/XMonad/Layout/WindowArranger.hs +++ b/XMonad/Layout/WindowArranger.hs @@ -35,7 +35,7 @@ import Control.Arrow ((***), (>>>), (&&&), first) -- $usage -- You can use this module with the following in your --- @~\/.xmonad\/xmonad.hs@: +-- @xmonad.hs@: -- -- > import XMonad.Layout.WindowArranger -- > myLayout = layoutHook def diff --git a/XMonad/Layout/WindowNavigation.hs b/XMonad/Layout/WindowNavigation.hs index f2ada0908d..510443029d 100644 --- a/XMonad/Layout/WindowNavigation.hs +++ b/XMonad/Layout/WindowNavigation.hs @@ -35,7 +35,7 @@ import XMonad.Util.Types (Direction2D(..)) import XMonad.Util.XUtils -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.WindowNavigation -- diff --git a/XMonad/Layout/WindowSwitcherDecoration.hs b/XMonad/Layout/WindowSwitcherDecoration.hs index 2ed17387ba..24d0417905 100644 --- a/XMonad/Layout/WindowSwitcherDecoration.hs +++ b/XMonad/Layout/WindowSwitcherDecoration.hs @@ -36,7 +36,7 @@ import Foreign.C.Types(CInt) -- $usage -- You can use this module with the following in your --- @~\/.xmonad\/xmonad.hs@: +-- @xmonad.hs@: -- -- > import XMonad.Layout.WindowSwitcherDecoration -- > import XMonad.Layout.DraggingVisualizer diff --git a/XMonad/Layout/WorkspaceDir.hs b/XMonad/Layout/WorkspaceDir.hs index a3a0b9f7de..afe235cf69 100644 --- a/XMonad/Layout/WorkspaceDir.hs +++ b/XMonad/Layout/WorkspaceDir.hs @@ -41,7 +41,7 @@ import XMonad.Layout.LayoutModifier import XMonad.StackSet ( tag, currentTag ) -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Layout.WorkspaceDir -- diff --git a/XMonad/Layout/ZoomRow.hs b/XMonad/Layout/ZoomRow.hs index bc475acdbf..c1563d7c7b 100644 --- a/XMonad/Layout/ZoomRow.hs +++ b/XMonad/Layout/ZoomRow.hs @@ -49,7 +49,7 @@ import Control.Arrow (second) -- and decreased, and a window can be set to use the whole available -- space whenever it has focus. -- --- You can use this module by including the following in your @~\/.xmonad/xmonad.hs@: +-- You can use this module by including the following in your @xmonad.hs@: -- -- > import XMonad.Layout.ZoomRow -- diff --git a/XMonad/Prelude.hs b/XMonad/Prelude.hs index 4f118a174d..21d0a4f5da 100644 --- a/XMonad/Prelude.hs +++ b/XMonad/Prelude.hs @@ -1,6 +1,9 @@ +{-# OPTIONS_GHC -Wno-dodgy-imports #-} {-# LANGUAGE BangPatterns #-} +{-# LANGUAGE InstanceSigs #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeFamilies #-} -------------------------------------------------------------------- -- | -- Module : XMonad.Prelude @@ -36,6 +39,14 @@ module XMonad.Prelude ( multimediaKeys, functionKeys, WindowScreen, + + -- * Infinite streams + Stream(..), + (+~), + cycleS, + takeS, + toList, + fromList, ) where import Foreign (alloca, peek) @@ -45,10 +56,10 @@ import Control.Applicative as Exports import Control.Monad as Exports import Data.Bool as Exports import Data.Char as Exports -import Data.Foldable as Exports +import Data.Foldable as Exports hiding (toList) import Data.Function as Exports -import Data.Functor as Exports -import Data.List as Exports +import Data.Functor as Exports hiding (unzip) +import Data.List as Exports hiding ((!?)) import Data.Maybe as Exports import Data.Monoid as Exports import Data.Traversable as Exports @@ -56,14 +67,15 @@ import Data.Traversable as Exports import qualified Data.Map.Strict as Map import Control.Arrow ((&&&), first) +import Control.Exception (SomeException, handle) import Data.Bifunctor (bimap) import Data.Bits import Data.List.NonEmpty (NonEmpty ((:|))) import Data.Tuple (swap) +import GHC.Exts (IsList(..)) import GHC.Stack import System.Directory (getHomeDirectory) import System.Environment (getEnv) -import Control.Exception (SomeException, handle) import qualified XMonad.StackSet as W -- | Short for 'fromIntegral'. @@ -465,3 +477,36 @@ multimediaKeys = filter ((/= noSymbol) . snd) . map (id &&& stringToKeysym) $ -- | The specialized 'W.Screen' derived from 'WindowSet'. type WindowScreen -- FIXME move to core = W.Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail + +-- | An infinite stream type +data Stream a = !a :~ Stream a +infixr 5 :~ + +instance Functor Stream where + fmap :: (a -> b) -> Stream a -> Stream b + fmap f = go + where go (x :~ xs) = f x :~ go xs + +instance IsList (Stream a) where + type (Item (Stream a)) = a + + fromList :: [a] -> Stream a + fromList (x : xs) = x :~ fromList xs + fromList [] = errorWithoutStackTrace "XMonad.Prelude.Stream.fromList: Can't create stream out of finite list." + + toList :: Stream a -> [a] + toList (x :~ xs) = x : toList xs + +-- | Absorb a list into an infinite stream. +(+~) :: [a] -> Stream a -> Stream a +xs +~ s = foldr (:~) s xs +infixr 5 +~ + +-- | Absorb a non-empty list into an infinite stream. +cycleS :: NonEmpty a -> Stream a +cycleS (x :| xs) = s where s = x :~ xs +~ s + +-- | @takeS n stream@ returns the first @n@ elements of @stream@; if @n < 0@, +-- this returns the empty list. +takeS :: Int -> Stream a -> [a] +takeS n = take n . toList diff --git a/XMonad/Prompt.hs b/XMonad/Prompt.hs index 07b47f2c5e..85e7f2b0ed 100644 --- a/XMonad/Prompt.hs +++ b/XMonad/Prompt.hs @@ -7,6 +7,7 @@ {-# LANGUAGE PatternGuards #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE ViewPatterns #-} +{-# LANGUAGE MultiWayIf #-} ----------------------------------------------------------------------------- -- | -- Module : XMonad.Prompt @@ -61,6 +62,7 @@ module XMonad.Prompt , defaultColor, modifyColor, setColor , resetColor, setBorderColor , modifyPrompter, setPrompter, resetPrompter + , selectedCompletion, setCurrentCompletions, getCurrentCompletions , moveWord, moveWord', killWord, killWord' , changeWord, deleteString , moveHistory, setSuccess, setDone, setModeDone @@ -98,7 +100,7 @@ module XMonad.Prompt ) where import XMonad hiding (cleanMask, config) -import XMonad.Prelude hiding (toList) +import XMonad.Prelude hiding (toList, fromList) import qualified XMonad.StackSet as W import XMonad.Util.Font import XMonad.Util.Types @@ -112,11 +114,13 @@ import Control.Monad.State import Data.Bifunctor (bimap) import Data.Bits import Data.IORef +import qualified Data.List.NonEmpty as NE import qualified Data.Map as M import Data.Set (fromList, toList) import System.IO import System.IO.Unsafe (unsafePerformIO) import System.Posix.Files +import Data.List.NonEmpty (nonEmpty) -- $usage -- For usage examples see "XMonad.Prompt.Shell", @@ -183,7 +187,8 @@ data XPConfig = -- history entries to remember , promptKeymap :: M.Map (KeyMask,KeySym) (XP ()) -- ^ Mapping from key combinations to actions - , completionKey :: (KeyMask, KeySym) -- ^ Key that should trigger completion + , completionKey :: (KeyMask, KeySym) -- ^ Key to trigger forward completion + , prevCompletionKey :: (KeyMask, KeySym) -- ^ Key to trigger backward completion , changeModeKey :: KeySym -- ^ Key to change mode (when the prompt has multiple modes) , defaultText :: String -- ^ The text by default in the prompt line , autoComplete :: Maybe Int -- ^ Just x: if only one completion remains, auto-select it, @@ -285,14 +290,14 @@ data XPPosition = Top | Bottom -- | Prompt will be placed in the center horizontally and -- in the certain place of screen vertically. If it's in the upper - -- part of the screen, completion window will be placed below(like - -- in 'Top') and otherwise above(like in 'Bottom') + -- part of the screen, completion window will be placed below (like + -- in 'Top') and otherwise above (like in 'Bottom') | CenteredAt { xpCenterY :: Rational -- ^ Rational between 0 and 1, giving -- y coordinate of center of the prompt relative to the screen height. , xpWidth :: Rational -- ^ Rational between 0 and 1, giving - -- width of the prompt relatave to the screen width. + -- width of the prompt relative to the screen width. } deriving (Show,Read) @@ -329,7 +334,8 @@ instance Default XPConfig where , borderColor = border def , promptBorderWidth = 1 , promptKeymap = defaultXPKeymap - , completionKey = (0,xK_Tab) + , completionKey = (0, xK_Tab) + , prevCompletionKey = (shiftMask, xK_Tab) , changeModeKey = xK_grave , position = Bottom , height = 18 @@ -439,7 +445,7 @@ setCommand xs s = s { commandHistory = (commandHistory s) { W.focus = xs }} setInput :: String -> XP () setInput = modify . setCommand --- | Returns the current input string. Intented for use in custom keymaps +-- | Returns the current input string. Intended for use in custom keymaps -- where 'get' or similar can't be used to retrieve it. getInput :: XP String getInput = gets command @@ -533,11 +539,11 @@ mkXPrompt t conf compl action = void $ mkXPromptWithReturn t conf compl action -- The argument supplied to the action to execute is always the current highlighted item, -- that means that this prompt overrides the value `alwaysHighlight` for its configuration to True. mkXPromptWithModes :: [XPType] -> XPConfig -> X () -mkXPromptWithModes modes conf = do - let defaultMode = head modes - modeStack = W.Stack { W.focus = defaultMode -- Current mode +mkXPromptWithModes [] _ = pure () +mkXPromptWithModes (defaultMode : modes) conf = do + let modeStack = W.Stack { W.focus = defaultMode -- Current mode , W.up = [] - , W.down = tail modes -- Other modes + , W.down = modes -- Other modes } om = XPMultipleModes modeStack st' <- mkXPromptImplementation (showXPrompt defaultMode) conf { alwaysHighlight = True } om @@ -624,6 +630,10 @@ runXP st = do type KeyStroke = (KeySym, String) +-- | Check whether the given key stroke is a modifier. +isModifier :: KeyStroke -> Bool +isModifier (_, keyString) = null keyString + -- | Main event "loop". Gives priority to events from the state's event buffer. eventLoop :: (KeyStroke -> Event -> XP ()) -> XP Bool @@ -642,11 +652,11 @@ eventLoop handle stopAction = do ks <- keycodeToKeysym d (ev_keycode ev) 0 return (ks, s, ev) else return (noSymbol, "", ev) - l -> do - modify $ \s -> s { eventBuffer = tail l } - return $ head l + (l : ls) -> do + modify $ \s -> s { eventBuffer = ls } + return l handle (keysym,keystr) event - stopAction >>= flip unless (eventLoop handle stopAction) + stopAction >>= \stop -> unless stop (eventLoop handle stopAction) -- | Default event loop stop condition. evDefaultStop :: XP Bool @@ -688,34 +698,39 @@ merely discarded, but passed to the respective application window. -- | Prompt event handler for the main loop. Dispatches to input, completion -- and mode switching handlers. handleMain :: KeyStroke -> Event -> XP () -handleMain stroke@(keysym,_) KeyEvent{ev_event_type = t, ev_state = m} = do - (compKey,modeKey) <- gets $ (completionKey &&& changeModeKey) . config - keymask <- gets cleanMask <*> pure m - -- haven't subscribed to keyRelease, so just in case - when (t == keyPress) $ - if (keymask,keysym) == compKey - then getCurrentCompletions >>= handleCompletionMain - else do - setCurrentCompletions Nothing - if keysym == modeKey - then modify setNextMode >> updateWindows - else handleInputMain keymask stroke -handleMain stroke event = handleOther stroke event - --- | Prompt input handler for the main loop. -handleInputMain :: KeyMask -> KeyStroke -> XP () -handleInputMain keymask (keysym,keystr) = do - keymap <- gets (promptKeymap . config) - case M.lookup (keymask,keysym) keymap of - -- 'null keystr' i.e. when only a modifier was pressed +handleMain stroke@(keysym, keystr) = \case + KeyEvent{ev_event_type = t, ev_state = m} -> do + (prevCompKey, (compKey, modeKey)) <- gets $ + (prevCompletionKey &&& completionKey &&& changeModeKey) . config + keymask <- gets cleanMask <*> pure m + -- haven't subscribed to keyRelease, so just in case + when (t == keyPress) $ if + | (keymask, keysym) == compKey -> + getCurrentCompletions >>= handleCompletionMain Next + | (keymask, keysym) == prevCompKey -> + getCurrentCompletions >>= handleCompletionMain Prev + | otherwise -> do + keymap <- gets (promptKeymap . config) + let mbAction = M.lookup (keymask, keysym) keymap + -- Either run when we can insert a valid character, or the + -- pressed key has an action associated to it. + unless (isModifier stroke && isNothing mbAction) $ do + setCurrentCompletions Nothing + if keysym == modeKey + then modify setNextMode >> updateWindows + else handleInput keymask mbAction + event -> handleOther stroke event + where + -- Prompt input handler for the main loop. + handleInput :: KeyMask -> Maybe (XP ()) -> XP () + handleInput keymask = \case Just action -> action >> updateWindows - Nothing -> unless (null keystr) $ - when (keymask .&. controlMask == 0) $ do - insertString $ utf8Decode keystr - updateWindows - updateHighlightedCompl - complete <- tryAutoComplete - when complete acceptSelection + Nothing -> when (keymask .&. controlMask == 0) $ do + insertString $ utf8Decode keystr + updateWindows + updateHighlightedCompl + complete <- tryAutoComplete + when complete acceptSelection -- There are two options to store the completion list during the main loop: -- * Use the State monad, with 'Nothing' as the initial state. @@ -725,17 +740,18 @@ handleInputMain keymask (keysym,keystr) = do -- -- | Prompt completion handler for the main loop. Given 'Nothing', generate the -- current completion list. With the current list, trigger a completion. -handleCompletionMain :: Maybe [String] -> XP () -handleCompletionMain Nothing = do - cs <- getCompletions - when (length cs > 1) $ - modify $ \s -> s { showComplWin = True } - setCurrentCompletions $ Just cs - handleCompletion cs -handleCompletionMain (Just cs) = handleCompletion cs - -handleCompletion :: [String] -> XP () -handleCompletion cs = do +handleCompletionMain :: Direction1D -> Maybe [String] -> XP () +handleCompletionMain dir compls = case compls of + Just cs -> handleCompletion dir cs + Nothing -> do + cs <- getCompletions + when (length cs > 1) $ + modify $ \s -> s { showComplWin = True } + setCurrentCompletions $ Just cs + handleCompletion dir cs + +handleCompletion :: Direction1D -> [String] -> XP () +handleCompletion dir cs = do alwaysHlight <- gets $ alwaysHighlight . config st <- get @@ -775,8 +791,10 @@ handleCompletion cs = do | -- We only have one suggestion, so we need to be a little -- bit smart in order to avoid a loop. - length cs == 1 = - if command st == hlCompl then put st else replaceCompletion (head cs) + Just (ch :| []) <- nonEmpty cs = + if command st == hlCompl + then put st + else replaceCompletion ch -- The current suggestion matches the command, so advance -- to the next completion and try again. @@ -790,7 +808,7 @@ handleCompletion cs = do | otherwise = replaceCompletion prevCompl where hlCompl :: String = fromMaybe (command st) $ highlightedItem st l - complIndex' :: (Int, Int) = nextComplIndex st + complIndex' :: (Int, Int) = computeComplIndex dir st nextHlCompl :: Maybe String = highlightedItem st{ complIndex = complIndex' } cs isSuffixOfCmd :: Bool = hlCompl `isSuffixOf` command st @@ -831,10 +849,10 @@ handleInputSubmap :: XP () -> KeyMask -> KeyStroke -> XP () -handleInputSubmap defaultAction keymap keymask (keysym,keystr) = +handleInputSubmap defaultAction keymap keymask stroke@(keysym, _) = case M.lookup (keymask,keysym) keymap of Just action -> action >> updateWindows - Nothing -> unless (null keystr) $ defaultAction >> updateWindows + Nothing -> unless (isModifier stroke) $ defaultAction >> updateWindows -- | Initiate a prompt input buffer event loop. Input is sent to a buffer and -- bypasses the prompt. The provided function is given the existing buffer and @@ -888,8 +906,8 @@ handleInputBuffer :: (String -> String -> (Bool,Bool)) -> KeyStroke -> Event -> XP () -handleInputBuffer f keymask (keysym,keystr) event = - unless (null keystr || keymask .&. controlMask /= 0) $ do +handleInputBuffer f keymask stroke@(keysym, keystr) event = + unless (isModifier stroke || keymask .&. controlMask /= 0) $ do (evB,inB) <- gets (eventBuffer &&& inputBuffer) let keystr' = utf8Decode keystr let (cont,keep) = f inB keystr' @@ -905,18 +923,24 @@ handleInputBuffer f keymask (keysym,keystr) event = bufferOne :: String -> String -> (Bool,Bool) bufferOne xs x = (null xs && null x,True) --- | Return the @(column, row)@ of the next highlight, or @(0, 0)@ if +-- | Return the @(column, row)@ of the desired highlight, or @(0, 0)@ if -- there is no prompt window or a wrap-around occurs. -nextComplIndex :: XPState -> (Int, Int) -nextComplIndex st = case complWinDim st of +computeComplIndex :: Direction1D -> XPState -> (Int, Int) +computeComplIndex dir st = case complWinDim st of Nothing -> (0, 0) -- no window dimensions (just destroyed or not created) Just ComplWindowDim{ cwCols, cwRows } -> - let (currentcol, currentrow) = complIndex st - (colm, rowm) = - ((currentcol + 1) `mod` length cwCols, (currentrow + 1) `mod` length cwRows) - in if rowm == currentrow + 1 - then (currentcol, currentrow + 1) -- We are not in the last row, so go down - else (colm, rowm) -- otherwise advance to the next column + if rowm == currentrow + direction + then (currentcol, rowm) -- We are not in the last row, so advance the row + else (colm, rowm) -- otherwise advance to the respective column + where + (currentcol, currentrow) = complIndex st + (colm, rowm) = + ( (currentcol + direction) `mod` length cwCols + , (currentrow + direction) `mod` length cwRows + ) + direction = case dir of + Next -> 1 + Prev -> -1 tryAutoComplete :: XP Bool tryAutoComplete = do @@ -1115,6 +1139,8 @@ vimLikeXPKeymap' fromColor promptF pasteFilter notWord = M.fromList $ , (xK_c, promptSubmap (setModeDone True) changeVimXPKeymap >> setModeDone True ) + , (xK_Return, acceptSelection) + , (xK_KP_Enter, acceptSelection) ] ++ map (first $ (,) shiftMask) [ (xK_dollar, endOfLine >> moveCursor Prev) @@ -1299,7 +1325,7 @@ deleteString d = c oc oo | oo >= length oc && d == Prev = take (oo - 1) oc | oo < length oc && d == Prev = take (oo - 1) f ++ ss - | oo < length oc && d == Next = f ++ tail ss + | oo < length oc && d == Next = f ++ drop 1 ss | otherwise = oc where (f,ss) = splitAt oo oc @@ -1380,11 +1406,11 @@ moveHistory f = do -- starting cursor character is not considered, and the cursor is placed over -- the matching character. toHeadChar :: Direction1D -> String -> XP () -toHeadChar d s = unless (null s) $ do +toHeadChar _ "" = pure () +toHeadChar d (c : _) = do cmd <- gets command off <- gets offset - let c = head s - off' = (if d == Prev then negate . fst else snd) + let off' = (if d == Prev then negate . fst else snd) . join (***) (maybe 0 (+1) . elemIndex c) . (reverse *** drop 1) $ splitAt off cmd @@ -1448,9 +1474,7 @@ redrawWindows redrawWindows emptyAction compls = do d <- gets dpy drawWin - case compls of - [] -> emptyAction - l -> redrawComplWin l + maybe emptyAction redrawComplWin (nonEmpty compls) io $ sync d False where -- | Draw the main prompt window. @@ -1469,14 +1493,14 @@ redrawWindows emptyAction compls = do io $ freePixmap dpy pm -- | Redraw the completion window, if necessary. -redrawComplWin :: [String] -> XP () +redrawComplWin :: NonEmpty String -> XP () redrawComplWin compl = do XPS{ showComplWin, complWinDim, complWin } <- get nwi <- getComplWinDim compl let recreate = do destroyComplWin w <- createComplWin nwi drawComplWin w compl - if compl /= [] && showComplWin + if showComplWin then io (readIORef complWin) >>= \case Just w -> case complWinDim of Just wi -> if nwi == wi -- complWinDim did not change @@ -1507,7 +1531,7 @@ printPrompt drw = do (preCursor, cursor, postCursor) = if offset >= length com then (str, " ","") -- add a space: it will be our cursor ;-) else let (a, b) = splitAt offset com - in (prt ++ a, [head b], tail b) + in (prt ++ a, take 1 b, drop 1 b) -- vertical and horizontal text alignment (asc, desc) <- io $ textExtentsXMF fontS str -- font ascent and descent @@ -1550,7 +1574,7 @@ destroyComplWin = do -- | Given the completions that we would like to show, calculate the -- required dimensions for the completion windows. -getComplWinDim :: [String] -> XP ComplWindowDim +getComplWinDim :: NonEmpty String -> XP ComplWindowDim getComplWinDim compl = do XPS{ config = cfg, screen = scr, fontS = fs, dpy, winWidth } <- get let -- Height of a single completion row @@ -1591,7 +1615,7 @@ getComplWinDim compl = do -- Get font ascent and descent. Coherence condition: we will print -- everything using the same font. - (asc, desc) <- io $ textExtentsXMF fs $ head compl + (asc, desc) <- io $ textExtentsXMF fs $ NE.head compl let yp = fi $ (ht + fi (asc - desc)) `div` 2 -- y position of the first row yRows = take (fi rows) [yp, yp + fi ht ..] -- y positions of all rows @@ -1601,7 +1625,7 @@ getComplWinDim compl = do pure $ ComplWindowDim x y winWidth rowHeight xCols yRows -- | Draw the completion window. -drawComplWin :: Window -> [String] -> XP () +drawComplWin :: Window -> NonEmpty String -> XP () drawComplWin w entries = do XPS{ config, color, dpy, gcon } <- get let scr = defaultScreenOfDisplay dpy @@ -1624,7 +1648,7 @@ printComplEntries -> GC -> String -- ^ Default foreground color -> String -- ^ Default background color - -> [String] -- ^ Entries to be printed... + -> NonEmpty String -- ^ Entries to be printed... -> ComplWindowDim -- ^ ...into a window of this size -> XP () printComplEntries dpy drw gc fc bc entries ComplWindowDim{ cwCols, cwRows } = do @@ -1646,7 +1670,7 @@ printComplEntries dpy drw gc fc bc entries ComplWindowDim{ cwCols, cwRows } = do where -- | Create the completion matrix to be printed. complMat :: [[String]] - = chunksOf (length cwRows) (take (length cwCols * length cwRows) entries) + = chunksOf (length cwRows) (take (length cwCols * length cwRows) (NE.toList entries)) -- | Find the column and row indexes in which a string appears. -- If the string is not in the matrix, the indices default to @(0, 0)@. @@ -1764,7 +1788,7 @@ breakAtSpace s | " \\" `isPrefixOf` s2 = (s1 ++ " " ++ s1', s2') | otherwise = (s1, s2) where (s1, s2 ) = break isSpace s - (s1',s2') = breakAtSpace $ tail s2 + (s1',s2') = breakAtSpace $ drop 1 s2 -- | 'historyCompletion' provides a canned completion function much like -- 'getShellCompl'; you pass it to mkXPrompt, and it will make completions work @@ -1792,7 +1816,8 @@ uniqSort = toList . fromList -- immediately next to each other. deleteAllDuplicates, deleteConsecutive :: [String] -> [String] deleteAllDuplicates = nub -deleteConsecutive = map head . group +deleteConsecutive = map (NE.head . notEmpty) . group +-- The elements of group will always have at least one element. newtype HistoryMatches = HistoryMatches (IORef ([String],Maybe (W.Stack String))) diff --git a/XMonad/Prompt/AppendFile.hs b/XMonad/Prompt/AppendFile.hs index 24fcabc45d..9f2b16735e 100644 --- a/XMonad/Prompt/AppendFile.hs +++ b/XMonad/Prompt/AppendFile.hs @@ -37,7 +37,7 @@ import System.IO -- $usage -- -- You can use this module by importing it, along with --- "XMonad.Prompt", into your ~\/.xmonad\/xmonad.hs file: +-- "XMonad.Prompt", into your @xmonad.hs@ file: -- -- > import XMonad.Prompt -- > import XMonad.Prompt.AppendFile diff --git a/XMonad/Prompt/DirExec.hs b/XMonad/Prompt/DirExec.hs index 5b77d4391d..d716bbe26e 100644 --- a/XMonad/Prompt/DirExec.hs +++ b/XMonad/Prompt/DirExec.hs @@ -35,7 +35,7 @@ econst :: Monad m => a -> IOException -> m a econst = const . return -- $usage --- 1. In your @~\/.xmonad\/xmonad.hs@: +-- 1. In your @xmonad.hs@: -- -- > import XMonad.Prompt.DirExec -- diff --git a/XMonad/Prompt/Email.hs b/XMonad/Prompt/Email.hs index 9455c40c5e..901f123ca8 100644 --- a/XMonad/Prompt/Email.hs +++ b/XMonad/Prompt/Email.hs @@ -31,7 +31,7 @@ import XMonad.Prompt.Input -- $usage -- -- You can use this module by importing it, along with --- "XMonad.Prompt", into your ~\/.xmonad\/xmonad.hs file: +-- "XMonad.Prompt", into your @xmonad.hs@ file: -- -- > import XMonad.Prompt -- > import XMonad.Prompt.Email diff --git a/XMonad/Prompt/Input.hs b/XMonad/Prompt/Input.hs index 9545035754..ec1180a84a 100644 --- a/XMonad/Prompt/Input.hs +++ b/XMonad/Prompt/Input.hs @@ -56,7 +56,7 @@ import XMonad.Prompt -- create an autocompleting version, like this: -- -- > firingPrompt' = inputPromptWithCompl def "Fire" --- > (mkComplFunFromList employees) ?+ fireEmployee +-- > (mkComplFunFromList def employees) ?+ fireEmployee -- -- Now all he has to do is add a keybinding to @firingPrompt@ (or -- @firingPrompt'@), such as diff --git a/XMonad/Prompt/Layout.hs b/XMonad/Prompt/Layout.hs index 79c129891f..fb52e4e173 100644 --- a/XMonad/Prompt/Layout.hs +++ b/XMonad/Prompt/Layout.hs @@ -26,7 +26,7 @@ import XMonad.Prompt.Workspace ( Wor(..) ) import XMonad.StackSet ( workspaces, layout ) -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Prompt -- > import XMonad.Prompt.Layout diff --git a/XMonad/Prompt/Man.hs b/XMonad/Prompt/Man.hs index f4df5ada92..59f5424808 100644 --- a/XMonad/Prompt/Man.hs +++ b/XMonad/Prompt/Man.hs @@ -39,7 +39,7 @@ import System.Process import qualified Control.Exception as E -- $usage --- 1. In your @~\/.xmonad\/xmonad.hs@: +-- 1. In your @xmonad.hs@: -- -- > import XMonad.Prompt -- > import XMonad.Prompt.Man diff --git a/XMonad/Prompt/OrgMode.hs b/XMonad/Prompt/OrgMode.hs index b8e62ca5c6..1745f4abdf 100644 --- a/XMonad/Prompt/OrgMode.hs +++ b/XMonad/Prompt/OrgMode.hs @@ -67,6 +67,7 @@ import XMonad.Util.XSelection (getSelection) import XMonad.Util.Run import Control.DeepSeq (deepseq) +import qualified Data.List.NonEmpty as NE (head) import Data.Time (Day (ModifiedJulianDay), NominalDiffTime, UTCTime (utctDay), addUTCTime, fromGregorian, getCurrentTime, nominalDay, toGregorian) #if MIN_VERSION_time(1, 9, 0) import Data.Time.Format.ISO8601 (iso8601Show) @@ -525,7 +526,7 @@ pInput inp = (`runParser` inp) . choice $ where go :: String -> Parser String go consumed = do - str <- munch (/= head ptn) + str <- munch (/= NE.head (notEmpty ptn)) word <- munch1 (/= ' ') bool go pure (word == ptn) $ consumed <> str <> word diff --git a/XMonad/Prompt/Pass.hs b/XMonad/Prompt/Pass.hs index 0c4734ccf4..bca8a41ded 100644 --- a/XMonad/Prompt/Pass.hs +++ b/XMonad/Prompt/Pass.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE LambdaCase #-} ----------------------------------------------------------------------------- -- | -- Module : XMonad.Prompt.Pass @@ -16,12 +17,15 @@ -- completion system provided by "XMonad.Prompt". Specifically, we -- provide -- --- - two functions to lookup passwords in the password-store: +-- - various functions to lookup passwords in the password-store: -- --- - 'passPrompt' copies the password directly to the clipboard. +-- + 'passPrompt' copies the password directly to the clipboard. -- --- - 'passTypePrompt' uses @xdotool@ to type the password --- directly. +-- + 'passOTPPrompt' copies a one-time-password to the clipboard +-- (this uses ). +-- +-- + 'passTypePrompt' and 'passOTPTypePrompt' work like the above, +-- respectively, but use @xdotool@ to type out the password. -- -- - 'passGeneratePrompt' generates a password for a given password -- label that the user inputs. @@ -64,26 +68,30 @@ module XMonad.Prompt.Pass , passGenerateAndCopyPrompt , passGenerateAndCopyPrompt' - -- * Misc + -- * One-time-passwords , passOTPPrompt + , passOTPTypePrompt ) where import System.Directory (getHomeDirectory) -import System.FilePath (combine, dropExtension, takeExtension) +import System.FilePath (dropExtension, ()) import System.Posix.Env (getEnv) -import XMonad.Core -import XMonad.Prompt ( XPrompt - , showXPrompt - , commandToComplete - , nextCompletion - , getNextCompletion - , XPConfig - , mkXPrompt - , searchPredicate) +import XMonad +import XMonad.Prelude +import XMonad.Prompt + ( XPConfig, + XPrompt, + commandToComplete, + getNextCompletion, + mkXPrompt, + nextCompletion, + searchPredicate, + showXPrompt, + ) import XMonad.Util.Run (runProcessWithInput) -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Prompt.Pass -- @@ -119,10 +127,8 @@ import XMonad.Util.Run (runProcessWithInput) -- or @man 1 pass@. -- -type Predicate = String -> String -> Bool - -getPassCompl :: [String] -> Predicate -> String -> IO [String] -getPassCompl compls p s = return $ filter (p s) compls +--------------------------------------------------------------------------------- +-- Prompt type PromptLabel = String @@ -133,28 +139,6 @@ instance XPrompt Pass where commandToComplete _ c = c nextCompletion _ = getNextCompletion --- | Default password store folder in @$HOME/.password-store@. --- -passwordStoreFolderDefault :: String -> String -passwordStoreFolderDefault home = combine home ".password-store" - --- | Compute the password store's location. --- Use the @$PASSWORD_STORE_DIR@ environment variable to set the password store. --- If empty, return the password store located in user's home. --- -passwordStoreFolder :: IO String -passwordStoreFolder = - getEnv "PASSWORD_STORE_DIR" >>= computePasswordStoreDir - where computePasswordStoreDir Nothing = fmap passwordStoreFolderDefault getHomeDirectory - computePasswordStoreDir (Just storeDir) = return storeDir - --- | A pass prompt factory. --- -mkPassPrompt :: PromptLabel -> (String -> X ()) -> XPConfig -> X () -mkPassPrompt promptLabel passwordFunction xpconfig = do - passwords <- io (passwordStoreFolder >>= getPasswords) - mkXPrompt (Pass promptLabel) xpconfig (getPassCompl passwords $ searchPredicate xpconfig) passwordFunction - -- | A prompt to retrieve a password from a given entry. -- passPrompt :: XPConfig -> X () @@ -171,6 +155,13 @@ passPrompt' s = mkPassPrompt s selectPassword passOTPPrompt :: XPConfig -> X () passOTPPrompt = mkPassPrompt "Select OTP" selectOTP +-- | A prompt to retrieve a OTP from a given entry. Note that you will +-- need to use the +-- extension for this to work. +-- +passOTPTypePrompt :: XPConfig -> X () +passOTPTypePrompt = mkPassPrompt "Select OTP" selectOTPType + -- | A prompt to generate a password for a given entry. -- This can be used to override an already stored entry. -- (Beware that no confirmation is asked) @@ -219,63 +210,104 @@ passEditPrompt = passEditPrompt' "Edit password" passEditPrompt' :: String -> XPConfig -> X () passEditPrompt' s = mkPassPrompt s editPassword +-- | A pass prompt factory. +-- +mkPassPrompt :: PromptLabel -> (String -> X ()) -> XPConfig -> X () +mkPassPrompt promptLabel passwordFunction xpconfig = do + passwords <- io (passwordStoreFolder >>= getPasswords) + mkXPrompt (Pass promptLabel) + xpconfig + (getPassCompl passwords $ searchPredicate xpconfig) + passwordFunction + where + getPassCompl :: [String] -> (String -> String -> Bool) -> String -> IO [String] + getPassCompl compls p s = return $ filter (p s) compls + + -- Compute the password store's location. Use the @$PASSWORD_STORE_DIR@ + -- environment variable to set the password store. If empty, return the + -- password store located in user's home. + passwordStoreFolder :: IO String + passwordStoreFolder = + getEnv "PASSWORD_STORE_DIR" >>= computePasswordStoreDir + where + -- Default password store folder in @$HOME/.password-store@. + computePasswordStoreDir :: Maybe String -> IO String + computePasswordStoreDir = \case + Nothing -> getHomeDirectory <&> ( ".password-store") + Just storeDir -> return storeDir + + -- Retrieve the list of passwords from the password store @passwordStoreDir@. + getPasswords :: FilePath -> IO [String] + getPasswords passwordStoreDir = do + files <- runProcessWithInput "find" [ + "-L", -- Traverse symlinks + passwordStoreDir, + "-type", "f", + "-name", "*.gpg", + "-printf", "%P\n"] [] + return . map dropExtension $ lines files + +--------------------------------------------------------------------------------- +-- Selecting a password + -- | Select a password. -- selectPassword :: String -> X () -selectPassword passLabel = spawn $ "pass --clip \"" ++ escapeQuote passLabel ++ "\"" +selectPassword = spawn . pass "--clip" --- | Select an OTP. +-- | Select a one-time-password and copy it to the clipboard. -- selectOTP :: String -> X () -selectOTP passLabel = spawn $ "pass otp --clip \"" ++ escapeQuote passLabel ++ "\"" +selectOTP = spawn . pass "otp --clip" + +-- | Select a one-time-password and type it out. +-- +selectOTPType :: String -> X () +selectOTPType = spawn . typeString . pass "otp" -- | Generate a 30 characters password for a given entry. -- If the entry already exists, it is updated with a new password. -- generatePassword :: String -> X () -generatePassword passLabel = spawn $ "pass generate --force \"" ++ escapeQuote passLabel ++ "\" 30" +generatePassword passLabel = spawn $ pass "generate --force" passLabel ++ " 30" -- | Generate a 30 characters password for a given entry. -- If the entry already exists, it is updated with a new password. -- After generating the password, it is copied to the clipboard. -- generateAndCopyPassword :: String -> X () -generateAndCopyPassword passLabel = spawn $ "pass generate --force -c \"" ++ escapeQuote passLabel ++ "\" 30" +generateAndCopyPassword passLabel = spawn $ pass "generate --force -c" passLabel ++ " 30" -- | Remove a password stored for a given entry. -- removePassword :: String -> X () -removePassword passLabel = spawn $ "pass rm --force \"" ++ escapeQuote passLabel ++ "\"" +removePassword = spawn . pass "rm --force" -- | Edit a password stored for a given entry. -- editPassword :: String -> X () -editPassword passLabel = spawn $ "pass edit \"" ++ escapeQuote passLabel ++ "\"" +editPassword = spawn . pass "edit" -- | Type a password stored for a given entry using xdotool. -- typePassword :: String -> X () -typePassword passLabel = spawn $ "pass \"" ++ escapeQuote passLabel - ++ "\"|head -n1|tr -d '\n'|xdotool type --clearmodifiers --file -" - -escapeQuote :: String -> String -escapeQuote = concatMap escape - where escape :: Char -> String - escape '"' = "\\\"" - escape x = [x] - --- | Retrieve the list of passwords from the password store 'passwordStoreDir' --- -getPasswords :: FilePath -> IO [String] -getPasswords passwordStoreDir = do - files <- runProcessWithInput "find" [ - "-L", -- Traverse symlinks - passwordStoreDir, - "-type", "f", - "-name", "*.gpg", - "-printf", "%P\n"] [] - return . map removeGpgExtension $ lines files - -removeGpgExtension :: String -> String -removeGpgExtension file | takeExtension file == ".gpg" = dropExtension file - | otherwise = file +typePassword = spawn . typeString . pass "" + +-- | Type the given string with @xdotool@. +-- +-- >>> typeString (pass "" "arXiv") +-- "pass \"arXiv\" | head -n1 | tr -d '\n' | xdotool type --clearmodifiers --file -" +typeString :: String -> String +typeString cmd = cmd ++ " | head -n1 | tr -d '\n' | xdotool type --clearmodifiers --file -" + +-- | Generate a pass prompt. +-- +-- >>> pass "otp" "\\n'git'\"hub\"" +-- "pass otp \"\\\\n'git'\\\"hub\\\"\"" +pass :: String -> String -> String +pass cmd label = concat ["pass ", cmd, " \"", concatMap escape label, "\""] + where + escape :: Char -> String + escape '"' = "\\\"" + escape '\\' = "\\\\" + escape x = [x] diff --git a/XMonad/Prompt/RunOrRaise.hs b/XMonad/Prompt/RunOrRaise.hs index 9618d2b2fe..87c516962d 100644 --- a/XMonad/Prompt/RunOrRaise.hs +++ b/XMonad/Prompt/RunOrRaise.hs @@ -22,7 +22,7 @@ module XMonad.Prompt.RunOrRaise ) where import XMonad hiding (config) -import XMonad.Prelude (isNothing, isSuffixOf, liftA2) +import XMonad.Prelude import XMonad.Prompt import XMonad.Prompt.Shell import XMonad.Actions.WindowGo (runOrRaise) @@ -35,7 +35,7 @@ econst :: Monad m => a -> IOException -> m a econst = const . return {- $usage -1. In your @~\/.xmonad\/xmonad.hs@: +1. In your @xmonad.hs@: > import XMonad.Prompt > import XMonad.Prompt.RunOrRaise diff --git a/XMonad/Prompt/Shell.hs b/XMonad/Prompt/Shell.hs index 000babbfb8..2c1af000d9 100644 --- a/XMonad/Prompt/Shell.hs +++ b/XMonad/Prompt/Shell.hs @@ -52,7 +52,7 @@ econst :: Monad m => a -> IOException -> m a econst = const . return {- $usage -1. In your @~\/.xmonad\/xmonad.hs@: +1. In your @xmonad.hs@: > import XMonad.Prompt > import XMonad.Prompt.Shell @@ -197,7 +197,7 @@ getCommands = do p <- getEnv "PATH" `E.catch` econst [] let ds = filter (/= "") $ split ':' p es <- forM ds $ \d -> getDirectoryContents d `E.catch` econst [] - return . uniqSort . filter ((/= '.') . head) . concat $ es + return . uniqSort . filter (not . ("." `isPrefixOf`)) . concat $ es split :: Eq a => a -> [a] -> [[a]] split _ [] = [] diff --git a/XMonad/Prompt/Ssh.hs b/XMonad/Prompt/Ssh.hs index 02f209b3e8..5934c44983 100644 --- a/XMonad/Prompt/Ssh.hs +++ b/XMonad/Prompt/Ssh.hs @@ -33,7 +33,7 @@ econst :: Monad m => a -> IOException -> m a econst = const . return -- $usage --- 1. In your @~\/.xmonad\/xmonad.hs@: +-- 1. In your @xmonad.hs@: -- -- > import XMonad.Prompt -- > import XMonad.Prompt.Ssh diff --git a/XMonad/Prompt/Theme.hs b/XMonad/Prompt/Theme.hs index 92efacd0bc..76cb2e068b 100644 --- a/XMonad/Prompt/Theme.hs +++ b/XMonad/Prompt/Theme.hs @@ -29,7 +29,7 @@ import XMonad.Util.Themes -- $usage -- You can use this module with the following in your --- @~\/.xmonad\/xmonad.hs@: +-- @xmonad.hs@: -- -- > import XMonad.Prompt -- > import XMonad.Prompt.Theme diff --git a/XMonad/Prompt/Unicode.hs b/XMonad/Prompt/Unicode.hs index d3c365aa02..5be9c27e89 100644 --- a/XMonad/Prompt/Unicode.hs +++ b/XMonad/Prompt/Unicode.hs @@ -52,7 +52,7 @@ instance ExtensionClass UnicodeData where {- $usage You can use this module by importing it, along with -"XMonad.Prompt", into your ~\/.xmonad\/xmonad.hs file: +"XMonad.Prompt", into your @xmonad.hs@ file: > import XMonad.Prompt > import XMonad.Prompt.Unicode @@ -61,6 +61,11 @@ and adding an appropriate keybinding, for example: > , ((modm .|. controlMask, xK_u), unicodePrompt "/path/to/unicode-data" def) +A path to a @UnicodeData.txt@ file or equivalent must be provided. This file +should be available through your package manager; search for @unicode-data@. +If no package is found, one may opt to download this file directly from +[unicode.org](http://www.unicode.org/Public/UNIDATA/UnicodeData.txt). + More flexibility is given by the @mkUnicodePrompt@ function, which takes a command and a list of arguments to pass as its first two arguments. See @unicodePrompt@ for details. diff --git a/XMonad/Prompt/Window.hs b/XMonad/Prompt/Window.hs index fa0678b645..7fcb2d6fb8 100644 --- a/XMonad/Prompt/Window.hs +++ b/XMonad/Prompt/Window.hs @@ -44,7 +44,7 @@ import XMonad.Util.NamedWindows -- where you left your XChat. It also offers helpers to build the -- subset of windows which is used for the prompt completion. -- --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Prompt -- > import XMonad.Prompt.Window diff --git a/XMonad/Prompt/Workspace.hs b/XMonad/Prompt/Workspace.hs index 3e03c6de35..4ebb6cf818 100644 --- a/XMonad/Prompt/Workspace.hs +++ b/XMonad/Prompt/Workspace.hs @@ -28,7 +28,7 @@ import XMonad.StackSet ( workspaces, tag ) import XMonad.Util.WorkspaceCompare ( getSortByIndex ) -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Prompt -- > import XMonad.Prompt.Workspace diff --git a/XMonad/Prompt/XMonad.hs b/XMonad/Prompt/XMonad.hs index 7b0a907e82..1bc975c0f2 100644 --- a/XMonad/Prompt/XMonad.hs +++ b/XMonad/Prompt/XMonad.hs @@ -28,7 +28,7 @@ import XMonad.Actions.Commands (defaultCommands) import XMonad.Prelude (fromMaybe) -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad.Prompt -- > import XMonad.Prompt.XMonad diff --git a/XMonad/Prompt/Zsh.hs b/XMonad/Prompt/Zsh.hs index 5f65c0a0d6..f205bd255a 100644 --- a/XMonad/Prompt/Zsh.hs +++ b/XMonad/Prompt/Zsh.hs @@ -28,7 +28,7 @@ import XMonad.Util.Run {- $usage 1. Grab the @capture.zsh@ script to capture zsh completions from -2. In your @~\/.xmonad\/xmonad.hs@: +2. In your @xmonad.hs@: > import XMonad.Prompt > import XMonad.Prompt.Zsh diff --git a/XMonad/Util/CustomKeys.hs b/XMonad/Util/CustomKeys.hs index e45a334a42..eb7ecd1970 100644 --- a/XMonad/Util/CustomKeys.hs +++ b/XMonad/Util/CustomKeys.hs @@ -25,7 +25,7 @@ import qualified Data.Map as M -- $usage -- --- In @~\/.xmonad\/xmonad.hs@ add: +-- In @xmonad.hs@ add: -- -- > import XMonad.Util.CustomKeys -- diff --git a/XMonad/Util/DebugWindow.hs b/XMonad/Util/DebugWindow.hs index b0b72b8e44..7b5fdfc555 100644 --- a/XMonad/Util/DebugWindow.hs +++ b/XMonad/Util/DebugWindow.hs @@ -57,9 +57,7 @@ debugWindow w = do \s -> if null s then Nothing else let (w'',s'') = break (== '\NUL') s - s' = if null s'' - then s'' - else tail s'' + s' = drop 1 s'' in Just (w'',s') t <- catchX' (wrap <$> getEWMHTitle "VISIBLE" w) $ catchX' (wrap <$> getEWMHTitle "" w) $ @@ -202,7 +200,7 @@ windowType d w ts = do Just s'' -> s'' _ -> '<':show a ++ ">" unAtoms as (t ++ (if i then ' ':s else s)) True - + simplify :: String -> Atom -> X String simplify pfx a = do s' <- io $ getAtomName d a @@ -214,10 +212,10 @@ windowType d w ts = do return s -- note that above it says this checks all of them before simplifying. - -- I'll do that after I'm confident this works as intended. + -- I'll do that after I'm confident this works as intended. windowState :: [Atom] -> X String windowState [] = return "" windowState as' = go as' ";" where go [] t = return t - go (a:as) t = simplify "_NET_WM_STATE_" a >>= \t' -> go as (t ++ ' ':t') + go (a:as) t = simplify "_NET_WM_STATE_" a >>= \t' -> go as (t ++ ' ':t') diff --git a/XMonad/Util/EZConfig.hs b/XMonad/Util/EZConfig.hs index d69a92ab13..1b3aa369db 100644 --- a/XMonad/Util/EZConfig.hs +++ b/XMonad/Util/EZConfig.hs @@ -1,5 +1,6 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE TupleSections #-} +{-# LANGUAGE ScopedTypeVariables #-} -------------------------------------------------------------------- -- | -- Module : XMonad.Util.EZConfig @@ -51,11 +52,13 @@ import XMonad.Util.NamedActions import XMonad.Util.Parser import Control.Arrow (first, (&&&)) +import qualified Data.List.NonEmpty as NE import qualified Data.Map as M import Data.Ord (comparing) +import Data.List.NonEmpty (nonEmpty) -- $usage --- To use this module, first import it into your @~\/.xmonad\/xmonad.hs@: +-- To use this module, first import it into your @xmonad.hs@: -- -- > import XMonad.Util.EZConfig -- @@ -139,8 +142,8 @@ remapKeysP conf keyList = keyList' :: XConfig Layout -> [(String, X ())] keyList' cnf = mapMaybe (traverse (\s -> case readKeySequence cnf s of - Just [ks] -> keys conf cnf M.!? ks - _ -> Nothing)) + Just (ks :| []) -> keys conf cnf M.!? ks + _ -> Nothing)) keyList infixl 4 `remapKeysP` @@ -426,35 +429,40 @@ mkNamedKeymap c = mkNamedSubmaps . readKeymap c -- | Given a list of pairs of parsed key sequences and actions, -- group them into submaps in the appropriate way. -mkNamedSubmaps :: [([(KeyMask, KeySym)], NamedAction)] -> [((KeyMask, KeySym), NamedAction)] +mkNamedSubmaps :: [(NonEmpty (KeyMask, KeySym), NamedAction)] -> [((KeyMask, KeySym), NamedAction)] mkNamedSubmaps = mkSubmaps' submapName -mkSubmaps :: [ ([(KeyMask,KeySym)], X ()) ] -> [((KeyMask, KeySym), X ())] +mkSubmaps :: [ (NonEmpty (KeyMask, KeySym), X ()) ] -> [((KeyMask, KeySym), X ())] mkSubmaps = mkSubmaps' $ submap . M.fromList -mkSubmaps' :: (Ord a) => ([(a, c)] -> c) -> [([a], c)] -> [(a, c)] +mkSubmaps' :: forall a b. (Ord a) => ([(a, b)] -> b) -> [(NonEmpty a, b)] -> [(a, b)] mkSubmaps' subm binds = map combine gathered - where gathered = groupBy fstKey - . sortBy (comparing fst) - $ binds - combine [([k],act)] = (k,act) - combine ks = (head . fst . head $ ks, - subm . mkSubmaps' subm $ map (first tail) ks) - fstKey = (==) `on` (head . fst) + where + gathered :: [[(NonEmpty a, b)]] + gathered = groupBy fstKey . sortBy (comparing fst) $ binds + + combine :: [(NonEmpty a, b)] -> (a, b) + combine [(k :| [], act)] = (k, act) + combine ks = ( NE.head . fst . NE.head . notEmpty $ ks + , subm . mkSubmaps' subm $ map (first (notEmpty . NE.drop 1)) ks + ) + + fstKey :: (NonEmpty a, b) -> (NonEmpty a, b) -> Bool + fstKey = (==) `on` (NE.head . fst) -- | Given a configuration record and a list of (key sequence -- description, action) pairs, parse the key sequences into lists of -- @(KeyMask,KeySym)@ pairs. Key sequences which fail to parse will -- be ignored. -readKeymap :: XConfig l -> [(String, t)] -> [([(KeyMask, KeySym)], t)] +readKeymap :: XConfig l -> [(String, t)] -> [(NonEmpty (KeyMask, KeySym), t)] readKeymap c = mapMaybe (maybeKeys . first (readKeySequence c)) where maybeKeys (Nothing,_) = Nothing maybeKeys (Just k, act) = Just (k, act) -- | Parse a sequence of keys, returning Nothing if there is -- a parse failure (no parse, or ambiguous parse). -readKeySequence :: XConfig l -> String -> Maybe [(KeyMask, KeySym)] -readKeySequence c = runParser (parseKeySequence c <* eof) +readKeySequence :: XConfig l -> String -> Maybe (NonEmpty (KeyMask, KeySym)) +readKeySequence c = nonEmpty <=< runParser (parseKeySequence c <* eof) -- | Parse a sequence of key combinations separated by spaces, e.g. -- @\"M-c x C-S-2\"@ (mod+c, x, ctrl+shift+2). @@ -544,7 +552,7 @@ doKeymapCheck :: XConfig l -> [(String,a)] -> ([String], [String]) doKeymapCheck conf km = (bad,dups) where ks = map ((readKeySequence conf &&& id) . fst) km bad = nub . map snd . filter (isNothing . fst) $ ks - dups = map (snd . head) + dups = map (snd . NE.head . notEmpty) . filter ((>1) . length) . groupBy ((==) `on` fst) . sortBy (comparing fst) diff --git a/XMonad/Util/ExclusiveScratchpads.hs b/XMonad/Util/ExclusiveScratchpads.hs index 05da0fd6fd..76e67bcfb1 100644 --- a/XMonad/Util/ExclusiveScratchpads.hs +++ b/XMonad/Util/ExclusiveScratchpads.hs @@ -39,13 +39,14 @@ module XMonad.Util.ExclusiveScratchpads customFloating ) where -import XMonad.Prelude (appEndo, filterM, liftA2, (<=<)) +import XMonad.Prelude import XMonad import XMonad.Actions.Minimize import XMonad.Actions.TagWindows (addTag,delTag) import XMonad.Hooks.ManageHelpers (doRectFloat,isInProperty) import qualified XMonad.StackSet as W +import qualified Data.List.NonEmpty as NE -- $usage -- @@ -53,7 +54,7 @@ import qualified XMonad.StackSet as W -- "XMonad.Layout.Minimize", please refer to the documentation of these modules for more -- information on how to configure them. -- --- To use this module, put the following in your @~\/.xmonad\/xmonad.hs@: +-- To use this module, put the following in your @xmonad.hs@: -- -- > import XMonad.Utils.ExclusiveScratchpads -- > import XMonad.ManageHook (title,appName) @@ -174,8 +175,8 @@ resetExclusiveSp xs = withFocused $ \w -> whenX (isScratchpad xs w) $ do let ys = filterM (flip runQuery w . query) xs unlessX (null <$> ys) $ do - mh <- head . map hook <$> ys -- ys /= [], so `head` is fine - n <- head . map name <$> ys -- same + mh <- NE.head . notEmpty . map hook <$> ys -- ys /= [], so `head` is fine + n <- NE.head . notEmpty . map name <$> ys -- same (windows . appEndo <=< runQuery mh) w hideOthers xs n diff --git a/XMonad/Util/Image.hs b/XMonad/Util/Image.hs index 361a898571..f0dc3fe4a1 100644 --- a/XMonad/Util/Image.hs +++ b/XMonad/Util/Image.hs @@ -22,7 +22,8 @@ module XMonad.Util.Image ) where import XMonad -import XMonad.Util.Font (stringToPixel,fi) +import XMonad.Prelude +import XMonad.Util.Font (stringToPixel) -- | Placement of the icon in the title bar data Placement = OffsetLeft Int Int -- ^ An exact amount of pixels from the upper left corner @@ -42,7 +43,7 @@ data Placement = OffsetLeft Int Int -- ^ An exact amount of pixels from the up -- | Gets the ('width', 'height') of an image imageDims :: [[Bool]] -> (Int, Int) -imageDims img = (length (head img), length img) +imageDims img = (length (fromMaybe [] (listToMaybe img)), length img) -- | Return the 'x' and 'y' positions inside a 'Rectangle' to start drawing -- the image given its 'Placement' diff --git a/XMonad/Util/Loggers.hs b/XMonad/Util/Loggers.hs index 8b8d9c23cc..a338496ce0 100644 --- a/XMonad/Util/Loggers.hs +++ b/XMonad/Util/Loggers.hs @@ -74,7 +74,7 @@ econst :: Monad m => a -> IOException -> m a econst = const . return -- $usage --- Use this module by importing it into your @~\/.xmonad\/xmonad.hs@: +-- Use this module by importing it into your @xmonad.hs@: -- -- > import XMonad.Util.Loggers -- @@ -217,7 +217,7 @@ logTitlesOnScreen' sid (TitlesFormat formatFoc formatUnfoc formatUrg) = -- | Like 'logTitlesOnScreen', but directly use the "focused" screen -- (the one with the currently focused workspace). logTitles :: (String -> String) -> (String -> String) -> Logger -logTitles formatFoc formatUnfoc = +logTitles formatFoc formatUnfoc = logWindowInfoFocusedScreen fetchWindowTitle formatFoc formatUnfoc formatUnfoc -- | Variant of 'logTitles', but with support for urgent windows. @@ -293,11 +293,11 @@ instance Default ClassnamesFormat where } -- | Internal function to get the specified window information for all windows on --- the visible workspace of the given screen and format them according to the +-- the visible workspace of the given screen and format them according to the -- given functions. -logWindowInfoOnScreen +logWindowInfoOnScreen :: (Window -> X String) - -> ScreenId + -> ScreenId -> (String -> String) -> (String -> String) -> (String -> String) @@ -312,10 +312,10 @@ logWindowInfoOnScreen getWindowInfo sid formatFoc formatUnfoc formatUrg = | otherwise -> formatUnfoc name -- | Internal helper function for 'logWindowInfoOnScreen'. -logWindowInfoOnScreenWorker - :: (Window -> X String) - -> WindowScreen - -> (Window -> String -> String) +logWindowInfoOnScreenWorker + :: (Window -> X String) + -> WindowScreen + -> (Window -> String -> String) -> Logger logWindowInfoOnScreenWorker getWindowInfo screen logger = do let wins = maybe [] W.integrate . W.stack . W.workspace $ screen @@ -396,7 +396,7 @@ logTitleOnScreen = logWindowInfoFocusedWindowOnScreen fetchWindowTitle logClassnameOnScreen :: ScreenId -> Logger logClassnameOnScreen = logWindowInfoFocusedWindowOnScreen fetchWindowClassname --- | Internal function to get the specified information for the focused window, +-- | Internal function to get the specified information for the focused window, -- on the given screen. logWindowInfoFocusedWindowOnScreen :: (Window -> X String) -> ScreenId -> Logger logWindowInfoFocusedWindowOnScreen getWindowInfo = diff --git a/XMonad/Util/NamedScratchpad.hs b/XMonad/Util/NamedScratchpad.hs index 61a533827b..c49caa7fbc 100644 --- a/XMonad/Util/NamedScratchpad.hs +++ b/XMonad/Util/NamedScratchpad.hs @@ -31,6 +31,7 @@ module XMonad.Util.NamedScratchpad ( allNamedScratchpadAction, namedScratchpadManageHook, nsHideOnFocusLoss, + nsSingleScratchpadPerWorkspace, -- * Dynamic Scratchpads -- $dynamic-scratchpads @@ -61,7 +62,7 @@ import XMonad.Actions.TagWindows (addTag, delTag) import XMonad.Hooks.ManageHelpers (doRectFloat) import XMonad.Hooks.RefocusLast (withRecentsIn) import XMonad.Hooks.StatusBar.PP (PP, ppSort) -import XMonad.Prelude (appEndo, filterM, findM, foldl', for_, unless, void, when, (<=<)) +import XMonad.Prelude (appEndo, filterM, findM, foldl', for_, liftA2, unless, void, when, (<=<)) import qualified Data.List.NonEmpty as NE import qualified Data.Map.Strict as Map @@ -284,19 +285,58 @@ allNamedScratchpadAction = someNamedScratchpadAction mapM_ runApplication -- > -- enable hiding for all of @myScratchpads@ -- > } nsHideOnFocusLoss :: NamedScratchpads -> X () -nsHideOnFocusLoss scratches = withWindowSet $ \winSet -> do +nsHideOnFocusLoss scratches = + nsHideOnCondition $ \ lastFocus _curFoc _ws hideScratch -> + whenX (isNSP lastFocus scratches) $ + hideScratch lastFocus + +-- | A @logHook@ to have only one active scratchpad on a workspace. This can +-- be useful when working with multiple floating scratchpads which would +-- otherwise be stacked. Note that this also requires you to use the +-- 'XMonad.Hooks.RefocusLast.refocusLastLogHook'. +-- +-- ==== __Example__ +-- +-- > import XMonad.Hooks.RefocusLast (refocusLastLogHook) +-- > import XMonad.Util.NamedScratchpad +-- > +-- > main = xmonad $ def +-- > { logHook = refocusLastLogHook +-- > >> nsHideOnNewScratchpad myScratchpads +-- > -- enable hiding for all of @myScratchpads@ +-- > } +nsSingleScratchpadPerWorkspace :: NamedScratchpads -> X () +nsSingleScratchpadPerWorkspace scratches = + nsHideOnCondition $ \ _lastFocus curFocus winSet hideScratch -> do + allScratchesButCurrent <- + filterM (liftA2 (<||>) (pure . (/= curFocus)) (`isNSP` scratches)) + (W.index winSet) + whenX (isNSP curFocus scratches) $ + for_ allScratchesButCurrent hideScratch + +-- | Hide scratchpads according to some condition. See 'nsHideOnFocusLoss' and +-- 'nsSingleScratchpadPerWorkspace' for usage examples. +nsHideOnCondition + :: ( Window -- Last focus. + -> Window -- Current focus. + -> WindowSet -- Current windowset. + -> (Window -> X ()) -- A function to hide the named scratchpad. + -> X ()) + -> X () +nsHideOnCondition cond = withWindowSet $ \winSet -> do let cur = W.currentTag winSet withRecentsIn cur () $ \lastFocus curFocus -> do - let isWorthy = - -- Check for the window being on the current workspace; if there - -- is no history (i.e., curFocus ≡ lastFocus), don't do anything - -- because the potential scratchpad is definitely focused. - lastFocus `elem` W.index winSet && lastFocus /= curFocus - -- Don't do anything on the NSP workspace, lest the world explodes. - && cur /= scratchpadWorkspaceTag + let hideScratch :: Window -> X () + hideScratch win = shiftToNSP (W.workspaces winSet) ($ win) + isWorthy = + -- Check for the window being on the current workspace; if there + -- is no history (i.e., curFocus ≡ lastFocus), don't do anything + -- because the potential scratchpad is definitely focused. + lastFocus `elem` W.index winSet && lastFocus /= curFocus + -- Don't do anything on the NSP workspace, lest the world explodes. + && cur /= scratchpadWorkspaceTag when isWorthy $ - whenX (isNSP lastFocus scratches) $ - shiftToNSP (W.workspaces winSet) ($ lastFocus) + cond lastFocus curFocus winSet hideScratch -- | Execute some action on a named scratchpad. -- diff --git a/XMonad/Util/Paste.hs b/XMonad/Util/Paste.hs index d0c8eb2f60..7820e0df77 100644 --- a/XMonad/Util/Paste.hs +++ b/XMonad/Util/Paste.hs @@ -35,7 +35,7 @@ import XMonad.Util.Parser (runParser) {- $usage -Import this module into your xmonad.hs as usual: +Import this module into your @xmonad.hs@ as usual: > import XMonad.Util.Paste diff --git a/XMonad/Util/Rectangle.hs b/XMonad/Util/Rectangle.hs index 6f20cc5424..5b8cf22ebc 100644 --- a/XMonad/Util/Rectangle.hs +++ b/XMonad/Util/Rectangle.hs @@ -67,7 +67,7 @@ data PointRectangle a = PointRectangle -- @[N,N+1]@, as though each real-valued coordinate had been rounded (either -- down or up) to the nearest integers. So each pixel, from zero, is listed as: -- @[0,0]@, @[1,1]@, @[2,2]@, and so on. Rather than a coordinate system, this --- considers pixels as row/colum indices. While easiest to reason with, +-- considers pixels as row/column indices. While easiest to reason with, -- indices are unable to represent zero-dimension rectangles. -- -- Consider pixels as indices. Do not use this on empty rectangles. diff --git a/XMonad/Util/RemoteWindows.hs b/XMonad/Util/RemoteWindows.hs index 450625e72f..1f9a725ac3 100644 --- a/XMonad/Util/RemoteWindows.hs +++ b/XMonad/Util/RemoteWindows.hs @@ -44,7 +44,7 @@ import XMonad.Prelude import System.Posix.Env -- $usage --- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@: +-- You can use this module with the following in your @xmonad.hs@: -- -- > import XMonad -- > import XMonad.Util.RemoteWindows diff --git a/XMonad/Util/Stack.hs b/XMonad/Util/Stack.hs index 0ef69a93a2..75f09e2864 100644 --- a/XMonad/Util/Stack.hs +++ b/XMonad/Util/Stack.hs @@ -27,6 +27,7 @@ module XMonad.Util.Stack ( -- * Usage , toIndex , fromTags , toTags + , zipperFocusedAtFirstOf -- * 'Zipper' manipulation functions -- ** Insertion, movement @@ -123,6 +124,18 @@ toTags Nothing = [] toTags (Just s) = map Left (reverse . W.up $ s) ++ [Right . W.focus $ s] ++ map Left (W.down s) +-- | @differentiate zs xs@ takes the first @z@ from @z2 that also belongs to +-- @xs@ and turns @xs@ into a stack with @z@ being the current element. Acts +-- as 'XMonad.StackSet.differentiate' if @zs@ and @xs@ don't intersect. +zipperFocusedAtFirstOf :: Eq q => [q] -> [q] -> Zipper q +zipperFocusedAtFirstOf [] xs = W.differentiate xs +zipperFocusedAtFirstOf (z : zs) xs + | z `elem` xs = Just $ + W.Stack { W.focus = z + , W.up = reverse $ takeWhile (/= z) xs + , W.down = drop 1 $ dropWhile (/= z) xs + } + | otherwise = zipperFocusedAtFirstOf zs xs -- * Zipper functions @@ -162,20 +175,20 @@ focusUpZ :: Zipper a -> Zipper a focusUpZ Nothing = Nothing focusUpZ (Just s) | u:up <- W.up s = Just $ W.Stack u up (W.focus s:W.down s) focusUpZ (Just s) | null $ W.down s = Just s -focusUpZ (Just (W.Stack f _ down)) = Just $ W.Stack (last down) (tail (reverse down) ++ [f]) [] +focusUpZ (Just (W.Stack f _ down)) = Just $ W.Stack (last down) (drop 1 (reverse down) ++ [f]) [] -- | Move the focus to the next element focusDownZ :: Zipper a -> Zipper a focusDownZ Nothing = Nothing focusDownZ (Just s) | d:down <- W.down s = Just $ W.Stack d (W.focus s:W.up s) down focusDownZ (Just s) | null $ W.up s = Just s -focusDownZ (Just (W.Stack f up _)) = Just $ W.Stack (last up) [] (tail (reverse up) ++ [f]) +focusDownZ (Just (W.Stack f up _)) = Just $ W.Stack (last up) [] (drop 1 (reverse up) ++ [f]) -- | Move the focus to the first element focusMasterZ :: Zipper a -> Zipper a focusMasterZ Nothing = Nothing focusMasterZ (Just (W.Stack f up down)) | not $ null up - = Just $ W.Stack (last up) [] (tail (reverse up) ++ [f] ++ down) + = Just $ W.Stack (last up) [] (drop 1 (reverse up) ++ [f] ++ down) focusMasterZ (Just s) = Just s -- | Refocus a @Stack a@ on an element satisfying the predicate, or fail to diff --git a/XMonad/Util/Timer.hs b/XMonad/Util/Timer.hs index 342f942264..2654163b72 100644 --- a/XMonad/Util/Timer.hs +++ b/XMonad/Util/Timer.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE MultiWayIf #-} ----------------------------------------------------------------------------- -- | -- Module : XMonad.Util.Timer @@ -20,9 +21,10 @@ module XMonad.Util.Timer , TimerId ) where -import XMonad import Control.Concurrent import Data.Unique +import XMonad +import XMonad.Prelude (listToMaybe) -- $usage -- This module can be used to setup a timer to handle deferred events. @@ -53,7 +55,6 @@ handleTimer :: TimerId -> Event -> X (Maybe a) -> X (Maybe a) handleTimer ti ClientMessageEvent{ev_message_type = mt, ev_data = dt} action = do d <- asks display a <- io $ internAtom d "XMONAD_TIMER" False - if mt == a && dt /= [] && fromIntegral (head dt) == ti - then action - else return Nothing + if | mt == a, Just dth <- listToMaybe dt, fromIntegral dth == ti -> action + | otherwise -> return Nothing handleTimer _ _ _ = return Nothing diff --git a/XMonad/Util/Ungrab.hs b/XMonad/Util/Ungrab.hs index 9a7a35ef34..c58094762a 100644 --- a/XMonad/Util/Ungrab.hs +++ b/XMonad/Util/Ungrab.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} ----------------------------------------------------------------------------- -- | -- Module : XMonad.Util.Ungrab @@ -13,12 +14,15 @@ -- ----------------------------------------------------------------------------- -module XMonad.Util.Ungrab +module XMonad.Util.Ungrab {-# DEPRECATED "Use XMonad.Operations.unGrab instead" #-} ( -- * Usage: -- $usage unGrab ) where +#if MIN_VERSION_xmonad(0, 17, 9) +import XMonad.Operations (unGrab) +#else import Graphics.X11.Xlib (sync) import Graphics.X11.Xlib.Extras (currentTime) import Graphics.X11.Xlib.Misc (ungrabKeyboard, ungrabPointer) @@ -43,3 +47,4 @@ import XMonad.Core -- | Release xmonad's keyboard grab, so other grabbers can do their thing. unGrab :: X () unGrab = withDisplay $ \d -> io (ungrabKeyboard d currentTime >> ungrabPointer d currentTime >> sync d False) +#endif diff --git a/scripts/build/build-with-cabal.sh b/scripts/build/build-with-cabal.sh index 713926f154..dac18cf409 100755 --- a/scripts/build/build-with-cabal.sh +++ b/scripts/build/build-with-cabal.sh @@ -15,7 +15,7 @@ output="$1" if [ "$SRC_DIR" = "" ]; then # look for the config directory, fall back to the old one - SRC_DIR="${XMONAD_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config/xmonad}}" + SRC_DIR="${XMONAD_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/xmonad}" if test -f "$SRC_DIR/build"; then : else diff --git a/stack.yaml b/stack.yaml index 039c865768..72ff7add97 100644 --- a/stack.yaml +++ b/stack.yaml @@ -1,7 +1,7 @@ # This stack.yaml is used to build xmonad-contrib with released versions # of X11 and xmonad -resolver: lts-21.6 +resolver: lts-21.12 packages: - ./ diff --git a/tests/Selective.hs b/tests/Selective.hs index c9df5ddfe5..1842f7dcf5 100644 --- a/tests/Selective.hs +++ b/tests/Selective.hs @@ -34,14 +34,14 @@ prop_select_focus sel (stk :: Stack Int) = focus stk == focus (select sel' stk) prop_select_increasing :: Selection l -> Stack Int -> Bool prop_select_increasing sel (stk :: Stack Int) = let res = integrate $ select sel stk - in and . zipWith (<) res $ tail res + in and . zipWith (<) res $ drop 1 res -- selection has the form [0..l] ++ [m..n] -- relies on the Arbitrary instance for Stack Int generating stacks like [0..k] prop_select_two_consec :: Selection l -> Stack Int -> Bool prop_select_two_consec sel (stk :: Stack Int) = let wins = integrate $ select sel stk - in (length . filter not . zipWith ((==) . (+1)) wins $ tail wins) <= 1 + in (length . filter not . zipWith ((==) . (+1)) wins $ drop 1 wins) <= 1 -- update preserves invariants on selections prop_update_nm :: Selection l -> Stack Int -> Bool diff --git a/xmonad-contrib.cabal b/xmonad-contrib.cabal index 339ef5249c..eb1d007035 100644 --- a/xmonad-contrib.cabal +++ b/xmonad-contrib.cabal @@ -1,5 +1,5 @@ name: xmonad-contrib -version: 0.17.1.9 +version: 0.18.0.9 -- ^ also update cpp-options: -DXMONAD_CONTRIB_VERSION_* homepage: https://xmonad.org/ @@ -38,7 +38,7 @@ cabal-version: 1.12 build-type: Simple bug-reports: https://github.com/xmonad/xmonad-contrib/issues -tested-with: GHC == 8.6.5 || == 8.8.4 || == 8.10.7 || == 9.0.2 || == 9.2.8 || == 9.4.5 || == 9.6.2 +tested-with: GHC == 8.6.5 || == 8.8.4 || == 8.10.7 || == 9.0.2 || == 9.2.8 || == 9.4.8 || == 9.6.4 || == 9.8.2 source-repository head type: git @@ -55,8 +55,8 @@ flag pedantic library build-depends: base >= 4.11 && < 5, - bytestring >= 0.10 && < 0.12, - containers >= 0.5 && < 0.7, + bytestring >= 0.10 && < 0.13, + containers >= 0.5 && < 0.8, directory, filepath, time >= 1.8 && < 1.13, @@ -65,14 +65,14 @@ library mtl >= 1 && < 3, unix, X11 >= 1.10 && < 1.11, - xmonad >= 0.16.99999 && < 0.18, + xmonad >= 0.16.99999 && < 0.19, utf8-string, deepseq default-language: Haskell2010 cpp-options: -DXMONAD_CONTRIB_VERSION_MAJOR=0 - -DXMONAD_CONTRIB_VERSION_MINOR=17 - -DXMONAD_CONTRIB_VERSION_PATCH=1 + -DXMONAD_CONTRIB_VERSION_MINOR=18 + -DXMONAD_CONTRIB_VERSION_PATCH=0 ghc-options: -Wall -Wno-unused-do-bind if flag(pedantic) @@ -130,6 +130,7 @@ library XMonad.Actions.PhysicalScreens XMonad.Actions.Plane XMonad.Actions.Prefix + XMonad.Actions.Profiles XMonad.Actions.Promote XMonad.Actions.RandomBackground XMonad.Actions.RepeatAction @@ -232,11 +233,21 @@ library XMonad.Layout.CenteredIfSingle XMonad.Layout.CenteredMaster XMonad.Layout.Circle + XMonad.Layout.CircleEx XMonad.Layout.Column XMonad.Layout.Combo XMonad.Layout.ComboP XMonad.Layout.Cross XMonad.Layout.Decoration + XMonad.Layout.DecorationEx + XMonad.Layout.DecorationEx.Common + XMonad.Layout.DecorationEx.Engine + XMonad.Layout.DecorationEx.Geometry + XMonad.Layout.DecorationEx.Widgets + XMonad.Layout.DecorationEx.LayoutModifier + XMonad.Layout.DecorationEx.TextEngine + XMonad.Layout.DecorationEx.DwmGeometry + XMonad.Layout.DecorationEx.TabbedGeometry XMonad.Layout.DecorationAddons XMonad.Layout.DecorationMadness XMonad.Layout.Dishes @@ -467,7 +478,7 @@ test-suite tests build-depends: base , QuickCheck >= 2 , X11 >= 1.10 && < 1.11 - , bytestring >= 0.10 && < 0.12 + , bytestring >= 0.10 && < 0.13 , containers , directory , time >= 1.8 && < 1.13 @@ -478,7 +489,7 @@ test-suite tests , unix , utf8-string , deepseq - , xmonad >= 0.16.9999 && < 0.18 + , xmonad >= 0.16.9999 && < 0.19 cpp-options: -DTESTING ghc-options: -Wall -Wno-unused-do-bind default-language: Haskell2010 @@ -491,3 +502,6 @@ test-suite tests -- don't treat unused-imports warning as errors, they may be necessary -- for compatibility with older versions of base (or other deps) ghc-options: -Wwarn=unused-imports + + if impl(ghc > 9.8) + ghc-options: -Wno-x-partial