From 4b54df524a318680d93382f8253c8da5a7546b58 Mon Sep 17 00:00:00 2001 From: gotmor Date: Wed, 28 Mar 2007 17:11:37 +0000 Subject: 0.1.9 initial svn release git-svn-id: http://dzen.googlecode.com/svn/trunk@1 f2baff5b-bf2c-0410-a398-912abdc3d8b2 --- LICENSE | 21 +++ Makefile | 51 ++++++++ README | 222 ++++++++++++++++++++++++++++++++ TODO | 10 ++ action.c | 329 +++++++++++++++++++++++++++++++++++++++++++++++ action.h | 69 ++++++++++ config.mk | 30 +++++ draw.c | 139 ++++++++++++++++++++ dzen.h | 120 +++++++++++++++++ main.c | 435 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ util.c | 62 +++++++++ 11 files changed, 1488 insertions(+) create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README create mode 100644 TODO create mode 100644 action.c create mode 100644 action.h create mode 100644 config.mk create mode 100644 draw.c create mode 100644 dzen.h create mode 100644 main.c create mode 100644 util.c diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9aea25c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT/X Consortium License + +(C)opyright MMVII Robert Manea + +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 diff --git a/README b/README new file mode 100644 index 0000000..11ecb43 --- /dev/null +++ b/README @@ -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,...,action;...;event' + +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 ' 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}' diff --git a/TODO b/TODO new file mode 100644 index 0000000..11fbf9b --- /dev/null +++ b/TODO @@ -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 + * See LICENSE file for license details. + */ + +#include "dzen.h" +#include "action.h" +#include +#include +#include + +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 + * See LICENSE file for license details. + * + */ + +#include + +/* 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} diff --git a/draw.c b/draw.c new file mode 100644 index 0000000..d209d80 --- /dev/null +++ b/draw.c @@ -0,0 +1,139 @@ +/* +* (C)opyright MMVII Robert Manea +* See LICENSE file for license details. +*/ +#include "dzen.h" +#include +#include + +/* 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; +} diff --git a/dzen.h b/dzen.h new file mode 100644 index 0000000..8b92e9d --- /dev/null +++ b/dzen.h @@ -0,0 +1,120 @@ +/* + * (C)opyright MMVII Robert Manea + * See LICENSE file for license details. + */ + +#include +#include +#include + +#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 */ diff --git a/main.c b/main.c new file mode 100644 index 0000000..67c48e0 --- /dev/null +++ b/main.c @@ -0,0 +1,435 @@ +/* + * (C)opyright MMVII Robert Manea + * See LICENSE file for license details. + * + */ +#include "dzen.h" +#include "action.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +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) { + 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 ] [-y ] [-w ]\n" + " [-l ] [-fn ] [-bg ] [-fg ]\n" + " [-e ]\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; +} diff --git a/util.c b/util.c new file mode 100644 index 0000000..d584a4a --- /dev/null +++ b/util.c @@ -0,0 +1,62 @@ +/* (C)opyright MMVI-MMVII Anselm R. Garbe + * (C)opyright MMVII Robert Manea + * See LICENSE file for license details. + */ + +#include "dzen.h" +#include +#include +#include +#include +#include +#include + +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); +} + -- cgit v1.2.3-54-g00ecf