aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--LICENSE21
-rw-r--r--Makefile51
-rw-r--r--README222
-rw-r--r--TODO10
-rw-r--r--action.c329
-rw-r--r--action.h69
-rw-r--r--config.mk30
-rw-r--r--draw.c139
-rw-r--r--dzen.h120
-rw-r--r--main.c435
-rw-r--r--util.c62
11 files changed, 1488 insertions, 0 deletions
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 <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
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<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}'
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 <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}
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 <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;
+}
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 <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 */
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 <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;
+}
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 <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);
+}
+