diff options
author | gotmor <gotmor@f2baff5b-bf2c-0410-a398-912abdc3d8b2> | 2007-03-28 17:11:37 +0000 |
---|---|---|
committer | gotmor <gotmor@f2baff5b-bf2c-0410-a398-912abdc3d8b2> | 2007-03-28 17:11:37 +0000 |
commit | 4b54df524a318680d93382f8253c8da5a7546b58 (patch) | |
tree | 24cb2f61910d7591856616af9330e9c7d3b60f64 | |
download | dzen-4b54df524a318680d93382f8253c8da5a7546b58.tar.gz dzen-4b54df524a318680d93382f8253c8da5a7546b58.zip |
0.1.9 initial svn release
git-svn-id: http://dzen.googlecode.com/svn/trunk@1 f2baff5b-bf2c-0410-a398-912abdc3d8b2
-rw-r--r-- | LICENSE | 21 | ||||
-rw-r--r-- | Makefile | 51 | ||||
-rw-r--r-- | README | 222 | ||||
-rw-r--r-- | TODO | 10 | ||||
-rw-r--r-- | action.c | 329 | ||||
-rw-r--r-- | action.h | 69 | ||||
-rw-r--r-- | config.mk | 30 | ||||
-rw-r--r-- | draw.c | 139 | ||||
-rw-r--r-- | dzen.h | 120 | ||||
-rw-r--r-- | main.c | 435 | ||||
-rw-r--r-- | util.c | 62 |
11 files changed, 1488 insertions, 0 deletions
@@ -0,0 +1,21 @@ +MIT/X Consortium License + +(C)opyright MMVII Robert Manea <rob dot manea at gmail dot 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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..454cd02 --- /dev/null +++ b/Makefile @@ -0,0 +1,51 @@ +# dzen2 +# (C)opyright MMVII Robert Manea + +include config.mk + +SRC = draw.c main.c util.c action.c +OBJ = ${SRC:.c=.o} + +all: options dzen2 + +options: + @echo dzen2 build options: + @echo "CFLAGS = ${CFLAGS}" + @echo "LDFLAGS = ${LDFLAGS}" + @echo "CC = ${CC}" + @echo "LD = ${LD}" + +.c.o: + @echo CC $< + @${CC} -c ${CFLAGS} $< + +${OBJ}: dzen.h action.h config.mk + +dzen2: ${OBJ} + @echo LD $@ + @${LD} -o $@ ${OBJ} ${LDFLAGS} + @strip $@ + +clean: + @echo cleaning + @rm -f dzen2 ${OBJ} dzen2-${VERSION}.tar.gz + +dist: clean + @echo creating dist tarball + @mkdir -p dzen2-${VERSION} + @cp -R LICENSE Makefile README config.mk action.h dzen.h ${SRC} dzen2-${VERSION} + @tar -cf dzen2-${VERSION}.tar dzen2-${VERSION} + @gzip dzen2-${VERSION}.tar + @rm -rf dzen2-${VERSION} + +install: all + @echo installing executable file to ${DESTDIR}${PREFIX}/bin + @mkdir -p ${DESTDIR}${PREFIX}/bin + @cp -f dzen2 ${DESTDIR}${PREFIX}/bin + @chmod 755 ${DESTDIR}${PREFIX}/bin/dzen2 + +uninstall: + @echo removing executable file from ${DESTDIR}${PREFIX}/bin + @rm -f ${DESTDIR}${PREFIX}/bin/dzen2 + +.PHONY: all options clean dist install uninstall @@ -0,0 +1,222 @@ +dzen +===== +A general purpose messaging, notification and launcher programm. + + +Features +-------- + + * scritable in any language + * single line and/or multi + line windows + * menu functionality + * flexible event/action + mechanism + * hideable, collapsable + + +Requirements +------------ +In order to build dzen you need the Xlib and phtread header files. + + +Installation +------------ +Edit config.mk to match your local setup (dzen is installed into +the /usr/local namespace by default). + +Afterwards enter the following command to build and install dzen (if +necessary as root): + + make clean install + + +Running dzen +------------ +dzen accepts a couple of options: + + -fg foreground color + -bg background color + -fn font + -a autohide, see 1) + -l lines, see 2) + -e events and actions, see 3) + -m menu, see 4) + -p persistent, never time out + -x x position + -y y position + -w width + -v version information + +Termination: +------------ +dzen2 uses to different approaches to terminate itself: + + * If EOF is received -> terminate + - unless the '-p' flag is set + + * If mouse button3 is clicked -> terminate + - this is the default behaviour, see 3) + + +Return values: +-------------- +0 - dzen received EOF +1 - some error occured, inspect the error message +user defined - set with 'exit:retval' action, see 3) + + + +1) Option "-a": Autohide +------------------------ + +Hides the title window as soon as the pointer leaves it (default). This +very much resembles autohiding panels known from most desktops. + +Note: See the 3) for events/actions to use with autohide. + + +2) Option "-l": Slave window +-------------------------------- + +Enables support for displaying multiple lines. The parameter to "-l" +specifies the number of lines to be displayed. + +These lines of input are held in the slave window which becomes active as soon +as the pointer enters the title (default action) window. + +If the mouse leaves the multiline window it will be hidden unless it is set +sticky by clicking with Button2 into it (default action). + +Button4 and Button5 (mouse wheel) will scroll the multiline window up +and down if the content exceeds the window height (default action). + + +3) Option '-e': Events and actions +---------------------------------- + +dzen allows the user to associate actions to events. + +The command line syntax is as follow: +-e 'event1=action1:option1:...option<n>,...,action<m>;...;event<l>' + +Every event can take any number of actions and every action can take any number +of options. (Currently limited to 256 each) + +An example: + -e 'button1=exec:xterm:firefox;entertitle=uncollapse,unhide;button3=exit' + + Meaning: + + button1=exec:xterm:firefox; + on Button1 event (Button1 press on the mouse) execute xterm and + firefox. + (Note: xterm and firefox are options to the exec action) + + entertitle=uncollapse,unhide; + on entertitle (mouse pointer enters the title window) uncollapse + multiline window and unhide the title window + + button3=exit + on button3 event exit dzen + + +Supported events: + button1 Mouse button1 released + button2 Mouse button2 released + button3 Mouse button3 released + button4 Mouse button4 released (usually scrollwheel) + button5 Mouse button5 released (usually scrollwheel) + entertitle Mouse enters the title window + leavetitle Mouse leaves the title window + enterslave Mouse enters the Ń•lave window + leaveslave Mouse leaves the slave window + sigusr1 SIGUSR1 received + sigusr2 SIGUSR2 received + +Supported actions: + exec:command1:..:n execute all given options + menuexec executes selected menu entry + exit:retval exit dzen and return 'retval' + print:str1:...:n write all given options to STDOUT + menuprint write selected menu entry to STDOUT + collapse collapse multiline window + uncollapse uncollapse multiline window + stick stick multiline window + unstick unstick multiline window + togglestick toggle sticky state + hide hide title window + unhide unhide title window + scrollup scroll slave window one line up + scrolldown scroll slave window one line down + + +Note: If no events/actions are specified dzen defaults to: + + -e 'entertitle=uncollapse;leaveslave=collapse; + button1=menuexec; + button2=togglestick;button3=exit:13; + button4=scrollup;button5=scrolldown' + + If you define any events/actions, there is no default behaviour, + i.e. you will need to specify _all_ events/actions you want to + use. + + +Note: The hide/unhide actions can only be used if '-a' is supplied on + the command line. This is subject to change as soon as a + autohide action will be introduced. + + + +4) Option '-m', Menu +-------------------- + +If menu mode is activated the line under the pointer will be +highlighted. +All actions beginning with "menu" work on the selected menu entry. + +Note: Menu mode only makes sense if '-l <n>' is specified! + + + +Examples: +--------- + +* Display message and timeout after 10 seconds: + (echo "This is a message"; sleep 10) | dzen2 -bg darkred -fg grey80 -fn fixed + +* Display message and never timeout: + echo "This is a message"| dzen2 -p + +* Display updating single line message: + for i in $(seq 1 20); do A=${A}'='; print $A; sleep 1; done | ./dzen2 + +* Display header and multiline message: + (echo Header; cal; sleep 20) | dzen2 -l 8 + + Displays "Header" in the main alert window and + the output of cal in the 8 lines high multiline + window. + +* Display updating messages: + (echo Header; while true; do echo test$((i++)); sleep 1; done) | dzen2 -l 12 + + The slave window will update contents if new input has arrived. + +* Display log files: + (su -c "echo LOGFILENAME; tail -f /var/log/messages") | dzen2 -l 20 -x 100 -y 300 -w 500 + +* Monthly schedule with remind: + (echo Monthly Schedule; remind -c1 -m) | dzen2 -l 52 -w 410 -p -fn lime -bg '#e0e8ea' -fg black -x 635 + +* Simple menu: + echo "Applications" | dzen2 -l 4 -p -m < menufile + +* Menu without any files: + {echo Menu; echo -e "xterm\nxclock\nxeyes\nxfontsel"} | dzen2 -l 4 -m -p + +* Extract PIDs from the process table + + {echo Procs; ps -a} | dzen2 -m -l 12 -p -e 'button1=menuprint;button3=exit; + button4=scrollup;button5=scrolldown' | awk '{print $1}' @@ -0,0 +1,10 @@ +Drawing: +-------- + * Fix windowsize + + +Events/Actions: +-------------- + * textchange event + + diff --git a/action.c b/action.c new file mode 100644 index 0000000..375efc7 --- /dev/null +++ b/action.c @@ -0,0 +1,329 @@ +/* + * (C)opyright MMVII Robert Manea <rob dot manea at gmail dot com> + * See LICENSE file for license details. + */ + +#include "dzen.h" +#include "action.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +struct event_lookup ev_lookup_table[] = { + { "exposet", exposetitle}, + { "exposes", exposeslave}, + { "button1", button1}, + { "button2", button2}, + { "button3", button3}, + { "button4", button4}, + { "button5", button5}, + { "entertitle", entertitle}, + { "leavetitle", leavetitle}, + { "enterslave", enterslave}, + { "leaveslave", leaveslave}, + { "sigusr1", sigusr1}, + { "sigusr2", sigusr2}, + { 0, 0 } +}; + +struct action_lookup ac_lookup_table[] = { + { "exposetitle", a_exposetitle}, + { "exposeslave", a_exposeslave}, + { "print", a_print }, + { "exec", a_exec}, + { "exit", a_exit}, + { "collapse", a_collapse}, + { "uncollapse", a_uncollapse}, + { "stick", a_stick}, + { "unstick", a_unstick}, + { "togglestick", a_togglestick}, + { "hide", a_hide}, + { "unhide", a_unhide}, + { "scrollup", a_scrollup}, + { "scrolldown", a_scrolldown}, + { "menuprint", a_menuprint}, + { "menuexec", a_menuexec}, + { 0, 0 } +}; + +Ev ev_table[256] = {{0}, {0}}; + +/* utilities */ +void +do_action(int event) { + int i; + + if(ev_table[event].isset) + for(i=0; ev_table[event].action[i]->handler; i++) + ev_table[event].action[i]->handler(ev_table[event].action[i]->options); +} + +int +get_ev_id(char *evname) { + int i; + + for(i=0; ev_lookup_table[i].name; i++) { + if(strcmp(ev_lookup_table[i].name, evname) == 0) + return ev_lookup_table[i].id; + } + return -1; +} + +void * +get_action_handler(char *acname) { + int i; + + for(i=0; ac_lookup_table[i].name; i++) { + if(strcmp(ac_lookup_table[i].name, acname) == 0) + return ac_lookup_table[i].handler; + } + return (void *)NULL; +} + +void +fill_ev_table(char *input) +{ + char *str1, *str2, *str3, *str4, + *token, *subtoken, *kommatoken, *dptoken; + char *saveptr1, *saveptr2, *saveptr3, *saveptr4; + int j, i=0, k=0; + int eid=0; + void *ah=0; + + + for (j = 1, str1 = input; ; j++, str1 = NULL) { + token = strtok_r(str1, ";", &saveptr1); + if (token == NULL) + break; + + for (str2 = token; ; str2 = NULL) { + subtoken = strtok_r(str2, "=", &saveptr2); + if (subtoken == NULL) + break; + if( (str2 == token) && ((eid = get_ev_id(subtoken)) != -1)) + 0; + else if(eid == -1) + break; + + for (str3 = subtoken; ; str3 = NULL) { + kommatoken = strtok_r(str3, ",", &saveptr3); + if (kommatoken == NULL) + break; + + for (str4 = kommatoken; ; str4 = NULL) { + dptoken = strtok_r(str4, ":", &saveptr4); + if (dptoken == NULL) { + break; + } + if(str4 == kommatoken && str4 != token && eid != -1) { + ev_table[eid].isset = 1; + ev_table[eid].action[i] = malloc(sizeof(As)); + if((ah = (void *)get_action_handler(dptoken))) + ev_table[eid].action[i]->handler= get_action_handler(dptoken); + i++; + } + else if(str4 != token && eid != -1 && ah) { + ev_table[eid].action[i-1]->options[k] = strdup(dptoken); + k++; + } + else if(!ah) + break; + } + k=0; + } + ev_table[eid].action[i] = malloc(sizeof(As)); + ev_table[eid].action[i]->handler = NULL; + i=0; + } + } +} + +/* drawing utilities */ +static void +x_draw_body(void) { + dzen.x = 0; + dzen.y = 0; + dzen.w = dzen.mw; + dzen.h = dzen.mh; + int i; + + pthread_mutex_lock(&dzen.mt); + if(!dzen.slave_win.last_line_vis) { + if(dzen.slave_win.tcnt < dzen.slave_win.max_lines) { + dzen.slave_win.first_line_vis = 0; + dzen.slave_win.last_line_vis = dzen.slave_win.tcnt; + } + if(dzen.slave_win.tcnt >= dzen.slave_win.max_lines) { + dzen.slave_win.first_line_vis = dzen.slave_win.tcnt - dzen.slave_win.max_lines; + dzen.slave_win.last_line_vis = dzen.slave_win.tcnt; + } + } + + for(i=0; i < dzen.slave_win.max_lines; i++) { + if(i < dzen.slave_win.last_line_vis) { + drawtext(dzen.slave_win.tbuf[i + dzen.slave_win.first_line_vis], 0, i+1); + XCopyArea(dzen.dpy, dzen.slave_win.drawable, dzen.slave_win.line[i], dzen.gc, + 0, 0, dzen.mw, dzen.mh, 0, 0); + } + else if(i < dzen.slave_win.max_lines) { + drawtext("", 0, i+1); + XCopyArea(dzen.dpy, dzen.slave_win.drawable, dzen.slave_win.line[i], dzen.gc, + 0, 0, dzen.mw, dzen.mh, 0, 0); + } + } + pthread_mutex_unlock(&dzen.mt); +} + +/* actions */ + +/* used internally */ +int +a_exposetitle(char * opt[]) { + XCopyArea(dzen.dpy, dzen.title_win.drawable, dzen.title_win.win, + dzen.gc, 0, 0, dzen.mw, dzen.mh, 0, 0); + return 0; +} + +/* used internally */ +int +a_exposeslave(char * opt[]) { + x_draw_body(); + return 0; +} + +/* user selectable actions */ +int +a_exit(char * opt[]) { + if(opt[0]) + dzen.ret_val = atoi(opt[0]); + dzen.running = False; + return 0; +} + +int +a_collapse(char * opt[]){ + int i; + if(dzen.slave_win.max_lines && !dzen.slave_win.issticky) { + for(i=0; i < dzen.slave_win.max_lines; i++) + XUnmapWindow(dzen.dpy, dzen.slave_win.line[i]); + XUnmapWindow(dzen.dpy, dzen.slave_win.win); + } + return 0; +} + +int +a_uncollapse(char * opt[]){ + int i; + if(dzen.slave_win.max_lines && !dzen.slave_win.issticky) { + XMapRaised(dzen.dpy, dzen.slave_win.win); + for(i=0; i < dzen.slave_win.max_lines; i++) + XMapRaised(dzen.dpy, dzen.slave_win.line[i]); + x_draw_body(); + } + return 0; +} + +int +a_togglecollapse(char * opt[]){ + return 0; +} + +int +a_stick(char * opt[]) { + if(dzen.slave_win.max_lines) + dzen.slave_win.issticky = True; + return 0; +} + +int +a_unstick(char * opt[]) { + if(dzen.slave_win.max_lines) + dzen.slave_win.issticky = False; + return 0; +} + +int +a_togglestick(char * opt[]) { + if(dzen.slave_win.max_lines) + dzen.slave_win.issticky = dzen.slave_win.issticky ? False : True; + return 0; +} + +int +a_scrollup(char * opt[]) { + if(dzen.slave_win.max_lines + && dzen.slave_win.first_line_vis + && dzen.slave_win.last_line_vis > dzen.slave_win.max_lines) { + dzen.slave_win.first_line_vis--; + dzen.slave_win.last_line_vis--; + x_draw_body(); + } + return 0; +} + +int +a_scrolldown(char * opt[]) { + if(dzen.slave_win.max_lines + && dzen.slave_win.last_line_vis >= dzen.slave_win.max_lines + && dzen.slave_win.last_line_vis < dzen.slave_win.tcnt) { + dzen.slave_win.first_line_vis++; + dzen.slave_win.last_line_vis++; + x_draw_body(); + } + return 0; +} + +int +a_hide(char * opt[]) { + if(dzen.title_win.autohide && !dzen.title_win.ishidden) { + XResizeWindow(dzen.dpy, dzen.title_win.win, dzen.mw, 1); + dzen.title_win.ishidden = True; + } + return 0; +} + +int +a_unhide(char * opt[]) { + if(dzen.title_win.autohide && dzen.title_win.ishidden) { + XResizeWindow(dzen.dpy, dzen.title_win.win, dzen.mw, dzen.mh); + dzen.title_win.ishidden = False; + } + return 0; +} + +int +a_exec(char * opt[]) { + int i; + + if(opt) + for(i=0; opt[i]; i++) + if(opt[i]) + spawn(opt[i]); + return 0; +} + +int +a_print(char * opt[]) { + int i; + + if(opt) + for(i=0; opt[i]; i++) + puts(opt[i]); + return 0; +} + +int +a_menuprint(char * opt[]) { + if(dzen.slave_win.ismenu) { + puts(dzen.slave_win.tbuf[dzen.slave_win.sel_line + dzen.slave_win.first_line_vis]); + fflush(stdout); + } + return 0; +} + +int +a_menuexec(char * opt[]) { + if(dzen.slave_win.ismenu) + spawn(dzen.slave_win.tbuf[dzen.slave_win.sel_line + dzen.slave_win.first_line_vis]); + return 0; +} diff --git a/action.h b/action.h new file mode 100644 index 0000000..9db6531 --- /dev/null +++ b/action.h @@ -0,0 +1,69 @@ +/* + * (C)opyright MMVII Robert Manea <rob dot manea at gmail dot com> + * See LICENSE file for license details. + * + */ + +#include <pthread.h> + +/* Event, Action data structures */ +typedef struct AS As; +typedef struct EV Ev; + +enum ev_id { + /* internal events, should not be used by the user */ + exposetitle, exposeslave, + /* mouse buttons */ + button1, button2, button3, button4, button5, + /* entering/leaving windows */ + entertitle, leavetitle, enterslave, leaveslave, + /* external signals */ + sigusr1, sigusr2 +}; + +struct event_lookup { + char *name; + int id; +}; + +struct action_lookup { + char *name; + int (*handler)(char **); +}; + +struct AS { + char *options[256]; + int (*handler)(char **); +}; + +struct EV { + int isset; + As *action[256]; +}; + +extern Ev ev_table[256]; + +/* utility functions */ +void do_action(int); +int get_ev_id(char *); +void * get_action_handler(char *); +void fill_ev_table(char *); + +/* action handlers */ +int a_exposetitle(char **); +int a_exposeslave(char **); +int a_print(char **); +int a_exit(char **); +int a_exec(char **); +int a_collapse(char **); +int a_uncollapse(char **); +int a_stick(char **); +int a_unstick(char **); +int a_togglestick(char **); +int a_scrollup(char **); +int a_scrolldown(char **); +int a_hide(char **); +int a_unhide(char **); +int a_menuprint(char **); +int a_menuexec(char **); + diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..d839c08 --- /dev/null +++ b/config.mk @@ -0,0 +1,30 @@ +# dzen version +VERSION = 0.1.9 + +# Customize below to fit your system + +# paths +PREFIX = /usr/local +MANPREFIX = ${PREFIX}/share/man + +X11INC = /usr/X11R6/include +X11LIB = /usr/X11R6/lib + +# includes and libs +INCS = -I. -I/usr/include -I${X11INC} +LIBS = -L/usr/lib -lc -L${X11LIB} -lX11 -lpthread + +# flags +CFLAGS = -Os ${INCS} -DVERSION=\"${VERSION}\" +LDFLAGS = ${LIBS} +#CFLAGS = -g -Wall -O2 ${INCS} -DVERSION=\"${VERSION}\" -DPOSIX_SOURCE +#LDFLAGS = -g ${LIBS} + +# Solaris +#CFLAGS = -fast ${INCS} -DVERSION=\"${VERSION}\" +#LDFLAGS = ${LIBS} +#CFLAGS += -xtarget=ultra + +# compiler and linker +CC = cc +LD = ${CC} @@ -0,0 +1,139 @@ +/* +* (C)opyright MMVII Robert Manea <rob dot manea at gmail dot com> +* See LICENSE file for license details. +*/ +#include "dzen.h" +#include <stdio.h> +#include <string.h> + +/* static */ +static unsigned int +textnw(const char *text, unsigned int len) { + XRectangle r; + + if(dzen.font.set) { + XmbTextExtents(dzen.font.set, text, len, NULL, &r); + return r.width; + } + return XTextWidth(dzen.font.xfont, text, len); +} + +void drawtext(const char *text, int reverse, int line) { + int x, y, w, h; + static char buf[1024]; + unsigned int len, olen; + XGCValues gcv; + GC mgc; + XRectangle r = { dzen.x, dzen.y, dzen.w, dzen.h}; + + + mgc = reverse ? dzen.gc : dzen.rgc; + /* title win */ + if(line == -1) + XFillRectangles(dzen.dpy, dzen.title_win.drawable, mgc, &r, 1); + /* slave win */ + else + XFillRectangles(dzen.dpy, dzen.slave_win.drawable, mgc, &r, 1); + + if(!text) + return; + + w = 0; + olen = len = strlen(text); + if(len >= sizeof buf) + len = sizeof buf - 1; + memcpy(buf, text, len); + buf[len] = 0; + h = dzen.font.ascent + dzen.font.descent; + /* shorten text if necessary */ + while(len && (w = textnw(buf, len)) > dzen.w - h) + buf[--len] = 0; + if(len < olen) { + if(len > 1) + buf[len - 1] = '.'; + if(len > 2) + buf[len - 2] = '.'; + if(len > 3) + buf[len - 3] = '.'; + } + + if(line != -1) + x = h; + else + x = (dzen.w - textw(buf)+h)/2; + y = dzen.font.ascent + (dzen.mh - h) / 2; + + mgc = reverse ? dzen.rgc : dzen.gc; + if(dzen.font.set) { + if(line == -1) + XmbDrawString(dzen.dpy, dzen.title_win.drawable, dzen.font.set, + mgc, x, y, buf, len); + else + XmbDrawString(dzen.dpy, dzen.slave_win.drawable, dzen.font.set, + mgc, x, y, buf, len); + } + else { + gcv.font = dzen.font.xfont->fid; + XChangeGC(dzen.dpy, mgc, GCForeground | GCFont, &gcv); + + if(line != -1) + XDrawString(dzen.dpy, dzen.slave_win.drawable, + mgc, x, y, buf, len); + else + XDrawString(dzen.dpy, dzen.title_win.drawable, + mgc, x, y, buf, len); + } +} + +unsigned long +getcolor(const char *colstr) { + Colormap cmap = DefaultColormap(dzen.dpy, dzen.screen); + XColor color; + + if(!XAllocNamedColor(dzen.dpy, cmap, colstr, &color, &color)) + eprint("error, cannot allocate color '%s'\n", colstr); + return color.pixel; +} + +void +setfont(const char *fontstr) { + char *def, **missing; + int i, n; + + missing = NULL; + if(dzen.font.set) + XFreeFontSet(dzen.dpy, dzen.font.set); + dzen.font.set = XCreateFontSet(dzen.dpy, fontstr, &missing, &n, &def); + if(missing) + XFreeStringList(missing); + if(dzen.font.set) { + XFontSetExtents *font_extents; + XFontStruct **xfonts; + char **font_names; + dzen.font.ascent = dzen.font.descent = 0; + font_extents = XExtentsOfFontSet(dzen.font.set); + n = XFontsOfFontSet(dzen.font.set, &xfonts, &font_names); + for(i = 0, dzen.font.ascent = 0, dzen.font.descent = 0; i < n; i++) { + if(dzen.font.ascent < (*xfonts)->ascent) + dzen.font.ascent = (*xfonts)->ascent; + if(dzen.font.descent < (*xfonts)->descent) + dzen.font.descent = (*xfonts)->descent; + xfonts++; + } + } + else { + if(dzen.font.xfont) + XFreeFont(dzen.dpy, dzen.font.xfont); + dzen.font.xfont = NULL; + if(!(dzen.font.xfont = XLoadQueryFont(dzen.dpy, fontstr))) + eprint("error, cannot load font: '%s'\n", fontstr); + dzen.font.ascent = dzen.font.xfont->ascent; + dzen.font.descent = dzen.font.xfont->descent; + } + dzen.font.height = dzen.font.ascent + dzen.font.descent; +} + +unsigned int +textw(const char *text) { + return textnw(text, strlen(text)) + dzen.font.height; +} @@ -0,0 +1,120 @@ +/* + * (C)opyright MMVII Robert Manea <rob dot manea at gmail dot com> + * See LICENSE file for license details. + */ + +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/keysym.h> + +#define FONT "-*-fixed-*-*-*-*-*-*-*-*-*-*-*-*" +#define BGCOLOR "#ab0b0b" +#define FGCOLOR "#efefef" + +#define BUF_SIZE 4096 + +/* gui data structures */ +enum { ColFG, ColBG, ColLast }; + +typedef struct DZEN Dzen; +typedef struct Fnt Fnt; +typedef struct TW TWIN; +typedef struct SW SWIN; + +struct Fnt { + XFontStruct *xfont; + XFontSet set; + int ascent; + int descent; + int height; +}; + +/* title window */ +struct TW { + int x, y, width, height; + int screen; + char *fnt; + char *bg; + char *fg; + + Window win; + Drawable drawable; + Bool autohide; + Bool ishidden; +}; + +/* slave window */ +struct SW { + int x, y, width, height; + int screen; + char *fnt; + char *bg; + char *fg; + + Window win; + Window *line; + Drawable drawable; + + char *tbuf[BUF_SIZE]; + int tcnt; + int max_lines; + int first_line_vis; + int last_line_vis; + int sel_line; + + Bool ismenu; + Bool issticky; + Bool ispersistent; + Bool ismapped; +}; + +/* TODO: Remove unused variables */ +struct DZEN { + int x, y, w, h; + int sx, sy, sw, sh; + Bool running; + unsigned long norm[ColLast]; + + TWIN title_win; + SWIN slave_win; + + /* to be removed */ + char *fnt; + char *bg; + char *fg; + int mw, mh; + /*---------------*/ + + Display *dpy; + int screen; + unsigned int depth; + + Visual *visual; + GC gc, rgc; + Fnt font; + + /* position */ + int hx, hy, hw; + int cur_line; + + pthread_t read_thread; + pthread_mutex_t mt; + + int ret_val; +}; + +extern Dzen dzen; + +/* draw.c */ +extern void drawtext(const char *text, + int reverse, + int line); +extern unsigned long getcolor(const char *colstr); /* returns color of colstr */ +extern void setfont(const char *fontstr); /* sets global font */ +extern unsigned int textw(const char *text); /* returns width of text in px */ + +/* util.c */ +extern void *emalloc(unsigned int size); /* allocates memory, exits on error */ +extern void eprint(const char *errstr, ...); /* prints errstr and exits with 1 */ +extern char *estrdup(const char *str); /* duplicates str, exits on allocation error */ +extern void spawn(const char *arg); /* execute arg */ @@ -0,0 +1,435 @@ +/* + * (C)opyright MMVII Robert Manea <rob dot manea at gmail dot com> + * See LICENSE file for license details. + * + */ +#include "dzen.h" +#include "action.h" + +#include <ctype.h> +#include <locale.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <signal.h> + +Dzen dzen = {0}; +static int last_cnt = 0; +typedef void sigfunc(int); + + +static void +catch_sigusr1() { + do_action(sigusr1); +} + +static void +catch_sigusr2() { + do_action(sigusr2); +} + +sigfunc * +setup_signal(int signr, sigfunc *shandler) { + struct sigaction nh, oh; + + nh.sa_handler = shandler; + sigemptyset(&nh.sa_mask); + nh.sa_flags = 0; + + if(sigaction(signr, &nh, &oh) < 0) + return SIG_ERR; + + return NULL; +} + +static void +chomp(char *buf, unsigned int len) { + if(buf && (buf[len-1] == '\n')) + buf[len-1] = '\0'; +} + +static void +drawheader(char * text) { + dzen.x = 0; + dzen.y = 0; + dzen.w = dzen.mw; + dzen.h = dzen.mh; + + if(text) + drawtext(text, 0, -1); + XCopyArea(dzen.dpy, dzen.title_win.drawable, dzen.title_win.win, + dzen.gc, 0, 0, dzen.mw, dzen.mh, 0, 0); +} + +static void +free_buffer(void) { + int i; + for(i=0; i<BUF_SIZE; i++) + free(dzen.slave_win.tbuf[i]); + dzen.slave_win.tcnt = 0; + last_cnt = 0; +} + +static void +drawbody(char * text) { + if(dzen.slave_win.tcnt >= BUF_SIZE) { + pthread_mutex_lock(&dzen.mt); + free_buffer(); + pthread_mutex_unlock(&dzen.mt); + } + if(dzen.slave_win.tcnt < BUF_SIZE) { + pthread_mutex_lock(&dzen.mt); + dzen.slave_win.tbuf[dzen.slave_win.tcnt] = estrdup(text); + dzen.slave_win.tcnt++; + pthread_mutex_unlock(&dzen.mt); + } +} + +static void +x_resize_header(int width, int height) { + XResizeWindow(dzen.dpy, dzen.title_win.win, width, height); +} + +static void +x_highlight_line(int line) { + drawtext(dzen.slave_win.tbuf[line + dzen.slave_win.first_line_vis], 1, line+1); + XCopyArea(dzen.dpy, dzen.slave_win.drawable, dzen.slave_win.line[line], dzen.rgc, + 0, 0, dzen.mw, dzen.mh, 0, 0); +} + +static void +x_unhighlight_line(int line) { + drawtext(dzen.slave_win.tbuf[line + dzen.slave_win.first_line_vis], 0, line+1); + XCopyArea(dzen.dpy, dzen.slave_win.drawable, dzen.slave_win.line[line], dzen.gc, + 0, 0, dzen.mw, dzen.mh, 0, 0); +} + +static void +event_loop(void *ptr) { + XEvent ev; + XWindowAttributes wa; + int i; + + while(dzen.running) { + if(dzen.slave_win.max_lines && (dzen.slave_win.tcnt > last_cnt)) { + if (XGetWindowAttributes(dzen.dpy, dzen.slave_win.win, &wa), + wa.map_state != IsUnmapped) { + dzen.slave_win.first_line_vis = 0; + dzen.slave_win.last_line_vis = 0; + do_action(exposeslave); + } + last_cnt = dzen.slave_win.tcnt; + } + + if(XPending(dzen.dpy)) { + XNextEvent(dzen.dpy, &ev); + switch(ev.type) { + case Expose: + if(ev.xexpose.count == 0) { + if(ev.xexpose.window == dzen.title_win.win) + drawheader(NULL); + if(ev.xexpose.window == dzen.slave_win.win) + do_action(exposeslave); + for(i=0; i < dzen.slave_win.max_lines; i++) + if(ev.xcrossing.window == dzen.slave_win.line[i]) + do_action(exposeslave); + } + break; + case EnterNotify: + if(dzen.slave_win.ismenu) { + for(i=0; i < dzen.slave_win.max_lines; i++) + if(ev.xcrossing.window == dzen.slave_win.line[i]) + x_highlight_line(i); + } + if(ev.xcrossing.window == dzen.title_win.win) + do_action(entertitle); + if(ev.xcrossing.window == dzen.slave_win.win) + do_action(enterslave); + break; + case LeaveNotify: + if(dzen.slave_win.ismenu) { + for(i=0; i < dzen.slave_win.max_lines; i++) + if(ev.xcrossing.window == dzen.slave_win.line[i]) + x_unhighlight_line(i); + } + if(ev.xcrossing.window == dzen.title_win.win) + do_action(leavetitle); + if(ev.xcrossing.window == dzen.slave_win.win) { + do_action(leaveslave); + } + XSync(dzen.dpy, False); + break; + case ButtonRelease: + if(dzen.slave_win.ismenu) { + for(i=0; i < dzen.slave_win.max_lines; i++) + if(ev.xbutton.window == dzen.slave_win.line[i]) + dzen.slave_win.sel_line = i; + } + switch(ev.xbutton.button) { + case Button1: + do_action(button1); + break; + case Button2: + do_action(button2); + break; + case Button3: + do_action(button3); + break; + case Button4: + do_action(button4); + break; + case Button5: + do_action(button5); + break; + } + XSync(dzen.dpy, False); + } + XFlush(dzen.dpy); + } else + usleep(10000); + } +} + +void * +read_stdin(void *ptr) { + char buf[1024], *text = NULL; + + /* draw background until data is available */ + drawheader(""); + + while(dzen.running) { + text = fgets(buf, sizeof buf, stdin); + if(feof(stdin) && !dzen.slave_win.ispersistent) { + dzen.running = False; + break; + } + if(feof(stdin) && dzen.slave_win.ispersistent) + break; + + if(text) { + chomp(text, strlen(text)); + + if(!dzen.cur_line || !dzen.slave_win.max_lines) { + drawheader(text); + } + else + drawbody(text); + dzen.cur_line++; + } + } + return NULL; +} + +static void +x_create_windows(void) { + XSetWindowAttributes wa; + Window root; + int i; + + dzen.dpy = XOpenDisplay(0); + if(!dzen.dpy) + eprint("dzen: cannot open display\n"); + + dzen.screen = DefaultScreen(dzen.dpy); + root = RootWindow(dzen.dpy, dzen.screen); + + /* style */ + dzen.norm[ColBG] = getcolor(dzen.bg); + dzen.norm[ColFG] = getcolor(dzen.fg); + setfont(dzen.fnt); + + /* window attributes */ + wa.override_redirect = 1; + wa.background_pixmap = ParentRelative; + wa.event_mask = ExposureMask | ButtonReleaseMask | EnterWindowMask | LeaveWindowMask; + + + /* check geometry */ + dzen.hx = dzen.hx > DisplayWidth(dzen.dpy, dzen.screen) ? 0 : dzen.hx; + dzen.mw = dzen.hw ? (dzen.hw + dzen.hx > DisplayWidth(dzen.dpy, dzen.screen) + ? (DisplayWidth(dzen.dpy, dzen.screen) - dzen.hx) : dzen.hw) + : DisplayWidth(dzen.dpy, dzen.screen); + dzen.mh = dzen.font.height + 2; + dzen.hy = (dzen.hy + dzen.mh) > DisplayHeight(dzen.dpy, dzen.screen) ? 0 : dzen.hy; + + /* title window */ + dzen.title_win.win = XCreateWindow(dzen.dpy, root, + dzen.hx, dzen.hy, dzen.mw, dzen.mh, 0, + DefaultDepth(dzen.dpy, dzen.screen), CopyFromParent, + DefaultVisual(dzen.dpy, dzen.screen), + CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa); + dzen.title_win.drawable = XCreatePixmap(dzen.dpy, root, dzen.mw, dzen.mh, DefaultDepth(dzen.dpy, dzen.screen)); + + /* slave window */ + if(dzen.slave_win.max_lines) { + dzen.slave_win.first_line_vis = 0; + dzen.slave_win.last_line_vis = 0; + dzen.slave_win.issticky = False; + + if(dzen.hy + dzen.mh*dzen.slave_win.max_lines > DisplayHeight(dzen.dpy, dzen.screen)) + dzen.hy = (dzen.hy - dzen.mh) - dzen.mh*(dzen.slave_win.max_lines); + + dzen.slave_win.win = XCreateWindow(dzen.dpy, root, + dzen.hx, dzen.hy+dzen.mh, dzen.mw, dzen.slave_win.max_lines * dzen.mh, 0, + DefaultDepth(dzen.dpy, dzen.screen), CopyFromParent, + DefaultVisual(dzen.dpy, dzen.screen), + CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa); + + dzen.slave_win.drawable = XCreatePixmap(dzen.dpy, root, dzen.mw, + dzen.mh, DefaultDepth(dzen.dpy, dzen.screen)); + + /* windows holding the lines */ + dzen.slave_win.line = emalloc(sizeof(Window) * dzen.slave_win.max_lines); + for(i=0; i < dzen.slave_win.max_lines; i++) { + dzen.slave_win.line[i] = XCreateWindow(dzen.dpy, dzen.slave_win.win, + 0, i*dzen.mh, dzen.mw, dzen.mh, 0, + DefaultDepth(dzen.dpy, dzen.screen), CopyFromParent, + DefaultVisual(dzen.dpy, dzen.screen), + CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa); + } + + } + /* normal GC */ + dzen.gc = XCreateGC(dzen.dpy, root, 0, 0); + XSetForeground(dzen.dpy, dzen.gc, dzen.norm[ColFG]); + XSetBackground(dzen.dpy, dzen.gc, dzen.norm[ColBG]); + /* reverse GC */ + dzen.rgc = XCreateGC(dzen.dpy, root, 0, 0); + XSetForeground(dzen.dpy, dzen.rgc, dzen.norm[ColBG]); + XSetBackground(dzen.dpy, dzen.rgc, dzen.norm[ColFG]); +} + +static void +x_map_window(Window win) { + XMapRaised(dzen.dpy, win); + XSync(dzen.dpy, False); +} + +int +main(int argc, char *argv[]) { + int i; + char *action_string = NULL; + + /* default values */ + dzen.cur_line = 0; + dzen.ret_val = 0; + dzen.hx = 0; + dzen.hy = 0; + dzen.hw = 0; + dzen.fnt = FONT; + dzen.bg = BGCOLOR; + dzen.fg = FGCOLOR; + dzen.slave_win.max_lines = 0; + dzen.title_win.autohide = False; + dzen.running = True; + + + /* cmdline args */ + for(i = 1; i < argc; i++) + if(!strncmp(argv[i], "-l", 3)){ + if(++i < argc) dzen.slave_win.max_lines = atoi(argv[i]); + } + else if(!strncmp(argv[i], "-p", 3)) { + dzen.slave_win.ispersistent = True; + } + else if(!strncmp(argv[i], "-a", 3)) { + dzen.title_win.autohide = True; + } + else if(!strncmp(argv[i], "-m", 3)) { + dzen.slave_win.ismenu = True; + } + else if(!strncmp(argv[i], "-fn", 4)) { + if(++i < argc) dzen.fnt = argv[i]; + } + else if(!strncmp(argv[i], "-e", 3)) { + if(++i < argc) action_string = argv[i]; + } + else if(!strncmp(argv[i], "-bg", 4)) { + if(++i < argc) dzen.bg = argv[i]; + } + else if(!strncmp(argv[i], "-fg", 4)) { + if(++i < argc) dzen.fg = argv[i]; + } + else if(!strncmp(argv[i], "-x", 3)) { + if(++i < argc) dzen.hx = atoi(argv[i]); + } + else if(!strncmp(argv[i], "-y", 3)) { + if(++i < argc) dzen.hy = atoi(argv[i]); + } + else if(!strncmp(argv[i], "-w", 3)) { + if(++i < argc) dzen.hw = atoi(argv[i]); + } + else if(!strncmp(argv[i], "-v", 3)) + eprint("dzen-"VERSION", (C)opyright 2007 Robert Manea\n"); + else + eprint("usage: dzen [-v] [-p] [-a] [-m] [-x <pixel>] [-y <pixel>] [-w <pixel>]\n" + " [-l <lines>] [-fn <font>] [-bg <color>] [-fg <color>]\n" + " [-e <string>]\n"); + + if(!XInitThreads()) + eprint("dzen: no multithreading support in xlib.\n"); + if(!setlocale(LC_ALL, "") || !XSupportsLocale()) + puts("dzen: locale not available, expect problems with fonts.\n"); + + if(action_string) { + char edef[] = "exposet=exposetitle;exposes=exposeslave"; + fill_ev_table(edef); + fill_ev_table(action_string); + } else { + char edef[] = "exposet=exposetitle;exposes=exposeslave;" + "entertitle=uncollapse;leaveslave=collapse;" + "button1=menuexec;button2=togglestick;button3=exit:13;" + "button4=scrollup;button5=scrolldown"; + fill_ev_table(edef); + } + + /* setup signal handlers */ + if(ev_table[sigusr1].isset && (setup_signal(SIGUSR1, catch_sigusr1) == SIG_ERR)) + fprintf(stderr, "dzen: error hooking SIGUSR1\n"); + if(ev_table[sigusr2].isset && (setup_signal(SIGUSR2, catch_sigusr2) == SIG_ERR)) + fprintf(stderr, "dzen: error hooking SIGUSR2\n"); + + /* set up windows */ + x_create_windows(); + x_map_window(dzen.title_win.win); + + /* autohiding */ + if(dzen.title_win.autohide) { + x_resize_header(dzen.mw, 1); + dzen.title_win.ishidden = True; + } + + /* reader */ + pthread_create(&dzen.read_thread, NULL, read_stdin, NULL); + + /* catch events */ + event_loop(NULL); + + /* clean up */ + if(!dzen.running) + pthread_cancel(dzen.read_thread); + + if(dzen.font.set) + XFreeFontSet(dzen.dpy, dzen.font.set); + else + XFreeFont(dzen.dpy, dzen.font.xfont); + + XFreePixmap(dzen.dpy, dzen.title_win.drawable); + if(dzen.slave_win.max_lines) { + XFreePixmap(dzen.dpy, dzen.slave_win.drawable); + for(i=0; i < dzen.slave_win.max_lines; i++) + XDestroyWindow(dzen.dpy, dzen.slave_win.line[i]); + free(&dzen.slave_win.line); + XDestroyWindow(dzen.dpy, dzen.slave_win.win); + } + XFreeGC(dzen.dpy, dzen.gc); + XDestroyWindow(dzen.dpy, dzen.title_win.win); + XCloseDisplay(dzen.dpy); + + if(dzen.ret_val) + return dzen.ret_val; + + return EXIT_SUCCESS; +} @@ -0,0 +1,62 @@ +/* (C)opyright MMVI-MMVII Anselm R. Garbe <garbeam at gmail dot com> + * (C)opyright MMVII Robert Manea <rob dot manea at gmail dot com> + * See LICENSE file for license details. + */ + +#include "dzen.h" +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/wait.h> +#include <unistd.h> + +void * +emalloc(unsigned int size) { + void *res = malloc(size); + + if(!res) + eprint("fatal: could not malloc() %u bytes\n", size); + return res; +} + +void +eprint(const char *errstr, ...) { + va_list ap; + + va_start(ap, errstr); + vfprintf(stderr, errstr, ap); + va_end(ap); + exit(EXIT_FAILURE); +} + +char * +estrdup(const char *str) { + void *res = strdup(str); + + if(!res) + eprint("fatal: could not malloc() %u bytes\n", strlen(str)); + return res; +} +void +spawn(const char *arg) { + static char *shell = NULL; + + if(!shell && !(shell = getenv("SHELL"))) + shell = "/bin/sh"; + if(!arg) + return; + /* The double-fork construct avoids zombie processes and keeps the code + * clean from stupid signal handlers. */ + if(fork() == 0) { + if(fork() == 0) { + setsid(); + execl(shell, shell, "-c", arg, (char *)NULL); + fprintf(stderr, "dzen: execl '%s -c %s'", shell, arg); + perror(" failed"); + } + exit(0); + } + wait(0); +} + |