ttabbed works. - ttabbed - Simple tabbing application for X11.
       
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 21ae332817f235ba45e28fe6389abf39308ed93b
 (DIR) parent 9bd43db63a9bf890cb6ed170796c0dc7002aae8e
 (HTM) Author: Enno Boland (tox) <tox@s01.de>
       Date:   Tue,  8 Sep 2009 11:30:08 +0200
       
       ttabbed works.
       Diffstat:
         config.def.h                        |      12 +++++++++---
         tabbed.c                            |     263 +++++++++++++++++++++++++++++--
       
       2 files changed, 258 insertions(+), 17 deletions(-)
       ---
 (DIR) 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} } },
       +};
 (DIR) 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;
        
        ttypedef struct {
       +        void (*func)(const Arg *);
       +        const Arg arg;
       +} Autostart;
       +
       +ttypedef 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();