summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--DISTRIBUTORS88
-rw-r--r--LICENSE22
-rw-r--r--Makefile33
-rw-r--r--NEWS83
-rw-r--r--NOTES6
-rw-r--r--PKGBUILD31
-rw-r--r--README92
-rw-r--r--TODO13
-rw-r--r--alternative_wmiircs/Makefile13
-rw-r--r--alternative_wmiircs/README23
-rw-r--r--alternative_wmiircs/plan9port/Makefile9
-rw-r--r--alternative_wmiircs/plan9port/README10
-rwxr-xr-xalternative_wmiircs/plan9port/wmiirc284
-rw-r--r--alternative_wmiircs/python/Makefile14
-rw-r--r--alternative_wmiircs/python/README16
-rw-r--r--alternative_wmiircs/python/pygmi/Makefile12
-rw-r--r--alternative_wmiircs/python/pygmi/__init__.py27
-rw-r--r--alternative_wmiircs/python/pygmi/event.py304
-rw-r--r--alternative_wmiircs/python/pygmi/fs.py699
-rw-r--r--alternative_wmiircs/python/pygmi/menu.py54
-rw-r--r--alternative_wmiircs/python/pygmi/monitor.py118
-rw-r--r--alternative_wmiircs/python/pygmi/util.py66
-rw-r--r--alternative_wmiircs/python/pyxp/Makefile15
-rw-r--r--alternative_wmiircs/python/pyxp/__init__.py7
-rw-r--r--alternative_wmiircs/python/pyxp/asyncclient.py193
-rw-r--r--alternative_wmiircs/python/pyxp/client.py346
-rw-r--r--alternative_wmiircs/python/pyxp/dial.py35
-rw-r--r--alternative_wmiircs/python/pyxp/fcall.py131
-rw-r--r--alternative_wmiircs/python/pyxp/fields.py132
-rw-r--r--alternative_wmiircs/python/pyxp/messages.py73
-rw-r--r--alternative_wmiircs/python/pyxp/mux.py195
-rw-r--r--alternative_wmiircs/python/pyxp/types.py55
-rwxr-xr-xalternative_wmiircs/python/wmiirc12
-rw-r--r--alternative_wmiircs/python/wmiirc.py308
-rw-r--r--alternative_wmiircs/ruby/HISTORY233
-rw-r--r--alternative_wmiircs/ruby/LICENSE21
-rw-r--r--alternative_wmiircs/ruby/Makefile13
-rw-r--r--alternative_wmiircs/ruby/README94
-rw-r--r--alternative_wmiircs/ruby/config.rb547
-rw-r--r--alternative_wmiircs/ruby/config.yaml536
-rwxr-xr-xalternative_wmiircs/ruby/wmiirc88
-rw-r--r--cmd/Makefile33
-rw-r--r--cmd/click/Makefile22
-rw-r--r--cmd/click/_util.c77
-rw-r--r--cmd/click/dat.h27
-rw-r--r--cmd/click/fns.h4
-rw-r--r--cmd/click/main.c62
-rw-r--r--cmd/clientutil.c50
-rw-r--r--cmd/menu/Makefile36
-rw-r--r--cmd/menu/bindings.c51
-rw-r--r--cmd/menu/caret.c164
-rw-r--r--cmd/menu/dat.h116
-rw-r--r--cmd/menu/event.c334
-rw-r--r--cmd/menu/fns.h51
-rw-r--r--cmd/menu/history.c90
-rw-r--r--cmd/menu/keys.c142
-rw-r--r--cmd/menu/keys.txt49
-rw-r--r--cmd/menu/main.c346
-rw-r--r--cmd/menu/menu.c344
-rw-r--r--cmd/strut/Makefile27
-rw-r--r--cmd/strut/_util.c77
-rw-r--r--cmd/strut/dat.h33
-rw-r--r--cmd/strut/event.c309
-rw-r--r--cmd/strut/ewmh.c84
-rw-r--r--cmd/strut/fns.h18
-rw-r--r--cmd/strut/main.c102
-rw-r--r--cmd/strut/printevent_kludge.c12
-rw-r--r--cmd/strut/win.c102
-rw-r--r--cmd/util.c272
-rw-r--r--cmd/wihack.sh44
-rwxr-xr-xcmd/wmii.rc.rc181
-rwxr-xr-xcmd/wmii.sh.sh219
-rw-r--r--cmd/wmii/Makefile47
-rw-r--r--cmd/wmii/_util.c378
-rw-r--r--cmd/wmii/area.c328
-rw-r--r--cmd/wmii/bar.c300
-rw-r--r--cmd/wmii/client.c1212
-rw-r--r--cmd/wmii/column.c738
-rw-r--r--cmd/wmii/dat.h404
-rw-r--r--cmd/wmii/div.c190
-rw-r--r--cmd/wmii/error.c41
-rw-r--r--cmd/wmii/event.c359
-rw-r--r--cmd/wmii/ewmh.c544
-rw-r--r--cmd/wmii/float.c245
-rw-r--r--cmd/wmii/fns.h328
-rw-r--r--cmd/wmii/frame.c681
-rw-r--r--cmd/wmii/fs.c725
-rw-r--r--cmd/wmii/geom.c94
-rw-r--r--cmd/wmii/key.c244
-rw-r--r--cmd/wmii/layout.c608
-rw-r--r--cmd/wmii/main.c470
-rw-r--r--cmd/wmii/map.c126
-rw-r--r--cmd/wmii/message.c1177
-rw-r--r--cmd/wmii/mouse.c642
-rw-r--r--cmd/wmii/print.c124
-rw-r--r--cmd/wmii/printevent.c994
-rw-r--r--cmd/wmii/printevent.h248
-rw-r--r--cmd/wmii/root.c89
-rw-r--r--cmd/wmii/rule.c107
-rw-r--r--cmd/wmii/screen.c189
-rw-r--r--cmd/wmii/utf.c60
-rw-r--r--cmd/wmii/view.c630
-rw-r--r--cmd/wmii/x11.c1317
-rw-r--r--cmd/wmii/xdnd.c88
-rw-r--r--cmd/wmii/xext.c160
-rw-r--r--cmd/wmii9menu.c341
-rw-r--r--cmd/wmiir.c421
-rw-r--r--config.mk63
-rw-r--r--debian/changelog267
-rw-r--r--debian/compat1
-rw-r--r--debian/control25
-rw-r--r--debian/copyright44
-rw-r--r--debian/desktop/wmii.desktop7
-rw-r--r--debian/icons/wmii.pngbin0 -> 301 bytes
-rw-r--r--debian/patches/01-x-terminal-emulator.patch15
-rw-r--r--debian/patches/02-cflags.patch15
-rw-r--r--debian/patches/03-font.patch65
-rw-r--r--debian/patches/series3
-rwxr-xr-xdebian/rules19
-rw-r--r--debian/source/format1
-rw-r--r--debian/source/include-binaries1
-rw-r--r--debian/source/options2
-rw-r--r--debian/wmii.docs1
-rw-r--r--debian/wmii.install2
-rw-r--r--debian/wmii.lintian-overrides1
-rw-r--r--debian/wmii.menu4
-rw-r--r--debian/wmii.wm1
-rw-r--r--doc/Makefile13
-rw-r--r--doc/floating.pngbin0 -> 599 bytes
-rw-r--r--doc/focused.pngbin0 -> 726 bytes
-rw-r--r--doc/managed.pngbin0 -> 579 bytes
-rw-r--r--doc/selected.pngbin0 -> 698 bytes
-rw-r--r--doc/unfocused.pngbin0 -> 754 bytes
-rw-r--r--doc/unselected.pngbin0 -> 787 bytes
-rw-r--r--doc/wmii.pdfbin0 -> 169718 bytes
-rw-r--r--doc/wmii.tex1497
-rw-r--r--img/icon.pngbin0 -> 515 bytes
-rw-r--r--img/mkfile41
-rw-r--r--img/wmii.eps29
-rw-r--r--img/wmii.mp19
-rw-r--r--img/wmii.pdfbin0 -> 2444 bytes
-rw-r--r--img/wmii.pngbin0 -> 1027 bytes
-rw-r--r--include/Makefile9
-rw-r--r--include/bio.h87
-rw-r--r--include/clientutil.h9
-rw-r--r--include/fmt.h155
-rw-r--r--include/ixp.h704
-rw-r--r--include/ixp_local.h91
-rw-r--r--include/ixp_srvutil.h69
-rw-r--r--include/plan9.h45
-rw-r--r--include/regcomp.h71
-rw-r--r--include/regexp9.h88
-rw-r--r--include/utf.h47
-rw-r--r--include/util.h91
-rw-r--r--include/x11.h292
-rw-r--r--libbio/Makefile27
-rw-r--r--libbio/NOTICE34
-rw-r--r--libbio/README5
-rw-r--r--libbio/bbuffered.c20
-rw-r--r--libbio/bcat.c46
-rw-r--r--libbio/bfildes.c9
-rw-r--r--libbio/bflush.c33
-rw-r--r--libbio/bgetc.c53
-rw-r--r--libbio/bgetd.c36
-rw-r--r--libbio/bgetrune.c47
-rw-r--r--libbio/binit.c154
-rw-r--r--libbio/bio.3371
-rw-r--r--libbio/boffset.c25
-rw-r--r--libbio/bprint.c14
-rw-r--r--libbio/bputc.c20
-rw-r--r--libbio/bputrune.c23
-rw-r--r--libbio/brdline.c94
-rw-r--r--libbio/brdstr.c112
-rw-r--r--libbio/bread.c45
-rw-r--r--libbio/bseek.c60
-rw-r--r--libbio/bvprint.c36
-rw-r--r--libbio/bwrite.c38
-rw-r--r--libfmt/Makefile48
-rw-r--r--libfmt/NOTICE25
-rw-r--r--libfmt/README5
-rw-r--r--libfmt/charstod.c85
-rw-r--r--libfmt/dofmt.c537
-rw-r--r--libfmt/dorfmt.c60
-rw-r--r--libfmt/errfmt.c28
-rw-r--r--libfmt/fltfmt.c392
-rw-r--r--libfmt/fmt.c218
-rw-r--r--libfmt/fmtdef.h112
-rw-r--r--libfmt/fmtfd.c46
-rw-r--r--libfmt/fmtfdflush.c34
-rw-r--r--libfmt/fmtinstall.3379
-rw-r--r--libfmt/fmtlock.c27
-rw-r--r--libfmt/fmtprint.c48
-rw-r--r--libfmt/fmtquote.c264
-rw-r--r--libfmt/fmtrune.c40
-rw-r--r--libfmt/fmtstr.c27
-rw-r--r--libfmt/fmtvprint.c49
-rw-r--r--libfmt/fprint.c29
-rw-r--r--libfmt/libfmt.abin0 -> 143602 bytes
-rw-r--r--libfmt/nan64.c67
-rw-r--r--libfmt/pow10.c57
-rw-r--r--libfmt/print.3482
-rw-r--r--libfmt/print.c29
-rw-r--r--libfmt/runefmtstr.c27
-rw-r--r--libfmt/runeseprint.c30
-rw-r--r--libfmt/runesmprint.c30
-rw-r--r--libfmt/runesnprint.c31
-rw-r--r--libfmt/runesprint.c30
-rw-r--r--libfmt/runevseprint.c40
-rw-r--r--libfmt/runevsmprint.c91
-rw-r--r--libfmt/runevsnprint.c39
-rw-r--r--libfmt/seprint.c29
-rw-r--r--libfmt/smprint.c29
-rw-r--r--libfmt/snprint.c30
-rw-r--r--libfmt/sprint.c37
-rw-r--r--libfmt/strtod.c532
-rw-r--r--libfmt/test.c44
-rw-r--r--libfmt/test2.c9
-rw-r--r--libfmt/test3.c52
-rw-r--r--libfmt/vfprint.c33
-rw-r--r--libfmt/vseprint.c39
-rw-r--r--libfmt/vsmprint.c88
-rw-r--r--libfmt/vsnprint.c39
-rw-r--r--libixp/LICENSE21
-rw-r--r--libixp/Makefile23
-rw-r--r--libixp/README14
-rw-r--r--libixp/client.c676
-rw-r--r--libixp/convert.c200
-rw-r--r--libixp/error.c103
-rw-r--r--libixp/map.c132
-rw-r--r--libixp/message.c205
-rw-r--r--libixp/request.c550
-rw-r--r--libixp/rpc.c262
-rw-r--r--libixp/server.c165
-rw-r--r--libixp/socket.c278
-rw-r--r--libixp/srv_util.c457
-rw-r--r--libixp/thread.c97
-rw-r--r--libixp/timer.c139
-rw-r--r--libixp/transport.c97
-rw-r--r--libixp/util.c240
-rw-r--r--libregexp/Makefile17
-rw-r--r--libregexp/NOTICE25
-rw-r--r--libregexp/README5
-rw-r--r--libregexp/regaux.c112
-rw-r--r--libregexp/regcomp.c559
-rw-r--r--libregexp/regerror.c15
-rw-r--r--libregexp/regexec.c232
-rw-r--r--libregexp/regexp9.3220
-rw-r--r--libregexp/regexp9.7141
-rw-r--r--libregexp/regsub.c63
-rw-r--r--libregexp/rregexec.c213
-rw-r--r--libregexp/rregsub.c63
-rw-r--r--libregexp/test.c46
-rw-r--r--libregexp/test2.c20
-rw-r--r--libutf/Makefile30
-rw-r--r--libutf/NOTICE25
-rw-r--r--libutf/README5
-rw-r--r--libutf/isalpharune.357
-rw-r--r--libutf/libutf.abin0 -> 59448 bytes
-rw-r--r--libutf/rune.3194
-rw-r--r--libutf/rune.c175
-rw-r--r--libutf/runestrcat.374
-rw-r--r--libutf/runestrcat.c24
-rw-r--r--libutf/runestrchr.c35
-rw-r--r--libutf/runestrcmp.c35
-rw-r--r--libutf/runestrcpy.c28
-rw-r--r--libutf/runestrdup.c27
-rw-r--r--libutf/runestrecpy.c32
-rw-r--r--libutf/runestrlen.c21
-rw-r--r--libutf/runestrncat.c32
-rw-r--r--libutf/runestrncmp.c37
-rw-r--r--libutf/runestrncpy.c33
-rw-r--r--libutf/runestrrchr.c30
-rw-r--r--libutf/runestrstr.c45
-rw-r--r--libutf/runetype.c1151
-rw-r--r--libutf/utf.799
-rw-r--r--libutf/utfecpy.c36
-rw-r--r--libutf/utflen.c37
-rw-r--r--libutf/utfnlen.c41
-rw-r--r--libutf/utfrrune.c45
-rw-r--r--libutf/utfrune.c44
-rw-r--r--libutf/utfutf.c41
-rw-r--r--libwmii_hack/Makefile15
-rw-r--r--libwmii_hack/hack.c134
-rw-r--r--libwmii_hack/hack.h28
-rw-r--r--libwmii_hack/util.c101
-rw-r--r--libwmii_hack/x11.c212
-rw-r--r--libwmii_hack/x11.h18
-rw-r--r--man/Makefile13
-rw-r--r--man/header.t2t31
-rw-r--r--man/mkfile9
-rw-r--r--man/wimenu.1204
-rw-r--r--man/wimenu.man1174
-rw-r--r--man/wmii.1575
-rw-r--r--man/wmii.man1477
-rw-r--r--man/wmii9menu.166
-rw-r--r--man/wmii9menu.man158
-rw-r--r--man/wmiir.191
-rw-r--r--man/wmiir.man177
-rw-r--r--mk/common.mk34
-rw-r--r--mk/dir.mk33
-rw-r--r--mk/gcc.mk28
-rw-r--r--mk/hdr.mk155
-rw-r--r--mk/ixp.mk5
-rw-r--r--mk/lib.mk32
-rw-r--r--mk/man.mk9
-rw-r--r--mk/many.mk20
-rw-r--r--mk/one.mk26
-rw-r--r--mk/so.mk29
-rw-r--r--mk/wmii.mk17
-rw-r--r--rc/Makefile9
-rwxr-xr-xrc/sh.wmii289
-rw-r--r--rc/welcome.sh61
-rw-r--r--rc/wmiirc.sh269
-rw-r--r--test/Makefile14
-rwxr-xr-xtest/event19
-rw-r--r--test/event.b148
-rw-r--r--test/grav.c128
-rw-r--r--test/mkfile11
-rwxr-xr-xutil/cleanname18
-rwxr-xr-xutil/compile70
-rwxr-xr-xutil/genconfig236
-rwxr-xr-xutil/link37
322 files changed, 44544 insertions, 0 deletions
diff --git a/DISTRIBUTORS b/DISTRIBUTORS
new file mode 100644
index 0000000..fceee2c
--- /dev/null
+++ b/DISTRIBUTORS
@@ -0,0 +1,88 @@
+Distributers
+============
+
+This file only applies to those who wish to distribute wmii in any
+form. The conditions herein do not apply to end users in any manner
+whatsoever.
+
+License Terms
+=============
+
+wmii is licensed under the liberal MIT License. This license allows
+for free modification and distribution of the source code, so long
+as credit is given to the author. To this end, the LICENSE file, or
+an equivalent statement of its contents, MUST be distributed with
+any significant binary or source portions of this software. The file
+SHOULD be included with the software's documentation. The default
+installation sequence provides for this.
+
+Note that this condition only applies to distribution, and that the
+end use is under no obligation to keep or install a copy of the
+LICENSE file. Note also that this software may be sublicensed under
+more restrictive terms, though the original LICENSE text MUST remain.
+
+The wmii Name
+=============
+
+The following conditions apply to any distribution which uses the
+name wmii. These conditions apply only to wmii name, and not to its
+source code or any other included materials.
+
+When in doubt about any of these conditions or other matters of
+packaging or distribution, please contact the wmii mailing lists at
+<dev@suckless.org> or Kris Maglione <maglione.k@gmail.com>. The
+conditions herein MAY be contravened by any more lenient
+distribution terms agreed upon by the latter, which SHOULD replace
+this file in the form of a PGP signed permissions notice.
+
+Version Strings
+---------------
+
+Any binary distribution of wmii MUST have a properly set VERSION
+string. This is the string printed by the 'wmii' binary when
+invoked with the '-v' flag.
+
+This string may normally be set in 'mk/wmii.mk'. Unmodified builds
+from the Mercurial tree automatically set this string based on the
+Mercurial local revision number, so long as the 'hg' command is
+present and properly functioning.
+
+Any version which is an official release, alpha, or beta, MUST
+contain the release version. Alpha and beta releases MUST be
+proceeded directly by "a" or "b" followed by the alpha or beta
+number respectively. wmii 4.0, Alpha 3, for instance, MUST be
+formatted as 4.0a3
+
+Any version which is not an official release or snapshot MUST be
+contain the Mercurial local revision number or changeset hash in its
+version string. The local revion number MUST be within 5 revisions
+of the equivalent changeset in the official canonical repositories
+at http://hg.suckless.org/ and http://wmii.googlecode.com/. This
+SHOULD be formatted as hgXXXX, where XXXX is the decimal revision
+number.
+
+The version string of any official snapshot release MUST, if it does
+not contain Mercurial revision information as above, contain the
+date of the snapshot in the form YYYYMMDD, and SHOULD contain the
+word snap or snapshot.
+
+The version string of a snapshot MAY contain the version name of a
+full release that the snapshot is expected to precede, but it MUST
+be either directly preceded, or directly followed by, the word
+'pre', optionally separated by a non-alphanumeric character,
+including -~_,./.
+
+Modifications
+-------------
+
+Any binary distribution which is modified in any non-trivial way
+MUST signify the modifications in its name or version string. This
+DOES NOT include minor patches to improve consistency with the rest
+of the system, including changing the default terminal emulator,
+POSIX-compliant shell, or installation prefix.
+
+Source form distribution MAY include non-trivial patches without
+such modifications, provided that the user is made clearly aware of
+them at build or install time and/or prompted in some way to enable
+or disable them.
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..e186d2e
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,22 @@
+
+Copyright © 2006-2010 Kris Maglione <maglione.k@gmail.com>
+Copyright © 2003-2006 Anselm R Garbe <anselm@garbe.us>
+Portions Copyright © 2002 by Lucent Technologies.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..d5f8552
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,33 @@
+ROOT=.
+include $(ROOT)/mk/hdr.mk
+include $(ROOT)/mk/wmii.mk
+
+PDIRS = \
+ doc \
+ man \
+ cmd \
+ libwmii_hack \
+ rc \
+ alternative_wmiircs
+
+DIRS = \
+ libbio \
+ libfmt \
+ libregexp \
+ libutf \
+ libixp \
+ $(PDIRS)
+
+DOCS = README \
+ LICENSE
+
+deb-dep:
+ IFS=', '; \
+ apt-get -qq install build-essential $$(sed -n 's/([^)]*)//; s/^Build-Depends: \(.*\)/\1/p' debian/control)
+
+deb:
+ dpkg-buildpackage -rfakeroot -b -nc
+
+include ${ROOT}/mk/dir.mk
+INSTDIRS = $(PDIRS)
+
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..e9b90ad
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,83 @@
+3.9.2:
+ * Work around mawk bug that broke wmiirc.
+
+3.9.1:
+ * Workaround a dash 0.5.6 bug that broke wmiirc.
+ * Noticably speed-up python wmiirc startup.
+ * Disable static linking which breaks wmiir in glibc 2.12.
+ * Add PKGBUILD.
+
+3.9:
+ * wmii9menu is now Xinerama aware.
+ * Install READMEs to $(PREFIX)/share/doc/wmii/.
+ * Documentation updates. Add wmiir.1, wmii9menu.1.
+ * Allow dragging floating clients from anywhere in their titlebars.
+ * Allow specifying screen in area specs.
+ * Change default $MODKEY to Mod4.
+ * Minor changes to pygmi.events API.
+ * Allow client to follow tag change in python wmiirc.
+ * Update /tag/*/index to be more useful on Xinerama.
+ * Add showkeys action to shell and python wmiirc.
+ * Restore windows from floating layer to their original Xinerama screen.
+ * Hide bar on non-primary Xinerama screens.
+ * Allow resizing of rightmost and leftmost column dividers.
+
+3.9a2:
+ * Add Suraj's Rumai-based wmiirc.
+ * Move rc.wmii to alternative_wmiircs/plan9port/wmiirc.
+ * Install wmii.pdf to $(PREFIX)/share/doc/.
+ * Focus windows regardless of whether they form a new group.
+ * Update selection and execution of wmiirc: no more magic.
+ * Update wmii.1
+ * Add alternative_wmiircs READMEs.
+
+3.9a1:
+ * Add new wmii guide. See doc/wmii.pdf
+ * Allow for programmable completion in wimenu.
+ * Use pkg-config globally.
+ * Add Xft (antialiased font) support.
+ * Add python wmiirc/9P client library
+ * Allow bindings to work regardless of caps lock.
+ * Add M-f fullscreen toggle key binding.
+ * Augment /client/*/ctl Fullscreen command.
+ * Allow setting of increment display from /ctl.
+ * Show a client's extra tags in its titlebar.
+ * Darken background when floating area selected.
+ * Allow bar on top or bottom.
+ * Allow for wmiirc_local.
+ * Add grow and nudge commands to /tag/*/ctl.
+ * Cascade windows when the floating layer fills.
+ * Support alpha-transparant windows.
+ * Add regex tag support.
+ * It is now possible to float/unfloat windows with the mouse.
+ * Make the bar Xdnd aware; DND between views is now possible. Fixed some window raising/moving bugs.
+ * Add a notification bar.
+ * Improved floating mouse resizing.
+ * Improved mouse move/resize support for managed mode.
+ * Better return from floating/fullscreen to managed mode.
+ * Allow comments (#.*\n) in rules and ctl files.
+ * Add /client/*/ctl ‘slay’ command.
+ * Detect unresponsive clients on ‘kill’.
+ * Draw titlebars of floating clients differently.
+ * Add wihack: LD_PRELOAD hack to set window properties of programs:
+ * Respect window groups
+ * Add ‘Kill’ to client right-click menu
+ * wmii9menu now takes similar args to wimenu
+ * Document grow/nudge commands.
+ * Add wimenu with history and caret support
+ * Add wistrut. Undocumented, not built by default.
+ * EWMH strut support.
+ * Basic EWMH support.
+ * Better fullscreen support.
+ * XRandR support.
+ * Xinerama support.
+
+2008-08-25:
+ * libixp version 97 now required
+ * Stack and max modes now affect floating clients:
+ - max: Collapsed clients disappear, all clients disappear
+ when managed layer is selected.
+ - stack: All clients but selected are collapsed.
+ * Adobe's Flash plugin's fullscreen mode now works.
+ * Some annoying focus bugs are fixed.
+
diff --git a/NOTES b/NOTES
new file mode 100644
index 0000000..ae3f060
--- /dev/null
+++ b/NOTES
@@ -0,0 +1,6 @@
+3.6 Release Notes
+
+wmiirc users: the semantics of WMII_MENU, WMII_9MENU and WMII_TERM
+have changed. If you're using them in custom scripts you'll need to
+change them to "eval $WMII_MENU" instead of just "$WMII_MENU".
+
diff --git a/PKGBUILD b/PKGBUILD
new file mode 100644
index 0000000..fbdf95b
--- /dev/null
+++ b/PKGBUILD
@@ -0,0 +1,31 @@
+
+pkgname=wmii
+pkgver=3.9.2
+pkgrel=1
+pkgdesc="A lightweight, dynamic window manager for X11"
+url="http://wmii.suckless.org"
+license=("MIT")
+arch=("i686" "x86_64")
+depends=("libx11" "libxft" "libxinerama" "libxrandr")
+makedepends=("mercurial")
+optdepends=("plan9port: for use of the alternative plan9port wmiirc" \
+ "python: for use of the alternative Python wmiirc" \
+ "ruby-rumai: for use of the alternative Ruby wmiirc")
+provides=("wmii")
+conflicts=("wmii")
+source=()
+
+build()
+{
+ cd $startdir
+ flags=(PREFIX=/usr \
+ ETC=/etc \
+ DESTDIR="$pkgdir")
+
+ make "${flags[@]}" || return 1
+ make "${flags[@]}" install || return 1
+
+ mkdir -p $pkgdir/etc/X11/sessions
+ install -m644 -D ./LICENSE $pkgdir/usr/share/licenses/wmii/LICENSE
+}
+
diff --git a/README b/README
new file mode 100644
index 0000000..de82fd7
--- /dev/null
+++ b/README
@@ -0,0 +1,92 @@
+Abstract
+--------
+wmii is a dynamic window manager for X11. It supports classic and
+tiled window management with extended keyboard, mouse, and 9P-based[1]
+remote control. It consists of the wmii(1) window manager and the
+wmiir(1) the remote access utility.
+
+
+Requirements
+------------
+In order to build wmii you need the Xlib header files and libixp.
+xmessage is used by the default scripts. Libixp, if not provided, can
+be obtained from http://libs.suckless.org/. On debian, you should be
+able to obtain all dependencies by running `make deb-dep`. Python is
+recommended for more advanced configurations.
+
+
+Installation
+------------
+First, edit config.mk to match your local setup.
+
+To build, simply run:
+ make
+
+To install, run the following, as root if necessary:
+ make install
+
+On debian, you should only have to run `make deb` to create a debian
+package. No further configuration should be necessary.
+
+
+Running wmii
+------------
+Add the following line to your .xinitrc to start wmii using startx:
+
+ until wmii; do :; done
+
+In order to connect wmii to a specific display, make sure that the
+DISPLAY environment variable is set correctly. For example:
+
+ DISPLAY=:1 wmii
+
+This will start wmii on display :1.
+
+
+Configuration
+-------------
+The configuration of wmii is done by customizing the rc script wmiirc,
+which remotely controls the window manager and handles various events.
+The main wmiirc script lives in @CONFPREFIX@/wmii@CONFVERSION@/, while
+wmiirc_local goes in $HOME/.wmii@CONFVERSION@/.
+
+More advanced versions of wmiirc are provided in python and ruby.
+For more information on them, see alternative_wmiircs/README.
+
+Credits
+-------
+The following people have contributed especially to wmii in various
+ways:
+
+- Christoph Wegscheider <christoph dot wegscheider at wegi dot net>
+- Georg Neis <gn at suckless dot org>
+- Uwe Zeisberger <zeisberg at informatik dot uni-freiburg dot de>
+- Uriel <uriel99 at gmail dot com>
+- Scot Doyle <scot at scotdoyle dot com>
+- Sebastian Hartmann <seb dot wmi at gmx dot de>
+- Bernhard Leiner <bleiner at gmail dot com>
+- Jonas Domeij <jonas dot domeij at gmail dot com>
+- Vincent <10 dot 50 at free dot fr>
+- Oliver Kopp <olly at flupp dot de>
+- Sebastian Roth <sebastian dot roth at gmail dot com>
+- Nico Golde <nico at ngolde dot de>
+- Steve Hoffman <steveh at g2switchworks dot com>
+- Christof Musik <christof at senfdax dot de>
+- Steffen Liebergeld <perl at gmx dot org>
+- Tobias Walkowiak <wal at ivu dot de>
+- Sander van Dijk <a dot h dot vandijk at gmail dot com>
+- Salvador Peiro <saoret dot one at gmail dot com>
+- Anthony Martin <ality at pbrane dot org>
+- Icarus Sparry <wmii at icarus dot freeuk dot com>
+- Norman Golisz <norman dot golisz at arcor dot de>
+- Stefano K. Lee <wizinblack at gmail dot com >
+- Stefan Tibus <sjti at gmx dot net>
+- Neptun <neptun at gmail dot com>
+- Daniel Wäber <_wabu at web dot de>
+
+
+References
+----------
+[1] http://9p.cat-v.org
+[2] http://plan9.us
+
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..3f57b0d
--- /dev/null
+++ b/TODO
@@ -0,0 +1,13 @@
+BUGS
+* collapsed clients outside stacked mode don't always uncollapse when they receive focus
+* various qiv brokenness
+* dosbox won't grab the mouse
+
+4.0
+* Opaque managed moves. I know I've argued against it, but it may be doable.
+* Clicking layout boxes should do useful things.
+* Collapse/uncollapse frames with the keyboard.
+* Open modes (to replace colmodes).
+* Resizable managed area. Maybe. Struts seem to do everything this might.
+* New dmenu, with real cursor; snarfable.
+
diff --git a/alternative_wmiircs/Makefile b/alternative_wmiircs/Makefile
new file mode 100644
index 0000000..066739c
--- /dev/null
+++ b/alternative_wmiircs/Makefile
@@ -0,0 +1,13 @@
+ROOT=..
+include $(ROOT)/mk/hdr.mk
+include $(ROOT)/mk/wmii.mk
+
+BIN = $(ETC)/wmii$(CONFVERSION)
+DIRS = python \
+ plan9port \
+ ruby
+
+DOCS = README
+DOCDIR = $(DOC)/alternative_wmiircs
+
+include $(ROOT)/mk/dir.mk
diff --git a/alternative_wmiircs/README b/alternative_wmiircs/README
new file mode 100644
index 0000000..a98d4fb
--- /dev/null
+++ b/alternative_wmiircs/README
@@ -0,0 +1,23 @@
+Alternative wmiirc scripts
+==========================
+
+This folder contains alternative implementations of wmii's rc
+scripts. Each folder contains a different implementation,
+described below, including its own README, wmiirc script, and
+possibly other suppporting files and libraries. These scripts
+are installed along with wmii to $(ETC) as defined in config.mk.
+
+It usually suffices to start the included `wmiirc` script at
+wmii startup. Invoking wmii with the flag '-r python/wmiirc',
+for instance, will start the python implementation.
+Alternatively, if you use a session manager, you can add this
+line to ~/.wmii/wmiirc (which must be executable):
+
+ wmiir xwrite /ctl spawn python/wmiirc
+
+ Index
+ ------------- ----------------------------------------------------
+ python/ A pure Python wmiirc implementation.
+ plan9port/ A Plan 9 Port/rc shell based wmiirc implementation
+ ruby/ A pure-ruby wmiirc implementation, by Suraj Kurapati
+
diff --git a/alternative_wmiircs/plan9port/Makefile b/alternative_wmiircs/plan9port/Makefile
new file mode 100644
index 0000000..e582ccf
--- /dev/null
+++ b/alternative_wmiircs/plan9port/Makefile
@@ -0,0 +1,9 @@
+ROOT=../..
+include $(ROOT)/mk/hdr.mk
+include $(ROOT)/mk/wmii.mk
+
+DOCS = README
+EXECS = wmiirc
+
+DIR = $(ETC)/wmii$(CONFVERSION)/plan9port
+DOCDIR = $(DOC)/alternative_wmiircs/plan9port
diff --git a/alternative_wmiircs/plan9port/README b/alternative_wmiircs/plan9port/README
new file mode 100644
index 0000000..37d388d
--- /dev/null
+++ b/alternative_wmiircs/plan9port/README
@@ -0,0 +1,10 @@
+plan9port wmiirc
+================
+
+This directory contains a Plan 9 based wmiirc script. This script was
+traditionally the default wmiirc for wmii, but has been moved for
+portability reasons. To run this script, either Plan 9 from User
+Space[1] (plan9port for short) or 9base[2] is required. Modifications
+can be placed in $home/.wmii@CONFVERSION@/wmiirc_local.rc, which must
+be executable.
+
diff --git a/alternative_wmiircs/plan9port/wmiirc b/alternative_wmiircs/plan9port/wmiirc
new file mode 100755
index 0000000..1724bdf
--- /dev/null
+++ b/alternative_wmiircs/plan9port/wmiirc
@@ -0,0 +1,284 @@
+#!/bin/sh -f
+p="$PATH"
+which rc >/dev/null || PATH="$PLAN9:$p"
+which rc >/dev/null || PATH="/usr/local/plan9/bin:$p"
+which rc >/dev/null || PATH="/usr/local/9/bin:$p"
+which rc >/dev/null || PATH="/opt/plan9/bin:$p"
+which rc >/dev/null || PATH="/opt/9/bin:$p"
+which rc >/dev/null || PATH="/usr/plan9/bin:$p"
+which rc >/dev/null || PATH="/usr/9/bin:$p"
+test $#* '=' 0 || exec rc $0
+
+cd
+scriptname=$0
+oldpath=$path; path=($PLAN9/bin $path)
+. wmii.rc wmiirc # Include utility functions
+
+# WMII Configuration
+
+# Keys
+MODKEY=Mod4
+UP=k
+DOWN=j
+LEFT=h
+RIGHT=l
+
+# Bars
+noticetimeout=5
+noticebar=/rbar/!notice
+
+# Theme
+wmiifont='drift,-*-fixed-*-*-*-*-9-*-*-*-*-*-*-*'
+wmiifont='-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*'
+wmiinormcol=`{echo '#000000 #c1c48b #81654f'}
+wmiifocuscol=`{echo '#000000 #81654f #000000'}
+wmiibackground='#333333'
+wmiifloatbackground='#222222'
+fn setbackground { xsetroot -solid $* }
+
+# Programs
+WMII_TERM=(xterm)
+
+# Column Rules
+wmiir write /colrules <<!
+/gimp/ -> 17+83+41
+/.*/ -> 62+38 # Golden Ratio
+!
+
+# Tagging Rules
+wmiir write /tagrules <<!
+/MPlayer|VLC/ -> ~
+!
+
+# Status Bar Info
+fn status {
+ echo -n `{uptime | sed 's/.*://; s/,//g'} \
+ '|' `{date}
+}
+
+# End Configuration
+
+# For the time being, this file follows the lisp bracing
+# convention. i.e.:
+# if(frob this) {
+# frob that
+# if(frob theother) {
+# unfrob this
+# unfrob that}}
+# Comments welcome.
+
+confpath=`{echo $WMII_CONFPATH | sed 'y/:/ /'}
+
+# Events
+fn sigexit {
+ rm -f $progs_file
+ wi_cleankeys
+}
+
+fn Event-CreateTag {
+ echo $wmiinormcol $* | wmiir create /lbar/$"*}
+fn Event-DestroyTag {
+ wmiir remove /lbar/$"*}
+fn Event-FocusTag {
+ wmiir xwrite /lbar/$"* $wmiifocuscol $*}
+fn Event-UnfocusTag {
+ wmiir xwrite /lbar/$"* $wmiinormcol $*}
+fn Event-UrgentTag {
+ shift
+ wmiir xwrite /lbar/$"* '*'$"*}
+fn Event-NotUrgentTag {
+ shift
+ wmiir xwrite /lbar/$"* $"*}
+fn Event-AreaFocus {
+ if(~ $1 '~')
+ setbackground $wmiifloatbackground
+ if not
+ setbackground $wmiibackground }
+
+fn Event-Unresponsive {
+ client = $1; shift
+ @{
+ msg = 'The following client is not responding. What would you like to do?'
+ resp = `{wihack -transient $client \
+ xmessage -nearmouse -buttons Kill,Wait -print \
+ $msg $wi_newline '' `{wmiir read /client/$client/label}}
+ if(~ $resp Kill)
+ wmiir xwrite /client/$client/ctl slay
+ }&}
+fn Event-Notice {
+ wmiir xwrite $noticebar $wi_arg
+
+ /bin/kill $xpid >[2]/dev/null # Let's hope this isn't reused...
+ { sleep $noticetimeout; wmiir xwrite $noticebar ' ' }& # Bug...
+ xpid = $apid}
+
+fn Event-LeftBar^(Click DND) {
+ shift; wmiir xwrite /ctl view $*}
+
+fn ClientMenu-3-Delete {
+ wmiir xwrite /client/$1/ctl kill}
+fn ClientMenu-3-Kill {
+ wmiir xwrite /client/$1/ctl slay}
+fn ClientMenu-3-Fullscreen {
+ wmiir xwrite /client/$1/ctl Fullscreen on}
+fn Event-ClientMouseDown {
+ wi_fnmenu Client $2 $1 &}
+
+fn LBarMenu-3-Delete {
+ tag=$1; clients=`{wmiir read /tag/$tag/index | awk '/[^#]/{print $2}'}
+ for(c in $clients) {
+ if(~ $tag `{wmiir read /client/$c/tags})
+ wmiir xwrite /client/$c/ctl kill
+ if not
+ wmiir xwrite /client/$c/tags -$tag}
+ if(~ $tag `{wi_seltag}) {
+ newtag = `{wi_tags | awk -v't='$tag '
+ $1 == t { if(!l) getline l
+ print l
+ exit }
+ { l = $0 }'}
+ wmiir xwrite /ctl view $newtag}}
+fn Event-LeftBarMouseDown {
+ wi_fnmenu LBar $* &}
+
+# Actions
+fn Action-rehash {
+ comm -23 <{ls `{namespace}^/proglist.* >[2]/dev/null | awk -F'.' '{print $NF}'} \
+ <{ps | awk '{print $2}'} |
+ while(id=`{read})
+ rm `{namespace}^/proglist.$id
+ wi_proglist $PATH >$progs_file}
+fn Action-quit {
+ wmiir xwrite /ctl quit}
+fn Action-exec {
+ wmiir xwrite /ctl exec $*}
+fn Action-status {
+ flag x -; flag r -
+ if(wmiir remove /rbar/status >[2]/dev/null)
+ sleep 2
+ echo $wmiinormcol | wmiir create /rbar/status
+ while(status | wmiir write /rbar/status)
+ sleep 1
+}
+
+# Source Variables, &c
+if(~ $0 ('' */)wmiirc_local.rc)
+ wi_notice This file should not be named wmiirc_local.rc
+if not
+ . `{wi_script -f wmiirc_local.rc}
+echo $wmiinormcol | wmiir create $noticebar
+
+# Key Bindings
+_keys = `{wi_getfuns Key}
+fn key {
+ key=()
+ for(k) if(! ~ $k $_keys) key = ($key Key-$k)
+ ~ $#key 0}
+
+# This is... ugly.
+
+key $MODKEY-Control-t || fn $key {
+ switch(`{wmiir read /keys | wc -l}) {
+ case 0 1
+ wmiir xwrite /keys $keys
+ wmiir xwrite /ctl grabmod $MODKEY
+ case *
+ ifs=() { keys=`{wmiir read /keys} }
+ wmiir xwrite /keys $MODKEY-Control-t
+ wmiir xwrite /ctl grabmod Mod3
+ }}
+
+key $MODKEY-$LEFT || fn $key {
+ wmiir xwrite /tag/sel/ctl select left}
+key $MODKEY-$RIGHT || fn $key {
+ wmiir xwrite /tag/sel/ctl select right}
+key $MODKEY-$DOWN || fn $key {
+ wmiir xwrite /tag/sel/ctl select down}
+key $MODKEY-$UP || fn $key {
+ wmiir xwrite /tag/sel/ctl select up}
+key $MODKEY-Control-$DOWN || fn $key {
+ wmiir xwrite /tag/sel/ctl select down stack}
+key $MODKEY-Control-$UP || fn $key {
+ wmiir xwrite /tag/sel/ctl select up stack}
+
+key $MODKEY-Shift-$LEFT || fn $key {
+ wmiir xwrite /tag/sel/ctl send sel left}
+key $MODKEY-Shift-$RIGHT || fn $key {
+ wmiir xwrite /tag/sel/ctl send sel right}
+key $MODKEY-Shift-$DOWN || fn $key {
+ wmiir xwrite /tag/sel/ctl send sel down}
+key $MODKEY-Shift-$UP || fn $key {
+ wmiir xwrite /tag/sel/ctl send sel up}
+
+key $MODKEY-f || fn $key {
+ wmiir xwrite /client/sel/ctl Fullscreen toggle}
+
+key $MODKEY-space || fn $key {
+ wmiir xwrite /tag/sel/ctl select toggle}
+key $MODKEY-Shift-space || fn $key {
+ wmiir xwrite /tag/sel/ctl send sel toggle}
+key $MODKEY-d || fn $key {
+ wmiir xwrite /tag/sel/ctl colmode sel default-max}
+key $MODKEY-s || fn $key {
+ wmiir xwrite /tag/sel/ctl colmode sel stack-max}
+key $MODKEY-m || fn $key {
+ wmiir xwrite /tag/sel/ctl colmode sel stack+max}
+
+key $MODKEY-Shift-c || fn $key {
+ wmiir xwrite /client/sel/ctl kill}
+
+key $MODKEY-a || fn $key {
+ Action `{wi_actions | wimenu -h $hist.action -n $histlen} &}
+key $MODKEY-p || fn $key {
+ ifs=() { cmd = `{wimenu -h $hist.prog -n $histlen <$progs_file} }
+ wi_runcmd $cmd & }
+
+key $MODKEY-Return || fn $key {
+ wi_runcmd $WMII_TERM &}
+
+key $MODKEY-t || fn $key {
+ tag=`{wi_tags | wimenu -h $hist.tag -n 50} && wmiir xwrite /ctl view $tag &}
+key $MODKEY-Shift-t || fn $key {
+ sel=`{wi_selclient} {
+ tag=`{wi_tags | wimenu -h $hist.tag -n 50} && wmiir xwrite /client/$sel/tags $tag } &}
+
+key $MODKEY-^`{seq 0 9} || fn $key {
+ wmiir xwrite /ctl view `{echo $1 | sed 's/.*-//'}}
+key Shift-$MODKEY-^`{seq 0 9} || fn $key {
+ wmiir xwrite /client/sel/tags `{echo $1 | sed 's/.*-//'}}
+
+#` WM Configuration
+wmiir write /ctl <<!
+ grabmod $MODKEY
+ border 2
+ font $wmiifont
+ focuscolors $wmiifocuscol
+ normcolors $wmiinormcol
+!
+setbackground $wmiibackground
+
+# Source Overrides
+Action overridekeys
+
+# Misc Setup
+progs_file=`{namespace}^/proglist.$pid
+hist=`{echo $WMII_CONFPATH | sed 's,:.*,/,'}^/history
+histlen=5000
+Action status &
+Action rehash &
+
+# Tag Bar Setup
+ifs=$wi_newline {
+ rc -c 'wmiir rm /lbar/^$*' >[2]/dev/null \
+ `{comm -23 <{wmiir ls /lbar} \
+ <{wi_tags}}
+ seltag=`{wi_seltag}
+ for(tag in `{wi_tags}) {{
+ if(~ $tag $seltag)
+ echo $wmiifocuscol $tag
+ if not
+ echo $wmiinormcol $tag
+ } | wmiir create /lbar/$tag}}
+
+wi_eventloop
+
diff --git a/alternative_wmiircs/python/Makefile b/alternative_wmiircs/python/Makefile
new file mode 100644
index 0000000..6c2a9ab
--- /dev/null
+++ b/alternative_wmiircs/python/Makefile
@@ -0,0 +1,14 @@
+ROOT=../..
+include $(ROOT)/mk/hdr.mk
+include $(ROOT)/mk/wmii.mk
+
+DOCS = README
+EXECS = wmiirc
+TEXT = wmiirc.py
+DIRS = pygmi \
+ pyxp
+
+DIR = $(ETC)/wmii$(CONFVERSION)/python
+DOCDIR = $(DOC)/alternative_wmiircs/python
+
+include $(ROOT)/mk/dir.mk
diff --git a/alternative_wmiircs/python/README b/alternative_wmiircs/python/README
new file mode 100644
index 0000000..97bece9
--- /dev/null
+++ b/alternative_wmiircs/python/README
@@ -0,0 +1,16 @@
+Python wmiirc
+=============
+
+This directory contains a pure Python implementation of
+wmiirc. The two included libraries, pyxp and pygmi, are a 9P
+client and wmii filesystem utility module, respectively. To
+use this library, simply copy the contents of this direcctory
+to ~/.wmii/. To customize it, either modify wmiirc.py
+directly, or create wmii_local.py and store your modifications
+there. The latter approach is preferable in that future
+modifications to wmiirc.py can usually be painlessly
+integrated.
+
+The documentation is sparse, but wmiirc.py should serve as a
+fairly comprehensive example.
+
diff --git a/alternative_wmiircs/python/pygmi/Makefile b/alternative_wmiircs/python/pygmi/Makefile
new file mode 100644
index 0000000..24bfa69
--- /dev/null
+++ b/alternative_wmiircs/python/pygmi/Makefile
@@ -0,0 +1,12 @@
+ROOT=../../..
+include $(ROOT)/mk/hdr.mk
+include $(ROOT)/mk/wmii.mk
+
+BINARY = __init__.py \
+ event.py \
+ fs.py \
+ menu.py \
+ monitor.py \
+ util.py
+
+DIR = $(ETC)/wmii$(CONFVERSION)/python/pygmi
diff --git a/alternative_wmiircs/python/pygmi/__init__.py b/alternative_wmiircs/python/pygmi/__init__.py
new file mode 100644
index 0000000..6ec1ddc
--- /dev/null
+++ b/alternative_wmiircs/python/pygmi/__init__.py
@@ -0,0 +1,27 @@
+import os
+import sys
+
+from pyxp.asyncclient import Client
+
+if 'WMII_ADDRESS' in os.environ:
+ client = Client(os.environ['WMII_ADDRESS'])
+else:
+ client = Client(namespace='wmii')
+
+confpath = os.environ.get('WMII_CONFPATH', '%s/.wmii' % os.environ['HOME']).split(':')
+shell = os.environ['SHELL']
+
+sys.path += confpath
+
+from pygmi.util import *
+from pygmi.event import *
+from pygmi.fs import *
+from pygmi.menu import *
+from pygmi.monitor import *
+from pygmi import util, event, fs, menu, monitor
+
+__all__ = (fs.__all__ + monitor.__all__ + event.__all__ +
+ menu.__all__ + util.__all__ +
+ ('client', 'confpath', 'shell'))
+
+# vim:se sts=4 sw=4 et:
diff --git a/alternative_wmiircs/python/pygmi/event.py b/alternative_wmiircs/python/pygmi/event.py
new file mode 100644
index 0000000..c56460a
--- /dev/null
+++ b/alternative_wmiircs/python/pygmi/event.py
@@ -0,0 +1,304 @@
+import os
+import re
+import sys
+import traceback
+
+import pygmi
+from pygmi.util import prop
+from pygmi import monitor, client, curry, call, program_list, _
+
+__all__ = ('keys', 'events', 'Match')
+
+class Match(object):
+ """
+ A class used for matching events based on simple patterns.
+ """
+ def __init__(self, *args):
+ """
+ Creates a new Match object based on arbitrary arguments
+ which constitute a match pattern. Each argument matches an
+ element of the original event. Arguments are matched based
+ on their type:
+
+ _: Matches anything
+ set: Matches any string equal to any of its elements
+ list: Matches any string equal to any of its elements
+ tuple: Matches any string equal to any of its elements
+
+ Additionally, any type with a 'search' attribute matches if
+ that callable attribute returns True given element in
+ question as its first argument.
+
+ Any other object matches if it compares equal to the
+ element.
+ """
+ self.args = args
+ self.matchers = []
+ for a in args:
+ if a is _:
+ a = lambda k: True
+ elif isinstance(a, basestring):
+ a = a.__eq__
+ elif isinstance(a, (list, tuple, set)):
+ a = curry(lambda ary, k: k in ary, a)
+ elif hasattr(a, 'search'):
+ a = a.search
+ else:
+ a = str(a).__eq__
+ self.matchers.append(a)
+
+ def match(self, string):
+ """
+ Returns true if this object matches an arbitrary string when
+ split on ascii spaces.
+ """
+ ary = string.split(' ', len(self.matchers))
+ if all(m(a) for m, a in zip(self.matchers, ary)):
+ return ary
+
+def flatten(items):
+ """
+ Given an iterator which returns (key, value) pairs, returns a
+ new iterator of (k, value) pairs such that every list- or
+ tuple-valued key in the original sequence yields an individual
+ pair.
+
+ Example: flatten({(1, 2, 3): 'foo', 4: 'bar'}.items()) ->
+ (1, 'foo'), (2: 'foo'), (3: 'foo'), (4: 'bar')
+ """
+ for k, v in items:
+ if not isinstance(k, (list, tuple)):
+ k = k,
+ for key in k:
+ yield key, v
+
+class Events():
+ """
+ A class to handle events read from wmii's '/event' file.
+ """
+ def __init__(self):
+ """
+ Initializes the event handler
+ """
+ self.events = {}
+ self.eventmatchers = {}
+ self.alive = True
+
+ def dispatch(self, event, args=''):
+ """
+ Distatches an event to any matching event handlers.
+
+ The handler which specifically matches the event name will
+ be called first, followed by any handlers with a 'match'
+ method which matches the event name concatenated to the args
+ string.
+
+ Param event: The name of the event to dispatch.
+ Param args: The single arguments string for the event.
+ """
+ try:
+ if event in self.events:
+ self.events[event](args)
+ for matcher, action in self.eventmatchers.iteritems():
+ ary = matcher.match(' '.join((event, args)))
+ if ary is not None:
+ action(*ary)
+ except Exception, e:
+ traceback.print_exc(sys.stderr)
+
+ def loop(self):
+ """
+ Enters the event loop, reading lines from wmii's '/event'
+ and dispatching them, via #dispatch, to event handlers.
+ Continues so long as #alive is True.
+ """
+ keys.mode = 'main'
+ for line in client.readlines('/event'):
+ if not self.alive:
+ break
+ self.dispatch(*line.split(' ', 1))
+ self.alive = False
+
+ def bind(self, items={}, **kwargs):
+ """
+ Binds a number of event handlers for wmii events. Keyword
+ arguments other than 'items' are added to the 'items' dict.
+ Handlers are called by #loop when a matching line is read
+ from '/event'. Each handler is called with, as its sole
+ argument, the string read from /event with its first token
+ stripped.
+
+ Param items: A dict of action-handler pairs to bind. Passed
+ through pygmi.event.flatten. Keys with a 'match' method,
+ such as pygmi.event.Match objects or regular expressions,
+ are matched against the entire event string. Any other
+ object matches if it compares equal to the first token of
+ the event.
+ """
+ kwargs.update(items)
+ for k, v in flatten(kwargs.iteritems()):
+ if hasattr(k, 'match'):
+ self.eventmatchers[k] = v
+ else:
+ self.events[k] = v
+
+ def event(self, fn):
+ """
+ A decorator which binds its wrapped function, as via #bind,
+ for the event which matches its name.
+ """
+ self.bind({fn.__name__: fn})
+events = Events()
+
+class Keys(object):
+ """
+ A class to manage wmii key bindings.
+ """
+ def __init__(self):
+ """
+ Initializes the class and binds an event handler for the Key
+ event, as via pygmi.event.events.bind.
+
+ Takes no arguments.
+ """
+ self.modes = {}
+ self.modelist = []
+ self._set_mode('main', False)
+ self.defs = {}
+ events.bind(Key=self.dispatch)
+
+ def _add_mode(self, mode):
+ if mode not in self.modes:
+ self.modes[mode] = {
+ 'name': mode,
+ 'desc': {},
+ 'groups': [],
+ 'keys': {},
+ 'import': {},
+ }
+ self.modelist.append(mode)
+
+ def _set_mode(self, mode, execute=True):
+ self._add_mode(mode)
+ self._mode = mode
+ self._keys = dict((k % self.defs, v) for k, v in
+ self.modes[mode]['keys'].items() +
+ self.modes[mode]['import'].items());
+ if execute:
+ client.write('/keys', '\n'.join(self._keys.keys()) + '\n')
+
+ mode = property(lambda self: self._mode, _set_mode,
+ doc="The current mode for which to dispatch keys")
+
+ @prop(doc="Returns a short help text describing the bound keys in all modes")
+ def help(self):
+ return '\n\n'.join(
+ ('Mode %s\n' % mode['name']) +
+ '\n\n'.join((' %s\n' % str(group or '')) +
+ '\n'.join(' %- 20s %s' % (key % self.defs,
+ mode['keys'][key].__doc__)
+ for key in mode['desc'][group])
+ for group in mode['groups'])
+ for mode in (self.modes[name]
+ for name in self.modelist))
+
+ def bind(self, mode='main', keys=(), import_={}):
+ """
+ Binds a series of keys for the given 'mode'. Keys may be
+ specified as a dict or as a sequence of tuple values and
+ strings.
+
+ In the latter case, documentation may be interspersed with
+ key bindings. Any value in the sequence which is not a tuple
+ begins a new key group, with that value as a description.
+ A tuple with two values is considered a key-value pair,
+ where the value is the handler for the named key. A
+ three valued tuple is considered a key-description-value
+ tuple, with the same semantics as above.
+
+ Each key binding is interpolated with the values of
+ #defs, as if processed by (key % self.defs)
+
+ Param mode: The name of the mode for which to bind the keys.
+ Param keys: A sequence of keys to bind.
+ Param import_: A dict specifying keys which should be
+ imported from other modes, of the form
+ { 'mode': ['key1', 'key2', ...] }
+ """
+ self._add_mode(mode)
+ mode = self.modes[mode]
+ group = None
+ def add_desc(key, desc):
+ if group not in mode['desc']:
+ mode['desc'][group] = []
+ mode['groups'].append(group)
+ if key not in mode['desc'][group]:
+ mode['desc'][group].append(key);
+
+ if isinstance(keys, dict):
+ keys = keys.iteritems()
+ for obj in keys:
+ if isinstance(obj, tuple) and len(obj) in (2, 3):
+ if len(obj) == 2:
+ key, val = obj
+ desc = ''
+ elif len(obj) == 3:
+ key, desc, val = obj
+ mode['keys'][key] = val
+ add_desc(key, desc)
+ val.__doc__ = str(desc)
+ else:
+ group = obj
+
+ def wrap_import(mode, key):
+ return lambda k: self.modes[mode]['keys'][key](k)
+ for k, v in flatten((v, k) for k, v in import_.iteritems()):
+ mode['import'][k % self.defs] = wrap_import(v, k)
+
+ def dispatch(self, key):
+ """
+ Dispatches a key event for the current mode.
+
+ Param key: The key spec for which to dispatch.
+ """
+ mode = self.modes[self.mode]
+ if key in self._keys:
+ return self._keys[key](key)
+keys = Keys()
+
+class Actions(object):
+ """
+ A class to represent user-callable actions. All methods without
+ leading underscores in their names are treated as callable actions.
+ """
+ def __getattr__(self, name):
+ if name.startswith('_') or name.endswith('_'):
+ raise AttributeError()
+ if hasattr(self, name + '_'):
+ return getattr(self, name + '_')
+ def action(args=''):
+ cmd = pygmi.find_script(name)
+ if cmd:
+ call(pygmi.shell, '-c', '$* %s' % args, '--', cmd,
+ background=True)
+ return action
+
+ def _call(self, args):
+ """
+ Calls a method named for the first token of 'args', with the
+ rest of the string as its first argument. If the method
+ doesn't exist, a trailing underscore is appended.
+ """
+ a = args.split(' ', 1)
+ if a:
+ getattr(self, a[0])(*a[1:])
+
+ @prop(doc="Returns the names of the public methods callable as actions, with trailing underscores stripped.")
+ def _choices(self):
+ return sorted(
+ program_list(pygmi.confpath) +
+ [re.sub('_$', '', k) for k in dir(self)
+ if not re.match('^_', k) and callable(getattr(self, k))])
+
+
+# vim:se sts=4 sw=4 et:
diff --git a/alternative_wmiircs/python/pygmi/fs.py b/alternative_wmiircs/python/pygmi/fs.py
new file mode 100644
index 0000000..1d05d8e
--- /dev/null
+++ b/alternative_wmiircs/python/pygmi/fs.py
@@ -0,0 +1,699 @@
+import collections
+from datetime import datetime, timedelta
+
+from pyxp import *
+from pyxp.client import *
+from pygmi import *
+from pygmi.util import prop
+
+__all__ = ('wmii', 'Tags', 'Tag', 'Area', 'Frame', 'Client',
+ 'Button', 'Colors', 'Color')
+
+def constrain(min, max, val):
+ if val < min:
+ return min
+ if val > max:
+ return max
+ return val
+
+class Ctl(object):
+ """
+ An abstract class to represent the 'ctl' files of the wmii filesystem.
+ Instances act as live, writable dictionaries of the settings represented
+ in the file.
+
+ Abstract roperty ctl_path: The path to the file represented by this
+ control.
+ Property ctl_hasid: When true, the first line of the represented
+ file is treated as an id, rather than a key-value pair. In this
+ case, the value is available via the 'id' property.
+ Property ctl_types: A dict mapping named dictionary keys to two valued
+ tuples, each containing a decoder and encoder function for the
+ property's plain text value.
+ """
+ sentinel = {}
+ ctl_types = {}
+ ctl_hasid = False
+
+ def __eq__(self, other):
+ if self.ctl_hasid and isinstance(other, Ctl) and other.ctl_hasid:
+ return self.id == other.id
+ return False
+
+ def __init__(self):
+ self.cache = {}
+
+ def ctl(self, *args):
+ """
+ Arguments are joined by ascii spaces and written to the ctl file.
+ """
+ client.awrite(self.ctl_path, ' '.join(args))
+
+ def __getitem__(self, key):
+ for line in self.ctl_lines():
+ key_, rest = line.split(' ', 1)
+ if key_ == key:
+ if key in self.ctl_types:
+ return self.ctl_types[key][0](rest)
+ return rest
+ raise KeyError()
+ def __hasitem__(self, key):
+ return key in self.keys()
+ def __setitem__(self, key, val):
+ assert '\n' not in key
+ self.cache[key] = val
+ if key in self.ctl_types:
+ val = self.ctl_types[key][1](val)
+ self.ctl(key, val)
+
+ def get(self, key, default=sentinel):
+ """
+ Gets the instance's dictionary value for 'key'. If the key doesn't
+ exist, 'default' is returned. If 'default' isn't provided and the key
+ doesn't exist, a KeyError is raised.
+ """
+ try:
+ val = self[key]
+ except KeyError, e:
+ if default is not self.sentinel:
+ return default
+ raise e
+ def set(self, key, val):
+ """
+ Sets the dictionary value for 'key' to 'val', as self[key] = val
+ """
+ self[key] = val
+
+ def keys(self):
+ return [line.split(' ', 1)[0]
+ for line in self.ctl_lines()]
+ def iteritems(self):
+ return (tuple(line.split(' ', 1))
+ for line in self.ctl_lines())
+ def items(self):
+ return [tuple(line.split(' ', 1))
+ for line in self.ctl_lines()]
+
+ def ctl_lines(self):
+ """
+ Returns the lines of the ctl file as a tuple, with the first line
+ stripped if #ctl_hasid is set.
+ """
+ lines = tuple(client.readlines(self.ctl_path))
+ if self.ctl_hasid:
+ lines = lines[1:]
+ return lines
+
+ _id = None
+ @prop(doc="If #ctl_hasid is set, returns the id of this ctl file.")
+ def id(self):
+ if self._id is None and self.ctl_hasid:
+ return client.read(self.ctl_path).split('\n', 1)[0]
+ return self._id
+
+class Dir(Ctl):
+ """
+ An abstract class representing a directory in the wmii filesystem with a
+ ctl file and sub-objects.
+
+ Abstract property base_path: The path directly under which all objects
+ represented by this class reside. e.g., /client, /tag
+ """
+ ctl_hasid = True
+
+ def __init__(self, id):
+ """
+ Initializes the directory object.
+
+ Param id: The id of the object in question. If 'sel', the object
+ dynamically represents the selected object, even as it
+ changes. In this case, #id will return the actual ID of the
+ object.
+ """
+ super(Dir, self).__init__()
+ if isinstance(id, Dir):
+ id = id.id
+ if id != 'sel':
+ self._id = id
+
+ def __eq__(self, other):
+ return (self.__class__ == other.__class__ and
+ self.id == other.id)
+
+ class ctl_property(object):
+ """
+ A class which maps instance properties to ctl file properties.
+ """
+ def __init__(self, key):
+ self.key = key
+ def __get__(self, dir, cls):
+ return dir[self.key]
+ def __set__(self, dir, val):
+ dir[self.key] = val
+
+ class toggle_property(ctl_property):
+ """
+ A class which maps instance properties to ctl file properties. The
+ values True and False map to the strings "on" and "off" in the
+ filesystem.
+ """
+ props = {
+ 'on': True,
+ 'off': False,
+ }
+ def __get__(self, dir, cls):
+ val = dir[self.key]
+ if val in self.props:
+ return self.props[val]
+ return val
+ def __set__(self, dir, val):
+ for k, v in self.props.iteritems():
+ if v == val:
+ val = k
+ break
+ dir[self.key] = val
+
+ class file_property(object):
+ """
+ A class which maps instance properties to files in the directory
+ represented by this object.
+ """
+ def __init__(self, name, writable=False):
+ self.name = name
+ self.writable = writable
+ def __get__(self, dir, cls):
+ return client.read('%s/%s' % (dir.path, self.name))
+ def __set__(self, dir, val):
+ if not self.writable:
+ raise NotImplementedError('File %s is not writable' % self.name)
+ return client.awrite('%s/%s' % (dir.path, self.name),
+ str(val))
+
+ @prop(doc="The path to this directory's ctl file")
+ def ctl_path(self):
+ return '%s/ctl' % self.path
+
+ @prop(doc="The path to this directory")
+ def path(self):
+ return '%s/%s' % (self.base_path, self._id or 'sel')
+
+ @classmethod
+ def all(cls):
+ """
+ Returns all of the objects that exist for this type of directory.
+ """
+ return (cls(s.name)
+ for s in client.readdir(cls.base_path)
+ if s.name != 'sel')
+
+ def __repr__(self):
+ return '%s(%s)' % (self.__class__.__name__,
+ repr(self._id or 'sel'))
+
+class Client(Dir):
+ """
+ A class which represents wmii clients. Maps to the directories directly
+ below /client.
+ """
+ base_path = '/client'
+
+ fullscreen = Dir.toggle_property('Fullscreen')
+ urgent = Dir.toggle_property('Urgent')
+
+ label = Dir.file_property('label', writable=True)
+ tags = Dir.file_property('tags', writable=True)
+ props = Dir.file_property('props')
+
+ def kill(self):
+ """Politely asks a client to quit."""
+ self.ctl('kill')
+ def slay(self):
+ """Forcibly severs a client's connection to the X server."""
+ self.ctl('slay')
+
+class liveprop(object):
+ def __init__(self, get):
+ self.get = get
+ self.attr = str(self)
+ def __get__(self, area, cls):
+ if getattr(area, self.attr, None) is not None:
+ return getattr(area, self.attr)
+ return self.get(area)
+ def __set__(self, area, val):
+ setattr(area, self.attr, val)
+
+class Area(object):
+ def __init__(self, tag, ord, screen='sel', offset=None, width=None, height=None, frames=None):
+ self.tag = tag
+ if ':' in str(ord):
+ screen, ord = ord.split(':', 2)
+ self.ord = str(ord)
+ self.screen = str(screen)
+ self.offset = offset
+ self.width = width
+ self.height = height
+ self.frames = frames
+
+ def prop(key):
+ @liveprop
+ def prop(self):
+ for area in self.tag.index:
+ if str(area.ord) == str(self.ord):
+ return getattr(area, key)
+ return prop
+ offset = prop('offset')
+ width = prop('width')
+ height = prop('height')
+ frames = prop('frames')
+
+ @property
+ def spec(self):
+ return '%s:%s' % (self.screen, self.ord)
+
+ def _get_mode(self):
+ for k, v in self.tag.iteritems():
+ if k == 'colmode':
+ v = v.split(' ')
+ if v[0] == self.ord:
+ return v[1]
+ mode = property(
+ _get_mode,
+ lambda self, val: self.tag.set('colmode %s' % self.spec, val))
+
+ def grow(self, dir, amount=None):
+ self.tag.grow(self, dir, amount)
+ def nudge(self, dir, amount=None):
+ self.tag.nudge(self, dir, amount)
+
+class Frame(object):
+ live = False
+
+ def __init__(self, client, area=None, ord=None, offset=None, height=None):
+ self.client = client
+ self.ord = ord
+ self.offset = offset
+ self.height = height
+
+ @property
+ def width(self):
+ return self.area.width
+
+ def prop(key):
+ @liveprop
+ def prop(self):
+ for area in self.tag.index:
+ for frame in area.frames:
+ if frame.client == self.client:
+ return getattr(frame, key)
+ return prop
+ offset = prop('area')
+ offset = prop('ord')
+ offset = prop('offset')
+ height = prop('height')
+
+ def grow(self, dir, amount=None):
+ self.area.tag.grow(self, dir, amount)
+ def nudge(self, dir, amount=None):
+ self.area.tag.nudge(self, dir, amount)
+
+class Tag(Dir):
+ base_path = '/tag'
+
+ @classmethod
+ def framespec(cls, frame):
+ if isinstance(frame, Frame):
+ frame = frame.client
+ if isinstance(frame, Area):
+ frame = (frame.ord, 'sel')
+ if isinstance(frame, Client):
+ if frame._id is None:
+ return 'sel sel'
+ return 'client %s' % frame.id
+ elif isinstance(frame, basestring):
+ return frame
+ else:
+ return '%s %s' % tuple(map(str, frame))
+ def dirspec(cls, dir):
+ if isinstance(dir, tuple):
+ dir = ' '.join(dir)
+ return dir
+
+ def _set_selected(self, frame):
+ if not isinstance(frame, basestring) or ' ' not in frame:
+ frame = self.framespec(frame)
+ self['select'] = frame
+ selected = property(lambda self: tuple(self['select'].split(' ')),
+ _set_selected)
+
+ def _get_selclient(self):
+ for k, v in self.iteritems():
+ if k == 'select' and 'client' in v:
+ return Client(v.split(' ')[1])
+ return None
+ selclient = property(_get_selclient,
+ lambda self, val: self.set('select',
+ self.framespec(val)))
+
+ @property
+ def selcol(self):
+ return Area(self, self.selected[0])
+
+ @property
+ def index(self):
+ areas = []
+ for l in [l.split(' ')
+ for l in client.readlines('%s/index' % self.path)
+ if l]:
+ if l[0] == '#':
+ m = re.match(r'(?:(\d+):)?(\d+|~)', l[1])
+ if m.group(2) == '~':
+ area = Area(tag=self, screen=m.group(1), ord=l[1], width=l[2],
+ height=l[3], frames=[])
+ else:
+ area = Area(tag=self, screen=m.group(1) or 0,
+ ord=m.group(2), offset=l[2], width=l[3],
+ frames=[])
+ areas.append(area)
+ i = 0
+ else:
+ area.frames.append(
+ Frame(client=Client(l[1]), area=area, ord=i,
+ offset=l[2], height=l[3]))
+ i += 1
+ return areas
+
+ def delete(self):
+ id = self.id
+ for a in self.index:
+ for f in a.frames:
+ if f.client.tags == id:
+ f.client.kill()
+ else:
+ f.client.tags = '-%s' % id
+ if self == Tag('sel'):
+ Tags.instance.select(Tags.instance.next())
+
+ def select(self, frame, stack=False):
+ self['select'] = '%s %s' % (
+ self.framespec(frame),
+ stack and 'stack' or '')
+
+ def send(self, src, dest, stack=False, cmd='send'):
+ if isinstance(src, tuple):
+ src = ' '.join(src)
+ if isinstance(src, Frame):
+ src = src.client
+ if isinstance(src, Client):
+ src = src._id or 'sel'
+
+ if isinstance(dest, tuple):
+ dest = ' '.join(dest)
+
+ self[cmd] = '%s %s' % (src, dest)
+
+ def swap(self, src, dest):
+ self.send(src, dest, cmd='swap')
+
+ def nudge(self, frame, dir, amount=None):
+ frame = self.framespec(frame)
+ self['nudge'] = '%s %s %s' % (frame, dir, str(amount or ''))
+ def grow(self, frame, dir, amount=None):
+ frame = self.framespec(frame)
+ self['grow'] = '%s %s %s' % (frame, dir, str(amount or ''))
+
+class Button(object):
+ sides = {
+ 'left': 'lbar',
+ 'right': 'rbar',
+ }
+ def __init__(self, side, name, colors=None, label=None):
+ self.side = side
+ self.name = name
+ self.base_path = self.sides[side]
+ self.path = '%s/%s' % (self.base_path, self.name)
+ self.file = None
+ if colors or label:
+ self.create(colors, label)
+
+ def create(self, colors=None, label=None):
+ def fail(resp, exc, tb):
+ self.file = None
+ if not self.file:
+ self.file = client.create(self.path, ORDWR)
+ if colors:
+ self.file.awrite(self.getval(colors, label), offset=0, fail=fail)
+ elif label:
+ self.file.awrite(label, offset=24, fail=fail)
+
+ def remove(self):
+ if self.file:
+ self.file.aremove()
+ self.file = None
+
+ def getval(self, colors=None, label=None):
+ if label is None:
+ label = self.label
+ if colors is None and re.match(
+ r'#[0-9a-f]{6} #[0-9a-f]{6} #[0-9a-f]{6}', label, re.I):
+ colors = self.colors
+ if not colors:
+ return str(label)
+ return ' '.join([Color(c).hex for c in colors] + [str(label)])
+
+ colors = property(
+ lambda self: self.file and
+ tuple(map(Color, self.file.read(offset=0).split(' ')[:3]))
+ or (),
+ lambda self, val: self.create(colors=val))
+
+ label = property(
+ lambda self: self.file and self.file.read(offset=0).split(' ', 3)[3] or '',
+ lambda self, val: self.create(label=val))
+
+ @classmethod
+ def all(cls, side):
+ return (Button(side, s.name)
+ for s in client.readdir(cls.sides[side])
+ if s.name != 'sel')
+
+class Colors(object):
+ def __init__(self, foreground=None, background=None, border=None):
+ vals = foreground, background, border
+ self.vals = tuple(map(Color, vals))
+
+ def __iter__(self):
+ return iter(self.vals)
+ def __list__(self):
+ return list(self.vals)
+ def __tuple__(self):
+ return self.vals
+
+ @classmethod
+ def from_string(cls, val):
+ return cls(*val.split(' '))
+
+ def __getitem__(self, key):
+ if isinstance(key, basestring):
+ key = {'foreground': 0, 'background': 1, 'border': 2}[key]
+ return self.vals[key]
+
+ def __str__(self):
+ return str(unicode(self))
+ def __unicode__(self):
+ return ' '.join(c.hex for c in self.vals)
+ def __repr__(self):
+ return 'Colors(%s, %s, %s)' % tuple(repr(c.rgb) for c in self.vals)
+
+class Color(object):
+ def __init__(self, colors):
+ if isinstance(colors, Color):
+ colors = colors.rgb
+ elif isinstance(colors, basestring):
+ match = re.match(r'^#(..)(..)(..)$', colors)
+ colors = tuple(int(match.group(group), 16) for group in range(1, 4))
+ def toint(val):
+ if isinstance(val, float):
+ val = int(255 * val)
+ assert 0 <= val <= 255
+ return val
+ self.rgb = tuple(map(toint, colors))
+
+ def __getitem__(self, key):
+ if isinstance(key, basestring):
+ key = {'red': 0, 'green': 1, 'blue': 2}[key]
+ return self.rgb[key]
+
+ @property
+ def hex(self):
+ return '#%02x%02x%02x' % self.rgb
+
+ def __str__(self):
+ return str(unicode(self))
+ def __unicode__(self):
+ return 'rgb(%d, %d, %d)' % self.rgb
+ def __repr__(self):
+ return 'Color(%s)' % repr(self.rgb)
+
+class Rules(collections.MutableMapping):
+ regex = re.compile(r'^\s*/(.*?)/\s*(?:->)?\s*(.*)$')
+
+ def __get__(self, obj, cls):
+ return self
+ def __set__(self, obj, val):
+ self.setitems(val)
+
+ def __init__(self, path, rules=None):
+ self.path = path
+ if rules:
+ self.setitems(rules)
+
+ def __getitem__(self, key):
+ for k, v in self.iteritems():
+ if k == key:
+ return v
+ raise KeyError()
+ def __setitem__(self, key, val):
+ items = []
+ for k, v in self.iteritems():
+ if key == k:
+ v = val
+ key = None
+ items.append((k, v))
+ if key is not None:
+ items.append((key, val))
+ self.setitems(items)
+ def __delitem__(self, key):
+ self.setitems((k, v) for k, v in self.iteritems() if k != key)
+
+ def __len__(self):
+ return len(tuple(self.iteritems()))
+ def __iter__(self):
+ for k, v in self.iteritems():
+ yield k
+ def __list__(self):
+ return list(iter(self))
+ def __tuple__(self):
+ return tuple(iter(self))
+
+ def append(self, item):
+ self.setitems(self + (item,))
+ def __add__(self, items):
+ return tuple(self.iteritems()) + tuple(items)
+
+ def setitems(self, items):
+ lines = []
+ for k, v in items:
+ assert '/' not in k and '\n' not in v
+ lines.append('/%s/ -> %s' % (k, v))
+ lines.append('')
+ client.awrite(self.path, '\n'.join(lines))
+
+ def iteritems(self):
+ for line in client.readlines(self.path):
+ match = self.regex.match(line)
+ if match:
+ yield match.groups()
+ def items(self):
+ return list(self.iteritems())
+
+@apply
+class wmii(Ctl):
+ ctl_path = '/ctl'
+ ctl_types = {
+ 'normcolors': (Colors.from_string, lambda c: str(Colors(*c))),
+ 'focuscolors': (Colors.from_string, lambda c: str(Colors(*c))),
+ 'border': (int, str),
+ }
+
+ clients = property(lambda self: Client.all())
+ tags = property(lambda self: Tag.all())
+ lbuttons = property(lambda self: Button.all('left'))
+ rbuttons = property(lambda self: Button.all('right'))
+
+ tagrules = Rules('/tagrules')
+ colrules = Rules('/colrules')
+
+class Tags(object):
+ PREV = []
+ NEXT = []
+
+ def __init__(self, normcol=None, focuscol=None):
+ self.ignore = set()
+ self.tags = {}
+ self.sel = None
+ self.normcol = normcol
+ self.focuscol = focuscol
+ self.lastselect = datetime.now()
+ for t in wmii.tags:
+ self.add(t.id)
+ for b in wmii.lbuttons:
+ if b.name not in self.tags:
+ b.remove()
+ self.focus(Tag('sel').id)
+
+ self.mru = [self.sel.id]
+ self.idx = -1
+ Tags.instance = self
+
+ def add(self, tag):
+ self.tags[tag] = Tag(tag)
+ self.tags[tag].button = Button('left', tag, self.normcol or wmii.cache['normcolors'], tag)
+ def delete(self, tag):
+ self.tags.pop(tag).button.remove()
+
+ def focus(self, tag):
+ self.sel = self.tags[tag]
+ self.sel.button.colors = self.focuscol or wmii.cache['focuscolors']
+ def unfocus(self, tag):
+ self.tags[tag].button.colors = self.normcol or wmii.cache['normcolors']
+
+ def set_urgent(self, tag, urgent=True):
+ self.tags[tag].button.label = urgent and '*' + tag or tag
+
+ def next(self, reverse=False):
+ tags = [t for t in wmii.tags if t.id not in self.ignore]
+ tags.append(tags[0])
+ if reverse:
+ tags.reverse()
+ for i in range(0, len(tags)):
+ if tags[i] == self.sel:
+ return tags[i+1]
+ return self.sel
+
+ def select(self, tag, take_client=None):
+ def goto(tag):
+ if take_client:
+ sel = Tag('sel').id
+ take_client.tags = '+%s' % tag
+ wmii['view'] = tag
+ if tag != sel:
+ take_client.tags = '-%s' % sel
+ else:
+ wmii['view'] = tag
+
+ if tag is self.PREV:
+ if self.sel.id not in self.ignore:
+ self.idx -= 1
+ elif tag is self.NEXT:
+ self.idx += 1
+ else:
+ if isinstance(tag, Tag):
+ tag = tag.id
+ goto(tag)
+
+ if tag not in self.ignore:
+ if self.idx < -1:
+ self.mru = self.mru[:self.idx + 1]
+ self.idx = -1
+ if self.mru and datetime.now() - self.lastselect < timedelta(seconds=.5):
+ self.mru[self.idx] = tag
+ elif tag != self.mru[-1]:
+ self.mru.append(tag)
+ self.mru = self.mru[-10:]
+ self.lastselect = datetime.now()
+ return
+
+ self.idx = constrain(-len(self.mru), -1, self.idx)
+ goto(self.mru[self.idx])
+
+# vim:se sts=4 sw=4 et:
diff --git a/alternative_wmiircs/python/pygmi/menu.py b/alternative_wmiircs/python/pygmi/menu.py
new file mode 100644
index 0000000..4711576
--- /dev/null
+++ b/alternative_wmiircs/python/pygmi/menu.py
@@ -0,0 +1,54 @@
+from threading import Thread
+from pygmi.util import call
+
+__all__ = 'Menu', 'ClickMenu'
+
+def inthread(args, action, **kwargs):
+ fn = lambda: call(*args, **kwargs)
+ if not action:
+ return fn()
+ t = Thread(target=lambda: action(fn()))
+ t.daemon = True
+ t.start()
+
+class Menu(object):
+ def __init__(self, choices=(), action=None,
+ histfile=None, nhist=None):
+ self.choices = choices
+ self.action = action
+ self.histfile = histfile
+ self.nhist = nhist
+
+ def __call__(self, choices=None):
+ if choices is None:
+ choices = self.choices
+ if callable(choices):
+ choices = choices()
+ args = ['wimenu']
+ if self.histfile:
+ args += ['-h', self.histfile]
+ if self.nhist:
+ args += ['-n', self.nhist]
+ return inthread(map(str, args), self.action, input='\n'.join(choices))
+ call = __call__
+
+class ClickMenu(object):
+ def __init__(self, choices=(), action=None,
+ histfile=None, nhist=None):
+ self.choices = choices
+ self.action = action
+ self.prev = None
+
+ def __call__(self, choices=None):
+ if choices is None:
+ choices = self.choices
+ if callable(choices):
+ choices = choices()
+ args = ['wmii9menu']
+ if self.prev:
+ args += ['-i', self.prev]
+ args += ['--'] + list(choices)
+ return inthread(map(str, args), self.action)
+ call = __call__
+
+# vim:se sts=4 sw=4 et:
diff --git a/alternative_wmiircs/python/pygmi/monitor.py b/alternative_wmiircs/python/pygmi/monitor.py
new file mode 100644
index 0000000..528e9cd
--- /dev/null
+++ b/alternative_wmiircs/python/pygmi/monitor.py
@@ -0,0 +1,118 @@
+from threading import Timer
+
+from pygmi import client
+from pygmi.fs import *
+
+__all__ = 'monitors', 'defmonitor', 'Monitor'
+
+monitors = {}
+
+def defmonitor(*args, **kwargs):
+ """
+ Defines a new monitor to appear in wmii's bar based on
+ the wrapped function. Creates a new Monitor object,
+ initialized with *args and **kwargs. The wrapped function
+ is assigned to the 'action' keyword argument for the
+ Monitor, its name is assigned to the 'name' argument.
+
+ The new monitor is added to the 'monitors' dict in this
+ module.
+ """
+ def monitor(fn):
+ kwargs['action'] = fn
+ if not args and 'name' not in kwargs:
+ kwargs['name'] = fn.__name__
+ monitor = Monitor(*args, **kwargs)
+ monitors[monitor.name] = monitor
+ return monitor
+ if args and callable(args[0]):
+ fn = args[0]
+ args = args[1:]
+ return monitor(fn)
+ return monitor
+
+class Monitor(object):
+ """
+ A class to manage status monitors for wmii's bar. The bar item
+ is updated on a fixed interval based on the values returned
+ by the 'action' method.
+
+ Property active: When true, the monitor is updated at regular
+ intervals. When false, monitor is hidden.
+ Property name: The name of the monitor, which acts as the name
+ of the bar in wmii's filesystem.
+ Property interval: The update interval, in seconds.
+ Property side: The side of the bar on which to place the monitor.
+ Property action: A function of no arguments which returns the
+ value of the monitor. Called at each update interval.
+ May return a string, a tuple of (Color, string), or None
+ to hide the monitor for one iteration.
+ """
+ side = 'right'
+ interval = 1.0
+
+ def __init__(self, name=None, interval=None, side=None,
+ action=None, colors=None, label=None):
+ """
+ Initializes the new monitor. For parameter values, see the
+ corresponding property values in the class's docstring.
+
+ Param colors: The initial colors for the monitor.
+ Param label: The initial label for the monitor.
+ """
+ if side:
+ self.side = side
+ if name:
+ self.name = name
+ if interval:
+ self.interval = interval
+ if action:
+ self.action = action
+
+ self.timer = None
+ self.button = Button(self.side, self.name, colors, label)
+ self.tick()
+
+ def tick(self):
+ """
+ Called internally at the interval defined by #interval.
+ Calls #action and updates the monitor based on the result.
+ """
+ mon = monitors.get(self.name, None)
+ if self.timer and mon is not self:
+ return
+ if self.active:
+ label = self.getlabel()
+ if isinstance(label, basestring):
+ label = None, label
+ if label is None:
+ self.button.remove()
+ else:
+ self.button.create(*label)
+
+ self.timer = Timer(self.interval, self.tick)
+ self.timer.daemon = True
+ self.timer.start()
+
+ def getlabel(self):
+ """
+ Calls #action and returns the result, ignoring any
+ exceptions.
+ """
+ try:
+ return self.action(self)
+ except Exception:
+ return None
+
+ _active = True
+ def _set_active(self, val):
+ self._active = bool(val)
+ self.tick()
+ if not val:
+ self.button.remove()
+
+ active = property(
+ lambda self: self._active,
+ _set_active)
+
+# vim:se sts=4 sw=4 et:
diff --git a/alternative_wmiircs/python/pygmi/util.py b/alternative_wmiircs/python/pygmi/util.py
new file mode 100644
index 0000000..8821478
--- /dev/null
+++ b/alternative_wmiircs/python/pygmi/util.py
@@ -0,0 +1,66 @@
+import os
+import subprocess
+
+import pygmi
+
+__all__ = 'call', 'message', 'program_list', 'curry', 'find_script', '_', 'prop'
+
+def _():
+ pass
+
+def call(*args, **kwargs):
+ background = kwargs.pop('background', False)
+ input = kwargs.pop('input', None)
+ p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE, cwd=os.environ['HOME'],
+ close_fds=True, **kwargs)
+ if not background:
+ return p.communicate(input)[0].rstrip('\n')
+
+def message(message):
+ args = ['xmessage', '-file', '-'];
+ font = pygmi.wmii['font']
+ if not font.startswith('xft:'):
+ args += ['-fn', font.split(',')[0]]
+ call(*args, input=message)
+
+def program_list(path):
+ names = []
+ for d in path:
+ try:
+ for f in os.listdir(d):
+ p = '%s/%s' % (d, f)
+ if f not in names and os.access(p, os.X_OK) and (
+ os.path.isfile(p) or os.path.islink(p)):
+ names.append(f)
+ except Exception:
+ pass
+ return sorted(names)
+
+def curry(func, *args, **kwargs):
+ if _ in args:
+ blank = [i for i in range(0, len(args)) if args[i] is _]
+ def curried(*newargs, **newkwargs):
+ ary = list(args)
+ for k, v in zip(blank, newargs):
+ ary[k] = v
+ ary = tuple(ary) + newargs[len(blank):]
+ return func(*ary, **dict(kwargs, **newkwargs))
+ else:
+ def curried(*newargs, **newkwargs):
+ return func(*(args + newargs), **dict(kwargs, **newkwargs))
+ curried.__name__ = func.__name__ + '__curried__'
+ return curried
+
+def find_script(name):
+ for path in pygmi.confpath:
+ if os.access('%s/%s' % (path, name), os.X_OK):
+ return '%s/%s' % (path, name)
+
+def prop(**kwargs):
+ def prop_(wrapped):
+ kwargs['fget'] = wrapped
+ return property(**kwargs)
+ return prop_
+
+# vim:se sts=4 sw=4 et:
diff --git a/alternative_wmiircs/python/pyxp/Makefile b/alternative_wmiircs/python/pyxp/Makefile
new file mode 100644
index 0000000..ad2edeb
--- /dev/null
+++ b/alternative_wmiircs/python/pyxp/Makefile
@@ -0,0 +1,15 @@
+ROOT=../../..
+include $(ROOT)/mk/hdr.mk
+include $(ROOT)/mk/wmii.mk
+
+BINARY = __init__.py \
+ asyncclient.py \
+ client.py \
+ dial.py \
+ fcall.py \
+ fields.py \
+ messages.py \
+ mux.py \
+ types.py
+
+DIR = $(ETC)/wmii$(CONFVERSION)/python/pyxp
diff --git a/alternative_wmiircs/python/pyxp/__init__.py b/alternative_wmiircs/python/pyxp/__init__.py
new file mode 100644
index 0000000..2ba7400
--- /dev/null
+++ b/alternative_wmiircs/python/pyxp/__init__.py
@@ -0,0 +1,7 @@
+from pyxp.client import Client
+from pyxp.dial import dial
+from pyxp.types import Qid, Stat
+
+VERSION = '9P2000'
+
+# vim:se sts=4 sw=4 et:
diff --git a/alternative_wmiircs/python/pyxp/asyncclient.py b/alternative_wmiircs/python/pyxp/asyncclient.py
new file mode 100644
index 0000000..b7ebc08
--- /dev/null
+++ b/alternative_wmiircs/python/pyxp/asyncclient.py
@@ -0,0 +1,193 @@
+from pyxp import client, fcall
+from pyxp.client import *
+
+def awithfile(*oargs, **okwargs):
+ def wrapper(fn):
+ def next(self, path, *args, **kwargs):
+ def next(file, exc, tb):
+ fn(self, (file, exc, tb), *args, **kwargs)
+ self.aopen(path, next, *oargs, **okwargs)
+ return next
+ return wrapper
+def wrap_callback(fn, file):
+ def callback(data, exc, tb):
+ file.close()
+ Client.respond(fn, data, exc, tb)
+ return callback
+
+class Client(client.Client):
+ ROOT_FID = 0
+
+ def _awalk(self, path, callback, fail=None):
+ ctxt = dict(path=path, fid=self._getfid(), ofid=ROOT_FID)
+ def next(resp=None, exc=None, tb=None):
+ if exc and ctxt['ofid'] != ROOT_FID:
+ self._aclunk(ctxt['fid'])
+ if not ctxt['path'] and resp or exc:
+ if exc and fail:
+ return self.respond(fail, None, exc, tb)
+ return self.respond(callback, ctxt['fid'], exc, tb)
+ wname = ctxt['path'][:fcall.MAX_WELEM]
+ ofid = ctxt['ofid']
+ ctxt['path'] = ctxt['path'][fcall.MAX_WELEM:]
+ if resp:
+ ctxt['ofid'] = ctxt['fid']
+ self._dorpc(fcall.Twalk(fid=ofid,
+ newfid=ctxt['fid'],
+ wname=wname),
+ next)
+ next()
+
+ _file = property(lambda self: File)
+ def _aopen(self, path, mode, open, callback, fail=None, origpath=None):
+ resp = None
+ def next(fid, exc, tb):
+ def next(resp, exc, tb):
+ def cleanup():
+ self._clunk(fid)
+ file = self._file(self, origpath or '/'.join(path), resp, fid, mode, cleanup)
+ self.respond(callback, file)
+ self._dorpc(open(fid), next, fail or callback)
+ self._awalk(path, next, fail or callback)
+
+ def aopen(self, path, callback=True, fail=None, mode=OREAD):
+ assert callable(callback)
+ path = self._splitpath(path)
+ def open(fid):
+ return fcall.Topen(fid=fid, mode=mode)
+ return self._aopen(path, mode, open, fail or callback)
+
+ def acreate(self, path, callback=True, fail=None, mode=OREAD, perm=0):
+ path = self._splitpath(path)
+ name = path.pop()
+ def open(fid):
+ return fcall.Tcreate(fid=fid, mode=mode, name=name, perm=perm)
+ if not callable(callback):
+ def callback(resp, exc, tb):
+ if resp:
+ resp.close()
+ return self._aopen(path, mode, open, callback, fail,
+ origpath='/'.join(path + [name]))
+
+ def aremove(self, path, callback=True, fail=None):
+ path = self._splitpath(path)
+ def next(fid, exc, tb):
+ self._dorpc(fcall.Tremove(fid=fid), callback, fail)
+ self._awalk(path, next, callback, fail)
+
+ def astat(self, path, callback, fail = None):
+ path = self._splitpath(path)
+ def next(fid, exc, tb):
+ def next(resp, exc, tb):
+ callback(resp.stat, exc, tb)
+ self._dorpc(fcall.Tstat(fid=fid), next, callback)
+
+ @awithfile()
+ def aread(self, (file, exc, tb), callback, *args, **kwargs):
+ if exc:
+ callback(file, exc, tb)
+ else:
+ file.aread(wrap_callback(callback, file), *args, **kwargs)
+ @awithfile(mode=OWRITE)
+ def awrite(self, (file, exc, tb), data, callback=True, *args, **kwargs):
+ if exc:
+ self.respond(callback, file, exc, tb)
+ else:
+ file.awrite(data, wrap_callback(callback, file), *args, **kwargs)
+ @awithfile()
+ def areadlines(self, (file, exc, tb), fn):
+ def callback(resp):
+ if resp is None:
+ file.close()
+ if fn(resp) is False:
+ file.close()
+ return False
+ if exc:
+ callback(None)
+ else:
+ file.sreadlines(callback)
+
+class File(client.File):
+ @staticmethod
+ def respond(callback, data, exc=None, tb=None):
+ if callable(callback):
+ callback(data, exc, tb)
+
+ def stat(self, callback):
+ def next(resp, exc, tb):
+ callback(resp.stat, exc, tb)
+ resp = self._dorpc(fcall.Tstat(), next, callback)
+
+ def aread(self, callback, fail=None, count=None, offset=None, buf=''):
+ ctxt = dict(res=[], count=self.iounit, offset=self.offset)
+ if count is not None:
+ ctxt['count'] = count
+ if offset is not None:
+ ctxt['offset'] = offset
+ def next(resp=None, exc=None, tb=None):
+ if resp and resp.data:
+ ctxt['res'].append(resp.data)
+ ctxt['offset'] += len(resp.data)
+ if ctxt['count'] == 0:
+ if offset is None:
+ self.offset = ctxt['offset']
+ return callback(''.join(ctxt['res']), exc, tb)
+
+ n = min(ctxt['count'], self.iounit)
+ ctxt['count'] -= n
+
+ self._dorpc(fcall.Tread(offset=ctxt['offset'], count=n),
+ next, fail or callback)
+ next()
+
+ def areadlines(self, callback):
+ ctxt = dict(last=None)
+ def next(data, exc, tb):
+ res = True
+ if data:
+ lines = data.split('\n')
+ if ctxt['last']:
+ lines[0] = ctxt['last'] + lines[0]
+ for i in range(0, len(lines) - 1):
+ res = callback(lines[i])
+ if res is False:
+ break
+ ctxt['last'] = lines[-1]
+ if res is not False:
+ self.aread(next)
+ else:
+ if ctxt['last']:
+ callback(ctxt['last'])
+ callback(None)
+ self.aread(next)
+
+ def awrite(self, data, callback=True, fail=None, offset=None):
+ ctxt = dict(offset=self.offset, off=0)
+ if offset is not None:
+ ctxt['offset'] = offset
+ def next(resp=None, exc=None, tb=None):
+ if resp:
+ ctxt['off'] += resp.count
+ ctxt['offset'] += resp.count
+ if ctxt['off'] < len(data) or not (exc or resp):
+ n = min(len(data), self.iounit)
+
+ self._dorpc(fcall.Twrite(offset=ctxt['offset'],
+ data=data[ctxt['off']:ctxt['off']+n]),
+ next, fail or callback)
+ else:
+ if offset is None:
+ self.offset = ctxt['offset']
+ self.respond(callback, ctxt['off'], exc, tb)
+ next()
+
+ def aremove(self, callback=True, fail=None):
+ def next(resp, exc, tb):
+ self.close()
+ if exc and fail:
+ self.respond(fail, resp and True, exc, tb)
+ else:
+ self.respond(callback, resp and True, exc, tb)
+ self._dorpc(fcall.Tremove(), next)
+
+# vim:se sts=4 sw=4 et:
diff --git a/alternative_wmiircs/python/pyxp/client.py b/alternative_wmiircs/python/pyxp/client.py
new file mode 100644
index 0000000..85b8e3e
--- /dev/null
+++ b/alternative_wmiircs/python/pyxp/client.py
@@ -0,0 +1,346 @@
+# Copyright (C) 2009 Kris Maglione
+
+import operator
+import os
+import re
+import sys
+from threading import *
+import traceback
+
+import pyxp
+from pyxp import fcall, fields
+from pyxp.mux import Mux
+from pyxp.types import *
+
+if os.environ.get('NAMESPACE', None):
+ namespace = os.environ['NAMESPACE']
+else:
+ try:
+ namespace = '/tmp/ns.%s.%s' % (
+ os.environ['USER'],
+ re.sub(r'\.0$', '', os.environ['DISPLAY']))
+ except Exception:
+ pass
+NAMESPACE = namespace
+
+OREAD = 0x00
+OWRITE = 0x01
+ORDWR = 0x02
+OEXEC = 0x03
+OEXCL = 0x04
+OTRUNC = 0x10
+OREXEC = 0x20
+ORCLOSE = 0x40
+OAPPEND = 0x80
+
+ROOT_FID = 0
+
+class ProtocolException(Exception):
+ pass
+class RPCError(Exception):
+ pass
+
+class Client(object):
+ ROOT_FID = 0
+
+ @staticmethod
+ def respond(callback, data, exc=None, tb=None):
+ if callable(callback):
+ callback(data, exc, tb)
+
+
+ def __enter__(self):
+ return self
+ def __exit__(self, *args):
+ self._cleanup()
+
+ def __init__(self, conn=None, namespace=None, root=None):
+ if not conn and namespace:
+ conn = 'unix!%s/%s' % (NAMESPACE, namespace)
+ try:
+ self.lastfid = ROOT_FID
+ self.fids = set()
+ self.lock = RLock()
+
+ def process(data):
+ return fcall.Fcall.unmarshall(data)[1]
+ self.mux = Mux(conn, process, maxtag=256)
+
+ resp = self._dorpc(fcall.Tversion(version=pyxp.VERSION, msize=65535))
+ if resp.version != pyxp.VERSION:
+ raise ProtocolException, "Can't speak 9P version '%s'" % resp.version
+ self.msize = resp.msize
+
+ self._dorpc(fcall.Tattach(fid=ROOT_FID, afid=fcall.NO_FID,
+ uname=os.environ['USER'], aname=''))
+
+ if root:
+ path = self._splitpath(root)
+ resp = self._dorpc(fcall.Twalk(fid=ROOT_FID,
+ newfid=ROOT_FID,
+ wname=path))
+ except Exception, e:
+ traceback.print_exc(sys.stdout)
+ if getattr(self, 'mux', None):
+ self.mux.fd.close()
+ raise e
+
+ def _cleanup(self):
+ try:
+ for f in self.files:
+ f.close()
+ finally:
+ self.mux.fd.close()
+ self.mux = None
+
+ def _dorpc(self, req, callback=None, error=None):
+ def doresp(resp):
+ if isinstance(resp, fcall.Rerror):
+ raise RPCError, "%s[%d] RPC returned error: %s" % (
+ req.__class__.__name__, resp.tag, resp.ename)
+ if req.type != resp.type ^ 1:
+ raise ProtocolException, "Missmatched RPC message types: %s => %s" % (
+ req.__class__.__name__, resp.__class__.__name__)
+ return resp
+ def next(mux, resp):
+ try:
+ res = doresp(resp)
+ except Exception, e:
+ if error:
+ self.respond(error, None, e, None)
+ else:
+ self.respond(callback, None, e, None)
+ else:
+ self.respond(callback, res)
+ if not callback:
+ return doresp(self.mux.rpc(req))
+ self.mux.rpc(req, next)
+
+ def _splitpath(self, path):
+ return [v for v in path.split('/') if v != '']
+
+ def _getfid(self):
+ with self.lock:
+ if self.fids:
+ return self.fids.pop()
+ self.lastfid += 1
+ return self.lastfid
+ def _putfid(self, fid):
+ with self.lock:
+ self.fids.add(fid)
+
+ def _aclunk(self, fid, callback=None):
+ def next(resp, exc, tb):
+ if resp:
+ self._putfid(fid)
+ self.respond(callback, resp, exc, tb)
+ self._dorpc(fcall.Tclunk(fid=fid), next)
+
+ def _clunk(self, fid):
+ try:
+ self._dorpc(fcall.Tclunk(fid=fid))
+ finally:
+ self._putfid(fid)
+
+ def _walk(self, path):
+ fid = self._getfid()
+ ofid = ROOT_FID
+ while True:
+ self._dorpc(fcall.Twalk(fid=ofid, newfid=fid,
+ wname=path[0:fcall.MAX_WELEM]))
+ path = path[fcall.MAX_WELEM:]
+ ofid = fid
+ if len(path) == 0:
+ break
+
+ @apply
+ class Res:
+ def __enter__(res):
+ return fid
+ def __exit__(res, exc_type, exc_value, traceback):
+ if exc_type:
+ self._clunk(fid)
+ return Res
+
+ _file = property(lambda self: File)
+ def _open(self, path, mode, open, origpath=None):
+ resp = None
+
+ with self._walk(path) as nfid:
+ fid = nfid
+ resp = self._dorpc(open(fid))
+
+ def cleanup():
+ self._aclunk(fid)
+ file = self._file(self, origpath or '/'.join(path), resp, fid, mode, cleanup)
+ return file
+
+ def open(self, path, mode=OREAD):
+ path = self._splitpath(path)
+
+ def open(fid):
+ return fcall.Topen(fid=fid, mode=mode)
+ return self._open(path, mode, open)
+
+ def create(self, path, mode=OREAD, perm=0):
+ path = self._splitpath(path)
+ name = path.pop()
+
+ def open(fid):
+ return fcall.Tcreate(fid=fid, mode=mode, name=name, perm=perm)
+ return self._open(path, mode, open, origpath='/'.join(path + [name]))
+
+ def remove(self, path):
+ path = self._splitpath(path)
+
+ with self._walk(path) as fid:
+ self._dorpc(fcall.Tremove(fid=fid))
+
+ def stat(self, path):
+ path = self._splitpath(path)
+
+ try:
+ with self._walk(path) as fid:
+ resp = self._dorpc(fcall.Tstat(fid= fid))
+ st = resp.stat()
+ self._clunk(fid)
+ return st
+ except RPCError:
+ return None
+
+ def read(self, path, *args, **kwargs):
+ with self.open(path) as f:
+ return f.read(*args, **kwargs)
+ def readlines(self, path, *args, **kwargs):
+ with self.open(path) as f:
+ for l in f.readlines(*args, **kwargs):
+ yield l
+ def readdir(self, path, *args, **kwargs):
+ with self.open(path) as f:
+ for s in f.readdir(*args, **kwargs):
+ yield s
+ def write(self, path, *args, **kwargs):
+ with self.open(path, OWRITE) as f:
+ return f.write(*args, **kwargs)
+
+class File(object):
+
+ def __enter__(self):
+ return self
+ def __exit__(self, *args):
+ self.close()
+
+ def __init__(self, client, path, fcall, fid, mode, cleanup):
+ self.lock = RLock()
+ self.client = client
+ self.path = path
+ self.fid = fid
+ self._cleanup = cleanup
+ self.mode = mode
+ self.iounit = fcall.iounit
+ self.qid = fcall.qid
+ self.closed = False
+
+ self.offset = 0
+ def __del__(self):
+ if not self.closed:
+ self._cleanup()
+
+ def _dorpc(self, fcall, async=None, error=None):
+ if hasattr(fcall, 'fid'):
+ fcall.fid = self.fid
+ return self.client._dorpc(fcall, async, error)
+
+ def stat(self):
+ resp = self._dorpc(fcall.Tstat())
+ return resp.stat
+
+ def read(self, count=None, offset=None, buf=''):
+ if count is None:
+ count = self.iounit
+ res = []
+ with self.lock:
+ offs = self.offset
+ if offset is not None:
+ offs = offset
+ while count > 0:
+ n = min(count, self.iounit)
+ count -= n
+
+ resp = self._dorpc(fcall.Tread(offset=offs, count=n))
+ data = resp.data
+
+ offs += len(data)
+ res.append(data)
+
+ if len(data) < n:
+ break
+ if offset is None:
+ self.offset = offs
+ return ''.join(res)
+ def readlines(self):
+ last = None
+ while True:
+ data = self.read()
+ if not data:
+ break
+ lines = data.split('\n')
+ if last:
+ lines[0] = last + lines[0]
+ last = None
+ for i in range(0, len(lines) - 1):
+ yield lines[i]
+ last = lines[-1]
+ if last:
+ yield last
+ def write(self, data, offset=None):
+ if offset is None:
+ offset = self.offset
+ off = 0
+ with self.lock:
+ offs = self.offset
+ if offset is not None:
+ offs = offset
+ while off < len(data):
+ n = min(len(data), self.iounit)
+
+ resp = self._dorpc(fcall.Twrite(offset=offs,
+ data=data[off:off+n]))
+ off += resp.count
+ offs += resp.count
+ if resp.count < n:
+ break
+ if offset is None:
+ self.offset = offs
+ return off
+ def readdir(self):
+ if not self.qid.type & Qid.QTDIR:
+ raise Exception, "Can only call readdir on a directory"
+ off = 0
+ while True:
+ data = self.read(self.iounit, off)
+ if not data:
+ break
+ off += len(data)
+ for s in Stat.unmarshall_list(data):
+ yield s
+
+ def close(self):
+ assert not self.closed
+ self.closed = True
+ self._cleanup()
+ self.tg = None
+ self.fid = None
+ self.client = None
+ self.qid = None
+
+ def remove(self):
+ try:
+ self._dorpc(fcall.Tremove())
+ finally:
+ try:
+ self.close()
+ except Exception:
+ pass
+
+# vim:se sts=4 sw=4 et:
diff --git a/alternative_wmiircs/python/pyxp/dial.py b/alternative_wmiircs/python/pyxp/dial.py
new file mode 100644
index 0000000..55dcf9d
--- /dev/null
+++ b/alternative_wmiircs/python/pyxp/dial.py
@@ -0,0 +1,35 @@
+from socket import *
+
+__all__ = 'dial',
+
+def dial_unix(address):
+ sock = socket(AF_UNIX, SOCK_STREAM, 0)
+ sock.connect(address)
+ return sock
+
+def dial_tcp(host):
+ host = host.split('!')
+ if len(host) != 2:
+ return
+ host, port = host
+
+ res = getaddrinfo(host, port, AF_INET, SOCK_STREAM, 0, AI_PASSIVE)
+ for family, socktype, protocol, name, addr in res:
+ try:
+ sock = socket(family, socktype, protocol)
+ sock.connect(addr)
+ return sock
+ except error:
+ if sock:
+ sock.close()
+
+def dial(address):
+ proto, address = address.split('!', 1)
+ if proto == 'unix':
+ return dial_unix(address)
+ elif proto == 'tcp':
+ return dial_tcp(address)
+ else:
+ raise Exception('invalid protocol')
+
+# vim:se sts=4 sw=4 et:
diff --git a/alternative_wmiircs/python/pyxp/fcall.py b/alternative_wmiircs/python/pyxp/fcall.py
new file mode 100644
index 0000000..8e3c264
--- /dev/null
+++ b/alternative_wmiircs/python/pyxp/fcall.py
@@ -0,0 +1,131 @@
+from pyxp.messages import MessageBase, Message
+from pyxp.fields import *
+from types import Qid, Stat
+
+__all__ = 'Fcall',
+
+NO_FID = 1<<32 - 1
+MAX_WELEM = 16
+
+class FcallBase(MessageBase):
+ idx = 99
+ def __new__(cls, name, bases, attrs):
+ new_cls = super(FcallBase, cls).__new__(cls, name, bases, attrs)
+ new_cls.type = FcallBase.idx
+ if new_cls.type > 99:
+ new_cls.types[new_cls.type] = new_cls
+ FcallBase.idx += 1
+ return new_cls
+
+class Fcall(Message):
+ __metaclass__ = FcallBase
+ types = {}
+
+ def response(self, *args, **kwargs):
+ assert self.type % 2 == 0, "No respense type for response fcalls"
+ kwargs['tag'] = self.tag
+ return self.types[self.type + 1]()
+
+ @classmethod
+ def unmarshall(cls, data, offset=0):
+ res = super(Fcall, cls).unmarshall(data, offset)
+ if cls.type < 100:
+ res = cls.types[res[1].type].unmarshall(data, offset)
+ return res
+
+ size = Size(4, 4)
+ type = Int(1)
+ tag = Int(2)
+
+class Tversion(Fcall):
+ msize = Int(4)
+ version = String()
+class Rversion(Fcall):
+ msize = Int(4)
+ version = String()
+
+class Tauth(Fcall):
+ afid = Int(4)
+ uname = String()
+ aname = String()
+class Rauth(Fcall):
+ aqid = Qid.field()
+
+class Tattach(Fcall):
+ fid = Int(4)
+ afid = Int(4)
+ uname = String()
+ aname = String()
+class Rattach(Fcall):
+ qid = Qid.field()
+
+class Terror(Fcall):
+ def __init__(self):
+ raise Exception("Illegal 9P tag 'Terror' encountered")
+class Rerror(Fcall):
+ ename = String()
+
+class Tflush(Fcall):
+ oldtag = Int(2)
+class Rflush(Fcall):
+ pass
+
+class Twalk(Fcall):
+ fid = Int(4)
+ newfid = Int(4)
+ wname = Array(2, String())
+class Rwalk(Fcall):
+ wqid = Array(2, Qid.field())
+
+class Topen(Fcall):
+ fid = Int(4)
+ mode = Int(1)
+class Ropen(Fcall):
+ qid = Qid.field()
+ iounit = Int(4)
+
+class Tcreate(Fcall):
+ fid = Int(4)
+ name = String()
+ perm = Int(4)
+ mode = Int(1)
+class Rcreate(Fcall):
+ qid = Qid.field()
+ iounit = Int(4)
+
+class Tread(Fcall):
+ fid = Int(4)
+ offset = Int(8)
+ count = Int(4)
+class Rread(Fcall):
+ data = Data(4)
+
+class Twrite(Fcall):
+ fid = Int(4)
+ offset = Int(8)
+ data = Data(4)
+class Rwrite(Fcall):
+ count = Int(4)
+
+class Tclunk(Fcall):
+ fid = Int(4)
+class Rclunk(Fcall):
+ pass
+
+class Tremove(Tclunk):
+ pass
+class Rremove(Fcall):
+ pass
+
+class Tstat(Tclunk):
+ pass
+class Rstat(Fcall):
+ sstat = Size(2)
+ stat = Stat.field()
+
+class Twstat(Rstat):
+ pass
+class Rwstat(Fcall):
+ pass
+
+# vim:se sts=4 sw=4 et:
diff --git a/alternative_wmiircs/python/pyxp/fields.py b/alternative_wmiircs/python/pyxp/fields.py
new file mode 100644
index 0000000..ba61909
--- /dev/null
+++ b/alternative_wmiircs/python/pyxp/fields.py
@@ -0,0 +1,132 @@
+from datetime import datetime
+import operator
+
+class Field(object):
+ idx = 0
+
+ def __init__(self):
+ Field.idx += 1
+ self.id = Field.idx
+
+ def repr(self):
+ return self.__class__.__name__
+
+ def __repr__(self):
+ if hasattr(self, 'name'):
+ return '<Field %s "%s">' % (self.repr(), self.name)
+ return super(Field, self).__repr__()
+
+class Int(Field):
+ encoders = {}
+ decoders = {}
+ @classmethod
+ def encoder(cls, n):
+ if n not in cls.encoders:
+ exec ('def enc(n):\n' +
+ ' assert n == n & 0x%s, "Arithmetic overflow"\n' % ('ff' * n) +
+ ' return "".join((' + ','.join(
+ 'chr((n >> %d) & 0xff)' % (i * 8)
+ for i in range(0, n)) + ',))\n')
+ cls.encoders[n] = enc
+ return cls.encoders[n]
+ @classmethod
+ def decoder(cls, n):
+ if n not in cls.decoders:
+ cls.decoders[n] = eval('lambda data, offset: ' + '|'.join(
+ 'ord(data[offset + %d]) << %d' % (i, i * 8)
+ for i in range(0, n)))
+ return cls.decoders[n]
+
+ def __init__(self, size):
+ super(Int, self).__init__()
+ self.size = size
+ self.encode = self.encoder(size)
+ self.decode = self.decoder(size)
+ if self.__class__ == Int:
+ self.marshall = self.encode
+
+ def unmarshall(self, data, offset):
+ return self.size, self.decode(data, offset)
+ def marshall(self, val):
+ return self.encode(val)
+
+ def repr(self):
+ return '%s(%d)' % (self.__class__.__name__, self.size)
+
+class Size(Int):
+ def __init__(self, size, extra=0):
+ super(Size, self).__init__(size)
+ self.extra = extra
+
+ def marshall(self, val):
+ return lambda vals, i: self.encode(
+ reduce(lambda n, i: n + len(vals[i]),
+ range(i + 1, len(vals)),
+ self.extra))
+
+class Date(Int):
+ def __init__(self):
+ super(Date, self).__init__(4)
+
+ def unmarshall(self, data, offset):
+ val = self.decode(data, offset)
+ return 4, datetime.fromtimestamp(val)
+ def marshall(self, val):
+ return self.encode(int(val.strftime('%s')))
+
+class Data(Int):
+ def __init__(self, size=2):
+ super(Data, self).__init__(size)
+ def unmarshall(self, data, offset):
+ n = self.decode(data, offset)
+ offset += self.size
+ assert offset + n <= len(data), "String too long to unpack"
+ return self.size + n, data[offset:offset + n]
+ def marshall(self, val):
+ if isinstance(val, unicode):
+ val = val.encode('UTF-8')
+ return [self.encode(len(val)), val]
+
+# Note: Py3K strings are Unicode by default. They can't store binary
+# data.
+class String(Data):
+ def unmarshall(self, data, offset):
+ off, val = super(String, self).unmarshall(data, offset)
+ return off, val.decode('UTF-8')
+ def marshall(self, val):
+ if isinstance(val, str):
+ # Check for valid UTF-8
+ str.decode('UTF-8')
+ else:
+ val = val.encode('UTF-8')
+ return super(String, self).marshall(val)
+
+class Array(Int):
+ def __init__(self, size, spec):
+ super(Array, self).__init__(size)
+ self.spec = spec
+
+ def unmarshall(self, data, offset):
+ start = offset
+ n = self.decode(data, offset)
+ offset += self.size
+ res = []
+ for i in range(0, n):
+ size, val = self.spec.unmarshall(data, offset)
+ if isinstance(val, list):
+ res += val
+ else:
+ res.append(val)
+ offset += size
+ return offset - start, res
+ def marshall(self, vals):
+ res = [self.encode(len(vals))]
+ for val in vals:
+ val = self.spec.marshall(val)
+ if isinstance(val, list):
+ res += val
+ else:
+ res.append(val)
+ return res
+
+# vim:se sts=4 sw=4 et:
diff --git a/alternative_wmiircs/python/pyxp/messages.py b/alternative_wmiircs/python/pyxp/messages.py
new file mode 100644
index 0000000..8498e50
--- /dev/null
+++ b/alternative_wmiircs/python/pyxp/messages.py
@@ -0,0 +1,73 @@
+from pyxp.fields import *
+
+class MessageBase(type):
+ idx = 0
+
+ def __new__(cls, name, bases, attrs):
+ fields = []
+ fieldmap = {}
+ for k, v in attrs.items():
+ if isinstance(v, Field):
+ attrs[k] = None
+ fields.append(v)
+ fieldmap[k] = v
+ v.name = k
+ fields.sort(lambda a, b: cmp(a.id, b.id))
+
+ new_cls = super(MessageBase, cls).__new__(cls, name, bases, attrs)
+
+ map = getattr(new_cls, 'fieldmap', {})
+ map.update(fieldmap)
+ new_cls.fields = getattr(new_cls, 'fields', ()) + tuple(fields)
+ new_cls.fieldmap = map
+ for f in fields:
+ f.message = new_cls
+ return new_cls
+
+class Message(object):
+ __metaclass__ = MessageBase
+ def __init__(self, *args, **kwargs):
+ if args:
+ args = dict(zip([f.name for f in self.fields], args))
+ args.update(kwargs)
+ kwargs = args;
+ for k, v in kwargs.iteritems():
+ assert k in self.fieldmap, "Invalid keyword argument"
+ setattr(self, k, v)
+
+ @classmethod
+ def field(cls):
+ class MessageField(Field):
+ def repr(self):
+ return cls.__name__
+ def unmarshall(self, data, offset):
+ return cls.unmarshall(data, offset)
+ def marshall(self, val):
+ return val.marshall()
+ return MessageField()
+
+ @classmethod
+ def unmarshall(cls, data, offset=0):
+ vals = {}
+ start = offset
+ for field in cls.fields:
+ size, val = field.unmarshall(data, offset)
+ offset += size
+ vals[field.name] = val
+ return offset - start, cls(**vals)
+ def marshall(self):
+ res = []
+ callbacks = []
+ for field in self.fields:
+ val = field.marshall(getattr(self, field.name, None))
+ if callable(val):
+ callbacks.append((val, len(res)))
+ if isinstance(val, list):
+ res += val
+ else:
+ res.append(val)
+ for fn, i in reversed(callbacks):
+ res[i] = fn(res, i)
+ return res
+
+# vim:se sts=4 sw=4 et:
diff --git a/alternative_wmiircs/python/pyxp/mux.py b/alternative_wmiircs/python/pyxp/mux.py
new file mode 100644
index 0000000..6e7babb
--- /dev/null
+++ b/alternative_wmiircs/python/pyxp/mux.py
@@ -0,0 +1,195 @@
+# Derived from libmux, available in Plan 9 under /sys/src/libmux
+# under the following terms:
+#
+# Copyright (C) 2003-2006 Russ Cox, Massachusetts Institute of Technology
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+
+import sys
+import traceback
+
+from pyxp import fields
+from pyxp.dial import dial
+from threading import *
+Condition = Condition().__class__
+
+__all__ = 'Mux',
+
+class Mux(object):
+ def __init__(self, con, process, flush=None, mintag=0, maxtag=1<<16 - 1):
+ self.queue = set()
+ self.lock = RLock()
+ self.rendez = Condition(self.lock)
+ self.outlock = RLock()
+ self.inlock = RLock()
+ self.process = process
+ self.flush = flush
+ self.wait = {}
+ self.free = set(range(mintag, maxtag))
+ self.mintag = mintag
+ self.maxtag = maxtag
+ self.muxer = None
+
+ if isinstance(con, basestring):
+ con = dial(con)
+ self.fd = con
+
+ if self.fd is None:
+ raise Exception("No connection")
+
+ def mux(self, rpc):
+ try:
+ rpc.waiting = True
+ self.lock.acquire()
+ while self.muxer and self.muxer != rpc and rpc.data is None:
+ rpc.wait()
+
+ if rpc.data is None:
+ assert not self.muxer or self.muxer is rpc
+ self.muxer = rpc
+ self.lock.release()
+ try:
+ while rpc.data is None:
+ data = self.recv()
+ if data is None:
+ self.lock.acquire()
+ self.queue.remove(rpc)
+ raise Exception("unexpected eof")
+ self.dispatch(data)
+ finally:
+ self.lock.acquire()
+ self.electmuxer()
+ except Exception, e:
+ traceback.print_exc(sys.stdout)
+ if self.flush:
+ self.flush(self, rpc.data)
+ raise e
+ finally:
+ if self.lock._is_owned():
+ self.lock.release()
+
+ if rpc.async:
+ if callable(rpc.async):
+ rpc.async(self, rpc.data)
+ else:
+ return rpc.data
+
+ def rpc(self, dat, async=None):
+ rpc = self.newrpc(dat, async)
+ if async:
+ with self.lock:
+ if self.muxer is None:
+ self.electmuxer()
+ else:
+ return self.mux(rpc)
+
+ def electmuxer(self):
+ async = None
+ for rpc in self.queue:
+ if self.muxer != rpc:
+ if rpc.async:
+ async = rpc
+ else:
+ self.muxer = rpc
+ rpc.notify()
+ return
+ self.muxer = None
+ if async:
+ self.muxer = async
+ t = Thread(target=self.mux, args=(async,))
+ t.daemon = True
+ t.start()
+
+ def dispatch(self, dat):
+ tag = dat.tag
+ rpc = None
+ with self.lock:
+ rpc = self.wait.get(tag, None)
+ if rpc is None or rpc not in self.queue:
+ #print "bad rpc tag: %u (no one waiting on it)" % dat.tag
+ return
+ self.puttag(rpc)
+ self.queue.remove(rpc)
+ rpc.dispatch(dat)
+
+ def gettag(self, r):
+ tag = 0
+
+ while not self.free:
+ self.rendez.wait()
+
+ tag = self.free.pop()
+
+ if tag in self.wait:
+ raise Exception("nwait botch")
+
+ self.wait[tag] = r
+
+ r.tag = tag
+ r.orig.tag = r.tag
+ return r.tag
+
+ def puttag(self, rpc):
+ if rpc.tag in self.wait:
+ del self.wait[rpc.tag]
+ self.free.add(rpc.tag)
+ self.rendez.notify()
+
+ def send(self, dat):
+ data = ''.join(dat.marshall())
+ n = self.fd.send(data)
+ return n == len(data)
+ def recv(self):
+ try:
+ with self.inlock:
+ data = self.fd.recv(4)
+ if data:
+ len = fields.Int.decoders[4](data, 0)
+ data += self.fd.recv(len - 4)
+ return self.process(data)
+ except Exception, e:
+ traceback.print_exc(sys.stdout)
+ print repr(data)
+ return None
+
+ def newrpc(self, dat, async=None):
+ rpc = Rpc(self, dat, async)
+ tag = None
+
+ with self.lock:
+ self.gettag(rpc)
+ self.queue.add(rpc)
+
+ if rpc.tag >= 0 and self.send(dat):
+ return rpc
+
+ with self.lock:
+ self.queue.remove(rpc)
+ self.puttag(rpc)
+
+class Rpc(Condition):
+ def __init__(self, mux, data, async=None):
+ super(Rpc, self).__init__(mux.lock)
+ self.mux = mux
+ self.orig = data
+ self.data = None
+ self.waiting = False
+ self.async = async
+
+ def dispatch(self, data=None):
+ self.data = data
+ if not self.async or self.waiting:
+ self.notify()
+ elif callable(self.async):
+ Thread(target=self.async, args=(self.mux, data)).start()
+
+# vim:se sts=4 sw=4 et:
diff --git a/alternative_wmiircs/python/pyxp/types.py b/alternative_wmiircs/python/pyxp/types.py
new file mode 100644
index 0000000..a5ca1c0
--- /dev/null
+++ b/alternative_wmiircs/python/pyxp/types.py
@@ -0,0 +1,55 @@
+from pyxp.messages import Message
+from pyxp.fields import *
+
+__all__ = 'Qid', 'Stat'
+
+class Qid(Message):
+ QTFILE = 0x00
+ QTLINK = 0x01
+ QTSYMLINK = 0x02
+ QTTMP = 0x04
+ QTAUTH = 0x08
+ QTMOUNT = 0x10
+ QTEXCL = 0x20
+ QTAPPEND = 0x40
+ QTDIR = 0x80
+
+ type = Int(1)
+ version = Int(4)
+ path = Int(8)
+
+class Stat(Message):
+ DMDIR = 0x80000000
+ DMAPPEND = 0x40000000
+ DMEXCL = 0x20000000
+ DMMOUNT = 0x10000000
+ DMAUTH = 0x08000000
+ DMTMP = 0x04000000
+ DMSYMLINK = 0x02000000
+ DMDEVICE = 0x00800000
+ DMNAMEDPIPE = 0x00200000
+ DMSOCKET = 0x00100000
+ DMSETUID = 0x00080000
+ DMSETGID = 0x00040000
+
+ @classmethod
+ def unmarshall_list(cls, data, offset=0):
+ while offset < len(data):
+ n, stat = cls.unmarshall(data, offset)
+ offset += n
+ yield stat
+
+ size = Size(2)
+ type = Int(2)
+ dev = Int(4)
+ qid = Qid.field()
+ mode = Int(4)
+ atime = Date()
+ mtime = Date()
+ length = Int(8)
+ name = String()
+ uid = String()
+ gid = String()
+ muid = String()
+
+# vim:se sts=4 sw=4 et:
diff --git a/alternative_wmiircs/python/wmiirc b/alternative_wmiircs/python/wmiirc
new file mode 100755
index 0000000..75a148a
--- /dev/null
+++ b/alternative_wmiircs/python/wmiirc
@@ -0,0 +1,12 @@
+#!/usr/bin/env python
+import os, sys
+path = []
+for p in os.environ.get("WMII_CONFPATH", "").split(':'):
+ path += [p, p + '/python']
+sys.path = path + sys.path
+
+from pygmi import events
+import wmiirc
+
+events.loop()
+
diff --git a/alternative_wmiircs/python/wmiirc.py b/alternative_wmiircs/python/wmiirc.py
new file mode 100644
index 0000000..f385757
--- /dev/null
+++ b/alternative_wmiircs/python/wmiirc.py
@@ -0,0 +1,308 @@
+import datetime
+import operator
+import os
+import re
+import sys
+import traceback
+from threading import Thread, Timer
+
+import pygmi
+from pygmi import *
+from pygmi import event
+
+identity = lambda k: k
+
+# Begin Configuration
+#
+# Note: This file loads ~/.wmii/wmiirc_local.py if it exists.
+# Configuration should be placed in that file, and this file
+# left unmodified, if possible. wmiirc_local should import
+# wmiirc or any other modules it needs.
+
+# Keys
+keys.defs = dict(
+ mod='Mod4',
+ left='h',
+ down='j',
+ up='k',
+ right='l')
+
+# Bars
+noticetimeout=5
+noticebar=('right', '!notice')
+
+# Theme
+background = '#333333'
+floatbackground='#222222'
+
+wmii['font'] = 'drift,-*-fixed-*-*-*-*-9-*-*-*-*-*-*-*'
+wmii['normcolors'] = '#000000', '#c1c48b', '#81654f'
+wmii['focuscolors'] = '#000000', '#81654f', '#000000'
+wmii['grabmod'] = keys.defs['mod']
+wmii['border'] = 2
+
+def setbackground(color):
+ call('xsetroot', '-solid', color)
+setbackground(background)
+
+terminal = 'wmiir', 'setsid', '@TERMINAL@'
+pygmi.shell = os.environ.get('SHELL', 'sh')
+
+@defmonitor
+def load(self):
+ return wmii.cache['normcolors'], re.sub(r'^.*: ', '', call('uptime')).replace(', ', ' ')
+@defmonitor
+def time(self):
+ return wmii.cache['focuscolors'], datetime.datetime.now().strftime('%c')
+
+wmii.colrules = (
+ ('gimp', '17+83+41'),
+ ('.*', '62+38 # Golden Ratio'),
+)
+
+wmii.tagrules = (
+ ('MPlayer|VLC', '~'),
+)
+
+def unresponsive_client(client):
+ msg = 'The following client is not responding. What would you like to do?'
+ resp = call('wihack', '-transient', client.id,
+ 'xmessage', '-nearmouse', '-buttons', 'Kill,Wait', '-print',
+ '%s\n %s' % (msg, client.label))
+ if resp == 'Kill':
+ client.slay()
+
+# End Configuration
+
+client.awrite('/event', 'Start wmiirc')
+
+tags = Tags()
+events.bind({
+ ('Quit', Match('Start', 'wmiirc')): lambda *a: sys.exit(),
+ 'CreateTag': tags.add,
+ 'DestroyTag': tags.delete,
+ 'FocusTag': tags.focus,
+ 'UnfocusTag': tags.unfocus,
+ 'UrgentTag': lambda args: tags.set_urgent(args.split()[1], True),
+ 'NotUrgentTag': lambda args: tags.set_urgent(args.split()[1], False),
+
+ 'AreaFocus': lambda args: (args == '~' and
+ (setbackground(floatbackground), True) or
+ setbackground(background)),
+
+ 'Unresponsive': lambda args: Thread(target=unresponsive_client,
+ args=(Client(args),)).start(),
+
+ 'Notice': lambda args: notice.show(args),
+
+ Match(('LeftBarClick', 'LeftBarDND'), '1'): lambda e, b, tag: tags.select(tag),
+ Match('LeftBarClick', '4'): lambda *a: tags.select(tags.next(True)),
+ Match('LeftBarClick', '5'): lambda *a: tags.select(tags.next()),
+
+ Match('LeftBarMouseDown', 3): lambda e, n, tag: clickmenu((
+ ('Delete', lambda t: Tag(t).delete()),
+ ), (tag,)),
+ Match('ClientMouseDown', _, 3): lambda e, client, n: clickmenu((
+ ('Delete', lambda c: Client(c).kill()),
+ ('Kill', lambda c: Client(c).slay()),
+ ('Fullscreen', lambda c: Client(c).set('Fullscreen', 'on')),
+ ), (client,)),
+
+ Match('ClientClick', _, 4): lambda e, c, n: Tag('sel').select('up'),
+ Match('ClientClick', _, 5): lambda e, c, n: Tag('sel').select('down'),
+})
+
+@apply
+class Actions(event.Actions):
+ def rehash(self, args=''):
+ program_menu.choices = program_list(os.environ['PATH'].split(':'))
+ def showkeys(self, args=''):
+ message(keys.help)
+ def quit(self, args=''):
+ wmii.ctl('quit')
+ def eval_(self, args=''):
+ exec args
+ def exec_(self, args=''):
+ wmii['exec'] = args
+ def exit(self, args=''):
+ client.awrite('/event', 'Quit')
+
+program_menu = Menu(histfile='%s/history.progs' % confpath[0], nhist=5000,
+ action=curry(call, 'wmiir', 'setsid',
+ pygmi.shell, '-c', background=True))
+action_menu = Menu(histfile='%s/history.actions' % confpath[0], nhist=500,
+ choices=lambda: Actions._choices,
+ action=Actions._call)
+tag_menu = Menu(histfile='%s/history.tags' % confpath[0], nhist=100,
+ choices=lambda: sorted(tags.tags.keys()))
+
+def clickmenu(choices, args):
+ ClickMenu(choices=(k for k, v in choices),
+ action=lambda choice: dict(choices).get(choice, identity)(*args)
+ ).call()
+
+class Notice(Button):
+ def __init__(self):
+ super(Notice, self).__init__(*noticebar, colors=wmii.cache['normcolors'])
+ self.timer = None
+ self.show(' ')
+
+ def tick(self):
+ self.create(wmii.cache['normcolors'], ' ')
+
+ def write(self, notice):
+ client.awrite('/event', 'Notice %s' % notice.replace('\n', ' '))
+
+ def show(self, notice):
+ if self.timer:
+ self.timer.cancel()
+ self.create(wmii.cache['normcolors'], notice)
+ self.timer = Timer(noticetimeout, self.tick)
+ self.timer.start()
+notice = Notice()
+
+keys.bind('main', (
+ "Moving around",
+ ('%(mod)s-%(left)s', "Select the client to the left",
+ lambda k: Tag('sel').select('left')),
+ ('%(mod)s-%(right)s', "Select the client to the right",
+ lambda k: Tag('sel').select('right')),
+ ('%(mod)s-%(up)s', "Select the client above",
+ lambda k: Tag('sel').select('up')),
+ ('%(mod)s-%(down)s', "Select the client below",
+ lambda k: Tag('sel').select('down')),
+
+ ('%(mod)s-space', "Toggle between floating and managed layers",
+ lambda k: Tag('sel').select('toggle')),
+
+ "Moving through stacks",
+ ('%(mod)s-Control-%(up)s', "Select the stack above",
+ lambda k: Tag('sel').select('up', stack=True)),
+ ('%(mod)s-Control-%(down)s', "Select the stack below",
+ lambda k: Tag('sel').select('down', stack=True)),
+
+
+ "Moving clients around",
+ ('%(mod)s-Shift-%(left)s', "Move selected client to the left",
+ lambda k: Tag('sel').send(Client('sel'), 'left')),
+ ('%(mod)s-Shift-%(right)s', "Move selected client to the right",
+ lambda k: Tag('sel').send(Client('sel'), 'right')),
+ ('%(mod)s-Shift-%(up)s', "Move selected client up",
+ lambda k: Tag('sel').send(Client('sel'), 'up')),
+ ('%(mod)s-Shift-%(down)s', "Move selected client down",
+ lambda k: Tag('sel').send(Client('sel'), 'down')),
+
+ ('%(mod)s-Shift-space', "Toggle selected client between floating and managed layers",
+ lambda k: Tag('sel').send(Client('sel'), 'toggle')),
+
+ "Client actions",
+ ('%(mod)s-f', "Toggle selected client's fullsceen state",
+ lambda k: Client('sel').set('Fullscreen', 'toggle')),
+ ('%(mod)s-Shift-c', "Close client",
+ lambda k: Client('sel').kill()),
+
+ "Changing column modes",
+ ('%(mod)s-d', "Set column to default mode",
+ lambda k: setattr(Tag('sel').selcol, 'mode', 'default-max')),
+ ('%(mod)s-s', "Set column to stack mode",
+ lambda k: setattr(Tag('sel').selcol, 'mode', 'stack-max')),
+ ('%(mod)s-m', "Set column to max mode",
+ lambda k: setattr(Tag('sel').selcol, 'mode', 'stack+max')),
+
+ "Running programs",
+ ('%(mod)s-a', "Open wmii actions menu",
+ lambda k: action_menu()),
+ ('%(mod)s-p', "Open program menu",
+ lambda k: program_menu()),
+
+ ('%(mod)s-Return', "Launch a terminal",
+ lambda k: call(*terminal, background=True)),
+
+ "Tag actions",
+ ('%(mod)s-t', "Change to another tag",
+ lambda k: tags.select(tag_menu())),
+ ('%(mod)s-Shift-t', "Retag the selected client",
+ lambda k: setattr(Client('sel'), 'tags', tag_menu())),
+
+ ('%(mod)s-n', "Move to the view to the left",
+ lambda k: tags.select(tags.next())),
+ ('%(mod)s-b', "Move to the view to the right",
+ lambda k: tags.select(tags.next(True))),
+ ('%(mod)s-Shift-n', "Move to the view to the left, take along current client",
+ lambda k: tags.select(tags.next(), take_client=Client('sel'))),
+ ('%(mod)s-Shift-b', "Move to the view to the right, take along current client",
+ lambda k: tags.select(tags.next(True), take_client=Client('sel'))),
+
+ ('%(mod)s-i', "Move to the newer tag in the tag stack",
+ lambda k: tags.select(tags.NEXT)),
+ ('%(mod)s-o', "Move to the older tag in the tag stack",
+ lambda k: tags.select(tags.PREV)),
+ ('%(mod)s-Shift-i', "Move to the newer tag in the tag stack, take along current client",
+ lambda k: tags.select(tags.NEXT, take_client=Client('sel'))),
+ ('%(mod)s-Shift-o', "Move to the older tag in the tag stack, take along current client",
+ lambda k: tags.select(tags.PREV, take_client=Client('sel'))),
+
+))
+def bind_num(i):
+ keys.bind('main', (
+ "Tag actions",
+ ('%%(mod)s-%d' % i, "Move to view '%d'" % i,
+ lambda k: tags.select(str(i))),
+ ('%%(mod)s-Shift-%d' % i, "Retag selected client with tag '%d'" % i,
+ lambda k: setattr(Client('sel'), 'tags', i)),
+ ))
+map(bind_num, range(0, 10))
+
+keys.bind('main', (
+ "Changing modes",
+ ('%(mod)s-Control-r', "Enter resize mode",
+ lambda k: setattr(keys, 'mode', 'resize')),
+ ('%(mod)s-Control-t', "Enter passthrough mode",
+ lambda k: setattr(keys, 'mode', 'passthrough')),
+));
+keys.bind('passthrough', (
+ "Changing modes",
+ ('%(mod)s-Control-t', "Leave passthrough mode",
+ lambda k: setattr(keys, 'mode', 'main')),
+));
+
+keys.bind('resize', (
+ ('Escape', "Leave resize mode",
+ lambda k: setattr(keys, 'mode', 'main')),
+), import_={'main': ('%(mod)s-%(left)s', '%(mod)s-%(right)s',
+ '%(mod)s-%(up)s', '%(mod)s-%(down)s',
+ '%(mod)s-Space')})
+
+def addresize(mod, desc, cmd, *args):
+ keys.bind('resize', (
+ (mod + '%(left)s', "%s selected client to the left" % desc,
+ lambda k: Tag('sel').ctl(cmd, 'sel sel', 'left',
+ *args)),
+ (mod + '%(right)s', "%s selected client to the right" % desc,
+ lambda k: Tag('sel').ctl(cmd, 'sel sel', 'right',
+ *args)),
+ (mod + '%(up)s', "%s selected client up" % desc,
+ lambda k: Tag('sel').ctl(cmd, 'sel sel', 'up',
+ *args)),
+ (mod + '%(down)s', "%s selected client down" % desc,
+ lambda k: Tag('sel').ctl(cmd, 'sel sel', 'down',
+ *args)),
+ ));
+addresize('', 'Grow', 'grow')
+addresize('Control-', 'Shrink', 'grow', '-1')
+addresize('Shift-', 'Nudge', 'nudge')
+
+Thread(target=lambda: Actions.rehash()).start()
+
+if not os.environ.get('WMII_NOPLUGINS', ''):
+ dirs = filter(curry(os.access, _, os.R_OK),
+ ('%s/plugins' % dir for dir in confpath))
+ files = filter(re.compile(r'\.py$').search,
+ reduce(operator.add, map(os.listdir, dirs), []))
+ for f in ['wmiirc_local'] + ['plugins.%s' % file[:-3] for file in files]:
+ try:
+ exec 'import %s' % f
+ except Exception, e:
+ traceback.print_exc(sys.stdout)
+
+# vim:se sts=4 sw=4 et:
diff --git a/alternative_wmiircs/ruby/HISTORY b/alternative_wmiircs/ruby/HISTORY
new file mode 100644
index 0000000..d75f69d
--- /dev/null
+++ b/alternative_wmiircs/ruby/HISTORY
@@ -0,0 +1,233 @@
+= 2006-09-30
+
+* Included 1.1.0 release of Ruby-IXP.
+
+
+= 2006-09-29
+
+* Fixed bug in toggle_maximize method (in rc.rb) due
+ to accessing a nonexistent file in IXP file system.
+
+ Thanks to Christian von Mueffling for reporting this bug.
+
+* Fixed problem with reading
+ index (Wmii::Client#index) of
+ currently selected client.
+
+* Wmii.find_client now accepts a variable number of places to be searched.
+
+
+= 2006-09-28
+
+* Added number_view_buttons method (in rc.rb) which numbers
+ the view buttons displayed on the bar, from left to right.
+
+
+= 2006-09-27
+
+* Included two main concurrency fixes for Ruby-IXP.
+
+
+= 2006-09-24
+
+* Added two-stage event handling,
+ to minimize the number of events
+ missed while processing an event.
+
+
+= 2006-09-23
+
+* Fixed event & status bar loop. It was forgotten when I transitioned
+ to the new Ixp::Node#method_missing behavior on 2006-09-22.
+
+ Thanks to Fredrik Ternerot for reporting this bug.
+
+* When selecting views based on their first letter: if more than one
+ view matches, then they are cycled (adapted from Fredrik Ternerot).
+
+* Added focus_view_matching method in rc.rb.
+
+* Fixed errors that occurred when the tile and
+ diamond arrangements were applied to empty views.
+
+
+= 2006-09-22
+
+* Ixp::Node#method_missing now only dereferences files. Also,
+ the ! notation has been removed, as you can see below.
+
+ >> Wmii.fs.bar.status
+ => #<Ixp::Node:0xb7b5940c @path="/bar/status">
+ >> Wmii.fs.bar.status.read
+ => ["colors", "data"]
+ >> Wmii.fs.bar.status.data
+ => "Fri Sep 22 18:46:11 PDT 2006 | 0.06 0.10 0.08 | 531M 100% /home"
+ >> Wmii.fs.bar.status.data!
+ => #<Ixp::Node:0xb7b377e4 @path="/bar/status/data!">
+
+
+= 2006-09-21
+
+* Fix some forgotten changes from show_menu() returning *nil*.
+
+* Exception error message (xmessage) now lets you restart *wmiirc*.
+
+* Updated event loop to generate less 9P traffic.
+
+
+= 2006-09-20
+
+* Included code from upcoming Ruby-IXP 1.1.0 release.
+
+* Ixp::Node#method_missing now only dereferences a node
+ if the method is suffixed with an exclamation mark.
+
+* show_menu now returns *nil* if nothing was chosen.
+
+* Updated event loop for {wmii-3.1's /event overload bug
+ fix}[http://wmii.de/pipermail/wmii/2006-September/002718.html].
+
+* Added explicit termination of already running instances
+ in *wmiirc* via Process.kill and `ps`, instead of using
+ /event as a means of coordinating said task.
+
+
+= 2006-09-19
+
+* Included Ruby-IXP 1.0.3 release.
+
+* Added Ixp::Node#open method to reduce 9P traffic.
+
+* Added ability to fetch a sub-node
+ via Ixp::Node#method_missing, while
+ not dereferencing it (reading its
+ contents if it is a file), by adding
+ an exclamation to the file name.
+
+ For example, consider the following output in *wmiish*.
+
+ >> Wmii.fs.bar.status.data
+ => "Tue Sep 19 10:50:41 PDT 2006 | 0.30 0.43 0.29 | 1.7G 98% /home"
+ >> Wmii.fs.bar.status.data!
+ => #<Ixp::Node:0xb7bf1f18 @path="/bar/status/data">
+
+* *wmiirc* no longer automatically resumes from error. Instead,
+ it throws you a terminal and shows you the error details so
+ you have a chance to fix it and restart *wmiirc* yourself.
+
+
+= 2006-09-18
+
+* Included Ruby-IXP 1.0.2 release.
+
+
+= 2006-09-17
+
+* Added Wmii::View#empty? and Wmii::Area#empty? methods.
+
+* change_tag_from_menu now returns the chosen tag.
+
+* Included Ruby-IXP 1.0.1 release.
+
+
+= 2006-09-16
+
+* Fixed toggling of maximization
+ of currently focused client,
+ via toggle_maximize in rc.rb.
+
+ Thanks to Fredrik Ternerot for reporting this bug.
+
+
+= 2006-09-15
+
+* Added Wmii.get_view and Wmii.get_client
+ methods, to further minimize hard-coded
+ IXP file system paths. This will make it
+ easier to upgrade to wmii-4 later on.
+
+* Fixed ruby-ixp to be internally buffered for Ixp#read.
+
+* Event loop now uses Ixp#read instead of *wmiir*.
+
+* Already running configurations now correctly
+ exit when another instance starts up.
+
+
+= 2006-09-14
+
+* Added ability to swap current client with the
+ currently focused client in any other column.
+
+
+= 2006-09-13
+
+* Reverted to *wmiir* for event loop, because
+ Ixp#read isn't internally buffered!
+
+* Changed Wmii::View#each to Wmii::View#each_column because
+ floating area isn't a column (it doesn't have /mode file).
+
+* Added shortcuts for setting layouts of all columns in current view.
+
+* Added shortcuts for selection of current column.
+
+* Fixed ability to terminate multiple clients.
+
+
+= 2006-09-12
+
+* Event loop now uses Ixp#read instead of *wmiir*.
+
+ * Already running configurations now correctly
+ exit when another instance starts up.
+
+* Added Wmii::View#diamond! -- a diamond-shaped automated client arrangement.
+
+* Added Wmii::Area#length= for setting number of clients in a column.
+
+
+= 2006-09-11
+
+* Added exception logging and recovery mechanism.
+
+ * wmiirc is now split into a loader
+ file (wmiirc) and a configuration
+ file (wmiirc-config.rb), just
+ like in the ruby-wmii project.
+
+* IXPException' are no longer hidden away inside Ixp.
+
+* Moved support for destructive area-operations
+ from Wmii#with_selection into Array#each so
+ that it is generally available.
+
+
+= 2006-09-10
+
+* Added wmiish--an interactive Ruby shell for controlling wmii.
+
+* Lots of major refactoring in Ixp and Wmii.
+ * Moved utility methods from wmiirc into rc.rb.
+
+
+= 2006-09-09
+
+* Cleaned up IXP abstraction... now
+ multiple levels of method_missing
+ works, and so does self[sub_path]
+
+* Wmii#with_selection now supports destructive area-operations.
+
+* Update for compliance with new unique-client-id in filesystem patch.
+
+
+= 2006-08-31
+
+* Added facility which sends the selection
+ to temporary view or switches back again.
+
+
+= 2006-08-30
+
+* Add Wmii#with_selection method for operating on all clients in selection.
diff --git a/alternative_wmiircs/ruby/LICENSE b/alternative_wmiircs/ruby/LICENSE
new file mode 100644
index 0000000..893414a
--- /dev/null
+++ b/alternative_wmiircs/ruby/LICENSE
@@ -0,0 +1,21 @@
+(the ISC license)
+
+Copyright 2006 Suraj N. Kurapati <sunaku@gmail.com>
+Copyright 2007 Kris Maglione <jg@suckless.org>
+Copyright 2007 Nick Stenning <nick@whiteink.com>
+Copyright 2009 Daniel Wäber <waeber@inf.fu-berlin.de>
+Copyright 2009 Michael Andrus <centyx@centyx.net>
+Copyright 2009 Simon Hafner <hafnersimon@gmail.com>
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
diff --git a/alternative_wmiircs/ruby/Makefile b/alternative_wmiircs/ruby/Makefile
new file mode 100644
index 0000000..5d8fd65
--- /dev/null
+++ b/alternative_wmiircs/ruby/Makefile
@@ -0,0 +1,13 @@
+ROOT=../..
+include $(ROOT)/mk/hdr.mk
+include $(ROOT)/mk/wmii.mk
+
+DOCS = README \
+ HISTORY \
+ LICENSE
+EXECS = wmiirc
+TEXT = config.rb \
+ config.yaml
+
+DIR = $(ETC)/wmii$(CONFVERSION)/ruby
+DOCDIR = $(DOC)/alternative_wmiircs/ruby
diff --git a/alternative_wmiircs/ruby/README b/alternative_wmiircs/ruby/README
new file mode 100644
index 0000000..788e0b4
--- /dev/null
+++ b/alternative_wmiircs/ruby/README
@@ -0,0 +1,94 @@
+
+This is a modified version of sunaku's wmiirc, designed for
+his Rumai Ruby module. Minor configuration changes, namely to
+the color scheme and default key bindings, as well as the
+configuration search path, exist in this version. Builtin mpd
+support has also been removed. Also added is support for
+string interpolation in key bindings, as should be apparent in
+the included config.yaml.
+
+In particular, not that there is no need to copy any files to
+~/.wmii-hg or ~/.wmii other than config.yaml. The script will
+happily load the requisite files from their default install
+location. They can be loaded either by involing wmii as
+follows:
+
+ wmiir -r ruby/wmiirc
+
+or running the following after startup:
+
+ wmiir xwrite /ctl spawn ruby/wmiirc
+
+The rumai gem is still required, as noted below.
+
+The original readme appears below unmodified:
+
+sunaku's Ruby wmiirc
+====================
+
+This is my wmii configuration, described in these articles:
+
+ http://wmii.suckless.org/alternative_wmiirc_scripts
+
+ http://snk.tuxfamily.org/lib/rumai/
+
+ http://article.gmane.org/gmane.comp.window-managers.wmii/1704
+
+ http://snk.tuxfamily.org/web/2006-07-01-wmii-3-1-configuration-in-ruby.html
+
+Dependencies:
+
+ wmii 3.6 or newer (preferably wmii-hg)
+
+ Ruby 1.8.6 or newer
+
+ RubyGems 1.3.1 or newer
+
+Installation:
+
+ # library
+ gem install rumai # required
+ gem install librmpd # optional
+
+ # install
+ mv ~/.wmii-hg ~/.wmii-hg.backup
+ git clone git://github.com/sunaku/wmiirc.git ~/.wmii-hg
+
+ # choose
+ cd ~/.wmii-hg
+ git checkout --track -b CHOICE origin/CHOICE # choices are:
+
+ +--------+------------------------------------------------+
+ | CHOICE | DESCRIPTION |
+ +--------+------------------------------------------------+
+ | dvorak | sunaku's personal configuration; DSK friendly! |
+ | qwerty | QWERTY port of sunaku's personal configuration |
+ | strict | port of the default wmiirc shipped with wmii |
+ | master | barebones template for starting from scratch |
+ +--------+------------------------------------------------+
+
+ # run
+ ~/.wmii-hg/wmiirc
+
+Documentation:
+
+ # see list of all key bindings
+ egrep '^ +\$\{\w+\}' ~/.wmii-hg/config.yaml
+
+ # read the configuration file
+ less ~/.wmii-hg/config.yaml
+
+Configuration:
+
+ Edit ~/.wmii-hg/config.yaml to your liking.
+
+ Run ~/.wmii-hg/wmiirc to apply your changes.
+
+Contribution:
+
+ Fork this project on GitHub and send pull requests.
+
+Questions:
+
+ Send me an e-mail (see LICENSE for my address).
+
diff --git a/alternative_wmiircs/ruby/config.rb b/alternative_wmiircs/ruby/config.rb
new file mode 100644
index 0000000..c86797a
--- /dev/null
+++ b/alternative_wmiircs/ruby/config.rb
@@ -0,0 +1,547 @@
+# DSL for wmiirc configuration.
+#--
+# Copyright protects this work.
+# See LICENSE file for details.
+#++
+
+require 'shellwords'
+require 'pathname'
+require 'yaml'
+
+require 'rubygems'
+gem 'rumai', '~> 3'
+require 'rumai'
+
+include Rumai
+
+class Handler < Hash
+ def initialize
+ super {|h,k| h[k] = [] }
+ end
+
+ ##
+ # If a block is given, registers a handler
+ # for the given key and returns the handler.
+ #
+ # Otherwise, executes all handlers registered for the given key.
+ #
+ def handle key, *args, &block
+ if block
+ self[key] << block
+
+ elsif key? key
+ self[key].each do |block|
+ block.call(*args)
+ end
+ end
+
+ block
+ end
+end
+
+EVENTS = Handler.new
+ACTIONS = Handler.new
+KEYS = Handler.new
+
+##
+# If a block is given, registers a handler
+# for the given event and returns the handler.
+#
+# Otherwise, executes all handlers for the given event.
+#
+def event *a, &b
+ EVENTS.handle(*a, &b)
+end
+
+##
+# Returns a list of registered event names.
+#
+def events
+ EVENTS.keys
+end
+
+##
+# If a block is given, registers a handler for
+# the given action and returns the handler.
+#
+# Otherwise, executes all handlers for the given action.
+#
+def action *a, &b
+ ACTIONS.handle(*a, &b)
+end
+
+##
+# Returns a list of registered action names.
+#
+def actions
+ ACTIONS.keys
+end
+
+##
+# If a block is given, registers a handler for
+# the given keypress and returns the handler.
+#
+# Otherwise, executes all handlers for the given keypress.
+#
+def key *a, &b
+ KEYS.handle(*a, &b)
+end
+
+##
+# Returns a list of registered action names.
+#
+def keys
+ KEYS.keys
+end
+
+##
+# Shows a menu (where the user must press keys on their keyboard to
+# make a choice) with the given items and returns the chosen item.
+#
+# If nothing was chosen, then nil is returned.
+#
+# ==== Parameters
+#
+# [prompt]
+# Instruction on what the user should enter or choose.
+#
+def key_menu choices, prompt = nil
+ words = ['dmenu', '-fn', CONFIG['display']['font']]
+
+ # show menu at the same location as the status bar
+ words << '-b' if CONFIG['display']['bar'] == 'bottom'
+
+ words.concat %w[-nf -nb -sf -sb].zip(
+ [
+ CONFIG['display']['color']['normal'],
+ CONFIG['display']['color']['focus'],
+
+ ].map {|c| c.to_s.split[0,2] }.flatten
+
+ ).flatten
+
+ words.push '-p', prompt if prompt
+
+ command = words.shelljoin
+ IO.popen(command, 'r+') do |menu|
+ menu.puts choices
+ menu.close_write
+
+ choice = menu.read
+ choice unless choice.empty?
+ end
+end
+
+##
+# Shows a menu (where the user must click a menu
+# item using their mouse to make a choice) with
+# the given items and returns the chosen item.
+#
+# If nothing was chosen, then nil is returned.
+#
+# ==== Parameters
+#
+# [choices]
+# List of choices to display in the menu.
+#
+# [initial]
+# The choice that should be initially selected.
+#
+# If this choice is not included in the list
+# of choices, then this item will be made
+# into a makeshift title-bar for the menu.
+#
+def click_menu choices, initial = nil
+ words = ['wmii9menu']
+
+ if initial
+ words << '-i'
+
+ unless choices.include? initial
+ initial = "<<#{initial}>>:"
+ words << initial
+ end
+
+ words << initial
+ end
+
+ words.concat choices
+ command = words.shelljoin
+
+ choice = `#{command}`.chomp
+ choice unless choice.empty?
+end
+
+##
+# Shows a key_menu() containing the given
+# clients and returns the chosen client.
+#
+# If nothing was chosen, then nil is returned.
+#
+# ==== Parameters
+#
+# [prompt]
+# Instruction on what the user should enter or choose.
+#
+# [clients]
+# List of clients to present as choices to the user.
+#
+# If this parameter is not specified,
+# its default value will be a list of
+# all currently available clients.
+#
+def client_menu prompt = nil, clients = Rumai.clients
+ choices = []
+
+ clients.each_with_index do |c, i|
+ choices << "%d. [%s] %s" % [i, c[:tags].read, c[:label].read.downcase]
+ end
+
+ if target = key_menu(choices, prompt)
+ clients[target.scan(/\d+/).first.to_i]
+ end
+end
+
+##
+# Returns the basenames of executable files present in the given directories.
+#
+def find_programs *dirs
+ dirs.flatten.
+ map {|d| Pathname.new(d).expand_path.children rescue [] }.flatten.
+ map {|f| f.basename.to_s if f.file? and f.executable? }.compact.uniq.sort
+end
+
+##
+# Launches the command built from the given words in the background.
+#
+def launch *words
+ command = words.shelljoin
+ system "#{command} &"
+end
+
+##
+# A button on a bar.
+#
+class Button < Thread
+ ##
+ # Creates a new button at the given node and updates its label
+ # according to the given refresh rate (measured in seconds). The
+ # given block is invoked to calculate the label of the button.
+ #
+ # The return value of the given block can be either an
+ # array (whose first item is a wmii color sequence for the
+ # button, and the remaining items compose the label of the
+ # button) or a string containing the label of the button.
+ #
+ # If the given block raises a standard exception, then that will be
+ # rescued and displayed (using error colors) as the button's label.
+ #
+ def initialize fs_bar_node, refresh_rate, &button_label
+ raise ArgumentError, 'block must be given' unless block_given?
+
+ super(fs_bar_node) do |button|
+ while true
+ label =
+ begin
+ Array(button_label.call)
+ rescue Exception => e
+ LOG.error e
+ [CONFIG['display']['color']['error'], e]
+ end
+
+ # provide default color
+ unless label.first =~ /(?:#[[:xdigit:]]{6} ?){3}/
+ label.unshift CONFIG['display']['color']['normal']
+ end
+
+ button.create unless button.exist?
+ button.write label.join(' ')
+ sleep refresh_rate
+ end
+ end
+ end
+
+ ##
+ # Refreshes the label of this button.
+ #
+ alias refresh wakeup
+end
+
+##
+# Loads the given YAML configuration file.
+#
+def load_config config_file
+ Object.const_set :CONFIG, YAML.load_file(config_file)
+
+ # script
+ eval CONFIG['script']['before'].to_s, TOPLEVEL_BINDING,
+ "#{config_file}:script:before"
+
+ # display
+ fo = ENV['WMII_FONT'] = CONFIG['display']['font']
+ fc = ENV['WMII_FOCUSCOLORS'] = CONFIG['display']['color']['focus']
+ nc = ENV['WMII_NORMCOLORS'] = CONFIG['display']['color']['normal']
+
+ settings = {
+ 'font' => fo,
+ 'focuscolors' => fc,
+ 'normcolors' => nc,
+ 'border' => CONFIG['display']['border'],
+ 'bar on' => CONFIG['display']['bar'],
+ 'colmode' => CONFIG['display']['column']['mode'],
+ 'grabmod' => CONFIG['control']['grab'],
+ }
+
+ begin
+ fs.ctl.write settings.map {|pair| pair.join(' ') }.join("\n")
+
+ rescue Rumai::IXP::Error => e
+ #
+ # settings that are not supported in a particular wmii version
+ # are ignored, and those that are supported are (silently)
+ # applied. but a "bad command" error is raised nevertheless!
+ #
+ warn e.inspect
+ warn e.backtrace.join("\n")
+ end
+
+ launch 'xsetroot', '-solid', CONFIG['display']['background']
+
+ # column
+ fs.colrules.write CONFIG['display']['column']['rule']
+
+ # client
+ event 'CreateClient' do |client_id|
+ client = Client.new(client_id)
+
+ unless defined? @client_tags_by_regexp
+ @client_tags_by_regexp = CONFIG['display']['client'].map {|hash|
+ k, v = hash.to_a.first
+ [eval(k, TOPLEVEL_BINDING, "#{config_file}:display:client"), v]
+ }
+ end
+
+ if label = client.props.read rescue nil
+ catch :found do
+ @client_tags_by_regexp.each do |regexp, tags|
+ if label =~ regexp
+ client.tags = tags
+ throw :found
+ end
+ end
+
+ # force client onto current view
+ begin
+ client.tags = curr_tag
+ client.focus
+ rescue
+ # ignore
+ end
+ end
+ end
+ end
+
+ # status
+ action 'status' do
+ fs.rbar.clear
+
+ unless defined? @status_button_by_name
+ @status_button_by_name = {}
+ @status_button_by_file = {}
+ @on_click_by_status_button = {}
+
+ CONFIG['display']['status'].each_with_index do |hash, position|
+ name, defn = hash.to_a.first
+
+ # buttons appear in ASCII order of their IXP file name
+ file = "#{position}-#{name}"
+
+ button = eval(
+ "Button.new(fs.rbar[#{file.inspect}], #{defn['refresh']}) { #{defn['content']} }",
+ TOPLEVEL_BINDING, "#{config_file}:display:status:#{name}"
+ )
+
+ @status_button_by_name[name] = button
+ @status_button_by_file[file] = button
+
+ # mouse click handler
+ if code = defn['click']
+ @on_click_by_status_button[button] = eval(
+ "lambda {|mouse_button| #{code} }", TOPLEVEL_BINDING,
+ "#{config_file}:display:status:#{name}:click"
+ )
+ end
+ end
+ end
+
+ @status_button_by_name.each_value {|b| b.refresh }
+
+ end
+
+ ##
+ # Returns the status button associated with the given name.
+ #
+ # ==== Parameters
+ #
+ # [name]
+ # Either the the user-defined name of
+ # the status button or the basename
+ # of the status button's IXP file.
+ #
+ def status_button name
+ @status_button_by_name[name] || @status_button_by_file[name]
+ end
+
+ ##
+ # Refreshes the content of the status button with the given name.
+ #
+ # ==== Parameters
+ #
+ # [name]
+ # Either the the user-defined name of
+ # the status button or the basename
+ # of the status button's IXP file.
+ #
+ def status name
+ if button = status_button(name)
+ button.refresh
+ end
+ end
+
+ ##
+ # Invokes the mouse click handler for the given mouse
+ # button on the status button that has the given name.
+ #
+ # ==== Parameters
+ #
+ # [name]
+ # Either the the user-defined name of
+ # the status button or the basename
+ # of the status button's IXP file.
+ #
+ # [mouse_button]
+ # The identification number of
+ # the mouse button (as defined
+ # by X server) that was clicked.
+ #
+ def status_click name, mouse_button
+ if button = status_button(name) and
+ handle = @on_click_by_status_button[button]
+ then
+ handle.call mouse_button.to_i
+ end
+ end
+
+ # control
+ action 'reload' do
+ # reload this wmii configuration
+ reload_config
+ end
+
+ action 'rehash' do
+ # scan for available programs and actions
+ @programs = find_programs(ENV['PATH'].squeeze(':').split(':'))
+ end
+
+ # kill all currently open clients
+ action 'clear' do
+ # firefox's restore session feature does not
+ # work unless the whole process is killed.
+ system 'killall firefox firefox-bin thunderbird thunderbird-bin'
+
+ # gnome-panel refuses to die by any other means
+ system 'killall -s TERM gnome-panel'
+
+ Thread.pass until clients.each do |c|
+ begin
+ c.focus # XXX: client must be on current view in order to be killed
+ c.kill
+ rescue
+ # ignore
+ end
+ end.empty?
+ end
+
+ # kill the window manager only; do not touch the clients!
+ action 'kill' do
+ fs.ctl.write 'quit'
+ end
+
+ # kill both clients and window manager
+ action 'quit' do
+ action 'clear'
+ action 'kill'
+ end
+
+ event 'Unresponsive' do |client_id|
+ client = Client.new(client_id)
+
+ IO.popen('xmessage -nearmouse -file - -buttons Kill,Wait -print', 'w+') do |f|
+ f.puts 'The following client is not responding.', ''
+ f.puts client.inspect
+ f.puts client.label.read
+
+ f.puts '', 'What would you like to do?'
+ f.close_write
+
+ if f.read.chomp == 'Kill'
+ client.slay
+ end
+ end
+ end
+
+ event 'Notice' do |*argv|
+ unless defined? @notice_mutex
+ require 'thread'
+ @notice_mutex = Mutex.new
+ end
+
+ Thread.new do
+ # prevent notices from overwriting each other
+ @notice_mutex.synchronize do
+ button = fs.rbar['!notice']
+ button.create unless button.exist?
+
+ # display the notice
+ message = argv.join(' ')
+
+ LOG.info message # also log it in case the user is AFK
+ button.write "#{CONFIG['display']['color']['notice']} #{message}"
+
+ # clear the notice
+ sleep [1, CONFIG['display']['notice'].to_i].max
+ button.remove
+ end
+ end
+ end
+
+ %w[key action event].each do |param|
+ if settings = CONFIG['control'][param]
+ settings.each do |name, code|
+ if param == 'key'
+ # expand ${...} expressions in shortcut key sequences
+ name = name.gsub(/\$\{(.+?)\}/) { CONFIG['control'][$1] }
+ end
+
+ eval "#{param}(#{name.inspect}) {|*argv| #{code} }",
+ TOPLEVEL_BINDING, "#{config_file}:control:#{param}:#{name}"
+ end
+ end
+ end
+
+ # script
+ action 'status'
+ action 'rehash'
+
+ eval CONFIG['script']['after'].to_s, TOPLEVEL_BINDING,
+ "#{config_file}:script:after"
+
+end
+
+##
+# Reloads the entire wmii configuration.
+#
+def reload_config
+ LOG.info 'reload'
+ exec $0
+end
diff --git a/alternative_wmiircs/ruby/config.yaml b/alternative_wmiircs/ruby/config.yaml
new file mode 100644
index 0000000..60065b3
--- /dev/null
+++ b/alternative_wmiircs/ruby/config.yaml
@@ -0,0 +1,536 @@
+#
+# High-level wmii configuration.
+#
+# Ruby code in this file has access
+# to a CONFIG constant which contains
+# the data in this configuration file.
+#
+#--
+# Copyright protects this work.
+# See LICENSE file for details.
+#++
+
+
+##
+# Program preferences.
+#
+program:
+ terminal: @TERMINAL@
+ browser: firefox
+ editor: mousepad
+ filer: thunar
+
+
+##
+# Appearance settings.
+#
+display:
+
+ ##
+ # Where to display the horizontal status bar?
+ #
+ # Possible choices are "top" and "bottom".
+ #
+ bar: bottom
+
+ ##
+ # The font to use in all text drawn by wmii.
+ #
+ font: -*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*
+
+ ##
+ # Thickness of client border (measured in pixels).
+ #
+ border: 1
+
+ ##
+ # Number of seconds a notice should be displayed.
+ #
+ notice: 5
+
+ ##
+ # Color schemes for everything drawn by wmii.
+ #
+ # <scheme>: "<text> <background> <border>"
+ #
+ # You can find more color schemes here:
+ #
+ # http://wmii.suckless.org/scripts_n_snips/themes
+ #
+ color:
+ normal: "#000000 #c1c48b #81654f"
+ focus: "#000000 #81654f #000000"
+ error: "#000000 #81654f #000000"
+ notice: "#000000 #a1956d #413328"
+ success: "#000000 #c1c48b #81654f"
+
+ ##
+ # Color of desktop background.
+ #
+ background: "#333333"
+
+ ##
+ # Settings for columns drawn by wmii.
+ #
+ # mode: <the wmii "colmode" setting>
+ # rule: <the wmii "colrules" setting>
+ #
+ column:
+ mode: default
+ rule: |
+ /gimp/ -> 17+83+41
+ /.*/ -> 62+38 # Golden Ratio
+
+ ##
+ # Mapping of clients to views they must appear on.
+ #
+ # - <client props regular expression> : <tags to apply>
+ #
+ # These mappings are processed in top-to-bottom order.
+ # Processing stops after the first matching mapping is applied.
+ #
+ client:
+ - /MPlayer|VLC/ : ~
+
+ ##
+ # Self-refreshing buttons on the status bar.
+ #
+ # - <button name>:
+ # refresh: <number of seconds to wait before refreshing the content>
+ # content: <Ruby code whose result is displayed as the content>
+ # click: <Ruby code to handle mouse clicks on the status button.
+ # This code has access to a "mouse_button" variable which is
+ # an integer representing the mouse button that was clicked.>
+ #
+ # You can refresh a particular status button in Ruby using:
+ #
+ # status "your button name"
+ #
+ # The horizontal order in which these buttons appear on the status
+ # bar reflects the vertical order in which they are defined below.
+ #
+ status:
+ - system_load:
+ refresh: 10
+ content: |
+ load_averages = File.read('/proc/loadavg').split.first(3)
+ current_load = load_averages.first.to_f
+
+ # visually indicate the intensity of system load
+ color = case
+ when current_load > 3.0 then CONFIG['display']['color']['error']
+ when current_load > 1.5 then CONFIG['display']['color']['notice']
+ end
+
+ [color, *load_averages]
+
+ - clock:
+ refresh: 5
+ content: Time.now.to_s
+
+
+##
+# Interaction settings.
+#
+control:
+
+ ##
+ # The wmii "grabmod" setting.
+ #
+ grab: Mod4
+
+ ##
+ # Key sequence prefixes.
+ #
+ mod: Mod4
+ move: Mod4-Shift
+ swap: Mod4-w
+ view: Mod4-v
+ group: Mod4-g
+
+ ##
+ # Direction keys.
+ #
+ up: k
+ down: j
+ left: h
+ right: l
+
+ ##
+ # Sequence keys.
+ #
+ prev: b
+ next: n
+
+ ##
+ # Key bindings.
+ #
+ # <key sequence>: <Ruby code to execute>
+ #
+ # A key sequence may contain ${...} expressions which
+ # are replaced with the value corresponding to '...'
+ # in the 'control' section of this configuration file.
+ #
+ # For example, if the 'control' section of
+ # this configuration file appeared like this:
+ #
+ # control:
+ # foo: Mod4
+ # bar: y
+ #
+ # and the following key sequence was used:
+ #
+ # ${foo}-${bar},${bar}
+ #
+ # then after ${...} expression replacement,
+ # that key sequence would appear like this:
+ #
+ # Mod4-y,y
+ #
+ key:
+ #---------------------------------------------------------------------------
+ # focus
+ #---------------------------------------------------------------------------
+
+ ${mod}-${up}: | # focus above client
+ curr_view.select(:up) rescue nil
+
+ ${mod}-${down}: | # focus below client
+ curr_view.select(:down) rescue nil
+
+ ${mod}-${left}: | # focus left client
+ curr_view.select(:left) rescue nil
+
+ ${mod}-${right}: | # focus right client
+ curr_view.select(:right) rescue nil
+
+ ${mod}-space: | # focus floating area (toggle)
+ curr_view.select(:toggle)
+
+ ${mod}-${prev}: | # focus previous view
+ prev_view.focus
+
+ ${mod}-${next}: | # focus next view
+ next_view.focus
+
+ # focus the view whose index or name equals the pressed number
+ ${mod}-1: focus_view tags[0] || 1
+ ${mod}-2: focus_view tags[1] || 2
+ ${mod}-3: focus_view tags[2] || 3
+ ${mod}-4: focus_view tags[3] || 4
+ ${mod}-5: focus_view tags[4] || 5
+ ${mod}-6: focus_view tags[5] || 6
+ ${mod}-7: focus_view tags[6] || 7
+ ${mod}-8: focus_view tags[7] || 8
+ ${mod}-9: focus_view tags[8] || 9
+ ${mod}-0: focus_view tags[9] || 10
+
+ # focus the view whose name begins with the pressed alphabet
+ ${view},a: t = tags.grep(/^a/i).first and focus_view(t)
+ ${view},b: t = tags.grep(/^b/i).first and focus_view(t)
+ ${view},c: t = tags.grep(/^c/i).first and focus_view(t)
+ ${view},d: t = tags.grep(/^d/i).first and focus_view(t)
+ ${view},e: t = tags.grep(/^e/i).first and focus_view(t)
+ ${view},f: t = tags.grep(/^f/i).first and focus_view(t)
+ ${view},g: t = tags.grep(/^g/i).first and focus_view(t)
+ ${view},h: t = tags.grep(/^h/i).first and focus_view(t)
+ ${view},i: t = tags.grep(/^i/i).first and focus_view(t)
+ ${view},j: t = tags.grep(/^j/i).first and focus_view(t)
+ ${view},k: t = tags.grep(/^k/i).first and focus_view(t)
+ ${view},l: t = tags.grep(/^l/i).first and focus_view(t)
+ ${view},m: t = tags.grep(/^m/i).first and focus_view(t)
+ ${view},n: t = tags.grep(/^n/i).first and focus_view(t)
+ ${view},o: t = tags.grep(/^o/i).first and focus_view(t)
+ ${view},p: t = tags.grep(/^p/i).first and focus_view(t)
+ ${view},q: t = tags.grep(/^q/i).first and focus_view(t)
+ ${view},r: t = tags.grep(/^r/i).first and focus_view(t)
+ ${view},s: t = tags.grep(/^s/i).first and focus_view(t)
+ ${view},t: t = tags.grep(/^t/i).first and focus_view(t)
+ ${view},u: t = tags.grep(/^u/i).first and focus_view(t)
+ ${view},v: t = tags.grep(/^v/i).first and focus_view(t)
+ ${view},w: t = tags.grep(/^w/i).first and focus_view(t)
+ ${view},x: t = tags.grep(/^x/i).first and focus_view(t)
+ ${view},y: t = tags.grep(/^y/i).first and focus_view(t)
+ ${view},z: t = tags.grep(/^z/i).first and focus_view(t)
+
+ #---------------------------------------------------------------------------
+ # move
+ #---------------------------------------------------------------------------
+
+ ${move}-${up}: | # move grouping toward the top
+ grouping.each {|c| c.send(:up) rescue nil }
+
+ ${move}-${down}: | # move grouping toward the bottom
+ grouping.each {|c| c.send(:down) rescue nil }
+
+ ${move}-${left}: | # move grouping toward the left
+ grouping.each {|c| c.send(:left) rescue nil }
+
+ ${move}-${right}: | # move grouping toward the right
+ grouping.each {|c| c.send(:right) rescue nil }
+
+ ${move}-space: | # move grouping to floating area (toggle)
+ grouping.each {|c| c.send(:toggle) rescue nil }
+
+ ${move}-t: | # move grouping to chosen view
+ #
+ # Changes the tag (according to a menu choice) of
+ # each grouped client and returns the chosen tag.
+ #
+ # The +tag -tag idea is from Jonas Pfenniger:
+ #
+ # http://zimbatm.oree.ch/articles/2006/06/15/wmii-3-and-ruby
+ #
+ choices = tags.map {|t| [t, "+#{t}", "-#{t}"] }.flatten
+
+ if target = key_menu(choices, 'tag as:')
+ grouping.each {|c| c.tags = target }
+ end
+
+ # move grouping to the view whose index or name equals the pressed number
+ ${move}-1: grouping.each {|c| c.tags = tags[0] || 1 }
+ ${move}-2: grouping.each {|c| c.tags = tags[1] || 2 }
+ ${move}-3: grouping.each {|c| c.tags = tags[2] || 3 }
+ ${move}-4: grouping.each {|c| c.tags = tags[3] || 4 }
+ ${move}-5: grouping.each {|c| c.tags = tags[4] || 5 }
+ ${move}-6: grouping.each {|c| c.tags = tags[5] || 6 }
+ ${move}-7: grouping.each {|c| c.tags = tags[6] || 7 }
+ ${move}-8: grouping.each {|c| c.tags = tags[7] || 8 }
+ ${move}-9: grouping.each {|c| c.tags = tags[8] || 9 }
+ ${move}-0: grouping.each {|c| c.tags = tags[9] || 10 }
+
+ #---------------------------------------------------------------------------
+ # group
+ #---------------------------------------------------------------------------
+
+ ${group},g: | # toggle current client from grouping
+ curr_client.group!
+
+ ${group},c: | # add clients in current area to grouping
+ curr_area.group
+
+ ${group},Shift-c: | # remove clients in current area from grouping
+ curr_area.ungroup
+
+ ${group},f: | # add clients in floating area to grouping
+ Area.floating.group
+
+ ${group},Shift-f: | # remove clients in floating area from grouping
+ Area.floating.ungroup
+
+ ${group},m: | # add clients in managed areas to grouping
+ curr_view.managed_areas.each {|a| a.group }
+
+ ${group},Shift-m: | # remove clients in managed areas from grouping
+ curr_view.managed_areas.each {|a| a.ungroup }
+
+ ${group},v: | # add clients in current view to grouping
+ curr_view.group
+
+ ${group},Shift-v: | # remove clients in current view from grouping
+ curr_view.ungroup
+
+ ${group},i: | # invert the grouping in the current view
+ curr_view.group!
+
+ ${group},Shift-i: | # invert the grouping in all views
+ Rumai.group!
+
+ ${group},n: | # remove all clients everywhere from grouping
+ Rumai.ungroup
+
+ #---------------------------------------------------------------------------
+ # swap
+ #---------------------------------------------------------------------------
+
+ ${swap},${up}: | # swap with above client
+ curr_client.swap(:up) rescue nil
+
+ ${swap},${down}: | # swap with below client
+ curr_client.swap(:down) rescue nil
+
+ ${swap},${left}: | # swap with left client
+ curr_client.swap(:left) rescue nil
+
+ ${swap},${right}: | # swap with right client
+ curr_client.swap(:right) rescue nil
+
+ # swap current client with the column whose index equals the pressed number
+ ${swap},1: curr_client.swap 1
+ ${swap},2: curr_client.swap 2
+ ${swap},3: curr_client.swap 3
+ ${swap},4: curr_client.swap 4
+ ${swap},5: curr_client.swap 5
+ ${swap},6: curr_client.swap 6
+ ${swap},7: curr_client.swap 7
+ ${swap},8: curr_client.swap 8
+ ${swap},9: curr_client.swap 9
+ ${swap},0: curr_client.swap 10
+
+ #---------------------------------------------------------------------------
+ # client
+ #---------------------------------------------------------------------------
+
+ ${mod}-f: | # zoom client to fullscreen (toggle)
+ curr_client.fullscreen!
+
+ ${mod}-Shift-c: | # kill the current client
+ curr_client.kill
+
+ #---------------------------------------------------------------------------
+ # column
+ #---------------------------------------------------------------------------
+
+ ${mod}-d: | # apply equal-spacing layout to current column
+ curr_area.layout = 'default-max'
+
+ ${mod}-s: | # apply stacked layout to current column
+ curr_area.layout = 'stack-max'
+
+ ${mod}-m: | # apply maximized layout to current column
+ curr_area.layout = 'stack+max'
+
+ #---------------------------------------------------------------------------
+ # menu
+ #---------------------------------------------------------------------------
+
+ ${mod}-a: | # run internal action chosen from a menu
+ if choice = key_menu(actions, 'run action:')
+ action choice
+ end
+
+ ${mod}-p: | # run external program chosen from a menu
+ if choice = key_menu(@programs, 'run program:')
+ launch choice
+ end
+
+ ${mod}-t: | # focus view chosen from a menu
+ if choice = key_menu(tags, 'show view:')
+ focus_view choice
+ end
+
+ #---------------------------------------------------------------------------
+ # launcher
+ #---------------------------------------------------------------------------
+
+ ${mod}-Return: | # launch a terminal
+ #
+ # Launch a new terminal and set its
+ # working directory to be the same
+ # as the currently focused terminal.
+ #
+ work = ENV['HOME']
+
+ label = curr_client.label.read rescue ''
+
+ # iterate in reverse order because
+ # paths are usually at end of label
+ label.split(' ').reverse_each do |s|
+ path = File.expand_path(s)
+
+ if File.exist? path
+ unless File.directory? path
+ path = File.dirname(path)
+ end
+
+ work = path
+ break
+ end
+ end
+
+ require 'fileutils'
+ FileUtils.cd work do
+ launch CONFIG['program']['terminal']
+ end
+
+ ##
+ # Event handlers.
+ #
+ # <event name>: <Ruby code to execute>
+ #
+ # The Ruby code has access to an "argv" variable which
+ # is a list of arguments that were passed to the event.
+ #
+ event:
+ CreateTag: |
+ tag = argv[0]
+ but = fs.lbar[tag]
+ but.create unless but.exist?
+ but.write "#{CONFIG['display']['color']['normal']} #{tag}"
+
+ DestroyTag: |
+ tag = argv[0]
+ but = fs.lbar[tag]
+ but.remove if but.exist?
+
+ FocusTag: |
+ tag = argv[0]
+ but = fs.lbar[tag]
+ but.write "#{CONFIG['display']['color']['focus']} #{tag}" if but.exist?
+
+ UnfocusTag: |
+ tag = argv[0]
+ but = fs.lbar[tag]
+ but.write "#{CONFIG['display']['color']['normal']} #{tag}" if but.exist?
+
+ UrgentTag: |
+ tag = argv[1]
+ but = fs.lbar[tag]
+ but.write "#{CONFIG['display']['color']['notice']} #{tag}" if but.exist?
+
+ NotUrgentTag: |
+ tag = argv[1]
+ but = fs.lbar[tag]
+ color = curr_view.id == tag ? 'focus' : 'normal'
+ but.write "#{CONFIG['display']['color'][color]} #{tag}" if but.exist?
+
+ LeftBarClick: &LeftBarClick |
+ mouse_button, view_id = argv
+
+ if mouse_button == '1' # primary button
+ focus_view view_id
+ end
+
+ ##
+ # allows the user to drag a file over a
+ # view button and activate that view while
+ # still holding on to their dragged file!
+ #
+ LeftBarDND: *LeftBarClick
+
+ RightBarClick: |
+ status_click *argv.reverse
+
+ ClientMouseDown: |
+ client_id, mouse_button = argv
+
+ if mouse_button == '3' # secondary button
+ client = Client.new(client_id)
+
+ case click_menu %w[stick group fullscreen kill slay], 'client'
+ when 'stick' then client.stick!
+ when 'group' then client.group!
+ when 'fullscreen' then client.fullscreen!
+ when 'kill' then client.kill
+ when 'slay' then client.slay
+ end
+ end
+
+ ##
+ # Internal scripts.
+ #
+ # <action name>: <Ruby code to execute>
+ #
+ action:
+
+
+##
+# Arbitrary logic.
+#
+# script:
+# before: <Ruby code to execute before processing this file>
+# after: <Ruby code to execute after processing this file>
+#
+script:
+ before:
+ after:
diff --git a/alternative_wmiircs/ruby/wmiirc b/alternative_wmiircs/ruby/wmiirc
new file mode 100755
index 0000000..2545137
--- /dev/null
+++ b/alternative_wmiircs/ruby/wmiirc
@@ -0,0 +1,88 @@
+#!/usr/bin/env ruby
+#
+# Bootloader for wmii configuration.
+#
+#--
+# Copyright protects this work.
+# See LICENSE file for details.
+#++
+
+# create a logger to aid debugging
+require 'logger'
+LOG = Logger.new(__FILE__ + '.log', 5)
+
+class << LOG
+ # emulate IO.write
+ alias write <<
+
+ def flush
+ # ignore
+ end
+end
+
+# capture standard output in logger
+$stdout = $stderr = LOG
+
+begin
+ LOG.info 'birth'
+
+ # load configuration library
+ def find_config file
+ base_dirs = ENV['WMII_CONFPATH'].to_s.split(/:+/)
+ ruby_dirs = base_dirs.map {|dir| File.join(dir, 'ruby') }
+
+ Dir["{#{base_dirs.zip(ruby_dirs).join(',')}}/#{file}"].first
+ end
+
+ require find_config('config.rb')
+
+ # terminate any existing wmiirc
+ fs.event.write 'Start wmiirc'
+
+ event 'Start' do |arg|
+ exit if arg == 'wmiirc'
+ end
+
+ # apply user configuration
+ load_config find_config('config.yaml')
+
+ # setup tag bar (buttons that correspond to views)
+ fs.lbar.clear
+ tags.each {|t| event 'CreateTag', t }
+ event 'FocusTag', curr_tag
+
+ # register key bindings
+ fs.keys.write keys.join("\n")
+ event('Key') {|*a| key(*a) }
+
+ # the main event loop
+ fs.event.each_line do |line|
+ line.split("\n").each do |call|
+ name, args = call.split(' ', 2)
+
+ argv = args.to_s.split(' ')
+ event name, *argv
+ end
+ end
+
+rescue SystemExit
+ # ignore it; the program wants to terminate
+
+rescue Exception => e
+ LOG.error e
+
+ # allow the user to rescue themselves
+ system '@TERMINAL@ &'
+
+ IO.popen('xmessage -nearmouse -file - -buttons Recover,Ignore -print', 'w+') do |f|
+ f.puts e.inspect, e.backtrace
+ f.close_write
+
+ if f.read.chomp == 'Recover'
+ reload_config
+ end
+ end
+
+ensure
+ LOG.info 'death'
+end
diff --git a/cmd/Makefile b/cmd/Makefile
new file mode 100644
index 0000000..ea8690b
--- /dev/null
+++ b/cmd/Makefile
@@ -0,0 +1,33 @@
+ROOT=..
+include $(ROOT)/mk/hdr.mk
+include $(ROOT)/mk/wmii.mk
+
+wmiir.c: $(ROOT)/mk/wmii.mk
+
+DIRS = wmii \
+ menu
+TARG = wihack \
+ wmii.rc \
+ wmii.sh \
+ wmii9menu \
+ wmiir
+
+OFILES = util.o
+
+LIBS += $(LIBS9)
+CFLAGS += $(INCX11)
+
+include $(ROOT)/mk/many.mk
+include $(ROOT)/mk/dir.mk
+
+OWMIIR=wmiir.o $(OFILES) $(LIBIXP)
+wmiir.out: $(OWMIIR)
+ $(LINK) $@ $(OWMIIR)
+
+wmii/x11.o wmii/xext.o wmii/geom.o wmii/map.o: dall
+ true
+
+O9MENU=wmii9menu.o clientutil.o wmii/x11.o wmii/xext.o wmii/geom.o wmii/map.o $(OFILES) $(LIBIXP)
+wmii9menu.out: $(O9MENU)
+ $(LINK) $@ $(O9MENU) $$(pkg-config --libs $(X11PACKAGES) xrandr xinerama) -lXext
+
diff --git a/cmd/click/Makefile b/cmd/click/Makefile
new file mode 100644
index 0000000..1abf6cb
--- /dev/null
+++ b/cmd/click/Makefile
@@ -0,0 +1,22 @@
+ROOT= ../..
+include ${ROOT}/mk/hdr.mk
+include ${ROOT}/mk/wmii.mk
+
+main.c: ${ROOT}/mk/wmii.mk
+
+TARG = click
+HFILES= dat.h fns.h
+
+PACKAGES += $(X11PACKAGES) xext xrandr xrender xinerama
+
+LIB = $(LIBIXP)
+LIBS += -lm -lXtst $(LIBS9)
+CFLAGS += -DVERSION=\"$(VERSION)\" -DIXP_NEEDAPI=86
+OBJ = main \
+ _util \
+ ../wmii/map \
+ ../wmii/x11 \
+ ../util
+
+include ${ROOT}/mk/one.mk
+
diff --git a/cmd/click/_util.c b/cmd/click/_util.c
new file mode 100644
index 0000000..364ff81
--- /dev/null
+++ b/cmd/click/_util.c
@@ -0,0 +1,77 @@
+/* Copyright ©2008-2010 Kris Maglione <fbsdaemon@gmail.com>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include <ctype.h>
+#include <string.h>
+#include "fns.h"
+
+#define strbcmp(str, const) (strncmp((str), (const), sizeof(const)-1))
+static int
+getbase(const char **s, long *sign) {
+ const char *p;
+ int ret;
+
+ ret = 10;
+ *sign = 1;
+ if(**s == '-') {
+ *sign = -1;
+ *s += 1;
+ }else if(**s == '+')
+ *s += 1;
+
+ p = *s;
+ if(!strbcmp(p, "0x")) {
+ *s += 2;
+ ret = 16;
+ }
+ else if(isdigit(p[0])) {
+ if(p[1] == 'r') {
+ *s += 2;
+ ret = p[0] - '0';
+ }
+ else if(isdigit(p[1]) && p[2] == 'r') {
+ *s += 3;
+ ret = 10*(p[0]-'0') + (p[1]-'0');
+ }
+ }
+ else if(p[0] == '0') {
+ ret = 8;
+ }
+ if(ret != 10 && (**s == '-' || **s == '+'))
+ *sign = 0;
+ return ret;
+}
+
+bool
+getlong(const char *s, long *ret) {
+ const char *end;
+ char *rend;
+ int base;
+ long sign;
+
+ end = s+strlen(s);
+ base = getbase(&s, &sign);
+ if(sign == 0)
+ return false;
+
+ *ret = sign * strtol(s, &rend, base);
+ return (end == rend);
+}
+
+bool
+getulong(const char *s, ulong *ret) {
+ const char *end;
+ char *rend;
+ int base;
+ long sign;
+
+ end = s+strlen(s);
+ base = getbase(&s, &sign);
+ if(sign < 1)
+ return false;
+
+ *ret = strtoul(s, &rend, base);
+ return (end == rend);
+}
+
diff --git a/cmd/click/dat.h b/cmd/click/dat.h
new file mode 100644
index 0000000..5096865
--- /dev/null
+++ b/cmd/click/dat.h
@@ -0,0 +1,27 @@
+#include <fmt.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <util.h>
+#include <ixp.h>
+#include <x11.h>
+
+#define BLOCK(x) do { x; }while(0)
+
+#ifndef EXTERN
+# define EXTERN extern
+#endif
+
+EXTERN Window win;
+
+EXTERN char buffer[8092];
+EXTERN char* _buffer;
+
+static char* const _buf_end = buffer + sizeof buffer;
+
+#define bufclear() \
+ BLOCK( _buffer = buffer; _buffer[0] = '\0' )
+#define bufprint(...) \
+ _buffer = seprint(_buffer, _buf_end, __VA_ARGS__)
+
diff --git a/cmd/click/fns.h b/cmd/click/fns.h
new file mode 100644
index 0000000..d41b840
--- /dev/null
+++ b/cmd/click/fns.h
@@ -0,0 +1,4 @@
+
+bool getlong(const char*, long*);
+bool getulong(const char*, ulong*);
+
diff --git a/cmd/click/main.c b/cmd/click/main.c
new file mode 100644
index 0000000..3ddc8ef
--- /dev/null
+++ b/cmd/click/main.c
@@ -0,0 +1,62 @@
+/* Copyright ©2006-2010 Kris Maglione <fbsdaemon@gmail.com>
+ * See LICENSE file for license details.
+ */
+#define EXTERN
+#include "dat.h"
+#include <X11/Xproto.h>
+#include <X11/extensions/XTest.h>
+#include <locale.h>
+#include <string.h>
+#include "fns.h"
+
+static const char version[] = "click-"VERSION", ©2010 Kris Maglione\n";
+
+static void
+usage(void) {
+ fatal("usage: %s [window]\n", argv0);
+}
+
+static void
+click(Window *w, Point p) {
+ Rectangle r;
+ Point rp;
+
+ r = getwinrect(w);
+ rp = subpt(r.max, p);
+
+ XTestFakeMotionEvent(display, 0, rp.x, rp.y, 0);
+
+ XTestFakeButtonEvent(display, 1, true, 0);
+ XTestFakeButtonEvent(display, 1, false, 0);
+
+ XTestFakeMotionEvent(display, 0, r.max.x, r.max.y, 0);
+}
+
+int
+main(int argc, char *argv[]) {
+ char *s;
+
+ ARGBEGIN{
+ default:
+ usage();
+ }ARGEND;
+
+ setlocale(LC_CTYPE, "");
+
+ initdisplay();
+
+ s = ARGF();
+ if(s && !getulong(s, &win.w))
+ usage();
+ if (!s)
+ win.w = getfocus();
+
+ if(argc)
+ usage();
+
+ click(&win, Pt(1, 1));
+
+ XCloseDisplay(display);
+ return 0;
+}
+
diff --git a/cmd/clientutil.c b/cmd/clientutil.c
new file mode 100644
index 0000000..411fe67
--- /dev/null
+++ b/cmd/clientutil.c
@@ -0,0 +1,50 @@
+#define IXP_NO_P9_
+#define IXP_P9_STRUCTS
+#define CLIENTEXTERN
+#include <string.h>
+#include <ixp.h>
+#include <clientutil.h>
+#include <util.h>
+
+static IxpCFid* ctlfid;
+static char ctl[1024];
+static char* ectl;
+
+char*
+readctl(char *key) {
+ char *s, *p;
+ int nkey, n;
+
+ if(ctlfid == nil) {
+ ctlfid = ixp_open(client, "ctl", OREAD);
+ n = ixp_read(ctlfid, ctl, 1023);
+ ectl = ctl + n;
+ ixp_close(ctlfid);
+ }
+
+ nkey = strlen(key);
+ p = ctl - 1;
+ do {
+ p++;
+ if(!strncmp(p, key, nkey)) {
+ p += nkey;
+ s = strchr(p, '\n');
+ n = (s ? s : ectl) - p;
+ s = freelater(emalloc(n + 1));
+ s[n] = '\0';
+ return strncpy(s, p, n);
+ }
+ } while((p = strchr(p, '\n')));
+ return "";
+}
+
+void
+client_init(char* address) {
+ if(address && *address)
+ client = ixp_mount(address);
+ else
+ client = ixp_nsmount("wmii");
+ if(client == nil)
+ fatal("can't mount: %r\n");
+}
+
diff --git a/cmd/menu/Makefile b/cmd/menu/Makefile
new file mode 100644
index 0000000..a2fc9ad
--- /dev/null
+++ b/cmd/menu/Makefile
@@ -0,0 +1,36 @@
+ROOT= ../..
+include $(ROOT)/mk/hdr.mk
+include $(ROOT)/mk/wmii.mk
+
+main.c: $(ROOT)/mk/wmii.mk
+
+bindings.c: keys.txt Makefile
+ ( echo "char binding_spec[] = "; \
+ sed 's/.*/ "&\\n"/' keys.txt; \
+ echo " ;" ) >bindings.c
+
+TARG = wimenu
+HFILES= dat.h fns.h
+
+PACKAGES += $(X11PACKAGES) xext xrandr xrender xinerama
+
+LIB = $(LIBIXP)
+LIBS += -lm $(LIBS9)
+CFLAGS += -DIXP_NEEDAPI=86
+OBJ = main \
+ caret \
+ history \
+ event \
+ menu \
+ keys \
+ bindings \
+ ../wmii/geom \
+ ../wmii/map \
+ ../wmii/printevent \
+ ../wmii/x11 \
+ ../wmii/xext \
+ ../clientutil \
+ ../util
+
+include $(ROOT)/mk/one.mk
+
diff --git a/cmd/menu/bindings.c b/cmd/menu/bindings.c
new file mode 100644
index 0000000..28aad75
--- /dev/null
+++ b/cmd/menu/bindings.c
@@ -0,0 +1,51 @@
+char binding_spec[] =
+ "Control-j Accept\n"
+ "Control-m Accept\n"
+ "Return Accept\n"
+ "Control-Shift-j Accept literal\n"
+ "Control-Shift-m Accept literal\n"
+ "Shift-Return Accept literal\n"
+ "\n"
+ "Escape Reject\n"
+ "Control-Bracketleft Reject\n"
+ "\n"
+ "Left Backward char\n"
+ "Control-b Backward char\n"
+ "Right Forward char\n"
+ "Control-f Forward char\n"
+ "\n"
+ "Mod1-b Backward word\n"
+ "Mod1-f Forward word\n"
+ "\n"
+ "Control-a Backward line\n"
+ "Control-e Forward line\n"
+ "\n"
+ "Control-p History backward\n"
+ "Up History backward\n"
+ "Control-n History forward\n"
+ "Down History forward\n"
+ "\n"
+ "Backspace Kill char\n"
+ "Control-h Kill char\n"
+ "Control-Backspace Kill word\n"
+ "Control-w Kill word\n"
+ "Control-u Kill line\n"
+ "\n"
+ "Tab Complete next\n"
+ "Control-i Complete next\n"
+ "Mod1-l Complete next\n"
+ "\n"
+ "Shift-Tab Complete prev\n"
+ "Control-Shift-i Complete prev\n"
+ "Mod1-h Complete prev\n"
+ "\n"
+ "Prior Complete prevpage\n"
+ "Mod1-k Complete prevpage\n"
+ "Next Complete nextpage\n"
+ "Mod1-j Complete nextpage\n"
+ "Home Complete first\n"
+ "Mod1-g Complete first\n"
+ "End Complete last\n"
+ "Mod1-Shift-g Complete last\n"
+ "\n"
+ ;
diff --git a/cmd/menu/caret.c b/cmd/menu/caret.c
new file mode 100644
index 0000000..c0380e3
--- /dev/null
+++ b/cmd/menu/caret.c
@@ -0,0 +1,164 @@
+#include "dat.h"
+#include <ctype.h>
+#include <string.h>
+#include "fns.h"
+
+static int
+iswordrune(Rune r) {
+ if(isalpharune(r))
+ return 1;
+ return r < 0x80 && (r == '_' || isdigit(r));
+}
+
+static char*
+prev_rune(char *start, char *p, Rune *r) {
+
+ *r = 0;
+ if(p == start)
+ return p;
+ while(p > start && (*(--p)&0xC0) == 0x80)
+ ;
+ chartorune(r, p);
+ return p;
+}
+
+static char*
+next_rune(char *p, Rune *r) {
+ int i;
+
+ *r = 0;
+ if(!*p)
+ return p;
+ i = chartorune(r, p);
+ return p + i;
+}
+
+void
+caret_set(int start, int end) {
+ int len;
+
+ len = input.end - input.string;
+ start = max(0, min(len, start));
+
+ input.pos = input.string + start;
+ if(end < 0)
+ input.pos_end = nil;
+ else
+ input.pos_end = input.string + max(start, end);
+}
+
+char*
+caret_find(int dir, int type) {
+ char *end;
+ char *next, *p;
+ Rune r;
+ int res;
+
+ p = input.pos;
+ if(dir == FORWARD) {
+ end = input.end;
+ switch(type) {
+ case LINE:
+ return end;
+ case WORD:
+ chartorune(&r, p);
+ res = iswordrune(r);
+ while(next=next_rune(p, &r), r && iswordrune(r) == res && !isspacerune(r))
+ p = next;
+ while(next=next_rune(p, &r), r && isspacerune(r))
+ p = next;
+ return p;
+ case CHAR:
+ if(p < end)
+ return p+1;
+ return p;
+ }
+ }
+ else if(dir == BACKWARD) {
+ end = input.string;
+ switch(type) {
+ case LINE:
+ return end;
+ case WORD:
+ while(next=prev_rune(end, p, &r), r && isspacerune(r))
+ p = next;
+ prev_rune(end, p, &r);
+ res = iswordrune(r);
+ while(next=prev_rune(end, p, &r), r && iswordrune(r) == res && !isspacerune(r))
+ p = next;
+ return p;
+ case CHAR:
+ if(p > end)
+ return p-1;
+ return end;
+ }
+ }
+ input.pos_end = nil;
+ return input.pos;
+}
+
+void
+caret_move(int dir, int type) {
+ input.pos = caret_find(dir, type);
+ input.pos_end = nil;
+}
+
+void
+caret_delete(int dir, int type) {
+ char *pos, *p;
+ int n;
+
+ if(input.pos_end)
+ p = input.pos_end;
+ else
+ p = caret_find(dir, type);
+ pos = input.pos;
+ if(p == input.end)
+ input.end = pos;
+ else {
+ if(p < pos) {
+ pos = p;
+ p = input.pos;
+ }
+ n = input.end - p;
+ memmove(pos, p, n);
+ input.pos = pos;
+ input.end = pos + n;
+ }
+ *input.end = '\0';
+ input.pos_end = nil;
+}
+
+void
+caret_insert(char *s, bool clear) {
+ int pos, end, len, size;
+
+ if(s == nil)
+ return;
+ if(clear) {
+ input.pos = input.string;
+ input.end = input.string;
+ }else if(input.pos_end)
+ caret_delete(0, 0);
+
+ len = strlen(s);
+ pos = input.pos - input.string;
+ end = input.end - input.string;
+
+ size = input.size;
+ if(input.size == 0)
+ input.size = 1;
+ while(input.size < end + len + 1)
+ input.size <<= 2;
+ if(input.size != size)
+ input.string = erealloc(input.string, input.size);
+
+ input.pos = input.string + pos;
+ input.end = input.string + end + len;
+ *input.end = '\0';
+ memmove(input.pos + len, input.pos, end - pos);
+ memmove(input.pos, s, len);
+ input.pos += len;
+ input.pos_end = nil;
+}
+
diff --git a/cmd/menu/dat.h b/cmd/menu/dat.h
new file mode 100644
index 0000000..1d805fa
--- /dev/null
+++ b/cmd/menu/dat.h
@@ -0,0 +1,116 @@
+#define _XOPEN_SOURCE 600
+#define IXP_P9_STRUCTS
+#define IXP_NO_P9_
+#include <fmt.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <util.h>
+#include <ixp.h>
+#include <x11.h>
+
+#define BLOCK(x) do { x; }while(0)
+
+#ifndef EXTERN
+# define EXTERN extern
+#endif
+
+enum {
+ FORWARD,
+ BACKWARD,
+ LINE,
+ WORD,
+ CHAR,
+ CARET_LAST,
+};
+
+enum {
+ LACCEPT,
+ LBACKWARD,
+ LCHAR,
+ LCOMPLETE,
+ LFIRST,
+ LFORWARD,
+ LHISTORY,
+ LKILL,
+ LLAST,
+ LLINE,
+ LLITERAL,
+ LNEXT,
+ LNEXTPAGE,
+ LPREV,
+ LPREVPAGE,
+ LREJECT,
+ LWORD,
+};
+
+typedef struct Item Item;
+
+struct Item {
+ char* string;
+ char* retstring;
+ Item* next_link;
+ Item* next;
+ Item* prev;
+ int len;
+ int width;
+};
+
+EXTERN struct {
+ char* string;
+ char* end;
+ char* pos;
+ char* pos_end;
+ int size;
+
+ char* filter;
+ int filter_start;
+} input;
+
+extern char binding_spec[];
+
+EXTERN int numlock;
+
+EXTERN long xtime;
+EXTERN Image* ibuf;
+EXTERN Font* font;
+EXTERN CTuple cnorm, csel;
+EXTERN bool ontop;
+
+EXTERN Cursor cursor[1];
+EXTERN Visual* render_visual;
+
+EXTERN IxpServer srv;
+
+EXTERN Window* barwin;
+
+EXTERN Item* items;
+EXTERN Item* matchfirst;
+EXTERN Item* matchstart;
+EXTERN Item* matchend;
+EXTERN Item* matchidx;
+
+EXTERN Item hist;
+EXTERN Item* histidx;
+
+EXTERN int maxwidth;
+EXTERN int result;
+
+EXTERN char* (*find)(const char*, const char*);
+EXTERN int (*compare)(const char*, const char*, size_t);
+
+EXTERN char* prompt;
+EXTERN int promptw;
+
+EXTERN char buffer[8092];
+EXTERN char* _buffer;
+
+static char* const _buf_end = buffer + sizeof buffer;
+
+#define bufclear() \
+ BLOCK( _buffer = buffer; _buffer[0] = '\0' )
+#define bufprint(...) \
+ _buffer = seprint(_buffer, _buf_end, __VA_ARGS__)
+
diff --git a/cmd/menu/event.c b/cmd/menu/event.c
new file mode 100644
index 0000000..fd524f9
--- /dev/null
+++ b/cmd/menu/event.c
@@ -0,0 +1,334 @@
+/* Copyright ©2006-2010 Kris Maglione <fbsdaemon@gmail.com>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include "fns.h"
+
+typedef void (*EvHandler)(XEvent*);
+static EvHandler handler[LASTEvent];
+
+void
+dispatch_event(XEvent *e) {
+ if(e->type < nelem(handler)) {
+ if(handler[e->type])
+ handler[e->type](e);
+ }else
+ xext_event(e);
+}
+
+#define handle(w, fn, ev) \
+ BLOCK(if((w)->handler->fn) (w)->handler->fn((w), ev))
+
+static int
+findtime(Display *d, XEvent *e, XPointer v) {
+ Window *w;
+
+ w = (Window*)v;
+ if(e->type == PropertyNotify && e->xproperty.window == w->xid) {
+ xtime = e->xproperty.time;
+ return true;
+ }
+ return false;
+}
+
+void
+xtime_kludge(void) {
+ /* Round trip. */
+ static Window *w;
+ WinAttr wa;
+ XEvent e;
+ long l;
+
+ if(w == nil) {
+ w = createwindow(&scr.root, Rect(0, 0, 1, 1), 0, InputOnly, &wa, 0);
+ selectinput(w, PropertyChangeMask);
+ }
+ changeprop_long(w, "ATOM", "ATOM", &l, 0);
+ sync();
+ XIfEvent(display, &e, findtime, (void*)w);
+}
+
+uint
+flushevents(long event_mask, bool dispatch) {
+ XEvent ev;
+ uint n = 0;
+
+ while(XCheckMaskEvent(display, event_mask, &ev)) {
+ if(dispatch)
+ dispatch_event(&ev);
+ n++;
+ }
+ return n;
+}
+
+static int
+findenter(Display *d, XEvent *e, XPointer v) {
+ long *l;
+
+ USED(d);
+ l = (long*)v;
+ if(*l)
+ return false;
+ if(e->type == EnterNotify)
+ return true;
+ if(e->type == MotionNotify)
+ (*l)++;
+ return false;
+}
+
+/* This isn't perfect. If there were motion events in the queue
+ * before this was called, then it flushes nothing. If we don't
+ * check for them, we might lose a legitamate enter event.
+ */
+uint
+flushenterevents(void) {
+ XEvent e;
+ long l;
+ int n;
+
+ l = 0;
+ n = 0;
+ while(XCheckIfEvent(display, &e, findenter, (void*)&l))
+ n++;
+ return n;
+}
+
+static void
+buttonrelease(XButtonPressedEvent *ev) {
+ Window *w;
+
+ if((w = findwin(ev->window)))
+ handle(w, bup, ev);
+}
+
+static void
+buttonpress(XButtonPressedEvent *ev) {
+ Window *w;
+
+ if((w = findwin(ev->window)))
+ handle(w, bdown, ev);
+ else
+ XAllowEvents(display, ReplayPointer, ev->time);
+}
+
+static void
+configurerequest(XConfigureRequestEvent *ev) {
+ XWindowChanges wc;
+ Window *w;
+
+ if((w = findwin(ev->window)))
+ handle(w, configreq, ev);
+ else{
+ wc.x = ev->x;
+ wc.y = ev->y;
+ wc.width = ev->width;
+ wc.height = ev->height;
+ wc.border_width = ev->border_width;
+ wc.sibling = ev->above;
+ wc.stack_mode = ev->detail;
+ XConfigureWindow(display, ev->window, ev->value_mask, &wc);
+ }
+}
+
+static void
+configurenotify(XConfigureEvent *ev) {
+ Window *w;
+
+ USED(ev);
+ if((w = findwin(ev->window)))
+ handle(w, config, ev);
+}
+
+static void
+clientmessage(XClientMessageEvent *ev) {
+
+ USED(ev);
+}
+
+static void
+destroynotify(XDestroyWindowEvent *ev) {
+ Window *w;
+
+ if((w = findwin(ev->window)))
+ handle(w, destroy, ev);
+}
+
+static void
+enternotify(XCrossingEvent *ev) {
+ Window *w;
+ static int sel_screen;
+
+ xtime = ev->time;
+ if(ev->mode != NotifyNormal)
+ return;
+
+ if((w = findwin(ev->window)))
+ handle(w, enter, ev);
+ else if(ev->window == scr.root.xid)
+ sel_screen = true;
+}
+
+static void
+leavenotify(XCrossingEvent *ev) {
+
+ xtime = ev->time;
+#if 0
+ if((ev->window == scr.root.xid) && !ev->same_screen)
+ sel_screen = true;
+#endif
+}
+
+static void
+focusin(XFocusChangeEvent *ev) {
+ Window *w;
+
+ /* Yes, we're focusing in on nothing, here. */
+ if(ev->detail == NotifyDetailNone) {
+ /* FIXME: Do something. */
+ return;
+ }
+
+ if(!((ev->detail == NotifyNonlinear)
+ ||(ev->detail == NotifyNonlinearVirtual)
+ ||(ev->detail == NotifyVirtual)
+ ||(ev->detail == NotifyInferior)
+ ||(ev->detail == NotifyAncestor)))
+ return;
+ if((ev->mode == NotifyWhileGrabbed)) /* && (screen->hasgrab != &c_root)) */
+ return;
+
+ if((w = findwin(ev->window)))
+ handle(w, focusin, ev);
+#if 0
+ else if(ev->mode == NotifyGrab) {
+ if(ev->window == scr.root.xid)
+ screen->hasgrab = &c_root;
+ /* Some unmanaged window has grabbed focus */
+ else if((c = screen->focus)) {
+ print_focus("focusin", &c_magic, "<magic>");
+ screen->focus = &c_magic;
+ if(c->sel)
+ frame_draw(c->sel);
+ }
+ }
+#endif
+}
+
+static void
+focusout(XFocusChangeEvent *ev) {
+ Window *w;
+
+ if(!((ev->detail == NotifyNonlinear)
+ ||(ev->detail == NotifyNonlinearVirtual)
+ ||(ev->detail == NotifyVirtual)
+ ||(ev->detail == NotifyInferior)
+ ||(ev->detail == NotifyAncestor)))
+ return;
+#if 0
+ if(ev->mode == NotifyUngrab)
+ screen->hasgrab = nil;
+#endif
+
+ if((w = findwin(ev->window)))
+ handle(w, focusout, ev);
+}
+
+static void
+expose(XExposeEvent *ev) {
+ Window *w;
+
+ if(ev->count == 0) {
+ if((w = findwin(ev->window)))
+ handle(w, expose, ev);
+ }
+}
+
+static void
+keypress(XKeyEvent *ev) {
+ Window *w;
+
+ xtime = ev->time;
+ if((w = findwin(ev->window)))
+ handle(w, kdown, ev);
+}
+
+static void
+mappingnotify(XMappingEvent *ev) {
+
+ /* Why do you need me to tell you this? */
+ XRefreshKeyboardMapping(ev);
+}
+
+static void
+maprequest(XMapRequestEvent *ev) {
+
+ USED(ev);
+}
+
+static void
+motionnotify(XMotionEvent *ev) {
+ Window *w;
+
+ xtime = ev->time;
+ if((w = findwin(ev->window)))
+ handle(w, motion, ev);
+}
+
+static void
+propertynotify(XPropertyEvent *ev) {
+ Window *w;
+
+ xtime = ev->time;
+ if((w = findwin(ev->window)))
+ handle(w, property, ev);
+}
+
+static void
+mapnotify(XMapEvent *ev) {
+ Window *w;
+
+ if((w = findwin(ev->window)))
+ handle(w, map, ev);
+}
+
+static void
+unmapnotify(XUnmapEvent *ev) {
+ Window *w;
+
+ if((w = findwin(ev->window)) && (ev->event == w->parent->xid)) {
+ w->mapped = false;
+ if(ev->send_event || w->unmapped-- == 0)
+ handle(w, unmap, ev);
+ }
+}
+
+static EvHandler handler[LASTEvent] = {
+ [ButtonPress] = (EvHandler)buttonpress,
+ [ButtonRelease] = (EvHandler)buttonrelease,
+ [ConfigureRequest] = (EvHandler)configurerequest,
+ [ConfigureNotify] = (EvHandler)configurenotify,
+ [ClientMessage] = (EvHandler)clientmessage,
+ [DestroyNotify] = (EvHandler)destroynotify,
+ [EnterNotify] = (EvHandler)enternotify,
+ [Expose] = (EvHandler)expose,
+ [FocusIn] = (EvHandler)focusin,
+ [FocusOut] = (EvHandler)focusout,
+ [KeyPress] = (EvHandler)keypress,
+ [LeaveNotify] = (EvHandler)leavenotify,
+ [MapNotify] = (EvHandler)mapnotify,
+ [MapRequest] = (EvHandler)maprequest,
+ [MappingNotify] = (EvHandler)mappingnotify,
+ [MotionNotify] = (EvHandler)motionnotify,
+ [PropertyNotify] = (EvHandler)propertynotify,
+ [UnmapNotify] = (EvHandler)unmapnotify,
+};
+
+void
+check_x_event(IxpConn *c) {
+ XEvent ev;
+
+ USED(c);
+ while(XCheckMaskEvent(display, ~0, &ev))
+ dispatch_event(&ev);
+}
+
diff --git a/cmd/menu/fns.h b/cmd/menu/fns.h
new file mode 100644
index 0000000..d95ab7f
--- /dev/null
+++ b/cmd/menu/fns.h
@@ -0,0 +1,51 @@
+
+void check_x_event(IxpConn*);
+void dispatch_event(XEvent*);
+uint flushenterevents(void);
+uint flushevents(long, bool);
+void xtime_kludge(void);
+
+/* caret.c */
+void caret_delete(int, int);
+char* caret_find(int, int);
+void caret_insert(char*, bool);
+void caret_move(int, int);
+void caret_set(int, int);
+
+/* history.c */
+void history_dump(const char*, int);
+char* history_search(int, char*, int);
+
+/* main.c */
+void debug(int, const char*, ...);
+Item* filter_list(Item*, char*);
+void init_screens(int);
+void update_filter(bool);
+void update_input(void);
+
+/* menu.c */
+void menu_draw(void);
+void menu_init(void);
+void menu_show(void);
+
+/* keys.c */
+void parse_keys(char*);
+char** find_key(char*, long);
+int getsym(char*);
+
+/* geom.c */
+Align get_sticky(Rectangle src, Rectangle dst);
+Cursor quad_cursor(Align);
+Align quadrant(Rectangle, Point);
+bool rect_contains_p(Rectangle, Rectangle);
+bool rect_haspoint_p(Point, Rectangle);
+bool rect_intersect_p(Rectangle, Rectangle);
+Rectangle rect_intersection(Rectangle, Rectangle);
+
+/* xext.c */
+void randr_event(XEvent*);
+bool render_argb_p(Visual*);
+void xext_event(XEvent*);
+void xext_init(void);
+Rectangle* xinerama_screens(int*);
+
diff --git a/cmd/menu/history.c b/cmd/menu/history.c
new file mode 100644
index 0000000..38b0598
--- /dev/null
+++ b/cmd/menu/history.c
@@ -0,0 +1,90 @@
+#include "dat.h"
+#include <assert.h>
+#include <bio.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include "fns.h"
+
+static void
+splice(Item *i) {
+ if(i->next != nil)
+ i->next->prev = i->prev;
+ if(i->prev != nil)
+ i->prev->next = i->next;
+}
+
+char*
+history_search(int dir, char *string, int n) {
+ Item *i;
+
+ if(dir == FORWARD) {
+ if(histidx == &hist)
+ return hist.string;
+ for(i=histidx->next; i != hist.next; i=i->next)
+ if(!i->string || !compare(i->string, string, n)) {
+ histidx = i;
+ return i->string;
+ }
+ return string;
+ }
+ assert(dir == BACKWARD);
+
+ if(histidx == &hist) {
+ free(hist.string);
+ hist.string = estrdup(input.string);
+ }
+
+ for(i=histidx->prev; i != &hist; i=i->prev)
+ if(!compare(i->string, string, n)) {
+ histidx = i;
+ return i->string;
+ }
+ return string;
+}
+
+void
+history_dump(const char *path, int max) {
+ static char *items[20];
+ static char *tmp;
+ Biobuf b;
+ Item *h, *first;
+ int i, n, fd;
+
+ SET(first);
+ if(fork() != 0)
+ return;
+
+ tmp = smprint("%s.XXXXXX", path);
+ fd = mkstemp(tmp);
+ if(fd < 0) {
+ fprint(2, "%s: Can't create temporary history file %q: %r\n", argv0, path);
+ return;
+ }
+
+ hist.string = input.string;
+ n = 0;
+ hist.next->prev = nil;
+ for(h=&hist; h; h=h->prev) {
+ for(i=0; i < nelem(items); i++)
+ if(items[i] && !strcmp(h->string, items[i])) {
+ splice(h);
+ goto next;
+ }
+ items[n++ % nelem(items)] = h->string;
+ first = h;
+ if(!max || n >= max)
+ break;
+ next:
+ continue;
+ }
+
+ Binit(&b, fd, OWRITE);
+ hist.next = nil;
+ for(h=first; h; h=h->next)
+ Bprint(&b, "%s\n", h->string);
+ Bterm(&b);
+ rename(tmp, path);
+ exit(0);
+}
+
diff --git a/cmd/menu/keys.c b/cmd/menu/keys.c
new file mode 100644
index 0000000..e99f061
--- /dev/null
+++ b/cmd/menu/keys.c
@@ -0,0 +1,142 @@
+#include "dat.h"
+#include <ctype.h>
+#include <strings.h>
+#include <unistd.h>
+#include "fns.h"
+
+typedef struct Key Key;
+
+struct Key {
+ Key* next;
+ long mask;
+ char* key;
+ char** action;
+};
+
+static Key* bindings;
+
+static void
+init_numlock(void) {
+ static int masks[] = {
+ ShiftMask, LockMask, ControlMask, Mod1Mask,
+ Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask
+ };
+ XModifierKeymap *modmap;
+ KeyCode kcode;
+ int i, max;
+
+ modmap = XGetModifierMapping(display);
+ kcode = keycode("Num_Lock");
+ if(kcode)
+ if(modmap && modmap->max_keypermod > 0) {
+ max = nelem(masks) * modmap->max_keypermod;
+ for(i = 0; i < max; i++)
+ if(modmap->modifiermap[i] == kcode)
+ numlock = masks[i / modmap->max_keypermod];
+ }
+ XFreeModifiermap(modmap);
+}
+
+/*
+ * To do: Find my red black tree implementation.
+ */
+void
+parse_keys(char *spec) {
+ static char *lines[1024];
+ static char *words[16];
+ Key *k;
+ char *p, *line;
+ int mask;
+ int i, nlines, nwords;
+
+ if(!numlock)
+ init_numlock();
+
+ nlines = tokenize(lines, nelem(lines), spec, '\n');
+ for(i=0; i < nlines; i++) {
+ line = lines[i];
+ p = strchr(line, '#');
+ if(p)
+ *p = '\0';
+
+ nwords = stokenize(words, nelem(words) - 1, line, " \t");
+ words[nwords] = nil;
+ if(!words[0])
+ continue;
+ if(parsekey(words[0], &mask, &p)) {
+ k = emallocz(sizeof *k);
+ k->key = p;
+ k->mask = mask;
+ k->action = strlistdup(words + 1);
+ k->next = bindings;
+ bindings = k;
+ }
+ }
+}
+
+char**
+find_key(char *key, long mask) {
+ Key *k;
+
+ /* Horrible hack. */
+ if(!strcmp(key, "ISO_Left_Tab"))
+ key = "Tab";
+
+ mask &= ~(numlock | LockMask) & ((1<<8) - 1);
+ for(k=bindings; k; k=k->next)
+ if(!strcasecmp(k->key, key) && k->mask == mask)
+ return k->action;
+ return nil;
+}
+
+ /* sed 's/"([^"]+)"/L\1/g' | tr 'a-z' 'A-Z' */
+ /* awk '{$1=""; print}' keys.txt | perl -e '$_=lc join "", <>; print join "\n", m/(\w+)/g;' | sort -u | sed 's:.*: "&",:' */
+char *symtab[] = {
+ "accept",
+ "backward",
+ "char",
+ "complete",
+ "first",
+ "forward",
+ "history",
+ "kill",
+ "last",
+ "line",
+ "literal",
+ "next",
+ "nextpage",
+ "prev",
+ "prevpage",
+ "reject",
+ "word",
+};
+
+static int
+_bsearch(char *s, char **tab, int ntab) {
+ int i, n, m, cmp;
+
+ if(s == nil)
+ return -1;
+
+ n = ntab;
+ i = 0;
+ while(n) {
+ m = n/2;
+ cmp = strcasecmp(s, tab[i+m]);
+ if(cmp == 0)
+ return i+m;
+ if(cmp < 0 || m == 0)
+ n = m;
+ else {
+ i += m;
+ n = n-m;
+ }
+ }
+ return -1;
+}
+
+int
+getsym(char *s) {
+ return _bsearch(s, symtab, nelem(symtab));
+}
+
diff --git a/cmd/menu/keys.txt b/cmd/menu/keys.txt
new file mode 100644
index 0000000..d16a6c6
--- /dev/null
+++ b/cmd/menu/keys.txt
@@ -0,0 +1,49 @@
+Control-j Accept
+Control-m Accept
+Return Accept
+Control-Shift-j Accept literal
+Control-Shift-m Accept literal
+Shift-Return Accept literal
+
+Escape Reject
+Control-Bracketleft Reject
+
+Left Backward char
+Control-b Backward char
+Right Forward char
+Control-f Forward char
+
+Mod1-b Backward word
+Mod1-f Forward word
+
+Control-a Backward line
+Control-e Forward line
+
+Control-p History backward
+Up History backward
+Control-n History forward
+Down History forward
+
+Backspace Kill char
+Control-h Kill char
+Control-Backspace Kill word
+Control-w Kill word
+Control-u Kill line
+
+Tab Complete next
+Control-i Complete next
+Mod1-l Complete next
+
+Shift-Tab Complete prev
+Control-Shift-i Complete prev
+Mod1-h Complete prev
+
+Prior Complete prevpage
+Mod1-k Complete prevpage
+Next Complete nextpage
+Mod1-j Complete nextpage
+Home Complete first
+Mod1-g Complete first
+End Complete last
+Mod1-Shift-g Complete last
+
diff --git a/cmd/menu/main.c b/cmd/menu/main.c
new file mode 100644
index 0000000..aaab27d
--- /dev/null
+++ b/cmd/menu/main.c
@@ -0,0 +1,346 @@
+/* Copyright ©2006-2010 Kris Maglione <fbsdaemon@gmail.com>
+ * See LICENSE file for license details.
+ */
+#define IXP_NO_P9_
+#define IXP_P9_STRUCTS
+#define EXTERN
+#include "dat.h"
+#include <X11/Xproto.h>
+#include <locale.h>
+#include <strings.h>
+#include <unistd.h>
+#include <bio.h>
+#include <clientutil.h>
+#include "fns.h"
+#define link _link
+
+static const char version[] = "wimenu-"VERSION", "COPYRIGHT"\n";
+static Biobuf* cmplbuf;
+static Biobuf* inbuf;
+static bool alwaysprint;
+static char* cmdsep;
+
+static void
+usage(void) {
+ fatal("usage: wimenu -i [-h <history>] [-a <address>] [-p <prompt>] [-s <screen>]\n");
+}
+
+static int
+errfmt(Fmt *f) {
+ return fmtstrcpy(f, ixp_errbuf());
+}
+
+/* Stubs. */
+void
+debug(int flag, const char *fmt, ...) {
+ va_list ap;
+
+ USED(flag);
+ va_start(ap, fmt);
+ vfprint(2, fmt, ap);
+ va_end(ap);
+}
+
+void dprint(long, char*, ...);
+void dprint(long mask, char *fmt, ...) {
+ va_list ap;
+
+ USED(mask);
+ va_start(ap, fmt);
+ vfprint(2, fmt, ap);
+ va_end(ap);
+}
+
+static inline void
+splice(Item *i) {
+ i->next->prev = i->prev;
+ i->prev->next = i->next;
+}
+static inline void
+link(Item *i, Item *j) {
+ i->next = j;
+ j->prev = i;
+}
+
+static Item*
+populate_list(Biobuf *buf, bool hist) {
+ Item ret;
+ Item *i;
+ char *p;
+ bool stop;
+
+ stop = !hist && !isatty(buf->fid);
+ i = &ret;
+ while((p = Brdstr(buf, '\n', true))) {
+ if(stop && p[0] == '\0')
+ break;
+ link(i, emallocz(sizeof *i));
+ i->next_link = i->next;
+ i = i->next;
+ i->string = p;
+ i->retstring = p;
+ if(cmdsep && (p = strstr(p, cmdsep))) {
+ *p = '\0';
+ i->retstring = p + strlen(cmdsep);
+ }
+ if(!hist) {
+ i->len = strlen(i->string);
+ i->width = textwidth_l(font, i->string, i->len);
+ if(i->width > maxwidth)
+ maxwidth = i->width;
+ }
+ }
+
+ link(i, &ret);
+ splice(&ret);
+ return ret.next != &ret ? ret.next : nil;
+}
+
+static void
+check_competions(IxpConn *c) {
+ char *s;
+
+ s = Brdstr(cmplbuf, '\n', true);
+ if(!s) {
+ ixp_hangup(c);
+ return;
+ }
+ input.filter_start = strtol(s, nil, 10);
+ items = populate_list(cmplbuf, false);
+ update_filter(false);
+ menu_draw();
+}
+
+Item*
+filter_list(Item *i, char *filter) {
+ static Item exact;
+ Item start, substr;
+ Item *exactp, *startp, *substrp;
+ Item **ip;
+ char *p;
+ int len;
+
+ len = strlen(filter);
+ exactp = &exact;
+ startp = &start;
+ substrp = &substr;
+ for(; i; i=i->next_link)
+ if((p = find(i->string, filter))) {
+ ip = &substrp;
+ if(p == i->string)
+ if(strlen(p) == len)
+ ip = &exactp;
+ else
+ ip = &startp;
+ link(*ip, i);
+ *ip = i;
+ }
+
+ link(substrp, &exact);
+ link(startp, &substr);
+ link(exactp, &start);
+ splice(&substr);
+ splice(&start);
+ splice(&exact);
+ return exact.next;
+}
+
+void
+update_input(void) {
+ if(alwaysprint) {
+ write(1, input.string, input.pos - input.string);
+ write(1, "\n", 1);
+ write(1, input.pos, input.end - input.pos);
+ write(1, "\n", 1);
+ }
+}
+
+void
+update_filter(bool print) {
+ char *filter;
+
+ filter = input.string + min(input.filter_start, input.pos - input.string);
+ if(input.pos < input.end)
+ filter = freelater(estrndup(filter, input.pos - filter));
+
+ matchidx = nil;
+ matchfirst = matchstart = filter_list(items, filter);
+ if(print)
+ update_input();
+}
+
+ErrorCode ignored_xerrors[] = {
+ { 0, }
+};
+
+static void
+end(IxpConn *c) {
+
+ USED(c);
+ srv.running = 0;
+}
+
+static void
+preselect(IxpServer *s) {
+
+ USED(s);
+ check_x_event(nil);
+}
+
+enum { PointerScreen = -1 };
+
+void
+init_screens(int screen_hint) {
+ Rectangle *rects;
+ Point p;
+ int i, n;
+
+ rects = xinerama_screens(&n);
+ if (screen_hint >= 0 && screen_hint < n)
+ /* We were given a valid screen index, use that. */
+ i = screen_hint;
+ else {
+ /* Pick the screen with the pointer, for now. Later,
+ * try for the screen with the focused window first.
+ */
+ p = querypointer(&scr.root);
+ for(i=0; i < n; i++)
+ if(rect_haspoint_p(p, rects[i]))
+ break;
+ if(i == n)
+ i = 0;
+ }
+ scr.rect = rects[i];
+ menu_show();
+}
+
+int
+main(int argc, char *argv[]) {
+ Item *item;
+ static char *address;
+ static char *histfile;
+ static char *keyfile;
+ static bool nokeys;
+ int i;
+ long ndump;
+ int screen;
+
+ quotefmtinstall();
+ fmtinstall('r', errfmt);
+ address = getenv("WMII_ADDRESS");
+ screen = PointerScreen;
+
+ find = strstr;
+ compare = strncmp;
+
+ ndump = -1;
+
+ ARGBEGIN{
+ case 'a':
+ address = EARGF(usage());
+ break;
+ case 'c':
+ alwaysprint = true;
+ break;
+ case 'h':
+ histfile = EARGF(usage());
+ break;
+ case 'i':
+ find = strcasestr;
+ compare = strncasecmp;
+ break;
+ case 'K':
+ nokeys = true;
+ case 'k':
+ keyfile = EARGF(usage());
+ break;
+ case 'n':
+ ndump = strtol(EARGF(usage()), nil, 10);
+ break;
+ case 'p':
+ prompt = EARGF(usage());
+ break;
+ case 's':
+ screen = strtol(EARGF(usage()), nil, 10);
+ break;
+ case 'S':
+ cmdsep = EARGF(usage());
+ break;
+ case 'v':
+ print("%s", version);
+ return 0;
+ default:
+ usage();
+ }ARGEND;
+
+ if(argc)
+ usage();
+
+ setlocale(LC_CTYPE, "");
+
+ initdisplay();
+
+ xext_init();
+ if(!isatty(0))
+ menu_init();
+
+ client_init(address);
+
+ srv.preselect = preselect;
+ ixp_listen(&srv, ConnectionNumber(display), nil, check_x_event, end);
+
+ ontop = !strcmp(readctl("bar on "), "top");
+ loadcolor(&cnorm, readctl("normcolors "));
+ loadcolor(&csel, readctl("focuscolors "));
+ font = loadfont(readctl("font "));
+ sscanf(readctl("fontpad "), "%d %d %d %d", &font->pad.min.x, &font->pad.max.x,
+ &font->pad.min.x, &font->pad.max.y);
+ if(!font)
+ fatal("Can't load font %q", readctl("font "));
+
+ cmplbuf = Bfdopen(0, OREAD);
+ items = populate_list(cmplbuf, false);
+ if(!isatty(cmplbuf->fid))
+ ixp_listen(&srv, cmplbuf->fid, inbuf, check_competions, nil);
+
+ caret_insert("", true);
+ update_filter(false);
+
+ if(!nokeys)
+ parse_keys(binding_spec);
+ if(keyfile) {
+ i = open(keyfile, O_RDONLY);
+ if(read(i, buffer, sizeof(buffer)) > 0)
+ parse_keys(buffer);
+ }
+
+ histidx = &hist;
+ link(&hist, &hist);
+ if(histfile) {
+ inbuf = Bopen(histfile, OREAD);
+ if(inbuf) {
+ item = populate_list(inbuf, true);
+ if(item) {
+ link(item->prev, &hist);
+ link(&hist, item);
+ }
+ Bterm(inbuf);
+ }
+ }
+
+ if(barwin == nil)
+ menu_init();
+
+ init_screens(screen);
+
+ i = ixp_serverloop(&srv);
+ if(i)
+ fprint(2, "%s: error: %r\n", argv0);
+ XCloseDisplay(display);
+
+ if(ndump >= 0 && histfile && result == 0)
+ history_dump(histfile, ndump);
+
+ return result;
+}
+
diff --git a/cmd/menu/menu.c b/cmd/menu/menu.c
new file mode 100644
index 0000000..c947ec4
--- /dev/null
+++ b/cmd/menu/menu.c
@@ -0,0 +1,344 @@
+#include "dat.h"
+#include <ctype.h>
+#include <strings.h>
+#include <unistd.h>
+#include "fns.h"
+
+static Handlers handlers;
+
+static int ltwidth;
+
+static void _menu_draw(bool);
+
+enum {
+ ACCEPT = CARET_LAST,
+ REJECT,
+ HIST,
+ KILL,
+ CMPL_NEXT,
+ CMPL_PREV,
+ CMPL_FIRST,
+ CMPL_LAST,
+ CMPL_NEXT_PAGE,
+ CMPL_PREV_PAGE,
+};
+
+void
+menu_init(void) {
+ WinAttr wa;
+
+ wa.override_redirect = 1;
+ wa.background_pixmap = ParentRelative;
+ wa.event_mask = ExposureMask | KeyPressMask;
+ barwin = createwindow(&scr.root, Rect(-1, -1, 1, 1), scr.depth, InputOutput,
+ &wa, CWOverrideRedirect
+ | CWBackPixmap
+ | CWEventMask);
+ sethandler(barwin, &handlers);
+ mapwin(barwin);
+
+ int i = 0;
+ while(!grabkeyboard(barwin)) {
+ if(i++ > 1000)
+ fatal("can't grab keyboard");
+ usleep(1000);
+ }
+}
+
+static void
+menu_unmap(long id, void *p) {
+
+ USED(id, p);
+ unmapwin(barwin);
+ XFlush(display);
+}
+
+static void
+selectitem(Item *i) {
+ if(i != matchidx) {
+ caret_set(input.filter_start, input.pos - input.string);
+ caret_insert(i->string, 0);
+ matchidx = i;
+ }
+}
+
+static void
+menu_cmd(int op, int motion) {
+ int n;
+
+ switch(op) {
+ case HIST:
+ n = input.pos - input.string;
+ caret_insert(history_search(motion, input.string, n), true);
+ input.pos = input.string + n;
+ break;
+ case KILL:
+ caret_delete(BACKWARD, motion);
+ break;
+ default:
+ goto next;
+ }
+ update_filter(true);
+next:
+ switch(op) {
+ case ACCEPT:
+ srv.running = false;
+ if(!matchidx && matchfirst->retstring && !motion)
+ if(input.filter_start == 0 && input.pos == input.end)
+ menu_cmd(CMPL_FIRST, 0);
+ if(!motion && matchidx && !strcmp(input.string, matchidx->string))
+ print("%s", matchidx->retstring);
+ else
+ print("%s", input.string);
+ break;
+ case REJECT:
+ srv.running = false;
+ result = 1;
+ break;
+ case BACKWARD:
+ case FORWARD:
+ caret_move(op, motion);
+ update_input();
+ break;
+ case CMPL_NEXT:
+ selectitem(matchidx ? matchidx->next : matchfirst);
+ break;
+ case CMPL_PREV:
+ selectitem((matchidx ? matchidx : matchstart)->prev);
+ break;
+ case CMPL_FIRST:
+ matchstart = matchfirst;
+ matchend = nil;
+ selectitem(matchstart);
+ break;
+ case CMPL_LAST:
+ selectitem(matchfirst->prev);
+ break;
+ case CMPL_NEXT_PAGE:
+ if(matchend)
+ selectitem(matchend->next);
+ break;
+ case CMPL_PREV_PAGE:
+ matchend = matchstart->prev;
+ matchidx = nil;
+ _menu_draw(false);
+ selectitem(matchstart);
+ break;
+ }
+ menu_draw();
+}
+
+static void
+_menu_draw(bool draw) {
+ Rectangle r, rd, rp, r2, extent;
+ CTuple *c;
+ Item *i;
+ int inputw, itemoff, end, pad, n, offset;
+
+ r = barwin->r;
+ r = rectsetorigin(r, ZP);
+
+ pad = (font->height & ~1) + font->pad.min.x + font->pad.max.x;
+
+ rd = r;
+ rp = ZR; // SET(rp)
+ if (prompt) {
+ if (!promptw)
+ promptw = textwidth(font, prompt) + 2 * ltwidth + pad;
+ rd.min.x += promptw;
+
+ rp = r;
+ rp.max.x = promptw;
+ }
+
+ inputw = min(Dx(rd) / 3, maxwidth);
+ inputw = max(inputw, textwidth(font, input.string)) + pad;
+ itemoff = inputw + 2 * ltwidth;
+ end = Dx(rd) - ltwidth;
+
+ fill(ibuf, r, cnorm.bg);
+
+ if(matchend && matchidx == matchend->next)
+ matchstart = matchidx;
+ else if(matchidx == matchstart->prev)
+ matchend = matchidx;
+ if (matchend == nil)
+ matchend = matchstart;
+
+ if(matchend == matchstart->prev && matchstart != matchidx) {
+ n = itemoff;
+ matchstart = matchend;
+ for(i=matchend; ; i=i->prev) {
+ n += i->width + pad;
+ if(n > end)
+ break;
+ matchstart = i;
+ if(i == matchfirst)
+ break;
+ }
+ }
+
+ if(!draw)
+ return;
+
+ r2 = rd;
+ for(i=matchstart; i->string; i=i->next) {
+ r2.min.x = promptw + itemoff;
+ itemoff = itemoff + i->width + pad;
+ r2.max.x = promptw + min(itemoff, end);
+ if(i != matchstart && itemoff > end)
+ break;
+
+ c = (i == matchidx) ? &csel : &cnorm;
+ fill(ibuf, r2, c->bg);
+ drawstring(ibuf, font, r2, Center, i->string, c->fg);
+ matchend = i;
+ if(i->next == matchfirst)
+ break;
+ }
+
+ r2 = rd;
+ r2.min.x = promptw + inputw;
+ if(matchstart != matchfirst)
+ drawstring(ibuf, font, r2, West, "<", cnorm.fg);
+ if(matchend->next != matchfirst)
+ drawstring(ibuf, font, r2, East, ">", cnorm.fg);
+
+ r2 = rd;
+ r2.max.x = promptw + inputw;
+ drawstring(ibuf, font, r2, West, input.string, cnorm.fg);
+
+ extent = textextents_l(font, input.string, input.pos - input.string, &offset);
+ r2.min.x = promptw + offset + font->pad.min.x - extent.min.x + pad/2 - 1;
+ r2.max.x = r2.min.x + 2;
+ r2.min.y++;
+ r2.max.y--;
+ border(ibuf, r2, 1, cnorm.border);
+
+ if (prompt)
+ drawstring(ibuf, font, rp, West, prompt, cnorm.fg);
+
+ border(ibuf, rd, 1, cnorm.border);
+ copyimage(barwin, r, ibuf, ZP);
+}
+
+void
+menu_draw(void) {
+ _menu_draw(true);
+}
+
+void
+menu_show(void) {
+ Rectangle r;
+ int height, pad;
+
+ USED(menu_unmap);
+
+ ltwidth = textwidth(font, "<");
+
+ pad = (font->height & ~1)/2;
+ height = labelh(font);
+
+ r = scr.rect;
+ if(ontop)
+ r.max.y = r.min.y + height;
+ else
+ r.min.y = r.max.y - height;
+ reshapewin(barwin, r);
+
+ freeimage(ibuf);
+ ibuf = allocimage(Dx(r), Dy(r), scr.depth);
+
+ mapwin(barwin);
+ raisewin(barwin);
+ menu_draw();
+}
+
+static void
+kdown_event(Window *w, XKeyEvent *e) {
+ char **action, **p;
+ char *key;
+ char buf[32];
+ int num;
+ KeySym ksym;
+
+ buf[0] = 0;
+ num = XLookupString(e, buf, sizeof buf, &ksym, 0);
+ key = XKeysymToString(ksym);
+ if(IsKeypadKey(ksym))
+ if(ksym == XK_KP_Enter)
+ ksym = XK_Return;
+ else if(ksym >= XK_KP_0 && ksym <= XK_KP_9)
+ ksym = (ksym - XK_KP_0) + XK_0;
+
+ if(IsFunctionKey(ksym)
+ || IsMiscFunctionKey(ksym)
+ || IsKeypadKey(ksym)
+ || IsPrivateKeypadKey(ksym)
+ || IsPFKey(ksym))
+ return;
+
+ action = find_key(key, e->state);
+ if(action == nil || action[0] == nil) {
+ if(num && !iscntrl(buf[0])) {
+ caret_insert(buf, false);
+ update_filter(true);
+ menu_draw();
+ }
+ }
+ else {
+ long mask = 0;
+# define have(val) !!(mask & (1 << val))
+ for(p=action+1; *p; p++)
+ mask |= 1 << getsym(*p);
+ int amount = (
+ have(LCHAR) ? CHAR :
+ have(LWORD) ? WORD :
+ have(LLINE) ? LINE :
+ -1);
+ switch(getsym(action[0])) {
+ case LACCEPT:
+ menu_cmd(ACCEPT, have(LLITERAL));
+ break;
+ case LBACKWARD:
+ menu_cmd(BACKWARD, amount);
+ break;
+ case LCOMPLETE:
+ amount = (
+ have(LNEXT) ? CMPL_NEXT :
+ have(LPREV) ? CMPL_PREV :
+ have(LNEXTPAGE) ? CMPL_NEXT_PAGE :
+ have(LPREVPAGE) ? CMPL_PREV_PAGE :
+ have(LFIRST) ? CMPL_FIRST :
+ have(LLAST) ? CMPL_LAST :
+ CMPL_NEXT);
+ menu_cmd(amount, 0);
+ break;
+ case LFORWARD:
+ menu_cmd(FORWARD, amount);
+ break;
+ case LHISTORY:
+ menu_cmd(HIST, have(LBACKWARD) ? BACKWARD : FORWARD);
+ break;
+ case LKILL:
+ menu_cmd(KILL, amount);
+ break;
+ case LREJECT:
+ menu_cmd(REJECT, 0);
+ break;
+ }
+ }
+}
+
+static void
+expose_event(Window *w, XExposeEvent *e) {
+
+ USED(w);
+ menu_draw();
+}
+
+static Handlers handlers = {
+ .expose = expose_event,
+ .kdown = kdown_event,
+};
+
diff --git a/cmd/strut/Makefile b/cmd/strut/Makefile
new file mode 100644
index 0000000..fca4bd2
--- /dev/null
+++ b/cmd/strut/Makefile
@@ -0,0 +1,27 @@
+ROOT= ../..
+include ${ROOT}/mk/hdr.mk
+include ${ROOT}/mk/wmii.mk
+
+main.c: ${ROOT}/mk/wmii.mk
+
+TARG = wistrut
+HFILES= dat.h fns.h
+
+PACKAGES += $(X11PACKAGES) xext xrandr xinerama
+
+LIB = $(LIBIXP)
+LIBS += -lm $(LIBS9)
+CFLAGS += -DIXP_NEEDAPI=86
+OBJ = main \
+ event \
+ ewmh \
+ win \
+ _util \
+ ../wmii/map \
+ ../wmii/printevent \
+ printevent_kludge \
+ ../wmii/x11 \
+ ../util
+
+include ${ROOT}/mk/one.mk
+
diff --git a/cmd/strut/_util.c b/cmd/strut/_util.c
new file mode 100644
index 0000000..364ff81
--- /dev/null
+++ b/cmd/strut/_util.c
@@ -0,0 +1,77 @@
+/* Copyright ©2008-2010 Kris Maglione <fbsdaemon@gmail.com>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include <ctype.h>
+#include <string.h>
+#include "fns.h"
+
+#define strbcmp(str, const) (strncmp((str), (const), sizeof(const)-1))
+static int
+getbase(const char **s, long *sign) {
+ const char *p;
+ int ret;
+
+ ret = 10;
+ *sign = 1;
+ if(**s == '-') {
+ *sign = -1;
+ *s += 1;
+ }else if(**s == '+')
+ *s += 1;
+
+ p = *s;
+ if(!strbcmp(p, "0x")) {
+ *s += 2;
+ ret = 16;
+ }
+ else if(isdigit(p[0])) {
+ if(p[1] == 'r') {
+ *s += 2;
+ ret = p[0] - '0';
+ }
+ else if(isdigit(p[1]) && p[2] == 'r') {
+ *s += 3;
+ ret = 10*(p[0]-'0') + (p[1]-'0');
+ }
+ }
+ else if(p[0] == '0') {
+ ret = 8;
+ }
+ if(ret != 10 && (**s == '-' || **s == '+'))
+ *sign = 0;
+ return ret;
+}
+
+bool
+getlong(const char *s, long *ret) {
+ const char *end;
+ char *rend;
+ int base;
+ long sign;
+
+ end = s+strlen(s);
+ base = getbase(&s, &sign);
+ if(sign == 0)
+ return false;
+
+ *ret = sign * strtol(s, &rend, base);
+ return (end == rend);
+}
+
+bool
+getulong(const char *s, ulong *ret) {
+ const char *end;
+ char *rend;
+ int base;
+ long sign;
+
+ end = s+strlen(s);
+ base = getbase(&s, &sign);
+ if(sign < 1)
+ return false;
+
+ *ret = strtoul(s, &rend, base);
+ return (end == rend);
+}
+
diff --git a/cmd/strut/dat.h b/cmd/strut/dat.h
new file mode 100644
index 0000000..810d384
--- /dev/null
+++ b/cmd/strut/dat.h
@@ -0,0 +1,33 @@
+#include <fmt.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <util.h>
+#include <ixp.h>
+#include <x11.h>
+
+#define BLOCK(x) do { x; }while(0)
+
+#ifndef EXTERN
+# define EXTERN extern
+#endif
+
+extern Handlers handlers;
+
+EXTERN bool running;
+
+EXTERN Window win;
+EXTERN Window frame;
+EXTERN long xtime;
+
+EXTERN char buffer[8092];
+EXTERN char* _buffer;
+
+static char* const _buf_end = buffer + sizeof buffer;
+
+#define bufclear() \
+ BLOCK( _buffer = buffer; _buffer[0] = '\0' )
+#define bufprint(...) \
+ _buffer = seprint(_buffer, _buf_end, __VA_ARGS__)
+
diff --git a/cmd/strut/event.c b/cmd/strut/event.c
new file mode 100644
index 0000000..aebd8ba
--- /dev/null
+++ b/cmd/strut/event.c
@@ -0,0 +1,309 @@
+/* Copyright ©2006-2010 Kris Maglione <fbsdaemon@gmail.com>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include "fns.h"
+
+static void (*handler[LASTEvent])(XEvent*);
+
+void
+dispatch_event(XEvent *e) {
+ /* print("%E\n", e); */
+ if(e->type < nelem(handler) && handler[e->type])
+ handler[e->type](e);
+}
+
+#define handle(w, fn, ev) \
+ BLOCK(if((w)->handler->fn) (w)->handler->fn((w), ev))
+
+#ifdef notdef
+uint
+flushevents(long event_mask, bool dispatch) {
+ XEvent ev;
+ uint n = 0;
+
+ while(XCheckMaskEvent(display, event_mask, &ev)) {
+ if(dispatch)
+ dispatch_event(&ev);
+ n++;
+ }
+ return n;
+}
+
+static int
+findenter(Display *d, XEvent *e, XPointer v) {
+ long *l;
+
+ USED(d);
+ l = (long*)v;
+ if(*l)
+ return false;
+ if(e->type == EnterNotify)
+ return true;
+ if(e->type == MotionNotify)
+ (*l)++;
+ return false;
+}
+
+/* This isn't perfect. If there were motion events in the queue
+ * before this was called, then it flushes nothing. If we don't
+ * check for them, we might lose a legitamate enter event.
+ */
+uint
+flushenterevents(void) {
+ XEvent e;
+ long l;
+ int n;
+
+ l = 0;
+ n = 0;
+ while(XCheckIfEvent(display, &e, findenter, (void*)&l))
+ n++;
+ return n;
+}
+#endif
+
+static int
+findtime(Display *d, XEvent *e, XPointer v) {
+ Window *w;
+
+ w = (Window*)v;
+ if(e->type == PropertyNotify && e->xproperty.window == w->xid) {
+ xtime = e->xproperty.time;
+ return true;
+ }
+ return false;
+}
+
+void
+xtime_kludge(void) {
+ Window *w;
+ WinAttr wa;
+ XEvent e;
+ long l;
+
+ w = createwindow(&scr.root, Rect(0, 0, 1, 1), 0, InputOnly, &wa, 0);
+
+ XSelectInput(display, w->xid, PropertyChangeMask);
+ changeprop_long(w, "ATOM", "ATOM", &l, 0);
+ XIfEvent(display, &e, findtime, (void*)w);
+
+ destroywindow(w);
+}
+
+static void
+buttonrelease(XEvent *e) {
+ XButtonPressedEvent *ev;
+ Window *w;
+
+ ev = &e->xbutton;
+ if((w = findwin(ev->window)))
+ handle(w, bup, ev);
+}
+
+static void
+buttonpress(XEvent *e) {
+ XButtonPressedEvent *ev;
+ Window *w;
+
+ ev = &e->xbutton;
+ if((w = findwin(ev->window)))
+ handle(w, bdown, ev);
+ else
+ XAllowEvents(display, ReplayPointer, ev->time);
+}
+
+static void
+clientmessage(XEvent *e) {
+ XClientMessageEvent *ev;
+
+ ev = &e->xclient;
+ USED(ev);
+}
+
+static void
+configurenotify(XEvent *e) {
+ XConfigureEvent *ev;
+ Window *w;
+
+ ev = &e->xconfigure;
+ if((w = findwin(ev->window)))
+ handle(w, config, ev);
+}
+
+static void
+destroynotify(XEvent *e) {
+ XDestroyWindowEvent *ev;
+ Window *w;
+
+ ev = &e->xdestroywindow;
+ if((w = findwin(ev->window)))
+ handle(w, destroy, ev);
+}
+
+static void
+enternotify(XEvent *e) {
+ XCrossingEvent *ev;
+ Window *w;
+
+ ev = &e->xcrossing;
+ xtime = ev->time;
+ if(ev->mode != NotifyNormal)
+ return;
+
+ if((w = findwin(ev->window)))
+ handle(w, enter, ev);
+}
+
+static void
+leavenotify(XEvent *e) {
+ XCrossingEvent *ev;
+
+ ev = &e->xcrossing;
+ xtime = ev->time;
+}
+
+static void
+focusin(XEvent *e) {
+ XFocusChangeEvent *ev;
+ Window *w;
+
+ ev = &e->xfocus;
+ /* Yes, we're focusing in on nothing, here. */
+ if(ev->detail == NotifyDetailNone) {
+ /* FIXME: Do something. */
+ return;
+ }
+
+ if(!((ev->detail == NotifyNonlinear)
+ ||(ev->detail == NotifyNonlinearVirtual)
+ ||(ev->detail == NotifyVirtual)
+ ||(ev->detail == NotifyInferior)
+ ||(ev->detail == NotifyAncestor)))
+ return;
+ if((ev->mode == NotifyWhileGrabbed))
+ return;
+
+ if((w = findwin(ev->window)))
+ handle(w, focusin, ev);
+}
+
+static void
+focusout(XEvent *e) {
+ XFocusChangeEvent *ev;
+ Window *w;
+
+ ev = &e->xfocus;
+ if(!((ev->detail == NotifyNonlinear)
+ ||(ev->detail == NotifyNonlinearVirtual)
+ ||(ev->detail == NotifyVirtual)
+ ||(ev->detail == NotifyInferior)
+ ||(ev->detail == NotifyAncestor)))
+ return;
+
+ if((w = findwin(ev->window)))
+ handle(w, focusout, ev);
+}
+
+static void
+expose(XEvent *e) {
+ XExposeEvent *ev;
+ Window *w;
+
+ ev = &e->xexpose;
+ if(ev->count == 0) {
+ if((w = findwin(ev->window)))
+ handle(w, expose, ev);
+ }
+}
+
+static void
+keypress(XEvent *e) {
+ XKeyEvent *ev;
+
+ ev = &e->xkey;
+ xtime = ev->time;
+}
+
+static void
+mappingnotify(XEvent *e) {
+ XMappingEvent *ev;
+
+ ev = &e->xmapping;
+ /* Why do you need me to tell you this? */
+ XRefreshKeyboardMapping(ev);
+}
+
+static void
+motionnotify(XEvent *e) {
+ XMotionEvent *ev;
+ Window *w;
+
+ ev = &e->xmotion;
+ xtime = ev->time;
+ if((w = findwin(ev->window)))
+ handle(w, motion, ev);
+}
+
+static void
+propertynotify(XEvent *e) {
+ XPropertyEvent *ev;
+ Window *w;
+
+ ev = &e->xproperty;
+ xtime = ev->time;
+ if((w = findwin(ev->window)))
+ handle(w, property, ev);
+}
+
+static void
+mapnotify(XEvent *e) {
+ XMapEvent *ev;
+ Window *w;
+
+ ev = &e->xmap;
+ if((w = findwin(ev->window)))
+ handle(w, map, ev);
+}
+
+static void
+unmapnotify(XEvent *e) {
+ XUnmapEvent *ev;
+ Window *w;
+
+ ev = &e->xunmap;
+ if((w = findwin(ev->window)) && w->parent && (ev->event == w->parent->xid)) {
+ if(ev->send_event || w->unmapped-- == 0)
+ handle(w, unmap, ev);
+ }
+}
+
+static void (*handler[LASTEvent])(XEvent*) = {
+ [ButtonPress] = buttonpress,
+ [ButtonRelease] = buttonrelease,
+ [ClientMessage] = clientmessage,
+ [ConfigureNotify] = configurenotify,
+ [DestroyNotify] = destroynotify,
+ [EnterNotify] = enternotify,
+ [Expose] = expose,
+ [FocusIn] = focusin,
+ [FocusOut] = focusout,
+ [KeyPress] = keypress,
+ [LeaveNotify] = leavenotify,
+ [MapNotify] = mapnotify,
+ [MappingNotify] = mappingnotify,
+ [MotionNotify] = motionnotify,
+ [PropertyNotify] = propertynotify,
+ [UnmapNotify] = unmapnotify,
+};
+
+void
+xevent_loop(void) {
+ XEvent ev;
+
+ while(running) {
+ XNextEvent(display, &ev);
+ dispatch_event(&ev);
+ }
+}
+
diff --git a/cmd/strut/ewmh.c b/cmd/strut/ewmh.c
new file mode 100644
index 0000000..b56923c
--- /dev/null
+++ b/cmd/strut/ewmh.c
@@ -0,0 +1,84 @@
+/* Copyright ©2007-2010 Kris Maglione <fbsdaemon@gmail.com>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include <limits.h>
+#include <string.h>
+#include "fns.h"
+
+#define Net(x) ("_NET_" x)
+#define Action(x) ("_NET_WM_ACTION_" x)
+#define State(x) ("_NET_WM_STATE_" x)
+#define Type(x) ("_NET_WM_WINDOW_TYPE_" x)
+#define NET(x) xatom(Net(x))
+#define ACTION(x) xatom(Action(x))
+#define STATE(x) xatom(State(x))
+#define TYPE(x) xatom(Type(x))
+
+enum {
+ Left, Right, Top, Bottom,
+ LeftMin, LeftMax,
+ RightMin, RightMax,
+ TopMin, TopMax,
+ BottomMin, BottomMax,
+ Last
+};
+
+void
+ewmh_getstrut(Window *w, Rectangle struts[4]) {
+ long *strut;
+ ulong n;
+
+ memset(struts, 0, sizeof struts);
+
+ n = getprop_long(w, Net("WM_STRUT_PARTIAL"), "CARDINAL",
+ 0L, &strut, Last);
+ if(n != Last) {
+ free(strut);
+ n = getprop_long(w, Net("WM_STRUT"), "CARDINAL",
+ 0L, &strut, 4L);
+ if(n != 4) {
+ free(strut);
+ return;
+ }
+ strut = erealloc(strut, Last * sizeof *strut);
+ strut[LeftMin] = strut[RightMin] = 0;
+ strut[LeftMax] = strut[RightMax] = INT_MAX;
+ strut[TopMin] = strut[BottomMin] = 0;
+ strut[TopMax] = strut[BottomMax] = INT_MAX;
+ }
+ struts[Left] = Rect(0, strut[LeftMin], strut[Left], strut[LeftMax]);
+ struts[Right] = Rect(-strut[Right], strut[RightMin], 0, strut[RightMax]);
+ struts[Top] = Rect(strut[TopMin], 0, strut[TopMax], strut[Top]);
+ struts[Bottom] = Rect(strut[BottomMin], -strut[Bottom], strut[BottomMax], 0);
+ free(strut);
+}
+
+void
+ewmh_setstrut(Window *w, Rectangle struts[4]) {
+ long strut[Last];
+ int i;
+
+ strut[LeftMin] = struts[Left].min.y;
+ strut[Left] = struts[Left].max.x;
+ strut[LeftMax] = struts[Left].max.y;
+
+ strut[RightMin] = struts[Right].min.y;
+ strut[Right] = -struts[Right].min.x;
+ strut[RightMax] = struts[Right].max.y;
+
+ strut[TopMin] = struts[Top].min.x;
+ strut[Top] = struts[Top].max.y;
+ strut[TopMax] = struts[Top].max.x;
+
+ strut[BottomMin] = struts[Bottom].min.x;
+ strut[Bottom] = -struts[Bottom].min.y;
+ strut[BottomMax] = struts[Bottom].max.x;
+
+ for(i=0; i<Last; i++)
+ if(strut[i] < 0)
+ strut[i] = 0;
+
+ changeprop_long(w, Net("WM_STRUT_PARTIAL"), "CARDINAL", strut, nelem(strut));
+}
+
diff --git a/cmd/strut/fns.h b/cmd/strut/fns.h
new file mode 100644
index 0000000..af6383e
--- /dev/null
+++ b/cmd/strut/fns.h
@@ -0,0 +1,18 @@
+
+void debug(int, const char*, ...);
+void dispatch_event(XEvent*);
+uint flushevents(long, bool);
+uint flushenterevents(void);
+void xevent_loop(void);
+void xtime_kludge(void);
+
+void restrut(void);
+
+bool getlong(const char*, long*);
+bool getulong(const char*, ulong*);
+
+void ewmh_getstrut(Window*, Rectangle[4]);
+void ewmh_setstrut(Window*, Rectangle[4]);
+
+void printevent(XEvent *e);
+
diff --git a/cmd/strut/main.c b/cmd/strut/main.c
new file mode 100644
index 0000000..ef9d27f
--- /dev/null
+++ b/cmd/strut/main.c
@@ -0,0 +1,102 @@
+/* Copyright ©2006-2010 Kris Maglione <fbsdaemon@gmail.com>
+ * See LICENSE file for license details.
+ */
+#define EXTERN
+#include "dat.h"
+#include <X11/Xproto.h>
+#include <locale.h>
+#include <string.h>
+#include "fns.h"
+
+static const char version[] = "witray-"VERSION", ©2007 Kris Maglione\n";
+
+static void
+usage(void) {
+ fatal("usage: %s <window>\n", argv0);
+}
+
+static int
+errfmt(Fmt *f) {
+ return fmtstrcpy(f, ixp_errbuf());
+}
+
+void
+debug(int flag, const char *fmt, ...) {
+ va_list ap;
+
+ USED(flag);
+ va_start(ap, fmt);
+ vfprint(2, fmt, ap);
+ va_end(ap);
+}
+
+ErrorCode ignored_xerrors[] = { {0,} };
+
+static Window
+findframe(Window *w) {
+ XWindow *children;
+ XWindow xw, par, root;
+ Window ret = {0, };
+ uint n;
+
+ for(par=w->xid; par != scr.root.xid; ) {
+ xw = par;
+ XQueryTree(display, xw, &root, &par, &children, &n);
+ XFree(children);
+ }
+ ret.xid = xw;
+ ret.parent = &scr.root;
+ return ret;
+}
+
+static void
+getwinsize(Window *win) {
+ int x, y;
+ uint w, h;
+ XWindow root;
+ uint border, depth;
+
+ XGetGeometry(display, win->xid, &root,
+ &x, &y, &w, &h,
+ &border, &depth);
+ win->r = rectaddpt(Rect(0, 0, w, h),
+ Pt(x+border, y+border));
+}
+
+int
+main(int argc, char *argv[]) {
+ char *s;
+
+ fmtinstall('r', errfmt);
+extern int fmtevent(Fmt*);
+ fmtinstall('E', fmtevent);
+
+ ARGBEGIN{
+ default:
+ usage();
+ }ARGEND;
+
+ s = EARGF(usage());
+ if(!getulong(s, &win.xid))
+ usage();
+
+ if(argc)
+ usage();
+
+ setlocale(LC_CTYPE, "");
+
+ initdisplay();
+
+ frame = findframe(&win);
+ getwinsize(&frame);
+ restrut();
+ sethandler(&frame, &handlers);
+ selectinput(&frame, StructureNotifyMask);
+
+ running = true;
+ xevent_loop();
+
+ XCloseDisplay(display);
+ return 0;
+}
+
diff --git a/cmd/strut/printevent_kludge.c b/cmd/strut/printevent_kludge.c
new file mode 100644
index 0000000..c5e53cb
--- /dev/null
+++ b/cmd/strut/printevent_kludge.c
@@ -0,0 +1,12 @@
+#include "dat.h"
+
+void dprint(const char *fmt, ...);
+void
+dprint(const char *fmt, ...) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ vfprint(2, fmt, ap);
+ va_end(ap);
+}
+
diff --git a/cmd/strut/win.c b/cmd/strut/win.c
new file mode 100644
index 0000000..ba607cf
--- /dev/null
+++ b/cmd/strut/win.c
@@ -0,0 +1,102 @@
+/* Copyright ©2008-2010 Kris Maglione <fbsdaemon@gmail.com>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include <string.h>
+#include "fns.h"
+
+void
+restrut(void) {
+ enum { Left, Right, Top, Bottom };
+ Rectangle strut[4];
+ Rectangle r;
+
+ r = frame.r;
+ memset(strut, 0, sizeof strut);
+ if(Dx(r) < Dx(scr.rect)/2) {
+ if(r.min.x <= scr.rect.min.x) {
+ strut[Left] = r;
+ strut[Left].min.x = 0;
+ strut[Left].max.x -= scr.rect.min.x;
+ }
+ if(r.max.x >= scr.rect.max.x) {
+ strut[Right] = r;
+ strut[Right].min.x -= scr.rect.max.x;
+ strut[Right].max.x = 0;
+ }
+ }
+ if(Dy(r) < Dy(scr.rect)/2) {
+ if(r.min.y <= scr.rect.min.y) {
+ strut[Top] = r;
+ strut[Top].min.y = 0;
+ strut[Top].max.y -= scr.rect.min.y;
+ }
+ if(r.max.y >= scr.rect.max.y) {
+ strut[Bottom] = r;
+ strut[Bottom].min.y -= scr.rect.max.y;
+ strut[Bottom].max.y = 0;
+ }
+ }
+
+#define pstrut(name) \
+ if(!eqrect(strut[name], ZR)) \
+ fprint(2, "strut["#name"] = %R\n", strut[name])
+ /* Choose the struts which take up the least space.
+ * Not ideal.
+ */
+ if(Dy(strut[Top])) {
+ if(Dx(strut[Left]))
+ if(Dy(strut[Top]) < Dx(strut[Left]))
+ strut[Left] = ZR;
+ else
+ strut[Top] = ZR;
+ if(Dx(strut[Right]))
+ if(Dy(strut[Top]) < Dx(strut[Right]))
+ strut[Right] = ZR;
+ else
+ strut[Top] = ZR;
+ }
+ if(Dy(strut[Bottom])) {
+ if(Dx(strut[Left]))
+ if(Dy(strut[Bottom]) < Dx(strut[Left]))
+ strut[Left] = ZR;
+ else
+ strut[Bottom] = ZR;
+ if(Dx(strut[Right]))
+ if(Dy(strut[Bottom]) < Dx(strut[Right]))
+ strut[Right] = ZR;
+ else
+ strut[Bottom] = ZR;
+ }
+
+#if 0
+ pstrut(Left);
+ pstrut(Right);
+ pstrut(Top);
+ pstrut(Bottom);
+#endif
+
+ ewmh_setstrut(&win, strut);
+}
+
+static void
+config(Window *w, XConfigureEvent *ev) {
+
+ USED(w);
+
+ frame.r = rectaddpt(Rect(0, 0, ev->width, ev->height),
+ Pt(ev->x+ev->border_width, ev->y+ev->border_width));
+ restrut();
+}
+
+static void
+destroy(Window *w, XDestroyWindowEvent *ev) {
+ USED(w, ev);
+ running = false;
+}
+
+Handlers handlers = {
+ .config = config,
+ .destroy = destroy,
+};
+
diff --git a/cmd/util.c b/cmd/util.c
new file mode 100644
index 0000000..2556d28
--- /dev/null
+++ b/cmd/util.c
@@ -0,0 +1,272 @@
+/* Written by Kris Maglione <fbsdaemon at gmail dot com> */
+/* Public domain */
+#include <ctype.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+#include <util.h>
+#include <fmt.h>
+
+typedef struct VFmt VFmt;
+struct VFmt {
+ const char *fmt;
+ va_list args;
+};
+
+#ifdef VARARGCK
+# pragma varargck type "V" VFmt*
+#endif
+
+static int
+Vfmt(Fmt *f) {
+ VFmt *vf;
+ int i;
+
+ vf = va_arg(f->args, VFmt*);
+ i = fmtvprint(f, vf->fmt, vf->args);
+ return i;
+}
+
+void
+fatal(const char *fmt, ...) {
+ VFmt fp;
+
+ fmtinstall('V', Vfmt);
+ fmtinstall('\001', Vfmt);
+
+ fp.fmt = fmt;
+ va_start(fp.args, fmt);
+ fprint(2, "%s: fatal: %V\n", argv0, &fp);
+ va_end(fp.args);
+
+ exit(1);
+}
+
+void*
+freelater(void *p) {
+ static char* obj[16];
+ static long nobj;
+ int id;
+
+ id = nobj++ % nelem(obj);
+ free(obj[id]);
+ obj[id] = p;
+ return p;
+}
+
+char*
+vsxprint(const char *fmt, va_list ap) {
+ char *s;
+
+ s = vsmprint(fmt, ap);
+ freelater(s);
+ return s;
+}
+
+char*
+sxprint(const char *fmt, ...) {
+ va_list ap;
+ char *ret;
+
+ va_start(ap, fmt);
+ ret = vsxprint(fmt, ap);
+ va_end(ap);
+ return ret;
+}
+
+void
+_die(char *file, int line, char *msg, ...) {
+ va_list ap;
+
+ va_start(ap, msg);
+ fprint(2, "%s: dieing at %s:%d: %s\n",
+ argv0, file, line,
+ vsxprint(msg, ap));
+ va_end(ap);
+
+ kill(getpid(), SIGABRT);
+ abort(); /* Adds too many frames:
+ * _die()
+ * abort()
+ * raise(SIGABRT)
+ * kill(getpid(), SIGABRT)
+ */
+}
+
+/* Can't malloc */
+static void
+mfatal(char *name, uint size) {
+ const char
+ couldnot[] = ": fatal: Could not ",
+ paren[] = "() ",
+ bytes[] = " bytes\n";
+ char buf[1024];
+ char sizestr[8];
+ int i;
+
+ i = sizeof sizestr;
+ do {
+ sizestr[--i] = '0' + (size%10);
+ size /= 10;
+ } while(size > 0);
+
+ strlcat(buf, argv0, sizeof buf);
+ strlcat(buf, couldnot, sizeof buf);
+ strlcat(buf, name, sizeof buf);
+ strlcat(buf, paren, sizeof buf);
+ strlcat(buf, sizestr+i, sizeof buf);
+ strlcat(buf, bytes, sizeof buf);
+ write(2, buf, strlen(buf));
+
+ exit(1);
+}
+
+void *
+emalloc(uint size) {
+ void *ret = malloc(size);
+ if(!ret)
+ mfatal("malloc", size);
+ return ret;
+}
+
+void *
+emallocz(uint size) {
+ void *ret = emalloc(size);
+ memset(ret, 0, size);
+ return ret;
+}
+
+void *
+erealloc(void *ptr, uint size) {
+ void *ret = realloc(ptr, size);
+ if(!ret)
+ mfatal("realloc", size);
+ return ret;
+}
+
+char*
+estrdup(const char *str) {
+ void *ret = strdup(str);
+ if(!ret)
+ mfatal("strdup", strlen(str));
+ return ret;
+}
+
+char*
+estrndup(const char *str, uint len) {
+ char *ret;
+
+ len = min(len, strlen(str));
+ ret = emalloc(len + 1);
+ memcpy(ret, str, len);
+ ret[len] = '\0';
+ return ret;
+}
+
+
+uint
+tokenize(char *res[], uint reslen, char *str, char delim) {
+ char *s;
+ uint i;
+
+ i = 0;
+ s = str;
+ while(i < reslen && *s) {
+ while(*s == delim)
+ *(s++) = '\0';
+ if(*s)
+ res[i++] = s;
+ while(*s && *s != delim)
+ s++;
+ }
+ return i;
+}
+
+uint
+stokenize(char *res[], uint reslen, char *str, char *delim) {
+ char *s;
+ uint i;
+
+ i = 0;
+ s = str;
+ while(i < reslen && *s) {
+ while(strchr(delim, *s))
+ *(s++) = '\0';
+ if(*s)
+ res[i++] = s;
+ while(*s && !strchr(delim, *s))
+ s++;
+ }
+ return i;
+}
+
+int
+max(int a, int b) {
+ if(a > b)
+ return a;
+ return b;
+}
+
+int
+min(int a, int b) {
+ if(a < b)
+ return a;
+ return b;
+}
+
+int
+utflcpy(char *to, const char *from, int l) {
+ char *p;
+
+ p = utfecpy(to, to+l, from);
+ return p-to;
+}
+
+uint
+strlcat(char *dst, const char *src, uint size) {
+ const char *s;
+ char *d;
+ int n, len;
+
+ d = dst;
+ s = src;
+ n = size;
+ while(n-- > 0 && *d != '\0')
+ d++;
+ len = n;
+
+ while(*s != '\0') {
+ if(n-- > 0)
+ *d++ = *s;
+ s++;
+ }
+ if(len > 0)
+ *d = '\0';
+ return size - n - 1;
+}
+
+/* TODO: Make this UTF-8 compliant. */
+char*
+strcasestr(const char *dst, const char *src) {
+ int dc, sc;
+ int len;
+
+ len = strlen(src) - 1;
+ for(; (sc = *src) && *dst; src++) {
+ sc = tolower(dc);
+ for(; (dc = *dst); dst++) {
+ dc = tolower(dc);
+ if(sc == dc && !strncasecmp(dst+1, src+1, len))
+ return (char*)(uintptr_t)dst;
+ }
+ }
+ return nil;
+}
+
diff --git a/cmd/wihack.sh b/cmd/wihack.sh
new file mode 100644
index 0000000..2c401d4
--- /dev/null
+++ b/cmd/wihack.sh
@@ -0,0 +1,44 @@
+#!/bin/sh -f
+
+usage() {
+ echo 1>&2 Usage: \
+ "$0 [-transient <window>] [-type <window_type>[,...]] [-tags <tags>] <command> [<arg> ...]"
+ exit 1
+}
+
+checkarg='[ ${#@} -gt 0 ] || usage'
+export WMII_HACK_TIME=$(date +%s)
+
+while [ ${#@} -gt 0 ]
+do
+ case $1 in
+ -transient)
+ shift; eval $checkarg
+ export WMII_HACK_TRANSIENT=$1
+ shift;;
+ -type)
+ shift; eval $checkarg
+ export WMII_HACK_TYPE=$1
+ shift;;
+ -tags)
+ shift; eval $checkarg
+ export WMII_HACK_TAGS=$1
+ shift;;
+ -*)
+ usage;;
+ *)
+ break;;
+ esac
+done
+
+eval $checkarg
+
+if [ ! -u "`which $1`" -a ! -g "`which $1`" ]
+then
+ export LD_PRELOAD=libwmii_hack.so
+ export LD_LIBRARY_PATH="@LIBDIR@${LD_LIBRARY_PATH:+:}${LD_LIBRARY_PATH}"
+else
+ unset WMII_HACK_TRANSIENT WMII_HACK_TYPE WMII_HACK_TAGS
+fi
+exec "$@"
+
diff --git a/cmd/wmii.rc.rc b/cmd/wmii.rc.rc
new file mode 100755
index 0000000..392a0d7
--- /dev/null
+++ b/cmd/wmii.rc.rc
@@ -0,0 +1,181 @@
+
+# For the time being, this file follows the lisp bracing
+# convention. i.e.:
+# if(frob this) {
+# frob that
+# if(frob theother) {
+# unfrob this
+# unfrob that}}
+
+wmiiscript=$1
+wmiikeys=()
+
+wi_newline='
+'
+
+echo Start $wmiiscript | wmiir write /event >[2]/dev/null \
+ || exit write
+
+if(~ $scriptname '')
+ scriptname=$wmiiscript
+
+# Blech.
+if(! test -x $PLAN9/bin/read)
+ fn read { sh -c 'read -r x || exit 1; echo $x' }
+
+fn wi_atexit {}
+fn sigexit {
+ wi_atexit
+}
+
+fn wi_fatal {
+ echo $scriptname: Fatal: $*
+ exit fatal
+}
+
+fn wi_notice {
+ xmessage $scriptname: Notice: $*
+}
+
+fn wi_readctl { wmiir read /ctl | sed -n 's/^'$1' (.*)/\1/p' }
+
+wmiifont=`{wi_readctl font}
+wmiinormcol=`{wi_readctl normcolors}
+wmiifocuscol=`{wi_readctl focuscolors}
+
+fn wi_fnmenu {
+ group=$1^Menu-$2 last=$group^_last fns=`{wi_getfuns $group} {
+ shift 2
+ if(! ~ $#fns 0) {
+ res = `{wmii9menu -i $"($last) $fns} \
+ if(! ~ $res '') {
+ ($last) = $res
+ $group-$res $*}}}
+}
+
+fn wi_fn-p {
+ rc -c 'whatis '$1 >[2]/dev/null | grep -s '^fn '
+}
+
+fn wi_proglist {
+ # XXX: maxdepth is not POSIX compliant.
+ ifs=: { find -L `{echo -n $*} -type f -a -maxdepth 1 \
+ '(' -perm -0100 -o -perm -0010 -o -perm -0001 ')' >[2]/dev/null \
+ | sed 's,.*/,,' | sort | uniq}
+}
+
+fn wi_actions {
+ { wi_proglist $WMII_CONFPATH
+ wi_getfuns Action
+ } | sort | uniq
+}
+
+fn wi_script {
+ noprog=true prog=() {
+ if(~ $1 -f) {
+ shift
+ noprog=/dev/null
+ }
+ prog = `{rc -c 'path=$confpath whatis '$1 >[2]/dev/null \
+ | grep -v '^fn |=' || echo $noprog}
+ shift; echo $prog $*}
+}
+
+
+fn wi_initkeys {
+ ifs=() {
+ wmiikeys = `{wmiir read /keys} {
+ mykeys = `{comm -23 \
+ <{wi_getfuns Key | sort | uniq} \
+ <{echo $wmiikeys | sort | uniq}}
+ {echo $wmiikeys; wi_getfuns Key} \
+ | sort | uniq \
+ | wmiir write /keys }}
+ fn wi_atexit {
+ wi_cleankeys
+ }
+}
+
+fn wi_cleankeys {
+ ifs=() {
+ wmiikeys = `{wmiir read /keys} {
+ comm -23 <{echo $wmiikeys | sort | uniq} \
+ <{echo $mykeys} \
+ | wmiir write /keys }}
+}
+
+fn wi_runcmd { @{
+ rfork ns
+ path=$oldpath
+ if(~ $1 -t) {
+ shift
+ * = (wihack -tags `{wmiir read /tag/sel/ctl | sed 1q} $*) }
+ fn `{env | 9 sed -n 's/^fn#([^=]+).*/\1/p'}
+ mykeys=()
+ if(! ~ $* '')
+ eval exec $* & }
+}
+
+fn wi_getfuns {
+ env | sed -n 's/^fn#'^$1^'-([^=]+).*/\1/p' | sort | uniq
+}
+
+for(i in Key Event Action '*Menu')
+ fns=`{wi_getfuns $i} {
+ if(! ~ $fns '')
+ fn $i-^$fns}
+
+fn wi_tags {
+ wmiir ls /tag | sed 's,/,,; /^sel$/d'
+}
+
+fn wi_seltag {
+ wmiir read /tag/sel/ctl | sed 1q
+}
+
+fn wi_selclient {
+ wmiir read /client/sel/ctl | sed 1q
+}
+
+fn wi_readevent {
+ wmiir read /event
+}
+
+fn wi_eventloop {
+ wi_initkeys
+
+ wi_readevent |
+ while(ifs=$wi_ewlinel{wi_event=`{read}}) {
+ ifs=$wi_newline{
+ wi_arg=`{echo $wi_event | sed 's/^[^ ]+ //'}}
+ * = `{echo $wi_event}
+ event = $1; shift
+ Event-$"event $*
+ } >[2]/dev/null </dev/null
+ true
+}
+
+fn Event-Key {
+ Key-$1 $1
+}
+
+fn Event-Quit {
+ exit
+}
+
+fn Event-Start {
+ if(~ $1 $wmiiscript)
+ exit
+}
+
+fn Action {
+ cmd=$1 action=Action-$"cmd { shift
+ if(! ~ $cmd '') {
+ if(wi_fn-p $action)
+ $action $*
+ if not
+ wi_runcmd `{wi_script $cmd} $*
+ }
+ }
+}
+
diff --git a/cmd/wmii.sh.sh b/cmd/wmii.sh.sh
new file mode 100755
index 0000000..d636f26
--- /dev/null
+++ b/cmd/wmii.sh.sh
@@ -0,0 +1,219 @@
+
+[ -z "$scriptname" ] && scriptname="$wmiiscript"
+echo Start $wmiiscript | wmiir write /event 2>/dev/null ||
+ exit 1
+
+wi_newline='
+'
+
+_wi_script() {
+ # Awk script to mangle key/event/action definition spec
+ # into switch-case functions and lists of the cases. Also
+ # generates a simple key binding help text based on KeyGroup
+ # clauses and comments that appear on key lines.
+ #
+ # Each clause (Key, Event, Action) generates a function of the
+ # same name which executes the indented text after the matching
+ # clause. Clauses are selected based on the first argument passed
+ # to the mangled function. Additionally, a variable is created named
+ # for the plouralized version of the clause name (Keys, Events,
+ # Actions) which lists each case value. These are used for actions
+ # menus and to write wmii's /keys file.
+ cat <<'!'
+ BEGIN {
+ arg[1] = "Nop"
+ narg = 1;
+ body = "";
+ keyhelp = ""
+ }
+ function quote(s) {
+ gsub(/'/, "'\\''", s)
+ return "'" s "'"
+ }
+ function addevent() {
+ var = arg[1] "s"
+ for(i=2; i <= narg; i++) {
+ if(body == "")
+ delete a[arg[1],arg[i]]
+ else
+ a[arg[1],arg[i]] = body
+ if(i == 2) {
+ # There's a bug here. Can you spot it?
+ gsub("[^a-zA-Z_0-9]", "_", arg[2]);
+ body = sprintf("%s %s \"$@\"", arg[1], arg[2])
+ }
+ }
+ }
+ /^(Key)Group[ \t]/ {
+ sub(/^[^ \t]+[ \t]+/, "")
+ keyhelp = keyhelp "\n " $0 "\n"
+ }
+ /^(Event|Key|Action|Menu)[ \t]/ {
+ addevent()
+ split($0, tmp, /[ \t]+#[ \t]*/)
+ narg = split(tmp[1], arg)
+ if(arg[1] == "Key" && tmp[2])
+ for (i=2; i <= narg; i++)
+ keyhelp = keyhelp sprintf(" %-20s %s\n",
+ arg[i], tmp[2])
+ body = ""
+ }
+ /^[ \t]/ {
+ sub(/^( |\t)/, "")
+ body = body"\n"$0
+ }
+
+ END {
+ addevent()
+ for(k in a) {
+ split(k, b, SUBSEP)
+ c[b[1]] = c[b[1]] b[2] "\n"
+ if(body != "")
+ d[b[1]] = d[b[1]] quote(b[2]) ")" a[k] "\n;;\n"
+ }
+ for(k in c)
+ printf "%ss=%s\n", k, quote(c[k])
+ for(k in d) {
+ printf "%s() {\n", k
+ printf " %s=$1; shift\n", tolower(k)
+ printf "case $%s in\n%s\n*) return 1\nesac\n", tolower(k), d[k]
+ printf "}\n"
+ }
+ print "KeysHelp=" quote(keyhelp)
+ }
+!
+}
+
+_wi_text() {
+ cat <<'!'
+Event Start
+ if [ "$1" = "$wmiiscript" ]; then
+ exit
+ fi
+Event Key
+ Key "$@"
+!
+ eval "cat <<!
+$( (test ! -t 0 && cat; for a; do eval "$a"; done) | sed '/^[ ]/s/\([$`\\]\)/\\\1/g')
+!
+"
+}
+
+wi_events() {
+ #cho "$(_wi_text "$@" | awk "$(_wi_script)")" | cat -n
+ eval "$(_wi_text "$@" | awk "$(_wi_script)")"
+}
+
+wi_fatal() {
+ echo $scriptname: Fatal: $*
+ exit 1
+}
+
+wi_notice() {
+ xmessage $scriptname: Notice: $*
+}
+
+wi_readctl() {
+ wmiir read /ctl | sed -n 's/^'$1' //p'
+}
+
+wmiifont="$(wi_readctl font)"
+wmiinormcol="$(wi_readctl normcolors)"
+wmiifocuscol="$(wi_readctl focuscolors)"
+
+wi_fnmenu() {
+ group="$1-$2"; shift 2
+ _last="$(echo $group|tr - _)_last"
+ eval "last=\"\$$_last\""
+ res=$(set -- $(echo "$Menus" | awk -v "s=$group" 'BEGIN{n=length(s)}
+ substr($1,1,n) == s{print substr($1,n+2)}')
+ [ $# != 0 ] && wmii9menu -i "$last" "$@")
+ if [ -n "$res" ]; then
+ eval "$_last="'"$res"'
+ Menu $group-$res "$@"
+ fi
+}
+
+wi_proglist() {
+ ls -lL $(echo $* | sed 'y/:/ /') 2>/dev/null \
+ | awk '$1 ~ /^[^d].*x/ { print $NF }' \
+ | sort | uniq
+}
+
+wi_actions() {
+ { wi_proglist $WMII_CONFPATH
+ echo -n "$Actions"
+ } | sort | uniq
+}
+
+wi_runconf() {
+ sflag=""; if [ "$1" = -s ]; then sflag=1; shift; fi
+ which="$(which which)"
+ prog=$(PATH="$WMII_CONFPATH" "$which" -- $1 2>/dev/null); shift
+ if [ -n "$prog" ]; then
+ if [ -z "$sflag" ]
+ then "$prog" "$@"
+ else . "$prog"
+ fi
+ else return 1
+ fi
+}
+
+wi_script() {
+ _noprog=true
+ if [ "$1" = -f ]; then
+ shift
+ _noprog=/dev/null
+ fi
+ which=$(which which)
+ _prog=$(PATH="$WMII_CONFPATH" $which $1 || echo $_noprog); shift
+ shift; echo "$_prog $*"
+}
+
+wi_runcmd() {
+ if [ "$1" = -t ]; then
+ shift
+ set -- wihack -tags $(wmiir read /tag/sel/ctl | sed 1q) "$*"
+ fi
+ eval exec "$*" &
+}
+
+wi_tags() {
+ wmiir ls /tag | sed 's,/,,; /^sel$/d'
+}
+
+wi_seltag() {
+ wmiir read /tag/sel/ctl | sed 1q | tr -d '\012'
+}
+
+wi_selclient() {
+ wmiir read /client/sel/ctl | sed 1q | tr -d '\012'
+}
+
+wi_eventloop() {
+ echo "$Keys" | wmiir write /keys
+
+ if [ "$1" = -i ]
+ then cat
+ else wmiir read /event
+ fi |
+ while read wi_event; do
+ IFS="$wi_newline"
+ wi_arg=$(echo "$wi_event" | sed 's/^[^ ]* //')
+ unset IFS
+ set -- $wi_event
+ event=$1; shift
+ ( Event $event "$@" )
+ done
+ true
+}
+
+action() {
+ action=$1; shift
+ if [ -n "$action" ]; then
+ set +x
+ Action $action "$@" \
+ || wi_runconf $action "$@"
+ fi
+}
+
diff --git a/cmd/wmii/Makefile b/cmd/wmii/Makefile
new file mode 100644
index 0000000..de635ca
--- /dev/null
+++ b/cmd/wmii/Makefile
@@ -0,0 +1,47 @@
+ROOT= ../..
+include $(ROOT)/mk/hdr.mk
+include $(ROOT)/mk/wmii.mk
+
+main.c: $(ROOT)/mk/wmii.mk
+
+TARG = wmii
+HFILES= dat.h fns.h
+
+PACKAGES += $(X11PACKAGES) xext xrandr xrender xinerama
+
+LIB = $(LIBIXP)
+LIBS += -lm $(LIBS9)
+
+CFLAGS += $(INCICONV) -DIXP_NEEDAPI=97
+OBJ = area \
+ bar \
+ client \
+ column \
+ div \
+ error \
+ event \
+ ewmh \
+ float \
+ frame \
+ fs \
+ geom \
+ key \
+ layout \
+ main \
+ map \
+ message \
+ mouse \
+ print \
+ root \
+ rule \
+ printevent\
+ screen \
+ _util \
+ view \
+ xdnd \
+ x11 \
+ xext \
+ ../util
+
+include $(ROOT)/mk/one.mk
+
diff --git a/cmd/wmii/_util.c b/cmd/wmii/_util.c
new file mode 100644
index 0000000..feafba9
--- /dev/null
+++ b/cmd/wmii/_util.c
@@ -0,0 +1,378 @@
+/* Copyright ©2008-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <sys/signal.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <bio.h>
+#include "fns.h"
+
+/* Blech. */
+#define VECTOR(type, nam, c) \
+void \
+vector_##c##init(Vector_##nam *v) { \
+ memset(v, 0, sizeof *v); \
+} \
+ \
+void \
+vector_##c##free(Vector_##nam *v) { \
+ free(v->ary); \
+ memset(v, 0, sizeof *v); \
+} \
+ \
+void \
+vector_##c##push(Vector_##nam *v, type val) { \
+ if(v->n == v->size) { \
+ if(v->size == 0) \
+ v->size = 2; \
+ v->size <<= 2; \
+ v->ary = erealloc(v->ary, v->size * sizeof *v->ary); \
+ } \
+ v->ary[v->n++] = val; \
+} \
+
+VECTOR(long, long, l)
+VECTOR(Rectangle, rect, r)
+VECTOR(void*, ptr, p)
+
+int
+doublefork(void) {
+ pid_t pid;
+ int status;
+
+ switch(pid=fork()) {
+ case -1:
+ fatal("Can't fork(): %r");
+ case 0:
+ switch(pid=fork()) {
+ case -1:
+ fatal("Can't fork(): %r");
+ case 0:
+ return 0;
+ default:
+ exit(0);
+ }
+ default:
+ waitpid(pid, &status, 0);
+ return pid;
+ }
+ /* NOTREACHED */
+}
+
+void
+closeexec(int fd) {
+ if(fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
+ fatal("can't set %d close on exec: %r", fd);
+}
+
+int
+spawn3(int fd[3], const char *file, char *argv[]) {
+ /* Some ideas from Russ Cox's libthread port. */
+ int p[2];
+ int pid;
+
+ if(pipe(p) < 0)
+ return -1;
+ closeexec(p[1]);
+
+ switch(pid = doublefork()) {
+ case 0:
+ dup2(fd[0], 0);
+ dup2(fd[1], 1);
+ dup2(fd[2], 2);
+
+ execvp(file, argv);
+ write(p[1], &errno, sizeof errno);
+ exit(1);
+ break;
+ default:
+ close(p[1]);
+ if(read(p[0], &errno, sizeof errno) == sizeof errno)
+ pid = -1;
+ close(p[0]);
+ break;
+ case -1: /* can't happen */
+ break;
+ }
+
+ close(fd[0]);
+ /* These could fail if any of these was also a previous fd. */
+ close(fd[1]);
+ close(fd[2]);
+ return pid;
+}
+
+int
+spawn3l(int fd[3], const char *file, ...) {
+ va_list ap;
+ char **argv;
+ int i, n;
+
+ va_start(ap, file);
+ for(n=0; va_arg(ap, char*); n++)
+ ;
+ va_end(ap);
+
+ argv = emalloc((n+1) * sizeof *argv);
+ va_start(ap, file);
+ quotefmtinstall();
+ for(i=0; i <= n; i++)
+ argv[i] = va_arg(ap, char*);
+ va_end(ap);
+
+ i = spawn3(fd, file, argv);
+ free(argv);
+ return i;
+}
+
+#ifdef __linux__
+# define PROGTXT "exe"
+#else
+# define PROGTXT "file"
+#endif
+
+static void
+_backtrace(int pid, char *btarg) {
+ char *proc, *spid, *gdbcmd;
+ int fd[3], p[2];
+ int status, cmdfd;
+
+ gdbcmd = estrdup("/tmp/gdbcmd.XXXXXX");
+ if(pipe(p) < 0)
+ goto done;
+ closeexec(p[0]);
+
+ cmdfd = mkstemp(gdbcmd);
+ if(cmdfd < 0)
+ goto done;
+
+ fprint(cmdfd, "bt %s\n", btarg);
+ fprint(cmdfd, "detach\n");
+ close(cmdfd);
+
+ fd[0] = open("/dev/null", O_RDONLY);
+ fd[1] = p[1];
+ fd[2] = dup(2);
+
+ proc = sxprint("/proc/%d/" PROGTXT, pid);
+ spid = sxprint("%d", pid);
+ if(spawn3l(fd, "gdb", "gdb", "-batch", "-x", gdbcmd, proc, spid, nil) < 0) {
+ unlink(gdbcmd);
+ goto done;
+ }
+
+ Biobuf bp;
+ char *s;
+
+ Binit(&bp, p[0], OREAD);
+ while((s = Brdstr(&bp, '\n', 1))) {
+ Dprint(DStack, "%s\n", s);
+ free(s);
+ }
+ unlink(gdbcmd);
+
+done:
+ free(gdbcmd);
+ kill(pid, SIGKILL);
+ waitpid(pid, &status, 0);
+}
+
+void
+backtrace(char *btarg) {
+ int pid;
+
+ /* Fork so we can backtrace the child. Keep this stack
+ * frame minimal, so the trace is fairly clean.
+ */
+ Debug(DStack)
+ switch(pid = fork()) {
+ case -1:
+ return;
+ case 0:
+ kill(getpid(), SIGSTOP);
+ _exit(0);
+ default:
+ _backtrace(pid, btarg);
+ break;
+ }
+
+}
+
+void
+reinit(Regex *r, char *regx) {
+
+ refree(r);
+
+ if(regx[0] != '\0') {
+ r->regex = estrdup(regx);
+ r->regc = regcomp(regx);
+ }
+}
+
+void
+refree(Regex *r) {
+
+ free(r->regex);
+ free(r->regc);
+ r->regex = nil;
+ r->regc = nil;
+}
+
+void
+uniq(char **toks) {
+ char **p, **q;
+
+ q = toks;
+ if(*q == nil)
+ return;
+ for(p=q+1; *p; p++)
+ if(strcmp(*q, *p))
+ *++q = *p;
+ *++q = nil;
+}
+
+char**
+comm(int cols, char **toka, char **tokb) {
+ Vector_ptr vec;
+ char **ret;
+ int cmp;
+
+ vector_pinit(&vec);
+ while(*toka || *tokb) {
+ if(!*toka)
+ cmp = 1;
+ else if(!*tokb)
+ cmp = -1;
+ else
+ cmp = strcmp(*toka, *tokb);
+ if(cmp < 0) {
+ if(cols & CLeft)
+ vector_ppush(&vec, *toka);
+ toka++;
+ }else if(cmp > 0) {
+ if(cols & CRight)
+ vector_ppush(&vec, *tokb);
+ tokb++;
+ }else {
+ if(cols & CCenter)
+ vector_ppush(&vec, *toka);
+ toka++;
+ tokb++;
+ }
+ }
+ vector_ppush(&vec, nil);
+ ret = strlistdup((char**)vec.ary);
+ free(vec.ary);
+ return ret;
+}
+
+void
+grep(char **list, Reprog *re, int flags) {
+ char **p, **q;
+ int res;
+
+ q = list;
+ for(p=q; *p; p++) {
+ res = 0;
+ if(re)
+ res = regexec(re, *p, nil, 0);
+ if(res && !(flags & GInvert)
+ || !res && (flags & GInvert))
+ *q++ = *p;
+ }
+ *q = nil;
+}
+
+char*
+join(char **list, char *sep) {
+ Fmt f;
+ char **p;
+
+ if(fmtstrinit(&f) < 0)
+ abort();
+
+ for(p=list; *p; p++) {
+ if(p != list)
+ fmtstrcpy(&f, sep);
+ fmtstrcpy(&f, *p);
+ }
+
+ return fmtstrflush(&f);
+}
+
+int
+strlcatprint(char *buf, int len, const char *fmt, ...) {
+ va_list ap;
+ int buflen;
+ int ret;
+
+ va_start(ap, fmt);
+ buflen = strlen(buf);
+ ret = vsnprint(buf+buflen, len-buflen, fmt, ap);
+ va_end(ap);
+ return ret;
+}
+
+char*
+pathsearch(const char *path, const char *file, bool slashok) {
+ char *orig, *p, *s;
+
+ if(!slashok && strchr(file, '/') > file)
+ file = sxprint("%s/%s", getcwd(buffer, sizeof buffer), file);
+ else if(!strncmp(file, "./", 2))
+ file = sxprint("%s/%s", getcwd(buffer, sizeof buffer), file+2);
+ if(file[0] == '/') {
+ if(access(file, X_OK))
+ return strdup(file);
+ return nil;
+ }
+
+ orig = estrdup(path ? path : getenv("PATH"));
+ for(p=orig; (s=strtok(p, ":")); p=nil) {
+ s = smprint("%s/%s", s, file);
+ if(!access(s, X_OK))
+ break;
+ free(s);
+ }
+ free(orig);
+ return s;
+}
+
+int
+unquote(char *buf, char *toks[], int ntoks) {
+ char *s, *t;
+ bool inquote;
+ int n;
+
+ n = 0;
+ s = buf;
+ while(*s && n < ntoks) {
+ while(*s && utfrune(" \t\r\n", *s))
+ s++;
+ inquote = false;
+ toks[n] = s;
+ t = s;
+ while(*s && (inquote || !utfrune(" \t\r\n", *s))) {
+ if(*s == '\'') {
+ if(inquote && s[1] == '\'')
+ *t++ = *s++;
+ else
+ inquote = !inquote;
+ }
+ else
+ *t++ = *s;
+ s++;
+ }
+ if(*s)
+ s++;
+ *t = '\0';
+ if(s != toks[n])
+ n++;
+ }
+ return n;
+}
+
diff --git a/cmd/wmii/area.c b/cmd/wmii/area.c
new file mode 100644
index 0000000..0f94e72
--- /dev/null
+++ b/cmd/wmii/area.c
@@ -0,0 +1,328 @@
+/* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include <math.h>
+#include <limits.h>
+#include "fns.h"
+
+Client*
+area_selclient(Area *a) {
+ if(a && a->sel)
+ return a->sel->client;
+ return nil;
+}
+
+int
+area_idx(Area *a) {
+ View *v;
+ Area *ap;
+ uint i;
+
+ v = a->view;
+ if(a->floating)
+ return -1;
+ i = 1;
+ for(ap=v->areas[a->screen]; a != ap; ap=ap->next)
+ i++;
+ return i;
+}
+
+static Rectangle
+area_rect(void *v) {
+ Area *a;
+
+ a = v;
+ return a->r;
+}
+
+Area*
+area_find(View *v, Rectangle r, int dir, bool wrap) {
+ static Vector_ptr vec;
+ Area *a;
+ int s;
+
+ vec.n = 0;
+ foreach_column(v, s, a)
+ vector_ppush(&vec, a);
+
+ return findthing(r, dir, &vec, area_rect, wrap);
+}
+
+int
+afmt(Fmt *f) {
+ Area *a;
+
+ a = va_arg(f->args, Area*);
+ if(a == nil)
+ return fmtstrcpy(f, "<nil>");
+ if(a->floating)
+ return fmtstrcpy(f, "~");
+ if(a->screen > 0 || (f->flags & FmtSharp))
+ return fmtprint(f, "%d:%d", a->screen, area_idx(a));
+ return fmtprint(f, "%d", area_idx(a));
+}
+
+char*
+area_name(Area *a) {
+
+ if(a == nil)
+ return "<nil>";
+ if(a->floating)
+ return "~";
+ return sxprint("%d", area_idx(a));
+}
+
+Area*
+area_create(View *v, Area *pos, int scrn, uint width) {
+ static ushort id = 1;
+ int i, j;
+ uint minwidth, index;
+ int numcols;
+ Area *a;
+
+ assert(!pos || pos->screen == scrn);
+ SET(index);
+ if(v->areas) { /* Creating a column. */
+ minwidth = column_minwidth();
+ index = pos ? area_idx(pos) : 1;
+ numcols = 0;
+ for(a=v->areas[scrn]; a; a=a->next)
+ numcols++;
+
+ /* TODO: Need a better sizing/placing algorithm.
+ */
+ if(width == 0) {
+ if(numcols >= 0) {
+ width = view_newcolwidth(v, index);
+ if (width == 0)
+ width = Dx(v->r[scrn]) / (numcols + 1);
+ }
+ else
+ width = Dx(v->r[scrn]);
+ }
+
+ if(width < minwidth)
+ width = minwidth;
+ minwidth = numcols * minwidth + minwidth;
+ if(minwidth > Dx(v->r[scrn]))
+ return nil;
+
+ i = minwidth - Dx(v->pad[scrn]) - Dx(v->r[scrn]);
+ if(i > 0 && Dx(v->pad[scrn])) {
+ j = min(i/2, v->pad[scrn].min.x);
+ v->pad[scrn].min.x -= j;
+ v->pad[scrn].max.x += i - j;
+ }
+
+ view_scale(v, scrn, Dx(v->r[scrn]) - width);
+ }
+
+ a = emallocz(sizeof *a);
+ a->view = v;
+ a->screen = scrn;
+ a->id = id++;
+ a->floating = !v->floating;
+ if(a->floating)
+ a->mode = Coldefault;
+ else
+ a->mode = def.colmode;
+ a->frame = nil;
+ a->sel = nil;
+
+ a->r = v->r[scrn];
+ a->r.min.x = 0;
+ a->r.max.x = width;
+
+ if(a->floating) {
+ v->floating = a;
+ a->screen = -1;
+ }
+ else if(pos) {
+ a->next = pos->next;
+ a->prev = pos;
+ }
+ else {
+ a->next = v->areas[scrn];
+ v->areas[scrn] = a;
+ }
+ if(a->prev)
+ a->prev->next = a;
+ if(a->next)
+ a->next->prev = a;
+
+ if(v->sel == nil && !a->floating)
+ area_focus(a);
+
+ if(!a->floating)
+ event("CreateColumn %ud\n", index);
+ return a;
+}
+
+void
+area_destroy(Area *a) {
+ Area *newfocus;
+ View *v;
+ int idx;
+
+ v = a->view;
+
+ if(a->frame)
+ die("destroying non-empty area");
+
+ if(v->revert == a)
+ v->revert = nil;
+ if(v->oldsel == a)
+ v->oldsel = nil;
+
+ idx = area_idx(a);
+
+ if(a->prev && !a->prev->floating)
+ newfocus = a->prev;
+ else
+ newfocus = a->next;
+
+ /* Can only destroy the floating area when destroying a
+ * view---after destroying all columns.
+ */
+ assert(!a->floating || !v->areas[0]);
+ if(a->prev)
+ a->prev->next = a->next;
+ else if(!a->floating)
+ v->areas[a->screen] = a->next;
+ else
+ v->floating = nil;
+ if(a->next)
+ a->next->prev = a->prev;
+
+ if(newfocus && v->sel == a)
+ area_focus(newfocus);
+
+ view_arrange(v);
+ event("DestroyArea %d\n", idx);
+
+ free(a);
+}
+
+void
+area_moveto(Area *to, Frame *f) {
+ Area *from;
+
+ assert(to->view == f->view);
+
+ if(f->client->fullscreen >= 0 && !to->floating)
+ return;
+
+ from = f->area;
+ if (from == to)
+ return;
+
+ area_detach(f);
+
+ /* Temporary kludge. */
+ if(!to->floating
+ && to->floating != from->floating
+ && !eqrect(f->colr, ZR))
+ column_attachrect(to, f, f->colr);
+ else
+ area_attach(to, f);
+}
+
+void
+area_setsel(Area *a, Frame *f) {
+ View *v;
+
+ v = a->view;
+ /* XXX: Stack. */
+ for(; f && f->collapsed && f->anext; f=f->anext)
+ ;
+ for(; f && f->collapsed && f->aprev; f=f->aprev)
+ ;
+
+ if(a == v->sel && f)
+ frame_focus(f);
+ else
+ a->sel = f;
+}
+
+void
+area_attach(Area *a, Frame *f) {
+
+ f->area = a;
+ if(a->floating)
+ float_attach(a, f);
+ else
+ column_attach(a, f);
+
+ view_arrange(a->view);
+
+ if(btassert("4 full", a->frame && a->sel == nil))
+ a->sel = a->frame;
+}
+
+void
+area_detach(Frame *f) {
+ View *v;
+ Area *a;
+
+ a = f->area;
+ v = a->view;
+
+ if(a->floating)
+ float_detach(f);
+ else
+ column_detach(f);
+
+ if(v->sel->sel == nil && v->floating->sel)
+ v->sel = v->floating;
+
+ view_arrange(v);
+}
+
+void
+area_focus(Area *a) {
+ Frame *f;
+ View *v;
+ Area *old_a;
+
+ v = a->view;
+ f = a->sel;
+ old_a = v->sel;
+
+ if(!a->floating && view_fullscreen_p(v, a->screen))
+ return;
+
+ v->sel = a;
+ if(!a->floating) {
+ v->selcol = area_idx(a);
+ v->selscreen = a->screen;
+ }
+ if(a != old_a)
+ v->oldsel = nil;
+
+ if((old_a) && (a->floating != old_a->floating)) {
+ v->revert = old_a;
+ if(v->floating->max)
+ view_update(v);
+ }
+
+ if(v != selview)
+ return;
+
+ move_focus(old_a->sel, f);
+
+ if(f)
+ client_focus(f->client);
+ else
+ client_focus(nil);
+
+ if(a != old_a) {
+ event("AreaFocus %a\n", a);
+ /* Deprecated */
+ if(a->floating)
+ event("FocusFloating\n");
+ else
+ event("ColumnFocus %d\n", area_idx(a));
+ }
+}
+
diff --git a/cmd/wmii/bar.c b/cmd/wmii/bar.c
new file mode 100644
index 0000000..fd4ba26
--- /dev/null
+++ b/cmd/wmii/bar.c
@@ -0,0 +1,300 @@
+/* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include "fns.h"
+
+static Handlers handlers;
+
+#define foreach_bar(s, b) \
+ for(int __bar_n=0; __bar_n < nelem((s)->bar); __bar_n++) \
+ for((b)=(s)->bar[__bar_n]; (b); (b)=(b)->next)
+
+void
+bar_init(WMScreen *s) {
+ WinAttr wa;
+
+ if(s->barwin) {
+ bar_resize(s);
+ return;
+ }
+
+ s->brect = s->r;
+ s->brect.min.y = s->brect.max.y - labelh(def.font);
+
+ wa.override_redirect = 1;
+ wa.background_pixmap = ParentRelative;
+ wa.event_mask = ExposureMask
+ | ButtonPressMask
+ | ButtonReleaseMask
+ | FocusChangeMask;
+ s->barwin = createwindow(&scr.root, s->brect, scr.depth, InputOutput,
+ &wa, CWOverrideRedirect
+ | CWBackPixmap
+ | CWEventMask);
+ s->barwin->aux = s;
+ xdnd_initwindow(s->barwin);
+ sethandler(s->barwin, &handlers);
+ if(s == screens[0])
+ mapwin(s->barwin);
+}
+
+void
+bar_resize(WMScreen *s) {
+
+ s->brect = s->r;
+ s->brect.min.y = s->r.max.y - labelh(def.font);
+ if(s == screens[0])
+ reshapewin(s->barwin, s->brect);
+ else
+ s->brect.min.y = s->r.max.y;
+ /* FIXME: view_arrange. */
+}
+
+void
+bar_setbounds(WMScreen *s, int left, int right) {
+ Rectangle *r;
+
+ if(s != screens[0])
+ return;
+
+ r = &s->brect;
+ r->min.x = left;
+ r->max.x = right;
+ if(Dy(*r))
+ reshapewin(s->barwin, *r);
+}
+
+void
+bar_sety(WMScreen *s, int y) {
+ Rectangle *r;
+ int dy;
+
+ r = &s->brect;
+
+ dy = Dy(*r);
+ r->min.y = y;
+ r->max.y = y + dy;
+ if(Dy(*r))
+ reshapewin(s->barwin, *r);
+}
+
+Bar*
+bar_create(Bar **bp, const char *name) {
+ static uint id = 1;
+ WMScreen *s, **sp;
+ Bar *b;
+ uint i;
+
+ b = bar_find(*bp, name);;
+ if(b)
+ return b;
+
+ b = emallocz(sizeof *b);
+ b->id = id++;
+ utflcpy(b->name, name, sizeof b->name);
+ b->col = def.normcolor;
+
+ strlcat(b->buf, b->col.colstr, sizeof(b->buf));
+ strlcat(b->buf, " ", sizeof(b->buf));
+ strlcat(b->buf, b->text, sizeof(b->buf));
+
+ SET(i);
+ for(sp=screens; (s = *sp); sp++) {
+ i = bp - s->bar;
+ if(i < nelem(s->bar))
+ break;
+ }
+ b->bar = i;
+ b->screen = s;
+
+ for(; *bp; bp = &bp[0]->next)
+ if(strcmp(bp[0]->name, name) >= 0)
+ break;
+ b->next = *bp;
+ *bp = b;
+
+ return b;
+}
+
+void
+bar_destroy(Bar **bp, Bar *b) {
+ Bar **p;
+
+ for(p = bp; *p; p = &p[0]->next)
+ if(*p == b) break;
+ *p = b->next;
+ free(b);
+}
+
+void
+bar_draw(WMScreen *s) {
+ Bar *b, *tb, *largest, **pb;
+ Rectangle r;
+ Align align;
+ uint width, tw;
+ float shrink;
+
+ /* To do: Generalize this. */
+
+ largest = nil;
+ width = 0;
+ foreach_bar(s, b) {
+ b->r.min = ZP;
+ b->r.max.y = Dy(s->brect);
+ b->r.max.x = (def.font->height & ~1) + def.font->pad.min.x + def.font->pad.max.x;
+ if(b->text && strlen(b->text))
+ b->r.max.x += textwidth(def.font, b->text);
+ width += Dx(b->r);
+ }
+
+ if(width > Dx(s->brect)) { /* Not enough room. Shrink bars until they all fit. */
+ foreach_bar(s, b) {
+ for(pb=&largest; *pb; pb=&pb[0]->smaller)
+ if(Dx(pb[0]->r) < Dx(b->r))
+ break;
+ b->smaller = *pb;
+ *pb = b;
+ }
+ SET(shrink);
+ tw = 0;
+ for(tb=largest; tb; tb=tb->smaller) {
+ width -= Dx(tb->r);
+ tw += Dx(tb->r);
+ shrink = (Dx(s->brect) - width) / (float)tw;
+ if(tb->smaller && Dx(tb->r) * shrink < Dx(tb->smaller->r))
+ continue;
+ if(width + (int)(tw * shrink) <= Dx(s->brect))
+ break;
+ }
+ if(tb)
+ for(b=largest; b != tb->smaller; b=b->smaller)
+ b->r.max.x *= shrink;
+ width += tw * shrink;
+ }
+
+ tb = nil;
+ foreach_bar(s, b) {
+ if(tb)
+ b->r = rectaddpt(b->r, Pt(tb->r.max.x, 0));
+ if(b == s->bar[BRight])
+ b->r.max.x += Dx(s->brect) - width;
+ tb = b;
+ }
+
+ r = rectsubpt(s->brect, s->brect.min);
+ fill(disp.ibuf, r, def.normcolor.bg);
+ border(disp.ibuf, r, 1, def.normcolor.border);
+ foreach_bar(s, b) {
+ align = Center;
+ if(b == s->bar[BRight])
+ align = East;
+ fill(disp.ibuf, b->r, b->col.bg);
+ drawstring(disp.ibuf, def.font, b->r, align, b->text, b->col.fg);
+ border(disp.ibuf, b->r, 1, b->col.border);
+ }
+ copyimage(s->barwin, r, disp.ibuf, ZP);
+}
+
+void
+bar_load(Bar *b) {
+ IxpMsg m;
+ char *p, *q;
+
+ p = b->buf;
+ m = ixp_message(p, strlen(p), 0);
+ msg_parsecolors(&m, &b->col);
+
+ q = (char*)m.end-1;
+ while(q >= (char*)m.pos && *q == '\n')
+ *q-- = '\0';
+
+ q = b->text;
+ utflcpy(q, (char*)m.pos, sizeof b->text);
+
+ p[0] = '\0';
+ strlcat(p, b->col.colstr, sizeof b->buf);
+ strlcat(p, " ", sizeof b->buf);
+ strlcat(p, b->text, sizeof b->buf);
+
+ bar_draw(b->screen);
+}
+
+Bar*
+bar_find(Bar *bp, const char *name) {
+ Bar *b;
+
+ for(b=bp; b; b=b->next)
+ if(!strcmp(b->name, name))
+ break;
+ return b;
+}
+
+static char *barside[] = {
+ [BLeft] = "Left",
+ [BRight] = "Right",
+};
+
+static Bar*
+findbar(WMScreen *s, Point p) {
+ Bar *b;
+
+ foreach_bar(s, b)
+ if(rect_haspoint_p(p, b->r))
+ return b;
+ return nil;
+}
+
+static void
+bdown_event(Window *w, XButtonPressedEvent *e) {
+ WMScreen *s;
+ Bar *b;
+
+ /* Ungrab so a menu can receive events before the button is released */
+ XUngrabPointer(display, e->time);
+ sync();
+
+ s = w->aux;
+ b = findbar(s, Pt(e->x, e->y));
+ if(b)
+ event("%sBarMouseDown %d %s\n", barside[b->bar], e->button, b->name);
+}
+
+static void
+bup_event(Window *w, XButtonPressedEvent *e) {
+ WMScreen *s;
+ Bar *b;
+
+ s = w->aux;
+ b = findbar(s, Pt(e->x, e->y));
+ if(b)
+ event("%sBarClick %d %s\n", barside[b->bar], e->button, b->name);
+}
+
+static Rectangle
+dndmotion_event(Window *w, Point p) {
+ WMScreen *s;
+ Bar *b;
+
+ s = w->aux;
+ b = findbar(s, p);
+ if(b) {
+ event("%sBarDND 1 %s\n", barside[b->bar], b->name);
+ return b->r;
+ }
+ return ZR;
+}
+
+static void
+expose_event(Window *w, XExposeEvent *e) {
+ USED(w, e);
+ bar_draw(w->aux);
+}
+
+static Handlers handlers = {
+ .bdown = bdown_event,
+ .bup = bup_event,
+ .dndmotion = dndmotion_event,
+ .expose = expose_event,
+};
+
diff --git a/cmd/wmii/client.c b/cmd/wmii/client.c
new file mode 100644
index 0000000..5f82455
--- /dev/null
+++ b/cmd/wmii/client.c
@@ -0,0 +1,1212 @@
+/* Copyright ©2004-2006 Anselm R. Garbe <garbeam at gmail dot com>
+ * Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include <ctype.h>
+#include <strings.h>
+#include <X11/Xatom.h>
+#include "fns.h"
+
+#define Mbsearch(k, l, cmp) bsearch(k, l, nelem(l), sizeof(*l), cmp)
+
+static Handlers handlers;
+
+enum {
+ ClientMask = StructureNotifyMask
+ | PropertyChangeMask
+ | EnterWindowMask
+ | FocusChangeMask,
+ ButtonMask = ButtonPressMask
+ | ButtonReleaseMask,
+};
+
+static Group* group;
+
+static void
+group_init(Client *c) {
+ Group *g;
+ long *ret;
+ XWindow w;
+ long n;
+
+ w = c->w.hints->group;
+ if(w == 0) {
+ /* Not quite ICCCM compliant, but it seems to work. */
+ n = getprop_long(&c->w, "WM_CLIENT_LEADER", "WINDOW", 0L, &ret, 1L);
+ if(n == 0)
+ return;
+ w = *ret;
+ free(ret);
+ }
+
+ for(g=group; g; g=g->next)
+ if(g->leader == w)
+ break;
+ if(g == nil) {
+ g = emallocz(sizeof *g);
+ g->leader = w;
+ g->next = group;
+ group = g;
+ }
+ c->group = g;
+ g->ref++;
+}
+
+static void
+group_remove(Client *c) {
+ Group **gp;
+ Group *g;
+
+ g = c->group;
+ if(g == nil)
+ return;
+ if(g->client == c)
+ g->client = nil;
+ g->ref--;
+ if(g->ref == 0) {
+ for(gp=&group; *gp; gp=&gp[0]->next)
+ if(*gp == g) break;
+ assert(*gp == g);
+ gp[0] = gp[0]->next;
+ free(g);
+ }
+}
+
+Client*
+group_leader(Group *g) {
+ Client *c;
+
+ c = win2client(g->leader);
+ if(c)
+ return c;
+ if(g->client)
+ return g->client;
+ /* Could do better. */
+ for(c=client; c; c=c->next)
+ if(c->frame && c->group == g)
+ break;
+ return c;
+}
+
+Client*
+client_create(XWindow w, XWindowAttributes *wa) {
+ Client **t, *c;
+ WinAttr fwa;
+ Point p;
+ Visual *vis;
+ int depth;
+
+ c = emallocz(sizeof *c);
+ c->fullscreen = -1;
+ c->border = wa->border_width;
+
+ c->r.min = Pt(wa->x, wa->y);
+ c->r.max = addpt(c->r.min,
+ Pt(wa->width, wa->height));
+
+ c->w.type = WWindow;
+ c->w.xid = w;
+ c->w.r = c->r;
+
+ depth = scr.depth;
+ vis = scr.visual;
+ /* XXX: Multihead. */
+ c->ibuf = &ibuf;
+ if(render_argb_p(wa->visual)) {
+ depth = 32;
+ vis = render_visual;
+ c->ibuf = &ibuf32;
+ }
+
+ client_prop(c, xatom("WM_PROTOCOLS"));
+ client_prop(c, xatom("WM_TRANSIENT_FOR"));
+ client_prop(c, xatom("WM_NORMAL_HINTS"));
+ client_prop(c, xatom("WM_HINTS"));
+ client_prop(c, xatom("WM_CLASS"));
+ client_prop(c, xatom("WM_NAME"));
+ client_prop(c, xatom("_MOTIF_WM_HINTS"));
+
+ XSetWindowBorderWidth(display, w, 0);
+ XAddToSaveSet(display, w);
+
+ fwa.background_pixmap = None;
+ fwa.bit_gravity = NorthWestGravity;
+ fwa.border_pixel = 0;
+ fwa.colormap = XCreateColormap(display, scr.root.xid, vis, AllocNone);
+ fwa.event_mask = SubstructureRedirectMask
+ | SubstructureNotifyMask
+ | StructureNotifyMask
+ | ExposureMask
+ | EnterWindowMask
+ | PointerMotionMask
+ | ButtonPressMask
+ | ButtonReleaseMask;
+ fwa.override_redirect = true;
+ c->framewin = createwindow_visual(&scr.root, c->r,
+ depth, vis, InputOutput,
+ &fwa, CWBackPixmap
+ | CWBitGravity
+ /* These next two matter for ARGB windows. Donno why. */
+ | CWBorderPixel
+ | CWColormap
+ | CWEventMask
+ | CWOverrideRedirect);
+ XFreeColormap(display, fwa.colormap);
+
+ c->framewin->aux = c;
+ c->w.aux = c;
+ sethandler(c->framewin, &framehandler);
+ sethandler(&c->w, &handlers);
+
+ selectinput(&c->w, ClientMask);
+
+ p.x = def.border;
+ p.y = labelh(def.font);
+
+ group_init(c);
+
+ grab_button(c->framewin->xid, AnyButton, AnyModifier);
+
+ for(t=&client ;; t=&t[0]->next)
+ if(!*t) {
+ c->next = *t;
+ *t = c;
+ break;
+ }
+
+
+ /*
+ * It's actually possible for a window to be destroyed
+ * before we get a chance to reparent it. Check for that
+ * now, because otherwise we'll wind up mapping a
+ * perceptibly empty frame before it's destroyed.
+ */
+ traperrors(true);
+ reparentwindow(&c->w, c->framewin, p);
+ if(traperrors(false)) {
+ client_destroy(c);
+ return nil;
+ }
+
+ ewmh_initclient(c);
+
+ event("CreateClient %C\n", c);
+ client_manage(c);
+ return c;
+}
+
+static bool
+apply_rules(Client *c) {
+ Rule *r;
+
+ if(def.tagrules.string)
+ for(r=def.tagrules.rule; r; r=r->next)
+ if(regexec(r->regex, c->props, nil, 0))
+ return client_applytags(c, r->value);
+ return false;
+}
+
+void
+client_manage(Client *c) {
+ Client *leader;
+ Frame *f;
+ char *tags;
+ bool rules;
+
+ if(Dx(c->r) == Dx(screen->r))
+ if(Dy(c->r) == Dy(screen->r))
+ if(c->w.ewmh.type == 0)
+ fullscreen(c, true, -1);
+
+ tags = getprop_string(&c->w, "_WMII_TAGS");
+ rules = apply_rules(c);
+
+ leader = win2client(c->trans);
+ if(leader == nil && c->group)
+ leader = group_leader(c->group);
+
+ if(tags) // && (!leader || leader == c || starting))
+ utflcpy(c->tags, tags, sizeof c->tags);
+ else if(leader && !rules)
+ utflcpy(c->tags, leader->tags, sizeof c->tags);
+ free(tags);
+
+ if(c->tags[0])
+ client_applytags(c, c->tags);
+ else
+ client_applytags(c, "sel");
+
+ if(!starting)
+ view_update_all();
+
+ bool newgroup = !c->group
+ || c->group->ref == 1
+ || selclient() && (selclient()->group == c->group)
+ || group_leader(c->group)
+ && !client_viewframe(group_leader(c->group),
+ c->sel->view);
+
+ f = c->sel;
+ if(!(c->w.ewmh.type & TypeSplash))
+ if(newgroup) {
+ /* XXX: Look over this.
+ if(f->area != f->view->sel)
+ f->view->oldsel = f->view->sel;
+ */
+ }else {
+ frame_restack(c->sel, c->sel->area->sel);
+ view_restack(c->sel->view);
+ }
+}
+
+void
+client_destroy(Client *c) {
+ Rectangle r;
+ char *none;
+ Client **tc;
+ bool hide;
+
+ unmapwin(c->framewin);
+ client_seturgent(c, false, UrgClient);
+
+ for(tc=&client; *tc; tc=&tc[0]->next)
+ if(*tc == c) {
+ *tc = c->next;
+ break;
+ }
+
+ r = client_grav(c, ZR);
+
+ hide = false;
+ if(!c->sel || c->sel->view != selview)
+ hide = true;
+
+ XGrabServer(display);
+
+ /* In case the client is already destroyed. */
+ traperrors(true);
+
+ sethandler(&c->w, nil);
+ if(hide)
+ reparentwindow(&c->w, &scr.root, screen->r.max);
+ else
+ reparentwindow(&c->w, &scr.root, r.min);
+
+ if(starting > -1)
+ XRemoveFromSaveSet(display, c->w.xid);
+
+ traperrors(false);
+ XUngrabServer(display);
+
+ none = nil;
+ client_setviews(c, &none);
+ if(starting > -1)
+ client_unmap(c, WithdrawnState);
+ refree(&c->tagre);
+ refree(&c->tagvre);
+ free(c->retags);
+
+ destroywindow(c->framewin);
+
+ ewmh_destroyclient(c);
+ group_remove(c);
+ if(starting > -1)
+ event("DestroyClient %C\n", c);
+
+ flushevents(FocusChangeMask, true);
+ free(c->w.hints);
+ free(c);
+}
+
+/* Convenience functions */
+Frame*
+client_viewframe(Client *c, View *v) {
+ Frame *f;
+
+ for(f=c->frame; f; f=f->cnext)
+ if(f->view == v)
+ break;
+ return f;
+}
+
+Client*
+selclient(void) {
+ if(selview->sel->sel)
+ return selview->sel->sel->client;
+ return nil;
+}
+
+Client*
+win2client(XWindow w) {
+ Client *c;
+ for(c=client; c; c=c->next)
+ if(c->w.xid == w) break;
+ return c;
+}
+
+int
+Cfmt(Fmt *f) {
+ Client *c;
+
+ c = va_arg(f->args, Client*);
+ if(c)
+ return fmtprint(f, "%W", &c->w);
+ return fmtprint(f, "<nil>");
+}
+
+char*
+clientname(Client *c) {
+ if(c)
+ return c->name;
+ return "<nil>";
+}
+
+Rectangle
+client_grav(Client *c, Rectangle rd) {
+ Rectangle r, cr;
+ Point sp;
+ WinHints *h;
+
+ h = c->w.hints;
+
+ if(eqrect(rd, ZR)) {
+ if(c->sel) {
+ r = c->sel->floatr;
+ cr = frame_rect2client(c, r, true);
+ }else {
+ cr = c->r;
+ r = frame_client2rect(c, cr, true);
+ r = rectsetorigin(r, cr.min);
+ }
+ sp = subpt(cr.min, r.min);
+ r = gravitate(r, cr, h->grav);
+ if(!h->gravstatic)
+ r = rectsubpt(r, sp);
+ return frame_rect2client(c, r, true);
+ }else {
+ r = frame_client2rect(c, rd, true);
+ sp = subpt(rd.min, r.min);
+ r = gravitate(rd, r, h->grav);
+ if(!h->gravstatic)
+ r = rectaddpt(r, sp);
+ return frame_client2rect(c, r, true);
+ }
+}
+
+bool
+client_floats_p(Client *c) {
+ return c->trans
+ || c->floating
+ || c->fixedsize
+ || c->titleless
+ || c->borderless
+ || c->fullscreen >= 0
+ || (c->w.ewmh.type & (TypeDialog|TypeSplash|TypeDock));
+}
+
+Frame*
+client_groupframe(Client *c, View *v) {
+ if(c->group && c->group->client)
+ return client_viewframe(c->group->client, v);
+ return nil;
+}
+
+Rectangle
+frame_hints(Frame *f, Rectangle r, Align sticky) {
+ Rectangle or;
+ WinHints h;
+ Point p;
+ Client *c;
+
+ c = f->client;
+ if(c->w.hints == nil)
+ return r;
+
+ or = r;
+ h = frame_gethints(f);
+ r = sizehint(&h, r);
+
+ if(!f->area->floating) {
+ /* Not allowed to grow */
+ if(Dx(r) > Dx(or))
+ r.max.x = r.min.x+Dx(or);
+ if(Dy(r) > Dy(or))
+ r.max.y = r.min.y+Dy(or);
+ }
+
+ p = ZP;
+ if((sticky&(East|West)) == East)
+ p.x = Dx(or) - Dx(r);
+ if((sticky&(North|South)) == South)
+ p.y = Dy(or) - Dy(r);
+ return rectaddpt(r, p);
+}
+
+static void
+client_setstate(Client * c, int state) {
+ long data[] = { state, None };
+
+ changeprop_long(&c->w, "WM_STATE", "WM_STATE", data, nelem(data));
+}
+
+void
+client_map(Client *c) {
+ if(!c->w.mapped) {
+ mapwin(&c->w);
+ client_setstate(c, NormalState);
+ }
+}
+
+void
+client_unmap(Client *c, int state) {
+ if(c->w.mapped)
+ unmapwin(&c->w);
+ client_setstate(c, state);
+}
+
+int
+map_frame(Client *c) {
+ return mapwin(c->framewin);
+}
+
+int
+unmap_frame(Client *c) {
+ return unmapwin(c->framewin);
+}
+
+void
+focus(Client *c, bool user) {
+ View *v;
+ Frame *f;
+
+ USED(user);
+ f = c->sel;
+ if(!f)
+ return;
+ /*
+ if(!user && c->noinput)
+ return;
+ */
+
+ v = f->view;
+ if(v != selview)
+ view_focus(screen, v);
+ frame_focus(c->sel);
+}
+
+void
+client_focus(Client *c) {
+ /* Round trip. */
+
+ if(c && c->group)
+ c->group->client = c;
+
+ sync();
+ flushevents(FocusChangeMask, true);
+
+ Dprint(DFocus, "client_focus([%C]%s)\n", c, clientname(c));
+ Dprint(DFocus, "\t[%C]%s\n\t=> [%C]%s\n",
+ disp.focus, clientname(disp.focus),
+ c, clientname(c));
+ if(disp.focus != c) {
+ if(c) {
+ if(!c->noinput)
+ setfocus(&c->w, RevertToParent);
+ else if(c->proto & ProtoTakeFocus) {
+ xtime_kludge();
+ client_message(c, "WM_TAKE_FOCUS", 0);
+ }
+ }else
+ setfocus(screen->barwin, RevertToParent);
+ event("ClientFocus %C\n", c);
+
+ sync();
+ flushevents(FocusChangeMask, true);
+ }
+}
+
+void
+client_resize(Client *c, Rectangle r) {
+ Frame *f;
+
+ f = c->sel;
+ frame_resize(f, r);
+
+ if(f->view != selview) {
+ client_unmap(c, IconicState);
+ unmap_frame(c);
+ return;
+ }
+
+ c->r = rectaddpt(f->crect, f->r.min);
+
+ if(f->collapsed) {
+ if(f->area->max && !resizing)
+ unmap_frame(c);
+ else {
+ reshapewin(c->framewin, f->r);
+ movewin(&c->w, f->crect.min);
+ map_frame(c);
+ }
+ client_unmap(c, IconicState);
+ }else {
+ client_map(c);
+ reshapewin(c->framewin, f->r);
+ reshapewin(&c->w, f->crect);
+ map_frame(c);
+ client_configure(c);
+ ewmh_framesize(c);
+ }
+}
+
+void
+client_setcursor(Client *c, Cursor cur) {
+ WinAttr wa;
+
+ if(c->cursor != cur) {
+ c->cursor = cur;
+ wa.cursor = cur;
+ setwinattr(c->framewin, &wa, CWCursor);
+ }
+}
+
+void
+client_configure(Client *c) {
+ XConfigureEvent e;
+ Rectangle r;
+
+ r = rectsubpt(c->r, Pt(c->border, c->border));
+
+ e.type = ConfigureNotify;
+ e.event = c->w.xid;
+ e.window = c->w.xid;
+ e.above = None;
+ e.override_redirect = false;
+
+ e.x = r.min.x;
+ e.y = r.min.y;
+ e.width = Dx(r);
+ e.height = Dy(r);
+ e.border_width = c->border;
+
+ sendevent(&c->w, false, StructureNotifyMask, (XEvent*)&e);
+}
+
+void
+client_message(Client *c, char *msg, long l2) {
+ sendmessage(&c->w, "WM_PROTOCOLS", xatom(msg), xtime, l2, 0, 0);
+}
+
+void
+client_kill(Client *c, bool nice) {
+ if(nice && (c->proto & ProtoDelete)) {
+ client_message(c, "WM_DELETE_WINDOW", 0);
+ ewmh_pingclient(c);
+ }else
+ XKillClient(display, c->w.xid);
+}
+
+void
+fullscreen(Client *c, int fullscreen, long screen) {
+ Client *leader;
+ Frame *f;
+ bool wassel;
+
+ if(fullscreen == Toggle)
+ fullscreen = (c->fullscreen >= 0) ^ On;
+ if(fullscreen == (c->fullscreen >= 0))
+ return;
+
+ event("Fullscreen %C %s\n", c, (fullscreen ? "on" : "off"));
+ ewmh_updatestate(c);
+
+ c->fullscreen = -1;
+ if(!fullscreen)
+ for(f=c->frame; f; f=f->cnext) {
+ if(f->oldarea == 0) {
+ frame_resize(f, f->floatr);
+ if(f->view == selview) /* FIXME */
+ client_resize(f->client, f->r);
+
+ }
+ else if(f->oldarea > 0) {
+ wassel = (f == f->area->sel);
+ area_moveto(view_findarea(f->view, f->oldscreen, f->oldarea, true),
+ f);
+ if(wassel)
+ frame_focus(f);
+ }
+ }
+ else {
+ c->fullscreen = 0;
+ if(screen >= 0)
+ c->fullscreen = screen;
+ else if(c->sel)
+ c->fullscreen = ownerscreen(c->r);
+ else if(c->group && (leader = group_leader(c->group)) && leader->sel)
+ c->fullscreen = ownerscreen(leader->r);
+ else if(selclient())
+ c->fullscreen = ownerscreen(selclient()->r);
+
+ for(f=c->frame; f; f=f->cnext)
+ f->oldarea = -1;
+ if((f = c->sel))
+ view_update(f->view);
+ }
+}
+
+void
+client_seturgent(Client *c, int urgent, int from) {
+ XWMHints *wmh;
+ char *cfrom, *cnot;
+ Frame *f, *ff;
+ Area *a;
+ int s;
+
+ if(urgent == Toggle)
+ urgent = c->urgent ^ On;
+
+ cfrom = (from == UrgManager ? "Manager" : "Client");
+ cnot = (urgent ? "" : "Not");
+
+ if(urgent != c->urgent) {
+ event("%sUrgent %C %s\n", cnot, c, cfrom);
+ c->urgent = urgent;
+ ewmh_updatestate(c);
+ if(c->sel) {
+ if(c->sel->view == selview)
+ frame_draw(c->sel);
+ for(f=c->frame; f; f=f->cnext) {
+ SET(ff);
+ if(!urgent)
+ foreach_frame(f->view, s, a, ff)
+ if(ff->client->urgent) break;
+ if(urgent || ff == nil)
+ event("%sUrgentTag %s %s\n",
+ cnot, cfrom, f->view->name);
+ }
+ }
+ }
+
+ if(from == UrgManager) {
+ wmh = XGetWMHints(display, c->w.xid);
+ if(wmh == nil)
+ wmh = emallocz(sizeof *wmh);
+
+ wmh->flags &= ~XUrgencyHint;
+ if(urgent)
+ wmh->flags |= XUrgencyHint;
+ XSetWMHints(display, c->w.xid, wmh);
+ XFree(wmh);
+ }
+}
+
+/* X11 stuff */
+void
+update_class(Client *c) {
+ char *str;
+
+ str = utfrune(c->props, L':');
+ if(str)
+ str = utfrune(str+1, L':');
+ if(str == nil) {
+ strcpy(c->props, "::");
+ str = c->props + 1;
+ }
+ utflcpy(str+1, c->name, sizeof c->props);
+}
+
+static void
+client_updatename(Client *c) {
+ char *str;
+
+ c->name[0] = '\0';
+
+ str = getprop_string(&c->w, "_NET_WM_NAME");
+ if(str == nil)
+ str = getprop_string(&c->w, "WM_NAME");
+ if(str)
+ utflcpy(c->name, str, sizeof c->name);
+ free(str);
+
+ update_class(c);
+ if(c->sel)
+ frame_draw(c->sel);
+}
+
+static void
+updatemwm(Client *c) {
+ enum {
+ All = 0x1,
+ Border = 0x2,
+ Title = 0x8,
+ FlagDecor = 0x2,
+ Flags = 0,
+ Decor = 2,
+ };
+ Rectangle r;
+ ulong *ret;
+ int n;
+
+ /* To quote Metacity, or KWin quoting Metacity:
+ *
+ * We support MWM hints deemed non-stupid
+ *
+ * Our definition of non-stupid is a bit less lenient than
+ * theirs, though. In fact, we don't really even support the
+ * idea of supporting the hints that we support, but apps
+ * like xmms (which no one should use) break if we don't.
+ */
+
+ n = getprop_ulong(&c->w, "_MOTIF_WM_HINTS", "_MOTIF_WM_HINTS",
+ 0L, &ret, 3L);
+
+ /* FIXME: Should somehow handle all frames of a client. */
+ if(c->sel)
+ r = client_grav(c, ZR);
+
+ c->borderless = 0;
+ c->titleless = 0;
+ if(n >= 3 && (ret[Flags] & FlagDecor)) {
+ if(ret[Decor] & All)
+ ret[Decor] ^= ~0;
+ c->borderless = !(ret[Decor] & Border);
+ c->titleless = !(ret[Decor] & Title);
+ }
+ free(ret);
+
+ if(c->sel && false) {
+ c->sel->floatr = client_grav(c, r);
+ if(c->sel->area->floating) {
+ client_resize(c, c->sel->floatr);
+ frame_draw(c->sel);
+ }
+ }
+}
+
+void
+client_prop(Client *c, Atom a) {
+ WinHints h;
+ XWMHints *wmh;
+ char **class;
+ int n;
+
+ if(a == xatom("WM_PROTOCOLS"))
+ c->proto = ewmh_protocols(&c->w);
+ else
+ if(a == xatom("_NET_WM_NAME"))
+ goto wmname;
+ else
+ if(a == xatom("_MOTIF_WM_HINTS"))
+ updatemwm(c);
+ else
+ switch (a) {
+ default:
+ ewmh_prop(c, a);
+ break;
+ case XA_WM_TRANSIENT_FOR:
+ XGetTransientForHint(display, c->w.xid, &c->trans);
+ break;
+ case XA_WM_NORMAL_HINTS:
+ memset(&h, 0, sizeof h);
+ if(c->w.hints)
+ bcopy(c->w.hints, &h, sizeof h);
+ sethints(&c->w);
+ if(c->w.hints)
+ c->fixedsize = eqpt(c->w.hints->min, c->w.hints->max);
+ if(memcmp(&h, c->w.hints, sizeof h))
+ if(c->sel)
+ view_update(c->sel->view);
+ break;
+ case XA_WM_HINTS:
+ wmh = XGetWMHints(display, c->w.xid);
+ if(wmh) {
+ c->noinput = (wmh->flags&InputFocus) && !wmh->input;
+ client_seturgent(c, (wmh->flags & XUrgencyHint) != 0, UrgClient);
+ XFree(wmh);
+ }
+ break;
+ case XA_WM_CLASS:
+ n = getprop_textlist(&c->w, "WM_CLASS", &class);
+ snprint(c->props, sizeof c->props, "%s:%s:",
+ (n > 0 ? class[0] : "<nil>"),
+ (n > 1 ? class[1] : "<nil>"));
+ freestringlist(class);
+ update_class(c);
+ break;
+ case XA_WM_NAME:
+ wmname:
+ client_updatename(c);
+ break;
+ }
+}
+
+/* Handlers */
+static void
+configreq_event(Window *w, XConfigureRequestEvent *e) {
+ Rectangle r, cr;
+ Client *c;
+
+ c = w->aux;
+
+ r = client_grav(c, ZR);
+ r.max = subpt(r.max, r.min);
+
+ if(e->value_mask & CWX)
+ r.min.x = e->x;
+ if(e->value_mask & CWY)
+ r.min.y = e->y;
+ if(e->value_mask & CWWidth)
+ r.max.x = e->width;
+ if(e->value_mask & CWHeight)
+ r.max.y = e->height;
+
+ if(e->value_mask & CWBorderWidth)
+ c->border = e->border_width;
+
+ r.max = addpt(r.min, r.max);
+ cr = r;
+ r = client_grav(c, r);
+
+ if(c->sel->area->floating) {
+ client_resize(c, r);
+ }else {
+ c->sel->floatr = r;
+ client_configure(c);
+ }
+}
+
+static void
+destroy_event(Window *w, XDestroyWindowEvent *e) {
+ USED(w, e);
+
+ client_destroy(w->aux);
+}
+
+static void
+enter_event(Window *w, XCrossingEvent *e) {
+ Client *c;
+
+ c = w->aux;
+ if(e->detail != NotifyInferior) {
+ if(e->detail != NotifyVirtual)
+ if(e->serial != ignoreenter && disp.focus != c) {
+ Dprint(DFocus, "enter_notify([%C]%s)\n", c, c->name);
+ focus(c, false);
+ }
+ client_setcursor(c, cursor[CurNormal]);
+ }else
+ Dprint(DFocus, "enter_notify(%C[NotifyInferior]%s)\n", c, c->name);
+}
+
+static void
+focusin_event(Window *w, XFocusChangeEvent *e) {
+ Client *c, *old;
+
+ c = w->aux;
+
+ print_focus("focusin_event", c, c->name);
+
+ if(e->mode == NotifyGrab)
+ disp.hasgrab = c;
+
+ old = disp.focus;
+ disp.focus = c;
+ if(c != old) {
+ if(c->sel)
+ frame_draw(c->sel);
+ }
+}
+
+static void
+focusout_event(Window *w, XFocusChangeEvent *e) {
+ Client *c;
+
+ c = w->aux;
+ if((e->mode == NotifyWhileGrabbed) && (disp.hasgrab != &c_root)) {
+ if(disp.focus)
+ disp.hasgrab = disp.focus;
+ }else if(disp.focus == c) {
+ print_focus("focusout_event", &c_magic, "<magic>");
+ disp.focus = &c_magic;
+ if(c->sel)
+ frame_draw(c->sel);
+ }
+}
+
+static void
+unmap_event(Window *w, XUnmapEvent *e) {
+ Client *c;
+
+ c = w->aux;
+ if(!e->send_event)
+ c->unmapped--;
+ client_destroy(c);
+}
+
+static void
+map_event(Window *w, XMapEvent *e) {
+ Client *c;
+
+ USED(e);
+
+ c = w->aux;
+ if(c == selclient())
+ client_focus(c);
+}
+
+static void
+property_event(Window *w, XPropertyEvent *e) {
+ Client *c;
+
+ if(e->state == PropertyDelete) /* FIXME */
+ return;
+
+ c = w->aux;
+ client_prop(c, e->atom);
+}
+
+static Handlers handlers = {
+ .configreq = configreq_event,
+ .destroy = destroy_event,
+ .enter = enter_event,
+ .focusin = focusin_event,
+ .focusout = focusout_event,
+ .map = map_event,
+ .unmap = unmap_event,
+ .property = property_event,
+};
+
+/* Other */
+void
+client_setviews(Client *c, char **tags) {
+ Frame **fp, *f;
+ int cmp;
+
+ fp = &c->frame;
+ while(*fp || *tags) {
+ SET(cmp);
+ while(*fp) {
+ if(*tags) {
+ cmp = strcmp(fp[0]->view->name, *tags);
+ if(cmp >= 0)
+ break;
+ }
+
+ f = *fp;
+ view_detach(f);
+ *fp = f->cnext;
+ if(c->sel == f)
+ c->sel = *fp;
+ free(f);
+ }
+ if(*tags) {
+ if(!*fp || cmp > 0) {
+ f = frame_create(c, view_create(*tags));
+ if(f->view == selview || !c->sel)
+ c->sel = f;
+ kludge = c; /* FIXME */
+ view_attach(f->view, f);
+ kludge = nil;
+ f->cnext = *fp;
+ *fp = f;
+ }
+ if(fp[0]) fp=&fp[0]->cnext;
+ tags++;
+ }
+ }
+ if(c->sel == nil)
+ c->sel = c->frame;
+ if(c->sel)
+ frame_draw(c->sel);
+}
+
+static int
+bsstrcmp(const void *a, const void *b) {
+ return strcmp((char*)a, *(char**)b);
+}
+
+static int
+strpcmp(const void *ap, const void *bp) {
+ char **a, **b;
+
+ a = (char**)ap;
+ b = (char**)bp;
+ return strcmp(*a, *b);
+}
+
+static char *badtags[] = {
+ ".",
+ "..",
+ "sel",
+};
+
+char*
+client_extratags(Client *c) {
+ Frame *f;
+ char *toks[32];
+ char **tags;
+ char *s, *s2;
+ int i;
+
+ i = 0;
+ toks[i++] = "";
+ for(f=c->frame; f && i < nelem(toks)-1; f=f->cnext)
+ if(f != c->sel)
+ toks[i++] = f->view->name;
+ toks[i] = nil;
+ tags = comm(CLeft, toks, c->retags);
+
+ s = nil;
+ if(i > 1)
+ s = join(tags, "+");
+ free(tags);
+ if(!c->tagre.regex && !c->tagvre.regex)
+ return s;
+
+ if(c->tagre.regex) {
+ s2 = s;
+ s = smprint("%s+/%s/", s ? s : "", c->tagre.regex);
+ free(s2);
+ }
+ if(c->tagvre.regex) {
+ s2 = s;
+ s = smprint("%s-/%s/", s ? s : "", c->tagvre.regex);
+ free(s2);
+ }
+ return s;
+}
+
+bool
+client_applytags(Client *c, const char *tags) {
+ uint i, j, k, n;
+ bool add, found;
+ char buf[512], last;
+ char *toks[32];
+ char **p;
+ char *cur, *s;
+
+ buf[0] = 0;
+
+ for(n = 0; tags[n]; n++)
+ if(!isspace(tags[n]))
+ break;
+
+ if(tags[n] == '+' || tags[n] == '-')
+ utflcpy(buf, c->tags, sizeof c->tags);
+ else {
+ refree(&c->tagre);
+ refree(&c->tagvre);
+ }
+ strlcat(buf, &tags[n], sizeof buf);
+
+ n = 0;
+ add = true;
+ if(buf[0] == '+')
+ n++;
+ else if(buf[0] == '-') {
+ n++;
+ add = false;
+ }
+
+ found = false;
+
+ j = 0;
+ while(buf[n] && n < sizeof(buf) && j < 32) {
+ /* Check for regex. */
+ if(buf[n] == '/') {
+ for(i=n+1; i < sizeof(buf) - 1; i++)
+ if(buf[i] == '/') break;
+ if(buf[i] == '/') {
+ i++;
+ if(buf[i] == '+'
+ || buf[i] == '-'
+ || buf[i] == '\0') { /* Don't be lenient */
+ buf[i-1] = '\0';
+ if(add)
+ reinit(&c->tagre, buf+n+1);
+ else
+ reinit(&c->tagvre, buf+n+1);
+ last = buf[i];
+ buf[i] = '\0';
+
+ found = true;
+ goto next;
+ }
+ }
+ }
+
+ for(i = n; i < sizeof(buf) - 1; i++)
+ if(buf[i] == '+'
+ || buf[i] == '-'
+ || buf[i] == '\0')
+ break;
+ last = buf[i];
+ buf[i] = '\0';
+
+ trim(buf+n, " \t/");
+
+ cur = nil;
+ if(!strcmp(buf+n, "~"))
+ c->floating = add;
+ else
+ if(!strcmp(buf+n, "!") || !strcmp(buf+n, "sel"))
+ cur = selview->name;
+ else
+ if(!Mbsearch(buf+n, badtags, bsstrcmp))
+ cur = buf+n;
+
+ if(cur && j < nelem(toks)-1) {
+ if(add) {
+ found = true;
+ toks[j++] = cur;
+ }else {
+ for(i = 0, k = 0; i < j; i++)
+ if(strcmp(toks[i], cur))
+ toks[k++] = toks[i];
+ j = k;
+ }
+ }
+
+ next:
+ n = i + 1;
+ if(last == '+')
+ add = true;
+ if(last == '-')
+ add = false;
+ if(last == '\0')
+ break;
+ }
+
+ toks[j] = nil;
+ qsort(toks, j, sizeof *toks, strpcmp);
+ uniq(toks);
+
+ s = join(toks, "+");
+ utflcpy(c->tags, s, sizeof c->tags);
+ if(c->tagre.regex)
+ strlcatprint(c->tags, sizeof c->tags, "+/%s/", c->tagre.regex);
+ if(c->tagvre.regex)
+ strlcatprint(c->tags, sizeof c->tags, "-/%s/", c->tagvre.regex);
+ changeprop_string(&c->w, "_WMII_TAGS", c->tags);
+ free(s);
+
+ free(c->retags);
+ p = view_names();
+ grep(p, c->tagre.regc, 0);
+ grep(p, c->tagvre.regc, GInvert);
+ c->retags = comm(CRight, toks, p);
+ free(p);
+
+ if(c->retags[0] == nil && toks[0] == nil) {
+ toks[0] = "orphans";
+ toks[1] = nil;
+ }
+
+ p = comm(~0, c->retags, toks);
+ client_setviews(c, p);
+ free(p);
+ return found;
+}
+
diff --git a/cmd/wmii/column.c b/cmd/wmii/column.c
new file mode 100644
index 0000000..e94f6a9
--- /dev/null
+++ b/cmd/wmii/column.c
@@ -0,0 +1,738 @@
+/* Copyright ©2004-2006 Anselm R. Garbe <garbeam at gmail dot com>
+ * Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include <math.h>
+#include <strings.h>
+#include "fns.h"
+
+static void column_resizeframe_h(Frame*, Rectangle);
+
+char *modes[] = {
+ [Coldefault] = "default",
+ [Colstack] = "stack",
+ [Colmax] = "max",
+};
+
+bool
+column_setmode(Area *a, const char *mode) {
+ char *str, *tok, *orig;
+ char add, old;
+
+ /*
+ * The mapping between the current internal
+ * representation and the external interface
+ * is currently a bit complex. That will probably
+ * change.
+ */
+
+ orig = strdup(mode);
+ str = orig;
+ old = '\0';
+ while(*(tok = str)) {
+ add = old;
+ while((old=*str) && !strchr("+-^", old))
+ str++;
+ *str = '\0';
+ if(str > tok) {
+ print("'%s' %c\n", tok, add);
+ if(!strcmp(tok, "max")) {
+ if(add == '\0' || add == '+')
+ a->max = true;
+ else if(add == '-')
+ a->max = false;
+ else
+ a->max = !a->max;
+ }else
+ if(!strcmp(tok, "stack")) {
+ if(add == '\0' || add == '+')
+ a->mode = Colstack;
+ else if(add == '-')
+ a->mode = Coldefault;
+ else
+ a->mode = a->mode == Colstack ? Coldefault : Colstack;
+ }else
+ if(!strcmp(tok, "default")) {
+ if(add == '\0' || add == '+') {
+ a->mode = Coldefault;
+ column_arrange(a, true);
+ }else if(add == '-')
+ a->mode = Colstack;
+ else
+ a->mode = a->mode == Coldefault ? Colstack : Coldefault;
+ }else {
+ free(orig);
+ return false;
+ }
+ }
+ if(old)
+ str++;
+
+ }
+ free(orig);
+ return true;
+}
+
+char*
+column_getmode(Area *a) {
+ return sxprint("%s%cmax", a->mode == Colstack ? "stack" : "default",
+ a->max ? '+' : '-');
+}
+
+int
+column_minwidth(void)
+{
+ return 4 * labelh(def.font);
+}
+
+Area*
+column_new(View *v, Area *pos, int scrn, uint w) {
+ Area *a;
+
+ assert(!pos || !pos->floating && pos->screen == scrn);
+ a = area_create(v, pos, scrn, w);
+ return a;
+#if 0
+ if(!a)
+ return nil;
+
+ view_arrange(v);
+ view_update(v);
+#endif
+}
+
+void
+column_insert(Area *a, Frame *f, Frame *pos) {
+
+ f->area = a;
+ f->client->floating = false;
+ f->screen = a->screen;
+ f->column = area_idx(a);
+ frame_insert(f, pos);
+ if(a->sel == nil)
+ area_setsel(a, f);
+}
+
+/* Temporary. */
+static void
+stack_scale(Frame *first, int height) {
+ Frame *f;
+ Area *a;
+ uint dy;
+ int surplus;
+
+ a = first->area;
+
+ /*
+ * Will need something like this.
+ column_fit(a, &ncol, &nuncol);
+ */
+
+ dy = 0;
+ for(f=first; f && !f->collapsed; f=f->anext)
+ dy += Dy(f->colr);
+
+ /* Distribute the surplus.
+ */
+ surplus = height - dy;
+ for(f=first; f && !f->collapsed; f=f->anext)
+ f->colr.max.y += ((float)Dy(f->r) / dy) * surplus;
+}
+
+static void
+stack_info(Frame *f, Frame **firstp, Frame **lastp, int *dyp, int *nframep) {
+ Frame *ft, *first, *last;
+ int dy, nframe;
+
+ nframe = 0;
+ dy = 0;
+ first = f;
+ last = f;
+
+ for(ft=f; ft && ft->collapsed; ft=ft->anext)
+ ;
+ if(ft && ft != f) {
+ f = ft;
+ dy += Dy(f->colr);
+ }
+ for(ft=f; ft && !ft->collapsed; ft=ft->aprev) {
+ first = ft;
+ nframe++;
+ dy += Dy(ft->colr);
+ }
+ for(ft=f->anext; ft && !ft->collapsed; ft=ft->anext) {
+ if(first == nil)
+ first = ft;
+ last = ft;
+ nframe++;
+ dy += Dy(ft->colr);
+ }
+ if(nframep) *nframep = nframe;
+ if(firstp) *firstp = first;
+ if(lastp) *lastp = last;
+ if(dyp) *dyp = dy;
+}
+
+int
+stack_count(Frame *f, int *mp) {
+ Frame *fp;
+ int n, m;
+
+ n = 0;
+ for(fp=f->aprev; fp && fp->collapsed; fp=fp->aprev)
+ n++;
+ m = ++n;
+ for(fp=f->anext; fp && fp->collapsed; fp=fp->anext)
+ n++;
+ if(mp) *mp = m;
+ return n;
+}
+
+Frame*
+stack_find(Area *a, Frame *f, int dir, bool stack) {
+ Frame *fp;
+
+ switch (dir) {
+ default:
+ die("not reached");
+ case North:
+ if(f)
+ for(f=f->aprev; f && f->collapsed && stack; f=f->aprev)
+ ;
+ else {
+ f = nil;
+ for(fp=a->frame; fp; fp=fp->anext)
+ if(!fp->collapsed || !stack)
+ f = fp;
+ }
+ break;
+ case South:
+ if(f)
+ for(f=f->anext; f && f->collapsed && stack; f=f->anext)
+ ;
+ else
+ for(f=a->frame; f && f->collapsed && stack; f=f->anext)
+ ;
+ break;
+ }
+ return f;
+}
+
+/* TODO: Move elsewhere. */
+bool
+find(Area **ap, Frame **fp, int dir, bool wrap, bool stack) {
+ Rectangle r;
+ Frame *f;
+ Area *a;
+
+ f = *fp;
+ a = *ap;
+ r = f ? f->r : a->r;
+
+ if(dir == North || dir == South) {
+ *fp = stack_find(a, f, dir, stack);
+ if(*fp)
+ return true;
+ if (!a->floating)
+ *ap = area_find(a->view, r, dir, wrap);
+ if(!*ap)
+ return false;
+ *fp = stack_find(*ap, *fp, dir, stack);
+ return true;
+ }
+ if(dir != East && dir != West)
+ die("not reached");
+ *ap = area_find(a->view, r, dir, wrap);
+ if(!*ap)
+ return false;
+ *fp = ap[0]->sel;
+ return true;
+}
+
+void
+column_attach(Area *a, Frame *f) {
+ Frame *first;
+ int nframe, dy, h;
+
+ f->colr = a->r;
+
+ if(a->sel) {
+ stack_info(a->sel, &first, nil, &dy, &nframe);
+ h = dy / (nframe+1);
+ f->colr.max.y = f->colr.min.y + h;
+ stack_scale(first, dy - h);
+ }
+
+ column_insert(a, f, a->sel);
+ column_arrange(a, false);
+}
+
+void
+column_detach(Frame *f) {
+ Frame *first;
+ Area *a;
+ int dy;
+
+ a = f->area;
+ stack_info(f, &first, nil, &dy, nil);
+ if(first && first == f)
+ first = f->anext;
+ column_remove(f);
+ if(a->frame) {
+ if(first)
+ stack_scale(first, dy);
+ column_arrange(a, false);
+ }else if(a->view->areas[a->screen]->next)
+ area_destroy(a);
+}
+
+static void column_scale(Area*);
+
+void
+column_attachrect(Area *a, Frame *f, Rectangle r) {
+ Frame *fp, *pos;
+ int before, after;
+
+ pos = nil;
+ for(fp=a->frame; fp; pos=fp, fp=fp->anext) {
+ if(r.max.y < fp->r.min.y || r.min.y > fp->r.max.y)
+ continue;
+ before = fp->r.min.y - r.min.y;
+ after = -fp->r.max.y + r.max.y;
+ }
+ column_insert(a, f, pos);
+ column_resizeframe_h(f, r);
+ column_scale(a);
+}
+
+void
+column_remove(Frame *f) {
+ Frame *pr;
+ Area *a;
+
+ a = f->area;
+ pr = f->aprev;
+
+ frame_remove(f);
+
+ f->area = nil;
+ if(a->sel == f) {
+ if(pr == nil)
+ pr = a->frame;
+ if(pr && pr->collapsed)
+ if(pr->anext && !pr->anext->collapsed)
+ pr = pr->anext;
+ else
+ pr->collapsed = false;
+ a->sel = nil;
+ area_setsel(a, pr);
+ }
+}
+
+static int
+column_surplus(Area *a) {
+ Frame *f;
+ int surplus;
+
+ surplus = Dy(a->r);
+ for(f=a->frame; f; f=f->anext)
+ surplus -= Dy(f->r);
+ return surplus;
+}
+
+static void
+column_fit(Area *a, uint *n_colp, uint *n_uncolp) {
+ Frame *f, **fp;
+ uint minh, dy;
+ uint n_col, n_uncol;
+ uint col_h, uncol_h;
+ int surplus, i, j;
+
+ /* The minimum heights of collapsed and uncollpsed frames.
+ */
+ minh = labelh(def.font);
+ col_h = labelh(def.font);
+ uncol_h = minh + col_h + 1;
+ if(a->max && !resizing)
+ col_h = 0;
+
+ /* Count collapsed and uncollapsed frames. */
+ n_col = 0;
+ n_uncol = 0;
+ for(f=a->frame; f; f=f->anext) {
+ frame_resize(f, f->colr);
+ if(f->collapsed)
+ n_col++;
+ else
+ n_uncol++;
+ }
+
+ if(n_uncol == 0) {
+ n_uncol++;
+ n_col--;
+ (a->sel ? a->sel : a->frame)->collapsed = false;
+ }
+
+ /* FIXME: Kludge. See frame_attachrect. */
+ dy = Dy(a->view->r[a->screen]) - Dy(a->r);
+ minh = col_h * (n_col + n_uncol - 1) + uncol_h;
+ if(dy && Dy(a->r) < minh)
+ a->r.max.y += min(dy, minh - Dy(a->r));
+
+ surplus = Dy(a->r)
+ - (n_col * col_h)
+ - (n_uncol * uncol_h);
+
+ /* Collapse until there is room */
+ if(surplus < 0) {
+ i = ceil(-1.F * surplus / (uncol_h - col_h));
+ if(i >= n_uncol)
+ i = n_uncol - 1;
+ n_uncol -= i;
+ n_col += i;
+ surplus += i * (uncol_h - col_h);
+ }
+ /* Push to the floating layer until there is room */
+ if(surplus < 0) {
+ i = ceil(-1.F * surplus / col_h);
+ if(i > n_col)
+ i = n_col;
+ n_col -= i;
+ surplus += i * col_h;
+ }
+
+ /* Decide which to collapse and which to float. */
+ j = n_uncol - 1;
+ i = n_col - 1;
+ for(fp=&a->frame; *fp;) {
+ f = *fp;
+ if(f != a->sel) {
+ if(!f->collapsed) {
+ if(j < 0)
+ f->collapsed = true;
+ j--;
+ }
+ if(f->collapsed) {
+ if(i < 0) {
+ f->collapsed = false;
+ area_moveto(f->view->floating, f);
+ continue;
+ }
+ i--;
+ }
+ }
+ /* Doesn't change if we 'continue' */
+ fp = &f->anext;
+ }
+
+ if(n_colp) *n_colp = n_col;
+ if(n_uncolp) *n_uncolp = n_uncol;
+}
+
+void
+column_settle(Area *a) {
+ Frame *f;
+ uint yoff, yoffcr;
+ int surplus, n_uncol, n;
+
+ n_uncol = 0;
+ surplus = column_surplus(a);
+ for(f=a->frame; f; f=f->anext)
+ if(!f->collapsed) n_uncol++;
+
+ if(n_uncol == 0) {
+ fprint(2, "%s: Badness: No uncollapsed frames, column %d, view %q\n",
+ argv0, area_idx(a), a->view->name);
+ return;
+ }
+ if(surplus < 0)
+ fprint(2, "%s: Badness: surplus = %d in column_settle, column %d, view %q\n",
+ argv0, surplus, area_idx(a), a->view->name);
+
+ yoff = a->r.min.y;
+ yoffcr = yoff;
+ n = surplus % n_uncol;
+ surplus /= n_uncol;
+ for(f=a->frame; f; f=f->anext) {
+ f->r = rectsetorigin(f->r, Pt(a->r.min.x, yoff));
+ f->colr = rectsetorigin(f->colr, Pt(a->r.min.x, yoffcr));
+ f->r.min.x = a->r.min.x;
+ f->r.max.x = a->r.max.x;
+ if(def.incmode == ISqueeze && !resizing)
+ if(!f->collapsed) {
+ f->r.max.y += surplus;
+ if(n-- > 0)
+ f->r.max.y++;
+ }
+ yoff = f->r.max.y;
+ yoffcr = f->colr.max.y;
+ }
+}
+
+/*
+ * Returns how much a frame "wants" to grow.
+ */
+static int
+foo(Frame *f) {
+ WinHints h;
+ int maxh;
+
+ h = frame_gethints(f);
+ maxh = 0;
+ if(h.aspect.max.x)
+ maxh = h.baspect.y +
+ (Dx(f->r) - h.baspect.x) *
+ h.aspect.max.y / h.aspect.max.x;
+ maxh = max(maxh, h.max.y);
+
+ if(Dy(f->r) >= maxh)
+ return 0;
+ return h.inc.y - (Dy(f->r) - h.base.y) % h.inc.y;
+}
+
+static int
+comp_frame(const void *a, const void *b) {
+ int ia, ib;
+
+ ia = foo(*(Frame**)a);
+ ib = foo(*(Frame**)b);
+ /*
+ * I'd like to favor the selected client, but
+ * it causes windows to jump as focus changes.
+ */
+ return ia < ib ? -1 :
+ ia > ib ? 1 :
+ 0;
+}
+
+static void
+column_squeeze(Area *a) {
+ static Vector_ptr fvec;
+ Frame *f;
+ int surplus, osurplus, dy, i;
+
+ fvec.n = 0;
+ for(f=a->frame; f; f=f->anext)
+ if(!f->collapsed) {
+ f->r = frame_hints(f, f->r, 0);
+ vector_ppush(&fvec, f);
+ }
+
+ surplus = column_surplus(a);
+ for(osurplus=0; surplus != osurplus;) {
+ osurplus = surplus;
+ qsort(fvec.ary, fvec.n, sizeof *fvec.ary, comp_frame);
+ for(i=0; i < fvec.n; i++) {
+ f=fvec.ary[i];
+ dy = foo(f);
+ if(dy > surplus)
+ break;
+ surplus -= dy;
+ f->r.max.y += dy;
+ }
+ }
+}
+
+/*
+ * Frobs a column. Which is to say, *temporary* kludge.
+ * Essentially seddles the column and resizes its clients.
+ */
+void
+column_frob(Area *a) {
+ Frame *f;
+
+ for(f=a->frame; f; f=f->anext)
+ f->r = f->colr;
+ column_settle(a);
+ if(a->view == selview)
+ for(f=a->frame; f; f=f->anext)
+ client_resize(f->client, f->r);
+}
+
+static void
+column_scale(Area *a) {
+ Frame *f;
+ uint dy;
+ uint ncol, nuncol;
+ uint colh;
+ int surplus;
+
+ if(!a->frame)
+ return;
+
+ column_fit(a, &ncol, &nuncol);
+
+ colh = labelh(def.font);
+ if(a->max && !resizing)
+ colh = 0;
+
+ dy = 0;
+ surplus = Dy(a->r);
+ for(f=a->frame; f; f=f->anext) {
+ if(f->collapsed)
+ f->colr.max.y = f->colr.min.y + colh;
+ else if(Dy(f->colr) == 0)
+ f->colr.max.y++;
+ surplus -= Dy(f->colr);
+ if(!f->collapsed)
+ dy += Dy(f->colr);
+ }
+ for(f=a->frame; f; f=f->anext) {
+ f->dy = Dy(f->colr);
+ f->colr.min.x = a->r.min.x;
+ f->colr.max.x = a->r.max.x;
+ if(!f->collapsed)
+ f->colr.max.y += ((float)f->dy / dy) * surplus;
+ if(btassert("6 full", !(f->collapsed ? Dy(f->r) >= 0 : dy > 0)))
+ warning("Something's fucked: %s:%d:%s()",
+ __FILE__, __LINE__, __func__);
+ frame_resize(f, f->colr);
+ }
+
+ if(def.incmode == ISqueeze && !resizing)
+ column_squeeze(a);
+ column_settle(a);
+}
+
+void
+column_arrange(Area *a, bool dirty) {
+ Frame *f;
+ View *v;
+
+ if(a->floating)
+ float_arrange(a);
+ if(a->floating || !a->frame)
+ return;
+
+ v = a->view;
+
+ switch(a->mode) {
+ case Coldefault:
+ if(dirty)
+ for(f=a->frame; f; f=f->anext)
+ f->colr = Rect(0, 0, 100, 100);
+ break;
+ case Colstack:
+ /* XXX */
+ for(f=a->frame; f; f=f->anext)
+ f->collapsed = (f != a->sel);
+ break;
+ default:
+ fprint(2, "Dieing: %s: screen: %d a: %p mode: %x floating: %d\n",
+ v->name, a->screen, a, a->mode, a->floating);
+ die("not reached");
+ break;
+ }
+ column_scale(a);
+ /* XXX */
+ if(a->sel->collapsed)
+ area_setsel(a, a->sel);
+ if(v == selview) {
+ //view_restack(v);
+ client_resize(a->sel->client, a->sel->r);
+ for(f=a->frame; f; f=f->anext)
+ client_resize(f->client, f->r);
+ }
+}
+
+void
+column_resize(Area *a, int w) {
+ Area *an;
+ int dw;
+
+ an = a->next;
+ assert(an != nil);
+
+ dw = w - Dx(a->r);
+ a->r.max.x += dw;
+ an->r.min.x += dw;
+
+ /* view_arrange(a->view); */
+ view_update(a->view);
+}
+
+static void
+column_resizeframe_h(Frame *f, Rectangle r) {
+ Area *a;
+ Frame *fn, *fp;
+ uint minh;
+
+ minh = labelh(def.font);
+
+ a = f->area;
+ fn = f->anext;
+ fp = f->aprev;
+
+ if(fp)
+ r.min.y = max(r.min.y, fp->colr.min.y + minh);
+ else
+ r.min.y = max(r.min.y, a->r.min.y);
+
+ if(fn)
+ r.max.y = min(r.max.y, fn->colr.max.y - minh);
+ else
+ r.max.y = min(r.max.y, a->r.max.y);
+
+ if(fp) {
+ fp->colr.max.y = r.min.y;
+ frame_resize(fp, fp->colr);
+ }
+ else
+ r.min.y = min(r.min.y, r.max.y - minh);
+
+ if(fn) {
+ fn->colr.min.y = r.max.y;
+ frame_resize(fn, fn->colr);
+ }
+ else
+ r.max.y = max(r.max.y, r.min.y + minh);
+
+ f->colr = r;
+ frame_resize(f, r);
+}
+
+void
+column_resizeframe(Frame *f, Rectangle r) {
+ Area *a, *al, *ar;
+ View *v;
+ uint minw;
+
+ a = f->area;
+ v = a->view;
+
+ minw = column_minwidth();
+
+ al = a->prev;
+ ar = a->next;
+
+ if(al)
+ r.min.x = max(r.min.x, al->r.min.x + minw);
+ else { /* Hm... */
+ r.min.x = max(r.min.x, v->r[a->screen].min.x);
+ r.max.x = max(r.max.x, r.min.x + minw);
+ }
+
+ if(ar)
+ r.max.x = min(r.max.x, ar->r.max.x - minw);
+ else {
+ r.max.x = min(r.max.x, v->r[a->screen].max.x);
+ r.min.x = min(r.min.x, r.max.x - minw);
+ }
+
+ a->r.min.x = r.min.x;
+ a->r.max.x = r.max.x;
+ if(al) {
+ al->r.max.x = a->r.min.x;
+ column_arrange(al, false);
+ }
+ if(ar) {
+ ar->r.min.x = a->r.max.x;
+ column_arrange(ar, false);
+ }
+
+ column_resizeframe_h(f, r);
+
+ view_update(v);
+}
+
diff --git a/cmd/wmii/dat.h b/cmd/wmii/dat.h
new file mode 100644
index 0000000..8038026
--- /dev/null
+++ b/cmd/wmii/dat.h
@@ -0,0 +1,404 @@
+/* Copyright ©2007-2010 Kris Maglione <jg@suckless.org>
+ * See LICENSE file for license details.
+ */
+
+#define _XOPEN_SOURCE 600
+#define IXP_P9_STRUCTS
+#define IXP_NO_P9_
+#include <assert.h>
+#include <regexp9.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ixp.h>
+#include <util.h>
+#include <utf.h>
+#include <fmt.h>
+#include <x11.h>
+
+#define FONT "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*"
+#define FOCUSCOLORS "#ffffff #335577 #447799"
+#define NORMCOLORS "#222222 #eeeeee #666666"
+
+enum {
+ PingTime = 10000,
+};
+
+enum {
+ CLeft = 1<<0,
+ CCenter = 1<<1,
+ CRight = 1<<2,
+};
+
+enum IncMode {
+ IIgnore,
+ IShow,
+ ISqueeze,
+};
+
+enum {
+ GInvert = 1<<0,
+};
+
+enum {
+ UrgManager,
+ UrgClient,
+};
+
+enum EWMHType {
+ TypeDesktop = 1<<0,
+ TypeDock = 1<<1,
+ TypeToolbar = 1<<2,
+ TypeMenu = 1<<3,
+ TypeUtility = 1<<4,
+ TypeSplash = 1<<5,
+ TypeDialog = 1<<6,
+ TypeNormal = 1<<7,
+};
+
+enum {
+ Coldefault, Colstack, Colmax, Collast
+};
+
+extern char* modes[];
+
+#define TOGGLE(x) \
+ (x == On ? "on" : \
+ x == Off ? "off" : \
+ x == Toggle ? "toggle" : \
+ "<toggle>")
+enum {
+ Off,
+ On,
+ Toggle,
+};
+
+enum Barpos {
+ BBottom,
+ BTop,
+};
+
+enum {
+ CurNormal,
+ CurNECorner, CurNWCorner, CurSECorner, CurSWCorner,
+ CurDHArrow, CurDVArrow, CurMove, CurInput, CurSizing,
+ CurTCross, CurIcon,
+ CurNone,
+ CurLast,
+};
+
+enum {
+ NCOL = 16,
+};
+
+enum Protocols {
+ ProtoDelete = 1<<0,
+ ProtoTakeFocus = 1<<1,
+ ProtoPing = 1<<2,
+};
+
+enum DebugOpt {
+ D9p = 1<<0,
+ DDnd = 1<<1,
+ DEvent = 1<<2,
+ DEwmh = 1<<3,
+ DFocus = 1<<4,
+ DGeneric= 1<<5,
+ DStack = 1<<6,
+ NDebugOpt = 7,
+};
+
+/* Data Structures */
+typedef struct Area Area;
+typedef struct Bar Bar;
+typedef struct Client Client;
+typedef struct Divide Divide;
+typedef struct Frame Frame;
+typedef struct Group Group;
+typedef struct Key Key;
+typedef struct Map Map;
+typedef struct MapEnt MapEnt;
+typedef struct Regex Regex;
+typedef struct Rule Rule;
+typedef struct Ruleset Ruleset;
+typedef struct Strut Strut;
+typedef struct View View;
+typedef struct WMScreen WMScreen;
+
+struct Area {
+ Area* next;
+ Area* prev;
+ Frame* frame;
+ Frame* frame_old;
+ Frame* stack;
+ Frame* sel;
+ View* view;
+ bool floating;
+ ushort id;
+ int mode;
+ int screen;
+ bool max;
+ Rectangle r;
+ Rectangle r_old;
+};
+
+struct Bar {
+ Bar* next;
+ Bar* smaller;
+ char buf[280];
+ char text[256];
+ char name[256];
+ int bar;
+ ushort id;
+ CTuple col;
+ Rectangle r;
+ WMScreen* screen;
+};
+
+struct Regex {
+ char* regex;
+ Reprog* regc;
+};
+
+struct Client {
+ Client* next;
+ Frame* frame;
+ Frame* sel;
+ Window w;
+ Window* framewin;
+ Image** ibuf;
+ XWindow trans;
+ Regex tagre;
+ Regex tagvre;
+ Group* group;
+ Strut* strut;
+ Cursor cursor;
+ Rectangle r;
+ char** retags;
+ char name[256];
+ char tags[256];
+ char props[512];
+ long proto;
+ uint border;
+ int fullscreen;
+ int unmapped;
+ bool floating;
+ bool fixedsize;
+ bool urgent;
+ bool borderless;
+ bool titleless;
+ bool noinput;
+};
+
+struct Divide {
+ Divide* next;
+ Window* w;
+ Area* left;
+ Area* right;
+ bool mapped;
+ int x;
+};
+
+struct Frame {
+ Frame* cnext;
+ Frame* anext;
+ Frame* aprev;
+ Frame* anext_old;
+ Frame* snext;
+ Frame* sprev;
+ Client* client;
+ View* view;
+ Area* area;
+ int oldscreen;
+ int oldarea;
+ int screen;
+ int column;
+ ushort id;
+ bool collapsed;
+ int dy;
+ Rectangle r;
+ Rectangle colr;
+ Rectangle colr_old;
+ Rectangle floatr;
+ Rectangle crect;
+ Rectangle grabbox;
+ Rectangle titlebar;
+};
+
+struct Group {
+ Group* next;
+ XWindow leader;
+ Client *client;
+ int ref;
+};
+
+struct Key {
+ Key* next;
+ Key* lnext;
+ Key* tnext;
+ ushort id;
+ char name[128];
+ ulong mod;
+ KeyCode key;
+};
+
+struct Map {
+ MapEnt**bucket;
+ uint nhash;
+};
+
+struct Rule {
+ Rule* next;
+ Reprog* regex;
+ char value[256];
+
+};
+
+struct Ruleset {
+ Rule* rule;
+ char* string;
+ uint size;
+};
+
+struct Strut {
+ Rectangle left;
+ Rectangle right;
+ Rectangle top;
+ Rectangle bottom;
+};
+
+#define firstarea areas[screen->idx]
+#define screenr r[screen->idx]
+struct View {
+ View* next;
+ char name[256];
+ ushort id;
+ Area* floating;
+ Area** areas;
+ Area* sel;
+ Area* oldsel;
+ Area* revert;
+ int selcol;
+ int selscreen;
+ bool dead;
+ Rectangle *r;
+ Rectangle *pad;
+};
+
+/* Yuck. */
+#define VECTOR(type, nam, c) \
+typedef struct Vector_##nam Vector_##nam; \
+struct Vector_##nam { \
+ type* ary; \
+ long n; \
+ long size; \
+}; \
+void vector_##c##free(Vector_##nam*); \
+void vector_##c##init(Vector_##nam*); \
+void vector_##c##push(Vector_##nam*, type); \
+
+VECTOR(long, long, l)
+VECTOR(Rectangle, rect, r)
+VECTOR(void*, ptr, p)
+#undef VECTOR
+
+#ifndef EXTERN
+# define EXTERN extern
+#endif
+
+/* global variables */
+EXTERN struct {
+ CTuple focuscolor;
+ CTuple normcolor;
+ Font* font;
+ char* keys;
+ uint keyssz;
+ Ruleset tagrules;
+ Ruleset colrules;
+ char grabmod[5];
+ ulong mod;
+ uint border;
+ uint snap;
+ int colmode;
+ int incmode;
+} def;
+
+enum {
+ BLeft, BRight
+};
+
+#define BLOCK(x) do { x; }while(0)
+
+EXTERN struct WMScreen {
+ Bar* bar[2];
+ Window* barwin;
+ bool showing;
+ int barpos;
+ int idx;
+
+ Rectangle r;
+ Rectangle brect;
+} **screens, *screen;
+EXTERN uint nscreens;
+
+EXTERN struct {
+ Client* focus;
+ Client* hasgrab;
+ Image* ibuf;
+ Image* ibuf32;
+ bool sel;
+} disp;
+
+EXTERN Client* client;
+EXTERN View* view;
+EXTERN View* selview;
+EXTERN Key* key;
+EXTERN Divide* divs;
+EXTERN Client c_magic;
+EXTERN Client c_root;
+
+EXTERN Handlers framehandler;
+
+EXTERN char buffer[8092];
+EXTERN char* _buffer;
+static char* const _buf_end = buffer + sizeof buffer;
+
+#define bufclear() \
+ BLOCK( _buffer = buffer; _buffer[0] = '\0' )
+#define bufprint(...) \
+ _buffer = seprint(_buffer, _buf_end, __VA_ARGS__)
+
+/* IXP */
+EXTERN IxpServer srv;
+EXTERN Ixp9Srv p9srv;
+
+/* X11 */
+EXTERN uint valid_mask;
+EXTERN uint numlock_mask;
+EXTERN Image* ibuf;
+EXTERN Image* ibuf32;
+
+EXTERN Cursor cursor[CurLast];
+
+typedef void (*XHandler)(XEvent*);
+EXTERN XHandler handler[LASTEvent];
+
+/* Misc */
+EXTERN int starting;
+EXTERN bool resizing;
+EXTERN long ignoreenter;
+EXTERN char* user;
+EXTERN char* execstr;
+EXTERN int debugflag;
+EXTERN int debugfile;
+EXTERN long xtime;
+EXTERN Visual* render_visual;
+
+EXTERN Client* kludge;
+
+extern char* debugtab[];
+
+#define Debug(x) if(((debugflag|debugfile)&(x)) && setdebug(x))
+#define Dprint(x, ...) BLOCK( if((debugflag|debugfile)&(x)) debug(x, __VA_ARGS__) )
+
diff --git a/cmd/wmii/div.c b/cmd/wmii/div.c
new file mode 100644
index 0000000..986d6a2
--- /dev/null
+++ b/cmd/wmii/div.c
@@ -0,0 +1,190 @@
+/* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include "fns.h"
+
+static Image* divimg;
+static Image* divmask;
+static CTuple divcolor;
+static Handlers handlers;
+
+static Divide*
+getdiv(Divide ***dp) {
+ WinAttr wa;
+ Divide *d;
+
+ if(**dp)
+ d = **dp;
+ else {
+ d = emallocz(sizeof *d);
+
+ wa.override_redirect = true;
+ wa.cursor = cursor[CurDHArrow];
+ wa.event_mask =
+ ExposureMask
+ | EnterWindowMask
+ | ButtonPressMask
+ | ButtonReleaseMask;
+ d->w = createwindow(&scr.root, Rect(0, 0, 1, 1), scr.depth,
+ InputOutput, &wa,
+ CWOverrideRedirect
+ | CWEventMask
+ | CWCursor);
+ d->w->aux = d;
+ sethandler(d->w, &handlers);
+ **dp = d;
+ }
+ *dp = &d->next;
+ return d;
+}
+
+static void
+mapdiv(Divide *d) {
+ mapwin(d->w);
+}
+
+static void
+unmapdiv(Divide *d) {
+ unmapwin(d->w);
+}
+
+void
+div_set(Divide *d, int x) {
+ Rectangle r;
+ int scrn;
+
+ scrn = d->left ? d->left->screen : d->right->screen;
+
+ d->x = x;
+ r = rectaddpt(divimg->r, Pt(x - Dx(divimg->r)/2, 0));
+ r.min.y = selview->r[scrn].min.y;
+ r.max.y = selview->r[scrn].max.y;
+
+ reshapewin(d->w, r);
+ mapdiv(d);
+}
+
+static void
+drawimg(Image *img, Color cbg, Color cborder, Divide *d) {
+ Point pt[8];
+ int n, start, w;
+
+ w = Dx(img->r)/2;
+ n = 0;
+ pt[n++] = Pt(w, 0);
+ pt[n++] = Pt(0, 0);
+ pt[n++] = Pt(w - 1, w - 1);
+
+ pt[n++] = Pt(w - 1, Dy(img->r));
+ pt[n++] = Pt(w, pt[n-1].y);
+
+ pt[n++] = Pt(w, w - 1);
+ pt[n++] = Pt(2*w - 1, 0);
+ pt[n++] = Pt(w, 0);
+
+ start = d->left ? 0 : n/2;
+ n = d->right && d->left ? n : n/2;
+
+ fillpoly(img, pt + start, n, cbg);
+ drawpoly(img, pt + start, n, CapNotLast, 1, cborder);
+}
+
+static void
+drawdiv(Divide *d) {
+
+ fill(divmask, divmask->r, (Color){0});
+ drawimg(divmask, (Color){1}, (Color){1}, d);
+ drawimg(divimg, divcolor.bg, divcolor.border, d);
+
+ copyimage(d->w, divimg->r, divimg, ZP);
+ setshapemask(d->w, divmask, ZP);
+}
+
+static void
+update_imgs(void) {
+ Divide *d;
+ int w, h;
+
+ w = 2 * (labelh(def.font) / 3);
+ w = max(w, 10);
+ /* XXX: Multihead. */
+ h = Dy(scr.rect);
+
+ if(divimg) {
+ if(w == Dx(divimg->r) && h == Dy(divimg->r)
+ && !memcmp(&divcolor, &def.normcolor, sizeof divcolor))
+ return;
+ freeimage(divimg);
+ freeimage(divmask);
+ }
+
+ divimg = allocimage(w, h, scr.depth);
+ divmask = allocimage(w, h, 1);
+ divcolor = def.normcolor;
+
+ for(d = divs; d && d->w->mapped; d = d->next)
+ drawdiv(d);
+}
+
+void
+div_update_all(void) {
+ Divide **dp, *d;
+ Area *a, *ap;
+ View *v;
+ int s;
+
+ update_imgs();
+
+ v = selview;
+ dp = &divs;
+ ap = nil;
+ foreach_column(v, s, a) {
+ if (ap && ap->screen != s)
+ ap = nil;
+
+ d = getdiv(&dp);
+ d->left = ap;
+ d->right = a;
+ div_set(d, a->r.min.x);
+ drawdiv(d);
+ ap = a;
+
+ if(!a->next) {
+ d = getdiv(&dp);
+ d->left = a;
+ d->right = nil;
+ div_set(d, a->r.max.x);
+ drawdiv(d);
+ }
+ }
+ for(d = *dp; d; d = d->next)
+ unmapdiv(d);
+}
+
+/* Div Handlers */
+static void
+bdown_event(Window *w, XButtonEvent *e) {
+ Divide *d;
+
+ USED(e);
+
+ d = w->aux;
+ mouse_resizecol(d);
+}
+
+static void
+expose_event(Window *w, XExposeEvent *e) {
+ Divide *d;
+
+ USED(e);
+
+ d = w->aux;
+ drawdiv(d);
+}
+
+static Handlers handlers = {
+ .bdown = bdown_event,
+ .expose = expose_event,
+};
+
diff --git a/cmd/wmii/error.c b/cmd/wmii/error.c
new file mode 100644
index 0000000..3f66653
--- /dev/null
+++ b/cmd/wmii/error.c
@@ -0,0 +1,41 @@
+/* Copyright ©2007-2010 Kris Maglione <jg@suckless.org>
+ * See LICENSE file for license details.
+ */
+
+#include "dat.h"
+#include "fns.h"
+
+static jmp_buf errjmp[16];
+static long nerror;
+
+void
+error(char *fmt, ...) {
+ char errbuf[IXP_ERRMAX];
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsnprint(errbuf, IXP_ERRMAX, fmt, ap);
+ va_end(ap);
+ ixp_errstr(errbuf, IXP_ERRMAX);
+
+ nexterror();
+}
+
+void
+nexterror(void) {
+ assert(nerror > 0);
+ longjmp(errjmp[--nerror], 1);
+}
+
+void
+poperror(void) {
+ assert(nerror > 0);
+ --nerror;
+}
+
+jmp_buf*
+pusherror(void) {
+ assert(nerror < nelem(errjmp));
+ return &errjmp[nerror++];
+}
+
diff --git a/cmd/wmii/event.c b/cmd/wmii/event.c
new file mode 100644
index 0000000..82a51eb
--- /dev/null
+++ b/cmd/wmii/event.c
@@ -0,0 +1,359 @@
+/* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include <X11/keysym.h>
+#include "fns.h"
+
+typedef void (*EvHandler)(XEvent*);
+
+void
+dispatch_event(XEvent *e) {
+ Dprint(DEvent, "%E\n", e);
+ if(e->type < nelem(handler)) {
+ if(handler[e->type])
+ handler[e->type](e);
+ }else
+ xext_event(e);
+}
+
+#define handle(w, fn, ev) \
+ BLOCK(if((w)->handler->fn) (w)->handler->fn((w), ev))
+
+static int
+findtime(Display *d, XEvent *e, XPointer v) {
+ Window *w;
+
+ w = (Window*)v;
+ if(e->type == PropertyNotify && e->xproperty.window == w->xid) {
+ xtime = e->xproperty.time;
+ return true;
+ }
+ return false;
+}
+
+void
+xtime_kludge(void) {
+ /* Round trip. */
+ static Window *w;
+ WinAttr wa;
+ XEvent e;
+ long l;
+
+ if(w == nil) {
+ w = createwindow(&scr.root, Rect(0, 0, 1, 1), 0, InputOnly, &wa, 0);
+ selectinput(w, PropertyChangeMask);
+ }
+ changeprop_long(w, "ATOM", "ATOM", &l, 0);
+ sync();
+ XIfEvent(display, &e, findtime, (void*)w);
+}
+
+uint
+flushevents(long event_mask, bool dispatch) {
+ XEvent ev;
+ uint n = 0;
+
+ while(XCheckMaskEvent(display, event_mask, &ev)) {
+ if(dispatch)
+ dispatch_event(&ev);
+ n++;
+ }
+ return n;
+}
+
+static Bool
+findenter(Display *d, XEvent *e, XPointer v) {
+ long *l;
+
+ USED(d);
+ l = (long*)v;
+ if(*l)
+ return false;
+ if(e->type == EnterNotify)
+ return true;
+ if(e->type == MotionNotify)
+ (*l)++;
+ return false;
+}
+
+/* This isn't perfect. If there were motion events in the queue
+ * before this was called, then it flushes nothing. If we don't
+ * check for them, we might lose a legitamate enter event.
+ */
+uint
+flushenterevents(void) {
+ XEvent e;
+ long l;
+ int n;
+
+ l = 0;
+ n = 0;
+ while(XCheckIfEvent(display, &e, findenter, (void*)&l))
+ n++;
+ return n;
+}
+
+static void
+buttonrelease(XButtonPressedEvent *ev) {
+ Window *w;
+
+ if((w = findwin(ev->window)))
+ handle(w, bup, ev);
+}
+
+static void
+buttonpress(XButtonPressedEvent *ev) {
+ Window *w;
+
+ if((w = findwin(ev->window)))
+ handle(w, bdown, ev);
+ else
+ XAllowEvents(display, ReplayPointer, ev->time);
+}
+
+static void
+configurerequest(XConfigureRequestEvent *ev) {
+ XWindowChanges wc;
+ Window *w;
+
+ if((w = findwin(ev->window)))
+ handle(w, configreq, ev);
+ else{
+ wc.x = ev->x;
+ wc.y = ev->y;
+ wc.width = ev->width;
+ wc.height = ev->height;
+ wc.border_width = ev->border_width;
+ wc.sibling = ev->above;
+ wc.stack_mode = ev->detail;
+ XConfigureWindow(display, ev->window, ev->value_mask, &wc);
+ }
+}
+
+static void
+configurenotify(XConfigureEvent *ev) {
+ Window *w;
+
+ ignoreenter = ev->serial;
+ if((w = findwin(ev->window)))
+ handle(w, config, ev);
+}
+
+static void
+clientmessage(XClientMessageEvent *ev) {
+
+ if(ewmh_clientmessage(ev))
+ return;
+ if(xdnd_clientmessage(ev))
+ return;
+}
+
+static void
+destroynotify(XDestroyWindowEvent *ev) {
+ Window *w;
+ Client *c;
+
+ if((w = findwin(ev->window)))
+ handle(w, destroy, ev);
+ else {
+ if((c = win2client(ev->window)))
+ fprint(2, "Badness: Unhandled DestroyNotify: "
+ "Client: %p, Window: %W, Name: %s\n",
+ c, &c->w, c->name);
+ }
+}
+
+static void
+enternotify(XCrossingEvent *ev) {
+ Window *w;
+
+ xtime = ev->time;
+ if(ev->mode != NotifyNormal)
+ return;
+
+ if((w = findwin(ev->window)))
+ handle(w, enter, ev);
+}
+
+static void
+leavenotify(XCrossingEvent *ev) {
+ Window *w;
+
+ xtime = ev->time;
+ if((w = findwin(ev->window)))
+ handle(w, leave, ev);
+}
+
+void
+print_focus(const char *fn, Client *c, const char *to) {
+ Dprint(DFocus, "%s() disp.focus:\n", fn);
+ Dprint(DFocus, "\t%C => %C\n", disp.focus, c);
+ Dprint(DFocus, "\t%s => %s\n", clientname(disp.focus), to);
+}
+
+static void
+focusin(XFocusChangeEvent *ev) {
+ Window *w;
+ Client *c;
+
+ /* Yes, we're focusing in on nothing, here. */
+ if(ev->detail == NotifyDetailNone) {
+ print_focus("focusin", &c_magic, "<magic[none]>");
+ disp.focus = &c_magic;
+ setfocus(screen->barwin, RevertToParent);
+ return;
+ }
+
+ if(!((ev->detail == NotifyNonlinear)
+ ||(ev->detail == NotifyNonlinearVirtual)
+ ||(ev->detail == NotifyVirtual)
+ ||(ev->detail == NotifyInferior)
+ ||(ev->detail == NotifyAncestor)))
+ return;
+ if((ev->mode == NotifyWhileGrabbed) && (disp.hasgrab != &c_root))
+ return;
+
+ if(ev->window == screen->barwin->xid) {
+ print_focus("focusin", nil, "<nil>");
+ disp.focus = nil;
+ }
+ else if((w = findwin(ev->window)))
+ handle(w, focusin, ev);
+ else if(ev->mode == NotifyGrab) {
+ /* Some unmanaged window has grabbed focus */
+ if((c = disp.focus)) {
+ print_focus("focusin", &c_magic, "<magic>");
+ disp.focus = &c_magic;
+ if(c->sel)
+ frame_draw(c->sel);
+ }
+ }
+}
+
+static void
+focusout(XFocusChangeEvent *ev) {
+ XEvent me;
+ Window *w;
+
+ if(!((ev->detail == NotifyNonlinear)
+ ||(ev->detail == NotifyNonlinearVirtual)
+ ||(ev->detail == NotifyVirtual)
+ ||(ev->detail == NotifyInferior)
+ ||(ev->detail == NotifyAncestor)))
+ return;
+ if(ev->mode == NotifyUngrab)
+ disp.hasgrab = nil;
+
+ if((ev->mode == NotifyGrab)
+ && XCheckMaskEvent(display, KeyPressMask, &me))
+ dispatch_event(&me);
+ else if((w = findwin(ev->window)))
+ handle(w, focusout, ev);
+}
+
+static void
+expose(XExposeEvent *ev) {
+ Window *w;
+
+ if(ev->count == 0)
+ if((w = findwin(ev->window)))
+ handle(w, expose, ev);
+}
+
+static void
+keypress(XKeyEvent *ev) {
+ Window *w;
+
+ xtime = ev->time;
+ if((w = findwin(ev->window)))
+ handle(w, kdown, ev);
+}
+
+static void
+mappingnotify(XMappingEvent *ev) {
+
+ XRefreshKeyboardMapping(ev);
+ if(ev->request == MappingKeyboard)
+ update_keys();
+}
+
+static void
+maprequest(XMapRequestEvent *ev) {
+ Window *w;
+
+ if((w = findwin(ev->parent)))
+ handle(w, mapreq, ev);
+}
+
+static void
+motionnotify(XMotionEvent *ev) {
+ Window *w;
+
+ xtime = ev->time;
+ if((w = findwin(ev->window)))
+ handle(w, motion, ev);
+}
+
+static void
+propertynotify(XPropertyEvent *ev) {
+ Window *w;
+
+ xtime = ev->time;
+ if((w = findwin(ev->window)))
+ handle(w, property, ev);
+}
+
+static void
+mapnotify(XMapEvent *ev) {
+ Window *w;
+
+ ignoreenter = ev->serial;
+ if((w = findwin(ev->window)))
+ handle(w, map, ev);
+}
+
+static void
+unmapnotify(XUnmapEvent *ev) {
+ Window *w;
+
+ ignoreenter = ev->serial;
+ if((w = findwin(ev->window)) && (ev->event == w->parent->xid)) {
+ w->mapped = false;
+ if(ev->send_event || w->unmapped-- == 0)
+ handle(w, unmap, ev);
+ }
+}
+
+EvHandler handler[LASTEvent] = {
+ [ButtonPress] = (EvHandler)buttonpress,
+ [ButtonRelease] = (EvHandler)buttonrelease,
+ [ConfigureRequest] = (EvHandler)configurerequest,
+ [ConfigureNotify] = (EvHandler)configurenotify,
+ [ClientMessage] = (EvHandler)clientmessage,
+ [DestroyNotify] = (EvHandler)destroynotify,
+ [EnterNotify] = (EvHandler)enternotify,
+ [Expose] = (EvHandler)expose,
+ [FocusIn] = (EvHandler)focusin,
+ [FocusOut] = (EvHandler)focusout,
+ [KeyPress] = (EvHandler)keypress,
+ [LeaveNotify] = (EvHandler)leavenotify,
+ [MapNotify] = (EvHandler)mapnotify,
+ [MapRequest] = (EvHandler)maprequest,
+ [MappingNotify] = (EvHandler)mappingnotify,
+ [MotionNotify] = (EvHandler)motionnotify,
+ [PropertyNotify] = (EvHandler)propertynotify,
+ [UnmapNotify] = (EvHandler)unmapnotify,
+};
+
+void
+check_x_event(IxpConn *c) {
+ XEvent ev;
+
+ USED(c);
+ while(XPending(display)) {
+ XNextEvent(display, &ev);
+ dispatch_event(&ev);
+ }
+}
+
diff --git a/cmd/wmii/ewmh.c b/cmd/wmii/ewmh.c
new file mode 100644
index 0000000..6509e5f
--- /dev/null
+++ b/cmd/wmii/ewmh.c
@@ -0,0 +1,544 @@
+/* Copyright ©2007-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include <limits.h>
+#include "fns.h"
+
+Window *ewmhwin;
+
+static void ewmh_getwinstate(Client*);
+static void ewmh_setstate(Client*, Atom, int);
+
+#define Net(x) ("_NET_" x)
+#define Action(x) Net("WM_ACTION_" x)
+#define State(x) Net("WM_STATE_" x)
+#define Type(x) Net("WM_WINDOW_TYPE_" x)
+#define NET(x) xatom(Net(x))
+#define ACTION(x) xatom(Action(x))
+#define STATE(x) xatom(State(x))
+#define TYPE(x) xatom(Type(x))
+
+void
+ewmh_init(void) {
+ WinAttr wa;
+ char myname[] = "wmii";
+ long win;
+
+ ewmhwin = createwindow(&scr.root,
+ Rect(0, 0, 1, 1), 0 /*depth*/,
+ InputOnly, &wa, 0);
+
+ win = ewmhwin->xid;
+ changeprop_long(&scr.root, Net("SUPPORTING_WM_CHECK"), "WINDOW", &win, 1);
+ changeprop_long(ewmhwin, Net("SUPPORTING_WM_CHECK"), "WINDOW", &win, 1);
+ changeprop_string(ewmhwin, Net("WM_NAME"), myname);
+
+ long zz[] = {0, 0};
+ changeprop_long(&scr.root, Net("DESKTOP_VIEWPORT"), "CARDINAL",
+ zz, 2);
+
+ long supported[] = {
+ /* Misc */
+ NET("SUPPORTED"),
+ /* Root Properties/Messages */
+ NET("ACTIVE_WINDOW"),
+ NET("CLOSE_WINDOW"),
+ NET("CURRENT_DESKTOP"),
+ /* Client Properties */
+ NET("FRAME_EXTENTS"),
+ NET("WM_DESKTOP"),
+ NET("WM_FULLSCREEN_MONITORS"),
+ NET("WM_NAME"),
+ NET("WM_STRUT"),
+ NET("WM_STRUT_PARTIAL"),
+ /* States */
+ NET("WM_STATE"),
+ STATE("DEMANDS_ATTENTION"),
+ STATE("FULLSCREEN"),
+ STATE("SHADED"),
+ /* Window Types */
+ NET("WM_WINDOW_TYPE"),
+ TYPE("DIALOG"),
+ TYPE("DOCK"),
+ TYPE("NORMAL"),
+ TYPE("SPLASH"),
+ /* Actions */
+ NET("WM_ALLOWED_ACTIONS"),
+ ACTION("FULLSCREEN"),
+ /* Desktops */
+ NET("DESKTOP_NAMES"),
+ NET("NUMBER_OF_DESKTOPS"),
+ /* Client List */
+ NET("CLIENT_LIST"),
+ NET("CLIENT_LIST_STACKING"),
+ };
+ changeprop_long(&scr.root, Net("SUPPORTED"), "ATOM", supported, nelem(supported));
+}
+
+void
+ewmh_updateclientlist(void) {
+ Vector_long vec;
+ Client *c;
+
+ vector_linit(&vec);
+ for(c=client; c; c=c->next)
+ vector_lpush(&vec, c->w.xid);
+ changeprop_long(&scr.root, Net("CLIENT_LIST"), "WINDOW", vec.ary, vec.n);
+ free(vec.ary);
+}
+
+void
+ewmh_updatestacking(void) {
+ Vector_long vec;
+ Frame *f;
+ Area *a;
+ View *v;
+ int s;
+
+ vector_linit(&vec);
+
+ for(v=view; v; v=v->next) {
+ foreach_column(v, s, a)
+ for(f=a->frame; f; f=f->anext)
+ if(f->client->sel == f)
+ vector_lpush(&vec, f->client->w.xid);
+ }
+ for(v=view; v; v=v->next) {
+ for(f=v->floating->stack; f; f=f->snext)
+ if(!f->snext) break;
+ for(; f; f=f->sprev)
+ if(f->client->sel == f)
+ vector_lpush(&vec, f->client->w.xid);
+ }
+
+ changeprop_long(&scr.root, Net("CLIENT_LIST_STACKING"), "WINDOW", vec.ary, vec.n);
+ vector_lfree(&vec);
+}
+
+void
+ewmh_initclient(Client *c) {
+ long allowed[] = {
+ ACTION("FULLSCREEN"),
+ };
+
+ changeprop_long(&c->w, Net("WM_ALLOWED_ACTIONS"), "ATOM",
+ allowed, nelem(allowed));
+ ewmh_getwintype(c);
+ ewmh_getwinstate(c);
+ ewmh_getstrut(c);
+ ewmh_updateclientlist();
+}
+
+void
+ewmh_destroyclient(Client *c) {
+ Ewmh *e;
+
+ ewmh_updateclientlist();
+
+ e = &c->w.ewmh;
+ if(e->timer)
+ if(!ixp_unsettimer(&srv, e->timer))
+ fprint(2, "Badness: %C: Can't unset timer\n", c);
+ free(c->strut);
+}
+
+static void
+pingtimeout(long id, void *v) {
+ Client *c;
+
+ USED(id);
+ c = v;
+ event("Unresponsive %C\n", c);
+ c->w.ewmh.ping = 0;
+ c->w.ewmh.timer = 0;
+}
+
+void
+ewmh_pingclient(Client *c) {
+ Ewmh *e;
+
+ if(!(c->proto & ProtoPing))
+ return;
+
+ e = &c->w.ewmh;
+ if(e->ping)
+ return;
+
+ client_message(c, Net("WM_PING"), c->w.xid);
+ e->ping = xtime++;
+ e->timer = ixp_settimer(&srv, PingTime, pingtimeout, c);
+}
+
+int
+ewmh_prop(Client *c, Atom a) {
+ if(a == NET("WM_WINDOW_TYPE"))
+ ewmh_getwintype(c);
+ else
+ if(a == NET("WM_STRUT_PARTIAL"))
+ ewmh_getstrut(c);
+ else
+ return 0;
+ return 1;
+}
+
+typedef struct Prop Prop;
+struct Prop {
+ char* name;
+ long mask;
+ Atom atom;
+};
+
+static long
+getmask(Prop *props, ulong *vals, int n) {
+ Prop *p;
+ long ret;
+
+ if(props[0].atom == 0)
+ for(p=props; p->name; p++)
+ p->atom = xatom(p->name);
+
+ ret = 0;
+ while(n--) {
+ Dprint(DEwmh, "\tvals[%d] = \"%A\"\n", n, vals[n]);
+ for(p=props; p->name; p++)
+ if(p->atom == vals[n]) {
+ ret |= p->mask;
+ break;
+ }
+ }
+ return ret;
+}
+
+static long
+getprop_mask(Window *w, char *prop, Prop *props) {
+ ulong *vals;
+ long n, mask;
+
+ n = getprop_ulong(w, prop, "ATOM",
+ 0L, &vals, 16);
+ mask = getmask(props, vals, n);
+ free(vals);
+ return mask;
+}
+
+void
+ewmh_getwintype(Client *c) {
+ static Prop props[] = {
+ {Type("DESKTOP"), TypeDesktop},
+ {Type("DOCK"), TypeDock},
+ {Type("TOOLBAR"), TypeToolbar},
+ {Type("MENU"), TypeMenu},
+ {Type("UTILITY"), TypeUtility},
+ {Type("SPLASH"), TypeSplash},
+ {Type("DIALOG"), TypeDialog},
+ {Type("NORMAL"), TypeNormal},
+ {0, }
+ };
+ long mask;
+
+ mask = getprop_mask(&c->w, Net("WM_WINDOW_TYPE"), props);
+
+ c->w.ewmh.type = mask;
+ if(mask & TypeDock) {
+ c->borderless = 1;
+ c->titleless = 1;
+ }
+}
+
+static void
+ewmh_getwinstate(Client *c) {
+ ulong *vals;
+ long n;
+
+ n = getprop_ulong(&c->w, Net("WM_STATE"), "ATOM",
+ 0L, &vals, 16);
+ while(--n >= 0)
+ ewmh_setstate(c, vals[n], On);
+ free(vals);
+}
+
+long
+ewmh_protocols(Window *w) {
+ static Prop props[] = {
+ {"WM_DELETE_WINDOW", ProtoDelete},
+ {"WM_TAKE_FOCUS", ProtoTakeFocus},
+ {Net("WM_PING"), ProtoPing},
+ {0, }
+ };
+
+ return getprop_mask(w, "WM_PROTOCOLS", props);
+}
+
+void
+ewmh_getstrut(Client *c) {
+ enum {
+ Left, Right, Top, Bottom,
+ LeftMin, LeftMax,
+ RightMin, RightMax,
+ TopMin, TopMax,
+ BottomMin, BottomMax,
+ Last
+ };
+ long *strut;
+ ulong n;
+
+ if(c->strut == nil)
+ free(c->strut);
+ c->strut = nil;
+
+ n = getprop_long(&c->w, Net("WM_STRUT_PARTIAL"), "CARDINAL",
+ 0L, &strut, Last);
+ if(n != Last) {
+ free(strut);
+ n = getprop_long(&c->w, Net("WM_STRUT"), "CARDINAL",
+ 0L, &strut, 4L);
+ if(n != 4) {
+ free(strut);
+ return;
+ }
+ Dprint(DEwmh, "ewmh_getstrut(%C[%s]) Using WM_STRUT\n", c, clientname(c));
+ strut = erealloc(strut, Last * sizeof *strut);
+ strut[LeftMin] = strut[RightMin] = 0;
+ strut[LeftMax] = strut[RightMax] = INT_MAX;
+ strut[TopMin] = strut[BottomMin] = 0;
+ strut[TopMax] = strut[BottomMax] = INT_MAX;
+ }
+ c->strut = emalloc(sizeof *c->strut);
+ c->strut->left = Rect(0, strut[LeftMin], strut[Left], strut[LeftMax]);
+ c->strut->right = Rect(-strut[Right], strut[RightMin], 0, strut[RightMax]);
+ c->strut->top = Rect(strut[TopMin], 0, strut[TopMax], strut[Top]);
+ c->strut->bottom = Rect(strut[BottomMin], -strut[Bottom], strut[BottomMax], 0);
+ Dprint(DEwmh, "ewmh_getstrut(%C[%s])\n", c, clientname(c));
+ Dprint(DEwmh, "\ttop: %R\n", c->strut->top);
+ Dprint(DEwmh, "\tleft: %R\n", c->strut->left);
+ Dprint(DEwmh, "\tright: %R\n", c->strut->right);
+ Dprint(DEwmh, "\tbottom: %R\n", c->strut->bottom);
+ free(strut);
+ view_update(selview);
+}
+
+static void
+ewmh_setstate(Client *c, Atom state, int action) {
+
+ Dprint(DEwmh, "\tSTATE = %A\n", state);
+ if(state == 0)
+ return;
+
+ if(state == STATE("FULLSCREEN"))
+ fullscreen(c, action, -1);
+ else
+ if(state == STATE("DEMANDS_ATTENTION"))
+ client_seturgent(c, action, UrgClient);
+}
+
+int
+ewmh_clientmessage(XClientMessageEvent *e) {
+ Client *c;
+ View *v;
+ ulong *l;
+ ulong msg;
+ int action, i;
+
+ l = (ulong*)e->data.l;
+ msg = e->message_type;
+ Dprint(DEwmh, "ClientMessage: %A\n", msg);
+
+ if(msg == NET("WM_STATE")) {
+ enum {
+ StateUnset,
+ StateSet,
+ StateToggle,
+ };
+ if(e->format != 32)
+ return -1;
+ c = win2client(e->window);
+ if(c == nil)
+ return 0;
+ switch(l[0]) {
+ case StateUnset: action = Off; break;
+ case StateSet: action = On; break;
+ case StateToggle: action = Toggle; break;
+ default: return -1;
+ }
+ Dprint(DEwmh, "\tAction: %s\n", TOGGLE(action));
+ ewmh_setstate(c, l[1], action);
+ ewmh_setstate(c, l[2], action);
+ return 1;
+ }else
+ if(msg == NET("ACTIVE_WINDOW")) {
+ if(e->format != 32)
+ return -1;
+ Dprint(DEwmh, "\tsource: %ld\n", l[0]);
+ Dprint(DEwmh, "\twindow: 0x%lx\n", e->window);
+ c = win2client(e->window);
+ if(c == nil)
+ return 1;
+ Dprint(DEwmh, "\tclient: %s\n", clientname(c));
+ if(l[0] != 2)
+ return 1;
+ focus(c, true);
+ return 1;
+ }else
+ if(msg == NET("CLOSE_WINDOW")) {
+ if(e->format != 32)
+ return -1;
+ Dprint(DEwmh, "\tsource: %ld\n", l[0]);
+ Dprint(DEwmh, "\twindow: 0x%lx\n", e->window);
+ c = win2client(e->window);
+ if(c == nil)
+ return 1;
+ client_kill(c, true);
+ return 1;
+ }else
+ if(msg == NET("CURRENT_DESKTOP")) {
+ if(e->format != 32)
+ return -1;
+ for(v=view, i=l[0]; v; v=v->next, i--)
+ if(i == 0)
+ break;
+ Dprint(DEwmh, "\t%s\n", v->name);
+ if(i == 0)
+ view_select(v->name);
+ return 1;
+ }else
+ if(msg == xatom("WM_PROTOCOLS")) {
+ if(e->format != 32)
+ return 0;
+ Dprint(DEwmh, "\t%A\n", l[0]);
+ if(l[0] == NET("WM_PING")) {
+ if(e->window != scr.root.xid)
+ return -1;
+ c = win2client(l[2]);
+ if(c == nil)
+ return 1;
+ Dprint(DEwmh, "\tclient = [%C]\"%s\"\n", c, clientname(c));
+ Dprint(DEwmh, "\ttimer = %ld, ping = %ld\n",
+ c->w.ewmh.timer, c->w.ewmh.ping);
+ if(c->w.ewmh.timer)
+ ixp_unsettimer(&srv, c->w.ewmh.timer);
+ c->w.ewmh.timer = 0;
+ c->w.ewmh.ping = 0;
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+void
+ewmh_framesize(Client *c) {
+ Rectangle r;
+ Frame *f;
+
+ f = c->sel;
+ r.min.x = f->crect.min.x;
+ r.min.y = f->crect.min.y;
+ r.max.x = Dx(f->r) - f->crect.max.x;
+ r.max.y = Dy(f->r) - f->crect.max.y;
+
+ long extents[] = {
+ r.min.x, r.max.x,
+ r.min.y, r.max.y,
+ };
+ changeprop_long(&c->w, Net("FRAME_EXTENTS"), "CARDINAL",
+ extents, nelem(extents));
+}
+
+void
+ewmh_updatestate(Client *c) {
+ long state[16];
+ Frame *f;
+ int i;
+
+ f = c->sel;
+ if(f == nil || f->view != selview)
+ return;
+
+ i = 0;
+ if(f->collapsed)
+ state[i++] = STATE("SHADED");
+ if(c->fullscreen >= 0)
+ state[i++] = STATE("FULLSCREEN");
+ if(c->urgent)
+ state[i++] = STATE("DEMANDS_ATTENTION");
+
+ if(i > 0)
+ changeprop_long(&c->w, Net("WM_STATE"), "ATOM", state, i);
+ else
+ delproperty(&c->w, Net("WM_STATE"));
+
+ if(c->fullscreen >= 0)
+ changeprop_long(&c->w, Net("WM_FULLSCREEN_MONITORS"), "CARDINAL",
+ (long[]) { c->fullscreen, c->fullscreen,
+ c->fullscreen, c->fullscreen },
+ 4);
+ else
+ delproperty(&c->w, Net("WM_FULLSCREEN_MONITORS"));
+}
+
+/* Views */
+void
+ewmh_updateviews(void) {
+ View *v;
+ Vector_ptr tags;
+ long i;
+
+ if(starting)
+ return;
+
+ vector_pinit(&tags);
+ for(v=view, i=0; v; v=v->next, i++)
+ vector_ppush(&tags, v->name);
+ vector_ppush(&tags, nil);
+ changeprop_textlist(&scr.root, Net("DESKTOP_NAMES"), "UTF8_STRING", (char**)tags.ary);
+ changeprop_long(&scr.root, Net("NUMBER_OF_DESKTOPS"), "CARDINAL", &i, 1);
+ vector_pfree(&tags);
+ ewmh_updateview();
+ ewmh_updateclients();
+}
+
+static int
+viewidx(View *v) {
+ View *vp;
+ int i;
+
+ for(vp=view, i=0; vp; vp=vp->next, i++)
+ if(vp == v)
+ break;
+ assert(vp);
+ return i;
+}
+
+void
+ewmh_updateview(void) {
+ long i;
+
+ if(starting)
+ return;
+
+ i = viewidx(selview);
+ changeprop_long(&scr.root, Net("CURRENT_DESKTOP"), "CARDINAL", &i, 1);
+}
+
+void
+ewmh_updateclient(Client *c) {
+ long i;
+
+ i = -1;
+ if(c->sel)
+ i = viewidx(c->sel->view);
+ changeprop_long(&c->w, Net("WM_DESKTOP"), "CARDINAL", &i, 1);
+}
+
+void
+ewmh_updateclients(void) {
+ Client *c;
+
+ if(starting)
+ return;
+
+ for(c=client; c; c=c->next)
+ ewmh_updateclient(c);
+}
+
diff --git a/cmd/wmii/float.c b/cmd/wmii/float.c
new file mode 100644
index 0000000..23998d5
--- /dev/null
+++ b/cmd/wmii/float.c
@@ -0,0 +1,245 @@
+/* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include <limits.h>
+#include "fns.h"
+
+static void float_placeframe(Frame*);
+
+void
+float_attach(Area *a, Frame *f) {
+
+ f->client->floating = true;
+
+ f->r = f->floatr;
+ float_placeframe(f);
+ assert(a->sel != f);
+ frame_insert(f, a->sel);
+
+ if(a->sel == nil)
+ area_setsel(a, f);
+}
+
+void
+float_detach(Frame *f) {
+ Frame *pr;
+ Area *a, *sel, *oldsel;
+ View *v;
+
+ v = f->view;
+ a = f->area;
+ sel = view_findarea(v, v->selscreen, v->selcol, false);
+ oldsel = v->oldsel;
+ pr = f->aprev;
+
+ frame_remove(f);
+
+ if(a->sel == f) {
+ if(!pr)
+ pr = a->frame;
+ a->sel = nil;
+ area_setsel(a, pr);
+ }
+ f->area = nil;
+
+ if(oldsel)
+ area_focus(oldsel);
+ else if(!a->frame)
+ if(sel && sel->frame)
+ area_focus(sel);
+}
+
+void
+float_resizeframe(Frame *f, Rectangle r) {
+
+ if(f->area->view == selview)
+ client_resize(f->client, r);
+ else
+ frame_resize(f, r);
+}
+
+void
+float_arrange(Area *a) {
+ Frame *f;
+
+ assert(a->floating);
+
+ switch(a->mode) {
+ case Coldefault:
+ for(f=a->frame; f; f=f->anext)
+ f->collapsed = false;
+ break;
+ case Colstack:
+ for(f=a->frame; f; f=f->anext)
+ f->collapsed = (f != a->sel);
+ break;
+ default:
+ die("not reached");
+ break;
+ }
+ for(f=a->frame; f; f=f->anext)
+ f->r = f->floatr;
+ view_update(a->view);
+}
+
+static void
+rect_push(Vector_rect *vec, Rectangle r) {
+ Rectangle *rp;
+ int i;
+
+ for(i=0; i < vec->n; i++) {
+ rp = &vec->ary[i];
+ if(rect_contains_p(*rp, r))
+ return;
+ if(rect_contains_p(r, *rp)) {
+ *rp = r;
+ return;
+ }
+ }
+ vector_rpush(vec, r);
+}
+
+Vector_rect*
+unique_rects(Vector_rect *vec, Rectangle orig) {
+ static Vector_rect vec1, vec2;
+ Vector_rect *v1, *v2, *v;
+ Rectangle r1, r2;
+ int i, j;
+
+ v1 = &vec1;
+ v2 = &vec2;
+ v1->n = 0;
+ vector_rpush(v1, orig);
+ for(i=0; i < vec->n; i++) {
+ v2->n = 0;
+ r1 = vec->ary[i];
+ for(j=0; j < v1->n; j++) {
+ r2 = v1->ary[j];
+ if(!rect_intersect_p(r1, r2)) {
+ rect_push(v2, r2);
+ continue;
+ }
+ if(r2.min.x < r1.min.x)
+ rect_push(v2, Rect(r2.min.x, r2.min.y, r1.min.x, r2.max.y));
+ if(r2.min.y < r1.min.y)
+ rect_push(v2, Rect(r2.min.x, r2.min.y, r2.max.x, r1.min.y));
+ if(r2.max.x > r1.max.x)
+ rect_push(v2, Rect(r1.max.x, r2.min.y, r2.max.x, r2.max.y));
+ if(r2.max.y > r1.max.y)
+ rect_push(v2, Rect(r2.min.x, r1.max.y, r2.max.x, r2.max.y));
+ }
+ v = v1;
+ v1 = v2;
+ v2 = v;
+ }
+ return v1;
+}
+
+Rectangle
+max_rect(Vector_rect *vec) {
+ Rectangle *r, *rp;
+ int i, a, area;
+
+ area = 0;
+ r = 0;
+ for(i=0; i < vec->n; i++) {
+ rp = &vec->ary[i];
+ a = Dx(*rp) * Dy(*rp);
+ if(a > area) {
+ area = a;
+ r = rp;
+ }
+ }
+ return r ? *r : ZR;
+}
+
+static void
+float_placeframe(Frame *f) {
+ static Vector_rect vec;
+ Vector_rect *vp;
+ Rectangle r;
+ Point dim, p;
+ Client *c;
+ Frame *ff;
+ Area *a, *sel;
+ long area, l;
+ int i, s;
+
+ a = f->area;
+ c = f->client;
+
+ /*
+ if(c->trans)
+ return;
+ */
+
+ if(c->fullscreen >= 0 || c->w.hints->position || starting) {
+ f->r = f->floatr;
+ return;
+ }
+
+ /* Find all rectangles on the floating layer into which
+ * the new frame would fit.
+ */
+ vec.n = 0;
+ for(ff=a->frame; ff; ff=ff->anext)
+ /* TODO: Find out why this check is needed.
+ * The frame hasn't been inserted yet, but somehow,
+ * its old rectangle winds up in the list.
+ */
+ if(ff->client != f->client)
+ vector_rpush(&vec, ff->r);
+
+ /* Decide which screen we want to place this on.
+ * Ideally, it should probably Do the Right Thing
+ * when a screen fills, but what's the right thing?
+ * I think usage will show...
+ */
+ s = -1;
+ ff = client_groupframe(c, f->view);
+ if (f->screen >= 0)
+ s = f->screen;
+ else if (ff)
+ s = ownerscreen(ff->r);
+ else if (selclient())
+ s = ownerscreen(selclient()->sel->r);
+ else {
+ sel = view_findarea(a->view, a->view->selscreen, a->view->selcol, false);
+ if (sel)
+ s = sel->screen;
+ }
+
+ r = s == -1 ? a->r : screens[s]->r;
+ vp = unique_rects(&vec, r);
+
+ area = LONG_MAX;
+ dim.x = Dx(f->r);
+ dim.y = Dy(f->r);
+ p = ZP;
+
+ for(i=0; i < vp->n; i++) {
+ r = vp->ary[i];
+ if(Dx(r) < dim.x || Dy(r) < dim.y)
+ continue;
+ l = Dx(r) * Dy(r);
+ if(l < area) {
+ area = l;
+ p = r.min;
+ }
+ }
+
+ if(area == LONG_MAX) {
+ /* Cascade. */
+ s = max(s, 0);
+ ff = a->sel;
+ if(ff)
+ p = addpt(ff->r.min, Pt(Dy(ff->titlebar), Dy(ff->titlebar)));
+ if(p.x + Dx(f->r) > screens[s]->r.max.x ||
+ p.y + Dy(f->r) > screens[s]->r.max.y)
+ p = screens[s]->r.min;
+ }
+
+ f->floatr = rectsetorigin(f->r, p);
+}
+
diff --git a/cmd/wmii/fns.h b/cmd/wmii/fns.h
new file mode 100644
index 0000000..9b53ef9
--- /dev/null
+++ b/cmd/wmii/fns.h
@@ -0,0 +1,328 @@
+/* Copyright ©2007-2010 Kris Maglione <jg@suckless.org>
+ * See LICENSE file for license details.
+ */
+
+#ifdef VARARGCK
+# pragma varargck argpos debug 2
+# pragma varargck argpos dprint 1
+# pragma varargck argpos event 1
+# pragma varargck argpos warning 1
+#
+# pragma varargck type "a" Area*
+# pragma varargck type "C" Client*
+# pragma varargck type "r" void
+#endif
+
+#define _cond(cond, n) (cond) && __alive++ == n
+#define _cont(cont) (void)(__alive--, cont)
+
+#define with(type, var) \
+ for(type var=(type)-1; (var == (type)-1) && ((var=0) || true);)
+
+/* Grotesque, but worth it. */
+
+#define foreach_area(v, s, a) \
+ with(int, __alive) \
+ with(Area*, __anext) \
+ for(s=0; _cond(s <= nscreens, 0); _cont(s++)) \
+ for((a)=(s < nscreens ? (v)->areas[s] : v->floating), __anext=(a)->next; _cond(a, 1); _cont(((a)=__anext) && (__anext=(a)->next)))
+
+#define foreach_column(v, s, a) \
+ with(int, __alive) \
+ with(Area*, __anext) \
+ for(s=0; _cond(s < nscreens, 0); _cont(s++)) \
+ for((a)=(v)->areas[s], __anext=(a)->next; _cond(a, 1); _cont(((a)=__anext) && (__anext=(a)->next)))
+
+#define foreach_frame(v, s, a, f) \
+ with(Frame*, __fnext) \
+ foreach_area(v, s, a) \
+ for((void)(((f)=(a)->frame) && (__fnext=(f)->anext)); _cond(f, 2); _cont(((f)=__fnext) && (__fnext=(f)->anext)))
+
+#define btassert(arg, cond) \
+ (cond ? fprint(1, __FILE__":%d: failed assertion: " #cond "\n", __LINE__), backtrace(arg), true : false)
+
+/* area.c */
+int afmt(Fmt*);
+void area_attach(Area*, Frame*);
+Area* area_create(View*, Area *pos, int scrn, uint w);
+void area_destroy(Area*);
+void area_detach(Frame*);
+Area* area_find(View*, Rectangle, int, bool);
+void area_focus(Area*);
+int area_idx(Area*);
+void area_moveto(Area*, Frame*);
+char* area_name(Area*);
+Client* area_selclient(Area*);
+void area_setsel(Area*, Frame*);
+
+/* bar.c */
+Bar* bar_create(Bar**, const char*);
+void bar_destroy(Bar**, Bar*);
+void bar_draw(WMScreen*);
+Bar* bar_find(Bar*, const char*);
+void bar_init(WMScreen*);
+void bar_load(Bar*);
+void bar_resize(WMScreen*);
+void bar_sety(WMScreen*, int);
+void bar_setbounds(WMScreen*, int, int);
+
+/* client.c */
+int Cfmt(Fmt *f);
+bool client_applytags(Client*, const char*);
+void client_configure(Client*);
+Client* client_create(XWindow, XWindowAttributes*);
+void client_destroy(Client*);
+char* client_extratags(Client*);
+bool client_floats_p(Client*);
+void client_focus(Client*);
+Frame* client_groupframe(Client*, View*);
+void client_kill(Client*, bool);
+void client_manage(Client*);
+void client_map(Client*);
+void client_message(Client*, char*, long);
+void client_prop(Client*, Atom);
+void client_reparent(Client*, Window*, Point);
+void client_resize(Client*, Rectangle);
+void client_setcursor(Client*, Cursor);
+void client_seturgent(Client*, int, int);
+void client_setviews(Client*, char**);
+void client_unmap(Client*, int state);
+Frame* client_viewframe(Client *c, View *v);
+char* clientname(Client*);
+void focus(Client*, bool restack);
+void fullscreen(Client*, int, long);
+Client* group_leader(Group*);
+int map_frame(Client*);
+Client* selclient(void);
+int unmap_frame(Client*);
+void update_class(Client*);
+Client* win2client(XWindow);
+Rectangle client_grav(Client*, Rectangle);
+
+/* column.c */
+bool column_setmode(Area*, const char*);
+char* column_getmode(Area*);
+void column_arrange(Area*, bool dirty);
+void column_attach(Area*, Frame*);
+void column_attachrect(Area*, Frame*, Rectangle);
+void column_detach(Frame*);
+void column_frob(Area*);
+void column_insert(Area*, Frame*, Frame*);
+int column_minwidth(void);
+Area* column_new(View*, Area*, int, uint);
+void column_remove(Frame*);
+void column_resize(Area*, int);
+void column_resizeframe(Frame*, Rectangle);
+void column_settle(Area*);
+void div_draw(Divide*);
+void div_set(Divide*, int x);
+void div_update_all(void);
+bool find(Area**, Frame**, int, bool, bool);
+int stack_count(Frame*, int*);
+Frame* stack_find(Area*, Frame*, int, bool);
+
+/* error.c */
+#define waserror() setjmp(pusherror())
+void error(char*, ...);
+void nexterror(void);
+void poperror(void);
+jmp_buf* pusherror(void);
+
+/* event.c */
+void check_x_event(IxpConn*);
+void dispatch_event(XEvent*);
+uint flushenterevents(void);
+uint flushevents(long, bool dispatch);
+void print_focus(const char*, Client*, const char*);
+void xtime_kludge(void);
+
+/* ewmh.c */
+int ewmh_clientmessage(XClientMessageEvent*);
+void ewmh_destroyclient(Client*);
+void ewmh_framesize(Client*);
+void ewmh_getstrut(Client*);
+void ewmh_getwintype(Client*);
+void ewmh_init(void);
+void ewmh_initclient(Client*);
+void ewmh_pingclient(Client*);
+int ewmh_prop(Client*, Atom);
+long ewmh_protocols(Window*);
+void ewmh_updateclient(Client*);
+void ewmh_updateclientlist(void);
+void ewmh_updateclients(void);
+void ewmh_updatestacking(void);
+void ewmh_updatestate(Client*);
+void ewmh_updateview(void);
+void ewmh_updateviews(void);
+
+/* float.c */
+void float_arrange(Area*);
+void float_attach(Area*, Frame*);
+void float_detach(Frame*);
+void float_resizeframe(Frame*, Rectangle);
+Vector_rect* unique_rects(Vector_rect*, Rectangle);
+Rectangle max_rect(Vector_rect*);
+
+/* frame.c */
+Frame* frame_create(Client*, View*);
+int frame_delta_h(void);
+void frame_draw(Frame*);
+void frame_draw_all(void);
+void frame_focus(Frame*);
+uint frame_idx(Frame*);
+void frame_insert(Frame*, Frame *pos);
+void frame_remove(Frame*);
+void frame_resize(Frame*, Rectangle);
+bool frame_restack(Frame*, Frame*);
+void frame_swap(Frame*, Frame*);
+int ingrabbox_p(Frame*, int x, int y);
+void move_focus(Frame*, Frame*);
+Rectangle constrain(Rectangle, int);
+Rectangle frame_client2rect(Client*, Rectangle, bool);
+WinHints frame_gethints(Frame*);
+Rectangle frame_hints(Frame*, Rectangle, Align);
+Rectangle frame_rect2client(Client*, Rectangle, bool);
+
+/* fs.c */
+void fs_attach(Ixp9Req*);
+void fs_clunk(Ixp9Req*);
+void fs_create(Ixp9Req*);
+void fs_flush(Ixp9Req*);
+void fs_freefid(Fid*);
+void fs_open(Ixp9Req*);
+void fs_read(Ixp9Req*);
+void fs_remove(Ixp9Req*);
+void fs_stat(Ixp9Req*);
+void fs_walk(Ixp9Req*);
+void fs_write(Ixp9Req*);
+void event(const char*, ...);
+
+/* geom.c */
+Align get_sticky(Rectangle src, Rectangle dst);
+Cursor quad_cursor(Align);
+Align quadrant(Rectangle, Point);
+bool rect_contains_p(Rectangle, Rectangle);
+bool rect_haspoint_p(Point, Rectangle);
+bool rect_intersect_p(Rectangle, Rectangle);
+Rectangle rect_intersection(Rectangle, Rectangle);
+
+/* key.c */
+void init_lock_keys(void);
+void kpress(XWindow, ulong mod, KeyCode);
+void update_keys(void);
+
+/* main.c */
+void init_screens(void);
+void spawn_command(const char*);
+
+/* map.c */
+void** hash_get(Map*, const char*, bool create);
+void* hash_rm(Map*, const char*);
+void** map_get(Map*, ulong, bool create);
+void* map_rm(Map*, ulong);
+
+/* message.c */
+bool getlong(const char*, long*);
+bool getulong(const char*, ulong*);
+char* message_client(Client*, IxpMsg*);
+char* message_root(void*, IxpMsg*);
+char* message_view(View*, IxpMsg*);
+char* msg_debug(IxpMsg*);
+char* msg_getword(IxpMsg*);
+char* msg_parsecolors(IxpMsg*, CTuple*);
+char* msg_selectarea(Area*, IxpMsg*);
+char* msg_sendclient(View*, IxpMsg*, bool swap);
+char* readctl_client(Client*);
+char* readctl_root(void);
+char* readctl_view(View*);
+Area* strarea(View*, ulong, const char*);
+void warning(const char*, ...);
+/* debug */
+void debug(int, const char*, ...);
+void dprint(const char*, ...);
+void dwrite(int, void*, int, bool);
+bool setdebug(int);
+void vdebug(int, const char*, va_list);
+
+/* mouse.c */
+Window* constraintwin(Rectangle);
+void destroyconstraintwin(Window*);
+void grab_button(XWindow, uint button, ulong mod);
+void mouse_checkresize(Frame*, Point, bool);
+void mouse_movegrabbox(Client*, bool);
+void mouse_resize(Client*, Align, bool);
+void mouse_resizecol(Divide*);
+bool readmotion(Point*);
+int readmouse(Point*, uint*);
+Align snap_rect(const Rectangle *rects, int num, Rectangle *current, Align *mask, int snapw);
+
+/* print.c */
+int Ffmt(Fmt*);
+
+/* printevent.c */
+void printevent(XEvent*);
+
+/* root.c */
+void root_init(void);
+
+/* screen.c */
+void* findthing(Rectangle, int, Vector_ptr*, Rectangle(*)(void*), bool);
+int ownerscreen(Rectangle);
+
+/* rule.c */
+void trim(char *str, const char *chars);
+void update_rules(Rule**, const char*);
+
+/* view.c */
+void view_arrange(View*);
+void view_attach(View*, Frame*);
+View* view_create(const char*);
+void view_destroy(View*);
+void view_detach(Frame*);
+Area* view_findarea(View*, int, int, bool);
+void view_focus(WMScreen*, View*);
+bool view_fullscreen_p(View*, int);
+char* view_index(View*);
+void view_init(View*, int iscreen);
+char** view_names(void);
+uint view_newcolwidth(View*, int i);
+void view_restack(View*);
+void view_scale(View*, int, int);
+Client* view_selclient(View*);
+void view_select(const char*);
+void view_update(View*);
+void view_update_all(void);
+void view_update_rect(View*);
+Rectangle* view_rects(View*, uint *num, Frame *ignore);
+
+/* _util.c */
+void backtrace(char*);
+void closeexec(int);
+char** comm(int, char**, char**);
+int doublefork(void);
+void grep(char**, Reprog*, int);
+char* join(char**, char*);
+char* pathsearch(const char*, const char*, bool);
+void refree(Regex*);
+void reinit(Regex*, char*);
+int strlcatprint(char*, int, const char*, ...);
+int spawn3(int[3], const char*, char*[]);
+int spawn3l(int[3], const char*, ...);
+void uniq(char**);
+int unquote(char*, char*[], int);
+
+/* utf.c */
+char* toutf8(const char*);
+char* toutf8n(const char*, size_t);
+
+/* xdnd.c */
+int xdnd_clientmessage(XClientMessageEvent*);
+void xdnd_initwindow(Window*);
+
+/* xext.c */
+void randr_event(XEvent*);
+bool render_argb_p(Visual*);
+void xext_event(XEvent*);
+void xext_init(void);
+Rectangle* xinerama_screens(int*);
+
diff --git a/cmd/wmii/frame.c b/cmd/wmii/frame.c
new file mode 100644
index 0000000..6bedd82
--- /dev/null
+++ b/cmd/wmii/frame.c
@@ -0,0 +1,681 @@
+/* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include <math.h>
+#include "fns.h"
+
+uint
+frame_idx(Frame *f) {
+ Frame *fp;
+ uint i;
+
+ fp = f->area->frame;
+ for(i = 1; fp != f; fp = fp->anext)
+ i++;
+ return i;
+}
+
+Frame*
+frame_create(Client *c, View *v) {
+ static ushort id = 1;
+ Frame *f;
+
+ f = emallocz(sizeof *f);
+ f->id = id++;
+ f->client = c;
+ f->view = v;
+
+ if(c->sel) {
+ f->floatr = c->sel->floatr;
+ f->r = c->sel->r;
+ }else {
+ f->r = client_grav(c, c->r);
+ f->floatr = f->r;
+ c->sel = f;
+ }
+ f->collapsed = false;
+ f->screen = -1;
+ f->oldarea = -1;
+ f->oldscreen = -1;
+
+ return f;
+}
+
+void
+frame_remove(Frame *f) {
+ Area *a;
+
+ a = f->area;
+ if(f->aprev)
+ f->aprev->anext = f->anext;
+ if(f->anext)
+ f->anext->aprev = f->aprev;
+ if(f == a->frame)
+ a->frame = f->anext;
+
+ if(a->floating) {
+ if(f->sprev)
+ f->sprev->snext = f->snext;
+ if(f->snext)
+ f->snext->sprev = f->sprev;
+ if(f == a->stack)
+ a->stack = f->snext;
+ }
+ f->anext = f->aprev = f->snext = f->sprev = nil;
+}
+
+void
+frame_insert(Frame *f, Frame *pos) {
+ Area *a;
+
+ a = f->area;
+
+ if(pos) {
+ assert(pos != f);
+ f->aprev = pos;
+ f->anext = pos->anext;
+ }else {
+ assert(f->area->frame != f);
+ f->anext = f->area->frame;
+ f->area->frame = f;
+ }
+ if(f->aprev)
+ f->aprev->anext = f;
+ if(f->anext)
+ f->anext->aprev = f;
+
+ if(a->floating) {
+ assert(f->sprev == nil);
+ frame_restack(f, nil);
+ }
+}
+
+bool
+frame_restack(Frame *f, Frame *above) {
+ Client *c;
+ Frame *fp;
+ Area *a;
+
+ c = f->client;
+ a = f->area;
+ if(!a->floating)
+ return false;
+ if(f == above)
+ return false;
+
+ if(above == nil && !(c->w.ewmh.type & TypeDock))
+ for(fp=a->stack; fp; fp=fp->snext)
+ if(fp->client->w.ewmh.type & TypeDock)
+ above = fp;
+ else
+ break;
+
+ if(f->sprev || f == a->stack)
+ if(f->sprev == above)
+ return false;
+
+ if(f->sprev)
+ f->sprev->snext = f->snext;
+ else if(f->snext)
+ a->stack = f->snext;
+ if(f->snext)
+ f->snext->sprev = f->sprev;
+
+ f->sprev = above;
+ if(above == nil) {
+ f->snext = a->stack;
+ a->stack = f;
+ }
+ else {
+ f->snext = above->snext;
+ above->snext = f;
+ }
+ if(f->snext)
+ f->snext->sprev = f;
+ assert(f->snext != f && f->sprev != f);
+
+ return true;
+}
+
+/* Handlers */
+static void
+bup_event(Window *w, XButtonEvent *e) {
+ if((e->state & def.mod) != def.mod)
+ XAllowEvents(display, ReplayPointer, e->time);
+ else
+ XUngrabPointer(display, e->time);
+ event("ClientClick %C %d\n", w->aux, e->button);
+}
+
+static void
+bdown_event(Window *w, XButtonEvent *e) {
+ Frame *f;
+ Client *c;
+
+ c = w->aux;
+ f = c->sel;
+
+ if((e->state & def.mod) == def.mod) {
+ switch(e->button) {
+ case Button1:
+ focus(c, false);
+ mouse_resize(c, Center, true);
+ break;
+ case Button2:
+ frame_restack(f, nil);
+ view_restack(f->view);
+ focus(c, false);
+ grabpointer(c->framewin, nil, cursor[CurNone], ButtonReleaseMask);
+ break;
+ case Button3:
+ focus(c, false);
+ mouse_resize(c, quadrant(f->r, Pt(e->x_root, e->y_root)), true);
+ break;
+ default:
+ XAllowEvents(display, ReplayPointer, e->time);
+ break;
+ }
+ }else {
+ if(e->button == Button1) {
+ if(!e->subwindow) {
+ frame_restack(f, nil);
+ view_restack(f->view);
+ mouse_checkresize(f, Pt(e->x, e->y), true);
+ }
+
+ if(f->client != selclient())
+ focus(c, false);
+ }
+ if(e->subwindow)
+ XAllowEvents(display, ReplayPointer, e->time);
+ else {
+ /* Ungrab so a menu can receive events before the button is released */
+ XUngrabPointer(display, e->time);
+ sync();
+
+ event("ClientMouseDown %C %d\n", f->client, e->button);
+ }
+ }
+}
+
+static void
+config_event(Window *w, XConfigureEvent *e) {
+
+ USED(w, e);
+}
+
+static void
+enter_event(Window *w, XCrossingEvent *e) {
+ Client *c;
+ Frame *f;
+
+ c = w->aux;
+ f = c->sel;
+ if(disp.focus != c || selclient() != c) {
+ Dprint(DFocus, "enter_notify(f) => [%C]%s%s\n",
+ f->client, f->client->name,
+ ignoreenter == e->serial ? " (ignored)" : "");
+ if(e->detail != NotifyInferior)
+ if(e->serial != ignoreenter && (f->area->floating || !f->collapsed))
+ if(!(c->w.ewmh.type & TypeSplash))
+ focus(f->client, false);
+ }
+ mouse_checkresize(f, Pt(e->x, e->y), false);
+}
+
+static void
+expose_event(Window *w, XExposeEvent *e) {
+ Client *c;
+
+ USED(e);
+
+ c = w->aux;
+ if(c->sel)
+ frame_draw(c->sel);
+ else
+ fprint(2, "Badness: Expose event on a client frame which shouldn't be visible: %C\n",
+ c);
+}
+
+static void
+motion_event(Window *w, XMotionEvent *e) {
+ Client *c;
+
+ c = w->aux;
+ mouse_checkresize(c->sel, Pt(e->x, e->y), false);
+}
+
+Handlers framehandler = {
+ .bup = bup_event,
+ .bdown = bdown_event,
+ .config = config_event,
+ .enter = enter_event,
+ .expose = expose_event,
+ .motion = motion_event,
+};
+
+WinHints
+frame_gethints(Frame *f) {
+ WinHints h;
+ Client *c;
+ Rectangle r;
+ Point d;
+ int minh;
+
+ minh = labelh(def.font);
+
+ c = f->client;
+ h = *c->w.hints;
+
+ r = frame_client2rect(c, ZR, f->area->floating);
+ d = subpt(r.max, r.min);
+
+ if(!f->area->floating && def.incmode == IIgnore)
+ h.inc = Pt(1, 1);
+
+ if(h.min.x < 2*minh)
+ h.min.x = minh + (2*minh) % h.inc.x;
+ if(h.min.y < minh)
+ h.min.y = minh + minh % h.inc.y;
+
+ h.min.x += d.x;
+ h.min.y += d.y;
+ /* Guard against overflow. */
+ h.max.x = max(h.max.x + d.x, h.max.x);
+ h.max.y = max(h.max.y + d.y, h.max.y);
+
+ h.base.x += d.x;
+ h.base.y += d.y;
+ h.baspect.x += d.x;
+ h.baspect.y += d.y;
+
+ h.group = 0;
+ h.grav = ZP;
+ h.gravstatic = 0;
+ h.position = 0;
+ return h;
+}
+
+#define ADJ(PE, ME) \
+ if(c->fullscreen >= 0) \
+ return r; \
+ \
+ if(!floating) { \
+ r.min.x PE 1; \
+ r.min.y PE labelh(def.font); \
+ r.max.x ME 1; \
+ r.max.y ME 1; \
+ }else { \
+ if(!c->borderless) { \
+ r.min.x PE def.border; \
+ r.max.x ME def.border; \
+ r.max.y ME def.border; \
+ } \
+ if(!c->titleless) \
+ r.min.y PE labelh(def.font); \
+ } \
+
+Rectangle
+frame_rect2client(Client *c, Rectangle r, bool floating) {
+
+ ADJ(+=, -=)
+
+ /* Force clients to be at least 1x1 */
+ r.max.x = max(r.max.x, r.min.x+1);
+ r.max.y = max(r.max.y, r.min.y+1);
+ return r;
+}
+
+Rectangle
+frame_client2rect(Client *c, Rectangle r, bool floating) {
+
+ ADJ(-=, +=)
+
+ return r;
+}
+
+#undef ADJ
+
+void
+frame_resize(Frame *f, Rectangle r) {
+ Client *c;
+ Rectangle fr, cr;
+ int collapsed, dx;
+
+ if(btassert("8 full", Dx(r) <= 0 || Dy(r) < 0
+ || Dy(r) == 0 && (!f->area->max || resizing)
+ && !f->collapsed)) {
+ fprint(2, "Frame rect: %R\n", r);
+ r.max.x = min(r.min.x+1, r.max.x);
+ r.max.y = min(r.min.y+1, r.max.y);
+ }
+
+ c = f->client;
+ if(c->fullscreen >= 0) {
+ f->r = screens[c->fullscreen]->r;
+ f->crect = rectsetorigin(f->r, ZP);
+ return;
+ }
+
+ /*
+ if(f->area->floating)
+ f->collapsed = false;
+ */
+
+ fr = frame_hints(f, r, get_sticky(f->r, r));
+ if(f->area->floating && !c->strut)
+ fr = constrain(fr, -1);
+
+ /* Collapse managed frames which are too small */
+ /* XXX. */
+ collapsed = f->collapsed;
+ if(!f->area->floating && f->area->mode == Coldefault) {
+ f->collapsed = false;
+ if(Dy(r) < 2 * labelh(def.font))
+ f->collapsed = true;
+ }
+ if(collapsed != f->collapsed)
+ ewmh_updatestate(c);
+
+ fr.max.x = max(fr.max.x, fr.min.x + 2*labelh(def.font));
+ if(f->collapsed && f->area->floating)
+ fr.max.y = fr.min.y + labelh(def.font);
+
+ cr = frame_rect2client(c, fr, f->area->floating);
+ if(f->area->floating)
+ f->r = fr;
+ else {
+ f->r = r;
+ dx = Dx(r) - Dx(cr);
+ dx -= 2 * (cr.min.x - fr.min.x);
+ cr.min.x += dx / 2;
+ cr.max.x += dx / 2;
+ }
+ f->crect = rectsubpt(cr, f->r.min);
+
+ if(f->area->floating && !f->collapsed)
+ f->floatr = f->r;
+}
+
+static void
+pushlabel(Image *img, Rectangle *rp, char *s, CTuple *col) {
+ Rectangle r;
+ int w;
+
+ w = textwidth(def.font, s) + def.font->height;
+ w = min(w, Dx(*rp) - 30); /* Magic number. */
+ if(w > 0) {
+ r = *rp;
+ rp->max.x -= w;
+ if(0)
+ drawline(img, Pt(rp->max.x, r.min.y+2),
+ Pt(rp->max.x, r.max.y-2),
+ CapButt, 1, col->border);
+ drawstring(img, def.font, r, East,
+ s, col->fg);
+ }
+}
+
+void
+frame_draw(Frame *f) {
+ Rectangle r, fr;
+ Client *c;
+ CTuple *col;
+ Image *img;
+ char *s;
+ uint w;
+ int n, m;
+
+ if(f->view != selview)
+ return;
+ if(f->area == nil) /* Blech. */
+ return;
+
+ c = f->client;
+ img = *c->ibuf;
+ fr = rectsetorigin(c->framewin->r, ZP);
+
+ /* Pick colors. */
+ if(c == selclient() || c == disp.focus)
+ col = &def.focuscolor;
+ else
+ col = &def.normcolor;
+
+ /* Background/border */
+ r = fr;
+ fill(img, r, col->bg);
+ border(img, r, 1, col->border);
+
+ /* Title border */
+ r.max.y = r.min.y + labelh(def.font);
+ border(img, r, 1, col->border);
+
+ f->titlebar = insetrect(r, 3);
+ f->titlebar.max.y += 3;
+
+ /* Odd focus. Unselected, with keyboard focus. */
+ /* Draw a border just inside the titlebar. */
+ if(c != selclient() && c == disp.focus) {
+ border(img, insetrect(r, 1), 1, def.normcolor.bg);
+ border(img, insetrect(r, 2), 1, def.focuscolor.border);
+ }
+
+ /* grabbox */
+ r.min = Pt(2, 2);
+ r.max.y -= 2;
+ r.max.x = r.min.x + Dy(r);
+ f->grabbox = r;
+
+ if(c->urgent)
+ fill(img, r, col->fg);
+ border(img, r, 1, col->border);
+
+ /* Odd focus. Selected, without keyboard focus. */
+ /* Draw a border around the grabbox. */
+ if(c != disp.focus && col == &def.focuscolor)
+ border(img, insetrect(r, -1), 1, def.normcolor.bg);
+
+ /* Draw a border on borderless+titleless selected apps. */
+ if(f->area->floating && c->borderless && c->titleless && !c->fullscreen && c == selclient())
+ setborder(c->framewin, def.border, def.focuscolor.border);
+ else
+ setborder(c->framewin, 0, def.focuscolor.border);
+
+ /* Label */
+ r.min.x = r.max.x;
+ r.max.x = fr.max.x;
+ r.min.y = 0;
+ r.max.y = labelh(def.font);
+ /* Draw count on frames in 'max' columns. */
+ if(f->area->max && !resizing) {
+ /* XXX */
+ n = stack_count(f, &m);
+ s = smprint("%d/%d", m, n);
+ pushlabel(img, &r, s, col);
+ free(s);
+ }
+ /* Label clients with extra tags. */
+ if((s = client_extratags(c))) {
+ pushlabel(img, &r, s, col);
+ free(s);
+ }else /* Make sure floating clients have room for their indicators. */
+ if(c->floating)
+ r.max.x -= Dx(f->grabbox);
+ w = drawstring(img, def.font, r, West,
+ c->name, col->fg);
+
+ /* Draw inner border on floating clients. */
+ if(f->area->floating) {
+ r.min.x = r.min.x + w + 10;
+ r.max.x += Dx(f->grabbox) - 2;
+ r.min.y = f->grabbox.min.y;
+ r.max.y = f->grabbox.max.y;
+ border(img, r, 1, col->border);
+ }
+
+ /* Border increment gaps... */
+ r.min.y = f->crect.min.y;
+ r.min.x = max(1, f->crect.min.x - 1);
+ r.max.x = min(fr.max.x - 1, f->crect.max.x + 1);
+ r.max.y = min(fr.max.y - 1, f->crect.max.y + 1);
+ border(img, r, 1, col->border);
+
+ /* Why? Because some non-ICCCM-compliant apps feel the need to
+ * change the background properties of all of their ancestor windows
+ * in order to implement pseudo-transparency.
+ * What's more, the designers of X11 felt that it would be unfair to
+ * implementers to make it possible to detect, or forbid, such changes.
+ */
+ XSetWindowBackgroundPixmap(display, c->framewin->xid, None);
+
+ copyimage(c->framewin, fr, img, ZP);
+}
+
+void
+frame_draw_all(void) {
+ Client *c;
+
+ for(c=client; c; c=c->next)
+ if(c->sel && c->sel->view == selview)
+ frame_draw(c->sel);
+}
+
+void
+frame_swap(Frame *fa, Frame *fb) {
+ Frame **fp;
+ Client *c;
+
+ if(fa == fb) return;
+
+ for(fp = &fa->client->frame; *fp; fp = &fp[0]->cnext)
+ if(*fp == fa) break;
+ fp[0] = fp[0]->cnext;
+
+ for(fp = &fb->client->frame; *fp; fp = &fp[0]->cnext)
+ if(*fp == fb) break;
+ fp[0] = fp[0]->cnext;
+
+ c = fa->client;
+ fa->client = fb->client;
+ fb->client = c;
+ fb->cnext = c->frame;
+ c->frame = fb;
+
+ c = fa->client;
+ fa->cnext = c->frame;
+ c->frame = fa;
+
+ if(c->sel)
+ view_update(c->sel->view);
+}
+
+void
+move_focus(Frame *old_f, Frame *f) {
+ int noinput;
+
+ noinput = (old_f && old_f->client->noinput) ||
+ (f && f->client->noinput) ||
+ disp.hasgrab != &c_root;
+ if(noinput) {
+ if(old_f)
+ frame_draw(old_f);
+ if(f)
+ frame_draw(f);
+ }
+}
+
+void
+frame_focus(Frame *f) {
+ Frame *old_f, *ff;
+ View *v;
+ Area *a, *old_a;
+
+ v = f->view;
+ a = f->area;
+ old_a = v->sel;
+
+ if(0 && f->collapsed) {
+ for(ff=f; ff->collapsed && ff->anext; ff=ff->anext)
+ ;
+ for(; ff->collapsed && ff->aprev; ff=ff->aprev)
+ ;
+ /* XXX */
+ f->colr.max.y = f->colr.min.y + Dy(ff->colr);
+ ff->colr.max.y = ff->colr.min.y + labelh(def.font);
+ }else if(f->area->mode == Coldefault) {
+ for(; f->collapsed && f->anext; f=f->anext)
+ ;
+ for(; f->collapsed && f->aprev; f=f->aprev)
+ ;
+ }
+
+ old_f = old_a->sel;
+ a->sel = f;
+
+ if(a != old_a)
+ area_focus(f->area);
+ if(old_a != v->oldsel && f != old_f)
+ v->oldsel = nil;
+
+ if(v != selview || a != v->sel || resizing)
+ return;
+
+ move_focus(old_f, f);
+ if(a->floating)
+ float_arrange(a);
+ client_focus(f->client);
+
+ /*
+ if(!a->floating && ((a->mode == Colstack) || (a->mode == Colmax)))
+ */
+ column_arrange(a, false);
+}
+
+int
+frame_delta_h(void) {
+ return def.border + labelh(def.font);
+}
+
+Rectangle
+constrain(Rectangle r, int inset) {
+ WMScreen **sp;
+ WMScreen *s, *sbest;
+ Rectangle isect;
+ Point p;
+ int best, n;
+
+ if(inset < 0)
+ inset = Dy(screen->brect);
+ /*
+ * FIXME: This will cause problems for windows with
+ * D(r) < 2 * inset
+ */
+
+ SET(best);
+ sbest = nil;
+ for(sp=screens; (s = *sp); sp++) {
+ if (!screen->showing)
+ continue;
+ isect = rect_intersection(r, insetrect(s->r, inset));
+ if(Dx(isect) >= 0 && Dy(isect) >= 0)
+ return r;
+ if(Dx(isect) <= 0 && Dy(isect) <= 0)
+ n = max(Dx(isect), Dy(isect));
+ else
+ n = min(Dx(isect), Dy(isect));
+ if(!sbest || n > best) {
+ sbest = s;
+ best = n;
+ }
+ }
+
+ isect = insetrect(sbest->r, inset);
+ p = ZP;
+ p.x -= min(r.max.x - isect.min.x, 0);
+ p.x -= max(r.min.x - isect.max.x, 0);
+ p.y -= min(r.max.y - isect.min.y, 0);
+ p.y -= max(r.min.y - isect.max.y, 0);
+ return rectaddpt(r, p);
+}
+
diff --git a/cmd/wmii/fs.c b/cmd/wmii/fs.c
new file mode 100644
index 0000000..5fdfca4
--- /dev/null
+++ b/cmd/wmii/fs.c
@@ -0,0 +1,725 @@
+/* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include <ctype.h>
+#include <stdarg.h>
+#include <time.h>
+#include <unistd.h>
+#include "fns.h"
+
+typedef union IxpFileIdU IxpFileIdU;
+union IxpFileIdU {
+ Bar* bar;
+ Bar** bar_p;
+ CTuple* col;
+ Client* client;
+ Ruleset* rule;
+ View* view;
+ char* buf;
+ void* ref;
+};
+
+#include <ixp_srvutil.h>
+
+static IxpPending events;
+static IxpPending pdebug[NDebugOpt];
+
+/* Constants */
+enum { /* Dirs */
+ FsDBars,
+ FsDClient,
+ FsDClients,
+ FsDDebug,
+ FsDTag,
+ FsDTags,
+ FsRoot,
+ /* Files */
+ FsFBar,
+ FsFCctl,
+ FsFClabel,
+ FsFColRules,
+ FsFCtags,
+ FsFDebug,
+ FsFEvent,
+ FsFKeys,
+ FsFRctl,
+ FsFTagRules,
+ FsFTctl,
+ FsFTindex,
+ FsFprops,
+};
+
+/* Error messages */
+static char
+ Enoperm[] = "permission denied",
+ Enofile[] = "file not found",
+ Ebadvalue[] = "bad value",
+ Einterrupted[] = "interrupted";
+
+/* Macros */
+#define QID(t, i) (((vlong)((t)&0xFF)<<32)|((i)&0xFFFFFFFF))
+
+/* Global Vars */
+/***************/
+Ixp9Srv p9srv = {
+ .open= fs_open,
+ .walk= fs_walk,
+ .read= fs_read,
+ .stat= fs_stat,
+ .write= fs_write,
+ .clunk= fs_clunk,
+ .flush= fs_flush,
+ .attach=fs_attach,
+ .create=fs_create,
+ .remove=fs_remove,
+ .freefid=fs_freefid
+};
+
+/* ad-hoc file tree. Empty names ("") indicate dynamic entries to be filled
+ * in by lookup_file
+ */
+static IxpDirtab
+dirtab_root[]= {{".", QTDIR, FsRoot, 0500|DMDIR },
+ {"rbar", QTDIR, FsDBars, 0700|DMDIR },
+ {"lbar", QTDIR, FsDBars, 0700|DMDIR },
+ {"debug", QTDIR, FsDDebug, 0500|DMDIR, FLHide },
+ {"client", QTDIR, FsDClients, 0500|DMDIR },
+ {"tag", QTDIR, FsDTags, 0500|DMDIR },
+ {"ctl", QTAPPEND, FsFRctl, 0600|DMAPPEND },
+ {"colrules", QTFILE, FsFColRules, 0600 },
+ {"event", QTFILE, FsFEvent, 0600 },
+ {"keys", QTFILE, FsFKeys, 0600 },
+ {"tagrules", QTFILE, FsFTagRules, 0600 },
+ {nil}},
+dirtab_clients[]={{".", QTDIR, FsDClients, 0500|DMDIR },
+ {"", QTDIR, FsDClient, 0500|DMDIR },
+ {nil}},
+dirtab_client[]= {{".", QTDIR, FsDClient, 0500|DMDIR },
+ {"ctl", QTAPPEND, FsFCctl, 0600|DMAPPEND },
+ {"label", QTFILE, FsFClabel, 0600 },
+ {"tags", QTFILE, FsFCtags, 0600 },
+ {"props", QTFILE, FsFprops, 0400 },
+ {nil}},
+dirtab_debug[]= {{".", QTDIR, FsDDebug, 0500|DMDIR, FLHide },
+ {"", QTFILE, FsFDebug, 0400 },
+ {nil}},
+dirtab_bars[]= {{".", QTDIR, FsDBars, 0700|DMDIR },
+ {"", QTFILE, FsFBar, 0600 },
+ {nil}},
+dirtab_tags[]= {{".", QTDIR, FsDTags, 0500|DMDIR },
+ {"", QTDIR, FsDTag, 0500|DMDIR },
+ {nil}},
+dirtab_tag[]= {{".", QTDIR, FsDTag, 0500|DMDIR },
+ {"ctl", QTAPPEND, FsFTctl, 0600|DMAPPEND },
+ {"index", QTFILE, FsFTindex, 0400 },
+ {nil}};
+static IxpDirtab* dirtab[] = {
+ [FsRoot] = dirtab_root,
+ [FsDBars] = dirtab_bars,
+ [FsDClients] = dirtab_clients,
+ [FsDClient] = dirtab_client,
+ [FsDDebug] = dirtab_debug,
+ [FsDTags] = dirtab_tags,
+ [FsDTag] = dirtab_tag,
+};
+typedef char* (*MsgFunc)(void*, IxpMsg*);
+
+void
+event(const char *format, ...) {
+ va_list ap;
+
+ va_start(ap, format);
+ vsnprint(buffer, sizeof buffer, format, ap);
+ va_end(ap);
+
+ ixp_pending_write(&events, buffer, strlen(buffer));
+}
+
+static int dflags;
+
+bool
+setdebug(int flag) {
+ dflags = flag;
+ return true;
+}
+
+void
+vdebug(int flag, const char *fmt, va_list ap) {
+ char *s;
+
+ if(flag == 0)
+ flag = dflags;
+
+ if(!((debugflag|debugfile) & flag))
+ return;
+
+ s = vsmprint(fmt, ap);
+ dwrite(flag, s, strlen(s), false);
+ free(s);
+}
+
+void
+debug(int flag, const char *fmt, ...) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ vdebug(flag, fmt, ap);
+ va_end(ap);
+}
+
+void
+dprint(const char *fmt, ...) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ vdebug(0, fmt, ap);
+ va_end(ap);
+}
+
+void
+dwrite(int flag, void *buf, int n, bool always) {
+ int i;
+
+ if(flag == 0)
+ flag = dflags;
+
+ if(always || debugflag&flag)
+ write(2, buf, n);
+
+ if(debugfile&flag)
+ for(i=0; i < nelem(pdebug); i++)
+ if(flag & (1<<i))
+ ixp_pending_write(pdebug+i, buf, n);
+}
+
+static uint fs_size(IxpFileId*);
+
+static void
+dostat(Stat *s, IxpFileId *f) {
+ s->type = 0;
+ s->dev = 0;
+ s->qid.path = QID(f->tab.type, f->id);
+ s->qid.version = 0;
+ s->qid.type = f->tab.qtype;
+ s->mode = f->tab.perm;
+ s->atime = time(nil);
+ s->mtime = s->atime;
+ s->length = fs_size(f);;
+ s->name = f->tab.name;
+ s->uid = user;
+ s->gid = user;
+ s->muid = user;
+}
+
+/*
+ * All lookups and directory organization should be performed through
+ * lookup_file, mostly through the dirtab[] tree.
+ */
+static IxpFileId*
+lookup_file(IxpFileId *parent, char *name)
+{
+ IxpFileId *ret, *file, **last;
+ IxpDirtab *dir;
+ Client *c;
+ View *v;
+ Bar *b;
+ uint id;
+ int i;
+
+
+ if(!(parent->tab.perm & DMDIR))
+ return nil;
+ dir = dirtab[parent->tab.type];
+ last = &ret;
+ ret = nil;
+ for(; dir->name; dir++) {
+# define push_file(nam) \
+ file = ixp_srv_getfile(); \
+ *last = file; \
+ last = &file->next; \
+ file->tab = *dir; \
+ file->tab.name = estrdup(nam)
+ /* Dynamic dirs */
+ if(dir->name[0] == '\0') {
+ switch(parent->tab.type) {
+ case FsDClients:
+ if(!name || !strcmp(name, "sel")) {
+ if((c = selclient())) {
+ push_file("sel");
+ file->volatil = true;
+ file->p.client = c;
+ file->id = c->w.xid;
+ file->index = c->w.xid;
+ }
+ if(name)
+ goto LastItem;
+ }
+ SET(id);
+ if(name) {
+ id = (uint)strtol(name, &name, 16);
+ if(*name)
+ goto NextItem;
+ }
+ for(c=client; c; c=c->next) {
+ if(!name || c->w.xid == id) {
+ push_file(sxprint("%C", c));
+ file->volatil = true;
+ file->p.client = c;
+ file->id = c->w.xid;
+ file->index = c->w.xid;
+ assert(file->tab.name);
+ if(name)
+ goto LastItem;
+ }
+ }
+ break;
+ case FsDDebug:
+ for(i=0; i < nelem(pdebug); i++)
+ if(!name || !strcmp(name, debugtab[i])) {
+ push_file(debugtab[i]);
+ file->id = i;
+ if(name)
+ goto LastItem;
+ }
+ break;
+ case FsDTags:
+ if(!name || !strcmp(name, "sel")) {
+ if(selview) {
+ push_file("sel");
+ file->volatil = true;
+ file->p.view = selview;
+ file->id = selview->id;
+ }
+ if(name)
+ goto LastItem;
+ }
+ for(v=view; v; v=v->next) {
+ if(!name || !strcmp(name, v->name)) {
+ push_file(v->name);
+ file->volatil = true;
+ file->p.view = v;
+ file->id = v->id;
+ if(name)
+ goto LastItem;
+ }
+ }
+ break;
+ case FsDBars:
+ for(b=*parent->p.bar_p; b; b=b->next) {
+ if(!name || !strcmp(name, b->name)) {
+ push_file(b->name);
+ file->volatil = true;
+ file->p.bar = b;
+ file->id = b->id;
+ if(name)
+ goto LastItem;
+ }
+ }
+ break;
+ }
+ }else /* Static dirs */
+ if(!name && !(dir->flags & FLHide) || name && !strcmp(name, dir->name)) {
+ push_file(file->tab.name);
+ file->id = 0;
+ file->p.ref = parent->p.ref;
+ file->index = parent->index;
+ /* Special considerations: */
+ switch(file->tab.type) {
+ case FsDBars:
+ if(!strcmp(file->tab.name, "lbar"))
+ file->p.bar_p = &screen[0].bar[BLeft];
+ else
+ file->p.bar_p = &screen[0].bar[BRight];
+ file->id = (int)(uintptr_t)file->p.bar_p;
+ break;
+ case FsFColRules:
+ file->p.rule = &def.colrules;
+ break;
+ case FsFTagRules:
+ file->p.rule = &def.tagrules;
+ break;
+ }
+ if(name)
+ goto LastItem;
+ }
+ NextItem:
+ continue;
+# undef push_file
+ }
+LastItem:
+ *last = nil;
+ return ret;
+}
+
+/* Service Functions */
+void
+fs_attach(Ixp9Req *r) {
+ IxpFileId *f;
+
+ f = ixp_srv_getfile();
+ f->tab = dirtab[FsRoot][0];
+ f->tab.name = estrdup("/");
+ r->fid->aux = f;
+ r->fid->qid.type = f->tab.qtype;
+ r->fid->qid.path = QID(f->tab.type, 0);
+ r->ofcall.rattach.qid = r->fid->qid;
+ respond(r, nil);
+}
+
+void
+fs_walk(Ixp9Req *r) {
+
+ ixp_srv_walkandclone(r, lookup_file);
+}
+
+static uint
+fs_size(IxpFileId *f) {
+ switch(f->tab.type) {
+ default:
+ return 0;
+ case FsFColRules:
+ case FsFTagRules:
+ return f->p.rule->size;
+ case FsFKeys:
+ return def.keyssz;
+ case FsFCtags:
+ return strlen(f->p.client->tags);
+ case FsFClabel:
+ return strlen(f->p.client->name);
+ case FsFprops:
+ return strlen(f->p.client->props);
+ }
+}
+
+void
+fs_stat(Ixp9Req *r) {
+ IxpMsg m;
+ Stat s;
+ int size;
+ char *buf;
+ IxpFileId *f;
+
+ f = r->fid->aux;
+
+ if(!ixp_srv_verifyfile(f, lookup_file)) {
+ respond(r, Enofile);
+ return;
+ }
+
+ dostat(&s, f);
+ size = ixp_sizeof_stat(&s);
+ r->ofcall.rstat.nstat = size;
+ buf = emallocz(size);
+
+ m = ixp_message(buf, size, MsgPack);
+ ixp_pstat(&m, &s);
+
+ r->ofcall.rstat.stat = (uchar*)m.data;
+ respond(r, nil);
+}
+
+void
+fs_read(Ixp9Req *r) {
+ char *buf;
+ IxpFileId *f;
+ int n;
+
+ f = r->fid->aux;
+
+ if(!ixp_srv_verifyfile(f, lookup_file)) {
+ respond(r, Enofile);
+ return;
+ }
+
+ if(f->tab.perm & DMDIR && f->tab.perm & 0400) {
+ ixp_srv_readdir(r, lookup_file, dostat);
+ return;
+ }
+ else{
+ if(f->pending) {
+ ixp_pending_respond(r);
+ return;
+ }
+ switch(f->tab.type) {
+ case FsFprops:
+ ixp_srv_readbuf(r, f->p.client->props, strlen(f->p.client->props));
+ respond(r, nil);
+ return;
+ case FsFColRules:
+ case FsFTagRules:
+ ixp_srv_readbuf(r, f->p.rule->string, f->p.rule->size);
+ respond(r, nil);
+ return;
+ case FsFKeys:
+ ixp_srv_readbuf(r, def.keys, def.keyssz);
+ respond(r, nil);
+ return;
+ case FsFCtags:
+ ixp_srv_readbuf(r, f->p.client->tags, strlen(f->p.client->tags));
+ respond(r, nil);
+ return;
+ case FsFClabel:
+ ixp_srv_readbuf(r, f->p.client->name, strlen(f->p.client->name));
+ respond(r, nil);
+ return;
+ case FsFBar:
+ ixp_srv_readbuf(r, f->p.bar->buf, strlen(f->p.bar->buf));
+ respond(r, nil);
+ return;
+ case FsFRctl:
+ buf = readctl_root();
+ ixp_srv_readbuf(r, buf, strlen(buf));
+ respond(r, nil);
+ return;
+ case FsFCctl:
+ buf = readctl_client(f->p.client);
+ ixp_srv_readbuf(r, buf, strlen(buf));
+ respond(r, nil);
+ return;
+ case FsFTindex:
+ buf = view_index(f->p.view);
+ ixp_srv_readbuf(r, buf, strlen(buf));
+ respond(r, nil);
+ return;
+ case FsFTctl:
+ buf = readctl_view(f->p.view);
+ n = strlen(buf);
+ ixp_srv_readbuf(r, buf, n);
+ respond(r, nil);
+ return;
+ }
+ }
+ /* This should not be called if the file is not open for reading. */
+ die("Read called on an unreadable file");
+}
+
+void
+fs_write(Ixp9Req *r) {
+ MsgFunc mf;
+ IxpFileId *f;
+ char *errstr;
+ char *p;
+ uint i;
+
+ if(r->ifcall.io.count == 0) {
+ respond(r, nil);
+ return;
+ }
+ f = r->fid->aux;
+
+ if(!ixp_srv_verifyfile(f, lookup_file)) {
+ respond(r, Enofile);
+ return;
+ }
+
+ switch(f->tab.type) {
+ case FsFColRules:
+ case FsFTagRules:
+ ixp_srv_writebuf(r, &f->p.rule->string, &f->p.rule->size, 0);
+ respond(r, nil);
+ return;
+ case FsFKeys:
+ ixp_srv_writebuf(r, &def.keys, &def.keyssz, 0);
+ respond(r, nil);
+ return;
+ case FsFClabel:
+ ixp_srv_data2cstring(r);
+ utfecpy(f->p.client->name,
+ f->p.client->name+sizeof(client->name),
+ r->ifcall.io.data);
+ frame_draw(f->p.client->sel);
+ update_class(f->p.client);
+ r->ofcall.io.count = r->ifcall.io.count;
+ respond(r, nil);
+ return;
+ case FsFCtags:
+ ixp_srv_data2cstring(r);
+ client_applytags(f->p.client, r->ifcall.io.data);
+ r->ofcall.io.count = r->ifcall.io.count;
+ respond(r, nil);
+ return;
+ case FsFBar:
+ i = strlen(f->p.bar->buf);
+ p = f->p.bar->buf;
+ ixp_srv_writebuf(r, &p, &i, 279);
+ bar_load(f->p.bar);
+ r->ofcall.io.count = i - r->ifcall.io.offset;
+ respond(r, nil);
+ return;
+ case FsFCctl:
+ mf = (MsgFunc)message_client;
+ goto msg;
+ case FsFTctl:
+ mf = (MsgFunc)message_view;
+ goto msg;
+ case FsFRctl:
+ mf = (MsgFunc)message_root;
+ goto msg;
+ msg:
+ errstr = ixp_srv_writectl(r, mf);
+ r->ofcall.io.count = r->ifcall.io.count;
+ respond(r, errstr);
+ return;
+ case FsFEvent:
+ if(r->ifcall.io.data[r->ifcall.io.count-1] == '\n')
+ event("%.*s", (int)r->ifcall.io.count, r->ifcall.io.data);
+ else
+ event("%.*s\n", (int)r->ifcall.io.count, r->ifcall.io.data);
+ r->ofcall.io.count = r->ifcall.io.count;
+ respond(r, nil);
+ return;
+ }
+ /*
+ /* This should not be called if the file is not open for writing. */
+ die("Write called on an unwritable file");
+}
+
+void
+fs_open(Ixp9Req *r) {
+ IxpFileId *f;
+
+ f = r->fid->aux;
+
+ if(!ixp_srv_verifyfile(f, lookup_file)) {
+ respond(r, Enofile);
+ return;
+ }
+
+ switch(f->tab.type) {
+ case FsFEvent:
+ ixp_pending_pushfid(&events, r->fid);
+ break;
+ case FsFDebug:
+ ixp_pending_pushfid(pdebug+f->id, r->fid);
+ debugfile |= 1<<f->id;
+ break;
+ }
+
+ if((r->ifcall.topen.mode&3) == OEXEC
+ || (r->ifcall.topen.mode&3) != OREAD && !(f->tab.perm & 0200)
+ || (r->ifcall.topen.mode&3) != OWRITE && !(f->tab.perm & 0400)
+ || (r->ifcall.topen.mode & ~(3|OAPPEND|OTRUNC)))
+ respond(r, Enoperm);
+ else
+ respond(r, nil);
+}
+
+void
+fs_create(Ixp9Req *r) {
+ IxpFileId *f;
+
+ f = r->fid->aux;
+
+ switch(f->tab.type) {
+ default:
+ respond(r, Enoperm);
+ return;
+ case FsDBars:
+ if(!strlen(r->ifcall.tcreate.name)) {
+ respond(r, Ebadvalue);
+ return;
+ }
+ bar_create(f->p.bar_p, r->ifcall.tcreate.name);
+ f = lookup_file(f, r->ifcall.tcreate.name);
+ if(!f) {
+ respond(r, Enofile);
+ return;
+ }
+ r->ofcall.ropen.qid.type = f->tab.qtype;
+ r->ofcall.ropen.qid.path = QID(f->tab.type, f->id);
+ f->next = r->fid->aux;
+ r->fid->aux = f;
+ respond(r, nil);
+ break;
+ }
+}
+
+void
+fs_remove(Ixp9Req *r) {
+ IxpFileId *f;
+ WMScreen *s;
+
+ f = r->fid->aux;
+ if(!ixp_srv_verifyfile(f, lookup_file)) {
+ respond(r, Enofile);
+ return;
+ }
+
+
+ switch(f->tab.type) {
+ default:
+ respond(r, Enoperm);
+ return;
+ case FsFBar:
+ s = f->p.bar->screen;
+ bar_destroy(f->next->p.bar_p, f->p.bar);
+ bar_draw(s);
+ respond(r, nil);
+ break;
+ }
+}
+
+void
+fs_clunk(Ixp9Req *r) {
+ IxpFileId *f;
+
+ f = r->fid->aux;
+ if(!ixp_srv_verifyfile(f, lookup_file)) {
+ respond(r, nil);
+ return;
+ }
+
+ if(f->pending) {
+ /* Should probably be in freefid */
+ if(ixp_pending_clunk(r)) {
+ if(f->tab.type == FsFDebug)
+ debugfile &= ~(1<<f->id);
+ }
+ return;
+ }
+
+ switch(f->tab.type) {
+ case FsFColRules:
+ update_rules(&f->p.rule->rule, f->p.rule->string);
+ break;
+ case FsFTagRules:
+ update_rules(&f->p.rule->rule, f->p.rule->string);
+ /*
+ for(c=client; c; c=c->next)
+ apply_rules(c);
+ view_update_all();
+ */
+ break;
+ case FsFKeys:
+ update_keys();
+ break;
+ }
+ respond(r, nil);
+}
+
+void
+fs_flush(Ixp9Req *r) {
+ Ixp9Req *or;
+ IxpFileId *f;
+
+ or = r->oldreq;
+ f = or->fid->aux;
+ if(f->pending)
+ ixp_pending_flush(r);
+ /* else die() ? */
+ respond(r->oldreq, Einterrupted);
+ respond(r, nil);
+}
+
+void
+fs_freefid(Fid *f) {
+ IxpFileId *id, *tid;
+
+ tid = f->aux;
+ while((id = tid)) {
+ tid = id->next;
+ ixp_srv_freefile(id);
+ }
+}
+
diff --git a/cmd/wmii/geom.c b/cmd/wmii/geom.c
new file mode 100644
index 0000000..464eb67
--- /dev/null
+++ b/cmd/wmii/geom.c
@@ -0,0 +1,94 @@
+/* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include "fns.h"
+
+bool
+rect_haspoint_p(Point pt, Rectangle r) {
+ return (pt.x >= r.min.x) && (pt.x < r.max.x)
+ && (pt.y >= r.min.y) && (pt.y < r.max.y);
+}
+
+bool
+rect_intersect_p(Rectangle r, Rectangle r2) {
+ return r.min.x <= r2.max.x
+ && r.max.x >= r2.min.x
+ && r.min.y <= r2.max.y
+ && r.max.y >= r2.min.y;
+}
+
+Rectangle
+rect_intersection(Rectangle r, Rectangle r2) {
+ Rectangle ret;
+
+ /* ret != canonrect(ret) ≡ no intersection. */
+ ret.min.x = max(r.min.x, r2.min.x);
+ ret.max.x = min(r.max.x, r2.max.x);
+ ret.min.y = max(r.min.y, r2.min.y);
+ ret.max.y = min(r.max.y, r2.max.y);
+ return ret;
+}
+
+bool
+rect_contains_p(Rectangle r, Rectangle r2) {
+ return r2.min.x >= r.min.x
+ && r2.max.x <= r.max.x
+ && r2.min.y >= r.min.y
+ && r2.max.y <= r.max.y;
+}
+
+Align
+quadrant(Rectangle r, Point pt) {
+ Align ret;
+
+ pt = subpt(pt, r.min);
+ ret = 0;
+
+ if(pt.x >= Dx(r) * .5)
+ ret |= East;
+ if(pt.x <= Dx(r) * .5)
+ ret |= West;
+ if(pt.y <= Dy(r) * .5)
+ ret |= North;
+ if(pt.y >= Dy(r) * .5)
+ ret |= South;
+
+ return ret;
+}
+
+Cursor
+quad_cursor(Align align) {
+ switch(align) {
+ case NEast: return cursor[CurNECorner];
+ case NWest: return cursor[CurNWCorner];
+ case SEast: return cursor[CurSECorner];
+ case SWest: return cursor[CurSWCorner];
+ case South:
+ case North: return cursor[CurDVArrow];
+ case East:
+ case West: return cursor[CurDHArrow];
+ default: return cursor[CurMove];
+ }
+}
+
+Align
+get_sticky(Rectangle src, Rectangle dst) {
+ Align corner;
+
+ corner = 0;
+ if(src.min.x != dst.min.x
+ && src.max.x == dst.max.x)
+ corner |= East;
+ else
+ corner |= West;
+
+ if(src.min.y != dst.min.y
+ && src.max.y == dst.max.y)
+ corner |= South;
+ else
+ corner |= North;
+
+ return corner;
+}
+
diff --git a/cmd/wmii/key.c b/cmd/wmii/key.c
new file mode 100644
index 0000000..f2c3471
--- /dev/null
+++ b/cmd/wmii/key.c
@@ -0,0 +1,244 @@
+/* Copyright ©2006-2010 Kris Maglione <fbsdaemon at Gmail>
+ * Copyright ©2004-2006 Anselm R. Garbe <garbeam at gmail dot com>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include <X11/keysym.h>
+#include "fns.h"
+
+void
+init_lock_keys(void) {
+ static int masks[] = {
+ ShiftMask, LockMask, ControlMask, Mod1Mask, Mod2Mask,
+ Mod3Mask, Mod4Mask, Mod5Mask
+ };
+ XModifierKeymap *modmap;
+ KeyCode numlock;
+ int i, max;
+
+ numlock_mask = 0;
+ modmap = XGetModifierMapping(display);
+ numlock = keycode("Num_Lock");
+ if(numlock)
+ if(modmap && modmap->max_keypermod > 0) {
+ max = nelem(masks) * modmap->max_keypermod;
+ for(i = 0; i < max; i++)
+ if(modmap->modifiermap[i] == numlock)
+ numlock_mask = masks[i / modmap->max_keypermod];
+ }
+ XFreeModifiermap(modmap);
+ valid_mask = 255 & ~(numlock_mask | LockMask);
+}
+
+static void
+freekey(Key *k) {
+ Key *n;
+
+ while((n = k)) {
+ k = k->next;
+ free(n);
+ }
+}
+
+static void
+_grab(XWindow w, int keycode, uint mod) {
+ XGrabKey(display, keycode, mod, w,
+ true, GrabModeAsync, GrabModeAsync);
+}
+
+static void
+grabkey(Key *k) {
+ _grab(scr.root.xid, k->key, k->mod);
+ _grab(scr.root.xid, k->key, k->mod | LockMask);
+ if(numlock_mask) {
+ _grab(scr.root.xid, k->key, k->mod | numlock_mask);
+ _grab(scr.root.xid, k->key, k->mod | numlock_mask | LockMask);
+ }
+}
+
+static void
+ungrabkey(Key *k) {
+ XUngrabKey(display, k->key, k->mod, scr.root.xid);
+ XUngrabKey(display, k->key, k->mod | LockMask, scr.root.xid);
+ if(numlock_mask) {
+ XUngrabKey(display, k->key, k->mod | numlock_mask, scr.root.xid);
+ XUngrabKey(display, k->key, k->mod | numlock_mask | LockMask, scr.root.xid);
+ }
+}
+
+static Key *
+name2key(const char *name) {
+ Key *k;
+
+ for(k=key; k; k=k->lnext)
+ if(!strncmp(k->name, name, sizeof k->name))
+ return k;
+ return nil;
+}
+
+static Key*
+getkey(const char *name) {
+ Key *k, *r;
+ char buf[128];
+ char *seq[8];
+ char *kstr;
+ int mask;
+ uint i, toks;
+ static ushort id = 1;
+
+ r = nil;
+
+ if((k = name2key(name))) {
+ ungrabkey(k);
+ return k;
+ }
+ utflcpy(buf, name, sizeof buf);
+ toks = tokenize(seq, 8, buf, ',');
+ for(i = 0; i < toks; i++) {
+ if(!k)
+ r = k = emallocz(sizeof *k);
+ else {
+ k->next = emallocz(sizeof *k);
+ k = k->next;
+ }
+ utflcpy(k->name, name, sizeof k->name);
+ if(parsekey(seq[i], &mask, &kstr)) {
+ k->key = keycode(kstr);
+ k->mod = mask;
+ }
+ if(k->key == 0) {
+ freekey(r);
+ return nil;
+ }
+ }
+ if(r) {
+ r->id = id++;
+ r->lnext = key;
+ key = r;
+ }
+
+ return r;
+}
+
+static void
+next_keystroke(ulong *mod, KeyCode *code) {
+ XEvent e;
+ KeySym sym;
+ *mod = 0;
+
+ do {
+ XMaskEvent(display, KeyPressMask, &e);
+ *mod |= e.xkey.state & valid_mask;
+ *code = (KeyCode) e.xkey.keycode;
+ sym = XKeycodeToKeysym(display, e.xkey.keycode, 0);
+ } while(IsModifierKey(sym));
+}
+
+static void
+fake_keypress(ulong mod, KeyCode key) {
+ XKeyEvent e;
+ Client *c;
+
+ c = disp.focus;
+ if(c == nil || c->w.xid == 0)
+ return;
+
+ e.time = CurrentTime;
+ e.window = c->w.xid;
+ e.display = display;
+ e.state = mod;
+ e.keycode = key;
+
+ e.type = KeyPress;
+ sendevent(&c->w, true, KeyPressMask, (XEvent*)&e);
+ e.type = KeyRelease;
+ sendevent(&c->w, true, KeyReleaseMask, (XEvent*)&e);
+
+ sync();
+}
+
+static Key *
+match_keys(Key *k, ulong mod, KeyCode keycode, bool seq) {
+ Key *ret, *next;
+ volatile int i; /* shut up ken */
+
+ ret = nil;
+ for(next = k->tnext; k; i = (k=next) && (next=k->tnext)) {
+ if(seq)
+ k = k->next;
+ if(k && (k->mod == mod) && (k->key == keycode)) {
+ k->tnext = ret;
+ ret = k;
+ }
+ }
+ return ret;
+}
+
+static void
+kpress_seq(XWindow w, Key *done) {
+ ulong mod;
+ KeyCode key;
+ Key *found;
+
+ next_keystroke(&mod, &key);
+ found = match_keys(done, mod, key, true);
+ if((done->mod == mod) && (done->key == key))
+ fake_keypress(mod, key); /* double key */
+ else {
+ if(!found)
+ XBell(display, 0);
+ else if(!found->tnext && !found->next)
+ event("Key %s\n", found->name);
+ else
+ kpress_seq(w, found);
+ }
+}
+
+void
+kpress(XWindow w, ulong mod, KeyCode keycode) {
+ Key *k, *found;
+
+ for(k=key; k; k=k->lnext)
+ k->tnext = k->lnext;
+
+ found = match_keys(key, mod, keycode, false);
+ if(!found) /* grabbed but not found */
+ XBell(display, 0);
+ else if(!found->tnext && !found->next)
+ event("Key %s\n", found->name);
+ else {
+ XGrabKeyboard(display, w, true, GrabModeAsync, GrabModeAsync, CurrentTime);
+ flushevents(FocusChangeMask, true);
+ kpress_seq(w, found);
+ XUngrabKeyboard(display, CurrentTime);
+ }
+}
+
+void
+update_keys(void) {
+ Key *k;
+ char *l, *p;
+
+ init_lock_keys();
+ while((k = key)) {
+ key = key->lnext;
+ ungrabkey(k);
+ freekey(k);
+ }
+ for(l = p = def.keys; p && *p;) {
+ if(*p == '\n') {
+ *p = 0;
+ if((k = getkey(l)))
+ grabkey(k);
+ *p = '\n';
+ l = ++p;
+ }
+ else
+ p++;
+ }
+ if(l < p && strlen(l)) {
+ if((k = getkey(l)))
+ grabkey(k);
+ }
+}
+
diff --git a/cmd/wmii/layout.c b/cmd/wmii/layout.c
new file mode 100644
index 0000000..eb70302
--- /dev/null
+++ b/cmd/wmii/layout.c
@@ -0,0 +1,608 @@
+/* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include "fns.h"
+
+/* Here be dragons. */
+/* Actually, I'm happy to say, the dragons have dissipated. */
+
+enum {
+ ButtonMask =
+ ButtonPressMask | ButtonReleaseMask,
+ MouseMask =
+ ButtonMask | PointerMotionMask
+};
+
+static Handlers handlers;
+
+enum { OHoriz, OVert };
+typedef struct Framewin Framewin;
+struct Framewin {
+ /* Todo... give these better names. */
+ Window* w;
+ Rectangle grabbox;
+ Frame* f;
+ Area* ra;
+ Point pt;
+ int orientation;
+ int xy;
+ int screen;
+};
+
+static Rectangle
+framerect(Framewin *f) {
+ Rectangle r;
+ Point p;
+ int scrn;
+
+ r.min = ZP;
+ if(f->orientation == OHoriz) {
+ r.max.x = f->xy;
+ r.max.y = f->grabbox.max.y + f->grabbox.min.y;
+ }else {
+ r.max.x = f->grabbox.max.x + f->grabbox.min.x;
+ r.max.y = f->xy;
+ r = rectsubpt(r, Pt(Dx(r)/2, 0));
+ }
+ r = rectaddpt(r, f->pt);
+
+ scrn = f->screen;
+ if (scrn == -1)
+ scrn = max(ownerscreen(f->f->r), 0);
+
+ /* Keep onscreen */
+ p = ZP;
+ p.x -= min(0, r.min.x);
+ p.x -= max(0, r.max.x - screens[scrn]->r.max.x);
+ p.y -= max(0, r.max.y - screens[scrn]->brect.min.y - Dy(r)/2);
+ return rectaddpt(r, p);
+}
+
+static void
+frameadjust(Framewin *f, Point pt, int orientation, int xy) {
+ f->orientation = orientation;
+ f->xy = xy;
+ f->pt = pt;
+}
+
+static Framewin*
+framewin(Frame *f, Point pt, int orientation, int n) {
+ WinAttr wa;
+ Framewin *fw;
+
+ fw = emallocz(sizeof *fw);
+ wa.override_redirect = true;
+ wa.event_mask = ExposureMask;
+ fw->w = createwindow(&scr.root, Rect(0, 0, 1, 1),
+ scr.depth, InputOutput,
+ &wa, CWEventMask);
+ fw->w->aux = fw;
+ sethandler(fw->w, &handlers);
+
+ fw->f = f;
+ fw->screen = f->area->screen;
+ fw->grabbox = f->grabbox;
+ frameadjust(fw, pt, orientation, n);
+ reshapewin(fw->w, framerect(fw));
+
+ mapwin(fw->w);
+ raisewin(fw->w);
+
+ return fw;
+}
+
+static void
+framedestroy(Framewin *f) {
+ destroywindow(f->w);
+ free(f);
+}
+
+static void
+expose_event(Window *w, XExposeEvent *e) {
+ Rectangle r;
+ Framewin *f;
+ Image *buf;
+ CTuple *c;
+
+ USED(e);
+
+ f = w->aux;
+ c = &def.focuscolor;
+ buf = disp.ibuf;
+
+ r = rectsubpt(w->r, w->r.min);
+ fill(buf, r, c->bg);
+ border(buf, r, 1, c->border);
+ border(buf, f->grabbox, 1, c->border);
+ border(buf, insetrect(f->grabbox, -f->grabbox.min.x), 1, c->border);
+
+ copyimage(w, r, buf, ZP);
+}
+
+static Handlers handlers = {
+ .expose = expose_event,
+};
+
+static Area*
+find_area(Point pt) {
+ View *v;
+ Area *a;
+ int s;
+
+ v = selview;
+ for(s=0; s < nscreens; s++) {
+ if(!rect_haspoint_p(pt, screens[s]->r))
+ continue;
+ for(a=v->areas[s]; a; a=a->next)
+ if(pt.x < a->r.max.x)
+ return a;
+ }
+ return nil;
+}
+
+static void
+vplace(Framewin *fw, Point pt) {
+ Vector_long vec = {0};
+ Rectangle r;
+ Frame *f;
+ Area *a;
+ View *v;
+ long l;
+ int hr;
+
+ v = selview;
+
+ a = find_area(pt);
+ if(a == nil)
+ return;
+
+ fw->ra = a;
+ fw->screen = a->screen;
+
+ pt.x = a->r.min.x;
+ frameadjust(fw, pt, OHoriz, Dx(a->r));
+
+ r = fw->w->r;
+ hr = Dy(r)/2;
+ pt.y -= hr;
+
+ if(a->frame == nil)
+ goto done;
+
+ vector_lpush(&vec, a->frame->r.min.y);
+ for(f=a->frame; f; f=f->anext) {
+ if(f == fw->f)
+ vector_lpush(&vec, f->r.min.y + 0*hr);
+ else if(f->collapsed)
+ vector_lpush(&vec, f->r.min.y + 1*hr);
+ else
+ vector_lpush(&vec, f->r.min.y + 2*hr);
+ if(!f->collapsed && f->anext != fw->f)
+ vector_lpush(&vec, f->r.max.y - 2*hr);
+ }
+
+ for(int i=0; i < vec.n; i++) {
+ l = vec.ary[i];
+ if(abs(pt.y - l) < hr) {
+ pt.y = l;
+ break;
+ }
+ }
+ vector_lfree(&vec);
+
+done:
+ pt.x = a->r.min.x;
+ frameadjust(fw, pt, OHoriz, Dx(a->r));
+ reshapewin(fw->w, framerect(fw));
+}
+
+static void
+hplace(Framewin *fw, Point pt) {
+ Area *a;
+ View *v;
+ int minw;
+
+ v = selview;
+
+ a = find_area(pt);
+ if(a == nil)
+ return; /* XXX: Multihead. */
+
+ fw->screen = a->screen;
+ fw->ra = nil;
+ minw = column_minwidth();
+ if(abs(pt.x - a->r.min.x) < minw/2) {
+ pt.x = a->r.min.x;
+ fw->ra = a->prev;
+ }
+ else if(abs(pt.x - a->r.max.x) < minw/2) {
+ pt.x = a->r.max.x;
+ fw->ra = a;
+ }
+
+ pt.y = a->r.min.y;
+ frameadjust(fw, pt, OVert, Dy(a->r));
+ reshapewin(fw->w, framerect(fw));
+}
+
+static Point
+grabboxcenter(Frame *f) {
+ Point p;
+
+ p = addpt(f->r.min, f->grabbox.min);
+ p.x += Dx(f->grabbox)/2;
+ p.y += Dy(f->grabbox)/2;
+ return p;
+}
+
+static int tvcol(Frame*);
+static int thcol(Frame*);
+static int tfloat(Frame*);
+
+enum {
+ TDone,
+ TVCol,
+ THCol,
+ TFloat,
+};
+
+static int (*tramp[])(Frame*) = {
+ 0,
+ tvcol,
+ thcol,
+ tfloat,
+};
+
+/* Trampoline to allow properly tail recursive move/resize routines.
+ * We could probably get away with plain tail calls, but I don't
+ * like the idea.
+ */
+static void
+trampoline(int fn, Frame *f, bool grabbox) {
+
+ while(fn > 0) {
+ resizing = fn != TFloat;
+ view_update(f->view);
+ if(grabbox)
+ warppointer(grabboxcenter(f));
+ //f->collapsed = false;
+ fn = tramp[fn](f);
+ }
+ ungrabpointer();
+ resizing = false;
+ view_update(f->view);
+}
+
+void
+mouse_movegrabbox(Client *c, bool grabmod) {
+ Frame *f;
+ Point p;
+ float x, y;
+
+ f = c->sel;
+
+ SET(x);
+ SET(y);
+ if(grabmod) {
+ p = querypointer(f->client->framewin);
+ x = (float)p.x / Dx(f->r);
+ y = (float)p.y / Dy(f->r);
+ }
+
+ if(f->area->floating)
+ trampoline(TFloat, f, !grabmod);
+ else
+ trampoline(THCol, f, true);
+
+ if(grabmod)
+ warppointer(addpt(f->r.min, Pt(x * Dx(f->r),
+ y * Dy(f->r))));
+ else
+ warppointer(grabboxcenter(f));
+}
+
+static int
+_openstack_down(Frame *f, int h) {
+ int ret;
+ int dy;
+
+ if(f == nil)
+ return 0;;
+ ret = 0;
+ if(!f->collapsed) {
+ dy = Dy(f->colr) - labelh(def.font);
+ if(dy >= h) {
+ f->colr.min.y += h;
+ return h;
+ }else {
+ f->collapsed = true;
+ f->colr.min.y += dy;
+ ret = dy;
+ h -= dy;
+ }
+ }
+ dy = _openstack_down(f->anext, h);
+ f->colr.min.y += dy;
+ f->colr.max.y += dy;
+ return ret + dy;
+}
+
+static int
+_openstack_up(Frame *f, int h) {
+ int ret;
+ int dy;
+
+ if(f == nil)
+ return 0;
+ ret = 0;
+ if(!f->collapsed) {
+ dy = Dy(f->colr) - labelh(def.font);
+ if(dy >= h) {
+ f->colr.max.y -= h;
+ return h;
+ }else {
+ f->collapsed = true;
+ f->colr.max.y -= dy;
+ ret = dy;
+ h -= dy;
+ }
+ }
+ dy = _openstack_up(f->aprev, h);
+ f->colr.min.y -= dy;
+ f->colr.max.y -= dy;
+ return ret + dy;
+}
+
+static void
+column_openstack(Area *a, Frame *f, int h) {
+
+ if(f == nil)
+ _openstack_down(a->frame, h);
+ else {
+ h -= _openstack_down(f->anext, h);
+ if(h)
+ _openstack_up(f->aprev, h);
+ }
+}
+
+static void
+column_drop(Area *a, Frame *f, int y) {
+ Frame *ff;
+ int dy;
+
+ for(ff=a->frame; ff; ff=ff->anext)
+ assert(ff != f);
+
+ if(a->frame == nil || y <= a->frame->r.min.y) {
+ f->collapsed = true;
+ f->colr.min.y = 0;
+ f->colr.max.y = labelh(def.font);
+ column_openstack(a, nil, labelh(def.font));
+ column_insert(a, f, nil);
+ return;
+ }
+ for(ff=a->frame; ff->anext; ff=ff->anext)
+ if(y <= ff->colr.max.y) break;
+
+ y = max(y, ff->colr.min.y + labelh(def.font));
+ y = min(y, ff->colr.max.y);
+ dy = ff->colr.max.y - y;
+ if(dy <= labelh(def.font)) {
+ f->collapsed = true;
+ f->colr.min.y = 0;
+ f->colr.max.y = labelh(def.font);
+ column_openstack(a, ff, labelh(def.font) - dy);
+ }else {
+ f->colr.min.y = y;
+ f->colr.max.y = ff->colr.max.y;
+ ff->colr.max.y = y;
+ }
+ column_insert(a, f, ff);
+}
+
+static int
+thcol(Frame *f) {
+ Framewin *fw;
+ Frame *fp, *fn;
+ Area *a;
+ Point pt, pt2;
+ uint button;
+ int ret, collapsed;
+
+ focus(f->client, false);
+
+ ret = TDone;
+ if(!grabpointer(&scr.root, nil, cursor[CurIcon], MouseMask))
+ return TDone;
+
+ pt = querypointer(&scr.root);
+ pt2.x = f->area->r.min.x;
+ pt2.y = pt.y;
+ fw = framewin(f, pt2, OHoriz, Dx(f->area->r));
+
+ vplace(fw, pt);
+ for(;;)
+ switch (readmouse(&pt, &button)) {
+ case MotionNotify:
+ vplace(fw, pt);
+ break;
+ case ButtonRelease:
+ if(button != 1)
+ continue;
+ SET(collapsed);
+ SET(fp);
+ SET(fn);
+ a = f->area;
+ if(a->floating)
+ area_detach(f);
+ else {
+ collapsed = f->collapsed;
+ fp = f->aprev;
+ fn = f->anext;
+ column_remove(f);
+ if(!f->collapsed)
+ if(fp)
+ fp->colr.max.y = f->colr.max.y;
+ else if(fn && fw->pt.y > fn->r.min.y)
+ fn->colr.min.y = f->colr.min.y;
+ }
+
+ column_drop(fw->ra, f, fw->pt.y);
+ if(!a->floating && collapsed) {
+ /* XXX */
+ for(; fn && fn->collapsed; fn=fn->anext)
+ ;
+ if(fn == nil)
+ for(fn=fp; fn && fn->collapsed; fn=fn->aprev)
+ ;
+ if(fp)
+ fp->colr.max.x += labelh(def.font);
+ }
+
+
+ if(!a->frame && !a->floating && a->view->areas[a->screen]->next)
+ area_destroy(a);
+
+ frame_focus(f);
+ goto done;
+ case ButtonPress:
+ if(button == 2)
+ ret = TVCol;
+ else if(button == 3)
+ ret = TFloat;
+ else
+ continue;
+ goto done;
+ }
+done:
+ framedestroy(fw);
+ return ret;
+}
+
+static int
+tvcol(Frame *f) {
+ Framewin *fw;
+ Window *cwin;
+ WinAttr wa;
+ Rectangle r;
+ Point pt, pt2;
+ uint button;
+ int ret, scrn;
+
+ focus(f->client, false);
+
+ pt = querypointer(&scr.root);
+ pt2.x = pt.x;
+ pt2.y = f->area->r.min.y;
+
+ scrn = f->area->screen > -1 ? f->area->screen : find_area(pt) ? find_area(pt)->screen : 0;
+ r = f->view->r[scrn];
+ fw = framewin(f, pt2, OVert, Dy(r));
+
+ r.min.y += fw->grabbox.min.y + Dy(fw->grabbox)/2;
+ r.max.y = r.min.y + 1;
+ cwin = createwindow(&scr.root, r, 0, InputOnly, &wa, 0);
+ mapwin(cwin);
+
+ ret = TDone;
+ if(!grabpointer(&scr.root, cwin, cursor[CurIcon], MouseMask))
+ goto done;
+
+ hplace(fw, pt);
+ for(;;)
+ switch (readmouse(&pt, &button)) {
+ case MotionNotify:
+ hplace(fw, pt);
+ continue;
+ case ButtonPress:
+ if(button == 2)
+ ret = THCol;
+ else if(button == 3)
+ ret = TFloat;
+ else
+ continue;
+ goto done;
+ case ButtonRelease:
+ if(button != 1)
+ continue;
+ if(fw->ra) {
+ fw->ra = column_new(f->view, fw->ra, screen->idx, 0);
+ area_moveto(fw->ra, f);
+ }
+ goto done;
+ }
+
+done:
+ framedestroy(fw);
+ destroywindow(cwin);
+ return ret;
+}
+
+static int
+tfloat(Frame *f) {
+ Rectangle *rects;
+ Rectangle frect, origin;
+ Point pt, pt1;
+ Client *c;
+ Align align;
+ uint nrect, button;
+ int ret;
+
+ c = f->client;
+ if(!f->area->floating) {
+ if(f->anext)
+ f->anext->colr.min.y = f->colr.min.y;
+ else if(f->aprev)
+ f->aprev->colr.max.y = f->colr.max.y;
+ area_moveto(f->view->floating, f);
+ }
+ map_frame(f->client);
+ focus(f->client, false);
+
+ ret = TDone;
+ if(!grabpointer(c->framewin, nil, cursor[CurMove], MouseMask))
+ return TDone;
+
+ rects = view_rects(f->view, &nrect, f);
+ origin = f->r;
+ frect = f->r;
+
+ pt = querypointer(&scr.root);
+ /* pt1 = grabboxcenter(f); */
+ pt1 = pt;
+ goto case_motion;
+
+shut_up_ken:
+ for(;;pt1=pt)
+ switch (readmouse(&pt, &button)) {
+ default: goto shut_up_ken;
+ case MotionNotify:
+ case_motion:
+ origin = rectaddpt(origin, subpt(pt, pt1));
+ origin = constrain(origin, -1);
+ frect = origin;
+
+ align = Center;
+ snap_rect(rects, nrect, &frect, &align, def.snap);
+
+ frect = frame_hints(f, frect, Center);
+ frect = constrain(frect, -1);
+ client_resize(c, frect);
+ continue;
+ case ButtonRelease:
+ if(button != 1)
+ continue;
+ goto done;
+ case ButtonPress:
+ if(button != 3)
+ continue;
+ unmap_frame(f->client);
+ ret = THCol;
+ goto done;
+ }
+done:
+ free(rects);
+ return ret;
+}
+
diff --git a/cmd/wmii/main.c b/cmd/wmii/main.c
new file mode 100644
index 0000000..fba5d5f
--- /dev/null
+++ b/cmd/wmii/main.c
@@ -0,0 +1,470 @@
+/* Copyright ©2004-2006 Anselm R. Garbe <garbeam at gmail dot com>
+ * Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#define EXTERN
+#include "dat.h"
+#include <X11/Xproto.h>
+#include <X11/cursorfont.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <locale.h>
+#include <pwd.h>
+#include <sys/signal.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include "fns.h"
+
+static const char
+ version[] = "wmii-"VERSION", "COPYRIGHT"\n";
+
+static char* address;
+static char* ns_path;
+static int sleeperfd;
+static int sock;
+static int exitsignal;
+
+static struct sigaction sa;
+static struct passwd* passwd;
+
+static void
+usage(void) {
+ fatal("usage: wmii [-a <address>] [-r <wmiirc>] [-v]\n");
+}
+
+static int
+errfmt(Fmt *f) {
+ return fmtstrcpy(f, ixp_errbuf());
+}
+
+static void
+scan_wins(void) {
+ int i;
+ uint num;
+ XWindow *wins;
+ XWindowAttributes wa;
+ XWindow d1, d2;
+
+ if(XQueryTree(display, scr.root.xid, &d1, &d2, &wins, &num)) {
+ for(i = 0; i < num; i++) {
+ if(!XGetWindowAttributes(display, wins[i], &wa))
+ continue;
+ /* Skip transients. */
+ if(wa.override_redirect || XGetTransientForHint(display, wins[i], &d1))
+ continue;
+ if(wa.map_state == IsViewable)
+ client_create(wins[i], &wa);
+ }
+ /* Manage transients. */
+ for(i = 0; i < num; i++) {
+ if(!XGetWindowAttributes(display, wins[i], &wa))
+ continue;
+ if((XGetTransientForHint(display, wins[i], &d1))
+ && (wa.map_state == IsViewable))
+ client_create(wins[i], &wa);
+ }
+ }
+ if(wins)
+ XFree(wins);
+}
+
+static void
+init_ns(void) {
+ char *s;
+
+ if(address && strncmp(address, "unix!", 5) == 0) {
+ ns_path = estrdup(&address[5]);
+ s = strrchr(ns_path, '/');
+ if(s != nil)
+ *s = '\0';
+ if(ns_path[0] != '/' || ns_path[0] == '\0')
+ fatal("address %q is not an absolute path", address);
+ setenv("NAMESPACE", ns_path, true);
+ }else
+ ns_path = ixp_namespace();
+
+ if(ns_path == nil)
+ fatal("Bad namespace path: %r\n");
+}
+
+static void
+init_environment(void) {
+ init_ns();
+
+ if(address)
+ setenv("WMII_ADDRESS", address, true);
+ else
+ address = smprint("unix!%s/wmii", ns_path);
+ setenv("WMII_CONFPATH", sxprint("%s/.wmii%s:%s/wmii%s",
+ getenv("HOME"), CONFVERSION,
+ CONFPREFIX, CONFVERSION), true);
+}
+
+static void
+create_cursor(int ident, uint shape) {
+ cursor[ident] = XCreateFontCursor(display, shape);
+}
+
+static void
+init_cursors(void) {
+ static char zchar[1];
+ Pixmap pix;
+ XColor black, dummy;
+
+ create_cursor(CurNormal, XC_left_ptr);
+ create_cursor(CurNECorner, XC_top_right_corner);
+ create_cursor(CurNWCorner, XC_top_left_corner);
+ create_cursor(CurSECorner, XC_bottom_right_corner);
+ create_cursor(CurSWCorner, XC_bottom_left_corner);
+ create_cursor(CurMove, XC_fleur);
+ create_cursor(CurDHArrow, XC_sb_h_double_arrow);
+ create_cursor(CurDVArrow, XC_sb_v_double_arrow);
+ create_cursor(CurInput, XC_xterm);
+ create_cursor(CurSizing, XC_sizing);
+ create_cursor(CurIcon, XC_icon);
+ create_cursor(CurTCross, XC_tcross);
+
+ XAllocNamedColor(display, scr.colormap,
+ "black", &black, &dummy);
+ pix = XCreateBitmapFromData(
+ display, scr.root.xid,
+ zchar, 1, 1);
+
+ cursor[CurNone] = XCreatePixmapCursor(display,
+ pix, pix,
+ &black, &black,
+ 0, 0);
+
+ XFreePixmap(display, pix);
+}
+
+/*
+ * There's no way to check accesses to destroyed windows, thus
+ * those cases are ignored (especially on UnmapNotifies).
+ * Other types of errors call Xlib's default error handler, which
+ * calls exit().
+ */
+ErrorCode ignored_xerrors[] = {
+ { 0, BadWindow },
+ { X_SetInputFocus, BadMatch },
+ { X_PolyText8, BadDrawable },
+ { X_PolyFillRectangle, BadDrawable },
+ { X_PolySegment, BadDrawable },
+ { X_ConfigureWindow, BadMatch },
+ { X_GrabKey, BadAccess },
+ { X_GetAtomName, BadAtom },
+ { 0, }
+};
+
+void
+regerror(char *err) {
+ fprint(2, "%s: %s\n", argv0, err);
+}
+
+void
+init_screens(void) {
+ Rectangle *rects;
+ View *v;
+ int i, n, m;
+
+#ifdef notdef
+ d.x = Dx(scr.rect) - Dx(screen->r);
+ d.y = Dy(scr.rect) - Dy(screen->r);
+ for(v=view; v; v=v->next) {
+ v->r.max.x += d.x;
+ v->r.max.y += d.y;
+ }
+#endif
+
+ /* Reallocate screens, zero any new ones. */
+ rects = xinerama_screens(&n);
+ m = max(n, nscreens);
+ screens = erealloc(screens, (m + 1) * sizeof *screens);
+ screens[m] = nil;
+ for(v=view; v; v=v->next) {
+ v->areas = erealloc(v->areas, m * sizeof *v->areas);
+ v->r = erealloc(v->r, m * sizeof *v->r);
+ v->pad = erealloc(v->pad, m * sizeof *v->pad);
+ }
+
+ for(i=nscreens; i < m; i++) {
+ screens[i] = emallocz(sizeof *screens[i]);
+ for(v=view; v; v=v->next)
+ view_init(v, i);
+ }
+
+ nscreens = m;
+
+ /* Reallocate buffers. */
+ freeimage(ibuf);
+ freeimage(ibuf32);
+ ibuf = allocimage(Dx(scr.rect), Dy(scr.rect), scr.depth);
+ ibuf32 = nil; /* Probably shouldn't do this until it's needed. */
+ if(render_visual)
+ ibuf32 = allocimage(Dx(scr.rect), Dy(scr.rect), 32);
+ disp.ibuf = ibuf;
+ disp.ibuf32 = ibuf32;
+
+ /* Resize and initialize screens. */
+ for(i=0; i < nscreens; i++) {
+ screen = screens[i];
+ screen->idx = i;
+
+ screen->showing = i < n;
+ if(screen->showing)
+ screen->r = rects[i];
+ else
+ screen->r = rectsetorigin(screen->r, scr.rect.max);
+ def.snap = Dy(screen->r) / 63;
+ bar_init(screens[i]);
+ }
+ screen = screens[0];
+ if(selview)
+ view_update(selview);
+}
+
+static void
+cleanup(void) {
+ starting = -1;
+ while(client)
+ client_destroy(client);
+ ixp_server_close(&srv);
+ close(sleeperfd);
+}
+
+static void
+cleanup_handler(int signal) {
+ sa.sa_handler = SIG_DFL;
+ sigaction(signal, &sa, nil);
+
+ srv.running = false;
+
+ switch(signal) {
+ case SIGTERM:
+ sa.sa_handler = cleanup_handler;
+ sigaction(SIGALRM, &sa, nil);
+ alarm(1);
+ default:
+ exitsignal = signal;
+ break;
+ case SIGALRM:
+ raise(SIGTERM);
+ case SIGINT:
+ break;
+ }
+}
+
+static void
+init_traps(void) {
+ char buf[1];
+ int fd[2];
+
+ if(pipe(fd) != 0)
+ fatal("Can't pipe(): %r");
+
+ if(doublefork() == 0) {
+ close(fd[1]);
+ close(ConnectionNumber(display));
+ setsid();
+
+ display = XOpenDisplay(nil);
+ if(!display)
+ fatal("Can't open display");
+
+ /* Wait for parent to exit */
+ read(fd[0], buf, 1);
+
+ setfocus(pointerwin, RevertToPointerRoot);
+ XCloseDisplay(display);
+ exit(0);
+ }
+
+ close(fd[0]);
+ sleeperfd = fd[1];
+
+ sa.sa_flags = 0;
+ sa.sa_handler = cleanup_handler;
+ sigaction(SIGINT, &sa, nil);
+ sigaction(SIGTERM, &sa, nil);
+ sigaction(SIGQUIT, &sa, nil);
+ sigaction(SIGHUP, &sa, nil);
+ sigaction(SIGUSR1, &sa, nil);
+ sigaction(SIGUSR2, &sa, nil);
+}
+
+void
+spawn_command(const char *cmd) {
+ char *shell, *p;
+
+
+ if(doublefork() == 0) {
+ if((p = pathsearch(getenv("WMII_CONFPATH"), cmd, true)))
+ cmd = p;
+
+ if(setsid() == -1)
+ fatal("Can't setsid: %r");
+
+ /* Run through the user's shell as a login shell */
+ shell = passwd->pw_shell;
+ if(shell[0] != '/')
+ fatal("Shell is not an absolute path: %s", shell);
+ p = smprint("-%s", strrchr(shell, '/') + 1);
+
+ close(0);
+ open("/dev/null", O_RDONLY);
+
+ execl(shell, p, "-c", cmd, nil);
+ fatal("Can't exec '%s': %r", cmd);
+ /* NOTREACHED */
+ }
+}
+
+static void
+check_preselect(IxpServer *s) {
+ USED(s);
+
+ check_x_event(nil);
+}
+
+static void
+closedisplay(IxpConn *c) {
+ USED(c);
+
+ XCloseDisplay(display);
+}
+
+static void
+printfcall(IxpFcall *f) {
+ Dprint(D9p, "%F\n", f);
+}
+
+int
+main(int argc, char *argv[]) {
+ IxpMsg m;
+ char **oargv;
+ char *wmiirc, *s;
+ int i;
+
+ quotefmtinstall();
+ fmtinstall('r', errfmt);
+ fmtinstall('a', afmt);
+ fmtinstall('C', Cfmt);
+extern int fmtevent(Fmt*);
+ fmtinstall('E', fmtevent);
+
+ wmiirc = "wmiirc";
+
+ oargv = argv;
+ ARGBEGIN{
+ case 'a':
+ address = EARGF(usage());
+ break;
+ case 'r':
+ wmiirc = EARGF(usage());
+ break;
+ case 'v':
+ print("%s", version);
+ exit(0);
+ case 'D':
+ s = EARGF(usage());
+ m = ixp_message(s, strlen(s), 0);
+ msg_debug(&m);
+ break;
+ default:
+ usage();
+ break;
+ }ARGEND;
+
+ if(argc)
+ usage();
+
+ setlocale(LC_CTYPE, "");
+ starting = true;
+
+ initdisplay();
+
+ traperrors(true);
+ selectinput(&scr.root, EnterWindowMask
+ | SubstructureRedirectMask);
+ if(traperrors(false))
+ fatal("another window manager is already running");
+
+ passwd = getpwuid(getuid());
+ user = estrdup(passwd->pw_name);
+
+ init_environment();
+
+ fmtinstall('F', Ffmt);
+ ixp_printfcall = printfcall;
+
+ sock = ixp_announce(address);
+ if(sock < 0)
+ fatal("Can't create socket '%s': %r", address);
+ closeexec(ConnectionNumber(display));
+ closeexec(sock);
+
+ if(wmiirc[0])
+ spawn_command(wmiirc);
+
+ init_traps();
+ init_cursors();
+ init_lock_keys();
+ ewmh_init();
+ xext_init();
+
+ srv.preselect = check_preselect;
+ ixp_listen(&srv, sock, &p9srv, serve_9pcon, nil);
+ ixp_listen(&srv, ConnectionNumber(display), nil, check_x_event, closedisplay);
+
+ def.border = 1;
+ def.colmode = Colstack;
+ def.font = loadfont(FONT);
+ def.incmode = ISqueeze;
+
+ def.mod = Mod1Mask;
+ strcpy(def.grabmod, "Mod1");
+
+ loadcolor(&def.focuscolor, FOCUSCOLORS);
+ loadcolor(&def.normcolor, NORMCOLORS);
+
+ disp.sel = pointerscreen();
+
+ init_screens();
+ root_init();
+
+ disp.focus = nil;
+ setfocus(screen->barwin, RevertToParent);
+ view_select("1");
+
+ scan_wins();
+ starting = false;
+
+ view_update_all();
+ ewmh_updateviews();
+
+ event("FocusTag %s\n", selview->name);
+
+ i = ixp_serverloop(&srv);
+ if(i)
+ fprint(2, "%s: error: %r\n", argv0);
+ else
+ event("Quit");
+
+ cleanup();
+
+ if(exitsignal)
+ raise(exitsignal);
+ if(execstr) {
+ char *toks[32];
+ int n;
+
+ n = unquote(strdup(execstr), toks, nelem(toks)-1);
+ toks[n] = nil;
+ execvp(toks[0], toks);
+ fprint(2, "%s: failed to exec %q: %r\n", argv0, execstr);
+ execvp(argv0, oargv);
+ fatal("failed to exec myself");
+ }
+ return i;
+}
+
diff --git a/cmd/wmii/map.c b/cmd/wmii/map.c
new file mode 100644
index 0000000..08b137a
--- /dev/null
+++ b/cmd/wmii/map.c
@@ -0,0 +1,126 @@
+/* Written by Kris Maglione */
+/* Public domain */
+#include "dat.h"
+#include "fns.h"
+
+/* Edit s/^([a-zA-Z].*)\n([a-z].*) {/\1 \2;/g x/^([^a-zA-Z]|static|$)/-+d s/ (\*map|val|*str)//g */
+
+struct MapEnt {
+ ulong hash;
+ const char* key;
+ void* val;
+ MapEnt* next;
+};
+
+MapEnt *NM;
+
+/* By Dan Bernstein. Public domain. */
+static ulong
+hash(const char *str) {
+ ulong h;
+
+ h = 5381;
+ while (*str != '\0') {
+ h += h << 5; /* h *= 33 */
+ h ^= *str++;
+ }
+ return h;
+}
+
+static void
+insert(MapEnt **e, ulong val, const char *key) {
+ MapEnt *te;
+
+ te = emallocz(sizeof *te);
+ te->hash = val;
+ te->key = key;
+ te->next = *e;
+ *e = te;
+}
+
+static MapEnt**
+map_getp(Map *map, ulong val, int create) {
+ MapEnt **e;
+
+ e = &map->bucket[val%map->nhash];
+ for(; *e; e = &(*e)->next)
+ if((*e)->hash >= val) break;
+ if(*e == nil || (*e)->hash != val) {
+ if(create)
+ insert(e, val, nil);
+ else
+ e = &NM;
+ }
+ return e;
+}
+
+static MapEnt**
+hash_getp(Map *map, const char *str, int create) {
+ MapEnt **e;
+ ulong h;
+ int cmp;
+
+ h = hash(str);
+ e = map_getp(map, h, create);
+ if(*e && (*e)->key == nil)
+ (*e)->key = str;
+ else {
+ SET(cmp);
+ for(; *e; e = &(*e)->next)
+ if((*e)->hash > h || (cmp = strcmp((*e)->key, str)) >= 0)
+ break;
+ if(*e == nil || (*e)->hash > h || cmp > 0)
+ if(create)
+ insert(e, h, str);
+ }
+ return e;
+}
+
+void**
+map_get(Map *map, ulong val, bool create) {
+ MapEnt *e;
+
+ e = *map_getp(map, val, create);
+ return e ? &e->val : nil;
+}
+
+void**
+hash_get(Map *map, const char *str, bool create) {
+ MapEnt *e;
+
+ e = *hash_getp(map, str, create);
+ return e ? &e->val : nil;
+}
+
+void*
+map_rm(Map *map, ulong val) {
+ MapEnt **e, *te;
+ void *ret;
+
+ ret = nil;
+ e = map_getp(map, val, 0);
+ if(*e) {
+ te = *e;
+ ret = te->val;
+ *e = te->next;
+ free(te);
+ }
+ return ret;
+}
+
+void*
+hash_rm(Map *map, const char *str) {
+ MapEnt **e, *te;
+ void *ret;
+
+ ret = nil;
+ e = hash_getp(map, str, 0);
+ if(*e) {
+ te = *e;
+ ret = te->val;
+ *e = te->next;
+ free(te);
+ }
+ return ret;
+}
+
diff --git a/cmd/wmii/message.c b/cmd/wmii/message.c
new file mode 100644
index 0000000..d1dd4a7
--- /dev/null
+++ b/cmd/wmii/message.c
@@ -0,0 +1,1177 @@
+/* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include <ctype.h>
+#include "fns.h"
+
+static char* msg_grow(View*, IxpMsg*);
+static char* msg_nudge(View*, IxpMsg*);
+static char* msg_selectframe(Area*, IxpMsg*, int);
+static char* msg_sendframe(Frame*, int, bool);
+
+#define DIR(s) (\
+ s == LUP ? North : \
+ s == LDOWN ? South : \
+ s == LLEFT ? West : \
+ s == LRIGHT ? East : \
+ (abort(), 0))
+
+static char
+ Ebadcmd[] = "bad command",
+ Ebadvalue[] = "bad value",
+ Ebadusage[] = "bad usage";
+
+/* Edit |sort Edit |sed 's/"([^"]+)"/L\1/g' | tr 'a-z' 'A-Z' */
+enum {
+ LFULLSCREEN,
+ LURGENT,
+ LBAR,
+ LBORDER,
+ LCLIENT,
+ LCOLMODE,
+ LDEBUG,
+ LDOWN,
+ LEXEC,
+ LFOCUSCOLORS,
+ LFONT,
+ LFONTPAD,
+ LGRABMOD,
+ LGROW,
+ LINCMODE,
+ LKILL,
+ LLEFT,
+ LNORMCOLORS,
+ LNUDGE,
+ LOFF,
+ LON,
+ LQUIT,
+ LRIGHT,
+ LSELCOLORS,
+ LSELECT,
+ LSEND,
+ LSLAY,
+ LSPAWN,
+ LSWAP,
+ LTOGGLE,
+ LUP,
+ LVIEW,
+ LTILDE,
+};
+char *symtab[] = {
+ "Fullscreen",
+ "Urgent",
+ "bar",
+ "border",
+ "client",
+ "colmode",
+ "debug",
+ "down",
+ "exec",
+ "focuscolors",
+ "font",
+ "fontpad",
+ "grabmod",
+ "grow",
+ "incmode",
+ "kill",
+ "left",
+ "normcolors",
+ "nudge",
+ "off",
+ "on",
+ "quit",
+ "right",
+ "selcolors",
+ "select",
+ "send",
+ "slay",
+ "spawn",
+ "swap",
+ "toggle",
+ "up",
+ "view",
+ "~",
+};
+
+char* debugtab[] = {
+ "9p",
+ "dnd",
+ "event",
+ "ewmh",
+ "focus",
+ "generic",
+ "stack",
+};
+
+static char* barpostab[] = {
+ "bottom",
+ "top",
+};
+static char* incmodetab[] = {
+ "ignore",
+ "show",
+ "squeeze",
+};
+static char* toggletab[] = {
+ "off",
+ "on",
+ "toggle",
+};
+
+/* Edit ,y/^[a-zA-Z].*\n.* {\n/d
+ * Edit s/^([a-zA-Z].*)\n(.*) {\n/\1 \2;\n/
+ * Edit ,x/^static.*\n/d
+ */
+
+static int
+_bsearch(char *s, char **tab, int ntab) {
+ int i, n, m, cmp;
+
+ if(s == nil)
+ return -1;
+
+ n = ntab;
+ i = 0;
+ while(n) {
+ m = n/2;
+ cmp = strcmp(s, tab[i+m]);
+ if(cmp == 0)
+ return i+m;
+ if(cmp < 0 || m == 0)
+ n = m;
+ else {
+ i += m;
+ n = n-m;
+ }
+ }
+ return -1;
+}
+
+static int
+getsym(char *s) {
+ return _bsearch(s, symtab, nelem(symtab));
+}
+
+static bool
+setdef(int *ptr, char *s, char *tab[], int ntab) {
+ int i;
+
+ i = _bsearch(s, tab, ntab);
+ if(i >= 0)
+ *ptr = i;
+ return i >= 0;
+}
+
+static int
+gettoggle(char *s) {
+ switch(getsym(s)) {
+ case LON: return On;
+ case LOFF: return Off;
+ case LTOGGLE: return Toggle;
+ default:
+ return -1;
+ }
+}
+
+static int
+getdirection(IxpMsg *m) {
+ int i;
+
+ switch(i = getsym(msg_getword(m))) {
+ case LLEFT:
+ case LRIGHT:
+ case LUP:
+ case LDOWN:
+ return i;
+ default:
+ return -1;
+ }
+}
+
+static void
+eatrunes(IxpMsg *m, int (*p)(Rune), int val) {
+ Rune r;
+ int n;
+
+ while(m->pos < m->end) {
+ n = chartorune(&r, m->pos);
+ if(p(r) != val)
+ break;
+ m->pos += n;
+ }
+ if(m->pos > m->end)
+ m->pos = m->end;
+}
+
+char*
+msg_getword(IxpMsg *m) {
+ char *ret;
+ Rune r;
+ int n;
+
+ eatrunes(m, isspacerune, true);
+ ret = m->pos;
+ eatrunes(m, isspacerune, false);
+ n = chartorune(&r, m->pos);
+ *m->pos = '\0';
+ m->pos += n;
+ eatrunes(m, isspacerune, true);
+
+ /* Filter out comments. */
+ if(*ret == '#') {
+ *ret = '\0';
+ m->pos = m->end;
+ }
+ if(*ret == '\\')
+ if(ret[1] == '\\' || ret[1] == '#')
+ ret++;
+ if(*ret == '\0')
+ return nil;
+ return ret;
+}
+
+#define strbcmp(str, const) (strncmp((str), (const), sizeof(const)-1))
+static int
+getbase(const char **s, long *sign) {
+ const char *p;
+ int ret;
+
+ ret = 10;
+ *sign = 1;
+ if(**s == '-') {
+ *sign = -1;
+ *s += 1;
+ }else if(**s == '+')
+ *s += 1;
+
+ p = *s;
+ if(!strbcmp(p, "0x")) {
+ *s += 2;
+ ret = 16;
+ }
+ else if(isdigit(p[0])) {
+ if(p[1] == 'r') {
+ *s += 2;
+ ret = p[0] - '0';
+ }
+ else if(isdigit(p[1]) && p[2] == 'r') {
+ *s += 3;
+ ret = 10*(p[0]-'0') + (p[1]-'0');
+ }
+ }
+ else if(p[0] == '0') {
+ ret = 8;
+ }
+ if(ret != 10 && (**s == '-' || **s == '+'))
+ *sign = 0;
+ return ret;
+}
+
+static bool
+getint(const char *s, int *ret) {
+ long l;
+ bool res;
+
+ res = getlong(s, &l);
+ *ret = l;
+ return res;
+}
+
+bool
+getlong(const char *s, long *ret) {
+ const char *end;
+ char *rend;
+ int base;
+ long sign;
+
+ if(s == nil)
+ return false;
+ end = s+strlen(s);
+ base = getbase(&s, &sign);
+ if(sign == 0)
+ return false;
+
+ *ret = sign * strtol(s, &rend, base);
+ return (end == rend);
+}
+
+bool
+getulong(const char *s, ulong *ret) {
+ const char *end;
+ char *rend;
+ int base;
+ long sign;
+
+ if(s == nil)
+ return false;
+ end = s+strlen(s);
+ base = getbase(&s, &sign);
+ if(sign < 1)
+ return false;
+
+ *ret = strtoul(s, &rend, base);
+ return (end == rend);
+}
+
+static char*
+strend(char *s, int n) {
+ int len;
+
+ len = strlen(s);
+ return s + max(0, len - n);
+}
+
+static Client*
+strclient(View *v, char *s) {
+ ulong id;
+
+ /*
+ * sel
+ * 0x<window xid>
+ */
+
+ if(s == nil)
+ return nil;
+ if(!strcmp(s, "sel"))
+ return view_selclient(v);
+ if(getulong(s, &id))
+ return win2client(id);
+
+ return nil;
+}
+
+Area*
+strarea(View *v, ulong scrn, const char *s) {
+ Area *a;
+ char *p;
+ long i;
+
+ /*
+ * sel
+ * ~
+ * <column number>
+ */
+
+ if(s == nil)
+ return nil;
+
+ if((p = strchr(s, ':'))) {
+ *p++ = '\0';
+ if(!strcmp(s, "sel"))
+ scrn = v->selscreen;
+ else if(!getulong(s, &scrn))
+ return nil;
+ s = p;
+ }
+ else if(!strcmp(s, "sel"))
+ return v->sel;
+
+ if(!strcmp(s, "sel")) {
+ if(scrn != v->selscreen)
+ return nil;
+ return v->sel;
+ }
+ if(!strcmp(s, "~"))
+ return v->floating;
+ if(scrn < 0 || !getlong(s, &i) || i == 0)
+ return nil;
+
+ if(i > 0) {
+ for(a = v->areas[scrn]; a; a = a->next)
+ if(i-- == 1) break;
+ }
+ else {
+ /* FIXME: Switch to circularly linked list. */
+ for(a = v->areas[scrn]; a->next; a = a->next)
+ ;
+ for(; a; a = a->prev)
+ if(++i == 0) break;
+ }
+ return a;
+}
+
+static Frame*
+getframe(View *v, int scrn, IxpMsg *m) {
+ Client *c;
+ Frame *f;
+ Area *a;
+ char *s;
+ ulong l;
+
+ s = msg_getword(m);
+ if(!s || !strcmp(s, "client")) {
+ c = strclient(v, msg_getword(m));
+ if(c == nil)
+ return nil;
+ return client_viewframe(c, v);
+ }
+
+ /* XXX: Multihead */
+ a = strarea(v, scrn, s);
+ if(a == nil) {
+ fprint(2, "a == nil\n");
+ return nil;
+ }
+
+ s = msg_getword(m);
+ if(!s)
+ return nil;
+ if(!strcmp(s, "sel"))
+ return a->sel;
+ if(!getulong(s, &l))
+ return nil;
+ for(f=a->frame; f; f=f->anext)
+ if(--l == 0) break;
+ return f;
+}
+
+char*
+readctl_client(Client *c) {
+ bufclear();
+ bufprint("%C\n", c);
+ if(c->fullscreen >= 0)
+ bufprint("Fullscreen %d\n", c->fullscreen);
+ else
+ bufprint("Fullscreen off\n");
+ bufprint("Urgent %s\n", toggletab[(int)c->urgent]);
+ return buffer;
+}
+
+char*
+message_client(Client *c, IxpMsg *m) {
+ char *s;
+ long l;
+ int i;
+
+ s = msg_getword(m);
+
+ /*
+ * Toggle ::= on
+ * | off
+ * | toggle
+ * | <screen>
+ * Fullscreen <toggle>
+ * Urgent <toggle>
+ * kill
+ * slay
+ */
+
+ switch(getsym(s)) {
+ case LFULLSCREEN:
+ s = msg_getword(m);
+ if(getlong(s, &l))
+ fullscreen(c, On, l);
+ else {
+ i = gettoggle(s);
+ if(i == -1)
+ return Ebadusage;
+ fullscreen(c, i, -1);
+ }
+ break;
+ case LKILL:
+ client_kill(c, true);
+ break;
+ case LSLAY:
+ client_kill(c, false);
+ break;
+ case LURGENT:
+ i = gettoggle(msg_getword(m));
+ if(i == -1)
+ return Ebadusage;
+ client_seturgent(c, i, UrgManager);
+ break;
+ default:
+ return Ebadcmd;
+ }
+ return nil;
+}
+
+char*
+message_root(void *p, IxpMsg *m) {
+ Font *fn;
+ char *s, *ret;
+ ulong n;
+ int i;
+
+ USED(p);
+ ret = nil;
+ s = msg_getword(m);
+ if(s == nil)
+ return nil;
+
+ if(!strcmp(s, "backtrace")) {
+ backtrace(m->pos);
+ return nil;
+ }
+
+ switch(getsym(s)) {
+ case LBAR: /* bar on? <"top" | "bottom"> */
+ s = msg_getword(m);
+ if(!strcmp(s, "on"))
+ s = msg_getword(m);
+ if(!setdef(&screen->barpos, s, barpostab, nelem(barpostab)))
+ return Ebadvalue;
+ view_update(selview);
+ break;
+ case LBORDER:
+ if(!getulong(msg_getword(m), &n))
+ return Ebadvalue;
+ def.border = n;
+ view_update(selview);
+ break;
+ case LCOLMODE:
+ s = msg_getword(m);
+ if(!setdef(&def.colmode, s, modes, Collast))
+ return Ebadvalue;
+ break;
+ case LDEBUG:
+ ret = msg_debug(m);
+ break;
+ case LEXEC:
+ execstr = strdup(m->pos);
+ srv.running = 0;
+ break;
+ case LSPAWN:
+ spawn_command(m->pos);
+ break;
+ case LFOCUSCOLORS:
+ ret = msg_parsecolors(m, &def.focuscolor);
+ view_update(selview);
+ break;
+ case LFONT:
+ fn = loadfont(m->pos);
+ if(fn) {
+ freefont(def.font);
+ def.font = fn;
+ for(n=0; n < nscreens; n++)
+ bar_resize(screens[n]);
+ }else
+ ret = "can't load font";
+ view_update(selview);
+ break;
+ case LFONTPAD:
+ if(!getint(msg_getword(m), &def.font->pad.min.x) ||
+ !getint(msg_getword(m), &def.font->pad.max.x) ||
+ !getint(msg_getword(m), &def.font->pad.max.y) ||
+ !getint(msg_getword(m), &def.font->pad.min.y))
+ ret = "invalid rectangle";
+ else {
+ for(n=0; n < nscreens; n++)
+ bar_resize(screens[n]);
+ view_update(selview);
+ }
+ break;
+ case LGRABMOD:
+ s = msg_getword(m);
+ if(!parsekey(s, &i, nil) || i == 0)
+ return Ebadvalue;
+
+ utflcpy(def.grabmod, s, sizeof def.grabmod);
+ def.mod = i;
+ break;
+ case LINCMODE:
+ if(!setdef(&def.incmode, msg_getword(m), incmodetab, nelem(incmodetab)))
+ return Ebadvalue;
+ view_update(selview);
+ break;
+ case LNORMCOLORS:
+ ret = msg_parsecolors(m, &def.normcolor);
+ view_update(selview);
+ break;
+ case LSELCOLORS:
+ warning("selcolors have been removed");
+ return Ebadcmd;
+ case LVIEW:
+ view_select(m->pos);
+ break;
+ case LQUIT:
+ srv.running = 0;
+ break;
+ default:
+ return Ebadcmd;
+ }
+ return ret;
+}
+
+static void
+printdebug(int mask) {
+ int i, j;
+
+ for(i=0, j=0; i < nelem(debugtab); i++)
+ if(mask & (1<<i)) {
+ if(j++ > 0) bufprint(" ");
+ bufprint("%s", debugtab[i]);
+ }
+}
+
+char*
+readctl_root(void) {
+ bufclear();
+ bufprint("bar on %s\n", barpostab[screen->barpos]);
+ bufprint("border %d\n", def.border);
+ bufprint("colmode %s\n", modes[def.colmode]);
+ if(debugflag) {
+ bufprint("debug ");
+ printdebug(debugflag);
+ bufprint("\n");
+ }
+ if(debugfile) {
+ bufprint("debugfile ");
+ printdebug(debugfile);
+ bufprint("\n");
+ }
+ bufprint("focuscolors %s\n", def.focuscolor.colstr);
+ bufprint("font %s\n", def.font->name);
+ bufprint("fontpad %d %d %d %d\n", def.font->pad.min.x, def.font->pad.max.x,
+ def.font->pad.max.y, def.font->pad.min.y);
+ bufprint("grabmod %s\n", def.grabmod);
+ bufprint("incmode %s\n", incmodetab[def.incmode]);
+ bufprint("normcolors %s\n", def.normcolor.colstr);
+ bufprint("view %s\n", selview->name);
+ return buffer;
+}
+
+char*
+message_view(View *v, IxpMsg *m) {
+ Area *a;
+ char *s;
+
+ s = msg_getword(m);
+ if(s == nil)
+ return nil;
+
+ /*
+ * area ::= ~
+ * | <column number>
+ * | sel
+ * direction ::= left
+ * | right
+ * | up
+ * | down
+ * # This *should* be identical to <frame>
+ * place ::= <column number>
+ * #| ~ # This should be, but isn't.
+ * | <direction>
+ * | toggle
+ * colmode ::= default
+ * | stack
+ * | normal
+ * column ::= sel
+ * | <column number>
+ * frame ::= up
+ * | down
+ * | left
+ * | right
+ * | toggle
+ * | client <window xid>
+ * | sel
+ * | ~
+ * | <column> <frame number>
+ * | <column>
+ * amount ::=
+ * | <number>
+ * | <number>px
+ *
+ * colmode <area> <colmode>
+ * select <area>
+ * send <frame> <place>
+ * swap <frame> <place>
+ * grow <thing> <direction> <amount>
+ * nudge <thing> <direction> <amount>
+ * select <ordframe>
+ */
+
+ switch(getsym(s)) {
+ case LCOLMODE:
+ s = msg_getword(m);
+ a = strarea(v, screen->idx, s);
+ if(a == nil) /* || a->floating) */
+ return Ebadvalue;
+
+ s = msg_getword(m);
+ if(s == nil || !column_setmode(a, s))
+ return Ebadvalue;
+
+ column_arrange(a, false);
+ view_restack(v);
+
+ view_update(v);
+ return nil;
+ case LGROW:
+ return msg_grow(v, m);
+ case LNUDGE:
+ return msg_nudge(v, m);
+ case LSELECT:
+ return msg_selectarea(v->sel, m);
+ case LSEND:
+ return msg_sendclient(v, m, false);
+ case LSWAP:
+ return msg_sendclient(v, m, true);
+ default:
+ return Ebadcmd;
+ }
+ /* not reached */
+}
+
+char*
+readctl_view(View *v) {
+ Area *a;
+ int s;
+
+ bufclear();
+ bufprint("%s\n", v->name);
+
+ /* select <area>[ <frame>] */
+ bufprint("select %a", v->sel);
+ if(v->sel->sel)
+ bufprint(" %d", frame_idx(v->sel->sel));
+ bufprint("\n");
+
+ /* select client <client> */
+ if(v->sel->sel)
+ bufprint("select client %C\n", v->sel->sel->client);
+
+ foreach_area(v, s, a)
+ bufprint("colmode %a %s\n", a, column_getmode(a));
+ return buffer;
+}
+
+char*
+msg_debug(IxpMsg *m) {
+ char *opt;
+ int d;
+ char add;
+
+ bufclear();
+ while((opt = msg_getword(m))) {
+ add = '+';
+ if(opt[0] == '+' || opt[0] == '-')
+ add = *opt++;
+ d = _bsearch(opt, debugtab, nelem(debugtab));
+ if(d == -1) {
+ bufprint(", %s", opt);
+ continue;
+ }
+ if(add == '+')
+ debugflag |= 1<<d;
+ else
+ debugflag &= ~(1<<d);
+ }
+ if(buffer[0] != '\0')
+ return sxprint("Bad debug options: %s", buffer+2);
+ return nil;
+}
+
+static bool
+getamt(IxpMsg *m, Point *amt) {
+ char *s, *p;
+ long l;
+
+ s = msg_getword(m);
+ if(s) {
+ p = strend(s, 2);
+ if(!strcmp(p, "px")) {
+ *p = '\0';
+ amt->x = 1;
+ amt->y = 1;
+ }
+
+ if(!getlong(s, &l))
+ return false;
+ amt->x *= l;
+ amt->y *= l;
+ }
+ return true;
+}
+
+static char*
+msg_grow(View *v, IxpMsg *m) {
+ Client *c;
+ Frame *f;
+ Rectangle r;
+ Point amount;
+ int dir;
+
+ f = getframe(v, screen->idx, m);
+ if(f == nil)
+ return "bad frame";
+ c = f->client;
+
+ dir = getdirection(m);
+ if(dir == -1)
+ return "bad direction";
+
+ amount.x = Dy(f->titlebar);
+ amount.y = Dy(f->titlebar);
+ if(amount.x < c->w.hints->inc.x)
+ amount.x = c->w.hints->inc.x;
+ if(amount.y < c->w.hints->inc.y)
+ amount.y = c->w.hints->inc.y;
+
+ if(!getamt(m, &amount))
+ return Ebadvalue;
+
+ if(f->area->floating)
+ r = f->r;
+ else
+ r = f->colr;
+ switch(dir) {
+ case LLEFT: r.min.x -= amount.x; break;
+ case LRIGHT: r.max.x += amount.x; break;
+ case LUP: r.min.y -= amount.y; break;
+ case LDOWN: r.max.y += amount.y; break;
+ default: abort();
+ }
+
+ if(f->area->floating)
+ float_resizeframe(f, r);
+ else
+ column_resizeframe(f, r);
+
+ return nil;
+}
+
+static char*
+msg_nudge(View *v, IxpMsg *m) {
+ Frame *f;
+ Rectangle r;
+ Point amount;
+ int dir;
+
+ f = getframe(v, screen->idx, m);
+ if(f == nil)
+ return "bad frame";
+
+ dir = getdirection(m);
+ if(dir == -1)
+ return "bad direction";
+
+ amount.x = Dy(f->titlebar);
+ amount.y = Dy(f->titlebar);
+ if(!getamt(m, &amount))
+ return Ebadvalue;
+
+ if(f->area->floating)
+ r = f->r;
+ else
+ r = f->colr;
+ switch(dir) {
+ case LLEFT: r = rectaddpt(r, Pt(-amount.x, 0)); break;
+ case LRIGHT: r = rectaddpt(r, Pt( amount.x, 0)); break;
+ case LUP: r = rectaddpt(r, Pt(0, -amount.y)); break;
+ case LDOWN: r = rectaddpt(r, Pt(0, amount.y)); break;
+ default: abort();
+ }
+
+ if(f->area->floating)
+ float_resizeframe(f, r);
+ else
+ column_resizeframe(f, r);
+
+ return nil;
+}
+
+char*
+msg_parsecolors(IxpMsg *m, CTuple *col) {
+ static char Ebad[] = "bad color string";
+ Rune r;
+ char c, *p;
+ int i, j;
+
+ /* '#%6x #%6x #%6x' */
+ p = m->pos;
+ for(i = 0; i < 3 && p < m->end; i++) {
+ if(*p++ != '#')
+ return Ebad;
+ for(j = 0; j < 6; j++)
+ if(p >= m->end || !isxdigit(*p++))
+ return Ebad;
+
+ chartorune(&r, p);
+ if(i < 2) {
+ if(r != ' ')
+ return Ebad;
+ p++;
+ }else if(*p != '\0' && !isspacerune(r))
+ return Ebad;
+ }
+
+ c = *p;
+ *p = '\0';
+ loadcolor(col, m->pos);
+ *p = c;
+
+ m->pos = p;
+ eatrunes(m, isspacerune, true);
+ return nil;
+}
+
+char*
+msg_selectarea(Area *a, IxpMsg *m) {
+ Frame *f;
+ Area *ap;
+ View *v;
+ char *s;
+ ulong i;
+ int sym;
+
+ v = a->view;
+ s = msg_getword(m);
+ sym = getsym(s);
+
+ switch(sym) {
+ case LTOGGLE:
+ if(!a->floating)
+ ap = v->floating;
+ else if(v->revert && v->revert != a)
+ ap = v->revert;
+ else
+ ap = v->firstarea;
+ break;
+ case LLEFT:
+ case LRIGHT:
+ case LUP:
+ case LDOWN:
+ case LCLIENT:
+ return msg_selectframe(a, m, sym);
+ case LTILDE:
+ ap = v->floating;
+ break;
+ default:
+ /* XXX: Multihead */
+ ap = strarea(v, a->screen, s);
+ if(!ap || ap->floating)
+ return Ebadvalue;
+ if((s = msg_getword(m))) {
+ if(!getulong(s, &i))
+ return Ebadvalue;
+ for(f = ap->frame; f; f = f->anext)
+ if(--i == 0) break;
+ if(i != 0)
+ return Ebadvalue;
+ frame_focus(f);
+ return nil;
+ }
+ }
+
+ area_focus(ap);
+ return nil;
+}
+
+static char*
+msg_selectframe(Area *a, IxpMsg *m, int sym) {
+ Client *c;
+ Frame *f, *fp;
+ char *s;
+ bool stack;
+ ulong i, dy;
+
+ f = a->sel;
+ fp = f;
+
+ stack = false;
+ if(sym == LUP || sym == LDOWN) {
+ s = msg_getword(m);
+ if(s)
+ if(!strcmp(s, "stack"))
+ stack = true;
+ else
+ return Ebadvalue;
+ }
+
+ if(sym == LCLIENT) {
+ s = msg_getword(m);
+ if(s == nil || !getulong(s, &i))
+ return "usage: select client <client>";
+ c = win2client(i);
+ if(c == nil)
+ return "unknown client";
+ f = client_viewframe(c, a->view);
+ if(!f)
+ return Ebadvalue;
+ }
+ else {
+ if(!find(&a, &f, DIR(sym), true, stack))
+ return Ebadvalue;
+ }
+
+ area_focus(a);
+
+ if(!f)
+ return nil;
+
+ /* XXX */
+ if(fp && fp->area == a)
+ if(f->collapsed && !f->area->floating && f->area->mode == Coldefault) {
+ dy = Dy(f->colr);
+ f->colr.max.y = f->colr.min.y + Dy(fp->colr);
+ fp->colr.max.y = fp->colr.min.y + dy;
+ column_arrange(a, false);
+ }
+
+ frame_focus(f);
+ frame_restack(f, nil);
+ if(f->view == selview)
+ view_restack(a->view);
+ return nil;
+}
+
+static char*
+sendarea(Frame *f, Area *to, bool swap) {
+ Client *c;
+
+ c = f->client;
+ if(!to)
+ return Ebadvalue;
+
+ if(!swap)
+ area_moveto(to, f);
+ else if(to->sel)
+ frame_swap(f, to->sel);
+ else
+ return Ebadvalue;
+
+ frame_focus(client_viewframe(c, f->view));
+ /* view_arrange(v); */
+ view_update_all();
+ return nil;
+}
+
+char*
+msg_sendclient(View *v, IxpMsg *m, bool swap) {
+ Area *to, *a;
+ Frame *f, *ff;
+ Client *c;
+ char *s;
+ int sym;
+
+ s = msg_getword(m);
+
+ c = strclient(v, s);
+ if(c == nil)
+ return Ebadvalue;
+
+ f = client_viewframe(c, v);
+ if(f == nil)
+ return Ebadvalue;
+
+ a = f->area;
+ to = nil;
+
+ s = msg_getword(m);
+ sym = getsym(s);
+
+ /* FIXME: Should use a helper function. */
+ switch(sym) {
+ case LUP:
+ case LDOWN:
+ return msg_sendframe(f, sym, swap);
+ case LLEFT:
+ if(a->floating)
+ return Ebadvalue;
+ to = a->prev;
+ break;
+ case LRIGHT:
+ if(a->floating)
+ return Ebadvalue;
+ to = a->next;
+ break;
+ case LTOGGLE:
+ if(!a->floating)
+ to = v->floating;
+ else if(f->column)
+ to = view_findarea(v, f->screen, f->column, true);
+ else
+ to = view_findarea(v, v->selscreen, v->selcol, true);
+ break;
+ case LTILDE:
+ if(a->floating)
+ return Ebadvalue;
+ to = v->floating;
+ break;
+ default:
+ to = strarea(v, v->selscreen, s);
+ // to = view_findarea(v, scrn, i, true);
+ break;
+ }
+
+
+ if(!to && !swap) {
+ /* XXX: Multihead - clean this up, move elsewhere. */
+ if(!f->anext && f == f->area->frame) {
+ ff = f;
+ to = a;
+ if(!find(&to, &ff, DIR(sym), false, false))
+ return Ebadvalue;
+ }
+ else {
+ to = (sym == LLEFT) ? nil : a;
+ to = column_new(v, to, a->screen, 0);
+ }
+ }
+
+ return sendarea(f, to, swap);
+}
+
+static char*
+msg_sendframe(Frame *f, int sym, bool swap) {
+ Client *c;
+ Area *a;
+ Frame *fp;
+
+ SET(fp);
+ c = f->client;
+
+ a = f->area;
+ fp = f;
+ if(!find(&a, &fp, DIR(sym), false, false))
+ return Ebadvalue;
+ if(a != f->area)
+ return sendarea(f, a, swap);
+
+ switch(sym) {
+ case LUP:
+ fp = f->aprev;
+ if(!fp)
+ return Ebadvalue;
+ if(!swap)
+ fp = fp->aprev;
+ break;
+ case LDOWN:
+ fp = f->anext;
+ if(!fp)
+ return Ebadvalue;
+ break;
+ default:
+ die("can't get here");
+ }
+
+ if(swap)
+ frame_swap(f, fp);
+ else {
+ frame_remove(f);
+ frame_insert(f, fp);
+ }
+
+ /* view_arrange(f->view); */
+
+ frame_focus(client_viewframe(c, f->view));
+ view_update_all();
+ return nil;
+}
+
+void
+warning(const char *fmt, ...) {
+ va_list ap;
+ char *s;
+
+ va_start(ap, fmt);
+ s = vsmprint(fmt, ap);
+ va_end(ap);
+
+ event("Warning %s\n", s);
+ fprint(2, "%s: warning: %s\n", argv0, s);
+ free(s);
+}
+
diff --git a/cmd/wmii/mouse.c b/cmd/wmii/mouse.c
new file mode 100644
index 0000000..fb7e2ff
--- /dev/null
+++ b/cmd/wmii/mouse.c
@@ -0,0 +1,642 @@
+/* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include "fns.h"
+
+/* Here be dragons. */
+
+enum {
+ ButtonMask =
+ ButtonPressMask | ButtonReleaseMask,
+ MouseMask =
+ ButtonMask | PointerMotionMask
+};
+
+static void
+cwin_expose(Window *w, XExposeEvent *e) {
+
+ fill(w, rectsubpt(w->r, w->r.min), def.focuscolor.bg);
+ fill(w, w->r, def.focuscolor.bg);
+}
+
+static Handlers chandler = {
+ .expose = cwin_expose,
+};
+
+Window*
+constraintwin(Rectangle r) {
+ Window *w;
+ WinAttr wa;
+
+ w = createwindow(&scr.root, r, 0, InputOnly, &wa, 0);
+ if(0) {
+ Window *w2;
+
+ w2 = createwindow(&scr.root, r, 0, InputOutput, &wa, 0);
+ selectinput(w2, ExposureMask);
+ w->aux = w2;
+
+ setborder(w2, 1, def.focuscolor.border);
+ sethandler(w2, &chandler);
+ mapwin(w2);
+ raisewin(w2);
+ }
+ mapwin(w);
+ return w;
+}
+
+void
+destroyconstraintwin(Window *w) {
+ Window *w2;
+
+ if(w->aux) {
+ w2 = w->aux;
+ sethandler(w2, nil);
+ destroywindow(w2);
+ }
+ destroywindow(w);
+}
+
+static Window*
+gethsep(Rectangle r) {
+ Window *w;
+ WinAttr wa;
+
+ wa.background_pixel = def.normcolor.border.pixel;
+ w = createwindow(&scr.root, r, scr.depth, InputOutput, &wa, CWBackPixel);
+ mapwin(w);
+ raisewin(w);
+ return w;
+}
+
+static void
+rect_morph(Rectangle *r, Point d, Align *mask) {
+ int n;
+
+ if(*mask & North)
+ r->min.y += d.y;
+ if(*mask & West)
+ r->min.x += d.x;
+ if(*mask & South)
+ r->max.y += d.y;
+ if(*mask & East)
+ r->max.x += d.x;
+
+ if(r->min.x > r->max.x) {
+ n = r->min.x;
+ r->min.x = r->max.x;
+ r->max.x = n;
+ *mask ^= East|West;
+ }
+ if(r->min.y > r->max.y) {
+ n = r->min.y;
+ r->min.y = r->max.y;
+ r->max.y = n;
+ *mask ^= North|South;
+ }
+}
+
+/* Yes, yes, macros are evil. So are patterns. */
+#define frob(x, y) \
+ const Rectangle *rp; \
+ int i, tx; \
+ \
+ for(i=0; i < nrect; i++) { \
+ rp = &rects[i]; \
+ if((rp->min.y <= r->max.y) && (rp->max.y >= r->min.y)) { \
+ tx = rp->min.x; \
+ if(abs(tx - x) <= abs(dx)) \
+ dx = tx - x; \
+ \
+ tx = rp->max.x; \
+ if(abs(tx - x) <= abs(dx)) \
+ dx = tx - x; \
+ } \
+ } \
+ return dx \
+
+static int
+snap_hline(const Rectangle *rects, int nrect, int dx, const Rectangle *r, int y) {
+ frob(y, x);
+}
+
+static int
+snap_vline(const Rectangle *rects, int nrect, int dx, const Rectangle *r, int x) {
+ frob(x, y);
+}
+
+#undef frob
+
+/* Returns a gravity for increment handling. It's normally the
+ * opposite of the mask (the directions that we're resizing in),
+ * unless a snap occurs, in which case, it's the direction of the
+ * snap.
+ */
+Align
+snap_rect(const Rectangle *rects, int num, Rectangle *r, Align *mask, int snap) {
+ Align ret;
+ Point d;
+
+ d.x = snap+1;
+ d.y = snap+1;
+
+ if(*mask&North)
+ d.y = snap_hline(rects, num, d.y, r, r->min.y);
+ if(*mask&South)
+ d.y = snap_hline(rects, num, d.y, r, r->max.y);
+
+ if(*mask&East)
+ d.x = snap_vline(rects, num, d.x, r, r->max.x);
+ if(*mask&West)
+ d.x = snap_vline(rects, num, d.x, r, r->min.x);
+
+ ret = Center;
+ if(abs(d.x) <= snap)
+ ret ^= East|West;
+ else
+ d.x = 0;
+
+ if(abs(d.y) <= snap)
+ ret ^= North|South;
+ else
+ d.y = 0;
+
+ rect_morph(r, d, mask);
+ return ret ^ *mask;
+}
+
+int
+readmouse(Point *p, uint *button) {
+ XEvent ev;
+
+ for(;;) {
+ XMaskEvent(display, MouseMask|ExposureMask|PropertyChangeMask, &ev);
+ switch(ev.type) {
+ case Expose:
+ case NoExpose:
+ case PropertyNotify:
+ dispatch_event(&ev);
+ default:
+ Dprint(DEvent, "readmouse(): ignored: %E\n", &ev);
+ continue;
+ case ButtonPress:
+ case ButtonRelease:
+ *button = ev.xbutton.button;
+ case MotionNotify:
+ p->x = ev.xmotion.x_root;
+ p->y = ev.xmotion.y_root;
+ break;
+ }
+ return ev.type;
+ }
+}
+
+bool
+readmotion(Point *p) {
+ uint button;
+
+ for(;;)
+ switch(readmouse(p, &button)) {
+ case MotionNotify:
+ return true;
+ case ButtonRelease:
+ return false;
+ }
+}
+
+static void
+mouse_resizecolframe(Frame *f, Align align) {
+ Window *cwin, *hwin;
+ Divide *d;
+ View *v;
+ Area *a;
+ Rectangle r;
+ Point pt, min;
+ int s;
+
+ assert((align&(East|West)) != (East|West));
+ assert((align&(North|South)) != (North|South));
+
+ f->collapsed = false;
+
+ v = selview;
+ d = divs;
+ SET(a);
+ foreach_column(v, s, a) {
+ if(a == f->area)
+ break;
+ d = d->next;
+ }
+
+ if(align&East)
+ d = d->next;
+
+ min.x = column_minwidth();
+ min.y = /*frame_delta_h() +*/ labelh(def.font);
+ /* Set the limits of where this box may be dragged. */
+#define frob(pred, f, aprev, rmin, rmax, plus, minus, xy) BLOCK( \
+ if(pred) { \
+ r.rmin.xy = f->aprev->r.rmin.xy plus min.xy; \
+ r.rmax.xy = f->r.rmax.xy minus min.xy; \
+ }else { \
+ r.rmin.xy = a->r.rmin.xy; \
+ r.rmax.xy = r.rmin.xy plus 1; \
+ })
+ if(align&North)
+ frob(f->aprev, f, aprev, min, max, +, -, y);
+ else
+ frob(f->anext, f, anext, max, min, -, +, y);
+ if(align&West)
+ frob(a->prev, a, prev, min, max, +, -, x);
+ else
+ frob(a->next, a, next, max, min, -, +, x);
+#undef frob
+
+ cwin = constraintwin(r);
+
+ r = f->r;
+ if(align&North)
+ r.min.y--;
+ else
+ r.min.y = r.max.y - 1;
+ r.max.y = r.min.y + 2;
+
+ hwin = gethsep(r);
+
+ if(!grabpointer(&scr.root, cwin, cursor[CurSizing], MouseMask))
+ goto done;
+
+ pt.x = ((align&West) ? f->r.min.x : f->r.max.x);
+ pt.y = ((align&North) ? f->r.min.y : f->r.max.y);
+ warppointer(pt);
+
+ while(readmotion(&pt)) {
+ if(align&West)
+ r.min.x = pt.x;
+ else
+ r.max.x = pt.x;
+ r.min.y = ((align&South) ? pt.y : pt.y-1);
+ r.max.y = r.min.y+2;
+
+ div_set(d, pt.x);
+ reshapewin(hwin, r);
+ }
+
+ r = f->r;
+ if(align&West)
+ r.min.x = pt.x;
+ else
+ r.max.x = pt.x;
+ if(align&North)
+ r.min.y = pt.y;
+ else
+ r.max.y = pt.y;
+ column_resizeframe(f, r);
+
+ /* XXX: Magic number... */
+ if(align&West)
+ pt.x = f->r.min.x + 4;
+ else
+ pt.x = f->r.max.x - 4;
+ if(align&North)
+ pt.y = f->r.min.y + 4;
+ else
+ pt.y = f->r.max.y - 4;
+ warppointer(pt);
+
+done:
+ ungrabpointer();
+ destroyconstraintwin(cwin);
+ destroywindow(hwin);
+}
+
+void
+mouse_resizecol(Divide *d) {
+ Window *cwin;
+ View *v;
+ Rectangle r;
+ Point pt;
+ int minw, scrn;
+
+ v = selview;
+
+ scrn = (d->left ? d->left : d->right)->screen;
+
+ pt = querypointer(&scr.root);
+
+ minw = column_minwidth();
+ r.min.x = d->left ? d->left->r.min.x + minw : v->r[scrn].min.x;
+ r.max.x = d->right ? d->right->r.max.x - minw : v->r[scrn].max.x;
+ r.min.y = pt.y;
+ r.max.y = pt.y+1;
+
+ cwin = constraintwin(r);
+
+ if(!grabpointer(&scr.root, cwin, cursor[CurNone], MouseMask))
+ goto done;
+
+ while(readmotion(&pt))
+ div_set(d, pt.x);
+
+ if(d->left)
+ d->left->r.max.x = pt.x;
+ else
+ v->pad[scrn].min.x = pt.x - v->r[scrn].min.x;
+
+ if(d->right)
+ d->right->r.min.x = pt.x;
+ else
+ v->pad[scrn].max.x = pt.x - v->r[scrn].max.x;
+
+ view_arrange(v);
+
+done:
+ ungrabpointer();
+ destroyconstraintwin(cwin);
+}
+
+void
+mouse_resize(Client *c, Align align, bool grabmod) {
+ Rectangle *rects;
+ Rectangle frect, origin;
+ Align grav;
+ Cursor cur;
+ Point d, pt, hr;
+ float rx, ry, hrx, hry;
+ uint nrect;
+ Frame *f;
+
+ f = c->sel;
+ if(f->client->fullscreen >= 0) {
+ ungrabpointer();
+ return;
+ }
+ if(!f->area->floating) {
+ if(align==Center)
+ mouse_movegrabbox(c, grabmod);
+ else
+ mouse_resizecolframe(f, align);
+ return;
+ }
+
+ cur = quad_cursor(align);
+ if(align == Center)
+ cur = cursor[CurSizing];
+
+ if(!grabpointer(c->framewin, nil, cur, MouseMask))
+ return;
+
+ origin = f->r;
+ frect = f->r;
+ rects = view_rects(f->area->view, &nrect, c->frame);
+
+ pt = querypointer(c->framewin);
+ rx = (float)pt.x / Dx(frect);
+ ry = (float)pt.y / Dy(frect);
+
+ pt = querypointer(&scr.root);
+
+ SET(hrx);
+ SET(hry);
+ if(align != Center) {
+ hr = subpt(frect.max, frect.min);
+ hr = divpt(hr, Pt(2, 2));
+ d = hr;
+ if(align&North) d.y -= hr.y;
+ if(align&South) d.y += hr.y;
+ if(align&East) d.x += hr.x;
+ if(align&West) d.x -= hr.x;
+
+ pt = addpt(d, f->r.min);
+ warppointer(pt);
+ }else {
+ hrx = (double)(Dx(scr.rect)
+ + Dx(frect)
+ - 2 * labelh(def.font))
+ / Dx(scr.rect);
+ hry = (double)(Dy(scr.rect)
+ + Dy(frect)
+ - 3 * labelh(def.font))
+ / Dy(scr.rect);
+
+ pt.x = frect.max.x - labelh(def.font);
+ pt.y = frect.max.y - labelh(def.font);
+ d.x = pt.x / hrx;
+ d.y = pt.y / hry;
+
+ warppointer(d);
+ }
+ sync();
+ flushevents(PointerMotionMask, false);
+
+ while(readmotion(&d)) {
+ if(align == Center) {
+ d.x = (d.x * hrx) - pt.x;
+ d.y = (d.y * hry) - pt.y;
+ }else
+ d = subpt(d, pt);
+ pt = addpt(pt, d);
+
+ rect_morph(&origin, d, &align);
+ frect = constrain(origin, -1);
+
+ grav = snap_rect(rects, nrect, &frect, &align, def.snap);
+
+ frect = frame_hints(f, frect, grav);
+ frect = constrain(frect, -1);
+
+ client_resize(c, frect);
+ }
+
+ pt = addpt(c->framewin->r.min,
+ Pt(Dx(frect) * rx,
+ Dy(frect) * ry));
+ if(pt.y > scr.rect.max.y)
+ pt.y = scr.rect.max.y - 1;
+ warppointer(pt);
+
+ free(rects);
+ ungrabpointer();
+}
+
+static int
+pushstack_down(Frame *f, int y) {
+ int ret;
+ int dh, dy;
+
+ if(f == nil)
+ return 0;;
+ ret = 0;
+ dy = y - f->colr.min.y;
+ if(dy < 0)
+ return 0;
+ if(!f->collapsed) {
+ dh = Dy(f->colr) - labelh(def.font);
+ if(dy <= dh) {
+ f->colr.min.y += dy;
+ return dy;
+ }else {
+ f->collapsed = true;
+ f->colr.min.y += dh;
+ ret = dh;
+ dy -= dh;
+ }
+ }
+ dy = pushstack_down(f->anext, f->colr.max.y + dy);
+ f->colr.min.y += dy;
+ f->colr.max.y += dy;
+ return ret + dy;
+}
+
+static int
+pushstack_up(Frame *f, int y) {
+ int ret;
+ int dh, dy;
+
+ if(f == nil)
+ return 0;
+ ret = 0;
+ dy = f->colr.max.y - y;
+ if(dy < 0)
+ return 0;
+ if(!f->collapsed) {
+ dh = Dy(f->colr) - labelh(def.font);
+ if(dy <= dh) {
+ f->colr.max.y -= dy;
+ return dy;
+ }else {
+ f->collapsed = true;
+ f->colr.max.y -= dh;
+ ret = dh;
+ dy -= dh;
+ }
+ }
+ dy = pushstack_up(f->aprev, f->colr.min.y - dy);
+ f->colr.min.y -= dy;
+ f->colr.max.y -= dy;
+ return ret + dy;
+}
+
+static void
+mouse_tempvertresize(Area *a, Point p) {
+ Frame *fa, *fb, *f;
+ Window *cwin;
+ Rectangle r;
+ Point pt;
+ int incmode, nabove, nbelow;
+
+ if(a->mode != Coldefault)
+ return;
+
+ for(fa=a->frame; fa; fa=fa->anext)
+ if(p.y < fa->r.max.y + labelh(def.font)/2)
+ break;
+ if(!(fa && fa->anext))
+ return;
+ fb = fa->anext;
+ nabove=0;
+ nbelow=0;
+ for(f=fa; f; f=f->aprev)
+ nabove++;
+ for(f=fa->anext; f; f=f->anext)
+ nbelow++;
+
+ incmode = def.incmode;
+ def.incmode = IIgnore;
+ resizing = true;
+ column_arrange(a, false);
+
+ r.min.x = p.x;
+ r.max.x = p.x + 1;
+ r.min.y = a->r.min.y + labelh(def.font) * nabove;
+ r.max.y = a->r.max.y - labelh(def.font) * nbelow;
+ cwin = constraintwin(r);
+
+ if(!grabpointer(&scr.root, cwin, cursor[CurDVArrow], MouseMask))
+ goto done;
+
+ for(f=a->frame; f; f=f->anext)
+ f->colr_old = f->colr;
+
+ while(readmotion(&pt)) {
+ for(f=a->frame; f; f=f->anext) {
+ f->collapsed = false;
+ f->colr = f->colr_old;
+ }
+ if(pt.y > p.y)
+ pushstack_down(fb, pt.y);
+ else
+ pushstack_up(fa, pt.y);
+ fa->colr.max.y = pt.y;
+ fb->colr.min.y = pt.y;
+ column_frob(a);
+ }
+
+done:
+ ungrabpointer();
+ destroyconstraintwin(cwin);
+ def.incmode = incmode;
+ resizing = false;
+ column_arrange(a, false);
+}
+
+void
+mouse_checkresize(Frame *f, Point p, bool exec) {
+ Rectangle r;
+ Cursor cur;
+ int q;
+
+ cur = cursor[CurNormal];
+ if(rect_haspoint_p(p, f->crect)) {
+ client_setcursor(f->client, cur);
+ return;
+ }
+
+ r = rectsubpt(f->r, f->r.min);
+ q = quadrant(r, p);
+ if(rect_haspoint_p(p, f->grabbox)) {
+ cur = cursor[CurTCross];
+ if(exec)
+ mouse_movegrabbox(f->client, false);
+ }
+ else if(f->area->floating) {
+ if(p.x <= 2
+ || p.y <= 2
+ || r.max.x - p.x <= 2
+ || r.max.y - p.y <= 2) {
+ cur = quad_cursor(q);
+ if(exec)
+ mouse_resize(f->client, q, false);
+ }
+ else if(exec && rect_haspoint_p(p, f->titlebar))
+ mouse_movegrabbox(f->client, true);
+ }else {
+ if(f->aprev && p.y <= 2
+ || f->anext && r.max.y - p.y <= 2) {
+ cur = cursor[CurDVArrow];
+ if(exec)
+ mouse_tempvertresize(f->area, addpt(p, f->r.min));
+ }
+ }
+
+ if(!exec)
+ client_setcursor(f->client, cur);
+}
+
+static void
+_grab(XWindow w, uint button, ulong mod) {
+ XGrabButton(display, button, mod, w, false, ButtonMask,
+ GrabModeSync, GrabModeAsync, None, None);
+}
+
+/* Doesn't belong here */
+void
+grab_button(XWindow w, uint button, ulong mod) {
+ _grab(w, button, mod);
+ if((mod != AnyModifier) && numlock_mask) {
+ _grab(w, button, mod | numlock_mask);
+ _grab(w, button, mod | numlock_mask | LockMask);
+ }
+}
+
diff --git a/cmd/wmii/print.c b/cmd/wmii/print.c
new file mode 100644
index 0000000..cddf609
--- /dev/null
+++ b/cmd/wmii/print.c
@@ -0,0 +1,124 @@
+#include "dat.h"
+#include <fmt.h>
+#include "fns.h"
+
+static char* fcnames[] = {
+ "TVersion",
+ "RVersion",
+ "TAuth",
+ "RAuth",
+ "TAttach",
+ "RAttach",
+ "TError",
+ "RError",
+ "TFlush",
+ "RFlush",
+ "TWalk",
+ "RWalk",
+ "TOpen",
+ "ROpen",
+ "TCreate",
+ "RCreate",
+ "TRead",
+ "RRead",
+ "TWrite",
+ "RWrite",
+ "TClunk",
+ "RClunk",
+ "TRemove",
+ "RRemove",
+ "TStat",
+ "RStat",
+ "TWStat",
+ "RWStat",
+};
+
+static int
+qid(Fmt *f, Qid *q) {
+ return fmtprint(f, "(%uhd,%uld,%ullx)", q->type, q->version, q->path);
+}
+
+int
+Ffmt(Fmt *f) {
+ Fcall *fcall;
+
+ fcall = va_arg(f->args, Fcall*);
+ fmtprint(f, "% 2d %s\t", fcall->hdr.tag, fcnames[fcall->hdr.type - TVersion]);
+ switch(fcall->hdr.type) {
+ case TVersion:
+ case RVersion:
+ fmtprint(f, " msize: %uld version: \"%s\"", (ulong)fcall->version.msize, fcall->version.version);
+ break;
+ case TAuth:
+ fmtprint(f, " afid: %uld uname: \"%s\" aname: \"%s\"", (ulong)fcall->tauth.afid, fcall->tauth.uname, fcall->tauth.aname);
+ break;
+ case RAuth:
+ fmtprint(f, " aqid: ");
+ qid(f, &fcall->rauth.aqid);
+ break;
+ case RAttach:
+ fmtprint(f, " qid: ");
+ qid(f, &fcall->rattach.qid);
+ break;
+ case TAttach:
+ fmtprint(f, " fid: %uld afid: %uld uname: \"%s\" aname: \"%s\"", (ulong)fcall->hdr.fid, (ulong)fcall->tattach.afid, fcall->tattach.uname, fcall->tattach.aname);
+ break;
+ case RError:
+ fmtprint(f, " \"%s\"", fcall->error.ename);
+ break;
+ case TFlush:
+ fmtprint(f, " oldtag: %uld", (ulong)fcall->tflush.oldtag);
+ break;
+ case TWalk:
+ fmtprint(f, " newfid: %uld wname: {", (ulong)fcall->twalk.newfid);
+ for(int i=0; i<fcall->twalk.nwname; i++) {
+ if(i > 0) fmtprint(f, ", ");
+ fmtprint(f, "\"%s\"", fcall->twalk.wname[i]);
+ }
+ fmtprint(f, "}");
+ break;
+ case RWalk:
+ fmtprint(f, " wqid: {");
+ for(int i=0; i<fcall->rwalk.nwqid; i++) {
+ if(i > 0) fmtprint(f, ", ");
+ qid(f, &fcall->rwalk.wqid[i]);
+ }
+ fmtprint(f, "}");
+ break;
+ case TOpen:
+ fmtprint(f, " fid: %uld mode: %ulo", (ulong)fcall->hdr.fid, (ulong)fcall->topen.mode);
+ break;
+ case ROpen:
+ case RCreate:
+ fmtprint(f, " qid: ");
+ qid(f, &fcall->ropen.qid);
+ fmtprint(f, " %uld", (ulong)fcall->ropen.iounit);
+ break;
+ case TCreate:
+ fmtprint(f, " fid: %uld name: \"%s\" perm: %ulo mode: %ulo", (ulong)fcall->hdr.fid, fcall->tcreate.name, (ulong)fcall->tcreate.perm, (ulong)fcall->tcreate.mode);
+ break;
+ case TRead:
+ fmtprint(f, " fid: %uld offset: %ulld count: %uld", (ulong)fcall->hdr.fid, fcall->tread.offset, (ulong)fcall->tread.count);
+ break;
+ case RRead:
+ fmtprint(f, " data: {data: %uld}", fcall->rread.count);
+ break;
+ case TWrite:
+ fmtprint(f, " fid: %uld offset: %ulld data: {data: %uld}", (ulong)fcall->hdr.fid, fcall->twrite.offset, fcall->twrite.count);
+ break;
+ case RWrite:
+ fmtprint(f, " count: %uld", (ulong)fcall->rwrite.count);
+ break;
+ case TClunk:
+ case TRemove:
+ case TStat:
+ fmtprint(f, " fid: %uld", (ulong)fcall->hdr.fid);
+ break;
+ case RStat:
+ fmtprint(f, " stat: {data: %uld}", fcall->rstat.nstat);
+ break;
+ }
+
+ return 0;
+}
+
diff --git a/cmd/wmii/printevent.c b/cmd/wmii/printevent.c
new file mode 100644
index 0000000..5b52c43
--- /dev/null
+++ b/cmd/wmii/printevent.c
@@ -0,0 +1,994 @@
+/*
+ * Original code posted to comp.sources.x
+ * Modifications by Russ Cox <rsc@swtch.com>.
+ * Further modifications by Kris Maglione <maglione.k at Gmail>
+ */
+
+/*
+ * Path: uunet!wyse!mikew From: mikew@wyse.wyse.com (Mike Wexler) Newsgroups:
+ * comp.sources.x Subject: v02i056: subroutine to print events in human
+ * readable form, Part01/01 Message-ID: <1935@wyse.wyse.com> Date: 22 Dec 88
+ * 19:28:25 GMT Organization: Wyse Technology, San Jose Lines: 1093 Approved:
+ * mikew@wyse.com
+ *
+ * Submitted-by: richsun!darkstar!ken Posting-number: Volume 2, Issue 56
+ * Archive-name: showevent/part01
+ *
+ *
+ * There are times during debugging when it would be real useful to be able to
+ * print the fields of an event in a human readable form. Too many times I
+ * found myself scrounging around in section 8 of the Xlib manual looking for
+ * the valid fields for the events I wanted to see, then adding printf's to
+ * display the numeric values of the fields, and then scanning through X.h
+ * trying to decode the cryptic detail and state fields. After playing with
+ * xev, I decided to write a couple of standard functions that I could keep
+ * in a library and call on whenever I needed a little debugging verbosity.
+ * The first function, GetType(), is useful for returning the string
+ * representation of the type of an event. The second function, ShowEvent(),
+ * is used to display all the fields of an event in a readable format. The
+ * functions are not complicated, in fact, they are mind-numbingly boring -
+ * but that's just the point nobody wants to spend the time writing functions
+ * like this, they just want to have them when they need them.
+ *
+ * A simple, sample program is included which does little else but to
+ * demonstrate the use of these two functions. These functions have saved me
+ * many an hour during debugging and I hope you find some benefit to these.
+ * If you have any comments, suggestions, improvements, or if you find any
+ * blithering errors you can get it touch with me at the following location:
+ *
+ * ken@richsun.UUCP
+ */
+
+#include "dat.h"
+#include <stdarg.h>
+#include <bio.h>
+//#include "fns.h"
+#include "printevent.h"
+#define Window XWindow
+
+#define nil ((void*)0)
+
+typedef struct Pair Pair;
+
+struct Pair {
+ int key;
+ char *val;
+};
+
+static char* sep = " ";
+
+static char *
+search(Pair *lst, int key, char *(*def)(int)) {
+ for(; lst->val; lst++)
+ if(lst->key == key)
+ return lst->val;
+ return def(key);
+}
+
+static char*
+unmask(Pair *list, uint val)
+{
+ Pair *p;
+ char *s, *end;
+ int n;
+
+ buffer[0] = '\0';
+ end = buffer + sizeof buffer;
+ s = buffer;
+
+ n = 0;
+ s = utfecpy(s, end, "(");
+ for (p = list; p->val; p++)
+ if (val & p->key) {
+ if(n++)
+ s = utfecpy(s, end, "|");
+ s = utfecpy(s, end, p->val);
+ }
+ utfecpy(s, end, ")");
+
+ return buffer;
+}
+
+static char *
+strhex(int key) {
+ sprint(buffer, "0x%x", key);
+ return buffer;
+}
+
+static char *
+strdec(int key) {
+ sprint(buffer, "%d", key);
+ return buffer;
+}
+
+static char *
+strign(int key) {
+ USED(key);
+
+ return "?";
+}
+
+/******************************************************************************/
+/**** Miscellaneous routines to convert values to their string equivalents ****/
+/******************************************************************************/
+
+static void
+TInt(Fmt *b, va_list *ap) {
+ fmtprint(b, "%d", va_arg(*ap, int));
+}
+
+static void
+TWindow(Fmt *b, va_list *ap) {
+ Window w;
+
+ w = va_arg(*ap, Window);
+ fmtprint(b, "0x%ux", (uint)w);
+}
+
+static void
+TData(Fmt *b, va_list *ap) {
+ long *l;
+ int i;
+
+ l = va_arg(*ap, long*);
+ fmtprint(b, "{");
+ for (i = 0; i < 5; i++) {
+ if(i > 0)
+ fmtprint(b, ", ");
+ fmtprint(b, "0x%08lx", l[i]);
+ }
+ fmtprint(b, "}");
+}
+
+/* Returns the string equivalent of a timestamp */
+static void
+TTime(Fmt *b, va_list *ap) {
+ ldiv_t d;
+ ulong msec;
+ ulong sec;
+ ulong min;
+ ulong hr;
+ ulong day;
+ Time time;
+
+ time = va_arg(*ap, Time);
+
+ msec = time/1000;
+ d = ldiv(msec, 60);
+ msec = time-msec*1000;
+
+ sec = d.rem;
+ d = ldiv(d.quot, 60);
+ min = d.rem;
+ d = ldiv(d.quot, 24);
+ hr = d.rem;
+ day = d.quot;
+
+#ifdef notdef
+ sprintf(buffer, "%lu day%s %02lu:%02lu:%02lu.%03lu",
+ day, day == 1 ? "" : "(s)", hr, min, sec, msec);
+#endif
+
+ fmtprint(b, "%ludd_%ludh_%ludm_%lud.%03luds", day, hr, min, sec, msec);
+}
+
+/* Returns the string equivalent of a boolean parameter */
+static void
+TBool(Fmt *b, va_list *ap) {
+ static Pair list[] = {
+ {True, "True"},
+ {False, "False"},
+ {0, nil},
+ };
+ Bool key;
+
+ key = va_arg(*ap, Bool);
+ fmtprint(b, "%s", search(list, key, strign));
+}
+
+/* Returns the string equivalent of a property notify state */
+static void
+TPropState(Fmt *b, va_list *ap) {
+ static Pair list[] = {
+ {PropertyNewValue, "PropertyNewValue"},
+ {PropertyDelete, "PropertyDelete"},
+ {0, nil},
+ };
+ uint key;
+
+ key = va_arg(*ap, uint);
+ fmtprint(b, "%s", search(list, key, strign));
+}
+
+/* Returns the string equivalent of a visibility notify state */
+static void
+TVis(Fmt *b, va_list *ap) {
+ static Pair list[] = {
+ {VisibilityUnobscured, "VisibilityUnobscured"},
+ {VisibilityPartiallyObscured, "VisibilityPartiallyObscured"},
+ {VisibilityFullyObscured, "VisibilityFullyObscured"},
+ {0, nil},
+ };
+ int key;
+
+ key = va_arg(*ap, int);
+ fmtprint(b, "%s", search(list, key, strign));
+}
+
+/* Returns the string equivalent of a mask of buttons and/or modifier keys */
+static void
+TModState(Fmt *b, va_list *ap) {
+ static Pair list[] = {
+ {Button1Mask, "Button1Mask"},
+ {Button2Mask, "Button2Mask"},
+ {Button3Mask, "Button3Mask"},
+ {Button4Mask, "Button4Mask"},
+ {Button5Mask, "Button5Mask"},
+ {ShiftMask, "ShiftMask"},
+ {LockMask, "LockMask"},
+ {ControlMask, "ControlMask"},
+ {Mod1Mask, "Mod1Mask"},
+ {Mod2Mask, "Mod2Mask"},
+ {Mod3Mask, "Mod3Mask"},
+ {Mod4Mask, "Mod4Mask"},
+ {Mod5Mask, "Mod5Mask"},
+ {0, nil},
+ };
+ uint state;
+
+ state = va_arg(*ap, uint);
+ fmtprint(b, "%s", unmask(list, state));
+}
+
+/* Returns the string equivalent of a mask of configure window values */
+static void
+TConfMask(Fmt *b, va_list *ap) {
+ static Pair list[] = {
+ {CWX, "CWX"},
+ {CWY, "CWY"},
+ {CWWidth, "CWWidth"},
+ {CWHeight, "CWHeight"},
+ {CWBorderWidth, "CWBorderWidth"},
+ {CWSibling, "CWSibling"},
+ {CWStackMode, "CWStackMode"},
+ {0, nil},
+ };
+ uint valuemask;
+
+ valuemask = va_arg(*ap, uint);
+ fmtprint(b, "%s", unmask(list, valuemask));
+}
+
+/* Returns the string equivalent of a motion hint */
+#if 0
+static void
+IsHint(Fmt *b, va_list *ap) {
+ static Pair list[] = {
+ {NotifyNormal, "NotifyNormal"},
+ {NotifyHint, "NotifyHint"},
+ {0, nil},
+ };
+ char key;
+
+ key = va_arg(*ap, char);
+ fmtprint(b, "%s", search(list, key, strign));
+}
+#endif
+
+/* Returns the string equivalent of an id or the value "None" */
+static void
+TIntNone(Fmt *b, va_list *ap) {
+ static Pair list[] = {
+ {None, "None"},
+ {0, nil},
+ };
+ int key;
+
+ key = va_arg(*ap, int);
+ fmtprint(b, "%s", search(list, key, strhex));
+}
+
+/* Returns the string equivalent of a colormap state */
+static void
+TColMap(Fmt *b, va_list *ap) {
+ static Pair list[] = {
+ {ColormapInstalled, "ColormapInstalled"},
+ {ColormapUninstalled, "ColormapUninstalled"},
+ {0, nil},
+ };
+ int key;
+
+ key = va_arg(*ap, int);
+ fmtprint(b, "%s", search(list, key, strign));
+}
+
+/* Returns the string equivalent of a crossing detail */
+static void
+TXing(Fmt *b, va_list *ap) {
+ static Pair list[] = {
+ {NotifyAncestor, "NotifyAncestor"},
+ {NotifyInferior, "NotifyInferior"},
+ {NotifyVirtual, "NotifyVirtual"},
+ {NotifyNonlinear, "NotifyNonlinear"},
+ {NotifyNonlinearVirtual, "NotifyNonlinearVirtual"},
+ {0, nil},
+ };
+ int key;
+
+ key = va_arg(*ap, int);
+ fmtprint(b, "%s", search(list, key, strign));
+}
+
+/* Returns the string equivalent of a focus change detail */
+static void
+TFocus(Fmt *b, va_list *ap) {
+ static Pair list[] = {
+ {NotifyAncestor, "NotifyAncestor"},
+ {NotifyInferior, "NotifyInferior"},
+ {NotifyVirtual, "NotifyVirtual"},
+ {NotifyNonlinear, "NotifyNonlinear"},
+ {NotifyNonlinearVirtual, "NotifyNonlinearVirtual"},
+ {NotifyPointer, "NotifyPointer"},
+ {NotifyPointerRoot, "NotifyPointerRoot"},
+ {NotifyDetailNone, "NotifyDetailNone"},
+ {0, nil},
+ };
+ int key;
+
+ key = va_arg(*ap, int);
+ fmtprint(b, "%s", search(list, key, strign));
+}
+
+/* Returns the string equivalent of a configure detail */
+static void
+TConfDetail(Fmt *b, va_list *ap) {
+ static Pair list[] = {
+ {Above, "Above"},
+ {Below, "Below"},
+ {TopIf, "TopIf"},
+ {BottomIf, "BottomIf"},
+ {Opposite, "Opposite"},
+ {0, nil},
+ };
+ int key;
+
+ key = va_arg(*ap, int);
+ fmtprint(b, "%s", search(list, key, strign));
+}
+
+/* Returns the string equivalent of a grab mode */
+static void
+TGrabMode(Fmt *b, va_list *ap) {
+ static Pair list[] = {
+ {NotifyNormal, "NotifyNormal"},
+ {NotifyGrab, "NotifyGrab"},
+ {NotifyUngrab, "NotifyUngrab"},
+ {NotifyWhileGrabbed, "NotifyWhileGrabbed"},
+ {0, nil},
+ };
+ int key;
+
+ key = va_arg(*ap, int);
+ fmtprint(b, "%s", search(list, key, strign));
+}
+
+/* Returns the string equivalent of a mapping request */
+static void
+TMapping(Fmt *b, va_list *ap) {
+ static Pair list[] = {
+ {MappingModifier, "MappingModifier"},
+ {MappingKeyboard, "MappingKeyboard"},
+ {MappingPointer, "MappingPointer"},
+ {0, nil},
+ };
+ int key;
+
+ key = va_arg(*ap, int);
+ fmtprint(b, "%s", search(list, key, strign));
+}
+
+/* Returns the string equivalent of a stacking order place */
+static void
+TPlace(Fmt *b, va_list *ap) {
+ static Pair list[] = {
+ {PlaceOnTop, "PlaceOnTop"},
+ {PlaceOnBottom, "PlaceOnBottom"},
+ {0, nil},
+ };
+ int key;
+
+ key = va_arg(*ap, int);
+ fmtprint(b, "%s", search(list, key, strign));
+}
+
+/* Returns the string equivalent of a major code */
+static void
+TMajor(Fmt *b, va_list *ap) {
+ static char *list[] = { XMajors };
+ char *s;
+ uint key;
+
+ key = va_arg(*ap, uint);
+ s = "<nil>";
+ if(key < nelem(list))
+ s = list[key];
+ fmtprint(b, "%s", s);
+}
+
+static char*
+eventtype(int key) {
+ static Pair list[] = {
+ {ButtonPress, "ButtonPress"},
+ {ButtonRelease, "ButtonRelease"},
+ {CirculateNotify, "CirculateNotify"},
+ {CirculateRequest, "CirculateRequest"},
+ {ClientMessage, "ClientMessage"},
+ {ColormapNotify, "ColormapNotify"},
+ {ConfigureNotify, "ConfigureNotify"},
+ {ConfigureRequest, "ConfigureRequest"},
+ {CreateNotify, "CreateNotify"},
+ {DestroyNotify, "DestroyNotify"},
+ {EnterNotify, "EnterNotify"},
+ {Expose, "Expose"},
+ {FocusIn, "FocusIn"},
+ {FocusOut, "FocusOut"},
+ {GraphicsExpose, "GraphicsExpose"},
+ {GravityNotify, "GravityNotify"},
+ {KeyPress, "KeyPress"},
+ {KeyRelease, "KeyRelease"},
+ {KeymapNotify, "KeymapNotify"},
+ {LeaveNotify, "LeaveNotify"},
+ {MapNotify, "MapNotify"},
+ {MapRequest, "MapRequest"},
+ {MappingNotify, "MappingNotify"},
+ {MotionNotify, "MotionNotify"},
+ {NoExpose, "NoExpose"},
+ {PropertyNotify, "PropertyNotify"},
+ {ReparentNotify, "ReparentNotify"},
+ {ResizeRequest, "ResizeRequest"},
+ {SelectionClear, "SelectionClear"},
+ {SelectionNotify, "SelectionNotify"},
+ {SelectionRequest, "SelectionRequest"},
+ {UnmapNotify, "UnmapNotify"},
+ {VisibilityNotify, "VisibilityNotify"},
+ {0, nil},
+ };
+ return search(list, key, strdec);
+}
+/* Returns the string equivalent the keycode contained in the key event */
+static void
+TKeycode(Fmt *b, va_list *ap) {
+ KeySym keysym_str;
+ XKeyEvent *ev;
+ char *keysym_name;
+
+ ev = va_arg(*ap, XKeyEvent*);
+
+ XLookupString(ev, buffer, sizeof buffer, &keysym_str, nil);
+
+ if (keysym_str == NoSymbol)
+ keysym_name = "NoSymbol";
+ else
+ keysym_name = XKeysymToString(keysym_str);
+ if(keysym_name == nil)
+ keysym_name = "(no name)";
+
+ fmtprint(b, "%ud (keysym 0x%x \"%s\")", (int)ev->keycode,
+ (int)keysym_str, keysym_name);
+}
+
+/* Returns the string equivalent of an atom or "None" */
+static void
+TAtom(Fmt *b, va_list *ap) {
+ char *atom_name;
+ Atom atom;
+
+ atom = va_arg(*ap, Atom);
+ atom_name = XGetAtomName(display, atom);
+ fmtprint(b, "%s", atom_name);
+ XFree(atom_name);
+}
+
+#define _(m) #m, ev->m
+#define TEnd nil
+typedef void (*Tfn)(Fmt*, va_list*);
+
+static int
+pevent(Fmt *fmt, void *e, ...) {
+ va_list ap;
+ Tfn fn;
+ XAnyEvent *ev;
+ char *key;
+ int n;
+
+ ev = e;
+ fmtprint(fmt, "%3ld %-20s ", ev->serial, eventtype(ev->type));
+ if(ev->send_event)
+ fmtstrcpy(fmt, "(sendevent) ");
+
+ n = 0;
+ va_start(ap, e);
+ for(;;) {
+ fn = va_arg(ap, Tfn);
+ if(fn == TEnd)
+ break;
+
+ if(n++ != 0)
+ fmtprint(fmt, "%s", sep);
+
+ key = va_arg(ap, char*);
+ fmtprint(fmt, "%s=", key);
+ fn(fmt, &ap);
+ }
+ va_end(ap);
+ return 0;
+}
+
+/*****************************************************************************/
+/*** Routines to print out readable values for the field of various events ***/
+/*****************************************************************************/
+
+static int
+VerbMotion(Fmt *fmt, XEvent *e) {
+ XMotionEvent *ev = &e->xmotion;
+
+ return pevent(fmt, ev,
+ TWindow, _(window),
+ TWindow, _(root),
+ TWindow, _(subwindow),
+ TTime, _(time),
+ TInt, _(x), TInt, _(y),
+ TInt, _(x_root), TInt, _(y_root),
+ TModState, _(state),
+ TBool, _(same_screen),
+ TEnd
+ );
+ //fprintf(stderr, "is_hint=%s%s", IsHint(ev->is_hint), sep);
+}
+
+static int
+VerbButton(Fmt *fmt, XEvent *e) {
+ XButtonEvent *ev = &e->xbutton;
+
+ return pevent(fmt, ev,
+ TWindow, _(window),
+ TWindow, _(root),
+ TWindow, _(subwindow),
+ TTime, _(time),
+ TInt, _(x), TInt, _(y),
+ TInt, _(x_root), TInt, _(y_root),
+ TModState, _(state),
+ TModState, _(button),
+ TBool, _(same_screen),
+ TEnd
+ );
+}
+
+static int
+VerbColormap(Fmt *fmt, XEvent *e) {
+ XColormapEvent *ev = &e->xcolormap;
+
+ return pevent(fmt, ev,
+ TWindow, _(window),
+ TIntNone, _(colormap),
+ TBool, _(new),
+ TColMap, _(state),
+ TEnd
+ );
+}
+
+static int
+VerbCrossing(Fmt *fmt, XEvent *e) {
+ XCrossingEvent *ev = &e->xcrossing;
+
+ return pevent(fmt, ev,
+ TWindow, _(window),
+ TWindow, _(root),
+ TWindow, _(subwindow),
+ TTime, _(time),
+ TInt, _(x), TInt, _(y),
+ TInt, _(x_root), TInt, _(y_root),
+ TGrabMode, _(mode),
+ TXing, _(detail),
+ TBool, _(same_screen),
+ TBool, _(focus),
+ TModState, _(state),
+ TEnd
+ );
+}
+
+static int
+VerbExpose(Fmt *fmt, XEvent *e) {
+ XExposeEvent *ev = &e->xexpose;
+
+ return pevent(fmt, ev,
+ TWindow, _(window),
+ TInt, _(x), TInt, _(y),
+ TInt, _(width), TInt, _(height),
+ TInt, _(count),
+ TEnd
+ );
+}
+
+static int
+VerbGraphicsExpose(Fmt *fmt, XEvent *e) {
+ XGraphicsExposeEvent *ev = &e->xgraphicsexpose;
+
+ return pevent(fmt, ev,
+ TWindow, _(drawable),
+ TInt, _(x), TInt, _(y),
+ TInt, _(width), TInt, _(height),
+ TMajor, _(major_code),
+ TInt, _(minor_code),
+ TEnd
+ );
+}
+
+static int
+VerbNoExpose(Fmt *fmt, XEvent *e) {
+ XNoExposeEvent *ev = &e->xnoexpose;
+
+ return pevent(fmt, ev,
+ TWindow, _(drawable),
+ TMajor, _(major_code),
+ TInt, _(minor_code),
+ TEnd
+ );
+}
+
+static int
+VerbFocus(Fmt *fmt, XEvent *e) {
+ XFocusChangeEvent *ev = &e->xfocus;
+
+ return pevent(fmt, ev,
+ TWindow, _(window),
+ TGrabMode, _(mode),
+ TFocus, _(detail),
+ TEnd
+ );
+}
+
+static int
+VerbKeymap(Fmt *fmt, XEvent *e) {
+ XKeymapEvent *ev = &e->xkeymap;
+ int i;
+
+ fmtprint(fmt, "window=0x%x%s", (int)ev->window, sep);
+ fmtprint(fmt, "key_vector=");
+ for (i = 0; i < 32; i++)
+ fmtprint(fmt, "%02x", ev->key_vector[i]);
+ fmtprint(fmt, "\n");
+ return 0;
+}
+
+static int
+VerbKey(Fmt *fmt, XEvent *e) {
+ XKeyEvent *ev = &e->xkey;
+
+ return pevent(fmt, ev,
+ TWindow, _(window),
+ TWindow, _(root),
+ TWindow, _(subwindow),
+ TTime, _(time),
+ TInt, _(x), TInt, _(y),
+ TInt, _(x_root), TInt, _(y_root),
+ TModState, _(state),
+ TKeycode, "keycode", ev,
+ TBool, _(same_screen),
+ TEnd
+ );
+}
+
+static int
+VerbProperty(Fmt *fmt, XEvent *e) {
+ XPropertyEvent *ev = &e->xproperty;
+
+ return pevent(fmt, ev,
+ TWindow, _(window),
+ TAtom, _(atom),
+ TTime, _(time),
+ TPropState, _(state),
+ TEnd
+ );
+}
+
+static int
+VerbResizeRequest(Fmt *fmt, XEvent *e) {
+ XResizeRequestEvent *ev = &e->xresizerequest;
+
+ return pevent(fmt, ev,
+ TWindow, _(window),
+ TInt, _(width), TInt, _(height),
+ TEnd
+ );
+}
+
+static int
+VerbCirculate(Fmt *fmt, XEvent *e) {
+ XCirculateEvent *ev = &e->xcirculate;
+
+ return pevent(fmt, ev,
+ TWindow, _(event),
+ TWindow, _(window),
+ TPlace, _(place),
+ TEnd
+ );
+}
+
+static int
+VerbConfigure(Fmt *fmt, XEvent *e) {
+ XConfigureEvent *ev = &e->xconfigure;
+
+ return pevent(fmt, ev,
+ TWindow, _(event),
+ TWindow, _(window),
+ TInt, _(x), TInt, _(y),
+ TInt, _(width), TInt, _(height),
+ TInt, _(border_width),
+ TIntNone, _(above),
+ TBool, _(override_redirect),
+ TEnd
+ );
+}
+
+static int
+VerbCreateWindow(Fmt *fmt, XEvent *e) {
+ XCreateWindowEvent *ev = &e->xcreatewindow;
+
+ return pevent(fmt, ev,
+ TWindow, _(parent),
+ TWindow, _(window),
+ TInt, _(x), TInt, _(y),
+ TInt, _(width), TInt, _(height),
+ TInt, _(border_width),
+ TBool, _(override_redirect),
+ TEnd
+ );
+}
+
+static int
+VerbDestroyWindow(Fmt *fmt, XEvent *e) {
+ XDestroyWindowEvent *ev = &e->xdestroywindow;
+
+ return pevent(fmt, ev,
+ TWindow, _(event),
+ TWindow, _(window),
+ TEnd
+ );
+}
+
+static int
+VerbGravity(Fmt *fmt, XEvent *e) {
+ XGravityEvent *ev = &e->xgravity;
+
+ return pevent(fmt, ev,
+ TWindow, _(event),
+ TWindow, _(window),
+ TInt, _(x), TInt, _(y),
+ TEnd
+ );
+}
+
+static int
+VerbMap(Fmt *fmt, XEvent *e) {
+ XMapEvent *ev = &e->xmap;
+
+ return pevent(fmt, ev,
+ TWindow, _(event),
+ TWindow, _(window),
+ TBool, _(override_redirect),
+ TEnd
+ );
+}
+
+static int
+VerbReparent(Fmt *fmt, XEvent *e) {
+ XReparentEvent *ev = &e->xreparent;
+
+ return pevent(fmt, ev,
+ TWindow, _(event),
+ TWindow, _(window),
+ TWindow, _(parent),
+ TInt, _(x), TInt, _(y),
+ TBool, _(override_redirect),
+ TEnd
+ );
+}
+
+static int
+VerbUnmap(Fmt *fmt, XEvent *e) {
+ XUnmapEvent *ev = &e->xunmap;
+
+ return pevent(fmt, ev,
+ TWindow, _(event),
+ TWindow, _(window),
+ TBool, _(from_configure),
+ TEnd
+ );
+}
+
+static int
+VerbCirculateRequest(Fmt *fmt, XEvent *e) {
+ XCirculateRequestEvent *ev = &e->xcirculaterequest;
+
+ return pevent(fmt, ev,
+ TWindow, _(parent),
+ TWindow, _(window),
+ TPlace, _(place),
+ TEnd
+ );
+}
+
+static int
+VerbConfigureRequest(Fmt *fmt, XEvent *e) {
+ XConfigureRequestEvent *ev = &e->xconfigurerequest;
+
+ return pevent(fmt, ev,
+ TWindow, _(parent),
+ TWindow, _(window),
+ TInt, _(x), TInt, _(y),
+ TInt, _(width), TInt, _(height),
+ TInt, _(border_width),
+ TIntNone, _(above),
+ TConfDetail, _(detail),
+ TConfMask, _(value_mask),
+ TEnd
+ );
+}
+
+static int
+VerbMapRequest(Fmt *fmt, XEvent *e) {
+ XMapRequestEvent *ev = &e->xmaprequest;
+
+ return pevent(fmt, ev,
+ TWindow, _(parent),
+ TWindow, _(window),
+ TEnd
+ );
+}
+
+static int
+VerbClient(Fmt *fmt, XEvent *e) {
+ XClientMessageEvent *ev = &e->xclient;
+
+ return pevent(fmt, ev,
+ TWindow, _(window),
+ TAtom, _(message_type),
+ TInt, _(format),
+ TData, "data (as longs)", &ev->data,
+ TEnd
+ );
+}
+
+static int
+VerbMapping(Fmt *fmt, XEvent *e) {
+ XMappingEvent *ev = &e->xmapping;
+
+ return pevent(fmt, ev,
+ TWindow, _(window),
+ TMapping, _(request),
+ TWindow, _(first_keycode),
+ TWindow, _(count),
+ TEnd
+ );
+}
+
+static int
+VerbSelectionClear(Fmt *fmt, XEvent *e) {
+ XSelectionClearEvent *ev = &e->xselectionclear;
+
+ return pevent(fmt, ev,
+ TWindow, _(window),
+ TAtom, _(selection),
+ TTime, _(time),
+ TEnd
+ );
+}
+
+static int
+VerbSelection(Fmt *fmt, XEvent *e) {
+ XSelectionEvent *ev = &e->xselection;
+
+ return pevent(fmt, ev,
+ TWindow, _(requestor),
+ TAtom, _(selection),
+ TAtom, _(target),
+ TAtom, _(property),
+ TTime, _(time),
+ TEnd
+ );
+}
+
+static int
+VerbSelectionRequest(Fmt *fmt, XEvent *e) {
+ XSelectionRequestEvent *ev = &e->xselectionrequest;
+
+ return pevent(fmt, ev,
+ TWindow, _(owner),
+ TWindow, _(requestor),
+ TAtom, _(selection),
+ TAtom, _(target),
+ TAtom, _(property),
+ TTime, _(time),
+ TEnd
+ );
+}
+
+static int
+VerbVisibility(Fmt *fmt, XEvent *e) {
+ XVisibilityEvent *ev = &e->xvisibility;
+
+ return pevent(fmt, ev,
+ TWindow, _(window),
+ TVis, _(state),
+ TEnd
+ );
+}
+
+/******************************************************************************/
+/**************** Print the values of all fields for any event ****************/
+/******************************************************************************/
+
+typedef struct Handler Handler;
+struct Handler {
+ int key;
+ int (*fn)(Fmt*, XEvent*);
+};
+
+int
+fmtevent(Fmt *fmt) {
+ XEvent *e;
+ XAnyEvent *ev;
+ /*
+ fprintf(stderr, "type=%s%s", eventtype(e->xany.type), sep);
+ fprintf(stderr, "serial=%lu%s", ev->serial, sep);
+ fprintf(stderr, "send_event=%s%s", TorF(ev->send_event), sep);
+ fprintf(stderr, "display=0x%p%s", ev->display, sep);
+ */
+ static Handler fns[] = {
+ {MotionNotify, VerbMotion},
+ {ButtonPress, VerbButton},
+ {ButtonRelease, VerbButton},
+ {ColormapNotify, VerbColormap},
+ {EnterNotify, VerbCrossing},
+ {LeaveNotify, VerbCrossing},
+ {Expose, VerbExpose},
+ {GraphicsExpose, VerbGraphicsExpose},
+ {NoExpose, VerbNoExpose},
+ {FocusIn, VerbFocus},
+ {FocusOut, VerbFocus},
+ {KeymapNotify, VerbKeymap},
+ {KeyPress, VerbKey},
+ {KeyRelease, VerbKey},
+ {PropertyNotify, VerbProperty},
+ {ResizeRequest, VerbResizeRequest},
+ {CirculateNotify, VerbCirculate},
+ {ConfigureNotify, VerbConfigure},
+ {CreateNotify, VerbCreateWindow},
+ {DestroyNotify, VerbDestroyWindow},
+ {GravityNotify, VerbGravity},
+ {MapNotify, VerbMap},
+ {ReparentNotify, VerbReparent},
+ {UnmapNotify, VerbUnmap},
+ {CirculateRequest, VerbCirculateRequest},
+ {ConfigureRequest, VerbConfigureRequest},
+ {MapRequest, VerbMapRequest},
+ {ClientMessage, VerbClient},
+ {MappingNotify, VerbMapping},
+ {SelectionClear, VerbSelectionClear},
+ {SelectionNotify, VerbSelection},
+ {SelectionRequest, VerbSelectionRequest},
+ {VisibilityNotify, VerbVisibility},
+ {0, nil},
+ };
+ Handler *p;
+
+ e = va_arg(fmt->args, XEvent*);
+ ev = &e->xany;
+
+ for (p = fns; p->fn; p++)
+ if (p->key == ev->type)
+ return p->fn(fmt, e);
+ return 1;
+}
+
diff --git a/cmd/wmii/printevent.h b/cmd/wmii/printevent.h
new file mode 100644
index 0000000..22d6b25
--- /dev/null
+++ b/cmd/wmii/printevent.h
@@ -0,0 +1,248 @@
+int fmtevent(Fmt *fmt);
+
+enum {
+ X_CreateWindow = 1,
+ X_ChangeWindowAttributes,
+ X_GetWindowAttributes,
+ X_DestroyWindow,
+ X_DestroySubwindows,
+ X_ChangeSaveSet,
+ X_ReparentWindow,
+ X_MapWindow,
+ X_MapSubwindows,
+ X_UnmapWindow,
+ X_UnmapSubwindows,
+ X_ConfigureWindow,
+ X_CirculateWindow,
+ X_GetGeometry,
+ X_QueryTree,
+ X_InternAtom,
+ X_GetAtomName,
+ X_ChangeProperty,
+ X_DeleteProperty,
+ X_GetProperty,
+ X_ListProperties,
+ X_SetSelectionOwner,
+ X_GetSelectionOwner,
+ X_ConvertSelection,
+ X_SendEvent,
+ X_GrabPointer,
+ X_UngrabPointer,
+ X_GrabButton,
+ X_UngrabButton,
+ X_ChangeActivePointerGrab,
+ X_GrabKeyboard,
+ X_UngrabKeyboard,
+ X_GrabKey,
+ X_UngrabKey,
+ X_AllowEvents,
+ X_GrabServer,
+ X_UngrabServer,
+ X_QueryPointer,
+ X_GetMotionEvents,
+ X_TranslateCoords,
+ X_WarpPointer,
+ X_SetInputFocus,
+ X_GetInputFocus,
+ X_QueryKeymap,
+ X_OpenFont,
+ X_CloseFont,
+ X_QueryFont,
+ X_QueryTextExtents,
+ X_ListFonts,
+ X_ListFontsWithInfo,
+ X_SetFontPath,
+ X_GetFontPath,
+ X_CreatePixmap,
+ X_FreePixmap,
+ X_CreateGC,
+ X_ChangeGC,
+ X_CopyGC,
+ X_SetDashes,
+ X_SetClipRectangles,
+ X_FreeGC,
+ X_ClearArea,
+ X_CopyArea,
+ X_CopyPlane,
+ X_PolyPoint,
+ X_PolyLine,
+ X_PolySegment,
+ X_PolyRectangle,
+ X_PolyArc,
+ X_FillPoly,
+ X_PolyFillRectangle,
+ X_PolyFillArc,
+ X_PutImage,
+ X_GetImage,
+ X_PolyText8,
+ X_PolyText16,
+ X_ImageText8,
+ X_ImageText16,
+ X_CreateColormap,
+ X_FreeColormap,
+ X_CopyColormapAndFree,
+ X_InstallColormap,
+ X_UninstallColormap,
+ X_ListInstalledColormaps,
+ X_AllocColor,
+ X_AllocNamedColor,
+ X_AllocColorCells,
+ X_AllocColorPlanes,
+ X_FreeColors,
+ X_StoreColors,
+ X_StoreNamedColor,
+ X_QueryColors,
+ X_LookupColor,
+ X_CreateCursor,
+ X_CreateGlyphCursor,
+ X_FreeCursor,
+ X_RecolorCursor,
+ X_QueryBestSize,
+ X_QueryExtension,
+ X_ListExtensions,
+ X_ChangeKeyboardMapping,
+ X_GetKeyboardMapping,
+ X_ChangeKeyboardControl,
+ X_GetKeyboardControl,
+ X_Bell,
+ X_ChangePointerControl,
+ X_GetPointerControl,
+ X_SetScreenSaver,
+ X_GetScreenSaver,
+ X_ChangeHosts,
+ X_ListHosts,
+ X_SetAccessControl,
+ X_SetCloseDownMode,
+ X_KillClient,
+ X_RotateProperties,
+ X_ForceScreenSaver,
+ X_SetPointerMapping,
+ X_GetPointerMapping,
+ X_SetModifierMapping,
+ X_GetModifierMapping,
+ X_NoOperation,
+};
+
+#define XMajors \
+ "<nil>",\
+ "CreateWindow",\
+ "ChangeWindowAttributes",\
+ "GetWindowAttributes",\
+ "DestroyWindow",\
+ "DestroySubwindows",\
+ "ChangeSaveSet",\
+ "ReparentWindow",\
+ "MapWindow",\
+ "MapSubwindows",\
+ "UnmapWindow",\
+ "UnmapSubwindows",\
+ "ConfigureWindow",\
+ "CirculateWindow",\
+ "GetGeometry",\
+ "QueryTree",\
+ "InternAtom",\
+ "GetAtomName",\
+ "ChangeProperty",\
+ "DeleteProperty",\
+ "GetProperty",\
+ "ListProperties",\
+ "SetSelectionOwner",\
+ "GetSelectionOwner",\
+ "ConvertSelection",\
+ "SendEvent",\
+ "GrabPointer",\
+ "UngrabPointer",\
+ "GrabButton",\
+ "UngrabButton",\
+ "ChangeActivePointerGrab",\
+ "GrabKeyboard",\
+ "UngrabKeyboard",\
+ "GrabKey",\
+ "UngrabKey",\
+ "AllowEvents",\
+ "GrabServer",\
+ "UngrabServer",\
+ "QueryPointer",\
+ "GetMotionEvents",\
+ "TranslateCoords",\
+ "WarpPointer",\
+ "SetInputFocus",\
+ "GetInputFocus",\
+ "QueryKeymap",\
+ "OpenFont",\
+ "CloseFont",\
+ "QueryFont",\
+ "QueryTextExtents",\
+ "ListFonts",\
+ "ListFontsWithInfo",\
+ "SetFontPath",\
+ "GetFontPath",\
+ "CreatePixmap",\
+ "FreePixmap",\
+ "CreateGC",\
+ "ChangeGC",\
+ "CopyGC",\
+ "SetDashes",\
+ "SetClipRectangles",\
+ "FreeGC",\
+ "ClearArea",\
+ "CopyArea",\
+ "CopyPlane",\
+ "PolyPoint",\
+ "PolyLine",\
+ "PolySegment",\
+ "PolyRectangle",\
+ "PolyArc",\
+ "FillPoly",\
+ "PolyFillRectangle",\
+ "PolyFillArc",\
+ "PutImage",\
+ "GetImage",\
+ "PolyText8",\
+ "PolyText16",\
+ "ImageText8",\
+ "ImageText16",\
+ "CreateColormap",\
+ "FreeColormap",\
+ "CopyColormapAndFree",\
+ "InstallColormap",\
+ "UninstallColormap",\
+ "ListInstalledColormaps",\
+ "AllocColor",\
+ "AllocNamedColor",\
+ "AllocColorCells",\
+ "AllocColorPlanes",\
+ "FreeColors",\
+ "StoreColors",\
+ "StoreNamedColor",\
+ "QueryColors",\
+ "LookupColor",\
+ "CreateCursor",\
+ "CreateGlyphCursor",\
+ "FreeCursor",\
+ "RecolorCursor",\
+ "QueryBestSize",\
+ "QueryExtension",\
+ "ListExtensions",\
+ "ChangeKeyboardMapping",\
+ "GetKeyboardMapping",\
+ "ChangeKeyboardControl",\
+ "GetKeyboardControl",\
+ "Bell",\
+ "ChangePointerControl",\
+ "GetPointerControl",\
+ "SetScreenSaver",\
+ "GetScreenSaver",\
+ "ChangeHosts",\
+ "ListHosts",\
+ "SetAccessControl",\
+ "SetCloseDownMode",\
+ "KillClient",\
+ "RotateProperties",\
+ "ForceScreenSaver",\
+ "SetPointerMapping",\
+ "GetPointerMapping",\
+ "SetModifierMapping",\
+ "GetModifierMapping",\
+ "NoOperation",\
+
diff --git a/cmd/wmii/root.c b/cmd/wmii/root.c
new file mode 100644
index 0000000..e3e53e7
--- /dev/null
+++ b/cmd/wmii/root.c
@@ -0,0 +1,89 @@
+/* Copyright ©2008-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include "fns.h"
+
+static Handlers handlers;
+
+void
+root_init(void) {
+ WinAttr wa;
+
+ wa.event_mask = EnterWindowMask
+ | FocusChangeMask
+ | LeaveWindowMask
+ | PointerMotionMask
+ | SubstructureNotifyMask
+ | SubstructureRedirectMask;
+ wa.cursor = cursor[CurNormal];
+ setwinattr(&scr.root, &wa,
+ CWEventMask
+ | CWCursor);
+ sethandler(&scr.root, &handlers);
+}
+
+static void
+enter_event(Window *w, XCrossingEvent *e) {
+ disp.sel = true;
+ frame_draw_all();
+}
+
+static void
+leave_event(Window *w, XCrossingEvent *e) {
+ if(!e->same_screen) {
+ disp.sel = false;
+ frame_draw_all();
+ }
+}
+
+static void
+focusin_event(Window *w, XFocusChangeEvent *e) {
+ if(e->mode == NotifyGrab)
+ disp.hasgrab = &c_root;
+}
+
+static void
+mapreq_event(Window *w, XMapRequestEvent *e) {
+ XWindowAttributes wa;
+
+ if(!XGetWindowAttributes(display, e->window, &wa))
+ return;
+ if(wa.override_redirect) {
+ /* Do I really want these? */
+ /* Probably not.
+ XSelectInput(display, e->window,
+ PropertyChangeMask | StructureNotifyMask);
+ */
+ return;
+ }
+ if(!win2client(e->window))
+ client_create(e->window, &wa);
+}
+
+static void
+motion_event(Window *w, XMotionEvent *e) {
+ Rectangle r, r2;
+
+ r = rectsetorigin(Rect(0, 0, 1, 1), Pt(e->x_root, e->y_root));
+ r2 = constrain(r, 0);
+ if(!eqrect(r, r2))
+ warppointer(r2.min);
+}
+
+static void
+kdown_event(Window *w, XKeyEvent *e) {
+
+ e->state &= valid_mask;
+ kpress(w->xid, e->state, (KeyCode)e->keycode);
+}
+
+static Handlers handlers = {
+ .enter = enter_event,
+ .focusin = focusin_event,
+ .kdown = kdown_event,
+ .leave = leave_event,
+ .mapreq = mapreq_event,
+ .motion = motion_event,
+};
+
diff --git a/cmd/wmii/rule.c b/cmd/wmii/rule.c
new file mode 100644
index 0000000..b2f838f
--- /dev/null
+++ b/cmd/wmii/rule.c
@@ -0,0 +1,107 @@
+/* Copyright ©2006 Anselm R. Garbe <garbeam at gmail dot com>
+ * See LICENSE file for license details.
+ */
+
+#include "dat.h"
+#include "fns.h"
+
+void
+trim(char *str, const char *chars) {
+ const char *cp;
+ char *p, *q;
+ char c;
+
+ q = str;
+ for(p=str; *p; p++) {
+ for(cp=chars; (c = *cp); cp++)
+ if(*p == c)
+ break;
+ if(c == '\0')
+ *q++ = *p;
+ }
+ *q = '\0';
+}
+
+/* XXX: I hate this. --KM */
+void
+update_rules(Rule **rule, const char *data) {
+ /* basic rule matching language /regex/ -> value
+ * regex might contain POSIX regex syntax defined in regex(3) */
+ enum {
+ IGNORE,
+ REGEX,
+ VALUE,
+ COMMENT,
+ };
+ int state;
+ Rule *rul;
+ char regex[256], value[256];
+ char *regex_end = regex + sizeof(regex) - 1;
+ char *value_end = value + sizeof(value) - 1;
+ char *r, *v;
+ const char *p;
+ char c;
+
+ SET(r);
+ SET(v);
+
+ if(!data || !strlen(data))
+ return;
+ while((rul = *rule)) {
+ *rule = rul->next;
+ free(rul->regex);
+ free(rul);
+ }
+ state = IGNORE;
+ for(p = data; (c = *p); p++)
+ switch(state) {
+ case COMMENT:
+ if(c == '\n')
+ state = IGNORE;
+ break;
+ case IGNORE:
+ if(c == '#')
+ state = COMMENT;
+ else if(c == '/') {
+ r = regex;
+ state = REGEX;
+ }
+ else if(c == '>') {
+ value[0] = 0;
+ v = value;
+ state = VALUE;
+ }
+ break;
+ case REGEX:
+ if(c == '\\' && p[1] == '/')
+ p++;
+ else if(c == '/') {
+ *r = 0;
+ state = IGNORE;
+ break;
+ }
+ if(r < regex_end)
+ *r++ = c;
+ break;
+ case VALUE:
+ if(c == '\n' || c == '#' || c == 0) {
+ *v = 0;
+ trim(value, " \t");
+ *rule = emallocz(sizeof **rule);
+ (*rule)->regex = regcomp(regex);
+ if((*rule)->regex) {
+ utflcpy((*rule)->value, value, sizeof rul->value);
+ rule = &(*rule)->next;
+ }else
+ free(*rule);
+ state = IGNORE;
+ if(c == '#')
+ state = COMMENT;
+ }
+ else if(v < value_end)
+ *v++ = c;
+ break;
+ default: /* can't happen */
+ die("invalid state");
+ }
+}
diff --git a/cmd/wmii/screen.c b/cmd/wmii/screen.c
new file mode 100644
index 0000000..76d89eb
--- /dev/null
+++ b/cmd/wmii/screen.c
@@ -0,0 +1,189 @@
+/* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include <math.h>
+#include <stdlib.h>
+#include "fns.h"
+
+#ifdef notdef
+void
+mapscreens(void) {
+ WMScreen *s, *ss;
+ Rectangle r;
+ int i, j;
+
+#define frob(left, min, max, x, y) \
+ if(Dy(r) > 0) /* If they intersect at some point on this axis */ \
+ if(ss->r.min.x < s->r.min.x) { \
+ if((!s->left) \
+ || (abs(Dy(r)) < abs(s->left.max.x - s->min.x))) \
+ s->left = ss; \
+ }
+
+ /* Variable hell? Certainly. */
+ for(i=0; i < nscreens; i++) {
+ s = screens[i];
+ for(j=0; j < nscreens; j++) {
+ if(i == j)
+ continue;
+ ss = screens[j];
+ r = rect_intersection(ss->r, s->r);
+ frob(left, min, max, x, y);
+ frob(right, max, min, x, y);
+ frob(atop, min, max, y, x);
+ frob(below, max, min, y, x);
+ }
+ }
+#undef frob
+}
+
+int findscreen(Rectangle, int);
+int
+findscreen(Rectangle rect, int direction) {
+ Rectangle r;
+ WMScreen *ss, *s;
+ int best, i, j;
+
+ best = -1;
+#define frob(min, max, x, y)
+ if(Dy(r) > 0) /* If they intersect at some point on this axis */
+ if(ss->r.min.x < rect.min.x) {
+ if(best == -1
+ || (abs(ss->r.max.x - rect.min.x) < abs(screens[best]->r.max.x - rect.min.x)))
+ best = s->idx;
+ }
+
+ /* Variable hell? Certainly. */
+ for(i=0; i < nscreens; i++) {
+ ss = screens[j];
+ r = rect_intersection(ss->r, rect);
+ switch(direction) {
+ default:
+ return -1;
+ case West:
+ frob(min, max, x, y);
+ break;
+ case East:
+ frob(max, min, x, y);
+ break;
+ case North:
+ frob(min, max, y, x);
+ break;
+ case South:
+ frob(max, min, y, x);
+ break;
+ }
+ }
+#undef frob
+}
+#endif
+
+static Rectangle
+leastthing(Rectangle rect, int direction, Vector_ptr *vec, Rectangle (*key)(void*)) {
+ Rectangle r;
+ int i, best, d;
+
+ SET(d);
+ SET(best);
+ for(i=0; i < vec->n; i++) {
+ r = key(vec->ary[i]);
+ switch(direction) {
+ case South: d = r.min.y; break;
+ case North: d = -r.max.y; break;
+ case East: d = r.min.x; break;
+ case West: d = -r.max.x; break;
+ }
+ if(i == 0 || d < best)
+ best = d;
+ }
+ switch(direction) {
+ case South: rect.min.y = rect.max.y = best; break;
+ case North: rect.min.y = rect.max.y = -best; break;
+ case East: rect.min.x = rect.max.x = best; break;
+ case West: rect.min.x = rect.max.x = -best; break;
+ }
+ return rect;
+}
+
+void*
+findthing(Rectangle rect, int direction, Vector_ptr *vec, Rectangle (*key)(void*), bool wrap) {
+ Rectangle isect;
+ Rectangle r, bestisect = {0,}, bestr = {0,};
+ void *best, *p;
+ int i, n;
+
+ best = nil;
+
+ /* For the record, I really hate these macros. */
+#define frob(min, max, LT, x, y) \
+ if(D##y(isect) > 0) /* If they intersect at some point on this axis */ \
+ if(r.min.x LT rect.min.x) { \
+ n = abs(r.max.x - rect.min.x) - abs(bestr.max.x - rect.min.x); \
+ if(best == nil \
+ || n == 0 && D##y(isect) > D##y(bestisect) \
+ || n < 0 \
+ ) { \
+ best = p; \
+ bestr = r; \
+ bestisect = isect; \
+ } \
+ }
+
+ /* Variable hell? Certainly. */
+ for(i=0; i < vec->n; i++) {
+ p = vec->ary[i];
+ r = key(p);
+ isect = rect_intersection(rect, r);
+ switch(direction) {
+ default:
+ die("not reached");
+ /* Not reached */
+ case West:
+ frob(min, max, <, x, y);
+ break;
+ case East:
+ frob(max, min, >, x, y);
+ break;
+ case North:
+ frob(min, max, <, y, x);
+ break;
+ case South:
+ frob(max, min, >, y, x);
+ break;
+ }
+ }
+#undef frob
+ if(!best && wrap) {
+ r = leastthing(rect, direction, vec, key);
+ return findthing(r, direction, vec, key, false);
+ }
+ return best;
+}
+
+static int
+area(Rectangle r) {
+ return Dx(r) * Dy(r) *
+ (Dx(r) < 0 && Dy(r) < 0 ? -1 : 1);
+}
+
+int
+ownerscreen(Rectangle r) {
+ Rectangle isect;
+ int s, a, best, besta;
+
+ SET(besta);
+ best = -1;
+ for(s=0; s < nscreens; s++) {
+ if(!screens[s]->showing)
+ continue;
+ isect = rect_intersection(r, screens[s]->r);
+ a = area(isect);
+ if(best < 0 || a > besta) {
+ besta = a;
+ best = s;
+ }
+ }
+ return best;
+}
+
diff --git a/cmd/wmii/utf.c b/cmd/wmii/utf.c
new file mode 100644
index 0000000..48e2a6d
--- /dev/null
+++ b/cmd/wmii/utf.c
@@ -0,0 +1,60 @@
+/* Public Domain --Kris Maglione */
+#include "dat.h"
+#include <errno.h>
+#include <iconv.h>
+#include <langinfo.h>
+#include <string.h>
+#include "fns.h"
+
+char*
+toutf8n(const char *str, size_t nstr) {
+ static iconv_t cd;
+ static bool haveiconv;
+ char *buf, *pos;
+ size_t nbuf, bsize;
+
+ if(cd == nil) {
+ cd = iconv_open("UTF-8", nl_langinfo(CODESET));
+ if((long)cd == -1)
+ warning("Can't convert from local character encoding to UTF-8");
+ else
+ haveiconv = true;
+ }
+ if(!haveiconv) {
+ buf = emalloc(nstr+1);
+ memcpy(buf, str, nstr);
+ buf[nstr+1] = '\0';
+ return buf;
+ }
+
+ iconv(cd, nil, nil, nil, nil);
+
+ bsize = (nstr+1) << 1;
+ buf = emalloc(bsize);
+ pos = buf;
+ nbuf = bsize-1;
+ /* The (void*) cast is because, while the BSDs declare:
+ * size_t iconv(iconv_t, const char**, size_t*, char**, size_t*),
+ * GNU/Linux and POSIX declare:
+ * size_t iconv(iconv_t, char**, size_t*, char**, size_t*).
+ * This just happens to be safer than declaring our own
+ * prototype.
+ */
+ while(iconv(cd, (void*)&str, &nstr, &pos, &nbuf) == -1)
+ if(errno == E2BIG) {
+ bsize <<= 1;
+ nbuf = pos - buf;
+ buf = erealloc(buf, bsize);
+ pos = buf + nbuf;
+ nbuf = bsize - nbuf - 1;
+ }else
+ break;
+ *pos++ = '\0';
+ return erealloc(buf, pos-buf);
+}
+
+char*
+toutf8(const char *str) {
+ return toutf8n(str, strlen(str));
+}
+
diff --git a/cmd/wmii/view.c b/cmd/wmii/view.c
new file mode 100644
index 0000000..e853003
--- /dev/null
+++ b/cmd/wmii/view.c
@@ -0,0 +1,630 @@
+/* Copyright ©2004-2006 Anselm R. Garbe <garbeam at gmail dot com>
+ * Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include "fns.h"
+
+static bool
+empty_p(View *v) {
+ Frame *f;
+ Area *a;
+ char **p;
+ int cmp;
+ int s;
+
+ foreach_frame(v, s, a, f) {
+ cmp = 1;
+ for(p=f->client->retags; *p; p++) {
+ cmp = strcmp(*p, v->name);
+ if(cmp >= 0)
+ break;
+ }
+ if(cmp)
+ return false;
+ }
+ return true;
+}
+
+static void
+_view_select(View *v) {
+ if(selview != v) {
+ if(selview)
+ event("UnfocusTag %s\n",selview->name);
+ selview = v;
+ event("FocusTag %s\n", v->name);
+ event("AreaFocus %a\n", v->sel);
+ ewmh_updateview();
+ }
+}
+
+Client*
+view_selclient(View *v) {
+ if(v->sel && v->sel->sel)
+ return v->sel->sel->client;
+ return nil;
+}
+
+bool
+view_fullscreen_p(View *v, int scrn) {
+ Frame *f;
+
+ for(f=v->floating->frame; f; f=f->anext)
+ if(f->client->fullscreen == scrn)
+ return true;
+ return false;
+}
+
+View*
+view_create(const char *name) {
+ static ushort id = 1;
+ View **vp;
+ Client *c;
+ View *v;
+ int i;
+
+ for(vp=&view; *vp; vp=&(*vp)->next) {
+ i = strcmp((*vp)->name, name);
+ if(i == 0)
+ return *vp;
+ if(i > 0)
+ break;
+ }
+
+ v = emallocz(sizeof *v);
+ v->id = id++;
+ v->r = emallocz(nscreens * sizeof *v->r);
+ v->pad = emallocz(nscreens * sizeof *v->pad);
+
+ utflcpy(v->name, name, sizeof v->name);
+
+ event("CreateTag %s\n", v->name);
+ area_create(v, nil, screen->idx, 0);
+
+ v->areas = emallocz(nscreens * sizeof *v->areas);
+
+ for(i=0; i < nscreens; i++)
+ view_init(v, i);
+
+
+ area_focus(v->firstarea);
+
+ v->next = *vp;
+ *vp = v;
+
+ /* FIXME: Belongs elsewhere */
+ /* FIXME: Can do better. */
+ for(c=client; c; c=c->next)
+ if(c != kludge)
+ client_applytags(c, c->tags);
+
+ view_arrange(v);
+ if(!selview)
+ _view_select(v);
+ ewmh_updateviews();
+ return v;
+}
+
+void
+view_init(View *v, int iscreen) {
+ v->r[iscreen] = screens[iscreen]->r;
+ v->areas[iscreen] = nil;
+ column_new(v, nil, iscreen, 0);
+}
+
+void
+view_destroy(View *v) {
+ View **vp;
+ Frame *f;
+ View *tv;
+ Area *a;
+ int s;
+
+ if(v->dead)
+ return;
+ v->dead = true;
+
+ for(vp=&view; *vp; vp=&(*vp)->next)
+ if(*vp == v) break;
+ *vp = v->next;
+ assert(v != v->next);
+
+ /* Detach frames held here by regex tags. */
+ /* FIXME: Can do better. */
+ foreach_frame(v, s, a, f)
+ client_applytags(f->client, f->client->tags);
+
+ foreach_area(v, s, a)
+ area_destroy(a);
+
+ event("DestroyTag %s\n", v->name);
+
+ if(v == selview) {
+ for(tv=view; tv; tv=tv->next)
+ if(tv->next == *vp) break;
+ if(tv == nil)
+ tv = view;
+ if(tv)
+ view_focus(screen, tv);
+ }
+ free(v->areas);
+ free(v->r);
+ free(v);
+ ewmh_updateviews();
+}
+
+Area*
+view_findarea(View *v, int screen, int idx, bool create) {
+ Area *a;
+
+ assert(screen >= 0 && screen < nscreens);
+
+ for(a=v->areas[screen]; a && --idx > 0; a=a->next)
+ if(create && a->next == nil)
+ return area_create(v, a, screen, 0);
+ return a;
+}
+
+static void
+frames_update_sel(View *v) {
+ Frame *f;
+ Area *a;
+ int s;
+
+ foreach_frame(v, s, a, f)
+ f->client->sel = f;
+}
+
+/* Don't let increment hints take up more than half
+ * of the screen, in either direction.
+ */
+static Rectangle
+fix_rect(Rectangle old, Rectangle new) {
+ double r;
+
+ new = rect_intersection(new, old);
+
+ r = (Dy(old) - Dy(new)) / Dy(old);
+ if(r > .5) {
+ r -= .5;
+ new.min.y -= r * (new.min.y - old.min.y);
+ new.max.y += r * (old.max.y - new.max.y);
+ }
+ r = (Dx(old) - Dx(new)) / Dx(old);
+ if(r > .5) {
+ r -= .5;
+ new.min.x -= r * (new.min.x - old.min.x);
+ new.max.x += r * (old.max.x - new.max.x);
+ }
+ return new;
+}
+
+void
+view_update_rect(View *v) {
+ static Vector_rect vec;
+ static Vector_rect *vp;
+ Rectangle r, sr, rr, brect, scrnr;
+ WMScreen *scrn;
+ Strut *strut;
+ Frame *f;
+ int s, i;
+ /* These short variable names are hell, eh? */
+
+ /* XXX:
+ if(v != selview)
+ return false;
+ */
+ vec.n = 0;
+ for(f=v->floating->frame; f; f=f->anext) {
+ strut = f->client->strut;
+ if(!strut)
+ continue;
+ vector_rpush(&vec, strut->top);
+ vector_rpush(&vec, strut->left);
+ vector_rpush(&vec, rectaddpt(strut->right, Pt(scr.rect.max.x, 0)));
+ vector_rpush(&vec, rectaddpt(strut->bottom, Pt(0, scr.rect.max.y)));
+ }
+ /* Find the largest screen space not occupied by struts. */
+ vp = unique_rects(&vec, scr.rect);
+ scrnr = max_rect(vp);
+
+ /* FIXME: Multihead. */
+ v->floating->r = scr.rect;
+
+ for(s=0; s < nscreens; s++) {
+ scrn = screens[s];
+ r = fix_rect(scrn->r, scrnr);
+
+ /* Ugly. Very, very ugly. */
+ /*
+ * Try to find some rectangle near the edge of the
+ * screen where the bar will fit. This way, for
+ * instance, a system tray can be placed there
+ * without taking up too much extra screen real
+ * estate.
+ */
+ rr = r;
+ brect = scrn->brect;
+ for(i=0; i < vp->n; i++) {
+ sr = rect_intersection(vp->ary[i], scrn->r);
+ if(Dx(sr) < Dx(r)/2 || Dy(sr) < Dy(brect))
+ continue;
+ if(scrn->barpos == BTop && sr.min.y < rr.min.y
+ || scrn->barpos != BTop && sr.max.y > rr.max.y)
+ rr = sr;
+ }
+ if(scrn->barpos == BTop) {
+ bar_sety(scrn, rr.min.y);
+ r.min.y = max(r.min.y, scrn->brect.max.y);
+ }else {
+ bar_sety(scrn, rr.max.y - Dy(brect));
+ r.max.y = min(r.max.y, scrn->brect.min.y);
+ }
+ bar_setbounds(scrn, rr.min.x, rr.max.x);
+ v->r[s] = r;
+ }
+}
+
+void
+view_update(View *v) {
+ Client *c;
+ Frame *f;
+ Area *a;
+ int s;
+
+ if(v != selview)
+ return;
+ if(starting)
+ return;
+
+ frames_update_sel(v);
+
+ foreach_frame(v, s, a, f)
+ if(f->client->fullscreen >= 0) {
+ f->collapsed = false;
+ if(!f->area->floating) {
+ f->oldarea = area_idx(f->area);
+ f->oldscreen = f->area->screen;
+ area_moveto(v->floating, f);
+ area_setsel(v->floating, f);
+ }else if(f->oldarea == -1)
+ f->oldarea = 0;
+ }
+
+ view_arrange(v);
+
+ for(c=client; c; c=c->next) {
+ f = c->sel;
+ if((f && f->view == v)
+ && (f->area == v->sel || !(f->area && f->area->max && f->area->floating))) {
+ if(f->area)
+ client_resize(c, f->r);
+ }else {
+ unmap_frame(c);
+ client_unmap(c, IconicState);
+ }
+ ewmh_updatestate(c);
+ ewmh_updateclient(c);
+ }
+
+ view_restack(v);
+ if(!v->sel->floating && view_fullscreen_p(v, v->sel->screen))
+ area_focus(v->floating);
+ else
+ area_focus(v->sel);
+ frame_draw_all();
+}
+
+void
+view_focus(WMScreen *s, View *v) {
+
+ USED(s);
+
+ _view_select(v);
+ view_update(v);
+}
+
+void
+view_select(const char *arg) {
+ char buf[256];
+
+ utflcpy(buf, arg, sizeof buf);
+ trim(buf, " \t+/");
+
+ if(buf[0] == '\0')
+ return;
+ if(!strcmp(buf, ".") || !strcmp(buf, ".."))
+ return;
+
+ _view_select(view_create(buf));
+ view_update_all(); /* performs view_focus */
+}
+
+void
+view_attach(View *v, Frame *f) {
+ Client *c;
+ Frame *ff;
+ Area *a, *oldsel;
+
+ c = f->client;
+
+ oldsel = v->oldsel;
+ a = v->sel;
+ if(client_floats_p(c)) {
+ if(v->sel != v->floating && c->fullscreen < 0)
+ oldsel = v->sel;
+ a = v->floating;
+ }
+ else if((ff = client_groupframe(c, v)))
+ a = ff->area;
+ else if(v->sel->floating) {
+ if(v->oldsel)
+ a = v->oldsel;
+ /* Don't float a frame when starting or when its
+ * last focused frame didn't float. Important when
+ * tagging with +foo.
+ */
+ else if(starting
+ || c->sel && c->sel->area && !c->sel->area->floating)
+ a = v->firstarea;
+ }
+ if(!a->floating && view_fullscreen_p(v, a->screen))
+ a = v->floating;
+
+ area_attach(a, f);
+ /* TODO: Decide whether to focus this frame */
+ bool newgroup = !c->group
+ || c->group->ref == 1
+ || view_selclient(v)
+ && view_selclient(v)->group == c->group
+ || group_leader(c->group)
+ && !client_viewframe(group_leader(c->group),
+ c->sel->view);
+ USED(newgroup);
+
+ if(!(c->w.ewmh.type & (TypeSplash|TypeDock))) {
+ if(!(c->tagre.regex && regexec(c->tagre.regc, v->name, nil, 0)))
+ frame_focus(f);
+ else if(c->group && f->area->sel->client->group == c->group)
+ /* XXX: Stack. */
+ area_setsel(f->area, f);
+ }
+
+ if(oldsel)
+ v->oldsel = oldsel;
+
+ if(c->sel == nil)
+ c->sel = f;
+ view_update(v);
+}
+
+void
+view_detach(Frame *f) {
+ Client *c;
+ View *v;
+
+ v = f->view;
+ c = f->client;
+
+ area_detach(f);
+ if(c->sel == f)
+ c->sel = f->cnext;
+
+ if(v == selview)
+ view_update(v);
+ else if(empty_p(v))
+ view_destroy(v);
+}
+
+char**
+view_names(void) {
+ Vector_ptr vec;
+ View *v;
+
+ vector_pinit(&vec);
+ for(v=view; v; v=v->next)
+ vector_ppush(&vec, v->name);
+ vector_ppush(&vec, nil);
+ return erealloc(vec.ary, vec.n * sizeof *vec.ary);
+}
+
+void
+view_restack(View *v) {
+ static Vector_long wins;
+ Divide *d;
+ Frame *f;
+ Area *a;
+ int s;
+
+ if(v != selview)
+ return;
+
+ wins.n = 0;
+
+ /* *sigh */
+ for(f=v->floating->stack; f; f=f->snext)
+ if(f->client->w.ewmh.type & TypeDock)
+ vector_lpush(&wins, f->client->framewin->xid);
+ else
+ break;
+
+ for(; f; f=f->snext)
+ vector_lpush(&wins, f->client->framewin->xid);
+
+ for(int s=0; s < nscreens; s++)
+ vector_lpush(&wins, screens[s]->barwin->xid);
+
+ for(d = divs; d && d->w->mapped; d = d->next)
+ vector_lpush(&wins, d->w->xid);
+
+ foreach_column(v, s, a)
+ if(a->frame) {
+ vector_lpush(&wins, a->sel->client->framewin->xid);
+ for(f=a->frame; f; f=f->anext)
+ if(f != a->sel)
+ vector_lpush(&wins, f->client->framewin->xid);
+ }
+
+ ewmh_updatestacking();
+ if(wins.n)
+ XRestackWindows(display, (ulong*)wins.ary, wins.n);
+}
+
+void
+view_scale(View *v, int scrn, int width) {
+ uint xoff, numcol;
+ uint minwidth;
+ Area *a;
+ float scale;
+ int dx, minx;
+
+ minwidth = column_minwidth();
+ minx = v->r[scrn].min.x + v->pad[scrn].min.x;
+
+ if(!v->areas[scrn])
+ return;
+
+ numcol = 0;
+ dx = 0;
+ for(a=v->areas[scrn]; a; a=a->next) {
+ numcol++;
+ dx += Dx(a->r);
+ }
+
+ scale = (float)width / dx;
+ xoff = minx;
+ for(a=v->areas[scrn]; a; a=a->next) {
+ a->r.max.x = xoff + Dx(a->r) * scale;
+ a->r.min.x = xoff;
+ if(!a->next)
+ a->r.max.x = v->r[scrn].min.x + width;
+ xoff = a->r.max.x;
+ }
+
+ if(numcol * minwidth > width)
+ return;
+
+ xoff = minx;
+ for(a=v->areas[scrn]; a; a=a->next) {
+ a->r.min.x = xoff;
+
+ if(Dx(a->r) < minwidth)
+ a->r.max.x = xoff + minwidth;
+ if(!a->next)
+ a->r.max.x = minx + width;
+ xoff = a->r.max.x;
+ }
+}
+
+/* XXX: Multihead. */
+void
+view_arrange(View *v) {
+ Area *a;
+ int s;
+
+ if(!v->firstarea)
+ return;
+
+ view_update_rect(v);
+ for(s=0; s < nscreens; s++)
+ view_scale(v, s, Dx(v->r[s]) + Dx(v->pad[s]));
+ foreach_area(v, s, a) {
+ if(a->floating)
+ continue;
+ /* This is wrong... */
+ a->r.min.y = v->r[s].min.y;
+ a->r.max.y = v->r[s].max.y;
+ column_arrange(a, false);
+ }
+ if(v == selview)
+ div_update_all();
+}
+
+Rectangle*
+view_rects(View *v, uint *num, Frame *ignore) {
+ Vector_rect result;
+ Frame *f;
+ int i;
+
+ vector_rinit(&result);
+
+ for(f=v->floating->frame; f; f=f->anext)
+ if(f != ignore)
+ vector_rpush(&result, f->r);
+ for(i=0; i < nscreens; i++) {
+ vector_rpush(&result, v->r[i]);
+ vector_rpush(&result, screens[i]->r);
+ }
+
+ *num = result.n;
+ return result.ary;
+}
+
+void
+view_update_all(void) {
+ View *n, *v, *old;
+
+ old = selview;
+ for(v=view; v; v=v->next)
+ frames_update_sel(v);
+
+ for(v=view; v; v=n) {
+ n=v->next;
+ if(v != old && empty_p(v))
+ view_destroy(v);
+ }
+
+ view_update(selview);
+}
+
+uint
+view_newcolwidth(View *v, int num) {
+ Rule *r;
+ char *toks[16];
+ char buf[sizeof r->value];
+ ulong n;
+
+ for(r=def.colrules.rule; r; r=r->next)
+ if(regexec(r->regex, v->name, nil, 0)) {
+ utflcpy(buf, r->value, sizeof buf);
+ n = tokenize(toks, 16, buf, '+');
+ if(num < n)
+ if(getulong(toks[num], &n))
+ return Dx(v->screenr) * (n / 100.0); /* XXX: Multihead. */
+ break;
+ }
+ return 0;
+}
+
+char*
+view_index(View *v) {
+ Rectangle *r;
+ Frame *f;
+ Area *a;
+ int s;
+
+ bufclear();
+ foreach_area(v, s, a) {
+ if(a->floating)
+ bufprint("# %a %d %d\n", a, Dx(a->r), Dy(a->r));
+ else
+ bufprint("# %a %d %d\n", a, a->r.min.x, Dx(a->r));
+
+ for(f=a->frame; f; f=f->anext) {
+ r = &f->r;
+ if(a->floating)
+ bufprint("%a %C %d %d %d %d %s\n",
+ a, f->client,
+ r->min.x, r->min.y,
+ Dx(*r), Dy(*r),
+ f->client->props);
+ else
+ bufprint("%a %C %d %d %s\n",
+ a, f->client,
+ r->min.y, Dy(*r),
+ f->client->props);
+ }
+ }
+ return buffer;
+}
+
diff --git a/cmd/wmii/x11.c b/cmd/wmii/x11.c
new file mode 100644
index 0000000..dafe85c
--- /dev/null
+++ b/cmd/wmii/x11.c
@@ -0,0 +1,1317 @@
+/* Copyright ©2007-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#define _X11_VISIBLE
+#define pointerwin __pointerwin
+#include "dat.h"
+#include <limits.h>
+#include <math.h>
+#include <strings.h>
+#include <unistd.h>
+#include <bio.h>
+#include "fns.h"
+#undef pointerwin
+
+const Point ZP = {0, 0};
+const Rectangle ZR = {{0, 0}, {0, 0}};
+
+const Window _pointerwin = { .xid = PointerRoot };
+Window* const pointerwin = (Window*)&_pointerwin;
+
+static Map windowmap;
+static Map atommap;
+static MapEnt* wbucket[137];
+static MapEnt* abucket[137];
+
+static int errorhandler(Display*, XErrorEvent*);
+static int (*xlib_errorhandler) (Display*, XErrorEvent*);
+
+static XftColor* xftcolor(Color);
+
+
+/* Rectangles/Points */
+XRectangle
+XRect(Rectangle r) {
+ XRectangle xr;
+
+ xr.x = r.min.x;
+ xr.y = r.min.y;
+ xr.width = Dx(r);
+ xr.height = Dy(r);
+ return xr;
+}
+
+int
+eqrect(Rectangle a, Rectangle b) {
+ return a.min.x==b.min.x && a.max.x==b.max.x
+ && a.min.y==b.min.y && a.max.y==b.max.y;
+}
+
+int
+eqpt(Point p, Point q) {
+ return p.x==q.x && p.y==q.y;
+}
+
+Point
+addpt(Point p, Point q) {
+ p.x += q.x;
+ p.y += q.y;
+ return p;
+}
+
+Point
+subpt(Point p, Point q) {
+ p.x -= q.x;
+ p.y -= q.y;
+ return p;
+}
+
+Point
+mulpt(Point p, Point q) {
+ p.x *= q.x;
+ p.y *= q.y;
+ return p;
+}
+
+Point
+divpt(Point p, Point q) {
+ p.x /= q.x;
+ p.y /= q.y;
+ return p;
+}
+
+Rectangle
+insetrect(Rectangle r, int n) {
+ r.min.x += n;
+ r.min.y += n;
+ r.max.x -= n;
+ r.max.y -= n;
+ return r;
+}
+
+Rectangle
+rectaddpt(Rectangle r, Point p) {
+ r.min.x += p.x;
+ r.max.x += p.x;
+ r.min.y += p.y;
+ r.max.y += p.y;
+ return r;
+}
+
+Rectangle
+rectsubpt(Rectangle r, Point p) {
+ r.min.x -= p.x;
+ r.max.x -= p.x;
+ r.min.y -= p.y;
+ r.max.y -= p.y;
+ return r;
+}
+
+Rectangle
+rectsetorigin(Rectangle r, Point p) {
+ Rectangle ret;
+
+ ret.min.x = p.x;
+ ret.min.y = p.y;
+ ret.max.x = p.x + Dx(r);
+ ret.max.y = p.y + Dy(r);
+ return ret;
+}
+
+/* Formatters */
+static int
+Afmt(Fmt *f) {
+ Atom a;
+ char *s;
+ int i;
+
+ a = va_arg(f->args, Atom);
+ s = XGetAtomName(display, a);
+ i = fmtprint(f, "%s", s);
+ free(s);
+ return i;
+}
+
+static int
+Rfmt(Fmt *f) {
+ Rectangle r;
+
+ r = va_arg(f->args, Rectangle);
+ return fmtprint(f, "%P+%dx%d", r.min, Dx(r), Dy(r));
+}
+
+static int
+Pfmt(Fmt *f) {
+ Point p;
+
+ p = va_arg(f->args, Point);
+ return fmtprint(f, "(%d,%d)", p.x, p.y);
+}
+
+static int
+Wfmt(Fmt *f) {
+ Window *w;
+
+ w = va_arg(f->args, Window*);
+ return fmtprint(f, "0x%ulx", w->xid);
+}
+
+/* Init */
+void
+initdisplay(void) {
+ display = XOpenDisplay(nil);
+ if(display == nil)
+ fatal("Can't open display");
+ scr.screen = DefaultScreen(display);
+ scr.colormap = DefaultColormap(display, scr.screen);
+ scr.visual = DefaultVisual(display, scr.screen);
+ scr.visual32 = DefaultVisual(display, scr.screen);
+ scr.gc = DefaultGC(display, scr.screen);
+ scr.depth = DefaultDepth(display, scr.screen);
+
+ scr.white = WhitePixel(display, scr.screen);
+ scr.black = BlackPixel(display, scr.screen);
+
+ scr.root.xid = RootWindow(display, scr.screen);
+ scr.root.r = Rect(0, 0,
+ DisplayWidth(display, scr.screen),
+ DisplayHeight(display, scr.screen));
+ scr.rect = scr.root.r;
+
+ scr.root.parent = &scr.root;
+
+ windowmap.bucket = wbucket;
+ windowmap.nhash = nelem(wbucket);
+ atommap.bucket = abucket;
+ atommap.nhash = nelem(abucket);
+
+ fmtinstall('A', Afmt);
+ fmtinstall('R', Rfmt);
+ fmtinstall('P', Pfmt);
+ fmtinstall('W', Wfmt);
+
+ xlib_errorhandler = XSetErrorHandler(errorhandler);
+}
+
+/* Error handling */
+
+extern ErrorCode ignored_xerrors[];
+static bool _trap_errors;
+static long nerrors;
+
+static int
+errorhandler(Display *dpy, XErrorEvent *error) {
+ ErrorCode *e;
+
+ USED(dpy);
+
+ if(_trap_errors)
+ nerrors++;
+
+ e = ignored_xerrors;
+ if(e)
+ for(; e->rcode || e->ecode; e++)
+ if((e->rcode == 0 || e->rcode == error->request_code)
+ && (e->ecode == 0 || e->ecode == error->error_code))
+ return 0;
+
+ fprint(2, "%s: fatal error: Xrequest code=%d, Xerror code=%d\n",
+ argv0, error->request_code, error->error_code);
+ return xlib_errorhandler(display, error); /* calls exit() */
+}
+
+int
+traperrors(bool enable) {
+
+ sync();
+ _trap_errors = enable;
+ if (enable)
+ nerrors = 0;
+ return nerrors;
+
+}
+
+/* Images */
+Image*
+allocimage(int w, int h, int depth) {
+ Image *img;
+
+ img = emallocz(sizeof *img);
+ img->type = WImage;
+ img->xid = XCreatePixmap(display, scr.root.xid, w, h, depth);
+ img->gc = XCreateGC(display, img->xid, 0, nil);
+ img->colormap = scr.colormap;
+ img->visual = scr.visual;
+ if(depth == 32)
+ img->visual = scr.visual32;
+ img->depth = depth;
+ img->r = Rect(0, 0, w, h);
+ return img;
+}
+
+void
+freeimage(Image *img) {
+ if(img == nil)
+ return;
+
+ assert(img->type == WImage);
+
+ if(img->xft)
+ XftDrawDestroy(img->xft);
+ XFreePixmap(display, img->xid);
+ XFreeGC(display, img->gc);
+ free(img);
+}
+
+static XftDraw*
+xftdrawable(Image *img) {
+ if(img->xft == nil)
+ img->xft = XftDrawCreate(display, img->xid, img->visual, img->colormap);
+ return img->xft;
+}
+
+/* Windows */
+Window*
+createwindow_visual(Window *parent, Rectangle r,
+ int depth, Visual *vis, uint class,
+ WinAttr *wa, int valmask) {
+ Window *w;
+
+ assert(parent->type == WWindow);
+
+ w = emallocz(sizeof *w);
+ w->visual = vis;
+ w->type = WWindow;
+ w->parent = parent;
+ if(valmask & CWColormap)
+ w->colormap = wa->colormap;
+
+ w->xid = XCreateWindow(display, parent->xid, r.min.x, r.min.y, Dx(r), Dy(r),
+ 0 /* border */, depth, class, vis, valmask, wa);
+#if 0
+ print("createwindow_visual(%W, %R, %d, %p, %ud, %p, %x) = %W\n",
+ parent, r, depth, vis, class, wa, valmask, w);
+#endif
+ if(class != InputOnly)
+ w->gc = XCreateGC(display, w->xid, 0, nil);
+
+ w->r = r;
+ w->depth = depth;
+ return w;
+}
+
+Window*
+createwindow(Window *parent, Rectangle r, int depth, uint class, WinAttr *wa, int valmask) {
+ return createwindow_visual(parent, r, depth, scr.visual, class, wa, valmask);
+}
+
+Window*
+window(XWindow xw) {
+ Window *w;
+
+ w = malloc(sizeof *w);
+ w->type = WWindow;
+ w->xid = xw;
+ return freelater(w);
+}
+
+void
+reparentwindow(Window *w, Window *par, Point p) {
+ assert(w->type == WWindow);
+ XReparentWindow(display, w->xid, par->xid, p.x, p.y);
+ w->parent = par;
+ w->r = rectsubpt(w->r, w->r.min);
+ w->r = rectaddpt(w->r, p);
+}
+
+void
+destroywindow(Window *w) {
+ assert(w->type == WWindow);
+ sethandler(w, nil);
+ if(w->xft)
+ XftDrawDestroy(w->xft);
+ if(w->gc)
+ XFreeGC(display, w->gc);
+ XDestroyWindow(display, w->xid);
+ free(w);
+}
+
+void
+setwinattr(Window *w, WinAttr *wa, int valmask) {
+ assert(w->type == WWindow);
+ XChangeWindowAttributes(display, w->xid, valmask, wa);
+}
+
+void
+selectinput(Window *w, long mask) {
+ XSelectInput(display, w->xid, mask);
+}
+
+static void
+configwin(Window *w, Rectangle r, int border) {
+ XWindowChanges wc;
+
+ if(eqrect(r, w->r) && border == w->border)
+ return;
+
+ wc.x = r.min.x - border;
+ wc.y = r.min.y - border;
+ wc.width = Dx(r);
+ wc.height = Dy(r);
+ wc.border_width = border;
+ XConfigureWindow(display, w->xid, CWX|CWY|CWWidth|CWHeight|CWBorderWidth, &wc);
+
+ w->r = r;
+ w->border = border;
+}
+
+void
+setborder(Window *w, int width, Color col) {
+
+ assert(w->type == WWindow);
+ if(width)
+ XSetWindowBorder(display, w->xid, col.pixel);
+ if(width != w->border)
+ configwin(w, w->r, width);
+}
+
+void
+reshapewin(Window *w, Rectangle r) {
+ assert(w->type == WWindow);
+ assert(Dx(r) > 0 && Dy(r) > 0); /* Rather than an X error. */
+
+ configwin(w, r, w->border);
+}
+
+void
+movewin(Window *w, Point pt) {
+ Rectangle r;
+
+ assert(w->type == WWindow);
+ r = rectsetorigin(w->r, pt);
+ reshapewin(w, r);
+}
+
+int
+mapwin(Window *w) {
+ assert(w->type == WWindow);
+ if(!w->mapped) {
+ XMapWindow(display, w->xid);
+ w->mapped = 1;
+ return 1;
+ }
+ return 0;
+}
+
+int
+unmapwin(Window *w) {
+ assert(w->type == WWindow);
+ if(w->mapped) {
+ XUnmapWindow(display, w->xid);
+ w->mapped = 0;
+ w->unmapped++;
+ return 1;
+ }
+ return 0;
+}
+
+void
+raisewin(Window *w) {
+ assert(w->type == WWindow);
+ XRaiseWindow(display, w->xid);
+}
+
+void
+lowerwin(Window *w) {
+ assert(w->type == WWindow);
+ XLowerWindow(display, w->xid);
+}
+
+Handlers*
+sethandler(Window *w, Handlers *new) {
+ Handlers *old;
+ void **e;
+
+ assert(w->type == WWindow);
+ assert((w->prev != nil && w->next != nil) || w->next == w->prev);
+
+ if(new == nil)
+ map_rm(&windowmap, (ulong)w->xid);
+ else {
+ e = map_get(&windowmap, (ulong)w->xid, true);
+ *e = w;
+ }
+ old = w->handler;
+ w->handler = new;
+ return old;
+}
+
+Window*
+findwin(XWindow w) {
+ void **e;
+
+ e = map_get(&windowmap, (ulong)w, false);
+ if(e)
+ return *e;
+ return nil;
+}
+
+/* Shape */
+void
+setshapemask(Window *dst, Image *src, Point pt) {
+ /* Assumes that we have the shape extension... */
+ XShapeCombineMask (display, dst->xid,
+ ShapeBounding, pt.x, pt.y, src->xid, ShapeSet);
+}
+
+static void
+setgccol(Image *dst, Color col) {
+ XSetForeground(display, dst->gc, col.pixel);
+}
+
+/* Drawing */
+void
+border(Image *dst, Rectangle r, int w, Color col) {
+ if(w == 0)
+ return;
+
+ r = insetrect(r, w/2);
+ r.max.x -= w%2;
+ r.max.y -= w%2;
+
+ XSetLineAttributes(display, dst->gc, w, LineSolid, CapButt, JoinMiter);
+ setgccol(dst, col);
+ XDrawRectangle(display, dst->xid, dst->gc,
+ r.min.x, r.min.y, Dx(r), Dy(r));
+}
+
+void
+fill(Image *dst, Rectangle r, Color col) {
+ setgccol(dst, col);
+ XFillRectangle(display, dst->xid, dst->gc,
+ r.min.x, r.min.y, Dx(r), Dy(r));
+}
+
+static XPoint*
+convpts(Point *pt, int np) {
+ XPoint *rp;
+ int i;
+
+ rp = emalloc(np * sizeof *rp);
+ for(i = 0; i < np; i++) {
+ rp[i].x = pt[i].x;
+ rp[i].y = pt[i].y;
+ }
+ return rp;
+}
+
+void
+drawpoly(Image *dst, Point *pt, int np, int cap, int w, Color col) {
+ XPoint *xp;
+
+ xp = convpts(pt, np);
+ XSetLineAttributes(display, dst->gc, w, LineSolid, cap, JoinMiter);
+ setgccol(dst, col);
+ XDrawLines(display, dst->xid, dst->gc, xp, np, CoordModeOrigin);
+ free(xp);
+}
+
+void
+fillpoly(Image *dst, Point *pt, int np, Color col) {
+ XPoint *xp;
+
+ xp = convpts(pt, np);
+ setgccol(dst, col);
+ XFillPolygon(display, dst->xid, dst->gc, xp, np, Complex, CoordModeOrigin);
+ free(xp);
+}
+
+void
+drawline(Image *dst, Point p1, Point p2, int cap, int w, Color col) {
+ XSetLineAttributes(display, dst->gc, w, LineSolid, cap, JoinMiter);
+ setgccol(dst, col);
+ XDrawLine(display, dst->xid, dst->gc, p1.x, p1.y, p2.x, p2.y);
+}
+
+uint
+drawstring(Image *dst, Font *font,
+ Rectangle r, Align align,
+ char *text, Color col) {
+ Rectangle tr;
+ char *buf;
+ uint x, y, width, height, len;
+ int shortened;
+
+ shortened = 0;
+
+ len = strlen(text);
+ buf = emalloc(len+1);
+ memcpy(buf, text, len+1);
+
+ r.max.y -= font->pad.min.y;
+ r.min.y += font->pad.max.y;
+
+ height = font->ascent + font->descent;
+ y = r.min.y + Dy(r) / 2 - height / 2 + font->ascent;
+
+ width = Dx(r) - font->pad.min.x - font->pad.max.x - (font->height & ~1);
+
+ r.min.x += font->pad.min.x;
+ r.max.x -= font->pad.max.x;
+
+ /* shorten text if necessary */
+ tr = ZR;
+ while(len > 0) {
+ tr = textextents_l(font, buf, len + min(shortened, 3), nil);
+ if(Dx(tr) <= width)
+ break;
+ while(len > 0 && (buf[--len]&0xC0) == 0x80)
+ buf[len] = '.';
+ buf[len] = '.';
+ shortened++;
+ }
+
+ if(len == 0 || Dx(tr) > width)
+ goto done;
+
+ /* mark shortened info in the string */
+ if(shortened)
+ len += min(shortened, 3);
+
+ switch (align) {
+ case East:
+ x = r.max.x - (tr.max.x + (font->height / 2));
+ break;
+ case Center:
+ x = r.min.x + (Dx(r) - Dx(tr)) / 2 - tr.min.x;
+ break;
+ default:
+ x = r.min.x + (font->height / 2) - tr.min.x;
+ break;
+ }
+
+ setgccol(dst, col);
+ switch(font->type) {
+ case FFontSet:
+ Xutf8DrawString(display, dst->xid,
+ font->font.set, dst->gc,
+ x, y,
+ buf, len);
+ break;
+ case FXft:
+ XftDrawStringUtf8(xftdrawable(dst), xftcolor(col),
+ font->font.xft,
+ x, y, (uchar*)buf, len);
+ break;
+ case FX11:
+ XSetFont(display, dst->gc, font->font.x11->fid);
+ XDrawString(display, dst->xid, dst->gc,
+ x, y, buf, len);
+ break;
+ default:
+ die("Invalid font type.");
+ }
+
+done:
+ free(buf);
+ return Dx(tr);
+}
+
+void
+copyimage(Image *dst, Rectangle r, Image *src, Point p) {
+ XCopyArea(display,
+ src->xid, dst->xid,
+ dst->gc,
+ r.min.x, r.min.y, Dx(r), Dy(r),
+ p.x, p.y);
+}
+
+/* Colors */
+bool
+namedcolor(char *name, Color *ret) {
+ XColor c, c2;
+
+ if(XAllocNamedColor(display, scr.colormap, name, &c, &c2)) {
+ *ret = (Color) {
+ c.pixel, {
+ c.red,
+ c.green,
+ c.blue,
+ 0xffff
+ },
+ };
+ return true;
+ }
+ return false;
+}
+
+bool
+loadcolor(CTuple *c, char *str) {
+ char buf[24];
+
+ utflcpy(buf, str, sizeof buf);
+ memcpy(c->colstr, str, sizeof c->colstr);
+
+ buf[7] = buf[15] = buf[23] = '\0';
+ return namedcolor(buf, &c->fg)
+ && namedcolor(buf+8, &c->bg)
+ && namedcolor(buf+16, &c->border);
+}
+
+static XftColor*
+xftcolor(Color col) {
+ XftColor *c;
+
+ c = emallocz(sizeof *c);
+ *c = (XftColor) {
+ ((col.render.alpha&0xff00) << 24)
+ | ((col.render.red&0xff00) << 8)
+ | ((col.render.green&0xff00) << 0)
+ | ((col.render.blue&0xff00) >> 8),
+ col.render
+ };
+ return freelater(c);
+}
+
+/* Fonts */
+Font*
+loadfont(char *name) {
+ XFontStruct **xfonts;
+ char **missing, **font_names;
+ Biobuf *b;
+ Font *f;
+ int n, i;
+
+ missing = nil;
+ f = emallocz(sizeof *f);
+ f->name = estrdup(name);
+ if(!strncmp(f->name, "xft:", 4)) {
+ f->type = FXft;
+
+ f->font.xft = XftFontOpenXlfd(display, scr.screen, f->name + 4);
+ if(!f->font.xft)
+ f->font.xft = XftFontOpenName(display, scr.screen, f->name + 4);
+ if(!f->font.xft)
+ goto error;
+
+ f->ascent = f->font.xft->ascent;
+ f->descent = f->font.xft->descent;
+ }else {
+ f->font.set = XCreateFontSet(display, name, &missing, &n, nil);
+ if(missing) {
+ if(false) {
+ b = Bfdopen(dup(2), O_WRONLY);
+ Bprint(b, "%s: note: missing fontset%s for '%s':", argv0,
+ (n > 1 ? "s" : ""), name);
+ for(i = 0; i < n; i++)
+ Bprint(b, "%s %s", (i ? "," : ""), missing[i]);
+ Bprint(b, "\n");
+ Bterm(b);
+ }
+ freestringlist(missing);
+ }
+
+ if(f->font.set) {
+ f->type = FFontSet;
+ XFontsOfFontSet(f->font.set, &xfonts, &font_names);
+ f->ascent = xfonts[0]->ascent;
+ f->descent = xfonts[0]->descent;
+ }else {
+ f->type = FX11;
+ f->font.x11 = XLoadQueryFont(display, name);
+ if(!f->font.x11)
+ goto error;
+
+ f->ascent = f->font.x11->ascent;
+ f->descent = f->font.x11->descent;
+ }
+ }
+ f->height = f->ascent + f->descent;
+ return f;
+
+error:
+ fprint(2, "%s: cannot load font: %s\n", argv0, name);
+ f->type = 0;
+ freefont(f);
+ return nil;
+}
+
+void
+freefont(Font *f) {
+ switch(f->type) {
+ case FFontSet:
+ XFreeFontSet(display, f->font.set);
+ break;
+ case FXft:
+ XftFontClose(display, f->font.xft);
+ break;
+ case FX11:
+ XFreeFont(display, f->font.x11);
+ break;
+ default:
+ break;
+ }
+ free(f->name);
+ free(f);
+}
+
+Rectangle
+textextents_l(Font *font, char *text, uint len, int *offset) {
+ Rectangle rect;
+ XRectangle r;
+ XGlyphInfo i;
+ int unused;
+
+ if(!offset)
+ offset = &unused;
+
+ switch(font->type) {
+ case FFontSet:
+ *offset = Xutf8TextExtents(font->font.set, text, len, &r, nil);
+ return Rect(r.x, -r.y - r.height, r.x + r.width, -r.y);
+ case FXft:
+ XftTextExtentsUtf8(display, font->font.xft, (uchar*)text, len, &i);
+ *offset = i.xOff;
+ return Rect(-i.x, i.y - i.height, -i.x + i.width, i.y);
+ case FX11:
+ rect = ZR;
+ rect.max.x = XTextWidth(font->font.x11, text, len);
+ rect.max.y = font->ascent;
+ *offset = rect.max.x;
+ return rect;
+ default:
+ die("Invalid font type");
+ return ZR; /* shut up ken */
+ }
+}
+
+uint
+textwidth_l(Font *font, char *text, uint len) {
+ Rectangle r;
+
+ r = textextents_l(font, text, len, nil);
+ return Dx(r);
+}
+
+uint
+textwidth(Font *font, char *text) {
+ return textwidth_l(font, text, strlen(text));
+}
+
+uint
+labelh(Font *font) {
+ return max(font->height + font->descent + font->pad.min.y + font->pad.max.y, 1);
+}
+
+/* Misc */
+Atom
+xatom(char *name) {
+ void **e;
+
+ e = hash_get(&atommap, name, true);
+ if(*e == nil)
+ *e = (void*)XInternAtom(display, name, false);
+ return (Atom)*e;
+}
+
+void
+sendmessage(Window *w, char *name, long l0, long l1, long l2, long l3, long l4) {
+ XClientMessageEvent e;
+
+ e.type = ClientMessage;
+ e.window = w->xid;
+ e.message_type = xatom(name);
+ e.format = 32;
+ e.data.l[0] = l0;
+ e.data.l[1] = l1;
+ e.data.l[2] = l2;
+ e.data.l[3] = l3;
+ e.data.l[4] = l4;
+ sendevent(w, false, NoEventMask, (XEvent*)&e);
+}
+
+void
+sendevent(Window *w, bool propegate, long mask, XEvent *e) {
+ XSendEvent(display, w->xid, propegate, mask, e);
+}
+
+KeyCode
+keycode(char *name) {
+ return XKeysymToKeycode(display, XStringToKeysym(name));
+}
+
+typedef struct KMask KMask;
+
+static struct KMask {
+ int mask;
+ const char* name;
+} masks[] = {
+ {ShiftMask, "Shift"},
+ {ControlMask, "Control"},
+ {Mod1Mask, "Mod1"},
+ {Mod2Mask, "Mod2"},
+ {Mod3Mask, "Mod3"},
+ {Mod4Mask, "Mod4"},
+ {Mod5Mask, "Mod5"},
+ {0,}
+};
+
+bool
+parsekey(char *str, int *mask, char **key) {
+ static char *keys[16];
+ KMask *m;
+ int i, nkeys;
+
+ *mask = 0;
+ nkeys = tokenize(keys, nelem(keys), str, '-');
+ for(i=0; i < nkeys; i++) {
+ for(m=masks; m->mask; m++)
+ if(!strcasecmp(m->name, keys[i])) {
+ *mask |= m->mask;
+ goto next;
+ }
+ break;
+ next: continue;
+ }
+ if(key) {
+ if(nkeys)
+ *key = keys[i];
+ return i == nkeys - 1;
+ }
+ else
+ return i == nkeys;
+}
+
+void
+sync(void) {
+ XSync(display, false);
+}
+
+/* Properties */
+void
+delproperty(Window *w, char *prop) {
+ XDeleteProperty(display, w->xid, xatom(prop));
+}
+
+void
+changeproperty(Window *w, char *prop, char *type,
+ int width, uchar data[], int n) {
+ XChangeProperty(display, w->xid, xatom(prop), xatom(type), width,
+ PropModeReplace, data, n);
+}
+
+void
+changeprop_string(Window *w, char *prop, char *string) {
+ changeprop_char(w, prop, "UTF8_STRING", string, strlen(string));
+}
+
+void
+changeprop_char(Window *w, char *prop, char *type, char data[], int len) {
+ changeproperty(w, prop, type, 8, (uchar*)data, len);
+}
+
+void
+changeprop_short(Window *w, char *prop, char *type, short data[], int len) {
+ changeproperty(w, prop, type, 16, (uchar*)data, len);
+}
+
+void
+changeprop_long(Window *w, char *prop, char *type, long data[], int len) {
+ changeproperty(w, prop, type, 32, (uchar*)data, len);
+}
+
+void
+changeprop_ulong(Window *w, char *prop, char *type, ulong data[], int len) {
+ changeproperty(w, prop, type, 32, (uchar*)data, len);
+}
+
+void
+changeprop_textlist(Window *w, char *prop, char *type, char *data[]) {
+ char **p, *s, *t;
+ int len, n;
+
+ len = 0;
+ for(p=data; *p; p++)
+ len += strlen(*p) + 1;
+ s = emalloc(len);
+ t = s;
+ for(p=data; *p; p++) {
+ n = strlen(*p) + 1;
+ memcpy(t, *p, n);
+ t += n;
+ }
+ changeprop_char(w, prop, type, s, len);
+ free(s);
+}
+
+void
+freestringlist(char *list[]) {
+ XFreeStringList(list);
+}
+
+static ulong
+getprop(Window *w, char *prop, char *type, Atom *actual, int *format,
+ ulong offset, uchar **ret, ulong length) {
+ Atom typea;
+ ulong n, extra;
+ int status;
+
+ typea = (type ? xatom(type) : 0L);
+
+ status = XGetWindowProperty(display, w->xid,
+ xatom(prop), offset, length, false /* delete */,
+ typea, actual, format, &n, &extra, ret);
+
+ if(status != Success) {
+ *ret = nil;
+ return 0;
+ }
+ if(n == 0) {
+ free(*ret);
+ *ret = nil;
+ }
+ return n;
+}
+
+ulong
+getproperty(Window *w, char *prop, char *type, Atom *actual,
+ ulong offset, uchar **ret, ulong length) {
+ int format;
+
+ return getprop(w, prop, type, actual, &format, offset, ret, length);
+}
+
+ulong
+getprop_long(Window *w, char *prop, char *type,
+ ulong offset, long **ret, ulong length) {
+ Atom actual;
+ ulong n;
+ int format;
+
+ n = getprop(w, prop, type, &actual, &format, offset, (uchar**)ret, length);
+ if(n == 0 || format == 32 && xatom(type) == actual)
+ return n;
+ free(*ret);
+ *ret = 0;
+ return 0;
+}
+
+ulong
+getprop_ulong(Window *w, char *prop, char *type,
+ ulong offset, ulong **ret, ulong length) {
+ return getprop_long(w, prop, type, offset, (long**)ret, length);
+}
+
+char**
+strlistdup(char *list[]) {
+ char **p;
+ char *q;
+ int i, m, n;
+
+ n = 0;
+ m = 0;
+ for(p=list; *p; p++, n++)
+ m += strlen(*p) + 1;
+
+ p = malloc((n+1) * sizeof(*p) + m);
+ q = (char*)&p[n+1];
+
+ for(i=0; i < n; i++) {
+ p[i] = q;
+ m = strlen(list[i]) + 1;
+ memcpy(q, list[i], m);
+ q += m;
+ }
+ p[n] = nil;
+ return p;
+}
+
+int
+getprop_textlist(Window *w, char *name, char **ret[]) {
+ XTextProperty prop;
+ char **list;
+ int n;
+
+ *ret = nil;
+ n = 0;
+
+ XGetTextProperty(display, w->xid, &prop, xatom(name));
+ if(prop.nitems > 0) {
+ if(Xutf8TextPropertyToTextList(display, &prop, &list, &n) == Success)
+ *ret = list;
+ XFree(prop.value);
+ }
+ return n;
+}
+
+char*
+getprop_string(Window *w, char *name) {
+ char **list, *str;
+ int n;
+
+ str = nil;
+
+ n = getprop_textlist(w, name, &list);
+ if(n > 0)
+ str = estrdup(*list);
+ freestringlist(list);
+
+ return str;
+}
+
+Rectangle
+getwinrect(Window *w) {
+ XWindowAttributes wa;
+ Point p;
+
+ if(!XGetWindowAttributes(display, w->xid, &wa))
+ return ZR;
+ p = translate(w, &scr.root, ZP);
+ return rectaddpt(Rect(0, 0, wa.width, wa.height), p);
+}
+
+void
+setfocus(Window *w, int mode) {
+ XSetInputFocus(display, w->xid, mode, CurrentTime);
+}
+
+XWindow
+getfocus(void) {
+ XWindow ret;
+ int revert;
+
+ XGetInputFocus(display, &ret, &revert);
+ return ret;
+}
+
+/* Mouse */
+Point
+querypointer(Window *w) {
+ XWindow win;
+ Point pt;
+ uint ui;
+ int i;
+
+ XQueryPointer(display, w->xid, &win, &win, &i, &i, &pt.x, &pt.y, &ui);
+ return pt;
+}
+
+int
+pointerscreen(void) {
+ XWindow win;
+ Point pt;
+ uint ui;
+ int i;
+
+ return XQueryPointer(display, scr.root.xid, &win, &win, &i, &i,
+ &pt.x, &pt.y, &ui);
+}
+
+void
+warppointer(Point pt) {
+ /* Nasty kludge for xephyr, xnest. */
+ static int havereal = -1;
+ static char* real;
+
+ if(havereal == -1) {
+ real = getenv("REALDISPLAY");
+ havereal = real != nil;
+ }
+ if(havereal)
+ system(sxprint("DISPLAY=%s wiwarp %d %d", real, pt.x, pt.y));
+
+ XWarpPointer(display,
+ /* src, dest w */ None, scr.root.xid,
+ /* src_rect */ 0, 0, 0, 0,
+ /* target */ pt.x, pt.y);
+}
+
+Point
+translate(Window *src, Window *dst, Point sp) {
+ Point pt;
+ XWindow w;
+
+ XTranslateCoordinates(display, src->xid, dst->xid, sp.x, sp.y,
+ &pt.x, &pt.y, &w);
+ return pt;
+}
+
+int
+grabpointer(Window *w, Window *confine, Cursor cur, int mask) {
+ XWindow cw;
+
+ cw = None;
+ if(confine)
+ cw = confine->xid;
+ return XGrabPointer(display, w->xid, false /* owner events */, mask,
+ GrabModeAsync, GrabModeAsync, cw, cur, CurrentTime
+ ) == GrabSuccess;
+}
+
+void
+ungrabpointer(void) {
+ XUngrabPointer(display, CurrentTime);
+}
+
+int
+grabkeyboard(Window *w) {
+
+ return XGrabKeyboard(display, w->xid, true /* owner events */,
+ GrabModeAsync, GrabModeAsync, CurrentTime
+ ) == GrabSuccess;
+}
+
+void
+ungrabkeyboard(void) {
+ XUngrabKeyboard(display, CurrentTime);
+}
+
+/* Insanity */
+void
+sethints(Window *w) {
+ XSizeHints xs;
+ XWMHints *wmh;
+ WinHints *h;
+ Point p;
+ long size;
+
+ if(w->hints == nil)
+ w->hints = emalloc(sizeof *h);
+
+ h = w->hints;
+ memset(h, 0, sizeof *h);
+
+ h->max = Pt(INT_MAX, INT_MAX);
+ h->inc = Pt(1,1);
+
+ wmh = XGetWMHints(display, w->xid);
+ if(wmh) {
+ if(wmh->flags & WindowGroupHint)
+ h->group = wmh->window_group;
+ free(wmh);
+ }
+
+ if(!XGetWMNormalHints(display, w->xid, &xs, &size))
+ return;
+
+ if(xs.flags & PMinSize) {
+ h->min.x = xs.min_width;
+ h->min.y = xs.min_height;
+ }
+ if(xs.flags & PMaxSize) {
+ h->max.x = xs.max_width;
+ h->max.y = xs.max_height;
+ }
+
+ /* Goddamn buggy clients. */
+ if(h->max.x < h->min.x)
+ h->max.x = h->min.x;
+ if(h->max.y < h->min.y)
+ h->max.y = h->min.y;
+
+ h->base = h->min;
+ if(xs.flags & PBaseSize) {
+ p.x = xs.base_width;
+ p.y = xs.base_height;
+ h->base = p;
+ h->baspect = p;
+ }
+
+ if(xs.flags & PResizeInc) {
+ h->inc.x = max(xs.width_inc, 1);
+ h->inc.y = max(xs.height_inc, 1);
+ }
+
+ if(xs.flags & PAspect) {
+ h->aspect.min.x = xs.min_aspect.x;
+ h->aspect.min.y = xs.min_aspect.y;
+ h->aspect.max.x = xs.max_aspect.x;
+ h->aspect.max.y = xs.max_aspect.y;
+ }
+
+ h->position = (xs.flags & (USPosition|PPosition)) != 0;
+
+ if(!(xs.flags & PWinGravity))
+ xs.win_gravity = NorthWestGravity;
+ p = ZP;
+ switch (xs.win_gravity) {
+ case EastGravity:
+ case CenterGravity:
+ case WestGravity:
+ p.y = 1;
+ break;
+ case SouthEastGravity:
+ case SouthGravity:
+ case SouthWestGravity:
+ p.y = 2;
+ break;
+ }
+ switch (xs.win_gravity) {
+ case NorthGravity:
+ case CenterGravity:
+ case SouthGravity:
+ p.x = 1;
+ break;
+ case NorthEastGravity:
+ case EastGravity:
+ case SouthEastGravity:
+ p.x = 2;
+ break;
+ }
+ h->grav = p;
+ h->gravstatic = (xs.win_gravity == StaticGravity);
+}
+
+Rectangle
+sizehint(WinHints *h, Rectangle r) {
+ Point p, aspect, origin;
+
+ if(h == nil)
+ return r;
+
+ origin = r.min;
+ r = rectsubpt(r, origin);
+
+ /* Min/max */
+ r.max.x = max(r.max.x, h->min.x);
+ r.max.y = max(r.max.y, h->min.y);
+ r.max.x = min(r.max.x, h->max.x);
+ r.max.y = min(r.max.y, h->max.y);
+
+ /* Increment */
+ p = subpt(r.max, h->base);
+ r.max.x -= p.x % h->inc.x;
+ r.max.y -= p.y % h->inc.y;
+
+ /* Aspect */
+ p = subpt(r.max, h->baspect);
+ p.y = max(p.y, 1);
+
+ aspect = h->aspect.min;
+ if(p.x * aspect.y / p.y < aspect.x)
+ r.max.y = h->baspect.y
+ + p.x * aspect.y / aspect.x;
+
+ aspect = h->aspect.max;
+ if(p.x * aspect.y / p.y > aspect.x)
+ r.max.x = h->baspect.x
+ + p.y * aspect.x / aspect.y;
+
+ return rectaddpt(r, origin);
+}
+
+Rectangle
+gravitate(Rectangle rc, Rectangle rf, Point grav) {
+ Point d;
+
+ /* Get delta between frame and client rectangles */
+ d = subpt(subpt(rf.max, rf.min),
+ subpt(rc.max, rc.min));
+
+ /* Divide by 2 and apply gravity */
+ d = divpt(d, Pt(2, 2));
+ d = mulpt(d, grav);
+
+ return rectsubpt(rc, d);
+}
+
diff --git a/cmd/wmii/xdnd.c b/cmd/wmii/xdnd.c
new file mode 100644
index 0000000..abb3612
--- /dev/null
+++ b/cmd/wmii/xdnd.c
@@ -0,0 +1,88 @@
+/* Copyright ©2008-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#include "dat.h"
+#include "fns.h"
+
+void
+xdnd_initwindow(Window *w) {
+ long l;
+
+ l = 3; /* They are insane. Why is this an ATOM?! */
+ changeprop_long(w, "XdndAware", "ATOM", &l, 1);
+}
+
+typedef struct Dnd Dnd;
+struct Dnd {
+ XWindow source;
+ Rectangle r;
+};
+
+int
+xdnd_clientmessage(XClientMessageEvent *e) {
+ Window *w;
+ Dnd *dnd;
+ long *l;
+ Rectangle r;
+ Point p;
+ long pos, siz;
+ ulong msg;
+
+ dnd = nil;
+ msg = e->message_type;
+ l = e->data.l;
+ Dprint(DDnd, "ClientMessage: %A\n", msg);
+
+ if(msg == xatom("XdndEnter")) {
+ if(e->format != 32)
+ return -1;
+ w = findwin(e->window);
+ if(w) {
+ if(w->dnd == nil)
+ w->dnd = emallocz(sizeof *dnd);
+ dnd = w->dnd;
+ dnd->source = l[0];
+ dnd->r = ZR;
+ }
+ return 1;
+ }else
+ if(msg == xatom("XdndLeave")) {
+ if(e->format != 32)
+ return -1;
+ w = findwin(e->window);
+ if(w && w->dnd) {
+ free(w->dnd);
+ w->dnd = nil;
+ }
+ return 1;
+ }else
+ if(msg == xatom("XdndPosition")) {
+ if(e->format != 32)
+ return -1;
+ r = ZR;
+ w = findwin(e->window);
+ if(w)
+ dnd = w->dnd;
+ if(dnd) {
+ p.x = (ulong)l[2] >> 16;
+ p.y = (ulong)l[2] & 0xffff;
+ p = subpt(p, w->r.min);
+ Dprint(DDnd, "\tw: %W\n", w);
+ Dprint(DDnd, "\tp: %P\n", p);
+ if(eqrect(dnd->r, ZR) || !rect_haspoint_p(p, dnd->r))
+ if(w->handler->dndmotion)
+ dnd->r = w->handler->dndmotion(w, p);
+ r = dnd->r;
+ if(!eqrect(r, ZR))
+ r = rectaddpt(r, w->r.min);
+ Dprint(DDnd, "\tr: %R\n", r);
+ }
+ pos = (r.min.x<<16) | r.min.y;
+ siz = (Dx(r)<<16) | Dy(r);
+ sendmessage(window(l[0]), "XdndStatus", e->window, 0, pos, siz, 0);
+ return 1;
+ }
+
+ return 0;
+}
+
diff --git a/cmd/wmii/xext.c b/cmd/wmii/xext.c
new file mode 100644
index 0000000..ba851e5
--- /dev/null
+++ b/cmd/wmii/xext.c
@@ -0,0 +1,160 @@
+/* Copyright ©2008-2010 Kris Maglione <maglione.k at Gmail>
+ * See LICENSE file for license details.
+ */
+#define _X11_VISIBLE
+#include "dat.h"
+#include <X11/extensions/Xrender.h>
+#include <X11/extensions/Xinerama.h>
+#include "fns.h"
+
+#if RANDR_MAJOR < 1
+# error XRandR versions less than 1.0 are not supported
+#endif
+
+static void randr_screenchange(XRRScreenChangeNotifyEvent*);
+static bool randr_event_p(XEvent *e);
+static void randr_init(void);
+static void render_init(void);
+static void xinerama_init(void);
+
+typedef void (*EvHandler)(XEvent*);
+static EvHandler randr_handlers[RRNumberEvents];
+
+bool have_RandR;
+bool have_render;
+bool have_xinerama;
+int randr_eventbase;
+
+static void
+handle(XEvent *e, EvHandler h[], int base) {
+
+ if(h[e->type-base])
+ h[e->type-base](e);
+}
+
+void
+xext_init(void) {
+ randr_init();
+ render_init();
+ xinerama_init();
+}
+
+void
+xext_event(XEvent *e) {
+
+ if(randr_event_p(e))
+ handle(e, randr_handlers, randr_eventbase);
+}
+
+static void
+randr_init(void) {
+ int errorbase, major, minor;
+
+ have_RandR = XRRQueryExtension(display, &randr_eventbase, &errorbase);
+ if(have_RandR)
+ if(XRRQueryVersion(display, &major, &minor) && major < 1)
+ have_RandR = false;
+ if(have_RandR)
+ XRRSelectInput(display, scr.root.xid, RRScreenChangeNotifyMask);
+}
+
+static bool
+randr_event_p(XEvent *e) {
+ return have_RandR
+ && (uint)e->type - randr_eventbase < RRNumberEvents;
+}
+
+static void
+randr_screenchange(XRRScreenChangeNotifyEvent *ev) {
+
+ XRRUpdateConfiguration((XEvent*)ev);
+ if(ev->rotation*90 % 180)
+ scr.rect = Rect(0, 0, ev->width, ev->height);
+ else
+ scr.rect = Rect(0, 0, ev->height, ev->width);
+ init_screens();
+}
+
+static EvHandler randr_handlers[] = {
+ [RRScreenChangeNotify] = (EvHandler)randr_screenchange,
+};
+
+/* Ripped most graciously from ecore_x. XRender documentation
+ * is sparse.
+ */
+static void
+render_init(void) {
+ XVisualInfo *vip;
+ XVisualInfo vi;
+ int base, i, n;
+
+ have_render = XRenderQueryExtension(display, &base, &base);
+ if(!have_render)
+ return;
+
+ vi.class = TrueColor;
+ vi.depth = 32;
+ vi.screen = scr.screen;
+ vip = XGetVisualInfo(display, VisualClassMask
+ | VisualDepthMask
+ | VisualScreenMask,
+ &vi, &n);
+ for(i=0; i < n; i++)
+ if(render_argb_p(vip[i].visual)) {
+ render_visual = vip[i].visual;
+ scr.visual32 = render_visual;
+ break;
+ }
+ XFree(vip);
+}
+
+bool
+render_argb_p(Visual *v) {
+ XRenderPictFormat *f;
+
+ if(!have_render)
+ return false;
+ f = XRenderFindVisualFormat(display, v);
+ return f
+ && f->type == PictTypeDirect
+ && f->direct.alphaMask;
+}
+
+static void
+xinerama_init(void) {
+ int base;
+
+ have_xinerama = XineramaQueryExtension(display, &base, &base);
+}
+
+static bool
+xinerama_active(void) {
+ return have_xinerama && XineramaIsActive(display);
+}
+
+Rectangle*
+xinerama_screens(int *np) {
+ static Rectangle *rects;
+ XineramaScreenInfo *res;
+ int i, n;
+
+ if(!xinerama_active()) {
+ *np = 1;
+ return &scr.rect;
+ }
+
+ free(rects);
+ res = XineramaQueryScreens(display, &n);
+ rects = emalloc(n * sizeof *rects);
+ for(i=0; i < n; i++) {
+ rects[i].min.x = res[i].x_org;
+ rects[i].min.y = res[i].y_org;
+ rects[i].max.x = res[i].x_org + res[i].width;
+ rects[i].max.y = res[i].y_org + res[i].height;
+ }
+ XFree(res);
+
+ *np = n;
+ return rects;
+}
+
diff --git a/cmd/wmii9menu.c b/cmd/wmii9menu.c
new file mode 100644
index 0000000..02049f3
--- /dev/null
+++ b/cmd/wmii9menu.c
@@ -0,0 +1,341 @@
+/* Licence
+ * =======
+ *
+ * 9menu is free software, and is Copyright (c) 1994 by David Hogan and
+ * Arnold Robbins. Permission is granted to all sentient beings to use
+ * this software, to make copies of it, and to distribute those copies,
+ * provided that:
+ *
+ * (1) the copyright and licence notices are left intact
+ * (2) the recipients are aware that it is free software
+ * (3) any unapproved changes in functionality are either
+ * (i) only distributed as patches
+ * or (ii) distributed as a new program which is not called 9menu
+ * and whose documentation gives credit where it is due
+ * (4) the authors are not held responsible for any defects
+ * or shortcomings in the software, or damages caused by it.
+ *
+ * There is no warranty for this software. Have a nice day.
+ *
+ * --
+ * Arnold Robbins
+ * arnold@skeeve.com
+ *
+ * 9menu.c
+ *
+ * This program puts up a window that is just a menu, and executes
+ * commands that correspond to the items selected.
+ *
+ * Initial idea: Arnold Robbins
+ * Version using libXg: Matty Farrow (some ideas borrowed)
+ * This code by: David Hogan and Arnold Robbins
+ */
+
+/*
+ * Heavily modified by Kris Maglione for use with wmii.
+ */
+
+#define IXP_NO_P9_
+#define IXP_P9_STRUCTS
+#include <fmt.h>
+#include <ixp.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <clientutil.h>
+#include <util.h>
+#include <x11.h>
+
+char version[] = "wmii9menu-" VERSION " ©2010 Kris Maglione, ©1994 David Hogan, Arnold Robbins";
+
+static Window* menuwin;
+
+static CTuple cnorm;
+static CTuple csel;
+static Font* font;
+
+static int wborder;
+
+char buffer[8092];
+char* _buffer;
+
+/* for XSetWMProperties to use */
+int g_argc;
+char **g_argv;
+
+char *initial = "";
+int cur;
+
+static char** labels; /* list of labels and commands */
+static char** commands;
+static int numitems;
+
+void usage(void);
+void run_menu(void);
+void create_window(void);
+void size_window(int, int);
+void redraw(int, int);
+void warpmouse(int, int);
+void memory(void);
+int args(void);
+
+ErrorCode ignored_xerrors[] = {
+ { 0, }
+};
+
+/* xext.c */
+void xext_init(void);
+Rectangle* xinerama_screens(int*);
+/* geom.c */
+bool rect_haspoint_p(Point, Rectangle);
+
+Cursor cursor[1];
+Visual* render_visual;
+
+void init_screens(void);
+void
+init_screens(void) {
+ Rectangle *rects;
+ Point p;
+ int i, n;
+
+ rects = xinerama_screens(&n);
+ p = querypointer(&scr.root);
+ for(i=0; i < n; i++) {
+ if(rect_haspoint_p(p, rects[i]))
+ break;
+ }
+ if(i == n)
+ i = 0;
+ scr.rect = rects[i];
+}
+
+/* main --- crack arguments, set up X stuff, run the main menu loop */
+
+int
+main(int argc, char **argv)
+{
+ static char *address;
+ char *cp;
+ int i;
+
+ g_argc = argc;
+ g_argv = argv;
+
+ ARGBEGIN{
+ case 'v':
+ print("%s\n", version);
+ return 0;
+ case 'a':
+ address = EARGF(usage());
+ break;
+ case 'i':
+ initial = EARGF(usage());
+ break;
+ default:
+ usage();
+ }ARGEND;
+
+ if(argc == 0)
+ usage();
+
+ initdisplay();
+ xext_init();
+ init_screens();
+ create_window();
+
+ numitems = argc;
+
+ labels = emalloc(numitems * sizeof *labels);
+ commands = emalloc(numitems * sizeof *labels);
+
+ for(i = 0; i < numitems; i++) {
+ labels[i] = argv[i];
+ if((cp = strchr(labels[i], ':')) != nil) {
+ *cp++ = '\0';
+ commands[i] = cp;
+ } else
+ commands[i] = labels[i];
+ if(strcmp(labels[i], initial) == 0)
+ cur = i;
+ }
+
+ client_init(address);
+
+ wborder = strtol(readctl("border "), nil, 10);
+ loadcolor(&cnorm, readctl("normcolors "));
+ loadcolor(&csel, readctl("focuscolors "));
+ font = loadfont(readctl("font "));
+ if(!font)
+ fatal("Can't load font");
+
+ run_menu();
+
+ XCloseDisplay(display);
+ return 0;
+}
+
+/* usage --- print a usage message and die */
+
+void
+usage(void)
+{
+ fprintf(stderr, "usage: %s -v\n", argv0);
+ fprintf(stderr, " %s [-a <address>] [-i <arg>] menitem[:command] ...\n", argv0);
+ exit(0);
+}
+
+/* run_menu --- put up the window, execute selected commands */
+
+enum {
+ MouseMask =
+ ButtonPressMask
+ | ButtonReleaseMask
+ | ButtonMotionMask
+ | PointerMotionMask,
+ MenuMask =
+ MouseMask
+ | StructureNotifyMask
+ | ExposureMask
+};
+
+void
+run_menu(void)
+{
+ XEvent ev;
+ int i, old, wide, high;
+
+ wide = 0;
+ high = labelh(font);
+ for(i = 0; i < numitems; i++)
+ wide = max(wide, textwidth(font, labels[i]));
+ wide += font->height & ~1;
+
+ size_window(wide, high);
+ warpmouse(wide, high);
+
+ for(;;) {
+ XNextEvent(display, &ev);
+ switch (ev.type) {
+ default:
+ fprintf(stderr, "%s: unknown ev.type %d\n",
+ argv0, ev.type);
+ break;
+ case ButtonRelease:
+ i = ev.xbutton.y / high;
+ if(ev.xbutton.x < 0 || ev.xbutton.x > wide)
+ return;
+ else if(i < 0 || i >= numitems)
+ return;
+
+ printf("%s\n", commands[i]);
+ return;
+ case ButtonPress:
+ case MotionNotify:
+ old = cur;
+ cur = ev.xbutton.y / high;
+ if(ev.xbutton.x < 0 || ev.xbutton.x > wide)
+ cur = ~0;
+ if(cur == old)
+ break;
+ redraw(high, wide);
+ break;
+ case MapNotify:
+ redraw(high, wide);
+ break;
+ case Expose:
+ redraw(high, wide);
+ break;
+ case ConfigureNotify:
+ case MappingNotify:
+ break;
+ }
+ }
+}
+
+/* set_wm_hints --- set all the window manager hints */
+
+void
+create_window(void)
+{
+ WinAttr wa = { 0 };
+ XEvent e;
+
+ wa.override_redirect = true;
+ menuwin = createwindow(&scr.root, Rect(-1, -1, 0, 0),
+ scr.depth, InputOutput,
+ &wa, CWOverrideRedirect);
+ selectinput(menuwin, MenuMask);
+ mapwin(menuwin);
+ XMaskEvent(display, StructureNotifyMask, &e);
+ if(!grabpointer(menuwin, nil, 0, MouseMask))
+ fatal("Failed to grab the mouse\n");
+ XSetCommand(display, menuwin->xid, g_argv, g_argc);
+}
+
+void
+size_window(int wide, int high)
+{
+ Rectangle r;
+ Point p;
+ int h;
+
+ h = high * numitems;
+ r = Rect(0, 0, wide, h);
+
+ p = querypointer(&scr.root);
+ p.x -= wide / 2;
+ p.x = max(p.x, scr.rect.min.x);
+ p.x = min(p.x, scr.rect.max.x - wide);
+
+ p.y -= cur * high + high / 2;
+ p.y = max(p.y, scr.rect.min.y);
+ p.y = min(p.y, scr.rect.max.y - h);
+
+ reshapewin(menuwin, rectaddpt(r, p));
+
+ //XSetWindowBackground(display, menuwin->xid, cnorm.bg);
+ setborder(menuwin, 1, cnorm.border);
+}
+
+/* redraw --- actually redraw the menu */
+
+void
+redraw(int high, int wide)
+{
+ Rectangle r;
+ CTuple *c;
+ int i;
+
+ r = Rect(0, 0, wide, high);
+ for(i = 0; i < numitems; i++) {
+ if(cur == i)
+ c = &csel;
+ else
+ c = &cnorm;
+ r = rectsetorigin(r, Pt(0, i * high));
+ fill(menuwin, r, c->bg);
+ drawstring(menuwin, font, r, Center, labels[i], c->fg);
+ }
+}
+
+/* warpmouse --- bring the mouse to the menu */
+
+void
+warpmouse(int wide, int high)
+{
+ Point p;
+ int offset;
+
+ /* move tip of pointer into middle of menu item */
+ offset = labelh(font) / 2;
+ offset += 6; /* fudge factor */
+
+ p = Pt(wide / 2, cur*high - high/2 + offset);
+ p = addpt(p, menuwin->r.min);
+
+ warppointer(p);
+}
+
diff --git a/cmd/wmiir.c b/cmd/wmiir.c
new file mode 100644
index 0000000..1801df8
--- /dev/null
+++ b/cmd/wmiir.c
@@ -0,0 +1,421 @@
+/* Copyight ©2007-2010 Kris Maglione <fbsdaemon@gmail.com>
+ * See LICENSE file for license details.
+ */
+#define IXP_NO_P9_
+#define IXP_P9_STRUCTS
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <ixp.h>
+#include <util.h>
+#include <fmt.h>
+
+static IxpClient *client;
+
+static void
+usage(void) {
+ fprint(1,
+ "usage: %s [-a <address>] {create | ls [-dlp] | read | remove | write} <file>\n"
+ " %s [-a <address>] xwrite <file> <data>\n"
+ " %s -v\n", argv0, argv0, argv0);
+ exit(1);
+}
+
+static int
+errfmt(Fmt *f) {
+ return fmtstrcpy(f, ixp_errbuf());
+}
+
+/* Utility Functions */
+static void
+write_data(IxpCFid *fid, char *name) {
+ void *buf;
+ int len;
+
+ buf = emalloc(fid->iounit);;
+ for(;;) {
+ len = read(0, buf, fid->iounit);
+ if(len <= 0)
+ break;
+ if(ixp_write(fid, buf, len) != len)
+ fatal("cannot write file %q\n", name);
+ }
+ free(buf);
+}
+
+static int
+comp_stat(const void *s1, const void *s2) {
+ Stat *st1, *st2;
+
+ st1 = (Stat*)s1;
+ st2 = (Stat*)s2;
+ return strcmp(st1->name, st2->name);
+}
+
+static void
+setrwx(long m, char *s) {
+ static char *modes[] = {
+ "---", "--x", "-w-",
+ "-wx", "r--", "r-x",
+ "rw-", "rwx",
+ };
+ strncpy(s, modes[m], 3);
+}
+
+static char *
+modestr(uint mode) {
+ static char buf[16];
+
+ buf[0]='-';
+ if(mode & P9_DMDIR)
+ buf[0]='d';
+ buf[1]='-';
+ setrwx((mode >> 6) & 7, &buf[2]);
+ setrwx((mode >> 3) & 7, &buf[5]);
+ setrwx((mode >> 0) & 7, &buf[8]);
+ buf[11] = 0;
+ return buf;
+}
+
+static char*
+timestr(time_t val) {
+ static char buf[32];
+
+ strftime(buf, sizeof buf, "%Y-%m-%d %H:%M", localtime(&val));
+ return buf;
+}
+
+static void
+print_stat(Stat *s, int lflag, char *file, int pflag) {
+ char *slash;
+
+ slash = "";
+ if(pflag)
+ slash = "/";
+ else
+ file = "";
+
+ if(lflag)
+ print("%s %s %s %5llud %s %s%s%s\n",
+ modestr(s->mode), s->uid, s->gid, s->length,
+ timestr(s->mtime), file, slash, s->name);
+ else {
+ if((s->mode&P9_DMDIR) && strcmp(s->name, "/"))
+ print("%s%s%s/\n", file, slash, s->name);
+ else
+ print("%s%s%s\n", file, slash, s->name);
+ }
+}
+
+/* Service Functions */
+static int
+xwrite(int argc, char *argv[]) {
+ IxpCFid *fid;
+ char *file;
+
+ ARGBEGIN{
+ default:
+ usage();
+ }ARGEND;
+
+ file = EARGF(usage());
+ fid = ixp_open(client, file, P9_OWRITE);
+ if(fid == nil)
+ fatal("Can't open file '%s': %r\n", file);
+
+ write_data(fid, file);
+ ixp_close(fid);
+ return 0;
+}
+
+static int
+xawrite(int argc, char *argv[]) {
+ IxpCFid *fid;
+ char *file, *buf;
+ int nbuf, i;
+
+ ARGBEGIN{
+ default:
+ usage();
+ }ARGEND;
+
+ file = EARGF(usage());
+ fid = ixp_open(client, file, P9_OWRITE);
+ if(fid == nil)
+ fatal("Can't open file '%s': %r\n", file);
+
+ nbuf = 1;
+ for(i=0; i < argc; i++)
+ nbuf += strlen(argv[i]) + (i > 0);
+ buf = emalloc(nbuf);
+ buf[0] = '\0';
+ while(argc) {
+ strcat(buf, ARGF());
+ if(argc)
+ strcat(buf, " ");
+ }
+
+ if(ixp_write(fid, buf, nbuf) == -1)
+ fatal("cannot write file '%s': %r\n", file);
+ ixp_close(fid);
+ free(buf);
+ return 0;
+}
+
+static int
+xcreate(int argc, char *argv[]) {
+ IxpCFid *fid;
+ char *file;
+
+ ARGBEGIN{
+ default:
+ usage();
+ }ARGEND;
+
+ file = EARGF(usage());
+ fid = ixp_create(client, file, 0777, P9_OWRITE);
+ if(fid == nil)
+ fatal("Can't create file '%s': %r\n", file);
+
+ if((fid->qid.type&P9_DMDIR) == 0)
+ write_data(fid, file);
+ ixp_close(fid);
+ return 0;
+}
+
+static int
+xremove(int argc, char *argv[]) {
+ char *file;
+
+ ARGBEGIN{
+ default:
+ usage();
+ }ARGEND;
+
+ file = EARGF(usage());
+ do {
+ if(!ixp_remove(client, file))
+ fprint(2, "%s: Can't remove file '%s': %r\n", argv0, file);
+ }while((file = ARGF()));
+ return 0;
+}
+
+static int
+xread(int argc, char *argv[]) {
+ IxpCFid *fid;
+ char *file, *buf;
+ int count;
+
+ ARGBEGIN{
+ default:
+ usage();
+ }ARGEND;
+
+ if(argc == 0)
+ usage();
+ file = EARGF(usage());
+ do {
+ fid = ixp_open(client, file, P9_OREAD);
+ if(fid == nil)
+ fatal("Can't open file '%s': %r\n", file);
+
+ buf = emalloc(fid->iounit);
+ while((count = ixp_read(fid, buf, fid->iounit)) > 0)
+ write(1, buf, count);
+ ixp_close(fid);
+
+ if(count == -1)
+ fprint(2, "%s: cannot read file '%s': %r\n", argv0, file);
+ } while((file = ARGF()));
+
+ return 0;
+}
+
+static int
+xls(int argc, char *argv[]) {
+ IxpMsg m;
+ Stat *stat;
+ IxpCFid *fid;
+ char *file;
+ char *buf;
+ int lflag, dflag, pflag;
+ int count, nstat, mstat, i;
+
+ lflag = dflag = pflag = 0;
+
+ ARGBEGIN{
+ case 'l':
+ lflag++;
+ break;
+ case 'd':
+ dflag++;
+ break;
+ case 'p':
+ pflag++;
+ break;
+ default:
+ usage();
+ }ARGEND;
+
+ count = 0;
+ file = EARGF(usage());
+ do {
+ stat = ixp_stat(client, file);
+ if(stat == nil)
+ fatal("cannot stat file '%s': %r\n", file);
+
+ i = strlen(file);
+ if(file[i-1] == '/') {
+ file[i-1] = '\0';
+ if(!(stat->mode&P9_DMDIR))
+ fatal("%s: not a directory", file);
+ }
+ if(dflag || (stat->mode&P9_DMDIR) == 0) {
+ print_stat(stat, lflag, file, pflag);
+ ixp_freestat(stat);
+ continue;
+ }
+ ixp_freestat(stat);
+
+ fid = ixp_open(client, file, P9_OREAD);
+ if(fid == nil)
+ fatal("Can't open file '%s': %r\n", file);
+
+ nstat = 0;
+ mstat = 16;
+ stat = emalloc(mstat * sizeof *stat);
+ buf = emalloc(fid->iounit);
+ while((count = ixp_read(fid, buf, fid->iounit)) > 0) {
+ m = ixp_message(buf, count, MsgUnpack);
+ while(m.pos < m.end) {
+ if(nstat == mstat) {
+ mstat <<= 1;
+ stat = erealloc(stat, mstat * sizeof *stat);
+ }
+ ixp_pstat(&m, &stat[nstat++]);
+ }
+ }
+ ixp_close(fid);
+
+ qsort(stat, nstat, sizeof *stat, comp_stat);
+ for(i = 0; i < nstat; i++) {
+ print_stat(&stat[i], lflag, file, pflag);
+ ixp_freestat(&stat[i]);
+ }
+ free(stat);
+ } while((file = ARGF()));
+
+ if(count == -1)
+ fatal("cannot read directory '%s': %r\n", file);
+ return 0;
+}
+
+static int
+xnamespace(int argc, char *argv[]) {
+ char *path;
+
+ ARGBEGIN{
+ default:
+ usage();
+ }ARGEND;
+
+ path = ixp_namespace();
+ if(path == nil)
+ fatal("can't find namespace: %r\n");
+ print("%s\n", path);
+ return 0;
+}
+
+static int
+xsetsid(int argc, char *argv[]) {
+ char *av0;
+
+ av0 = nil;
+ ARGBEGIN{
+ case '0':
+ av0 = EARGF(usage());
+ break;
+ default:
+ usage();
+ }ARGEND;
+ if(av0 == nil)
+ av0 = argv[0];
+ if(av0 == nil)
+ return 1;
+
+ setsid();
+ execvp(av0, argv);
+ fatal("setsid: can't exec: %r");
+ return 1; /* NOTREACHED */
+}
+
+typedef struct exectab exectab;
+struct exectab {
+ char *cmd;
+ int (*fn)(int, char**);
+} fstab[] = {
+ {"cat", xread},
+ {"create", xcreate},
+ {"ls", xls},
+ {"read", xread},
+ {"remove", xremove},
+ {"rm", xremove},
+ {"write", xwrite},
+ {"xwrite", xawrite},
+ {0, }
+}, utiltab[] = {
+ {"namespace", xnamespace},
+ {"ns", xnamespace},
+ {"setsid", xsetsid},
+ {0, }
+};
+
+int
+main(int argc, char *argv[]) {
+ char *address;
+ exectab *tab;
+ int ret;
+
+ quotefmtinstall();
+ fmtinstall('r', errfmt);
+
+ address = getenv("WMII_ADDRESS");
+
+ ARGBEGIN{
+ case 'v':
+ print("%s-" VERSION ", " COPYRIGHT "\n", argv0);
+ exit(0);
+ case 'a':
+ address = EARGF(usage());
+ break;
+ default:
+ usage();
+ }ARGEND;
+
+ if(argc < 1)
+ usage();
+
+ for(tab=utiltab; tab->cmd; tab++)
+ if(!strcmp(*argv, tab->cmd))
+ return tab->fn(argc, argv);
+
+ if(address && *address)
+ client = ixp_mount(address);
+ else
+ client = ixp_nsmount("wmii");
+ if(client == nil)
+ fatal("can't mount: %r\n");
+
+ for(tab=fstab; tab->cmd; tab++)
+ if(strcmp(*argv, tab->cmd) == 0) break;
+ if(tab->cmd == 0)
+ usage();
+
+ ret = tab->fn(argc, argv);
+
+ ixp_unmount(client);
+ return ret;
+}
+
diff --git a/config.mk b/config.mk
new file mode 100644
index 0000000..7b47cc8
--- /dev/null
+++ b/config.mk
@@ -0,0 +1,63 @@
+# Customize below to fit your system
+
+# Paths
+PREFIX = /usr/local
+ BIN = $(PREFIX)/bin
+ MAN = $(PREFIX)/share/man
+ DOC = $(PREFIX)/share/doc/wmii
+ ETC = $(PREFIX)/etc
+ LIBDIR = $(PREFIX)/lib
+ INCLUDE = $(PREFIX)/include
+
+# Includes and libs
+INCLUDES = -I. -I$(ROOT)/include -I$(INCLUDE) -I/usr/include
+LIBS = -L$(ROOT)/lib -L/usr/lib
+
+TERMINAL = xterm
+
+# Flags
+include $(ROOT)/mk/gcc.mk
+CFLAGS += -Os # $(DEBUGCFLAGS) -O0
+LDFLAGS += # -g $(LIBS)
+SOLDFLAGS += $(LDFLAGS)
+SHARED = -shared -Wl,-soname=$(SONAME)
+STATIC = -static
+
+# Compiler, Linker. Linker should usually *not* be ld.
+CC = cc -c
+LD = cc
+# Archiver
+AR = ar crs
+
+X11PACKAGES = xft
+INCX11 = $$(pkg-config --cflags $(X11PACKAGES))
+LIBICONV = # Leave blank if your libc includes iconv (glibc does)
+LIBIXP = $(ROOT)/lib/libixp.a
+
+# Your make shell. By default, the first found of /bin/dash, /bin/ksh,
+# /bin/sh. Except with bsdmake, which assumes /bin/sh is sane. bash and zsh
+# are painfully slow, and should be avoided.
+#BINSH = /bin/ash
+
+## Operating System Configurations
+
+# KenCC
+# Note: wmii *must* always compile under KenCC. It's vital for
+# argument checking in formatted IO, and similar diagnostics.
+#CFLAGS = -wF
+#STATIC = # Implied
+#CC=pcc -c
+#LD=pcc
+
+# *BSD
+#LIBICONV = -L/usr/local/lib -liconv
+# +Darwin
+#STATIC = # Darwin doesn't like static linking
+#SHARED = -dynamiclib
+#SOEXT = dylib
+
+# Solaris
+#CFLAGS = -fast $(INCS)
+#LDFLAGS = $(LIBS) -R$(PREFIX)/lib -lsocket -lnsl
+#CFLAGS += -xtarget=ultra
+
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..247dc17
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,267 @@
+wmii (3.9.2+debian-2) unstable; urgency=low
+
+ * QA upload
+ * Added build-dependencies to fix FTBFS: libxft-dev, libfreetype6-dev,
+ libxrandr-dev, libxinerama-dev. Thanks to Nobuhiro Iwamatsu for the
+ patch! Closes: #606070.
+
+ -- Ralf Treinen <treinen@debian.org> Mon, 06 Dec 2010 21:11:52 +0100
+
+wmii (3.9.2+debian-1) unstable; urgency=low
+
+ * Updating depends on suckless-tools, formerly dwm-tools.
+ * Merging upstream version 3.9.2+debian.
+ * Rediffing x-terminal-emulator.patch.
+ * Rediffing cflags.patch.
+ * Rediffing font.patch.
+ * Removing manpage.patch, not required anymore.
+ * Removing ixp-api.patch, not required anymore.
+ * Updating rules file to remove useless files.
+ * Removing another useless file through rules.
+ * Updating standards version to 3.9.0.
+ * Switching to source format 3.0 (quilt).
+ * Updating to debhelper version 8.
+ * Updating to standards version 3.9.1.
+ * Removing version from libixp build-depends, not needed anymore.
+ * Orphaning package.
+
+ -- Daniel Baumann <daniel@debian.org> Mon, 29 Nov 2010 21:24:05 +0100
+
+wmii (3.6+debian-8) unstable; urgency=low
+
+ * Adding maintainer homepage field to control.
+ * Marking maintainer homepage field to be also included in binary
+ packages and changelog.
+ * Adding README.source.
+ * Adding explicit debian source version 1.0 until switch to 3.0.
+ * Updating year in copyright file.
+ * Updating to standards 3.8.4.
+ * Sorting dh call in rules to more common order.
+ * Updating README.source.
+ * Making build-depends versioned where needed.
+ * Moving maintainer homepage from control to copyright.
+ * Removing mercurial files in clean target of rules.
+
+ -- Daniel Baumann <daniel@debian.org> Thu, 08 Apr 2010 06:35:44 +0200
+
+wmii (3.6+debian-7) unstable; urgency=low
+
+ * Updating maintainer field.
+ * Updating vcs fields.
+ * Updating package to standards version 3.8.3.
+ * Sorting depends.
+ * Correcting desktop file location.
+
+ -- Daniel Baumann <daniel@debian.org> Thu, 20 Aug 2009 20:31:37 +0200
+
+wmii (3.6+debian-6) unstable; urgency=low
+
+ * Updating package to standards version 3.8.2.
+ * Using correct rfc-2822 date formats in changelog.
+ * Prefixing debhelper files with package name.
+ * Updating year in copyright file.
+ * Adding description field in menu file.
+ * Using quilt rather than dpatch.
+ * Minimizing rules file.
+
+ -- Daniel Baumann <daniel@debian.org> Tue, 04 Aug 2009 16:09:38 +0200
+
+wmii (3.6+debian-5) unstable; urgency=low
+
+ * Applying patch from James Westby <james.westby@canonical.com> to define
+ IXP_NEEDAPI to 88, which is the required ixp API (Closes: #511954).
+
+ -- Daniel Baumann <daniel@debian.org> Sun, 03 May 2009 10:01:00 +0200
+
+wmii (3.6+debian-4) unstable; urgency=low
+
+ * Bumped to new policy.
+ * Updated menu file to new policy.
+ * Removing empty directories.
+ * Added patch to complete manpage headers.
+
+ -- Daniel Baumann <daniel@debian.org> Sun, 23 Dec 2007 20:52:00 +0100
+
+wmii (3.6+debian-3) unstable; urgency=low
+
+ * Replaced not available fixed variant with plain 'fixed' font.
+
+ -- Daniel Baumann <daniel@debian.org> Fri, 23 Nov 2007 13:51:00 +0100
+
+wmii (3.6+debian-2) unstable; urgency=low
+
+ * Removing -std=c99 from CFLAGS, thanks to Dann Frazier <dannf@debian.org>
+ (Closes: #452015).
+
+ -- Daniel Baumann <daniel@debian.org> Mon, 19 Nov 2007 21:25:00 +0100
+
+wmii (3.6+debian-1) unstable; urgency=low
+
+ * New upstream release.
+ * Rebuild upstream tarball without conflicting debian directory.
+
+ -- Daniel Baumann <daniel@debian.org> Sun, 18 Nov 2007 17:39:00 +0100
+
+wmii (3.6~rc2+20070518-3) unstable; urgency=medium
+
+ * Added libxext-dev to build-depends.
+
+ -- Daniel Baumann <daniel@debian.org> Tue, 04 Sep 2007 23:23:00 +0200
+
+wmii (3.6~rc2+20070518-2) unstable; urgency=medium
+
+ [ Don Armstrong ]
+ * Fix wmiirc and wmiiloop.sh to properly populate keys and the commands
+ to handle them (Closes: #423521)
+
+ -- Daniel Baumann <daniel@debian.org> Thu, 12 Jul 2007 11:37:00 +0200
+
+wmii (3.6~rc2+20070518-1) unstable; urgency=low
+
+ * New upstream snapshot.
+
+ -- Daniel Baumann <daniel@debian.org> Fri, 18 May 2007 09:08:00 +0200
+
+wmii (3.6~rc2+20070501-2) unstable; urgency=low
+
+ * Added lintian override.
+ * Do not compile wmiir statically.
+
+ -- Daniel Baumann <daniel@debian.org> Tue, 15 May 2007 14:52:00 +0200
+
+wmii (3.6~rc2+20070501-1) unstable; urgency=low
+
+ * New upstream snapshot.
+ * Minor cleanups.
+
+ -- Daniel Baumann <daniel@debian.org> Tue, 01 May 2007 09:10:00 +0200
+
+wmii (3.6~rc2+20070329-3) unstable; urgency=low
+
+ * Rebuild against fixed debhelper, see #420158 (Closes: #420119, #420146).
+
+ -- Daniel Baumann <daniel@debian.org> Sat, 21 Apr 2007 07:33:00 +0200
+
+wmii (3.6~rc2+20070329-2) unstable; urgency=low
+
+ * Fixed ETC in rules (Closes: #418003).
+
+ -- Daniel Baumann <daniel@debian.org> Thu, 19 Apr 2007 10:28:00 +0200
+
+wmii (3.6~rc2+20070329-1) unstable; urgency=low
+
+ * New upstream snapshot:
+ - Fixes problem with libixp (Closes: #416170).
+ - Temporarily using and shipping embedded libixp for this snapshot only,
+ this is not related to #416170.
+ * Minor cleanups.
+
+ -- Daniel Baumann <daniel@debian.org> Thu, 05 Apr 2007 17:01:00 +0200
+
+wmii (3.6~rc2-1) unstable; urgency=low
+
+ * New upstream release.
+
+ -- Daniel Baumann <daniel@debian.org> Wed, 07 Feb 2007 13:09:00 +0100
+
+wmii (3.6~rc1-1) unstable; urgency=low
+
+ * New upstream release.
+
+ -- Daniel Baumann <daniel@debian.org> Tue, 06 Feb 2007 16:47:00 +0100
+
+wmii (3.5.1+20070202-1) unstable; urgency=low
+
+ * New upstream snapshot.
+
+ -- Daniel Baumann <daniel@debian.org> Sat, 03 Feb 2007 10:32:00 +0100
+
+wmii (3.5.1+20070116-1) unstable; urgency=medium
+
+ * New upstream snapshot:
+ - fixes bug with tags.
+ * Removed libxrand-dev build-depends, xrand patch was not ported to wmii 3.5.
+
+ -- Daniel Baumann <daniel@debian.org> Thu, 18 Jan 2007 20:16:00 +0100
+
+wmii (3.5.1-2) unstable; urgency=low
+
+ * Triggering rebuild (Closes: #405647).
+
+ -- Daniel Baumann <daniel@debian.org> Mon, 15 Jan 2007 18:46:00 +0100
+
+wmii (3.5.1-1) unstable; urgency=low
+
+ * New upstream release.
+
+ -- Daniel Baumann <daniel@debian.org> Tue, 02 Jan 2007 21:05:00 +0100
+
+wmii (3.1-5) unstable; urgency=medium
+
+ * Added patch from Evan Deaubl <evan@warpedview.com> to add support for xrandr
+ (Closes: #398900).
+
+ -- Daniel Baumann <daniel@debian.org> Thu, 16 Nov 2006 17:04:00 +0100
+
+wmii (3.1-4) unstable; urgency=medium
+
+ * Updated upstream homepage (Closes: #395005).
+
+ -- Daniel Baumann <daniel@debian.org> Sun, 29 Oct 2006 17:05:00 +0200
+
+wmii (3.1-3) unstable; urgency=medium
+
+ * Applied patch from Gonzalo Tornaria <tornaria@math.utexas.ed> to fix fontset
+ problem with UTF (Closes: #394781).
+
+ -- Daniel Baumann <daniel@debian.org> Mon, 23 Oct 2006 18:12:00 +0200
+
+wmii (3.1-2) unstable; urgency=low
+
+ * New email address.
+
+ -- Daniel Baumann <daniel@debian.org> Tue, 04 Jul 2006 23:45:00 +0200
+
+wmii (3.1-1) unstable; urgency=low
+
+ * New upstream release.
+
+ -- Daniel Baumann <daniel.baumann@panthera-systems.net> Sun, 18 Jun 2006 11:19:00 +0200
+
+wmii (3.0-1) unstable; urgency=low
+
+ * New upstream release.
+
+ -- Daniel Baumann <daniel.baumann@panthera-systems.net> Fri, 19 May 2006 00:00:00 -0500
+
+wmii (2.5.2-1) unstable; urgency=low
+
+ * New upstream release.
+ * Adjusted confdir path.
+ * Adjusted 9base path in examples.
+
+ -- Daniel Baumann <daniel.baumann@panthera-systems.net> Sat, 21 Jan 2006 01:14:00 +0100
+
+wmii (2.5.1-1) unstable; urgency=low
+
+ * New upstream release.
+ * Removed now obsolete binary-package python2.3-libixp.
+ * Removed conflict to wmifs, upstream did rename the binary accordingly.
+ * Added icon and desktop entry for login-session managers (Closes: #345390).
+
+ -- Daniel Baumann <daniel.baumann@panthera-systems.net> Sat, 14 Jan 2006 14:42:00 +0100
+
+wmii (2-2) unstable; urgency=low
+
+ * Added temporary conflict to wmifs (Closes: #335446, #338033). I will
+ rename wmifs in the next revision, this conflict is just a quick fix.
+ * Modifing default configuration:
+ - using x-terminal-emulator instead of xterm (Closes: #334017).
+
+ -- Daniel Baumann <daniel.baumann@panthera-systems.net> Thu, 03 Nov 2005 22:58:00 +0100
+
+wmii (2-1) unstable; urgency=low
+
+ * Initial release (Closes: #311567).
+
+ -- Daniel Baumann <daniel.baumann@panthera-systems.net> Fri, 07 Oct 2005 11:26:13 +0200
diff --git a/debian/compat b/debian/compat
new file mode 100644
index 0000000..45a4fb7
--- /dev/null
+++ b/debian/compat
@@ -0,0 +1 @@
+8
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..daac39d
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,25 @@
+Source: wmii
+Section: x11
+Priority: optional
+Maintainer: Debian QA Group <packages@qa.debian.org>
+Build-Depends: debhelper (>= 8), libx11-dev, libxext-dev, libixp, libxt-dev,
+ libxft-dev, libfreetype6-dev, libxrandr-dev, libxinerama-dev
+Standards-Version: 3.9.1
+Homepage: http://www.suckless.org/
+
+Package: wmii
+Architecture: any
+Depends: ${misc:Depends}, ${shlibs:Depends}, suckless-tools | dwm-tools
+Conflicts: wmii2
+Replaces: wmii2
+Recommends: wmii-doc
+Provides: x-window-manager
+Description: lightweight tabbed and tiled X11 window manager, version 3
+ wmii is a dynamic window manager for X11, which is highly customizable and
+ usable with keyboard and mouse. It supports conventional, tabbed and tiled
+ window management with low memory usage. It is highly modularized and uses an
+ inter-process communication interface which is oriented on the 9p protocol of
+ plan9.
+ .
+ This package contains version 3 of the window manager, wmii2 contains version
+ 2.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..6226f8e
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,44 @@
+Files: *
+Copyright:
+ (C) 2006-2007 Kris Maglione <bsdaemon@comcast.net>
+ (C) 2003-2006 Anselm R. Garbe <garbeam@gmail.com>
+ (C) 2005 Georg Neis <gn@oglaroon.de>
+License: MIT
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+ .
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+ .
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+
+Files: debian/*
+Copyright: (C) 2005-2010 Daniel Baumann <daniel@debian.org>
+License: MIT
+ Permission is hereby granted, free of charge, to any person obtaining a
+ copy of this software and associated documentation files (the "Software"),
+ to deal in the Software without restriction, including without limitation
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ and/or sell copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following conditions:
+ .
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+ .
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
diff --git a/debian/desktop/wmii.desktop b/debian/desktop/wmii.desktop
new file mode 100644
index 0000000..7fe93cd
--- /dev/null
+++ b/debian/desktop/wmii.desktop
@@ -0,0 +1,7 @@
+[Desktop Entry]
+Encoding=UTF-8
+Name=Wmii
+Comment=Window manager improved 3
+Exec=wmii
+Icon=wmii.png
+Type=XSession
diff --git a/debian/icons/wmii.png b/debian/icons/wmii.png
new file mode 100644
index 0000000..89d5aac
--- /dev/null
+++ b/debian/icons/wmii.png
Binary files differ
diff --git a/debian/patches/01-x-terminal-emulator.patch b/debian/patches/01-x-terminal-emulator.patch
new file mode 100644
index 0000000..9247339
--- /dev/null
+++ b/debian/patches/01-x-terminal-emulator.patch
@@ -0,0 +1,15 @@
+Author: Daniel Baumann <daniel@debian.org>
+Description: Replaces xterm with x-terminal-emulator.
+
+diff -Naurp wmii.orig/config.mk wmii/config.mk
+--- wmii.orig/config.mk 2010-06-10 15:31:39.000000000 +0000
++++ wmii/config.mk 2010-06-10 15:35:11.000000000 +0000
+@@ -13,7 +13,7 @@ PREFIX = /usr/local
+ INCLUDES = -I. -I$(ROOT)/include -I$(INCLUDE) -I/usr/include
+ LIBS = -L$(ROOT)/lib -L/usr/lib
+
+-TERMINAL = xterm
++TERMINAL = x-terminal-emulator
+
+ # Flags
+ include $(ROOT)/mk/gcc.mk
diff --git a/debian/patches/02-cflags.patch b/debian/patches/02-cflags.patch
new file mode 100644
index 0000000..98de051
--- /dev/null
+++ b/debian/patches/02-cflags.patch
@@ -0,0 +1,15 @@
+Author: Dann Frazier <dannf@debian.org>
+Description: Removing -std=c99 from CFLAGS (Closes: #452015).
+
+diff -Naurp wmii.orig/config.mk wmii/config.mk
+--- wmii.orig/config.mk 2010-06-10 15:31:39.000000000 +0000
++++ wmii/config.mk 2010-06-10 15:36:36.000000000 +0000
+@@ -17,7 +17,7 @@ TERMINAL = xterm
+
+ # Flags
+ include $(ROOT)/mk/gcc.mk
+-CFLAGS += -Os # $(DEBUGCFLAGS) -O0
++#CFLAGS += -Os # $(DEBUGCFLAGS) -O0
+ LDFLAGS += # -g $(LIBS)
+ SOLDFLAGS += $(LDFLAGS)
+ SHARED = -shared -Wl,-soname=$(SONAME)
diff --git a/debian/patches/03-font.patch b/debian/patches/03-font.patch
new file mode 100644
index 0000000..8c062f7
--- /dev/null
+++ b/debian/patches/03-font.patch
@@ -0,0 +1,65 @@
+Author: Daniel Baumann <daniel@debian.org>
+Description: Replaces not available fixed variant with plain 'fixed' font.
+
+diff -Naurp wmii.orig/alternative_wmiircs/plan9port/wmiirc wmii/alternative_wmiircs/plan9port/wmiirc
+--- wmii.orig/alternative_wmiircs/plan9port/wmiirc 2010-06-10 15:31:39.000000000 +0000
++++ wmii/alternative_wmiircs/plan9port/wmiirc 2010-06-10 15:40:05.000000000 +0000
+@@ -28,8 +28,8 @@ noticetimeout=5
+ noticebar=/rbar/!notice
+
+ # Theme
+-wmiifont='drift,-*-fixed-*-*-*-*-9-*-*-*-*-*-*-*'
+-wmiifont='-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*'
++wmiifont='fixed'
++wmiifont='fixed'
+ wmiinormcol=`{echo '#000000 #c1c48b #81654f'}
+ wmiifocuscol=`{echo '#000000 #81654f #000000'}
+ wmiibackground='#333333'
+diff -Naurp wmii.orig/alternative_wmiircs/python/wmiirc.py wmii/alternative_wmiircs/python/wmiirc.py
+--- wmii.orig/alternative_wmiircs/python/wmiirc.py 2010-06-10 15:31:39.000000000 +0000
++++ wmii/alternative_wmiircs/python/wmiirc.py 2010-06-10 15:40:17.000000000 +0000
+@@ -35,7 +35,7 @@ noticebar=('right', '!notice')
+ background = '#333333'
+ floatbackground='#222222'
+
+-wmii['font'] = 'drift,-*-fixed-*-*-*-*-9-*-*-*-*-*-*-*'
++wmii['font'] = 'fixed'
+ wmii['normcolors'] = '#000000', '#c1c48b', '#81654f'
+ wmii['focuscolors'] = '#000000', '#81654f', '#000000'
+ wmii['grabmod'] = keys.defs['mod']
+diff -Naurp wmii.orig/alternative_wmiircs/ruby/config.yaml wmii/alternative_wmiircs/ruby/config.yaml
+--- wmii.orig/alternative_wmiircs/ruby/config.yaml 2010-06-10 15:31:39.000000000 +0000
++++ wmii/alternative_wmiircs/ruby/config.yaml 2010-06-10 15:40:10.000000000 +0000
+@@ -36,7 +36,7 @@ display:
+ ##
+ # The font to use in all text drawn by wmii.
+ #
+- font: -*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*
++ font: fixed
+
+ ##
+ # Thickness of client border (measured in pixels).
+diff -Naurp wmii.orig/cmd/wmii/dat.h wmii/cmd/wmii/dat.h
+--- wmii.orig/cmd/wmii/dat.h 2010-06-10 15:31:39.000000000 +0000
++++ wmii/cmd/wmii/dat.h 2010-06-10 15:39:47.000000000 +0000
+@@ -17,7 +17,7 @@
+ #include <fmt.h>
+ #include <x11.h>
+
+-#define FONT "-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*"
++#define FONT "fixed"
+ #define FOCUSCOLORS "#ffffff #335577 #447799"
+ #define NORMCOLORS "#222222 #eeeeee #666666"
+
+diff -Naurp wmii.orig/rc/wmiirc.sh wmii/rc/wmiirc.sh
+--- wmii.orig/rc/wmiirc.sh 2010-06-10 15:31:39.000000000 +0000
++++ wmii/rc/wmiirc.sh 2010-06-10 15:39:36.000000000 +0000
+@@ -20,7 +20,7 @@ export WMII_NORMCOLORS='#000000 #c1c48b
+ export WMII_FOCUSCOLORS='#000000 #81654f #000000'
+
+ export WMII_BACKGROUND='#333333'
+-export WMII_FONT='-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*'
++export WMII_FONT='fixed'
+
+ set -- $(echo $WMII_NORMCOLORS $WMII_FOCUSCOLORS)
+ export WMII_TERM="@TERMINAL@"
diff --git a/debian/patches/series b/debian/patches/series
new file mode 100644
index 0000000..8620182
--- /dev/null
+++ b/debian/patches/series
@@ -0,0 +1,3 @@
+01-x-terminal-emulator.patch
+02-cflags.patch
+03-font.patch
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 0000000..0e09f88
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,19 @@
+#!/usr/bin/make -f
+
+%:
+ dh ${@}
+
+override_dh_auto_clean:
+ dh_auto_clean
+
+ rm -f .hg*
+
+override_dh_auto_build:
+ CFLAGS="$(CFLAGS)" $(MAKE) PREFIX=/usr ETC=/etc/X11 LIBIXP=/usr/lib/libixp.a STATIC=""
+
+override_dh_auto_install:
+ $(MAKE) PREFIX=$(CURDIR)/debian/wmii/usr ETC=$(CURDIR)/debian/wmii/etc/X11 LIBIXP=/usr/lib/libixp.a install
+
+ # Removing useless files
+ rm -f debian/wmii/usr/share/doc/wmii/LICENSE
+ rm -f debian/wmii/usr/share/doc/wmii/alternative_wmiircs/ruby/LICENSE
diff --git a/debian/source/format b/debian/source/format
new file mode 100644
index 0000000..163aaf8
--- /dev/null
+++ b/debian/source/format
@@ -0,0 +1 @@
+3.0 (quilt)
diff --git a/debian/source/include-binaries b/debian/source/include-binaries
new file mode 100644
index 0000000..8ecfd55
--- /dev/null
+++ b/debian/source/include-binaries
@@ -0,0 +1 @@
+debian/icons/wmii.png
diff --git a/debian/source/options b/debian/source/options
new file mode 100644
index 0000000..d053b65
--- /dev/null
+++ b/debian/source/options
@@ -0,0 +1,2 @@
+compression = gzip
+compression-level = 9
diff --git a/debian/wmii.docs b/debian/wmii.docs
new file mode 100644
index 0000000..e845566
--- /dev/null
+++ b/debian/wmii.docs
@@ -0,0 +1 @@
+README
diff --git a/debian/wmii.install b/debian/wmii.install
new file mode 100644
index 0000000..ee76b1a
--- /dev/null
+++ b/debian/wmii.install
@@ -0,0 +1,2 @@
+debian/desktop/* /usr/share/xsessions
+debian/icons/* /usr/share/icons
diff --git a/debian/wmii.lintian-overrides b/debian/wmii.lintian-overrides
new file mode 100644
index 0000000..97f9cd7
--- /dev/null
+++ b/debian/wmii.lintian-overrides
@@ -0,0 +1 @@
+wmii binary: unusual-interpreter ./etc/X11/wmii-3.5/rc.wmii #!wmii9rc
diff --git a/debian/wmii.menu b/debian/wmii.menu
new file mode 100644
index 0000000..cddebbc
--- /dev/null
+++ b/debian/wmii.menu
@@ -0,0 +1,4 @@
+?package(wmii):needs="wm" section="Window Managers"\
+ title="Wmii" longtitle="Window manager improved 3"\
+ description="wmii is a dynamic window manager for X11, which is highly customizable and usable with keyboard and mouse."\
+ command="/usr/bin/wmii"
diff --git a/debian/wmii.wm b/debian/wmii.wm
new file mode 100644
index 0000000..c47af70
--- /dev/null
+++ b/debian/wmii.wm
@@ -0,0 +1 @@
+/usr/bin/wmii
diff --git a/doc/Makefile b/doc/Makefile
new file mode 100644
index 0000000..25e73da
--- /dev/null
+++ b/doc/Makefile
@@ -0,0 +1,13 @@
+ROOT=..
+include $(ROOT)/mk/hdr.mk
+include $(ROOT)/mk/wmii.mk
+
+TARG = wmii.pdf
+
+all: $(TARG)
+
+install: $(TARG:.pdf=.install)
+uninstall: $(TARG:.pdf=.uninstall)
+
+clean:; true
+
diff --git a/doc/floating.png b/doc/floating.png
new file mode 100644
index 0000000..42c07d0
--- /dev/null
+++ b/doc/floating.png
Binary files differ
diff --git a/doc/focused.png b/doc/focused.png
new file mode 100644
index 0000000..ceeb51e
--- /dev/null
+++ b/doc/focused.png
Binary files differ
diff --git a/doc/managed.png b/doc/managed.png
new file mode 100644
index 0000000..eb76ae5
--- /dev/null
+++ b/doc/managed.png
Binary files differ
diff --git a/doc/selected.png b/doc/selected.png
new file mode 100644
index 0000000..708b123
--- /dev/null
+++ b/doc/selected.png
Binary files differ
diff --git a/doc/unfocused.png b/doc/unfocused.png
new file mode 100644
index 0000000..3e92f7a
--- /dev/null
+++ b/doc/unfocused.png
Binary files differ
diff --git a/doc/unselected.png b/doc/unselected.png
new file mode 100644
index 0000000..5124bc3
--- /dev/null
+++ b/doc/unselected.png
Binary files differ
diff --git a/doc/wmii.pdf b/doc/wmii.pdf
new file mode 100644
index 0000000..db88998
--- /dev/null
+++ b/doc/wmii.pdf
Binary files differ
diff --git a/doc/wmii.tex b/doc/wmii.tex
new file mode 100644
index 0000000..3159d76
--- /dev/null
+++ b/doc/wmii.tex
@@ -0,0 +1,1497 @@
+\documentclass[letterpaper,oneside]{scrbook}
+
+\usepackage{txfonts}
+
+\usepackage{fontspec}
+\usepackage{xunicode}
+\usepackage{xltxtra}
+
+\usepackage{fancyvrb}
+\usepackage[top=1in,bottom=1in]{geometry}
+\usepackage{graphicx}
+\usepackage{makeidx}
+\usepackage{xcolor}
+\usepackage[xetex,breaklinks,colorlinks,linkcolor=black]{hyperref}
+
+% Indexes
+\makeindex
+\let\primary=\textbf
+
+\setmainfont[Mapping=tex-text, Numbers=OldStyle]{Palatino LT Std}
+
+\let\primary=\textbf
+
+\def\titlebar#1{%
+ \begin{center}\includegraphics[width=5.5in]{#1.png}\end{center}}
+
+\def\man#1#2{#2\textbf{(#1)}}
+
+% Key specs
+\def\key#1{{\small$\langle$\addfontfeature{Numbers=Lining}#1\/$\rangle$}}
+\let\<=<
+\catcode`\<=\active
+\def<#1>{\key{#1}}
+
+% Display ‹...› and «...» as text in left and right pointing
+% angle brackets. I use «» and ‹› because my terminal doesn't
+% display left and right pointing angle brackets properly, and
+% Xorg's compose maps don't provide them, anyway.
+\catcode`\«=\active
+\catcode`\‹=\active
+\def‹#1›{$\langle${\itshape#1}$\rangle$}
+\def«#1»{$\langle\langle${\itshape#1}$\rangle\rangle$}
+
+% Display |...| as verbatim, teletype text.
+\DefineShortVerb{\|}
+
+\makeatletter
+\let\:=:
+\catcode`\:=\active
+\def:{\@ifnextchar:{\coloncoloneq}{\:}}
+\def\coloncoloneq#1{\@ifnextchar={$\Coloneqq$\coloncoloneqq}{\:\:}}
+\def\coloncoloneqq#1{}
+
+% Create a verbatim {code} environment which highlights strings
+% and comments. Several unicode characters are hacked to replace
+% the grabbed characters, since we can't escape them in the
+% verbatim environment.
+\colorlet{comment}{gray}
+\colorlet{string}{red!100!black!90}
+\let\‘=‘
+\let\“=“
+\catcode`¶=6
+\catcode`#=\active\let#=\#
+\catcode`\#=\active
+\catcode`“=\active
+\catcode`‘=\active
+\def“¶1”{{\color{string}\“¶1”}}%
+\def‘¶1’{{\color{string}\‘¶1’}}%
+\DefineVerbatimEnvironment{code}{Verbatim}{xleftmargin=2em,gobble=2,%
+ codes={\catcode`\#=\active\catcode`\:=\active\catcode`“=\active\catcode`‘=\active},%
+ defineactive={%
+ \def#{\itshape\color{comment}\let“=\“\let‘=\‘\#}%
+ }}
+\catcode`\#=6
+\catcode`“=12
+\catcode`‘=12
+
+% Convenience defs for the various wmii commands, and a few
+% others.
+\def\wmii{\texttt{wmii}}
+\def\wiIXmenu{\texttt{wi9menu}}
+\def\wimenu{\texttt{wimenu}}
+\def\wmiir{\texttt{wmiir}}
+\def\ninep{{\addfontfeature{Numbers=Lining}9P}}
+\def\POSIX{\textsc{POSIX}}
+
+\begin{document}
+\thispagestyle{empty}
+\leavevmode
+\vfill
+
+\begin{center}
+ \centerline{\includegraphics[width=2in]{../img/wmii.pdf}}
+
+ \vskip 1in
+
+ \LARGE
+ The \wmii\ User Guide
+
+ \vskip .5in
+
+ \Large
+ Kris Maglione \\[1em]
+ \addfontfeature{Numbers=Lining}
+ 13 October 2009
+
+\end{center}
+
+\vfill
+
+\newpage
+
+\frontmatter
+
+\tableofcontents
+
+\newpage
+\chapter*{License}
+
+This file is distributed under the same terms as wmii:
+
+\begingroup
+\ttfamily
+\parindent=0pt
+\parskip=1em
+
+\catcode`\:=12
+Copyright © 2009 Kris Maglione <\href{mailto:maglione.k@gmail.com}{maglione.k@gmail.com}>
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+\endgroup
+
+\mainmatter
+
+\chapter{Introduction}
+
+\wmii\ is a simple but powerful window manager for the X Window
+System. It provides both the classic (“floating”) and tiling
+(“managed”) window management paradigms, which is to say, it does
+the job of managing your windows, so you don't have to. It also
+provides programability by means of a simple file-like
+interface, which allows the user to program in virtually any
+language he chooses. These basic features have become
+indispensable to the many users of \wmii\ and other similar
+window managers, but they come at a cost. Though our penchant
+for simplicity makes \wmii's learning curve significantly
+shorter than most of its competitors, there's still a lot to
+learn. The rest of this guide will be devoted to familiarizing
+new users with \wmii's novel features and eccentricities, as
+well as provide advanced users with an in-depth look at our
+customization facilities.
+
+\section{Concepts}
+
+As noted, \wmii\ provides two management styles:
+
+\begin{description}
+ \item[Managed] This is the primary style of window management
+ in \wmii. Windows managed in this style are automatically
+ arranged by \wmii\ into columns. Columns are created and
+ destroyed on demand. Individual windows in the column may be
+ moved or resized, and are often collapsed or hidden
+ entirely. Ad-hoc stacks of collapsed and uncollapsed windows
+ allow the user to efficiently manage their tasks. When
+ switching from an active to a collapsed window, the active
+ window collapses and the collapsed one effectively takes
+ its place.
+
+ Managed windows have an unadorned titlebar:
+
+ \titlebar{managed}
+
+ \item[Floating] Since some programs aren't designed in ways
+ conducive to the managed work flow, \wmii\ also provides the
+ classic “floating” window management model. Windows managed
+ in this model float above the managed windows and may be moved
+ freely about. Other than automatic placement of new windows
+ and snapping of edges, \wmii\ doesn't manage floating
+ windows at all.
+
+ Floating windows are indicated by a decorated titlebar:
+
+ \titlebar{floating}
+
+ \item[Fullscreen] Fullscreen mode is actually a subset of the
+ floating style. Windows may be toggled to and from
+ fullscreen mode at will. When fullscreen, windows reside in
+ the floating layer, above the managed windows. They have no
+ borders or titlebars, and occupy the full area of the
+ screen. Other than that, however, they're not special in any
+ way. Other floating windows may appear above them and the
+ user can still select, open, and close other windows at
+ will.
+\end{description}
+
+\subsection{The Filesystem}
+
+All of \wmii's customization is done via a virtual filesystem.
+Since the filesystem is implemented in the standardized \ninep\
+protocol, it can be accessed in many ways. \wmii\ provides a
+simple command-line client, \wmiir, but many alternatives exist,
+including libraries for Python, Perl, Ruby, PHP, and C. It can
+even be mounted, either by Linux's 9p.ko kernel module or
+indirectly via FUSE.
+
+The filesystem that \wmii\ provides is “virtual”, which is to
+say that it doesn't reside on disk anywhere. In a sense, it's a
+figment of \wmii's imagination. Files, when read, represent
+\wmii's current configuration or state. When written, they
+perform actions, update the UI, etc. For instance, the directory
+|/client/| contains a directory for each window that \wmii\
+is currently managing. Each of those directories, in turn,
+contains files describing the client's properties (its title,
+its views\footnote{Views in \wmii\ are akin to workspaces or
+virtual desktops in other window managers, but with some subtle
+differences.}, its state). Most files can be written to update
+the state they describe. For instance,
+|/client/sel/ctl| describes the state of the selected
+client. If a client is fullscreen, it contains the line:
+
+\begin{code}
+ Fullscreen on
+\end{code}
+
+\noindent To change this, you'd update the file with the line
+% XXX: Broken /ctl cmd.
+|Fullscreen off| or even |Fullscreen| |toggle| to toggle
+the client's fullscreen state.
+
+The concept of controlling a program via a filesystem derives
+from Plan 9, where such interfaces are extensive and well
+proven. The metaphor has shown itself to be quite intuitive to
+Unix users, once the shock of a “virtual” filesystem wears off.
+The flexibility of being able to control \wmii\ from myriad
+programming languages, including the standard Unix shell and
+even from the command line, is well worth the shock.
+
+\subsection{Views and Tags}
+
+Like most X11 window managers, \wmii\ provides virtual
+workspaces. Unlike other window managers though, \wmii's
+workspaces are created and destroyed on demand. Instead of being
+sent to a workspace, windows in \wmii\ are tagged with any
+number of names. Views are created dynamically from these tags,
+and automatically if the user tries to access them. For
+instance, if a window is given the tags ‘foo’ and ‘bar’, the two
+views ‘foo’ and ‘bar’ are created, if they don't already exist.
+The window is now visible on both of them. Moreover, tags can be
+specified as regular expressions. So, a client tagged with {\tt
+\verb+/^foo/+} will appear on any view named ‘foo’, ‘foo:bar’,
+and so forth. Any time a client is tagged with a matching tag,
+or the user opens a matching view, the window is automatically
+added to it.
+
+\subsection{The Bar}
+
+\wmii\ provides a general purpose information bar at the top or
+bottom of the screen. The bar is divided into a left and a right
+section. Each section is made up of buttons, with a single
+button spanning the gap between the two sides. Buttons can be
+individually styled and can hold any text content the user
+wishes. By convention, the buttons to the left show view names,
+and those to the right display status information.
+
+\subsection{The Menus}
+
+\wmii\ includes two simple, external menu programs. The first,
+\wimenu, is keyboard-based, and is used to launch programs and
+generally prompt the user for input. It provides a list of
+completions which are automatically filtered as you type. The
+second, \wiIXmenu, is mouse-based, and is generally used to
+provide context menus for titlebars and view buttons. Both menus
+can be easily launched from shell scripts or the command line,
+as well as from more complex scripting languages.
+
+\subsection{The Keyboard}
+
+\wmii\ is a very keyboard friendly window manager. Most actions
+can be performed without touching the mouse, including
+launching, closing, moving, resizing, and selecting programs.
+New keybindings of any complexity can easily be added to handle
+any missing functionality, or to simplify any repetitive tasks.
+
+\subsection{The Mouse}
+
+Despite being highly keyboard-accessible, \wmii\ strives to be
+highly mouse accessible as well. Windows can be moved or resized
+by dragging their window borders. When combined with a key
+press, they can be moved, resized, or raised by dragging any
+visible portion of the window. Mouse menus are accessed with a
+single click and drag. View buttons in the bar and client
+titlebars respond to the mouse wheel; view buttons can be
+activated by dragging any draggable object (e.g., a file from a
+file manager) over them.
+
+\chapter{Getting Started}
+
+This section will walk you through your first \wmii\ startup.
+For your first experience, we recommend running \wmii\ in its
+own X session, so you can easily switch back to a more
+comfortable environment if you get lost. Though you may start
+\wmii\ from a session manager in your day to day use, these
+instructions will use |xinit|. To begin with, copy this file
+to your home directory, so we can open it in your new X session.
+Then setup your |~/.xinitrc| as follows:
+
+\begin{code}
+ cd
+
+ # Start a PDF viewer with this guide. Use any viewer
+ # you're comfortable with.
+ xpdf wmii.pdf &
+
+ # Launch wmii
+ exec wmii
+
+ # That was easy.
+\end{code}
+
+Before you run |xinit|, make sure you know how to switch
+between terminals. Depending on your system, your current X
+session is probably on terminal 5 or 7. You should be able to
+switch between your terminals by pressing
+Ctrl-Alt-F$\langle n\rangle$. Assuming that your current X
+session is on terminal 7, you should be able to switch between
+it and your new session by pressing Ctrl-Alt-F7 and Ctrl-Alt-F8.
+Now you should be ready to start \wmii. When you run the
+following command, you should be presented with a new X session
+running wmii and a PDF viewer showing this document.
+
+\begin{code}
+ xinit
+\end{code}
+
+When you're there, find this page in the new PDF viewer and
+continue.
+
+\section{Your First Steps}
+
+If everything went according to plan, you should be viewing this
+from a nearly empty \wmii\ session. We're going to be using the
+keyboard a lot, so let's start with a convention for key
+notation. We'll be using the key modifiers Control, Alt, Shift,
+and Meta\footnote{The Windows$^{\mbox{\tiny®}}$ key on most
+keyboards. The Penguin key, on the more tongue in cheek
+varieties.}, which we'll specify as C-, A-, S-, and M-,
+respectively. So, <C-S-a> means pressing ‘|a|’ while holding
+|Control| and |Shift|. We'll also express mouse clicks this
+way, with <M-Mouse1> signifying a press of the right mouse
+button, with the Meta key depressed. Buttons 4 and 5 are the up
+and down scroll wheel directions, respectively.
+
+\subsection{Floating Mode}
+
+Beginning with what's familiar to most users, we'll first explore
+floating mode. First, we need to select the floating layer.
+Press <M-Space>. You should see the titlebar of this window
+change color. Now, press <M-Return> to launch a terminal.
+The easiest way to drag the terminal around is to press and hold
+<M-Mouse1> over the window and simply drag the window
+around. You should be able to drag the window anywhere onscreen
+without ever releasing the mouse button. As you drag near the
+screen edges, you should notice a snap. If you try to drag the
+window fully off-screen, you'll find it constrained so that a
+portion always remains visible. Now, release the window and move
+the mouse toward one of its corners. Press and hold
+<M-Mouse3>\footnote{The right button.}. As you drag the
+mouse around, you should see the window resized accordingly.
+
+To move the window without the modifier key, move the pointer
+over the layout box to the left of its titlebar. You should see
+the cursor change. Now, simply click and drag. To resize it,
+move the pointer toward the window's edge until you see the
+cursor change, and again, click and drag. Now, to close the
+window, move the mouse over the windows titlebar, press and hold
+<Mouse3>, select |Delete|, and release it. You should
+see this window's titlebar return to its original color,
+indicating that it's regained focus.
+
+\subsection{Managed Mode}
+
+Now, for the fun part. We'll start exploring managed mode by
+looking at the basics of columns. In the default configuration,
+columns have three modes:
+
+\begin{description}
+ \item[Stack] <M-s> The default mode for new columns. Only one window
+ is fully visible per column at once. The others only display
+ their title bars. When new windows are added to the column,
+ the active window collapses, and the new one takes its
+ place. Whenever a collapsed client is selected, the active
+ window is collapsed to take its place.
+ \item[Max] <M-m> Like stack mode, but the titlebars of collapsed
+ clients are hidden.
+ \item[Default] <M-d> Multiple uncollapsed windows may be visible at
+ once. New windows split the space with the other uncollapsed
+ windows in their vicinity. Windows may still be collapsed by
+ shrinking them to the size of their titlebars. At this
+ point, the behavior of a stack of collapsed and uncollapsed
+ clients is similar to that of stack mode.
+\end{description}
+
+Before we open any new windows in managed mode, we need to
+explore the column modes a bit. Column modes are activated with
+the key bindings listed above. This column should be in stack
+mode now. Watch the right side of the titlebar as you press
+<M-m> to enter max mode. You should see an indicator appear.
+This tells you the number of hidden windows directly above and
+below the current window, and its position in that stack. Press
+<M-d> to enter default mode. Now we're ready to open another
+client. Press <M-Return> to launch another terminal. Now,
+press <M-S-l> to move the terminal to a new column to the
+right of this one. Once it's there, press <M-Return> two
+more times to launch two more terminals. Now that you have more
+than one window in a column, cycle through the three column
+modes again until they seem familiar.
+
+\subsection{Keyboard Navigation}
+
+To begin, switch back to default mode. The basic keyboard
+navigation keys, <M-h>, <M-j>, <M-k>, and <M-l>,
+derive from vi, and represent moving left, down, up, and right
+respectively. Try selecting each of the four windows currently
+visible on screen. Notice that navigation wraps from one side of
+the screen to the other, and from the top to the bottom. Now,
+return to the write column, switch to stack mode, and select
+each of the three terminals again. Do the same in max mode,
+paying careful attention to the indicator to the right of the
+titlebar.
+
+Now that you can select windows, you'll want to move them
+around. To move a window, just add the Shift key to the
+direction keys. So, to move a window left, instead of <M-h>,
+type <M-S-h>. Now, experiment with moving windows, just as
+you did with navigating them, in each of the three column modes.
+Once you're comfortable with that, move a window to the floating
+layer. Since we toggled between the floating and managed layers
+with <M-Space>, we'll move windows between them with
+<M-S-Space>. Try moving some windows back and forth until it
+becomes familiar. Now, move several windows to the floating
+layer and try switching between them with the keyboard. You'll
+notice that <M-h> and <M-l> don't function in the
+floating layer. This is for both historical and logistical
+reasons. <M-j> and <M-k> cycle through floating windows
+in order of their most recent use.
+
+\subsection{Mouse Navigation}
+
+\wmii\ uses the “sloppy focus” model, which is to say, it focuses
+windows when the mouse enters them and when you click them. It
+focuses windows only when you select them with the keyboard,
+click their titlebars, or press click them with <M-Mouse2>.
+Collapsed windows may be opened with the mouse by clicking their
+titlebars. Moving and resizing floating windows should be
+largely familiar, and has already been covered. The same can't
+be said for managed windows.
+
+Let's begin working with the mouse in the managed layer. Return
+to a layout with this document in a column on the left, and
+three terminals in a column to the right. Switch the right
+column to default mode. Now, bring the mouse to the top of the
+third terminal's titlebar until you see a resize cursor. Click
+and drag the titlebar to the very top of the screen. Now, move
+the cursor to the top of the second terminal's titlebar and drag
+it to the very bottom of the screen. Press <M-d> to restore the
+terminals to their original sizes. Now, click and hold the
+layout box of the second terminal. Drag it to the middle of the
+terminal's window and release. Click and hold the layout box of
+the third terminal and drag it to the middle of the first
+terminal's window. Finally, drag the first terminal's layout box
+to halfway down this window. <M-Mouse1> works to the same
+effect as dragging the layout box, but allows you to click
+anywhere in the window.
+
+Now that you've seen the basics of moving and dragging windows,
+let's move on to columns. Click and drag the border between the
+two columns. If that's a difficult target to click, there's a
+triangle at the top of the division between the two columns that
+you can click and drag as well. If that's still too hard a
+target, try using <M-Mouse3>, which works anywhere and provides
+much richer functionality.
+
+\subsection{Window Focus and Selection}
+
+For the purposes of keyboard navigation, \wmii\ keeps track of
+which window is currently selected, and confers its titlebar a
+different color scheme from the other windows. This window is
+the basis of relative motion commands, such as “select the
+window to the left”, and the target of commands such as “close
+this window”. Normally, the selected window is the same as the
+focused window, i.e., the window that receives keyboard events.
+Some applications, however, present strange corner cases.
+
+\begin{description}
+ \item[Focused, selected window] This is the normal case of a
+ window which is both selected and has the keyboard focus.
+ \titlebar{selected}
+ \item[Unfocused, unselected window] This is the normal case for an
+ unselected window which does not have the keyboard focus.
+ \titlebar{unselected}
+ \item[Unfocused, selected window] This is the first unusual
+ case. This is the selected window, for the purposes of
+ keyboard navigation, but it does not receive keyboard events.
+ A good example is an onscreen keyboard, which will receive
+ mouse clicks and translate them to keyboard events, but
+ won't absorb those keyboard events itself. Other examples
+ include any window whilst another (such as \wimenu) has
+ grabbed the keyboard.
+ \titlebar{unfocused}
+ \item[Focused, unselected window] This is the second unusual
+ focus case. The window has the keyboard focus, but for the
+ purposes of keyboard navigation, it is not considered
+ selected. In the case of an onscreen keyboard, this is the
+ window which will receive the generated events. In the case
+ of a keyboard grab, the will likely be the window holding
+ the grab.
+ \titlebar{focused}
+\end{description}
+
+\section{Running Programs}
+
+You've already seen the convenient key binding to launch a
+terminal, but what about other programs? To get a menu of all of
+the executables in your path, type <M-p>. This should replace
+the bar at the bottom of the screen with a prompt, followed by a
+string of completions. Start typing the name of a program that
+you want to open. You can press <Tab> and <S-Tab> to cycle
+through the completions, or you can just press <Return> to
+select the first one. If you want to execute a more complex
+command, just type it out and press <Return>. If you want to
+recall that command later, use \wimenu's history. Start typing
+the command you want and then press <C-p> until you come to it.
+
+When you're done with a program, you'll probably want an easy
+way to close it. The first way is to ask the program to close
+itself. Since that can be tedious (and sometimes impossible),
+\wmii\ provides other ways. As mentioned, you can right click
+the titlebar and select |Delete|. If you're at the keyboard,
+you can type <M-S-c>. These two actions cause \wmii\ to ask
+nicely that the program exit. In those sticky cases where the
+program doesn't respond, \wmii\ will wait 10 seconds before
+prompting you to kill the program. If you don't feel like
+waiting, you can select |Kill| from the window's titlebar
+menu, in which case \wmii\ will forcefully and immediately kill
+it. Beware, killing clients is a last resort. In cases where the
+same program opens multiple windows, killing one will kill them
+all—without warning.
+
+\section{Using Views}
+
+As already noticed, \wmii's concept of virtual workspaces is
+somewhat unique, so let's begin exploring it. Open up a terminal
+and press <M-S-2>. You should see a new button on the bar at the
+bottom of the screen. When you click it, you should see your
+original terminal. Press <M-1> to come back here. Now, press
+<M-3>, and <M-1> again to return here once more. Notice that the
+views were created when needed, and destroyed when no longer
+necessary. If you want to select a view with a proper name, use
+<M-t> and enter the name. Other than the dynamic creation of
+views, this is still similar to the familiar X11 workspace
+model. But that's just the beginning of \wmii's model. Open a new
+terminal, and type:
+
+\begin{code}
+ echo ‘Hello world!’
+\end{code}
+
+\noindent Now, type <M-S-t>. In the menu that appears, enter
+|1+2+3|. Now, visit the views |1|, |2|, and |3|, and you'll see
+the client on each. To remove a tag, type <M-S-t> again, and
+this time enter |-2|. You'll notice that the client is no longer
+on the |2| view. Finally, tag names needn't be discrete,
+ordinary strings. They can also be regular expressions. Select
+the terminal again, and enter |+/^5/|. Now, switch to the |5|
+view. Now try the |6| view. Finally, type <M-t> and enter |50|
+to check the |50| view. Clients tagged with regular expressions
+are attached to any matching views when they're created. So,
+when you switch to an empty view, or tag a client with a new
+tag, any clients with matching regular expressions are
+automatically added to it. When all explicitly tagged clients
+disappear from the view, and it's no longer visible, clients
+held there by regular expressions are automatically removed.
+
+\section{Learning More}
+
+For full tables of the standard key bindings, and descriptions
+of the precise semantics of the topics discussed above, you
+should refer to \wmii's |man| pages.
+
+\chapter{Customizing \wmii}
+
+There are several configuration schemes available for \wmii. If
+you're only looking to add basic key bindings, status monitors,
+\emph{et cetera}, you should have no trouble modifying the stock
+configuration for your language of choice. If you're looking for
+deeper knowledge of \wmii's control interface though, this
+section is for you. We'll proceed by building a configuration
+script in \POSIX\ |sh| syntax and then move on to a discussion
+of the higher level constructs in the stock configuration
+scripts.
+
+\section{Events}
+
+The \wmii\ control interface is largely event driven. Each event
+is represented by a single, plain-text line written to the
+|/event| file. You can think of this file as a named pipe. When
+reading it, you won't receive an EOF\footnote{End of File} until
+\wmii\ exits. Moreover, any lines written to the file will be
+transmitted to everyone currently reading from it. Notable
+events include key presses, the creation and destruction of
+windows, and changes of focus and views.
+
+We'll start building our configuration with an event processing
+framework:
+
+\begin{code}
+ «Event Loop» ::=
+ # Broadcast a custom event
+ wmiir xwrite /event Start wmiirc
+
+ # Turn off globbing
+ set -f
+ # Open /event for reading
+ wmiir read /event |
+ # Read the events line by line
+ while read line; do
+ # Split the line into words, store in $@
+ set -- $line
+ event=$1; shift
+ line = "$(echo $line | sed ‘s/^[^ ]* //’ | tr -d ‘\n’)"
+ # Process the event
+ case $event in
+ Start) # Quit when a new instance starts
+ [ $1 = wmiirc ] && exit;;
+ «Event Handlers»
+ esac
+ done
+\end{code}
+
+Now, we need to consider which types of events we'll need to
+handle:
+
+\begin{code}
+ «Event Handlers» ::=
+ «View Button Events»
+ «Urgency Events»
+ «Unresponsive Clients»
+ «Notice Events»
+ «Key Events»
+ «Client Menu Events»
+ «Tag Menu Events»
+\end{code}
+
+\section{Bar Items}
+
+The bar is described by the files in the two directories |/lbar/| and
+|/rbar/| for buttons on the left and right side of the bar,
+respectively. The format of the files is:
+
+\begin{code}
+ ‹Color Tuple› ‹Label›
+\end{code}
+
+although the color tuple may be elided in cases where the label
+doesn't match its format.
+
+A ‹Color Tuple› is defined as:
+
+\begin{code}
+ ‹tuple› ::= ‹foreground color› ‹background color› ‹border color›
+ ‹color› ::= #‹6 character RGB hex color code›
+\end{code}
+
+Let's define our basic theme information now:
+
+\begin{code}
+ «Theme Definitions» ::=
+ normcolors=‘#000000 #c1c48b #81654f’
+ focuscolors=‘#000000 #81654f #000000’
+ background=‘#333333’
+ font=‘drift,-*-fixed-*-*-*-*-9-*-*-*-*-*-*-*’
+\end{code}
+
+\subsection{View Buttons}
+
+With a basic understanding of bar items in mind, we can write
+our view event handlers:
+
+\index{events!CreateTag}
+\index{events!DestroyTag}
+\index{events!FocusTag}
+\index{events!UnfocusTag}
+\begin{code}
+ «View Button Events» ::=
+ CreateTag) # CreateTag ‹Tag Name›
+ echo $normcolors $1 | wmiir create /lbar/$1;;
+ DestroyTag) # DestroyTag ‹Tag Name›
+ wmiir rm /lbar/$1;;
+ FocusTag) # FocusTag ‹Tag Name›
+ wmiir xwrite /lbar/$1 $focuscolors $1;;
+ UnfocusTag) # UnfocusTag ‹Tag Name›
+ wmiir xwrite /lbar/$1 $normcolors $1;;
+\end{code}
+
+\subsection{Urgency}
+
+\index{events!UrgentTag|(}
+\index{events!NotUrgentTag|(}
+Windows can specify that they require attention, and in X11
+parlance, this is called urgency. When a window requests
+attention as such, or declares that it's been satisfied, \wmii\
+broadcasts an event for the client and an event for each view
+that it belongs to, and fills in the client's layout box. It's
+the job of a script to decide how to handle it above and beyond
+that. The standard scripts simply mark urgent views with an
+asterisk:
+
+\begin{code}
+ «Urgency Events» ::=
+ # The urgency events are ‘Client’ events when the program
+ # owning the window sets its urgency state. They're ‘Manager’
+ # events when wmii or the wmii user sets the state.
+ UrgentTag) # UrgentTag ‹‘Client’ or ‘Manager’› ‹Tag Name›
+ wmiir xwrite /lbar/$2 $2;;
+ NotUrgentTag) # NotUrgentTag ‹‘Client’ or ‘Manager’› ‹Tag Name›
+ wmiir xwrite /lbar/$2 $2;;
+\end{code}
+\index{events!UrgentTag|)}
+\index{events!NotUrgentTag|)}
+
+\subsection{Notices}
+
+The standard scripts provide a custom Notice event for
+displaying status information. The events appear in the long bar
+between the left and right sides for five seconds.
+
+\begin{code}
+ «Notice Events» ::=
+ Notice)
+ wmiir xwrite /rbar/!notice $line
+ kill $xpid 2>/dev/null # Let's hope this isn't reused...
+ { sleep 5; wmiir xwrite /rbar/!notice ‘ ’; } &
+ xpid = $!;;
+\end{code}
+
+\section{Keys}
+
+\label{keybindings}
+\index{key bindings}
+\index{filesystem!/!keys}
+\index{filesystem!/!event}
+Now to the part you've no doubt been waiting for: binding keys.
+When binding keys, you need to be aware of two files, |/keys|
+and |/event|. The former defines which keys \wmii\ needs to
+grab, and the latter broadcasts the events when they're pressed.
+
+Key names are specified as a series of modifiers followed by a
+key name, all separated by hyphens. Valid modifier names are
+|Control|, |Shift|, |Mod1| (usually Alt), |Mod2|, |Mod3|, |Mod4|
+(usually the Windows® key), and |Mod5|. Modifier keys can be
+changed via |xmodmap(1)|, the details of which are beyond the
+scope of this document.
+
+Key names can be detected by running |xev| from a
+terminal, pressing the desired key, and looking at the output
+(it's in the parentheses, after the keysym). A \wmii-specific
+utility is forthcoming.
+
+Examples of key bindings:
+
+\begin{description}
+ \item[Windows® key + Capital A] |Mod4-Shift-A|
+ \item[Control + Alt + Space] |Mod1-Control-Space|
+\end{description}
+
+Now, let's bind the keys we plan on using:
+
+\begin{code}
+ «Bind Keys» ::=
+ {
+ cat <<!
+ Mod4-space
+ Mod4-d
+ Mod4-s
+ Mod4-m
+ Mod4-a
+ Mod4-p
+ Mod4-t
+ Mod4-Return
+ Mod4-Shift-space
+ Mod4-f
+ Mod4-Shift-c
+ Mod4-Shift-t
+ Mod4-h
+ Mod4-j
+ Mod4-k
+ Mod4-l
+ Mod4-Shift-h
+ Mod4-Shift-j
+ Mod4-Shift-k
+ Mod4-Shift-l
+ !
+ for i in 1 2 3 4 5 6 7 8 9 0; do
+ echo Mod4-$i
+ echo Mod4-Shift-$i
+ done
+ } | wmiir write /keys
+\end{code}
+
+and lay a framework for processing their events:
+
+\begin{code}
+ «Key Events» ::=
+ Key) # Key ‹Key Name›
+ case $1 in
+ «Motion Keys»
+ «Client Movement Keys»
+ «Column Mode Keys»
+ «Client Command Keys»
+ «Command Execution Keys»
+ «Tag Selection Keys»
+ «Tagging Keys»
+ esac;;
+\end{code}
+
+\section{Click Menus}
+
+Sometimes, you have your hand on the mouse and don't want to
+reach for the keyboard. To help cope, \wmii\ provides a
+mouse-driven, single-click menu. The default configuration uses
+it for client and tag menus.
+
+\begin{code}
+ «Click Menu Initialization» ::=
+ clickmenu() {
+ if res=$(wmii9menu -- “$@”); then eval “$res”; fi
+ }
+\end{code}
+
+\section{Control Files}
+
+Several directories including the root, have control files,
+named |ctl|. These files are used to control the object (e.g., a
+client or tag) represented by the directory. Each line of the
+file, with the possible section of the first, represents a
+control variable and its value. In the case of all but the root
+|/ctl| file, the first line represents the id of the directory.
+In the case of |/tag/foo/ctl|, for instance, the first line
+should read |foo|. This is useful when dealing with the special
+|sel/| directories. For instance, when |foo| is the selected
+tag, the special |/tag/sel| directory is a link to |/tag/foo|,
+and the first line of |/tag/sel/ctl| will read |foo|, just as
+if you'd accessed |/tag/foo/ctl| directly.
+
+The rest of the lines, the control variables, can be modified by
+writing new values to the control file. For instance, if a
+client is fullscreen, its control file will contain the line:
+
+\begin{code}
+ Fullscreen on
+\end{code}
+
+\noindent To restore the client from fullscreen, either of the
+following lines may be written to its control file:
+
+\begin{code}
+ Fullscreen off
+ Fullscreen toggle
+\end{code}
+
+When next read, the |Fullscreen on| line will have been replaced
+with |Fullscreen off|. No care need be taken to preserve the
+other contents of the file. They're generated anew each time
+it's read.
+
+\section{Clients}
+
+\def\clientlabel{/client/$\langle\mathit{client}\rangle$/}
+\index{filesystem!/client/*/@\clientlabel|(}
+Clients are represented by directories under the |/client/|
+tree. Subdirectory names represent the client's X11 window ID.
+The special |sel/| directory represents the currently selected
+client. The files in these directories are:
+
+\begin{description}
+ \item[ctl] The control file. The properties are:
+ \index{filesystem!/client/*/@\clientlabel!ctl}
+ \begin{description}
+ \item[Fullscreen] The client's fullscreen state. When
+ |on|, the client is displayed fullscreen on all of its
+ views. Possible values are |on|, |off|, and |toggle|.
+ \item[Urgent] The client's urgency state. When |on|, the
+ client's layout box will be highlighted. Possible values
+ are |on|, |off|, and |toggle|.
+ \item[kill] When written, the window is closed politely,
+ if possible.
+ \item[slay] When written, the client is killed peremptorily.
+ \end{description}
+ \item[props] The client's window class (the X11 |WM_CLASS|
+ property) and title string, separated by colons. This file
+ is not writable.
+ \index{filesystem!/client/*/@\clientlabel!props}
+ \item[label] The client's window title. May be written to
+ change the client's title.
+ \index{filesystem!/client/*/@\clientlabel!label}
+ \item[tags]
+ \index{filesystem!/client/*/@\clientlabel!tags}
+ The client's tags. Tag names are separated by |+|
+ signs. Tags beginning and ending with |/| are treated as
+ regular expressions. If the written value begins with a |+|
+ or a |-|, the tags are updated rather than overwritten. Tag
+ names which directly follow a |-| sign are removed rather
+ than added. Regular expression tags which directly follow a
+ minus sign are treated as exclusion expressions. For
+ example, the tag string |+/foo/-/food/| will match the tag
+ |foobar|, but not the tag |foodstand|.
+\end{description}
+
+\index{filesystem!/client/*/@\clientlabel|)}
+
+\subsection{Key Bindings}
+
+To control clients, we'll add the following key bindings:
+
+\begin{code}
+ «Client Command Keys» ::=
+ Mod4-Shift-c) wmiir xwrite /client/sel/ctl kill;;
+ Mod4-f) wmiir xwrite /client/sel/ctl Fullscreen toggle;;
+\end{code}
+
+And to manage their tags, we'll need:
+
+\begin{code}
+ «Tagging Keys» ::=
+ Mod4-Shift-t)
+ # Get the selected client's id
+ c=$(wmiir read /client/sel/ctl | sed 1q)
+ # Prompt the user for new tags
+ tags=$(wmiir ls /tag | sed ‘s,/,,; /^sel$/d’ | wimenu)
+ # Write them to the client
+ wmiir xwrite /client/$c/tags $tag;;
+ Mod4-Shift-[0-9])
+ wmiir xwrite /client/sel/tags ${1##*-};;
+\end{code}
+
+\subsection{Click Menus}
+
+\index{events!ClientMouseDown}
+\begin{code}
+ «Client Menu Events» ::=
+ ClientMouseDown) # ClientMouseDown ‹Client ID› ‹Button›
+ [ $2 = 3 ] && clickmenu \
+ “Delete:xwrite /client/$1/ctl kill” \
+ “Kill:xwrite /client/$1/ctl slay” \
+ “Fullscreen:/client/$1/ctl Fullscreen on”
+\end{code}
+
+\subsection{Unresponsive Clients}
+
+\index{events!UnresponsiveClient|(}
+When \wmii\ tries to close a window, it waits 8 seconds for the
+client to respond, and then lets its scripts decide what to do
+with it. The stock scripts prompt the user for input:
+
+\begin{code}
+ «Unresponsive Clients» ::=
+ UnresponsiveClient) # UnresponsiveClient ‹Client ID›
+ {
+ # Use wihack to make the xmessage a transient window of
+ # the problem client. This will force it to open in the
+ # floaing layer of whatever views the client is attached to
+ resp=$(wihack -transient $1 \
+ xmessage -nearmouse -buttons Kill,Wait -print \
+ “The following client is not responding.” \
+ “What would you like to do?$(echo)” \
+ $(wmiir read /client/$1/label))
+ [ $resp = Kill ] && wmiir xwrite /client/$1/ctl slay
+ } &;;
+\end{code}
+\index{events!UnresponsiveClient|)}
+
+\section{Views}
+
+\def\taglabel{/tag/$\langle\mathit{tag}\rangle$/}
+\index{filesystem!/tag/*/@\taglabel|(}
+Views are represented by directories under the |/tag/| tree. The
+special |sel/| directory represents the currently selected
+client. The |sel| tag is treated similarly elsewhere. The files
+in these directories are:
+
+\begin{description}
+ \item[ctl]
+ The view's control file. The properties are:
+ \index{filesystem!/tag/*/@\taglabel!ctl|(}
+ \begin{description}
+ \item[select ‹Area›] Select the column ‹Area›, where
+ ‹Area› is a 1-based column index, or |~| for the floating
+ area. It may be optionally preceded by ‹Screen›|:|, where
+ ‹Screen› is a 0-based Xinerama screen index, or “sel”. When
+ omitted, ‹Screen› defaults to 0, the primary screen.
+ \item[select ‹Area› ‹Client Index›] Select the column ‹Area›, and
+ the ‹Client Index›th client.
+ \item[select client ‹Client ID›] Select the client with the
+ X11 window ID ‹Client ID›.
+ \item[select ‹Direction›]
+ Select the client in ‹Direction› where ‹Direction› may be
+ one of ‹up $\wedge$ down $\wedge$ left $\wedge$ right›.
+ \item[send client ‹Client ID› ‹Area›] Send ‹Client ID› to
+ ‹Area›. ‹Area› may be |sel| for the selected area, and
+ |client ‹Client ID›| may be |sel| for the currently selected
+ client.
+ \item[send client ‹Client ID› ‹Direction›]
+ Send ‹Client ID› to a column or position in its column in
+ the given direction.
+ \item[send client ‹Client ID› toggle] If ‹Client ID› is
+ floating, send it to the managed layer. If it's managed,
+ send it to the floating layer.
+ \item[swap client ‹Client ID› \ldots] The same as the |send|
+ commands, but swap ‹Client ID› with the client at the given
+ location.
+ \item[colmode ‹Area› ‹Mode›] Set ‹Area›'s mode to ‹Mode›,
+ where ‹Mode› is a string of values similar to tag
+ specifications. Values which may be added and removed are as
+ follows for managed areas:
+
+ \begin{description}
+ \item[stack] One and only one client in the area is
+ uncollapsed at any given time. When a new client is
+ selected, it is uncollapsed and the previously selected
+ client is collapsed.
+ \item[max] Collapsed clients are hidden from view
+ entirely. Uncollapsed clients display an indicator
+ {\it‹n›/‹m›}, where ‹m› is the number of collapsed
+ clients directly above and below the client, plus one,
+ and ‹n› is the client's index in the stack.
+ \item[default] Like subtracting the stack mode, but all
+ clients in the column are given equal height.
+ \end{description}
+
+ For the floating area, the values are the same, except that
+ in |max| mode, floating clients are hidden when the managed
+ layer is selected.
+ \item[grow ‹Frame› ‹Direction› {[‹Amount›]}] Grow ‹Frame› in
+ the given direction, by ‹Amount›. ‹Amount› may be any
+ integer, positive or negative. If suffixed with |px|,
+ it specifies an exact pixel amount, otherwise it specifies a
+ “reasonable increment”. Defaults to 1.
+
+ ‹Frame› may be one of:
+ \begin{itemize}
+ \item client ‹Client ID›
+ \item ‹Area› ‹Client Index›
+ \end{itemize}
+ \item[nudge ‹Frame› ‹Direction› {[‹Amount›]}] Like
+ |grow|, but move the client in ‹Direction› instead of
+ resizing it.
+ \end{description}
+ \index{filesystem!/tag/*/@\taglabel!ctl|)}
+\end{description}
+
+\index{filesystem!/tag/*/@\taglabel|)}
+
+\subsection{Key Bindings}
+
+We'll use the following key bindings to interact with views:
+
+\begin{code}
+ «Motion Keys» ::=
+ Mod4-h) wmiir xwrite /tag/sel/ctl select left;;
+ Mod4-l) wmiir xwrite /tag/sel/ctl select right;;
+ Mod4-k) wmiir xwrite /tag/sel/ctl select up;;
+ Mod4-j) wmiir xwrite /tag/sel/ctl select down;;
+ Mod4-space) wmiir xwrite /tag/sel/ctl select toggle;;
+
+ «Client Movement Keys» ::=
+ Mod4-Shift-h) wmiir xwrite /tag/sel/ctl send sel left;;
+ Mod4-Shift-l) wmiir xwrite /tag/sel/ctl send sel right;;
+ Mod4-Shift-k) wmiir xwrite /tag/sel/ctl send sel up;;
+ Mod4-Shift-j) wmiir xwrite /tag/sel/ctl send sel down;;
+ Mod4-Shift-space) wmiir xwrite /tag/sel/ctl send sel toggle;;
+
+ «Column Mode Keys» ::=
+ Mod4-d) wmiir xwrite /tag/sel/ctl colmode sel -stack-max;;
+ Mod4-s) wmiir xwrite /tag/sel/ctl colmode sel stack-max;;
+ Mod4-m) wmiir xwrite /tag/sel/ctl colmode sel stack+max;;
+\end{code}
+
+\subsection{Click Menus}
+
+\index{events!LeftBarMouseDown}
+\begin{code}
+ «Tag Menu Events» ::=
+ LeftBarMouseDown) # LeftBarMouseDown ‹Button› ‹Bar Name›
+ [ $1 = 3 ] && clickmenu \
+ “Delete:delete_view $2”
+\end{code}
+
+\section{Command and Program Execution}
+
+Perhaps the most important function we need to provide for is
+the execution of programs. Since \wmii\ users tend to use
+terminals often, we'll add a direct shortcut to launch one.
+Aside from that, we'll add a menu to launch arbitrary programs
+(with completions) and a separate menu to launch wmii specific
+commands.
+
+We use |wmiir setsid| to launch programs with their own session
+IDs to prevent untoward effects when this script dies.
+
+\begin{code}
+ «Command Execution Initialization» ::=
+ terminal() { wmiir setsid xterm “$@” }
+ proglist() {
+ IFS=: set -- $1
+ find -L $@ -maxdepth 1 -perm /111 | sed ‘1d; s,.*/,,’ | sort | uniq
+ unset IFS
+ }
+\end{code}
+
+\subsection{Key Bindings}
+\begin{code}
+ «Command Execution Keys» ::=
+ Mod4-Return) terminal & ;;
+ Mod4-p) eval exec wmiir setsid "$(proglist $PATH | wimenu)" &;;
+ Mod4-a) {
+ set -- $(proglist $WMII_CONFPATH | wimenu)
+ which=$(which which)
+ prog=$(PATH=$WMII_CONFPATH $which $1); shift
+ eval exec $prog “$@”
+ } &;;
+\end{code}
+
+\section{The Root}
+
+The root filesystem contains the following:
+
+\index{!filesystem!/|(}
+\begin{description}
+ \item[ctl] The control file. The properties are:
+ \index{filesystem!/!ctl}
+ \begin{description}
+ \item[bar on ‹top $\wedge$ bottom›] Controls where the bar
+ is shown.
+ \item[bar off] Disables the bar entirely.
+ \item[border] The border width, in pixels, of floating
+ clients.
+ \item[colmode ‹Mode›] The default column mode for newly
+ created columns.
+ \item[focuscolors ‹Color Tuple›] The colors of focused
+ clients.
+ \item[normcolors ‹Color Tuple›] The colors of unfocused
+ clients and the default color of bar buttons.
+ \item[font ‹Font›] The font used throughout \wmii. If
+ prefixed with |xft:|, the Xft font renderer is used, and
+ fonts may be antialiased. Xft font names follow the
+ fontconfig formula. For instance, 10pt, italic Lucida
+ Sans would be specified as
+
+ \begin{code}
+ xft:Lucida Sans-10:italic
+ \end{code}
+
+ See \man 1 {fc-match}.
+
+ \item[grabmod ‹Modifier Keys›] The key which must be
+ pressed to move and resize windows with the mouse
+ without clicking hot spots.
+ \item[incmode ‹Mode›] Controls how X11 increment hints are
+ handled in managed mode. Possible values are:
+ \begin{description}
+ \item[ignore] Increment hints are ignored entirely.
+ Clients are stretched to fill their full allocated
+ space.
+ \item[show] Gaps are shown around managed client
+ windows when their increment hints prevent them from
+ filling their entire allocated space.
+ \item[squeeze] When increment hints cause gaps to show
+ around clients, \wmii\ will try to adjust the sizes
+ of the clients in the column to minimize lost space.
+ \end{description}
+ \item[view ‹Tag›] Change the currently visible view.
+ \item[exec ‹Command›] Replaces this \wmii\ instance with
+ ‹Command›. ‹Command› is split according to rc quoting
+ rules, and no expansion occurs. If the command fails to
+ execute, \wmii\ will respawn.
+ \item[spawn ‹Command›] Spawns ‹Command› as it would spawn
+ |wmiirc| at startup. If ‹Command› is a single argument
+ and doesn't begin with |/| or |./|,%
+ \hskip 1ex|$WMII_CONF|\-|PATH| is
+ searched for the executable. Otherwise, the whole
+ argument is passed to the shell for evaluation.
+ \end{description}
+ \item[keys] The global keybindings. See section \ref{keybindings}.
+ \index{filesystem!/!keys|primary}
+ \item[event] The global event feed. See section \ref{keybindings}.
+ \index{filesystem!/!event|primary}
+ \item[colrules]
+ \index{filesystem!/!colrules}
+ The |/colrules| file contains a list of
+ rules which affect the width of newly created columns.
+ Rules have the form:
+
+ \begin{quote}\texttt{
+ /‹regex›/ -> ‹width›{\color{gray}[}+‹width›{\color{gray}]*}}
+ \end{quote}
+
+ When a new column, ‹n›, is created on a view whose
+ name matches ‹regex›, the ‹n›th given
+ ‹width› percentage of the screen is given to it. If
+ there is no ‹n›th width, $1/\mbox{‹ncol›th}$ of the
+ screen is given to it.
+
+ \item[tagrules]
+ \index{filesystem!/!tagrules}
+ The |/tagrules| file contains a list of
+ rules similar to the colrules. These rules specify
+ the tags a client is to be given when it is created.
+ Rules are specified:
+
+ \begin{quote}\texttt{
+ /‹regex›/ -> ‹tag›{\color{gray}[}+‹tag›{\color{gray}]*}}
+ \end{quote}
+
+ When a client's ‹name›:‹class›:‹title› matches
+ ‹regex›, it is given the tagstring ‹tag›. There are
+ two special tags. |!|, which is deprecated, and identical
+ to |sel|, represents the current tag. |~|
+ represents the floating layer.
+\end{description}
+
+\index{!filesystem!/|)}
+
+\subsection{Configuration}
+
+We'll need to let \wmii\ know about our previously defined theme
+information:
+
+\begin{code}
+ «Configuration» ::=
+ «Theme Definitions»
+
+ xsetroot -solid $background
+ wmiir write /ctl <<!
+ border 2
+ focuscolors $focuscolors
+ normcolors $normcolors
+ font $font
+ grabmod Mod4
+ !
+\end{code}
+
+\subsection{Key Bindings}
+
+And we need a few more key bindings to select our views:
+
+\begin{code}
+ «Tag Selection Keys» ::=
+ Mod4-t)
+ # Prompt the user for a tag
+ tags=$(wmiir ls /tag | sed ‘s,/,,; /^sel$/d’ | wimenu)
+ # Write it to the filesystem.
+ wmiir xwrite /ctl view $tags;;
+ Mod4-[0-9])
+ wmiir xwrite /ctl view ${1##*-};;
+\end{code}
+
+\section{Tieing it All Together}
+
+\begin{code}
+ #!/bin/sh
+ «Click Menu Initialization»
+ «Command Execution Initialization»
+
+ «Configuration»
+
+ «Bind Keys»
+ «Event Loop»
+\end{code}
+
+\section{The End Result}
+
+For clarity, here is the end result:
+
+\begin{code}
+ #!/bin/sh
+ # «Click Menu Initialization»
+ clickmenu() {
+ if res=$(wmii9menu -- “$@”); then eval “$res”; fi
+ }
+ # «Command Execution Initialization»
+ terminal() { wmiir setsid xterm “$@” }
+ proglist() {
+ IFS=: set -- $1
+ find -L $@ -maxdepth 1 -perm /111 | sed ‘1d; s,.*/,,’ | sort | uniq
+ unset IFS
+ }
+
+ # «Configuration»
+ # «Theme Definitions»
+ normcolors=‘#000000 #c1c48b #81654f’
+ focuscolors=‘#000000 #81654f #000000’
+ background=‘#333333’
+ font=‘drift,-*-fixed-*-*-*-*-9-*-*-*-*-*-*-*’
+
+ xsetroot -solid $background
+ wmiir write /ctl <<!
+ border 2
+ focuscolors $focuscolors
+ normcolors $normcolors
+ font $font
+ grabmod Mod4
+ !
+
+ # «Bind Keys»
+ {
+ cat <<!
+ Mod4-space
+ Mod4-d
+ Mod4-s
+ Mod4-m
+ Mod4-a
+ Mod4-p
+ Mod4-t
+ Mod4-Return
+ Mod4-Shift-space
+ Mod4-f
+ Mod4-Shift-c
+ Mod4-Shift-t
+ Mod4-h
+ Mod4-j
+ Mod4-k
+ Mod4-l
+ Mod4-Shift-h
+ Mod4-Shift-j
+ Mod4-Shift-k
+ Mod4-Shift-l
+ !
+ for i in 1 2 3 4 5 6 7 8 9 0; do
+ echo Mod4-$i
+ echo Mod4-Shift-$i
+ done
+ } | wmiir write /keys
+
+ # «Event Loop»
+ # Broadcast a custom event
+ wmiir xwrite /event Start wmiirc
+
+ # Turn off globbing
+ set -f
+ # Open /event for reading
+ wmiir read /event |
+ # Read the events line by line
+ while read line; do
+ # Split the line into words, store in $@
+ set -- $line
+ event=$1; shift
+ line = "$(echo $line | sed ‘s/^[^ ]* //’ | tr -d ‘\n’)"
+
+ # Process the event
+ case $event in
+ Start) # Quit when a new instance starts
+ [ $1 = wmiirc ] && exit;;
+
+ # «Event Handlers»
+ # «View Button Events»
+ CreateTag) # CreateTag ‹Tag Name›
+ echo $normcolors $1 | wmiir create /lbar/$1;;
+ DestroyTag) # DestroyTag ‹Tag Name›
+ wmiir rm /lbar/$1;;
+ FocusTag) # FocusTag ‹Tag Name›
+ wmiir xwrite /lbar/$1 $focuscolors $1;;
+ UnfocusTag) # UnfocusTag ‹Tag Name›
+ wmiir xwrite /lbar/$1 $normcolors $1;;
+
+ # «Urgency Events»
+ # The urgency events are ‘Client’ events when the program
+ # owning the window sets its urgency state. They're ‘Manager’
+ # events when wmii or the wmii user sets the state.
+ UrgentTag) # UrgentTag ‹‘Client’ or ‘Manager’› ‹Tag Name›
+ wmiir xwrite /lbar/$2 $2;;
+ NotUrgentTag) # NotUrgentTag ‹‘Client’ or ‘Manager’› ‹Tag Name›
+ wmiir xwrite /lbar/$2 $2;;
+
+ # «Unresponsive Clients»
+ UnresponsiveClient) # UnresponsiveClient ‹Client ID›
+ {
+ # Use wihack to make the xmessage a transient window of
+ # the problem client. This will force it to open in the
+ # floaing layer of whatever views the client is attached to
+ resp=$(wihack -transient $1 \
+ xmessage -nearmouse -buttons Kill,Wait -print \
+ “The following client is not responding.” \
+ “What would you like to do?$(echo)” \
+ $(wmiir read /client/$1/label))
+ [ $resp = Kill ] && wmiir xwrite /client/$1/ctl slay
+ } &;;
+
+ # «Notice Events»
+ Notice)
+ wmiir xwrite /rbar/!notice $line
+ kill $xpid 2>/dev/null # Let's hope this isn't reused...
+ { sleep 5; wmiir xwrite /rbar/!notice ‘ ’; } &
+ xpid = $!;;
+
+ # «Key Events»
+ Key) # Key ‹Key Name›
+ case $1 in
+ # «Motion Keys»
+ Mod4-h) wmiir xwrite /tag/sel/ctl select left;;
+ Mod4-l) wmiir xwrite /tag/sel/ctl select right;;
+ Mod4-k) wmiir xwrite /tag/sel/ctl select up;;
+ Mod4-j) wmiir xwrite /tag/sel/ctl select down;;
+ Mod4-space) wmiir xwrite /tag/sel/ctl select toggle;;
+
+ # «Client Movement Keys»
+ Mod4-Shift-h) wmiir xwrite /tag/sel/ctl send sel left;;
+ Mod4-Shift-l) wmiir xwrite /tag/sel/ctl send sel right;;
+ Mod4-Shift-k) wmiir xwrite /tag/sel/ctl send sel up;;
+ Mod4-Shift-j) wmiir xwrite /tag/sel/ctl send sel down;;
+ Mod4-Shift-space) wmiir xwrite /tag/sel/ctl send sel toggle;;
+
+ # «Column Mode Keys»
+ Mod4-d) wmiir xwrite /tag/sel/ctl colmode sel -stack-max;;
+ Mod4-s) wmiir xwrite /tag/sel/ctl colmode sel stack-max;;
+ Mod4-m) wmiir xwrite /tag/sel/ctl colmode sel stack+max;;
+
+ # «Client Command Keys»
+ Mod4-Shift-c) wmiir xwrite /client/sel/ctl kill;;
+ Mod4-f) wmiir xwrite /client/sel/ctl Fullscreen toggle;;
+
+ # «Command Execution Keys»
+ Mod4-Return) terminal & ;;
+ Mod4-p) eval exec wmiir setsid "$(proglist $PATH | wimenu)" &;;
+ Mod4-a) {
+ set -- $(proglist $WMII_CONFPATH | wimenu)
+ prog=$(PATH=$WMII_CONFPATH which $1); shift
+ eval exec $prog “$@”
+ } &;;
+
+ # «Tag Selection Keys»
+ Mod4-t)
+ # Prompt the user for a tag
+ tags=$(wmiir ls /tag | sed ‘s,/,,; /^sel$/d’ | wimenu)
+ # Write it to the filesystem.
+ wmiir xwrite /ctl view $tag;;
+ Mod4-[0-9])
+ wmiir xwrite /ctl view ${1##*-};;
+
+ # «Tagging Keys»
+ Mod4-Shift-t)
+ # Get the selected client's id
+ c=$(wmiir read /client/sel/ctl | sed 1q)
+ # Prompt the user for new tags
+ tags=$(wmiir ls /tag | sed ‘s,/,,; /^sel$/d’ | wimenu)
+ # Write them to the client
+ wmiir xwrite /client/$c/tags $tag;;
+ Mod4-Shift-[0-9])
+ wmiir xwrite /client/sel/tags ${1##*-};;
+
+ esac;;
+
+ # «Client Menu Events»
+ ClientMouseDown) # ClientMouseDown ‹Client ID› ‹Button›
+ [ $2 = 3 ] && clickmenu \
+ “Delete:xwrite /client/$1/ctl kill” \
+ “Kill:xwrite /client/$1/ctl slay” \
+ “Fullscreen:/client/$1/ctl Fullscreen on”
+
+ # «Tag Menu Events»
+ LeftBarMouseDown) # LeftBarMouseDown ‹Button› ‹Bar Name›
+ [ $1 = 3 ] && clickmenu \
+ “Delete:delete_view $2”
+ esac
+ done
+\end{code}
+
+\backmatter
+
+\printindex
+
+\end{document}
diff --git a/img/icon.png b/img/icon.png
new file mode 100644
index 0000000..9784430
--- /dev/null
+++ b/img/icon.png
Binary files differ
diff --git a/img/mkfile b/img/mkfile
new file mode 100644
index 0000000..ff5fc86
--- /dev/null
+++ b/img/mkfile
@@ -0,0 +1,41 @@
+MKSHELL=rc
+path=$PLAN9/bin $path
+
+eps = wmii.eps
+calc = rc -c 'hoc -e $"*'
+
+epsbox = `{sed -n '/^%%BoundingBox:/{s/.*://p; q;}' $eps}
+iconwidth = 154
+iconscale = `{*=$epsbox; $calc $iconwidth / '('$3 - $1')'}
+iconheight = `{*=$epsbox; $calc '('$4 - $2') *' $iconscale}
+
+%.png: %.eps
+ * = `{hoc -e'-('$epsbox')'}
+ x = $1
+ y = $2
+ gs -q -dBATCH -dNOPAUSE -s'DEVICE=pngalpha' -s'OutputFile='$target -g$iconwidth'x'$iconheight - <<!
+ $iconscale $iconscale scale
+ $x $y translate
+ ($eps) run
+ showpage
+ quit
+ !
+
+%.pdf: %.eps
+ sh epstopdf $stem.eps
+
+%-small.png: %.eps
+ iconwidth = 16
+ iconscale = `{*=$epsbox; hoc -e $iconwidth/'('$3-' '$1')'}
+ iconheight = `{*=$epsbox; hoc -e '('$4-' '$2')*'$iconscale}
+ * = `{hoc -e'-('$epsbox')'}
+ x = $1
+ y = $2
+ gs -q -dBATCH -dNOPAUSE -s'DEVICE=pngalpha' -s'OutputFile='$target -g$iconwidth'x'$iconheight - <<!
+ $iconscale $iconscale scale
+ $x $y translate
+ ($eps) run
+ showpage
+ quit
+ !
+
diff --git a/img/wmii.eps b/img/wmii.eps
new file mode 100644
index 0000000..727dd52
--- /dev/null
+++ b/img/wmii.eps
@@ -0,0 +1,29 @@
+%!PS-Adobe-2.0 EPSF-1.2
+%%BoundingBox: -1 0 51 27
+%%Creator: MetaPost
+%%CreationDate: 2007.02.27:1944
+%%Pages: 1
+%%EndProlog
+%%Page: 1 1
+ 0 6.23616 dtransform truncate idtransform setlinewidth pop [] 0 setdash
+ 0 setlinecap 2 setlinejoin 10 setmiterlimit
+newpath 2.83461 17.00761 moveto
+2.83461 2.83461 lineto
+14.17302 2.83461 lineto
+14.17302 17.00761 lineto
+14.17302 2.83461 lineto
+25.51143 2.83461 lineto
+25.51143 14.173 lineto
+36.84984 14.173 lineto stroke
+ 0 setlinejoin
+newpath 36.84984 14.173 moveto
+36.84984 0 lineto
+36.84984 14.173 lineto
+48.18825 14.173 lineto
+48.18825 0 lineto stroke
+newpath 36.84984 20.40916 moveto
+36.84984 26.07837 lineto stroke
+newpath 48.18825 20.40916 moveto
+48.18825 26.07837 lineto stroke
+showpage
+%%EOF
diff --git a/img/wmii.mp b/img/wmii.mp
new file mode 100644
index 0000000..342ce0d
--- /dev/null
+++ b/img/wmii.mp
@@ -0,0 +1,19 @@
+beginfig(1)
+%u=0.6cm;
+u=0.2cm;
+h=3u;
+space=u;
+linecap:=butt;
+linejoin:=beveled;
+pickup pencircle scaled 1.1u;
+draw (.5u,h)--(.5u,.5u)--(1.5u+space,.5u)--(1.5u+space,h)--(1.5u+space,.5u)--(2.5u+2space,.5u)\
+ --(2.5u+2space,h-.5u)--(3.5u+3space,h-.5u);
+linejoin:=mitered;
+draw (3.5u+3space,h-.5u)--(3.5u+3space,0)--(3.5u+3space,h-.5u)--(4.5u+4space,h-.5u)--(4.5u+4space,0);;
+
+gap=.6u;
+draw (3.5u+3space,h+gap)--(3.5u+3space,h+u+gap);
+draw (4.5u+4space,h+gap)--(4.5u+4space,h+u+gap);
+endfig
+
+end
diff --git a/img/wmii.pdf b/img/wmii.pdf
new file mode 100644
index 0000000..ef7618a
--- /dev/null
+++ b/img/wmii.pdf
Binary files differ
diff --git a/img/wmii.png b/img/wmii.png
new file mode 100644
index 0000000..539c013
--- /dev/null
+++ b/img/wmii.png
Binary files differ
diff --git a/include/Makefile b/include/Makefile
new file mode 100644
index 0000000..3cb885d
--- /dev/null
+++ b/include/Makefile
@@ -0,0 +1,9 @@
+ROOT= ..
+include ${ROOT}/mk/hdr.mk
+include ${ROOT}/mk/ixp.mk
+
+HFILES = ixp.h \
+ ixp_srvutil.h
+
+install: ${HFILES:.h=.install}
+
diff --git a/include/bio.h b/include/bio.h
new file mode 100644
index 0000000..e1eb878
--- /dev/null
+++ b/include/bio.h
@@ -0,0 +1,87 @@
+#ifndef _BIO_H_
+#define _BIO_H_ 1
+
+#ifdef AUTOLIB
+AUTOLIB(bio)
+#endif
+
+#include <sys/types.h> /* for off_t */
+#include <fcntl.h> /* for O_RDONLY, O_WRONLY */
+#include <stdarg.h> /* for va_list */
+
+typedef struct Biobuf Biobuf;
+
+enum
+{
+ Bsize = 8*1024,
+ Bungetsize = 4, /* space for ungetc */
+ Bmagic = 0x314159,
+ Beof = -1,
+ Bbad = -2,
+
+ Binactive = 0, /* states */
+ Bractive,
+ Bwactive,
+ Bracteof,
+
+ Bend
+};
+
+struct Biobuf
+{
+ int icount; /* neg num of bytes at eob */
+ int ocount; /* num of bytes at bob */
+ int rdline; /* num of bytes after rdline */
+ int runesize; /* num of bytes of last getrune */
+ int state; /* r/w/inactive */
+ int fid; /* open file */
+ int flag; /* magic if malloc'ed */
+ off_t offset; /* offset of buffer in file */
+ int bsize; /* size of buffer */
+ unsigned char* bbuf; /* pointer to beginning of buffer */
+ unsigned char* ebuf; /* pointer to end of buffer */
+ unsigned char* gbuf; /* pointer to good data in buf */
+ unsigned char b[Bungetsize+Bsize];
+};
+
+#define BGETC(bp)\
+ ((bp)->icount?(bp)->bbuf[(bp)->bsize+(bp)->icount++]:Bgetc((bp)))
+#define BPUTC(bp,c)\
+ ((bp)->ocount?(bp)->bbuf[(bp)->bsize+(bp)->ocount++]=(c),0:Bputc((bp),(c)))
+#define BOFFSET(bp)\
+ (((bp)->state==Bractive)?\
+ (bp)->offset + (bp)->icount:\
+ (((bp)->state==Bwactive)?\
+ (bp)->offset + ((bp)->bsize + (bp)->ocount):\
+ -1))
+#define BLINELEN(bp)\
+ (bp)->rdline
+#define BFILDES(bp)\
+ (bp)->fid
+
+int Bbuffered(Biobuf*);
+Biobuf* Bfdopen(int, int);
+int Bfildes(Biobuf*);
+int Bflush(Biobuf*);
+int Bgetc(Biobuf*);
+int Bgetd(Biobuf*, double*);
+long Bgetrune(Biobuf*);
+int Binit(Biobuf*, int, int);
+int Binits(Biobuf*, int, int, unsigned char*, int);
+int Blinelen(Biobuf*);
+off_t Boffset(Biobuf*);
+Biobuf* Bopen(const char*, int);
+int Bprint(Biobuf*, const char*, ...);
+int Bputc(Biobuf*, int);
+int Bputrune(Biobuf*, long);
+void* Brdline(Biobuf*, int);
+char* Brdstr(Biobuf*, int, int);
+long Bread(Biobuf*, void*, long);
+off_t Bseek(Biobuf*, off_t, int);
+int Bterm(Biobuf*);
+int Bungetc(Biobuf*);
+int Bungetrune(Biobuf*);
+long Bwrite(Biobuf*, void*, long);
+int Bvprint(Biobuf*, const char*, va_list);
+
+#endif
diff --git a/include/clientutil.h b/include/clientutil.h
new file mode 100644
index 0000000..eeba43c
--- /dev/null
+++ b/include/clientutil.h
@@ -0,0 +1,9 @@
+#ifndef CLIENTEXTERN
+# define CLIENTEXTERN extern
+#endif
+
+char* readctl(char*);
+void client_init(char*);
+
+CLIENTEXTERN IxpClient* client;
+
diff --git a/include/fmt.h b/include/fmt.h
new file mode 100644
index 0000000..4b3a98a
--- /dev/null
+++ b/include/fmt.h
@@ -0,0 +1,155 @@
+#ifndef _FMT_H_
+#define _FMT_H_ 1
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
+ * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+
+#include <stdarg.h>
+#include <utf.h>
+
+typedef struct Fmt Fmt;
+struct Fmt{
+ unsigned char runes; /* output buffer is runes or chars? */
+ void *start; /* of buffer */
+ void *to; /* current place in the buffer */
+ void *stop; /* end of the buffer; overwritten if flush fails */
+ int (*flush)(Fmt *); /* called when to == stop */
+ void *farg; /* to make flush a closure */
+ int nfmt; /* num chars formatted so far */
+ va_list args; /* args passed to dofmt */
+ int r; /* % format Rune */
+ int width;
+ int prec;
+ unsigned long flags;
+};
+
+enum{
+ FmtWidth = 1,
+ FmtLeft = FmtWidth << 1,
+ FmtPrec = FmtLeft << 1,
+ FmtSharp = FmtPrec << 1,
+ FmtSpace = FmtSharp << 1,
+ FmtSign = FmtSpace << 1,
+ FmtZero = FmtSign << 1,
+ FmtUnsigned = FmtZero << 1,
+ FmtShort = FmtUnsigned << 1,
+ FmtLong = FmtShort << 1,
+ FmtVLong = FmtLong << 1,
+ FmtComma = FmtVLong << 1,
+ FmtByte = FmtComma << 1,
+ FmtLDouble = FmtByte << 1,
+
+ FmtFlag = FmtLDouble << 1
+};
+
+extern int (*fmtdoquote)(int);
+
+#ifdef VARARGCK
+/* *sigh* */
+ typedef unsigned char _fmt_uchar;
+ typedef unsigned short _fmt_ushort;
+ typedef unsigned int _fmt_uint;
+ typedef unsigned long _fmt_ulong;
+ typedef unsigned long long _fmt_uvlong;
+ typedef long long _fmt_vlong;
+# pragma varargck argpos fmtprint 2
+# pragma varargck argpos fprint 2
+# pragma varargck argpos print 1
+# pragma varargck argpos runeseprint 3
+# pragma varargck argpos runesmprint 1
+# pragma varargck argpos runesnprint 3
+# pragma varargck argpos runesprint 2
+# pragma varargck argpos seprint 3
+# pragma varargck argpos smprint 1
+# pragma varargck argpos snprint 3
+# pragma varargck argpos sprint 2
+
+# pragma varargck type "lld" _fmt_vlong
+# pragma varargck type "llx" _fmt_vlong
+# pragma varargck type "lld" _fmt_uvlong
+# pragma varargck type "llx" _fmt_uvlong
+# pragma varargck type "ld" long
+# pragma varargck type "lx" long
+# pragma varargck type "lb" long
+# pragma varargck type "ld" _fmt_ulong
+# pragma varargck type "lx" _fmt_ulong
+# pragma varargck type "lb" _fmt_ulong
+# pragma varargck type "d" int
+# pragma varargck type "x" int
+# pragma varargck type "c" int
+# pragma varargck type "C" int
+# pragma varargck type "b" int
+# pragma varargck type "d" _fmt_uint
+# pragma varargck type "x" _fmt_uint
+# pragma varargck type "c" _fmt_uint
+# pragma varargck type "C" _fmt_uint
+# pragma varargck type "b" _fmt_uint
+# pragma varargck type "f" double
+# pragma varargck type "e" double
+# pragma varargck type "g" double
+# pragma varargck type "s" char*
+# pragma varargck type "q" char*
+# pragma varargck type "S" Rune*
+# pragma varargck type "Q" Rune*
+# pragma varargck type "r" void
+# pragma varargck type "%" void
+# pragma varargck type "n" int*
+# pragma varargck type "p" uintptr_t
+# pragma varargck type "p" void*
+# pragma varargck flag ','
+# pragma varargck flag 'h'
+# pragma varargck type "<" void*
+# pragma varargck type "[" void*
+# pragma varargck type "H" void*
+# pragma varargck type "lH" void*
+#endif
+
+/* Edit .+1,/^$/ | cfn $PLAN9/src/lib9/fmt/?*.c | grep -v static |grep -v __ */
+int dofmt(Fmt*, const char *fmt);
+int dorfmt(Fmt*, const Rune *fmt);
+double fmtcharstod(int(*f)(void*), void*);
+int fmtfdflush(Fmt*);
+int fmtfdinit(Fmt*, int fd, char *buf, int size);
+int fmtinstall(int, int (*f)(Fmt*));
+int fmtprint(Fmt*, const char*, ...);
+int fmtrune(Fmt*, int);
+int fmtrunestrcpy(Fmt*, Rune*);
+int fmtstrcpy(Fmt*, const char*);
+char* fmtstrflush(Fmt*);
+int fmtstrinit(Fmt*);
+double fmtstrtod(const char*, char**);
+int fmtvprint(Fmt*, const char*, va_list);
+int fprint(int, const char*, ...);
+int print(const char*, ...);
+void quotefmtinstall(void);
+int quoterunestrfmt(Fmt*);
+int quotestrfmt(Fmt*);
+Rune* runefmtstrflush(Fmt*);
+int runefmtstrinit(Fmt*);
+Rune* runeseprint(Rune*,Rune*, const char*, ...);
+Rune* runesmprint(const char*, ...);
+int runesnprint(Rune*, int, const char*, ...);
+int runesprint(Rune*, const char*, ...);
+Rune* runevseprint(Rune*, Rune *, const char*, va_list);
+Rune* runevsmprint(const char*, va_list);
+int runevsnprint(Rune*, int, const char*, va_list);
+char* seprint(char*, char*, const char*, ...);
+char* smprint(const char*, ...);
+int snprint(char*, int, const char *, ...);
+int sprint(char*, const char*, ...);
+int vfprint(int, const char*, va_list);
+char* vseprint(char*, char*, const char*, va_list);
+char* vsmprint(const char*, va_list);
+int vsnprint(char*, int, const char*, va_list);
+
+#endif
diff --git a/include/ixp.h b/include/ixp.h
new file mode 100644
index 0000000..128d930
--- /dev/null
+++ b/include/ixp.h
@@ -0,0 +1,704 @@
+/* Copyright ©2004-2006 Anselm R. Garbe <garbeam at gmail dot com>
+ * Copyright ©2006-2007 Kris Maglione <fbsdaemon@gmail.com>
+ * See LICENSE file for license details.
+ */
+
+#include <stdarg.h>
+#include <sys/types.h>
+#include <sys/select.h>
+
+#define IXP_API 116
+
+/* Gunk */
+#if defined(IXP_NEEDAPI) && IXP_API < IXP_NEEDAPI
+# error A newer version of libixp is needed for this compilation.
+#endif
+#if defined(IXP_MAXAPI) && IXP_API > IXP_MAXAPI
+# warning This version of libixp has a newer API than this compilation requires.
+#endif
+
+#undef uchar
+#undef ushort
+#undef uint
+#undef ulong
+#undef vlong
+#undef uvlong
+#define uchar _ixpuchar
+#define ushort _ixpushort
+#define uint _ixpuint
+#define ulong _ixpulong
+#define vlong _ixpvlong
+#define uvlong _ixpuvlong
+
+#ifdef KENC
+# define STRUCT(x) struct {x};
+# define UNION(x) union {x};
+#elif defined(__GNUC__)
+# define STRUCT(x) __extension__ struct {x};
+# define UNION(x) __extension__ union {x};
+#else
+# define IXP_NEEDAPI 89
+# define STRUCT(x) x
+# define UNION(x) x
+#endif
+/* End Gunk */
+
+typedef unsigned char uchar;
+typedef unsigned short ushort;
+typedef unsigned int uint;
+typedef unsigned long ulong;
+typedef unsigned long long uvlong;
+
+typedef long long vlong;
+
+#define IXP_VERSION "9P2000"
+#define IXP_NOTAG ((ushort)~0) /* Dummy tag */
+#define IXP_NOFID (~0U)
+
+enum {
+ IXP_MAX_VERSION = 32,
+ IXP_MAX_MSG = 8192,
+ IXP_MAX_ERROR = 128,
+ IXP_MAX_CACHE = 32,
+ IXP_MAX_FLEN = 128,
+ IXP_MAX_ULEN = 32,
+ IXP_MAX_WELEM = 16,
+};
+
+/* 9P message types */
+enum { P9_TVersion = 100,
+ P9_RVersion,
+ P9_TAuth = 102,
+ P9_RAuth,
+ P9_TAttach = 104,
+ P9_RAttach,
+ P9_TError = 106, /* illegal */
+ P9_RError,
+ P9_TFlush = 108,
+ P9_RFlush,
+ P9_TWalk = 110,
+ P9_RWalk,
+ P9_TOpen = 112,
+ P9_ROpen,
+ P9_TCreate = 114,
+ P9_RCreate,
+ P9_TRead = 116,
+ P9_RRead,
+ P9_TWrite = 118,
+ P9_RWrite,
+ P9_TClunk = 120,
+ P9_RClunk,
+ P9_TRemove = 122,
+ P9_RRemove,
+ P9_TStat = 124,
+ P9_RStat,
+ P9_TWStat = 126,
+ P9_RWStat,
+};
+
+/* from libc.h in p9p */
+enum { P9_OREAD = 0, /* open for read */
+ P9_OWRITE = 1, /* write */
+ P9_ORDWR = 2, /* read and write */
+ P9_OEXEC = 3, /* execute, == read but check execute permission */
+ P9_OTRUNC = 16, /* or'ed in (except for exec), truncate file first */
+ P9_OCEXEC = 32, /* or'ed in, close on exec */
+ P9_ORCLOSE = 64, /* or'ed in, remove on close */
+ P9_ODIRECT = 128, /* or'ed in, direct access */
+ P9_ONONBLOCK = 256, /* or'ed in, non-blocking call */
+ P9_OEXCL = 0x1000, /* or'ed in, exclusive use (create only) */
+ P9_OLOCK = 0x2000, /* or'ed in, lock after opening */
+ P9_OAPPEND = 0x4000 /* or'ed in, append only */
+};
+
+/* bits in Qid.type */
+enum { P9_QTDIR = 0x80, /* type bit for directories */
+ P9_QTAPPEND = 0x40, /* type bit for append only files */
+ P9_QTEXCL = 0x20, /* type bit for exclusive use files */
+ P9_QTMOUNT = 0x10, /* type bit for mounted channel */
+ P9_QTAUTH = 0x08, /* type bit for authentication file */
+ P9_QTTMP = 0x04, /* type bit for non-backed-up file */
+ P9_QTSYMLINK = 0x02, /* type bit for symbolic link */
+ P9_QTFILE = 0x00 /* type bits for plain file */
+};
+
+/* bits in Dir.mode */
+enum {
+ P9_DMEXEC = 0x1, /* mode bit for execute permission */
+ P9_DMWRITE = 0x2, /* mode bit for write permission */
+ P9_DMREAD = 0x4, /* mode bit for read permission */
+};
+
+/* Larger than int, can't be enum */
+#define P9_DMDIR 0x80000000 /* mode bit for directories */
+#define P9_DMAPPEND 0x40000000 /* mode bit for append only files */
+#define P9_DMEXCL 0x20000000 /* mode bit for exclusive use files */
+#define P9_DMMOUNT 0x10000000 /* mode bit for mounted channel */
+#define P9_DMAUTH 0x08000000 /* mode bit for authentication file */
+#define P9_DMTMP 0x04000000 /* mode bit for non-backed-up file */
+#define P9_DMSYMLINK 0x02000000 /* mode bit for symbolic link (Unix, 9P2000.u) */
+#define P9_DMDEVICE 0x00800000 /* mode bit for device file (Unix, 9P2000.u) */
+#define P9_DMNAMEDPIPE 0x00200000 /* mode bit for named pipe (Unix, 9P2000.u) */
+#define P9_DMSOCKET 0x00100000 /* mode bit for socket (Unix, 9P2000.u) */
+#define P9_DMSETUID 0x00080000 /* mode bit for setuid (Unix, 9P2000.u) */
+#define P9_DMSETGID 0x00040000 /* mode bit for setgid (Unix, 9P2000.u) */
+
+#ifdef IXP_NO_P9_
+# define TVersion P9_TVersion
+# define RVersion P9_RVersion
+# define TAuth P9_TAuth
+# define RAuth P9_RAuth
+# define TAttach P9_TAttach
+# define RAttach P9_RAttach
+# define TError P9_TError
+# define RError P9_RError
+# define TFlush P9_TFlush
+# define RFlush P9_RFlush
+# define TWalk P9_TWalk
+# define RWalk P9_RWalk
+# define TOpen P9_TOpen
+# define ROpen P9_ROpen
+# define TCreate P9_TCreate
+# define RCreate P9_RCreate
+# define TRead P9_TRead
+# define RRead P9_RRead
+# define TWrite P9_TWrite
+# define RWrite P9_RWrite
+# define TClunk P9_TClunk
+# define RClunk P9_RClunk
+# define TRemove P9_TRemove
+# define RRemove P9_RRemove
+# define TStat P9_TStat
+# define RStat P9_RStat
+# define TWStat P9_TWStat
+# define RWStat P9_RWStat
+#
+# define OREAD P9_OREAD
+# define OWRITE P9_OWRITE
+# define ORDWR P9_ORDWR
+# define OEXEC P9_OEXEC
+# define OTRUNC P9_OTRUNC
+# define OCEXEC P9_OCEXEC
+# define ORCLOSE P9_ORCLOSE
+# define ODIRECT P9_ODIRECT
+# define ONONBLOCK P9_ONONBLOCK
+# define OEXCL P9_OEXCL
+# define OLOCK P9_OLOCK
+# define OAPPEND P9_OAPPEND
+#
+# define QTDIR P9_QTDIR
+# define QTAPPEND P9_QTAPPEND
+# define QTEXCL P9_QTEXCL
+# define QTMOUNT P9_QTMOUNT
+# define QTAUTH P9_QTAUTH
+# define QTTMP P9_QTTMP
+# define QTSYMLINK P9_QTSYMLINK
+# define QTFILE P9_QTFILE
+# define DMDIR P9_DMDIR
+# define DMAPPEND P9_DMAPPEND
+# define DMEXCL P9_DMEXCL
+# define DMMOUNT P9_DMMOUNT
+# define DMAUTH P9_DMAUTH
+# define DMTMP P9_DMTMP
+#
+# define DMSYMLINK P9_DMSYMLINK
+# define DMDEVICE P9_DMDEVICE
+# define DMNAMEDPIPE P9_DMNAMEDPIPE
+# define DMSOCKET P9_DMSOCKET
+# define DMSETUID P9_DMSETUID
+# define DMSETGID P9_DMSETGID
+#endif
+
+#ifdef IXP_P9_STRUCTS
+# define IxpFcall Fcall
+# define IxpFid Fid
+# define IxpQid Qid
+# define IxpStat Stat
+#endif
+
+typedef struct IxpMap IxpMap;
+typedef struct Ixp9Conn Ixp9Conn;
+typedef struct Ixp9Req Ixp9Req;
+typedef struct Ixp9Srv Ixp9Srv;
+typedef struct IxpCFid IxpCFid;
+typedef struct IxpClient IxpClient;
+typedef struct IxpConn IxpConn;
+typedef struct IxpFid IxpFid;
+typedef struct IxpMsg IxpMsg;
+typedef struct IxpQid IxpQid;
+typedef struct IxpRpc IxpRpc;
+typedef struct IxpServer IxpServer;
+typedef struct IxpStat IxpStat;
+typedef struct IxpTimer IxpTimer;
+
+typedef struct IxpMutex IxpMutex;
+typedef struct IxpRWLock IxpRWLock;
+typedef struct IxpRendez IxpRendez;
+typedef struct IxpThread IxpThread;
+
+/* Threading */
+enum {
+ IXP_ERRMAX = IXP_MAX_ERROR,
+};
+
+struct IxpMutex {
+ void* aux;
+};
+
+struct IxpRWLock {
+ void* aux;
+};
+
+struct IxpRendez {
+ IxpMutex* mutex;
+ void* aux;
+};
+
+enum { MsgPack, MsgUnpack, };
+struct IxpMsg {
+ char* data;
+ char* pos;
+ char* end;
+ uint size;
+ uint mode;
+};
+
+struct IxpQid {
+ uchar type;
+ ulong version;
+ uvlong path;
+ /* internal use only */
+ uchar dir_type;
+};
+
+/* stat structure */
+struct IxpStat {
+ ushort type;
+ ulong dev;
+ IxpQid qid;
+ ulong mode;
+ ulong atime;
+ ulong mtime;
+ uvlong length;
+ char* name;
+ char* uid;
+ char* gid;
+ char* muid;
+};
+
+typedef struct IxpFHdr IxpFHdr;
+typedef struct IxpFError IxpFError;
+typedef struct IxpFROpen IxpFRAttach;
+typedef struct IxpFRAuth IxpFRAuth;
+typedef struct IxpFROpen IxpFRCreate;
+typedef struct IxpFROpen IxpFROpen;
+typedef struct IxpFIO IxpFRRead;
+typedef struct IxpFRStat IxpFRStat;
+typedef struct IxpFVersion IxpFRVersion;
+typedef struct IxpFRWalk IxpFRWalk;
+typedef struct IxpFAttach IxpFTAttach;
+typedef struct IxpFAttach IxpFTAuth;
+typedef struct IxpFTCreate IxpFTCreate;
+typedef struct IxpFTFlush IxpFTFlush;
+typedef struct IxpFTCreate IxpFTOpen;
+typedef struct IxpFIO IxpFTRead;
+typedef struct IxpFVersion IxpFTVersion;
+typedef struct IxpFTWalk IxpFTWalk;
+typedef struct IxpFIO IxpFTWrite;
+typedef struct IxpFTWStat IxpFTWStat;
+typedef struct IxpFAttach IxpFAttach;
+typedef struct IxpFIO IxpFIO;
+typedef struct IxpFVersion IxpFVersion;
+
+struct IxpFHdr {
+ uchar type;
+ ushort tag;
+ ulong fid;
+};
+struct IxpFVersion {
+ IxpFHdr hdr;
+ ulong msize;
+ char* version;
+};
+struct IxpFTFlush {
+ IxpFHdr hdr;
+ ushort oldtag;
+};
+struct IxpFError {
+ IxpFHdr hdr;
+ char* ename;
+};
+struct IxpFROpen {
+ IxpFHdr hdr;
+ IxpQid qid; /* +Rattach */
+ ulong iounit;
+};
+struct IxpFRAuth {
+ IxpFHdr hdr;
+ IxpQid aqid;
+};
+struct IxpFAttach {
+ IxpFHdr hdr;
+ ulong afid;
+ char* uname;
+ char* aname;
+};
+struct IxpFTCreate {
+ IxpFHdr hdr;
+ ulong perm;
+ char* name;
+ uchar mode; /* +Topen */
+};
+struct IxpFTWalk {
+ IxpFHdr hdr;
+ ulong newfid;
+ ushort nwname;
+ char* wname[IXP_MAX_WELEM];
+};
+struct IxpFRWalk {
+ IxpFHdr hdr;
+ ushort nwqid;
+ IxpQid wqid[IXP_MAX_WELEM];
+};
+struct IxpFIO {
+ IxpFHdr hdr;
+ uvlong offset; /* Tread, Twrite */
+ ulong count; /* Tread, Twrite, Rread */
+ char* data; /* Twrite, Rread */
+};
+struct IxpFRStat {
+ IxpFHdr hdr;
+ ushort nstat;
+ uchar* stat;
+};
+struct IxpFTWStat {
+ IxpFHdr hdr;
+ IxpStat stat;
+};
+#if defined(IXP_NEEDAPI) && IXP_NEEDAPI <= 89
+/* from fcall(3) in plan9port */
+typedef struct IxpFcall IxpFcall;
+struct IxpFcall {
+ uchar type;
+ ushort tag;
+ ulong fid;
+
+ UNION (
+ STRUCT ( /* Tversion, Rversion */
+ ulong msize;
+ char *version;
+ )
+ STRUCT ( /* Tflush */
+ ushort oldtag;
+ )
+ STRUCT ( /* Rerror */
+ char *ename;
+ )
+ STRUCT ( /* Ropen, Rcreate */
+ IxpQid qid; /* +Rattach */
+ ulong iounit;
+ )
+ STRUCT ( /* Rauth */
+ IxpQid aqid;
+ )
+ STRUCT ( /* Tauth, Tattach */
+ ulong afid;
+ char *uname;
+ char *aname;
+ )
+ STRUCT ( /* Tcreate */
+ ulong perm;
+ char *name;
+ uchar mode; /* +Topen */
+ )
+ STRUCT ( /* Twalk */
+ ulong newfid;
+ ushort nwname;
+ char *wname[IXP_MAX_WELEM];
+ )
+ STRUCT ( /* Rwalk */
+ ushort nwqid;
+ IxpQid wqid[IXP_MAX_WELEM];
+ )
+ STRUCT (
+ uvlong offset; /* Tread, Twrite */
+ ulong count; /* Tread, Twrite, Rread */
+ char *data; /* Twrite, Rread */
+ )
+ STRUCT ( /* Rstat */
+ ushort nstat;
+ uchar *stat;
+ )
+ STRUCT ( /* Twstat */
+ IxpStat st;
+ )
+ )
+};
+#else
+typedef union IxpFcall IxpFcall;
+union IxpFcall {
+ IxpFHdr hdr;
+ IxpFVersion version;
+ IxpFVersion tversion;
+ IxpFVersion rversion;
+ IxpFTFlush tflush;
+ IxpFROpen ropen;
+ IxpFROpen rcreate;
+ IxpFROpen rattach;
+ IxpFError error;
+ IxpFRAuth rauth;
+ IxpFAttach tattach;
+ IxpFAttach tauth;
+ IxpFTCreate tcreate;
+ IxpFTCreate topen;
+ IxpFTWalk twalk;
+ IxpFRWalk rwalk;
+ IxpFTWStat twstat;
+ IxpFRStat rstat;
+ IxpFIO twrite;
+ IxpFIO rwrite;
+ IxpFIO tread;
+ IxpFIO rread;
+ IxpFIO io;
+};
+#endif
+
+struct IxpConn {
+ IxpServer* srv;
+ void* aux;
+ int fd;
+ void (*read)(IxpConn *);
+ void (*close)(IxpConn *);
+ char closed;
+
+ /* Implementation details, do not use */
+ IxpConn *next;
+};
+
+struct IxpServer {
+ IxpConn* conn;
+ IxpMutex lk;
+ IxpTimer* timer;
+ void (*preselect)(IxpServer*);
+ void* aux;
+ int running;
+ int maxfd;
+ fd_set rd;
+};
+
+struct IxpRpc {
+ IxpClient* mux;
+ IxpRpc* next;
+ IxpRpc* prev;
+ IxpRendez r;
+ uint tag;
+ IxpFcall* p;
+ int waiting;
+ int async;
+};
+
+struct IxpClient {
+ int fd;
+ uint msize;
+ uint lastfid;
+
+ /* Implementation details */
+ uint nwait;
+ uint mwait;
+ uint freetag;
+ IxpCFid* freefid;
+ IxpMsg rmsg;
+ IxpMsg wmsg;
+ IxpMutex lk;
+ IxpMutex rlock;
+ IxpMutex wlock;
+ IxpRendez tagrend;
+ IxpRpc** wait;
+ IxpRpc* muxer;
+ IxpRpc sleep;
+ int mintag;
+ int maxtag;
+};
+
+struct IxpCFid {
+ uint fid;
+ IxpQid qid;
+ uchar mode;
+ uint open;
+ uint iounit;
+ uvlong offset;
+ IxpClient* client;
+ /* internal use only */
+ IxpCFid* next;
+ IxpMutex iolock;
+};
+
+struct IxpFid {
+ char* uid;
+ void* aux;
+ ulong fid;
+ IxpQid qid;
+ signed char omode;
+ uint iounit;
+
+ /* Implementation details */
+ Ixp9Conn* conn;
+ IxpMap* map;
+};
+
+struct Ixp9Req {
+ Ixp9Srv* srv;
+ IxpFid* fid;
+ IxpFid* newfid;
+ Ixp9Req* oldreq;
+ IxpFcall ifcall;
+ IxpFcall ofcall;
+ void* aux;
+
+ /* Implementation details */
+ Ixp9Conn *conn;
+};
+
+struct Ixp9Srv {
+ void* aux;
+ void (*attach)(Ixp9Req *r);
+ void (*clunk)(Ixp9Req *r);
+ void (*create)(Ixp9Req *r);
+ void (*flush)(Ixp9Req *r);
+ void (*open)(Ixp9Req *r);
+ void (*read)(Ixp9Req *r);
+ void (*remove)(Ixp9Req *r);
+ void (*stat)(Ixp9Req *r);
+ void (*walk)(Ixp9Req *r);
+ void (*write)(Ixp9Req *r);
+ void (*wstat)(Ixp9Req *r);
+ void (*freefid)(IxpFid *f);
+};
+
+struct IxpThread {
+ /* RWLock */
+ int (*initrwlock)(IxpRWLock*);
+ void (*rlock)(IxpRWLock*);
+ int (*canrlock)(IxpRWLock*);
+ void (*runlock)(IxpRWLock*);
+ void (*wlock)(IxpRWLock*);
+ int (*canwlock)(IxpRWLock*);
+ void (*wunlock)(IxpRWLock*);
+ void (*rwdestroy)(IxpRWLock*);
+ /* Mutex */
+ int (*initmutex)(IxpMutex*);
+ void (*lock)(IxpMutex*);
+ int (*canlock)(IxpMutex*);
+ void (*unlock)(IxpMutex*);
+ void (*mdestroy)(IxpMutex*);
+ /* Rendez */
+ int (*initrendez)(IxpRendez*);
+ void (*sleep)(IxpRendez*);
+ int (*wake)(IxpRendez*);
+ int (*wakeall)(IxpRendez*);
+ void (*rdestroy)(IxpRendez*);
+ /* Other */
+ char *(*errbuf)(void);
+ ssize_t (*read)(int, void*, size_t);
+ ssize_t (*write)(int, const void*, size_t);
+ int (*select)(int, fd_set*, fd_set*, fd_set*, struct timeval*);
+};
+
+extern IxpThread *ixp_thread;
+extern int (*ixp_vsnprint)(char*, int, const char*, va_list);
+extern char* (*ixp_vsmprint)(const char*, va_list);
+extern void (*ixp_printfcall)(IxpFcall*);
+
+/* thread_*.c */
+int ixp_taskinit(void);
+int ixp_rubyinit(void);
+int ixp_pthread_init(void);
+
+#ifdef VARARGCK
+# pragma varargck argpos ixp_print 2
+# pragma varargck argpos ixp_werrstr 1
+# pragma varargck argpos ixp_eprint 1
+#endif
+
+/* client.c */
+int ixp_close(IxpCFid*);
+long ixp_pread(IxpCFid*, void*, long, vlong);
+int ixp_print(IxpCFid*, const char*, ...);
+long ixp_pwrite(IxpCFid*, const void*, long, vlong);
+long ixp_read(IxpCFid*, void*, long);
+int ixp_remove(IxpClient*, const char*);
+void ixp_unmount(IxpClient*);
+int ixp_vprint(IxpCFid*, const char*, va_list);
+long ixp_write(IxpCFid*, const void*, long);
+IxpCFid* ixp_create(IxpClient*, const char*, uint perm, uchar mode);
+IxpStat* ixp_fstat(IxpCFid*);
+IxpClient* ixp_mount(const char*);
+IxpClient* ixp_mountfd(int);
+IxpClient* ixp_nsmount(const char*);
+IxpCFid* ixp_open(IxpClient*, const char*, uchar);
+IxpStat* ixp_stat(IxpClient*, const char*);
+
+/* convert.c */
+void ixp_pu8(IxpMsg*, uchar*);
+void ixp_pu16(IxpMsg*, ushort*);
+void ixp_pu32(IxpMsg*, ulong*);
+void ixp_pu64(IxpMsg*, uvlong*);
+void ixp_pdata(IxpMsg*, char**, uint);
+void ixp_pstring(IxpMsg*, char**);
+void ixp_pstrings(IxpMsg*, ushort*, char**);
+void ixp_pqid(IxpMsg*, IxpQid*);
+void ixp_pqids(IxpMsg*, ushort*, IxpQid*);
+void ixp_pstat(IxpMsg*, IxpStat*);
+void ixp_pfcall(IxpMsg*, IxpFcall*);
+
+/* error.h */
+char* ixp_errbuf(void);
+void ixp_errstr(char*, int);
+void ixp_rerrstr(char*, int);
+void ixp_werrstr(const char*, ...);
+
+/* request.c */
+void respond(Ixp9Req*, const char *err);
+void serve_9pcon(IxpConn*);
+
+/* message.c */
+ushort ixp_sizeof_stat(IxpStat*);
+IxpMsg ixp_message(char*, uint len, uint mode);
+void ixp_freestat(IxpStat*);
+void ixp_freefcall(IxpFcall*);
+uint ixp_msg2fcall(IxpMsg*, IxpFcall*);
+uint ixp_fcall2msg(IxpMsg*, IxpFcall*);
+
+/* server.c */
+IxpConn* ixp_listen(IxpServer*, int, void*,
+ void (*read)(IxpConn*),
+ void (*close)(IxpConn*));
+void ixp_hangup(IxpConn*);
+int ixp_serverloop(IxpServer*);
+void ixp_server_close(IxpServer*);
+
+/* socket.c */
+int ixp_dial(const char*);
+int ixp_announce(const char*);
+
+/* transport.c */
+uint ixp_sendmsg(int, IxpMsg*);
+uint ixp_recvmsg(int, IxpMsg*);
+
+/* timer.c */
+long ixp_msec(void);
+long ixp_settimer(IxpServer*, long, void (*)(long, void*), void*);
+int ixp_unsettimer(IxpServer*, long);
+
+/* util.c */
+void ixp_cleanname(char*);
+void* ixp_emalloc(uint);
+void* ixp_emallocz(uint);
+void ixp_eprint(const char*, ...);
+void* ixp_erealloc(void*, uint);
+char* ixp_estrdup(const char*);
+char* ixp_namespace(void);
+char* ixp_smprint(const char*, ...);
+uint ixp_strlcat(char*, const char*, uint);
+uint ixp_tokenize(char**, uint len, char*, char);
+
diff --git a/include/ixp_local.h b/include/ixp_local.h
new file mode 100644
index 0000000..1d747e7
--- /dev/null
+++ b/include/ixp_local.h
@@ -0,0 +1,91 @@
+#define IXP_NO_P9_
+#define IXP_P9_STRUCTS
+#include <ixp.h>
+#include <stdbool.h>
+
+char *argv0;
+#define ARGBEGIN \
+ int _argtmp=0, _inargv=0; char *_argv=nil; \
+ if(!argv0) {argv0=*argv; argv++, argc--;} \
+ _inargv=1; USED(_inargv); \
+ while(argc && argv[0][0] == '-') { \
+ _argv=&argv[0][1]; argv++; argc--; \
+ if(_argv[0] == '-' && _argv[1] == '\0') \
+ break; \
+ while(*_argv) switch(*_argv++)
+#define ARGEND }_inargv=0;USED(_argtmp, _argv, _inargv)
+
+#define EARGF(f) ((_inargv && *_argv) ? \
+ (_argtmp=strlen(_argv), _argv+=_argtmp, _argv-_argtmp) \
+ : ((argc > 0) ? \
+ (--argc, ++argv, _used(argc), *(argv-1)) \
+ : ((f), (char*)0)))
+#define ARGF() EARGF(_used(0))
+
+#ifndef KENC
+ static inline void _used(long a, ...) { if(a){} }
+# define USED(...) _used((long)__VA_ARGS__)
+# define SET(x) (x = 0)
+/* # define SET(x) USED(&x) GCC 4 is 'too smart' for this. */
+#endif
+
+#undef nil
+#define nil ((void*)0)
+#define nelem(ary) (sizeof(ary) / sizeof(*ary))
+
+#define thread ixp_thread
+
+#define eprint ixp_eprint
+#define emalloc ixp_emalloc
+#define emallocz ixp_emallocz
+#define estrdup ixp_estrdup
+#define erealloc ixp_erealloc
+#define strlcat ixp_strlcat
+#define tokenize ixp_tokenize
+
+#define muxinit ixp_muxinit
+#define muxfree ixp_muxfree
+#define muxrpc ixp_muxrpc
+
+#define errstr ixp_errstr
+#define rerrstr ixp_rerrstr
+#define werrstr ixp_werrstr
+
+typedef struct IxpMap Map;
+typedef struct MapEnt MapEnt;
+
+typedef IxpTimer Timer;
+
+typedef struct timeval timeval;
+
+struct IxpMap {
+ MapEnt** bucket;
+ int nhash;
+
+ IxpRWLock lock;
+};
+
+struct IxpTimer {
+ Timer* link;
+ long msec;
+ long id;
+ void (*fn)(long, void*);
+ void* aux;
+};
+
+/* map.c */
+void ixp_mapfree(Map*, void(*)(void*));
+void ixp_mapexec(Map*, void(*)(void*, void*), void*);
+void ixp_mapinit(Map*, MapEnt**, int);
+bool ixp_mapinsert(Map*, ulong, void*, bool);
+void* ixp_mapget(Map*, ulong);
+void* ixp_maprm(Map*, ulong);
+
+/* mux.c */
+void muxfree(IxpClient*);
+void muxinit(IxpClient*);
+Fcall* muxrpc(IxpClient*, Fcall*);
+
+/* timer.c */
+long ixp_nexttimer(IxpServer*);
+
diff --git a/include/ixp_srvutil.h b/include/ixp_srvutil.h
new file mode 100644
index 0000000..2b29a01
--- /dev/null
+++ b/include/ixp_srvutil.h
@@ -0,0 +1,69 @@
+
+typedef struct IxpDirtab IxpDirtab;
+typedef struct IxpFileId IxpFileId;
+typedef struct IxpPendingLink IxpPendingLink;
+typedef struct IxpPending IxpPending;
+typedef struct IxpQueue IxpQueue;
+typedef struct IxpRequestLink IxpRequestLink;
+
+typedef IxpFileId* (*IxpLookupFn)(IxpFileId*, char*);
+
+struct IxpPendingLink {
+ IxpPendingLink* next;
+ IxpPendingLink* prev;
+ IxpFid* fid;
+ IxpQueue* queue;
+ IxpPending* pending;
+};
+
+struct IxpRequestLink {
+ IxpRequestLink* next;
+ IxpRequestLink* prev;
+ Ixp9Req* req;
+};
+
+struct IxpPending {
+ IxpRequestLink req;
+ IxpPendingLink fids;
+};
+
+struct IxpDirtab {
+ char* name;
+ uchar qtype;
+ uint type;
+ uint perm;
+ uint flags;
+};
+
+struct IxpFileId {
+ IxpFileId* next;
+ IxpFileIdU p;
+ bool pending;
+ uint id;
+ uint index;
+ IxpDirtab tab;
+ ushort nref;
+ uchar volatil;
+};
+
+enum {
+ FLHide = 1,
+};
+
+bool ixp_pending_clunk(Ixp9Req*);
+void ixp_pending_flush(Ixp9Req*);
+void ixp_pending_pushfid(IxpPending*, IxpFid*);
+void ixp_pending_respond(Ixp9Req*);
+void ixp_pending_write(IxpPending*, char*, long);
+IxpFileId* ixp_srv_clonefiles(IxpFileId*);
+void ixp_srv_data2cstring(Ixp9Req*);
+void ixp_srv_freefile(IxpFileId*);
+void ixp_srv_readbuf(Ixp9Req*, char*, uint);
+void ixp_srv_readdir(Ixp9Req*, IxpLookupFn, void (*)(IxpStat*, IxpFileId*));
+bool ixp_srv_verifyfile(IxpFileId*, IxpLookupFn);
+void ixp_srv_walkandclone(Ixp9Req*, IxpLookupFn);
+void ixp_srv_writebuf(Ixp9Req*, char**, uint*, uint);
+char* ixp_srv_writectl(Ixp9Req*, char* (*)(void*, IxpMsg*));
+IxpFileId* ixp_srv_getfile(void);
+
+
diff --git a/include/plan9.h b/include/plan9.h
new file mode 100644
index 0000000..da32b72
--- /dev/null
+++ b/include/plan9.h
@@ -0,0 +1,45 @@
+/*
+ * compiler directive on Plan 9
+ */
+
+/*
+ * easiest way to make sure these are defined
+ */
+#ifndef KENC
+# ifndef USED
+# define USED(x) if(x);else
+# endif
+#endif
+
+#define uchar _p9uchar
+#define ushort _p9ushort
+#define uint _p9uint
+#define ulong _p9ulong
+typedef unsigned char uchar;
+typedef unsigned short ushort;
+typedef unsigned int uint;
+typedef unsigned long ulong;
+typedef long long vlong;
+typedef unsigned long long uvlong;
+
+#include <utf.h>
+#include <stdint.h>
+#include <fmt.h>
+#include <string.h>
+#include <unistd.h>
+
+#define OREAD O_RDONLY
+#define OWRITE O_WRONLY
+
+#define OCEXEC 0
+#define ORCLOSE 0
+#define OTRUNC 0
+
+#define exits(x) exit(x && *x ? 1 : 0)
+
+#undef nil
+#define nil ((void*)0)
+
+#undef nelem
+#define nelem(x) (sizeof (x)/sizeof (x)[0])
+
diff --git a/include/regcomp.h b/include/regcomp.h
new file mode 100644
index 0000000..8d995b7
--- /dev/null
+++ b/include/regcomp.h
@@ -0,0 +1,71 @@
+/*
+ * substitution list
+ */
+
+#define NSUBEXP 32
+typedef struct Resublist Resublist;
+struct Resublist
+{
+ Resub m[NSUBEXP];
+};
+
+/* max character classes per program */
+extern Reprog RePrOg;
+#define NCLASS (sizeof(RePrOg.class)/sizeof(Reclass))
+
+/* max rune ranges per character class */
+#define NCCRUNE (sizeof(Reclass)/sizeof(Rune))
+
+/*
+ * Actions and Tokens (Reinst types)
+ *
+ * 02xx are operators, value == precedence
+ * 03xx are tokens, i.e. operands for operators
+ */
+#define RUNE 0177
+#define OPERATOR 0200 /* Bitmask of all operators */
+#define START 0200 /* Start, used for marker on stack */
+#define RBRA 0201 /* Right bracket, ) */
+#define LBRA 0202 /* Left bracket, ( */
+#define OR 0203 /* Alternation, | */
+#define CAT 0204 /* Concatentation, implicit operator */
+#define STAR 0205 /* Closure, * */
+#define PLUS 0206 /* a+ == aa* */
+#define QUEST 0207 /* a? == a|nothing, i.e. 0 or 1 a's */
+#define ANY 0300 /* Any character except newline, . */
+#define ANYNL 0301 /* Any character including newline, . */
+#define NOP 0302 /* No operation, internal use only */
+#define BOL 0303 /* Beginning of line, ^ */
+#define EOL 0304 /* End of line, $ */
+#define CCLASS 0305 /* Character class, [] */
+#define NCCLASS 0306 /* Negated character class, [] */
+#define END 0377 /* Terminate: match found */
+
+/*
+ * regexec execution lists
+ */
+#define LISTSIZE 10
+#define BIGLISTSIZE (10*LISTSIZE)
+typedef struct Relist Relist;
+struct Relist
+{
+ Reinst* inst; /* Reinstruction of the thread */
+ Resublist se; /* matched subexpressions in this thread */
+};
+typedef struct Reljunk Reljunk;
+struct Reljunk
+{
+ Relist* relist[2];
+ Relist* reliste[2];
+ int starttype;
+ Rune startchar;
+ char* starts;
+ char* eol;
+ Rune* rstarts;
+ Rune* reol;
+};
+
+extern Relist* _renewthread(Relist*, Reinst*, int, Resublist*);
+extern void _renewmatch(Resub*, int, Resublist*);
+extern Relist* _renewemptythread(Relist*, Reinst*, int, char*);
+extern Relist* _rrenewemptythread(Relist*, Reinst*, int, Rune*);
diff --git a/include/regexp9.h b/include/regexp9.h
new file mode 100644
index 0000000..99bfd54
--- /dev/null
+++ b/include/regexp9.h
@@ -0,0 +1,88 @@
+#ifndef _REGEXP9_H_
+#define _REGEXP9_H_ 1
+
+#ifdef AUTOLIB
+AUTOLIB(regexp9)
+#endif
+
+#include <utf.h>
+
+typedef struct Resub Resub;
+typedef struct Reclass Reclass;
+typedef struct Reinst Reinst;
+typedef struct Reprog Reprog;
+
+/*
+ * Sub expression matches
+ */
+struct Resub{
+ union {
+ char *sp;
+ Rune *rsp;
+ }s;
+ union {
+ char *ep;
+ Rune *rep;
+ }e;
+};
+
+/*
+ * character class, each pair of rune's defines a range
+ */
+struct Reclass{
+ Rune *end;
+ Rune spans[64];
+};
+
+/*
+ * Machine instructions
+ */
+struct Reinst{
+ int type;
+ union {
+ Reclass *cp; /* class pointer */
+ Rune r; /* character */
+ int subid; /* sub-expression id for RBRA and LBRA */
+ Reinst *right; /* right child of OR */
+ }u1;
+ union { /* regexp relies on these two being in the same union */
+ Reinst *left; /* left child of OR */
+ Reinst *next; /* next instruction for CAT & LBRA */
+ }u2;
+};
+
+/*
+ * Reprogram definition
+ */
+struct Reprog{
+ Reinst *startinst; /* start pc */
+ Reclass class[32]; /* .data */
+ Reinst firstinst[5]; /* .text */
+};
+
+extern Reprog *regcomp9(char*);
+extern Reprog *regcomplit9(char*);
+extern Reprog *regcompnl9(char*);
+extern void regerror9(char*);
+extern int regexec9(Reprog*, char*, Resub*, int);
+extern void regsub9(char*, char*, int, Resub*, int);
+
+extern int rregexec9(Reprog*, Rune*, Resub*, int);
+extern void rregsub9(Rune*, Rune*, int, Resub*, int);
+
+/*
+ * Darwin simply cannot handle having routines that
+ * override other library routines.
+ */
+#ifndef NOPLAN9DEFINES
+#define regcomp regcomp9
+#define regcomplit regcomplit9
+#define regcompnl regcompnl9
+#define regerror regerror9
+#define regexec regexec9
+#define regsub regsub9
+#define rregexec rregexec9
+#define rregsub rregsub9
+#endif
+
+#endif
diff --git a/include/utf.h b/include/utf.h
new file mode 100644
index 0000000..66a92d7
--- /dev/null
+++ b/include/utf.h
@@ -0,0 +1,47 @@
+#ifndef _UTF_H_
+#define _UTF_H_ 1
+
+typedef unsigned short Rune; /* 16 bits */
+
+enum
+{
+ UTFmax = 3, /* maximum bytes per rune */
+ Runesync = 0x80, /* cannot represent part of a UTF sequence (<) */
+ Runeself = 0x80, /* rune and UTF sequences are the same (<) */
+ Runeerror = 0xFFFD, /* decoding error in UTF */
+};
+
+/* Edit .+1,/^$/ | cfn $PLAN9/src/lib9/utf/?*.c | grep -v static |grep -v __ */
+int chartorune(Rune *rune, const char *str);
+int fullrune(const char *str, int n);
+int isalpharune(Rune);
+int islowerrune(Rune);
+int isspacerune(Rune);
+int istitlerune(Rune);
+int isupperrune(Rune);
+int runelen(Rune);
+int runenlen(const Rune*, int);
+Rune* runestrcat(Rune*, const Rune*);
+Rune* runestrchr(const Rune*, Rune);
+int runestrcmp(const Rune*, const Rune*);
+Rune* runestrcpy(Rune*, const Rune*);
+Rune* runestrdup(const Rune*) ;
+Rune* runestrecpy(Rune*, Rune *e, const Rune*);
+long runestrlen(const Rune*);
+Rune* runestrncat(Rune*, const Rune*, long);
+int runestrncmp(const Rune*, const Rune*, long);
+Rune* runestrncpy(Rune*, const Rune*, long);
+Rune* runestrrchr(const Rune*, Rune);
+Rune* runestrstr(const Rune*, const Rune*);
+int runetochar(char*, const Rune*);
+Rune tolowerrune(Rune);
+Rune totitlerune(Rune);
+Rune toupperrune(Rune);
+char* utfecpy(char*, char*, const char*);
+int utflen(const char*);
+int utfnlen(const char*, long);
+char* utfrrune(const char*, long);
+char* utfrune(const char*, long);
+char* utfutf(const char*, const char*);
+
+#endif
diff --git a/include/util.h b/include/util.h
new file mode 100644
index 0000000..6cfd604
--- /dev/null
+++ b/include/util.h
@@ -0,0 +1,91 @@
+/* Copyright ©2007-2010 Kris Maglione <fbsdaemon@gmail.com>
+ * See LICENSE file for license details.
+ */
+#define nil ((void*)0)
+#define nelem(ary) (sizeof(ary) / sizeof(*ary))
+
+/* Types */
+#undef uchar
+#undef ushort
+#undef uint
+#undef ulong
+#undef uvlong
+#undef vlong
+#define uchar _x_uchar
+#define ushort _x_ushort
+#define uint _x_uint
+#define ulong _x_ulong
+#define uvlong _x_uvlong
+#define vlong _x_vlong
+
+typedef unsigned char uchar;
+typedef unsigned short ushort;
+typedef unsigned int uint;
+typedef unsigned long ulong;
+typedef unsigned long long uvlong;
+typedef long long vlong;
+
+#ifdef VARARGCK
+# pragma varargck argpos _die 3
+# pragma varargck argpos fatal 1
+# pragma varargck argpos sxprint 1
+#endif
+
+#define strlcat wmii_strlcat
+#define strcasestr wmii_strcasestr
+/* util.c */
+void _die(char*, int, char*, ...);
+void* emalloc(uint);
+void* emallocz(uint);
+void* erealloc(void*, uint);
+char* estrdup(const char*);
+char* estrndup(const char*, uint);
+void fatal(const char*, ...);
+void* freelater(void*);
+int max(int, int);
+int min(int, int);
+uint strlcat(char*, const char*, uint);
+char* strcasestr(const char*, const char*);
+char* sxprint(const char*, ...);
+uint tokenize(char**, uint, char*, char);
+uint stokenize(char**, uint, char*, char*);
+int utflcpy(char*, const char*, int);
+char* vsxprint(const char*, va_list);
+
+#define die(...) \
+ _die(__FILE__, __LINE__, __VA_ARGS__)
+
+char *argv0;
+#undef ARGBEGIN
+#undef ARGEND
+#undef ARGF
+#undef EARGF
+#define ARGBEGIN \
+ int _argtmp=0, _inargv; char *_argv=nil; \
+ if(!argv0) argv0=*argv; argv++, argc--; \
+ _inargv=1; USED(_inargv); \
+ while(argc && argv[0][0] == '-') { \
+ _argv=&argv[0][1]; argv++; argc--; \
+ if(_argv[0] == '-' && _argv[1] == '\0') \
+ break; \
+ while(*_argv) switch(*_argv++)
+#define ARGEND }_inargv=0;USED(_argtmp, _argv, _inargv)
+
+#define EARGF(f) ((_inargv && *_argv) ? \
+ (_argtmp=strlen(_argv), _argv+=_argtmp, _argv-_argtmp) \
+ : ((argc > 0) ? \
+ (--argc, ++argv, _used(argc), *(argv-1)) \
+ : ((f), (char*)0)))
+#define ARGF() EARGF(_used(0))
+
+static inline void
+_used(long a, ...) {
+ if(a){}
+}
+#ifndef KENC
+# undef USED
+# undef SET
+# define USED(...) _used((long)__VA_ARGS__)
+# define SET(x) (x = 0)
+/* # define SET(x) USED(&x) GCC 4 is 'too smart' for this. */
+#endif
diff --git a/include/x11.h b/include/x11.h
new file mode 100644
index 0000000..7dea1aa
--- /dev/null
+++ b/include/x11.h
@@ -0,0 +1,292 @@
+/* Copyright ©2007-2010 Kris Maglione <fbsdaemon@gmail.com>
+ * See LICENSE file for license details.
+ */
+#define Window XWindow
+#define Font XFont
+#define Screen XScreen
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xft/Xft.h>
+#include <X11/extensions/Xrender.h>
+#ifdef _X11_VISIBLE
+# include <X11/Xatom.h>
+# include <X11/extensions/shape.h>
+# include <X11/extensions/Xrandr.h>
+#endif
+#undef Window
+#undef Font
+#undef Screen
+
+enum Align {
+ North = 0x01,
+ East = 0x02,
+ South = 0x04,
+ West = 0x08,
+ NEast = North | East,
+ NWest = North | West,
+ SEast = South | East,
+ SWest = South | West,
+ Center = NEast | SWest,
+};
+
+enum FontType {
+ FX11 = 1,
+ FFontSet,
+ FXft,
+};
+
+enum WindowType {
+ WWindow,
+ WImage,
+};
+
+typedef enum Align Align;
+typedef enum FontType FontType;
+typedef enum WindowType WindowType;
+
+typedef XSetWindowAttributes WinAttr;
+
+typedef struct Point Point;
+typedef struct Rectangle Rectangle;
+
+struct Point {
+ int x, y;
+};
+
+struct Rectangle {
+ Point min, max;
+};
+
+typedef struct Color Color;
+typedef struct CTuple CTuple;
+typedef struct ErrorCode ErrorCode;
+typedef struct Ewmh Ewmh;
+typedef struct Font Font;
+typedef struct Handlers Handlers;
+typedef struct Screen Screen;
+typedef struct WinHints WinHints;
+typedef struct Window Image;
+typedef struct Window Window;
+
+struct Color {
+ ulong pixel;
+ XRenderColor render;
+};
+
+struct CTuple {
+ Color bg;
+ Color fg;
+ Color border;
+ char colstr[24]; /* #RRGGBB #RRGGBB #RRGGBB */
+};
+
+struct ErrorCode {
+ uchar rcode;
+ uchar ecode;
+};
+
+struct Ewmh {
+ long type;
+ long ping;
+ long timer;
+};
+
+struct Font {
+ int type;
+ union {
+ XFontStruct* x11;
+ XFontSet set;
+ XftFont* xft;
+ } font;
+ Rectangle pad;
+ int ascent;
+ int descent;
+ uint height;
+ char* name;
+};
+
+struct Handlers {
+ Rectangle (*dndmotion)(Window*, Point);
+ void (*bdown)(Window*, XButtonEvent*);
+ void (*bup)(Window*, XButtonEvent*);
+ void (*config)(Window*, XConfigureEvent*);
+ void (*configreq)(Window*, XConfigureRequestEvent*);
+ void (*destroy)(Window*, XDestroyWindowEvent*);
+ void (*enter)(Window*, XCrossingEvent*);
+ void (*expose)(Window*, XExposeEvent*);
+ void (*focusin)(Window*, XFocusChangeEvent*);
+ void (*focusout)(Window*, XFocusChangeEvent*);
+ void (*kdown)(Window*, XKeyEvent*);
+ void (*kup)(Window*, XKeyEvent*);
+ void (*leave)(Window*, XCrossingEvent*);
+ void (*map)(Window*, XMapEvent*);
+ void (*mapreq)(Window*, XMapRequestEvent*);
+ void (*motion)(Window*, XMotionEvent*);
+ void (*property)(Window*, XPropertyEvent*);
+ void (*unmap)(Window*, XUnmapEvent*);
+};
+
+struct WinHints {
+ Point min;
+ Point max;
+ Point base;
+ Point baspect;
+ Point inc;
+ Point grav;
+ Rectangle aspect;
+ XWindow group;
+ bool gravstatic;
+ bool position;
+};
+
+struct Window {
+ int type;
+ XID xid;
+ GC gc;
+ Visual* visual;
+ Colormap colormap;
+ XftDraw* xft;
+ Rectangle r;
+ int border;
+ Window* parent;
+ Window* next;
+ Window* prev;
+ Handlers* handler;
+ WinHints* hints;
+ Ewmh ewmh;
+ void* dnd;
+ void* aux;
+ bool mapped;
+ int unmapped;
+ int depth;
+};
+
+struct Screen {
+ int screen;
+ Window root;
+ GC gc;
+ Colormap colormap;
+ Visual* visual;
+ Visual* visual32;
+ Rectangle rect;
+ int depth;
+ int fd;
+ ulong black;
+ ulong white;
+};
+
+#ifdef VARARGCK
+# pragma varargck type "A" Atom
+# pragma varargck type "P" Point
+# pragma varargck type "R" Rectangle
+# pragma varargck type "W" Window*
+#endif
+
+Display *display;
+Screen scr;
+
+extern const Point ZP;
+extern const Rectangle ZR;
+extern Window* pointerwin;
+
+Point Pt(int x, int y);
+Rectangle Rect(int x0, int y0, int x1, int y1);
+Rectangle Rpt(Point min, Point max);
+
+XRectangle XRect(Rectangle r);
+
+#define Dx(r) ((r).max.x - (r).min.x)
+#define Dy(r) ((r).max.y - (r).min.y)
+#define Pt(x, y) ((Point){(x), (y)})
+#define Rpt(p, q) ((Rectangle){p, q})
+#define Rect(x0, y0, x1, y1) ((Rectangle){Pt(x0, y0), Pt(x1, y1)})
+#define changeprop(w, prop, type, data, n) \
+ changeproperty(w, prop, type, \
+ ((sizeof(*(data)) == 8 ? 4 : sizeof(*(data))) * 8), \
+ (uchar*)(data), n)
+
+/* x11.c */
+Point addpt(Point, Point);
+Image* allocimage(int w, int h, int depth);
+void border(Image *dst, Rectangle, int w, Color);
+void changeprop_char(Window*, char*, char*, char[], int);
+void changeprop_long(Window*, char*, char*, long[], int);
+void changeprop_short(Window*, char*, char*, short[], int);
+void changeprop_string(Window*, char*, char*);
+void changeprop_textlist(Window*, char*, char*, char*[]);
+void changeprop_ulong(Window*, char*, char*, ulong[], int);
+void changeproperty(Window*, char*, char*, int width, uchar*, int);
+void copyimage(Image*, Rectangle, Image*, Point);
+Window* createwindow(Window*, Rectangle, int depth, uint class, WinAttr*, int valuemask);
+Window* createwindow_visual(Window*, Rectangle, int depth, Visual*, uint class, WinAttr*, int);
+void delproperty(Window*, char*);
+void destroywindow(Window*);
+Point divpt(Point, Point);
+void drawline(Image*, Point, Point, int cap, int w, Color);
+void drawpoly(Image*, Point*, int, int cap, int w, Color);
+uint drawstring(Image*, Font*, Rectangle, Align, char*, Color);
+int eqpt(Point, Point);
+int eqrect(Rectangle, Rectangle);
+void fill(Image*, Rectangle, Color);
+void fillpoly(Image*, Point*, int, Color);
+Window* findwin(XWindow);
+void freefont(Font*);
+void freeimage(Image *);
+void freestringlist(char**);
+XWindow getfocus(void);
+ulong getprop_long(Window*, char*, char*, ulong, long**, ulong);
+char* getprop_string(Window*, char*);
+int getprop_textlist(Window *w, char *name, char **ret[]);
+ulong getprop_ulong(Window*, char*, char*, ulong, ulong**, ulong);
+ulong getproperty(Window*, char *prop, char *type, Atom *actual, ulong offset, uchar **ret, ulong length);
+int grabkeyboard(Window*);
+int grabpointer(Window*, Window *confine, Cursor, int mask);
+void initdisplay(void);
+KeyCode keycode(char*);
+uint labelh(Font*);
+bool loadcolor(CTuple*, char*);
+Font* loadfont(char*);
+void lowerwin(Window*);
+int mapwin(Window*);
+void movewin(Window*, Point);
+Point mulpt(Point p, Point q);
+bool namedcolor(char *name, Color*);
+bool parsekey(char*, int*, char**);
+int pointerscreen(void);
+Point querypointer(Window*);
+void raisewin(Window*);
+void reparentwindow(Window*, Window*, Point);
+void reshapewin(Window*, Rectangle);
+void selectinput(Window*, long);
+void sendevent(Window*, bool propegate, long mask, XEvent*);
+void setborder(Window*, int, Color);
+void setfocus(Window*, int mode);
+void sethints(Window*);
+void setshapemask(Window *dst, Image *src, Point);
+void setwinattr(Window*, WinAttr*, int valmask);
+char** strlistdup(char**);
+Point subpt(Point, Point);
+void sync(void);
+uint textwidth(Font*, char*);
+uint textwidth_l(Font*, char*, uint len);
+Rectangle textextents_l(Font*, char*, uint, int*);
+int traperrors(bool);
+Point translate(Window*, Window*, Point);
+void ungrabkeyboard(void);
+void ungrabpointer(void);
+int unmapwin(Window*);
+void warppointer(Point);
+Window* window(XWindow);
+long winprotocols(Window*);
+Atom xatom(char*);
+void sendmessage(Window*, char*, long, long, long, long, long);
+XRectangle XRect(Rectangle);
+Rectangle getwinrect(Window*);
+Rectangle gravitate(Rectangle dst, Rectangle src, Point grav);
+Rectangle insetrect(Rectangle, int);
+Rectangle rectaddpt(Rectangle, Point);
+Rectangle rectsetorigin(Rectangle, Point);
+Rectangle rectsubpt(Rectangle, Point);
+Handlers* sethandler(Window*, Handlers*);
+Rectangle sizehint(WinHints*, Rectangle);
+
diff --git a/libbio/Makefile b/libbio/Makefile
new file mode 100644
index 0000000..11dabb6
--- /dev/null
+++ b/libbio/Makefile
@@ -0,0 +1,27 @@
+ROOT= ..
+include ${ROOT}/mk/hdr.mk
+
+VERSION=2.0
+TARG=libbio
+
+OBJ=\
+ bbuffered\
+ bfildes\
+ bflush\
+ bgetc\
+ bgetd\
+ bgetrune\
+ binit\
+ boffset\
+ bprint\
+ bvprint\
+ bputc\
+ bputrune\
+ brdline\
+ brdstr\
+ bread\
+ bseek\
+ bwrite
+
+include ${ROOT}/mk/lib.mk
+
diff --git a/libbio/NOTICE b/libbio/NOTICE
new file mode 100644
index 0000000..42bc739
--- /dev/null
+++ b/libbio/NOTICE
@@ -0,0 +1,34 @@
+This copyright NOTICE applies to all files in this directory and
+subdirectories, unless another copyright notice appears in a given
+file or subdirectory. If you take substantial code from this software to use in
+other programs, you must somehow include with it an appropriate
+copyright notice that includes the copyright notice and the other
+notices below. It is fine (and often tidier) to do that in a separate
+file such as NOTICE, LICENCE or COPYING.
+
+ Copyright © 1994-1999 Lucent Technologies Inc. All rights reserved.
+ Revisions Copyright © 2000-2005 Vita Nuova Holdings Limited (www.vitanuova.com). All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+----
+
+This software is also made available under the Lucent Public License
+version 1.02; see http://plan9.bell-labs.com/plan9dist/license.html
+
diff --git a/libbio/README b/libbio/README
new file mode 100644
index 0000000..602ee26
--- /dev/null
+++ b/libbio/README
@@ -0,0 +1,5 @@
+This software was packaged for Unix by Russ Cox.
+Please send comments to rsc@swtch.com.
+
+http://swtch.com/plan9port/unix
+
diff --git a/libbio/bbuffered.c b/libbio/bbuffered.c
new file mode 100644
index 0000000..327e2ca
--- /dev/null
+++ b/libbio/bbuffered.c
@@ -0,0 +1,20 @@
+#include "plan9.h"
+#include <bio.h>
+
+int
+Bbuffered(Biobuf *bp)
+{
+ switch(bp->state) {
+ case Bracteof:
+ case Bractive:
+ return -bp->icount;
+
+ case Bwactive:
+ return bp->bsize + bp->ocount;
+
+ case Binactive:
+ return 0;
+ }
+ fprint(2, "Bbuffered: unknown state %d\n", bp->state);
+ return 0;
+}
diff --git a/libbio/bcat.c b/libbio/bcat.c
new file mode 100644
index 0000000..7c9b39e
--- /dev/null
+++ b/libbio/bcat.c
@@ -0,0 +1,46 @@
+#include <fmt.h>
+#include "bio.h"
+
+Biobuf bout;
+
+void
+bcat(Biobuf *b, char *name)
+{
+ char buf[1000];
+ int n;
+
+ while((n = Bread(b, buf, sizeof buf)) > 0){
+ if(Bwrite(&bout, buf, n) < 0)
+ fprint(2, "writing during %s: %r\n", name);
+ }
+ if(n < 0)
+ fprint(2, "reading %s: %r\n", name);
+}
+
+int
+main(int argc, char **argv)
+{
+ int i;
+ Biobuf b, *bp;
+ Fmt fmt;
+
+ Binit(&bout, 1, O_WRONLY);
+ Bfmtinit(&fmt, &bout);
+ fmtprint(&fmt, "hello, world\n");
+ Bfmtflush(&fmt);
+
+ if(argc == 1){
+ Binit(&b, 0, O_RDONLY);
+ bcat(&b, "<stdin>");
+ }else{
+ for(i=1; i<argc; i++){
+ if((bp = Bopen(argv[i], O_RDONLY)) == 0){
+ fprint(2, "Bopen %s: %r\n", argv[i]);
+ continue;
+ }
+ bcat(bp, argv[i]);
+ Bterm(bp);
+ }
+ }
+ exit(0);
+}
diff --git a/libbio/bfildes.c b/libbio/bfildes.c
new file mode 100644
index 0000000..072a385
--- /dev/null
+++ b/libbio/bfildes.c
@@ -0,0 +1,9 @@
+#include "plan9.h"
+#include <bio.h>
+
+int
+Bfildes(Biobuf *bp)
+{
+
+ return bp->fid;
+}
diff --git a/libbio/bflush.c b/libbio/bflush.c
new file mode 100644
index 0000000..42bbe9f
--- /dev/null
+++ b/libbio/bflush.c
@@ -0,0 +1,33 @@
+#include "plan9.h"
+#include <bio.h>
+
+int
+Bflush(Biobuf *bp)
+{
+ int n, c;
+
+ switch(bp->state) {
+ case Bwactive:
+ n = bp->bsize+bp->ocount;
+ if(n == 0)
+ return 0;
+ c = write(bp->fid, bp->bbuf, n);
+ if(n == c) {
+ bp->offset += n;
+ bp->ocount = -bp->bsize;
+ return 0;
+ }
+ bp->state = Binactive;
+ bp->ocount = 0;
+ break;
+
+ case Bracteof:
+ bp->state = Bractive;
+
+ case Bractive:
+ bp->icount = 0;
+ bp->gbuf = bp->ebuf;
+ return 0;
+ }
+ return Beof;
+}
diff --git a/libbio/bgetc.c b/libbio/bgetc.c
new file mode 100644
index 0000000..125c940
--- /dev/null
+++ b/libbio/bgetc.c
@@ -0,0 +1,53 @@
+#include "plan9.h"
+#include <bio.h>
+
+int
+Bgetc(Biobuf *bp)
+{
+ int i;
+
+loop:
+ i = bp->icount;
+ if(i != 0) {
+ bp->icount = i+1;
+ return bp->ebuf[i];
+ }
+ if(bp->state != Bractive) {
+ if(bp->state == Bracteof)
+ bp->state = Bractive;
+ return Beof;
+ }
+ /*
+ * get next buffer, try to keep Bungetsize
+ * characters pre-catenated from the previous
+ * buffer to allow that many ungets.
+ */
+ memmove(bp->bbuf-Bungetsize, bp->ebuf-Bungetsize, Bungetsize);
+ i = read(bp->fid, bp->bbuf, bp->bsize);
+ bp->gbuf = bp->bbuf;
+ if(i <= 0) {
+ bp->state = Bracteof;
+ if(i < 0)
+ bp->state = Binactive;
+ return Beof;
+ }
+ if(i < bp->bsize) {
+ memmove(bp->ebuf-i-Bungetsize, bp->bbuf-Bungetsize, i+Bungetsize);
+ bp->gbuf = bp->ebuf-i;
+ }
+ bp->icount = -i;
+ bp->offset += i;
+ goto loop;
+}
+
+int
+Bungetc(Biobuf *bp)
+{
+
+ if(bp->state == Bracteof)
+ bp->state = Bractive;
+ if(bp->state != Bractive)
+ return Beof;
+ bp->icount--;
+ return 1;
+}
diff --git a/libbio/bgetd.c b/libbio/bgetd.c
new file mode 100644
index 0000000..bc2197f
--- /dev/null
+++ b/libbio/bgetd.c
@@ -0,0 +1,36 @@
+#include "plan9.h"
+#include <bio.h>
+
+struct bgetd
+{
+ Biobuf* b;
+ int eof;
+};
+
+static int
+Bgetdf(void *vp)
+{
+ int c;
+ struct bgetd *bg = vp;
+
+ c = Bgetc(bg->b);
+ if(c == Beof)
+ bg->eof = 1;
+ return c;
+}
+
+int
+Bgetd(Biobuf *bp, double *dp)
+{
+ double d;
+ struct bgetd b;
+
+ b.b = bp;
+ b.eof = 0;
+ d = fmtcharstod(Bgetdf, &b);
+ if(b.eof)
+ return -1;
+ Bungetc(bp);
+ *dp = d;
+ return 1;
+}
diff --git a/libbio/bgetrune.c b/libbio/bgetrune.c
new file mode 100644
index 0000000..1a6e8ab
--- /dev/null
+++ b/libbio/bgetrune.c
@@ -0,0 +1,47 @@
+#include "plan9.h"
+#include <bio.h>
+#include <utf.h>
+
+long
+Bgetrune(Biobuf *bp)
+{
+ int c, i;
+ Rune rune;
+ char str[4];
+
+ c = Bgetc(bp);
+ if(c < Runeself) { /* one char */
+ bp->runesize = 1;
+ return c;
+ }
+ str[0] = c;
+
+ for(i=1;;) {
+ c = Bgetc(bp);
+ if(c < 0)
+ return c;
+ str[i++] = c;
+
+ if(fullrune(str, i)) {
+ bp->runesize = chartorune(&rune, str);
+ while(i > bp->runesize) {
+ Bungetc(bp);
+ i--;
+ }
+ return rune;
+ }
+ }
+}
+
+int
+Bungetrune(Biobuf *bp)
+{
+
+ if(bp->state == Bracteof)
+ bp->state = Bractive;
+ if(bp->state != Bractive)
+ return Beof;
+ bp->icount -= bp->runesize;
+ bp->runesize = 0;
+ return 1;
+}
diff --git a/libbio/binit.c b/libbio/binit.c
new file mode 100644
index 0000000..19dbed0
--- /dev/null
+++ b/libbio/binit.c
@@ -0,0 +1,154 @@
+#include <stdlib.h>
+#include <plan9.h>
+#include <bio.h>
+
+enum
+{
+ MAXBUFS = 20
+};
+
+static Biobuf* wbufs[MAXBUFS];
+static int atexitflag;
+
+static
+void
+batexit(void)
+{
+ Biobuf *bp;
+ int i;
+
+ for(i=0; i<MAXBUFS; i++) {
+ bp = wbufs[i];
+ if(bp != 0) {
+ wbufs[i] = 0;
+ Bflush(bp);
+ }
+ }
+}
+
+static
+void
+deinstall(Biobuf *bp)
+{
+ int i;
+
+ for(i=0; i<MAXBUFS; i++)
+ if(wbufs[i] == bp)
+ wbufs[i] = 0;
+}
+
+static
+void
+install(Biobuf *bp)
+{
+ int i;
+
+ deinstall(bp);
+ for(i=0; i<MAXBUFS; i++)
+ if(wbufs[i] == 0) {
+ wbufs[i] = bp;
+ break;
+ }
+ if(atexitflag == 0) {
+ atexitflag = 1;
+ atexit(batexit);
+ }
+}
+
+int
+Binits(Biobuf *bp, int f, int mode, unsigned char *p, int size)
+{
+
+ p += Bungetsize; /* make room for Bungets */
+ size -= Bungetsize;
+
+ switch(mode&~(OCEXEC|ORCLOSE|OTRUNC)) {
+ default:
+ fprint(2, "Bopen: unknown mode %d\n", mode);
+ return Beof;
+
+ case OREAD:
+ bp->state = Bractive;
+ bp->ocount = 0;
+ break;
+
+ case OWRITE:
+ install(bp);
+ bp->state = Bwactive;
+ bp->ocount = -size;
+ break;
+ }
+ bp->bbuf = p;
+ bp->ebuf = p+size;
+ bp->bsize = size;
+ bp->icount = 0;
+ bp->gbuf = bp->ebuf;
+ bp->fid = f;
+ bp->flag = 0;
+ bp->rdline = 0;
+ bp->offset = 0;
+ bp->runesize = 0;
+ return 0;
+}
+
+
+int
+Binit(Biobuf *bp, int f, int mode)
+{
+ return Binits(bp, f, mode, bp->b, sizeof(bp->b));
+}
+
+Biobuf*
+Bfdopen(int f, int mode)
+{
+ Biobuf *bp;
+
+ bp = malloc(sizeof(Biobuf));
+ if(bp == 0)
+ return 0;
+ Binits(bp, f, mode, bp->b, sizeof(bp->b));
+ bp->flag = Bmagic;
+ return bp;
+}
+
+Biobuf*
+Bopen(const char *name, int mode)
+{
+ Biobuf *bp;
+ int f;
+
+ switch(mode&~(OCEXEC|ORCLOSE|OTRUNC)) {
+ default:
+ fprint(2, "Bopen: unknown mode %d\n", mode);
+ return 0;
+
+ case OREAD:
+ f = open(name, OREAD);
+ if(f < 0)
+ return 0;
+ break;
+
+ case OWRITE:
+ f = creat(name, 0666);
+ if(f < 0)
+ return 0;
+ }
+ bp = Bfdopen(f, mode);
+ if(bp == 0)
+ close(f);
+ return bp;
+}
+
+int
+Bterm(Biobuf *bp)
+{
+
+ deinstall(bp);
+ Bflush(bp);
+ if(bp->flag == Bmagic) {
+ bp->flag = 0;
+ close(bp->fid);
+ free(bp);
+ }
+ return 0;
+}
diff --git a/libbio/bio.3 b/libbio/bio.3
new file mode 100644
index 0000000..4e789a2
--- /dev/null
+++ b/libbio/bio.3
@@ -0,0 +1,371 @@
+.deEX
+.ift .ft5
+.nf
+..
+.deEE
+.ft1
+.fi
+..
+.TH BIO 3
+.SH NAME
+Bopen, Bfdopen, Binit, Binits, Brdline, Brdstr, Bgetc, Bgetrune, Bgetd, Bungetc, Bungetrune, Bread, Bseek, Boffset, Bfildes, Blinelen, Bputc, Bputrune, Bprint, Bvprint, Bwrite, Bflush, Bterm, Bbuffered \- buffered input/output
+.SH SYNOPSIS
+.ta \w'\fLBiobuf* 'u
+.B #include <utf.h>
+.br
+.B #include <fmt.h>
+.br
+.B #include <bio.h>
+.PP
+.B
+Biobuf* Bopen(char *file, int mode)
+.PP
+.B
+Biobuf* Bfdopen(int fd, int mode)
+.PP
+.B
+int Binit(Biobuf *bp, int fd, int mode)
+.PP
+.B
+int Binits(Biobufhdr *bp, int fd, int mode, uchar *buf, int size)
+.PP
+.B
+int Bterm(Biobufhdr *bp)
+.PP
+.B
+int Bprint(Biobufhdr *bp, char *format, ...)
+.PP
+.B
+int Bvprint(Biobufhdr *bp, char *format, va_list arglist);
+.PP
+.B
+void* Brdline(Biobufhdr *bp, int delim)
+.PP
+.B
+char* Brdstr(Biobufhdr *bp, int delim, int nulldelim)
+.PP
+.B
+int Blinelen(Biobufhdr *bp)
+.PP
+.B
+vlong Boffset(Biobufhdr *bp)
+.PP
+.B
+int Bfildes(Biobufhdr *bp)
+.PP
+.B
+int Bgetc(Biobufhdr *bp)
+.PP
+.B
+long Bgetrune(Biobufhdr *bp)
+.PP
+.B
+int Bgetd(Biobufhdr *bp, double *d)
+.PP
+.B
+int Bungetc(Biobufhdr *bp)
+.PP
+.B
+int Bungetrune(Biobufhdr *bp)
+.PP
+.B
+vlong Bseek(Biobufhdr *bp, vlong n, int type)
+.PP
+.B
+int Bputc(Biobufhdr *bp, int c)
+.PP
+.B
+int Bputrune(Biobufhdr *bp, long c)
+.PP
+.B
+long Bread(Biobufhdr *bp, void *addr, long nbytes)
+.PP
+.B
+long Bwrite(Biobufhdr *bp, void *addr, long nbytes)
+.PP
+.B
+int Bflush(Biobufhdr *bp)
+.PP
+.B
+int Bbuffered(Biobufhdr *bp)
+.PP
+.SH DESCRIPTION
+These routines implement fast buffered I/O.
+I/O on different file descriptors is independent.
+.PP
+.I Bopen
+opens
+.I file
+for mode
+.B O_RDONLY
+or creates for mode
+.BR O_WRONLY .
+It calls
+.IR malloc (3)
+to allocate a buffer.
+.PP
+.I Bfdopen
+allocates a buffer for the already-open file descriptor
+.I fd
+for mode
+.B O_RDONLY
+or
+.BR O_WRONLY .
+It calls
+.IR malloc (3)
+to allocate a buffer.
+.PP
+.I Binit
+initializes a standard size buffer, type
+.IR Biobuf ,
+with the open file descriptor passed in
+by the user.
+.I Binits
+initializes a non-standard size buffer, type
+.IR Biobufhdr ,
+with the open file descriptor,
+buffer area, and buffer size passed in
+by the user.
+.I Biobuf
+and
+.I Biobufhdr
+are related by the declaration:
+.IP
+.EX
+typedef struct Biobuf Biobuf;
+struct Biobuf
+{
+ Biobufhdr;
+ uchar b[Bungetsize+Bsize];
+};
+.EE
+.PP
+Arguments
+of types pointer to Biobuf and pointer to Biobufhdr
+can be used interchangeably in the following routines.
+.PP
+.IR Bopen ,
+.IR Binit ,
+or
+.I Binits
+should be called before any of the
+other routines on that buffer.
+.I Bfildes
+returns the integer file descriptor of the associated open file.
+.PP
+.I Bterm
+flushes the buffer for
+.IR bp .
+If the buffer was allocated by
+.IR Bopen ,
+the buffer is
+.I freed
+and the file is closed.
+.PP
+.I Brdline
+reads a string from the file associated with
+.I bp
+up to and including the first
+.I delim
+character.
+The delimiter character at the end of the line is
+not altered.
+.I Brdline
+returns a pointer to the start of the line or
+.L 0
+on end-of-file or read error.
+.I Blinelen
+returns the length (including the delimiter)
+of the most recent string returned by
+.IR Brdline .
+.PP
+.I Brdstr
+returns a
+.IR malloc (3)-allocated
+buffer containing the next line of input delimited by
+.IR delim ,
+terminated by a NUL (0) byte.
+Unlike
+.IR Brdline ,
+which returns when its buffer is full even if no delimiter has been found,
+.I Brdstr
+will return an arbitrarily long line in a single call.
+If
+.I nulldelim
+is set, the terminal delimiter will be overwritten with a NUL.
+After a successful call to
+.IR Brdstr ,
+the return value of
+.I Blinelen
+will be the length of the returned buffer, excluding the NUL.
+.PP
+.I Bgetc
+returns the next character from
+.IR bp ,
+or a negative value
+at end of file.
+.I Bungetc
+may be called immediately after
+.I Bgetc
+to allow the same character to be reread.
+.PP
+.I Bgetrune
+calls
+.I Bgetc
+to read the bytes of the next
+.SM UTF
+sequence in the input stream and returns the value of the rune
+represented by the sequence.
+It returns a negative value
+at end of file.
+.I Bungetrune
+may be called immediately after
+.I Bgetrune
+to allow the same
+.SM UTF
+sequence to be reread as either bytes or a rune.
+.I Bungetc
+and
+.I Bungetrune
+may back up a maximum of five bytes.
+.PP
+.I Bgetd
+uses
+.I fmtcharstod
+(see
+.IR fmtstrtod (3))
+and
+.I Bgetc
+to read the formatted
+floating-point number in the input stream,
+skipping initial blanks and tabs.
+The value is stored in
+.BR *d.
+.PP
+.I Bread
+reads
+.I nbytes
+of data from
+.I bp
+into memory starting at
+.IR addr .
+The number of bytes read is returned on success
+and a negative value is returned if a read error occurred.
+.PP
+.I Bseek
+applies
+.IR lseek (2)
+to
+.IR bp .
+It returns the new file offset.
+.I Boffset
+returns the file offset of the next character to be processed.
+.PP
+.I Bputc
+outputs the low order 8 bits of
+.I c
+on
+.IR bp .
+If this causes a
+.IR write
+to occur and there is an error,
+a negative value is returned.
+Otherwise, a zero is returned.
+.PP
+.I Bputrune
+calls
+.I Bputc
+to output the low order
+16 bits of
+.I c
+as a rune
+in
+.SM UTF
+format
+on the output stream.
+.PP
+.I Bprint
+is a buffered interface to
+.IR print (3).
+If this causes a
+.IR write
+to occur and there is an error,
+a negative value
+.RB ( Beof )
+is returned.
+Otherwise, the number of bytes output is returned.
+.I Bvprint
+does the same except it takes as argument a
+.B va_list
+parameter, so it can be called within a variadic function.
+.PP
+.I Bwrite
+outputs
+.I nbytes
+of data starting at
+.I addr
+to
+.IR bp .
+If this causes a
+.IR write
+to occur and there is an error,
+a negative value is returned.
+Otherwise, the number of bytes written is returned.
+.PP
+.I Bflush
+causes any buffered output associated with
+.I bp
+to be written.
+The return is as for
+.IR Bputc .
+.I Bflush
+is called on
+exit for every buffer still open
+for writing.
+.PP
+.I Bbuffered
+returns the number of bytes in the buffer.
+When reading, this is the number of bytes still available from the last
+read on the file; when writing, it is the number of bytes ready to be
+written.
+.SH SOURCE
+.B http://swtch.com/plan9port/unix
+.SH SEE ALSO
+.IR open (2),
+.IR print (3),
+.IR atexit (3),
+.IR utf (7),
+.SH DIAGNOSTICS
+.I Bio
+routines that return integers yield
+.B Beof
+if
+.I bp
+is not the descriptor of an open file.
+.I Bopen
+returns zero if the file cannot be opened in the given mode.
+All routines set
+.I errstr
+on error.
+.SH BUGS
+.I Brdline
+returns an error on strings longer than the buffer associated
+with the file
+and also if the end-of-file is encountered
+before a delimiter.
+.I Blinelen
+will tell how many characters are available
+in these cases.
+In the case of a true end-of-file,
+.I Blinelen
+will return zero.
+At the cost of allocating a buffer,
+.I Brdstr
+sidesteps these issues.
+.PP
+The data returned by
+.I Brdline
+may be overwritten by calls to any other
+.I bio
+routine on the same
+.IR bp.
diff --git a/libbio/boffset.c b/libbio/boffset.c
new file mode 100644
index 0000000..ae729e9
--- /dev/null
+++ b/libbio/boffset.c
@@ -0,0 +1,25 @@
+#include "plan9.h"
+#include <bio.h>
+
+off_t
+Boffset(Biobuf *bp)
+{
+ off_t n;
+
+ switch(bp->state) {
+ default:
+ fprint(2, "Boffset: unknown state %d\n", bp->state);
+ n = Beof;
+ break;
+
+ case Bracteof:
+ case Bractive:
+ n = bp->offset + bp->icount;
+ break;
+
+ case Bwactive:
+ n = bp->offset + (bp->bsize + bp->ocount);
+ break;
+ }
+ return n;
+}
diff --git a/libbio/bprint.c b/libbio/bprint.c
new file mode 100644
index 0000000..6bdff71
--- /dev/null
+++ b/libbio/bprint.c
@@ -0,0 +1,14 @@
+#include "plan9.h"
+#include <bio.h>
+
+int
+Bprint(Biobuf *bp, const char *fmt, ...)
+{
+ int n;
+ va_list arg;
+
+ va_start(arg, fmt);
+ n = Bvprint(bp, fmt, arg);
+ va_end(arg);
+ return n;
+}
diff --git a/libbio/bputc.c b/libbio/bputc.c
new file mode 100644
index 0000000..96c188b
--- /dev/null
+++ b/libbio/bputc.c
@@ -0,0 +1,20 @@
+#include "plan9.h"
+#include <bio.h>
+
+int
+Bputc(Biobuf *bp, int c)
+{
+ int i;
+
+ for(;;) {
+ i = bp->ocount;
+ if(i) {
+ bp->ebuf[i++] = c;
+ bp->ocount = i;
+ return 0;
+ }
+ if(Bflush(bp) == Beof)
+ break;
+ }
+ return Beof;
+}
diff --git a/libbio/bputrune.c b/libbio/bputrune.c
new file mode 100644
index 0000000..c7808c0
--- /dev/null
+++ b/libbio/bputrune.c
@@ -0,0 +1,23 @@
+#include "plan9.h"
+#include <bio.h>
+#include <utf.h>
+
+int
+Bputrune(Biobuf *bp, long c)
+{
+ Rune rune;
+ char str[4];
+ int n;
+
+ rune = c;
+ if(rune < Runeself) {
+ Bputc(bp, rune);
+ return 1;
+ }
+ n = runetochar(str, &rune);
+ if(n == 0)
+ return Bbad;
+ if(Bwrite(bp, str, n) != n)
+ return Beof;
+ return n;
+}
diff --git a/libbio/brdline.c b/libbio/brdline.c
new file mode 100644
index 0000000..dfd04c6
--- /dev/null
+++ b/libbio/brdline.c
@@ -0,0 +1,94 @@
+#include "plan9.h"
+#include <bio.h>
+
+void*
+Brdline(Biobuf *bp, int delim)
+{
+ char *ip, *ep;
+ int i, j;
+
+ i = -bp->icount;
+ if(i == 0) {
+ /*
+ * eof or other error
+ */
+ if(bp->state != Bractive) {
+ if(bp->state == Bracteof)
+ bp->state = Bractive;
+ bp->rdline = 0;
+ bp->gbuf = bp->ebuf;
+ return 0;
+ }
+ }
+
+ /*
+ * first try in remainder of buffer (gbuf doesn't change)
+ */
+ ip = (char*)bp->ebuf - i;
+ ep = memchr(ip, delim, i);
+ if(ep) {
+ j = (ep - ip) + 1;
+ bp->rdline = j;
+ bp->icount += j;
+ return ip;
+ }
+
+ /*
+ * copy data to beginning of buffer
+ */
+ if(i < bp->bsize)
+ memmove(bp->bbuf, ip, i);
+ bp->gbuf = bp->bbuf;
+
+ /*
+ * append to buffer looking for the delim
+ */
+ ip = (char*)bp->bbuf + i;
+ while(i < bp->bsize) {
+ j = read(bp->fid, ip, bp->bsize-i);
+ if(j <= 0) {
+ /*
+ * end of file with no delim
+ */
+ memmove(bp->ebuf-i, bp->bbuf, i);
+ bp->rdline = i;
+ bp->icount = -i;
+ bp->gbuf = bp->ebuf-i;
+ return 0;
+ }
+ bp->offset += j;
+ i += j;
+ ep = memchr(ip, delim, j);
+ if(ep) {
+ /*
+ * found in new piece
+ * copy back up and reset everything
+ */
+ ip = (char*)bp->ebuf - i;
+ if(i < bp->bsize){
+ memmove(ip, bp->bbuf, i);
+ bp->gbuf = (unsigned char*)ip;
+ }
+ j = (ep - (char*)bp->bbuf) + 1;
+ bp->rdline = j;
+ bp->icount = j - i;
+ return ip;
+ }
+ ip += j;
+ }
+
+ /*
+ * full buffer without finding
+ */
+ bp->rdline = bp->bsize;
+ bp->icount = -bp->bsize;
+ bp->gbuf = bp->bbuf;
+ return 0;
+}
+
+int
+Blinelen(Biobuf *bp)
+{
+
+ return bp->rdline;
+}
diff --git a/libbio/brdstr.c b/libbio/brdstr.c
new file mode 100644
index 0000000..ce720c2
--- /dev/null
+++ b/libbio/brdstr.c
@@ -0,0 +1,112 @@
+#include <stdlib.h>
+#include <plan9.h>
+#include <bio.h>
+
+static char*
+badd(char *p, int *np, char *data, int ndata, int delim, int nulldelim)
+{
+ int n;
+
+ n = *np;
+ p = realloc(p, n+ndata+1);
+ if(p){
+ memmove(p+n, data, ndata);
+ n += ndata;
+ if(n>0 && nulldelim && p[n-1]==delim)
+ p[--n] = '\0';
+ else
+ p[n] = '\0';
+ *np = n;
+ }
+ return p;
+}
+
+char*
+Brdstr(Biobuf *bp, int delim, int nulldelim)
+{
+ char *ip, *ep, *p;
+ int i, j;
+
+ i = -bp->icount;
+ bp->rdline = 0;
+ if(i == 0) {
+ /*
+ * eof or other error
+ */
+ if(bp->state != Bractive) {
+ if(bp->state == Bracteof)
+ bp->state = Bractive;
+ bp->gbuf = bp->ebuf;
+ return nil;
+ }
+ }
+
+ /*
+ * first try in remainder of buffer (gbuf doesn't change)
+ */
+ ip = (char*)bp->ebuf - i;
+ ep = memchr(ip, delim, i);
+ if(ep) {
+ j = (ep - ip) + 1;
+ bp->icount += j;
+ return badd(nil, &bp->rdline, ip, j, delim, nulldelim);
+ }
+
+ /*
+ * copy data to beginning of buffer
+ */
+ if(i < bp->bsize)
+ memmove(bp->bbuf, ip, i);
+ bp->gbuf = bp->bbuf;
+
+ /*
+ * append to buffer looking for the delim
+ */
+ p = nil;
+ for(;;){
+ ip = (char*)bp->bbuf + i;
+ while(i < bp->bsize) {
+ j = read(bp->fid, ip, bp->bsize-i);
+ if(j <= 0 && i == 0)
+ return p;
+ if(j <= 0 && i > 0){
+ /*
+ * end of file but no delim. pretend we got a delim
+ * by making the delim \0 and smashing it with nulldelim.
+ */
+ j = 1;
+ ep = ip;
+ delim = '\0';
+ nulldelim = 1;
+ *ep = delim; /* there will be room for this */
+ }else{
+ bp->offset += j;
+ ep = memchr(ip, delim, j);
+ }
+ i += j;
+ if(ep) {
+ /*
+ * found in new piece
+ * copy back up and reset everything
+ */
+ ip = (char*)bp->ebuf - i;
+ if(i < bp->bsize){
+ memmove(ip, bp->bbuf, i);
+ bp->gbuf = (unsigned char*)ip;
+ }
+ j = (ep - (char*)bp->bbuf) + 1;
+ bp->icount = j - i;
+ return badd(p, &bp->rdline, ip, j, delim, nulldelim);
+ }
+ ip += j;
+ }
+
+ /*
+ * full buffer without finding; add to user string and continue
+ */
+ p = badd(p, &bp->rdline, (char*)bp->bbuf, bp->bsize, 0, 0);
+ i = 0;
+ bp->icount = 0;
+ bp->gbuf = bp->ebuf;
+ }
+}
diff --git a/libbio/bread.c b/libbio/bread.c
new file mode 100644
index 0000000..a2621e7
--- /dev/null
+++ b/libbio/bread.c
@@ -0,0 +1,45 @@
+#include "plan9.h"
+#include <bio.h>
+
+long
+Bread(Biobuf *bp, void *ap, long count)
+{
+ long c;
+ unsigned char *p;
+ int i, n, ic;
+
+ p = ap;
+ c = count;
+ ic = bp->icount;
+
+ while(c > 0) {
+ n = -ic;
+ if(n > c)
+ n = c;
+ if(n == 0) {
+ if(bp->state != Bractive)
+ break;
+ i = read(bp->fid, bp->bbuf, bp->bsize);
+ if(i <= 0) {
+ bp->state = Bracteof;
+ if(i < 0)
+ bp->state = Binactive;
+ break;
+ }
+ bp->gbuf = bp->bbuf;
+ bp->offset += i;
+ if(i < bp->bsize) {
+ memmove(bp->ebuf-i, bp->bbuf, i);
+ bp->gbuf = bp->ebuf-i;
+ }
+ ic = -i;
+ continue;
+ }
+ memmove(p, bp->ebuf+ic, n);
+ c -= n;
+ ic += n;
+ p += n;
+ }
+ bp->icount = ic;
+ return count-c;
+}
diff --git a/libbio/bseek.c b/libbio/bseek.c
new file mode 100644
index 0000000..6fb9a98
--- /dev/null
+++ b/libbio/bseek.c
@@ -0,0 +1,60 @@
+#include "plan9.h"
+#include <bio.h>
+
+off_t
+Bseek(Biobuf *bp, off_t offset, int base)
+{
+ vlong n, d;
+ int bufsz;
+
+ switch(bp->state) {
+ default:
+ fprint(2, "Bseek: unknown state %d\n", bp->state);
+ return Beof;
+
+ case Bracteof:
+ bp->state = Bractive;
+ bp->icount = 0;
+ bp->gbuf = bp->ebuf;
+
+ case Bractive:
+ n = offset;
+ if(base == 1) {
+ n += Boffset(bp);
+ base = 0;
+ }
+
+ /*
+ * try to seek within buffer
+ */
+ if(base == 0) {
+ d = n - Boffset(bp);
+ bufsz = bp->ebuf - bp->gbuf;
+ if(-bufsz <= d && d <= bufsz){
+ bp->icount += d;
+ if(d >= 0) {
+ if(bp->icount <= 0)
+ return n;
+ } else {
+ if(bp->ebuf - bp->gbuf >= -bp->icount)
+ return n;
+ }
+ }
+ }
+
+ /*
+ * reset the buffer
+ */
+ n = lseek(bp->fid, n, base);
+ bp->icount = 0;
+ bp->gbuf = bp->ebuf;
+ break;
+
+ case Bwactive:
+ Bflush(bp);
+ n = lseek(bp->fid, offset, base);
+ break;
+ }
+ bp->offset = n;
+ return n;
+}
diff --git a/libbio/bvprint.c b/libbio/bvprint.c
new file mode 100644
index 0000000..a65da64
--- /dev/null
+++ b/libbio/bvprint.c
@@ -0,0 +1,36 @@
+#include "plan9.h"
+#include <bio.h>
+
+static int
+fmtBflush(Fmt *f)
+{
+ Biobuf *bp;
+
+ bp = f->farg;
+ bp->ocount = (char*)f->to - (char*)f->stop;
+ if(Bflush(bp) < 0)
+ return 0;
+ f->stop = bp->ebuf;
+ f->to = (char*)f->stop + bp->ocount;
+ f->start = f->to;
+ return 1;
+}
+
+int
+Bvprint(Biobuf *bp, const char *fmt, va_list arg)
+{
+ int n;
+ Fmt f;
+
+ f.runes = 0;
+ f.stop = bp->ebuf;
+ f.start = (char*)f.stop + bp->ocount;
+ f.to = f.start;
+ f.flush = fmtBflush;
+ f.farg = bp;
+ f.nfmt = 0;
+ n = fmtvprint(&f, fmt, arg);
+ bp->ocount = (char*)f.to - (char*)f.stop;
+ return n;
+}
+
diff --git a/libbio/bwrite.c b/libbio/bwrite.c
new file mode 100644
index 0000000..fe82c44
--- /dev/null
+++ b/libbio/bwrite.c
@@ -0,0 +1,38 @@
+#include "plan9.h"
+#include <bio.h>
+
+long
+Bwrite(Biobuf *bp, void *ap, long count)
+{
+ long c;
+ unsigned char *p;
+ int i, n, oc;
+
+ p = ap;
+ c = count;
+ oc = bp->ocount;
+
+ while(c > 0) {
+ n = -oc;
+ if(n > c)
+ n = c;
+ if(n == 0) {
+ if(bp->state != Bwactive)
+ return Beof;
+ i = write(bp->fid, bp->bbuf, bp->bsize);
+ if(i != bp->bsize) {
+ bp->state = Binactive;
+ return Beof;
+ }
+ bp->offset += i;
+ oc = -bp->bsize;
+ continue;
+ }
+ memmove(bp->ebuf+oc, p, n);
+ oc += n;
+ c -= n;
+ p += n;
+ }
+ bp->ocount = oc;
+ return count-c;
+}
diff --git a/libfmt/Makefile b/libfmt/Makefile
new file mode 100644
index 0000000..b4598a4
--- /dev/null
+++ b/libfmt/Makefile
@@ -0,0 +1,48 @@
+ROOT= ..
+include ${ROOT}/mk/hdr.mk
+
+VERSION=2.0
+TARG=libfmt
+
+NUM=\
+ charstod\
+ pow10\
+ nan64
+
+OBJ=\
+ dofmt\
+ dorfmt\
+ errfmt\
+ fltfmt\
+ fmt\
+ fmtfd\
+ fmtfdflush\
+ fmtlock\
+ fmtprint\
+ fmtquote\
+ fmtrune\
+ fmtstr\
+ fmtvprint\
+ fprint\
+ print\
+ runefmtstr\
+ runeseprint\
+ runesmprint\
+ runesnprint\
+ runesprint\
+ runevseprint\
+ runevsmprint\
+ runevsnprint\
+ seprint\
+ smprint\
+ snprint\
+ sprint\
+ strtod\
+ vfprint\
+ vseprint\
+ vsmprint\
+ vsnprint\
+ $(NUM)
+
+include ${ROOT}/mk/lib.mk
+
diff --git a/libfmt/NOTICE b/libfmt/NOTICE
new file mode 100644
index 0000000..43f24ce
--- /dev/null
+++ b/libfmt/NOTICE
@@ -0,0 +1,25 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
+ * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+*/
+
+This is a Unix port of the Plan 9 formatted I/O package.
+
+Please send comments about the packaging
+to Russ Cox <rsc@swtch.com>.
+
+
+----
+
+This software is also made available under the Lucent Public License
+version 1.02; see http://plan9.bell-labs.com/plan9dist/license.html
+
diff --git a/libfmt/README b/libfmt/README
new file mode 100644
index 0000000..602ee26
--- /dev/null
+++ b/libfmt/README
@@ -0,0 +1,5 @@
+This software was packaged for Unix by Russ Cox.
+Please send comments to rsc@swtch.com.
+
+http://swtch.com/plan9port/unix
+
diff --git a/libfmt/charstod.c b/libfmt/charstod.c
new file mode 100644
index 0000000..cbe6d3d
--- /dev/null
+++ b/libfmt/charstod.c
@@ -0,0 +1,85 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "plan9.h"
+#include "fmt.h"
+#include "fmtdef.h"
+
+/*
+ * Reads a floating-point number by interpreting successive characters
+ * returned by (*f)(vp). The last call it makes to f terminates the
+ * scan, so is not a character in the number. It may therefore be
+ * necessary to back up the input stream up one byte after calling charstod.
+ */
+
+double
+fmtcharstod(int(*f)(void*), void *vp)
+{
+ double num, dem;
+ int neg, eneg, dig, exp, c;
+
+ num = 0;
+ neg = 0;
+ dig = 0;
+ exp = 0;
+ eneg = 0;
+
+ c = (*f)(vp);
+ while(c == ' ' || c == '\t')
+ c = (*f)(vp);
+ if(c == '-' || c == '+'){
+ if(c == '-')
+ neg = 1;
+ c = (*f)(vp);
+ }
+ while(c >= '0' && c <= '9'){
+ num = num*10 + c-'0';
+ c = (*f)(vp);
+ }
+ if(c == '.')
+ c = (*f)(vp);
+ while(c >= '0' && c <= '9'){
+ num = num*10 + c-'0';
+ dig++;
+ c = (*f)(vp);
+ }
+ if(c == 'e' || c == 'E'){
+ c = (*f)(vp);
+ if(c == '-' || c == '+'){
+ if(c == '-'){
+ dig = -dig;
+ eneg = 1;
+ }
+ c = (*f)(vp);
+ }
+ while(c >= '0' && c <= '9'){
+ exp = exp*10 + c-'0';
+ c = (*f)(vp);
+ }
+ }
+ exp -= dig;
+ if(exp < 0){
+ exp = -exp;
+ eneg = !eneg;
+ }
+ dem = __fmtpow10(exp);
+ if(eneg)
+ num /= dem;
+ else
+ num *= dem;
+ if(neg)
+ return -num;
+ return num;
+}
diff --git a/libfmt/dofmt.c b/libfmt/dofmt.c
new file mode 100644
index 0000000..a7d4d2f
--- /dev/null
+++ b/libfmt/dofmt.c
@@ -0,0 +1,537 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "plan9.h"
+#include "fmt.h"
+#include "fmtdef.h"
+
+/* format the output into f->to and return the number of characters fmted */
+int
+dofmt(Fmt *f, const char *fmt)
+{
+ Rune rune, *rt, *rs;
+ int r;
+ char *t, *s;
+ int n, nfmt;
+
+ nfmt = f->nfmt;
+ for(;;){
+ if(f->runes){
+ rt = (Rune*)f->to;
+ rs = (Rune*)f->stop;
+ while((r = *(uchar*)fmt) && r != '%'){
+ if(r < Runeself)
+ fmt++;
+ else{
+ fmt += chartorune(&rune, fmt);
+ r = rune;
+ }
+ FMTRCHAR(f, rt, rs, r);
+ }
+ fmt++;
+ f->nfmt += rt - (Rune *)f->to;
+ f->to = rt;
+ if(!r)
+ return f->nfmt - nfmt;
+ f->stop = rs;
+ }else{
+ t = (char*)f->to;
+ s = (char*)f->stop;
+ while((r = *(uchar*)fmt) && r != '%'){
+ if(r < Runeself){
+ FMTCHAR(f, t, s, r);
+ fmt++;
+ }else{
+ n = chartorune(&rune, fmt);
+ if(t + n > s){
+ t = (char*)__fmtflush(f, t, n);
+ if(t != nil)
+ s = (char*)f->stop;
+ else
+ return -1;
+ }
+ while(n--)
+ *t++ = *fmt++;
+ }
+ }
+ fmt++;
+ f->nfmt += t - (char *)f->to;
+ f->to = t;
+ if(!r)
+ return f->nfmt - nfmt;
+ f->stop = s;
+ }
+
+ fmt = (char*)__fmtdispatch(f, fmt, 0);
+ if(fmt == nil)
+ return -1;
+ }
+}
+
+void *
+__fmtflush(Fmt *f, void *t, int len)
+{
+ if(f->runes)
+ f->nfmt += (Rune*)t - (Rune*)f->to;
+ else
+ f->nfmt += (char*)t - (char *)f->to;
+ f->to = t;
+ if(f->flush == 0 || (*f->flush)(f) == 0 || (char*)f->to + len > (char*)f->stop){
+ f->stop = f->to;
+ return nil;
+ }
+ return f->to;
+}
+
+/*
+ * put a formatted block of memory sz bytes long of n runes into the output buffer,
+ * left/right justified in a field of at least f->width charactes
+ */
+int
+__fmtpad(Fmt *f, int n)
+{
+ char *t, *s;
+ int i;
+
+ t = (char*)f->to;
+ s = (char*)f->stop;
+ for(i = 0; i < n; i++)
+ FMTCHAR(f, t, s, ' ');
+ f->nfmt += t - (char *)f->to;
+ f->to = t;
+ return 0;
+}
+
+int
+__rfmtpad(Fmt *f, int n)
+{
+ Rune *t, *s;
+ int i;
+
+ t = (Rune*)f->to;
+ s = (Rune*)f->stop;
+ for(i = 0; i < n; i++)
+ FMTRCHAR(f, t, s, ' ');
+ f->nfmt += t - (Rune *)f->to;
+ f->to = t;
+ return 0;
+}
+
+int
+__fmtcpy(Fmt *f, const void *vm, int n, int sz)
+{
+ Rune *rt, *rs, r;
+ char *t, *s, *m, *me;
+ ulong fl;
+ int nc, w;
+
+ m = (char*)vm;
+ me = m + sz;
+ w = f->width;
+ fl = f->flags;
+ if((fl & FmtPrec) && n > f->prec)
+ n = f->prec;
+ if(f->runes){
+ if(!(fl & FmtLeft) && __rfmtpad(f, w - n) < 0)
+ return -1;
+ rt = (Rune*)f->to;
+ rs = (Rune*)f->stop;
+ for(nc = n; nc > 0; nc--){
+ r = *(uchar*)m;
+ if(r < Runeself)
+ m++;
+ else if((me - m) >= UTFmax || fullrune(m, me-m))
+ m += chartorune(&r, m);
+ else
+ break;
+ FMTRCHAR(f, rt, rs, r);
+ }
+ f->nfmt += rt - (Rune *)f->to;
+ f->to = rt;
+ if(fl & FmtLeft && __rfmtpad(f, w - n) < 0)
+ return -1;
+ }else{
+ if(!(fl & FmtLeft) && __fmtpad(f, w - n) < 0)
+ return -1;
+ t = (char*)f->to;
+ s = (char*)f->stop;
+ for(nc = n; nc > 0; nc--){
+ r = *(uchar*)m;
+ if(r < Runeself)
+ m++;
+ else if((me - m) >= UTFmax || fullrune(m, me-m))
+ m += chartorune(&r, m);
+ else
+ break;
+ FMTRUNE(f, t, s, r);
+ }
+ f->nfmt += t - (char *)f->to;
+ f->to = t;
+ if(fl & FmtLeft && __fmtpad(f, w - n) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+int
+__fmtrcpy(Fmt *f, const void *vm, int n)
+{
+ Rune r, *m, *me, *rt, *rs;
+ char *t, *s;
+ ulong fl;
+ int w;
+
+ m = (Rune*)vm;
+ w = f->width;
+ fl = f->flags;
+ if((fl & FmtPrec) && n > f->prec)
+ n = f->prec;
+ if(f->runes){
+ if(!(fl & FmtLeft) && __rfmtpad(f, w - n) < 0)
+ return -1;
+ rt = (Rune*)f->to;
+ rs = (Rune*)f->stop;
+ for(me = m + n; m < me; m++)
+ FMTRCHAR(f, rt, rs, *m);
+ f->nfmt += rt - (Rune *)f->to;
+ f->to = rt;
+ if(fl & FmtLeft && __rfmtpad(f, w - n) < 0)
+ return -1;
+ }else{
+ if(!(fl & FmtLeft) && __fmtpad(f, w - n) < 0)
+ return -1;
+ t = (char*)f->to;
+ s = (char*)f->stop;
+ for(me = m + n; m < me; m++){
+ r = *m;
+ FMTRUNE(f, t, s, r);
+ }
+ f->nfmt += t - (char *)f->to;
+ f->to = t;
+ if(fl & FmtLeft && __fmtpad(f, w - n) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+/* fmt out one character */
+int
+__charfmt(Fmt *f)
+{
+ char x[1];
+
+ x[0] = va_arg(f->args, int);
+ f->prec = 1;
+ return __fmtcpy(f, (const char*)x, 1, 1);
+}
+
+/* fmt out one rune */
+int
+__runefmt(Fmt *f)
+{
+ Rune x[1];
+
+ x[0] = va_arg(f->args, int);
+ return __fmtrcpy(f, (const void*)x, 1);
+}
+
+/* public helper routine: fmt out a null terminated string already in hand */
+int
+fmtstrcpy(Fmt *f, const char *s)
+{
+ int i, j;
+ Rune r;
+
+ if(!s)
+ return __fmtcpy(f, "<nil>", 5, 5);
+ /* if precision is specified, make sure we don't wander off the end */
+ if(f->flags & FmtPrec){
+ i = 0;
+ for(j=0; j<f->prec && s[i]; j++)
+ i += chartorune(&r, s+i);
+ return __fmtcpy(f, s, j, i);
+ }
+ return __fmtcpy(f, s, utflen(s), strlen(s));
+}
+
+/* fmt out a null terminated utf string */
+int
+__strfmt(Fmt *f)
+{
+ char *s;
+
+ s = va_arg(f->args, char *);
+ return fmtstrcpy(f, s);
+}
+
+/* public helper routine: fmt out a null terminated rune string already in hand */
+int
+fmtrunestrcpy(Fmt *f, Rune *s)
+{
+ Rune *e;
+ int n, p;
+
+ if(!s)
+ return __fmtcpy(f, "<nil>", 5, 5);
+ /* if precision is specified, make sure we don't wander off the end */
+ if(f->flags & FmtPrec){
+ p = f->prec;
+ for(n = 0; n < p; n++)
+ if(s[n] == 0)
+ break;
+ }else{
+ for(e = s; *e; e++)
+ ;
+ n = e - s;
+ }
+ return __fmtrcpy(f, s, n);
+}
+
+/* fmt out a null terminated rune string */
+int
+__runesfmt(Fmt *f)
+{
+ Rune *s;
+
+ s = va_arg(f->args, Rune *);
+ return fmtrunestrcpy(f, s);
+}
+
+/* fmt a % */
+int
+__percentfmt(Fmt *f)
+{
+ Rune x[1];
+
+ x[0] = f->r;
+ f->prec = 1;
+ return __fmtrcpy(f, (const void*)x, 1);
+}
+
+/* fmt an integer */
+int
+__ifmt(Fmt *f)
+{
+ char buf[70], *p, *conv;
+ uvlong vu;
+ ulong u;
+ int neg, base, i, n, fl, w, isv;
+
+ neg = 0;
+ fl = f->flags;
+ isv = 0;
+ vu = 0;
+ u = 0;
+ if(f->r == 'p'){
+ u = (ulong)va_arg(f->args, void*);
+ f->r = 'x';
+ fl |= FmtUnsigned;
+ }else if(fl & FmtVLong){
+ isv = 1;
+ if(fl & FmtUnsigned)
+ vu = va_arg(f->args, uvlong);
+ else
+ vu = va_arg(f->args, vlong);
+ }else if(fl & FmtLong){
+ if(fl & FmtUnsigned)
+ u = va_arg(f->args, ulong);
+ else
+ u = va_arg(f->args, long);
+ }else if(fl & FmtByte){
+ if(fl & FmtUnsigned)
+ u = (uchar)va_arg(f->args, int);
+ else
+ u = (char)va_arg(f->args, int);
+ }else if(fl & FmtShort){
+ if(fl & FmtUnsigned)
+ u = (ushort)va_arg(f->args, int);
+ else
+ u = (short)va_arg(f->args, int);
+ }else{
+ if(fl & FmtUnsigned)
+ u = va_arg(f->args, uint);
+ else
+ u = va_arg(f->args, int);
+ }
+ conv = "0123456789abcdef";
+ switch(f->r){
+ case 'd':
+ case 'i':
+ case 'u':
+ base = 10;
+ break;
+ case 'x':
+ base = 16;
+ break;
+ case 'X':
+ base = 16;
+ conv = "0123456789ABCDEF";
+ break;
+ case 'b':
+ base = 2;
+ break;
+ case 'o':
+ base = 8;
+ break;
+ default:
+ return -1;
+ }
+ if(!(fl & FmtUnsigned)){
+ if(isv && (vlong)vu < 0){
+ vu = -(vlong)vu;
+ neg = 1;
+ }else if(!isv && (long)u < 0){
+ u = -(long)u;
+ neg = 1;
+ }
+ }
+ p = buf + sizeof buf - 1;
+ n = 0;
+ if(isv){
+ while(vu){
+ i = vu % base;
+ vu /= base;
+ if((fl & FmtComma) && n % 4 == 3){
+ *p-- = ',';
+ n++;
+ }
+ *p-- = conv[i];
+ n++;
+ }
+ }else{
+ while(u){
+ i = u % base;
+ u /= base;
+ if((fl & FmtComma) && n % 4 == 3){
+ *p-- = ',';
+ n++;
+ }
+ *p-- = conv[i];
+ n++;
+ }
+ }
+ if(n == 0){
+ *p-- = '0';
+ n = 1;
+ }
+ for(w = f->prec; n < w && p > buf+3; n++)
+ *p-- = '0';
+ if(neg || (fl & (FmtSign|FmtSpace)))
+ n++;
+ if(fl & FmtSharp){
+ if(base == 16)
+ n += 2;
+ else if(base == 8){
+ if(p[1] == '0')
+ fl &= ~FmtSharp;
+ else
+ n++;
+ }
+ }
+ if((fl & FmtZero) && !(fl & (FmtLeft|FmtPrec))){
+ for(w = f->width; n < w && p > buf+3; n++)
+ *p-- = '0';
+ f->width = 0;
+ }
+ if(fl & FmtSharp){
+ if(base == 16)
+ *p-- = f->r;
+ if(base == 16 || base == 8)
+ *p-- = '0';
+ }
+ if(neg)
+ *p-- = '-';
+ else if(fl & FmtSign)
+ *p-- = '+';
+ else if(fl & FmtSpace)
+ *p-- = ' ';
+ f->flags &= ~FmtPrec;
+ return __fmtcpy(f, p + 1, n, n);
+}
+
+int
+__countfmt(Fmt *f)
+{
+ void *p;
+ ulong fl;
+
+ fl = f->flags;
+ p = va_arg(f->args, void*);
+ if(fl & FmtVLong){
+ *(vlong*)p = f->nfmt;
+ }else if(fl & FmtLong){
+ *(long*)p = f->nfmt;
+ }else if(fl & FmtByte){
+ *(char*)p = f->nfmt;
+ }else if(fl & FmtShort){
+ *(short*)p = f->nfmt;
+ }else{
+ *(int*)p = f->nfmt;
+ }
+ return 0;
+}
+
+int
+__flagfmt(Fmt *f)
+{
+ switch(f->r){
+ case ',':
+ f->flags |= FmtComma;
+ break;
+ case '-':
+ f->flags |= FmtLeft;
+ break;
+ case '+':
+ f->flags |= FmtSign;
+ break;
+ case '#':
+ f->flags |= FmtSharp;
+ break;
+ case ' ':
+ f->flags |= FmtSpace;
+ break;
+ case 'u':
+ f->flags |= FmtUnsigned;
+ break;
+ case 'h':
+ if(f->flags & FmtShort)
+ f->flags |= FmtByte;
+ f->flags |= FmtShort;
+ break;
+ case 'L':
+ f->flags |= FmtLDouble;
+ break;
+ case 'l':
+ if(f->flags & FmtLong)
+ f->flags |= FmtVLong;
+ f->flags |= FmtLong;
+ break;
+ }
+ return 1;
+}
+
+/* default error format */
+int
+__badfmt(Fmt *f)
+{
+ char x[3];
+
+ x[0] = '%';
+ x[1] = f->r;
+ x[2] = '%';
+ f->prec = 3;
+ __fmtcpy(f, (const void*)x, 3, 3);
+ return 0;
+}
diff --git a/libfmt/dorfmt.c b/libfmt/dorfmt.c
new file mode 100644
index 0000000..08f3c00
--- /dev/null
+++ b/libfmt/dorfmt.c
@@ -0,0 +1,60 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "plan9.h"
+#include "fmt.h"
+#include "fmtdef.h"
+
+/* format the output into f->to and return the number of characters fmted */
+
+int
+dorfmt(Fmt *f, const Rune *fmt)
+{
+ Rune *rt, *rs;
+ int r;
+ char *t, *s;
+ int nfmt;
+
+ nfmt = f->nfmt;
+ for(;;){
+ if(f->runes){
+ rt = f->to;
+ rs = f->stop;
+ while((r = *fmt++) && r != '%'){
+ FMTRCHAR(f, rt, rs, r);
+ }
+ f->nfmt += rt - (Rune *)f->to;
+ f->to = rt;
+ if(!r)
+ return f->nfmt - nfmt;
+ f->stop = rs;
+ }else{
+ t = f->to;
+ s = f->stop;
+ while((r = *fmt++) && r != '%'){
+ FMTRUNE(f, t, f->stop, r);
+ }
+ f->nfmt += t - (char *)f->to;
+ f->to = t;
+ if(!r)
+ return f->nfmt - nfmt;
+ f->stop = s;
+ }
+
+ fmt = __fmtdispatch(f, (Rune*)fmt, 1);
+ if(fmt == nil)
+ return -1;
+ }
+}
diff --git a/libfmt/errfmt.c b/libfmt/errfmt.c
new file mode 100644
index 0000000..b0eae73
--- /dev/null
+++ b/libfmt/errfmt.c
@@ -0,0 +1,28 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <errno.h>
+#include <string.h>
+#include "plan9.h"
+#include "fmt.h"
+#include "fmtdef.h"
+
+int
+__errfmt(Fmt *f)
+{
+ char *s;
+
+ s = strerror(errno);
+ return fmtstrcpy(f, s);
+}
diff --git a/libfmt/fltfmt.c b/libfmt/fltfmt.c
new file mode 100644
index 0000000..e600712
--- /dev/null
+++ b/libfmt/fltfmt.c
@@ -0,0 +1,392 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdio.h>
+#include <math.h>
+#include <float.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include "plan9.h"
+#include "fmtdef.h"
+
+enum
+{
+ FDIGIT = 30,
+ FDEFLT = 6,
+ NSIGNIF = 17
+};
+
+/*
+ * first few powers of 10, enough for about 1/2 of the
+ * total space for doubles.
+ */
+static double pows10[] =
+{
+ 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9,
+ 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19,
+ 1e20, 1e21, 1e22, 1e23, 1e24, 1e25, 1e26, 1e27, 1e28, 1e29,
+ 1e30, 1e31, 1e32, 1e33, 1e34, 1e35, 1e36, 1e37, 1e38, 1e39,
+ 1e40, 1e41, 1e42, 1e43, 1e44, 1e45, 1e46, 1e47, 1e48, 1e49,
+ 1e50, 1e51, 1e52, 1e53, 1e54, 1e55, 1e56, 1e57, 1e58, 1e59,
+ 1e60, 1e61, 1e62, 1e63, 1e64, 1e65, 1e66, 1e67, 1e68, 1e69,
+ 1e70, 1e71, 1e72, 1e73, 1e74, 1e75, 1e76, 1e77, 1e78, 1e79,
+ 1e80, 1e81, 1e82, 1e83, 1e84, 1e85, 1e86, 1e87, 1e88, 1e89,
+ 1e90, 1e91, 1e92, 1e93, 1e94, 1e95, 1e96, 1e97, 1e98, 1e99,
+ 1e100, 1e101, 1e102, 1e103, 1e104, 1e105, 1e106, 1e107, 1e108, 1e109,
+ 1e110, 1e111, 1e112, 1e113, 1e114, 1e115, 1e116, 1e117, 1e118, 1e119,
+ 1e120, 1e121, 1e122, 1e123, 1e124, 1e125, 1e126, 1e127, 1e128, 1e129,
+ 1e130, 1e131, 1e132, 1e133, 1e134, 1e135, 1e136, 1e137, 1e138, 1e139,
+ 1e140, 1e141, 1e142, 1e143, 1e144, 1e145, 1e146, 1e147, 1e148, 1e149,
+ 1e150, 1e151, 1e152, 1e153, 1e154, 1e155, 1e156, 1e157, 1e158, 1e159,
+};
+
+#define pow10(x) fmtpow10(x)
+
+static double
+pow10(int n)
+{
+ double d;
+ int neg;
+
+ neg = 0;
+ if(n < 0){
+ if(n < DBL_MIN_10_EXP){
+ return 0.;
+ }
+ neg = 1;
+ n = -n;
+ }else if(n > DBL_MAX_10_EXP){
+ return HUGE_VAL;
+ }
+ if(n < (int)(sizeof(pows10)/sizeof(pows10[0])))
+ d = pows10[n];
+ else{
+ d = pows10[sizeof(pows10)/sizeof(pows10[0]) - 1];
+ for(;;){
+ n -= sizeof(pows10)/sizeof(pows10[0]) - 1;
+ if(n < (int)(sizeof(pows10)/sizeof(pows10[0]))){
+ d *= pows10[n];
+ break;
+ }
+ d *= pows10[sizeof(pows10)/sizeof(pows10[0]) - 1];
+ }
+ }
+ if(neg){
+ return 1./d;
+ }
+ return d;
+}
+
+static int
+xadd(char *a, int n, int v)
+{
+ char *b;
+ int c;
+
+ if(n < 0 || n >= NSIGNIF)
+ return 0;
+ for(b = a+n; b >= a; b--) {
+ c = *b + v;
+ if(c <= '9') {
+ *b = c;
+ return 0;
+ }
+ *b = '0';
+ v = 1;
+ }
+ *a = '1'; /* overflow adding */
+ return 1;
+}
+
+static int
+xsub(char *a, int n, int v)
+{
+ char *b;
+ int c;
+
+ for(b = a+n; b >= a; b--) {
+ c = *b - v;
+ if(c >= '0') {
+ *b = c;
+ return 0;
+ }
+ *b = '9';
+ v = 1;
+ }
+ *a = '9'; /* underflow subtracting */
+ return 1;
+}
+
+static void
+xdtoa(Fmt *fmt, char *s2, double f)
+{
+ char s1[NSIGNIF+10];
+ double g, h;
+ int e, d, i, n;
+ int c1, c2, c3, c4, ucase, sign, chr, prec;
+
+ prec = FDEFLT;
+ if(fmt->flags & FmtPrec)
+ prec = fmt->prec;
+ if(prec > FDIGIT)
+ prec = FDIGIT;
+ if(__isNaN(f)) {
+ strcpy(s2, "NaN");
+ return;
+ }
+ if(__isInf(f, 1)) {
+ strcpy(s2, "+Inf");
+ return;
+ }
+ if(__isInf(f, -1)) {
+ strcpy(s2, "-Inf");
+ return;
+ }
+ sign = 0;
+ if(f < 0) {
+ f = -f;
+ sign++;
+ }
+ ucase = 0;
+ chr = fmt->r;
+ if(isupper(chr)) {
+ ucase = 1;
+ chr = tolower(chr);
+ }
+
+ e = 0;
+ g = f;
+ if(g != 0) {
+ frexp(f, &e);
+ e = e * .301029995664;
+ if(e >= -150 && e <= +150) {
+ d = 0;
+ h = f;
+ } else {
+ d = e/2;
+ h = f * pow10(-d);
+ }
+ g = h * pow10(d-e);
+ while(g < 1) {
+ e--;
+ g = h * pow10(d-e);
+ }
+ while(g >= 10) {
+ e++;
+ g = h * pow10(d-e);
+ }
+ }
+
+ /*
+ * convert NSIGNIF digits and convert
+ * back to get accuracy.
+ */
+ for(i=0; i<NSIGNIF; i++) {
+ d = g;
+ s1[i] = d + '0';
+ g = (g - d) * 10;
+ }
+ s1[i] = 0;
+
+ /*
+ * try decimal rounding to eliminate 9s
+ */
+ c2 = prec + 1;
+ if(chr == 'f')
+ c2 += e;
+ if(c2 >= NSIGNIF-2) {
+ strcpy(s2, s1);
+ d = e;
+ s1[NSIGNIF-2] = '0';
+ s1[NSIGNIF-1] = '0';
+ sprint(s1+NSIGNIF, "e%d", e-NSIGNIF+1);
+ g = strtod(s1, nil);
+ if(g == f)
+ goto found;
+ if(xadd(s1, NSIGNIF-3, 1)) {
+ e++;
+ sprint(s1+NSIGNIF, "e%d", e-NSIGNIF+1);
+ }
+ g = strtod(s1, nil);
+ if(g == f)
+ goto found;
+ strcpy(s1, s2);
+ e = d;
+ }
+
+ /*
+ * convert back so s1 gets exact answer
+ */
+ for(;;) {
+ sprint(s1+NSIGNIF, "e%d", e-NSIGNIF+1);
+ g = strtod(s1, nil);
+ if(f > g) {
+ if(xadd(s1, NSIGNIF-1, 1))
+ e--;
+ continue;
+ }
+ if(f < g) {
+ if(xsub(s1, NSIGNIF-1, 1))
+ e++;
+ continue;
+ }
+ break;
+ }
+
+found:
+ /*
+ * sign
+ */
+ d = 0;
+ i = 0;
+ if(sign)
+ s2[d++] = '-';
+ else if(fmt->flags & FmtSign)
+ s2[d++] = '+';
+ else if(fmt->flags & FmtSpace)
+ s2[d++] = ' ';
+
+ /*
+ * copy into final place
+ * c1 digits of leading '0'
+ * c2 digits from conversion
+ * c3 digits of trailing '0'
+ * c4 digits after '.'
+ */
+ c1 = 0;
+ c2 = prec + 1;
+ c3 = 0;
+ c4 = prec;
+ switch(chr) {
+ default:
+ if(xadd(s1, c2, 5))
+ e++;
+ break;
+ case 'g':
+ /*
+ * decide on 'e' of 'f' style convers
+ */
+ if(xadd(s1, c2, 5))
+ e++;
+ if(e >= -5 && e <= prec) {
+ c1 = -e - 1;
+ c4 = prec - e;
+ chr = 'h'; // flag for 'f' style
+ }
+ break;
+ case 'f':
+ if(xadd(s1, c2+e, 5))
+ e++;
+ c1 = -e;
+ if(c1 > prec)
+ c1 = c2;
+ c2 += e;
+ break;
+ }
+
+ /*
+ * clean up c1 c2 and c3
+ */
+ if(c1 < 0)
+ c1 = 0;
+ if(c2 < 0)
+ c2 = 0;
+ if(c2 > NSIGNIF) {
+ c3 = c2-NSIGNIF;
+ c2 = NSIGNIF;
+ }
+
+ /*
+ * copy digits
+ */
+ while(c1 > 0) {
+ if(c1+c2+c3 == c4)
+ s2[d++] = '.';
+ s2[d++] = '0';
+ c1--;
+ }
+ while(c2 > 0) {
+ if(c2+c3 == c4)
+ s2[d++] = '.';
+ s2[d++] = s1[i++];
+ c2--;
+ }
+ while(c3 > 0) {
+ if(c3 == c4)
+ s2[d++] = '.';
+ s2[d++] = '0';
+ c3--;
+ }
+
+ /*
+ * strip trailing '0' on g conv
+ */
+ if(fmt->flags & FmtSharp) {
+ if(0 == c4)
+ s2[d++] = '.';
+ } else
+ if(chr == 'g' || chr == 'h') {
+ for(n=d-1; n>=0; n--)
+ if(s2[n] != '0')
+ break;
+ for(i=n; i>=0; i--)
+ if(s2[i] == '.') {
+ d = n;
+ if(i != n)
+ d++;
+ break;
+ }
+ }
+ if(chr == 'e' || chr == 'g') {
+ if(ucase)
+ s2[d++] = 'E';
+ else
+ s2[d++] = 'e';
+ c1 = e;
+ if(c1 < 0) {
+ s2[d++] = '-';
+ c1 = -c1;
+ } else
+ s2[d++] = '+';
+ if(c1 >= 100) {
+ s2[d++] = c1/100 + '0';
+ c1 = c1%100;
+ }
+ s2[d++] = c1/10 + '0';
+ s2[d++] = c1%10 + '0';
+ }
+ s2[d] = 0;
+}
+
+static int
+floatfmt(Fmt *fmt, double f)
+{
+ char s[FDIGIT+10];
+
+ xdtoa(fmt, s, f);
+ fmt->flags &= FmtWidth|FmtLeft;
+ __fmtcpy(fmt, s, strlen(s), strlen(s));
+ return 0;
+}
+
+int
+__efgfmt(Fmt *f)
+{
+ double d;
+
+ d = va_arg(f->args, double);
+ return floatfmt(f, d);
+}
diff --git a/libfmt/fmt.c b/libfmt/fmt.c
new file mode 100644
index 0000000..0d9639d
--- /dev/null
+++ b/libfmt/fmt.c
@@ -0,0 +1,218 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "plan9.h"
+#include "fmt.h"
+#include "fmtdef.h"
+
+enum
+{
+ Maxfmt = 64
+};
+
+typedef struct Convfmt Convfmt;
+struct Convfmt
+{
+ int c;
+ volatile Fmts fmt; /* for spin lock in fmtfmt; avoids race due to write order */
+};
+
+struct
+{
+ /* lock by calling __fmtlock, __fmtunlock */
+ int nfmt;
+ Convfmt fmt[Maxfmt];
+} fmtalloc;
+
+static Convfmt knownfmt[] = {
+ ' ', __flagfmt,
+ '#', __flagfmt,
+ '%', __percentfmt,
+ '+', __flagfmt,
+ ',', __flagfmt,
+ '-', __flagfmt,
+ 'C', __runefmt, /* Plan 9 addition */
+ 'E', __efgfmt,
+ 'G', __efgfmt,
+ 'S', __runesfmt, /* Plan 9 addition */
+ 'X', __ifmt,
+ 'b', __ifmt, /* Plan 9 addition */
+ 'c', __charfmt,
+ 'd', __ifmt,
+ 'e', __efgfmt,
+ 'f', __efgfmt,
+ 'g', __efgfmt,
+ 'h', __flagfmt,
+ 'l', __flagfmt,
+ 'n', __countfmt,
+ 'o', __ifmt,
+ 'p', __ifmt,
+ 'r', __errfmt,
+ 's', __strfmt,
+ 'u', __flagfmt,
+ 'x', __ifmt,
+ 0, nil,
+};
+
+
+int (*fmtdoquote)(int);
+
+/*
+ * __fmtlock() must be set
+ */
+static int
+__fmtinstall(int c, Fmts f)
+{
+ Convfmt *p, *ep;
+
+ if(c<=0 || c>=65536)
+ return -1;
+ if(!f)
+ f = __badfmt;
+
+ ep = &fmtalloc.fmt[fmtalloc.nfmt];
+ for(p=fmtalloc.fmt; p<ep; p++)
+ if(p->c == c)
+ break;
+
+ if(p == &fmtalloc.fmt[Maxfmt])
+ return -1;
+
+ p->fmt = f;
+ if(p == ep){ /* installing a new format character */
+ fmtalloc.nfmt++;
+ p->c = c;
+ }
+
+ return 0;
+}
+
+int
+fmtinstall(int c, int (*f)(Fmt*))
+{
+ int ret;
+
+ __fmtlock();
+ ret = __fmtinstall(c, f);
+ __fmtunlock();
+ return ret;
+}
+
+static Fmts
+fmtfmt(int c)
+{
+ Convfmt *p, *ep;
+
+ ep = &fmtalloc.fmt[fmtalloc.nfmt];
+ for(p=fmtalloc.fmt; p<ep; p++)
+ if(p->c == c){
+ while(p->fmt == nil) /* loop until value is updated */
+ ;
+ return p->fmt;
+ }
+
+ /* is this a predefined format char? */
+ __fmtlock();
+ for(p=knownfmt; p->c; p++)
+ if(p->c == c){
+ __fmtinstall(p->c, p->fmt);
+ __fmtunlock();
+ return p->fmt;
+ }
+ __fmtunlock();
+
+ return __badfmt;
+}
+
+void*
+__fmtdispatch(Fmt *f, const void *fmt, int isrunes)
+{
+ Rune rune, r;
+ int i, n;
+
+ f->flags = 0;
+ f->width = f->prec = 0;
+
+ for(;;){
+ if(isrunes){
+ r = *(Rune*)fmt;
+ fmt = (Rune*)fmt + 1;
+ }else{
+ fmt = (char*)fmt + chartorune(&rune, (char*)fmt);
+ r = rune;
+ }
+ f->r = r;
+ switch(r){
+ case '\0':
+ return nil;
+ case '.':
+ f->flags |= FmtWidth|FmtPrec;
+ continue;
+ case '0':
+ if(!(f->flags & FmtWidth)){
+ f->flags |= FmtZero;
+ continue;
+ }
+ /* fall through */
+ case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ i = 0;
+ while(r >= '0' && r <= '9'){
+ i = i * 10 + r - '0';
+ if(isrunes){
+ r = *(Rune*)fmt;
+ fmt = (Rune*)fmt + 1;
+ }else{
+ r = *(char*)fmt;
+ fmt = (char*)fmt + 1;
+ }
+ }
+ if(isrunes)
+ fmt = (Rune*)fmt - 1;
+ else
+ fmt = (char*)fmt - 1;
+ numflag:
+ if(f->flags & FmtWidth){
+ f->flags |= FmtPrec;
+ f->prec = i;
+ }else{
+ f->flags |= FmtWidth;
+ f->width = i;
+ }
+ continue;
+ case '*':
+ i = va_arg(f->args, int);
+ if(i < 0){
+ /*
+ * negative precision =>
+ * ignore the precision.
+ */
+ if(f->flags & FmtPrec){
+ f->flags &= ~FmtPrec;
+ f->prec = 0;
+ continue;
+ }
+ i = -i;
+ f->flags |= FmtLeft;
+ }
+ goto numflag;
+ }
+ n = (*fmtfmt(r))(f);
+ if(n < 0)
+ return nil;
+ if(n == 0)
+ return (void*)fmt;
+ }
+}
diff --git a/libfmt/fmtdef.h b/libfmt/fmtdef.h
new file mode 100644
index 0000000..d367831
--- /dev/null
+++ b/libfmt/fmtdef.h
@@ -0,0 +1,112 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
+ * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+
+/*
+ * dofmt -- format to a buffer
+ * the number of characters formatted is returned,
+ * or -1 if there was an error.
+ * if the buffer is ever filled, flush is called.
+ * it should reset the buffer and return whether formatting should continue.
+ */
+
+typedef int (*Fmts)(Fmt*);
+
+typedef struct Quoteinfo Quoteinfo;
+struct Quoteinfo
+{
+ int quoted; /* if set, string must be quoted */
+ int nrunesin; /* number of input runes that can be accepted */
+ int nbytesin; /* number of input bytes that can be accepted */
+ int nrunesout; /* number of runes that will be generated */
+ int nbytesout; /* number of bytes that will be generated */
+};
+
+/* Edit .+1,/^$/ |cfn |grep -v static | grep __ */
+double __Inf(int sign);
+double __NaN(void);
+int __badfmt(Fmt *f);
+int __charfmt(Fmt *f);
+int __countfmt(Fmt *f);
+int __efgfmt(Fmt *fmt);
+int __errfmt(Fmt *f);
+int __flagfmt(Fmt *f);
+int __fmtFdFlush(Fmt *f);
+int __fmtcpy(Fmt *f, const void *vm, int n, int sz);
+void* __fmtdispatch(Fmt *f, const void *fmt, int isrunes);
+void * __fmtflush(Fmt *f, void *t, int len);
+void __fmtlock(void);
+int __fmtpad(Fmt *f, int n);
+double __fmtpow10(int n);
+int __fmtrcpy(Fmt *f, const void *vm, int n);
+void __fmtunlock(void);
+int __ifmt(Fmt *f);
+int __isInf(double d, int sign);
+int __isNaN(double d);
+int __needsquotes(char *s, int *quotelenp);
+int __percentfmt(Fmt *f);
+void __quotesetup(char *s, Rune *r, int nin, int nout, Quoteinfo *q, int sharp, int runesout);
+int __quotestrfmt(int runesin, Fmt *f);
+int __rfmtpad(Fmt *f, int n);
+int __runefmt(Fmt *f);
+int __runeneedsquotes(Rune *r, int *quotelenp);
+int __runesfmt(Fmt *f);
+int __strfmt(Fmt *f);
+
+#define FMTCHAR(f, t, s, c)\
+ do{\
+ if(t + 1 > (char*)s){\
+ t = __fmtflush(f, t, 1);\
+ if(t != nil)\
+ s = f->stop;\
+ else\
+ return -1;\
+ }\
+ *t++ = c;\
+ }while(0)
+
+#define FMTRCHAR(f, t, s, c)\
+ do{\
+ if(t + 1 > (Rune*)s){\
+ t = __fmtflush(f, t, sizeof(Rune));\
+ if(t != nil)\
+ s = f->stop;\
+ else\
+ return -1;\
+ }\
+ *t++ = c;\
+ }while(0)
+
+#define FMTRUNE(f, t, s, r)\
+ do{\
+ Rune _rune;\
+ int _runelen;\
+ if(t + UTFmax > (char*)s && t + (_runelen = runelen(r)) > (char*)s){\
+ t = __fmtflush(f, t, _runelen);\
+ if(t != nil)\
+ s = f->stop;\
+ else\
+ return -1;\
+ }\
+ if(r < Runeself)\
+ *t++ = r;\
+ else{\
+ _rune = r;\
+ t += runetochar(t, &_rune);\
+ }\
+ }while(0)
+
+#ifndef va_copy
+# define va_copy(a,b) (a) = (b)
+#endif
+
diff --git a/libfmt/fmtfd.c b/libfmt/fmtfd.c
new file mode 100644
index 0000000..9f35f02
--- /dev/null
+++ b/libfmt/fmtfd.c
@@ -0,0 +1,46 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "plan9.h"
+#include "fmt.h"
+#include "fmtdef.h"
+
+/*
+ * public routine for final flush of a formatting buffer
+ * to a file descriptor; returns total char count.
+ */
+int
+fmtfdflush(Fmt *f)
+{
+ if(__fmtFdFlush(f) <= 0)
+ return -1;
+ return f->nfmt;
+}
+
+/*
+ * initialize an output buffer for buffered printing
+ */
+int
+fmtfdinit(Fmt *f, int fd, char *buf, int size)
+{
+ f->runes = 0;
+ f->start = buf;
+ f->to = buf;
+ f->stop = buf + size;
+ f->flush = __fmtFdFlush;
+ f->farg = (void*)(uintptr_t)fd;
+ f->nfmt = 0;
+ return 0;
+}
diff --git a/libfmt/fmtfdflush.c b/libfmt/fmtfdflush.c
new file mode 100644
index 0000000..9209efc
--- /dev/null
+++ b/libfmt/fmtfdflush.c
@@ -0,0 +1,34 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <unistd.h>
+#include "plan9.h"
+#include "fmt.h"
+#include "fmtdef.h"
+
+/*
+ * generic routine for flushing a formatting buffer
+ * to a file descriptor
+ */
+int
+__fmtFdFlush(Fmt *f)
+{
+ int n;
+
+ n = (char*)f->to - (char*)f->start;
+ if(n && write((uintptr_t)f->farg, f->start, n) != n)
+ return 0;
+ f->to = f->start;
+ return 1;
+}
diff --git a/libfmt/fmtinstall.3 b/libfmt/fmtinstall.3
new file mode 100644
index 0000000..735b8a6
--- /dev/null
+++ b/libfmt/fmtinstall.3
@@ -0,0 +1,379 @@
+.deEX
+.ift .ft5
+.nf
+..
+.deEE
+.ft1
+.fi
+..
+.TH FMTINSTALL 3
+.SH NAME
+fmtinstall, dofmt, dorfmt, fmtprint, fmtvprint, fmtrune, fmtstrcpy, fmtrunestrcpy, fmtfdinit, fmtfdflush, fmtstrinit, fmtstrflush, runefmtstrinit, runefmtstrflush, errfmt \- support for user-defined print formats and output routines
+.SH SYNOPSIS
+.B #include <utf.h>
+.br
+.B #include <fmt.h>
+.PP
+.ft L
+.nf
+.ta \w' 'u +\w' 'u +\w' 'u +\w' 'u +\w' 'u
+typedef struct Fmt Fmt;
+struct Fmt{
+ uchar runes; /* output buffer is runes or chars? */
+ void *start; /* of buffer */
+ void *to; /* current place in the buffer */
+ void *stop; /* end of the buffer; overwritten if flush fails */
+ int (*flush)(Fmt*); /* called when to == stop */
+ void *farg; /* to make flush a closure */
+ int nfmt; /* num chars formatted so far */
+ va_list args; /* args passed to dofmt */
+ int r; /* % format Rune */
+ int width;
+ int prec;
+ ulong flags;
+};
+
+enum{
+ FmtWidth = 1,
+ FmtLeft = FmtWidth << 1,
+ FmtPrec = FmtLeft << 1,
+ FmtSharp = FmtPrec << 1,
+ FmtSpace = FmtSharp << 1,
+ FmtSign = FmtSpace << 1,
+ FmtZero = FmtSign << 1,
+ FmtUnsigned = FmtZero << 1,
+ FmtShort = FmtUnsigned << 1,
+ FmtLong = FmtShort << 1,
+ FmtVLong = FmtLong << 1,
+ FmtComma = FmtVLong << 1,
+
+ FmtFlag = FmtComma << 1
+};
+.fi
+.PP
+.B
+.ta \w'\fLchar* 'u
+
+.PP
+.B
+int fmtfdinit(Fmt *f, int fd, char *buf, int nbuf);
+.PP
+.B
+int fmtfdflush(Fmt *f);
+.PP
+.B
+int fmtstrinit(Fmt *f);
+.PP
+.B
+char* fmtstrflush(Fmt *f);
+.PP
+.B
+int runefmtstrinit(Fmt *f);
+.PP
+.B
+Rune* runefmtstrflush(Fmt *f);
+
+.PP
+.B
+int fmtinstall(int c, int (*fn)(Fmt*));
+.PP
+.B
+int dofmt(Fmt *f, char *fmt);
+.PP
+.B
+int dorfmt(Fmt*, Rune *fmt);
+.PP
+.B
+int fmtprint(Fmt *f, char *fmt, ...);
+.PP
+.B
+int fmtvprint(Fmt *f, char *fmt, va_list v);
+.PP
+.B
+int fmtrune(Fmt *f, int r);
+.PP
+.B
+int fmtstrcpy(Fmt *f, char *s);
+.PP
+.B
+int fmtrunestrcpy(Fmt *f, Rune *s);
+.PP
+.B
+int errfmt(Fmt *f);
+.SH DESCRIPTION
+The interface described here allows the construction of custom
+.IR print (3)
+verbs and output routines.
+In essence, they provide access to the workings of the formatted print code.
+.PP
+The
+.IR print (3)
+suite maintains its state with a data structure called
+.BR Fmt .
+A typical call to
+.IR print (3)
+or its relatives initializes a
+.B Fmt
+structure, passes it to subsidiary routines to process the output,
+and finishes by emitting any saved state recorded in the
+.BR Fmt .
+The details of the
+.B Fmt
+are unimportant to outside users, except insofar as the general
+design influences the interface.
+The
+.B Fmt
+records whether the output is in runes or bytes,
+the verb being processed, its precision and width,
+and buffering parameters.
+Most important, it also records a
+.I flush
+routine that the library will call if a buffer overflows.
+When printing to a file descriptor, the flush routine will
+emit saved characters and reset the buffer; when printing
+to an allocated string, it will resize the string to receive more output.
+The flush routine is nil when printing to fixed-size buffers.
+User code need never provide a flush routine; this is done internally
+by the library.
+.SS Custom output routines
+To write a custom output routine, such as an error handler that
+formats and prints custom error messages, the output sequence can be run
+from outside the library using the routines described here.
+There are two main cases: output to an open file descriptor
+and output to a string.
+.PP
+To write to a file descriptor, call
+.I fmtfdinit
+to initialize the local
+.B Fmt
+structure
+.IR f ,
+giving the file descriptor
+.IR fd ,
+the buffer
+.IR buf ,
+and its size
+.IR nbuf .
+Then call
+.IR fmtprint
+or
+.IR fmtvprint
+to generate the output.
+These behave like
+.B fprint
+(see
+.IR print (3))
+or
+.B vfprint
+except that the characters are buffered until
+.I fmtfdflush
+is called and the return value is either 0 or \-1.
+A typical example of this sequence appears in the Examples section.
+.PP
+The same basic sequence applies when outputting to an allocated string:
+call
+.I fmtstrinit
+to initialize the
+.BR Fmt ,
+then call
+.I fmtprint
+and
+.I fmtvprint
+to generate the output.
+Finally,
+.I fmtstrflush
+will return the allocated string, which should be freed after use.
+To output to a rune string, use
+.I runefmtstrinit
+and
+.IR runefmtstrflush .
+Regardless of the output style or type,
+.I fmtprint
+or
+.I fmtvprint
+generates the characters.
+.SS Custom format verbs
+.I Fmtinstall
+is used to install custom verbs and flags labeled by character
+.IR c ,
+which may be any non-zero Unicode character.
+.I Fn
+should be declared as
+.IP
+.EX
+int fn(Fmt*)
+.EE
+.PP
+.IB Fp ->r
+is the flag or verb character to cause
+.I fn
+to be called.
+In
+.IR fn ,
+.IB fp ->width ,
+.IB fp ->prec
+are the width and precision, and
+.IB fp ->flags
+the decoded flags for the verb (see
+.IR print (3)
+for a description of these items).
+The standard flag values are:
+.B FmtSign
+.RB ( + ),
+.B FmtLeft
+.RB ( - ),
+.B FmtSpace
+.RB ( '\ ' ),
+.B FmtSharp
+.RB ( # ),
+.B FmtComma
+.RB ( , ),
+.B FmtLong
+.RB ( l ),
+.B FmtShort
+.RB ( h ),
+.B FmtUnsigned
+.RB ( u ),
+and
+.B FmtVLong
+.RB ( ll ).
+The flag bits
+.B FmtWidth
+and
+.B FmtPrec
+identify whether a width and precision were specified.
+.PP
+.I Fn
+is passed a pointer to the
+.B Fmt
+structure recording the state of the output.
+If
+.IB fp ->r
+is a verb (rather than a flag),
+.I fn
+should use
+.B Fmt->args
+to fetch its argument from the list,
+then format it, and return zero.
+If
+.IB fp ->r
+is a flag,
+.I fn
+should return one.
+All interpretation of
+.IB fp ->width\f1,
+.IB fp ->prec\f1,
+and
+.IB fp-> flags
+is left up to the conversion routine.
+.I Fmtinstall
+returns 0 if the installation succeeds, \-1 if it fails.
+.PP
+.IR Fmtprint
+and
+.IR fmtvprint
+may be called to
+help prepare output in custom conversion routines.
+However, these functions clear the width, precision, and flags.
+Both functions return 0 for success and \-1 for failure.
+.PP
+The functions
+.I dofmt
+and
+.I dorfmt
+are the underlying formatters; they
+use the existing contents of
+.B Fmt
+and should be called only by sophisticated conversion routines.
+These routines return the number of characters (bytes of UTF or runes)
+produced.
+.PP
+Some internal functions may be useful to format primitive types.
+They honor the width, precision and flags as described in
+.IR print (3).
+.I Fmtrune
+formats a single character
+.BR r .
+.I Fmtstrcpy
+formats a string
+.BR s ;
+.I fmtrunestrcpy
+formats a rune string
+.BR s .
+.I Errfmt
+formats the system error string.
+All these routines return zero for successful execution.
+Conversion routines that call these functions will work properly
+regardless of whether the output is bytes or runes.
+.\" .PP
+.\" .IR 2c (1)
+.\" describes the C directive
+.\" .B #pragma
+.\" .B varargck
+.\" that can be used to provide type-checking for custom print verbs and output routines.
+.SH EXAMPLES
+This function prints an error message with a variable
+number of arguments and then quits.
+Compared to the corresponding example in
+.IR print (3),
+this version uses a smaller buffer, will never truncate
+the output message, but might generate multiple
+.B write
+system calls to produce its output.
+.IP
+.EX
+.ta 6n +6n +6n +6n +6n +6n +6n +6n +6n
+#pragma varargck argpos error 1
+
+void fatal(char *fmt, ...)
+{
+ Fmt f;
+ char buf[64];
+ va_list arg;
+
+ fmtfdinit(&f, 1, buf, sizeof buf);
+ fmtprint(&f, "fatal: ");
+ va_start(arg, fmt);
+ fmtvprint(&f, fmt, arg);
+ va_end(arg);
+ fmtprint(&f, "\en");
+ fmtfdflush(&f);
+ exits("fatal error");
+}
+.EE
+.PP
+This example adds a verb to print complex numbers.
+.IP
+.EX
+typedef
+struct {
+ double r, i;
+} Complex;
+
+#pragma varargck type "X" Complex
+
+int
+Xfmt(Fmt *f)
+{
+ Complex c;
+
+ c = va_arg(f->args, Complex);
+ return fmtprint(f, "(%g,%g)", c.r, c.i);
+}
+
+main(...)
+{
+ Complex x = (Complex){ 1.5, -2.3 };
+
+ fmtinstall('X', Xfmt);
+ print("x = %X\en", x);
+}
+.EE
+.SH SOURCE
+.B http://swtch.com/plan9port/unix
+.SH SEE ALSO
+.IR print (3),
+.IR utf (7)
+.SH DIAGNOSTICS
+These routines return negative numbers or nil for errors and set
+.IR errstr .
diff --git a/libfmt/fmtlock.c b/libfmt/fmtlock.c
new file mode 100644
index 0000000..5c7afbc
--- /dev/null
+++ b/libfmt/fmtlock.c
@@ -0,0 +1,27 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include "plan9.h"
+#include "fmt.h"
+#include "fmtdef.h"
+
+void
+__fmtlock(void)
+{
+}
+
+void
+__fmtunlock(void)
+{
+}
diff --git a/libfmt/fmtprint.c b/libfmt/fmtprint.c
new file mode 100644
index 0000000..3367125
--- /dev/null
+++ b/libfmt/fmtprint.c
@@ -0,0 +1,48 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "plan9.h"
+#include "fmt.h"
+#include "fmtdef.h"
+
+/*
+ * format a string into the output buffer
+ * designed for formats which themselves call fmt,
+ * but ignore any width flags
+ */
+int
+fmtprint(Fmt *f, const char *fmt, ...)
+{
+ va_list va;
+ int n;
+
+ f->flags = 0;
+ f->width = 0;
+ f->prec = 0;
+ va_copy(va, f->args);
+ va_end(f->args);
+ va_start(f->args, fmt);
+ n = dofmt(f, fmt);
+ va_end(f->args);
+ f->flags = 0;
+ f->width = 0;
+ f->prec = 0;
+ va_copy(f->args,va);
+ va_end(va);
+ if(n >= 0)
+ return 0;
+ return n;
+}
+
diff --git a/libfmt/fmtquote.c b/libfmt/fmtquote.c
new file mode 100644
index 0000000..b6f2e17
--- /dev/null
+++ b/libfmt/fmtquote.c
@@ -0,0 +1,264 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "plan9.h"
+#include "fmt.h"
+#include "fmtdef.h"
+
+/*
+ * How many bytes of output UTF will be produced by quoting (if necessary) this string?
+ * How many runes? How much of the input will be consumed?
+ * The parameter q is filled in by __quotesetup.
+ * The string may be UTF or Runes (s or r).
+ * Return count does not include NUL.
+ * Terminate the scan at the first of:
+ * NUL in input
+ * count exceeded in input
+ * count exceeded on output
+ * *ninp is set to number of input bytes accepted.
+ * nin may be <0 initially, to avoid checking input by count.
+ */
+void
+__quotesetup(char *s, Rune *r, int nin, int nout, Quoteinfo *q, int sharp, int runesout)
+{
+ int w;
+ Rune c;
+
+ q->quoted = 0;
+ q->nbytesout = 0;
+ q->nrunesout = 0;
+ q->nbytesin = 0;
+ q->nrunesin = 0;
+ if(sharp || nin==0 || (s && *s=='\0') || (r && *r=='\0')){
+ if(nout < 2)
+ return;
+ q->quoted = 1;
+ q->nbytesout = 2;
+ q->nrunesout = 2;
+ }
+ for(; nin!=0; nin--){
+ if(s)
+ w = chartorune(&c, s);
+ else{
+ c = *r;
+ w = runelen(c);
+ }
+
+ if(c == '\0')
+ break;
+ if(runesout){
+ if(q->nrunesout+1 > nout)
+ break;
+ }else{
+ if(q->nbytesout+w > nout)
+ break;
+ }
+
+ if((c <= L' ') || (c == L'\'') || (fmtdoquote!=nil && fmtdoquote(c))){
+ if(!q->quoted){
+ if(runesout){
+ if(1+q->nrunesout+1+1 > nout) /* no room for quotes */
+ break;
+ }else{
+ if(1+q->nbytesout+w+1 > nout) /* no room for quotes */
+ break;
+ }
+ q->nrunesout += 2; /* include quotes */
+ q->nbytesout += 2; /* include quotes */
+ q->quoted = 1;
+ }
+ if(c == '\'') {
+ if(runesout){
+ if(1+q->nrunesout+1 > nout) /* no room for quotes */
+ break;
+ }else{
+ if(1+q->nbytesout+w > nout) /* no room for quotes */
+ break;
+ }
+ q->nbytesout++;
+ q->nrunesout++; /* quotes reproduce as two characters */
+ }
+ }
+
+ /* advance input */
+ if(s)
+ s += w;
+ else
+ r++;
+ q->nbytesin += w;
+ q->nrunesin++;
+
+ /* advance output */
+ q->nbytesout += w;
+ q->nrunesout++;
+ }
+}
+
+static int
+qstrfmt(char *sin, Rune *rin, Quoteinfo *q, Fmt *f)
+{
+ Rune r, *rm, *rme;
+ char *t, *s, *m, *me;
+ Rune *rt, *rs;
+ ulong fl;
+ int nc, w;
+
+ m = sin;
+ me = m + q->nbytesin;
+ rm = rin;
+ rme = rm + q->nrunesin;
+
+ w = f->width;
+ fl = f->flags;
+ if(f->runes){
+ if(!(fl & FmtLeft) && __rfmtpad(f, w - q->nrunesout) < 0)
+ return -1;
+ }else{
+ if(!(fl & FmtLeft) && __fmtpad(f, w - q->nbytesout) < 0)
+ return -1;
+ }
+ t = (char*)f->to;
+ s = (char*)f->stop;
+ rt = (Rune*)f->to;
+ rs = (Rune*)f->stop;
+ if(f->runes)
+ FMTRCHAR(f, rt, rs, '\'');
+ else
+ FMTRUNE(f, t, s, '\'');
+ for(nc = q->nrunesin; nc > 0; nc--){
+ if(sin){
+ r = *(uchar*)m;
+ if(r < Runeself)
+ m++;
+ else if((me - m) >= UTFmax || fullrune(m, me-m))
+ m += chartorune(&r, m);
+ else
+ break;
+ }else{
+ if(rm >= rme)
+ break;
+ r = *(uchar*)rm++;
+ }
+ if(f->runes){
+ FMTRCHAR(f, rt, rs, r);
+ if(r == '\'')
+ FMTRCHAR(f, rt, rs, r);
+ }else{
+ FMTRUNE(f, t, s, r);
+ if(r == '\'')
+ FMTRUNE(f, t, s, r);
+ }
+ }
+
+ if(f->runes){
+ FMTRCHAR(f, rt, rs, '\'');
+ USED(rs);
+ f->nfmt += rt - (Rune *)f->to;
+ f->to = rt;
+ if(fl & FmtLeft && __rfmtpad(f, w - q->nrunesout) < 0)
+ return -1;
+ }else{
+ FMTRUNE(f, t, s, '\'');
+ USED(s);
+ f->nfmt += t - (char *)f->to;
+ f->to = t;
+ if(fl & FmtLeft && __fmtpad(f, w - q->nbytesout) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+int
+__quotestrfmt(int runesin, Fmt *f)
+{
+ int nin, outlen;
+ Rune *r;
+ char *s;
+ Quoteinfo q;
+
+ nin = -1;
+ if(f->flags&FmtPrec)
+ nin = f->prec;
+ if(runesin){
+ r = va_arg(f->args, Rune *);
+ s = nil;
+ }else{
+ s = va_arg(f->args, char *);
+ r = nil;
+ }
+ if(!s && !r)
+ return __fmtcpy(f, (void*)"<nil>", 5, 5);
+
+ if(f->flush)
+ outlen = 0x7FFFFFFF; /* if we can flush, no output limit */
+ else if(f->runes)
+ outlen = (Rune*)f->stop - (Rune*)f->to;
+ else
+ outlen = (char*)f->stop - (char*)f->to;
+
+ __quotesetup(s, r, nin, outlen, &q, f->flags&FmtSharp, f->runes);
+//print("bytes in %d bytes out %d runes in %d runesout %d\n", q.nbytesin, q.nbytesout, q.nrunesin, q.nrunesout);
+
+ if(runesin){
+ if(!q.quoted)
+ return __fmtrcpy(f, r, q.nrunesin);
+ return qstrfmt(nil, r, &q, f);
+ }
+
+ if(!q.quoted)
+ return __fmtcpy(f, s, q.nrunesin, q.nbytesin);
+ return qstrfmt(s, nil, &q, f);
+}
+
+int
+quotestrfmt(Fmt *f)
+{
+ return __quotestrfmt(0, f);
+}
+
+int
+quoterunestrfmt(Fmt *f)
+{
+ return __quotestrfmt(1, f);
+}
+
+void
+quotefmtinstall(void)
+{
+ fmtinstall('q', quotestrfmt);
+ fmtinstall('Q', quoterunestrfmt);
+}
+
+int
+__needsquotes(char *s, int *quotelenp)
+{
+ Quoteinfo q;
+
+ __quotesetup(s, nil, -1, 0x7FFFFFFF, &q, 0, 0);
+ *quotelenp = q.nbytesout;
+
+ return q.quoted;
+}
+
+int
+__runeneedsquotes(Rune *r, int *quotelenp)
+{
+ Quoteinfo q;
+
+ __quotesetup(nil, r, -1, 0x7FFFFFFF, &q, 0, 0);
+ *quotelenp = q.nrunesout;
+
+ return q.quoted;
+}
diff --git a/libfmt/fmtrune.c b/libfmt/fmtrune.c
new file mode 100644
index 0000000..b1ddd3b
--- /dev/null
+++ b/libfmt/fmtrune.c
@@ -0,0 +1,40 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "plan9.h"
+#include "fmt.h"
+#include "fmtdef.h"
+
+int
+fmtrune(Fmt *f, int r)
+{
+ Rune *rt;
+ char *t;
+ int n;
+
+ if(f->runes){
+ rt = (Rune*)f->to;
+ FMTRCHAR(f, rt, f->stop, r);
+ f->to = rt;
+ n = 1;
+ }else{
+ t = (char*)f->to;
+ FMTRUNE(f, t, f->stop, r);
+ n = t - (char*)f->to;
+ f->to = t;
+ }
+ f->nfmt += n;
+ return 0;
+}
diff --git a/libfmt/fmtstr.c b/libfmt/fmtstr.c
new file mode 100644
index 0000000..a5f8f8d
--- /dev/null
+++ b/libfmt/fmtstr.c
@@ -0,0 +1,27 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdlib.h>
+#include <stdarg.h>
+#include "plan9.h"
+#include "fmt.h"
+#include "fmtdef.h"
+
+char*
+fmtstrflush(Fmt *f)
+{
+ if(f->start == nil)
+ return nil;
+ *(char*)f->to = '\0';
+ return (char*)f->start;
+}
diff --git a/libfmt/fmtvprint.c b/libfmt/fmtvprint.c
new file mode 100644
index 0000000..a02e673
--- /dev/null
+++ b/libfmt/fmtvprint.c
@@ -0,0 +1,49 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "plan9.h"
+#include "fmt.h"
+#include "fmtdef.h"
+
+
+/*
+ * format a string into the output buffer
+ * designed for formats which themselves call fmt,
+ * but ignore any width flags
+ */
+int
+fmtvprint(Fmt *f, const char *fmt, va_list args)
+{
+ va_list va;
+ int n;
+
+ f->flags = 0;
+ f->width = 0;
+ f->prec = 0;
+ va_copy(va,f->args);
+ va_end(f->args);
+ va_copy(f->args,args);
+ n = dofmt(f, fmt);
+ f->flags = 0;
+ f->width = 0;
+ f->prec = 0;
+ va_end(f->args);
+ va_copy(f->args,va);
+ va_end(va);
+ if(n >= 0)
+ return 0;
+ return n;
+}
+
diff --git a/libfmt/fprint.c b/libfmt/fprint.c
new file mode 100644
index 0000000..b9ca15e
--- /dev/null
+++ b/libfmt/fprint.c
@@ -0,0 +1,29 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include "plan9.h"
+#include "fmt.h"
+#include "fmtdef.h"
+
+int
+fprint(int fd, const char *fmt, ...)
+{
+ int n;
+ va_list args;
+
+ va_start(args, fmt);
+ n = vfprint(fd, fmt, args);
+ va_end(args);
+ return n;
+}
diff --git a/libfmt/libfmt.a b/libfmt/libfmt.a
new file mode 100644
index 0000000..3b6fcd5
--- /dev/null
+++ b/libfmt/libfmt.a
Binary files differ
diff --git a/libfmt/nan64.c b/libfmt/nan64.c
new file mode 100644
index 0000000..6e355a2
--- /dev/null
+++ b/libfmt/nan64.c
@@ -0,0 +1,67 @@
+/*
+ * 64-bit IEEE not-a-number routines.
+ * This is big/little-endian portable assuming that
+ * the 64-bit doubles and 64-bit integers have the
+ * same byte ordering.
+ */
+
+#include "plan9.h"
+#include "fmt.h"
+#include "fmtdef.h"
+
+#if defined (__APPLE__) || (__powerpc__)
+#define _NEEDLL
+#endif
+
+static uvlong uvnan = ((uvlong)0x7FF00000<<32)|0x00000001;
+static uvlong uvinf = ((uvlong)0x7FF00000<<32)|0x00000000;
+static uvlong uvneginf = ((uvlong)0xFFF00000<<32)|0x00000000;
+
+double
+__NaN(void)
+{
+ uvlong *p;
+
+ /* gcc complains about "return *(double*)&uvnan;" */
+ p = &uvnan;
+ return *(double*)p;
+}
+
+int
+__isNaN(double d)
+{
+ uvlong x;
+ double *p;
+
+ p = &d;
+ x = *(uvlong*)p;
+ return (ulong)(x>>32)==0x7FF00000 && !__isInf(d, 0);
+}
+
+double
+__Inf(int sign)
+{
+ uvlong *p;
+
+ if(sign < 0)
+ p = &uvinf;
+ else
+ p = &uvneginf;
+ return *(double*)p;
+}
+
+int
+__isInf(double d, int sign)
+{
+ uvlong x;
+ double *p;
+
+ p = &d;
+ x = *(uvlong*)p;
+ if(sign == 0)
+ return x==uvinf || x==uvneginf;
+ else if(sign > 0)
+ return x==uvinf;
+ else
+ return x==uvneginf;
+}
diff --git a/libfmt/pow10.c b/libfmt/pow10.c
new file mode 100644
index 0000000..5d578f9
--- /dev/null
+++ b/libfmt/pow10.c
@@ -0,0 +1,57 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "plan9.h"
+#include "fmt.h"
+#include "fmtdef.h"
+
+/*
+ * this table might overflow 127-bit exponent representations.
+ * in that case, truncate it after 1.0e38.
+ * it is important to get all one can from this
+ * routine since it is used in atof to scale numbers.
+ * the presumption is that C converts fp numbers better
+ * than multipication of lower powers of 10.
+ */
+
+static
+double tab[] =
+{
+ 1.0e0, 1.0e1, 1.0e2, 1.0e3, 1.0e4, 1.0e5, 1.0e6, 1.0e7, 1.0e8, 1.0e9,
+ 1.0e10,1.0e11,1.0e12,1.0e13,1.0e14,1.0e15,1.0e16,1.0e17,1.0e18,1.0e19,
+ 1.0e20,1.0e21,1.0e22,1.0e23,1.0e24,1.0e25,1.0e26,1.0e27,1.0e28,1.0e29,
+ 1.0e30,1.0e31,1.0e32,1.0e33,1.0e34,1.0e35,1.0e36,1.0e37,1.0e38,1.0e39,
+ 1.0e40,1.0e41,1.0e42,1.0e43,1.0e44,1.0e45,1.0e46,1.0e47,1.0e48,1.0e49,
+ 1.0e50,1.0e51,1.0e52,1.0e53,1.0e54,1.0e55,1.0e56,1.0e57,1.0e58,1.0e59,
+ 1.0e60,1.0e61,1.0e62,1.0e63,1.0e64,1.0e65,1.0e66,1.0e67,1.0e68,1.0e69,
+};
+
+double
+__fmtpow10(int n)
+{
+ int m;
+
+ if(n < 0) {
+ n = -n;
+ if(n < (int)(sizeof(tab)/sizeof(tab[0])))
+ return 1/tab[n];
+ m = n/2;
+ return __fmtpow10(-m) * __fmtpow10(m-n);
+ }
+ if(n < (int)(sizeof(tab)/sizeof(tab[0])))
+ return tab[n];
+ m = n/2;
+ return __fmtpow10(m) * __fmtpow10(n-m);
+}
diff --git a/libfmt/print.3 b/libfmt/print.3
new file mode 100644
index 0000000..2606071
--- /dev/null
+++ b/libfmt/print.3
@@ -0,0 +1,482 @@
+.deEX
+.ift .ft5
+.nf
+..
+.deEE
+.ft1
+.fi
+..
+.\" diffs from /usr/local/plan9/man/man3/print.3:
+.\"
+.\" - include different headers
+.\" - drop reference to bio(3)
+.\" - change exits to exit
+.\" - text about unsigned verbs
+.\" - source pointer
+.\"
+.TH PRINT 3
+.SH NAME
+print, fprint, sprint, snprint, seprint, smprint, runesprint, runesnprint, runeseprint, runesmprint, vfprint, vsnprint, vseprint, vsmprint, runevsnprint, runevseprint, runevsmprint \- print formatted output
+.SH SYNOPSIS
+.B #include <utf.h>
+.PP
+.B #include <fmt.h>
+.PP
+.ta \w'\fLchar* 'u
+.B
+int print(char *format, ...)
+.PP
+.B
+int fprint(int fd, char *format, ...)
+.PP
+.B
+int sprint(char *s, char *format, ...)
+.PP
+.B
+int snprint(char *s, int len, char *format, ...)
+.PP
+.B
+char* seprint(char *s, char *e, char *format, ...)
+.PP
+.B
+char* smprint(char *format, ...)
+.PP
+.B
+int runesprint(Rune *s, char *format, ...)
+.PP
+.B
+int runesnprint(Rune *s, int len, char *format, ...)
+.PP
+.B
+Rune* runeseprint(Rune *s, Rune *e, char *format, ...)
+.PP
+.B
+Rune* runesmprint(char *format, ...)
+.PP
+.B
+int vfprint(int fd, char *format, va_list v)
+.PP
+.B
+int vsnprint(char *s, int len, char *format, va_list v)
+.PP
+.B
+char* vseprint(char *s, char *e, char *format, va_list v)
+.PP
+.B
+char* vsmprint(char *format, va_list v)
+.PP
+.B
+int runevsnprint(Rune *s, int len, char *format, va_list v)
+.PP
+.B
+Rune* runevseprint(Rune *s, Rune *e, char *format, va_list v)
+.PP
+.B
+Rune* runevsmprint(Rune *format, va_list v)
+.PP
+.B
+.SH DESCRIPTION
+.I Print
+writes text to the standard output.
+.I Fprint
+writes to the named output
+file descriptor:
+a buffered form
+is described in
+.IR bio (3).
+.I Sprint
+places text
+followed by the NUL character
+.RB ( \e0 )
+in consecutive bytes starting at
+.IR s ;
+it is the user's responsibility to ensure that
+enough storage is available.
+Each function returns the number of bytes
+transmitted (not including the NUL
+in the case of
+.IR sprint ),
+or
+a negative value if an output error was encountered.
+.PP
+.I Snprint
+is like
+.IR sprint ,
+but will not place more than
+.I len
+bytes in
+.IR s .
+Its result is always NUL-terminated and holds the maximal
+number of complete UTF-8 characters that can fit.
+.I Seprint
+is like
+.IR snprint ,
+except that the end is indicated by a pointer
+.I e
+rather than a count and the return value points to the terminating NUL of the
+resulting string.
+.I Smprint
+is like
+.IR sprint ,
+except that it prints into and returns a string of the required length, which is
+allocated by
+.IR malloc (3).
+.PP
+The routines
+.IR runesprint ,
+.IR runesnprint ,
+.IR runeseprint ,
+and
+.I runesmprint
+are the same as
+.IR sprint ,
+.IR snprint ,
+.IR seprint
+and
+.I smprint
+except that their output is rune strings instead of byte strings.
+.PP
+Finally, the routines
+.IR vfprint ,
+.IR vsnprint ,
+.IR vseprint ,
+.IR vsmprint ,
+.IR runevsnprint ,
+.IR runevseprint ,
+and
+.I runevsmprint
+are like their
+.BR v-less
+relatives except they take as arguments a
+.B va_list
+parameter, so they can be called within a variadic function.
+The Example section shows a representative usage.
+.PP
+Each of these functions
+converts, formats, and prints its
+trailing arguments
+under control of a
+.IR format
+string.
+The
+format
+contains two types of objects:
+plain characters, which are simply copied to the
+output stream,
+and conversion specifications,
+each of which results in fetching of
+zero or more
+arguments.
+The results are undefined if there are arguments of the
+wrong type or too few
+arguments for the format.
+If the format is exhausted while
+arguments remain, the excess
+is ignored.
+.PP
+Each conversion specification has the following format:
+.IP
+.B "% [flags] verb
+.PP
+The verb is a single character and each flag is a single character or a
+(decimal) numeric string.
+Up to two numeric strings may be used;
+the first is called
+.IR width ,
+the second
+.IR precision .
+A period can be used to separate them, and if the period is
+present then
+.I width
+and
+.I precision
+are taken to be zero if missing, otherwise they are `omitted'.
+Either or both of the numbers may be replaced with the character
+.BR * ,
+meaning that the actual number will be obtained from the argument list
+as an integer.
+The flags and numbers are arguments to
+the
+.I verb
+described below.
+.PP
+The numeric verbs
+.BR d ,
+.BR i ,
+.BR u ,
+.BR o ,
+.BR b ,
+.BR x ,
+and
+.B X
+format their arguments in decimal, decimal,
+unsigned decimal, octal, binary, hexadecimal, and upper case hexadecimal.
+Each interprets the flags
+.BR 0 ,
+.BR h ,
+.BR hh ,
+.BR l ,
+.BR + ,
+.BR - ,
+.BR , ,
+and
+.B #
+to mean pad with zeros,
+short, byte, long, always print a sign, left justified, commas every three digits,
+and alternate format.
+Also, a space character in the flag
+position is like
+.BR + ,
+but prints a space instead of a plus sign for non-negative values.
+If neither
+short nor long is specified,
+then the argument is an
+.BR int .
+If an unsigned verb is specified,
+then the argument is interpreted as a
+positive number and no sign is output;
+space and
+.B +
+flags are ignored for unsigned verbs.
+If two
+.B l
+flags are given,
+then the argument is interpreted as a
+.B vlong
+(usually an 8-byte, sometimes a 4-byte integer).
+If
+.I precision
+is not omitted, the number is padded on the left with zeros
+until at least
+.I precision
+digits appear.
+If
+.I precision
+is explicitly 0, and the number is 0,
+no digits are generated, and alternate formatting
+does not apply.
+Then, if alternate format is specified,
+for
+.B o
+conversion, the number is preceded by a
+.B 0
+if it doesn't already begin with one.
+For non-zero numbers and
+.B x
+conversion, the number is preceded by
+.BR 0x ;
+for
+.B X
+conversion, the number is preceded by
+.BR 0X .
+Finally, if
+.I width
+is not omitted, the number is padded on the left (or right, if
+left justification is specified) with enough blanks to
+make the field at least
+.I width
+characters long.
+.PP
+The floating point verbs
+.BR f ,
+.BR e ,
+.BR E ,
+.BR g ,
+and
+.B G
+take a
+.B double
+argument.
+Each interprets the flags
+.BR 0 ,
+.BR L
+.BR + ,
+.BR - ,
+and
+.B #
+to mean pad with zeros,
+long double argument,
+always print a sign,
+left justified,
+and
+alternate format.
+.I Width
+is the minimum field width and,
+if the converted value takes up less than
+.I width
+characters, it is padded on the left (or right, if `left justified')
+with spaces.
+.I Precision
+is the number of digits that are converted after the decimal place for
+.BR e ,
+.BR E ,
+and
+.B f
+conversions,
+and
+.I precision
+is the maximum number of significant digits for
+.B g
+and
+.B G
+conversions.
+The
+.B f
+verb produces output of the form
+.RB [ - ] digits [ .digits\fR].
+.B E
+conversion appends an exponent
+.BR E [ - ] digits ,
+and
+.B e
+conversion appends an exponent
+.BR e [ - ] digits .
+The
+.B g
+verb will output the argument in either
+.B e
+or
+.B f
+with the goal of producing the smallest output.
+Also, trailing zeros are omitted from the fraction part of
+the output, and a trailing decimal point appears only if it is followed
+by a digit.
+The
+.B G
+verb is similar, but uses
+.B E
+format instead of
+.BR e .
+When alternate format is specified, the result will always contain a decimal point,
+and for
+.B g
+and
+.B G
+conversions, trailing zeros are not removed.
+.PP
+The
+.B s
+verb copies a string
+(pointer to
+.BR char )
+to the output.
+The number of characters copied
+.RI ( n )
+is the minimum
+of the size of the string and
+.IR precision .
+These
+.I n
+characters are justified within a field of
+.I width
+characters as described above.
+If a
+.I precision
+is given, it is safe for the string not to be nul-terminated
+as long as it is at least
+.I precision
+characters (not bytes!) long.
+The
+.B S
+verb is similar, but it interprets its pointer as an array
+of runes (see
+.IR utf (7));
+the runes are converted to
+.SM UTF
+before output.
+.PP
+The
+.B c
+verb copies a single
+.B char
+(promoted to
+.BR int )
+justified within a field of
+.I width
+characters as described above.
+The
+.B C
+verb is similar, but works on runes.
+.PP
+The
+.B p
+verb formats a pointer value.
+At the moment, it is a synonym for
+.BR x ,
+but that will change if pointers and integers are different sizes.
+.PP
+The
+.B r
+verb takes no arguments; it copies the error string returned by a call to
+.IR strerror (3)
+with an argument of
+.IR errno.
+.PP
+Custom verbs may be installed using
+.IR fmtinstall (3).
+.SH EXAMPLE
+This function prints an error message with a variable
+number of arguments and then quits.
+.IP
+.EX
+.ta 6n +6n +6n
+void fatal(char *msg, ...)
+{
+ char buf[1024], *out;
+ va_list arg;
+
+ out = seprint(buf, buf+sizeof buf, "Fatal error: ");
+ va_start(arg, msg);
+ out = vseprint(out, buf+sizeof buf, msg, arg);
+ va_end(arg);
+ write(2, buf, out-buf);
+ exit(1);
+}
+.EE
+.SH SOURCE
+.B http://swtch.com/plan9port/unix
+.SH SEE ALSO
+.IR fmtinstall (3),
+.IR fprintf (3),
+.IR utf (7)
+.SH DIAGNOSTICS
+Routines that write to a file descriptor or call
+.IR malloc
+set
+.IR errstr .
+.SH BUGS
+The formatting is close to that specified for ANSI
+.IR fprintf (3);
+the main difference is that
+.B b
+and
+.B r
+are not in ANSI and some
+.B C9X
+verbs and syntax are missing.
+Also, and distinctly not a bug,
+.I print
+and friends generate
+.SM UTF
+rather than
+.SM ASCII.
+.PP
+There is no
+.IR runeprint ,
+.IR runefprint ,
+etc. because runes are byte-order dependent and should not be written directly to a file; use the
+UTF output of
+.I print
+or
+.I fprint
+instead.
+Also,
+.I sprint
+is deprecated for safety reasons; use
+.IR snprint ,
+.IR seprint ,
+or
+.I smprint
+instead.
+Safety also precludes the existence of
+.IR runesprint .
diff --git a/libfmt/print.c b/libfmt/print.c
new file mode 100644
index 0000000..d380262
--- /dev/null
+++ b/libfmt/print.c
@@ -0,0 +1,29 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include "plan9.h"
+#include "fmt.h"
+#include "fmtdef.h"
+
+int
+print(const char *fmt, ...)
+{
+ int n;
+ va_list args;
+
+ va_start(args, fmt);
+ n = vfprint(1, fmt, args);
+ va_end(args);
+ return n;
+}
diff --git a/libfmt/runefmtstr.c b/libfmt/runefmtstr.c
new file mode 100644
index 0000000..e17bc16
--- /dev/null
+++ b/libfmt/runefmtstr.c
@@ -0,0 +1,27 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <stdlib.h>
+#include "plan9.h"
+#include "fmt.h"
+#include "fmtdef.h"
+
+Rune*
+runefmtstrflush(Fmt *f)
+{
+ if(f->start == nil)
+ return nil;
+ *(Rune*)f->to = '\0';
+ return f->start;
+}
diff --git a/libfmt/runeseprint.c b/libfmt/runeseprint.c
new file mode 100644
index 0000000..1e06df0
--- /dev/null
+++ b/libfmt/runeseprint.c
@@ -0,0 +1,30 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "plan9.h"
+#include "fmt.h"
+#include "fmtdef.h"
+
+Rune*
+runeseprint(Rune *buf, Rune *e, const char *fmt, ...)
+{
+ Rune *p;
+ va_list args;
+
+ va_start(args, fmt);
+ p = runevseprint(buf, e, fmt, args);
+ va_end(args);
+ return p;
+}
diff --git a/libfmt/runesmprint.c b/libfmt/runesmprint.c
new file mode 100644
index 0000000..fc56bdc
--- /dev/null
+++ b/libfmt/runesmprint.c
@@ -0,0 +1,30 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "plan9.h"
+#include "fmt.h"
+#include "fmtdef.h"
+
+Rune*
+runesmprint(const char *fmt, ...)
+{
+ va_list args;
+ Rune *p;
+
+ va_start(args, fmt);
+ p = runevsmprint(fmt, args);
+ va_end(args);
+ return p;
+}
diff --git a/libfmt/runesnprint.c b/libfmt/runesnprint.c
new file mode 100644
index 0000000..fad02b3
--- /dev/null
+++ b/libfmt/runesnprint.c
@@ -0,0 +1,31 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "plan9.h"
+#include "fmt.h"
+#include "fmtdef.h"
+
+int
+runesnprint(Rune *buf, int len, const char *fmt, ...)
+{
+ int n;
+ va_list args;
+
+ va_start(args, fmt);
+ n = runevsnprint(buf, len, fmt, args);
+ va_end(args);
+ return n;
+}
+
diff --git a/libfmt/runesprint.c b/libfmt/runesprint.c
new file mode 100644
index 0000000..8924e7b
--- /dev/null
+++ b/libfmt/runesprint.c
@@ -0,0 +1,30 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "plan9.h"
+#include "fmt.h"
+#include "fmtdef.h"
+
+int
+runesprint(Rune *buf, const char *fmt, ...)
+{
+ int n;
+ va_list args;
+
+ va_start(args, fmt);
+ n = runevsnprint(buf, 256, fmt, args);
+ va_end(args);
+ return n;
+}
diff --git a/libfmt/runevseprint.c b/libfmt/runevseprint.c
new file mode 100644
index 0000000..ee9d9c5
--- /dev/null
+++ b/libfmt/runevseprint.c
@@ -0,0 +1,40 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "plan9.h"
+#include "fmt.h"
+#include "fmtdef.h"
+
+Rune*
+runevseprint(Rune *buf, Rune *e, const char *fmt, va_list args)
+{
+ Fmt f;
+
+ if(e <= buf)
+ return nil;
+ f.runes = 1;
+ f.start = buf;
+ f.to = buf;
+ f.stop = e - 1;
+ f.flush = nil;
+ f.farg = nil;
+ f.nfmt = 0;
+ va_copy(f.args,args);
+ dofmt(&f, fmt);
+ va_end(f.args);
+ *(Rune*)f.to = '\0';
+ return (Rune*)f.to;
+}
+
diff --git a/libfmt/runevsmprint.c b/libfmt/runevsmprint.c
new file mode 100644
index 0000000..295f73d
--- /dev/null
+++ b/libfmt/runevsmprint.c
@@ -0,0 +1,91 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+/*
+ * Plan 9 port version must include libc.h in order to
+ * get Plan 9 debugging malloc, which sometimes returns
+ * different pointers than the standard malloc.
+ */
+#include <stdlib.h>
+#include <string.h>
+#include "plan9.h"
+#include "fmt.h"
+#include "fmtdef.h"
+
+static int
+runeFmtStrFlush(Fmt *f)
+{
+ Rune *s;
+ int n;
+
+ if(f->start == nil)
+ return 0;
+ n = (uintptr_t)f->farg;
+ n *= 2;
+ s = (Rune*)f->start;
+ f->start = realloc(s, sizeof(Rune)*n);
+ if(f->start == nil){
+ f->farg = nil;
+ f->to = nil;
+ f->stop = nil;
+ free(s);
+ return 0;
+ }
+ f->farg = (void*)(uintptr_t)n;
+ f->to = (Rune*)f->start + ((Rune*)f->to - s);
+ f->stop = (Rune*)f->start + n - 1;
+ return 1;
+}
+
+int
+runefmtstrinit(Fmt *f)
+{
+ int n;
+
+ memset(f, 0, sizeof *f);
+ f->runes = 1;
+ n = 32;
+ f->start = malloc(sizeof(Rune)*n);
+ if(f->start == nil)
+ return -1;
+ f->to = f->start;
+ f->stop = (Rune*)f->start + n - 1;
+ f->flush = runeFmtStrFlush;
+ f->farg = (void*)(uintptr_t)n;
+ f->nfmt = 0;
+ return 0;
+}
+
+/*
+ * print into an allocated string buffer
+ */
+Rune*
+runevsmprint(const char *fmt, va_list args)
+{
+ Fmt f;
+ int n;
+
+ if(runefmtstrinit(&f) < 0)
+ return nil;
+ va_copy(f.args,args);
+ n = dofmt(&f, fmt);
+ va_end(f.args);
+ if(f.start == nil)
+ return nil;
+ if(n < 0){
+ free(f.start);
+ return nil;
+ }
+ *(Rune*)f.to = '\0';
+ return (Rune*)f.start;
+}
diff --git a/libfmt/runevsnprint.c b/libfmt/runevsnprint.c
new file mode 100644
index 0000000..e602be0
--- /dev/null
+++ b/libfmt/runevsnprint.c
@@ -0,0 +1,39 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "plan9.h"
+#include "fmt.h"
+#include "fmtdef.h"
+
+int
+runevsnprint(Rune *buf, int len, const char *fmt, va_list args)
+{
+ Fmt f;
+
+ if(len <= 0)
+ return -1;
+ f.runes = 1;
+ f.start = buf;
+ f.to = buf;
+ f.stop = buf + len - 1;
+ f.flush = nil;
+ f.farg = nil;
+ f.nfmt = 0;
+ va_copy(f.args,args);
+ dofmt(&f, fmt);
+ va_end(f.args);
+ *(Rune*)f.to = '\0';
+ return (Rune*)f.to - buf;
+}
diff --git a/libfmt/seprint.c b/libfmt/seprint.c
new file mode 100644
index 0000000..276ba0b
--- /dev/null
+++ b/libfmt/seprint.c
@@ -0,0 +1,29 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include "plan9.h"
+#include "fmt.h"
+#include "fmtdef.h"
+
+char*
+seprint(char *buf, char *e, const char *fmt, ...)
+{
+ char *p;
+ va_list args;
+
+ va_start(args, fmt);
+ p = vseprint(buf, e, fmt, args);
+ va_end(args);
+ return p;
+}
diff --git a/libfmt/smprint.c b/libfmt/smprint.c
new file mode 100644
index 0000000..741269a
--- /dev/null
+++ b/libfmt/smprint.c
@@ -0,0 +1,29 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include "plan9.h"
+#include "fmt.h"
+#include "fmtdef.h"
+
+char*
+smprint(const char *fmt, ...)
+{
+ va_list args;
+ char *p;
+
+ va_start(args, fmt);
+ p = vsmprint(fmt, args);
+ va_end(args);
+ return p;
+}
diff --git a/libfmt/snprint.c b/libfmt/snprint.c
new file mode 100644
index 0000000..81170f2
--- /dev/null
+++ b/libfmt/snprint.c
@@ -0,0 +1,30 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include "plan9.h"
+#include "fmt.h"
+#include "fmtdef.h"
+
+int
+snprint(char *buf, int len, const char *fmt, ...)
+{
+ int n;
+ va_list args;
+
+ va_start(args, fmt);
+ n = vsnprint(buf, len, fmt, args);
+ va_end(args);
+ return n;
+}
+
diff --git a/libfmt/sprint.c b/libfmt/sprint.c
new file mode 100644
index 0000000..897c978
--- /dev/null
+++ b/libfmt/sprint.c
@@ -0,0 +1,37 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include "plan9.h"
+#include "fmtdef.h"
+
+int
+sprint(char *buf, const char *fmt, ...)
+{
+ int n;
+ uint len;
+ va_list args;
+
+ len = 1<<30; /* big number, but sprint is deprecated anyway */
+ /*
+ * on PowerPC, the stack is near the top of memory, so
+ * we must be sure not to overflow a 32-bit pointer.
+ */
+ if(buf+len < buf)
+ len = -(uintptr_t)buf-1;
+
+ va_start(args, fmt);
+ n = vsnprint(buf, len, fmt, args);
+ va_end(args);
+ return n;
+}
diff --git a/libfmt/strtod.c b/libfmt/strtod.c
new file mode 100644
index 0000000..fbc1c59
--- /dev/null
+++ b/libfmt/strtod.c
@@ -0,0 +1,532 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdlib.h>
+#include <math.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include "plan9.h"
+#include "fmt.h"
+#include "fmtdef.h"
+
+static ulong
+umuldiv(ulong a, ulong b, ulong c)
+{
+ double d;
+
+ d = ((double)a * (double)b) / (double)c;
+ if(d >= 4294967295.)
+ d = 4294967295.;
+ return (ulong)d;
+}
+
+/*
+ * This routine will convert to arbitrary precision
+ * floating point entirely in multi-precision fixed.
+ * The answer is the closest floating point number to
+ * the given decimal number. Exactly half way are
+ * rounded ala ieee rules.
+ * Method is to scale input decimal between .500 and .999...
+ * with external power of 2, then binary search for the
+ * closest mantissa to this decimal number.
+ * Nmant is is the required precision. (53 for ieee dp)
+ * Nbits is the max number of bits/word. (must be <= 28)
+ * Prec is calculated - the number of words of fixed mantissa.
+ */
+enum
+{
+ Nbits = 28, /* bits safely represented in a ulong */
+ Nmant = 53, /* bits of precision required */
+ Prec = (Nmant+Nbits+1)/Nbits, /* words of Nbits each to represent mantissa */
+ Sigbit = 1<<(Prec*Nbits-Nmant), /* first significant bit of Prec-th word */
+ Ndig = 1500,
+ One = (ulong)(1<<Nbits),
+ Half = (ulong)(One>>1),
+ Maxe = 310,
+
+ Fsign = 1<<0, /* found - */
+ Fesign = 1<<1, /* found e- */
+ Fdpoint = 1<<2, /* found . */
+
+ S0 = 0, /* _ _S0 +S1 #S2 .S3 */
+ S1, /* _+ #S2 .S3 */
+ S2, /* _+# #S2 .S4 eS5 */
+ S3, /* _+. #S4 */
+ S4, /* _+#.# #S4 eS5 */
+ S5, /* _+#.#e +S6 #S7 */
+ S6, /* _+#.#e+ #S7 */
+ S7, /* _+#.#e+# #S7 */
+};
+
+static int xcmp(char*, char*);
+static int fpcmp(char*, ulong*);
+static void frnorm(ulong*);
+static void divascii(char*, int*, int*, int*);
+static void mulascii(char*, int*, int*, int*);
+
+typedef struct Tab Tab;
+struct Tab
+{
+ int bp;
+ int siz;
+ char* cmp;
+};
+
+double
+fmtstrtod(const char *as, char **aas)
+{
+ int na, ex, dp, bp, c, i, flag, state;
+ ulong low[Prec], hig[Prec], mid[Prec];
+ double d;
+ char *s, a[Ndig];
+
+ flag = 0; /* Fsign, Fesign, Fdpoint */
+ na = 0; /* number of digits of a[] */
+ dp = 0; /* na of decimal point */
+ ex = 0; /* exonent */
+
+ state = S0;
+ for(s=(char*)as;; s++) {
+ c = *s;
+ if(c >= '0' && c <= '9') {
+ switch(state) {
+ case S0:
+ case S1:
+ case S2:
+ state = S2;
+ break;
+ case S3:
+ case S4:
+ state = S4;
+ break;
+
+ case S5:
+ case S6:
+ case S7:
+ state = S7;
+ ex = ex*10 + (c-'0');
+ continue;
+ }
+ if(na == 0 && c == '0') {
+ dp--;
+ continue;
+ }
+ if(na < Ndig-50)
+ a[na++] = c;
+ continue;
+ }
+ switch(c) {
+ case '\t':
+ case '\n':
+ case '\v':
+ case '\f':
+ case '\r':
+ case ' ':
+ if(state == S0)
+ continue;
+ break;
+ case '-':
+ if(state == S0)
+ flag |= Fsign;
+ else
+ flag |= Fesign;
+ case '+':
+ if(state == S0)
+ state = S1;
+ else
+ if(state == S5)
+ state = S6;
+ else
+ break; /* syntax */
+ continue;
+ case '.':
+ flag |= Fdpoint;
+ dp = na;
+ if(state == S0 || state == S1) {
+ state = S3;
+ continue;
+ }
+ if(state == S2) {
+ state = S4;
+ continue;
+ }
+ break;
+ case 'e':
+ case 'E':
+ if(state == S2 || state == S4) {
+ state = S5;
+ continue;
+ }
+ break;
+ }
+ break;
+ }
+
+ /*
+ * clean up return char-pointer
+ */
+ switch(state) {
+ case S0:
+ if(xcmp(s, "nan") == 0) {
+ if(aas != nil)
+ *aas = s+3;
+ goto retnan;
+ }
+ case S1:
+ if(xcmp(s, "infinity") == 0) {
+ if(aas != nil)
+ *aas = s+8;
+ goto retinf;
+ }
+ if(xcmp(s, "inf") == 0) {
+ if(aas != nil)
+ *aas = s+3;
+ goto retinf;
+ }
+ case S3:
+ if(aas != nil)
+ *aas = (char*)as;
+ goto ret0; /* no digits found */
+ case S6:
+ s--; /* back over +- */
+ case S5:
+ s--; /* back over e */
+ break;
+ }
+ if(aas != nil)
+ *aas = s;
+
+ if(flag & Fdpoint)
+ while(na > 0 && a[na-1] == '0')
+ na--;
+ if(na == 0)
+ goto ret0; /* zero */
+ a[na] = 0;
+ if(!(flag & Fdpoint))
+ dp = na;
+ if(flag & Fesign)
+ ex = -ex;
+ dp += ex;
+ if(dp < -Maxe){
+ errno = ERANGE;
+ goto ret0; /* underflow by exp */
+ } else
+ if(dp > +Maxe)
+ goto retinf; /* overflow by exp */
+
+ /*
+ * normalize the decimal ascii number
+ * to range .[5-9][0-9]* e0
+ */
+ bp = 0; /* binary exponent */
+ while(dp > 0)
+ divascii(a, &na, &dp, &bp);
+ while(dp < 0 || a[0] < '5')
+ mulascii(a, &na, &dp, &bp);
+
+ /* close approx by naive conversion */
+ mid[0] = 0;
+ mid[1] = 1;
+ for(i=0; c=a[i]; i++) {
+ mid[0] = mid[0]*10 + (c-'0');
+ mid[1] = mid[1]*10;
+ if(i >= 8)
+ break;
+ }
+ low[0] = umuldiv(mid[0], One, mid[1]);
+ hig[0] = umuldiv(mid[0]+1, One, mid[1]);
+ for(i=1; i<Prec; i++) {
+ low[i] = 0;
+ hig[i] = One-1;
+ }
+
+ /* binary search for closest mantissa */
+ for(;;) {
+ /* mid = (hig + low) / 2 */
+ c = 0;
+ for(i=0; i<Prec; i++) {
+ mid[i] = hig[i] + low[i];
+ if(c)
+ mid[i] += One;
+ c = mid[i] & 1;
+ mid[i] >>= 1;
+ }
+ frnorm(mid);
+
+ /* compare */
+ c = fpcmp(a, mid);
+ if(c > 0) {
+ c = 1;
+ for(i=0; i<Prec; i++)
+ if(low[i] != mid[i]) {
+ c = 0;
+ low[i] = mid[i];
+ }
+ if(c)
+ break; /* between mid and hig */
+ continue;
+ }
+ if(c < 0) {
+ for(i=0; i<Prec; i++)
+ hig[i] = mid[i];
+ continue;
+ }
+
+ /* only hard part is if even/odd roundings wants to go up */
+ c = mid[Prec-1] & (Sigbit-1);
+ if(c == Sigbit/2 && (mid[Prec-1]&Sigbit) == 0)
+ mid[Prec-1] -= c;
+ break; /* exactly mid */
+ }
+
+ /* normal rounding applies */
+ c = mid[Prec-1] & (Sigbit-1);
+ mid[Prec-1] -= c;
+ if(c >= Sigbit/2) {
+ mid[Prec-1] += Sigbit;
+ frnorm(mid);
+ }
+ goto out;
+
+ret0:
+ return 0;
+
+retnan:
+ return __NaN();
+
+retinf:
+ /*
+ * Unix strtod requires these. Plan 9 would return Inf(0) or Inf(-1). */
+ errno = ERANGE;
+ if(flag & Fsign)
+ return -HUGE_VAL;
+ return HUGE_VAL;
+
+out:
+ d = 0;
+ for(i=0; i<Prec; i++)
+ d = d*One + mid[i];
+ if(flag & Fsign)
+ d = -d;
+ d = ldexp(d, bp - Prec*Nbits);
+ if(d == 0){ /* underflow */
+ errno = ERANGE;
+ }
+ return d;
+}
+
+static void
+frnorm(ulong *f)
+{
+ int i, c;
+
+ c = 0;
+ for(i=Prec-1; i>0; i--) {
+ f[i] += c;
+ c = f[i] >> Nbits;
+ f[i] &= One-1;
+ }
+ f[0] += c;
+}
+
+static int
+fpcmp(char *a, ulong* f)
+{
+ ulong tf[Prec];
+ int i, d, c;
+
+ for(i=0; i<Prec; i++)
+ tf[i] = f[i];
+
+ for(;;) {
+ /* tf *= 10 */
+ for(i=0; i<Prec; i++)
+ tf[i] = tf[i]*10;
+ frnorm(tf);
+ d = (tf[0] >> Nbits) + '0';
+ tf[0] &= One-1;
+
+ /* compare next digit */
+ c = *a;
+ if(c == 0) {
+ if('0' < d)
+ return -1;
+ if(tf[0] != 0)
+ goto cont;
+ for(i=1; i<Prec; i++)
+ if(tf[i] != 0)
+ goto cont;
+ return 0;
+ }
+ if(c > d)
+ return +1;
+ if(c < d)
+ return -1;
+ a++;
+ cont:;
+ }
+}
+
+static void
+divby(char *a, int *na, int b)
+{
+ int n, c;
+ char *p;
+
+ p = a;
+ n = 0;
+ while(n>>b == 0) {
+ c = *a++;
+ if(c == 0) {
+ while(n) {
+ c = n*10;
+ if(c>>b)
+ break;
+ n = c;
+ }
+ goto xx;
+ }
+ n = n*10 + c-'0';
+ (*na)--;
+ }
+ for(;;) {
+ c = n>>b;
+ n -= c<<b;
+ *p++ = c + '0';
+ c = *a++;
+ if(c == 0)
+ break;
+ n = n*10 + c-'0';
+ }
+ (*na)++;
+xx:
+ while(n) {
+ n = n*10;
+ c = n>>b;
+ n -= c<<b;
+ *p++ = c + '0';
+ (*na)++;
+ }
+ *p = 0;
+}
+
+static Tab tab1[] =
+{
+ 1, 0, "",
+ 3, 1, "7",
+ 6, 2, "63",
+ 9, 3, "511",
+ 13, 4, "8191",
+ 16, 5, "65535",
+ 19, 6, "524287",
+ 23, 7, "8388607",
+ 26, 8, "67108863",
+ 27, 9, "134217727",
+};
+
+static void
+divascii(char *a, int *na, int *dp, int *bp)
+{
+ int b, d;
+ Tab *t;
+
+ d = *dp;
+ if(d >= (int)(nelem(tab1)))
+ d = (int)(nelem(tab1))-1;
+ t = tab1 + d;
+ b = t->bp;
+ if(memcmp(a, t->cmp, t->siz) > 0)
+ d--;
+ *dp -= d;
+ *bp += b;
+ divby(a, na, b);
+}
+
+static void
+mulby(char *a, char *p, char *q, int b)
+{
+ int n, c;
+
+ n = 0;
+ *p = 0;
+ for(;;) {
+ q--;
+ if(q < a)
+ break;
+ c = *q - '0';
+ c = (c<<b) + n;
+ n = c/10;
+ c -= n*10;
+ p--;
+ *p = c + '0';
+ }
+ while(n) {
+ c = n;
+ n = c/10;
+ c -= n*10;
+ p--;
+ *p = c + '0';
+ }
+}
+
+static Tab tab2[] =
+{
+ 1, 1, "", /* dp = 0-0 */
+ 3, 3, "125",
+ 6, 5, "15625",
+ 9, 7, "1953125",
+ 13, 10, "1220703125",
+ 16, 12, "152587890625",
+ 19, 14, "19073486328125",
+ 23, 17, "11920928955078125",
+ 26, 19, "1490116119384765625",
+ 27, 19, "7450580596923828125", /* dp 8-9 */
+};
+
+static void
+mulascii(char *a, int *na, int *dp, int *bp)
+{
+ char *p;
+ int d, b;
+ Tab *t;
+
+ d = -*dp;
+ if(d >= (int)(nelem(tab2)))
+ d = (int)(nelem(tab2))-1;
+ t = tab2 + d;
+ b = t->bp;
+ if(memcmp(a, t->cmp, t->siz) < 0)
+ d--;
+ p = a + *na;
+ *bp -= b;
+ *dp += d;
+ *na += d;
+ mulby(a, p+d, p, b);
+}
+
+static int
+xcmp(char *a, char *b)
+{
+ int c1, c2;
+
+ while(c1 = *b++) {
+ c2 = *a++;
+ if(isupper(c2))
+ c2 = tolower(c2);
+ if(c1 != c2)
+ return 1;
+ }
+ return 0;
+}
diff --git a/libfmt/test.c b/libfmt/test.c
new file mode 100644
index 0000000..04296e2
--- /dev/null
+++ b/libfmt/test.c
@@ -0,0 +1,44 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdio.h>
+#include <stdarg.h>
+#include <utf.h>
+#include "plan9.h"
+#include "fmt.h"
+#include "fmtdef.h"
+
+int
+main(int argc, char *argv[])
+{
+ quotefmtinstall();
+ print("hello world\n");
+ print("x: %x\n", 0x87654321);
+ print("u: %u\n", 0x87654321);
+ print("d: %d\n", 0x87654321);
+ print("s: %s\n", "hi there");
+ print("q: %q\n", "hi i'm here");
+ print("c: %c\n", '!');
+ print("g: %g %g %g\n", 3.14159, 3.14159e10, 3.14159e-10);
+ print("e: %e %e %e\n", 3.14159, 3.14159e10, 3.14159e-10);
+ print("f: %f %f %f\n", 3.14159, 3.14159e10, 3.14159e-10);
+ print("smiley: %C\n", (Rune)0x263a);
+ print("%g %.18g\n", 2e25, 2e25);
+ print("%2.18g\n", 1.0);
+ print("%2.18f\n", 1.0);
+ print("%f\n", 3.1415927/4);
+ print("%d\n", 23);
+ print("%i\n", 23);
+ print("%0.10d\n", 12345);
+ return 0;
+}
diff --git a/libfmt/test2.c b/libfmt/test2.c
new file mode 100644
index 0000000..715fcd5
--- /dev/null
+++ b/libfmt/test2.c
@@ -0,0 +1,9 @@
+#include <stdarg.h>
+#include <utf.h>
+#include <fmt.h>
+
+int
+main(int argc, char **argv)
+{
+ print("%020.10d\n", 100);
+}
diff --git a/libfmt/test3.c b/libfmt/test3.c
new file mode 100644
index 0000000..7cda8dc
--- /dev/null
+++ b/libfmt/test3.c
@@ -0,0 +1,52 @@
+#include <u.h>
+#include <libc.h>
+#include <stdio.h>
+
+void
+test(char *fmt, ...)
+{
+ va_list arg;
+ char fmtbuf[100], stdbuf[100];
+
+ va_start(arg, fmt);
+ vsnprint(fmtbuf, sizeof fmtbuf, fmt, arg);
+ va_end(arg);
+
+ va_start(arg, fmt);
+ vsnprint(stdbuf, sizeof stdbuf, fmt, arg);
+ va_end(arg);
+
+ if(strcmp(fmtbuf, stdbuf) != 0)
+ print("fmt %s: fmt=\"%s\" std=\"%s\"\n", fmt, fmtbuf, stdbuf);
+
+ print("fmt %s: %s\n", fmt, fmtbuf);
+}
+
+
+int
+main(int argc, char *argv[])
+{
+ test("%f", 3.14159);
+ test("%f", 3.14159e10);
+ test("%f", 3.14159e-10);
+
+ test("%e", 3.14159);
+ test("%e", 3.14159e10);
+ test("%e", 3.14159e-10);
+
+ test("%g", 3.14159);
+ test("%g", 3.14159e10);
+ test("%g", 3.14159e-10);
+
+ test("%g", 2e25);
+ test("%.18g", 2e25);
+
+ test("%2.18g", 1.0);
+ test("%2.18f", 1.0);
+ test("%f", 3.1415927/4);
+
+ test("%20.10d", 12345);
+ test("%0.10d", 12345);
+
+ return 0;
+}
diff --git a/libfmt/vfprint.c b/libfmt/vfprint.c
new file mode 100644
index 0000000..8e35c33
--- /dev/null
+++ b/libfmt/vfprint.c
@@ -0,0 +1,33 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include "plan9.h"
+#include "fmt.h"
+#include "fmtdef.h"
+
+int
+vfprint(int fd, const char *fmt, va_list args)
+{
+ Fmt f;
+ char buf[256];
+ int n;
+
+ fmtfdinit(&f, fd, buf, sizeof(buf));
+ va_copy(f.args,args);
+ n = dofmt(&f, fmt);
+ va_end(f.args);
+ if(n > 0 && __fmtFdFlush(&f) == 0)
+ return -1;
+ return n;
+}
diff --git a/libfmt/vseprint.c b/libfmt/vseprint.c
new file mode 100644
index 0000000..c55dc32
--- /dev/null
+++ b/libfmt/vseprint.c
@@ -0,0 +1,39 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include "plan9.h"
+#include "fmt.h"
+#include "fmtdef.h"
+
+char*
+vseprint(char *buf, char *e, const char *fmt, va_list args)
+{
+ Fmt f;
+
+ if(e <= buf)
+ return nil;
+ f.runes = 0;
+ f.start = buf;
+ f.to = buf;
+ f.stop = e - 1;
+ f.flush = 0;
+ f.farg = nil;
+ f.nfmt = 0;
+ va_copy(f.args,args);
+ dofmt(&f, fmt);
+ va_end(f.args);
+ *(char*)f.to = '\0';
+ return (char*)f.to;
+}
+
diff --git a/libfmt/vsmprint.c b/libfmt/vsmprint.c
new file mode 100644
index 0000000..a285565
--- /dev/null
+++ b/libfmt/vsmprint.c
@@ -0,0 +1,88 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+/*
+ * Plan 9 port version must include libc.h in order to
+ * get Plan 9 debugging malloc, which sometimes returns
+ * different pointers than the standard malloc.
+ */
+#include <stdlib.h>
+#include <string.h>
+#include "plan9.h"
+#include "fmt.h"
+#include "fmtdef.h"
+
+static int
+fmtStrFlush(Fmt *f)
+{
+ char *s;
+ int n;
+
+ if(f->start == nil)
+ return 0;
+ n = (uintptr_t)f->farg;
+ n *= 2;
+ s = (char*)f->start;
+ f->start = realloc(s, n);
+ if(f->start == nil){
+ f->farg = nil;
+ f->to = nil;
+ f->stop = nil;
+ free(s);
+ return 0;
+ }
+ f->farg = (void*)(uintptr_t)n;
+ f->to = (char*)f->start + ((char*)f->to - s);
+ f->stop = (char*)f->start + n - 1;
+ return 1;
+}
+
+int
+fmtstrinit(Fmt *f)
+{
+ int n;
+
+ memset(f, 0, sizeof *f);
+ f->runes = 0;
+ n = 32;
+ f->start = malloc(n);
+ if(f->start == nil)
+ return -1;
+ f->to = f->start;
+ f->stop = (char*)f->start + n - 1;
+ f->flush = fmtStrFlush;
+ f->farg = (void*)(uintptr_t)n;
+ f->nfmt = 0;
+ return 0;
+}
+
+/*
+ * print into an allocated string buffer
+ */
+char*
+vsmprint(const char *fmt, va_list args)
+{
+ Fmt f;
+ int n;
+
+ if(fmtstrinit(&f) < 0)
+ return nil;
+ va_copy(f.args,args);
+ n = dofmt(&f, fmt);
+ va_end(f.args);
+ if(n < 0){
+ free(f.start);
+ return nil;
+ }
+ return fmtstrflush(&f);
+}
diff --git a/libfmt/vsnprint.c b/libfmt/vsnprint.c
new file mode 100644
index 0000000..7c79b9c
--- /dev/null
+++ b/libfmt/vsnprint.c
@@ -0,0 +1,39 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdlib.h>
+#include <stdarg.h>
+#include "plan9.h"
+#include "fmt.h"
+#include "fmtdef.h"
+
+int
+vsnprint(char *buf, int len, const char *fmt, va_list args)
+{
+ Fmt f;
+
+ if(len <= 0)
+ return -1;
+ f.runes = 0;
+ f.start = buf;
+ f.to = buf;
+ f.stop = buf + len - 1;
+ f.flush = 0;
+ f.farg = nil;
+ f.nfmt = 0;
+ va_copy(f.args,args);
+ dofmt(&f, fmt);
+ va_end(f.args);
+ *(char*)f.to = '\0';
+ return (char*)f.to - buf;
+}
diff --git a/libixp/LICENSE b/libixp/LICENSE
new file mode 100644
index 0000000..8efa860
--- /dev/null
+++ b/libixp/LICENSE
@@ -0,0 +1,21 @@
+
+© 2005-2006 Anselm R. Garbe <garbeam@gmail.com>
+© 2006-2009 Kris Maglione <maglione.k at Gmail>
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/libixp/Makefile b/libixp/Makefile
new file mode 100644
index 0000000..7704c2b
--- /dev/null
+++ b/libixp/Makefile
@@ -0,0 +1,23 @@
+ROOT= ..
+include $(ROOT)/mk/hdr.mk
+include $(ROOT)/mk/ixp.mk
+
+TARG = libixp
+
+OBJ = client \
+ convert \
+ error \
+ map \
+ message \
+ request \
+ rpc \
+ server \
+ srv_util \
+ socket \
+ thread \
+ timer \
+ transport \
+ util
+
+include ${ROOT}/mk/lib.mk
+
diff --git a/libixp/README b/libixp/README
new file mode 100644
index 0000000..d0cc55e
--- /dev/null
+++ b/libixp/README
@@ -0,0 +1,14 @@
+libixp - simple 9P client-/server-library
+===============================
+libixp is an extremly simple, stand-alone 9P library.
+
+
+Installation
+------------
+Edit config.mk to match your local setup. libixp is installed into
+/usr/local by default.
+
+Afterwards enter the following command to build and install libixp
+(if necessary as root):
+
+ $ make clean install
diff --git a/libixp/client.c b/libixp/client.c
new file mode 100644
index 0000000..fabf153
--- /dev/null
+++ b/libixp/client.c
@@ -0,0 +1,676 @@
+/* Copyright ©2007-2008 Kris Maglione <fbsdaemon@gmail.com>
+ * See LICENSE file for license details.
+ */
+#include <assert.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include "ixp_local.h"
+
+#define nelem(ary) (sizeof(ary) / sizeof(*ary))
+
+enum {
+ RootFid = 1,
+};
+
+static int
+min(int a, int b) {
+ if(a < b)
+ return a;
+ return b;
+}
+
+static IxpCFid*
+getfid(IxpClient *c) {
+ IxpCFid *f;
+
+ thread->lock(&c->lk);
+ f = c->freefid;
+ if(f != nil)
+ c->freefid = f->next;
+ else {
+ f = emallocz(sizeof *f);
+ f->client = c;
+ f->fid = ++c->lastfid;
+ thread->initmutex(&f->iolock);
+ }
+ f->next = nil;
+ f->open = 0;
+ thread->unlock(&c->lk);
+ return f;
+}
+
+static void
+putfid(IxpCFid *f) {
+ IxpClient *c;
+
+ c = f->client;
+ thread->lock(&c->lk);
+ if(f->fid == c->lastfid) {
+ c->lastfid--;
+ thread->mdestroy(&f->iolock);
+ free(f);
+ }else {
+ f->next = c->freefid;
+ c->freefid = f;
+ }
+ thread->unlock(&c->lk);
+}
+
+static int
+dofcall(IxpClient *c, Fcall *fcall) {
+ Fcall *ret;
+
+ ret = muxrpc(c, fcall);
+ if(ret == nil)
+ return 0;
+ if(ret->hdr.type == RError) {
+ werrstr("%s", ret->error.ename);
+ goto fail;
+ }
+ if(ret->hdr.type != (fcall->hdr.type^1)) {
+ werrstr("received mismatched fcall");
+ goto fail;
+ }
+ memcpy(fcall, ret, sizeof *fcall);
+ free(ret);
+ return 1;
+fail:
+ ixp_freefcall(fcall);
+ free(ret);
+ return 0;
+}
+
+/**
+ * Function: ixp_unmount
+ *
+ * Unmounts the client P<c> and frees its data structures.
+ */
+void
+ixp_unmount(IxpClient *c) {
+ IxpCFid *f;
+
+ shutdown(c->fd, SHUT_RDWR);
+ close(c->fd);
+
+ muxfree(c);
+
+ while((f = c->freefid)) {
+ c->freefid = f->next;
+ thread->mdestroy(&f->iolock);
+ free(f);
+ }
+ free(c->rmsg.data);
+ free(c->wmsg.data);
+ free(c);
+}
+
+static void
+allocmsg(IxpClient *c, int n) {
+ c->rmsg.size = n;
+ c->wmsg.size = n;
+ c->rmsg.data = erealloc(c->rmsg.data, n);
+ c->wmsg.data = erealloc(c->wmsg.data, n);
+}
+
+/**
+ * Function: ixp_mountfd
+ * Function: ixp_mount
+ * Function: ixp_nsmount
+ *
+ * Params:
+ * fd - A file descriptor which is already connected
+ * to a 9P server.
+ * address - An address (in Plan 9 resource fomat) at
+ * which to connect to a 9P server.
+ * name - The name of a socket in the process's canonical
+ * namespace directory.
+ *
+ * Initiate a 9P connection with the server at P<address>,
+ * connected to on P<fd>, or under the process's namespace
+ * directory as P<name>.
+ *
+ * Returns:
+ * A pointer to a new 9P client.
+ */
+
+IxpClient*
+ixp_mountfd(int fd) {
+ IxpClient *c;
+ Fcall fcall;
+
+ c = emallocz(sizeof *c);
+ c->fd = fd;
+
+ muxinit(c);
+
+ allocmsg(c, 256);
+ c->lastfid = RootFid;
+ /* Override tag matching on TVersion */
+ c->mintag = IXP_NOTAG;
+ c->maxtag = IXP_NOTAG+1;
+
+ fcall.hdr.type = TVersion;
+ fcall.version.msize = IXP_MAX_MSG;
+ fcall.version.version = IXP_VERSION;
+
+ if(dofcall(c, &fcall) == 0) {
+ ixp_unmount(c);
+ return nil;
+ }
+
+ if(strcmp(fcall.version.version, IXP_VERSION)
+ || fcall.version.msize > IXP_MAX_MSG) {
+ werrstr("bad 9P version response");
+ ixp_unmount(c);
+ return nil;
+ }
+
+ c->mintag = 0;
+ c->maxtag = 255;
+ c->msize = fcall.version.msize;
+
+ allocmsg(c, fcall.version.msize);
+ ixp_freefcall(&fcall);
+
+ fcall.hdr.type = TAttach;
+ fcall.hdr.fid = RootFid;
+ fcall.tattach.afid = IXP_NOFID;
+ fcall.tattach.uname = getenv("USER");
+ fcall.tattach.aname = "";
+ if(dofcall(c, &fcall) == 0) {
+ ixp_unmount(c);
+ return nil;
+ }
+
+ return c;
+}
+
+IxpClient*
+ixp_mount(const char *address) {
+ int fd;
+
+ fd = ixp_dial(address);
+ if(fd < 0)
+ return nil;
+ return ixp_mountfd(fd);
+}
+
+IxpClient*
+ixp_nsmount(const char *name) {
+ char *address;
+ IxpClient *c;
+
+ address = ixp_namespace();
+ if(address)
+ address = ixp_smprint("unix!%s/%s", address, name);
+ if(address == nil)
+ return nil;
+ c = ixp_mount(address);
+ free(address);
+ return c;
+}
+
+static IxpCFid*
+walk(IxpClient *c, const char *path) {
+ IxpCFid *f;
+ char *p;
+ Fcall fcall;
+ int n;
+
+ p = estrdup(path);
+ n = tokenize(fcall.twalk.wname, nelem(fcall.twalk.wname), p, '/');
+ f = getfid(c);
+
+ fcall.hdr.type = TWalk;
+ fcall.hdr.fid = RootFid;
+ fcall.twalk.nwname = n;
+ fcall.twalk.newfid = f->fid;
+ if(dofcall(c, &fcall) == 0)
+ goto fail;
+ if(fcall.rwalk.nwqid < n) {
+ werrstr("File does not exist");
+ if(fcall.rwalk.nwqid == 0)
+ werrstr("Protocol botch");
+ goto fail;
+ }
+
+ f->qid = fcall.rwalk.wqid[n-1];
+
+ ixp_freefcall(&fcall);
+ free(p);
+ return f;
+fail:
+ putfid(f);
+ free(p);
+ return nil;
+}
+
+static IxpCFid*
+walkdir(IxpClient *c, char *path, const char **rest) {
+ char *p;
+
+ p = path + strlen(path) - 1;
+ assert(p >= path);
+ while(*p == '/')
+ *p-- = '\0';
+
+ while((p > path) && (*p != '/'))
+ p--;
+ if(*p != '/') {
+ werrstr("bad path");
+ return nil;
+ }
+
+ *p++ = '\0';
+ *rest = p;
+ return walk(c, path);
+}
+
+static int
+clunk(IxpCFid *f) {
+ IxpClient *c;
+ Fcall fcall;
+ int ret;
+
+ c = f->client;
+
+ fcall.hdr.type = TClunk;
+ fcall.hdr.fid = f->fid;
+ ret = dofcall(c, &fcall);
+ if(ret)
+ putfid(f);
+ ixp_freefcall(&fcall);
+ return ret;
+}
+
+/**
+ * Function: ixp_remove
+ *
+ * Params:
+ * path - The path of the file to remove.
+ *
+ * Removes a file or directory from the remote server.
+ *
+ * Returns:
+ * ixp_remove returns 0 on failure, 1 on success.
+ */
+
+int
+ixp_remove(IxpClient *c, const char *path) {
+ Fcall fcall;
+ IxpCFid *f;
+ int ret;
+
+ if((f = walk(c, path)) == nil)
+ return 0;
+
+ fcall.hdr.type = TRemove;
+ fcall.hdr.fid = f->fid;;
+ ret = dofcall(c, &fcall);
+ ixp_freefcall(&fcall);
+ putfid(f);
+
+ return ret;
+}
+
+static void
+initfid(IxpCFid *f, Fcall *fcall) {
+ f->open = 1;
+ f->offset = 0;
+ f->iounit = fcall->ropen.iounit;
+ if(f->iounit == 0 || fcall->ropen.iounit > f->client->msize-24)
+ f->iounit = f->client->msize-24;
+ f->qid = fcall->ropen.qid;
+}
+
+/**
+ * Function: ixp_create
+ * Function: ixp_open
+ *
+ * Params:
+ * path - The path of the file to open or create.
+ * perm - The permissions with which to create the new
+ * file. These will be ANDed with those of the
+ * parent directory by the server.
+ * mode - The file's open mode.
+ *
+ * ixp_open and ixp_create each open a file at P<path>.
+ * P<mode> must include OREAD, OWRITE, or ORDWR, and may
+ * include any of the modes specified in 9pmodes(3).
+ * ixp_create, additionally, creates a file at P<path> if it
+ * doesn't already exist.
+ *
+ * Returns:
+ * A pointer on which to operate on the newly
+ * opened file.
+ */
+
+IxpCFid*
+ixp_create(IxpClient *c, const char *path, uint perm, uchar mode) {
+ Fcall fcall;
+ IxpCFid *f;
+ char *tpath;;
+
+ tpath = estrdup(path);
+
+ f = walkdir(c, tpath, &path);
+ if(f == nil)
+ goto done;
+
+ fcall.hdr.type = TCreate;
+ fcall.hdr.fid = f->fid;
+ fcall.tcreate.name = (char*)(uintptr_t)path;
+ fcall.tcreate.perm = perm;
+ fcall.tcreate.mode = mode;
+
+ if(dofcall(c, &fcall) == 0) {
+ clunk(f);
+ f = nil;
+ goto done;
+ }
+
+ initfid(f, &fcall);
+ f->mode = mode;
+
+ ixp_freefcall(&fcall);
+
+done:
+ free(tpath);
+ return f;
+}
+
+IxpCFid*
+ixp_open(IxpClient *c, const char *path, uchar mode) {
+ Fcall fcall;
+ IxpCFid *f;
+
+ f = walk(c, path);
+ if(f == nil)
+ return nil;
+
+ fcall.hdr.type = TOpen;
+ fcall.hdr.fid = f->fid;
+ fcall.topen.mode = mode;
+
+ if(dofcall(c, &fcall) == 0) {
+ clunk(f);
+ return nil;
+ }
+
+ initfid(f, &fcall);
+ f->mode = mode;
+
+ ixp_freefcall(&fcall);
+ return f;
+}
+
+/**
+ * Function: ixp_close
+ *
+ * Closes the file pointed to by P<f> and frees its
+ * associated data structures;
+ *
+ * Returns:
+ * Returns 1 on success, and zero on failure.
+ */
+
+int
+ixp_close(IxpCFid *f) {
+ return clunk(f);
+}
+
+static Stat*
+_stat(IxpClient *c, ulong fid) {
+ IxpMsg msg;
+ Fcall fcall;
+ Stat *stat;
+
+ fcall.hdr.type = TStat;
+ fcall.hdr.fid = fid;
+ if(dofcall(c, &fcall) == 0)
+ return nil;
+
+ msg = ixp_message((char*)fcall.rstat.stat, fcall.rstat.nstat, MsgUnpack);
+
+ stat = emalloc(sizeof *stat);
+ ixp_pstat(&msg, stat);
+ ixp_freefcall(&fcall);
+ if(msg.pos > msg.end) {
+ free(stat);
+ stat = nil;
+ }
+ return stat;
+}
+
+/**
+ * Function: ixp_stat
+ * Function: ixp_fstat
+ *
+ * Params:
+ * path - The path of the file to stat.
+ * f - A CFid of an open file to stat.
+ *
+ * Stats the file at P<path> or pointed to by P<f>.
+ *
+ * Returns:
+ * Returns a Stat structure, which must be freed by
+ * the caller with free(3).
+ *
+ * S<Stat>
+ */
+
+Stat*
+ixp_stat(IxpClient *c, const char *path) {
+ Stat *stat;
+ IxpCFid *f;
+
+ f = walk(c, path);
+ if(f == nil)
+ return nil;
+
+ stat = _stat(c, f->fid);
+ clunk(f);
+ return stat;
+}
+
+Stat*
+ixp_fstat(IxpCFid *f) {
+ return _stat(f->client, f->fid);
+}
+
+static long
+_pread(IxpCFid *f, char *buf, long count, vlong offset) {
+ Fcall fcall;
+ int n, len;
+
+ len = 0;
+ while(len < count) {
+ n = min(count-len, f->iounit);
+
+ fcall.hdr.type = TRead;
+ fcall.hdr.fid = f->fid;
+ fcall.tread.offset = offset;
+ fcall.tread.count = n;
+ if(dofcall(f->client, &fcall) == 0)
+ return -1;
+ if(fcall.rread.count > n)
+ return -1;
+
+ memcpy(buf+len, fcall.rread.data, fcall.rread.count);
+ offset += fcall.rread.count;
+ len += fcall.rread.count;
+
+ ixp_freefcall(&fcall);
+ if(fcall.rread.count < n)
+ break;
+ }
+ return len;
+}
+
+/**
+ * Function: ixp_read
+ * Function: ixp_pread
+ *
+ * Params:
+ * buf - A buffer in which to store the read data.
+ * count - The number of bytes to read.
+ * offset - The offset at which to begin reading.
+ *
+ * ixp_read and ixp_pread each read P<count> bytes of data
+ * from the file pointed to by P<f>, into P<buf>. ixp_read
+ * begins reading at its stored offset, and increments it by
+ * the number of bytes read. ixp_pread reads beginning at
+ * P<offset> and does not alter C<f>'s stored offset.
+ *
+ * Returns:
+ * These functions return the number of bytes read on
+ * success and -1 on failure.
+ */
+
+long
+ixp_read(IxpCFid *f, void *buf, long count) {
+ int n;
+
+ thread->lock(&f->iolock);
+ n = _pread(f, buf, count, f->offset);
+ if(n > 0)
+ f->offset += n;
+ thread->unlock(&f->iolock);
+ return n;
+}
+
+long
+ixp_pread(IxpCFid *f, void *buf, long count, vlong offset) {
+ int n;
+
+ thread->lock(&f->iolock);
+ n = _pread(f, buf, count, offset);
+ thread->unlock(&f->iolock);
+ return n;
+}
+
+static long
+_pwrite(IxpCFid *f, const void *buf, long count, vlong offset) {
+ Fcall fcall;
+ int n, len;
+
+ len = 0;
+ do {
+ n = min(count-len, f->iounit);
+ fcall.hdr.type = TWrite;
+ fcall.hdr.fid = f->fid;
+ fcall.twrite.offset = offset;
+ fcall.twrite.data = (char*)buf + len;
+ fcall.twrite.count = n;
+ if(dofcall(f->client, &fcall) == 0)
+ return -1;
+
+ offset += fcall.rwrite.count;
+ len += fcall.rwrite.count;
+
+ ixp_freefcall(&fcall);
+ if(fcall.rwrite.count < n)
+ break;
+ } while(len < count);
+ return len;
+}
+
+/**
+ * Function: ixp_write
+ * Function: ixp_pwrite
+ *
+ * Params:
+ * buf - A buffer holding the contents to store.
+ * count - The number of bytes to store.
+ * offset - The offset at which to write the data.
+ *
+ * ixp_write and ixp_pwrite each write P<count> bytes of
+ * data stored in P<buf> to the file pointed to by C<f>.
+ * ixp_write writes its data at its stored offset, and
+ * increments it by P<count>. ixp_pwrite writes its data a
+ * P<offset> and does not alter C<f>'s stored offset.
+ *
+ * Returns:
+ * These functions return the number of bytes actually
+ * written. Any value less than P<count> must be considered
+ * a failure.
+ */
+
+long
+ixp_write(IxpCFid *f, const void *buf, long count) {
+ int n;
+
+ thread->lock(&f->iolock);
+ n = _pwrite(f, buf, count, f->offset);
+ if(n > 0)
+ f->offset += n;
+ thread->unlock(&f->iolock);
+ return n;
+}
+
+long
+ixp_pwrite(IxpCFid *f, const void *buf, long count, vlong offset) {
+ int n;
+
+ thread->lock(&f->iolock);
+ n = _pwrite(f, buf, count, offset);
+ thread->unlock(&f->iolock);
+ return n;
+}
+
+/**
+ * Function: ixp_vprint
+ * Function: ixp_print
+ * Variable: ixp_vsmprint
+ *
+ * Params:
+ * fmt - The string with which to format the data.
+ * ap - A va_list holding the arguments to the format
+ * string.
+ * ... - The arguments to the format string.
+ *
+ * These functions act like the standard formatted IO
+ * functions. They write the result of the formatting to the
+ * file pointed to by C<f>.
+ *
+ * V<ixp_vsmprint> may be set to a function which will
+ * format its arguments and return a null terminated string
+ * allocated with malloc(3).
+ *
+ * Returns:
+ * These functions return the number of bytes written.
+ * There is currently no way to detect failure.
+ */
+
+int
+ixp_vprint(IxpCFid *f, const char *fmt, va_list ap) {
+ char *buf;
+ int n;
+
+ buf = ixp_vsmprint(fmt, ap);
+ if(buf == nil)
+ return -1;
+
+ n = ixp_write(f, buf, strlen(buf));
+ free(buf);
+ return n;
+}
+
+int
+ixp_print(IxpCFid *f, const char *fmt, ...) {
+ va_list ap;
+ int n;
+
+ va_start(ap, fmt);
+ n = ixp_vprint(f, fmt, ap);
+ va_end(ap);
+
+ return n;
+}
+
diff --git a/libixp/convert.c b/libixp/convert.c
new file mode 100644
index 0000000..24d05a5
--- /dev/null
+++ b/libixp/convert.c
@@ -0,0 +1,200 @@
+/* Copyright ©2007-2008 Kris Maglione <fbsdaemon@gmail.com>
+ * See LICENSE file for license details.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "ixp_local.h"
+
+enum {
+ SByte = 1,
+ SWord = 2,
+ SDWord = 4,
+ SQWord = 8,
+};
+
+static void
+ixp_puint(IxpMsg *msg, uint size, ulong *val) {
+ uchar *pos;
+ int v;
+
+ if(msg->pos + size <= msg->end) {
+ pos = (uchar*)msg->pos;
+ switch(msg->mode) {
+ case MsgPack:
+ v = *val;
+ switch(size) {
+ case SDWord:
+ pos[3] = v>>24;
+ pos[2] = v>>16;
+ case SWord:
+ pos[1] = v>>8;
+ case SByte:
+ pos[0] = v;
+ break;
+ }
+ case MsgUnpack:
+ v = 0;
+ switch(size) {
+ case SDWord:
+ v |= pos[3]<<24;
+ v |= pos[2]<<16;
+ case SWord:
+ v |= pos[1]<<8;
+ case SByte:
+ v |= pos[0];
+ break;
+ }
+ *val = v;
+ }
+ }
+ msg->pos += size;
+}
+
+void
+ixp_pu32(IxpMsg *msg, ulong *val) {
+ ixp_puint(msg, SDWord, val);
+}
+void
+ixp_pu8(IxpMsg *msg, uchar *val) {
+ ulong v;
+
+ v = *val;
+ ixp_puint(msg, SByte, &v);
+ *val = (uchar)v;
+}
+void
+ixp_pu16(IxpMsg *msg, ushort *val) {
+ ulong v;
+
+ v = *val;
+ ixp_puint(msg, SWord, &v);
+ *val = (ushort)v;
+}
+void
+ixp_pu64(IxpMsg *msg, uvlong *val) {
+ ulong vl, vb;
+
+ vl = (uint)*val;
+ vb = (uint)(*val>>32);
+ ixp_puint(msg, SDWord, &vl);
+ ixp_puint(msg, SDWord, &vb);
+ *val = vl | ((uvlong)vb<<32);
+}
+
+void
+ixp_pstring(IxpMsg *msg, char **s) {
+ ushort len;
+
+ if(msg->mode == MsgPack)
+ len = strlen(*s);
+ ixp_pu16(msg, &len);
+
+ if(msg->pos + len <= msg->end) {
+ if(msg->mode == MsgUnpack) {
+ *s = emalloc(len + 1);
+ memcpy(*s, msg->pos, len);
+ (*s)[len] = '\0';
+ }else
+ memcpy(msg->pos, *s, len);
+ }
+ msg->pos += len;
+}
+
+void
+ixp_pstrings(IxpMsg *msg, ushort *num, char *strings[]) {
+ char *s;
+ uint i, size;
+ ushort len;
+
+ ixp_pu16(msg, num);
+ if(*num > IXP_MAX_WELEM) {
+ msg->pos = msg->end+1;
+ return;
+ }
+
+ SET(s);
+ if(msg->mode == MsgUnpack) {
+ s = msg->pos;
+ size = 0;
+ for(i=0; i < *num; i++) {
+ ixp_pu16(msg, &len);
+ msg->pos += len;
+ size += len;
+ if(msg->pos > msg->end)
+ return;
+ }
+ msg->pos = s;
+ size += *num;
+ s = emalloc(size);
+ }
+
+ for(i=0; i < *num; i++) {
+ if(msg->mode == MsgPack)
+ len = strlen(strings[i]);
+ ixp_pu16(msg, &len);
+
+ if(msg->mode == MsgUnpack) {
+ memcpy(s, msg->pos, len);
+ strings[i] = (char*)s;
+ s += len;
+ msg->pos += len;
+ *s++ = '\0';
+ }else
+ ixp_pdata(msg, &strings[i], len);
+ }
+}
+
+void
+ixp_pdata(IxpMsg *msg, char **data, uint len) {
+ if(msg->pos + len <= msg->end) {
+ if(msg->mode == MsgUnpack) {
+ *data = emalloc(len);
+ memcpy(*data, msg->pos, len);
+ }else
+ memcpy(msg->pos, *data, len);
+ }
+ msg->pos += len;
+}
+
+void
+ixp_pqid(IxpMsg *msg, Qid *qid) {
+ ixp_pu8(msg, &qid->type);
+ ixp_pu32(msg, &qid->version);
+ ixp_pu64(msg, &qid->path);
+}
+
+void
+ixp_pqids(IxpMsg *msg, ushort *num, Qid qid[]) {
+ int i;
+
+ ixp_pu16(msg, num);
+ if(*num > IXP_MAX_WELEM) {
+ msg->pos = msg->end+1;
+ return;
+ }
+
+ for(i = 0; i < *num; i++)
+ ixp_pqid(msg, &qid[i]);
+}
+
+void
+ixp_pstat(IxpMsg *msg, Stat *stat) {
+ ushort size;
+
+ if(msg->mode == MsgPack)
+ size = ixp_sizeof_stat(stat) - 2;
+
+ ixp_pu16(msg, &size);
+ ixp_pu16(msg, &stat->type);
+ ixp_pu32(msg, &stat->dev);
+ ixp_pqid(msg, &stat->qid);
+ ixp_pu32(msg, &stat->mode);
+ ixp_pu32(msg, &stat->atime);
+ ixp_pu32(msg, &stat->mtime);
+ ixp_pu64(msg, &stat->length);
+ ixp_pstring(msg, &stat->name);
+ ixp_pstring(msg, &stat->uid);
+ ixp_pstring(msg, &stat->gid);
+ ixp_pstring(msg, &stat->muid);
+}
diff --git a/libixp/error.c b/libixp/error.c
new file mode 100644
index 0000000..871f5bc
--- /dev/null
+++ b/libixp/error.c
@@ -0,0 +1,103 @@
+/* Public Domain --Kris Maglione */
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "ixp_local.h"
+
+static int
+_vsnprint(char *buf, int n, const char *fmt, va_list ap) {
+ return vsnprintf(buf, n, fmt, ap);
+}
+
+static char*
+_vsmprint(const char *fmt, va_list ap) {
+ va_list al;
+ char *buf = "";
+ int n;
+
+ va_copy(al, ap);
+ n = vsnprintf(buf, 0, fmt, al);
+ va_end(al);
+
+ buf = malloc(++n);
+ if(buf)
+ vsnprintf(buf, n, fmt, ap);
+ return buf;
+}
+
+int (*ixp_vsnprint)(char*, int, const char*, va_list) = _vsnprint;
+char* (*ixp_vsmprint)(const char*, va_list) = _vsmprint;
+
+/* Approach to errno handling taken from Plan 9 Port. */
+enum {
+ EPLAN9 = 0x19283745,
+};
+
+/**
+ * Function: ixp_errbuf
+ * Function: ixp_errstr
+ * Function: ixp_rerrstr
+ * Function: ixp_werrstr
+ *
+ * Params:
+ * buf - The buffer to read and/or fill.
+ * n - The size of the buffer.
+ * fmt - A format string with which to write the * errstr.
+ * ... - Arguments to P<fmt>.
+ *
+ * These functions simulate Plan 9's errstr functionality.
+ * They replace errno in libixp. Note that these functions
+ * are not internationalized.
+ *
+ * F<ixp_errbuf> returns the errstr buffer for the current
+ * thread. F<ixp_rerrstr> fills P<buf> with the data from
+ * the current thread's error buffer, while F<ixp_errstr>
+ * exchanges P<buf>'s contents with those of the current
+ * thread's error buffer. F<ixp_werrstr> is takes a format
+ * string from which to construct an errstr.
+ *
+ * Returns:
+ * F<ixp_errbuf> returns the current thread's error
+ * string buffer.
+ */
+char*
+ixp_errbuf() {
+ char *errbuf;
+
+ errbuf = thread->errbuf();
+ if(errno == EINTR)
+ strncpy(errbuf, "interrupted", IXP_ERRMAX);
+ else if(errno != EPLAN9)
+ strncpy(errbuf, strerror(errno), IXP_ERRMAX);
+ return errbuf;
+}
+
+void
+errstr(char *buf, int n) {
+ char tmp[IXP_ERRMAX];
+
+ strncpy(tmp, buf, sizeof tmp);
+ rerrstr(buf, n);
+ strncpy(thread->errbuf(), tmp, IXP_ERRMAX);
+ errno = EPLAN9;
+}
+
+void
+rerrstr(char *buf, int n) {
+ strncpy(buf, ixp_errbuf(), n);
+}
+
+void
+werrstr(const char *fmt, ...) {
+ char tmp[IXP_ERRMAX];
+ va_list ap;
+
+ va_start(ap, fmt);
+ ixp_vsnprint(tmp, sizeof tmp, fmt, ap);
+ va_end(ap);
+ strncpy(thread->errbuf(), tmp, IXP_ERRMAX);
+ errno = EPLAN9;
+}
+
diff --git a/libixp/map.c b/libixp/map.c
new file mode 100644
index 0000000..c1e7379
--- /dev/null
+++ b/libixp/map.c
@@ -0,0 +1,132 @@
+/* Written by Kris Maglione */
+/* Public domain */
+#include <stdlib.h>
+#include "ixp_local.h"
+
+/* Edit s/^([a-zA-Z].*)\n([a-z].*) {/\1 \2;/g x/^([^a-zA-Z]|static|$)/-+d s/ (\*map|val|*str)//g */
+
+struct MapEnt {
+ ulong hash;
+ const char* key;
+ void* val;
+ MapEnt* next;
+};
+
+MapEnt *NM;
+
+static void
+insert(MapEnt **e, ulong val, const char *key) {
+ MapEnt *te;
+
+ te = emallocz(sizeof *te);
+ te->hash = val;
+ te->key = key;
+ te->next = *e;
+ *e = te;
+}
+
+static MapEnt**
+map_getp(Map *map, ulong val, bool create, bool *exists) {
+ MapEnt **e;
+
+ e = &map->bucket[val%map->nhash];
+ for(; *e; e = &(*e)->next)
+ if((*e)->hash >= val) break;
+ if(exists)
+ *exists = *e && (*e)->hash == val;
+
+ if(*e == nil || (*e)->hash != val) {
+ if(create)
+ insert(e, val, nil);
+ else
+ e = &NM;
+ }
+ return e;
+}
+
+void
+ixp_mapfree(Map *map, void (*destroy)(void*)) {
+ int i;
+ MapEnt *e;
+
+ thread->wlock(&map->lock);
+ for(i=0; i < map->nhash; i++)
+ while((e = map->bucket[i])) {
+ map->bucket[i] = e->next;
+ if(destroy)
+ destroy(e->val);
+ free(e);
+ }
+ thread->wunlock(&map->lock);
+ thread->rwdestroy(&map->lock);
+}
+
+void
+ixp_mapexec(Map *map, void (*run)(void*, void*), void *context) {
+ int i;
+ MapEnt *e;
+
+ thread->rlock(&map->lock);
+ for(i=0; i < map->nhash; i++)
+ for(e=map->bucket[i]; e; e=e->next)
+ run(context, e->val);
+ thread->runlock(&map->lock);
+}
+
+void
+ixp_mapinit(Map *map, MapEnt **buckets, int nbuckets) {
+
+ map->bucket = buckets;
+ map->nhash = nbuckets;
+
+ thread->initrwlock(&map->lock);
+}
+
+bool
+ixp_mapinsert(Map *map, ulong key, void *val, bool overwrite) {
+ MapEnt *e;
+ bool existed, res;
+
+ res = true;
+ thread->wlock(&map->lock);
+ e = *map_getp(map, key, true, &existed);
+ if(existed && !overwrite)
+ res = false;
+ else
+ e->val = val;
+ thread->wunlock(&map->lock);
+ return res;
+}
+
+void*
+ixp_mapget(Map *map, ulong val) {
+ MapEnt *e;
+ void *res;
+
+ thread->rlock(&map->lock);
+ e = *map_getp(map, val, false, nil);
+ res = e ? e->val : nil;
+ thread->runlock(&map->lock);
+ return res;
+}
+
+void*
+ixp_maprm(Map *map, ulong val) {
+ MapEnt **e, *te;
+ void *ret;
+
+ ret = nil;
+ thread->wlock(&map->lock);
+ e = map_getp(map, val, false, nil);
+ if(*e) {
+ te = *e;
+ ret = te->val;
+ *e = te->next;
+ thread->wunlock(&map->lock);
+ free(te);
+ }
+ else
+ thread->wunlock(&map->lock);
+ return ret;
+}
+
diff --git a/libixp/message.c b/libixp/message.c
new file mode 100644
index 0000000..08ba71e
--- /dev/null
+++ b/libixp/message.c
@@ -0,0 +1,205 @@
+/* Copyright ©2007-2008 Kris Maglione <fbsdaemon@gmail.com>
+ * See LICENSE file for license details.
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include "ixp_local.h"
+
+enum {
+ SByte = 1,
+ SWord = 2,
+ SDWord = 4,
+ SQWord = 8,
+};
+
+#define SString(s) (SWord + strlen(s))
+enum {
+ SQid = SByte + SDWord + SQWord,
+};
+
+IxpMsg
+ixp_message(char *data, uint length, uint mode) {
+ IxpMsg m;
+
+ m.data = data;
+ m.pos = data;
+ m.end = data + length;
+ m.size = length;
+ m.mode = mode;
+ return m;
+}
+
+void
+ixp_freestat(Stat *s) {
+ free(s->name);
+ free(s->uid);
+ free(s->gid);
+ free(s->muid);
+ s->name = s->uid = s->gid = s->muid = nil;
+}
+
+void
+ixp_freefcall(Fcall *fcall) {
+ switch(fcall->hdr.type) {
+ case RStat:
+ free(fcall->rstat.stat);
+ fcall->rstat.stat = nil;
+ break;
+ case RRead:
+ free(fcall->rread.data);
+ fcall->rread.data = nil;
+ break;
+ case RVersion:
+ free(fcall->version.version);
+ fcall->version.version = nil;
+ break;
+ case RError:
+ free(fcall->error.ename);
+ fcall->error.ename = nil;
+ break;
+ }
+}
+
+ushort
+ixp_sizeof_stat(Stat * stat) {
+ return SWord /* size */
+ + SWord /* type */
+ + SDWord /* dev */
+ + SQid /* qid */
+ + 3 * SDWord /* mode, atime, mtime */
+ + SQWord /* length */
+ + SString(stat->name)
+ + SString(stat->uid)
+ + SString(stat->gid)
+ + SString(stat->muid);
+}
+
+void
+ixp_pfcall(IxpMsg *msg, Fcall *fcall) {
+ ixp_pu8(msg, &fcall->hdr.type);
+ ixp_pu16(msg, &fcall->hdr.tag);
+
+ switch (fcall->hdr.type) {
+ case TVersion:
+ case RVersion:
+ ixp_pu32(msg, &fcall->version.msize);
+ ixp_pstring(msg, &fcall->version.version);
+ break;
+ case TAuth:
+ ixp_pu32(msg, &fcall->tauth.afid);
+ ixp_pstring(msg, &fcall->tauth.uname);
+ ixp_pstring(msg, &fcall->tauth.aname);
+ break;
+ case RAuth:
+ ixp_pqid(msg, &fcall->rauth.aqid);
+ break;
+ case RAttach:
+ ixp_pqid(msg, &fcall->rattach.qid);
+ break;
+ case TAttach:
+ ixp_pu32(msg, &fcall->hdr.fid);
+ ixp_pu32(msg, &fcall->tattach.afid);
+ ixp_pstring(msg, &fcall->tattach.uname);
+ ixp_pstring(msg, &fcall->tattach.aname);
+ break;
+ case RError:
+ ixp_pstring(msg, &fcall->error.ename);
+ break;
+ case TFlush:
+ ixp_pu16(msg, &fcall->tflush.oldtag);
+ break;
+ case TWalk:
+ ixp_pu32(msg, &fcall->hdr.fid);
+ ixp_pu32(msg, &fcall->twalk.newfid);
+ ixp_pstrings(msg, &fcall->twalk.nwname, fcall->twalk.wname);
+ break;
+ case RWalk:
+ ixp_pqids(msg, &fcall->rwalk.nwqid, fcall->rwalk.wqid);
+ break;
+ case TOpen:
+ ixp_pu32(msg, &fcall->hdr.fid);
+ ixp_pu8(msg, &fcall->topen.mode);
+ break;
+ case ROpen:
+ case RCreate:
+ ixp_pqid(msg, &fcall->ropen.qid);
+ ixp_pu32(msg, &fcall->ropen.iounit);
+ break;
+ case TCreate:
+ ixp_pu32(msg, &fcall->hdr.fid);
+ ixp_pstring(msg, &fcall->tcreate.name);
+ ixp_pu32(msg, &fcall->tcreate.perm);
+ ixp_pu8(msg, &fcall->tcreate.mode);
+ break;
+ case TRead:
+ ixp_pu32(msg, &fcall->hdr.fid);
+ ixp_pu64(msg, &fcall->tread.offset);
+ ixp_pu32(msg, &fcall->tread.count);
+ break;
+ case RRead:
+ ixp_pu32(msg, &fcall->rread.count);
+ ixp_pdata(msg, &fcall->rread.data, fcall->rread.count);
+ break;
+ case TWrite:
+ ixp_pu32(msg, &fcall->hdr.fid);
+ ixp_pu64(msg, &fcall->twrite.offset);
+ ixp_pu32(msg, &fcall->twrite.count);
+ ixp_pdata(msg, &fcall->twrite.data, fcall->twrite.count);
+ break;
+ case RWrite:
+ ixp_pu32(msg, &fcall->rwrite.count);
+ break;
+ case TClunk:
+ case TRemove:
+ case TStat:
+ ixp_pu32(msg, &fcall->hdr.fid);
+ break;
+ case RStat:
+ ixp_pu16(msg, &fcall->rstat.nstat);
+ ixp_pdata(msg, (char**)&fcall->rstat.stat, fcall->rstat.nstat);
+ break;
+ case TWStat: {
+ ushort size;
+ ixp_pu32(msg, &fcall->hdr.fid);
+ ixp_pu16(msg, &size);
+ ixp_pstat(msg, &fcall->twstat.stat);
+ break;
+ }
+ }
+}
+
+uint
+ixp_fcall2msg(IxpMsg *msg, Fcall *fcall) {
+ ulong size;
+
+ msg->end = msg->data + msg->size;
+ msg->pos = msg->data + SDWord;
+ msg->mode = MsgPack;
+ ixp_pfcall(msg, fcall);
+
+ if(msg->pos > msg->end)
+ return 0;
+
+ msg->end = msg->pos;
+ size = msg->end - msg->data;
+
+ msg->pos = msg->data;
+ ixp_pu32(msg, &size);
+
+ msg->pos = msg->data;
+ return size;
+}
+
+uint
+ixp_msg2fcall(IxpMsg *msg, Fcall *fcall) {
+ msg->pos = msg->data + SDWord;
+ msg->mode = MsgUnpack;
+ ixp_pfcall(msg, fcall);
+
+ if(msg->pos > msg->end)
+ return 0;
+
+ return msg->pos - msg->data;
+}
+
diff --git a/libixp/request.c b/libixp/request.c
new file mode 100644
index 0000000..3ea868d
--- /dev/null
+++ b/libixp/request.c
@@ -0,0 +1,550 @@
+/* Copyright ©2006-2008 Kris Maglione <fbsdaemon@gmail.com>
+ * See LICENSE file for license details.
+ */
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/socket.h>
+#include "ixp_local.h"
+
+static void handlereq(Ixp9Req *r);
+
+static void
+_printfcall(Fcall *f) {
+ USED(f);
+}
+void (*ixp_printfcall)(Fcall*) = _printfcall;
+
+static int
+min(int a, int b) {
+ if(a < b)
+ return a;
+ return b;
+}
+
+static char
+ Eduptag[] = "tag in use",
+ Edupfid[] = "fid in use",
+ Enofunc[] = "function not implemented",
+ Eopen[] = "fid is already open",
+ Enofile[] = "file does not exist",
+ Enoread[] = "file not open for reading",
+ Enofid[] = "fid does not exist",
+ Enotag[] = "tag does not exist",
+ Enotdir[] = "not a directory",
+ Eintr[] = "interrupted",
+ Eisdir[] = "cannot perform operation on a directory";
+
+enum {
+ TAG_BUCKETS = 61,
+ FID_BUCKETS = 61,
+};
+
+struct Ixp9Conn {
+ Map tagmap;
+ Map fidmap;
+ MapEnt* taghash[TAG_BUCKETS];
+ MapEnt* fidhash[FID_BUCKETS];
+ Ixp9Srv* srv;
+ IxpConn* conn;
+ IxpMutex rlock;
+ IxpMutex wlock;
+ IxpMsg rmsg;
+ IxpMsg wmsg;
+ int ref;
+};
+
+static void
+decref_p9conn(Ixp9Conn *p9conn) {
+ thread->lock(&p9conn->wlock);
+ if(--p9conn->ref > 0) {
+ thread->unlock(&p9conn->wlock);
+ return;
+ }
+ thread->unlock(&p9conn->wlock);
+
+ assert(p9conn->conn == nil);
+
+ thread->mdestroy(&p9conn->rlock);
+ thread->mdestroy(&p9conn->wlock);
+
+ ixp_mapfree(&p9conn->tagmap, nil);
+ ixp_mapfree(&p9conn->fidmap, nil);
+
+ free(p9conn->rmsg.data);
+ free(p9conn->wmsg.data);
+ free(p9conn);
+}
+
+static void*
+createfid(Map *map, int fid, Ixp9Conn *p9conn) {
+ Fid *f;
+
+ f = emallocz(sizeof *f);
+ p9conn->ref++;
+ f->conn = p9conn;
+ f->fid = fid;
+ f->omode = -1;
+ f->map = map;
+ if(ixp_mapinsert(map, fid, f, false))
+ return f;
+ free(f);
+ return nil;
+}
+
+static int
+destroyfid(Ixp9Conn *p9conn, ulong fid) {
+ Fid *f;
+
+ f = ixp_maprm(&p9conn->fidmap, fid);
+ if(f == nil)
+ return 0;
+
+ if(p9conn->srv->freefid)
+ p9conn->srv->freefid(f);
+
+ decref_p9conn(p9conn);
+ free(f);
+ return 1;
+}
+
+static void
+handlefcall(IxpConn *c) {
+ Fcall fcall = {0};
+ Ixp9Conn *p9conn;
+ Ixp9Req *req;
+
+ p9conn = c->aux;
+
+ thread->lock(&p9conn->rlock);
+ if(ixp_recvmsg(c->fd, &p9conn->rmsg) == 0)
+ goto Fail;
+ if(ixp_msg2fcall(&p9conn->rmsg, &fcall) == 0)
+ goto Fail;
+ thread->unlock(&p9conn->rlock);
+
+ req = emallocz(sizeof *req);
+ p9conn->ref++;
+ req->conn = p9conn;
+ req->srv = p9conn->srv;
+ req->ifcall = fcall;
+ p9conn->conn = c;
+
+ if(!ixp_mapinsert(&p9conn->tagmap, fcall.hdr.tag, req, false)) {
+ respond(req, Eduptag);
+ return;
+ }
+
+ handlereq(req);
+ return;
+
+Fail:
+ thread->unlock(&p9conn->rlock);
+ ixp_hangup(c);
+ return;
+}
+
+static void
+handlereq(Ixp9Req *r) {
+ Ixp9Conn *p9conn;
+ Ixp9Srv *srv;
+
+ p9conn = r->conn;
+ srv = p9conn->srv;
+
+ ixp_printfcall(&r->ifcall);
+
+ switch(r->ifcall.hdr.type) {
+ default:
+ respond(r, Enofunc);
+ break;
+ case TVersion:
+ if(!strcmp(r->ifcall.version.version, "9P"))
+ r->ofcall.version.version = "9P";
+ else if(!strcmp(r->ifcall.version.version, "9P2000"))
+ r->ofcall.version.version = "9P2000";
+ else
+ r->ofcall.version.version = "unknown";
+ r->ofcall.version.msize = r->ifcall.version.msize;
+ respond(r, nil);
+ break;
+ case TAttach:
+ if(!(r->fid = createfid(&p9conn->fidmap, r->ifcall.hdr.fid, p9conn))) {
+ respond(r, Edupfid);
+ return;
+ }
+ /* attach is a required function */
+ srv->attach(r);
+ break;
+ case TClunk:
+ if(!(r->fid = ixp_mapget(&p9conn->fidmap, r->ifcall.hdr.fid))) {
+ respond(r, Enofid);
+ return;
+ }
+ if(!srv->clunk) {
+ respond(r, nil);
+ return;
+ }
+ srv->clunk(r);
+ break;
+ case TFlush:
+ if(!(r->oldreq = ixp_mapget(&p9conn->tagmap, r->ifcall.tflush.oldtag))) {
+ respond(r, Enotag);
+ return;
+ }
+ if(!srv->flush) {
+ respond(r, Enofunc);
+ return;
+ }
+ srv->flush(r);
+ break;
+ case TCreate:
+ if(!(r->fid = ixp_mapget(&p9conn->fidmap, r->ifcall.hdr.fid))) {
+ respond(r, Enofid);
+ return;
+ }
+ if(r->fid->omode != -1) {
+ respond(r, Eopen);
+ return;
+ }
+ if(!(r->fid->qid.type&QTDIR)) {
+ respond(r, Enotdir);
+ return;
+ }
+ if(!p9conn->srv->create) {
+ respond(r, Enofunc);
+ return;
+ }
+ p9conn->srv->create(r);
+ break;
+ case TOpen:
+ if(!(r->fid = ixp_mapget(&p9conn->fidmap, r->ifcall.hdr.fid))) {
+ respond(r, Enofid);
+ return;
+ }
+ if((r->fid->qid.type&QTDIR) && (r->ifcall.topen.mode|P9_ORCLOSE) != (P9_OREAD|P9_ORCLOSE)) {
+ respond(r, Eisdir);
+ return;
+ }
+ r->ofcall.ropen.qid = r->fid->qid;
+ if(!p9conn->srv->open) {
+ respond(r, Enofunc);
+ return;
+ }
+ p9conn->srv->open(r);
+ break;
+ case TRead:
+ if(!(r->fid = ixp_mapget(&p9conn->fidmap, r->ifcall.hdr.fid))) {
+ respond(r, Enofid);
+ return;
+ }
+ if(r->fid->omode == -1 || r->fid->omode == P9_OWRITE) {
+ respond(r, Enoread);
+ return;
+ }
+ if(!p9conn->srv->read) {
+ respond(r, Enofunc);
+ return;
+ }
+ p9conn->srv->read(r);
+ break;
+ case TRemove:
+ if(!(r->fid = ixp_mapget(&p9conn->fidmap, r->ifcall.hdr.fid))) {
+ respond(r, Enofid);
+ return;
+ }
+ if(!p9conn->srv->remove) {
+ respond(r, Enofunc);
+ return;
+ }
+ p9conn->srv->remove(r);
+ break;
+ case TStat:
+ if(!(r->fid = ixp_mapget(&p9conn->fidmap, r->ifcall.hdr.fid))) {
+ respond(r, Enofid);
+ return;
+ }
+ if(!p9conn->srv->stat) {
+ respond(r, Enofunc);
+ return;
+ }
+ p9conn->srv->stat(r);
+ break;
+ case TWalk:
+ if(!(r->fid = ixp_mapget(&p9conn->fidmap, r->ifcall.hdr.fid))) {
+ respond(r, Enofid);
+ return;
+ }
+ if(r->fid->omode != -1) {
+ respond(r, "cannot walk from an open fid");
+ return;
+ }
+ if(r->ifcall.twalk.nwname && !(r->fid->qid.type&QTDIR)) {
+ respond(r, Enotdir);
+ return;
+ }
+ if((r->ifcall.hdr.fid != r->ifcall.twalk.newfid)) {
+ if(!(r->newfid = createfid(&p9conn->fidmap, r->ifcall.twalk.newfid, p9conn))) {
+ respond(r, Edupfid);
+ return;
+ }
+ }else
+ r->newfid = r->fid;
+ if(!p9conn->srv->walk) {
+ respond(r, Enofunc);
+ return;
+ }
+ p9conn->srv->walk(r);
+ break;
+ case TWrite:
+ if(!(r->fid = ixp_mapget(&p9conn->fidmap, r->ifcall.hdr.fid))) {
+ respond(r, Enofid);
+ return;
+ }
+ if((r->fid->omode&3) != P9_OWRITE && (r->fid->omode&3) != P9_ORDWR) {
+ respond(r, "write on fid not opened for writing");
+ return;
+ }
+ if(!p9conn->srv->write) {
+ respond(r, Enofunc);
+ return;
+ }
+ p9conn->srv->write(r);
+ break;
+ case TWStat:
+ if(!(r->fid = ixp_mapget(&p9conn->fidmap, r->ifcall.hdr.fid))) {
+ respond(r, Enofid);
+ return;
+ }
+ if((ushort)~r->ifcall.twstat.stat.type) {
+ respond(r, "wstat of type");
+ return;
+ }
+ if((uint)~r->ifcall.twstat.stat.dev) {
+ respond(r, "wstat of dev");
+ return;
+ }
+ if((uchar)~r->ifcall.twstat.stat.qid.type || (ulong)~r->ifcall.twstat.stat.qid.version || (uvlong)~r->ifcall.twstat.stat.qid.path) {
+ respond(r, "wstat of qid");
+ return;
+ }
+ if(r->ifcall.twstat.stat.muid && r->ifcall.twstat.stat.muid[0]) {
+ respond(r, "wstat of muid");
+ return;
+ }
+ if((ulong)~r->ifcall.twstat.stat.mode && ((r->ifcall.twstat.stat.mode&DMDIR)>>24) != r->fid->qid.type&QTDIR) {
+ respond(r, "wstat on DMDIR bit");
+ return;
+ }
+ if(!p9conn->srv->wstat) {
+ respond(r, Enofunc);
+ return;
+ }
+ p9conn->srv->wstat(r);
+ break;
+ /* Still to be implemented: auth */
+ }
+}
+
+void
+respond(Ixp9Req *r, const char *error) {
+ Ixp9Conn *p9conn;
+ int msize;
+
+ p9conn = r->conn;
+
+ switch(r->ifcall.hdr.type) {
+ default:
+ if(!error)
+ assert(!"Respond called on unsupported fcall type");
+ break;
+ case TVersion:
+ assert(error == nil);
+ free(r->ifcall.version.version);
+
+ thread->lock(&p9conn->rlock);
+ thread->lock(&p9conn->wlock);
+ msize = min(r->ofcall.version.msize, IXP_MAX_MSG);
+ p9conn->rmsg.data = erealloc(p9conn->rmsg.data, msize);
+ p9conn->wmsg.data = erealloc(p9conn->wmsg.data, msize);
+ p9conn->rmsg.size = msize;
+ p9conn->wmsg.size = msize;
+ thread->unlock(&p9conn->wlock);
+ thread->unlock(&p9conn->rlock);
+ r->ofcall.version.msize = msize;
+ break;
+ case TAttach:
+ if(error)
+ destroyfid(p9conn, r->fid->fid);
+ free(r->ifcall.tattach.uname);
+ free(r->ifcall.tattach.aname);
+ break;
+ case TOpen:
+ case TCreate:
+ if(!error) {
+ r->ofcall.ropen.iounit = p9conn->rmsg.size - 24;
+ r->fid->iounit = r->ofcall.ropen.iounit;
+ r->fid->omode = r->ifcall.topen.mode;
+ r->fid->qid = r->ofcall.ropen.qid;
+ }
+ free(r->ifcall.tcreate.name);
+ break;
+ case TWalk:
+ if(error || r->ofcall.rwalk.nwqid < r->ifcall.twalk.nwname) {
+ if(r->ifcall.hdr.fid != r->ifcall.twalk.newfid && r->newfid)
+ destroyfid(p9conn, r->newfid->fid);
+ if(!error && r->ofcall.rwalk.nwqid == 0)
+ error = Enofile;
+ }else{
+ if(r->ofcall.rwalk.nwqid == 0)
+ r->newfid->qid = r->fid->qid;
+ else
+ r->newfid->qid = r->ofcall.rwalk.wqid[r->ofcall.rwalk.nwqid-1];
+ }
+ free(*r->ifcall.twalk.wname);
+ break;
+ case TWrite:
+ free(r->ifcall.twrite.data);
+ break;
+ case TRemove:
+ if(r->fid)
+ destroyfid(p9conn, r->fid->fid);
+ break;
+ case TClunk:
+ if(r->fid)
+ destroyfid(p9conn, r->fid->fid);
+ break;
+ case TFlush:
+ if((r->oldreq = ixp_mapget(&p9conn->tagmap, r->ifcall.tflush.oldtag)))
+ respond(r->oldreq, Eintr);
+ break;
+ case TWStat:
+ ixp_freestat(&r->ifcall.twstat.stat);
+ break;
+ case TRead:
+ case TStat:
+ break;
+ /* Still to be implemented: auth */
+ }
+
+ r->ofcall.hdr.tag = r->ifcall.hdr.tag;
+
+ if(error == nil)
+ r->ofcall.hdr.type = r->ifcall.hdr.type + 1;
+ else {
+ r->ofcall.hdr.type = RError;
+ r->ofcall.error.ename = (char*)error;
+ }
+
+ ixp_printfcall(&r->ofcall);
+
+ ixp_maprm(&p9conn->tagmap, r->ifcall.hdr.tag);;
+
+ if(p9conn->conn) {
+ thread->lock(&p9conn->wlock);
+ msize = ixp_fcall2msg(&p9conn->wmsg, &r->ofcall);
+ if(ixp_sendmsg(p9conn->conn->fd, &p9conn->wmsg) != msize)
+ ixp_hangup(p9conn->conn);
+ thread->unlock(&p9conn->wlock);
+ }
+
+ switch(r->ofcall.hdr.type) {
+ case RStat:
+ free(r->ofcall.rstat.stat);
+ break;
+ case RRead:
+ free(r->ofcall.rread.data);
+ break;
+ }
+ free(r);
+ decref_p9conn(p9conn);
+}
+
+/* Flush a pending request */
+static void
+voidrequest(void *context, void *arg) {
+ Ixp9Req *orig_req, *flush_req;
+ Ixp9Conn *conn;
+
+ orig_req = arg;
+ conn = orig_req->conn;
+ conn->ref++;
+
+ flush_req = emallocz(sizeof *orig_req);
+ flush_req->ifcall.hdr.type = TFlush;
+ flush_req->ifcall.hdr.tag = IXP_NOTAG;
+ flush_req->ifcall.tflush.oldtag = orig_req->ifcall.hdr.tag;
+ flush_req->conn = conn;
+
+ flush_req->aux = *(void**)context;
+ *(void**)context = flush_req;
+}
+
+/* Clunk an open Fid */
+static void
+voidfid(void *context, void *arg) {
+ Ixp9Conn *p9conn;
+ Ixp9Req *clunk_req;
+ Fid *fid;
+
+ fid = arg;
+ p9conn = fid->conn;
+ p9conn->ref++;
+
+ clunk_req = emallocz(sizeof *clunk_req);
+ clunk_req->ifcall.hdr.type = TClunk;
+ clunk_req->ifcall.hdr.tag = IXP_NOTAG;
+ clunk_req->ifcall.hdr.fid = fid->fid;
+ clunk_req->fid = fid;
+ clunk_req->conn = p9conn;
+
+ clunk_req->aux = *(void**)context;
+ *(void**)context = clunk_req;
+}
+
+static void
+cleanupconn(IxpConn *c) {
+ Ixp9Conn *p9conn;
+ Ixp9Req *req, *r;
+
+ p9conn = c->aux;
+ p9conn->conn = nil;
+ req = nil;
+ if(p9conn->ref > 1) {
+ ixp_mapexec(&p9conn->fidmap, voidfid, &req);
+ ixp_mapexec(&p9conn->tagmap, voidrequest, &req);
+ }
+ while((r = req)) {
+ req = r->aux;
+ r->aux = nil;
+ handlereq(r);
+ }
+ decref_p9conn(p9conn);
+}
+
+/* Handle incoming 9P connections */
+void
+serve_9pcon(IxpConn *c) {
+ Ixp9Conn *p9conn;
+ int fd;
+
+ fd = accept(c->fd, nil, nil);
+ if(fd < 0)
+ return;
+
+ p9conn = emallocz(sizeof *p9conn);
+ p9conn->ref++;
+ p9conn->srv = c->aux;
+ p9conn->rmsg.size = 1024;
+ p9conn->wmsg.size = 1024;
+ p9conn->rmsg.data = emalloc(p9conn->rmsg.size);
+ p9conn->wmsg.data = emalloc(p9conn->wmsg.size);
+
+ ixp_mapinit(&p9conn->tagmap, p9conn->taghash, nelem(p9conn->taghash));
+ ixp_mapinit(&p9conn->fidmap, p9conn->fidhash, nelem(p9conn->fidhash));
+ thread->initmutex(&p9conn->rlock);
+ thread->initmutex(&p9conn->wlock);
+
+ ixp_listen(c->srv, fd, p9conn, handlefcall, cleanupconn);
+}
diff --git a/libixp/rpc.c b/libixp/rpc.c
new file mode 100644
index 0000000..4a22436
--- /dev/null
+++ b/libixp/rpc.c
@@ -0,0 +1,262 @@
+/* From Plan 9's libmux.
+ * Copyright (c) 2003 Russ Cox, Massachusetts Institute of Technology
+ * Distributed under the same terms as libixp.
+ */
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include "ixp_local.h"
+
+static int gettag(IxpClient*, IxpRpc*);
+static void puttag(IxpClient*, IxpRpc*);
+static void enqueue(IxpClient*, IxpRpc*);
+static void dequeue(IxpClient*, IxpRpc*);
+
+void
+muxinit(IxpClient *mux)
+{
+ mux->tagrend.mutex = &mux->lk;
+ mux->sleep.next = &mux->sleep;
+ mux->sleep.prev = &mux->sleep;
+ thread->initmutex(&mux->lk);
+ thread->initmutex(&mux->rlock);
+ thread->initmutex(&mux->wlock);
+ thread->initrendez(&mux->tagrend);
+}
+
+void
+muxfree(IxpClient *mux)
+{
+ thread->mdestroy(&mux->lk);
+ thread->mdestroy(&mux->rlock);
+ thread->mdestroy(&mux->wlock);
+ thread->rdestroy(&mux->tagrend);
+ free(mux->wait);
+}
+
+static void
+initrpc(IxpClient *mux, IxpRpc *r)
+{
+ r->mux = mux;
+ r->waiting = 1;
+ r->r.mutex = &mux->lk;
+ r->p = nil;
+ thread->initrendez(&r->r);
+}
+
+static void
+freemuxrpc(IxpRpc *r)
+{
+ thread->rdestroy(&r->r);
+}
+
+static int
+sendrpc(IxpRpc *r, Fcall *f)
+{
+ int ret;
+ IxpClient *mux;
+
+ ret = 0;
+ mux = r->mux;
+ /* assign the tag, add selves to response queue */
+ thread->lock(&mux->lk);
+ r->tag = gettag(mux, r);
+ f->hdr.tag = r->tag;
+ enqueue(mux, r);
+ thread->unlock(&mux->lk);
+
+ thread->lock(&mux->wlock);
+ if(!ixp_fcall2msg(&mux->wmsg, f) || !ixp_sendmsg(mux->fd, &mux->wmsg)) {
+ /* werrstr("settag/send tag %d: %r", tag); fprint(2, "%r\n"); */
+ thread->lock(&mux->lk);
+ dequeue(mux, r);
+ puttag(mux, r);
+ thread->unlock(&mux->lk);
+ ret = -1;
+ }
+ thread->unlock(&mux->wlock);
+ return ret;
+}
+
+static Fcall*
+muxrecv(IxpClient *mux)
+{
+ Fcall *f;
+
+ f = nil;
+ thread->lock(&mux->rlock);
+ if(ixp_recvmsg(mux->fd, &mux->rmsg) == 0)
+ goto fail;
+ f = emallocz(sizeof *f);
+ if(ixp_msg2fcall(&mux->rmsg, f) == 0) {
+ free(f);
+ f = nil;
+ }
+fail:
+ thread->unlock(&mux->rlock);
+ return f;
+}
+
+static void
+dispatchandqlock(IxpClient *mux, Fcall *f)
+{
+ int tag;
+ IxpRpc *r2;
+
+ tag = f->hdr.tag - mux->mintag;
+ thread->lock(&mux->lk);
+ /* hand packet to correct sleeper */
+ if(tag < 0 || tag >= mux->mwait) {
+ fprintf(stderr, "libixp: recieved unfeasible tag: %d (min: %d, max: %d)\n", f->hdr.tag, mux->mintag, mux->mintag+mux->mwait);
+ goto fail;
+ }
+ r2 = mux->wait[tag];
+ if(r2 == nil || r2->prev == nil) {
+ fprintf(stderr, "libixp: recieved message with bad tag\n");
+ goto fail;
+ }
+ r2->p = f;
+ dequeue(mux, r2);
+ thread->wake(&r2->r);
+ return;
+fail:
+ ixp_freefcall(f);
+ free(f);
+}
+
+static void
+electmuxer(IxpClient *mux)
+{
+ IxpRpc *rpc;
+
+ /* if there is anyone else sleeping, wake them to mux */
+ for(rpc=mux->sleep.next; rpc != &mux->sleep; rpc = rpc->next){
+ if(!rpc->async){
+ mux->muxer = rpc;
+ thread->wake(&rpc->r);
+ return;
+ }
+ }
+ mux->muxer = nil;
+}
+
+Fcall*
+muxrpc(IxpClient *mux, Fcall *tx)
+{
+ IxpRpc r;
+ Fcall *p;
+
+ initrpc(mux, &r);
+ if(sendrpc(&r, tx) < 0)
+ return nil;
+
+ thread->lock(&mux->lk);
+ /* wait for our packet */
+ while(mux->muxer && mux->muxer != &r && !r.p)
+ thread->sleep(&r.r);
+
+ /* if not done, there's no muxer; start muxing */
+ if(!r.p){
+ assert(mux->muxer == nil || mux->muxer == &r);
+ mux->muxer = &r;
+ while(!r.p){
+ thread->unlock(&mux->lk);
+ p = muxrecv(mux);
+ if(p == nil){
+ /* eof -- just give up and pass the buck */
+ thread->lock(&mux->lk);
+ dequeue(mux, &r);
+ break;
+ }
+ dispatchandqlock(mux, p);
+ }
+ electmuxer(mux);
+ }
+ p = r.p;
+ puttag(mux, &r);
+ thread->unlock(&mux->lk);
+ if(p == nil)
+ werrstr("unexpected eof");
+ return p;
+}
+
+static void
+enqueue(IxpClient *mux, IxpRpc *r)
+{
+ r->next = mux->sleep.next;
+ r->prev = &mux->sleep;
+ r->next->prev = r;
+ r->prev->next = r;
+}
+
+static void
+dequeue(IxpClient *mux, IxpRpc *r)
+{
+ r->next->prev = r->prev;
+ r->prev->next = r->next;
+ r->prev = nil;
+ r->next = nil;
+}
+
+static int
+gettag(IxpClient *mux, IxpRpc *r)
+{
+ int i, mw;
+ IxpRpc **w;
+
+ for(;;){
+ /* wait for a free tag */
+ while(mux->nwait == mux->mwait){
+ if(mux->mwait < mux->maxtag-mux->mintag){
+ mw = mux->mwait;
+ if(mw == 0)
+ mw = 1;
+ else
+ mw <<= 1;
+ w = realloc(mux->wait, mw * sizeof *w);
+ if(w == nil)
+ return -1;
+ memset(w+mux->mwait, 0, (mw-mux->mwait) * sizeof *w);
+ mux->wait = w;
+ mux->freetag = mux->mwait;
+ mux->mwait = mw;
+ break;
+ }
+ thread->sleep(&mux->tagrend);
+ }
+
+ i=mux->freetag;
+ if(mux->wait[i] == 0)
+ goto Found;
+ for(; i<mux->mwait; i++)
+ if(mux->wait[i] == 0)
+ goto Found;
+ for(i=0; i<mux->freetag; i++)
+ if(mux->wait[i] == 0)
+ goto Found;
+ /* should not fall out of while without free tag */
+ abort();
+ }
+
+Found:
+ mux->nwait++;
+ mux->wait[i] = r;
+ r->tag = i+mux->mintag;
+ return r->tag;
+}
+
+static void
+puttag(IxpClient *mux, IxpRpc *r)
+{
+ int i;
+
+ i = r->tag - mux->mintag;
+ assert(mux->wait[i] == r);
+ mux->wait[i] = nil;
+ mux->nwait--;
+ mux->freetag = i;
+ thread->wake(&mux->tagrend);
+ freemuxrpc(r);
+}
+
diff --git a/libixp/server.c b/libixp/server.c
new file mode 100644
index 0000000..271cf44
--- /dev/null
+++ b/libixp/server.c
@@ -0,0 +1,165 @@
+/* Copyright ©2004-2006 Anselm R. Garbe <garbeam at gmail dot com>
+ * See LICENSE file for license details.
+ */
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include "ixp_local.h"
+
+/**
+ * Function: ixp_listen
+ *
+ * Params:
+ * fs - The file descriptor on which to listen.
+ * aux - A piece of data to store in the connection's
+ * T<IxpConn> data structure.
+ * read - The function to call when the connection has
+ * data available to read.
+ * close - A cleanup function to call when the
+ * connection is closed.
+ *
+ * Starts the server P<s> listening on P<fd>. The optional
+ * callbacks are called as described, with the connections
+ * T<IxpConn> data structure as their arguments.
+ *
+ * Returns:
+ * Returns the connection's new T<IxpConn> data
+ * structure.
+ *
+ * S<IxpConn>
+ */
+IxpConn*
+ixp_listen(IxpServer *s, int fd, void *aux,
+ void (*read)(IxpConn *c),
+ void (*close)(IxpConn *c)
+ ) {
+ IxpConn *c;
+
+ c = emallocz(sizeof *c);
+ c->fd = fd;
+ c->aux = aux;
+ c->srv = s;
+ c->read = read;
+ c->close = close;
+ c->next = s->conn;
+ s->conn = c;
+ return c;
+}
+
+/**
+ * Function: ixp_hangup
+ * Function: ixp_server_close
+ *
+ * ixp_hangup closes a connection, and stops the server
+ * listening on it. It calls the connection's close
+ * function, if it exists. ixp_server_close calls ixp_hangup
+ * on all of the connections on which the server is
+ * listening.
+ */
+
+void
+ixp_hangup(IxpConn *c) {
+ IxpServer *s;
+ IxpConn **tc;
+
+ s = c->srv;
+ for(tc=&s->conn; *tc; tc=&(*tc)->next)
+ if(*tc == c) break;
+ assert(*tc == c);
+
+ *tc = c->next;
+ c->closed = 1;
+ if(c->close)
+ c->close(c);
+ else
+ shutdown(c->fd, SHUT_RDWR);
+
+ close(c->fd);
+ free(c);
+}
+
+void
+ixp_server_close(IxpServer *s) {
+ IxpConn *c, *next;
+
+ for(c = s->conn; c; c = next) {
+ next = c->next;
+ ixp_hangup(c);
+ }
+}
+
+static void
+prepare_select(IxpServer *s) {
+ IxpConn *c;
+
+ FD_ZERO(&s->rd);
+ for(c = s->conn; c; c = c->next)
+ if(c->read) {
+ if(s->maxfd < c->fd)
+ s->maxfd = c->fd;
+ FD_SET(c->fd, &s->rd);
+ }
+}
+
+static void
+handle_conns(IxpServer *s) {
+ IxpConn *c, *n;
+ for(c = s->conn; c; c = n) {
+ n = c->next;
+ if(FD_ISSET(c->fd, &s->rd))
+ c->read(c);
+ }
+}
+
+/**
+ * Function: ixp_serverloop
+ *
+ * Enters the main loop of the server. Exits when
+ * P<s>->running becomes false, or when select(2) returns an
+ * error other than EINTR.
+ *
+ * S<IxpServer>
+ *
+ * Returns:
+ * Returns 0 when the loop exits normally, and 1 when
+ * it exits on error. V<errno> or the return value of
+ * ixp_errbuf(3) may be inspected.
+ *
+ */
+
+int
+ixp_serverloop(IxpServer *s) {
+ timeval *tvp;
+ timeval tv;
+ long timeout;
+ int r;
+
+ s->running = 1;
+ thread->initmutex(&s->lk);
+ while(s->running) {
+ if(s->preselect)
+ s->preselect(s);
+
+ tvp = nil;
+ timeout = ixp_nexttimer(s);
+ if(timeout > 0) {
+ tv.tv_sec = timeout/1000;
+ tv.tv_usec = timeout%1000 * 1000;
+ tvp = &tv;
+ }
+
+ prepare_select(s);
+ r = thread->select(s->maxfd + 1, &s->rd, 0, 0, tvp);
+ if(r < 0) {
+ if(errno == EINTR)
+ continue;
+ return 1;
+ }
+ handle_conns(s);
+ }
+ return 0;
+}
+
diff --git a/libixp/socket.c b/libixp/socket.c
new file mode 100644
index 0000000..d34f5c3
--- /dev/null
+++ b/libixp/socket.c
@@ -0,0 +1,278 @@
+/* Copyright ©2007-2008 Kris Maglione <fbsdaemon@gmail.com>
+ * Copyright ©2004-2006 Anselm R. Garbe <garbeam at gmail dot com>
+ * See LICENSE file for license details.
+ */
+#include <errno.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <signal.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include "ixp_local.h"
+
+/* Note: These functions modify the strings that they are passed.
+ * The lookup function duplicates the original string, so it is
+ * not modified.
+ */
+
+/* From FreeBSD's sys/su.h */
+#define SUN_LEN(su) \
+ (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path))
+
+typedef struct addrinfo addrinfo;
+typedef struct sockaddr sockaddr;
+typedef struct sockaddr_un sockaddr_un;
+typedef struct sockaddr_in sockaddr_in;
+
+static char*
+get_port(char *addr) {
+ char *s;
+
+ s = strchr(addr, '!');
+ if(s == nil) {
+ werrstr("no port provided");
+ return nil;
+ }
+
+ *s++ = '\0';
+ if(*s == '\0') {
+ werrstr("invalid port number");
+ return nil;
+ }
+ return s;
+}
+
+static int
+sock_unix(char *address, sockaddr_un *sa, socklen_t *salen) {
+ int fd;
+
+ memset(sa, 0, sizeof *sa);
+
+ sa->sun_family = AF_UNIX;
+ strncpy(sa->sun_path, address, sizeof sa->sun_path);
+ *salen = SUN_LEN(sa);
+
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if(fd < 0)
+ return -1;
+ return fd;
+}
+
+static int
+dial_unix(char *address) {
+ sockaddr_un sa;
+ socklen_t salen;
+ int fd;
+
+ fd = sock_unix(address, &sa, &salen);
+ if(fd == -1)
+ return fd;
+
+ if(connect(fd, (sockaddr*) &sa, salen)) {
+ close(fd);
+ return -1;
+ }
+ return fd;
+}
+
+static int
+announce_unix(char *file) {
+ const int yes = 1;
+ sockaddr_un sa;
+ socklen_t salen;
+ int fd;
+
+ signal(SIGPIPE, SIG_IGN);
+
+ fd = sock_unix(file, &sa, &salen);
+ if(fd == -1)
+ return fd;
+
+ if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void*)&yes, sizeof yes) < 0)
+ goto fail;
+
+ unlink(file);
+ if(bind(fd, (sockaddr*)&sa, salen) < 0)
+ goto fail;
+
+ chmod(file, S_IRWXU);
+ if(listen(fd, IXP_MAX_CACHE) < 0)
+ goto fail;
+
+ return fd;
+
+fail:
+ close(fd);
+ return -1;
+}
+
+static addrinfo*
+alookup(char *host, int announce) {
+ addrinfo hints, *ret;
+ char *port;
+ int err;
+
+ /* Truncates host at '!' */
+ port = get_port(host);
+ if(port == nil)
+ return nil;
+
+ memset(&hints, 0, sizeof hints);
+ hints.ai_family = AF_INET;
+ hints.ai_socktype = SOCK_STREAM;
+
+ if(announce) {
+ hints.ai_flags = AI_PASSIVE;
+ if(!strcmp(host, "*"))
+ host = nil;
+ }
+
+ err = getaddrinfo(host, port, &hints, &ret);
+ if(err) {
+ werrstr("getaddrinfo: %s", gai_strerror(err));
+ return nil;
+ }
+ return ret;
+}
+
+static int
+ai_socket(addrinfo *ai) {
+ return socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+}
+
+static int
+dial_tcp(char *host) {
+ addrinfo *ai, *aip;
+ int fd;
+
+ aip = alookup(host, 0);
+ if(aip == nil)
+ return -1;
+
+ SET(fd);
+ for(ai = aip; ai; ai = ai->ai_next) {
+ fd = ai_socket(ai);
+ if(fd == -1) {
+ werrstr("socket: %s", strerror(errno));
+ continue;
+ }
+
+ if(connect(fd, ai->ai_addr, ai->ai_addrlen) == 0)
+ break;
+
+ werrstr("connect: %s", strerror(errno));
+ close(fd);
+ fd = -1;
+ }
+
+ freeaddrinfo(aip);
+ return fd;
+}
+
+static int
+announce_tcp(char *host) {
+ addrinfo *ai, *aip;
+ int fd;
+
+ aip = alookup(host, 1);
+ if(aip == nil)
+ return -1;
+
+ /* Probably don't need to loop */
+ SET(fd);
+ for(ai = aip; ai; ai = ai->ai_next) {
+ fd = ai_socket(ai);
+ if(fd == -1)
+ continue;
+
+ if(bind(fd, ai->ai_addr, ai->ai_addrlen) < 0)
+ goto fail;
+
+ if(listen(fd, IXP_MAX_CACHE) < 0)
+ goto fail;
+ break;
+ fail:
+ close(fd);
+ fd = -1;
+ }
+
+ freeaddrinfo(aip);
+ return fd;
+}
+
+typedef struct addrtab addrtab;
+static
+struct addrtab {
+ char *type;
+ int (*fn)(char*);
+} dtab[] = {
+ {"tcp", dial_tcp},
+ {"unix", dial_unix},
+ {0, 0}
+}, atab[] = {
+ {"tcp", announce_tcp},
+ {"unix", announce_unix},
+ {0, 0}
+};
+
+static int
+lookup(const char *address, addrtab *tab) {
+ char *addr, *type;
+ int ret;
+
+ ret = -1;
+ type = estrdup(address);
+
+ addr = strchr(type, '!');
+ if(addr == nil)
+ werrstr("no address type defined");
+ else {
+ *addr++ = '\0';
+ for(; tab->type; tab++)
+ if(strcmp(tab->type, type) == 0) break;
+ if(tab->type == nil)
+ werrstr("unsupported address type");
+ else
+ ret = tab->fn(addr);
+ }
+
+ free(type);
+ return ret;
+}
+
+/**
+ * Function: ixp_dial
+ * Function: ixp_announce
+ *
+ * Params:
+ * address - An address on which to connect or listen,
+ * specified in the Plan 9 resources
+ * specification format
+ * (<protocol>!address[!<port>])
+ *
+ * These functions hide some of the ugliness of Berkely
+ * Sockets. ixp_dial connects to the resource at P<address>,
+ * while ixp_announce begins listening on P<address>.
+ *
+ * Returns:
+ * These functions return file descriptors on success,
+ * and -1 on failure. ixp_errbuf(3) may be inspected on
+ * failure.
+ */
+
+int
+ixp_dial(const char *address) {
+ return lookup(address, dtab);
+}
+
+int
+ixp_announce(const char *address) {
+ return lookup(address, atab);
+}
+
diff --git a/libixp/srv_util.c b/libixp/srv_util.c
new file mode 100644
index 0000000..48b9369
--- /dev/null
+++ b/libixp/srv_util.c
@@ -0,0 +1,457 @@
+/* Copyright ©2006-2008 Kris Maglione <fbsdaemon at gmail dot com>
+ * See LICENSE file for license details.
+ */
+#include <assert.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include "ixp_local.h"
+
+typedef void* IxpFileIdU;
+
+static char
+ Enofile[] = "file not found";
+
+#include "ixp_srvutil.h"
+
+struct IxpQueue {
+ IxpQueue* link;
+ char* dat;
+ long len;
+};
+
+/* Macros */
+#define QID(t, i) (((vlong)((t)&0xFF)<<32)|((i)&0xFFFFFFFF))
+
+/* Global Vars */
+/***************/
+static IxpFileId* free_fileid;
+
+/* Utility Functions */
+/**
+ * Obtain an empty, reference counted IxpFileId struct.
+ */
+IxpFileId*
+ixp_srv_getfile(void) {
+ IxpFileId *file;
+ int i;
+
+ if(!free_fileid) {
+ i = 15;
+ file = emallocz(i * sizeof *file);
+ for(; i; i--) {
+ file->next = free_fileid;
+ free_fileid = file++;
+ }
+ }
+ file = free_fileid;
+ free_fileid = file->next;
+ file->p = nil;
+ file->volatil = 0;
+ file->nref = 1;
+ file->next = nil;
+ file->pending = false;
+ return file;
+}
+
+/**
+ * Decrease the reference count of the given IxpFileId,
+ * and push it onto the free list when it reaches 0;
+ */
+void
+ixp_srv_freefile(IxpFileId *f) {
+ if(--f->nref)
+ return;
+ free(f->tab.name);
+ f->next = free_fileid;
+ free_fileid = f;
+}
+
+/**
+ * Increase the reference count of every IxpFileId linked
+ * to 'f'.
+ */
+IxpFileId*
+ixp_srv_clonefiles(IxpFileId *f) {
+ IxpFileId *r;
+
+ r = emalloc(sizeof *r);
+ memcpy(r, f, sizeof *r);
+ r->tab.name = estrdup(r->tab.name);
+ r->nref = 1;
+ for(f=f->next; f; f=f->next)
+ assert(f->nref++);
+ return r;
+}
+
+void
+ixp_srv_readbuf(Ixp9Req *req, char *buf, uint len) {
+
+ if(req->ifcall.io.offset >= len)
+ return;
+
+ len -= req->ifcall.io.offset;
+ if(len > req->ifcall.io.count)
+ len = req->ifcall.io.count;
+ req->ofcall.io.data = emalloc(len);
+ memcpy(req->ofcall.io.data, buf + req->ifcall.io.offset, len);
+ req->ofcall.io.count = len;
+}
+
+void
+ixp_srv_writebuf(Ixp9Req *req, char **buf, uint *len, uint max) {
+ IxpFileId *file;
+ char *p;
+ uint offset, count;
+
+ file = req->fid->aux;
+
+ offset = req->ifcall.io.offset;
+ if(file->tab.perm & DMAPPEND)
+ offset = *len;
+
+ if(offset > *len || req->ifcall.io.count == 0) {
+ req->ofcall.io.count = 0;
+ return;
+ }
+
+ count = req->ifcall.io.count;
+ if(max && (offset + count > max))
+ count = max - offset;
+
+ *len = offset + count;
+ if(max == 0)
+ *buf = erealloc(*buf, *len + 1);
+ p = *buf;
+
+ memcpy(p+offset, req->ifcall.io.data, count);
+ req->ofcall.io.count = count;
+ p[offset+count] = '\0';
+}
+
+/**
+ * Ensure that the data member of 'r' is null terminated,
+ * removing any new line from its end.
+ */
+void
+ixp_srv_data2cstring(Ixp9Req *req) {
+ char *p, *q;
+ uint i;
+
+ i = req->ifcall.io.count;
+ p = req->ifcall.io.data;
+ if(i && p[i - 1] == '\n')
+ i--;
+ q = memchr(p, '\0', i);
+ if(q)
+ i = q - p;
+
+ p = erealloc(req->ifcall.io.data, i+1);
+ p[i] = '\0';
+ req->ifcall.io.data = p;
+}
+
+char*
+ixp_srv_writectl(Ixp9Req *req, char* (*fn)(void*, IxpMsg*)) {
+ char *err, *s, *p, c;
+ IxpFileId *file;
+ IxpMsg msg;
+
+ file = req->fid->aux;
+
+ ixp_srv_data2cstring(req);
+ s = req->ifcall.io.data;
+
+ err = nil;
+ c = *s;
+ while(c != '\0') {
+ while(*s == '\n')
+ s++;
+ p = s;
+ while(*p != '\0' && *p != '\n')
+ p++;
+ c = *p;
+ *p = '\0';
+
+ msg = ixp_message(s, p-s, 0);
+ s = fn(file->p, &msg);
+ if(s)
+ err = s;
+ s = p + 1;
+ }
+ return err;
+}
+
+void
+ixp_pending_respond(Ixp9Req *req) {
+ IxpFileId *file;
+ IxpPendingLink *p;
+ IxpRequestLink *req_link;
+ IxpQueue *queue;
+
+ file = req->fid->aux;
+ assert(file->pending);
+ p = file->p;
+ if(p->queue) {
+ queue = p->queue;
+ p->queue = queue->link;
+ req->ofcall.io.data = queue->dat;
+ req->ofcall.io.count = queue->len;
+ if(req->aux) {
+ req_link = req->aux;
+ req_link->next->prev = req_link->prev;
+ req_link->prev->next = req_link->next;
+ free(req_link);
+ }
+ respond(req, nil);
+ free(queue);
+ }else {
+ req_link = emallocz(sizeof *req_link);
+ req_link->req = req;
+ req_link->next = &p->pending->req;
+ req_link->prev = req_link->next->prev;
+ req_link->next->prev = req_link;
+ req_link->prev->next = req_link;
+ req->aux = req_link;
+ }
+}
+
+void
+ixp_pending_write(IxpPending *pending, char *dat, long n) {
+ IxpRequestLink req_link;
+ IxpQueue **qp, *queue;
+ IxpPendingLink *pp;
+ IxpRequestLink *rp;
+
+ if(n == 0)
+ return;
+
+ if(pending->req.next == nil) {
+ pending->req.next = &pending->req;
+ pending->req.prev = &pending->req;
+ pending->fids.prev = &pending->fids;
+ pending->fids.next = &pending->fids;
+ }
+
+ for(pp=pending->fids.next; pp != &pending->fids; pp=pp->next) {
+ for(qp=&pp->queue; *qp; qp=&qp[0]->link)
+ ;
+ queue = emallocz(sizeof *queue);
+ queue->dat = emalloc(n);
+ memcpy(queue->dat, dat, n);
+ queue->len = n;
+ *qp = queue;
+ }
+
+ req_link.next = &req_link;
+ req_link.prev = &req_link;
+ if(pending->req.next != &pending->req) {
+ req_link.next = pending->req.next;
+ req_link.prev = pending->req.prev;
+ pending->req.prev = &pending->req;
+ pending->req.next = &pending->req;
+ }
+ req_link.prev->next = &req_link;
+ req_link.next->prev = &req_link;
+
+ while((rp = req_link.next) != &req_link)
+ ixp_pending_respond(rp->req);
+}
+
+void
+ixp_pending_pushfid(IxpPending *pending, IxpFid *fid) {
+ IxpPendingLink *pend_link;
+ IxpFileId *file;
+
+ if(pending->req.next == nil) {
+ pending->req.next = &pending->req;
+ pending->req.prev = &pending->req;
+ pending->fids.prev = &pending->fids;
+ pending->fids.next = &pending->fids;
+ }
+
+ file = fid->aux;
+ pend_link = emallocz(sizeof *pend_link);
+ pend_link->fid = fid;
+ pend_link->pending = pending;
+ pend_link->next = &pending->fids;
+ pend_link->prev = pend_link->next->prev;
+ pend_link->next->prev = pend_link;
+ pend_link->prev->next = pend_link;
+ file->pending = true;
+ file->p = pend_link;
+}
+
+static void
+pending_flush(Ixp9Req *req) {
+ IxpFileId *file;
+ IxpRequestLink *req_link;
+
+ file = req->fid->aux;
+ if(file->pending) {
+ req_link = req->aux;
+ if(req_link) {
+ req_link->prev->next = req_link->next;
+ req_link->next->prev = req_link->prev;
+ free(req_link);
+ }
+ }
+}
+
+void
+ixp_pending_flush(Ixp9Req *req) {
+
+ pending_flush(req->oldreq);
+}
+
+bool
+ixp_pending_clunk(Ixp9Req *req) {
+ IxpPending *pending;
+ IxpPendingLink *pend_link;
+ IxpRequestLink *req_link;
+ Ixp9Req *r;
+ IxpFileId *file;
+ IxpQueue *queue;
+ bool more;
+
+ file = req->fid->aux;
+ pend_link = file->p;
+
+ pending = pend_link->pending;
+ for(req_link=pending->req.next; req_link != &pending->req;) {
+ r = req_link->req;
+ req_link = req_link->next;
+ if(r->fid == pend_link->fid) {
+ pending_flush(r);
+ respond(r, "interrupted");
+ }
+ }
+
+ pend_link->prev->next = pend_link->next;
+ pend_link->next->prev = pend_link->prev;
+
+ while((queue = pend_link->queue)) {
+ pend_link->queue = queue->link;
+ free(queue->dat);
+ free(queue);
+ }
+ more = (pend_link->pending->fids.next == &pend_link->pending->fids);
+ free(pend_link);
+ respond(req, nil);
+ return more;
+}
+
+bool
+ixp_srv_verifyfile(IxpFileId *file, IxpLookupFn lookup) {
+ IxpFileId *tfile;
+ int ret;
+
+ if(!file->next)
+ return true;
+
+ ret = false;
+ if(ixp_srv_verifyfile(file->next, lookup)) {
+ tfile = lookup(file->next, file->tab.name);
+ if(tfile) {
+ if(!tfile->volatil || tfile->p == file->p)
+ ret = true;
+ ixp_srv_freefile(tfile);
+ }
+ }
+ return ret;
+}
+
+void
+ixp_srv_readdir(Ixp9Req *req, IxpLookupFn lookup, void (*dostat)(IxpStat*, IxpFileId*)) {
+ IxpMsg msg;
+ IxpFileId *file, *tfile;
+ IxpStat stat;
+ char *buf;
+ ulong size, n;
+ uvlong offset;
+
+ file = req->fid->aux;
+
+ size = req->ifcall.io.count;
+ if(size > req->fid->iounit)
+ size = req->fid->iounit;
+ buf = emallocz(size);
+ msg = ixp_message(buf, size, MsgPack);
+
+ file = lookup(file, nil);
+ tfile = file;
+ /* Note: The first file is ".", so we skip it. */
+ offset = 0;
+ for(file=file->next; file; file=file->next) {
+ dostat(&stat, file);
+ n = ixp_sizeof_stat(&stat);
+ if(offset >= req->ifcall.io.offset) {
+ if(size < n)
+ break;
+ ixp_pstat(&msg, &stat);
+ size -= n;
+ }
+ offset += n;
+ }
+ while((file = tfile)) {
+ tfile=tfile->next;
+ ixp_srv_freefile(file);
+ }
+ req->ofcall.io.count = msg.pos - msg.data;
+ req->ofcall.io.data = msg.data;
+ respond(req, nil);
+}
+
+void
+ixp_srv_walkandclone(Ixp9Req *req, IxpLookupFn lookup) {
+ IxpFileId *file, *tfile;
+ int i;
+
+ file = ixp_srv_clonefiles(req->fid->aux);
+ for(i=0; i < req->ifcall.twalk.nwname; i++) {
+ if(!strcmp(req->ifcall.twalk.wname[i], "..")) {
+ if(file->next) {
+ tfile=file;
+ file=file->next;
+ ixp_srv_freefile(tfile);
+ }
+ }else{
+ tfile = lookup(file, req->ifcall.twalk.wname[i]);
+ if(!tfile)
+ break;
+ assert(!tfile->next);
+ if(strcmp(req->ifcall.twalk.wname[i], ".")) {
+ tfile->next = file;
+ file = tfile;
+ }
+ }
+ req->ofcall.rwalk.wqid[i].type = file->tab.qtype;
+ req->ofcall.rwalk.wqid[i].path = QID(file->tab.type, file->id);
+ }
+ /* There should be a way to do this on freefid() */
+ if(i < req->ifcall.twalk.nwname) {
+ while((tfile = file)) {
+ file=file->next;
+ ixp_srv_freefile(tfile);
+ }
+ respond(req, Enofile);
+ return;
+ }
+ /* Remove refs for req->fid if no new fid */
+ if(req->ifcall.hdr.fid == req->ifcall.twalk.newfid) {
+ tfile = req->fid->aux;
+ req->fid->aux = file;
+ while((file = tfile)) {
+ tfile = tfile->next;
+ ixp_srv_freefile(file);
+ }
+ }else
+ req->newfid->aux = file;
+ req->ofcall.rwalk.nwqid = i;
+ respond(req, nil);
+}
+
diff --git a/libixp/thread.c b/libixp/thread.c
new file mode 100644
index 0000000..ee0216c
--- /dev/null
+++ b/libixp/thread.c
@@ -0,0 +1,97 @@
+/* Public Domain --Kris Maglione */
+#include <unistd.h>
+#include "ixp_local.h"
+
+static IxpThread ixp_nothread;
+IxpThread*ixp_thread = &ixp_nothread;
+
+static char*
+errbuf(void) {
+ static char errbuf[IXP_ERRMAX];
+
+ return errbuf;
+}
+
+static void
+mvoid(IxpMutex *m) {
+ USED(m);
+ return;
+}
+
+static int
+mtrue(IxpMutex *m) {
+ USED(m);
+ return 1;
+}
+
+static int
+mfalse(IxpMutex *m) {
+ USED(m);
+ return 0;
+}
+
+static void
+rwvoid(IxpRWLock *rw) {
+ USED(rw);
+ return;
+}
+
+static int
+rwtrue(IxpRWLock *rw) {
+ USED(rw);
+ return 1;
+}
+
+static int
+rwfalse(IxpRWLock *m) {
+ USED(m);
+ return 0;
+}
+
+static void
+rvoid(IxpRendez *r) {
+ USED(r);
+ return;
+}
+
+static int
+rfalse(IxpRendez *r) {
+ USED(r);
+ return 0;
+}
+
+static void
+rsleep(IxpRendez *r) {
+ USED(r);
+ eprint("rsleep called when not implemented\n");
+}
+
+static IxpThread ixp_nothread = {
+ /* RWLock */
+ .initrwlock = rwfalse,
+ .rlock = rwvoid,
+ .runlock = rwvoid,
+ .canrlock = rwtrue,
+ .wlock = rwvoid,
+ .wunlock = rwvoid,
+ .canwlock = rwtrue,
+ .rwdestroy = rwvoid,
+ /* Mutex */
+ .initmutex = mfalse,
+ .lock = mvoid,
+ .unlock = mvoid,
+ .canlock = mtrue,
+ .mdestroy = mvoid,
+ /* Rendez */
+ .initrendez = rfalse,
+ .sleep = rsleep,
+ .wake = rfalse,
+ .wakeall = rfalse,
+ .rdestroy = rvoid,
+ /* Other */
+ .errbuf = errbuf,
+ .read = read,
+ .write = write,
+ .select = select,
+};
+
diff --git a/libixp/timer.c b/libixp/timer.c
new file mode 100644
index 0000000..77c68cc
--- /dev/null
+++ b/libixp/timer.c
@@ -0,0 +1,139 @@
+/* Copyright ©2008 Kris Maglione <fbsdaemon@gmail.com>
+ * See LICENSE file for license details.
+ */
+#include <assert.h>
+#include <stdlib.h>
+#include <sys/time.h>
+#include "ixp_local.h"
+
+/* This really needn't be threadsafe, as it has little use in
+ * threaded programs, but it is, nonetheless.
+ */
+
+static long lastid = 1;
+
+/**
+ * Function: ixp_msec
+ *
+ * Returns:
+ * Returns the time since the Epoch in milliseconds.
+ * Be aware that this may overflow.
+ */
+long
+ixp_msec(void) {
+ timeval tv;
+
+ if(gettimeofday(&tv, 0) < 0)
+ return -1;
+ return tv.tv_sec*1000 + tv.tv_usec/1000;
+}
+
+/**
+ * Function: ixp_settimer
+ *
+ * Params:
+ * msec - The timeout in milliseconds.
+ * fn - The function to call after P<msec> milliseconds
+ * have elapsed.
+ * aux - An arbitrary argument to pass to P<fn> when it
+ * is called.
+ *
+ * Initializes a callback-based timer to be triggerred after
+ * P<msec> milliseconds. The timer is passed its id number
+ * and the value of P<aux>.
+ *
+ * Returns:
+ * Returns the new timer's unique id number.
+ */
+long
+ixp_settimer(IxpServer *s, long msec, void (*fn)(long, void*), void *aux) {
+ Timer **tp;
+ Timer *t;
+ long time;
+
+ time = ixp_msec();
+ if(time == -1)
+ return -1;
+ msec += time;
+
+ t = emallocz(sizeof *t);
+ thread->lock(&s->lk);
+ t->id = lastid++;
+ t->msec = msec;
+ t->fn = fn;
+ t->aux = aux;
+
+ for(tp=&s->timer; *tp; tp=&tp[0]->link)
+ if(tp[0]->msec < msec)
+ break;
+ t->link = *tp;
+ *tp = t;
+ thread->unlock(&s->lk);
+ return t->id;
+}
+
+/**
+ * Function: ixp_unsettimer
+ *
+ * Params:
+ * id - The id number of the timer to void.
+ *
+ * Voids the timer identified by P<id>.
+ *
+ * Returns:
+ * Returns true if a timer was stopped, false
+ * otherwise.
+ */
+int
+ixp_unsettimer(IxpServer *s, long id) {
+ Timer **tp;
+ Timer *t;
+
+ thread->lock(&s->lk);
+ for(tp=&s->timer; (t=*tp); tp=&t->link)
+ if(t->id == id)
+ break;
+ if(t) {
+ *tp = t->link;
+ free(t);
+ }
+ thread->unlock(&s->lk);
+ return t != nil;
+}
+
+/**
+ * Function: ixp_nexttimer
+ *
+ * Triggers any timers whose timeouts have ellapsed. This is
+ * primarilly intended to be called from libixp's select
+ * loop.
+ *
+ * Returns:
+ * Returns the number of milliseconds until the next
+ * timer's timeout.
+ */
+long
+ixp_nexttimer(IxpServer *s) {
+ Timer *t;
+ long time, ret;
+
+ SET(time);
+ thread->lock(&s->lk);
+ while((t = s->timer)) {
+ time = ixp_msec();
+ if(t->msec > time)
+ break;
+ s->timer = t->link;
+
+ thread->unlock(&s->lk);
+ t->fn(t->id, t->aux);
+ free(t);
+ thread->lock(&s->lk);
+ }
+ ret = 0;
+ if(t)
+ ret = t->msec - time;
+ thread->unlock(&s->lk);
+ return ret;
+}
+
diff --git a/libixp/transport.c b/libixp/transport.c
new file mode 100644
index 0000000..89db84c
--- /dev/null
+++ b/libixp/transport.c
@@ -0,0 +1,97 @@
+/* Copyright ©2007-2008 Kris Maglione <fbsdaemon@gmail.com>
+ * See LICENSE file for license details.
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include "ixp_local.h"
+
+static int
+mread(int fd, IxpMsg *msg, uint count) {
+ int r, n;
+
+ n = msg->end - msg->pos;
+ if(n <= 0) {
+ werrstr("buffer full");
+ return -1;
+ }
+ if(n > count)
+ n = count;
+
+ r = thread->read(fd, msg->pos, n);
+ if(r > 0)
+ msg->pos += r;
+ return r;
+}
+
+static int
+readn(int fd, IxpMsg *msg, uint count) {
+ uint num;
+ int r;
+
+ num = count;
+ while(num > 0) {
+ r = mread(fd, msg, num);
+ if(r == -1 && errno == EINTR)
+ continue;
+ if(r == 0) {
+ werrstr("broken pipe: %s", ixp_errbuf());
+ return count - num;
+ }
+ num -= r;
+ }
+ return count - num;
+}
+
+uint
+ixp_sendmsg(int fd, IxpMsg *msg) {
+ int r;
+
+ msg->pos = msg->data;
+ while(msg->pos < msg->end) {
+ r = thread->write(fd, msg->pos, msg->end - msg->pos);
+ if(r < 1) {
+ if(errno == EINTR)
+ continue;
+ werrstr("broken pipe: %s", ixp_errbuf());
+ return 0;
+ }
+ msg->pos += r;
+ }
+ return msg->pos - msg->data;
+}
+
+uint
+ixp_recvmsg(int fd, IxpMsg *msg) {
+ enum { SSize = 4 };
+ ulong msize, size;
+
+ msg->mode = MsgUnpack;
+ msg->pos = msg->data;
+ msg->end = msg->data + msg->size;
+ if(readn(fd, msg, SSize) != SSize)
+ return 0;
+
+ msg->pos = msg->data;
+ ixp_pu32(msg, &msize);
+
+ size = msize - SSize;
+ if(size >= msg->end - msg->pos) {
+ werrstr("message too large");
+ return 0;
+ }
+ if(readn(fd, msg, size) != size) {
+ werrstr("message incomplete");
+ return 0;
+ }
+
+ msg->end = msg->pos;
+ return msize;
+}
+
diff --git a/libixp/util.c b/libixp/util.c
new file mode 100644
index 0000000..01d183f
--- /dev/null
+++ b/libixp/util.c
@@ -0,0 +1,240 @@
+/* Written by Kris Maglione <fbsdaemon at gmail dot com> */
+/* Public domain */
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <pwd.h>
+#include "ixp_local.h"
+
+char*
+ixp_smprint(const char *fmt, ...) {
+ va_list ap;
+ char *s;
+
+ va_start(ap, fmt);
+ s = ixp_vsmprint(fmt, ap);
+ va_end(ap);
+ if(s == nil)
+ ixp_werrstr("no memory");
+ return s;
+}
+
+static char*
+_user(void) {
+ static char *user;
+ struct passwd *pw;
+
+ if(user == nil) {
+ pw = getpwuid(getuid());
+ if(pw)
+ user = strdup(pw->pw_name);
+ }
+ if(user == nil)
+ user = "none";
+ return user;
+}
+
+static int
+rmkdir(char *path, int mode) {
+ char *p;
+ int ret;
+ char c;
+
+ for(p = path+1; ; p++) {
+ c = *p;
+ if((c == '/') || (c == '\0')) {
+ *p = '\0';
+ ret = mkdir(path, mode);
+ if((ret == -1) && (errno != EEXIST)) {
+ ixp_werrstr("Can't create path '%s': %s", path, ixp_errbuf());
+ return 0;
+ }
+ *p = c;
+ }
+ if(c == '\0')
+ break;
+ }
+ return 1;
+}
+
+static char*
+ns_display(void) {
+ char *path, *disp;
+ struct stat st;
+
+ disp = getenv("DISPLAY");
+ if(disp == nil || disp[0] == '\0') {
+ ixp_werrstr("$DISPLAY is unset");
+ return nil;
+ }
+
+ disp = estrdup(disp);
+ path = &disp[strlen(disp) - 2];
+ if(path > disp && !strcmp(path, ".0"))
+ *path = '\0';
+
+ path = ixp_smprint("/tmp/ns.%s.%s", _user(), disp);
+ free(disp);
+
+ if(!rmkdir(path, 0700))
+ ;
+ else if(stat(path, &st))
+ ixp_werrstr("Can't stat ns_path '%s': %s", path, ixp_errbuf());
+ else if(getuid() != st.st_uid)
+ ixp_werrstr("ns_path '%s' exists but is not owned by you", path);
+ else if((st.st_mode & 077) && chmod(path, st.st_mode & ~077))
+ ixp_werrstr("Namespace path '%s' exists, but has wrong permissions: %s", path, ixp_errbuf());
+ else
+ return path;
+ free(path);
+ return nil;
+}
+
+/**
+ * Function: ixp_namespace
+ *
+ * Returns the path of the canonical 9p namespace directory.
+ * Either the value of $NAMESPACE, if it's set, or, roughly,
+ * /tmp/ns.${USER}.${DISPLAY:%.0=%}. In the latter case, the
+ * directory is created if it doesn't exist, and it is
+ * ensured to be owned by the current user, with no group or
+ * other permissions.
+ *
+ * Returns:
+ * A statically allocated string which must not be freed
+ * or altered by the caller. The same value is returned
+ * upon successive calls.
+ */
+/* Not especially threadsafe. */
+char*
+ixp_namespace(void) {
+ static char *namespace;
+
+ if(namespace == nil)
+ namespace = getenv("NAMESPACE");
+ if(namespace == nil)
+ namespace = ns_display();
+ return namespace;
+}
+
+void
+eprint(const char *fmt, ...) {
+ va_list ap;
+ int err;
+
+ err = errno;
+ fprintf(stderr, "libixp: fatal: ");
+
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+
+ if(fmt[strlen(fmt)-1] == ':')
+ fprintf(stderr, " %s\n", strerror(err));
+ else
+ fprintf(stderr, "\n");
+
+ exit(1);
+}
+
+/* Can't malloc */
+static void
+mfatal(char *name, uint size) {
+ const char
+ couldnot[] = "libixp: fatal: Could not ",
+ paren[] = "() ",
+ bytes[] = " bytes\n";
+ char sizestr[8];
+ int i;
+
+ i = sizeof sizestr;
+ do {
+ sizestr[--i] = '0' + (size%10);
+ size /= 10;
+ } while(size > 0);
+
+ write(1, couldnot, sizeof(couldnot)-1);
+ write(1, name, strlen(name));
+ write(1, paren, sizeof(paren)-1);
+ write(1, sizestr+i, sizeof(sizestr)-i);
+ write(1, bytes, sizeof(bytes)-1);
+
+ exit(1);
+}
+
+void*
+emalloc(uint size) {
+ void *ret = malloc(size);
+ if(!ret)
+ mfatal("malloc", size);
+ return ret;
+}
+
+void*
+emallocz(uint size) {
+ void *ret = emalloc(size);
+ memset(ret, 0, size);
+ return ret;
+}
+
+void*
+erealloc(void *ptr, uint size) {
+ void *ret = realloc(ptr, size);
+ if(!ret)
+ mfatal("realloc", size);
+ return ret;
+}
+
+char*
+estrdup(const char *str) {
+ void *ret = strdup(str);
+ if(!ret)
+ mfatal("strdup", strlen(str));
+ return ret;
+}
+
+uint
+tokenize(char *res[], uint reslen, char *str, char delim) {
+ char *s;
+ uint i;
+
+ i = 0;
+ s = str;
+ while(i < reslen && *s) {
+ while(*s == delim)
+ *(s++) = '\0';
+ if(*s)
+ res[i++] = s;
+ while(*s && *s != delim)
+ s++;
+ }
+ return i;
+}
+
+uint
+strlcat(char *dst, const char *src, uint size) {
+ const char *s;
+ char *d;
+ int n, len;
+
+ d = dst;
+ s = src;
+ n = size;
+ while(n-- > 0 && *d != '\0')
+ d++;
+ len = n;
+
+ while(*s != '\0' && n-- > 0)
+ *d++ = *s++;
+ while(*s++ != '\0')
+ n--;
+ if(len > 0)
+ *d = '\0';
+ return size - n - 1;
+}
+
diff --git a/libregexp/Makefile b/libregexp/Makefile
new file mode 100644
index 0000000..807bc53
--- /dev/null
+++ b/libregexp/Makefile
@@ -0,0 +1,17 @@
+ROOT= ..
+include ${ROOT}/mk/hdr.mk
+
+VERSION=2.0
+TARG=libregexp9
+
+OBJ=\
+ regcomp\
+ regerror\
+ regexec\
+ regsub\
+ regaux\
+ rregexec\
+ rregsub
+
+include ${ROOT}/mk/lib.mk
+
diff --git a/libregexp/NOTICE b/libregexp/NOTICE
new file mode 100644
index 0000000..02856cf
--- /dev/null
+++ b/libregexp/NOTICE
@@ -0,0 +1,25 @@
+/*
+ * The authors of this software is Rob Pike.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
+ * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+*/
+
+This is a Unix port of the Plan 9 regular expression library.
+
+Please send comments about the packaging
+to Russ Cox <rsc@swtch.com>.
+
+
+----
+
+This software is also made available under the Lucent Public License
+version 1.02; see http://plan9.bell-labs.com/plan9dist/license.html
+
diff --git a/libregexp/README b/libregexp/README
new file mode 100644
index 0000000..602ee26
--- /dev/null
+++ b/libregexp/README
@@ -0,0 +1,5 @@
+This software was packaged for Unix by Russ Cox.
+Please send comments to rsc@swtch.com.
+
+http://swtch.com/plan9port/unix
+
diff --git a/libregexp/regaux.c b/libregexp/regaux.c
new file mode 100644
index 0000000..81b1288
--- /dev/null
+++ b/libregexp/regaux.c
@@ -0,0 +1,112 @@
+#include "plan9.h"
+#include "regexp9.h"
+#include "regcomp.h"
+
+
+/*
+ * save a new match in mp
+ */
+extern void
+_renewmatch(Resub *mp, int ms, Resublist *sp)
+{
+ int i;
+
+ if(mp==0 || ms<=0)
+ return;
+ if(mp[0].s.sp==0 || sp->m[0].s.sp<mp[0].s.sp ||
+ (sp->m[0].s.sp==mp[0].s.sp && sp->m[0].e.ep>mp[0].e.ep)){
+ for(i=0; i<ms && i<NSUBEXP; i++)
+ mp[i] = sp->m[i];
+ for(; i<ms; i++)
+ mp[i].s.sp = mp[i].e.ep = 0;
+ }
+}
+
+/*
+ * Note optimization in _renewthread:
+ * *lp must be pending when _renewthread called; if *l has been looked
+ * at already, the optimization is a bug.
+ */
+extern Relist*
+_renewthread(Relist *lp, /* _relist to add to */
+ Reinst *ip, /* instruction to add */
+ int ms,
+ Resublist *sep) /* pointers to subexpressions */
+{
+ Relist *p;
+
+ for(p=lp; p->inst; p++){
+ if(p->inst == ip){
+ if(sep->m[0].s.sp < p->se.m[0].s.sp){
+ if(ms > 1)
+ p->se = *sep;
+ else
+ p->se.m[0] = sep->m[0];
+ }
+ return 0;
+ }
+ }
+ p->inst = ip;
+ if(ms > 1)
+ p->se = *sep;
+ else
+ p->se.m[0] = sep->m[0];
+ (++p)->inst = 0;
+ return p;
+}
+
+/*
+ * same as renewthread, but called with
+ * initial empty start pointer.
+ */
+extern Relist*
+_renewemptythread(Relist *lp, /* _relist to add to */
+ Reinst *ip, /* instruction to add */
+ int ms,
+ char *sp) /* pointers to subexpressions */
+{
+ Relist *p;
+
+ for(p=lp; p->inst; p++){
+ if(p->inst == ip){
+ if(sp < p->se.m[0].s.sp) {
+ if(ms > 1)
+ memset(&p->se, 0, sizeof(p->se));
+ p->se.m[0].s.sp = sp;
+ }
+ return 0;
+ }
+ }
+ p->inst = ip;
+ if(ms > 1)
+ memset(&p->se, 0, sizeof(p->se));
+ p->se.m[0].s.sp = sp;
+ (++p)->inst = 0;
+ return p;
+}
+
+extern Relist*
+_rrenewemptythread(Relist *lp, /* _relist to add to */
+ Reinst *ip, /* instruction to add */
+ int ms,
+ Rune *rsp) /* pointers to subexpressions */
+{
+ Relist *p;
+
+ for(p=lp; p->inst; p++){
+ if(p->inst == ip){
+ if(rsp < p->se.m[0].s.rsp) {
+ if(ms > 1)
+ memset(&p->se, 0, sizeof(p->se));
+ p->se.m[0].s.rsp = rsp;
+ }
+ return 0;
+ }
+ }
+ p->inst = ip;
+ if(ms > 1)
+ memset(&p->se, 0, sizeof(p->se));
+ p->se.m[0].s.rsp = rsp;
+ (++p)->inst = 0;
+ return p;
+}
diff --git a/libregexp/regcomp.c b/libregexp/regcomp.c
new file mode 100644
index 0000000..9ab3795
--- /dev/null
+++ b/libregexp/regcomp.c
@@ -0,0 +1,559 @@
+#include <setjmp.h>
+#include <stdlib.h>
+#include "plan9.h"
+#include "regexp9.h"
+#include "regcomp.h"
+
+enum {
+ FALSE,
+ TRUE,
+};
+
+/*
+ * Parser Information
+ */
+typedef struct Node Node;
+struct Node
+{
+ Reinst* first;
+ Reinst* last;
+};
+
+#define NSTACK 20
+static Node andstack[NSTACK];
+static Node *andp;
+static int atorstack[NSTACK];
+static int* atorp;
+static int cursubid; /* id of current subexpression */
+static int subidstack[NSTACK]; /* parallel to atorstack */
+static int* subidp;
+static int lastwasand; /* Last token was operand */
+static int nbra;
+static char* exprp; /* pointer to next character in source expression */
+static int lexdone;
+static int nclass;
+static Reclass*classp;
+static Reinst* freep;
+static int errors;
+static Rune yyrune; /* last lex'd rune */
+static Reclass*yyclassp; /* last lex'd class */
+
+/* predeclared crap */
+static void operator(int);
+static void pushand(Reinst*, Reinst*);
+static void pushator(int);
+static void evaluntil(int);
+static int bldcclass(void);
+
+static jmp_buf regkaboom;
+
+static void
+rcerror(char *s)
+{
+ errors++;
+ regerror(s);
+ longjmp(regkaboom, 1);
+}
+
+static Reinst*
+newinst(int t)
+{
+ freep->type = t;
+ freep->u2.left = 0;
+ freep->u1.right = 0;
+ return freep++;
+}
+
+static void
+operand(int t)
+{
+ Reinst *i;
+
+ if(lastwasand)
+ operator(CAT); /* catenate is implicit */
+ i = newinst(t);
+
+ if(t == CCLASS || t == NCCLASS)
+ i->u1.cp = yyclassp;
+ if(t == RUNE)
+ i->u1.r = yyrune;
+
+ pushand(i, i);
+ lastwasand = TRUE;
+}
+
+static void
+operator(int t)
+{
+ if(t==RBRA && --nbra<0)
+ rcerror("unmatched right paren");
+ if(t==LBRA){
+ if(++cursubid >= NSUBEXP)
+ rcerror ("too many subexpressions");
+ nbra++;
+ if(lastwasand)
+ operator(CAT);
+ } else
+ evaluntil(t);
+ if(t != RBRA)
+ pushator(t);
+ lastwasand = FALSE;
+ if(t==STAR || t==QUEST || t==PLUS || t==RBRA)
+ lastwasand = TRUE; /* these look like operands */
+}
+
+static void
+regerr2(char *s, int c)
+{
+ char buf[100];
+ char *cp = buf;
+ while(*s)
+ *cp++ = *s++;
+ *cp++ = c;
+ *cp = '\0';
+ rcerror(buf);
+}
+
+static void
+cant(char *s)
+{
+ char buf[100];
+ strcpy(buf, "can't happen: ");
+ strcat(buf, s);
+ rcerror(buf);
+}
+
+static void
+pushand(Reinst *f, Reinst *l)
+{
+ if(andp >= &andstack[NSTACK])
+ cant("operand stack overflow");
+ andp->first = f;
+ andp->last = l;
+ andp++;
+}
+
+static void
+pushator(int t)
+{
+ if(atorp >= &atorstack[NSTACK])
+ cant("operator stack overflow");
+ *atorp++ = t;
+ *subidp++ = cursubid;
+}
+
+static Node*
+popand(int op)
+{
+ Reinst *inst;
+
+ if(andp <= &andstack[0]){
+ regerr2("missing operand for ", op);
+ inst = newinst(NOP);
+ pushand(inst,inst);
+ }
+ return --andp;
+}
+
+static int
+popator(void)
+{
+ if(atorp <= &atorstack[0])
+ cant("operator stack underflow");
+ --subidp;
+ return *--atorp;
+}
+
+static void
+evaluntil(int pri)
+{
+ Node *op1, *op2;
+ Reinst *inst1, *inst2;
+
+ while(pri==RBRA || atorp[-1]>=pri){
+ switch(popator()){
+ default:
+ rcerror("unknown operator in evaluntil");
+ break;
+ case LBRA: /* must have been RBRA */
+ op1 = popand('(');
+ inst2 = newinst(RBRA);
+ inst2->u1.subid = *subidp;
+ op1->last->u2.next = inst2;
+ inst1 = newinst(LBRA);
+ inst1->u1.subid = *subidp;
+ inst1->u2.next = op1->first;
+ pushand(inst1, inst2);
+ return;
+ case OR:
+ op2 = popand('|');
+ op1 = popand('|');
+ inst2 = newinst(NOP);
+ op2->last->u2.next = inst2;
+ op1->last->u2.next = inst2;
+ inst1 = newinst(OR);
+ inst1->u1.right = op1->first;
+ inst1->u2.left = op2->first;
+ pushand(inst1, inst2);
+ break;
+ case CAT:
+ op2 = popand(0);
+ op1 = popand(0);
+ op1->last->u2.next = op2->first;
+ pushand(op1->first, op2->last);
+ break;
+ case STAR:
+ op2 = popand('*');
+ inst1 = newinst(OR);
+ op2->last->u2.next = inst1;
+ inst1->u1.right = op2->first;
+ pushand(inst1, inst1);
+ break;
+ case PLUS:
+ op2 = popand('+');
+ inst1 = newinst(OR);
+ op2->last->u2.next = inst1;
+ inst1->u1.right = op2->first;
+ pushand(op2->first, inst1);
+ break;
+ case QUEST:
+ op2 = popand('?');
+ inst1 = newinst(OR);
+ inst2 = newinst(NOP);
+ inst1->u2.left = inst2;
+ inst1->u1.right = op2->first;
+ op2->last->u2.next = inst2;
+ pushand(inst1, inst2);
+ break;
+ }
+ }
+}
+
+static Reprog*
+optimize(Reprog *pp)
+{
+ Reinst *inst, *target;
+ int size;
+ Reprog *npp;
+ Reclass *cl;
+ int diff;
+
+ /*
+ * get rid of NOOP chains
+ */
+ for(inst=pp->firstinst; inst->type!=END; inst++){
+ target = inst->u2.next;
+ while(target->type == NOP)
+ target = target->u2.next;
+ inst->u2.next = target;
+ }
+
+ /*
+ * The original allocation is for an area larger than
+ * necessary. Reallocate to the actual space used
+ * and then relocate the code.
+ */
+ size = sizeof(Reprog) + (freep - pp->firstinst)*sizeof(Reinst);
+ npp = realloc(pp, size);
+ if(npp==0 || npp==pp)
+ return pp;
+ diff = (char *)npp - (char *)pp;
+ freep = (Reinst *)((char *)freep + diff);
+ for(inst=npp->firstinst; inst<freep; inst++){
+ switch(inst->type){
+ case OR:
+ case STAR:
+ case PLUS:
+ case QUEST:
+ *(char **)&inst->u1.right += diff;
+ break;
+ case CCLASS:
+ case NCCLASS:
+ *(char **)&inst->u1.right += diff;
+ cl = inst->u1.cp;
+ *(char **)&cl->end += diff;
+ break;
+ }
+ *(char **)&inst->u2.left += diff;
+ }
+ *(char **)&npp->startinst += diff;
+ return npp;
+}
+
+#ifdef DEBUG
+static void
+dumpstack(void){
+ Node *stk;
+ int *ip;
+
+ print("operators\n");
+ for(ip=atorstack; ip<atorp; ip++)
+ print("0%o\n", *ip);
+ print("operands\n");
+ for(stk=andstack; stk<andp; stk++)
+ print("0%o\t0%o\n", stk->first->type, stk->last->type);
+}
+
+static void
+dump(Reprog *pp)
+{
+ Reinst *l;
+ Rune *p;
+
+ l = pp->firstinst;
+ do{
+ print("%d:\t0%o\t%d\t%d", l-pp->firstinst, l->type,
+ l->u2.left-pp->firstinst, l->u1.right-pp->firstinst);
+ if(l->type == RUNE)
+ print("\t%C\n", l->u1.r);
+ else if(l->type == CCLASS || l->type == NCCLASS){
+ print("\t[");
+ if(l->type == NCCLASS)
+ print("^");
+ for(p = l->u1.cp->spans; p < l->u1.cp->end; p += 2)
+ if(p[0] == p[1])
+ print("%C", p[0]);
+ else
+ print("%C-%C", p[0], p[1]);
+ print("]\n");
+ } else
+ print("\n");
+ }while(l++->type);
+}
+#endif
+
+static Reclass*
+newclass(void)
+{
+ if(nclass >= NCLASS)
+ regerr2("too many character classes; limit", NCLASS+'0');
+ return &(classp[nclass++]);
+}
+
+static int
+nextc(Rune *rp)
+{
+ if(lexdone){
+ *rp = 0;
+ return 1;
+ }
+ exprp += chartorune(rp, exprp);
+ if(*rp == '\\'){
+ exprp += chartorune(rp, exprp);
+ return 1;
+ }
+ if(*rp == 0)
+ lexdone = 1;
+ return 0;
+}
+
+static int
+lex(int literal, int dot_type)
+{
+ int quoted;
+
+ quoted = nextc(&yyrune);
+ if(literal || quoted){
+ if(yyrune == 0)
+ return END;
+ return RUNE;
+ }
+
+ switch(yyrune){
+ case 0:
+ return END;
+ case '*':
+ return STAR;
+ case '?':
+ return QUEST;
+ case '+':
+ return PLUS;
+ case '|':
+ return OR;
+ case '.':
+ return dot_type;
+ case '(':
+ return LBRA;
+ case ')':
+ return RBRA;
+ case '^':
+ return BOL;
+ case '$':
+ return EOL;
+ case '[':
+ return bldcclass();
+ }
+ return RUNE;
+}
+
+static int
+bldcclass(void)
+{
+ int type;
+ Rune r[NCCRUNE];
+ Rune *p, *ep, *np;
+ Rune rune;
+ int quoted;
+
+ /* we have already seen the '[' */
+ type = CCLASS;
+ yyclassp = newclass();
+
+ /* look ahead for negation */
+ /* SPECIAL CASE!!! negated classes don't match \n */
+ ep = r;
+ quoted = nextc(&rune);
+ if(!quoted && rune == '^'){
+ type = NCCLASS;
+ quoted = nextc(&rune);
+ *ep++ = '\n';
+ *ep++ = '\n';
+ }
+
+ /* parse class into a set of spans */
+ for(; ep<&r[NCCRUNE];){
+ if(rune == 0){
+ rcerror("malformed '[]'");
+ return 0;
+ }
+ if(!quoted && rune == ']')
+ break;
+ if(!quoted && rune == '-'){
+ if(ep == r){
+ rcerror("malformed '[]'");
+ return 0;
+ }
+ quoted = nextc(&rune);
+ if((!quoted && rune == ']') || rune == 0){
+ rcerror("malformed '[]'");
+ return 0;
+ }
+ *(ep-1) = rune;
+ } else {
+ *ep++ = rune;
+ *ep++ = rune;
+ }
+ quoted = nextc(&rune);
+ }
+
+ /* sort on span start */
+ for(p = r; p < ep; p += 2){
+ for(np = p; np < ep; np += 2)
+ if(*np < *p){
+ rune = np[0];
+ np[0] = p[0];
+ p[0] = rune;
+ rune = np[1];
+ np[1] = p[1];
+ p[1] = rune;
+ }
+ }
+
+ /* merge spans */
+ np = yyclassp->spans;
+ p = r;
+ if(r == ep)
+ yyclassp->end = np;
+ else {
+ np[0] = *p++;
+ np[1] = *p++;
+ for(; p < ep; p += 2)
+ if(p[0] <= np[1]){
+ if(p[1] > np[1])
+ np[1] = p[1];
+ } else {
+ np += 2;
+ np[0] = p[0];
+ np[1] = p[1];
+ }
+ yyclassp->end = np+2;
+ }
+
+ return type;
+}
+
+static Reprog*
+regcomp1(char *s, int literal, int dot_type)
+{
+ int token;
+ Reprog *volatile pp;
+
+ /* get memory for the program */
+ pp = malloc(sizeof(Reprog) + 6*sizeof(Reinst)*strlen(s));
+ if(pp == 0){
+ regerror("out of memory");
+ return 0;
+ }
+ freep = pp->firstinst;
+ classp = pp->class;
+ errors = 0;
+
+ if(setjmp(regkaboom))
+ goto out;
+
+ /* go compile the sucker */
+ lexdone = 0;
+ exprp = s;
+ nclass = 0;
+ nbra = 0;
+ atorp = atorstack;
+ andp = andstack;
+ subidp = subidstack;
+ lastwasand = FALSE;
+ cursubid = 0;
+
+ /* Start with a low priority operator to prime parser */
+ pushator(START-1);
+ while((token = lex(literal, dot_type)) != END){
+ if((token&0300) == OPERATOR)
+ operator(token);
+ else
+ operand(token);
+ }
+
+ /* Close with a low priority operator */
+ evaluntil(START);
+
+ /* Force END */
+ operand(END);
+ evaluntil(START);
+#ifdef DEBUG
+ dumpstack();
+#endif
+ if(nbra)
+ rcerror("unmatched left paren");
+ --andp; /* points to first and only operand */
+ pp->startinst = andp->first;
+#ifdef DEBUG
+ dump(pp);
+#endif
+ pp = optimize(pp);
+#ifdef DEBUG
+ print("start: %d\n", andp->first-pp->firstinst);
+ dump(pp);
+#endif
+out:
+ if(errors){
+ free(pp);
+ pp = 0;
+ }
+ return pp;
+}
+
+extern Reprog*
+regcomp(char *s)
+{
+ return regcomp1(s, 0, ANY);
+}
+
+extern Reprog*
+regcomplit(char *s)
+{
+ return regcomp1(s, 1, ANY);
+}
+
+extern Reprog*
+regcompnl(char *s)
+{
+ return regcomp1(s, 0, ANYNL);
+}
diff --git a/libregexp/regerror.c b/libregexp/regerror.c
new file mode 100644
index 0000000..f379994
--- /dev/null
+++ b/libregexp/regerror.c
@@ -0,0 +1,15 @@
+#include <stdlib.h>
+#include <plan9.h>
+#include <regexp9.h>
+
+void
+regerror(char *s)
+{
+ char buf[132];
+
+ strcpy(buf, "regerror: ");
+ strcat(buf, s);
+ strcat(buf, "\n");
+ write(2, buf, strlen(buf));
+ exits("regerr");
+}
diff --git a/libregexp/regexec.c b/libregexp/regexec.c
new file mode 100644
index 0000000..b793df6
--- /dev/null
+++ b/libregexp/regexec.c
@@ -0,0 +1,232 @@
+#include <stdlib.h>
+#include "plan9.h"
+#include "regexp9.h"
+#include "regcomp.h"
+
+
+/*
+ * return 0 if no match
+ * >0 if a match
+ * <0 if we ran out of _relist space
+ */
+static int
+regexec1(Reprog *progp, /* program to run */
+ char *bol, /* string to run machine on */
+ Resub *mp, /* subexpression elements */
+ int ms, /* number of elements at mp */
+ Reljunk *j
+)
+{
+ int flag=0;
+ Reinst *inst;
+ Relist *tlp;
+ char *s;
+ int i, checkstart;
+ Rune r, *rp, *ep;
+ int n;
+ Relist* tl; /* This list, next list */
+ Relist* nl;
+ Relist* tle; /* ends of this and next list */
+ Relist* nle;
+ int match;
+ char *p;
+
+ match = 0;
+ checkstart = j->starttype;
+ if(mp)
+ for(i=0; i<ms; i++) {
+ mp[i].s.sp = 0;
+ mp[i].e.ep = 0;
+ }
+ j->relist[0][0].inst = 0;
+ j->relist[1][0].inst = 0;
+
+ /* Execute machine once for each character, including terminal NUL */
+ s = j->starts;
+ do{
+ /* fast check for first char */
+ if(checkstart) {
+ switch(j->starttype) {
+ case RUNE:
+ p = utfrune(s, j->startchar);
+ if(p == 0 || s == j->eol)
+ return match;
+ s = p;
+ break;
+ case BOL:
+ if(s == bol)
+ break;
+ p = utfrune(s, '\n');
+ if(p == 0 || s == j->eol)
+ return match;
+ s = p;
+ break;
+ }
+ }
+ r = *(uchar*)s;
+ if(r < Runeself)
+ n = 1;
+ else
+ n = chartorune(&r, s);
+
+ /* switch run lists */
+ tl = j->relist[flag];
+ tle = j->reliste[flag];
+ nl = j->relist[flag^=1];
+ nle = j->reliste[flag];
+ nl->inst = 0;
+
+ /* Add first instruction to current list */
+ if(match == 0)
+ _renewemptythread(tl, progp->startinst, ms, s);
+
+ /* Execute machine until current list is empty */
+ for(tlp=tl; tlp->inst; tlp++){ /* assignment = */
+ for(inst = tlp->inst; ; inst = inst->u2.next){
+ switch(inst->type){
+ case RUNE: /* regular character */
+ if(inst->u1.r == r){
+ if(_renewthread(nl, inst->u2.next, ms, &tlp->se)==nle)
+ return -1;
+ }
+ break;
+ case LBRA:
+ tlp->se.m[inst->u1.subid].s.sp = s;
+ continue;
+ case RBRA:
+ tlp->se.m[inst->u1.subid].e.ep = s;
+ continue;
+ case ANY:
+ if(r != '\n')
+ if(_renewthread(nl, inst->u2.next, ms, &tlp->se)==nle)
+ return -1;
+ break;
+ case ANYNL:
+ if(_renewthread(nl, inst->u2.next, ms, &tlp->se)==nle)
+ return -1;
+ break;
+ case BOL:
+ if(s == bol || *(s-1) == '\n')
+ continue;
+ break;
+ case EOL:
+ if(s == j->eol || r == 0 || r == '\n')
+ continue;
+ break;
+ case CCLASS:
+ ep = inst->u1.cp->end;
+ for(rp = inst->u1.cp->spans; rp < ep; rp += 2)
+ if(r >= rp[0] && r <= rp[1]){
+ if(_renewthread(nl, inst->u2.next, ms, &tlp->se)==nle)
+ return -1;
+ break;
+ }
+ break;
+ case NCCLASS:
+ ep = inst->u1.cp->end;
+ for(rp = inst->u1.cp->spans; rp < ep; rp += 2)
+ if(r >= rp[0] && r <= rp[1])
+ break;
+ if(rp == ep)
+ if(_renewthread(nl, inst->u2.next, ms, &tlp->se)==nle)
+ return -1;
+ break;
+ case OR:
+ /* evaluate right choice later */
+ if(_renewthread(tlp, inst->u1.right, ms, &tlp->se) == tle)
+ return -1;
+ /* efficiency: advance and re-evaluate */
+ continue;
+ case END: /* Match! */
+ match = 1;
+ tlp->se.m[0].e.ep = s;
+ if(mp != 0)
+ _renewmatch(mp, ms, &tlp->se);
+ break;
+ }
+ break;
+ }
+ }
+ if(s == j->eol)
+ break;
+ checkstart = j->starttype && nl->inst==0;
+ s += n;
+ }while(r);
+ return match;
+}
+
+static int
+regexec2(Reprog *progp, /* program to run */
+ char *bol, /* string to run machine on */
+ Resub *mp, /* subexpression elements */
+ int ms, /* number of elements at mp */
+ Reljunk *j
+)
+{
+ int rv;
+ Relist *relist0, *relist1;
+
+ /* mark space */
+ relist0 = malloc(BIGLISTSIZE*sizeof(Relist));
+ if(relist0 == nil)
+ return -1;
+ relist1 = malloc(BIGLISTSIZE*sizeof(Relist));
+ if(relist1 == nil){
+ free(relist1);
+ return -1;
+ }
+ j->relist[0] = relist0;
+ j->relist[1] = relist1;
+ j->reliste[0] = relist0 + BIGLISTSIZE - 2;
+ j->reliste[1] = relist1 + BIGLISTSIZE - 2;
+
+ rv = regexec1(progp, bol, mp, ms, j);
+ free(relist0);
+ free(relist1);
+ return rv;
+}
+
+extern int
+regexec(Reprog *progp, /* program to run */
+ char *bol, /* string to run machine on */
+ Resub *mp, /* subexpression elements */
+ int ms) /* number of elements at mp */
+{
+ Reljunk j;
+ Relist relist0[LISTSIZE], relist1[LISTSIZE];
+ int rv;
+
+ /*
+ * use user-specified starting/ending location if specified
+ */
+ j.starts = bol;
+ j.eol = 0;
+ if(mp && ms>0){
+ if(mp->s.sp)
+ j.starts = mp->s.sp;
+ if(mp->e.ep)
+ j.eol = mp->e.ep;
+ }
+ j.starttype = 0;
+ j.startchar = 0;
+ if(progp->startinst->type == RUNE && progp->startinst->u1.r < Runeself) {
+ j.starttype = RUNE;
+ j.startchar = progp->startinst->u1.r;
+ }
+ if(progp->startinst->type == BOL)
+ j.starttype = BOL;
+
+ /* mark space */
+ j.relist[0] = relist0;
+ j.relist[1] = relist1;
+ j.reliste[0] = relist0 + nelem(relist0) - 2;
+ j.reliste[1] = relist1 + nelem(relist1) - 2;
+
+ rv = regexec1(progp, bol, mp, ms, &j);
+ if(rv >= 0)
+ return rv;
+ rv = regexec2(progp, bol, mp, ms, &j);
+ if(rv >= 0)
+ return rv;
+ return -1;
+}
diff --git a/libregexp/regexp9.3 b/libregexp/regexp9.3
new file mode 100644
index 0000000..d4faf56
--- /dev/null
+++ b/libregexp/regexp9.3
@@ -0,0 +1,220 @@
+.deEX
+.ift .ft5
+.nf
+..
+.deEE
+.ft1
+.fi
+..
+.TH REGEXP9 3
+.SH NAME
+regcomp, regcomplit, regcompnl, regexec, regsub, rregexec, rregsub, regerror \- regular expression
+.SH SYNOPSIS
+.B #include <utf.h>
+.br
+.B #include <fmt.h>
+.br
+.B #include <regexp9.h>
+.PP
+.ta \w'\fLRegprog 'u
+.B
+Reprog *regcomp(char *exp)
+.PP
+.B
+Reprog *regcomplit(char *exp)
+.PP
+.B
+Reprog *regcompnl(char *exp)
+.PP
+.nf
+.B
+int regexec(Reprog *prog, char *string, Resub *match, int msize)
+.PP
+.nf
+.B
+void regsub(char *source, char *dest, int dlen, Resub *match, int msize)
+.PP
+.nf
+.B
+int rregexec(Reprog *prog, Rune *string, Resub *match, int msize)
+.PP
+.nf
+.B
+void rregsub(Rune *source, Rune *dest, int dlen, Resub *match, int msize)
+.PP
+.B
+void regerror(char *msg)
+.SH DESCRIPTION
+.I Regcomp
+compiles a
+regular expression and returns
+a pointer to the generated description.
+The space is allocated by
+.IR malloc (3)
+and may be released by
+.IR free .
+Regular expressions are exactly as in
+.IR regexp9 (7).
+.PP
+.I Regcomplit
+is like
+.I regcomp
+except that all characters are treated literally.
+.I Regcompnl
+is like
+.I regcomp
+except that the
+.B .
+metacharacter matches all characters, including newlines.
+.PP
+.I Regexec
+matches a null-terminated
+.I string
+against the compiled regular expression in
+.IR prog .
+If it matches,
+.I regexec
+returns
+.B 1
+and fills in the array
+.I match
+with character pointers to the substrings of
+.I string
+that correspond to the
+parenthesized subexpressions of
+.IR exp :
+.BI match[ i ].sp
+points to the beginning and
+.BI match[ i ].ep
+points just beyond
+the end of the
+.IR i th
+substring.
+(Subexpression
+.I i
+begins at the
+.IR i th
+left parenthesis, counting from 1.)
+Pointers in
+.B match[0]
+pick out the substring that corresponds to
+the whole regular expression.
+Unused elements of
+.I match
+are filled with zeros.
+Matches involving
+.LR * ,
+.LR + ,
+and
+.L ?
+are extended as far as possible.
+The number of array elements in
+.I match
+is given by
+.IR msize .
+The structure of elements of
+.I match
+is:
+.IP
+.EX
+typedef struct {
+ union {
+ char *sp;
+ Rune *rsp;
+ };
+ union {
+ char *ep;
+ Rune *rep;
+ };
+} Resub;
+.EE
+.LP
+If
+.B match[0].sp
+is nonzero on entry,
+.I regexec
+starts matching at that point within
+.IR string .
+If
+.B match[0].ep
+is nonzero on entry,
+the last character matched is the one
+preceding that point.
+.PP
+.I Regsub
+places in
+.I dest
+a substitution instance of
+.I source
+in the context of the last
+.I regexec
+performed using
+.IR match .
+Each instance of
+.BI \e n\f1,
+where
+.I n
+is a digit, is replaced by the
+string delimited by
+.BI match[ n ].sp
+and
+.BI match[ n ].ep\f1.
+Each instance of
+.L &
+is replaced by the string delimited by
+.B match[0].sp
+and
+.BR match[0].ep .
+The substitution will always be null terminated and
+trimmed to fit into dlen bytes.
+.PP
+.IR Regerror ,
+called whenever an error is detected in
+.IR regcomp ,
+writes the string
+.I msg
+on the standard error file and exits.
+.I Regerror
+can be replaced to perform
+special error processing.
+If the user supplied
+.I regerror
+returns rather than exits,
+.I regcomp
+will return 0.
+.PP
+.I Rregexec
+and
+.I rregsub
+are variants of
+.I regexec
+and
+.I regsub
+that use strings of
+.B Runes
+instead of strings of
+.BR chars .
+With these routines, the
+.I rsp
+and
+.I rep
+fields of the
+.I match
+array elements should be used.
+.SH SOURCE
+.B http://swtch.com/plan9port/unix
+.SH "SEE ALSO"
+.IR grep (1)
+.SH DIAGNOSTICS
+.I Regcomp
+returns
+.B 0
+for an illegal expression
+or other failure.
+.I Regexec
+returns 0
+if
+.I string
+is not matched.
+.SH BUGS
+There is no way to specify or match a NUL character; NULs terminate patterns and strings.
diff --git a/libregexp/regexp9.7 b/libregexp/regexp9.7
new file mode 100644
index 0000000..c356faf
--- /dev/null
+++ b/libregexp/regexp9.7
@@ -0,0 +1,141 @@
+.deEX
+.ift .ft5
+.nf
+..
+.deEE
+.ft1
+.fi
+..
+.TH REGEXP9 7
+.SH NAME
+regexp \- Plan 9 regular expression notation
+.SH DESCRIPTION
+This manual page describes the regular expression
+syntax used by the Plan 9 regular expression library
+.IR regexp9 (3).
+It is the form used by
+.IR egrep (1)
+before
+.I egrep
+got complicated.
+.PP
+A
+.I "regular expression"
+specifies
+a set of strings of characters.
+A member of this set of strings is said to be
+.I matched
+by the regular expression. In many applications
+a delimiter character, commonly
+.LR / ,
+bounds a regular expression.
+In the following specification for regular expressions
+the word `character' means any character (rune) but newline.
+.PP
+The syntax for a regular expression
+.B e0
+is
+.IP
+.EX
+e3: literal | charclass | '.' | '^' | '$' | '(' e0 ')'
+
+e2: e3
+ | e2 REP
+
+REP: '*' | '+' | '?'
+
+e1: e2
+ | e1 e2
+
+e0: e1
+ | e0 '|' e1
+.EE
+.PP
+A
+.B literal
+is any non-metacharacter, or a metacharacter
+(one of
+.BR .*+?[]()|\e^$ ),
+or the delimiter
+preceded by
+.LR \e .
+.PP
+A
+.B charclass
+is a nonempty string
+.I s
+bracketed
+.BI [ \|s\| ]
+(or
+.BI [^ s\| ]\fR);
+it matches any character in (or not in)
+.IR s .
+A negated character class never
+matches newline.
+A substring
+.IB a - b\f1,
+with
+.I a
+and
+.I b
+in ascending
+order, stands for the inclusive
+range of
+characters between
+.I a
+and
+.IR b .
+In
+.IR s ,
+the metacharacters
+.LR - ,
+.LR ] ,
+an initial
+.LR ^ ,
+and the regular expression delimiter
+must be preceded by a
+.LR \e ;
+other metacharacters
+have no special meaning and
+may appear unescaped.
+.PP
+A
+.L .
+matches any character.
+.PP
+A
+.L ^
+matches the beginning of a line;
+.L $
+matches the end of the line.
+.PP
+The
+.B REP
+operators match zero or more
+.RB ( * ),
+one or more
+.RB ( + ),
+zero or one
+.RB ( ? ),
+instances respectively of the preceding regular expression
+.BR e2 .
+.PP
+A concatenated regular expression,
+.BR "e1\|e2" ,
+matches a match to
+.B e1
+followed by a match to
+.BR e2 .
+.PP
+An alternative regular expression,
+.BR "e0\||\|e1" ,
+matches either a match to
+.B e0
+or a match to
+.BR e1 .
+.PP
+A match to any part of a regular expression
+extends as far as possible without preventing
+a match to the remainder of the regular expression.
+.SH "SEE ALSO
+.IR regexp9 (3)
diff --git a/libregexp/regsub.c b/libregexp/regsub.c
new file mode 100644
index 0000000..bd6d1c1
--- /dev/null
+++ b/libregexp/regsub.c
@@ -0,0 +1,63 @@
+#include "plan9.h"
+#include "regexp9.h"
+
+/* substitute into one string using the matches from the last regexec() */
+extern void
+regsub(char *sp, /* source string */
+ char *dp, /* destination string */
+ int dlen,
+ Resub *mp, /* subexpression elements */
+ int ms) /* number of elements pointed to by mp */
+{
+ char *ssp, *ep;
+ int i;
+
+ ep = dp+dlen-1;
+ while(*sp != '\0'){
+ if(*sp == '\\'){
+ switch(*++sp){
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ i = *sp-'0';
+ if(mp[i].s.sp != 0 && mp!=0 && ms>i)
+ for(ssp = mp[i].s.sp;
+ ssp < mp[i].e.ep;
+ ssp++)
+ if(dp < ep)
+ *dp++ = *ssp;
+ break;
+ case '\\':
+ if(dp < ep)
+ *dp++ = '\\';
+ break;
+ case '\0':
+ sp--;
+ break;
+ default:
+ if(dp < ep)
+ *dp++ = *sp;
+ break;
+ }
+ }else if(*sp == '&'){
+ if(mp[0].s.sp != 0 && mp!=0 && ms>0)
+ if(mp[0].s.sp != 0)
+ for(ssp = mp[0].s.sp;
+ ssp < mp[0].e.ep; ssp++)
+ if(dp < ep)
+ *dp++ = *ssp;
+ }else{
+ if(dp < ep)
+ *dp++ = *sp;
+ }
+ sp++;
+ }
+ *dp = '\0';
+}
diff --git a/libregexp/rregexec.c b/libregexp/rregexec.c
new file mode 100644
index 0000000..6071e7d
--- /dev/null
+++ b/libregexp/rregexec.c
@@ -0,0 +1,213 @@
+#include "plan9.h"
+#include "regexp9.h"
+#include "regcomp.h"
+
+/*
+ * return 0 if no match
+ * >0 if a match
+ * <0 if we ran out of _relist space
+ */
+static int
+rregexec1(Reprog *progp, /* program to run */
+ Rune *bol, /* string to run machine on */
+ Resub *mp, /* subexpression elements */
+ int ms, /* number of elements at mp */
+ Reljunk *j)
+{
+ int flag=0;
+ Reinst *inst;
+ Relist *tlp;
+ Rune *s;
+ int i, checkstart;
+ Rune r, *rp, *ep;
+ Relist* tl; /* This list, next list */
+ Relist* nl;
+ Relist* tle; /* ends of this and next list */
+ Relist* nle;
+ int match;
+
+ match = 0;
+ checkstart = j->startchar;
+ if(mp)
+ for(i=0; i<ms; i++) {
+ mp[i].s.rsp = 0;
+ mp[i].e.rep = 0;
+ }
+ j->relist[0][0].inst = 0;
+ j->relist[1][0].inst = 0;
+
+ /* Execute machine once for each character, including terminal NUL */
+ s = j->rstarts;
+ do{
+
+ /* fast check for first char */
+ if(checkstart) {
+ switch(j->starttype) {
+ case RUNE:
+ while(*s != j->startchar) {
+ if(*s == 0 || s == j->reol)
+ return match;
+ s++;
+ }
+ break;
+ case BOL:
+ if(s == bol)
+ break;
+ while(*s != '\n') {
+ if(*s == 0 || s == j->reol)
+ return match;
+ s++;
+ }
+ break;
+ }
+ }
+
+ r = *s;
+
+ /* switch run lists */
+ tl = j->relist[flag];
+ tle = j->reliste[flag];
+ nl = j->relist[flag^=1];
+ nle = j->reliste[flag];
+ nl->inst = 0;
+
+ /* Add first instruction to current list */
+ _rrenewemptythread(tl, progp->startinst, ms, s);
+
+ /* Execute machine until current list is empty */
+ for(tlp=tl; tlp->inst; tlp++){
+ for(inst=tlp->inst; ; inst = inst->u2.next){
+ switch(inst->type){
+ case RUNE: /* regular character */
+ if(inst->u1.r == r)
+ if(_renewthread(nl, inst->u2.next, ms, &tlp->se)==nle)
+ return -1;
+ break;
+ case LBRA:
+ tlp->se.m[inst->u1.subid].s.rsp = s;
+ continue;
+ case RBRA:
+ tlp->se.m[inst->u1.subid].e.rep = s;
+ continue;
+ case ANY:
+ if(r != '\n')
+ if(_renewthread(nl, inst->u2.next, ms, &tlp->se)==nle)
+ return -1;
+ break;
+ case ANYNL:
+ if(_renewthread(nl, inst->u2.next, ms, &tlp->se)==nle)
+ return -1;
+ break;
+ case BOL:
+ if(s == bol || *(s-1) == '\n')
+ continue;
+ break;
+ case EOL:
+ if(s == j->reol || r == 0 || r == '\n')
+ continue;
+ break;
+ case CCLASS:
+ ep = inst->u1.cp->end;
+ for(rp = inst->u1.cp->spans; rp < ep; rp += 2)
+ if(r >= rp[0] && r <= rp[1]){
+ if(_renewthread(nl, inst->u2.next, ms, &tlp->se)==nle)
+ return -1;
+ break;
+ }
+ break;
+ case NCCLASS:
+ ep = inst->u1.cp->end;
+ for(rp = inst->u1.cp->spans; rp < ep; rp += 2)
+ if(r >= rp[0] && r <= rp[1])
+ break;
+ if(rp == ep)
+ if(_renewthread(nl, inst->u2.next, ms, &tlp->se)==nle)
+ return -1;
+ break;
+ case OR:
+ /* evaluate right choice later */
+ if(_renewthread(tlp, inst->u1.right, ms, &tlp->se) == tle)
+ return -1;
+ /* efficiency: advance and re-evaluate */
+ continue;
+ case END: /* Match! */
+ match = 1;
+ tlp->se.m[0].e.rep = s;
+ if(mp != 0)
+ _renewmatch(mp, ms, &tlp->se);
+ break;
+ }
+ break;
+ }
+ }
+ if(s == j->reol)
+ break;
+ checkstart = j->startchar && nl->inst==0;
+ s++;
+ }while(r);
+ return match;
+}
+
+static int
+rregexec2(Reprog *progp, /* program to run */
+ Rune *bol, /* string to run machine on */
+ Resub *mp, /* subexpression elements */
+ int ms, /* number of elements at mp */
+ Reljunk *j
+)
+{
+ Relist relist0[5*LISTSIZE], relist1[5*LISTSIZE];
+
+ /* mark space */
+ j->relist[0] = relist0;
+ j->relist[1] = relist1;
+ j->reliste[0] = relist0 + nelem(relist0) - 2;
+ j->reliste[1] = relist1 + nelem(relist1) - 2;
+
+ return rregexec1(progp, bol, mp, ms, j);
+}
+
+extern int
+rregexec(Reprog *progp, /* program to run */
+ Rune *bol, /* string to run machine on */
+ Resub *mp, /* subexpression elements */
+ int ms) /* number of elements at mp */
+{
+ Reljunk j;
+ Relist relist0[LISTSIZE], relist1[LISTSIZE];
+ int rv;
+
+ /*
+ * use user-specified starting/ending location if specified
+ */
+ j.rstarts = bol;
+ j.reol = 0;
+ if(mp && ms>0){
+ if(mp->s.sp)
+ j.rstarts = mp->s.rsp;
+ if(mp->e.ep)
+ j.reol = mp->e.rep;
+ }
+ j.starttype = 0;
+ j.startchar = 0;
+ if(progp->startinst->type == RUNE && progp->startinst->u1.r < Runeself) {
+ j.starttype = RUNE;
+ j.startchar = progp->startinst->u1.r;
+ }
+ if(progp->startinst->type == BOL)
+ j.starttype = BOL;
+
+ /* mark space */
+ j.relist[0] = relist0;
+ j.relist[1] = relist1;
+ j.reliste[0] = relist0 + nelem(relist0) - 2;
+ j.reliste[1] = relist1 + nelem(relist1) - 2;
+
+ rv = rregexec1(progp, bol, mp, ms, &j);
+ if(rv >= 0)
+ return rv;
+ rv = rregexec2(progp, bol, mp, ms, &j);
+ if(rv >= 0)
+ return rv;
+ return -1;
+}
diff --git a/libregexp/rregsub.c b/libregexp/rregsub.c
new file mode 100644
index 0000000..6e53d9b
--- /dev/null
+++ b/libregexp/rregsub.c
@@ -0,0 +1,63 @@
+#include "plan9.h"
+#include "regexp9.h"
+
+/* substitute into one string using the matches from the last regexec() */
+extern void
+rregsub(Rune *sp, /* source string */
+ Rune *dp, /* destination string */
+ int dlen,
+ Resub *mp, /* subexpression elements */
+ int ms) /* number of elements pointed to by mp */
+{
+ Rune *ssp, *ep;
+ int i;
+
+ ep = dp+(dlen/sizeof(Rune))-1;
+ while(*sp != '\0'){
+ if(*sp == '\\'){
+ switch(*++sp){
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ i = *sp-'0';
+ if(mp[i].s.rsp != 0 && mp!=0 && ms>i)
+ for(ssp = mp[i].s.rsp;
+ ssp < mp[i].e.rep;
+ ssp++)
+ if(dp < ep)
+ *dp++ = *ssp;
+ break;
+ case '\\':
+ if(dp < ep)
+ *dp++ = '\\';
+ break;
+ case '\0':
+ sp--;
+ break;
+ default:
+ if(dp < ep)
+ *dp++ = *sp;
+ break;
+ }
+ }else if(*sp == '&'){
+ if(mp[0].s.rsp != 0 && mp!=0 && ms>0)
+ if(mp[0].s.rsp != 0)
+ for(ssp = mp[0].s.rsp;
+ ssp < mp[0].e.rep; ssp++)
+ if(dp < ep)
+ *dp++ = *ssp;
+ }else{
+ if(dp < ep)
+ *dp++ = *sp;
+ }
+ sp++;
+ }
+ *dp = '\0';
+}
diff --git a/libregexp/test.c b/libregexp/test.c
new file mode 100644
index 0000000..2f9470a
--- /dev/null
+++ b/libregexp/test.c
@@ -0,0 +1,46 @@
+#include "plan9.h"
+#include <regexp9.h>
+
+struct x
+{
+ char *re;
+ char *s;
+ Reprog *p;
+};
+
+struct x t[] = {
+ { "^[^!@]+$", "/bin/upas/aliasmail '&'", 0 },
+ { "^local!(.*)$", "/mail/box/\\1/mbox", 0 },
+ { "^plan9!(.*)$", "\\1", 0 },
+ { "^helix!(.*)$", "\\1", 0 },
+ { "^([^!]+)@([^!@]+)$", "\\2!\\1", 0 },
+ { "^(uk\\.[^!]*)(!.*)$", "/bin/upas/uk2uk '\\1' '\\2'", 0 },
+ { "^[^!]*\\.[^!]*!.*$", "inet!&", 0 },
+ { "^\xE2\x98\xBA$", "smiley", 0 },
+ { "^(coma|research|pipe|pyxis|inet|hunny|gauss)!(.*)$", "/mail/lib/qmail '\\s' 'net!\\1' '\\2'", 0 },
+ { "^.*$", "/mail/lib/qmail '\\s' 'net!research' '&'", 0 },
+ { 0, 0, 0 },
+};
+
+main(int ac, char **av)
+{
+ Resub rs[10];
+ char dst[128];
+ int n;
+ struct x *tp;
+
+ for(tp = t; tp->re; tp++)
+ tp->p = regcomp(tp->re);
+
+
+ for(tp = t; tp->re; tp++){
+ print("%s VIA %s", av[1], tp->re);
+ memset(rs, 0, sizeof rs);
+ if(regexec(tp->p, av[1], rs, 10)){
+ regsub(tp->s, dst, sizeof dst, rs, 10);
+ print(" sub %s -> %s", tp->s, dst);
+ }
+ print("\n");
+ }
+ exit(0);
+}
diff --git a/libregexp/test2.c b/libregexp/test2.c
new file mode 100644
index 0000000..a7396fc
--- /dev/null
+++ b/libregexp/test2.c
@@ -0,0 +1,20 @@
+#include "plan9.h"
+#include <regexp9.h>
+
+
+main(int ac, char **av)
+{
+ Resub rs[10];
+ Reprog *p;
+ char *s;
+ int i;
+
+ p = regcomp("[^a-z]");
+ s = "\n";
+ if(regexec(p, s, rs, 10))
+ print("%s %lux %lux %lux\n", s, s, rs[0].sp, rs[0].ep);
+ s = "0";
+ if(regexec(p, s, rs, 10))
+ print("%s %lux %lux %lux\n", s, s, rs[0].sp, rs[0].ep);
+ exit(0);
+}
diff --git a/libutf/Makefile b/libutf/Makefile
new file mode 100644
index 0000000..0fdd571
--- /dev/null
+++ b/libutf/Makefile
@@ -0,0 +1,30 @@
+ROOT= ..
+include ${ROOT}/mk/hdr.mk
+
+VERSION=2.0
+TARG=libutf
+
+OBJ=\
+ rune\
+ runestrcat\
+ runestrchr\
+ runestrcmp\
+ runestrcpy\
+ runestrdup\
+ runestrlen\
+ runestrecpy\
+ runestrncat\
+ runestrncmp\
+ runestrncpy\
+ runestrrchr\
+ runestrstr\
+ runetype\
+ utfecpy\
+ utflen\
+ utfnlen\
+ utfrrune\
+ utfrune\
+ utfutf
+
+include ${ROOT}/mk/lib.mk
+
diff --git a/libutf/NOTICE b/libutf/NOTICE
new file mode 100644
index 0000000..43f24ce
--- /dev/null
+++ b/libutf/NOTICE
@@ -0,0 +1,25 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE ANY
+ * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+*/
+
+This is a Unix port of the Plan 9 formatted I/O package.
+
+Please send comments about the packaging
+to Russ Cox <rsc@swtch.com>.
+
+
+----
+
+This software is also made available under the Lucent Public License
+version 1.02; see http://plan9.bell-labs.com/plan9dist/license.html
+
diff --git a/libutf/README b/libutf/README
new file mode 100644
index 0000000..602ee26
--- /dev/null
+++ b/libutf/README
@@ -0,0 +1,5 @@
+This software was packaged for Unix by Russ Cox.
+Please send comments to rsc@swtch.com.
+
+http://swtch.com/plan9port/unix
+
diff --git a/libutf/isalpharune.3 b/libutf/isalpharune.3
new file mode 100644
index 0000000..8d487ce
--- /dev/null
+++ b/libutf/isalpharune.3
@@ -0,0 +1,57 @@
+.deEX
+.ift .ft5
+.nf
+..
+.deEE
+.ft1
+.fi
+..
+.TH ISALPHARUNE 3
+.SH NAME
+isalpharune, islowerrune, isspacerune, istitlerune, isupperrune, tolowerrune, totitlerune, toupperrune \- Unicode character classes and cases
+.SH SYNOPSIS
+.B #include <utf.h>
+.PP
+.B
+int isalpharune(Rune c)
+.PP
+.B
+int islowerrune(Rune c)
+.PP
+.B
+int isspacerune(Rune c)
+.PP
+.B
+int istitlerune(Rune c)
+.PP
+.B
+int isupperrune(Rune c)
+.PP
+.B
+Rune tolowerrune(Rune c)
+.PP
+.B
+Rune totitlerune(Rune c)
+.PP
+.B
+Rune toupperrune(Rune c)
+.SH DESCRIPTION
+These routines examine and operate on Unicode characters,
+in particular a subset of their properties as defined in the Unicode standard.
+Unicode defines some characters as alphabetic and specifies three cases:
+upper, lower, and title.
+Analogously to
+.IR isalpha (3)
+for
+.SM ASCII\c
+,
+these routines
+test types and modify cases for Unicode characters.
+The names are self-explanatory.
+.PP
+The case-conversion routines return the character unchanged if it has no case.
+.SH SOURCE
+.B http://swtch.com/plan9port/unix
+.SH "SEE ALSO
+.IR isalpha (3) ,
+.IR "The Unicode Standard" .
diff --git a/libutf/libutf.a b/libutf/libutf.a
new file mode 100644
index 0000000..ff08935
--- /dev/null
+++ b/libutf/libutf.a
Binary files differ
diff --git a/libutf/rune.3 b/libutf/rune.3
new file mode 100644
index 0000000..0e38eb3
--- /dev/null
+++ b/libutf/rune.3
@@ -0,0 +1,194 @@
+.deEX
+.ift .ft5
+.nf
+..
+.deEE
+.ft1
+.fi
+..
+.TH RUNE 3
+.SH NAME
+runetochar, chartorune, runelen, runenlen, fullrune, utfecpy, utflen, utfnlen, utfrune, utfrrune, utfutf \- rune/UTF conversion
+.SH SYNOPSIS
+.ta \w'\fLchar*xx'u
+.B #include <utf.h>
+.PP
+.B
+int runetochar(char *s, Rune *r)
+.PP
+.B
+int chartorune(Rune *r, char *s)
+.PP
+.B
+int runelen(long r)
+.PP
+.B
+int runenlen(Rune *r, int n)
+.PP
+.B
+int fullrune(char *s, int n)
+.PP
+.B
+char* utfecpy(char *s1, char *es1, char *s2)
+.PP
+.B
+int utflen(char *s)
+.PP
+.B
+int utfnlen(char *s, long n)
+.PP
+.B
+char* utfrune(char *s, long c)
+.PP
+.B
+char* utfrrune(char *s, long c)
+.PP
+.B
+char* utfutf(char *s1, char *s2)
+.SH DESCRIPTION
+These routines convert to and from a
+.SM UTF
+byte stream and runes.
+.PP
+.I Runetochar
+copies one rune at
+.I r
+to at most
+.B UTFmax
+bytes starting at
+.I s
+and returns the number of bytes copied.
+.BR UTFmax ,
+defined as
+.B 3
+in
+.BR <libc.h> ,
+is the maximum number of bytes required to represent a rune.
+.PP
+.I Chartorune
+copies at most
+.B UTFmax
+bytes starting at
+.I s
+to one rune at
+.I r
+and returns the number of bytes copied.
+If the input is not exactly in
+.SM UTF
+format,
+.I chartorune
+will convert to 0x80 and return 1.
+.PP
+.I Runelen
+returns the number of bytes
+required to convert
+.I r
+into
+.SM UTF.
+.PP
+.I Runenlen
+returns the number of bytes
+required to convert the
+.I n
+runes pointed to by
+.I r
+into
+.SM UTF.
+.PP
+.I Fullrune
+returns 1 if the string
+.I s
+of length
+.I n
+is long enough to be decoded by
+.I chartorune
+and 0 otherwise.
+This does not guarantee that the string
+contains a legal
+.SM UTF
+encoding.
+This routine is used by programs that
+obtain input a byte at
+a time and need to know when a full rune
+has arrived.
+.PP
+The following routines are analogous to the
+corresponding string routines with
+.B utf
+substituted for
+.B str
+and
+.B rune
+substituted for
+.BR chr .
+.PP
+.I Utfecpy
+copies UTF sequences until a null sequence has been copied, but writes no
+sequences beyond
+.IR es1 .
+If any sequences are copied,
+.I s1
+is terminated by a null sequence, and a pointer to that sequence is returned.
+Otherwise, the original
+.I s1
+is returned.
+.PP
+.I Utflen
+returns the number of runes that
+are represented by the
+.SM UTF
+string
+.IR s .
+.PP
+.I Utfnlen
+returns the number of complete runes that
+are represented by the first
+.I n
+bytes of
+.SM UTF
+string
+.IR s .
+If the last few bytes of the string contain an incompletely coded rune,
+.I utfnlen
+will not count them; in this way, it differs from
+.IR utflen ,
+which includes every byte of the string.
+.PP
+.I Utfrune
+.RI ( utfrrune )
+returns a pointer to the first (last)
+occurrence of rune
+.I c
+in the
+.SM UTF
+string
+.IR s ,
+or 0 if
+.I c
+does not occur in the string.
+The NUL byte terminating a string is considered to
+be part of the string
+.IR s .
+.PP
+.I Utfutf
+returns a pointer to the first occurrence of
+the
+.SM UTF
+string
+.I s2
+as a
+.SM UTF
+substring of
+.IR s1 ,
+or 0 if there is none.
+If
+.I s2
+is the null string,
+.I utfutf
+returns
+.IR s1 .
+.SH SOURCE
+.B http://swtch.com/plan9port/unix
+.SH SEE ALSO
+.IR utf (7),
+.IR tcs (1)
diff --git a/libutf/rune.c b/libutf/rune.c
new file mode 100644
index 0000000..19339ed
--- /dev/null
+++ b/libutf/rune.c
@@ -0,0 +1,175 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "plan9.h"
+#include "utf.h"
+
+enum
+{
+ Bit1 = 7,
+ Bitx = 6,
+ Bit2 = 5,
+ Bit3 = 4,
+ Bit4 = 3,
+
+ T1 = ((1<<(Bit1+1))-1) ^ 0xFF, /* 0000 0000 */
+ Tx = ((1<<(Bitx+1))-1) ^ 0xFF, /* 1000 0000 */
+ T2 = ((1<<(Bit2+1))-1) ^ 0xFF, /* 1100 0000 */
+ T3 = ((1<<(Bit3+1))-1) ^ 0xFF, /* 1110 0000 */
+ T4 = ((1<<(Bit4+1))-1) ^ 0xFF, /* 1111 0000 */
+
+ Rune1 = (1<<(Bit1+0*Bitx))-1, /* 0000 0000 0111 1111 */
+ Rune2 = (1<<(Bit2+1*Bitx))-1, /* 0000 0111 1111 1111 */
+ Rune3 = (1<<(Bit3+2*Bitx))-1, /* 1111 1111 1111 1111 */
+
+ Maskx = (1<<Bitx)-1, /* 0011 1111 */
+ Testx = Maskx ^ 0xFF, /* 1100 0000 */
+
+ Bad = Runeerror,
+};
+
+int
+chartorune(Rune *rune, const char *str)
+{
+ int c, c1, c2;
+ long l;
+
+ /*
+ * one character sequence
+ * 00000-0007F => T1
+ */
+ c = *(uchar*)str;
+ if(c < Tx) {
+ *rune = c;
+ return 1;
+ }
+
+ /*
+ * two character sequence
+ * 0080-07FF => T2 Tx
+ */
+ c1 = *(uchar*)(str+1) ^ Tx;
+ if(c1 & Testx)
+ goto bad;
+ if(c < T3) {
+ if(c < T2)
+ goto bad;
+ l = ((c << Bitx) | c1) & Rune2;
+ if(l <= Rune1)
+ goto bad;
+ *rune = l;
+ return 2;
+ }
+
+ /*
+ * three character sequence
+ * 0800-FFFF => T3 Tx Tx
+ */
+ c2 = *(uchar*)(str+2) ^ Tx;
+ if(c2 & Testx)
+ goto bad;
+ if(c < T4) {
+ l = ((((c << Bitx) | c1) << Bitx) | c2) & Rune3;
+ if(l <= Rune2)
+ goto bad;
+ *rune = l;
+ return 3;
+ }
+
+ /*
+ * bad decoding
+ */
+bad:
+ *rune = Bad;
+ return 1;
+}
+
+int
+runetochar(char *str, const Rune *rune)
+{
+ long c;
+
+ /*
+ * one character sequence
+ * 00000-0007F => 00-7F
+ */
+ c = *rune;
+ if(c <= Rune1) {
+ str[0] = c;
+ return 1;
+ }
+
+ /*
+ * two character sequence
+ * 0080-07FF => T2 Tx
+ */
+ if(c <= Rune2) {
+ str[0] = T2 | (c >> 1*Bitx);
+ str[1] = Tx | (c & Maskx);
+ return 2;
+ }
+
+ /*
+ * three character sequence
+ * 0800-FFFF => T3 Tx Tx
+ */
+ str[0] = T3 | (c >> 2*Bitx);
+ str[1] = Tx | ((c >> 1*Bitx) & Maskx);
+ str[2] = Tx | (c & Maskx);
+ return 3;
+}
+
+int
+runelen(Rune c)
+{
+ char str[10];
+
+ return runetochar(str, &c);
+}
+
+int
+runenlen(const Rune *r, int nrune)
+{
+ int nb, c;
+
+ nb = 0;
+ while(nrune--) {
+ c = *r++;
+ if(c <= Rune1)
+ nb++;
+ else
+ if(c <= Rune2)
+ nb += 2;
+ else
+ nb += 3;
+ }
+ return nb;
+}
+
+int
+fullrune(const char *str, int n)
+{
+ int c;
+
+ if(n > 0) {
+ c = *(uchar*)str;
+ if(c < Tx)
+ return 1;
+ if(n > 1)
+ if(c < T3 || n > 2)
+ return 1;
+ }
+ return 0;
+}
diff --git a/libutf/runestrcat.3 b/libutf/runestrcat.3
new file mode 100644
index 0000000..f75a386
--- /dev/null
+++ b/libutf/runestrcat.3
@@ -0,0 +1,74 @@
+.deEX
+.ift .ft5
+.nf
+..
+.deEE
+.ft1
+.fi
+..
+.TH RUNESTRCAT 3
+.SH NAME
+runestrcat,
+runestrncat,
+runestrcmp,
+runestrncmp,
+runestrcpy,
+runestrncpy,
+runestrecpy,
+runestrlen,
+runestrchr,
+runestrrchr,
+runestrdup,
+runestrstr \- rune string operations
+.SH SYNOPSIS
+.B #include <u.h>
+.br
+.B #include <libc.h>
+.PP
+.ta \w'\fLRune* \fP'u
+.B
+Rune* runestrcat(Rune *s1, Rune *s2)
+.PP
+.B
+Rune* runestrncat(Rune *s1, Rune *s2, long n)
+.PP
+.B
+int runestrcmp(Rune *s1, Rune *s2)
+.PP
+.B
+int runestrncmp(Rune *s1, Rune *s2, long n)
+.PP
+.B
+Rune* runestrcpy(Rune *s1, Rune *s2)
+.PP
+.B
+Rune* runestrncpy(Rune *s1, Rune *s2, long n)
+.PP
+.B
+Rune* runestrecpy(Rune *s1, Rune *es1, Rune *s2)
+.PP
+.B
+long runestrlen(Rune *s)
+.PP
+.B
+Rune* runestrchr(Rune *s, Rune c)
+.PP
+.B
+Rune* runestrrchr(Rune *s, Rune c)
+.PP
+.B
+Rune* runestrdup(Rune *s)
+.PP
+.B
+Rune* runestrstr(Rune *s1, Rune *s2)
+.SH DESCRIPTION
+These functions are rune string analogues of
+the corresponding functions in
+.IR strcat (3).
+.SH SOURCE
+.B http://swtch.com/plan9port/unix
+.SH SEE ALSO
+.IR rune (3),
+.IR strcat (3)
+.SH BUGS
+The outcome of overlapping moves varies among implementations.
diff --git a/libutf/runestrcat.c b/libutf/runestrcat.c
new file mode 100644
index 0000000..30ac555
--- /dev/null
+++ b/libutf/runestrcat.c
@@ -0,0 +1,24 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include <plan9.h>
+
+Rune*
+runestrcat(Rune *s1, const Rune *s2)
+{
+
+ runestrcpy(runestrchr(s1, 0), s2);
+ return s1;
+}
diff --git a/libutf/runestrchr.c b/libutf/runestrchr.c
new file mode 100644
index 0000000..b04bc3e
--- /dev/null
+++ b/libutf/runestrchr.c
@@ -0,0 +1,35 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "plan9.h"
+#include "utf.h"
+
+Rune*
+runestrchr(const Rune *s, Rune c)
+{
+ Rune c0 = c;
+ Rune c1;
+
+ if(c == 0) {
+ while(*s++)
+ ;
+ return (Rune*)s-1;
+ }
+
+ while(c1 = *s++)
+ if(c1 == c0)
+ return (Rune*)s-1;
+ return 0;
+}
diff --git a/libutf/runestrcmp.c b/libutf/runestrcmp.c
new file mode 100644
index 0000000..0bad90d
--- /dev/null
+++ b/libutf/runestrcmp.c
@@ -0,0 +1,35 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "plan9.h"
+#include "utf.h"
+
+int
+runestrcmp(const Rune *s1, const Rune *s2)
+{
+ Rune c1, c2;
+
+ for(;;) {
+ c1 = *s1++;
+ c2 = *s2++;
+ if(c1 != c2) {
+ if(c1 > c2)
+ return 1;
+ return -1;
+ }
+ if(c1 == 0)
+ return 0;
+ }
+}
diff --git a/libutf/runestrcpy.c b/libutf/runestrcpy.c
new file mode 100644
index 0000000..787ff1b
--- /dev/null
+++ b/libutf/runestrcpy.c
@@ -0,0 +1,28 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "plan9.h"
+#include "utf.h"
+
+Rune*
+runestrcpy(Rune *s1, const Rune *s2)
+{
+ Rune *os1;
+
+ os1 = s1;
+ while(*s1++ = *s2++)
+ ;
+ return os1;
+}
diff --git a/libutf/runestrdup.c b/libutf/runestrdup.c
new file mode 100644
index 0000000..54e3d09
--- /dev/null
+++ b/libutf/runestrdup.c
@@ -0,0 +1,27 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdlib.h>
+#include <plan9.h>
+
+Rune*
+runestrdup(const Rune *s)
+{
+ Rune *ns;
+
+ ns = malloc(sizeof(Rune)*(runestrlen(s) + 1));
+ if(ns == 0)
+ return 0;
+
+ return runestrcpy(ns, s);
+}
diff --git a/libutf/runestrecpy.c b/libutf/runestrecpy.c
new file mode 100644
index 0000000..4f32a97
--- /dev/null
+++ b/libutf/runestrecpy.c
@@ -0,0 +1,32 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "plan9.h"
+#include "utf.h"
+
+Rune*
+runestrecpy(Rune *s1, Rune *es1, const Rune *s2)
+{
+ if(s1 >= es1)
+ return s1;
+
+ while(*s1++ = *s2++){
+ if(s1 == es1){
+ *--s1 = '\0';
+ break;
+ }
+ }
+ return s1;
+}
diff --git a/libutf/runestrlen.c b/libutf/runestrlen.c
new file mode 100644
index 0000000..fcc2313
--- /dev/null
+++ b/libutf/runestrlen.c
@@ -0,0 +1,21 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <plan9.h>
+
+long
+runestrlen(const Rune *s)
+{
+
+ return runestrchr(s, 0) - s;
+}
diff --git a/libutf/runestrncat.c b/libutf/runestrncat.c
new file mode 100644
index 0000000..70f2347
--- /dev/null
+++ b/libutf/runestrncat.c
@@ -0,0 +1,32 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "plan9.h"
+#include "utf.h"
+
+Rune*
+runestrncat(Rune *s1, const Rune *s2, long n)
+{
+ Rune *os1;
+
+ os1 = s1;
+ s1 = runestrchr(s1, 0);
+ while(*s1++ = *s2++)
+ if(--n < 0) {
+ s1[-1] = 0;
+ break;
+ }
+ return os1;
+}
diff --git a/libutf/runestrncmp.c b/libutf/runestrncmp.c
new file mode 100644
index 0000000..69ccdc6
--- /dev/null
+++ b/libutf/runestrncmp.c
@@ -0,0 +1,37 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "plan9.h"
+#include "utf.h"
+
+int
+runestrncmp(const Rune *s1, const Rune *s2, long n)
+{
+ Rune c1, c2;
+
+ while(n > 0) {
+ c1 = *s1++;
+ c2 = *s2++;
+ n--;
+ if(c1 != c2) {
+ if(c1 > c2)
+ return 1;
+ return -1;
+ }
+ if(c1 == 0)
+ break;
+ }
+ return 0;
+}
diff --git a/libutf/runestrncpy.c b/libutf/runestrncpy.c
new file mode 100644
index 0000000..c7f3dfd
--- /dev/null
+++ b/libutf/runestrncpy.c
@@ -0,0 +1,33 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "plan9.h"
+#include "utf.h"
+
+Rune*
+runestrncpy(Rune *s1, const Rune *s2, long n)
+{
+ int i;
+ Rune *os1;
+
+ os1 = s1;
+ for(i = 0; i < n; i++)
+ if((*s1++ = *s2++) == 0) {
+ while(++i < n)
+ *s1++ = 0;
+ return os1;
+ }
+ return os1;
+}
diff --git a/libutf/runestrrchr.c b/libutf/runestrrchr.c
new file mode 100644
index 0000000..033eb3f
--- /dev/null
+++ b/libutf/runestrrchr.c
@@ -0,0 +1,30 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "plan9.h"
+#include "utf.h"
+
+Rune*
+runestrrchr(const Rune *s, Rune c)
+{
+ const Rune *r;
+
+ if(c == 0)
+ return runestrchr(s, 0);
+ r = 0;
+ while(s = runestrchr(s, c))
+ r = s++;
+ return (Rune*)r;
+}
diff --git a/libutf/runestrstr.c b/libutf/runestrstr.c
new file mode 100644
index 0000000..2894223
--- /dev/null
+++ b/libutf/runestrstr.c
@@ -0,0 +1,45 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "plan9.h"
+#include "utf.h"
+
+/*
+ * Return pointer to first occurrence of s2 in s1,
+ * 0 if none
+ */
+Rune*
+runestrstr(const Rune *s1, const Rune *s2)
+{
+ const Rune *pa, *pb;
+ Rune *p;
+ int c0, c;
+
+ c0 = *s2;
+ if(c0 == 0)
+ return (Rune*)s1;
+ s2++;
+ for(p=runestrchr(s1, c0); p; p=runestrchr(p+1, c0)) {
+ pa = p;
+ for(pb=s2;; pb++) {
+ c = *pb;
+ if(c == 0)
+ return p;
+ if(c != *++pa)
+ break;
+ }
+ }
+ return 0;
+}
diff --git a/libutf/runetype.c b/libutf/runetype.c
new file mode 100644
index 0000000..ac6d7b5
--- /dev/null
+++ b/libutf/runetype.c
@@ -0,0 +1,1151 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "plan9.h"
+#include "utf.h"
+
+/*
+ * alpha ranges -
+ * only covers ranges not in lower||upper
+ */
+static
+Rune __alpha2[] =
+{
+ 0x00d8, 0x00f6, /* Ø - ö */
+ 0x00f8, 0x01f5, /* ø - ǵ */
+ 0x0250, 0x02a8, /* ɐ - ʨ */
+ 0x038e, 0x03a1, /* Ύ - Ρ */
+ 0x03a3, 0x03ce, /* Σ - ώ */
+ 0x03d0, 0x03d6, /* ϐ - ϖ */
+ 0x03e2, 0x03f3, /* Ϣ - ϳ */
+ 0x0490, 0x04c4, /* Ґ - ӄ */
+ 0x0561, 0x0587, /* ա - և */
+ 0x05d0, 0x05ea, /* א - ת */
+ 0x05f0, 0x05f2, /* װ - ײ */
+ 0x0621, 0x063a, /* ء - غ */
+ 0x0640, 0x064a, /* ـ - ي */
+ 0x0671, 0x06b7, /* ٱ - ڷ */
+ 0x06ba, 0x06be, /* ں - ھ */
+ 0x06c0, 0x06ce, /* ۀ - ێ */
+ 0x06d0, 0x06d3, /* ې - ۓ */
+ 0x0905, 0x0939, /* अ - ह */
+ 0x0958, 0x0961, /* क़ - ॡ */
+ 0x0985, 0x098c, /* অ - ঌ */
+ 0x098f, 0x0990, /* এ - ঐ */
+ 0x0993, 0x09a8, /* ও - ন */
+ 0x09aa, 0x09b0, /* প - র */
+ 0x09b6, 0x09b9, /* শ - হ */
+ 0x09dc, 0x09dd, /* ড় - ঢ় */
+ 0x09df, 0x09e1, /* য় - ৡ */
+ 0x09f0, 0x09f1, /* ৰ - ৱ */
+ 0x0a05, 0x0a0a, /* ਅ - ਊ */
+ 0x0a0f, 0x0a10, /* ਏ - ਐ */
+ 0x0a13, 0x0a28, /* ਓ - ਨ */
+ 0x0a2a, 0x0a30, /* ਪ - ਰ */
+ 0x0a32, 0x0a33, /* ਲ - ਲ਼ */
+ 0x0a35, 0x0a36, /* ਵ - ਸ਼ */
+ 0x0a38, 0x0a39, /* ਸ - ਹ */
+ 0x0a59, 0x0a5c, /* ਖ਼ - ੜ */
+ 0x0a85, 0x0a8b, /* અ - ઋ */
+ 0x0a8f, 0x0a91, /* એ - ઑ */
+ 0x0a93, 0x0aa8, /* ઓ - ન */
+ 0x0aaa, 0x0ab0, /* પ - ર */
+ 0x0ab2, 0x0ab3, /* લ - ળ */
+ 0x0ab5, 0x0ab9, /* વ - હ */
+ 0x0b05, 0x0b0c, /* ଅ - ଌ */
+ 0x0b0f, 0x0b10, /* ଏ - ଐ */
+ 0x0b13, 0x0b28, /* ଓ - ନ */
+ 0x0b2a, 0x0b30, /* ପ - ର */
+ 0x0b32, 0x0b33, /* ଲ - ଳ */
+ 0x0b36, 0x0b39, /* ଶ - ହ */
+ 0x0b5c, 0x0b5d, /* ଡ଼ - ଢ଼ */
+ 0x0b5f, 0x0b61, /* ୟ - ୡ */
+ 0x0b85, 0x0b8a, /* அ - ஊ */
+ 0x0b8e, 0x0b90, /* எ - ஐ */
+ 0x0b92, 0x0b95, /* ஒ - க */
+ 0x0b99, 0x0b9a, /* ங - ச */
+ 0x0b9e, 0x0b9f, /* ஞ - ட */
+ 0x0ba3, 0x0ba4, /* ண - த */
+ 0x0ba8, 0x0baa, /* ந - ப */
+ 0x0bae, 0x0bb5, /* ம - வ */
+ 0x0bb7, 0x0bb9, /* ஷ - ஹ */
+ 0x0c05, 0x0c0c, /* అ - ఌ */
+ 0x0c0e, 0x0c10, /* ఎ - ఐ */
+ 0x0c12, 0x0c28, /* ఒ - న */
+ 0x0c2a, 0x0c33, /* ప - ళ */
+ 0x0c35, 0x0c39, /* వ - హ */
+ 0x0c60, 0x0c61, /* ౠ - ౡ */
+ 0x0c85, 0x0c8c, /* ಅ - ಌ */
+ 0x0c8e, 0x0c90, /* ಎ - ಐ */
+ 0x0c92, 0x0ca8, /* ಒ - ನ */
+ 0x0caa, 0x0cb3, /* ಪ - ಳ */
+ 0x0cb5, 0x0cb9, /* ವ - ಹ */
+ 0x0ce0, 0x0ce1, /* ೠ - ೡ */
+ 0x0d05, 0x0d0c, /* അ - ഌ */
+ 0x0d0e, 0x0d10, /* എ - ഐ */
+ 0x0d12, 0x0d28, /* ഒ - ന */
+ 0x0d2a, 0x0d39, /* പ - ഹ */
+ 0x0d60, 0x0d61, /* ൠ - ൡ */
+ 0x0e01, 0x0e30, /* ก - ะ */
+ 0x0e32, 0x0e33, /* า - ำ */
+ 0x0e40, 0x0e46, /* เ - ๆ */
+ 0x0e5a, 0x0e5b, /* ๚ - ๛ */
+ 0x0e81, 0x0e82, /* ກ - ຂ */
+ 0x0e87, 0x0e88, /* ງ - ຈ */
+ 0x0e94, 0x0e97, /* ດ - ທ */
+ 0x0e99, 0x0e9f, /* ນ - ຟ */
+ 0x0ea1, 0x0ea3, /* ມ - ຣ */
+ 0x0eaa, 0x0eab, /* ສ - ຫ */
+ 0x0ead, 0x0eae, /* ອ - ຮ */
+ 0x0eb2, 0x0eb3, /* າ - ຳ */
+ 0x0ec0, 0x0ec4, /* ເ - ໄ */
+ 0x0edc, 0x0edd, /* ໜ - ໝ */
+ 0x0f18, 0x0f19, /* ༘ - ༙ */
+ 0x0f40, 0x0f47, /* ཀ - ཇ */
+ 0x0f49, 0x0f69, /* ཉ - ཀྵ */
+ 0x10d0, 0x10f6, /* ა - ჶ */
+ 0x1100, 0x1159, /* ᄀ - ᅙ */
+ 0x115f, 0x11a2, /* ᅟ - ᆢ */
+ 0x11a8, 0x11f9, /* ᆨ - ᇹ */
+ 0x1e00, 0x1e9b, /* Ḁ - ẛ */
+ 0x1f50, 0x1f57, /* ὐ - ὗ */
+ 0x1f80, 0x1fb4, /* ᾀ - ᾴ */
+ 0x1fb6, 0x1fbc, /* ᾶ - ᾼ */
+ 0x1fc2, 0x1fc4, /* ῂ - ῄ */
+ 0x1fc6, 0x1fcc, /* ῆ - ῌ */
+ 0x1fd0, 0x1fd3, /* ῐ - ΐ */
+ 0x1fd6, 0x1fdb, /* ῖ - Ί */
+ 0x1fe0, 0x1fec, /* ῠ - Ῥ */
+ 0x1ff2, 0x1ff4, /* ῲ - ῴ */
+ 0x1ff6, 0x1ffc, /* ῶ - ῼ */
+ 0x210a, 0x2113, /* ℊ - ℓ */
+ 0x2115, 0x211d, /* ℕ - ℝ */
+ 0x2120, 0x2122, /* ℠ - ™ */
+ 0x212a, 0x2131, /* K - ℱ */
+ 0x2133, 0x2138, /* ℳ - ℸ */
+ 0x3041, 0x3094, /* ぁ - ゔ */
+ 0x30a1, 0x30fa, /* ァ - ヺ */
+ 0x3105, 0x312c, /* ㄅ - ㄬ */
+ 0x3131, 0x318e, /* ㄱ - ㆎ */
+ 0x3192, 0x319f, /* ㆒ - ㆟ */
+ 0x3260, 0x327b, /* ㉠ - ㉻ */
+ 0x328a, 0x32b0, /* ㊊ - ㊰ */
+ 0x32d0, 0x32fe, /* ㋐ - ㋾ */
+ 0x3300, 0x3357, /* ㌀ - ㍗ */
+ 0x3371, 0x3376, /* ㍱ - ㍶ */
+ 0x337b, 0x3394, /* ㍻ - ㎔ */
+ 0x3399, 0x339e, /* ㎙ - ㎞ */
+ 0x33a9, 0x33ad, /* ㎩ - ㎭ */
+ 0x33b0, 0x33c1, /* ㎰ - ㏁ */
+ 0x33c3, 0x33c5, /* ㏃ - ㏅ */
+ 0x33c7, 0x33d7, /* ㏇ - ㏗ */
+ 0x33d9, 0x33dd, /* ㏙ - ㏝ */
+ 0x4e00, 0x9fff, /* 一 - 鿿 */
+ 0xac00, 0xd7a3, /* 가 - 힣 */
+ 0xf900, 0xfb06, /* 豈 - st */
+ 0xfb13, 0xfb17, /* ﬓ - ﬗ */
+ 0xfb1f, 0xfb28, /* ײַ - ﬨ */
+ 0xfb2a, 0xfb36, /* שׁ - זּ */
+ 0xfb38, 0xfb3c, /* טּ - לּ */
+ 0xfb40, 0xfb41, /* נּ - סּ */
+ 0xfb43, 0xfb44, /* ףּ - פּ */
+ 0xfb46, 0xfbb1, /* צּ - ﮱ */
+ 0xfbd3, 0xfd3d, /* ﯓ - ﴽ */
+ 0xfd50, 0xfd8f, /* ﵐ - ﶏ */
+ 0xfd92, 0xfdc7, /* ﶒ - ﷇ */
+ 0xfdf0, 0xfdf9, /* ﷰ - ﷹ */
+ 0xfe70, 0xfe72, /* ﹰ - ﹲ */
+ 0xfe76, 0xfefc, /* ﹶ - ﻼ */
+ 0xff66, 0xff6f, /* ヲ - ッ */
+ 0xff71, 0xff9d, /* ア - ン */
+ 0xffa0, 0xffbe, /* ᅠ - ᄒ */
+ 0xffc2, 0xffc7, /* ᅡ - ᅦ */
+ 0xffca, 0xffcf, /* ᅧ - ᅬ */
+ 0xffd2, 0xffd7, /* ᅭ - ᅲ */
+ 0xffda, 0xffdc, /* ᅳ - ᅵ */
+};
+
+/*
+ * alpha singlets -
+ * only covers ranges not in lower||upper
+ */
+static
+Rune __alpha1[] =
+{
+ 0x00aa, /* ª */
+ 0x00b5, /* µ */
+ 0x00ba, /* º */
+ 0x03da, /* Ϛ */
+ 0x03dc, /* Ϝ */
+ 0x03de, /* Ϟ */
+ 0x03e0, /* Ϡ */
+ 0x06d5, /* ە */
+ 0x09b2, /* ল */
+ 0x0a5e, /* ਫ਼ */
+ 0x0a8d, /* ઍ */
+ 0x0ae0, /* ૠ */
+ 0x0b9c, /* ஜ */
+ 0x0cde, /* ೞ */
+ 0x0e4f, /* ๏ */
+ 0x0e84, /* ຄ */
+ 0x0e8a, /* ຊ */
+ 0x0e8d, /* ຍ */
+ 0x0ea5, /* ລ */
+ 0x0ea7, /* ວ */
+ 0x0eb0, /* ະ */
+ 0x0ebd, /* ຽ */
+ 0x1fbe, /* ι */
+ 0x207f, /* ⁿ */
+ 0x20a8, /* ₨ */
+ 0x2102, /* ℂ */
+ 0x2107, /* ℇ */
+ 0x2124, /* ℤ */
+ 0x2126, /* Ω */
+ 0x2128, /* ℨ */
+ 0xfb3e, /* מּ */
+ 0xfe74, /* ﹴ */
+};
+
+/*
+ * space ranges
+ */
+static
+Rune __space2[] =
+{
+ 0x0009, 0x000a, /* tab and newline */
+ 0x0020, 0x0020, /* space */
+ 0x00a0, 0x00a0, /*   */
+ 0x2000, 0x200b, /*   - ​ */
+ 0x2028, 0x2029, /* 
 - 
 */
+ 0x3000, 0x3000, /*   */
+ 0xfeff, 0xfeff, /*  */
+};
+
+/*
+ * lower case ranges
+ * 3rd col is conversion excess 500
+ */
+static
+Rune __toupper2[] =
+{
+ 0x0061, 0x007a, 468, /* a-z A-Z */
+ 0x00e0, 0x00f6, 468, /* à-ö À-Ö */
+ 0x00f8, 0x00fe, 468, /* ø-þ Ø-Þ */
+ 0x0256, 0x0257, 295, /* ɖ-ɗ Ɖ-Ɗ */
+ 0x0258, 0x0259, 298, /* ɘ-ə Ǝ-Ə */
+ 0x028a, 0x028b, 283, /* ʊ-ʋ Ʊ-Ʋ */
+ 0x03ad, 0x03af, 463, /* έ-ί Έ-Ί */
+ 0x03b1, 0x03c1, 468, /* α-ρ Α-Ρ */
+ 0x03c3, 0x03cb, 468, /* σ-ϋ Σ-Ϋ */
+ 0x03cd, 0x03ce, 437, /* ύ-ώ Ύ-Ώ */
+ 0x0430, 0x044f, 468, /* а-я А-Я */
+ 0x0451, 0x045c, 420, /* ё-ќ Ё-Ќ */
+ 0x045e, 0x045f, 420, /* ў-џ Ў-Џ */
+ 0x0561, 0x0586, 452, /* ա-ֆ Ա-Ֆ */
+ 0x1f00, 0x1f07, 508, /* ἀ-ἇ Ἀ-Ἇ */
+ 0x1f10, 0x1f15, 508, /* ἐ-ἕ Ἐ-Ἕ */
+ 0x1f20, 0x1f27, 508, /* ἠ-ἧ Ἠ-Ἧ */
+ 0x1f30, 0x1f37, 508, /* ἰ-ἷ Ἰ-Ἷ */
+ 0x1f40, 0x1f45, 508, /* ὀ-ὅ Ὀ-Ὅ */
+ 0x1f60, 0x1f67, 508, /* ὠ-ὧ Ὠ-Ὧ */
+ 0x1f70, 0x1f71, 574, /* ὰ-ά Ὰ-Ά */
+ 0x1f72, 0x1f75, 586, /* ὲ-ή Ὲ-Ή */
+ 0x1f76, 0x1f77, 600, /* ὶ-ί Ὶ-Ί */
+ 0x1f78, 0x1f79, 628, /* ὸ-ό Ὸ-Ό */
+ 0x1f7a, 0x1f7b, 612, /* ὺ-ύ Ὺ-Ύ */
+ 0x1f7c, 0x1f7d, 626, /* ὼ-ώ Ὼ-Ώ */
+ 0x1f80, 0x1f87, 508, /* ᾀ-ᾇ ᾈ-ᾏ */
+ 0x1f90, 0x1f97, 508, /* ᾐ-ᾗ ᾘ-ᾟ */
+ 0x1fa0, 0x1fa7, 508, /* ᾠ-ᾧ ᾨ-ᾯ */
+ 0x1fb0, 0x1fb1, 508, /* ᾰ-ᾱ Ᾰ-Ᾱ */
+ 0x1fd0, 0x1fd1, 508, /* ῐ-ῑ Ῐ-Ῑ */
+ 0x1fe0, 0x1fe1, 508, /* ῠ-ῡ Ῠ-Ῡ */
+ 0x2170, 0x217f, 484, /* ⅰ-ⅿ Ⅰ-Ⅿ */
+ 0x24d0, 0x24e9, 474, /* ⓐ-ⓩ Ⓐ-Ⓩ */
+ 0xff41, 0xff5a, 468, /* a-z A-Z */
+};
+
+/*
+ * lower case singlets
+ * 2nd col is conversion excess 500
+ */
+static
+Rune __toupper1[] =
+{
+ 0x00ff, 621, /* ÿ Ÿ */
+ 0x0101, 499, /* ā Ā */
+ 0x0103, 499, /* ă Ă */
+ 0x0105, 499, /* ą Ą */
+ 0x0107, 499, /* ć Ć */
+ 0x0109, 499, /* ĉ Ĉ */
+ 0x010b, 499, /* ċ Ċ */
+ 0x010d, 499, /* č Č */
+ 0x010f, 499, /* ď Ď */
+ 0x0111, 499, /* đ Đ */
+ 0x0113, 499, /* ē Ē */
+ 0x0115, 499, /* ĕ Ĕ */
+ 0x0117, 499, /* ė Ė */
+ 0x0119, 499, /* ę Ę */
+ 0x011b, 499, /* ě Ě */
+ 0x011d, 499, /* ĝ Ĝ */
+ 0x011f, 499, /* ğ Ğ */
+ 0x0121, 499, /* ġ Ġ */
+ 0x0123, 499, /* ģ Ģ */
+ 0x0125, 499, /* ĥ Ĥ */
+ 0x0127, 499, /* ħ Ħ */
+ 0x0129, 499, /* ĩ Ĩ */
+ 0x012b, 499, /* ī Ī */
+ 0x012d, 499, /* ĭ Ĭ */
+ 0x012f, 499, /* į Į */
+ 0x0131, 268, /* ı I */
+ 0x0133, 499, /* ij IJ */
+ 0x0135, 499, /* ĵ Ĵ */
+ 0x0137, 499, /* ķ Ķ */
+ 0x013a, 499, /* ĺ Ĺ */
+ 0x013c, 499, /* ļ Ļ */
+ 0x013e, 499, /* ľ Ľ */
+ 0x0140, 499, /* ŀ Ŀ */
+ 0x0142, 499, /* ł Ł */
+ 0x0144, 499, /* ń Ń */
+ 0x0146, 499, /* ņ Ņ */
+ 0x0148, 499, /* ň Ň */
+ 0x014b, 499, /* ŋ Ŋ */
+ 0x014d, 499, /* ō Ō */
+ 0x014f, 499, /* ŏ Ŏ */
+ 0x0151, 499, /* ő Ő */
+ 0x0153, 499, /* œ Œ */
+ 0x0155, 499, /* ŕ Ŕ */
+ 0x0157, 499, /* ŗ Ŗ */
+ 0x0159, 499, /* ř Ř */
+ 0x015b, 499, /* ś Ś */
+ 0x015d, 499, /* ŝ Ŝ */
+ 0x015f, 499, /* ş Ş */
+ 0x0161, 499, /* š Š */
+ 0x0163, 499, /* ţ Ţ */
+ 0x0165, 499, /* ť Ť */
+ 0x0167, 499, /* ŧ Ŧ */
+ 0x0169, 499, /* ũ Ũ */
+ 0x016b, 499, /* ū Ū */
+ 0x016d, 499, /* ŭ Ŭ */
+ 0x016f, 499, /* ů Ů */
+ 0x0171, 499, /* ű Ű */
+ 0x0173, 499, /* ų Ų */
+ 0x0175, 499, /* ŵ Ŵ */
+ 0x0177, 499, /* ŷ Ŷ */
+ 0x017a, 499, /* ź Ź */
+ 0x017c, 499, /* ż Ż */
+ 0x017e, 499, /* ž Ž */
+ 0x017f, 200, /* ſ S */
+ 0x0183, 499, /* ƃ Ƃ */
+ 0x0185, 499, /* ƅ Ƅ */
+ 0x0188, 499, /* ƈ Ƈ */
+ 0x018c, 499, /* ƌ Ƌ */
+ 0x0192, 499, /* ƒ Ƒ */
+ 0x0199, 499, /* ƙ Ƙ */
+ 0x01a1, 499, /* ơ Ơ */
+ 0x01a3, 499, /* ƣ Ƣ */
+ 0x01a5, 499, /* ƥ Ƥ */
+ 0x01a8, 499, /* ƨ Ƨ */
+ 0x01ad, 499, /* ƭ Ƭ */
+ 0x01b0, 499, /* ư Ư */
+ 0x01b4, 499, /* ƴ Ƴ */
+ 0x01b6, 499, /* ƶ Ƶ */
+ 0x01b9, 499, /* ƹ Ƹ */
+ 0x01bd, 499, /* ƽ Ƽ */
+ 0x01c5, 499, /* Dž DŽ */
+ 0x01c6, 498, /* dž DŽ */
+ 0x01c8, 499, /* Lj LJ */
+ 0x01c9, 498, /* lj LJ */
+ 0x01cb, 499, /* Nj NJ */
+ 0x01cc, 498, /* nj NJ */
+ 0x01ce, 499, /* ǎ Ǎ */
+ 0x01d0, 499, /* ǐ Ǐ */
+ 0x01d2, 499, /* ǒ Ǒ */
+ 0x01d4, 499, /* ǔ Ǔ */
+ 0x01d6, 499, /* ǖ Ǖ */
+ 0x01d8, 499, /* ǘ Ǘ */
+ 0x01da, 499, /* ǚ Ǚ */
+ 0x01dc, 499, /* ǜ Ǜ */
+ 0x01df, 499, /* ǟ Ǟ */
+ 0x01e1, 499, /* ǡ Ǡ */
+ 0x01e3, 499, /* ǣ Ǣ */
+ 0x01e5, 499, /* ǥ Ǥ */
+ 0x01e7, 499, /* ǧ Ǧ */
+ 0x01e9, 499, /* ǩ Ǩ */
+ 0x01eb, 499, /* ǫ Ǫ */
+ 0x01ed, 499, /* ǭ Ǭ */
+ 0x01ef, 499, /* ǯ Ǯ */
+ 0x01f2, 499, /* Dz DZ */
+ 0x01f3, 498, /* dz DZ */
+ 0x01f5, 499, /* ǵ Ǵ */
+ 0x01fb, 499, /* ǻ Ǻ */
+ 0x01fd, 499, /* ǽ Ǽ */
+ 0x01ff, 499, /* ǿ Ǿ */
+ 0x0201, 499, /* ȁ Ȁ */
+ 0x0203, 499, /* ȃ Ȃ */
+ 0x0205, 499, /* ȅ Ȅ */
+ 0x0207, 499, /* ȇ Ȇ */
+ 0x0209, 499, /* ȉ Ȉ */
+ 0x020b, 499, /* ȋ Ȋ */
+ 0x020d, 499, /* ȍ Ȍ */
+ 0x020f, 499, /* ȏ Ȏ */
+ 0x0211, 499, /* ȑ Ȑ */
+ 0x0213, 499, /* ȓ Ȓ */
+ 0x0215, 499, /* ȕ Ȕ */
+ 0x0217, 499, /* ȗ Ȗ */
+ 0x0253, 290, /* ɓ Ɓ */
+ 0x0254, 294, /* ɔ Ɔ */
+ 0x025b, 297, /* ɛ Ɛ */
+ 0x0260, 295, /* ɠ Ɠ */
+ 0x0263, 293, /* ɣ Ɣ */
+ 0x0268, 291, /* ɨ Ɨ */
+ 0x0269, 289, /* ɩ Ɩ */
+ 0x026f, 289, /* ɯ Ɯ */
+ 0x0272, 287, /* ɲ Ɲ */
+ 0x0283, 282, /* ʃ Ʃ */
+ 0x0288, 282, /* ʈ Ʈ */
+ 0x0292, 281, /* ʒ Ʒ */
+ 0x03ac, 462, /* ά Ά */
+ 0x03cc, 436, /* ό Ό */
+ 0x03d0, 438, /* ϐ Β */
+ 0x03d1, 443, /* ϑ Θ */
+ 0x03d5, 453, /* ϕ Φ */
+ 0x03d6, 446, /* ϖ Π */
+ 0x03e3, 499, /* ϣ Ϣ */
+ 0x03e5, 499, /* ϥ Ϥ */
+ 0x03e7, 499, /* ϧ Ϧ */
+ 0x03e9, 499, /* ϩ Ϩ */
+ 0x03eb, 499, /* ϫ Ϫ */
+ 0x03ed, 499, /* ϭ Ϭ */
+ 0x03ef, 499, /* ϯ Ϯ */
+ 0x03f0, 414, /* ϰ Κ */
+ 0x03f1, 420, /* ϱ Ρ */
+ 0x0461, 499, /* ѡ Ѡ */
+ 0x0463, 499, /* ѣ Ѣ */
+ 0x0465, 499, /* ѥ Ѥ */
+ 0x0467, 499, /* ѧ Ѧ */
+ 0x0469, 499, /* ѩ Ѩ */
+ 0x046b, 499, /* ѫ Ѫ */
+ 0x046d, 499, /* ѭ Ѭ */
+ 0x046f, 499, /* ѯ Ѯ */
+ 0x0471, 499, /* ѱ Ѱ */
+ 0x0473, 499, /* ѳ Ѳ */
+ 0x0475, 499, /* ѵ Ѵ */
+ 0x0477, 499, /* ѷ Ѷ */
+ 0x0479, 499, /* ѹ Ѹ */
+ 0x047b, 499, /* ѻ Ѻ */
+ 0x047d, 499, /* ѽ Ѽ */
+ 0x047f, 499, /* ѿ Ѿ */
+ 0x0481, 499, /* ҁ Ҁ */
+ 0x0491, 499, /* ґ Ґ */
+ 0x0493, 499, /* ғ Ғ */
+ 0x0495, 499, /* ҕ Ҕ */
+ 0x0497, 499, /* җ Җ */
+ 0x0499, 499, /* ҙ Ҙ */
+ 0x049b, 499, /* қ Қ */
+ 0x049d, 499, /* ҝ Ҝ */
+ 0x049f, 499, /* ҟ Ҟ */
+ 0x04a1, 499, /* ҡ Ҡ */
+ 0x04a3, 499, /* ң Ң */
+ 0x04a5, 499, /* ҥ Ҥ */
+ 0x04a7, 499, /* ҧ Ҧ */
+ 0x04a9, 499, /* ҩ Ҩ */
+ 0x04ab, 499, /* ҫ Ҫ */
+ 0x04ad, 499, /* ҭ Ҭ */
+ 0x04af, 499, /* ү Ү */
+ 0x04b1, 499, /* ұ Ұ */
+ 0x04b3, 499, /* ҳ Ҳ */
+ 0x04b5, 499, /* ҵ Ҵ */
+ 0x04b7, 499, /* ҷ Ҷ */
+ 0x04b9, 499, /* ҹ Ҹ */
+ 0x04bb, 499, /* һ Һ */
+ 0x04bd, 499, /* ҽ Ҽ */
+ 0x04bf, 499, /* ҿ Ҿ */
+ 0x04c2, 499, /* ӂ Ӂ */
+ 0x04c4, 499, /* ӄ Ӄ */
+ 0x04c8, 499, /* ӈ Ӈ */
+ 0x04cc, 499, /* ӌ Ӌ */
+ 0x04d1, 499, /* ӑ Ӑ */
+ 0x04d3, 499, /* ӓ Ӓ */
+ 0x04d5, 499, /* ӕ Ӕ */
+ 0x04d7, 499, /* ӗ Ӗ */
+ 0x04d9, 499, /* ә Ә */
+ 0x04db, 499, /* ӛ Ӛ */
+ 0x04dd, 499, /* ӝ Ӝ */
+ 0x04df, 499, /* ӟ Ӟ */
+ 0x04e1, 499, /* ӡ Ӡ */
+ 0x04e3, 499, /* ӣ Ӣ */
+ 0x04e5, 499, /* ӥ Ӥ */
+ 0x04e7, 499, /* ӧ Ӧ */
+ 0x04e9, 499, /* ө Ө */
+ 0x04eb, 499, /* ӫ Ӫ */
+ 0x04ef, 499, /* ӯ Ӯ */
+ 0x04f1, 499, /* ӱ Ӱ */
+ 0x04f3, 499, /* ӳ Ӳ */
+ 0x04f5, 499, /* ӵ Ӵ */
+ 0x04f9, 499, /* ӹ Ӹ */
+ 0x1e01, 499, /* ḁ Ḁ */
+ 0x1e03, 499, /* ḃ Ḃ */
+ 0x1e05, 499, /* ḅ Ḅ */
+ 0x1e07, 499, /* ḇ Ḇ */
+ 0x1e09, 499, /* ḉ Ḉ */
+ 0x1e0b, 499, /* ḋ Ḋ */
+ 0x1e0d, 499, /* ḍ Ḍ */
+ 0x1e0f, 499, /* ḏ Ḏ */
+ 0x1e11, 499, /* ḑ Ḑ */
+ 0x1e13, 499, /* ḓ Ḓ */
+ 0x1e15, 499, /* ḕ Ḕ */
+ 0x1e17, 499, /* ḗ Ḗ */
+ 0x1e19, 499, /* ḙ Ḙ */
+ 0x1e1b, 499, /* ḛ Ḛ */
+ 0x1e1d, 499, /* ḝ Ḝ */
+ 0x1e1f, 499, /* ḟ Ḟ */
+ 0x1e21, 499, /* ḡ Ḡ */
+ 0x1e23, 499, /* ḣ Ḣ */
+ 0x1e25, 499, /* ḥ Ḥ */
+ 0x1e27, 499, /* ḧ Ḧ */
+ 0x1e29, 499, /* ḩ Ḩ */
+ 0x1e2b, 499, /* ḫ Ḫ */
+ 0x1e2d, 499, /* ḭ Ḭ */
+ 0x1e2f, 499, /* ḯ Ḯ */
+ 0x1e31, 499, /* ḱ Ḱ */
+ 0x1e33, 499, /* ḳ Ḳ */
+ 0x1e35, 499, /* ḵ Ḵ */
+ 0x1e37, 499, /* ḷ Ḷ */
+ 0x1e39, 499, /* ḹ Ḹ */
+ 0x1e3b, 499, /* ḻ Ḻ */
+ 0x1e3d, 499, /* ḽ Ḽ */
+ 0x1e3f, 499, /* ḿ Ḿ */
+ 0x1e41, 499, /* ṁ Ṁ */
+ 0x1e43, 499, /* ṃ Ṃ */
+ 0x1e45, 499, /* ṅ Ṅ */
+ 0x1e47, 499, /* ṇ Ṇ */
+ 0x1e49, 499, /* ṉ Ṉ */
+ 0x1e4b, 499, /* ṋ Ṋ */
+ 0x1e4d, 499, /* ṍ Ṍ */
+ 0x1e4f, 499, /* ṏ Ṏ */
+ 0x1e51, 499, /* ṑ Ṑ */
+ 0x1e53, 499, /* ṓ Ṓ */
+ 0x1e55, 499, /* ṕ Ṕ */
+ 0x1e57, 499, /* ṗ Ṗ */
+ 0x1e59, 499, /* ṙ Ṙ */
+ 0x1e5b, 499, /* ṛ Ṛ */
+ 0x1e5d, 499, /* ṝ Ṝ */
+ 0x1e5f, 499, /* ṟ Ṟ */
+ 0x1e61, 499, /* ṡ Ṡ */
+ 0x1e63, 499, /* ṣ Ṣ */
+ 0x1e65, 499, /* ṥ Ṥ */
+ 0x1e67, 499, /* ṧ Ṧ */
+ 0x1e69, 499, /* ṩ Ṩ */
+ 0x1e6b, 499, /* ṫ Ṫ */
+ 0x1e6d, 499, /* ṭ Ṭ */
+ 0x1e6f, 499, /* ṯ Ṯ */
+ 0x1e71, 499, /* ṱ Ṱ */
+ 0x1e73, 499, /* ṳ Ṳ */
+ 0x1e75, 499, /* ṵ Ṵ */
+ 0x1e77, 499, /* ṷ Ṷ */
+ 0x1e79, 499, /* ṹ Ṹ */
+ 0x1e7b, 499, /* ṻ Ṻ */
+ 0x1e7d, 499, /* ṽ Ṽ */
+ 0x1e7f, 499, /* ṿ Ṿ */
+ 0x1e81, 499, /* ẁ Ẁ */
+ 0x1e83, 499, /* ẃ Ẃ */
+ 0x1e85, 499, /* ẅ Ẅ */
+ 0x1e87, 499, /* ẇ Ẇ */
+ 0x1e89, 499, /* ẉ Ẉ */
+ 0x1e8b, 499, /* ẋ Ẋ */
+ 0x1e8d, 499, /* ẍ Ẍ */
+ 0x1e8f, 499, /* ẏ Ẏ */
+ 0x1e91, 499, /* ẑ Ẑ */
+ 0x1e93, 499, /* ẓ Ẓ */
+ 0x1e95, 499, /* ẕ Ẕ */
+ 0x1ea1, 499, /* ạ Ạ */
+ 0x1ea3, 499, /* ả Ả */
+ 0x1ea5, 499, /* ấ Ấ */
+ 0x1ea7, 499, /* ầ Ầ */
+ 0x1ea9, 499, /* ẩ Ẩ */
+ 0x1eab, 499, /* ẫ Ẫ */
+ 0x1ead, 499, /* ậ Ậ */
+ 0x1eaf, 499, /* ắ Ắ */
+ 0x1eb1, 499, /* ằ Ằ */
+ 0x1eb3, 499, /* ẳ Ẳ */
+ 0x1eb5, 499, /* ẵ Ẵ */
+ 0x1eb7, 499, /* ặ Ặ */
+ 0x1eb9, 499, /* ẹ Ẹ */
+ 0x1ebb, 499, /* ẻ Ẻ */
+ 0x1ebd, 499, /* ẽ Ẽ */
+ 0x1ebf, 499, /* ế Ế */
+ 0x1ec1, 499, /* ề Ề */
+ 0x1ec3, 499, /* ể Ể */
+ 0x1ec5, 499, /* ễ Ễ */
+ 0x1ec7, 499, /* ệ Ệ */
+ 0x1ec9, 499, /* ỉ Ỉ */
+ 0x1ecb, 499, /* ị Ị */
+ 0x1ecd, 499, /* ọ Ọ */
+ 0x1ecf, 499, /* ỏ Ỏ */
+ 0x1ed1, 499, /* ố Ố */
+ 0x1ed3, 499, /* ồ Ồ */
+ 0x1ed5, 499, /* ổ Ổ */
+ 0x1ed7, 499, /* ỗ Ỗ */
+ 0x1ed9, 499, /* ộ Ộ */
+ 0x1edb, 499, /* ớ Ớ */
+ 0x1edd, 499, /* ờ Ờ */
+ 0x1edf, 499, /* ở Ở */
+ 0x1ee1, 499, /* ỡ Ỡ */
+ 0x1ee3, 499, /* ợ Ợ */
+ 0x1ee5, 499, /* ụ Ụ */
+ 0x1ee7, 499, /* ủ Ủ */
+ 0x1ee9, 499, /* ứ Ứ */
+ 0x1eeb, 499, /* ừ Ừ */
+ 0x1eed, 499, /* ử Ử */
+ 0x1eef, 499, /* ữ Ữ */
+ 0x1ef1, 499, /* ự Ự */
+ 0x1ef3, 499, /* ỳ Ỳ */
+ 0x1ef5, 499, /* ỵ Ỵ */
+ 0x1ef7, 499, /* ỷ Ỷ */
+ 0x1ef9, 499, /* ỹ Ỹ */
+ 0x1f51, 508, /* ὑ Ὑ */
+ 0x1f53, 508, /* ὓ Ὓ */
+ 0x1f55, 508, /* ὕ Ὕ */
+ 0x1f57, 508, /* ὗ Ὗ */
+ 0x1fb3, 509, /* ᾳ ᾼ */
+ 0x1fc3, 509, /* ῃ ῌ */
+ 0x1fe5, 507, /* ῥ Ῥ */
+ 0x1ff3, 509, /* ῳ ῼ */
+};
+
+/*
+ * upper case ranges
+ * 3rd col is conversion excess 500
+ */
+static
+Rune __tolower2[] =
+{
+ 0x0041, 0x005a, 532, /* A-Z a-z */
+ 0x00c0, 0x00d6, 532, /* À-Ö à-ö */
+ 0x00d8, 0x00de, 532, /* Ø-Þ ø-þ */
+ 0x0189, 0x018a, 705, /* Ɖ-Ɗ ɖ-ɗ */
+ 0x018e, 0x018f, 702, /* Ǝ-Ə ɘ-ə */
+ 0x01b1, 0x01b2, 717, /* Ʊ-Ʋ ʊ-ʋ */
+ 0x0388, 0x038a, 537, /* Έ-Ί έ-ί */
+ 0x038e, 0x038f, 563, /* Ύ-Ώ ύ-ώ */
+ 0x0391, 0x03a1, 532, /* Α-Ρ α-ρ */
+ 0x03a3, 0x03ab, 532, /* Σ-Ϋ σ-ϋ */
+ 0x0401, 0x040c, 580, /* Ё-Ќ ё-ќ */
+ 0x040e, 0x040f, 580, /* Ў-Џ ў-џ */
+ 0x0410, 0x042f, 532, /* А-Я а-я */
+ 0x0531, 0x0556, 548, /* Ա-Ֆ ա-ֆ */
+ 0x10a0, 0x10c5, 548, /* Ⴀ-Ⴥ ა-ჵ */
+ 0x1f08, 0x1f0f, 492, /* Ἀ-Ἇ ἀ-ἇ */
+ 0x1f18, 0x1f1d, 492, /* Ἐ-Ἕ ἐ-ἕ */
+ 0x1f28, 0x1f2f, 492, /* Ἠ-Ἧ ἠ-ἧ */
+ 0x1f38, 0x1f3f, 492, /* Ἰ-Ἷ ἰ-ἷ */
+ 0x1f48, 0x1f4d, 492, /* Ὀ-Ὅ ὀ-ὅ */
+ 0x1f68, 0x1f6f, 492, /* Ὠ-Ὧ ὠ-ὧ */
+ 0x1f88, 0x1f8f, 492, /* ᾈ-ᾏ ᾀ-ᾇ */
+ 0x1f98, 0x1f9f, 492, /* ᾘ-ᾟ ᾐ-ᾗ */
+ 0x1fa8, 0x1faf, 492, /* ᾨ-ᾯ ᾠ-ᾧ */
+ 0x1fb8, 0x1fb9, 492, /* Ᾰ-Ᾱ ᾰ-ᾱ */
+ 0x1fba, 0x1fbb, 426, /* Ὰ-Ά ὰ-ά */
+ 0x1fc8, 0x1fcb, 414, /* Ὲ-Ή ὲ-ή */
+ 0x1fd8, 0x1fd9, 492, /* Ῐ-Ῑ ῐ-ῑ */
+ 0x1fda, 0x1fdb, 400, /* Ὶ-Ί ὶ-ί */
+ 0x1fe8, 0x1fe9, 492, /* Ῠ-Ῡ ῠ-ῡ */
+ 0x1fea, 0x1feb, 388, /* Ὺ-Ύ ὺ-ύ */
+ 0x1ff8, 0x1ff9, 372, /* Ὸ-Ό ὸ-ό */
+ 0x1ffa, 0x1ffb, 374, /* Ὼ-Ώ ὼ-ώ */
+ 0x2160, 0x216f, 516, /* Ⅰ-Ⅿ ⅰ-ⅿ */
+ 0x24b6, 0x24cf, 526, /* Ⓐ-Ⓩ ⓐ-ⓩ */
+ 0xff21, 0xff3a, 532, /* A-Z a-z */
+};
+
+/*
+ * upper case singlets
+ * 2nd col is conversion excess 500
+ */
+static
+Rune __tolower1[] =
+{
+ 0x0100, 501, /* Ā ā */
+ 0x0102, 501, /* Ă ă */
+ 0x0104, 501, /* Ą ą */
+ 0x0106, 501, /* Ć ć */
+ 0x0108, 501, /* Ĉ ĉ */
+ 0x010a, 501, /* Ċ ċ */
+ 0x010c, 501, /* Č č */
+ 0x010e, 501, /* Ď ď */
+ 0x0110, 501, /* Đ đ */
+ 0x0112, 501, /* Ē ē */
+ 0x0114, 501, /* Ĕ ĕ */
+ 0x0116, 501, /* Ė ė */
+ 0x0118, 501, /* Ę ę */
+ 0x011a, 501, /* Ě ě */
+ 0x011c, 501, /* Ĝ ĝ */
+ 0x011e, 501, /* Ğ ğ */
+ 0x0120, 501, /* Ġ ġ */
+ 0x0122, 501, /* Ģ ģ */
+ 0x0124, 501, /* Ĥ ĥ */
+ 0x0126, 501, /* Ħ ħ */
+ 0x0128, 501, /* Ĩ ĩ */
+ 0x012a, 501, /* Ī ī */
+ 0x012c, 501, /* Ĭ ĭ */
+ 0x012e, 501, /* Į į */
+ 0x0130, 301, /* İ i */
+ 0x0132, 501, /* IJ ij */
+ 0x0134, 501, /* Ĵ ĵ */
+ 0x0136, 501, /* Ķ ķ */
+ 0x0139, 501, /* Ĺ ĺ */
+ 0x013b, 501, /* Ļ ļ */
+ 0x013d, 501, /* Ľ ľ */
+ 0x013f, 501, /* Ŀ ŀ */
+ 0x0141, 501, /* Ł ł */
+ 0x0143, 501, /* Ń ń */
+ 0x0145, 501, /* Ņ ņ */
+ 0x0147, 501, /* Ň ň */
+ 0x014a, 501, /* Ŋ ŋ */
+ 0x014c, 501, /* Ō ō */
+ 0x014e, 501, /* Ŏ ŏ */
+ 0x0150, 501, /* Ő ő */
+ 0x0152, 501, /* Œ œ */
+ 0x0154, 501, /* Ŕ ŕ */
+ 0x0156, 501, /* Ŗ ŗ */
+ 0x0158, 501, /* Ř ř */
+ 0x015a, 501, /* Ś ś */
+ 0x015c, 501, /* Ŝ ŝ */
+ 0x015e, 501, /* Ş ş */
+ 0x0160, 501, /* Š š */
+ 0x0162, 501, /* Ţ ţ */
+ 0x0164, 501, /* Ť ť */
+ 0x0166, 501, /* Ŧ ŧ */
+ 0x0168, 501, /* Ũ ũ */
+ 0x016a, 501, /* Ū ū */
+ 0x016c, 501, /* Ŭ ŭ */
+ 0x016e, 501, /* Ů ů */
+ 0x0170, 501, /* Ű ű */
+ 0x0172, 501, /* Ų ų */
+ 0x0174, 501, /* Ŵ ŵ */
+ 0x0176, 501, /* Ŷ ŷ */
+ 0x0178, 379, /* Ÿ ÿ */
+ 0x0179, 501, /* Ź ź */
+ 0x017b, 501, /* Ż ż */
+ 0x017d, 501, /* Ž ž */
+ 0x0181, 710, /* Ɓ ɓ */
+ 0x0182, 501, /* Ƃ ƃ */
+ 0x0184, 501, /* Ƅ ƅ */
+ 0x0186, 706, /* Ɔ ɔ */
+ 0x0187, 501, /* Ƈ ƈ */
+ 0x018b, 501, /* Ƌ ƌ */
+ 0x0190, 703, /* Ɛ ɛ */
+ 0x0191, 501, /* Ƒ ƒ */
+ 0x0193, 705, /* Ɠ ɠ */
+ 0x0194, 707, /* Ɣ ɣ */
+ 0x0196, 711, /* Ɩ ɩ */
+ 0x0197, 709, /* Ɨ ɨ */
+ 0x0198, 501, /* Ƙ ƙ */
+ 0x019c, 711, /* Ɯ ɯ */
+ 0x019d, 713, /* Ɲ ɲ */
+ 0x01a0, 501, /* Ơ ơ */
+ 0x01a2, 501, /* Ƣ ƣ */
+ 0x01a4, 501, /* Ƥ ƥ */
+ 0x01a7, 501, /* Ƨ ƨ */
+ 0x01a9, 718, /* Ʃ ʃ */
+ 0x01ac, 501, /* Ƭ ƭ */
+ 0x01ae, 718, /* Ʈ ʈ */
+ 0x01af, 501, /* Ư ư */
+ 0x01b3, 501, /* Ƴ ƴ */
+ 0x01b5, 501, /* Ƶ ƶ */
+ 0x01b7, 719, /* Ʒ ʒ */
+ 0x01b8, 501, /* Ƹ ƹ */
+ 0x01bc, 501, /* Ƽ ƽ */
+ 0x01c4, 502, /* DŽ dž */
+ 0x01c5, 501, /* Dž dž */
+ 0x01c7, 502, /* LJ lj */
+ 0x01c8, 501, /* Lj lj */
+ 0x01ca, 502, /* NJ nj */
+ 0x01cb, 501, /* Nj nj */
+ 0x01cd, 501, /* Ǎ ǎ */
+ 0x01cf, 501, /* Ǐ ǐ */
+ 0x01d1, 501, /* Ǒ ǒ */
+ 0x01d3, 501, /* Ǔ ǔ */
+ 0x01d5, 501, /* Ǖ ǖ */
+ 0x01d7, 501, /* Ǘ ǘ */
+ 0x01d9, 501, /* Ǚ ǚ */
+ 0x01db, 501, /* Ǜ ǜ */
+ 0x01de, 501, /* Ǟ ǟ */
+ 0x01e0, 501, /* Ǡ ǡ */
+ 0x01e2, 501, /* Ǣ ǣ */
+ 0x01e4, 501, /* Ǥ ǥ */
+ 0x01e6, 501, /* Ǧ ǧ */
+ 0x01e8, 501, /* Ǩ ǩ */
+ 0x01ea, 501, /* Ǫ ǫ */
+ 0x01ec, 501, /* Ǭ ǭ */
+ 0x01ee, 501, /* Ǯ ǯ */
+ 0x01f1, 502, /* DZ dz */
+ 0x01f2, 501, /* Dz dz */
+ 0x01f4, 501, /* Ǵ ǵ */
+ 0x01fa, 501, /* Ǻ ǻ */
+ 0x01fc, 501, /* Ǽ ǽ */
+ 0x01fe, 501, /* Ǿ ǿ */
+ 0x0200, 501, /* Ȁ ȁ */
+ 0x0202, 501, /* Ȃ ȃ */
+ 0x0204, 501, /* Ȅ ȅ */
+ 0x0206, 501, /* Ȇ ȇ */
+ 0x0208, 501, /* Ȉ ȉ */
+ 0x020a, 501, /* Ȋ ȋ */
+ 0x020c, 501, /* Ȍ ȍ */
+ 0x020e, 501, /* Ȏ ȏ */
+ 0x0210, 501, /* Ȑ ȑ */
+ 0x0212, 501, /* Ȓ ȓ */
+ 0x0214, 501, /* Ȕ ȕ */
+ 0x0216, 501, /* Ȗ ȗ */
+ 0x0386, 538, /* Ά ά */
+ 0x038c, 564, /* Ό ό */
+ 0x03e2, 501, /* Ϣ ϣ */
+ 0x03e4, 501, /* Ϥ ϥ */
+ 0x03e6, 501, /* Ϧ ϧ */
+ 0x03e8, 501, /* Ϩ ϩ */
+ 0x03ea, 501, /* Ϫ ϫ */
+ 0x03ec, 501, /* Ϭ ϭ */
+ 0x03ee, 501, /* Ϯ ϯ */
+ 0x0460, 501, /* Ѡ ѡ */
+ 0x0462, 501, /* Ѣ ѣ */
+ 0x0464, 501, /* Ѥ ѥ */
+ 0x0466, 501, /* Ѧ ѧ */
+ 0x0468, 501, /* Ѩ ѩ */
+ 0x046a, 501, /* Ѫ ѫ */
+ 0x046c, 501, /* Ѭ ѭ */
+ 0x046e, 501, /* Ѯ ѯ */
+ 0x0470, 501, /* Ѱ ѱ */
+ 0x0472, 501, /* Ѳ ѳ */
+ 0x0474, 501, /* Ѵ ѵ */
+ 0x0476, 501, /* Ѷ ѷ */
+ 0x0478, 501, /* Ѹ ѹ */
+ 0x047a, 501, /* Ѻ ѻ */
+ 0x047c, 501, /* Ѽ ѽ */
+ 0x047e, 501, /* Ѿ ѿ */
+ 0x0480, 501, /* Ҁ ҁ */
+ 0x0490, 501, /* Ґ ґ */
+ 0x0492, 501, /* Ғ ғ */
+ 0x0494, 501, /* Ҕ ҕ */
+ 0x0496, 501, /* Җ җ */
+ 0x0498, 501, /* Ҙ ҙ */
+ 0x049a, 501, /* Қ қ */
+ 0x049c, 501, /* Ҝ ҝ */
+ 0x049e, 501, /* Ҟ ҟ */
+ 0x04a0, 501, /* Ҡ ҡ */
+ 0x04a2, 501, /* Ң ң */
+ 0x04a4, 501, /* Ҥ ҥ */
+ 0x04a6, 501, /* Ҧ ҧ */
+ 0x04a8, 501, /* Ҩ ҩ */
+ 0x04aa, 501, /* Ҫ ҫ */
+ 0x04ac, 501, /* Ҭ ҭ */
+ 0x04ae, 501, /* Ү ү */
+ 0x04b0, 501, /* Ұ ұ */
+ 0x04b2, 501, /* Ҳ ҳ */
+ 0x04b4, 501, /* Ҵ ҵ */
+ 0x04b6, 501, /* Ҷ ҷ */
+ 0x04b8, 501, /* Ҹ ҹ */
+ 0x04ba, 501, /* Һ һ */
+ 0x04bc, 501, /* Ҽ ҽ */
+ 0x04be, 501, /* Ҿ ҿ */
+ 0x04c1, 501, /* Ӂ ӂ */
+ 0x04c3, 501, /* Ӄ ӄ */
+ 0x04c7, 501, /* Ӈ ӈ */
+ 0x04cb, 501, /* Ӌ ӌ */
+ 0x04d0, 501, /* Ӑ ӑ */
+ 0x04d2, 501, /* Ӓ ӓ */
+ 0x04d4, 501, /* Ӕ ӕ */
+ 0x04d6, 501, /* Ӗ ӗ */
+ 0x04d8, 501, /* Ә ә */
+ 0x04da, 501, /* Ӛ ӛ */
+ 0x04dc, 501, /* Ӝ ӝ */
+ 0x04de, 501, /* Ӟ ӟ */
+ 0x04e0, 501, /* Ӡ ӡ */
+ 0x04e2, 501, /* Ӣ ӣ */
+ 0x04e4, 501, /* Ӥ ӥ */
+ 0x04e6, 501, /* Ӧ ӧ */
+ 0x04e8, 501, /* Ө ө */
+ 0x04ea, 501, /* Ӫ ӫ */
+ 0x04ee, 501, /* Ӯ ӯ */
+ 0x04f0, 501, /* Ӱ ӱ */
+ 0x04f2, 501, /* Ӳ ӳ */
+ 0x04f4, 501, /* Ӵ ӵ */
+ 0x04f8, 501, /* Ӹ ӹ */
+ 0x1e00, 501, /* Ḁ ḁ */
+ 0x1e02, 501, /* Ḃ ḃ */
+ 0x1e04, 501, /* Ḅ ḅ */
+ 0x1e06, 501, /* Ḇ ḇ */
+ 0x1e08, 501, /* Ḉ ḉ */
+ 0x1e0a, 501, /* Ḋ ḋ */
+ 0x1e0c, 501, /* Ḍ ḍ */
+ 0x1e0e, 501, /* Ḏ ḏ */
+ 0x1e10, 501, /* Ḑ ḑ */
+ 0x1e12, 501, /* Ḓ ḓ */
+ 0x1e14, 501, /* Ḕ ḕ */
+ 0x1e16, 501, /* Ḗ ḗ */
+ 0x1e18, 501, /* Ḙ ḙ */
+ 0x1e1a, 501, /* Ḛ ḛ */
+ 0x1e1c, 501, /* Ḝ ḝ */
+ 0x1e1e, 501, /* Ḟ ḟ */
+ 0x1e20, 501, /* Ḡ ḡ */
+ 0x1e22, 501, /* Ḣ ḣ */
+ 0x1e24, 501, /* Ḥ ḥ */
+ 0x1e26, 501, /* Ḧ ḧ */
+ 0x1e28, 501, /* Ḩ ḩ */
+ 0x1e2a, 501, /* Ḫ ḫ */
+ 0x1e2c, 501, /* Ḭ ḭ */
+ 0x1e2e, 501, /* Ḯ ḯ */
+ 0x1e30, 501, /* Ḱ ḱ */
+ 0x1e32, 501, /* Ḳ ḳ */
+ 0x1e34, 501, /* Ḵ ḵ */
+ 0x1e36, 501, /* Ḷ ḷ */
+ 0x1e38, 501, /* Ḹ ḹ */
+ 0x1e3a, 501, /* Ḻ ḻ */
+ 0x1e3c, 501, /* Ḽ ḽ */
+ 0x1e3e, 501, /* Ḿ ḿ */
+ 0x1e40, 501, /* Ṁ ṁ */
+ 0x1e42, 501, /* Ṃ ṃ */
+ 0x1e44, 501, /* Ṅ ṅ */
+ 0x1e46, 501, /* Ṇ ṇ */
+ 0x1e48, 501, /* Ṉ ṉ */
+ 0x1e4a, 501, /* Ṋ ṋ */
+ 0x1e4c, 501, /* Ṍ ṍ */
+ 0x1e4e, 501, /* Ṏ ṏ */
+ 0x1e50, 501, /* Ṑ ṑ */
+ 0x1e52, 501, /* Ṓ ṓ */
+ 0x1e54, 501, /* Ṕ ṕ */
+ 0x1e56, 501, /* Ṗ ṗ */
+ 0x1e58, 501, /* Ṙ ṙ */
+ 0x1e5a, 501, /* Ṛ ṛ */
+ 0x1e5c, 501, /* Ṝ ṝ */
+ 0x1e5e, 501, /* Ṟ ṟ */
+ 0x1e60, 501, /* Ṡ ṡ */
+ 0x1e62, 501, /* Ṣ ṣ */
+ 0x1e64, 501, /* Ṥ ṥ */
+ 0x1e66, 501, /* Ṧ ṧ */
+ 0x1e68, 501, /* Ṩ ṩ */
+ 0x1e6a, 501, /* Ṫ ṫ */
+ 0x1e6c, 501, /* Ṭ ṭ */
+ 0x1e6e, 501, /* Ṯ ṯ */
+ 0x1e70, 501, /* Ṱ ṱ */
+ 0x1e72, 501, /* Ṳ ṳ */
+ 0x1e74, 501, /* Ṵ ṵ */
+ 0x1e76, 501, /* Ṷ ṷ */
+ 0x1e78, 501, /* Ṹ ṹ */
+ 0x1e7a, 501, /* Ṻ ṻ */
+ 0x1e7c, 501, /* Ṽ ṽ */
+ 0x1e7e, 501, /* Ṿ ṿ */
+ 0x1e80, 501, /* Ẁ ẁ */
+ 0x1e82, 501, /* Ẃ ẃ */
+ 0x1e84, 501, /* Ẅ ẅ */
+ 0x1e86, 501, /* Ẇ ẇ */
+ 0x1e88, 501, /* Ẉ ẉ */
+ 0x1e8a, 501, /* Ẋ ẋ */
+ 0x1e8c, 501, /* Ẍ ẍ */
+ 0x1e8e, 501, /* Ẏ ẏ */
+ 0x1e90, 501, /* Ẑ ẑ */
+ 0x1e92, 501, /* Ẓ ẓ */
+ 0x1e94, 501, /* Ẕ ẕ */
+ 0x1ea0, 501, /* Ạ ạ */
+ 0x1ea2, 501, /* Ả ả */
+ 0x1ea4, 501, /* Ấ ấ */
+ 0x1ea6, 501, /* Ầ ầ */
+ 0x1ea8, 501, /* Ẩ ẩ */
+ 0x1eaa, 501, /* Ẫ ẫ */
+ 0x1eac, 501, /* Ậ ậ */
+ 0x1eae, 501, /* Ắ ắ */
+ 0x1eb0, 501, /* Ằ ằ */
+ 0x1eb2, 501, /* Ẳ ẳ */
+ 0x1eb4, 501, /* Ẵ ẵ */
+ 0x1eb6, 501, /* Ặ ặ */
+ 0x1eb8, 501, /* Ẹ ẹ */
+ 0x1eba, 501, /* Ẻ ẻ */
+ 0x1ebc, 501, /* Ẽ ẽ */
+ 0x1ebe, 501, /* Ế ế */
+ 0x1ec0, 501, /* Ề ề */
+ 0x1ec2, 501, /* Ể ể */
+ 0x1ec4, 501, /* Ễ ễ */
+ 0x1ec6, 501, /* Ệ ệ */
+ 0x1ec8, 501, /* Ỉ ỉ */
+ 0x1eca, 501, /* Ị ị */
+ 0x1ecc, 501, /* Ọ ọ */
+ 0x1ece, 501, /* Ỏ ỏ */
+ 0x1ed0, 501, /* Ố ố */
+ 0x1ed2, 501, /* Ồ ồ */
+ 0x1ed4, 501, /* Ổ ổ */
+ 0x1ed6, 501, /* Ỗ ỗ */
+ 0x1ed8, 501, /* Ộ ộ */
+ 0x1eda, 501, /* Ớ ớ */
+ 0x1edc, 501, /* Ờ ờ */
+ 0x1ede, 501, /* Ở ở */
+ 0x1ee0, 501, /* Ỡ ỡ */
+ 0x1ee2, 501, /* Ợ ợ */
+ 0x1ee4, 501, /* Ụ ụ */
+ 0x1ee6, 501, /* Ủ ủ */
+ 0x1ee8, 501, /* Ứ ứ */
+ 0x1eea, 501, /* Ừ ừ */
+ 0x1eec, 501, /* Ử ử */
+ 0x1eee, 501, /* Ữ ữ */
+ 0x1ef0, 501, /* Ự ự */
+ 0x1ef2, 501, /* Ỳ ỳ */
+ 0x1ef4, 501, /* Ỵ ỵ */
+ 0x1ef6, 501, /* Ỷ ỷ */
+ 0x1ef8, 501, /* Ỹ ỹ */
+ 0x1f59, 492, /* Ὑ ὑ */
+ 0x1f5b, 492, /* Ὓ ὓ */
+ 0x1f5d, 492, /* Ὕ ὕ */
+ 0x1f5f, 492, /* Ὗ ὗ */
+ 0x1fbc, 491, /* ᾼ ᾳ */
+ 0x1fcc, 491, /* ῌ ῃ */
+ 0x1fec, 493, /* Ῥ ῥ */
+ 0x1ffc, 491, /* ῼ ῳ */
+};
+
+/*
+ * title characters are those between
+ * upper and lower case. ie DZ Dz dz
+ */
+static
+Rune __totitle1[] =
+{
+ 0x01c4, 501, /* DŽ Dž */
+ 0x01c6, 499, /* dž Dž */
+ 0x01c7, 501, /* LJ Lj */
+ 0x01c9, 499, /* lj Lj */
+ 0x01ca, 501, /* NJ Nj */
+ 0x01cc, 499, /* nj Nj */
+ 0x01f1, 501, /* DZ Dz */
+ 0x01f3, 499, /* dz Dz */
+};
+
+static Rune*
+bsearch(Rune c, Rune *t, int n, int ne)
+{
+ Rune *p;
+ int m;
+
+ while(n > 1) {
+ m = n/2;
+ p = t + m*ne;
+ if(c >= p[0]) {
+ t = p;
+ n = n-m;
+ } else
+ n = m;
+ }
+ if(n && c >= t[0])
+ return t;
+ return 0;
+}
+
+Rune
+tolowerrune(Rune c)
+{
+ Rune *p;
+
+ p = bsearch(c, __tolower2, nelem(__tolower2)/3, 3);
+ if(p && c >= p[0] && c <= p[1])
+ return c + p[2] - 500;
+ p = bsearch(c, __tolower1, nelem(__tolower1)/2, 2);
+ if(p && c == p[0])
+ return c + p[1] - 500;
+ return c;
+}
+
+Rune
+toupperrune(Rune c)
+{
+ Rune *p;
+
+ p = bsearch(c, __toupper2, nelem(__toupper2)/3, 3);
+ if(p && c >= p[0] && c <= p[1])
+ return c + p[2] - 500;
+ p = bsearch(c, __toupper1, nelem(__toupper1)/2, 2);
+ if(p && c == p[0])
+ return c + p[1] - 500;
+ return c;
+}
+
+Rune
+totitlerune(Rune c)
+{
+ Rune *p;
+
+ p = bsearch(c, __totitle1, nelem(__totitle1)/2, 2);
+ if(p && c == p[0])
+ return c + p[1] - 500;
+ return c;
+}
+
+int
+islowerrune(Rune c)
+{
+ Rune *p;
+
+ p = bsearch(c, __toupper2, nelem(__toupper2)/3, 3);
+ if(p && c >= p[0] && c <= p[1])
+ return 1;
+ p = bsearch(c, __toupper1, nelem(__toupper1)/2, 2);
+ if(p && c == p[0])
+ return 1;
+ return 0;
+}
+
+int
+isupperrune(Rune c)
+{
+ Rune *p;
+
+ p = bsearch(c, __tolower2, nelem(__tolower2)/3, 3);
+ if(p && c >= p[0] && c <= p[1])
+ return 1;
+ p = bsearch(c, __tolower1, nelem(__tolower1)/2, 2);
+ if(p && c == p[0])
+ return 1;
+ return 0;
+}
+
+int
+isalpharune(Rune c)
+{
+ Rune *p;
+
+ if(isupperrune(c) || islowerrune(c))
+ return 1;
+ p = bsearch(c, __alpha2, nelem(__alpha2)/2, 2);
+ if(p && c >= p[0] && c <= p[1])
+ return 1;
+ p = bsearch(c, __alpha1, nelem(__alpha1), 1);
+ if(p && c == p[0])
+ return 1;
+ return 0;
+}
+
+int
+istitlerune(Rune c)
+{
+ return isupperrune(c) && islowerrune(c);
+}
+
+int
+isspacerune(Rune c)
+{
+ Rune *p;
+
+ p = bsearch(c, __space2, nelem(__space2)/2, 2);
+ if(p && c >= p[0] && c <= p[1])
+ return 1;
+ return 0;
+}
diff --git a/libutf/utf.7 b/libutf/utf.7
new file mode 100644
index 0000000..13eea25
--- /dev/null
+++ b/libutf/utf.7
@@ -0,0 +1,99 @@
+.deEX
+.ift .ft5
+.nf
+..
+.deEE
+.ft1
+.fi
+..
+.TH UTF 7
+.SH NAME
+UTF, Unicode, ASCII, rune \- character set and format
+.SH DESCRIPTION
+The Plan 9 character set and representation are
+based on the Unicode Standard and on the ISO multibyte
+.SM UTF-8
+encoding (Universal Character
+Set Transformation Format, 8 bits wide).
+The Unicode Standard represents its characters in 16
+bits;
+.SM UTF-8
+represents such
+values in an 8-bit byte stream.
+Throughout this manual,
+.SM UTF-8
+is shortened to
+.SM UTF.
+.PP
+In Plan 9, a
+.I rune
+is a 16-bit quantity representing a Unicode character.
+Internally, programs may store characters as runes.
+However, any external manifestation of textual information,
+in files or at the interface between programs, uses a
+machine-independent, byte-stream encoding called
+.SM UTF.
+.PP
+.SM UTF
+is designed so the 7-bit
+.SM ASCII
+set (values hexadecimal 00 to 7F),
+appear only as themselves
+in the encoding.
+Runes with values above 7F appear as sequences of two or more
+bytes with values only from 80 to FF.
+.PP
+The
+.SM UTF
+encoding of the Unicode Standard is backward compatible with
+.SM ASCII\c
+:
+programs presented only with
+.SM ASCII
+work on Plan 9
+even if not written to deal with
+.SM UTF,
+as do
+programs that deal with uninterpreted byte streams.
+However, programs that perform semantic processing on
+.SM ASCII
+graphic
+characters must convert from
+.SM UTF
+to runes
+in order to work properly with non-\c
+.SM ASCII
+input.
+See
+.IR rune (3).
+.PP
+Letting numbers be binary,
+a rune x is converted to a multibyte
+.SM UTF
+sequence
+as follows:
+.PP
+01. x in [00000000.0bbbbbbb] → 0bbbbbbb
+.br
+10. x in [00000bbb.bbbbbbbb] → 110bbbbb, 10bbbbbb
+.br
+11. x in [bbbbbbbb.bbbbbbbb] → 1110bbbb, 10bbbbbb, 10bbbbbb
+.br
+.PP
+Conversion 01 provides a one-byte sequence that spans the
+.SM ASCII
+character set in a compatible way.
+Conversions 10 and 11 represent higher-valued characters
+as sequences of two or three bytes with the high bit set.
+Plan 9 does not support the 4, 5, and 6 byte sequences proposed by X-Open.
+When there are multiple ways to encode a value, for example rune 0,
+the shortest encoding is used.
+.PP
+In the inverse mapping,
+any sequence except those described above
+is incorrect and is converted to rune hexadecimal 0080.
+.SH "SEE ALSO"
+.IR ascii (1),
+.IR tcs (1),
+.IR rune (3),
+.IR "The Unicode Standard" .
diff --git a/libutf/utfecpy.c b/libutf/utfecpy.c
new file mode 100644
index 0000000..1a76d72
--- /dev/null
+++ b/libutf/utfecpy.c
@@ -0,0 +1,36 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "plan9.h"
+#include "utf.h"
+
+char*
+utfecpy(char *to, char *e, const char *from)
+{
+ char *end;
+
+ if(to >= e)
+ return to;
+ end = memccpy(to, from, '\0', e - to);
+ if(end == nil){
+ end = e-1;
+ while(end>to && (*--end&0xC0)==0x80)
+ ;
+ *end = '\0';
+ }else{
+ end--;
+ }
+ return end;
+}
diff --git a/libutf/utflen.c b/libutf/utflen.c
new file mode 100644
index 0000000..338cf6e
--- /dev/null
+++ b/libutf/utflen.c
@@ -0,0 +1,37 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "plan9.h"
+#include "utf.h"
+
+int
+utflen(const char *s)
+{
+ int c;
+ long n;
+ Rune rune;
+
+ n = 0;
+ for(;;) {
+ c = *(uchar*)s;
+ if(c < Runeself) {
+ if(c == 0)
+ return n;
+ s++;
+ } else
+ s += chartorune(&rune, s);
+ n++;
+ }
+}
diff --git a/libutf/utfnlen.c b/libutf/utfnlen.c
new file mode 100644
index 0000000..61985aa
--- /dev/null
+++ b/libutf/utfnlen.c
@@ -0,0 +1,41 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "plan9.h"
+#include "utf.h"
+
+int
+utfnlen(const char *s, long m)
+{
+ int c;
+ long n;
+ Rune rune;
+ const char *es;
+
+ es = s + m;
+ for(n = 0; s < es; n++) {
+ c = *(uchar*)s;
+ if(c < Runeself){
+ if(c == '\0')
+ break;
+ s++;
+ continue;
+ }
+ if(!fullrune(s, es-s))
+ break;
+ s += chartorune(&rune, s);
+ }
+ return n;
+}
diff --git a/libutf/utfrrune.c b/libutf/utfrrune.c
new file mode 100644
index 0000000..f571c1f
--- /dev/null
+++ b/libutf/utfrrune.c
@@ -0,0 +1,45 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "plan9.h"
+#include "utf.h"
+
+char*
+utfrrune(const char *s, long c)
+{
+ long c1;
+ Rune r;
+ const char *s1;
+
+ if(c < Runesync) /* not part of utf sequence */
+ return strrchr(s, c);
+
+ s1 = 0;
+ for(;;) {
+ c1 = *(uchar*)s;
+ if(c1 < Runeself) { /* one byte rune */
+ if(c1 == 0)
+ return (char*)s1;
+ if(c1 == c)
+ s1 = s;
+ s++;
+ continue;
+ }
+ c1 = chartorune(&r, s);
+ if(r == c)
+ s1 = s;
+ s += c1;
+ }
+}
diff --git a/libutf/utfrune.c b/libutf/utfrune.c
new file mode 100644
index 0000000..bcbf1a4
--- /dev/null
+++ b/libutf/utfrune.c
@@ -0,0 +1,44 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "plan9.h"
+#include "utf.h"
+
+char*
+utfrune(const char *s, long c)
+{
+ long c1;
+ Rune r;
+ int n;
+
+ if(c < Runesync) /* not part of utf sequence */
+ return strchr(s, c);
+
+ for(;;) {
+ c1 = *(uchar*)s;
+ if(c1 < Runeself) { /* one byte rune */
+ if(c1 == 0)
+ return 0;
+ if(c1 == c)
+ return (char*)s;
+ s++;
+ continue;
+ }
+ n = chartorune(&r, s);
+ if(r == c)
+ return (char*)s;
+ s += n;
+ }
+}
diff --git a/libutf/utfutf.c b/libutf/utfutf.c
new file mode 100644
index 0000000..a03d5c4
--- /dev/null
+++ b/libutf/utfutf.c
@@ -0,0 +1,41 @@
+/*
+ * The authors of this software are Rob Pike and Ken Thompson.
+ * Copyright (c) 2002 by Lucent Technologies.
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose without fee is hereby granted, provided that this entire notice
+ * is included in all copies of any software which is or includes a copy
+ * or modification of this software and in all copies of the supporting
+ * documentation for such software.
+ * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR LUCENT TECHNOLOGIES MAKE
+ * ANY REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+ * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+ */
+#include <stdarg.h>
+#include <string.h>
+#include "plan9.h"
+#include "utf.h"
+
+
+/*
+ * Return pointer to first occurrence of s2 in s1,
+ * 0 if none
+ */
+char*
+utfutf(const char *s1, const char *s2)
+{
+ const char *p;
+ long f, n1, n2;
+ Rune r;
+
+ n1 = chartorune(&r, s2);
+ f = r;
+ if(f <= Runesync) /* represents self */
+ return strstr(s1, s2);
+
+ n2 = strlen(s2);
+ for(p=s1; p=utfrune(p, f); p+=n1)
+ if(strncmp(p, s2, n2) == 0)
+ return (char*)p;
+ return 0;
+}
diff --git a/libwmii_hack/Makefile b/libwmii_hack/Makefile
new file mode 100644
index 0000000..ec1f0fb
--- /dev/null
+++ b/libwmii_hack/Makefile
@@ -0,0 +1,15 @@
+ROOT= ..
+include ${ROOT}/mk/hdr.mk
+
+hack.o hack.o_pic: util.c x11.c hack.h x11.h
+
+CFLAGS += $(INCX11)
+SOLDFLAGS += $(LIBX11)
+
+TARG = libwmii_hack
+OBJ = hack
+# util \
+# x11
+
+include ${ROOT}/mk/so.mk
+
diff --git a/libwmii_hack/hack.c b/libwmii_hack/hack.c
new file mode 100644
index 0000000..b282c52
--- /dev/null
+++ b/libwmii_hack/hack.c
@@ -0,0 +1,134 @@
+/* Copyright ©2008 Kris Maglione <fbsdaemon@gmail.com>
+ * See LICENSE file for license details.
+ */
+#include "hack.h"
+#include <dlfcn.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "util.c"
+#include "x11.c"
+
+enum {
+ Timeout = 10,
+};
+
+static void* xlib;
+
+static long transient;
+static Atom types[32];
+static long ntypes;
+static char** tags;
+static long pid;
+static long stime;
+static char hostname[256];
+static long nsec;
+
+typedef Window (*mapfn)(Display*, Window);
+
+static Window (*mapwindow)(Display*, Window);
+static Window (*mapraised)(Display*, Window);
+
+static void
+init(Display *d) { /* Hrm... assumes one display... */
+ char *toks[nelem(types)];
+ char *s, *p;
+ long n;
+ int i;
+
+ xlib = dlopen("libX11.so", RTLD_GLOBAL | RTLD_LAZY);
+ if(xlib == nil)
+ return;
+ mapwindow = (mapfn)(uintptr_t)dlsym(xlib, "XMapWindow");
+ mapraised = (mapfn)(uintptr_t)dlsym(xlib, "XMapRaised");
+
+ unsetenv("LD_PRELOAD");
+
+ if((s = getenv("WMII_HACK_TRANSIENT"))) {
+ if(getlong(s, &n))
+ transient = n;
+ unsetenv("WMII_HACK_TRANSIENT");
+ }
+ if((s = getenv("WMII_HACK_TYPE"))) {
+ s = strdup(s);
+ unsetenv("WMII_HACK_TYPE");
+
+ n = tokenize(toks, nelem(toks), s, ',');
+ for(i=0; i < n; i++) {
+ for(p=toks[i]; *p; p++)
+ if(*p >= 'a' && *p <= 'z')
+ *p += 'A' - 'a';
+ toks[i] = smprint("_NET_WM_WINDOW_TYPE_%s", toks[i]);
+ }
+ XInternAtoms(d, toks, n, false, types);
+ ntypes = n;
+ for(i=0; i < n; i++)
+ free(toks[i]);
+ free(s);
+ }
+ if((s = getenv("WMII_HACK_TAGS"))) {
+ s = strdup(s);
+ unsetenv("WMII_HACK_TAGS");
+
+ n = tokenize(toks, nelem(toks)-1, s, '+');
+ tags = strlistdup(toks, n);
+ free(s);
+ }
+ if((s = getenv("WMII_HACK_TIME"))) {
+ getlong(s, &stime);
+ unsetenv("WMII_HACK_TIME");
+ }
+
+ pid = getpid();
+ gethostname(hostname, sizeof hostname);
+}
+
+static void
+setprops(Display *d, Window w) {
+ long *l;
+
+ if(!xlib)
+ init(d);
+
+ if(getprop_long(d, w, "_NET_WM_PID", "CARDINAL", 0L, &l, 1L))
+ free(l);
+ else {
+ changeprop_long(d, w, "_NET_WM_PID", "CARDINAL", &pid, 1);
+ changeprop_string(d, w, "WM_CLIENT_MACHINE", hostname);
+ }
+
+ /* Kludge. */
+ if(nsec == 0)
+ nsec = time(0);
+ else if(time(0) > nsec + Timeout)
+ return;
+
+ if(transient)
+ changeprop_long(d, w, "WM_TRANSIENT_FOR", "WINDOW", &transient, 1);
+ if(ntypes)
+ changeprop_long(d, w, "_NET_WM_WINDOW_TYPE", "ATOM", (long*)types, ntypes);
+ if(tags)
+ changeprop_textlist(d, w, "_WMII_TAGS", "UTF8_STRING", tags);
+ if(stime)
+ changeprop_long(d, w, "_WMII_LAUNCH_TIME", "CARDINAL", &stime, 1);
+}
+
+int
+XMapWindow(Display *d, Window w) {
+
+ setprops(d, w);
+ return mapwindow(d, w);
+}
+
+int
+XMapRaised(Display *d, Window w) {
+
+ setprops(d, w);
+ return mapraised(d, w);
+}
+
diff --git a/libwmii_hack/hack.h b/libwmii_hack/hack.h
new file mode 100644
index 0000000..8622d98
--- /dev/null
+++ b/libwmii_hack/hack.h
@@ -0,0 +1,28 @@
+
+typedef unsigned long ulong;
+typedef unsigned int uint;
+typedef unsigned char uchar;
+
+#define _XOPEN_SOURCE 600
+#define IXP_P9_STRUCTS
+#define IXP_NO_P9_
+#include <assert.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <x11.h>
+#include <X11/Xlib.h>
+
+#define strdup my_strdup
+
+static int getlong(const char*, long*);
+static uint tokenize(char*[], uint, char*, char);
+static char* smprint(const char*, ...);
+static char* vsmprint(const char*, va_list);
+static char* strdup(const char*);
+
+#define nil ((void*)0)
+#define nelem(ary) (sizeof(ary) / sizeof(*ary))
+
diff --git a/libwmii_hack/util.c b/libwmii_hack/util.c
new file mode 100644
index 0000000..32988e4
--- /dev/null
+++ b/libwmii_hack/util.c
@@ -0,0 +1,101 @@
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+
+#define strbcmp(str, const) (strncmp((str), (const), sizeof(const)-1))
+static int
+getbase(const char **s) {
+ const char *p;
+
+ p = *s;
+ if(!strbcmp(p, "0x")) {
+ *s += 2;
+ return 16;
+ }
+ if(isdigit(p[0])) {
+ if(p[1] == 'r') {
+ *s += 2;
+ return p[0] - '0';
+ }
+ if(isdigit(p[1]) && p[2] == 'r') {
+ *s += 3;
+ return 10*(p[0]-'0') + (p[1]-'0');
+ }
+ }
+ if(p[0] == '0') {
+ *s += 1;
+ return 8;
+ }
+ return 10;
+}
+
+static int
+getlong(const char *s, long *ret) {
+ const char *end;
+ char *rend;
+ int base;
+
+ end = s+strlen(s);
+ base = getbase(&s);
+
+ *ret = strtol(s, &rend, base);
+ return (end == rend);
+}
+
+static uint
+tokenize(char *res[], uint reslen, char *str, char delim) {
+ char *s;
+ uint i;
+
+ i = 0;
+ s = str;
+ while(i < reslen && *s) {
+ while(*s == delim)
+ *(s++) = '\0';
+ if(*s)
+ res[i++] = s;
+ while(*s && *s != delim)
+ s++;
+ }
+ return i;
+}
+
+static char*
+vsmprint(const char *fmt, va_list ap) {
+ va_list al;
+ char *buf = "";
+ int n;
+
+ va_copy(al, ap);
+ n = vsnprintf(buf, 0, fmt, al);
+ va_end(al);
+
+ buf = malloc(++n);
+ if(buf)
+ vsnprintf(buf, n, fmt, ap);
+ return buf;
+}
+
+static char*
+smprint(const char *fmt, ...) {
+ va_list ap;
+ char *ret;
+
+ va_start(ap, fmt);
+ ret = vsmprint(fmt, ap);
+ va_end(ap);
+ return ret;
+}
+
+static char*
+strdup(const char *s) {
+ char *ret;
+ int len;
+
+ len = strlen(s)+1;
+ ret = malloc(len);
+ if(ret)
+ memcpy(ret, s, len);
+ return ret;
+}
+
diff --git a/libwmii_hack/x11.c b/libwmii_hack/x11.c
new file mode 100644
index 0000000..e3e097f
--- /dev/null
+++ b/libwmii_hack/x11.c
@@ -0,0 +1,212 @@
+/* Copyright ©2007 Kris Maglione <fbsdaemon@gmail.com>
+ * See LICENSE file for license details.
+ */
+#include <assert.h>
+
+/* Misc */
+static Atom
+xatom(Display *display, char *name) {
+ /* Blech. I don't trust Xlib's cacheing.
+ MapEnt *e;
+
+ e = hash_get(&amap, name, 1);
+ if(e->val == nil)
+ e->val = (void*)XInternAtom(display, name, False);
+ return (Atom)e->val;
+ */
+ return XInternAtom(display, name, False);
+}
+/* Properties */
+#if 0
+static void
+delproperty(Display *display, Window w, char *prop) {
+ XDeleteProperty(display, w, xatom(display, prop));
+}
+#endif
+
+static void
+changeproperty(Display *display, Window w, char *prop, char *type, int width, uchar data[], int n) {
+ XChangeProperty(display, w, xatom(display, prop), xatom(display, type), width, PropModeReplace, data, n);
+}
+
+static void
+changeprop_string(Display *display, Window w, char *prop, char *string) {
+ changeprop_char(display, w, prop, "UTF8_STRING", string, strlen(string));
+}
+
+static void
+changeprop_char(Display *display, Window w, char *prop, char *type, char data[], int len) {
+ changeproperty(display, w, prop, type, 8, (uchar*)data, len);
+}
+
+#if 0
+static void
+changeprop_short(Display *display, Window w, char *prop, char *type, short data[], int len) {
+ changeproperty(display, w, prop, type, 16, (uchar*)data, len);
+}
+#endif
+
+static void
+changeprop_long(Display *display, Window w, char *prop, char *type, long data[], int len) {
+ changeproperty(display, w, prop, type, 32, (uchar*)data, len);
+}
+
+static void
+changeprop_textlist(Display *display, Window w, char *prop, char *type, char *data[]) {
+ char **p, *s, *t;
+ int len, n;
+
+ len = 0;
+ for(p=data; *p; p++)
+ len += strlen(*p) + 1;
+ s = malloc(len);
+ if(s == nil)
+ return;
+ t = s;
+ for(p=data; *p; p++) {
+ n = strlen(*p) + 1;
+ memcpy(t, *p, n);
+ t += n;
+ }
+ changeprop_char(display, w, prop, type, s, len);
+ free(s);
+}
+
+#if 0
+static void
+freestringlist(char *list[]) {
+ XFreeStringList(list);
+}
+#endif
+
+static ulong
+getprop(Display *display, Window w, char *prop, char *type, Atom *actual, int *format, ulong offset, uchar **ret, ulong length) {
+ Atom typea;
+ ulong n, extra;
+ int status;
+
+ typea = (type ? xatom(display, type) : 0L);
+
+ status = XGetWindowProperty(display, w,
+ xatom(display, prop), offset, length, False /* delete */,
+ typea, actual, format, &n, &extra, ret);
+
+ if(status != Success) {
+ *ret = nil;
+ return 0;
+ }
+ if(n == 0) {
+ free(*ret);
+ *ret = nil;
+ }
+ return n;
+}
+
+#if 0
+static ulong
+getproperty(Display *display, Window w, char *prop, char *type, Atom *actual, ulong offset, uchar **ret, ulong length) {
+ int format;
+
+ return getprop(display, w, prop, type, actual, &format, offset, ret, length);
+}
+#endif
+
+static ulong
+getprop_long(Display *display, Window w, char *prop, char *type, ulong offset, long **ret, ulong length) {
+ Atom actual;
+ ulong n;
+ int format;
+
+ n = getprop(display, w, prop, type, &actual, &format, offset, (uchar**)ret, length);
+ if(n == 0 || format == 32 && xatom(display, type) == actual)
+ return n;
+ free(*ret);
+ *ret = 0;
+ return 0;
+}
+
+#ifdef notdef
+static char**
+strlistdup(char *list[], int n) {
+ char **p, *q;
+ int i, m;
+
+ for(i=0, m=0; i < n; i++)
+ m += strlen(list[i])+1;
+
+ p = malloc((n+1)*sizeof(char*) + m);
+ if(p == nil)
+ return nil;
+ q = (char*)&p[n+1];
+
+ for(i=0; i < n; i++) {
+ p[i] = q;
+ m = strlen(list[i])+1;
+ memcpy(q, list[i], m);
+ q += m;
+ }
+ p[n] = nil;
+ return p;
+}
+#endif
+
+static char**
+strlistdup(char *list[], int n) {
+ char **p, *q;
+ int i, m;
+
+ m = 0;
+ for(i=0; i < n; i++)
+ m += strlen(list[i]) + 1;
+
+ p = malloc((n+1) * sizeof(*p) + m);
+ q = (char*)&p[n+1];
+
+ for(i=0; i < n; i++) {
+ p[i] = q;
+ m = strlen(list[i]) + 1;
+ memcpy(q, list[i], m);
+ q += m;
+ }
+ p[n] = nil;
+ return p;
+}
+
+#if 0
+static int
+getprop_textlist(Display *display, Window w, char *name, char **ret[]) {
+ XTextProperty prop;
+ char **list;
+ int n;
+
+ n = 0;
+
+ XGetTextProperty(display, w, &prop, xatom(display, name));
+ if(prop.nitems > 0) {
+ if(Xutf8TextPropertyToTextList(display, &prop, &list, &n) == Success) {
+ *ret = strlistdup(list, n);
+ XFreeStringList(list);
+ }
+ XFree(prop.value);
+ }
+ return n;
+}
+#endif
+
+#if 0
+static char*
+getprop_string(Display *display, Window w, char *name) {
+ char **list, *str;
+ int n;
+
+ str = nil;
+
+ n = getprop_textlist(display, w, name, &list);
+ if(n > 0)
+ str = strdup(*list);
+ freestringlist(list);
+
+ return str;
+}
+#endif
+
diff --git a/libwmii_hack/x11.h b/libwmii_hack/x11.h
new file mode 100644
index 0000000..5a4d9c3
--- /dev/null
+++ b/libwmii_hack/x11.h
@@ -0,0 +1,18 @@
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+
+static void changeprop_char(Display*, Window, char*, char*, char[], int);
+static void changeprop_long(Display*, Window, char*, char*, long[], int);
+/* static void changeprop_short(Display*, Window, char*, char*, short[], int); */
+static void changeprop_string(Display*, Window, char*, char*);
+static void changeprop_textlist(Display*, Window, char*, char*, char*[]);
+static void changeproperty(Display*, Window, char*, char*, int width, uchar*, int);
+/* static void delproperty(Display*, Window, char*); */
+/* static void freestringlist(char**); */
+static ulong getprop_long(Display*, Window, char*, char*, ulong, long**, ulong);
+/* static char* getprop_string(Display*, Window, char*); */
+/* static int getprop_textlist(Display*, Window, char*, char**[]); */
+/* static ulong getproperty(Display*, Window, char*, char*, Atom*, ulong, uchar**, ulong); */
+static Atom xatom(Display*, char*);
+
diff --git a/man/Makefile b/man/Makefile
new file mode 100644
index 0000000..f64126a
--- /dev/null
+++ b/man/Makefile
@@ -0,0 +1,13 @@
+ROOT=..
+include ${ROOT}/mk/hdr.mk
+include ${ROOT}/mk/wmii.mk
+
+TARG = wmii.1 \
+ wmiir.1 \
+ wmii9menu.1\
+ wimenu.1
+
+$(TARG): Makefile $(ROOT)/mk/wmii.mk header.t2t
+
+include ${ROOT}/mk/man.mk
+
diff --git a/man/header.t2t b/man/header.t2t
new file mode 100644
index 0000000..edc8cb2
--- /dev/null
+++ b/man/header.t2t
@@ -0,0 +1,31 @@
+%!target: man
+%!encoding: UTF-8
+
+% Special formatting for certain constructs. They, unfortunately
+% have to be post-processed. The _italic_ hack is necessary for
+% italicising things like /_foo_/, which txt2tags will ignore.
+% The others need to work in ``` lines.
+%!postproc(man): (<.*?>) \\fI\1\\fR
+%!postproc(man): \b_(.*?)_ \\fI\1\\fR
+%!postproc(man): `(.*?)` \\fB\1\\fR
+%!postproc(man): (\[.*?\]) \\fI\1\\fR
+%!postproc(man): \+$ \n.P
+%!postproc(man): (\$[a-zA-Z_]+) \\fB\1\\fR
+%!postproc(man): (\${[a-zA-Z_]+)(.*?)} \\fB\1\\fR\2\\fB}\\fR
+
+%!postproc(html): (<.*?>) (:arg \1:)
+%!postproc(html): \b_(.*?)_ (:emph \1:)
+%!postproc(html): `(.*?)` (:code \1:)
+%!postproc(html): \+$ <br/>
+
+%!postproc(html) \(:(\w+)\s*(.*):\) <span class="\1">\2</span>
+
+% Well, it seems that txt2tags isn't particularly well suited
+% to troff output. These two hacks make multi-level definition
+% lists possible.
+%!postproc(man): ^\s*>>$ .RS 8
+%!postproc(man): ^\s*<<$ .RS -8
+
+%!postproc(html): ^\s*>>$
+%!postproc(html): ^\s*<<$
+
diff --git a/man/mkfile b/man/mkfile
new file mode 100644
index 0000000..37b8bb9
--- /dev/null
+++ b/man/mkfile
@@ -0,0 +1,9 @@
+TARG = `{bmake -VTARG}
+
+default:V: all
+
+all:V: $TARG
+
+%.1: %.tex
+ latex2man -M $stem.tex $stem.1
+
diff --git a/man/wimenu.1 b/man/wimenu.1
new file mode 100644
index 0000000..d4e62d2
--- /dev/null
+++ b/man/wimenu.1
@@ -0,0 +1,204 @@
+.TH "WIMENU" 1 "Oct, 2009" "wmii-@VERSION@"
+
+.SH NAME
+.P
+wimenu \- The wmii menu program
+
+.SH SYNOPSIS
+.P
+wimenu \fI[\-i]\fR \fI[\-h \fI<history file>\fR]\fR \fI[\-n \fI<history count>\fR]\fR \fI[\-p \fI<prompt>\fR]\fR
+.P
+wimenu \-v
+
+.SH DESCRIPTION
+.P
+\fBwimenu\fR is \fBwmii\fR's standard menu program. It's used
+extensively by \fBwmii\fR and related programs to prompt the user
+for input. The standard configuration uses it to launch
+programs, select views, and perform standard actions. It
+supports basic item completion and history searching.
+
+.SH BASIC ARGUMENTS
+.P
+Normal use of \fBwimenu\fR shouldn't require any arguments other than the
+following. More advanced options are documented below.
+
+.TP
+\-h \fI<history file>\fR
+Causes \fBwimenu\fR to read its command history from
+\fI<history file>\fR and to append its result to that file if
+\fI\-n\fR is given.
+.TP
+\-i
+Causes matching of completion items to be performed in a
+case insensitive manner.
+.TP
+\-n \fI<count>\fR
+Write at most \fI<count>\fR items back to the history file.
+The file is never modified unless this option is
+provided. Duplicates are filtered out within a 20 item
+sliding window before this limit is imposed.
+.TP
+\-p \fI<prompt>\fR
+The string \fI<prompt>\fR will be show before the input field
+when the menu is opened.
+
+
+.SH ADVANCED ARGUMENTS
+.TP
+\-a
+The address at which to connect to \fBwmii\fR.
+.TP
+\-K
+Prevents \fBwimenu\fR from initializing its default key
+bindings. WARNING: If you do this, be sure to bind a key
+with the Accept or Reject action, or you will have no way
+to exit \fBwimenu\fR.
+.TP
+\-k \fI<key file>\fR
+Key bindings will be read from \fI<key file>\fR. Bindings
+appear as:
+
+\fI<key>\fR \fI[action]\fR \fI[args]\fR
+
+where \fI<key>\fR is a key name, similar to the format used by
+wmii. For action and args, please refer to the default
+bindings, provided in the source distribution under
+cmd/menu/keys.txt, or use strings(1) on the \fBwimenu\fR
+executable (this level of customization is reserved for the
+determined).
+.TP
+\-s \fI<screen>\fR
+Suggests that the menu open on Xinerama screen \fI<screen>\fR.
+.TP
+\-S \fI<command separator>\fR
+
+.RS
+Causes each input item to be split at the first occurance of
+\fI<command sep>\fR. The text to the left of the separator is displayed
+as a menu option, and the text to the right is displayed when a
+selection is made.
+.RE
+
+.SH CUSTOM COMPLETION
+.P
+Custom, multipart completion data may be proveded by an
+external application. When the standard input is not a TTY,
+processing of a set of completions stops at every blank line.
+After the first new line or EOF, \fBwimenu\fR displays the first
+set of menu items, and waits for further input. The completion
+items may be replaced by writing out a new set, again followed
+by a new line. Every set following the first must begin with a
+line containing a single decimal number specifying where the
+new completion results are to be spliced into the input. When
+an item is selected, text from this position to the position
+of the caret is replaced.
+
+.SS ARGUMENTS
+.TP
+\-c
+Prints the contents of the input buffer each time the
+user inputs a character, as such:
+
+\fI<text before caret>\fR\en\fI<text after caret>\fR\en
+
+
+.SS EXAMPLE
+.P
+Let's assume that a script would like to provide a menu with
+completions first for a command name, then for arguments
+to that command. Given three commands and argument sets,
+
+.TP
+foo
+
+.RS
+1, 2, 3
+.RE
+.TP
+bar
+
+.RS
+4, 5, 6
+.RE
+.TP
+baz
+
+.RS
+7, 8, 9
+.RE
+
+.P
+the following script provides the appropriate completions:
+
+.nf
+ #!/bin/sh -f
+
+ rm fifo
+ mkfifo fifo
+
+ # Open wimenu with a fifo as its stdin
+ wimenu -c <fifo | awk '
+ BEGIN {
+ # Define the completion results
+ cmds = "foo\enbar\enbaz\en"
+ cmd\fI["foo"]\fR = "1\en2\en3\en"
+ cmd\fI["bar"]\fR = "4\en5\en6\en"
+ cmd\fI["baz"]\fR = "7\en8\en9\en"
+
+ # Print the first set of completions to wimenu’s fifo
+ fifo = "fifo"
+ print cmds >fifo; fflush(fifo)
+ }
+
+ # Store the last line we get and print it when done
+ { last = $0 }
+ END { print last }
+
+ # Push out a new set of completions
+ function update(str, opts) {
+ print length(str) >fifo # Print the length of the preceding string
+ print opts >fifo # and the options themself
+ fflush(fifo)
+ }
+
+ # Ensure correct argument count with trailing spaces
+ / $/ { $0 = $0 "#"; }
+
+ { # Process the input and provide the completions
+ if (NF == 1)
+ update("", cmds) # The first arg, command choices
+ else
+ update($1 " ", cmd\fI[$1]\fR) # The second arg, command arguments
+ # Skip the trailing part of the command
+ getline rest
+ }
+ \&'
+.fi
+
+
+.P
+In theory, this facility can be used for myriad purposes,
+including hijacking the programmable completion facilities of
+most shells.
+
+.SH ENVIRONMENT
+.TP
+\fB$WMII_ADDRESS\fR
+The address at which to connect to wmii.
+.TP
+\fB$NAMESPACE\fR
+The namespace directory to use if no address is
+provided.
+
+.SH SEE ALSO
+.P
+wmii(1), wmiir(1), wmii9menu(1), dmenu(1)
+
+.P
+\fI[1]\fR http://www.suckless.org/wiki/wmii/tips/9p_tips
+
+
+.\" man code generated by txt2tags 2.5 (http://txt2tags.sf.net)
+.\" cmdline: txt2tags -o- wimenu.man1
+
diff --git a/man/wimenu.man1 b/man/wimenu.man1
new file mode 100644
index 0000000..ce1c747
--- /dev/null
+++ b/man/wimenu.man1
@@ -0,0 +1,174 @@
+WIMENU
+wmii-@VERSION@
+Oct, 2009
+
+%!includeconf: header.t2t
+
+= NAME =
+
+wimenu - The wmii menu program
+
+= SYNOPSIS =
+
+wimenu [-i] [-h <history file>] [-n <history count>] [-p <prompt>] +
+wimenu -v
+
+= DESCRIPTION =
+
+`wimenu` is `wmii`'s standard menu program. It's used
+extensively by `wmii` and related programs to prompt the user
+for input. The standard configuration uses it to launch
+programs, select views, and perform standard actions. It
+supports basic item completion and history searching.
+
+= BASIC ARGUMENTS =
+
+Normal use of `wimenu` shouldn't require any arguments other than the
+following. More advanced options are documented below.
+
+: -h <history file>
+ Causes `wimenu` to read its command history from
+ <history file> and to append its result to that file if
+ _-n_ is given.
+: -i
+ Causes matching of completion items to be performed in a
+ case insensitive manner.
+: -n <count>
+ Write at most <count> items back to the history file.
+ The file is never modified unless this option is
+ provided. Duplicates are filtered out within a 20 item
+ sliding window before this limit is imposed.
+: -p <prompt>
+ The string <prompt> will be show before the input field
+ when the menu is opened.
+:
+
+= ADVANCED ARGUMENTS =
+
+: -a
+ The address at which to connect to `wmii`.
+: -K
+ Prevents `wimenu` from initializing its default key
+ bindings. WARNING: If you do this, be sure to bind a key
+ with the Accept or Reject action, or you will have no way
+ to exit `wimenu`.
+: -k <key file>
+ Key bindings will be read from <key file>. Bindings
+ appear as:
+
+ <key> [action] [args]
+
+ where <key> is a key name, similar to the format used by
+ wmii. For action and args, please refer to the default
+ bindings, provided in the source distribution under
+ cmd/menu/keys.txt, or use strings(1) on the `wimenu`
+ executable (this level of customization is reserved for the
+ determined).
+: -s <screen>
+ Suggests that the menu open on Xinerama screen <screen>.
+: -S <command separator>
+ Causes each input item to be split at the first occurance of
+ <command sep>. The text to the left of the separator is displayed
+ as a menu option, and the text to the right is displayed when a
+ selection is made.
+
+= CUSTOM COMPLETION =
+
+Custom, multipart completion data may be proveded by an
+external application. When the standard input is not a TTY,
+processing of a set of completions stops at every blank line.
+After the first new line or EOF, `wimenu` displays the first
+set of menu items, and waits for further input. The completion
+items may be replaced by writing out a new set, again followed
+by a new line. Every set following the first must begin with a
+line containing a single decimal number specifying where the
+new completion results are to be spliced into the input. When
+an item is selected, text from this position to the position
+of the caret is replaced.
+
+== ARGUMENTS ==
+
+: -c
+ Prints the contents of the input buffer each time the
+ user inputs a character, as such:
+
+ <text before caret>\n<text after caret>\n
+:
+
+== EXAMPLE ==
+
+Let's assume that a script would like to provide a menu with
+completions first for a command name, then for arguments
+to that command. Given three commands and argument sets,
+
+: foo
+ 1, 2, 3
+: bar
+ 4, 5, 6
+: baz
+ 7, 8, 9
+
+the following script provides the appropriate completions:
+
+```
+#!/bin/sh -f
+
+rm fifo
+mkfifo fifo
+
+# Open wimenu with a fifo as its stdin
+wimenu -c <fifo | awk '
+ BEGIN {
+ # Define the completion results
+ cmds = "foo\nbar\nbaz\n"
+ cmd["foo"] = "1\n2\n3\n"
+ cmd["bar"] = "4\n5\n6\n"
+ cmd["baz"] = "7\n8\n9\n"
+
+ # Print the first set of completions to wimenu’s fifo
+ fifo = "fifo"
+ print cmds >fifo; fflush(fifo)
+ }
+
+ # Store the last line we get and print it when done
+ { last = $0 }
+ END { print last }
+
+ # Push out a new set of completions
+ function update(str, opts) {
+ print length(str) >fifo # Print the length of the preceding string
+ print opts >fifo # and the options themself
+ fflush(fifo)
+ }
+
+ # Ensure correct argument count with trailing spaces
+ / $/ { $0 = $0 "#"; }
+
+ { # Process the input and provide the completions
+ if (NF == 1)
+ update("", cmds) # The first arg, command choices
+ else
+ update($1 " ", cmd[$1]) # The second arg, command arguments
+ # Skip the trailing part of the command
+ getline rest
+ }
+'
+```
+
+In theory, this facility can be used for myriad purposes,
+including hijacking the programmable completion facilities of
+most shells.
+
+= ENVIRONMENT =
+
+: $WMII_ADDRESS
+ The address at which to connect to wmii.
+: $NAMESPACE
+ The namespace directory to use if no address is
+ provided.
+:
+= SEE ALSO =
+wmii(1), wmiir(1), wmii9menu(1), dmenu(1)
+
+[1] http://www.suckless.org/wiki/wmii/tips/9p_tips
+
diff --git a/man/wmii.1 b/man/wmii.1
new file mode 100644
index 0000000..44ca951
--- /dev/null
+++ b/man/wmii.1
@@ -0,0 +1,575 @@
+.TH "WMII" 1 "Oct, 2009" "wmii-@VERSION@"
+
+.SH NAME
+.P
+wmii \- Window Manager Improved²
+
+.SH SYNOPSIS
+.P
+wmii \fI[\-a \fI<address>\fR]\fR \fI[\-r \fI<wmiirc>\fR]\fR
+.P
+wmii \-v
+
+.SH DESCRIPTION
+.SS Overview
+.P
+\fBwmii\fR is a dynamic window manager for X11. In contrast to
+static window management the user rarely has to think about how
+to organize windows, no matter what he is doing or how many
+applications are used at the same time. The window manager
+adapts to the current environment and fits to the needs of the
+user, rather than forcing him to use a preset, fixed layout and
+trying to shoehorn all windows and applications into it.
+
+.P
+\fBwmii\fR supports classic and tiled window management with
+extended keyboard and mouse control. The classic window
+management arranges windows in a floating layer in which windows
+can be moved and resized freely. The tiled window management is
+based on columns which split up the screen horizontally. Each
+column handles arbitrary windows and arranges them vertically in
+a non\-overlapping way. They can then be moved and resized
+between and within columns at will.
+
+.P
+\fBwmii\fR provides a virtual filesystem which represents the
+internal state similar to the procfs of Unix operating systems.
+Modifying this virtual filesystem results in changing the state
+of the window manager. The virtual filesystem service can be
+accessed through 9P\-capable client programs, like
+wmiir(1). This allows simple and powerful remote control
+of the core window manager.
+
+.P
+\fBwmii\fR basically consists of clients, columns, views, and
+the bar, which are described in detail in the
+\fBTerminology\fR section.
+
+.SS Command Line Arguments
+.TP
+\-a \fI<address>\fR
+Specifies the address on which \fBwmii\fR should listen for
+connections. The address takes the form
+\fB\fI<protocol>\fR!\fI<address>\fR\fR. The default is of the form:
+
+unix!/tmp/ns.\fB$USER\fR.\fB${DISPLAY\fR%.0\fB}\fR/wmii
+
+which opens a unix socket per Plan 9 Port conventions. To
+open a TCP socket, listening at port 4332 on the loopback
+interface, use:
+
+tcp!localhost!4332
+
+\fB$WMII_NAMESPACE\fR is automatically set to this value.
+
+.TP
+\-r \fI<wmiirc>\fR
+Specifies which rc script to run. If \fI<wmiirc>\fR consists of a
+single argument, \fB$WMII_CONFPATH\fR is searched before \fB$PATH\fR.
+Otherwise, it is passed to the shell for evaluation. The
+environment variables \fB$WMII_ADDRESS\fR and \fB$WMII_CONFPATH\fR are
+preset for the script.
+
+== Terminology ==
+
+.TP
+Display
+A running X server instance consisting of input
+devices and screens.
+.TP
+Screen
+A physical or virtual (Xinerama or Xnest(1))
+screen of an X display. A screen displays a bar window
+and a view at a time.
+.TP
+Window
+A (rectangular) drawable X object which is
+displayed on a screen, usually an application window.
+.TP
+Client
+An application window surrounded by a frame window
+containing a border and a titlebar.
+
+.TP
+Floating layer
+A screen layer of \fBwmii\fR on top of
+all other layers, where clients are arranged in a
+classic (floating) way. They can be resized or moved
+freely.
+.TP
+Managed layer
+A screen layer of \fBwmii\fR behind the
+floating layer, where clients are arranged in a
+non\-overlapping (managed) way. Here, the window
+manager dynamically assigns each client a size and
+position. The managed layer consists of columns.
+.TP
+Tag
+Alphanumeric strings which can be assigned to a
+client. This provides a mechanism to group clients with
+similar properties. Clients can have one tag, e.g.
+\fIwork\fR, or several tags, e.g. \fIwork+mail\fR.
+Tags are separated with the \fI+\fR character.
+.TP
+View
+A set of clients containing a specific tag, quite
+similar to a workspace in other window managers. It
+consists of the floating and managed layers.
+.TP
+Column
+A column is a screen area which arranges clients
+vertically in a non\-overlapping way. Columns provide
+three different modes, which arrange clients with equal
+size, stacked, or maximized respectively. Clients can
+be moved and resized between and within columns freely.
+.TP
+Bar
+The bar at the bottom of the screen displays a label
+for each view and allows the creation of arbitrary
+user\-defined labels.
+.TP
+Event
+An event is a message which can be read from a
+special file in the filesystem of \fBwmii\fR, such as a
+mouse button press, a key press, or a message written by
+a different 9P\-client.
+
+
+.SS Basic window management
+.P
+Running a raw \fBwmii\fR process without a wmiirc(1)
+script provides basic window management capabilities already.
+However, to use it effectively, remote control through its
+filesystem interface is necessary. By default it is only usable
+with the mouse in conjunction with the \fIMod1 (Alt)\fR
+modifier key. Other interactions, such as customizing the style,
+killing or retagging clients, and grabbing keys, cannot be
+achieved without accessing the filesystem.
+
+.P
+The filesystem can be accessed by connecting to the
+\fIaddress\fR of \fBwmii\fR with any 9P\-capable client, such
+as wmiir(1)
+
+.SS Actions
+.P
+An action is a shell script in the default setup, but it can
+actually be any executable file. It is executed usually by
+selecting it from the actions menu. You can customize an action
+by copying it from the global action directory
+\&'@CONFPREFIX@/wmii@CONFVERSION@' to '\fB$HOME\fR/.wmii@CONFVERSION@' and then
+editing the copy to fit your needs. Of course you can also
+create your own actions there; make sure that they are
+executable.
+
+.P
+Here is a list of the default actions:
+
+.TS
+tab(^); ll.
+ quit^leave the window manager nicely
+ status^periodically print date and load average to the bar
+ welcome^display a welcome message that contains the wmii tutorial
+ wmiirc^configure wmii
+.TE
+
+.SS Default Key Bindings
+.P
+All of the provided \fBwmiirc\fR scripts accept at least the following key
+bindings. They should also provide a \fBshowkeys\fR action to open a
+key binding quick\-reference.
+
+.SS Moving Around
+.TS
+tab(^); ll.
+ \fBKey\fR^\fBAction\fR
+ Mod\-h^Move to a window to the \fIleft\fR of the one currently focused
+ Mod\-l^Move to a window to the \fIright\fR of the one currently focused
+ Mod\-j^Move to the window \fIbelow\fR the one currently focused
+ Mod\-k^Move to a window \fIabove\fR the one currently focused
+ Mod\-space^Toggle between the managed and floating layers
+ Mod\-t \fI<tag>\fR^Move to the view of the given \fI<tag>\fR
+ Mod\-\fI\fI[0\-9]\fR\fR^Move to the view with the given number
+.TE
+
+.SS Moving Things Around
+.TS
+tab(^); ll.
+ \fBKey\fR^\fBAction\fR
+ Mod\-Shift\-h^Move the current window \fIwindow\fR to a column on the \fIleft\fR
+ Mod\-Shift\-l^Move the current window to a column on the \fIright\fR
+ Mod\-Shift\-j^Move the current window below the window beneath it.
+ Mod\-Shift\-k^Move the current window above the window above it.
+ Mod\-Shift\-space^Toggle the current window between the managed and floating layer
+ Mod\-Shift\-t \fI<tag>\fR^Move the current window to the view of the given \fI<tag>\fR
+ Mod\-Shift\-\fI\fI[0\-9]\fR\fR^Move the current window to the view with the given number
+.TE
+
+.SS Miscellaneous
+.TS
+tab(^); ll.
+ \fBKey\fR^\fBAction\fR
+ Mod\-m^Switch the current column to \fImax mode\fR
+ Mod\-s^Switch the current column to \fIstack mode\fR
+ Mod\-d^Switch the current column to \fIdefault mode\fR
+ Mod\-Shift\-c^\fBKill\fR the selected client
+ Mod\-p \fI<program>\fR^\fBExecute\fR \fI<program>\fR
+ Mod\-a \fI<action>\fR^\fBExecute\fR the named <action
+ Mod\-Enter^\fBExecute\fR an \fB@TERMINAL@\fR
+.TE
+
+.SH Configuration
+.P
+If you feel the need to change the default configuration, then
+customize (as described above) the \fBwmiirc\fR action. This
+action is executed at the end of the \fBwmii\fR script and does
+all the work of setting up the window manager, the key bindings,
+the bar labels, etc.
+
+.SS Filesystem
+.P
+Most aspects of \fBwmii\fR are controlled via the filesystem.
+It is usually accessed via the wmiir(1) command, but it
+can be accessed by any 9P, including plan9port's
+9P\fI[1]\fR, and can be mounted natively on Linux via v9fs\fI[1]\fR,
+and on Inferno (which man run on top of Linux).
+
+.P
+The filesystem is, as are many other 9P filesystems, entirely
+synthetic. The files exist only in memory, and are not written
+to disk. They are generally initiated on wmii startup via a
+script such as rc.wmii or wmiirc. Several files read commands,
+others simply act as if they were ordinary files (their contents
+are updated and returned exactly as written), though writing
+them has side\-effects (such as changing key bindings). A
+description of the filesystem layout and control commands
+follows.
+
+.SS Hierarchy
+.TP
+/
+Global control files
+.TP
+/client/\fI*\fR/
+Client control files
+.TP
+/tag/\fI*\fR/
+View control files
+.TP
+/lbar/, /rbar/
+Files representing the contents of the bottom bar
+
+
+.SS The / Hierarchy
+.TP
+colrules
+The \fIcolrules\fR file contains a list of
+rules which affect the width of newly created columns.
+Rules have the form:
+
+.nf
+ /\fI<regex>\fR/ -> \fI<width>\fR\fI[+\fI<width>\fR]\fR*
+.fi
+
+
+When a new column, \fIn\fR, is created on a view whose
+name matches \fI<regex>\fR, the \fIn\fRth given
+\fI<width>\fR percentage of the screen is given to it. If
+there is no \fIn\fRth width, 1/\fIncol\fRth of the
+screen is given to it.
+
+.TP
+tagrules
+The \fItagrules\fR file contains a list of
+rules similar to the colrules. These rules specify
+the tags a client is to be given when it is created.
+Rules are specified:
+
+.nf
+ /\fI<regex>\fR/ -> \fI<tag>\fR\fI[+\fI<tag>\fR]\fR*
+.fi
+
+
+When a client's \fI<name>\fR:\fI<class>\fR:\fI<title>\fR matches
+\fI<regex>\fR, it is given the tagstring \fI<tag>\fR. There are
+two special tags. \fB!\fR, which is deprecated, and identical
+to \fIsel\fR, represents the current tag. \fB~\fR
+represents the floating layer.
+
+.TP
+keys
+The \fIkeys\fR file contains a list of keys which
+\fBwmii\fR will grab. Whenever these key combinations
+are pressed, the string which represents them are
+written to '/event' as: Key \fI<string>\fR
+.TP
+event
+The \fIevent\fR file never returns EOF while
+\fBwmii\fR is running. It stays open and reports events
+as they occur. Included among them are:
+.RS 8
+.TP
+\fI[Not]\fRUrgent \fI<client>\fR \fI[Manager|Client]\fR
+\fI<client>\fR's urgent hint has been set or
+unset. The second arg is \fI[Client]\fR if it's
+been set by the client, and \fI[Manager]\fR if
+it's been set by \fBwmii\fR via a control
+message.
+.TP
+\fI[Not]\fRUrgentTag \fI<tag>\fR \fI[Manager|Client]\fR
+A client on \fI<tag>\fR has had its urgent hint
+set, or the last urgent client has had its
+urgent hint unset.
+.TP
+Client\fI<Click|MouseDown>\fR \fI<client>\fR \fI<button>\fR
+A client's titlebar has either been clicked or
+has a button pressed over it.
+.TP
+\fI[Left|Right]\fRBar\fI[Click|MouseDown]\fR \fI<button>\fR \fI<bar>\fR
+A left or right bar has been clicked or has a
+button pressed over it.
+.TP
+.RS -8
+For a more comprehensive list of available events, see
+\fIwmii.pdf\fR\fI[2]\fR
+
+.TP
+ctl
+The \fIctl\fR file takes a number of messages to
+change global settings such as color and font, which can
+be viewed by reading it. It also takes the following
+commands:
+.RS 8
+.TP
+quit
+Quit \fBwmii\fR
+.TP
+exec \fI<prog>\fR
+Replace \fBwmii\fR with \fI<prog>\fR
+.TP
+spawn \fI<prog>\fR
+Spawn a new program, as if by the \fI\-r\fR flag.
+.RS -8
+
+
+.SS The /client/ Hierarchy
+.P
+Each directory under '/client/' represents an X11 client.
+Each directory is named for the X window id of the window the
+client represents, in the form that most X utilities recognize.
+The one exception is the special 'sel' directory, which
+represents the currently selected client.
+
+.TP
+ctl
+When read, the 'ctl' file returns the X window id
+of the client. The following commands may be written to
+it:
+.RS 8
+.TP
+kill
+Close the client's window. This command will
+likely kill the X client in the future
+(including its other windows), while the close
+command will replace it.
+.TP
+Urgent \fI<on | off | toggle>\fR
+Set or unset the client's urgent hint.
+.TP
+Fullscreen \fI<on | off | toggle>\fR
+.RS -8
+
+.TP
+label
+Set or read a client's label (title).
+.TP
+props
+Returns a clients class and label as:
+\fI<name>\fR:\fI<class>\fR:\fI<label>\fR
+.TP
+tags
+Set or read a client's tags. Tags are separated by
+\fB+\fR or \fB\-\fR. Tags beginning with \fB+\fR are
+added, while those beginning with \fB\-\fR are removed.
+If the tag string written begins with \fB+\fR or
+\fB\-\fR, the written tags are added to or removed from
+the client's set, otherwise, the set is overwritten.
+
+
+.SS The /tag/ Hierarchy
+.P
+Each directory under '/tag/' represents a view, containing
+all of the clients with the given tag applied. The special
+\&'sel' directory represents the currently selected tag.
+
+.TP
+ctl
+The 'ctl' file can be read to retrieve the name
+of the tag the directory represents, or written with the
+following commands:
+.RS 8
+.TP
+select
+Select a client:
+select \fI[left|right|up|down]\fR
+.P
+select \fI[\fI<row number>\fR|sel]\fR \fI[\fI<frame number>\fR]\fR
+.P
+select client \fI<client>\fR
+.TP
+send
+Send a client somewhere:
+.RS 8
+.TP
+send \fI[\fI<client>\fR|sel]\fR \fI[up|down|left|right]\fR
+.TP
+send \fI[\fI<client>\fR|sel]\fR \fI<area>\fR
+Send \fI<client>\fR to the \fIn\fRth \fI<area>\fR
+.TP
+send \fI[\fI<client>\fR|sel]\fR toggle
+Toggle \fI<client>\fR between the floating and managed layer.
+.RS -8
+.TP
+swap
+Swap a client with another. Same syntax as send.
+
+.TP
+grow
+Grow or shrink a client.
+
+.nf
+ grow \fI<frame>\fR \fI<direction>\fR \fI[\fI<amount>\fR]\fR
+.fi
+
+.TP
+nudge
+Nudge a client in a given direction.
+
+.nf
+ grow \fI<frame>\fR \fI<direction>\fR \fI[\fI<amount>\fR]\fR
+.fi
+
+.RS -8
+Where the arguments are defined as follows:
+.RS 8
+.TP
+area
+Selects a column or the floating area.
+
+.nf
+ area ::= \fI<area_spec>\fR | \fI<screen_spec>\fR:\fI<area_spec>\fR
+.fi
+
+
+When \fI<screen_spec>\fR is omitted and \fI<area_spec>\fR is not "sel",
+0 is assumed. "sel" by itself represents the selected client no
+matter which screen it is on.
+
+.nf
+ area_spec ::= "~" | \fI<number>\fR | "sel"
+.fi
+
+
+Where "~" represents the floating area and \fI<number>\fR represents a column
+index, starting at one.
+
+.nf
+ screen_spec ::= \fI<number>\fR
+.fi
+
+
+Where \fI<number>\fR representes the 0\-based Xinerama screen number.
+
+.TP
+frame
+Selects a client window.
+
+.nf
+ frame ::= \fI<area>\fR \fI<index>\fR | \fI<area>\fR sel | client \fI<window-id>\fR
+.fi
+
+
+Where \fI<index>\fR represents the nth frame of \fI<area>\fR or \fI<window\-id>\fR is
+the X11 window id of the given client.
+
+.TP
+amount
+The amount to grow or nudge something.
+
+.nf
+ amount ::= \fI<number>\fR | \fI<number>\fRpx
+.fi
+
+
+If "px" is given, \fI<number>\fR is interperated as an exact pixel count.
+Otherwise, it's interperated as a "reasonable" amount, which is
+usually either the height of a window's title bar, or its sizing
+increment (as defined by X11) in a given direction.
+.RS -8
+.TP
+index
+Read for a description of the contents of a tag.
+
+
+.SS The /rbar/, /lbar/ Hierarchy
+.P
+The files under '/rbar/' and '/lbar/' represent the
+items of the bar at the bottom of the screen. Files under
+\&'/lbar/' appear on the left side of the bar, while those
+under '/rbar/' appear on the right, with the leftmost item
+occupying all extra available space. The items are sorted
+lexicographically.
+
+.P
+The files may be read to obtain the colors and text of the bars.
+The colors are at the beginning of the string, represented as a
+tuple of 3 hex color codes for the foreground, background, and
+border, respectively. When writing the bar files, the colors may
+be omitted if the text would not otherwise appear to contain
+them.
+
+.SH FILES
+.TP
+/tmp/ns.\fB$USER\fR.\fB${DISPLAY\fR%.0\fB}\fR/wmii
+The wmii socket file which provides a 9P service.
+.TP
+@CONFPREFIX@/wmii@CONFVERSION@
+Global action directory.
+.TP
+\fB$HOME\fR/.wmii@CONFVERSION@
+User\-specific action directory. Actions are first searched here.
+
+
+.SH ENVIRONMENT
+.TP
+\fB$HOME\fR, \fB$DISPLAY\fR
+See the section \fBFILES\fR above.
+
+.P
+The following variables are set and exported within \fBwmii\fR and
+thus can be used in actions:
+
+.TP
+\fB$WMII_ADDRESS\fR
+The address on which \fBwmii\fR is listening.
+.TP
+\fB$NAMESPACE\fR
+The namespace directory to use if no address is provided.
+
+.SH SEE ALSO
+.P
+dmenu(1), wmiir(1)
+
+.P
+@DOCDIR@/wmii.pdf
+
+.P
+\fI[1]\fR http://www.suckless.org/wiki/wmii/tips/9p_tips
+.P
+\fI[2]\fR @DOCDIR@/wmii.pdf
+
+
+.\" man code generated by txt2tags 2.5 (http://txt2tags.sf.net)
+.\" cmdline: txt2tags -o- wmii.man1
+
diff --git a/man/wmii.man1 b/man/wmii.man1
new file mode 100644
index 0000000..d4c15da
--- /dev/null
+++ b/man/wmii.man1
@@ -0,0 +1,477 @@
+WMII
+wmii-@VERSION@
+Oct, 2009
+
+%!includeconf: header.t2t
+
+= NAME =
+
+wmii - Window Manager Improved²
+
+= SYNOPSIS =
+
+wmii [-a <address>] [-r <wmiirc>] +
+wmii -v
+
+= DESCRIPTION =
+
+== Overview ==
+
+`wmii` is a dynamic window manager for X11. In contrast to
+static window management the user rarely has to think about how
+to organize windows, no matter what he is doing or how many
+applications are used at the same time. The window manager
+adapts to the current environment and fits to the needs of the
+user, rather than forcing him to use a preset, fixed layout and
+trying to shoehorn all windows and applications into it.
+
+`wmii` supports classic and tiled window management with
+extended keyboard and mouse control. The classic window
+management arranges windows in a floating layer in which windows
+can be moved and resized freely. The tiled window management is
+based on columns which split up the screen horizontally. Each
+column handles arbitrary windows and arranges them vertically in
+a non-overlapping way. They can then be moved and resized
+between and within columns at will.
+
+`wmii` provides a virtual filesystem which represents the
+internal state similar to the procfs of Unix operating systems.
+Modifying this virtual filesystem results in changing the state
+of the window manager. The virtual filesystem service can be
+accessed through 9P-capable client programs, like
+wmiir(1). This allows simple and powerful remote control
+of the core window manager.
+
+`wmii` basically consists of clients, columns, views, and
+the bar, which are described in detail in the
+**Terminology** section.
+
+== Command Line Arguments ==
+
+: -a <address>
+ Specifies the address on which `wmii` should listen for
+ connections. The address takes the form
+ `<protocol>!<address>`. The default is of the form:
+
+ unix!/tmp/ns.$USER.${DISPLAY%.0}/wmii
+
+ which opens a unix socket per Plan 9 Port conventions. To
+ open a TCP socket, listening at port 4332 on the loopback
+ interface, use:
+
+ tcp!localhost!4332
+
+ $WMII_NAMESPACE is automatically set to this value.
+
+: -r <wmiirc>
+ Specifies which rc script to run. If <wmiirc> consists of a
+ single argument, $WMII_CONFPATH is searched before $PATH.
+ Otherwise, it is passed to the shell for evaluation. The
+ environment variables $WMII_ADDRESS and $WMII_CONFPATH are
+ preset for the script.
+
+== Terminology ==
+
+: Display
+ A running X server instance consisting of input
+ devices and screens.
+: Screen
+ A physical or virtual (Xinerama or Xnest(1))
+ screen of an X display. A screen displays a bar window
+ and a view at a time.
+: Window
+ A (rectangular) drawable X object which is
+ displayed on a screen, usually an application window.
+: Client
+ An application window surrounded by a frame window
+ containing a border and a titlebar.
+
+: Floating layer
+ A screen layer of `wmii` on top of
+ all other layers, where clients are arranged in a
+ classic (floating) way. They can be resized or moved
+ freely.
+: Managed layer
+ A screen layer of `wmii` behind the
+ floating layer, where clients are arranged in a
+ non-overlapping (managed) way. Here, the window
+ manager dynamically assigns each client a size and
+ position. The managed layer consists of columns.
+: Tag
+ Alphanumeric strings which can be assigned to a
+ client. This provides a mechanism to group clients with
+ similar properties. Clients can have one tag, e.g.
+ _work_, or several tags, e.g. _work+mail_.
+ Tags are separated with the _+_ character.
+: View
+ A set of clients containing a specific tag, quite
+ similar to a workspace in other window managers. It
+ consists of the floating and managed layers.
+: Column
+ A column is a screen area which arranges clients
+ vertically in a non-overlapping way. Columns provide
+ three different modes, which arrange clients with equal
+ size, stacked, or maximized respectively. Clients can
+ be moved and resized between and within columns freely.
+: Bar
+ The bar at the bottom of the screen displays a label
+ for each view and allows the creation of arbitrary
+ user-defined labels.
+: Event
+ An event is a message which can be read from a
+ special file in the filesystem of `wmii`, such as a
+ mouse button press, a key press, or a message written by
+ a different 9P-client.
+:
+
+== Basic window management ==
+
+Running a raw `wmii` process without a wmiirc(1)
+script provides basic window management capabilities already.
+However, to use it effectively, remote control through its
+filesystem interface is necessary. By default it is only usable
+with the mouse in conjunction with the //Mod1 (Alt)//
+modifier key. Other interactions, such as customizing the style,
+killing or retagging clients, and grabbing keys, cannot be
+achieved without accessing the filesystem.
+
+The filesystem can be accessed by connecting to the
+//address// of `wmii` with any 9P-capable client, such
+as wmiir(1)
+
+== Actions ==
+
+An action is a shell script in the default setup, but it can
+actually be any executable file. It is executed usually by
+selecting it from the actions menu. You can customize an action
+by copying it from the global action directory
+'@CONFPREFIX@/wmii@CONFVERSION@' to '$HOME/.wmii@CONFVERSION@' and then
+editing the copy to fit your needs. Of course you can also
+create your own actions there; make sure that they are
+executable.
+
+Here is a list of the default actions:
+
+| quit | leave the window manager nicely
+| status | periodically print date and load average to the bar
+| welcome | display a welcome message that contains the wmii tutorial
+| wmiirc | configure wmii
+
+== Default Key Bindings ==
+
+All of the provided `wmiirc` scripts accept at least the following key
+bindings. They should also provide a `showkeys` action to open a
+key binding quick-reference.
+
+=== Moving Around ===
+
+|| Key | Action
+| Mod-h | Move to a window to the _left_ of the one currently focused
+| Mod-l | Move to a window to the _right_ of the one currently focused
+| Mod-j | Move to the window _below_ the one currently focused
+| Mod-k | Move to a window _above_ the one currently focused
+| Mod-space | Toggle between the managed and floating layers
+| Mod-t <tag> | Move to the view of the given <tag>
+| Mod-//[0-9]// | Move to the view with the given number
+
+=== Moving Things Around ===
+
+|| Key | Action
+| Mod-Shift-h | Move the current window _window_ to a column on the _left_
+| Mod-Shift-l | Move the current window to a column on the _right_
+| Mod-Shift-j | Move the current window below the window beneath it.
+| Mod-Shift-k | Move the current window above the window above it.
+| Mod-Shift-space | Toggle the current window between the managed and floating layer
+| Mod-Shift-t <tag> | Move the current window to the view of the given <tag>
+| Mod-Shift-//[0-9]// | Move the current window to the view with the given number
+
+=== Miscellaneous ===
+
+|| Key | Action
+| Mod-m | Switch the current column to _max mode_
+| Mod-s | Switch the current column to _stack mode_
+| Mod-d | Switch the current column to _default mode_
+| Mod-Shift-c | `Kill` the selected client
+| Mod-p <program> | `Execute` <program>
+| Mod-a <action> | `Execute` the named <action
+| Mod-Enter | `Execute` an `@TERMINAL@`
+
+= Configuration =
+
+If you feel the need to change the default configuration, then
+customize (as described above) the `wmiirc` action. This
+action is executed at the end of the `wmii` script and does
+all the work of setting up the window manager, the key bindings,
+the bar labels, etc.
+
+== Filesystem ==
+
+Most aspects of `wmii` are controlled via the filesystem.
+It is usually accessed via the wmiir(1) command, but it
+can be accessed by any ``9P``, including plan9port's
+9P[1], and can be mounted natively on Linux via v9fs[1],
+and on Inferno (which man run on top of Linux).
+
+The filesystem is, as are many other 9P filesystems, entirely
+synthetic. The files exist only in memory, and are not written
+to disk. They are generally initiated on wmii startup via a
+script such as rc.wmii or wmiirc. Several files read commands,
+others simply act as if they were ordinary files (their contents
+are updated and returned exactly as written), though writing
+them has side-effects (such as changing key bindings). A
+description of the filesystem layout and control commands
+follows.
+
+== Hierarchy ==
+
+: /
+ Global control files
+: /client/_*_/
+ Client control files
+: /tag/_*_/
+ View control files
+: /lbar/, /rbar/
+ Files representing the contents of the bottom bar
+:
+
+== The / Hierarchy ==
+
+: colrules
+ The _colrules_ file contains a list of
+ rules which affect the width of newly created columns.
+ Rules have the form:
+
+``` /<regex>/ -> <width>[+<width>]*
+
+ When a new column, _n_, is created on a view whose
+ name matches <regex>, the _n_th given
+ <width> percentage of the screen is given to it. If
+ there is no _n_th width, 1/_ncol_th of the
+ screen is given to it.
+
+: tagrules
+ The _tagrules_ file contains a list of
+ rules similar to the colrules. These rules specify
+ the tags a client is to be given when it is created.
+ Rules are specified:
+
+``` /<regex>/ -> <tag>[+<tag>]*
+
+ When a client's <name>:<class>:<title> matches
+ <regex>, it is given the tagstring <tag>. There are
+ two special tags. **!**, which is deprecated, and identical
+ to _sel_, represents the current tag. **~**
+ represents the floating layer.
+
+: keys
+ The _keys_ file contains a list of keys which
+ `wmii` will grab. Whenever these key combinations
+ are pressed, the string which represents them are
+ written to '/event' as: Key <string>
+: event
+ The _event_ file never returns EOF while
+ `wmii` is running. It stays open and reports events
+ as they occur. Included among them are:
+ >>
+ : [Not]Urgent <client> [Manager|Client]
+ <client>'s urgent hint has been set or
+ unset. The second arg is [Client] if it's
+ been set by the client, and [Manager] if
+ it's been set by `wmii` via a control
+ message.
+ : [Not]UrgentTag <tag> [Manager|Client]
+ A client on <tag> has had its urgent hint
+ set, or the last urgent client has had its
+ urgent hint unset.
+ : Client<Click|MouseDown> <client> <button>
+ A client's titlebar has either been clicked or
+ has a button pressed over it.
+ : [Left|Right]Bar[Click|MouseDown] <button> <bar>
+ A left or right bar has been clicked or has a
+ button pressed over it.
+ :  
+ <<
+ For a more comprehensive list of available events, see
+ _wmii.pdf_[2]
+
+: ctl
+ The _ctl_ file takes a number of messages to
+ change global settings such as color and font, which can
+ be viewed by reading it. It also takes the following
+ commands:
+ >>
+ : quit
+ Quit `wmii`
+ : exec <prog>
+ Replace `wmii` with <prog>
+ : spawn <prog>
+ Spawn a new program, as if by the _-r_ flag.
+ :
+ <<
+:
+
+== The /client/ Hierarchy ==
+
+Each directory under '/client/' represents an X11 client.
+Each directory is named for the X window id of the window the
+client represents, in the form that most X utilities recognize.
+The one exception is the special 'sel' directory, which
+represents the currently selected client.
+
+: ctl
+ When read, the 'ctl' file returns the X window id
+ of the client. The following commands may be written to
+ it:
+ >>
+ : kill
+ Close the client's window. This command will
+ likely kill the X client in the future
+ (including its other windows), while the close
+ command will replace it.
+ : Urgent <on | off | toggle>
+ Set or unset the client's urgent hint.
+ : Fullscreen <on | off | toggle>
+ <<
+
+: label
+ Set or read a client's label (title).
+: props
+ Returns a clients class and label as:
+ <name>:<class>:<label>
+: tags
+ Set or read a client's tags. Tags are separated by
+ **+** or **-**. Tags beginning with **+** are
+ added, while those beginning with **-** are removed.
+ If the tag string written begins with **+** or
+ **-**, the written tags are added to or removed from
+ the client's set, otherwise, the set is overwritten.
+:
+
+== The /tag/ Hierarchy ==
+
+Each directory under '/tag/' represents a view, containing
+all of the clients with the given tag applied. The special
+'sel' directory represents the currently selected tag.
+
+: ctl
+ The 'ctl' file can be read to retrieve the name
+ of the tag the directory represents, or written with the
+ following commands:
+ >>
+ : select
+ Select a client:
+ select [left|right|up|down] +
+ select [<row number>|sel] [<frame number>] +
+ select client <client>
+ : send
+ Send a client somewhere:
+ >>
+ : send [<client>|sel] [up|down|left|right]
+ : send [<client>|sel] <area>
+ Send <client> to the _n_th <area>
+ : send [<client>|sel] toggle
+ Toggle <client> between the floating and managed layer.
+ <<
+ : swap
+ Swap a client with another. Same syntax as send.
+
+ : grow
+ Grow or shrink a client.
+
+``` grow <frame> <direction> [<amount>]
+ : nudge
+ Nudge a client in a given direction.
+
+``` grow <frame> <direction> [<amount>]
+ :
+ <<
+ Where the arguments are defined as follows:
+ >>
+ : area
+ Selects a column or the floating area.
+
+``` area ::= <area_spec> | <screen_spec>:<area_spec>
+
+ When <screen_spec> is omitted and <area_spec> is not "sel",
+ 0 is assumed. "sel" by itself represents the selected client no
+ matter which screen it is on.
+
+``` area_spec ::= "~" | <number> | "sel"
+
+ Where "~" represents the floating area and <number> represents a column
+ index, starting at one.
+
+``` screen_spec ::= <number>
+
+ Where <number> representes the 0-based Xinerama screen number.
+
+ : frame
+ Selects a client window.
+
+``` frame ::= <area> <index> | <area> sel | client <window-id>
+
+ Where <index> represents the nth frame of <area> or <window-id> is
+ the X11 window id of the given client.
+
+ : amount
+ The amount to grow or nudge something.
+
+``` amount ::= <number> | <number>px
+
+ If "px" is given, <number> is interperated as an exact pixel count.
+ Otherwise, it's interperated as a "reasonable" amount, which is
+ usually either the height of a window's title bar, or its sizing
+ increment (as defined by X11) in a given direction.
+ <<
+: index
+ Read for a description of the contents of a tag.
+:
+
+
+== The /rbar/, /lbar/ Hierarchy ==
+
+The files under '/rbar/' and '/lbar/' represent the
+items of the bar at the bottom of the screen. Files under
+'/lbar/' appear on the left side of the bar, while those
+under '/rbar/' appear on the right, with the leftmost item
+occupying all extra available space. The items are sorted
+lexicographically.
+
+The files may be read to obtain the colors and text of the bars.
+The colors are at the beginning of the string, represented as a
+tuple of 3 hex color codes for the foreground, background, and
+border, respectively. When writing the bar files, the colors may
+be omitted if the text would not otherwise appear to contain
+them.
+
+= FILES =
+
+: /tmp/ns.$USER.${DISPLAY%.0}/wmii
+ The wmii socket file which provides a 9P service.
+: @CONFPREFIX@/wmii@CONFVERSION@
+ Global action directory.
+: $HOME/.wmii@CONFVERSION@
+ User-specific action directory. Actions are first searched here.
+:
+
+= ENVIRONMENT =
+
+: $HOME, $DISPLAY
+ See the section **FILES** above.
+:
+The following variables are set and exported within `wmii` and
+thus can be used in actions:
+
+: $WMII_ADDRESS
+ The address on which `wmii` is listening.
+: $NAMESPACE
+ The namespace directory to use if no address is provided.
+:
+= SEE ALSO =
+dmenu(1), wmiir(1)
+
+@DOCDIR@/wmii.pdf
+
+[1] http://www.suckless.org/wiki/wmii/tips/9p_tips +
+[2] @DOCDIR@/wmii.pdf
+
diff --git a/man/wmii9menu.1 b/man/wmii9menu.1
new file mode 100644
index 0000000..935c66d
--- /dev/null
+++ b/man/wmii9menu.1
@@ -0,0 +1,66 @@
+.TH "WMII9MENU" 1 "Oct, 2009" "wmii-@VERSION@"
+
+.SH NAME
+.P
+wmii9menu \- The wmii menu program
+
+.SH SYNOPSIS
+.P
+wmii9menu \fI[\-a \fI<address>\fR]\fR \fI[\-i \fI<initial>\fR]\fR \fI<item>\fR\fI[:\fI<command>\fR]\fR...
+wmii9menu \-v
+
+.SH DESCRIPTION
+.P
+\fBwmii9menu\fR is \fBwmii\fR's standard clickable menu program. It's used
+extensively by \fBwmii\fR and related programs to display clickable
+menus, namely for window titlebars and bar items. The name, along
+with the code, derives from the 9menu program, which in turn derives
+its name from Plan 9's style of clickable menus.
+
+.SH ARGUMENTS
+.TP
+\-a
+The address at which to connect to \fBwmii\fR.
+.TP
+\-i \fI<initial>\fR
+
+.RS
+If \fI<initial>\fR is listed among the other items on the command
+line, it is selected at startup, and the menu is positioned
+so that the mouse pointer is centered on said item.
+.RE
+.P
+:
+
+.SH USAGE
+.P
+\fBwmii9menu\fR is invoked with a list of arguments, each of which is
+displayed as a menu item. The first \fI:\fR in the item name, and any
+text following it, is stripped. The menu is opened such that the
+mouse pointer is centered on the selected item. If a mouse button is
+depressed when the menu opens, then releasing it will confirm the
+selection. Otherwise, a mouse press will do the same. When a
+selection is made, \fBwmii9menu\fR prints the result. If the selected
+item initially contained a \fI:\fR, the text following it is printed.
+Otherwise, the item text itself is printed.
+
+.SH ENVIRONMENT
+.TP
+\fB$WMII_ADDRESS\fR
+The address at which to connect to wmii.
+.TP
+\fB$NAMESPACE\fR
+The namespace directory to use if no address is
+provided.
+
+.SH SEE ALSO
+.P
+wmii(1), wmiir(1), wimenu(1)
+
+.P
+\fI[1]\fR http://www.suckless.org/wiki/wmii/tips/9p_tips
+
+
+.\" man code generated by txt2tags 2.5 (http://txt2tags.sf.net)
+.\" cmdline: txt2tags -o- wmii9menu.man1
+
diff --git a/man/wmii9menu.man1 b/man/wmii9menu.man1
new file mode 100644
index 0000000..a1e6f5e
--- /dev/null
+++ b/man/wmii9menu.man1
@@ -0,0 +1,58 @@
+WMII9MENU
+wmii-@VERSION@
+Oct, 2009
+
+%!includeconf: header.t2t
+
+= NAME =
+
+wmii9menu - The wmii menu program
+
+= SYNOPSIS =
+
+wmii9menu [-a <address>] [-i <initial>] <item>[:<command>]...
+wmii9menu -v
+
+= DESCRIPTION =
+
+`wmii9menu` is `wmii`'s standard clickable menu program. It's used
+extensively by `wmii` and related programs to display clickable
+menus, namely for window titlebars and bar items. The name, along
+with the code, derives from the 9menu program, which in turn derives
+its name from Plan 9's style of clickable menus.
+
+= ARGUMENTS =
+
+: -a
+ The address at which to connect to `wmii`.
+: -i <initial>
+ If <initial> is listed among the other items on the command
+ line, it is selected at startup, and the menu is positioned
+ so that the mouse pointer is centered on said item.
+:
+
+= USAGE =
+
+`wmii9menu` is invoked with a list of arguments, each of which is
+displayed as a menu item. The first _:_ in the item name, and any
+text following it, is stripped. The menu is opened such that the
+mouse pointer is centered on the selected item. If a mouse button is
+depressed when the menu opens, then releasing it will confirm the
+selection. Otherwise, a mouse press will do the same. When a
+selection is made, `wmii9menu` prints the result. If the selected
+item initially contained a _:_, the text following it is printed.
+Otherwise, the item text itself is printed.
+
+= ENVIRONMENT =
+
+: $WMII_ADDRESS
+ The address at which to connect to wmii.
+: $NAMESPACE
+ The namespace directory to use if no address is
+ provided.
+:
+= SEE ALSO =
+wmii(1), wmiir(1), wimenu(1)
+
+[1] http://www.suckless.org/wiki/wmii/tips/9p_tips
+
diff --git a/man/wmiir.1 b/man/wmiir.1
new file mode 100644
index 0000000..acf658d
--- /dev/null
+++ b/man/wmiir.1
@@ -0,0 +1,91 @@
+.TH "wmiir" 1 "Oct, 2009" "wmii-@VERSION@"
+
+.SH NAME
+.P
+wmiir \- The wmii 9P filesystem client
+
+.SH SYNOPSIS
+.P
+wmiir \fI[\-a \fI<address>\fR]\fR {create | ls \fI[\-dlp]\fR | read | remove | write} \fI<file>\fR
+.P
+wmiir \fI[\-a \fI<address>\fR]\fR xwrite \fI<file>\fR \fI<data>\fR ...
+.P
+wmiir \-v
+
+.SH DESCRIPTION
+.P
+\fBwmiir\fR is a simple 9P filesystem client which ships with \fBwmii\fR, and connects
+to its virtual filesystem by default. \fBwmiir\fR is most often used to query and
+issue commands to \fBwmii\fR, both from the command line and from its \fBsh\fR\-based
+configuration scripts.
+
+.SH ARGUMENTS
+.TP
+\-a
+The address at which to connect to \fBwmii\fR.
+
+.SH COMMANDS
+.TP
+create \fI<file>\fR
+Creates a new file or directory in the filesystem. Permissions and
+file type are inferred by \fBwmii\fR. The contents of the standard input
+are written to the new file.
+.TP
+ls \fI[\-dlp]\fR \fI<path>\fR
+Lists the contents of \fI<path>\fR.
+
+Flags:
+.RS 8
+.TP
+\-d
+Don't list the contents of directories.
+.TP
+\-l
+Long output. For each file, list its permissions, owner,
+group, size (bytes), mtime, and name.
+.TP
+\-p
+Print the full path to each file.
+.RS -8
+.TP
+read \fI<file>\fR
+Reads the entire contents of a file from the filesystem. Blocks until
+interrupted or EOF is received.
+
+Synonyms: \fBcat\fR
+.TP
+remove \fI<path>\fR
+Removes \fI<path>\fR from the filesystem.
+
+Synonyms: rm
+.TP
+write \fI<file>\fR
+Writes the contents of the standard input to \fI<file>\fR.
+.TP
+xwrite \fI<file>\fR \fI<data>\fR ...
+Writes each argument after \fI<file>\fR to the latter.
+
+
+.SH ENVIRONMENT
+.TP
+\fB$WMII_ADDRESS\fR
+The address at which to connect to wmii.
+.TP
+\fB$NAMESPACE\fR
+The namespace directory to use if no address is
+provided.
+
+
+.SH SEE ALSO
+.P
+wmii(1), libixp\fI[2]\fR
+
+.P
+\fI[1]\fR http://www.suckless.org/wiki/wmii/tips/9p_tips
+.P
+\fI[2]\fR http://libs.suckless.org/libixp
+
+
+.\" man code generated by txt2tags 2.5 (http://txt2tags.sf.net)
+.\" cmdline: txt2tags -o- wmiir.man1
+
diff --git a/man/wmiir.man1 b/man/wmiir.man1
new file mode 100644
index 0000000..8ea04ed
--- /dev/null
+++ b/man/wmiir.man1
@@ -0,0 +1,77 @@
+wmiir
+wmii-@VERSION@
+Oct, 2009
+
+%!includeconf: header.t2t
+
+= NAME =
+
+wmiir - The wmii 9P filesystem client
+
+= SYNOPSIS =
+
+wmiir [-a <address>] {create | ls [-dlp] | read | remove | write} <file> +
+wmiir [-a <address>] xwrite <file> <data> ... +
+wmiir -v
+
+= DESCRIPTION =
+
+`wmiir` is a simple 9P filesystem client which ships with `wmii`, and connects
+to its virtual filesystem by default. `wmiir` is most often used to query and
+issue commands to `wmii`, both from the command line and from its `sh`-based
+configuration scripts.
+
+= ARGUMENTS =
+
+: -a
+ The address at which to connect to `wmii`.
+:
+= COMMANDS =
+
+: create <file>
+ Creates a new file or directory in the filesystem. Permissions and
+ file type are inferred by `wmii`. The contents of the standard input
+ are written to the new file.
+: ls [-dlp] <path>
+ Lists the contents of <path>.
+
+ Flags:
+ >>
+ : -d
+ Don't list the contents of directories.
+ : -l
+ Long output. For each file, list its permissions, owner,
+ group, size (bytes), mtime, and name.
+ : -p
+ Print the full path to each file.
+ <<
+: read <file>
+ Reads the entire contents of a file from the filesystem. Blocks until
+ interrupted or EOF is received.
+
+ Synonyms: `cat`
+: remove <path>
+ Removes <path> from the filesystem.
+
+ Synonyms: rm
+: write <file>
+ Writes the contents of the standard input to <file>.
+: xwrite <file> <data> ...
+ Writes each argument after <file> to the latter.
+:
+
+= ENVIRONMENT =
+
+: $WMII_ADDRESS
+ The address at which to connect to wmii.
+: $NAMESPACE
+ The namespace directory to use if no address is
+ provided.
+:
+
+= SEE ALSO =
+wmii(1), libixp[2]
+
+[1] http://www.suckless.org/wiki/wmii/tips/9p_tips +
+[2] http://libs.suckless.org/libixp
+
diff --git a/mk/common.mk b/mk/common.mk
new file mode 100644
index 0000000..9875e53
--- /dev/null
+++ b/mk/common.mk
@@ -0,0 +1,34 @@
+all:
+
+install: all simpleinstall
+uninstall: simpleuninstall
+
+DOCDIR = $(DOC)
+simpleinstall:
+ for f in $(DOCS); do \
+ $(INSTALL) 0644 $$f $(DOCDIR) $$f; \
+ done
+ for f in $(TEXT); do \
+ $(INSTALL) 0644 $$f $(DIR) $$f; \
+ done
+ for f in $(BINARY); do \
+ $(INSTALL) -b 0644 $$f $(DIR) $$f; \
+ done
+ for f in $(EXECS); do \
+ $(INSTALL) -b 0755 $$f $(DIR) $$f; \
+ done
+
+cleandep:
+ echo CLEANDEP
+ rm .depend 2>/dev/null || true
+
+tags:
+ files=; \
+ for f in $(OBJ); do \
+ [ -f "$$f.c" ] && files="$$files $$f.c"; \
+ done; \
+ echo CTAGS $$files $(TAGFILES) || \
+ ctags $$files $(TAGFILES)
+
+.PHONY: all options clean dist install uninstall depend cleandep tags
+.PHONY: simpleuninstall simpleinstall
diff --git a/mk/dir.mk b/mk/dir.mk
new file mode 100644
index 0000000..3344bf8
--- /dev/null
+++ b/mk/dir.mk
@@ -0,0 +1,33 @@
+MKSUBDIR = \
+ set -e; \
+ targ=$@; targ=$${targ\#d}; \
+ for i in $$dirs; do \
+ export $(SUBMAKE_EXPORT); \
+ export BASE=$(BASE)$$i/; \
+ if [ ! -d $$i ]; then \
+ echo Skipping nonexistent directory: $$i 1>&2; \
+ else \
+ echo MAKE $$targ $$BASE; \
+ (cd $$i && $(MAKE) $$targ) || exit $?; \
+ fi; \
+ done
+
+dall:
+ +dirs="$(DIRS)"; $(MKSUBDIR)
+dclean:
+ +dirs="$(DIRS)"; $(MKSUBDIR)
+dinstall:
+ +dirs="$(INSTDIRS)"; $(MKSUBDIR)
+duninstall:
+ +dirs="$(INSTDIRS)"; $(MKSUBDIR)
+ddepend:
+ +dirs="$(DIRS)"; $(MKSUBDIR)
+
+all: dall
+clean: dclean
+install: dinstall
+uninstall: duninstall
+depend: ddepend
+
+INSTDIRS = $(DIRS)
+
diff --git a/mk/gcc.mk b/mk/gcc.mk
new file mode 100644
index 0000000..b37b59c
--- /dev/null
+++ b/mk/gcc.mk
@@ -0,0 +1,28 @@
+DEBUGCFLAGS = \
+ -g \
+ -O1 \
+ -fno-builtin \
+ -fno-inline \
+ -fno-omit-frame-pointer \
+ -fno-optimize-sibling-calls \
+ -fno-unroll-loops
+CFLAGS += \
+ -std=c99 \
+ -pedantic \
+ -pipe \
+ -fno-strict-aliasing \
+ -Wall \
+ -Wimplicit \
+ -Wmissing-prototypes \
+ -Wno-comment \
+ -Wno-missing-braces \
+ -Wno-parentheses \
+ -Wno-sign-compare \
+ -Wno-switch \
+ -Wpointer-arith \
+ -Wreturn-type \
+ -Wstrict-prototypes \
+ -Wtrigraphs
+MKDEP = cpp -M
+SOCFLAGS += -fPIC
+
diff --git a/mk/hdr.mk b/mk/hdr.mk
new file mode 100644
index 0000000..55481a0
--- /dev/null
+++ b/mk/hdr.mk
@@ -0,0 +1,155 @@
+DIR =
+DIRS =
+DOC =
+DOCDIR =
+DOCS =
+EXECS =
+HFILES =
+INCLUDES =
+LIB =
+LIBS =
+OBJ =
+OFILES =
+OFILES_PIC =
+PACKAGES =
+PROG =
+SO =
+TAGFILES =
+TARG =
+TEXT =
+
+FILTER = cat
+
+EXCFLAGS = $(INCLUDES) -D_XOPEN_SOURCE=600
+
+COMPILE = $(ROOT)/util/compile "$(CC)" "$(EXCFLAGS) $(CFLAGS) $$(pkg-config --cflags $(PACKAGES))"
+COMPILEPIC = $(ROOT)/util/compile "$(CC)" "$(EXCFLAGS) $(CFLAGS) $$(pkg-config --cflags $(PACKAGES)) $(SOCFLAGS)"
+
+LINK = $(ROOT)/util/link "$(LD)" "$$(pkg-config --libs $(PACKAGES)) $(LDFLAGS) $(LIBS)"
+LINKSO = $(ROOT)/util/link "$(LD)" "$$(pkg-config --libs $(PACKAGES)) $(SOLDFLAGS) $(LIBS) $(SHARED)"
+
+CLEANNAME=$(ROOT)/util/cleanname
+
+SOEXT=so
+TAGFILES=
+
+CTAGS=ctags
+
+PACKAGES = 2>/dev/null
+
+# and this:
+# Try to find a sane shell. /bin/sh is a last resort, because it's
+# usually bash on Linux, which means it's painfully slow.
+BINSH := $(shell \
+ if [ -x /bin/dash ]; then echo /bin/dash; \
+ elif [ -x /bin/ksh ]; then echo /bin/ksh; \
+ else echo /bin/sh; fi)
+BINSH != echo /bin/sh
+
+include $(ROOT)/config.mk
+
+# I hate this.
+MKCFGSH=if test -f $(ROOT)/config.local.mk; then echo $(ROOT)/config.local.mk; else echo /dev/null; fi
+MKCFG:=$(shell $(MKCFGSH))
+MKCFG!=$(MKCFGSH)
+include $(MKCFG)
+
+.SILENT:
+.SUFFIXES: .out .o .o_pic .c .pdf .sh .rc .$(SOEXT) .awk .1 .man1 .depend .install .uninstall .clean
+all:
+
+MAKEFILES=.depend
+.c.depend:
+ echo MKDEP $<
+ [ -n "${noisycc}" ] && echo $(MKDEP) $(EXCFLAGS) $(CFLAGS) $$(pkg-config --cflags $(PACKAGES)) $< || true
+ $(MKDEP) $(EXCFLAGS) $(CFLAGS) $$(pkg-config --cflags $(PACKAGES)) $< >>.depend
+
+.sh.depend .rc.depend .1.depend .awk.depend:
+ :
+
+.c.o:
+ $(COMPILE) $@ $<
+.c.o_pic:
+ $(COMPILEPIC) $@ $<
+
+.o.out:
+ $(LINK) $@ $<
+.c.out:
+ $(COMPILE) ${<:.c=.o} $<
+ $(LINK) $@ ${<:.c=.o}
+
+.rc.out .awk.out .sh.out:
+ echo FILTER $(BASE)$<
+ [ -n "${<:%.sh=}" ] || sh -n $<
+ set -e; \
+ [ -n "${noisycc}" ] && set -x; \
+ $(FILTER) $< >$@; \
+ chmod 0755 $@
+
+.man1.1:
+ echo TXT2TAGS $(BASE)$<
+ [ -n "${noisycc}" ] && set -x; \
+ txt2tags -o- $< >$@
+
+INSTALL= _install() { set -e; \
+ dashb=$$1; [ $$1 = -b ] && shift; \
+ d=$(DESTDIR)$$3; f=$$d/$$(basename $$4); \
+ if [ ! -d $$d ]; then echo MKDIR $$3; mkdir -p $$d; fi; \
+ echo INSTALL $$($(CLEANNAME) $(BASE)$$2); \
+ [ -n "$(noisycc)" ] && set -x; \
+ if [ "$$dashb" = -b ]; \
+ then cp -f $$2 $$f; \
+ else $(FILTER) <$$2 >$$f; \
+ fi; \
+ chmod $$1 $$f; \
+ set +x; \
+ }; _install
+UNINSTALL= _uninstall() { set -e; \
+ echo UNINSTALL $$($(CLEANNAME) $(BASE)$$2); \
+ [ -n "$(noisycc)" ] && set -x; \
+ rm -f $(DESTDIR)$$3/$$(basename $$4); \
+ }; _uninstall
+
+.out.install:
+ $(INSTALL) -b 0755 $< $(BIN) $*
+.out.uninstall:
+ $(UNINSTALL) $< $(BIN) $*
+
+.a.install .$(SOEXT).install:
+ $(INSTALL) -b 0644 $< $(LIBDIR) $<
+.a.uninstall .$(SOEXT).uninstall:
+ $(UNINSTALL) $< $(LIBDIR) $<
+
+.h.install:
+ $(INSTALL) 0644 $< $(INCLUDE) $<
+.h.uninstall:
+ $(UNINSTALL) $< $(INCLUDE) $<
+
+.pdf.install:
+ $(INSTALL) -b 0644 $< $(DOC) $<
+.pdf.uninstall:
+ $(UNINSTALL) $< $(DOC) $<
+
+INSTALMAN= _installman() { man=$${1\#\#*.}; $(INSTALL) 0644 $$1 $(MAN)/man$$man $$1; }; _installman
+UNINSTALLMAN=_uninstallman() { man=$${1\#\#*.}; $(UNINSTALL) $$1 $(MAN)/man$$man $$1; }; _uninstallman
+MANSECTIONS=1 2 3 4 5 6 7 8 9
+${MANSECTIONS:%=.%.install}:
+ $(INSTALMAN) $<
+${MANSECTIONS:%=.%.uninstall}:
+ $(UNINSTALL) $<
+
+.out.clean:
+ echo CLEAN $$($(CLEANNAME) $(BASE)$<)
+ rm -f $< || true 2>/dev/null
+ rm -f $*.o || true 2>/dev/null
+.o.clean .o_pic.clean:
+ echo CLEAN $$($(CLEANNAME) $(BASE)$<)
+ rm -f $< || true 2>/dev/null
+
+printinstall:
+clean:
+install: printinstall
+depend: cleandep
+
+include $(ROOT)/mk/common.mk
+
diff --git a/mk/ixp.mk b/mk/ixp.mk
new file mode 100644
index 0000000..84c13aa
--- /dev/null
+++ b/mk/ixp.mk
@@ -0,0 +1,5 @@
+VERSION = 0.5
+
+$(ROOT)/include/ixp.h: $(ROOT)/config.mk
+CFLAGS += '-DVERSION=\"$(VERSION)\"' -D_XOPEN_SOURCE=600
+
diff --git a/mk/lib.mk b/mk/lib.mk
new file mode 100644
index 0000000..a557520
--- /dev/null
+++ b/mk/lib.mk
@@ -0,0 +1,32 @@
+PTARG = $(ROOT)/lib/$(TARG)
+LIB = $(PTARG).a
+OFILES = ${OBJ:=.o}
+
+all: $(HFILES) $(LIB)
+
+install: $(PTARG).install
+uninstall: $(PTARG).uninstall
+clean: libclean
+depend: ${OBJ:=.depend}
+
+libclean:
+ for i in $(LIB) $(OFILES); do \
+ [ -e $$i ] && \
+ echo CLEAN $$($(CLEANNAME) $(BASE)$$i); \
+ rm -f $$i; \
+ done 2>/dev/null || true
+
+printinstall:
+ echo 'Install directories:'
+ echo ' Lib: $(LIBDIR)'
+
+$(LIB): $(OFILES)
+ echo AR $$($(CLEANNAME) $(BASE)/$@)
+ mkdir $(ROOT)/lib 2>/dev/null || true
+ $(AR) $@ $(OFILES)
+
+SOMKSH=case "$(MAKESO)" in 1|[Yy][Ee][Ss]|[Tt][Rr][Uu][Ee]) echo $(ROOT)/mk/so.mk;; *) echo /dev/null;; esac
+SOMK:=${shell $(SOMKSH)}
+SOMK!=$(SOMKSH)
+include $(SOMK)
+
diff --git a/mk/man.mk b/mk/man.mk
new file mode 100644
index 0000000..8eba847
--- /dev/null
+++ b/mk/man.mk
@@ -0,0 +1,9 @@
+
+all: $(TARG)
+install: ${TARG:.1=.install}
+uninstall: ${TARG:.1=.uninstall}
+
+printinstall:
+ echo 'Install directories:'
+ echo ' Man: $(MAN)'
+
diff --git a/mk/many.mk b/mk/many.mk
new file mode 100644
index 0000000..78ac1be
--- /dev/null
+++ b/mk/many.mk
@@ -0,0 +1,20 @@
+PROGS = ${TARG:=.out}
+
+all: $(OFILES) $(PROGS)
+
+install: ${TARG:=.install}
+uninstall: ${TARG:=.uninstall}
+depend: ${OFILES:.o=.depend} ${TARG:=.depend}
+clean: manyclean
+
+printinstall:
+ echo 'Install directories:'
+ echo ' Bin: $(BIN)'
+
+manyclean:
+ for i in ${TARG:=.o} ${TARG:=.out} $(OFILES); do \
+ [ -e $$i ] && \
+ echo CLEAN $$($(CLEANNAME) $(BASE)$$i); \
+ rm -f $$i; \
+ done 2>/dev/null || true
+
diff --git a/mk/one.mk b/mk/one.mk
new file mode 100644
index 0000000..992f31c
--- /dev/null
+++ b/mk/one.mk
@@ -0,0 +1,26 @@
+PROG = $(TARG).out
+OFILES = ${OBJ:=.o}
+
+all: $(PROG)
+
+install: $(TARG).install
+uninstall: $(TARG).uninstall
+clean: oneclean
+depend: ${OBJ:=.depend}
+
+printinstall:
+ echo 'Install directories:'
+ echo ' Bin: $(BIN)'
+
+oneclean:
+ for i in $(PROG) $(OFILES); do \
+ [ -e $$i ] && \
+ echo CLEAN $$($(CLEANNAME) $(BASE)$$i); \
+ rm -f $$i; \
+ done 2>/dev/null || true
+
+$(OFILES): $(HFILES)
+
+$(PROG): $(OFILES) $(LIB)
+ $(LINK) $@ $(OFILES) $(LIB)
+
diff --git a/mk/so.mk b/mk/so.mk
new file mode 100644
index 0000000..25cd81d
--- /dev/null
+++ b/mk/so.mk
@@ -0,0 +1,29 @@
+SOPTARG = $(ROOT)/lib/$(TARG)
+SO = $(SOPTARG).$(SOEXT)
+SONAME = $(TARG).$(SOEXT)
+OFILES_PIC = ${OBJ:=.o_pic}
+
+all: $(HFILES) $(SO)
+
+install: $(SOPTARG).install
+uninstall: $(SOPTARG).uninstall
+clean: soclean
+depend: ${OBJ:=.depend}
+
+soclean:
+ for i in $(SO) $(OFILES_PIC); do \
+ [ -e $$i ] && \
+ echo CLEAN $$($(CLEANNAME) $(BASE)$$i); \
+ rm -f $$i; \
+ done 2>/dev/null || true
+
+printsoinstall:
+ echo 'Install directories:'
+ echo ' Lib: $(LIBDIR)'
+
+printinstall: printsoinstall
+
+$(SO): $(OFILES_PIC)
+ mkdir $(ROOT)/lib 2>/dev/null || true
+ $(LINKSO) $@ $(OFILES_PIC)
+
diff --git a/mk/wmii.mk b/mk/wmii.mk
new file mode 100644
index 0000000..9170ebb
--- /dev/null
+++ b/mk/wmii.mk
@@ -0,0 +1,17 @@
+VERSION = 3.9.2
+CONFVERSION =
+COPYRIGHT = ©2010 Kris Maglione
+SUBMAKE_EXPORT = WMII_HGVERSION=""
+
+LIBS9 = $(ROOT)/lib/libregexp9.a $(ROOT)/lib/libbio.a $(ROOT)/lib/libfmt.a $(ROOT)/lib/libutf.a
+
+CFLAGS += '-DVERSION=\"$(VERSION)\"' '-DCOPYRIGHT=\"$(COPYRIGHT)\"' \
+ '-DCONFVERSION=\"$(CONFVERSION)\"' '-DCONFPREFIX=\"$(ETC)\"'
+FILTER = sed "s|@CONFPREFIX@|$(ETC)|g; \
+ s|@CONFVERSION@|$(CONFVERSION)|g; \
+ s|@DOCDIR@|$(DOC)|g; \
+ s|@VERSION@|$(VERSION)|g; \
+ s|@LIBDIR@|$(LIBDIR)|g; \
+ s|@BINSH@|$(BINSH)|g; \
+ s|@TERMINAL@|$(TERMINAL)|g;"
+
diff --git a/rc/Makefile b/rc/Makefile
new file mode 100644
index 0000000..23631b7
--- /dev/null
+++ b/rc/Makefile
@@ -0,0 +1,9 @@
+ROOT=..
+include $(ROOT)/mk/hdr.mk
+include $(ROOT)/mk/wmii.mk
+
+BIN = $(ETC)/wmii$(CONFVERSION)
+TARG = wmiirc \
+ welcome
+
+include $(ROOT)/mk/many.mk
diff --git a/rc/sh.wmii b/rc/sh.wmii
new file mode 100755
index 0000000..32758b4
--- /dev/null
+++ b/rc/sh.wmii
@@ -0,0 +1,289 @@
+#!sh
+# WMII Configuration
+load std string regex expr echo
+
+argv0 = $0
+
+#mount -Aa {os dial unix!/tmp/ns.kris.:1/wmii >[1=0]} / || raise mount
+#mount -Aa {styxmon {os rc -c 'dial $WMII_ADDRESS' >[1=0]}} / || raise mount
+mount -Aa {os rc -c 'dial $WMII_ADDRESS' >[1=0]} / || raise mount
+
+{`{read} && echo halt>/dev/sysctl}&
+
+MODKEY=Mod1
+UP=k
+DOWN=j
+LEFT=h
+RIGHT=l
+
+WMII_FONT=fixed
+WMII_NORMCOLORS=('#222222' '#5FBF77' '#2A7F3F')
+WMII_FOCUSCOLORS=('#ffffff' '#153F1F' '#2A7F3F')
+WMII_BACKGROUND='#333333'
+
+WMII_TERM=urxvt
+
+fn wmiimenu {
+ (nf nb nil sf sb nil) := ($WMII_NORMCOLORS $WMII_FOCUSCOLORS)
+ os -d ${hostenv HOME} (
+ dmenu -b -fn $WMII_FONT
+ -nf $nf -nb $nb -sf $sf -sb $sb)
+}
+
+fn 9menu {
+ os -d ${hostenv HOME} (
+ wmii9menu -font ${hd ${split , $WMII_FONT}}
+ -^(nf nb br)^$WMII_NORMCOLORS
+ -^(sf sb br)^$WMII_FOCUSCOLORS $*)
+}
+
+# Column Rules
+echo '/./ -> 60+40' >/colrules
+
+# Tagging Rules
+echo '
+/Firefox/ -> www
+/XMMS.*/ -> ~
+/MPlayer.*/ -> ~
+/.*/ -> sel
+' >/tagrules
+
+subfn seq {
+ result=${expr $* seq}
+}
+
+subfn hostenv {
+ arg := $"*
+ result="{os rc -c 'echo -n $'^$arg </dev/null}
+}
+
+subfn ftl {
+ result=${tl $*}
+ result=$"result
+}
+
+subfn lines {
+ ifs := "{echo}
+ arg := $*
+ result = `{$arg}
+}
+
+fn 'fn?' {
+ args := $*
+ ~ ("{rescue '*' {} {whatis $args >[2]/dev/null}}
+ 'load std; fn '*)
+}
+
+fn ifx {
+ (pred fn val args) := $*
+ if {$pred $val} {$fn $val $args}
+}
+
+fn dofn {
+ ifx 'fn?' {$*} $*
+}
+
+fn run_command {
+ os -b -d ${hostenv HOME} $* &
+}
+
+fn dprint {
+ arg := $*
+ or {~ $#debug 0} {~ $debug '' 0} { echo $arg }
+}
+
+subfn config_whatis {
+ result=${lines {os rc -c 'PATH=$WMII_CONFPATH which $*' $* </dev/null} $*}
+}
+
+# Status Bar Info
+fn status {
+ echo ${re mg '[0-9]+\.[0-9]+' "{os uptime}} '|' `{date}
+}
+
+for(i in Key Event Action) {
+ '{fn $i { fn '$i'-$1 ${tl $*} }}'
+}
+
+# Events
+Event Start {
+ if {~ $1 wmiirc} {
+ rm -f $progs_file
+ exit
+ }
+}
+
+Event Key {
+ dprint Key-$1
+ Key-$1 $1
+}
+
+Event CreateTag { echo $WMII_NORMCOLORS $* > /lbar/$"* }
+Event DestroyTag { rm /lbar/$"* }
+Event FocusTag { echo $WMII_FOCUSCOLORS $* > /lbar/$"* }
+Event UnfocusTag { echo $WMII_NORMCOLORS $* > /lbar/$"* }
+Event UrgentTag { echo '*'${ftl $*} > /lbar/${ftl $*} }
+Event NotUrgentTag { echo ${tl $*} > /lbar/${ftl $*} }
+
+Event LeftBarClick {
+ (button name) := $*
+ if {~ $button 1} { echo view $name >/ctl }
+}
+Event LeftBarMouseDown {
+ (button name) := $*
+ if {~ $button 3} { echo view "{9menu ${lines read_tags}} >/ctl & }
+}
+lastcmd=''
+Event ClientMouseDown {
+ (client button) := $*
+ if {~ $button 3} {
+ lastcmd = `{9menu -initial $lastcmd Nop Delete Fullscreen}
+ if{~ $#lastcmd 0} {lastcmd=''}
+ cmp := {~ $lastcmd $*}
+ if {$cmp Nop} {
+ } {$cmp Delete} { echo kill >/client/$client/ctl
+ } {$cmp Fullscreen} { echo Fullscreen toggle >/client/$client/ctl
+ }
+ }
+}
+
+# Actions
+Action quit { echo quit >>/ctl }
+Action rehash {
+ flag x -
+ proglist ${hostenv PATH} >$progs_file
+}
+Action status {
+ flag x -
+ if {rm /rbar/status >[2]/dev/null} { sleep 1 }
+ echo $WMII_NORMCOLORS >/rbar/status
+ while {status >/rbar/status} { sleep 1 }
+}
+
+ifx {ftest -x $*} {run $*} $home/.wmii-3.5/sh.wmii.local
+fn Key { ifx {! 'fn?' $*} {fn $*} Key-$1 ${tl $*} }
+
+fn Action {
+ (action args) := $*
+ or {dofn Action-$action $args} {
+ ifx {! ~ $#* 0} {run_command $*} ${config_whatis $action} $args
+ }
+}
+
+# Key Bindings
+Key $MODKEY-Control-t {
+ if { ~ `{wc -l /keys} 0 1} {
+ initkeys
+ echo grabmod $MODKEY >/ctl
+ } {
+ echo $MODKEY-Control-t >/keys
+ echo grabmod Mod3 >/ctl
+ }
+}
+
+Key $MODKEY-$LEFT { echo select left >/tag/sel/ctl }
+Key $MODKEY-$RIGHT { echo select right >/tag/sel/ctl }
+Key $MODKEY-$UP { echo select up >/tag/sel/ctl }
+Key $MODKEY-$DOWN { echo select down >/tag/sel/ctl }
+
+Key $MODKEY-Shift-$LEFT { echo send sel left >/tag/sel/ctl }
+Key $MODKEY-Shift-$RIGHT { echo send sel right >/tag/sel/ctl }
+Key $MODKEY-Shift-$DOWN { echo send sel down >/tag/sel/ctl }
+Key $MODKEY-Shift-$UP { echo send sel up >/tag/sel/ctl }
+
+Key $MODKEY-space { echo select toggle >/tag/sel/ctl }
+Key $MODKEY-Shift-space { echo send sel toggle >/tag/sel/ctl }
+
+Key $MODKEY-d { echo colmode sel default >/tag/sel/ctl }
+Key $MODKEY-s { echo colmode sel stack >/tag/sel/ctl }
+Key $MODKEY-m { echo colmode sel max >/tag/sel/ctl }
+
+Key $MODKEY-f { echo Fullscreen toggle >/client/sel/ctl }
+
+Key $MODKEY-Shift-c { echo kill >/client/sel/ctl }
+
+Key $MODKEY-a { Action `{actionlist | wmiimenu} & }
+Key $MODKEY-p { run_command rc -c "{wmiimenu <$progs_file} & }
+Key $MODKEY-Return { run_command $WMII_TERM & }
+Key $MODKEY-t { echo view `{read_tags | wmiimenu} >/ctl & }
+Key $MODKEY-Shift-t {
+ sel := "{cat /client/sel/ctl}
+ read_tags | wmiimenu >/client/$sel/tags
+}
+
+Key $MODKEY-^${seq 0 9} { echo view ${tl ${splitr $1 -}} >/ctl }
+Key Shift-$MODKEY-${seq 0 9} { echo ${tl ${splitr $1 -}} >/client/sel/tags}
+
+# Functions
+fn proglist {
+ os find ${split : $"*} -maxdepth 1 -type f </dev/null | sed 's,.*/,,' | sort | uniq
+ #for(d in /n/local^${split : $"*}) {
+ # fs filter {mode -d} $d
+ #} | sed 's,.*/,,' | sort | uniq
+}
+
+fn getfuns {
+ ls -p /env | sed -n 's/^fn-' ^ $1 ^ '-//p'
+}
+
+fn actionlist {
+ { rescue '*' {} {
+ proglist ${hostenv WMII_CONFPATH}
+ }
+ getfuns Action
+ } | sort | uniq
+}
+
+fn initkeys {
+ getfuns Key >/keys
+}
+
+fn read_tags {
+ ls -p /tag | grep -v '^sel$'
+}
+
+# WM Configuration
+{
+ echo grabmod $MODKEY
+ echo border 2
+ echo font $WMII_FONT
+ echo focuscolors $WMII_FOCUSCOLORS
+ echo normcolors $WMII_NORMCOLORS
+} >/ctl
+
+# Misc Setup
+os xsetroot -solid $WMII_BACKGROUND </dev/null
+
+dofn Local-Overrides
+
+Action status &
+progs_file=/tmp/proglist.${pid}
+Action rehash &
+
+# Tag Bar Setup
+seltag=${lines sed 1q /tag/sel/ctl}
+comm -13 ${pipe from {read_tags}} ${pipe from {ls -p /lbar/*}} |
+ getlines { rm /lbar/$line }
+read_tags | getlines {
+ if {~ $line $seltag} {
+ echo $WMII_FOCUSCOLORS $line
+ } {
+ echo $WMII_NORMCOLORS $line
+ } >/lbar/$line
+}
+
+# Keygrab Setup
+initkeys
+
+echo Start wmiirc >/event
+
+# Event Loop
+getlines {
+ (event args) := ${split ' ' $line}
+ dprint Event-$event: $args
+ rescue '*' { dprint Exception: $exception } {
+ dofn Event-$event $args
+ } </dev/null
+ dprint loop
+} </event
+
diff --git a/rc/welcome.sh b/rc/welcome.sh
new file mode 100644
index 0000000..e17e271
--- /dev/null
+++ b/rc/welcome.sh
@@ -0,0 +1,61 @@
+#!/bin/sh
+# display a welcome message that contains the wmii tutorial
+
+xmessage -file - <<'EOF'
+Welcome to wmii, the non-wimp environment of the Suckless Project.
+
+This is a small step by step tutorial, intended to make you a
+little bit familiar with wmii. For a more detailed walkthrough,
+see @DOCDIR@/wmii.pdf.
+
+From here on, keypresses will be described such that M-a refers to
+pressing your modifier and a at the same time. The default modifier
+key, hereafter $MODKEY, is the Windows(R) key, but it may also be Alt.
+
+Let's go!
+
+ * Start two @TERMINAL@s by pressing M-Return twice.
+ * Switch between the three windows: M-j, M-k,
+ M*h, M-l
+ If you prefer to use the mouse, then just move the pointer to
+ the desired window.
+ * Try the other column modes: M-s for stack mode,
+ M*m for max mode Press M-d to return to default
+ mode.
+ * Create a new column with: M-Shift-l
+ This moves the client rightwards.
+ * Tag the selected client with another tag: M-Shift-2
+ IMPORTANT: before you do the next step, note that you
+ can select the current tag with M-1.
+ * Select the new tag: M-2
+ * Select the floating area: M-Space
+ * Open the programs menu: M-p
+ Type 'xclock' and press Enter.
+ * Move the xclock window: Hold $MODKEY, left-click on the
+ window and move the cursor around.
+ * Resize the xclock window: Hold $MODKEY, right-click the
+ window and move the cursor around.
+ * Kill the selected client (the xclock window) with: M-Shift-c
+ * Open the actions menu: M-a
+ Show the list of key bindings by selecting 'showkeys'
+ * We'll now have a look at the internal filesystem used by
+ wmii. Executing
+ wmiir ls /
+ in the shell of the terminal will list all the files in the
+ root directory.
+ Entries ending with / are directories.
+ If you are curious, you can now dig deeper into the
+ directory trees. For instance,
+ wmiir ls /rbar/
+ will show you the content of the right half of the bar.
+
+We hope that these steps gave you an idea of how wmii works.
+You can reread them at any time by pressing M-a and
+selecting 'welcome'.
+
+You should now take a look at the wmii(1) man page. A FAQ is
+available at <http://wmii.suckless.org>.
+
+Further documentation, including alternative configuration
+possibilities, is provided in @DOCDIR@.
+EOF
diff --git a/rc/wmiirc.sh b/rc/wmiirc.sh
new file mode 100644
index 0000000..4d439c6
--- /dev/null
+++ b/rc/wmiirc.sh
@@ -0,0 +1,269 @@
+#!@BINSH@ -f
+# Configure wmii
+wmiiscript=wmiirc # For wmii.sh
+. wmii.sh
+
+
+# Configuration Variables
+MODKEY=Mod4
+UP=k
+DOWN=j
+LEFT=h
+RIGHT=l
+
+# Bars
+noticetimeout=5
+noticebar=/rbar/!notice
+
+# Colors tuples: "<text> <background> <border>"
+export WMII_NORMCOLORS='#000000 #c1c48b #81654f'
+export WMII_FOCUSCOLORS='#000000 #81654f #000000'
+
+export WMII_BACKGROUND='#333333'
+export WMII_FONT='-*-fixed-medium-r-*-*-13-*-*-*-*-*-*-*'
+
+set -- $(echo $WMII_NORMCOLORS $WMII_FOCUSCOLORS)
+export WMII_TERM="@TERMINAL@"
+
+if ! test -d "${WMII_CONFPATH%%:*}"; then
+ mkdir "${WMII_CONFPATH%%:*}"
+ res=$(wihack -type DIALOG xmessage -nearmouse -buttons Windows,Alt -print -fn $WMII_FONT \
+ "Welcome to wmii,$wi_newline$wi_newline" \
+ "Most of wmii's default key bindings make use of the$wi_newline" \
+ "Windows key, or equivalent. For keyboards lacking such$wi_newline" \
+ "a key, many users change this to the Alt key.$wi_newline$wi_newline" \
+ "Which would you prefer?")
+ [ "$res" = "Alt" ] && MODKEY=Mod1
+ echo "MODKEY=$MODKEY" >"${WMII_CONFPATH%%:*}/wmiirc_local"
+ chmod +x "${WMII_CONFPATH%%:*}/wmiirc_local"
+fi
+
+# Menu history
+hist="${WMII_CONFPATH%%:*}/history"
+histnum=5000
+
+# Column Rules
+wmiir write /colrules <<!
+/gimp/ -> 17+83+41
+/.*/ -> 62+38 # Golden Ratio
+!
+
+# Tagging Rules
+wmiir write /tagrules <<!
+/MPlayer|VLC/ -> ~
+!
+
+# Status Bar Info
+status() {
+ echo -n $(uptime | sed 's/.*://; s/,//g') '|' $(date)
+}
+
+local_events() { true;}
+wi_runconf -s wmiirc_local
+
+echo $WMII_NORMCOLORS | wmiir create $noticebar
+
+# Event processing
+events() {
+ cat <<'!'
+# Events
+Event CreateTag
+ echo "$WMII_NORMCOLORS" "$@" | wmiir create "/lbar/$@"
+Event DestroyTag
+ wmiir remove "/lbar/$@"
+Event FocusTag
+ wmiir xwrite "/lbar/$@" "$WMII_FOCUSCOLORS" "$@"
+Event UnfocusTag
+ wmiir xwrite "/lbar/$@" "$WMII_NORMCOLORS" "$@"
+Event UrgentTag
+ shift
+ wmiir xwrite "/lbar/$@" "*$@"
+Event NotUrgentTag
+ shift
+ wmiir xwrite "/lbar/$@" "$@"
+Event LeftBarClick LeftBarDND
+ shift
+ wmiir xwrite /ctl view "$@"
+Event Unresponsive
+ {
+ client=$1; shift
+ msg="The following client is not responding. What would you like to do?$wi_newline"
+ resp=$(wihack -transient $client \
+ xmessage -nearmouse -buttons Kill,Wait -print
+ -fn "${WMII_FONT%%,*}" "$msg $(wmiir read /client/sel/label)")
+ if [ "$resp" = Kill ]; then
+ wmiir xwrite /client/$client/ctl slay &
+ fi
+ }&
+Event Notice
+ wmiir xwrite $noticebar $wi_arg
+
+ kill $xpid 2>/dev/null # Let's hope this isn't reused...
+ { sleep $noticetimeout; wmiir xwrite $noticebar ' '; }&
+ xpid = $!
+
+# Menus
+Menu Client-3-Delete
+ wmiir xwrite /client/$1/ctl kill
+Menu Client-3-Kill
+ wmiir xwrite /client/$1/ctl slay
+Menu Client-3-Fullscreen
+ wmiir xwrite /client/$1/ctl Fullscreen on
+Event ClientMouseDown
+ wi_fnmenu Client $2 $1 &
+
+Menu LBar-3-Delete
+ tag=$1; clients=$(wmiir read "/tag/$tag/index" | awk '/[^#]/{print $2}')
+ for c in $clients; do
+ if [ "$tag" = "$(wmiir read /client/$c/tags)" ]; then
+ wmiir xwrite /client/$c/ctl kill
+ else
+ wmiir xwrite /client/$c/tags -$tag
+ fi
+ if [ "$tag" = "$(wi_seltag)" ]; then
+ newtag=$(wi_tags | awk -v't='$tag '
+ $1 == t { if(!l) getline l
+ print l
+ exit }
+ { l = $0 }')
+ wmiir xwrite /ctl view $newtag
+ fi
+ done
+Event LeftBarMouseDown
+ wi_fnmenu LBar "$@" &
+
+# Actions
+Action showkeys
+ echo "$KeysHelp" | xmessage -file - -fn ${WMII_FONT%%,*}
+Action quit
+ wmiir xwrite /ctl quit
+Action exec
+ wmiir xwrite /ctl exec "$@"
+Action rehash
+ wi_proglist $PATH >$progsfile
+Action status
+ set +xv
+ if wmiir remove /rbar/status 2>/dev/null; then
+ sleep 2
+ fi
+ echo "$WMII_NORMCOLORS" | wmiir create /rbar/status
+ while status | wmiir write /rbar/status; do
+ sleep 1
+ done
+
+# Key Bindings
+KeyGroup Moving around
+Key $MODKEY-$LEFT # Select the client to the left
+ wmiir xwrite /tag/sel/ctl select left
+Key $MODKEY-$RIGHT # Select the client to the right
+ wmiir xwrite /tag/sel/ctl select right
+Key $MODKEY-$UP # Select the client above
+ wmiir xwrite /tag/sel/ctl select up
+Key $MODKEY-$DOWN # Select the client below
+ wmiir xwrite /tag/sel/ctl select down
+
+Key $MODKEY-space # Toggle between floating and managed layers
+ wmiir xwrite /tag/sel/ctl select toggle
+
+KeyGroup Moving through stacks
+Key $MODKEY-Control-$UP # Select the stack above
+ wmiir xwrite /tag/sel/ctl select up stack
+Key $MODKEY-Control-$DOWN # Select the stack below
+ wmiir xwrite /tag/sel/ctl select down stack
+
+KeyGroup Moving clients around
+Key $MODKEY-Shift-$LEFT # Move selected client to the left
+ wmiir xwrite /tag/sel/ctl send sel left
+Key $MODKEY-Shift-$RIGHT # Move selected client to the right
+ wmiir xwrite /tag/sel/ctl send sel right
+Key $MODKEY-Shift-$UP # Move selected client up
+ wmiir xwrite /tag/sel/ctl send sel up
+Key $MODKEY-Shift-$DOWN # Move selected client down
+ wmiir xwrite /tag/sel/ctl send sel down
+
+Key $MODKEY-Shift-space # Toggle selected client between floating and managed layers
+ wmiir xwrite /tag/sel/ctl send sel toggle
+
+KeyGroup Client actions
+Key $MODKEY-f # Toggle selected client's fullsceen state
+ wmiir xwrite /client/sel/ctl Fullscreen toggle
+Key $MODKEY-Shift-c # Close client
+ wmiir xwrite /client/sel/ctl kill
+
+KeyGroup Changing column modes
+Key $MODKEY-d # Set column to default mode
+ wmiir xwrite /tag/sel/ctl colmode sel default-max
+Key $MODKEY-s # Set column to stack mode
+ wmiir xwrite /tag/sel/ctl colmode sel stack-max
+Key $MODKEY-m # Set column to max mode
+ wmiir xwrite /tag/sel/ctl colmode sel stack+max
+
+KeyGroup Running programs
+Key $MODKEY-a # Open wmii actions menu
+ action $(wi_actions | wimenu -h "${hist}.actions" -n $histnum) &
+Key $MODKEY-p # Open program menu
+ eval wmiir setsid "$(wimenu -h "${hist}.progs" -n $histnum <$progsfile)" &
+
+Key $MODKEY-Return # Launch a terminal
+ eval wmiir setsid $WMII_TERM &
+
+KeyGroup Other
+Key $MODKEY-Control-t # Toggle all other key bindings
+ case $(wmiir read /keys | wc -l | tr -d ' \t\n') in
+ 0|1)
+ echo -n "$Keys" | wmiir write /keys
+ wmiir xwrite /ctl grabmod $MODKEY;;
+ *)
+ wmiir xwrite /keys $MODKEY-Control-t
+ wmiir xwrite /ctl grabmod Mod3;;
+ esac
+
+KeyGroup Tag actions
+Key $MODKEY-t # Change to another tag
+ (tag=$(wi_tags | wimenu -h "${hist}.tags" -n 50) && wmiir xwrite /ctl view $tag) &
+Key $MODKEY-Shift-t # Retag the selected client
+ c=$(wi_selclient)
+ (tag=$(wi_tags | wimenu -h "${hist}.tags" -n 50) && wmiir xwrite /client/$c/tags $tag) &
+!
+ for i in 0 1 2 3 4 5 6 7 8 9; do
+ cat <<!
+Key $MODKEY-$i # Move to the numbered view
+ wmiir xwrite /ctl view "$i"
+Key $MODKEY-Shift-$i # Retag selected client with the numbered tag
+ wmiir xwrite /client/sel/tags "$i"
+!
+ done
+}
+wi_events events local_events
+
+# WM Configuration
+wmiir write /ctl <<!
+ font $WMII_FONT
+ focuscolors $WMII_FOCUSCOLORS
+ normcolors $WMII_NORMCOLORS
+ grabmod $MODKEY
+ border 1
+!
+xsetroot -solid "$WMII_BACKGROUND" &
+
+# Misc
+progsfile="$(wmiir namespace)/.proglist"
+action status &
+wi_proglist $PATH >$progsfile &
+
+# Setup Tag Bar
+IFS="$wi_newline"
+wmiir rm $(wmiir ls /lbar | sed 's,^,/lbar/,') >/dev/null
+seltag=$(wmiir read /tag/sel/ctl | sed 1q)
+unset IFS
+wi_tags | while read tag
+do
+ if [ "$tag" = "$seltag" ]; then
+ echo "$WMII_FOCUSCOLORS" "$tag"
+ else
+ echo "$WMII_NORMCOLORS" "$tag"
+ fi | wmiir create "/lbar/$tag"
+done
+
+wi_eventloop
+
diff --git a/test/Makefile b/test/Makefile
new file mode 100644
index 0000000..f1bfd73
--- /dev/null
+++ b/test/Makefile
@@ -0,0 +1,14 @@
+ROOT=..
+include $(ROOT)/mk/hdr.mk
+
+TARG = grav
+
+OFILES = ../cmd/util.o \
+ ../cmd/wmii/map.o \
+ ../cmd/wmii/x11.o
+
+LDFLAGS += $(OFILES) -lfmt -lutf -lbio $(LIBX11) -lXext
+CFLAGS += $(INCX11)
+
+include $(ROOT)/mk/many.mk
+
diff --git a/test/event b/test/event
new file mode 100755
index 0000000..7a9a7d6
--- /dev/null
+++ b/test/event
@@ -0,0 +1,19 @@
+#!/bin/rc
+
+wm=''
+if(~ $1 -d) {
+ wm = '&wm/wm wm/logon</dev/null'
+ shift
+}
+
+inferno '{$home/wmii/test/event.dis $*'$wm'}' $* &
+inf = $apid
+
+. 9.rc
+
+fn sigint sigterm {exit}
+fn sigexit {/bin/kill $apid}
+
+while(! ~ `{read </dev/tty} q)
+ true
+
diff --git a/test/event.b b/test/event.b
new file mode 100644
index 0000000..0c13c32
--- /dev/null
+++ b/test/event.b
@@ -0,0 +1,148 @@
+implement Event;
+
+include "sys.m";
+ sys: Sys;
+ OREAD, OWRITE: import Sys;
+ print, sprint, read, open: import sys;
+include "draw.m";
+include "string.m";
+ str: String;
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "lists.m";
+ lists: Lists;
+ append, reverse: import lists;
+include "regex.m";
+ regex: Regex;
+ Re: import regex;
+include "sh.m";
+ sh: Sh;
+
+Event: module
+{
+ init: fn(nil: ref Draw->Context, argv: list of string);
+};
+
+line: chan of string;
+
+suicide()
+{
+ fd := open(sprint("/proc/%d/pgrp", sys->pctl(0, nil)), OWRITE);
+ sys->fprint(fd, "kill");
+}
+
+buflines(in, out: chan of string)
+{
+ lines: list of string;
+ for(;;) {
+ if(lines == nil)
+ lines = <-in :: nil;
+ alt {
+ l := <-in =>
+ lines = append(lines, l);
+ out <-= hd lines =>
+ if(hd lines == nil)
+ suicide();
+ lines = tl lines;
+ }
+ }
+}
+
+readlines(c: chan of string, fd: ref sys->FD)
+{
+ out := chan of string;
+
+ spawn buflines(out, c);
+
+ b := bufio->fopen(fd, OREAD);
+ while((s := b.gets('\n')) != nil)
+ out <-= s;
+ out <-= nil;
+}
+
+readfile(file: string): (string, int)
+{
+ fd := open(file, OREAD);
+ if(fd == nil)
+ return ("", 0);
+
+ ret := "";
+ buf := array[512] of byte;
+ while((n := read(fd, buf, len buf)) > 0)
+ ret += string buf[:n];
+ return (ret, 1);
+}
+
+ishex(s: string): int
+{
+ if(len s < 3 || s[0:2] != "0x")
+ return 0;
+ s = s[2:];
+ (nil, end) := str->toint(s, 16);
+ return end == nil;
+}
+
+init(draw: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ str = load String String->PATH;
+ bufio = load Bufio Bufio->PATH;
+ lists = load Lists "/dis/lib/lists.dis";
+ regex = load Regex Regex->PATH;
+ sh = load Sh Sh->PATH;
+
+ sys->pctl(sys->NEWPGRP, nil);
+
+ sh->system(draw, "mount -A {os rc -c 'exec dial $WMII_ADDRESS' >[1=0]} /mnt/wmii &");
+
+ line = chan of string;
+ spawn readlines(line, sys->fildes(0));
+
+ relist: list of ref (Re, int);
+
+ argv = tl argv;
+ for(; argv != nil; argv = tl argv) {
+ vflag := 0;
+ if(hd argv == "-v") {
+ argv = tl argv;
+ vflag = 1;
+ }
+ (re, err) := regex->compile(hd argv, 0);
+ if(err != nil)
+ raise sprint("bad regex %q: %s", hd argv, err);
+ relist = ref (re, vflag) :: relist;
+ }
+
+ relist = reverse(relist);
+
+line: for(;;) {
+ lin := <-line;
+ if(lin == nil)
+ break;
+ l := str->unquoted(lin);
+ if(l == nil)
+ continue;
+
+ (evn, end) := str->toint(hd l, 10);
+ if(end == nil) {
+ for(rel := relist; rel != nil; rel = tl relist) {
+ (re, vflag) := *(hd rel);
+ match := regex->execute(re, lin);
+ if((match == nil) != vflag)
+ continue line;
+ }
+ print("%s", lin);
+ for(; l != nil; l = tl l) {
+ (k, v) := str->splitstrr(hd l, "=");
+ if(ishex(v)) {
+ (name, ok) := readfile(sprint("/mnt/wmii/client/%s/props", v));
+ if(ok)
+ print("%d %s%s\n", evn, k, name);
+ }
+ }
+ }else
+ print("%s", lin);
+ }
+}
+
diff --git a/test/grav.c b/test/grav.c
new file mode 100644
index 0000000..3b31b9f
--- /dev/null
+++ b/test/grav.c
@@ -0,0 +1,128 @@
+#if 0
+ set -e
+ name=grav
+ root=..
+ obj=$root/cmd
+ lib=$root/lib
+ inc=$root/include
+ cc -I$inc -I/usr/local/include \
+ -o o.$name \
+ -Wall \
+ $name.c \
+ $obj/util.o \
+ $obj/wmii/map.o \
+ $obj/wmii/x11.o \
+ -L$lib -lfmt -lutf -lbio \
+ -L/usr/local/lib -lX11 -lXext \
+
+ exec o.$name
+#endif
+#include <fmt.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <util.h>
+#include <x11.h>
+
+char buffer[8196];
+void debug() {}
+
+static Window* win;
+
+static char* gravity[] = {
+ [NorthEastGravity] = "NorthEastGravity",
+ [NorthWestGravity] = "NorthWestGravity",
+ [SouthEastGravity] = "SouthEastGravity",
+ [SouthWestGravity] = "SouthWestGravity",
+ [StaticGravity] = "StaticGravity",
+};
+
+static void
+draw(Window *w) {
+ Rectangle r;
+
+ r = w->r;
+ r = rectsubpt(r, r.min);
+
+ fill(w, r, 0UL);
+ border(w, Rect(3, 3, 97, 97), 2, ~0UL);
+ border(w, Rect(8, 8, 92, 92), 2, ~0UL);
+ sync();
+}
+
+static void
+setgravity(Window *w, long gravity) {
+ XSizeHints wmh;
+
+ wmh.flags = PWinGravity;
+ wmh.win_gravity = gravity;
+ XSetWMNormalHints(display, w->w, &wmh);
+}
+
+static void
+config(Window *w, long grav, Point p) {
+ Rectangle r;
+
+ r = rectsetorigin(Rect(0, 0, 100, 100), p);
+
+ print("%s: %R\n", gravity[grav], r);
+
+ setgravity(w, grav);
+ w->r = ZR; /* Kludge. */
+ reshapewin(w, r);
+ draw(w);
+ sleep(1);
+}
+
+int
+main(void) {
+ XSizeHints wmh;
+ WinAttr wa;
+ XEvent ev;
+
+ initdisplay();
+
+ /* Kludge the bar height. */
+ scr.rect.max.y -= 14;
+
+ wa.background_pixmap = ParentRelative;
+ wa.event_mask = ExposureMask|StructureNotifyMask;
+ win = createwindow(&scr.root,
+ Rect(0, 0, 100, 100), scr.depth, InputOutput,
+ &wa, CWEventMask | CWBackPixmap);
+ XSelectInput(display, win->w, ExposureMask);
+
+ wmh.flags = PMinSize|PMaxSize|USPosition;
+ wmh.min_width = wmh.max_width = 100;
+ wmh.min_height = wmh.max_height = 100;
+ XSetWMNormalHints(display, win->w, &wmh);
+
+ mapwin(win);
+ raisewin(win);
+ XMaskEvent(display, ExposureMask, &ev);
+
+ draw(win);
+ sleep(2);
+
+ config(win, StaticGravity, Pt(0, 0));
+
+ config(win, NorthWestGravity,
+ Pt(0,
+ 0));
+
+ config(win, NorthEastGravity,
+ Pt(Dx(scr.rect) - 100,
+ 0));
+
+ config(win, SouthEastGravity,
+ Pt(Dx(scr.rect) - 100,
+ Dy(scr.rect) - 100));
+
+ config(win, SouthWestGravity,
+ Pt(0,
+ Dy(scr.rect) - 100));
+
+ sleep(1);
+ return 0;
+}
+
diff --git a/test/mkfile b/test/mkfile
new file mode 100644
index 0000000..5d3d724
--- /dev/null
+++ b/test/mkfile
@@ -0,0 +1,11 @@
+default:V: all
+
+TARG=\
+ event.dis\
+
+all:V: $TARG
+
+nuke:V: rm *.dis *.sbl
+
+%.dis: %.b
+ limbo -gw $stem.b
diff --git a/util/cleanname b/util/cleanname
new file mode 100755
index 0000000..988754d
--- /dev/null
+++ b/util/cleanname
@@ -0,0 +1,18 @@
+#!/bin/sh -f
+
+echo "$@" |
+ awk -F'/+' '{
+ delete a
+ n = 0
+ for(i = 1; i <= NF; i++) {
+ if($i == ".." && n > 0 && a[n] != "..")
+ n--
+ else if($i != "" && $i != ".")
+ a[++n] = $i
+ }
+ s = ""
+ for(i = 1; i <= n; i++)
+ s = s "/" a[i]
+ print substr(s, 2)
+ }'
+
diff --git a/util/compile b/util/compile
new file mode 100755
index 0000000..53f3f1d
--- /dev/null
+++ b/util/compile
@@ -0,0 +1,70 @@
+#!/bin/sh -f
+
+CC=$1
+CFLAGS=$2; shift 2
+
+outfile="$1"; shift
+bin="$(echo $0 | sed 's,/[^/]*$,,')"
+
+# Derived from Russ Cox's 9c in plan9port.
+
+xtmp=/tmp/cc.$$.$USER.out
+
+echo CC $($bin/cleanname ${BASE}$outfile)
+[ -n "$noisycc" ] && echo $CC -o $outfile $CFLAGS $@
+eval '$CC -o $outfile '"$CFLAGS"' $@ >$xtmp 2>&1'
+status=$?
+[ $? -eq 0 ] || echo $CC -o $outfile $CFLAGS $@ >&2
+
+base=$(echo $BASE | sed 's/,/\\,/g')
+re='\([^[:space:]/]*\..:[0-9]\)'
+
+undup() { # GCC is crap.
+ awk '
+ function shift() {
+ for(n=1; n<=3; n++)
+ if(2*n <= nl)
+ for(i=1; i<=n; i++) {
+ if(l[i] != l[i+n])
+ break;
+ if(i == n) {
+ for(i=1; i<=n; i++)
+ print l[i]
+ nl -= 2*n;
+ for(i=1; i<=nl; i++)
+ l[i] = l[i+2*n];
+ return;
+ }
+ }
+ if(nl == 0)
+ return
+ print l[1]
+ for(i=1; i<nl; i++)
+ l[i] = l[i+1]
+ nl--
+ }
+ BEGIN{
+ nl=0
+ maxl=6
+ }
+ {
+ if(nl == maxl)
+ shift()
+ l[++nl] = $0
+ }
+ END{
+ while(nl > 0)
+ shift();
+ }'
+}
+
+cat $xtmp | sed "s,^$re,$base&,g; s,\([[:space:]]\)$re,\1$base\2,g" |
+ egrep -v ': error: .Each undeclared identifier|: error: for each function it appears|is dangerous, better use|is almost always misused|: In function |: At top level:|support .long long.|use of C99 long long|ISO C forbids conversion|warning:.*warn_unused_result' |
+ sed 's/ .first use in this function.$//; s/\"\([^\"][^\"]*\)\", line \([0-9][0-9]*\)/\1:\2/g' |
+ awk '$1 == "warning:"{t=$2" "$1; sub(/^[^ ]+ [^ ]+ /, ""); $0 = t" "$0}; //' |
+ awk '{sub(/\[/, ": [", $1); print}' |
+ undup 1>&2
+
+rm -f $xtmp
+exit $status
+
diff --git a/util/genconfig b/util/genconfig
new file mode 100755
index 0000000..2287191
--- /dev/null
+++ b/util/genconfig
@@ -0,0 +1,236 @@
+#!/bin/sh -f
+
+# What I wouldn't do for rc...
+# Well... it's better than m4shgmake.
+
+CONFIG="$ROOT/config.mk"
+CONFSTR='# Generated by "make config"'
+
+# ==================== The Messy Part ====================
+
+#XXX Ignores lines ending in \
+parseconfig() {
+ cat <<'!' | sed "s/CONFSTR/$CONFSTR/"
+ /^CONFSTR/ {exit}
+ /^( *#| )/ {next}
+ function fixup() {
+ sub(/ #.*/, "")
+ sub(/^ */, "")
+ gsub(/'/, "'\"'\"'")
+ sub(/[ ]*\+?=[ ]*/, "='")
+ sub(/[ ]*$/, "'")
+ var = $0
+ val = $0
+ sub(/\+?=.*/, "", var)
+ sub(/^[^=]+=/, "", val)
+ }
+ /\+=/ && !/^ / {
+ fixup()
+ if(!set[var]) {
+ print var"=''"
+ append[var]++
+ }
+ print var"=\"$" var " \"" val
+ print var"_orig=\"$" var "\""
+ next
+ }
+ /=/ && !/^ / {
+ fixup()
+ delete append[var]
+ set[var]++
+
+ print var"="val
+ print var"_orig=\"$" var "\""
+ }
+ END{
+ for(v in set)
+ print v "_append=''"
+ for(v in append)
+ print v "_append=true"
+ }
+!
+}
+
+findinc() {
+ var="$1"; file="$2"; shift 2
+ for d in "$@"; do
+ if [ -d "$d" -a -f "$d/$file" ]; then
+ eval "$var=\"-I$d\""
+ break; fi; done
+}
+soext=.so
+aext=.a
+findlib() {
+ var="$1"; lib="$2"; shift 2
+ for d in "$@"; do
+ if [ -d "$d" -a -f "$d/$lib.so" -o -f "$d/lib$lib.a" ]; then
+ _libdir="$d"; _libname="$lib"
+ eval "$var=\"-L$d -l$lib\""
+ break; fi; done
+}
+
+expand() {
+ _expand="$@"; _expand_old=''
+ while [ "$_expand" != "$_expand_old" ]; do
+ _expand_old="$_expand"
+ eval "_expand=\"$_expand\""; done
+ echo -n "$_expand"
+}
+
+cfg() {
+ CFG="$CFG
+$@"
+}
+
+equals() {
+ if [ -z "$append" ]; then
+ echo -n "=";
+ else
+ echo -n "+="; fi
+}
+
+prompt() {
+ var=$1; shift
+ eval "def=\$$var; orig=\$${var}_orig; append=\$${var}_append"
+
+ unset val
+ if [ -z "$def" -o -n "$force" ]; then
+ echo "$@"
+ echo -n "$var[$def]$(equals) "
+ read val
+ echo
+ fi
+
+ if [ -z "$val" ]; then
+ val="$def"; fi
+ if [ "$(expand $val)" != "$(expand "$orig")" ]; then
+ cfg "$var$(equals)$val"; fi
+}
+
+# The messy sed... Turns $(VAR) into ${VAR}, but not $$(VAR) into $${VAR}
+eval "$(sed 's/\([^$]\)\$(\([A-Za-z_]*\))/\1${\2}/g' <"$CONFIG" | awk "`parseconfig`")"
+CFG="$(sed -n "/^$CONFSTR/q; p" "$CONFIG"; echo "$CONFSTR")"
+
+# ==================== The Fun Part ====================
+
+cat <<!
+Configuring for the wmii build.
+
+You'll be prompted for a number of settings which wmii needs to build,
+install, and run properly. In most cases, the defaults will be sufficient,
+in which case, you may simply press return to accept them.
+
+!
+
+# Guess...
+AWKPATH=$(which awk 2>/dev/null)
+
+prompt AWKPATH "Full path to your system's 'awk' program"
+prompt PLAN9 "Path of a Plan 9 Port or 9base installation"
+
+force=1
+prompt PREFIX Installation prefix
+
+echo
+echo "Compilation details (if you don't understand these, just leave the defaults)"
+echo
+prompt CC C object compiler
+prompt LD 'Linker (this should normally not be "ld")'
+
+set -- $CC
+if $1 -v 2>&1 | grep 'gcc version' >/dev/null; then
+ echo -n You appear to be using gcc. Is this correct? '[yes] '
+ while :; do
+ read resp
+ case "$resp" in
+ [Yy][Ee][Ss]|'')
+ cfg 'include $(ROOT)/mk/gcc.mk'
+ break;;
+ [Nn][Oo])
+ cfg 'CFLAGS=""'
+ # Not perfect. Botches system cflags, but we
+ # need to ditch the gcc ones.
+ break;;
+ *)
+ echo -n 'Please answer yes or no: '
+ esac
+ done
+ echo
+fi
+
+prompt INCPATH Search path for include files
+prompt LIBS Libraries to be linked with every executable
+
+prompt CFLAGS Flags for the C compiler
+prompt LDFLAGS Flags for the linker
+case $(uname -s) in
+ [Dd]arwin) cfg 'STATIC=""';;
+ *) prompt STATIC Extra linker flags to produce a static executable;;
+esac
+unset force
+
+# Make some guesses
+
+# Extract the -L paths from ldflags.
+ldlibs="$(awk 'BEGIN{
+ for(i=1; i <= ARGC; i++)
+ if(ARGV[i] ~ /-L/)
+ print ":" substr(ARGV[i], 3)
+ }' $LDFLAGS)"
+# Turn include paths into ../lib paths.
+inclibs="$(expand "$INCPATH"|sed 's,/include:,/lib:,g; s,/include$,/lib,')"
+# Lace them all together, and expand them.
+libs="$(expand "$LIBDIR:$ldlibs:$inclibs:/usr/local/lib:/opt/local/lib")"
+
+LIBIXP=${LIBIXP%/libixp.a}
+INCPATH="$INCPATH:/usr/local/include:/opt/local/include"
+incpath="$(expand "$INCPATH")"
+
+oifs="$IFS"; IFS=:
+findinc INCX11 X11/Xlib.h $incpath \
+ /usr/X11R6/include /usr/x11/include /usr/x11/include /usr/X11/include \
+ /usr/openwin/include /opt/x11/include /opt/X11/include
+findinc INCICONV iconv.h $incpath
+
+findlib LIBX11 X11 $libs \
+ /usr/X11R6/lib /usr/X11/lib /usr/openwin/lib /usr/x11/lib \
+ /opt/X11 /opt/x11 /usr/local/lib /opt/local/lib
+findlib LIBICONV iconv $libs
+
+if [ -d "$ROOT/libixp" ]; then
+ _libdir="$ROOT/lib"; else
+ soext=.a findlib LIBIXP ixp "$ROOT/lib" $libs; fi
+LIBIXP="$_libdir/libixp.a"
+IFS="$oifs"
+
+# Back to prompting
+echo Library paths...
+prompt INCX11 Compiler flags to find X11 includes
+prompt LIBX11 Linker flags to link against libX11
+
+# Find out if the system libc includes iconv...
+# GNU systems tend to make /usr/lib/libc.so or /lib/libc.so
+# linker scripts rather than libraries, so I can't parse them.
+# As a kludge, I ask ldd what actual libc.so /bin/sh links to,
+# which is pretty reliable. It's very unlikely that anyone will
+# actually be prompted for this.
+# I suppose the auto*hell way would be to just compile
+# something against libc. Perhaps another day.
+if nm -D $(ldd /bin/sh | awk '$1 ~ /^libc\.so/ {print $3}') |
+ awk -ve=1 '$2 == "T" && $3 == "iconv" {e=0; exit}; END {exit e}'
+then
+ echo
+ echo "Your system's libc appears to include iconv."
+ echo
+else
+ prompt LIBICONV Linker flags to link agains libiconv \
+ '(may be left blank if iconv is part of your system libc)'
+fi
+prompt INCICONV Compiler flags to find iconv.h
+prompt LIBIXP Path to libixp.a
+
+echo Writing config.mk
+echo "$CFG
+" >config.mk
+echo Done.
+
diff --git a/util/link b/util/link
new file mode 100755
index 0000000..5c6fc94
--- /dev/null
+++ b/util/link
@@ -0,0 +1,37 @@
+#!/bin/sh -f
+
+LD=$1
+LDFLAGS=$2; shift 2
+
+outfile="$1"; shift
+bin="$(echo $0 | sed 's,/[^/]*$,,')"
+
+# Derived from Russ Cox's 9l in plan9port.
+ofiles=""
+args=""
+for i
+do
+ case "$i" in
+ *.[ao]|*.o_pic)
+ ofiles="$ofiles $i"
+ ;;
+ *)
+ args="$args $i"
+ ;;
+ esac
+done
+
+xtmp=/tmp/ld.$$.$USER.out
+
+echo LD "$($bin/cleanname ${BASE}$outfile)"
+[ -n "$noisycc" ] && echo $LD -o $outfile $ofiles $LDFLAGS $args
+$LD -o $outfile $ofiles $LDFLAGS $args >$xtmp 2>&1
+status=$?
+[ $? -eq 0 ] || $LD -o $outfile $ofiles $LDFLAGS $args >&2
+
+sed 's/.*: In function `[^:]*: *//' $xtmp | egrep . |
+egrep -v 'is almost always misused|is dangerous, better use|in statically linked applications requires at runtime'
+rm -f $xtmp
+
+exit $status
+