diff options
| -rw-r--r-- | .gitignore | 4 | ||||
| -rw-r--r-- | LICENSE | 23 | ||||
| -rw-r--r-- | Makefile | 60 | ||||
| -rw-r--r-- | README | 22 | ||||
| -rw-r--r-- | arg.h | 52 | ||||
| -rw-r--r-- | config.def.h | 58 | ||||
| -rw-r--r-- | config.h | 58 | ||||
| -rw-r--r-- | config.h.old | 59 | ||||
| -rw-r--r-- | config.mk | 25 | ||||
| -rw-r--r-- | patches/tabbed-0.6-xft.diff | 233 | ||||
| -rw-r--r-- | patches/tabbed-clientnumber-0.6.diff | 23 | ||||
| -rw-r--r-- | tabbed.1 | 144 | ||||
| -rw-r--r-- | tabbed.c | 1254 |
13 files changed, 2015 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..62ea7db --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.o +*.orig +*.rej +tabbed @@ -0,0 +1,23 @@ +MIT/X Consortium License + +© 2009-2011 Enno Boland <g s01 de> +© 2011 Connor Lane Smith <cls@lubutu.com> +© 2012 Christoph Lohmann <20h@r-36.net> + +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..32cc25b --- /dev/null +++ b/Makefile @@ -0,0 +1,60 @@ +# tabbed - tabbing interface +# See LICENSE file for copyright and license details. + +include config.mk + +SRC = tabbed.c +OBJ = ${SRC:.c=.o} + +all: options tabbed + +options: + @echo tabbed build options: + @echo "CFLAGS = ${CFLAGS}" + @echo "LDFLAGS = ${LDFLAGS}" + @echo "CC = ${CC}" + +.c.o: + @echo CC $< + @${CC} -c ${CFLAGS} $< + +${OBJ}: config.h config.mk + +config.h: + @echo creating $@ from config.def.h + @cp config.def.h $@ + +tabbed: tabbed.o + @echo CC -o $@ + @${CC} -o $@ tabbed.o ${LDFLAGS} + +clean: + @echo cleaning + @rm -f tabbed ${OBJ} tabbed-${VERSION}.tar.gz + +dist: clean + @echo creating dist tarball + @mkdir -p tabbed-${VERSION} + @cp -R LICENSE Makefile README config.def.h config.mk \ + tabbed.1 arg.h ${SRC} tabbed-${VERSION} + @tar -cf tabbed-${VERSION}.tar tabbed-${VERSION} + @gzip tabbed-${VERSION}.tar + @rm -rf tabbed-${VERSION} + +install: all + @echo installing executable file to ${DESTDIR}${PREFIX}/bin + @mkdir -p ${DESTDIR}${PREFIX}/bin + @cp -f tabbed ${DESTDIR}${PREFIX}/bin + @chmod 755 ${DESTDIR}${PREFIX}/bin/tabbed + @echo installing manual page to ${DESTDIR}${MANPREFIX}/man1 + @mkdir -p ${DESTDIR}${MANPREFIX}/man1 + @sed "s/VERSION/${VERSION}/g" < tabbed.1 > ${DESTDIR}${MANPREFIX}/man1/tabbed.1 + @chmod 644 ${DESTDIR}${MANPREFIX}/man1/tabbed.1 + +uninstall: + @echo removing executable file from ${DESTDIR}${PREFIX}/bin + @rm -f ${DESTDIR}${PREFIX}/bin/tabbed + @echo removing manual page from ${DESTDIR}${MANPREFIX}/man1 + @rm -f ${DESTDIR}${MANPREFIX}/man1/tabbed.1 + +.PHONY: all options clean dist install uninstall @@ -0,0 +1,22 @@ +tabbed - generic tabbed interface +================================= +tabbed is a simple tabbed X window container. + +Requirements +------------ +In order to build tabbed you need the Xlib header files. + +Installation +------------ +Edit config.mk to match your local setup (tabbed is installed into +the /usr/local namespace by default). + +Afterwards enter the following command to build and install tabbed +(if necessary as root): + + make clean install + +Running tabbed +-------------- +See the man page for details. + @@ -0,0 +1,52 @@ +/* See the LICENSE file for copyright and license details. */ + +#ifndef __ARG_H__ +#define __ARG_H__ + +extern char *argv0; + +#define USED(x) ((void)(x)) + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][1]\ + && argv[0][0] == '-';\ + argc--, argv++) {\ + char _argc;\ + char **_argv;\ + int brk;\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + for (brk = 0, argv[0]++, _argv = argv;\ + argv[0][0] && !brk;\ + argv[0]++) {\ + if (_argv != argv)\ + break;\ + _argc = argv[0][0];\ + switch (_argc) + +#define ARGEND }\ + USED(_argc);\ + }\ + USED(argv);\ + USED(argc); + +#define ARGC() _argc + +#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\ + ((x), abort(), (char *)0) :\ + (brk = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\ + (char *)0 :\ + (brk = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#endif + diff --git a/config.def.h b/config.def.h new file mode 100644 index 0000000..bc7cfe2 --- /dev/null +++ b/config.def.h @@ -0,0 +1,58 @@ +/* See LICENSE file for copyright and license details. */ + +/* appearance */ +static const char font[] = "monospace-9"; +static const char* normbgcolor = "#222222"; +static const char* normfgcolor = "#cccccc"; +static const char* selbgcolor = "#555555"; +static const char* selfgcolor = "#ffffff"; +static const char before[] = "<"; +static const char after[] = ">"; +static const int tabwidth = 200; +static const Bool foreground = True; + +/* + * Where to place a new tab when it is opened. When npisrelative is True, + * then the current position is changed + newposition. If npisrelative + * is False, then newposition is an absolute position. + */ +static int newposition = 0; +static Bool npisrelative = False; + +#define SETPROP(p) { \ + .v = (char *[]){ "/bin/sh", "-c", \ + "prop=\"`xwininfo -children -id $1 | grep '^ 0x' | sed -e's@^ *\\(0x[0-9a-f]*\\) \"\\([^\"]*\\)\".*@\\1 \\2@' | xargs -0 printf %b | dmenu -l 10`\" &&" \ + "xprop -id $1 -f $0 8s -set $0 \"$prop\"", \ + p, winid, NULL \ + } \ +} + +#define MODKEY ControlMask +static Key keys[] = { \ + /* modifier key function argument */ + { MODKEY|ShiftMask, XK_Return, focusonce, { 0 } }, + { MODKEY|ShiftMask, XK_Return, spawn, { 0 } }, + { MODKEY, XK_t, spawn, SETPROP("_TABBED_SELECT_TAB") }, + + { MODKEY|ShiftMask, XK_l, rotate, { .i = +1 } }, + { MODKEY|ShiftMask, XK_h, rotate, { .i = -1 } }, + { MODKEY|ShiftMask, XK_j, movetab, { .i = -1 } }, + { MODKEY|ShiftMask, XK_k, movetab, { .i = +1 } }, + { MODKEY, XK_Tab, rotate, { .i = 0 } }, + + { MODKEY, XK_1, move, { .i = 0 } }, + { MODKEY, XK_2, move, { .i = 1 } }, + { MODKEY, XK_3, move, { .i = 2 } }, + { MODKEY, XK_4, move, { .i = 3 } }, + { MODKEY, XK_5, move, { .i = 4 } }, + { MODKEY, XK_6, move, { .i = 5 } }, + { MODKEY, XK_7, move, { .i = 6 } }, + { MODKEY, XK_8, move, { .i = 7 } }, + { MODKEY, XK_9, move, { .i = 8 } }, + { MODKEY, XK_0, move, { .i = 9 } }, + + { MODKEY, XK_q, killclient, { 0 } }, + + { 0, XK_F11, fullscreen, { 0 } }, +}; + diff --git a/config.h b/config.h new file mode 100644 index 0000000..12fdf4a --- /dev/null +++ b/config.h @@ -0,0 +1,58 @@ +/* See LICENSE file for copyright and license details. */ + +/* appearance */ +static const char font[] = "monospace-10"; +static const char* normbgcolor = "#222222"; +static const char* normfgcolor = "#cccccc"; +static const char* selbgcolor = "#555555"; +static const char* selfgcolor = "#ffffff"; +static const char before[] = "<"; +static const char after[] = ">"; +static const int tabwidth = 200; +static const Bool foreground = True; + +/* + * Where to place a new tab when it is opened. When npisrelative is True, + * then the current position is changed + newposition. If npisrelative + * is False, then newposition is an absolute position. + */ +static int newposition = 0; +static Bool npisrelative = False; + +#define SETPROP(p) { \ + .v = (char *[]){ "/bin/sh", "-c", \ + "prop=\"`xwininfo -children -id $1 | grep '^ 0x' | sed -e's@^ *\\(0x[0-9a-f]*\\) \"\\([^\"]*\\)\".*@\\1 \\2@' | xargs -0 printf %b | dmenu -l 10`\" &&" \ + "xprop -id $1 -f $0 8s -set $0 \"$prop\"", \ + p, winid, NULL \ + } \ +} + +#define MODKEY ControlMask +static Key keys[] = { \ + /* modifier key function argument */ + { MODKEY, XK_Return, focusonce, { 0 } }, + { MODKEY, XK_Return, spawn, { 0 } }, + { MODKEY, XK_t, spawn, SETPROP("_TABBED_SELECT_TAB") }, + + { MODKEY|ShiftMask, XK_l, rotate, { .i = +1 } }, + { MODKEY|ShiftMask, XK_h, rotate, { .i = -1 } }, + { MODKEY|ShiftMask, XK_j, movetab, { .i = -1 } }, + { MODKEY|ShiftMask, XK_k, movetab, { .i = +1 } }, + { MODKEY, XK_Tab, rotate, { .i = 0 } }, + + { MODKEY, XK_1, move, { .i = 0 } }, + { MODKEY, XK_2, move, { .i = 1 } }, + { MODKEY, XK_3, move, { .i = 2 } }, + { MODKEY, XK_4, move, { .i = 3 } }, + { MODKEY, XK_5, move, { .i = 4 } }, + { MODKEY, XK_6, move, { .i = 5 } }, + { MODKEY, XK_7, move, { .i = 6 } }, + { MODKEY, XK_8, move, { .i = 7 } }, + { MODKEY, XK_9, move, { .i = 8 } }, + { MODKEY, XK_0, move, { .i = 9 } }, + + { MODKEY, XK_q, killclient, { 0 } }, + + { 0, XK_F11, fullscreen, { 0 } }, +}; + diff --git a/config.h.old b/config.h.old new file mode 100644 index 0000000..22d396a --- /dev/null +++ b/config.h.old @@ -0,0 +1,59 @@ +/* See LICENSE file for copyright and license details. */ + +/* appearance */ +// static const char font[] = "-*-*-medium-*-*-*-14-*-*-*-*-*-*-*"; +static const char font[] = "Liberation Mono:pixelsize=20:antialias=true:autohint=true"; +static const char* normbgcolor = "#222222"; +static const char* normfgcolor = "#cccccc"; +static const char* selbgcolor = "#555555"; +static const char* selfgcolor = "#ffffff"; +static const char before[] = "<"; +static const char after[] = ">"; +static const int tabwidth = 300; +static const Bool foreground = True; + +/* + * Where to place a new tab when it is opened. When npisrelative is True, + * then the current position is changed + newposition. If npisrelative + * is False, then newposition is an absolute position. + */ +static int newposition = 0; +static Bool npisrelative = False; + +#define SETPROP(p) { \ + .v = (char *[]){ "/bin/sh", "-c", \ + "prop=\"`xwininfo -children -id $1 | grep '^ 0x' | sed -e's@^ *\\(0x[0-9a-f]*\\) \"\\([^\"]*\\)\".*@\\1 \\2@' | xargs -0 printf %b | dmenu -l 10`\" &&" \ + "xprop -id $1 -f $0 8s -set $0 \"$prop\"", \ + p, winid, NULL \ + } \ +} + +#define MODKEY ControlMask +static Key keys[] = { \ + /* modifier key function argument */ + { MODKEY|ShiftMask, XK_Return, focusonce, { 0 } }, + { MODKEY|ShiftMask, XK_Return, spawn, { 0 } }, + { MODKEY, XK_t, spawn, SETPROP("_TABBED_SELECT_TAB") }, + + { MODKEY|ShiftMask, XK_l, rotate, { .i = +1 } }, + { MODKEY|ShiftMask, XK_h, rotate, { .i = -1 } }, + { MODKEY|ShiftMask, XK_j, movetab, { .i = -1 } }, + { MODKEY|ShiftMask, XK_k, movetab, { .i = +1 } }, + { MODKEY, XK_Tab, rotate, { .i = 0 } }, + + { MODKEY, XK_1, move, { .i = 0 } }, + { MODKEY, XK_2, move, { .i = 1 } }, + { MODKEY, XK_3, move, { .i = 2 } }, + { MODKEY, XK_4, move, { .i = 3 } }, + { MODKEY, XK_5, move, { .i = 4 } }, + { MODKEY, XK_6, move, { .i = 5 } }, + { MODKEY, XK_7, move, { .i = 6 } }, + { MODKEY, XK_8, move, { .i = 7 } }, + { MODKEY, XK_9, move, { .i = 8 } }, + { MODKEY, XK_0, move, { .i = 9 } }, + + { MODKEY, XK_q, killclient, { 0 } }, + + { 0, XK_F11, fullscreen, { 0 } }, +}; + diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..037f9d7 --- /dev/null +++ b/config.mk @@ -0,0 +1,25 @@ +# tabbed version +VERSION = 0.6 + +# Customize below to fit your system + +# paths +PREFIX = /usr/local +MANPREFIX = ${PREFIX}/share/man + +# includes and libs +INCS = -I. -I/usr/include -I/usr/include/freetype2 +LIBS = -L/usr/lib -lc -lX11 -lfontconfig -lXft + +# flags +CPPFLAGS = -DVERSION=\"${VERSION}\" -D_BSD_SOURCE +CFLAGS = -std=c99 -pedantic -Wall -O0 ${INCS} ${CPPFLAGS} +LDFLAGS = -s ${LIBS} + +# Solaris +#CFLAGS = -fast ${INCS} -DVERSION=\"${VERSION}\" +#LDFLAGS = ${LIBS} + +# compiler and linker +CC = cc + diff --git a/patches/tabbed-0.6-xft.diff b/patches/tabbed-0.6-xft.diff new file mode 100644 index 0000000..7ec00fb --- /dev/null +++ b/patches/tabbed-0.6-xft.diff @@ -0,0 +1,233 @@ +diff --git a/config.def.h b/config.def.h +index ceda9f7..bc7cfe2 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -1,7 +1,7 @@ + /* See LICENSE file for copyright and license details. */ + + /* appearance */ +-static const char font[] = "-*-*-medium-*-*-*-14-*-*-*-*-*-*-*"; ++static const char font[] = "monospace-9"; + static const char* normbgcolor = "#222222"; + static const char* normfgcolor = "#cccccc"; + static const char* selbgcolor = "#555555"; +diff --git a/config.mk b/config.mk +index 5279711..037f9d7 100644 +--- a/config.mk ++++ b/config.mk +@@ -8,8 +8,8 @@ PREFIX = /usr/local + MANPREFIX = ${PREFIX}/share/man + + # includes and libs +-INCS = -I. -I/usr/include +-LIBS = -L/usr/lib -lc -lX11 ++INCS = -I. -I/usr/include -I/usr/include/freetype2 ++LIBS = -L/usr/lib -lc -lX11 -lfontconfig -lXft + + # flags + CPPFLAGS = -DVERSION=\"${VERSION}\" -D_BSD_SOURCE +diff --git a/tabbed.c b/tabbed.c +index d30206b..d08348c 100644 +--- a/tabbed.c ++++ b/tabbed.c +@@ -15,6 +15,7 @@ + #include <X11/Xproto.h> + #include <X11/Xutil.h> + #include <X11/XKBlib.h> ++#include <X11/Xft/Xft.h> + + #include "arg.h" + +@@ -64,16 +65,15 @@ typedef struct { + + typedef struct { + int x, y, w, h; +- unsigned long norm[ColLast]; +- unsigned long sel[ColLast]; ++ XftColor norm[ColLast]; ++ XftColor sel[ColLast]; + Drawable drawable; + GC gc; + struct { + int ascent; + int descent; + int height; +- XFontSet set; +- XFontStruct *xfont; ++ XftFont *xfont; + } font; + } DC; /* draw context */ + +@@ -95,7 +95,7 @@ static void createnotify(const XEvent *e); + static void destroynotify(const XEvent *e); + static void die(const char *errstr, ...); + static void drawbar(void); +-static void drawtext(const char *text, unsigned long col[ColLast]); ++static void drawtext(const char *text, XftColor col[ColLast]); + static void *emallocz(size_t size); + static void *erealloc(void *o, size_t size); + static void expose(const XEvent *e); +@@ -105,7 +105,7 @@ static void focusonce(const Arg *arg); + static void fullscreen(const Arg *arg); + static char* getatom(int a); + static int getclient(Window w); +-static unsigned long getcolor(const char *colstr); ++static XftColor getcolor(const char *colstr); + static int getfirsttab(void); + static Bool gettextprop(Window w, Atom atom, char *text, unsigned int size); + static void initfont(const char *fontstr); +@@ -219,12 +219,6 @@ cleanup(void) { + free(clients); + clients = NULL; + +- if(dc.font.set) { +- XFreeFontSet(dpy, dc.font.set); +- } else { +- XFreeFont(dpy, dc.font.xfont); +- } +- + XFreePixmap(dpy, dc.drawable); + XFreeGC(dpy, dc.gc); + XDestroyWindow(dpy, win); +@@ -305,7 +299,7 @@ die(const char *errstr, ...) { + + void + drawbar(void) { +- unsigned long *col; ++ XftColor *col; + int c, fc, width, n = 0; + char *name = NULL; + +@@ -362,12 +356,13 @@ drawbar(void) { + } + + void +-drawtext(const char *text, unsigned long col[ColLast]) { ++drawtext(const char *text, XftColor col[ColLast]) { + int i, x, y, h, len, olen; + char buf[256]; ++ XftDraw *d; + XRectangle r = { dc.x, dc.y, dc.w, dc.h }; + +- XSetForeground(dpy, dc.gc, col[ColBG]); ++ XSetForeground(dpy, dc.gc, col[ColBG].pixel); + XFillRectangles(dpy, dc.drawable, dc.gc, &r, 1); + if(!text) + return; +@@ -388,13 +383,10 @@ drawtext(const char *text, unsigned long col[ColLast]) { + for(i = len; i && i > len - 3; buf[--i] = '.'); + } + +- XSetForeground(dpy, dc.gc, col[ColFG]); +- if(dc.font.set) { +- XmbDrawString(dpy, dc.drawable, dc.font.set, +- dc.gc, x, y, buf, len); +- } else { +- XDrawString(dpy, dc.drawable, dc.gc, x, y, buf, len); +- } ++ d = XftDrawCreate(dpy, dc.drawable, DefaultVisual(dpy, screen), DefaultColormap(dpy, screen)); ++ ++ XftDrawStringUtf8(d, &col[ColFG], dc.font.xfont, x, y, (XftChar8 *) buf, len); ++ XftDrawDestroy(d); + } + + void * +@@ -524,15 +516,14 @@ getclient(Window w) { + return -1; + } + +-unsigned long ++XftColor + getcolor(const char *colstr) { +- Colormap cmap = DefaultColormap(dpy, screen); +- XColor color; ++ XftColor color; + +- if(!XAllocNamedColor(dpy, cmap, colstr, &color, &color)) ++ if(!XftColorAllocName(dpy, DefaultVisual(dpy, screen), DefaultColormap(dpy, screen), colstr, &color)) + die("tabbed: cannot allocate color '%s'\n", colstr); + +- return color.pixel; ++ return color; + } + + int +@@ -585,41 +576,12 @@ gettextprop(Window w, Atom atom, char *text, unsigned int size) { + + void + initfont(const char *fontstr) { +- char *def, **missing, **font_names; +- int i, n; +- XFontStruct **xfonts; +- +- missing = NULL; +- if(dc.font.set) +- XFreeFontSet(dpy, dc.font.set); +- +- dc.font.set = XCreateFontSet(dpy, fontstr, &missing, &n, &def); +- if(missing) { +- while(n--) +- fprintf(stderr, "tabbed: missing fontset: %s\n", missing[n]); +- XFreeStringList(missing); +- } ++ if(!(dc.font.xfont = XftFontOpenName(dpy, screen, fontstr)) ++ && !(dc.font.xfont = XftFontOpenName(dpy, screen, "fixed"))) ++ die("error, cannot load font: '%s'\n", fontstr); + +- if(dc.font.set) { +- dc.font.ascent = dc.font.descent = 0; +- n = XFontsOfFontSet(dc.font.set, &xfonts, &font_names); +- for(i = 0, dc.font.ascent = 0, dc.font.descent = 0; i < n; i++) { +- dc.font.ascent = MAX(dc.font.ascent, (*xfonts)->ascent); +- dc.font.descent = MAX(dc.font.descent,(*xfonts)->descent); +- xfonts++; +- } +- } else { +- if(dc.font.xfont) +- XFreeFont(dpy, dc.font.xfont); +- dc.font.xfont = NULL; +- if(!(dc.font.xfont = XLoadQueryFont(dpy, fontstr)) +- && !(dc.font.xfont = XLoadQueryFont(dpy, "fixed"))) { +- die("tabbed: cannot load font: '%s'\n", fontstr); +- } +- +- dc.font.ascent = dc.font.xfont->ascent; +- dc.font.descent = dc.font.xfont->descent; +- } ++ dc.font.ascent = dc.font.xfont->ascent; ++ dc.font.descent = dc.font.xfont->descent; + dc.font.height = dc.font.ascent + dc.font.descent; + } + +@@ -972,11 +934,9 @@ setup(void) { + dc.drawable = XCreatePixmap(dpy, root, ww, wh, + DefaultDepth(dpy, screen)); + dc.gc = XCreateGC(dpy, root, 0, 0); +- if(!dc.font.set) +- XSetFont(dpy, dc.gc, dc.font.xfont->fid); + + win = XCreateSimpleWindow(dpy, root, wx, wy, ww, wh, 0, +- dc.norm[ColFG], dc.norm[ColBG]); ++ dc.norm[ColFG].pixel, dc.norm[ColBG].pixel); + XMapRaised(dpy, win); + XSelectInput(dpy, win, SubstructureNotifyMask|FocusChangeMask| + ButtonPressMask|ExposureMask|KeyPressMask|PropertyChangeMask| +@@ -1040,15 +1000,9 @@ spawn(const Arg *arg) { + + int + textnw(const char *text, unsigned int len) { +- XRectangle r; +- +- if(dc.font.set) { +- XmbTextExtents(dc.font.set, text, len, NULL, &r); +- +- return r.width; +- } +- +- return XTextWidth(dc.font.xfont, text, len); ++ XGlyphInfo ext; ++ XftTextExtentsUtf8(dpy, dc.font.xfont, (XftChar8 *) text, len, &ext); ++ return ext.xOff; + } + + void diff --git a/patches/tabbed-clientnumber-0.6.diff b/patches/tabbed-clientnumber-0.6.diff new file mode 100644 index 0000000..430245c --- /dev/null +++ b/patches/tabbed-clientnumber-0.6.diff @@ -0,0 +1,23 @@ +diff --git a/tabbed.c b/tabbed.c +index d30206b..70642cb 100644 +--- a/tabbed.c ++++ b/tabbed.c +@@ -308,6 +308,7 @@ drawbar(void) { + unsigned long *col; + int c, fc, width, n = 0; + char *name = NULL; ++ char tabtitle[256]; + + if(nclients == 0) { + dc.x = 0; +@@ -353,7 +354,9 @@ drawbar(void) { + } else { + col = dc.norm; + } +- drawtext(clients[c]->name, col); ++ snprintf(tabtitle, sizeof(tabtitle), "%d: %s", ++ c + 1, clients[c]->name); ++ drawtext(tabtitle, col); + dc.x += dc.w; + clients[c]->tabx = dc.x; + } diff --git a/tabbed.1 b/tabbed.1 new file mode 100644 index 0000000..0ae29ce --- /dev/null +++ b/tabbed.1 @@ -0,0 +1,144 @@ +.TH TABBED 1 tabbed\-VERSION +.SH NAME +tabbed \- generic tabbed interface +.SH SYNOPSIS +.B tabbed +.RB [ \-c ] +.RB [ \-d ] +.RB [ \-h ] +.RB [ \-s ] +.RB [ \-v ] +.RB [ \-g +.IR geometry ] +.RB [ \-n +.IR name ] +.RB [ \-p +.IR [ s +/- ] pos ] +.RB [ \-r +.IR narg ] +.IR [ command ... ] +.SH DESCRIPTION +.B tabbed +is a simple tabbed container for applications which support XEmbed. Tabbed +will then run the provided command with the xid of tabbed as appended +argument. (See EXAMPLES.) The automatic spawning of the command can be +disabled by providing the -s parameter. If no command is provided +tabbed will just print its xid and run no command. +.SH OPTIONS +.TP +.B \-c +close tabbed when the last tab is closed. Mutually exclusive with -f. +.TP +.B \-d +detaches tabbed from the terminal and prints its XID to stdout. +.TP +.B \-f +fill up tabbed again by spawning the provided command, when the last tab is +closed. Mutually exclusive with -c. +.TP +.B \-h +will print the usage of tabbed. +.TP +.BI \-g " geometry" +defines the X11 geometry string, which will fixate the height and width of +tabbed. +Them form is [=][<width>{xX}<height>][{+-}<xoffset>{+-}<yoffset>]. See +.BR XParseGeometry (3) +for further details. +.TP +.BI \-n " name" +will set the WM_CLASS attribute to +.I name. +.TP +.BI \-p " [ s +/-] pos" +will set the absolute or relative position of where to start a new tab. When +.I pos +is is given without 's' in front it is an absolute position. Then negative +numbers will be the position from the last tab, where -1 is the last tab. +If 's' is given, then +.I pos +is a relative position to the current selected tab. If this reaches the limits +of the tabs; those limits then apply. +.TP +.BI \-r " narg" +will replace the +.I narg +th argument in +.I command +with the window id, rather than appending it to the end. +.TP +.B \-s +will disable automatic spawning of the command. +.TP +.BI \-t " color" +defines the selected background color. +.IR #RGB , +.IR #RRGGBB , +and X color names are supported. +.TP +.BI \-T " color" +defines the selected foreground color. +.TP +.BI \-u " color" +defines the normal background color. +.TP +.BI \-U " color" +defines the normal foreground color. +.TP +.B \-v +prints version information to stderr, then exits. +.SH USAGE +.TP +.B Ctrl\-Shift\-Return +open new tab +.TP +.B Ctrl\-Shift\-h +previous tab +.TP +.B Ctrl\-Shift\-l +next tab +.TP +.B Ctrl\-Shift\-j +move selected tab one to the left +.TP +.B Ctrl\-Shift\-k +move selected tab one to the right +.TP +.B Ctrl\-Tab +toggle between the selected and last selected tab +.TP +.B Ctrl\-t +open dmenu to either create a new tab appending the entered string or select +an already existing tab. +.TP +.B Ctrl\-q +close tab +.TP +.B Ctrl\-[0..9] +jumps to nth tab +.TP +.B F11 +Toggle fullscreen mode. +.SH EXAMPLES +$ tabbed surf -e +.TP +$ tabbed urxvt -embed +.TP +$ tabbed xterm -into +.TP +$ $(tabbed -d >/tmp/tabbed.xid); urxvt -embed $(</tmp/tabbed.xid); +.TP +$ tabbed -r 2 st -w '' -e tmux +.SH CUSTOMIZATION +.B tabbed +can be customized by creating a custom config.h and (re)compiling the source +code. This keeps it fast, secure and simple. +.SH AUTHORS +See the LICENSE file for the authors. +.SH LICENSE +See the LICENSE file for the terms of redistribution. +.SH SEE ALSO +.BR st (1) +.SH BUGS +Please report them. + diff --git a/tabbed.c b/tabbed.c new file mode 100644 index 0000000..6c3bf55 --- /dev/null +++ b/tabbed.c @@ -0,0 +1,1254 @@ +/* + * See LICENSE file for copyright and license details. + */ + +#include <sys/wait.h> +#include <locale.h> +#include <stdarg.h> +#include <unistd.h> +#include <signal.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <X11/Xatom.h> +#include <X11/Xlib.h> +#include <X11/Xproto.h> +#include <X11/Xutil.h> +#include <X11/XKBlib.h> +#include <X11/Xft/Xft.h> + +#include "arg.h" + +/* XEMBED messages */ +#define XEMBED_EMBEDDED_NOTIFY 0 +#define XEMBED_WINDOW_ACTIVATE 1 +#define XEMBED_WINDOW_DEACTIVATE 2 +#define XEMBED_REQUEST_FOCUS 3 +#define XEMBED_FOCUS_IN 4 +#define XEMBED_FOCUS_OUT 5 +#define XEMBED_FOCUS_NEXT 6 +#define XEMBED_FOCUS_PREV 7 +/* 8-9 were used for XEMBED_GRAB_KEY/XEMBED_UNGRAB_KEY */ +#define XEMBED_MODALITY_ON 10 +#define XEMBED_MODALITY_OFF 11 +#define XEMBED_REGISTER_ACCELERATOR 12 +#define XEMBED_UNREGISTER_ACCELERATOR 13 +#define XEMBED_ACTIVATE_ACCELERATOR 14 + +/* Details for XEMBED_FOCUS_IN: */ +#define XEMBED_FOCUS_CURRENT 0 +#define XEMBED_FOCUS_FIRST 1 +#define XEMBED_FOCUS_LAST 2 + +/* Macros */ +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define LENGTH(x) (sizeof((x)) / sizeof(*(x))) +#define CLEANMASK(mask) (mask & ~(numlockmask|LockMask)) +#define TEXTW(x) (textnw(x, strlen(x)) + dc.font.height) + +enum { ColFG, ColBG, ColLast }; /* color */ +enum { WMProtocols, WMDelete, WMName, WMState, WMFullscreen, + XEmbed, WMSelectTab, WMLast }; /* default atoms */ + +typedef union { + int i; + const void *v; +} Arg; + +typedef struct { + unsigned int mod; + KeySym keysym; + void (*func)(const Arg *); + const Arg arg; +} Key; + +typedef struct { + int x, y, w, h; + XftColor norm[ColLast]; + XftColor sel[ColLast]; + Drawable drawable; + GC gc; + struct { + int ascent; + int descent; + int height; + XftFont *xfont; + } font; +} DC; /* draw context */ + +typedef struct Client { + char name[256]; + Window win; + int tabx; + Bool mapped; + Bool closed; +} Client; + +/* function declarations */ +static void buttonpress(const XEvent *e); +static void cleanup(void); +static void clientmessage(const XEvent *e); +static void configurenotify(const XEvent *e); +static void configurerequest(const XEvent *e); +static void createnotify(const XEvent *e); +static void destroynotify(const XEvent *e); +static void die(const char *errstr, ...); +static void drawbar(void); +static void drawtext(const char *text, XftColor col[ColLast]); +static void *emallocz(size_t size); +static void *erealloc(void *o, size_t size); +static void expose(const XEvent *e); +static void focus(int c); +static void focusin(const XEvent *e); +static void focusonce(const Arg *arg); +static void fullscreen(const Arg *arg); +static char* getatom(int a); +static int getclient(Window w); +static XftColor getcolor(const char *colstr); +static int getfirsttab(void); +static Bool gettextprop(Window w, Atom atom, char *text, unsigned int size); +static void initfont(const char *fontstr); +static Bool isprotodel(int c); +static void keypress(const XEvent *e); +static void killclient(const Arg *arg); +static void manage(Window win); +static void maprequest(const XEvent *e); +static void move(const Arg *arg); +static void movetab(const Arg *arg); +static void propertynotify(const XEvent *e); +static void resize(int c, int w, int h); +static void rotate(const Arg *arg); +static void run(void); +static void sendxembed(int c, long msg, long detail, long d1, long d2); +static void setup(void); +static void setcmd(int argc, char *argv[], int); +static void sigchld(int unused); +static void spawn(const Arg *arg); +static int textnw(const char *text, unsigned int len); +static void unmanage(int c); +static void updatenumlockmask(void); +static void updatetitle(int c); +static int xerror(Display *dpy, XErrorEvent *ee); +static void xsettitle(Window w, const char *str); + +/* variables */ +static int screen; +static void (*handler[LASTEvent]) (const XEvent *) = { + [ButtonPress] = buttonpress, + [ClientMessage] = clientmessage, + [ConfigureNotify] = configurenotify, + [ConfigureRequest] = configurerequest, + [CreateNotify] = createnotify, + [DestroyNotify] = destroynotify, + [Expose] = expose, + [FocusIn] = focusin, + [KeyPress] = keypress, + [MapRequest] = maprequest, + [PropertyNotify] = propertynotify, +}; +static int bh, wx, wy, ww, wh; +static unsigned int numlockmask = 0; +static Bool running = True, nextfocus, doinitspawn = True, + fillagain = False, closelastclient = False; +static Display *dpy; +static DC dc; +static Atom wmatom[WMLast]; +static Window root, win; +static Client **clients = NULL; +static int nclients = 0, sel = -1, lastsel = -1; +static int (*xerrorxlib)(Display *, XErrorEvent *); +static int cmd_append_pos = 0; +static char winid[64]; +static char **cmd = NULL; +static char *wmname = "tabbed"; +static const char *geometry = NULL; + +char *argv0; + +/* configuration, allows nested code to access above variables */ +#include "config.h" + +void +buttonpress(const XEvent *e) { + const XButtonPressedEvent *ev = &e->xbutton; + int i; + int fc; + Arg arg; + + fc = getfirsttab(); + + if((fc > 0 && ev->x < TEXTW(before)) || ev->x < 0) + return; + + if(ev->y < 0 || ev-> y > bh) + return; + + for(i = (fc > 0) ? fc : 0; i < nclients; i++) { + if(clients[i]->tabx > ev->x) { + switch(ev->button) { + case Button1: + focus(i); + break; + case Button2: + focus(i); + killclient(NULL); + break; + case Button4: + case Button5: + arg.i = ev->button == Button4 ? -1 : 1; + rotate(&arg); + break; + } + break; + } + } +} + +void +cleanup(void) { + int i; + + for(i = 0; i < nclients; i++) { + focus(i); + killclient(NULL); + killclient(NULL); + XReparentWindow(dpy, clients[i]->win, root, 0, 0); + unmanage(i); + } + free(clients); + clients = NULL; + + XFreePixmap(dpy, dc.drawable); + XFreeGC(dpy, dc.gc); + XDestroyWindow(dpy, win); + XSync(dpy, False); + free(cmd); +} + +void +clientmessage(const XEvent *e) { + const XClientMessageEvent *ev = &e->xclient; + + if(ev->message_type == wmatom[WMProtocols] + && ev->data.l[0] == wmatom[WMDelete]) { + running = False; + } +} + +void +configurenotify(const XEvent *e) { + const XConfigureEvent *ev = &e->xconfigure; + + if(ev->window == win && (ev->width != ww || ev->height != wh)) { + ww = ev->width; + wh = ev->height; + XFreePixmap(dpy, dc.drawable); + dc.drawable = XCreatePixmap(dpy, root, ww, wh, + DefaultDepth(dpy, screen)); + if(sel > -1) + resize(sel, ww, wh - bh); + XSync(dpy, False); + } +} + +void +configurerequest(const XEvent *e) { + const XConfigureRequestEvent *ev = &e->xconfigurerequest; + XWindowChanges wc; + int c; + + if((c = getclient(ev->window)) > -1) { + wc.x = 0; + wc.y = bh; + wc.width = ww; + wc.height = wh - bh; + wc.border_width = 0; + wc.sibling = ev->above; + wc.stack_mode = ev->detail; + XConfigureWindow(dpy, clients[c]->win, ev->value_mask, &wc); + } +} + +void +createnotify(const XEvent *e) { + const XCreateWindowEvent *ev = &e->xcreatewindow; + + if(ev->window != win && getclient(ev->window) < 0) + manage(ev->window); +} + +void +destroynotify(const XEvent *e) { + const XDestroyWindowEvent *ev = &e->xdestroywindow; + int c; + + if((c = getclient(ev->window)) > -1) + unmanage(c); +} + +void +die(const char *errstr, ...) { + va_list ap; + + va_start(ap, errstr); + vfprintf(stderr, errstr, ap); + va_end(ap); + exit(EXIT_FAILURE); +} + +void +drawbar(void) { + XftColor *col; + int c, fc, width, n = 0; + char *name = NULL; + char tabtitle[256]; + + if(nclients == 0) { + dc.x = 0; + dc.w = ww; + XFetchName(dpy, win, &name); + drawtext(name ? name : "", dc.norm); + XCopyArea(dpy, dc.drawable, win, dc.gc, 0, 0, ww, bh, 0, 0); + XSync(dpy, False); + + return; + } + + width = ww; + clients[nclients-1]->tabx = -1; + fc = getfirsttab(); + if(fc > -1) + n = nclients - fc; + + if((n * tabwidth) > width) { + dc.w = TEXTW(after); + dc.x = width - dc.w; + drawtext(after, dc.sel); + width -= dc.w; + } + dc.x = 0; + + if(fc > 0) { + dc.w = TEXTW(before); + drawtext(before, dc.sel); + dc.x += dc.w; + width -= dc.w; + } + + for(c = (fc > 0)? fc : 0; c < nclients && dc.x < width; c++) { + dc.w = tabwidth; + if(c == sel) { + col = dc.sel; + if((n * tabwidth) > width) { + dc.w += width % tabwidth; + } else { + dc.w = width - (n - 1) * tabwidth; + } + } else { + col = dc.norm; + } + snprintf(tabtitle, sizeof(tabtitle), "%d: %s", + c + 1, clients[c]->name); + drawtext(tabtitle, col); + dc.x += dc.w; + clients[c]->tabx = dc.x; + } + XCopyArea(dpy, dc.drawable, win, dc.gc, 0, 0, ww, bh, 0, 0); + XSync(dpy, False); +} + +void +drawtext(const char *text, XftColor col[ColLast]) { + int i, x, y, h, len, olen; + char buf[256]; + XftDraw *d; + XRectangle r = { dc.x, dc.y, dc.w, dc.h }; + + XSetForeground(dpy, dc.gc, col[ColBG].pixel); + XFillRectangles(dpy, dc.drawable, dc.gc, &r, 1); + if(!text) + return; + + olen = strlen(text); + h = dc.font.ascent + dc.font.descent; + y = dc.y + (dc.h / 2) - (h / 2) + dc.font.ascent; + x = dc.x + (h / 2); + + /* shorten text if necessary */ + for(len = MIN(olen, sizeof(buf)); + len && textnw(text, len) > dc.w - h; len--); + if(!len) + return; + + memcpy(buf, text, len); + if(len < olen) { + for(i = len; i && i > len - 3; buf[--i] = '.'); + } + + d = XftDrawCreate(dpy, dc.drawable, DefaultVisual(dpy, screen), DefaultColormap(dpy, screen)); + + XftDrawStringUtf8(d, &col[ColFG], dc.font.xfont, x, y, (XftChar8 *) buf, len); + XftDrawDestroy(d); +} + +void * +emallocz(size_t size) { + void *p; + + if(!(p = calloc(1, size))) + die("tabbed: cannot malloc\n"); + return p; +} + +void * +erealloc(void *o, size_t size) { + void *p; + + if(!(p = realloc(o, size))) + die("tabbed: cannot realloc\n"); + return p; +} + +void +expose(const XEvent *e) { + const XExposeEvent *ev = &e->xexpose; + + if(ev->count == 0 && win == ev->window) + drawbar(); +} + +void +focus(int c) { + char buf[BUFSIZ] = "tabbed-"VERSION" ::"; + size_t i, n; + + /* If c, sel and clients are -1, raise tabbed-win itself */ + if(nclients == 0) { + cmd[cmd_append_pos] = NULL; + for(i = 0, n = strlen(buf); cmd[i] && n < sizeof(buf); i++) + n += snprintf(&buf[n], sizeof(buf) - n, " %s", cmd[i]); + + xsettitle(win, buf); + XRaiseWindow(dpy, win); + + return; + } + + if(c < 0 || c >= nclients) + return; + + resize(c, ww, wh - bh); + XRaiseWindow(dpy, clients[c]->win); + XSetInputFocus(dpy, clients[c]->win, RevertToParent, CurrentTime); + sendxembed(c, XEMBED_FOCUS_IN, XEMBED_FOCUS_CURRENT, 0, 0); + sendxembed(c, XEMBED_WINDOW_ACTIVATE, 0, 0, 0); + xsettitle(win, clients[c]->name); + + /* If sel is already c, change nothing. */ + if(sel != c) { + lastsel = sel; + sel = c; + } + + drawbar(); + XSync(dpy, False); +} + +void +focusin(const XEvent *e) { + const XFocusChangeEvent *ev = &e->xfocus; + int dummy; + Window focused; + + if(ev->mode != NotifyUngrab) { + XGetInputFocus(dpy, &focused, &dummy); + if(focused == win) + focus(sel); + } +} + +void +focusonce(const Arg *arg) { + nextfocus = True; +} + +void +fullscreen(const Arg *arg) { + XEvent e; + + e.type = ClientMessage; + e.xclient.window = win; + e.xclient.message_type = wmatom[WMState]; + e.xclient.format = 32; + e.xclient.data.l[0] = 2; + e.xclient.data.l[1] = wmatom[WMFullscreen]; + e.xclient.data.l[2] = 0; + XSendEvent(dpy, root, False, SubstructureNotifyMask, &e); +} + +char * +getatom(int a) { + static char buf[BUFSIZ]; + Atom adummy; + int idummy; + unsigned long ldummy; + unsigned char *p = NULL; + + XGetWindowProperty(dpy, win, wmatom[a], 0L, BUFSIZ, False, XA_STRING, + &adummy, &idummy, &ldummy, &ldummy, &p); + if(p) { + strncpy(buf, (char *)p, LENGTH(buf)-1); + } else { + buf[0] = '\0'; + } + XFree(p); + + return buf; +} + +int +getclient(Window w) { + int i; + + for(i = 0; i < nclients; i++) { + if(clients[i]->win == w) + return i; + } + + return -1; +} + +XftColor +getcolor(const char *colstr) { + XftColor color; + + if(!XftColorAllocName(dpy, DefaultVisual(dpy, screen), DefaultColormap(dpy, screen), colstr, &color)) + die("tabbed: cannot allocate color '%s'\n", colstr); + + return color; +} + +int +getfirsttab(void) { + int c, n, fc; + + if(sel < 0) + return -1; + + c = sel; + fc = 0; + n = nclients; + if((n * tabwidth) > ww) { + for(; (c * tabwidth) > (ww / 2) + && (n * tabwidth) > ww; + c--, n--, fc++); + } + + return fc; +} + +Bool +gettextprop(Window w, Atom atom, char *text, unsigned int size) { + char **list = NULL; + int n; + XTextProperty name; + + if(!text || size == 0) + return False; + + text[0] = '\0'; + XGetTextProperty(dpy, w, &name, atom); + if(!name.nitems) + return False; + + if(name.encoding == XA_STRING) { + strncpy(text, (char *)name.value, size - 1); + } else { + if(XmbTextPropertyToTextList(dpy, &name, &list, &n) >= Success + && n > 0 && *list) { + strncpy(text, *list, size - 1); + XFreeStringList(list); + } + } + text[size - 1] = '\0'; + XFree(name.value); + + return True; +} + +void +initfont(const char *fontstr) { + if(!(dc.font.xfont = XftFontOpenName(dpy, screen, fontstr)) + && !(dc.font.xfont = XftFontOpenName(dpy, screen, "fixed"))) + die("error, cannot load font: '%s'\n", fontstr); + + dc.font.ascent = dc.font.xfont->ascent; + dc.font.descent = dc.font.xfont->descent; + dc.font.height = dc.font.ascent + dc.font.descent; +} + +Bool +isprotodel(int c) { + int i, n; + Atom *protocols; + Bool ret = False; + + if(XGetWMProtocols(dpy, clients[c]->win, &protocols, &n)) { + for(i = 0; !ret && i < n; i++) { + if(protocols[i] == wmatom[WMDelete]) + ret = True; + } + XFree(protocols); + } + + return ret; +} + +void +keypress(const XEvent *e) { + const XKeyEvent *ev = &e->xkey; + unsigned int i; + KeySym keysym; + + keysym = XkbKeycodeToKeysym(dpy, (KeyCode)ev->keycode, 0, 0); + for(i = 0; i < LENGTH(keys); i++) { + if(keysym == keys[i].keysym + && CLEANMASK(keys[i].mod) == CLEANMASK(ev->state) + && keys[i].func) { + keys[i].func(&(keys[i].arg)); + } + } +} + +void +killclient(const Arg *arg) { + XEvent ev; + + if(sel < 0) + return; + + if(isprotodel(sel) && !clients[sel]->closed) { + ev.type = ClientMessage; + ev.xclient.window = clients[sel]->win; + ev.xclient.message_type = wmatom[WMProtocols]; + ev.xclient.format = 32; + ev.xclient.data.l[0] = wmatom[WMDelete]; + ev.xclient.data.l[1] = CurrentTime; + XSendEvent(dpy, clients[sel]->win, False, NoEventMask, &ev); + clients[sel]->closed = True; + } else { + XKillClient(dpy, clients[sel]->win); + } +} + +void +manage(Window w) { + updatenumlockmask(); + { + int i, j, nextpos; + unsigned int modifiers[] = { 0, LockMask, numlockmask, + numlockmask|LockMask }; + KeyCode code; + Client *c; + XEvent e; + + XWithdrawWindow(dpy, w, 0); + XReparentWindow(dpy, w, win, 0, bh); + XSelectInput(dpy, w, PropertyChangeMask + |StructureNotifyMask|EnterWindowMask); + XSync(dpy, False); + + for(i = 0; i < LENGTH(keys); i++) { + if((code = XKeysymToKeycode(dpy, keys[i].keysym))) { + for(j = 0; j < LENGTH(modifiers); j++) { + XGrabKey(dpy, code, keys[i].mod + | modifiers[j], w, + True, GrabModeAsync, + GrabModeAsync); + } + } + } + + c = emallocz(sizeof(*c)); + c->win = w; + + nclients++; + clients = erealloc(clients, sizeof(Client *) * nclients); + + if(npisrelative) { + nextpos = sel + newposition; + } else { + if(newposition < 0) { + nextpos = nclients - newposition; + } else { + nextpos = newposition; + } + } + if(nextpos >= nclients) + nextpos = nclients - 1; + if(nextpos < 0) + nextpos = 0; + + if(nclients > 1 && nextpos < nclients - 1) { + memmove(&clients[nextpos + 1], &clients[nextpos], + sizeof(Client *) * + (nclients - nextpos - 1)); + } + clients[nextpos] = c; + updatetitle(nextpos); + + XLowerWindow(dpy, w); + XMapWindow(dpy, w); + + e.xclient.window = w; + e.xclient.type = ClientMessage; + e.xclient.message_type = wmatom[XEmbed]; + e.xclient.format = 32; + e.xclient.data.l[0] = CurrentTime; + e.xclient.data.l[1] = XEMBED_EMBEDDED_NOTIFY; + e.xclient.data.l[2] = 0; + e.xclient.data.l[3] = win; + e.xclient.data.l[4] = 0; + XSendEvent(dpy, root, False, NoEventMask, &e); + + XSync(dpy, False); + + /* Adjust sel before focus does set it to lastsel. */ + if(sel >= nextpos) + sel++; + focus((nextfocus)? nextpos : ((sel < 0)? 0 : sel)); + nextfocus = foreground; + } +} + +void +maprequest(const XEvent *e) { + const XMapRequestEvent *ev = &e->xmaprequest; + + if(getclient(ev->window) < 0) + manage(ev->window); +} + +void +move(const Arg *arg) { + if(arg->i >= 0 && arg->i < nclients) + focus(arg->i); +} + +void +movetab(const Arg *arg) { + int c; + Client *new; + + if(sel < 0 || (arg->i == 0)) + return; + + c = sel + arg->i; + while(c >= nclients) + c -= nclients; + while(c < 0) + c += nclients; + + new = clients[c]; + clients[c] = clients[sel]; + clients[sel] = new; + + sel = c; + + drawbar(); +} + +void +propertynotify(const XEvent *e) { + const XPropertyEvent *ev = &e->xproperty; + int c; + char* selection = NULL; + Arg arg; + + if(ev->state == PropertyNewValue && ev->atom == wmatom[WMSelectTab]) { + selection = getatom(WMSelectTab); + if(!strncmp(selection, "0x", 2)) { + arg.i = getclient(strtoul(selection, NULL, 0)); + move(&arg); + } else { + cmd[cmd_append_pos] = selection; + arg.v = cmd; + spawn(&arg); + } + } else if(ev->state != PropertyDelete && ev->atom == XA_WM_NAME + && (c = getclient(ev->window)) > -1) { + updatetitle(c); + } +} + +void +resize(int c, int w, int h) { + XConfigureEvent ce; + XWindowChanges wc; + + ce.x = 0; + ce.y = bh; + ce.width = wc.width = w; + ce.height = wc.height = h; + ce.type = ConfigureNotify; + ce.display = dpy; + ce.event = clients[c]->win; + ce.window = clients[c]->win; + ce.above = None; + ce.override_redirect = False; + ce.border_width = 0; + + XConfigureWindow(dpy, clients[c]->win, CWWidth|CWHeight, &wc); + XSendEvent(dpy, clients[c]->win, False, StructureNotifyMask, + (XEvent *)&ce); +} + +void +rotate(const Arg *arg) { + int nsel = -1; + + if(sel < 0) + return; + + if(arg->i == 0) { + if(lastsel > -1) + focus(lastsel); + } else if(sel > -1) { + /* Rotating in an arg->i step around the clients. */ + nsel = sel + arg->i; + while(nsel >= nclients) + nsel -= nclients; + while(nsel < 0) + nsel += nclients; + focus(nsel); + } +} + +void +run(void) { + XEvent ev; + + /* main event loop */ + XSync(dpy, False); + drawbar(); + if(doinitspawn == True) + spawn(NULL); + + while(running) { + XNextEvent(dpy, &ev); + if(handler[ev.type]) + (handler[ev.type])(&ev); /* call handler */ + } +} + +void +sendxembed(int c, long msg, long detail, long d1, long d2) { + XEvent e = { 0 }; + + e.xclient.window = clients[c]->win; + e.xclient.type = ClientMessage; + e.xclient.message_type = wmatom[XEmbed]; + e.xclient.format = 32; + e.xclient.data.l[0] = CurrentTime; + e.xclient.data.l[1] = msg; + e.xclient.data.l[2] = detail; + e.xclient.data.l[3] = d1; + e.xclient.data.l[4] = d2; + XSendEvent(dpy, clients[c]->win, False, NoEventMask, &e); +} + +void +setcmd(int argc, char *argv[], int replace) { + int i; + + cmd = emallocz((argc+3) * sizeof(*cmd)); + if (argc == 0) + return; + for(i = 0; i < argc; i++) + cmd[i] = argv[i]; + cmd[(replace > 0)? replace : argc] = winid; + cmd_append_pos = argc + !replace; + cmd[cmd_append_pos] = cmd[cmd_append_pos+1] = NULL; +} + +void +setup(void) { + int bitm, tx, ty, tw, th, dh, dw, isfixed; + XClassHint class_hint; + XSizeHints *size_hint; + + /* clean up any zombies immediately */ + sigchld(0); + + /* init screen */ + screen = DefaultScreen(dpy); + root = RootWindow(dpy, screen); + initfont(font); + bh = dc.h = dc.font.height + 2; + + /* init atoms */ + wmatom[WMProtocols] = XInternAtom(dpy, "WM_PROTOCOLS", False); + wmatom[WMDelete] = XInternAtom(dpy, "WM_DELETE_WINDOW", False); + wmatom[XEmbed] = XInternAtom(dpy, "_XEMBED", False); + wmatom[WMName] = XInternAtom(dpy, "_NET_WM_NAME", False); + wmatom[WMState] = XInternAtom(dpy, "_NET_WM_STATE", False); + wmatom[WMFullscreen] = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False); + wmatom[WMSelectTab] = XInternAtom(dpy, "_TABBED_SELECT_TAB", False); + + /* init appearance */ + wx = 0; + wy = 0; + ww = 800; + wh = 600; + isfixed = 0; + + if(geometry) { + tx = ty = tw = th = 0; + bitm = XParseGeometry(geometry, &tx, &ty, (unsigned *)&tw, + (unsigned *)&th); + if(bitm & XValue) + wx = tx; + if(bitm & YValue) + wy = ty; + if(bitm & WidthValue) + ww = tw; + if(bitm & HeightValue) + wh = th; + if(bitm & XNegative && wx == 0) + wx = -1; + if(bitm & YNegative && wy == 0) + wy = -1; + if(bitm & (HeightValue|WidthValue)) + isfixed = 1; + + dw = DisplayWidth(dpy, screen); + dh = DisplayHeight(dpy, screen); + if(wx < 0) + wx = dw + wx - ww - 1; + if(wy < 0) + wy = dh + wy - wh - 1; + } + + dc.norm[ColBG] = getcolor(normbgcolor); + dc.norm[ColFG] = getcolor(normfgcolor); + dc.sel[ColBG] = getcolor(selbgcolor); + dc.sel[ColFG] = getcolor(selfgcolor); + dc.drawable = XCreatePixmap(dpy, root, ww, wh, + DefaultDepth(dpy, screen)); + dc.gc = XCreateGC(dpy, root, 0, 0); + + win = XCreateSimpleWindow(dpy, root, wx, wy, ww, wh, 0, + dc.norm[ColFG].pixel, dc.norm[ColBG].pixel); + XMapRaised(dpy, win); + XSelectInput(dpy, win, SubstructureNotifyMask|FocusChangeMask| + ButtonPressMask|ExposureMask|KeyPressMask|PropertyChangeMask| + StructureNotifyMask|SubstructureRedirectMask); + xerrorxlib = XSetErrorHandler(xerror); + + class_hint.res_name = wmname; + class_hint.res_class = "tabbed"; + XSetClassHint(dpy, win, &class_hint); + + size_hint = XAllocSizeHints(); + if(!isfixed) { + size_hint->flags = PSize; + size_hint->height = wh; + size_hint->width = ww; + } else { + size_hint->flags = PMaxSize | PMinSize; + size_hint->min_width = size_hint->max_width = ww; + size_hint->min_height = size_hint->max_height = wh; + } + XSetWMProperties(dpy, win, NULL, NULL, NULL, 0, size_hint, NULL, NULL); + XFree(size_hint); + + XSetWMProtocols(dpy, win, &wmatom[WMDelete], 1); + + snprintf(winid, sizeof(winid), "%lu", win); + setenv("XEMBED", winid, 1); + + nextfocus = foreground; + focus(-1); +} + +void +sigchld(int unused) { + if(signal(SIGCHLD, sigchld) == SIG_ERR) + die("tabbed: cannot install SIGCHLD handler"); + + while(0 < waitpid(-1, NULL, WNOHANG)); +} + +void +spawn(const Arg *arg) { + if(fork() == 0) { + if(dpy) + close(ConnectionNumber(dpy)); + + setsid(); + if(arg && arg->v) { + execvp(((char **)arg->v)[0], (char **)arg->v); + fprintf(stderr, "tabbed: execvp %s", + ((char **)arg->v)[0]); + } else { + cmd[cmd_append_pos] = NULL; + execvp(cmd[0], cmd); + fprintf(stderr, "tabbed: execvp %s", cmd[0]); + } + perror(" failed"); + exit(0); + } +} + +int +textnw(const char *text, unsigned int len) { + XGlyphInfo ext; + XftTextExtentsUtf8(dpy, dc.font.xfont, (XftChar8 *) text, len, &ext); + return ext.xOff; +} + +void +unmanage(int c) { + if(c < 0 || c >= nclients) { + drawbar(); + XSync(dpy, False); + return; + } + + if(!nclients) { + return; + } else if(c == 0) { + /* First client. */ + nclients--; + free(clients[0]); + memmove(&clients[0], &clients[1], sizeof(Client *) * nclients); + } else if(c == nclients - 1) { + /* Last client. */ + nclients--; + free(clients[c]); + clients = erealloc(clients, sizeof(Client *) * nclients); + } else { + /* Somewhere inbetween. */ + free(clients[c]); + memmove(&clients[c], &clients[c+1], + sizeof(Client *) * (nclients - (c + 1))); + nclients--; + } + + if(nclients <= 0) { + sel = -1; + lastsel = -1; + + if (closelastclient) { + running = False; + } else if (fillagain && running) { + spawn(NULL); + } + } else { + if(c == lastsel) { + lastsel = -1; + } else if(lastsel > c) { + lastsel--; + } + lastsel = MIN(lastsel, nclients - 1); + + if(c == sel) { + /* Note that focus() will never set lastsel == sel, + * so if here lastsel == sel, it was decreased by above if() clause + * and was actually (sel + 1) before. + */ + if(lastsel > 0) { + focus(lastsel); + } else { + focus(0); + lastsel = 1; + } + } else { + if(sel > c) + sel -= 1; + if(sel >= nclients) + sel = nclients - 1; + + focus(sel); + } + } + + drawbar(); + XSync(dpy, False); +} + +void +updatenumlockmask(void) { + unsigned int i, j; + XModifierKeymap *modmap; + + numlockmask = 0; + modmap = XGetModifierMapping(dpy); + for(i = 0; i < 8; i++) { + for(j = 0; j < modmap->max_keypermod; j++) { + if(modmap->modifiermap[i * modmap->max_keypermod + j] + == XKeysymToKeycode(dpy, + XK_Num_Lock)) { + numlockmask = (1 << i); + } + } + } + XFreeModifiermap(modmap); +} + +void +updatetitle(int c) { + if(!gettextprop(clients[c]->win, wmatom[WMName], + clients[c]->name, sizeof(clients[c]->name))) { + gettextprop(clients[c]->win, XA_WM_NAME, + clients[c]->name, sizeof(clients[c]->name)); + } + if(sel == c) + xsettitle(win, clients[c]->name); + drawbar(); +} + +/* There's no way to check accesses to destroyed windows, thus those cases are + * ignored (especially on UnmapNotify's). Other types of errors call Xlibs + * default error handler, which may call exit. */ +int +xerror(Display *dpy, XErrorEvent *ee) { + if(ee->error_code == BadWindow + || (ee->request_code == X_SetInputFocus + && ee->error_code == BadMatch) + || (ee->request_code == X_PolyText8 + && ee->error_code == BadDrawable) + || (ee->request_code == X_PolyFillRectangle + && ee->error_code == BadDrawable) + || (ee->request_code == X_PolySegment + && ee->error_code == BadDrawable) + || (ee->request_code == X_ConfigureWindow + && ee->error_code == BadMatch) + || (ee->request_code == X_GrabButton + && ee->error_code == BadAccess) + || (ee->request_code == X_GrabKey + && ee->error_code == BadAccess) + || (ee->request_code == X_CopyArea + && ee->error_code == BadDrawable)) { + return 0; + } + + fprintf(stderr, "tabbed: fatal error: request code=%d, error code=%d\n", + ee->request_code, ee->error_code); + return xerrorxlib(dpy, ee); /* may call exit */ +} + +void +xsettitle(Window w, const char *str) { + XTextProperty xtp; + + if(XmbTextListToTextProperty(dpy, (char **)&str, 1, XCompoundTextStyle, + &xtp) == Success) { + XSetTextProperty(dpy, w, &xtp, wmatom[WMName]); + XSetTextProperty(dpy, w, &xtp, XA_WM_NAME); + XFree(xtp.value); + } +} + +char *argv0; + +void +usage(void) { + die("usage: %s [-dfhsv] [-g geometry] [-n name] [-p [s+/-]pos] [-r narg] " + "[-u color] [-U color] [-t color] [-T color] command...\n", argv0); +} + +int +main(int argc, char *argv[]) { + Bool detach = False; + int replace = 0; + char *pstr; + + ARGBEGIN { + case 'c': + closelastclient = True; + fillagain = False; + break; + case 'd': + detach = True; + break; + case 'f': + fillagain = True; + break; + case 'g': + geometry = EARGF(usage()); + break; + case 'n': + wmname = EARGF(usage()); + break; + case 'p': + pstr = EARGF(usage()); + if(pstr[0] == 's') { + npisrelative = True; + newposition = atoi(&pstr[1]); + } else { + newposition = atoi(pstr); + } + break; + case 'r': + replace = atoi(EARGF(usage())); + break; + case 's': + doinitspawn = False; + break; + case 'v': + die("tabbed-"VERSION", © 2009-2012" + " tabbed engineers, see LICENSE" + " for details.\n"); + break; + case 't': + selbgcolor = EARGF(usage()); + break; + case 'T': + selfgcolor = EARGF(usage()); + break; + case 'u': + normbgcolor = EARGF(usage()); + break; + case 'U': + normfgcolor = EARGF(usage()); + break; + default: + case 'h': + usage(); + } ARGEND; + + if(argc < 1) { + doinitspawn = False; + fillagain = False; + } + + setcmd(argc, argv, replace); + + if(!setlocale(LC_CTYPE, "") || !XSupportsLocale()) + fprintf(stderr, "tabbed: no locale support\n"); + if(!(dpy = XOpenDisplay(NULL))) + die("tabbed: cannot open display\n"); + + setup(); + printf("0x%lx\n", win); + fflush(NULL); + + if(detach) { + if(fork() == 0) { + fclose(stdout); + } else { + if(dpy) + close(ConnectionNumber(dpy)); + return EXIT_SUCCESS; + } + } + + run(); + cleanup(); + XCloseDisplay(dpy); + + return EXIT_SUCCESS; +} + |