mirror of
https://github.com/xmonad/xmonad.git
synced 2025-07-30 19:51:52 -07:00
Compare commits
784 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
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 | ||
|
bec871d254 | ||
|
258f85dd08 | ||
|
16abab4241 | ||
|
01cf4a5581 | ||
|
bd6a52e587 | ||
|
0938298f29 | ||
|
5bd96a8e1a | ||
|
874e6f80f0 | ||
|
6fecf7c425 | ||
|
3a18204adb | ||
|
2599706141 | ||
|
2edc5a92c2 | ||
|
b6d36f3c70 | ||
|
5c9850bf6d | ||
|
3f3b4251c2 | ||
|
2de6cc7cf1 | ||
|
3aa746c0db | ||
|
48b001f9a2 | ||
|
775172983b | ||
|
f5bec53b83 | ||
|
77e3876d07 | ||
|
f439e766a4 | ||
|
c436e63a15 | ||
|
d610407cf8 | ||
|
f7d6f6b6f7 | ||
|
6dba9ddeb3 | ||
|
49f64197b2 | ||
|
1f625a6c0d | ||
|
1eaee82e85 | ||
|
07be5998c0 | ||
|
6d7307030a | ||
|
6c94b3b217 | ||
|
1a48b527ff | ||
|
75874040cc | ||
|
e331dd4a82 | ||
|
3cf5c1f9d4 | ||
|
4cfe583f63 | ||
|
d348f2ae72 | ||
|
41063f2e57 | ||
|
42dde26d4d | ||
|
c66ff8335e | ||
|
f7ecf70a35 | ||
|
fd10c198e6 | ||
|
c49b8f567f | ||
|
1d0191184f | ||
|
6c38226553 | ||
|
5cd9094f58 | ||
|
053f1adb7c | ||
|
6294e6adf5 | ||
|
1f9e77bd90 | ||
|
8d3b6fa304 | ||
|
6316d4f2ff | ||
|
a88a0b1b8b | ||
|
bab04b71d3 | ||
|
d83ce46a1e | ||
|
aaaeae54c3 | ||
|
e0bcad162f | ||
|
b07e334405 | ||
|
c237441003 | ||
|
42b691d515 | ||
|
65f3f4db8a | ||
|
172e046e84 | ||
|
7dac92057d | ||
|
8dbf8896c9 | ||
|
f11ce95528 | ||
|
d57aab25ef | ||
|
89645e0999 | ||
|
ab0ebe1050 | ||
|
8e303a6bea | ||
|
e70fb29efc | ||
|
2afa8c3a7a | ||
|
ce0d5d376d | ||
|
4bcad8fe60 | ||
|
da3db68b59 | ||
|
045ed777a2 | ||
|
e8bbba9694 | ||
|
8b3dc01e53 | ||
|
8b8433a9e7 | ||
|
acbe7976d7 | ||
|
d05b01431d | ||
|
60a40be09e | ||
|
2b6a200ad3 | ||
|
2196ab7469 | ||
|
ff1918ad20 | ||
|
5e3317b28e | ||
|
306b3c11c3 | ||
|
5ef7c5f5d0 | ||
|
16d4ce5706 | ||
|
6640e434bf | ||
|
cee31df81d | ||
|
029dd68860 | ||
|
bd64d169fe | ||
|
d0d81db6de | ||
|
f1c1e982a2 | ||
|
60dda50181 | ||
|
3b64981c78 | ||
|
a7c4c38ba8 | ||
|
34bbbf59c4 | ||
|
4fd7353d8e | ||
|
7e8de677cb | ||
|
3a9dc57c69 | ||
|
5f9222efb4 | ||
|
d1fdd4a020 | ||
|
2ab2195782 | ||
|
71bce5e525 | ||
|
9c78ba538b | ||
|
68c72b34e1 | ||
|
2caf68ee69 | ||
|
f420ae881d | ||
|
e062265b38 | ||
|
9c35abaa46 | ||
|
ee39e7fdb8 | ||
|
e6fb743e5a | ||
|
0b11d6666d | ||
|
4a7ec374d0 | ||
|
1c603ebc4b | ||
|
3af0ccf73c | ||
|
fe397edf4a | ||
|
70282f23dc | ||
|
f3f12383f0 | ||
|
cb13207644 | ||
|
eb1e38405d | ||
|
d43384cfc7 | ||
|
197c834331 | ||
|
5f12ca0faa | ||
|
b4929576e7 | ||
|
0e5f8b03e8 | ||
|
3f03dcb5c1 | ||
|
bee79c83e6 | ||
|
29a5256c10 | ||
|
6cff2dddcf | ||
|
d1ad738f6b | ||
|
3b6bfbf54c | ||
|
f8c0ae5407 | ||
|
f1aa00f96f | ||
|
5e943d512c | ||
|
019315e70c | ||
|
bc525b79e3 | ||
|
f67ebbf495 | ||
|
3060c36d00 | ||
|
c6f346f887 | ||
|
e87a111a50 | ||
|
3b5ca225f6 | ||
|
46ef80ad06 | ||
|
b72c096bc6 | ||
|
0842194940 | ||
|
2b207a28ef | ||
|
54af88d5f6 | ||
|
b6f00e9aab | ||
|
874a4264c3 | ||
|
6898a0e583 | ||
|
f668b6238a | ||
|
bccf8dd5f8 | ||
|
41e3b073c8 | ||
|
82dd5b8119 | ||
|
1fb52ce2cc | ||
|
dede0a2ce9 | ||
|
74441202a0 | ||
|
bda704297c | ||
|
2819adfef4 | ||
|
8146dd46dd | ||
|
92a1335cff | ||
|
49cebc6130 | ||
|
314b5ee6bd | ||
|
aaba52043d | ||
|
6ec342ff75 | ||
|
8a8438a5c2 | ||
|
2716b1ada6 | ||
|
34d8d51a77 | ||
|
ca0d87664b | ||
|
6a273c2afa | ||
|
6dcd66f16e | ||
|
df4c18a181 | ||
|
ec0995a3a6 | ||
|
919774dff8 | ||
|
447d662d1d | ||
|
fae3cbebb1 | ||
|
4c40661047 | ||
|
2f3ccd7ab6 | ||
|
8bb313ea53 | ||
|
2e7aa7d055 | ||
|
6875437c44 | ||
|
808894c217 | ||
|
84c6432c82 | ||
|
bf4388e3aa | ||
|
cc3527a975 | ||
|
9a2f57552e | ||
|
189c2d31f9 | ||
|
5068bd27f0 | ||
|
fc70bed46b | ||
|
d0482810b3 | ||
|
c146940154 | ||
|
bfd638d818 | ||
|
a48ec57cd9 | ||
|
54c024583f | ||
|
2efa369dfc | ||
|
e74e8050d0 | ||
|
ab830ec227 | ||
|
bb12b08239 | ||
|
61d7524bcd | ||
|
d0566a28be | ||
|
6f9a060118 | ||
|
dbdf0fd5e4 | ||
|
ce28fc1eb2 | ||
|
977f8328fc | ||
|
776886660b | ||
|
c6da7fc14a | ||
|
e99d7431c8 | ||
|
5dea6605fc | ||
|
6091bfd0fe | ||
|
d411736ded | ||
|
e517aedfa1 | ||
|
b84a9b875b | ||
|
33bb745880 | ||
|
be08dd80ec | ||
|
dbd58faffe | ||
|
a2c5aa3612 | ||
|
fa2b56c14e | ||
|
7d1a23698f | ||
|
8169445cbd | ||
|
753b42ae65 | ||
|
d1e4699944 | ||
|
62344287da | ||
|
8cdcceab48 | ||
|
194a934c37 | ||
|
5f8202e79e | ||
|
4ffee115e1 | ||
|
00e1038d71 | ||
|
7158a58792 | ||
|
c0a9636f3b | ||
|
ff6b48382c | ||
|
bb9e46df6c | ||
|
f68a954fc3 | ||
|
d21e61a315 | ||
|
4b9bacb1f9 | ||
|
9992737e84 | ||
|
615a4a1af1 | ||
|
33447129dd | ||
|
14971546bb | ||
|
6f7030f875 | ||
|
4f5c307d6f | ||
|
e7b37ca646 | ||
|
5f3e91676a | ||
|
b3bdbf3588 | ||
|
00b930b09e | ||
|
0d17ca9436 | ||
|
b668133c08 | ||
|
330179ea20 | ||
|
854d3239cc | ||
|
c8b6388fb8 | ||
|
b97e8836e2 | ||
|
ab6f210300 | ||
|
e1885f27e1 | ||
|
6365601c77 | ||
|
3bfa0930af | ||
|
0d4a7d098f | ||
|
16c8622fbf | ||
|
1c3931a0d6 | ||
|
7706f38dc8 | ||
|
0ada17c34a | ||
|
a21c4d02f1 | ||
|
cf9828cbcd | ||
|
b257658781 | ||
|
5da458c755 | ||
|
d7d8c586cb | ||
|
86ea7f7bc0 | ||
|
a2a0670397 | ||
|
02a9e4c589 | ||
|
d4676d93e8 | ||
|
0010a23c18 | ||
|
7ae7029b50 | ||
|
21e09361a6 | ||
|
8f200d408f | ||
|
d3632eb8fe | ||
|
b22ebceb80 | ||
|
ba14f07093 | ||
|
2a6d6d4ed7 | ||
|
68b2859aa2 | ||
|
a10f11f623 | ||
|
6a2f9d739d | ||
|
4d74099851 | ||
|
26b388189e | ||
|
1c609288dd | ||
|
68a63688ad | ||
|
6b8e9570c2 | ||
|
85e57cfa47 | ||
|
89a31eaf52 | ||
|
fc70307325 | ||
|
f9110c999b | ||
|
795e96d353 | ||
|
fec58e8a09 | ||
|
d01623db88 | ||
|
1912feee50 | ||
|
cad36baa19 | ||
|
77da6e4c72 | ||
|
71349314c5 | ||
|
f2eb5ac6bb | ||
|
e4e1724842 | ||
|
cd73165c63 | ||
|
225a2e89a3 | ||
|
9b429f4f41 | ||
|
ca896686a1 | ||
|
f06d042b56 | ||
|
29a32bc146 | ||
|
27b7cccd3a | ||
|
b20a9cff7f | ||
|
8f3258a348 | ||
|
0226ba3441 | ||
|
84f22f7102 | ||
|
7e9fbf5883 | ||
|
7ae4bc8f39 | ||
|
a6098f6010 | ||
|
72a50ead89 | ||
|
0be589ae8c | ||
|
b46a449baf | ||
|
9669c26fdc | ||
|
ddffd109ce | ||
|
68e6643356 | ||
|
7246a9e2d2 | ||
|
777cf28bdf | ||
|
3cb64d7461 | ||
|
1d764ecf2e | ||
|
5594c71e66 | ||
|
a3479aa9f5 | ||
|
b069f84add | ||
|
fc08bd48b4 | ||
|
e83ae8ba62 | ||
|
67ae8fcd7c | ||
|
f6b14b7123 | ||
|
fbfbb14658 | ||
|
79a9c58f92 | ||
|
bc2e6b2112 | ||
|
89417a6e25 | ||
|
2be4f5f216 | ||
|
c023e9a681 | ||
|
da63d4a4b7 | ||
|
46b04b3fa5 | ||
|
0b8c9c407e | ||
|
5818e5a7fc | ||
|
eda3ab2849 | ||
|
c2318fa67d | ||
|
f85dac53e4 | ||
|
39fd73a7f7 | ||
|
abdbc23551 | ||
|
cf52e66ec1 | ||
|
f6bac98678 | ||
|
94b64e7035 | ||
|
58f180aefb | ||
|
257aa4776f | ||
|
0cca848c54 | ||
|
c613073cb6 | ||
|
1d2c5ca35a | ||
|
b0cfe9b6ab | ||
|
19256758a2 | ||
|
c7655df3cb | ||
|
25616c3b9f | ||
|
d16d751207 | ||
|
2231879268 | ||
|
5fdbe0711d | ||
|
7fb1dd96de | ||
|
77f59efcc6 | ||
|
bb4bd97c87 | ||
|
a64c9f1856 | ||
|
e5e4b46ffa | ||
|
7be0a2103d | ||
|
ef5326ccff | ||
|
ee2c2c311b | ||
|
fdc6af5faa | ||
|
325329e5d7 | ||
|
8cfe050be7 | ||
|
d3f56af172 | ||
|
60f4f4e5e4 | ||
|
6346e11ff6 | ||
|
a5aa4b1686 | ||
|
6f9998ad27 | ||
|
e64e434750 | ||
|
52608185b4 | ||
|
8d6b914409 | ||
|
7afc18b0e1 | ||
|
a36bd31973 | ||
|
d502891ef0 | ||
|
f8caf7f982 | ||
|
654e64b772 | ||
|
bcf305cd1e | ||
|
0df8dffc78 | ||
|
865939b660 | ||
|
9b52525417 | ||
|
3202fa0673 | ||
|
07a354e5a3 | ||
|
c4dd126200 | ||
|
e300df5425 | ||
|
d074b1bcfd | ||
|
ff975f6d40 | ||
|
a9d7b7ef49 | ||
|
270d80297f | ||
|
3c2ad2509e | ||
|
90dd7705a8 | ||
|
127fd0b309 | ||
|
c8cfc1faca | ||
|
daefb508d7 | ||
|
b59d4d1dc0 | ||
|
b1345e037c | ||
|
ea80d2a71f | ||
|
02073c547b | ||
|
5c44fa79fd | ||
|
d28d4251e0 | ||
|
d3d058345d | ||
|
b757a526db | ||
|
1c4b0a51d8 | ||
|
2e5084319a | ||
|
77e46027ed | ||
|
f9af744b1e | ||
|
4206c4bae9 | ||
|
810c19d7f2 | ||
|
3cc55de0f4 | ||
|
21cbab3f06 | ||
|
67fe5ab219 | ||
|
54ee507cca | ||
|
378aa87173 | ||
|
47ae5e4ea5 | ||
|
c4030d45e2 | ||
|
f5b0df6a73 | ||
|
c5c958dc2c | ||
|
626d25bb3a | ||
|
97141b9a07 | ||
|
4afb251f41 | ||
|
a846eb18ff | ||
|
f03ca10714 | ||
|
ba9e15e772 | ||
|
70fe61971b | ||
|
1276edc861 | ||
|
27f1f50071 | ||
|
9f4fd822b6 | ||
|
ab27c7d48d | ||
|
ab0f3be0af | ||
|
7a56422491 | ||
|
0928bb484a | ||
|
72e7bed426 | ||
|
d0ef78e5c3 | ||
|
b5ed587f2e | ||
|
7a89f431b1 | ||
|
a5e0e2458d | ||
|
9d3d2f8503 | ||
|
204c90b072 | ||
|
bedc069143 | ||
|
5b7c6c8631 | ||
|
ea1134db26 | ||
|
f0df95da72 | ||
|
f5e8b2b6a8 | ||
|
08ce2a5efa | ||
|
b63e8c029e | ||
|
fa271e00ce | ||
|
3416eceb5d | ||
|
833d5ae357 | ||
|
1dff21001c | ||
|
0c569a64e1 | ||
|
c0266c0cb8 | ||
|
56a4164a90 | ||
|
fdc73b4cb1 | ||
|
121e20d342 | ||
|
af7c76d3fe | ||
|
3586379ecc | ||
|
08e514b28f | ||
|
ecbff364c9 | ||
|
1e83de8cde | ||
|
4d9fa8bc98 | ||
|
17f70344ec | ||
|
14773f6300 | ||
|
89182406a8 | ||
|
0d7969be18 | ||
|
5d086df912 | ||
|
5c1982cc35 | ||
|
bdbca84bcd | ||
|
8097060259 | ||
|
df7d1d95fa | ||
|
8265cae8a8 | ||
|
a07f0778ad | ||
|
9fafa995c7 | ||
|
966da43176 | ||
|
a839238483 | ||
|
5386ab0094 | ||
|
d5e73b70ae | ||
|
9b80a36cf8 | ||
|
90b4eb607c | ||
|
0dd75f9d68 | ||
|
bd41b81c16 | ||
|
c03b53db67 | ||
|
4fbb4e23a3 | ||
|
ea94892e1c | ||
|
767bc68acf | ||
|
70a87063d1 | ||
|
2a5be03dd1 | ||
|
50f89990a0 | ||
|
5f6ac3573d | ||
|
5475c751ab | ||
|
47eb93c694 |
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 $ defaultConfig
|
||||
{ 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 succesfully, 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.
|
150
Config.hs
150
Config.hs
@@ -1,150 +0,0 @@
|
||||
-----------------------------------------------------------------------------
|
||||
-- |
|
||||
-- Module : Config.hs
|
||||
-- Copyright : (c) Spencer Janssen 2007
|
||||
-- License : BSD3-style (see LICENSE)
|
||||
--
|
||||
-- Maintainer : dons@cse.unsw.edu.au
|
||||
-- Stability : stable
|
||||
-- Portability : portable
|
||||
--
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
module Config where
|
||||
|
||||
--
|
||||
-- xmonad bindings follow mostly the dwm/wmii conventions:
|
||||
--
|
||||
-- key combination action
|
||||
--
|
||||
-- mod-shift-return new xterm
|
||||
-- mod-p launch dmenu
|
||||
-- mod-shift-p launch gmrun
|
||||
--
|
||||
-- mod-space switch tiling mode
|
||||
--
|
||||
-- mod-tab raise next window in stack
|
||||
-- mod-j
|
||||
-- mod-k
|
||||
--
|
||||
-- mod-h decrease the size of the master area
|
||||
-- mod-l increase the size of the master area
|
||||
--
|
||||
-- mod-shift-c kill client
|
||||
-- mod-shift-q exit window manager
|
||||
-- mod-shift-ctrl-q restart window manager ('xmonad' must be in $PATH)
|
||||
--
|
||||
-- mod-return cycle the current tiling order
|
||||
--
|
||||
-- mod-1..9 switch to workspace N
|
||||
-- mod-shift-1..9 move client to workspace N
|
||||
--
|
||||
-- mod-w,e,r switch to physical/Xinerama screen 1, 2 or 3.
|
||||
--
|
||||
-- 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. When xmonad 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, xmonad simply switches focus to
|
||||
-- that screen. If you switch to a workspace which is *not* visible, xmonad
|
||||
-- replaces the workspace on the *current* screen with the workspace you
|
||||
-- selected.
|
||||
--
|
||||
-- For example, if you have the following configuration:
|
||||
--
|
||||
-- Screen 1: Workspace 2
|
||||
-- Screen 2: Workspace 5 (current workspace)
|
||||
--
|
||||
-- and you wanted to view workspace 7 on screen 1, you would press:
|
||||
--
|
||||
-- mod-2 (to select workspace 2, and make screen 1 the current screen)
|
||||
-- mod-7 (to select workspace 7)
|
||||
--
|
||||
-- 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.
|
||||
--
|
||||
|
||||
import Data.Ratio
|
||||
import Data.Bits
|
||||
import qualified Data.Map as M
|
||||
import System.Exit
|
||||
import Graphics.X11.Xlib
|
||||
import XMonad
|
||||
import Operations
|
||||
|
||||
-- The number of workspaces:
|
||||
workspaces :: Int
|
||||
workspaces = 9
|
||||
|
||||
-- modMask lets you easily change which modkey you 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
|
||||
|
||||
-- How much to change the horizontal/vertical split bar by defalut.
|
||||
defaultDelta :: Rational
|
||||
defaultDelta = 3%100
|
||||
|
||||
-- The mask for the numlock key. You may need to change this on some systems.
|
||||
numlockMask :: KeyMask
|
||||
numlockMask = lockMask
|
||||
|
||||
-- What layout to start in, and what the default proportion for the
|
||||
-- left pane should be in the tiled layout. See LayoutDesc and
|
||||
-- friends in XMonad.hs for options.
|
||||
startingLayoutDesc :: LayoutDesc
|
||||
startingLayoutDesc =
|
||||
LayoutDesc { layoutType = Full
|
||||
, tileFraction = 1%2 }
|
||||
|
||||
-- The keys list.
|
||||
keys :: M.Map (KeyMask, KeySym) (X ())
|
||||
keys = M.fromList $
|
||||
[ ((modMask .|. shiftMask, xK_Return), spawn "xterm")
|
||||
, ((modMask, xK_p ), spawn "exe=`dmenu_path | dmenu` && exec $exe")
|
||||
, ((modMask .|. shiftMask, xK_p ), spawn "gmrun")
|
||||
, ((modMask, xK_space ), switchLayout)
|
||||
|
||||
, ((modMask, xK_Tab ), raise GT)
|
||||
, ((modMask, xK_j ), raise GT)
|
||||
, ((modMask, xK_k ), raise LT)
|
||||
|
||||
, ((modMask, xK_h ), changeSplit (negate defaultDelta))
|
||||
, ((modMask, xK_l ), changeSplit defaultDelta)
|
||||
|
||||
, ((modMask .|. shiftMask, xK_c ), kill)
|
||||
|
||||
, ((modMask .|. shiftMask, xK_q ), io $ exitWith ExitSuccess)
|
||||
, ((modMask .|. shiftMask .|. controlMask, xK_q ), io restart)
|
||||
|
||||
-- Cycle the current tiling order
|
||||
, ((modMask, xK_Return), promote)
|
||||
|
||||
] ++
|
||||
-- Keybindings to get to each workspace:
|
||||
[((m .|. modMask, xK_0 + fromIntegral i), f (fromIntegral (pred i))) -- index from 0.
|
||||
| i <- [1 .. workspaces]
|
||||
, (f, m) <- [(view, 0), (tag, shiftMask)]]
|
||||
|
||||
-- Keybindings to each screen :
|
||||
-- mod-wer (underneath 123) switches to physical/Xinerama screens 1 2 and 3
|
||||
++
|
||||
[((m .|. modMask, key), screenWorkspace sc >>= f)
|
||||
| (key, sc) <- zip [xK_w, xK_e, xK_r] [0..]
|
||||
, (f, m) <- [(view, 0), (tag, shiftMask)]]
|
||||
|
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 REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE 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.
|
||||
|
225
Main.hs
225
Main.hs
@@ -1,6 +1,6 @@
|
||||
-----------------------------------------------------------------------------
|
||||
----------------------------------------------------------------------------
|
||||
-- |
|
||||
-- Module : Main.hs
|
||||
-- Module : Main
|
||||
-- Copyright : (c) Spencer Janssen 2007
|
||||
-- License : BSD3-style (see LICENSE)
|
||||
--
|
||||
@@ -8,182 +8,77 @@
|
||||
-- Stability : unstable
|
||||
-- Portability : not portable, uses mtl, X11, posix
|
||||
--
|
||||
-- xmonad, a minimalist, tiling window manager for X11
|
||||
--
|
||||
-----------------------------------------------------------------------------
|
||||
--
|
||||
-- xmonad, a minimal window manager for X11
|
||||
--
|
||||
|
||||
import Data.Bits
|
||||
import qualified Data.Map as M
|
||||
|
||||
import Graphics.X11.Xlib hiding (refreshKeyboardMapping)
|
||||
import Graphics.X11.Xlib.Extras
|
||||
import Graphics.X11.Xinerama
|
||||
|
||||
import Control.Monad.State
|
||||
|
||||
import qualified StackSet as W
|
||||
module Main (main) where
|
||||
|
||||
import XMonad
|
||||
import Operations
|
||||
import Config
|
||||
|
||||
--
|
||||
-- The main entry point
|
||||
--
|
||||
import System.IO
|
||||
import System.Info
|
||||
import System.Environment
|
||||
import System.Posix.Process (executeFile)
|
||||
|
||||
import Paths_xmonad (version)
|
||||
import Data.Version (showVersion)
|
||||
|
||||
#ifdef TESTING
|
||||
import qualified Properties
|
||||
#endif
|
||||
|
||||
-- | The entry point into xmonad. Attempts to compile any custom main
|
||||
-- for xmonad, and if it doesn't find one, just launches the default.
|
||||
main :: IO ()
|
||||
main = do
|
||||
dpy <- openDisplay ""
|
||||
let dflt = defaultScreen dpy
|
||||
rootw <- rootWindow dpy dflt
|
||||
wmdelt <- internAtom dpy "WM_DELETE_WINDOW" False
|
||||
wmprot <- internAtom dpy "WM_PROTOCOLS" False
|
||||
xinesc <- getScreenInfo dpy
|
||||
args <- getArgs
|
||||
let launch = catchIO buildLaunch >> xmonad defaultConfig
|
||||
case args of
|
||||
[] -> launch
|
||||
["--resume", _] -> launch
|
||||
["--help"] -> usage
|
||||
["--recompile"] -> recompile True >> return ()
|
||||
["--version"] -> putStrLn ("xmonad " ++ showVersion version)
|
||||
#ifdef TESTING
|
||||
("--run-tests":_) -> Properties.main
|
||||
#endif
|
||||
_ -> fail "unrecognized flags"
|
||||
|
||||
let st = XState
|
||||
{ display = dpy
|
||||
, xineScreens = xinesc
|
||||
, theRoot = rootw
|
||||
, wmdelete = wmdelt
|
||||
, wmprotocols = wmprot
|
||||
-- fromIntegral needed for X11 versions that use Int instead of CInt.
|
||||
, dimensions = (fromIntegral (displayWidth dpy dflt),
|
||||
fromIntegral (displayHeight dpy dflt))
|
||||
, workspace = W.empty workspaces (length xinesc)
|
||||
, defaultLayoutDesc = startingLayoutDesc
|
||||
, layoutDescs = M.empty
|
||||
}
|
||||
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" :
|
||||
#ifdef TESTING
|
||||
" --run-tests Run the test suite" :
|
||||
#endif
|
||||
[]
|
||||
|
||||
xSetErrorHandler -- in C, I'm too lazy to write the binding
|
||||
|
||||
-- setup initial X environment
|
||||
sync dpy False
|
||||
selectInput dpy rootw $ substructureRedirectMask
|
||||
.|. substructureNotifyMask
|
||||
.|. enterWindowMask
|
||||
.|. leaveWindowMask
|
||||
grabKeys dpy rootw
|
||||
sync dpy False
|
||||
|
||||
ws <- scan dpy rootw
|
||||
allocaXEvent $ \e ->
|
||||
runX st $ do
|
||||
mapM_ manage ws
|
||||
forever $ handle =<< xevent dpy e
|
||||
where
|
||||
xevent d e = io (nextEvent d e >> getEvent e)
|
||||
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 initial windows to manage
|
||||
scan :: Display -> Window -> IO [Window]
|
||||
scan dpy rootw = do
|
||||
(_, _, ws) <- queryTree dpy rootw
|
||||
filterM ok ws
|
||||
where
|
||||
ok w = do wa <- getWindowAttributes dpy w
|
||||
return $ not (wa_override_redirect wa)
|
||||
&& wa_map_state wa == waIsViewable
|
||||
|
||||
-- | Grab the keys back
|
||||
grabKeys :: Display -> Window -> IO ()
|
||||
grabKeys dpy rootw = do
|
||||
ungrabKey dpy '\0' {-AnyKey-} anyModifier rootw
|
||||
flip mapM_ (M.keys keys) $ \(mask,sym) -> do
|
||||
kc <- keysymToKeycode dpy sym
|
||||
mapM_ (grab kc) [mask, mask .|. numlockMask] -- note: no numlock
|
||||
where
|
||||
grab kc m = grabKey dpy kc m rootw True grabModeAsync grabModeAsync
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Event handler
|
||||
-- | Build "~/.xmonad/xmonad.hs" with ghc, then execute it. If there are no
|
||||
-- errors, this function does not return. An exception is raised in any of
|
||||
-- these cases:
|
||||
--
|
||||
-- | handle. Handle X events
|
||||
-- * ghc missing
|
||||
--
|
||||
-- Events dwm handles that we don't:
|
||||
-- * ~/.xmonad/xmonad.hs missing
|
||||
--
|
||||
-- [ButtonPress] = buttonpress,
|
||||
-- [Expose] = expose,
|
||||
-- [PropertyNotify] = propertynotify,
|
||||
-- * xmonad.hs fails to compile
|
||||
--
|
||||
-- Todo: seperate IO from X monad stuff. We want to be able to test the
|
||||
-- handler, and client functions, with dummy X interface ops, in QuickCheck
|
||||
-- ** wrong ghc in path (fails to compile)
|
||||
--
|
||||
-- Will require an abstract interpreter from Event -> X Action, which
|
||||
-- modifies the internal X state, and then produces an IO action to
|
||||
-- evaluate.
|
||||
-- ** type error, syntax error, ..
|
||||
--
|
||||
-- XCreateWindowEvent(3X11)
|
||||
-- Window manager clients normally should ignore this window if the
|
||||
-- override_redirect member is True.
|
||||
-- * Missing xmonad/XMonadContrib modules due to ghc upgrade
|
||||
--
|
||||
|
||||
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
|
||||
whenJust (M.lookup (m,s) keys) id
|
||||
|
||||
-- manage a new window
|
||||
handle (MapRequestEvent {ev_window = w}) = withDisplay $ \dpy -> do
|
||||
wa <- io $ getWindowAttributes dpy w -- ignore override windows
|
||||
when (not (wa_override_redirect wa)) $ manage w
|
||||
|
||||
-- window destroyed, unmanage it
|
||||
handle (DestroyWindowEvent {ev_window = w}) = do b <- isClient w; when b $ unmanage w
|
||||
|
||||
-- window gone, unmanage it
|
||||
handle (UnmapEvent {ev_window = w}) = do b <- isClient w; when b $ unmanage w
|
||||
|
||||
-- set keyboard mapping
|
||||
handle e@(MappingNotifyEvent {ev_window = w}) = do
|
||||
-- this fromIntegral is only necessary with the old X11 version that uses
|
||||
-- Int instead of CInt. TODO delete it when there is a new release of X11
|
||||
let m = (ev_request e, ev_first_keycode e, fromIntegral $ ev_count e)
|
||||
withDisplay $ \d -> io $ refreshKeyboardMapping d m
|
||||
when (ev_request e == mappingKeyboard) $ withDisplay $ io . flip grabKeys w
|
||||
|
||||
-- click on an unfocussed window
|
||||
handle (ButtonEvent {ev_window = w, ev_event_type = t})
|
||||
| t == buttonPress
|
||||
= safeFocus w
|
||||
|
||||
-- entered a normal window
|
||||
handle e@(CrossingEvent {ev_window = w, ev_event_type = t})
|
||||
| t == enterNotify && ev_mode e == notifyNormal && ev_detail e /= notifyInferior
|
||||
= safeFocus w
|
||||
|
||||
-- left a window, check if we need to focus root
|
||||
handle e@(CrossingEvent {ev_event_type = t})
|
||||
| t == leaveNotify
|
||||
= do rootw <- gets theRoot
|
||||
when (ev_window e == rootw && not (ev_same_screen e)) $ setFocus rootw
|
||||
|
||||
-- configure a window
|
||||
handle e@(ConfigureRequestEvent {ev_window = w}) = do
|
||||
XState { display = dpy, workspace = ws } <- get
|
||||
|
||||
when (W.member w ws) $ -- already managed, reconfigure (see client:configure()
|
||||
trace ("Reconfigure already managed window: " ++ show w)
|
||||
|
||||
io $ configureWindow dpy (ev_window e) (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 = ev_border_width e
|
||||
, wc_sibling = ev_above e
|
||||
-- this fromIntegral is only necessary with the old X11 version that uses
|
||||
-- Int instead of CInt. TODO delete it when there is a new release of X11
|
||||
, wc_stack_mode = fromIntegral $ ev_detail e
|
||||
}
|
||||
|
||||
io $ sync dpy False
|
||||
|
||||
handle e = trace (eventName e) -- ignoring
|
||||
buildLaunch :: IO ()
|
||||
buildLaunch = do
|
||||
recompile False
|
||||
dir <- getXMonadDir
|
||||
args <- getArgs
|
||||
executeFile (dir ++ "/xmonad-"++arch++"-"++os) False args Nothing
|
||||
return ()
|
||||
|
282
Operations.hs
282
Operations.hs
@@ -1,282 +0,0 @@
|
||||
-----------------------------------------------------------------------------
|
||||
-- |
|
||||
-- Module : Operations.hs
|
||||
-- Copyright : (c) Spencer Janssen 2007
|
||||
-- License : BSD3-style (see LICENSE)
|
||||
--
|
||||
-- Maintainer : dons@cse.unsw.edu.au
|
||||
-- Stability : stable
|
||||
-- Portability : portable
|
||||
--
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
module Operations where
|
||||
|
||||
import Data.List
|
||||
import Data.Maybe
|
||||
import Data.Bits
|
||||
import qualified Data.Map as M
|
||||
|
||||
import Control.Monad.State
|
||||
import Control.Arrow
|
||||
|
||||
import System.Posix.Process
|
||||
import System.Environment
|
||||
import System.Directory
|
||||
|
||||
import Graphics.X11.Xlib
|
||||
import Graphics.X11.Xlib.Extras
|
||||
|
||||
import XMonad
|
||||
|
||||
import qualified StackSet as W
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Managing windows
|
||||
|
||||
-- | refresh. Refresh the currently focused window. Resizes to full
|
||||
-- screen and raises the window.
|
||||
refresh :: X ()
|
||||
refresh = do
|
||||
XState {workspace = ws, xineScreens = xinesc
|
||||
,display = d ,layoutDescs = fls ,defaultLayoutDesc = dfltfl } <- get
|
||||
|
||||
flip mapM_ (M.assocs (W.screen2ws ws)) $ \(scn, n) -> do
|
||||
let sc = genericIndex xinesc scn -- temporary coercion!
|
||||
fl = M.findWithDefault dfltfl n fls
|
||||
mapM_ (\(w, rect) -> io $ moveWindowInside d w rect) $
|
||||
case layoutType fl of
|
||||
Full -> fmap (flip (,) sc) $ maybeToList $ W.peekStack n ws
|
||||
Tall -> tile (tileFraction fl) sc $ W.index n ws
|
||||
Wide -> vtile (tileFraction fl) sc $ W.index n ws
|
||||
whenJust (W.peekStack n ws) (io . raiseWindow d)
|
||||
whenJust (W.peek ws) setFocus
|
||||
clearEnterEvents
|
||||
|
||||
-- | clearEnterEvents. Remove all window entry events from the event queue.
|
||||
clearEnterEvents :: X ()
|
||||
clearEnterEvents = do
|
||||
d <- gets display
|
||||
io $ sync d False
|
||||
io $ allocaXEvent $ \p -> fix $ \again -> do
|
||||
more <- checkMaskEvent d enterWindowMask p
|
||||
when more again
|
||||
|
||||
-- | tile. Compute the positions for windows in horizontal layout
|
||||
-- mode.
|
||||
--
|
||||
tile :: Rational -> Rectangle -> [Window] -> [(Window, Rectangle)]
|
||||
tile _ _ [] = []
|
||||
tile _ d [w] = [(w, d)]
|
||||
tile r (Rectangle sx sy sw sh) (w:s)
|
||||
= (w, Rectangle sx sy (fromIntegral lw) sh) : zipWith f [sy, sy + rh ..] s
|
||||
where
|
||||
lw = floor $ fromIntegral sw * r
|
||||
rw = sw - fromIntegral lw
|
||||
rh = fromIntegral sh `div` fromIntegral (length s)
|
||||
f i a = (a, Rectangle (sx + lw) i rw (fromIntegral rh))
|
||||
|
||||
-- | vtile. Tile vertically.
|
||||
vtile :: Rational -> Rectangle -> [Window] -> [(Window, Rectangle)]
|
||||
vtile r rect = map (second flipRect) . tile r (flipRect rect)
|
||||
|
||||
-- | Flip rectangles around
|
||||
flipRect :: Rectangle -> Rectangle
|
||||
flipRect (Rectangle rx ry rw rh) = (Rectangle ry rx rh rw)
|
||||
|
||||
-- | switchLayout. Switch to another layout scheme. Switches the
|
||||
-- current workspace. By convention, a window set as master in Tall mode
|
||||
-- remains as master in Wide mode. When switching from full screen to a
|
||||
-- tiling mode, the currently focused window becomes a master. When
|
||||
-- switching back , the focused window is uppermost.
|
||||
--
|
||||
switchLayout :: X ()
|
||||
switchLayout = layout $ \fl -> fl { layoutType = rotateLayout (layoutType fl) }
|
||||
|
||||
-- | changeSplit. Changes the window split.
|
||||
changeSplit :: Rational -> X ()
|
||||
changeSplit delta = layout $ \fl ->
|
||||
fl { tileFraction = min 1 (max 0 (tileFraction fl + delta)) }
|
||||
|
||||
-- | layout. Modify the current workspace's layout with a pure
|
||||
-- function and refresh.
|
||||
layout :: (LayoutDesc -> LayoutDesc) -> X ()
|
||||
layout f = do
|
||||
modify $ \s ->
|
||||
let fls = layoutDescs s
|
||||
n = W.current . workspace $ s
|
||||
fl = M.findWithDefault (defaultLayoutDesc s) n fls
|
||||
in s { layoutDescs = M.insert n (f fl) fls }
|
||||
refresh
|
||||
|
||||
-- | windows. Modify the current window list with a pure function, and refresh
|
||||
windows :: (WindowSet -> WindowSet) -> X ()
|
||||
windows f = do
|
||||
modify $ \s -> s { workspace = f (workspace s) }
|
||||
refresh
|
||||
ws <- gets workspace
|
||||
trace (show ws) -- log state changes to stderr
|
||||
|
||||
-- | hide. Hide a window by moving it offscreen.
|
||||
hide :: Window -> X ()
|
||||
hide w = withDisplay $ \d -> do
|
||||
(sw,sh) <- gets dimensions
|
||||
io $ moveWindow d w (2*fromIntegral sw) (2*fromIntegral sh)
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Window operations
|
||||
|
||||
-- | setButtonGrab. Tell whether or not to intercept clicks on a given window
|
||||
buttonsToGrab :: [Button]
|
||||
buttonsToGrab = [button1, button2, button3]
|
||||
|
||||
setButtonGrab :: Bool -> Window -> X ()
|
||||
setButtonGrab True w = withDisplay $ \d -> io $
|
||||
flip mapM_ buttonsToGrab $ \b ->
|
||||
grabButton d b anyModifier w False
|
||||
(buttonPressMask .|. buttonReleaseMask)
|
||||
grabModeAsync grabModeSync none none
|
||||
|
||||
setButtonGrab False w = withDisplay $ \d -> io $
|
||||
flip mapM_ buttonsToGrab $ \b ->
|
||||
ungrabButton d b anyModifier w
|
||||
|
||||
-- | moveWindowInside. Moves and resizes w such that it fits inside the given
|
||||
-- rectangle, including its border.
|
||||
moveWindowInside :: Display -> Window -> Rectangle -> IO ()
|
||||
moveWindowInside d w r = do
|
||||
bw <- (fromIntegral . wa_border_width) `liftM` getWindowAttributes d w
|
||||
moveResizeWindow d w (rect_x r) (rect_y r)
|
||||
(rect_width r - bw*2)
|
||||
(rect_height r - bw*2)
|
||||
|
||||
-- | manage. Add a new window to be managed in the current workspace. Bring it into focus.
|
||||
-- If the window is already under management, it is just raised.
|
||||
--
|
||||
manage :: Window -> X ()
|
||||
manage w = do
|
||||
withDisplay $ \d -> io $ do
|
||||
selectInput d w $ structureNotifyMask .|. enterWindowMask .|. propertyChangeMask
|
||||
mapWindow d w
|
||||
windows $ W.push w
|
||||
|
||||
-- | unmanage. A window no longer exists, remove it from the window
|
||||
-- list, on whatever workspace it is.
|
||||
unmanage :: Window -> X ()
|
||||
unmanage w = do
|
||||
windows $ W.delete w
|
||||
withServerX $ do
|
||||
setTopFocus
|
||||
withDisplay $ \d -> io (sync d False)
|
||||
-- TODO, everything operates on the current display, so wrap it up.
|
||||
|
||||
-- | Grab the X server (lock it) from the X monad
|
||||
withServerX :: X () -> X ()
|
||||
withServerX f = withDisplay $ \dpy -> do
|
||||
io $ grabServer dpy
|
||||
f
|
||||
io $ ungrabServer dpy
|
||||
|
||||
safeFocus :: Window -> X ()
|
||||
safeFocus w = do ws <- gets workspace
|
||||
if W.member w ws
|
||||
then setFocus w
|
||||
else do b <- isRoot w
|
||||
when b setTopFocus
|
||||
|
||||
-- | Explicitly set the keyboard focus to the given window
|
||||
setFocus :: Window -> X ()
|
||||
setFocus w = do
|
||||
ws <- gets workspace
|
||||
|
||||
-- clear mouse button grab and border on other windows
|
||||
flip mapM_ (W.visibleWorkspaces ws) $ \n -> do
|
||||
flip mapM_ (W.index n ws) $ \otherw -> do
|
||||
setButtonGrab True otherw
|
||||
setBorder otherw 0xdddddd
|
||||
|
||||
withDisplay $ \d -> io $ setInputFocus d w revertToPointerRoot 0
|
||||
setButtonGrab False w
|
||||
setBorder w 0xff0000 -- make this configurable
|
||||
|
||||
-- This does not use 'windows' intentionally. 'windows' calls refresh,
|
||||
-- which means infinite loops.
|
||||
modify $ \s -> s { workspace = W.raiseFocus w (workspace s) }
|
||||
|
||||
-- | Set the focus to the window on top of the stack, or root
|
||||
setTopFocus :: X ()
|
||||
setTopFocus = do
|
||||
ws <- gets workspace
|
||||
case W.peek ws of
|
||||
Just new -> setFocus new
|
||||
Nothing -> gets theRoot >>= setFocus
|
||||
|
||||
-- | Set the border color for a particular window.
|
||||
setBorder :: Window -> Pixel -> X ()
|
||||
setBorder w p = withDisplay $ \d -> io $ setWindowBorder d w p
|
||||
|
||||
-- | raise. focus to window at offset 'n' in list.
|
||||
-- The currently focused window is always the head of the list
|
||||
raise :: Ordering -> X ()
|
||||
raise = windows . W.rotate
|
||||
|
||||
-- | promote. Move the currently focused window into the master frame
|
||||
promote :: X ()
|
||||
promote = windows W.promote
|
||||
|
||||
-- | Kill the currently focused client
|
||||
kill :: X ()
|
||||
kill = withDisplay $ \d -> do
|
||||
ws <- gets workspace
|
||||
whenJust (W.peek ws) $ \w -> do
|
||||
protocols <- io $ getWMProtocols d w
|
||||
XState {wmdelete = wmdelt, wmprotocols = wmprot} <- get
|
||||
if wmdelt `elem` protocols
|
||||
then io $ allocaXEvent $ \ev -> do
|
||||
setEventType ev clientMessage
|
||||
setClientMessageEvent ev w wmprot 32 wmdelt 0
|
||||
sendEvent d w False noEventMask ev
|
||||
else io (killClient d w) >> return ()
|
||||
|
||||
-- | tag. Move a window to a new workspace, 0 indexed.
|
||||
tag :: WorkspaceId -> X ()
|
||||
tag n = do
|
||||
ws <- gets workspace
|
||||
let m = W.current ws -- :: WorkspaceId
|
||||
when (n /= m) $
|
||||
whenJust (W.peek ws) $ \w -> do
|
||||
hide w
|
||||
windows $ W.shift n
|
||||
|
||||
-- | view. Change the current workspace to workspace at offset n (0 indexed).
|
||||
view :: WorkspaceId -> X ()
|
||||
view n = do
|
||||
ws <- gets workspace
|
||||
let m = W.current ws
|
||||
windows $ W.view n
|
||||
ws' <- gets workspace
|
||||
-- If the old workspace isn't visible anymore, we have to hide the windows
|
||||
-- in case we're switching to an empty workspace.
|
||||
when (m `notElem` (W.visibleWorkspaces ws')) (mapM_ hide (W.index m ws))
|
||||
clearEnterEvents
|
||||
setTopFocus
|
||||
|
||||
-- | 'screenWorkspace sc' returns the workspace number viewed by 'sc'.
|
||||
screenWorkspace :: ScreenId -> X WorkspaceId
|
||||
screenWorkspace sc = fmap (fromMaybe 0 . W.workspace sc) (gets workspace)
|
||||
|
||||
-- | True if window is under management by us
|
||||
isClient :: Window -> X Bool
|
||||
isClient w = liftM (W.member w) (gets workspace)
|
||||
|
||||
-- | Restart xmonad by exec()'ing self. This doesn't save state and xmonad has
|
||||
-- to be in PATH for this to work.
|
||||
restart :: IO ()
|
||||
restart = do
|
||||
prog <- getProgName
|
||||
prog_path <- findExecutable prog
|
||||
case prog_path of
|
||||
Nothing -> return () -- silently fail
|
||||
Just p -> do args <- getArgs
|
||||
executeFile p True args Nothing
|
151
README
151
README
@@ -1,42 +1,149 @@
|
||||
xmonad : a lightweight X11 window manager.
|
||||
xmonad : a tiling window manager
|
||||
|
||||
Motivation:
|
||||
http://xmonad.org
|
||||
|
||||
dwm is great, but we can do better, building a more robust,
|
||||
more correct window manager in fewer lines of code, using strong
|
||||
static typing. Enter Haskell.
|
||||
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.
|
||||
|
||||
If the aim of dwm is to fit in under 2000 lines of C, the aim of
|
||||
xmonad is to fit in under 500 lines of Haskell with similar functionality.
|
||||
Quick start:
|
||||
|
||||
Obtain the dependent libraries, then build with:
|
||||
|
||||
runhaskell Setup.lhs configure --user --prefix=$HOME
|
||||
runhaskell Setup.lhs build
|
||||
runhaskell Setup.lhs install --user
|
||||
|
||||
For the full story, read on.
|
||||
|
||||
Building:
|
||||
|
||||
Get the dependencies
|
||||
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.
|
||||
|
||||
X11 http://hackage.haskell.org/cgi-bin/hackage-scripts/package/X11-1.2
|
||||
(Unfortunately X11-1.2 does not work correctly on AMD64. The latest
|
||||
darcs version from http://darcs.haskell.org/packages/X11 does.)
|
||||
We'll now walk through the complete list of toolchain dependencies.
|
||||
|
||||
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 (included with ghc)
|
||||
* 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, you can find
|
||||
them here:
|
||||
|
||||
X11-extras: darcs get http://darcs.haskell.org/~sjanssen/X11-extras
|
||||
http://haskell.org/ghc
|
||||
|
||||
dmenu 2.{5,6,7} http://www.suckless.org/download/dmenu-2.7.tar.gz
|
||||
For example, in Debian you would install GHC with:
|
||||
|
||||
And then build with Cabal:
|
||||
apt-get install ghc6
|
||||
|
||||
runhaskell Setup.lhs configure --prefix=/home/dons
|
||||
runhaskell Setup.lhs build
|
||||
runhaskell Setup.lhs install
|
||||
It shouldn't be necessary to compile GHC from source -- every common
|
||||
system has a pre-build binary version.
|
||||
|
||||
Then add:
|
||||
* X11 libraries:
|
||||
|
||||
exec /home/dons/bin/xmonad
|
||||
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:
|
||||
|
||||
to the last line of your .xsession file
|
||||
apt-get install libx11-dev
|
||||
|
||||
Typically you need: libXinerama libXext libX11
|
||||
|
||||
* Cabal
|
||||
|
||||
xmonad requires a recent version of Cabal, >= 1.2.0. If you're using
|
||||
GHC 6.8, then it comes bundled with the right version. If you're
|
||||
using GHC 6.6.x, you'll need to build and install Cabal from hackage
|
||||
first:
|
||||
|
||||
http://hackage.haskell.org/cgi-bin/hackage-scripts/package/Cabal
|
||||
|
||||
You can check which version you have with the command:
|
||||
|
||||
$ ghc-pkg list Cabal
|
||||
Cabal-1.2.2.0
|
||||
|
||||
* Haskell libraries: mtl, unix, X11
|
||||
|
||||
Finally, you need the Haskell libraries xmonad depends on. Since
|
||||
you've a working GHC installation now, most of these will be
|
||||
provided. To check whether you've got a package run 'ghc-pkg list
|
||||
some_package_name'. You will need the following packages:
|
||||
|
||||
mtl http://hackage.haskell.org/cgi-bin/hackage-scripts/package/mtl
|
||||
unix http://hackage.haskell.org/cgi-bin/hackage-scripts/package/unix
|
||||
X11 http://hackage.haskell.org/cgi-bin/hackage-scripts/package/X11
|
||||
|
||||
* Build xmonad:
|
||||
|
||||
Once you've got all the dependencies in place (which should be
|
||||
straightforward), build xmonad:
|
||||
|
||||
runhaskell Setup.lhs configure --user --prefix=$HOME
|
||||
runhaskell Setup.lhs build
|
||||
runhaskell Setup.lhs install --user
|
||||
|
||||
And you're done!
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
Running xmonad:
|
||||
|
||||
Add:
|
||||
|
||||
$HOME/bin/xmonad
|
||||
|
||||
to the last line of your .xsession or .xinitrc file.
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
Configuring:
|
||||
|
||||
See the CONFIG document
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
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/cgi-bin/hackage-scripts/package/xmonad-contrib
|
||||
|
||||
darcs version: darcs get http://code.haskell.org/XMonadContrib
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
Other useful programs:
|
||||
|
||||
A nicer xterm replacment, that supports resizing better:
|
||||
|
||||
urxvt http://software.schmorp.de/pkg/rxvt-unicode.html
|
||||
|
||||
For custom status bars:
|
||||
|
||||
dzen http://gotmor.googlepages.com/dzen
|
||||
xmobar http://hackage.haskell.org/cgi-bin/hackage-scripts/package/xmobar
|
||||
|
||||
For a program dispatch menu:
|
||||
|
||||
dmenu http://www.suckless.org/download/
|
||||
gmrun (in your package system)
|
||||
|
||||
Authors:
|
||||
|
||||
Spencer Janssen
|
||||
Don Stewart
|
||||
Jason Creighton
|
||||
|
21
STYLE
Normal file
21
STYLE
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
== Coding guidelines for contributing to
|
||||
== xmonad and the xmonad contributed extensions
|
||||
|
||||
* Comment every top level function (particularly exported funtions), 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. There should be no warnings.
|
||||
|
||||
* Partial functions should be avoided: the window manager should not
|
||||
crash, so do not call `error` or `undefined`
|
||||
|
||||
* Tabs are illegal. Use 4 spaces for indenting.
|
||||
|
||||
* Any pure function added to the core should have QuickCheck properties
|
||||
precisely defining its behaviour.
|
||||
|
||||
* New modules should identify the author, and be submitted under
|
||||
the same license as xmonad (BSD3 license or freer).
|
252
StackSet.hs
252
StackSet.hs
@@ -1,252 +0,0 @@
|
||||
-----------------------------------------------------------------------------
|
||||
-- |
|
||||
-- Module : StackSet
|
||||
-- Copyright : (c) Don Stewart 2007
|
||||
-- License : BSD3-style (see LICENSE)
|
||||
--
|
||||
-- Maintainer : dons@cse.unsw.edu.au
|
||||
-- Stability : stable
|
||||
-- Portability : portable, needs GHC 6.6
|
||||
--
|
||||
-----------------------------------------------------------------------------
|
||||
--
|
||||
-- The 'StackSet' data type encodes a set of stacks. A given stack in the
|
||||
-- set is always current. Elements may appear only once in the entire
|
||||
-- stack set.
|
||||
--
|
||||
-- A StackSet provides a nice data structure for window managers with
|
||||
-- multiple physical screens, and multiple workspaces, where each screen
|
||||
-- has a stack of windows, and a window may be on only 1 screen at any
|
||||
-- given time.
|
||||
--
|
||||
|
||||
module StackSet (
|
||||
StackSet(..), -- abstract
|
||||
|
||||
screen, peekStack, index, empty, peek, push, delete, member,
|
||||
raiseFocus, rotate, promote, shift, view, workspace, fromList,
|
||||
toList, size, visibleWorkspaces, swap {- helper -}
|
||||
) where
|
||||
|
||||
import Data.Maybe
|
||||
import qualified Data.List as L (delete,genericLength,elemIndex)
|
||||
import qualified Data.Map as M
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
-- | The StackSet data structure. Multiple screens containing tables of
|
||||
-- stacks, with a current pointer
|
||||
data StackSet i j a =
|
||||
StackSet
|
||||
{ current :: !i -- ^ the currently visible stack
|
||||
, screen2ws:: !(M.Map j i) -- ^ screen -> workspace
|
||||
, ws2screen:: !(M.Map i j) -- ^ workspace -> screen map
|
||||
, stacks :: !(M.Map i [a]) -- ^ the separate stacks
|
||||
, focus :: !(M.Map i a) -- ^ the window focused in each stack
|
||||
, cache :: !(M.Map a i) -- ^ a cache of windows back to their stacks
|
||||
} deriving Eq
|
||||
|
||||
instance (Show i, Show a) => Show (StackSet i j a) where
|
||||
showsPrec p s r = showsPrec p (show . toList $ s) r
|
||||
|
||||
-- The cache is used to check on insertion that we don't already have
|
||||
-- this window managed on another stack
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
-- | /O(n)/. Create a new empty stacks of size 'n', indexed from 0, with 'm'
|
||||
-- screens. (also indexed from 0) The 0-indexed stack will be current.
|
||||
empty :: (Integral i, Integral j) => Int -> Int -> StackSet i j a
|
||||
empty n m = StackSet { current = 0
|
||||
, screen2ws = wsScrs2Works
|
||||
, ws2screen = wsWorks2Scrs
|
||||
, stacks = M.fromList (zip [0..fromIntegral n-1] (repeat []))
|
||||
, focus = M.empty
|
||||
, cache = M.empty }
|
||||
|
||||
where (scrs,wrks) = unzip $ map (\x -> (fromIntegral x, fromIntegral x)) [0..m-1]
|
||||
wsScrs2Works = M.fromList (zip scrs wrks)
|
||||
wsWorks2Scrs = M.fromList (zip wrks scrs)
|
||||
|
||||
-- | /O(log w)/. True if x is somewhere in the StackSet
|
||||
member :: Ord a => a -> StackSet i j a -> Bool
|
||||
member a w = M.member a (cache w)
|
||||
|
||||
-- | /O(log n)/. Looks up the workspace that x is in, if it is in the StackSet
|
||||
-- lookup :: (Monad m, Ord a) => a -> StackSet i j a -> m i
|
||||
-- lookup x w = M.lookup x (cache w)
|
||||
|
||||
-- | /O(n)/. Number of stacks
|
||||
size :: StackSet i j a -> Int
|
||||
size = M.size . stacks
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
-- | fromList. Build a new StackSet from a list of list of elements,
|
||||
-- keeping track of the currently focused workspace, and the total
|
||||
-- number of workspaces. If there are duplicates in the list, the last
|
||||
-- occurence wins.
|
||||
fromList :: (Integral i, Integral j, Ord a) => (i, Int,[[a]]) -> StackSet i j a
|
||||
fromList (_,_,[]) = error "Cannot build a StackSet from an empty list"
|
||||
|
||||
fromList (n,m,xs) | n < 0 || n >= L.genericLength xs
|
||||
= error $ "Cursor index is out of range: " ++ show (n, length xs)
|
||||
| m < 1 || m > L.genericLength xs
|
||||
= error $ "Can't have more screens than workspaces: " ++ show (m, length xs)
|
||||
|
||||
fromList (o,m,xs) = view o $ foldr (\(i,ys) s ->
|
||||
foldr (\a t -> insert a i t) s ys)
|
||||
(empty (length xs) m) (zip [0..] xs)
|
||||
|
||||
|
||||
-- | toList. Flatten a stackset to a list of lists
|
||||
toList :: StackSet i j a -> (i,Int,[[a]])
|
||||
toList x = (current x, M.size $ screen2ws x, map snd $ M.toList (stacks x))
|
||||
|
||||
-- | Push. Insert an element onto the top of the current stack.
|
||||
-- If the element is already in the current stack, it is moved to the top.
|
||||
-- If the element is managed on another stack, it is removed from that
|
||||
-- stack first.
|
||||
push :: (Integral i, Ord a) => a -> StackSet i j a -> StackSet i j a
|
||||
push k w = insert k (current w) w
|
||||
|
||||
-- | /O(log s)/. Extract the element on the top of the current stack. If no such
|
||||
-- element exists, Nothing is returned.
|
||||
peek :: Integral i => StackSet i j a -> Maybe a
|
||||
peek w = peekStack (current w) w
|
||||
|
||||
-- | /O(log s)/. Extract the element on the top of the given stack. If no such
|
||||
-- element exists, Nothing is returned.
|
||||
peekStack :: Integral i => i -> StackSet i j a -> Maybe a
|
||||
peekStack i w = M.lookup i (focus w)
|
||||
|
||||
-- | /O(log s)/. Index. Extract the stack at workspace 'n'.
|
||||
-- If the index is invalid, an exception is thrown.
|
||||
index :: Integral i => i -> StackSet i j a -> [a]
|
||||
index k w = fromJust (M.lookup k (stacks w))
|
||||
|
||||
-- | view. Set the stack specified by the argument as being visible and the
|
||||
-- current StackSet. If the stack wasn't previously visible, it will become
|
||||
-- visible on the current screen. If the index is out of range an exception is
|
||||
-- thrown.
|
||||
view :: (Integral i, Integral j) => i -> StackSet i j a -> StackSet i j a
|
||||
-- view n w | n >= 0 && n < fromIntegral (M.size (stacks w)) -- coerce
|
||||
|
||||
view n w | M.member n (stacks w)
|
||||
= if M.member n (ws2screen w) then w { current = n }
|
||||
else tweak (fromJust $ screen (current w) w)
|
||||
| otherwise = error $ "view: index out of bounds: " ++ show n
|
||||
where
|
||||
tweak sc = w { screen2ws = M.insert sc n (screen2ws w)
|
||||
, ws2screen = M.insert n sc (M.filter (/=sc) (ws2screen w))
|
||||
, current = n
|
||||
}
|
||||
|
||||
-- | That screen that workspace 'n' is visible on, if any.
|
||||
screen :: Integral i => i -> StackSet i j a -> Maybe j
|
||||
screen n w = M.lookup n (ws2screen w)
|
||||
|
||||
-- | The workspace visible on screen 'sc'. Nothing if screen is out of bounds.
|
||||
workspace :: Integral j => j -> StackSet i j a -> Maybe i
|
||||
workspace sc w = M.lookup sc (screen2ws w)
|
||||
|
||||
-- | A list of the currently visible workspaces.
|
||||
visibleWorkspaces :: StackSet i j a -> [i]
|
||||
visibleWorkspaces = M.keys . ws2screen
|
||||
|
||||
--
|
||||
-- | /O(log n)/. rotate. cycle the current window list up or down.
|
||||
-- Has the effect of rotating focus. In fullscreen mode this will cause
|
||||
-- a new window to be visible.
|
||||
--
|
||||
-- rotate EQ --> [5,6,7,8,1,2,3,4]
|
||||
-- rotate GT --> [6,7,8,1,2,3,4,5]
|
||||
-- rotate LT --> [4,5,6,7,8,1,2,3]
|
||||
--
|
||||
-- where xs = [5..8] ++ [1..4]
|
||||
--
|
||||
rotate :: (Integral i, Eq a) => Ordering -> StackSet i j a -> StackSet i j a
|
||||
rotate o w = maybe w id $ do
|
||||
f <- M.lookup (current w) (focus w)
|
||||
s <- M.lookup (current w) (stacks w)
|
||||
ea <- case o of
|
||||
EQ -> Nothing
|
||||
GT -> elemAfter f s
|
||||
LT -> elemAfter f (reverse s)
|
||||
return $ w { focus = M.insert (current w) ea (focus w) }
|
||||
|
||||
-- | /O(log n)/. shift. move the client on top of the current stack to
|
||||
-- the top of stack 'n'. If the stack to move to is not valid, and
|
||||
-- exception is thrown.
|
||||
--
|
||||
shift :: (Integral i, Ord a) => i -> StackSet i j a -> StackSet i j a
|
||||
shift n w = maybe w (\k -> insert k n (delete k w)) (peek w)
|
||||
|
||||
-- | /O(log n)/. Insert an element onto the top of stack 'n'.
|
||||
-- If the element is already in the stack 'n', it is moved to the top.
|
||||
-- If the element exists on another stack, it is removed from that stack.
|
||||
-- If the index is wrong an exception is thrown.
|
||||
--
|
||||
insert :: (Integral i, Ord a) => a -> i -> StackSet i j a -> StackSet i j a
|
||||
insert k n old = new { cache = M.insert k n (cache new)
|
||||
, stacks = M.adjust (k:) n (stacks new)
|
||||
, focus = M.insert n k (focus new) }
|
||||
where new = delete k old
|
||||
|
||||
-- | /O(log n)/. Delete an element entirely from from the StackSet.
|
||||
-- This can be used to ensure that a given element is not managed elsewhere.
|
||||
-- If the element doesn't exist, the original StackSet is returned unmodified.
|
||||
delete :: (Integral i, Ord a) => a -> StackSet i j a -> StackSet i j a
|
||||
delete k w = maybe w tweak (M.lookup k (cache w))
|
||||
where
|
||||
tweak i = w { cache = M.delete k (cache w)
|
||||
, stacks = M.adjust (L.delete k) i (stacks w)
|
||||
, focus = M.update (\k' -> if k == k' then elemAfter k (stacks w M.! i)
|
||||
else Just k') i
|
||||
(focus w)
|
||||
}
|
||||
|
||||
-- | /O(log n)/. If the given window is contained in a workspace, make it the
|
||||
-- focused window of that workspace, and make that workspace the current one.
|
||||
raiseFocus :: (Integral i, Integral j, Ord a) => a -> StackSet i j a -> StackSet i j a
|
||||
raiseFocus k w = case M.lookup k (cache w) of
|
||||
Nothing -> w
|
||||
Just i -> (view i w) { focus = M.insert i k (focus w) }
|
||||
|
||||
-- | Swap the currently focused window with the master window (the
|
||||
-- window on top of the stack). Focus moves to the master.
|
||||
promote :: (Integral i, Ord a) => StackSet i j a -> StackSet i j a
|
||||
promote w = maybe w id $ do
|
||||
a <- peek w -- fail if null
|
||||
let w' = w { stacks = M.adjust (\s -> swap a (head s) s) (current w) (stacks w) }
|
||||
return $ insert a (current w) w' -- and maintain focus (?)
|
||||
|
||||
--
|
||||
-- | Swap first occurences of 'a' and 'b' in list.
|
||||
-- If both elements are not in the list, the list is unchanged.
|
||||
--
|
||||
-- Given a set as a list (no duplicates)
|
||||
--
|
||||
-- > swap a b . swap a b == id
|
||||
--
|
||||
swap :: Eq a => a -> a -> [a] -> [a]
|
||||
swap a b xs
|
||||
| a == b = xs -- do nothing
|
||||
| Just ai <- L.elemIndex a xs
|
||||
, Just bi <- L.elemIndex b xs = insertAt bi a (insertAt ai b xs)
|
||||
where
|
||||
insertAt n x ys = as ++ x : tail bs
|
||||
where (as,bs) = splitAt n ys
|
||||
|
||||
swap _ _ xs = xs -- do nothing
|
||||
|
||||
--
|
||||
-- cycling:
|
||||
-- promote w = w { stacks = M.adjust next (current w) (stacks w) }
|
||||
-- where next [] = []
|
||||
-- next xs = last xs : init xs
|
||||
--
|
||||
|
||||
-- | Find the element in the (circular) list after given element.
|
||||
elemAfter :: Eq a => a -> [a] -> Maybe a
|
||||
elemAfter w ws = listToMaybe . filter (/= w) . dropWhile (/= w) $ ws ++ ws
|
40
TODO
40
TODO
@@ -1,29 +1,21 @@
|
||||
- think about the statusbar/multithreading.
|
||||
Three shared TVars:
|
||||
windowTitle :: TVar String
|
||||
workspace :: TVar Int
|
||||
statusText :: TVar String
|
||||
Three threads:
|
||||
Main thread, handles all of the events that it handles now. When
|
||||
necessary, it writes to workspace or windowTitle
|
||||
- Write down invariants for the window life cycle, especially:
|
||||
- When are borders set? Prove that the current handling is sufficient.
|
||||
|
||||
Status IO thread, the algorithm is something like this:
|
||||
forever $ do
|
||||
s <- getLine
|
||||
atomic (writeTVar statusText s)
|
||||
- current floating layer handling is unoptimal. FocusUp should raise,
|
||||
for example
|
||||
|
||||
Statusbar drawing thread, waits for changes in all three TVars, and
|
||||
redraws whenever it finds a change.
|
||||
- Issues still with stacking order.
|
||||
|
||||
- Notes on new StackSet:
|
||||
= Release management =
|
||||
|
||||
The actors: screens, workspaces, windows
|
||||
* configuration documentation
|
||||
|
||||
Invariants:
|
||||
- There is exactly one screen in focus at any given time.
|
||||
- A screen views exactly one workspace.
|
||||
- A workspace is visible on one or zero screens.
|
||||
- A workspace has zero or more windows.
|
||||
- A workspace has either one or zero windows in focus. Zero if the
|
||||
workspace has no windows, one in all other cases.
|
||||
- A window is a member of only one workspace.
|
||||
* generate haddocks for core and XMC, upload to xmonad.org
|
||||
* generate manpage, generate html manpage
|
||||
* double check README build instructions
|
||||
* test core with 6.6 and 6.8
|
||||
* bump xmonad.cabal version and X11 version
|
||||
* upload X11 and xmonad to hackage
|
||||
* check examples/text in user-facing Config.hs
|
||||
* check tour.html and intro.html are up to date, and mention all core bindings
|
||||
* confirm template config is type correct
|
||||
|
151
XMonad.hs
151
XMonad.hs
@@ -1,122 +1,47 @@
|
||||
{-# OPTIONS -fglasgow-exts #-}
|
||||
-----------------------------------------------------------------------------
|
||||
--------------------------------------------------------------------
|
||||
-- |
|
||||
-- Module : XMonad.hs
|
||||
-- Copyright : (c) Spencer Janssen 2007
|
||||
-- License : BSD3-style (see LICENSE)
|
||||
-- Module : XMonad
|
||||
-- Copyright : (c) Don Stewart
|
||||
-- License : BSD3
|
||||
--
|
||||
-- Maintainer : sjanssen@cse.unl.edu
|
||||
-- Stability : unstable
|
||||
-- Portability : not portable, uses cunning newtype deriving
|
||||
-- Maintainer: Don Stewart <dons@galois.com>
|
||||
-- Stability : provisional
|
||||
-- Portability:
|
||||
--
|
||||
-----------------------------------------------------------------------------
|
||||
--
|
||||
-- The X monad, a state monad transformer over IO, for the window
|
||||
-- manager state, and support routines.
|
||||
--------------------------------------------------------------------
|
||||
--
|
||||
-- Useful exports for configuration files.
|
||||
|
||||
module XMonad (
|
||||
X, WindowSet, WorkspaceId(..), ScreenId(..), XState(..), Layout(..), LayoutDesc(..),
|
||||
runX, io, withDisplay, isRoot, spawn, trace, whenJust, rotateLayout
|
||||
) where
|
||||
|
||||
import StackSet (StackSet)
|
||||
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 System.IO
|
||||
import System.Posix.Process (executeFile, forkProcess, getProcessStatus)
|
||||
import System.Exit
|
||||
import Graphics.X11.Xlib
|
||||
|
||||
import qualified Data.Map as M
|
||||
|
||||
-- | XState, the window manager state.
|
||||
-- Just the display, width, height and a window list
|
||||
data XState = XState
|
||||
{ display :: Display -- ^ the X11 display
|
||||
|
||||
, theRoot :: !Window -- ^ the root window
|
||||
, wmdelete :: !Atom -- ^ window deletion atom
|
||||
, wmprotocols :: !Atom -- ^ wm protocols atom
|
||||
, dimensions :: !(Int,Int) -- ^ dimensions of the screen,
|
||||
-- used for hiding windows
|
||||
, workspace :: !WindowSet -- ^ workspace list
|
||||
|
||||
, xineScreens :: ![Rectangle] -- ^ dimensions of each screen
|
||||
, defaultLayoutDesc :: !LayoutDesc -- ^ default layout
|
||||
, layoutDescs :: !(M.Map WorkspaceId LayoutDesc) -- ^ mapping of workspaces
|
||||
-- to descriptions of their layouts
|
||||
}
|
||||
|
||||
type WindowSet = StackSet WorkspaceId ScreenId Window
|
||||
|
||||
-- | Virtual workspace indicies
|
||||
newtype WorkspaceId = W Int deriving (Eq,Ord,Show,Enum,Num,Integral,Real)
|
||||
|
||||
-- | Physical screen indicies
|
||||
newtype ScreenId = S Int deriving (Eq,Ord,Show,Enum,Num,Integral,Real)
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
-- | The X monad, a StateT transformer over IO encapsulating the window
|
||||
-- manager state
|
||||
newtype X a = X (StateT XState IO a)
|
||||
deriving (Functor, Monad, MonadIO, MonadState XState)
|
||||
|
||||
-- | Run the X monad, given a chunk of X monad code, and an initial state
|
||||
-- Return the result, and final state
|
||||
runX :: XState -> X a -> IO ()
|
||||
runX st (X a) = runStateT a st >> return ()
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Convenient wrappers to state
|
||||
|
||||
-- | Run a monad action with the current display settings
|
||||
withDisplay :: (Display -> X ()) -> X ()
|
||||
withDisplay f = gets display >>= f
|
||||
|
||||
-- | True if the given window is the root window
|
||||
isRoot :: Window -> X Bool
|
||||
isRoot w = liftM (w==) (gets theRoot)
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Layout handling
|
||||
|
||||
-- | The different layout modes
|
||||
data Layout = Full | Tall | Wide deriving (Enum, Bounded, Eq)
|
||||
|
||||
-- | 'rot' for Layout.
|
||||
rotateLayout :: Layout -> Layout
|
||||
rotateLayout x = if x == maxBound then minBound else succ x
|
||||
|
||||
-- | A full description of a particular workspace's layout parameters.
|
||||
data LayoutDesc = LayoutDesc { layoutType :: !Layout
|
||||
, tileFraction :: !Rational
|
||||
}
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- Utilities
|
||||
|
||||
-- | Lift an IO action into the X monad
|
||||
io :: IO a -> X a
|
||||
io = liftIO
|
||||
{-# INLINE io #-}
|
||||
|
||||
-- | spawn. Launch an external application
|
||||
spawn :: String -> X ()
|
||||
spawn x = io $ do
|
||||
pid <- forkProcess $ do
|
||||
forkProcess (executeFile "/bin/sh" False ["-c", x] Nothing)
|
||||
exitWith ExitSuccess
|
||||
return ()
|
||||
getProcessStatus True False pid
|
||||
return ()
|
||||
|
||||
-- | 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
|
||||
|
||||
-- | 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
|
||||
import Control.Monad.Reader
|
||||
|
253
XMonad/Config.hs
Normal file
253
XMonad/Config.hs
Normal file
@@ -0,0 +1,253 @@
|
||||
{-# OPTIONS -fno-warn-missing-signatures #-}
|
||||
-----------------------------------------------------------------------------
|
||||
-- |
|
||||
-- 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 'defaultConfig'. 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) where
|
||||
|
||||
--
|
||||
-- Useful imports
|
||||
--
|
||||
import XMonad.Core as XMonad hiding
|
||||
(workspaces,manageHook,numlockMask,keys,logHook,startupHook,borderWidth,mouseBindings
|
||||
,layoutHook,modMask,terminal,normalBorderColor,focusedBorderColor,focusFollowsMouse)
|
||||
import qualified XMonad.Core as XMonad
|
||||
(workspaces,manageHook,numlockMask,keys,logHook,startupHook,borderWidth,mouseBindings
|
||||
,layoutHook,modMask,terminal,normalBorderColor,focusedBorderColor,focusFollowsMouse)
|
||||
|
||||
import XMonad.Layout
|
||||
import XMonad.Operations
|
||||
import XMonad.ManageHook
|
||||
import qualified XMonad.StackSet as W
|
||||
import Data.Bits ((.|.))
|
||||
import qualified Data.Map as M
|
||||
import System.Exit
|
||||
import Graphics.X11.Xlib
|
||||
|
||||
-- | 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
|
||||
|
||||
-- | 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 = "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 =? "Gimp" --> 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 ()
|
||||
|
||||
-- | 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
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- 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
|
||||
|
||||
-- | 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 "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 $ 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
|
||||
|
||||
-- toggle the status bar gap
|
||||
--, ((modMask , xK_b ), modifyGap (\i n -> let x = (XMonad.defaultGaps conf ++ 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 ), restart "xmonad" True) -- %! Restart xmonad
|
||||
]
|
||||
++
|
||||
-- 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)]]
|
||||
|
||||
-- | 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.swapMaster))
|
||||
-- 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
|
||||
>> windows W.swapMaster))
|
||||
-- you may also bind events to the mouse scroll wheel (button4 and button5)
|
||||
]
|
||||
|
||||
-- | And, finally, the default set of configuration values itself
|
||||
defaultConfig = XConfig
|
||||
{ XMonad.borderWidth = borderWidth
|
||||
, XMonad.workspaces = workspaces
|
||||
, XMonad.layoutHook = layout
|
||||
, XMonad.terminal = terminal
|
||||
, XMonad.normalBorderColor = normalBorderColor
|
||||
, XMonad.focusedBorderColor = focusedBorderColor
|
||||
, XMonad.numlockMask = numlockMask
|
||||
, XMonad.modMask = defaultModMask
|
||||
, XMonad.keys = keys
|
||||
, XMonad.logHook = logHook
|
||||
, XMonad.startupHook = startupHook
|
||||
, XMonad.mouseBindings = mouseBindings
|
||||
, XMonad.manageHook = manageHook
|
||||
, XMonad.focusFollowsMouse = focusFollowsMouse }
|
426
XMonad/Core.hs
Normal file
426
XMonad/Core.hs
Normal file
@@ -0,0 +1,426 @@
|
||||
{-# LANGUAGE ExistentialQuantification, FlexibleInstances, GeneralizedNewtypeDeriving,
|
||||
MultiParamTypeClasses, TypeSynonymInstances, CPP #-}
|
||||
-- required for deriving Typeable
|
||||
{-# OPTIONS_GHC -fglasgow-exts #-}
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- |
|
||||
-- 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(..),
|
||||
runX, catchX, userCode, io, catchIO, doubleFork,
|
||||
withDisplay, withWindowSet, isRoot, runOnWorkspaces,
|
||||
getAtom, spawn, getXMonadDir, recompile, trace, whenJust, whenX,
|
||||
atom_WM_STATE, atom_WM_PROTOCOLS, atom_WM_DELETE_WINDOW, ManageHook, Query(..), runQuery
|
||||
) where
|
||||
|
||||
import XMonad.StackSet hiding (modify)
|
||||
|
||||
import Prelude hiding ( catch )
|
||||
import Control.Exception (catch, bracket, throw, Exception(ExitException))
|
||||
import Control.Applicative
|
||||
import Control.Monad.State
|
||||
import Control.Monad.Reader
|
||||
import System.IO
|
||||
import System.Info
|
||||
import System.Posix.Process (executeFile, forkProcess, getProcessStatus, createSession)
|
||||
import System.Process
|
||||
import System.Directory
|
||||
import System.Exit
|
||||
import Graphics.X11.Xlib
|
||||
import Graphics.X11.Xlib.Extras (Event)
|
||||
import Data.Typeable
|
||||
import Data.Monoid
|
||||
|
||||
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 ())) }
|
||||
|
||||
-- | 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?
|
||||
}
|
||||
|
||||
-- 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
|
||||
, workspaces :: ![String] -- ^ The list of workspaces' names
|
||||
, numlockMask :: !KeyMask -- ^ The numlock modifier
|
||||
, 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
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
#ifndef __HADDOCK__
|
||||
deriving (Functor, Monad, MonadIO, MonadState XState, MonadReader XConf)
|
||||
#endif
|
||||
|
||||
instance Applicative X where
|
||||
pure = return
|
||||
(<*>) = ap
|
||||
|
||||
instance (Monoid a) => Monoid (X a) where
|
||||
mempty = return mempty
|
||||
mappend = liftM2 mappend
|
||||
|
||||
type ManageHook = Query (Endo WindowSet)
|
||||
newtype Query a = Query (ReaderT Window X a)
|
||||
#ifndef __HADDOCK__
|
||||
deriving (Functor, Monad, MonadReader Window, MonadIO)
|
||||
#endif
|
||||
|
||||
runQuery :: Query a -> Window -> X a
|
||||
runQuery (Query m) w = runReaderT m w
|
||||
|
||||
instance Monoid a => Monoid (Query a) where
|
||||
mempty = return mempty
|
||||
mappend = liftM2 mappend
|
||||
|
||||
-- | 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 = (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 '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
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- | 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 `catch` \e -> hPrint stderr e >> hFlush stderr)
|
||||
|
||||
-- | spawn. Launch an external application
|
||||
spawn :: MonadIO m => String -> m ()
|
||||
spawn x = doubleFork $ executeFile "/bin/sh" False ["-c", x] Nothing
|
||||
|
||||
-- | Double fork and execute an 'IO' action (usually one of the exec family of
|
||||
-- functions)
|
||||
doubleFork :: MonadIO m => IO () -> m ()
|
||||
doubleFork m = io $ do
|
||||
pid <- forkProcess $ do
|
||||
forkProcess (createSession >> m)
|
||||
exitWith ExitSuccess
|
||||
getProcessStatus True False pid
|
||||
return ()
|
||||
|
||||
-- | 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 @~\/.xmonad@.
|
||||
getXMonadDir :: MonadIO m => m String
|
||||
getXMonadDir = io $ getAppUserDataDirectory "xmonad"
|
||||
|
||||
-- | 'recompile force', recompile @~\/.xmonad\/xmonad.hs@ when any of the
|
||||
-- following apply:
|
||||
--
|
||||
-- * force is 'True'
|
||||
--
|
||||
-- * the xmonad executable does not exist
|
||||
--
|
||||
-- * the xmonad executable is older than xmonad.hs
|
||||
--
|
||||
-- The -i flag is used to restrict recompilation to the xmonad.hs file only.
|
||||
--
|
||||
-- Compilation errors (if any) are logged to ~\/.xmonad\/xmonad.errors. 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
|
||||
dir <- getXMonadDir
|
||||
let binn = "xmonad-"++arch++"-"++os
|
||||
bin = dir ++ "/" ++ binn
|
||||
base = dir ++ "/" ++ "xmonad"
|
||||
err = base ++ ".errors"
|
||||
src = base ++ ".hs"
|
||||
srcT <- getModTime src
|
||||
binT <- getModTime bin
|
||||
if (force || srcT > binT)
|
||||
then do
|
||||
status <- bracket (openFile err WriteMode) hClose $ \h -> do
|
||||
waitForProcess =<< runProcess "ghc" ["--make", "xmonad.hs", "-i", "-no-recomp", "-v0", "-o",binn] (Just dir)
|
||||
Nothing Nothing Nothing (Just h)
|
||||
|
||||
-- now, if it fails, run xmessage to let the user know:
|
||||
when (status /= ExitSuccess) $ do
|
||||
ghcErr <- readFile err
|
||||
let msg = unlines $
|
||||
["Error detected while loading xmonad configuration file: " ++ src]
|
||||
++ lines ghcErr ++ ["","Please check the file for errors."]
|
||||
-- nb, the ordering of printing, then forking, is crucial due to
|
||||
-- lazy evaluation
|
||||
hPutStrLn stderr msg
|
||||
doubleFork $ executeFile "xmessage" True ["-default", "okay", msg] Nothing
|
||||
return (status == ExitSuccess)
|
||||
else return True
|
||||
where getModTime f = catch (Just <$> getModificationTime f) (const $ return Nothing)
|
||||
|
||||
-- | 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
|
207
XMonad/Layout.hs
Normal file
207
XMonad/Layout.hs
Normal file
@@ -0,0 +1,207 @@
|
||||
{-# OPTIONS_GHC -fglasgow-exts #-} -- For deriving Data/Typeable
|
||||
{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, PatternGuards, TypeSynonymInstances #-}
|
||||
|
||||
-- --------------------------------------------------------------------------
|
||||
-- |
|
||||
-- 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 !Int !Rational !Rational 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
|
||||
(|||) :: (LayoutClass l a, LayoutClass r a) => 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 = do
|
||||
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'
|
314
XMonad/Main.hsc
Normal file
314
XMonad/Main.hsc
Normal file
@@ -0,0 +1,314 @@
|
||||
{-# LANGUAGE MultiParamTypeClasses, FlexibleContexts, ForeignFunctionInterface #-}
|
||||
----------------------------------------------------------------------------
|
||||
-- |
|
||||
-- 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) where
|
||||
|
||||
import Data.Bits
|
||||
import Data.List ((\\))
|
||||
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 Foreign.C
|
||||
import Foreign.Ptr
|
||||
|
||||
import System.Environment (getArgs)
|
||||
import System.Posix.Signals
|
||||
|
||||
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
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Locale support
|
||||
|
||||
#include <locale.h>
|
||||
|
||||
foreign import ccall unsafe "locale.h setlocale"
|
||||
c_setlocale :: CInt -> Ptr CChar -> IO (Ptr CChar)
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
-- |
|
||||
-- The main entry point
|
||||
--
|
||||
xmonad :: (LayoutClass l Window, Read (l Window)) => XConfig l -> IO ()
|
||||
xmonad initxmc = do
|
||||
-- setup locale information from environment
|
||||
withCString "" $ c_setlocale (#const LC_ALL)
|
||||
-- ignore SIGPIPE
|
||||
installHandler openEndedPipe Ignore Nothing
|
||||
-- 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
|
||||
xinesc <- getCleanedScreenInfo dpy
|
||||
nbc <- do v <- initColor dpy $ normalBorderColor xmc
|
||||
~(Just nbc_) <- initColor dpy $ normalBorderColor Default.defaultConfig
|
||||
return (fromMaybe nbc_ v)
|
||||
|
||||
fbc <- do v <- initColor dpy $ focusedBorderColor xmc
|
||||
~(Just fbc_) <- initColor dpy $ focusedBorderColor Default.defaultConfig
|
||||
return (fromMaybe fbc_ v)
|
||||
|
||||
hSetBuffering stdout NoBuffering
|
||||
args <- getArgs
|
||||
|
||||
let layout = layoutHook xmc
|
||||
lreads = readsLayout layout
|
||||
initialWinset = new layout (workspaces xmc) $ map SD xinesc
|
||||
|
||||
maybeRead reads' s = case reads' s of
|
||||
[(x, "")] -> Just x
|
||||
_ -> Nothing
|
||||
|
||||
winset = fromMaybe initialWinset $ do
|
||||
("--resume" : s : _) <- return args
|
||||
ws <- maybeRead reads s
|
||||
return . W.ensureTags layout (workspaces xmc)
|
||||
$ W.mapLayout (fromMaybe layout . maybeRead lreads) ws
|
||||
|
||||
cf = XConf
|
||||
{ display = dpy
|
||||
, config = xmc
|
||||
, theRoot = rootw
|
||||
, normalBorder = nbc
|
||||
, focusedBorder = fbc
|
||||
, keyActions = keys xmc xmc
|
||||
, buttonActions = mouseBindings xmc xmc
|
||||
, mouseFocused = False }
|
||||
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
|
||||
|
||||
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.
|
||||
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_ $ handle =<< io (nextEvent dpy e >> getEvent e)
|
||||
|
||||
return ()
|
||||
where forever_ a = a >> forever_ a
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- | 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
|
||||
userCode $ whenJust (M.lookup (mClean, s) ks) 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) $ 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 == 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
|
||||
m <- cleanMask $ ev_state e
|
||||
ba <- asks buttonActions
|
||||
if isr then userCode $ whenJust (M.lookup (m, b) ba) ($ ev_subwindow e)
|
||||
else focus w
|
||||
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
|
||||
&& ev_detail e /= notifyInferior
|
||||
= whenX (asks $ focusFollowsMouse . config) (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
|
||||
|
||||
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 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 =<< asks (logHook . config)
|
||||
|
||||
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 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
|
||||
ks <- asks keyActions
|
||||
forM_ (M.keys ks) $ \(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
|
||||
ems <- extraModifiers
|
||||
ba <- asks buttonActions
|
||||
mapM_ (\(m,b) -> mapM_ (grab b . (m .|.)) ems) (M.keys $ ba)
|
114
XMonad/ManageHook.hs
Normal file
114
XMonad/ManageHook.hs
Normal file
@@ -0,0 +1,114 @@
|
||||
{-# 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 Prelude hiding (catch)
|
||||
import XMonad.Core
|
||||
import Graphics.X11.Xlib.Extras
|
||||
import Graphics.X11.Xlib (Display, Window, internAtom, wM_NAME)
|
||||
import Control.Exception (bracket, catch)
|
||||
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 :: ManageHook
|
||||
idHook = doF id
|
||||
|
||||
-- | Compose two 'ManageHook's.
|
||||
(<+>) :: ManageHook -> ManageHook -> ManageHook
|
||||
(<+>) = mappend
|
||||
|
||||
-- | Compose the list of 'ManageHook's.
|
||||
composeAll :: [ManageHook] -> ManageHook
|
||||
composeAll = mconcat
|
||||
|
||||
-- | @p --> x@. If @p@ returns 'True', execute the 'ManageHook'.
|
||||
(-->) :: Query Bool -> ManageHook -> ManageHook
|
||||
p --> f = p >>= \b -> if b then f else 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)
|
||||
`catch` \_ -> getTextProperty d w wM_NAME
|
||||
extract = fmap head . wcTextPropertyToTextList d
|
||||
io $ bracket getProp (xFree . tp_value) extract `catch` \_ -> 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 :: (WindowSet -> WindowSet) -> ManageHook
|
||||
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 = doF . W.shift
|
542
XMonad/Operations.hs
Normal file
542
XMonad/Operations.hs
Normal file
@@ -0,0 +1,542 @@
|
||||
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||||
{-# OPTIONS_GHC -fglasgow-exts #-} -- For deriving Data/Typeable
|
||||
{-# LANGUAGE 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 (appEndo)
|
||||
import Data.List (nub, (\\), find)
|
||||
import Data.Bits ((.|.), (.&.), complement)
|
||||
import Data.Ratio
|
||||
import qualified Data.Map as M
|
||||
import qualified Data.Set as S
|
||||
|
||||
import Control.Applicative
|
||||
import Control.Monad.Reader
|
||||
import Control.Monad.State
|
||||
import qualified Control.Exception as C
|
||||
|
||||
import System.IO
|
||||
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 <- fmap appEndo (runQuery mh w) `catchX` return id
|
||||
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 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
|
||||
|
||||
-- | 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 -> 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 (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
|
||||
visible <- fmap concat $ forM (zip allscreens summed_visible) $ \ (w, vis) -> do
|
||||
let wsp = W.workspace w
|
||||
this = W.view n ws
|
||||
n = W.tag wsp
|
||||
flt = filter (flip M.member (W.floating ws)) (W.index this)
|
||||
tiled = (W.stack . W.workspace . W.current $ this)
|
||||
>>= W.filter (`M.notMember` W.floating ws)
|
||||
>>= W.filter (`notElem` vis)
|
||||
viewrect@(Rectangle sx sy sw sh) = 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
|
||||
mapM_ (uncurry tileWindow) rs
|
||||
updateLayout n ml'
|
||||
|
||||
-- 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
|
||||
asks (logHook . config) >>= userCode
|
||||
|
||||
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
|
||||
|
||||
-- | 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 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
|
||||
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 -> do
|
||||
setWMState w iconicState
|
||||
io $ selectInput d w $ clientMask
|
||||
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 -> do
|
||||
bw <- (fromIntegral . wa_border_width) <$> 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)
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
|
||||
-- | 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 = 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) $ do
|
||||
local (\c -> c { mouseFocused = True }) $ do
|
||||
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 <$> 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 <$> 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 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 <- asks (numlockMask . config)
|
||||
return [0, nlm, lockMask, nlm .|. lockMask ]
|
||||
|
||||
-- | Strip numlock\/capslock from a mask
|
||||
cleanMask :: KeyMask -> X KeyMask
|
||||
cleanMask km = do
|
||||
nlm <- asks (numlockMask . config)
|
||||
return (complement (nlm .|. lockMask) .&. km)
|
||||
|
||||
-- | Get the 'Pixel' value for a named color
|
||||
initColor :: Display -> String -> IO (Maybe Pixel)
|
||||
initColor dpy c = C.handle (\_ -> return Nothing) $
|
||||
(Just . color_pixel . fst) <$> allocNamedColor dpy colormap c
|
||||
where colormap = defaultColormap dpy (defaultScreen dpy)
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
-- | @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
|
||||
args <- if resume then gets (("--resume":) . return . showWs . windowset) else return []
|
||||
catchIO (executeFile prog True args Nothing)
|
||||
where showWs = show . W.mapLayout show
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- | 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
|
||||
bw <- fi <$> asks (borderWidth . config)
|
||||
|
||||
-- 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
|
||||
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.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
|
||||
|
||||
-- | 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`
|
||||
applySizeHintsContents sh (ex - fromIntegral (wa_x wa),
|
||||
ey - fromIntegral (wa_y wa)))
|
||||
(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
|
||||
bw <- fmap (fromIntegral . wa_border_width) $ getWindowAttributes d w
|
||||
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
|
548
XMonad/StackSet.hs
Normal file
548
XMonad/StackSet.hs
Normal file
@@ -0,0 +1,548 @@
|
||||
{-# LANGUAGE PatternGuards #-}
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
-- |
|
||||
-- Module : XMonad.StackSet
|
||||
-- Copyright : (c) Don Stewart 2007
|
||||
-- License : BSD3-style (see LICENSE)
|
||||
--
|
||||
-- Maintainer : dons@galois.com
|
||||
-- Stability : experimental
|
||||
-- Portability : portable, Haskell 98
|
||||
--
|
||||
|
||||
module XMonad.StackSet (
|
||||
-- * Introduction
|
||||
-- $intro
|
||||
|
||||
-- ** 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, currentTag,
|
||||
-- * Operations on the current stack
|
||||
-- $stackOperations
|
||||
peek, index, integrate, integrate', differentiate,
|
||||
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
|
||||
-- * Composite operations
|
||||
-- $composite
|
||||
shift, shiftWin,
|
||||
|
||||
-- for testing
|
||||
abort
|
||||
) where
|
||||
|
||||
import Prelude hiding (filter)
|
||||
import Data.Maybe (listToMaybe,fromJust,isJust)
|
||||
import qualified Data.List as L (deleteBy,find,splitAt,filter,nub)
|
||||
import Data.List ( (\\) )
|
||||
import qualified Data.Map as M (Map,insert,delete,empty)
|
||||
|
||||
-- $intro
|
||||
--
|
||||
-- The 'StackSet' data type encodes a window manager abstraction. The
|
||||
-- window manager is a set of virtual workspaces. On each workspace is a
|
||||
-- stack of windows. A given workspace is always current, and a given
|
||||
-- window on each workspace has focus. The focused window on the current
|
||||
-- workspace is the one which will take user input. It can be visualised
|
||||
-- as follows:
|
||||
--
|
||||
-- > Workspace { 0*} { 1 } { 2 } { 3 } { 4 }
|
||||
-- >
|
||||
-- > Windows [1 [] [3* [6*] []
|
||||
-- > ,2*] ,4
|
||||
-- > ,5]
|
||||
--
|
||||
-- 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
|
||||
--
|
||||
-- We encode all the focus tracking directly in the data structure, with a 'zipper':
|
||||
--
|
||||
-- A Zipper is essentially an `updateable' and yet pure functional
|
||||
-- cursor into a data structure. Zipper is also a delimited
|
||||
-- continuation reified as a data structure.
|
||||
--
|
||||
-- The Zipper lets us replace an item deep in a complex data
|
||||
-- structure, e.g., a tree or a term, without an mutation. The
|
||||
-- resulting data structure will share as much of its components with
|
||||
-- the old structure as possible.
|
||||
--
|
||||
-- Oleg Kiselyov, 27 Apr 2005, haskell\@, "Zipper as a delimited continuation"
|
||||
--
|
||||
-- We use the zipper to keep track of the focused workspace and the
|
||||
-- focused window on each workspace, allowing us to have correct focus
|
||||
-- by construction. We closely follow Huet's original implementation:
|
||||
--
|
||||
-- G. Huet, /Functional Pearl: The Zipper/,
|
||||
-- 1997, J. Functional Programming 75(5):549-554.
|
||||
-- and:
|
||||
-- R. Hinze and J. Jeuring, /Functional Pearl: The Web/.
|
||||
--
|
||||
-- and Conor McBride's zipper differentiation paper.
|
||||
-- Another good reference is:
|
||||
--
|
||||
-- The Zipper, Haskell wikibook
|
||||
|
||||
-- $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. 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, particularly in relation to 'insert' and
|
||||
-- 'delete'.
|
||||
--
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- |
|
||||
-- A cursor into a non-empty list of workspaces.
|
||||
--
|
||||
-- We puncture the workspace list, producing a hole in the structure
|
||||
-- used to track the currently focused workspace. The two other lists
|
||||
-- that are produced are used to track those workspaces visible as
|
||||
-- Xinerama screens, and those workspaces not visible anywhere.
|
||||
|
||||
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
|
||||
} deriving (Show, Read, Eq)
|
||||
|
||||
-- | Visible workspaces, and their Xinerama screens.
|
||||
data Screen i l a sid sd = Screen { workspace :: !(Workspace i l a)
|
||||
, screen :: !sid
|
||||
, screenDetail :: !sd }
|
||||
deriving (Show, Read, Eq)
|
||||
|
||||
-- |
|
||||
-- A workspace is just a tag, a layout, and a stack.
|
||||
--
|
||||
data Workspace i l a = Workspace { tag :: !i, layout :: l, stack :: Maybe (Stack a) }
|
||||
deriving (Show, Read, Eq)
|
||||
|
||||
-- | A structure for window geometries
|
||||
data RationalRect = RationalRect Rational Rational Rational Rational
|
||||
deriving (Show, Read, Eq)
|
||||
|
||||
-- |
|
||||
-- A stack is a cursor onto a (possibly empty) 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
|
||||
-- flattening the cursor. The structure can be envisaged as:
|
||||
--
|
||||
-- > +-- master: < '7' >
|
||||
-- > up | [ '2' ]
|
||||
-- > +--------- [ '3' ]
|
||||
-- > focus: < '4' >
|
||||
-- > dn +----------- [ '8' ]
|
||||
--
|
||||
-- A 'Stack' can be viewed as a list with a hole punched in it to make
|
||||
-- the focused position. Under the zipper\/calculus view of such
|
||||
-- structures, it is the differentiation of a [a], and integrating it
|
||||
-- back has a natural implementation used in 'index'.
|
||||
--
|
||||
data Stack a = Stack { focus :: !a -- focused thing in this set
|
||||
, up :: [a] -- clowns to the left
|
||||
, down :: [a] } -- jokers to the right
|
||||
deriving (Show, Read, Eq)
|
||||
|
||||
|
||||
-- | this function indicates to catch that an error is expected
|
||||
abort :: String -> a
|
||||
abort x = error $ "xmonad: StackSet: " ++ x
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- $construction
|
||||
|
||||
-- | /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
|
||||
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
|
||||
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'.
|
||||
--
|
||||
-- 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
|
||||
-- current.
|
||||
|
||||
view :: (Eq s, Eq i) => i -> StackSet i l a s sd -> StackSet i l a s sd
|
||||
view i s
|
||||
| i == currentTag s = s -- current
|
||||
|
||||
| Just x <- L.find ((i==).tag.workspace) (visible s)
|
||||
-- if it is visible, it is just raised
|
||||
= s { current = x, visible = current s : L.deleteBy (equating screen) x (visible s) }
|
||||
|
||||
| Just x <- L.find ((i==).tag) (hidden s) -- must be hidden then
|
||||
-- if it was hidden, it is raised on the xine screen currently used
|
||||
= s { current = (current s) { workspace = x }
|
||||
, hidden = workspace (current s) : L.deleteBy (equating tag) x (hidden s) }
|
||||
|
||||
| otherwise = s -- not a member of the stackset
|
||||
|
||||
where equating f = \x y -> f x == f y
|
||||
|
||||
-- 'Catch'ing this might be hard. Relies on monotonically increasing
|
||||
-- workspace tags defined in 'new'
|
||||
--
|
||||
-- and now tags are not monotonic, what happens here?
|
||||
|
||||
-- |
|
||||
-- Set focus to the given workspace. If that workspace does not exist
|
||||
-- in the stackset, the original workspace is returned. If that workspace is
|
||||
-- 'hidden', then display that workspace on the current screen, and move the
|
||||
-- current workspace to 'hidden'. If that workspace is 'visible' on another
|
||||
-- screen, the workspaces of the current screen and the other screen are
|
||||
-- swapped.
|
||||
|
||||
greedyView :: (Eq s, Eq i) => i -> StackSet i l a s sd -> StackSet i l a s sd
|
||||
greedyView w ws
|
||||
| any wTag (hidden ws) = view w ws
|
||||
| (Just s) <- L.find (wTag . workspace) (visible ws)
|
||||
= ws { current = (current ws) { workspace = workspace s }
|
||||
, visible = s { workspace = workspace (current ws) }
|
||||
: L.filter (not . wTag . workspace) (visible ws) }
|
||||
| otherwise = ws
|
||||
where wTag = (w == ) . tag
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- $xinerama
|
||||
|
||||
-- | Find the tag of the workspace visible on Xinerama screen 'sc'.
|
||||
-- '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 ]
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- $stackOperations
|
||||
|
||||
-- |
|
||||
-- The 'with' function takes a default value, a function, and a
|
||||
-- StackSet. If the current stack is Nothing, 'with' returns the
|
||||
-- default value. Otherwise, it applies the function to the stack,
|
||||
-- returning the result. It is like 'maybe' for the focused workspace.
|
||||
--
|
||||
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.
|
||||
--
|
||||
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 }}}
|
||||
|
||||
-- |
|
||||
-- Apply a function to modify the current stack if it isn't empty, and we don't
|
||||
-- want to empty it.
|
||||
--
|
||||
modify' :: (Stack a -> Stack a) -> StackSet i l a s sd -> StackSet i l a s sd
|
||||
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.
|
||||
--
|
||||
peek :: StackSet i l a s sd -> Maybe a
|
||||
peek = with Nothing (return . focus)
|
||||
|
||||
-- |
|
||||
-- /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' :: Maybe (Stack a) -> [a]
|
||||
integrate' = maybe [] integrate
|
||||
|
||||
-- |
|
||||
-- /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'.
|
||||
--
|
||||
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
|
||||
f':ls' -> Just $ Stack f' ls' [] -- else up
|
||||
[] -> Nothing
|
||||
|
||||
-- |
|
||||
-- /O(s)/. Extract the stack on the current workspace, as a list.
|
||||
-- The order of the stack is determined by the master window -- it will be
|
||||
-- the head of the list. The implementation is given by the natural
|
||||
-- integration of a one-hole list cursor, back to a list.
|
||||
--
|
||||
index :: StackSet i l a s sd -> [a]
|
||||
index = with [] integrate
|
||||
|
||||
-- |
|
||||
-- /O(1), O(w) on the wrapping case/.
|
||||
--
|
||||
-- focusUp, focusDown. Move the window focus up or down the stack,
|
||||
-- wrapping if we reach the end. The wrapping should model a 'cycle'
|
||||
-- on the current stack. The 'master' window, and window order,
|
||||
-- are unaffected by movement of focus.
|
||||
--
|
||||
-- swapUp, swapDown, swap the neighbour in the stack ordering, wrapping
|
||||
-- if we reach the end. Again the wrapping model should 'cycle' on
|
||||
-- the current stack.
|
||||
--
|
||||
focusUp, focusDown, swapUp, swapDown :: StackSet i l a s sd -> StackSet i l a s sd
|
||||
focusUp = modify' focusUp'
|
||||
focusDown = modify' (reverseStack . focusUp' . reverseStack)
|
||||
|
||||
swapUp = modify' swapUp'
|
||||
swapDown = modify' (reverseStack . swapUp' . reverseStack)
|
||||
|
||||
focusUp', swapUp' :: 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)
|
||||
|
||||
swapUp' (Stack t (l:ls) rs) = Stack t ls (l:rs)
|
||||
swapUp' (Stack t [] rs) = Stack t (reverse rs) []
|
||||
|
||||
-- | reverse a stack: up becomes down and down becomes up.
|
||||
reverseStack :: Stack a -> Stack a
|
||||
reverseStack (Stack t ls rs) = Stack t rs ls
|
||||
|
||||
--
|
||||
-- | /O(1) on current window, O(n) in general/. Focus the window 'w',
|
||||
-- and set its workspace as current.
|
||||
--
|
||||
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 <- findTag w s
|
||||
return $ until ((Just w ==) . peek) focusUp (view n s)
|
||||
|
||||
-- | 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'.
|
||||
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
|
||||
allWindows :: Eq a => StackSet i l a s sd -> [a]
|
||||
allWindows = L.nub . concatMap (integrate' . stack) . workspaces
|
||||
|
||||
-- | 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'.
|
||||
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 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
|
||||
et (i:is) rn s | i `tagMember` s = et is rn s
|
||||
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'.
|
||||
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'.
|
||||
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'?
|
||||
member :: Eq a => a -> StackSet i l a s sd -> Bool
|
||||
member a s = isJust (findTag a s)
|
||||
|
||||
-- | /O(1) on current window, O(n) in general/.
|
||||
-- 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)
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- $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; the previously focused element is moved
|
||||
-- down.
|
||||
--
|
||||
-- If the element is already in the stackset, the original stackset is
|
||||
-- returned unmodified.
|
||||
--
|
||||
-- Semantics in Huet's paper is that insert doesn't move the cursor.
|
||||
-- However, we choose to insert above, and move the focus.
|
||||
--
|
||||
insertUp :: Eq a => a -> StackSet i l a s sd -> StackSet i l a s sd
|
||||
insertUp a s = if member a s then s else insert
|
||||
where insert = modify (Just $ Stack a [] []) (\(Stack t l r) -> Just $ Stack a l (t:r)) s
|
||||
|
||||
-- insertDown :: a -> StackSet i l a s sd -> StackSet i l a s sd
|
||||
-- insertDown a = modify (Stack a [] []) $ \(Stack t l r) -> Stack a (t:l) r
|
||||
-- Old semantics, from Huet.
|
||||
-- > w { down = a : down w }
|
||||
|
||||
-- |
|
||||
-- /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
|
||||
--
|
||||
-- * 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'
|
||||
--
|
||||
-- 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 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
|
||||
delete' w s = s { current = removeFromScreen (current s)
|
||||
, visible = map removeFromScreen (visible s)
|
||||
, hidden = map removeFromWorkspace (hidden s) }
|
||||
where removeFromWorkspace ws = ws { stack = stack ws >>= filter (/=w) }
|
||||
removeFromScreen scr = scr { workspace = removeFromWorkspace (workspace scr) }
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
-- | Given a window, and its preferred rectangle, set it as floating
|
||||
-- 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) }
|
||||
|
||||
-- | Clear the floating status of a window
|
||||
sink :: Ord a => a -> StackSet i l a s sd -> StackSet i l a s sd
|
||||
sink w s = s { floating = M.delete w (floating s) }
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- $settingMW
|
||||
|
||||
-- | /O(s)/. Set the master window to the focused window.
|
||||
-- The old master window is swapped in the tiling order with the focused window.
|
||||
-- Focus stays with the item moved.
|
||||
swapMaster :: StackSet i l a s sd -> StackSet i l a s sd
|
||||
swapMaster = modify' $ \c -> case c of
|
||||
Stack _ [] _ -> c -- already master.
|
||||
Stack t ls rs -> Stack t [] (xs ++ x : rs) where (x:xs) = reverse ls
|
||||
|
||||
-- natural! keep focus, move current to the top, move top to current.
|
||||
|
||||
-- | /O(s)/. Set focus to the master window.
|
||||
focusMaster :: StackSet i l a s sd -> StackSet i l a s sd
|
||||
focusMaster = modify' $ \c -> case c of
|
||||
Stack _ [] _ -> c
|
||||
Stack t ls rs -> Stack x [] (xs ++ t : rs) where (x:xs) = reverse ls
|
||||
|
||||
--
|
||||
-- ---------------------------------------------------------------------
|
||||
-- $composite
|
||||
|
||||
-- | /O(w)/. shift. Move the focused element of the current stack to stack
|
||||
-- 'n', leaving it as the focused element on that stack. The item is
|
||||
-- inserted above the currently focused element on that workspace.
|
||||
-- The actual focused workspace doesn't change. If there is no
|
||||
-- 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 = currentTag 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
|
||||
-- element on that stack. The item is inserted above the currently
|
||||
-- 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 = findTag w s
|
||||
|
||||
go = on n (insertUp w) . on (fromJust from) (delete' w) $ s
|
||||
on i f = view (currentTag s) . f . view i
|
||||
|
40
man/xmonad.1.in
Normal file
40
man/xmonad.1.in
Normal file
@@ -0,0 +1,40 @@
|
||||
./" 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 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
|
||||
\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. mod-{w,e,r} switch the focus between screens, while shift-mod-{w,e,r} move the current window to that screen. When \fBxmonad\fR 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.
|
||||
.PP
|
||||
.SS Flags
|
||||
\fBxmonad\fR has several flags which you may pass to the executable. These flags are:
|
||||
.TP
|
||||
\fB--recompile
|
||||
Recompiles your configuration in ~/.xmonad/xmonad.hs
|
||||
.TP
|
||||
\fB--version
|
||||
Display version of \fBxmonad\fR.
|
||||
.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 in ~/.xmonad/xmonad.hs, and then restarting with mod-q.
|
||||
.SH BUGS
|
||||
Probably. If you find any, please report them: http://code.google.com/p/xmonad/issues/list
|
277
man/xmonad.hs
Normal file
277
man/xmonad.hs
Normal file
@@ -0,0 +1,277 @@
|
||||
--
|
||||
-- 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 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"
|
||||
|
||||
-- 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 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.
|
||||
--
|
||||
myNumlockMask = mod2Mask
|
||||
|
||||
-- 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 = modMask}) = M.fromList $
|
||||
|
||||
-- launch a terminal
|
||||
[ ((modMask .|. shiftMask, xK_Return), spawn $ XMonad.terminal conf)
|
||||
|
||||
-- launch dmenu
|
||||
, ((modMask, xK_p ), spawn "exe=`dmenu_path | dmenu` && eval \"exec $exe\"")
|
||||
|
||||
-- launch gmrun
|
||||
, ((modMask .|. shiftMask, xK_p ), spawn "gmrun")
|
||||
|
||||
-- close focused window
|
||||
, ((modMask .|. shiftMask, xK_c ), kill)
|
||||
|
||||
-- Rotate through the available layout algorithms
|
||||
, ((modMask, xK_space ), sendMessage NextLayout)
|
||||
|
||||
-- Reset the layouts on the current workspace to default
|
||||
, ((modMask .|. shiftMask, xK_space ), setLayout $ XMonad.layoutHook conf)
|
||||
|
||||
-- Resize viewed windows to the correct size
|
||||
, ((modMask, xK_n ), refresh)
|
||||
|
||||
-- Move focus to the next window
|
||||
, ((modMask, xK_Tab ), windows W.focusDown)
|
||||
|
||||
-- Move focus to the next window
|
||||
, ((modMask, xK_j ), windows W.focusDown)
|
||||
|
||||
-- Move focus to the previous window
|
||||
, ((modMask, xK_k ), windows W.focusUp )
|
||||
|
||||
-- Move focus to the master window
|
||||
, ((modMask, xK_m ), windows W.focusMaster )
|
||||
|
||||
-- Swap the focused window and the master window
|
||||
, ((modMask, xK_Return), windows W.swapMaster)
|
||||
|
||||
-- Swap the focused window with the next window
|
||||
, ((modMask .|. shiftMask, xK_j ), windows W.swapDown )
|
||||
|
||||
-- Swap the focused window with the previous window
|
||||
, ((modMask .|. shiftMask, xK_k ), windows W.swapUp )
|
||||
|
||||
-- Shrink the master area
|
||||
, ((modMask, xK_h ), sendMessage Shrink)
|
||||
|
||||
-- Expand the master area
|
||||
, ((modMask, xK_l ), sendMessage Expand)
|
||||
|
||||
-- Push window back into tiling
|
||||
, ((modMask, xK_t ), withFocused $ windows . W.sink)
|
||||
|
||||
-- Increment the number of windows in the master area
|
||||
, ((modMask , xK_comma ), sendMessage (IncMasterN 1))
|
||||
|
||||
-- Deincrement the number of windows in the master area
|
||||
, ((modMask , xK_period), sendMessage (IncMasterN (-1)))
|
||||
|
||||
-- toggle the status bar gap
|
||||
-- TODO, update this binding with avoidStruts , ((modMask , xK_b ),
|
||||
|
||||
-- Quit xmonad
|
||||
, ((modMask .|. shiftMask, xK_q ), io (exitWith ExitSuccess))
|
||||
|
||||
-- Restart xmonad
|
||||
, ((modMask , xK_q ), restart "xmonad" True)
|
||||
]
|
||||
++
|
||||
|
||||
--
|
||||
-- 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)]]
|
||||
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Mouse bindings: default actions bound to mouse events
|
||||
--
|
||||
myMouseBindings (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))
|
||||
|
||||
-- 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)
|
||||
]
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- 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 ]
|
||||
|
||||
-- Whether focus follows the mouse pointer.
|
||||
myFocusFollowsMouse :: Bool
|
||||
myFocusFollowsMouse = True
|
||||
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Status bars and logging
|
||||
|
||||
-- Perform an arbitrary action on each internal state change or X event.
|
||||
-- See the 'DynamicLog' extension for examples.
|
||||
--
|
||||
-- To emulate dwm's status bar
|
||||
--
|
||||
-- > logHook = dynamicLogDzen
|
||||
--
|
||||
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 = defaultConfig {
|
||||
-- simple stuff
|
||||
terminal = myTerminal,
|
||||
focusFollowsMouse = myFocusFollowsMouse,
|
||||
borderWidth = myBorderWidth,
|
||||
modMask = myModMask,
|
||||
numlockMask = myNumlockMask,
|
||||
workspaces = myWorkspaces,
|
||||
normalBorderColor = myNormalBorderColor,
|
||||
focusedBorderColor = myFocusedBorderColor,
|
||||
|
||||
-- key bindings
|
||||
keys = myKeys,
|
||||
mouseBindings = myMouseBindings,
|
||||
|
||||
-- hooks, layouts
|
||||
layoutHook = myLayout,
|
||||
manageHook = myManageHook,
|
||||
logHook = myLogHook,
|
||||
startupHook = myStartupHook
|
||||
}
|
1269
tests/Properties.hs
1269
tests/Properties.hs
File diff suppressed because it is too large
Load Diff
10
tests/coverage.hs
Normal file
10
tests/coverage.hs
Normal file
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env runhaskell
|
||||
|
||||
import System.Cmd
|
||||
|
||||
-- generate appropriate .hpc files
|
||||
main = do
|
||||
system $ "rm -rf *.tix"
|
||||
system $ "dist/build/xmonad/xmonad --run-tests"
|
||||
system $ "hpc markup xmonad --exclude=Main --exclude=Properties --exclude=XMonad --exclude=Paths_xmonad"
|
||||
system $ "hpc report xmonad --exclude=Main --exclude=Properties --exclude=XMonad --exclude=Paths_xmonad"
|
@@ -8,9 +8,7 @@ main = do foo <- getContents
|
||||
putStrLn $ show loc
|
||||
-- uncomment the following to check for mistakes in isntcomment
|
||||
-- putStr $ unlines $ actual_loc
|
||||
when (loc > 500) $ fail "Too many lines of code!"
|
||||
|
||||
isntcomment "" = False
|
||||
isntcomment ('-':'-':_) = False
|
||||
isntcomment ('{':'-':_) = False -- pragmas
|
||||
isntcomment _ = True
|
||||
|
47
util/GenerateManpage.hs
Normal file
47
util/GenerateManpage.hs
Normal file
@@ -0,0 +1,47 @@
|
||||
--
|
||||
-- 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
|
||||
import Data.Char
|
||||
import Data.List
|
||||
|
||||
trim :: String -> String
|
||||
trim = reverse . dropWhile isSpace . reverse . dropWhile isSpace
|
||||
|
||||
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])
|
||||
|
||||
binding :: [String] -> (String, String)
|
||||
binding [ _, bindingLine, "", desc ] = (guessKeys bindingLine, desc)
|
||||
binding [ _, _, keyCombo, desc ] = (keyCombo, desc)
|
||||
|
||||
allBindings :: String -> [(String, String)]
|
||||
allBindings xs = map (binding . map trim) (xs =~ "(.*)--(.*)%!(.*)")
|
||||
|
||||
-- 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"
|
||||
|
||||
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 "./XMonad/Config.hs"
|
||||
let sed = unlines . replace "___KEYBINDINGS___" troffBindings . lines
|
||||
readFile "./man/xmonad.1.in" >>= return . sed >>= writeFile "./man/xmonad.1"
|
83
xmonad.cabal
83
xmonad.cabal
@@ -1,18 +1,77 @@
|
||||
name: xmonad
|
||||
version: 0.1
|
||||
description: A lightweight X11 window manager.
|
||||
synopsis: A lightweight X11 window manager.
|
||||
version: 0.8
|
||||
homepage: http://xmonad.org
|
||||
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.
|
||||
category: System
|
||||
license: BSD3
|
||||
license-file: LICENSE
|
||||
author: Spencer Janssen
|
||||
maintainer: sjanssen@cse.unl.edu
|
||||
build-depends: base>=1.0, X11>=1.1, X11-extras>=0.0, mtl>=1.0, unix>=1.0
|
||||
extra-source-files: README TODO tests/loc.hs tests/Properties.hs
|
||||
maintainer: xmonad@haskell.org
|
||||
extra-source-files: README TODO CONFIG STYLE tests/loc.hs tests/Properties.hs
|
||||
man/xmonad.1.in man/xmonad.1 man/xmonad.html man/xmonad.hs
|
||||
util/GenerateManpage.hs
|
||||
cabal-version: >= 1.2
|
||||
build-type: Simple
|
||||
|
||||
executable: xmonad
|
||||
main-is: Main.hs
|
||||
other-modules: Config Operations StackSet XMonad
|
||||
ghc-options: -funbox-strict-fields -O2 -Wall -optl-Wl,-s
|
||||
ghc-prof-options: -prof -auto-all
|
||||
extensions: GeneralizedNewtypeDeriving
|
||||
flag small_base
|
||||
description: Choose the new smaller, split-up base package.
|
||||
|
||||
flag testing
|
||||
description: Testing mode, only build minimal components
|
||||
default: False
|
||||
|
||||
library
|
||||
exposed-modules: XMonad
|
||||
XMonad.Main
|
||||
XMonad.Core
|
||||
XMonad.Config
|
||||
XMonad.Layout
|
||||
XMonad.ManageHook
|
||||
XMonad.Operations
|
||||
XMonad.StackSet
|
||||
|
||||
if flag(small_base)
|
||||
build-depends: base >= 3, containers, directory, process
|
||||
else
|
||||
build-depends: base < 3
|
||||
build-depends: X11>=1.4.1, mtl, unix
|
||||
|
||||
ghc-options: -funbox-strict-fields -Wall
|
||||
ghc-prof-options: -prof -auto-all
|
||||
extensions: CPP
|
||||
|
||||
if flag(testing)
|
||||
buildable: False
|
||||
|
||||
executable xmonad
|
||||
main-is: Main.hs
|
||||
other-modules: XMonad
|
||||
XMonad.Main
|
||||
XMonad.Core
|
||||
XMonad.Config
|
||||
XMonad.Layout
|
||||
XMonad.ManageHook
|
||||
XMonad.Operations
|
||||
XMonad.StackSet
|
||||
|
||||
ghc-options: -funbox-strict-fields -Wall
|
||||
ghc-prof-options: -prof -auto-all
|
||||
extensions: CPP
|
||||
|
||||
if flag(testing)
|
||||
cpp-options: -DTESTING
|
||||
hs-source-dirs: . tests/
|
||||
build-depends: QuickCheck < 2
|
||||
ghc-options: -Werror
|
||||
if flag(testing) && flag(small_base)
|
||||
build-depends: random
|
||||
|
Reference in New Issue
Block a user