tabbed-0.5/000077500000000000000000000000001214177311100125755ustar00rootroot00000000000000tabbed-0.5/LICENSE000066400000000000000000000022241214177311100136020ustar00rootroot00000000000000MIT/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. tabbed-0.5/Makefile000066400000000000000000000027721214177311100142450ustar00rootroot00000000000000# 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 tabbed-0.5/README000066400000000000000000000007741214177311100134650ustar00rootroot00000000000000tabbed - 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. tabbed-0.5/TODO000066400000000000000000000001121214177311100132570ustar00rootroot00000000000000# TODO * add some way to detach windows * add some way to attach windows tabbed-0.5/arg.h000066400000000000000000000021031214177311100135130ustar00rootroot00000000000000/* * Copy me if you can. * by 20h */ #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 tabbed-0.5/config.def.h000066400000000000000000000043511214177311100147530ustar00rootroot00000000000000/* See LICENSE file for copyright and license details. */ /* appearance */ static const char font[] = "-*-*-medium-*-*-*-14-*-*-*-*-*-*-*"; 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 MODKEY ControlMask static Key keys[] = { \ /* modifier key function argument */ { MODKEY|ShiftMask, XK_Return, focusonce, { 0 } }, { MODKEY|ShiftMask, XK_Return, spawn, { 0 } }, { 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 } }, }; tabbed-0.5/config.mk000066400000000000000000000007111214177311100143720ustar00rootroot00000000000000# tabbed version VERSION = 0.5 # Customize below to fit your system # paths PREFIX = /usr/local MANPREFIX = ${PREFIX}/share/man # includes and libs INCS = -I. -I/usr/include LIBS = -L/usr/lib -lc -lX11 # flags CPPFLAGS = -DVERSION=\"${VERSION}\" -D_BSD_SOURCE CFLAGS = -g -std=c99 -pedantic -Wall -O0 ${INCS} ${CPPFLAGS} LDFLAGS = -g ${LIBS} # Solaris #CFLAGS = -fast ${INCS} -DVERSION=\"${VERSION}\" #LDFLAGS = ${LIBS} # compiler and linker CC = cc tabbed-0.5/tabbed.1000066400000000000000000000050001214177311100140730ustar00rootroot00000000000000.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 [ \-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 \-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 .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\-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 "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, 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; unsigned long norm[ColLast]; unsigned long sel[ColLast]; Drawable drawable; GC gc; struct { int ascent; int descent; int height; XFontSet set; XFontStruct *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, unsigned long 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 int getclient(Window w); static unsigned long 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 char winid[64]; static char **cmd = NULL; static char *wmname = "tabbed"; 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; Arg arg; if(getfirsttab() != 0 && ev->x < TEXTW(before)) return; for(i = 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; 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); 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) { unsigned long *col; int c, fc, width, n = 0; char *name = NULL; 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; } drawtext(clients[c]->name, 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, unsigned long col[ColLast]) { int i, x, y, h, len, olen; char buf[256]; 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; 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) { 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); } int getclient(Window w) { int i; for(i = 0; i < nclients; i++) { if(clients[i]->win == w) return i; } return -1; } unsigned long getcolor(const char *colstr) { Colormap cmap = DefaultColormap(dpy, screen); XColor color; if(!XAllocNamedColor(dpy, cmap, colstr, &color, &color)) die("tabbed: cannot allocate color '%s'\n", colstr); return color.pixel; } 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) { 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.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.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; 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+2) * sizeof(*cmd)); for(i = 0; i < argc; i++) cmd[i] = argv[i]; cmd[(replace > 0)? replace : argc] = winid; cmd[argc + !(replace > 0)] = NULL; } void setup(void) { /* 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); /* init appearance */ wx = 0; wy = 0; ww = 800; wh = 600; 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); 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]); XMapRaised(dpy, win); XSelectInput(dpy, win, SubstructureNotifyMask|FocusChangeMask| ButtonPressMask|ExposureMask|KeyPressMask| StructureNotifyMask|SubstructureRedirectMask); xerrorxlib = XSetErrorHandler(xerror); XClassHint class_hint; class_hint.res_name = wmname; class_hint.res_class = "tabbed"; XSetClassHint(dpy, win, &class_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 { execvp(cmd[0], cmd); fprintf(stderr, "tabbed: execvp %s", cmd[0]); } perror(" failed"); exit(0); } } 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); } 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--; } if(c == sel) { if(lastsel > 0 && lastsel != sel) { focus(lastsel); } else { focus(0); } } 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] [-n name] [-p [s+/-]pos] [-r narg]" " command...\n", argv0); } int main(int argc, char *argv[]) { Bool detach = False; int replace = 0; char *pstr; ARGBEGIN { case 'c': closelastclient = True; fillagain = False; case 'd': detach = True; break; case 'f': fillagain = True; 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"); 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; }