diff options
Diffstat (limited to 'tabbed')
| -rw-r--r-- | tabbed/LICENSE | 23 | ||||
| -rw-r--r-- | tabbed/Makefile | 65 | ||||
| -rw-r--r-- | tabbed/README | 22 | ||||
| -rw-r--r-- | tabbed/TODO | 4 | ||||
| -rw-r--r-- | tabbed/arg.h | 48 | ||||
| -rw-r--r-- | tabbed/config.def.h | 66 | ||||
| -rw-r--r-- | tabbed/config.h | 74 | ||||
| -rw-r--r-- | tabbed/config.mk | 24 | ||||
| -rw-r--r-- | tabbed/tabbed.1 | 171 | ||||
| -rw-r--r-- | tabbed/tabbed.c | 1406 | ||||
| -rw-r--r-- | tabbed/xembed.1 | 35 | ||||
| -rw-r--r-- | tabbed/xembed.c | 45 | 
12 files changed, 1983 insertions, 0 deletions
| diff --git a/tabbed/LICENSE b/tabbed/LICENSE new file mode 100644 index 0000000..d8e9678 --- /dev/null +++ b/tabbed/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/tabbed/Makefile b/tabbed/Makefile new file mode 100644 index 0000000..1b95d15 --- /dev/null +++ b/tabbed/Makefile @@ -0,0 +1,65 @@ +# tabbed - tabbing interface +# See LICENSE file for copyright and license details. + +include config.mk + +SRC = tabbed.c xembed.c +OBJ = ${SRC:.c=.o} +BIN = ${OBJ:.o=} + +all: options ${BIN} + +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 $@ + +.o: +	@echo CC -o $@ +	@${CC} -o $@ $< ${LDFLAGS} + +clean: +	@echo cleaning +	@rm -f ${BIN} ${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 files to ${DESTDIR}${PREFIX}/bin +	@mkdir -p "${DESTDIR}${PREFIX}/bin" +	@cp -f ${BIN} "${DESTDIR}${PREFIX}/bin" +	@chmod 755 "${DESTDIR}${PREFIX}/bin/tabbed" +	@echo installing manual pages 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" +	@sed "s/VERSION/${VERSION}/g" < xembed.1 > "${DESTDIR}${MANPREFIX}/man1/xembed.1" +	@chmod 644 "${DESTDIR}${MANPREFIX}/man1/xembed.1" + +uninstall: +	@echo removing executable files from ${DESTDIR}${PREFIX}/bin +	@rm -f "${DESTDIR}${PREFIX}/bin/tabbed" +	@rm -f "${DESTDIR}${PREFIX}/bin/xembed" +	@echo removing manual pages from ${DESTDIR}${MANPREFIX}/man1 +	@rm -f "${DESTDIR}${MANPREFIX}/man1/tabbed.1" +	@rm -f "${DESTDIR}${MANPREFIX}/man1/xembed.1" + +.PHONY: all options clean dist install uninstall diff --git a/tabbed/README b/tabbed/README new file mode 100644 index 0000000..4ed6bbe --- /dev/null +++ b/tabbed/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/tabbed/TODO b/tabbed/TODO new file mode 100644 index 0000000..8e1986d --- /dev/null +++ b/tabbed/TODO @@ -0,0 +1,4 @@ +# TODO +* add some way to detach windows +* add some way to attach windows + diff --git a/tabbed/arg.h b/tabbed/arg.h new file mode 100644 index 0000000..ba3fb3f --- /dev/null +++ b/tabbed/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/tabbed/config.def.h b/tabbed/config.def.h new file mode 100644 index 0000000..989992c --- /dev/null +++ b/tabbed/config.def.h @@ -0,0 +1,66 @@ +/* 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`\" &&" \ +                "xprop -id $1 -f $0 8s -set $0 \"$prop\"", \ +                p, winid, NULL \ +        } \ +} + +#define MODKEY ControlMask +static Key keys[] = { +	/* modifier             key        function     argument */ +	{ MODKEY|ShiftMask,     XK_Return, focusonce,   { 0 } }, +	{ MODKEY|ShiftMask,     XK_Return, spawn,       { 0 } }, +	{ MODKEY,               XK_t,      spawn,       SETPROP("_TABBED_SELECT_TAB") }, + +	{ 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 } }, + +	{ MODKEY,               XK_u,      focusurgent, { 0 } }, +	{ MODKEY|ShiftMask,     XK_u,      toggle,      { .v = (void*) &urgentswitch } }, + +	{ 0,                    XK_F11,    fullscreen,  { 0 } }, +}; diff --git a/tabbed/config.h b/tabbed/config.h new file mode 100644 index 0000000..1a5e933 --- /dev/null +++ b/tabbed/config.h @@ -0,0 +1,74 @@ +/* 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    = False; +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   = 1; +static Bool npisrelative  = True; + +#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`\" &&" \ +                "xprop -id $1 -f $0 8s -set $0 \"$prop\"", \ +                p, winid, NULL \ +        } \ +} + +#define MODKEY ControlMask +static Key keys[] = { +	/* modifier             key        function     argument */ + +	{ ShiftMask,            XK_Down,   focusonce,        { 0 } }, +	{ ShiftMask,            XK_Down,   spawn,        { 0 } }, +	{ MODKEY,            XK_Left,   movetab,     { .i = -1 } }, +	{ MODKEY,            XK_Right,  movetab,     { .i = +1 } }, +	{ ShiftMask,            XK_Left,   rotate,     { .i = -1 } }, +	{ ShiftMask,            XK_Right,  rotate,     { .i = +1 } }, + +//	{ MODKEY|ShiftMask,     XK_Return, focusonce,   { 0 } }, +//	{ MODKEY|ShiftMask,     XK_Return, spawn,       { 0 } }, +//	{ MODKEY,               XK_t,      spawn,       SETPROP("_TABBED_SELECT_TAB") }, +// +//	{ 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 } }, +// +//	{ MODKEY,               XK_u,      focusurgent, { 0 } }, +//	{ MODKEY|ShiftMask,     XK_u,      toggle,      { .v = (void*) &urgentswitch } }, +// +//	{ 0,                    XK_F11,    fullscreen,  { 0 } }, +}; diff --git a/tabbed/config.mk b/tabbed/config.mk new file mode 100644 index 0000000..095cead --- /dev/null +++ b/tabbed/config.mk @@ -0,0 +1,24 @@ +# tabbed version +VERSION = 0.6 + +# Customize below to fit your system + +# paths +PREFIX = /usr/local +MANPREFIX = ${PREFIX}/share/man + +# includes and libs +INCS = -I. -I/usr/include -I/usr/include/freetype2 +LIBS = -L/usr/lib -lc -lX11 -lfontconfig -lXft -lXrender + +# flags +CPPFLAGS = -DVERSION=\"${VERSION}\" -D_DEFAULT_SOURCE +CFLAGS = -std=c99 -pedantic -Wall -Os ${INCS} ${CPPFLAGS} +LDFLAGS = -s ${LIBS} + +# Solaris +#CFLAGS = -fast ${INCS} -DVERSION=\"${VERSION}\" +#LDFLAGS = ${LIBS} + +# compiler and linker +CC = cc diff --git a/tabbed/tabbed.1 b/tabbed/tabbed.1 new file mode 100644 index 0000000..bc28b8c --- /dev/null +++ b/tabbed/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\-t +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/tabbed.c b/tabbed/tabbed.c new file mode 100644 index 0000000..b4d47d1 --- /dev/null +++ b/tabbed/tabbed.c @@ -0,0 +1,1406 @@ +/* + * 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 <X11/Xatom.h> +#include <X11/Xlib.h> +#include <X11/Xproto.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; +} 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 sigchld(int unused); +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, 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; + +static Colormap cmap; +static Visual *visual = NULL; + +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); +		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, win, ww, wh, +		              32); +		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, visual, cmap); +	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; + +	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, visual, cmap, 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; + +	c = (sel + arg->i) % nclients; +	if (c < 0) +		c += nclients; + +	if (sel < 0 || 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 = 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 = 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; + +	/* 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[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; +	} + +	XVisualInfo *vis; +	XRenderPictFormat *fmt; +	int nvi; +	int i; + +	XVisualInfo tpl = { +		.screen = screen, +		.depth = 32, +		.class = TrueColor +	}; + +	vis = XGetVisualInfo(dpy, VisualScreenMask | VisualDepthMask | VisualClassMask, &tpl, &nvi); +	for(i = 0; i < nvi; i ++) { +		fmt = XRenderFindVisualFormat(dpy, vis[i].visual); +		if (fmt->type == PictTypeDirect && fmt->direct.alphaMask) { +			visual = vis[i].visual; +			break; +		} +	} + +	XFree(vis); + +	if (! visual) { +		fprintf(stderr, "Couldn't find ARGB visual.\n"); +		exit(1); +	} + +	cmap = XCreateColormap( dpy, root, visual, None); +	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); + +	XSetWindowAttributes attrs; +	attrs.background_pixel = dc.norm[ColBG].pixel; +	attrs.border_pixel = dc.norm[ColFG].pixel; +	attrs.bit_gravity = NorthWestGravity; +	attrs.event_mask = FocusChangeMask | KeyPressMask +		| ExposureMask | VisibilityChangeMask | StructureNotifyMask +		| ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; +	attrs.background_pixmap = None ; +	attrs.colormap = cmap; + +	win = XCreateWindow(dpy, root, wx, wy, +	ww, wh, 0, 32, InputOutput, +	visual, CWBackPixmap | CWBorderPixel | CWBitGravity +	| CWEventMask | CWColormap, &attrs); + +	dc.drawable = XCreatePixmap(dpy, win, ww, wh, +	                            32); +	dc.gc = XCreateGC(dpy, dc.drawable, 0, 0); + +	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; +		size_hint->height = wh; +		size_hint->width = ww; +	} 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 +sigchld(int unused) +{ +	if (signal(SIGCHLD, sigchld) == SIG_ERR) +		die("%s: cannot install SIGCHLD handler", argv0); + +	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, "%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; +} diff --git a/tabbed/xembed.1 b/tabbed/xembed.1 new file mode 100644 index 0000000..5b0c28c --- /dev/null +++ b/tabbed/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/tabbed/xembed.c b/tabbed/xembed.c new file mode 100644 index 0000000..cbb0e97 --- /dev/null +++ b/tabbed/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; +} | 
