Outdated egg!
This is an egg for CHICKEN 4, the unsupported old release. You're almost certainly looking for the CHICKEN 5 version of this egg, if it exists.
If it does not exist, there may be equivalent functionality provided by another egg; have a look at the egg index. Otherwise, please consider porting this egg to the current version of CHICKEN.
wmiirc
Description
A library for writing configuration scripts for wmii, window manager improved 2.
See also the wmiirc snippets page for useful code snippets you can use in your own wmiirc scripts.
Author
Requirements
Requires the 9p egg.
Works with wmii 3.6 (and possibly 3.5) only.
Documentation
wmii is a minimalist windowmanager for the X window system that, instead of implementing a lot of policy in the WM, allows you to control it completely through a virtual filesystem it exports over the 9p protocol. Because of this, you can script it using any language or tool that can speak this protocol and define (almost) any behaviour you want.
This egg is an attempt to make an abstraction that lets you write scripts that can control wmii without having to study the structure of the 9p filesystem that wmii exports. Instead, you can do everything by calling procedures from this egg. Of course, if you know the filesystem structure you can still choose to access the filesystem directly if you need to do something extreme.
Concepts
Because wmii works slightly different than most other window managers, it is useful to start by exploring its fundamental concepts. There's an official guide that explains these concepts at http://www.suckless.org/wmii/guide.html but it is not very complete at the time of this writing.
First off, wmii is a so-called dynamic window manager. This means it manages the windows for you, so you don't have to bother about placing them on your screen. In default mode, it will divide the screen up in equal parts and tile the windows so they are not obscured. However, it also has a floated mode, which is the only mode classic window managers support. This will simply place the windows somewhere on the screen such that they can overlap. You will have to drag them around and/or resize them manually (or write a smart script that can position them sanely).
Tags and views
wmii manages windows (or clients in X speak) by tagging them. Any window can have several tags, but it always displays the contents of only one tag on the screen. This is called a view on that tag. This feature provides a superset of the functionality that other window managers offer with virtual desktops. Because windows can have multiple tags, it is possible for one window to show up in multiple views, effectively causing it to be "sticky" for only a selected number of views.
Modes
As explained above, wmii supports dynamic management as well as floated window management. Floating windows all live in a special tag sometimes identified by the name '~' (tilde). In dynamic mode there are three "submodes". In 'default' mode the windows are tiled, or divided equally across the screen. When a new window is created, the other windows are made smaller to accommodate for the new one, which is given an equal amount of space as all the other windows, and the window is placed such that it does not obscure any of the other windows. In 'stacked' mode, all windows are placed behind eachother in a staircased fashion so only their titlebars show. The window that has focus is placed in front of all the others, so it takes up almost all of your screen. Clicking on any titlebar sends that window to the front, and restacks such that all other windows' titlebars are still visible. In 'maximized' mode you only see one window with its titlebar.
States
Individual windows can also have different states. When a window is in the 'fullscreen' state, all window decoration and other stuff is hidden so your entire screen is used by one window of one application. When a window has the 'urgent' state, it has its "urgent hint" set, which is a hint to the window manager that this window's state demands immediate attention from the user (for example, in an IM client a private message may have arrived). Wmii can take action when this state is set and it can distinguish between client-requested urgency and manager-requested urgency.
Columns
The screen is further divided up into columns. When you first start wmii, the screen has only one column so you don't see it. However, when you send a window to the right or the left, it will create a new column it will live in from then on. The first column will be resized so the columns can fit next to eachother. Wmii allows you to define rules what the sizes of these columns will be. Each column has its own mode, so you can have one column maximized or stacked while the column next to it is in tiled mode.
Bars and tabs
The bottom of the screen contains a bar, which is actually two bars: one on the left and one on the right (lbar and rbar). These bars consist of tabs. Tabs can contain text in a given color and are clickable. Their name does not have to match their text contents, but it is advisable to keep these matched for your own sanity. In the default config, the left tab shows a list of available tags/views, with the current one highlighted. When you click a tab, a view on the corresponding tag is shown. By default the right bar shows a status that is continuously updated with the current time and system load.
Events
Whenever anything happens in wmii, an event is fired which your wmiirc can catch and act upon. Events are fired when a key is pressed (but only when it is grabbed by the WM, otherwise it goes to applications), when a window is (de)selected, when a tag is (de)selected, when a tag is created/destroyed etc. The exact events are listed with the event-handlers procedure.
Special names and limitations
wmii reserves a couple of names and syntaxes, which means you can't use arbitrary names for tags, tabs and some other things. It uses the plus symbol (+) as a separator for tag names. This cannot be escaped in any way, which means the + can simply not be used inside names. The special name sel is reserved for the currently selected tag/view/client etc. This has an alias as the exclamation mark, but its use is deprecated. Contents of tabs can be prefixed with three 6-digit hexadecimal numbers which are prefixed with a pound sign (#). If they are, this indicates the colors of that tab. This egg tries to take care of doing the formatting of this so you can simply pass integer values as colorcodes instead. Spaces are not allowed in several names either. It's probably safest to avoid all these special characters and names altogether.
Initialization
To get a connection to the server, call the following:
procedure: (connect [inport outport])
If you provide an inport and outport, it will connect to a 9p server on those input/output ports. If you do not provide them, it will try to connect to the wmii 9p server on the default location, namely a unix domain socket named /tmp/ns.USERNAME.DISPLAY/wmii, where USERNAME is your Unix username and DISPLAY is the current X display string.
After initializing, you can set up column rules, tag rules and event handlers. After having done this, you can enter the event loop using
procedure: (event-loop [kill-others])
This procedure will only return when another wmiirc wants to take over from the current one. The kill-others parameter tells wmiirc to tell other wmiirc scripts (whether written using this egg or not) to quit. It defaults to #t.
wmii operations
procedure: (quit)
Tells wmii to quit.
procedure: (exec cmdline)
Tells wmii to execute the given commandline, replacing the wmii process. This can be used to change to a different window manager.
procedure: (global-settings)
Returns an alist of the current global settings.
procedure: (global-settings-set! alist)
Change the global settings. Example:
(global-settings-set!
`((font . "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*")
(focuscolors . (#xffffff #x285577 #x4c7899))
(normcolors . (#x888888 #x222222 #x333333))
(grabmod . "Mod1")
(border . "1")))
This sets the default font (used in titlebars, for example) to the one in the string, sets the colors of a window that is focused to the given colorcodes in focuscolors, the default colors to the one in normcolors and grabs Mod1 for dragging windows around. The border thickness is set to one pixel.
Column rules
You can define the percentage of the screen each column should take up by setting up column rules. This can be done per view, using a simple alist and the following procedure:
procedure: (colrules-set! alist)
The keys of the alist are strings that are interpreted by wmii as a regular expression of view names on which the column rules should apply. The values of the alist are integers or lists of integers which represent percentages of the screen width the columns can take. The percentages apply to the columns in the same order, from left to right.
Example:
(colrules-set! '(("www" . (20 80))
("email" . (10 20 70))
("graphics" . 100) ;; can also be (100)
(".*" . (30 50 20))))
You can request the current column rules with the following procedure:
procedure: (colrules)
This returns an alist of the same type as accepted by colrules-set!.
Tag rules
Tag rules work much like column rules, except they specify which tags a client should get when it's first created.
procedure: (tagrules-set! alist)
The alist here takes again strings as keys which are interpreted as regexes by wmii. The values are either strings or lists of strings which are taken to be tagnames. Example:
(tagrules-set! `(("XMMS.*" . ("music" "~")) ("display.*" . ("graphics" "~")) ("Gimp.*" . ("graphics" "~")) ("xjump.*" . ("games" "~")) ("MPlayer.*" . "~") ("VICE.*" . ("games" "~")) (".*" . "sel") ;; Default to current tag (".*" . "1"))) ;; If no tag exists yet, start with '1'
Just like colrules, tagrules can be listed:
procedure: (tagrules)
Returns an alist that looks just like the one from tagrules-set!.
Event handlers
The core of wmiirc script writing is in the event handlers. You can register those with this procedure:
procedure: (event-handlers-set! alist [grab-keys])
The alist has keys that are either simply symbols that name the event or lists that provide a full match for the incoming event. The first rule that matches an incoming event is used, the others are ignored. The values of the alist are lambdas which handle the event. The grab-keys parameter defaults to #t and indicates if key handlers should be filtered from the event handlers and the keys used by them should be grabbed. If a key is not grabbed, it will never be visible by the wmiirc script; it will be passed to the client directly without being passed to the script by wmii.
The events that you can expect are as follows:
- (create-tag TAGNAME)
- The tag with the name TAGNAME has just been created.
- (destroy-tag TAGNAME)
- The tag with name TAGNAME has just been destroyed.
- (focus-tag TAGNAME)
- The focus was changed to the tag with name TAGNAME
- (unfocus-tag TAGNAME)
- The tag with name TAGNAME which had focus is now not in focus anymore.
- urgent CLIENT CLIENT-REQUEST?
- The client with identifier CLIENT just got the urgency hint set. CLIENT-REQUEST? is #t if the client requested it, #f if the window manager did.
- not-urgent CLIENT CLIENT-REQUEST?
- The client with identifier CLIENT just got the urgency hint removed. CLIENT-REQUEST? is #t if the client removed it, #f if the window manager did.
- urgent-tag TAGNAME CLIENT-REQUEST?
- The tag with name TAGNAME has a client which just got the urgency hint set. CLIENT-REQUEST is #t if the client requested it, #f if the urgency hint was set by the window manager itself.
- not-urgent-tag TAGNAME CLIENT-REQUEST?
- The tag with name TAGNAME has a client which just lost the urgency hint. CLIENT is #t if the client removed it, #f if the urgency hint was removed by the window manager itself.
- left-bar-click BUTTON TAB
- The user clicked on the left bar. BUTTON is an integer which indicates the mouse button the user pressed (1 = left, 2 = middle, 3 = right). TAB is the name of the tab on which was clicked.
- right-bar-click BUTTON TAB
- The user clicked on the right bar. BUTTON is an integer which indicates the mouse button the user pressed (1 = left, 2 = middle, 3 = right). TAB is the name of the tab on which was clicked.
- client-mouse-down CLIENT BUTTON
- The user pressed a mouse button while his mouse cursor was on the titlebar of a window. CLIENT indicates the client on which was clicked, BUTTON is an integer which indicates the mouse button the user pressed (1 = left, 2 = middle, 3 = right).
- client-mouse-click CLIENT BUTTON
- The user completed a mouse click on the titlebar of a window (he did not leave the titlebar before releasing the button). CLIENT indicates the client on which was clicked, BUTTON is an integer which indicates the mouse button the user pressed (1 = left, 2 = middle, 3 = right).
- (key keys ...)
- A key was pressed. The exact key pressed is encoded as several strings with key names. Usually it is easiest to handle the keys with rest arg notation to capture them in a list.
See the example section for a good example of how to use these events.
You can also use the following procedure to get the current event handlers:
procedure: (event-handlers)
Note that this procedure can not return event handlers in other processes. It's simply a getter for the current wmiirc instance's list of event handlers.
Keys
procedure: (grabbed-keys)
Get a list of currently grabbed keys.
procedure: (grabbed-keys-set! keys)
Sets the keys grabbed by wmii. keys is a list of key descriptions. Keys must be grabbed or they won't be passed to the wmiirc event loop. Example:
(grabbed-keys-set! '(("Mod1" "x")
("Mod2" "Space")
("Mod1" "Shift" "Space")))
This grabs the key combinations Mod1-x, Mod2-space and Mod1-shift-space. Key names of "special" keys are always capitalized in wmii. Normal literal keys like the x in the example are always lower case. If you would like to hook shift-x, that would not be ("X"), but ("Shift" "x").
procedure: (key-code->string) procedure: (string->key-code)
Translate a chicken-wmiirc keycode to a string that can be written to wmii's /keys file. Example:
(key-code->string '("Mod1" "Shift" "x")) => "Mod1-Shift-x" (string->keycode "Mod1-Shift-x") => ("Mod1" "Shift" "x")
Navigating
procedure: (goto-tag tag)
Switch the view to the named tag.
procedure: (navigate-to direction [tag])
Navigate to the given direction in the given tag. Tag defaults to "sel", the current tag. where can be "up", "down", "left", "right" or "toggle". If one of the directions, the client above, below, to the left or to the right of the current client is selected. If the direction is toggle, clients in float mode are selected if currently a client in normal mode is selected, or the other way around. If tag is not the current tag, the currently selected client on that tag is changed, but nothing happens in the current view, unless the current view just happens to be a view on that tag.
procedure: (send-to direction [client] [tag])
Send the given client (defaults to "sel", the currently selected client) into the given direction, as described above. If tag is given, the client is sent to the given direction on that tag. It is not sent to that tag.
Tags
procedure: (tags)
This procedure returns a list of all the tags known to wmii, as strings.
procedure: (tag-settings-set! alist [tag])
Change the settings for the named tag, which defaults to "sel", the current tag. alist is an alist with setting names (strings) as keys, and setting lists as values. Example:
(tag-settings-set! '(("colmode" . ("sel" "stack"))))
This sets the current tag's column mode to stacked.
procedure: (tag-settings [tag])
Returns an alist of the settings for the named tag. tag defaults to "sel", that is the current tag.
Bars
procedure: (tabs bar)
Return a list of all tabs on the bar. bar can be "lbar" (the left bar) or "rbar" (the right bar).
procedure: (write-tab bar tab contents [colors])
Change the string contents of a tab on the indicated bar. Creates the tab if it doesn't exist yet. If colors is given and not #f it should be a list of three numbers which represent in hexadecimal the color values of the foreground, background and border, respectively.
Example:
(write-tab "lbar" "hello" "hello there")
Creates a tab called "hello" on the left bar, which displays the string "hello there".
procedure: (destroy-tab bar tab)
Destroy tab on bar.
Clients
procedure: (client=? client1 client2)
Are two clients one and the same? Currently clients are represented as simple strings, the way wmii returns them, but in the future clients may have more information-rich representations, so always use this to compare them to be forwards-compatible. These "opaque" client objects can be obtained by event handlers from the various events that operate on a client, or by clients, for example.
procedure: (clients [tag ...])
Returns a list of all the clients that have any of the named tags. If no tags are given, returns all clients.
procedure: (client-tags [client])
Return a list of all tags on client. Defaults to the currently selected client ("sel").
procedure: (client-tags-set! tags [client])
Set the tags (a list of strings) on the client.
Low-level procedures
If you would like access to the 9p filesystem exported by wmii, you can use the following procedures:
procedure: (write file data)
Write the string contents of data to the named file. If the file does not exist, it is created.
procedure: (read file)
Read the given file into a string.
procedure: (read-lines file)
Read the given file line by line and create a list with an entry per line.
procedure: (directory path)
Show the directory contents of the named path.
Example
This example simply translates the default wmiirc script that's shipped with wmii from shell to Chicken:
#!/usr/pkg/bin/csi -s (require-library wmiirc srfi-18) (import scheme chicken (prefix wmiirc wmii:) srfi-18) (wmii:connect) (define modkey "Mod2") (define directions `((up . "k") (down . "j") (left . "h") (right . "l"))) (define wmii-normcolors '(#x888888 #x222222 #x333333)) (define wmii-focuscolors '(#xffffff #x285577 #x4c7899)) (define wmii-background #x333333) (define wmii-font "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*") (define (wmii9menu options . rest) (let-optionals rest ((default #f)) (receive (in out pid) (process "wmii9menu" `(,@(if default (list "-initial" default) '()) "-sf" ,(wmii:color->string (first wmii-focuscolors)) "-sb" ,(wmii:color->string (second wmii-focuscolors)) "-nf" ,(wmii:color->string (first wmii-normcolors)) "-nb" ,(wmii:color->string (second wmii-normcolors)) "-font" ,wmii-font ,@options)) (close-output-port out) (let ((chosen (read-line in))) (close-input-port in) (and (string? chosen) chosen))))) (define (dmenu options . rest) (receive (in out pid) (process "dmenu" `("-b" "-sf" ,(wmii:color->string (first wmii-focuscolors)) "-sb" ,(wmii:color->string (second wmii-focuscolors)) "-nf" ,(wmii:color->string (first wmii-normcolors)) "-nb" ,(wmii:color->string (second wmii-normcolors)) "-fn" ,wmii-font)) (display (string-join options "\n") out) (close-output-port out) (let ((chosen (read-line in))) (close-input-port in) (and (string? chosen) chosen)))) (define client-menu (let ((last-option "nonexistingoption")) (lambda (client) (and-let* ((option (wmii9menu '("Nop" "Delete" "Fullscreen") last-option))) (cond ((string=? option "Delete") (wmii:kill client)) ((string=? option "Fullscreen") (wmii:change-state "Fullscreen" #t client))) (set! last-option option))))) (define wmii-term "xterm") (wmii:colrules-set! `((".*" . (58 42)))) (wmii:tagrules-set! `(("XMMS.*" . "~") ("MPlayer.*" . "~") (".*" . "sel") (".*" . "1"))) ;; We need to do this in order to avoid getting lots of zombie processes (define (run . args) (process-wait (process-fork (lambda () (apply process-run args))))) (define status (let ((status-thread #f)) (lambda () (and status-thread (thread-terminate! status-thread)) (set! status-thread (thread-start! (make-thread (lambda () (let loop () (wmii:write-tab "rbar" "status" (with-input-from-pipe "echo -n $(uptime | sed 's/.*://; s/,//g') '|' $(date)" read-string)) (thread-sleep! 1) (loop))))))))) (wmii:event-handlers-set! `((create-tag . ,(lambda (event tag) (wmii:write-tab "lbar" tag tag wmii-normcolors))) (destroy-tag . ,(lambda (event tag) (wmii:destroy-tab "lbar" tag))) (focus-tag . ,(lambda (event tag) (if (member tag (wmii:tabs "lbar")) (wmii:write-tab "lbar" tag tag wmii-focuscolors)))) (unfocus-tag . ,(lambda (event tag) (if (member tag (wmii:tabs "lbar")) (wmii:write-tab "lbar" tag tag wmii-normcolors)))) (urgent-tag . ,(lambda (event tag client?) (wmii:write-tab "lbar" tag (string-append "*" tag)))) (not-urgent-tag . ,(lambda (event tag client?) (wmii:write-tab "lbar" tag tag))) (left-bar-click . ,(lambda (event button tab) (wmii:goto-tag tab))) (client-mouse-down . ,(lambda (event client button) (case button ((3) (client-menu client))))) ((key ,modkey "Control" "t") . ,(let ((prev #f)) (lambda _ (let ((keys (wmii:grabbed-keys))) (if prev (begin (wmii:grabbed-keys-set! prev) (set! prev #f)) (begin (set! prev keys) (wmii:grabbed-keys-set! `((,modkey "Control" "t"))))))))) ((key ,modkey "space") . ,(lambda _ (wmii:navigate-to "toggle"))) ((key ,modkey "d") . ,(lambda _ (wmii:tag-settings-set! '(("colmode" . ("sel" "default")))))) ((key ,modkey "s") . ,(lambda _ (wmii:tag-settings-set! '(("colmode" . ("sel" "stack")))))) ((key ,modkey "m") . ,(lambda _ (wmii:tag-settings-set! '(("colmode" . ("sel" "max")))))) ((key ,modkey "a") . ,(lambda _ (and-let* ((action (dmenu (append `("rehash" "exec" "status" "quit") (proglist (string-split (get-environment-variable "WMII_CONFPATH") ":")))))) (cond ((string=? action "rehash") (update-programs)) ((string-prefix? "exec " action) (wmii:exec (string-drop action 5))) ((string=? action "status") (status)) ((string=? action "quit") (wmii:quit) (exit)) (else (run (sprintf "env PATH=${WMII_CONFPATH}:${PATH} ~A" action))))))) ((key ,modkey "p") . ,(lambda _ (and-let* ((program (dmenu programs))) (run program)))) ((key ,modkey "t") . ,(lambda _ (and-let* ((tag (dmenu (wmii:tags)))) (wmii:goto-tag tag)))) ((key ,modkey "Return") . ,(lambda _ (run wmii-term))) ((key ,modkey "Shift" "space") . ,(lambda _ (wmii:send-to "toggle"))) ((key ,modkey "f") . ,(lambda _ (wmii:change-state "Fullscreen" 'toggle))) ((key ,modkey "Shift" "c") . ,(lambda _ (wmii:kill))) ((key ,modkey "Shift" "t") . ,(lambda _ (and-let* ((tag (dmenu (wmii:tags)))) (wmii:client-tags-set! (list tag))))) ,@(map (lambda (x) `((key ,modkey ,(cdr x)) . ,(lambda _ (wmii:navigate-to (->string (car x)))))) directions) ,@(map (lambda (x) `((key ,modkey "Shift" ,(cdr x)) . ,(lambda _ (wmii:send-to (->string (car x)))))) directions) ,@(map (lambda (x) `((key ,modkey ,(->string x)) . ,(lambda _ (wmii:goto-tag x)))) (iota 10)) ,@(map (lambda (x) `((key ,modkey "Shift" ,(->string x)) . ,(lambda _ (wmii:client-tags-set! (list (->string x)))))) (iota 10)))) (wmii:global-settings-set! `((font . ,wmii-font) (focuscolors . ,wmii-focuscolors) (normcolors . ,wmii-normcolors) (grabmod . ,modkey) (border . "1"))) (define (proglist path) (sort! (delete-duplicates! (flatten (map (lambda (dir) (if ((conjoin directory? file-execute-access? file-read-access?) dir) (map pathname-strip-directory (find-files dir test: (conjoin (complement directory?) file-execute-access?) action: cons seed: '() limit: 0)) '())) path)) string=?) string<?)) (define programs '()) (define (update-programs) (thread-start! (make-thread (lambda () (set! programs (proglist (string-split (get-environment-variable "PATH") ":"))))))) (update-programs) (let ((curtag (wmii:tag))) (for-each (cut wmii:destroy-tab "lbar" <>) (wmii:tabs "lbar")) (for-each (lambda (t) (if (string=? curtag t) (wmii:write-tab "lbar" t t wmii-focuscolors) (wmii:write-tab "lbar" t t wmii-normcolors))) (wmii:tags))) (run (sprintf "xsetroot -solid '~A'" (wmii:color->string wmii-background))) (status) (wmii:event-loop)
For some other cool snippets, have a look at the wmiirc snippets page.
Changelog
- 0.5 Convert deprecated getenv calls to get-environment-variable so it works with new Chicken versions (thanks to Christian Kellermann).
- 0.4 Fix crash in colrules procedure, remove dependency on matchable egg
- 0.3 Improvement in clients procedure
- 0.2 Port to hygienic Chicken
- 0.1 Initial release
License
Copyright (c) 2008-2011, Peter Bex All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.