mirror of
https://github.com/xmonad/xmonad.git
synced 2025-07-27 02:01:52 -07:00
Compare commits
441 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
1396343a58 | ||
|
c9334fbae7 | ||
|
c496c31158 | ||
|
31f63bb162 | ||
|
ddb4292d5a | ||
|
765e059470 | ||
|
c4cf4715f7 | ||
|
fa124f5658 | ||
|
855ff2f73c | ||
|
5a04fa185d | ||
|
706f54862c | ||
|
1d43cd203f | ||
|
8421b100dd | ||
|
0156e2963b | ||
|
32afd5e7e8 | ||
|
404e50d560 | ||
|
05d6037f53 | ||
|
2f58567912 | ||
|
f2aa84e102 | ||
|
2b7e278f7f | ||
|
bd69d20d01 | ||
|
35fa7bf4a2 | ||
|
a239a00977 | ||
|
afb66dd55c | ||
|
1b7dea7acc | ||
|
8e820945f4 | ||
|
21cc6ebd93 | ||
|
050ba6420d | ||
|
67b5510dde | ||
|
327c2cf0c1 | ||
|
96b3628b54 | ||
|
dc4d304802 | ||
|
c264e4cdb3 | ||
|
bb895d8415 | ||
|
897597463a | ||
|
937493256d | ||
|
8ec512b437 | ||
|
e563e01a5f | ||
|
0e4c1e6837 | ||
|
5c2ba06902 | ||
|
efffa8946a | ||
|
431bb4b57c | ||
|
3c80296733 | ||
|
f289b3b134 | ||
|
0932779f15 | ||
|
9138046ec5 | ||
|
a24e9b4c7f | ||
|
1aac6611d8 | ||
|
51e507e953 | ||
|
10abd059b7 | ||
|
d4c607c4f9 | ||
|
28bc7dacde | ||
|
b7a76a5e8c | ||
|
58f3e8c6f1 | ||
|
00045cfc2a | ||
|
69134f9e8a | ||
|
025a78508c | ||
|
b03fa7a67b | ||
|
64fbf6a09d | ||
|
eeac754ac7 | ||
|
5ee76ca48f | ||
|
aa9dd2696a | ||
|
19cba6b25f | ||
|
a09ca446fb | ||
|
5641038500 | ||
|
990555c8ab | ||
|
a207e30751 | ||
|
970893f556 | ||
|
460096dfc5 | ||
|
0817c6a7ff | ||
|
b59473b016 | ||
|
c2aeaffc03 | ||
|
7f95f5ef07 | ||
|
57b715972b | ||
|
a9866836d9 | ||
|
099233812e | ||
|
9cb13bdd3d | ||
|
86bb4d2a21 | ||
|
30103efbc8 | ||
|
a16541b834 | ||
|
202fecf7ba | ||
|
dd1e02555e | ||
|
4931bc4e41 | ||
|
67267b7346 | ||
|
4806b51a23 | ||
|
98e5d1583d | ||
|
dc48e9e9c9 | ||
|
572d02d8e8 | ||
|
f53db04285 | ||
|
2324d21202 | ||
|
f2c9c75f67 | ||
|
1364ee4b1f | ||
|
a17fa0d28b | ||
|
b394435443 | ||
|
faf5cf7b27 | ||
|
9d0fd62cb2 | ||
|
386d4e6295 | ||
|
ea295dabcc | ||
|
4b3e5e0d07 | ||
|
4b2107a07a | ||
|
3ae5f46052 | ||
|
f734f19c1a | ||
|
391c0fc0c9 | ||
|
5ecdf7591d | ||
|
301428e5df | ||
|
63f73e18f9 | ||
|
57c3a13125 | ||
|
e6329968ff | ||
|
c1670303c0 | ||
|
1d1c012cb9 | ||
|
a19ffb0404 | ||
|
5aa70bd88a | ||
|
2502fd8d55 | ||
|
d0942e37ad | ||
|
40f8246080 | ||
|
be8fd7fdc9 | ||
|
6e35910b62 | ||
|
2f2d105098 | ||
|
cd86480ff7 | ||
|
5c7c28060c | ||
|
78719507a9 | ||
|
f4d25fcef4 | ||
|
314390937c | ||
|
cf4d6f31b1 | ||
|
044d9244e5 | ||
|
ab99c17a68 | ||
|
d170e99bc5 | ||
|
96452213f4 | ||
|
c19eb06807 | ||
|
6d7da8dc25 | ||
|
f96b3f0398 | ||
|
34f257ad6f | ||
|
f94ad61a27 | ||
|
3977a7a4e2 | ||
|
3cd839f0ac | ||
|
a9abc4e09c | ||
|
25a4ed69da | ||
|
262dc4779f | ||
|
a2259bb309 | ||
|
3d8238b35d | ||
|
fd9de8903f | ||
|
fb1f33258e | ||
|
d0f12af1ae | ||
|
df6b40c980 | ||
|
b771abeadc | ||
|
2db596fbe8 | ||
|
0f31b24bd2 | ||
|
5cdddab1f1 | ||
|
488b52ffaa | ||
|
83a8bb8d51 | ||
|
b06d885e76 | ||
|
a13a1dcee8 | ||
|
8965e41d06 | ||
|
28afc9bdc6 | ||
|
23f36d7e23 | ||
|
117583e473 | ||
|
bf6e66b100 | ||
|
366c09b3d7 | ||
|
ed5c8667b1 | ||
|
1695aeb28a | ||
|
521e8356fc | ||
|
45a89130d9 | ||
|
79602bfec5 | ||
|
711b28f494 | ||
|
0edb65107b | ||
|
845d770f35 | ||
|
3f1a37f216 | ||
|
165e25f9e0 | ||
|
9189d002dd | ||
|
29475fa7f8 | ||
|
e5a258f19c | ||
|
0c8ed88d8a | ||
|
9442871016 | ||
|
adb363a480 | ||
|
3d65a37c7e | ||
|
54d921c5a6 | ||
|
f61fdbaf0c | ||
|
d88643c639 | ||
|
eaaf0aafcd | ||
|
23df88d778 | ||
|
90d0ca4a2e | ||
|
f3f0c712d8 | ||
|
a5b708ba00 | ||
|
6fc90cd9d3 | ||
|
3009304352 | ||
|
5dd964e109 | ||
|
90c719148b | ||
|
831ca49331 | ||
|
2c9e24e0f6 | ||
|
a854cdaf9b | ||
|
c2904425e9 | ||
|
89ea1356c1 | ||
|
f4a5b88e64 | ||
|
906b9d34b3 | ||
|
c537a0658a | ||
|
8546ea095b | ||
|
c2e632a2b9 | ||
|
b6af6bb86a | ||
|
eee0a0dc39 | ||
|
0f5b5c2297 | ||
|
e25d090112 | ||
|
eb2ee340e4 | ||
|
79278d9475 | ||
|
dbe9c4f799 | ||
|
f6e4e278b5 | ||
|
673de33436 | ||
|
a5b6e09985 | ||
|
bb448cc293 | ||
|
ae4c5e26be | ||
|
54df2e9acd | ||
|
7f6d758ce5 | ||
|
9f64c2ca90 | ||
|
9849800dc5 | ||
|
a902fefaf1 | ||
|
0f708e76b1 | ||
|
6e6f562b0d | ||
|
12d1b31d6c | ||
|
b92bd28d97 | ||
|
e1daf46c75 | ||
|
a8e1249ba7 | ||
|
e3824687c7 | ||
|
c979ee67c0 | ||
|
6c92dd22ad | ||
|
66ac855959 | ||
|
7fab71f5f0 | ||
|
055dce10af | ||
|
292f19eab8 | ||
|
a204c9ed04 | ||
|
78f1a8e716 | ||
|
72794d92b1 | ||
|
546d0b5ddb | ||
|
a03b6e86de | ||
|
856c8b2c8d | ||
|
dbd441cc1b | ||
|
2e89e5ed23 | ||
|
0ebedbb533 | ||
|
c2a1a3c0a6 | ||
|
7d10e470d7 | ||
|
6608f0012b | ||
|
bc8f7ff133 | ||
|
7845145706 | ||
|
79afdfbbb9 | ||
|
8774081c15 | ||
|
60f36e78ba | ||
|
b198b80559 | ||
|
6bbd8b869e | ||
|
97aeb8577c | ||
|
0be6d2bec5 | ||
|
b1fef9b18c | ||
|
b5b95e27ce | ||
|
4e30ef13a7 | ||
|
d92125485a | ||
|
33a86c0cdb | ||
|
3bb653bf9c | ||
|
ebce32d891 | ||
|
b3bd9c90d1 | ||
|
5da25c5413 | ||
|
11d76e284c | ||
|
6d661203d3 | ||
|
30c2eeeeb3 | ||
|
d66e71d464 | ||
|
5c7e61def2 | ||
|
eb48bb4aef | ||
|
5eff329fc6 | ||
|
183e14725f | ||
|
0ab42d4228 | ||
|
52a5e7ca8c | ||
|
f89df98f40 | ||
|
30719202b9 | ||
|
aa18707c3e | ||
|
b77ba03ed9 | ||
|
be1d2269ce | ||
|
7bdc7ab9dc | ||
|
ae97c1f107 | ||
|
782ac25b8e | ||
|
8aa0d4a3e0 | ||
|
1f8e5b43e1 | ||
|
9813e218b0 | ||
|
403e4df624 | ||
|
aa35ea1856 | ||
|
3b6d00ba91 | ||
|
befc4bc8d8 | ||
|
6c31aad683 | ||
|
3e76270245 | ||
|
3a414660fc | ||
|
453010bb6d | ||
|
2ac8f0ea27 | ||
|
ad7288030f | ||
|
206fc918bb | ||
|
f5a60f82ee | ||
|
a1ee3b4530 | ||
|
89218fc57d | ||
|
71af4239bd | ||
|
f1d6316526 | ||
|
92d01e37a0 | ||
|
101c7052f4 | ||
|
7b7feeca42 | ||
|
cbe7ee7c03 | ||
|
8adb8463ab | ||
|
256eb29ef1 | ||
|
4ba9c8b8c1 | ||
|
98173777fe | ||
|
05aeef0dc2 | ||
|
85787ce059 | ||
|
d64aeba80f | ||
|
72cbe0667d | ||
|
af354f7528 | ||
|
1a4c95fac8 | ||
|
42d319545b | ||
|
2e6eb9068d | ||
|
13849c6230 | ||
|
6a7eb85e84 | ||
|
2a3c358533 | ||
|
28637d0db7 | ||
|
b14b3ffcec | ||
|
bbb4a0ef25 | ||
|
9db74715f2 | ||
|
5b064f474d | ||
|
bffa6dc2ce | ||
|
341dea5907 | ||
|
676530307b | ||
|
09425bbe43 | ||
|
1805666e9d | ||
|
40cd2081da | ||
|
66514127f3 | ||
|
fdc3bf0484 | ||
|
f97d2527ff | ||
|
7199d953a7 | ||
|
15653d4669 | ||
|
d64a22d8db | ||
|
2e9f8dc831 | ||
|
e8bfc5bb69 | ||
|
9e5b16ed8a | ||
|
d72da951c9 | ||
|
90101613e7 | ||
|
6caac22df1 | ||
|
e9987b1adf | ||
|
383ffb772e | ||
|
6379307baa | ||
|
d620639f7d | ||
|
a5cee9bac2 | ||
|
131fd3669f | ||
|
56f810d182 | ||
|
46f637e0be | ||
|
095d0e37d6 | ||
|
7e798afd11 | ||
|
669a9aed9e | ||
|
c869129c71 | ||
|
b8523a3c9b | ||
|
400730fe3b | ||
|
6c5204b91c | ||
|
910d99cb74 | ||
|
031bbd6230 | ||
|
05e8c204e9 | ||
|
2c91ea1621 | ||
|
5cdf428f55 | ||
|
22b579bd14 | ||
|
14d9fa247a | ||
|
cfe99998fc | ||
|
9fce3805fc | ||
|
fd243ca1c1 | ||
|
c90df53081 | ||
|
e4659c2475 | ||
|
caae51c399 | ||
|
fb390fa9cc | ||
|
4a0b166998 | ||
|
b9ce5b034b | ||
|
a90558c07e | ||
|
56b0f850bc | ||
|
51a179dc68 | ||
|
8a8d5f71b1 | ||
|
4b69a456cc | ||
|
e12d0be1b2 | ||
|
002326ceb1 | ||
|
758e3d85e6 | ||
|
f5bd77a7f8 | ||
|
519c79a57e | ||
|
0aa40480f9 | ||
|
36dd6afb49 | ||
|
3015968ee4 | ||
|
5bb6c88b41 | ||
|
f875a56620 | ||
|
70a75e5e3f | ||
|
735fb58f6c | ||
|
e8f48b77f9 | ||
|
e363c44bb0 | ||
|
ec1c3e0159 | ||
|
2a1a18023a | ||
|
ff738988d3 | ||
|
fc4657d529 | ||
|
bd961b7866 | ||
|
3df77d6f20 | ||
|
b59c768cdd | ||
|
a37a3cb6e8 | ||
|
823581816a | ||
|
3ea0d74954 | ||
|
b3c860b892 | ||
|
958e701bf4 | ||
|
28e75da77f | ||
|
a812869c0c | ||
|
96fb01b9be | ||
|
6fa0bb7d4f | ||
|
0a040cbc96 | ||
|
96d4f1fe85 | ||
|
30f2d9f325 | ||
|
5be975b4f2 | ||
|
22c370a068 | ||
|
c3e032e08e | ||
|
2c3bf17dfb | ||
|
a926b68838 | ||
|
6dc1e319d1 | ||
|
0db71d552a | ||
|
f52ed1d19e | ||
|
ffcb01ad80 | ||
|
66d2241703 | ||
|
11814bfec3 | ||
|
bbc1c010ed | ||
|
eeeae810ae | ||
|
3c6f52a349 | ||
|
40466b2be2 | ||
|
6a4a742feb | ||
|
f8b243b66e | ||
|
fa9a3abe49 | ||
|
78b967198b | ||
|
68574be2cf | ||
|
2ab37aa4a4 | ||
|
5ab9fede6c | ||
|
a81ba4ba53 | ||
|
b65728032d | ||
|
e747377775 | ||
|
d6f88918de | ||
|
21cd920b61 | ||
|
bb13853929 | ||
|
3d1720c3f3 | ||
|
0614ffb65c | ||
|
85b47fc3ac | ||
|
1a99280227 | ||
|
e8133eb9a6 | ||
|
4ccaff8f25 | ||
|
56dc186e68 | ||
|
10b2efe81c |
16
.github/ISSUE_TEMPLATE.md
vendored
16
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,12 +1,16 @@
|
||||
### Problem Description
|
||||
|
||||
Describe the problem you are having, what you expect to happen
|
||||
instead, and how to reproduce the problem.
|
||||
Describe the problem you are having and what you expect to happen
|
||||
instead.
|
||||
|
||||
### Steps to Reproduce
|
||||
|
||||
Give detailed step-by-step instructions on how to reproduce the problem.
|
||||
|
||||
### Configuration File
|
||||
|
||||
Please include the smallest configuration file that reproduces the
|
||||
problem you are experiencing:
|
||||
Please include the smallest _full_ configuration file that reproduces
|
||||
the problem you are experiencing:
|
||||
|
||||
```haskell
|
||||
module Main (main) where
|
||||
@@ -21,4 +25,6 @@ main = xmonad def
|
||||
|
||||
- [ ] I've read [CONTRIBUTING.md](https://github.com/xmonad/xmonad/blob/master/CONTRIBUTING.md)
|
||||
|
||||
- [ ] I tested my configuration with [xmonad-testing](https://github.com/xmonad/xmonad-testing)
|
||||
- I tested my configuration
|
||||
- [ ] With `xmonad` version XXX (commit XXX if using git)
|
||||
- [ ] With `xmonad-contrib` version XXX (commit XXX if using git)
|
||||
|
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -9,6 +9,7 @@ behind them.
|
||||
|
||||
- [ ] I've confirmed these changes don't belong in xmonad-contrib instead
|
||||
|
||||
- [ ] I tested my changes with [xmonad-testing](https://github.com/xmonad/xmonad-testing)
|
||||
- [ ] I've considered how to best test these changes (property, unit,
|
||||
manually, ...) and concluded: XXX
|
||||
|
||||
- [ ] I updated the `CHANGES.md` file
|
||||
|
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"
|
33
.github/workflows/generatemanpage.yml
vendored
Normal file
33
.github/workflows/generatemanpage.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
name: Generate manpage
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Clone project
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
set -ex
|
||||
sudo apt install -y pandoc
|
||||
|
||||
- name: Generate manpage
|
||||
run: |
|
||||
set -ex
|
||||
for d in /opt/ghc/*/bin; do PATH="$d:$PATH"; break; done
|
||||
make -B -C man
|
||||
|
||||
- name: Commit/push if changed
|
||||
run: |
|
||||
set -ex
|
||||
git config user.name 'github-actions[bot]'
|
||||
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
|
||||
git diff --quiet --exit-code && exit
|
||||
git commit -a -m 'man: Update'
|
||||
git push
|
134
.github/workflows/haskell-ci-hackage.patch
vendored
Normal file
134
.github/workflows/haskell-ci-hackage.patch
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
Piggy-back on the haskell-ci workflow for automatic releases to Hackage.
|
||||
|
||||
This extends the workflow with two additional triggers:
|
||||
|
||||
* When the Haskell-CI workflow is triggered manually with a non-empty version
|
||||
input (matching the version in the cabal file), a candidate release is
|
||||
uploaded to Hackage and docs are submitted for it as Hackage can't build
|
||||
them itself (https://github.com/haskell/hackage-server/issues/925).
|
||||
|
||||
Note that promoting the candidate on Hackage discards the uploaded docs
|
||||
(https://github.com/haskell/hackage-server/issues/70). Don't do that.
|
||||
|
||||
* When a release is created on GitHub, a final release is uploaded to Hackage
|
||||
and docs are submitted for it.
|
||||
|
||||
The automation uses a special Hackage user: https://hackage.haskell.org/user/xmonad
|
||||
and each repo (X11, xmonad, xmonad-contrib) has its own HACKAGE_API_KEY token
|
||||
set in GitHub repository secrets.
|
||||
|
||||
--- .github/workflows/haskell-ci.yml.orig
|
||||
+++ .github/workflows/haskell-ci.yml
|
||||
@@ -14,8 +14,15 @@
|
||||
#
|
||||
name: Haskell-CI
|
||||
on:
|
||||
- - push
|
||||
- - pull_request
|
||||
+ push:
|
||||
+ pull_request:
|
||||
+ release:
|
||||
+ types:
|
||||
+ - published
|
||||
+ workflow_dispatch:
|
||||
+ inputs:
|
||||
+ version:
|
||||
+ description: candidate version (must match version in cabal file)
|
||||
jobs:
|
||||
linux:
|
||||
name: Haskell-CI - Linux - ${{ matrix.compiler }}
|
||||
@@ -33,6 +40,7 @@
|
||||
compilerVersion: 9.8.1
|
||||
setup-method: ghcup
|
||||
allow-failure: false
|
||||
+ upload: true
|
||||
- compiler: ghc-9.6.4
|
||||
compilerKind: ghc
|
||||
compilerVersion: 9.6.4
|
||||
@@ -257,6 +265,10 @@
|
||||
- name: haddock
|
||||
run: |
|
||||
$CABAL v2-haddock --disable-documentation $ARG_COMPILER --with-haddock $HADDOCK $ARG_TESTS $ARG_BENCH all
|
||||
+ - 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
|
||||
@@ -267,3 +279,75 @@
|
||||
with:
|
||||
key: ${{ runner.os }}-${{ matrix.compiler }}-${{ github.sha }}
|
||||
path: ~/.cabal/store
|
||||
+ - name: upload artifacts (sdist)
|
||||
+ if: matrix.upload
|
||||
+ uses: actions/upload-artifact@v3
|
||||
+ with:
|
||||
+ path: ${{ github.workspace }}/sdist/*.tar.gz
|
||||
+ - name: upload artifacts (haddock)
|
||||
+ if: matrix.upload
|
||||
+ uses: actions/upload-artifact@v3
|
||||
+ with:
|
||||
+ path: ${{ github.workspace }}/haddock/*-docs.tar.gz
|
||||
+ - name: hackage upload (candidate)
|
||||
+ if: matrix.upload && github.event_name == 'workflow_dispatch' && github.event.inputs.version != ''
|
||||
+ shell: bash
|
||||
+ run: |
|
||||
+ set -ex
|
||||
+ PACKAGE_VERSION="${PACKAGE_VERSION#v}"
|
||||
+ res=$(
|
||||
+ curl \
|
||||
+ --silent --show-error --output /dev/stderr --write-out '%{http_code}' \
|
||||
+ --header "Accept: text/plain" \
|
||||
+ --header "Authorization: X-ApiKey $HACKAGE_API_KEY" \
|
||||
+ --form package=@"${GITHUB_WORKSPACE}/sdist/${PACKAGE_NAME}-${PACKAGE_VERSION}.tar.gz" \
|
||||
+ https://hackage.haskell.org/packages/candidates/
|
||||
+ )
|
||||
+ [[ $res == 2?? ]] # TODO: --fail-with-body once curl 7.76.0 is available
|
||||
+ res=$(
|
||||
+ curl \
|
||||
+ --silent --show-error --output /dev/stderr --write-out '%{http_code}' \
|
||||
+ -X PUT \
|
||||
+ --header "Accept: text/plain" \
|
||||
+ --header "Authorization: X-ApiKey $HACKAGE_API_KEY" \
|
||||
+ --header "Content-Type: application/x-tar" \
|
||||
+ --header "Content-Encoding: gzip" \
|
||||
+ --data-binary @"${GITHUB_WORKSPACE}/haddock/${PACKAGE_NAME}-${PACKAGE_VERSION}-docs.tar.gz" \
|
||||
+ https://hackage.haskell.org/package/${PACKAGE_NAME}-${PACKAGE_VERSION}/candidate/docs
|
||||
+ )
|
||||
+ [[ $res == 2?? ]]
|
||||
+ env:
|
||||
+ HACKAGE_API_KEY: ${{ secrets.HACKAGE_API_KEY }}
|
||||
+ PACKAGE_NAME: ${{ github.event.repository.name }}
|
||||
+ PACKAGE_VERSION: ${{ github.event.inputs.version }}
|
||||
+ - name: hackage upload (release)
|
||||
+ if: matrix.upload && github.event_name == 'release'
|
||||
+ shell: bash
|
||||
+ run: |
|
||||
+ set -ex
|
||||
+ PACKAGE_VERSION="${PACKAGE_VERSION#v}"
|
||||
+ res=$(
|
||||
+ curl \
|
||||
+ --silent --show-error --output /dev/stderr --write-out '%{http_code}' \
|
||||
+ --header "Accept: text/plain" \
|
||||
+ --header "Authorization: X-ApiKey $HACKAGE_API_KEY" \
|
||||
+ --form package=@"${GITHUB_WORKSPACE}/sdist/${PACKAGE_NAME}-${PACKAGE_VERSION}.tar.gz" \
|
||||
+ https://hackage.haskell.org/packages/
|
||||
+ )
|
||||
+ [[ $res == 2?? ]] # TODO: --fail-with-body once curl 7.76.0 is available
|
||||
+ res=$(
|
||||
+ curl \
|
||||
+ --silent --show-error --output /dev/stderr --write-out '%{http_code}' \
|
||||
+ -X PUT \
|
||||
+ --header "Accept: text/plain" \
|
||||
+ --header "Authorization: X-ApiKey $HACKAGE_API_KEY" \
|
||||
+ --header "Content-Type: application/x-tar" \
|
||||
+ --header "Content-Encoding: gzip" \
|
||||
+ --data-binary @"${GITHUB_WORKSPACE}/haddock/${PACKAGE_NAME}-${PACKAGE_VERSION}-docs.tar.gz" \
|
||||
+ https://hackage.haskell.org/package/${PACKAGE_NAME}-${PACKAGE_VERSION}/docs
|
||||
+ )
|
||||
+ [[ $res == 2?? ]]
|
||||
+ env:
|
||||
+ HACKAGE_API_KEY: ${{ secrets.HACKAGE_API_KEY }}
|
||||
+ PACKAGE_NAME: ${{ github.event.repository.name }}
|
||||
+ PACKAGE_VERSION: ${{ github.event.release.tag_name }}
|
335
.github/workflows/haskell-ci.yml
vendored
Normal file
335
.github/workflows/haskell-ci.yml
vendored
Normal file
@@ -0,0 +1,335 @@
|
||||
# This GitHub workflow config has been generated by a script via
|
||||
#
|
||||
# haskell-ci 'github' 'cabal.project'
|
||||
#
|
||||
# To regenerate the script (for example after adjusting tested-with) run
|
||||
#
|
||||
# haskell-ci regenerate
|
||||
#
|
||||
# For more information, see https://github.com/haskell-CI/haskell-ci
|
||||
#
|
||||
# version: 0.17.20240109
|
||||
#
|
||||
# REGENDATA ("0.17.20240109",["github","cabal.project"])
|
||||
#
|
||||
name: Haskell-CI
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: candidate version (must match version in cabal file)
|
||||
jobs:
|
||||
linux:
|
||||
name: Haskell-CI - Linux - ${{ matrix.compiler }}
|
||||
runs-on: ubuntu-20.04
|
||||
timeout-minutes:
|
||||
60
|
||||
container:
|
||||
image: buildpack-deps:bionic
|
||||
continue-on-error: ${{ matrix.allow-failure }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- compiler: ghc-9.8.1
|
||||
compilerKind: ghc
|
||||
compilerVersion: 9.8.1
|
||||
setup-method: ghcup
|
||||
allow-failure: false
|
||||
upload: true
|
||||
- compiler: ghc-9.6.4
|
||||
compilerKind: ghc
|
||||
compilerVersion: 9.6.4
|
||||
setup-method: ghcup
|
||||
allow-failure: false
|
||||
- compiler: ghc-9.4.8
|
||||
compilerKind: ghc
|
||||
compilerVersion: 9.4.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
|
||||
compilerKind: ghc
|
||||
compilerVersion: 9.0.2
|
||||
setup-method: ghcup
|
||||
allow-failure: false
|
||||
- compiler: ghc-8.10.7
|
||||
compilerKind: ghc
|
||||
compilerVersion: 8.10.7
|
||||
setup-method: ghcup
|
||||
allow-failure: false
|
||||
- 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
|
||||
fail-fast: false
|
||||
steps:
|
||||
- name: apt
|
||||
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.20.0/x86_64-linux-ghcup-0.1.20.0 > "$HOME/.ghcup/bin/ghcup"
|
||||
chmod a+x "$HOME/.ghcup/bin/ghcup"
|
||||
"$HOME/.ghcup/bin/ghcup" install ghc "$HCVER" || (cat "$HOME"/.ghcup/logs/*.* && false)
|
||||
"$HOME/.ghcup/bin/ghcup" install cabal 3.10.2.0 || (cat "$HOME"/.ghcup/logs/*.* && false)
|
||||
apt-get update
|
||||
apt-get install -y libx11-dev libxext-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 libxinerama-dev libxrandr-dev libxss-dev
|
||||
mkdir -p "$HOME/.ghcup/bin"
|
||||
curl -sL https://downloads.haskell.org/ghcup/0.1.20.0/x86_64-linux-ghcup-0.1.20.0 > "$HOME/.ghcup/bin/ghcup"
|
||||
chmod a+x "$HOME/.ghcup/bin/ghcup"
|
||||
"$HOME/.ghcup/bin/ghcup" install cabal 3.10.2.0 || (cat "$HOME"/.ghcup/logs/*.* && false)
|
||||
fi
|
||||
env:
|
||||
HCKIND: ${{ matrix.compilerKind }}
|
||||
HCNAME: ${{ matrix.compiler }}
|
||||
HCVER: ${{ matrix.compilerVersion }}
|
||||
- name: Set PATH and environment variables
|
||||
run: |
|
||||
echo "$HOME/.cabal/bin" >> $GITHUB_PATH
|
||||
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/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"
|
||||
echo "CABAL=$HOME/.ghcup/bin/cabal-3.10.2.0 -vnormal+nowrap" >> "$GITHUB_ENV"
|
||||
else
|
||||
HC=$HCDIR/bin/$HCKIND
|
||||
echo "HC=$HC" >> "$GITHUB_ENV"
|
||||
echo "HCPKG=$HCDIR/bin/$HCKIND-pkg" >> "$GITHUB_ENV"
|
||||
echo "HADDOCK=$HCDIR/bin/haddock" >> "$GITHUB_ENV"
|
||||
echo "CABAL=$HOME/.ghcup/bin/cabal-3.10.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 }}
|
||||
HCVER: ${{ matrix.compilerVersion }}
|
||||
- name: env
|
||||
run: |
|
||||
env
|
||||
- name: write cabal config
|
||||
run: |
|
||||
mkdir -p $CABAL_DIR
|
||||
cat >> $CABAL_CONFIG <<EOF
|
||||
remote-build-reporting: anonymous
|
||||
write-ghc-environment-files: never
|
||||
remote-repo-cache: $CABAL_DIR/packages
|
||||
logs-dir: $CABAL_DIR/logs
|
||||
world-file: $CABAL_DIR/world
|
||||
extra-prog-path: $CABAL_DIR/bin
|
||||
symlink-bindir: $CABAL_DIR/bin
|
||||
installdir: $CABAL_DIR/bin
|
||||
build-summary: $CABAL_DIR/logs/build.log
|
||||
store-dir: $CABAL_DIR/store
|
||||
install-dirs user
|
||||
prefix: $CABAL_DIR
|
||||
repository hackage.haskell.org
|
||||
url: http://hackage.haskell.org/
|
||||
EOF
|
||||
cat >> $CABAL_CONFIG <<EOF
|
||||
program-default-options
|
||||
ghc-options: $GHCJOBS +RTS -M3G -RTS
|
||||
EOF
|
||||
cat $CABAL_CONFIG
|
||||
- name: versions
|
||||
run: |
|
||||
$HC --version || true
|
||||
$HC --print-project-git-commit-id || true
|
||||
$CABAL --version || true
|
||||
- name: update cabal index
|
||||
run: |
|
||||
$CABAL v2-update -v
|
||||
- name: install cabal-plan
|
||||
run: |
|
||||
mkdir -p $HOME/.cabal/bin
|
||||
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: checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
path: source
|
||||
- name: initial cabal.project for sdist
|
||||
run: |
|
||||
touch cabal.project
|
||||
echo "packages: $GITHUB_WORKSPACE/source/." >> cabal.project
|
||||
cat cabal.project
|
||||
- name: sdist
|
||||
run: |
|
||||
mkdir -p sdist
|
||||
$CABAL sdist all --output-dir $GITHUB_WORKSPACE/sdist
|
||||
- name: unpack
|
||||
run: |
|
||||
mkdir -p unpacked
|
||||
find sdist -maxdepth 1 -type f -name '*.tar.gz' -exec tar -C $GITHUB_WORKSPACE/unpacked -xzvf {} \;
|
||||
- name: generate cabal.project
|
||||
run: |
|
||||
PKGDIR_xmonad="$(find "$GITHUB_WORKSPACE/unpacked" -maxdepth 1 -type d -regex '.*/xmonad-[0-9.]*')"
|
||||
echo "PKGDIR_xmonad=${PKGDIR_xmonad}" >> "$GITHUB_ENV"
|
||||
rm -f cabal.project cabal.project.local
|
||||
touch cabal.project
|
||||
touch cabal.project.local
|
||||
echo "packages: ${PKGDIR_xmonad}" >> cabal.project
|
||||
echo "package xmonad" >> cabal.project
|
||||
echo " ghc-options: -Werror=missing-methods" >> cabal.project
|
||||
cat >> cabal.project <<EOF
|
||||
optimization: False
|
||||
|
||||
package xmonad
|
||||
flags: +pedantic
|
||||
EOF
|
||||
$HCPKG list --simple-output --names-only | perl -ne 'for (split /\s+/) { print "constraints: $_ installed\n" unless /^(xmonad)$/; }' >> 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: restore cache
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
key: ${{ runner.os }}-${{ matrix.compiler }}-${{ github.sha }}
|
||||
path: ~/.cabal/store
|
||||
restore-keys: ${{ runner.os }}-${{ matrix.compiler }}-
|
||||
- name: install dependencies
|
||||
run: |
|
||||
$CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks --dependencies-only -j2 all
|
||||
$CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH --dependencies-only -j2 all
|
||||
- name: build w/o tests
|
||||
run: |
|
||||
$CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks all
|
||||
- name: build
|
||||
run: |
|
||||
$CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH all --write-ghc-environment-files=always
|
||||
- name: tests
|
||||
run: |
|
||||
$CABAL v2-test $ARG_COMPILER $ARG_TESTS $ARG_BENCH all --test-show-details=direct
|
||||
- name: cabal check
|
||||
run: |
|
||||
cd ${PKGDIR_xmonad} || 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: save cache
|
||||
uses: actions/cache/save@v3
|
||||
if: always()
|
||||
with:
|
||||
key: ${{ runner.os }}-${{ matrix.compiler }}-${{ github.sha }}
|
||||
path: ~/.cabal/store
|
||||
- name: upload artifacts (sdist)
|
||||
if: matrix.upload
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: ${{ github.workspace }}/sdist/*.tar.gz
|
||||
- name: upload artifacts (haddock)
|
||||
if: matrix.upload
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: ${{ github.workspace }}/haddock/*-docs.tar.gz
|
||||
- name: hackage upload (candidate)
|
||||
if: matrix.upload && github.event_name == 'workflow_dispatch' && github.event.inputs.version != ''
|
||||
shell: bash
|
||||
run: |
|
||||
set -ex
|
||||
PACKAGE_VERSION="${PACKAGE_VERSION#v}"
|
||||
res=$(
|
||||
curl \
|
||||
--silent --show-error --output /dev/stderr --write-out '%{http_code}' \
|
||||
--header "Accept: text/plain" \
|
||||
--header "Authorization: X-ApiKey $HACKAGE_API_KEY" \
|
||||
--form package=@"${GITHUB_WORKSPACE}/sdist/${PACKAGE_NAME}-${PACKAGE_VERSION}.tar.gz" \
|
||||
https://hackage.haskell.org/packages/candidates/
|
||||
)
|
||||
[[ $res == 2?? ]] # TODO: --fail-with-body once curl 7.76.0 is available
|
||||
res=$(
|
||||
curl \
|
||||
--silent --show-error --output /dev/stderr --write-out '%{http_code}' \
|
||||
-X PUT \
|
||||
--header "Accept: text/plain" \
|
||||
--header "Authorization: X-ApiKey $HACKAGE_API_KEY" \
|
||||
--header "Content-Type: application/x-tar" \
|
||||
--header "Content-Encoding: gzip" \
|
||||
--data-binary @"${GITHUB_WORKSPACE}/haddock/${PACKAGE_NAME}-${PACKAGE_VERSION}-docs.tar.gz" \
|
||||
https://hackage.haskell.org/package/${PACKAGE_NAME}-${PACKAGE_VERSION}/candidate/docs
|
||||
)
|
||||
[[ $res == 2?? ]]
|
||||
env:
|
||||
HACKAGE_API_KEY: ${{ secrets.HACKAGE_API_KEY }}
|
||||
PACKAGE_NAME: ${{ github.event.repository.name }}
|
||||
PACKAGE_VERSION: ${{ github.event.inputs.version }}
|
||||
- name: hackage upload (release)
|
||||
if: matrix.upload && github.event_name == 'release'
|
||||
shell: bash
|
||||
run: |
|
||||
set -ex
|
||||
PACKAGE_VERSION="${PACKAGE_VERSION#v}"
|
||||
res=$(
|
||||
curl \
|
||||
--silent --show-error --output /dev/stderr --write-out '%{http_code}' \
|
||||
--header "Accept: text/plain" \
|
||||
--header "Authorization: X-ApiKey $HACKAGE_API_KEY" \
|
||||
--form package=@"${GITHUB_WORKSPACE}/sdist/${PACKAGE_NAME}-${PACKAGE_VERSION}.tar.gz" \
|
||||
https://hackage.haskell.org/packages/
|
||||
)
|
||||
[[ $res == 2?? ]] # TODO: --fail-with-body once curl 7.76.0 is available
|
||||
res=$(
|
||||
curl \
|
||||
--silent --show-error --output /dev/stderr --write-out '%{http_code}' \
|
||||
-X PUT \
|
||||
--header "Accept: text/plain" \
|
||||
--header "Authorization: X-ApiKey $HACKAGE_API_KEY" \
|
||||
--header "Content-Type: application/x-tar" \
|
||||
--header "Content-Encoding: gzip" \
|
||||
--data-binary @"${GITHUB_WORKSPACE}/haddock/${PACKAGE_NAME}-${PACKAGE_VERSION}-docs.tar.gz" \
|
||||
https://hackage.haskell.org/package/${PACKAGE_NAME}-${PACKAGE_VERSION}/docs
|
||||
)
|
||||
[[ $res == 2?? ]]
|
||||
env:
|
||||
HACKAGE_API_KEY: ${{ secrets.HACKAGE_API_KEY }}
|
||||
PACKAGE_NAME: ${{ github.event.repository.name }}
|
||||
PACKAGE_VERSION: ${{ github.event.release.tag_name }}
|
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: '.'
|
||||
fail-on: status
|
25
.github/workflows/nix.yml
vendored
Normal file
25
.github/workflows/nix.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: Nix
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-20.04 # FIXME
|
||||
name: Nix Flake - Linux
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Install Nix
|
||||
uses: cachix/install-nix-action@v25
|
||||
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 }}
|
||||
- name: Clone project
|
||||
uses: actions/checkout@v4
|
||||
- name: Build
|
||||
run: nix build --print-build-logs
|
49
.github/workflows/packdeps.yml
vendored
Normal file
49
.github/workflows/packdeps.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
name: Packdeps
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
# Run every Saturday
|
||||
- cron: '0 3 * * 6'
|
||||
|
||||
jobs:
|
||||
packdeps:
|
||||
name: Packdeps
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Clone project
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Haskell
|
||||
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
|
||||
cd # go somewhere without a cabal.project
|
||||
cabal install packdeps
|
||||
- name: Check package bounds (all)
|
||||
continue-on-error: true
|
||||
run: |
|
||||
set -ex
|
||||
packdeps \
|
||||
--exclude X11 \
|
||||
*.cabal
|
||||
- name: Check package bounds (preferred)
|
||||
run: |
|
||||
set -ex
|
||||
packdeps \
|
||||
--preferred \
|
||||
--exclude X11 \
|
||||
*.cabal
|
||||
|
||||
workflow-keepalive:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Re-enable workflow
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh api -X PUT repos/${{ github.repository }}/actions/workflows/packdeps.yml/enable
|
77
.github/workflows/stack.yml
vendored
Normal file
77
.github/workflows/stack.yml
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
name: Stack
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Stack CI - Linux - ${{ matrix.resolver }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- resolver: lts-14 # GHC 8.6
|
||||
- resolver: lts-16 # GHC 8.8
|
||||
- resolver: lts-18 # GHC 8.10
|
||||
- resolver: lts-19 # GHC 9.0
|
||||
- resolver: lts-20 # GHC 9.2
|
||||
- resolver: lts-21 # GHC 9.4
|
||||
|
||||
steps:
|
||||
- name: Clone project
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install C dependencies
|
||||
run: |
|
||||
set -ex
|
||||
sudo apt update -y
|
||||
sudo apt install -y \
|
||||
libx11-dev \
|
||||
libxext-dev \
|
||||
libxinerama-dev \
|
||||
libxrandr-dev \
|
||||
libxss-dev \
|
||||
#
|
||||
|
||||
- name: Refresh caches once a month
|
||||
id: cache-date
|
||||
# GHA writes caches on the first miss and then never updates them again;
|
||||
# force updating the cache at least once a month. Additionally, the
|
||||
# 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: |
|
||||
date +date=1-%Y-%m >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache Haskell package metadata
|
||||
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@v4
|
||||
with:
|
||||
path: |
|
||||
~/.stack/*
|
||||
!~/.stack/pantry
|
||||
!~/.stack/programs
|
||||
key: stack-${{ runner.os }}-${{ matrix.resolver }}-${{ steps.cache-date.outputs.date }}-${{ hashFiles('stack.yaml') }}-${{ hashFiles('*.cabal') }}
|
||||
restore-keys: |
|
||||
stack-${{ runner.os }}-${{ matrix.resolver }}-${{ steps.cache-date.outputs.date }}-${{ hashFiles('stack.yaml') }}-
|
||||
stack-${{ runner.os }}-${{ matrix.resolver }}-${{ steps.cache-date.outputs.date }}-
|
||||
|
||||
- name: Update hackage index
|
||||
# always update index to prevent the shared ~/.stack/pantry cache from being empty
|
||||
run: |
|
||||
set -ex
|
||||
stack update
|
||||
|
||||
- name: Build and test
|
||||
run: |
|
||||
set -ex
|
||||
stack test \
|
||||
--fast --no-terminal \
|
||||
--resolver=${{ matrix.resolver }} --system-ghc \
|
||||
--flag=xmonad:pedantic
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -27,3 +27,6 @@ tags
|
||||
/cabal.sandbox.config
|
||||
/dist-newstyle/
|
||||
/dist/
|
||||
|
||||
# nix artifacts
|
||||
result
|
||||
|
2
.hlint.yaml
Normal file
2
.hlint.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
# Ignore these warnings.
|
||||
- ignore: {name: "Use camelCase"}
|
38
.mailmap
Normal file
38
.mailmap
Normal file
@@ -0,0 +1,38 @@
|
||||
Adam Plaice <plaice.adam+github@gmail.com>
|
||||
Brandon S Allbery KF8NH <allbery.b@gmail.com>
|
||||
Brent Yorgey <byorgey@gmail.com> <byorgey@cis.upenn.edu>
|
||||
Conrad Irwin <conrad.irwin@gmail.com>
|
||||
Daniel Neri <daniel.neri@sigicom.com> <daniel.neri@sigicom.se>
|
||||
Daniel Schoepe <daniel.schoepe@gmail.com> <asgaroth_@gmx.de>
|
||||
Daniel Wagner <me@dmwit.com> <daniel@wagner-home.com>
|
||||
David Glasser <glasser@mit.edu>
|
||||
Deven Lahoti <deven.lahoti@gmail.com>
|
||||
Devin Mullins <devin.mullins@gmail.com> <me@twifkak.com>
|
||||
Don Stewart <dons00@gmail.com> <dons@cse.unsw.edu.au>
|
||||
Don Stewart <dons00@gmail.com> <dons@galois.com>
|
||||
Felix Springer <felixspringer149@gmail.com> <39434424+jumper149@users.noreply.github.com>
|
||||
Gwern Branwen <gwern@gwern.net> <gwern0@gmail.com>
|
||||
Lukas Mai <l.mai@web.de>
|
||||
Marshall Lochbaum <mwlochbaum@gmail.com>
|
||||
Michael G. Sloan <mgsloan@gmail.com>
|
||||
Neil Mitchell <ndmitchell@gmail.com> <http://www.cs.york.ac.uk/~ndm/>
|
||||
Neil Mitchell <ndmitchell@gmail.com> Neil Mitchell <unknown>
|
||||
Nick Burlett <nickburlett@mac.com>
|
||||
Nicolas Pouillard <nicolas.pouillard@gmail.com>
|
||||
Nik Nyby <nnyby@columbia.edu>
|
||||
Peter J. Jones <pjones@devalot.com>
|
||||
Peter J. Jones <pjones@devalot.com> <pjones@pmade.com>
|
||||
Robert Marlow <bobstopper@bobturf.org>
|
||||
Robert Marlow <bobstopper@bobturf.org> <robreim@bobturf.org>
|
||||
Sam Hughes <hughes@rpi.edu>
|
||||
Shae Erisson <shae@ScannedInAvian.com>
|
||||
Sibi Prabakaran <sibi@psibi.in>
|
||||
Sibi Prabakaran <sibi@psibi.in> <psibi2000@gmail.com>
|
||||
Spencer Janssen <spencerjanssen@gmail.com> <sjanssen@cse.unl.edu>
|
||||
Timothy Hobbs <tim.thelion@gmail.com>
|
||||
Tomas Janousek <tomi@nomi.cz>
|
||||
Valery V. Vorotyntsev <valery.vv@gmail.com>
|
||||
Vanessa McHale <vamchale@gmail.com> <vanessa.mchale@reconfigure.io>
|
||||
Wirt Wolff <wirtwolff@gmail.com>
|
||||
|
||||
slotThe <soliditsallgood@mailbox.org> <50166980+slotThe@users.noreply.github.com>
|
135
.travis.yml
135
.travis.yml
@@ -1,135 +0,0 @@
|
||||
# This Travis job script has been generated by a script via
|
||||
#
|
||||
# runghc make_travis_yml_2.hs '-o' '.travis.yml' 'xmonad.cabal' 'libxrandr-dev'
|
||||
#
|
||||
# For more information, see https://github.com/haskell-CI/haskell-ci
|
||||
#
|
||||
language: c
|
||||
sudo: false
|
||||
|
||||
git:
|
||||
submodules: false # whether to recursively clone submodules
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.cabal/packages
|
||||
- $HOME/.cabal/store
|
||||
|
||||
before_cache:
|
||||
- rm -fv $HOME/.cabal/packages/hackage.haskell.org/build-reports.log
|
||||
# remove files that are regenerated by 'cabal update'
|
||||
- rm -fv $HOME/.cabal/packages/hackage.haskell.org/00-index.*
|
||||
- rm -fv $HOME/.cabal/packages/hackage.haskell.org/*.json
|
||||
- rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.cache
|
||||
- rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.tar
|
||||
- rm -fv $HOME/.cabal/packages/hackage.haskell.org/01-index.tar.idx
|
||||
|
||||
- rm -rfv $HOME/.cabal/packages/head.hackage
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- compiler: "ghc-8.6.1"
|
||||
env: GHCHEAD=true
|
||||
addons: {apt: {packages: [ghc-ppa-tools,cabal-install-head,ghc-8.6.1,libxrandr-dev], sources: [hvr-ghc]}}
|
||||
- compiler: "ghc-8.4.3"
|
||||
# env: TEST=--disable-tests BENCH=--disable-benchmarks
|
||||
addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-8.4.3,libxrandr-dev], sources: [hvr-ghc]}}
|
||||
- compiler: "ghc-8.2.2"
|
||||
# env: TEST=--disable-tests BENCH=--disable-benchmarks
|
||||
addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-8.2.2,libxrandr-dev], sources: [hvr-ghc]}}
|
||||
- compiler: "ghc-8.0.2"
|
||||
# env: TEST=--disable-tests BENCH=--disable-benchmarks
|
||||
addons: {apt: {packages: [ghc-ppa-tools,cabal-install-2.2,ghc-8.0.2,libxrandr-dev], sources: [hvr-ghc]}}
|
||||
|
||||
allow_failures:
|
||||
- compiler: "ghc-8.6.1"
|
||||
|
||||
before_install:
|
||||
- HC=${CC}
|
||||
- HCPKG=${HC/ghc/ghc-pkg}
|
||||
- unset CC
|
||||
- ROOTDIR=$(pwd)
|
||||
- mkdir -p $HOME/.local/bin
|
||||
- "PATH=/opt/ghc/bin:/opt/ghc-ppa-tools/bin:$HOME/local/bin:$PATH"
|
||||
- HCNUMVER=$(( $(${HC} --numeric-version|sed -E 's/([0-9]+)\.([0-9]+)\.([0-9]+).*/\1 * 10000 + \2 * 100 + \3/') ))
|
||||
- echo $HCNUMVER
|
||||
|
||||
install:
|
||||
- cabal --version
|
||||
- echo "$(${HC} --version) [$(${HC} --print-project-git-commit-id 2> /dev/null || echo '?')]"
|
||||
- BENCH=${BENCH---enable-benchmarks}
|
||||
- TEST=${TEST---enable-tests}
|
||||
- HADDOCK=${HADDOCK-true}
|
||||
- UNCONSTRAINED=${UNCONSTRAINED-true}
|
||||
- NOINSTALLEDCONSTRAINTS=${NOINSTALLEDCONSTRAINTS-false}
|
||||
- GHCHEAD=${GHCHEAD-false}
|
||||
- travis_retry cabal update -v
|
||||
- "sed -i.bak 's/^jobs:/-- jobs:/' ${HOME}/.cabal/config"
|
||||
- rm -fv cabal.project cabal.project.local
|
||||
# Overlay Hackage Package Index for GHC HEAD: https://github.com/hvr/head.hackage
|
||||
- |
|
||||
if $GHCHEAD; then
|
||||
sed -i 's/-- allow-newer: .*/allow-newer: *:base/' ${HOME}/.cabal/config
|
||||
for pkg in $($HCPKG list --simple-output); do pkg=$(echo $pkg | sed 's/-[^-]*$//'); sed -i "s/allow-newer: /allow-newer: *:$pkg, /" ${HOME}/.cabal/config; done
|
||||
|
||||
echo 'repository head.hackage' >> ${HOME}/.cabal/config
|
||||
echo ' url: http://head.hackage.haskell.org/' >> ${HOME}/.cabal/config
|
||||
echo ' secure: True' >> ${HOME}/.cabal/config
|
||||
echo ' root-keys: 07c59cb65787dedfaef5bd5f987ceb5f7e5ebf88b904bbd4c5cbdeb2ff71b740' >> ${HOME}/.cabal/config
|
||||
echo ' 2e8555dde16ebd8df076f1a8ef13b8f14c66bad8eafefd7d9e37d0ed711821fb' >> ${HOME}/.cabal/config
|
||||
echo ' 8f79fd2389ab2967354407ec852cbe73f2e8635793ac446d09461ffb99527f6e' >> ${HOME}/.cabal/config
|
||||
echo ' key-threshold: 3' >> ${HOME}/.cabal.config
|
||||
|
||||
grep -Ev -- '^\s*--' ${HOME}/.cabal/config | grep -Ev '^\s*$'
|
||||
|
||||
cabal new-update head.hackage -v
|
||||
fi
|
||||
- grep -Ev -- '^\s*--' ${HOME}/.cabal/config | grep -Ev '^\s*$'
|
||||
- "printf 'packages: \".\"\\n' > cabal.project"
|
||||
- "if [ $HCNUMVER -lt 80600 ]; then printf 'package xmonad\\n flags: +generatemanpage\n' >> cabal.project; fi"
|
||||
- touch cabal.project.local
|
||||
- "if ! $NOINSTALLEDCONSTRAINTS; then for pkg in $($HCPKG list --simple-output); do echo $pkg | grep -vw -- xmonad | sed 's/^/constraints: /' | sed 's/-[^-]*$/ installed/' >> cabal.project.local; done; fi"
|
||||
- cat cabal.project || true
|
||||
- cat cabal.project.local || true
|
||||
- if [ -f "./configure.ac" ]; then
|
||||
(cd "." && autoreconf -i);
|
||||
fi
|
||||
- rm -f cabal.project.freeze
|
||||
- cabal new-build -w ${HC} ${TEST} ${BENCH} --project-file="cabal.project" --dep -j2 all
|
||||
- cabal new-build -w ${HC} --disable-tests --disable-benchmarks --project-file="cabal.project" --dep -j2 all
|
||||
- rm -rf .ghc.environment.* "."/dist
|
||||
- DISTDIR=$(mktemp -d /tmp/dist-test.XXXX)
|
||||
|
||||
# Here starts the actual work to be performed for the package under test;
|
||||
# any command which exits with a non-zero exit code causes the build to fail.
|
||||
script:
|
||||
# test that source-distributions can be generated
|
||||
- (cd "." && cabal sdist)
|
||||
- mv "."/dist/xmonad-*.tar.gz ${DISTDIR}/
|
||||
- cd ${DISTDIR} || false
|
||||
- find . -maxdepth 1 -name '*.tar.gz' -exec tar -xvf '{}' \;
|
||||
- "printf 'packages: xmonad-*/*.cabal\\n' > cabal.project"
|
||||
- "if [ $HCNUMVER -lt 80600 ]; then printf 'package xmonad\\n flags: +generatemanpage\n' >> cabal.project; fi"
|
||||
- touch cabal.project.local
|
||||
- "if ! $NOINSTALLEDCONSTRAINTS; then for pkg in $($HCPKG list --simple-output); do echo $pkg | grep -vw -- xmonad | sed 's/^/constraints: /' | sed 's/-[^-]*$/ installed/' >> cabal.project.local; done; fi"
|
||||
- cat cabal.project || true
|
||||
- cat cabal.project.local || true
|
||||
# this builds all libraries and executables (without tests/benchmarks)
|
||||
- cabal new-build -w ${HC} --disable-tests --disable-benchmarks all
|
||||
|
||||
# build & run tests, build benchmarks
|
||||
- cabal new-build -w ${HC} ${TEST} ${BENCH} all
|
||||
- if [ "x$TEST" = "x--enable-tests" ]; then cabal new-test -w ${HC} ${TEST} ${BENCH} all; fi
|
||||
|
||||
# cabal check
|
||||
- (cd xmonad-* && cabal check)
|
||||
|
||||
# haddock
|
||||
- rm -rf ./dist-newstyle
|
||||
- if $HADDOCK; then cabal new-haddock -w ${HC} ${TEST} ${BENCH} all; else echo "Skipping haddock generation";fi
|
||||
|
||||
# Build without installed constraints for packages in global-db
|
||||
- if $UNCONSTRAINED; then rm -f cabal.project.local; echo cabal new-build -w ${HC} --disable-tests --disable-benchmarks all; else echo "Not building without installed constraints"; fi
|
||||
|
||||
# REGENDATA ["-o",".travis.yml","xmonad.cabal","libxrandr-dev"]
|
||||
# EOF
|
151
CHANGES.md
151
CHANGES.md
@@ -1,6 +1,155 @@
|
||||
# Change Log / Release Notes
|
||||
|
||||
## unknown (unknown)
|
||||
## 0.18.0 (February 3, 2024)
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* Dropped support for GHC 8.4.
|
||||
|
||||
### Enhancements
|
||||
|
||||
* Exported `sendRestart` and `sendReplace` from `XMonad.Operations`.
|
||||
|
||||
* Exported `buildLaunch` from `XMonad.Main`.
|
||||
|
||||
* `Tall` does not draw windows with zero area.
|
||||
|
||||
* `XMonad.Operations.floatLocation` now applies size hints. This means windows
|
||||
will snap to these hints as soon as they're floated (mouse move, keybinding).
|
||||
Previously that only happened on mouse resize.
|
||||
|
||||
* Recompilation now detects `flake.nix` and `default.nix` (can be a
|
||||
symlink) and switches to using `nix build` as appropriate.
|
||||
|
||||
* Added `unGrab` to `XMonad.Operations`; this releases XMonad's passive
|
||||
keyboard grab, so other applications (like `scrot`) can do their
|
||||
thing.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Duplicated floats (e.g. from X.A.CopyToAll) no longer escape to inactive
|
||||
screens.
|
||||
|
||||
## 0.17.2 (April 2, 2023)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fixed the build with GHC 9.6.
|
||||
|
||||
## 0.17.1 (September 3, 2022)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* Added custom cursor shapes for resizing and moving windows.
|
||||
|
||||
* Exported `cacheNumlockMask` and `mkGrabs` from `XMonad.Operations`.
|
||||
|
||||
* Added `willFloat` function to `XMonad.ManageHooks` to detect whether the
|
||||
(about to be) managed window will be a floating window or not.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fixed border color of windows with alpha channel. Now all windows have the
|
||||
same opaque border color.
|
||||
|
||||
* Change the main loop to try to avoid [GHC bug 21708] on systems
|
||||
running GHC 9.2 up to version 9.2.3. The issue has been fixed in
|
||||
[GHC 9.2.4] and all later releases.
|
||||
|
||||
[GHC bug 21708]: https://gitlab.haskell.org/ghc/ghc/-/issues/21708
|
||||
[GHC 9.2.4]: https://discourse.haskell.org/t/ghc-9-2-4-released/4851
|
||||
|
||||
## 0.17.0 (October 27, 2021)
|
||||
|
||||
### Enhancements
|
||||
|
||||
* Migrated `X.L.LayoutCombinators.(|||)` into `XMonad.Layout`, providing the
|
||||
ability to directly jump to a layout with the `JumpToLayout` message.
|
||||
|
||||
* Recompilation now detects `stack.yaml` (can be a symlink) alongside
|
||||
`xmonad.hs` and switches to using `stack ghc`. We also updated INSTALL.md
|
||||
with instructions for cabal-install that lead to correct recompilation.
|
||||
|
||||
Deprecation warnings during recompilation are no longer suppressed to make
|
||||
it easier for us to clean up the codebase. These can still be suppressed
|
||||
manually using an `OPTIONS_GHC` pragma with `-Wno-deprecations`.
|
||||
|
||||
* Improve handling of XDG directories.
|
||||
|
||||
1. If all three of xmonad's environment variables (`XMONAD_DATA_DIR,`
|
||||
`XMONAD_CONFIG_DIR`, and `XMONAD_CACHE_DIR`) are set, use them.
|
||||
2. If there is a build script called `build` (see [these build scripts]
|
||||
for usage examples) or configuration `xmonad.hs` in `~/.xmonad`, set
|
||||
all three directories to `~/.xmonad`.
|
||||
3. Otherwise, use the `xmonad` directory in `XDG_DATA_HOME`,
|
||||
`XDG_CONFIG_HOME`, and `XDG_CACHE_HOME` (or their respective
|
||||
fallbacks). These directories are created if necessary.
|
||||
|
||||
In the cases of 1. and 3., the build script or executable is expected to be
|
||||
in the config dir.
|
||||
|
||||
Additionally, the xmonad config binary and intermediate object files were
|
||||
moved to the cache directory (only relevant if using XDG or
|
||||
`XMONAD_CACHE_DIR`).
|
||||
|
||||
* Added `Foldable`, `Functor`, and `Traversable` instances for `Stack`.
|
||||
|
||||
* Added `Typeable layout` constraint to `LayoutClass`, making it possible to
|
||||
cast `Layout` back into a concrete type and extract current layout state
|
||||
from it.
|
||||
|
||||
* Export constructor for `Choose` and `CLR` from `Module.Layout` to allow
|
||||
pattern-matching on the left and right sub-layouts of `Choose l r a`.
|
||||
|
||||
* Added `withUnfocused` function to `XMonad.Operations`, allowing for `X`
|
||||
operations to be applied to unfocused windows.
|
||||
|
||||
[these build scripts]: https://github.com/xmonad/xmonad-testing/tree/master/build-scripts
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fixed a bug when using multiple screens with different dimensions, causing
|
||||
some floating windows to be smaller/larger than the size they requested.
|
||||
|
||||
* Compatibility with GHC 9.0
|
||||
|
||||
* Fixed dunst notifications being obscured when moving floats.
|
||||
https://github.com/xmonad/xmonad/issues/208
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* Made `(<&&>)` and `(<||>)` non-strict in their right operand; i.e., these
|
||||
operators now implement short-circuit evaluation so the right operand is
|
||||
evaluated only if the left operand does not suffice to determine the
|
||||
result.
|
||||
|
||||
* Change `ScreenDetail` to a newtype and make `RationalRect` strict in its
|
||||
contents.
|
||||
|
||||
* Added the `extensibleConf` field to `XConfig` which makes it easier for
|
||||
contrib modules to have composable configuration (custom hooks, …).
|
||||
|
||||
* `util/GenerateManpage.hs` is no longer distributed in the tarball.
|
||||
Instead, the manpage source is regenerated and manpage rebuilt
|
||||
automatically in CI.
|
||||
|
||||
* `DestroyWindowEvent` is now broadcasted to layouts to let them know
|
||||
window-specific resources can be discarded.
|
||||
|
||||
## 0.15 (September 30, 2018)
|
||||
|
||||
* Reimplement `sendMessage` to deal properly with windowset changes made
|
||||
during handling.
|
||||
|
||||
* Add new library functions `windowBracket` and `modifyWindowSet` to
|
||||
`XMonad.Operations`.
|
||||
|
||||
## 0.14.2 (August 21, 2018)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Add the sample configuration file xmonad.hs again to the release tarball.
|
||||
[https://github.com/xmonad/xmonad/issues/181]
|
||||
|
||||
## 0.14.1 (August 20, 2018)
|
||||
|
||||
|
82
CONFIG
82
CONFIG
@@ -1,82 +0,0 @@
|
||||
== Configuring xmonad ==
|
||||
|
||||
xmonad is configured by creating and editing the file:
|
||||
|
||||
~/.xmonad/xmonad.hs
|
||||
|
||||
xmonad then uses settings from this file as arguments to the window manager,
|
||||
on startup. For a complete example of possible settings, see the file:
|
||||
|
||||
man/xmonad.hs
|
||||
|
||||
Further examples are on the website, wiki and extension documentation.
|
||||
|
||||
http://haskell.org/haskellwiki/Xmonad
|
||||
|
||||
== A simple example ==
|
||||
|
||||
Here is a basic example, which overrides the default border width,
|
||||
default terminal, and some colours. This text goes in the file
|
||||
$HOME/.xmonad/xmonad.hs :
|
||||
|
||||
import XMonad
|
||||
|
||||
main = xmonad $ def
|
||||
{ borderWidth = 2
|
||||
, terminal = "urxvt"
|
||||
, normalBorderColor = "#cccccc"
|
||||
, focusedBorderColor = "#cd8b00" }
|
||||
|
||||
You can find the defaults in the file:
|
||||
|
||||
XMonad/Config.hs
|
||||
|
||||
== Checking your xmonad.hs is correct ==
|
||||
|
||||
Place this text in ~/.xmonad/xmonad.hs, and then check that it is
|
||||
syntactically and type correct by loading it in the Haskell
|
||||
interpreter:
|
||||
|
||||
$ ghci ~/.xmonad/xmonad.hs
|
||||
GHCi, version 6.8.1: http://www.haskell.org/ghc/ :? for help
|
||||
Loading package base ... linking ... done.
|
||||
Ok, modules loaded: Main.
|
||||
|
||||
Prelude Main> :t main
|
||||
main :: IO ()
|
||||
|
||||
Ok, looks good.
|
||||
|
||||
== Loading your configuration ==
|
||||
|
||||
To have xmonad start using your settings, type 'mod-q'. xmonad will
|
||||
then load this new file, and run it. If it is unable to, the defaults
|
||||
are used.
|
||||
|
||||
To load successfully, both 'xmonad' and 'ghc' must be in your $PATH
|
||||
environment variable. If GHC isn't in your path, for some reason, you
|
||||
can compile the xmonad.hs file yourself:
|
||||
|
||||
$ cd ~/.xmonad
|
||||
$ ghc --make xmonad.hs
|
||||
$ ls
|
||||
xmonad xmonad.hi xmonad.hs xmonad.o
|
||||
|
||||
When you hit mod-q, this newly compiled xmonad will be used.
|
||||
|
||||
== Where are the defaults? ==
|
||||
|
||||
The default configuration values are defined in the source file:
|
||||
|
||||
XMonad/Config.hs
|
||||
|
||||
the XConfig data structure itself is defined in:
|
||||
|
||||
XMonad/Core.hs
|
||||
|
||||
== Extensions ==
|
||||
|
||||
Since the xmonad.hs file is just another Haskell module, you may import
|
||||
and use any Haskell code or libraries you wish. For example, you can use
|
||||
things from the xmonad-contrib library, or other code you write
|
||||
yourself.
|
122
CONTRIBUTING.md
122
CONTRIBUTING.md
@@ -34,9 +34,15 @@ Awesome! Here are a few things to keep in mind:
|
||||
nontrivial changes to xmonad. There are a couple of ways you can
|
||||
chat with us:
|
||||
|
||||
- Join the [`#xmonad` IRC channel] on `irc.libera.chat` or the
|
||||
official [matrix channel], which is linked to IRC. This is the
|
||||
preferred (and fastest!) way to get into contact with us.
|
||||
|
||||
- Post a message to the [mailing list][ml].
|
||||
|
||||
- Join the `#xmonad` IRC channel on `chat.freenode.org`.
|
||||
* [XMonad.Doc.Developing][xmonad-doc-developing] is a great
|
||||
resource to get an overview of xmonad. Make sure to also check
|
||||
it if you want more details on the coding style.
|
||||
|
||||
* Continue reading this document!
|
||||
|
||||
@@ -55,87 +61,71 @@ Here are some tips for getting your changes merged into xmonad:
|
||||
* Your changes should include relevant entries in the `CHANGES.md`
|
||||
file. Help us communicate changes to the community.
|
||||
|
||||
* Make sure you test your changes using the [xmonad-testing][]
|
||||
repository. Include a new configuration file that shows off your
|
||||
changes if possible by creating a PR on that repository as well.
|
||||
* Make sure you test your changes against the most recent commit of
|
||||
[xmonad][] (and [xmonad-contrib][], if you're contributing there).
|
||||
If you're adding a new module or functionality, make sure to add an
|
||||
example in the documentation and in the PR description.
|
||||
|
||||
* Make sure you read the section on rebasing and squashing commits
|
||||
below.
|
||||
* Make sure you run the automated tests. Both [xmonad-contrib][]
|
||||
and [xmonad][] have test-suites that you could run with
|
||||
`stack test` for example.
|
||||
|
||||
## Rebasing and Squashing Commits
|
||||
* When committing, try to follow existing practices. For more
|
||||
information on what good commit messages look like, see [How to
|
||||
Write a Git Commit Message][commit-cbeams] and the [Kernel
|
||||
documentation][commit-kernel] about committing logical changes
|
||||
separately.
|
||||
|
||||
Under no circumstances should you ever merge the master branch into
|
||||
your feature branch. This makes it nearly impossible to review your
|
||||
changes and we *will not accept your PR* if you do this.
|
||||
## Style Guidelines
|
||||
|
||||
Instead of merging you should rebase your changes on top of the master
|
||||
branch. If a core team member asks you to "rebase your changes" this
|
||||
is what they are talking about.
|
||||
Below are some common style guidelines that all of the core modules
|
||||
follow. Before submitting a pull request, make sure that your code does
|
||||
as well!
|
||||
|
||||
It's also helpful to squash all of your commits so that your pull
|
||||
request only contains a single commit. Again, this makes it easier to
|
||||
review your changes and identify the changes later on in the Git
|
||||
history.
|
||||
* Comment every top level function (particularly exported functions),
|
||||
and provide a type signature; use Haddock syntax in the comments.
|
||||
|
||||
### How to Rebase Your Changes
|
||||
* Follow the coding style of the module that you are making changes to
|
||||
(`n` spaces for indentation, where to break long type signatures, …).
|
||||
|
||||
The goal of rebasing is to bring recent changes from the master branch
|
||||
into your feature branch. This often helps resolve conflicts where
|
||||
you have changed a file that also changed in a recently merged pull
|
||||
request (i.e. the `CHANGES.md` file). Here is how you do that.
|
||||
* New code should not introduce any new warnings. If you want to
|
||||
check this yourself before submitting a pull request, there is the
|
||||
`pedantic` flag, which is enforced in our CI. You can enable it by
|
||||
building your changes with `stack build --flag xmonad:pedantic` or
|
||||
`cabal build --flag pedantic`.
|
||||
|
||||
1. Make sure that you have a `git remote` configured for the main
|
||||
repository. I like to call this remote `upstream`:
|
||||
* Likewise, your code should be free of [hlint] warnings; this is also
|
||||
enforced in our GitHub CI.
|
||||
|
||||
$ git remote add upstream https://github.com/xmonad/xmonad-contrib.git
|
||||
* Partial functions are to be avoided: the window manager should not
|
||||
crash, so do not call `error` or `undefined`.
|
||||
|
||||
2. Pull from upstream and rewrite your changes on top of master. For
|
||||
this to work you should not have any modified files in your
|
||||
working directory. Run these commands from within your feature
|
||||
branch (the branch you are asking to be merged):
|
||||
* Any pure function added to the core should have QuickCheck
|
||||
properties precisely defining its behavior.
|
||||
|
||||
$ git fetch --all
|
||||
$ git pull --rebase upstream master
|
||||
* New modules should identify the author, and be submitted under the
|
||||
same license as xmonad (BSD3 license).
|
||||
|
||||
3. If the rebase was successful you can now push your feature branch
|
||||
back to GitHub. You need to force the push since your commits
|
||||
have been rewritten and have new IDs:
|
||||
## Keep rocking!
|
||||
|
||||
$ git push --force-with-lease
|
||||
|
||||
4. Your pull request should now be conflict-free and only contain the
|
||||
changes that you actually made.
|
||||
|
||||
### How to Squash Commits
|
||||
|
||||
The goal of squashing commits is to produce a clean Git history where
|
||||
each pull request contains just one commit.
|
||||
|
||||
1. Use `git log` to see how many commits you are including in your
|
||||
pull request. (If you've already submitted your pull request you
|
||||
can see this in the GitHub interface.)
|
||||
|
||||
2. Rebase all of those commits into a single commit. Assuming you
|
||||
want to squash the last four (4) commits into a single commit:
|
||||
|
||||
$ git rebase -i HEAD~4
|
||||
|
||||
3. Git will open your editor and display the commits you are
|
||||
rebasing with the word "pick" in front of them.
|
||||
|
||||
4. Leave the first listed commit as "pick" and change the remaining
|
||||
commits from "pick" to "squash".
|
||||
|
||||
5. Save the file and exit your editor. Git will create a new commit
|
||||
and open your editor so you can modify the commit message.
|
||||
|
||||
6. If everything was successful you can push your changed history
|
||||
back up to GitHub:
|
||||
|
||||
$ git push --force-with-lease
|
||||
xmonad is a passion project created and maintained by the community.
|
||||
We'd love for you to maintain your own contributed modules (approve
|
||||
changes from other contributors, review code, etc.). However, before
|
||||
we'd be comfortable adding you to the [xmonad GitHub
|
||||
organization][xmonad-gh-org] we need to trust that you have sufficient
|
||||
knowledge of Haskell and git; and have a way of chatting with you ([IRC,
|
||||
Matrix, etc.][community]).
|
||||
|
||||
[hlint]: https://github.com/ndmitchell/hlint
|
||||
[xmonad]: https://github.com/xmonad/xmonad
|
||||
[xmonad-contrib]: https://github.com/xmonad/xmonad-contrib
|
||||
[xmonad-testing]: https://github.com/xmonad/xmonad-testing
|
||||
[x11]: https://github.com/xmonad/X11
|
||||
[ml]: https://mail.haskell.org/cgi-bin/mailman/listinfo/xmonad
|
||||
[xmonad-doc-developing]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Doc-Developing.html
|
||||
[`#xmonad` IRC channel]: https://web.libera.chat/#xmonad
|
||||
[matrix channel]: https://matrix.to/#/#xmonad:matrix.org
|
||||
[commit-cbeams]: https://cbea.ms/git-commit/
|
||||
[commit-kernel]: https://www.kernel.org/doc/html/v4.10/process/submitting-patches.html#separate-your-changes
|
||||
[community]: https://xmonad.org/community.html
|
||||
[xmonad-gh-org]: https://github.com/xmonad
|
||||
|
402
INSTALL.md
Normal file
402
INSTALL.md
Normal file
@@ -0,0 +1,402 @@
|
||||
# Install XMonad
|
||||
|
||||
On many systems xmonad is available as a binary package in your
|
||||
distribution (Debian, Ubuntu, Fedora, Arch, Gentoo, …).
|
||||
It's by far the easiest way to get xmonad, although you'll miss out on the
|
||||
latest features and fixes that may not have been released yet.
|
||||
|
||||
If you do want the latest and greatest, continue reading.
|
||||
Those who install from distro can skip this and go straight to
|
||||
[the XMonad Configuration Tutorial](TUTORIAL.md).
|
||||
|
||||
<!-- https://github.com/frnmst/md-toc -->
|
||||
<!-- regenerate via: md_toc -s1 -p github INSTALL.md -->
|
||||
<!--TOC-->
|
||||
|
||||
- [Dependencies](#dependencies)
|
||||
- [Preparation](#preparation)
|
||||
- [Download XMonad sources](#download-xmonad-sources)
|
||||
- [Build XMonad](#build-xmonad)
|
||||
- [Build using Stack](#build-using-stack)
|
||||
- [Build using cabal-install](#build-using-cabal-install)
|
||||
- [Make XMonad your window manager](#make-xmonad-your-window-manager)
|
||||
- [Custom Build Script](#custom-build-script)
|
||||
|
||||
<!--TOC-->
|
||||
|
||||
## Dependencies
|
||||
|
||||
#### Debian, Ubuntu
|
||||
|
||||
``` console
|
||||
$ sudo apt install \
|
||||
> git \
|
||||
> libx11-dev libxft-dev libxinerama-dev libxrandr-dev libxss-dev
|
||||
```
|
||||
|
||||
#### Fedora
|
||||
|
||||
``` console
|
||||
$ sudo dnf install \
|
||||
> git \
|
||||
> libX11-devel libXft-devel libXinerama-devel libXrandr-devel libXScrnSaver-devel
|
||||
```
|
||||
|
||||
#### Arch
|
||||
|
||||
``` console
|
||||
$ sudo pacman -S \
|
||||
> git \
|
||||
> xorg-server xorg-apps xorg-xinit xorg-xmessage \
|
||||
> libx11 libxft libxinerama libxrandr libxss \
|
||||
> pkgconf
|
||||
```
|
||||
|
||||
#### Void
|
||||
|
||||
``` console
|
||||
$ sudo xbps-install \
|
||||
> git \
|
||||
> ncurses-libtinfo-libs ncurses-libtinfo-devel \
|
||||
> libX11-devel libXft-devel libXinerama-devel libXrandr-devel libXScrnSaver-devel \
|
||||
> pkg-config
|
||||
```
|
||||
|
||||
## Preparation
|
||||
|
||||
We'll use the [XDG] directory specifications here, meaning our
|
||||
configuration will reside within `$XDG_CONFIG_HOME`, which is
|
||||
`~/.config` on most systems. Let's create this directory and move to
|
||||
it:
|
||||
|
||||
``` console
|
||||
$ mkdir -p ~/.config/xmonad && cd ~/.config/xmonad
|
||||
```
|
||||
|
||||
If you already have an `xmonad.hs` configuration, you can copy it over
|
||||
now. If not, you can use the defaults: create a file called `xmonad.hs`
|
||||
with the following content:
|
||||
|
||||
``` haskell
|
||||
import XMonad
|
||||
|
||||
main :: IO ()
|
||||
main = xmonad def
|
||||
```
|
||||
|
||||
Older versions of xmonad used `~/.xmonad` instead.
|
||||
This is still supported, but XDG is preferred.
|
||||
|
||||
## Download XMonad sources
|
||||
|
||||
Still in `~/.config/xmonad`, clone `xmonad` and `xmonad-contrib` repositories
|
||||
using [git][]:
|
||||
|
||||
``` console
|
||||
$ git clone https://github.com/xmonad/xmonad
|
||||
$ git clone https://github.com/xmonad/xmonad-contrib
|
||||
```
|
||||
|
||||
This will give you the latest `HEAD`; if you want you can also check
|
||||
out a tagged release, e.g.:
|
||||
|
||||
``` console
|
||||
$ git clone --branch v0.17.2 https://github.com/xmonad/xmonad
|
||||
$ git clone --branch v0.17.1 https://github.com/xmonad/xmonad-contrib
|
||||
```
|
||||
|
||||
(Sources and binaries don't usually go into `~/.config`. In our case,
|
||||
however, it avoids complexities related to Haskell build tools and lets us
|
||||
focus on the important bits of XMonad installation.)
|
||||
|
||||
## Build XMonad
|
||||
|
||||
There are two widely used Haskell build tools:
|
||||
|
||||
* [Stack][stack]
|
||||
* [cabal-install][cabal-install]
|
||||
|
||||
We include instructions for both.
|
||||
Unless you already know which one you prefer, use Stack, which is easier.
|
||||
|
||||
### Build using Stack
|
||||
|
||||
#### Install Stack
|
||||
|
||||
Probably one of the best ways to get [stack] is to use [GHCup], which is the main Haskell installer according to language's official [website][GHCup] and community [survey]. GHCup is [widely available] and is considered less error prone than other installation options.
|
||||
|
||||
You can also use your system's package
|
||||
manager:
|
||||
|
||||
``` console
|
||||
$ sudo apt install haskell-stack # Debian, Ubuntu
|
||||
$ sudo dnf install stack # Fedora
|
||||
$ sudo pacman -S stack # Arch
|
||||
```
|
||||
|
||||
If you install stack via this method, it is advisable that you run
|
||||
`stack upgrade` after installation. This will make sure that you are on
|
||||
the most recent version of the program, regardless of which version your
|
||||
distribution actually packages.
|
||||
|
||||
If your distribution does not package stack, you can also easily install
|
||||
it via the following command (this is the recommended way to install
|
||||
stack via its [documentation][stack]):
|
||||
|
||||
``` console
|
||||
$ curl -sSL https://get.haskellstack.org/ | sh
|
||||
```
|
||||
|
||||
#### Create a New Project
|
||||
|
||||
Let's create a stack project. Since we're already in the correct
|
||||
directory (`~/.config/xmonad`) with `xmonad` and `xmonad-contrib`
|
||||
subdirectories, starting a new stack project is as simple as running `stack
|
||||
init`.
|
||||
|
||||
Stack should now inform you that it will use the relevant `stack` and
|
||||
`cabal` files from `xmonad` and `xmonad-contrib` to generate its
|
||||
`stack.yaml` file. At the time of writing, this looks a little bit like
|
||||
this:
|
||||
|
||||
``` console
|
||||
$ stack init
|
||||
Looking for .cabal or package.yaml files to use to init the project.
|
||||
Using cabal packages:
|
||||
- xmonad-contrib/
|
||||
- xmonad/
|
||||
|
||||
Selecting the best among 19 snapshots...
|
||||
|
||||
* Matches https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/17/9.yaml
|
||||
|
||||
Selected resolver: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/17/9.yaml
|
||||
Initialising configuration using resolver: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/17/9.yaml
|
||||
Total number of user packages considered: 2
|
||||
Writing configuration to file: stack.yaml
|
||||
All done.
|
||||
```
|
||||
|
||||
If you look into your current directory now, you should see a freshly
|
||||
generated `stack.yaml` file:
|
||||
|
||||
``` console
|
||||
$ ls
|
||||
xmonad xmonad-contrib stack.yaml xmonad.hs
|
||||
```
|
||||
|
||||
The meat of that file (comments start with `#`, we've omitted them here)
|
||||
will look a little bit like
|
||||
|
||||
``` yaml
|
||||
resolver:
|
||||
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/17/9.yaml
|
||||
|
||||
packages:
|
||||
- xmonad
|
||||
- xmonad-contrib
|
||||
```
|
||||
|
||||
With `stack.yaml` alongside `xmonad.hs`, xmonad now knows that it needs to use
|
||||
`stack ghc` instead of just `ghc` when (re)compiling its configuration.
|
||||
If you want to keep xmonad sources and the stack project elsewhere, but still
|
||||
use `xmonad --recompile`, symlink your real `stack.yaml` into the xmonad
|
||||
configuration directory, or [use a custom build script](#custom-build-script).
|
||||
|
||||
#### Install Everything
|
||||
|
||||
Installing things is as easy as typing `stack install`. This will
|
||||
install the correct version of GHC, as well as build all of the required
|
||||
packages (`stack build`) and then copy the relevant executables
|
||||
(`xmonad`, in our case) to `~/.local/bin`. Make sure to add that
|
||||
directory to your `$PATH`! The command `which xmonad` should now return
|
||||
that executable. In case it does not, check if you still have xmonad
|
||||
installed via your package manager and uninstall it.
|
||||
|
||||
If you're getting build failures while building the `X11` package it may
|
||||
be that you don't have the required C libraries installed. See
|
||||
[above](#dependencies).
|
||||
|
||||
### Build using cabal-install
|
||||
|
||||
#### Install cabal-install
|
||||
|
||||
Probably one of the best ways to get [cabal-install] is to use [GHCup], which is the main Haskell installer according to language's official [website][GHCup] and community [survey]. GHCup is [widely available] and is considered less error prone than other installation options.
|
||||
|
||||
You can also use your system's package
|
||||
manager:
|
||||
|
||||
``` console
|
||||
$ sudo apt install cabal-install # Debian, Ubuntu
|
||||
$ sudo dnf install cabal-install # Fedora
|
||||
$ sudo pacman -S cabal-install # Arch
|
||||
```
|
||||
|
||||
See also <https://www.haskell.org/cabal/#install-upgrade>.
|
||||
|
||||
#### Create a New Project
|
||||
|
||||
If you want to use `xmonad` or `xmonad-contrib` from git, you will need a
|
||||
`cabal.project` file. If you want to use both from [Hackage][], you should
|
||||
skip this step.
|
||||
|
||||
Create a file named `cabal.project` containing:
|
||||
|
||||
```
|
||||
packages: */*.cabal
|
||||
```
|
||||
|
||||
(If you do this step without using [git] checkouts, you will get an error from
|
||||
cabal in the next step. Simply remove `cabal.project` and try again.)
|
||||
|
||||
#### Install Everything
|
||||
|
||||
You'll need to update the cabal package index, build xmonad and xmonad-contrib
|
||||
libraries and then build the xmonad binary:
|
||||
|
||||
``` console
|
||||
$ cabal update
|
||||
$ cabal install --package-env=$HOME/.config/xmonad --lib base xmonad xmonad-contrib
|
||||
$ cabal install --package-env=$HOME/.config/xmonad xmonad
|
||||
```
|
||||
|
||||
This will create a GHC environment in `~/.config/xmonad` so that the libraries
|
||||
are available for recompilation of the config file, and also install the
|
||||
xmonad binary to `~/.cabal/bin/xmonad`. Make sure you have that directory in
|
||||
your `$PATH`!
|
||||
|
||||
If you're getting build failures while building the `X11` package it may
|
||||
be that you don't have the required C libraries installed. See
|
||||
[above](#dependencies).
|
||||
|
||||
## Make XMonad your window manager
|
||||
|
||||
This step varies depending on your distribution and X display manager (if
|
||||
any).
|
||||
|
||||
#### Debian, Ubuntu
|
||||
|
||||
`/etc/X11/xinit/xinitrc` runs `/etc/X11/Xsession` which runs `~/.xsession`, so
|
||||
you probably want to put `exec xmonad` there (don't forget the shebang and chmod).
|
||||
|
||||
(Tested with `startx`, `xdm`, `lightdm`.)
|
||||
|
||||
By using `~/.xsession`, the distro takes care of stuff like dbus, ssh-agent, X
|
||||
resources, etc. If you want a completely manual X session, use `~/.xinitrc`
|
||||
instead. Or invoke `startx`/`xinit` with an explicit path.
|
||||
|
||||
Some newer display managers require an entry in `/usr/share/xsessions`.
|
||||
To use your custom `~/.xsession`, put these lines to
|
||||
`/usr/share/xsessions/default.desktop`:
|
||||
|
||||
```
|
||||
[Desktop Entry]
|
||||
Name=Default X session
|
||||
Type=Application
|
||||
Exec=default
|
||||
```
|
||||
|
||||
(Tested with `sddm`.)
|
||||
|
||||
#### Fedora
|
||||
|
||||
`/etc/X11/xinit/xinitrc` runs `~/.Xclients`, so you probably want to put `exec
|
||||
xmonad` there (don't forget the shebang and chmod). Like in Debian, this can
|
||||
be overridden by having a completely custom `~/.xinitrc` or passing arguments
|
||||
to `startx`/`xinit`.
|
||||
|
||||
X display managers (e.g. `lightdm`) usually invoke `/etc/X11/xinit/Xsession`
|
||||
instead, which additionally redirects output to `~/.xsession-errors` and also
|
||||
tries `~/.xsession` before `~/.Xclients`.
|
||||
|
||||
Newer display managers require an entry in `/usr/share/xsessions`, which is
|
||||
available in the `xorg-x11-xinit-session` package.
|
||||
|
||||
#### Arch
|
||||
|
||||
`/etc/X11/xinit/xinitrc` runs `twm`, `xclock` and 3 `xterm`s; users are
|
||||
meant to just copy that to `~/.xinitrc` and
|
||||
[customize](https://wiki.archlinux.org/title/Xinit#xinitrc) it: replace the
|
||||
last few lines with `exec xmonad`.
|
||||
|
||||
Display managers like `lightdm` have their own `Xsession` script which invokes
|
||||
`~/.xsession`. Other display managers need an entry in
|
||||
`/usr/share/xsessions`, <https://aur.archlinux.org/packages/xinit-xsession/>
|
||||
provides one.
|
||||
|
||||
#### See also
|
||||
|
||||
* <https://xmonad.org/documentation.html#in-your-environment>
|
||||
* [FAQ: How can I use xmonad with a display manager? (xdm, kdm, gdm)](https://wiki.haskell.org/Xmonad/Frequently_asked_questions#How_can_I_use_xmonad_with_a_display_manager.3F_.28xdm.2C_kdm.2C_gdm.29)
|
||||
|
||||
## Custom Build Script
|
||||
|
||||
If you need to customize what happens during `xmonad --recompile` (bound to
|
||||
`M-q` by default), perhaps because your xmonad configuration is a whole
|
||||
separate Haskell package, you need to create a so-called `build` file. This
|
||||
is quite literally just a shell script called `build` in your xmonad directory
|
||||
(which is `~/.config/xmonad` for us) that tells xmonad how it should build its
|
||||
executable.
|
||||
|
||||
A good starting point (this is essentially [what xmonad would do][]
|
||||
without a build file, with the exception that we are invoking `stack
|
||||
ghc` instead of plain `ghc`) would be
|
||||
|
||||
``` shell
|
||||
#!/bin/sh
|
||||
|
||||
exec stack ghc -- \
|
||||
--make xmonad.hs \
|
||||
-i \
|
||||
-ilib \
|
||||
-fforce-recomp \
|
||||
-main-is main \
|
||||
-v0 \
|
||||
-o "$1"
|
||||
```
|
||||
|
||||
Don't forget to mark the file as `+x`: `chmod +x build`!
|
||||
|
||||
Some example build scripts for `stack` and `cabal` are provided in the
|
||||
`xmonad-contrib` distribution. You can see those online in the
|
||||
[scripts/build][] directory. You might wish to use these if you have
|
||||
special dependencies for your `xmonad.hs`, especially with cabal as
|
||||
you must use a cabal file and often a `cabal.project` to specify them;
|
||||
`cabal install --lib` above generally isn't enough, and when it is
|
||||
it can be difficult to keep track of when you want to replicate your
|
||||
configuration on another system.
|
||||
|
||||
#### Don't Recompile on Every Startup
|
||||
|
||||
By default, xmonad always recompiles itself when a build script is used
|
||||
(because the build script could contain arbitrary code, so a simple
|
||||
check whether the `xmonad.hs` file changed is not enough). If you find
|
||||
that too annoying, then you can use the `xmonad-ARCH` executable that
|
||||
`xmonad --recompile` generates instead of `xmonad` in your startup. For
|
||||
example, instead of writing
|
||||
|
||||
``` shell
|
||||
exec xmonad
|
||||
```
|
||||
|
||||
in your `~/.xinitrc`, you would write
|
||||
|
||||
``` shell
|
||||
exec $HOME/.cache/xmonad/xmonad-x86_64-linux
|
||||
```
|
||||
|
||||
The `~/.cache` prefix is the `$XDG_CACHE_HOME` directory. Note that
|
||||
if your xmonad configuration resides within `~/.xmonad`, then the
|
||||
executable will also be within that directory and not in
|
||||
`$XDG_CACHE_HOME`.
|
||||
|
||||
[XDG]: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
|
||||
[git]: https://git-scm.com/
|
||||
[stack]: https://docs.haskellstack.org/en/stable/README/
|
||||
[cabal-install]: https://www.haskell.org/cabal/
|
||||
[GHCup]: https://www.haskell.org/ghcup/
|
||||
[survey]: https://taylor.fausak.me/2022/11/18/haskell-survey-results/
|
||||
[widely available]: https://www.haskell.org/ghcup/install/#supported-platforms
|
||||
[what xmonad would do]: https://github.com/xmonad/xmonad/blob/master/src/XMonad/Core.hs#L659-L667
|
||||
[Hackage]: https://hackage.haskell.org/
|
||||
[scripts/build]: https://github.com/xmonad/xmonad-contrib/blob/master/scripts/build
|
45
LICENSE
45
LICENSE
@@ -1,31 +1,28 @@
|
||||
Copyright (c) 2007,2008 Spencer Janssen
|
||||
Copyright (c) 2007,2008 Don Stewart
|
||||
Copyright (c) The Xmonad Community. All rights reserved.
|
||||
|
||||
All rights reserved.
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
3. Neither the name of the copyright holder nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
3. Neither the name of the author nor the names of his contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
161
MAINTAINERS.md
161
MAINTAINERS.md
@@ -2,82 +2,145 @@
|
||||
|
||||
## The XMonad Core Team
|
||||
|
||||
* Adam Vogt [GitHub][aavogt]
|
||||
|
||||
* Brandon S Allbery [GitHub][geekosaur], IRC: `geekosaur`
|
||||
* Brandon S Allbery [GitHub][geekosaur], IRC: `geekosaur`, [GPG][gpg:geekosaur]
|
||||
|
||||
* Brent Yorgey [GitHub][byorgey], IRC: `byorgey`
|
||||
|
||||
* Daniel Wagner [GitHub][dmwit], IRC: `dmwit`
|
||||
* Daniel Wagner [GitHub][dmwit], [Twitter][twitter:dmwit], IRC: `dmwit`
|
||||
|
||||
* David Lazar [GitHub][davidlazar]
|
||||
* Sibi Prabakaran [GitHub][psibi], [Twitter][twitter:psibi], IRC: `sibi`
|
||||
|
||||
* Devin Mullins [GitHub][twifkak]
|
||||
* Tomáš Janoušek [GitHub][liskin], [Twitter][twitter:liskin], IRC: `liskin`, [GPG][gpg:liskin]
|
||||
|
||||
* Peter J. Jones [GitHub][pjones], [Twitter][twitter:pjones], [OpenPGP Key][pgp:pjones], IRC: `pmade`
|
||||
* Tony Zorman [GitHub][slotThe], IRC: `Solid`, [GPG][gpg:slotThe]
|
||||
|
||||
[geekosaur]: https://github.com/geekosaur
|
||||
[byorgey]: https://github.com/byorgey
|
||||
[dmwit]: https://github.com/dmwit
|
||||
[psibi]: https://github.com/psibi
|
||||
[liskin]: https://github.com/liskin
|
||||
[slotThe]: https://github.com/slotThe
|
||||
|
||||
[gpg:geekosaur]: https://github.com/geekosaur.gpg
|
||||
[gpg:liskin]: https://github.com/liskin.gpg
|
||||
[gpg:slotThe]: https://github.com/slotThe.gpg
|
||||
|
||||
[twitter:dmwit]: https://twitter.com/dmwit13
|
||||
[twitter:psibi]: https://twitter.com/psibi
|
||||
[twitter:liskin]: https://twitter.com/Liskni_si
|
||||
|
||||
## Hall of Fame (past maintainers/developers)
|
||||
|
||||
* Adam Vogt [GitHub](https://github.com/aavogt)
|
||||
|
||||
* Peter Simons [GitHub](https://github.com/peti), [Twitter](https://twitter.com/OriginalPeti)
|
||||
|
||||
* Spencer Janssen [GitHub](https://github.com/spencerjanssen)
|
||||
|
||||
* Don Stewart [GitHub](https://github.com/donsbot), [Twitter](https://twitter.com/donsbot)
|
||||
|
||||
* Jason Creighton [GitHub](https://github.com/JasonCreighton)
|
||||
|
||||
* David Roundy [GitHub](https://github.com/droundy)
|
||||
|
||||
* Daniel Schoepe [GitHub](https://github.com/dschoepe)
|
||||
|
||||
* Eric Mertens [GitHub](https://github.com/glguy)
|
||||
|
||||
* Nicolas Pouillard [GitHub](https://github.com/np)
|
||||
|
||||
* Roman Cheplyaka [GitHub](https://github.com/UnkindPartition)
|
||||
|
||||
* Gwern Branwen [GitHub](https://github.com/gwern)
|
||||
|
||||
* Lukas Mai [GitHub](https://github.com/mauke)
|
||||
|
||||
* Braden Shepherdson [GitHub](https://github.com/shepheb)
|
||||
|
||||
* Devin Mullins [GitHub](https://github.com/twifkak)
|
||||
|
||||
* David Lazar [GitHub](https://github.com/davidlazar)
|
||||
|
||||
* Peter J. Jones [GitHub](https://github.com/pjones)
|
||||
|
||||
## Release Procedures
|
||||
|
||||
When the time comes to release another version of XMonad and Contrib...
|
||||
When the time comes to release another version of xmonad and xmonad-contrib:
|
||||
|
||||
1. Create a release branch (e.g., `release-0.XX`).
|
||||
1. Update the version number in all the `*.cabal` files and let the CI
|
||||
verify that it all builds together.
|
||||
|
||||
This will allow you to separate the release process from main
|
||||
development. Changes you make on this branch will be merged back
|
||||
into `master` as one of the last steps.
|
||||
2. Review documentation files and make sure they are accurate:
|
||||
|
||||
2. Update the version number in the `*.cabal` files and verify
|
||||
dependencies and documentation. This includes the `tested-with:`
|
||||
field.
|
||||
- [`README.md`](README.md)
|
||||
- [`CHANGES.md`](CHANGES.md) (bump version, set date)
|
||||
- [`INSTALL.md`](INSTALL.md)
|
||||
- [`man/xmonad.1.markdown.in`](man/xmonad.1.markdown.in)
|
||||
- [haddocks](https://xmonad.github.io/xmonad-docs/)
|
||||
|
||||
3. Use the [packdeps][] tool to ensure you have the dependency
|
||||
versions correct. If you need to update the version of a
|
||||
dependency then you should rebuild and retest.
|
||||
If the manpage changes, wait for the CI to rebuild the rendered outputs.
|
||||
|
||||
4. Review documentation files and make sure they are accurate:
|
||||
3. Update the website:
|
||||
|
||||
- `README.md`
|
||||
- `CHANGES.md`
|
||||
- and the `example-config.hs` in the `xmonad-testing` repo
|
||||
- Draft a [new release announcement][web-announce].
|
||||
- Check install instructions, guided tour, keybindings cheat sheet, …
|
||||
|
||||
5. Generate the manpage:
|
||||
4. Make sure that `tested-with:` covers several recent releases of GHC, that
|
||||
`.github/workflows/haskell-ci.yml` had been updated to test all these GHC
|
||||
versions and that `.github/workflows/stack.yml` tests with several recent
|
||||
revisions of [Stackage][] LTS.
|
||||
|
||||
* `cabal configure` with the `-fgeneratemanpage` flag
|
||||
* Build the project
|
||||
* Run the `generatemanpage` tool from the top level of this repo
|
||||
* Review the man page: `man -l man/xmonad.1`
|
||||
5. Trigger the Haskell-CI workflow and fill in the candidate version number.
|
||||
This will upload a release candidate to Hackage.
|
||||
|
||||
6. Tag the repository with the release version (e.g., `v0.13`)
|
||||
- https://github.com/xmonad/xmonad/actions/workflows/haskell-ci.yml
|
||||
- https://github.com/xmonad/xmonad-contrib/actions/workflows/haskell-ci.yml
|
||||
|
||||
7. Build the project tarballs (`cabal sdist`)
|
||||
Check that everything looks good. If not, push fixes and do another
|
||||
candidate. When everything's ready, create a release on GitHub:
|
||||
|
||||
8. Upload the packages to Hackage (`cabal upload`)
|
||||
- https://github.com/xmonad/xmonad/releases/new
|
||||
- https://github.com/xmonad/xmonad-contrib/releases/new
|
||||
|
||||
9. Merge the release branches into `master`
|
||||
CI will automatically upload the final release to Hackage.
|
||||
|
||||
10. Update the website:
|
||||
See [haskell-ci-hackage.patch][] for details about the Hackage automation.
|
||||
|
||||
* Generate and push haddocks with `xmonad-web/gen-docs.sh`
|
||||
6. Post announcement to:
|
||||
|
||||
* Check that `tour.html` and `intro.html` are up to date, and
|
||||
mention all core bindings
|
||||
- [xmonad.org website](https://github.com/xmonad/xmonad-web/tree/gh-pages/news/_posts)
|
||||
- [XMonad mailing list](https://mail.haskell.org/mailman/listinfo/xmonad)
|
||||
- [Haskell Cafe](https://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe)
|
||||
- [Haskell Discourse](https://discourse.haskell.org/)
|
||||
- [Twitter](https://twitter.com/xmonad)
|
||||
- [Reddit](https://www.reddit.com/r/xmonad/)
|
||||
|
||||
11. Update the topic for the IRC channel (`#xmonad`)
|
||||
See [old announcements][old-announce] ([even older][older-announce]) for inspiration.
|
||||
|
||||
12. Send the `announce-0.XX.txt` file to:
|
||||
7. Trigger xmonad-docs build to generate and persist docs for the just
|
||||
released version:
|
||||
|
||||
- XMonad mailing list
|
||||
- Haskell Cafe
|
||||
- https://github.com/xmonad/xmonad-docs/actions/workflows/stack.yml
|
||||
|
||||
[packdeps]: http://hackage.haskell.org/package/packdeps
|
||||
8. Bump version for development (add `.9`) and prepare fresh sections in
|
||||
[`CHANGES.md`](CHANGES.md).
|
||||
|
||||
[aavogt]: https://github.com/orgs/xmonad/people/aavogt
|
||||
[geekosaur]: https://github.com/orgs/xmonad/people/geekosaur
|
||||
[byorgey]: https://github.com/orgs/xmonad/people/byorgey
|
||||
[dmwit]: https://github.com/orgs/xmonad/people/dmwit
|
||||
[davidlazar]: https://github.com/orgs/xmonad/people/davidlazar
|
||||
[twifkak]: https://github.com/orgs/xmonad/people/twifkak
|
||||
[packdeps]: https://hackage.haskell.org/package/packdeps
|
||||
[Stackage]: https://www.stackage.org/
|
||||
[haskell-ci-hackage.patch]: .github/workflows/haskell-ci-hackage.patch
|
||||
[web-announce]: https://github.com/xmonad/xmonad-web/tree/gh-pages/news/_posts
|
||||
[old-announce]: https://github.com/xmonad/xmonad-web/blob/gh-pages/news/_posts/2021-10-27-xmonad-0-17-0.md
|
||||
[older-announce]: https://github.com/xmonad/xmonad-web/tree/55614349421ebafaef4a47424fcb16efa80ff768
|
||||
|
||||
[pjones]: https://github.com/orgs/xmonad/people/pjones
|
||||
[twitter:pjones]: https://twitter.com/contextualdev
|
||||
[pgp:pjones]: http://pgp.mit.edu/pks/lookup?op=get&search=0x526722D1204284CB
|
||||
## Website and Other Accounts
|
||||
|
||||
* The [xmonad twitter] is tended to by [liskin].
|
||||
|
||||
* The [xmonad.org] domain is owned by [eyenx] and the website itself is
|
||||
deployed via GitHub Pages. It can be updated by making a pull request
|
||||
against the [xmonad-web] repository.
|
||||
|
||||
[eyenx]: https://github.com/eyenx
|
||||
[xmonad-web]: https://github.com/xmonad/xmonad-web/
|
||||
[xmonad.org]: https://xmonad.org/
|
||||
[xmonad twitter]: https://twitter.com/xmonad
|
||||
|
187
README.md
187
README.md
@@ -1,8 +1,27 @@
|
||||
# xmonad: A Tiling Window Manager
|
||||
<p align="center">
|
||||
<a href="https://xmonad.org/"><img alt="XMonad logo" src="https://xmonad.org/images/logo-wrapped.svg" height=150></a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://hackage.haskell.org/package/xmonad"><img alt="Hackage" src="https://img.shields.io/hackage/v/xmonad?logo=haskell"></a>
|
||||
<a href="https://github.com/xmonad/xmonad/blob/readme/LICENSE"><img alt="License" src="https://img.shields.io/github/license/xmonad/xmonad"></a>
|
||||
<a href="https://haskell.org/"><img alt="Made in Haskell" src="https://img.shields.io/badge/Made%20in-Haskell-%235e5086?logo=haskell"></a>
|
||||
<br>
|
||||
<a href="https://github.com/xmonad/xmonad/actions/workflows/stack.yml"><img alt="Stack" src="https://img.shields.io/github/actions/workflow/status/xmonad/xmonad/stack.yml?label=Stack&logo=githubactions&logoColor=white"></a>
|
||||
<a href="https://github.com/xmonad/xmonad/actions/workflows/haskell-ci.yml"><img alt="Cabal" src="https://img.shields.io/github/actions/workflow/status/xmonad/xmonad/haskell-ci.yml?label=Cabal&logo=githubactions&logoColor=white"></a>
|
||||
<a href="https://github.com/xmonad/xmonad/actions/workflows/nix.yml"><img alt="Nix" src="https://img.shields.io/github/actions/workflow/status/xmonad/xmonad/nix.yml?label=Nix&logo=githubactions&logoColor=white"></a>
|
||||
<br>
|
||||
<a href="https://github.com/sponsors/xmonad"><img alt="GitHub Sponsors" src="https://img.shields.io/github/sponsors/xmonad?label=GitHub%20Sponsors&logo=githubsponsors"></a>
|
||||
<a href="https://opencollective.com/xmonad"><img alt="Open Collective" src="https://img.shields.io/opencollective/all/xmonad?label=Open%20Collective&logo=opencollective"></a>
|
||||
<br>
|
||||
<a href="https://web.libera.chat/#xmonad"><img alt="Chat on #xmonad@irc.libera.chat" src="https://img.shields.io/badge/%23%20chat-on%20libera-brightgreen"></a>
|
||||
<a href="https://matrix.to/#/#xmonad:matrix.org"><img alt="Chat on #xmonad:matrix.org" src="https://img.shields.io/matrix/xmonad:matrix.org?logo=matrix"></a>
|
||||
</p>
|
||||
|
||||
[](https://travis-ci.org/xmonad/xmonad)
|
||||
# xmonad
|
||||
|
||||
[xmonad][] is a tiling window manager for X. Windows are arranged
|
||||
**A tiling window manager for X11.**
|
||||
|
||||
[XMonad][web:xmonad] is a tiling window manager for X11. Windows are arranged
|
||||
automatically to tile the screen without gaps or overlap, maximising
|
||||
screen use. Window manager features are accessible from the keyboard:
|
||||
a mouse is optional. xmonad is written, configured and extensible in
|
||||
@@ -12,118 +31,74 @@ dynamically, and different layouts may be used on each
|
||||
workspace. Xinerama is fully supported, allowing windows to be tiled
|
||||
on several physical screens.
|
||||
|
||||
## Quick Start
|
||||
This repository contains the [xmonad][hackage:xmonad] package, a minimal,
|
||||
stable, yet extensible core. It is accompanied by
|
||||
[xmonad-contrib][gh:xmonad-contrib], a library of hundreds of additional
|
||||
community-maintained tiling algorithms and extension modules. The two combined
|
||||
make for a powerful X11 window-manager with endless customization
|
||||
possibilities. They are, quite literally, libraries for creating your own
|
||||
window manager.
|
||||
|
||||
* From hackage:
|
||||
## Installation
|
||||
|
||||
cabal update
|
||||
cabal install xmonad xmonad-contrib
|
||||
For installation and configuration instructions, please see:
|
||||
|
||||
* Alternatively, build from source using the following repositories:
|
||||
* [downloading and installing xmonad][web:download]
|
||||
* [installing latest xmonad snapshot from git][web:install]
|
||||
* [configuring xmonad][web:tutorial]
|
||||
|
||||
- <https://github.com/xmonad/xmonad>
|
||||
If you run into any trouble, consult our [documentation][web:documentation] or
|
||||
ask the [community][web:community] for help.
|
||||
|
||||
- <https://github.com/xmonad/xmonad-contrib>
|
||||
## Contributing
|
||||
|
||||
For the full story, read on.
|
||||
We welcome all forms of contributions:
|
||||
|
||||
## Building
|
||||
* [bug reports and feature ideas][gh:xmonad:issues]
|
||||
(also to [xmonad-contrib][gh:xmonad-contrib:issues])
|
||||
* [bug fixes, new features, new extensions][gh:xmonad:pulls]
|
||||
(usually to [xmonad-contrib][gh:xmonad-contrib:pulls])
|
||||
* documentation fixes and improvements: [xmonad][gh:xmonad],
|
||||
[xmonad-contrib][gh:xmonad-contrib], [xmonad-web][gh:xmonad-web]
|
||||
* helping others in the [community][web:community]
|
||||
* financial support: [GitHub Sponsors][gh:xmonad:sponsors],
|
||||
[Open Collective][opencollective:xmonad]
|
||||
|
||||
Building is quite straightforward, and requires a basic Haskell toolchain.
|
||||
On many systems xmonad is available as a binary package in your
|
||||
package system (e.g. on Debian or Gentoo). If at all possible, use this
|
||||
in preference to a source build, as the dependency resolution will be
|
||||
simpler.
|
||||
|
||||
We'll now walk through the complete list of toolchain dependencies.
|
||||
|
||||
* GHC: the Glasgow Haskell Compiler
|
||||
|
||||
You first need a Haskell compiler. Your distribution's package
|
||||
system will have binaries of GHC (the Glasgow Haskell Compiler),
|
||||
the compiler we use, so install that first. If your operating
|
||||
system's package system doesn't provide a binary version of GHC
|
||||
and the `cabal-install` tool, you can install both using the
|
||||
[Haskell Platform][platform].
|
||||
|
||||
It shouldn't be necessary to compile GHC from source -- every common
|
||||
system has a pre-build binary version. However, if you want to
|
||||
build from source, the following links will be helpful:
|
||||
|
||||
- GHC: <http://haskell.org/ghc/>
|
||||
|
||||
- Cabal: <http://haskell.org/cabal/download.html>
|
||||
|
||||
* X11 libraries:
|
||||
|
||||
Since you're building an X application, you'll need the C X11
|
||||
library headers. On many platforms, these come pre-installed. For
|
||||
others, such as Debian, you can get them from your package manager:
|
||||
|
||||
# for xmonad
|
||||
$ apt-get install libx11-dev libxinerama-dev libxext-dev libxrandr-dev libxss-dev
|
||||
|
||||
# for xmonad-contrib
|
||||
$ apt-get install libxft-dev
|
||||
|
||||
Then build and install with:
|
||||
|
||||
$ cabal install
|
||||
|
||||
## Running xmonad
|
||||
|
||||
If you built XMonad using `cabal` then add:
|
||||
|
||||
exec $HOME/.cabal/bin/xmonad
|
||||
|
||||
to the last line of your `.xsession` or `.xinitrc` file.
|
||||
|
||||
## Configuring
|
||||
|
||||
See the [CONFIG][] document and the [example configuration file][example-config].
|
||||
|
||||
## XMonadContrib
|
||||
|
||||
There are many extensions to xmonad available in the XMonadContrib
|
||||
(xmc) library. Examples include an ion3-like tabbed layout, a
|
||||
prompt/program launcher, and various other useful modules.
|
||||
XMonadContrib is available at:
|
||||
|
||||
* Latest release: <http://hackage.haskell.org/package/xmonad-contrib>
|
||||
|
||||
* Git version: <https://github.com/xmonad/xmonad-contrib>
|
||||
|
||||
## Other Useful Programs
|
||||
|
||||
A nicer xterm replacement, that supports resizing better:
|
||||
|
||||
* urxvt: <http://software.schmorp.de/pkg/rxvt-unicode.html>
|
||||
|
||||
For custom status bars:
|
||||
|
||||
* xmobar: <http://hackage.haskell.org/package/xmobar>
|
||||
|
||||
* taffybar: <https://github.com/travitch/taffybar>
|
||||
|
||||
* dzen: <http://gotmor.googlepages.com/dzen>
|
||||
|
||||
For a program dispatch menu:
|
||||
|
||||
* [XMonad.Prompt.Shell][xmc-prompt-shell]: (from [XMonadContrib][])
|
||||
|
||||
* dmenu: <http://www.suckless.org/download/>
|
||||
|
||||
* gmrun: (in your package system)
|
||||
Please do read the [CONTRIBUTING][gh:xmonad:contributing] document for more
|
||||
information about bug reporting and code contributions. For a brief overview
|
||||
of the architecture and code conventions, see the [documentation for the
|
||||
`XMonad.Doc.Developing` module][doc:developing]. If in doubt, [talk to
|
||||
us][web:community].
|
||||
|
||||
## Authors
|
||||
|
||||
* Spencer Janssen
|
||||
* Don Stewart
|
||||
* Jason Creighton
|
||||
Started in 2007 by [Spencer Janssen][gh:spencerjanssen], [Don
|
||||
Stewart][gh:donsbot] and [Jason Creighton][gh:JasonCreighton], the
|
||||
[XMonad][web:xmonad] project lives on thanks to [new generations of
|
||||
maintainers][gh:xmonad:maintainers] and [dozens of
|
||||
contributors][gh:xmonad:contributors].
|
||||
|
||||
[xmonad]: http://xmonad.org
|
||||
[xmonadcontrib]: https://hackage.haskell.org/package/xmonad-contrib
|
||||
[xmc-prompt-shell]: https://hackage.haskell.org/package/xmonad-contrib/docs/XMonad-Prompt-Shell.html
|
||||
[platform]: http://haskell.org/platform/
|
||||
[example-config]: https://github.com/xmonad/xmonad-testing/blob/master/example-config.hs
|
||||
[config]: https://github.com/xmonad/xmonad/blob/master/CONFIG
|
||||
[gh:spencerjanssen]: https://github.com/spencerjanssen
|
||||
[gh:donsbot]: https://github.com/donsbot
|
||||
[gh:JasonCreighton]: https://github.com/JasonCreighton
|
||||
|
||||
[doc:developing]: https://xmonad.github.io/xmonad-docs/xmonad-contrib/XMonad-Doc-Developing.html
|
||||
[gh:xmonad-contrib:issues]: https://github.com/xmonad/xmonad-contrib/issues
|
||||
[gh:xmonad-contrib:pulls]: https://github.com/xmonad/xmonad-contrib/pulls
|
||||
[gh:xmonad-contrib]: https://github.com/xmonad/xmonad-contrib
|
||||
[gh:xmonad-web]: https://github.com/xmonad/xmonad-web
|
||||
[gh:xmonad:contributing]: https://github.com/xmonad/xmonad/blob/master/CONTRIBUTING.md
|
||||
[gh:xmonad:contributors]: https://github.com/xmonad/xmonad/graphs/contributors
|
||||
[gh:xmonad:issues]: https://github.com/xmonad/xmonad/issues
|
||||
[gh:xmonad:maintainers]: https://github.com/xmonad/xmonad/blob/master/MAINTAINERS.md
|
||||
[gh:xmonad:pulls]: https://github.com/xmonad/xmonad/pulls
|
||||
[gh:xmonad:sponsors]: https://github.com/sponsors/xmonad
|
||||
[gh:xmonad]: https://github.com/xmonad/xmonad
|
||||
[hackage:xmonad]: https://hackage.haskell.org/package/xmonad
|
||||
[opencollective:xmonad]: https://opencollective.com/xmonad
|
||||
[web:community]: https://xmonad.org/community.html
|
||||
[web:documentation]: https://xmonad.org/documentation.html
|
||||
[web:download]: https://xmonad.org/download.html
|
||||
[web:install]: https://xmonad.org/INSTALL.html
|
||||
[web:tutorial]: https://xmonad.org/TUTORIAL.html
|
||||
[web:xmonad]: https://xmonad.org/
|
||||
|
22
STYLE
22
STYLE
@@ -1,22 +0,0 @@
|
||||
|
||||
== Coding guidelines for contributing to
|
||||
== xmonad and the xmonad contributed extensions
|
||||
|
||||
* Comment every top level function (particularly exported functions), and
|
||||
provide a type signature; use Haddock syntax in the comments.
|
||||
|
||||
* Follow the coding style of the other modules.
|
||||
|
||||
* Code should be compilable with -Wall -Werror -fno-warn-unused-do-bind -fwarn-tabs.
|
||||
There should be no warnings.
|
||||
|
||||
* Partial functions should be avoided: the window manager should not
|
||||
crash, so do not call `error` or `undefined`
|
||||
|
||||
* Use 4 spaces for indenting.
|
||||
|
||||
* Any pure function added to the core should have QuickCheck properties
|
||||
precisely defining its behavior.
|
||||
|
||||
* New modules should identify the author, and be submitted under
|
||||
the same license as xmonad (BSD3 license or freer).
|
1325
TUTORIAL.md
Normal file
1325
TUTORIAL.md
Normal file
File diff suppressed because it is too large
Load Diff
17
cabal.haskell-ci
Normal file
17
cabal.haskell-ci
Normal file
@@ -0,0 +1,17 @@
|
||||
apt:
|
||||
libx11-dev
|
||||
libxext-dev
|
||||
libxinerama-dev
|
||||
libxrandr-dev
|
||||
libxss-dev
|
||||
|
||||
github-patches:
|
||||
.github/workflows/haskell-ci-hackage.patch
|
||||
|
||||
raw-project
|
||||
optimization: False
|
||||
package xmonad
|
||||
flags: +pedantic
|
||||
|
||||
-- avoid --haddock-all which overwrites *-docs.tar.gz with tests docs
|
||||
haddock-components: libs
|
@@ -1 +1,4 @@
|
||||
packages: ./
|
||||
-- cabal.project
|
||||
|
||||
packages:
|
||||
xmonad.cabal
|
||||
|
106
flake.nix
Normal file
106
flake.nix
Normal file
@@ -0,0 +1,106 @@
|
||||
# This file is maintained by @IvanMalison and @LSLeary (github)
|
||||
# See xmonad-contrib/NIX.md for an overview of module usage.
|
||||
{
|
||||
inputs = {
|
||||
flake-utils.url = github:numtide/flake-utils;
|
||||
git-ignore-nix.url = github:hercules-ci/gitignore.nix/master;
|
||||
unstable.url = github:NixOS/nixpkgs/nixos-unstable;
|
||||
};
|
||||
outputs = { self, flake-utils, nixpkgs, unstable, git-ignore-nix }:
|
||||
let
|
||||
hpath = { prefix ? null, compiler ? null }:
|
||||
(if prefix == null then [] else [ prefix ]) ++
|
||||
(if compiler == null
|
||||
then [ "haskellPackages" ]
|
||||
else [ "haskell" "packages" compiler ]
|
||||
);
|
||||
fromHOL = hol: comp: final: prev: with prev.lib; with attrsets;
|
||||
let
|
||||
path = hpath comp;
|
||||
root = head path;
|
||||
branch = tail path;
|
||||
hpkgs' = (getAttrFromPath path prev).override (old: {
|
||||
overrides = composeExtensions (old.overrides or (_: _: {}))
|
||||
(hol final prev);
|
||||
});
|
||||
in {
|
||||
${root} = recursiveUpdate prev.${root} (setAttrByPath branch hpkgs');
|
||||
};
|
||||
hoverlay = final: prev: hself: hsuper:
|
||||
with prev.haskell.lib.compose; {
|
||||
xmonad = hself.callCabal2nix "xmonad"
|
||||
(git-ignore-nix.lib.gitignoreSource ./.) { };
|
||||
};
|
||||
defComp = if builtins.pathExists ./comp.nix
|
||||
then import ./comp.nix
|
||||
else { };
|
||||
overlay = fromHOL hoverlay defComp;
|
||||
overlays = [ overlay ];
|
||||
nixosModule = { config, pkgs, lib, ... }: with lib; with attrsets;
|
||||
let
|
||||
cfg = config.services.xserver.windowManager.xmonad.flake;
|
||||
comp = { inherit (cfg) prefix compiler; };
|
||||
in {
|
||||
options = {
|
||||
services.xserver.windowManager.xmonad.flake = with types; {
|
||||
enable = mkEnableOption "flake";
|
||||
prefix = mkOption {
|
||||
default = null;
|
||||
type = nullOr string;
|
||||
example = literalExpression "\"unstable\"";
|
||||
description = ''
|
||||
Specify a nested alternative <literal>pkgs</literal> by attrName.
|
||||
'';
|
||||
};
|
||||
compiler = mkOption {
|
||||
default = null;
|
||||
type = nullOr string;
|
||||
example = literalExpression "\"ghc922\"";
|
||||
description = ''
|
||||
Which compiler to build xmonad with.
|
||||
Must be an attribute of <literal>pkgs.haskell.packages</literal>.
|
||||
Sets <option>xmonad.haskellPackages</option> to match.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
config = mkIf cfg.enable {
|
||||
nixpkgs.overlays = [ (fromHOL hoverlay comp) ];
|
||||
services.xserver.windowManager.xmonad.haskellPackages =
|
||||
getAttrFromPath (hpath comp) pkgs;
|
||||
};
|
||||
};
|
||||
nixosModules = [ nixosModule ];
|
||||
in flake-utils.lib.eachDefaultSystem (system:
|
||||
let pkgs = import nixpkgs { inherit system overlays; };
|
||||
hpkg = pkgs.lib.attrsets.getAttrFromPath (hpath defComp) pkgs;
|
||||
modifyDevShell =
|
||||
if builtins.pathExists ./develop.nix
|
||||
then import ./develop.nix
|
||||
else _: x: x;
|
||||
in
|
||||
rec {
|
||||
devShell = hpkg.shellFor (modifyDevShell pkgs {
|
||||
packages = p: [ p.xmonad ];
|
||||
});
|
||||
defaultPackage = hpkg.xmonad;
|
||||
# An auxiliary NixOS module that modernises the standard xmonad NixOS module
|
||||
# and wrapper script used, replacing them with versions from unstable.
|
||||
# Currently, due to the NIX_GHC --> XMONAD_GHC env var change, this is
|
||||
# necessary in order for Mod-q recompilation to work out-of-the-box.
|
||||
modernise =
|
||||
let
|
||||
xmonadModFile = "services/x11/window-managers/xmonad.nix";
|
||||
unpkgs = import unstable { inherit system; };
|
||||
replaceWrapper = _: _:
|
||||
{ xmonad-with-packages = unpkgs.xmonad-with-packages; };
|
||||
in {
|
||||
disabledModules = [ xmonadModFile ];
|
||||
imports = [ (unstable + "/nixos/modules/" + xmonadModFile) ];
|
||||
nixpkgs.overlays = [ replaceWrapper ];
|
||||
};
|
||||
}) // {
|
||||
inherit hoverlay overlay overlays nixosModule nixosModules;
|
||||
lib = { inherit hpath fromHOL; };
|
||||
};
|
||||
}
|
11
man/Makefile
Normal file
11
man/Makefile
Normal file
@@ -0,0 +1,11 @@
|
||||
.PHONY: all
|
||||
all: xmonad.1 xmonad.1.html
|
||||
|
||||
xmonad.1.markdown: xmonad.1.markdown.in
|
||||
(cd .. && util/GenerateManpage.hs) <$< >$@
|
||||
|
||||
xmonad.1: xmonad.1.markdown
|
||||
pandoc --from=markdown --to=man --standalone --output=$@ $<
|
||||
|
||||
xmonad.1.html: xmonad.1.markdown
|
||||
pandoc --from=markdown --to=html --standalone --table-of-contents --output=$@ $<
|
232
man/xmonad.1
232
man/xmonad.1
@@ -1,13 +1,13 @@
|
||||
.\" Automatically generated by Pandoc 2.2.1
|
||||
.\" Automatically generated by Pandoc 2.9.2.1
|
||||
.\"
|
||||
.TH "XMONAD" "1" "20 August 2018" "Tiling Window Manager" ""
|
||||
.TH "XMONAD" "1" "27 October 2021" "Tiling Window Manager" ""
|
||||
.hy
|
||||
.SH Name
|
||||
.PP
|
||||
xmonad \- Tiling Window Manager
|
||||
xmonad - Tiling Window Manager
|
||||
.SH Description
|
||||
.PP
|
||||
\f[I]xmonad\f[] is a minimalist tiling window manager for X, written in
|
||||
\f[I]xmonad\f[R] is a minimalist tiling window manager for X, written in
|
||||
Haskell.
|
||||
Windows are managed using automatic layout algorithms, which can be
|
||||
dynamically reconfigured.
|
||||
@@ -15,14 +15,14 @@ At any time windows are arranged so as to maximize the use of screen
|
||||
real estate.
|
||||
All features of the window manager are accessible purely from the
|
||||
keyboard: a mouse is entirely optional.
|
||||
\f[I]xmonad\f[] is configured in Haskell, and custom layout algorithms
|
||||
\f[I]xmonad\f[R] is configured in Haskell, and custom layout algorithms
|
||||
may be implemented by the user in config files.
|
||||
A principle of \f[I]xmonad\f[] is predictability: the user should know
|
||||
A principle of \f[I]xmonad\f[R] is predictability: the user should know
|
||||
in advance precisely the window arrangement that will result from any
|
||||
action.
|
||||
.PP
|
||||
By default, \f[I]xmonad\f[] provides three layout algorithms: tall, wide
|
||||
and fullscreen.
|
||||
By default, \f[I]xmonad\f[R] provides three layout algorithms: tall,
|
||||
wide and fullscreen.
|
||||
In tall or wide mode, windows are tiled and arranged to prevent overlap
|
||||
and maximize screen use.
|
||||
Sets of windows are grouped together on virtual screens, and each screen
|
||||
@@ -31,34 +31,34 @@ Multiple physical monitors are supported via Xinerama, allowing
|
||||
simultaneous display of a number of screens.
|
||||
.PP
|
||||
By utilizing the expressivity of a modern functional language with a
|
||||
rich static type system, \f[I]xmonad\f[] provides a complete, featureful
|
||||
window manager in less than 1200 lines of code, with an emphasis on
|
||||
correctness and robustness.
|
||||
rich static type system, \f[I]xmonad\f[R] provides a complete,
|
||||
featureful window manager in less than 1200 lines of code, with an
|
||||
emphasis on correctness and robustness.
|
||||
Internal properties of the window manager are checked using a
|
||||
combination of static guarantees provided by the type system, and
|
||||
type\-based automated testing.
|
||||
type-based automated testing.
|
||||
A benefit of this is that the code is simple to understand, and easy to
|
||||
modify.
|
||||
.SH Usage
|
||||
.PP
|
||||
\f[I]xmonad\f[] places each window into a \[lq]workspace\[rq].
|
||||
\f[I]xmonad\f[R] places each window into a \[lq]workspace\[rq].
|
||||
Each workspace can have any number of windows, which you can cycle
|
||||
though with mod\-j and mod\-k.
|
||||
though with mod-j and mod-k.
|
||||
Windows are either displayed full screen, tiled horizontally, or tiled
|
||||
vertically.
|
||||
You can toggle the layout mode with mod\-space, which will cycle through
|
||||
You can toggle the layout mode with mod-space, which will cycle through
|
||||
the available modes.
|
||||
.PP
|
||||
You can switch to workspace N with mod\-N.
|
||||
For example, to switch to workspace 5, you would press mod\-5.
|
||||
You can switch to workspace N with mod-N.
|
||||
For example, to switch to workspace 5, you would press mod-5.
|
||||
Similarly, you can move the current window to another workspace with
|
||||
mod\-shift\-N.
|
||||
mod-shift-N.
|
||||
.PP
|
||||
When running with multiple monitors (Xinerama), each screen has exactly
|
||||
1 workspace visible.
|
||||
mod\-{w,e,r} switch the focus between screens, while shift\-mod\-{w,e,r}
|
||||
mod-{w,e,r} switch the focus between screens, while shift-mod-{w,e,r}
|
||||
move the current window to that screen.
|
||||
When \f[I]xmonad\f[] starts, workspace 1 is on screen 1, workspace 2 is
|
||||
When \f[I]xmonad\f[R] starts, workspace 1 is on screen 1, workspace 2 is
|
||||
on screen 2, etc.
|
||||
When switching workspaces to one that is already visible, the current
|
||||
and visible workspaces are swapped.
|
||||
@@ -67,221 +67,159 @@ and visible workspaces are swapped.
|
||||
xmonad has several flags which you may pass to the executable.
|
||||
These flags are:
|
||||
.TP
|
||||
.B \[en]recompile
|
||||
Recompiles your configuration in \f[I]~/.xmonad/xmonad.hs\f[]
|
||||
.RS
|
||||
.RE
|
||||
\[en]recompile
|
||||
Recompiles your \f[I]xmonad.hs\f[R] configuration
|
||||
.TP
|
||||
.B \[en]restart
|
||||
Causes the currently running \f[I]xmonad\f[] process to restart
|
||||
.RS
|
||||
.RE
|
||||
\[en]restart
|
||||
Causes the currently running \f[I]xmonad\f[R] process to restart
|
||||
.TP
|
||||
.B \[en]replace
|
||||
\[en]replace
|
||||
Replace the current window manager with xmonad
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \[en]version
|
||||
Display version of \f[I]xmonad\f[]
|
||||
.RS
|
||||
.RE
|
||||
\[en]version
|
||||
Display version of \f[I]xmonad\f[R]
|
||||
.TP
|
||||
.B \[en]verbose\-version
|
||||
Display detailed version of \f[I]xmonad\f[]
|
||||
.RS
|
||||
.RE
|
||||
.PP
|
||||
##Default keyboard bindings
|
||||
\[en]verbose-version
|
||||
Display detailed version of \f[I]xmonad\f[R]
|
||||
.SS Default keyboard bindings
|
||||
.TP
|
||||
.B mod\-shift\-return
|
||||
mod-shift-return
|
||||
Launch terminal
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-p
|
||||
mod-p
|
||||
Launch dmenu
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-shift\-p
|
||||
mod-shift-p
|
||||
Launch gmrun
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-shift\-c
|
||||
mod-shift-c
|
||||
Close the focused window
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-space
|
||||
mod-space
|
||||
Rotate through the available layout algorithms
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-shift\-space
|
||||
mod-shift-space
|
||||
Reset the layouts on the current workspace to default
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-n
|
||||
mod-n
|
||||
Resize viewed windows to the correct size
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-tab
|
||||
mod-tab
|
||||
Move focus to the next window
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-shift\-tab
|
||||
mod-shift-tab
|
||||
Move focus to the previous window
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-j
|
||||
mod-j
|
||||
Move focus to the next window
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-k
|
||||
mod-k
|
||||
Move focus to the previous window
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-m
|
||||
mod-m
|
||||
Move focus to the master window
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-return
|
||||
mod-return
|
||||
Swap the focused window and the master window
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-shift\-j
|
||||
mod-shift-j
|
||||
Swap the focused window with the next window
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-shift\-k
|
||||
mod-shift-k
|
||||
Swap the focused window with the previous window
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-h
|
||||
mod-h
|
||||
Shrink the master area
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-l
|
||||
mod-l
|
||||
Expand the master area
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-t
|
||||
mod-t
|
||||
Push window back into tiling
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-comma
|
||||
mod-comma
|
||||
Increment the number of windows in the master area
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-period
|
||||
mod-period
|
||||
Deincrement the number of windows in the master area
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-shift\-q
|
||||
mod-shift-q
|
||||
Quit xmonad
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-q
|
||||
mod-q
|
||||
Restart xmonad
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-shift\-slash
|
||||
mod-shift-slash
|
||||
Run xmessage with a summary of the default keybindings (useful for
|
||||
beginners)
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-question
|
||||
mod-question
|
||||
Run xmessage with a summary of the default keybindings (useful for
|
||||
beginners)
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-[1..9]
|
||||
mod-[1..9]
|
||||
Switch to workspace N
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-shift\-[1..9]
|
||||
mod-shift-[1..9]
|
||||
Move client to workspace N
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-{w,e,r}
|
||||
mod-{w,e,r}
|
||||
Switch to physical/Xinerama screens 1, 2, or 3
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-shift\-{w,e,r}
|
||||
mod-shift-{w,e,r}
|
||||
Move client to screen 1, 2, or 3
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-button1
|
||||
mod-button1
|
||||
Set the window to floating mode and move by dragging
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-button2
|
||||
mod-button2
|
||||
Raise the window to the top of the stack
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-button3
|
||||
mod-button3
|
||||
Set the window to floating mode and resize by dragging
|
||||
.RS
|
||||
.RE
|
||||
.SH Examples
|
||||
.PP
|
||||
To use xmonad as your window manager add to your \f[I]~/.xinitrc\f[]
|
||||
file:
|
||||
To use xmonad as your window manager add to your
|
||||
\f[I]\[ti]/.xinitrc\f[R] file:
|
||||
.RS
|
||||
.PP
|
||||
exec xmonad
|
||||
.RE
|
||||
.SH Customization
|
||||
.PP
|
||||
xmonad is customized in ~/.xmonad/xmonad.hs, and then restarted with
|
||||
mod\-q.
|
||||
xmonad is customized in your \f[I]xmonad.hs\f[R], and then restarted
|
||||
with mod-q.
|
||||
You can choose where your configuration file lives by
|
||||
.IP "1." 3
|
||||
Setting \f[C]XMONAD_DATA_DIR,\f[R] \f[C]XMONAD_CONFIG_DIR\f[R], and
|
||||
\f[C]XMONAD_CACHE_DIR\f[R]; \f[I]xmonad.hs\f[R] is then expected to be
|
||||
in \f[C]XMONAD_CONFIG_DIR\f[R].
|
||||
.IP "2." 3
|
||||
Creating \f[I]xmonad.hs\f[R] in \f[I]\[ti]/.xmonad\f[R].
|
||||
.IP "3." 3
|
||||
Creating \f[I]xmonad.hs\f[R] in \f[C]XDG_CONFIG_HOME\f[R].
|
||||
Note that, in this case, xmonad will use \f[C]XDG_DATA_HOME\f[R] and
|
||||
\f[C]XDG_CACHE_HOME\f[R] for its data and cache directory respectively.
|
||||
.PP
|
||||
You can find many extensions to the core feature set in the xmonad\-
|
||||
You can find many extensions to the core feature set in the xmonad-
|
||||
contrib package, available through your package manager or from
|
||||
xmonad.org (http://xmonad.org).
|
||||
xmonad.org (https://xmonad.org).
|
||||
.SS Modular Configuration
|
||||
.PP
|
||||
As of \f[I]xmonad\-0.9\f[], any additional Haskell modules may be placed
|
||||
in \f[I]~/.xmonad/lib/\f[] are available in GHC's searchpath.
|
||||
As of \f[I]xmonad-0.9\f[R], any additional Haskell modules may be placed
|
||||
in \f[I]\[ti]/.xmonad/lib/\f[R] are available in GHC\[cq]s searchpath.
|
||||
Hierarchical modules are supported: for example, the file
|
||||
\f[I]~/.xmonad/lib/XMonad/Stack/MyAdditions.hs\f[] could contain:
|
||||
\f[I]\[ti]/.xmonad/lib/XMonad/Stack/MyAdditions.hs\f[R] could contain:
|
||||
.IP
|
||||
.nf
|
||||
\f[C]
|
||||
module\ XMonad.Stack.MyAdditions\ (function1)\ where
|
||||
\ \ function1\ =\ error\ "function1:\ Not\ implemented\ yet!"
|
||||
\f[]
|
||||
module XMonad.Stack.MyAdditions (function1) where
|
||||
function1 = error \[dq]function1: Not implemented yet!\[dq]
|
||||
\f[R]
|
||||
.fi
|
||||
.PP
|
||||
Your xmonad.hs may then import XMonad.Stack.MyAdditions as if that
|
||||
module was contained within xmonad or xmonad\-contrib.
|
||||
module was contained within xmonad or xmonad-contrib.
|
||||
.SH Bugs
|
||||
.PP
|
||||
Probably.
|
||||
|
@@ -5,96 +5,97 @@
|
||||
<meta name="generator" content="pandoc" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
|
||||
<meta name="author" content="" />
|
||||
<meta name="dcterms.date" content="2018-08-20" />
|
||||
<meta name="dcterms.date" content="2021-10-27" />
|
||||
<title>XMONAD(1) Tiling Window Manager</title>
|
||||
<style type="text/css">
|
||||
code{white-space: pre-wrap;}
|
||||
span.smallcaps{font-variant: small-caps;}
|
||||
span.underline{text-decoration: underline;}
|
||||
div.column{display: inline-block; vertical-align: top; width: 50%;}
|
||||
<style>
|
||||
code{white-space: pre-wrap;}
|
||||
span.smallcaps{font-variant: small-caps;}
|
||||
span.underline{text-decoration: underline;}
|
||||
div.column{display: inline-block; vertical-align: top; width: 50%;}
|
||||
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
|
||||
ul.task-list{list-style: none;}
|
||||
pre > code.sourceCode { white-space: pre; position: relative; }
|
||||
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
|
||||
pre > code.sourceCode > span:empty { height: 1.2em; }
|
||||
code.sourceCode > span { color: inherit; text-decoration: inherit; }
|
||||
div.sourceCode { margin: 1em 0; }
|
||||
pre.sourceCode { margin: 0; }
|
||||
@media screen {
|
||||
div.sourceCode { overflow: auto; }
|
||||
}
|
||||
@media print {
|
||||
pre > code.sourceCode { white-space: pre-wrap; }
|
||||
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
|
||||
}
|
||||
pre.numberSource code
|
||||
{ counter-reset: source-line 0; }
|
||||
pre.numberSource code > span
|
||||
{ position: relative; left: -4em; counter-increment: source-line; }
|
||||
pre.numberSource code > span > a:first-child::before
|
||||
{ content: counter(source-line);
|
||||
position: relative; left: -1em; text-align: right; vertical-align: baseline;
|
||||
border: none; display: inline-block;
|
||||
-webkit-touch-callout: none; -webkit-user-select: none;
|
||||
-khtml-user-select: none; -moz-user-select: none;
|
||||
-ms-user-select: none; user-select: none;
|
||||
padding: 0 4px; width: 4em;
|
||||
color: #aaaaaa;
|
||||
}
|
||||
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
|
||||
div.sourceCode
|
||||
{ }
|
||||
@media screen {
|
||||
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
|
||||
}
|
||||
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
|
||||
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
|
||||
code span.at { color: #7d9029; } /* Attribute */
|
||||
code span.bn { color: #40a070; } /* BaseN */
|
||||
code span.bu { } /* BuiltIn */
|
||||
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
|
||||
code span.ch { color: #4070a0; } /* Char */
|
||||
code span.cn { color: #880000; } /* Constant */
|
||||
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
|
||||
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
|
||||
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
|
||||
code span.dt { color: #902000; } /* DataType */
|
||||
code span.dv { color: #40a070; } /* DecVal */
|
||||
code span.er { color: #ff0000; font-weight: bold; } /* Error */
|
||||
code span.ex { } /* Extension */
|
||||
code span.fl { color: #40a070; } /* Float */
|
||||
code span.fu { color: #06287e; } /* Function */
|
||||
code span.im { } /* Import */
|
||||
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
|
||||
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
|
||||
code span.op { color: #666666; } /* Operator */
|
||||
code span.ot { color: #007020; } /* Other */
|
||||
code span.pp { color: #bc7a00; } /* Preprocessor */
|
||||
code span.sc { color: #4070a0; } /* SpecialChar */
|
||||
code span.ss { color: #bb6688; } /* SpecialString */
|
||||
code span.st { color: #4070a0; } /* String */
|
||||
code span.va { color: #19177c; } /* Variable */
|
||||
code span.vs { color: #4070a0; } /* VerbatimString */
|
||||
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
|
||||
</style>
|
||||
<style type="text/css">
|
||||
a.sourceLine { display: inline-block; line-height: 1.25; }
|
||||
a.sourceLine { pointer-events: none; color: inherit; text-decoration: inherit; }
|
||||
a.sourceLine:empty { height: 1.2em; }
|
||||
.sourceCode { overflow: visible; }
|
||||
code.sourceCode { white-space: pre; position: relative; }
|
||||
div.sourceCode { margin: 1em 0; }
|
||||
pre.sourceCode { margin: 0; }
|
||||
@media screen {
|
||||
div.sourceCode { overflow: auto; }
|
||||
}
|
||||
@media print {
|
||||
code.sourceCode { white-space: pre-wrap; }
|
||||
a.sourceLine { text-indent: -1em; padding-left: 1em; }
|
||||
}
|
||||
pre.numberSource a.sourceLine
|
||||
{ position: relative; left: -4em; }
|
||||
pre.numberSource a.sourceLine::before
|
||||
{ content: attr(data-line-number);
|
||||
position: relative; left: -1em; text-align: right; vertical-align: baseline;
|
||||
border: none; pointer-events: all; display: inline-block;
|
||||
-webkit-touch-callout: none; -webkit-user-select: none;
|
||||
-khtml-user-select: none; -moz-user-select: none;
|
||||
-ms-user-select: none; user-select: none;
|
||||
padding: 0 4px; width: 4em;
|
||||
color: #aaaaaa;
|
||||
}
|
||||
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
|
||||
div.sourceCode
|
||||
{ }
|
||||
@media screen {
|
||||
a.sourceLine::before { text-decoration: underline; }
|
||||
}
|
||||
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
|
||||
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
|
||||
code span.at { color: #7d9029; } /* Attribute */
|
||||
code span.bn { color: #40a070; } /* BaseN */
|
||||
code span.bu { } /* BuiltIn */
|
||||
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
|
||||
code span.ch { color: #4070a0; } /* Char */
|
||||
code span.cn { color: #880000; } /* Constant */
|
||||
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
|
||||
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
|
||||
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
|
||||
code span.dt { color: #902000; } /* DataType */
|
||||
code span.dv { color: #40a070; } /* DecVal */
|
||||
code span.er { color: #ff0000; font-weight: bold; } /* Error */
|
||||
code span.ex { } /* Extension */
|
||||
code span.fl { color: #40a070; } /* Float */
|
||||
code span.fu { color: #06287e; } /* Function */
|
||||
code span.im { } /* Import */
|
||||
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
|
||||
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
|
||||
code span.op { color: #666666; } /* Operator */
|
||||
code span.ot { color: #007020; } /* Other */
|
||||
code span.pp { color: #bc7a00; } /* Preprocessor */
|
||||
code span.sc { color: #4070a0; } /* SpecialChar */
|
||||
code span.ss { color: #bb6688; } /* SpecialString */
|
||||
code span.st { color: #4070a0; } /* String */
|
||||
code span.va { color: #19177c; } /* Variable */
|
||||
code span.vs { color: #4070a0; } /* VerbatimString */
|
||||
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
|
||||
</style>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<header id="title-block-header">
|
||||
<h1 class="title">XMONAD(1) Tiling Window Manager</h1>
|
||||
<p class="author"></p>
|
||||
<p class="date">20 August 2018</p>
|
||||
<p class="date">27 October 2021</p>
|
||||
</header>
|
||||
<nav id="TOC">
|
||||
<nav id="TOC" role="doc-toc">
|
||||
<ul>
|
||||
<li><a href="#name">Name</a></li>
|
||||
<li><a href="#description">Description</a></li>
|
||||
<li><a href="#usage">Usage</a><ul>
|
||||
<li><a href="#usage">Usage</a>
|
||||
<ul>
|
||||
<li><a href="#flags">Flags</a></li>
|
||||
<li><a href="#default-keyboard-bindings">Default keyboard bindings</a></li>
|
||||
</ul></li>
|
||||
<li><a href="#examples">Examples</a></li>
|
||||
<li><a href="#customization">Customization</a><ul>
|
||||
<li><a href="#customization">Customization</a>
|
||||
<ul>
|
||||
<li><a href="#modular-configuration">Modular Configuration</a></li>
|
||||
</ul></li>
|
||||
<li><a href="#bugs">Bugs</a></li>
|
||||
@@ -114,7 +115,7 @@ code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warni
|
||||
<p>xmonad has several flags which you may pass to the executable. These flags are:</p>
|
||||
<dl>
|
||||
<dt>–recompile</dt>
|
||||
<dd>Recompiles your configuration in <em>~/.xmonad/xmonad.hs</em>
|
||||
<dd>Recompiles your <em>xmonad.hs</em> configuration
|
||||
</dd>
|
||||
<dt>–restart</dt>
|
||||
<dd>Causes the currently running <em>xmonad</em> process to restart
|
||||
@@ -129,7 +130,7 @@ code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warni
|
||||
<dd>Display detailed version of <em>xmonad</em>
|
||||
</dd>
|
||||
</dl>
|
||||
<p>##Default keyboard bindings</p>
|
||||
<h2 id="default-keyboard-bindings">Default keyboard bindings</h2>
|
||||
<dl>
|
||||
<dt>mod-shift-return</dt>
|
||||
<dd>Launch terminal
|
||||
@@ -231,12 +232,17 @@ code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warni
|
||||
<p>exec xmonad</p>
|
||||
</blockquote>
|
||||
<h1 id="customization">Customization</h1>
|
||||
<p>xmonad is customized in ~/.xmonad/xmonad.hs, and then restarted with mod-q.</p>
|
||||
<p>You can find many extensions to the core feature set in the xmonad- contrib package, available through your package manager or from <a href="http://xmonad.org">xmonad.org</a>.</p>
|
||||
<p>xmonad is customized in your <em>xmonad.hs</em>, and then restarted with mod-q. You can choose where your configuration file lives by</p>
|
||||
<ol type="1">
|
||||
<li>Setting <code>XMONAD_DATA_DIR,</code> <code>XMONAD_CONFIG_DIR</code>, and <code>XMONAD_CACHE_DIR</code>; <em>xmonad.hs</em> is then expected to be in <code>XMONAD_CONFIG_DIR</code>.</li>
|
||||
<li>Creating <em>xmonad.hs</em> in <em>~/.xmonad</em>.</li>
|
||||
<li>Creating <em>xmonad.hs</em> in <code>XDG_CONFIG_HOME</code>. Note that, in this case, xmonad will use <code>XDG_DATA_HOME</code> and <code>XDG_CACHE_HOME</code> for its data and cache directory respectively.</li>
|
||||
</ol>
|
||||
<p>You can find many extensions to the core feature set in the xmonad- contrib package, available through your package manager or from <a href="https://xmonad.org">xmonad.org</a>.</p>
|
||||
<h2 id="modular-configuration">Modular Configuration</h2>
|
||||
<p>As of <em>xmonad-0.9</em>, any additional Haskell modules may be placed in <em>~/.xmonad/lib/</em> are available in GHC’s searchpath. Hierarchical modules are supported: for example, the file <em>~/.xmonad/lib/XMonad/Stack/MyAdditions.hs</em> could contain:</p>
|
||||
<div class="sourceCode" id="cb1"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb1-1" data-line-number="1"><span class="kw">module</span> <span class="dt">XMonad.Stack.MyAdditions</span> (function1) <span class="kw">where</span></a>
|
||||
<a class="sourceLine" id="cb1-2" data-line-number="2"> function1 <span class="fu">=</span> error <span class="st">"function1: Not implemented yet!"</span></a></code></pre></div>
|
||||
<div class="sourceCode" id="cb1"><pre class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true"></a><span class="kw">module</span> <span class="dt">XMonad.Stack.MyAdditions</span> (function1) <span class="kw">where</span></span>
|
||||
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true"></a> function1 <span class="ot">=</span> <span class="fu">error</span> <span class="st">"function1: Not implemented yet!"</span></span></code></pre></div>
|
||||
<p>Your xmonad.hs may then import XMonad.Stack.MyAdditions as if that module was contained within xmonad or xmonad-contrib.</p>
|
||||
<h1 id="bugs">Bugs</h1>
|
||||
<p>Probably. If you find any, please report them to the <a href="https://github.com/xmonad/xmonad/issues">bugtracker</a></p>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
% XMONAD(1) Tiling Window Manager
|
||||
%
|
||||
% 20 August 2018
|
||||
% 27 October 2021
|
||||
|
||||
# Name
|
||||
|
||||
@@ -58,7 +58,7 @@ xmonad has several flags which you may pass to the executable.
|
||||
These flags are:
|
||||
|
||||
--recompile
|
||||
: Recompiles your configuration in _~/.xmonad/xmonad.hs_
|
||||
: Recompiles your _xmonad.hs_ configuration
|
||||
|
||||
--restart
|
||||
: Causes the currently running _xmonad_ process to restart
|
||||
@@ -72,9 +72,100 @@ These flags are:
|
||||
--verbose-version
|
||||
: Display detailed version of _xmonad_
|
||||
|
||||
##Default keyboard bindings
|
||||
## Default keyboard bindings
|
||||
|
||||
___KEYBINDINGS___
|
||||
mod-shift-return
|
||||
: Launch terminal
|
||||
|
||||
mod-p
|
||||
: Launch dmenu
|
||||
|
||||
mod-shift-p
|
||||
: Launch gmrun
|
||||
|
||||
mod-shift-c
|
||||
: Close the focused window
|
||||
|
||||
mod-space
|
||||
: Rotate through the available layout algorithms
|
||||
|
||||
mod-shift-space
|
||||
: Reset the layouts on the current workspace to default
|
||||
|
||||
mod-n
|
||||
: Resize viewed windows to the correct size
|
||||
|
||||
mod-tab
|
||||
: Move focus to the next window
|
||||
|
||||
mod-shift-tab
|
||||
: Move focus to the previous window
|
||||
|
||||
mod-j
|
||||
: Move focus to the next window
|
||||
|
||||
mod-k
|
||||
: Move focus to the previous window
|
||||
|
||||
mod-m
|
||||
: Move focus to the master window
|
||||
|
||||
mod-return
|
||||
: Swap the focused window and the master window
|
||||
|
||||
mod-shift-j
|
||||
: Swap the focused window with the next window
|
||||
|
||||
mod-shift-k
|
||||
: Swap the focused window with the previous window
|
||||
|
||||
mod-h
|
||||
: Shrink the master area
|
||||
|
||||
mod-l
|
||||
: Expand the master area
|
||||
|
||||
mod-t
|
||||
: Push window back into tiling
|
||||
|
||||
mod-comma
|
||||
: Increment the number of windows in the master area
|
||||
|
||||
mod-period
|
||||
: Deincrement the number of windows in the master area
|
||||
|
||||
mod-shift-q
|
||||
: Quit xmonad
|
||||
|
||||
mod-q
|
||||
: Restart xmonad
|
||||
|
||||
mod-shift-slash
|
||||
: Run xmessage with a summary of the default keybindings (useful for beginners)
|
||||
|
||||
mod-question
|
||||
: Run xmessage with a summary of the default keybindings (useful for beginners)
|
||||
|
||||
mod-[1..9]
|
||||
: Switch to workspace N
|
||||
|
||||
mod-shift-[1..9]
|
||||
: Move client to workspace N
|
||||
|
||||
mod-{w,e,r}
|
||||
: Switch to physical/Xinerama screens 1, 2, or 3
|
||||
|
||||
mod-shift-{w,e,r}
|
||||
: Move client to screen 1, 2, or 3
|
||||
|
||||
mod-button1
|
||||
: Set the window to floating mode and move by dragging
|
||||
|
||||
mod-button2
|
||||
: Raise the window to the top of the stack
|
||||
|
||||
mod-button3
|
||||
: Set the window to floating mode and resize by dragging
|
||||
|
||||
# Examples
|
||||
|
||||
@@ -83,8 +174,16 @@ To use xmonad as your window manager add to your _~/.xinitrc_ file:
|
||||
> exec xmonad
|
||||
|
||||
# Customization
|
||||
xmonad is customized in ~/.xmonad/xmonad.hs, and then restarted
|
||||
with mod-q.
|
||||
xmonad is customized in your _xmonad.hs_, and then restarted with mod-q.
|
||||
You can choose where your configuration file lives by
|
||||
|
||||
1. Setting `XMONAD_DATA_DIR,` `XMONAD_CONFIG_DIR`, and
|
||||
`XMONAD_CACHE_DIR`; _xmonad.hs_ is then expected to be in
|
||||
`XMONAD_CONFIG_DIR`.
|
||||
2. Creating _xmonad.hs_ in _~/.xmonad_.
|
||||
3. Creating _xmonad.hs_ in `XDG_CONFIG_HOME`. Note that, in this
|
||||
case, xmonad will use `XDG_DATA_HOME` and `XDG_CACHE_HOME` for its
|
||||
data and cache directory respectively.
|
||||
|
||||
You can find many extensions to the core feature set in the xmonad-
|
||||
contrib package, available through your package manager or from
|
||||
@@ -107,5 +206,5 @@ module was contained within xmonad or xmonad-contrib.
|
||||
# Bugs
|
||||
Probably. If you find any, please report them to the [bugtracker]
|
||||
|
||||
[xmonad.org]: http://xmonad.org
|
||||
[xmonad.org]: https://xmonad.org
|
||||
[bugtracker]: https://github.com/xmonad/xmonad/issues
|
||||
|
119
man/xmonad.1.markdown.in
Normal file
119
man/xmonad.1.markdown.in
Normal file
@@ -0,0 +1,119 @@
|
||||
% XMONAD(1) Tiling Window Manager
|
||||
%
|
||||
% 27 October 2021
|
||||
|
||||
# Name
|
||||
|
||||
xmonad - Tiling Window Manager
|
||||
|
||||
# Description
|
||||
|
||||
_xmonad_ is a minimalist tiling window manager for X, written in Haskell.
|
||||
Windows are managed using automatic layout algorithms, which can be
|
||||
dynamically reconfigured. At any time windows are arranged so as to
|
||||
maximize the use of screen real estate. All features of the window manager
|
||||
are accessible purely from the keyboard: a mouse is entirely optional.
|
||||
_xmonad_ is configured in Haskell, and custom layout algorithms may be
|
||||
implemented by the user in config files. A principle of _xmonad_ is
|
||||
predictability: the user should know in advance precisely the window
|
||||
arrangement that will result from any action.
|
||||
|
||||
By default, _xmonad_ provides three layout algorithms: tall, wide and
|
||||
fullscreen. In tall or wide mode, windows are tiled and arranged to prevent
|
||||
overlap and maximize screen use. Sets of windows are grouped together on
|
||||
virtual screens, and each screen retains its own layout, which may be
|
||||
reconfigured dynamically. Multiple physical monitors are supported via
|
||||
Xinerama, allowing simultaneous display of a number of screens.
|
||||
|
||||
By utilizing the expressivity of a modern functional language with a rich
|
||||
static type system, _xmonad_ provides a complete, featureful window manager
|
||||
in less than 1200 lines of code, with an emphasis on correctness and
|
||||
robustness. Internal properties of the window manager are checked using a
|
||||
combination of static guarantees provided by the type system, and
|
||||
type-based automated testing. A benefit of this is that the code is simple
|
||||
to understand, and easy to modify.
|
||||
|
||||
# Usage
|
||||
|
||||
_xmonad_ places each window into a "workspace". Each workspace can have
|
||||
any number of windows, which you can cycle though with mod-j and mod-k.
|
||||
Windows are either displayed full screen, tiled horizontally, or tiled
|
||||
vertically. You can toggle the layout mode with mod-space, which will cycle
|
||||
through the available modes.
|
||||
|
||||
You can switch to workspace N with mod-N. For example, to switch to
|
||||
workspace 5, you would press mod-5. Similarly, you can move the current
|
||||
window to another workspace with mod-shift-N.
|
||||
|
||||
When running with multiple monitors (Xinerama), each screen has exactly 1
|
||||
workspace visible. mod-{w,e,r} switch the focus between screens, while
|
||||
shift-mod-{w,e,r} move the current window to that screen. When _xmonad_
|
||||
starts, workspace 1 is on screen 1, workspace 2 is on screen 2, etc. When
|
||||
switching workspaces to one that is already visible, the current and
|
||||
visible workspaces are swapped.
|
||||
|
||||
## Flags
|
||||
|
||||
xmonad has several flags which you may pass to the executable.
|
||||
These flags are:
|
||||
|
||||
--recompile
|
||||
: Recompiles your _xmonad.hs_ configuration
|
||||
|
||||
--restart
|
||||
: Causes the currently running _xmonad_ process to restart
|
||||
|
||||
--replace
|
||||
: Replace the current window manager with xmonad
|
||||
|
||||
--version
|
||||
: Display version of _xmonad_
|
||||
|
||||
--verbose-version
|
||||
: Display detailed version of _xmonad_
|
||||
|
||||
## Default keyboard bindings
|
||||
|
||||
___KEYBINDINGS___
|
||||
|
||||
# Examples
|
||||
|
||||
To use xmonad as your window manager add to your _~/.xinitrc_ file:
|
||||
|
||||
> exec xmonad
|
||||
|
||||
# Customization
|
||||
xmonad is customized in your _xmonad.hs_, and then restarted with mod-q.
|
||||
You can choose where your configuration file lives by
|
||||
|
||||
1. Setting `XMONAD_DATA_DIR,` `XMONAD_CONFIG_DIR`, and
|
||||
`XMONAD_CACHE_DIR`; _xmonad.hs_ is then expected to be in
|
||||
`XMONAD_CONFIG_DIR`.
|
||||
2. Creating _xmonad.hs_ in _~/.xmonad_.
|
||||
3. Creating _xmonad.hs_ in `XDG_CONFIG_HOME`. Note that, in this
|
||||
case, xmonad will use `XDG_DATA_HOME` and `XDG_CACHE_HOME` for its
|
||||
data and cache directory respectively.
|
||||
|
||||
You can find many extensions to the core feature set in the xmonad-
|
||||
contrib package, available through your package manager or from
|
||||
[xmonad.org].
|
||||
|
||||
## Modular Configuration
|
||||
As of _xmonad-0.9_, any additional Haskell modules may be placed in
|
||||
_~/.xmonad/lib/_ are available in GHC's searchpath. Hierarchical modules
|
||||
are supported: for example, the file
|
||||
_~/.xmonad/lib/XMonad/Stack/MyAdditions.hs_ could contain:
|
||||
|
||||
```haskell
|
||||
module XMonad.Stack.MyAdditions (function1) where
|
||||
function1 = error "function1: Not implemented yet!"
|
||||
```
|
||||
|
||||
Your xmonad.hs may then import XMonad.Stack.MyAdditions as if that
|
||||
module was contained within xmonad or xmonad-contrib.
|
||||
|
||||
# Bugs
|
||||
Probably. If you find any, please report them to the [bugtracker]
|
||||
|
||||
[xmonad.org]: https://xmonad.org
|
||||
[bugtracker]: https://github.com/xmonad/xmonad/issues
|
@@ -123,13 +123,13 @@ myKeys conf@(XConfig {XMonad.modMask = modm}) = M.fromList $
|
||||
-- , ((modm , xK_b ), sendMessage ToggleStruts)
|
||||
|
||||
-- Quit xmonad
|
||||
, ((modm .|. shiftMask, xK_q ), io (exitWith ExitSuccess))
|
||||
, ((modm .|. shiftMask, xK_q ), io exitSuccess)
|
||||
|
||||
-- Restart xmonad
|
||||
, ((modm , xK_q ), spawn "xmonad --recompile; xmonad --restart")
|
||||
|
||||
-- Run xmessage with a summary of the default keybindings (useful for beginners)
|
||||
, ((modm .|. shiftMask, xK_slash ), spawn ("echo \"" ++ help ++ "\" | xmessage -file -"))
|
||||
, ((modm .|. shiftMask, xK_slash ), xmessage help)
|
||||
]
|
||||
++
|
||||
|
||||
@@ -154,18 +154,18 @@ myKeys conf@(XConfig {XMonad.modMask = modm}) = M.fromList $
|
||||
------------------------------------------------------------------------
|
||||
-- Mouse bindings: default actions bound to mouse events
|
||||
--
|
||||
myMouseBindings (XConfig {XMonad.modMask = modm}) = M.fromList $
|
||||
myMouseBindings (XConfig {XMonad.modMask = modm}) = M.fromList
|
||||
|
||||
-- mod-button1, Set the window to floating mode and move by dragging
|
||||
[ ((modm, button1), (\w -> focus w >> mouseMoveWindow w
|
||||
>> windows W.shiftMaster))
|
||||
[ ((modm, button1), \w -> focus w >> mouseMoveWindow w
|
||||
>> windows W.shiftMaster)
|
||||
|
||||
-- mod-button2, Raise the window to the top of the stack
|
||||
, ((modm, button2), (\w -> focus w >> windows W.shiftMaster))
|
||||
, ((modm, button2), \w -> focus w >> windows W.shiftMaster)
|
||||
|
||||
-- mod-button3, Set the window to floating mode and resize by dragging
|
||||
, ((modm, button3), (\w -> focus w >> mouseResizeWindow w
|
||||
>> windows W.shiftMaster))
|
||||
, ((modm, button3), \w -> focus w >> mouseResizeWindow w
|
||||
>> windows W.shiftMaster)
|
||||
|
||||
-- you may also bind events to the mouse scroll wheel (button4 and button5)
|
||||
]
|
||||
@@ -293,6 +293,7 @@ help = unlines ["The default modifier key is 'alt'. Default keybindings:",
|
||||
"mod-Space Rotate through the available layout algorithms",
|
||||
"mod-Shift-Space Reset the layouts on the current workSpace to default",
|
||||
"mod-n Resize/refresh viewed windows to the correct size",
|
||||
"mod-Shift-/ Show this help message with the default keybindings",
|
||||
"",
|
||||
"-- move focus up or down the window stack",
|
||||
"mod-Tab Move focus to the next window",
|
||||
|
@@ -1,5 +1,6 @@
|
||||
{-# OPTIONS_GHC -fno-warn-missing-signatures -fno-warn-orphans #-}
|
||||
{-# LANGUAGE TypeFamilies #-}
|
||||
{-# LANGUAGE TypeOperators #-}
|
||||
-----------------------------------------------------------------------------
|
||||
-- |
|
||||
-- Module : XMonad.Config
|
||||
@@ -39,7 +40,7 @@ import XMonad.Operations
|
||||
import XMonad.ManageHook
|
||||
import qualified XMonad.StackSet as W
|
||||
import Data.Bits ((.|.))
|
||||
import Data.Default
|
||||
import Data.Default.Class
|
||||
import Data.Monoid
|
||||
import qualified Data.Map as M
|
||||
import System.Exit
|
||||
@@ -218,7 +219,7 @@ keys conf@(XConfig {XMonad.modMask = modMask}) = M.fromList $
|
||||
, ((modMask , xK_period), sendMessage (IncMasterN (-1))) -- %! Deincrement the number of windows in the master area
|
||||
|
||||
-- quit, or restart
|
||||
, ((modMask .|. shiftMask, xK_q ), io (exitWith ExitSuccess)) -- %! Quit xmonad
|
||||
, ((modMask .|. shiftMask, xK_q ), io exitSuccess) -- %! Quit xmonad
|
||||
, ((modMask , xK_q ), spawn "if type xmonad; then xmonad --recompile && xmonad --restart; else xmessage xmonad not in \\$PATH: \"$PATH\"; fi") -- %! Restart xmonad
|
||||
|
||||
, ((modMask .|. shiftMask, xK_slash ), helpCommand) -- %! Run xmessage with a summary of the default keybindings (useful for beginners)
|
||||
@@ -239,7 +240,7 @@ keys conf@(XConfig {XMonad.modMask = modMask}) = M.fromList $
|
||||
, (f, m) <- [(W.view, 0), (W.shift, shiftMask)]]
|
||||
where
|
||||
helpCommand :: X ()
|
||||
helpCommand = spawn ("echo " ++ show help ++ " | xmessage -file -")
|
||||
helpCommand = xmessage help
|
||||
|
||||
-- | Mouse bindings: default actions bound to mouse events
|
||||
mouseBindings :: XConfig Layout -> M.Map (KeyMask, Button) (Window -> X ())
|
||||
@@ -277,6 +278,7 @@ instance (a ~ Choose Tall (Choose (Mirror Tall) Full)) => Default (XConfig a) wh
|
||||
, XMonad.handleExtraArgs = \ xs theConf -> case xs of
|
||||
[] -> return theConf
|
||||
_ -> fail ("unrecognized flags:" ++ show xs)
|
||||
, XMonad.extensibleConf = M.empty
|
||||
}
|
||||
|
||||
-- | The default set of configuration values itself
|
||||
@@ -296,6 +298,7 @@ help = unlines ["The default modifier key is 'alt'. Default keybindings:",
|
||||
"mod-Space Rotate through the available layout algorithms",
|
||||
"mod-Shift-Space Reset the layouts on the current workSpace to default",
|
||||
"mod-n Resize/refresh viewed windows to the correct size",
|
||||
"mod-Shift-/ Show this help message with the default keybindings",
|
||||
"",
|
||||
"-- move focus up or down the window stack",
|
||||
"mod-Tab Move focus to the next window",
|
||||
|
@@ -1,5 +1,13 @@
|
||||
{-# LANGUAGE ExistentialQuantification, FlexibleInstances, GeneralizedNewtypeDeriving,
|
||||
MultiParamTypeClasses, TypeSynonymInstances, DeriveDataTypeable #-}
|
||||
{-# LANGUAGE CPP #-}
|
||||
{-# LANGUAGE DeriveTraversable #-}
|
||||
{-# LANGUAGE ExistentialQuantification #-}
|
||||
{-# LANGUAGE FlexibleInstances #-}
|
||||
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
||||
{-# LANGUAGE LambdaCase #-}
|
||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||
{-# LANGUAGE NamedFieldPuns #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
{-# LANGUAGE DerivingVia #-}
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- |
|
||||
@@ -22,26 +30,32 @@ module XMonad.Core (
|
||||
XConf(..), XConfig(..), LayoutClass(..),
|
||||
Layout(..), readsLayout, Typeable, Message,
|
||||
SomeMessage(..), fromMessage, LayoutMessages(..),
|
||||
StateExtension(..), ExtensionClass(..),
|
||||
StateExtension(..), ExtensionClass(..), ConfExtension(..),
|
||||
runX, catchX, userCode, userCodeDef, io, catchIO, installSignalHandlers, uninstallSignalHandlers,
|
||||
withDisplay, withWindowSet, isRoot, runOnWorkspaces,
|
||||
getAtom, spawn, spawnPID, xfork, recompile, trace, whenJust, whenX,
|
||||
getXMonadDir, getXMonadCacheDir, getXMonadDataDir, stateFileName,
|
||||
getAtom, spawn, spawnPID, xfork, xmessage, recompile, trace, whenJust, whenX, ifM,
|
||||
getXMonadDir, getXMonadCacheDir, getXMonadDataDir, stateFileName, binFileName,
|
||||
atom_WM_STATE, atom_WM_PROTOCOLS, atom_WM_DELETE_WINDOW, atom_WM_TAKE_FOCUS, withWindowAttributes,
|
||||
ManageHook, Query(..), runQuery
|
||||
ManageHook, Query(..), runQuery, Directories'(..), Directories, getDirectories,
|
||||
) where
|
||||
|
||||
import XMonad.StackSet hiding (modify)
|
||||
|
||||
import Prelude
|
||||
import Control.Exception.Extensible (fromException, try, bracket, throw, finally, SomeException(..))
|
||||
import qualified Control.Exception.Extensible as E
|
||||
import Control.Applicative(Applicative, pure, (<$>), (<*>))
|
||||
import Control.Exception (fromException, try, bracket_, throw, finally, SomeException(..))
|
||||
import qualified Control.Exception as E
|
||||
import Control.Applicative ((<|>), empty)
|
||||
import Control.Monad.Fail
|
||||
import Control.Monad.Fix (fix)
|
||||
import Control.Monad.State
|
||||
import Control.Monad.Reader
|
||||
import Control.Monad (filterM, guard, void, when)
|
||||
import Data.Semigroup
|
||||
import Data.Default
|
||||
import Data.Traversable (for)
|
||||
import Data.Time.Clock (UTCTime)
|
||||
import Data.Default.Class
|
||||
import System.Environment (lookupEnv)
|
||||
import Data.List (isInfixOf, intercalate, (\\))
|
||||
import System.FilePath
|
||||
import System.IO
|
||||
import System.Info
|
||||
@@ -56,10 +70,8 @@ import System.Exit
|
||||
import Graphics.X11.Xlib
|
||||
import Graphics.X11.Xlib.Extras (getWindowAttributes, WindowAttributes, Event)
|
||||
import Data.Typeable
|
||||
import Data.List ((\\))
|
||||
import Data.Maybe (isJust,fromMaybe)
|
||||
import Data.Monoid hiding ((<>))
|
||||
import System.Environment (lookupEnv)
|
||||
import Data.Monoid (Ap(..))
|
||||
|
||||
import qualified Data.Map as M
|
||||
import qualified Data.Set as S
|
||||
@@ -93,8 +105,8 @@ data XConf = XConf
|
||||
, mousePosition :: !(Maybe (Position, Position))
|
||||
-- ^ position of the mouse according to
|
||||
-- the event currently being processed
|
||||
, currentEvent :: !(Maybe Event)
|
||||
-- ^ event currently being processed
|
||||
, currentEvent :: !(Maybe Event) -- ^ event currently being processed
|
||||
, directories :: !Directories -- ^ directories to use
|
||||
}
|
||||
|
||||
-- todo, better name
|
||||
@@ -122,6 +134,11 @@ data XConfig l = XConfig
|
||||
, rootMask :: !EventMask -- ^ The root events that xmonad is interested in
|
||||
, handleExtraArgs :: !([String] -> XConfig Layout -> IO (XConfig Layout))
|
||||
-- ^ Modify the configuration, complain about extra arguments etc. with arguments that are not handled by default
|
||||
, extensibleConf :: !(M.Map TypeRep ConfExtension)
|
||||
-- ^ Stores custom config information.
|
||||
--
|
||||
-- The module "XMonad.Util.ExtensibleConf" in xmonad-contrib
|
||||
-- provides additional information and a simple interface for using this.
|
||||
}
|
||||
|
||||
|
||||
@@ -135,7 +152,8 @@ type WorkspaceId = String
|
||||
newtype ScreenId = S Int deriving (Eq,Ord,Show,Read,Enum,Num,Integral,Real)
|
||||
|
||||
-- | The 'Rectangle' with screen dimensions
|
||||
data ScreenDetail = SD { screenRect :: !Rectangle } deriving (Eq,Show, Read)
|
||||
newtype ScreenDetail = SD { screenRect :: Rectangle }
|
||||
deriving (Eq,Show, Read)
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
@@ -148,18 +166,8 @@ data ScreenDetail = SD { screenRect :: !Rectangle } deriving (Eq,Show, Read)
|
||||
-- instantiated on 'XConf' and 'XState' automatically.
|
||||
--
|
||||
newtype X a = X (ReaderT XConf (StateT XState IO) a)
|
||||
deriving (Functor, Monad, MonadFail, MonadIO, MonadState XState, MonadReader XConf, Typeable)
|
||||
|
||||
instance Applicative X where
|
||||
pure = return
|
||||
(<*>) = ap
|
||||
|
||||
instance Semigroup a => Semigroup (X a) where
|
||||
(<>) = liftM2 (<>)
|
||||
|
||||
instance (Monoid a) => Monoid (X a) where
|
||||
mempty = return mempty
|
||||
mappend = liftM2 mappend
|
||||
deriving (Functor, Applicative, Monad, MonadFail, MonadIO, MonadState XState, MonadReader XConf)
|
||||
deriving (Semigroup, Monoid) via Ap X a
|
||||
|
||||
instance Default a => Default (X a) where
|
||||
def = return def
|
||||
@@ -167,16 +175,10 @@ instance Default a => Default (X a) where
|
||||
type ManageHook = Query (Endo WindowSet)
|
||||
newtype Query a = Query (ReaderT Window X a)
|
||||
deriving (Functor, Applicative, Monad, MonadReader Window, MonadIO)
|
||||
deriving (Semigroup, Monoid) via Ap Query a
|
||||
|
||||
runQuery :: Query a -> Window -> X a
|
||||
runQuery (Query m) w = runReaderT m w
|
||||
|
||||
instance Semigroup a => Semigroup (Query a) where
|
||||
(<>) = liftM2 (<>)
|
||||
|
||||
instance Monoid a => Monoid (Query a) where
|
||||
mempty = return mempty
|
||||
mappend = liftM2 mappend
|
||||
runQuery (Query m) = runReaderT m
|
||||
|
||||
instance Default a => Default (Query a) where
|
||||
def = return def
|
||||
@@ -193,7 +195,7 @@ catchX job errcase = do
|
||||
st <- get
|
||||
c <- ask
|
||||
(a, s') <- io $ runX c st job `E.catch` \e -> case fromException e of
|
||||
Just x -> throw e `const` (x `asTypeOf` ExitSuccess)
|
||||
Just (_ :: ExitCode) -> throw e
|
||||
_ -> do hPrint stderr e; runX c st errcase
|
||||
put s'
|
||||
return a
|
||||
@@ -201,12 +203,12 @@ catchX job errcase = do
|
||||
-- | Execute the argument, catching all exceptions. Either this function or
|
||||
-- 'catchX' should be used at all callsites of user customized code.
|
||||
userCode :: X a -> X (Maybe a)
|
||||
userCode a = catchX (Just `liftM` a) (return Nothing)
|
||||
userCode a = catchX (Just <$> a) (return Nothing)
|
||||
|
||||
-- | Same as userCode but with a default argument to return instead of using
|
||||
-- Maybe, provided for convenience.
|
||||
userCodeDef :: a -> X a -> X a
|
||||
userCodeDef defValue a = fromMaybe defValue `liftM` userCode a
|
||||
userCodeDef defValue a = fromMaybe defValue <$> userCode a
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Convenient wrappers to state
|
||||
@@ -227,7 +229,7 @@ withWindowAttributes dpy win f = do
|
||||
|
||||
-- | True if the given window is the root window
|
||||
isRoot :: Window -> X Bool
|
||||
isRoot w = (w==) <$> asks theRoot
|
||||
isRoot w = asks $ (w ==) . theRoot
|
||||
|
||||
-- | Wrapper for the common case of atom internment
|
||||
getAtom :: String -> X Atom
|
||||
@@ -255,14 +257,18 @@ readsLayout (Layout l) s = [(Layout (asTypeOf x l), rs) | (x, rs) <- reads s]
|
||||
-- | Every layout must be an instance of 'LayoutClass', which defines
|
||||
-- the basic layout operations along with a sensible default for each.
|
||||
--
|
||||
-- Minimal complete definition:
|
||||
-- All of the methods have default implementations, so there is no
|
||||
-- minimal complete definition. They do, however, have a dependency
|
||||
-- structure by default; this is something to be aware of should you
|
||||
-- choose to implement one of these methods. Here is how a minimal
|
||||
-- complete definition would look like if we did not provide any default
|
||||
-- implementations:
|
||||
--
|
||||
-- * 'runLayout' || (('doLayout' || 'pureLayout') && 'emptyLayout'), and
|
||||
-- * 'runLayout' || (('doLayout' || 'pureLayout') && 'emptyLayout')
|
||||
--
|
||||
-- * 'handleMessage' || 'pureMessage'
|
||||
--
|
||||
-- You should also strongly consider implementing 'description',
|
||||
-- although it is not required.
|
||||
-- * 'description'
|
||||
--
|
||||
-- Note that any code which /uses/ 'LayoutClass' methods should only
|
||||
-- ever call 'runLayout', 'handleMessage', and 'description'! In
|
||||
@@ -271,7 +277,7 @@ readsLayout (Layout l) s = [(Layout (asTypeOf x l), rs) | (x, rs) <- reads s]
|
||||
-- 'runLayout', 'handleMessage', and so on. This ensures that the
|
||||
-- proper methods will be used, regardless of the particular methods
|
||||
-- that any 'LayoutClass' instance chooses to define.
|
||||
class Show (layout a) => LayoutClass layout a where
|
||||
class (Show (layout a), Typeable layout) => LayoutClass layout a where
|
||||
|
||||
-- | By default, 'runLayout' calls 'doLayout' if there are any
|
||||
-- windows to be laid out, and 'emptyLayout' otherwise. Most
|
||||
@@ -373,12 +379,12 @@ instance Message Event
|
||||
-- layouts) should consider handling.
|
||||
data LayoutMessages = Hide -- ^ sent when a layout becomes non-visible
|
||||
| ReleaseResources -- ^ sent when xmonad is exiting or restarting
|
||||
deriving (Typeable, Eq)
|
||||
deriving Eq
|
||||
|
||||
instance Message LayoutMessages
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Extensible state
|
||||
-- Extensible state/config
|
||||
--
|
||||
|
||||
-- | Every module must make the data it wants to store
|
||||
@@ -386,6 +392,7 @@ instance Message LayoutMessages
|
||||
--
|
||||
-- Minimal complete definition: initialValue
|
||||
class Typeable a => ExtensionClass a where
|
||||
{-# MINIMAL initialValue #-}
|
||||
-- | Defines an initial value for the state extension
|
||||
initialValue :: a
|
||||
-- | Specifies whether the state extension should be
|
||||
@@ -404,10 +411,17 @@ data StateExtension =
|
||||
| forall a. (Read a, Show a, ExtensionClass a) => PersistentExtension a
|
||||
-- ^ Persistent extension
|
||||
|
||||
-- | Existential type to store a config extension.
|
||||
data ConfExtension = forall a. Typeable a => ConfExtension a
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- | General utilities
|
||||
--
|
||||
-- Lift an 'IO' action into the 'X' monad
|
||||
-- General utilities
|
||||
|
||||
-- | If-then-else lifted to a 'Monad'.
|
||||
ifM :: Monad m => m Bool -> m a -> m a -> m a
|
||||
ifM mb t f = mb >>= \b -> if b then t else f
|
||||
|
||||
-- | Lift an 'IO' action into the 'X' monad
|
||||
io :: MonadIO m => IO a -> m a
|
||||
io = liftIO
|
||||
|
||||
@@ -421,7 +435,7 @@ catchIO f = io (f `E.catch` \(SomeException e) -> hPrint stderr e >> hFlush stde
|
||||
--
|
||||
-- Note this function assumes your locale uses utf8.
|
||||
spawn :: MonadIO m => String -> m ()
|
||||
spawn x = spawnPID x >> return ()
|
||||
spawn x = void $ spawnPID x
|
||||
|
||||
-- | Like 'spawn', but returns the 'ProcessID' of the launched application
|
||||
spawnPID :: MonadIO m => String -> m ProcessID
|
||||
@@ -435,10 +449,25 @@ xfork x = io . forkProcess . finally nullStdin $ do
|
||||
x
|
||||
where
|
||||
nullStdin = do
|
||||
#if MIN_VERSION_unix(2,8,0)
|
||||
fd <- openFd "/dev/null" ReadOnly defaultFileFlags
|
||||
#else
|
||||
fd <- openFd "/dev/null" ReadOnly Nothing defaultFileFlags
|
||||
#endif
|
||||
dupTo fd stdInput
|
||||
closeFd fd
|
||||
|
||||
-- | Use @xmessage@ to show information to the user.
|
||||
xmessage :: MonadIO m => String -> m ()
|
||||
xmessage msg = void . xfork $ do
|
||||
xmessageBin <- fromMaybe "xmessage" <$> liftIO (lookupEnv "XMONAD_XMESSAGE")
|
||||
executeFile xmessageBin True
|
||||
[ "-default", "okay"
|
||||
, "-xrm", "*international:true"
|
||||
, "-xrm", "*fontSet:-*-fixed-medium-r-normal-*-18-*-*-*-*-*-*-*,-*-fixed-*-*-*-*-18-*-*-*-*-*-*-*,-*-*-*-*-*-*-18-*-*-*-*-*-*-*"
|
||||
, msg
|
||||
] Nothing
|
||||
|
||||
-- | This is basically a map function, running a function in the 'X' monad on
|
||||
-- each workspace with the output of that function being the modified workspace.
|
||||
runOnWorkspaces :: (WindowSpace -> X WindowSpace) -> X ()
|
||||
@@ -449,137 +478,302 @@ runOnWorkspaces job = do
|
||||
$ current ws : visible ws
|
||||
modify $ \s -> s { windowset = ws { current = c, visible = v, hidden = h } }
|
||||
|
||||
-- | Return the path to the xmonad configuration directory. This
|
||||
-- directory is where user configuration files are stored (e.g, the
|
||||
-- xmonad.hs file). You may also create a @lib@ subdirectory in the
|
||||
-- configuration directory and the default recompile command will add
|
||||
-- it to the GHC include path.
|
||||
-- | All the directories that xmonad will use. They will be used for
|
||||
-- the following purposes:
|
||||
--
|
||||
-- Several directories are considered. In order of
|
||||
-- preference:
|
||||
-- * @dataDir@: This directory is used by XMonad to store data files
|
||||
-- such as the run-time state file.
|
||||
--
|
||||
-- 1. The directory specified in the @XMONAD_CONFIG_DIR@ environment variable.
|
||||
-- 2. The @~\/.xmonad@ directory.
|
||||
-- 3. The @XDG_CONFIG_HOME/xmonad@ directory.
|
||||
-- * @cfgDir@: This directory is where user configuration files are
|
||||
-- stored (e.g, the xmonad.hs file). You may also create a @lib@
|
||||
-- subdirectory in the configuration directory and the default recompile
|
||||
-- command will add it to the GHC include path.
|
||||
--
|
||||
-- The first directory that exists will be used. If none of the
|
||||
-- directories exist then (1) will be used if it is set, otherwise (2)
|
||||
-- will be used. Either way, a directory will be created if necessary.
|
||||
getXMonadDir :: MonadIO m => m String
|
||||
getXMonadDir =
|
||||
findFirstDirWithEnv "XMONAD_CONFIG_DIR"
|
||||
[ getAppUserDataDirectory "xmonad"
|
||||
, getXDGDirectory XDGConfig "xmonad"
|
||||
]
|
||||
-- * @cacheDir@: This directory is used to store temporary files that
|
||||
-- can easily be recreated such as the configuration binary and any
|
||||
-- intermediate object files generated by GHC.
|
||||
-- Also, the XPrompt history file goes here.
|
||||
--
|
||||
-- For how these directories are chosen, see 'getDirectories'.
|
||||
--
|
||||
data Directories' a = Directories
|
||||
{ dataDir :: !a
|
||||
, cfgDir :: !a
|
||||
, cacheDir :: !a
|
||||
}
|
||||
deriving (Show, Functor, Foldable, Traversable)
|
||||
|
||||
-- | Return the path to the xmonad cache directory. This directory is
|
||||
-- used to store temporary files that can easily be recreated. For
|
||||
-- example, the XPrompt history file.
|
||||
--
|
||||
-- Several directories are considered. In order of preference:
|
||||
--
|
||||
-- 1. The directory specified in the @XMONAD_CACHE_DIR@ environment variable.
|
||||
-- 2. The @~\/.xmonad@ directory.
|
||||
-- 3. The @XDG_CACHE_HOME/xmonad@ directory.
|
||||
--
|
||||
-- The first directory that exists will be used. If none of the
|
||||
-- directories exist then (1) will be used if it is set, otherwise (2)
|
||||
-- will be used. Either way, a directory will be created if necessary.
|
||||
getXMonadCacheDir :: MonadIO m => m String
|
||||
getXMonadCacheDir =
|
||||
findFirstDirWithEnv "XMONAD_CACHE_DIR"
|
||||
[ getAppUserDataDirectory "xmonad"
|
||||
, getXDGDirectory XDGCache "xmonad"
|
||||
]
|
||||
-- | Convenient type alias for the most common case in which one might
|
||||
-- want to use the 'Directories' type.
|
||||
type Directories = Directories' FilePath
|
||||
|
||||
-- | Return the path to the xmonad data directory. This directory is
|
||||
-- used by XMonad to store data files such as the run-time state file
|
||||
-- and the configuration binary generated by GHC.
|
||||
-- | Build up the 'Dirs' that xmonad will use. They are chosen as
|
||||
-- follows:
|
||||
--
|
||||
-- Several directories are considered. In order of preference:
|
||||
-- 1. If all three of xmonad's environment variables (@XMONAD_DATA_DIR@,
|
||||
-- @XMONAD_CONFIG_DIR@, and @XMONAD_CACHE_DIR@) are set, use them.
|
||||
-- 2. If there is a build script called @build@ or configuration
|
||||
-- @xmonad.hs@ in @~\/.xmonad@, set all three directories to
|
||||
-- @~\/.xmonad@.
|
||||
-- 3. Otherwise, use the @xmonad@ directory in @XDG_DATA_HOME@,
|
||||
-- @XDG_CONFIG_HOME@, and @XDG_CACHE_HOME@ (or their respective
|
||||
-- fallbacks). These directories are created if necessary.
|
||||
--
|
||||
-- 1. The directory specified in the @XMONAD_DATA_DIR@ environment variable.
|
||||
-- 2. The @~\/.xmonad@ directory.
|
||||
-- 3. The @XDG_DATA_HOME/xmonad@ directory.
|
||||
-- The xmonad configuration file (or the build script, if present) is
|
||||
-- always assumed to be in @cfgDir@.
|
||||
--
|
||||
-- The first directory that exists will be used. If none of the
|
||||
-- directories exist then (1) will be used if it is set, otherwise (2)
|
||||
-- will be used. Either way, a directory will be created if necessary.
|
||||
getXMonadDataDir :: MonadIO m => m String
|
||||
getXMonadDataDir =
|
||||
findFirstDirWithEnv "XMONAD_DATA_DIR"
|
||||
[ getAppUserDataDirectory "xmonad"
|
||||
, getXDGDirectory XDGData "xmonad"
|
||||
]
|
||||
|
||||
-- | Helper function that will find the first existing directory and
|
||||
-- return its path. If none of the directories can be found, create
|
||||
-- and return the first from the list. If the list is empty this
|
||||
-- function returns the historical @~\/.xmonad@ directory.
|
||||
findFirstDirOf :: MonadIO m => [IO FilePath] -> m FilePath
|
||||
findFirstDirOf [] = findFirstDirOf [getAppUserDataDirectory "xmonad"]
|
||||
findFirstDirOf possibles = do
|
||||
found <- go possibles
|
||||
|
||||
case found of
|
||||
Just path -> return path
|
||||
Nothing -> do
|
||||
primary <- io (head possibles)
|
||||
io (createDirectoryIfMissing True primary)
|
||||
return primary
|
||||
|
||||
getDirectories :: IO Directories
|
||||
getDirectories = xmEnvDirs <|> xmDirs <|> xdgDirs
|
||||
where
|
||||
go [] = return Nothing
|
||||
go (x:xs) = do
|
||||
dir <- io x
|
||||
exists <- io (doesDirectoryExist dir)
|
||||
if exists then return (Just dir) else go xs
|
||||
-- | Check for xmonad's environment variables first
|
||||
xmEnvDirs :: IO Directories
|
||||
xmEnvDirs = do
|
||||
let xmEnvs = Directories{ dataDir = "XMONAD_DATA_DIR"
|
||||
, cfgDir = "XMONAD_CONFIG_DIR"
|
||||
, cacheDir = "XMONAD_CACHE_DIR"
|
||||
}
|
||||
maybe empty pure . sequenceA =<< traverse getEnv xmEnvs
|
||||
|
||||
-- | Simple wrapper around @findFirstDirOf@ that allows the primary
|
||||
-- path to be specified by an environment variable.
|
||||
findFirstDirWithEnv :: MonadIO m => String -> [IO FilePath] -> m FilePath
|
||||
findFirstDirWithEnv envName paths = do
|
||||
envPath' <- io (getEnv envName)
|
||||
-- | Check whether the config file or a build script is in the
|
||||
-- @~\/.xmonad@ directory
|
||||
xmDirs :: IO Directories
|
||||
xmDirs = do
|
||||
xmDir <- getAppUserDataDirectory "xmonad"
|
||||
conf <- doesFileExist $ xmDir </> "xmonad.hs"
|
||||
build <- doesFileExist $ xmDir </> "build"
|
||||
|
||||
case envPath' of
|
||||
Nothing -> findFirstDirOf paths
|
||||
Just envPath -> findFirstDirOf (return envPath:paths)
|
||||
-- Place *everything* in ~/.xmonad if yes
|
||||
guard $ conf || build
|
||||
pure Directories{ dataDir = xmDir, cfgDir = xmDir, cacheDir = xmDir }
|
||||
|
||||
-- | Helper function to retrieve the various XDG directories.
|
||||
-- This has been based on the implementation shipped with GHC version 8.0.1 or
|
||||
-- higher. Put here to preserve compatibility with older GHC versions.
|
||||
getXDGDirectory :: XDGDirectory -> FilePath -> IO FilePath
|
||||
getXDGDirectory xdgDir suffix =
|
||||
normalise . (</> suffix) <$>
|
||||
case xdgDir of
|
||||
XDGData -> get "XDG_DATA_HOME" ".local/share"
|
||||
XDGConfig -> get "XDG_CONFIG_HOME" ".config"
|
||||
XDGCache -> get "XDG_CACHE_HOME" ".cache"
|
||||
-- | Use XDG directories as a fallback
|
||||
xdgDirs :: IO Directories
|
||||
xdgDirs =
|
||||
for Directories{ dataDir = XdgData, cfgDir = XdgConfig, cacheDir = XdgCache }
|
||||
$ \dir -> do d <- getXdgDirectory dir "xmonad"
|
||||
d <$ createDirectoryIfMissing True d
|
||||
|
||||
-- | Return the path to the xmonad configuration directory.
|
||||
getXMonadDir :: X String
|
||||
getXMonadDir = asks (cfgDir . directories)
|
||||
{-# DEPRECATED getXMonadDir "Use `asks (cfgDir . directories)' instead." #-}
|
||||
|
||||
-- | Return the path to the xmonad cache directory.
|
||||
getXMonadCacheDir :: X String
|
||||
getXMonadCacheDir = asks (cacheDir . directories)
|
||||
{-# DEPRECATED getXMonadCacheDir "Use `asks (cacheDir . directories)' instead." #-}
|
||||
|
||||
-- | Return the path to the xmonad data directory.
|
||||
getXMonadDataDir :: X String
|
||||
getXMonadDataDir = asks (dataDir . directories)
|
||||
{-# DEPRECATED getXMonadDataDir "Use `asks (dataDir . directories)' instead." #-}
|
||||
|
||||
binFileName, buildDirName :: Directories -> FilePath
|
||||
binFileName Directories{ cacheDir } = cacheDir </> "xmonad-" <> arch <> "-" <> os
|
||||
buildDirName Directories{ cacheDir } = cacheDir </> "build-" <> arch <> "-" <> os
|
||||
|
||||
errFileName, stateFileName :: Directories -> FilePath
|
||||
errFileName Directories{ dataDir } = dataDir </> "xmonad.errors"
|
||||
stateFileName Directories{ dataDir } = dataDir </> "xmonad.state"
|
||||
|
||||
srcFileName, libFileName :: Directories -> FilePath
|
||||
srcFileName Directories{ cfgDir } = cfgDir </> "xmonad.hs"
|
||||
libFileName Directories{ cfgDir } = cfgDir </> "lib"
|
||||
|
||||
buildScriptFileName, stackYamlFileName, nixFlakeFileName, nixDefaultFileName :: Directories -> FilePath
|
||||
buildScriptFileName Directories{ cfgDir } = cfgDir </> "build"
|
||||
stackYamlFileName Directories{ cfgDir } = cfgDir </> "stack.yaml"
|
||||
nixFlakeFileName Directories{ cfgDir } = cfgDir </> "flake.nix"
|
||||
nixDefaultFileName Directories{ cfgDir } = cfgDir </> "default.nix"
|
||||
|
||||
-- | Compilation method for xmonad configuration.
|
||||
data Compile
|
||||
= CompileGhc
|
||||
| CompileStackGhc FilePath
|
||||
| CompileNixFlake
|
||||
| CompileNixDefault
|
||||
| CompileScript FilePath
|
||||
deriving (Show)
|
||||
|
||||
-- | Detect compilation method by looking for known file names in xmonad
|
||||
-- configuration directory.
|
||||
detectCompile :: Directories -> IO Compile
|
||||
detectCompile dirs =
|
||||
tryScript <|> tryStack <|> tryNixFlake <|> tryNixDefault <|> useGhc
|
||||
where
|
||||
get name fallback = do
|
||||
env <- lookupEnv name
|
||||
case env of
|
||||
Nothing -> fallback'
|
||||
Just path
|
||||
| isRelative path -> fallback'
|
||||
| otherwise -> return path
|
||||
where
|
||||
fallback' = (</> fallback) <$> getHomeDirectory
|
||||
data XDGDirectory = XDGData | XDGConfig | XDGCache
|
||||
buildScript = buildScriptFileName dirs
|
||||
stackYaml = stackYamlFileName dirs
|
||||
flakeNix = nixFlakeFileName dirs
|
||||
defaultNix = nixDefaultFileName dirs
|
||||
|
||||
-- | Get the name of the file used to store the xmonad window state.
|
||||
stateFileName :: (Functor m, MonadIO m) => m FilePath
|
||||
stateFileName = (</> "xmonad.state") <$> getXMonadDataDir
|
||||
tryScript = do
|
||||
guard =<< doesFileExist buildScript
|
||||
isExe <- isExecutable buildScript
|
||||
if isExe
|
||||
then do
|
||||
trace $ "XMonad will use build script at " <> show buildScript <> " to recompile."
|
||||
pure $ CompileScript buildScript
|
||||
else do
|
||||
trace $ "XMonad will not use build script, because " <> show buildScript <> " is not executable."
|
||||
trace $ "Suggested resolution to use it: chmod u+x " <> show buildScript
|
||||
empty
|
||||
|
||||
-- | 'recompile force', recompile the xmonad configuration file when
|
||||
-- any of the following apply:
|
||||
tryNixFlake = do
|
||||
guard =<< doesFileExist flakeNix
|
||||
canonNixFlake <- canonicalizePath flakeNix
|
||||
trace $ "XMonad will use nix flake at " <> show canonNixFlake <> " to recompile"
|
||||
pure CompileNixFlake
|
||||
|
||||
tryNixDefault = do
|
||||
guard =<< doesFileExist defaultNix
|
||||
canonNixDefault <- canonicalizePath defaultNix
|
||||
trace $ "XMonad will use nix file at " <> show canonNixDefault <> " to recompile"
|
||||
pure CompileNixDefault
|
||||
|
||||
tryStack = do
|
||||
guard =<< doesFileExist stackYaml
|
||||
canonStackYaml <- canonicalizePath stackYaml
|
||||
trace $ "XMonad will use stack ghc --stack-yaml " <> show canonStackYaml <> " to recompile."
|
||||
pure $ CompileStackGhc canonStackYaml
|
||||
|
||||
useGhc = do
|
||||
trace $ "XMonad will use ghc to recompile, because none of "
|
||||
<> intercalate ", "
|
||||
[ show buildScript
|
||||
, show stackYaml
|
||||
, show flakeNix
|
||||
, show defaultNix
|
||||
] <> " exist."
|
||||
pure CompileGhc
|
||||
|
||||
-- | Determine whether or not the file found at the provided filepath is executable.
|
||||
isExecutable :: FilePath -> IO Bool
|
||||
isExecutable f = E.catch (executable <$> getPermissions f) (\(SomeException _) -> return False)
|
||||
|
||||
-- | Should we recompile xmonad configuration? Is it newer than the compiled
|
||||
-- binary?
|
||||
shouldCompile :: Directories -> Compile -> IO Bool
|
||||
shouldCompile dirs CompileGhc = do
|
||||
libTs <- mapM getModTime . Prelude.filter isSource =<< allFiles (libFileName dirs)
|
||||
srcT <- getModTime (srcFileName dirs)
|
||||
binT <- getModTime (binFileName dirs)
|
||||
if any (binT <) (srcT : libTs)
|
||||
then True <$ trace "XMonad recompiling because some files have changed."
|
||||
else False <$ trace "XMonad skipping recompile because it is not forced (e.g. via --recompile), and neither xmonad.hs nor any *.hs / *.lhs / *.hsc files in lib/ have been changed."
|
||||
where
|
||||
isSource = flip elem [".hs",".lhs",".hsc"] . takeExtension
|
||||
allFiles t = do
|
||||
let prep = map (t</>) . Prelude.filter (`notElem` [".",".."])
|
||||
cs <- prep <$> E.catch (getDirectoryContents t) (\(SomeException _) -> return [])
|
||||
ds <- filterM doesDirectoryExist cs
|
||||
concat . ((cs \\ ds):) <$> mapM allFiles ds
|
||||
shouldCompile dirs CompileStackGhc{} = do
|
||||
stackYamlT <- getModTime (stackYamlFileName dirs)
|
||||
binT <- getModTime (binFileName dirs)
|
||||
if binT < stackYamlT
|
||||
then True <$ trace "XMonad recompiling because some files have changed."
|
||||
else shouldCompile dirs CompileGhc
|
||||
shouldCompile _dirs CompileNixFlake{} = True <$ trace "XMonad recompiling because flake recompilation is being used."
|
||||
shouldCompile _dirs CompileNixDefault{} = True <$ trace "XMonad recompiling because nix recompilation is being used."
|
||||
shouldCompile _dirs CompileScript{} =
|
||||
True <$ trace "XMonad recompiling because a custom build script is being used."
|
||||
|
||||
getModTime :: FilePath -> IO (Maybe UTCTime)
|
||||
getModTime f = E.catch (Just <$> getModificationTime f) (\(SomeException _) -> return Nothing)
|
||||
|
||||
-- | Compile the configuration.
|
||||
compile :: Directories -> Compile -> IO ExitCode
|
||||
compile dirs method =
|
||||
bracket_ uninstallSignalHandlers installSignalHandlers $
|
||||
withFile (errFileName dirs) WriteMode $ \err -> do
|
||||
let run = runProc (cfgDir dirs) err
|
||||
case method of
|
||||
CompileGhc -> do
|
||||
ghc <- fromMaybe "ghc" <$> lookupEnv "XMONAD_GHC"
|
||||
run ghc ghcArgs
|
||||
CompileStackGhc stackYaml ->
|
||||
run "stack" ["build", "--silent", "--stack-yaml", stackYaml] .&&.
|
||||
run "stack" ("ghc" : "--stack-yaml" : stackYaml : "--" : ghcArgs)
|
||||
CompileNixFlake ->
|
||||
run "nix" ["build"] >>= andCopyFromResultDir
|
||||
CompileNixDefault ->
|
||||
run "nix-build" [] >>= andCopyFromResultDir
|
||||
CompileScript script ->
|
||||
run script [binFileName dirs]
|
||||
where
|
||||
ghcArgs = [ "--make"
|
||||
, "xmonad.hs"
|
||||
, "-i" -- only look in @lib@
|
||||
, "-ilib"
|
||||
, "-fforce-recomp"
|
||||
, "-main-is", "main"
|
||||
, "-v0"
|
||||
, "-outputdir", buildDirName dirs
|
||||
, "-o", binFileName dirs
|
||||
]
|
||||
andCopyFromResultDir exitCode = do
|
||||
if exitCode == ExitSuccess then copyFromResultDir else return exitCode
|
||||
findM p = foldr (\x -> ifM (p x) (pure $ Just x)) (pure Nothing)
|
||||
catchAny :: IO a -> (SomeException -> IO a) -> IO a
|
||||
catchAny = E.catch
|
||||
copyFromResultDir = do
|
||||
let binaryDirectory = cfgDir dirs </> "result" </> "bin"
|
||||
binFiles <- map (binaryDirectory </>) <$> catchAny (listDirectory binaryDirectory) (\_ -> return [])
|
||||
mfilepath <- findM isExecutable binFiles
|
||||
case mfilepath of
|
||||
Just filepath -> copyFile filepath (binFileName dirs) >> return ExitSuccess
|
||||
Nothing -> return $ ExitFailure 1
|
||||
|
||||
-- waitForProcess =<< System.Process.runProcess, but without closing the err handle
|
||||
runProc cwd err exe args = do
|
||||
hPutStrLn err $ unwords $ "$" : exe : args
|
||||
hFlush err
|
||||
(_, _, _, h) <- createProcess_ "runProc" (proc exe args){ cwd = Just cwd, std_err = UseHandle err }
|
||||
waitForProcess h
|
||||
|
||||
cmd1 .&&. cmd2 = cmd1 >>= \case
|
||||
ExitSuccess -> cmd2
|
||||
e -> pure e
|
||||
|
||||
-- | Check GHC output for deprecation warnings and notify the user if there
|
||||
-- were any. Report success otherwise.
|
||||
checkCompileWarnings :: Directories -> IO ()
|
||||
checkCompileWarnings dirs = do
|
||||
ghcErr <- readFile (errFileName dirs)
|
||||
if "-Wdeprecations" `isInfixOf` ghcErr
|
||||
then do
|
||||
let msg = unlines $
|
||||
["Deprecations detected while compiling xmonad config: " <> srcFileName dirs]
|
||||
++ lines ghcErr
|
||||
++ ["","Please correct them or silence using {-# OPTIONS_GHC -Wno-deprecations #-}."]
|
||||
trace msg
|
||||
xmessage msg
|
||||
else
|
||||
trace "XMonad recompilation process exited with success!"
|
||||
|
||||
-- | Notify the user that compilation failed and what was wrong.
|
||||
compileFailed :: Directories -> ExitCode -> IO ()
|
||||
compileFailed dirs status = do
|
||||
ghcErr <- readFile (errFileName dirs)
|
||||
let msg = unlines $
|
||||
["Errors detected while compiling xmonad config: " <> srcFileName dirs]
|
||||
++ lines (if null ghcErr then show status else ghcErr)
|
||||
++ ["","Please check the file for errors."]
|
||||
-- nb, the ordering of printing, then forking, is crucial due to
|
||||
-- lazy evaluation
|
||||
trace msg
|
||||
xmessage msg
|
||||
|
||||
-- | Recompile the xmonad configuration file when any of the following apply:
|
||||
--
|
||||
-- * force is 'True'
|
||||
-- * force is 'True'
|
||||
--
|
||||
-- * the xmonad executable does not exist
|
||||
-- * the xmonad executable does not exist
|
||||
--
|
||||
-- * the xmonad executable is older than xmonad.hs or any file in
|
||||
-- the @lib@ directory (under the configuration directory).
|
||||
-- * the xmonad executable is older than @xmonad.hs@ or any file in
|
||||
-- the @lib@ directory (under the configuration directory)
|
||||
--
|
||||
-- * custom @build@ script is being used
|
||||
--
|
||||
-- The -i flag is used to restrict recompilation to the xmonad.hs file only,
|
||||
-- and any files in the aforementioned @lib@ directory.
|
||||
@@ -590,106 +784,21 @@ stateFileName = (</> "xmonad.state") <$> getXMonadDataDir
|
||||
--
|
||||
-- 'False' is returned if there are compilation errors.
|
||||
--
|
||||
recompile :: MonadIO m => Bool -> m Bool
|
||||
recompile force = io $ do
|
||||
cfgdir <- getXMonadDir
|
||||
datadir <- getXMonadDataDir
|
||||
let binn = "xmonad-"++arch++"-"++os
|
||||
bin = datadir </> binn
|
||||
err = datadir </> "xmonad.errors"
|
||||
src = cfgdir </> "xmonad.hs"
|
||||
lib = cfgdir </> "lib"
|
||||
buildscript = cfgdir </> "build"
|
||||
|
||||
libTs <- mapM getModTime . Prelude.filter isSource =<< allFiles lib
|
||||
srcT <- getModTime src
|
||||
binT <- getModTime bin
|
||||
|
||||
useBuildscript <- do
|
||||
exists <- doesFileExist buildscript
|
||||
if exists
|
||||
then do
|
||||
isExe <- isExecutable buildscript
|
||||
if isExe
|
||||
then do
|
||||
trace $ "XMonad will use build script at " ++ show buildscript ++ " to recompile."
|
||||
return True
|
||||
else do
|
||||
trace $ unlines
|
||||
[ "XMonad will not use build script, because " ++ show buildscript ++ " is not executable."
|
||||
, "Suggested resolution to use it: chmod u+x " ++ show buildscript
|
||||
]
|
||||
return False
|
||||
else do
|
||||
trace $
|
||||
"XMonad will use ghc to recompile, because " ++ show buildscript ++ " does not exist."
|
||||
return False
|
||||
|
||||
shouldRecompile <-
|
||||
if useBuildscript || force
|
||||
then return True
|
||||
else if any (binT <) (srcT : libTs)
|
||||
then do
|
||||
trace "XMonad doing recompile because some files have changed."
|
||||
return True
|
||||
else do
|
||||
trace "XMonad skipping recompile because it is not forced (e.g. via --recompile), and neither xmonad.hs nor any *.hs / *.lhs / *.hsc files in lib/ have been changed."
|
||||
return False
|
||||
|
||||
if shouldRecompile
|
||||
recompile :: MonadIO m => Directories -> Bool -> m Bool
|
||||
recompile dirs force = io $ do
|
||||
method <- detectCompile dirs
|
||||
willCompile <- if force
|
||||
then True <$ trace "XMonad recompiling (forced)."
|
||||
else shouldCompile dirs method
|
||||
if willCompile
|
||||
then do
|
||||
-- temporarily disable SIGCHLD ignoring:
|
||||
uninstallSignalHandlers
|
||||
status <- bracket (openFile err WriteMode) hClose $ \errHandle ->
|
||||
waitForProcess =<< if useBuildscript
|
||||
then compileScript bin cfgdir buildscript errHandle
|
||||
else compileGHC bin cfgdir errHandle
|
||||
|
||||
-- re-enable SIGCHLD:
|
||||
installSignalHandlers
|
||||
|
||||
-- now, if it fails, run xmessage to let the user know:
|
||||
status <- compile dirs method
|
||||
if status == ExitSuccess
|
||||
then trace "XMonad recompilation process exited with success!"
|
||||
else do
|
||||
ghcErr <- readFile err
|
||||
let msg = unlines $
|
||||
["Error detected while loading xmonad configuration file: " ++ src]
|
||||
++ lines (if null ghcErr then show status else ghcErr)
|
||||
++ ["","Please check the file for errors."]
|
||||
-- nb, the ordering of printing, then forking, is crucial due to
|
||||
-- lazy evaluation
|
||||
hPutStrLn stderr msg
|
||||
forkProcess $ executeFile "xmessage" True ["-default", "okay", replaceUnicode msg] Nothing
|
||||
return ()
|
||||
return (status == ExitSuccess)
|
||||
else return True
|
||||
where getModTime f = E.catch (Just <$> getModificationTime f) (\(SomeException _) -> return Nothing)
|
||||
isSource = flip elem [".hs",".lhs",".hsc"] . takeExtension
|
||||
isExecutable f = E.catch (executable <$> getPermissions f) (\(SomeException _) -> return False)
|
||||
allFiles t = do
|
||||
let prep = map (t</>) . Prelude.filter (`notElem` [".",".."])
|
||||
cs <- prep <$> E.catch (getDirectoryContents t) (\(SomeException _) -> return [])
|
||||
ds <- filterM doesDirectoryExist cs
|
||||
concat . ((cs \\ ds):) <$> mapM allFiles ds
|
||||
-- Replace some of the unicode symbols GHC uses in its output
|
||||
replaceUnicode = map $ \c -> case c of
|
||||
'\8226' -> '*' -- •
|
||||
'\8216' -> '`' -- ‘
|
||||
'\8217' -> '`' -- ’
|
||||
_ -> c
|
||||
compileGHC bin dir errHandle =
|
||||
runProcess "ghc" ["--make"
|
||||
, "xmonad.hs"
|
||||
, "-i"
|
||||
, "-ilib"
|
||||
, "-fforce-recomp"
|
||||
, "-main-is", "main"
|
||||
, "-v0"
|
||||
, "-o", bin
|
||||
] (Just dir) Nothing Nothing Nothing (Just errHandle)
|
||||
compileScript bin dir script errHandle =
|
||||
runProcess script [bin] (Just dir) Nothing Nothing Nothing (Just errHandle)
|
||||
then checkCompileWarnings dirs
|
||||
else compileFailed dirs status
|
||||
pure $ status == ExitSuccess
|
||||
else
|
||||
pure True
|
||||
|
||||
-- | Conditionally run an action, using a @Maybe a@ to decide.
|
||||
whenJust :: Monad m => Maybe a -> (a -> m ()) -> m ()
|
||||
|
@@ -1,4 +1,6 @@
|
||||
{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, PatternGuards, TypeSynonymInstances, DeriveDataTypeable #-}
|
||||
{-# LANGUAGE FlexibleInstances #-}
|
||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||
{-# LANGUAGE PatternGuards #-}
|
||||
|
||||
-- --------------------------------------------------------------------------
|
||||
-- |
|
||||
@@ -8,7 +10,7 @@
|
||||
--
|
||||
-- Maintainer : spencerjanssen@gmail.com
|
||||
-- Stability : unstable
|
||||
-- Portability : not portable, Typeable deriving, mtl, posix
|
||||
-- Portability : not portable, mtl, posix
|
||||
--
|
||||
-- The collection of core layouts.
|
||||
--
|
||||
@@ -16,7 +18,7 @@
|
||||
|
||||
module XMonad.Layout (
|
||||
Full(..), Tall(..), Mirror(..),
|
||||
Resize(..), IncMasterN(..), Choose, (|||), ChangeLayout(..),
|
||||
Resize(..), IncMasterN(..), Choose(..), (|||), CLR(..), ChangeLayout(..), JumpToLayout(..),
|
||||
mirrorRect, splitVertically,
|
||||
splitHorizontally, splitHorizontallyBy, splitVerticallyBy,
|
||||
|
||||
@@ -27,6 +29,7 @@ module XMonad.Layout (
|
||||
import XMonad.Core
|
||||
|
||||
import Graphics.X11 (Rectangle(..))
|
||||
import Graphics.X11.Xlib.Extras ( Event(DestroyWindowEvent) )
|
||||
import qualified XMonad.StackSet as W
|
||||
import Control.Arrow ((***), second)
|
||||
import Control.Monad
|
||||
@@ -35,10 +38,10 @@ import Data.Maybe (fromMaybe)
|
||||
------------------------------------------------------------------------
|
||||
|
||||
-- | Change the size of the master pane.
|
||||
data Resize = Shrink | Expand deriving Typeable
|
||||
data Resize = Shrink | Expand
|
||||
|
||||
-- | Increase the number of clients in the master pane.
|
||||
data IncMasterN = IncMasterN !Int deriving Typeable
|
||||
newtype IncMasterN = IncMasterN Int
|
||||
|
||||
instance Message Resize
|
||||
instance Message IncMasterN
|
||||
@@ -59,9 +62,13 @@ data Tall a = Tall { tallNMaster :: !Int -- ^ The default number o
|
||||
|
||||
-- a nice pure layout, lots of properties for the layout, and its messages, in Properties.hs
|
||||
instance LayoutClass Tall a where
|
||||
pureLayout (Tall nmaster _ frac) r s = zip ws rs
|
||||
pureLayout (Tall nmaster _ frac) r s
|
||||
| frac == 0 = drop nmaster layout
|
||||
| frac == 1 = take nmaster layout
|
||||
| otherwise = layout
|
||||
where ws = W.integrate s
|
||||
rs = tile frac r nmaster (length ws)
|
||||
layout = zip ws rs
|
||||
|
||||
pureMessage (Tall nmaster delta frac) m =
|
||||
msum [fmap resize (fromMessage m)
|
||||
@@ -77,7 +84,7 @@ instance LayoutClass Tall a where
|
||||
-- algorithm.
|
||||
--
|
||||
-- The screen is divided into two panes. All clients are
|
||||
-- then partioned between these two panes. One pane, the master, by
|
||||
-- then partitioned between these two panes. One pane, the master, by
|
||||
-- convention has the least number of windows in it.
|
||||
tile
|
||||
:: Rational -- ^ @frac@, what proportion of the screen to devote to the master area
|
||||
@@ -131,23 +138,53 @@ mirrorRect (Rectangle rx ry rw rh) = Rectangle ry rx rh rw
|
||||
-- LayoutClass selection manager
|
||||
-- Layouts that transition between other layouts
|
||||
|
||||
-- | Messages to change the current layout.
|
||||
data ChangeLayout = FirstLayout | NextLayout deriving (Eq, Show, Typeable)
|
||||
-- | Messages to change the current layout. Also see 'JumpToLayout'.
|
||||
data ChangeLayout = FirstLayout | NextLayout deriving (Eq, Show)
|
||||
|
||||
instance Message ChangeLayout
|
||||
|
||||
-- | A message to jump to a particular layout, specified by its
|
||||
-- description string.
|
||||
--
|
||||
-- The argument given to a 'JumpToLayout' message should be the
|
||||
-- @description@ of the layout to be selected. If you use
|
||||
-- "XMonad.Hooks.DynamicLog" from @xmonad-contrib@, this is the name of
|
||||
-- the layout displayed in your status bar. Alternatively, you can use
|
||||
-- GHCi to determine the proper name to use. For example:
|
||||
--
|
||||
-- > $ ghci
|
||||
-- > GHCi, version 6.8.2: http://www.haskell.org/ghc/ :? for help
|
||||
-- > Loading package base ... linking ... done.
|
||||
-- > :set prompt "> " -- don't show loaded module names
|
||||
-- > > :m +XMonad.Core -- load the xmonad core
|
||||
-- > > :m +XMonad.Layout.Grid -- load whatever module you want to use
|
||||
-- > > description Grid -- find out what it's called
|
||||
-- > "Grid"
|
||||
--
|
||||
-- As yet another (possibly easier) alternative, you can use the
|
||||
-- "XMonad.Layout.Renamed" module (also in @xmonad-contrib@) to give
|
||||
-- custom names to your layouts, and use those.
|
||||
--
|
||||
-- For example, if you want to jump directly to the 'Full' layout you
|
||||
-- can do
|
||||
--
|
||||
-- > , ((modm .|. controlMask, xK_f), sendMessage $ JumpToLayout "Full")
|
||||
--
|
||||
newtype JumpToLayout = JumpToLayout String
|
||||
instance Message JumpToLayout
|
||||
|
||||
-- | The layout choice combinator
|
||||
(|||) :: l a -> r a -> Choose l r a
|
||||
(|||) = Choose L
|
||||
(|||) = Choose CL
|
||||
infixr 5 |||
|
||||
|
||||
-- | A layout that allows users to switch between various layout options.
|
||||
data Choose l r a = Choose LR (l a) (r a) deriving (Read, Show)
|
||||
data Choose l r a = Choose CLR (l a) (r a) deriving (Read, Show)
|
||||
|
||||
-- | Are we on the left or right sub-layout?
|
||||
data LR = L | R deriving (Read, Show, Eq)
|
||||
-- | Choose the current sub-layout (left or right) in 'Choose'.
|
||||
data CLR = CL | CR deriving (Read, Show, Eq)
|
||||
|
||||
data NextNoWrap = NextNoWrap deriving (Eq, Show, Typeable)
|
||||
data NextNoWrap = NextNoWrap deriving (Eq, Show)
|
||||
instance Message NextNoWrap
|
||||
|
||||
-- | A small wrapper around handleMessage, as it is tedious to write
|
||||
@@ -159,26 +196,26 @@ handle l m = handleMessage l (SomeMessage m)
|
||||
-- new structure if any fields have changed, and performs any necessary cleanup
|
||||
-- on newly non-visible layouts.
|
||||
choose :: (LayoutClass l a, LayoutClass r a)
|
||||
=> Choose l r a-> LR -> Maybe (l a) -> Maybe (r a) -> X (Maybe (Choose l r a))
|
||||
=> Choose l r a -> CLR -> Maybe (l a) -> Maybe (r a) -> X (Maybe (Choose l r a))
|
||||
choose (Choose d _ _) d' Nothing Nothing | d == d' = return Nothing
|
||||
choose (Choose d l r) d' ml mr = f lr
|
||||
where
|
||||
(l', r') = (fromMaybe l ml, fromMaybe r mr)
|
||||
lr = case (d, d') of
|
||||
(L, R) -> (hide l' , return r')
|
||||
(R, L) -> (return l', hide r' )
|
||||
(_, _) -> (return l', return r')
|
||||
f (x,y) = fmap Just $ liftM2 (Choose d') x y
|
||||
hide x = fmap (fromMaybe x) $ handle x Hide
|
||||
(CL, CR) -> (hide l' , return r')
|
||||
(CR, CL) -> (return l', hide r' )
|
||||
(_ , _ ) -> (return l', return r')
|
||||
f (x,y) = Just <$> liftM2 (Choose d') x y
|
||||
hide x = fromMaybe x <$> handle x Hide
|
||||
|
||||
instance (LayoutClass l a, LayoutClass r a) => LayoutClass (Choose l r) a where
|
||||
runLayout (W.Workspace i (Choose L l r) ms) =
|
||||
fmap (second . fmap $ flip (Choose L) r) . runLayout (W.Workspace i l ms)
|
||||
runLayout (W.Workspace i (Choose R l r) ms) =
|
||||
fmap (second . fmap $ Choose R l) . runLayout (W.Workspace i r ms)
|
||||
runLayout (W.Workspace i (Choose CL l r) ms) =
|
||||
fmap (second . fmap $ flip (Choose CL) r) . runLayout (W.Workspace i l ms)
|
||||
runLayout (W.Workspace i (Choose CR l r) ms) =
|
||||
fmap (second . fmap $ Choose CR l) . runLayout (W.Workspace i r ms)
|
||||
|
||||
description (Choose L l _) = description l
|
||||
description (Choose R _ r) = description r
|
||||
description (Choose CL l _) = description l
|
||||
description (Choose CR _ r) = description r
|
||||
|
||||
handleMessage lr m | Just NextLayout <- fromMessage m = do
|
||||
mlr' <- handle lr NextNoWrap
|
||||
@@ -186,25 +223,36 @@ instance (LayoutClass l a, LayoutClass r a) => LayoutClass (Choose l r) a where
|
||||
|
||||
handleMessage c@(Choose d l r) m | Just NextNoWrap <- fromMessage m =
|
||||
case d of
|
||||
L -> do
|
||||
CL -> do
|
||||
ml <- handle l NextNoWrap
|
||||
case ml of
|
||||
Just _ -> choose c L ml Nothing
|
||||
Nothing -> choose c R Nothing =<< handle r FirstLayout
|
||||
Just _ -> choose c CL ml Nothing
|
||||
Nothing -> choose c CR Nothing =<< handle r FirstLayout
|
||||
|
||||
R -> choose c R Nothing =<< handle r NextNoWrap
|
||||
CR -> choose c CR Nothing =<< handle r NextNoWrap
|
||||
|
||||
handleMessage c@(Choose _ l _) m | Just FirstLayout <- fromMessage m =
|
||||
flip (choose c L) Nothing =<< handle l FirstLayout
|
||||
flip (choose c CL) Nothing =<< handle l FirstLayout
|
||||
|
||||
handleMessage c@(Choose d l r) m | Just ReleaseResources <- fromMessage m =
|
||||
join $ liftM2 (choose c d) (handle l ReleaseResources) (handle r ReleaseResources)
|
||||
|
||||
handleMessage c@(Choose d l r) m | Just e@DestroyWindowEvent{} <- fromMessage m =
|
||||
join $ liftM2 (choose c d) (handle l e) (handle r e)
|
||||
|
||||
handleMessage c@(Choose d l r) m | Just (JumpToLayout desc) <- fromMessage m = do
|
||||
ml <- handleMessage l m
|
||||
mr <- handleMessage r m
|
||||
let md | desc == description (fromMaybe l ml) = CL
|
||||
| desc == description (fromMaybe r mr) = CR
|
||||
| otherwise = d
|
||||
choose c md ml mr
|
||||
|
||||
handleMessage c@(Choose d l r) m = do
|
||||
ml' <- case d of
|
||||
L -> handleMessage l m
|
||||
R -> return Nothing
|
||||
CL -> handleMessage l m
|
||||
CR -> return Nothing
|
||||
mr' <- case d of
|
||||
L -> return Nothing
|
||||
R -> handleMessage r m
|
||||
CL -> return Nothing
|
||||
CR -> handleMessage r m
|
||||
choose c d ml' mr'
|
||||
|
@@ -1,4 +1,6 @@
|
||||
{-# LANGUAGE MultiParamTypeClasses, FlexibleContexts #-}
|
||||
{-# LANGUAGE FlexibleContexts #-}
|
||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||
|
||||
----------------------------------------------------------------------------
|
||||
-- |
|
||||
-- Module : XMonad.Main
|
||||
@@ -13,18 +15,19 @@
|
||||
--
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
module XMonad.Main (xmonad, launch) where
|
||||
module XMonad.Main (xmonad, buildLaunch, launch) where
|
||||
|
||||
import System.Locale.SetLocale
|
||||
import qualified Control.Exception.Extensible as E
|
||||
import qualified Control.Exception as E
|
||||
import Data.Bits
|
||||
import Data.List ((\\))
|
||||
import Data.Function
|
||||
import Data.Foldable (traverse_)
|
||||
import qualified Data.Map as M
|
||||
import qualified Data.Set as S
|
||||
import Control.Monad.Reader
|
||||
import Control.Monad.State
|
||||
import Data.Maybe (fromMaybe)
|
||||
import Control.Monad (filterM, guard, unless, void, when)
|
||||
import Data.Maybe (fromMaybe, isJust)
|
||||
import Data.Monoid (getAll)
|
||||
|
||||
import Graphics.X11.Xlib hiding (refreshKeyboardMapping)
|
||||
@@ -39,7 +42,7 @@ import XMonad.Operations
|
||||
import System.IO
|
||||
import System.Directory
|
||||
import System.Info
|
||||
import System.Environment
|
||||
import System.Environment (getArgs, getProgName, withArgs)
|
||||
import System.Posix.Process (executeFile)
|
||||
import System.Exit (exitFailure)
|
||||
import System.FilePath
|
||||
@@ -48,6 +51,7 @@ import Paths_xmonad (version)
|
||||
import Data.Version (showVersion)
|
||||
|
||||
import Graphics.X11.Xinerama (compiledWithXinerama)
|
||||
import Graphics.X11.Xrandr (xrrQueryExtension, xrrUpdateConfiguration)
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
@@ -59,17 +63,17 @@ xmonad :: (LayoutClass l Window, Read (l Window)) => XConfig l -> IO ()
|
||||
xmonad conf = do
|
||||
installSignalHandlers -- important to ignore SIGCHLD to avoid zombies
|
||||
|
||||
dirs <- getDirectories
|
||||
let launch' args = do
|
||||
catchIO buildLaunch
|
||||
conf' @ XConfig { layoutHook = Layout l }
|
||||
catchIO (buildLaunch dirs)
|
||||
conf'@XConfig { layoutHook = Layout l }
|
||||
<- handleExtraArgs conf args conf{ layoutHook = Layout (layoutHook conf) }
|
||||
withArgs [] $ launch (conf' { layoutHook = l })
|
||||
withArgs [] $ launch (conf' { layoutHook = l }) dirs
|
||||
|
||||
args <- getArgs
|
||||
case args of
|
||||
("--resume": ws : xs : args') -> migrateState ws xs >> launch' args'
|
||||
["--help"] -> usage
|
||||
["--recompile"] -> recompile True >>= flip unless exitFailure
|
||||
["--recompile"] -> recompile dirs True >>= flip unless exitFailure
|
||||
["--restart"] -> sendRestart
|
||||
["--version"] -> putStrLn $ unwords shortVersion
|
||||
["--verbose-version"] -> putStrLn . unwords $ shortVersion ++ longVersion
|
||||
@@ -86,14 +90,14 @@ usage :: IO ()
|
||||
usage = do
|
||||
self <- getProgName
|
||||
putStr . unlines $
|
||||
concat ["Usage: ", self, " [OPTION]"] :
|
||||
"Options:" :
|
||||
" --help Print this message" :
|
||||
" --version Print the version number" :
|
||||
" --recompile Recompile your ~/.xmonad/xmonad.hs" :
|
||||
" --replace Replace the running window manager with xmonad" :
|
||||
" --restart Request a running xmonad process to restart" :
|
||||
[]
|
||||
[ "Usage: " <> self <> " [OPTION]"
|
||||
, "Options:"
|
||||
, " --help Print this message"
|
||||
, " --version Print the version number"
|
||||
, " --recompile Recompile your xmonad.hs"
|
||||
, " --replace Replace the running window manager with xmonad"
|
||||
, " --restart Request a running xmonad process to restart"
|
||||
]
|
||||
|
||||
-- | Build the xmonad configuration file with ghc, then execute it.
|
||||
-- If there are no errors, this function does not return. An
|
||||
@@ -111,40 +115,21 @@ usage = do
|
||||
--
|
||||
-- * Missing XMonad\/XMonadContrib modules due to ghc upgrade
|
||||
--
|
||||
buildLaunch :: IO ()
|
||||
buildLaunch = do
|
||||
buildLaunch :: Directories -> IO ()
|
||||
buildLaunch dirs = do
|
||||
whoami <- getProgName
|
||||
let compiledConfig = "xmonad-"++arch++"-"++os
|
||||
let bin = binFileName dirs
|
||||
let compiledConfig = takeFileName bin
|
||||
unless (whoami == compiledConfig) $ do
|
||||
trace $ concat
|
||||
[ "XMonad is recompiling and replacing itself another XMonad process because the current process is called "
|
||||
[ "XMonad is recompiling and replacing itself with another XMonad process because the current process is called "
|
||||
, show whoami
|
||||
, " but the compiled configuration should be called "
|
||||
, show compiledConfig
|
||||
]
|
||||
recompile False
|
||||
dir <- getXMonadDataDir
|
||||
recompile dirs False
|
||||
args <- getArgs
|
||||
executeFile (dir </> compiledConfig) False args Nothing
|
||||
|
||||
sendRestart :: IO ()
|
||||
sendRestart = do
|
||||
dpy <- openDisplay ""
|
||||
rw <- rootWindow dpy $ defaultScreen dpy
|
||||
xmonad_restart <- internAtom dpy "XMONAD_RESTART" False
|
||||
allocaXEvent $ \e -> do
|
||||
setEventType e clientMessage
|
||||
setClientMessageEvent e rw xmonad_restart 32 0 currentTime
|
||||
sendEvent dpy rw False structureNotifyMask e
|
||||
sync dpy False
|
||||
|
||||
-- | a wrapper for 'replace'
|
||||
sendReplace :: IO ()
|
||||
sendReplace = do
|
||||
dpy <- openDisplay ""
|
||||
let dflt = defaultScreen dpy
|
||||
rootw <- rootWindow dpy dflt
|
||||
replace dpy dflt rootw
|
||||
executeFile bin False args Nothing
|
||||
|
||||
-- | Entry point into xmonad for custom builds.
|
||||
--
|
||||
@@ -166,8 +151,8 @@ sendReplace = do
|
||||
-- function instead of 'xmonad'. You probably also want to have a key
|
||||
-- binding to the 'XMonad.Operations.restart` function that restarts
|
||||
-- your custom binary with the resume flag set to @True@.
|
||||
launch :: (LayoutClass l Window, Read (l Window)) => XConfig l -> IO ()
|
||||
launch initxmc = do
|
||||
launch :: (LayoutClass l Window, Read (l Window)) => XConfig l -> Directories -> IO ()
|
||||
launch initxmc drs = do
|
||||
-- setup locale information from environment
|
||||
setLocale LC_ALL (Just "")
|
||||
-- ignore SIGPIPE and SIGCHLD
|
||||
@@ -192,12 +177,12 @@ launch initxmc = do
|
||||
|
||||
xinesc <- getCleanedScreenInfo dpy
|
||||
|
||||
nbc <- do v <- initColor dpy $ normalBorderColor xmc
|
||||
~(Just nbc_) <- initColor dpy $ normalBorderColor Default.def
|
||||
nbc <- do v <- initColor dpy $ normalBorderColor xmc
|
||||
Just nbc_ <- initColor dpy $ normalBorderColor Default.def
|
||||
return (fromMaybe nbc_ v)
|
||||
|
||||
fbc <- do v <- initColor dpy $ focusedBorderColor xmc
|
||||
~(Just fbc_) <- initColor dpy $ focusedBorderColor Default.def
|
||||
Just fbc_ <- initColor dpy $ focusedBorderColor Default.def
|
||||
return (fromMaybe fbc_ v)
|
||||
|
||||
hSetBuffering stdout NoBuffering
|
||||
@@ -216,7 +201,9 @@ launch initxmc = do
|
||||
, buttonActions = mouseBindings xmc xmc
|
||||
, mouseFocused = False
|
||||
, mousePosition = Nothing
|
||||
, currentEvent = Nothing }
|
||||
, currentEvent = Nothing
|
||||
, directories = drs
|
||||
}
|
||||
|
||||
st = XState
|
||||
{ windowset = initialWinset
|
||||
@@ -231,7 +218,7 @@ launch initxmc = do
|
||||
runX cf st $ do
|
||||
-- check for serialized state in a file.
|
||||
serializedSt <- do
|
||||
path <- stateFileName
|
||||
path <- asks $ stateFileName . directories
|
||||
exists <- io (doesFileExist path)
|
||||
if exists then readStateFile initxmc else return Nothing
|
||||
|
||||
@@ -239,7 +226,7 @@ launch initxmc = do
|
||||
let extst = maybe M.empty extensibleState serializedSt
|
||||
modify (\s -> s {extensibleState = extst})
|
||||
|
||||
setNumlockMask
|
||||
cacheNumlockMask
|
||||
grabKeys
|
||||
grabButtons
|
||||
|
||||
@@ -260,8 +247,12 @@ launch initxmc = do
|
||||
|
||||
userCode $ startupHook initxmc
|
||||
|
||||
rrData <- io $ xrrQueryExtension dpy
|
||||
|
||||
-- main loop, for all you HOF/recursion fans out there.
|
||||
forever $ prehandle =<< io (nextEvent dpy e >> getEvent e)
|
||||
-- forever $ prehandle =<< io (nextEvent dpy e >> rrUpdate e >> getEvent e)
|
||||
-- sadly, 9.2.{1,2,3} join points mishandle the above and trash the heap (see #389)
|
||||
mainLoop dpy e rrData
|
||||
|
||||
return ()
|
||||
where
|
||||
@@ -272,6 +263,8 @@ launch initxmc = do
|
||||
in local (\c -> c { mousePosition = mouse, currentEvent = Just e }) (handleWithHook e)
|
||||
evs = [ keyPress, keyRelease, enterNotify, leaveNotify
|
||||
, buttonPress, buttonRelease]
|
||||
rrUpdate e r = when (isJust r) (void (xrrUpdateConfiguration e))
|
||||
mainLoop d e r = io (nextEvent d e >> rrUpdate e r >> getEvent e) >>= prehandle >> mainLoop d e r
|
||||
|
||||
|
||||
-- | Runs handleEventHook from the configuration and runs the default handler
|
||||
@@ -310,16 +303,21 @@ handle (MapRequestEvent {ev_window = w}) = withDisplay $ \dpy -> do
|
||||
|
||||
-- window destroyed, unmanage it
|
||||
-- window gone, unmanage it
|
||||
handle (DestroyWindowEvent {ev_window = w}) = whenX (isClient w) $ do
|
||||
-- broadcast to layouts
|
||||
handle e@(DestroyWindowEvent {ev_window = w}) = do
|
||||
whenX (isClient w) $ do
|
||||
unmanage w
|
||||
modify (\s -> s { mapped = S.delete w (mapped s)
|
||||
, waitingUnmap = M.delete w (waitingUnmap s)})
|
||||
-- the window is already unmanged, but we broadcast the event to all layouts
|
||||
-- to trigger garbage-collection in case they hold window-specific resources
|
||||
broadcastMessage e
|
||||
|
||||
-- We track expected unmap events in waitingUnmap. We ignore this event unless
|
||||
-- it is synthetic or we are not expecting an unmap notification from a window.
|
||||
handle (UnmapEvent {ev_window = w, ev_send_event = synthetic}) = whenX (isClient w) $ do
|
||||
e <- gets (fromMaybe 0 . M.lookup w . waitingUnmap)
|
||||
if (synthetic || e == 0)
|
||||
if synthetic || e == 0
|
||||
then unmanage w
|
||||
else modify (\s -> s { waitingUnmap = M.update mpred w (waitingUnmap s) })
|
||||
where mpred 1 = Nothing
|
||||
@@ -329,7 +327,7 @@ handle (UnmapEvent {ev_window = w, ev_send_event = synthetic}) = whenX (isClient
|
||||
handle e@(MappingNotifyEvent {}) = do
|
||||
io $ refreshKeyboardMapping e
|
||||
when (ev_request e `elem` [mappingKeyboard, mappingModifier]) $ do
|
||||
setNumlockMask
|
||||
cacheNumlockMask
|
||||
grabKeys
|
||||
|
||||
-- handle button release, which may finish dragging.
|
||||
@@ -417,7 +415,7 @@ handle event@(PropertyEvent { ev_event_type = t, ev_atom = a })
|
||||
|
||||
handle e@ClientMessageEvent { ev_message_type = mt } = do
|
||||
a <- getAtom "XMONAD_RESTART"
|
||||
if (mt == a)
|
||||
if mt == a
|
||||
then restart "xmonad" True
|
||||
else broadcastMessage e
|
||||
|
||||
@@ -448,35 +446,14 @@ scan dpy rootw = do
|
||||
skip :: E.SomeException -> IO Bool
|
||||
skip _ = return False
|
||||
|
||||
setNumlockMask :: X ()
|
||||
setNumlockMask = do
|
||||
dpy <- asks display
|
||||
ms <- io $ getModifierMapping dpy
|
||||
xs <- sequence [ do
|
||||
ks <- io $ keycodeToKeysym dpy kc 0
|
||||
if ks == xK_Num_Lock
|
||||
then return (setBit 0 (fromIntegral m))
|
||||
else return (0 :: KeyMask)
|
||||
| (m, kcs) <- ms, kc <- kcs, kc /= 0]
|
||||
modify (\s -> s { numberlockMask = foldr (.|.) 0 xs })
|
||||
|
||||
-- | Grab the keys back
|
||||
grabKeys :: X ()
|
||||
grabKeys = do
|
||||
XConf { display = dpy, theRoot = rootw } <- ask
|
||||
let grab kc m = io $ grabKey dpy kc m rootw True grabModeAsync grabModeAsync
|
||||
(minCode, maxCode) = displayKeycodes dpy
|
||||
allCodes = [fromIntegral minCode .. fromIntegral maxCode]
|
||||
io $ ungrabKey dpy anyKey anyModifier rootw
|
||||
ks <- asks keyActions
|
||||
-- build a map from keysyms to lists of keysyms (doing what
|
||||
-- XGetKeyboardMapping would do if the X11 package bound it)
|
||||
syms <- forM allCodes $ \code -> io (keycodeToKeysym dpy code 0)
|
||||
let keysymMap = M.fromListWith (++) (zip syms [[code] | code <- allCodes])
|
||||
keysymToKeycodes sym = M.findWithDefault [] sym keysymMap
|
||||
forM_ (M.keys ks) $ \(mask,sym) ->
|
||||
forM_ (keysymToKeycodes sym) $ \kc ->
|
||||
mapM_ (grab kc . (mask .|.)) =<< extraModifiers
|
||||
let grab :: (KeyMask, KeyCode) -> X ()
|
||||
grab (km, kc) = io $ grabKey dpy kc km rootw True grabModeAsync grabModeAsync
|
||||
traverse_ grab =<< mkGrabs =<< asks (M.keys . keyActions)
|
||||
|
||||
-- | Grab the buttons
|
||||
grabButtons :: X ()
|
||||
@@ -487,37 +464,4 @@ grabButtons = do
|
||||
io $ ungrabButton dpy anyButton anyModifier rootw
|
||||
ems <- extraModifiers
|
||||
ba <- asks buttonActions
|
||||
mapM_ (\(m,b) -> mapM_ (grab b . (m .|.)) ems) (M.keys $ ba)
|
||||
|
||||
-- | @replace@ to signals compliant window managers to exit.
|
||||
replace :: Display -> ScreenNumber -> Window -> IO ()
|
||||
replace dpy dflt rootw = do
|
||||
-- check for other WM
|
||||
wmSnAtom <- internAtom dpy ("WM_S" ++ show dflt) False
|
||||
currentWmSnOwner <- xGetSelectionOwner dpy wmSnAtom
|
||||
when (currentWmSnOwner /= 0) $ do
|
||||
-- prepare to receive destroyNotify for old WM
|
||||
selectInput dpy currentWmSnOwner structureNotifyMask
|
||||
|
||||
-- create off-screen window
|
||||
netWmSnOwner <- allocaSetWindowAttributes $ \attributes -> do
|
||||
set_override_redirect attributes True
|
||||
set_event_mask attributes propertyChangeMask
|
||||
let screen = defaultScreenOfDisplay dpy
|
||||
visual = defaultVisualOfScreen screen
|
||||
attrmask = cWOverrideRedirect .|. cWEventMask
|
||||
createWindow dpy rootw (-100) (-100) 1 1 0 copyFromParent copyFromParent visual attrmask attributes
|
||||
|
||||
-- try to acquire wmSnAtom, this should signal the old WM to terminate
|
||||
xSetSelectionOwner dpy wmSnAtom netWmSnOwner currentTime
|
||||
|
||||
-- SKIPPED: check if we acquired the selection
|
||||
-- SKIPPED: send client message indicating that we are now the WM
|
||||
|
||||
-- wait for old WM to go away
|
||||
fix $ \again -> do
|
||||
evt <- allocaXEvent $ \event -> do
|
||||
windowEvent dpy currentWmSnOwner structureNotifyMask event
|
||||
get_EventType event
|
||||
|
||||
when (evt /= destroyNotify) again
|
||||
mapM_ (\(m,b) -> mapM_ (grab b . (m .|.)) ems) (M.keys ba)
|
||||
|
@@ -1,5 +1,3 @@
|
||||
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- |
|
||||
-- Module : XMonad.ManageHook
|
||||
@@ -8,7 +6,6 @@
|
||||
--
|
||||
-- Maintainer : spencerjanssen@gmail.com
|
||||
-- Stability : unstable
|
||||
-- Portability : not portable, uses cunning newtype deriving
|
||||
--
|
||||
-- An EDSL for ManageHooks
|
||||
--
|
||||
@@ -21,13 +18,13 @@ module XMonad.ManageHook where
|
||||
import XMonad.Core
|
||||
import Graphics.X11.Xlib.Extras
|
||||
import Graphics.X11.Xlib (Display, Window, internAtom, wM_NAME)
|
||||
import Control.Exception.Extensible (bracket, SomeException(..))
|
||||
import qualified Control.Exception.Extensible as E
|
||||
import Control.Exception (bracket, SomeException(..))
|
||||
import qualified Control.Exception as E
|
||||
import Control.Monad.Reader
|
||||
import Data.Maybe
|
||||
import Data.Monoid
|
||||
import qualified XMonad.StackSet as W
|
||||
import XMonad.Operations (floatLocation, reveal)
|
||||
import XMonad.Operations (floatLocation, reveal, isFixedSizeOrTransient)
|
||||
|
||||
-- | Lift an 'X' action to a 'Query'.
|
||||
liftX :: X a -> Query a
|
||||
@@ -61,11 +58,11 @@ infixr 3 <&&>, <||>
|
||||
|
||||
-- | '&&' lifted to a 'Monad'.
|
||||
(<&&>) :: Monad m => m Bool -> m Bool -> m Bool
|
||||
(<&&>) = liftM2 (&&)
|
||||
x <&&> y = ifM x y (pure False)
|
||||
|
||||
-- | '||' lifted to a 'Monad'.
|
||||
(<||>) :: Monad m => m Bool -> m Bool -> m Bool
|
||||
(<||>) = liftM2 (||)
|
||||
x <||> y = ifM x (pure True) y
|
||||
|
||||
-- | Return the window title.
|
||||
title :: Query String
|
||||
@@ -76,10 +73,11 @@ title = ask >>= \w -> liftX $ do
|
||||
(internAtom d "_NET_WM_NAME" False >>= getTextProperty d w)
|
||||
`E.catch` \(SomeException _) -> getTextProperty d w wM_NAME
|
||||
extract prop = do l <- wcTextPropertyToTextList d prop
|
||||
return $ if null l then "" else head l
|
||||
return $ fromMaybe "" $ listToMaybe l
|
||||
io $ bracket getProp (xFree . tp_value) extract `E.catch` \(SomeException _) -> return ""
|
||||
|
||||
-- | Return the application name.
|
||||
-- | Return the application name; i.e., the /first/ string returned by
|
||||
-- @WM_CLASS@.
|
||||
appName :: Query String
|
||||
appName = ask >>= (\w -> liftX $ withDisplay $ \d -> fmap resName $ io $ getClassHint d w)
|
||||
|
||||
@@ -87,14 +85,15 @@ appName = ask >>= (\w -> liftX $ withDisplay $ \d -> fmap resName $ io $ getClas
|
||||
resource :: Query String
|
||||
resource = appName
|
||||
|
||||
-- | Return the resource class.
|
||||
-- | Return the resource class; i.e., the /second/ string returned by
|
||||
-- @WM_CLASS@.
|
||||
className :: Query String
|
||||
className = ask >>= (\w -> liftX $ withDisplay $ \d -> fmap resClass $ io $ getClassHint d w)
|
||||
|
||||
-- | A query that can return an arbitrary X property of type 'String',
|
||||
-- identified by name.
|
||||
stringProperty :: String -> Query String
|
||||
stringProperty p = ask >>= (\w -> liftX $ withDisplay $ \d -> fmap (fromMaybe "") $ getStringProperty d w p)
|
||||
stringProperty p = ask >>= (\w -> liftX $ withDisplay $ \d -> fromMaybe "" <$> getStringProperty d w p)
|
||||
|
||||
getStringProperty :: Display -> Window -> String -> X (Maybe String)
|
||||
getStringProperty d w p = do
|
||||
@@ -102,6 +101,10 @@ getStringProperty d w p = do
|
||||
md <- io $ getWindowProperty8 d a w
|
||||
return $ fmap (map (toEnum . fromIntegral)) md
|
||||
|
||||
-- | Return whether the window will be a floating window or not
|
||||
willFloat :: Query Bool
|
||||
willFloat = ask >>= \w -> liftX $ withDisplay $ \d -> isFixedSizeOrTransient d w
|
||||
|
||||
-- | Modify the 'WindowSet' with a pure function.
|
||||
doF :: (s -> s) -> Query (Endo s)
|
||||
doF = return . Endo
|
||||
|
@@ -1,5 +1,10 @@
|
||||
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||||
{-# LANGUAGE FlexibleContexts, FlexibleInstances, MultiParamTypeClasses, PatternGuards, TypeSynonymInstances #-}
|
||||
{-# LANGUAGE FlexibleContexts #-}
|
||||
{-# LANGUAGE FlexibleInstances #-}
|
||||
{-# LANGUAGE LambdaCase #-}
|
||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||
{-# LANGUAGE PatternGuards #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
|
||||
-- --------------------------------------------------------------------------
|
||||
-- |
|
||||
-- Module : XMonad.Operations
|
||||
@@ -8,31 +13,69 @@
|
||||
--
|
||||
-- Maintainer : dons@cse.unsw.edu.au
|
||||
-- Stability : unstable
|
||||
-- Portability : not portable, Typeable deriving, mtl, posix
|
||||
-- Portability : not portable, mtl, posix
|
||||
--
|
||||
-- Operations.
|
||||
-- Operations. A module for functions that don't cleanly fit anywhere else.
|
||||
--
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
module XMonad.Operations where
|
||||
module XMonad.Operations (
|
||||
-- * Manage One Window
|
||||
manage, unmanage, killWindow, kill, isClient,
|
||||
setInitialProperties, setWMState, setWindowBorderWithFallback,
|
||||
hide, reveal, tileWindow,
|
||||
setTopFocus, focus, isFixedSizeOrTransient,
|
||||
|
||||
-- * Manage Windows
|
||||
windows, refresh, rescreen, modifyWindowSet, windowBracket, windowBracket_, clearEvents, getCleanedScreenInfo,
|
||||
withFocused, withUnfocused,
|
||||
|
||||
-- * Keyboard and Mouse
|
||||
cleanMask, extraModifiers,
|
||||
mouseDrag, mouseMoveWindow, mouseResizeWindow,
|
||||
setButtonGrab, setFocusX, cacheNumlockMask, mkGrabs, unGrab,
|
||||
|
||||
-- * Messages
|
||||
sendMessage, broadcastMessage, sendMessageWithNoRefresh,
|
||||
sendRestart, sendReplace,
|
||||
|
||||
-- * Save and Restore State
|
||||
StateFile (..), writeStateToFile, readStateFile, restart,
|
||||
|
||||
-- * Floating Layer
|
||||
float, floatLocation,
|
||||
|
||||
-- * Window Size Hints
|
||||
D, mkAdjust, applySizeHints, applySizeHints', applySizeHintsContents,
|
||||
applyAspectHint, applyResizeIncHint, applyMaxSizeHint,
|
||||
|
||||
-- * Rectangles
|
||||
containedIn, nubScreens, pointWithin, scaleRationalRect,
|
||||
|
||||
-- * Other Utilities
|
||||
initColor, pointScreen, screenWorkspace,
|
||||
setLayout, updateLayout,
|
||||
) where
|
||||
|
||||
import XMonad.Core
|
||||
import XMonad.Layout (Full(..))
|
||||
import qualified XMonad.StackSet as W
|
||||
|
||||
import Data.Maybe
|
||||
import Data.Monoid (Endo(..))
|
||||
import Data.Monoid (Endo(..),Any(..))
|
||||
import Data.List (nub, (\\), find)
|
||||
import Data.Bits ((.|.), (.&.), complement, testBit)
|
||||
import Data.Bits ((.|.), (.&.), complement, setBit, testBit)
|
||||
import Data.Function (on)
|
||||
import Data.Ratio
|
||||
import qualified Data.Map as M
|
||||
import qualified Data.Set as S
|
||||
|
||||
import Control.Applicative((<$>), (<*>))
|
||||
import Control.Arrow (second)
|
||||
import Control.Monad.Fix (fix)
|
||||
import Control.Monad.Reader
|
||||
import Control.Monad.State
|
||||
import qualified Control.Exception.Extensible as C
|
||||
import Control.Monad (forM, forM_, guard, join, unless, void, when)
|
||||
import qualified Control.Exception as C
|
||||
|
||||
import System.IO
|
||||
import System.Directory
|
||||
@@ -42,9 +85,20 @@ import Graphics.X11.Xinerama (getScreenInfo)
|
||||
import Graphics.X11.Xlib.Extras
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- |
|
||||
-- Window manager operations
|
||||
-- manage. Add a new window to be managed in the current workspace.
|
||||
|
||||
-- | Detect whether a window has fixed size or is transient. This check
|
||||
-- can be used to determine whether the window should be floating or not
|
||||
--
|
||||
isFixedSizeOrTransient :: Display -> Window -> X Bool
|
||||
isFixedSizeOrTransient d w = do
|
||||
sh <- io $ getWMNormalHints d w
|
||||
let isFixedSize = isJust (sh_min_size sh) && sh_min_size sh == sh_max_size sh
|
||||
isTransient <- isJust <$> io (getTransientForHint d w)
|
||||
return (isFixedSize || isTransient)
|
||||
|
||||
-- |
|
||||
-- Add a new window to be managed in the current workspace.
|
||||
-- Bring it into focus.
|
||||
--
|
||||
-- Whether the window is already managed, or not, it is mapped, has its
|
||||
@@ -52,10 +106,8 @@ import Graphics.X11.Xlib.Extras
|
||||
--
|
||||
manage :: Window -> X ()
|
||||
manage w = whenX (not <$> isClient w) $ withDisplay $ \d -> do
|
||||
sh <- io $ getWMNormalHints d w
|
||||
|
||||
let isFixedSize = sh_min_size sh /= Nothing && sh_min_size sh == sh_max_size sh
|
||||
isTransient <- isJust <$> io (getTransientForHint d w)
|
||||
shouldFloat <- isFixedSizeOrTransient d w
|
||||
|
||||
rr <- snd `fmap` floatLocation w
|
||||
-- ensure that float windows don't go over the edge of the screen
|
||||
@@ -63,15 +115,15 @@ manage w = whenX (not <$> isClient w) $ withDisplay $ \d -> do
|
||||
= W.RationalRect (0.5 - wid/2) (0.5 - h/2) wid h
|
||||
adjust r = r
|
||||
|
||||
f ws | isFixedSize || isTransient = W.float w (adjust rr) . W.insertUp w . W.view i $ ws
|
||||
| otherwise = W.insertUp w ws
|
||||
f ws | shouldFloat = W.float w (adjust rr) . W.insertUp w . W.view i $ ws
|
||||
| otherwise = W.insertUp w ws
|
||||
where i = W.tag $ W.workspace $ W.current ws
|
||||
|
||||
mh <- asks (manageHook . config)
|
||||
g <- appEndo <$> userCodeDef (Endo id) (runQuery mh w)
|
||||
windows (g . f)
|
||||
|
||||
-- | unmanage. A window no longer exists, remove it from the window
|
||||
-- | A window no longer exists; remove it from the window
|
||||
-- list, on whatever workspace it is.
|
||||
--
|
||||
unmanage :: Window -> X ()
|
||||
@@ -91,9 +143,9 @@ killWindow w = withDisplay $ \d -> do
|
||||
io $ if wmdelt `elem` protocols
|
||||
then allocaXEvent $ \ev -> do
|
||||
setEventType ev clientMessage
|
||||
setClientMessageEvent ev w wmprot 32 wmdelt 0
|
||||
setClientMessageEvent ev w wmprot 32 wmdelt currentTime
|
||||
sendEvent d w False noEventMask ev
|
||||
else killClient d w >> return ()
|
||||
else void (killClient d w)
|
||||
|
||||
-- | Kill the currently focused client.
|
||||
kill :: X ()
|
||||
@@ -102,7 +154,7 @@ kill = withFocused killWindow
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Managing windows
|
||||
|
||||
-- | windows. Modify the current window list with a pure function, and refresh
|
||||
-- | Modify the current window list with a pure function, and refresh
|
||||
windows :: (WindowSet -> WindowSet) -> X ()
|
||||
windows f = do
|
||||
XState { windowset = old } <- get
|
||||
@@ -144,7 +196,8 @@ windows f = do
|
||||
|
||||
let m = W.floating ws
|
||||
flt = [(fw, scaleRationalRect viewrect r)
|
||||
| fw <- filter (flip M.member m) (W.index this)
|
||||
| fw <- filter (`M.member` m) (W.index this)
|
||||
, fw `notElem` vis
|
||||
, Just r <- [M.lookup fw m]]
|
||||
vs = flt ++ rs
|
||||
|
||||
@@ -170,38 +223,58 @@ windows f = do
|
||||
-- all windows that are no longer in the windowset are marked as
|
||||
-- withdrawn, it is important to do this after the above, otherwise 'hide'
|
||||
-- will overwrite withdrawnState with iconicState
|
||||
mapM_ (flip setWMState withdrawnState) (W.allWindows old \\ W.allWindows ws)
|
||||
mapM_ (`setWMState` withdrawnState) (W.allWindows old \\ W.allWindows ws)
|
||||
|
||||
isMouseFocused <- asks mouseFocused
|
||||
unless isMouseFocused $ clearEvents enterWindowMask
|
||||
asks (logHook . config) >>= userCodeDef ()
|
||||
|
||||
-- | Modify the @WindowSet@ in state with no special handling.
|
||||
modifyWindowSet :: (WindowSet -> WindowSet) -> X ()
|
||||
modifyWindowSet f = modify $ \xst -> xst { windowset = f (windowset xst) }
|
||||
|
||||
-- | Perform an @X@ action and check its return value against a predicate p.
|
||||
-- If p holds, unwind changes to the @WindowSet@ and replay them using @windows@.
|
||||
windowBracket :: (a -> Bool) -> X a -> X a
|
||||
windowBracket p action = withWindowSet $ \old -> do
|
||||
a <- action
|
||||
when (p a) . withWindowSet $ \new -> do
|
||||
modifyWindowSet $ const old
|
||||
windows $ const new
|
||||
return a
|
||||
|
||||
-- | Perform an @X@ action. If it returns @Any True@, unwind the
|
||||
-- changes to the @WindowSet@ and replay them using @windows@. This is
|
||||
-- a version of @windowBracket@ that discards the return value and
|
||||
-- handles an @X@ action that reports its need for refresh via @Any@.
|
||||
windowBracket_ :: X Any -> X ()
|
||||
windowBracket_ = void . windowBracket getAny
|
||||
|
||||
-- | Produce the actual rectangle from a screen and a ratio on that screen.
|
||||
scaleRationalRect :: Rectangle -> W.RationalRect -> Rectangle
|
||||
scaleRationalRect (Rectangle sx sy sw sh) (W.RationalRect rx ry rw rh)
|
||||
= Rectangle (sx + scale sw rx) (sy + scale sh ry) (scale sw rw) (scale sh rh)
|
||||
where scale s r = floor (toRational s * r)
|
||||
|
||||
-- | setWMState. set the WM_STATE property
|
||||
-- | Set a window's WM_STATE property.
|
||||
setWMState :: Window -> Int -> X ()
|
||||
setWMState w v = withDisplay $ \dpy -> do
|
||||
a <- atom_WM_STATE
|
||||
io $ changeProperty32 dpy w a a propModeReplace [fromIntegral v, fromIntegral none]
|
||||
|
||||
-- | Set the border color using the window's color map, if possible,
|
||||
-- otherwise fallback to the color in @Pixel@.
|
||||
-- | Set the border color using the window's color map, if possible;
|
||||
-- otherwise fall back to the color in @Pixel@.
|
||||
setWindowBorderWithFallback :: Display -> Window -> String -> Pixel -> X ()
|
||||
setWindowBorderWithFallback dpy w color basic = io $
|
||||
C.handle fallback $ do
|
||||
wa <- getWindowAttributes dpy w
|
||||
pixel <- color_pixel . fst <$> allocNamedColor dpy (wa_colormap wa) color
|
||||
pixel <- setPixelSolid . color_pixel . fst <$> allocNamedColor dpy (wa_colormap wa) color
|
||||
setWindowBorder dpy w pixel
|
||||
where
|
||||
fallback :: C.SomeException -> IO ()
|
||||
fallback e = do hPrint stderr e >> hFlush stderr
|
||||
setWindowBorder dpy w basic
|
||||
fallback _ = setWindowBorder dpy w basic
|
||||
|
||||
-- | hide. Hide a window by unmapping it, and setting Iconified.
|
||||
-- | Hide a window by unmapping it and setting Iconified.
|
||||
hide :: Window -> X ()
|
||||
hide w = whenX (gets (S.member w . mapped)) $ withDisplay $ \d -> do
|
||||
cMask <- asks $ clientMask . config
|
||||
@@ -214,15 +287,15 @@ hide w = whenX (gets (S.member w . mapped)) $ withDisplay $ \d -> do
|
||||
modify (\s -> s { waitingUnmap = M.insertWith (+) w 1 (waitingUnmap s)
|
||||
, mapped = S.delete w (mapped s) })
|
||||
|
||||
-- | reveal. Show a window by mapping it and setting Normal
|
||||
-- this is harmless if the window was already visible
|
||||
-- | Show a window by mapping it and setting Normal.
|
||||
-- This is harmless if the window was already visible.
|
||||
reveal :: Window -> X ()
|
||||
reveal w = withDisplay $ \d -> do
|
||||
setWMState w normalState
|
||||
io $ mapWindow d w
|
||||
whenX (isClient w) $ modify (\s -> s { mapped = S.insert w (mapped s) })
|
||||
|
||||
-- | Set some properties when we initially gain control of a window
|
||||
-- | Set some properties when we initially gain control of a window.
|
||||
setInitialProperties :: Window -> X ()
|
||||
setInitialProperties w = asks normalBorder >>= \nb -> withDisplay $ \d -> do
|
||||
setWMState w iconicState
|
||||
@@ -233,7 +306,7 @@ setInitialProperties w = asks normalBorder >>= \nb -> withDisplay $ \d -> do
|
||||
-- required by the border setting in 'windows'
|
||||
io $ setWindowBorder d w nb
|
||||
|
||||
-- | refresh. Render the currently visible workspaces, as determined by
|
||||
-- | Render the currently visible workspaces, as determined by
|
||||
-- the 'StackSet'. Also, set focus to the focused window.
|
||||
--
|
||||
-- This is our 'view' operation (MVC), in that it pretty prints our model
|
||||
@@ -242,7 +315,7 @@ setInitialProperties w = asks normalBorder >>= \nb -> withDisplay $ \d -> do
|
||||
refresh :: X ()
|
||||
refresh = windows id
|
||||
|
||||
-- | clearEvents. Remove all events of a given type from the event queue.
|
||||
-- | Remove all events of a given type from the event queue.
|
||||
clearEvents :: EventMask -> X ()
|
||||
clearEvents mask = withDisplay $ \d -> io $ do
|
||||
sync d False
|
||||
@@ -250,8 +323,8 @@ clearEvents mask = withDisplay $ \d -> io $ do
|
||||
more <- checkMaskEvent d mask p
|
||||
when more again -- beautiful
|
||||
|
||||
-- | tileWindow. Moves and resizes w such that it fits inside the given
|
||||
-- rectangle, including its border.
|
||||
-- | Move and resize @w@ such that it fits inside the given rectangle,
|
||||
-- including its border.
|
||||
tileWindow :: Window -> Rectangle -> X ()
|
||||
tileWindow w r = withDisplay $ \d -> withWindowAttributes d w $ \wa -> do
|
||||
-- give all windows at least 1x1 pixels
|
||||
@@ -278,27 +351,28 @@ containedIn r1@(Rectangle x1 y1 w1 h1) r2@(Rectangle x2 y2 w2 h2)
|
||||
nubScreens :: [Rectangle] -> [Rectangle]
|
||||
nubScreens xs = nub . filter (\x -> not $ any (x `containedIn`) xs) $ xs
|
||||
|
||||
-- | Cleans the list of screens according to the rules documented for
|
||||
-- | Clean the list of screens according to the rules documented for
|
||||
-- nubScreens.
|
||||
getCleanedScreenInfo :: MonadIO m => Display -> m [Rectangle]
|
||||
getCleanedScreenInfo = io . fmap nubScreens . getScreenInfo
|
||||
|
||||
-- | rescreen. The screen configuration may have changed (due to
|
||||
-- xrandr), update the state and refresh the screen, and reset the gap.
|
||||
-- | The screen configuration may have changed (due to -- xrandr),
|
||||
-- update the state and refresh the screen, and reset the gap.
|
||||
rescreen :: X ()
|
||||
rescreen = do
|
||||
xinesc <- withDisplay getCleanedScreenInfo
|
||||
|
||||
windows $ \ws@(W.StackSet { W.current = v, W.visible = vs, W.hidden = hs }) ->
|
||||
let (xs, ys) = splitAt (length xinesc) $ map W.workspace (v:vs) ++ hs
|
||||
(a:as) = zipWith3 W.Screen xs [0..] $ map SD xinesc
|
||||
in ws { W.current = a
|
||||
, W.visible = as
|
||||
, W.hidden = ys }
|
||||
rescreen = withDisplay getCleanedScreenInfo >>= \case
|
||||
[] -> trace "getCleanedScreenInfo returned []"
|
||||
xinesc:xinescs ->
|
||||
windows $ \ws@W.StackSet{ W.current = v, W.visible = vs, W.hidden = hs } ->
|
||||
let (xs, ys) = splitAt (length xinescs) (map W.workspace vs ++ hs)
|
||||
a = W.Screen (W.workspace v) 0 (SD xinesc)
|
||||
as = zipWith3 W.Screen xs [1..] $ map SD xinescs
|
||||
in ws { W.current = a
|
||||
, W.visible = as
|
||||
, W.hidden = ys }
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
|
||||
-- | setButtonGrab. Tell whether or not to intercept clicks on a given window
|
||||
-- | Tell whether or not to intercept clicks on a given window
|
||||
setButtonGrab :: Bool -> Window -> X ()
|
||||
setButtonGrab grab w = do
|
||||
pointerMode <- asks $ \c -> if clickJustFocuses (config c)
|
||||
@@ -353,7 +427,7 @@ setFocusX w = withWindowSet $ \ws -> do
|
||||
currevt <- asks currentEvent
|
||||
let inputHintSet = wmh_flags hints `testBit` inputHintBit
|
||||
|
||||
when ((inputHintSet && wmh_input hints) || (not inputHintSet)) $
|
||||
when (inputHintSet && wmh_input hints || not inputHintSet) $
|
||||
io $ do setInputFocus dpy w revertToPointerRoot 0
|
||||
when (wmtf `elem` protocols) $
|
||||
io $ allocaXEvent $ \ev -> do
|
||||
@@ -361,56 +435,171 @@ setFocusX w = withWindowSet $ \ws -> do
|
||||
setClientMessageEvent ev w wmprot 32 wmtf $ maybe currentTime event_time currevt
|
||||
sendEvent dpy w False noEventMask ev
|
||||
where event_time ev =
|
||||
if (ev_event_type ev) `elem` timedEvents then
|
||||
if ev_event_type ev `elem` timedEvents then
|
||||
ev_time ev
|
||||
else
|
||||
currentTime
|
||||
timedEvents = [ keyPress, keyRelease, buttonPress, buttonRelease, enterNotify, leaveNotify, selectionRequest ]
|
||||
|
||||
cacheNumlockMask :: X ()
|
||||
cacheNumlockMask = do
|
||||
dpy <- asks display
|
||||
ms <- io $ getModifierMapping dpy
|
||||
xs <- sequence [ do ks <- io $ keycodeToKeysym dpy kc 0
|
||||
if ks == xK_Num_Lock
|
||||
then return (setBit 0 (fromIntegral m))
|
||||
else return (0 :: KeyMask)
|
||||
| (m, kcs) <- ms, kc <- kcs, kc /= 0
|
||||
]
|
||||
modify (\s -> s { numberlockMask = foldr (.|.) 0 xs })
|
||||
|
||||
-- | Given a list of keybindings, turn the given 'KeySym's into actual
|
||||
-- 'KeyCode's and prepare them for grabbing.
|
||||
mkGrabs :: [(KeyMask, KeySym)] -> X [(KeyMask, KeyCode)]
|
||||
mkGrabs ks = withDisplay $ \dpy -> do
|
||||
let (minCode, maxCode) = displayKeycodes dpy
|
||||
allCodes = [fromIntegral minCode .. fromIntegral maxCode]
|
||||
-- build a map from keysyms to lists of keysyms (doing what
|
||||
-- XGetKeyboardMapping would do if the X11 package bound it)
|
||||
syms <- forM allCodes $ \code -> io (keycodeToKeysym dpy code 0)
|
||||
let -- keycodeToKeysym returns noSymbol for all unbound keycodes,
|
||||
-- and we don't want to grab those whenever someone accidentally
|
||||
-- uses def :: KeySym
|
||||
keysymMap = M.delete noSymbol $
|
||||
M.fromListWith (++) (zip syms [[code] | code <- allCodes])
|
||||
keysymToKeycodes sym = M.findWithDefault [] sym keysymMap
|
||||
extraMods <- extraModifiers
|
||||
pure [ (mask .|. extraMod, keycode)
|
||||
| (mask, sym) <- ks
|
||||
, keycode <- keysymToKeycodes sym
|
||||
, extraMod <- extraMods
|
||||
]
|
||||
|
||||
-- | Release XMonad's keyboard grab, so other grabbers can do their thing.
|
||||
--
|
||||
-- Start a keyboard action with this if it is going to run something
|
||||
-- that needs to do a keyboard, pointer, or server grab. For example,
|
||||
--
|
||||
-- > , ((modm .|. controlMask, xK_p), unGrab >> spawn "scrot")
|
||||
--
|
||||
-- (Other examples are certain screen lockers and "gksu".)
|
||||
-- This avoids needing to insert a pause/sleep before running the
|
||||
-- command.
|
||||
--
|
||||
-- XMonad retains the keyboard grab during key actions because if they
|
||||
-- use a submap, they need the keyboard to be grabbed, and if they had
|
||||
-- to assert their own grab then the asynchronous nature of X11 allows
|
||||
-- race conditions between XMonad, other clients, and the X server that
|
||||
-- would cause keys to sometimes be "leaked" to the focused window.
|
||||
unGrab :: X ()
|
||||
unGrab = withDisplay $ \d -> io $ do
|
||||
ungrabKeyboard d currentTime
|
||||
ungrabPointer d currentTime
|
||||
sync d False
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Message handling
|
||||
|
||||
-- | Throw a message to the current 'LayoutClass' possibly modifying how we
|
||||
-- layout the windows, then refresh.
|
||||
-- layout the windows, in which case changes are handled through a refresh.
|
||||
sendMessage :: Message a => a -> X ()
|
||||
sendMessage a = do
|
||||
w <- W.workspace . W.current <$> gets windowset
|
||||
sendMessage a = windowBracket_ $ do
|
||||
w <- gets $ W.workspace . W.current . windowset
|
||||
ml' <- handleMessage (W.layout w) (SomeMessage a) `catchX` return Nothing
|
||||
whenJust ml' $ \l' ->
|
||||
windows $ \ws -> ws { W.current = (W.current ws)
|
||||
modifyWindowSet $ \ws -> ws { W.current = (W.current ws)
|
||||
{ W.workspace = (W.workspace $ W.current ws)
|
||||
{ W.layout = l' }}}
|
||||
return (Any $ isJust ml')
|
||||
|
||||
-- | Send a message to all layouts, without refreshing.
|
||||
broadcastMessage :: Message a => a -> X ()
|
||||
broadcastMessage a = withWindowSet $ \ws -> do
|
||||
let c = W.workspace . W.current $ ws
|
||||
v = map W.workspace . W.visible $ ws
|
||||
h = W.hidden ws
|
||||
mapM_ (sendMessageWithNoRefresh a) (c : v ++ h)
|
||||
-- this is O(n²), but we can't really fix this as there's code in
|
||||
-- xmonad-contrib that touches the windowset during handleMessage
|
||||
-- (returning Nothing for changes to not get overwritten), so we
|
||||
-- unfortunately need to do this one by one and persist layout states
|
||||
-- of each workspace separately)
|
||||
let c = W.workspace . W.current $ ws
|
||||
v = map W.workspace . W.visible $ ws
|
||||
h = W.hidden ws
|
||||
mapM_ (sendMessageWithNoRefresh a) (c : v ++ h)
|
||||
|
||||
-- | Send a message to a layout, without refreshing.
|
||||
sendMessageWithNoRefresh :: Message a => a -> W.Workspace WorkspaceId (Layout Window) Window -> X ()
|
||||
sendMessageWithNoRefresh :: Message a => a -> WindowSpace -> X ()
|
||||
sendMessageWithNoRefresh a w =
|
||||
handleMessage (W.layout w) (SomeMessage a) `catchX` return Nothing >>=
|
||||
updateLayout (W.tag w)
|
||||
|
||||
-- | Update the layout field of a workspace
|
||||
-- | Update the layout field of a workspace.
|
||||
updateLayout :: WorkspaceId -> Maybe (Layout Window) -> X ()
|
||||
updateLayout i ml = whenJust ml $ \l ->
|
||||
runOnWorkspaces $ \ww -> return $ if W.tag ww == i then ww { W.layout = l} else ww
|
||||
|
||||
-- | Set the layout of the currently viewed workspace
|
||||
-- | Set the layout of the currently viewed workspace.
|
||||
setLayout :: Layout Window -> X ()
|
||||
setLayout l = do
|
||||
ss@(W.StackSet { W.current = c@(W.Screen { W.workspace = ws })}) <- gets windowset
|
||||
ss@W.StackSet{ W.current = c@W.Screen{ W.workspace = ws }} <- gets windowset
|
||||
handleMessage (W.layout ws) (SomeMessage ReleaseResources)
|
||||
windows $ const $ ss {W.current = c { W.workspace = ws { W.layout = l } } }
|
||||
windows $ const $ ss{ W.current = c{ W.workspace = ws{ W.layout = l } } }
|
||||
|
||||
-- | Signal xmonad to restart itself.
|
||||
sendRestart :: IO ()
|
||||
sendRestart = do
|
||||
dpy <- openDisplay ""
|
||||
rw <- rootWindow dpy $ defaultScreen dpy
|
||||
xmonad_restart <- internAtom dpy "XMONAD_RESTART" False
|
||||
allocaXEvent $ \e -> do
|
||||
setEventType e clientMessage
|
||||
setClientMessageEvent' e rw xmonad_restart 32 []
|
||||
sendEvent dpy rw False structureNotifyMask e
|
||||
sync dpy False
|
||||
|
||||
-- | Signal compliant window managers to exit.
|
||||
sendReplace :: IO ()
|
||||
sendReplace = do
|
||||
dpy <- openDisplay ""
|
||||
let dflt = defaultScreen dpy
|
||||
rootw <- rootWindow dpy dflt
|
||||
replace dpy dflt rootw
|
||||
|
||||
-- | Signal compliant window managers to exit.
|
||||
replace :: Display -> ScreenNumber -> Window -> IO ()
|
||||
replace dpy dflt rootw = do
|
||||
-- check for other WM
|
||||
wmSnAtom <- internAtom dpy ("WM_S" ++ show dflt) False
|
||||
currentWmSnOwner <- xGetSelectionOwner dpy wmSnAtom
|
||||
when (currentWmSnOwner /= 0) $ do
|
||||
-- prepare to receive destroyNotify for old WM
|
||||
selectInput dpy currentWmSnOwner structureNotifyMask
|
||||
|
||||
-- create off-screen window
|
||||
netWmSnOwner <- allocaSetWindowAttributes $ \attributes -> do
|
||||
set_override_redirect attributes True
|
||||
set_event_mask attributes propertyChangeMask
|
||||
let screen = defaultScreenOfDisplay dpy
|
||||
visual = defaultVisualOfScreen screen
|
||||
attrmask = cWOverrideRedirect .|. cWEventMask
|
||||
createWindow dpy rootw (-100) (-100) 1 1 0 copyFromParent copyFromParent visual attrmask attributes
|
||||
|
||||
-- try to acquire wmSnAtom, this should signal the old WM to terminate
|
||||
xSetSelectionOwner dpy wmSnAtom netWmSnOwner currentTime
|
||||
|
||||
-- SKIPPED: check if we acquired the selection
|
||||
-- SKIPPED: send client message indicating that we are now the WM
|
||||
|
||||
-- wait for old WM to go away
|
||||
fix $ \again -> do
|
||||
evt <- allocaXEvent $ \event -> do
|
||||
windowEvent dpy currentWmSnOwner structureNotifyMask event
|
||||
get_EventType event
|
||||
|
||||
when (evt /= destroyNotify) again
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Utilities
|
||||
|
||||
-- | Return workspace visible on screen 'sc', or 'Nothing'.
|
||||
-- | Return workspace visible on screen @sc@, or 'Nothing'.
|
||||
screenWorkspace :: ScreenId -> X (Maybe WorkspaceId)
|
||||
screenWorkspace sc = withWindowSet $ return . W.lookupWorkspace sc
|
||||
|
||||
@@ -418,7 +607,14 @@ screenWorkspace sc = withWindowSet $ return . W.lookupWorkspace sc
|
||||
withFocused :: (Window -> X ()) -> X ()
|
||||
withFocused f = withWindowSet $ \w -> whenJust (W.peek w) f
|
||||
|
||||
-- | 'True' if window is under management by us
|
||||
-- | Apply an 'X' operation to all unfocused windows on the current workspace, if there are any.
|
||||
withUnfocused :: (Window -> X ()) -> X ()
|
||||
withUnfocused f = withWindowSet $ \ws ->
|
||||
whenJust (W.peek ws) $ \w ->
|
||||
let unfocusedWindows = filter (/= w) $ W.index ws
|
||||
in mapM_ f unfocusedWindows
|
||||
|
||||
-- | Is the window is under management by xmonad?
|
||||
isClient :: Window -> X Bool
|
||||
isClient w = withWindowSet $ return . W.member w
|
||||
|
||||
@@ -429,16 +625,20 @@ extraModifiers = do
|
||||
nlm <- gets numberlockMask
|
||||
return [0, nlm, lockMask, nlm .|. lockMask ]
|
||||
|
||||
-- | Strip numlock\/capslock from a mask
|
||||
-- | Strip numlock\/capslock from a mask.
|
||||
cleanMask :: KeyMask -> X KeyMask
|
||||
cleanMask km = do
|
||||
nlm <- gets numberlockMask
|
||||
return (complement (nlm .|. lockMask) .&. km)
|
||||
|
||||
-- | Get the 'Pixel' value for a named color
|
||||
-- | Set the 'Pixel' alpha value to 255.
|
||||
setPixelSolid :: Pixel -> Pixel
|
||||
setPixelSolid p = p .|. 0xff000000
|
||||
|
||||
-- | Get the 'Pixel' value for a named color.
|
||||
initColor :: Display -> String -> IO (Maybe Pixel)
|
||||
initColor dpy c = C.handle (\(C.SomeException _) -> return Nothing) $
|
||||
(Just . color_pixel . fst) <$> allocNamedColor dpy colormap c
|
||||
Just . setPixelSolid . color_pixel . fst <$> allocNamedColor dpy colormap c
|
||||
where colormap = defaultColormap dpy (defaultScreen dpy)
|
||||
|
||||
------------------------------------------------------------------------
|
||||
@@ -458,9 +658,9 @@ writeStateToFile = do
|
||||
maybeShow _ = Nothing
|
||||
|
||||
wsData = W.mapLayout show . windowset
|
||||
extState = catMaybes . map maybeShow . M.toList . extensibleState
|
||||
extState = mapMaybe maybeShow . M.toList . extensibleState
|
||||
|
||||
path <- stateFileName
|
||||
path <- asks $ stateFileName . directories
|
||||
stateData <- gets (\s -> StateFile (wsData s) (extState s))
|
||||
catchIO (writeFile path $ show stateData)
|
||||
|
||||
@@ -468,7 +668,7 @@ writeStateToFile = do
|
||||
-- return that state. The state file is removed after reading it.
|
||||
readStateFile :: (LayoutClass l Window, Read (l Window)) => XConfig l -> X (Maybe XState)
|
||||
readStateFile xmc = do
|
||||
path <- stateFileName
|
||||
path <- asks $ stateFileName . directories
|
||||
|
||||
-- I'm trying really hard here to make sure we read the entire
|
||||
-- contents of the file before it is removed from the file system.
|
||||
@@ -501,23 +701,8 @@ readStateFile xmc = do
|
||||
readStrict :: Handle -> IO String
|
||||
readStrict h = hGetContents h >>= \s -> length s `seq` return s
|
||||
|
||||
-- | Migrate state from a previously running xmonad instance that used
|
||||
-- the older @--resume@ technique.
|
||||
{-# DEPRECATED migrateState "will be removed some point in the future." #-}
|
||||
migrateState :: (Functor m, MonadIO m) => String -> String -> m ()
|
||||
migrateState ws xs = do
|
||||
io (putStrLn "WARNING: --resume is no longer supported.")
|
||||
whenJust stateData $ \s -> do
|
||||
path <- stateFileName
|
||||
catchIO (writeFile path $ show s)
|
||||
where
|
||||
stateData = StateFile <$> maybeRead ws <*> maybeRead xs
|
||||
maybeRead s = case reads s of
|
||||
[(x, "")] -> Just x
|
||||
_ -> Nothing
|
||||
|
||||
-- | @restart name resume@. Attempt to restart xmonad by executing the program
|
||||
-- @name@. If @resume@ is 'True', restart with the current window state.
|
||||
-- | @restart name resume@ attempts to restart xmonad by executing the program
|
||||
-- @name@. If @resume@ is 'True', restart with the current window state.
|
||||
-- When executing another window manager, @resume@ should be 'False'.
|
||||
restart :: String -> Bool -> X ()
|
||||
restart prog resume = do
|
||||
@@ -527,33 +712,49 @@ restart prog resume = do
|
||||
catchIO (executeFile prog True [] Nothing)
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- | Floating layer support
|
||||
-- Floating layer support
|
||||
|
||||
-- | Given a window, find the screen it is located on, and compute
|
||||
-- the geometry of that window wrt. that screen.
|
||||
-- the geometry of that window WRT that screen.
|
||||
floatLocation :: Window -> X (ScreenId, W.RationalRect)
|
||||
floatLocation w =
|
||||
catchX go $ do
|
||||
-- Fallback solution if `go' fails. Which it might, since it
|
||||
-- calls `getWindowAttributes'.
|
||||
sc <- W.current <$> gets windowset
|
||||
sc <- gets $ W.current . windowset
|
||||
return (W.screen sc, W.RationalRect 0 0 1 1)
|
||||
|
||||
where fi x = fromIntegral x
|
||||
go = withDisplay $ \d -> do
|
||||
where go = withDisplay $ \d -> do
|
||||
ws <- gets windowset
|
||||
sh <- io $ getWMNormalHints d w
|
||||
wa <- io $ getWindowAttributes d w
|
||||
let bw = (fromIntegral . wa_border_width) wa
|
||||
sc <- fromMaybe (W.current ws) <$> pointScreen (fi $ wa_x wa) (fi $ wa_y wa)
|
||||
point_sc <- pointScreen (fi $ wa_x wa) (fi $ wa_y wa)
|
||||
managed <- isClient w
|
||||
|
||||
let sr = screenRect . W.screenDetail $ sc
|
||||
rr = W.RationalRect ((fi (wa_x wa) - fi (rect_x sr)) % fi (rect_width sr))
|
||||
((fi (wa_y wa) - fi (rect_y sr)) % fi (rect_height sr))
|
||||
(fi (wa_width wa + bw*2) % fi (rect_width sr))
|
||||
(fi (wa_height wa + bw*2) % fi (rect_height sr))
|
||||
-- ignore pointScreen for new windows unless it's the current
|
||||
-- screen, otherwise the float's relative size is computed against
|
||||
-- a different screen and the float ends up with the wrong size
|
||||
let sr_eq = (==) `on` fmap (screenRect . W.screenDetail)
|
||||
sc = fromMaybe (W.current ws) $
|
||||
if managed || point_sc `sr_eq` Just (W.current ws) then point_sc else Nothing
|
||||
sr = screenRect . W.screenDetail $ sc
|
||||
x = (fi (wa_x wa) - fi (rect_x sr)) % fi (rect_width sr)
|
||||
y = (fi (wa_y wa) - fi (rect_y sr)) % fi (rect_height sr)
|
||||
(width, height) = applySizeHintsContents sh (wa_width wa, wa_height wa)
|
||||
rwidth = fi (width + bw*2) % fi (rect_width sr)
|
||||
rheight = fi (height + bw*2) % fi (rect_height sr)
|
||||
-- adjust x/y of unmanaged windows if we ignored or didn't get pointScreen,
|
||||
-- it might be out of bounds otherwise
|
||||
rr = if managed || point_sc `sr_eq` Just sc
|
||||
then W.RationalRect x y rwidth rheight
|
||||
else W.RationalRect (0.5 - rwidth/2) (0.5 - rheight/2) rwidth rheight
|
||||
|
||||
return (W.screen sc, rr)
|
||||
|
||||
fi :: (Integral a, Num b) => a -> b
|
||||
fi = fromIntegral
|
||||
|
||||
-- | Given a point, determine the screen (if any) that contains it.
|
||||
pointScreen :: Position -> Position
|
||||
-> X (Maybe (W.Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail))
|
||||
@@ -584,14 +785,20 @@ float w = do
|
||||
|
||||
-- | Accumulate mouse motion events
|
||||
mouseDrag :: (Position -> Position -> X ()) -> X () -> X ()
|
||||
mouseDrag f done = do
|
||||
mouseDrag = mouseDragCursor Nothing
|
||||
|
||||
-- | Like 'mouseDrag', but with the ability to specify a custom cursor
|
||||
-- shape.
|
||||
mouseDragCursor :: Maybe Glyph -> (Position -> Position -> X ()) -> X () -> X ()
|
||||
mouseDragCursor cursorGlyph f done = do
|
||||
drag <- gets dragging
|
||||
case drag of
|
||||
Just _ -> return () -- error case? we're already dragging
|
||||
Nothing -> do
|
||||
XConf { theRoot = root, display = d } <- ask
|
||||
io $ grabPointer d root False (buttonReleaseMask .|. pointerMotionMask)
|
||||
grabModeAsync grabModeAsync none none currentTime
|
||||
io $ do cursor <- maybe (pure none) (createFontCursor d) cursorGlyph
|
||||
grabPointer d root False (buttonReleaseMask .|. pointerMotionMask)
|
||||
grabModeAsync grabModeAsync none cursor currentTime
|
||||
modify $ \s -> s { dragging = Just (motion, cleanup) }
|
||||
where
|
||||
cleanup = do
|
||||
@@ -602,39 +809,41 @@ mouseDrag f done = do
|
||||
clearEvents pointerMotionMask
|
||||
return z
|
||||
|
||||
-- | drag the window under the cursor with the mouse while it is dragged
|
||||
-- | Drag the window under the cursor with the mouse while it is dragged.
|
||||
mouseMoveWindow :: Window -> X ()
|
||||
mouseMoveWindow w = whenX (isClient w) $ withDisplay $ \d -> do
|
||||
io $ raiseWindow d w
|
||||
wa <- io $ getWindowAttributes d w
|
||||
(_, _, _, ox', oy', _, _, _) <- io $ queryPointer d w
|
||||
let ox = fromIntegral ox'
|
||||
oy = fromIntegral oy'
|
||||
mouseDrag (\ex ey -> do
|
||||
mouseDragCursor
|
||||
(Just xC_fleur)
|
||||
(\ex ey -> do
|
||||
io $ moveWindow d w (fromIntegral (fromIntegral (wa_x wa) + (ex - ox)))
|
||||
(fromIntegral (fromIntegral (wa_y wa) + (ey - oy)))
|
||||
float w
|
||||
)
|
||||
(float w)
|
||||
|
||||
-- | resize the window under the cursor with the mouse while it is dragged
|
||||
-- | Resize the window under the cursor with the mouse while it is dragged.
|
||||
mouseResizeWindow :: Window -> X ()
|
||||
mouseResizeWindow w = whenX (isClient w) $ withDisplay $ \d -> do
|
||||
io $ raiseWindow d w
|
||||
wa <- io $ getWindowAttributes d w
|
||||
sh <- io $ getWMNormalHints d w
|
||||
io $ warpPointer d none w 0 0 0 0 (fromIntegral (wa_width wa)) (fromIntegral (wa_height wa))
|
||||
mouseDrag (\ex ey -> do
|
||||
mouseDragCursor
|
||||
(Just xC_bottom_right_corner)
|
||||
(\ex ey -> do
|
||||
io $ resizeWindow d w `uncurry`
|
||||
applySizeHintsContents sh (ex - fromIntegral (wa_x wa),
|
||||
ey - fromIntegral (wa_y wa))
|
||||
float w)
|
||||
|
||||
(float w)
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- | Support for window size hints
|
||||
-- Support for window size hints
|
||||
|
||||
-- | An alias for a (width, height) pair
|
||||
type D = (Dimension, Dimension)
|
||||
|
||||
-- | Given a window, build an adjuster function that will reduce the given
|
||||
@@ -644,7 +853,7 @@ mkAdjust w = withDisplay $ \d -> liftIO $ do
|
||||
sh <- getWMNormalHints d w
|
||||
wa <- C.try $ getWindowAttributes d w
|
||||
case wa of
|
||||
Left err -> const (return id) (err :: C.SomeException)
|
||||
Left (_ :: C.SomeException) -> return id
|
||||
Right wa' ->
|
||||
let bw = fromIntegral $ wa_border_width wa'
|
||||
in return $ applySizeHints bw sh
|
||||
@@ -662,7 +871,7 @@ applySizeHintsContents :: Integral a => SizeHints -> (a, a) -> D
|
||||
applySizeHintsContents sh (w, h) =
|
||||
applySizeHints' sh (fromIntegral $ max 1 w, fromIntegral $ max 1 h)
|
||||
|
||||
-- | XXX comment me
|
||||
-- | Use X11 size hints to scale a pair of dimensions.
|
||||
applySizeHints' :: SizeHints -> D -> D
|
||||
applySizeHints' sh =
|
||||
maybe id applyMaxSizeHint (sh_max_size sh)
|
||||
|
@@ -1,4 +1,5 @@
|
||||
{-# LANGUAGE PatternGuards #-}
|
||||
{-# LANGUAGE DeriveFunctor #-}
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- |
|
||||
@@ -52,9 +53,13 @@ module XMonad.StackSet (
|
||||
) where
|
||||
|
||||
import Prelude hiding (filter)
|
||||
import Control.Applicative.Backwards (Backwards (Backwards, forwards))
|
||||
import Data.Foldable (foldr, toList)
|
||||
import Data.Maybe (listToMaybe,isJust,fromMaybe)
|
||||
import qualified Data.List as L (deleteBy,find,splitAt,filter,nub)
|
||||
import Data.List ( (\\) )
|
||||
import qualified Data.List.NonEmpty as NE
|
||||
import Data.List.NonEmpty (NonEmpty((:|)))
|
||||
import qualified Data.Map as M (Map,insert,delete,empty)
|
||||
|
||||
-- $intro
|
||||
@@ -85,25 +90,27 @@ import qualified Data.Map as M (Map,insert,delete,empty)
|
||||
-- continuation reified as a data structure.
|
||||
--
|
||||
-- The Zipper lets us replace an item deep in a complex data
|
||||
-- structure, e.g., a tree or a term, without an mutation. The
|
||||
-- structure, e.g., a tree or a term, without a mutation. The
|
||||
-- resulting data structure will share as much of its components with
|
||||
-- the old structure as possible.
|
||||
--
|
||||
-- Oleg Kiselyov, 27 Apr 2005, haskell\@, "Zipper as a delimited continuation"
|
||||
-- <https://mail.haskell.org/pipermail/haskell/2005-April/015769.html Oleg Kiselyov, 27 Apr 2005, haskell\@, "Zipper as a delimited continuation">
|
||||
--
|
||||
-- We use the zipper to keep track of the focused workspace and the
|
||||
-- focused window on each workspace, allowing us to have correct focus
|
||||
-- by construction. We closely follow Huet's original implementation:
|
||||
--
|
||||
-- G. Huet, /Functional Pearl: The Zipper/,
|
||||
-- 1997, J. Functional Programming 75(5):549-554.
|
||||
-- and:
|
||||
-- R. Hinze and J. Jeuring, /Functional Pearl: The Web/.
|
||||
-- <https://www.st.cs.uni-saarland.de/edu/seminare/2005/advanced-fp/docs/huet-zipper.pdf G. Huet, Functional Pearl: The Zipper; 1997, J. Functional Programming 75(5):549–554>
|
||||
--
|
||||
-- and Conor McBride's zipper differentiation paper.
|
||||
-- Another good reference is:
|
||||
-- and
|
||||
--
|
||||
-- The Zipper, Haskell wikibook
|
||||
-- <https://dspace.library.uu.nl/handle/1874/2532 R. Hinze and J. Jeuring, Functional Pearl: Weaving a Web>
|
||||
--
|
||||
-- and
|
||||
--
|
||||
-- <http://strictlypositive.org/diff.pdf Conor McBride, The Derivative of a Regular Type is its Type of One-Hole Contexts>.
|
||||
--
|
||||
-- Another good reference is: <https://wiki.haskell.org/Zipper The Zipper, Haskell wikibook>
|
||||
|
||||
-- $xinerama
|
||||
-- Xinerama in X11 lets us view multiple virtual workspaces
|
||||
@@ -151,7 +158,7 @@ data Workspace i l a = Workspace { tag :: !i, layout :: l, stack :: Maybe (Stac
|
||||
deriving (Show, Read, Eq)
|
||||
|
||||
-- | A structure for window geometries
|
||||
data RationalRect = RationalRect Rational Rational Rational Rational
|
||||
data RationalRect = RationalRect !Rational !Rational !Rational !Rational
|
||||
deriving (Show, Read, Eq)
|
||||
|
||||
-- |
|
||||
@@ -175,8 +182,19 @@ data RationalRect = RationalRect Rational Rational Rational Rational
|
||||
data Stack a = Stack { focus :: !a -- focused thing in this set
|
||||
, up :: [a] -- clowns to the left
|
||||
, down :: [a] } -- jokers to the right
|
||||
deriving (Show, Read, Eq)
|
||||
deriving (Show, Read, Eq, Functor)
|
||||
|
||||
instance Foldable Stack where
|
||||
toList = integrate
|
||||
foldr f z = foldr f z . toList
|
||||
|
||||
instance Traversable Stack where
|
||||
traverse f s =
|
||||
flip Stack
|
||||
-- 'Backwards' applies the Applicative in reverse order.
|
||||
<$> forwards (traverse (Backwards . f) (up s))
|
||||
<*> f (focus s)
|
||||
<*> traverse f (down s)
|
||||
|
||||
-- | this function indicates to catch that an error is expected
|
||||
abort :: String -> a
|
||||
@@ -194,10 +212,11 @@ abort x = error $ "xmonad: StackSet: " ++ x
|
||||
-- Xinerama: Virtual workspaces are assigned to physical screens, starting at 0.
|
||||
--
|
||||
new :: (Integral s) => l -> [i] -> [sd] -> StackSet i l a s sd
|
||||
new l wids m | not (null wids) && length m <= length wids && not (null m)
|
||||
= StackSet cur visi unseen M.empty
|
||||
where (seen,unseen) = L.splitAt (length m) $ map (\i -> Workspace i l Nothing) wids
|
||||
(cur:visi) = [ Screen i s sd | (i, s, sd) <- zip3 seen [0..] m ]
|
||||
new l (wid:wids) (m:ms) | length ms <= length wids
|
||||
= StackSet cur visi (map ws unseen) M.empty
|
||||
where ws i = Workspace i l Nothing
|
||||
(seen, unseen) = L.splitAt (length ms) wids
|
||||
cur:visi = Screen (ws wid) 0 m : [ Screen (ws i) s sd | (i, s, sd) <- zip3 seen [1..] ms ]
|
||||
-- now zip up visibles with their screen id
|
||||
new _ _ _ = abort "non-positive argument to StackSet.new"
|
||||
|
||||
@@ -224,7 +243,7 @@ view i s
|
||||
|
||||
| otherwise = s -- not a member of the stackset
|
||||
|
||||
where equating f = \x y -> f x == f y
|
||||
where equating f x y = f x == f y
|
||||
|
||||
-- 'Catch'ing this might be hard. Relies on monotonically increasing
|
||||
-- workspace tags defined in 'new'
|
||||
@@ -297,7 +316,7 @@ integrate :: Stack a -> [a]
|
||||
integrate (Stack x l r) = reverse l ++ x : r
|
||||
|
||||
-- |
|
||||
-- /O(n)/ Flatten a possibly empty stack into a list.
|
||||
-- /O(n)/. Flatten a possibly empty stack into a list.
|
||||
integrate' :: Maybe (Stack a) -> [a]
|
||||
integrate' = maybe [] integrate
|
||||
|
||||
@@ -329,32 +348,44 @@ filter p (Stack f ls rs) = case L.filter p (f:rs) of
|
||||
index :: StackSet i l a s sd -> [a]
|
||||
index = with [] integrate
|
||||
|
||||
-- |
|
||||
-- /O(1), O(w) on the wrapping case/.
|
||||
--
|
||||
-- focusUp, focusDown. Move the window focus up or down the stack,
|
||||
-- wrapping if we reach the end. The wrapping should model a 'cycle'
|
||||
-- on the current stack. The 'master' window, and window order,
|
||||
-- | /O(1), O(w) on the wrapping case/. Move the window focus up the
|
||||
-- stack, wrapping if we reach the end. The wrapping should model a
|
||||
-- @cycle@ on the current stack. The @master@ window and window order
|
||||
-- are unaffected by movement of focus.
|
||||
--
|
||||
-- swapUp, swapDown, swap the neighbour in the stack ordering, wrapping
|
||||
-- if we reach the end. Again the wrapping model should 'cycle' on
|
||||
-- the current stack.
|
||||
--
|
||||
focusUp, focusDown, swapUp, swapDown :: StackSet i l a s sd -> StackSet i l a s sd
|
||||
focusUp :: StackSet i l a s sd -> StackSet i l a s sd
|
||||
focusUp = modify' focusUp'
|
||||
|
||||
-- | /O(1), O(w) on the wrapping case/. Like 'focusUp', but move the
|
||||
-- window focus down the stack.
|
||||
focusDown :: StackSet i l a s sd -> StackSet i l a s sd
|
||||
focusDown = modify' focusDown'
|
||||
|
||||
-- | /O(1), O(w) on the wrapping case/. Swap the upwards (left)
|
||||
-- neighbour in the stack ordering, wrapping if we reach the end. Much
|
||||
-- like for 'focusUp' and 'focusDown', the wrapping model should 'cycle'
|
||||
-- on the current stack.
|
||||
swapUp :: StackSet i l a s sd -> StackSet i l a s sd
|
||||
swapUp = modify' swapUp'
|
||||
|
||||
-- | /O(1), O(w) on the wrapping case/. Like 'swapUp', but for swapping
|
||||
-- the downwards (right) neighbour.
|
||||
swapDown :: StackSet i l a s sd -> StackSet i l a s sd
|
||||
swapDown = modify' (reverseStack . swapUp' . reverseStack)
|
||||
|
||||
-- | Variants of 'focusUp' and 'focusDown' that work on a
|
||||
-- | A variant of 'focusUp' with the same asymptotics that works on a
|
||||
-- 'Stack' rather than an entire 'StackSet'.
|
||||
focusUp', focusDown' :: Stack a -> Stack a
|
||||
focusUp' :: Stack a -> Stack a
|
||||
focusUp' (Stack t (l:ls) rs) = Stack l ls (t:rs)
|
||||
focusUp' (Stack t [] rs) = Stack x xs [] where (x:xs) = reverse (t:rs)
|
||||
focusDown' = reverseStack . focusUp' . reverseStack
|
||||
focusUp' (Stack t [] rs) = Stack x xs []
|
||||
where (x :| xs) = NE.reverse (t :| rs)
|
||||
|
||||
-- | A variant of 'focusDown' with the same asymptotics that works on a
|
||||
-- 'Stack' rather than an entire 'StackSet'.
|
||||
focusDown' :: Stack a -> Stack a
|
||||
focusDown' = reverseStack . focusUp' . reverseStack
|
||||
|
||||
-- | A variant of 'spawUp' with the same asymptotics that works on a
|
||||
-- 'Stack' rather than an entire 'StackSet'.
|
||||
swapUp' :: Stack a -> Stack a
|
||||
swapUp' (Stack t (l:ls) rs) = Stack t ls (l:rs)
|
||||
swapUp' (Stack t [] rs) = Stack t (reverse rs) []
|
||||
@@ -508,8 +539,8 @@ sink w s = s { floating = M.delete w (floating s) }
|
||||
-- Focus stays with the item moved.
|
||||
swapMaster :: StackSet i l a s sd -> StackSet i l a s sd
|
||||
swapMaster = modify' $ \c -> case c of
|
||||
Stack _ [] _ -> c -- already master.
|
||||
Stack t ls rs -> Stack t [] (xs ++ x : rs) where (x:xs) = reverse ls
|
||||
Stack _ [] _ -> c -- already master.
|
||||
Stack t (l:ls) rs -> Stack t [] (xs ++ x : rs) where (x :| xs) = NE.reverse (l :| ls)
|
||||
|
||||
-- natural! keep focus, move current to the top, move top to current.
|
||||
|
||||
@@ -525,8 +556,8 @@ shiftMaster = modify' $ \c -> case c of
|
||||
-- | /O(s)/. Set focus to the master window.
|
||||
focusMaster :: StackSet i l a s sd -> StackSet i l a s sd
|
||||
focusMaster = modify' $ \c -> case c of
|
||||
Stack _ [] _ -> c
|
||||
Stack t ls rs -> Stack x [] (xs ++ t : rs) where (x:xs) = reverse ls
|
||||
Stack _ [] _ -> c
|
||||
Stack t (l:ls) rs -> Stack x [] (xs ++ t : rs) where (x :| xs) = NE.reverse (l :| ls)
|
||||
|
||||
--
|
||||
-- ---------------------------------------------------------------------
|
||||
|
14
stack.yaml
14
stack.yaml
@@ -1,7 +1,15 @@
|
||||
resolver: lts-7.19
|
||||
resolver: lts-21.12
|
||||
|
||||
packages:
|
||||
- ./
|
||||
- ./
|
||||
|
||||
extra-deps:
|
||||
- X11-1.8
|
||||
- X11-1.10
|
||||
|
||||
nix:
|
||||
packages:
|
||||
- zlib
|
||||
- xorg.libX11
|
||||
- xorg.libXrandr
|
||||
- xorg.libXScrnSaver
|
||||
- xorg.libXext
|
||||
|
@@ -36,7 +36,7 @@ instance (Integral i, Integral s, Eq a, Arbitrary a, Arbitrary l, Arbitrary sd)
|
||||
-- Pick a random window "number" in each workspace, to give focus.
|
||||
focus <- sequence [ if null windows
|
||||
then return Nothing
|
||||
else liftM Just $ choose (0, length windows - 1)
|
||||
else Just <$> choose (0, length windows - 1)
|
||||
| windows <- wsWindows ]
|
||||
|
||||
let tags = [1 .. fromIntegral numWs]
|
||||
@@ -80,7 +80,7 @@ newtype NonEmptyWindowsStackSet = NonEmptyWindowsStackSet T
|
||||
|
||||
instance Arbitrary NonEmptyWindowsStackSet where
|
||||
arbitrary =
|
||||
NonEmptyWindowsStackSet `fmap` (arbitrary `suchThat` (not . null . allWindows))
|
||||
NonEmptyWindowsStackSet <$> (arbitrary `suchThat` (not . null . allWindows))
|
||||
|
||||
instance Arbitrary Rectangle where
|
||||
arbitrary = Rectangle <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
|
||||
@@ -99,7 +99,7 @@ newtype NonEmptyNubList a = NonEmptyNubList [a]
|
||||
deriving ( Eq, Ord, Show, Read )
|
||||
|
||||
instance (Eq a, Arbitrary a) => Arbitrary (NonEmptyNubList a) where
|
||||
arbitrary = NonEmptyNubList `fmap` ((liftM nub arbitrary) `suchThat` (not . null))
|
||||
arbitrary = NonEmptyNubList <$> ((nub <$> arbitrary) `suchThat` (not . null))
|
||||
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ arbitraryTag :: T -> Gen Tag
|
||||
arbitraryTag x = do
|
||||
let ts = tags x
|
||||
-- There must be at least 1 workspace, thus at least 1 tag.
|
||||
idx <- choose (0, (length ts) - 1)
|
||||
idx <- choose (0, length ts - 1)
|
||||
return $ ts!!idx
|
||||
|
||||
-- | Pull out an arbitrary window from a StackSet that is guaranteed to have a
|
||||
@@ -136,5 +136,5 @@ arbitraryWindow :: NonEmptyWindowsStackSet -> Gen Window
|
||||
arbitraryWindow (NonEmptyWindowsStackSet x) = do
|
||||
let ws = allWindows x
|
||||
-- We know that there are at least 1 window in a NonEmptyWindowsStackSet.
|
||||
idx <- choose(0, (length ws) - 1)
|
||||
idx <- choose (0, length ws - 1)
|
||||
return $ ws!!idx
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import Test.QuickCheck
|
||||
|
||||
-- Our QC instances and properties.
|
||||
-- Our QC instances and properties:
|
||||
import Instances
|
||||
import Properties.Delete
|
||||
import Properties.Failure
|
||||
@@ -166,11 +166,16 @@ tests =
|
||||
-- tall layout
|
||||
|
||||
,("tile 1 window fullsize", property prop_tile_fullscreen)
|
||||
,("tile max ratio", property prop_tile_max_ratio)
|
||||
,("tile min ratio", property prop_tile_min_ratio)
|
||||
,("tiles never overlap", property prop_tile_non_overlap)
|
||||
,("split horizontal", property prop_split_horizontal)
|
||||
,("split vertical", property prop_split_vertical)
|
||||
|
||||
,("pure layout tall", property prop_purelayout_tall)
|
||||
,("pure layout tall", property prop_purelayout_tall)
|
||||
{- Following two test cases should be automatically generated by QuickCheck ideally, but it fails. -}
|
||||
,("pure layout tall: ratio = 0", property (\n d rect -> prop_purelayout_tall n d 0 rect))
|
||||
,("pure layout tall: ratio = 1", property (\n d rect -> prop_purelayout_tall n d 1 rect))
|
||||
,("send shrink tall", property prop_shrink_tall)
|
||||
,("send expand tall", property prop_expand_tall)
|
||||
,("send incmaster tall", property prop_incmaster_tall)
|
||||
@@ -196,6 +201,5 @@ tests =
|
||||
,("pointWithin", property prop_point_within)
|
||||
,("pointWithin mirror", property prop_point_within_mirror)
|
||||
|
||||
]
|
||||
|
||||
|
||||
] <>
|
||||
prop_laws_Stack
|
||||
|
@@ -18,7 +18,7 @@ prop_delete x =
|
||||
where _ = x :: T
|
||||
|
||||
-- delete is reversible with 'insert'.
|
||||
-- It is the identiy, except for the 'master', which is reset on insert and delete.
|
||||
-- It is the identity, except for the 'master', which is reset on insert and delete.
|
||||
--
|
||||
prop_delete_insert (x :: T) =
|
||||
case peek x of
|
||||
@@ -64,7 +64,7 @@ prop_delete_focus_not_end = do
|
||||
-- last one in the stack.
|
||||
`suchThat` \(x' :: T) ->
|
||||
let currWins = index x'
|
||||
in length (currWins) >= 2 && peek x' /= Just (last currWins)
|
||||
in length currWins >= 2 && peek x' /= Just (last currWins)
|
||||
-- This is safe, as we know there are >= 2 windows
|
||||
let Just n = peek x
|
||||
return $ peek (delete n x) == peek (focusDown x)
|
||||
|
@@ -2,7 +2,7 @@ module Properties.Failure where
|
||||
|
||||
import XMonad.StackSet hiding (filter)
|
||||
|
||||
import qualified Control.Exception.Extensible as C
|
||||
import qualified Control.Exception as C
|
||||
import System.IO.Unsafe
|
||||
import Data.List (isPrefixOf)
|
||||
|
||||
|
@@ -32,8 +32,8 @@ prop_focusWindow_master (NonNegative n) (x :: T) =
|
||||
in index (focusWindow (s !! i) x) == index x
|
||||
|
||||
-- shifting focus is trivially reversible
|
||||
prop_focus_left (x :: T) = (focusUp (focusDown x)) == x
|
||||
prop_focus_right (x :: T) = (focusDown (focusUp x)) == x
|
||||
prop_focus_left (x :: T) = focusUp (focusDown x) == x
|
||||
prop_focus_right (x :: T) = focusDown (focusUp x) == x
|
||||
|
||||
-- focus master is idempotent
|
||||
prop_focusMaster_idem (x :: T) = focusMaster x == focusMaster (focusMaster x)
|
||||
@@ -47,9 +47,9 @@ prop_focusWindow_works (NonNegative (n :: Int)) (x :: T) =
|
||||
in (focus . fromJust . stack . workspace . current) (focusWindow (s !! i) x) == (s !! i)
|
||||
|
||||
-- rotation through the height of a stack gets us back to the start
|
||||
prop_focus_all_l (x :: T) = (foldr (const focusUp) x [1..n]) == x
|
||||
prop_focus_all_l (x :: T) = foldr (const focusUp) x [1..n] == x
|
||||
where n = length (index x)
|
||||
prop_focus_all_r (x :: T) = (foldr (const focusDown) x [1..n]) == x
|
||||
prop_focus_all_r (x :: T) = foldr (const focusDown) x [1..n] == x
|
||||
where n = length (index x)
|
||||
|
||||
-- prop_rotate_all (x :: T) = f (f x) == f x
|
||||
|
@@ -35,7 +35,7 @@ prop_greedyView_local (x :: T) = do
|
||||
-- greedyView is idempotent
|
||||
prop_greedyView_idem (x :: T) = do
|
||||
n <- arbitraryTag x
|
||||
return $ greedyView n (greedyView n x) == (greedyView n x)
|
||||
return $ greedyView n (greedyView n x) == greedyView n x
|
||||
|
||||
-- greedyView is reversible, though shuffles the order of hidden/visible
|
||||
prop_greedyView_reversible (x :: T) = do
|
||||
|
@@ -46,7 +46,7 @@ prop_insert_delete x = do
|
||||
|
||||
-- inserting n elements increases current stack size by n
|
||||
prop_size_insert is (EmptyStackSet x) =
|
||||
size (foldr insertUp x ws ) == (length ws)
|
||||
size (foldr insertUp x ws) == length ws
|
||||
where
|
||||
ws = nub is
|
||||
size = length . index
|
||||
|
@@ -29,6 +29,6 @@ prop_purelayout_full rect = do
|
||||
|
||||
-- what happens when we send an IncMaster message to Full --- Nothing
|
||||
prop_sendmsg_full (NonNegative k) =
|
||||
isNothing (Full `pureMessage` (SomeMessage (IncMasterN k)))
|
||||
isNothing (Full `pureMessage` SomeMessage (IncMasterN k))
|
||||
|
||||
prop_desc_full = description Full == show Full
|
||||
|
@@ -11,8 +11,9 @@ import XMonad.Layout
|
||||
|
||||
import Graphics.X11.Xlib.Types (Rectangle(..))
|
||||
|
||||
import Data.Maybe
|
||||
import Control.Applicative
|
||||
import Data.List (sort)
|
||||
import Data.Maybe
|
||||
import Data.Ratio
|
||||
|
||||
------------------------------------------------------------------------
|
||||
@@ -27,14 +28,30 @@ prop_tile_non_overlap rect windows nmaster = noOverlaps (tile pct rect nmaster w
|
||||
where _ = rect :: Rectangle
|
||||
pct = 3 % 100
|
||||
|
||||
-- with a ratio of 1, no stack windows are drawn of there is at least
|
||||
-- one master window around.
|
||||
prop_tile_max_ratio = extremeRatio 1 drop
|
||||
|
||||
-- with a ratio of 0, no master windows are drawn at all if there are
|
||||
-- any stack windows around.
|
||||
prop_tile_min_ratio = extremeRatio 0 take
|
||||
|
||||
extremeRatio amount getRects rect = do
|
||||
w@(NonNegative windows) <- arbitrary `suchThat` (> NonNegative 0)
|
||||
NonNegative nmaster <- arbitrary `suchThat` (< w)
|
||||
let tiled = tile amount rect nmaster windows
|
||||
pure $ if nmaster == 0
|
||||
then prop_tile_non_overlap rect windows nmaster
|
||||
else all ((== 0) . rect_width) $ getRects nmaster tiled
|
||||
|
||||
-- splitting horizontally yields sensible results
|
||||
prop_split_horizontal (NonNegative n) x =
|
||||
(noOverflows (+) (rect_x x) (rect_width x)) ==>
|
||||
noOverflows (+) (rect_x x) (rect_width x) ==>
|
||||
sum (map rect_width xs) == rect_width x
|
||||
&&
|
||||
all (== rect_height x) (map rect_height xs)
|
||||
all (\s -> rect_height s == rect_height x) xs
|
||||
&&
|
||||
(map rect_x xs) == (sort $ map rect_x xs)
|
||||
map rect_x xs == sort (map rect_x xs)
|
||||
|
||||
where
|
||||
xs = splitHorizontally n x
|
||||
@@ -49,13 +66,20 @@ prop_split_vertical (r :: Rational) x =
|
||||
|
||||
|
||||
-- pureLayout works.
|
||||
prop_purelayout_tall n r1 r2 rect = do
|
||||
prop_purelayout_tall n d r rect = do
|
||||
x <- (arbitrary :: Gen T) `suchThat` (isJust . peek)
|
||||
let layout = Tall n r1 r2
|
||||
let layout = Tall n d r
|
||||
st = fromJust . stack . workspace . current $ x
|
||||
ts = pureLayout layout rect st
|
||||
ntotal = length (index x)
|
||||
return $
|
||||
length ts == length (index x)
|
||||
(if r == 0 then
|
||||
-- (<=) for Bool is the logical implication
|
||||
(0 <= n && n <= ntotal) <= (length ts == ntotal - n)
|
||||
else if r == 1 then
|
||||
(0 <= n && n <= ntotal) <= (length ts == n)
|
||||
else
|
||||
length ts == ntotal)
|
||||
&&
|
||||
noOverlaps (map snd ts)
|
||||
&&
|
||||
@@ -72,7 +96,7 @@ prop_shrink_tall (NonNegative n) (Positive delta) (NonNegative frac) =
|
||||
-- remaining fraction should shrink
|
||||
where
|
||||
l1 = Tall n delta frac
|
||||
Just l2@(Tall n' delta' frac') = l1 `pureMessage` (SomeMessage Shrink)
|
||||
Just l2@(Tall n' delta' frac') = l1 `pureMessage` SomeMessage Shrink
|
||||
-- pureMessage :: layout a -> SomeMessage -> Maybe (layout a)
|
||||
|
||||
|
||||
@@ -93,7 +117,7 @@ prop_expand_tall (NonNegative n)
|
||||
where
|
||||
frac = min 1 (n1 % d1)
|
||||
l1 = Tall n delta frac
|
||||
Just l2@(Tall n' delta' frac') = l1 `pureMessage` (SomeMessage Expand)
|
||||
Just l2@(Tall n' delta' frac') = l1 `pureMessage` SomeMessage Expand
|
||||
-- pureMessage :: layout a -> SomeMessage -> Maybe (layout a)
|
||||
|
||||
-- what happens when we send an IncMaster message to Tall
|
||||
@@ -102,7 +126,7 @@ prop_incmaster_tall (NonNegative n) (Positive delta) (NonNegative frac)
|
||||
delta == delta' && frac == frac' && n' == n + k
|
||||
where
|
||||
l1 = Tall n delta frac
|
||||
Just l2@(Tall n' delta' frac') = l1 `pureMessage` (SomeMessage (IncMasterN k))
|
||||
Just l2@(Tall n' delta' frac') = l1 `pureMessage` SomeMessage (IncMasterN k)
|
||||
-- pureMessage :: layout a -> SomeMessage -> Maybe (layout a)
|
||||
|
||||
|
||||
|
@@ -52,15 +52,14 @@ prop_aspect_hint_shrink hint (w,h) = case applyAspectHint hint (w,h) of
|
||||
-- applyAspectHint does nothing when the supplied (x,y) fits
|
||||
-- the desired range
|
||||
prop_aspect_fits =
|
||||
forAll ((,,,) <$> pos <*> pos <*> pos <*> pos) $ \ (x,y,a,b) ->
|
||||
let f v = applyAspectHint ((x, y+a), (x+b, y)) v
|
||||
in and [ noOverflows (*) x (y+a), noOverflows (*) (x+b) y ]
|
||||
forAll ((,,,) <$> pos <*> pos <*> pos <*> pos) $ \ (x,y,a,b) ->
|
||||
let f = applyAspectHint ((x, y+a), (x+b, y))
|
||||
in noOverflows (*) x (y+a) && noOverflows (*) (x+b) y
|
||||
==> f (x,y) == (x,y)
|
||||
|
||||
where pos = choose (0, 65535)
|
||||
mul a b = toInteger (a*b) /= toInteger a * toInteger b
|
||||
|
||||
prop_point_within r @ (Rectangle x y w h) =
|
||||
prop_point_within r@(Rectangle x y w h) =
|
||||
forAll ((,) <$>
|
||||
choose (0, fromIntegral w - 1) <*>
|
||||
choose (0, fromIntegral h - 1)) $
|
||||
|
@@ -27,7 +27,7 @@ prop_shift_reversible (x :: T) = do
|
||||
-- shiftMaster
|
||||
|
||||
-- focus/local/idempotent same as swapMaster:
|
||||
prop_shift_master_focus (x :: T) = peek x == (peek $ shiftMaster x)
|
||||
prop_shift_master_focus (x :: T) = peek x == peek (shiftMaster x)
|
||||
prop_shift_master_local (x :: T) = hidden_spaces x == hidden_spaces (shiftMaster x)
|
||||
prop_shift_master_idempotent (x :: T) = shiftMaster (shiftMaster x) == shiftMaster x
|
||||
-- ordering is constant modulo the focused window:
|
||||
@@ -57,14 +57,14 @@ prop_shift_win_fix_current = do
|
||||
x <- arbitrary `suchThat` \(x' :: T) ->
|
||||
-- Invariant, otherWindows are NOT in the current workspace.
|
||||
let otherWindows = allWindows x' L.\\ index x'
|
||||
in length(tags x') >= 2 && length(otherWindows) >= 1
|
||||
in length (tags x') >= 2 && not (null otherWindows)
|
||||
-- Sadly we have to construct `otherWindows` again, for the actual StackSet
|
||||
-- that got chosen.
|
||||
let otherWindows = allWindows x L.\\ index x
|
||||
-- We know such tag must exists, due to the precondition
|
||||
n <- arbitraryTag x `suchThat` (/= currentTag x)
|
||||
-- we know length is >= 1, from above precondition
|
||||
idx <- choose(0, length(otherWindows) - 1)
|
||||
idx <- choose (0, length otherWindows - 1)
|
||||
let w = otherWindows !! idx
|
||||
return $ (current $ x) == (current $ shiftWin n w x)
|
||||
return $ current x == current (shiftWin n w x)
|
||||
|
||||
|
@@ -1,4 +1,6 @@
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
||||
|
||||
module Properties.Stack where
|
||||
|
||||
import Test.QuickCheck
|
||||
@@ -9,12 +11,18 @@ import qualified XMonad.StackSet as S (filter)
|
||||
|
||||
import Data.Maybe
|
||||
|
||||
import Data.Proxy
|
||||
import Test.QuickCheck.Classes (
|
||||
Laws (lawsTypeclass, lawsProperties), Proxy1 (Proxy1),
|
||||
foldableLaws, traversableLaws,
|
||||
)
|
||||
|
||||
|
||||
-- The list returned by index should be the same length as the actual
|
||||
-- windows kept in the zipper
|
||||
prop_index_length (x :: T) =
|
||||
case stack . workspace . current $ x of
|
||||
Nothing -> length (index x) == 0
|
||||
Nothing -> null (index x)
|
||||
Just it -> length (index x) == length (focus it : up it ++ down it)
|
||||
|
||||
|
||||
@@ -33,7 +41,7 @@ prop_allWindowsMember (NonEmptyWindowsStackSet x) = do
|
||||
-- which is a key component in this test (together with member).
|
||||
let ws = allWindows x
|
||||
-- We know that there are at least 1 window in a NonEmptyWindowsStackSet.
|
||||
idx <- choose(0, (length ws) - 1)
|
||||
idx <- choose (0, length ws - 1)
|
||||
return $ member (ws!!idx) x
|
||||
|
||||
|
||||
@@ -46,6 +54,24 @@ prop_filter_order (x :: T) =
|
||||
-- differentiate should return Nothing if the list is empty or Just stack, with
|
||||
-- the first element of the list is current, and the rest of the list is down.
|
||||
prop_differentiate xs =
|
||||
if null xs then differentiate xs == Nothing
|
||||
else (differentiate xs) == Just (Stack (head xs) [] (tail xs))
|
||||
if null xs then isNothing (differentiate xs)
|
||||
else differentiate xs == Just (Stack (head xs) [] (tail xs))
|
||||
where _ = xs :: [Int]
|
||||
|
||||
|
||||
-- Check type class laws of 'Data.Foldable.Foldable' and 'Data.Traversable.Traversable'.
|
||||
newtype TestStack a = TestStack (Stack a)
|
||||
deriving (Eq, Read, Show, Foldable, Functor)
|
||||
|
||||
instance (Arbitrary a) => Arbitrary (TestStack a) where
|
||||
arbitrary = TestStack <$> (Stack <$> arbitrary <*> arbitrary <*> arbitrary)
|
||||
shrink = traverse shrink
|
||||
|
||||
instance Traversable TestStack where
|
||||
traverse f (TestStack sx) = fmap TestStack (traverse f sx)
|
||||
|
||||
prop_laws_Stack = format (foldableLaws p) <> format (traversableLaws p)
|
||||
where
|
||||
p = Proxy :: Proxy TestStack
|
||||
format laws = [ ("Stack: " <> lawsTypeclass laws <> ": " <> name, prop)
|
||||
| (name, prop) <- lawsProperties laws ]
|
||||
|
@@ -58,7 +58,7 @@ invariant (s :: T) = and
|
||||
-- inBounds = and [ w >=0 && w < size s | (w,sc) <- M.assocs (screens s) ]
|
||||
|
||||
monotonic [] = True
|
||||
monotonic (x:[]) = True
|
||||
monotonic [x] = True
|
||||
monotonic (x:y:zs) | x == y-1 = monotonic (y:zs)
|
||||
| otherwise = False
|
||||
|
||||
@@ -126,7 +126,7 @@ prop_empty (EmptyStackSet x) =
|
||||
prop_empty_current (EmptyStackSet x) = currentTag x == head (tags x)
|
||||
|
||||
-- no windows will be a member of an empty workspace
|
||||
prop_member_empty i (EmptyStackSet x) = member i x == False
|
||||
prop_member_empty i (EmptyStackSet x) = not (member i x)
|
||||
|
||||
-- peek either yields nothing on the Empty workspace, or Just a valid window
|
||||
prop_member_peek (x :: T) =
|
||||
|
@@ -11,8 +11,8 @@ import XMonad.StackSet hiding (filter)
|
||||
-- swapUp, swapDown, swapMaster: reordiring windows
|
||||
|
||||
-- swap is trivially reversible
|
||||
prop_swap_left (x :: T) = (swapUp (swapDown x)) == x
|
||||
prop_swap_right (x :: T) = (swapDown (swapUp x)) == x
|
||||
prop_swap_left (x :: T) = swapUp (swapDown x) == x
|
||||
prop_swap_right (x :: T) = swapDown (swapUp x) == x
|
||||
-- TODO swap is reversible
|
||||
-- swap is reversible, but involves moving focus back the window with
|
||||
-- master on it. easy to do with a mouse...
|
||||
@@ -26,12 +26,12 @@ prop_promote_reversible x b = (not . null . fromMaybe [] . flip index x . curren
|
||||
-}
|
||||
|
||||
-- swap doesn't change focus
|
||||
prop_swap_master_focus (x :: T) = peek x == (peek $ swapMaster x)
|
||||
prop_swap_master_focus (x :: T) = peek x == peek (swapMaster x)
|
||||
-- = case peek x of
|
||||
-- Nothing -> True
|
||||
-- Just f -> focus (stack (workspace $ current (swap x))) == f
|
||||
prop_swap_left_focus (x :: T) = peek x == (peek $ swapUp x)
|
||||
prop_swap_right_focus (x :: T) = peek x == (peek $ swapDown x)
|
||||
prop_swap_left_focus (x :: T) = peek x == peek (swapUp x)
|
||||
prop_swap_right_focus (x :: T) = peek x == peek (swapDown x)
|
||||
|
||||
-- swap is local
|
||||
prop_swap_master_local (x :: T) = hidden_spaces x == hidden_spaces (swapMaster x)
|
||||
@@ -39,9 +39,9 @@ prop_swap_left_local (x :: T) = hidden_spaces x == hidden_spaces (swapUp x)
|
||||
prop_swap_right_local (x :: T) = hidden_spaces x == hidden_spaces (swapDown x)
|
||||
|
||||
-- rotation through the height of a stack gets us back to the start
|
||||
prop_swap_all_l (x :: T) = (foldr (const swapUp) x [1..n]) == x
|
||||
prop_swap_all_l (x :: T) = foldr (const swapUp) x [1..n] == x
|
||||
where n = length (index x)
|
||||
prop_swap_all_r (x :: T) = (foldr (const swapDown) x [1..n]) == x
|
||||
prop_swap_all_r (x :: T) = foldr (const swapDown) x [1..n] == x
|
||||
where n = length (index x)
|
||||
|
||||
prop_swap_master_idempotent (x :: T) = swapMaster (swapMaster x) == swapMaster x
|
||||
|
@@ -37,7 +37,7 @@ prop_view_local (x :: T) = do
|
||||
-- view is idempotent
|
||||
prop_view_idem (x :: T) = do
|
||||
n <- arbitraryTag x
|
||||
return $ view n (view n x) == (view n x)
|
||||
return $ view n (view n x) == view n x
|
||||
|
||||
-- view is reversible, though shuffles the order of hidden/visible
|
||||
prop_view_reversible (x :: T) = do
|
||||
|
@@ -12,8 +12,8 @@ hidden_spaces x = map workspace (visible x) ++ hidden x
|
||||
-- normalise workspace list
|
||||
normal s = s { hidden = sortBy g (hidden s), visible = sortBy f (visible s) }
|
||||
where
|
||||
f = \a b -> tag (workspace a) `compare` tag (workspace b)
|
||||
g = \a b -> tag a `compare` tag b
|
||||
f a b = tag (workspace a) `compare` tag (workspace b)
|
||||
g a b = tag a `compare` tag b
|
||||
|
||||
|
||||
noOverlaps [] = True
|
||||
|
83
util/GenerateManpage.hs
Normal file → Executable file
83
util/GenerateManpage.hs
Normal file → Executable file
@@ -1,47 +1,20 @@
|
||||
{-# LANGUAGE FlexibleContexts #-}
|
||||
#!/usr/bin/env runhaskell
|
||||
|
||||
-- Generates a in-memory version of "man/xmonad.1.markdown" that has the list
|
||||
-- of known key-bindings is inserted automatically from "Config.hs". That
|
||||
-- document is then rendered with Pandoc as "man/xmonad.1" and
|
||||
-- "man/xmonad.1.html".
|
||||
-- Reads markdown (man/xmonad.1.markdown) from stdin, subtitutes
|
||||
-- ___KEYBINDINGS___ for key-binding definitions generated from
|
||||
-- src/XMonad/Config.hs, prints result to stdout.
|
||||
--
|
||||
-- Unlike the rest of xmonad, this file is released under the GNU General
|
||||
-- Public License version 2 or later.
|
||||
-- Public License version 2 or later. (Historical reasons, used to link with
|
||||
-- GPL-licensed pandoc.)
|
||||
|
||||
import Control.Monad.IO.Class (liftIO)
|
||||
import Data.Char
|
||||
import Data.List
|
||||
import qualified Data.Text as T
|
||||
import qualified Data.Text.IO as TIO
|
||||
import Text.Pandoc
|
||||
import Text.Regex.Posix
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
keybindings <- guessBindings
|
||||
|
||||
markdownSource <- readFile "./man/xmonad.1.markdown"
|
||||
|
||||
runIOorExplode $ do
|
||||
parsed <- readMarkdown (def { readerStandalone = True, readerExtensions = pandocExtensions })
|
||||
. T.pack
|
||||
. unlines
|
||||
. replace "___KEYBINDINGS___" keybindings
|
||||
. lines
|
||||
$ markdownSource
|
||||
|
||||
manTemplate <- getDefaultTemplate "man"
|
||||
manBody <- writeMan def { writerTemplate = Just manTemplate } parsed
|
||||
liftIO $ TIO.writeFile "./man/xmonad.1" $ manBody
|
||||
liftIO $ putStrLn "Documentation created: man/xmonad.1"
|
||||
|
||||
htmltemplate <- getDefaultTemplate "html"
|
||||
htmlBody <- writeHtml5String def
|
||||
{ writerTemplate = Just htmltemplate
|
||||
, writerTableOfContents = True }
|
||||
parsed
|
||||
liftIO $ TIO.writeFile "./man/xmonad.1.html" htmlBody
|
||||
liftIO $ putStrLn "Documentation created: man/xmonad.1.html"
|
||||
interact $ unlines . replace "___KEYBINDINGS___" keybindings . lines
|
||||
|
||||
-- | The format for the docstrings in "Config.hs" takes the following form:
|
||||
--
|
||||
@@ -49,7 +22,7 @@ main = do
|
||||
-- -- mod-x %! Frob the whatsit
|
||||
-- @
|
||||
--
|
||||
-- "Frob the whatsit" will be used as the description for keybinding "mod-x".--
|
||||
-- "Frob the whatsit" will be used as the description for keybinding "mod-x".
|
||||
-- If the name of the key binding is omitted, the function tries to guess it
|
||||
-- from the rest of the line. For example:
|
||||
--
|
||||
@@ -61,25 +34,33 @@ main = do
|
||||
|
||||
guessBindings :: IO String
|
||||
guessBindings = do
|
||||
buf <- readFile "./src/XMonad/Config.hs"
|
||||
return (intercalate "\n\n" (map markdownDefn (allBindings buf)))
|
||||
buf <- readFile "./src/XMonad/Config.hs"
|
||||
return (intercalate "\n\n" (map markdownDefn (allBindings buf)))
|
||||
|
||||
allBindings :: String -> [(String, String)]
|
||||
allBindings xs = map (binding . map trim) (xs =~ "(.*)--(.*)%!(.*)")
|
||||
|
||||
binding :: [String] -> (String, String)
|
||||
binding [ _, bindingLine, "", desc ] = (guessKeys bindingLine, desc)
|
||||
binding [ _, _, keyCombo, desc ] = (keyCombo, desc)
|
||||
binding x = error ("binding: called with unexpected argument " ++ show x)
|
||||
|
||||
guessKeys :: String -> String
|
||||
guessKeys line =
|
||||
case keys of
|
||||
[key] -> concat $ intersperse "-" (modifiers ++ [map toLower key])
|
||||
_ -> error ("guessKeys: unexpected number of keys " ++ show keys)
|
||||
allBindings = concatMap parseLine . lines
|
||||
where
|
||||
modifiers = map (!!1) (line =~ "(mod|shift|control)Mask")
|
||||
(_, _, _, keys) = line =~ "xK_([_[:alnum:]]+)" :: (String, String, String, [String])
|
||||
parseLine :: String -> [(String, String)]
|
||||
parseLine l
|
||||
| " -- " `isInfixOf` l
|
||||
, Just d <- parseDesc l = [(intercalate "-" (parseKeys l), d)]
|
||||
| otherwise = []
|
||||
|
||||
parseDesc :: String -> Maybe String
|
||||
parseDesc = fmap (trim . drop 4) . find (" %! " `isPrefixOf`) . tails
|
||||
|
||||
parseKeys :: String -> [String]
|
||||
parseKeys l = case lex l of
|
||||
[("", _)] -> []
|
||||
[("--", rest)] -> case words rest of
|
||||
k : "%!" : _ -> [k]
|
||||
_ -> []
|
||||
[(k, rest)] -> parseKey k ++ parseKeys rest
|
||||
|
||||
parseKey :: String -> [String]
|
||||
parseKey k | "Mask" `isSuffixOf` k = [reverse (drop 4 (reverse k))]
|
||||
| "xK_" `isPrefixOf` k = [map toLower (drop 3 k)]
|
||||
| otherwise = []
|
||||
|
||||
-- FIXME: What escaping should we be doing on these strings?
|
||||
markdownDefn :: (String, String) -> String
|
||||
|
106
xmonad.cabal
106
xmonad.cabal
@@ -1,5 +1,5 @@
|
||||
name: xmonad
|
||||
version: 0.14.1
|
||||
version: 0.18.0
|
||||
synopsis: A tiling window manager
|
||||
description: xmonad is a tiling window manager for X. Windows are arranged
|
||||
automatically to tile the screen without gaps or overlap, maximising
|
||||
@@ -12,53 +12,47 @@ description: xmonad is a tiling window manager for X. Windows are arrange
|
||||
screens.
|
||||
license: BSD3
|
||||
license-file: LICENSE
|
||||
author: Spencer Janssen, Don Stewart, Adam Vogt, David Roundy, Jason Creighton
|
||||
, Brent Yorgey, Peter Jones, Peter Simons, Andrea Rossato, Devin Mullins
|
||||
, Lukas Mai, Alec Berryman, Stefan O'Rear, Daniel Wagner, Peter J. Jones
|
||||
, Daniel Schoepe, Karsten Schoelzel, Neil Mitchell, Joachim Breitner
|
||||
, Peter De Wachter, Eric Mertens, Geoff Reedy, Michiel Derhaeg
|
||||
, Philipp Balzarek, Valery V. Vorotyntsev, Alex Tarkovsky, Fabian Beuke
|
||||
, Felix Hirn, Michael Sloan, Tomas Janousek, Vanessa McHale, Nicolas Pouillard
|
||||
, Aaron Denney, Austin Seipp, Benno Fünfstück, Brandon S Allbery, Chris Mears
|
||||
, Christian Thiemann, Clint Adams, Daniel Neri, David Lazar, Ferenc Wagner
|
||||
, Francesco Ariis, Gábor Lipták, Ivan N. Veselov, Ivan Tarasov, Javran Cheng
|
||||
, Jens Petersen, Joey Hess, Jonne Ransijn, Josh Holland, Khudyakov Alexey
|
||||
, Klaus Weidner, Michael G. Sloan, Mikkel Christiansen, Nicolas Dudebout
|
||||
, Ondřej Súkup, Paul Hebble, Shachaf Ben-Kiki, Siim Põder, Tim McIver
|
||||
, Trevor Elliott, Wouter Swierstra, Conrad Irwin, Tim Thelion
|
||||
author: Spencer Janssen, Don Stewart, Adam Vogt, David Roundy, Jason Creighton,
|
||||
Brent Yorgey, Peter Jones, Peter Simons, Andrea Rossato, Devin Mullins,
|
||||
Lukas Mai, Alec Berryman, Stefan O'Rear, Daniel Wagner, Peter J. Jones,
|
||||
Daniel Schoepe, Karsten Schoelzel, Neil Mitchell, Joachim Breitner,
|
||||
Peter De Wachter, Eric Mertens, Geoff Reedy, Michiel Derhaeg,
|
||||
Philipp Balzarek, Valery V. Vorotyntsev, Alex Tarkovsky, Fabian Beuke,
|
||||
Felix Hirn, Michael Sloan, Tomas Janousek, Vanessa McHale, Nicolas Pouillard,
|
||||
Aaron Denney, Austin Seipp, Benno Fünfstück, Brandon S Allbery, Chris Mears,
|
||||
Christian Thiemann, Clint Adams, Daniel Neri, David Lazar, Ferenc Wagner,
|
||||
Francesco Ariis, Gábor Lipták, Ivan N. Veselov, Ivan Tarasov, Javran Cheng,
|
||||
Jens Petersen, Joey Hess, Jonne Ransijn, Josh Holland, Khudyakov Alexey,
|
||||
Klaus Weidner, Michael G. Sloan, Mikkel Christiansen, Nicolas Dudebout,
|
||||
Ondřej Súkup, Paul Hebble, Shachaf Ben-Kiki, Siim Põder, Tim McIver,
|
||||
Trevor Elliott, Wouter Swierstra, Conrad Irwin, Tim Thelion, Tony Zorman
|
||||
maintainer: xmonad@haskell.org
|
||||
tested-with: GHC == 8.0.2, GHC == 8.2.2, GHC == 8.4.3, GHC == 8.6.1
|
||||
tested-with: GHC == 8.6.5 || == 8.8.4 || == 8.10.7 || == 9.0.2 || == 9.2.8 || == 9.4.8 || == 9.6.4 || == 9.8.1
|
||||
category: System
|
||||
homepage: http://xmonad.org
|
||||
bug-reports: https://github.com/xmonad/xmonad/issues
|
||||
build-type: Simple
|
||||
extra-source-files: README.md
|
||||
CHANGES.md
|
||||
CONFIG
|
||||
STYLE
|
||||
tests/*.hs
|
||||
tests/Properties/*.hs
|
||||
tests/Properties/Layout/*.hs
|
||||
CONTRIBUTING.md
|
||||
INSTALL.md
|
||||
MAINTAINERS.md
|
||||
TUTORIAL.md
|
||||
man/xmonad.1.markdown
|
||||
man/xmonad.1
|
||||
man/xmonad.1.html
|
||||
util/GenerateManpage.hs
|
||||
man/xmonad.hs
|
||||
util/hpcReport.sh
|
||||
cabal-version: >= 1.8
|
||||
cabal-version: 1.12
|
||||
|
||||
source-repository head
|
||||
type: git
|
||||
location: https://github.com/xmonad/xmonad
|
||||
|
||||
flag testing
|
||||
flag pedantic
|
||||
description: Be pedantic (-Werror and the like)
|
||||
default: False
|
||||
manual: True
|
||||
description: Testing mode, only build minimal components
|
||||
|
||||
flag generatemanpage
|
||||
default: False
|
||||
manual: True
|
||||
description: Build the tool for generating the man page
|
||||
|
||||
library
|
||||
exposed-modules: XMonad
|
||||
@@ -71,36 +65,40 @@ library
|
||||
XMonad.StackSet
|
||||
other-modules: Paths_xmonad
|
||||
hs-source-dirs: src
|
||||
build-depends: base >= 4.9 && < 5
|
||||
, X11 >= 1.8 && < 1.10
|
||||
build-depends: base >= 4.11 && < 5
|
||||
, X11 >= 1.10 && < 1.11
|
||||
, containers
|
||||
, data-default
|
||||
, data-default-class
|
||||
, directory
|
||||
, extensible-exceptions
|
||||
, filepath
|
||||
, mtl
|
||||
, process
|
||||
, setlocale
|
||||
, time
|
||||
, transformers >= 0.3
|
||||
, unix
|
||||
, utf8-string >= 0.3 && < 1.1
|
||||
ghc-options: -funbox-strict-fields -Wall -fno-warn-unused-do-bind
|
||||
ghc-options: -funbox-strict-fields -Wall -Wno-unused-do-bind
|
||||
default-language: Haskell2010
|
||||
|
||||
if flag(testing)
|
||||
buildable: False
|
||||
-- Keep this in sync with the oldest version in 'tested-with'
|
||||
if impl(ghc > 8.6.5)
|
||||
ghc-options: -Wno-unused-imports
|
||||
|
||||
if flag(pedantic)
|
||||
ghc-options: -Werror
|
||||
|
||||
executable xmonad
|
||||
main-is: Main.hs
|
||||
build-depends: base, X11, mtl, unix, xmonad
|
||||
ghc-options: -Wall -fno-warn-unused-do-bind
|
||||
build-depends: base, xmonad
|
||||
ghc-options: -Wall -Wno-unused-do-bind
|
||||
default-language: Haskell2010
|
||||
|
||||
executable generatemanpage
|
||||
main-is: GenerateManpage.hs
|
||||
hs-source-dirs: util
|
||||
-- Keep this in sync with the oldest version in 'tested-with'
|
||||
if impl(ghc > 8.6.5)
|
||||
ghc-options: -Wno-unused-imports
|
||||
|
||||
if flag(generatemanpage)
|
||||
build-depends: base, pandoc >= 2, regex-posix, text
|
||||
else
|
||||
buildable: False
|
||||
if flag(pedantic)
|
||||
ghc-options: -Werror
|
||||
|
||||
test-suite properties
|
||||
type: exitcode-stdio-1.0
|
||||
@@ -123,4 +121,16 @@ test-suite properties
|
||||
Properties.Workspace
|
||||
Utils
|
||||
hs-source-dirs: tests
|
||||
build-depends: base, QuickCheck >= 2, X11, containers, extensible-exceptions, xmonad
|
||||
build-depends: base
|
||||
, QuickCheck >= 2
|
||||
, quickcheck-classes >= 0.4.3
|
||||
, X11
|
||||
, containers
|
||||
, xmonad
|
||||
default-language: Haskell2010
|
||||
|
||||
if impl(ghc > 9.8)
|
||||
ghc-options: -Wno-x-partial
|
||||
|
||||
if flag(pedantic)
|
||||
ghc-options: -Werror
|
||||
|
Reference in New Issue
Block a user