mirror of
https://github.com/xmonad/xmonad-contrib.git
synced 2025-05-19 03:20:21 -07:00
Compare commits
406 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
00832bf5e6 | ||
|
410b34f074 | ||
|
15dd45be0e | ||
|
f7451b9378 | ||
|
849208d1b8 | ||
|
4b86621051 | ||
|
18eb8aca94 | ||
|
a84f3e8540 | ||
|
bd81961a63 | ||
|
209839f3ca | ||
|
50e7dd4262 | ||
|
e2cdc0cc2c | ||
|
68da8c44ba | ||
|
0517c94960 | ||
|
b1bf33d6eb | ||
|
195a0ac3c0 | ||
|
b470de0d75 | ||
|
87585a6884 | ||
|
41f1d1434c | ||
|
ddcce31597 | ||
|
4496b4f2d5 | ||
|
5119626269 | ||
|
27c86d0dda | ||
|
58dbf59cab | ||
|
9d457a73ce | ||
|
f1f392cd01 | ||
|
6c1441d9db | ||
|
6a6d913dee | ||
|
4fc3642fa2 | ||
|
7614f94d92 | ||
|
c7061b0d73 | ||
|
6df1044265 | ||
|
619a347f3f | ||
|
2b11459496 | ||
|
beabe75dda | ||
|
0404372fd3 | ||
|
c0a5bc5f0f | ||
|
1f13bb2468 | ||
|
d4473946d4 | ||
|
55e1adde4c | ||
|
de01015af5 | ||
|
7f0f0ad498 | ||
|
195537e97e | ||
|
d9e54c1b96 | ||
|
b570ab1a74 | ||
|
0dc879698d | ||
|
fe826ca8db | ||
|
d19ea051d4 | ||
|
c5032a43fb | ||
|
61f8b4aa8e | ||
|
60fc830e2e | ||
|
f97ce867ac | ||
|
2f42d2e7b4 | ||
|
b454f1e0be | ||
|
5680205c72 | ||
|
1c5261d65a | ||
|
2c161ff670 | ||
|
2ec4bbc833 | ||
|
42340e0f76 | ||
|
4350936ba5 | ||
|
2973c283ae | ||
|
a96a2031f6 | ||
|
1e5fcb1216 | ||
|
6bc6bf8abd | ||
|
c98715623d | ||
|
b3c249434d | ||
|
d0d9d42761 | ||
|
e203096143 | ||
|
6811b9e296 | ||
|
f5f99c8abf | ||
|
d41f36fa5c | ||
|
e4e04aa017 | ||
|
ec5c751b35 | ||
|
eb7268451c | ||
|
0dcecb41c5 | ||
|
5d285ced1f | ||
|
91e59c3651 | ||
|
5f82296536 | ||
|
e8029cb51d | ||
|
1211a709dc | ||
|
66d334f4cd | ||
|
55f4c4ff1b | ||
|
bbbf5c3b44 | ||
|
b3ca4362af | ||
|
5325ca8902 | ||
|
0bc28ac473 | ||
|
077b4ff34b | ||
|
7109b0ce8f | ||
|
b57212cc18 | ||
|
da3e4bef33 | ||
|
d014d7ac84 | ||
|
a88b5aa58d | ||
|
58f956c29f | ||
|
7ac0f44db4 | ||
|
d6ea38e7be | ||
|
c8c5cc1838 | ||
|
7210251138 | ||
|
7dc39c7154 | ||
|
6fece17d3d | ||
|
8e34c2f745 | ||
|
02f124cf4b | ||
|
840ede1e9e | ||
|
8fb1973e05 | ||
|
abef527f73 | ||
|
cbdee7db6f | ||
|
0622ed11ed | ||
|
700507fcd0 | ||
|
ca5e70ffc4 | ||
|
67472aa307 | ||
|
c33efbbefd | ||
|
2b77997259 | ||
|
3839c8bce9 | ||
|
8efff53a06 | ||
|
cab938f07b | ||
|
f3d936ef97 | ||
|
aff212654d | ||
|
6e43da8598 | ||
|
fcd2f60226 | ||
|
51926854d9 | ||
|
0d2b68374c | ||
|
933cb57b90 | ||
|
ebe1b9b036 | ||
|
d691d25d1c | ||
|
8ac84079a2 | ||
|
7aa2ff6798 | ||
|
21a75bfeb4 | ||
|
78bad11578 | ||
|
51ee223ec3 | ||
|
2b079bf9fb | ||
|
94bccd3e16 | ||
|
35ded4259b | ||
|
e735339b75 | ||
|
de5ef6cabd | ||
|
da5566d59f | ||
|
82191700e6 | ||
|
ced5b7abfc | ||
|
ca8e9ce722 | ||
|
5ce04d6664 | ||
|
bfe2f5b3f9 | ||
|
c8dff5e2dc | ||
|
aec21860ba | ||
|
93ad0ef2ea | ||
|
ae5949657b | ||
|
dda929dfc5 | ||
|
a84cec9b2d | ||
|
1d8305d515 | ||
|
e963382d62 | ||
|
e6dae98c44 | ||
|
7843d4dd28 | ||
|
9217ac18e9 | ||
|
c65ea8d3ab | ||
|
d54d37e344 | ||
|
903e50a65c | ||
|
f3ee6289ec | ||
|
318ee92e61 | ||
|
a5fb7e021a | ||
|
09e37131ca | ||
|
39310a6695 | ||
|
14e80f2f74 | ||
|
2272691ada | ||
|
4bb6a2eedb | ||
|
7cb6b0f87b | ||
|
385d7e9579 | ||
|
99b24f314b | ||
|
9f51b2a1d6 | ||
|
82e4260084 | ||
|
5ceaed8126 | ||
|
cd32cac54b | ||
|
b1b3c4c469 | ||
|
c01cd3a33b | ||
|
4f2a5c7f43 | ||
|
19edf5a7a8 | ||
|
75d67cfb1d | ||
|
9609e0ef2e | ||
|
3613d4d543 | ||
|
d54a7e21dd | ||
|
0fe948feb3 | ||
|
3c329e0aad | ||
|
92a7c030b2 | ||
|
e75eb16a93 | ||
|
00993d46fa | ||
|
5c30cadaf6 | ||
|
22372abaab | ||
|
a34287dda6 | ||
|
34d55ede85 | ||
|
7ce7ddf9e6 | ||
|
7464b36afa | ||
|
45dd67433c | ||
|
ff8d4ebb94 | ||
|
f9d98e0936 | ||
|
ad28ae1daa | ||
|
192c0da831 | ||
|
e38e19edc1 | ||
|
8035db1bd0 | ||
|
da566d63e6 | ||
|
c176a752de | ||
|
532d64a0f9 | ||
|
487b6d1677 | ||
|
a9ba19fb24 | ||
|
105e529826 | ||
|
42179b8625 | ||
|
d668e4cb10 | ||
|
f654082c5b | ||
|
e1dc2a3750 | ||
|
105cbe0362 | ||
|
9c4325f3ef | ||
|
46a26487ba | ||
|
7680ebb93b | ||
|
c3d16bfa99 | ||
|
7599c898ef | ||
|
8ee129483a | ||
|
52a40f376c | ||
|
c8c81474a2 | ||
|
8c0ca8b9fe | ||
|
3cd1b066a2 | ||
|
ba5011b874 | ||
|
60867bdd2d | ||
|
8f8730b222 | ||
|
7854f7766e | ||
|
fde30fc073 | ||
|
431ba22e3c | ||
|
570bd17a51 | ||
|
4c0d3cac8d | ||
|
a379850f50 | ||
|
7bb1f0b887 | ||
|
4f6a9deaf8 | ||
|
32ff4db11c | ||
|
ac9265e774 | ||
|
c9a096ac1f | ||
|
51ba23b776 | ||
|
b358c90a73 | ||
|
000cd14799 | ||
|
0e2d3c159d | ||
|
63a758243a | ||
|
2df26cf9f8 | ||
|
6912eca829 | ||
|
348df92b89 | ||
|
1b7fd6d8c9 | ||
|
0ca3ce7a18 | ||
|
bf41a85d9c | ||
|
a0bfa8a447 | ||
|
7062b75ea9 | ||
|
34b544b7a1 | ||
|
5b5a51787c | ||
|
4d0f3ee944 | ||
|
e02400b1c7 | ||
|
4a60866ea4 | ||
|
4bdcab8bf6 | ||
|
f2ac181616 | ||
|
b6c0dd9cfc | ||
|
611317f398 | ||
|
ad48cdc2cb | ||
|
1486d076dc | ||
|
1243bbbded | ||
|
cf00d2d237 | ||
|
87ae269b82 | ||
|
673de11ca8 | ||
|
02bd9eb0c0 | ||
|
d86f9ade22 | ||
|
2dcc3a92e7 | ||
|
815a595b46 | ||
|
1d84db959c | ||
|
f127f71c91 | ||
|
fb63987ac8 | ||
|
635711e994 | ||
|
4929da0eac | ||
|
e60805bd45 | ||
|
49edaf37fd | ||
|
5324f53501 | ||
|
4a97716d59 | ||
|
6063855a3e | ||
|
1bcdbc9072 | ||
|
2dd3c614e2 | ||
|
6b19388139 | ||
|
a52e646cc1 | ||
|
29f0e03256 | ||
|
726e887239 | ||
|
6fba80168d | ||
|
90f4a96f93 | ||
|
33c1e24288 | ||
|
49904ec4d3 | ||
|
339dbbf2fd | ||
|
6caba97b34 | ||
|
0afbbf9129 | ||
|
df1fc2d334 | ||
|
7665b67ff4 | ||
|
a790c816d2 | ||
|
b9263ad17e | ||
|
8f0912a674 | ||
|
dd7855da3d | ||
|
e85f0151b2 | ||
|
840d740366 | ||
|
a5bd5d96ca | ||
|
4e8857ecee | ||
|
cf13f8f9a7 | ||
|
e2ffa533da | ||
|
6b20dbca42 | ||
|
6117a867d9 | ||
|
f7e9c0cf0d | ||
|
e0be851074 | ||
|
55855ccb5f | ||
|
4df2036187 | ||
|
4998e946cc | ||
|
04c0ddd3e0 | ||
|
b7afb0c0bc | ||
|
18eca4e8d6 | ||
|
797fd24112 | ||
|
cf0e3531f9 | ||
|
8cd09601b1 | ||
|
0bd43c072f | ||
|
10c1a93963 | ||
|
58e7f6d3c3 | ||
|
502f6a0e75 | ||
|
35e434ed85 | ||
|
b96bb908db | ||
|
5d0013ef53 | ||
|
0f69215fc8 | ||
|
57b5055858 | ||
|
af5fced79a | ||
|
d7499d1db0 | ||
|
233a05908d | ||
|
e406e27139 | ||
|
1c6ae39fc9 | ||
|
b627306772 | ||
|
8b4560dc1e | ||
|
608a8e4b88 | ||
|
6373dc41fa | ||
|
8cb789af39 | ||
|
cf975d082e | ||
|
e0d1f177ea | ||
|
bb9d6edad6 | ||
|
ab6299a488 | ||
|
226629977b | ||
|
f059829f03 | ||
|
49e9570f12 | ||
|
48a6d34f55 | ||
|
ca866229f6 | ||
|
d301affabb | ||
|
a97a1acf87 | ||
|
bdb13e2551 | ||
|
3d0502f7b6 | ||
|
6880417911 | ||
|
c4dca3592e | ||
|
4242fa02a0 | ||
|
f81b9fb725 | ||
|
b0b8e4fb2c | ||
|
7312d6f3f3 | ||
|
b75ed7295c | ||
|
c27a1f0791 | ||
|
eaab5bfbba | ||
|
fce5558b62 | ||
|
2ab520eeda | ||
|
cd95bf9c28 | ||
|
b9c8294045 | ||
|
2ffc5de6cf | ||
|
08071706ba | ||
|
f55502f723 | ||
|
7cee191516 | ||
|
dd26fcc3f1 | ||
|
571d017b82 | ||
|
570eb8ccb8 | ||
|
cca2ccfc71 | ||
|
41d6ac96d5 | ||
|
4c8edd3bfb | ||
|
3d65a6bf72 | ||
|
05c4c776af | ||
|
2b1a15c9e5 | ||
|
0bef428f8f | ||
|
4d7ae81f7a | ||
|
7d4f0aaece | ||
|
4734551c76 | ||
|
865c63a361 | ||
|
c3ea62c972 | ||
|
005951535b | ||
|
ecd052b7fd | ||
|
e15f2d17e5 | ||
|
9e55ae9184 | ||
|
b9794e6a13 | ||
|
1e3b49f064 | ||
|
f02b3a9869 | ||
|
3bf9d80c40 | ||
|
4a8bd762af | ||
|
d28c0a2425 | ||
|
6f49a9394f | ||
|
287b8bf95f | ||
|
d8a23d47bf | ||
|
16701c2df2 | ||
|
6ab4d9c0bc | ||
|
24f11362c7 | ||
|
a267fed24f | ||
|
a44df170f4 | ||
|
2ebbe57bc2 | ||
|
ea16598a78 | ||
|
4a3f8eb032 | ||
|
b0fc55499d | ||
|
ae652b40f4 | ||
|
8a6542f6b3 | ||
|
5378e93e15 | ||
|
cedb8d7c78 | ||
|
9809b2013f | ||
|
9b6bef7e9d | ||
|
fd5970fb34 | ||
|
b0ca330d08 | ||
|
edf8e2526d | ||
|
d0d222e974 | ||
|
c688975625 |
6
.github/dependabot.yml
vendored
Normal file
6
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
34
.github/workflows/haskell-ci-hackage.patch
vendored
34
.github/workflows/haskell-ci-hackage.patch
vendored
@ -37,33 +37,43 @@ 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.4
|
||||
setup-method: ghcup
|
||||
allow-failure: false
|
||||
+ upload: true
|
||||
- compiler: ghc-8.10.7
|
||||
- compiler: ghc-9.6.7
|
||||
compilerKind: ghc
|
||||
compilerVersion: 8.10.7
|
||||
@@ -209,8 +217,80 @@
|
||||
${CABAL} -vnormal check
|
||||
compilerVersion: 9.6.7
|
||||
@@ -257,6 +265,10 @@
|
||||
- name: haddock
|
||||
run: |
|
||||
- $CABAL v2-haddock $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
|
||||
$CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks all
|
||||
+ - name: upload artifacts (sdist)
|
||||
@@ -267,3 +279,80 @@
|
||||
with:
|
||||
key: ${{ runner.os }}-${{ matrix.compiler }}-${{ github.sha }}
|
||||
path: ~/.cabal/store
|
||||
+ # must be separate artifacts because GitHub Actions are still broken:
|
||||
+ # https://github.com/actions/upload-artifact/issues/441
|
||||
+ # https://github.com/actions/upload-artifact/issues/457
|
||||
+ - name: upload artifact (sdist)
|
||||
+ if: matrix.upload
|
||||
+ uses: actions/upload-artifact@v2
|
||||
+ uses: actions/upload-artifact@v4
|
||||
+ with:
|
||||
+ name: sdist
|
||||
+ path: ${{ github.workspace }}/sdist/*.tar.gz
|
||||
+ - name: upload artifacts (haddock)
|
||||
+ - name: upload artifact (haddock)
|
||||
+ if: matrix.upload
|
||||
+ uses: actions/upload-artifact@v2
|
||||
+ uses: actions/upload-artifact@v4
|
||||
+ with:
|
||||
+ name: haddock
|
||||
+ path: ${{ github.workspace }}/haddock/*-docs.tar.gz
|
||||
+ - name: hackage upload (candidate)
|
||||
+ if: matrix.upload && github.event_name == 'workflow_dispatch' && github.event.inputs.version != ''
|
||||
|
157
.github/workflows/haskell-ci.yml
vendored
157
.github/workflows/haskell-ci.yml
vendored
@ -8,9 +8,9 @@
|
||||
#
|
||||
# For more information, see https://github.com/haskell-CI/haskell-ci
|
||||
#
|
||||
# version: 0.14.3
|
||||
# version: 0.19.20250506
|
||||
#
|
||||
# REGENDATA ("0.14.3",["github","cabal.project"])
|
||||
# REGENDATA ("0.19.20250506",["github","cabal.project"])
|
||||
#
|
||||
name: Haskell-CI
|
||||
on:
|
||||
@ -26,18 +26,44 @@ on:
|
||||
jobs:
|
||||
linux:
|
||||
name: Haskell-CI - Linux - ${{ matrix.compiler }}
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes:
|
||||
60
|
||||
container:
|
||||
image: buildpack-deps:bionic
|
||||
image: buildpack-deps:jammy
|
||||
continue-on-error: ${{ matrix.allow-failure }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- compiler: ghc-9.2.2
|
||||
- compiler: ghc-9.12.2
|
||||
compilerKind: ghc
|
||||
compilerVersion: 9.2.2
|
||||
compilerVersion: 9.12.2
|
||||
setup-method: ghcup
|
||||
allow-failure: false
|
||||
- compiler: ghc-9.10.2
|
||||
compilerKind: ghc
|
||||
compilerVersion: 9.10.2
|
||||
setup-method: ghcup
|
||||
allow-failure: false
|
||||
- compiler: ghc-9.8.4
|
||||
compilerKind: ghc
|
||||
compilerVersion: 9.8.4
|
||||
setup-method: ghcup
|
||||
allow-failure: false
|
||||
upload: true
|
||||
- compiler: ghc-9.6.7
|
||||
compilerKind: ghc
|
||||
compilerVersion: 9.6.7
|
||||
setup-method: ghcup
|
||||
allow-failure: false
|
||||
- compiler: ghc-9.4.8
|
||||
compilerKind: ghc
|
||||
compilerVersion: 9.4.8
|
||||
setup-method: ghcup
|
||||
allow-failure: false
|
||||
- compiler: ghc-9.2.8
|
||||
compilerKind: ghc
|
||||
compilerVersion: 9.2.8
|
||||
setup-method: ghcup
|
||||
allow-failure: false
|
||||
- compiler: ghc-9.0.2
|
||||
@ -45,7 +71,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
|
||||
@ -54,41 +79,34 @@ jobs:
|
||||
- compiler: ghc-8.8.4
|
||||
compilerKind: ghc
|
||||
compilerVersion: 8.8.4
|
||||
setup-method: hvr-ppa
|
||||
allow-failure: false
|
||||
- compiler: ghc-8.6.5
|
||||
compilerKind: ghc
|
||||
compilerVersion: 8.6.5
|
||||
setup-method: hvr-ppa
|
||||
allow-failure: false
|
||||
- compiler: ghc-8.4.4
|
||||
compilerKind: ghc
|
||||
compilerVersion: 8.4.4
|
||||
setup-method: hvr-ppa
|
||||
setup-method: ghcup
|
||||
allow-failure: false
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: apt
|
||||
- name: apt-get install
|
||||
run: |
|
||||
apt-get update
|
||||
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.17.5/x86_64-linux-ghcup-0.1.17.5 > "$HOME/.ghcup/bin/ghcup"
|
||||
chmod a+x "$HOME/.ghcup/bin/ghcup"
|
||||
"$HOME/.ghcup/bin/ghcup" install ghc "$HCVER"
|
||||
"$HOME/.ghcup/bin/ghcup" install cabal 3.6.2.0
|
||||
apt-get update
|
||||
apt-get install -y libx11-dev libxext-dev libxft-dev libxinerama-dev libxrandr-dev libxss-dev
|
||||
else
|
||||
apt-add-repository -y 'ppa:hvr/ghc'
|
||||
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.17.5/x86_64-linux-ghcup-0.1.17.5 > "$HOME/.ghcup/bin/ghcup"
|
||||
chmod a+x "$HOME/.ghcup/bin/ghcup"
|
||||
"$HOME/.ghcup/bin/ghcup" install cabal 3.6.2.0
|
||||
fi
|
||||
apt-get install -y libx11-dev libxext-dev libxft-dev libxinerama-dev libxrandr-dev libxss-dev
|
||||
- name: Install GHCup
|
||||
run: |
|
||||
mkdir -p "$HOME/.ghcup/bin"
|
||||
curl -sL https://downloads.haskell.org/ghcup/0.1.50.1/x86_64-linux-ghcup-0.1.50.1 > "$HOME/.ghcup/bin/ghcup"
|
||||
chmod a+x "$HOME/.ghcup/bin/ghcup"
|
||||
- name: Install cabal-install
|
||||
run: |
|
||||
"$HOME/.ghcup/bin/ghcup" install cabal 3.14.2.0 || (cat "$HOME"/.ghcup/logs/*.* && false)
|
||||
echo "CABAL=$HOME/.ghcup/bin/cabal-3.14.2.0 -vnormal+nowrap" >> "$GITHUB_ENV"
|
||||
- name: Install GHC (GHCup)
|
||||
if: matrix.setup-method == 'ghcup'
|
||||
run: |
|
||||
"$HOME/.ghcup/bin/ghcup" install ghc "$HCVER" || (cat "$HOME"/.ghcup/logs/*.* && false)
|
||||
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=$HCPKG" >> "$GITHUB_ENV"
|
||||
echo "HADDOCK=$HADDOCK" >> "$GITHUB_ENV"
|
||||
env:
|
||||
HCKIND: ${{ matrix.compilerKind }}
|
||||
HCNAME: ${{ matrix.compiler }}
|
||||
@ -99,28 +117,12 @@ jobs:
|
||||
echo "LANG=C.UTF-8" >> "$GITHUB_ENV"
|
||||
echo "CABAL_DIR=$HOME/.cabal" >> "$GITHUB_ENV"
|
||||
echo "CABAL_CONFIG=$HOME/.cabal/config" >> "$GITHUB_ENV"
|
||||
HCDIR=/opt/$HCKIND/$HCVER
|
||||
if [ "${{ matrix.setup-method }}" = ghcup ]; then
|
||||
HC=$HOME/.ghcup/bin/$HCKIND-$HCVER
|
||||
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.6.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.6.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))')
|
||||
echo "HCNUMVER=$HCNUMVER" >> "$GITHUB_ENV"
|
||||
echo "ARG_TESTS=--enable-tests" >> "$GITHUB_ENV"
|
||||
echo "ARG_BENCH=--enable-benchmarks" >> "$GITHUB_ENV"
|
||||
echo "HEADHACKAGE=false" >> "$GITHUB_ENV"
|
||||
echo "ARG_COMPILER=--$HCKIND --with-compiler=$HC" >> "$GITHUB_ENV"
|
||||
echo "GHCJSARITH=0" >> "$GITHUB_ENV"
|
||||
env:
|
||||
HCKIND: ${{ matrix.compilerKind }}
|
||||
HCNAME: ${{ matrix.compiler }}
|
||||
@ -160,28 +162,17 @@ jobs:
|
||||
- name: update cabal index
|
||||
run: |
|
||||
$CABAL v2-update -v
|
||||
- name: cache (tools)
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
key: ${{ runner.os }}-${{ matrix.compiler }}-tools-c0dbbd39
|
||||
path: ~/.haskell-ci-tools
|
||||
- name: install cabal-plan
|
||||
run: |
|
||||
mkdir -p $HOME/.cabal/bin
|
||||
curl -sL https://github.com/haskell-hvr/cabal-plan/releases/download/v0.6.2.0/cabal-plan-0.6.2.0-x86_64-linux.xz > cabal-plan.xz
|
||||
echo 'de73600b1836d3f55e32d80385acc055fd97f60eaa0ab68a755302685f5d81bc cabal-plan.xz' | sha256sum -c -
|
||||
curl -sL https://github.com/haskell-hvr/cabal-plan/releases/download/v0.7.3.0/cabal-plan-0.7.3.0-x86_64-linux.xz > cabal-plan.xz
|
||||
echo 'f62ccb2971567a5f638f2005ad3173dba14693a45154c1508645c52289714cb2 cabal-plan.xz' | sha256sum -c -
|
||||
xz -d < cabal-plan.xz > $HOME/.cabal/bin/cabal-plan
|
||||
rm -f cabal-plan.xz
|
||||
chmod a+x $HOME/.cabal/bin/cabal-plan
|
||||
cabal-plan --version
|
||||
- name: install hlint
|
||||
run: |
|
||||
if [ $((HCNUMVER >= 90000 && HCNUMVER < 90200)) -ne 0 ] ; then HLINTVER=$(cd /tmp && (${CABAL} v2-install -v $ARG_COMPILER --dry-run hlint --constraint='hlint >=3.4 && <3.5' | perl -ne 'if (/\bhlint-(\d+(\.\d+)*)\b/) { print "$1"; last; }')); echo "HLint version $HLINTVER" ; fi
|
||||
if [ $((HCNUMVER >= 90000 && HCNUMVER < 90200)) -ne 0 ] ; then if [ ! -e $HOME/.haskell-ci-tools/hlint-$HLINTVER/hlint ]; then echo "Downloading HLint version $HLINTVER"; mkdir -p $HOME/.haskell-ci-tools; curl --write-out 'Status Code: %{http_code} Redirects: %{num_redirects} Total time: %{time_total} Total Dsize: %{size_download}\n' --silent --location --output $HOME/.haskell-ci-tools/hlint-$HLINTVER.tar.gz "https://github.com/ndmitchell/hlint/releases/download/v$HLINTVER/hlint-$HLINTVER-x86_64-linux.tar.gz"; tar -xzv -f $HOME/.haskell-ci-tools/hlint-$HLINTVER.tar.gz -C $HOME/.haskell-ci-tools; fi ; fi
|
||||
if [ $((HCNUMVER >= 90000 && HCNUMVER < 90200)) -ne 0 ] ; then mkdir -p $CABAL_DIR/bin && ln -sf "$HOME/.haskell-ci-tools/hlint-$HLINTVER/hlint" $CABAL_DIR/bin/hlint ; fi
|
||||
if [ $((HCNUMVER >= 90000 && HCNUMVER < 90200)) -ne 0 ] ; then hlint --version ; fi
|
||||
- name: checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: source
|
||||
- name: initial cabal.project for sdist
|
||||
@ -219,15 +210,15 @@ jobs:
|
||||
flags: +pedantic
|
||||
ghc-options: -j
|
||||
EOF
|
||||
$HCPKG list --simple-output --names-only | perl -ne 'for (split /\s+/) { print "constraints: $_ installed\n" unless /^(xmonad-contrib)$/; }' >> cabal.project.local
|
||||
$HCPKG list --simple-output --names-only | perl -ne 'for (split /\s+/) { print "constraints: any.$_ installed\n" unless /^(xmonad-contrib)$/; }' >> cabal.project.local
|
||||
cat cabal.project
|
||||
cat cabal.project.local
|
||||
- name: dump install plan
|
||||
run: |
|
||||
$CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH --dry-run all
|
||||
cabal-plan
|
||||
- name: cache
|
||||
uses: actions/cache@v2
|
||||
- name: restore cache
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
key: ${{ runner.os }}-${{ matrix.compiler }}-${{ github.sha }}
|
||||
path: ~/.cabal/store
|
||||
@ -245,29 +236,41 @@ jobs:
|
||||
- name: tests
|
||||
run: |
|
||||
$CABAL v2-test $ARG_COMPILER $ARG_TESTS $ARG_BENCH all --test-show-details=direct
|
||||
- name: hlint
|
||||
run: |
|
||||
if [ $((HCNUMVER >= 90000 && HCNUMVER < 90200)) -ne 0 ] ; then (cd ${PKGDIR_xmonad_contrib} && hlint -h ${GITHUB_WORKSPACE}/source/.hlint.yaml -XHaskell2010 .) ; fi
|
||||
- name: cabal check
|
||||
run: |
|
||||
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
|
||||
run: |
|
||||
rm -f cabal.project.local
|
||||
$CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks all
|
||||
- name: upload artifacts (sdist)
|
||||
if: matrix.upload
|
||||
uses: actions/upload-artifact@v2
|
||||
- name: save cache
|
||||
if: always()
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
key: ${{ runner.os }}-${{ matrix.compiler }}-${{ github.sha }}
|
||||
path: ~/.cabal/store
|
||||
# must be separate artifacts because GitHub Actions are still broken:
|
||||
# https://github.com/actions/upload-artifact/issues/441
|
||||
# https://github.com/actions/upload-artifact/issues/457
|
||||
- name: upload artifact (sdist)
|
||||
if: matrix.upload
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdist
|
||||
path: ${{ github.workspace }}/sdist/*.tar.gz
|
||||
- name: upload artifacts (haddock)
|
||||
- name: upload artifact (haddock)
|
||||
if: matrix.upload
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: haddock
|
||||
path: ${{ github.workspace }}/haddock/*-docs.tar.gz
|
||||
- name: hackage upload (candidate)
|
||||
if: matrix.upload && github.event_name == 'workflow_dispatch' && github.event.inputs.version != ''
|
||||
|
22
.github/workflows/hlint.yaml
vendored
Normal file
22
.github/workflows/hlint.yaml
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
name: hlint
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
hlint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: 'Set up HLint'
|
||||
uses: haskell-actions/hlint-setup@v2
|
||||
with:
|
||||
version: '3.5'
|
||||
|
||||
- name: 'Run HLint'
|
||||
uses: haskell-actions/hlint-run@v2
|
||||
with:
|
||||
path: '["XMonad/", "tests/", "scripts/"]'
|
||||
fail-on: status
|
14
.github/workflows/nix.yml
vendored
14
.github/workflows/nix.yml
vendored
@ -12,17 +12,15 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v13
|
||||
uses: cachix/install-nix-action@v31
|
||||
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'
|
||||
extra_nix_config: |
|
||||
experimental-features = nix-command flakes
|
||||
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||
github_access_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Clone project
|
||||
uses: actions/checkout@v2
|
||||
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.
|
||||
# As a workaround, we invoke cabal manually here.
|
||||
run: nix develop -c cabal v2-build -O0 -j
|
||||
run: |
|
||||
nix develop -c cabal v2-update -O0 -j
|
||||
nix develop -c cabal v2-build -O0 -j
|
||||
|
15
.github/workflows/packdeps.yml
vendored
15
.github/workflows/packdeps.yml
vendored
@ -13,16 +13,16 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Clone project
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Haskell
|
||||
uses: haskell/actions/setup@v1
|
||||
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)
|
||||
continue-on-error: true
|
||||
@ -42,10 +42,9 @@ jobs:
|
||||
*.cabal
|
||||
|
||||
workflow-keepalive:
|
||||
if: github.event_name == 'schedule'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: write
|
||||
steps:
|
||||
- name: Re-enable workflow
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh api -X PUT repos/${{ github.repository }}/actions/workflows/packdeps.yml/enable
|
||||
- uses: liskin/gh-workflow-keepalive@v1
|
||||
|
48
.github/workflows/stack.yml
vendored
48
.github/workflows/stack.yml
vendored
@ -12,41 +12,33 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- resolver: lts-12
|
||||
ghc: 8.4.4
|
||||
- resolver: lts-16 # GHC 8.8
|
||||
yaml: stack.yaml
|
||||
- resolver: lts-12
|
||||
ghc: 8.4.4
|
||||
- resolver: lts-16 # GHC 8.8
|
||||
yaml: stack-master.yaml
|
||||
- resolver: lts-14
|
||||
ghc: 8.6.5
|
||||
- resolver: lts-18 # GHC 8.10
|
||||
yaml: stack-master.yaml
|
||||
- resolver: lts-16
|
||||
ghc: 8.8.4
|
||||
- resolver: lts-19 # GHC 9.0
|
||||
yaml: stack-master.yaml
|
||||
- resolver: lts-18
|
||||
ghc: 8.10.7
|
||||
- resolver: lts-20 # GHC 9.2
|
||||
yaml: stack-master.yaml
|
||||
- resolver: lts-19
|
||||
ghc: 9.0.2
|
||||
- resolver: lts-21 # GHC 9.4
|
||||
yaml: stack-master.yaml
|
||||
- resolver: lts-22 # GHC 9.6
|
||||
yaml: stack-master.yaml
|
||||
- resolver: lts-23 # GHC 9.8
|
||||
yaml: stack.yaml
|
||||
- resolver: lts-19
|
||||
ghc: 9.0.2
|
||||
- resolver: lts-23 # GHC 9.8
|
||||
yaml: stack-master.yaml
|
||||
|
||||
steps:
|
||||
- name: Clone project
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Prepare apt sources
|
||||
run: |
|
||||
set -ex
|
||||
sudo add-apt-repository -y ppa:hvr/ghc
|
||||
sudo apt update -y
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install C dependencies
|
||||
run: |
|
||||
set -ex
|
||||
sudo apt update -y
|
||||
sudo apt install -y \
|
||||
libx11-dev \
|
||||
libxext-dev \
|
||||
@ -56,14 +48,6 @@ jobs:
|
||||
libxss-dev \
|
||||
#
|
||||
|
||||
- name: Install GHC
|
||||
# use system ghc (if available) in stack, don't waste GH Actions cache space
|
||||
continue-on-error: true
|
||||
run: |
|
||||
set -ex
|
||||
sudo apt install -y ghc-${{ matrix.ghc }}
|
||||
echo /opt/ghc/${{ matrix.ghc }}/bin >> $GITHUB_PATH
|
||||
|
||||
- name: Refresh caches once a month
|
||||
id: cache-date
|
||||
# GHA writes caches on the first miss and then never updates them again;
|
||||
@ -71,16 +55,16 @@ jobs:
|
||||
# date is prefixed with an epoch number to let us manually refresh the
|
||||
# cache when needed. This is a workaround for https://github.com/actions/cache/issues/2
|
||||
run: |
|
||||
echo "::set-output name=date::1-$(date +%Y-%m)"
|
||||
date +date=1-%Y-%m >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache Haskell package metadata
|
||||
uses: actions/cache@v2
|
||||
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@v2
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.stack/*
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -27,4 +27,6 @@ tags
|
||||
|
||||
stack.yaml.lock
|
||||
|
||||
# nix artifacts
|
||||
result
|
||||
flake.lock
|
||||
|
5
.mailmap
5
.mailmap
@ -103,6 +103,7 @@ hexago.nl <xmonad-contrib@hexago.nl>
|
||||
lithis <xmonad@selg.hethrael.org>
|
||||
lithis <xmonad@selg.hethrael.org> <xmonad@s001.hethrael.com>
|
||||
sam-barr <mail@samf.bar> <samfbarr@outlook.com>
|
||||
slotThe <soliditsallgood@mailbox.org> <50166980+slotThe@users.noreply.github.com>
|
||||
slotThe <soliditsallgood@mailbox.org> <soliditsallgood@tuta.io>
|
||||
Tony Zorman <soliditsallgood@mailbox.org> <50166980+slotThe@users.noreply.github.com>
|
||||
Tony Zorman <soliditsallgood@mailbox.org> <soliditsallgood@tuta.io>
|
||||
Tony Zorman <soliditsallgood@mailbox.org>
|
||||
spoonm <spoonm@spoonm.org>
|
||||
|
493
CHANGES.md
493
CHANGES.md
@ -1,6 +1,473 @@
|
||||
# Change Log / Release Notes
|
||||
|
||||
## 0.17.1 (September 3, 2021)
|
||||
## _unreleased_
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* Drop support for GHC 8.6
|
||||
|
||||
### Bug Fixes and Minor Changes
|
||||
|
||||
* `XMonad.Util.EZConfig`
|
||||
|
||||
- Added `XF86WLAN` and `Menu` to the list of supported special keys.
|
||||
|
||||
* `XMonad.Actions.DynamicProjects`
|
||||
|
||||
- No longer autodelete projects when `switchProject` is called from
|
||||
an empty workspace. This also fixes a bug where static workspaces
|
||||
would be deleted when switching to a dynamic project.
|
||||
- Improved documentation on how to close a project.
|
||||
|
||||
* `XMonad.Hooks.Rescreen`
|
||||
|
||||
- Allow overriding the `rescreen` operation itself. Additionally, the
|
||||
`XMonad.Actions.PhysicalScreens` module now provides an alternative
|
||||
implementation of `rescreen` that avoids reshuffling the workspaces if
|
||||
the number of screens doesn't change and only their locations do (which
|
||||
is especially common if one uses `xrandr --setmonitor` to split an
|
||||
ultra-wide display in two).
|
||||
|
||||
- Added an optional delay when waiting for events to settle. This may be
|
||||
used to avoid flicker and unnecessary workspace reshuffling if multiple
|
||||
`xrandr` commands are used to reconfigure the display layout.
|
||||
|
||||
* `XMonad.Layout.NoBorders`
|
||||
|
||||
- It's no longer necessary to use `borderEventHook` to garbage collect
|
||||
`alwaysHidden`/`neverHidden` lists. The layout listens to
|
||||
`DestroyWindowEvent` messages instead, which are broadcast to layouts
|
||||
since xmonad v0.17.0.
|
||||
|
||||
* `XMonad.Hooks.EwmhDesktops`
|
||||
|
||||
- Added a customization option for the action that gets executed when
|
||||
a client sends a **_NET_CURRENT_DESKTOP** request. It is now possible
|
||||
to change it using the `setEwmhSwitchDesktopHook`.
|
||||
- Added a customization option for mapping hidden workspaces to screens
|
||||
when setting the **_NET_DESKTOP_VIEWPORT**. This can be done using
|
||||
the `setEwmhHiddenWorkspaceToScreenMapping`.
|
||||
|
||||
* `XMonad.Layout.IndependentScreens`
|
||||
|
||||
- Added `focusWorkspace` for focusing workspaces on the screen that they
|
||||
belong to.
|
||||
- Added `doFocus'` hook as an alternative for `doFocus` when using
|
||||
IndependentScreens.
|
||||
- Added `screenOnMonitor` for getting the active screen for a monitor.
|
||||
|
||||
* `XMonad.Util.NamedScratchPad`
|
||||
|
||||
- Fix unintended window hiding in `nsSingleScratchpadPerWorkspace`.
|
||||
Only hide the previously active scratchpad.
|
||||
|
||||
## 0.18.1 (August 20, 2024)
|
||||
|
||||
### 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.
|
||||
|
||||
* `XMonad.Prompt`
|
||||
|
||||
- Added an additional `XPConfig` argument to `historyCompletion` and
|
||||
`historyCompletionP`. Calls along the lines of `historyCompletionP
|
||||
myFunc` should be changed to `historyCompletionP myConf myFunc`.
|
||||
If not `myConf` is lying around, `def` can be used instead.
|
||||
|
||||
* `XMonad.Actions.GridSelect`
|
||||
|
||||
- Added the `gs_cancelOnEmptyClick` field to `GSConfig`, which makes
|
||||
mouse clicks into "empty space" cancel the current grid-select.
|
||||
Users explicitly defining their own `GSConfig` record will have to
|
||||
add this to their definitions. Additionally, the field defaults to
|
||||
`True`—to retain the old behaviour, set it to `False`.
|
||||
|
||||
### New Modules
|
||||
|
||||
* `XMonad.Actions.Profiles`
|
||||
|
||||
- Group workspaces by similarity. Useful when one has lots
|
||||
of workspaces and uses only a couple per unit of work.
|
||||
|
||||
* `XMonad.Hooks.FloatConfigureReq`
|
||||
|
||||
- Customize handling of floating windows' move/resize/restack requests
|
||||
(ConfigureRequest). Useful as a workaround for some misbehaving client
|
||||
applications (Steam, rxvt-unicode, anything that tries to restore
|
||||
absolute position of floats).
|
||||
|
||||
* `XMonad.Layout.Columns`
|
||||
|
||||
- Organize windows in columns. This layout allows to move/resize windows in
|
||||
every directions.
|
||||
|
||||
* `XMonad.Prompt.WindowBringer`
|
||||
|
||||
- Added `copyMenu`, a convenient way to copy a window to the current workspace.
|
||||
|
||||
### 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`.
|
||||
|
||||
* `XMonad.Prompt.OrgMode`
|
||||
|
||||
- Added `HH:MM-HH:MM` and `HH:MM+HH` syntax to specify time spans.
|
||||
|
||||
* `XMonad.Prompt`
|
||||
|
||||
- The history file is not extraneously read and written anymore if
|
||||
the `historySize` is set to 0.
|
||||
|
||||
* `XMonad.Hooks.EwmhDesktops`
|
||||
|
||||
- Requests for unmanaged windows no longer cause a refresh. This avoids
|
||||
flicker and also fixes disappearing menus in the Steam client and
|
||||
possibly a few other client applications.
|
||||
|
||||
(See also `XMonad.Hooks.FloatConfigureReq` and/or `XMonad.Util.Hacks`
|
||||
for additional Steam client workarounds.)
|
||||
|
||||
* `XMonad.Actions.Submap`
|
||||
|
||||
- Added `visualSubmapSorted` to enable sorting of the keymap
|
||||
descriptions.
|
||||
|
||||
* `XMonad.Hooks.ScreenCorners`
|
||||
|
||||
- Added screen edge support with `SCTop`, `SCBottom`, `SCLeft` and
|
||||
`SCRight`. Now both corners and edges are supported.
|
||||
|
||||
* `XMonad.Actions.WindowNavigation`
|
||||
|
||||
- Improve navigation in presence of floating windows.
|
||||
- Handle window switching when in `Full` layout.
|
||||
|
||||
### 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`.
|
||||
|
||||
* Dropped support for GHC 8.4.
|
||||
|
||||
* `XMonad.Util.ExclusiveScratchpads`
|
||||
|
||||
- Deprecated the module in favour of the (new) exclusive scratchpad
|
||||
functionality of `XMonad.Util.NamedScratchpad`.
|
||||
|
||||
* `XMonad.Actions.CycleWorkspaceByScreen`
|
||||
|
||||
- The type of `repeatableAction` has changed, and it's deprecated in
|
||||
favour of `X.A.Repeatable.repeatable`.
|
||||
|
||||
* `XMonad.Hooks.DynamicProperty`
|
||||
|
||||
- Deprecated the module in favour of the more aptly named
|
||||
`XMonad.Hooks.OnPropertyChange`.
|
||||
|
||||
* `XMonad.Util.Scratchpad`:
|
||||
|
||||
- Deprecated the module; use `XMonad.Util.NamedScratchpad` instead.
|
||||
|
||||
* `XMonad.Actions.Navigation2D`
|
||||
|
||||
- Removed deprecated function `hybridNavigation`.
|
||||
|
||||
* `XMonad.Layout.Spacing`
|
||||
|
||||
- Removed deprecated functions `SpacingWithEdge`, `SmartSpacing`,
|
||||
`SmartSpacingWithEdge`, `ModifySpacing`, `setSpacing`, and
|
||||
`incSpacing`.
|
||||
|
||||
* `XMonad.Actions.MessageFeedback`
|
||||
|
||||
- Removed deprecated functions `send`, `sendSM`, `sendSM_`,
|
||||
`tryInOrder`, `tryInOrder_`, `tryMessage`, and `tryMessage_`.
|
||||
|
||||
* `XMonad.Prompt.Window`
|
||||
|
||||
- Removed deprecated functions `windowPromptGoto`,
|
||||
`windowPromptBring`, and `windowPromptBringCopy`.
|
||||
|
||||
* `XMonad.Hooks.ICCCMFocus`
|
||||
|
||||
- Removed deprecated module. This was merged into xmonad.
|
||||
|
||||
* `XMonad.Layout.LayoutBuilderP`
|
||||
|
||||
- Removed deprecated module; use `XMonad.Layout.LayoutBuilder`
|
||||
instead.
|
||||
|
||||
* `XMonad.Hooks.RestoreMinimized`
|
||||
|
||||
- Removed deprecated module; use `XMonad.Hooks.Minimize` instead.
|
||||
|
||||
* `XMonad.Layout.Named`
|
||||
|
||||
- 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
|
||||
top in bottom-to-top order, followed by visible workspaces, followed by
|
||||
invisible workspaces. Within visible and invisible groups, workspaces are
|
||||
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`
|
||||
- A three column layout with main column in the center and two stack
|
||||
column surrounding it. Master window will be on center column and
|
||||
spaces on the sides are reserved.
|
||||
|
||||
* `XMonad.Layout.FocusTracking`.
|
||||
|
||||
- Replaces `X.L.StateFull` and half of `X.L.TrackFloating`.
|
||||
|
||||
* `XMonad.Actions.MostRecentlyUsed`
|
||||
|
||||
- Tab through windows by recency of use. Based on the Alt+Tab behaviour
|
||||
common outside of xmonad.
|
||||
|
||||
* `XMonad.Util.History`
|
||||
|
||||
- Track history in *O(log n)* time. Provides `History`, a variation on a
|
||||
LIFO stack with a uniqueness property. In order to achieve the desired
|
||||
asymptotics, the data type is implemented as an ordered Map.
|
||||
|
||||
* `XMonad.Actions.Repeatable`
|
||||
|
||||
- Actions you'd like to repeat. Factors out the shared logic of
|
||||
`X.A.CycleRecentWS`, `X.A.CycleWorkspaceByScreen` and `X.A.CycleWindows`.
|
||||
|
||||
* `XMonad.Hooks.OnPropertyChange`:
|
||||
|
||||
- A new module replicating the functionality of
|
||||
`XMonad.Hooks.DynamicProperty`, but with more discoverable names.
|
||||
|
||||
* `XMonad.Actions.ToggleFullFloat`:
|
||||
|
||||
- Fullscreen (float) a window while remembering its original state.
|
||||
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`
|
||||
|
||||
- Added `magnifyxy` to allow for different magnification in the
|
||||
horizontal and vertical directions. Added `magnifierxy`,
|
||||
`magnifierxy'`, `magnifierxyOff`, and `magnifierxyOff'` as
|
||||
particular combinators.
|
||||
|
||||
* `XMonad.Util.Loggers`
|
||||
|
||||
- Added `logClassname`, `logClassnames`, `logClassnames'`,
|
||||
`logClassnameOnScreen`, `logClassnamesOnScreen`, `logClassnamesOnScreen'`,
|
||||
and `ClassnamesFormat`. These are all equivalents of their `Title`
|
||||
counterparts, allowing logging the window classname instead.
|
||||
|
||||
* `XMonad.Hooks.StatusBar.PP`
|
||||
|
||||
- `dynamicLogString` now forces its result and produces an error string if
|
||||
it throws an exception. Use `dynamicLogString'` if for some reason you
|
||||
need the old behavior.
|
||||
|
||||
* `XMonad.Util.EZConfig`
|
||||
|
||||
- Added `remapKeysP`, which remaps keybindings from one binding to
|
||||
another.
|
||||
|
||||
- Made `additionalKeys{,P}`, `removeKeys{,P}`, `remapKeysP`, and
|
||||
`{additional,remove}MouseBindings` `infixl 4` so they can more easily
|
||||
be concatenated with `(++)`.
|
||||
|
||||
* `XMonad.Util.NamedScratchpad`
|
||||
|
||||
- Added `addExclusives`, `resetFocusedNSP`, `setNoexclusive`,
|
||||
`resizeNoexclusive`, and `floatMoveNoexclusive` in order to augment
|
||||
named scratchpads with the exclusive scratchpad functionality of
|
||||
`XMonad.Util.ExclusiveScratchpads`.
|
||||
|
||||
* `XMonad.Layout.BorderResize`
|
||||
|
||||
- Added `borderResizeNear` as a variant of `borderResize` that can
|
||||
control how many pixels near a border resizing still works.
|
||||
|
||||
* `XMonad.Util.Run`
|
||||
|
||||
- It is now ensured that all arguments of `execute` and `eval` are
|
||||
quoted. Likewise, `executeNoQuote` is added as a version of
|
||||
`execute` that does not do that.
|
||||
|
||||
- Added `findFile` as a shorthand to call `find-file`.
|
||||
|
||||
- Added `list` and `saveExcursion` to the list of Emacs commands.
|
||||
|
||||
- Added `toList` to easily lift a `String` to an `X Input`.
|
||||
|
||||
- Added `>&&>` and `>||>` to glue together different inputs.
|
||||
|
||||
* `XMonad.Util.Parser`
|
||||
|
||||
- Added the `gather`, `count`, `between`, `option`, `optionally`,
|
||||
`skipMany`, `skipMany1`, `chainr`, `chainr1`, `chainl`, `chainl1`,
|
||||
and `manyTill` functions, in order to achieve feature parity with
|
||||
`Text.ParserCombinators.ReadP`.
|
||||
|
||||
* `XMonad.Actions.FloatKeys`
|
||||
|
||||
- Added `directionMoveWindow` and `directionMoveWindow` as more
|
||||
alternatives to the existing functions.
|
||||
|
||||
* `XMonad.Hooks.InsertPosition`
|
||||
|
||||
- Added `setupInsertPosition` as a combinator alternative to
|
||||
`insertPosition`.
|
||||
|
||||
* `XMonad.Actions.Navigation2D`
|
||||
|
||||
- Added `sideNavigation` as a fallback to the default tiling strategy,
|
||||
in case `lineNavigation` can't find a window. This benefits
|
||||
especially users who use `XMonad.Layout.Spacing`.
|
||||
|
||||
* `XMonad.Prompt.OrgMode`
|
||||
|
||||
- Added `orgPromptRefile` and `orgPromptRefileTo` for interactive
|
||||
and targeted refiling of the entered note into some existing tree
|
||||
of headings, respectively.
|
||||
|
||||
- Allowed the time specification in `HHMM` format.
|
||||
|
||||
* `XMonad.Actions.Search`
|
||||
|
||||
- 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`
|
||||
|
||||
- Fixed an issue where the bottom right window would not respond to
|
||||
`MirrorShrink` and `MirrorExpand` messages.
|
||||
|
||||
* `XMonad.Hooks.EwmhDesktops`
|
||||
|
||||
- Added `disableEwmhManageDesktopViewport` to avoid setting the
|
||||
`_NET_DESKTOP_VIEWPORT` property, as it can lead to issues with
|
||||
some status bars (see this
|
||||
[polybar issue](https://github.com/polybar/polybar/issues/2603)).
|
||||
|
||||
- Added `setEwmhFullscreenHooks` to override the default fullfloat/sink
|
||||
behaviour of `_NET_WM_STATE_FULLSCREEN` requests. See also
|
||||
`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.
|
||||
|
||||
* `XMonad.Util.NamedActions`
|
||||
|
||||
- Changed `addDescrKeys` and `addDescrKeys'` to not discard the
|
||||
keybindings in the current config.
|
||||
|
||||
* `XMonad.Prompt`
|
||||
|
||||
- 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-<TAB>` 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
|
||||
on whether any prefix argument was given.
|
||||
|
||||
* `XMonad.Actions.WorkspaceNames`
|
||||
|
||||
- Enabled prompt completion (from history) in `renameWorkspace`.
|
||||
|
||||
* `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)
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
@ -13,9 +480,10 @@
|
||||
* `XMonad.Config.{Arossato,Dmwit,Droundy,Monad,Prime,Saegesser,Sjanssen}`
|
||||
|
||||
- Deprecated all of these modules. The user-specific configuration
|
||||
modules may still be found [on the website].
|
||||
modules may still be found [on the
|
||||
website](https://xmonad.org/configurations.html)
|
||||
|
||||
* `XMonad.Util.NamedScratchpad`
|
||||
* `XMonad.Util.NamedScratchpad`
|
||||
|
||||
- Scratchpads are now only based on the argument given to
|
||||
`namedScratchpadManageHook`; all other scratchpad arguments are,
|
||||
@ -34,8 +502,6 @@
|
||||
- Deprecated `urgencyConfig`; use `def` from the new `Default`
|
||||
instance of `UrgencyConfig` instead.
|
||||
|
||||
[on the website]: https://xmonad.org/configurations.html
|
||||
|
||||
### New Modules
|
||||
|
||||
* `XMonad.Actions.PerLayoutKeys`
|
||||
@ -79,7 +545,7 @@
|
||||
A module for adding a keybinding to repeat the last action, similar
|
||||
to Vim's `.` or Emacs's `dot-mode`.
|
||||
|
||||
* `XMonad.Util.Grab`
|
||||
* `XMonad.Util.Grab`
|
||||
|
||||
Utilities for making grabbing and ungrabbing keys more convenient.
|
||||
|
||||
@ -110,7 +576,8 @@
|
||||
`todo +d 12 02 2024` work.
|
||||
|
||||
- Added the ability to specify alphabetic (`#A`, `#B`, and `#C`)
|
||||
[priorities] at the end of the input note.
|
||||
[priorities](https://orgmode.org/manual/Priorities.html) at the end of
|
||||
the input note.
|
||||
|
||||
* `XMonad.Prompt.Unicode`
|
||||
|
||||
@ -204,7 +671,8 @@
|
||||
|
||||
- Modified `mkAbsolutePath` to support a leading environment variable, so
|
||||
things like `$HOME/NOTES` work. If you want more general environment
|
||||
variable support, comment on [this PR].
|
||||
variable support, comment on [this
|
||||
PR](https://github.com/xmonad/xmonad-contrib/pull/744)
|
||||
|
||||
* `XMonad.Util.XUtils`
|
||||
|
||||
@ -243,9 +711,6 @@
|
||||
|
||||
- Added a `Default` instance for `UrgencyConfig` and `DzenUrgencyHook`.
|
||||
|
||||
[this PR]: https://github.com/xmonad/xmonad-contrib/pull/744
|
||||
[priorities]: https://orgmode.org/manual/Priorities.html
|
||||
|
||||
### Other changes
|
||||
|
||||
* Migrated the sample build scripts from the deprecated `xmonad-testing` repo to
|
||||
@ -1771,8 +2236,8 @@
|
||||
|
||||
* `XMonad.Prompt.Pass`
|
||||
|
||||
This module provides 3 `XMonad.Prompt`s to ease passwords
|
||||
manipulation (generate, read, remove) via [pass][].
|
||||
This module provides 3 `XMonad.Prompt`s to ease passwords manipulation
|
||||
(generate, read, remove) via [pass](http://www.passwordstore.org/).
|
||||
|
||||
* `XMonad.Util.RemoteWindows`
|
||||
|
||||
@ -1848,5 +2313,3 @@
|
||||
## See Also
|
||||
|
||||
<https://wiki.haskell.org/Xmonad/Notable_changes_since_0.8>
|
||||
|
||||
[pass]: http://www.passwordstore.org/
|
||||
|
57
NIX.md
57
NIX.md
@ -16,6 +16,24 @@ pkgs: devInputs: devInputs // {
|
||||
}
|
||||
```
|
||||
|
||||
## Selecting a Compiler
|
||||
|
||||
A `comp.nix` file can be used to set the compiler used for `nix build` etc. E.g.
|
||||
|
||||
```nix
|
||||
{ compiler = "ghc924"; }
|
||||
```
|
||||
|
||||
Note that you must `git add comp.nix`, or it will be invisible to the flake.
|
||||
|
||||
There is also a `prefix` option (see documentation below) but it doesn't really
|
||||
work in this context, since the xmonad flakes don't see the effects of your
|
||||
system overlays. Instead try the `--override-input` flag, e.g.
|
||||
|
||||
```sh
|
||||
$ nix develop . --override-input nixpkgs 'github:NixOS/nixpkgs/nixos-unstable'
|
||||
```
|
||||
|
||||
## NixOS Modules
|
||||
|
||||
The core and contrib flakes provide NixOS configuration modules.
|
||||
@ -25,24 +43,42 @@ You can bring them into your system flake like so:
|
||||
{
|
||||
inputs = {
|
||||
nixpkgs.url = github:NixOS/nixpkgs/nixos-<version>;
|
||||
xmonad.url = github:xmonad/xmonad;
|
||||
# The xmonad-contrib flake depends upon and re-exports from the xmonad
|
||||
# flake. As such, you don't need to use the latter directly. If you wish to
|
||||
# use /only/ the xmonad flake, you should beware that the version of
|
||||
# contrib you get from nixpkgs might not build against it.
|
||||
xmonad-contrib.url = github:xmonad/xmonad-contrib;
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, xmonad, xmonad-contrib }: {
|
||||
outputs = { self, nixpkgs, xmonad-contrib }: {
|
||||
nixosConfigurations.<hostname> = nixpkgs.lib.nixosSystem rec {
|
||||
system = <arch>;
|
||||
system = <system>;
|
||||
# NixOS module composition is /not/ commutative; order matters.
|
||||
# To avoid issues, add `xmonad-contrib.nixosModules` after your standard
|
||||
# configuration, but before `modernise` or any module overlaying in a
|
||||
# "prefix".
|
||||
modules = [
|
||||
./configuration.nix
|
||||
xmonad.nixosModule
|
||||
xmonad-contrib.nixosModule
|
||||
./hardware-configuration.nix
|
||||
<myMiscConfigModule>
|
||||
] ++ xmonad-contrib.nixosModules ++ [
|
||||
# `modernise` replaces the standard xmonad module and wrapper script
|
||||
# with those from unstable. This is currently a necessary workaround to
|
||||
# make Mod-q recompilation work.
|
||||
xmonad-contrib.modernise.${system}
|
||||
<myPrefixModule>
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Then you can set the provided options in your `configuration.nix` under `flake`:
|
||||
Note that `<thing>` should be replaced with a user-supplied `thing`.
|
||||
`<version>`, `<hostname>` and `<system>` are necessary, while
|
||||
` <myMiscConfigModule>` and `<myPrefixModule>` are entirely optional.
|
||||
|
||||
Having brought in `xmonad-contrib.nixosModules`, you can then set the provided
|
||||
options in your `configuration.nix` under `flake`:
|
||||
|
||||
```nix
|
||||
services.xserver.windowManager.xmonad = {
|
||||
@ -51,7 +87,7 @@ Then you can set the provided options in your `configuration.nix` under `flake`:
|
||||
flake = {
|
||||
enable = true;
|
||||
# prefix = "unstable";
|
||||
compiler = "ghc921";
|
||||
compiler = "ghc924";
|
||||
};
|
||||
};
|
||||
```
|
||||
@ -59,11 +95,12 @@ Then you can set the provided options in your `configuration.nix` under `flake`:
|
||||
This will use core and contrib from git for your system xmonad, building your
|
||||
config with the compiler of your choice.
|
||||
|
||||
With the flake enabled, the `xmonad.haskellPackages` option is not used directly,
|
||||
and is instead set by the `flake.compiler` option. When `compiler` is unset,
|
||||
the default `pkgs.haskellPackages` is used.
|
||||
With the flake enabled, the `xmonad.haskellPackages` option is not used
|
||||
directly, and is instead set by the `flake.compiler` option. When `compiler` is
|
||||
unset, the default `pkgs.haskellPackages` is used.
|
||||
|
||||
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).
|
||||
|
@ -6,9 +6,9 @@
|
||||
<a href="https://github.com/xmonad/xmonad-contrib/blob/readme/LICENSE"><img alt="License" src="https://img.shields.io/github/license/xmonad/xmonad-contrib"></a>
|
||||
<a href="https://haskell.org/"><img alt="Made in Haskell" src="https://img.shields.io/badge/Made%20in-Haskell-%235e5086?logo=haskell"></a>
|
||||
<br>
|
||||
<a href="https://github.com/xmonad/xmonad-contrib/actions/workflows/stack.yml"><img alt="Stack" src="https://img.shields.io/github/workflow/status/xmonad/xmonad-contrib/Stack?label=Stack&logo=githubactions&logoColor=white"></a>
|
||||
<a href="https://github.com/xmonad/xmonad-contrib/actions/workflows/haskell-ci.yml"><img alt="Cabal" src="https://img.shields.io/github/workflow/status/xmonad/xmonad-contrib/Haskell-CI?label=Cabal&logo=githubactions&logoColor=white"></a>
|
||||
<a href="https://github.com/xmonad/xmonad-contrib/actions/workflows/nix.yml"><img alt="Nix" src="https://img.shields.io/github/workflow/status/xmonad/xmonad-contrib/Nix?label=Nix&logo=githubactions&logoColor=white"></a>
|
||||
<a href="https://github.com/xmonad/xmonad-contrib/actions/workflows/stack.yml"><img alt="Stack" src="https://img.shields.io/github/actions/workflow/status/xmonad/xmonad-contrib/stack.yml?label=Stack&logo=githubactions&logoColor=white"></a>
|
||||
<a href="https://github.com/xmonad/xmonad-contrib/actions/workflows/haskell-ci.yml"><img alt="Cabal" src="https://img.shields.io/github/actions/workflow/status/xmonad/xmonad-contrib/haskell-ci.yml?label=Cabal&logo=githubactions&logoColor=white"></a>
|
||||
<a href="https://github.com/xmonad/xmonad-contrib/actions/workflows/nix.yml"><img alt="Nix" src="https://img.shields.io/github/actions/workflow/status/xmonad/xmonad-contrib/nix.yml?label=Nix&logo=githubactions&logoColor=white"></a>
|
||||
<br>
|
||||
<a href="https://github.com/sponsors/xmonad"><img alt="GitHub Sponsors" src="https://img.shields.io/github/sponsors/xmonad?label=GitHub%20Sponsors&logo=githubsponsors"></a>
|
||||
<a href="https://opencollective.com/xmonad"><img alt="Open Collective" src="https://img.shields.io/opencollective/all/xmonad?label=Open%20Collective&logo=opencollective"></a>
|
||||
|
@ -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
|
||||
--
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
--
|
||||
@ -57,7 +57,7 @@ import XMonad.Prelude
|
||||
-- bindings!)
|
||||
--
|
||||
-- For detailed instructions on editing your key bindings, see
|
||||
-- "XMonad.Doc.Extending#Editing_key_bindings".
|
||||
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial>.
|
||||
|
||||
-- | Create a 'Data.Map.Map' from @String@s to xmonad actions from a
|
||||
-- list of pairs.
|
||||
|
@ -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
|
||||
--
|
||||
|
@ -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
|
||||
--
|
||||
@ -77,7 +77,7 @@ import qualified XMonad.StackSet as W
|
||||
-- > , ((modm .|. shiftMask, xK_v ), killAllOtherCopies) -- @@ Toggle window state back
|
||||
--
|
||||
-- For detailed instructions on editing your key bindings, see
|
||||
-- "XMonad.Doc.Extending#Editing_key_bindings".
|
||||
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial>.
|
||||
|
||||
-- $logHook
|
||||
--
|
||||
|
@ -1,6 +1,7 @@
|
||||
{-# LANGUAGE CPP #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
{-# LANGUAGE PatternGuards #-}
|
||||
{-# LANGUAGE MultiWayIf #-}
|
||||
-----------------------------------------------------------------------------
|
||||
-- |
|
||||
-- Module : XMonad.Actions.CycleRecentWS
|
||||
@ -35,21 +36,25 @@ module XMonad.Actions.CycleRecentWS (
|
||||
#endif
|
||||
) where
|
||||
|
||||
import XMonad.Actions.Repeatable (repeatableSt)
|
||||
|
||||
import XMonad hiding (workspaces)
|
||||
import XMonad.StackSet hiding (filter)
|
||||
import XMonad.Prelude (void, when)
|
||||
import XMonad.StackSet hiding (filter, modify)
|
||||
|
||||
import Control.Arrow ((&&&))
|
||||
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
|
||||
-- >
|
||||
-- > , ((modm, xK_Tab), cycleRecentWS [xK_Alt_L] xK_Tab xK_grave)
|
||||
--
|
||||
-- For detailed instructions on editing your key bindings, see
|
||||
-- "XMonad.Doc.Extending#Editing_key_bindings".
|
||||
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial>.
|
||||
|
||||
-- | Cycle through most recent workspaces with repeated presses of a key, while
|
||||
-- a modifier key is held down. The recency of workspaces previewed while browsing
|
||||
@ -111,25 +116,15 @@ cycleWindowSets :: (WindowSet -> [WorkspaceId]) -- ^ A function used to create a
|
||||
-> X ()
|
||||
cycleWindowSets genOptions mods keyNext keyPrev = do
|
||||
(options, unView') <- gets $ (genOptions &&& unView) . windowset
|
||||
XConf {theRoot = root, display = d} <- ask
|
||||
let event = allocaXEvent $ \p -> do
|
||||
maskEvent d (keyPressMask .|. keyReleaseMask) p
|
||||
KeyEvent {ev_event_type = t, ev_keycode = c} <- getEvent p
|
||||
s <- keycodeToKeysym d c 0
|
||||
return (t, s)
|
||||
let setOption n = do windows $ view (options `cycref` n) . unView'
|
||||
(t, s) <- io event
|
||||
case () of
|
||||
() | t == keyPress && s == keyNext -> setOption (n+1)
|
||||
| t == keyPress && s == keyPrev -> setOption (n-1)
|
||||
| t == keyRelease && s `elem` mods -> return ()
|
||||
| otherwise -> setOption n
|
||||
io $ grabKeyboard d root False grabModeAsync grabModeAsync currentTime
|
||||
setOption 0
|
||||
io $ ungrabKeyboard d currentTime
|
||||
where
|
||||
cycref :: [a] -> Int -> a
|
||||
cycref l i = l !! (i `mod` length l)
|
||||
let
|
||||
preview = do
|
||||
i <- get
|
||||
lift $ windows (view (options !! (i `mod` n)) . unView')
|
||||
where n = length options
|
||||
void . repeatableSt (-1) mods keyNext $ \t s -> when (t == keyPress) $ if
|
||||
| s == keyNext -> modify succ >> preview
|
||||
| s == keyPrev -> modify pred >> preview
|
||||
| otherwise -> pure ()
|
||||
|
||||
-- | Given an old and a new 'WindowSet', which is __exactly__ one
|
||||
-- 'view' away from the old one, restore the workspace order of the
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
-- >
|
||||
@ -122,7 +122,7 @@ import XMonad.Util.WorkspaceCompare
|
||||
-- > windows . view $ t )
|
||||
--
|
||||
-- For detailed instructions on editing your key bindings, see
|
||||
-- "XMonad.Doc.Extending#Editing_key_bindings".
|
||||
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial>.
|
||||
--
|
||||
-- When using the toggle functions, in order to ensure that the workspace
|
||||
-- to which you switch is the previously viewed workspace, use the
|
||||
|
@ -1,4 +1,4 @@
|
||||
{-# LANGUAGE ViewPatterns #-}
|
||||
{-# LANGUAGE ViewPatterns, MultiWayIf #-}
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- |
|
||||
@ -50,7 +50,6 @@ module XMonad.Actions.CycleWindows (
|
||||
-- $pointer
|
||||
|
||||
-- * Generic list rotations
|
||||
-- $generic
|
||||
rotUp, rotDown
|
||||
) where
|
||||
|
||||
@ -59,11 +58,13 @@ import XMonad.Prelude
|
||||
import qualified XMonad.StackSet as W
|
||||
import qualified Data.List.NonEmpty as NE
|
||||
import XMonad.Actions.RotSlaves
|
||||
import XMonad.Actions.Repeatable (repeatableSt)
|
||||
|
||||
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
|
||||
@ -79,7 +80,7 @@ import Control.Arrow (second)
|
||||
--
|
||||
-- Also, if you use focus follows mouse, you will want to read the section
|
||||
-- on updating the mouse pointer below. For detailed instructions on
|
||||
-- editing your key bindings, see "XMonad.Doc.Extending#Editing_key_bindings".
|
||||
-- editing your key bindings, see <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial>.
|
||||
{- $pointer
|
||||
With FocusFollowsMouse == True, the focus is updated after binding
|
||||
actions, possibly focusing a window you didn't intend to focus. Most
|
||||
@ -139,27 +140,19 @@ cycleStacks' :: (W.Stack Window -> [W.Stack Window]) -- ^ A function to a finite
|
||||
-> KeySym -- ^ Key used to select a \"previous\" stack.
|
||||
-> X ()
|
||||
cycleStacks' filteredPerms mods keyNext keyPrev = do
|
||||
XConf {theRoot = root, display = d} <- ask
|
||||
stacks <- gets $ maybe [] filteredPerms . W.stack . W.workspace . W.current . windowset
|
||||
|
||||
let evt = allocaXEvent $
|
||||
\p -> do maskEvent d (keyPressMask .|. keyReleaseMask) p
|
||||
KeyEvent {ev_event_type = t, ev_keycode = c} <- getEvent p
|
||||
s <- keycodeToKeysym d c 0
|
||||
return (t, s)
|
||||
choose n (t, s)
|
||||
| t == keyPress && s == keyNext = io evt >>= choose (n+1)
|
||||
| t == keyPress && s == keyPrev = io evt >>= choose (n-1)
|
||||
| t == keyPress && s `elem` [xK_0..xK_9] = io evt >>= choose (numKeyToN s)
|
||||
| t == keyRelease && s `elem` mods = return ()
|
||||
| otherwise = doStack n >> io evt >>= choose n
|
||||
doStack n = windows . W.modify' . const $ stacks `cycref` n
|
||||
|
||||
io $ grabKeyboard d root False grabModeAsync grabModeAsync currentTime
|
||||
io evt >>= choose 1
|
||||
io $ ungrabKeyboard d currentTime
|
||||
where cycref l i = l !! (i `mod` length l) -- modify' ensures l is never [], but must also be finite
|
||||
numKeyToN = subtract 48 . read . show
|
||||
stacks <- gets $ maybe [] filteredPerms
|
||||
. W.stack . W.workspace . W.current . windowset
|
||||
let
|
||||
preview = do
|
||||
i <- get
|
||||
lift . windows . W.modify' . const $ stacks !! (i `mod` n)
|
||||
where n = length stacks
|
||||
void $ repeatableSt 0 mods keyNext $ \t s -> if
|
||||
| t == keyPress && s == keyNext -> modify succ
|
||||
| t == keyPress && s == keyPrev -> modify pred
|
||||
| t == keyPress && s `elem` [xK_0..xK_9] -> put (numKeyToN s)
|
||||
| otherwise -> preview
|
||||
where numKeyToN = subtract 48 . read . show
|
||||
|
||||
-- | Given a stack element and a stack, shift or insert the element (window)
|
||||
-- at the currently focused position.
|
||||
@ -229,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
|
||||
|
@ -25,11 +25,10 @@ module XMonad.Actions.CycleWorkspaceByScreen (
|
||||
|
||||
import Data.IORef
|
||||
|
||||
import Graphics.X11.Xlib.Extras
|
||||
|
||||
import XMonad
|
||||
import XMonad.Prelude
|
||||
import XMonad.Hooks.WorkspaceHistory
|
||||
import XMonad.Actions.Repeatable (repeatable)
|
||||
import qualified XMonad.StackSet as W
|
||||
|
||||
-- $usage
|
||||
@ -53,22 +52,9 @@ import qualified XMonad.StackSet as W
|
||||
--
|
||||
-- > , ((mod4Mask, xK_slash), cycleWorkspaceOnCurrentScreen [xK_Super_L] xK_slash xK_p)
|
||||
|
||||
repeatableAction :: [KeySym] -> (EventType -> KeySym -> X ()) -> X ()
|
||||
repeatableAction mods pressHandler = do
|
||||
XConf {theRoot = root, display = d} <- ask
|
||||
let getNextEvent = io $ allocaXEvent $ \p ->
|
||||
do
|
||||
maskEvent d (keyPressMask .|. keyReleaseMask) p
|
||||
KeyEvent {ev_event_type = t, ev_keycode = c} <- getEvent p
|
||||
s <- io $ keycodeToKeysym d c 0
|
||||
return (t, s)
|
||||
handleEvent (t, s)
|
||||
| t == keyRelease && s `elem` mods = return ()
|
||||
| otherwise = pressHandler t s >> getNextEvent >>= handleEvent
|
||||
|
||||
io $ grabKeyboard d root False grabModeAsync grabModeAsync currentTime
|
||||
getNextEvent >>= handleEvent
|
||||
io $ ungrabKeyboard d currentTime
|
||||
{-# DEPRECATED repeatableAction "Use XMonad.Actions.Repeatable.repeatable" #-}
|
||||
repeatableAction :: [KeySym] -> KeySym -> (EventType -> KeySym -> X ()) -> X ()
|
||||
repeatableAction = repeatable
|
||||
|
||||
handleKeyEvent :: EventType
|
||||
-> KeySym
|
||||
@ -109,8 +95,7 @@ cycleWorkspaceOnScreen screenId mods nextKey prevKey = workspaceHistoryTransacti
|
||||
return $ cycleWorkspaces !! current
|
||||
focusIncrement i = io (getAndIncrementWS i) >>= (windows . W.greedyView)
|
||||
|
||||
focusIncrement 1 -- Do the first workspace cycle
|
||||
repeatableAction mods $
|
||||
repeatable mods nextKey $
|
||||
runFirst
|
||||
[ handleKeyEvent keyPress nextKey $ focusIncrement 1
|
||||
, handleKeyEvent keyPress prevKey $ focusIncrement (-1)
|
||||
|
@ -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
|
||||
--
|
||||
@ -48,7 +48,7 @@ import XMonad
|
||||
-- > , ((modm, xK_d ), withFocused demanage)
|
||||
--
|
||||
-- For detailed instructions on editing your key bindings, see
|
||||
-- "XMonad.Doc.Extending#Editing_key_bindings".
|
||||
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial>.
|
||||
|
||||
-- | Stop managing the currently focused window.
|
||||
demanage :: Window -> X ()
|
||||
|
@ -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
|
||||
--
|
||||
@ -40,7 +40,7 @@ import qualified Data.List.NonEmpty as NE
|
||||
-- > , ((modm, xK_Return), dwmpromote)
|
||||
--
|
||||
-- For detailed instructions on editing your key bindings, see
|
||||
-- "XMonad.Doc.Extending#Editing_key_bindings".
|
||||
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial>.
|
||||
|
||||
-- | Swap the focused window with the master window. If focus is in
|
||||
-- the master, swap it with the next window in the stack. Focus
|
||||
|
@ -69,7 +69,9 @@ import qualified XMonad.Util.ExtensibleState as XS
|
||||
-- the working directory to the one configured for the matching
|
||||
-- project. If the workspace doesn't have any windows, the project's
|
||||
-- start-up hook is executed. This allows you to launch applications
|
||||
-- or further configure the workspace/project.
|
||||
-- or further configure the workspace/project. To close a project,
|
||||
-- you can use the functions provided by "XMonad.Actions.DynamicWorkspaces",
|
||||
-- such as @removeWorkspace@ or @removeWorkspaceByTag@.
|
||||
--
|
||||
-- When using the @switchProjectPrompt@ function, workspaces are
|
||||
-- created as needed. This means you can create new project spaces
|
||||
@ -113,7 +115,7 @@ import qualified XMonad.Util.ExtensibleState as XS
|
||||
-- > , ((modm, xK_slash), shiftToProjectPrompt def)
|
||||
--
|
||||
-- For detailed instructions on editing your key bindings, see
|
||||
-- "XMonad.Doc.Extending#Editing_key_bindings".
|
||||
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial>.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
type ProjectName = String
|
||||
@ -230,7 +232,9 @@ lookupProject name = Map.lookup name <$> XS.gets projects
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- | Fetch the current project (the one being used for the currently
|
||||
-- active workspace).
|
||||
-- active workspace). If the workspace doesn't have a project, a
|
||||
-- default project is returned, using the workspace name as the
|
||||
-- project name.
|
||||
currentProject :: X Project
|
||||
currentProject = do
|
||||
name <- gets (W.tag . W.workspace . W.current . windowset)
|
||||
@ -255,20 +259,7 @@ modifyProject f = do
|
||||
--------------------------------------------------------------------------------
|
||||
-- | Switch to the given project.
|
||||
switchProject :: Project -> X ()
|
||||
switchProject p = do
|
||||
oldws <- gets (W.workspace . W.current . windowset)
|
||||
oldp <- currentProject
|
||||
|
||||
let name = W.tag oldws
|
||||
ws = W.integrate' (W.stack oldws)
|
||||
|
||||
-- If the project we are switching away from has no windows, and
|
||||
-- it's a dynamic project, remove it from the configuration.
|
||||
when (null ws && isNothing (projectStartHook oldp)) $ do
|
||||
removeWorkspaceByTag name -- also remove the old workspace
|
||||
XS.modify (\s -> s {projects = Map.delete name $ projects s})
|
||||
|
||||
appendWorkspace (projectName p)
|
||||
switchProject p = appendWorkspace (projectName p)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- | Prompt for a project name and then switch to it. Automatically
|
||||
|
@ -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
|
||||
--
|
||||
|
@ -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
|
||||
--
|
||||
|
@ -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)
|
||||
@ -75,7 +75,7 @@ import qualified XMonad.Util.ExtensibleState as XS
|
||||
-- > zip (zip (repeat (modm .|. controlMask)) [xK_1..xK_9]) (map (setWorkspaceIndex) [1..])
|
||||
--
|
||||
-- For detailed instructions on editing your key bindings, see
|
||||
-- "XMonad.Doc.Extending#Editing_key_bindings". See also the documentation for
|
||||
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial>. See also the documentation for
|
||||
-- "XMonad.Actions.CopyWindow", 'windows', 'shift', and 'XPConfig'.
|
||||
|
||||
type WorkspaceTag = String
|
||||
|
@ -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 = [] })
|
||||
|
@ -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
|
||||
--
|
||||
@ -38,7 +38,7 @@ import XMonad.StackSet
|
||||
-- will tag the current window to an empty workspace and view it.
|
||||
--
|
||||
-- For detailed instructions on editing your key bindings, see
|
||||
-- "XMonad.Doc.Extending#Editing_key_bindings".
|
||||
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial>.
|
||||
|
||||
-- | Find the first hidden empty workspace in a StackSet. Returns
|
||||
-- Nothing if all workspaces are in use. Function searches currently
|
||||
|
@ -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
|
||||
--
|
||||
|
@ -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
|
||||
--
|
||||
|
@ -19,14 +19,18 @@ module XMonad.Actions.FloatKeys (
|
||||
keysMoveWindowTo,
|
||||
keysResizeWindow,
|
||||
keysAbsResizeWindow,
|
||||
directionMoveWindow,
|
||||
directionResizeWindow,
|
||||
Direction2D(..),
|
||||
P, G, ChangeDim
|
||||
) where
|
||||
|
||||
import XMonad
|
||||
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
|
||||
--
|
||||
@ -38,8 +42,36 @@ import XMonad.Prelude (fi)
|
||||
-- > , ((modm .|. shiftMask, xK_s ), withFocused (keysAbsResizeWindow (10,10) (1024,752)))
|
||||
-- > , ((modm, xK_a ), withFocused (keysMoveWindowTo (512,384) (1%2,1%2)))
|
||||
--
|
||||
-- Using "XMonad.Util.EZConfig" syntax, we can easily build keybindings
|
||||
-- where @M-\<arrow-keys\>@ moves the currently focused window and
|
||||
-- @M-S-\<arrow-keys\>@ resizes it using 'directionMoveWindow' and
|
||||
-- 'directionResizeWindow':
|
||||
--
|
||||
-- > [ ("M-" <> m <> k, withFocused $ f i)
|
||||
-- > | (i, k) <- zip [U, D, R, L] ["<Up>", "<Down>", "<Right>", "<Left>"]
|
||||
-- > , (f, m) <- [(directionMoveWindow 10, ""), (directionResizeWindow 10, "S-")]
|
||||
-- > ]
|
||||
--
|
||||
-- For detailed instructions on editing your key bindings, see
|
||||
-- "XMonad.Doc.Extending#Editing_key_bindings".
|
||||
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial>.
|
||||
|
||||
-- | @directionMoveWindow delta dir win@ moves the window @win@ by
|
||||
-- @delta@ pixels in direction @dir@.
|
||||
directionMoveWindow :: Int -> Direction2D -> Window -> X ()
|
||||
directionMoveWindow delta dir win = case dir of
|
||||
U -> keysMoveWindow (0, -delta) win
|
||||
D -> keysMoveWindow (0, delta) win
|
||||
R -> keysMoveWindow (delta, 0) win
|
||||
L -> keysMoveWindow (-delta, 0) win
|
||||
|
||||
-- | @directionResizeWindow delta dir win@ resizes the window @win@ by
|
||||
-- @delta@ pixels in direction @dir@.
|
||||
directionResizeWindow :: Int -> Direction2D -> Window -> X ()
|
||||
directionResizeWindow delta dir win = case dir of
|
||||
U -> keysResizeWindow (0, -delta) (0, 0) win
|
||||
D -> keysResizeWindow (0, delta) (0, 0) win
|
||||
R -> keysResizeWindow (delta, 0) (0, 0) win
|
||||
L -> keysResizeWindow (-delta, 0) (0, 0) win
|
||||
|
||||
-- | @keysMoveWindow (dx, dy)@ moves the window by @dx@ pixels to the
|
||||
-- right and @dy@ pixels down.
|
||||
|
@ -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
|
||||
--
|
||||
@ -53,7 +53,7 @@ import XMonad.Actions.AfterDrag
|
||||
-- > , ((modm .|. shiftMask, xK_Down), withFocused $ snapGrow D Nothing)
|
||||
--
|
||||
-- For detailed instructions on editing your key bindings, see
|
||||
-- "XMonad.Doc.Extending#Editing_key_bindings".
|
||||
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial>.
|
||||
--
|
||||
-- And possibly add appropriate mouse bindings, for example:
|
||||
--
|
||||
|
@ -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
|
||||
--
|
||||
@ -36,7 +36,7 @@ import XMonad.StackSet
|
||||
-- > | (i, k) <- zip [0 .. 8] [xK_1 ..]]
|
||||
--
|
||||
-- For detailed instructions on editing your key bindings, see
|
||||
-- "XMonad.Doc.Extending#Editing_key_bindings".
|
||||
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial>.
|
||||
|
||||
-- | Give focus to the nth window of the current workspace.
|
||||
focusNth :: Int -> X ()
|
||||
|
@ -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
|
||||
--
|
||||
@ -142,8 +143,8 @@ import Data.Word (Word8)
|
||||
--
|
||||
-- Then you can bind to:
|
||||
--
|
||||
-- > ,((modm, xK_g), goToSelected $ gsconfig2 myWinColorizer)
|
||||
-- > ,((modm, xK_p), spawnSelected $ spawnSelected defaultColorizer)
|
||||
-- > ,((modm, xK_g), goToSelected $ gsconfig2 myWinColorizer)
|
||||
-- > ,((modm, xK_p), spawnSelected (gsconfig2 defaultColorizer) ["xterm","gvim"])
|
||||
|
||||
-- $keybindings
|
||||
--
|
||||
@ -202,10 +203,13 @@ data GSConfig a = GSConfig {
|
||||
gs_colorizer :: a -> Bool -> X (String, String),
|
||||
gs_font :: String,
|
||||
gs_navigate :: TwoD a (Maybe a),
|
||||
-- ^ Customize key bindings for a GridSelect
|
||||
gs_rearranger :: Rearranger a,
|
||||
gs_originFractX :: Double,
|
||||
gs_originFractY :: Double,
|
||||
gs_bordercolor :: String
|
||||
gs_bordercolor :: String,
|
||||
gs_cancelOnEmptyClick :: Bool
|
||||
-- ^ When True, click on empty space will cancel GridSelect
|
||||
}
|
||||
|
||||
-- | That is 'fromClassName' if you are selecting a 'Window', or
|
||||
@ -302,14 +306,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)
|
||||
@ -385,13 +389,20 @@ updateElementsWithColorizer colorizer elementmap = do
|
||||
stdHandle :: Event -> TwoD a (Maybe a) -> TwoD a (Maybe a)
|
||||
stdHandle ButtonEvent{ ev_event_type = t, ev_x = x, ev_y = y } contEventloop
|
||||
| t == buttonRelease = do
|
||||
s@TwoDState { td_paneX = px, td_paneY = py,
|
||||
td_gsconfig = (GSConfig ch cw _ _ _ _ _ _ _ _) } <- get
|
||||
s@TwoDState{ td_paneX = px
|
||||
, td_paneY = py
|
||||
, td_gsconfig = GSConfig{ gs_cellheight = ch
|
||||
, gs_cellwidth = cw
|
||||
, gs_cancelOnEmptyClick = cancelOnEmptyClick
|
||||
}
|
||||
} <- get
|
||||
let gridX = (fi x - (px - cw) `div` 2) `div` cw
|
||||
gridY = (fi y - (py - ch) `div` 2) `div` ch
|
||||
case lookup (gridX,gridY) (td_elementmap s) of
|
||||
Just (_,el) -> return (Just el)
|
||||
Nothing -> contEventloop
|
||||
Nothing -> if cancelOnEmptyClick
|
||||
then return Nothing
|
||||
else contEventloop
|
||||
| otherwise = contEventloop
|
||||
|
||||
stdHandle ExposeEvent{} contEventloop = updateAllElements >> contEventloop
|
||||
@ -647,7 +658,7 @@ gridselect gsconfig elements =
|
||||
liftIO $ mapWindow dpy win
|
||||
liftIO $ selectInput dpy win (exposureMask .|. keyPressMask .|. buttonReleaseMask)
|
||||
status <- io $ grabKeyboard dpy win True grabModeAsync grabModeAsync currentTime
|
||||
io $ grabPointer dpy win True buttonReleaseMask grabModeAsync grabModeAsync none none currentTime
|
||||
void $ io $ grabPointer dpy win True buttonReleaseMask grabModeAsync grabModeAsync none none currentTime
|
||||
font <- initXMF (gs_font gsconfig)
|
||||
let screenWidth = toInteger $ rect_width scr
|
||||
screenHeight = toInteger $ rect_height scr
|
||||
@ -658,7 +669,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,
|
||||
@ -705,7 +716,7 @@ decorateName' w = do
|
||||
|
||||
-- | Builds a default gs config from a colorizer function.
|
||||
buildDefaultGSConfig :: (a -> Bool -> X (String,String)) -> GSConfig a
|
||||
buildDefaultGSConfig col = GSConfig 50 130 10 col "xft:Sans-8" defaultNavigation noRearranger (1/2) (1/2) "white"
|
||||
buildDefaultGSConfig col = GSConfig 50 130 10 col "xft:Sans-8" defaultNavigation noRearranger (1/2) (1/2) "white" True
|
||||
|
||||
-- | Brings selected window to the current workspace.
|
||||
bringSelected :: GSConfig Window -> X ()
|
||||
|
@ -33,8 +33,8 @@ module XMonad.Actions.GroupNavigation ( -- * Usage
|
||||
, isOnAnyVisibleWS
|
||||
) where
|
||||
|
||||
import Control.Monad.Reader
|
||||
import Control.Monad.State
|
||||
import Control.Monad.Reader (ask, asks)
|
||||
import Control.Monad.State (gets)
|
||||
import Control.DeepSeq
|
||||
import Data.Map ((!))
|
||||
import qualified Data.Map as Map
|
||||
@ -47,13 +47,13 @@ import Prelude hiding (drop, elem, filter, null, reverse)
|
||||
import XMonad.Core
|
||||
import XMonad.ManageHook
|
||||
import XMonad.Operations (windows, withFocused)
|
||||
import XMonad.Prelude (elem, foldl')
|
||||
import XMonad.Prelude (elem, foldl', (>=>))
|
||||
import qualified XMonad.StackSet as SS
|
||||
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
|
||||
|
@ -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
|
||||
--
|
||||
@ -58,7 +58,7 @@ import qualified Data.Map as M
|
||||
-- > , (i, k) <- zip (XMonad.workspaces conf) [xK_1 .. xK_9]]
|
||||
--
|
||||
-- For detailed instructions on editing your key bindings, see
|
||||
-- "XMonad.Doc.Extending#Editing_key_bindings".
|
||||
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial>.
|
||||
|
||||
data MessageConfig = MessageConfig { messageFunction :: ScreenId -> [Char] -> [Char] -> [Char] -> X()
|
||||
, foreground :: [Char]
|
||||
|
@ -42,24 +42,18 @@ module XMonad.Actions.MessageFeedback
|
||||
|
||||
-- ** Aliases
|
||||
, sm
|
||||
|
||||
-- * Backwards Compatibility
|
||||
-- $backwardsCompatibility
|
||||
, send, sendSM, sendSM_
|
||||
, tryInOrder, tryInOrder_
|
||||
, tryMessage, tryMessage_
|
||||
) where
|
||||
|
||||
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
|
||||
--
|
||||
@ -230,46 +224,3 @@ tryMessageWithNoRefreshToCurrent m = void . tryMessageWithNoRefreshToCurrentB m
|
||||
-- | Convenience shorthand for 'SomeMessage'.
|
||||
sm :: Message a => a -> SomeMessage
|
||||
sm = SomeMessage
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Backwards Compatibility:
|
||||
--------------------------------------------------------------------------------
|
||||
{-# DEPRECATED send "Use sendMessageB instead." #-}
|
||||
{-# DEPRECATED sendSM "Use sendSomeMessageB instead." #-}
|
||||
{-# DEPRECATED sendSM_ "Use sendSomeMessage instead." #-}
|
||||
{-# DEPRECATED tryInOrder "Use tryInOrderWithNoRefreshToCurrentB instead." #-}
|
||||
{-# DEPRECATED tryInOrder_ "Use tryInOrderWithNoRefreshToCurrent instead." #-}
|
||||
{-# DEPRECATED tryMessage "Use tryMessageWithNoRefreshToCurrentB instead." #-}
|
||||
{-# DEPRECATED tryMessage_ "Use tryMessageWithNoRefreshToCurrent instead." #-}
|
||||
|
||||
-- $backwardsCompatibility
|
||||
-- The following functions exist solely for compatibility with pre-0.14
|
||||
-- releases.
|
||||
|
||||
-- | See 'sendMessageWithNoRefreshToCurrentB'.
|
||||
send :: Message a => a -> X Bool
|
||||
send = sendMessageWithNoRefreshToCurrentB
|
||||
|
||||
-- | See 'sendSomeMessageWithNoRefreshToCurrentB'.
|
||||
sendSM :: SomeMessage -> X Bool
|
||||
sendSM = sendSomeMessageWithNoRefreshToCurrentB
|
||||
|
||||
-- | See 'sendSomeMessageWithNoRefreshToCurrent'.
|
||||
sendSM_ :: SomeMessage -> X ()
|
||||
sendSM_ = sendSomeMessageWithNoRefreshToCurrent
|
||||
|
||||
-- | See 'tryInOrderWithNoRefreshToCurrentB'.
|
||||
tryInOrder :: [SomeMessage] -> X Bool
|
||||
tryInOrder = tryInOrderWithNoRefreshToCurrentB
|
||||
|
||||
-- | See 'tryInOrderWithNoRefreshToCurrent'.
|
||||
tryInOrder_ :: [SomeMessage] -> X ()
|
||||
tryInOrder_ = tryInOrderWithNoRefreshToCurrent
|
||||
|
||||
-- | See 'tryMessageWithNoRefreshToCurrentB'.
|
||||
tryMessage :: (Message a, Message b) => a -> b -> X Bool
|
||||
tryMessage = tryMessageWithNoRefreshToCurrentB
|
||||
|
||||
-- | See 'tryMessageWithNoRefreshToCurrent'.
|
||||
tryMessage_ :: (Message a, Message b) => a -> b -> X ()
|
||||
tryMessage_ = tryMessageWithNoRefreshToCurrent
|
||||
|
@ -12,7 +12,7 @@
|
||||
-- Adds actions for minimizing and maximizing windows
|
||||
--
|
||||
-- This module should be used with "XMonad.Layout.Minimize". Add 'minimize' to your
|
||||
-- layout modifiers as described in "XMonad.Layout.Minimized" and use actions from
|
||||
-- layout modifiers as described in "XMonad.Layout.Minimize" and use actions from
|
||||
-- this module
|
||||
--
|
||||
-- Possible keybindings:
|
||||
|
205
XMonad/Actions/MostRecentlyUsed.hs
Normal file
205
XMonad/Actions/MostRecentlyUsed.hs
Normal file
@ -0,0 +1,205 @@
|
||||
{-# LANGUAGE NamedFieldPuns, GeneralizedNewtypeDeriving #-}
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- |
|
||||
-- Module : XMonad.Actions.MostRecentlyUsed
|
||||
-- Description : Tab through windows by recency of use.
|
||||
-- Copyright : (c) 2022 L. S. Leary
|
||||
-- License : BSD3-style (see LICENSE)
|
||||
--
|
||||
-- Maintainer : @LSLeary (on github)
|
||||
-- Stability : unstable
|
||||
-- Portability : unportable
|
||||
--
|
||||
-- Based on the Alt+Tab behaviour common outside of xmonad.
|
||||
--
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
-- --< Imports & Exports >-- {{{
|
||||
|
||||
module XMonad.Actions.MostRecentlyUsed (
|
||||
|
||||
-- * Usage
|
||||
-- $usage
|
||||
|
||||
-- * Interface
|
||||
configureMRU,
|
||||
mostRecentlyUsed,
|
||||
withMostRecentlyUsed,
|
||||
Location(..),
|
||||
|
||||
) where
|
||||
|
||||
-- base
|
||||
import Data.List.NonEmpty (nonEmpty)
|
||||
import Data.IORef (newIORef, readIORef, writeIORef, modifyIORef)
|
||||
import Control.Monad.IO.Class (MonadIO)
|
||||
|
||||
-- mtl
|
||||
import Control.Monad.Trans (lift)
|
||||
import Control.Monad.State (get, put, gets)
|
||||
|
||||
-- containers
|
||||
import qualified Data.Map.Strict as M
|
||||
|
||||
-- xmonad
|
||||
import XMonad
|
||||
( Window, KeySym, keyPress, io
|
||||
, Event (DestroyWindowEvent, UnmapEvent, ev_send_event, ev_window)
|
||||
)
|
||||
import XMonad.Core
|
||||
( X, XConfig(..), windowset, WorkspaceId, ScreenId
|
||||
, ExtensionClass(..), StateExtension(..)
|
||||
, waitingUnmap
|
||||
)
|
||||
import XMonad.Operations (screenWorkspace)
|
||||
import qualified XMonad.StackSet as W
|
||||
|
||||
-- xmonad-contrib
|
||||
import qualified XMonad.Util.ExtensibleConf as XC
|
||||
import qualified XMonad.Util.ExtensibleState as XS
|
||||
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
|
||||
|
||||
-- }}}
|
||||
|
||||
-- --< Core Data Types: WindowHistory & Location >-- {{{
|
||||
|
||||
data WindowHistory = WinHist
|
||||
{ busy :: !Bool
|
||||
, hist :: !(History Window Location)
|
||||
} deriving (Show, Read)
|
||||
|
||||
instance ExtensionClass WindowHistory where
|
||||
initialValue = WinHist
|
||||
{ busy = False
|
||||
, hist = origin
|
||||
}
|
||||
extensionType = PersistentExtension
|
||||
|
||||
data Location = Location
|
||||
{ workspace :: !WorkspaceId
|
||||
, screen :: !ScreenId
|
||||
} deriving (Show, Read, Eq, Ord)
|
||||
|
||||
-- }}}
|
||||
|
||||
-- --< Interface >-- {{{
|
||||
|
||||
-- $usage
|
||||
--
|
||||
-- 'configureMRU' must be applied to your config in order for 'mostRecentlyUsed'
|
||||
-- to work.
|
||||
--
|
||||
-- > main :: IO ()
|
||||
-- > main = xmonad . configureMRU . ... $ def
|
||||
-- > { ...
|
||||
-- > }
|
||||
--
|
||||
-- Once that's done, it can be used normally in keybinds:
|
||||
--
|
||||
-- > , ((mod1Mask, xK_Tab), mostRecentlyUsed [xK_Alt_L, xK_Alt_R] xK_Tab)
|
||||
--
|
||||
-- N.B.: This example assumes that 'mod1Mask' corresponds to alt, which is not
|
||||
-- always the case, depending on how your system is configured.
|
||||
|
||||
-- | Configure xmonad to support 'mostRecentlyUsed'.
|
||||
configureMRU :: XConfig l -> XConfig l
|
||||
configureMRU = XC.once f (MRU ()) where
|
||||
f cnf = cnf
|
||||
{ logHook = logHook cnf <> logWinHist
|
||||
, handleEventHook = handleEventHook cnf <> winHistEH
|
||||
}
|
||||
newtype MRU = MRU () deriving Semigroup
|
||||
|
||||
-- | An action to browse through the history of focused windows, taking
|
||||
-- another step back with each tap of the key.
|
||||
mostRecentlyUsed
|
||||
:: [KeySym] -- ^ The 'KeySym's corresponding to the modifier to which the
|
||||
-- action is bound.
|
||||
-> KeySym -- ^ The 'KeySym' corresponding to the key to which the action
|
||||
-- is bound.
|
||||
-> X ()
|
||||
mostRecentlyUsed mods key = do
|
||||
(toUndo, undo) <- undoer
|
||||
let undoably curThing withThing thing = curThing >>= \cur ->
|
||||
when (cur /= thing) $ withThing thing >> toUndo (withThing cur)
|
||||
withMostRecentlyUsed mods key $ \win Location{workspace,screen} ->
|
||||
handlingRefresh $ do
|
||||
undo
|
||||
undoably curScreenId viewScreen screen
|
||||
undoably curTag greedyView workspace
|
||||
mi <- gets (W.findTag win . windowset)
|
||||
for_ mi $ \i -> do
|
||||
undoably curTag greedyView i
|
||||
mfw <- peek
|
||||
for_ mfw $ \fw -> do
|
||||
undoably (pure fw) focusWindow win
|
||||
where
|
||||
undoer :: (MonadIO m, Monoid a) => m (m a -> m (), m a)
|
||||
undoer = do
|
||||
ref <- io . newIORef $ pure mempty
|
||||
let toUndo = io . modifyIORef ref . liftA2 (<>)
|
||||
undo = join (io $ readIORef ref)
|
||||
<* io (writeIORef ref $ pure mempty)
|
||||
pure (toUndo, undo)
|
||||
viewScreen :: ScreenId -> X Any
|
||||
viewScreen scr = screenWorkspace scr >>= foldMap view
|
||||
|
||||
-- | A version of 'mostRecentlyUsed' that allows you to customise exactly what
|
||||
-- is done with each window you tab through (the default being to visit its
|
||||
-- previous 'Location' and give it focus).
|
||||
withMostRecentlyUsed
|
||||
:: [KeySym] -- ^ The 'KeySym's corresponding to the
|
||||
-- modifier to which the action is bound.
|
||||
-> KeySym -- ^ The 'KeySym' corresponding to the key to
|
||||
-- which the action is bound.
|
||||
-> (Window -> Location -> X ()) -- ^ The function applied to each window.
|
||||
-> X ()
|
||||
withMostRecentlyUsed mods tab preview = do
|
||||
wh@WinHist{busy,hist} <- XS.get
|
||||
unless busy $ do
|
||||
XS.put wh{ busy = True }
|
||||
|
||||
for_ (nonEmpty $ ledger hist) $ \ne -> do
|
||||
mfw <- gets (W.peek . windowset)
|
||||
let iSt = case cycleS ne of
|
||||
(w, _) :~ s | mfw == Just w -> s
|
||||
s -> s
|
||||
repeatableSt iSt mods tab $ \t s ->
|
||||
when (t == keyPress && s == tab) (pop >>= lift . uncurry preview)
|
||||
|
||||
XS.modify $ \ws@WinHist{} -> ws{ busy = False }
|
||||
logWinHist
|
||||
where
|
||||
pop = do
|
||||
h :~ t <- get
|
||||
put t $> h
|
||||
|
||||
-- }}}
|
||||
|
||||
-- --< Raw Config >-- {{{
|
||||
|
||||
logWinHist :: X ()
|
||||
logWinHist = do
|
||||
wh@WinHist{busy,hist} <- XS.get
|
||||
unless busy $ do
|
||||
cs <- gets (W.current . windowset)
|
||||
let cws = W.workspace cs
|
||||
for_ (W.stack cws) $ \st -> do
|
||||
let location = Location{ workspace = W.tag cws, screen = W.screen cs }
|
||||
XS.put wh{ hist = event (W.focus st) location hist }
|
||||
|
||||
winHistEH :: Event -> X All
|
||||
winHistEH ev = All True <$ case ev of
|
||||
UnmapEvent{ ev_send_event = synth, ev_window = w } -> do
|
||||
e <- gets (fromMaybe 0 . M.lookup w . waitingUnmap)
|
||||
when (synth || e == 0) (collect w)
|
||||
DestroyWindowEvent{ ev_window = w } -> collect w
|
||||
_ -> pure ()
|
||||
where collect w = XS.modify $ \wh@WinHist{hist} -> wh{ hist = erase w hist }
|
||||
|
||||
-- }}}
|
@ -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
|
||||
|
@ -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
|
||||
@ -50,9 +50,9 @@ import XMonad.Util.XUtils
|
||||
--
|
||||
-- > main = xmonad def { layoutHook = myLayout }
|
||||
--
|
||||
-- For more detailed instructions on editing the layoutHook see:
|
||||
--
|
||||
-- "XMonad.Doc.Extending#Editing_the_layout_hook"
|
||||
-- For more detailed instructions on editing the layoutHook see
|
||||
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial> and
|
||||
-- "XMonad.Doc.Extending#Editing_the_layout_hook".
|
||||
|
||||
mouseResize :: l a -> ModifiedLayout MouseResize l a
|
||||
mouseResize = ModifiedLayout (MR [])
|
||||
|
@ -46,7 +46,6 @@ module XMonad.Actions.Navigation2D ( -- * Usage
|
||||
, sideNavigation
|
||||
, sideNavigationWithBias
|
||||
, hybridOf
|
||||
, hybridNavigation
|
||||
, fullScreenRect
|
||||
, singleWindowRect
|
||||
, switchLayer
|
||||
@ -67,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#
|
||||
@ -85,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
|
||||
--
|
||||
@ -98,7 +98,15 @@ import XMonad.Util.Types
|
||||
-- > False
|
||||
-- > $ def
|
||||
--
|
||||
-- Alternatively, you can use navigation2DP:
|
||||
-- /NOTE/: the @def@ argument to 'navigation2D' contains the strategy
|
||||
-- that decides which windows actually get selected. While the default
|
||||
-- behaviour tries to keep them into account, if you use modules that
|
||||
-- influence tiling in some way, like "XMonad.Layout.Spacing" or
|
||||
-- "XMonad.Layout.Gaps", you should think about using a different
|
||||
-- strategy, if you find the default behaviour to be unnatural. Check
|
||||
-- out the [finer points](#g:Finer_Points) below for more information.
|
||||
--
|
||||
-- Alternatively to 'navigation2D', you can use 'navigation2DP':
|
||||
--
|
||||
-- > main = xmonad $ navigation2DP def
|
||||
-- > ("<Up>", "<Left>", "<Down>", "<Right>")
|
||||
@ -108,7 +116,7 @@ import XMonad.Util.Types
|
||||
-- > $ def
|
||||
--
|
||||
-- That's it. If instead you'd like more control, you can combine
|
||||
-- withNavigation2DConfig and additionalNav2DKeys or additionalNav2DKeysP:
|
||||
-- 'withNavigation2DConfig' and 'additionalNav2DKeys' or 'additionalNav2DKeysP':
|
||||
--
|
||||
-- > main = xmonad $ withNavigation2DConfig def
|
||||
-- > $ additionalNav2DKeys (xK_Up, xK_Left, xK_Down, xK_Right)
|
||||
@ -162,7 +170,7 @@ import XMonad.Util.Types
|
||||
--
|
||||
-- For detailed instruction on editing the key binding see:
|
||||
--
|
||||
-- "XMonad.Doc.Extending#Editing_key_bindings".
|
||||
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial>.
|
||||
|
||||
-- $finer_points
|
||||
-- #Finer_Points#
|
||||
@ -179,9 +187,19 @@ import XMonad.Util.Types
|
||||
-- values in the above example to 'True'. You could also decide you want
|
||||
-- wrapping only for a subset of the operations and no wrapping for others.
|
||||
--
|
||||
-- By default, all layouts use the 'defaultTiledNavigation' strategy specified
|
||||
-- in the 'Navigation2DConfig' (by default, line navigation is used). To
|
||||
-- override this behaviour for some layouts, add a pair (\"layout name\",
|
||||
-- By default, all layouts use the 'defaultTiledNavigation' strategy
|
||||
-- specified in the 'Navigation2DConfig' (by default, line navigation is
|
||||
-- used). Many more navigation strategies are available; some may feel
|
||||
-- more natural, depending on the layout and user:
|
||||
--
|
||||
-- * 'lineNavigation'
|
||||
-- * 'centerNavigation'
|
||||
-- * 'sideNavigation'
|
||||
-- * 'sideNavigationWithBias'
|
||||
--
|
||||
-- There is also the ability to combine two strategies with 'hybridOf'.
|
||||
--
|
||||
-- To override the default behaviour for some layouts, add a pair (\"layout name\",
|
||||
-- navigation strategy) to the 'layoutNavigation' list in the
|
||||
-- 'Navigation2DConfig', where \"layout name\" is the string reported by the
|
||||
-- layout's description method (normally what is shown as the layout name in
|
||||
@ -327,7 +345,7 @@ centerNavigation = N 2 doCenterNavigation
|
||||
-- and push it to the right until it intersects with at least one other window.
|
||||
-- Of those windows, one with a point that is the closest to the centre of the
|
||||
-- line (+1) is selected. This is probably the most intuitive strategy for the
|
||||
-- tiled layer when using XMonad.Layout.Spacing.
|
||||
-- tiled layer when using "XMonad.Layout.Spacing".
|
||||
sideNavigation :: Navigation2D
|
||||
sideNavigation = N 1 (doSideNavigationWithBias 1)
|
||||
|
||||
@ -359,10 +377,6 @@ hybridOf (N g1 s1) (N g2 s2) = N (max g1 g2) $ applyToBoth s1 s2
|
||||
where
|
||||
applyToBoth f g a b c = f a b c <|> g a b c
|
||||
|
||||
{-# DEPRECATED hybridNavigation "Use hybridOf with lineNavigation and centerNavigation as arguments." #-}
|
||||
hybridNavigation :: Navigation2D
|
||||
hybridNavigation = hybridOf lineNavigation centerNavigation
|
||||
|
||||
-- | Stores the configuration of directional navigation. The 'Default' instance
|
||||
-- uses line navigation for the tiled layer and for navigation between screens,
|
||||
-- and center navigation for the float layer. No custom navigation strategies
|
||||
@ -451,7 +465,7 @@ withNavigation2DConfig conf2d xconf = xconf { startupHook = startupHook xconf
|
||||
}
|
||||
|
||||
instance Default Navigation2DConfig where
|
||||
def = Navigation2DConfig { defaultTiledNavigation = lineNavigation
|
||||
def = Navigation2DConfig { defaultTiledNavigation = hybridOf lineNavigation sideNavigation
|
||||
, floatNavigation = centerNavigation
|
||||
, screenNavigation = lineNavigation
|
||||
, layoutNavigation = []
|
||||
@ -770,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.
|
||||
@ -871,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
|
||||
|
@ -1,155 +1,187 @@
|
||||
-----------------------------------------------------------------------------
|
||||
-- |
|
||||
-- Module : XMonad.Actions.OnScreen
|
||||
-- Description : Control workspaces on different screens (in xinerama mode).
|
||||
-- Copyright : (c) 2009 Nils Schweinsberg
|
||||
-- Copyright : (c) 2009-2025 Nils Schweinsberg
|
||||
-- License : BSD3-style (see LICENSE)
|
||||
--
|
||||
-- Maintainer : Nils Schweinsberg <mail@n-sch.de>
|
||||
-- Maintainer : Nils Schweinsberg <mail@nils.cc>
|
||||
-- Stability : unstable
|
||||
-- Portability : unportable
|
||||
--
|
||||
-- Control workspaces on different screens (in xinerama mode).
|
||||
--
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
module XMonad.Actions.OnScreen (
|
||||
-- * Usage
|
||||
module XMonad.Actions.OnScreen
|
||||
( -- * Usage
|
||||
-- $usage
|
||||
onScreen
|
||||
, onScreen'
|
||||
, Focus(..)
|
||||
, viewOnScreen
|
||||
, greedyViewOnScreen
|
||||
, onlyOnScreen
|
||||
, toggleOnScreen
|
||||
, toggleGreedyOnScreen
|
||||
) where
|
||||
onScreen,
|
||||
onScreen',
|
||||
Focus (..),
|
||||
viewOnScreen,
|
||||
greedyViewOnScreen,
|
||||
onlyOnScreen,
|
||||
toggleOnScreen,
|
||||
toggleGreedyOnScreen,
|
||||
)
|
||||
where
|
||||
|
||||
import XMonad
|
||||
import XMonad.Prelude (fromMaybe, guard)
|
||||
import XMonad.Prelude (empty, fromMaybe, guard)
|
||||
import XMonad.StackSet hiding (new)
|
||||
|
||||
|
||||
-- | Focus data definitions
|
||||
data Focus = FocusNew -- ^ always focus the new screen
|
||||
| FocusCurrent -- ^ always keep the focus on the current screen
|
||||
| FocusTag WorkspaceId -- ^ always focus tag i on the new stack
|
||||
| FocusTagVisible WorkspaceId -- ^ focus tag i only if workspace with tag i is visible on the old stack
|
||||
|
||||
data Focus
|
||||
= -- | always focus the new screen
|
||||
FocusNew
|
||||
| -- | always keep the focus on the current screen
|
||||
FocusCurrent
|
||||
| -- | always focus tag i on the new stack
|
||||
FocusTag WorkspaceId
|
||||
| -- | focus tag i only if workspace with tag i is visible on the old stack
|
||||
FocusTagVisible WorkspaceId
|
||||
|
||||
-- | Run any function that modifies the stack on a given screen. This function
|
||||
-- will also need to know which Screen to focus after the function has been
|
||||
-- run.
|
||||
onScreen :: (WindowSet -> WindowSet) -- ^ function to run
|
||||
-> Focus -- ^ what to do with the focus
|
||||
-> ScreenId -- ^ screen id
|
||||
-> WindowSet -- ^ current stack
|
||||
-> WindowSet
|
||||
onScreen ::
|
||||
-- | function to run
|
||||
(WindowSet -> WindowSet) ->
|
||||
-- | what to do with the focus
|
||||
Focus ->
|
||||
-- | screen id
|
||||
ScreenId ->
|
||||
-- | current stack
|
||||
WindowSet ->
|
||||
WindowSet
|
||||
onScreen f foc sc st = fromMaybe st $ do
|
||||
ws <- lookupWorkspace sc st
|
||||
ws <- lookupWorkspace sc st
|
||||
|
||||
let fStack = f $ view ws st
|
||||
|
||||
return $ setFocus foc st fStack
|
||||
let fStack = f $ view ws st
|
||||
|
||||
return $ setFocus foc st fStack
|
||||
|
||||
-- set focus for new stack
|
||||
setFocus :: Focus
|
||||
-> WindowSet -- ^ old stack
|
||||
-> WindowSet -- ^ new stack
|
||||
-> WindowSet
|
||||
setFocus FocusNew _ new = new
|
||||
setFocus FocusCurrent old new =
|
||||
case lookupWorkspace (screen $ current old) new of
|
||||
Nothing -> new
|
||||
Just i -> view i new
|
||||
setFocus (FocusTag i) _ new = view i new
|
||||
setFocus ::
|
||||
Focus ->
|
||||
-- | old stack
|
||||
WindowSet ->
|
||||
-- | new stack
|
||||
WindowSet ->
|
||||
WindowSet
|
||||
setFocus FocusNew _ new = new
|
||||
setFocus FocusCurrent old new =
|
||||
case lookupWorkspace (screen $ current old) new of
|
||||
Nothing -> new
|
||||
Just i -> view i new
|
||||
setFocus (FocusTag i) _ new = view i new
|
||||
setFocus (FocusTagVisible i) old new =
|
||||
if i `elem` map (tag . workspace) (visible old)
|
||||
then setFocus (FocusTag i) old new
|
||||
else setFocus FocusCurrent old new
|
||||
if i `elem` map (tag . workspace) (visible old)
|
||||
then setFocus (FocusTag i) old new
|
||||
else setFocus FocusCurrent old new
|
||||
|
||||
-- | A variation of @onScreen@ which will take any @X ()@ function and run it
|
||||
-- on the given screen.
|
||||
-- Warning: This function will change focus even if the function it's supposed
|
||||
-- to run doesn't succeed.
|
||||
onScreen' :: X () -- ^ X function to run
|
||||
-> Focus -- ^ focus
|
||||
-> ScreenId -- ^ screen id
|
||||
-> X ()
|
||||
onScreen' ::
|
||||
-- | X function to run
|
||||
X () ->
|
||||
-- | focus
|
||||
Focus ->
|
||||
-- | screen id
|
||||
ScreenId ->
|
||||
X ()
|
||||
onScreen' x foc sc = do
|
||||
st <- gets windowset
|
||||
case lookupWorkspace sc st of
|
||||
Nothing -> return ()
|
||||
Just ws -> do
|
||||
windows $ view ws
|
||||
x
|
||||
windows $ setFocus foc st
|
||||
|
||||
st <- gets windowset
|
||||
case lookupWorkspace sc st of
|
||||
Nothing -> return ()
|
||||
Just ws -> do
|
||||
windows $ view ws
|
||||
x
|
||||
windows $ setFocus foc st
|
||||
|
||||
-- | Switch to workspace @i@ on screen @sc@. If @i@ is visible use @view@ to
|
||||
-- switch focus to the workspace @i@.
|
||||
viewOnScreen :: ScreenId -- ^ screen id
|
||||
-> WorkspaceId -- ^ index of the workspace
|
||||
-> WindowSet -- ^ current stack
|
||||
-> WindowSet
|
||||
viewOnScreen ::
|
||||
-- | screen id
|
||||
ScreenId ->
|
||||
-- | index of the workspace
|
||||
WorkspaceId ->
|
||||
-- | current stack
|
||||
WindowSet ->
|
||||
WindowSet
|
||||
viewOnScreen sid i =
|
||||
onScreen (view i) (FocusTag i) sid
|
||||
onScreen (view i) (FocusTag i) sid
|
||||
|
||||
-- | Switch to workspace @i@ on screen @sc@. If @i@ is visible use @greedyView@
|
||||
-- to switch the current workspace with workspace @i@.
|
||||
greedyViewOnScreen :: ScreenId -- ^ screen id
|
||||
-> WorkspaceId -- ^ index of the workspace
|
||||
-> WindowSet -- ^ current stack
|
||||
-> WindowSet
|
||||
greedyViewOnScreen ::
|
||||
-- | screen id
|
||||
ScreenId ->
|
||||
-- | index of the workspace
|
||||
WorkspaceId ->
|
||||
-- | current stack
|
||||
WindowSet ->
|
||||
WindowSet
|
||||
greedyViewOnScreen sid i =
|
||||
onScreen (greedyView i) (FocusTagVisible i) sid
|
||||
onScreen (greedyView i) (FocusTagVisible i) sid
|
||||
|
||||
-- | Switch to workspace @i@ on screen @sc@. If @i@ is visible do nothing.
|
||||
onlyOnScreen :: ScreenId -- ^ screen id
|
||||
-> WorkspaceId -- ^ index of the workspace
|
||||
-> WindowSet -- ^ current stack
|
||||
-> WindowSet
|
||||
onlyOnScreen ::
|
||||
-- | screen id
|
||||
ScreenId ->
|
||||
-- | index of the workspace
|
||||
WorkspaceId ->
|
||||
-- | current stack
|
||||
WindowSet ->
|
||||
WindowSet
|
||||
onlyOnScreen sid i =
|
||||
onScreen (view i) FocusCurrent sid
|
||||
onScreen (view i) FocusCurrent sid
|
||||
|
||||
-- | @toggleOrView@ as in "XMonad.Actions.CycleWS" for @onScreen@ with view
|
||||
toggleOnScreen :: ScreenId -- ^ screen id
|
||||
-> WorkspaceId -- ^ index of the workspace
|
||||
-> WindowSet -- ^ current stack
|
||||
-> WindowSet
|
||||
toggleOnScreen ::
|
||||
-- | screen id
|
||||
ScreenId ->
|
||||
-- | index of the workspace
|
||||
WorkspaceId ->
|
||||
-- | current stack
|
||||
WindowSet ->
|
||||
WindowSet
|
||||
toggleOnScreen sid i =
|
||||
onScreen (toggleOrView' view i) FocusCurrent sid
|
||||
onScreen (toggleOrView' view i) FocusCurrent sid
|
||||
|
||||
-- | @toggleOrView@ from "XMonad.Actions.CycleWS" for @onScreen@ with greedyView
|
||||
toggleGreedyOnScreen :: ScreenId -- ^ screen id
|
||||
-> WorkspaceId -- ^ index of the workspace
|
||||
-> WindowSet -- ^ current stack
|
||||
-> WindowSet
|
||||
toggleGreedyOnScreen ::
|
||||
-- | screen id
|
||||
ScreenId ->
|
||||
-- | index of the workspace
|
||||
WorkspaceId ->
|
||||
-- | current stack
|
||||
WindowSet ->
|
||||
WindowSet
|
||||
toggleGreedyOnScreen sid i =
|
||||
onScreen (toggleOrView' greedyView i) FocusCurrent sid
|
||||
|
||||
onScreen (toggleOrView' greedyView i) FocusCurrent sid
|
||||
|
||||
-- a \"pure\" version of X.A.CycleWS.toggleOrDoSkip
|
||||
toggleOrView' :: (WorkspaceId -> WindowSet -> WindowSet) -- ^ function to run
|
||||
-> WorkspaceId -- ^ tag to look for
|
||||
-> WindowSet -- ^ current stackset
|
||||
-> WindowSet
|
||||
toggleOrView' ::
|
||||
-- | function to run
|
||||
(WorkspaceId -> WindowSet -> WindowSet) ->
|
||||
-- | tag to look for
|
||||
WorkspaceId ->
|
||||
-- | current stackset
|
||||
WindowSet ->
|
||||
WindowSet
|
||||
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
|
||||
|
||||
let st' = hidden st
|
||||
-- make sure we actually have to do something
|
||||
guard $ i == (tag . workspace $ current 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
|
||||
--
|
||||
@ -183,4 +215,4 @@ toggleOrView' f i st = fromMaybe (f i st) $ do
|
||||
-- where 0 is the first screen and \"1\" the workspace with the tag \"1\".
|
||||
--
|
||||
-- For detailed instructions on editing your key bindings, see
|
||||
-- "XMonad.Doc.Extending#Editing_key_bindings".
|
||||
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial>.
|
||||
|
@ -25,14 +25,14 @@ 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
|
||||
--
|
||||
-- > ,((0, xK_F2), bindByLayout [("Tall", spawn "rxvt"), ("Mirror Tall", spawn "xeyes"), ("", spawn "xmessage hello")])
|
||||
--
|
||||
-- For detailed instructions on editing your key bindings, see
|
||||
-- "XMonad.Doc.Extending#Editing_key_bindings".
|
||||
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial>.
|
||||
|
||||
-- | Uses supplied function to decide which action to run depending on current layout name.
|
||||
chooseActionByLayout :: (String->X()) -> X()
|
||||
|
@ -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
|
||||
--
|
||||
@ -41,7 +41,7 @@ import XMonad
|
||||
-- doThisIfTheOthersFail)]@.
|
||||
--
|
||||
-- For detailed instructions on editing your key bindings, see
|
||||
-- "XMonad.Doc.Extending#Editing_key_bindings".
|
||||
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial>.
|
||||
|
||||
-- | Run an action if a Query holds true. Doesn't stop at the first one that
|
||||
-- does, however, and could potentially run all actions.
|
||||
|
@ -25,14 +25,14 @@ 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
|
||||
--
|
||||
-- > ,((0, xK_F2), bindOn [("1", spawn "rxvt"), ("2", spawn "xeyes"), ("", spawn "xmessage hello")])
|
||||
--
|
||||
-- For detailed instructions on editing your key bindings, see
|
||||
-- "XMonad.Doc.Extending#Editing_key_bindings".
|
||||
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial>.
|
||||
|
||||
-- | Uses supplied function to decide which action to run depending on current workspace name.
|
||||
chooseAction :: (String->X()) -> X()
|
||||
|
@ -1,4 +1,6 @@
|
||||
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
||||
{-# LANGUAGE LambdaCase #-}
|
||||
{-# LANGUAGE ParallelListComp #-}
|
||||
-----------------------------------------------------------------------------
|
||||
-- |
|
||||
-- Module : XMonad.Actions.PhysicalScreens
|
||||
@ -28,10 +30,13 @@ module XMonad.Actions.PhysicalScreens (
|
||||
, getScreenIdAndRectangle
|
||||
, screenComparatorById
|
||||
, screenComparatorByRectangle
|
||||
, rescreen
|
||||
) where
|
||||
|
||||
import XMonad
|
||||
import XMonad.Prelude (elemIndex, fromMaybe, on, sortBy)
|
||||
import Data.List.NonEmpty (nonEmpty)
|
||||
import XMonad hiding (rescreen)
|
||||
import XMonad.Prelude (elemIndex, fromMaybe, on, sortBy, NonEmpty((:|)))
|
||||
import qualified Data.List.NonEmpty as NE
|
||||
import qualified XMonad.StackSet as W
|
||||
|
||||
{- $usage
|
||||
@ -46,7 +51,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
|
||||
@ -65,7 +70,7 @@ Example usage in your @~\/.xmonad\/xmonad.hs@ file:
|
||||
> , (f, mask) <- [(viewScreen def, 0), (sendToScreen def, shiftMask)]]
|
||||
|
||||
For detailed instructions on editing your key bindings, see
|
||||
"XMonad.Doc.Extending#Editing_key_bindings".
|
||||
<https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial>.
|
||||
-}
|
||||
|
||||
-- | The type of the index of a screen by location
|
||||
@ -75,7 +80,7 @@ getScreenIdAndRectangle :: W.Screen i l a ScreenId ScreenDetail -> (ScreenId, Re
|
||||
getScreenIdAndRectangle screen = (W.screen screen, rect) where
|
||||
rect = screenRect $ W.screenDetail screen
|
||||
|
||||
-- | Translate a physical screen index to a "ScreenId"
|
||||
-- | Translate a physical screen index to a 'ScreenId'
|
||||
getScreen:: ScreenComparator -> PhysicalScreen -> X (Maybe ScreenId)
|
||||
getScreen (ScreenComparator cmpScreen) (P i) = do w <- gets windowset
|
||||
let screens = W.current w : W.visible w
|
||||
@ -146,3 +151,53 @@ onNextNeighbour sc = neighbourWindows sc 1
|
||||
-- | Apply operation on a WindowSet with the WorkspaceId of the previous screen in the physical order as parameter.
|
||||
onPrevNeighbour :: ScreenComparator -> (WorkspaceId -> WindowSet -> WindowSet) -> X ()
|
||||
onPrevNeighbour sc = neighbourWindows sc (-1)
|
||||
|
||||
-- | An alternative to 'XMonad.Operations.rescreen' that avoids reshuffling
|
||||
-- the workspaces if the number of screens doesn't change and only their
|
||||
-- locations do. Useful for users of @xrandr --setmonitor@.
|
||||
--
|
||||
-- See 'XMonad.Hooks.Rescreen.setRescreenWorkspacesHook', which lets you
|
||||
-- replace the builtin rescreen handler.
|
||||
rescreen :: ScreenComparator -> X ()
|
||||
rescreen (ScreenComparator cmpScreen) = withDisplay (fmap nonEmpty . getCleanedScreenInfo) >>= \case
|
||||
Nothing -> trace "getCleanedScreenInfo returned []"
|
||||
Just xinescs -> windows $ rescreen' xinescs
|
||||
where
|
||||
rescreen' :: NonEmpty Rectangle -> WindowSet -> WindowSet
|
||||
rescreen' xinescs ws
|
||||
| NE.length xinescs == length (W.visible ws) + 1 = rescreenSameLength xinescs ws
|
||||
| otherwise = rescreenCore xinescs ws
|
||||
|
||||
-- the 'XMonad.Operations.rescreen' implementation from core as a fallback
|
||||
rescreenCore :: NonEmpty Rectangle -> WindowSet -> WindowSet
|
||||
rescreenCore (xinesc :| xinescs) ws@W.StackSet{ W.current = v, W.visible = vs, W.hidden = hs } =
|
||||
let (xs, ys) = splitAt (length xinescs) (map W.workspace vs ++ hs)
|
||||
a = W.Screen (W.workspace v) 0 (SD xinesc)
|
||||
as = zipWith3 W.Screen xs [1..] $ map SD xinescs
|
||||
in ws{ W.current = a
|
||||
, W.visible = as
|
||||
, W.hidden = ys }
|
||||
|
||||
-- sort both existing screens and the screens we just got from xinerama
|
||||
-- using cmpScreen, and then replace the rectangles in the WindowSet,
|
||||
-- keeping the order of current/visible workspaces intact
|
||||
rescreenSameLength :: NonEmpty Rectangle -> WindowSet -> WindowSet
|
||||
rescreenSameLength xinescs ws =
|
||||
ws{ W.current = (W.current ws){ W.screenDetail = SD newCurrentRect }
|
||||
, W.visible = [ w{ W.screenDetail = SD r } | w <- W.visible ws | r <- newVisibleRects ]
|
||||
}
|
||||
where
|
||||
undoSort =
|
||||
NE.map fst $
|
||||
NE.sortBy (cmpScreen `on` (getScreenIdAndRectangle . snd)) $
|
||||
NE.zip ((0 :: Int) :| [1..]) $ -- add indices to undo the sort later
|
||||
W.current ws :| W.visible ws
|
||||
newCurrentRect :| newVisibleRects =
|
||||
NE.map snd $ NE.sortWith fst $ NE.zip undoSort $ -- sort back into current:visible order
|
||||
NE.map snd $ NE.sortBy cmpScreen $ NE.zip (0 :| [1..]) xinescs
|
||||
|
||||
-- TODO:
|
||||
-- If number of screens before and after isn't the same, we might still
|
||||
-- try to match locations and avoid changing the workspace for those that
|
||||
-- didn't move, while making sure that the current workspace is still
|
||||
-- visible somewhere.
|
||||
|
@ -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)
|
||||
@ -59,7 +59,7 @@ import XMonad.Util.Run
|
||||
-- > myNewKeys (XConfig {modMask = modm}) = planeKeys modm (Lines 3) Finite
|
||||
--
|
||||
-- For detailed instructions on editing your key bindings, see
|
||||
-- "XMonad.Doc.Extending#Editing_key_bindings".
|
||||
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial>.
|
||||
|
||||
-- | Direction to go in the plane.
|
||||
data Direction = ToLeft | ToUp | ToRight | ToDown deriving Enum
|
||||
|
@ -29,6 +29,7 @@ module XMonad.Actions.Prefix
|
||||
, withPrefixArgument
|
||||
, isPrefixRaw
|
||||
, isPrefixNumeric
|
||||
, orIfPrefixed
|
||||
, ppFormatPrefix
|
||||
) where
|
||||
|
||||
@ -40,11 +41,13 @@ 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
|
||||
|
||||
This module implements Emacs-style prefix argument. The argument
|
||||
comes in two flavours, "Raw" and "Numeric".
|
||||
comes in two flavours, 'Raw' and 'Numeric'.
|
||||
|
||||
To initiate the "prefix mode" you hit the prefix keybinding (default
|
||||
C-u). This sets the Raw argument value to 1. Repeatedly hitting this
|
||||
@ -72,7 +75,7 @@ Binding it in your config
|
||||
|
||||
> ((modm, xK_a), withPrefixArgument addMaybeClean)
|
||||
|
||||
Hitting MOD-a will add the <file> to the playlist while C-u MOD-a will
|
||||
Hitting MOD-a will add the @\<file\>@ to the playlist while C-u MOD-a will
|
||||
clear the playlist and then add the file.
|
||||
|
||||
You can of course use an anonymous action, like so:
|
||||
@ -128,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
|
||||
@ -140,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)
|
||||
@ -161,19 +164,19 @@ 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.
|
||||
--
|
||||
-- First, fetch the current prefix, then pass it as argument to the
|
||||
-- original function. You should use this to "run" your commands.
|
||||
withPrefixArgument :: (PrefixArgument -> X ()) -> X ()
|
||||
withPrefixArgument :: (PrefixArgument -> X a) -> X a
|
||||
withPrefixArgument = (>>=) XS.get
|
||||
|
||||
-- | Test if 'PrefixArgument' is 'Raw' or not.
|
||||
@ -186,6 +189,13 @@ isPrefixNumeric :: PrefixArgument -> Bool
|
||||
isPrefixNumeric (Numeric _) = True
|
||||
isPrefixNumeric _ = False
|
||||
|
||||
-- | Execute the first action, unless any prefix argument is given,
|
||||
-- in which case the second action is chosen instead.
|
||||
--
|
||||
-- > action1 `orIfPrefixed` action2
|
||||
orIfPrefixed :: X a -> X a -> X a
|
||||
orIfPrefixed xa xb = withPrefixArgument $ bool xa xb . isPrefixRaw
|
||||
|
||||
-- | Format the prefix using the Emacs convetion for use in a
|
||||
-- statusbar, like xmobar.
|
||||
--
|
||||
|
545
XMonad/Actions/Profiles.hs
Normal file
545
XMonad/Actions/Profiles.hs
Normal file
@ -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 <mislavzanic3@gmail.com>
|
||||
-- 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
|
@ -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
|
||||
--
|
||||
@ -37,7 +37,7 @@ import XMonad.StackSet
|
||||
-- > , ((modm, xK_Return), promote)
|
||||
--
|
||||
-- For detailed instructions on editing your key bindings, see
|
||||
-- "XMonad.Doc.Extending#Editing_key_bindings".
|
||||
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial>.
|
||||
|
||||
-- | Move the focused window to the master pane. All other windows
|
||||
-- retain their order. If focus is in the master, swap it with the
|
||||
|
89
XMonad/Actions/Repeatable.hs
Normal file
89
XMonad/Actions/Repeatable.hs
Normal file
@ -0,0 +1,89 @@
|
||||
-----------------------------------------------------------------------------
|
||||
-- |
|
||||
-- Module : XMonad.Actions.Repeatable
|
||||
-- Description : Actions you'd like to repeat.
|
||||
-- Copyright : (c) 2022 L. S. Leary
|
||||
-- License : BSD3-style (see LICENSE)
|
||||
--
|
||||
-- Maintainer : @LSLeary (on github)
|
||||
-- Stability : unstable
|
||||
-- Portability : unportable
|
||||
--
|
||||
-- This module factors out the shared logic of "XMonad.Actions.CycleRecentWS",
|
||||
-- "XMonad.Actions.CycleWorkspaceByScreen", "XMonad.Actions.CycleWindows" and
|
||||
-- "XMonad.Actions.MostRecentlyUsed".
|
||||
--
|
||||
-- See the source of these modules for usage examples.
|
||||
--
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
module XMonad.Actions.Repeatable
|
||||
( repeatable
|
||||
, repeatableSt
|
||||
, repeatableM
|
||||
) where
|
||||
|
||||
-- mtl
|
||||
import Control.Monad.State (StateT(..))
|
||||
|
||||
-- X11
|
||||
import Graphics.X11.Xlib.Extras
|
||||
|
||||
-- xmonad
|
||||
import XMonad
|
||||
|
||||
|
||||
-- | An action that temporarily usurps and responds to key press/release events,
|
||||
-- concluding when one of the modifier keys is released.
|
||||
repeatable
|
||||
:: [KeySym] -- ^ The list of 'KeySym's under the
|
||||
-- modifiers used to invoke the action.
|
||||
-> KeySym -- ^ The keypress that invokes the action.
|
||||
-> (EventType -> KeySym -> X ()) -- ^ The keypress handler.
|
||||
-> X ()
|
||||
repeatable = repeatableM id
|
||||
|
||||
-- | A more general variant of 'repeatable' with a stateful handler,
|
||||
-- accumulating a monoidal return value throughout the events.
|
||||
repeatableSt
|
||||
:: Monoid a
|
||||
=> s -- ^ Initial state.
|
||||
-> [KeySym] -- ^ The list of 'KeySym's under the
|
||||
-- modifiers used to invoke the
|
||||
-- action.
|
||||
-> KeySym -- ^ The keypress that invokes the
|
||||
-- action.
|
||||
-> (EventType -> KeySym -> StateT s X a) -- ^ The keypress handler.
|
||||
-> X (a, s)
|
||||
repeatableSt iSt = repeatableM $ \m -> runStateT m iSt
|
||||
|
||||
-- | A more general variant of 'repeatable' with an arbitrary monadic handler,
|
||||
-- accumulating a monoidal return value throughout the events.
|
||||
repeatableM
|
||||
:: (MonadIO m, Monoid a)
|
||||
=> (m a -> X b) -- ^ How to run the monad in 'X'.
|
||||
-> [KeySym] -- ^ The list of 'KeySym's under the
|
||||
-- modifiers used to invoke the action.
|
||||
-> KeySym -- ^ The keypress that invokes the action.
|
||||
-> (EventType -> KeySym -> m a) -- ^ The keypress handler.
|
||||
-> X b
|
||||
repeatableM run mods key pressHandler = do
|
||||
XConf{ theRoot = root, display = d } <- ask
|
||||
run (repeatableRaw d root mods key pressHandler)
|
||||
|
||||
repeatableRaw
|
||||
:: (MonadIO m, Monoid a)
|
||||
=> Display -> Window
|
||||
-> [KeySym] -> KeySym -> (EventType -> KeySym -> m a) -> m a
|
||||
repeatableRaw d root mods key pressHandler = do
|
||||
io (grabKeyboard d root False grabModeAsync grabModeAsync currentTime)
|
||||
handleEvent (keyPress, key) <* io (ungrabKeyboard d currentTime)
|
||||
where
|
||||
getNextEvent = io $ allocaXEvent $ \p -> do
|
||||
maskEvent d (keyPressMask .|. keyReleaseMask) p
|
||||
KeyEvent{ ev_event_type = t, ev_keycode = c } <- getEvent p
|
||||
s <- keycodeToKeysym d c 0
|
||||
return (t, s)
|
||||
handleEvent (t, s)
|
||||
| t == keyRelease && s `elem` mods = pure mempty
|
||||
| otherwise = (<>) <$> pressHandler t s <*> (getNextEvent >>= handleEvent)
|
@ -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
|
||||
@ -39,13 +43,13 @@ import XMonad.Prelude
|
||||
-- TwoPane layout (see "XMonad.Layout.TwoPane").
|
||||
--
|
||||
-- For detailed instructions on editing your key bindings, see
|
||||
-- "XMonad.Doc.Extending#Editing_key_bindings".
|
||||
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial>.
|
||||
|
||||
-- | 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
|
||||
|
@ -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
|
||||
|
||||
|
@ -31,33 +31,50 @@ module XMonad.Actions.Search ( -- * Usage
|
||||
prefixAware,
|
||||
namedEngine,
|
||||
|
||||
amazon,
|
||||
alpha,
|
||||
amazon,
|
||||
arXiv,
|
||||
aur,
|
||||
clojureDocs,
|
||||
codesearch,
|
||||
cratesIo,
|
||||
deb,
|
||||
debbts,
|
||||
debpts,
|
||||
dictionary,
|
||||
duckduckgo,
|
||||
ebay,
|
||||
flora,
|
||||
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,
|
||||
vocabulary,
|
||||
duckduckgo,
|
||||
zbmath,
|
||||
multi,
|
||||
-- * Use case: searching with a submap
|
||||
-- $tip
|
||||
@ -102,12 +119,20 @@ import XMonad.Util.XSelection (getSelection)
|
||||
|
||||
The currently available search engines are:
|
||||
|
||||
* 'amazon' -- Amazon keyword search.
|
||||
|
||||
* 'alpha' -- Wolfram|Alpha query.
|
||||
|
||||
* 'amazon' -- Amazon keyword search.
|
||||
|
||||
* 'arXiv' -- Open-access preprint archive.
|
||||
|
||||
* 'aur' -- Arch User Repository.
|
||||
|
||||
* 'clojureDocs' -- Documentation and examples repository for Clojure.
|
||||
|
||||
* 'codesearch' -- Google Labs Code Search search.
|
||||
|
||||
* 'cratesIo' -- Rust crate registry.
|
||||
|
||||
* 'deb' -- Debian package search.
|
||||
|
||||
* 'debbts' -- Debian Bug Tracking System.
|
||||
@ -116,17 +141,21 @@ import XMonad.Util.XSelection (getSelection)
|
||||
|
||||
* 'dictionary' -- dictionary.reference.com search.
|
||||
|
||||
* 'duckduckgo' -- DuckDuckGo search engine.
|
||||
|
||||
* 'ebay' -- Ebay keyword search.
|
||||
|
||||
* 'flora' -- Prettier Haskell package database.
|
||||
|
||||
* 'github' -- GitHub keyword search.
|
||||
|
||||
* 'google' -- basic Google search.
|
||||
|
||||
* 'hackage' -- Hackage, the Haskell package database.
|
||||
|
||||
* 'hoogle' -- Hoogle, the Haskell libraries API search engine.
|
||||
* 'homeManager' -- Search Nix's home-manager's options.
|
||||
|
||||
* 'stackage' -- Stackage, An alternative Haskell libraries API search engine.
|
||||
* 'hoogle' -- Hoogle, the Haskell libraries API search engine.
|
||||
|
||||
* 'images' -- Google images.
|
||||
|
||||
@ -138,21 +167,45 @@ import XMonad.Util.XSelection (getSelection)
|
||||
|
||||
* 'mathworld' -- Wolfram MathWorld search.
|
||||
|
||||
* '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.
|
||||
|
||||
* 'protondb' -- Steam Proton Game Database.
|
||||
|
||||
* 'rosettacode' -- Programming chrestomathy wiki.
|
||||
|
||||
* 'rustStd' -- Rust standard library documentation.
|
||||
|
||||
* 'scholar' -- Google scholar academic search.
|
||||
|
||||
* 'sourcehut' -- Sourcehut projects search.
|
||||
|
||||
* 'stackage' -- Stackage, An alternative Haskell libraries API search engine.
|
||||
|
||||
* 'steam' -- Steam games search.
|
||||
|
||||
* 'thesaurus' -- thesaurus.com search.
|
||||
|
||||
* 'vocabulary' -- Dictionary search.
|
||||
|
||||
* 'voidpgks_x86_64' -- Void Linux packages search for @x86_64@.
|
||||
|
||||
* 'voidpgks_x86_64_musl' -- Void Linux packages search for @x86_64-musl@.
|
||||
|
||||
* 'wayback' -- the Wayback Machine.
|
||||
|
||||
* 'wikipedia' -- basic Wikipedia search.
|
||||
|
||||
* 'wiktionary' -- Wiktionary search.
|
||||
|
||||
* 'youtube' -- Youtube video search.
|
||||
|
||||
* 'vocabulary' -- Dictionary search
|
||||
|
||||
* 'duckduckgo' -- DuckDuckGo search engine.
|
||||
* 'zbmath' -- Open alternative to MathSciNet.
|
||||
|
||||
* 'multi' -- Search based on the prefix. \"amazon:Potter\" will use amazon, etc. With no prefix searches google.
|
||||
|
||||
@ -269,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
|
||||
@ -286,39 +339,57 @@ searchEngineF :: Name -> Site -> SearchEngine
|
||||
searchEngineF = SearchEngine
|
||||
|
||||
-- The engines.
|
||||
amazon, alpha, codesearch, deb, debbts, debpts, dictionary, ebay, github, google, hackage, hoogle,
|
||||
images, imdb, lucky, maps, mathworld, openstreetmap, scholar, stackage, thesaurus, vocabulary, wayback, wikipedia, wiktionary,
|
||||
youtube, duckduckgo :: SearchEngine
|
||||
amazon = searchEngine "amazon" "https://www.amazon.com/s/ref=nb_sb_noss_2?url=search-alias%3Daps&field-keywords="
|
||||
alpha, amazon, arXiv, aur, clojureDocs, codesearch, cratesIo, deb, debbts, debpts, dictionary, duckduckgo, ebay, flora,
|
||||
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")
|
||||
aur = searchEngine "aur" "https://aur.archlinux.org/packages?&K="
|
||||
clojureDocs = searchEngine "clojureDocs" "https://clojuredocs.org/search?q="
|
||||
codesearch = searchEngine "codesearch" "https://developers.google.com/s/results/code-search?q="
|
||||
cratesIo = searchEngine "cratesIo" "https://crates.io/search?q="
|
||||
deb = searchEngine "deb" "https://packages.debian.org/"
|
||||
debbts = searchEngine "debbts" "https://bugs.debian.org/"
|
||||
debpts = searchEngine "debpts" "https://packages.qa.debian.org/"
|
||||
dictionary = searchEngine "dict" "https://dictionary.reference.com/browse/"
|
||||
duckduckgo = searchEngine "duckduckgo" "https://duckduckgo.com/?t=lm&q="
|
||||
ebay = searchEngine "ebay" "https://www.ebay.com/sch/i.html?_nkw="
|
||||
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="
|
||||
lucky = searchEngine "lucky" "https://www.google.com/search?btnI&q="
|
||||
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="
|
||||
rosettacode = searchEngine "rosettacode" "https://rosettacode.org/w/index.php?search="
|
||||
rustStd = searchEngine "rustStd" "https://doc.rust-lang.org/std/index.html?search="
|
||||
scholar = searchEngine "scholar" "https://scholar.google.com/scholar?q="
|
||||
sourcehut = searchEngine "sourcehut" "https://sr.ht/projects?search="
|
||||
stackage = searchEngine "stackage" "https://www.stackage.org/lts/hoogle?q="
|
||||
steam = searchEngine "steam" "https://store.steampowered.com/search/?term="
|
||||
thesaurus = searchEngine "thesaurus" "https://thesaurus.com/browse/"
|
||||
vocabulary = searchEngine "vocabulary" "https://www.vocabulary.com/search?q="
|
||||
voidpgks_x86_64 = searchEngine "voidpackages" "https://voidlinux.org/packages/?arch=x86_64&q="
|
||||
voidpgks_x86_64_musl = searchEngine "voidpackages" "https://voidlinux.org/packages/?arch=x86_64-musl&q="
|
||||
wayback = searchEngineF "wayback" ("https://web.archive.org/web/*/"++)
|
||||
wikipedia = searchEngine "wiki" "https://en.wikipedia.org/wiki/Special:Search?go=Go&search="
|
||||
wiktionary = searchEngine "wikt" "https://en.wiktionary.org/wiki/Special:Search?go=Go&search="
|
||||
youtube = searchEngine "youtube" "https://www.youtube.com/results?search_type=search_videos&search_query="
|
||||
wayback = searchEngineF "wayback" ("https://web.archive.org/web/*/"++)
|
||||
vocabulary = searchEngine "vocabulary" "https://www.vocabulary.com/search?q="
|
||||
duckduckgo = searchEngine "duckduckgo" "https://duckduckgo.com/?t=lm&q="
|
||||
zbmath = searchEngine "zbmath" "https://zbmath.org/?q="
|
||||
|
||||
multi :: SearchEngine
|
||||
multi = namedEngine "multi" $ foldr1 (!>) [amazon, alpha, codesearch, deb, debbts, debpts, dictionary, ebay, github, google, hackage, hoogle, images, imdb, lucky, maps, mathworld, openstreetmap, scholar, thesaurus, wayback, wikipedia, wiktionary, duckduckgo, prefixAware google]
|
||||
multi = namedEngine "multi" $ foldr1 (!>) [alpha, amazon, aur, codesearch, deb, debbts, debpts, dictionary, duckduckgo, ebay, flora, github, hackage, hoogle, images, imdb, lucky, maps, mathworld, ncatlab, openstreetmap, protondb, rosettacode, scholar, sourcehut, stackage, steam, thesaurus, vocabulary, voidpgks_x86_64, voidpgks_x86_64_musl, wayback, wikipedia, wiktionary, youtube, prefixAware google]
|
||||
|
||||
{- | This function wraps up a search engine and creates a new one, which works
|
||||
like the argument, but goes directly to a URL if one is given rather than
|
||||
@ -366,14 +437,14 @@ namedEngine name (SearchEngine _ site) = searchEngineF name site
|
||||
browser. -}
|
||||
promptSearchBrowser :: XPConfig -> Browser -> SearchEngine -> X ()
|
||||
promptSearchBrowser config browser (SearchEngine name site) = do
|
||||
hc <- historyCompletionP ("Search [" `isPrefixOf`)
|
||||
hc <- historyCompletionP config ("Search [" `isPrefixOf`)
|
||||
mkXPrompt (Search name) config hc $ search browser site
|
||||
|
||||
{- | Like 'promptSearchBrowser', but only suggest previous searches for the
|
||||
given 'SearchEngine' in the prompt. -}
|
||||
promptSearchBrowser' :: XPConfig -> Browser -> SearchEngine -> X ()
|
||||
promptSearchBrowser' config browser (SearchEngine name site) = do
|
||||
hc <- historyCompletionP (searchName `isPrefixOf`)
|
||||
hc <- historyCompletionP config (searchName `isPrefixOf`)
|
||||
mkXPrompt (Search name) config hc $ search browser site
|
||||
where
|
||||
searchName = showXPrompt (Search name)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
--
|
||||
|
@ -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
|
||||
--
|
||||
@ -35,7 +35,7 @@ import XMonad.Util.Run
|
||||
-- In this example, a popup date menu will now be bound to @mod-d@.
|
||||
--
|
||||
-- For detailed instructions on editing your key bindings, see
|
||||
-- "XMonad.Doc.Extending#Editing_key_bindings".
|
||||
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial>.
|
||||
|
||||
date :: X ()
|
||||
date = unsafeSpawn "(date; sleep 10) | dzen2"
|
||||
|
@ -13,7 +13,7 @@
|
||||
-- 'sinkAll' function for backwards compatibility.
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
module XMonad.Actions.SinkAll (
|
||||
module XMonad.Actions.SinkAll {-# DEPRECATED "Use XMonad.Actions.WithAll instead" #-} (
|
||||
-- * Usage
|
||||
-- $usage
|
||||
|
||||
@ -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
|
||||
--
|
||||
@ -32,4 +32,4 @@ import XMonad.Actions.WithAll (sinkAll)
|
||||
-- > , ((modm .|. shiftMask, xK_t), sinkAll)
|
||||
--
|
||||
-- For detailed instructions on editing your key bindings, see
|
||||
-- "XMonad.Doc.Extending#Editing_key_bindings".
|
||||
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial>.
|
||||
|
@ -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
|
||||
--
|
||||
@ -63,7 +63,7 @@ import XMonad.Util.Process (getPPIDChain)
|
||||
-- the spawned application(e.g. float or resize it).
|
||||
--
|
||||
-- For detailed instructions on editing your key bindings, see
|
||||
-- "XMonad.Doc.Extending#Editing_key_bindings".
|
||||
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial>.
|
||||
|
||||
newtype Spawner = Spawner {pidsRef :: [(ProcessID, ManageHook)]}
|
||||
|
||||
|
@ -18,6 +18,7 @@ module XMonad.Actions.Submap (
|
||||
-- $usage
|
||||
submap,
|
||||
visualSubmap,
|
||||
visualSubmapSorted,
|
||||
submapDefault,
|
||||
submapDefaultWithKey,
|
||||
|
||||
@ -32,10 +33,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
|
||||
|
||||
@ -56,7 +54,7 @@ because that is a special value passed to XGrabKey() and not an actual
|
||||
modifier.
|
||||
|
||||
For detailed instructions on editing your key bindings, see
|
||||
"XMonad.Doc.Extending#Editing_key_bindings".
|
||||
<https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial>.
|
||||
|
||||
-}
|
||||
|
||||
@ -76,30 +74,47 @@ submap = submapDefault (return ())
|
||||
-- >
|
||||
-- > gotoLayout :: [(String, X ())] -- for use with EZConfig
|
||||
-- > gotoLayout = -- assumes you have a layout named "Tall" and one named "Full".
|
||||
-- > ["M-l", visualSubmap def $ Map.fromList $ map (\(k, s, a) -> ((0, k), (s, a)))
|
||||
-- > [ (xK_t, "Tall", switchToLayout "Tall") -- "M-l t" switches to "Tall"
|
||||
-- > , (xK_r, "Full", switchToLayout "Full") -- "M-l r" switches to "full"
|
||||
-- > ]]
|
||||
-- > [("M-l", visualSubmap def $ Map.fromList $ map (\(k, s, a) -> ((0, k), (s, a)))
|
||||
-- > [ (xK_t, "Tall", switchToLayout "Tall") -- "M-l t" switches to "Tall"
|
||||
-- > , (xK_r, "Full", switchToLayout "Full") -- "M-l r" switches to "full"
|
||||
-- > ])]
|
||||
--
|
||||
-- One could alternatively also write @gotoLayout@ as
|
||||
--
|
||||
-- > gotoLayout = ["M-l", visualSubmap def $ Map.fromList $
|
||||
-- > [ ((0, xK_t), subName "Tall" $ switchToLayout "Tall")
|
||||
-- > , ((0, xK_r), subName "Full" $ switchToLayout "Full")
|
||||
-- > ]]
|
||||
-- > gotoLayout = [("M-l", visualSubmap def $ Map.fromList $
|
||||
-- > [ ((0, xK_t), subName "Tall" $ switchToLayout "Tall")
|
||||
-- > , ((0, xK_r), subName "Full" $ switchToLayout "Full")
|
||||
-- > ])]
|
||||
visualSubmap :: WindowConfig -- ^ The config for the spawned window.
|
||||
-> M.Map (KeyMask, KeySym) (String, X ())
|
||||
-- ^ A map @keybinding -> (description, action)@.
|
||||
-> X ()
|
||||
visualSubmap wc keys =
|
||||
visualSubmap = visualSubmapSorted id
|
||||
|
||||
-- | Like 'visualSubmap', but is able to sort the descriptions.
|
||||
-- For example,
|
||||
--
|
||||
-- > import Data.Ord (comparing, Down)
|
||||
-- >
|
||||
-- > visualSubmapSorted (sortBy (comparing Down)) def
|
||||
--
|
||||
-- would sort the @(key, description)@ pairs by their keys in descending
|
||||
-- order.
|
||||
visualSubmapSorted :: ([((KeyMask, KeySym), String)] -> [((KeyMask, KeySym), String)])
|
||||
-- ^ A function to resort the descriptions
|
||||
-> WindowConfig -- ^ The config for the spawned window.
|
||||
-> M.Map (KeyMask, KeySym) (String, X ())
|
||||
-- ^ A map @keybinding -> (description, action)@.
|
||||
-> X ()
|
||||
visualSubmapSorted sorted wc keys =
|
||||
withSimpleWindow wc descriptions waitForKeyPress >>= \(m', s) ->
|
||||
maybe (pure ()) snd (M.lookup (m', s) keys)
|
||||
where
|
||||
descriptions :: [String]
|
||||
descriptions =
|
||||
zipWith (\key desc -> keyToString key <> ": " <> desc)
|
||||
(M.keys keys)
|
||||
(map fst (M.elems keys))
|
||||
map (\(key, desc) -> keyToString key <> ": " <> desc)
|
||||
. sorted
|
||||
$ zip (M.keys keys) (map fst (M.elems keys))
|
||||
|
||||
-- | Give a name to an action.
|
||||
subName :: String -> X () -> (String, X ())
|
||||
|
@ -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
|
||||
@ -99,7 +100,7 @@ import Control.Arrow
|
||||
-- So far floating windows have been treated no differently than tiled windows
|
||||
-- even though their positions are independent of the stack. Often, yanking
|
||||
-- floating windows in and out of the workspace will obliterate the stack
|
||||
-- history - particularly frustrating with 'XMonad.Util.Scratchpad' since it is
|
||||
-- history - particularly frustrating with "XMonad.Util.Scratchpad" since it is
|
||||
-- toggled so frequenty and always replaces the master window. That's why the
|
||||
-- swap functions accept a boolean argument; when @True@ non-focused floating
|
||||
-- windows will be ignored.
|
||||
@ -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'
|
||||
|
@ -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
|
||||
--
|
||||
@ -44,7 +44,7 @@ import XMonad.Util.WorkspaceCompare
|
||||
-- will swap workspaces 1 and 5.
|
||||
--
|
||||
-- For detailed instructions on editing your key bindings, see
|
||||
-- "XMonad.Doc.Extending#Editing_key_bindings".
|
||||
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial>.
|
||||
|
||||
-- | Swaps the currently focused workspace with the given workspace tag, via
|
||||
-- @swapWorkspaces@.
|
||||
|
@ -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
|
||||
@ -64,7 +64,7 @@ econst = const . return
|
||||
-- the tags \"a\" and \"b\" but not \"a b\".
|
||||
--
|
||||
-- For detailed instructions on editing your key bindings, see
|
||||
-- "XMonad.Doc.Extending#Editing_key_bindings".
|
||||
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial>.
|
||||
|
||||
-- | set multiple tags for a window at once (overriding any previous tags)
|
||||
setTags :: [String] -> Window -> X ()
|
||||
@ -134,11 +134,6 @@ focusTagged' :: (WindowSet -> [Window]) -> String -> X ()
|
||||
focusTagged' wl t = gets windowset >>= findM (hasTag t) . wl >>=
|
||||
maybe (return ()) (windows . focusWindow)
|
||||
|
||||
findM :: (Monad m) => (a -> m Bool) -> [a] -> m (Maybe a)
|
||||
findM _ [] = return Nothing
|
||||
findM p (x:xs) = do b <- p x
|
||||
if b then return (Just x) else findM p xs
|
||||
|
||||
-- | apply a pure function to windows with a tag
|
||||
withTaggedP, withTaggedGlobalP :: String -> (Window -> WindowSet -> WindowSet) -> X ()
|
||||
withTaggedP t f = withTagged' t (winMap f)
|
||||
|
@ -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
|
||||
|
122
XMonad/Actions/ToggleFullFloat.hs
Normal file
122
XMonad/Actions/ToggleFullFloat.hs
Normal file
@ -0,0 +1,122 @@
|
||||
-- |
|
||||
-- Module : XMonad.Actions.ToggleFullFloat
|
||||
-- Description : Fullscreen (float) a window while remembering its original state.
|
||||
-- Copyright : (c) 2022 Tomáš Janoušek <tomi@nomi.cz>
|
||||
-- License : BSD3
|
||||
-- Maintainer : Tomáš Janoušek <tomi@nomi.cz>
|
||||
--
|
||||
module XMonad.Actions.ToggleFullFloat (
|
||||
-- * Usage
|
||||
-- $usage
|
||||
toggleFullFloatEwmhFullscreen,
|
||||
toggleFullFloat,
|
||||
fullFloat,
|
||||
unFullFloat,
|
||||
gcToggleFullFloat,
|
||||
) where
|
||||
|
||||
import qualified Data.Map.Strict as M
|
||||
|
||||
import XMonad
|
||||
import XMonad.Prelude
|
||||
import XMonad.Hooks.EwmhDesktops (setEwmhFullscreenHooks)
|
||||
import XMonad.Hooks.ManageHelpers
|
||||
import qualified XMonad.StackSet as W
|
||||
import qualified XMonad.Util.ExtensibleState as XS
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- $usage
|
||||
--
|
||||
-- The main use-case is to make 'ewmhFullscreen' (re)store the size and
|
||||
-- position of floating windows instead of just unconditionally sinking them
|
||||
-- into the floating layer. To enable this, you'll need this in your
|
||||
-- @xmonad.hs@:
|
||||
--
|
||||
-- > import XMonad
|
||||
-- > import XMonad.Actions.ToggleFullFloat
|
||||
-- > import XMonad.Hooks.EwmhDesktops
|
||||
-- >
|
||||
-- > main = xmonad $ … . toggleFullFloatEwmhFullscreen . ewmhFullscreen . ewmh . … $ def{…}
|
||||
--
|
||||
-- Additionally, this "smart" fullscreening can be bound to a key and invoked
|
||||
-- manually whenever one needs a larger window temporarily:
|
||||
--
|
||||
-- > , ((modMask .|. shiftMask, xK_t), withFocused toggleFullFloat)
|
||||
|
||||
newtype ToggleFullFloat = ToggleFullFloat{ fromToggleFullFloat :: M.Map Window (Maybe W.RationalRect) }
|
||||
deriving (Show, Read)
|
||||
|
||||
instance ExtensionClass ToggleFullFloat where
|
||||
extensionType = PersistentExtension
|
||||
initialValue = ToggleFullFloat mempty
|
||||
|
||||
-- | Full-float a window, remembering its state (tiled/floating and
|
||||
-- position/size).
|
||||
fullFloat :: Window -> X ()
|
||||
fullFloat = windows . appEndo <=< runQuery doFullFloatSave
|
||||
|
||||
-- | Restore window to its remembered state.
|
||||
unFullFloat :: Window -> X ()
|
||||
unFullFloat = windows . appEndo <=< runQuery doFullFloatRestore
|
||||
|
||||
-- | Full-float a window, if it's not already full-floating. Otherwise,
|
||||
-- restore its original state.
|
||||
toggleFullFloat :: Window -> X ()
|
||||
toggleFullFloat w = ifM (isFullFloat w) (unFullFloat w) (fullFloat w)
|
||||
|
||||
isFullFloat :: Window -> X Bool
|
||||
isFullFloat w = gets $ (Just fullRect ==) . M.lookup w . W.floating . windowset
|
||||
where
|
||||
fullRect = W.RationalRect 0 0 1 1
|
||||
|
||||
doFullFloatSave :: ManageHook
|
||||
doFullFloatSave = do
|
||||
w <- ask
|
||||
liftX $ do
|
||||
f <- gets $ M.lookup w . W.floating . windowset
|
||||
-- @M.insertWith const@ = don't overwrite stored original state
|
||||
XS.modify' $ ToggleFullFloat . M.insertWith const w f . fromToggleFullFloat
|
||||
doFullFloat
|
||||
|
||||
doFullFloatRestore :: ManageHook
|
||||
doFullFloatRestore = do
|
||||
w <- ask
|
||||
mf <- liftX $ do
|
||||
mf <- XS.gets $ M.lookup w . fromToggleFullFloat
|
||||
XS.modify' $ ToggleFullFloat . M.delete w . fromToggleFullFloat
|
||||
pure mf
|
||||
doF $ case mf of
|
||||
Just (Just f) -> W.float w f -- was floating before
|
||||
Just Nothing -> W.sink w -- was tiled before
|
||||
Nothing -> W.sink w -- fallback when not found in ToggleFullFloat
|
||||
|
||||
-- | Install ToggleFullFloat garbage collection hooks.
|
||||
--
|
||||
-- Note: This is included in 'toggleFullFloatEwmhFullscreen', only needed if
|
||||
-- using the 'toggleFullFloat' separately from the EWMH hook.
|
||||
gcToggleFullFloat :: XConfig a -> XConfig a
|
||||
gcToggleFullFloat c = c { startupHook = startupHook c <> gcToggleFullFloatStartupHook
|
||||
, handleEventHook = handleEventHook c <> gcToggleFullFloatEventHook }
|
||||
|
||||
-- | ToggleFullFloat garbage collection: drop windows when they're destroyed.
|
||||
gcToggleFullFloatEventHook :: Event -> X All
|
||||
gcToggleFullFloatEventHook DestroyWindowEvent{ev_window = w} = do
|
||||
XS.modify' $ ToggleFullFloat . M.delete w . fromToggleFullFloat
|
||||
mempty
|
||||
gcToggleFullFloatEventHook _ = mempty
|
||||
|
||||
-- | ToggleFullFloat garbage collection: restrict to existing windows at
|
||||
-- startup.
|
||||
gcToggleFullFloatStartupHook :: X ()
|
||||
gcToggleFullFloatStartupHook = withWindowSet $ \ws ->
|
||||
XS.modify' $ ToggleFullFloat . M.filterWithKey (\w _ -> w `W.member` ws) . fromToggleFullFloat
|
||||
|
||||
-- | Hook this module into 'XMonad.Hooks.EwmhDesktops.ewmhFullscreen'. This
|
||||
-- makes windows restore their original state (size and position if floating)
|
||||
-- instead of unconditionally sinking into the tiling layer.
|
||||
--
|
||||
-- ('gcToggleFullFloat' is included here.)
|
||||
toggleFullFloatEwmhFullscreen :: XConfig a -> XConfig a
|
||||
toggleFullFloatEwmhFullscreen =
|
||||
setEwmhFullscreenHooks doFullFloatSave doFullFloatRestore .
|
||||
gcToggleFullFloat
|
@ -103,9 +103,12 @@ import XMonad.Hooks.WorkspaceHistory
|
||||
-- display your topics in an historical way using a custom `pprWindowSet'
|
||||
-- function. You can also easily switch to recent topics using this history
|
||||
-- of last focused topics.
|
||||
--
|
||||
-- A blog post highlighting some features of this module can be found
|
||||
-- <https://tony-zorman.com/posts/topic-space/2022-09-11-topic-spaces.html here>.
|
||||
|
||||
-- $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
|
||||
|
169
XMonad/Actions/UpKeys.hs
Normal file
169
XMonad/Actions/UpKeys.hs
Normal file
@ -0,0 +1,169 @@
|
||||
{-# LANGUAGE BlockArguments #-}
|
||||
{-# LANGUAGE InstanceSigs #-}
|
||||
{-# LANGUAGE NamedFieldPuns #-}
|
||||
{-# LANGUAGE TypeApplications #-}
|
||||
{- |
|
||||
Module : XMonad.Actions.UpKeys
|
||||
Description : Bind an action to the release of a key
|
||||
Copyright : (c) Tony Zorman, 2024
|
||||
License : BSD-3
|
||||
Maintainer : Tony Zorman <soliditsallgood@mailbox.org>
|
||||
|
||||
A combinator for binding an action to the release of a key. This can be
|
||||
useful for hold-type buttons, where the press of a key engages some
|
||||
functionality, and its release… releases it again.
|
||||
-}
|
||||
module XMonad.Actions.UpKeys
|
||||
( -- * Usage
|
||||
-- $usage
|
||||
useUpKeys,
|
||||
UpKeysConfig (..),
|
||||
ezUpKeys,
|
||||
)
|
||||
where
|
||||
|
||||
import Data.Map.Strict (Map)
|
||||
import qualified Data.Map.Strict as Map
|
||||
import XMonad
|
||||
import XMonad.Prelude
|
||||
import XMonad.Util.EZConfig (mkKeymap)
|
||||
import qualified XMonad.Util.ExtensibleConf as XC
|
||||
|
||||
{- $usage
|
||||
You can use this module with the following in your @xmonad.hs@:
|
||||
|
||||
> import XMonad.Actions.UpKeys
|
||||
|
||||
Next, define the keys and actions you want to have happen on the release
|
||||
of a key:
|
||||
|
||||
> myUpKeys = ezUpKeys $
|
||||
> [ ("M-z", myAction)
|
||||
> , ("M-a", myAction2)
|
||||
> ]
|
||||
|
||||
All that's left is to plug this definition into the 'useUpKeys'
|
||||
combinator that this module provides:
|
||||
|
||||
> main :: IO ()
|
||||
> main = xmonad
|
||||
> . useUpKeys (def{ grabKeys = True, upKeys = myUpKeys })
|
||||
> $ myConfig
|
||||
|
||||
Note the presence of @'grabKeys' = True@; this is for situations where
|
||||
you don't have any of these keys bound to do something upon pressing
|
||||
them; i.e., you use them solely for their release actions. If you want
|
||||
something to happen in both cases, remove that part (@'grabKeys' =
|
||||
False@ is the default) and bind the keys to actions as you normally
|
||||
would.
|
||||
|
||||
==== __Examples__
|
||||
|
||||
As an extended example, consider the case where you want all of your
|
||||
docks (e.g., status bar) to "pop up" when you press the super key, and
|
||||
then vanish again once that keys is released.
|
||||
|
||||
Since docks are not generally part of XMonad's window-set—otherwise, we
|
||||
would have to manage them—we first need a way to access and manipulate
|
||||
all docks.
|
||||
|
||||
> onAllDocks :: (Display -> Window -> IO ()) -> X ()
|
||||
> onAllDocks act = withDisplay \dpy -> do
|
||||
> rootw <- asks theRoot
|
||||
> (_, _, wins) <- io $ queryTree dpy rootw
|
||||
> traverse_ (io . act dpy) =<< filterM (runQuery checkDock) wins
|
||||
|
||||
This is also the place where one could filter for just status bar,
|
||||
trayer, and so on.
|
||||
|
||||
Now we have to decide what kinds of keys we want to watch out for. Since
|
||||
you most likely use left super as your modifier key, this is a little
|
||||
bit more complicated than for other keys, as you will most likely see
|
||||
the key both as a @KeyMask@, as well as a @KeySym@. One could think a
|
||||
bit and probably come up with an elegant solution for this—or one could
|
||||
grab all possible key combinations by brute-force!
|
||||
|
||||
> dockKeys :: X () -> [((KeyMask, KeySym), X ())]
|
||||
> dockKeys act = map (actKey . foldr1 (.|.)) . combinations $ keyMasks
|
||||
> where
|
||||
> actKey :: KeyMask -> ((KeyMask, KeySym), X ())
|
||||
> actKey mask = ((mask, xK_Super_L), act)
|
||||
>
|
||||
> keyMasks :: [KeyMask]
|
||||
> keyMasks = [ noModMask, shiftMask, lockMask, controlMask, mod1Mask, mod2Mask, mod3Mask, mod4Mask, mod5Mask ]
|
||||
>
|
||||
> -- Return all combinations of a sequence of values.
|
||||
> combinations :: [a] -> [[a]]
|
||||
> combinations xs = concat [combs i xs | i <- [1 .. length xs]]
|
||||
> where
|
||||
> combs 0 _ = [[]]
|
||||
> combs _ [] = []
|
||||
> combs n (x:xs) = map (x:) (combs (n-1) xs) <> combs n xs
|
||||
|
||||
Given some action, like lowering or raising the window, we generate all
|
||||
possible combinations of modifiers that may be pressed with the super
|
||||
key. This is a good time to say that this is just for demonstrative
|
||||
purposes, btw—please don't actually do this.
|
||||
|
||||
All that's left is to plug everything into the machinery of this module,
|
||||
and we're done!
|
||||
|
||||
> import qualified Data.Map.Strict as Map
|
||||
>
|
||||
> main :: IO ()
|
||||
> main = xmonad
|
||||
> . … -- other combinators
|
||||
> . useUpKeys (def { upKeys = Map.fromList $ dockKeys (onAllDocks lowerWindow) })
|
||||
> $ myConfig `additionalKeys` dockKeys (onAllDocks raiseWindow)
|
||||
>
|
||||
> myConfig = …
|
||||
-}
|
||||
|
||||
data UpKeysConfig = UpKeysConfig
|
||||
{ -- | Whether to grab all keys that are not already grabbed.
|
||||
grabKeys :: !Bool
|
||||
-- | The keys themselves.
|
||||
, upKeys :: !(Map (KeyMask, KeySym) (X ()))
|
||||
}
|
||||
|
||||
-- | The default 'UpKeysConfig'; keys are not grabbed, and no upkeys are
|
||||
-- specified.
|
||||
instance Default UpKeysConfig where
|
||||
def :: UpKeysConfig
|
||||
def = UpKeysConfig { grabKeys = False, upKeys = mempty }
|
||||
|
||||
instance Semigroup UpKeysConfig where
|
||||
(<>) :: UpKeysConfig -> UpKeysConfig -> UpKeysConfig
|
||||
UpKeysConfig g u <> UpKeysConfig g' u' = UpKeysConfig (g && g') (u <> u')
|
||||
|
||||
-- | Bind actions to keys upon their release.
|
||||
useUpKeys :: UpKeysConfig -> (XConfig l -> XConfig l)
|
||||
useUpKeys upKeysConf = flip XC.once upKeysConf \conf -> conf
|
||||
{ handleEventHook = handleEventHook conf <> (\e -> handleKeyUp e $> All True)
|
||||
, startupHook = startupHook conf <> when (grabKeys upKeysConf) grabUpKeys
|
||||
}
|
||||
where
|
||||
grabUpKeys :: X ()
|
||||
grabUpKeys = do
|
||||
XConf{ display = dpy, theRoot = rootw } <- ask
|
||||
realKeys <- maybe mempty upKeys <$> XC.ask @X @UpKeysConfig
|
||||
let grab :: (KeyMask, KeyCode) -> X ()
|
||||
grab (km, kc) = io $ grabKey dpy kc km rootw True grabModeAsync grabModeAsync
|
||||
traverse_ grab =<< mkGrabs (Map.keys realKeys)
|
||||
|
||||
-- | Parse the given EZConfig-style keys into the internal keymap
|
||||
-- representation.
|
||||
--
|
||||
-- This is just 'mkKeymap' with a better name.
|
||||
ezUpKeys :: XConfig l -> [(String, X ())] -> Map (KeyMask, KeySym) (X ())
|
||||
ezUpKeys = mkKeymap
|
||||
|
||||
-- | A handler for key-up events.
|
||||
handleKeyUp :: Event -> X ()
|
||||
handleKeyUp KeyEvent{ ev_event_type, ev_state, ev_keycode }
|
||||
| ev_event_type == keyRelease = withDisplay \dpy -> do
|
||||
s <- io $ keycodeToKeysym dpy ev_keycode 0
|
||||
cln <- cleanMask ev_state
|
||||
ks <- maybe mempty upKeys <$> XC.ask @X @UpKeysConfig
|
||||
userCodeDef () $ whenJust (ks Map.!? (cln, s)) id
|
||||
handleKeyUp _ = pure ()
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
@ -45,7 +45,7 @@ Note that warping to a particular screen may change the focus.
|
||||
-}
|
||||
|
||||
-- For detailed instructions on editing your key bindings, see
|
||||
-- "XMonad.Doc.Extending#Editing_key_bindings".
|
||||
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial>.
|
||||
|
||||
|
||||
data Corner = UpperLeft | UpperRight | LowerLeft | LowerRight
|
||||
|
@ -22,6 +22,7 @@ module XMonad.Actions.WindowBringer (
|
||||
WindowBringerConfig(..),
|
||||
gotoMenu, gotoMenuConfig, gotoMenu', gotoMenuArgs, gotoMenuArgs',
|
||||
bringMenu, bringMenuConfig, bringMenu', bringMenuArgs, bringMenuArgs',
|
||||
copyMenu, copyMenuConfig, copyMenu', copyMenuArgs, copyMenuArgs',
|
||||
windowMap, windowAppMap, windowMap', bringWindow, actionMenu
|
||||
) where
|
||||
|
||||
@ -33,10 +34,11 @@ import XMonad
|
||||
import qualified XMonad as X
|
||||
import XMonad.Util.Dmenu (menuMapArgs)
|
||||
import XMonad.Util.NamedWindows (getName, getNameWMClass)
|
||||
import XMonad.Actions.CopyWindow (copyWindow)
|
||||
|
||||
-- $usage
|
||||
--
|
||||
-- Import the module into your @~\/.xmonad\/xmonad.hs@:
|
||||
-- Import the module into your @xmonad.hs@:
|
||||
--
|
||||
-- > import XMonad.Actions.WindowBringer
|
||||
--
|
||||
@ -44,9 +46,10 @@ import XMonad.Util.NamedWindows (getName, getNameWMClass)
|
||||
--
|
||||
-- > , ((modm .|. shiftMask, xK_g ), gotoMenu)
|
||||
-- > , ((modm .|. shiftMask, xK_b ), bringMenu)
|
||||
-- > , ((modm .|. shiftMask, xK_y ), copyMenu)
|
||||
--
|
||||
-- For detailed instructions on editing your key bindings, see
|
||||
-- "XMonad.Doc.Extending#Editing_key_bindings".
|
||||
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial>.
|
||||
|
||||
data WindowBringerConfig = WindowBringerConfig
|
||||
{ menuCommand :: String -- ^ The shell command that will handle window selection
|
||||
@ -90,6 +93,37 @@ gotoMenu' cmd = gotoMenuConfig def { menuArgs = [], menuCommand = cmd }
|
||||
gotoMenuArgs' :: String -> [String] -> X ()
|
||||
gotoMenuArgs' cmd args = gotoMenuConfig def { menuCommand = cmd, menuArgs = args }
|
||||
|
||||
-- | Pops open a dmenu with window titles. Choose one, and it will be copied into your current workspace.
|
||||
copyMenu :: X ()
|
||||
copyMenu = copyMenuArgs def
|
||||
|
||||
-- | Pops open a dmenu with window titles. Choose one, and it will be
|
||||
-- copied into your current workspace. This version
|
||||
-- accepts a configuration object.
|
||||
copyMenuConfig :: WindowBringerConfig -> X ()
|
||||
copyMenuConfig wbConfig = actionMenu wbConfig copyBringWindow
|
||||
|
||||
-- | Pops open a dmenu with window titles. Choose one, and it will be
|
||||
-- copied into your current workspace. This version
|
||||
-- takes a list of arguments to pass to dmenu.
|
||||
copyMenuArgs :: [String] -> X ()
|
||||
copyMenuArgs args = copyMenuConfig def { menuArgs = args }
|
||||
|
||||
-- | Pops open an application with window titles given over stdin. Choose one,
|
||||
-- and it will be copied into your current workspace.
|
||||
copyMenu' :: String -> X ()
|
||||
copyMenu' cmd = copyMenuConfig def { menuArgs = [], menuCommand = cmd }
|
||||
|
||||
-- | Pops open an application with window titles given over stdin. Choose one,
|
||||
-- and it will be copied into your current
|
||||
-- workspace. This version allows arguments to the chooser to be specified.
|
||||
copyMenuArgs' :: String -> [String] -> X ()
|
||||
copyMenuArgs' cmd args = copyMenuConfig def { menuArgs = args, menuCommand = cmd }
|
||||
|
||||
-- | Brings a copy of the specified window into the current workspace.
|
||||
copyBringWindow :: Window -> X.WindowSet -> X.WindowSet
|
||||
copyBringWindow w ws = copyWindow w (W.currentTag ws) ws
|
||||
|
||||
-- | Pops open a dmenu with window titles. Choose one, and it will be
|
||||
-- dragged, kicking and screaming, into your current workspace.
|
||||
bringMenu :: X ()
|
||||
@ -159,7 +193,7 @@ decorateName ws w = do
|
||||
return $ name ++ " [" ++ W.tag ws ++ "]"
|
||||
|
||||
-- | Returns the window name as will be listed in dmenu. This will
|
||||
-- return the executable name of the window along with it's workspace
|
||||
-- return the executable name of the window along with its workspace
|
||||
-- ID.
|
||||
decorateAppName :: X.WindowSpace -> Window -> X String
|
||||
decorateAppName ws w = do
|
||||
|
@ -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
|
||||
|
||||
@ -66,7 +68,8 @@ appropriate one, or cover your bases by using instead something like:
|
||||
> (className =? "Firefox" <||> className =? "Firefox-bin")
|
||||
|
||||
For detailed instructions on editing your key bindings, see
|
||||
"XMonad.Doc.Extending#Editing_key_bindings". -}
|
||||
<https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial>.
|
||||
-}
|
||||
|
||||
-- | Get the list of workspaces sorted by their tag
|
||||
workspacesSorted :: Ord i => W.StackSet i l a s sd -> [W.Workspace i l a]
|
||||
@ -89,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.
|
||||
@ -164,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.
|
||||
|
@ -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
|
||||
--
|
||||
|
@ -1,10 +1,12 @@
|
||||
{-# LANGUAGE TupleSections #-} -- I didn't want this, it's hlint's "suggestion" and it's apparently non-negotiable
|
||||
-----------------------------------------------------------------------------
|
||||
-- |
|
||||
-- Module : XMonad.Actions.WindowNavigation
|
||||
-- Description : Experimental rewrite of "XMonad.Layout.WindowNavigation".
|
||||
-- Copyright : (c) 2007 David Roundy <droundy@darcs.net>,
|
||||
-- Devin Mullins <me@twifkak.com>
|
||||
-- Maintainer : Devin Mullins <me@twifkak.com>
|
||||
-- Maintainer : Devin Mullins <me@twifkak.com>,
|
||||
-- Platon Pronko <platon7pronko@gmail.com>
|
||||
-- License : BSD3-style (see LICENSE)
|
||||
-- Stability : unstable
|
||||
-- Portability : unportable
|
||||
@ -37,17 +39,19 @@ module XMonad.Actions.WindowNavigation (
|
||||
withWindowNavigationKeys,
|
||||
WNAction(..),
|
||||
go, swap,
|
||||
goPure, swapPure,
|
||||
Direction2D(..), WNState,
|
||||
) where
|
||||
|
||||
import XMonad
|
||||
import XMonad.Prelude (catMaybes, fromMaybe, listToMaybe, sortOn)
|
||||
import XMonad hiding (state)
|
||||
import XMonad.Prelude (catMaybes, fromMaybe, sortOn)
|
||||
import XMonad.Util.Types (Direction2D(..))
|
||||
import qualified XMonad.StackSet as W
|
||||
|
||||
import Control.Arrow (second)
|
||||
import Data.IORef
|
||||
import Data.Map (Map())
|
||||
import Data.List (partition, find)
|
||||
import qualified Data.Map as M
|
||||
import qualified Data.Set as S
|
||||
|
||||
@ -101,27 +105,60 @@ withWindowNavigation (u,l,d,r) conf@XConfig{modMask=modm} =
|
||||
|
||||
withWindowNavigationKeys :: [((KeyMask, KeySym), WNAction)] -> XConfig l -> IO (XConfig l)
|
||||
withWindowNavigationKeys wnKeys conf = do
|
||||
posRef <- newIORef M.empty
|
||||
return conf { keys = \cnf -> M.fromList (map (second (fromWNAction posRef)) wnKeys)
|
||||
stateRef <- newIORef M.empty
|
||||
return conf { keys = \cnf -> M.fromList (map (second (fromWNAction stateRef)) wnKeys)
|
||||
`M.union` keys conf cnf,
|
||||
logHook = logHook conf >> trackMovement posRef }
|
||||
where fromWNAction posRef (WNGo dir) = go posRef dir
|
||||
fromWNAction posRef (WNSwap dir) = swap posRef dir
|
||||
logHook = logHook conf >> trackMovement stateRef }
|
||||
where fromWNAction stateRef (WNGo dir) = go stateRef dir
|
||||
fromWNAction stateRef (WNSwap dir) = swap stateRef dir
|
||||
|
||||
data WNAction = WNGo Direction2D | WNSwap Direction2D
|
||||
|
||||
type WNState = Map WorkspaceId Point
|
||||
|
||||
-- go:
|
||||
-- 1. get current position, verifying it matches the current window
|
||||
-- 2. get target windowrect
|
||||
-- 3. focus window
|
||||
-- 4. set new position
|
||||
-- | Focus window in the given direction.
|
||||
go :: IORef WNState -> Direction2D -> X ()
|
||||
go = withTargetWindow W.focusWindow
|
||||
go stateRef dir = runPureAction stateRef (goPure dir)
|
||||
|
||||
-- | Swap current window with the window in the given direction.
|
||||
-- Note: doesn't work with floating windows (don't think it makes much sense to swap floating windows).
|
||||
swap :: IORef WNState -> Direction2D -> X ()
|
||||
swap = withTargetWindow swapWithFocused
|
||||
swap stateRef dir = runPureAction stateRef (swapPure dir)
|
||||
|
||||
type WindowRectFn x = (Window -> x (Maybe Rectangle))
|
||||
-- | (state, oldWindowSet, mappedWindows, windowRect)
|
||||
type WNInput x = (WNState, WindowSet, S.Set Window, WindowRectFn x)
|
||||
type WNOutput = (WNState, WindowSet)
|
||||
|
||||
-- | Run the pure action inside X monad.
|
||||
runPureAction :: IORef WNState -> (WNInput X -> X WNOutput) -> X ()
|
||||
runPureAction stateRef action = do
|
||||
oldState <- io (readIORef stateRef)
|
||||
oldWindowSet <- gets windowset
|
||||
mappedWindows <- gets mapped
|
||||
(newState, newWindowSet) <- action (oldState, oldWindowSet, mappedWindows, windowRectX)
|
||||
windows (const newWindowSet)
|
||||
io $ writeIORef stateRef newState
|
||||
|
||||
-- | Version of `go` not dependent on X monad (needed for testing).
|
||||
goPure :: Monad x => Direction2D -> WNInput x -> x WNOutput
|
||||
goPure dir input@(oldState, oldWindowSet, mappedWindows, _) =
|
||||
if length (filter (`S.member` mappedWindows) $ W.integrate' $ W.stack $ W.workspace $ W.current oldWindowSet) == 1
|
||||
then
|
||||
-- Handle the special case of Full layout, when there's only one mapped window on a screen.
|
||||
return ( oldState
|
||||
, case dir of
|
||||
U -> W.focusUp oldWindowSet
|
||||
L -> W.focusDown oldWindowSet
|
||||
D -> W.focusDown oldWindowSet
|
||||
R -> W.focusUp oldWindowSet
|
||||
)
|
||||
else
|
||||
withTargetWindow W.focusWindow dir input
|
||||
|
||||
-- | Version of `swap` not dependent on X monad (needed for testing).
|
||||
swapPure :: Monad x => Direction2D -> WNInput x -> x WNOutput
|
||||
swapPure = withTargetWindow swapWithFocused
|
||||
where swapWithFocused targetWin winSet =
|
||||
case W.peek winSet of
|
||||
Just currentWin -> W.focusWindow currentWin $
|
||||
@ -135,87 +172,249 @@ swap = withTargetWindow swapWithFocused
|
||||
| win == win2 = win1
|
||||
| otherwise = win
|
||||
|
||||
withTargetWindow :: (Window -> WindowSet -> WindowSet) -> IORef WNState -> Direction2D -> X ()
|
||||
withTargetWindow adj posRef dir = fromCurrentPoint posRef $ \win pos -> do
|
||||
targets <- filter ((/= win) . fst) <$> navigableTargets pos dir
|
||||
whenJust (listToMaybe targets) $ \(targetWin, targetRect) -> do
|
||||
windows (adj targetWin)
|
||||
setPosition posRef pos targetRect
|
||||
-- | Select a target window in the given direction and modify the WindowSet.
|
||||
-- 1. Get current position, verifying it matches the current window (exit if no focused window).
|
||||
-- 2. Get the target window.
|
||||
-- 3. Execute an action on the target window and windowset.
|
||||
-- 4. Set the new position.
|
||||
withTargetWindow :: Monad x => (Window -> WindowSet -> WindowSet) -> Direction2D -> WNInput x -> x WNOutput
|
||||
withTargetWindow adj dir input@(oldState, oldWindowSet, _, _) = do
|
||||
whenJust' (getCurrentWindow input) (oldState, oldWindowSet) $ \(win, winRect, pos) -> do
|
||||
targetMaybe <- find ((/= win) . fst) <$> navigableTargets input dir winRect pos
|
||||
whenJust' (pure targetMaybe) (oldState, oldWindowSet) $ \(targetWin, newPos) ->
|
||||
let newWindowSet = adj targetWin oldWindowSet
|
||||
in return (modifyState newWindowSet newPos oldState, newWindowSet)
|
||||
|
||||
-- | Update position on outside changes in windows.
|
||||
trackMovement :: IORef WNState -> X ()
|
||||
trackMovement posRef = fromCurrentPoint posRef $ \win pos ->
|
||||
windowRect win >>= flip whenJust (setPosition posRef pos . snd)
|
||||
trackMovement stateRef = do
|
||||
oldState <- io (readIORef stateRef)
|
||||
oldWindowSet <- gets windowset
|
||||
mappedWindows <- gets mapped
|
||||
whenJust' (getCurrentWindow (oldState, oldWindowSet, mappedWindows, windowRectX)) () $ \(_, _, pos) -> do
|
||||
io $ writeIORef stateRef $ modifyState oldWindowSet pos oldState
|
||||
|
||||
fromCurrentPoint :: IORef WNState -> (Window -> Point -> X ()) -> X ()
|
||||
fromCurrentPoint posRef f = withFocused $ \win ->
|
||||
currentPosition posRef >>= f win
|
||||
-- | Get focused window and current position.
|
||||
getCurrentWindow :: Monad x => WNInput x -> x (Maybe (Window, Rectangle, Point))
|
||||
getCurrentWindow input@(_, oldWindowSet, _, _) =
|
||||
whenJust' (pure $ W.peek oldWindowSet) Nothing $ \window -> do
|
||||
(pos, rect) <- currentPosition input
|
||||
return $ Just (window, rect, pos)
|
||||
|
||||
-- Gets the current position from the IORef passed in, or if nothing (say, from
|
||||
-- a restart), derives the current position from the current window. Also,
|
||||
-- verifies that the position is congruent with the current window (say, if you
|
||||
-- used mod-j/k or mouse or something).
|
||||
currentPosition :: IORef WNState -> X Point
|
||||
currentPosition posRef = do
|
||||
root <- asks theRoot
|
||||
currentWindow <- gets (W.peek . windowset)
|
||||
currentRect <- maybe (Rectangle 0 0 0 0) snd <$> windowRect (fromMaybe root currentWindow)
|
||||
-- | Gets the current position from the state passed in, or if nothing
|
||||
-- (say, from a restart), derives the current position from the current window.
|
||||
-- Also, verifies that the position is congruent with the current window
|
||||
-- (say, if you moved focus using mouse or something).
|
||||
-- Returns the window rectangle for convenience, since we'll need it later anyway.
|
||||
currentPosition :: Monad x => WNInput x -> x (Point, Rectangle)
|
||||
currentPosition (state, oldWindowSet, _, windowRect) = do
|
||||
currentRect <- fromMaybe (Rectangle 0 0 0 0) <$> maybe (pure Nothing) windowRect (W.peek oldWindowSet)
|
||||
let posMaybe = M.lookup (W.currentTag oldWindowSet) state
|
||||
middleOf (Rectangle x y w h) = Point (midPoint x w) (midPoint y h)
|
||||
return $ case posMaybe of
|
||||
Nothing -> (middleOf currentRect, currentRect)
|
||||
Just pos -> (centerPosition currentRect pos, currentRect)
|
||||
|
||||
wsid <- gets (W.currentTag . windowset)
|
||||
mp <- M.lookup wsid <$> io (readIORef posRef)
|
||||
-- | Inserts new position into the state.
|
||||
modifyState :: WindowSet -> Point -> WNState -> WNState
|
||||
modifyState oldWindowSet =
|
||||
M.insert (W.currentTag oldWindowSet)
|
||||
|
||||
return $ maybe (middleOf currentRect) (`inside` currentRect) mp
|
||||
|
||||
where middleOf (Rectangle x y w h) = Point (midPoint x w) (midPoint y h)
|
||||
|
||||
setPosition :: IORef WNState -> Point -> Rectangle -> X ()
|
||||
setPosition posRef oldPos newRect = do
|
||||
wsid <- gets (W.currentTag . windowset)
|
||||
io $ modifyIORef posRef $ M.insert wsid (oldPos `inside` newRect)
|
||||
|
||||
inside :: Point -> Rectangle -> Point
|
||||
Point x y `inside` Rectangle rx ry rw rh =
|
||||
Point (x `within` (rx, rw)) (y `within` (ry, rh))
|
||||
where pos `within` (lower, dim) = if pos >= lower && pos < lower + fromIntegral dim
|
||||
then pos
|
||||
else midPoint lower dim
|
||||
-- | "Jumps" the current position into the middle of target rectangle.
|
||||
-- (keeps the position as-is if it is already inside the target rectangle)
|
||||
centerPosition :: Rectangle -> Point -> Point
|
||||
centerPosition r@(Rectangle rx ry rw rh) pos@(Point x y) = do
|
||||
if pointWithin x y r
|
||||
then pos
|
||||
else Point (midPoint rx rw) (midPoint ry rh)
|
||||
|
||||
midPoint :: Position -> Dimension -> Position
|
||||
midPoint pos dim = pos + fromIntegral dim `div` 2
|
||||
|
||||
navigableTargets :: Point -> Direction2D -> X [(Window, Rectangle)]
|
||||
navigableTargets point dir = navigable dir point <$> windowRects
|
||||
-- | Make a list of target windows we can navigate to,
|
||||
-- sorted by desirability of navigation.
|
||||
navigableTargets :: Monad x => WNInput x -> Direction2D -> Rectangle -> Point -> x [(Window, Point)]
|
||||
navigableTargets input@(_, oldWindowSet, _, _) dir currentRect currentPos = do
|
||||
allScreensWindowsAndRectangles <- mapSnd (rectTransform dir) <$> windowRects input
|
||||
let
|
||||
screenWindows = S.fromList $ W.integrate' $ W.stack $ W.workspace $ W.current oldWindowSet
|
||||
(thisScreenWindowsAndRectangles, otherScreensWindowsAndRectangles) = partition (\(w, _) -> S.member w screenWindows) allScreensWindowsAndRectangles
|
||||
|
||||
-- Filters and sorts the windows in terms of what is closest from the Point in
|
||||
-- the Direction2D.
|
||||
navigable :: Direction2D -> Point -> [(Window, Rectangle)] -> [(Window, Rectangle)]
|
||||
navigable d pt = sortby d . filter (inr d pt . snd)
|
||||
pos = pointTransform dir currentPos
|
||||
wr = rectTransform dir currentRect
|
||||
|
||||
-- Produces a list of normal-state windows, on any screen. Rectangles are
|
||||
-- adjusted based on screen position relative to the current screen, because I'm
|
||||
-- bad like that.
|
||||
windowRects :: X [(Window, Rectangle)]
|
||||
windowRects = fmap catMaybes . mapM windowRect . S.toList =<< gets mapped
|
||||
rectInside r = (rect_p1 r >= rect_p1 wr && rect_p1 r < rect_p2 wr && rect_p2 r > rect_p1 wr && rect_p2 r <= rect_p2 wr) &&
|
||||
((rect_o1 r >= rect_o1 wr && rect_o1 r < rect_o2 wr && rect_o2 r > rect_o1 wr && rect_o2 r <= rect_o2 wr) ||
|
||||
(rect_o1 r <= rect_o1 wr && rect_o2 r >= rect_o2 wr)) -- include windows that fully overlaps current on the orthogonal axis
|
||||
sortByP2 = sortOn (rect_p2 . snd)
|
||||
posBeforeEdge r = point_p pos < rect_p2 r
|
||||
|
||||
windowRect :: Window -> X (Maybe (Window, Rectangle))
|
||||
windowRect win = withDisplay $ \dpy -> do
|
||||
rectOverlapsEdge r = rect_p1 r <= rect_p2 wr && rect_p2 r > rect_p2 wr &&
|
||||
rect_o1 r < rect_o2 wr && rect_o2 r > rect_o1 wr
|
||||
rectOverlapsOneEdge r = rectOverlapsEdge r && rect_p1 r > rect_p1 wr
|
||||
rectOverlapsBothEdges r = rectOverlapsEdge r &&
|
||||
rect_o1 r > rect_o1 wr && rect_o2 r < rect_o2 wr && point_o pos >= rect_o1 r && point_o pos < rect_o2 r
|
||||
distanceToRectEdge r = max (max 0 (rect_o1 r - point_o pos)) (max 0 (point_o pos + 1 - rect_o2 r))
|
||||
distanceToRectCenter r =
|
||||
let distance = (rect_o1 r + rect_o2 r) `div` 2 - point_o pos
|
||||
in if distance <= 0
|
||||
then distance + 1
|
||||
else distance
|
||||
sortByPosDistance = sortOn ((\r -> (rect_p1 r, distanceToRectEdge r, distanceToRectCenter r)) . snd)
|
||||
|
||||
rectOutside r = rect_p1 r < rect_p1 wr && rect_p2 r > rect_p2 wr &&
|
||||
rect_o1 r < rect_o1 wr && rect_o2 r > rect_o2 wr
|
||||
sortByLength = sortOn (rect_psize . snd)
|
||||
|
||||
rectAfterEdge r = rect_p1 r > rect_p2 wr
|
||||
|
||||
-- Modified from David Roundy and Devin Mullins original implementation of WindowNavigation:
|
||||
inr r = point_p pos < rect_p2 r && point_o pos >= rect_o1 r && point_o pos < rect_o2 r
|
||||
|
||||
clamp v v1 v2 | v < v1 = v1
|
||||
| v >= v2 = v2 - 1
|
||||
| otherwise = v
|
||||
dragPos r = DirPoint (max (point_p pos) (rect_p1 r)) (clamp (point_o pos) (rect_o1 r) (rect_o2 r))
|
||||
|
||||
return $ mapSnd (inversePointTransform dir) $ concat
|
||||
[
|
||||
-- First, navigate to windows that are fully inside current window
|
||||
-- and have higher coordinate bigger than current position.
|
||||
-- ┌──────────────────┐
|
||||
-- │ current │ (all examples assume direction=R)
|
||||
-- │ ┌──────────┐ │
|
||||
-- │ ──┼─► inside │ │
|
||||
-- │ └──────────┘ │
|
||||
-- └──────────────────┘
|
||||
-- Also include windows fully overlapping current on the orthogonal axis:
|
||||
-- ┌──────────────┐
|
||||
-- │ overlapping │
|
||||
-- ┌───────────┤ ├────┐
|
||||
-- │ current ──┼─► │ │
|
||||
-- └───────────┤ ├────┘
|
||||
-- └──────────────┘
|
||||
mapSnd dragPos $ sortByP2 $ filterSnd posBeforeEdge $ filterSnd rectInside thisScreenWindowsAndRectangles
|
||||
|
||||
-- Then navigate to windows that touch or overlap the edge of current window in the chosen direction.
|
||||
-- ┌──────────────┬─────────────┐ ┌───────────┐ ┌─────────────┐
|
||||
-- │ current │ adjacent │ │ current │ │ current │
|
||||
-- │ ──┼─► │ │ ┌───┴───────────────┐ │ ┌───┴─────────────┐
|
||||
-- │ │ │ │ ──┼─► │ overlapping │ │ ──┼─► │
|
||||
-- │ ├─────────────┘ │ └───┬───────────────┘ └─────────┤ overlapping │
|
||||
-- │ │ │ │ │ │
|
||||
-- └──────────────┘ └───────────┘ └─────────────────┘
|
||||
, mapSnd dragPos $ sortByPosDistance $ filterSnd rectOverlapsOneEdge thisScreenWindowsAndRectangles
|
||||
|
||||
-- Windows fully overlapping current window "in the middle" on the parallel axis are also included,
|
||||
-- if position is inside them:
|
||||
-- ┌───────────┐
|
||||
-- │ current │
|
||||
-- ┌───┤-----------├────────────────┐
|
||||
-- │ │ * ──┼─► overlapping │
|
||||
-- └───┤-----------├────────────────┘
|
||||
-- └───────────┘
|
||||
, mapSnd (\_ -> DirPoint (rect_p2 wr) (point_o pos)) $ sortByPosDistance $ filterSnd rectOverlapsBothEdges thisScreenWindowsAndRectangles
|
||||
|
||||
-- Then navigate to windows that fully encompass the current window.
|
||||
-- ┌─────────────────────┐
|
||||
-- │ outer │
|
||||
-- │ ┌─────────────┐ │
|
||||
-- │ │ current ──┼─► │
|
||||
-- │ └─────────────┘ │
|
||||
-- └─────────────────────┘
|
||||
, mapSnd (\_ -> DirPoint (rect_p2 wr) (point_o pos)) $ sortByLength $ filterSnd rectOutside thisScreenWindowsAndRectangles
|
||||
|
||||
-- Then navigate to windows that are fully after current window in the chosen direction.
|
||||
-- ┌──────────────┐
|
||||
-- │ current │ ┌────────────────┐
|
||||
-- │ │ │ │
|
||||
-- │ ──┼──┼─► not adjacent │
|
||||
-- │ │ │ │
|
||||
-- │ │ └────────────────┘
|
||||
-- └──────────────┘
|
||||
, mapSnd dragPos $ sortByPosDistance $ filterSnd rectAfterEdge thisScreenWindowsAndRectangles
|
||||
|
||||
-- Cast a ray from the current position, jump to the first window (on another screen) that intersects this ray.
|
||||
, mapSnd dragPos $ sortByPosDistance $ filterSnd inr otherScreensWindowsAndRectangles
|
||||
|
||||
-- If everything else fails, then navigate to the window that is fully inside current window,
|
||||
-- but is before the current position.
|
||||
-- This can happen when we are at the last window on a screen, and attempt to navigate even further.
|
||||
-- In this case it seems okay to jump to the remaining inner windows, since we don't have any other choice anyway,
|
||||
-- and user is probably not so fully aware of the precise position anyway.
|
||||
, mapSnd (\r -> DirPoint (rect_p2 r - 1) (clamp (point_o pos) (rect_o1 r) (rect_o2 r))) $
|
||||
sortByP2 $ filterSnd (not . posBeforeEdge) $ filterSnd rectInside thisScreenWindowsAndRectangles
|
||||
]
|
||||
|
||||
-- Structs for direction-independent space - equivalent to rotating points and rectangles such that
|
||||
-- navigation direction points to the right.
|
||||
-- Allows us to abstract over direction in the navigation functions.
|
||||
data DirPoint = DirPoint
|
||||
{ point_p :: Position -- coordinate parallel to the direction
|
||||
, point_o :: Position -- coordinate orthogonal to the direction
|
||||
}
|
||||
data DirRectangle = DirRectangle
|
||||
{ rect_p1 :: Position -- lower rectangle coordinate parallel to the direction
|
||||
, rect_p2 :: Position -- higher rectangle coordinate parallel to the direction
|
||||
, rect_o1 :: Position -- lower rectangle coordinate orthogonal to the direction
|
||||
, rect_o2 :: Position -- higher rectangle coordinate orthogonal to the direction
|
||||
}
|
||||
{- HLINT ignore "Use camelCase" -}
|
||||
rect_psize :: DirRectangle -> Dimension
|
||||
rect_psize r = fromIntegral (rect_p2 r - rect_p1 r)
|
||||
|
||||
-- | Transform a point from screen space into direction-independent space.
|
||||
pointTransform :: Direction2D -> Point -> DirPoint
|
||||
pointTransform dir (Point x y) = case dir of
|
||||
U -> DirPoint (negate y - 1) x
|
||||
L -> DirPoint (negate x - 1) (negate y - 1)
|
||||
D -> DirPoint y (negate x - 1)
|
||||
R -> DirPoint x y
|
||||
|
||||
-- | Transform a point from direction-independent space back into screen space.
|
||||
inversePointTransform :: Direction2D -> DirPoint -> Point
|
||||
inversePointTransform dir p = case dir of
|
||||
U -> Point (point_o p) (negate $ point_p p + 1)
|
||||
L -> Point (negate $ point_p p + 1) (negate $ point_o p + 1)
|
||||
D -> Point (negate $ point_o p + 1) (point_p p)
|
||||
R -> Point (point_p p) (point_o p)
|
||||
|
||||
-- | Transform a rectangle from screen space into direction-independent space.
|
||||
rectTransform :: Direction2D -> Rectangle -> DirRectangle
|
||||
rectTransform dir (Rectangle x y w h) = case dir of
|
||||
U -> DirRectangle (negate $ y + fromIntegral h) (negate y) x (x + fromIntegral w)
|
||||
L -> DirRectangle (negate $ x + fromIntegral w) (negate x) (negate $ y + fromIntegral h) (negate y)
|
||||
D -> DirRectangle y (y + fromIntegral h) (negate $ x + fromIntegral w) (negate x)
|
||||
R -> DirRectangle x (x + fromIntegral w) y (y + fromIntegral h)
|
||||
|
||||
-- | Produces a list of normal-state windows on all screens, excluding currently focused window.
|
||||
windowRects :: Monad x => WNInput x -> x [(Window, Rectangle)]
|
||||
windowRects (_, oldWindowSet, mappedWindows, windowRect) =
|
||||
let
|
||||
allWindows = filter (\w -> w `notElem` W.peek oldWindowSet) $ S.toList mappedWindows
|
||||
windowRect2 w = fmap (w,) <$> windowRect w
|
||||
in catMaybes <$> mapM windowRect2 allWindows
|
||||
|
||||
windowRectX :: Window -> X (Maybe Rectangle)
|
||||
windowRectX win = withDisplay $ \dpy -> do
|
||||
(_, x, y, w, h, bw, _) <- io $ getGeometry dpy win
|
||||
return $ Just (win, Rectangle x y (w + 2 * bw) (h + 2 * bw))
|
||||
return $ Just $ Rectangle x y (w + 2 * bw) (h + 2 * bw)
|
||||
`catchX` return Nothing
|
||||
|
||||
-- Modified from droundy's implementation of WindowNavigation:
|
||||
-- Maybe below functions can be replaced with some standard helper functions?
|
||||
|
||||
inr :: Direction2D -> Point -> Rectangle -> Bool
|
||||
inr D (Point px py) (Rectangle rx ry w h) = px >= rx && px < rx + fromIntegral w &&
|
||||
py < ry + fromIntegral h
|
||||
inr U (Point px py) (Rectangle rx ry w _) = px >= rx && px < rx + fromIntegral w &&
|
||||
py > ry
|
||||
inr R (Point px py) (Rectangle rx ry w h) = px < rx + fromIntegral w &&
|
||||
py >= ry && py < ry + fromIntegral h
|
||||
inr L (Point px py) (Rectangle rx ry _ h) = px > rx &&
|
||||
py >= ry && py < ry + fromIntegral h
|
||||
-- | Execute a monadic action on the contents if Just, otherwise wrap default value and return it.
|
||||
whenJust' :: Monad x => x (Maybe a) -> b -> (a -> x b) -> x b
|
||||
whenJust' monadMaybeValue deflt f = do
|
||||
maybeValue <- monadMaybeValue
|
||||
case maybeValue of
|
||||
Nothing -> return deflt
|
||||
Just value -> f value
|
||||
|
||||
sortby :: Direction2D -> [(a,Rectangle)] -> [(a,Rectangle)]
|
||||
sortby D = sortOn (rect_y . snd)
|
||||
sortby R = sortOn (rect_x . snd)
|
||||
sortby U = reverse . sortby D
|
||||
sortby L = reverse . sortby R
|
||||
-- | Filter a list of tuples on the second tuple member.
|
||||
filterSnd :: (b -> Bool) -> [(a, b)] -> [(a, b)]
|
||||
filterSnd f = filter (f . snd)
|
||||
|
||||
-- | Map a second tuple member in a list of tuples.
|
||||
mapSnd :: (b -> b') -> [(a, b)] -> [(a, b')]
|
||||
mapSnd f = map (second f)
|
||||
|
@ -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
|
||||
--
|
||||
@ -33,7 +33,7 @@ import XMonad.StackSet
|
||||
-- , ((modm .|. shiftMask, xK_t), sinkAll)
|
||||
--
|
||||
-- For detailed instructions on editing your key bindings, see
|
||||
-- "XMonad.Doc.Extending#Editing_key_bindings".
|
||||
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial>.
|
||||
|
||||
-- | Un-float all floating windows on the current workspace.
|
||||
sinkAll :: X ()
|
||||
|
@ -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"]
|
||||
@ -58,7 +58,7 @@ import XMonad.Actions.OnScreen
|
||||
-- > , (f, m) <- [(Workscreen.viewWorkscreen, 0), (Workscreen.shiftToWorkscreen, shiftMask)]]
|
||||
--
|
||||
-- For detailed instructions on editing your key bindings, see
|
||||
-- "XMonad.Doc.Extending#Editing_key_bindings".
|
||||
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial>.
|
||||
|
||||
|
||||
data Workscreen = Workscreen{workscreenId::Int,workspaces::[WorkspaceId]} deriving (Show)
|
||||
@ -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
|
||||
|
@ -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
|
||||
|
@ -51,14 +51,14 @@ import XMonad.Actions.CycleWS (findWorkspace, WSType(..), Direction1D(..), anyWS
|
||||
import qualified XMonad.Actions.SwapWorkspaces as Swap
|
||||
import XMonad.Hooks.StatusBar.PP (PP(..))
|
||||
import XMonad.Hooks.EwmhDesktops (addEwmhWorkspaceRename)
|
||||
import XMonad.Prompt (mkXPrompt, XPConfig)
|
||||
import XMonad.Prompt (mkXPrompt, XPConfig, historyCompletionP)
|
||||
import XMonad.Prompt.Workspace (Wor(Wor))
|
||||
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
|
||||
--
|
||||
@ -88,7 +88,7 @@ import qualified Data.Map as M
|
||||
-- > | (i, k) <- zip workspaces [xK_1 ..]]
|
||||
--
|
||||
-- For detailed instructions on editing your key bindings, see
|
||||
-- "XMonad.Doc.Extending#Editing_key_bindings".
|
||||
-- <https://xmonad.org/TUTORIAL.html#customizing-xmonad the tutorial>.
|
||||
|
||||
|
||||
|
||||
@ -137,9 +137,11 @@ setCurrentWorkspaceName name = do
|
||||
|
||||
-- | Prompt for a new name for the current workspace and set it.
|
||||
renameWorkspace :: XPConfig -> X ()
|
||||
renameWorkspace conf =
|
||||
mkXPrompt pr conf (const (return [])) setCurrentWorkspaceName
|
||||
where pr = Wor "Workspace name: "
|
||||
renameWorkspace conf = do
|
||||
completion <- historyCompletionP conf (prompt ==)
|
||||
mkXPrompt (Wor prompt) conf completion setCurrentWorkspaceName
|
||||
where
|
||||
prompt = "Workspace name: "
|
||||
|
||||
-- | See 'XMonad.Actions.SwapWorkspaces.swapTo'. This is the same with names.
|
||||
swapTo :: Direction1D -> X ()
|
||||
|
@ -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
|
||||
--
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -29,7 +29,7 @@ module XMonad.Config.Bluetile (
|
||||
import XMonad
|
||||
|
||||
import XMonad.Layout.BorderResize
|
||||
import XMonad.Layout.BoringWindows
|
||||
import XMonad.Layout.BoringWindows hiding (Replace)
|
||||
import XMonad.Layout.ButtonDecoration
|
||||
import XMonad.Layout.Decoration
|
||||
import XMonad.Layout.DecorationAddons
|
||||
@ -37,7 +37,7 @@ import XMonad.Layout.DraggingVisualizer
|
||||
import XMonad.Layout.Maximize
|
||||
import XMonad.Layout.Minimize
|
||||
import XMonad.Layout.MouseResizableTile
|
||||
import XMonad.Layout.Named
|
||||
import XMonad.Layout.Renamed
|
||||
import XMonad.Layout.NoBorders
|
||||
import XMonad.Layout.PositionStoreFloat
|
||||
import XMonad.Layout.WindowSwitcherDecoration
|
||||
@ -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
|
||||
@ -183,10 +183,10 @@ bluetileManageHook = composeAll
|
||||
, isFullscreen --> doFullFloat]
|
||||
|
||||
bluetileLayoutHook = avoidStruts $ minimize $ boringWindows $
|
||||
named "Floating" floating |||
|
||||
named "Tiled1" tiled1 |||
|
||||
named "Tiled2" tiled2 |||
|
||||
named "Fullscreen" fullscreen
|
||||
renamed [Replace "Floating"] floating |||
|
||||
renamed [Replace "Tiled1"] tiled1 |||
|
||||
renamed [Replace "Tiled2"] tiled2 |||
|
||||
renamed [Replace "Fullscreen"] fullscreen
|
||||
where
|
||||
floating = floatingDeco $ maximize $ borderResize positionStoreFloat
|
||||
tiled1 = tilingDeco $ maximize mouseResizableTileMirrored
|
||||
|
@ -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
|
||||
-- <http://haskell.org/haskellwiki/Xmonad>
|
||||
--
|
||||
-- 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@.
|
||||
@ -91,7 +91,7 @@ import qualified Data.Map as M
|
||||
|
||||
-- $customizing
|
||||
-- To customize a desktop config, modify its fields as is illustrated with
|
||||
-- the default configuration @def@ in "XMonad.Doc.Extending#Extending xmonad".
|
||||
-- the default configuration @def@ in <https://xmonad.org/TUTORIAL.html the tutorial>.
|
||||
|
||||
-- $layouts
|
||||
-- See also "XMonad.Util.EZConfig" for more options for modifying key bindings.
|
||||
|
@ -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
|
||||
-- }}}
|
||||
|
@ -1,4 +1,5 @@
|
||||
{-# LANGUAGE PatternGuards #-}
|
||||
{-# LANGUAGE TupleSections #-}
|
||||
{-# OPTIONS_GHC -fno-warn-missing-signatures -fno-warn-orphans #-}
|
||||
-----------------------------------------------------------------------------
|
||||
-- |
|
||||
@ -20,7 +21,7 @@ import System.Exit ( exitSuccess )
|
||||
import XMonad.Layout.Tabbed ( tabbed,
|
||||
shrinkText, Shrinker, shrinkIt, CustomShrink(CustomShrink) )
|
||||
import XMonad.Layout.Combo ( combineTwo )
|
||||
import XMonad.Layout.Named ( named )
|
||||
import XMonad.Layout.Renamed ( Rename(Replace), renamed )
|
||||
import XMonad.Layout.LayoutCombinators
|
||||
import XMonad.Layout.Square ( Square(Square) )
|
||||
import XMonad.Layout.WindowNavigation ( Navigate(Move,Swap,Go), Direction2D(U,D,R,L),
|
||||
@ -114,9 +115,9 @@ keys x = M.fromList $
|
||||
]
|
||||
|
||||
++
|
||||
zip (zip (repeat $ modMask x) [xK_F1..xK_F12]) (map (withNthWorkspace W.greedyView) [0..])
|
||||
zip (map (modMask x,) [xK_F1..xK_F12]) (map (withNthWorkspace W.greedyView) [0..])
|
||||
++
|
||||
zip (zip (repeat (modMask x .|. shiftMask)) [xK_F1..xK_F12]) (map (withNthWorkspace copy) [0..])
|
||||
zip (map (modMask x .|. shiftMask,) [xK_F1..xK_F12]) (map (withNthWorkspace copy) [0..])
|
||||
|
||||
config = docks $ ewmh def
|
||||
{ borderWidth = 1 -- Width of the window border in pixels.
|
||||
@ -124,10 +125,10 @@ config = docks $ ewmh def
|
||||
, layoutHook = showWName $ workspaceDir "~" $
|
||||
boringWindows $ smartBorders $ windowNavigation $
|
||||
maximizeVertical $ toggleLayouts Full $ avoidStruts $
|
||||
named "tabbed" mytab |||
|
||||
named "xclock" (mytab ****//* combineTwo Square mytab mytab) |||
|
||||
named "three" (mytab **//* mytab *//* combineTwo Square mytab mytab) |||
|
||||
named "widescreen" ((mytab *||* mytab)
|
||||
renamed [Replace "tabbed"] mytab |||
|
||||
renamed [Replace "xclock"] (mytab ****//* combineTwo Square mytab mytab) |||
|
||||
renamed [Replace "three"] (mytab **//* mytab *//* combineTwo Square mytab mytab) |||
|
||||
renamed [Replace "widescreen"] ((mytab *||* mytab)
|
||||
****//* combineTwo Square mytab mytab) -- |||
|
||||
--mosaic 0.25 0.5
|
||||
, terminal = "xterm" -- The preferred terminal program.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -1,3 +1,4 @@
|
||||
{-# OPTIONS_HADDOCK hide #-}
|
||||
{-# LANGUAGE FlexibleContexts, FlexibleInstances, FunctionalDependencies, KindSignatures, UndecidableInstances #-}
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
@ -126,7 +127,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
|
||||
@ -388,7 +389,7 @@ instance RemovableClass MouseBindings [(ButtonMask, Button)] where
|
||||
MouseBindings { mRemove = r } =- sadBindings = return . r sadBindings
|
||||
|
||||
-- | Mouse button bindings to an 'X' actions on a window. Default: see @`man
|
||||
-- xmonad`@. To make mod-<scrollwheel> switch workspaces:
|
||||
-- xmonad`@. To make @mod-\<scrollwheel\>@ switch workspaces:
|
||||
--
|
||||
-- > import XMonad.Actions.CycleWS (nextWS, prevWS)
|
||||
-- > ...
|
||||
|
@ -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
|
||||
|
@ -14,7 +14,9 @@
|
||||
-- <https://xmonad.org/TUTORIAL.html xmonad website>.
|
||||
--
|
||||
-- For more detailed instructions on extending xmonad with the
|
||||
-- xmonad-contrib library, see "XMonad.Doc.Extending".
|
||||
-- xmonad-contrib library, see
|
||||
-- <https://xmonad.org/TUTORIAL.html the tutorial>
|
||||
-- and "XMonad.Doc.Extending".
|
||||
--
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
|
@ -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".
|
||||
|
@ -71,20 +71,15 @@ module XMonad.Doc.Extending
|
||||
-- *** Removing key bindings
|
||||
-- $keyDel
|
||||
|
||||
-- *** Adding and removing key bindings
|
||||
-- $keyAddDel
|
||||
|
||||
-- ** Editing mouse bindings
|
||||
-- $mouse
|
||||
|
||||
-- ** Editing the layout hook
|
||||
-- ** Editing the layout hook #LayoutHook#
|
||||
-- $layoutHook
|
||||
|
||||
-- ** Editing the manage hook
|
||||
-- ** Editing the manage hook #ManageHook#
|
||||
-- $manageHook
|
||||
|
||||
-- ** The log hook and external status bars
|
||||
-- $logHook
|
||||
) where
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
@ -108,8 +103,8 @@ For more information about any particular module, go to the root of
|
||||
the documentation and just click on its name to view its Haddock
|
||||
documentation; each module should come with extensive documentation.
|
||||
If you find a module that could be better documented, or has incorrect
|
||||
documentation, please report it as a bug
|
||||
(<https://github.com/xmonad/xmonad-contrib/issues>)!
|
||||
documentation, please
|
||||
<https://github.com/xmonad/xmonad-contrib/issues report it as a bug>!
|
||||
|
||||
First and foremost, xmonad defines its own prelude for commonly used
|
||||
functions, as well as re-exports from @base@.
|
||||
@ -137,33 +132,31 @@ A list of the contrib modules can be found at
|
||||
In the @XMonad.Actions@ namespace you can find modules exporting
|
||||
various functions that are usually intended to be bound to key
|
||||
combinations or mouse actions, in order to provide functionality
|
||||
beyond the standard keybindings provided by xmonad.
|
||||
beyond the standard keybindings offered by xmonad.
|
||||
|
||||
-}
|
||||
|
||||
{- $hooks
|
||||
|
||||
In the @XMonad.Hooks@ namespace you can find modules exporting
|
||||
hooks. Hooks are actions that xmonad performs when certain events
|
||||
occur. The three most important hooks are:
|
||||
hooks—actions that xmonad performs when certain events occur.
|
||||
The three most important hooks are:
|
||||
|
||||
* 'XMonad.Core.manageHook': this hook is called when a new window that
|
||||
xmonad must take care of is created. This is a very powerful hook,
|
||||
since it lets us examine the new window's properties and act
|
||||
accordingly. For instance, we can configure xmonad to put windows
|
||||
belonging to a given application in the float layer, not to manage
|
||||
dock applications, or open them in a given workspace. See
|
||||
"XMonad.Doc.Extending#Editing_the_manage_hook" for more information
|
||||
on customizing 'XMonad.Core.manageHook'.
|
||||
dock applications, or open them in a given workspace. See [Editing the
|
||||
manage hook](#g:Editing_the_manage_hook) for more information on
|
||||
customizing 'XMonad.Core.manageHook'.
|
||||
|
||||
* 'XMonad.Core.logHook': this hook is called when the stack of windows
|
||||
managed by xmonad has been changed; for example, this is invoked at
|
||||
the end of the 'XMonad.Operations.windows' function. For instance
|
||||
"XMonad.Hooks.DynamicLog" will produce a string (whose format can be
|
||||
configured) to be printed to the standard output. This can be used
|
||||
to display some information about the xmonad state in a status bar.
|
||||
See "XMonad.Doc.Extending#The_log_hook_and_external_status_bars" for
|
||||
more information.
|
||||
managed by xmonad changes; for example, this is invoked at the end of
|
||||
the 'XMonad.Operations.windows' function. A big application for this
|
||||
is to display some information about xmonad in a status bar. The aptly
|
||||
named "XMonad.Hooks.StatusBar" will produce a string (whose format can
|
||||
be configured) to be written, for example, to an X11 property.
|
||||
|
||||
* 'XMonad.Core.handleEventHook': this hook is called on all events handled
|
||||
by xmonad, thus it is extremely powerful. See "Graphics.X11.Xlib.Extras"
|
||||
@ -187,13 +180,13 @@ interface for writing layout modifiers is implemented in
|
||||
"XMonad.Layout.LayoutModifier".
|
||||
|
||||
For more information on using those modules for customizing your
|
||||
'XMonad.Core.layoutHook' see "XMonad.Doc.Extending#Editing_the_layout_hook".
|
||||
'XMonad.Core.layoutHook' see [Editing the layout hook](#g:LayoutHook).
|
||||
|
||||
-}
|
||||
|
||||
{- $prompts
|
||||
|
||||
In the @XMonad.Prompt@ name space you can find modules providing
|
||||
In the @XMonad.Prompt@ namespace you can find modules providing
|
||||
graphical prompts for getting user input and using it to perform
|
||||
various actions.
|
||||
|
||||
@ -206,7 +199,7 @@ prompts.
|
||||
|
||||
In the @XMonad.Util@ namespace you can find modules exporting various
|
||||
utility functions that are used by the other modules of the
|
||||
xmonad-contrib library.
|
||||
@xmonad-contrib@ library.
|
||||
|
||||
There are also utilities for helping in configuring xmonad or using
|
||||
external utilities.
|
||||
@ -222,7 +215,7 @@ external utilities.
|
||||
{- $extending
|
||||
#Extending_xmonad#
|
||||
|
||||
Since the @xmonad.hs@ file is just another Haskell module, you may
|
||||
Since the @xmonad.hs@ file is just another Haskell program, you may
|
||||
import and use any Haskell code or libraries you wish, such as
|
||||
extensions from the xmonad-contrib library, or other code you write
|
||||
yourself.
|
||||
@ -388,83 +381,74 @@ extend xmonad an scratch a particular itch!
|
||||
{- $keyDel
|
||||
#Removing_key_bindings#
|
||||
|
||||
Removing key bindings requires modifying the 'Data.Map.Strict.Map' which
|
||||
stores the key bindings. This can be done with 'Data.Map.difference' or
|
||||
with 'Data.Map.Strict.delete'.
|
||||
As we've learned, XMonad stores keybindings inside of a
|
||||
'Data.Map.Strict.Map', which means that removing keybindings requires
|
||||
modifying it. This can be done with 'Data.Map.difference' or with
|
||||
'Data.Map.Strict.delete'.
|
||||
|
||||
For example, suppose you want to get rid of @mod-q@ and @mod-shift-q@
|
||||
(you just want to leave xmonad running forever). To do this you need to
|
||||
define @newKeys@ as a 'Data.Map.Strict.difference' between the default
|
||||
map and the map of the key bindings you want to remove. Like so:
|
||||
For example, suppose you want to entirely rid yourself of @"M-q"@ and
|
||||
@"M-s-q"@ (you just want to leave xmonad running forever). To do this
|
||||
with bare @xmonad@, you need to define @newKeys@ as a
|
||||
'Data.Map.Strict.difference' between the default map and the map of the
|
||||
key bindings you want to remove. Like so:
|
||||
|
||||
> newKeys x = keys def x `M.difference` keysToRemove x
|
||||
> newKeys :: XConfig l -> Map (KeyMask, KeySym) (X ())
|
||||
> newKeys x = keys def x `M.difference` keysToRemove x
|
||||
>
|
||||
> keysToRemove :: XConfig Layout -> M.Map (KeyMask, KeySym) (X ())
|
||||
> keysToRemove x = M.fromList
|
||||
> [ ((modm , xK_q ), return ())
|
||||
> , ((modm .|. shiftMask, xK_q ), return ())
|
||||
> ]
|
||||
> keysToRemove :: XConfig l -> Map (KeyMask, KeySym) (X ())
|
||||
> keysToRemove x = M.fromList
|
||||
> [ ((modm , xK_q ), return ())
|
||||
> , ((modm .|. shiftMask, xK_q ), return ())
|
||||
> ]
|
||||
|
||||
As you can see, it doesn't matter what actions we associate with the
|
||||
keys listed in @keysToRemove@, so we just use @return ()@ (the
|
||||
\"null\" action).
|
||||
keys listed in @keysToRemove@, so we just use @return ()@ (the \"null\"
|
||||
action). Since @newKeys@ contains all of the default keys, you can
|
||||
simply pass it to 'XMonad.Core.XConfig' as your map of keybindings:
|
||||
|
||||
It is also possible to simply define a list of keys we want to unbind
|
||||
and then use 'Data.Map.Strict.delete' to remove them. In that case we
|
||||
would write something like:
|
||||
> main :: IO ()
|
||||
> main = xmonad $ def { keys = newKeys }
|
||||
|
||||
> newKeys x = foldr M.delete (keys def x) (keysToRemove x)
|
||||
>
|
||||
> keysToRemove :: XConfig Layout -> [(KeyMask, KeySym)]
|
||||
> keysToRemove x =
|
||||
> [ (modm , xK_q )
|
||||
> , (modm .|. shiftMask, xK_q )
|
||||
> ]
|
||||
However, having to manually type @return ()@ every time seems like a
|
||||
drag, doesn't it? And this approach isn't at all compatible with adding
|
||||
custom keybindings via 'XMonad.Util.EZConfig.additionalKeysP'! Well,
|
||||
good thing "XMonad.Util.EZConfig" also sports
|
||||
'XMonad.Util.EZConfig.removeKeysP'. You can use it as you would expect.
|
||||
|
||||
Another even simpler possibility is the use of some of the utilities
|
||||
provided by the xmonad-contrib library. Look, for instance, at
|
||||
'XMonad.Util.EZConfig.removeKeys'.
|
||||
> main :: IO ()
|
||||
> main = xmonad $ def
|
||||
> { … }
|
||||
> `removeKeysP` ["M-q", "M-S-q"]
|
||||
|
||||
-}
|
||||
Can you guess how 'XMonad.Util.EZConfig.removeKeysP' works? It's almost
|
||||
the same code we wrote above, just accepting a list of keybindings. Try
|
||||
to see if you can come up with an implementation of
|
||||
|
||||
{- $keyAddDel
|
||||
#Adding_and_removing_key_bindings#
|
||||
> removeKeysP :: XConfig l -> [String] -> XConfig l
|
||||
|
||||
Adding and removing key bindings requires simply combining the steps
|
||||
for removing and adding. Here is an example from
|
||||
"XMonad.Config.Arossato":
|
||||
If you're done, just click on @# Source@ when viewing the
|
||||
'XMonad.Util.EZConfig.removeKeysP' documentation (did you know that
|
||||
Haddock lets you do that for every function?) and compare.
|
||||
|
||||
> defKeys = keys def
|
||||
> delKeys x = foldr M.delete (defKeys x) (toRemove x)
|
||||
> newKeys x = foldr (uncurry M.insert) (delKeys x) (toAdd x)
|
||||
> -- remove some of the default key bindings
|
||||
> toRemove XConfig{modMask = modm} =
|
||||
> [ (modm , xK_j )
|
||||
> , (modm , xK_k )
|
||||
> , (modm , xK_p )
|
||||
> , (modm .|. shiftMask, xK_p )
|
||||
> , (modm .|. shiftMask, xK_q )
|
||||
> , (modm , xK_q )
|
||||
> ] ++
|
||||
> -- I want modm .|. shiftMask 1-9 to be free!
|
||||
> [(shiftMask .|. modm, k) | k <- [xK_1 .. xK_9]]
|
||||
> -- These are my personal key bindings
|
||||
> toAdd XConfig{modMask = modm} =
|
||||
> [ ((modm , xK_F12 ), xmonadPrompt def )
|
||||
> , ((modm , xK_F3 ), shellPrompt def )
|
||||
> ] ++
|
||||
> -- Use modm .|. shiftMask .|. controlMask 1-9 instead
|
||||
> [( (m .|. modm, k), windows $ f i)
|
||||
> | (i, k) <- zip (workspaces x) [xK_1 .. xK_9]
|
||||
> , (f, m) <- [(W.greedyView, 0), (W.shift, shiftMask .|. controlMask)]
|
||||
> ]
|
||||
By the way, one can conveniently combine
|
||||
'XMonad.Util.EZConfig.additionalKeysP' and
|
||||
'XMonad.Util.EZConfig.removeKeysP' by just intuitively chaining them:
|
||||
|
||||
You can achieve the same result using the "XMonad.Util.CustomKeys"
|
||||
module; take a look at the 'XMonad.Util.CustomKeys.customKeys'
|
||||
function in particular.
|
||||
> main :: IO ()
|
||||
> main = xmonad $ def
|
||||
> { … }
|
||||
> `additionalKeysP myKeys
|
||||
> `removeKeysP` ["M-q", "M-S-q"]
|
||||
|
||||
If you don't use the @P@ alternatives of EZConfig, there is also an
|
||||
aptly named 'XMonad.Util.EZConfig.removeKeys'. Again, can you try to
|
||||
come up with an implementation yourself that has the correct signature?
|
||||
|
||||
> removeKeys :: XConfig a -> [(KeyMask, KeySym)] -> XConfig a
|
||||
|
||||
In addition to 'Data.Map.Strict.delete', you will probably need to use
|
||||
'foldr'.
|
||||
|
||||
NOTE: modm is defined as the modMask you defined (or left as the default) in
|
||||
your config.
|
||||
-}
|
||||
|
||||
{- $mouse
|
||||
@ -506,8 +490,7 @@ xmonad will use for laying out windows on the screen(s).
|
||||
The problem is that the layout subsystem is implemented with an
|
||||
advanced feature of the Haskell programming language: type classes.
|
||||
This allows us to very easily write new layouts, combine or modify
|
||||
existing layouts, create layouts with internal state, etc. See
|
||||
"XMonad.Doc.Extending#The_LayoutClass" for more information. This
|
||||
existing layouts, create layouts with internal state, etc. This
|
||||
means that we cannot simply have a list of layouts: a list requires
|
||||
every member to belong to the same type!
|
||||
|
||||
@ -517,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
|
||||
>
|
||||
@ -546,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
|
||||
>
|
||||
@ -711,42 +694,3 @@ For additional rules and actions you can use in your manageHook, check
|
||||
out the contrib module "XMonad.Hooks.ManageHelpers".
|
||||
|
||||
-}
|
||||
|
||||
{- $logHook
|
||||
#The_log_hook_and_external_status_bars#
|
||||
|
||||
When the stack of the windows managed by xmonad changes for any
|
||||
reason, xmonad will call 'XMonad.Core.logHook', which can be used to
|
||||
output some information about the internal state of xmonad, such as the
|
||||
layout that is presently in use, the workspace we are in, the focused
|
||||
window's title, and so on.
|
||||
|
||||
Extracting information about the internal xmonad state can be somewhat
|
||||
difficult if you are not familiar with the source code. Therefore,
|
||||
it's usually easiest to use a module that has been designed
|
||||
specifically for logging some of the most interesting information
|
||||
about the internal state of xmonad: "XMonad.Hooks.DynamicLog". This
|
||||
module can be used with an external status bar to print the produced
|
||||
logs in a convenient way; the most commonly used status bars are dzen
|
||||
and xmobar. The module "XMonad.Hooks.StatusBar" offers another interface
|
||||
to interact with status bars, that might be more convenient to use.
|
||||
|
||||
By default the 'XMonad.Core.logHook' doesn't produce anything. To
|
||||
enable it you need first to import "XMonad.Hooks.DynamicLog":
|
||||
|
||||
> import XMonad.Hooks.DynamicLog
|
||||
|
||||
Then you just need to update the 'XMonad.Core.logHook' field of the
|
||||
'XMonad.Core.XConfig' record with one of the provided functions. For
|
||||
example:
|
||||
|
||||
> main = xmonad def { logHook = dynamicLog }
|
||||
|
||||
More interesting configurations are also possible; see the
|
||||
"XMonad.Hooks.DynamicLog" module for more possibilities.
|
||||
|
||||
You may now enjoy your extended xmonad experience.
|
||||
|
||||
Have fun!
|
||||
|
||||
-}
|
||||
|
@ -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)
|
||||
|
@ -33,16 +33,16 @@ import XMonad.Util.DebugWindow (debugWindow)
|
||||
-- import Graphics.X11.Xlib.Extras.GetAtomName (getAtomName)
|
||||
|
||||
import Control.Exception as E
|
||||
import Control.Monad.Fail
|
||||
import Control.Monad.State
|
||||
import Control.Monad.Reader
|
||||
import Codec.Binary.UTF8.String
|
||||
import Foreign
|
||||
import Foreign hiding (void)
|
||||
import Foreign.C.Types
|
||||
import Numeric (showHex)
|
||||
import System.Exit
|
||||
import System.IO
|
||||
import System.Process
|
||||
import GHC.Stack (HasCallStack, prettyCallStack, callStack)
|
||||
|
||||
-- | Event hook to dump all received events. You should probably not use this
|
||||
-- unconditionally; it will produce massive amounts of output.
|
||||
@ -203,7 +203,7 @@ windowEvent l w = debugWindow w >>= say l
|
||||
|
||||
-- | Helper to emit tagged event information.
|
||||
say :: String -> String -> X ()
|
||||
say l s = trace $ l ++ ' ':s
|
||||
say l s = XMonad.trace $ l ++ ' ':s
|
||||
|
||||
-- | Deconstuct a list of 'CInt's into raw bytes
|
||||
splitCInt :: [CInt] -> IO Raw
|
||||
@ -240,7 +240,7 @@ data DecodeState = DecS {value :: Raw -- unconsumed raw property value
|
||||
}
|
||||
|
||||
newtype Decoder a = Decoder (ReaderT Decode (StateT DecodeState X) a)
|
||||
#ifndef __HADDOCK__
|
||||
|
||||
deriving (Functor
|
||||
,Applicative
|
||||
,Monad
|
||||
@ -249,12 +249,12 @@ newtype Decoder a = Decoder (ReaderT Decode (StateT DecodeState X) a)
|
||||
,MonadState DecodeState
|
||||
,MonadReader Decode
|
||||
)
|
||||
#endif
|
||||
|
||||
|
||||
-- | Retrive, parse, and dump a window property. As all the high-level property
|
||||
-- interfaces lose information necessary to decode properties correctly, we
|
||||
-- work at the lowest level available.
|
||||
dumpProperty :: Atom -> String -> Window -> Int -> X String
|
||||
dumpProperty :: HasCallStack => Atom -> String -> Window -> Int -> X String
|
||||
dumpProperty a n w i = do
|
||||
prop <- withDisplay $ \d ->
|
||||
io $
|
||||
@ -279,7 +279,7 @@ dumpProperty a n w i = do
|
||||
case rc of
|
||||
0 -> do
|
||||
fmt <- fromIntegral <$> peek fmtp
|
||||
vs' <- peek vsp
|
||||
vs' <- peek vsp
|
||||
sz <- fromIntegral <$> peek szp
|
||||
case () of
|
||||
() | fmt == none -> xFree vs' >> return (Left "(property deleted)" )
|
||||
@ -303,7 +303,8 @@ dumpProperty a n w i = do
|
||||
|
||||
-- @@@ am I better off passing in the Decode and DecodeState?
|
||||
-- | Parse and dump a property (or a 'ClientMessage').
|
||||
dumpProperty' :: Window -- source window
|
||||
dumpProperty' :: HasCallStack
|
||||
=> Window -- source window
|
||||
-> Atom -- property id
|
||||
-> String -- property name
|
||||
-> Atom -- property type
|
||||
@ -334,11 +335,11 @@ dumpProperty' w a n fmt sz vs ack i = do
|
||||
(_,ds') <- runDecode dec ds $ dumpProp a n
|
||||
let fin = length (value ds')
|
||||
len = length vs
|
||||
lost = if ack == 0 then "" else "and " ++ show ack ++ " lost bytes"
|
||||
lost = if ack == 0 then "" else " and " ++ show ack ++ " lost bytes"
|
||||
unk = case () of
|
||||
() | fin == len -> "undecodeable "
|
||||
| fin == 0 -> "."
|
||||
| otherwise -> "and remainder (" ++ show (len - fin) ++ '/':show len ++ ")"
|
||||
| otherwise -> " and remainder (" ++ show (len - fin) ++ '/':show len ++ ")"
|
||||
(_,ds'') <- if fin == 0
|
||||
then return (True,ds')
|
||||
else runDecode dec' (withJoint' unk ds' ) $ dumpArray dump8
|
||||
@ -349,7 +350,7 @@ dumpProperty' w a n fmt sz vs ack i = do
|
||||
|
||||
-- | A simplified version of 'dumpProperty\'', to format random values from
|
||||
-- events.
|
||||
quickFormat :: (Storable i, Integral i) => [i] -> Decoder Bool -> X String
|
||||
quickFormat :: (HasCallStack, Storable i, Integral i) => [i] -> Decoder Bool -> X String
|
||||
quickFormat v f = do
|
||||
let vl = length v
|
||||
vs <- io $
|
||||
@ -382,7 +383,7 @@ bytes w = w `div` 8
|
||||
-- | The top level property decoder, for a wide variety of standard ICCCM and
|
||||
-- EWMH window properties. We pass part of the 'ReaderT' as arguments for
|
||||
-- pattern matching.
|
||||
dumpProp :: Atom -> String -> Decoder Bool
|
||||
dumpProp :: HasCallStack => Atom -> String -> Decoder Bool
|
||||
|
||||
dumpProp _ "CLIPBOARD" = dumpSelection
|
||||
dumpProp _ "_NET_SUPPORTED" = dumpArray dumpAtom
|
||||
@ -426,10 +427,13 @@ dumpProp _ "_NET_WM_NAME" = dumpUTF
|
||||
dumpProp _ "_NET_WM_VISIBLE_NAME" = dumpUTF
|
||||
dumpProp _ "_NET_WM_ICON_NAME" = dumpUTF
|
||||
dumpProp _ "_NET_WM_VISIBLE_ICON_NAME" = dumpUTF
|
||||
dumpProp _ "_NET_WM_DESKTOP" = dumpExcept [(0xFFFFFFFF,"all")]
|
||||
dump32
|
||||
-- @@@ the property is CARDINAL; the message is _NET_WM_DESKTOP of 5 dump32s
|
||||
-- [desktop/all, source indication, 3 zeroes]
|
||||
-- dumpProp _ "_NET_WM_DESKTOP" = dumpExcept [(0xFFFFFFFF,"all")]
|
||||
-- dump32
|
||||
dumpProp _ "_NET_WM_DESKTOP" = dumpSetDesktop
|
||||
dumpProp _ "_NET_WM_WINDOW_TYPE" = dumpArray dumpAtom
|
||||
dumpProp _ "_NET_WM_STATE" = dumpArray dumpAtom
|
||||
dumpProp _ "_NET_WM_STATE" = dumpNWState
|
||||
dumpProp _ "_NET_WM_ALLOWED_ACTIONS" = dumpArray dumpAtom
|
||||
dumpProp _ "_NET_WM_STRUT" = dumpList [("left gap" ,dump32)
|
||||
,("right gap" ,dump32)
|
||||
@ -467,6 +471,12 @@ dumpProp _ "_NET_FRAME_EXTENTS" = dumpList [("left" ,dum
|
||||
]
|
||||
dumpProp _ "_NET_WM_SYNC_REQUEST_COUNTER" = dumpExcept [(0,"illegal value 0")]
|
||||
dump64
|
||||
dumpProp _ "_NET_WM_OPAQUE_REGION" = dumpArray $ dumpList [("x",dump32)
|
||||
,("y",dump32)
|
||||
,("w",dump32)
|
||||
,("h",dump32)
|
||||
]
|
||||
dumpProp _ "_NET_WM_BYPASS_COMPOSITOR" = dumpEnum cpState
|
||||
dumpProp _ "_NET_STARTUP_ID" = dumpUTF
|
||||
dumpProp _ "WM_PROTOCOLS" = dumpArray dumpAtom
|
||||
dumpProp _ "WM_COLORMAP_WINDOWS" = dumpArray dumpWindow
|
||||
@ -521,8 +531,8 @@ dumpProp a _ | a == wM_NAME = dumpString
|
||||
]
|
||||
)
|
||||
]
|
||||
| a == wM_NORMAL_HINTS = (...)
|
||||
| a == wM_ZOOM_HINTS = (...) -- same as previous
|
||||
| a == wM_NORMAL_HINTS = dumpSizeHints
|
||||
| a == wM_ZOOM_HINTS = dumpSizeHints
|
||||
| a == rGB_DEFAULT_MAP = (...) -- XStandardColormap
|
||||
| a == rGB_BEST_MAP = (...) -- "
|
||||
| a == rGB_RED_MAP = (...) -- "
|
||||
@ -567,12 +577,12 @@ withIndent :: Int -> Decoder a -> Decoder a
|
||||
withIndent w = local (\r -> r {indent = indent r + w})
|
||||
|
||||
-- dump an array of items. this dumps the entire property
|
||||
dumpArray :: Decoder Bool -> Decoder Bool
|
||||
dumpArray :: HasCallStack => Decoder Bool -> Decoder Bool
|
||||
dumpArray item = do
|
||||
withIndent 1 $ append "[" >> withJoint "" (dumpArray' item "")
|
||||
|
||||
-- step through values as an array, ending on parse error or end of list
|
||||
dumpArray' :: Decoder Bool -> String -> Decoder Bool
|
||||
dumpArray' :: HasCallStack => Decoder Bool -> String -> Decoder Bool
|
||||
dumpArray' item pfx = do
|
||||
vs <- gets value
|
||||
if null vs
|
||||
@ -582,12 +592,12 @@ dumpArray' item pfx = do
|
||||
-- keep parsing until a parse step fails
|
||||
-- @@@ which points out that all my uses of @whenX (return ...)@ are actually 'when',
|
||||
-- which suggests that 'whenX' is *also* the same function... yep. ISAGN
|
||||
whenD :: Monad m => m Bool -> m Bool -> m Bool
|
||||
whenD :: (HasCallStack, Monad m) => m Bool -> m Bool -> m Bool
|
||||
whenD p f = p >>= \b -> if b then f else return False
|
||||
|
||||
-- verify a decoder parameter, else call error reporter
|
||||
-- once again, it's more general than I originally wrote
|
||||
guardR :: (MonadReader r m, Eq v)
|
||||
guardR :: (HasCallStack, MonadReader r m, Eq v)
|
||||
=> (r -> v) -- value selector
|
||||
-> v -- expected value
|
||||
-> (v -> v -> m a) -- error reporter
|
||||
@ -598,43 +608,47 @@ guardR sel val err good = do
|
||||
if v == val then good else err v val
|
||||
|
||||
-- this is kinda dumb
|
||||
fi :: Bool -> a -> a -> a
|
||||
fi :: HasCallStack => Bool -> a -> a -> a
|
||||
fi p n y = if p then y else n -- flip (if' p), if that existed
|
||||
|
||||
-- verify we have the expected word size
|
||||
guardSize :: Int -> Decoder Bool -> Decoder Bool
|
||||
guardSize :: HasCallStack => Int -> Decoder Bool -> Decoder Bool
|
||||
-- see XSync documentation for this insanity
|
||||
guardSize 64 = guardR width 32 propSizeErr . guardSize' 8 propShortErr
|
||||
guardSize w = guardR width w propSizeErr . guardSize' (bytes w) propShortErr
|
||||
guardSize 64 = guardR width 32 propSizeErr . guardSize' 8 (propShortErr' 1)
|
||||
guardSize w = guardR width w propSizeErr . guardSize' (bytes w) (propShortErr' 2)
|
||||
|
||||
guardSize' :: Int -> Decoder a -> Decoder a -> Decoder a
|
||||
guardSize' l n y = gets value >>= \vs -> fi (length vs >= l) n y
|
||||
guardSize' :: HasCallStack => Int -> Decoder a -> Decoder a -> Decoder a
|
||||
guardSize' l n y = gets value >>= \vs -> fi (length vs >= bytes l) n y
|
||||
|
||||
-- @guardSize@ doesn't work with empty arrays
|
||||
guardSize'' :: HasCallStack => Int -> Decoder a -> Decoder a -> Decoder a
|
||||
guardSize'' l n y = gets value >>= \vs -> fi (null vs || length vs >= bytes l) n y
|
||||
|
||||
-- verify we have the expected property type
|
||||
guardType :: Atom -> Decoder Bool -> Decoder Bool
|
||||
guardType :: HasCallStack => Atom -> Decoder Bool -> Decoder Bool
|
||||
guardType t = guardR pType t propTypeErr
|
||||
|
||||
-- dump a structure as a named tuple
|
||||
dumpList :: [(String,Decoder Bool)] -> Decoder Bool
|
||||
dumpList :: HasCallStack => [(String,Decoder Bool)] -> Decoder Bool
|
||||
dumpList proto = do
|
||||
a <- asks pType
|
||||
dumpList'' (maxBound :: CULong) (map (\(s,d) -> (s,d,a)) proto) "("
|
||||
|
||||
-- same but elements have their own distinct types
|
||||
dumpList' :: [(String,Decoder Bool,Atom)] -> Decoder Bool
|
||||
dumpList' :: HasCallStack => [(String,Decoder Bool,Atom)] -> Decoder Bool
|
||||
dumpList' proto = dumpList'' (maxBound :: CULong) proto "("
|
||||
|
||||
-- same but only dump elements identified by provided mask
|
||||
dumpListByMask :: CULong -> [(String,Decoder Bool)] -> Decoder Bool
|
||||
dumpListByMask :: HasCallStack => CULong -> [(String,Decoder Bool)] -> Decoder Bool
|
||||
dumpListByMask m p = do
|
||||
a <- asks pType
|
||||
dumpList'' m (map (\(s,d) -> (s,d,a)) p) "("
|
||||
|
||||
-- and the previous two combined
|
||||
dumpListByMask' :: CULong -> [(String,Decoder Bool,Atom)] -> Decoder Bool
|
||||
dumpListByMask' :: HasCallStack => CULong -> [(String,Decoder Bool,Atom)] -> Decoder Bool
|
||||
dumpListByMask' m p = dumpList'' m p "("
|
||||
|
||||
dumpList'' :: CULong -> [(String,Decoder Bool,Atom)] -> String -> Decoder Bool
|
||||
dumpList'' :: HasCallStack => CULong -> [(String,Decoder Bool,Atom)] -> String -> Decoder Bool
|
||||
dumpList'' _ [] _ = append ")" >> return True
|
||||
dumpList'' 0 _ _ = append ")" >> return True
|
||||
dumpList'' m ((l,p,t):ps) sep = do
|
||||
@ -659,22 +673,20 @@ dumpList'' m ((l,p,t):ps) sep = do
|
||||
|
||||
-- do the getTextProperty dance, the hard way.
|
||||
-- @@@ @COMPOUND_TEXT@ not supported yet.
|
||||
dumpString :: Decoder Bool
|
||||
dumpString :: HasCallStack => Decoder Bool
|
||||
dumpString = do
|
||||
fmt <- asks pType
|
||||
[cOMPOUND_TEXT,uTF8_STRING] <- inX $ mapM getAtom ["COMPOUND_TEXT","UTF8_STRING"]
|
||||
case () of
|
||||
() | fmt == cOMPOUND_TEXT -> guardSize 16 (...)
|
||||
| fmt == sTRING -> guardSize 8 $ do
|
||||
() | fmt == cOMPOUND_TEXT -> guardSize'' 16 (propShortErr' 3) ( ... )
|
||||
| fmt == sTRING -> guardSize'' 8 (propShortErr' 4) $ do
|
||||
vs <- gets value
|
||||
modify (\r -> r {value = []})
|
||||
let ss = flip unfoldr (map twiddle vs) $
|
||||
\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
|
||||
@ -688,7 +700,7 @@ dumpString = do
|
||||
failure . ("unrecognized string type " ++)
|
||||
|
||||
-- show who owns a selection
|
||||
dumpSelection :: Decoder Bool
|
||||
dumpSelection :: HasCallStack => Decoder Bool
|
||||
dumpSelection = do
|
||||
-- system selections contain a window ID; others are random
|
||||
-- note that the window ID will be the same as the owner, so
|
||||
@ -702,14 +714,14 @@ dumpSelection = do
|
||||
append $ "owned by " ++ w
|
||||
|
||||
-- for now, not querying Xkb
|
||||
dumpXKlInds :: Decoder Bool
|
||||
dumpXKlInds :: HasCallStack => Decoder Bool
|
||||
dumpXKlInds = guardType iNTEGER $ do
|
||||
n <- fmap fromIntegral <$> getInt' 32
|
||||
case n of
|
||||
Nothing -> propShortErr
|
||||
Nothing -> propShortErr' 5
|
||||
Just is -> append $ "indicators " ++ unwords (dumpInds is 1 1 [])
|
||||
where
|
||||
dumpInds :: Word32 -> Word32 -> Int -> [String] -> [String]
|
||||
dumpInds :: HasCallStack => Word32 -> Word32 -> Int -> [String] -> [String]
|
||||
dumpInds n bt c bs | n == 0 && c == 1 = ["none"]
|
||||
| n == 0 = bs
|
||||
| n .&. bt /= 0 = dumpInds (n .&. complement bt)
|
||||
@ -722,9 +734,20 @@ dumpXKlInds = guardType iNTEGER $ do
|
||||
bs
|
||||
|
||||
-- decode an Atom
|
||||
dumpAtom :: Decoder Bool
|
||||
dumpAtom =
|
||||
guardType aTOM $ do
|
||||
|
||||
dumpAtom :: HasCallStack => Decoder Bool
|
||||
dumpAtom = dumpAtom'' aTOM
|
||||
|
||||
{-
|
||||
dumpAtom' :: HasCallStack => String -> Decoder Bool
|
||||
dumpAtom' t' = do
|
||||
t <- inX $ getAtom t'
|
||||
dumpAtom'' t
|
||||
-}
|
||||
|
||||
dumpAtom'' :: HasCallStack => Atom -> Decoder Bool
|
||||
dumpAtom'' t =
|
||||
guardType t $ do
|
||||
a <- getInt' 32
|
||||
case a of
|
||||
Nothing -> return False
|
||||
@ -732,15 +755,16 @@ dumpAtom =
|
||||
an <- inX $ atomName $ fromIntegral a'
|
||||
append an
|
||||
|
||||
dumpWindow :: Decoder Bool
|
||||
dumpWindow :: HasCallStack => Decoder Bool
|
||||
dumpWindow = guardSize 32 $ guardType wINDOW $ do
|
||||
w <- getInt' 32
|
||||
case w of
|
||||
Nothing -> return False
|
||||
Just 0 -> append "none"
|
||||
Just w' -> inX (debugWindow (fromIntegral w')) >>= append
|
||||
|
||||
-- a bit of a hack; as a Property it's a wINDOW, as a ClientMessage it's a list
|
||||
dumpActiveWindow :: Decoder Bool
|
||||
dumpActiveWindow :: HasCallStack => Decoder Bool
|
||||
dumpActiveWindow = guardSize 32 $ do
|
||||
t <- asks pType
|
||||
nAW <- inX $ getAtom "_NET_ACTIVE_WINDOW"
|
||||
@ -754,49 +778,87 @@ dumpActiveWindow = guardSize 32 $ do
|
||||
t' <- inX $ atomName t
|
||||
failure $ concat ["(bad type "
|
||||
,t'
|
||||
,"; expected WINDOW or _NET_ACTIVE_WINDOW"
|
||||
,"; expected WINDOW or _NET_ACTIVE_WINDOW)"
|
||||
]
|
||||
|
||||
-- likewise but for _NET_WM_DESKTOP
|
||||
dumpSetDesktop :: HasCallStack => Decoder Bool
|
||||
dumpSetDesktop = guardSize 32 $ do
|
||||
t <- asks pType
|
||||
nWD <- inX $ getAtom "_NET_WM_DESKTOP"
|
||||
case () of
|
||||
() | t == cARDINAL -> dumpExcept [(0xFFFFFFFF,"all")]
|
||||
dump32
|
||||
| t == nWD -> dumpList' [("desktop",dumpExcept [(0xFFFFFFFF,"all")]
|
||||
dump32 ,cARDINAL)
|
||||
,("source" ,dumpEnum awSource ,cARDINAL)
|
||||
]
|
||||
_ -> do
|
||||
t' <- inX $ atomName t
|
||||
failure $ concat ["(bad type "
|
||||
,t'
|
||||
,"; expected CARDINAL or _NET_WM_DESKTOP)"
|
||||
]
|
||||
|
||||
-- and again for _NET_WM_STATE
|
||||
dumpNWState :: HasCallStack => Decoder Bool
|
||||
dumpNWState = guardSize'' 32 propShortErr $ do
|
||||
t <- asks pType
|
||||
nWS <- inX $ getAtom "_NET_WM_STATE"
|
||||
case () of
|
||||
() | t == aTOM -> dumpArray dumpAtom
|
||||
| t == nWS -> dumpList' [("action",dumpEnum nwAction,cARDINAL)
|
||||
,("atom1" ,dumpAtom ,aTOM)
|
||||
,("atom2" ,dumpAtom ,aTOM)
|
||||
]
|
||||
_ -> do
|
||||
t' <- inX $ atomName t
|
||||
failure $ concat ["(bad type "
|
||||
,t'
|
||||
,"; expected ATOM or _NET_WM_STATE)"
|
||||
]
|
||||
|
||||
-- dump a generic CARDINAL value
|
||||
dumpInt :: Int -> Decoder Bool
|
||||
dumpInt :: HasCallStack => Int -> Decoder Bool
|
||||
dumpInt w = guardSize w $ guardType cARDINAL $ getInt w show
|
||||
|
||||
-- INTEGER is the signed version of CARDINAL
|
||||
dumpInteger :: Int -> Decoder Bool
|
||||
dumpInteger :: HasCallStack => Int -> Decoder Bool
|
||||
dumpInteger w = guardSize w $ guardType iNTEGER $ getInt w (show . signed w)
|
||||
|
||||
-- reinterpret an unsigned as a signed
|
||||
signed :: Int -> Integer -> Integer
|
||||
signed :: HasCallStack => Int -> Integer -> Integer
|
||||
signed w i = bit (w + 1) - i
|
||||
|
||||
-- and wrappers to keep the parse list in bounds
|
||||
dump64 :: Decoder Bool
|
||||
dump64 :: HasCallStack => Decoder Bool
|
||||
dump64 = dumpInt 64
|
||||
|
||||
dump32 :: Decoder Bool
|
||||
dump32 :: HasCallStack => Decoder Bool
|
||||
dump32 = dumpInt 32
|
||||
|
||||
{- not used in standard properties
|
||||
dump16 :: Decoder Bool
|
||||
dump16 :: HasCallStack => Decoder Bool
|
||||
dump16 = dumpInt 16
|
||||
-}
|
||||
|
||||
dump8 :: Decoder Bool
|
||||
dump8 :: HasCallStack => Decoder Bool
|
||||
dump8 = dumpInt 8
|
||||
|
||||
-- I am assuming for the moment that this is a single string.
|
||||
-- This might be false; consider the way the STRING properties
|
||||
-- handle lists.
|
||||
dumpUTF :: Decoder Bool
|
||||
dumpUTF :: HasCallStack => Decoder Bool
|
||||
dumpUTF = do
|
||||
uTF8_STRING <- inX $ getAtom "UTF8_STRING"
|
||||
guardType uTF8_STRING $ guardSize 8 $ do
|
||||
guardType uTF8_STRING $ guardSize'' 8 propShortErr $ do
|
||||
s <- gets value
|
||||
modify (\r -> r {value = []})
|
||||
append . show . decode . map fromIntegral $ s
|
||||
return True
|
||||
|
||||
-- dump an enumerated value using a translation table
|
||||
dumpEnum' :: [String] -> Atom -> Decoder Bool
|
||||
dumpEnum' :: HasCallStack => [String] -> Atom -> Decoder Bool
|
||||
dumpEnum' ss fmt = guardType fmt $
|
||||
getInt 32 $
|
||||
\r -> case () of
|
||||
@ -805,11 +867,12 @@ dumpEnum' ss fmt = guardType fmt $
|
||||
| otherwise -> genericIndex ss r
|
||||
|
||||
-- we do not, unlike @xev@, try to ascii-art pixmaps.
|
||||
dumpPixmap :: Decoder Bool
|
||||
dumpPixmap :: HasCallStack => Decoder Bool
|
||||
dumpPixmap = guardType pIXMAP $ do
|
||||
p' <- getInt' 32
|
||||
case p' of
|
||||
Nothing -> return False
|
||||
Just 0 -> append "none"
|
||||
Just p -> do
|
||||
append $ "pixmap " ++ showHex p ""
|
||||
g' <- inX $ withDisplay $ \d -> io $
|
||||
@ -833,46 +896,80 @@ dumpPixmap = guardType pIXMAP $ do
|
||||
,")"
|
||||
]
|
||||
|
||||
dumpOLAttrs :: Decoder Bool
|
||||
dumpOLAttrs :: HasCallStack => Decoder Bool
|
||||
dumpOLAttrs = do
|
||||
pt <- inX $ getAtom "_OL_WIN_ATTR"
|
||||
guardType pt $ do
|
||||
msk <- getInt' 32
|
||||
case msk of
|
||||
Nothing -> propShortErr
|
||||
Nothing -> propShortErr' 7
|
||||
Just msk' -> dumpListByMask (fromIntegral msk') [("window type" ,dumpAtom )
|
||||
,("menu" ,dump32 ) -- @@@ unk
|
||||
,("pushpin" ,dumpEnum bool)
|
||||
,("limited menu",dump32 ) -- @@@ unk
|
||||
]
|
||||
|
||||
dumpMwmHints :: Decoder Bool
|
||||
dumpMwmHints :: HasCallStack => Decoder Bool
|
||||
dumpMwmHints = do
|
||||
ta <- asks property
|
||||
guardType ta $ do
|
||||
msk <- getInt' 32
|
||||
case msk of
|
||||
Nothing -> propShortErr
|
||||
Just msk' -> dumpListByMask (fromIntegral msk') [("functions" ,dumpBits mwmFuncs )
|
||||
,("decorations",dumpBits mwmDecos )
|
||||
,("input mode" ,dumpEnum mwmInputMode)
|
||||
,("status" ,dumpBits mwmState )
|
||||
]
|
||||
Nothing -> propShortErr' 8
|
||||
Just msk' -> dumpListByMask' (fromIntegral msk') [("functions" ,dumpBits mwmFuncs ,cARDINAL)
|
||||
,("decorations",dumpBits mwmDecos ,cARDINAL)
|
||||
,("input mode" ,dumpEnum mwmInputMode,cARDINAL) -- @@@ s/b iNTEGER?
|
||||
,("status" ,dumpBits mwmState ,cARDINAL)
|
||||
]
|
||||
|
||||
dumpMwmInfo :: Decoder Bool
|
||||
dumpMwmInfo :: HasCallStack => Decoder Bool
|
||||
dumpMwmInfo = do
|
||||
ta <- asks property
|
||||
guardType ta $ dumpList' [("flags" ,dumpBits mwmHints,cARDINAL)
|
||||
,("window",dumpWindow ,wINDOW )
|
||||
]
|
||||
|
||||
dumpSizeHints :: HasCallStack => Decoder Bool
|
||||
dumpSizeHints = do
|
||||
guardType wM_SIZE_HINTS $ do
|
||||
-- flags, 4 unused CARD32s, fields as specified by flags
|
||||
msk <- fmap fromIntegral <$> getInt' 32
|
||||
eat (4 * 4) >> pure False
|
||||
case msk of
|
||||
Nothing -> propShortErr' 9
|
||||
Just msk' -> dumpListByMask' msk' [("min size" ,dumpSize ,cARDINAL)
|
||||
,("max size" ,dumpSize ,cARDINAL)
|
||||
,("increment" ,dumpSize ,cARDINAL)
|
||||
,("aspect ratio",dumpAspect,cARDINAL)
|
||||
,("base size" ,dumpSize ,cARDINAL)
|
||||
,("gravity" ,dumpGrav ,cARDINAL)
|
||||
]
|
||||
|
||||
dumpSize :: HasCallStack => Decoder Bool
|
||||
dumpSize = append "(" >> dump32 >> append "," >> dump32 >> append ")"
|
||||
|
||||
dumpAspect :: HasCallStack => Decoder Bool
|
||||
dumpAspect = do
|
||||
-- have to do this manually since it doesn't really fit
|
||||
append "min = "
|
||||
dump32
|
||||
append "/"
|
||||
dump32
|
||||
append ", max = "
|
||||
dump32
|
||||
append "/"
|
||||
dump32
|
||||
|
||||
dumpGrav :: HasCallStack => Decoder Bool
|
||||
dumpGrav = dumpEnum wmGravity
|
||||
|
||||
-- the most common case
|
||||
dumpEnum :: [String] -> Decoder Bool
|
||||
dumpEnum :: HasCallStack => [String] -> Decoder Bool
|
||||
dumpEnum ss = dumpEnum' ss cARDINAL
|
||||
|
||||
-- implement exceptional cases atop a normal dumper
|
||||
-- @@@ there's gotta be a better way
|
||||
dumpExcept :: [(Integer,String)] -> Decoder Bool -> Decoder Bool
|
||||
dumpExcept :: HasCallStack => [(Integer,String)] -> Decoder Bool -> Decoder Bool
|
||||
dumpExcept xs item = do
|
||||
-- this horror brought to you by reparsing to get the right value for our use
|
||||
sp <- get
|
||||
@ -887,7 +984,8 @@ dumpExcept xs item = do
|
||||
-- and after all that, we can process the exception list
|
||||
dumpExcept' xs that v
|
||||
|
||||
dumpExcept' :: [(Integer,String)]
|
||||
dumpExcept' :: HasCallStack
|
||||
=> [(Integer,String)]
|
||||
-> DecodeState
|
||||
-> Integer
|
||||
-> Decoder Bool
|
||||
@ -897,7 +995,7 @@ dumpExcept' ((exc,str):xs) that val | exc == val = append str
|
||||
|
||||
-- use @ps@ to get process information.
|
||||
-- @@@@ assumes a POSIX @ps@, not a BSDish one.
|
||||
dumpPid :: Decoder Bool
|
||||
dumpPid :: HasCallStack => Decoder Bool
|
||||
dumpPid = guardType cARDINAL $ do
|
||||
n <- getInt' 32
|
||||
case n of
|
||||
@ -915,17 +1013,17 @@ dumpPid = guardType cARDINAL $ do
|
||||
then "pid " ++ pid
|
||||
else prc !! 1
|
||||
|
||||
dumpTime :: Decoder Bool
|
||||
dumpTime :: HasCallStack => Decoder Bool
|
||||
dumpTime = append "server event # " >> dump32
|
||||
|
||||
dumpState :: Decoder Bool
|
||||
dumpState :: HasCallStack => Decoder Bool
|
||||
dumpState = do
|
||||
wM_STATE <- inX $ getAtom "WM_STATE"
|
||||
guardType wM_STATE $ dumpList' [("state" ,dumpEnum wmState,cARDINAL)
|
||||
,("icon window",dumpWindow ,wINDOW )
|
||||
]
|
||||
|
||||
dumpMotifDragReceiver :: Decoder Bool
|
||||
dumpMotifDragReceiver :: HasCallStack => Decoder Bool
|
||||
dumpMotifDragReceiver = do
|
||||
ta <- inX $ getAtom "_MOTIF_DRAG_RECEIVER_INFO"
|
||||
guardType ta $ dumpList' [("endian" ,dumpMotifEndian,cARDINAL)
|
||||
@ -933,11 +1031,11 @@ dumpMotifDragReceiver = do
|
||||
,("style" ,dumpMDropStyle ,cARDINAL) -- @@@ dummy
|
||||
]
|
||||
|
||||
dumpMDropStyle :: Decoder Bool
|
||||
dumpMDropStyle :: HasCallStack => Decoder Bool
|
||||
dumpMDropStyle = do
|
||||
d <- getInt' 8
|
||||
pad 1 $ case d of
|
||||
Nothing -> propShortErr
|
||||
Nothing -> propShortErr' 9
|
||||
Just ps | ps == 0 -> pad 12 $ append "none"
|
||||
| ps == 1 -> pad 12 $ append "drop only"
|
||||
| ps == 2 -> append "prefer preregister " >> dumpMDPrereg
|
||||
@ -947,7 +1045,7 @@ dumpMDropStyle = do
|
||||
| ps == 6 -> pad 12 $ append "prefer receiver"
|
||||
| otherwise -> failure $ "unknown drop style " ++ show ps
|
||||
|
||||
dumpMDPrereg :: Decoder Bool
|
||||
dumpMDPrereg :: HasCallStack => Decoder Bool
|
||||
dumpMDPrereg = do
|
||||
-- this is a bit ugly; we pretend to be extending the above dumpList'
|
||||
append ","
|
||||
@ -957,7 +1055,7 @@ dumpMDPrereg = do
|
||||
append "drop sites = "
|
||||
dsc' <- getInt' 16
|
||||
case dsc' of
|
||||
Nothing -> propShortErr
|
||||
Nothing -> propShortErr' 10
|
||||
Just dsc -> do
|
||||
withIndent 13 $ append (show dsc)
|
||||
pad 2 $ do
|
||||
@ -969,7 +1067,7 @@ dumpMDPrereg = do
|
||||
dumpMDBlocks :: Int -> Decoder Bool
|
||||
dumpMDBlocks _ = propSimple "(drop site info)" -- @@@ maybe later if needed
|
||||
|
||||
dumpMotifEndian :: Decoder Bool
|
||||
dumpMotifEndian :: HasCallStack => Decoder Bool
|
||||
dumpMotifEndian = guardType cARDINAL $ guardSize 8 $ do
|
||||
c <- map twiddle <$> eat 1
|
||||
case c of
|
||||
@ -977,14 +1075,14 @@ dumpMotifEndian = guardType cARDINAL $ guardSize 8 $ do
|
||||
['B'] -> append "big"
|
||||
_ -> failure "bad endian flag"
|
||||
|
||||
pad :: Int -> Decoder Bool -> Decoder Bool
|
||||
pad :: HasCallStack => Int -> Decoder Bool -> Decoder Bool
|
||||
pad n p = do
|
||||
vs <- gets value
|
||||
if length vs < n
|
||||
then propShortErr
|
||||
then propShortErr' 11
|
||||
else modify (\r -> r {value = drop n vs}) >> p
|
||||
|
||||
dumpPercent :: Decoder Bool
|
||||
dumpPercent :: HasCallStack => Decoder Bool
|
||||
dumpPercent = guardType cARDINAL $ do
|
||||
n <- getInt' 32
|
||||
case n of
|
||||
@ -994,7 +1092,7 @@ dumpPercent = guardType cARDINAL $ do
|
||||
pct :: Double
|
||||
in append $ show (round pct :: Integer) ++ "%"
|
||||
|
||||
dumpWmHints :: Decoder Bool
|
||||
dumpWmHints :: HasCallStack => Decoder Bool
|
||||
dumpWmHints =
|
||||
guardType wM_HINTS $ do
|
||||
msk <- getInt' 32
|
||||
@ -1011,7 +1109,7 @@ dumpWmHints =
|
||||
,("window_group" ,dumpWindow ,wINDOW )
|
||||
]
|
||||
|
||||
dumpBits :: [String] -> Decoder Bool
|
||||
dumpBits :: HasCallStack => [String] -> Decoder Bool
|
||||
dumpBits bs = guardType cARDINAL $ do
|
||||
n <- getInt' 32
|
||||
case n of
|
||||
@ -1069,6 +1167,9 @@ awSource = ["unspecified"
|
||||
,"pager/task list"
|
||||
]
|
||||
|
||||
cpState :: [String]
|
||||
cpState = ["no preference","disable compositing","force compositing"]
|
||||
|
||||
{- eventually...
|
||||
wmHintsFlags :: [String]
|
||||
wmHintsFlags = ["Input"
|
||||
@ -1112,6 +1213,12 @@ nwmOrigin = nwmEnum Nothing ["TOPLEFT","TOPRIGHT","BOTTOMRIGHT","BOTTOMLEFT"]
|
||||
wmState :: [String]
|
||||
wmState = ["Withdrawn","Normal","Zoomed (obsolete)","Iconified","Inactive"]
|
||||
|
||||
nwAction :: [String]
|
||||
nwAction = ["Clear", "Set", "Toggle"]
|
||||
|
||||
wmGravity :: [String]
|
||||
wmGravity = ["forget/unmap","NW","N","NE","W","C","E","SW","S","SE","static"]
|
||||
|
||||
nwmEnum :: Maybe String
|
||||
-> [String]
|
||||
-> [String]
|
||||
@ -1121,7 +1228,7 @@ nwmEnum (Just prefix) vs = map (("_NET_WM_" ++ prefix ++ "_") ++) vs
|
||||
-- and the lowest level coercions --
|
||||
|
||||
-- parse and return an integral value
|
||||
getInt' :: Int -> Decoder (Maybe Integer)
|
||||
getInt' :: HasCallStack => Int -> Decoder (Maybe Integer)
|
||||
-- see XSync documentation for this insanity
|
||||
getInt' 64 = guardR width 32 (\a e -> propSizeErr a e >> return Nothing) $
|
||||
guardSize' 8 (propShortErr >> return Nothing) $ do
|
||||
@ -1129,7 +1236,7 @@ getInt' 64 = guardR width 32 (\a e -> propSizeErr a e >> return Nothing) $
|
||||
hi <- inhale 32
|
||||
return $ Just $ lo + hi * (fromIntegral (maxBound :: Word32) + 1)
|
||||
getInt' w = guardR width w (\a e -> propSizeErr a e >> return Nothing) $
|
||||
guardSize' (bytes w) (propShortErr >> return Nothing) $
|
||||
guardSize' (bytes w) (propShortErr' 13 >> return Nothing) $
|
||||
Just <$> inhale w
|
||||
|
||||
-- parse an integral value and feed it to a show-er of some kind
|
||||
@ -1171,8 +1278,8 @@ append :: String -> Decoder Bool
|
||||
append = append' True
|
||||
|
||||
-- and the same but for errors
|
||||
failure :: String -> Decoder Bool
|
||||
failure = append' False
|
||||
failure :: HasCallStack => String -> Decoder Bool
|
||||
failure = append' False . (++ prettyCallStack callStack)
|
||||
|
||||
-- common appender
|
||||
append' :: Bool -> String -> Decoder Bool
|
||||
@ -1186,9 +1293,13 @@ propSimple :: String -> Decoder Bool
|
||||
propSimple s = modify (\r -> r {value = []}) >> append s
|
||||
|
||||
-- report various errors
|
||||
propShortErr :: Decoder Bool
|
||||
propShortErr :: HasCallStack => Decoder Bool
|
||||
propShortErr = failure "(property ended prematurely)"
|
||||
|
||||
-- debug version
|
||||
propShortErr' :: HasCallStack => Int -> Decoder Bool
|
||||
propShortErr' n = failure $ "(short prop " ++ show n ++ ")"
|
||||
|
||||
propSizeErr :: Int -> Int -> Decoder Bool
|
||||
propSizeErr e a = failure $ "(bad bit width " ++
|
||||
show a ++
|
||||
|
@ -51,9 +51,8 @@ import System.IO (hPutStrLn
|
||||
-- the key; @mask@ is raw, and @clean@ is what @xmonad@ sees after
|
||||
-- sanitizing it (removing @numberLockMask@, etc.)
|
||||
--
|
||||
-- For more detailed instructions on editing the logHook see:
|
||||
--
|
||||
-- "XMonad.Doc.Extending#The_log_hook_and_external_status_bars"
|
||||
-- For more detailed instructions on editing the logHook see
|
||||
-- <https://xmonad.org/TUTORIAL.html#make-xmonad-and-xmobar-talk-to-each-other the tutorial>.
|
||||
|
||||
-- | Print key events to stderr for debugging
|
||||
debugKeyEvents :: Event -> X All
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user