tabbed

Simple tabbing application for X11.
git clone git://r-36.net/tabbed
Log | Files | Refs | README | LICENSE

commit 21ae332817f235ba45e28fe6389abf39308ed93b
parent 9bd43db63a9bf890cb6ed170796c0dc7002aae8e
Author: Enno Boland (tox) <tox@s01.de>
Date:   Tue,  8 Sep 2009 11:30:08 +0200

tabbed works.
Diffstat:
config.def.h | 12+++++++++---
tabbed.c | 263++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
2 files changed, 258 insertions(+), 17 deletions(-)

diff --git a/config.def.h b/config.def.h @@ -3,13 +3,14 @@ static const char normbgcolor[] = "#202020"; static const char normfgcolor[] = "#c0c0c0"; static const char selbgcolor[] = "#884400"; static const char selfgcolor[] = "#f0f0f0"; +static const int tabwidth = 200; -#define SURF "surf", "-x" +#define EXEC "surf", "-x" #define MODKEY ControlMask Key keys[] = { \ /* modifier key function argument */ - { MODKEY|ShiftMask, XK_Return, spawntab, { .v = (char*[]){ SURF, NULL} } }, - { MODKEY|ShiftMask, XK_t, spawntab, { .v = (char*[]){ SURF, NULL} } }, + { MODKEY|ShiftMask, XK_Return, spawntab, { .v = (char*[]){ EXEC, NULL} } }, + { MODKEY|ShiftMask, XK_t, spawntab, { .v = (char*[]){ EXEC, NULL} } }, { MODKEY|ShiftMask, XK_l, rotate, { .i = +1 } }, { MODKEY|ShiftMask, XK_h, rotate, { .i = -1 } }, { MODKEY, XK_1, move, { .i = 1 } }, @@ -24,3 +25,8 @@ Key keys[] = { \ { MODKEY, XK_0, move, { .i = 10 } }, { MODKEY, XK_q, killclient, { 0 } }, }; + +Autostart autostarts[] = { \ + /* function argument */ + { spawntab, { .v = (char*[]){ EXEC, NULL} } }, +}; diff --git a/tabbed.c b/tabbed.c @@ -12,13 +12,17 @@ #include <stdio.h> #include <string.h> #include <stdlib.h> +#include <X11/cursorfont.h> #include <X11/keysym.h> #include <X11/Xatom.h> #include <X11/Xlib.h> +#include <X11/Xproto.h> +#include <X11/Xutil.h> #include <errno.h> /* macros */ #define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) #define LENGTH(x) (sizeof x / sizeof x[0]) #define CLEANMASK(mask) (mask & ~(numlockmask|LockMask)) #define TEXTW(x) (textnw(x, strlen(x)) + dc.font.height) @@ -40,6 +44,11 @@ typedef struct { } Key; typedef struct { + void (*func)(const Arg *); + const Arg arg; +} Autostart; + +typedef struct { int x, y, w, h; unsigned long norm[ColLast]; unsigned long sel[ColLast]; @@ -66,30 +75,39 @@ typedef struct Listener { } Listener; /* function declarations */ +static void autostart(void); static void cleanup(void); static void configurenotify(XEvent *e); -static void unmapnotify(XEvent *e); static void die(const char *errstr, ...); +static void drawbar(); +static void drawtext(const char *text, unsigned long col[ColLast]); static void expose(XEvent *e); static unsigned long getcolor(const char *colstr); +static Client *getclient(Window w); +static Client *getfirsttab(); +static Bool gettextprop(Window w, Atom atom, char *text, unsigned int size); static void initfont(const char *fontstr); static void keypress(XEvent *e); static void killclient(const Arg *arg); static void move(const Arg *arg); static void spawntab(const Arg *arg); -static void reparent(Window win); +static void manage(Window win); +static void propertynotify(XEvent *e); +static void resize(Client *c, int w, int h); static void rotate(const Arg *arg); static void run(void); static void setup(void); static int textnw(const char *text, unsigned int len); +static void unmapnotify(XEvent *e); static void updatenumlockmask(void); +static void updatetitle(Client *c); static int xerror(Display *dpy, XErrorEvent *ee); /* variables */ static int screen; -static int wx, wy, ww, wh; static void (*handler[LASTEvent]) (XEvent *) = { [ConfigureNotify] = configurenotify, + [PropertyNotify] = propertynotify, [UnmapNotify] = unmapnotify, [KeyPress] = keypress, [Expose] = expose, @@ -99,12 +117,22 @@ static DC dc; static Window root, win; static Bool running = True; static unsigned int numlockmask = 0; -Client *clients, *sel; -Listener *listeners; +static unsigned bh, wx, wy, ww, wh; +static Client *clients, *sel; +static Listener *listeners; +static Bool badwindow = False; /* configuration, allows nested code to access above variables */ #include "config.h" void +autostart() { + int i; + + for(i = 0; i < LENGTH(autostarts); i++) + autostarts[i].func(&(autostarts[i].arg)); +} + +void cleanup(void) { if(dc.font.set) XFreeFontSet(dpy, dc.font.set); @@ -114,18 +142,21 @@ cleanup(void) { XFreeGC(dpy, dc.gc); XDestroyWindow(dpy, win); XSync(dpy, False); - XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime); } void configurenotify(XEvent *e) { XConfigureEvent *ev = &e->xconfigure; + Client *c; 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)); + for(c = clients; c; c = c->next) + resize(c, ww, wh - bh); + XSync(dpy, False); } } @@ -139,6 +170,58 @@ die(const char *errstr, ...) { exit(EXIT_FAILURE); } +void +drawbar() { + unsigned long *col; + unsigned int n; + Client *c, *fc; + + dc.x = 0; + drawtext("", dc.norm); + for(fc = c = getfirsttab(); c; c = c->next, n++); + for(c = fc; c && dc.x < ww; c = c->next) { + dc.w = tabwidth; + if(c == sel) { + col = dc.sel; + } + else { + col = dc.norm; + } + drawtext(c->name, col); + dc.x += dc.w; + } + XCopyArea(dpy, dc.drawable, win, dc.gc, 0, 0, ww, bh, 0, 0); + XSync(dpy, False); +} + +void +drawtext(const char *text, unsigned long col[ColLast]) { + char buf[256]; + int i, x, y, h, len, olen; + XRectangle r = { dc.x, dc.y, dc.w, dc.h }; + + XSetForeground(dpy, dc.gc, col[ColBG]); + 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] = '.'); + 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); +} + void * emallocz(size_t size) { void *p; @@ -150,7 +233,21 @@ emallocz(size_t size) { void expose(XEvent *e) { - /*XExposeEvent *ev = &e->xexpose;*/ + XExposeEvent *ev = &e->xexpose; + + if(ev->count == 0 && win == ev->window) + drawbar(); +} + +void +focus(Client *c) { + if(!c || !clients) + return; + XRaiseWindow(dpy, c->win); + XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime); + XSelectInput(dpy, c->win, PropertyChangeMask); + sel = c; + drawbar(); } unsigned long @@ -163,6 +260,57 @@ getcolor(const char *colstr) { return color.pixel; } +Client * +getclient(Window w) { + Client *c; + + for(c = clients; c; c = c->next) + if(c->win == w) + return c; + return NULL; +} + +Client * +getfirsttab() { + unsigned int n, seli; + Client *c, *fc; + + return clients; + c = fc = clients; + for(n = 0; c; c = c->next, n++); + if(n * tabwidth > ww) { + for(seli = 0, c = clients; c && c != sel; c = c->next, seli++); + for(; seli * tabwidth > ww / 2 && n * tabwidth > ww; + fc = fc->next, seli--, n--); + } + 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) { char *def, **missing; @@ -225,7 +373,13 @@ killclient(const Arg *arg) { void move(const Arg *arg) { - puts("move to nth tab"); + int i; + Client *c; + + for(i = 0, c = clients; c; c = c->next, i++) { + if(arg->i == i) + focus(c); + } } void @@ -262,14 +416,84 @@ spawntab(const Arg *arg) { } void -reparent(Window w) { +manage(Window w) { + updatenumlockmask(); + { + int i, j; + unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask }; + KeyCode code; + Client *c; + + XSync(dpy, False); + XReparentWindow(dpy, w, win, 0, bh); + if(badwindow) { + badwindow = False; + return; + } + 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(Client)); + c->next = clients; + c->win = w; + clients = c; + focus(c); + updatetitle(c); + resize(c, ww, wh - bh); + drawbar(); + } +} + +void +propertynotify(XEvent *e) { + Client *c; + XPropertyEvent *ev = &e->xproperty; + + if(ev->state != PropertyDelete && ev->atom == XA_WM_NAME + && (c = getclient(ev->window))) { + updatetitle(c); + } +} + +void +resize(Client *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 = c->win; + ce.window = c->win; + ce.above = None; + ce.override_redirect = False; + ce.border_width = 0; + XConfigureWindow(dpy, c->win, CWWidth|CWHeight, &wc); + XSendEvent(dpy, c->win, False, StructureNotifyMask, (XEvent *)&ce); XSync(dpy, False); - XReparentWindow(dpy, w, win, 0, 0); } void rotate(const Arg *arg) { - puts("next/prev tab"); + Client *c; + + if(arg->i > 0) { + if(sel && sel->next) + focus(sel->next); + else + focus(clients); + } + else { + for(c = clients; c && c->next && c->next != sel; c = c->next); + if(c) + focus(c); + } } void @@ -281,7 +505,7 @@ run(void) { XEvent ev; Listener *l, *pl; - /* main event loop, also reads status text from stdin */ + /* main event loop, also reads xids from stdin */ XSync(dpy, False); xfd = ConnectionNumber(dpy); buf[LENGTH(buf) - 1] = '\0'; /* 0-terminator is never touched */ @@ -318,7 +542,7 @@ run(void) { if(*p == '\n' || *p == '\0') { *p = '\0'; if((wid = atoi(buf))) - reparent((Window)wid); + manage((Window)wid); p += r - 1; /* p is buf + offset + r - 1 */ for(r = 0; *(p - r) && *(p - r) != '\n'; r++); offset = r; @@ -343,6 +567,7 @@ setup(void) { screen = DefaultScreen(dpy); root = RootWindow(dpy, screen); initfont(font); + bh = dc.h = dc.font.height + 2; /* init appearance */ wx = 0; @@ -397,13 +622,22 @@ updatenumlockmask(void) { XFreeModifiermap(modmap); } +void +updatetitle(Client *c) { + gettextprop(c->win, XA_WM_NAME, c->name, sizeof 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) + if(ee->error_code == BadWindow) { + badwindow = True; + puts("badwindow"); return 0; + } die("dwm: fatal error: request code=%d, error code=%d\n", ee->request_code, ee->error_code); return 1; @@ -420,6 +654,7 @@ main(int argc, char *argv[]) { if(!(dpy = XOpenDisplay(0))) die("tabbed: cannot open display\n"); setup(); + autostart(); run(); /*dummys*/ cleanup();