340 Commits
v0.1 ... v0.3

Author SHA1 Message Date
Spencer Janssen
dede0a2ce9 README: spelling 2007-09-04 19:30:42 +00:00
Spencer Janssen
74441202a0 Bump version to 0.3 2007-09-04 19:28:41 +00:00
Spencer Janssen
bda704297c Add a link to XMonadContrib 2007-09-04 19:27:59 +00:00
Spencer Janssen
2819adfef4 Point to X11-extras-0.3 in the README 2007-09-04 19:26:43 +00:00
Spencer Janssen
8146dd46dd Depend on X11-extras >= 0.3 2007-09-03 21:52:49 +00:00
Spencer Janssen
92a1335cff Add location of X11-extras to README 2007-08-24 16:09:35 +00:00
Spencer Janssen
49cebc6130 Add docstrings for mouse controls 2007-08-24 04:59:39 +00:00
Don Stewart
314b5ee6bd todos 2007-08-22 02:28:15 +00:00
Don Stewart
aaba52043d comment only: example of 2 monitor gaps 2007-08-21 03:25:38 +00:00
David Roundy
6ec342ff75 don't refresh when setting focus to already focussed window. 2007-08-20 15:02:25 +00:00
David Roundy
8a8438a5c2 clear out motion events when processing one motion event.
This is important if the hook is slow (e.g. try adding "float w"
to the window-dragging hook), as it allows xmonad to keep up with
the motion of the mouse.
2007-08-20 00:23:51 +00:00
David Roundy
2716b1ada6 remove unneeded do. 2007-08-13 14:37:21 +00:00
David Roundy
34d8d51a77 make splitHorizontallyBy accept any RealFrac. 2007-08-13 14:37:07 +00:00
Spencer Janssen
ca0d87664b Fix new bug in screen switching 2007-08-16 21:56:29 +00:00
Don Stewart
6a273c2afa -Wall police 2007-08-16 03:31:32 +00:00
Spencer Janssen
6dcd66f16e Comment only 2007-08-15 22:40:31 +00:00
David Roundy
df4c18a181 simplify code in StackSet. 2007-08-14 01:04:22 +00:00
David Roundy
ec0995a3a6 change workspaces to [WorkspaceId] 2007-08-14 00:37:22 +00:00
Spencer Janssen
919774dff8 Operations.windows: minor refactor 2007-08-15 03:15:21 +00:00
Spencer Janssen
447d662d1d Cleanup 2007-08-10 21:39:40 +00:00
David Roundy
fae3cbebb1 move event loop out of mouseDrag. 2007-08-07 20:16:16 +00:00
David Roundy
4c40661047 only display any given window once.
This change goes along with the sticky window work.  It makes xmonad
display each window once and only once, with preference given to the
focussed screen.  It has no effect when there are no duplicate windows,
except to make things less efficient.  We could do better using Data.Set
(or Data.Map) to store the set of windows that are visible.
2007-07-24 14:13:10 +00:00
Spencer Janssen
2f3ccd7ab6 Add greedyView, make it the default action for mod-wer 2007-08-15 02:55:04 +00:00
Spencer Janssen
8bb313ea53 Remove 'Eq' constraint from StackSet.index 2007-08-07 14:43:46 +00:00
Don Stewart
2e7aa7d055 trailing whitespace only 2007-08-05 07:27:16 +00:00
Andrea Rossato
6875437c44 added workspaces to hs-boot (needed by XMonadContrib.Commands and possibly other modules) 2007-07-28 13:17:56 +00:00
Karsten Schoelzel
808894c217 QuickCheck filter preserves order 2007-07-28 18:45:34 +00:00
Karsten Schoelzel
84c6432c82 Bugfix: reordering when filtering out the last window on a workspace
Say you have three windows A B C* on a workspace with * marking the focus.
If you close C or move it to another workspace, the resulting order will be B* A,
thus reordering the other windows, defying the comment of filter.
2007-07-28 13:25:07 +00:00
Spencer Janssen
bf4388e3aa shift: use guards instead of if 2007-07-24 15:23:40 +00:00
Spencer Janssen
cc3527a975 Remove unnecessary Integral constraints 2007-07-24 15:22:57 +00:00
David Roundy
9a2f57552e make delete work when window is in multiple workspaces. 2007-07-24 14:20:45 +00:00
Spencer Janssen
189c2d31f9 Remove redundant 'n >= 0' check from shift. (from David Roundy's 'simplify shift, removing unneeded check.' patch) 2007-07-24 14:59:27 +00:00
Michael G. Sloan
5068bd27f0 Cleanup of shift code 2007-07-22 20:53:37 +00:00
Don Stewart
fc70bed46b use $HOME in examples 2007-07-19 06:33:48 +00:00
Peter De Wachter
d0482810b3 Tweak dmenu binding
Add an "eval", so quotes and environment variables get evaluated
according to sh rules.
2007-07-17 19:07:22 +00:00
Jason Creighton
c146940154 restore focus to currently focused window after "float" (closes #32) 2007-07-10 04:26:31 +00:00
Spencer Janssen
bfd638d818 Operations.screenWorkspace: return Nothing when the screen does not exist 2007-07-07 22:38:42 +00:00
Spencer Janssen
a48ec57cd9 Operations.rescreen: screen indexes start at zero 2007-07-07 22:33:34 +00:00
Spencer Janssen
54c024583f Note and workaround bugs in Operations.float 2007-07-05 19:52:13 +00:00
Spencer Janssen
2efa369dfc refresh after starting 2007-06-30 05:03:46 +00:00
Spencer Janssen
e74e8050d0 UPGRADE X11-Extras! Manage iconified windows 2007-06-30 02:10:26 +00:00
Spencer Janssen
ab830ec227 Move screen details into StackSet 2007-06-29 21:39:17 +00:00
Jason Creighton
bb12b08239 Change a window's workspace when dragging across screens (closes #30) 2007-06-28 02:50:23 +00:00
David Roundy
61d7524bcd support self-modifying layouts. 2007-06-23 20:14:47 +00:00
Don Stewart
d0566a28be comment for (dubious?) integrate' 2007-06-26 05:24:31 +00:00
David Roundy
6f9a060118 broadcast unidentified events.
This change is independent of the doLayout change I just sent in, but fixes
the problem that change introduces in Decoration, by ensuring that all
Layouts get redraw events.  I think this is the correct behavior.
2007-06-23 21:41:25 +00:00
Don Stewart
dbdf0fd5e4 add 2 properties to state where focus goes on delete of focused window 2007-06-26 04:09:07 +00:00
Don Stewart
ce28fc1eb2 fix empty case in 'filter', and note differences in semantics wrt. focus to 'delete' 2007-06-26 03:57:41 +00:00
Don Stewart
977f8328fc clean up 'StackSet.filter' for style 2007-06-26 03:32:02 +00:00
Don Stewart
776886660b minor tweaks, ideas from joachim.fasting@ 2007-06-21 03:36:13 +00:00
Don Stewart
c6da7fc14a only perform mouse events on managed windows. closes #28 2007-06-21 01:17:00 +00:00
Spencer Janssen
e99d7431c8 Update Layout documentation 2007-06-20 15:08:58 +00:00
Spencer Janssen
5dea6605fc -Wall police 2007-06-20 15:08:23 +00:00
Spencer Janssen
6091bfd0fe Stack windows in the order they are returned by doLayout 2007-06-20 15:04:19 +00:00
Don Stewart
d411736ded remove out of date `(Included with GHC)' text in README 2007-06-20 06:04:30 +00:00
David Roundy
e517aedfa1 make Layouts able to layout whatever they like. 2007-06-19 15:08:16 +00:00
Peter De Wachter
b84a9b875b float fixed size windows 2007-06-18 21:46:57 +00:00
Spencer Janssen
33bb745880 Remove all references to 'exec' 2007-06-18 20:15:32 +00:00
Don Stewart
be08dd80ec -Wall police, and turn on -fno-warn-orphans 2007-06-17 05:23:22 +00:00
David Roundy
dbd58faffe make workspace tag not need to be a Num.
This change also removes the barely used 'size' field, and replaces
it with a tagMember predicate.  The idea is to move towards the ability
to make the workspace tag be a String, which by default might be "1".."9",
but could also be customized to be something meaningful to the user.
2007-06-14 14:07:09 +00:00
Spencer Janssen
a2c5aa3612 Fix float stacking 2007-06-14 21:34:12 +00:00
Spencer Janssen
fa2b56c14e Remove 'temporary work around' in 'windows' 2007-06-14 21:14:50 +00:00
Andrea Rossato
7d1a23698f haddock tuning for StackSet.hs
with this patch the documentation of StackSet.hs will have a nice TOC
2007-06-14 06:45:11 +00:00
Jason Creighton
8169445cbd move initColor to Operations and only store the Pixel value of colors
Moving initColor to Operations allows it to be used by extensions.

The Pixel component of the color is the only thing we need, so it's simpler
just to deal with that.
2007-06-13 23:45:01 +00:00
Andrea Rossato
753b42ae65 haddick fine tuning 2007-06-13 18:59:02 +00:00
Spencer Janssen
d1e4699944 Indentation 2007-06-13 04:30:18 +00:00
Jason Creighton
62344287da prevent keyboard focus from getting lost in some cases 2007-06-13 02:53:50 +00:00
David Roundy
8cdcceab48 resolve conflict in Operations. 2007-06-12 17:06:25 +00:00
David Roundy
194a934c37 add catchX to catch exceptions. 2007-06-12 15:42:53 +00:00
David Roundy
5f8202e79e make focus, up and down complete functions.
This is a rerun of my change to make (Stack a) never be empty.  Gives
us more type-safety.
2007-06-12 15:05:55 +00:00
David Roundy
4ffee115e1 add differentiate function to StackSet to go [a] -> Stack a. 2007-06-12 13:28:53 +00:00
Spencer Janssen
00e1038d71 Make 'view' a total function 2007-06-12 14:32:48 +00:00
Don Stewart
7158a58792 fmt 2007-06-12 13:49:38 +00:00
Stefan O'Rear
c0a9636f3b -Wall police 2007-06-12 06:05:46 +00:00
Stefan O'Rear
ff6b48382c Use a more descriptive name for the layout reversal message 2007-06-12 05:58:59 +00:00
Stefan O'Rear
bb9e46df6c Use broadcastMessage in windows and switchLayout, should improve Xinerama for tabbed and make xmonad robust in the presence of state-altering unlayout hooks 2007-06-12 05:55:10 +00:00
Stefan O'Rear
f68a954fc3 Add a broadcastMessage function, which sends to all visible workspaces without refreshing. (+6 loc) 2007-06-12 05:53:39 +00:00
Spencer Janssen
d21e61a315 TODO for scan 2007-06-11 21:42:17 +00:00
Spencer Janssen
4b9bacb1f9 Set withdrawn state after calling windows 2007-06-11 21:33:27 +00:00
Spencer Janssen
9992737e84 Remove obsolete 'layout' function 2007-06-11 20:36:22 +00:00
Spencer Janssen
615a4a1af1 -Wall police 2007-06-11 20:20:07 +00:00
Spencer Janssen
33447129dd Comment only 2007-06-11 19:58:27 +00:00
Spencer Janssen
14971546bb Hide windows that are not supposed to be visible 2007-06-11 19:18:09 +00:00
Spencer Janssen
6f7030f875 -Wall police 2007-06-11 18:57:08 +00:00
Spencer Janssen
4f5c307d6f API CHANGE: Give doLayout a Stack rather than a flattened list 2007-06-11 18:26:29 +00:00
Spencer Janssen
e7b37ca646 -Wall police 2007-06-11 18:01:23 +00:00
Spencer Janssen
5f3e91676a Add StackSet.filter 2007-06-11 16:51:54 +00:00
Spencer Janssen
b3bdbf3588 Use catchIO in 'restart' 2007-06-11 16:11:52 +00:00
Spencer Janssen
00b930b09e Rename safeIO to catchIO 2007-06-11 16:06:08 +00:00
David Roundy
0d17ca9436 add safeIO which catches and logs exceptions. 2007-06-11 15:36:50 +00:00
Spencer Janssen
b668133c08 Ensure windows get at least 1 pixel for width/height 2007-06-11 06:19:30 +00:00
Spencer Janssen
330179ea20 Restrict the master/slave ratio to [0, 1] 2007-06-11 05:32:30 +00:00
Jason Creighton
854d3239cc comment only 2007-06-11 02:02:49 +00:00
David Roundy
c8b6388fb8 a few modifications to event-sending to make Tabbed layout work. 2007-06-10 15:38:36 +00:00
David Roundy
b97e8836e2 send message when "windows" is called. 2007-06-10 01:35:31 +00:00
David Roundy
ab6f210300 implement Spencer's decoration suggestion. 2007-06-10 01:22:37 +00:00
Andrea Rossato
e1885f27e1 haddock compatibility 2007-06-10 12:37:46 +00:00
Don Stewart
6365601c77 Move state logging into Config.hs, via logHook :: X () 2007-06-10 06:19:32 +00:00
Don Stewart
3bfa0930af polish serialisation code (-7 lines) 2007-06-10 04:55:51 +00:00
David Roundy
0d4a7d098f cut incorrect comment. 2007-06-09 17:34:47 +00:00
David Roundy
16c8622fbf doLayout cleanup and commented exception-handling. 2007-06-09 14:50:36 +00:00
Stefan O'Rear
1c3931a0d6 Give refresh sole responsibility for establishing window properties (-3 loc) 2007-06-09 18:58:35 +00:00
Stefan O'Rear
7706f38dc8 Give refresh sole responsibility for establishing window properties (-3 loc) 2007-06-09 18:58:35 +00:00
Don Stewart
0ada17c34a HEADS UP: (logging format change). use a custom pretty printer, for an easier format to parse, than 'show' produces 2007-06-09 13:17:16 +00:00
Don Stewart
a21c4d02f1 Add notes on using X11-extras from darcs 2007-06-09 02:50:45 +00:00
Spencer Janssen
cf9828cbcd Fix unmap handling
According to the ICCCM, clients should send a synthetic unmap event when they
initiate an unmap.  The old code waited for these synthetic unmaps to unmanage
windows.  However, certain 'obsolete' clients do not send synthetic unmaps
(notably xpdf's find dialog).  These windows entered a zombified state: xmonad
does not manage them, yet they are still mapped and raised on screen.

The new algorithm (derived from wmii):
 - track windows that are mapped on screen
 - track the number of expected unmap events for each window, increment every
   time 'hide' is called on a window that is not mapped.
 - decrement the expected unmap counter on each unmap event
 - treat an unmap event as genuine (ie. unmap the window) when:
    - the event is synthetic (per ICCCM)
    - OR there are no expected unmap events for this window
2007-06-06 21:40:06 +00:00
Don Stewart
b257658781 dead import 2007-06-06 02:52:26 +00:00
Jason Creighton
5da458c755 move extraModifiers/cleanMask to Operations.hs
so XMonadContrib can use them
2007-06-06 00:50:56 +00:00
Don Stewart
d7d8c586cb temporary workaround for delete/focus issue in fullscreen mode 2007-06-06 02:49:38 +00:00
Don Stewart
86ea7f7bc0 whitespace 2007-06-06 02:48:57 +00:00
Don Stewart
a2a0670397 simplify code 2007-06-06 00:46:03 +00:00
Don Stewart
02a9e4c589 mention why StackSet needs -fglasgow-exts (for deriving Typeable) 2007-06-05 09:26:59 +00:00
Don Stewart
d4676d93e8 comments only 2007-06-05 09:18:03 +00:00
Don Stewart
0010a23c18 clean size hint code 2007-06-05 09:13:54 +00:00
Don Stewart
7ae7029b50 Enable logging of state changes to stdout 2007-06-05 08:37:35 +00:00
Don Stewart
21e09361a6 remove accidental logging of events 2007-06-05 08:14:52 +00:00
Don Stewart
8f200d408f Fix lost eventNotifyMask bug
When resuming, we were (implicitly) relying on 'scan' to find all
windows, and reset their event masks and WM_STATE. When we moved to
Iconfified hidden workspaces, 'scan' would only find and reset states on 
the current workspace.

The result being that hidden workspace windows no longer received
enterNotify events.

Fix this by traversing the StackSet serialised during a restart, setting
the intial X states for each window, whether visible or hidden.
2007-06-05 04:30:40 +00:00
Don Stewart
d3632eb8fe whitespace only 2007-06-05 00:07:23 +00:00
Spencer Janssen
b22ebceb80 Comment only 2007-06-04 21:19:56 +00:00
Spencer Janssen
ba14f07093 Wibble. 2007-06-04 21:18:16 +00:00
Spencer Janssen
2a6d6d4ed7 -Wall police 2007-06-04 21:15:31 +00:00
Peter De Wachter
68b2859aa2 apply size hints to floating windows 2007-06-04 19:29:43 +00:00
Peter De Wachter
a10f11f623 size hints infrastructure 2007-06-04 19:27:53 +00:00
Spencer Janssen
6a2f9d739d Delete stale comment 2007-06-04 20:46:17 +00:00
Spencer Janssen
4d74099851 Comment only 2007-06-04 20:36:59 +00:00
Spencer Janssen
26b388189e Use 'windows' in 'focus' 2007-06-04 20:16:39 +00:00
l.mai
1c609288dd realign guard 2007-06-04 18:20:45 +00:00
Spencer Janssen
68a63688ad swapUp/Down are also mirrored 2007-06-04 18:35:35 +00:00
Spencer Janssen
6b8e9570c2 Remove redundant cases in swapUp/Down 2007-06-04 18:33:44 +00:00
Spencer Janssen
85e57cfa47 focusUp/Down are the same, in reversed order 2007-06-04 18:31:43 +00:00
Spencer Janssen
89a31eaf52 Simplify focusUp/Down 2007-06-04 18:22:28 +00:00
Spencer Janssen
fc70307325 Integral implies Eq 2007-06-04 18:07:45 +00:00
Spencer Janssen
f9110c999b Comment typo. 2007-06-04 18:05:54 +00:00
Spencer Janssen
795e96d353 Dump state at launch (commented for now) 2007-06-04 16:24:50 +00:00
Spencer Janssen
fec58e8a09 Small clean up 2007-06-04 06:44:18 +00:00
Spencer Janssen
d01623db88 Merge windows and refresh 2007-06-04 06:36:57 +00:00
Spencer Janssen
1912feee50 Use the new integrate function 2007-06-04 06:26:53 +00:00
Spencer Janssen
cad36baa19 Add integrate 2007-06-04 06:25:01 +00:00
Spencer Janssen
77da6e4c72 Delete stale comments 2007-06-04 06:17:19 +00:00
Spencer Janssen
71349314c5 Remove inaccurate warnings about 'hide' 2007-06-04 06:06:11 +00:00
Spencer Janssen
f2eb5ac6bb base >= 2.0 means we can use forM_ 2007-06-04 05:09:14 +00:00
Stefan O'Rear
e4e1724842 Remove no-longer-needed 'dimensions' state (-5 loc) 2007-06-04 04:47:15 +00:00
Stefan O'Rear
cd73165c63 Set WM_STATE, iconify invisible windows (+9 loc)
Note that this breaks compatibility with certain programs described as
"obsolete" in the ICCCM (1994).  See the command above the UnmapEvent handler
for details.
2007-06-04 04:23:43 +00:00
Don Stewart
225a2e89a3 clean up Main.hs slightly 2007-06-04 03:56:37 +00:00
Don Stewart
9b429f4f41 whitespace 2007-06-04 01:55:32 +00:00
Don Stewart
ca896686a1 -Wall 2007-06-04 01:46:30 +00:00
Stefan O'Rear
f06d042b56 do not cache atom values within Xmonad, instead let Xlib worry about caching (a documented feature) 2007-06-04 01:39:38 +00:00
Spencer Janssen
29a32bc146 Honor configure requests from unmanaged windows 2007-06-03 23:47:30 +00:00
Spencer Janssen
27b7cccd3a -Wall police 2007-06-03 21:20:55 +00:00
Stefan O'Rear
b20a9cff7f Correctly handle resize requests (-12 +22)
Xmonad now implements resize requests in a consistent manner.

* If the window is FLOATING, we implement the program's request, and
  correctly update the StackSet; so it will keep the new size.  This
  should work correctly even for non-current windows.

* Otherwise, we ignore the request.  As per ICCCM, we send a fake
  ConfigureNotify containing the new (unchanged) geometry.  This is
  perfectly ICCCM compliant, and if it breaks your client, it's your
  own fault.

This patch requires setConfigureEvent, which is added to X11-extras by
a patch approximately contemporaneous with this one.
2007-06-03 20:31:53 +00:00
Don Stewart
8f3258a348 comments only 2007-06-03 07:15:56 +00:00
Don Stewart
0226ba3441 Polish core layout code. Lifts limitation on nmaster > 1. it may be 0 now 2007-06-03 06:43:06 +00:00
Don Stewart
84f22f7102 heads up: polish config.hs. moves tiling-local values into lexical scope. removes wide' as an explicit mode (it's mirror tall') 2007-06-03 05:47:40 +00:00
Don Stewart
7e9fbf5883 set build-depends base>=2.0 so people can't miss the missing Read instance issue 2007-06-03 03:23:19 +00:00
Chris Mears
7ae4bc8f39 Fix out-of-date comment in Config.hs. 2007-06-02 11:43:12 +00:00
Jason Creighton
a6098f6010 only grab button{1,2,3} for click-to-focus (scrollwheel shouldn't focus) 2007-06-02 05:26:05 +00:00
Jason Creighton
72a50ead89 make mouse bindings configurable 2007-06-02 04:06:47 +00:00
Don Stewart
0be589ae8c commented out implementation state logging. if someone has a client, we can enable this 2007-06-01 08:56:26 +00:00
Jason Creighton
b46a449baf ignore numlock/capslock on mouse bindings 2007-06-01 01:51:37 +00:00
Don Stewart
9669c26fdc now we handle transients properly, and restack windows, refresh from focus is ok 2007-06-01 02:23:29 +00:00
glasser
ddffd109ce Rename withWorkspace to withWindowSet. 2007-06-01 00:13:25 +00:00
Spencer Janssen
68e6643356 Revert accidental change to border color 2007-05-31 14:55:09 +00:00
Don Stewart
7246a9e2d2 comments on why fullscreen tiling doesn't work with `implicit' floating 2007-05-31 09:05:37 +00:00
Don Stewart
777cf28bdf clean up mouse code a bit 2007-05-31 08:53:08 +00:00
Jason Creighton
3cb64d7461 first shot at a floating layer
This is a first attempting at a floating layer:

mod-button1: move window
mod-button2: swapMaster
mod-button3: resize window

mod-t: make floating window tiled again

Moving or resizing a window automatically makes it floating.

Known issues:

Hard to manage stacking order. You can promote a window to move it to the top,
(which you can do with mod-button2) but it should be easier than that.

Moving a window by dragging it to a different Xinerama screen does not move it
to that workspace.

Code is ugly.
2007-05-31 04:47:33 +00:00
Jason Creighton
1d764ecf2e remove LOC cap (but still print count after tests) 2007-05-31 04:34:17 +00:00
Spencer Janssen
5594c71e66 Remove 0.2 TODOs 2007-05-31 00:58:55 +00:00
Spencer Janssen
a3479aa9f5 Bump version to 0.2 2007-05-30 20:25:29 +00:00
Spencer Janssen
b069f84add Minor style change. 2007-05-30 18:10:06 +00:00
nickburlett
fc08bd48b4 log errors on executeFile in restart
I found it difficult to track down a problem in the restart code where xmonad was silently not restarting. This will log the error to stderr, which should show up in .xsession-errors
2007-05-30 17:10:24 +00:00
Spencer Janssen
e83ae8ba62 Depend on X11-extras >= 0.2 2007-05-30 17:36:07 +00:00
Spencer Janssen
67ae8fcd7c Require X11 >= 1.2.1 2007-05-30 17:29:09 +00:00
Don Stewart
f6b14b7123 point out restart is used to propagate changes 2007-05-30 02:10:05 +00:00
Spencer Janssen
fbfbb14658 Really change restart keybinding this time 2007-05-30 06:14:54 +00:00
Spencer Janssen
79a9c58f92 HEADS UP: Change restart keybinding to mod-q 2007-05-30 06:10:44 +00:00
Spencer Janssen
bc2e6b2112 HEADS UP: Change restart keybinding to mod-q 2007-05-30 06:10:44 +00:00
Spencer Janssen
89417a6e25 Fix 'refresh' doc string 2007-05-29 02:04:46 +00:00
Jason Creighton
2be4f5f216 Give link to bugtracker in "BUGS" section of manpage 2007-05-29 01:58:51 +00:00
Don Stewart
c023e9a681 make 'tall' layout the default on startup. more useful for new users 2007-05-29 01:46:11 +00:00
Don Stewart
da63d4a4b7 notes about which dependant packages already come with ghc 2007-05-29 00:57:48 +00:00
Don Stewart
46b04b3fa5 forgot to set focus in 'focus'. this restores the old behaviour 2007-05-28 13:45:47 +00:00
Don Stewart
0b8c9c407e don't refresh on focus events
leads to a race. this will affect how gaps are redrawn when moving to a
new screen with the mouse.
2007-05-28 13:31:27 +00:00
Don Stewart
5818e5a7fc ensure !! won't go out of bounds in modifyGap 2007-05-28 07:06:09 +00:00
Don Stewart
eda3ab2849 mention .xinitrc 2007-05-28 06:12:52 +00:00
Don Stewart
c2318fa67d update readme 2007-05-28 05:14:44 +00:00
Spencer Janssen
f85dac53e4 Add the HTML manpage 2007-05-28 06:31:22 +00:00
Spencer Janssen
39fd73a7f7 Fix manpage generator 2007-05-28 06:26:58 +00:00
Don Stewart
abdbc23551 apply gap to each screen 2007-05-28 04:47:22 +00:00
Don Stewart
cf52e66ec1 move gapcalc.c 2007-05-28 04:04:02 +00:00
Spencer Janssen
f6bac98678 Remove gapcalc.c from the sdist, add generated manpage 2007-05-28 04:06:55 +00:00
Don Stewart
94b64e7035 help man script 2007-05-28 03:38:46 +00:00
Don Stewart
58f180aefb done with gap 2007-05-28 03:35:25 +00:00
Spencer Janssen
257aa4776f Document mod-n 2007-05-28 03:35:36 +00:00
Don Stewart
0cca848c54 be sure to reset the gap list on rescreen 2007-05-28 03:18:35 +00:00
Don Stewart
c613073cb6 support per-screen gap settings. you can have different gaps on individual screens now 2007-05-28 03:15:01 +00:00
Don Stewart
1d2c5ca35a Use (Int,Int,Int,Int) for arbitrary gaps on any side of the screen 2007-05-28 02:51:35 +00:00
Spencer Janssen
b0cfe9b6ab Update extra-source-files 2007-05-27 21:06:57 +00:00
Spencer Janssen
19256758a2 Note the manpage move in xmonad.cabal 2007-05-27 20:58:57 +00:00
Don Stewart
c7655df3cb refactor only 2007-05-27 15:43:53 +00:00
Don Stewart
25616c3b9f comments on alternative gap movement policies 2007-05-27 15:32:11 +00:00
Don Stewart
d16d751207 when focus is called from an event, better refresh too, since it might have switched workspaces (so gap follows screen focus) 2007-05-27 15:19:42 +00:00
Don Stewart
2231879268 only set gap on current physical screen 2007-05-27 15:08:05 +00:00
Don Stewart
5fdbe0711d gap 2007-05-27 15:00:53 +00:00
Don Stewart
7fb1dd96de todo 2007-05-27 14:32:16 +00:00
Don Stewart
77f59efcc6 revert raiseWindow in focus. Leads to some funny races with pop ups. Harmless with status bar support now 2007-05-27 13:45:05 +00:00
Don Stewart
bb4bd97c87 mod-b, toggle on or off the status bar gap 2007-05-27 12:59:28 +00:00
Don Stewart
a64c9f1856 Add new config value, defaultMenuGap, for specifying a gap for a status bar
By default, it is 0 (set in Config.hs), but set this to a pixel count to
get a permanent gap at the top of the screen. You can then at startup
launch dzen, and it will run in this gap, and not be obscured by other
windows.

Perfect for a persistant status bar.
2007-05-27 12:27:02 +00:00
Don Stewart
e5e4b46ffa raiseWindow when settings focus. The focused window should always be raised, I think 2007-05-27 09:41:05 +00:00
Don Stewart
7be0a2103d Be a bit more conservative with -O flags, and GC. Hope to avoid runtime GC bug 2007-05-27 07:44:38 +00:00
Don Stewart
ef5326ccff dead code 2007-05-27 07:26:52 +00:00
Don Stewart
ee2c2c311b refactor code smell in Operation.hs 2007-05-27 07:21:35 +00:00
Don Stewart
fdc6af5faa clean Main.hs slightly 2007-05-27 07:21:06 +00:00
Don Stewart
325329e5d7 todo 2007-05-27 06:37:40 +00:00
Jason Creighton
8cfe050be7 Generate keybindings section in manpage from Config.hs 2007-05-27 06:29:14 +00:00
Don Stewart
d3f56af172 specify --user, spotted by fasta 2007-05-27 01:40:32 +00:00
Don Stewart
60f4f4e5e4 HEADS UP: change key binding for swapLeft/Right and IncMaster
The use of arrow keys for swapLeft/Right clash with firefox's back
button. Use the more intuitive mod-shift-jk for this. (It's a movement
operation, after all).

This clashes with IncMaster, so we use mod+comma and mod+period for
these (i.e. the keys mod < and mod > , to move windows to and from the
master area).

While we're here, replace the use of the terms 'left' and 'right' for
navigation, in comments and identifiers, with 'up' and 'down' instead.
Hence mod-j == focusDown. Far more intuitive for people (dons) who live
in fullscreen mode and have vim movement wired into their central
nervous system.

Principle of least VI surprise: movement down or up means using j and k.
2007-05-26 11:14:53 +00:00
Don Stewart
6346e11ff6 type sig for abort. 2007-05-26 06:14:50 +00:00
Neil Mitchell
a5aa4b1686 Add an abort function, called for deliberate and intentional errors 2007-05-23 23:32:12 +00:00
Neil Mitchell
6f9998ad27 Delete the Catch wrapper, no longer required by the latest version of Catch 2007-05-23 23:29:41 +00:00
Don Stewart
e64e434750 start on TODO list needed for 0.2 to be tagged 2007-05-26 06:07:20 +00:00
glasser
52608185b4 Add a test that the size field of StackSet is correct to QuickCheck invariant. 2007-05-25 16:31:59 +00:00
Spencer Janssen
8d6b914409 Formatting only 2007-05-25 21:44:14 +00:00
Rob
7afc18b0e1 Quickcheck property to check that delete / focus behaviour
See patch "Deleting a window should not affect focus". Checks this property.
2007-05-25 03:54:32 +00:00
Rob
a36bd31973 Fix bug in noDuplicate invariant
ws used by noDuplicates is actually a list of list of elements which 
will pretty rarely raise any flags even if the StackSet actually does
contain duplicates. This patch concatenates ws to ensure the quickcheck
property tests accurately.
2007-05-25 06:08:42 +00:00
Spencer Janssen
d502891ef0 Add a note about already installed packages 2007-05-25 15:31:43 +00:00
Rob
f8caf7f982 Deleting a window should not affect focus
This fixes a bug whereby deleting a window will first move focus to
that window before deleting it without moving focus back afterwards.

The fix generalises the remove inner function to delete a window from
the stack whether it's in focus or not. If the window is in focus,
behaviour remains as it was.
2007-05-25 02:41:18 +00:00
Spencer Janssen
654e64b772 Use --resume by default 2007-05-23 19:14:18 +00:00
bobstopper
bcf305cd1e add swapLeft and swapRight 2007-05-22 05:00:08 +00:00
Spencer Janssen
0df8dffc78 restart: don't preserve old args 2007-05-22 06:03:57 +00:00
Spencer Janssen
865939b660 Wibble 2007-05-22 04:38:44 +00:00
Spencer Janssen
9b52525417 Generalize withDisplay's type 2007-05-22 04:37:58 +00:00
Don Stewart
3202fa0673 refactor using whenX 2007-05-22 04:31:16 +00:00
Spencer Janssen
07a354e5a3 Add preliminary randr support 2007-05-22 04:02:28 +00:00
Neil Mitchell
c4dd126200 Update the Catch checking to the new interface for StackSet 2007-05-22 01:54:22 +00:00
Spencer Janssen
e300df5425 Remove the magic '2' 2007-05-21 23:45:35 +00:00
Spencer Janssen
d074b1bcfd List --resume args first 2007-05-21 23:24:27 +00:00
Spencer Janssen
ff975f6d40 Move special case 'view' code into 'windows'.
This is ugly right now -- I promise to clean it up later.
2007-05-21 21:56:46 +00:00
Spencer Janssen
a9d7b7ef49 Experimental support for a beefier restart. 2007-05-21 19:46:53 +00:00
Spencer Janssen
270d80297f Catch the exception rather than explicitly checking the PATH 2007-05-21 19:19:00 +00:00
Spencer Janssen
3c2ad2509e Put restart in the X monad 2007-05-21 19:07:49 +00:00
Spencer Janssen
90dd7705a8 Show instances for WorkspaceId and ScreenId 2007-05-21 19:07:04 +00:00
Spencer Janssen
127fd0b309 Read instance for StackSet 2007-05-21 18:45:04 +00:00
Spencer Janssen
c8cfc1faca Remove redundant fromIntegrals 2007-05-21 16:51:23 +00:00
Spencer Janssen
daefb508d7 Use Position for dimensions 2007-05-21 16:28:09 +00:00
Spencer Janssen
b59d4d1dc0 Make screen info dynamic: first step to supporting randr 2007-05-21 15:27:59 +00:00
Don Stewart
b1345e037c modify 2007-05-21 11:57:50 +00:00
Don Stewart
ea80d2a71f Move xinerama current/visible/hidden workspace logic into StackSet directly. 2007-05-21 05:52:53 +00:00
Jason Creighton
02073c547b s/workspace/windowset/ 2007-05-21 04:03:30 +00:00
Jason Creighton
5c44fa79fd focusWindow: always view the containing workspace first 2007-05-21 03:55:51 +00:00
Don Stewart
d28d4251e0 explicit export list for StackSet 2007-05-21 02:52:50 +00:00
Don Stewart
d3d058345d comment only 2007-05-20 09:08:46 +00:00
Jason Creighton
b757a526db only hide old workspace on view if the old workspace is not visible (Xinerama) 2007-05-21 03:14:35 +00:00
Spencer Janssen
1c4b0a51d8 Fix mod-j/k bindings 2007-05-21 03:02:53 +00:00
Spencer Janssen
2e5084319a Be explicit about suspicious System.Mem import 2007-05-20 16:57:41 +00:00
Don Stewart
77e46027ed HEADS UP: Rewrite StackSet as a Zipper
In order to give a better account of how focus and master interact, and
how each operation affects focus, we reimplement the StackSet type as a
two level nested 'Zipper'. To quote Oleg:

    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.

That is, we use the Zipper as a cursor which encodes the window which is
in focus. Thus our data structure tracks focus correctly by
construction! We then get simple, obvious semantics for e.g. insert, in
terms of how it affects focus/master. Our transient-messes-with-focus
bug evaporates. 'swap' becomes trivial.

By moving focus directly into the stackset, we can toss some QC
properties about focus handling: it is simply impossible now for focus
to go wrong. As a benefit, we get a dozen new QC properties for free,
governing how master and focus operate.

The encoding of focus in the data type also simplifies the focus
handling in Operations: several operations affecting focus are now
simply wrappers over StackSet.

For the full story, please read the StackSet module, and the QC
properties.

Finally, we save ~40 lines with the simplified logic in Operations.hs

For more info, see the blog post on the implementation,

    http://cgi.cse.unsw.edu.au/~dons/blog/2007/05/17#xmonad_part1b_zipper
2007-05-20 07:00:53 +00:00
Spencer Janssen
f9af744b1e Read is not needed for StackSet 2007-05-16 05:42:33 +00:00
Jason Creighton
4206c4bae9 variable number of windows in master area 2007-05-16 03:14:37 +00:00
Spencer Janssen
810c19d7f2 Use camelCase, please. 2007-05-16 01:44:54 +00:00
David Roundy
3cc55de0f4 beautify tile 2007-05-15 15:40:11 +00:00
David Roundy
21cbab3f06 put doLayout in the X monad. 2007-05-12 21:53:01 +00:00
Spencer Janssen
67fe5ab219 setsid() before exec. Intended to fix issue #7 2007-05-14 04:45:47 +00:00
David Roundy
54ee507cca keep focus stack. 2007-05-10 13:16:37 +00:00
Jason Creighton
378aa87173 bump LOC limit to 550 2007-05-10 03:27:31 +00:00
Spencer Janssen
47ae5e4ea5 Remove broken prop_promoterotate, replace it with prop_promote_raise_id 2007-05-08 21:19:07 +00:00
Spencer Janssen
c4030d45e2 Disable shift_reversible until focus issues are decided. 2007-05-08 21:09:52 +00:00
Spencer Janssen
f5b0df6a73 Disable delete.push until focus issues are decided 2007-05-08 20:49:21 +00:00
Spencer Janssen
c5c958dc2c Remove unsafe fromJust 2007-05-08 16:38:22 +00:00
Neil Mitchell
626d25bb3a Add the initial Catch testing framework for StackSet 2007-05-08 15:46:21 +00:00
Neil Mitchell
97141b9a07 Work around the fact that Yhc gets defaulting a bit wrong 2007-05-08 12:49:49 +00:00
Spencer Janssen
4afb251f41 Make tests typecheck 2007-05-08 15:24:49 +00:00
Spencer Janssen
a846eb18ff Remove unsafe use of head 2007-05-08 15:21:16 +00:00
Spencer Janssen
f03ca10714 Make 'index' return Nothing, rather than error 2007-05-08 15:12:00 +00:00
Spencer Janssen
ba9e15e772 Use 'drop 1' rather than tail, skip equality check. 2007-05-08 15:09:43 +00:00
Spencer Janssen
70fe61971b Redundant parens 2007-05-08 15:04:12 +00:00
Spencer Janssen
1276edc861 StackSet.view: ignore invalid indices 2007-05-08 14:39:51 +00:00
Neil Mitchell
27f1f50071 Change the swap function so its Haskell 98, by using list-comps instead of pattern-guards. 2007-05-08 12:31:58 +00:00
Don Stewart
9f4fd822b6 Arbitrary instance for StackSet must set random focus on each workspace
When focus was separated from the stack order on each workspace, we
forgot to update the Arbitrary instance to set random focus. As spotted
by David R, this then invalidates 4 of our QC properties. In particular,
the property involving where focus goes after a random transient
(annoying behaviour) appeared to be correct, but wasn't, due to
inadequate coverage.

This patch sets focus to a random window on each workspace. As a result,
we now catch the focus/raise/delete issue people have been complaining
about.

Lesson: make sure your QuickCheck generators are doing what you think
they are.
2007-05-08 05:11:26 +00:00
David Roundy
ab27c7d48d make quickcheck tests friendlier to read. 2007-05-05 17:54:15 +00:00
Jason Creighton
ab0f3be0af make Properties.hs exit with failure on test failure 2007-05-05 17:43:57 +00:00
Don Stewart
7a56422491 since we just ignore type errors, no need to derive Show 2007-05-04 09:41:43 +00:00
Don Stewart
0928bb484a Constrain layout messages to be members of a Message class
Using Typeables as the only constraint on layout messages is a bit
scary, as a user can send arbitrary values to layoutMsg, whether they
make sense or not: there's basically no type feedback on the values you
supply to layoutMsg.

Folloing Simon Marlow's dynamically extensible exceptions paper, we use
an existential type, and a Message type class, to constrain valid
arguments to layoutMsg to be valid members of Message.

That is, a user writes some data type for messages their layout
algorithm accepts:

  data MyLayoutEvent = Zoom
                     | Explode
                     | Flaming3DGlassEffect
                     deriving (Typeable)

and they then add this to the set of valid message types:

  instance Message MyLayoutEvent

Done. We also reimplement the dynamic type check while we're here, to
just directly use 'cast', rather than expose a raw fromDynamic/toDyn.

With this, I'm much happier about out dynamically extensible layout
event subsystem.
2007-05-04 08:16:49 +00:00
Spencer Janssen
72e7bed426 Handle empty layout lists 2007-05-04 04:56:44 +00:00
Don Stewart
d0ef78e5c3 refactoring, style, comments on new layout code 2007-05-04 02:36:18 +00:00
Jason Creighton
b5ed587f2e use anyKey constant instead of magic number 2007-05-04 01:50:43 +00:00
Jason Creighton
7a89f431b1 added mirrorLayout to mirror arbitrary layouts 2007-05-04 01:46:53 +00:00
Spencer Janssen
a5e0e2458d Fix layout switching order 2007-05-03 23:56:32 +00:00
Spencer Janssen
9d3d2f8503 More Config.hs bugs 2007-05-03 23:46:07 +00:00
Spencer Janssen
204c90b072 Revert accidental change to Config.hs 2007-05-03 23:31:48 +00:00
Spencer Janssen
bedc069143 Add -fglasgow-exts for pattern guards. Properties.hs doesn't complain anymore 2007-05-03 21:42:21 +00:00
Spencer Janssen
5b7c6c8631 Avoid the unsafe pattern match, in case Config.hs has no layouts 2007-05-03 21:40:07 +00:00
David Roundy
ea1134db26 add support for extensible layouts. 2007-05-03 14:47:50 +00:00
Don Stewart
f0df95da72 comments. and stop tracing events to stderr 2007-05-03 07:58:21 +00:00
Don Stewart
f5e8b2b6a8 -Wall police 2007-05-03 07:49:37 +00:00
Don Stewart
08ce2a5efa elaborate documentation in Config.hs 2007-05-03 07:48:43 +00:00
Spencer Janssen
b63e8c029e Use updated refreshKeyboardMapping. Requires latest X11-extras 2007-05-03 03:20:40 +00:00
Jason Creighton
fa271e00ce run QC tests in addition to LOC test 2007-05-03 00:32:02 +00:00
Spencer Janssen
3416eceb5d Add 'mod-n': refreshes current layout 2007-05-03 00:22:52 +00:00
Spencer Janssen
833d5ae357 Fix tests after StackSet changes 2007-05-02 20:16:22 +00:00
Spencer Janssen
1dff21001c First steps to adding floating layer 2007-05-02 19:59:17 +00:00
Don Stewart
0c569a64e1 update motivational text using xmonad.org 2007-05-02 06:18:59 +00:00
Spencer Janssen
c0266c0cb8 Sort dependencies in installation order 2007-05-01 20:42:49 +00:00
Spencer Janssen
56a4164a90 Recommend X11-extras 0.1 2007-05-01 20:41:21 +00:00
Don Stewart
fdc73b4cb1 elaborate description in .cabal 2007-05-01 03:54:14 +00:00
Don Stewart
121e20d342 use -fasm by default. Much faster 2007-05-01 03:12:20 +00:00
Don Stewart
af7c76d3fe check we never generate invalid stack sets 2007-04-30 06:59:46 +00:00
Spencer Janssen
3586379ecc Make border width configurable 2007-04-30 16:35:15 +00:00
Spencer Janssen
08e514b28f Add Config.hs-boot, remove defaultLayoutDesc from XConf 2007-04-30 16:26:47 +00:00
Spencer Janssen
ecbff364c9 Comment only 2007-04-30 16:16:35 +00:00
Spencer Janssen
1e83de8cde Comment only 2007-04-30 16:15:11 +00:00
Don Stewart
4d9fa8bc98 view n . shift n . view i . shift i) x == x --> shift + view is invertible 2007-04-30 06:29:01 +00:00
Don Stewart
17f70344ec add rotate all and view idempotency tests 2007-04-30 05:57:51 +00:00
Don Stewart
14773f6300 push is idempotent 2007-04-30 05:43:45 +00:00
Don Stewart
89182406a8 add two properties relating to empty window managers 2007-04-30 05:10:16 +00:00
Don Stewart
0d7969be18 new QC property: opening a window only affects the current screen 2007-04-30 05:01:33 +00:00
Spencer Janssen
5d086df912 Add XConf for values that don't change. 2007-04-30 05:47:15 +00:00
Spencer Janssen
5c1982cc35 Control.Arrow is suspicious, add an explicit import 2007-04-30 05:36:23 +00:00
Jason Creighton
bdbca84bcd configurable border colors
This also fixes a bug where xmonad was assuming a 24-bit display, and just
using, eg, 0xff0000 as an index into a colormap without querying the X server
to determine the proper pixel value for "red".
2007-04-30 04:38:59 +00:00
Don Stewart
8097060259 a bit more precise about building non-empty stacksets for one test 2007-04-30 03:57:29 +00:00
Don Stewart
df7d1d95fa remove redundant call to 'delete' in 'shift' 2007-04-30 03:11:51 +00:00
Don Stewart
8265cae8a8 clean 'delete' a little 2007-04-30 02:53:19 +00:00
Don Stewart
a07f0778ad shrink 'swap' 2007-04-30 02:48:13 +00:00
Don Stewart
9fafa995c7 shrink 'rotate' a little 2007-04-30 02:45:25 +00:00
Don Stewart
966da43176 move size into Properties.hs 2007-04-30 02:17:58 +00:00
Don Stewart
a839238483 don't need 'size' operation on StackSet 2007-04-30 01:59:27 +00:00
Don Stewart
5386ab0094 add homepage: field to .cabal file 2007-04-29 04:10:11 +00:00
Don Stewart
d5e73b70ae add fromList to Properties.hs 2007-04-29 03:58:23 +00:00
Don Stewart
9b80a36cf8 move fromList into Properties.hs, -17 loc 2007-04-29 03:58:04 +00:00
Jason Creighton
90b4eb607c avoid grabbing all keys when a keysym is undefined
XKeysymToKeycode() returns zero if the keysym is undefined. Zero also happens
to be the value of AnyKey.
2007-04-28 18:00:46 +00:00
Spencer Janssen
0dd75f9d68 Further refactoring 2007-04-26 21:22:57 +00:00
Spencer Janssen
bd41b81c16 Refactor in Config.hs (no real changes) 2007-04-26 21:14:07 +00:00
Spencer Janssen
c03b53db67 Add the manpage to extra-source-files 2007-04-26 01:41:05 +00:00
David Lazar
4fbb4e23a3 add xmonad manpage 2007-04-26 01:08:12 +00:00
Spencer Janssen
ea94892e1c Remove toList 2007-04-26 00:57:13 +00:00
Jason Creighton
767bc68acf Ignore numlock and capslock in keybindings 2007-04-24 01:33:57 +00:00
Spencer Janssen
70a87063d1 Clear numlock bit 2007-04-24 01:03:52 +00:00
Jason Creighton
2a5be03dd1 force window border to 1px 2007-04-23 05:08:24 +00:00
Don Stewart
50f89990a0 s/creigh// 2007-04-23 02:40:26 +00:00
Don Stewart
5f6ac3573d some other things to do 2007-04-23 02:31:51 +00:00
Spencer Janssen
5475c751ab Start TODOs for 0.2 2007-04-23 02:15:26 +00:00
Don Stewart
47eb93c694 update readme 2007-04-22 09:05:07 +00:00
13 changed files with 2346 additions and 979 deletions

254
Config.hs
View File

@@ -7,144 +7,180 @@
-- Maintainer : dons@cse.unsw.edu.au
-- Stability : stable
-- Portability : portable
--
--
-----------------------------------------------------------------------------
-- This module specifies configurable defaults for xmonad. If you change
-- values here, be sure to recompile and restart (mod-q) xmonad,
-- for the changes to take effect.
--
------------------------------------------------------------------------
module Config where
--
-- Useful imports
--
-- 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 XMonad
import Operations
import qualified StackSet as W
import Data.Ratio
import Data.Bits
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
--
-- The number of workspaces (virtual screens, or window groups)
--
workspaces :: [WorkspaceId]
workspaces = [0..8]
-- 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 lets you specify which modkey you want to use. The default is
-- mod1Mask ("left alt"). You may also consider using mod3Mask ("right
-- alt"), which does not conflict with emacs keybindings. The "windows
-- key" is usually mod4Mask.
--
modMask :: KeyMask
modMask = mod1Mask
-- How much to change the horizontal/vertical split bar by defalut.
defaultDelta :: Rational
defaultDelta = 3%100
-- |
-- Default offset of drawable screen boundaries from each physical screen.
-- Anything non-zero here will leave a gap of that many pixels on the
-- given edge, on the that screen. A useful gap at top of screen for a
-- menu bar (e.g. 15)
--
-- An example, to set a top gap on monitor 1, and a gap on the bottom of
-- monitor 2, you'd use a list of geometries like so:
--
-- > defaultGaps = [(18,0,0,0),(0,18,0,0)] -- 2 gaps on 2 monitors
--
-- Fields are: top, bottom, left, right.
--
defaultGaps :: [(Int,Int,Int,Int)]
defaultGaps = [(0,0,0,0)] -- 15 for default dzen
-- |
-- numlock handling:
--
-- The mask for the numlock key. 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)
--
numlockMask :: KeyMask
numlockMask = lockMask
numlockMask = mod2Mask
-- 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 }
-- |
-- Border colors for unfocused and focused windows, respectively.
--
normalBorderColor, focusedBorderColor :: String
normalBorderColor = "#dddddd"
focusedBorderColor = "#ff0000"
-- The keys list.
-- |
-- Width of the window border in pixels
--
borderWidth :: Dimension
borderWidth = 1
-- |
-- The default set of tiling algorithms
--
defaultLayouts :: [Layout Window]
defaultLayouts = [ 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
-- |
-- Perform an arbitrary action on each state change.
-- Examples include:
-- * do nothing
-- * log the state to stdout
--
logHook :: X ()
logHook = return ()
-- |
-- The key bindings list.
--
-- The unusual comment format is used to generate the documentation
-- automatically.
--
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)
-- launching and killing programs
[ ((modMask .|. shiftMask, xK_Return), spawn "xterm") -- @@ Launch an xterm
, ((modMask, xK_p ), spawn "exe=`dmenu_path | dmenu` && eval \"exec $exe\"") -- @@ Launch dmenu
, ((modMask .|. shiftMask, xK_p ), spawn "gmrun") -- @@ Launch gmrun
, ((modMask .|. shiftMask, xK_c ), kill) -- @@ Close the focused window
, ((modMask, xK_Tab ), raise GT)
, ((modMask, xK_j ), raise GT)
, ((modMask, xK_k ), raise LT)
, ((modMask, xK_space ), switchLayout) -- @@ Rotate through the available layout algorithms
, ((modMask, xK_h ), changeSplit (negate defaultDelta))
, ((modMask, xK_l ), changeSplit defaultDelta)
, ((modMask, xK_n ), refresh) -- @@ Resize viewed windows to the correct size
, ((modMask .|. shiftMask, xK_c ), kill)
-- move focus up or down the window stack
, ((modMask, xK_Tab ), focusDown) -- @@ Move focus to the next window
, ((modMask, xK_j ), focusDown) -- @@ Move focus to the next window
, ((modMask, xK_k ), focusUp ) -- @@ Move focus to the previous window
, ((modMask .|. shiftMask, xK_q ), io $ exitWith ExitSuccess)
, ((modMask .|. shiftMask .|. controlMask, xK_q ), io restart)
-- modifying the window order
, ((modMask, xK_Return), swapMaster) -- @@ Swap the focused window and the master window
, ((modMask .|. shiftMask, xK_j ), swapDown ) -- @@ Swap the focused window with the next window
, ((modMask .|. shiftMask, xK_k ), swapUp ) -- @@ Swap the focused window with the previous window
-- Cycle the current tiling order
, ((modMask, xK_Return), promote)
-- resizing the master/slave ratio
, ((modMask, xK_h ), sendMessage Shrink) -- @@ Shrink the master area
, ((modMask, xK_l ), sendMessage Expand) -- @@ Expand the master area
, ((modMask, xK_t ), withFocused sink) -- @@ Push window back into tiling
-- increase or decrease number of windows in the master area
, ((modMask , xK_comma ), sendMessage (IncMasterN 1)) -- @@ Increment the number of windows in the master area
, ((modMask , xK_period), sendMessage (IncMasterN (-1))) -- @@ Deincrement the number of windows in the master area
-- toggle the status bar gap
, ((modMask , xK_b ), modifyGap (\i n -> let x = (defaultGaps ++ repeat (0,0,0,0)) !! i in if n == x then (0,0,0,0) else x)) -- @@ Toggle the status bar gap
-- quit, or restart
, ((modMask .|. shiftMask, xK_q ), io (exitWith ExitSuccess)) -- @@ Quit xmonad
, ((modMask , xK_q ), restart Nothing True) -- @@ Restart xmonad
] ++
-- 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)]]
-- mod-[1..9] @@ Switch to workspace N
-- mod-shift-[1..9] @@ Move client to workspace N
[((m .|. modMask, k), f i)
| (i, k) <- zip workspaces [xK_1 ..]
, (f, m) <- [(view, 0), (shift, shiftMask)]]
-- Keybindings to each screen :
-- mod-wer (underneath 123) switches to physical/Xinerama screens 1 2 and 3
-- 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 >>= f)
[((m .|. modMask, key), screenWorkspace sc >>= flip whenJust f)
| (key, sc) <- zip [xK_w, xK_e, xK_r] [0..]
, (f, m) <- [(view, 0), (tag, shiftMask)]]
, (f, m) <- [(windows . W.view, 0), (shift, shiftMask)]]
-- |
-- default actions bound to mouse events
--
mouseBindings :: M.Map (KeyMask, Button) (Window -> X ())
mouseBindings = M.fromList $
-- mod-button1 @@ Set the window to floating mode and move by dragging
[ ((modMask, button1), (\w -> focus w >> mouseMoveWindow w))
-- mod-button2 @@ Raise the window to the top of the stack
, ((modMask, button2), (\w -> focus w >> swapMaster))
-- mod-button3 @@ Set the window to floating mode and resize by dragging
, ((modMask, button3), (\w -> focus w >> mouseResizeWindow w)) ]

