tabbed-0.8004075500017500001750000000000001457627264500121555ustar00hiltjohiltjotabbed-0.8/Makefile010064400017500001750000000035071457627264500136760ustar00hiltjohiltjo.POSIX: NAME = tabbed VERSION = 0.8 # paths PREFIX = /usr/local MANPREFIX = ${PREFIX}/share/man DOCPREFIX = ${PREFIX}/share/doc/${NAME} # use system flags. TABBED_CFLAGS = -I/usr/X11R6/include -I/usr/include/freetype2 ${CFLAGS} TABBED_LDFLAGS = -L/usr/X11R6/lib -lX11 -lfontconfig -lXft ${LDFLAGS} TABBED_CPPFLAGS = -DVERSION=\"${VERSION}\" -D_DEFAULT_SOURCE -D_XOPEN_SOURCE=700L # OpenBSD (uncomment) #TABBED_CFLAGS = -I/usr/X11R6/include -I/usr/X11R6/include/freetype2 ${CFLAGS} SRC = tabbed.c xembed.c OBJ = ${SRC:.c=.o} BIN = ${OBJ:.o=} MAN1 = ${BIN:=.1} HDR = arg.h config.def.h DOC = LICENSE README all: ${BIN} .c.o: ${CC} -o $@ -c $< ${TABBED_CFLAGS} ${TABBED_CPPFLAGS} ${OBJ}: config.h config.h: cp config.def.h $@ .o: ${CC} -o $@ $< ${TABBED_LDFLAGS} clean: rm -f ${BIN} ${OBJ} "${NAME}-${VERSION}.tar.gz" dist: clean mkdir -p "${NAME}-${VERSION}" cp -fR Makefile ${MAN1} ${DOC} ${HDR} ${SRC} "${NAME}-${VERSION}" tar -cf - "${NAME}-${VERSION}" | gzip -c > "${NAME}-${VERSION}.tar.gz" rm -rf ${NAME}-${VERSION} install: all # installing executable files. mkdir -p "${DESTDIR}${PREFIX}/bin" cp -f ${BIN} "${DESTDIR}${PREFIX}/bin" for f in ${BIN}; do chmod 755 "${DESTDIR}${PREFIX}/bin/$$f"; done # installing doc files. mkdir -p "${DESTDIR}${DOCPREFIX}" cp -f README "${DESTDIR}${DOCPREFIX}" # installing manual pages for general commands: section 1. mkdir -p "${DESTDIR}${MANPREFIX}/man1" for m in ${MAN1}; do sed "s/VERSION/${VERSION}/g" < $$m > "${DESTDIR}${MANPREFIX}/man1/$$m"; done uninstall: # removing executable files. for f in ${BIN}; do rm -f "${DESTDIR}${PREFIX}/bin/$$f"; done # removing doc files. rm -f "${DESTDIR}${DOCPREFIX}/README" # removing manual pages. for m in ${MAN1}; do rm -f "${DESTDIR}${MANPREFIX}/man1/$$m"; done -rmdir "${DESTDIR}${DOCPREFIX}" .PHONY: all clean dist install uninstall tabbed-0.8/tabbed.1010064400017500001750000000071701457627264500135410ustar00hiltjohiltjo.TH TABBED 1 tabbed\-VERSION .SH NAME tabbed \- generic tabbed interface .SH SYNOPSIS .B tabbed .RB [ \-c ] .RB [ \-d ] .RB [ \-k ] .RB [ \-s ] .RB [ \-v ] .RB [ \-g .IR geometry ] .RB [ \-n .IR name ] .RB [ \-p .RB [ s {+/-} ] \fIpos\fR ] .RB [ \-o .IR normbgcol ] .RB [ \-O .IR normfgcol ] .RB [ \-t .IR selbgcol ] .RB [ \-T .IR selfgcol ] .RB [ \-u .IR urgbgcol ] .RB [ \-U .IR urgfgcol ] .RB [ \-r .IR narg ] .RI [ "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 .BI \-g " geometry" defines the X11 geometry string, which will fixate the height and width of tabbed. The syntax is .RI [=][ width {xX} height ][{+-} xoffset {+-} yoffset ]. See .BR XParseGeometry (3) for further details. .TP .B \-k close foreground tabbed client (instead of tabbed and all clients) when WM_DELETE_WINDOW is sent. .TP .BI \-n " name" will set the WM_CLASS attribute to .I name. .TP .BR \-p " [" s {+-}] \fIpos\fR 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 \-o " normbgcol" defines the normal background color. .RI # RGB , .RI # RRGGBB , and X color names are supported. .TP .BI \-O " normfgcol" defines the normal foreground color. .TP .BI \-t " selbgcol" defines the selected background color. .TP .BI \-T " selfgbcol" defines the selected foreground color. .TP .BI \-u " urgbgcol" defines the urgent background color. .TP .BI \-U " urgfgbcol" defines the urgent 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\-Shift\-u toggle autofocus of urgent tabs .TP .B Ctrl\-Tab toggle between the selected and last selected tab .TP .B Ctrl\-` 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\-u focus next urgent 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 $( © 2011,2015 Connor Lane Smith © 2012-2015 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.8/README010064400017500001750000000007741457627264500131210ustar00hiltjohiltjotabbed - 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.8/arg.h010064400017500001750000000017621457627264500131610ustar00hiltjohiltjo/* * Copy me if you can. * by 20h */ #ifndef ARG_H__ #define ARG_H__ extern char *argv0; /* use main(int argc, char *argv[]) */ #define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ argv[0] && argv[0][0] == '-'\ && argv[0][1];\ 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 }\ } #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.8/config.def.h010064400017500001750000000054261457627264500144130ustar00hiltjohiltjo/* See LICENSE file for copyright and license details. */ /* appearance */ static const char font[] = "monospace:size=9"; static const char* normbgcolor = "#222222"; static const char* normfgcolor = "#cccccc"; static const char* selbgcolor = "#555555"; static const char* selfgcolor = "#ffffff"; static const char* urgbgcolor = "#111111"; static const char* urgfgcolor = "#cc0000"; static const char before[] = "<"; static const char after[] = ">"; static const char titletrim[] = "..."; static const int tabwidth = 200; static const Bool foreground = True; static Bool urgentswitch = False; /* * 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 -w $1`\" &&" \ "xprop -id $1 -f $0 8s -set $0 \"$prop\"", \ p, winid, NULL \ } \ } #define MODKEY ControlMask static const 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_grave, spawn, SETPROP("_TABBED_SELECT_TAB") }, { 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 } }, { MODKEY, XK_u, focusurgent, { 0 } }, { MODKEY|ShiftMask, XK_u, toggle, { .v = (void*) &urgentswitch } }, { 0, XK_F11, fullscreen, { 0 } }, }; tabbed-0.8/tabbed.c010064400017500001750000000723531457627264500136300ustar00hiltjohiltjo/* * See LICENSE file for copyright and license details. */ #include #include #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]; XftColor urg[ColLast]; Drawable drawable; GC gc; struct { int ascent; int descent; int height; XftFont *xfont; } font; } DC; /* draw context */ typedef struct { char name[256]; Window win; int tabx; Bool urgent; 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 *ecalloc(size_t n, 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 focusurgent(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 setcmd(int argc, char *argv[], int); static void setup(void); static void spawn(const Arg *arg); static int textnw(const char *text, unsigned int len); static void toggle(const Arg *arg); static void unmanage(int c); static void unmapnotify(const XEvent *e); 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, [UnmapNotify] = unmapnotify, [DestroyNotify] = destroynotify, [Expose] = expose, [FocusIn] = focusin, [KeyPress] = keypress, [MapRequest] = maprequest, [PropertyNotify] = propertynotify, }; static int bh, obh, wx, wy, ww, wh; static unsigned int numlockmask; static Bool running = True, nextfocus, doinitspawn = True, fillagain = False, closelastclient = False, killclientsfirst = False; static Display *dpy; static DC dc; static Atom wmatom[WMLast]; static Window root, win; static Client **clients; static int nclients, sel = -1, lastsel = -1; static int (*xerrorxlib)(Display *, XErrorEvent *); static int cmd_append_pos; static char winid[64]; static char **cmd; static char *wmname = "tabbed"; static const char *geometry; 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, fc; Arg arg; if (ev->y < 0 || ev->y > bh) return; if (((fc = getfirsttab()) > 0 && ev->x < TEXTW(before)) || ev->x < 0) return; for (i = fc; 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: /* FALLTHROUGH */ 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); 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]) { if (nclients > 1 && killclientsfirst) { killclient(0); return; } 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 (!obh && (wh <= bh)) { obh = bh; bh = 0; } else if (!bh && (wh > obh)) { bh = obh; obh = 0; } 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, cc, fc, width; 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; cc = ww / tabwidth; if (nclients > cc) cc = (ww - TEXTW(before) - TEXTW(after)) / tabwidth; if ((fc = getfirsttab()) + cc < nclients) { 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; } cc = MIN(cc, nclients); for (c = fc; c < fc + cc; c++) { dc.w = width / cc; if (c == sel) { col = dc.sel; dc.w += width % cc; } else { col = clients[c]->urgent ? dc.urg : 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, XftColor col[ColLast]) { int i, j, 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, j = strlen(titletrim); j && i; buf[--i] = titletrim[--j]) ; } 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 * ecalloc(size_t n, size_t size) { void *p; if (!(p = calloc(n, size))) die("%s: cannot calloc\n", argv0); return p; } void * erealloc(void *o, size_t size) { void *p; if (!(p = realloc(o, size))) die("%s: cannot realloc\n", argv0); 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; XWMHints* wmh; /* 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 != c) { lastsel = sel; sel = c; } if (clients[c]->urgent && (wmh = XGetWMHints(dpy, clients[c]->win))) { wmh->flags &= ~XUrgencyHint; XSetWMHints(dpy, clients[c]->win, wmh); clients[c]->urgent = False; XFree(wmh); } 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 focusurgent(const Arg *arg) { int c; if (sel < 0) return; for (c = (sel + 1) % nclients; c != sel; c = (c + 1) % nclients) { if (clients[c]->urgent) { focus(c); return; } } } 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("%s: cannot allocate color '%s'\n", argv0, colstr); return color; } int getfirsttab(void) { int cc, ret; if (sel < 0) return 0; cc = ww / tabwidth; if (nclients > cc) cc = (ww - TEXTW(before) - TEXTW(after)) / tabwidth; ret = sel - cc / 2 + (cc + 1) % 2; return ret < 0 ? 0 : ret + cc > nclients ? MAX(0, nclients - cc) : ret; } 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 = ecalloc(1, 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) return; c = (sel + arg->i) % nclients; if (c < 0) c += nclients; if (c == sel) return; new = clients[sel]; if (sel < c) memmove(&clients[sel], &clients[sel+1], sizeof(Client *) * (c - sel)); else memmove(&clients[c+1], &clients[c], sizeof(Client *) * (sel - c)); clients[c] = new; sel = c; drawbar(); } void propertynotify(const XEvent *e) { const XPropertyEvent *ev = &e->xproperty; XWMHints *wmh; 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 == PropertyNewValue && ev->atom == XA_WM_HINTS && (c = getclient(ev->window)) > -1 && (wmh = XGetWMHints(dpy, clients[c]->win))) { if (wmh->flags & XUrgencyHint) { XFree(wmh); wmh = XGetWMHints(dpy, win); if (c != sel) { if (urgentswitch && wmh && !(wmh->flags & XUrgencyHint)) { /* only switch, if tabbed was focused * since last urgency hint if WMHints * could not be received, * default to no switch */ focus(c); } else { /* if no switch should be performed, * mark tab as urgent */ clients[c]->urgent = True; drawbar(); } } if (wmh && !(wmh->flags & XUrgencyHint)) { /* update tabbed urgency hint * if not set already */ wmh->flags |= XUrgencyHint; XSetWMHints(dpy, win, wmh); } } XFree(wmh); } 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 = wc.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, CWY | 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 = ecalloc(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; XWMHints *wmh; XClassHint class_hint; XSizeHints *size_hint; struct sigaction sa; /* do not transform children into zombies when they terminate */ sigemptyset(&sa.sa_mask); sa.sa_flags = SA_NOCLDSTOP | SA_NOCLDWAIT | SA_RESTART; sa.sa_handler = SIG_IGN; sigaction(SIGCHLD, &sa, NULL); /* clean up any zombies that might have been inherited */ while (waitpid(-1, NULL, WNOHANG) > 0); /* init screen */ screen = DefaultScreen(dpy); root = RootWindow(dpy, screen); initfont(font); bh = dc.h = dc.font.height + 2; /* init atoms */ wmatom[WMDelete] = XInternAtom(dpy, "WM_DELETE_WINDOW", False); wmatom[WMFullscreen] = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False); wmatom[WMName] = XInternAtom(dpy, "_NET_WM_NAME", False); wmatom[WMProtocols] = XInternAtom(dpy, "WM_PROTOCOLS", False); wmatom[WMSelectTab] = XInternAtom(dpy, "_TABBED_SELECT_TAB", False); wmatom[WMState] = XInternAtom(dpy, "_NET_WM_STATE", False); wmatom[XEmbed] = XInternAtom(dpy, "_XEMBED", 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.urg[ColBG] = getcolor(urgbgcolor); dc.urg[ColFG] = getcolor(urgfgcolor); 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 | PMinSize; size_hint->height = wh; size_hint->width = ww; size_hint->min_height = bh + 1; } else { size_hint->flags = PMaxSize | PMinSize; size_hint->min_width = size_hint->max_width = ww; size_hint->min_height = size_hint->max_height = wh; } wmh = XAllocWMHints(); XSetWMProperties(dpy, win, NULL, NULL, NULL, 0, size_hint, wmh, NULL); XFree(size_hint); XFree(wmh); XSetWMProtocols(dpy, win, &wmatom[WMDelete], 1); snprintf(winid, sizeof(winid), "%lu", win); setenv("XEMBED", winid, 1); nextfocus = foreground; focus(-1); } void spawn(const Arg *arg) { struct sigaction sa; if (fork() == 0) { if(dpy) close(ConnectionNumber(dpy)); setsid(); sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sa.sa_handler = SIG_DFL; sigaction(SIGCHLD, &sa, NULL); if (arg && arg->v) { execvp(((char **)arg->v)[0], (char **)arg->v); fprintf(stderr, "%s: execvp %s", argv0, ((char **)arg->v)[0]); } else { cmd[cmd_append_pos] = NULL; execvp(cmd[0], cmd); fprintf(stderr, "%s: execvp %s", argv0, 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 toggle(const Arg *arg) { *(Bool*) arg->v = !*(Bool*) arg->v; } void unmanage(int c) { if (c < 0 || c >= nclients) { drawbar(); XSync(dpy, False); return; } if (!nclients) return; 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) { lastsel = sel = -1; if (closelastclient) running = False; else if (fillagain && running) spawn(NULL); } else { if (lastsel >= nclients) lastsel = nclients - 1; else if (lastsel > c) lastsel--; if (c == sel && lastsel >= 0) { focus(lastsel); } else { if (sel > c) sel--; if (sel >= nclients) sel = nclients - 1; focus(sel); } } drawbar(); XSync(dpy, False); } void unmapnotify(const XEvent *e) { const XUnmapEvent *ev = &e->xunmap; int c; if ((c = getclient(ev->window)) > -1) unmanage(c); } 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, "%s: fatal error: request code=%d, error code=%d\n", argv0, 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); } } void usage(void) { die("usage: %s [-dfksv] [-g geometry] [-n name] [-p [s+/-]pos]\n" " [-r narg] [-o color] [-O color] [-t color] [-T color]\n" " [-u color] [-U 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 'k': killclientsfirst = True; break; case 'n': wmname = EARGF(usage()); break; case 'O': normfgcolor = EARGF(usage()); break; case 'o': normbgcolor = 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 'T': selfgcolor = EARGF(usage()); break; case 't': selbgcolor = EARGF(usage()); break; case 'U': urgfgcolor = EARGF(usage()); break; case 'u': urgbgcolor = EARGF(usage()); break; case 'v': die("tabbed-"VERSION", © 2009-2016 tabbed engineers, " "see LICENSE for details.\n"); break; default: usage(); break; } ARGEND; if (argc < 1) { doinitspawn = False; fillagain = False; } setcmd(argc, argv, replace); if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) fprintf(stderr, "%s: no locale support\n", argv0); if (!(dpy = XOpenDisplay(NULL))) die("%s: cannot open display\n", argv0); 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; } tabbed-0.8/xembed.c010064400017500001750000000012661457627264500136460ustar00hiltjohiltjo/* * See LICENSE file for copyright and license details. */ #include #include #include #include int main(int argc, char *argv[]) { char *xembed; int tty; pid_t pgrp, tcpgrp; if (argc < 3) { fprintf(stderr, "usage: %s flag cmd ...\n", argv[0]); return 2; } if (!(xembed = getenv("XEMBED"))) goto noembed; if ((tty = open("/dev/tty", O_RDONLY)) < 0) goto noembed; pgrp = getpgrp(); tcpgrp = tcgetpgrp(tty); close(tty); if (pgrp == tcpgrp) { /* in foreground of tty */ argv[0] = argv[2]; argv[2] = xembed; } else { noembed: argv += 2; } execvp(argv[0], argv); perror(argv[0]); /* failed to execute */ return 1; }