source: project/wiki/wmiirc @ 8655

Last change on this file since 8655 was 8655, checked in by sjamaan, 12 years ago

Use enscript on the bigger examples

File size: 22.2 KB
Line 
1[[tags: egg]]
2
3== wmiirc
4
5[[toc:]]
6
7=== Description
8
9A library for writing configuration scripts for [[http://wmii.suckless.org|wmii, window manager improved 2]].
10
11See also the [[wmiirc snippets]] page for useful code snippets you can
12use in your own wmiirc scripts.
13
14=== Author
15
16[[Peter Bex]]
17
18=== Requirements
19
20Requires the [[http://www.call-with-current-continuation.org/eggs/9p|9p]] egg.
21
22Works with wmii 3.6 (and possibly 3.5) only.
23
24=== Download
25
26[[http://www.call-with-current-continuation.org/eggs/wmiirc.egg|wmiirc.egg]]
27
28=== Documentation
29
30[[http://wmii.suckless.org|wmii]] is a minimalist windowmanager for
31the X window system that, instead of implementing a lot of policy in
32the WM, allows you to control it completely through a virtual
33filesystem it exports over the [[9p]] protocol. Because of this, you
34can script it using any language or tool that can speak this protocol
35and define (almost) any behaviour you want.
36
37This egg is an attempt to make an abstraction that lets you write
38scripts that can control wmii without having to study the structure of
39the 9p filesystem that wmii exports.  Instead, you can do everything
40by calling procedures from this egg.  Of course, if you know the
41filesystem structure you can still choose to access the filesystem
42directly if you need to do something extreme.
43
44==== Concepts
45
46Because wmii works slightly different than most other window managers,
47it is useful to start by exploring its fundamental concepts.
48
49First off, wmii is a so-called ''dynamic'' window manager.  This means
50it manages the windows for you, so you don't have to bother about
51placing them on your screen.  In default mode, it will divide the
52screen up in equal parts and tile the windows so they are not
53obscured.  However, it also has a ''floated'' mode, which is the only
54mode classic window managers support.  This will simply place the
55windows somewhere on the screen such that they can overlap.  You will
56have to drag them around and/or resize them manually (or write a smart
57script that can position them sanely).
58
59===== Tags and views
60
61wmii manages windows (or ''clients'' in X speak) by ''tagging'' them.
62Any window can have several tags, but it always displays the contents
63of only one tag on the screen.  This is called a ''view'' on that tag.
64This feature provides a superset of the functionality that other
65window managers offer with ''virtual desktops''.  Because windows can
66have multiple tags, it is possible for one window to show up in
67multiple views, effectively causing it to be "sticky" for only a
68selected number of views.
69
70===== Modes
71
72As explained above, wmii supports dynamic management as well as
73floated window management.  Floating windows all live in a special tag
74sometimes identified by the name '~' (tilde).  In dynamic mode there
75are three "submodes".  In 'default' mode the windows are tiled, or
76divided equally across the screen.  When a new window is created, the
77other windows are made smaller to accommodate for the new one, which
78is given an equal amount of space as all the other windows, and the
79window is placed such that it does not obscure any of the other
80windows.  In 'stacked' mode, all windows are placed behind eachother
81in a staircased fashion so '''only''' their titlebars show.  The
82window that has focus is placed in front of all the others, so it
83takes up almost all of your screen.  Clicking on any titlebar sends
84that window to the front, and restacks such that all other windows'
85titlebars are still visible.  In 'maximized' mode you only see one
86window with its titlebar.
87
88===== States
89
90Individual windows can also have different states.  When a window is
91in the 'fullscreen' state, '''all''' window decoration and other stuff
92is hidden so your entire screen is used by one window of one application.
93When a window has the 'urgent' state, it has its "urgent hint" set, which
94is a hint to the window manager that this window's state demands immediate
95attention from the user (for example, in an IM client a private message
96may have arrived).  Wmii can take action when this state is set and it can
97distinguish between client-requested urgency and manager-requested urgency.
98
99===== Columns
100
101The screen is further divided up into columns.  When you first start wmii,
102the screen has only one column so you don't see it.  However, when you send
103a window to the right or the left, it will create a new column it will live
104in from then on.  The first column will be resized so the columns can fit
105next to eachother.  Wmii allows you to define rules what the sizes of these
106columns will be.  Each column has its own mode, so you can have one column
107maximized or stacked while the column next to it is in tiled mode.
108
109===== Bars and tabs
110
111The bottom of the screen contains a bar, which is actually ''two'' bars: one
112on the left and one on the right (''lbar'' and ''rbar'').  These bars consist
113of ''tabs''.  Tabs can contain text in a given color and are clickable.
114Their name does not have to match their text contents, but it is advisable
115to keep these matched for your own sanity.  In the default config, the left
116tab shows a list of available tags/views, with the current one highlighted.
117When you click a tab, a view on the corresponding tag is shown.  By default
118the right bar shows a status that is continuously updated with the current
119time and system load.
120
121===== Events
122
123Whenever anything happens in wmii, an event is fired which your wmiirc
124can catch and act upon.  Events are fired when a key is pressed (but
125only when it is grabbed by the WM, otherwise it goes to applications),
126when a window is (de)selected, when a tag is (de)selected, when a tag
127is created/destroyed etc.  The exact events are listed with the
128{{wmii:event-handlers}} procedure.
129
130===== Special names and limitations
131
132wmii reserves a couple of names and syntaxes, which means you can't
133use arbitrary names for tags, tabs and some other things.  It uses the
134plus symbol (+) as a separator for tag names.  This cannot be escaped
135in any way, which means the + can simply not be used inside names.
136The special name ''sel'' is reserved for the currently selected
137tag/view/client etc. This has an alias as the exclamation mark, but
138its use is deprecated.  Contents of tabs can be prefixed with three
1396-digit hexadecimal numbers which are prefixed with a pound sign (#).
140If they are, this indicates the colors of that tab.  This egg tries to
141take care of doing the formatting of this so you can simply pass
142integer values as colorcodes instead.  Spaces are not allowed in
143several names either.  It's probably safest to avoid all these special
144characters and names altogether.
145
146==== Initialization
147
148To get a connection to the server, call the following:
149
150  procedure: (wmii:connect [inport outport])
151
152If you provide an inport and outport, it will connect to a 9p server
153on those input/output ports.  If you do not provide them, it will try
154to connect to the wmii 9p server on the default location, namely a unix
155domain socket named {{/tmp/ns.USERNAME.DISPLAY/wmii}}, where USERNAME
156is your Unix username and DISPLAY is the current X display string.
157
158'''important''' wmiirc does ''not'' support multiple threads calling
159wmiirc procedures.  It also does not support multiple processes doing
160this, so if you fork you'll need to call (wmii:connect) first in the
161child process (or the parent process) to create a new connection before
162doing anything else.
163
164After initializing, you can set up column rules, tag rules and event
165handlers.  After having done this, you can enter the event loop using
166
167  procedure: (wmii:event-loop [kill-others])
168
169This procedure will only return when another wmiirc wants to take over
170from the current one.  The {{kill-others}} parameter tells wmiirc to
171tell other wmiirc scripts (whether written using this egg or not) to
172quit.  It defaults to {{#t}}.
173
174==== Column rules
175
176You can define the percentage of the screen each column should take up
177by setting up column rules.  This can be done per view, using a simple
178alist and the following procedure:
179
180  procedure: (wmii:colrules-set! alist)
181
182The keys of the alist are strings that are interpreted by wmii as a
183regular expression of view names on which the column rules should
184apply.  The values of the alist are integers or lists of integers
185which represent percentages of the screen width the columns can take.
186The percentages apply to the columns in the same order, from left to
187right.
188
189Example:
190<enscript highlight=scheme>
191  (wmii:colrules-set! '(("www" . (20 80))
192                        ("email" . (10 20 70))
193                        ("graphics" . 100) ;; can also be (100)
194                        (".*" . (30 50 20))))
195</enscript>
196
197You can request the current column rules with the following procedure:
198
199  procedure: (wmii:colrules)
200
201This returns an alist of the same type as accepted by wmii:colrules-set!.
202
203==== Tag rules
204
205Tag rules work much like column rules, except they specify which
206tags a client should get when it's first created.
207
208  procedure: (wmii:tagrules-set! alist)
209
210The alist here takes again strings as keys which are interpreted as
211regexes by wmii.  The values are either strings or lists of strings
212which are taken to be tagnames. Example:
213
214<enscript highlight=scheme>
215  (wmii:tagrules-set! `(("XMMS.*" . ("music" "~"))
216                        ("display.*" . ("graphics" "~"))
217                        ("Gimp.*" . ("graphics" "~"))
218                        ("xjump.*" . ("games" "~"))
219                        ("MPlayer.*" . "~")
220                        ("VICE.*" . ("games" "~"))
221                        (".*" . "sel") ;; Default to current tag
222                        (".*" . "1"))) ;; If no tag exists yet, start with '1'
223</enscript>
224
225Just like colrules, tagrules can be listed:
226
227  procedure: (wmii:tagrules)
228
229Returns an alist that looks just like the one from {{wmii:tagrules-set!}}.
230
231==== Event handlers
232
233The core of wmiirc script writing is in the event handlers.  You can register
234those with this procedure:
235
236  procedure: (wmii:event-handlers-set! alist [grab-keys])
237
238The alist has keys that are either simply symbols that name the event
239or lists that provide a full match for the incoming event.  The first
240rule that matches an incoming event is used, the others are ignored.
241The values of the alist are lambdas which handle the event.  The
242{{grab-keys}} parameter defaults to {{#t}} and indicates if key
243handlers should be filtered from the event handlers and the keys used
244by them should be grabbed.  If a key is not grabbed, it will never be
245visible by the wmiirc script; it will be passed to the client directly
246without being passed to the script by wmii.
247
248The events that you can expect are as follows:
249
250; {{(create-tag TAGNAME)}} : The tag with the name {{TAGNAME}} has just
251been created.
252
253; {{(destroy-tag TAGNAME)}} : The tag with name {{TAGNAME}} has just been
254destroyed.
255
256; {{(focus-tag TAGNAME)}} : The focus was changed to the tag with name {{TAGNAME}}
257
258; {{(unfocus-tag TAGNAME)}} : The tag with name {{TAGNAME}} which had focus
259is now not in focus anymore.
260
261; {{urgent CLIENT CLIENT-REQUEST?}} : The client with identifier {{CLIENT}}
262just got the ''urgency hint'' set. {{CLIENT-REQUEST?}} is #{{t}} if the
263client request it, {{#f}} if the window manager did.
264
265; {{not-urgent CLIENT CLIENT-REQUEST?}} : The client with identifier {{CLIENT}}
266just got the ''urgency hint'' removed. {{CLIENT-REQUEST?}} is #{{t}} if the
267client removed it, {{#f}} if the window manager did.
268
269; {{urgent-tag TAGNAME CLIENT-REQUEST?}} : The tag with name {{TAGNAME}} has
270a client which just got the ''urgency hint'' set.  {{CLIENT-REQUEST}} is
271{{#t}} if the client requested it, {{#f}} if the urgency hint was set by the
272window manager itself.
273
274; {{not-urgent-tag TAGNAME CLIENT-REQUEST?}} : The tag with name {{TAGNAME}}
275has a client which just lost the ''urgency hint''.  {{CLIENT}} is {{#t}} if
276the client removed it, {{#f}} if the urgency hint was removed by the window
277manager itself.
278
279; {{left-bar-click BUTTON TAB}} : The user clicked on the left bar.
280{{BUTTON}} is an integer which indicates the mouse button the user pressed
281(1 = left, 2 = middle, 3 = right).  {{TAB}} is the name of the tab on
282which was clicked.
283
284; {{right-bar-click BUTTON TAB}} : The user clicked on the right bar.
285{{BUTTON}} is an integer which indicates the mouse button the user pressed
286(1 = left, 2 = middle, 3 = right).  {{TAB}} is the name of the tab on
287which was clicked.
288
289; {{client-mouse-down CLIENT BUTTON}} : The user pressed a mouse button
290while his mouse cursor was on the titlebar of a window. {{CLIENT}}
291indicates the client on which was clicked, {{BUTTON}} is an integer which
292indicates the mouse button the user pressed (1 = left, 2 = middle, 3 = right).
293
294; {{client-mouse-click CLIENT BUTTON}} : The user completed a mouse click
295on the titlebar of a window (he did not leave the titlebar before releasing
296the button).  {{CLIENT}} indicates the client on which was clicked,
297{{BUTTON}} is an integer which indicates the mouse button the user pressed
298(1 = left, 2 = middle, 3 = right).
299
300; {{(key keys ...)}} : A key was pressed.  The exact key pressed is encoded
301as several strings with key names.  Usually it is easiest to handle
302the keys with rest arg notation to capture them in a list.
303
304See the [[#Example|example section]] for a good example of how to use
305these events.
306
307.... MORE TO COME SOON ....
308
309=== Example
310
311This example simply translates the default wmiirc script that's shipped
312with wmii from shell to Chicken:
313
314<enscript highlight=scheme>
315#!/usr/pkg/bin/csi -s
316
317(use wmiirc srfi-18)
318
319(wmii:connect)
320
321(define modkey "Mod2")
322
323(define directions
324  `((up . "k")
325    (down . "j")
326    (left . "h")
327    (right . "l")))
328
329(define wmii-normcolors  '(#x888888 #x222222 #x333333))
330(define wmii-focuscolors '(#xffffff #x285577 #x4c7899))
331
332(define wmii-background #x333333)
333(define wmii-font "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*")
334
335(define (wmii9menu options . rest)
336  (let-optionals rest ((default #f))
337    (receive (in out pid)
338             (process
339              "wmii9menu"
340              `(,@(if default (list "-initial" default) '())
341                "-sf" ,(wmii:color->string (first wmii-focuscolors))
342                "-sb" ,(wmii:color->string (second wmii-focuscolors))
343                "-nf" ,(wmii:color->string (first wmii-normcolors))
344                "-nb" ,(wmii:color->string (second wmii-normcolors))
345                "-font" ,wmii-font
346                ,@options))
347             (close-output-port out)
348             (let ((chosen (read-line in)))
349               (close-input-port in)
350               (and (string? chosen) chosen)))))
351
352(define (dmenu options . rest)
353  (receive (in out pid)
354           (process
355            "dmenu"
356            `("-b"
357              "-sf" ,(wmii:color->string (first wmii-focuscolors))
358              "-sb" ,(wmii:color->string (second wmii-focuscolors))
359              "-nf" ,(wmii:color->string (first wmii-normcolors))
360              "-nb" ,(wmii:color->string (second wmii-normcolors))
361              "-fn" ,wmii-font))
362           (display (string-join options "\n") out)
363           (close-output-port out)
364           (let ((chosen (read-line in)))
365             (close-input-port in)
366             (and (string? chosen) chosen))))
367
368(define client-menu
369 (let ((last-option "nonexistingoption"))
370   (lambda (client)
371     (let ((option (wmii9menu '("Nop" "Delete" "Fullscreen") last-option)))
372       (when option
373         (cond
374          ((string=? option "Delete") (wmii:kill client))
375          ((string=? option "Fullscreen") (wmii:change-state "Fullscreen" #t client)))
376         (set! last-option option))))))
377
378(define wmii-term "xterm")
379
380(wmii:colrules-set! `((".*" . (58 42))))
381
382(wmii:tagrules-set! `(("XMMS.*" . "~")
383                      ("MPlayer.*" . "~")
384                      (".*" . "sel")
385                      (".*" . "1")))
386
387;; We need to do this in order to avoid getting lots of zombie processes
388(define (run . args) (process-wait (process-fork (lambda () (apply process-run args)))))
389
390(define status
391  (let ((status-pid #f))
392    (lambda ()
393      (and status-pid (process-signal status-pid))
394      (set! status-pid
395            (process-fork
396             (lambda ()
397               (wmii:connect)
398               (let loop ()
399                 (wmii:write-tab "rbar" "status"
400                                 (with-input-from-pipe "echo -n $(uptime | sed 's/.*://; s/,//g') '|' $(date)" read-string))
401                 (sleep 1)
402                 (loop))))))))
403
404(wmii:event-handlers-set!
405 `((create-tag
406    . ,(lambda (event tag) (wmii:write-tab "lbar" tag tag wmii-normcolors)))
407   (destroy-tag
408    . ,(lambda (event tag) (wmii:destroy-tab "lbar" tag)))
409   (focus-tag
410    . ,(lambda (event tag)
411         (if (member tag (wmii:tabs "lbar"))
412             (wmii:write-tab "lbar" tag tag wmii-focuscolors))))
413   (unfocus-tag
414    . ,(lambda (event tag)
415         (if (member tag (wmii:tabs "lbar"))
416             (wmii:write-tab "lbar" tag tag wmii-normcolors))))
417   (urgent-tag
418    . ,(lambda (event tag client?) (wmii:write-tab "lbar" tag (string-append "*" tag))))
419   (not-urgent-tag
420    . ,(lambda (event tag client?) (wmii:write-tab "lbar" tag tag)))
421   (left-bar-click
422    . ,(lambda (event button tab) (wmii:goto-tag tab)))
423   (client-mouse-down
424    . ,(lambda (event client button)
425         (case button
426           ((3) (client-menu client)))))
427   ((key ,modkey "Control" "t")
428    . ,(let ((prev #f))
429         (lambda _
430           (let ((keys (wmii:grabbed-keys)))
431             (if prev
432                 (begin (wmii:grabbed-keys-set! prev)
433                        (set! prev #f))
434                 (begin (set! prev keys)
435                        (wmii:grabbed-keys-set! `((,modkey "Control" "t")))))))))
436   ((key ,modkey "space")
437    . ,(lambda _ (wmii:navigate-to "toggle")))
438   ((key ,modkey "d")
439    . ,(lambda _ (wmii:tag-settings-set! '(("colmode" . ("sel" "default"))))))
440   ((key ,modkey "s")
441    . ,(lambda _ (wmii:tag-settings-set! '(("colmode" . ("sel" "stack"))))))
442   ((key ,modkey "m")
443    . ,(lambda _ (wmii:tag-settings-set! '(("colmode" . ("sel" "max"))))))
444   ((key ,modkey "a")
445    . ,(lambda _
446         (let ((action (dmenu (append
447                               `("rehash" "exec" "status" "quit")
448                               (proglist (string-split (getenv "WMII_CONFPATH") ":"))))))
449           (when action
450             (cond
451              ((string=? action "rehash") (update-programs))
452              ((string-prefix? "exec " action)
453               (wmii:exec (string-drop action 5)))
454              ((string=? action "status") (status))
455              ((string=? action "quit")
456               (wmii:quit)
457               (exit))
458              (else (run (sprintf "env PATH=${WMII_CONFPATH}:${PATH} ~A" action))))))))
459   ((key ,modkey "p")
460    . ,(lambda _
461         (and-let* ((program (dmenu programs))) (run program))))
462   ((key ,modkey "t")
463    . ,(lambda _
464         (and-let* ((tag (dmenu (wmii:tags)))) (wmii:goto-tag tag))))
465   ((key ,modkey "Return")
466    . ,(lambda _ (run wmii-term)))
467   ((key ,modkey "Shift" "space")
468    . ,(lambda _ (wmii:send-to "toggle")))
469   ((key ,modkey "f")
470    . ,(lambda _ (wmii:change-state "Fullscreen" 'toggle)))
471   ((key ,modkey "Shift" "c")
472    . ,(lambda _ (wmii:kill)))
473   ((key ,modkey "Shift" "t")
474    . ,(lambda _
475         (and-let* ((tag (dmenu (wmii:tags)))) (wmii:client-tags-set! (list tag)))))
476   ,@(map (lambda (x)
477            `((key ,modkey ,(cdr x))
478              . ,(lambda _ (wmii:navigate-to (->string (car x))))))
479          directions)
480   ,@(map (lambda (x)
481            `((key ,modkey "Shift" ,(cdr x))
482              . ,(lambda _ (wmii:send-to (->string (car x))))))
483          directions)   
484   ,@(map (lambda (x)
485            `((key ,modkey ,(->string x))
486              . ,(lambda _ (wmii:goto-tag x))))
487          (iota 10))
488   ,@(map (lambda (x)
489            `((key ,modkey "Shift" ,(->string x))
490              . ,(lambda _ (wmii:client-tags-set! (list (->string x)))))) (iota 10))))
491
492(wmii:global-settings-set!
493 `((font . ,wmii-font)
494   (focuscolors . ,wmii-focuscolors)
495   (normcolors . ,wmii-normcolors)
496   (grabmod . ,modkey)
497   (border . "1")))
498
499(define (proglist path)
500  (sort!
501   (delete-duplicates!
502    (flatten
503     (map (lambda (dir)
504            (if ((conjoin directory? file-execute-access? file-read-access?) dir)
505                (map pathname-strip-directory
506                     (find-files dir (conjoin (complement directory?) file-execute-access?) cons '() 0))
507                '()))
508          path))
509    string=?)
510   string<?))
511
512(define programs '())
513(define (update-programs)
514  (thread-start!
515   (make-thread
516    (lambda () (set! programs (proglist (string-split (getenv "PATH") ":")))))))
517(update-programs)
518
519(let ((curtag (wmii:tag)))
520  (for-each (cut wmii:destroy-tab "lbar" <>) (wmii:tabs "lbar"))
521  (for-each (lambda (t)
522              (if (string=? curtag t)
523                  (wmii:write-tab "lbar" t t wmii-focuscolors)
524                  (wmii:write-tab "lbar" t t wmii-normcolors)))
525            (wmii:tags)))
526
527(run (sprintf "xsetroot -solid '~A'" (wmii:color->string wmii-background)))
528
529(status)
530(wmii:event-loop)
531</enscript>
532
533For some other cool snippets, have a look at the [[wmiirc snippets]] page.
534
535=== Changelog
536
5370.1 Initial release
538
539=== License
540
541  Copyright (c) 2008, Peter Bex
542  All rights reserved.
543 
544  Redistribution and use in source and binary forms, with or without
545  modification, are permitted provided that the following conditions are
546  met:
547 
548  Redistributions of source code must retain the above copyright
549  notice, this list of conditions and the following disclaimer.
550 
551  Redistributions in binary form must reproduce the above copyright
552  notice, this list of conditions and the following disclaimer in the
553  documentation and/or other materials provided with the distribution.
554 
555  Neither the name of the author nor the names of its contributors may
556  be used to endorse or promote products derived from this software
557  without specific prior written permission.
558 
559  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
560  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
561  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
562  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
563  COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
564  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
565  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
566  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
567  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
568  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
569  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
570  OF THE POSSIBILITY OF SUCH DAMAGE.
Note: See TracBrowser for help on using the repository browser.