tabbed-noxz

[fork] suckless tabbed - personal fork
git clone https://noxz.tech/git/tabbed-noxz.git
tabbed-noxz

commit: 11393ac78cc2da68a148c23c61d26fe480e40fa5
parent: 
author: Chris Noxz <chris@noxz.tech>
date:   Sun, 9 Jun 2024 16:46:06 +0200
initial
ALICENSE23+
AMakefile69+
AREADME22+
ATODO4+
Aarg.h48+
Aconfig.def.h87++
Atabbed.1171+++
Atabbed.c1508++++++++++++++++++++
Axembed.135+
Axembed.c45+
10 files changed, 2012 insertions(+)
diff --git a/LICENSE b/LICENSE
@@ -0,0 +1,23 @@
+MIT/X Consortium License
+
+© 2009-2011 Enno Boland <g s01 de>
+© 2011,2015 Connor Lane Smith <cls@lubutu.com>
+© 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.
diff --git a/Makefile b/Makefile
@@ -0,0 +1,69 @@
+.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
diff --git a/README b/README
@@ -0,0 +1,22 @@
+tabbed - generic tabbed interface
+=================================
+tabbed is a simple tabbed X window container.
+
+Requirements
+------------
+In order to build tabbed you need the Xlib header files.
+
+Installation
+------------
+Edit config.mk to match your local setup (tabbed is installed into
+the /usr/local namespace by default).
+
+Afterwards enter the following command to build and install tabbed
+(if necessary as root):
+
+    make clean install
+
+Running tabbed
+--------------
+See the man page for details.
+
diff --git a/TODO b/TODO
@@ -0,0 +1,4 @@
+# TODO
+* add some way to detach windows
+* add some way to attach windows
+
diff --git a/arg.h b/arg.h
@@ -0,0 +1,48 @@
+/*
+ * 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
diff --git a/config.def.h b/config.def.h
@@ -0,0 +1,87 @@
+/* See LICENSE file for copyright and license details. */
+
+/* appearance */
+static char* font         = "monospace:size=9";
+static char* normbgcolor  = "#222222";
+static char* normfgcolor  = "#cccccc";
+static char* selbgcolor   = "#555555";
+static char* selfgcolor   = "#ffffff";
+static char* urgbgcolor   = "#111111";
+static char* urgfgcolor   = "#cc0000";
+static char* before       = "<";
+static char* after        = ">";
+static char* titletrim    = "...";
+static int tabwidth       = 200;
+static int focusnew       = 1;
+static int urgentswitch   = 0;
+
+/*
+ * 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   = -1;
+static int npisrelative  = 0;
+
+#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 \
+        } \
+}
+
+/*
+ * Xresources preferences to load at startup
+ */
+ResourcePref resources[] = {
+	{ "font",                   STRING,  &font},
+	{ "normalBackground",       STRING,  &normbgcolor},
+	{ "normalForeground",       STRING,  &normfgcolor},
+	{ "selectedBackground",     STRING,  &selbgcolor},
+	{ "selectedForeground",     STRING,  &selfgcolor},
+	{ "urgentBackground",       STRING,  &urgbgcolor},
+	{ "urgentForeground",       STRING,  &urgfgcolor},
+	{ "before",                 STRING,  &before},
+	{ "after",                  STRING,  &after},
+	{ "titletrim",              STRING,  &titletrim},
+	{ "tabwidth",               INTEGER, &tabwidth},
+	{ "focusnew",               INTEGER, &focusnew},
+	{ "urgentswitch",           INTEGER, &urgentswitch},
+	{ "newposition",            INTEGER, &newposition},
+	{ "npisrelative",           INTEGER, &npisrelative},
+};
+
+#define MODKEY Mod1Mask
+static const Key keys[] = {
+	/* modifier             key        function     argument */
+	{ MODKEY,               XK_t,      focusonce,   { 0 } },
+	{ MODKEY,               XK_t,      spawn,       { 0 } },
+
+	{ MODKEY,               XK_j,      rotate,      { .i = -1 } },
+	{ MODKEY,               XK_k,      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 } },
+};
diff --git a/tabbed.1 b/tabbed.1
@@ -0,0 +1,171 @@
+.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 $(</tmp/tabbed.xid);
+.TP
+$ tabbed -r 2 st -w '' -e tmux
+.SH CUSTOMIZATION
+.B tabbed
+can be customized by creating a custom config.h and (re)compiling the source
+code. This keeps it fast, secure and simple.
+.SH AUTHORS
+See the LICENSE file for the authors.
+.SH LICENSE
+See the LICENSE file for the terms of redistribution.
+.SH SEE ALSO
+.BR st (1),
+.BR xembed (1)
+.SH BUGS
+Please report them.
diff --git a/tabbed.c b/tabbed.c
@@ -0,0 +1,1508 @@
+/*
+ * See LICENSE file for copyright and license details.
+ */
+
+#include <sys/wait.h>
+#include <locale.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <linux/limits.h>
+#include <X11/Xatom.h>
+#include <X11/keysym.h>
+#include <X11/Xlib.h>
+#include <X11/Xproto.h>
+#include <X11/Xresource.h>
+#include <X11/Xutil.h>
+#include <X11/XKBlib.h>
+#include <X11/Xft/Xft.h>
+
+#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;
+	pid_t pid;
+} Client;
+ 
+/* Xresources preferences */
+enum resource_type {
+	STRING = 0,
+	INTEGER = 1,
+	FLOAT = 2
+};
+
+typedef struct {
+	char *name;
+	enum resource_type type;
+	void *dst;
+} ResourcePref;
+ 
+
+/* function declarations */
+static void buttonpress(const XEvent *e);
+static void cleanup(void);
+static void clientmessage(const XEvent *e);
+static void config_init(void);
+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 int resource_load(XrmDatabase db, char *name, enum resource_type rtype, void *dst);
+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 pid_t nextpid;
+static const char *geometry;
+
+char *argv0;
+
+/* configuration, allows nested code to access above variables */
+#include "config.h"
+
+// Given a pid, return its cwd to buf
+int getpidcwd(pid_t pid, char* buf, size_t bufsiz) {
+	static const int proc_max = 20; // '/proc/4194304/cwd'
+	int sn_ret;
+	ssize_t rl_ret;
+	char path[proc_max];
+
+	sn_ret = snprintf(path, proc_max, "/proc/%d/cwd", pid);
+	if(sn_ret < 0 || sn_ret >= proc_max)
+		return -1;
+
+	rl_ret = readlink(path, buf, bufsiz);
+	if(rl_ret < 0 || rl_ret == bufsiz)
+		return -1;
+
+	buf[rl_ret] = 0;
+	return 0;
+}
+
+// Given a pid, return a reasonable guess at its child pid
+pid_t getchildpid(pid_t pid) {
+	// '/proc/4194304/task/4194304/children'
+	static const int proc_max = 40;
+	int sn_ret;
+	char path[proc_max];
+	FILE* f;
+
+	// guessing tid == pid
+	sn_ret = snprintf(path, proc_max, "/proc/%d/task/%d/children", pid, pid);
+	if (sn_ret < 0 || sn_ret >= proc_max)
+		return -1;
+
+	f = fopen(path, "r");
+	if(f == NULL)
+		return -1;
+
+	// guess first child
+	if(fscanf(f, "%d ", &pid) != 1)
+		return -1;
+
+	return pid;
+}
+
+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
+config_init(void)
+{
+	char *resm;
+	XrmDatabase db;
+	ResourcePref *p;
+
+	XrmInitialize();
+	resm = XResourceManagerString(dpy);
+	if (!resm)
+		return;
+
+	db = XrmGetStringDatabase(resm);
+	for (p = resources; p < resources + LENGTH(resources); p++)
+		resource_load(db, p->name, p->type, p->dst);
+}
+
+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;
+	char tabtitle[256];
+
+	if (nclients == 0) {
+		dc.x = 0;
+		dc.w = ww;
+		XFetchName(dpy, win, &name);
+		drawtext(name ? name : "", dc.norm);
+		XCopyArea(dpy, dc.drawable, win, dc.gc, 0, 0, ww, bh, 0, 0);
+		XSync(dpy, False);
+
+		return;
+	}
+
+	width = ww;
+	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;
+		}
+		snprintf(tabtitle, sizeof(tabtitle), "[%d] %s", c + 1, clients[c]->name);
+		drawtext(tabtitle, col);
+		dc.x += dc.w;
+		clients[c]->tabx = dc.x;
+	}
+	XCopyArea(dpy, dc.drawable, win, dc.gc, 0, 0, ww, bh, 0, 0);
+	XSync(dpy, False);
+}
+
+void
+drawtext(const char *text, XftColor col[ColLast])
+{
+	int i, 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;
+		c->pid = nextpid;
+
+		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 = focusnew;
+	}
+}
+
+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);
+}
+
+int
+resource_load(XrmDatabase db, char *name, enum resource_type rtype, void *dst)
+{
+	char **sdst = dst;
+	int *idst = dst;
+	float *fdst = dst;
+
+	char fullname[256];
+	char fullclass[256];
+	char *type;
+	XrmValue ret;
+
+	snprintf(fullname, sizeof(fullname), "%s.%s", "tabbed", name);
+	snprintf(fullclass, sizeof(fullclass), "%s.%s", "tabbed", name);
+	fullname[sizeof(fullname) - 1] = fullclass[sizeof(fullclass) - 1] = '\0';
+
+	XrmGetResource(db, fullname, fullclass, &type, &ret);
+	if (ret.addr == NULL || strncmp("String", type, 64))
+		return 1;
+
+	switch (rtype) {
+	case STRING:
+		*sdst = ret.addr;
+		break;
+	case INTEGER:
+		*idst = strtoul(ret.addr, NULL, 10);
+		break;
+	case FLOAT:
+		*fdst = strtof(ret.addr, NULL);
+		break;
+	}
+	return 0;
+}
+
+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 = focusnew;
+	focus(-1);
+}
+
+void
+spawn(const Arg *arg)
+{
+	struct sigaction sa;
+	char sel_cwd[PATH_MAX];
+	pid_t pid = fork();
+
+	if (pid == 0) {
+		if (dpy)
+			close(ConnectionNumber(dpy));
+
+		setsid();
+		if (sel >= 0 && clients[sel] && clients[sel]->pid > 0 &&
+		    getpidcwd(getchildpid(clients[sel]->pid), sel_cwd, PATH_MAX) == 0) {
+			chdir(sel_cwd);
+		}
+
+		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);
+	} else {
+		nextpid = pid;
+	}
+}
+
+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);
+
+	config_init();
+	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;
+}
diff --git a/xembed.1 b/xembed.1
@@ -0,0 +1,35 @@
+.TH XEMBED 1 tabbed\-VERSION
+.SH NAME
+xembed \- XEmbed foreground process
+.SH SYNOPSIS
+.B xembed
+.I flag command
+.RI [ "argument ..." ]
+.SH DESCRIPTION
+If the environment variable XEMBED is set, and
+.B xembed
+is in the foreground of its controlling tty, it will execute
+.IP
+command flag $XEMBED [argument ...]
+.LP
+Otherwise it will execute
+.IP
+command [argument ...]
+.LP
+.SH EXAMPLE
+In a terminal emulator within a
+.B tabbed
+session, the shell alias
+.IP
+$ alias surf='xembed -e surf'
+.LP
+will cause `surf' to open in a new tab, unless it is run in the background,
+i.e. `surf &', in which case it will instead open in a new window.
+.SH AUTHORS
+See the LICENSE file for the authors.
+.SH LICENSE
+See the LICENSE file for the terms of redistribution.
+.SH SEE ALSO
+.BR tabbed (1)
+.SH BUGS
+Please report them.
diff --git a/xembed.c b/xembed.c
@@ -0,0 +1,45 @@
+/*
+ * See LICENSE file for copyright and license details.
+ */
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+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;
+}