From 2c55c043b94adcf9810ee9e7b7d46aa4472b3aa9 Mon Sep 17 00:00:00 2001 From: Andrew Guschin Date: Sun, 28 Mar 2021 16:34:54 +0400 Subject: Initial commit --- .gitignore | 4 + LICENSE | 23 + Makefile | 60 ++ README | 22 + arg.h | 52 ++ config.def.h | 58 ++ config.h | 58 ++ config.h.old | 59 ++ config.mk | 25 + patches/tabbed-0.6-xft.diff | 233 +++++++ patches/tabbed-clientnumber-0.6.diff | 23 + tabbed.1 | 144 ++++ tabbed.c | 1254 ++++++++++++++++++++++++++++++++++ 13 files changed, 2015 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README create mode 100644 arg.h create mode 100644 config.def.h create mode 100644 config.h create mode 100644 config.h.old create mode 100644 config.mk create mode 100644 patches/tabbed-0.6-xft.diff create mode 100644 patches/tabbed-clientnumber-0.6.diff create mode 100644 tabbed.1 create mode 100644 tabbed.c 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 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..add8a53 --- /dev/null +++ b/LICENSE @@ -0,0 +1,23 @@ +MIT/X Consortium License + +© 2009-2011 Enno Boland +© 2011 Connor Lane Smith +© 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 diff --git a/README b/README new file mode 100644 index 0000000..4ed6bbe --- /dev/null +++ b/README @@ -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. + diff --git a/arg.h b/arg.h new file mode 100644 index 0000000..a345b0b --- /dev/null +++ b/arg.h @@ -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 + #include + #include ++#include + + #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 [=][{xX}][{+-}{+-}]. 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 $( +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} + -- cgit v1.2.3