diff options
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. + @@ -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) + @@ -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. + @@ -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 +} + @@ -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 + @@ -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 Binary files differnew file mode 100644 index 0000000..89d5aac --- /dev/null +++ b/debian/icons/wmii.png 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 Binary files differnew file mode 100644 index 0000000..42c07d0 --- /dev/null +++ b/doc/floating.png diff --git a/doc/focused.png b/doc/focused.png Binary files differnew file mode 100644 index 0000000..ceeb51e --- /dev/null +++ b/doc/focused.png diff --git a/doc/managed.png b/doc/managed.png Binary files differnew file mode 100644 index 0000000..eb76ae5 --- /dev/null +++ b/doc/managed.png diff --git a/doc/selected.png b/doc/selected.png Binary files differnew file mode 100644 index 0000000..708b123 --- /dev/null +++ b/doc/selected.png diff --git a/doc/unfocused.png b/doc/unfocused.png Binary files differnew file mode 100644 index 0000000..3e92f7a --- /dev/null +++ b/doc/unfocused.png diff --git a/doc/unselected.png b/doc/unselected.png Binary files differnew file mode 100644 index 0000000..5124bc3 --- /dev/null +++ b/doc/unselected.png diff --git a/doc/wmii.pdf b/doc/wmii.pdf Binary files differnew file mode 100644 index 0000000..db88998 --- /dev/null +++ b/doc/wmii.pdf 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 Binary files differnew file mode 100644 index 0000000..9784430 --- /dev/null +++ b/img/icon.png 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 Binary files differnew file mode 100644 index 0000000..ef7618a --- /dev/null +++ b/img/wmii.pdf diff --git a/img/wmii.png b/img/wmii.png Binary files differnew file mode 100644 index 0000000..539c013 --- /dev/null +++ b/img/wmii.png 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 Binary files differnew file mode 100644 index 0000000..3b6fcd5 --- /dev/null +++ b/libfmt/libfmt.a 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 Binary files differnew file mode 100644 index 0000000..ff08935 --- /dev/null +++ b/libutf/libutf.a 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 + |