8
Config.hs-boot Normal file
View File

@@ -0,0 +1,8 @@
module Config where
import Graphics.X11.Xlib.Types (Dimension)
import Graphics.X11.Xlib (KeyMask)
import XMonad
borderWidth :: Dimension
logHook :: X ()
numlockMask :: KeyMask
workspaces :: [WorkspaceId]

261
Main.hs
View File

@@ -8,100 +8,142 @@
-- Stability : unstable
-- Portability : not portable, uses mtl, X11, posix
--
-- xmonad, a minimalist, tiling window manager for X11
--
-----------------------------------------------------------------------------
--
-- xmonad, a minimal window manager for X11
--
module Main where
import Data.Bits
import qualified Data.Map as M
import qualified Data.Set as S
import Control.Monad.Reader
import Control.Monad.State
import Data.Maybe (fromMaybe)
import System.Environment (getArgs)
import Graphics.X11.Xlib hiding (refreshKeyboardMapping)
import Graphics.X11.Xlib.Extras
import Graphics.X11.Xinerama
import Control.Monad.State
import qualified StackSet as W
import Graphics.X11.Xinerama (getScreenInfo)
import XMonad
import Operations
import Config
import StackSet (new, floating, member, findIndex, workspace, tag, current, visible)
import qualified StackSet as W
import Operations
--
import System.IO
-- |
-- The main entry point
--
main :: IO ()
main = do
dpy <- openDisplay ""
let dflt = defaultScreen dpy
rootw <- rootWindow dpy dflt
wmdelt <- internAtom dpy "WM_DELETE_WINDOW" False
wmprot <- internAtom dpy "WM_PROTOCOLS" False
xinesc <- getScreenInfo dpy
nbc <- initColor dpy normalBorderColor
fbc <- initColor dpy focusedBorderColor
hSetBuffering stdout NoBuffering
args <- getArgs
let st = XState
let winset | ("--resume" : s : _) <- args
, [(x, "")] <- reads s = x
| otherwise = new workspaces $ zipWith SD xinesc gaps
gaps = take (length xinesc) $ defaultGaps ++ repeat (0,0,0,0)
safeLayouts = case defaultLayouts of [] -> (full, []); (x:xs) -> (x,xs)
cf = XConf
{ 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
}
, normalBorder = nbc
, focusedBorder = fbc }
st = XState
{ windowset = winset
, layouts = M.fromList [(w, safeLayouts) | w <- workspaces]
, mapped = S.empty
, waitingUnmap = M.empty
, dragging = Nothing }
xSetErrorHandler -- in C, I'm too lazy to write the binding
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
selectInput dpy rootw $ substructureRedirectMask .|. substructureNotifyMask
.|. enterWindowMask .|. leaveWindowMask .|. structureNotifyMask
grabKeys dpy rootw
grabButtons dpy rootw
sync dpy False
ws <- scan dpy rootw
ws <- scan dpy rootw -- on the resume case, will pick up new windows
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
runX cf st $ do
-- walk workspace, resetting X states/mask for windows
-- TODO, general iterators for these lists.
sequence_ [ setInitialProperties w >> reveal w
| wk <- map W.workspace (W.current winset : W.visible winset)
, w <- W.integrate' (W.stack wk) ]
sequence_ [ setInitialProperties w >> hide w
| wk <- W.hidden winset
, w <- W.integrate' (W.stack wk) ]
mapM_ manage ws -- find new windows
refresh
-- main loop, for all you HOF/recursion fans out there.
forever $ handle =<< io (nextEvent dpy e >> getEvent e)
where forever a = a >> forever a
-- ---------------------------------------------------------------------
-- IO stuff. Doesn't require any X state
-- Most of these things run only on startup (bar grabkeys)
-- | scan for any initial windows to manage
-- | 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
where
ok w = do wa <- getWindowAttributes dpy w
return $ not (wa_override_redirect wa)
&& wa_map_state wa == waIsViewable
-- 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 :: Display -> Window -> IO ()
grabKeys dpy rootw = do
ungrabKey dpy '\0' {-AnyKey-} anyModifier rootw
flip mapM_ (M.keys keys) $ \(mask,sym) -> do
ungrabKey dpy anyKey anyModifier rootw
forM_ (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
-- "If the specified KeySym is not defined for any KeyCode,
-- XKeysymToKeycode() returns zero."
when (kc /= '\0') $ mapM_ (grab kc . (mask .|.)) extraModifiers
where grab kc m = grabKey dpy kc m rootw True grabModeAsync grabModeAsync
grabButtons :: Display -> Window -> IO ()
grabButtons dpy rootw = do
ungrabButton dpy anyButton anyModifier rootw
mapM_ (\(m,b) -> mapM_ (grab b . (m .|.)) extraModifiers) (M.keys mouseBindings)
where grab button mask = grabButton dpy button mask rootw False buttonPressMask
grabModeAsync grabModeSync none none
-- ---------------------------------------------------------------------
-- Event handler
--
-- | handle. Handle X events
-- | 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:
--
@@ -109,81 +151,104 @@ grabKeys dpy rootw = do
-- [Expose] = expose,
-- [PropertyNotify] = propertynotify,
--
-- 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
--
-- Will require an abstract interpreter from Event -> X Action, which
-- modifies the internal X state, and then produces an IO action to
-- evaluate.
--
-- XCreateWindowEvent(3X11)
-- Window manager clients normally should ignore this window if the
-- override_redirect member is True.
--
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
| t == keyPress = withDisplay $ \dpy -> do
s <- io $ keycodeToKeysym dpy code 0
whenJust (M.lookup (cleanMask m,s) keys) id
-- manage a new window
handle (MapRequestEvent {ev_window = w}) = withDisplay $ \dpy -> do
wa <- io $ getWindowAttributes dpy w -- ignore override windows
when (not (wa_override_redirect wa)) $ manage w
-- 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
handle (DestroyWindowEvent {ev_window = w}) = do b <- isClient w; when b $ unmanage w
-- window gone, unmanage it
handle (DestroyWindowEvent {ev_window = w}) = whenX (isClient w) $ unmanage w
-- window gone, unmanage it
handle (UnmapEvent {ev_window = w}) = do b <- isClient w; when b $ unmanage w
-- We track expected unmap events in waitingUnmap. We ignore this event unless
-- it is synthetic or we are not expecting an unmap notification from a window.
handle (UnmapEvent {ev_window = w, ev_send_event = synthetic}) = whenX (isClient w) $ do
e <- gets (fromMaybe 0 . M.lookup w . waitingUnmap)
if (synthetic || e == 0)
then unmanage w
else modify (\s -> s { waitingUnmap = M.adjust pred w (waitingUnmap s) })
-- set keyboard mapping
handle e@(MappingNotifyEvent {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
io $ refreshKeyboardMapping e
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
-- 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
-- entered a normal window
-- handle motionNotify event, which may mean we are dragging.
handle e@(MotionEvent {ev_event_type = _t, ev_x = x, ev_y = y}) = do
drag <- gets dragging
case drag of
Just (d,_) -> d (fromIntegral x) (fromIntegral y) -- we're dragging
Nothing -> broadcastMessage e
-- click on an unfocused window, makes it focused on this workspace
handle e@(ButtonEvent {ev_window = w,ev_event_type = t,ev_button = b })
| t == buttonPress = do
-- If it's the root window, then it's something we
-- grabbed in grabButtons. Otherwise, it's click-to-focus.
isr <- isRoot w
if isr then whenJust (M.lookup (cleanMask (ev_state e), b) mouseBindings) ($ ev_subwindow e)
else focus w
sendMessage e -- Always send button events.
-- entered a normal window, makes this focused.
handle e@(CrossingEvent {ev_window = w, ev_event_type = t})
| t == enterNotify && ev_mode e == notifyNormal && ev_detail e /= notifyInferior
= safeFocus w
| t == enterNotify && ev_mode e == notifyNormal
&& ev_detail e /= notifyInferior = focus w
-- left a window, check if we need to focus root
handle e@(CrossingEvent {ev_event_type = t})
| t == leaveNotify
= do rootw <- gets theRoot
when (ev_window e == rootw && not (ev_same_screen e)) $ setFocus rootw
= 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}) = 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
}
handle e@(ConfigureRequestEvent {ev_window = w}) = withDisplay $ \dpy -> do
ws <- gets windowset
wa <- io $ getWindowAttributes dpy w
-- TODO temporary workaround for some bugs in float. Don't call 'float' on
-- windows that aren't visible, because it changes the focused screen
let vis = any ((== findIndex w ws) . Just . tag . workspace) (current ws : visible ws)
if (M.member w (floating ws) && vis)
|| not (member w ws)
then do io $ configureWindow dpy w (ev_value_mask e) $ WindowChanges
{ wc_x = ev_x e
, wc_y = ev_y e
, wc_width = ev_width e
, wc_height = ev_height e
, wc_border_width = fromIntegral borderWidth
, wc_sibling = ev_above e
, wc_stack_mode = ev_detail e }
when (member w ws) (float w)
else io $ allocaXEvent $ \ev -> do
setEventType ev configureNotify
setConfigureEvent ev w w
(wa_x wa) (wa_y wa) (wa_width wa)
(wa_height wa) (ev_border_width e) none (wa_override_redirect wa)
sendEvent dpy w False 0 ev
io $ sync dpy False
handle e = trace (eventName e) -- ignoring
-- the root may have configured
handle (ConfigureEvent {ev_window = w}) = whenX (isRoot w) rescreen
handle e = broadcastMessage e -- trace (eventName e) -- ignoring

View File

@@ -1,282 +1,581 @@
-----------------------------------------------------------------------------
{-# OPTIONS -fno-warn-orphans -fglasgow-exts #-}
-- \^^ deriving Typeable
-- --------------------------------------------------------------------------
-- |
-- Module : Operations.hs
-- Copyright : (c) Spencer Janssen 2007
-- License : BSD3-style (see LICENSE)
--
-- Maintainer : dons@cse.unsw.edu.au
-- Stability : stable
-- Portability : portable
-- Stability : unstable
-- Portability : not portable, Typeable deriving, mtl, posix
--
-- Operations.
--
-----------------------------------------------------------------------------
module Operations where
import Data.List
import XMonad
import qualified StackSet as W
import {-# SOURCE #-} Config (borderWidth,logHook,numlockMask)
import Data.Maybe
import Data.Bits
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.Monad.State
import Control.Arrow
import System.Posix.Process
import System.Environment
import System.Directory
import Control.Monad.Reader
import Control.Arrow ((***), first, second)
import System.IO
import Graphics.X11.Xlib
import Graphics.X11.Xinerama (getScreenInfo)
import Graphics.X11.Xlib.Extras
import XMonad
import qualified Data.Traversable as T
import qualified StackSet as W
-- ---------------------------------------------------------------------
-- |
-- Window manager operations
-- manage. Add a new window to be managed in the current workspace.
-- Bring it into focus.
--
-- Whether the window is already managed, or not, it is mapped, has its
-- border set, and its event mask set.
--
manage :: Window -> X ()
manage w = whenX (fmap not $ isClient w) $ withDisplay $ \d -> do
setInitialProperties w >> reveal w
-- FIXME: This is pretty awkward. We can't can't let "refresh" happen
-- before the call to float, because that will resize the window and
-- lose the default sizing.
sh <- io $ getWMNormalHints d w
let isFixedSize = sh_min_size sh /= Nothing && sh_min_size sh == sh_max_size sh
isTransient <- isJust `liftM` io (getTransientForHint d w)
if isFixedSize || isTransient
then do modify $ \s -> s { windowset = W.insertUp w (windowset s) }
float w -- \^^ now go the refresh.
else windows $ W.insertUp w
-- | unmanage. A window no longer exists, remove it from the window
-- list, on whatever workspace it is.
--
-- FIXME: clearFloating should be taken care of in W.delete, but if we do it
-- there, floating status is lost when moving windows between workspaces,
-- because W.shift calls W.delete.
--
-- should also unmap?
--
unmanage :: Window -> X ()
unmanage w = do
windows (W.sink w . W.delete w)
setWMState w 0 {-withdrawn-}
modify (\s -> s {mapped = S.delete w (mapped s), waitingUnmap = M.delete w (waitingUnmap s)})
-- | focus. focus window up or down. or swap various windows.
focusUp, focusDown, swapUp, swapDown, swapMaster :: X ()
focusUp = windows W.focusUp
focusDown = windows W.focusDown
swapUp = windows W.swapUp
swapDown = windows W.swapDown
swapMaster = windows W.swapMaster
-- | shift. Move a window to a new workspace, 0 indexed.
shift :: WorkspaceId -> X ()
shift n = windows (W.shift n)
-- | view. Change the current workspace to workspace at offset n (0 indexed).
view :: WorkspaceId -> X ()
view = windows . W.greedyView
-- | Modify the size of the status gap at the top of the current screen
-- Taking a function giving the current screen, and current geometry.
modifyGap :: (Int -> (Int,Int,Int,Int) -> (Int,Int,Int,Int)) -> X ()
modifyGap f = do
windows $ \ws@(W.StackSet { W.current = c@(W.Screen { W.screenDetail = sd }) }) ->
let n = fromIntegral . W.screen $ c
g = f n . statusGap $ sd
in ws { W.current = c { W.screenDetail = sd { statusGap = g } } }
-- | Kill the currently focused client. If we do kill it, we'll get a
-- delete notify back from X.
--
-- There are two ways to delete a window. Either just kill it, or if it
-- supports the delete protocol, send a delete event (e.g. firefox)
--
kill :: X ()
kill = withDisplay $ \d -> withFocused $ \w -> do
wmdelt <- atom_WM_DELETE_WINDOW ; wmprot <- atom_WM_PROTOCOLS
protocols <- io $ getWMProtocols d w
io $ if wmdelt `elem` protocols
then allocaXEvent $ \ev -> do
setEventType ev clientMessage
setClientMessageEvent ev w wmprot 32 wmdelt 0
sendEvent d w False noEventMask ev
else killClient d w >> return ()
-- ---------------------------------------------------------------------
-- Managing windows
-- | 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
data UnDoLayout = UnDoLayout deriving ( Typeable, Eq )
instance Message UnDoLayout
-- | 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
-- Notify visible layouts to remove decorations etc
-- We cannot use sendMessage because this must not call refresh ever,
-- and must be called on all visible workspaces.
broadcastMessage UnDoLayout
XState { windowset = old, layouts = fls } <- get
let oldvisible = concatMap (W.integrate' . W.stack . W.workspace) $ W.current old : W.visible old
ws = f old
modify (\s -> s { windowset = ws })
d <- asks display
-- | hide. Hide a window by moving it offscreen.
-- for each workspace, layout the currently visible workspaces
let allscreens = W.current ws : W.visible ws
summed_visible = scanl (++) [] $ map (W.integrate' . W.stack . W.workspace) allscreens
visible <- fmap concat $ forM (zip allscreens summed_visible) $ \ (w, vis) -> do
let n = W.tag (W.workspace w)
this = W.view n ws
Just l = fmap fst $ M.lookup n fls
flt = filter (flip M.member (W.floating ws)) (W.index this)
tiled = (W.stack . W.workspace . W.current $ this)
>>= W.filter (not . flip M.member (W.floating ws))
>>= W.filter (not . (`elem` vis))
(SD (Rectangle sx sy sw sh)
(gt,gb,gl,gr)) = W.screenDetail w
viewrect = Rectangle (sx + fromIntegral gl) (sy + fromIntegral gt)
(sw - fromIntegral (gl + gr)) (sh - fromIntegral (gt + gb))
-- just the tiled windows:
-- now tile the windows on this workspace, modified by the gap
(rs, ml') <- runLayout l viewrect tiled `catchX` runLayout full viewrect tiled
mapM_ (uncurry tileWindow) rs
whenJust ml' $ \l' -> modify $ \ss ->
ss { layouts = M.adjust (first (const l')) n (layouts ss) }
-- 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
setTopFocus
logHook
-- io performGC -- really helps, but seems to trigger GC bugs?
-- hide every window that was potentially visible before, but is not
-- given a position by a layout now.
mapM_ hide (nub oldvisible \\ visible)
clearEvents enterWindowMask
-- | setWMState. set the WM_STATE property
setWMState :: Window -> Int -> X ()
setWMState w v = withDisplay $ \dpy -> do
a <- atom_WM_STATE
io $ changeProperty32 dpy w a a propModeReplace [fromIntegral v, fromIntegral none]
-- | hide. Hide a window by unmapping it, and setting Iconified.
hide :: Window -> X ()
hide w = withDisplay $ \d -> do
(sw,sh) <- gets dimensions
io $ moveWindow d w (2*fromIntegral sw) (2*fromIntegral sh)
hide w = whenX (gets (S.member w . mapped)) $ withDisplay $ \d -> do
io $ do selectInput d w (clientMask .&. complement structureNotifyMask)
unmapWindow d w
selectInput d w clientMask
setWMState w 3 --iconic
-- this part is key: we increment the waitingUnmap counter to distinguish
-- between client and xmonad initiated unmaps.
modify (\s -> s { waitingUnmap = M.insertWith (+) w 1 (waitingUnmap s)
, mapped = S.delete w (mapped s) })
-- | reveal. Show a window by mapping it and setting Normal
-- this is harmless if the window was already visible
reveal :: Window -> X ()
reveal w = withDisplay $ \d -> do
setWMState w 1 --normal
io $ mapWindow d w
modify (\s -> s { mapped = S.insert w (mapped s) })
-- | The client events that xmonad is interested in
clientMask :: EventMask
clientMask = structureNotifyMask .|. enterWindowMask .|. propertyChangeMask
-- | Set some properties when we initially gain control of a window
setInitialProperties :: Window -> X ()
setInitialProperties w = withDisplay $ \d -> io $ do
selectInput d w $ clientMask
setWindowBorderWidth d w borderWidth
-- | refresh. Render the currently visible workspaces, as determined by
-- the StackSet. Also, set focus to the focused window.
--
-- This is our 'view' operation (MVC), in that it pretty prints our model
-- with X calls.
--
refresh :: X ()
refresh = windows id
-- | clearEvents. Remove all events of a given type from the event queue.
clearEvents :: EventMask -> X ()
clearEvents mask = withDisplay $ \d -> io $ do
sync d False
allocaXEvent $ \p -> fix $ \again -> do
more <- checkMaskEvent d mask p
when more again -- beautiful
-- | tileWindow. Moves and resizes w such that it fits inside the given
-- rectangle, including its border.
tileWindow :: Window -> Rectangle -> X ()
tileWindow w r = withDisplay $ \d -> do
bw <- (fromIntegral . wa_border_width) `liftM` io (getWindowAttributes d w)
-- give all windows at least 1x1 pixels
let least x | x <= bw*2 = 1
| otherwise = x - bw*2
io $ moveResizeWindow d w (rect_x r) (rect_y r)
(least $ rect_width r) (least $ rect_height r)
reveal w
-- ---------------------------------------------------------------------
-- | rescreen. The screen configuration may have changed (due to
-- xrandr), update the state and refresh the screen, and reset the gap.
rescreen :: X ()
rescreen = do
xinesc <- withDisplay (io . getScreenInfo)
windows $ \ws@(W.StackSet { W.current = v, W.visible = vs, W.hidden = hs }) ->
let (xs, ys) = splitAt (length xinesc) $ map W.workspace (v:vs) ++ hs
(a:as) = zipWith3 W.Screen xs [0..] $ zipWith SD xinesc gs
sgs = map (statusGap . W.screenDetail) (v:vs)
gs = take (length xinesc) (sgs ++ repeat (0,0,0,0))
in ws { W.current = a
, W.visible = as
, W.hidden = ys }
-- ---------------------------------------------------------------------
-- 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 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
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) }
-- ---------------------------------------------------------------------
-- Setting keyboard focus
-- | 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
setTopFocus = withWindowSet $ maybe (setFocusX =<< asks theRoot) setFocusX . W.peek
-- | Set the border color for a particular window.
setBorder :: Window -> Pixel -> X ()
setBorder w p = withDisplay $ \d -> io $ setWindowBorder d w p
-- | Set focus explicitly to window 'w' if it is managed by us, or root.
-- This happens if X notices we've moved the mouse (and perhaps moved
-- the mouse to a new screen).
focus :: Window -> X ()
focus w = withWindowSet $ \s -> do
if W.member w s then when (W.peek s /= Just w) $ windows (W.focusWindow w)
else whenX (isRoot w) $ setFocusX w
-- | 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
-- | Call X to set the keyboard focus details.
setFocusX :: Window -> X ()
setFocusX w = withWindowSet $ \ws -> do
XConf { display = dpy , normalBorder = nbc, focusedBorder = fbc } <- ask
-- | promote. Move the currently focused window into the master frame
promote :: X ()
promote = windows W.promote
-- 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
io $ setWindowBorder dpy otherw nbc
-- | 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 ()
-- If we ungrab buttons on the root window, we lose our mouse bindings.
whenX (not `liftM` isRoot w) $ setButtonGrab False w
io $ do setInputFocus dpy w revertToPointerRoot 0
-- raiseWindow dpy w
io $ setWindowBorder dpy w fbc
-- | 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
-- ---------------------------------------------------------------------
-- Managing layout
-- | 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
-- | switchLayout. Switch to another layout scheme. Switches the
-- layout of 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.
--
-- Note that the new layout's deconstructor will be called, so it should be
-- idempotent.
switchLayout :: X ()
switchLayout = do
broadcastMessage UnDoLayout -- calling refresh now would defeat the point of deconstruction
n <- gets (W.tag . W.workspace . W.current . windowset)
modify $ \s -> s { layouts = M.adjust switch n (layouts s) }
refresh
where switch (x, xs) = let xs' = xs ++ [x] in (head xs', tail xs')
-- | 'screenWorkspace sc' returns the workspace number viewed by 'sc'.
screenWorkspace :: ScreenId -> X WorkspaceId
screenWorkspace sc = fmap (fromMaybe 0 . W.workspace sc) (gets workspace)
-- | Throw a message to the current Layout possibly modifying how we
-- layout the windows, then refresh.
--
sendMessage :: Message a => a -> X ()
sendMessage a = do n <- (W.tag . W.workspace . W.current) `fmap` gets windowset
Just (l,ls) <- M.lookup n `fmap` gets layouts
ml' <- modifyLayout l (SomeMessage a) `catchX` return (Just l)
whenJust ml' $ \l' -> do modify $ \s -> s { layouts = M.insert n (l',ls) (layouts s) }
refresh
-- | Send a message to all visible layouts, without necessarily refreshing.
-- This is how we implement the hooks, such as UnDoLayout.
broadcastMessage :: Message a => a -> X ()
broadcastMessage a = do
ol <- gets layouts
nl <- T.forM ol $ \ (l,ls) -> maybe (l,ls) (flip (,) ls) `fmap`
(modifyLayout l (SomeMessage a) `catchX` return (Just l))
modify $ \s -> s { layouts = nl }
instance Message Event
--
-- Builtin layout algorithms:
--
-- fullscreen mode
-- tall mode
--
-- The latter algorithms support the following operations:
--
-- Shrink
-- Expand
--
data Resize = Shrink | Expand deriving Typeable
data IncMasterN = IncMasterN Int deriving Typeable
instance Message Resize
instance Message IncMasterN
-- simple fullscreen mode, just render all windows fullscreen.
-- a plea for tuple sections: map . (,sc)
full :: Layout a
full = Layout { doLayout = \sc (W.Stack f _ _) -> return ([(f, sc)],Nothing)
, modifyLayout = const (return Nothing) } -- no changes
--
-- The tiling mode of xmonad, and its operations.
--
tall :: Int -> Rational -> Rational -> Layout a
tall nmaster delta frac =
Layout { doLayout = \r -> return . (\x->(x,Nothing)) .
ap zip (tile frac r nmaster . length) . W.integrate
, modifyLayout = \m -> return $ 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
-- | Mirror a rectangle
mirrorRect :: Rectangle -> Rectangle
mirrorRect (Rectangle rx ry rw rh) = (Rectangle ry rx rh rw)
-- | Mirror a layout, compute its 90 degree rotated form.
mirror :: Layout a -> Layout a
mirror (Layout { doLayout = dl, modifyLayout = ml }) =
Layout { doLayout = \sc w -> do (wrs, ml') <- dl (mirrorRect sc) w
return (map (second mirrorRect) wrs, mirror `fmap` ml')
, modifyLayout = fmap (fmap mirror) . ml }
-- | tile. Compute the positions for windows using the default 2 pane tiling algorithm.
--
-- The screen is divided (currently) into two panes. all clients are
-- then partioned between these two panes. one pane, the `master', by
-- convention has the least number of windows in it (by default, 1).
-- the variable `nmaster' controls how many windows are rendered in the
-- master pane.
--
-- `delta' specifies the ratio of the screen to resize by.
--
-- 'frac' specifies what proportion of the screen to devote to the
-- master area.
--
tile :: Rational -> Rectangle -> Int -> Int -> [Rectangle]
tile f r nmaster n = if n <= nmaster || nmaster == 0
then splitVertically n r
else splitVertically nmaster r1 ++ splitVertically (n-nmaster) r2 -- two columns
where (r1,r2) = splitHorizontallyBy f r
--
-- Divide the screen vertically into n subrectangles
--
splitVertically, splitHorizontally :: Int -> Rectangle -> [Rectangle]
splitVertically n r | n < 2 = [r]
splitVertically n (Rectangle sx sy sw sh) = Rectangle sx sy sw smallh :
splitVertically (n-1) (Rectangle sx (sy+fromIntegral smallh) sw (sh-smallh))
where smallh = sh `div` fromIntegral n --hmm, this is a fold or map.
splitHorizontally n = map mirrorRect . splitVertically n . mirrorRect
-- Divide the screen into two rectangles, using a rational to specify the ratio
splitHorizontallyBy, splitVerticallyBy :: RealFrac r => r -> Rectangle -> (Rectangle, Rectangle)
splitHorizontallyBy f (Rectangle sx sy sw sh) =
( Rectangle sx sy leftw sh
, Rectangle (sx + fromIntegral leftw) sy (sw-fromIntegral leftw) sh)
where leftw = floor $ fromIntegral sw * f
splitVerticallyBy f = (mirrorRect *** mirrorRect) . splitHorizontallyBy f . mirrorRect
------------------------------------------------------------------------
-- Utilities
-- | Return workspace visible on screen 'sc', or Nothing.
screenWorkspace :: ScreenId -> X (Maybe WorkspaceId)
screenWorkspace sc = withWindowSet $ return . W.lookupWorkspace sc
-- | Apply an X operation to the currently focused window, if there is one.
withFocused :: (Window -> X ()) -> X ()
withFocused f = withWindowSet $ \w -> whenJust (W.peek w) f
-- | True if window is under management by us
isClient :: Window -> X Bool
isClient w = liftM (W.member w) (gets workspace)
isClient w = withWindowSet $ return . W.member w
-- | 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
-- | Combinations of extra modifier masks we need to grab keys\/buttons for.
-- (numlock and capslock)
extraModifiers :: [KeyMask]
extraModifiers = [0, numlockMask, lockMask, numlockMask .|. lockMask ]
-- | Strip numlock\/capslock from a mask
cleanMask :: KeyMask -> KeyMask
cleanMask = (complement (numlockMask .|. lockMask) .&.)
-- | Get the Pixel value for a named color
initColor :: Display -> String -> IO Pixel
initColor dpy c = (color_pixel . fst) `liftM` allocNamedColor dpy colormap c
where colormap = defaultColormap dpy (defaultScreen dpy)
------------------------------------------------------------------------
-- | Floating layer support
-- | Make a floating window tiled
sink :: Window -> X ()
sink = windows . W.sink
-- | Make a tiled window floating, using its suggested rectangle
--
-- TODO: float changes the set of visible workspaces when we call it for an
-- invisible window -- this should not happen. See 'temporary workaround' in
-- the handler for ConfigureRequestEvent also.
float :: Window -> X ()
float w = withDisplay $ \d -> do
ws <- gets windowset
wa <- io $ getWindowAttributes d w
let sc = fromMaybe (W.current ws) $ find (pointWithin (fi $ wa_x wa) (fi $ wa_y wa) . screenRect . W.screenDetail) $ W.current ws : W.visible ws
sr = screenRect . W.screenDetail $ sc
sw = W.tag . W.workspace $ sc
bw = fi . wa_border_width $ wa
windows $ maybe id W.focusWindow (W.peek ws) . W.shift sw . W.focusWindow w . W.float w
(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)))
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)
-- ---------------------------------------------------------------------
-- 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
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)
mouseResizeWindow :: Window -> X ()
mouseResizeWindow w = whenX (isClient w) $ withDisplay $ \d -> do
io $ raiseWindow d w
wa <- io $ getWindowAttributes d w
sh <- io $ getWMNormalHints d w
io $ warpPointer d none w 0 0 0 0 (fromIntegral (wa_width wa)) (fromIntegral (wa_height wa))
mouseDrag (\ex ey -> do
io $ resizeWindow d w `uncurry`
applySizeHints sh ((fromIntegral (max 1 (ex - fromIntegral (wa_x wa)))),
(fromIntegral (max 1 (ey - fromIntegral (wa_y wa))))))
(float w)
-- ---------------------------------------------------------------------
-- | Support for window size hints
type D = (Dimension, Dimension)
-- | Reduce the dimensions if needed to comply to the given SizeHints.
applySizeHints :: 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

114
README
View File

@@ -1,42 +1,114 @@
xmonad : a lightweight X11 window manager.
xmonad : a lightweight X11 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.
------------------------------------------------------------------------
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.
About:
Xmonad is a tiling window manager for X. Windows are managed using
automatic tiling algorithms, which can be dynamically configured.
Windows are arranged so as to tile the screen without gaps, maximising
screen use. All features of the window manager are accessible
from the keyboard: a mouse is strictly optional. Xmonad is written
and extensible in Haskell, and custom layout algorithms may be
implemented by the user in config files. A guiding principle of the
user interface is <i>predictability</i>: users should know in
advance precisely the window arrangement that will result from any
action, leading to an intuitive user interface.
Xmonad provides three tiling algorithms by default: tall, wide and
fullscreen. In tall or wide mode, all windows are visible and tiled
to fill the plane without gaps. In fullscreen mode only the focused
window is visible, filling the screen. Alternative tiling
algorithms are provided as extensions. Sets of windows are grouped
together on virtual workspaces and each workspace retains its own
layout. Multiple physical monitors are supported via Xinerama,
allowing simultaneous display of several workspaces.
Adhering to a minimalist philosophy of doing one job, and doing it
well, the entire code base remains tiny, and is written to be simple
to understand and modify. By using Haskell as a configuration
language arbitrarily complex extensions may be implemented by the
user using a powerful `scripting' language, without needing to
modify the window manager directly. For example, users may write
their own tiling algorithms.
------------------------------------------------------------------------
Building:
Get the dependencies
Get the dependencies
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.)
It is likely that you already have some of these dependencies. To check
whether you've got a package run 'ghc-pkg list some_package_name'
mtl http://hackage.haskell.org/cgi-bin/hackage-scripts/package/mtl-1.0
unix http://hackage.haskell.org/cgi-bin/hackage-scripts/package/unix-2.0 (included with ghc)
X11-extras: darcs get http://darcs.haskell.org/~sjanssen/X11-extras
dmenu 2.{5,6,7} http://www.suckless.org/download/dmenu-2.7.tar.gz
unix http://hackage.haskell.org/cgi-bin/hackage-scripts/package/unix-2.0
X11 http://hackage.haskell.org/cgi-bin/hackage-scripts/package/X11-1.2.2
X11-extras: http://hackage.haskell.org/cgi-bin/hackage-scripts/package/X11-extras-0.3
And then build with Cabal:
runhaskell Setup.lhs configure --prefix=/home/dons
runhaskell Setup.lhs configure --prefix=$HOME
runhaskell Setup.lhs build
runhaskell Setup.lhs install
runhaskell Setup.lhs install --user
Then add:
------------------------------------------------------------------------
exec /home/dons/bin/xmonad
Notes for using the darcs version
to the last line of your .xsession file
If you're building the darcs version of xmonad, be sure to also
use the darcs version of X11-extras, which is developed concurrently
with xmonad.
darcs get http://darcs.haskell.org/~sjanssen/X11-extras
Not using X11-extras from darcs, is the most common reason for the
darcs version of xmonad to fail to build.
------------------------------------------------------------------------
Running xmonad:
Add:
$HOME/bin/xmonad
to the last line of your .xsession or .xinitrc file.
------------------------------------------------------------------------
XMonadContrib
There are various contributed modules that can be used with xmonad.
Examples include an ion3-like tabbed layout, a prompt/program launcher,
and various other useful modules. XMonadContrib is available at:
0.3 release: http://www.xmonad.org/XMonadContrib-0.3.tar.gz
darcs version: darcs get http://darcs.haskell.org/~sjanssen/XMonadContrib
------------------------------------------------------------------------
Other useful programs:
For a program dispatch menu:
dmenu http://www.suckless.org/download/
or
gmrun (in your package system)
For custom status bars:
dzen http://gotmor.googlepages.com/dzen
A nicer xterm replacment, that supports resizing better:
urxvt http://software.schmorp.de/pkg/rxvt-unicode.html
Authors:
Spencer Janssen
Don Stewart
Jason Creighton

View File

@@ -5,248 +5,495 @@
-- 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.
-- Stability : experimental
-- Portability : portable, Haskell 98
--
module StackSet (
StackSet(..), -- abstract
-- * Introduction
-- $intro
StackSet(..), Workspace(..), Screen(..), StackOrNot, Stack(..), RationalRect(..),
-- * Construction
-- $construction
new, view, greedyView,
-- * Xinerama operations
-- $xinerama
lookupWorkspace,
-- * Operations on the current stack
-- $stackOperations
peek, index, integrate, integrate', differentiate,
focusUp, focusDown,
focusWindow, tagMember, member, findIndex,
-- * Modifying the stackset
-- $modifyStackset
insertUp, delete, filter,
-- * Setting the master window
-- $settingMW
swapMaster, swapUp, swapDown, modify, modify', float, sink, -- needed by users
-- * Composite operations
-- $composite
shift
) where
screen, peekStack, index, empty, peek, push, delete, member,
raiseFocus, rotate, promote, shift, view, workspace, fromList,
toList, size, visibleWorkspaces, swap {- helper -}
) where
import Prelude hiding (filter)
import Data.Maybe (listToMaybe)
import qualified Data.List as L (delete,deleteBy,find,splitAt,filter)
import qualified Data.Map as M (Map,insert,delete,empty)
import Data.Maybe
import qualified Data.List as L (delete,genericLength,elemIndex)
import qualified Data.Map as M
-- $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 support:
--
-- Xinerama in X11 lets us view multiple virtual workspaces
-- simultaneously. While only one will ever be in focus (i.e. will
-- receive keyboard events), other workspaces may be passively viewable.
-- We thus need to track which virtual workspaces are associated
-- (viewed) on which physical screens. We use a simple Map Workspace
-- Screen for this.
--
-- Master and Focus
--
-- Each stack tracks a focused item, and for tiling purposes also tracks
-- a 'master' position. The connection between 'master' and 'focus'
-- needs to be well defined. Particular in relation to 'insert' and
-- 'delete'.
--
-- |
-- API changes from xmonad 0.1:
-- StackSet constructor arguments changed. StackSet workspace window screen
--
-- * new, -- was: empty
--
-- * view,
--
-- * index,
--
-- * peek, -- was: peek\/peekStack
--
-- * focusUp, focusDown, -- was: rotate
--
-- * swapUp, swapDown
--
-- * focus -- was: raiseFocus
--
-- * insertUp, -- was: insert\/push
--
-- * delete,
--
-- * swapMaster, -- was: promote\/swap
--
-- * member,
--
-- * shift,
--
-- * lookupWorkspace, -- was: workspace
--
-- * visibleWorkspaces -- gone.
--
------------------------------------------------------------------------
-- |
-- A cursor into a non-empty list of workspaces.
--
-- 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.
-- | 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
data StackSet i a sid sd =
StackSet { current :: !(Screen i a sid sd) -- ^ currently focused workspace
, visible :: [Screen i a sid sd] -- ^ non-focused workspaces, visible in xinerama
, hidden :: [Workspace i a] -- ^ workspaces not visible anywhere
, floating :: M.Map a RationalRect -- ^ floating windows
} deriving (Show, Read, 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
-- | Visible workspaces, and their Xinerama screens.
data Screen i a sid sd = Screen { workspace :: !(Workspace i a)
, screen :: !sid
, screenDetail :: !sd }
deriving (Show, Read, Eq)
-- |
-- A workspace is just a tag - its index - and a stack
--
-- | /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) }
data Workspace i a = Workspace { tag :: !i, stack :: StackOrNot a }
deriving (Show, Read, Eq)
-- | /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)
data RationalRect = RationalRect Rational Rational Rational Rational
deriving (Show, Read, Eq)
-- | /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.
-- |
-- 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:
--
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
-- > +-- 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'.
--
type StackOrNot a = Maybe (Stack a)
-- | /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)
}
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)
-- | /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 (?)
-- | 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
-- 'm' physical screens. 'm' should be less than or equal to the number of
-- workspace tags. The first workspace in the list will be current.
--
-- | Swap first occurences of 'a' and 'b' in list.
-- If both elements are not in the list, the list is unchanged.
-- Xinerama: Virtual workspaces are assigned to physical screens, starting at 0.
--
-- Given a set as a list (no duplicates)
new :: (Integral s) => [i] -> [sd] -> StackSet i a s sd
new wids m | not (null wids) && length m <= length wids = StackSet cur visi unseen M.empty
where (seen,unseen) = L.splitAt (length m) $ map (flip Workspace 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.
--
-- > 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)
-- 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 a, Eq s, Eq i) => i -> StackSet i a s sd -> StackSet i a s sd
view i s
| not (i `tagMember` s)
|| i == tag (workspace (current s)) = s -- out of bounds or 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 screenEq x (visible s) }
| Just x <- L.find ((i==).tag) (hidden s)
-- if it was hidden, it is raised on the xine screen currently used
= s { current = (current s) { workspace = x }
, hidden = workspace (current s) : L.delete x (hidden s) }
| otherwise = s
where screenEq x y = screen x == screen y
-- 'Catch'ing this might be hard. Relies on monotonically increasing
-- workspace tags defined in 'new'
-- |
-- 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 a, Eq s, Eq i) => i -> StackSet i a s sd -> StackSet i 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
insertAt n x ys = as ++ x : tail bs
where (as,bs) = splitAt n ys
wTag = (w == ) . tag
swap _ _ xs = xs -- do nothing
-- ---------------------------------------------------------------------
-- $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 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 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 :: StackOrNot a -> (Stack a -> StackOrNot a) -> StackSet i a s sd -> StackSet i 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 a s sd -> StackSet i 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 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' :: StackOrNot a -> [a]
integrate' = maybe [] integrate
-- |
-- /O(n)/. Texture a list.
--
differentiate :: [a] -> StackOrNot 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 -> StackOrNot 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 a s sd -> [a]
index = with [] integrate
-- let is = t : r ++ reverse l in take (length is) (dropWhile (/= m) (cycle is))
-- |
-- /O(1), O(w) on the wrapping case/.
--
-- 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 a s sd -> StackSet i 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
--
-- cycling:
-- promote w = w { stacks = M.adjust next (current w) (stacks w) }
-- where next [] = []
-- next xs = last xs : init xs
-- | /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 a s sd -> StackSet i a s sd
focusWindow w s | Just w == peek s = s
| otherwise = maybe s id $ do
n <- findIndex w s
return $ until ((Just w ==) . peek) focusUp (view n s)
-- | Get a list of all workspaces in the StackSet.
workspaces :: StackSet i a s sd -> [Workspace i a]
workspaces s = workspace (current s) : map workspace (visible s) ++ hidden s
-- | Is the given tag present in the StackSet?
tagMember :: Eq i => i -> StackSet i a s sd -> Bool
tagMember t = elem t . map tag . workspaces
-- |
-- Finding if a window is in the stackset is a little tedious. We could
-- keep a cache :: Map a i, but with more bookkeeping.
--
-- | 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
-- | /O(n)/. Is a window in the StackSet.
member :: Eq a => a -> StackSet i a s sd -> Bool
member a s = maybe False (const True) (findIndex a s)
-- | /O(1) on current window, O(n) in general/.
-- Return Just the workspace index of the given window, or Nothing
-- if the window is not in the StackSet.
findIndex :: Eq a => a -> StackSet i a s sd -> Maybe i
findIndex 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, and is set as the master window.
-- The previously focused element is moved down. The previously
-- 'master' element is forgotten. (Thus, 'insert' will cause a retiling).
--
-- 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 a s sd -> StackSet i 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 a s sd -> StackSet i 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 a s sd -> StackSet i 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 a s sd -> StackSet i 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 a s sd -> StackSet i 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 a s sd -> StackSet i a s sd
swapMaster = modify' $ \c -> case c of
Stack _ [] _ -> c -- already master.
Stack t ls rs -> Stack t [] (ys ++ x : rs) where (x:ys) = reverse ls
-- natural! keep focus, move current to the top, move top to current.
--
-- ---------------------------------------------------------------------
-- $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 a s sd -> StackSet i a s sd
shift n s | n `tagMember` s && n /= curtag = maybe s go (peek s)
| otherwise = s
where go w = view curtag . insertUp w . view n . delete w $ s
curtag = tag (workspace (current s))

38
TODO
View File

@@ -1,29 +1,17 @@
- 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
0.3 release:
* stable contrib repo tarball
* haddocks for core and contribs on xmonad.org
* tag xmonad
* tag X11-extras
* tag X11
* more QC tests
Status IO thread, the algorithm is something like this:
forever $ do
s <- getLine
atomic (writeTVar statusText s)
Statusbar drawing thread, waits for changes in all three TVars, and
redraws whenever it finds a change.
- possibles:
- use more constrained type in StackSet to avoid pattern match warnings
- audit for events handled in dwm.
- Notes on new StackSet:
- related:
- xcb bindings
- randr
The actors: screens, workspaces, windows
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.

182
XMonad.hs
View File

@@ -9,113 +9,203 @@
-- Stability : unstable
-- Portability : not portable, uses cunning newtype deriving
--
-----------------------------------------------------------------------------
--
-- The X monad, a state monad transformer over IO, for the window
-- manager state, and support routines.
--
-----------------------------------------------------------------------------
module XMonad (
X, WindowSet, WorkspaceId(..), ScreenId(..), XState(..), Layout(..), LayoutDesc(..),
runX, io, withDisplay, isRoot, spawn, trace, whenJust, rotateLayout
X, WindowSet, WorkspaceId(..), ScreenId(..), ScreenDetail(..), XState(..), XConf(..), Layout(..),
Typeable, Message, SomeMessage(..), fromMessage, runLayout,
runX, catchX, io, catchIO, withDisplay, withWindowSet, isRoot, spawn, restart, trace, whenJust, whenX,
atom_WM_STATE, atom_WM_PROTOCOLS, atom_WM_DELETE_WINDOW
) where
import StackSet (StackSet)
import StackSet
import Control.Monad.State
import Control.Monad.Reader
import System.IO
import System.Posix.Process (executeFile, forkProcess, getProcessStatus)
import System.Posix.Process (executeFile, forkProcess, getProcessStatus, createSession)
import System.Exit
import System.Environment
import Graphics.X11.Xlib
-- for Read instance
import Graphics.X11.Xlib.Extras ()
import Data.Typeable
import qualified Data.Map as M
import qualified Data.Set as S
-- | XState, the window manager state.
-- Just the display, width, height and a window list
data XState = XState
{ display :: Display -- ^ the X11 display
{ windowset :: !WindowSet -- ^ workspace list
, mapped :: !(S.Set Window) -- ^ the Set of mapped windows
, waitingUnmap :: !(M.Map Window Int) -- ^ the number of expected UnmapEvents
, layouts :: !(M.Map WorkspaceId (Layout Window, [Layout Window]))
-- ^ mapping of workspaces to descriptions of their layouts
, dragging :: !(Maybe (Position -> Position -> X (), X ())) }
data XConf = XConf
{ display :: Display -- ^ the X11 display
, theRoot :: !Window -- ^ the root window
, normalBorder :: !Pixel -- ^ border color of unfocused windows
, focusedBorder :: !Pixel } -- ^ border color of the focused window
, 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
type WindowSet = StackSet WorkspaceId Window ScreenId ScreenDetail
-- | Virtual workspace indicies
newtype WorkspaceId = W Int deriving (Eq,Ord,Show,Enum,Num,Integral,Real)
newtype WorkspaceId = W Int deriving (Eq,Ord,Show,Read,Enum,Num,Integral,Real)
-- | Physical screen indicies
newtype ScreenId = S Int deriving (Eq,Ord,Show,Enum,Num,Integral,Real)
newtype ScreenId = S Int deriving (Eq,Ord,Show,Read,Enum,Num,Integral,Real)
data ScreenDetail = SD { screenRect :: !Rectangle
, statusGap :: !(Int,Int,Int,Int) -- ^ width of status bar on the screen
} deriving (Eq,Show, Read)
------------------------------------------------------------------------
-- | The X monad, a StateT transformer over IO encapsulating the window
-- manager state
newtype X a = X (StateT XState IO a)
deriving (Functor, Monad, MonadIO, MonadState XState)
--
-- Dynamic components may be retrieved with 'get', static components
-- with 'ask'. With newtype deriving we get readers and state monads
-- instantiated on XConf and XState automatically.
--
newtype X a = X (ReaderT XConf (StateT XState IO) a)
deriving (Functor, Monad, MonadIO, MonadState XState, MonadReader XConf)
-- | Run the X monad, given a chunk of X monad code, and an initial state
-- Return the result, and final state
runX :: XState -> X a -> IO ()
runX st (X a) = runStateT a st >> return ()
runX :: XConf -> XState -> X a -> IO ()
runX c st (X a) = runStateT (runReaderT a c) st >> return ()
-- | 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 (X job) (X errcase) = do
st <- get
c <- ask
(a,s') <- io ((runStateT (runReaderT job c) st) `catch`
\e -> (do hPutStrLn stderr (show e); runStateT (runReaderT errcase c) st))
put s'
return a
-- ---------------------------------------------------------------------
-- Convenient wrappers to state
-- | Run a monad action with the current display settings
withDisplay :: (Display -> X ()) -> X ()
withDisplay f = gets display >>= f
withDisplay :: (Display -> X a) -> X a
withDisplay f = asks display >>= f
-- | Run a monadic action with the current stack set
withWindowSet :: (WindowSet -> X a) -> X a
withWindowSet f = gets windowset >>= f
-- | True if the given window is the root window
isRoot :: Window -> X Bool
isRoot w = liftM (w==) (gets theRoot)
isRoot w = liftM (w==) (asks theRoot)
-- | Wrapper for the common case of atom internment
getAtom :: String -> X Atom
getAtom str = withDisplay $ \dpy -> io $ internAtom dpy str False
-- | Common non-predefined atoms
atom_WM_PROTOCOLS, atom_WM_DELETE_WINDOW, atom_WM_STATE :: X Atom
atom_WM_PROTOCOLS = getAtom "WM_PROTOCOLS"
atom_WM_DELETE_WINDOW = getAtom "WM_DELETE_WINDOW"
atom_WM_STATE = getAtom "WM_STATE"
------------------------------------------------------------------------
-- Layout handling
-- | Layout handling
-- | The different layout modes
data Layout = Full | Tall | Wide deriving (Enum, Bounded, Eq)
-- The different layout modes
-- 'doLayout': given a Rectangle and a Stack, layout the stack elements
-- inside the given Rectangle. If an element is not given a Rectangle
-- by 'doLayout', then it is not shown on screen. Windows are restacked
-- according to the order they are returned by 'doLayout'.
--
-- 'modifyLayout' performs message handling for that layout. If
-- 'modifyLayout' returns Nothing, then the layout did not respond to
-- that message and the screen is not refreshed. Otherwise, 'modifyLayout'
-- returns an updated 'Layout' and the screen is refreshed.
--
data Layout a = Layout { doLayout :: Rectangle -> Stack a -> X ([(a, Rectangle)], Maybe (Layout a))
, modifyLayout :: SomeMessage -> X (Maybe (Layout a)) }
-- | 'rot' for Layout.
rotateLayout :: Layout -> Layout
rotateLayout x = if x == maxBound then minBound else succ x
runLayout :: Layout a -> Rectangle -> StackOrNot a -> X ([(a, Rectangle)], Maybe (Layout a))
runLayout l r = maybe (return ([], Nothing)) (doLayout l r)
-- | A full description of a particular workspace's layout parameters.
data LayoutDesc = LayoutDesc { layoutType :: !Layout
, tileFraction :: !Rational
}
-- | Based on ideas in /An Extensible Dynamically-Typed Hierarchy of Exceptions/,
-- Simon Marlow, 2006. Use extensible messages to the modifyLayout 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
-- ---------------------------------------------------------------------
-- Utilities
-- | Lift an IO action into the X monad
-- | General utilities
--
-- Lift an IO action into the X monad
io :: IO a -> X a
io = liftIO
{-# INLINE io #-}
-- | Lift an IO action into the X monad. If the action results in an IO
-- exception, log the exception to stderr and continue normal execution.
catchIO :: IO () -> X ()
catchIO f = liftIO (f `catch` \e -> do hPutStrLn stderr (show e); hFlush stderr)
-- | spawn. Launch an external application
spawn :: String -> X ()
spawn x = io $ do
pid <- forkProcess $ do
forkProcess (executeFile "/bin/sh" False ["-c", x] Nothing)
forkProcess (createSession >> executeFile "/bin/sh" False ["-c", x] Nothing)
exitWith ExitSuccess
return ()
getProcessStatus True False pid
return ()
-- | Restart xmonad via exec().
--
-- If the first parameter is 'Just name', restart will attempt to execute the
-- program corresponding to 'name'. Otherwise, xmonad will attempt to execute
-- the name of the current program.
--
-- When the second parameter is 'True', xmonad will attempt to resume with the
-- current window state.
restart :: Maybe String -> Bool -> X ()
restart mprog resume = do
prog <- maybe (io getProgName) return mprog
args <- if resume then gets (("--resume":) . return . show . windowset) else return []
catchIO (executeFile prog True args Nothing)
-- | Run a side effecting action with the current workspace. Like 'when' but
whenJust :: Maybe a -> (a -> X ()) -> X ()
whenJust mg f = maybe (return ()) f mg
-- | Conditionally run an action, using a X event to decide
whenX :: X Bool -> X () -> X ()
whenX a f = a >>= \b -> when b f
-- 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
-- | A 'trace' for the X monad. Logs a string to stderr. The result may
-- be found in your .xsession-errors file
trace :: String -> X ()

49
man/xmonad.1.in Normal file
View File

@@ -0,0 +1,49 @@
./" man page created by David Lazar on April 24, 2007
./" uses ``tmac.an'' macro set
.TH xmonad 1 "18 April 07" xmonad\-1.0 "xmonad manual"
.SH NAME
xmonad \- a tiling window manager
.SH DESCRIPTION
.PP
\fBxmonad\fR is a minimalist tiling window manager for X, written in Haskell. Windows are managed using automatic layout algorithms, which can be dynamically reconfigured. At any time windows are arranged so as to maximise the use of screen real estate. All features of the window manager are accessible purely from the keyboard: a mouse is entirely optional. \fBxmonad\fR is configured in Haskell, and custom layout algorithms may be implemented by the user in config files. A principle of \fBxmonad\fR is predictability: the user should know in advance precisely the window arrangement that will result from any action.
.PP
By default, \fBxmonad\fR provides three layout algorithms: tall, wide and fullscreen. In tall or wide mode, windows are tiled and arranged to prevent overlap and maximise screen use. Sets of windows are grouped together on virtual screens, and each screen retains its own layout, which may be reconfigured dynamically. Multiple physical monitors are supported via Xinerama, allowing simultaneous display of a number of screens.
.PP
By utilising the expressivity of a modern functional language with a rich static type system, \fBxmonad\fR provides a complete, featureful window manager in less than 500 lines of code, with an emphasis on correctness and robustness. Internal properties of the window manager are checked using a combination of static guarantees provided by the type system, and type-based automated testing. A benefit of this is that the code is simple to understand, and easy to modify.
.SH USAGE
.PP
\fBxmonad\fR places each window into a "workspace". Each workspace can have any number of windows, which you can cycle though with mod-j and mod-k. Windows are either displayed full screen, tiled horizontally, or tiled vertically. You can toggle the layout mode with mod-space, which will cycle through the available modes.
.PP
You can switch to workspace N with mod-N. For example, to switch to workspace 5, you would press mod-5. Similarly, you can move the current window to another workspace with mod-shift-N.
.PP
When running with multiple monitors (Xinerama), each screen has exactly 1 workspace visible. When \fBxmonad\fR starts, workspace 1 is on screen 1, workspace 2 is on screen 2, etc. If you switch to a workspace which is currently visible on another screen, \fBxmonad\fR simply switches focus to that screen. If you switch to a workspace which is *not* visible, \fBxmonad\fR replaces the workspace on the *current* screen with the workspace you selected.
.PP
For example, if you have the following configuration:
.RS
.PP
Screen 1: Workspace 2
.PP
Screen 2: Workspace 5 (current workspace)
.RE
.PP
and you wanted to view workspace 7 on screen 1, you would press:
.RS
.PP
mod-2 (to select workspace 2, and make screen 1 the current screen)
.PP
mod-7 (to select workspace 7)
.RE
.PP
Since switching to the workspace currently visible on a given screen is such a common operation, shortcuts are provided: mod-{w,e,r} switch to the workspace currently visible on screens 1, 2, and 3 respectively. Likewise, shift-mod-{w,e,r} moves the current window to the workspace on that screen. Using these keys, the above example would become mod-w mod-7.
.SS Default keyboard bindings
___KEYBINDINGS___
.SH EXAMPLES
To use \fBxmonad\fR as your window manager add:
.RS
xmonad
.RE
to your \fI~/.xinitrc\fR file
.SH CUSTOMIZATION
\fBxmonad\fR is customized by creating a custom Config.hs and (re)compiling the source code. After recompiling, 'restart' is used to fork the new version, with changes reflected immediately.
.SH BUGS
Probably. If you find any, please report them: http://code.google.com/p/xmonad/issues/list

View File

@@ -1,7 +1,8 @@
{-# OPTIONS -fglasgow-exts #-}
import StackSet
import Operations (tile,vtile)
import StackSet hiding (filter)
import qualified StackSet as S (filter)
import Operations (tile)
import Debug.Trace
import Data.Word
@@ -13,157 +14,494 @@ import Control.Exception (assert)
import Control.Monad
import Test.QuickCheck hiding (promote)
import System.IO
import System.Random
import System.Random hiding (next)
import Text.Printf
import Data.List (nub,sort,group,sort,intersperse)
import Data.List (nub,sort,sortBy,group,sort,intersperse,genericLength)
import qualified Data.List as L
import Data.Char (ord)
import Data.Map (keys,elems)
import qualified Data.Map as M
-- ---------------------------------------------------------------------
-- QuickCheck properties for the StackSet
-- | Height of stack 'n'
height :: Int -> T -> Int
height i w = length (index i w)
-- Some general hints for creating StackSet properties:
--
-- * ops that mutate the StackSet are usually local
-- * most ops on StackSet should either be trivially reversible, or
-- idempotent, or both.
-- build (non-empty) StackSets with between 1 and 100 stacks
instance (Integral i, Integral j, Ord a, Arbitrary a) => Arbitrary (StackSet i j a) where
--
-- The all important Arbitrary instance for StackSet.
--
instance (Integral i, Integral s, Eq a, Arbitrary a, Arbitrary sd)
=> Arbitrary (StackSet i a s sd) where
arbitrary = do
sz <- choose (1,20)
n <- choose (0,sz-1)
sc <- choose (1,sz)
ls <- vector sz
return $ fromList (fromIntegral n,sc,ls)
sz <- choose (1,10) -- number of workspaces
n <- choose (0,sz-1) -- pick one to be in focus
sc <- choose (1,sz) -- a number of physical screens
sds <- replicateM sc arbitrary
ls <- vector sz -- a vector of sz workspaces
-- pick a random item in each stack to focus
fs <- sequence [ if null s then return Nothing
else liftM Just (choose ((-1),length s-1))
| s <- ls ]
return $ fromList (fromIntegral n, sds,fs,ls)
coarbitrary = error "no coarbitrary for StackSet"
prop_id x = fromList (toList x) == x
where _ = x :: T
prop_member1 i n m = member i (push i x)
where x = empty n m :: T
-- | 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.
--
-- 'o' random workspace
-- 'm' number of physical screens
-- 'fs' random focused window on each workspace
-- 'xs' list of list of windows
--
fromList :: (Integral i, Integral s, Eq a) => (i, [sd], [Maybe Int], [[a]]) -> StackSet i a s sd
fromList (_,_,_,[]) = error "Cannot build a StackSet from an empty list"
prop_member2 i x = not (member i (delete i x))
where _ = x :: T
fromList (o,m,fs,xs) =
let s = view o $
foldr (\(i,ys) s ->
foldr insertUp (view i s) ys)
(new [0..genericLength xs-1] m) (zip [0..] xs)
in foldr (\f t -> case f of
Nothing -> t
Just i -> foldr (const focusUp) t [0..i] ) s fs
prop_member3 i n m = member i (empty n m :: T) == False
------------------------------------------------------------------------
prop_sizepush is n m = n > 0 ==> size (foldr push x is ) == n
where x = empty n m :: T
--
-- Just generate StackSets with Char elements.
--
type T = StackSet (NonNegative Int) Char Int Int
prop_currentpush is n m = n > 0 ==>
height (current x) (foldr push x js) == length js
-- Useful operation, the non-local workspaces
hidden_spaces x = map workspace (visible x) ++ hidden x
-- Basic data invariants of the StackSet
--
-- With the new zipper-based StackSet, tracking focus is no longer an
-- issue: the data structure enforces focus by construction.
--
-- But we still need to ensure there are no duplicates, and master/and
-- the xinerama mapping aren't checked by the data structure at all.
--
-- * no element should ever appear more than once in a StackSet
-- * the xinerama screen map should be:
-- -- keys should always index valid workspaces
-- -- monotonically ascending in the elements
-- * the current workspace should be a member of the xinerama screens
--
invariant (s :: T) = and
-- no duplicates
[ noDuplicates
-- all this xinerama stuff says we don't have the right structure
-- , validScreens
-- , validWorkspaces
-- , inBounds
]
where
ws = concat [ focus t : up t ++ down t
| w <- workspace (current s) : map workspace (visible s) ++ hidden s
, t <- maybeToList (stack w)] :: [Char]
noDuplicates = nub ws == ws
-- validScreens = monotonic . sort . M. . (W.current s : W.visible : W$ s
-- validWorkspaces = and [ w `elem` allworkspaces | w <- (M.keys . screens) s ]
-- where allworkspaces = map tag $ current s : prev s ++ next s
-- inBounds = and [ w >=0 && w < size s | (w,sc) <- M.assocs (screens s) ]
monotonic [] = True
monotonic (x:[]) = True
monotonic (x:y:zs) | x == y-1 = monotonic (y:zs)
| otherwise = False
prop_invariant = invariant
-- and check other ops preserve invariants
prop_empty_I (n :: Positive Int) = forAll (choose (1,fromIntegral n)) $ \m ->
forAll (vector m) $ \ms ->
invariant $ new [0..fromIntegral n-1] ms
prop_view_I (n :: NonNegative Int) (x :: T) =
n `tagMember` x ==> invariant $ view (fromIntegral n) x
prop_greedyView_I (n :: NonNegative Int) (x :: T) =
n `tagMember` x ==> invariant $ view (fromIntegral n) x
prop_focusUp_I (n :: NonNegative Int) (x :: T) =
invariant $ foldr (const focusUp) x [1..n]
prop_focusDown_I (n :: NonNegative Int) (x :: T) =
invariant $ foldr (const focusDown) x [1..n]
prop_focus_I (n :: NonNegative Int) (x :: T) =
case peek x of
Nothing -> True
Just _ -> let w = focus . fromJust . stack . workspace . current $ foldr (const focusUp) x [1..n]
in invariant $ focusWindow w x
prop_insertUp_I n (x :: T) = invariant $ insertUp n x
prop_delete_I (x :: T) = invariant $
case peek x of
Nothing -> x
Just i -> delete i x
prop_swap_master_I (x :: T) = invariant $ swapMaster x
prop_swap_left_I (n :: NonNegative Int) (x :: T) =
invariant $ foldr (const swapUp ) x [1..n]
prop_swap_right_I (n :: NonNegative Int) (x :: T) =
invariant $ foldr (const swapDown) x [1..n]
prop_shift_I (n :: NonNegative Int) (x :: T) =
n `tagMember` x ==> invariant $ shift (fromIntegral n) x
-- ---------------------------------------------------------------------
-- 'new'
-- empty StackSets have no windows in them
prop_empty (EmptyStackSet x) =
all (== Nothing) [ stack w | w <- workspace (current x)
: map workspace (visible x) ++ hidden x ]
-- empty StackSets always have focus on first workspace
prop_empty_current (NonEmptyNubList ns) (NonEmptyNubList sds) =
-- TODO, this is ugly
length sds <= length ns ==>
tag (workspace $ current x) == head ns
where x = new ns sds :: T
-- no windows will be a member of an empty workspace
prop_member_empty i (EmptyStackSet x)
= member i x == False
-- ---------------------------------------------------------------------
-- viewing workspaces
-- view sets the current workspace to 'n'
prop_view_current (x :: T) (n :: NonNegative Int) = i `tagMember` x ==>
tag (workspace $ current (view i x)) == i
where
i = fromIntegral n
-- view *only* sets the current workspace, and touches Xinerama.
-- no workspace contents will be changed.
prop_view_local (x :: T) (n :: NonNegative Int) = i `tagMember` x ==>
workspaces x == workspaces (view i x)
where
workspaces a = sortBy (\s t -> tag s `compare` tag t) $
workspace (current a)
: map workspace (visible a) ++ hidden a
i = fromIntegral n
-- view should result in a visible xinerama screen
-- prop_view_xinerama (x :: T) (n :: NonNegative Int) = i `tagMember` x ==>
-- M.member i (screens (view i x))
-- where
-- i = fromIntegral n
-- view is idempotent
prop_view_idem (x :: T) (i :: NonNegative Int) = i `tagMember` x ==> view i (view i x) == (view i x)
-- view is reversible, though shuffles the order of hidden/visible
prop_view_reversible (i :: NonNegative Int) (x :: T) =
i `tagMember` x ==> normal (view n (view i x)) == normal x
where n = tag (workspace $ current x)
-- ---------------------------------------------------------------------
-- greedyViewing workspaces
-- greedyView sets the current workspace to 'n'
prop_greedyView_current (x :: T) (n :: NonNegative Int) = i `tagMember` x ==>
tag (workspace $ current (greedyView i x)) == i
where
i = fromIntegral n
-- greedyView *only* sets the current workspace, and touches Xinerama.
-- no workspace contents will be changed.
prop_greedyView_local (x :: T) (n :: NonNegative Int) = i `tagMember` x ==>
workspaces x == workspaces (greedyView i x)
where
workspaces a = sortBy (\s t -> tag s `compare` tag t) $
workspace (current a)
: map workspace (visible a) ++ hidden a
i = fromIntegral n
-- greedyView is idempotent
prop_greedyView_idem (x :: T) (i :: NonNegative Int) = i `tagMember` x ==> greedyView i (greedyView i x) == (greedyView i x)
-- greedyView is reversible, though shuffles the order of hidden/visible
prop_greedyView_reversible (i :: NonNegative Int) (x :: T) =
i `tagMember` x ==> normal (greedyView n (greedyView i x)) == normal x
where n = tag (workspace $ current x)
-- normalise workspace list
normal s = s { hidden = sortBy g (hidden s), visible = sortBy f (visible s) }
where
js = nub is
x = empty n m :: T
f = \a b -> tag (workspace a) `compare` tag (workspace b)
g = \a b -> tag a `compare` tag b
prop_pushpeek x is = not (null is) ==> fromJust (peek (foldr push x is)) == head is
-- ---------------------------------------------------------------------
-- Xinerama
-- every screen should yield a valid workspace
-- prop_lookupWorkspace (n :: NonNegative Int) (x :: T) =
-- s < M.size (screens x) ==>
-- fromJust (lookupWorkspace s x) `elem` (map tag $ current x : prev x ++ next x)
-- where
-- s = fromIntegral n
-- ---------------------------------------------------------------------
-- peek/index
-- peek either yields nothing on the Empty workspace, or Just a valid window
prop_member_peek (x :: T) =
case peek x of
Nothing -> True {- then we don't know anything -}
Just i -> member i x
-- ---------------------------------------------------------------------
-- index
-- the list returned by index should be the same length as the actual
-- windows kept in the zipper
prop_index_length (x :: T) =
case stack . workspace . current $ x of
Nothing -> length (index x) == 0
Just it -> length (index x) == length (focus it : up it ++ down it)
-- ---------------------------------------------------------------------
-- rotating focus
--
-- master/focus
--
-- The tiling order, and master window, of a stack is unaffected by focus changes.
--
prop_focus_left_master (n :: NonNegative Int) (x::T) =
index (foldr (const focusUp) x [1..n]) == index x
prop_focus_right_master (n :: NonNegative Int) (x::T) =
index (foldr (const focusDown) x [1..n]) == index x
prop_focusWindow_master (n :: NonNegative Int) (x :: T) =
case peek x of
Nothing -> True
Just _ -> let s = index x
i = fromIntegral n `mod` length s
in index (focusWindow (s !! i) x) == index x
-- shifting focus is trivially reversible
prop_focus_left (x :: T) = (focusUp (focusDown x)) == x
prop_focus_right (x :: T) = (focusDown (focusUp x)) == x
-- focusWindow actually leaves the window focused...
prop_focusWindow_works (n :: NonNegative Int) (x :: T) =
case peek x of
Nothing -> True
Just _ -> let s = index x
i = fromIntegral n `mod` length s
in (focus . fromJust . stack . workspace . current) (focusWindow (s !! i) x) == (s !! i)
-- rotation through the height of a stack gets us back to the start
prop_focus_all_l (x :: T) = (foldr (const focusUp) x [1..n]) == x
where n = length (index x)
prop_focus_all_r (x :: T) = (foldr (const focusDown) x [1..n]) == x
where n = length (index x)
-- prop_rotate_all (x :: T) = f (f x) == f x
-- f x' = foldr (\_ y -> rotate GT y) x' [1..n]
-- focus is local to the current workspace
prop_focus_local (x :: T) = hidden_spaces (focusDown x) == hidden_spaces x
prop_focusWindow_local (n :: NonNegative Int) (x::T ) =
case peek x of
Nothing -> True
Just _ -> let s = index x
i = fromIntegral n `mod` length s
in hidden_spaces (focusWindow (s !! i) x) == hidden_spaces x
-- ---------------------------------------------------------------------
-- member/findIndex
--
-- For all windows in the stackSet, findIndex should identify the
-- correct workspace
--
prop_findIndex (x :: T) =
and [ tag w == fromJust (findIndex i x)
| w <- workspace (current x) : map workspace (visible x) ++ hidden x
, t <- maybeToList (stack w)
, i <- focus t : up t ++ down t
]
-- ---------------------------------------------------------------------
-- 'insert'
-- inserting a item into an empty stackset means that item is now a member
prop_insert_empty i (EmptyStackSet x)= member i (insertUp i x)
-- insert should be idempotent
prop_insert_idem i (x :: T) = insertUp i x == insertUp i (insertUp i x)
-- insert when an item is a member should leave the stackset unchanged
prop_insert_duplicate i (x :: T) = member i x ==> insertUp i x == x
-- push shouldn't change anything but the current workspace
prop_insert_local (x :: T) i = not (member i x) ==> hidden_spaces x == hidden_spaces (insertUp i x)
-- Inserting a (unique) list of items into an empty stackset should
-- result in the last inserted element having focus.
prop_insert_peek (EmptyStackSet x) (NonEmptyNubList is) =
peek (foldr insertUp x is) == Just (head is)
-- insert >> delete is the identity, when i `notElem` .
-- Except for the 'master', which is reset on insert and delete.
--
prop_insert_delete n x = not (member n x) ==> delete n (insertUp n y) == (y :: T)
where
y = swapMaster x -- sets the master window to the current focus.
-- otherwise, we don't have a rule for where master goes.
-- inserting n elements increases current stack size by n
prop_size_insert is (EmptyStackSet x) =
size (foldr insertUp x ws ) == (length ws)
where
ws = nub is
size = length . index
-- ---------------------------------------------------------------------
-- 'delete'
-- deleting the current item removes it.
prop_delete x =
case peek x of
Nothing -> True
Just i -> not (member i (delete i x))
where _ = x :: T
prop_peekmember x = case peek x of
Just w -> member w x
Nothing -> True {- then we don't know anything -}
where _ = x :: T
-- delete is reversible with 'insert'.
-- It is the identiy, except for the 'master', which is reset on insert and delete.
--
prop_delete_insert (x :: T) =
case peek x of
Nothing -> True
Just n -> insertUp n (delete n y) == y
where
y = swapMaster x
prop_peek_peekStack n x =
if current x == n then peekStack n x == peek x
else True -- so we don't exhaust
where _ = x :: T
-- delete should be local
prop_delete_local (x :: T) =
case peek x of
Nothing -> True
Just i -> hidden_spaces x == hidden_spaces (delete i x)
prop_notpeek_peekStack n x = current x /= n && isJust (peek x) ==> peekStack n x /= peek x
where _ = x :: T
-- delete should not affect focus unless the focused element is what is being deleted
prop_delete_focus n (x :: T) = member n x && Just n /= peek x ==> peek (delete n x) == peek x
------------------------------------------------------------------------
-- focus movement in the presence of delete:
-- when the last window in the stack set is focused, focus moves `up'.
-- usual case is that it moves 'down'.
prop_delete_focus_end (x :: T) =
length (index x) > 1
==>
peek (delete n y) == peek (focusUp y)
where
n = last (index x)
y = focusWindow n x -- focus last window in stack
type T = StackSet Int Int Int
-- focus movement in the presence of delete:
-- when not in the last item in the stack, focus moves down
prop_delete_focus_not_end (x :: T) =
length (index x) > 1 &&
n /= last (index x)
==>
peek (delete n x) == peek (focusDown x)
where
Just n = peek x
prop_delete_uniq i x = not (member i x) ==> delete i x == x
where _ = x :: T
-- ---------------------------------------------------------------------
-- filter
prop_delete_push i x = not (member i x) ==> delete i (push i x) == x
where _ = x :: T
-- preserve order
prop_filter_order (x :: T) =
case stack $ workspace $ current x of
Nothing -> True
Just s@(Stack i _ _) -> integrate' (S.filter (/= i) s) == filter (/= i) (integrate' (Just s))
prop_delete2 i x =
delete i x == delete i (delete i x)
where _ = x :: T
-- ---------------------------------------------------------------------
-- swapUp, swapDown, swapMaster: reordiring windows
prop_focus1 i x = member i x ==> peek (raiseFocus i x) == Just i
where _ = x :: T
-- swap is trivially reversible
prop_swap_left (x :: T) = (swapUp (swapDown x)) == x
prop_swap_right (x :: T) = (swapDown (swapUp x)) == x
-- TODO swap is reversible
-- swap is reversible, but involves moving focus back the window with
-- master on it. easy to do with a mouse...
{-
prop_promote_reversible x b = (not . null . fromMaybe [] . flip index x . current $ x) ==>
(raiseFocus y . promote . raiseFocus z . promote) x == x
where _ = x :: T
dir = if b then LT else GT
(Just y) = peek x
(Just (z:_)) = flip index x . current $ x
-}
prop_rotaterotate x = rotate LT (rotate GT x) == x
where _ = x :: T
-- swap doesn't change focus
prop_swap_master_focus (x :: T) = peek x == (peek $ swapMaster x)
-- = case peek x of
-- Nothing -> True
-- Just f -> focus (stack (workspace $ current (swap x))) == f
prop_swap_left_focus (x :: T) = peek x == (peek $ swapUp x)
prop_swap_right_focus (x :: T) = peek x == (peek $ swapDown x)
prop_viewview r x =
let n = current x
sz = size x
i = r `mod` sz
in view n (view (fromIntegral i) x) == x
-- swap is local
prop_swap_master_local (x :: T) = hidden_spaces x == hidden_spaces (swapMaster x)
prop_swap_left_local (x :: T) = hidden_spaces x == hidden_spaces (swapUp x)
prop_swap_right_local (x :: T) = hidden_spaces x == hidden_spaces (swapDown x)
where _ = x :: T
-- rotation through the height of a stack gets us back to the start
prop_swap_all_l (x :: T) = (foldr (const swapUp) x [1..n]) == x
where n = length (index x)
prop_swap_all_r (x :: T) = (foldr (const swapDown) x [1..n]) == x
where n = length (index x)
prop_shiftshift r x =
let n = current x
in shift n (shift r x) == x
where _ = x :: T
prop_swap_master_idempotent (x :: T) = swapMaster (swapMaster x) == swapMaster x
prop_fullcache x = cached == allvals where
cached = sort . keys $ cache x
allvals = sort . concat . elems $ stacks x
_ = x :: T
-- ---------------------------------------------------------------------
-- shift
prop_currentwsvisible x = (current x) `elem` (visibleWorkspaces x)
where _ = x :: T
prop_ws2screen_screen2ws x = (ws == ws') && (sc == sc')
where ws = sort . keys $ ws2screen x
ws' = sort . elems $ screen2ws x
sc = sort . keys $ screen2ws x
sc' = sort . elems $ ws2screen x
_ = x :: T
prop_screenworkspace x = all test [0..((fromIntegral $ size x)-1)]
where test ws = case screen ws x of
Nothing -> True
Just sc -> workspace sc x == Just ws
_ = x :: T
prop_swap a b xs = swap a b (swap a b ys) == ys
where ys = nub xs :: [Int]
------------------------------------------------------------------------
-- promote is idempotent
prop_promote2 x = promote (promote x) == (promote x)
where _ = x :: T
-- focus doesn't change
prop_promotefocus x = focus (promote x) == focus x
where _ = x :: T
-- screen certainly should't change
prop_promotecurrent x = current (promote x) == current x
where _ = x :: T
-- the physical screen doesn't change
prop_promotescreen n x = screen n (promote x) == screen n x
where _ = x :: T
-- promote doesn't mess with other windows
prop_promoterotate x b = focus (rotate dir (promote x)) == focus (rotate dir x)
where _ = x :: T
dir = if b then LT else GT
-- shift is fully reversible on current window, when focus and master
-- are the same. otherwise, master may move.
prop_shift_reversible i (x :: T) =
i `tagMember` x ==> case peek y of
Nothing -> True
Just _ -> normal ((view n . shift n . view i . shift i) y) == normal y
where
y = swapMaster x
n = tag (workspace $ current y)
------------------------------------------------------------------------
-- some properties for layouts:
-- 1 window should always be tiled fullscreen
prop_tile_fullscreen rect = tile pct rect [1] == [(1, rect)]
prop_vtile_fullscreen rect = vtile pct rect [1] == [(1, rect)]
{-
prop_tile_fullscreen rect = tile pct rect 1 1 == [rect]
-- multiple windows
prop_tile_non_overlap rect windows = noOverlaps (tile pct rect windows)
where _ = rect :: Rectangle
prop_vtile_non_overlap rect windows = noOverlaps (vtile pct rect windows)
prop_tile_non_overlap rect windows nmaster = noOverlaps (tile pct rect nmaster windows)
where _ = rect :: Rectangle
pct = 3 % 100
@@ -171,8 +509,8 @@ pct = 3 % 100
noOverlaps [] = True
noOverlaps [_] = True
noOverlaps xs = and [ verts a `notOverlap` verts b
| (_,a) <- xs
, (_,b) <- filter (\(_,b) -> a /= b) xs
| a <- xs
, b <- filter (a /=) xs
]
where
verts (Rectangle a b w h) = (a,b,a + fromIntegral w - 1, b + fromIntegral h - 1)
@@ -182,9 +520,174 @@ noOverlaps xs = and [ verts a `notOverlap` verts b
= (top1 < bottom2 || top2 < bottom1)
|| (right1 < left2 || right2 < left1)
-}
------------------------------------------------------------------------
main :: IO ()
main = do
args <- getArgs
let n = if null args then 100 else read (head args)
(results, passed) <- liftM unzip $ mapM (\(s,a) -> printf "%-25s: " s >> a n) tests
printf "Passed %d tests!\n" (sum passed)
when (not . and $ results) $ fail "Not all tests passed!"
where
tests =
[("StackSet invariants" , mytest prop_invariant)
,("empty: invariant" , mytest prop_empty_I)
,("empty is empty" , mytest prop_empty)
,("empty / current" , mytest prop_empty_current)
,("empty / member" , mytest prop_member_empty)
,("view : invariant" , mytest prop_view_I)
,("view sets current" , mytest prop_view_current)
,("view idempotent" , mytest prop_view_idem)
,("view reversible" , mytest prop_view_reversible)
-- ,("view / xinerama" , mytest prop_view_xinerama)
,("view is local" , mytest prop_view_local)
,("greedyView : invariant" , mytest prop_greedyView_I)
,("greedyView sets current" , mytest prop_greedyView_current)
,("greedyView idempotent" , mytest prop_greedyView_idem)
,("greedyView reversible" , mytest prop_greedyView_reversible)
,("greedyView is local" , mytest prop_greedyView_local)
--
-- ,("valid workspace xinerama", mytest prop_lookupWorkspace)
,("peek/member " , mytest prop_member_peek)
,("index/length" , mytest prop_index_length)
,("focus left : invariant", mytest prop_focusUp_I)
,("focus right: invariant", mytest prop_focusDown_I)
,("focusWindow: invariant", mytest prop_focus_I)
,("focus left/master" , mytest prop_focus_left_master)
,("focus right/master" , mytest prop_focus_right_master)
,("focusWindow master" , mytest prop_focusWindow_master)
,("focus left/right" , mytest prop_focus_left)
,("focus right/left" , mytest prop_focus_right)
,("focus all left " , mytest prop_focus_all_l)
,("focus all right " , mytest prop_focus_all_r)
,("focus is local" , mytest prop_focus_local)
,("focusWindow is local", mytest prop_focusWindow_local)
,("focusWindow works" , mytest prop_focusWindow_works)
,("findIndex" , mytest prop_findIndex)
,("insert: invariant" , mytest prop_insertUp_I)
,("insert/new" , mytest prop_insert_empty)
,("insert is idempotent", mytest prop_insert_idem)
,("insert is reversible", mytest prop_insert_delete)
,("insert is local" , mytest prop_insert_local)
,("insert duplicates" , mytest prop_insert_duplicate)
,("insert/peek " , mytest prop_insert_peek)
,("insert/size" , mytest prop_size_insert)
,("delete: invariant" , mytest prop_delete_I)
,("delete/empty" , mytest prop_empty)
,("delete/member" , mytest prop_delete)
,("delete is reversible", mytest prop_delete_insert)
,("delete is local" , mytest prop_delete_local)
,("delete/focus" , mytest prop_delete_focus)
,("delete last/focus up", mytest prop_delete_focus_end)
,("delete ~last/focus down", mytest prop_delete_focus_not_end)
,("filter preserves order", mytest prop_filter_order)
,("swapMaster: invariant", mytest prop_swap_master_I)
,("swapUp: invariant" , mytest prop_swap_left_I)
,("swapDown: invariant", mytest prop_swap_right_I)
,("swapMaster id on focus", mytest prop_swap_master_focus)
,("swapUp id on focus", mytest prop_swap_left_focus)
,("swapDown id on focus", mytest prop_swap_right_focus)
,("swapMaster is idempotent", mytest prop_swap_master_idempotent)
,("swap all left " , mytest prop_swap_all_l)
,("swap all right " , mytest prop_swap_all_r)
,("swapMaster is local" , mytest prop_swap_master_local)
,("swapUp is local" , mytest prop_swap_left_local)
,("swapDown is local" , mytest prop_swap_right_local)
,("shift: invariant" , mytest prop_shift_I)
,("shift is reversible" , mytest prop_shift_reversible)
{-
,("tile 1 window fullsize", mytest prop_tile_fullscreen)
,("tiles never overlap", mytest prop_tile_non_overlap)
-}
]
------------------------------------------------------------------------
--
-- QC driver
--
debug = False
mytest :: Testable a => a -> Int -> IO (Bool, Int)
mytest a n = mycheck defaultConfig
{ configMaxTest=n
, configEvery = \n args -> let s = show n in s ++ [ '\b' | _ <- s ] } a
-- , configEvery= \n args -> if debug then show n ++ ":\n" ++ unlines args else [] } a
mycheck :: Testable a => Config -> a -> IO (Bool, Int)
mycheck config a = do
rnd <- newStdGen
mytests config (evaluate a) rnd 0 0 []
mytests :: Config -> Gen Result -> StdGen -> Int -> Int -> [[String]] -> IO (Bool, Int)
mytests config gen rnd0 ntest nfail stamps
| ntest == configMaxTest config = done "OK," ntest stamps >> return (True, ntest)
| nfail == configMaxFail config = done "Arguments exhausted after" ntest stamps >> return (True, ntest)
| otherwise =
do putStr (configEvery config ntest (arguments result)) >> hFlush stdout
case ok result of
Nothing ->
mytests config gen rnd1 ntest (nfail+1) stamps
Just True ->
mytests config gen rnd1 (ntest+1) nfail (stamp result:stamps)
Just False ->
putStr ( "Falsifiable after "
++ show ntest
++ " tests:\n"
++ unlines (arguments result)
) >> hFlush stdout >> return (False, ntest)
where
result = generate (configSize config ntest) rnd2 gen
(rnd1,rnd2) = split rnd0
done :: String -> Int -> [[String]] -> IO ()
done mesg ntest stamps = putStr ( mesg ++ " " ++ show ntest ++ " tests" ++ table )
where
table = display
. map entry
. reverse
. sort
. map pairLength
. group
. sort
. filter (not . null)
$ stamps
display [] = ".\n"
display [x] = " (" ++ x ++ ").\n"
display xs = ".\n" ++ unlines (map (++ ".") xs)
pairLength xss@(xs:_) = (length xss, xs)
entry (n, xs) = percentage n ntest
++ " "
++ concat (intersperse ", " xs)
percentage n m = show ((100 * n) `div` m) ++ "%"
------------------------------------------------------------------------
instance Arbitrary Char where
arbitrary = choose ('a','z')
coarbitrary n = coarbitrary (ord n)
instance Random Word8 where
randomR = integralRandomR
random = randomR (minBound,maxBound)
@@ -223,6 +726,7 @@ instance Arbitrary Rectangle where
sw <- arbitrary
sh <- arbitrary
return $ Rectangle sx sy sw sh
coarbitrary = undefined
instance Arbitrary Rational where
arbitrary = do
@@ -233,115 +737,65 @@ instance Arbitrary Rational where
coarbitrary = undefined
------------------------------------------------------------------------
-- QC 2
main :: IO ()
main = do
args <- getArgs
let n = if null args then 100 else read (head args)
mapM_ (\(s,a) -> printf "%-25s: " s >> a n) tests
-- from QC2
-- | NonEmpty xs: guarantees that xs is non-empty.
newtype NonEmptyList a = NonEmpty [a]
deriving ( Eq, Ord, Show, Read )
instance Arbitrary a => Arbitrary (NonEmptyList a) where
arbitrary = NonEmpty `fmap` (arbitrary `suchThat` (not . null))
coarbitrary = undefined
newtype NonEmptyNubList a = NonEmptyNubList [a]
deriving ( Eq, Ord, Show, Read )
instance (Eq a, Arbitrary a) => Arbitrary (NonEmptyNubList a) where
arbitrary = NonEmptyNubList `fmap` ((liftM nub arbitrary) `suchThat` (not . null))
coarbitrary = undefined
type Positive a = NonZero (NonNegative a)
newtype NonZero a = NonZero a
deriving ( Eq, Ord, Num, Integral, Real, Enum, Show, Read )
instance (Num a, Ord a, Arbitrary a) => Arbitrary (NonZero a) where
arbitrary = fmap NonZero $ arbitrary `suchThat` (/= 0)
coarbitrary = undefined
newtype NonNegative a = NonNegative a
deriving ( Eq, Ord, Num, Integral, Real, Enum, Show, Read )
instance (Num a, Ord a, Arbitrary a) => Arbitrary (NonNegative a) where
arbitrary =
frequency
[ (5, (NonNegative . abs) `fmap` arbitrary)
, (1, return 0)
]
coarbitrary = undefined
newtype EmptyStackSet = EmptyStackSet T deriving Show
instance Arbitrary EmptyStackSet where
arbitrary = do
(NonEmptyNubList ns) <- arbitrary
(NonEmptyNubList sds) <- arbitrary
-- there cannot be more screens than workspaces:
return . EmptyStackSet . new ns $ take (min (length ns) (length sds)) sds
-- | Generates a value that satisfies a predicate.
suchThat :: Gen a -> (a -> Bool) -> Gen a
gen `suchThat` p =
do mx <- gen `suchThatMaybe` p
case mx of
Just x -> return x
Nothing -> sized (\n -> resize (n+1) (gen `suchThat` p))
-- | Tries to generate a value that satisfies a predicate.
suchThatMaybe :: Gen a -> (a -> Bool) -> Gen (Maybe a)
gen `suchThatMaybe` p = sized (try 0 . max 1)
where
n = 100
tests =
[("read.show ", mytest prop_id)
,("member/push ", mytest prop_member1)
,("member/peek ", mytest prop_peekmember)
,("member/delete ", mytest prop_member2)
,("member/empty ", mytest prop_member3)
,("size/push ", mytest prop_sizepush)
,("height/push ", mytest prop_currentpush)
,("push/peek ", mytest prop_pushpeek)
,("peek/peekStack" , mytest prop_peek_peekStack)
,("not . peek/peekStack", mytest prop_notpeek_peekStack)
,("delete/not.member", mytest prop_delete_uniq)
,("delete idempotent", mytest prop_delete2)
,("delete.push identity" , mytest prop_delete_push)
,("focus", mytest prop_focus1)
,("rotate/rotate ", mytest prop_rotaterotate)
,("view/view ", mytest prop_viewview)
,("fullcache ", mytest prop_fullcache)
,("currentwsvisible ", mytest prop_currentwsvisible)
,("ws screen mapping", mytest prop_ws2screen_screen2ws)
,("screen/workspace ", mytest prop_screenworkspace)
,("promote idempotent", mytest prop_promote2)
,("promote focus", mytest prop_promotefocus)
,("promote current", mytest prop_promotecurrent)
,("promote only swaps", mytest prop_promoterotate)
,("promote/screen" , mytest prop_promotescreen)
,("swap", mytest prop_swap)
------------------------------------------------------------------------
,("tile 1 window fullsize", mytest prop_tile_fullscreen)
,("vtile 1 window fullsize", mytest prop_vtile_fullscreen)
,("vtiles never overlap", mytest prop_vtile_non_overlap )
]
debug = False
mytest :: Testable a => a -> Int -> IO ()
mytest a n = mycheck defaultConfig
{ configMaxTest=n
, configEvery= \n args -> if debug then show n ++ ":\n" ++ unlines args else [] } a
mycheck :: Testable a => Config -> a -> IO ()
mycheck config a = do
rnd <- newStdGen
mytests config (evaluate a) rnd 0 0 []
mytests :: Config -> Gen Result -> StdGen -> Int -> Int -> [[String]] -> IO ()
mytests config gen rnd0 ntest nfail stamps
| ntest == configMaxTest config = do done "OK," ntest stamps
| nfail == configMaxFail config = do done "Arguments exhausted after" ntest stamps
| otherwise =
do putStr (configEvery config ntest (arguments result)) >> hFlush stdout
case ok result of
Nothing ->
mytests config gen rnd1 ntest (nfail+1) stamps
Just True ->
mytests config gen rnd1 (ntest+1) nfail (stamp result:stamps)
Just False ->
putStr ( "Falsifiable after "
++ show ntest
++ " tests:\n"
++ unlines (arguments result)
) >> hFlush stdout
where
result = generate (configSize config ntest) rnd2 gen
(rnd1,rnd2) = split rnd0
done :: String -> Int -> [[String]] -> IO ()
done mesg ntest stamps = putStr ( mesg ++ " " ++ show ntest ++ " tests" ++ table )
where
table = display
. map entry
. reverse
. sort
. map pairLength
. group
. sort
. filter (not . null)
$ stamps
display [] = ".\n"
display [x] = " (" ++ x ++ ").\n"
display xs = ".\n" ++ unlines (map (++ ".") xs)
pairLength xss@(xs:_) = (length xss, xs)
entry (n, xs) = percentage n ntest
++ " "
++ concat (intersperse ", " xs)
percentage n m = show ((100 * n) `div` m) ++ "%"
------------------------------------------------------------------------
try _ 0 = return Nothing
try k n = do x <- resize (2*k+n) gen
if p x then return (Just x) else try (k+1) (n-1)

View File

@@ -8,7 +8,6 @@ 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

47
util/GenerateManpage.hs Normal file
View 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 "./Config.hs"
let sed = unlines . replace "___KEYBINDINGS___" troffBindings . lines
readFile "./man/xmonad.1.in" >>= return . sed >>= writeFile "./man/xmonad.1"

View File

@@ -1,18 +1,31 @@
name: xmonad
version: 0.1
description: A lightweight X11 window manager.
version: 0.3
homepage: http://xmonad.org
synopsis: A lightweight X11 window manager.
description:
Xmonad is a minimalist tiling window manager for X, written in
Haskell. Windows are managed using automatic layout algorithms,
which can be dynamically reconfigured. At any time windows are
arranged so as to maximise the use of screen real estate. All
features of the window manager are accessible purely from the
keyboard: a mouse is entirely optional. Xmonad is configured in
Haskell, and custom layout algorithms may be implemented by the user
in config files. A principle of Xmonad is predictability: the user
should know in advance precisely the window arrangement that will
result from any action.
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
build-depends: base>=2.0, X11>=1.2.1, X11-extras>=0.3, mtl>=1.0, unix>=1.0
extra-source-files: README TODO tests/loc.hs tests/Properties.hs man/xmonad.1.in
Config.hs-boot util/GenerateManpage.hs man/xmonad.1 man/xmonad.html
executable: xmonad
main-is: Main.hs
other-modules: Config Operations StackSet XMonad
ghc-options: -funbox-strict-fields -O2 -Wall -optl-Wl,-s
ghc-options: -funbox-strict-fields -O2 -fasm -Wall -optl-Wl,-s
ghc-prof-options: -prof -auto-all
extensions: GeneralizedNewtypeDeriving
-- Also requires deriving Typeable