mirror of
https://github.com/xmonad/xmonad.git
synced 2025-07-25 17:21:52 -07:00
Compare commits
598 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
49c69fa73b | ||
|
120ebce490 | ||
|
c0cf91303f | ||
|
80f1c6f027 | ||
|
c54e7088f0 | ||
|
1f3a27f9b9 | ||
|
ec97d83f3f | ||
|
f0975b734c | ||
|
2324266fae | ||
|
3b0559c6cc | ||
|
886a0d4041 | ||
|
98f39eabc1 | ||
|
425c3c0872 | ||
|
29c9819daa | ||
|
3c2b09c213 | ||
|
64a660894d | ||
|
27b1ce9dd7 | ||
|
5caf235f6b | ||
|
4ef9c12d13 | ||
|
d6705fd595 | ||
|
7c1065c43f | ||
|
af104509c3 | ||
|
586ee75a9a | ||
|
013da018a1 | ||
|
71cb355948 | ||
|
19069b3d4b | ||
|
969fca9406 | ||
|
61f00e65f1 | ||
|
db11089e70 | ||
|
e601a7d16d | ||
|
0dd23bddfa | ||
|
55b14d4850 | ||
|
9df514b378 | ||
|
b6d92b4e38 | ||
|
ecf1a0ca0d | ||
|
d216e95f97 | ||
|
af3d3818c8 | ||
|
d065038c8a | ||
|
10bc213349 | ||
|
d22d93b43f | ||
|
871a80fee7 | ||
|
2d59f5157c | ||
|
0738262d9e | ||
|
63d6a66133 | ||
|
fe6215d309 | ||
|
c3cb4ad65f | ||
|
126f891d11 | ||
|
d3383ce0f5 | ||
|
c96a59fa0d | ||
|
12a45b4b99 | ||
|
462957b2f0 | ||
|
3ec3536761 | ||
|
179b6a30f4 | ||
|
3dc65c3d2e | ||
|
2e6312776b | ||
|
3897cab7c9 | ||
|
0c97a89754 | ||
|
5afdc16387 | ||
|
10b843ad21 | ||
|
bc320b69da | ||
|
89a8cc88c3 | ||
|
76f4a16258 | ||
|
8f2eb540d7 | ||
|
ba2d75b930 | ||
|
acf0652952 | ||
|
e4d231920c | ||
|
980828feea | ||
|
2e5ae02059 | ||
|
50eb1844eb | ||
|
f18bda7dc7 | ||
|
2d8cad02fe | ||
|
2baab28602 | ||
|
ef65f901ce | ||
|
f2da028ff9 | ||
|
bad3ce7a5e | ||
|
e1c555e3e6 | ||
|
ab20f7df8d | ||
|
a70bf6a6a3 | ||
|
f58b2399bd | ||
|
91d23656a3 | ||
|
d6b6189cc1 | ||
|
0248e3c9fa | ||
|
40fc10b6a5 | ||
|
3a140badf5 | ||
|
2b103ede55 | ||
|
4565e2c90e | ||
|
285ee2f836 | ||
|
7e9c9ccb1f | ||
|
dc078490d0 | ||
|
202e239ea4 | ||
|
e159ec36fe | ||
|
0b1ccc75ef | ||
|
b0f9a3d0b9 | ||
|
75d297a633 | ||
|
5f5e737d9c | ||
|
a39ed3ee1b | ||
|
e05a046bca | ||
|
12ddc800ab | ||
|
2fab1bb9f5 | ||
|
1b17d1c378 | ||
|
f490ced673 | ||
|
0919ecfbde | ||
|
41b7b1341e | ||
|
0f0aa5e8cb | ||
|
ad4417c8e0 | ||
|
b0f7643cc5 | ||
|
8b055621e9 | ||
|
dc6a972bc1 | ||
|
e4a3eede18 | ||
|
a0ffe7e47d | ||
|
b00b94fda7 | ||
|
45a78ba802 | ||
|
4c0717e9cc | ||
|
30b4ff5e40 | ||
|
b68ebc797a | ||
|
eb4ef5b23f | ||
|
73224be21b | ||
|
7e287ec815 | ||
|
60f472faa2 | ||
|
bd72c6e1e2 | ||
|
b5402e76d3 | ||
|
39dc00b16f | ||
|
038f77de5a | ||
|
59e731ea11 | ||
|
3ce9dcbbb5 | ||
|
f25afdab9f | ||
|
577d5ae968 | ||
|
29bcd465c2 | ||
|
307b82a53d | ||
|
197b0091f8 | ||
|
69c5dae00d | ||
|
28c3482411 | ||
|
73ee008cf6 | ||
|
82a1cae123 | ||
|
d01b913594 | ||
|
d9e3ebf531 | ||
|
252c9d5eee | ||
|
5f0b1601d5 | ||
|
d60791e3f5 | ||
|
f0054fdde7 | ||
|
a9de363b14 | ||
|
7eb6ba0126 | ||
|
f03d2cdf74 | ||
|
16c0cb9a33 | ||
|
fdf3fb2c58 | ||
|
22d5e7eaa3 | ||
|
f837b830fc | ||
|
939c0558e6 | ||
|
fbd406eb03 | ||
|
ecde376224 | ||
|
edf3394821 | ||
|
20be322b08 | ||
|
f8f53fdff8 | ||
|
1da1e2e21e | ||
|
4026075bc6 | ||
|
8863761d66 | ||
|
d67dcd8c4b | ||
|
aa84841289 | ||
|
d10abdcdd0 | ||
|
3073826dfc | ||
|
daed0062c6 | ||
|
abd737cfb4 | ||
|
e719be4e69 | ||
|
ec1a20c727 | ||
|
8f039ec434 | ||
|
057fcc5162 | ||
|
8e7634f543 | ||
|
40cb12ce17 | ||
|
b803fd74a5 | ||
|
d386a230f6 | ||
|
e87456ab77 | ||
|
cdc22f0849 | ||
|
70413b2e22 | ||
|
67ffde0dfb | ||
|
d904fb1cc4 | ||
|
4120be8ba0 | ||
|
e015155131 | ||
|
4c1536cd18 | ||
|
a34a5e979a | ||
|
38faddf9de | ||
|
3ab2b28711 | ||
|
934ff6a562 | ||
|
f8b07d8956 | ||
|
67d436a4e6 | ||
|
c6fef373dc | ||
|
2d4f304c0a | ||
|
1df8ea3d0e | ||
|
490719c035 | ||
|
3cd001e8df | ||
|
b0dda7b351 | ||
|
d8495adf0d | ||
|
06f35a650e | ||
|
56f5ecb320 | ||
|
ff674a27e2 | ||
|
6c51745122 | ||
|
108c2280ef | ||
|
e70b489936 | ||
|
450c3a34fe | ||
|
32f416a3c2 | ||
|
4be3b39cd2 | ||
|
75889ab62e | ||
|
792add376e | ||
|
87c50a911f | ||
|
d16aa9975e | ||
|
f34642cbac | ||
|
008c3638a5 | ||
|
f5c40e9e12 | ||
|
bd82cc9150 | ||
|
a025912ab7 | ||
|
19c1759b35 | ||
|
92acd1eb74 | ||
|
db9f39d6af | ||
|
ebcd67efac | ||
|
387a253f62 | ||
|
4c83e8e097 | ||
|
ae59a5184f | ||
|
fa8fe9aca4 | ||
|
673c3e9ed9 | ||
|
6ba45cdb38 | ||
|
b995b430bc | ||
|
ba482a4611 | ||
|
684907bc77 | ||
|
ad4136df26 | ||
|
defe0c282e | ||
|
c7bdac1a7e | ||
|
17799f131a | ||
|
8cd66aa380 | ||
|
32ba0d4a0d | ||
|
77b3f62610 | ||
|
f3b07eb5dc | ||
|
4372c256ed | ||
|
34239a79de | ||
|
5866db4f0f | ||
|
46d039cde5 | ||
|
dd22717961 | ||
|
0beeb4164b | ||
|
0b435028ff | ||
|
84a988da82 | ||
|
dbd739e41e | ||
|
d5d8d551e6 | ||
|
a2ba4d8a6c | ||
|
557d3edb7d | ||
|
262db2367f | ||
|
eddb445307 | ||
|
d5aadf2538 | ||
|
a16bb44934 | ||
|
2b854ee47c | ||
|
02ed1cabdc | ||
|
02693d307c | ||
|
37dc284460 | ||
|
73e406f4a6 | ||
|
44bc9558d9 | ||
|
0eb84e4866 | ||
|
b4bf8de874 | ||
|
17c89e327e | ||
|
da71b6c8ac | ||
|
2621f3f6a8 | ||
|
8ec0bf3290 | ||
|
7e20d0d308 | ||
|
24d8de93d7 | ||
|
2dd6eeba7d | ||
|
72997cf982 | ||
|
7365d7bc11 | ||
|
36e20f689c | ||
|
cde261ed56 | ||
|
8d8cc8bcd8 | ||
|
ccb6ff92f2 | ||
|
e944a6c8d3 | ||
|
eb1e29c8bb | ||
|
66e7715ea6 | ||
|
d9d3e40112 | ||
|
7385793c65 | ||
|
72885e7e24 | ||
|
a931776e54 | ||
|
61568318d6 | ||
|
3caa989e20 | ||
|
09fd11d13b | ||
|
f33681de49 | ||
|
bf8bfc66a5 | ||
|
4075e2d9d3 | ||
|
78856e1a6f | ||
|
4222dd9ad3 | ||
|
34a547ce57 | ||
|
353e7cd681 | ||
|
72dece0769 | ||
|
6e1c5e9b49 | ||
|
bf8ba79090 | ||
|
5edfb1d262 | ||
|
0fecae0abc | ||
|
26f4f734f9 | ||
|
5e7df396b9 | ||
|
314ba78335 | ||
|
7aa78ecc75 | ||
|
ba8e26458e | ||
|
c627e8cc4d | ||
|
04f894275d | ||
|
edb752136f | ||
|
2b463a632f | ||
|
ca122dd2cb | ||
|
77657b65f9 | ||
|
28c57a837a | ||
|
afda20b56d | ||
|
0cc7b12fd0 | ||
|
15a78ae715 | ||
|
18444799e0 | ||
|
cc60fa73ad | ||
|
8881e2ac78 | ||
|
533031e3d6 | ||
|
76d4af15e4 | ||
|
74c6dd2721 | ||
|
b605fd9fce | ||
|
85202ebd47 | ||
|
328c660ce7 | ||
|
b185a439b1 | ||
|
0016e06984 | ||
|
339b2d0097 | ||
|
5f4d63ba71 | ||
|
942572c830 | ||
|
46ac2ca24b | ||
|
3830d7a571 | ||
|
5b3eaf663a | ||
|
c93b7c7c3b | ||
|
42dee4768e | ||
|
e847b350ed | ||
|
cccbfa21e4 | ||
|
870b3ad282 | ||
|
ab30d76578 | ||
|
d8d636e573 | ||
|
ba3987f299 | ||
|
5a19425e79 | ||
|
28431e18c8 | ||
|
43c2d26cdb | ||
|
c24016882e | ||
|
9dae87c537 | ||
|
b67026dd02 | ||
|
aa58eea6dc | ||
|
7db13a2a45 | ||
|
029e668dbc | ||
|
6f61c83623 | ||
|
bcbccbfafc | ||
|
04c8d62361 | ||
|
4890116e49 | ||
|
708084dd48 | ||
|
ef516142b9 | ||
|
cb51875da6 | ||
|
167a6e155b | ||
|
2b2774f81d | ||
|
16725dfe0d | ||
|
15db3c6f0a | ||
|
6db444eb1a | ||
|
46bc3bbd17 | ||
|
d948210935 | ||
|
db08970071 | ||
|
4c69a85b3f | ||
|
ac103b8472 | ||
|
029965e4d4 | ||
|
9fd1d4f9d0 | ||
|
dbbd934b0b | ||
|
750544fda9 | ||
|
90eae3fd63 | ||
|
d6233d0463 | ||
|
5f088f4e99 | ||
|
f8a7d8d381 | ||
|
f7686746c6 | ||
|
04ee55c3ca | ||
|
50ce362626 | ||
|
209b88f821 | ||
|
c5cca485df | ||
|
0593a282ca | ||
|
351de8d2b6 | ||
|
4bd9073937 | ||
|
79754fd5d3 | ||
|
b14de19e8b | ||
|
e97c326ff0 | ||
|
bc13b4ba07 | ||
|
5bea59a823 | ||
|
669a162cfc | ||
|
310c22694e | ||
|
1c930ba955 | ||
|
797204fe6c | ||
|
a3ecf5d304 | ||
|
1a4a4a5000 | ||
|
a8d3564653 | ||
|
d5955b023c | ||
|
4d9a6c2681 | ||
|
87193ff61e | ||
|
3303c6e05d | ||
|
9d9acba45f | ||
|
cc2754d82a | ||
|
cea3492d28 | ||
|
14d9a194ff | ||
|
e8d1d028ba | ||
|
695860f1fd | ||
|
261f742404 | ||
|
1de1bcded2 | ||
|
0c697ebbb4 | ||
|
a626083721 | ||
|
481e42ab72 | ||
|
e751c4b62f | ||
|
730984fd60 | ||
|
ad85e11a4a | ||
|
2da09787da | ||
|
162a54d992 | ||
|
d00d4ca046 | ||
|
0dd54885eb | ||
|
f80d593d57 | ||
|
10be8aaae0 | ||
|
66f623b656 | ||
|
b86351f3c3 | ||
|
8399e80327 | ||
|
3e3d516092 | ||
|
d2ae7310d6 | ||
|
ca3e277d2b | ||
|
bb2b6c7bf8 | ||
|
d74814af35 | ||
|
f9799422f9 | ||
|
be5e27038f | ||
|
1f4b8cb5f6 | ||
|
da7ca1c29d | ||
|
e095621ab9 | ||
|
93c55c948e | ||
|
9ff105340e | ||
|
5e61b137fb | ||
|
aeef36f74c | ||
|
673f303646 | ||
|
7f3c6823d4 | ||
|
79f23d6cec | ||
|
46f5e68cfa | ||
|
76d2bddaf0 | ||
|
f5e55f3a27 | ||
|
6c72a03fb1 | ||
|
31c7734f7b | ||
|
d1af7d986d | ||
|
da167bfc11 | ||
|
c46f3ad549 | ||
|
5b42a58d06 | ||
|
e8292e0e9d | ||
|
6cd46e12bb | ||
|
2441275122 | ||
|
f70ab7964e | ||
|
237fdbf037 | ||
|
5166ede96b | ||
|
56463b2391 | ||
|
f427c2b0e9 | ||
|
287d364e0d | ||
|
8c31768b79 | ||
|
9ceef229c3 | ||
|
40581c9bf8 | ||
|
161ade3593 | ||
|
f2461c9e3a | ||
|
11b37429b1 | ||
|
bbf5d0010c | ||
|
2f60ee5680 | ||
|
3e2d48d5da | ||
|
462422d07a | ||
|
33f28ed2ac | ||
|
a29590034a | ||
|
f394956e56 | ||
|
039d9e2b96 | ||
|
a73f8ec709 | ||
|
1bb18654d6 | ||
|
fa45d59e95 | ||
|
f73f8f38a5 | ||
|
28cc666a75 | ||
|
c8f16a85cf | ||
|
6908189698 | ||
|
39eccc350c | ||
|
c8ab301c95 | ||
|
5e310c0c94 | ||
|
4fa10442ab | ||
|
1ab1d729a0 | ||
|
c95b8d9160 | ||
|
92b4510d7b | ||
|
6114bb371e | ||
|
7e2ec3840c | ||
|
6ce125a566 | ||
|
3456086f85 | ||
|
3b83895d28 | ||
|
dc6ba6b5ee | ||
|
df5003eb16 | ||
|
99dd1a30ba | ||
|
d6c5eb3e80 | ||
|
9d9b733994 | ||
|
ea71fd67e8 | ||
|
e9eadd6141 | ||
|
ddf9e49e49 | ||
|
81803ffe81 | ||
|
31ce83d04e | ||
|
c2ae7a8c71 | ||
|
45eea722be | ||
|
4bb6371155 | ||
|
dfd4d435d8 | ||
|
ac41c8fb52 | ||
|
223b48ab27 | ||
|
107b942414 | ||
|
6aee5509de | ||
|
ba6d9c8a52 | ||
|
3a995b40c9 | ||
|
656f4551da | ||
|
6ae94edbe4 | ||
|
22ccca29e6 | ||
|
2302bb3304 | ||
|
b4e0e77911 | ||
|
dcf53fbaf6 | ||
|
833e37da9c | ||
|
cf0c3b9ab6 | ||
|
532a920bce | ||
|
0d506daf45 | ||
|
4887c5ac42 | ||
|
3d0c08365d | ||
|
e4c2a81ca1 | ||
|
58fc2bc59e | ||
|
c8473e3ae9 | ||
|
11711e1a46 | ||
|
99fb75eb9b | ||
|
ceb1c51b3f | ||
|
14b6306ac2 | ||
|
b51f6f55a8 | ||
|
e2ab6e8a27 | ||
|
a5200b3862 | ||
|
f81ec95fa0 | ||
|
39f4fe7a90 | ||
|
d50d6c909d | ||
|
dbfd13207d | ||
|
6eb23670bb | ||
|
bbe4a27f65 | ||
|
94924123bb | ||
|
ece268cd1e | ||
|
dfd8e51136 | ||
|
0de10862c2 | ||
|
f7b6a4508f | ||
|
00f83ac78a | ||
|
3a902ce613 | ||
|
5342be0e67 | ||
|
88845e5d97 | ||
|
4732557c12 | ||
|
a13c11ff52 | ||
|
fcea17f920 | ||
|
a5acef3ad6 | ||
|
76e960a40c | ||
|
30af3a8f84 | ||
|
c9142952c2 | ||
|
934fb2c368 | ||
|
d1c29a40cf | ||
|
cd9c592ebc | ||
|
131e060533 | ||
|
4996b1bc47 | ||
|
4b2366b5ce | ||
|
0590f5da9e | ||
|
c3c39aae12 | ||
|
7bc4ab41c7 | ||
|
7dc2d254d1 | ||
|
528d51e58a | ||
|
9ef3fdcf08 | ||
|
e8d3f674ef | ||
|
8a5d2490bb | ||
|
22aacf9bf6 | ||
|
b0b43050f4 | ||
|
23035e944b | ||
|
bf52d34bbf | ||
|
8a8c538c23 | ||
|
e50927ffc0 | ||
|
3789f37f25 | ||
|
48ccbc7fb2 | ||
|
d679ceb234 | ||
|
7b3c1243b7 | ||
|
97fe14dfd2 | ||
|
c1e039ba88 | ||
|
9bd11aeea5 | ||
|
c350caf9b8 | ||
|
066da1cd99 | ||
|
cadf81976f | ||
|
ddd1fa9cae | ||
|
3bd63adb60 | ||
|
e384a358b5 | ||
|
8971ab7fae | ||
|
2e8794d0f3 | ||
|
92d58ae0a8 | ||
|
33e14e7ba7 | ||
|
ec45881d4c | ||
|
b73ac809ba | ||
|
d0507c9eb3 | ||
|
fc82a7d412 | ||
|
cc019f487c | ||
|
4c7cf15cdb | ||
|
350a4d6f6b | ||
|
1ddaffbfba | ||
|
0903c76d40 | ||
|
156a89b761 | ||
|
1ea1c05617 | ||
|
f5ad470815 | ||
|
1be4bc5d91 | ||
|
0514380d76 | ||
|
18cf8fbb10 | ||
|
eb65473591 | ||
|
c734586275 | ||
|
74131eb15f | ||
|
ac94932345 |
24
.github/ISSUE_TEMPLATE.md
vendored
Normal file
24
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
### Problem Description
|
||||
|
||||
Describe the problem you are having, what you expect to happen
|
||||
instead, and how to reproduce the problem.
|
||||
|
||||
### Configuration File
|
||||
|
||||
Please include the smallest configuration file that reproduces the
|
||||
problem you are experiencing:
|
||||
|
||||
```haskell
|
||||
module Main (main) where
|
||||
|
||||
import XMonad
|
||||
|
||||
main :: IO ()
|
||||
main = xmonad def
|
||||
```
|
||||
|
||||
### Checklist
|
||||
|
||||
- [ ] 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)
|
14
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
14
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
### Description
|
||||
|
||||
Include a description for your changes, including the motivation
|
||||
behind them.
|
||||
|
||||
### Checklist
|
||||
|
||||
- [ ] I've read [CONTRIBUTING.md](https://github.com/xmonad/xmonad/blob/master/CONTRIBUTING.md)
|
||||
|
||||
- [ ] 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 updated the `CHANGES.md` file
|
29
.gitignore
vendored
Normal file
29
.gitignore
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
.hpc/
|
||||
*.hi
|
||||
*.o
|
||||
*.p_hi
|
||||
*.prof
|
||||
*.tix
|
||||
|
||||
# editor temp files
|
||||
|
||||
*#
|
||||
.#*
|
||||
*~
|
||||
.*.swp
|
||||
|
||||
# TAGS files
|
||||
TAGS
|
||||
tags
|
||||
|
||||
# stack artifacts
|
||||
/.stack-work/
|
||||
|
||||
# cabal-install artifacts
|
||||
/.*.environment.*-*
|
||||
/.cabal-sandbox/
|
||||
/cabal.config
|
||||
/cabal.project.local
|
||||
/cabal.sandbox.config
|
||||
/dist-newstyle/
|
||||
/dist/
|
135
.travis.yml
Normal file
135
.travis.yml
Normal file
@@ -0,0 +1,135 @@
|
||||
# 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
|
152
CHANGES.md
Normal file
152
CHANGES.md
Normal file
@@ -0,0 +1,152 @@
|
||||
# Change Log / Release Notes
|
||||
|
||||
## unknown (unknown)
|
||||
|
||||
## 0.14.1 (August 20, 2018)
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* The cabal build no longer installs xmonad.hs, xmonad.1, and xmonad.1.html
|
||||
as data files. The location cabal picks for chose files isn't useful as
|
||||
standard tools like man(1) won't find them there. Instead, we rely on
|
||||
distributors to pick up the files from the source tarball during the build
|
||||
and to install them into proper locations where their users expect them.
|
||||
[https://github.com/xmonad/xmonad/pull/127]
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Add support for GHC 8.6.x by providing an instance for 'MonadFail X'. A
|
||||
side effect of that change is that our code no longer compiles with GHC
|
||||
versions prior to 8.0.x. We could work around that, no doubt, but the
|
||||
resulting code would require CPP and Cabal flags and whatnot. It feels more
|
||||
reasonable to just require a moderately recent compiler instead of going
|
||||
through all that trouble.
|
||||
|
||||
* xmonad no longer always recompile on startup. Now it only does so if the
|
||||
executable does not have the name that would be used for the compilation
|
||||
output. The purpose of recompiling and executing the results in this case is
|
||||
so that the `xmonad` executable in the package can be used with custom
|
||||
configurations.
|
||||
|
||||
### Enhancements
|
||||
|
||||
* Whenever xmonad recompiles, it now explains how it is attempting to
|
||||
recompile, by outputting logs to stderr. If you are using xmonad as a custom
|
||||
X session, then this will end up in a `.xsession-errors` file.
|
||||
|
||||
## 0.14 (July 30, 2018)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* The state file that xmonad uses while restarting itself is now
|
||||
removed after it is processed. This fixes a bug that manifested
|
||||
in several different ways:
|
||||
|
||||
- Names of old workspaces would be resurrected after a restart
|
||||
- Screen sizes would be wrong after changing monitor configuration (#90)
|
||||
- `spawnOnce` stopped working (xmonad/xmonad-contrib#155)
|
||||
- Focus did not follow when moving between workspaces (#87)
|
||||
- etc.
|
||||
|
||||
* Recover old behavior (in 0.12) when `focusFollowsMouse == True`:
|
||||
the focus follows when the mouse enters another workspace
|
||||
but not moving into any window.
|
||||
|
||||
* Compiles with GHC 8.4.1
|
||||
|
||||
* Restored compatability with GHC version prior to 8.0.1 by removing the
|
||||
dependency on directory version 1.2.3.
|
||||
|
||||
|
||||
## 0.13 (February 10, 2017)
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* When restarting xmonad, resume state is no longer passed to the
|
||||
next process via the command line. Instead, a temporary state
|
||||
file is created and xmonad's state is serialized to that file.
|
||||
|
||||
When upgrading to 0.13 from a previous version, the `--resume`
|
||||
command line option will automatically migrate to a state file.
|
||||
|
||||
This fixes issue #12.
|
||||
|
||||
### Enhancements
|
||||
|
||||
* You can now control which directory xmonad uses for finding your
|
||||
configuration file and which one is used for storing the compiled
|
||||
version of your configuration. In order of preference:
|
||||
|
||||
1. New environment variables. If you want to use these ensure
|
||||
you set the correct environment variable and also create the
|
||||
directory it references:
|
||||
|
||||
- `XMONAD_CONFIG_DIR`
|
||||
- `XMONAD_CACHE_DIR`
|
||||
- `XMONAD_DATA_DIR`
|
||||
|
||||
2. The `~/.xmonad` directory.
|
||||
|
||||
3. XDG Base Directory Specification directories, if they exist:
|
||||
|
||||
- `XDG_CONFIG_HOME/xmonad`
|
||||
- `XDG_CACHE_HOME/xmonad`
|
||||
- `XDG_DATA_HOME/xmonad`
|
||||
|
||||
If none of these directories exist then one will be created using
|
||||
the following logic: If the relevant environment variable
|
||||
mentioned in step (1) above is set, the referent directory will be
|
||||
created and used. Otherwise `~/.xmonad` will be created and used.
|
||||
|
||||
This fixes a few issues, notably #7 and #56.
|
||||
|
||||
* A custom build script can be used when xmonad is given the
|
||||
`--recompile` command line option. If an executable named `build`
|
||||
exists in the xmonad configuration directory it will be called
|
||||
instead of `ghc`. It takes one argument, the name of the
|
||||
executable binary it must produce.
|
||||
|
||||
This fixes #8. (One of two possible custom build solutions. See
|
||||
the next entry for another solution.)
|
||||
|
||||
* For users who build their xmonad configuration using tools such as
|
||||
cabal or stack, there is another option for executing xmonad.
|
||||
|
||||
Instead of running the `xmonad` executable directly, arrange to
|
||||
have your login manager run your configuration binary instead.
|
||||
Then, in your binary, use the new `launch` command instead of
|
||||
`xmonad`.
|
||||
|
||||
This will keep xmonad from using its configuration file
|
||||
checking/compiling code and directly start the window manager
|
||||
without `exec`ing any other binary.
|
||||
|
||||
See the documentation for the `launch` function in `XMonad.Main`
|
||||
for more details.
|
||||
|
||||
Fixes #8. (Second way to have a custom build environment for
|
||||
XMonad. See previous entry for another solution.)
|
||||
|
||||
## 0.12 (December 14, 2015)
|
||||
|
||||
* Compiles with GHC 7.10.2, 7.8.4, and 7.6.3
|
||||
|
||||
* Use of [data-default][] allows using `def` where previously you
|
||||
had to write `defaultConfig`, `defaultXPConfig`, etc.
|
||||
|
||||
* The [setlocale][] package is now used instead of a binding shipped
|
||||
with xmonad proper allowing the use of `Main.hs` instead of
|
||||
`Main.hsc`
|
||||
|
||||
* No longer encodes paths for `spawnPID`
|
||||
|
||||
* The default `manageHook` no longer floats Gimp windows
|
||||
|
||||
* Doesn't crash when there are fewer workspaces than screens
|
||||
|
||||
* `Query` is now an instance of `Applicative`
|
||||
|
||||
* Various improvements to the example configuration file
|
||||
|
||||
[data-default]: http://hackage.haskell.org/package/data-default
|
||||
[setlocale]: https://hackage.haskell.org/package/setlocale
|
82
CONFIG
Normal file
82
CONFIG
Normal file
@@ -0,0 +1,82 @@
|
||||
== 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.
|
141
CONTRIBUTING.md
Normal file
141
CONTRIBUTING.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# Contributing to xmonad and xmonad-contrib
|
||||
|
||||
## Before Creating a GitHub Issue
|
||||
|
||||
New issue submissions should adhere to the following guidelines:
|
||||
|
||||
* Does your issue have to do with [xmonad][], [xmonad-contrib][], or
|
||||
maybe even with the [X11][] library?
|
||||
|
||||
Please submit your issue to the **correct** GitHub repository.
|
||||
|
||||
* To help you figure out which repository to submit your issue to,
|
||||
and to help us resolve the problem you are having, create the
|
||||
smallest configuration file you can that reproduces the problem.
|
||||
|
||||
You may find that the [xmonad-testing][] repository is helpful in
|
||||
reproducing the problem with a smaller configuration file.
|
||||
|
||||
Once you've done that please include the configuration file with
|
||||
your GitHub issue.
|
||||
|
||||
* If possible, use the [xmonad-testing][] repository to test your
|
||||
configuration with the bleeding-edge development version of xmonad
|
||||
and xmonad-contrib. We might have already fixed your problem.
|
||||
|
||||
## Contributing Changes/Patches
|
||||
|
||||
Have a change to xmonad that you want included in the next release?
|
||||
Awesome! Here are a few things to keep in mind:
|
||||
|
||||
* Review the above section about creating GitHub issues.
|
||||
|
||||
* It's always best to talk with the community before making any
|
||||
nontrivial changes to xmonad. There are a couple of ways you can
|
||||
chat with us:
|
||||
|
||||
- Post a message to the [mailing list][ml].
|
||||
|
||||
- Join the `#xmonad` IRC channel on `chat.freenode.org`.
|
||||
|
||||
* Continue reading this document!
|
||||
|
||||
## Expediting Reviews and Merges
|
||||
|
||||
Here are some tips for getting your changes merged into xmonad:
|
||||
|
||||
* If your changes can go into [xmonad-contrib][] instead
|
||||
of [xmonad][], please do so. We rarely accept new features to
|
||||
xmonad. (Not that we don't accept changes to xmonad, just that we
|
||||
prefer changes to xmonad-contrib instead.)
|
||||
|
||||
* Change the fewest files as possible. If it makes sense, submit a
|
||||
completely new module to xmonad-contrib.
|
||||
|
||||
* Your changes should include relevant entries in the `CHANGES.md`
|
||||
file. Help us communicate changes to the community.
|
||||
|
||||
* Make sure you test your changes 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 read the section on rebasing and squashing commits
|
||||
below.
|
||||
|
||||
## Rebasing and Squashing Commits
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
### How to Rebase Your Changes
|
||||
|
||||
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.
|
||||
|
||||
1. Make sure that you have a `git remote` configured for the main
|
||||
repository. I like to call this remote `upstream`:
|
||||
|
||||
$ git remote add upstream https://github.com/xmonad/xmonad-contrib.git
|
||||
|
||||
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):
|
||||
|
||||
$ git fetch --all
|
||||
$ git pull --rebase upstream master
|
||||
|
||||
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:
|
||||
|
||||
$ 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]: 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
|
259
Config.hs
259
Config.hs
@@ -1,259 +0,0 @@
|
||||
-----------------------------------------------------------------------------
|
||||
-- |
|
||||
-- Module : Config.hs
|
||||
-- Copyright : (c) Spencer Janssen 2007
|
||||
-- License : BSD3-style (see LICENSE)
|
||||
--
|
||||
-- Maintainer : dons@galois.com
|
||||
-- Stability : stable
|
||||
-- Portability : portable
|
||||
--
|
||||
-- This module specifies configurable defaults for xmonad. If you change
|
||||
-- values here, be sure to recompile and restart (mod-q) xmonad,
|
||||
-- for the changes to take effect.
|
||||
--
|
||||
------------------------------------------------------------------------
|
||||
|
||||
module Config where
|
||||
|
||||
--
|
||||
-- Useful imports
|
||||
--
|
||||
import XMonad
|
||||
import Operations
|
||||
import qualified StackSet as W
|
||||
import Data.Ratio
|
||||
import Data.Bits ((.|.))
|
||||
import qualified Data.Map as M
|
||||
import System.Exit
|
||||
import Graphics.X11.Xlib
|
||||
|
||||
-- % Extension-provided imports
|
||||
|
||||
-- | The default number of workspaces (virtual screens) and their names.
|
||||
-- By default we use numeric strings, but any string may be used as a
|
||||
-- workspace name. The number of workspaces is determined by the length
|
||||
-- of this list.
|
||||
--
|
||||
-- A tagging example:
|
||||
--
|
||||
-- > workspaces = ["web", "irc", "code" ] ++ map show [4..9]
|
||||
--
|
||||
workspaces :: [WorkspaceId]
|
||||
workspaces = map show [1 .. 9 :: Int]
|
||||
|
||||
-- | modMask lets you specify which modkey you want to use. The default
|
||||
-- is mod1Mask ("left alt"). You may also consider using mod3Mask
|
||||
-- ("right alt"), which does not conflict with emacs keybindings. The
|
||||
-- "windows key" is usually mod4Mask.
|
||||
--
|
||||
modMask :: KeyMask
|
||||
modMask = mod1Mask
|
||||
|
||||
-- | The mask for the numlock key. Numlock status is "masked" from the
|
||||
-- current modifier status, so the keybindings will work with numlock on or
|
||||
-- off. You may need to change this on some systems.
|
||||
--
|
||||
-- You can find the numlock modifier by running "xmodmap" and looking for a
|
||||
-- modifier with Num_Lock bound to it:
|
||||
--
|
||||
-- > $ xmodmap | grep Num
|
||||
-- > mod2 Num_Lock (0x4d)
|
||||
--
|
||||
-- Set numlockMask = 0 if you don't have a numlock key, or want to treat
|
||||
-- numlock status separately.
|
||||
--
|
||||
numlockMask :: KeyMask
|
||||
numlockMask = mod2Mask
|
||||
|
||||
-- | Width of the window border in pixels.
|
||||
--
|
||||
borderWidth :: Dimension
|
||||
borderWidth = 1
|
||||
|
||||
-- | Border colors for unfocused and focused windows, respectively.
|
||||
--
|
||||
normalBorderColor, focusedBorderColor :: String
|
||||
normalBorderColor = "#dddddd"
|
||||
focusedBorderColor = "#ff0000"
|
||||
|
||||
-- | Default offset of drawable screen boundaries from each physical
|
||||
-- screen. Anything non-zero here will leave a gap of that many pixels
|
||||
-- on the given edge, on the that screen. A useful gap at top of screen
|
||||
-- for a menu bar (e.g. 15)
|
||||
--
|
||||
-- An example, to set a top gap on monitor 1, and a gap on the bottom of
|
||||
-- monitor 2, you'd use a list of geometries like so:
|
||||
--
|
||||
-- > defaultGaps = [(18,0,0,0),(0,18,0,0)] -- 2 gaps on 2 monitors
|
||||
--
|
||||
-- Fields are: top, bottom, left, right.
|
||||
--
|
||||
defaultGaps :: [(Int,Int,Int,Int)]
|
||||
defaultGaps = [(0,0,0,0)] -- 15 for default dzen font
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Window rules
|
||||
|
||||
-- | Execute arbitrary actions and WindowSet manipulations when managing
|
||||
-- a new window. You can use this to, for example, always float a
|
||||
-- particular program, or have a client always appear on a particular
|
||||
-- workspace.
|
||||
--
|
||||
manageHook :: Window -- ^ the new window to manage
|
||||
-> String -- ^ window title
|
||||
-> String -- ^ window resource name
|
||||
-> String -- ^ window resource class
|
||||
-> X (WindowSet -> WindowSet)
|
||||
|
||||
-- Always float various programs:
|
||||
manageHook w _ _ c | c `elem` floats = fmap (W.float w . snd) (floatLocation w)
|
||||
where floats = ["MPlayer", "Gimp"]
|
||||
|
||||
-- Desktop panels and dock apps should be ignored by xmonad:
|
||||
manageHook w _ n _ | n `elem` ignore = reveal w >> return (W.delete w)
|
||||
where ignore = ["gnome-panel", "desktop_window", "kicker", "kdesktop"]
|
||||
|
||||
-- Automatically send Firefox windows to the "web" workspace:
|
||||
-- If a workspace named "web" doesn't exist, the window will appear on the
|
||||
-- current workspace.
|
||||
manageHook _ _ "Gecko" _ = return $ W.shift "web"
|
||||
|
||||
-- The default rule: return the WindowSet unmodified. You typically do not
|
||||
-- want to modify this line.
|
||||
manageHook _ _ _ _ = return id
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Extensible layouts
|
||||
|
||||
-- | The list of possible layouts. Add your custom layouts to this list.
|
||||
layouts :: [Layout Window]
|
||||
layouts = [ Layout tiled
|
||||
, Layout $ Mirror tiled
|
||||
, Layout Full
|
||||
-- Add extra layouts you want to use here:
|
||||
-- % Extension-provided layouts
|
||||
]
|
||||
where
|
||||
-- default tiling algorithm partitions the screen into two panes
|
||||
tiled = Tall nmaster delta ratio
|
||||
|
||||
-- The default number of windows in the master pane
|
||||
nmaster = 1
|
||||
|
||||
-- Default proportion of screen occupied by master pane
|
||||
ratio = 1%2
|
||||
|
||||
-- Percent of screen to increment by when resizing panes
|
||||
delta = 3%100
|
||||
|
||||
-- | The top level layout switcher. Most users will not need to modify this binding.
|
||||
--
|
||||
-- By default, we simply switch between the layouts listed in `layouts'
|
||||
-- above, but you may program your own selection behaviour here. Layout
|
||||
-- transformers, for example, would be hooked in here.
|
||||
--
|
||||
layoutHook :: Layout Window
|
||||
layoutHook = Layout $ Select layouts
|
||||
|
||||
-- | Register with xmonad a list of layouts whose state we can preserve over restarts.
|
||||
-- There is typically no need to modify this list, the defaults are fine.
|
||||
--
|
||||
serialisedLayouts :: [Layout Window]
|
||||
serialisedLayouts = layoutHook : layouts
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Logging
|
||||
|
||||
-- | Perform an arbitrary action on each internal state change or X event.
|
||||
-- Examples include:
|
||||
-- * do nothing
|
||||
-- * log the state to stdout
|
||||
--
|
||||
-- See the 'DynamicLog' extension for examples.
|
||||
--
|
||||
logHook :: X ()
|
||||
logHook = return ()
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Key bindings:
|
||||
|
||||
-- | The xmonad key bindings. Add, modify or remove key bindings here.
|
||||
--
|
||||
-- (The comment formatting character is used when generating the manpage)
|
||||
--
|
||||
keys :: M.Map (KeyMask, KeySym) (X ())
|
||||
keys = M.fromList $
|
||||
-- launching and killing programs
|
||||
[ ((modMask .|. shiftMask, xK_Return), spawn "xterm") -- %! Launch an xterm
|
||||
, ((modMask, xK_p ), spawn "exe=`dmenu_path | dmenu` && eval \"exec $exe\"") -- %! Launch dmenu
|
||||
, ((modMask .|. shiftMask, xK_p ), spawn "gmrun") -- %! Launch gmrun
|
||||
, ((modMask .|. shiftMask, xK_c ), kill) -- %! Close the focused window
|
||||
|
||||
, ((modMask, xK_space ), sendMessage NextLayout) -- %! Rotate through the available layout algorithms
|
||||
, ((modMask .|. shiftMask, xK_space ), setLayout layoutHook) -- %! Reset the layouts on the current workspace to default
|
||||
|
||||
, ((modMask, xK_n ), refresh) -- %! Resize viewed windows to the correct size
|
||||
|
||||
-- move focus up or down the window stack
|
||||
, ((modMask, xK_Tab ), windows W.focusDown) -- %! Move focus to the next window
|
||||
, ((modMask, xK_j ), windows W.focusDown) -- %! Move focus to the next window
|
||||
, ((modMask, xK_k ), windows W.focusUp ) -- %! Move focus to the previous window
|
||||
, ((modMask, xK_m ), windows W.focusMaster ) -- %! Move focus to the master window
|
||||
|
||||
-- modifying the window order
|
||||
, ((modMask, xK_Return), windows W.swapMaster) -- %! Swap the focused window and the master window
|
||||
, ((modMask .|. shiftMask, xK_j ), windows W.swapDown ) -- %! Swap the focused window with the next window
|
||||
, ((modMask .|. shiftMask, xK_k ), windows W.swapUp ) -- %! Swap the focused window with the previous window
|
||||
|
||||
-- resizing the master/slave ratio
|
||||
, ((modMask, xK_h ), sendMessage Shrink) -- %! Shrink the master area
|
||||
, ((modMask, xK_l ), sendMessage Expand) -- %! Expand the master area
|
||||
|
||||
-- floating layer support
|
||||
, ((modMask, xK_t ), withFocused $ windows . W.sink) -- %! Push window back into tiling
|
||||
|
||||
-- increase or decrease number of windows in the master area
|
||||
, ((modMask , xK_comma ), sendMessage (IncMasterN 1)) -- %! Increment the number of windows in the master area
|
||||
, ((modMask , xK_period), sendMessage (IncMasterN (-1))) -- %! Deincrement the number of windows in the master area
|
||||
|
||||
-- toggle the status bar gap
|
||||
, ((modMask , xK_b ), modifyGap (\i n -> let x = (defaultGaps ++ repeat (0,0,0,0)) !! i in if n == x then (0,0,0,0) else x)) -- %! Toggle the status bar gap
|
||||
|
||||
-- quit, or restart
|
||||
, ((modMask .|. shiftMask, xK_q ), io (exitWith ExitSuccess)) -- %! Quit xmonad
|
||||
, ((modMask , xK_q ), broadcastMessage ReleaseResources >> restart Nothing True) -- %! Restart xmonad
|
||||
|
||||
-- % Extension-provided key bindings
|
||||
]
|
||||
++
|
||||
-- mod-[1..9] %! Switch to workspace N
|
||||
-- mod-shift-[1..9] %! Move client to workspace N
|
||||
[((m .|. modMask, k), windows $ f i)
|
||||
| (i, k) <- zip workspaces [xK_1 ..]
|
||||
, (f, m) <- [(W.greedyView, 0), (W.shift, shiftMask)]]
|
||||
++
|
||||
-- mod-{w,e,r} %! Switch to physical/Xinerama screens 1, 2, or 3
|
||||
-- mod-shift-{w,e,r} %! Move client to screen 1, 2, or 3
|
||||
[((m .|. modMask, key), screenWorkspace sc >>= flip whenJust (windows . f))
|
||||
| (key, sc) <- zip [xK_w, xK_e, xK_r] [0..]
|
||||
, (f, m) <- [(W.view, 0), (W.shift, shiftMask)]]
|
||||
|
||||
-- % Extension-provided key bindings lists
|
||||
|
||||
-- | Mouse bindings: default actions bound to mouse events
|
||||
--
|
||||
mouseBindings :: M.Map (KeyMask, Button) (Window -> X ())
|
||||
mouseBindings = M.fromList $
|
||||
-- mod-button1 %! Set the window to floating mode and move by dragging
|
||||
[ ((modMask, button1), (\w -> focus w >> mouseMoveWindow w))
|
||||
-- mod-button2 %! Raise the window to the top of the stack
|
||||
, ((modMask, button2), (\w -> focus w >> windows W.swapMaster))
|
||||
-- mod-button3 %! Set the window to floating mode and resize by dragging
|
||||
, ((modMask, button3), (\w -> focus w >> mouseResizeWindow w))
|
||||
-- you may also bind events to the mouse scroll wheel (button4 and button5)
|
||||
|
||||
-- % Extension-provided mouse bindings
|
||||
]
|
||||
|
||||
-- % Extension-provided definitions
|
@@ -1,10 +0,0 @@
|
||||
module Config where
|
||||
import Graphics.X11.Xlib.Types (Dimension)
|
||||
import Graphics.X11.Xlib (KeyMask,Window)
|
||||
import XMonad
|
||||
borderWidth :: Dimension
|
||||
numlockMask :: KeyMask
|
||||
workspaces :: [WorkspaceId]
|
||||
logHook :: X ()
|
||||
manageHook :: Window -> String -> String -> String -> X (WindowSet -> WindowSet)
|
||||
serialisedLayouts :: [Layout Window]
|
24
LICENSE
24
LICENSE
@@ -1,27 +1,31 @@
|
||||
Copyright (c) Spencer Janssen
|
||||
Copyright (c) 2007,2008 Spencer Janssen
|
||||
Copyright (c) 2007,2008 Don Stewart
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the 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
|
||||
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.
|
||||
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.
|
||||
|
83
MAINTAINERS.md
Normal file
83
MAINTAINERS.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# XMonad Maintainers
|
||||
|
||||
## The XMonad Core Team
|
||||
|
||||
* Adam Vogt [GitHub][aavogt]
|
||||
|
||||
* Brandon S Allbery [GitHub][geekosaur], IRC: `geekosaur`
|
||||
|
||||
* Brent Yorgey [GitHub][byorgey], IRC: `byorgey`
|
||||
|
||||
* Daniel Wagner [GitHub][dmwit], IRC: `dmwit`
|
||||
|
||||
* David Lazar [GitHub][davidlazar]
|
||||
|
||||
* Devin Mullins [GitHub][twifkak]
|
||||
|
||||
* Peter J. Jones [GitHub][pjones], [Twitter][twitter:pjones], [OpenPGP Key][pgp:pjones], IRC: `pmade`
|
||||
|
||||
## Release Procedures
|
||||
|
||||
When the time comes to release another version of XMonad and Contrib...
|
||||
|
||||
1. Create a release branch (e.g., `release-0.XX`).
|
||||
|
||||
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. Update the version number in the `*.cabal` files and verify
|
||||
dependencies and documentation. This includes the `tested-with:`
|
||||
field.
|
||||
|
||||
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.
|
||||
|
||||
4. Review documentation files and make sure they are accurate:
|
||||
|
||||
- `README.md`
|
||||
- `CHANGES.md`
|
||||
- and the `example-config.hs` in the `xmonad-testing` repo
|
||||
|
||||
5. Generate the manpage:
|
||||
|
||||
* `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`
|
||||
|
||||
6. Tag the repository with the release version (e.g., `v0.13`)
|
||||
|
||||
7. Build the project tarballs (`cabal sdist`)
|
||||
|
||||
8. Upload the packages to Hackage (`cabal upload`)
|
||||
|
||||
9. Merge the release branches into `master`
|
||||
|
||||
10. Update the website:
|
||||
|
||||
* Generate and push haddocks with `xmonad-web/gen-docs.sh`
|
||||
|
||||
* Check that `tour.html` and `intro.html` are up to date, and
|
||||
mention all core bindings
|
||||
|
||||
11. Update the topic for the IRC channel (`#xmonad`)
|
||||
|
||||
12. Send the `announce-0.XX.txt` file to:
|
||||
|
||||
- XMonad mailing list
|
||||
- Haskell Cafe
|
||||
|
||||
[packdeps]: http://hackage.haskell.org/package/packdeps
|
||||
|
||||
[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
|
||||
|
||||
[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
|
248
Main.hs
248
Main.hs
@@ -1,6 +1,6 @@
|
||||
----------------------------------------------------------------------------
|
||||
-- |
|
||||
-- Module : Main.hs
|
||||
-- Module : Main
|
||||
-- Copyright : (c) Spencer Janssen 2007
|
||||
-- License : BSD3-style (see LICENSE)
|
||||
--
|
||||
@@ -12,251 +12,9 @@
|
||||
--
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
module Main where
|
||||
|
||||
import Data.Bits
|
||||
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 System.Environment (getArgs)
|
||||
|
||||
import Graphics.X11.Xlib hiding (refreshKeyboardMapping)
|
||||
import Graphics.X11.Xlib.Extras
|
||||
import Graphics.X11.Xinerama (getScreenInfo)
|
||||
module Main (main) where
|
||||
|
||||
import XMonad
|
||||
import Config
|
||||
import StackSet (new, floating, member)
|
||||
import qualified StackSet as W
|
||||
import Operations
|
||||
|
||||
import System.IO
|
||||
|
||||
-- |
|
||||
-- The main entry point
|
||||
--
|
||||
main :: IO ()
|
||||
main = do
|
||||
dpy <- openDisplay ""
|
||||
let dflt = defaultScreen dpy
|
||||
|
||||
rootw <- rootWindow dpy dflt
|
||||
xinesc <- getScreenInfo dpy
|
||||
nbc <- initColor dpy normalBorderColor
|
||||
fbc <- initColor dpy focusedBorderColor
|
||||
hSetBuffering stdout NoBuffering
|
||||
args <- getArgs
|
||||
|
||||
let initialWinset = new layoutHook workspaces $ zipWith SD xinesc gaps
|
||||
|
||||
maybeRead s = case reads s of
|
||||
[(x, "")] -> Just x
|
||||
_ -> Nothing
|
||||
|
||||
winset = fromMaybe initialWinset $ do
|
||||
("--resume" : s : _) <- return args
|
||||
ws <- maybeRead s
|
||||
return . W.ensureTags layoutHook workspaces
|
||||
$ W.mapLayout (fromMaybe layoutHook . maybeRead) ws
|
||||
|
||||
gaps = take (length xinesc) $ defaultGaps ++ repeat (0,0,0,0)
|
||||
|
||||
cf = XConf
|
||||
{ display = dpy
|
||||
, theRoot = rootw
|
||||
, normalBorder = nbc
|
||||
, focusedBorder = fbc }
|
||||
st = XState
|
||||
{ windowset = initialWinset
|
||||
, mapped = S.empty
|
||||
, waitingUnmap = M.empty
|
||||
, dragging = Nothing }
|
||||
|
||||
xSetErrorHandler -- in C, I'm too lazy to write the binding: dons
|
||||
|
||||
-- setup initial X environment
|
||||
sync dpy False
|
||||
selectInput dpy rootw $ substructureRedirectMask .|. substructureNotifyMask
|
||||
.|. enterWindowMask .|. leaveWindowMask .|. structureNotifyMask
|
||||
|
||||
allocaXEvent $ \e ->
|
||||
runX cf st $ do
|
||||
|
||||
grabKeys
|
||||
grabButtons
|
||||
|
||||
io $ sync dpy False
|
||||
|
||||
-- bootstrap the windowset, Operations.windows will identify all
|
||||
-- the windows in winset as new and set initial properties for
|
||||
-- those windows
|
||||
windows (const winset)
|
||||
|
||||
-- scan for all top-level windows, add the unmanaged ones to the
|
||||
-- windowset
|
||||
ws <- io $ scan dpy rootw
|
||||
mapM_ manage ws
|
||||
|
||||
-- main loop, for all you HOF/recursion fans out there.
|
||||
forever_ $ handle =<< io (nextEvent dpy e >> getEvent e)
|
||||
|
||||
return ()
|
||||
where forever_ a = a >> forever_ a
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- IO stuff. Doesn't require any X state
|
||||
-- Most of these things run only on startup (bar grabkeys)
|
||||
|
||||
-- | scan for any new windows to manage. If they're already managed,
|
||||
-- this should be idempotent.
|
||||
scan :: Display -> Window -> IO [Window]
|
||||
scan dpy rootw = do
|
||||
(_, _, ws) <- queryTree dpy rootw
|
||||
filterM ok ws
|
||||
-- TODO: scan for windows that are either 'IsViewable' or where WM_STATE ==
|
||||
-- Iconic
|
||||
where ok w = do wa <- getWindowAttributes dpy w
|
||||
a <- internAtom dpy "WM_STATE" False
|
||||
p <- getWindowProperty32 dpy a w
|
||||
let ic = case p of
|
||||
Just (3:_) -> True -- 3 for iconified
|
||||
_ -> False
|
||||
return $ not (wa_override_redirect wa)
|
||||
&& (wa_map_state wa == waIsViewable || ic)
|
||||
|
||||
-- | 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
|
||||
io $ ungrabKey dpy anyKey anyModifier rootw
|
||||
forM_ (M.keys keys) $ \(mask,sym) -> do
|
||||
kc <- io $ keysymToKeycode dpy sym
|
||||
-- "If the specified KeySym is not defined for any KeyCode,
|
||||
-- XKeysymToKeycode() returns zero."
|
||||
when (kc /= '\0') $ mapM_ (grab kc . (mask .|.)) extraModifiers
|
||||
|
||||
-- | XXX comment me
|
||||
grabButtons :: X ()
|
||||
grabButtons = do
|
||||
XConf { display = dpy, theRoot = rootw } <- ask
|
||||
let grab button mask = io $ grabButton dpy button mask rootw False buttonPressMask
|
||||
grabModeAsync grabModeSync none none
|
||||
io $ ungrabButton dpy anyButton anyModifier rootw
|
||||
mapM_ (\(m,b) -> mapM_ (grab b . (m .|.)) extraModifiers) (M.keys mouseBindings)
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- | Event handler. Map X events onto calls into Operations.hs, which
|
||||
-- modify our internal model of the window manager state.
|
||||
--
|
||||
-- Events dwm handles that we don't:
|
||||
--
|
||||
-- [ButtonPress] = buttonpress,
|
||||
-- [Expose] = expose,
|
||||
-- [PropertyNotify] = propertynotify,
|
||||
--
|
||||
|
||||
handle :: Event -> X ()
|
||||
|
||||
-- run window manager command
|
||||
handle (KeyEvent {ev_event_type = t, ev_state = m, ev_keycode = code})
|
||||
| t == keyPress = withDisplay $ \dpy -> do
|
||||
s <- io $ keycodeToKeysym dpy code 0
|
||||
userCode $ whenJust (M.lookup (cleanMask m,s) keys) id
|
||||
|
||||
-- manage a new window
|
||||
handle (MapRequestEvent {ev_window = w}) = withDisplay $ \dpy -> do
|
||||
wa <- io $ getWindowAttributes dpy w -- ignore override windows
|
||||
-- need to ignore mapping requests by managed windows not on the current workspace
|
||||
managed <- isClient w
|
||||
when (not (wa_override_redirect wa) && not managed) $ do manage w
|
||||
|
||||
-- window destroyed, unmanage it
|
||||
-- window gone, unmanage it
|
||||
handle (DestroyWindowEvent {ev_window = w}) = whenX (isClient w) $ unmanage w
|
||||
|
||||
-- We track expected unmap events in waitingUnmap. We ignore this event unless
|
||||
-- it is synthetic or we are not expecting an unmap notification from a window.
|
||||
handle (UnmapEvent {ev_window = w, ev_send_event = synthetic}) = whenX (isClient w) $ do
|
||||
e <- gets (fromMaybe 0 . M.lookup w . waitingUnmap)
|
||||
if (synthetic || e == 0)
|
||||
then unmanage w
|
||||
else modify (\s -> s { waitingUnmap = M.adjust pred w (waitingUnmap s) })
|
||||
|
||||
-- set keyboard mapping
|
||||
handle e@(MappingNotifyEvent {}) = do
|
||||
io $ refreshKeyboardMapping e
|
||||
when (ev_request e == mappingKeyboard) grabKeys
|
||||
|
||||
-- handle button release, which may finish dragging.
|
||||
handle e@(ButtonEvent {ev_event_type = t})
|
||||
| t == buttonRelease = do
|
||||
drag <- gets dragging
|
||||
case drag of
|
||||
-- we're done dragging and have released the mouse:
|
||||
Just (_,f) -> modify (\s -> s { dragging = Nothing }) >> f
|
||||
Nothing -> broadcastMessage e
|
||||
|
||||
-- handle motionNotify event, which may mean we are dragging.
|
||||
handle e@(MotionEvent {ev_event_type = _t, ev_x = x, ev_y = y}) = do
|
||||
drag <- gets dragging
|
||||
case drag of
|
||||
Just (d,_) -> d (fromIntegral x) (fromIntegral y) -- we're dragging
|
||||
Nothing -> broadcastMessage e
|
||||
|
||||
-- click on an unfocused window, makes it focused on this workspace
|
||||
handle e@(ButtonEvent {ev_window = w,ev_event_type = t,ev_button = b })
|
||||
| t == buttonPress = do
|
||||
-- If it's the root window, then it's something we
|
||||
-- grabbed in grabButtons. Otherwise, it's click-to-focus.
|
||||
isr <- isRoot w
|
||||
if isr then userCode $ whenJust (M.lookup (cleanMask (ev_state e), b) mouseBindings) ($ ev_subwindow e)
|
||||
else focus w
|
||||
sendMessage e -- Always send button events.
|
||||
|
||||
-- entered a normal window, makes this focused.
|
||||
handle e@(CrossingEvent {ev_window = w, ev_event_type = t})
|
||||
| t == enterNotify && ev_mode e == notifyNormal
|
||||
&& ev_detail e /= notifyInferior = focus w
|
||||
|
||||
-- left a window, check if we need to focus root
|
||||
handle e@(CrossingEvent {ev_event_type = t})
|
||||
| t == leaveNotify
|
||||
= do rootw <- asks theRoot
|
||||
when (ev_window e == rootw && not (ev_same_screen e)) $ setFocusX rootw
|
||||
|
||||
-- configure a window
|
||||
handle e@(ConfigureRequestEvent {ev_window = w}) = withDisplay $ \dpy -> do
|
||||
ws <- gets windowset
|
||||
wa <- io $ getWindowAttributes dpy w
|
||||
|
||||
if M.member w (floating ws)
|
||||
|| not (member w ws)
|
||||
then do io $ configureWindow dpy w (ev_value_mask e) $ WindowChanges
|
||||
{ wc_x = ev_x e
|
||||
, wc_y = ev_y e
|
||||
, wc_width = ev_width e
|
||||
, wc_height = ev_height e
|
||||
, wc_border_width = fromIntegral borderWidth
|
||||
, wc_sibling = ev_above e
|
||||
, wc_stack_mode = ev_detail e }
|
||||
when (member w ws) (float w)
|
||||
else io $ allocaXEvent $ \ev -> do
|
||||
setEventType ev configureNotify
|
||||
setConfigureEvent ev w w
|
||||
(wa_x wa) (wa_y wa) (wa_width wa)
|
||||
(wa_height wa) (ev_border_width e) none (wa_override_redirect wa)
|
||||
sendEvent dpy w False 0 ev
|
||||
io $ sync dpy False
|
||||
|
||||
-- configuration changes in the root may mean display settings have changed
|
||||
handle (ConfigureEvent {ev_window = w}) = whenX (isRoot w) rescreen
|
||||
|
||||
-- property notify
|
||||
handle PropertyEvent { ev_event_type = t, ev_atom = a }
|
||||
| t == propertyNotify && a == wM_NAME = userCode logHook
|
||||
|
||||
handle e = broadcastMessage e -- trace (eventName e) -- ignoring
|
||||
main = xmonad def
|
||||
|
656
Operations.hs
656
Operations.hs
@@ -1,656 +0,0 @@
|
||||
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||||
{-# OPTIONS_GHC -fglasgow-exts #-} -- For deriving Data/Typeable
|
||||
{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, PatternGuards, TypeSynonymInstances #-}
|
||||
|
||||
-- --------------------------------------------------------------------------
|
||||
-- |
|
||||
-- Module : Operations.hs
|
||||
-- Copyright : (c) Spencer Janssen 2007
|
||||
-- License : BSD3-style (see LICENSE)
|
||||
--
|
||||
-- Maintainer : dons@cse.unsw.edu.au
|
||||
-- Stability : unstable
|
||||
-- Portability : not portable, Typeable deriving, mtl, posix
|
||||
--
|
||||
-- Operations.
|
||||
--
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
module Operations where
|
||||
|
||||
import XMonad
|
||||
import qualified StackSet as W
|
||||
import {-# SOURCE #-} Config (borderWidth,logHook,manageHook,numlockMask,serialisedLayouts)
|
||||
|
||||
import Data.Maybe
|
||||
import Data.List (nub, (\\), find, partition)
|
||||
import Data.Bits ((.|.), (.&.), complement)
|
||||
import Data.Ratio
|
||||
import qualified Data.Map as M
|
||||
import qualified Data.Set as S
|
||||
|
||||
import Control.Monad.State
|
||||
import Control.Monad.Reader
|
||||
import Control.Arrow ((***), second)
|
||||
|
||||
import System.IO
|
||||
import Graphics.X11.Xlib
|
||||
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.
|
||||
-- Bring it into focus.
|
||||
--
|
||||
-- Whether the window is already managed, or not, it is mapped, has its
|
||||
-- border set, and its event mask set.
|
||||
--
|
||||
manage :: Window -> X ()
|
||||
manage w = whenX (fmap 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 `liftM` io (getTransientForHint d w)
|
||||
|
||||
(sc, rr) <- floatLocation w
|
||||
-- ensure that float windows don't go over the edge of the screen
|
||||
let adjust (W.RationalRect x y wid h) | x + wid > 1 || y + h > 1 || x < 0 || y < 0
|
||||
= W.RationalRect (0.5 - wid/2) (0.5 - h/2) wid h
|
||||
adjust r = r
|
||||
|
||||
f ws | isFixedSize || isTransient = W.float w (adjust rr) . W.insertUp w . W.view i $ ws
|
||||
| otherwise = W.insertUp w ws
|
||||
where i = fromMaybe (W.tag . W.workspace . W.current $ ws) $ W.lookupWorkspace sc ws
|
||||
|
||||
n <- fmap (fromMaybe "") $ io $ fetchName d w
|
||||
(ClassHint rn rc) <- io $ getClassHint d w
|
||||
g <- manageHook w n rn rc `catchX` return id
|
||||
windows (g . f)
|
||||
|
||||
-- | unmanage. A window no longer exists, remove it from the window
|
||||
-- list, on whatever workspace it is.
|
||||
--
|
||||
-- should also unmap?
|
||||
--
|
||||
unmanage :: Window -> X ()
|
||||
unmanage w = do
|
||||
windows (W.delete w)
|
||||
setWMState w 0 {-withdrawn-}
|
||||
modify (\s -> s {mapped = S.delete w (mapped s), waitingUnmap = M.delete w (waitingUnmap s)})
|
||||
|
||||
-- | Modify the size of the status gap at the top of the current screen
|
||||
-- Taking a function giving the current screen, and current geometry.
|
||||
modifyGap :: (Int -> (Int,Int,Int,Int) -> (Int,Int,Int,Int)) -> X ()
|
||||
modifyGap f = do
|
||||
windows $ \ws@(W.StackSet { W.current = c@(W.Screen { W.screenDetail = sd }) }) ->
|
||||
let n = fromIntegral . W.screen $ c
|
||||
g = f n . statusGap $ sd
|
||||
in ws { W.current = c { W.screenDetail = sd { statusGap = g } } }
|
||||
|
||||
-- | Kill the currently focused client. If we do kill it, we'll get a
|
||||
-- delete notify back from X.
|
||||
--
|
||||
-- There are two ways to delete a window. Either just kill it, or if it
|
||||
-- supports the delete protocol, send a delete event (e.g. firefox)
|
||||
--
|
||||
kill :: X ()
|
||||
kill = withDisplay $ \d -> withFocused $ \w -> do
|
||||
wmdelt <- atom_WM_DELETE_WINDOW ; wmprot <- atom_WM_PROTOCOLS
|
||||
|
||||
protocols <- io $ getWMProtocols d w
|
||||
io $ if wmdelt `elem` protocols
|
||||
then allocaXEvent $ \ev -> do
|
||||
setEventType ev clientMessage
|
||||
setClientMessageEvent ev w wmprot 32 wmdelt 0
|
||||
sendEvent d w False noEventMask ev
|
||||
else killClient d w >> return ()
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Managing windows
|
||||
|
||||
data LayoutMessages = Hide | ReleaseResources deriving ( Typeable, Eq )
|
||||
|
||||
instance Message LayoutMessages
|
||||
|
||||
-- | windows. Modify the current window list with a pure function, and refresh
|
||||
windows :: (WindowSet -> WindowSet) -> X ()
|
||||
windows f = do
|
||||
XState { windowset = old } <- get
|
||||
let oldvisible = concatMap (W.integrate' . W.stack . W.workspace) $ W.current old : W.visible old
|
||||
ws = f old
|
||||
XConf { display = d , normalBorder = nbc, focusedBorder = fbc } <- ask
|
||||
mapM_ setInitialProperties (W.allWindows ws \\ W.allWindows old)
|
||||
whenJust (W.peek old) $ \otherw -> io $ setWindowBorder d otherw nbc
|
||||
modify (\s -> s { windowset = ws })
|
||||
|
||||
-- notify non visibility
|
||||
let tags_oldvisible = map (W.tag . W.workspace) $ W.current old : W.visible old
|
||||
gottenhidden = filter (`elem` tags_oldvisible) $ map W.tag $ W.hidden ws
|
||||
sendMessageToWorkspaces Hide gottenhidden
|
||||
|
||||
-- for each workspace, layout the currently visible workspaces
|
||||
let allscreens = W.screens ws
|
||||
summed_visible = scanl (++) [] $ map (W.integrate' . W.stack . W.workspace) allscreens
|
||||
visible <- fmap concat $ forM (zip allscreens summed_visible) $ \ (w, vis) -> do
|
||||
let n = W.tag (W.workspace w)
|
||||
this = W.view n ws
|
||||
l = W.layout (W.workspace w)
|
||||
flt = filter (flip M.member (W.floating ws)) (W.index this)
|
||||
tiled = (W.stack . W.workspace . W.current $ this)
|
||||
>>= W.filter (not . flip M.member (W.floating ws))
|
||||
>>= W.filter (not . (`elem` vis))
|
||||
(SD (Rectangle sx sy sw sh)
|
||||
(gt,gb,gl,gr)) = W.screenDetail w
|
||||
viewrect = Rectangle (sx + fromIntegral gl) (sy + fromIntegral gt)
|
||||
(sw - fromIntegral (gl + gr)) (sh - fromIntegral (gt + gb))
|
||||
|
||||
-- just the tiled windows:
|
||||
-- now tile the windows on this workspace, modified by the gap
|
||||
(rs, ml') <- runLayout l viewrect tiled `catchX` runLayout (Layout Full) viewrect tiled
|
||||
mapM_ (uncurry tileWindow) rs
|
||||
whenJust ml' $ \l' -> runOnWorkspaces (\ww -> if W.tag ww == n
|
||||
then return $ ww { W.layout = l'}
|
||||
else return ww)
|
||||
|
||||
-- now the floating windows:
|
||||
-- move/resize the floating windows, if there are any
|
||||
forM_ flt $ \fw -> whenJust (M.lookup fw (W.floating ws)) $
|
||||
\(W.RationalRect rx ry rw rh) -> do
|
||||
tileWindow fw $ Rectangle
|
||||
(sx + floor (toRational sw*rx)) (sy + floor (toRational sh*ry))
|
||||
(floor (toRational sw*rw)) (floor (toRational sh*rh))
|
||||
|
||||
let vs = flt ++ map fst rs
|
||||
io $ restackWindows d vs
|
||||
-- return the visible windows for this workspace:
|
||||
return vs
|
||||
|
||||
whenJust (W.peek ws) $ \w -> io $ setWindowBorder d w fbc
|
||||
setTopFocus
|
||||
userCode logHook
|
||||
-- io performGC -- really helps, but seems to trigger GC bugs?
|
||||
|
||||
-- hide every window that was potentially visible before, but is not
|
||||
-- given a position by a layout now.
|
||||
mapM_ hide (nub oldvisible \\ visible)
|
||||
|
||||
clearEvents enterWindowMask
|
||||
|
||||
-- | setWMState. set the 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]
|
||||
|
||||
-- | hide. Hide a window by unmapping it, and setting Iconified.
|
||||
hide :: Window -> X ()
|
||||
hide w = whenX (gets (S.member w . mapped)) $ withDisplay $ \d -> do
|
||||
io $ do selectInput d w (clientMask .&. complement structureNotifyMask)
|
||||
unmapWindow d w
|
||||
selectInput d w clientMask
|
||||
setWMState w 3 --iconic
|
||||
-- this part is key: we increment the waitingUnmap counter to distinguish
|
||||
-- between client and xmonad initiated unmaps.
|
||||
modify (\s -> s { waitingUnmap = M.insertWith (+) w 1 (waitingUnmap s)
|
||||
, mapped = S.delete w (mapped s) })
|
||||
|
||||
-- | reveal. 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 1 --normal
|
||||
io $ mapWindow d w
|
||||
modify (\s -> s { mapped = S.insert w (mapped s) })
|
||||
|
||||
-- | The client events that xmonad is interested in
|
||||
clientMask :: EventMask
|
||||
clientMask = structureNotifyMask .|. enterWindowMask .|. propertyChangeMask
|
||||
|
||||
-- | Set some properties when we initially gain control of a window
|
||||
setInitialProperties :: Window -> X ()
|
||||
setInitialProperties w = asks normalBorder >>= \nb -> withDisplay $ \d -> io $ do
|
||||
selectInput d w $ clientMask
|
||||
setWindowBorderWidth d w borderWidth
|
||||
-- we must initially set the color of new windows, to maintain invariants
|
||||
-- required by the border setting in 'windows'
|
||||
setWindowBorder d w nb
|
||||
|
||||
-- | refresh. Render the currently visible workspaces, as determined by
|
||||
-- the StackSet. Also, set focus to the focused window.
|
||||
--
|
||||
-- This is our 'view' operation (MVC), in that it pretty prints our model
|
||||
-- with X calls.
|
||||
--
|
||||
refresh :: X ()
|
||||
refresh = windows id
|
||||
|
||||
-- | clearEvents. Remove all events of a given type from the event queue.
|
||||
clearEvents :: EventMask -> X ()
|
||||
clearEvents mask = withDisplay $ \d -> io $ do
|
||||
sync d False
|
||||
allocaXEvent $ \p -> fix $ \again -> do
|
||||
more <- checkMaskEvent d mask p
|
||||
when more again -- beautiful
|
||||
|
||||
-- | tileWindow. Moves and resizes w such that it fits inside the given
|
||||
-- rectangle, including its border.
|
||||
tileWindow :: Window -> Rectangle -> X ()
|
||||
tileWindow w r = withDisplay $ \d -> do
|
||||
bw <- (fromIntegral . wa_border_width) `liftM` io (getWindowAttributes d w)
|
||||
-- give all windows at least 1x1 pixels
|
||||
let least x | x <= bw*2 = 1
|
||||
| otherwise = x - bw*2
|
||||
io $ moveResizeWindow d w (rect_x r) (rect_y r)
|
||||
(least $ rect_width r) (least $ rect_height r)
|
||||
reveal w
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
|
||||
-- | rescreen. 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 (io . getScreenInfo)
|
||||
|
||||
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..] $ zipWith SD xinesc gs
|
||||
sgs = map (statusGap . W.screenDetail) (v:vs)
|
||||
gs = take (length xinesc) (sgs ++ repeat (0,0,0,0))
|
||||
in ws { W.current = a
|
||||
, W.visible = as
|
||||
, W.hidden = ys }
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
|
||||
-- | setButtonGrab. Tell whether or not to intercept clicks on a given window
|
||||
setButtonGrab :: Bool -> Window -> X ()
|
||||
setButtonGrab grab w = withDisplay $ \d -> io $
|
||||
if grab
|
||||
then forM_ [button1, button2, button3] $ \b ->
|
||||
grabButton d b anyModifier w False buttonPressMask
|
||||
grabModeAsync grabModeSync none none
|
||||
else ungrabButton d anyButton anyModifier w
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Setting keyboard focus
|
||||
|
||||
-- | Set the focus to the window on top of the stack, or root
|
||||
setTopFocus :: X ()
|
||||
setTopFocus = withWindowSet $ maybe (setFocusX =<< asks theRoot) setFocusX . W.peek
|
||||
|
||||
-- | Set focus explicitly to window 'w' if it is managed by us, or root.
|
||||
-- This happens if X notices we've moved the mouse (and perhaps moved
|
||||
-- the mouse to a new screen).
|
||||
focus :: Window -> X ()
|
||||
focus w = withWindowSet $ \s -> do
|
||||
if W.member w s then when (W.peek s /= Just w) $ windows (W.focusWindow w)
|
||||
else whenX (isRoot w) $ setFocusX w
|
||||
|
||||
-- | Call X to set the keyboard focus details.
|
||||
setFocusX :: Window -> X ()
|
||||
setFocusX w = withWindowSet $ \ws -> do
|
||||
dpy <- asks display
|
||||
|
||||
-- clear mouse button grab and border on other windows
|
||||
forM_ (W.current ws : W.visible ws) $ \wk -> do
|
||||
forM_ (W.index (W.view (W.tag (W.workspace wk)) ws)) $ \otherw -> do
|
||||
setButtonGrab True otherw
|
||||
|
||||
-- If we ungrab buttons on the root window, we lose our mouse bindings.
|
||||
whenX (not `liftM` isRoot w) $ setButtonGrab False w
|
||||
io $ do setInputFocus dpy w revertToPointerRoot 0
|
||||
-- raiseWindow dpy w
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Message handling
|
||||
|
||||
-- | Throw a message to the current LayoutClass possibly modifying how we
|
||||
-- layout the windows, then refresh.
|
||||
sendMessage :: Message a => a -> X ()
|
||||
sendMessage a = do
|
||||
w <- (W.workspace . W.current) `fmap` gets windowset
|
||||
ml' <- handleMessage (W.layout w) (SomeMessage a) `catchX` return Nothing
|
||||
whenJust ml' $ \l' -> do
|
||||
windows $ \ws -> ws { W.current = (W.current ws)
|
||||
{ W.workspace = (W.workspace $ W.current ws)
|
||||
{ W.layout = l' }}}
|
||||
|
||||
-- | Send a message to a list of workspaces' layouts, without necessarily refreshing.
|
||||
sendMessageToWorkspaces :: Message a => a -> [WorkspaceId] -> X ()
|
||||
sendMessageToWorkspaces a l = runOnWorkspaces $ \w ->
|
||||
if W.tag w `elem` l
|
||||
then do ml' <- handleMessage (W.layout w) (SomeMessage a) `catchX` return Nothing
|
||||
return $ w { W.layout = maybe (W.layout w) id ml' }
|
||||
else return w
|
||||
|
||||
-- | Send a message to all visible layouts, without necessarily refreshing.
|
||||
-- This is how we implement the hooks, such as UnDoLayout.
|
||||
broadcastMessage :: Message a => a -> X ()
|
||||
broadcastMessage a = runOnWorkspaces $ \w -> do
|
||||
ml' <- handleMessage (W.layout w) (SomeMessage a) `catchX` return Nothing
|
||||
return $ w { W.layout = maybe (W.layout w) id ml' }
|
||||
|
||||
-- | This is basically a map function, running a function in the X monad on
|
||||
-- each workspace with the output of that function being the modified workspace.
|
||||
runOnWorkspaces :: (WindowSpace -> X WindowSpace) -> X ()
|
||||
runOnWorkspaces job =do
|
||||
ws <- gets windowset
|
||||
h <- mapM job $ W.hidden ws
|
||||
c:v <- mapM (\s -> fmap (\w -> s { W.workspace = w}) $ job (W.workspace s))
|
||||
$ W.current ws : W.visible ws
|
||||
modify $ \s -> s { windowset = ws { W.current = c, W.visible = v, W.hidden = h } }
|
||||
|
||||
-- | Set the layout of the currently viewed workspace
|
||||
setLayout :: Layout Window -> X ()
|
||||
setLayout l = do
|
||||
ss@(W.StackSet { W.current = c@(W.Screen { W.workspace = ws })}) <- gets windowset
|
||||
handleMessage (W.layout ws) (SomeMessage ReleaseResources)
|
||||
windows $ const $ ss {W.current = c { W.workspace = ws { W.layout = l } } }
|
||||
|
||||
-- | X Events are valid Messages
|
||||
instance Message Event
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- LayoutClass selection manager
|
||||
|
||||
-- | A layout that allows users to switch between various layout options.
|
||||
-- This layout accepts three Messages:
|
||||
--
|
||||
-- > NextLayout
|
||||
-- > PrevLayout
|
||||
-- > JumpToLayout.
|
||||
--
|
||||
data ChangeLayout = NextLayout | PrevLayout | JumpToLayout String
|
||||
deriving (Eq, Show, Typeable)
|
||||
|
||||
instance Message ChangeLayout
|
||||
|
||||
instance ReadableLayout Window where
|
||||
readTypes = Layout (Select []) :
|
||||
Layout Full : Layout (Tall 1 0.1 0.5) :
|
||||
Layout (Mirror $ Tall 1 0.1 0.5) :
|
||||
serialisedLayouts
|
||||
|
||||
data Select a = Select [Layout a] deriving (Show, Read)
|
||||
|
||||
instance ReadableLayout a => LayoutClass Select a where
|
||||
doLayout (Select (l:ls)) r s =
|
||||
second (fmap (Select . (:ls))) `fmap` doLayout l r s
|
||||
doLayout (Select []) r s =
|
||||
second (const Nothing) `fmap` doLayout Full r s
|
||||
|
||||
-- respond to messages only when there's an actual choice:
|
||||
handleMessage (Select (l:ls@(_:_))) m
|
||||
| Just NextLayout <- fromMessage m = switchl rls
|
||||
| Just PrevLayout <- fromMessage m = switchl rls'
|
||||
| Just (JumpToLayout x) <- fromMessage m = switchl (j x)
|
||||
| Just ReleaseResources <- fromMessage m = do -- each branch has a different type
|
||||
mlls' <- mapM (flip handleMessage m) (l:ls)
|
||||
let lls' = zipWith (flip maybe id) (l:ls) mlls'
|
||||
return (Just (Select lls'))
|
||||
|
||||
where rls [] = []
|
||||
rls (x:xs) = xs ++ [x]
|
||||
rls' = reverse . rls . reverse
|
||||
|
||||
j s zs = case partition ((s ==) . description) zs of (xs,ys) -> xs++ys
|
||||
|
||||
switchl f = do ml' <- handleMessage l (SomeMessage Hide)
|
||||
return $ Just (Select $ f $ fromMaybe l ml':ls)
|
||||
|
||||
-- otherwise, or if we don't understand the message, pass it along to the real layout:
|
||||
handleMessage (Select (l:ls)) m =
|
||||
fmap (Select . (:ls)) `fmap` handleMessage l m
|
||||
|
||||
-- Unless there is no layout...
|
||||
handleMessage (Select []) _ = return Nothing
|
||||
|
||||
description (Select (x:_)) = description x
|
||||
description _ = "default"
|
||||
|
||||
--
|
||||
-- | Builtin layout algorithms:
|
||||
--
|
||||
-- > fullscreen mode
|
||||
-- > tall mode
|
||||
--
|
||||
-- The latter algorithms support the following operations:
|
||||
--
|
||||
-- > Shrink
|
||||
-- > Expand
|
||||
--
|
||||
data Resize = Shrink | Expand deriving Typeable
|
||||
|
||||
-- | You can also increase the number of clients in the master pane
|
||||
data IncMasterN = IncMasterN Int deriving Typeable
|
||||
|
||||
instance Message Resize
|
||||
instance Message IncMasterN
|
||||
|
||||
-- | Simple fullscreen mode, just render all windows fullscreen.
|
||||
data Full a = Full deriving (Show, Read)
|
||||
|
||||
instance LayoutClass Full a
|
||||
|
||||
-- | The inbuilt tiling mode of xmonad, and its operations.
|
||||
data Tall a = Tall Int Rational Rational deriving (Show, Read)
|
||||
|
||||
instance LayoutClass Tall a where
|
||||
doLayout (Tall nmaster _ frac) r =
|
||||
return . (flip (,) Nothing) .
|
||||
ap zip (tile frac r nmaster . length) . W.integrate
|
||||
|
||||
pureMessage (Tall nmaster delta frac) m = msum [fmap resize (fromMessage m)
|
||||
,fmap incmastern (fromMessage m)]
|
||||
where resize Shrink = Tall nmaster delta (max 0 $ frac-delta)
|
||||
resize Expand = Tall nmaster delta (min 1 $ frac+delta)
|
||||
incmastern (IncMasterN d) = Tall (max 0 (nmaster+d)) delta frac
|
||||
description _ = "Tall"
|
||||
|
||||
-- | Mirror a rectangle
|
||||
mirrorRect :: Rectangle -> Rectangle
|
||||
mirrorRect (Rectangle rx ry rw rh) = (Rectangle ry rx rh rw)
|
||||
|
||||
-- | Mirror a layout, compute its 90 degree rotated form.
|
||||
data Mirror l a = Mirror (l a) deriving (Show, Read)
|
||||
|
||||
instance LayoutClass l a => LayoutClass (Mirror l) a where
|
||||
doLayout (Mirror l) r s = (map (second mirrorRect) *** fmap Mirror)
|
||||
`fmap` doLayout l (mirrorRect r) s
|
||||
handleMessage (Mirror l) = fmap (fmap Mirror) . handleMessage l
|
||||
description (Mirror l) = "Mirror "++ description l
|
||||
|
||||
-- | tile. Compute the positions for windows using the default 2 pane tiling algorithm.
|
||||
--
|
||||
-- The screen is divided (currently) into two panes. all clients are
|
||||
-- then partioned between these two panes. one pane, the `master', by
|
||||
-- convention has the least number of windows in it (by default, 1).
|
||||
-- the variable `nmaster' controls how many windows are rendered in the
|
||||
-- master pane.
|
||||
--
|
||||
-- `delta' specifies the ratio of the screen to resize by.
|
||||
--
|
||||
-- 'frac' specifies what proportion of the screen to devote to the
|
||||
-- master area.
|
||||
--
|
||||
tile :: Rational -> Rectangle -> Int -> Int -> [Rectangle]
|
||||
tile f r nmaster n = if n <= nmaster || nmaster == 0
|
||||
then splitVertically n r
|
||||
else splitVertically nmaster r1 ++ splitVertically (n-nmaster) r2 -- two columns
|
||||
where (r1,r2) = splitHorizontallyBy f r
|
||||
|
||||
--
|
||||
-- Divide the screen vertically into n subrectangles
|
||||
--
|
||||
splitVertically, splitHorizontally :: Int -> Rectangle -> [Rectangle]
|
||||
splitVertically n r | n < 2 = [r]
|
||||
splitVertically n (Rectangle sx sy sw sh) = Rectangle sx sy sw smallh :
|
||||
splitVertically (n-1) (Rectangle sx (sy+fromIntegral smallh) sw (sh-smallh))
|
||||
where smallh = sh `div` fromIntegral n --hmm, this is a fold or map.
|
||||
|
||||
splitHorizontally n = map mirrorRect . splitVertically n . mirrorRect
|
||||
|
||||
-- Divide the screen into two rectangles, using a rational to specify the ratio
|
||||
splitHorizontallyBy, splitVerticallyBy :: RealFrac r => r -> Rectangle -> (Rectangle, Rectangle)
|
||||
splitHorizontallyBy f (Rectangle sx sy sw sh) =
|
||||
( Rectangle sx sy leftw sh
|
||||
, Rectangle (sx + fromIntegral leftw) sy (sw-fromIntegral leftw) sh)
|
||||
where leftw = floor $ fromIntegral sw * f
|
||||
|
||||
-- | XXX comment me
|
||||
splitVerticallyBy f = (mirrorRect *** mirrorRect) . splitHorizontallyBy f . mirrorRect
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Utilities
|
||||
|
||||
-- | Return workspace visible on screen 'sc', or Nothing.
|
||||
screenWorkspace :: ScreenId -> X (Maybe WorkspaceId)
|
||||
screenWorkspace sc = withWindowSet $ return . W.lookupWorkspace sc
|
||||
|
||||
-- | Apply an X operation to the currently focused window, if there is one.
|
||||
withFocused :: (Window -> X ()) -> X ()
|
||||
withFocused f = withWindowSet $ \w -> whenJust (W.peek w) f
|
||||
|
||||
-- | True if window is under management by us
|
||||
isClient :: Window -> X Bool
|
||||
isClient w = withWindowSet $ return . W.member w
|
||||
|
||||
-- | Combinations of extra modifier masks we need to grab keys\/buttons for.
|
||||
-- (numlock and capslock)
|
||||
extraModifiers :: [KeyMask]
|
||||
extraModifiers = [0, numlockMask, lockMask, numlockMask .|. lockMask ]
|
||||
|
||||
-- | Strip numlock\/capslock from a mask
|
||||
cleanMask :: KeyMask -> KeyMask
|
||||
cleanMask = (complement (numlockMask .|. lockMask) .&.)
|
||||
|
||||
-- | Get the Pixel value for a named color
|
||||
initColor :: Display -> String -> IO Pixel
|
||||
initColor dpy c = (color_pixel . fst) `liftM` allocNamedColor dpy colormap c
|
||||
where colormap = defaultColormap dpy (defaultScreen dpy)
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- | Floating layer support
|
||||
|
||||
-- | Given a window, find the screen it is located on, and compute
|
||||
-- the geometry of that window wrt. that screen.
|
||||
floatLocation :: Window -> X (ScreenId, W.RationalRect)
|
||||
floatLocation w = withDisplay $ \d -> do
|
||||
ws <- gets windowset
|
||||
wa <- io $ getWindowAttributes d w
|
||||
|
||||
-- XXX horrible
|
||||
let sc = fromMaybe (W.current ws) $ find (pointWithin (fi $ wa_x wa) (fi $ wa_y wa) . screenRect . W.screenDetail) $ W.screens ws
|
||||
sr = screenRect . W.screenDetail $ sc
|
||||
bw = fi . wa_border_width $ wa
|
||||
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))
|
||||
|
||||
return (W.screen $ sc, rr)
|
||||
where fi x = fromIntegral x
|
||||
pointWithin :: Integer -> Integer -> Rectangle -> Bool
|
||||
pointWithin x y r = x >= fi (rect_x r) &&
|
||||
x < fi (rect_x r) + fi (rect_width r) &&
|
||||
y >= fi (rect_y r) &&
|
||||
y < fi (rect_y r) + fi (rect_height r)
|
||||
|
||||
-- | Make a tiled window floating, using its suggested rectangle
|
||||
float :: Window -> X ()
|
||||
float w = do
|
||||
(sc, rr) <- floatLocation w
|
||||
windows $ \ws -> W.float w rr . fromMaybe ws $ do
|
||||
i <- W.findIndex w ws
|
||||
guard $ i `elem` map (W.tag . W.workspace) (W.screens ws)
|
||||
f <- W.peek ws
|
||||
sw <- W.lookupWorkspace sc ws
|
||||
return (W.focusWindow f . W.shiftWin sw w $ ws)
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Mouse handling
|
||||
|
||||
-- | Accumulate mouse motion events
|
||||
mouseDrag :: (Position -> Position -> X ()) -> X () -> X ()
|
||||
mouseDrag 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
|
||||
modify $ \s -> s { dragging = Just (motion, cleanup) }
|
||||
where
|
||||
cleanup = do
|
||||
withDisplay $ io . flip ungrabPointer currentTime
|
||||
modify $ \s -> s { dragging = Nothing }
|
||||
done
|
||||
motion x y = do z <- f x y
|
||||
clearEvents pointerMotionMask
|
||||
return z
|
||||
|
||||
-- | XXX comment me
|
||||
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 -> io $ moveWindow d w (fromIntegral (fromIntegral (wa_x wa) + (ex - ox)))
|
||||
(fromIntegral (fromIntegral (wa_y wa) + (ey - oy))))
|
||||
(float w)
|
||||
|
||||
-- | XXX comment me
|
||||
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
|
||||
io $ resizeWindow d w `uncurry`
|
||||
applySizeHints sh (ex - fromIntegral (wa_x wa),
|
||||
ey - fromIntegral (wa_y wa)))
|
||||
(float w)
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- | Support for window size hints
|
||||
|
||||
type D = (Dimension, Dimension)
|
||||
|
||||
-- | Reduce the dimensions if needed to comply to the given SizeHints.
|
||||
applySizeHints :: Integral a => SizeHints -> (a,a) -> D
|
||||
applySizeHints sh (w,h) = applySizeHints' sh (fromIntegral $ max 1 w,
|
||||
fromIntegral $ max 1 h)
|
||||
|
||||
-- | XXX comment me
|
||||
applySizeHints' :: SizeHints -> D -> D
|
||||
applySizeHints' sh =
|
||||
maybe id applyMaxSizeHint (sh_max_size sh)
|
||||
. maybe id (\(bw, bh) (w, h) -> (w+bw, h+bh)) (sh_base_size sh)
|
||||
. maybe id applyResizeIncHint (sh_resize_inc sh)
|
||||
. maybe id applyAspectHint (sh_aspect sh)
|
||||
. maybe id (\(bw,bh) (w,h) -> (w-bw, h-bh)) (sh_base_size sh)
|
||||
|
||||
-- | Reduce the dimensions so their aspect ratio falls between the two given aspect ratios.
|
||||
applyAspectHint :: (D, D) -> D -> D
|
||||
applyAspectHint ((minx, miny), (maxx, maxy)) x@(w,h)
|
||||
| or [minx < 1, miny < 1, maxx < 1, maxy < 1] = x
|
||||
| w * maxy > h * maxx = (h * maxx `div` maxy, h)
|
||||
| w * miny < h * minx = (w, w * miny `div` minx)
|
||||
| otherwise = x
|
||||
|
||||
-- | Reduce the dimensions so they are a multiple of the size increments.
|
||||
applyResizeIncHint :: D -> D -> D
|
||||
applyResizeIncHint (iw,ih) x@(w,h) =
|
||||
if iw > 0 && ih > 0 then (w - w `mod` iw, h - h `mod` ih) else x
|
||||
|
||||
-- | Reduce the dimensions if they exceed the given maximum dimensions.
|
||||
applyMaxSizeHint :: D -> D -> D
|
||||
applyMaxSizeHint (mw,mh) x@(w,h) =
|
||||
if mw > 0 && mh > 0 then (min w mw,min h mh) else x
|
120
README
120
README
@@ -1,120 +0,0 @@
|
||||
xmonad : a lightweight X11 window manager.
|
||||
|
||||
http://xmonad.org
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
About:
|
||||
|
||||
Xmonad is a tiling window manager for X. Windows are managed using
|
||||
automatic tiling algorithms, which can be dynamically configured.
|
||||
Windows are arranged so as to tile the screen without gaps, maximising
|
||||
screen use. All features of the window manager are accessible
|
||||
from the keyboard: a mouse is strictly optional. Xmonad is written
|
||||
and extensible in Haskell, and custom layout algorithms may be
|
||||
implemented by the user in config files. A guiding principle of the
|
||||
user interface is <i>predictability</i>: users should know in
|
||||
advance precisely the window arrangement that will result from any
|
||||
action, leading to an intuitive user interface.
|
||||
|
||||
Xmonad provides three tiling algorithms by default: tall, wide and
|
||||
fullscreen. In tall or wide mode, all windows are visible and tiled
|
||||
to fill the plane without gaps. In fullscreen mode only the focused
|
||||
window is visible, filling the screen. Alternative tiling
|
||||
algorithms are provided as extensions. Sets of windows are grouped
|
||||
together on virtual workspaces and each workspace retains its own
|
||||
layout. Multiple physical monitors are supported via Xinerama,
|
||||
allowing simultaneous display of several workspaces.
|
||||
|
||||
Adhering to a minimalist philosophy of doing one job, and doing it
|
||||
well, the entire code base remains tiny, and is written to be simple
|
||||
to understand and modify. By using Haskell as a configuration
|
||||
language arbitrarily complex extensions may be implemented by the
|
||||
user using a powerful `scripting' language, without needing to
|
||||
modify the window manager directly. For example, users may write
|
||||
their own tiling algorithms.
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
Building:
|
||||
|
||||
Get the dependencies
|
||||
|
||||
Firstly, 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:
|
||||
|
||||
apt-get install libx11-dev
|
||||
|
||||
It is likely that you already have some of these dependencies. To check
|
||||
whether you've got a package run 'ghc-pkg list some_package_name'
|
||||
|
||||
mtl http://hackage.haskell.org/cgi-bin/hackage-scripts/package/mtl-1.0
|
||||
unix http://hackage.haskell.org/cgi-bin/hackage-scripts/package/unix-2.0
|
||||
X11 http://hackage.haskell.org/cgi-bin/hackage-scripts/package/X11-1.2.3
|
||||
X11-extras: http://hackage.haskell.org/cgi-bin/hackage-scripts/package/X11-extras-0.4
|
||||
|
||||
And then build with Cabal:
|
||||
|
||||
runhaskell Setup.lhs configure --prefix=$HOME
|
||||
runhaskell Setup.lhs build
|
||||
runhaskell Setup.lhs install --user
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
Notes for using the darcs version
|
||||
|
||||
If you're building the darcs version of xmonad, be sure to also
|
||||
use the darcs version of X11-extras, which is developed concurrently
|
||||
with xmonad.
|
||||
|
||||
darcs get http://code.haskell.org/X11-extras
|
||||
|
||||
Not using X11-extras from darcs, is the most common reason for the
|
||||
darcs version of xmonad to fail to build.
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
Running xmonad:
|
||||
|
||||
Add:
|
||||
|
||||
$HOME/bin/xmonad
|
||||
|
||||
to the last line of your .xsession or .xinitrc file.
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
XMonadContrib
|
||||
|
||||
There are various contributed modules that can be used with xmonad.
|
||||
Examples include an ion3-like tabbed layout, a prompt/program launcher,
|
||||
and various other useful modules. XMonadContrib is available at:
|
||||
|
||||
0.4 release: http://www.xmonad.org/XMonadContrib-0.4.tar.gz
|
||||
|
||||
darcs version: darcs get http://code.haskell.org/XMonadContrib
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
Other useful programs:
|
||||
|
||||
For a program dispatch menu:
|
||||
|
||||
dmenu http://www.suckless.org/download/
|
||||
or
|
||||
gmrun (in your package system)
|
||||
|
||||
For custom status bars:
|
||||
|
||||
dzen http://gotmor.googlepages.com/dzen
|
||||
|
||||
A nicer xterm replacment, that supports resizing better:
|
||||
|
||||
urxvt http://software.schmorp.de/pkg/rxvt-unicode.html
|
||||
|
||||
Authors:
|
||||
|
||||
Spencer Janssen
|
||||
Don Stewart
|
||||
Jason Creighton
|
129
README.md
Normal file
129
README.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# xmonad: A Tiling Window Manager
|
||||
|
||||
[](https://travis-ci.org/xmonad/xmonad)
|
||||
|
||||
[xmonad][] is a tiling window manager for X. Windows are arranged
|
||||
automatically to tile the screen without gaps or overlap, maximising
|
||||
screen use. Window manager features are accessible from the keyboard:
|
||||
a mouse is optional. xmonad is written, configured and extensible in
|
||||
Haskell. Custom layout algorithms, key bindings and other extensions
|
||||
may be written by the user in config files. Layouts are applied
|
||||
dynamically, and different layouts may be used on each
|
||||
workspace. Xinerama is fully supported, allowing windows to be tiled
|
||||
on several physical screens.
|
||||
|
||||
## Quick Start
|
||||
|
||||
* From hackage:
|
||||
|
||||
cabal update
|
||||
cabal install xmonad xmonad-contrib
|
||||
|
||||
* Alternatively, build from source using the following repositories:
|
||||
|
||||
- <https://github.com/xmonad/xmonad>
|
||||
|
||||
- <https://github.com/xmonad/xmonad-contrib>
|
||||
|
||||
For the full story, read on.
|
||||
|
||||
## Building
|
||||
|
||||
Building is quite straightforward, and requires a basic Haskell toolchain.
|
||||
On many systems xmonad is available as a binary package in your
|
||||
package system (e.g. on Debian or Gentoo). If at all possible, use this
|
||||
in preference to a source build, as the dependency resolution will be
|
||||
simpler.
|
||||
|
||||
We'll now walk through the complete list of toolchain dependencies.
|
||||
|
||||
* GHC: the Glasgow Haskell Compiler
|
||||
|
||||
You first need a Haskell compiler. Your distribution's package
|
||||
system will have binaries of GHC (the Glasgow Haskell Compiler),
|
||||
the compiler we use, so install that first. If your operating
|
||||
system's package system doesn't provide a binary version of GHC
|
||||
and the `cabal-install` tool, you can install both using the
|
||||
[Haskell Platform][platform].
|
||||
|
||||
It shouldn't be necessary to compile GHC from source -- every common
|
||||
system has a pre-build binary version. However, if you want to
|
||||
build from source, the following links will be helpful:
|
||||
|
||||
- GHC: <http://haskell.org/ghc/>
|
||||
|
||||
- Cabal: <http://haskell.org/cabal/download.html>
|
||||
|
||||
* X11 libraries:
|
||||
|
||||
Since you're building an X application, you'll need the C X11
|
||||
library headers. On many platforms, these come pre-installed. For
|
||||
others, such as Debian, you can get them from your package manager:
|
||||
|
||||
# for xmonad
|
||||
$ apt-get install libx11-dev libxinerama-dev libxext-dev libxrandr-dev libxss-dev
|
||||
|
||||
# for xmonad-contrib
|
||||
$ apt-get install libxft-dev
|
||||
|
||||
Then build and install with:
|
||||
|
||||
$ cabal install
|
||||
|
||||
## Running xmonad
|
||||
|
||||
If you built XMonad using `cabal` then add:
|
||||
|
||||
exec $HOME/.cabal/bin/xmonad
|
||||
|
||||
to the last line of your `.xsession` or `.xinitrc` file.
|
||||
|
||||
## Configuring
|
||||
|
||||
See the [CONFIG][] document and the [example configuration file][example-config].
|
||||
|
||||
## XMonadContrib
|
||||
|
||||
There are many extensions to xmonad available in the XMonadContrib
|
||||
(xmc) library. Examples include an ion3-like tabbed layout, a
|
||||
prompt/program launcher, and various other useful modules.
|
||||
XMonadContrib is available at:
|
||||
|
||||
* Latest release: <http://hackage.haskell.org/package/xmonad-contrib>
|
||||
|
||||
* Git version: <https://github.com/xmonad/xmonad-contrib>
|
||||
|
||||
## Other Useful Programs
|
||||
|
||||
A nicer xterm replacement, that supports resizing better:
|
||||
|
||||
* urxvt: <http://software.schmorp.de/pkg/rxvt-unicode.html>
|
||||
|
||||
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)
|
||||
|
||||
## Authors
|
||||
|
||||
* Spencer Janssen
|
||||
* Don Stewart
|
||||
* Jason Creighton
|
||||
|
||||
[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
|
22
STYLE
Normal file
22
STYLE
Normal file
@@ -0,0 +1,22 @@
|
||||
|
||||
== 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).
|
15
TODO
15
TODO
@@ -1,15 +0,0 @@
|
||||
- Write down invariants for the window life cycle, especially:
|
||||
- When are borders set? Prove that the current handling is sufficient.
|
||||
|
||||
= Release management =
|
||||
|
||||
* build and typecheck all XMC
|
||||
* generate haddocks for core and XMC, upload to xmonad.org
|
||||
* generate manpage, generate html manpage
|
||||
* document, with photos, any new layouts
|
||||
* double check README build instructions
|
||||
* test core with 6.6 and 6.8
|
||||
* upload X11/X11-extras/xmonad to hacakge
|
||||
* check examples/text in use-facing Config.hs
|
||||
* check tour.html and intro.html are up to date, and mention all core bindings
|
||||
* bump xmonad.cabal version
|
276
XMonad.hs
276
XMonad.hs
@@ -1,276 +0,0 @@
|
||||
{-# LANGUAGE ExistentialQuantification, FlexibleInstances, GeneralizedNewtypeDeriving, MultiParamTypeClasses #-}
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- |
|
||||
-- Module : XMonad.hs
|
||||
-- Copyright : (c) Spencer Janssen 2007
|
||||
-- License : BSD3-style (see LICENSE)
|
||||
--
|
||||
-- Maintainer : sjanssen@cse.unl.edu
|
||||
-- Stability : unstable
|
||||
-- Portability : not portable, uses cunning newtype deriving
|
||||
--
|
||||
-- The X monad, a state monad transformer over IO, for the window
|
||||
-- manager state, and support routines.
|
||||
--
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
module XMonad (
|
||||
X, WindowSet, WindowSpace, WorkspaceId, ScreenId(..), ScreenDetail(..), XState(..), XConf(..), LayoutClass(..), Layout(..), ReadableLayout(..),
|
||||
Typeable, Message, SomeMessage(..), fromMessage, runLayout,
|
||||
runX, catchX, userCode, io, catchIO, withDisplay, withWindowSet, isRoot, getAtom, spawn, restart, trace, whenJust, whenX,
|
||||
atom_WM_STATE, atom_WM_PROTOCOLS, atom_WM_DELETE_WINDOW
|
||||
) where
|
||||
|
||||
import StackSet
|
||||
|
||||
import Prelude hiding ( catch )
|
||||
import Control.Exception (catch, throw, Exception(ExitException))
|
||||
import Control.Monad.State
|
||||
import Control.Monad.Reader
|
||||
import Control.Arrow (first)
|
||||
import System.IO
|
||||
import System.Posix.Process (executeFile, forkProcess, getProcessStatus, createSession)
|
||||
import System.Exit
|
||||
import System.Environment
|
||||
import Graphics.X11.Xlib
|
||||
-- for Read instance
|
||||
import Graphics.X11.Xlib.Extras ()
|
||||
import Data.Typeable
|
||||
|
||||
import qualified Data.Map as M
|
||||
import qualified Data.Set as S
|
||||
|
||||
-- | XState, the window manager state.
|
||||
-- Just the display, width, height and a window list
|
||||
data XState = XState
|
||||
{ windowset :: !WindowSet -- ^ workspace list
|
||||
, mapped :: !(S.Set Window) -- ^ the Set of mapped windows
|
||||
, waitingUnmap :: !(M.Map Window Int) -- ^ the number of expected UnmapEvents
|
||||
, dragging :: !(Maybe (Position -> Position -> X (), X ())) }
|
||||
data XConf = XConf
|
||||
{ display :: Display -- ^ the X11 display
|
||||
, theRoot :: !Window -- ^ the root window
|
||||
, normalBorder :: !Pixel -- ^ border color of unfocused windows
|
||||
, focusedBorder :: !Pixel } -- ^ border color of the focused window
|
||||
|
||||
type WindowSet = StackSet WorkspaceId (Layout Window) Window ScreenId ScreenDetail
|
||||
type WindowSpace = Workspace WorkspaceId (Layout Window) Window
|
||||
|
||||
-- | Virtual workspace indicies
|
||||
type WorkspaceId = String
|
||||
|
||||
-- | Physical screen indicies
|
||||
newtype ScreenId = S Int deriving (Eq,Ord,Show,Read,Enum,Num,Integral,Real)
|
||||
|
||||
-- | TODO Comment me
|
||||
data ScreenDetail = SD { screenRect :: !Rectangle
|
||||
, statusGap :: !(Int,Int,Int,Int) -- ^ width of status bar on the screen
|
||||
} deriving (Eq,Show, Read)
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
-- | The X monad, a StateT transformer over IO encapsulating the window
|
||||
-- manager state
|
||||
--
|
||||
-- Dynamic components may be retrieved with 'get', static components
|
||||
-- with 'ask'. With newtype deriving we get readers and state monads
|
||||
-- instantiated on XConf and XState automatically.
|
||||
--
|
||||
newtype X a = X (ReaderT XConf (StateT XState IO) a)
|
||||
deriving (Functor, Monad, MonadIO, MonadState XState, MonadReader XConf)
|
||||
|
||||
-- | Run the X monad, given a chunk of X monad code, and an initial state
|
||||
-- Return the result, and final state
|
||||
runX :: XConf -> XState -> X a -> IO (a, XState)
|
||||
runX c st (X a) = runStateT (runReaderT a c) st
|
||||
|
||||
-- | Run in the X monad, and in case of exception, and catch it and log it
|
||||
-- to stderr, and run the error case.
|
||||
catchX :: X a -> X a -> X a
|
||||
catchX job errcase = do
|
||||
st <- get
|
||||
c <- ask
|
||||
(a, s') <- io $ runX c st job `catch`
|
||||
\e -> case e of
|
||||
ExitException {} -> throw e
|
||||
_ -> do hPrint stderr e; runX c st errcase
|
||||
put s'
|
||||
return a
|
||||
|
||||
-- | Execute the argument, catching all exceptions. Either this function or
|
||||
-- catchX should be used at all callsites of user customized code.
|
||||
userCode :: X () -> X ()
|
||||
userCode a = catchX (a >> return ()) (return ())
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Convenient wrappers to state
|
||||
|
||||
-- | Run a monad action with the current display settings
|
||||
withDisplay :: (Display -> X a) -> X a
|
||||
withDisplay f = asks display >>= f
|
||||
|
||||
-- | Run a monadic action with the current stack set
|
||||
withWindowSet :: (WindowSet -> X a) -> X a
|
||||
withWindowSet f = gets windowset >>= f
|
||||
|
||||
-- | True if the given window is the root window
|
||||
isRoot :: Window -> X Bool
|
||||
isRoot w = liftM (w==) (asks theRoot)
|
||||
|
||||
-- | Wrapper for the common case of atom internment
|
||||
getAtom :: String -> X Atom
|
||||
getAtom str = withDisplay $ \dpy -> io $ internAtom dpy str False
|
||||
|
||||
-- | Common non-predefined atoms
|
||||
atom_WM_PROTOCOLS, atom_WM_DELETE_WINDOW, atom_WM_STATE :: X Atom
|
||||
atom_WM_PROTOCOLS = getAtom "WM_PROTOCOLS"
|
||||
atom_WM_DELETE_WINDOW = getAtom "WM_DELETE_WINDOW"
|
||||
atom_WM_STATE = getAtom "WM_STATE"
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- | LayoutClass handling. See particular instances in Operations.hs
|
||||
|
||||
-- | An existential type that can hold any object that is in the LayoutClass.
|
||||
data Layout a = forall l. LayoutClass l a => Layout (l a)
|
||||
|
||||
|
||||
-- | This class defines a set of layout types (held in Layout
|
||||
-- objects) that are used when trying to read an existentially wrapped Layout.
|
||||
class ReadableLayout a where
|
||||
readTypes :: [Layout a]
|
||||
|
||||
-- | The different layout modes
|
||||
--
|
||||
-- 'doLayout': given a Rectangle and a Stack, layout the stack elements
|
||||
-- inside the given Rectangle. If an element is not given a Rectangle
|
||||
-- by 'doLayout', then it is not shown on screen. Windows are restacked
|
||||
-- according to the order they are returned by 'doLayout'.
|
||||
--
|
||||
class (Show (layout a), Read (layout a)) => LayoutClass layout a where
|
||||
|
||||
-- | Given a Rectangle in which to place the windows, and a Stack of
|
||||
-- windows, return a list of windows and their corresponding Rectangles.
|
||||
-- The order of windows in this list should be the desired stacking order.
|
||||
-- Also return a modified layout, if this layout needs to be modified
|
||||
-- (e.g. if we keep track of the windows we have displayed).
|
||||
doLayout :: layout a -> Rectangle -> Stack a -> X ([(a, Rectangle)], Maybe (layout a))
|
||||
doLayout l r s = return (pureLayout l r s, Nothing)
|
||||
|
||||
-- | This is a pure version of doLayout, for cases where we don't need
|
||||
-- access to the X monad to determine how to layou out the windows, and
|
||||
-- we don't need to modify our layout itself.
|
||||
pureLayout :: layout a -> Rectangle -> Stack a -> [(a, Rectangle)]
|
||||
pureLayout _ r s = [(focus s, r)]
|
||||
|
||||
-- | 'handleMessage' performs message handling for that layout. If
|
||||
-- 'handleMessage' returns Nothing, then the layout did not respond to
|
||||
-- that message and the screen is not refreshed. Otherwise, 'handleMessage'
|
||||
-- returns an updated 'LayoutClass' and the screen is refreshed.
|
||||
--
|
||||
handleMessage :: layout a -> SomeMessage -> X (Maybe (layout a))
|
||||
handleMessage l = return . pureMessage l
|
||||
|
||||
-- | Respond to a message by (possibly) changing our layout, but taking
|
||||
-- no other action. If the layout changes, the screen will be refreshed.
|
||||
pureMessage :: layout a -> SomeMessage -> Maybe (layout a)
|
||||
pureMessage _ _ = Nothing
|
||||
|
||||
-- | This should be a human-readable string that is used when selecting
|
||||
-- layouts by name.
|
||||
description :: layout a -> String
|
||||
description = show
|
||||
|
||||
-- Here's the magic for parsing serialised state of existentially
|
||||
-- wrapped layouts: attempt to parse using the Read instance from each
|
||||
-- type in our list of types, if any suceed, take the first one.
|
||||
instance ReadableLayout a => Read (Layout a) where
|
||||
|
||||
-- We take the first parse only, because multiple matches indicate a bad parse.
|
||||
readsPrec _ s = take 1 $ concatMap readLayout readTypes
|
||||
where
|
||||
readLayout (Layout x) = map (first Layout) $ readAsType x
|
||||
|
||||
-- the type indicates which Read instance to dispatch to.
|
||||
-- That is, read asTypeOf the argument from the readTypes.
|
||||
readAsType :: LayoutClass l a => l a -> [(l a, String)]
|
||||
readAsType _ = reads s
|
||||
|
||||
instance ReadableLayout a => LayoutClass Layout a where
|
||||
doLayout (Layout l) r s = fmap (fmap Layout) `liftM` doLayout l r s
|
||||
handleMessage (Layout l) = fmap (fmap Layout) . handleMessage l
|
||||
description (Layout l) = description l
|
||||
|
||||
instance Show (Layout a) where show (Layout l) = show l
|
||||
|
||||
-- | This calls doLayout if there are any windows to be laid out.
|
||||
runLayout :: LayoutClass l a => l a -> Rectangle -> StackOrNot a -> X ([(a, Rectangle)], Maybe (l a))
|
||||
runLayout l r = maybe (return ([], Nothing)) (doLayout l r)
|
||||
|
||||
-- | Based on ideas in /An Extensible Dynamically-Typed Hierarchy of Exceptions/,
|
||||
-- Simon Marlow, 2006. Use extensible messages to the handleMessage handler.
|
||||
--
|
||||
-- User-extensible messages must be a member of this class.
|
||||
--
|
||||
class Typeable a => Message a
|
||||
|
||||
-- |
|
||||
-- A wrapped value of some type in the Message class.
|
||||
--
|
||||
data SomeMessage = forall a. Message a => SomeMessage a
|
||||
|
||||
-- |
|
||||
-- And now, unwrap a given, unknown Message type, performing a (dynamic)
|
||||
-- type check on the result.
|
||||
--
|
||||
fromMessage :: Message m => SomeMessage -> Maybe m
|
||||
fromMessage (SomeMessage m) = cast m
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- | General utilities
|
||||
--
|
||||
-- Lift an IO action into the X monad
|
||||
io :: IO a -> X a
|
||||
io = liftIO
|
||||
|
||||
-- | Lift an IO action into the X monad. If the action results in an IO
|
||||
-- exception, log the exception to stderr and continue normal execution.
|
||||
catchIO :: IO () -> X ()
|
||||
catchIO f = liftIO (f `catch` \e -> hPrint stderr e >> hFlush stderr)
|
||||
|
||||
-- | spawn. Launch an external application
|
||||
spawn :: String -> X ()
|
||||
spawn x = io $ do
|
||||
pid <- forkProcess $ do
|
||||
forkProcess (createSession >> executeFile "/bin/sh" False ["-c", x] Nothing)
|
||||
exitWith ExitSuccess
|
||||
getProcessStatus True False pid
|
||||
return ()
|
||||
|
||||
-- | Restart xmonad via exec().
|
||||
--
|
||||
-- If the first parameter is 'Just name', restart will attempt to execute the
|
||||
-- program corresponding to 'name'. Otherwise, xmonad will attempt to execute
|
||||
-- the name of the current program.
|
||||
--
|
||||
-- When the second parameter is 'True', xmonad will attempt to resume with the
|
||||
-- current window state.
|
||||
restart :: Maybe String -> Bool -> X ()
|
||||
restart mprog resume = do
|
||||
prog <- maybe (io getProgName) return mprog
|
||||
args <- if resume then gets (("--resume":) . return . showWs . windowset) else return []
|
||||
catchIO (executeFile prog True args Nothing)
|
||||
where showWs = show . mapLayout show
|
||||
|
||||
-- | Run a side effecting action with the current workspace. Like 'when' but
|
||||
whenJust :: Maybe a -> (a -> X ()) -> X ()
|
||||
whenJust mg f = maybe (return ()) f mg
|
||||
|
||||
-- | Conditionally run an action, using a X event to decide
|
||||
whenX :: X Bool -> X () -> X ()
|
||||
whenX a f = a >>= \b -> when b f
|
||||
|
||||
-- | A 'trace' for the X monad. Logs a string to stderr. The result may
|
||||
-- be found in your .xsession-errors file
|
||||
trace :: String -> X ()
|
||||
trace msg = io $! do hPutStrLn stderr msg; hFlush stderr
|
1
cabal.project
Normal file
1
cabal.project
Normal file
@@ -0,0 +1 @@
|
||||
packages: ./
|
71
man/HCAR.tex
Normal file
71
man/HCAR.tex
Normal file
@@ -0,0 +1,71 @@
|
||||
% xmonad-Gx.tex
|
||||
\begin{hcarentry}{xmonad}
|
||||
\label{xmonad}
|
||||
\report{Gwern Branwen}%11/11
|
||||
\status{active development}
|
||||
\makeheader
|
||||
|
||||
XMonad is a tiling window manager for X. Windows are arranged
|
||||
automatically to tile the screen without gaps or overlap, maximizing
|
||||
screen use. Window manager features are accessible from the keyboard; a
|
||||
mouse is optional. XMonad is written, configured, and extensible in
|
||||
Haskell. Custom layout algorithms, key bindings, and other extensions may
|
||||
be written by the user in config files. Layouts are applied
|
||||
dynamically, and different layouts may be used on each workspace.
|
||||
Xinerama is fully supported, allowing windows to be tiled on several
|
||||
physical screens.
|
||||
|
||||
Development since the last report has continued; XMonad founder Don Stewart
|
||||
has stepped down and Adam Vogt is the new maintainer.
|
||||
After gestating for 2 years, version 0.10 has been released, with simultaneous
|
||||
releases of the XMonadContrib library of customizations (which has now grown to
|
||||
no less than 216 modules encompassing a dizzying array of features) and the
|
||||
xmonad-extras package of extensions,
|
||||
|
||||
Details of changes between releases can be found in the release notes:
|
||||
\begin{compactitem}
|
||||
\item \url{http://haskell.org/haskellwiki/Xmonad/Notable_changes_since_0.8}
|
||||
\item \url{http://haskell.org/haskellwiki/Xmonad/Notable_changes_since_0.9}
|
||||
% \item \url{http://haskell.org/haskellwiki/Xmonad/Notable_changes_since_0.10}
|
||||
\item the Darcs repositories have been upgraded to the hashed format
|
||||
\item XMonad.Config.PlainConfig allows writing configs in a more 'normal' style, and not raw Haskell
|
||||
\item Supports using local modules in xmonad.hs; for example: to use definitions from \~/.xmonad/lib/XMonad/Stack/MyAdditions.hs
|
||||
\item xmonad --restart CLI option
|
||||
\item xmonad --replace CLI option
|
||||
\item XMonad.Prompt now has customizable keymaps
|
||||
\item Actions.GridSelect - a GUI menu for selecting windows or workspaces \& substring search on window names
|
||||
\item Actions.OnScreen
|
||||
\item Extensions now can have state
|
||||
\item Actions.SpawnOn - uses state to spawn applications on the workspace the user was originally on,
|
||||
and not where the user happens to be
|
||||
\item Markdown manpages and not man/troff
|
||||
\item XMonad.Layout.ImageButtonDecoration \&\\ XMonad.Util.Image
|
||||
\item XMonad.Layout.Groups
|
||||
\item XMonad.Layout.ZoomRow
|
||||
\item XMonad.Layout.Renamed
|
||||
\item XMonad.Layout.Drawer
|
||||
\item XMonad.Layout.FullScreen
|
||||
\item XMonad.Hooks.ScreenCorners
|
||||
\item XMonad.Actions.DynamicWorkspaceOrder
|
||||
\item XMonad.Actions.WorkspaceNames
|
||||
\item XMonad.Actions.DynamicWorkspaceGroups
|
||||
\end{compactitem}
|
||||
|
||||
Binary packages of XMonad and XMonadContrib are available for all major Linux distributions.
|
||||
|
||||
\FurtherReading
|
||||
\begin{compactitem}
|
||||
\item Homepage:
|
||||
\url{http://xmonad.org/}
|
||||
|
||||
\item Git source:
|
||||
|
||||
\texttt{git clone} \url{https://github.com/xmonad/xmonad.git}
|
||||
|
||||
\item IRC channel:
|
||||
\verb+#xmonad @@ irc.freenode.org+
|
||||
|
||||
\item Mailing list:
|
||||
\email{xmonad@@haskell.org}
|
||||
\end{compactitem}
|
||||
\end{hcarentry}
|
289
man/xmonad.1
Normal file
289
man/xmonad.1
Normal file
@@ -0,0 +1,289 @@
|
||||
.\" Automatically generated by Pandoc 2.2.1
|
||||
.\"
|
||||
.TH "XMONAD" "1" "20 August 2018" "Tiling Window Manager" ""
|
||||
.hy
|
||||
.SH Name
|
||||
.PP
|
||||
xmonad \- Tiling Window Manager
|
||||
.SH Description
|
||||
.PP
|
||||
\f[I]xmonad\f[] is a minimalist tiling window manager for X, written in
|
||||
Haskell.
|
||||
Windows are managed using automatic layout algorithms, which can be
|
||||
dynamically reconfigured.
|
||||
At any time windows are arranged so as to maximize the use of screen
|
||||
real estate.
|
||||
All features of the window manager are accessible purely from the
|
||||
keyboard: a mouse is entirely optional.
|
||||
\f[I]xmonad\f[] 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
|
||||
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.
|
||||
In tall or wide mode, windows are tiled and arranged to prevent overlap
|
||||
and maximize screen use.
|
||||
Sets of windows are grouped together on virtual screens, and each screen
|
||||
retains its own layout, which may be reconfigured dynamically.
|
||||
Multiple physical monitors are supported via Xinerama, allowing
|
||||
simultaneous display of a number of screens.
|
||||
.PP
|
||||
By utilizing the expressivity of a modern functional language with a
|
||||
rich static type system, \f[I]xmonad\f[] provides a complete, featureful
|
||||
window manager in less than 1200 lines of code, with an emphasis on
|
||||
correctness and robustness.
|
||||
Internal properties of the window manager are checked using a
|
||||
combination of static guarantees provided by the type system, and
|
||||
type\-based automated testing.
|
||||
A benefit of this is that the code is simple to understand, and easy to
|
||||
modify.
|
||||
.SH Usage
|
||||
.PP
|
||||
\f[I]xmonad\f[] places each window into a \[lq]workspace\[rq].
|
||||
Each workspace can have any number of windows, which you can cycle
|
||||
though with mod\-j and mod\-k.
|
||||
Windows are either displayed full screen, tiled horizontally, or tiled
|
||||
vertically.
|
||||
You can toggle the layout mode with mod\-space, which will cycle through
|
||||
the available modes.
|
||||
.PP
|
||||
You can switch to workspace N with mod\-N.
|
||||
For example, to switch to workspace 5, you would press mod\-5.
|
||||
Similarly, you can move the current window to another workspace with
|
||||
mod\-shift\-N.
|
||||
.PP
|
||||
When running with multiple monitors (Xinerama), each screen has exactly
|
||||
1 workspace visible.
|
||||
mod\-{w,e,r} switch the focus between screens, while shift\-mod\-{w,e,r}
|
||||
move the current window to that screen.
|
||||
When \f[I]xmonad\f[] starts, workspace 1 is on screen 1, workspace 2 is
|
||||
on screen 2, etc.
|
||||
When switching workspaces to one that is already visible, the current
|
||||
and visible workspaces are swapped.
|
||||
.SS Flags
|
||||
.PP
|
||||
xmonad has several flags which you may pass to the executable.
|
||||
These flags are:
|
||||
.TP
|
||||
.B \[en]recompile
|
||||
Recompiles your configuration in \f[I]~/.xmonad/xmonad.hs\f[]
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \[en]restart
|
||||
Causes the currently running \f[I]xmonad\f[] process to restart
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \[en]replace
|
||||
Replace the current window manager with xmonad
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \[en]version
|
||||
Display version of \f[I]xmonad\f[]
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \[en]verbose\-version
|
||||
Display detailed version of \f[I]xmonad\f[]
|
||||
.RS
|
||||
.RE
|
||||
.PP
|
||||
##Default keyboard bindings
|
||||
.TP
|
||||
.B mod\-shift\-return
|
||||
Launch terminal
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-p
|
||||
Launch dmenu
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-shift\-p
|
||||
Launch gmrun
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-shift\-c
|
||||
Close the focused window
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-space
|
||||
Rotate through the available layout algorithms
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-shift\-space
|
||||
Reset the layouts on the current workspace to default
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-n
|
||||
Resize viewed windows to the correct size
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-tab
|
||||
Move focus to the next window
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-shift\-tab
|
||||
Move focus to the previous window
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-j
|
||||
Move focus to the next window
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-k
|
||||
Move focus to the previous window
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-m
|
||||
Move focus to the master window
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-return
|
||||
Swap the focused window and the master window
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-shift\-j
|
||||
Swap the focused window with the next window
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-shift\-k
|
||||
Swap the focused window with the previous window
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-h
|
||||
Shrink the master area
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-l
|
||||
Expand the master area
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-t
|
||||
Push window back into tiling
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-comma
|
||||
Increment the number of windows in the master area
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-period
|
||||
Deincrement the number of windows in the master area
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-shift\-q
|
||||
Quit xmonad
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-q
|
||||
Restart xmonad
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-shift\-slash
|
||||
Run xmessage with a summary of the default keybindings (useful for
|
||||
beginners)
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-question
|
||||
Run xmessage with a summary of the default keybindings (useful for
|
||||
beginners)
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-[1..9]
|
||||
Switch to workspace N
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-shift\-[1..9]
|
||||
Move client to workspace N
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-{w,e,r}
|
||||
Switch to physical/Xinerama screens 1, 2, or 3
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-shift\-{w,e,r}
|
||||
Move client to screen 1, 2, or 3
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-button1
|
||||
Set the window to floating mode and move by dragging
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B mod\-button2
|
||||
Raise the window to the top of the stack
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B 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:
|
||||
.RS
|
||||
.PP
|
||||
exec xmonad
|
||||
.RE
|
||||
.SH Customization
|
||||
.PP
|
||||
xmonad is customized in ~/.xmonad/xmonad.hs, and then restarted with
|
||||
mod\-q.
|
||||
.PP
|
||||
You can find many extensions to the core feature set in the xmonad\-
|
||||
contrib package, available through your package manager or from
|
||||
xmonad.org (http://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.
|
||||
Hierarchical modules are supported: for example, the file
|
||||
\f[I]~/.xmonad/lib/XMonad/Stack/MyAdditions.hs\f[] could contain:
|
||||
.IP
|
||||
.nf
|
||||
\f[C]
|
||||
module\ XMonad.Stack.MyAdditions\ (function1)\ where
|
||||
\ \ function1\ =\ error\ "function1:\ Not\ implemented\ yet!"
|
||||
\f[]
|
||||
.fi
|
||||
.PP
|
||||
Your xmonad.hs may then import XMonad.Stack.MyAdditions as if that
|
||||
module was contained within xmonad or xmonad\-contrib.
|
||||
.SH Bugs
|
||||
.PP
|
||||
Probably.
|
||||
If you find any, please report them to the
|
||||
bugtracker (https://github.com/xmonad/xmonad/issues)
|
244
man/xmonad.1.html
Normal file
244
man/xmonad.1.html
Normal file
@@ -0,0 +1,244 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<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" />
|
||||
<title>XMONAD(1) Tiling Window Manager</title>
|
||||
<style type="text/css">
|
||||
code{white-space: pre-wrap;}
|
||||
span.smallcaps{font-variant: small-caps;}
|
||||
span.underline{text-decoration: underline;}
|
||||
div.column{display: inline-block; vertical-align: top; width: 50%;}
|
||||
</style>
|
||||
<style type="text/css">
|
||||
a.sourceLine { display: inline-block; line-height: 1.25; }
|
||||
a.sourceLine { pointer-events: none; color: inherit; text-decoration: inherit; }
|
||||
a.sourceLine:empty { height: 1.2em; }
|
||||
.sourceCode { overflow: visible; }
|
||||
code.sourceCode { white-space: pre; position: relative; }
|
||||
div.sourceCode { margin: 1em 0; }
|
||||
pre.sourceCode { margin: 0; }
|
||||
@media screen {
|
||||
div.sourceCode { overflow: auto; }
|
||||
}
|
||||
@media print {
|
||||
code.sourceCode { white-space: pre-wrap; }
|
||||
a.sourceLine { text-indent: -1em; padding-left: 1em; }
|
||||
}
|
||||
pre.numberSource a.sourceLine
|
||||
{ position: relative; left: -4em; }
|
||||
pre.numberSource a.sourceLine::before
|
||||
{ content: attr(data-line-number);
|
||||
position: relative; left: -1em; text-align: right; vertical-align: baseline;
|
||||
border: none; pointer-events: all; display: inline-block;
|
||||
-webkit-touch-callout: none; -webkit-user-select: none;
|
||||
-khtml-user-select: none; -moz-user-select: none;
|
||||
-ms-user-select: none; user-select: none;
|
||||
padding: 0 4px; width: 4em;
|
||||
color: #aaaaaa;
|
||||
}
|
||||
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
|
||||
div.sourceCode
|
||||
{ }
|
||||
@media screen {
|
||||
a.sourceLine::before { text-decoration: underline; }
|
||||
}
|
||||
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
|
||||
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
|
||||
code span.at { color: #7d9029; } /* Attribute */
|
||||
code span.bn { color: #40a070; } /* BaseN */
|
||||
code span.bu { } /* BuiltIn */
|
||||
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
|
||||
code span.ch { color: #4070a0; } /* Char */
|
||||
code span.cn { color: #880000; } /* Constant */
|
||||
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
|
||||
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
|
||||
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
|
||||
code span.dt { color: #902000; } /* DataType */
|
||||
code span.dv { color: #40a070; } /* DecVal */
|
||||
code span.er { color: #ff0000; font-weight: bold; } /* Error */
|
||||
code span.ex { } /* Extension */
|
||||
code span.fl { color: #40a070; } /* Float */
|
||||
code span.fu { color: #06287e; } /* Function */
|
||||
code span.im { } /* Import */
|
||||
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
|
||||
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
|
||||
code span.op { color: #666666; } /* Operator */
|
||||
code span.ot { color: #007020; } /* Other */
|
||||
code span.pp { color: #bc7a00; } /* Preprocessor */
|
||||
code span.sc { color: #4070a0; } /* SpecialChar */
|
||||
code span.ss { color: #bb6688; } /* SpecialString */
|
||||
code span.st { color: #4070a0; } /* String */
|
||||
code span.va { color: #19177c; } /* Variable */
|
||||
code span.vs { color: #4070a0; } /* VerbatimString */
|
||||
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
|
||||
</style>
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1 class="title">XMONAD(1) Tiling Window Manager</h1>
|
||||
<p class="author"></p>
|
||||
<p class="date">20 August 2018</p>
|
||||
</header>
|
||||
<nav id="TOC">
|
||||
<ul>
|
||||
<li><a href="#name">Name</a></li>
|
||||
<li><a href="#description">Description</a></li>
|
||||
<li><a href="#usage">Usage</a><ul>
|
||||
<li><a href="#flags">Flags</a></li>
|
||||
</ul></li>
|
||||
<li><a href="#examples">Examples</a></li>
|
||||
<li><a href="#customization">Customization</a><ul>
|
||||
<li><a href="#modular-configuration">Modular Configuration</a></li>
|
||||
</ul></li>
|
||||
<li><a href="#bugs">Bugs</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<h1 id="name">Name</h1>
|
||||
<p>xmonad - Tiling Window Manager</p>
|
||||
<h1 id="description">Description</h1>
|
||||
<p><em>xmonad</em> is a minimalist tiling window manager for X, written in Haskell. Windows are managed using automatic layout algorithms, which can be dynamically reconfigured. At any time windows are arranged so as to maximize the use of screen real estate. All features of the window manager are accessible purely from the keyboard: a mouse is entirely optional. <em>xmonad</em> is configured in Haskell, and custom layout algorithms may be implemented by the user in config files. A principle of <em>xmonad</em> is predictability: the user should know in advance precisely the window arrangement that will result from any action.</p>
|
||||
<p>By default, <em>xmonad</em> provides three layout algorithms: tall, wide and fullscreen. In tall or wide mode, windows are tiled and arranged to prevent overlap and maximize screen use. Sets of windows are grouped together on virtual screens, and each screen retains its own layout, which may be reconfigured dynamically. Multiple physical monitors are supported via Xinerama, allowing simultaneous display of a number of screens.</p>
|
||||
<p>By utilizing the expressivity of a modern functional language with a rich static type system, <em>xmonad</em> provides a complete, featureful window manager in less than 1200 lines of code, with an emphasis on correctness and robustness. Internal properties of the window manager are checked using a combination of static guarantees provided by the type system, and type-based automated testing. A benefit of this is that the code is simple to understand, and easy to modify.</p>
|
||||
<h1 id="usage">Usage</h1>
|
||||
<p><em>xmonad</em> places each window into a “workspace”. Each workspace can have any number of windows, which you can cycle though with mod-j and mod-k. Windows are either displayed full screen, tiled horizontally, or tiled vertically. You can toggle the layout mode with mod-space, which will cycle through the available modes.</p>
|
||||
<p>You can switch to workspace N with mod-N. For example, to switch to workspace 5, you would press mod-5. Similarly, you can move the current window to another workspace with mod-shift-N.</p>
|
||||
<p>When running with multiple monitors (Xinerama), each screen has exactly 1 workspace visible. mod-{w,e,r} switch the focus between screens, while shift-mod-{w,e,r} move the current window to that screen. When <em>xmonad</em> starts, workspace 1 is on screen 1, workspace 2 is on screen 2, etc. When switching workspaces to one that is already visible, the current and visible workspaces are swapped.</p>
|
||||
<h2 id="flags">Flags</h2>
|
||||
<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>
|
||||
<dt>–restart</dt>
|
||||
<dd>Causes the currently running <em>xmonad</em> process to restart
|
||||
</dd>
|
||||
<dt>–replace</dt>
|
||||
<dd>Replace the current window manager with xmonad
|
||||
</dd>
|
||||
<dt>–version</dt>
|
||||
<dd>Display version of <em>xmonad</em>
|
||||
</dd>
|
||||
<dt>–verbose-version</dt>
|
||||
<dd>Display detailed version of <em>xmonad</em>
|
||||
</dd>
|
||||
</dl>
|
||||
<p>##Default keyboard bindings</p>
|
||||
<dl>
|
||||
<dt>mod-shift-return</dt>
|
||||
<dd>Launch terminal
|
||||
</dd>
|
||||
<dt>mod-p</dt>
|
||||
<dd>Launch dmenu
|
||||
</dd>
|
||||
<dt>mod-shift-p</dt>
|
||||
<dd>Launch gmrun
|
||||
</dd>
|
||||
<dt>mod-shift-c</dt>
|
||||
<dd>Close the focused window
|
||||
</dd>
|
||||
<dt>mod-space</dt>
|
||||
<dd>Rotate through the available layout algorithms
|
||||
</dd>
|
||||
<dt>mod-shift-space</dt>
|
||||
<dd>Reset the layouts on the current workspace to default
|
||||
</dd>
|
||||
<dt>mod-n</dt>
|
||||
<dd>Resize viewed windows to the correct size
|
||||
</dd>
|
||||
<dt>mod-tab</dt>
|
||||
<dd>Move focus to the next window
|
||||
</dd>
|
||||
<dt>mod-shift-tab</dt>
|
||||
<dd>Move focus to the previous window
|
||||
</dd>
|
||||
<dt>mod-j</dt>
|
||||
<dd>Move focus to the next window
|
||||
</dd>
|
||||
<dt>mod-k</dt>
|
||||
<dd>Move focus to the previous window
|
||||
</dd>
|
||||
<dt>mod-m</dt>
|
||||
<dd>Move focus to the master window
|
||||
</dd>
|
||||
<dt>mod-return</dt>
|
||||
<dd>Swap the focused window and the master window
|
||||
</dd>
|
||||
<dt>mod-shift-j</dt>
|
||||
<dd>Swap the focused window with the next window
|
||||
</dd>
|
||||
<dt>mod-shift-k</dt>
|
||||
<dd>Swap the focused window with the previous window
|
||||
</dd>
|
||||
<dt>mod-h</dt>
|
||||
<dd>Shrink the master area
|
||||
</dd>
|
||||
<dt>mod-l</dt>
|
||||
<dd>Expand the master area
|
||||
</dd>
|
||||
<dt>mod-t</dt>
|
||||
<dd>Push window back into tiling
|
||||
</dd>
|
||||
<dt>mod-comma</dt>
|
||||
<dd>Increment the number of windows in the master area
|
||||
</dd>
|
||||
<dt>mod-period</dt>
|
||||
<dd>Deincrement the number of windows in the master area
|
||||
</dd>
|
||||
<dt>mod-shift-q</dt>
|
||||
<dd>Quit xmonad
|
||||
</dd>
|
||||
<dt>mod-q</dt>
|
||||
<dd>Restart xmonad
|
||||
</dd>
|
||||
<dt>mod-shift-slash</dt>
|
||||
<dd>Run xmessage with a summary of the default keybindings (useful for beginners)
|
||||
</dd>
|
||||
<dt>mod-question</dt>
|
||||
<dd>Run xmessage with a summary of the default keybindings (useful for beginners)
|
||||
</dd>
|
||||
<dt>mod-[1..9]</dt>
|
||||
<dd>Switch to workspace N
|
||||
</dd>
|
||||
<dt>mod-shift-[1..9]</dt>
|
||||
<dd>Move client to workspace N
|
||||
</dd>
|
||||
<dt>mod-{w,e,r}</dt>
|
||||
<dd>Switch to physical/Xinerama screens 1, 2, or 3
|
||||
</dd>
|
||||
<dt>mod-shift-{w,e,r}</dt>
|
||||
<dd>Move client to screen 1, 2, or 3
|
||||
</dd>
|
||||
<dt>mod-button1</dt>
|
||||
<dd>Set the window to floating mode and move by dragging
|
||||
</dd>
|
||||
<dt>mod-button2</dt>
|
||||
<dd>Raise the window to the top of the stack
|
||||
</dd>
|
||||
<dt>mod-button3</dt>
|
||||
<dd>Set the window to floating mode and resize by dragging
|
||||
</dd>
|
||||
</dl>
|
||||
<h1 id="examples">Examples</h1>
|
||||
<p>To use xmonad as your window manager add to your <em>~/.xinitrc</em> file:</p>
|
||||
<blockquote>
|
||||
<p>exec xmonad</p>
|
||||
</blockquote>
|
||||
<h1 id="customization">Customization</h1>
|
||||
<p>xmonad is customized in ~/.xmonad/xmonad.hs, and then restarted with mod-q.</p>
|
||||
<p>You can find many extensions to the core feature set in the xmonad- contrib package, available through your package manager or from <a href="http://xmonad.org">xmonad.org</a>.</p>
|
||||
<h2 id="modular-configuration">Modular Configuration</h2>
|
||||
<p>As of <em>xmonad-0.9</em>, any additional Haskell modules may be placed in <em>~/.xmonad/lib/</em> are available in GHC’s searchpath. Hierarchical modules are supported: for example, the file <em>~/.xmonad/lib/XMonad/Stack/MyAdditions.hs</em> could contain:</p>
|
||||
<div class="sourceCode" id="cb1"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb1-1" data-line-number="1"><span class="kw">module</span> <span class="dt">XMonad.Stack.MyAdditions</span> (function1) <span class="kw">where</span></a>
|
||||
<a class="sourceLine" id="cb1-2" data-line-number="2"> function1 <span class="fu">=</span> error <span class="st">"function1: Not implemented yet!"</span></a></code></pre></div>
|
||||
<p>Your xmonad.hs may then import XMonad.Stack.MyAdditions as if that module was contained within xmonad or xmonad-contrib.</p>
|
||||
<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>
|
||||
</body>
|
||||
</html>
|
@@ -1,49 +0,0 @@
|
||||
./" man page created by David Lazar on April 24, 2007
|
||||
./" uses ``tmac.an'' macro set
|
||||
.TH xmonad 1 "18 April 07" xmonad\-1.0 "xmonad manual"
|
||||
.SH NAME
|
||||
xmonad \- a tiling window manager
|
||||
.SH DESCRIPTION
|
||||
.PP
|
||||
\fBxmonad\fR 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 maximise the use of screen real estate. All features of the window manager are accessible purely from the keyboard: a mouse is entirely optional. \fBxmonad\fR is configured in Haskell, and custom layout algorithms may be implemented by the user in config files. A principle of \fBxmonad\fR is predictability: the user should know in advance precisely the window arrangement that will result from any action.
|
||||
.PP
|
||||
By default, \fBxmonad\fR provides three layout algorithms: tall, wide and fullscreen. In tall or wide mode, windows are tiled and arranged to prevent overlap and maximise screen use. Sets of windows are grouped together on virtual screens, and each screen retains its own layout, which may be reconfigured dynamically. Multiple physical monitors are supported via Xinerama, allowing simultaneous display of a number of screens.
|
||||
.PP
|
||||
By utilising the expressivity of a modern functional language with a rich static type system, \fBxmonad\fR provides a complete, featureful window manager in less than 500 lines of code, with an emphasis on correctness and robustness. Internal properties of the window manager are checked using a combination of static guarantees provided by the type system, and type-based automated testing. A benefit of this is that the code is simple to understand, and easy to modify.
|
||||
.SH USAGE
|
||||
.PP
|
||||
\fBxmonad\fR 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.
|
||||
.PP
|
||||
You can switch to workspace N with mod-N. For example, to switch to workspace 5, you would press mod-5. Similarly, you can move the current window to another workspace with mod-shift-N.
|
||||
.PP
|
||||
When running with multiple monitors (Xinerama), each screen has exactly 1 workspace visible. When \fBxmonad\fR starts, workspace 1 is on screen 1, workspace 2 is on screen 2, etc. If you switch to a workspace which is currently visible on another screen, \fBxmonad\fR simply switches focus to that screen. If you switch to a workspace which is *not* visible, \fBxmonad\fR replaces the workspace on the *current* screen with the workspace you selected.
|
||||
.PP
|
||||
For example, if you have the following configuration:
|
||||
.RS
|
||||
.PP
|
||||
Screen 1: Workspace 2
|
||||
.PP
|
||||
Screen 2: Workspace 5 (current workspace)
|
||||
.RE
|
||||
.PP
|
||||
and you wanted to view workspace 7 on screen 1, you would press:
|
||||
.RS
|
||||
.PP
|
||||
mod-2 (to select workspace 2, and make screen 1 the current screen)
|
||||
.PP
|
||||
mod-7 (to select workspace 7)
|
||||
.RE
|
||||
.PP
|
||||
Since switching to the workspace currently visible on a given screen is such a common operation, shortcuts are provided: mod-{w,e,r} switch to the workspace currently visible on screens 1, 2, and 3 respectively. Likewise, shift-mod-{w,e,r} moves the current window to the workspace on that screen. Using these keys, the above example would become mod-w mod-7.
|
||||
.SS Default keyboard bindings
|
||||
___KEYBINDINGS___
|
||||
.SH EXAMPLES
|
||||
To use \fBxmonad\fR as your window manager add:
|
||||
.RS
|
||||
xmonad
|
||||
.RE
|
||||
to your \fI~/.xinitrc\fR file
|
||||
.SH CUSTOMIZATION
|
||||
\fBxmonad\fR is customized by creating a custom Config.hs and (re)compiling the source code. After recompiling, 'restart' is used to fork the new version, with changes reflected immediately.
|
||||
.SH BUGS
|
||||
Probably. If you find any, please report them: http://code.google.com/p/xmonad/issues/list
|
111
man/xmonad.1.markdown
Normal file
111
man/xmonad.1.markdown
Normal file
@@ -0,0 +1,111 @@
|
||||
% XMONAD(1) Tiling Window Manager
|
||||
%
|
||||
% 20 August 2018
|
||||
|
||||
# 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 configuration in _~/.xmonad/xmonad.hs_
|
||||
|
||||
--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 ~/.xmonad/xmonad.hs, and then restarted
|
||||
with mod-q.
|
||||
|
||||
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]: http://xmonad.org
|
||||
[bugtracker]: https://github.com/xmonad/xmonad/issues
|
333
man/xmonad.hs
Normal file
333
man/xmonad.hs
Normal file
@@ -0,0 +1,333 @@
|
||||
--
|
||||
-- xmonad example config file.
|
||||
--
|
||||
-- A template showing all available configuration hooks,
|
||||
-- and how to override the defaults in your own xmonad.hs conf file.
|
||||
--
|
||||
-- Normally, you'd only override those defaults you care about.
|
||||
--
|
||||
|
||||
import XMonad
|
||||
import Data.Monoid
|
||||
import System.Exit
|
||||
|
||||
import qualified XMonad.StackSet as W
|
||||
import qualified Data.Map as M
|
||||
|
||||
-- The preferred terminal program, which is used in a binding below and by
|
||||
-- certain contrib modules.
|
||||
--
|
||||
myTerminal = "xterm"
|
||||
|
||||
-- Whether focus follows the mouse pointer.
|
||||
myFocusFollowsMouse :: Bool
|
||||
myFocusFollowsMouse = True
|
||||
|
||||
-- Whether clicking on a window to focus also passes the click to the window
|
||||
myClickJustFocuses :: Bool
|
||||
myClickJustFocuses = False
|
||||
|
||||
-- Width of the window border in pixels.
|
||||
--
|
||||
myBorderWidth = 1
|
||||
|
||||
-- modMask lets you specify which modkey you want to use. The default
|
||||
-- is mod1Mask ("left alt"). You may also consider using mod3Mask
|
||||
-- ("right alt"), which does not conflict with emacs keybindings. The
|
||||
-- "windows key" is usually mod4Mask.
|
||||
--
|
||||
myModMask = mod1Mask
|
||||
|
||||
-- The default number of workspaces (virtual screens) and their names.
|
||||
-- By default we use numeric strings, but any string may be used as a
|
||||
-- workspace name. The number of workspaces is determined by the length
|
||||
-- of this list.
|
||||
--
|
||||
-- A tagging example:
|
||||
--
|
||||
-- > workspaces = ["web", "irc", "code" ] ++ map show [4..9]
|
||||
--
|
||||
myWorkspaces = ["1","2","3","4","5","6","7","8","9"]
|
||||
|
||||
-- Border colors for unfocused and focused windows, respectively.
|
||||
--
|
||||
myNormalBorderColor = "#dddddd"
|
||||
myFocusedBorderColor = "#ff0000"
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Key bindings. Add, modify or remove key bindings here.
|
||||
--
|
||||
myKeys conf@(XConfig {XMonad.modMask = modm}) = M.fromList $
|
||||
|
||||
-- launch a terminal
|
||||
[ ((modm .|. shiftMask, xK_Return), spawn $ XMonad.terminal conf)
|
||||
|
||||
-- launch dmenu
|
||||
, ((modm, xK_p ), spawn "dmenu_run")
|
||||
|
||||
-- launch gmrun
|
||||
, ((modm .|. shiftMask, xK_p ), spawn "gmrun")
|
||||
|
||||
-- close focused window
|
||||
, ((modm .|. shiftMask, xK_c ), kill)
|
||||
|
||||
-- Rotate through the available layout algorithms
|
||||
, ((modm, xK_space ), sendMessage NextLayout)
|
||||
|
||||
-- Reset the layouts on the current workspace to default
|
||||
, ((modm .|. shiftMask, xK_space ), setLayout $ XMonad.layoutHook conf)
|
||||
|
||||
-- Resize viewed windows to the correct size
|
||||
, ((modm, xK_n ), refresh)
|
||||
|
||||
-- Move focus to the next window
|
||||
, ((modm, xK_Tab ), windows W.focusDown)
|
||||
|
||||
-- Move focus to the next window
|
||||
, ((modm, xK_j ), windows W.focusDown)
|
||||
|
||||
-- Move focus to the previous window
|
||||
, ((modm, xK_k ), windows W.focusUp )
|
||||
|
||||
-- Move focus to the master window
|
||||
, ((modm, xK_m ), windows W.focusMaster )
|
||||
|
||||
-- Swap the focused window and the master window
|
||||
, ((modm, xK_Return), windows W.swapMaster)
|
||||
|
||||
-- Swap the focused window with the next window
|
||||
, ((modm .|. shiftMask, xK_j ), windows W.swapDown )
|
||||
|
||||
-- Swap the focused window with the previous window
|
||||
, ((modm .|. shiftMask, xK_k ), windows W.swapUp )
|
||||
|
||||
-- Shrink the master area
|
||||
, ((modm, xK_h ), sendMessage Shrink)
|
||||
|
||||
-- Expand the master area
|
||||
, ((modm, xK_l ), sendMessage Expand)
|
||||
|
||||
-- Push window back into tiling
|
||||
, ((modm, xK_t ), withFocused $ windows . W.sink)
|
||||
|
||||
-- Increment the number of windows in the master area
|
||||
, ((modm , xK_comma ), sendMessage (IncMasterN 1))
|
||||
|
||||
-- Deincrement the number of windows in the master area
|
||||
, ((modm , xK_period), sendMessage (IncMasterN (-1)))
|
||||
|
||||
-- Toggle the status bar gap
|
||||
-- Use this binding with avoidStruts from Hooks.ManageDocks.
|
||||
-- See also the statusBar function from Hooks.DynamicLog.
|
||||
--
|
||||
-- , ((modm , xK_b ), sendMessage ToggleStruts)
|
||||
|
||||
-- Quit xmonad
|
||||
, ((modm .|. shiftMask, xK_q ), io (exitWith 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 -"))
|
||||
]
|
||||
++
|
||||
|
||||
--
|
||||
-- mod-[1..9], Switch to workspace N
|
||||
-- mod-shift-[1..9], Move client to workspace N
|
||||
--
|
||||
[((m .|. modm, k), windows $ f i)
|
||||
| (i, k) <- zip (XMonad.workspaces conf) [xK_1 .. xK_9]
|
||||
, (f, m) <- [(W.greedyView, 0), (W.shift, shiftMask)]]
|
||||
++
|
||||
|
||||
--
|
||||
-- mod-{w,e,r}, Switch to physical/Xinerama screens 1, 2, or 3
|
||||
-- mod-shift-{w,e,r}, Move client to screen 1, 2, or 3
|
||||
--
|
||||
[((m .|. modm, key), screenWorkspace sc >>= flip whenJust (windows . f))
|
||||
| (key, sc) <- zip [xK_w, xK_e, xK_r] [0..]
|
||||
, (f, m) <- [(W.view, 0), (W.shift, shiftMask)]]
|
||||
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Mouse bindings: default actions bound to mouse events
|
||||
--
|
||||
myMouseBindings (XConfig {XMonad.modMask = modm}) = M.fromList $
|
||||
|
||||
-- mod-button1, Set the window to floating mode and move by dragging
|
||||
[ ((modm, button1), (\w -> focus w >> mouseMoveWindow w
|
||||
>> windows W.shiftMaster))
|
||||
|
||||
-- mod-button2, Raise the window to the top of the stack
|
||||
, ((modm, button2), (\w -> focus w >> windows W.shiftMaster))
|
||||
|
||||
-- mod-button3, Set the window to floating mode and resize by dragging
|
||||
, ((modm, button3), (\w -> focus w >> mouseResizeWindow w
|
||||
>> windows W.shiftMaster))
|
||||
|
||||
-- you may also bind events to the mouse scroll wheel (button4 and button5)
|
||||
]
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Layouts:
|
||||
|
||||
-- You can specify and transform your layouts by modifying these values.
|
||||
-- If you change layout bindings be sure to use 'mod-shift-space' after
|
||||
-- restarting (with 'mod-q') to reset your layout state to the new
|
||||
-- defaults, as xmonad preserves your old layout settings by default.
|
||||
--
|
||||
-- The available layouts. Note that each layout is separated by |||,
|
||||
-- which denotes layout choice.
|
||||
--
|
||||
myLayout = tiled ||| Mirror tiled ||| Full
|
||||
where
|
||||
-- default tiling algorithm partitions the screen into two panes
|
||||
tiled = Tall nmaster delta ratio
|
||||
|
||||
-- The default number of windows in the master pane
|
||||
nmaster = 1
|
||||
|
||||
-- Default proportion of screen occupied by master pane
|
||||
ratio = 1/2
|
||||
|
||||
-- Percent of screen to increment by when resizing panes
|
||||
delta = 3/100
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Window rules:
|
||||
|
||||
-- Execute arbitrary actions and WindowSet manipulations when managing
|
||||
-- a new window. You can use this to, for example, always float a
|
||||
-- particular program, or have a client always appear on a particular
|
||||
-- workspace.
|
||||
--
|
||||
-- To find the property name associated with a program, use
|
||||
-- > xprop | grep WM_CLASS
|
||||
-- and click on the client you're interested in.
|
||||
--
|
||||
-- To match on the WM_NAME, you can use 'title' in the same way that
|
||||
-- 'className' and 'resource' are used below.
|
||||
--
|
||||
myManageHook = composeAll
|
||||
[ className =? "MPlayer" --> doFloat
|
||||
, className =? "Gimp" --> doFloat
|
||||
, resource =? "desktop_window" --> doIgnore
|
||||
, resource =? "kdesktop" --> doIgnore ]
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Event handling
|
||||
|
||||
-- * EwmhDesktops users should change this to ewmhDesktopsEventHook
|
||||
--
|
||||
-- Defines a custom handler function for X Events. The function should
|
||||
-- return (All True) if the default handler is to be run afterwards. To
|
||||
-- combine event hooks use mappend or mconcat from Data.Monoid.
|
||||
--
|
||||
myEventHook = mempty
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Status bars and logging
|
||||
|
||||
-- Perform an arbitrary action on each internal state change or X event.
|
||||
-- See the 'XMonad.Hooks.DynamicLog' extension for examples.
|
||||
--
|
||||
myLogHook = return ()
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Startup hook
|
||||
|
||||
-- Perform an arbitrary action each time xmonad starts or is restarted
|
||||
-- with mod-q. Used by, e.g., XMonad.Layout.PerWorkspace to initialize
|
||||
-- per-workspace layout choices.
|
||||
--
|
||||
-- By default, do nothing.
|
||||
myStartupHook = return ()
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Now run xmonad with all the defaults we set up.
|
||||
|
||||
-- Run xmonad with the settings you specify. No need to modify this.
|
||||
--
|
||||
main = xmonad defaults
|
||||
|
||||
-- A structure containing your configuration settings, overriding
|
||||
-- fields in the default config. Any you don't override, will
|
||||
-- use the defaults defined in xmonad/XMonad/Config.hs
|
||||
--
|
||||
-- No need to modify this.
|
||||
--
|
||||
defaults = def {
|
||||
-- simple stuff
|
||||
terminal = myTerminal,
|
||||
focusFollowsMouse = myFocusFollowsMouse,
|
||||
clickJustFocuses = myClickJustFocuses,
|
||||
borderWidth = myBorderWidth,
|
||||
modMask = myModMask,
|
||||
workspaces = myWorkspaces,
|
||||
normalBorderColor = myNormalBorderColor,
|
||||
focusedBorderColor = myFocusedBorderColor,
|
||||
|
||||
-- key bindings
|
||||
keys = myKeys,
|
||||
mouseBindings = myMouseBindings,
|
||||
|
||||
-- hooks, layouts
|
||||
layoutHook = myLayout,
|
||||
manageHook = myManageHook,
|
||||
handleEventHook = myEventHook,
|
||||
logHook = myLogHook,
|
||||
startupHook = myStartupHook
|
||||
}
|
||||
|
||||
-- | Finally, a copy of the default bindings in simple textual tabular format.
|
||||
help :: String
|
||||
help = unlines ["The default modifier key is 'alt'. Default keybindings:",
|
||||
"",
|
||||
"-- launching and killing programs",
|
||||
"mod-Shift-Enter Launch xterminal",
|
||||
"mod-p Launch dmenu",
|
||||
"mod-Shift-p Launch gmrun",
|
||||
"mod-Shift-c Close/kill the focused window",
|
||||
"mod-Space Rotate through the available layout algorithms",
|
||||
"mod-Shift-Space Reset the layouts on the current workSpace to default",
|
||||
"mod-n Resize/refresh viewed windows to the correct size",
|
||||
"",
|
||||
"-- move focus up or down the window stack",
|
||||
"mod-Tab Move focus to the next window",
|
||||
"mod-Shift-Tab Move focus to the previous window",
|
||||
"mod-j Move focus to the next window",
|
||||
"mod-k Move focus to the previous window",
|
||||
"mod-m Move focus to the master window",
|
||||
"",
|
||||
"-- modifying the window order",
|
||||
"mod-Return Swap the focused window and the master window",
|
||||
"mod-Shift-j Swap the focused window with the next window",
|
||||
"mod-Shift-k Swap the focused window with the previous window",
|
||||
"",
|
||||
"-- resizing the master/slave ratio",
|
||||
"mod-h Shrink the master area",
|
||||
"mod-l Expand the master area",
|
||||
"",
|
||||
"-- floating layer support",
|
||||
"mod-t Push window back into tiling; unfloat and re-tile it",
|
||||
"",
|
||||
"-- increase or decrease number of windows in the master area",
|
||||
"mod-comma (mod-,) Increment the number of windows in the master area",
|
||||
"mod-period (mod-.) Deincrement the number of windows in the master area",
|
||||
"",
|
||||
"-- quit, or restart",
|
||||
"mod-Shift-q Quit xmonad",
|
||||
"mod-q Restart xmonad",
|
||||
"mod-[1..9] Switch to workSpace N",
|
||||
"",
|
||||
"-- Workspaces & screens",
|
||||
"mod-Shift-[1..9] Move client to workspace N",
|
||||
"mod-{w,e,r} Switch to physical/Xinerama screens 1, 2, or 3",
|
||||
"mod-Shift-{w,e,r} Move client to screen 1, 2, or 3",
|
||||
"",
|
||||
"-- Mouse bindings: default actions bound to mouse events",
|
||||
"mod-button1 Set the window to floating mode and move by dragging",
|
||||
"mod-button2 Raise the window to the top of the stack",
|
||||
"mod-button3 Set the window to floating mode and resize by dragging"]
|
47
src/XMonad.hs
Normal file
47
src/XMonad.hs
Normal file
@@ -0,0 +1,47 @@
|
||||
--------------------------------------------------------------------
|
||||
-- |
|
||||
-- Module : XMonad
|
||||
-- Copyright : (c) Don Stewart
|
||||
-- License : BSD3
|
||||
--
|
||||
-- Maintainer: Don Stewart <dons@galois.com>
|
||||
-- Stability : provisional
|
||||
-- Portability:
|
||||
--
|
||||
--------------------------------------------------------------------
|
||||
--
|
||||
-- Useful exports for configuration files.
|
||||
|
||||
module XMonad (
|
||||
|
||||
module XMonad.Main,
|
||||
module XMonad.Core,
|
||||
module XMonad.Config,
|
||||
module XMonad.Layout,
|
||||
module XMonad.ManageHook,
|
||||
module XMonad.Operations,
|
||||
module Graphics.X11,
|
||||
module Graphics.X11.Xlib.Extras,
|
||||
(.|.),
|
||||
MonadState(..), gets, modify,
|
||||
MonadReader(..), asks,
|
||||
MonadIO(..)
|
||||
|
||||
) where
|
||||
|
||||
-- core modules
|
||||
import XMonad.Main
|
||||
import XMonad.Core
|
||||
import XMonad.Config
|
||||
import XMonad.Layout
|
||||
import XMonad.ManageHook
|
||||
import XMonad.Operations
|
||||
-- import XMonad.StackSet -- conflicts with 'workspaces' defined in XMonad.hs
|
||||
|
||||
-- modules needed to get basic configuration working
|
||||
import Data.Bits
|
||||
import Graphics.X11 hiding (refreshKeyboardMapping)
|
||||
import Graphics.X11.Xlib.Extras
|
||||
|
||||
import Control.Monad.State
|
||||
import Control.Monad.Reader
|
336
src/XMonad/Config.hs
Normal file
336
src/XMonad/Config.hs
Normal file
@@ -0,0 +1,336 @@
|
||||
{-# OPTIONS_GHC -fno-warn-missing-signatures -fno-warn-orphans #-}
|
||||
{-# LANGUAGE TypeFamilies #-}
|
||||
-----------------------------------------------------------------------------
|
||||
-- |
|
||||
-- Module : XMonad.Config
|
||||
-- Copyright : (c) Spencer Janssen 2007
|
||||
-- License : BSD3-style (see LICENSE)
|
||||
--
|
||||
-- Maintainer : dons@galois.com
|
||||
-- Stability : stable
|
||||
-- Portability : portable
|
||||
--
|
||||
-- This module specifies the default configuration values for xmonad.
|
||||
--
|
||||
-- DO NOT MODIFY THIS FILE! It won't work. You may configure xmonad
|
||||
-- by providing your own @~\/.xmonad\/xmonad.hs@ that overrides
|
||||
-- specific fields in the default config, 'def'. For a starting point, you can
|
||||
-- copy the @xmonad.hs@ found in the @man@ directory, or look at
|
||||
-- examples on the xmonad wiki.
|
||||
--
|
||||
------------------------------------------------------------------------
|
||||
|
||||
module XMonad.Config (defaultConfig, Default(..)) where
|
||||
|
||||
--
|
||||
-- Useful imports
|
||||
--
|
||||
import XMonad.Core as XMonad hiding
|
||||
(workspaces,manageHook,keys,logHook,startupHook,borderWidth,mouseBindings
|
||||
,layoutHook,modMask,terminal,normalBorderColor,focusedBorderColor,focusFollowsMouse
|
||||
,handleEventHook,clickJustFocuses,rootMask,clientMask)
|
||||
import qualified XMonad.Core as XMonad
|
||||
(workspaces,manageHook,keys,logHook,startupHook,borderWidth,mouseBindings
|
||||
,layoutHook,modMask,terminal,normalBorderColor,focusedBorderColor,focusFollowsMouse
|
||||
,handleEventHook,clickJustFocuses,rootMask,clientMask)
|
||||
|
||||
import XMonad.Layout
|
||||
import XMonad.Operations
|
||||
import XMonad.ManageHook
|
||||
import qualified XMonad.StackSet as W
|
||||
import Data.Bits ((.|.))
|
||||
import Data.Default
|
||||
import Data.Monoid
|
||||
import qualified Data.Map as M
|
||||
import System.Exit
|
||||
import Graphics.X11.Xlib
|
||||
import Graphics.X11.Xlib.Extras
|
||||
|
||||
-- | The default number of workspaces (virtual screens) and their names.
|
||||
-- By default we use numeric strings, but any string may be used as a
|
||||
-- workspace name. The number of workspaces is determined by the length
|
||||
-- of this list.
|
||||
--
|
||||
-- A tagging example:
|
||||
--
|
||||
-- > workspaces = ["web", "irc", "code" ] ++ map show [4..9]
|
||||
--
|
||||
workspaces :: [WorkspaceId]
|
||||
workspaces = map show [1 .. 9 :: Int]
|
||||
|
||||
-- | modMask lets you specify which modkey you want to use. The default
|
||||
-- is mod1Mask ("left alt"). You may also consider using mod3Mask
|
||||
-- ("right alt"), which does not conflict with emacs keybindings. The
|
||||
-- "windows key" is usually mod4Mask.
|
||||
--
|
||||
defaultModMask :: KeyMask
|
||||
defaultModMask = mod1Mask
|
||||
|
||||
-- | Width of the window border in pixels.
|
||||
--
|
||||
borderWidth :: Dimension
|
||||
borderWidth = 1
|
||||
|
||||
-- | Border colors for unfocused and focused windows, respectively.
|
||||
--
|
||||
normalBorderColor, focusedBorderColor :: String
|
||||
normalBorderColor = "gray" -- "#dddddd"
|
||||
focusedBorderColor = "red" -- "#ff0000" don't use hex, not <24 bit safe
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Window rules
|
||||
|
||||
-- | Execute arbitrary actions and WindowSet manipulations when managing
|
||||
-- a new window. You can use this to, for example, always float a
|
||||
-- particular program, or have a client always appear on a particular
|
||||
-- workspace.
|
||||
--
|
||||
-- To find the property name associated with a program, use
|
||||
-- xprop | grep WM_CLASS
|
||||
-- and click on the client you're interested in.
|
||||
--
|
||||
manageHook :: ManageHook
|
||||
manageHook = composeAll
|
||||
[ className =? "MPlayer" --> doFloat
|
||||
, className =? "mplayer2" --> doFloat ]
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Logging
|
||||
|
||||
-- | Perform an arbitrary action on each internal state change or X event.
|
||||
-- Examples include:
|
||||
--
|
||||
-- * do nothing
|
||||
--
|
||||
-- * log the state to stdout
|
||||
--
|
||||
-- See the 'DynamicLog' extension for examples.
|
||||
--
|
||||
logHook :: X ()
|
||||
logHook = return ()
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Event handling
|
||||
|
||||
-- | Defines a custom handler function for X Events. The function should
|
||||
-- return (All True) if the default handler is to be run afterwards.
|
||||
-- To combine event hooks, use mappend or mconcat from Data.Monoid.
|
||||
handleEventHook :: Event -> X All
|
||||
handleEventHook _ = return (All True)
|
||||
|
||||
-- | Perform an arbitrary action at xmonad startup.
|
||||
startupHook :: X ()
|
||||
startupHook = return ()
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Extensible layouts
|
||||
--
|
||||
-- You can specify and transform your layouts by modifying these values.
|
||||
-- If you change layout bindings be sure to use 'mod-shift-space' after
|
||||
-- restarting (with 'mod-q') to reset your layout state to the new
|
||||
-- defaults, as xmonad preserves your old layout settings by default.
|
||||
--
|
||||
|
||||
-- | The available layouts. Note that each layout is separated by |||, which
|
||||
-- denotes layout choice.
|
||||
layout = tiled ||| Mirror tiled ||| Full
|
||||
where
|
||||
-- default tiling algorithm partitions the screen into two panes
|
||||
tiled = Tall nmaster delta ratio
|
||||
|
||||
-- The default number of windows in the master pane
|
||||
nmaster = 1
|
||||
|
||||
-- Default proportion of screen occupied by master pane
|
||||
ratio = 1/2
|
||||
|
||||
-- Percent of screen to increment by when resizing panes
|
||||
delta = 3/100
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Event Masks:
|
||||
|
||||
-- | The client events that xmonad is interested in
|
||||
clientMask :: EventMask
|
||||
clientMask = structureNotifyMask .|. enterWindowMask .|. propertyChangeMask
|
||||
|
||||
-- | The root events that xmonad is interested in
|
||||
rootMask :: EventMask
|
||||
rootMask = substructureRedirectMask .|. substructureNotifyMask
|
||||
.|. enterWindowMask .|. leaveWindowMask .|. structureNotifyMask
|
||||
.|. buttonPressMask
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Key bindings:
|
||||
|
||||
-- | The preferred terminal program, which is used in a binding below and by
|
||||
-- certain contrib modules.
|
||||
terminal :: String
|
||||
terminal = "xterm"
|
||||
|
||||
-- | Whether focus follows the mouse pointer.
|
||||
focusFollowsMouse :: Bool
|
||||
focusFollowsMouse = True
|
||||
|
||||
-- | Whether a mouse click select the focus or is just passed to the window
|
||||
clickJustFocuses :: Bool
|
||||
clickJustFocuses = True
|
||||
|
||||
|
||||
-- | The xmonad key bindings. Add, modify or remove key bindings here.
|
||||
--
|
||||
-- (The comment formatting character is used when generating the manpage)
|
||||
--
|
||||
keys :: XConfig Layout -> M.Map (KeyMask, KeySym) (X ())
|
||||
keys conf@(XConfig {XMonad.modMask = modMask}) = M.fromList $
|
||||
-- launching and killing programs
|
||||
[ ((modMask .|. shiftMask, xK_Return), spawn $ XMonad.terminal conf) -- %! Launch terminal
|
||||
, ((modMask, xK_p ), spawn "dmenu_run") -- %! Launch dmenu
|
||||
, ((modMask .|. shiftMask, xK_p ), spawn "gmrun") -- %! Launch gmrun
|
||||
, ((modMask .|. shiftMask, xK_c ), kill) -- %! Close the focused window
|
||||
|
||||
, ((modMask, xK_space ), sendMessage NextLayout) -- %! Rotate through the available layout algorithms
|
||||
, ((modMask .|. shiftMask, xK_space ), setLayout $ XMonad.layoutHook conf) -- %! Reset the layouts on the current workspace to default
|
||||
|
||||
, ((modMask, xK_n ), refresh) -- %! Resize viewed windows to the correct size
|
||||
|
||||
-- move focus up or down the window stack
|
||||
, ((modMask, xK_Tab ), windows W.focusDown) -- %! Move focus to the next window
|
||||
, ((modMask .|. shiftMask, xK_Tab ), windows W.focusUp ) -- %! Move focus to the previous window
|
||||
, ((modMask, xK_j ), windows W.focusDown) -- %! Move focus to the next window
|
||||
, ((modMask, xK_k ), windows W.focusUp ) -- %! Move focus to the previous window
|
||||
, ((modMask, xK_m ), windows W.focusMaster ) -- %! Move focus to the master window
|
||||
|
||||
-- modifying the window order
|
||||
, ((modMask, xK_Return), windows W.swapMaster) -- %! Swap the focused window and the master window
|
||||
, ((modMask .|. shiftMask, xK_j ), windows W.swapDown ) -- %! Swap the focused window with the next window
|
||||
, ((modMask .|. shiftMask, xK_k ), windows W.swapUp ) -- %! Swap the focused window with the previous window
|
||||
|
||||
-- resizing the master/slave ratio
|
||||
, ((modMask, xK_h ), sendMessage Shrink) -- %! Shrink the master area
|
||||
, ((modMask, xK_l ), sendMessage Expand) -- %! Expand the master area
|
||||
|
||||
-- floating layer support
|
||||
, ((modMask, xK_t ), withFocused $ windows . W.sink) -- %! Push window back into tiling
|
||||
|
||||
-- increase or decrease number of windows in the master area
|
||||
, ((modMask , xK_comma ), sendMessage (IncMasterN 1)) -- %! Increment the number of windows in the master area
|
||||
, ((modMask , xK_period), sendMessage (IncMasterN (-1))) -- %! Deincrement the number of windows in the master area
|
||||
|
||||
-- quit, or restart
|
||||
, ((modMask .|. shiftMask, xK_q ), io (exitWith ExitSuccess)) -- %! Quit xmonad
|
||||
, ((modMask , xK_q ), spawn "if type xmonad; then xmonad --recompile && xmonad --restart; else xmessage xmonad not in \\$PATH: \"$PATH\"; fi") -- %! Restart xmonad
|
||||
|
||||
, ((modMask .|. shiftMask, xK_slash ), helpCommand) -- %! Run xmessage with a summary of the default keybindings (useful for beginners)
|
||||
-- repeat the binding for non-American layout keyboards
|
||||
, ((modMask , xK_question), helpCommand) -- %! Run xmessage with a summary of the default keybindings (useful for beginners)
|
||||
]
|
||||
++
|
||||
-- mod-[1..9] %! Switch to workspace N
|
||||
-- mod-shift-[1..9] %! Move client to workspace N
|
||||
[((m .|. modMask, k), windows $ f i)
|
||||
| (i, k) <- zip (XMonad.workspaces conf) [xK_1 .. xK_9]
|
||||
, (f, m) <- [(W.greedyView, 0), (W.shift, shiftMask)]]
|
||||
++
|
||||
-- mod-{w,e,r} %! Switch to physical/Xinerama screens 1, 2, or 3
|
||||
-- mod-shift-{w,e,r} %! Move client to screen 1, 2, or 3
|
||||
[((m .|. modMask, key), screenWorkspace sc >>= flip whenJust (windows . f))
|
||||
| (key, sc) <- zip [xK_w, xK_e, xK_r] [0..]
|
||||
, (f, m) <- [(W.view, 0), (W.shift, shiftMask)]]
|
||||
where
|
||||
helpCommand :: X ()
|
||||
helpCommand = spawn ("echo " ++ show help ++ " | xmessage -file -")
|
||||
|
||||
-- | Mouse bindings: default actions bound to mouse events
|
||||
mouseBindings :: XConfig Layout -> M.Map (KeyMask, Button) (Window -> X ())
|
||||
mouseBindings (XConfig {XMonad.modMask = modMask}) = M.fromList
|
||||
-- mod-button1 %! Set the window to floating mode and move by dragging
|
||||
[ ((modMask, button1), \w -> focus w >> mouseMoveWindow w
|
||||
>> windows W.shiftMaster)
|
||||
-- mod-button2 %! Raise the window to the top of the stack
|
||||
, ((modMask, button2), windows . (W.shiftMaster .) . W.focusWindow)
|
||||
-- mod-button3 %! Set the window to floating mode and resize by dragging
|
||||
, ((modMask, button3), \w -> focus w >> mouseResizeWindow w
|
||||
>> windows W.shiftMaster)
|
||||
-- you may also bind events to the mouse scroll wheel (button4 and button5)
|
||||
]
|
||||
|
||||
instance (a ~ Choose Tall (Choose (Mirror Tall) Full)) => Default (XConfig a) where
|
||||
def = XConfig
|
||||
{ XMonad.borderWidth = borderWidth
|
||||
, XMonad.workspaces = workspaces
|
||||
, XMonad.layoutHook = layout
|
||||
, XMonad.terminal = terminal
|
||||
, XMonad.normalBorderColor = normalBorderColor
|
||||
, XMonad.focusedBorderColor = focusedBorderColor
|
||||
, XMonad.modMask = defaultModMask
|
||||
, XMonad.keys = keys
|
||||
, XMonad.logHook = logHook
|
||||
, XMonad.startupHook = startupHook
|
||||
, XMonad.mouseBindings = mouseBindings
|
||||
, XMonad.manageHook = manageHook
|
||||
, XMonad.handleEventHook = handleEventHook
|
||||
, XMonad.focusFollowsMouse = focusFollowsMouse
|
||||
, XMonad.clickJustFocuses = clickJustFocuses
|
||||
, XMonad.clientMask = clientMask
|
||||
, XMonad.rootMask = rootMask
|
||||
, XMonad.handleExtraArgs = \ xs theConf -> case xs of
|
||||
[] -> return theConf
|
||||
_ -> fail ("unrecognized flags:" ++ show xs)
|
||||
}
|
||||
|
||||
-- | The default set of configuration values itself
|
||||
{-# DEPRECATED defaultConfig "Use def (from Data.Default, and re-exported by XMonad and XMonad.Config) instead." #-}
|
||||
defaultConfig :: XConfig (Choose Tall (Choose (Mirror Tall) Full))
|
||||
defaultConfig = def
|
||||
|
||||
-- | Finally, a copy of the default bindings in simple textual tabular format.
|
||||
help :: String
|
||||
help = unlines ["The default modifier key is 'alt'. Default keybindings:",
|
||||
"",
|
||||
"-- launching and killing programs",
|
||||
"mod-Shift-Enter Launch xterminal",
|
||||
"mod-p Launch dmenu",
|
||||
"mod-Shift-p Launch gmrun",
|
||||
"mod-Shift-c Close/kill the focused window",
|
||||
"mod-Space Rotate through the available layout algorithms",
|
||||
"mod-Shift-Space Reset the layouts on the current workSpace to default",
|
||||
"mod-n Resize/refresh viewed windows to the correct size",
|
||||
"",
|
||||
"-- move focus up or down the window stack",
|
||||
"mod-Tab Move focus to the next window",
|
||||
"mod-Shift-Tab Move focus to the previous window",
|
||||
"mod-j Move focus to the next window",
|
||||
"mod-k Move focus to the previous window",
|
||||
"mod-m Move focus to the master window",
|
||||
"",
|
||||
"-- modifying the window order",
|
||||
"mod-Return Swap the focused window and the master window",
|
||||
"mod-Shift-j Swap the focused window with the next window",
|
||||
"mod-Shift-k Swap the focused window with the previous window",
|
||||
"",
|
||||
"-- resizing the master/slave ratio",
|
||||
"mod-h Shrink the master area",
|
||||
"mod-l Expand the master area",
|
||||
"",
|
||||
"-- floating layer support",
|
||||
"mod-t Push window back into tiling; unfloat and re-tile it",
|
||||
"",
|
||||
"-- increase or decrease number of windows in the master area",
|
||||
"mod-comma (mod-,) Increment the number of windows in the master area",
|
||||
"mod-period (mod-.) Deincrement the number of windows in the master area",
|
||||
"",
|
||||
"-- quit, or restart",
|
||||
"mod-Shift-q Quit xmonad",
|
||||
"mod-q Restart xmonad",
|
||||
"",
|
||||
"-- Workspaces & screens",
|
||||
"mod-[1..9] Switch to workSpace N",
|
||||
"mod-Shift-[1..9] Move client to workspace N",
|
||||
"mod-{w,e,r} Switch to physical/Xinerama screens 1, 2, or 3",
|
||||
"mod-Shift-{w,e,r} Move client to screen 1, 2, or 3",
|
||||
"",
|
||||
"-- Mouse bindings: default actions bound to mouse events",
|
||||
"mod-button1 Set the window to floating mode and move by dragging",
|
||||
"mod-button2 Raise the window to the top of the stack",
|
||||
"mod-button3 Set the window to floating mode and resize by dragging"]
|
723
src/XMonad/Core.hs
Normal file
723
src/XMonad/Core.hs
Normal file
@@ -0,0 +1,723 @@
|
||||
{-# LANGUAGE ExistentialQuantification, FlexibleInstances, GeneralizedNewtypeDeriving,
|
||||
MultiParamTypeClasses, TypeSynonymInstances, DeriveDataTypeable #-}
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- |
|
||||
-- Module : XMonad.Core
|
||||
-- Copyright : (c) Spencer Janssen 2007
|
||||
-- License : BSD3-style (see LICENSE)
|
||||
--
|
||||
-- Maintainer : spencerjanssen@gmail.com
|
||||
-- Stability : unstable
|
||||
-- Portability : not portable, uses cunning newtype deriving
|
||||
--
|
||||
-- The 'X' monad, a state monad transformer over 'IO', for the window
|
||||
-- manager state, and support routines.
|
||||
--
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
module XMonad.Core (
|
||||
X, WindowSet, WindowSpace, WorkspaceId,
|
||||
ScreenId(..), ScreenDetail(..), XState(..),
|
||||
XConf(..), XConfig(..), LayoutClass(..),
|
||||
Layout(..), readsLayout, Typeable, Message,
|
||||
SomeMessage(..), fromMessage, LayoutMessages(..),
|
||||
StateExtension(..), ExtensionClass(..),
|
||||
runX, catchX, userCode, userCodeDef, io, catchIO, installSignalHandlers, uninstallSignalHandlers,
|
||||
withDisplay, withWindowSet, isRoot, runOnWorkspaces,
|
||||
getAtom, spawn, spawnPID, xfork, recompile, trace, whenJust, whenX,
|
||||
getXMonadDir, getXMonadCacheDir, getXMonadDataDir, stateFileName,
|
||||
atom_WM_STATE, atom_WM_PROTOCOLS, atom_WM_DELETE_WINDOW, atom_WM_TAKE_FOCUS, withWindowAttributes,
|
||||
ManageHook, Query(..), runQuery
|
||||
) 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.Monad.Fail
|
||||
import Control.Monad.State
|
||||
import Control.Monad.Reader
|
||||
import Data.Semigroup
|
||||
import Data.Default
|
||||
import System.FilePath
|
||||
import System.IO
|
||||
import System.Info
|
||||
import System.Posix.Env (getEnv)
|
||||
import System.Posix.Process (executeFile, forkProcess, getAnyProcessStatus, createSession)
|
||||
import System.Posix.Signals
|
||||
import System.Posix.IO
|
||||
import System.Posix.Types (ProcessID)
|
||||
import System.Process
|
||||
import System.Directory
|
||||
import System.Exit
|
||||
import Graphics.X11.Xlib
|
||||
import Graphics.X11.Xlib.Extras (getWindowAttributes, WindowAttributes, Event)
|
||||
import Data.Typeable
|
||||
import Data.List ((\\))
|
||||
import Data.Maybe (isJust,fromMaybe)
|
||||
import Data.Monoid hiding ((<>))
|
||||
import System.Environment (lookupEnv)
|
||||
|
||||
import qualified Data.Map as M
|
||||
import qualified Data.Set as S
|
||||
|
||||
-- | XState, the (mutable) window manager state.
|
||||
data XState = XState
|
||||
{ windowset :: !WindowSet -- ^ workspace list
|
||||
, mapped :: !(S.Set Window) -- ^ the Set of mapped windows
|
||||
, waitingUnmap :: !(M.Map Window Int) -- ^ the number of expected UnmapEvents
|
||||
, dragging :: !(Maybe (Position -> Position -> X (), X ()))
|
||||
, numberlockMask :: !KeyMask -- ^ The numlock modifier
|
||||
, extensibleState :: !(M.Map String (Either String StateExtension))
|
||||
-- ^ stores custom state information.
|
||||
--
|
||||
-- The module "XMonad.Util.ExtensibleState" in xmonad-contrib
|
||||
-- provides additional information and a simple interface for using this.
|
||||
}
|
||||
|
||||
-- | XConf, the (read-only) window manager configuration.
|
||||
data XConf = XConf
|
||||
{ display :: Display -- ^ the X11 display
|
||||
, config :: !(XConfig Layout) -- ^ initial user configuration
|
||||
, theRoot :: !Window -- ^ the root window
|
||||
, normalBorder :: !Pixel -- ^ border color of unfocused windows
|
||||
, focusedBorder :: !Pixel -- ^ border color of the focused window
|
||||
, keyActions :: !(M.Map (KeyMask, KeySym) (X ()))
|
||||
-- ^ a mapping of key presses to actions
|
||||
, buttonActions :: !(M.Map (KeyMask, Button) (Window -> X ()))
|
||||
-- ^ a mapping of button presses to actions
|
||||
, mouseFocused :: !Bool -- ^ was refocus caused by mouse action?
|
||||
, mousePosition :: !(Maybe (Position, Position))
|
||||
-- ^ position of the mouse according to
|
||||
-- the event currently being processed
|
||||
, currentEvent :: !(Maybe Event)
|
||||
-- ^ event currently being processed
|
||||
}
|
||||
|
||||
-- todo, better name
|
||||
data XConfig l = XConfig
|
||||
{ normalBorderColor :: !String -- ^ Non focused windows border color. Default: \"#dddddd\"
|
||||
, focusedBorderColor :: !String -- ^ Focused windows border color. Default: \"#ff0000\"
|
||||
, terminal :: !String -- ^ The preferred terminal application. Default: \"xterm\"
|
||||
, layoutHook :: !(l Window) -- ^ The available layouts
|
||||
, manageHook :: !ManageHook -- ^ The action to run when a new window is opened
|
||||
, handleEventHook :: !(Event -> X All) -- ^ Handle an X event, returns (All True) if the default handler
|
||||
-- should also be run afterwards. mappend should be used for combining
|
||||
-- event hooks in most cases.
|
||||
, workspaces :: ![String] -- ^ The list of workspaces' names
|
||||
, modMask :: !KeyMask -- ^ the mod modifier
|
||||
, keys :: !(XConfig Layout -> M.Map (ButtonMask,KeySym) (X ()))
|
||||
-- ^ The key binding: a map from key presses and actions
|
||||
, mouseBindings :: !(XConfig Layout -> M.Map (ButtonMask, Button) (Window -> X ()))
|
||||
-- ^ The mouse bindings
|
||||
, borderWidth :: !Dimension -- ^ The border width
|
||||
, logHook :: !(X ()) -- ^ The action to perform when the windows set is changed
|
||||
, startupHook :: !(X ()) -- ^ The action to perform on startup
|
||||
, focusFollowsMouse :: !Bool -- ^ Whether window entry events can change focus
|
||||
, clickJustFocuses :: !Bool -- ^ False to make a click which changes focus to be additionally passed to the window
|
||||
, clientMask :: !EventMask -- ^ The client events that xmonad is interested in
|
||||
, rootMask :: !EventMask -- ^ The root events that xmonad is interested in
|
||||
, handleExtraArgs :: !([String] -> XConfig Layout -> IO (XConfig Layout))
|
||||
-- ^ Modify the configuration, complain about extra arguments etc. with arguments that are not handled by default
|
||||
}
|
||||
|
||||
|
||||
type WindowSet = StackSet WorkspaceId (Layout Window) Window ScreenId ScreenDetail
|
||||
type WindowSpace = Workspace WorkspaceId (Layout Window) Window
|
||||
|
||||
-- | Virtual workspace indices
|
||||
type WorkspaceId = String
|
||||
|
||||
-- | Physical screen indices
|
||||
newtype ScreenId = S Int deriving (Eq,Ord,Show,Read,Enum,Num,Integral,Real)
|
||||
|
||||
-- | The 'Rectangle' with screen dimensions
|
||||
data ScreenDetail = SD { screenRect :: !Rectangle } deriving (Eq,Show, Read)
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
-- | The X monad, 'ReaderT' and 'StateT' transformers over 'IO'
|
||||
-- encapsulating the window manager configuration and state,
|
||||
-- respectively.
|
||||
--
|
||||
-- Dynamic components may be retrieved with 'get', static components
|
||||
-- with 'ask'. With newtype deriving we get readers and state monads
|
||||
-- instantiated on 'XConf' and 'XState' automatically.
|
||||
--
|
||||
newtype X a = X (ReaderT XConf (StateT XState IO) a)
|
||||
deriving (Functor, 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
|
||||
|
||||
instance Default a => Default (X a) where
|
||||
def = return def
|
||||
|
||||
type ManageHook = Query (Endo WindowSet)
|
||||
newtype Query a = Query (ReaderT Window X a)
|
||||
deriving (Functor, Applicative, Monad, MonadReader Window, MonadIO)
|
||||
|
||||
runQuery :: Query a -> Window -> X a
|
||||
runQuery (Query m) 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
|
||||
|
||||
instance Default a => Default (Query a) where
|
||||
def = return def
|
||||
|
||||
-- | Run the 'X' monad, given a chunk of 'X' monad code, and an initial state
|
||||
-- Return the result, and final state
|
||||
runX :: XConf -> XState -> X a -> IO (a, XState)
|
||||
runX c st (X a) = runStateT (runReaderT a c) st
|
||||
|
||||
-- | Run in the 'X' monad, and in case of exception, and catch it and log it
|
||||
-- to stderr, and run the error case.
|
||||
catchX :: X a -> X a -> X a
|
||||
catchX job errcase = do
|
||||
st <- get
|
||||
c <- ask
|
||||
(a, s') <- io $ runX c st job `E.catch` \e -> case fromException e of
|
||||
Just x -> throw e `const` (x `asTypeOf` ExitSuccess)
|
||||
_ -> do hPrint stderr e; runX c st errcase
|
||||
put s'
|
||||
return a
|
||||
|
||||
-- | Execute the argument, catching all exceptions. Either this function or
|
||||
-- 'catchX' should be used at all callsites of user customized code.
|
||||
userCode :: X a -> X (Maybe a)
|
||||
userCode a = catchX (Just `liftM` 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
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Convenient wrappers to state
|
||||
|
||||
-- | Run a monad action with the current display settings
|
||||
withDisplay :: (Display -> X a) -> X a
|
||||
withDisplay f = asks display >>= f
|
||||
|
||||
-- | Run a monadic action with the current stack set
|
||||
withWindowSet :: (WindowSet -> X a) -> X a
|
||||
withWindowSet f = gets windowset >>= f
|
||||
|
||||
-- | Safely access window attributes.
|
||||
withWindowAttributes :: Display -> Window -> (WindowAttributes -> X ()) -> X ()
|
||||
withWindowAttributes dpy win f = do
|
||||
wa <- userCode (io $ getWindowAttributes dpy win)
|
||||
catchX (whenJust wa f) (return ())
|
||||
|
||||
-- | True if the given window is the root window
|
||||
isRoot :: Window -> X Bool
|
||||
isRoot w = (w==) <$> asks theRoot
|
||||
|
||||
-- | Wrapper for the common case of atom internment
|
||||
getAtom :: String -> X Atom
|
||||
getAtom str = withDisplay $ \dpy -> io $ internAtom dpy str False
|
||||
|
||||
-- | Common non-predefined atoms
|
||||
atom_WM_PROTOCOLS, atom_WM_DELETE_WINDOW, atom_WM_STATE, atom_WM_TAKE_FOCUS :: X Atom
|
||||
atom_WM_PROTOCOLS = getAtom "WM_PROTOCOLS"
|
||||
atom_WM_DELETE_WINDOW = getAtom "WM_DELETE_WINDOW"
|
||||
atom_WM_STATE = getAtom "WM_STATE"
|
||||
atom_WM_TAKE_FOCUS = getAtom "WM_TAKE_FOCUS"
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- LayoutClass handling. See particular instances in Operations.hs
|
||||
|
||||
-- | An existential type that can hold any object that is in 'Read'
|
||||
-- and 'LayoutClass'.
|
||||
data Layout a = forall l. (LayoutClass l a, Read (l a)) => Layout (l a)
|
||||
|
||||
-- | Using the 'Layout' as a witness, parse existentially wrapped windows
|
||||
-- from a 'String'.
|
||||
readsLayout :: Layout a -> String -> [(Layout a, String)]
|
||||
readsLayout (Layout l) s = [(Layout (asTypeOf x l), rs) | (x, rs) <- reads s]
|
||||
|
||||
-- | Every layout must be an instance of 'LayoutClass', which defines
|
||||
-- the basic layout operations along with a sensible default for each.
|
||||
--
|
||||
-- Minimal complete definition:
|
||||
--
|
||||
-- * 'runLayout' || (('doLayout' || 'pureLayout') && 'emptyLayout'), and
|
||||
--
|
||||
-- * 'handleMessage' || 'pureMessage'
|
||||
--
|
||||
-- You should also strongly consider implementing 'description',
|
||||
-- although it is not required.
|
||||
--
|
||||
-- Note that any code which /uses/ 'LayoutClass' methods should only
|
||||
-- ever call 'runLayout', 'handleMessage', and 'description'! In
|
||||
-- other words, the only calls to 'doLayout', 'pureMessage', and other
|
||||
-- such methods should be from the default implementations of
|
||||
-- 'runLayout', 'handleMessage', and so on. This ensures that the
|
||||
-- proper methods will be used, regardless of the particular methods
|
||||
-- that any 'LayoutClass' instance chooses to define.
|
||||
class Show (layout a) => LayoutClass layout a where
|
||||
|
||||
-- | By default, 'runLayout' calls 'doLayout' if there are any
|
||||
-- windows to be laid out, and 'emptyLayout' otherwise. Most
|
||||
-- instances of 'LayoutClass' probably do not need to implement
|
||||
-- 'runLayout'; it is only useful for layouts which wish to make
|
||||
-- use of more of the 'Workspace' information (for example,
|
||||
-- "XMonad.Layout.PerWorkspace").
|
||||
runLayout :: Workspace WorkspaceId (layout a) a
|
||||
-> Rectangle
|
||||
-> X ([(a, Rectangle)], Maybe (layout a))
|
||||
runLayout (Workspace _ l ms) r = maybe (emptyLayout l r) (doLayout l r) ms
|
||||
|
||||
-- | Given a 'Rectangle' in which to place the windows, and a 'Stack'
|
||||
-- of windows, return a list of windows and their corresponding
|
||||
-- Rectangles. If an element is not given a Rectangle by
|
||||
-- 'doLayout', then it is not shown on screen. The order of
|
||||
-- windows in this list should be the desired stacking order.
|
||||
--
|
||||
-- Also possibly return a modified layout (by returning @Just
|
||||
-- newLayout@), if this layout needs to be modified (e.g. if it
|
||||
-- keeps track of some sort of state). Return @Nothing@ if the
|
||||
-- layout does not need to be modified.
|
||||
--
|
||||
-- Layouts which do not need access to the 'X' monad ('IO', window
|
||||
-- manager state, or configuration) and do not keep track of their
|
||||
-- own state should implement 'pureLayout' instead of 'doLayout'.
|
||||
doLayout :: layout a -> Rectangle -> Stack a
|
||||
-> X ([(a, Rectangle)], Maybe (layout a))
|
||||
doLayout l r s = return (pureLayout l r s, Nothing)
|
||||
|
||||
-- | This is a pure version of 'doLayout', for cases where we
|
||||
-- don't need access to the 'X' monad to determine how to lay out
|
||||
-- the windows, and we don't need to modify the layout itself.
|
||||
pureLayout :: layout a -> Rectangle -> Stack a -> [(a, Rectangle)]
|
||||
pureLayout _ r s = [(focus s, r)]
|
||||
|
||||
-- | 'emptyLayout' is called when there are no windows.
|
||||
emptyLayout :: layout a -> Rectangle -> X ([(a, Rectangle)], Maybe (layout a))
|
||||
emptyLayout _ _ = return ([], Nothing)
|
||||
|
||||
-- | 'handleMessage' performs message handling. If
|
||||
-- 'handleMessage' returns @Nothing@, then the layout did not
|
||||
-- respond to the message and the screen is not refreshed.
|
||||
-- Otherwise, 'handleMessage' returns an updated layout and the
|
||||
-- screen is refreshed.
|
||||
--
|
||||
-- Layouts which do not need access to the 'X' monad to decide how
|
||||
-- to handle messages should implement 'pureMessage' instead of
|
||||
-- 'handleMessage' (this restricts the risk of error, and makes
|
||||
-- testing much easier).
|
||||
handleMessage :: layout a -> SomeMessage -> X (Maybe (layout a))
|
||||
handleMessage l = return . pureMessage l
|
||||
|
||||
-- | Respond to a message by (possibly) changing our layout, but
|
||||
-- taking no other action. If the layout changes, the screen will
|
||||
-- be refreshed.
|
||||
pureMessage :: layout a -> SomeMessage -> Maybe (layout a)
|
||||
pureMessage _ _ = Nothing
|
||||
|
||||
-- | This should be a human-readable string that is used when
|
||||
-- selecting layouts by name. The default implementation is
|
||||
-- 'show', which is in some cases a poor default.
|
||||
description :: layout a -> String
|
||||
description = show
|
||||
|
||||
instance LayoutClass Layout Window where
|
||||
runLayout (Workspace i (Layout l) ms) r = fmap (fmap Layout) `fmap` runLayout (Workspace i l ms) r
|
||||
doLayout (Layout l) r s = fmap (fmap Layout) `fmap` doLayout l r s
|
||||
emptyLayout (Layout l) r = fmap (fmap Layout) `fmap` emptyLayout l r
|
||||
handleMessage (Layout l) = fmap (fmap Layout) . handleMessage l
|
||||
description (Layout l) = description l
|
||||
|
||||
instance Show (Layout a) where show (Layout l) = show l
|
||||
|
||||
-- | Based on ideas in /An Extensible Dynamically-Typed Hierarchy of
|
||||
-- Exceptions/, Simon Marlow, 2006. Use extensible messages to the
|
||||
-- 'handleMessage' handler.
|
||||
--
|
||||
-- User-extensible messages must be a member of this class.
|
||||
--
|
||||
class Typeable a => Message a
|
||||
|
||||
-- |
|
||||
-- A wrapped value of some type in the 'Message' class.
|
||||
--
|
||||
data SomeMessage = forall a. Message a => SomeMessage a
|
||||
|
||||
-- |
|
||||
-- And now, unwrap a given, unknown 'Message' type, performing a (dynamic)
|
||||
-- type check on the result.
|
||||
--
|
||||
fromMessage :: Message m => SomeMessage -> Maybe m
|
||||
fromMessage (SomeMessage m) = cast m
|
||||
|
||||
-- X Events are valid Messages.
|
||||
instance Message Event
|
||||
|
||||
-- | 'LayoutMessages' are core messages that all layouts (especially stateful
|
||||
-- layouts) should consider handling.
|
||||
data LayoutMessages = Hide -- ^ sent when a layout becomes non-visible
|
||||
| ReleaseResources -- ^ sent when xmonad is exiting or restarting
|
||||
deriving (Typeable, Eq)
|
||||
|
||||
instance Message LayoutMessages
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Extensible state
|
||||
--
|
||||
|
||||
-- | Every module must make the data it wants to store
|
||||
-- an instance of this class.
|
||||
--
|
||||
-- Minimal complete definition: initialValue
|
||||
class Typeable a => ExtensionClass a where
|
||||
-- | Defines an initial value for the state extension
|
||||
initialValue :: a
|
||||
-- | Specifies whether the state extension should be
|
||||
-- persistent. Setting this method to 'PersistentExtension'
|
||||
-- will make the stored data survive restarts, but
|
||||
-- requires a to be an instance of Read and Show.
|
||||
--
|
||||
-- It defaults to 'StateExtension', i.e. no persistence.
|
||||
extensionType :: a -> StateExtension
|
||||
extensionType = StateExtension
|
||||
|
||||
-- | Existential type to store a state extension.
|
||||
data StateExtension =
|
||||
forall a. ExtensionClass a => StateExtension a
|
||||
-- ^ Non-persistent state extension
|
||||
| forall a. (Read a, Show a, ExtensionClass a) => PersistentExtension a
|
||||
-- ^ Persistent extension
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- | General utilities
|
||||
--
|
||||
-- Lift an 'IO' action into the 'X' monad
|
||||
io :: MonadIO m => IO a -> m a
|
||||
io = liftIO
|
||||
|
||||
-- | Lift an 'IO' action into the 'X' monad. If the action results in an 'IO'
|
||||
-- exception, log the exception to stderr and continue normal execution.
|
||||
catchIO :: MonadIO m => IO () -> m ()
|
||||
catchIO f = io (f `E.catch` \(SomeException e) -> hPrint stderr e >> hFlush stderr)
|
||||
|
||||
-- | spawn. Launch an external application. Specifically, it double-forks and
|
||||
-- runs the 'String' you pass as a command to \/bin\/sh.
|
||||
--
|
||||
-- Note this function assumes your locale uses utf8.
|
||||
spawn :: MonadIO m => String -> m ()
|
||||
spawn x = spawnPID x >> return ()
|
||||
|
||||
-- | Like 'spawn', but returns the 'ProcessID' of the launched application
|
||||
spawnPID :: MonadIO m => String -> m ProcessID
|
||||
spawnPID x = xfork $ executeFile "/bin/sh" False ["-c", x] Nothing
|
||||
|
||||
-- | A replacement for 'forkProcess' which resets default signal handlers.
|
||||
xfork :: MonadIO m => IO () -> m ProcessID
|
||||
xfork x = io . forkProcess . finally nullStdin $ do
|
||||
uninstallSignalHandlers
|
||||
createSession
|
||||
x
|
||||
where
|
||||
nullStdin = do
|
||||
fd <- openFd "/dev/null" ReadOnly Nothing defaultFileFlags
|
||||
dupTo fd stdInput
|
||||
closeFd fd
|
||||
|
||||
-- | This is basically a map function, running a function in the 'X' monad on
|
||||
-- each workspace with the output of that function being the modified workspace.
|
||||
runOnWorkspaces :: (WindowSpace -> X WindowSpace) -> X ()
|
||||
runOnWorkspaces job = do
|
||||
ws <- gets windowset
|
||||
h <- mapM job $ hidden ws
|
||||
c:v <- mapM (\s -> (\w -> s { workspace = w}) <$> job (workspace s))
|
||||
$ current ws : visible ws
|
||||
modify $ \s -> s { windowset = ws { current = c, visible = v, hidden = h } }
|
||||
|
||||
-- | Return the path to the xmonad configuration directory. This
|
||||
-- directory is where user configuration files are stored (e.g, the
|
||||
-- xmonad.hs file). You may also create a @lib@ subdirectory in the
|
||||
-- configuration directory and the default recompile command will add
|
||||
-- it to the GHC include path.
|
||||
--
|
||||
-- Several directories are considered. In order of
|
||||
-- preference:
|
||||
--
|
||||
-- 1. The directory specified in the @XMONAD_CONFIG_DIR@ environment variable.
|
||||
-- 2. The @~\/.xmonad@ directory.
|
||||
-- 3. The @XDG_CONFIG_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.
|
||||
getXMonadDir :: MonadIO m => m String
|
||||
getXMonadDir =
|
||||
findFirstDirWithEnv "XMONAD_CONFIG_DIR"
|
||||
[ getAppUserDataDirectory "xmonad"
|
||||
, getXDGDirectory XDGConfig "xmonad"
|
||||
]
|
||||
|
||||
-- | Return the path to the xmonad cache directory. This directory is
|
||||
-- used to store temporary files that can easily be recreated. For
|
||||
-- example, the XPrompt history file.
|
||||
--
|
||||
-- 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"
|
||||
]
|
||||
|
||||
-- | 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.
|
||||
--
|
||||
-- Several directories are considered. In order of preference:
|
||||
--
|
||||
-- 1. The directory specified in the @XMONAD_DATA_DIR@ environment variable.
|
||||
-- 2. The @~\/.xmonad@ directory.
|
||||
-- 3. The @XDG_DATA_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.
|
||||
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
|
||||
|
||||
where
|
||||
go [] = return Nothing
|
||||
go (x:xs) = do
|
||||
dir <- io x
|
||||
exists <- io (doesDirectoryExist dir)
|
||||
if exists then return (Just dir) else go xs
|
||||
|
||||
-- | 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)
|
||||
|
||||
case envPath' of
|
||||
Nothing -> findFirstDirOf paths
|
||||
Just envPath -> findFirstDirOf (return envPath:paths)
|
||||
|
||||
-- | 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"
|
||||
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
|
||||
|
||||
-- | Get the name of the file used to store the xmonad window state.
|
||||
stateFileName :: (Functor m, MonadIO m) => m FilePath
|
||||
stateFileName = (</> "xmonad.state") <$> getXMonadDataDir
|
||||
|
||||
-- | 'recompile force', recompile the xmonad configuration file when
|
||||
-- any of the following apply:
|
||||
--
|
||||
-- * force is 'True'
|
||||
--
|
||||
-- * the xmonad executable does not exist
|
||||
--
|
||||
-- * the xmonad executable is older than xmonad.hs or any file in
|
||||
-- the @lib@ directory (under the configuration directory).
|
||||
--
|
||||
-- The -i flag is used to restrict recompilation to the xmonad.hs file only,
|
||||
-- and any files in the aforementioned @lib@ directory.
|
||||
--
|
||||
-- Compilation errors (if any) are logged to the @xmonad.errors@ file
|
||||
-- in the xmonad data directory. If GHC indicates failure with a
|
||||
-- non-zero exit code, an xmessage displaying that file is spawned.
|
||||
--
|
||||
-- 'False' is returned if there are compilation errors.
|
||||
--
|
||||
recompile :: MonadIO m => 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
|
||||
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:
|
||||
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)
|
||||
|
||||
-- | Conditionally run an action, using a @Maybe a@ to decide.
|
||||
whenJust :: Monad m => Maybe a -> (a -> m ()) -> m ()
|
||||
whenJust mg f = maybe (return ()) f mg
|
||||
|
||||
-- | Conditionally run an action, using a 'X' event to decide
|
||||
whenX :: X Bool -> X () -> X ()
|
||||
whenX a f = a >>= \b -> when b f
|
||||
|
||||
-- | A 'trace' for the 'X' monad. Logs a string to stderr. The result may
|
||||
-- be found in your .xsession-errors file
|
||||
trace :: MonadIO m => String -> m ()
|
||||
trace = io . hPutStrLn stderr
|
||||
|
||||
-- | Ignore SIGPIPE to avoid termination when a pipe is full, and SIGCHLD to
|
||||
-- avoid zombie processes, and clean up any extant zombie processes.
|
||||
installSignalHandlers :: MonadIO m => m ()
|
||||
installSignalHandlers = io $ do
|
||||
installHandler openEndedPipe Ignore Nothing
|
||||
installHandler sigCHLD Ignore Nothing
|
||||
(try :: IO a -> IO (Either SomeException a))
|
||||
$ fix $ \more -> do
|
||||
x <- getAnyProcessStatus False False
|
||||
when (isJust x) more
|
||||
return ()
|
||||
|
||||
uninstallSignalHandlers :: MonadIO m => m ()
|
||||
uninstallSignalHandlers = io $ do
|
||||
installHandler openEndedPipe Default Nothing
|
||||
installHandler sigCHLD Default Nothing
|
||||
return ()
|
210
src/XMonad/Layout.hs
Normal file
210
src/XMonad/Layout.hs
Normal file
@@ -0,0 +1,210 @@
|
||||
{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, PatternGuards, TypeSynonymInstances, DeriveDataTypeable #-}
|
||||
|
||||
-- --------------------------------------------------------------------------
|
||||
-- |
|
||||
-- Module : XMonad.Layout
|
||||
-- Copyright : (c) Spencer Janssen 2007
|
||||
-- License : BSD3-style (see LICENSE)
|
||||
--
|
||||
-- Maintainer : spencerjanssen@gmail.com
|
||||
-- Stability : unstable
|
||||
-- Portability : not portable, Typeable deriving, mtl, posix
|
||||
--
|
||||
-- The collection of core layouts.
|
||||
--
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
module XMonad.Layout (
|
||||
Full(..), Tall(..), Mirror(..),
|
||||
Resize(..), IncMasterN(..), Choose, (|||), ChangeLayout(..),
|
||||
mirrorRect, splitVertically,
|
||||
splitHorizontally, splitHorizontallyBy, splitVerticallyBy,
|
||||
|
||||
tile
|
||||
|
||||
) where
|
||||
|
||||
import XMonad.Core
|
||||
|
||||
import Graphics.X11 (Rectangle(..))
|
||||
import qualified XMonad.StackSet as W
|
||||
import Control.Arrow ((***), second)
|
||||
import Control.Monad
|
||||
import Data.Maybe (fromMaybe)
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
-- | Change the size of the master pane.
|
||||
data Resize = Shrink | Expand deriving Typeable
|
||||
|
||||
-- | Increase the number of clients in the master pane.
|
||||
data IncMasterN = IncMasterN !Int deriving Typeable
|
||||
|
||||
instance Message Resize
|
||||
instance Message IncMasterN
|
||||
|
||||
-- | Simple fullscreen mode. Renders the focused window fullscreen.
|
||||
data Full a = Full deriving (Show, Read)
|
||||
|
||||
instance LayoutClass Full a
|
||||
|
||||
-- | The builtin tiling mode of xmonad. Supports 'Shrink', 'Expand' and
|
||||
-- 'IncMasterN'.
|
||||
data Tall a = Tall { tallNMaster :: !Int -- ^ The default number of windows in the master pane (default: 1)
|
||||
, tallRatioIncrement :: !Rational -- ^ Percent of screen to increment by when resizing panes (default: 3/100)
|
||||
, tallRatio :: !Rational -- ^ Default proportion of screen occupied by master pane (default: 1/2)
|
||||
}
|
||||
deriving (Show, Read)
|
||||
-- TODO should be capped [0..1] ..
|
||||
|
||||
-- a nice pure layout, lots of properties for the layout, and its messages, in Properties.hs
|
||||
instance LayoutClass Tall a where
|
||||
pureLayout (Tall nmaster _ frac) r s = zip ws rs
|
||||
where ws = W.integrate s
|
||||
rs = tile frac r nmaster (length ws)
|
||||
|
||||
pureMessage (Tall nmaster delta frac) m =
|
||||
msum [fmap resize (fromMessage m)
|
||||
,fmap incmastern (fromMessage m)]
|
||||
|
||||
where resize Shrink = Tall nmaster delta (max 0 $ frac-delta)
|
||||
resize Expand = Tall nmaster delta (min 1 $ frac+delta)
|
||||
incmastern (IncMasterN d) = Tall (max 0 (nmaster+d)) delta frac
|
||||
|
||||
description _ = "Tall"
|
||||
|
||||
-- | Compute the positions for windows using the default two-pane tiling
|
||||
-- algorithm.
|
||||
--
|
||||
-- The screen is divided into two panes. All clients are
|
||||
-- then partioned between these two panes. One pane, the master, by
|
||||
-- convention has the least number of windows in it.
|
||||
tile
|
||||
:: Rational -- ^ @frac@, what proportion of the screen to devote to the master area
|
||||
-> Rectangle -- ^ @r@, the rectangle representing the screen
|
||||
-> Int -- ^ @nmaster@, the number of windows in the master pane
|
||||
-> Int -- ^ @n@, the total number of windows to tile
|
||||
-> [Rectangle]
|
||||
tile f r nmaster n = if n <= nmaster || nmaster == 0
|
||||
then splitVertically n r
|
||||
else splitVertically nmaster r1 ++ splitVertically (n-nmaster) r2 -- two columns
|
||||
where (r1,r2) = splitHorizontallyBy f r
|
||||
|
||||
--
|
||||
-- Divide the screen vertically into n subrectangles
|
||||
--
|
||||
splitVertically, splitHorizontally :: Int -> Rectangle -> [Rectangle]
|
||||
splitVertically n r | n < 2 = [r]
|
||||
splitVertically n (Rectangle sx sy sw sh) = Rectangle sx sy sw smallh :
|
||||
splitVertically (n-1) (Rectangle sx (sy+fromIntegral smallh) sw (sh-smallh))
|
||||
where smallh = sh `div` fromIntegral n --hmm, this is a fold or map.
|
||||
|
||||
-- Not used in the core, but exported
|
||||
splitHorizontally n = map mirrorRect . splitVertically n . mirrorRect
|
||||
|
||||
-- Divide the screen into two rectangles, using a rational to specify the ratio
|
||||
splitHorizontallyBy, splitVerticallyBy :: RealFrac r => r -> Rectangle -> (Rectangle, Rectangle)
|
||||
splitHorizontallyBy f (Rectangle sx sy sw sh) =
|
||||
( Rectangle sx sy leftw sh
|
||||
, Rectangle (sx + fromIntegral leftw) sy (sw-fromIntegral leftw) sh)
|
||||
where leftw = floor $ fromIntegral sw * f
|
||||
|
||||
-- Not used in the core, but exported
|
||||
splitVerticallyBy f = (mirrorRect *** mirrorRect) . splitHorizontallyBy f . mirrorRect
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
-- | Mirror a layout, compute its 90 degree rotated form.
|
||||
newtype Mirror l a = Mirror (l a) deriving (Show, Read)
|
||||
|
||||
instance LayoutClass l a => LayoutClass (Mirror l) a where
|
||||
runLayout (W.Workspace i (Mirror l) ms) r = (map (second mirrorRect) *** fmap Mirror)
|
||||
`fmap` runLayout (W.Workspace i l ms) (mirrorRect r)
|
||||
handleMessage (Mirror l) = fmap (fmap Mirror) . handleMessage l
|
||||
description (Mirror l) = "Mirror "++ description l
|
||||
|
||||
-- | Mirror a rectangle.
|
||||
mirrorRect :: Rectangle -> Rectangle
|
||||
mirrorRect (Rectangle rx ry rw rh) = Rectangle ry rx rh rw
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- LayoutClass selection manager
|
||||
-- Layouts that transition between other layouts
|
||||
|
||||
-- | Messages to change the current layout.
|
||||
data ChangeLayout = FirstLayout | NextLayout deriving (Eq, Show, Typeable)
|
||||
|
||||
instance Message ChangeLayout
|
||||
|
||||
-- | The layout choice combinator
|
||||
(|||) :: l a -> r a -> Choose l r a
|
||||
(|||) = Choose L
|
||||
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)
|
||||
|
||||
-- | Are we on the left or right sub-layout?
|
||||
data LR = L | R deriving (Read, Show, Eq)
|
||||
|
||||
data NextNoWrap = NextNoWrap deriving (Eq, Show, Typeable)
|
||||
instance Message NextNoWrap
|
||||
|
||||
-- | A small wrapper around handleMessage, as it is tedious to write
|
||||
-- SomeMessage repeatedly.
|
||||
handle :: (LayoutClass l a, Message m) => l a -> m -> X (Maybe (l a))
|
||||
handle l m = handleMessage l (SomeMessage m)
|
||||
|
||||
-- | A smart constructor that takes some potential modifications, returns a
|
||||
-- new structure if any fields have changed, and performs any necessary cleanup
|
||||
-- on newly non-visible layouts.
|
||||
choose :: (LayoutClass l a, LayoutClass r a)
|
||||
=> Choose l r a-> LR -> 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
|
||||
|
||||
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)
|
||||
|
||||
description (Choose L l _) = description l
|
||||
description (Choose R _ r) = description r
|
||||
|
||||
handleMessage lr m | Just NextLayout <- fromMessage m = do
|
||||
mlr' <- handle lr NextNoWrap
|
||||
maybe (handle lr FirstLayout) (return . Just) mlr'
|
||||
|
||||
handleMessage c@(Choose d l r) m | Just NextNoWrap <- fromMessage m =
|
||||
case d of
|
||||
L -> do
|
||||
ml <- handle l NextNoWrap
|
||||
case ml of
|
||||
Just _ -> choose c L ml Nothing
|
||||
Nothing -> choose c R Nothing =<< handle r FirstLayout
|
||||
|
||||
R -> choose c R Nothing =<< handle r NextNoWrap
|
||||
|
||||
handleMessage c@(Choose _ l _) m | Just FirstLayout <- fromMessage m =
|
||||
flip (choose c L) 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 = do
|
||||
ml' <- case d of
|
||||
L -> handleMessage l m
|
||||
R -> return Nothing
|
||||
mr' <- case d of
|
||||
L -> return Nothing
|
||||
R -> handleMessage r m
|
||||
choose c d ml' mr'
|
523
src/XMonad/Main.hs
Normal file
523
src/XMonad/Main.hs
Normal file
@@ -0,0 +1,523 @@
|
||||
{-# LANGUAGE MultiParamTypeClasses, FlexibleContexts #-}
|
||||
----------------------------------------------------------------------------
|
||||
-- |
|
||||
-- Module : XMonad.Main
|
||||
-- Copyright : (c) Spencer Janssen 2007
|
||||
-- License : BSD3-style (see LICENSE)
|
||||
--
|
||||
-- Maintainer : spencerjanssen@gmail.com
|
||||
-- Stability : unstable
|
||||
-- Portability : not portable, uses mtl, X11, posix
|
||||
--
|
||||
-- xmonad, a minimalist, tiling window manager for X11
|
||||
--
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
module XMonad.Main (xmonad, launch) where
|
||||
|
||||
import System.Locale.SetLocale
|
||||
import qualified Control.Exception.Extensible as E
|
||||
import Data.Bits
|
||||
import Data.List ((\\))
|
||||
import Data.Function
|
||||
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 Data.Monoid (getAll)
|
||||
|
||||
import Graphics.X11.Xlib hiding (refreshKeyboardMapping)
|
||||
import Graphics.X11.Xlib.Extras
|
||||
|
||||
import XMonad.Core
|
||||
import qualified XMonad.Config as Default
|
||||
import XMonad.StackSet (new, floating, member)
|
||||
import qualified XMonad.StackSet as W
|
||||
import XMonad.Operations
|
||||
|
||||
import System.IO
|
||||
import System.Directory
|
||||
import System.Info
|
||||
import System.Environment
|
||||
import System.Posix.Process (executeFile)
|
||||
import System.Exit (exitFailure)
|
||||
import System.FilePath
|
||||
|
||||
import Paths_xmonad (version)
|
||||
import Data.Version (showVersion)
|
||||
|
||||
import Graphics.X11.Xinerama (compiledWithXinerama)
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
|
||||
-- |
|
||||
-- | The entry point into xmonad. Attempts to compile any custom main
|
||||
-- for xmonad, and if it doesn't find one, just launches the default.
|
||||
xmonad :: (LayoutClass l Window, Read (l Window)) => XConfig l -> IO ()
|
||||
xmonad conf = do
|
||||
installSignalHandlers -- important to ignore SIGCHLD to avoid zombies
|
||||
|
||||
let launch' args = do
|
||||
catchIO buildLaunch
|
||||
conf' @ XConfig { layoutHook = Layout l }
|
||||
<- handleExtraArgs conf args conf{ layoutHook = Layout (layoutHook conf) }
|
||||
withArgs [] $ launch (conf' { layoutHook = l })
|
||||
|
||||
args <- getArgs
|
||||
case args of
|
||||
("--resume": ws : xs : args') -> migrateState ws xs >> launch' args'
|
||||
["--help"] -> usage
|
||||
["--recompile"] -> recompile True >>= flip unless exitFailure
|
||||
["--restart"] -> sendRestart
|
||||
["--version"] -> putStrLn $ unwords shortVersion
|
||||
["--verbose-version"] -> putStrLn . unwords $ shortVersion ++ longVersion
|
||||
"--replace" : args' -> sendReplace >> launch' args'
|
||||
_ -> launch' args
|
||||
where
|
||||
shortVersion = ["xmonad", showVersion version]
|
||||
longVersion = [ "compiled by", compilerName, showVersion compilerVersion
|
||||
, "for", arch ++ "-" ++ os
|
||||
, "\nXinerama:", show compiledWithXinerama ]
|
||||
|
||||
|
||||
usage :: IO ()
|
||||
usage = do
|
||||
self <- getProgName
|
||||
putStr . unlines $
|
||||
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" :
|
||||
[]
|
||||
|
||||
-- | Build the xmonad configuration file with ghc, then execute it.
|
||||
-- If there are no errors, this function does not return. An
|
||||
-- exception is raised in any of these cases:
|
||||
--
|
||||
-- * ghc missing
|
||||
--
|
||||
-- * both the configuration file and executable are missing
|
||||
--
|
||||
-- * xmonad.hs fails to compile
|
||||
--
|
||||
-- ** wrong ghc in path (fails to compile)
|
||||
--
|
||||
-- ** type error, syntax error, ..
|
||||
--
|
||||
-- * Missing XMonad\/XMonadContrib modules due to ghc upgrade
|
||||
--
|
||||
buildLaunch :: IO ()
|
||||
buildLaunch = do
|
||||
whoami <- getProgName
|
||||
let compiledConfig = "xmonad-"++arch++"-"++os
|
||||
unless (whoami == compiledConfig) $ do
|
||||
trace $ concat
|
||||
[ "XMonad is recompiling and replacing itself another XMonad process because the current process is called "
|
||||
, show whoami
|
||||
, " but the compiled configuration should be called "
|
||||
, show compiledConfig
|
||||
]
|
||||
recompile False
|
||||
dir <- getXMonadDataDir
|
||||
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
|
||||
|
||||
-- | Entry point into xmonad for custom builds.
|
||||
--
|
||||
-- This function isn't meant to be called by the typical xmonad user
|
||||
-- because it:
|
||||
--
|
||||
-- * Does not process any command line arguments.
|
||||
--
|
||||
-- * Therefore doesn't know how to restart a running xmonad.
|
||||
--
|
||||
-- * Does not compile your configuration file since it assumes it's
|
||||
-- actually running from within your compiled configuration.
|
||||
--
|
||||
-- Unless you know what you are doing, you should probably be using
|
||||
-- the 'xmonad' function instead.
|
||||
--
|
||||
-- However, if you are using a custom build environment (such as
|
||||
-- stack, cabal, make, etc.) you will likely want to call this
|
||||
-- function instead of 'xmonad'. You probably also want to have a key
|
||||
-- binding to the 'XMonad.Operations.restart` function that restarts
|
||||
-- your custom binary with the resume flag set to @True@.
|
||||
launch :: (LayoutClass l Window, Read (l Window)) => XConfig l -> IO ()
|
||||
launch initxmc = do
|
||||
-- setup locale information from environment
|
||||
setLocale LC_ALL (Just "")
|
||||
-- ignore SIGPIPE and SIGCHLD
|
||||
installSignalHandlers
|
||||
-- First, wrap the layout in an existential, to keep things pretty:
|
||||
let xmc = initxmc { layoutHook = Layout $ layoutHook initxmc }
|
||||
dpy <- openDisplay ""
|
||||
let dflt = defaultScreen dpy
|
||||
|
||||
rootw <- rootWindow dpy dflt
|
||||
|
||||
-- If another WM is running, a BadAccess error will be returned. The
|
||||
-- default error handler will write the exception to stderr and exit with
|
||||
-- an error.
|
||||
selectInput dpy rootw $ rootMask initxmc
|
||||
|
||||
sync dpy False -- sync to ensure all outstanding errors are delivered
|
||||
|
||||
-- turn off the default handler in favor of one that ignores all errors
|
||||
-- (ugly, I know)
|
||||
xSetErrorHandler -- in C, I'm too lazy to write the binding: dons
|
||||
|
||||
xinesc <- getCleanedScreenInfo dpy
|
||||
|
||||
nbc <- do v <- initColor dpy $ normalBorderColor xmc
|
||||
~(Just nbc_) <- initColor dpy $ normalBorderColor Default.def
|
||||
return (fromMaybe nbc_ v)
|
||||
|
||||
fbc <- do v <- initColor dpy $ focusedBorderColor xmc
|
||||
~(Just fbc_) <- initColor dpy $ focusedBorderColor Default.def
|
||||
return (fromMaybe fbc_ v)
|
||||
|
||||
hSetBuffering stdout NoBuffering
|
||||
|
||||
let layout = layoutHook xmc
|
||||
initialWinset = let padToLen n xs = take (max n (length xs)) $ xs ++ repeat ""
|
||||
in new layout (padToLen (length xinesc) (workspaces xmc)) $ map SD xinesc
|
||||
|
||||
cf = XConf
|
||||
{ display = dpy
|
||||
, config = xmc
|
||||
, theRoot = rootw
|
||||
, normalBorder = nbc
|
||||
, focusedBorder = fbc
|
||||
, keyActions = keys xmc xmc
|
||||
, buttonActions = mouseBindings xmc xmc
|
||||
, mouseFocused = False
|
||||
, mousePosition = Nothing
|
||||
, currentEvent = Nothing }
|
||||
|
||||
st = XState
|
||||
{ windowset = initialWinset
|
||||
, numberlockMask = 0
|
||||
, mapped = S.empty
|
||||
, waitingUnmap = M.empty
|
||||
, dragging = Nothing
|
||||
, extensibleState = M.empty
|
||||
}
|
||||
|
||||
allocaXEvent $ \e ->
|
||||
runX cf st $ do
|
||||
-- check for serialized state in a file.
|
||||
serializedSt <- do
|
||||
path <- stateFileName
|
||||
exists <- io (doesFileExist path)
|
||||
if exists then readStateFile initxmc else return Nothing
|
||||
|
||||
-- restore extensibleState if we read it from a file.
|
||||
let extst = maybe M.empty extensibleState serializedSt
|
||||
modify (\s -> s {extensibleState = extst})
|
||||
|
||||
setNumlockMask
|
||||
grabKeys
|
||||
grabButtons
|
||||
|
||||
io $ sync dpy False
|
||||
|
||||
ws <- io $ scan dpy rootw
|
||||
|
||||
-- bootstrap the windowset, Operations.windows will identify all
|
||||
-- the windows in winset as new and set initial properties for
|
||||
-- those windows. Remove all windows that are no longer top-level
|
||||
-- children of the root, they may have disappeared since
|
||||
-- restarting.
|
||||
let winset = maybe initialWinset windowset serializedSt
|
||||
windows . const . foldr W.delete winset $ W.allWindows winset \\ ws
|
||||
|
||||
-- manage the as-yet-unmanaged windows
|
||||
mapM_ manage (ws \\ W.allWindows winset)
|
||||
|
||||
userCode $ startupHook initxmc
|
||||
|
||||
-- main loop, for all you HOF/recursion fans out there.
|
||||
forever $ prehandle =<< io (nextEvent dpy e >> getEvent e)
|
||||
|
||||
return ()
|
||||
where
|
||||
-- if the event gives us the position of the pointer, set mousePosition
|
||||
prehandle e = let mouse = do guard (ev_event_type e `elem` evs)
|
||||
return (fromIntegral (ev_x_root e)
|
||||
,fromIntegral (ev_y_root e))
|
||||
in local (\c -> c { mousePosition = mouse, currentEvent = Just e }) (handleWithHook e)
|
||||
evs = [ keyPress, keyRelease, enterNotify, leaveNotify
|
||||
, buttonPress, buttonRelease]
|
||||
|
||||
|
||||
-- | Runs handleEventHook from the configuration and runs the default handler
|
||||
-- function if it returned True.
|
||||
handleWithHook :: Event -> X ()
|
||||
handleWithHook e = do
|
||||
evHook <- asks (handleEventHook . config)
|
||||
whenX (userCodeDef True $ getAll `fmap` evHook e) (handle e)
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- | Event handler. Map X events onto calls into Operations.hs, which
|
||||
-- modify our internal model of the window manager state.
|
||||
--
|
||||
-- Events dwm handles that we don't:
|
||||
--
|
||||
-- [ButtonPress] = buttonpress,
|
||||
-- [Expose] = expose,
|
||||
-- [PropertyNotify] = propertynotify,
|
||||
--
|
||||
handle :: Event -> X ()
|
||||
|
||||
-- run window manager command
|
||||
handle (KeyEvent {ev_event_type = t, ev_state = m, ev_keycode = code})
|
||||
| t == keyPress = withDisplay $ \dpy -> do
|
||||
s <- io $ keycodeToKeysym dpy code 0
|
||||
mClean <- cleanMask m
|
||||
ks <- asks keyActions
|
||||
userCodeDef () $ whenJust (M.lookup (mClean, s) ks) id
|
||||
|
||||
-- manage a new window
|
||||
handle (MapRequestEvent {ev_window = w}) = withDisplay $ \dpy -> do
|
||||
withWindowAttributes dpy w $ \wa -> do -- ignore override windows
|
||||
-- need to ignore mapping requests by managed windows not on the current workspace
|
||||
managed <- isClient w
|
||||
when (not (wa_override_redirect wa) && not managed) $ manage w
|
||||
|
||||
-- window destroyed, unmanage it
|
||||
-- window gone, unmanage it
|
||||
handle (DestroyWindowEvent {ev_window = w}) = whenX (isClient w) $ do
|
||||
unmanage w
|
||||
modify (\s -> s { mapped = S.delete w (mapped s)
|
||||
, waitingUnmap = M.delete w (waitingUnmap s)})
|
||||
|
||||
-- We track expected unmap events in waitingUnmap. We ignore this event unless
|
||||
-- it is synthetic or we are not expecting an unmap notification from a window.
|
||||
handle (UnmapEvent {ev_window = w, ev_send_event = synthetic}) = whenX (isClient w) $ do
|
||||
e <- gets (fromMaybe 0 . M.lookup w . waitingUnmap)
|
||||
if (synthetic || e == 0)
|
||||
then unmanage w
|
||||
else modify (\s -> s { waitingUnmap = M.update mpred w (waitingUnmap s) })
|
||||
where mpred 1 = Nothing
|
||||
mpred n = Just $ pred n
|
||||
|
||||
-- set keyboard mapping
|
||||
handle e@(MappingNotifyEvent {}) = do
|
||||
io $ refreshKeyboardMapping e
|
||||
when (ev_request e `elem` [mappingKeyboard, mappingModifier]) $ do
|
||||
setNumlockMask
|
||||
grabKeys
|
||||
|
||||
-- handle button release, which may finish dragging.
|
||||
handle e@(ButtonEvent {ev_event_type = t})
|
||||
| t == buttonRelease = do
|
||||
drag <- gets dragging
|
||||
case drag of
|
||||
-- we're done dragging and have released the mouse:
|
||||
Just (_,f) -> modify (\s -> s { dragging = Nothing }) >> f
|
||||
Nothing -> broadcastMessage e
|
||||
|
||||
-- handle motionNotify event, which may mean we are dragging.
|
||||
handle e@(MotionEvent {ev_event_type = _t, ev_x = x, ev_y = y}) = do
|
||||
drag <- gets dragging
|
||||
case drag of
|
||||
Just (d,_) -> d (fromIntegral x) (fromIntegral y) -- we're dragging
|
||||
Nothing -> broadcastMessage e
|
||||
|
||||
-- click on an unfocused window, makes it focused on this workspace
|
||||
handle e@(ButtonEvent {ev_window = w,ev_event_type = t,ev_button = b })
|
||||
| t == buttonPress = do
|
||||
-- If it's the root window, then it's something we
|
||||
-- grabbed in grabButtons. Otherwise, it's click-to-focus.
|
||||
dpy <- asks display
|
||||
isr <- isRoot w
|
||||
m <- cleanMask $ ev_state e
|
||||
mact <- asks (M.lookup (m, b) . buttonActions)
|
||||
case mact of
|
||||
Just act | isr -> act $ ev_subwindow e
|
||||
_ -> do
|
||||
focus w
|
||||
ctf <- asks (clickJustFocuses . config)
|
||||
unless ctf $ io (allowEvents dpy replayPointer currentTime)
|
||||
broadcastMessage e -- Always send button events.
|
||||
|
||||
-- entered a normal window: focus it if focusFollowsMouse is set to
|
||||
-- True in the user's config.
|
||||
handle e@(CrossingEvent {ev_window = w, ev_event_type = t})
|
||||
| t == enterNotify && ev_mode e == notifyNormal
|
||||
= whenX (asks $ focusFollowsMouse . config) $ do
|
||||
dpy <- asks display
|
||||
root <- asks theRoot
|
||||
(_, _, w', _, _, _, _, _) <- io $ queryPointer dpy root
|
||||
-- when Xlib cannot find a child that contains the pointer,
|
||||
-- it returns None(0)
|
||||
when (w' == 0 || w == w') (focus w)
|
||||
|
||||
-- left a window, check if we need to focus root
|
||||
handle e@(CrossingEvent {ev_event_type = t})
|
||||
| t == leaveNotify
|
||||
= do rootw <- asks theRoot
|
||||
when (ev_window e == rootw && not (ev_same_screen e)) $ setFocusX rootw
|
||||
|
||||
-- configure a window
|
||||
handle e@(ConfigureRequestEvent {ev_window = w}) = withDisplay $ \dpy -> do
|
||||
ws <- gets windowset
|
||||
bw <- asks (borderWidth . config)
|
||||
|
||||
if M.member w (floating ws)
|
||||
|| not (member w ws)
|
||||
then do io $ configureWindow dpy w (ev_value_mask e) $ WindowChanges
|
||||
{ wc_x = ev_x e
|
||||
, wc_y = ev_y e
|
||||
, wc_width = ev_width e
|
||||
, wc_height = ev_height e
|
||||
, wc_border_width = fromIntegral bw
|
||||
, wc_sibling = ev_above e
|
||||
, wc_stack_mode = ev_detail e }
|
||||
when (member w ws) (float w)
|
||||
else withWindowAttributes dpy w $ \wa -> io $ allocaXEvent $ \ev -> do
|
||||
setEventType ev configureNotify
|
||||
setConfigureEvent ev w w
|
||||
(wa_x wa) (wa_y wa) (wa_width wa)
|
||||
(wa_height wa) (ev_border_width e) none (wa_override_redirect wa)
|
||||
sendEvent dpy w False 0 ev
|
||||
io $ sync dpy False
|
||||
|
||||
-- configuration changes in the root may mean display settings have changed
|
||||
handle (ConfigureEvent {ev_window = w}) = whenX (isRoot w) rescreen
|
||||
|
||||
-- property notify
|
||||
handle event@(PropertyEvent { ev_event_type = t, ev_atom = a })
|
||||
| t == propertyNotify && a == wM_NAME = asks (logHook . config) >>= userCodeDef () >>
|
||||
broadcastMessage event
|
||||
|
||||
handle e@ClientMessageEvent { ev_message_type = mt } = do
|
||||
a <- getAtom "XMONAD_RESTART"
|
||||
if (mt == a)
|
||||
then restart "xmonad" True
|
||||
else broadcastMessage e
|
||||
|
||||
handle e = broadcastMessage e -- trace (eventName e) -- ignoring
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- IO stuff. Doesn't require any X state
|
||||
-- Most of these things run only on startup (bar grabkeys)
|
||||
|
||||
-- | scan for any new windows to manage. If they're already managed,
|
||||
-- this should be idempotent.
|
||||
scan :: Display -> Window -> IO [Window]
|
||||
scan dpy rootw = do
|
||||
(_, _, ws) <- queryTree dpy rootw
|
||||
filterM (\w -> ok w `E.catch` skip) ws
|
||||
-- TODO: scan for windows that are either 'IsViewable' or where WM_STATE ==
|
||||
-- Iconic
|
||||
where ok w = do wa <- getWindowAttributes dpy w
|
||||
a <- internAtom dpy "WM_STATE" False
|
||||
p <- getWindowProperty32 dpy a w
|
||||
let ic = case p of
|
||||
Just (3:_) -> True -- 3 for iconified
|
||||
_ -> False
|
||||
return $ not (wa_override_redirect wa)
|
||||
&& (wa_map_state wa == waIsViewable || ic)
|
||||
|
||||
skip :: E.SomeException -> IO Bool
|
||||
skip _ = return False
|
||||
|
||||
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
|
||||
|
||||
-- | Grab the buttons
|
||||
grabButtons :: X ()
|
||||
grabButtons = do
|
||||
XConf { display = dpy, theRoot = rootw } <- ask
|
||||
let grab button mask = io $ grabButton dpy button mask rootw False buttonPressMask
|
||||
grabModeAsync grabModeSync none none
|
||||
io $ ungrabButton dpy anyButton anyModifier rootw
|
||||
ems <- extraModifiers
|
||||
ba <- asks buttonActions
|
||||
mapM_ (\(m,b) -> mapM_ (grab b . (m .|.)) ems) (M.keys $ ba)
|
||||
|
||||
-- | @replace@ to signals compliant window managers to exit.
|
||||
replace :: Display -> ScreenNumber -> Window -> IO ()
|
||||
replace dpy dflt rootw = do
|
||||
-- check for other WM
|
||||
wmSnAtom <- internAtom dpy ("WM_S" ++ show dflt) False
|
||||
currentWmSnOwner <- xGetSelectionOwner dpy wmSnAtom
|
||||
when (currentWmSnOwner /= 0) $ do
|
||||
-- prepare to receive destroyNotify for old WM
|
||||
selectInput dpy currentWmSnOwner structureNotifyMask
|
||||
|
||||
-- create off-screen window
|
||||
netWmSnOwner <- allocaSetWindowAttributes $ \attributes -> do
|
||||
set_override_redirect attributes True
|
||||
set_event_mask attributes propertyChangeMask
|
||||
let screen = defaultScreenOfDisplay dpy
|
||||
visual = defaultVisualOfScreen screen
|
||||
attrmask = cWOverrideRedirect .|. cWEventMask
|
||||
createWindow dpy rootw (-100) (-100) 1 1 0 copyFromParent copyFromParent visual attrmask attributes
|
||||
|
||||
-- try to acquire wmSnAtom, this should signal the old WM to terminate
|
||||
xSetSelectionOwner dpy wmSnAtom netWmSnOwner currentTime
|
||||
|
||||
-- SKIPPED: check if we acquired the selection
|
||||
-- SKIPPED: send client message indicating that we are now the WM
|
||||
|
||||
-- wait for old WM to go away
|
||||
fix $ \again -> do
|
||||
evt <- allocaXEvent $ \event -> do
|
||||
windowEvent dpy currentWmSnOwner structureNotifyMask event
|
||||
get_EventType event
|
||||
|
||||
when (evt /= destroyNotify) again
|
119
src/XMonad/ManageHook.hs
Normal file
119
src/XMonad/ManageHook.hs
Normal file
@@ -0,0 +1,119 @@
|
||||
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- |
|
||||
-- Module : XMonad.ManageHook
|
||||
-- Copyright : (c) Spencer Janssen 2007
|
||||
-- License : BSD3-style (see LICENSE)
|
||||
--
|
||||
-- Maintainer : spencerjanssen@gmail.com
|
||||
-- Stability : unstable
|
||||
-- Portability : not portable, uses cunning newtype deriving
|
||||
--
|
||||
-- An EDSL for ManageHooks
|
||||
--
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
-- XXX examples required
|
||||
|
||||
module XMonad.ManageHook where
|
||||
|
||||
import XMonad.Core
|
||||
import Graphics.X11.Xlib.Extras
|
||||
import Graphics.X11.Xlib (Display, Window, internAtom, wM_NAME)
|
||||
import Control.Exception.Extensible (bracket, SomeException(..))
|
||||
import qualified Control.Exception.Extensible as E
|
||||
import Control.Monad.Reader
|
||||
import Data.Maybe
|
||||
import Data.Monoid
|
||||
import qualified XMonad.StackSet as W
|
||||
import XMonad.Operations (floatLocation, reveal)
|
||||
|
||||
-- | Lift an 'X' action to a 'Query'.
|
||||
liftX :: X a -> Query a
|
||||
liftX = Query . lift
|
||||
|
||||
-- | The identity hook that returns the WindowSet unchanged.
|
||||
idHook :: Monoid m => m
|
||||
idHook = mempty
|
||||
|
||||
-- | Infix 'mappend'. Compose two 'ManageHook' from right to left.
|
||||
(<+>) :: Monoid m => m -> m -> m
|
||||
(<+>) = mappend
|
||||
|
||||
-- | Compose the list of 'ManageHook's.
|
||||
composeAll :: Monoid m => [m] -> m
|
||||
composeAll = mconcat
|
||||
|
||||
infix 0 -->
|
||||
|
||||
-- | @p --> x@. If @p@ returns 'True', execute the 'ManageHook'.
|
||||
--
|
||||
-- > (-->) :: Monoid m => Query Bool -> Query m -> Query m -- a simpler type
|
||||
(-->) :: (Monad m, Monoid a) => m Bool -> m a -> m a
|
||||
p --> f = p >>= \b -> if b then f else return mempty
|
||||
|
||||
-- | @q =? x@. if the result of @q@ equals @x@, return 'True'.
|
||||
(=?) :: Eq a => Query a -> a -> Query Bool
|
||||
q =? x = fmap (== x) q
|
||||
|
||||
infixr 3 <&&>, <||>
|
||||
|
||||
-- | '&&' lifted to a 'Monad'.
|
||||
(<&&>) :: Monad m => m Bool -> m Bool -> m Bool
|
||||
(<&&>) = liftM2 (&&)
|
||||
|
||||
-- | '||' lifted to a 'Monad'.
|
||||
(<||>) :: Monad m => m Bool -> m Bool -> m Bool
|
||||
(<||>) = liftM2 (||)
|
||||
|
||||
-- | Return the window title.
|
||||
title :: Query String
|
||||
title = ask >>= \w -> liftX $ do
|
||||
d <- asks display
|
||||
let
|
||||
getProp =
|
||||
(internAtom d "_NET_WM_NAME" False >>= getTextProperty d w)
|
||||
`E.catch` \(SomeException _) -> getTextProperty d w wM_NAME
|
||||
extract prop = do l <- wcTextPropertyToTextList d prop
|
||||
return $ if null l then "" else head l
|
||||
io $ bracket getProp (xFree . tp_value) extract `E.catch` \(SomeException _) -> return ""
|
||||
|
||||
-- | Return the application name.
|
||||
appName :: Query String
|
||||
appName = ask >>= (\w -> liftX $ withDisplay $ \d -> fmap resName $ io $ getClassHint d w)
|
||||
|
||||
-- | Backwards compatible alias for 'appName'.
|
||||
resource :: Query String
|
||||
resource = appName
|
||||
|
||||
-- | Return the resource class.
|
||||
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)
|
||||
|
||||
getStringProperty :: Display -> Window -> String -> X (Maybe String)
|
||||
getStringProperty d w p = do
|
||||
a <- getAtom p
|
||||
md <- io $ getWindowProperty8 d a w
|
||||
return $ fmap (map (toEnum . fromIntegral)) md
|
||||
|
||||
-- | Modify the 'WindowSet' with a pure function.
|
||||
doF :: (s -> s) -> Query (Endo s)
|
||||
doF = return . Endo
|
||||
|
||||
-- | Move the window to the floating layer.
|
||||
doFloat :: ManageHook
|
||||
doFloat = ask >>= \w -> doF . W.float w . snd =<< liftX (floatLocation w)
|
||||
|
||||
-- | Map the window and remove it from the 'WindowSet'.
|
||||
doIgnore :: ManageHook
|
||||
doIgnore = ask >>= \w -> liftX (reveal w) >> doF (W.delete w)
|
||||
|
||||
-- | Move the window to a given workspace
|
||||
doShift :: WorkspaceId -> ManageHook
|
||||
doShift i = doF . W.shiftWin i =<< ask
|
690
src/XMonad/Operations.hs
Normal file
690
src/XMonad/Operations.hs
Normal file
@@ -0,0 +1,690 @@
|
||||
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||||
{-# LANGUAGE FlexibleContexts, FlexibleInstances, MultiParamTypeClasses, PatternGuards, TypeSynonymInstances #-}
|
||||
-- --------------------------------------------------------------------------
|
||||
-- |
|
||||
-- Module : XMonad.Operations
|
||||
-- Copyright : (c) Spencer Janssen 2007
|
||||
-- License : BSD3-style (see LICENSE)
|
||||
--
|
||||
-- Maintainer : dons@cse.unsw.edu.au
|
||||
-- Stability : unstable
|
||||
-- Portability : not portable, Typeable deriving, mtl, posix
|
||||
--
|
||||
-- Operations.
|
||||
--
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
module XMonad.Operations where
|
||||
|
||||
import XMonad.Core
|
||||
import XMonad.Layout (Full(..))
|
||||
import qualified XMonad.StackSet as W
|
||||
|
||||
import Data.Maybe
|
||||
import Data.Monoid (Endo(..))
|
||||
import Data.List (nub, (\\), find)
|
||||
import Data.Bits ((.|.), (.&.), complement, testBit)
|
||||
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.Reader
|
||||
import Control.Monad.State
|
||||
import qualified Control.Exception.Extensible as C
|
||||
|
||||
import System.IO
|
||||
import System.Directory
|
||||
import System.Posix.Process (executeFile)
|
||||
import Graphics.X11.Xlib
|
||||
import Graphics.X11.Xinerama (getScreenInfo)
|
||||
import Graphics.X11.Xlib.Extras
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- |
|
||||
-- Window manager operations
|
||||
-- manage. Add a new window to be managed in the current workspace.
|
||||
-- Bring it into focus.
|
||||
--
|
||||
-- Whether the window is already managed, or not, it is mapped, has its
|
||||
-- border set, and its event mask set.
|
||||
--
|
||||
manage :: Window -> X ()
|
||||
manage w = whenX (not <$> isClient w) $ withDisplay $ \d -> do
|
||||
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)
|
||||
|
||||
rr <- snd `fmap` floatLocation w
|
||||
-- ensure that float windows don't go over the edge of the screen
|
||||
let adjust (W.RationalRect x y wid h) | x + wid > 1 || y + h > 1 || x < 0 || y < 0
|
||||
= W.RationalRect (0.5 - wid/2) (0.5 - h/2) wid h
|
||||
adjust r = r
|
||||
|
||||
f ws | isFixedSize || isTransient = 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
|
||||
-- list, on whatever workspace it is.
|
||||
--
|
||||
unmanage :: Window -> X ()
|
||||
unmanage = windows . W.delete
|
||||
|
||||
-- | Kill the specified window. If we do kill it, we'll get a
|
||||
-- delete notify back from X.
|
||||
--
|
||||
-- There are two ways to delete a window. Either just kill it, or if it
|
||||
-- supports the delete protocol, send a delete event (e.g. firefox)
|
||||
--
|
||||
killWindow :: Window -> X ()
|
||||
killWindow w = withDisplay $ \d -> do
|
||||
wmdelt <- atom_WM_DELETE_WINDOW ; wmprot <- atom_WM_PROTOCOLS
|
||||
|
||||
protocols <- io $ getWMProtocols d w
|
||||
io $ if wmdelt `elem` protocols
|
||||
then allocaXEvent $ \ev -> do
|
||||
setEventType ev clientMessage
|
||||
setClientMessageEvent ev w wmprot 32 wmdelt 0
|
||||
sendEvent d w False noEventMask ev
|
||||
else killClient d w >> return ()
|
||||
|
||||
-- | Kill the currently focused client.
|
||||
kill :: X ()
|
||||
kill = withFocused killWindow
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Managing windows
|
||||
|
||||
-- | windows. Modify the current window list with a pure function, and refresh
|
||||
windows :: (WindowSet -> WindowSet) -> X ()
|
||||
windows f = do
|
||||
XState { windowset = old } <- get
|
||||
let oldvisible = concatMap (W.integrate' . W.stack . W.workspace) $ W.current old : W.visible old
|
||||
newwindows = W.allWindows ws \\ W.allWindows old
|
||||
ws = f old
|
||||
XConf { display = d , normalBorder = nbc, focusedBorder = fbc } <- ask
|
||||
|
||||
mapM_ setInitialProperties newwindows
|
||||
|
||||
whenJust (W.peek old) $ \otherw -> do
|
||||
nbs <- asks (normalBorderColor . config)
|
||||
setWindowBorderWithFallback d otherw nbs nbc
|
||||
|
||||
modify (\s -> s { windowset = ws })
|
||||
|
||||
-- notify non visibility
|
||||
let tags_oldvisible = map (W.tag . W.workspace) $ W.current old : W.visible old
|
||||
gottenhidden = filter (flip elem tags_oldvisible . W.tag) $ W.hidden ws
|
||||
mapM_ (sendMessageWithNoRefresh Hide) gottenhidden
|
||||
|
||||
-- for each workspace, layout the currently visible workspaces
|
||||
let allscreens = W.screens ws
|
||||
summed_visible = scanl (++) [] $ map (W.integrate' . W.stack . W.workspace) allscreens
|
||||
rects <- fmap concat $ forM (zip allscreens summed_visible) $ \ (w, vis) -> do
|
||||
let wsp = W.workspace w
|
||||
this = W.view n ws
|
||||
n = W.tag wsp
|
||||
tiled = (W.stack . W.workspace . W.current $ this)
|
||||
>>= W.filter (`M.notMember` W.floating ws)
|
||||
>>= W.filter (`notElem` vis)
|
||||
viewrect = screenRect $ W.screenDetail w
|
||||
|
||||
-- just the tiled windows:
|
||||
-- now tile the windows on this workspace, modified by the gap
|
||||
(rs, ml') <- runLayout wsp { W.stack = tiled } viewrect `catchX`
|
||||
runLayout wsp { W.stack = tiled, W.layout = Layout Full } viewrect
|
||||
updateLayout n ml'
|
||||
|
||||
let m = W.floating ws
|
||||
flt = [(fw, scaleRationalRect viewrect r)
|
||||
| fw <- filter (flip M.member m) (W.index this)
|
||||
, Just r <- [M.lookup fw m]]
|
||||
vs = flt ++ rs
|
||||
|
||||
io $ restackWindows d (map fst vs)
|
||||
-- return the visible windows for this workspace:
|
||||
return vs
|
||||
|
||||
let visible = map fst rects
|
||||
|
||||
mapM_ (uncurry tileWindow) rects
|
||||
|
||||
whenJust (W.peek ws) $ \w -> do
|
||||
fbs <- asks (focusedBorderColor . config)
|
||||
setWindowBorderWithFallback d w fbs fbc
|
||||
|
||||
mapM_ reveal visible
|
||||
setTopFocus
|
||||
|
||||
-- hide every window that was potentially visible before, but is not
|
||||
-- given a position by a layout now.
|
||||
mapM_ hide (nub (oldvisible ++ newwindows) \\ visible)
|
||||
|
||||
-- all windows that are no longer in the windowset are marked as
|
||||
-- withdrawn, it is important to do this after the above, otherwise 'hide'
|
||||
-- will overwrite withdrawnState with iconicState
|
||||
mapM_ (flip setWMState withdrawnState) (W.allWindows old \\ W.allWindows ws)
|
||||
|
||||
isMouseFocused <- asks mouseFocused
|
||||
unless isMouseFocused $ clearEvents enterWindowMask
|
||||
asks (logHook . config) >>= userCodeDef ()
|
||||
|
||||
-- | 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
|
||||
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@.
|
||||
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
|
||||
setWindowBorder dpy w pixel
|
||||
where
|
||||
fallback :: C.SomeException -> IO ()
|
||||
fallback e = do hPrint stderr e >> hFlush stderr
|
||||
setWindowBorder dpy w basic
|
||||
|
||||
-- | hide. Hide a window by unmapping it, and setting Iconified.
|
||||
hide :: Window -> X ()
|
||||
hide w = whenX (gets (S.member w . mapped)) $ withDisplay $ \d -> do
|
||||
cMask <- asks $ clientMask . config
|
||||
io $ do selectInput d w (cMask .&. complement structureNotifyMask)
|
||||
unmapWindow d w
|
||||
selectInput d w cMask
|
||||
setWMState w iconicState
|
||||
-- this part is key: we increment the waitingUnmap counter to distinguish
|
||||
-- between client and xmonad initiated unmaps.
|
||||
modify (\s -> s { waitingUnmap = M.insertWith (+) w 1 (waitingUnmap s)
|
||||
, mapped = S.delete w (mapped s) })
|
||||
|
||||
-- | reveal. Show a window by mapping it and setting Normal
|
||||
-- this is harmless if the window was already visible
|
||||
reveal :: Window -> X ()
|
||||
reveal w = withDisplay $ \d -> do
|
||||
setWMState w normalState
|
||||
io $ mapWindow d w
|
||||
whenX (isClient w) $ modify (\s -> s { mapped = S.insert w (mapped s) })
|
||||
|
||||
-- | Set some properties when we initially gain control of a window
|
||||
setInitialProperties :: Window -> X ()
|
||||
setInitialProperties w = asks normalBorder >>= \nb -> withDisplay $ \d -> do
|
||||
setWMState w iconicState
|
||||
asks (clientMask . config) >>= io . selectInput d w
|
||||
bw <- asks (borderWidth . config)
|
||||
io $ setWindowBorderWidth d w bw
|
||||
-- we must initially set the color of new windows, to maintain invariants
|
||||
-- required by the border setting in 'windows'
|
||||
io $ setWindowBorder d w nb
|
||||
|
||||
-- | refresh. Render the currently visible workspaces, as determined by
|
||||
-- the 'StackSet'. Also, set focus to the focused window.
|
||||
--
|
||||
-- This is our 'view' operation (MVC), in that it pretty prints our model
|
||||
-- with X calls.
|
||||
--
|
||||
refresh :: X ()
|
||||
refresh = windows id
|
||||
|
||||
-- | clearEvents. Remove all events of a given type from the event queue.
|
||||
clearEvents :: EventMask -> X ()
|
||||
clearEvents mask = withDisplay $ \d -> io $ do
|
||||
sync d False
|
||||
allocaXEvent $ \p -> fix $ \again -> do
|
||||
more <- checkMaskEvent d mask p
|
||||
when more again -- beautiful
|
||||
|
||||
-- | tileWindow. Moves and resizes w such that it fits inside the given
|
||||
-- rectangle, including its border.
|
||||
tileWindow :: Window -> Rectangle -> X ()
|
||||
tileWindow w r = withDisplay $ \d -> withWindowAttributes d w $ \wa -> do
|
||||
-- give all windows at least 1x1 pixels
|
||||
let bw = fromIntegral $ wa_border_width wa
|
||||
least x | x <= bw*2 = 1
|
||||
| otherwise = x - bw*2
|
||||
io $ moveResizeWindow d w (rect_x r) (rect_y r)
|
||||
(least $ rect_width r) (least $ rect_height r)
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
|
||||
-- | Returns 'True' if the first rectangle is contained within, but not equal
|
||||
-- to the second.
|
||||
containedIn :: Rectangle -> Rectangle -> Bool
|
||||
containedIn r1@(Rectangle x1 y1 w1 h1) r2@(Rectangle x2 y2 w2 h2)
|
||||
= and [ r1 /= r2
|
||||
, x1 >= x2
|
||||
, y1 >= y2
|
||||
, fromIntegral x1 + w1 <= fromIntegral x2 + w2
|
||||
, fromIntegral y1 + h1 <= fromIntegral y2 + h2 ]
|
||||
|
||||
-- | Given a list of screens, remove all duplicated screens and screens that
|
||||
-- are entirely contained within another.
|
||||
nubScreens :: [Rectangle] -> [Rectangle]
|
||||
nubScreens xs = nub . filter (\x -> not $ any (x `containedIn`) xs) $ xs
|
||||
|
||||
-- | Cleans 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.
|
||||
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 }
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
|
||||
-- | setButtonGrab. Tell whether or not to intercept clicks on a given window
|
||||
setButtonGrab :: Bool -> Window -> X ()
|
||||
setButtonGrab grab w = do
|
||||
pointerMode <- asks $ \c -> if clickJustFocuses (config c)
|
||||
then grabModeAsync
|
||||
else grabModeSync
|
||||
withDisplay $ \d -> io $ if grab
|
||||
then forM_ [button1, button2, button3] $ \b ->
|
||||
grabButton d b anyModifier w False buttonPressMask
|
||||
pointerMode grabModeSync none none
|
||||
else ungrabButton d anyButton anyModifier w
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Setting keyboard focus
|
||||
|
||||
-- | Set the focus to the window on top of the stack, or root
|
||||
setTopFocus :: X ()
|
||||
setTopFocus = withWindowSet $ maybe (setFocusX =<< asks theRoot) setFocusX . W.peek
|
||||
|
||||
-- | Set focus explicitly to window 'w' if it is managed by us, or root.
|
||||
-- This happens if X notices we've moved the mouse (and perhaps moved
|
||||
-- the mouse to a new screen).
|
||||
focus :: Window -> X ()
|
||||
focus w = local (\c -> c { mouseFocused = True }) $ withWindowSet $ \s -> do
|
||||
let stag = W.tag . W.workspace
|
||||
curr = stag $ W.current s
|
||||
mnew <- maybe (return Nothing) (fmap (fmap stag) . uncurry pointScreen)
|
||||
=<< asks mousePosition
|
||||
root <- asks theRoot
|
||||
case () of
|
||||
_ | W.member w s && W.peek s /= Just w -> windows (W.focusWindow w)
|
||||
| Just new <- mnew, w == root && curr /= new
|
||||
-> windows (W.view new)
|
||||
| otherwise -> return ()
|
||||
|
||||
-- | Call X to set the keyboard focus details.
|
||||
setFocusX :: Window -> X ()
|
||||
setFocusX w = withWindowSet $ \ws -> do
|
||||
dpy <- asks display
|
||||
|
||||
-- clear mouse button grab and border on other windows
|
||||
forM_ (W.current ws : W.visible ws) $ \wk ->
|
||||
forM_ (W.index (W.view (W.tag (W.workspace wk)) ws)) $ \otherw ->
|
||||
setButtonGrab True otherw
|
||||
|
||||
-- If we ungrab buttons on the root window, we lose our mouse bindings.
|
||||
whenX (not <$> isRoot w) $ setButtonGrab False w
|
||||
|
||||
hints <- io $ getWMHints dpy w
|
||||
protocols <- io $ getWMProtocols dpy w
|
||||
wmprot <- atom_WM_PROTOCOLS
|
||||
wmtf <- atom_WM_TAKE_FOCUS
|
||||
currevt <- asks currentEvent
|
||||
let inputHintSet = wmh_flags hints `testBit` inputHintBit
|
||||
|
||||
when ((inputHintSet && wmh_input hints) || (not inputHintSet)) $
|
||||
io $ do setInputFocus dpy w revertToPointerRoot 0
|
||||
when (wmtf `elem` protocols) $
|
||||
io $ allocaXEvent $ \ev -> do
|
||||
setEventType ev clientMessage
|
||||
setClientMessageEvent ev w wmprot 32 wmtf $ maybe currentTime event_time currevt
|
||||
sendEvent dpy w False noEventMask ev
|
||||
where event_time ev =
|
||||
if (ev_event_type ev) `elem` timedEvents then
|
||||
ev_time ev
|
||||
else
|
||||
currentTime
|
||||
timedEvents = [ keyPress, keyRelease, buttonPress, buttonRelease, enterNotify, leaveNotify, selectionRequest ]
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Message handling
|
||||
|
||||
-- | Throw a message to the current 'LayoutClass' possibly modifying how we
|
||||
-- layout the windows, then refresh.
|
||||
sendMessage :: Message a => a -> X ()
|
||||
sendMessage a = do
|
||||
w <- W.workspace . W.current <$> gets windowset
|
||||
ml' <- handleMessage (W.layout w) (SomeMessage a) `catchX` return Nothing
|
||||
whenJust ml' $ \l' ->
|
||||
windows $ \ws -> ws { W.current = (W.current ws)
|
||||
{ W.workspace = (W.workspace $ W.current ws)
|
||||
{ W.layout = l' }}}
|
||||
|
||||
-- | 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)
|
||||
|
||||
-- | Send a message to a layout, without refreshing.
|
||||
sendMessageWithNoRefresh :: Message a => a -> W.Workspace WorkspaceId (Layout Window) Window -> X ()
|
||||
sendMessageWithNoRefresh a w =
|
||||
handleMessage (W.layout w) (SomeMessage a) `catchX` return Nothing >>=
|
||||
updateLayout (W.tag w)
|
||||
|
||||
-- | Update the layout field of a workspace
|
||||
updateLayout :: WorkspaceId -> Maybe (Layout Window) -> X ()
|
||||
updateLayout i ml = whenJust ml $ \l ->
|
||||
runOnWorkspaces $ \ww -> return $ if W.tag ww == i then ww { W.layout = l} else ww
|
||||
|
||||
-- | Set the layout of the currently viewed workspace
|
||||
setLayout :: Layout Window -> X ()
|
||||
setLayout l = do
|
||||
ss@(W.StackSet { W.current = c@(W.Screen { W.workspace = ws })}) <- gets windowset
|
||||
handleMessage (W.layout ws) (SomeMessage ReleaseResources)
|
||||
windows $ const $ ss {W.current = c { W.workspace = ws { W.layout = l } } }
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Utilities
|
||||
|
||||
-- | Return workspace visible on screen 'sc', or 'Nothing'.
|
||||
screenWorkspace :: ScreenId -> X (Maybe WorkspaceId)
|
||||
screenWorkspace sc = withWindowSet $ return . W.lookupWorkspace sc
|
||||
|
||||
-- | Apply an 'X' operation to the currently focused window, if there is one.
|
||||
withFocused :: (Window -> X ()) -> X ()
|
||||
withFocused f = withWindowSet $ \w -> whenJust (W.peek w) f
|
||||
|
||||
-- | 'True' if window is under management by us
|
||||
isClient :: Window -> X Bool
|
||||
isClient w = withWindowSet $ return . W.member w
|
||||
|
||||
-- | Combinations of extra modifier masks we need to grab keys\/buttons for.
|
||||
-- (numlock and capslock)
|
||||
extraModifiers :: X [KeyMask]
|
||||
extraModifiers = do
|
||||
nlm <- gets numberlockMask
|
||||
return [0, nlm, lockMask, nlm .|. lockMask ]
|
||||
|
||||
-- | Strip numlock\/capslock from a mask
|
||||
cleanMask :: KeyMask -> X KeyMask
|
||||
cleanMask km = do
|
||||
nlm <- gets numberlockMask
|
||||
return (complement (nlm .|. lockMask) .&. km)
|
||||
|
||||
-- | 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
|
||||
where colormap = defaultColormap dpy (defaultScreen dpy)
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
-- | A type to help serialize xmonad's state to a file.
|
||||
data StateFile = StateFile
|
||||
{ sfWins :: W.StackSet WorkspaceId String Window ScreenId ScreenDetail
|
||||
, sfExt :: [(String, String)]
|
||||
} deriving (Show, Read)
|
||||
|
||||
-- | Write the current window state (and extensible state) to a file
|
||||
-- so that xmonad can resume with that state intact.
|
||||
writeStateToFile :: X ()
|
||||
writeStateToFile = do
|
||||
let maybeShow (t, Right (PersistentExtension ext)) = Just (t, show ext)
|
||||
maybeShow (t, Left str) = Just (t, str)
|
||||
maybeShow _ = Nothing
|
||||
|
||||
wsData = W.mapLayout show . windowset
|
||||
extState = catMaybes . map maybeShow . M.toList . extensibleState
|
||||
|
||||
path <- stateFileName
|
||||
stateData <- gets (\s -> StateFile (wsData s) (extState s))
|
||||
catchIO (writeFile path $ show stateData)
|
||||
|
||||
-- | Read the state of a previous xmonad instance from a file and
|
||||
-- return that state. The state file is removed after reading it.
|
||||
readStateFile :: (LayoutClass l Window, Read (l Window)) => XConfig l -> X (Maybe XState)
|
||||
readStateFile xmc = do
|
||||
path <- stateFileName
|
||||
|
||||
-- I'm trying really hard here to make sure we read the entire
|
||||
-- contents of the file before it is removed from the file system.
|
||||
sf' <- userCode . io $ do
|
||||
raw <- withFile path ReadMode readStrict
|
||||
return $! maybeRead reads raw
|
||||
|
||||
io (removeFile path)
|
||||
|
||||
return $ do
|
||||
sf <- join sf'
|
||||
|
||||
let winset = W.ensureTags layout (workspaces xmc) $ W.mapLayout (fromMaybe layout . maybeRead lreads) (sfWins sf)
|
||||
extState = M.fromList . map (second Left) $ sfExt sf
|
||||
|
||||
return XState { windowset = winset
|
||||
, numberlockMask = 0
|
||||
, mapped = S.empty
|
||||
, waitingUnmap = M.empty
|
||||
, dragging = Nothing
|
||||
, extensibleState = extState
|
||||
}
|
||||
where
|
||||
layout = Layout (layoutHook xmc)
|
||||
lreads = readsLayout layout
|
||||
maybeRead reads' s = case reads' s of
|
||||
[(x, "")] -> Just x
|
||||
_ -> Nothing
|
||||
|
||||
readStrict :: Handle -> IO String
|
||||
readStrict h = hGetContents h >>= \s -> length s `seq` return s
|
||||
|
||||
-- | 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.
|
||||
-- When executing another window manager, @resume@ should be 'False'.
|
||||
restart :: String -> Bool -> X ()
|
||||
restart prog resume = do
|
||||
broadcastMessage ReleaseResources
|
||||
io . flush =<< asks display
|
||||
when resume writeStateToFile
|
||||
catchIO (executeFile prog True [] Nothing)
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- | Floating layer support
|
||||
|
||||
-- | Given a window, find the screen it is located on, and compute
|
||||
-- the geometry of that window wrt. that screen.
|
||||
floatLocation :: Window -> X (ScreenId, W.RationalRect)
|
||||
floatLocation w =
|
||||
catchX go $ do
|
||||
-- Fallback solution if `go' fails. Which it might, since it
|
||||
-- calls `getWindowAttributes'.
|
||||
sc <- W.current <$> gets windowset
|
||||
return (W.screen sc, W.RationalRect 0 0 1 1)
|
||||
|
||||
where fi x = fromIntegral x
|
||||
go = withDisplay $ \d -> do
|
||||
ws <- gets windowset
|
||||
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)
|
||||
|
||||
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))
|
||||
|
||||
return (W.screen sc, rr)
|
||||
|
||||
-- | Given a point, determine the screen (if any) that contains it.
|
||||
pointScreen :: Position -> Position
|
||||
-> X (Maybe (W.Screen WorkspaceId (Layout Window) Window ScreenId ScreenDetail))
|
||||
pointScreen x y = withWindowSet $ return . find p . W.screens
|
||||
where p = pointWithin x y . screenRect . W.screenDetail
|
||||
|
||||
-- | @pointWithin x y r@ returns 'True' if the @(x, y)@ co-ordinate is within
|
||||
-- @r@.
|
||||
pointWithin :: Position -> Position -> Rectangle -> Bool
|
||||
pointWithin x y r = x >= rect_x r &&
|
||||
x < rect_x r + fromIntegral (rect_width r) &&
|
||||
y >= rect_y r &&
|
||||
y < rect_y r + fromIntegral (rect_height r)
|
||||
|
||||
-- | Make a tiled window floating, using its suggested rectangle
|
||||
float :: Window -> X ()
|
||||
float w = do
|
||||
(sc, rr) <- floatLocation w
|
||||
windows $ \ws -> W.float w rr . fromMaybe ws $ do
|
||||
i <- W.findTag w ws
|
||||
guard $ i `elem` map (W.tag . W.workspace) (W.screens ws)
|
||||
f <- W.peek ws
|
||||
sw <- W.lookupWorkspace sc ws
|
||||
return (W.focusWindow f . W.shiftWin sw w $ ws)
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Mouse handling
|
||||
|
||||
-- | Accumulate mouse motion events
|
||||
mouseDrag :: (Position -> Position -> X ()) -> X () -> X ()
|
||||
mouseDrag 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
|
||||
modify $ \s -> s { dragging = Just (motion, cleanup) }
|
||||
where
|
||||
cleanup = do
|
||||
withDisplay $ io . flip ungrabPointer currentTime
|
||||
modify $ \s -> s { dragging = Nothing }
|
||||
done
|
||||
motion x y = do z <- f x y
|
||||
clearEvents pointerMotionMask
|
||||
return z
|
||||
|
||||
-- | drag the window under the cursor with the mouse while it is dragged
|
||||
mouseMoveWindow :: Window -> X ()
|
||||
mouseMoveWindow w = whenX (isClient w) $ withDisplay $ \d -> do
|
||||
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
|
||||
io $ moveWindow d w (fromIntegral (fromIntegral (wa_x wa) + (ex - ox)))
|
||||
(fromIntegral (fromIntegral (wa_y wa) + (ey - oy)))
|
||||
float w
|
||||
)
|
||||
(float w)
|
||||
|
||||
-- | resize the window under the cursor with the mouse while it is dragged
|
||||
mouseResizeWindow :: Window -> X ()
|
||||
mouseResizeWindow w = whenX (isClient w) $ withDisplay $ \d -> do
|
||||
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
|
||||
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
|
||||
|
||||
type D = (Dimension, Dimension)
|
||||
|
||||
-- | Given a window, build an adjuster function that will reduce the given
|
||||
-- dimensions according to the window's border width and size hints.
|
||||
mkAdjust :: Window -> X (D -> D)
|
||||
mkAdjust w = withDisplay $ \d -> liftIO $ do
|
||||
sh <- getWMNormalHints d w
|
||||
wa <- C.try $ getWindowAttributes d w
|
||||
case wa of
|
||||
Left err -> const (return id) (err :: C.SomeException)
|
||||
Right wa' ->
|
||||
let bw = fromIntegral $ wa_border_width wa'
|
||||
in return $ applySizeHints bw sh
|
||||
|
||||
-- | Reduce the dimensions if needed to comply to the given SizeHints, taking
|
||||
-- window borders into account.
|
||||
applySizeHints :: Integral a => Dimension -> SizeHints -> (a, a) -> D
|
||||
applySizeHints bw sh =
|
||||
tmap (+ 2 * bw) . applySizeHintsContents sh . tmap (subtract $ 2 * fromIntegral bw)
|
||||
where
|
||||
tmap f (x, y) = (f x, f y)
|
||||
|
||||
-- | Reduce the dimensions if needed to comply to the given SizeHints.
|
||||
applySizeHintsContents :: Integral a => SizeHints -> (a, a) -> D
|
||||
applySizeHintsContents sh (w, h) =
|
||||
applySizeHints' sh (fromIntegral $ max 1 w, fromIntegral $ max 1 h)
|
||||
|
||||
-- | XXX comment me
|
||||
applySizeHints' :: SizeHints -> D -> D
|
||||
applySizeHints' sh =
|
||||
maybe id applyMaxSizeHint (sh_max_size sh)
|
||||
. maybe id (\(bw, bh) (w, h) -> (w+bw, h+bh)) (sh_base_size sh)
|
||||
. maybe id applyResizeIncHint (sh_resize_inc sh)
|
||||
. maybe id applyAspectHint (sh_aspect sh)
|
||||
. maybe id (\(bw,bh) (w,h) -> (w-bw, h-bh)) (sh_base_size sh)
|
||||
|
||||
-- | Reduce the dimensions so their aspect ratio falls between the two given aspect ratios.
|
||||
applyAspectHint :: (D, D) -> D -> D
|
||||
applyAspectHint ((minx, miny), (maxx, maxy)) x@(w,h)
|
||||
| or [minx < 1, miny < 1, maxx < 1, maxy < 1] = x
|
||||
| w * maxy > h * maxx = (h * maxx `div` maxy, h)
|
||||
| w * miny < h * minx = (w, w * miny `div` minx)
|
||||
| otherwise = x
|
||||
|
||||
-- | Reduce the dimensions so they are a multiple of the size increments.
|
||||
applyResizeIncHint :: D -> D -> D
|
||||
applyResizeIncHint (iw,ih) x@(w,h) =
|
||||
if iw > 0 && ih > 0 then (w - w `mod` iw, h - h `mod` ih) else x
|
||||
|
||||
-- | Reduce the dimensions if they exceed the given maximum dimensions.
|
||||
applyMaxSizeHint :: D -> D -> D
|
||||
applyMaxSizeHint (mw,mh) x@(w,h) =
|
||||
if mw > 0 && mh > 0 then (min w mw,min h mh) else x
|
@@ -2,7 +2,7 @@
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- |
|
||||
-- Module : StackSet
|
||||
-- Module : XMonad.StackSet
|
||||
-- Copyright : (c) Don Stewart 2007
|
||||
-- License : BSD3-style (see LICENSE)
|
||||
--
|
||||
@@ -11,28 +11,38 @@
|
||||
-- Portability : portable, Haskell 98
|
||||
--
|
||||
|
||||
module StackSet (
|
||||
module XMonad.StackSet (
|
||||
-- * Introduction
|
||||
-- $intro
|
||||
StackSet(..), Workspace(..), Screen(..), StackOrNot, Stack(..), RationalRect(..),
|
||||
|
||||
-- ** The Zipper
|
||||
-- $zipper
|
||||
|
||||
-- ** Xinerama support
|
||||
-- $xinerama
|
||||
|
||||
-- ** Master and Focus
|
||||
-- $focus
|
||||
|
||||
StackSet(..), Workspace(..), Screen(..), Stack(..), RationalRect(..),
|
||||
-- * Construction
|
||||
-- $construction
|
||||
new, view, greedyView,
|
||||
-- * Xinerama operations
|
||||
-- $xinerama
|
||||
lookupWorkspace,
|
||||
screens, workspaces, allWindows,
|
||||
screens, workspaces, allWindows, currentTag,
|
||||
-- * Operations on the current stack
|
||||
-- $stackOperations
|
||||
peek, index, integrate, integrate', differentiate,
|
||||
focusUp, focusDown, focusMaster, focusWindow,
|
||||
tagMember, renameTag, ensureTags, member, findIndex, mapWorkspace, mapLayout,
|
||||
focusUp, focusDown, focusUp', focusDown', focusMaster, focusWindow,
|
||||
tagMember, renameTag, ensureTags, member, findTag, mapWorkspace, mapLayout,
|
||||
-- * Modifying the stackset
|
||||
-- $modifyStackset
|
||||
insertUp, delete, delete', filter,
|
||||
-- * Setting the master window
|
||||
-- $settingMW
|
||||
swapUp, swapDown, swapMaster, modify, modify', float, sink, -- needed by users
|
||||
swapUp, swapDown, swapMaster, shiftMaster, modify, modify', float, sink, -- needed by users
|
||||
-- * Composite operations
|
||||
-- $composite
|
||||
shift, shiftWin,
|
||||
@@ -42,7 +52,7 @@ module StackSet (
|
||||
) where
|
||||
|
||||
import Prelude hiding (filter)
|
||||
import Data.Maybe (listToMaybe,fromJust)
|
||||
import Data.Maybe (listToMaybe,isJust,fromMaybe)
|
||||
import qualified Data.List as L (deleteBy,find,splitAt,filter,nub)
|
||||
import Data.List ( (\\) )
|
||||
import qualified Data.Map as M (Map,insert,delete,empty)
|
||||
@@ -65,8 +75,8 @@ import qualified Data.Map as M (Map,insert,delete,empty)
|
||||
-- Note that workspaces are indexed from 0, windows are numbered
|
||||
-- uniquely. A '*' indicates the window on each workspace that has
|
||||
-- focus, and which workspace is current.
|
||||
--
|
||||
-- Zipper
|
||||
|
||||
-- $zipper
|
||||
--
|
||||
-- We encode all the focus tracking directly in the data structure, with a 'zipper':
|
||||
--
|
||||
@@ -94,56 +104,24 @@ import qualified Data.Map as M (Map,insert,delete,empty)
|
||||
-- Another good reference is:
|
||||
--
|
||||
-- The Zipper, Haskell wikibook
|
||||
--
|
||||
-- Xinerama support:
|
||||
--
|
||||
|
||||
-- $xinerama
|
||||
-- Xinerama in X11 lets us view multiple virtual workspaces
|
||||
-- simultaneously. While only one will ever be in focus (i.e. will
|
||||
-- receive keyboard events), other workspaces may be passively viewable.
|
||||
-- We thus need to track which virtual workspaces are associated
|
||||
-- (viewed) on which physical screens. We use a simple Map Workspace
|
||||
-- Screen for this.
|
||||
--
|
||||
-- Master and Focus
|
||||
-- receive keyboard events), other workspaces may be passively
|
||||
-- viewable. We thus need to track which virtual workspaces are
|
||||
-- associated (viewed) on which physical screens. To keep track of
|
||||
-- this, 'StackSet' keeps separate lists of visible but non-focused
|
||||
-- workspaces, and non-visible workspaces.
|
||||
|
||||
-- $focus
|
||||
--
|
||||
-- Each stack tracks a focused item, and for tiling purposes also tracks
|
||||
-- a 'master' position. The connection between 'master' and 'focus'
|
||||
-- needs to be well defined. Particular in relation to 'insert' and
|
||||
-- needs to be well defined, particularly in relation to 'insert' and
|
||||
-- 'delete'.
|
||||
--
|
||||
|
||||
-- |
|
||||
-- API changes from xmonad 0.1:
|
||||
-- StackSet constructor arguments changed. StackSet workspace window screen
|
||||
--
|
||||
-- * new, -- was: empty
|
||||
--
|
||||
-- * view,
|
||||
--
|
||||
-- * index,
|
||||
--
|
||||
-- * peek, -- was: peek\/peekStack
|
||||
--
|
||||
-- * focusUp, focusDown, -- was: rotate
|
||||
--
|
||||
-- * swapUp, swapDown
|
||||
--
|
||||
-- * focus -- was: raiseFocus
|
||||
--
|
||||
-- * insertUp, -- was: insert\/push
|
||||
--
|
||||
-- * delete,
|
||||
--
|
||||
-- * swapMaster, -- was: promote\/swap
|
||||
--
|
||||
-- * member,
|
||||
--
|
||||
-- * shift,
|
||||
--
|
||||
-- * lookupWorkspace, -- was: workspace
|
||||
--
|
||||
-- * visibleWorkspaces -- gone.
|
||||
--
|
||||
------------------------------------------------------------------------
|
||||
-- |
|
||||
-- A cursor into a non-empty list of workspaces.
|
||||
@@ -156,8 +134,8 @@ import qualified Data.Map as M (Map,insert,delete,empty)
|
||||
data StackSet i l a sid sd =
|
||||
StackSet { current :: !(Screen i l a sid sd) -- ^ currently focused workspace
|
||||
, visible :: [Screen i l a sid sd] -- ^ non-focused workspaces, visible in xinerama
|
||||
, hidden :: [Workspace i l a] -- ^ workspaces not visible anywhere
|
||||
, floating :: M.Map a RationalRect -- ^ floating windows
|
||||
, hidden :: [Workspace i l a] -- ^ workspaces not visible anywhere
|
||||
, floating :: M.Map a RationalRect -- ^ floating windows
|
||||
} deriving (Show, Read, Eq)
|
||||
|
||||
-- | Visible workspaces, and their Xinerama screens.
|
||||
@@ -167,9 +145,9 @@ data Screen i l a sid sd = Screen { workspace :: !(Workspace i l a)
|
||||
deriving (Show, Read, Eq)
|
||||
|
||||
-- |
|
||||
-- A workspace is just a tag - its index - and a stack
|
||||
-- A workspace is just a tag, a layout, and a stack.
|
||||
--
|
||||
data Workspace i l a = Workspace { tag :: !i, layout :: l, stack :: StackOrNot a }
|
||||
data Workspace i l a = Workspace { tag :: !i, layout :: l, stack :: Maybe (Stack a) }
|
||||
deriving (Show, Read, Eq)
|
||||
|
||||
-- | A structure for window geometries
|
||||
@@ -177,7 +155,7 @@ data RationalRect = RationalRect Rational Rational Rational Rational
|
||||
deriving (Show, Read, Eq)
|
||||
|
||||
-- |
|
||||
-- A stack is a cursor onto a (possibly empty) window list.
|
||||
-- A stack is a cursor onto a window list.
|
||||
-- The data structure tracks focus by construction, and
|
||||
-- the master window is by convention the top-most item.
|
||||
-- Focus operations will not reorder the list that results from
|
||||
@@ -194,8 +172,6 @@ data RationalRect = RationalRect Rational Rational Rational Rational
|
||||
-- structures, it is the differentiation of a [a], and integrating it
|
||||
-- back has a natural implementation used in 'index'.
|
||||
--
|
||||
type StackOrNot a = Maybe (Stack a)
|
||||
|
||||
data Stack a = Stack { focus :: !a -- focused thing in this set
|
||||
, up :: [a] -- clowns to the left
|
||||
, down :: [a] } -- jokers to the right
|
||||
@@ -209,14 +185,17 @@ abort x = error $ "xmonad: StackSet: " ++ x
|
||||
-- ---------------------------------------------------------------------
|
||||
-- $construction
|
||||
|
||||
-- | /O(n)/. Create a new stackset, of empty stacks, with given tags, with
|
||||
-- 'm' physical screens. 'm' should be less than or equal to the number of
|
||||
-- workspace tags. The first workspace in the list will be current.
|
||||
-- | /O(n)/. Create a new stackset, of empty stacks, with given tags,
|
||||
-- with physical screens whose descriptions are given by 'm'. The
|
||||
-- number of physical screens (@length 'm'@) should be less than or
|
||||
-- equal to the number of workspace tags. The first workspace in the
|
||||
-- list will be current.
|
||||
--
|
||||
-- Xinerama: Virtual workspaces are assigned to physical screens, starting at 0.
|
||||
--
|
||||
new :: (Integral s) => l -> [i] -> [sd] -> StackSet i l a s sd
|
||||
new l wids m | not (null wids) && length m <= length wids = StackSet cur visi unseen M.empty
|
||||
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 ]
|
||||
-- now zip up visibles with their screen id
|
||||
@@ -224,7 +203,7 @@ new _ _ _ = abort "non-positive argument to StackSet.new"
|
||||
|
||||
-- |
|
||||
-- /O(w)/. Set focus to the workspace with index \'i\'.
|
||||
-- If the index is out of range, return the original StackSet.
|
||||
-- If the index is out of range, return the original 'StackSet'.
|
||||
--
|
||||
-- Xinerama: If the workspace is not visible on any Xinerama screen, it
|
||||
-- becomes the current screen. If it is in the visible list, it becomes
|
||||
@@ -232,8 +211,7 @@ new _ _ _ = abort "non-positive argument to StackSet.new"
|
||||
|
||||
view :: (Eq s, Eq i) => i -> StackSet i l a s sd -> StackSet i l a s sd
|
||||
view i s
|
||||
| not (i `tagMember` s)
|
||||
|| i == tag (workspace (current s)) = s -- out of bounds or current
|
||||
| i == currentTag s = s -- current
|
||||
|
||||
| Just x <- L.find ((i==).tag.workspace) (visible s)
|
||||
-- if it is visible, it is just raised
|
||||
@@ -244,7 +222,7 @@ view i s
|
||||
= s { current = (current s) { workspace = x }
|
||||
, hidden = workspace (current s) : L.deleteBy (equating tag) x (hidden s) }
|
||||
|
||||
| otherwise = s -- can't happen: all workspaces are either invalid, current, visible, or hidden
|
||||
| otherwise = s -- not a member of the stackset
|
||||
|
||||
where equating f = \x y -> f x == f y
|
||||
|
||||
@@ -275,7 +253,7 @@ greedyView w ws
|
||||
-- $xinerama
|
||||
|
||||
-- | Find the tag of the workspace visible on Xinerama screen 'sc'.
|
||||
-- Nothing if screen is out of bounds.
|
||||
-- 'Nothing' if screen is out of bounds.
|
||||
lookupWorkspace :: Eq s => s -> StackSet i l a s sd -> Maybe i
|
||||
lookupWorkspace sc w = listToMaybe [ tag i | Screen i s _ <- current w : visible w, s == sc ]
|
||||
|
||||
@@ -292,9 +270,9 @@ with :: b -> (Stack a -> b) -> StackSet i l a s sd -> b
|
||||
with dflt f = maybe dflt f . stack . workspace . current
|
||||
|
||||
-- |
|
||||
-- Apply a function, and a default value for Nothing, to modify the current stack.
|
||||
-- Apply a function, and a default value for 'Nothing', to modify the current stack.
|
||||
--
|
||||
modify :: StackOrNot a -> (Stack a -> StackOrNot a) -> StackSet i l a s sd -> StackSet i l a s sd
|
||||
modify :: Maybe (Stack a) -> (Stack a -> Maybe (Stack a)) -> StackSet i l a s sd -> StackSet i l a s sd
|
||||
modify d f s = s { current = (current s)
|
||||
{ workspace = (workspace (current s)) { stack = with d f s }}}
|
||||
|
||||
@@ -307,34 +285,35 @@ modify' f = modify Nothing (Just . f)
|
||||
|
||||
-- |
|
||||
-- /O(1)/. Extract the focused element of the current stack.
|
||||
-- Return Just that element, or Nothing for an empty stack.
|
||||
-- Return 'Just' that element, or 'Nothing' for an empty stack.
|
||||
--
|
||||
peek :: StackSet i l a s sd -> Maybe a
|
||||
peek = with Nothing (return . focus)
|
||||
|
||||
-- |
|
||||
-- /O(n)/. Flatten a Stack into a list.
|
||||
-- /O(n)/. Flatten a 'Stack' into a list.
|
||||
--
|
||||
integrate :: Stack a -> [a]
|
||||
integrate (Stack x l r) = reverse l ++ x : r
|
||||
|
||||
-- |
|
||||
-- /O(n)/ Flatten a possibly empty stack into a list.
|
||||
integrate' :: StackOrNot a -> [a]
|
||||
integrate' :: Maybe (Stack a) -> [a]
|
||||
integrate' = maybe [] integrate
|
||||
|
||||
-- |
|
||||
-- /O(n)/. Texture a list.
|
||||
--
|
||||
differentiate :: [a] -> StackOrNot a
|
||||
-- /O(n)/. Turn a list into a possibly empty stack (i.e., a zipper):
|
||||
-- the first element of the list is current, and the rest of the list
|
||||
-- is down.
|
||||
differentiate :: [a] -> Maybe (Stack a)
|
||||
differentiate [] = Nothing
|
||||
differentiate (x:xs) = Just $ Stack x [] xs
|
||||
|
||||
-- |
|
||||
-- /O(n)/. 'filter p s' returns the elements of 's' such that 'p' evaluates to
|
||||
-- True. Order is preserved, and focus moves as described for 'delete'.
|
||||
-- 'True'. Order is preserved, and focus moves as described for 'delete'.
|
||||
--
|
||||
filter :: (a -> Bool) -> Stack a -> StackOrNot a
|
||||
filter :: (a -> Bool) -> Stack a -> Maybe (Stack a)
|
||||
filter p (Stack f ls rs) = case L.filter p (f:rs) of
|
||||
f':rs' -> Just $ Stack f' (L.filter p ls) rs' -- maybe move focus down
|
||||
[] -> case L.filter p ls of -- filter back up
|
||||
@@ -350,8 +329,6 @@ filter p (Stack f ls rs) = case L.filter p (f:rs) of
|
||||
index :: StackSet i l a s sd -> [a]
|
||||
index = with [] integrate
|
||||
|
||||
-- let is = t : r ++ reverse l in take (length is) (dropWhile (/= m) (cycle is))
|
||||
|
||||
-- |
|
||||
-- /O(1), O(w) on the wrapping case/.
|
||||
--
|
||||
@@ -366,15 +343,19 @@ index = with [] integrate
|
||||
--
|
||||
focusUp, focusDown, swapUp, swapDown :: StackSet i l a s sd -> StackSet i l a s sd
|
||||
focusUp = modify' focusUp'
|
||||
focusDown = modify' (reverseStack . focusUp' . reverseStack)
|
||||
focusDown = modify' focusDown'
|
||||
|
||||
swapUp = modify' swapUp'
|
||||
swapDown = modify' (reverseStack . swapUp' . reverseStack)
|
||||
|
||||
focusUp', swapUp' :: Stack a -> Stack a
|
||||
-- | Variants of 'focusUp' and 'focusDown' that work on a
|
||||
-- 'Stack' rather than an entire 'StackSet'.
|
||||
focusUp', focusDown' :: 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
|
||||
|
||||
swapUp' :: Stack a -> Stack a
|
||||
swapUp' (Stack t (l:ls) rs) = Stack t ls (l:rs)
|
||||
swapUp' (Stack t [] rs) = Stack t (reverse rs) []
|
||||
|
||||
@@ -388,32 +369,38 @@ reverseStack (Stack t ls rs) = Stack t rs ls
|
||||
--
|
||||
focusWindow :: (Eq s, Eq a, Eq i) => a -> StackSet i l a s sd -> StackSet i l a s sd
|
||||
focusWindow w s | Just w == peek s = s
|
||||
| otherwise = maybe s id $ do
|
||||
n <- findIndex w s
|
||||
| otherwise = fromMaybe s $ do
|
||||
n <- findTag w s
|
||||
return $ until ((Just w ==) . peek) focusUp (view n s)
|
||||
|
||||
-- | Get a list of all screens in the StackSet.
|
||||
-- | Get a list of all screens in the 'StackSet'.
|
||||
screens :: StackSet i l a s sd -> [Screen i l a s sd]
|
||||
screens s = current s : visible s
|
||||
|
||||
-- | Get a list of all workspaces in the StackSet.
|
||||
-- | Get a list of all workspaces in the 'StackSet'.
|
||||
workspaces :: StackSet i l a s sd -> [Workspace i l a]
|
||||
workspaces s = workspace (current s) : map workspace (visible s) ++ hidden s
|
||||
|
||||
-- | Get a list of all windows in the StackSet in no particular order
|
||||
-- | Get a list of all windows in the 'StackSet' in no particular order
|
||||
allWindows :: Eq a => StackSet i l a s sd -> [a]
|
||||
allWindows = L.nub . concatMap (integrate' . stack) . workspaces
|
||||
|
||||
-- | Is the given tag present in the StackSet?
|
||||
-- | Get the tag of the currently focused workspace.
|
||||
currentTag :: StackSet i l a s sd -> i
|
||||
currentTag = tag . workspace . current
|
||||
|
||||
-- | Is the given tag present in the 'StackSet'?
|
||||
tagMember :: Eq i => i -> StackSet i l a s sd -> Bool
|
||||
tagMember t = elem t . map tag . workspaces
|
||||
|
||||
-- | Rename a given tag if present in the StackSet.
|
||||
-- | Rename a given tag if present in the 'StackSet'.
|
||||
renameTag :: Eq i => i -> i -> StackSet i l a s sd -> StackSet i l a s sd
|
||||
renameTag o n = mapWorkspace rename
|
||||
where rename w = if tag w == o then w { tag = n } else w
|
||||
|
||||
-- | Ensure that a given set of tags is present.
|
||||
-- | Ensure that a given set of workspace tags is present by renaming
|
||||
-- existing workspaces and\/or creating new hidden workspaces as
|
||||
-- necessary.
|
||||
ensureTags :: Eq i => l -> [i] -> StackSet i l a s sd -> StackSet i l a s sd
|
||||
ensureTags l allt st = et allt (map tag (workspaces st) \\ allt) st
|
||||
where et [] _ s = s
|
||||
@@ -421,29 +408,29 @@ ensureTags l allt st = et allt (map tag (workspaces st) \\ allt) st
|
||||
et (i:is) [] s = et is [] (s { hidden = Workspace i l Nothing : hidden s })
|
||||
et (i:is) (r:rs) s = et is rs $ renameTag r i s
|
||||
|
||||
-- | Map a function on all the workspaces in the StackSet.
|
||||
-- | Map a function on all the workspaces in the 'StackSet'.
|
||||
mapWorkspace :: (Workspace i l a -> Workspace i l a) -> StackSet i l a s sd -> StackSet i l a s sd
|
||||
mapWorkspace f s = s { current = updScr (current s)
|
||||
, visible = map updScr (visible s)
|
||||
, hidden = map f (hidden s) }
|
||||
where updScr scr = scr { workspace = f (workspace scr) }
|
||||
|
||||
-- | Map a function on all the layouts in the StackSet.
|
||||
-- | Map a function on all the layouts in the 'StackSet'.
|
||||
mapLayout :: (l -> l') -> StackSet i l a s sd -> StackSet i l' a s sd
|
||||
mapLayout f (StackSet v vs hs m) = StackSet (fScreen v) (map fScreen vs) (map fWorkspace hs) m
|
||||
where
|
||||
fScreen (Screen ws s sd) = Screen (fWorkspace ws) s sd
|
||||
fWorkspace (Workspace t l s) = Workspace t (f l) s
|
||||
|
||||
-- | /O(n)/. Is a window in the StackSet.
|
||||
-- | /O(n)/. Is a window in the 'StackSet'?
|
||||
member :: Eq a => a -> StackSet i l a s sd -> Bool
|
||||
member a s = maybe False (const True) (findIndex a s)
|
||||
member a s = isJust (findTag a s)
|
||||
|
||||
-- | /O(1) on current window, O(n) in general/.
|
||||
-- Return Just the workspace index of the given window, or Nothing
|
||||
-- if the window is not in the StackSet.
|
||||
findIndex :: Eq a => a -> StackSet i l a s sd -> Maybe i
|
||||
findIndex a s = listToMaybe
|
||||
-- Return 'Just' the workspace tag of the given window, or 'Nothing'
|
||||
-- if the window is not in the 'StackSet'.
|
||||
findTag :: Eq a => a -> StackSet i l a s sd -> Maybe i
|
||||
findTag a s = listToMaybe
|
||||
[ tag w | w <- workspaces s, has a (stack w) ]
|
||||
where has _ Nothing = False
|
||||
has x (Just (Stack t l r)) = x `elem` (t : l ++ r)
|
||||
@@ -452,12 +439,10 @@ findIndex a s = listToMaybe
|
||||
-- $modifyStackset
|
||||
|
||||
-- |
|
||||
-- /O(n)/. (Complexity due to duplicate check). Insert a new element into
|
||||
-- the stack, above the currently focused element.
|
||||
--
|
||||
-- The new element is given focus, and is set as the master window.
|
||||
-- The previously focused element is moved down. The previously
|
||||
-- 'master' element is forgotten. (Thus, 'insert' will cause a retiling).
|
||||
-- /O(n)/. (Complexity due to duplicate check). Insert a new element
|
||||
-- into the stack, above the currently focused element. The new
|
||||
-- element is given focus; the previously focused element is moved
|
||||
-- down.
|
||||
--
|
||||
-- If the element is already in the stackset, the original stackset is
|
||||
-- returned unmodified.
|
||||
@@ -478,22 +463,26 @@ insertUp a s = if member a s then s else insert
|
||||
-- /O(1) on current window, O(n) in general/. Delete window 'w' if it exists.
|
||||
-- There are 4 cases to consider:
|
||||
--
|
||||
-- * delete on an Nothing workspace leaves it Nothing
|
||||
-- * delete on an 'Nothing' workspace leaves it Nothing
|
||||
--
|
||||
-- * otherwise, try to move focus to the down
|
||||
--
|
||||
-- * otherwise, try to move focus to the up
|
||||
-- * otherwise, you've got an empty workspace, becomes Nothing
|
||||
--
|
||||
-- * otherwise, you've got an empty workspace, becomes 'Nothing'
|
||||
--
|
||||
-- Behaviour with respect to the master:
|
||||
--
|
||||
-- * deleting the master window resets it to the newly focused window
|
||||
--
|
||||
-- * otherwise, delete doesn't affect the master.
|
||||
--
|
||||
delete :: (Ord a, Eq s) => a -> StackSet i l a s sd -> StackSet i l a s sd
|
||||
delete :: (Ord a) => a -> StackSet i l a s sd -> StackSet i l a s sd
|
||||
delete w = sink w . delete' w
|
||||
|
||||
-- | Only temporarily remove the window from the stack, thereby not destroying special
|
||||
-- information saved in the Stackset
|
||||
delete' :: (Eq a, Eq s) => a -> StackSet i l a s sd -> StackSet i l a s sd
|
||||
-- information saved in the 'Stackset'
|
||||
delete' :: (Eq a) => a -> StackSet i l a s sd -> StackSet i l a s sd
|
||||
delete' w s = s { current = removeFromScreen (current s)
|
||||
, visible = map removeFromScreen (visible s)
|
||||
, hidden = map removeFromWorkspace (hidden s) }
|
||||
@@ -503,7 +492,7 @@ delete' w s = s { current = removeFromScreen (current s)
|
||||
------------------------------------------------------------------------
|
||||
|
||||
-- | Given a window, and its preferred rectangle, set it as floating
|
||||
-- A floating window should already be managed by the StackSet.
|
||||
-- A floating window should already be managed by the 'StackSet'.
|
||||
float :: Ord a => a -> RationalRect -> StackSet i l a s sd -> StackSet i l a s sd
|
||||
float w r s = s { floating = M.insert w r (floating s) }
|
||||
|
||||
@@ -524,6 +513,15 @@ swapMaster = modify' $ \c -> case c of
|
||||
|
||||
-- natural! keep focus, move current to the top, move top to current.
|
||||
|
||||
-- | /O(s)/. Set the master window to the focused window.
|
||||
-- The other windows are kept in order and shifted down on the stack, as if you
|
||||
-- just hit mod-shift-k a bunch of times.
|
||||
-- Focus stays with the item moved.
|
||||
shiftMaster :: StackSet i l a s sd -> StackSet i l a s sd
|
||||
shiftMaster = modify' $ \c -> case c of
|
||||
Stack _ [] _ -> c -- already master.
|
||||
Stack t ls rs -> Stack t [] (reverse ls ++ rs)
|
||||
|
||||
-- | /O(s)/. Set focus to the master window.
|
||||
focusMaster :: StackSet i l a s sd -> StackSet i l a s sd
|
||||
focusMaster = modify' $ \c -> case c of
|
||||
@@ -541,10 +539,7 @@ focusMaster = modify' $ \c -> case c of
|
||||
-- element on the current stack, the original stackSet is returned.
|
||||
--
|
||||
shift :: (Ord a, Eq s, Eq i) => i -> StackSet i l a s sd -> StackSet i l a s sd
|
||||
shift n s | n `tagMember` s && n /= curtag = maybe s go (peek s)
|
||||
| otherwise = s
|
||||
where go w = view curtag . insertUp w . view n . delete' w $ s
|
||||
curtag = tag (workspace (current s))
|
||||
shift n s = maybe s (\w -> shiftWin n w s) (peek s)
|
||||
|
||||
-- | /O(n)/. shiftWin. Searches for the specified window 'w' on all workspaces
|
||||
-- of the stackSet and moves it to stack 'n', leaving it as the focused
|
||||
@@ -552,14 +547,12 @@ shift n s | n `tagMember` s && n /= curtag = maybe s go (peek s)
|
||||
-- focused element on that workspace.
|
||||
-- The actual focused workspace doesn't change. If the window is not
|
||||
-- found in the stackSet, the original stackSet is returned.
|
||||
-- TODO how does this duplicate 'shift's behaviour?
|
||||
shiftWin :: (Ord a, Eq a, Eq s, Eq i) => i -> a -> StackSet i l a s sd -> StackSet i l a s sd
|
||||
shiftWin n w s | from == Nothing = s -- not found
|
||||
| n `tagMember` s && (Just n) /= from = go
|
||||
| otherwise = s
|
||||
where from = findIndex w s
|
||||
|
||||
go = on n (insertUp w) . on (fromJust from) (delete' w) $ s
|
||||
curtag = tag (workspace (current s))
|
||||
on i f = view curtag . f . view i
|
||||
shiftWin :: (Ord a, Eq s, Eq i) => i -> a -> StackSet i l a s sd -> StackSet i l a s sd
|
||||
shiftWin n w s = case findTag w s of
|
||||
Just from | n `tagMember` s && n /= from -> go from s
|
||||
_ -> s
|
||||
where go from = onWorkspace n (insertUp w) . onWorkspace from (delete' w)
|
||||
|
||||
onWorkspace :: (Eq i, Eq s) => i -> (StackSet i l a s sd -> StackSet i l a s sd)
|
||||
-> (StackSet i l a s sd -> StackSet i l a s sd)
|
||||
onWorkspace n f s = view (currentTag s) . f . view n $ s
|
7
stack.yaml
Normal file
7
stack.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
resolver: lts-7.19
|
||||
|
||||
packages:
|
||||
- ./
|
||||
|
||||
extra-deps:
|
||||
- X11-1.8
|
140
tests/Instances.hs
Normal file
140
tests/Instances.hs
Normal file
@@ -0,0 +1,140 @@
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
module Instances where
|
||||
|
||||
import Test.QuickCheck
|
||||
|
||||
import Utils
|
||||
|
||||
import XMonad.StackSet
|
||||
import Control.Monad
|
||||
import Data.List (nub, genericLength)
|
||||
|
||||
import Debug.Trace
|
||||
|
||||
import Graphics.X11 (Rectangle(Rectangle))
|
||||
import Control.Applicative
|
||||
|
||||
--
|
||||
-- The all important Arbitrary instance for StackSet.
|
||||
--
|
||||
instance (Integral i, Integral s, Eq a, Arbitrary a, Arbitrary l, Arbitrary sd)
|
||||
=> Arbitrary (StackSet i l a s sd) where
|
||||
arbitrary = do
|
||||
-- TODO: Fix this to be a reasonable higher number, Possibly use PositiveSized
|
||||
numWs <- choose (1, 20) -- number of workspaces, there must be at least 1.
|
||||
numScreens <- choose (1, numWs) -- number of physical screens, there must be at least 1
|
||||
lay <- arbitrary -- pick any layout
|
||||
|
||||
wsIdxInFocus <- choose (1, numWs) -- pick index of WS to be in focus
|
||||
|
||||
-- The same screen id's will be present in the list, with high possibility.
|
||||
screens <- replicateM numScreens arbitrary
|
||||
|
||||
-- Generate a list of "windows" for each workspace.
|
||||
wsWindows <- vector numWs :: Gen [[a]]
|
||||
|
||||
-- Pick a random window "number" in each workspace, to give focus.
|
||||
focus <- sequence [ if null windows
|
||||
then return Nothing
|
||||
else liftM Just $ choose (0, length windows - 1)
|
||||
| windows <- wsWindows ]
|
||||
|
||||
let tags = [1 .. fromIntegral numWs]
|
||||
focusWsWindows = zip focus wsWindows
|
||||
wss = zip tags focusWsWindows -- tmp representation of a workspace (tag, windows)
|
||||
initSs = new lay tags screens
|
||||
return $
|
||||
view (fromIntegral wsIdxInFocus) $
|
||||
foldr (\(tag, (focus, windows)) ss -> -- Fold through all generated (tags,windows).
|
||||
-- set workspace active by tag and fold through all
|
||||
-- windows while inserting them. Apply the given number
|
||||
-- of `focusUp` on the resulting StackSet.
|
||||
applyN focus focusUp $ foldr insertUp (view tag ss) windows
|
||||
) initSs wss
|
||||
|
||||
|
||||
--
|
||||
-- Just generate StackSets with Char elements.
|
||||
--
|
||||
type Tag = Int
|
||||
type Window = Char
|
||||
type T = StackSet Tag Int Window Int Int
|
||||
|
||||
|
||||
|
||||
newtype EmptyStackSet = EmptyStackSet T
|
||||
deriving Show
|
||||
|
||||
instance Arbitrary EmptyStackSet where
|
||||
arbitrary = do
|
||||
(NonEmptyNubList ns) <- arbitrary
|
||||
(NonEmptyNubList sds) <- arbitrary
|
||||
l <- arbitrary
|
||||
-- there cannot be more screens than workspaces:
|
||||
return . EmptyStackSet . new l ns $ take (min (length ns) (length sds)) sds
|
||||
|
||||
|
||||
|
||||
newtype NonEmptyWindowsStackSet = NonEmptyWindowsStackSet T
|
||||
deriving Show
|
||||
|
||||
instance Arbitrary NonEmptyWindowsStackSet where
|
||||
arbitrary =
|
||||
NonEmptyWindowsStackSet `fmap` (arbitrary `suchThat` (not . null . allWindows))
|
||||
|
||||
instance Arbitrary Rectangle where
|
||||
arbitrary = Rectangle <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
|
||||
|
||||
|
||||
newtype SizedPositive = SizedPositive Int
|
||||
deriving (Eq, Ord, Show, Read)
|
||||
|
||||
instance Arbitrary SizedPositive where
|
||||
arbitrary = sized $ \s -> do x <- choose (1, max 1 s)
|
||||
return $ SizedPositive x
|
||||
|
||||
|
||||
|
||||
newtype NonEmptyNubList a = NonEmptyNubList [a]
|
||||
deriving ( Eq, Ord, Show, Read )
|
||||
|
||||
instance (Eq a, Arbitrary a) => Arbitrary (NonEmptyNubList a) where
|
||||
arbitrary = NonEmptyNubList `fmap` ((liftM nub arbitrary) `suchThat` (not . null))
|
||||
|
||||
|
||||
|
||||
-- | Pull out an arbitrary tag from the StackSet. This removes the need for the
|
||||
-- precondition "n `tagMember x` in many properties and thus reduces the number
|
||||
-- of discarded tests.
|
||||
--
|
||||
-- n <- arbitraryTag x
|
||||
--
|
||||
-- We can do the reverse with a simple `suchThat`:
|
||||
--
|
||||
-- n <- arbitrary `suchThat` \n' -> not $ n' `tagMember` x
|
||||
arbitraryTag :: T -> Gen Tag
|
||||
arbitraryTag x = do
|
||||
let ts = tags x
|
||||
-- There must be at least 1 workspace, thus at least 1 tag.
|
||||
idx <- choose (0, (length ts) - 1)
|
||||
return $ ts!!idx
|
||||
|
||||
-- | Pull out an arbitrary window from a StackSet that is guaranteed to have a
|
||||
-- non empty set of windows. This eliminates the precondition "i `member` x" in
|
||||
-- a few properties.
|
||||
--
|
||||
--
|
||||
-- foo (nex :: NonEmptyWindowsStackSet) = do
|
||||
-- let NonEmptyWindowsStackSet x = nex
|
||||
-- w <- arbitraryWindow nex
|
||||
-- return $ .......
|
||||
--
|
||||
-- We can do the reverse with a simple `suchThat`:
|
||||
--
|
||||
-- n <- arbitrary `suchThat` \n' -> not $ n `member` x
|
||||
arbitraryWindow :: NonEmptyWindowsStackSet -> Gen Window
|
||||
arbitraryWindow (NonEmptyWindowsStackSet x) = do
|
||||
let ws = allWindows x
|
||||
-- We know that there are at least 1 window in a NonEmptyWindowsStackSet.
|
||||
idx <- choose(0, (length ws) - 1)
|
||||
return $ ws!!idx
|
@@ -1,8 +0,0 @@
|
||||
module Main where
|
||||
|
||||
import qualified Properties
|
||||
|
||||
-- This will run all of the QC files for xmonad core. Currently, that's just
|
||||
-- Properties. If any more get added, sequence the main actions together.
|
||||
main = do
|
||||
Properties.main
|
1069
tests/Properties.hs
1069
tests/Properties.hs
File diff suppressed because it is too large
Load Diff
70
tests/Properties/Delete.hs
Normal file
70
tests/Properties/Delete.hs
Normal file
@@ -0,0 +1,70 @@
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
module Properties.Delete where
|
||||
|
||||
import Test.QuickCheck
|
||||
import Instances
|
||||
import Utils
|
||||
|
||||
import XMonad.StackSet hiding (filter)
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- 'delete'
|
||||
|
||||
-- deleting the current item removes it.
|
||||
prop_delete x =
|
||||
case peek x of
|
||||
Nothing -> True
|
||||
Just i -> not (member i (delete i x))
|
||||
where _ = x :: T
|
||||
|
||||
-- delete is reversible with 'insert'.
|
||||
-- It is the identiy, except for the 'master', which is reset on insert and delete.
|
||||
--
|
||||
prop_delete_insert (x :: T) =
|
||||
case peek x of
|
||||
Nothing -> True
|
||||
Just n -> insertUp n (delete n y) == y
|
||||
where
|
||||
y = swapMaster x
|
||||
|
||||
-- delete should be local
|
||||
prop_delete_local (x :: T) =
|
||||
case peek x of
|
||||
Nothing -> True
|
||||
Just i -> hidden_spaces x == hidden_spaces (delete i x)
|
||||
|
||||
-- delete should not affect focus unless the focused element is what is being deleted
|
||||
prop_delete_focus = do
|
||||
-- There should be at least two windows. One in focus, and some to try and
|
||||
-- delete (doesn't have to be windows on the current workspace). We generate
|
||||
-- our own, since we can't rely on NonEmptyWindowsStackSet returning one in
|
||||
-- the argument with at least two windows.
|
||||
x <- arbitrary `suchThat` \x' -> length (allWindows x') >= 2
|
||||
w <- arbitraryWindow (NonEmptyWindowsStackSet x)
|
||||
-- Make sure we pick a window that is NOT the currently focused
|
||||
`suchThat` \w' -> Just w' /= peek x
|
||||
return $ peek (delete w x) == peek x
|
||||
|
||||
-- focus movement in the presence of delete:
|
||||
-- when the last window in the stack set is focused, focus moves `up'.
|
||||
-- usual case is that it moves 'down'.
|
||||
prop_delete_focus_end = do
|
||||
-- Generate a StackSet with at least two windows on the current workspace.
|
||||
x <- arbitrary `suchThat` \(x' :: T) -> length (index x') >= 2
|
||||
let w = last (index x)
|
||||
y = focusWindow w x -- focus last window in stack
|
||||
return $ peek (delete w y) == peek (focusUp y)
|
||||
|
||||
|
||||
-- focus movement in the presence of delete:
|
||||
-- when not in the last item in the stack, focus moves down
|
||||
prop_delete_focus_not_end = do
|
||||
x <- arbitrary
|
||||
-- There must be at least two windows and the current focused is not the
|
||||
-- last one in the stack.
|
||||
`suchThat` \(x' :: T) ->
|
||||
let currWins = index x'
|
||||
in length (currWins) >= 2 && peek x' /= Just (last currWins)
|
||||
-- This is safe, as we know there are >= 2 windows
|
||||
let Just n = peek x
|
||||
return $ peek (delete n x) == peek (focusDown x)
|
30
tests/Properties/Failure.hs
Normal file
30
tests/Properties/Failure.hs
Normal file
@@ -0,0 +1,30 @@
|
||||
module Properties.Failure where
|
||||
|
||||
import XMonad.StackSet hiding (filter)
|
||||
|
||||
import qualified Control.Exception.Extensible as C
|
||||
import System.IO.Unsafe
|
||||
import Data.List (isPrefixOf)
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- testing for failure and help out hpc
|
||||
--
|
||||
-- Since base 4.9.0.0 `error` appends a stack trace. The tests below
|
||||
-- use `isPrefixOf` to only test equality on the error message.
|
||||
--
|
||||
prop_abort :: Int -> Bool
|
||||
prop_abort _ = unsafePerformIO $ C.catch (abort "fail") check
|
||||
where
|
||||
check (C.SomeException e) =
|
||||
return $ "xmonad: StackSet: fail" `isPrefixOf` show e
|
||||
|
||||
-- new should fail with an abort
|
||||
prop_new_abort :: Int -> Bool
|
||||
prop_new_abort _ = unsafePerformIO $ C.catch f check
|
||||
where
|
||||
f = new undefined{-layout-} [] [] `seq` return False
|
||||
check (C.SomeException e) =
|
||||
return $ "xmonad: StackSet: non-positive argument to StackSet.new" `isPrefixOf` show e
|
||||
|
||||
-- TODO: Fix this?
|
||||
-- prop_view_should_fail = view {- with some bogus data -}
|
36
tests/Properties/Floating.hs
Normal file
36
tests/Properties/Floating.hs
Normal file
@@ -0,0 +1,36 @@
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
module Properties.Floating where
|
||||
|
||||
import Test.QuickCheck
|
||||
import Instances
|
||||
|
||||
import XMonad.StackSet hiding (filter)
|
||||
|
||||
import qualified Data.Map as M
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- properties for the floating layer:
|
||||
|
||||
prop_float_reversible (nex :: NonEmptyWindowsStackSet) = do
|
||||
let NonEmptyWindowsStackSet x = nex
|
||||
w <- arbitraryWindow nex
|
||||
return $ sink w (float w geom x) == x
|
||||
where
|
||||
geom = RationalRect 100 100 100 100
|
||||
|
||||
prop_float_geometry (nex :: NonEmptyWindowsStackSet) = do
|
||||
let NonEmptyWindowsStackSet x = nex
|
||||
w <- arbitraryWindow nex
|
||||
let s = float w geom x
|
||||
return $ M.lookup w (floating s) == Just geom
|
||||
where
|
||||
geom = RationalRect 100 100 100 100
|
||||
|
||||
prop_float_delete (nex :: NonEmptyWindowsStackSet) = do
|
||||
let NonEmptyWindowsStackSet x = nex
|
||||
w <- arbitraryWindow nex
|
||||
let s = float w geom x
|
||||
t = delete w s
|
||||
return $ not (w `member` t)
|
||||
where
|
||||
geom = RationalRect 100 100 100 100
|
74
tests/Properties/Focus.hs
Normal file
74
tests/Properties/Focus.hs
Normal file
@@ -0,0 +1,74 @@
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
module Properties.Focus where
|
||||
|
||||
import Test.QuickCheck
|
||||
import Instances
|
||||
import Utils
|
||||
|
||||
import XMonad.StackSet hiding (filter)
|
||||
|
||||
import Data.Maybe (fromJust)
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- rotating focus
|
||||
--
|
||||
|
||||
-- master/focus
|
||||
--
|
||||
-- The tiling order, and master window, of a stack is unaffected by focus changes.
|
||||
--
|
||||
prop_focus_left_master (SizedPositive n) (x::T) =
|
||||
index (applyN (Just n) focusUp x) == index x
|
||||
prop_focus_right_master (SizedPositive n) (x::T) =
|
||||
index (applyN (Just n) focusDown x) == index x
|
||||
prop_focus_master_master (SizedPositive n) (x::T) =
|
||||
index (applyN (Just n) focusMaster x) == index x
|
||||
|
||||
prop_focusWindow_master (NonNegative n) (x :: T) =
|
||||
case peek x of
|
||||
Nothing -> True
|
||||
Just _ -> let s = index x
|
||||
i = n `mod` length s
|
||||
in index (focusWindow (s !! i) x) == index x
|
||||
|
||||
-- shifting focus is trivially reversible
|
||||
prop_focus_left (x :: T) = (focusUp (focusDown x)) == x
|
||||
prop_focus_right (x :: T) = (focusDown (focusUp x)) == x
|
||||
|
||||
-- focus master is idempotent
|
||||
prop_focusMaster_idem (x :: T) = focusMaster x == focusMaster (focusMaster x)
|
||||
|
||||
-- focusWindow actually leaves the window focused...
|
||||
prop_focusWindow_works (NonNegative (n :: Int)) (x :: T) =
|
||||
case peek x of
|
||||
Nothing -> True
|
||||
Just _ -> let s = index x
|
||||
i = fromIntegral n `mod` length s
|
||||
in (focus . fromJust . stack . workspace . current) (focusWindow (s !! i) x) == (s !! i)
|
||||
|
||||
-- rotation through the height of a stack gets us back to the start
|
||||
prop_focus_all_l (x :: T) = (foldr (const focusUp) x [1..n]) == x
|
||||
where n = length (index x)
|
||||
prop_focus_all_r (x :: T) = (foldr (const focusDown) x [1..n]) == x
|
||||
where n = length (index x)
|
||||
|
||||
-- prop_rotate_all (x :: T) = f (f x) == f x
|
||||
-- f x' = foldr (\_ y -> rotate GT y) x' [1..n]
|
||||
|
||||
-- focus is local to the current workspace
|
||||
prop_focus_down_local (x :: T) = hidden_spaces (focusDown x) == hidden_spaces x
|
||||
prop_focus_up_local (x :: T) = hidden_spaces (focusUp x) == hidden_spaces x
|
||||
|
||||
prop_focus_master_local (x :: T) = hidden_spaces (focusMaster x) == hidden_spaces x
|
||||
|
||||
prop_focusWindow_local (NonNegative (n :: Int)) (x::T ) =
|
||||
case peek x of
|
||||
Nothing -> True
|
||||
Just _ -> let s = index x
|
||||
i = fromIntegral n `mod` length s
|
||||
in hidden_spaces (focusWindow (s !! i) x) == hidden_spaces x
|
||||
|
||||
-- On an invalid window, the stackset is unmodified
|
||||
prop_focusWindow_identity (x::T ) = do
|
||||
n <- arbitrary `suchThat` \n' -> not $ n' `member` x
|
||||
return $ focusWindow n x == x
|
44
tests/Properties/GreedyView.hs
Normal file
44
tests/Properties/GreedyView.hs
Normal file
@@ -0,0 +1,44 @@
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
module Properties.GreedyView where
|
||||
|
||||
import Test.QuickCheck
|
||||
import Instances
|
||||
import Utils
|
||||
|
||||
import XMonad.StackSet hiding (filter)
|
||||
|
||||
import Data.List (sortBy)
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- greedyViewing workspaces
|
||||
|
||||
-- greedyView sets the current workspace to 'n'
|
||||
prop_greedyView_current (x :: T) = do
|
||||
n <- arbitraryTag x
|
||||
return $ currentTag (greedyView n x) == n
|
||||
|
||||
-- greedyView leaves things unchanged for invalid workspaces
|
||||
prop_greedyView_current_id (x :: T) = do
|
||||
n <- arbitrary `suchThat` \n' -> not $ n' `tagMember` x
|
||||
return $ currentTag (greedyView n x) == currentTag x
|
||||
|
||||
-- greedyView *only* sets the current workspace, and touches Xinerama.
|
||||
-- no workspace contents will be changed.
|
||||
prop_greedyView_local (x :: T) = do
|
||||
n <- arbitraryTag x
|
||||
return $ workspaces x == workspaces (greedyView n x)
|
||||
where
|
||||
workspaces a = sortBy (\s t -> tag s `compare` tag t) $
|
||||
workspace (current a)
|
||||
: map workspace (visible a) ++ hidden a
|
||||
|
||||
-- greedyView is idempotent
|
||||
prop_greedyView_idem (x :: T) = do
|
||||
n <- arbitraryTag x
|
||||
return $ greedyView n (greedyView n x) == (greedyView n x)
|
||||
|
||||
-- greedyView is reversible, though shuffles the order of hidden/visible
|
||||
prop_greedyView_reversible (x :: T) = do
|
||||
n <- arbitraryTag x
|
||||
return $ normal (greedyView n' (greedyView n x)) == normal x
|
||||
where n' = currentTag x
|
52
tests/Properties/Insert.hs
Normal file
52
tests/Properties/Insert.hs
Normal file
@@ -0,0 +1,52 @@
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
module Properties.Insert where
|
||||
|
||||
import Test.QuickCheck
|
||||
import Instances
|
||||
import Utils
|
||||
|
||||
import XMonad.StackSet hiding (filter)
|
||||
|
||||
import Data.List (nub)
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- 'insert'
|
||||
|
||||
-- inserting a item into an empty stackset means that item is now a member
|
||||
prop_insert_empty i (EmptyStackSet x)= member i (insertUp i x)
|
||||
|
||||
-- insert should be idempotent
|
||||
prop_insert_idem i (x :: T) = insertUp i x == insertUp i (insertUp i x)
|
||||
|
||||
-- insert when an item is a member should leave the stackset unchanged
|
||||
prop_insert_duplicate (nex :: NonEmptyWindowsStackSet) = do
|
||||
let NonEmptyWindowsStackSet x = nex
|
||||
w <- arbitraryWindow nex
|
||||
return $ insertUp w x == x
|
||||
|
||||
-- push shouldn't change anything but the current workspace
|
||||
prop_insert_local (x :: T) = do
|
||||
i <- arbitrary `suchThat` \i' -> not $ i' `member` x
|
||||
return $ hidden_spaces x == hidden_spaces (insertUp i x)
|
||||
|
||||
-- Inserting a (unique) list of items into an empty stackset should
|
||||
-- result in the last inserted element having focus.
|
||||
prop_insert_peek (EmptyStackSet x) (NonEmptyNubList is) =
|
||||
peek (foldr insertUp x is) == Just (head is)
|
||||
|
||||
-- insert >> delete is the identity, when i `notElem` .
|
||||
-- Except for the 'master', which is reset on insert and delete.
|
||||
--
|
||||
prop_insert_delete x = do
|
||||
n <- arbitrary `suchThat` \n -> not $ n `member` x
|
||||
return $ delete n (insertUp n y) == (y :: T)
|
||||
where
|
||||
y = swapMaster x -- sets the master window to the current focus.
|
||||
-- otherwise, we don't have a rule for where master goes.
|
||||
|
||||
-- inserting n elements increases current stack size by n
|
||||
prop_size_insert is (EmptyStackSet x) =
|
||||
size (foldr insertUp x ws ) == (length ws)
|
||||
where
|
||||
ws = nub is
|
||||
size = length . index
|
34
tests/Properties/Layout/Full.hs
Normal file
34
tests/Properties/Layout/Full.hs
Normal file
@@ -0,0 +1,34 @@
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
module Properties.Layout.Full where
|
||||
|
||||
import Test.QuickCheck
|
||||
import Instances
|
||||
|
||||
import XMonad.StackSet hiding (filter)
|
||||
import XMonad.Core
|
||||
import XMonad.Layout
|
||||
|
||||
import Data.Maybe
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Full layout
|
||||
|
||||
-- pureLayout works for Full
|
||||
prop_purelayout_full rect = do
|
||||
x <- (arbitrary :: Gen T) `suchThat` (isJust . peek)
|
||||
let layout = Full
|
||||
st = fromJust . stack . workspace . current $ x
|
||||
ts = pureLayout layout rect st
|
||||
return $
|
||||
length ts == 1 -- only one window to view
|
||||
&&
|
||||
snd (head ts) == rect -- and sets fullscreen
|
||||
&&
|
||||
fst (head ts) == fromJust (peek x) -- and the focused window is shown
|
||||
|
||||
|
||||
-- what happens when we send an IncMaster message to Full --- Nothing
|
||||
prop_sendmsg_full (NonNegative k) =
|
||||
isNothing (Full `pureMessage` (SomeMessage (IncMasterN k)))
|
||||
|
||||
prop_desc_full = description Full == show Full
|
116
tests/Properties/Layout/Tall.hs
Normal file
116
tests/Properties/Layout/Tall.hs
Normal file
@@ -0,0 +1,116 @@
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
module Properties.Layout.Tall where
|
||||
|
||||
import Test.QuickCheck
|
||||
import Instances
|
||||
import Utils
|
||||
|
||||
import XMonad.StackSet hiding (filter)
|
||||
import XMonad.Core
|
||||
import XMonad.Layout
|
||||
|
||||
import Graphics.X11.Xlib.Types (Rectangle(..))
|
||||
|
||||
import Data.Maybe
|
||||
import Data.List (sort)
|
||||
import Data.Ratio
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- The Tall layout
|
||||
|
||||
-- 1 window should always be tiled fullscreen
|
||||
prop_tile_fullscreen rect = tile pct rect 1 1 == [rect]
|
||||
where pct = 1/2
|
||||
|
||||
-- multiple windows
|
||||
prop_tile_non_overlap rect windows nmaster = noOverlaps (tile pct rect nmaster windows)
|
||||
where _ = rect :: Rectangle
|
||||
pct = 3 % 100
|
||||
|
||||
-- splitting horizontally yields sensible results
|
||||
prop_split_horizontal (NonNegative n) x =
|
||||
(noOverflows (+) (rect_x x) (rect_width x)) ==>
|
||||
sum (map rect_width xs) == rect_width x
|
||||
&&
|
||||
all (== rect_height x) (map rect_height xs)
|
||||
&&
|
||||
(map rect_x xs) == (sort $ map rect_x xs)
|
||||
|
||||
where
|
||||
xs = splitHorizontally n x
|
||||
|
||||
-- splitting vertically yields sensible results
|
||||
prop_split_vertical (r :: Rational) x =
|
||||
rect_x x == rect_x a && rect_x x == rect_x b
|
||||
&&
|
||||
rect_width x == rect_width a && rect_width x == rect_width b
|
||||
where
|
||||
(a,b) = splitVerticallyBy r x
|
||||
|
||||
|
||||
-- pureLayout works.
|
||||
prop_purelayout_tall n r1 r2 rect = do
|
||||
x <- (arbitrary :: Gen T) `suchThat` (isJust . peek)
|
||||
let layout = Tall n r1 r2
|
||||
st = fromJust . stack . workspace . current $ x
|
||||
ts = pureLayout layout rect st
|
||||
return $
|
||||
length ts == length (index x)
|
||||
&&
|
||||
noOverlaps (map snd ts)
|
||||
&&
|
||||
description layout == "Tall"
|
||||
|
||||
|
||||
-- Test message handling of Tall
|
||||
|
||||
-- what happens when we send a Shrink message to Tall
|
||||
prop_shrink_tall (NonNegative n) (Positive delta) (NonNegative frac) =
|
||||
n == n' && delta == delta' -- these state components are unchanged
|
||||
&& frac' <= frac && (if frac' < frac then frac' == 0 || frac' == frac - delta
|
||||
else frac == 0 )
|
||||
-- remaining fraction should shrink
|
||||
where
|
||||
l1 = Tall n delta frac
|
||||
Just l2@(Tall n' delta' frac') = l1 `pureMessage` (SomeMessage Shrink)
|
||||
-- pureMessage :: layout a -> SomeMessage -> Maybe (layout a)
|
||||
|
||||
|
||||
-- what happens when we send a Shrink message to Tall
|
||||
prop_expand_tall (NonNegative n)
|
||||
(Positive delta)
|
||||
(NonNegative n1)
|
||||
(Positive d1) =
|
||||
|
||||
n == n'
|
||||
&& delta == delta' -- these state components are unchanged
|
||||
&& frac' >= frac
|
||||
&& (if frac' > frac
|
||||
then frac' == 1 || frac' == frac + delta
|
||||
else frac == 1 )
|
||||
|
||||
-- remaining fraction should shrink
|
||||
where
|
||||
frac = min 1 (n1 % d1)
|
||||
l1 = Tall n delta frac
|
||||
Just l2@(Tall n' delta' frac') = l1 `pureMessage` (SomeMessage Expand)
|
||||
-- pureMessage :: layout a -> SomeMessage -> Maybe (layout a)
|
||||
|
||||
-- what happens when we send an IncMaster message to Tall
|
||||
prop_incmaster_tall (NonNegative n) (Positive delta) (NonNegative frac)
|
||||
(NonNegative k) =
|
||||
delta == delta' && frac == frac' && n' == n + k
|
||||
where
|
||||
l1 = Tall n delta frac
|
||||
Just l2@(Tall n' delta' frac') = l1 `pureMessage` (SomeMessage (IncMasterN k))
|
||||
-- pureMessage :: layout a -> SomeMessage -> Maybe (layout a)
|
||||
|
||||
|
||||
|
||||
-- toMessage LT = SomeMessage Shrink
|
||||
-- toMessage EQ = SomeMessage Expand
|
||||
-- toMessage GT = SomeMessage (IncMasterN 1)
|
||||
|
||||
|
||||
prop_desc_mirror n r1 r2 = description (Mirror $! t) == "Mirror Tall"
|
||||
where t = Tall n r1 r2
|
73
tests/Properties/Screen.hs
Normal file
73
tests/Properties/Screen.hs
Normal file
@@ -0,0 +1,73 @@
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
module Properties.Screen where
|
||||
|
||||
import Utils
|
||||
import Test.QuickCheck
|
||||
import Instances
|
||||
|
||||
import Control.Applicative
|
||||
import XMonad.StackSet hiding (filter)
|
||||
import XMonad.Operations
|
||||
import Graphics.X11.Xlib.Types (Dimension)
|
||||
|
||||
import Graphics.X11 (Rectangle(Rectangle))
|
||||
import XMonad.Layout
|
||||
|
||||
prop_screens (x :: T) = n `elem` screens x
|
||||
where
|
||||
n = current x
|
||||
|
||||
-- screens makes sense
|
||||
prop_screens_works (x :: T) = screens x == current x : visible x
|
||||
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Hints
|
||||
|
||||
prop_resize_inc (Positive inc_w,Positive inc_h) b@(w,h) =
|
||||
w' `mod` inc_w == 0 && h' `mod` inc_h == 0
|
||||
where (w',h') = applyResizeIncHint a b
|
||||
a = (inc_w,inc_h)
|
||||
|
||||
prop_resize_inc_extra ((NonNegative inc_w)) b@(w,h) =
|
||||
(w,h) == (w',h')
|
||||
where (w',h') = applyResizeIncHint a b
|
||||
a = (-inc_w,0::Dimension)-- inc_h)
|
||||
|
||||
prop_resize_max (Positive inc_w,Positive inc_h) b@(w,h) =
|
||||
w' <= inc_w && h' <= inc_h
|
||||
where (w',h') = applyMaxSizeHint a b
|
||||
a = (inc_w,inc_h)
|
||||
|
||||
prop_resize_max_extra ((NonNegative inc_w)) b@(w,h) =
|
||||
(w,h) == (w',h')
|
||||
where (w',h') = applyMaxSizeHint a b
|
||||
a = (-inc_w,0::Dimension)-- inc_h)
|
||||
|
||||
|
||||
prop_aspect_hint_shrink hint (w,h) = case applyAspectHint hint (w,h) of
|
||||
(w',h') -> w' <= w && h' <= h
|
||||
|
||||
|
||||
-- applyAspectHint does nothing when the supplied (x,y) fits
|
||||
-- the desired range
|
||||
prop_aspect_fits =
|
||||
forAll ((,,,) <$> pos <*> pos <*> pos <*> pos) $ \ (x,y,a,b) ->
|
||||
let f v = applyAspectHint ((x, y+a), (x+b, y)) v
|
||||
in and [ 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) =
|
||||
forAll ((,) <$>
|
||||
choose (0, fromIntegral w - 1) <*>
|
||||
choose (0, fromIntegral h - 1)) $
|
||||
\(dx,dy) ->
|
||||
and [ dx > 0, dy > 0,
|
||||
noOverflows (\ a b -> a + abs b) x w,
|
||||
noOverflows (\ a b -> a + abs b) y h ]
|
||||
==> pointWithin (x+dx) (y+dy) r
|
||||
|
||||
prop_point_within_mirror r (x,y) = pointWithin x y r == pointWithin y x (mirrorRect r)
|
70
tests/Properties/Shift.hs
Normal file
70
tests/Properties/Shift.hs
Normal file
@@ -0,0 +1,70 @@
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
module Properties.Shift where
|
||||
|
||||
import Test.QuickCheck
|
||||
import Instances
|
||||
import Utils
|
||||
|
||||
import XMonad.StackSet hiding (filter)
|
||||
|
||||
import qualified Data.List as L
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- shift
|
||||
|
||||
-- shift is fully reversible on current window, when focus and master
|
||||
-- are the same. otherwise, master may move.
|
||||
prop_shift_reversible (x :: T) = do
|
||||
i <- arbitraryTag x
|
||||
case peek y of
|
||||
Nothing -> return True
|
||||
Just _ -> return $ normal ((view n . shift n . view i . shift i) y) == normal y
|
||||
where
|
||||
y = swapMaster x
|
||||
n = currentTag y
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- shiftMaster
|
||||
|
||||
-- focus/local/idempotent same as swapMaster:
|
||||
prop_shift_master_focus (x :: T) = peek x == (peek $ shiftMaster x)
|
||||
prop_shift_master_local (x :: T) = hidden_spaces x == hidden_spaces (shiftMaster x)
|
||||
prop_shift_master_idempotent (x :: T) = shiftMaster (shiftMaster x) == shiftMaster x
|
||||
-- ordering is constant modulo the focused window:
|
||||
prop_shift_master_ordering (x :: T) = case peek x of
|
||||
Nothing -> True
|
||||
Just m -> L.delete m (index x) == L.delete m (index $ shiftMaster x)
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- shiftWin
|
||||
|
||||
-- shiftWin on current window is the same as shift
|
||||
prop_shift_win_focus (x :: T) = do
|
||||
n <- arbitraryTag x
|
||||
case peek x of
|
||||
Nothing -> return True
|
||||
Just w -> return $ shiftWin n w x == shift n x
|
||||
|
||||
-- shiftWin on a non-existant window is identity
|
||||
prop_shift_win_indentity (x :: T) = do
|
||||
n <- arbitraryTag x
|
||||
w <- arbitrary `suchThat` \w' -> not (w' `member` x)
|
||||
return $ shiftWin n w x == x
|
||||
|
||||
-- shiftWin leaves the current screen as it is, if neither n is the tag
|
||||
-- of the current workspace nor w on the current workspace
|
||||
prop_shift_win_fix_current = do
|
||||
x <- arbitrary `suchThat` \(x' :: T) ->
|
||||
-- Invariant, otherWindows are NOT in the current workspace.
|
||||
let otherWindows = allWindows x' L.\\ index x'
|
||||
in length(tags x') >= 2 && length(otherWindows) >= 1
|
||||
-- Sadly we have to construct `otherWindows` again, for the actual StackSet
|
||||
-- that got chosen.
|
||||
let otherWindows = allWindows x L.\\ index x
|
||||
-- We know such tag must exists, due to the precondition
|
||||
n <- arbitraryTag x `suchThat` (/= currentTag x)
|
||||
-- we know length is >= 1, from above precondition
|
||||
idx <- choose(0, length(otherWindows) - 1)
|
||||
let w = otherWindows !! idx
|
||||
return $ (current $ x) == (current $ shiftWin n w x)
|
||||
|
51
tests/Properties/Stack.hs
Normal file
51
tests/Properties/Stack.hs
Normal file
@@ -0,0 +1,51 @@
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
module Properties.Stack where
|
||||
|
||||
import Test.QuickCheck
|
||||
import Instances
|
||||
|
||||
import XMonad.StackSet hiding (filter)
|
||||
import qualified XMonad.StackSet as S (filter)
|
||||
|
||||
import Data.Maybe
|
||||
|
||||
|
||||
-- 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
|
||||
Just it -> length (index x) == length (focus it : up it ++ down it)
|
||||
|
||||
|
||||
-- For all windows in the stackSet, findTag should identify the
|
||||
-- correct workspace
|
||||
prop_findIndex (x :: T) =
|
||||
and [ tag w == fromJust (findTag i x)
|
||||
| w <- workspace (current x) : map workspace (visible x) ++ hidden x
|
||||
, t <- maybeToList (stack w)
|
||||
, i <- focus t : up t ++ down t
|
||||
]
|
||||
|
||||
prop_allWindowsMember (NonEmptyWindowsStackSet x) = do
|
||||
-- Reimplementation of arbitraryWindow, but to make sure that
|
||||
-- implementation doesn't change in the future, and stop using allWindows,
|
||||
-- which is a key component in this test (together with member).
|
||||
let ws = allWindows x
|
||||
-- We know that there are at least 1 window in a NonEmptyWindowsStackSet.
|
||||
idx <- choose(0, (length ws) - 1)
|
||||
return $ member (ws!!idx) x
|
||||
|
||||
|
||||
-- preserve order
|
||||
prop_filter_order (x :: T) =
|
||||
case stack $ workspace $ current x of
|
||||
Nothing -> True
|
||||
Just s@(Stack i _ _) -> integrate' (S.filter (/= i) s) == filter (/= i) (integrate' (Just s))
|
||||
|
||||
-- differentiate should return Nothing if the list is empty or Just stack, with
|
||||
-- the first element of the list is current, and the rest of the list is down.
|
||||
prop_differentiate xs =
|
||||
if null xs then differentiate xs == Nothing
|
||||
else (differentiate xs) == Just (Stack (head xs) [] (tail xs))
|
||||
where _ = xs :: [Int]
|
135
tests/Properties/StackSet.hs
Normal file
135
tests/Properties/StackSet.hs
Normal file
@@ -0,0 +1,135 @@
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
module Properties.StackSet where
|
||||
|
||||
import Test.QuickCheck
|
||||
import Instances
|
||||
import Utils
|
||||
|
||||
import XMonad.StackSet hiding (filter)
|
||||
|
||||
import Data.Maybe
|
||||
|
||||
import Data.List (nub)
|
||||
-- ---------------------------------------------------------------------
|
||||
-- QuickCheck properties for the StackSet
|
||||
|
||||
-- Some general hints for creating StackSet properties:
|
||||
--
|
||||
-- * ops that mutate the StackSet are usually local
|
||||
-- * most ops on StackSet should either be trivially reversible, or
|
||||
-- idempotent, or both.
|
||||
------------------------------------------------------------------------
|
||||
|
||||
-- Basic data invariants of the StackSet
|
||||
--
|
||||
-- With the new zipper-based StackSet, tracking focus is no longer an
|
||||
-- issue: the data structure enforces focus by construction.
|
||||
--
|
||||
-- But we still need to ensure there are no duplicates, and master/and
|
||||
-- the xinerama mapping aren't checked by the data structure at all.
|
||||
--
|
||||
-- * no element should ever appear more than once in a StackSet
|
||||
-- * the xinerama screen map should be:
|
||||
-- -- keys should always index valid workspaces
|
||||
-- -- monotonically ascending in the elements
|
||||
-- * the current workspace should be a member of the xinerama screens
|
||||
--
|
||||
invariant (s :: T) = and
|
||||
-- no duplicates
|
||||
[ noDuplicates
|
||||
|
||||
-- TODO: Fix this.
|
||||
-- all this xinerama stuff says we don't have the right structure
|
||||
-- , validScreens
|
||||
-- , validWorkspaces
|
||||
-- , inBounds
|
||||
]
|
||||
where
|
||||
ws = concat [ focus t : up t ++ down t
|
||||
| w <- workspace (current s) : map workspace (visible s) ++ hidden s
|
||||
, t <- maybeToList (stack w)] :: [Char]
|
||||
noDuplicates = nub ws == ws
|
||||
|
||||
-- validScreens = monotonic . sort . M. . (W.current s : W.visible : W$ s
|
||||
|
||||
-- validWorkspaces = and [ w `elem` allworkspaces | w <- (M.keys . screens) s ]
|
||||
-- where allworkspaces = map tag $ current s : prev s ++ next s
|
||||
|
||||
-- inBounds = and [ w >=0 && w < size s | (w,sc) <- M.assocs (screens s) ]
|
||||
|
||||
monotonic [] = True
|
||||
monotonic (x:[]) = True
|
||||
monotonic (x:y:zs) | x == y-1 = monotonic (y:zs)
|
||||
| otherwise = False
|
||||
|
||||
prop_invariant = invariant
|
||||
|
||||
-- and check other ops preserve invariants
|
||||
prop_empty_I (SizedPositive n) l = forAll (choose (1, fromIntegral n)) $ \m ->
|
||||
forAll (vector m) $ \ms ->
|
||||
invariant $ new l [0..fromIntegral n-1] ms
|
||||
|
||||
prop_view_I n (x :: T) =
|
||||
invariant $ view n x
|
||||
|
||||
prop_greedyView_I n (x :: T) =
|
||||
invariant $ greedyView n x
|
||||
|
||||
prop_focusUp_I (SizedPositive n) (x :: T) =
|
||||
invariant $ applyN (Just n) focusUp x
|
||||
prop_focusMaster_I (SizedPositive n) (x :: T) =
|
||||
invariant $ applyN (Just n) focusMaster x
|
||||
prop_focusDown_I (SizedPositive n) (x :: T) =
|
||||
invariant $ applyN (Just n) focusDown x
|
||||
|
||||
prop_focus_I (SizedPositive n) (x :: T) =
|
||||
case peek x of
|
||||
Nothing -> True
|
||||
Just _ -> let w = focus . fromJust . stack . workspace . current $
|
||||
applyN (Just n) focusUp x
|
||||
in invariant $ focusWindow w x
|
||||
|
||||
prop_insertUp_I n (x :: T) = invariant $ insertUp n x
|
||||
|
||||
prop_delete_I (x :: T) = invariant $
|
||||
case peek x of
|
||||
Nothing -> x
|
||||
Just i -> delete i x
|
||||
|
||||
prop_swap_master_I (x :: T) = invariant $ swapMaster x
|
||||
|
||||
prop_swap_left_I (SizedPositive n) (x :: T) =
|
||||
invariant $ applyN (Just n) swapUp x
|
||||
prop_swap_right_I (SizedPositive n) (x :: T) =
|
||||
invariant $ applyN (Just n) swapDown x
|
||||
|
||||
prop_shift_I (x :: T) = do
|
||||
n <- arbitraryTag x
|
||||
return $ invariant $ shift (fromIntegral n) x
|
||||
|
||||
prop_shift_win_I (nex :: NonEmptyWindowsStackSet) = do
|
||||
let NonEmptyWindowsStackSet x = nex
|
||||
w <- arbitraryWindow nex
|
||||
n <- arbitraryTag x
|
||||
return $ invariant $ shiftWin n w x
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
|
||||
|
||||
-- empty StackSets have no windows in them
|
||||
prop_empty (EmptyStackSet x) =
|
||||
all (== Nothing) [ stack w | w <- workspace (current x)
|
||||
: map workspace (visible x) ++ hidden x ]
|
||||
|
||||
-- empty StackSets always have focus on first workspace
|
||||
prop_empty_current (EmptyStackSet x) = currentTag x == head (tags x)
|
||||
|
||||
-- no windows will be a member of an empty workspace
|
||||
prop_member_empty i (EmptyStackSet x) = member i x == False
|
||||
|
||||
-- peek either yields nothing on the Empty workspace, or Just a valid window
|
||||
prop_member_peek (x :: T) =
|
||||
case peek x of
|
||||
Nothing -> True {- then we don't know anything -}
|
||||
Just i -> member i x
|
47
tests/Properties/Swap.hs
Normal file
47
tests/Properties/Swap.hs
Normal file
@@ -0,0 +1,47 @@
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
module Properties.Swap where
|
||||
|
||||
import Test.QuickCheck
|
||||
import Instances
|
||||
import Utils
|
||||
|
||||
import XMonad.StackSet hiding (filter)
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- swapUp, swapDown, swapMaster: reordiring windows
|
||||
|
||||
-- swap is trivially reversible
|
||||
prop_swap_left (x :: T) = (swapUp (swapDown x)) == x
|
||||
prop_swap_right (x :: T) = (swapDown (swapUp x)) == x
|
||||
-- TODO swap is reversible
|
||||
-- swap is reversible, but involves moving focus back the window with
|
||||
-- master on it. easy to do with a mouse...
|
||||
{-
|
||||
prop_promote_reversible x b = (not . null . fromMaybe [] . flip index x . current $ x) ==>
|
||||
(raiseFocus y . promote . raiseFocus z . promote) x == x
|
||||
where _ = x :: T
|
||||
dir = if b then LT else GT
|
||||
(Just y) = peek x
|
||||
(Just (z:_)) = flip index x . current $ x
|
||||
-}
|
||||
|
||||
-- swap doesn't change focus
|
||||
prop_swap_master_focus (x :: T) = peek x == (peek $ swapMaster x)
|
||||
-- = case peek x of
|
||||
-- Nothing -> True
|
||||
-- Just f -> focus (stack (workspace $ current (swap x))) == f
|
||||
prop_swap_left_focus (x :: T) = peek x == (peek $ swapUp x)
|
||||
prop_swap_right_focus (x :: T) = peek x == (peek $ swapDown x)
|
||||
|
||||
-- swap is local
|
||||
prop_swap_master_local (x :: T) = hidden_spaces x == hidden_spaces (swapMaster x)
|
||||
prop_swap_left_local (x :: T) = hidden_spaces x == hidden_spaces (swapUp x)
|
||||
prop_swap_right_local (x :: T) = hidden_spaces x == hidden_spaces (swapDown x)
|
||||
|
||||
-- rotation through the height of a stack gets us back to the start
|
||||
prop_swap_all_l (x :: T) = (foldr (const swapUp) x [1..n]) == x
|
||||
where n = length (index x)
|
||||
prop_swap_all_r (x :: T) = (foldr (const swapDown) x [1..n]) == x
|
||||
where n = length (index x)
|
||||
|
||||
prop_swap_master_idempotent (x :: T) = swapMaster (swapMaster x) == swapMaster x
|
47
tests/Properties/View.hs
Normal file
47
tests/Properties/View.hs
Normal file
@@ -0,0 +1,47 @@
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
module Properties.View where
|
||||
|
||||
import Test.QuickCheck
|
||||
import Instances
|
||||
import Utils
|
||||
|
||||
import XMonad.StackSet hiding (filter)
|
||||
|
||||
import Data.List (sortBy)
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- viewing workspaces
|
||||
|
||||
-- view sets the current workspace to 'n'
|
||||
prop_view_current (x :: T) = do
|
||||
n <- arbitraryTag x
|
||||
return $ (tag . workspace . current . view n) x == n
|
||||
|
||||
-- view *only* sets the current workspace, and touches Xinerama.
|
||||
-- no workspace contents will be changed.
|
||||
prop_view_local (x :: T) = do
|
||||
n <- arbitraryTag x
|
||||
return $ workspaces x == workspaces (view n x)
|
||||
where
|
||||
workspaces a = sortBy (\s t -> tag s `compare` tag t) $
|
||||
workspace (current a)
|
||||
: map workspace (visible a) ++ hidden a
|
||||
|
||||
-- TODO: Fix this
|
||||
-- view should result in a visible xinerama screen
|
||||
-- prop_view_xinerama (x :: T) (n :: NonNegative Int) = i `tagMember` x ==>
|
||||
-- M.member i (screens (view i x))
|
||||
-- where
|
||||
-- i = fromIntegral n
|
||||
|
||||
-- view is idempotent
|
||||
prop_view_idem (x :: T) = do
|
||||
n <- arbitraryTag x
|
||||
return $ view n (view n x) == (view n x)
|
||||
|
||||
-- view is reversible, though shuffles the order of hidden/visible
|
||||
prop_view_reversible (x :: T) = do
|
||||
n <- arbitraryTag x
|
||||
return $ normal (view n' (view n x)) == normal x
|
||||
where
|
||||
n' = currentTag x
|
65
tests/Properties/Workspace.hs
Normal file
65
tests/Properties/Workspace.hs
Normal file
@@ -0,0 +1,65 @@
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
module Properties.Workspace where
|
||||
|
||||
import Test.QuickCheck
|
||||
import Instances
|
||||
import Utils
|
||||
|
||||
import XMonad.StackSet hiding (filter)
|
||||
|
||||
import Data.Maybe
|
||||
|
||||
-- looking up the tag of the current workspace should always produce a tag.
|
||||
prop_lookup_current (x :: T) = lookupWorkspace scr x == Just tg
|
||||
where
|
||||
(Screen (Workspace tg _ _) scr _) = current x
|
||||
|
||||
-- looking at a visible tag
|
||||
prop_lookup_visible = do
|
||||
-- make sure we have some xinerama screens.
|
||||
x <- arbitrary `suchThat` \(x' :: T) -> visible x' /= []
|
||||
let tags = [ tag (workspace y) | y <- visible x ]
|
||||
scr = last [ screen y | y <- visible x ]
|
||||
return $ fromJust (lookupWorkspace scr x) `elem` tags
|
||||
|
||||
|
||||
prop_currentTag (x :: T) =
|
||||
currentTag x == tag (workspace (current x))
|
||||
|
||||
-- Rename a given tag if present in the StackSet.
|
||||
prop_rename1 (x::T) = do
|
||||
o <- arbitraryTag x
|
||||
n <- arbitrary `suchThat` \n' -> not $ n' `tagMember` x
|
||||
-- Rename o to n
|
||||
let y = renameTag o n x
|
||||
return $ n `tagMember` y
|
||||
|
||||
-- Ensure that a given set of workspace tags is present by renaming
|
||||
-- existing workspaces and\/or creating new hidden workspaces as
|
||||
-- necessary.
|
||||
--
|
||||
prop_ensure (x :: T) l xs = let y = ensureTags l xs x
|
||||
in and [ n `tagMember` y | n <- xs ]
|
||||
|
||||
-- adding a tag should create a new hidden workspace
|
||||
prop_ensure_append (x :: T) l = do
|
||||
n <- arbitrary `suchThat` \n' -> not $ n' `tagMember` x
|
||||
let ts = tags x
|
||||
y = ensureTags l (n:ts) x
|
||||
return $ hidden y /= hidden x -- doesn't append, renames
|
||||
&& and [ isNothing (stack z) && layout z == l | z <- hidden y, tag z == n ]
|
||||
|
||||
|
||||
|
||||
|
||||
prop_mapWorkspaceId (x::T) = x == mapWorkspace id x
|
||||
|
||||
prop_mapWorkspaceInverse (x::T) = x == mapWorkspace predTag (mapWorkspace succTag x)
|
||||
where predTag w = w { tag = pred $ tag w }
|
||||
succTag w = w { tag = succ $ tag w }
|
||||
|
||||
prop_mapLayoutId (x::T) = x == mapLayout id x
|
||||
|
||||
prop_mapLayoutInverse (x::T) = x == mapLayout pred (mapLayout succ x)
|
||||
|
||||
|
47
tests/Utils.hs
Normal file
47
tests/Utils.hs
Normal file
@@ -0,0 +1,47 @@
|
||||
{-# LANGUAGE RankNTypes #-}
|
||||
module Utils where
|
||||
|
||||
import XMonad.StackSet hiding (filter)
|
||||
import Graphics.X11.Xlib.Types (Rectangle(..))
|
||||
import Data.List (sortBy)
|
||||
|
||||
-- Useful operation, the non-local workspaces
|
||||
hidden_spaces x = map workspace (visible x) ++ hidden x
|
||||
|
||||
|
||||
-- normalise workspace list
|
||||
normal s = s { hidden = sortBy g (hidden s), visible = sortBy f (visible s) }
|
||||
where
|
||||
f = \a b -> tag (workspace a) `compare` tag (workspace b)
|
||||
g = \a b -> tag a `compare` tag b
|
||||
|
||||
|
||||
noOverlaps [] = True
|
||||
noOverlaps [_] = True
|
||||
noOverlaps xs = and [ verts a `notOverlap` verts b
|
||||
| a <- xs
|
||||
, b <- filter (a /=) xs
|
||||
]
|
||||
where
|
||||
verts (Rectangle a b w h) = (a,b,a + fromIntegral w - 1, b + fromIntegral h - 1)
|
||||
|
||||
notOverlap (left1,bottom1,right1,top1)
|
||||
(left2,bottom2,right2,top2)
|
||||
= (top1 < bottom2 || top2 < bottom1)
|
||||
|| (right1 < left2 || right2 < left1)
|
||||
|
||||
|
||||
applyN :: (Integral n) => Maybe n -> (a -> a) -> a -> a
|
||||
applyN Nothing f v = v
|
||||
applyN (Just 0) f v = v
|
||||
applyN (Just n) f v = applyN (Just $ n-1) f (f v)
|
||||
|
||||
tags x = map tag $ workspaces x
|
||||
|
||||
|
||||
-- | noOverflows op a b is True if @a `op` fromIntegral b@ overflows (or
|
||||
-- otherwise gives the same answer when done using Integer
|
||||
noOverflows :: (Integral b, Integral c) =>
|
||||
(forall a. Integral a => a -> a -> a) -> b -> c -> Bool
|
||||
noOverflows op a b = toInteger (a `op` fromIntegral b) == toInteger a `op` toInteger b
|
||||
|
@@ -5,9 +5,9 @@ main = do foo <- getContents
|
||||
let actual_loc = filter (not.null) $ filter isntcomment $
|
||||
map (dropWhile (==' ')) $ lines foo
|
||||
loc = length actual_loc
|
||||
putStrLn $ show loc
|
||||
print loc
|
||||
-- uncomment the following to check for mistakes in isntcomment
|
||||
-- putStr $ unlines $ actual_loc
|
||||
-- print actual_loc
|
||||
|
||||
isntcomment ('-':'-':_) = False
|
||||
isntcomment ('{':'-':_) = False -- pragmas
|
||||
|
@@ -1,47 +1,92 @@
|
||||
{-# LANGUAGE FlexibleContexts #-}
|
||||
|
||||
-- Generates a in-memory version of "man/xmonad.1.markdown" that has the list
|
||||
-- of known key-bindings is inserted automatically from "Config.hs". That
|
||||
-- document is then rendered with Pandoc as "man/xmonad.1" and
|
||||
-- "man/xmonad.1.html".
|
||||
--
|
||||
-- Generates man/xmonad.1 from man/xmonad.1.in by filling the list of
|
||||
-- keybindings with values scraped from Config.hs
|
||||
--
|
||||
-- Format for the docstrings in Config.hs takes the following form:
|
||||
--
|
||||
-- -- mod-x %! Frob the whatsit
|
||||
--
|
||||
-- "Frob the whatsit" will be used as the description for keybinding "mod-x"
|
||||
--
|
||||
-- If the keybinding name is omitted, it will try to guess from the rest of the
|
||||
-- line. For example:
|
||||
--
|
||||
-- [ ((modMask .|. shiftMask, xK_Return), spawn "xterm") -- %! Launch an xterm
|
||||
--
|
||||
-- Here, mod-shift-return will be used as the keybinding name.
|
||||
--
|
||||
import Control.Monad
|
||||
import Text.Regex.Posix
|
||||
-- Unlike the rest of xmonad, this file is released under the GNU General
|
||||
-- Public License version 2 or later.
|
||||
|
||||
import Control.Monad.IO.Class (liftIO)
|
||||
import Data.Char
|
||||
import Data.List
|
||||
import qualified Data.Text as T
|
||||
import qualified Data.Text.IO as TIO
|
||||
import Text.Pandoc
|
||||
import Text.Regex.Posix
|
||||
|
||||
trim :: String -> String
|
||||
trim = reverse . dropWhile isSpace . reverse . dropWhile isSpace
|
||||
main :: IO ()
|
||||
main = do
|
||||
keybindings <- guessBindings
|
||||
|
||||
guessKeys line = concat $ intersperse "-" (modifiers ++ [map toLower key])
|
||||
where modifiers = map (!!1) (line =~ "(mod|shift|control)Mask")
|
||||
(_, _, _, [key]) = line =~ "xK_(\\w+)" :: (String, String, String, [String])
|
||||
markdownSource <- readFile "./man/xmonad.1.markdown"
|
||||
|
||||
binding :: [String] -> (String, String)
|
||||
binding [ _, bindingLine, "", desc ] = (guessKeys bindingLine, desc)
|
||||
binding [ _, _, keyCombo, desc ] = (keyCombo, desc)
|
||||
runIOorExplode $ do
|
||||
parsed <- readMarkdown (def { readerStandalone = True, readerExtensions = pandocExtensions })
|
||||
. T.pack
|
||||
. unlines
|
||||
. replace "___KEYBINDINGS___" keybindings
|
||||
. lines
|
||||
$ markdownSource
|
||||
|
||||
manTemplate <- getDefaultTemplate "man"
|
||||
manBody <- writeMan def { writerTemplate = Just manTemplate } parsed
|
||||
liftIO $ TIO.writeFile "./man/xmonad.1" $ manBody
|
||||
liftIO $ putStrLn "Documentation created: man/xmonad.1"
|
||||
|
||||
htmltemplate <- getDefaultTemplate "html"
|
||||
htmlBody <- writeHtml5String def
|
||||
{ writerTemplate = Just htmltemplate
|
||||
, writerTableOfContents = True }
|
||||
parsed
|
||||
liftIO $ TIO.writeFile "./man/xmonad.1.html" htmlBody
|
||||
liftIO $ putStrLn "Documentation created: man/xmonad.1.html"
|
||||
|
||||
-- | The format for the docstrings in "Config.hs" takes the following form:
|
||||
--
|
||||
-- @
|
||||
-- -- mod-x %! Frob the whatsit
|
||||
-- @
|
||||
--
|
||||
-- "Frob the whatsit" will be used as the description for keybinding "mod-x".--
|
||||
-- If the name of the key binding is omitted, the function tries to guess it
|
||||
-- from the rest of the line. For example:
|
||||
--
|
||||
-- @
|
||||
-- [ ((modMask .|. shiftMask, xK_Return), spawn "xterm") -- %! Launch an xterm
|
||||
-- @
|
||||
--
|
||||
-- Here, "mod-shift-return" will be used as the key binding name.
|
||||
|
||||
guessBindings :: IO String
|
||||
guessBindings = do
|
||||
buf <- readFile "./src/XMonad/Config.hs"
|
||||
return (intercalate "\n\n" (map markdownDefn (allBindings buf)))
|
||||
|
||||
allBindings :: String -> [(String, String)]
|
||||
allBindings xs = map (binding . map trim) (xs =~ "(.*)--(.*)%!(.*)")
|
||||
|
||||
binding :: [String] -> (String, String)
|
||||
binding [ _, bindingLine, "", desc ] = (guessKeys bindingLine, desc)
|
||||
binding [ _, _, keyCombo, desc ] = (keyCombo, desc)
|
||||
binding x = error ("binding: called with unexpected argument " ++ show x)
|
||||
|
||||
guessKeys :: String -> String
|
||||
guessKeys line =
|
||||
case keys of
|
||||
[key] -> concat $ intersperse "-" (modifiers ++ [map toLower key])
|
||||
_ -> error ("guessKeys: unexpected number of keys " ++ show keys)
|
||||
where
|
||||
modifiers = map (!!1) (line =~ "(mod|shift|control)Mask")
|
||||
(_, _, _, keys) = line =~ "xK_([_[:alnum:]]+)" :: (String, String, String, [String])
|
||||
|
||||
-- FIXME: What escaping should we be doing on these strings?
|
||||
troff :: (String, String) -> String
|
||||
troff (key, desc) = ".IP\n \\fB" ++ key ++ "\\fR\n" ++ desc ++ "\n"
|
||||
markdownDefn :: (String, String) -> String
|
||||
markdownDefn (key, desc) = key ++ "\n: " ++ desc
|
||||
|
||||
replace :: Eq a => a -> a -> [a] -> [a]
|
||||
replace x y = map (\a -> if a == x then y else a)
|
||||
|
||||
main = do
|
||||
troffBindings <- (concatMap troff . allBindings) `liftM` readFile "./Config.hs"
|
||||
let sed = unlines . replace "___KEYBINDINGS___" troffBindings . lines
|
||||
readFile "./man/xmonad.1.in" >>= return . sed >>= writeFile "./man/xmonad.1"
|
||||
trim :: String -> String
|
||||
trim = reverse . dropWhile isSpace . reverse . dropWhile isSpace
|
||||
|
33
util/hpcReport.sh
Normal file
33
util/hpcReport.sh
Normal file
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
if [[ ! ( -e xmonad.cabal && -e dist/hpc/tix/properties/properties.tix ) ]]; then
|
||||
echo "run in the same dir as xmonad.cabal after having run
|
||||
|
||||
cabal configure --enable-tests --enable-library-coverage; cabal test
|
||||
|
||||
"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
propsExclude=$(find tests/Properties -name '*.hs' \
|
||||
| sed -e 's_/_._g' -e 's_.hs$__' -e 's_^tests._--exclude=_' )
|
||||
|
||||
hpcFlags="
|
||||
--hpcdir=dist/hpc/mix/
|
||||
dist/hpc/tix/properties/properties.tix
|
||||
"
|
||||
|
||||
|
||||
if [[ ! (-e dist/hpc/mix/Main.mix) ]]; then
|
||||
mv dist/hpc/mix/properties/* dist/hpc/mix/
|
||||
mv dist/hpc/mix/xmonad-*/xmonad-*/* dist/hpc/mix/xmonad-*/
|
||||
fi
|
||||
|
||||
|
||||
hpc markup --destdir=dist/hpc $hpcFlags > /dev/null
|
||||
echo "see dist/hpc/hpc_index.html
|
||||
"
|
||||
hpc report $hpcFlags
|
148
xmonad.cabal
148
xmonad.cabal
@@ -1,30 +1,126 @@
|
||||
name: xmonad
|
||||
version: 0.4
|
||||
homepage: http://xmonad.org
|
||||
synopsis: A lightweight X11 window manager.
|
||||
description:
|
||||
xmonad is a tiling window manager for X. Windows are arranged
|
||||
automatically to tile the screen without gaps or overlap, maximising
|
||||
screen use. All features of the window manager are accessible from
|
||||
the keyboard: a mouse is strictly optional. xmonad is written and
|
||||
extensible in Haskell. Custom layout algorithms, and other
|
||||
extensions, may be written by the user in config files. Layouts are
|
||||
applied dynamically, and different layouts may be used on each
|
||||
workspace. Xinerama is fully supported, allowing windows to be tiled
|
||||
on several screens.
|
||||
category: System
|
||||
version: 0.14.1
|
||||
synopsis: A tiling window manager
|
||||
description: xmonad is a tiling window manager for X. Windows are arranged
|
||||
automatically to tile the screen without gaps or overlap, maximising
|
||||
screen use. All features of the window manager are accessible from the
|
||||
keyboard: a mouse is strictly optional. xmonad is written and
|
||||
extensible in Haskell. Custom layout algorithms, and other extensions,
|
||||
may be written by the user in config files. Layouts are applied
|
||||
dynamically, and different layouts may be used on each workspace.
|
||||
Xinerama is fully supported, allowing windows to be tiled on several
|
||||
screens.
|
||||
license: BSD3
|
||||
license-file: LICENSE
|
||||
author: Spencer Janssen
|
||||
maintainer: sjanssen@cse.unl.edu
|
||||
build-depends: base>=2.0, X11>=1.2.1, X11-extras>=0.4, mtl>=1.0, unix>=1.0
|
||||
extra-source-files: README TODO tests/loc.hs tests/Properties.hs man/xmonad.1.in
|
||||
Config.hs-boot util/GenerateManpage.hs man/xmonad.1 man/xmonad.html
|
||||
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
|
||||
maintainer: xmonad@haskell.org
|
||||
tested-with: GHC == 8.0.2, GHC == 8.2.2, GHC == 8.4.3, GHC == 8.6.1
|
||||
category: System
|
||||
homepage: http://xmonad.org
|
||||
bug-reports: https://github.com/xmonad/xmonad/issues
|
||||
build-type: Simple
|
||||
extra-source-files: README.md
|
||||
CHANGES.md
|
||||
CONFIG
|
||||
STYLE
|
||||
tests/*.hs
|
||||
tests/Properties/*.hs
|
||||
tests/Properties/Layout/*.hs
|
||||
man/xmonad.1.markdown
|
||||
man/xmonad.1
|
||||
man/xmonad.1.html
|
||||
util/GenerateManpage.hs
|
||||
util/hpcReport.sh
|
||||
cabal-version: >= 1.8
|
||||
|
||||
executable: xmonad
|
||||
main-is: Main.hs
|
||||
other-modules: Config Operations StackSet XMonad
|
||||
ghc-options: -funbox-strict-fields -O2 -fasm -Wall -optl-Wl,-s
|
||||
ghc-prof-options: -prof -auto-all
|
||||
extensions: GeneralizedNewtypeDeriving
|
||||
-- Also requires deriving Typeable, PatternGuards
|
||||
source-repository head
|
||||
type: git
|
||||
location: https://github.com/xmonad/xmonad
|
||||
|
||||
flag testing
|
||||
default: False
|
||||
manual: True
|
||||
description: Testing mode, only build minimal components
|
||||
|
||||
flag generatemanpage
|
||||
default: False
|
||||
manual: True
|
||||
description: Build the tool for generating the man page
|
||||
|
||||
library
|
||||
exposed-modules: XMonad
|
||||
XMonad.Config
|
||||
XMonad.Core
|
||||
XMonad.Layout
|
||||
XMonad.Main
|
||||
XMonad.ManageHook
|
||||
XMonad.Operations
|
||||
XMonad.StackSet
|
||||
other-modules: Paths_xmonad
|
||||
hs-source-dirs: src
|
||||
build-depends: base >= 4.9 && < 5
|
||||
, X11 >= 1.8 && < 1.10
|
||||
, containers
|
||||
, data-default
|
||||
, directory
|
||||
, extensible-exceptions
|
||||
, filepath
|
||||
, mtl
|
||||
, process
|
||||
, setlocale
|
||||
, unix
|
||||
, utf8-string >= 0.3 && < 1.1
|
||||
ghc-options: -funbox-strict-fields -Wall -fno-warn-unused-do-bind
|
||||
|
||||
if flag(testing)
|
||||
buildable: False
|
||||
|
||||
executable xmonad
|
||||
main-is: Main.hs
|
||||
build-depends: base, X11, mtl, unix, xmonad
|
||||
ghc-options: -Wall -fno-warn-unused-do-bind
|
||||
|
||||
executable generatemanpage
|
||||
main-is: GenerateManpage.hs
|
||||
hs-source-dirs: util
|
||||
|
||||
if flag(generatemanpage)
|
||||
build-depends: base, pandoc >= 2, regex-posix, text
|
||||
else
|
||||
buildable: False
|
||||
|
||||
test-suite properties
|
||||
type: exitcode-stdio-1.0
|
||||
main-is: Properties.hs
|
||||
other-modules: Instances
|
||||
Properties.Delete
|
||||
Properties.Failure
|
||||
Properties.Floating
|
||||
Properties.Focus
|
||||
Properties.GreedyView
|
||||
Properties.Insert
|
||||
Properties.Layout.Full
|
||||
Properties.Layout.Tall
|
||||
Properties.Screen
|
||||
Properties.Shift
|
||||
Properties.Stack
|
||||
Properties.StackSet
|
||||
Properties.Swap
|
||||
Properties.View
|
||||
Properties.Workspace
|
||||
Utils
|
||||
hs-source-dirs: tests
|
||||
build-depends: base, QuickCheck >= 2, X11, containers, extensible-exceptions, xmonad
|
||||
|
Reference in New Issue
Block a user