aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Chudnick <sam@chudnick.com>2023-02-16 20:53:20 -0500
committerSam Chudnick <sam@chudnick.com>2023-02-16 20:53:20 -0500
commit07109bb861018f4de7785c082262c1e0ff3fede0 (patch)
treed4e90ebfb6aa88006afb978658add050a9cdbdce
initial commitHEADmaster
-rw-r--r--LICENSE23
-rw-r--r--Makefile69
-rw-r--r--README.md24
-rw-r--r--arg.h48
-rw-r--r--config.h79
-rw-r--r--patches/alpha.diff122
-rw-r--r--patches/tabbed-autohide-0.6.diff50
-rw-r--r--patches/tabbed-bar-height-0.6.diff24
-rw-r--r--patches/tabbed-clientnumber-0.6.diff23
-rw-r--r--patches/tabbed-xresources-20210317-dabf6a2.diff178
-rw-r--r--tabbed.1171
-rw-r--r--tabbed.c1503
-rw-r--r--xembed.135
-rw-r--r--xembed.c45
14 files changed, 2394 insertions, 0 deletions
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d8e9678
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,23 @@
1MIT/X Consortium License
2
3© 2009-2011 Enno Boland <g s01 de>
4© 2011,2015 Connor Lane Smith <cls@lubutu.com>
5© 2012-2015 Christoph Lohmann <20h@r-36.net>
6
7Permission is hereby granted, free of charge, to any person obtaining a
8copy of this software and associated documentation files (the "Software"),
9to deal in the Software without restriction, including without limitation
10the rights to use, copy, modify, merge, publish, distribute, sublicense,
11and/or sell copies of the Software, and to permit persons to whom the
12Software is furnished to do so, subject to the following conditions:
13
14The above copyright notice and this permission notice shall be included in
15all copies or substantial portions of the Software.
16
17THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23DEALINGS IN THE SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..f9a5d9e
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,69 @@
1.POSIX:
2
3NAME = tabbed
4VERSION = 0.7
5
6# paths
7PREFIX = /usr/local
8MANPREFIX = ${PREFIX}/share/man
9DOCPREFIX = ${PREFIX}/share/doc/${NAME}
10
11# use system flags.
12TABBED_CFLAGS = -I/usr/X11R6/include -I/usr/include/freetype2 ${CFLAGS}
13TABBED_LDFLAGS = -L/usr/X11R6/lib -lX11 -lfontconfig -lXft -lXrender ${LDFLAGS}
14TABBED_CPPFLAGS = -DVERSION=\"${VERSION}\" -D_DEFAULT_SOURCE
15
16# OpenBSD (uncomment)
17#TABBED_CFLAGS = -I/usr/X11R6/include -I/usr/X11R6/include/freetype2 ${CFLAGS}
18
19SRC = tabbed.c xembed.c
20OBJ = ${SRC:.c=.o}
21BIN = ${OBJ:.o=}
22MAN1 = ${BIN:=.1}
23HDR = arg.h config.def.h
24DOC = LICENSE README
25
26all: ${BIN}
27
28.c.o:
29 ${CC} -o $@ -c $< ${TABBED_CFLAGS} ${TABBED_CPPFLAGS}
30
31${OBJ}: config.h
32
33config.h:
34 cp config.def.h $@
35
36.o:
37 ${CC} -o $@ $< ${TABBED_LDFLAGS}
38
39clean:
40 rm -f ${BIN} ${OBJ} "${NAME}-${VERSION}.tar.gz"
41
42dist: clean
43 mkdir -p "${NAME}-${VERSION}"
44 cp -fR Makefile ${MAN1} ${DOC} ${HDR} ${SRC} "${NAME}-${VERSION}"
45 tar -cf - "${NAME}-${VERSION}" | gzip -c > "${NAME}-${VERSION}.tar.gz"
46 rm -rf ${NAME}-${VERSION}
47
48install: all
49 # installing executable files.
50 mkdir -p "${DESTDIR}${PREFIX}/bin"
51 cp -f ${BIN} "${DESTDIR}${PREFIX}/bin"
52 for f in ${BIN}; do chmod 755 "${DESTDIR}${PREFIX}/bin/$$f"; done
53 # installing doc files.
54 mkdir -p "${DESTDIR}${DOCPREFIX}"
55 cp -f README "${DESTDIR}${DOCPREFIX}"
56 # installing manual pages for general commands: section 1.
57 mkdir -p "${DESTDIR}${MANPREFIX}/man1"
58 for m in ${MAN1}; do sed "s/VERSION/${VERSION}/g" < $$m > "${DESTDIR}${MANPREFIX}/man1/$$m"; done
59
60uninstall:
61 # removing executable files.
62 for f in ${BIN}; do rm -f "${DESTDIR}${PREFIX}/bin/$$f"; done
63 # removing doc files.
64 rm -f "${DESTDIR}${DOCPREFIX}/README"
65 # removing manual pages.
66 for m in ${MAN1}; do rm -f "${DESTDIR}${MANPREFIX}/man1/$$m"; done
67 -rmdir "${DESTDIR}${DOCPREFIX}"
68
69.PHONY: all clean dist install uninstall
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5620608
--- /dev/null
+++ b/README.md
@@ -0,0 +1,24 @@
1# tabbed - generic tabbed interface
2
3tabbed is a simple tabbed X window container.
4
5## Installation
6
7```
8apt install libx11-dev
9git clone https://gitea.chudnick.com/sam/tabbed
10cd tabbed
11sudo make install
12```
13
14## Running tabbed
15
16See the man page for details.
17
18## patches
19
20- alpha
21- autohide
22- bar-height
23- clientnumber
24- xresources
diff --git a/arg.h b/arg.h
new file mode 100644
index 0000000..ba3fb3f
--- /dev/null
+++ b/arg.h
@@ -0,0 +1,48 @@
1/*
2 * Copy me if you can.
3 * by 20h
4 */
5
6#ifndef ARG_H__
7#define ARG_H__
8
9extern char *argv0;
10
11/* use main(int argc, char *argv[]) */
12#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\
13 argv[0] && argv[0][0] == '-'\
14 && argv[0][1];\
15 argc--, argv++) {\
16 char argc_;\
17 char **argv_;\
18 int brk_;\
19 if (argv[0][1] == '-' && argv[0][2] == '\0') {\
20 argv++;\
21 argc--;\
22 break;\
23 }\
24 for (brk_ = 0, argv[0]++, argv_ = argv;\
25 argv[0][0] && !brk_;\
26 argv[0]++) {\
27 if (argv_ != argv)\
28 break;\
29 argc_ = argv[0][0];\
30 switch (argc_)
31#define ARGEND }\
32 }
33
34#define ARGC() argc_
35
36#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\
37 ((x), abort(), (char *)0) :\
38 (brk_ = 1, (argv[0][1] != '\0')?\
39 (&argv[0][1]) :\
40 (argc--, argv++, argv[0])))
41
42#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\
43 (char *)0 :\
44 (brk_ = 1, (argv[0][1] != '\0')?\
45 (&argv[0][1]) :\
46 (argc--, argv++, argv[0])))
47
48#endif
diff --git a/config.h b/config.h
new file mode 100644
index 0000000..e5a5a9b
--- /dev/null
+++ b/config.h
@@ -0,0 +1,79 @@
1/* See LICENSE file for copyright and license details. */
2
3/* appearance */
4static char font[] = "monospace:size=9";
5static char* normbgcolor = "#222222";
6static char* normfgcolor = "#cccccc";
7static char* selbgcolor = "#555555";
8static char* selfgcolor = "#ffffff";
9static char* urgbgcolor = "#111111";
10static char* urgfgcolor = "#cc0000";
11static const char before[] = "<";
12static const char after[] = ">";
13static const char titletrim[] = "...";
14static const int tabwidth = 200;
15static const Bool foreground = True;
16static Bool urgentswitch = False;
17static const int barHeight = 24;
18
19/*
20 * Where to place a new tab when it is opened. When npisrelative is True,
21 * then the current position is changed + newposition. If npisrelative
22 * is False, then newposition is an absolute position.
23 */
24static int newposition = 0;
25static Bool npisrelative = False;
26
27#define SETPROP(p) { \
28 .v = (char *[]){ "/bin/sh", "-c", \
29 "prop=\"`xwininfo -children -id $1 | grep '^ 0x' |" \
30 "sed -e's@^ *\\(0x[0-9a-f]*\\) \"\\([^\"]*\\)\".*@\\1 \\2@' |" \
31 "xargs -0 printf %b | dmenu -l 10 -w $1`\" &&" \
32 "xprop -id $1 -f $0 8s -set $0 \"$prop\"", \
33 p, winid, NULL \
34 } \
35}
36
37/*
38 * Xresources preferences to load at startup
39 */
40ResourcePref resources[] = {
41 { "font", STRING, &font },
42 { "color0", STRING, &normbgcolor },
43 { "color4", STRING, &normfgcolor },
44 { "color4", STRING, &selbgcolor },
45 { "color7", STRING, &selfgcolor },
46 { "color2", STRING, &urgbgcolor },
47 { "color3", STRING, &urgfgcolor },
48};
49
50#define MODKEY ControlMask
51static const Key keys[] = {
52 /* modifier key function argument */
53 { MODKEY, XK_t, spawn, { 0 } },
54 { MODKEY|ShiftMask, XK_t, killclient, { 0 } },
55
56 { MODKEY|ShiftMask, XK_l, rotate, { .i = +1 } },
57 { MODKEY|ShiftMask, XK_h, rotate, { .i = -1 } },
58 { MODKEY|ShiftMask, XK_j, movetab, { .i = -1 } },
59 { MODKEY|ShiftMask, XK_k, movetab, { .i = +1 } },
60 { MODKEY, XK_Tab, rotate, { .i = 0 } },
61
62 { MODKEY, XK_grave, spawn, SETPROP("_TABBED_SELECT_TAB") },
63 { MODKEY, XK_1, move, { .i = 0 } },
64 { MODKEY, XK_2, move, { .i = 1 } },
65 { MODKEY, XK_3, move, { .i = 2 } },
66 { MODKEY, XK_4, move, { .i = 3 } },
67 { MODKEY, XK_5, move, { .i = 4 } },
68 { MODKEY, XK_6, move, { .i = 5 } },
69 { MODKEY, XK_7, move, { .i = 6 } },
70 { MODKEY, XK_8, move, { .i = 7 } },
71 { MODKEY, XK_9, move, { .i = 8 } },
72 { MODKEY, XK_0, move, { .i = 9 } },
73
74
75 { MODKEY, XK_u, focusurgent, { 0 } },
76 { MODKEY|ShiftMask, XK_u, toggle, { .v = (void*) &urgentswitch } },
77
78 { 0, XK_F11, fullscreen, { 0 } },
79};
diff --git a/patches/alpha.diff b/patches/alpha.diff
new file mode 100644
index 0000000..3ce77a7
--- /dev/null
+++ b/patches/alpha.diff
@@ -0,0 +1,122 @@
1diff --git a/config.mk b/config.mk
2index 3a71529..095cead 100644
3--- a/config.mk
4+++ b/config.mk
5@@ -9,7 +9,7 @@ MANPREFIX = ${PREFIX}/share/man
6
7 # includes and libs
8 INCS = -I. -I/usr/include -I/usr/include/freetype2
9-LIBS = -L/usr/lib -lc -lX11 -lfontconfig -lXft
10+LIBS = -L/usr/lib -lc -lX11 -lfontconfig -lXft -lXrender
11
12 # flags
13 CPPFLAGS = -DVERSION=\"${VERSION}\" -D_DEFAULT_SOURCE
14diff --git a/tabbed.c b/tabbed.c
15index 9a44795..b4d47d1 100644
16--- a/tabbed.c
17+++ b/tabbed.c
18@@ -170,6 +170,9 @@ static char **cmd;
19 static char *wmname = "tabbed";
20 static const char *geometry;
21
22+static Colormap cmap;
23+static Visual *visual = NULL;
24+
25 char *argv0;
26
27 /* configuration, allows nested code to access above variables */
28@@ -255,8 +258,8 @@ configurenotify(const XEvent *e)
29 ww = ev->width;
30 wh = ev->height;
31 XFreePixmap(dpy, dc.drawable);
32- dc.drawable = XCreatePixmap(dpy, root, ww, wh,
33- DefaultDepth(dpy, screen));
34+ dc.drawable = XCreatePixmap(dpy, win, ww, wh,
35+ 32);
36 if (sel > -1)
37 resize(sel, ww, wh - bh);
38 XSync(dpy, False);
39@@ -399,7 +402,7 @@ drawtext(const char *text, XftColor col[ColLast])
40 ;
41 }
42
43- d = XftDrawCreate(dpy, dc.drawable, DefaultVisual(dpy, screen), DefaultColormap(dpy, screen));
44+ d = XftDrawCreate(dpy, dc.drawable, visual, cmap);
45 XftDrawStringUtf8(d, &col[ColFG], dc.font.xfont, x, y, (XftChar8 *) buf, len);
46 XftDrawDestroy(d);
47 }
48@@ -564,7 +567,7 @@ getcolor(const char *colstr)
49 {
50 XftColor color;
51
52- if (!XftColorAllocName(dpy, DefaultVisual(dpy, screen), DefaultColormap(dpy, screen), colstr, &color))
53+ if (!XftColorAllocName(dpy, visual, cmap, colstr, &color))
54 die("%s: cannot allocate color '%s'\n", argv0, colstr);
55
56 return color;
57@@ -1016,18 +1019,60 @@ setup(void)
58 wy = dh + wy - wh - 1;
59 }
60
61+ XVisualInfo *vis;
62+ XRenderPictFormat *fmt;
63+ int nvi;
64+ int i;
65+
66+ XVisualInfo tpl = {
67+ .screen = screen,
68+ .depth = 32,
69+ .class = TrueColor
70+ };
71+
72+ vis = XGetVisualInfo(dpy, VisualScreenMask | VisualDepthMask | VisualClassMask, &tpl, &nvi);
73+ for(i = 0; i < nvi; i ++) {
74+ fmt = XRenderFindVisualFormat(dpy, vis[i].visual);
75+ if (fmt->type == PictTypeDirect && fmt->direct.alphaMask) {
76+ visual = vis[i].visual;
77+ break;
78+ }
79+ }
80+
81+ XFree(vis);
82+
83+ if (! visual) {
84+ fprintf(stderr, "Couldn't find ARGB visual.\n");
85+ exit(1);
86+ }
87+
88+ cmap = XCreateColormap( dpy, root, visual, None);
89 dc.norm[ColBG] = getcolor(normbgcolor);
90 dc.norm[ColFG] = getcolor(normfgcolor);
91 dc.sel[ColBG] = getcolor(selbgcolor);
92 dc.sel[ColFG] = getcolor(selfgcolor);
93 dc.urg[ColBG] = getcolor(urgbgcolor);
94 dc.urg[ColFG] = getcolor(urgfgcolor);
95- dc.drawable = XCreatePixmap(dpy, root, ww, wh,
96- DefaultDepth(dpy, screen));
97- dc.gc = XCreateGC(dpy, root, 0, 0);
98
99- win = XCreateSimpleWindow(dpy, root, wx, wy, ww, wh, 0,
100- dc.norm[ColFG].pixel, dc.norm[ColBG].pixel);
101+ XSetWindowAttributes attrs;
102+ attrs.background_pixel = dc.norm[ColBG].pixel;
103+ attrs.border_pixel = dc.norm[ColFG].pixel;
104+ attrs.bit_gravity = NorthWestGravity;
105+ attrs.event_mask = FocusChangeMask | KeyPressMask
106+ | ExposureMask | VisibilityChangeMask | StructureNotifyMask
107+ | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask;
108+ attrs.background_pixmap = None ;
109+ attrs.colormap = cmap;
110+
111+ win = XCreateWindow(dpy, root, wx, wy,
112+ ww, wh, 0, 32, InputOutput,
113+ visual, CWBackPixmap | CWBorderPixel | CWBitGravity
114+ | CWEventMask | CWColormap, &attrs);
115+
116+ dc.drawable = XCreatePixmap(dpy, win, ww, wh,
117+ 32);
118+ dc.gc = XCreateGC(dpy, dc.drawable, 0, 0);
119+
120 XMapRaised(dpy, win);
121 XSelectInput(dpy, win, SubstructureNotifyMask | FocusChangeMask |
122 ButtonPressMask | ExposureMask | KeyPressMask |
diff --git a/patches/tabbed-autohide-0.6.diff b/patches/tabbed-autohide-0.6.diff
new file mode 100644
index 0000000..fd641b4
--- /dev/null
+++ b/patches/tabbed-autohide-0.6.diff
@@ -0,0 +1,50 @@
1diff --git a/tabbed.c b/tabbed.c
2index ff3ada0..c41db0c 100644
3--- a/tabbed.c
4+++ b/tabbed.c
5@@ -152,7 +152,7 @@ static void (*handler[LASTEvent]) (const XEvent *) = {
6 [MapRequest] = maprequest,
7 [PropertyNotify] = propertynotify,
8 };
9-static int bh, wx, wy, ww, wh;
10+static int bh, wx, wy, ww, wh, vbh;
11 static unsigned int numlockmask = 0;
12 static Bool running = True, nextfocus, doinitspawn = True,
13 fillagain = False, closelastclient = False;
14@@ -307,6 +307,6 @@ void
15 drawbar(void) {
16 unsigned long *col;
17- int c, fc, width, n = 0;
18+ int c, fc, width, n = 0, nbh, i;
19 char *name = NULL;
20
21 if (nclients == 0) {
22@@ -314,10 +314,19 @@ drawbar(void)
23 dc.w = ww;
24 XFetchName(dpy, win, &name);
25 drawtext(name ? name : "", dc.norm);
26- XCopyArea(dpy, dc.drawable, win, dc.gc, 0, 0, ww, bh, 0, 0);
27+ XCopyArea(dpy, dc.drawable, win, dc.gc, 0, 0, ww, vbh, 0, 0);
28 XSync(dpy, False);
29
30 return;
31 }
32
33+ nbh = nclients > 1 ? vbh : 0;
34+ if (bh != nbh) {
35+ bh = nbh;
36+ for (i = 0; i < nclients; i++)
37+ XMoveResizeWindow(dpy, clients[i]->win, 0, bh, ww, wh - bh);
38+ }
39+ if (bh == 0)
40+ return;
41+
42 width = ww;
43@@ -920,6 +929,6 @@ setup(void)
44 screen = DefaultScreen(dpy);
45 root = RootWindow(dpy, screen);
46 initfont(font);
47- bh = dc.h = dc.font.height + 2;
48+ vbh = dc.h = dc.font.height + 2;
49
50 /* init atoms */
diff --git a/patches/tabbed-bar-height-0.6.diff b/patches/tabbed-bar-height-0.6.diff
new file mode 100644
index 0000000..fddcb28
--- /dev/null
+++ b/patches/tabbed-bar-height-0.6.diff
@@ -0,0 +1,24 @@
1diff --color -up tabbed-0.6-clean/config.def.h tabbed-0.6-modified/config.def.h
2--- tabbed-0.6-clean/config.def.h 2014-01-21 10:22:03.000000000 -0800
3+++ tabbed-0.6-modified/config.def.h 2021-03-30 20:23:45.752478278 -0700
4@@ -10,7 +10,7 @@ static const char before[] = "<";
5 static const char after[] = ">";
6 static const int tabwidth = 200;
7 static const Bool foreground = True;
8-
9+static const int barHeight = 24;
10 /*
11 * Where to place a new tab when it is opened. When npisrelative is True,
12 * then the current position is changed + newposition. If npisrelative
13diff --color -up tabbed-0.6-clean/tabbed.c tabbed-0.6-modified/tabbed.c
14--- tabbed-0.6-clean/tabbed.c 2014-01-21 10:22:03.000000000 -0800
15+++ tabbed-0.6-modified/tabbed.c 2021-03-30 20:24:23.712477426 -0700
16@@ -920,7 +920,7 @@ setup(void) {
17 screen = DefaultScreen(dpy);
18 root = RootWindow(dpy, screen);
19 initfont(font);
20- bh = dc.h = dc.font.height + 2;
21+ bh = dc.h = barHeight;
22
23 /* init atoms */
24 wmatom[WMProtocols] = XInternAtom(dpy, "WM_PROTOCOLS", False);
diff --git a/patches/tabbed-clientnumber-0.6.diff b/patches/tabbed-clientnumber-0.6.diff
new file mode 100644
index 0000000..430245c
--- /dev/null
+++ b/patches/tabbed-clientnumber-0.6.diff
@@ -0,0 +1,23 @@
1diff --git a/tabbed.c b/tabbed.c
2index d30206b..70642cb 100644
3--- a/tabbed.c
4+++ b/tabbed.c
5@@ -308,6 +308,7 @@ drawbar(void) {
6 unsigned long *col;
7 int c, fc, width, n = 0;
8 char *name = NULL;
9+ char tabtitle[256];
10
11 if(nclients == 0) {
12 dc.x = 0;
13@@ -353,7 +354,9 @@ drawbar(void) {
14 } else {
15 col = dc.norm;
16 }
17- drawtext(clients[c]->name, col);
18+ snprintf(tabtitle, sizeof(tabtitle), "%d: %s",
19+ c + 1, clients[c]->name);
20+ drawtext(tabtitle, col);
21 dc.x += dc.w;
22 clients[c]->tabx = dc.x;
23 }
diff --git a/patches/tabbed-xresources-20210317-dabf6a2.diff b/patches/tabbed-xresources-20210317-dabf6a2.diff
new file mode 100644
index 0000000..15610a7
--- /dev/null
+++ b/patches/tabbed-xresources-20210317-dabf6a2.diff
@@ -0,0 +1,178 @@
1From 8c48f1564c555bbd21758a3a70a9984e61c34a35 Mon Sep 17 00:00:00 2001
2From: 6d6f7274686f6e <4648531+6d6f7274686f6e@users.noreply.github.com>
3Date: Wed, 17 Mar 2021 10:59:18 +0100
4Subject: [PATCH] xresources support
5
6---
7 config.def.h | 27 ++++++++++++++------
8 tabbed.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++
9 2 files changed, 89 insertions(+), 7 deletions(-)
10
11diff --git a/config.def.h b/config.def.h
12index defa426..244e288 100644
13--- a/config.def.h
14+++ b/config.def.h
15@@ -1,13 +1,13 @@
16 /* See LICENSE file for copyright and license details. */
17
18 /* appearance */
19-static const char font[] = "monospace:size=9";
20-static const char* normbgcolor = "#222222";
21-static const char* normfgcolor = "#cccccc";
22-static const char* selbgcolor = "#555555";
23-static const char* selfgcolor = "#ffffff";
24-static const char* urgbgcolor = "#111111";
25-static const char* urgfgcolor = "#cc0000";
26+static char font[] = "monospace:size=9";
27+static char* normbgcolor = "#222222";
28+static char* normfgcolor = "#cccccc";
29+static char* selbgcolor = "#555555";
30+static char* selfgcolor = "#ffffff";
31+static char* urgbgcolor = "#111111";
32+static char* urgfgcolor = "#cc0000";
33 static const char before[] = "<";
34 static const char after[] = ">";
35 static const char titletrim[] = "...";
36@@ -33,6 +33,19 @@ static Bool npisrelative = False;
37 } \
38 }
39
40+/*
41+ * Xresources preferences to load at startup
42+ */
43+ResourcePref resources[] = {
44+ { "font", STRING, &font },
45+ { "color0", STRING, &normbgcolor },
46+ { "color4", STRING, &normfgcolor },
47+ { "color4", STRING, &selbgcolor },
48+ { "color7", STRING, &selfgcolor },
49+ { "color2", STRING, &urgbgcolor },
50+ { "color3", STRING, &urgfgcolor },
51+};
52+
53 #define MODKEY ControlMask
54 static Key keys[] = {
55 /* modifier key function argument */
56diff --git a/tabbed.c b/tabbed.c
57index eafe28a..c5bffc7 100644
58--- a/tabbed.c
59+++ b/tabbed.c
60@@ -13,6 +13,7 @@
61 #include <X11/Xatom.h>
62 #include <X11/Xlib.h>
63 #include <X11/Xproto.h>
64+#include <X11/Xresource.h>
65 #include <X11/Xutil.h>
66 #include <X11/XKBlib.h>
67 #include <X11/Xft/Xft.h>
68@@ -85,11 +86,26 @@ typedef struct {
69 Bool urgent;
70 Bool closed;
71 } Client;
72+
73+/* Xresources preferences */
74+enum resource_type {
75+ STRING = 0,
76+ INTEGER = 1,
77+ FLOAT = 2
78+};
79+
80+typedef struct {
81+ char *name;
82+ enum resource_type type;
83+ void *dst;
84+} ResourcePref;
85+
86
87 /* function declarations */
88 static void buttonpress(const XEvent *e);
89 static void cleanup(void);
90 static void clientmessage(const XEvent *e);
91+static void config_init(void);
92 static void configurenotify(const XEvent *e);
93 static void configurerequest(const XEvent *e);
94 static void createnotify(const XEvent *e);
95@@ -120,6 +136,7 @@ static void move(const Arg *arg);
96 static void movetab(const Arg *arg);
97 static void propertynotify(const XEvent *e);
98 static void resize(int c, int w, int h);
99+static int resource_load(XrmDatabase db, char *name, enum resource_type rtype, void *dst);
100 static void rotate(const Arg *arg);
101 static void run(void);
102 static void sendxembed(int c, long msg, long detail, long d1, long d2);
103@@ -245,6 +262,23 @@ clientmessage(const XEvent *e)
104 }
105 }
106
107+void
108+config_init(void)
109+{
110+ char *resm;
111+ XrmDatabase db;
112+ ResourcePref *p;
113+
114+ XrmInitialize();
115+ resm = XResourceManagerString(dpy);
116+ if (!resm)
117+ return;
118+
119+ db = XrmGetStringDatabase(resm);
120+ for (p = resources; p < resources + LENGTH(resources); p++)
121+ resource_load(db, p->name, p->type, p->dst);
122+}
123+
124 void
125 configurenotify(const XEvent *e)
126 {
127@@ -897,6 +931,40 @@ resize(int c, int w, int h)
128 (XEvent *)&ce);
129 }
130
131+int
132+resource_load(XrmDatabase db, char *name, enum resource_type rtype, void *dst)
133+{
134+ char **sdst = dst;
135+ int *idst = dst;
136+ float *fdst = dst;
137+
138+ char fullname[256];
139+ char fullclass[256];
140+ char *type;
141+ XrmValue ret;
142+
143+ snprintf(fullname, sizeof(fullname), "%s.%s", "tabbed", name);
144+ snprintf(fullclass, sizeof(fullclass), "%s.%s", "tabbed", name);
145+ fullname[sizeof(fullname) - 1] = fullclass[sizeof(fullclass) - 1] = '\0';
146+
147+ XrmGetResource(db, fullname, fullclass, &type, &ret);
148+ if (ret.addr == NULL || strncmp("String", type, 64))
149+ return 1;
150+
151+ switch (rtype) {
152+ case STRING:
153+ *sdst = ret.addr;
154+ break;
155+ case INTEGER:
156+ *idst = strtoul(ret.addr, NULL, 10);
157+ break;
158+ case FLOAT:
159+ *fdst = strtof(ret.addr, NULL);
160+ break;
161+ }
162+ return 0;
163+}
164+
165 void
166 rotate(const Arg *arg)
167 {
168@@ -1354,6 +1422,7 @@ main(int argc, char *argv[])
169 if (!(dpy = XOpenDisplay(NULL)))
170 die("%s: cannot open display\n", argv0);
171
172+ config_init();
173 setup();
174 printf("0x%lx\n", win);
175 fflush(NULL);
176--
1772.30.2
178
diff --git a/tabbed.1 b/tabbed.1
new file mode 100644
index 0000000..07bdbd7
--- /dev/null
+++ b/tabbed.1
@@ -0,0 +1,171 @@
1.TH TABBED 1 tabbed\-VERSION
2.SH NAME
3tabbed \- generic tabbed interface
4.SH SYNOPSIS
5.B tabbed
6.RB [ \-c ]
7.RB [ \-d ]
8.RB [ \-k ]
9.RB [ \-s ]
10.RB [ \-v ]
11.RB [ \-g
12.IR geometry ]
13.RB [ \-n
14.IR name ]
15.RB [ \-p
16.RB [ s {+/-} ] \fIpos\fR ]
17.RB [ \-o
18.IR normbgcol ]
19.RB [ \-O
20.IR normfgcol ]
21.RB [ \-t
22.IR selbgcol ]
23.RB [ \-T
24.IR selfgcol ]
25.RB [ \-u
26.IR urgbgcol ]
27.RB [ \-U
28.IR urgfgcol ]
29.RB [ \-r
30.IR narg ]
31.RI [ "command ..." ]
32.SH DESCRIPTION
33.B tabbed
34is a simple tabbed container for applications which support XEmbed. Tabbed
35will then run the provided command with the xid of tabbed as appended
36argument. (See EXAMPLES.) The automatic spawning of the command can be
37disabled by providing the -s parameter. If no command is provided
38tabbed will just print its xid and run no command.
39.SH OPTIONS
40.TP
41.B \-c
42close tabbed when the last tab is closed. Mutually exclusive with -f.
43.TP
44.B \-d
45detaches tabbed from the terminal and prints its XID to stdout.
46.TP
47.B \-f
48fill up tabbed again by spawning the provided command, when the last tab is
49closed. Mutually exclusive with -c.
50.TP
51.BI \-g " geometry"
52defines the X11 geometry string, which will fixate the height and width of
53tabbed.
54The syntax is
55.RI [=][ width {xX} height ][{+-} xoffset {+-} yoffset ].
56See
57.BR XParseGeometry (3)
58for further details.
59.TP
60.B \-k
61close foreground tabbed client (instead of tabbed and all clients) when
62WM_DELETE_WINDOW is sent.
63.TP
64.BI \-n " name"
65will set the WM_CLASS attribute to
66.I name.
67.TP
68.BR \-p " [" s {+-}] \fIpos\fR
69will set the absolute or relative position of where to start a new tab. When
70.I pos
71is is given without 's' in front it is an absolute position. Then negative
72numbers will be the position from the last tab, where -1 is the last tab.
73If 's' is given, then
74.I pos
75is a relative position to the current selected tab. If this reaches the limits
76of the tabs; those limits then apply.
77.TP
78.BI \-r " narg"
79will replace the
80.I narg
81th argument in
82.I command
83with the window id, rather than appending it to the end.
84.TP
85.B \-s
86will disable automatic spawning of the command.
87.TP
88.BI \-o " normbgcol"
89defines the normal background color.
90.RI # RGB ,
91.RI # RRGGBB ,
92and X color names are supported.
93.TP
94.BI \-O " normfgcol"
95defines the normal foreground color.
96.TP
97.BI \-t " selbgcol"
98defines the selected background color.
99.TP
100.BI \-T " selfgbcol"
101defines the selected foreground color.
102.TP
103.BI \-u " urgbgcol"
104defines the urgent background color.
105.TP
106.BI \-U " urgfgbcol"
107defines the urgent foreground color.
108.TP
109.B \-v
110prints version information to stderr, then exits.
111.SH USAGE
112.TP
113.B Ctrl\-Shift\-Return
114open new tab
115.TP
116.B Ctrl\-Shift\-h
117previous tab
118.TP
119.B Ctrl\-Shift\-l
120next tab
121.TP
122.B Ctrl\-Shift\-j
123move selected tab one to the left
124.TP
125.B Ctrl\-Shift\-k
126move selected tab one to the right
127.TP
128.B Ctrl\-Shift\-u
129toggle autofocus of urgent tabs
130.TP
131.B Ctrl\-Tab
132toggle between the selected and last selected tab
133.TP
134.B Ctrl\-`
135open dmenu to either create a new tab appending the entered string or select
136an already existing tab.
137.TP
138.B Ctrl\-q
139close tab
140.TP
141.B Ctrl\-u
142focus next urgent tab
143.TP
144.B Ctrl\-[0..9]
145jumps to nth tab
146.TP
147.B F11
148Toggle fullscreen mode.
149.SH EXAMPLES
150$ tabbed surf -e
151.TP
152$ tabbed urxvt -embed
153.TP
154$ tabbed xterm -into
155.TP
156$ $(tabbed -d >/tmp/tabbed.xid); urxvt -embed $(</tmp/tabbed.xid);
157.TP
158$ tabbed -r 2 st -w '' -e tmux
159.SH CUSTOMIZATION
160.B tabbed
161can be customized by creating a custom config.h and (re)compiling the source
162code. This keeps it fast, secure and simple.
163.SH AUTHORS
164See the LICENSE file for the authors.
165.SH LICENSE
166See the LICENSE file for the terms of redistribution.
167.SH SEE ALSO
168.BR st (1),
169.BR xembed (1)
170.SH BUGS
171Please report them.
diff --git a/tabbed.c b/tabbed.c
new file mode 100644
index 0000000..2514949
--- /dev/null
+++ b/tabbed.c
@@ -0,0 +1,1503 @@
1/*
2 * See LICENSE file for copyright and license details.
3 */
4
5#include <sys/wait.h>
6#include <locale.h>
7#include <signal.h>
8#include <stdarg.h>
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12#include <unistd.h>
13#include <X11/Xatom.h>
14#include <X11/Xlib.h>
15#include <X11/Xproto.h>
16#include <X11/Xresource.h>
17#include <X11/Xutil.h>
18#include <X11/XKBlib.h>
19#include <X11/Xft/Xft.h>
20
21#include "arg.h"
22
23/* XEMBED messages */
24#define XEMBED_EMBEDDED_NOTIFY 0
25#define XEMBED_WINDOW_ACTIVATE 1
26#define XEMBED_WINDOW_DEACTIVATE 2
27#define XEMBED_REQUEST_FOCUS 3
28#define XEMBED_FOCUS_IN 4
29#define XEMBED_FOCUS_OUT 5
30#define XEMBED_FOCUS_NEXT 6
31#define XEMBED_FOCUS_PREV 7
32/* 8-9 were used for XEMBED_GRAB_KEY/XEMBED_UNGRAB_KEY */
33#define XEMBED_MODALITY_ON 10
34#define XEMBED_MODALITY_OFF 11
35#define XEMBED_REGISTER_ACCELERATOR 12
36#define XEMBED_UNREGISTER_ACCELERATOR 13
37#define XEMBED_ACTIVATE_ACCELERATOR 14
38
39/* Details for XEMBED_FOCUS_IN: */
40#define XEMBED_FOCUS_CURRENT 0
41#define XEMBED_FOCUS_FIRST 1
42#define XEMBED_FOCUS_LAST 2
43
44/* Macros */
45#define MAX(a, b) ((a) > (b) ? (a) : (b))
46#define MIN(a, b) ((a) < (b) ? (a) : (b))
47#define LENGTH(x) (sizeof((x)) / sizeof(*(x)))
48#define CLEANMASK(mask) (mask & ~(numlockmask | LockMask))
49#define TEXTW(x) (textnw(x, strlen(x)) + dc.font.height)
50
51enum { ColFG, ColBG, ColLast }; /* color */
52enum { WMProtocols, WMDelete, WMName, WMState, WMFullscreen,
53 XEmbed, WMSelectTab, WMLast }; /* default atoms */
54
55typedef union {
56 int i;
57 const void *v;
58} Arg;
59
60typedef struct {
61 unsigned int mod;
62 KeySym keysym;
63 void (*func)(const Arg *);
64 const Arg arg;
65} Key;
66
67typedef struct {
68 int x, y, w, h;
69 XftColor norm[ColLast];
70 XftColor sel[ColLast];
71 XftColor urg[ColLast];
72 Drawable drawable;
73 GC gc;
74 struct {
75 int ascent;
76 int descent;
77 int height;
78 XftFont *xfont;
79 } font;
80} DC; /* draw context */
81
82typedef struct {
83 char name[256];
84 Window win;
85 int tabx;
86 Bool urgent;
87 Bool closed;
88} Client;
89
90/* Xresources preferences */
91enum resource_type {
92 STRING = 0,
93 INTEGER = 1,
94 FLOAT = 2
95};
96
97typedef struct {
98 char *name;
99 enum resource_type type;
100 void *dst;
101} ResourcePref;
102
103
104/* function declarations */
105static void buttonpress(const XEvent *e);
106static void cleanup(void);
107static void clientmessage(const XEvent *e);
108static void config_init(void);
109static void configurenotify(const XEvent *e);
110static void configurerequest(const XEvent *e);
111static void createnotify(const XEvent *e);
112static void destroynotify(const XEvent *e);
113static void die(const char *errstr, ...);
114static void drawbar(void);
115static void drawtext(const char *text, XftColor col[ColLast]);
116static void *ecalloc(size_t n, size_t size);
117static void *erealloc(void *o, size_t size);
118static void expose(const XEvent *e);
119static void focus(int c);
120static void focusin(const XEvent *e);
121static void focusonce(const Arg *arg);
122static void focusurgent(const Arg *arg);
123static void fullscreen(const Arg *arg);
124static char *getatom(int a);
125static int getclient(Window w);
126static XftColor getcolor(const char *colstr);
127static int getfirsttab(void);
128static Bool gettextprop(Window w, Atom atom, char *text, unsigned int size);
129static void initfont(const char *fontstr);
130static Bool isprotodel(int c);
131static void keypress(const XEvent *e);
132static void killclient(const Arg *arg);
133static void manage(Window win);
134static void maprequest(const XEvent *e);
135static void move(const Arg *arg);
136static void movetab(const Arg *arg);
137static void propertynotify(const XEvent *e);
138static void resize(int c, int w, int h);
139static int resource_load(XrmDatabase db, char *name, enum resource_type rtype, void *dst);
140static void rotate(const Arg *arg);
141static void run(void);
142static void sendxembed(int c, long msg, long detail, long d1, long d2);
143static void setcmd(int argc, char *argv[], int);
144static void setup(void);
145static void sigchld(int unused);
146static void spawn(const Arg *arg);
147static int textnw(const char *text, unsigned int len);
148static void toggle(const Arg *arg);
149static void unmanage(int c);
150static void unmapnotify(const XEvent *e);
151static void updatenumlockmask(void);
152static void updatetitle(int c);
153static int xerror(Display *dpy, XErrorEvent *ee);
154static void xsettitle(Window w, const char *str);
155
156/* variables */
157static int screen;
158static void (*handler[LASTEvent]) (const XEvent *) = {
159 [ButtonPress] = buttonpress,
160 [ClientMessage] = clientmessage,
161 [ConfigureNotify] = configurenotify,
162 [ConfigureRequest] = configurerequest,
163 [CreateNotify] = createnotify,
164 [UnmapNotify] = unmapnotify,
165 [DestroyNotify] = destroynotify,
166 [Expose] = expose,
167 [FocusIn] = focusin,
168 [KeyPress] = keypress,
169 [MapRequest] = maprequest,
170 [PropertyNotify] = propertynotify,
171};
172static int bh, wx, wy, ww, wh, vbh;
173static unsigned int numlockmask;
174static Bool running = True, nextfocus, doinitspawn = True,
175 fillagain = False, closelastclient = False,
176 killclientsfirst = False;
177static Display *dpy;
178static DC dc;
179static Atom wmatom[WMLast];
180static Window root, win;
181static Client **clients;
182static int nclients, sel = -1, lastsel = -1;
183static int (*xerrorxlib)(Display *, XErrorEvent *);
184static int cmd_append_pos;
185static char winid[64];
186static char **cmd;
187static char *wmname = "tabbed";
188static const char *geometry;
189
190static Colormap cmap;
191static Visual *visual = NULL;
192
193char *argv0;
194
195/* configuration, allows nested code to access above variables */
196#include "config.h"
197
198void
199buttonpress(const XEvent *e)
200{
201 const XButtonPressedEvent *ev = &e->xbutton;
202 int i, fc;
203 Arg arg;
204
205 if (ev->y < 0 || ev->y > bh)
206 return;
207
208 if (((fc = getfirsttab()) > 0 && ev->x < TEXTW(before)) || ev->x < 0)
209 return;
210
211 for (i = fc; i < nclients; i++) {
212 if (clients[i]->tabx > ev->x) {
213 switch (ev->button) {
214 case Button1:
215 focus(i);
216 break;
217 case Button2:
218 focus(i);
219 killclient(NULL);
220 break;
221 case Button4: /* FALLTHROUGH */
222 case Button5:
223 arg.i = ev->button == Button4 ? -1 : 1;
224 rotate(&arg);
225 break;
226 }
227 break;
228 }
229 }
230}
231
232void
233cleanup(void)
234{
235 int i;
236
237 for (i = 0; i < nclients; i++) {
238 focus(i);
239 killclient(NULL);
240 XReparentWindow(dpy, clients[i]->win, root, 0, 0);
241 unmanage(i);
242 }
243 free(clients);
244 clients = NULL;
245
246 XFreePixmap(dpy, dc.drawable);
247 XFreeGC(dpy, dc.gc);
248 XDestroyWindow(dpy, win);
249 XSync(dpy, False);
250 free(cmd);
251}
252
253void
254clientmessage(const XEvent *e)
255{
256 const XClientMessageEvent *ev = &e->xclient;
257
258 if (ev->message_type == wmatom[WMProtocols] &&
259 ev->data.l[0] == wmatom[WMDelete]) {
260 if (nclients > 1 && killclientsfirst) {
261 killclient(0);
262 return;
263 }
264 running = False;
265 }
266}
267
268void
269config_init(void)
270{
271 char *resm;
272 XrmDatabase db;
273 ResourcePref *p;
274
275 XrmInitialize();
276 resm = XResourceManagerString(dpy);
277 if (!resm)
278 return;
279
280 db = XrmGetStringDatabase(resm);
281 for (p = resources; p < resources + LENGTH(resources); p++)
282 resource_load(db, p->name, p->type, p->dst);
283}
284
285void
286configurenotify(const XEvent *e)
287{
288 int obh;
289 const XConfigureEvent *ev = &e->xconfigure;
290
291 if (ev->window == win && (ev->width != ww || ev->height != wh)) {
292 ww = ev->width;
293 wh = ev->height;
294 XFreePixmap(dpy, dc.drawable);
295 dc.drawable = XCreatePixmap(dpy, win, ww, wh,
296 32);
297
298 if (!obh && (wh <= bh)) {
299 obh = bh;
300 bh = 0;
301 } else if (!bh && (wh > obh)) {
302 bh = obh;
303 obh = 0;
304 }
305
306 if (sel > -1)
307 resize(sel, ww, wh - bh);
308 XSync(dpy, False);
309 }
310}
311
312void
313configurerequest(const XEvent *e)
314{
315 const XConfigureRequestEvent *ev = &e->xconfigurerequest;
316 XWindowChanges wc;
317 int c;
318
319 if ((c = getclient(ev->window)) > -1) {
320 wc.x = 0;
321 wc.y = bh;
322 wc.width = ww;
323 wc.height = wh - bh;
324 wc.border_width = 0;
325 wc.sibling = ev->above;
326 wc.stack_mode = ev->detail;
327 XConfigureWindow(dpy, clients[c]->win, ev->value_mask, &wc);
328 }
329}
330
331void
332createnotify(const XEvent *e)
333{
334 const XCreateWindowEvent *ev = &e->xcreatewindow;
335
336 if (ev->window != win && getclient(ev->window) < 0)
337 manage(ev->window);
338}
339
340void
341destroynotify(const XEvent *e)
342{
343 const XDestroyWindowEvent *ev = &e->xdestroywindow;
344 int c;
345
346 if ((c = getclient(ev->window)) > -1)
347 unmanage(c);
348}
349
350void
351die(const char *errstr, ...)
352{
353 va_list ap;
354
355 va_start(ap, errstr);
356 vfprintf(stderr, errstr, ap);
357 va_end(ap);
358 exit(EXIT_FAILURE);
359}
360
361void
362drawbar(void)
363{
364 XftColor *col;
365 int c, cc, fc, width, n = 0, nbh, i;
366 char *name = NULL;
367 char tabtitle[256];
368
369 if (nclients == 0) {
370 dc.x = 0;
371 dc.w = ww;
372 XFetchName(dpy, win, &name);
373 drawtext(name ? name : "", dc.norm);
374 XCopyArea(dpy, dc.drawable, win, dc.gc, 0, 0, ww, vbh, 0, 0);
375 XSync(dpy, False);
376
377 return;
378 }
379
380 nbh = nclients > 1 ? vbh : 0;
381 if (bh != nbh) {
382 bh = nbh;
383 for (i = 0; i < nclients; i++)
384 XMoveResizeWindow(dpy, clients[i]->win, 0, bh, ww, wh - bh);
385 }
386 if (bh == 0)
387 return;
388
389 width = ww;
390 cc = ww / tabwidth;
391 if (nclients > cc)
392 cc = (ww - TEXTW(before) - TEXTW(after)) / tabwidth;
393
394 if ((fc = getfirsttab()) + cc < nclients) {
395 dc.w = TEXTW(after);
396 dc.x = width - dc.w;
397 drawtext(after, dc.sel);
398 width -= dc.w;
399 }
400 dc.x = 0;
401
402 if (fc > 0) {
403 dc.w = TEXTW(before);
404 drawtext(before, dc.sel);
405 dc.x += dc.w;
406 width -= dc.w;
407 }
408
409 cc = MIN(cc, nclients);
410 for (c = fc; c < fc + cc; c++) {
411 dc.w = width / cc;
412 if (c == sel) {
413 col = dc.sel;
414 dc.w += width % cc;
415 } else {
416 col = clients[c]->urgent ? dc.urg : dc.norm;
417 }
418 snprintf(tabtitle, sizeof(tabtitle), "%d: %s",
419 c + 1, clients[c]->name);
420 drawtext(tabtitle, col);
421 dc.x += dc.w;
422 clients[c]->tabx = dc.x;
423 }
424 XCopyArea(dpy, dc.drawable, win, dc.gc, 0, 0, ww, bh, 0, 0);
425 XSync(dpy, False);
426}
427
428void
429drawtext(const char *text, XftColor col[ColLast])
430{
431 int i, j, x, y, h, len, olen;
432 char buf[256];
433 XftDraw *d;
434 XRectangle r = { dc.x, dc.y, dc.w, dc.h };
435
436 XSetForeground(dpy, dc.gc, col[ColBG].pixel);
437 XFillRectangles(dpy, dc.drawable, dc.gc, &r, 1);
438 if (!text)
439 return;
440
441 olen = strlen(text);
442 h = dc.font.ascent + dc.font.descent;
443 y = dc.y + (dc.h / 2) - (h / 2) + dc.font.ascent;
444 x = dc.x + (h / 2);
445
446 /* shorten text if necessary */
447 for (len = MIN(olen, sizeof(buf));
448 len && textnw(text, len) > dc.w - h; len--);
449
450 if (!len)
451 return;
452
453 memcpy(buf, text, len);
454 if (len < olen) {
455 for (i = len, j = strlen(titletrim); j && i;
456 buf[--i] = titletrim[--j])
457 ;
458 }
459
460 d = XftDrawCreate(dpy, dc.drawable, visual, cmap);
461 XftDrawStringUtf8(d, &col[ColFG], dc.font.xfont, x, y, (XftChar8 *) buf, len);
462 XftDrawDestroy(d);
463}
464
465void *
466ecalloc(size_t n, size_t size)
467{
468 void *p;
469
470 if (!(p = calloc(n, size)))
471 die("%s: cannot calloc\n", argv0);
472 return p;
473}
474
475void *
476erealloc(void *o, size_t size)
477{
478 void *p;
479
480 if (!(p = realloc(o, size)))
481 die("%s: cannot realloc\n", argv0);
482 return p;
483}
484
485void
486expose(const XEvent *e)
487{
488 const XExposeEvent *ev = &e->xexpose;
489
490 if (ev->count == 0 && win == ev->window)
491 drawbar();
492}
493
494void
495focus(int c)
496{
497 char buf[BUFSIZ] = "tabbed-"VERSION" ::";
498 size_t i, n;
499 XWMHints* wmh;
500
501 /* If c, sel and clients are -1, raise tabbed-win itself */
502 if (nclients == 0) {
503 cmd[cmd_append_pos] = NULL;
504 for(i = 0, n = strlen(buf); cmd[i] && n < sizeof(buf); i++)
505 n += snprintf(&buf[n], sizeof(buf) - n, " %s", cmd[i]);
506
507 xsettitle(win, buf);
508 XRaiseWindow(dpy, win);
509
510 return;
511 }
512
513 if (c < 0 || c >= nclients)
514 return;
515
516 resize(c, ww, wh - bh);
517 XRaiseWindow(dpy, clients[c]->win);
518 XSetInputFocus(dpy, clients[c]->win, RevertToParent, CurrentTime);
519 sendxembed(c, XEMBED_FOCUS_IN, XEMBED_FOCUS_CURRENT, 0, 0);
520 sendxembed(c, XEMBED_WINDOW_ACTIVATE, 0, 0, 0);
521 xsettitle(win, clients[c]->name);
522
523 if (sel != c) {
524 lastsel = sel;
525 sel = c;
526 }
527
528 if (clients[c]->urgent && (wmh = XGetWMHints(dpy, clients[c]->win))) {
529 wmh->flags &= ~XUrgencyHint;
530 XSetWMHints(dpy, clients[c]->win, wmh);
531 clients[c]->urgent = False;
532 XFree(wmh);
533 }
534
535 drawbar();
536 XSync(dpy, False);
537}
538
539void
540focusin(const XEvent *e)
541{
542 const XFocusChangeEvent *ev = &e->xfocus;
543 int dummy;
544 Window focused;
545
546 if (ev->mode != NotifyUngrab) {
547 XGetInputFocus(dpy, &focused, &dummy);
548 if (focused == win)
549 focus(sel);
550 }
551}
552
553void
554focusonce(const Arg *arg)
555{
556 nextfocus = True;
557}
558
559void
560focusurgent(const Arg *arg)
561{
562 int c;
563
564 if (sel < 0)
565 return;
566
567 for (c = (sel + 1) % nclients; c != sel; c = (c + 1) % nclients) {
568 if (clients[c]->urgent) {
569 focus(c);
570 return;
571 }
572 }
573}
574
575void
576fullscreen(const Arg *arg)
577{
578 XEvent e;
579
580 e.type = ClientMessage;
581 e.xclient.window = win;
582 e.xclient.message_type = wmatom[WMState];
583 e.xclient.format = 32;
584 e.xclient.data.l[0] = 2;
585 e.xclient.data.l[1] = wmatom[WMFullscreen];
586 e.xclient.data.l[2] = 0;
587 XSendEvent(dpy, root, False, SubstructureNotifyMask, &e);
588}
589
590char *
591getatom(int a)
592{
593 static char buf[BUFSIZ];
594 Atom adummy;
595 int idummy;
596 unsigned long ldummy;
597 unsigned char *p = NULL;
598
599 XGetWindowProperty(dpy, win, wmatom[a], 0L, BUFSIZ, False, XA_STRING,
600 &adummy, &idummy, &ldummy, &ldummy, &p);
601 if (p)
602 strncpy(buf, (char *)p, LENGTH(buf)-1);
603 else
604 buf[0] = '\0';
605 XFree(p);
606
607 return buf;
608}
609
610int
611getclient(Window w)
612{
613 int i;
614
615 for (i = 0; i < nclients; i++) {
616 if (clients[i]->win == w)
617 return i;
618 }
619
620 return -1;
621}
622
623XftColor
624getcolor(const char *colstr)
625{
626 XftColor color;
627
628 if (!XftColorAllocName(dpy, visual, cmap, colstr, &color))
629 die("%s: cannot allocate color '%s'\n", argv0, colstr);
630
631 return color;
632}
633
634int
635getfirsttab(void)
636{
637 int cc, ret;
638
639 if (sel < 0)
640 return 0;
641
642 cc = ww / tabwidth;
643 if (nclients > cc)
644 cc = (ww - TEXTW(before) - TEXTW(after)) / tabwidth;
645
646 ret = sel - cc / 2 + (cc + 1) % 2;
647 return ret < 0 ? 0 :
648 ret + cc > nclients ? MAX(0, nclients - cc) :
649 ret;
650}
651
652Bool
653gettextprop(Window w, Atom atom, char *text, unsigned int size)
654{
655 char **list = NULL;
656 int n;
657 XTextProperty name;
658
659 if (!text || size == 0)
660 return False;
661
662 text[0] = '\0';
663 XGetTextProperty(dpy, w, &name, atom);
664 if (!name.nitems)
665 return False;
666
667 if (name.encoding == XA_STRING) {
668 strncpy(text, (char *)name.value, size - 1);
669 } else if (XmbTextPropertyToTextList(dpy, &name, &list, &n) >= Success
670 && n > 0 && *list) {
671 strncpy(text, *list, size - 1);
672 XFreeStringList(list);
673 }
674 text[size - 1] = '\0';
675 XFree(name.value);
676
677 return True;
678}
679
680void
681initfont(const char *fontstr)
682{
683 if (!(dc.font.xfont = XftFontOpenName(dpy, screen, fontstr))
684 && !(dc.font.xfont = XftFontOpenName(dpy, screen, "fixed")))
685 die("error, cannot load font: '%s'\n", fontstr);
686
687 dc.font.ascent = dc.font.xfont->ascent;
688 dc.font.descent = dc.font.xfont->descent;
689 dc.font.height = dc.font.ascent + dc.font.descent;
690}
691
692Bool
693isprotodel(int c)
694{
695 int i, n;
696 Atom *protocols;
697 Bool ret = False;
698
699 if (XGetWMProtocols(dpy, clients[c]->win, &protocols, &n)) {
700 for (i = 0; !ret && i < n; i++) {
701 if (protocols[i] == wmatom[WMDelete])
702 ret = True;
703 }
704 XFree(protocols);
705 }
706
707 return ret;
708}
709
710void
711keypress(const XEvent *e)
712{
713 const XKeyEvent *ev = &e->xkey;
714 unsigned int i;
715 KeySym keysym;
716
717 keysym = XkbKeycodeToKeysym(dpy, (KeyCode)ev->keycode, 0, 0);
718 for (i = 0; i < LENGTH(keys); i++) {
719 if (keysym == keys[i].keysym &&
720 CLEANMASK(keys[i].mod) == CLEANMASK(ev->state) &&
721 keys[i].func)
722 keys[i].func(&(keys[i].arg));
723 }
724}
725
726void
727killclient(const Arg *arg)
728{
729 XEvent ev;
730
731 if (sel < 0)
732 return;
733
734 if (isprotodel(sel) && !clients[sel]->closed) {
735 ev.type = ClientMessage;
736 ev.xclient.window = clients[sel]->win;
737 ev.xclient.message_type = wmatom[WMProtocols];
738 ev.xclient.format = 32;
739 ev.xclient.data.l[0] = wmatom[WMDelete];
740 ev.xclient.data.l[1] = CurrentTime;
741 XSendEvent(dpy, clients[sel]->win, False, NoEventMask, &ev);
742 clients[sel]->closed = True;
743 } else {
744 XKillClient(dpy, clients[sel]->win);
745 }
746}
747
748void
749manage(Window w)
750{
751 updatenumlockmask();
752 {
753 int i, j, nextpos;
754 unsigned int modifiers[] = { 0, LockMask, numlockmask,
755 numlockmask | LockMask };
756 KeyCode code;
757 Client *c;
758 XEvent e;
759
760 XWithdrawWindow(dpy, w, 0);
761 XReparentWindow(dpy, w, win, 0, bh);
762 XSelectInput(dpy, w, PropertyChangeMask |
763 StructureNotifyMask | EnterWindowMask);
764 XSync(dpy, False);
765
766 for (i = 0; i < LENGTH(keys); i++) {
767 if ((code = XKeysymToKeycode(dpy, keys[i].keysym))) {
768 for (j = 0; j < LENGTH(modifiers); j++) {
769 XGrabKey(dpy, code, keys[i].mod |
770 modifiers[j], w, True,
771 GrabModeAsync, GrabModeAsync);
772 }
773 }
774 }
775
776 c = ecalloc(1, sizeof *c);
777 c->win = w;
778
779 nclients++;
780 clients = erealloc(clients, sizeof(Client *) * nclients);
781
782 if(npisrelative) {
783 nextpos = sel + newposition;
784 } else {
785 if (newposition < 0)
786 nextpos = nclients - newposition;
787 else
788 nextpos = newposition;
789 }
790 if (nextpos >= nclients)
791 nextpos = nclients - 1;
792 if (nextpos < 0)
793 nextpos = 0;
794
795 if (nclients > 1 && nextpos < nclients - 1)
796 memmove(&clients[nextpos + 1], &clients[nextpos],
797 sizeof(Client *) * (nclients - nextpos - 1));
798
799 clients[nextpos] = c;
800 updatetitle(nextpos);
801
802 XLowerWindow(dpy, w);
803 XMapWindow(dpy, w);
804
805 e.xclient.window = w;
806 e.xclient.type = ClientMessage;
807 e.xclient.message_type = wmatom[XEmbed];
808 e.xclient.format = 32;
809 e.xclient.data.l[0] = CurrentTime;
810 e.xclient.data.l[1] = XEMBED_EMBEDDED_NOTIFY;
811 e.xclient.data.l[2] = 0;
812 e.xclient.data.l[3] = win;
813 e.xclient.data.l[4] = 0;
814 XSendEvent(dpy, root, False, NoEventMask, &e);
815
816 XSync(dpy, False);
817
818 /* Adjust sel before focus does set it to lastsel. */
819 if (sel >= nextpos)
820 sel++;
821 focus(nextfocus ? nextpos :
822 sel < 0 ? 0 :
823 sel);
824 nextfocus = foreground;
825 }
826}
827
828void
829maprequest(const XEvent *e)
830{
831 const XMapRequestEvent *ev = &e->xmaprequest;
832
833 if (getclient(ev->window) < 0)
834 manage(ev->window);
835}
836
837void
838move(const Arg *arg)
839{
840 if (arg->i >= 0 && arg->i < nclients)
841 focus(arg->i);
842}
843
844void
845movetab(const Arg *arg)
846{
847 int c;
848 Client *new;
849
850 if (sel < 0)
851 return;
852
853 c = (sel + arg->i) % nclients;
854 if (c < 0)
855 c += nclients;
856
857 if (c == sel)
858 return;
859
860 new = clients[sel];
861 if (sel < c)
862 memmove(&clients[sel], &clients[sel+1],
863 sizeof(Client *) * (c - sel));
864 else
865 memmove(&clients[c+1], &clients[c],
866 sizeof(Client *) * (sel - c));
867 clients[c] = new;
868 sel = c;
869
870 drawbar();
871}
872
873void
874propertynotify(const XEvent *e)
875{
876 const XPropertyEvent *ev = &e->xproperty;
877 XWMHints *wmh;
878 int c;
879 char* selection = NULL;
880 Arg arg;
881
882 if (ev->state == PropertyNewValue && ev->atom == wmatom[WMSelectTab]) {
883 selection = getatom(WMSelectTab);
884 if (!strncmp(selection, "0x", 2)) {
885 arg.i = getclient(strtoul(selection, NULL, 0));
886 move(&arg);
887 } else {
888 cmd[cmd_append_pos] = selection;
889 arg.v = cmd;
890 spawn(&arg);
891 }
892 } else if (ev->state == PropertyNewValue && ev->atom == XA_WM_HINTS &&
893 (c = getclient(ev->window)) > -1 &&
894 (wmh = XGetWMHints(dpy, clients[c]->win))) {
895 if (wmh->flags & XUrgencyHint) {
896 XFree(wmh);
897 wmh = XGetWMHints(dpy, win);
898 if (c != sel) {
899 if (urgentswitch && wmh &&
900 !(wmh->flags & XUrgencyHint)) {
901 /* only switch, if tabbed was focused
902 * since last urgency hint if WMHints
903 * could not be received,
904 * default to no switch */
905 focus(c);
906 } else {
907 /* if no switch should be performed,
908 * mark tab as urgent */
909 clients[c]->urgent = True;
910 drawbar();
911 }
912 }
913 if (wmh && !(wmh->flags & XUrgencyHint)) {
914 /* update tabbed urgency hint
915 * if not set already */
916 wmh->flags |= XUrgencyHint;
917 XSetWMHints(dpy, win, wmh);
918 }
919 }
920 XFree(wmh);
921 } else if (ev->state != PropertyDelete && ev->atom == XA_WM_NAME &&
922 (c = getclient(ev->window)) > -1) {
923 updatetitle(c);
924 }
925}
926
927void
928resize(int c, int w, int h)
929{
930 XConfigureEvent ce;
931 XWindowChanges wc;
932
933 ce.x = 0;
934 ce.y = wc.y = bh;
935 ce.width = wc.width = w;
936 ce.height = wc.height = h;
937 ce.type = ConfigureNotify;
938 ce.display = dpy;
939 ce.event = clients[c]->win;
940 ce.window = clients[c]->win;
941 ce.above = None;
942 ce.override_redirect = False;
943 ce.border_width = 0;
944
945 XConfigureWindow(dpy, clients[c]->win, CWY | CWWidth | CWHeight, &wc);
946 XSendEvent(dpy, clients[c]->win, False, StructureNotifyMask,
947 (XEvent *)&ce);
948}
949
950int
951resource_load(XrmDatabase db, char *name, enum resource_type rtype, void *dst)
952{
953 char **sdst = dst;
954 int *idst = dst;
955 float *fdst = dst;
956
957 char fullname[256];
958 char fullclass[256];
959 char *type;
960 XrmValue ret;
961
962 snprintf(fullname, sizeof(fullname), "%s.%s", "tabbed", name);
963 snprintf(fullclass, sizeof(fullclass), "%s.%s", "tabbed", name);
964 fullname[sizeof(fullname) - 1] = fullclass[sizeof(fullclass) - 1] = '\0';
965
966 XrmGetResource(db, fullname, fullclass, &type, &ret);
967 if (ret.addr == NULL || strncmp("String", type, 64))
968 return 1;
969
970 switch (rtype) {
971 case STRING:
972 *sdst = ret.addr;
973 break;
974 case INTEGER:
975 *idst = strtoul(ret.addr, NULL, 10);
976 break;
977 case FLOAT:
978 *fdst = strtof(ret.addr, NULL);
979 break;
980 }
981 return 0;
982}
983
984void
985rotate(const Arg *arg)
986{
987 int nsel = -1;
988
989 if (sel < 0)
990 return;
991
992 if (arg->i == 0) {
993 if (lastsel > -1)
994 focus(lastsel);
995 } else if (sel > -1) {
996 /* Rotating in an arg->i step around the clients. */
997 nsel = sel + arg->i;
998 while (nsel >= nclients)
999 nsel -= nclients;
1000 while (nsel < 0)
1001 nsel += nclients;
1002 focus(nsel);
1003 }
1004}
1005
1006void
1007run(void)
1008{
1009 XEvent ev;
1010
1011 /* main event loop */
1012 XSync(dpy, False);
1013 drawbar();
1014 if (doinitspawn == True)
1015 spawn(NULL);
1016
1017 while (running) {
1018 XNextEvent(dpy, &ev);
1019 if (handler[ev.type])
1020 (handler[ev.type])(&ev); /* call handler */
1021 }
1022}
1023
1024void
1025sendxembed(int c, long msg, long detail, long d1, long d2)
1026{
1027 XEvent e = { 0 };
1028
1029 e.xclient.window = clients[c]->win;
1030 e.xclient.type = ClientMessage;
1031 e.xclient.message_type = wmatom[XEmbed];
1032 e.xclient.format = 32;
1033 e.xclient.data.l[0] = CurrentTime;
1034 e.xclient.data.l[1] = msg;
1035 e.xclient.data.l[2] = detail;
1036 e.xclient.data.l[3] = d1;
1037 e.xclient.data.l[4] = d2;
1038 XSendEvent(dpy, clients[c]->win, False, NoEventMask, &e);
1039}
1040
1041void
1042setcmd(int argc, char *argv[], int replace)
1043{
1044 int i;
1045
1046 cmd = ecalloc(argc + 3, sizeof(*cmd));
1047 if (argc == 0)
1048 return;
1049 for (i = 0; i < argc; i++)
1050 cmd[i] = argv[i];
1051 cmd[replace > 0 ? replace : argc] = winid;
1052 cmd_append_pos = argc + !replace;
1053 cmd[cmd_append_pos] = cmd[cmd_append_pos + 1] = NULL;
1054}
1055
1056void
1057setup(void)
1058{
1059 int bitm, tx, ty, tw, th, dh, dw, isfixed;
1060 XWMHints *wmh;
1061 XClassHint class_hint;
1062 XSizeHints *size_hint;
1063
1064 /* clean up any zombies immediately */
1065 sigchld(0);
1066
1067 /* init screen */
1068 screen = DefaultScreen(dpy);
1069 root = RootWindow(dpy, screen);
1070 initfont(font);
1071 vbh = dc.h = dc.font.height + 2;
1072
1073 /* init atoms */
1074 wmatom[WMDelete] = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
1075 wmatom[WMFullscreen] = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN",
1076 False);
1077 wmatom[WMName] = XInternAtom(dpy, "_NET_WM_NAME", False);
1078 wmatom[WMProtocols] = XInternAtom(dpy, "WM_PROTOCOLS", False);
1079 wmatom[WMSelectTab] = XInternAtom(dpy, "_TABBED_SELECT_TAB", False);
1080 wmatom[WMState] = XInternAtom(dpy, "_NET_WM_STATE", False);
1081 wmatom[XEmbed] = XInternAtom(dpy, "_XEMBED", False);
1082
1083 /* init appearance */
1084 wx = 0;
1085 wy = 0;
1086 ww = 800;
1087 wh = 600;
1088 isfixed = 0;
1089
1090 if (geometry) {
1091 tx = ty = tw = th = 0;
1092 bitm = XParseGeometry(geometry, &tx, &ty, (unsigned *)&tw,
1093 (unsigned *)&th);
1094 if (bitm & XValue)
1095 wx = tx;
1096 if (bitm & YValue)
1097 wy = ty;
1098 if (bitm & WidthValue)
1099 ww = tw;
1100 if (bitm & HeightValue)
1101 wh = th;
1102 if (bitm & XNegative && wx == 0)
1103 wx = -1;
1104 if (bitm & YNegative && wy == 0)
1105 wy = -1;
1106 if (bitm & (HeightValue | WidthValue))
1107 isfixed = 1;
1108
1109 dw = DisplayWidth(dpy, screen);
1110 dh = DisplayHeight(dpy, screen);
1111 if (wx < 0)
1112 wx = dw + wx - ww - 1;
1113 if (wy < 0)
1114 wy = dh + wy - wh - 1;
1115 }
1116
1117 XVisualInfo *vis;
1118 XRenderPictFormat *fmt;
1119 int nvi;
1120 int i;
1121
1122 XVisualInfo tpl = {
1123 .screen = screen,
1124 .depth = 32,
1125 .class = TrueColor
1126 };
1127
1128 vis = XGetVisualInfo(dpy, VisualScreenMask | VisualDepthMask | VisualClassMask, &tpl, &nvi);
1129 for(i = 0; i < nvi; i ++) {
1130 fmt = XRenderFindVisualFormat(dpy, vis[i].visual);
1131 if (fmt->type == PictTypeDirect && fmt->direct.alphaMask) {
1132 visual = vis[i].visual;
1133 break;
1134 }
1135 }
1136
1137 XFree(vis);
1138
1139 if (! visual) {
1140 fprintf(stderr, "Couldn't find ARGB visual.\n");
1141 exit(1);
1142 }
1143
1144 cmap = XCreateColormap( dpy, root, visual, None);
1145 dc.norm[ColBG] = getcolor(normbgcolor);
1146 dc.norm[ColFG] = getcolor(normfgcolor);
1147 dc.sel[ColBG] = getcolor(selbgcolor);
1148 dc.sel[ColFG] = getcolor(selfgcolor);
1149 dc.urg[ColBG] = getcolor(urgbgcolor);
1150 dc.urg[ColFG] = getcolor(urgfgcolor);
1151
1152 XSetWindowAttributes attrs;
1153 attrs.background_pixel = dc.norm[ColBG].pixel;
1154 attrs.border_pixel = dc.norm[ColFG].pixel;
1155 attrs.bit_gravity = NorthWestGravity;
1156 attrs.event_mask = FocusChangeMask | KeyPressMask
1157 | ExposureMask | VisibilityChangeMask | StructureNotifyMask
1158 | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask;
1159 attrs.background_pixmap = None ;
1160 attrs.colormap = cmap;
1161
1162 win = XCreateWindow(dpy, root, wx, wy,
1163 ww, wh, 0, 32, InputOutput,
1164 visual, CWBackPixmap | CWBorderPixel | CWBitGravity
1165 | CWEventMask | CWColormap, &attrs);
1166
1167 dc.drawable = XCreatePixmap(dpy, win, ww, wh,
1168 32);
1169 dc.gc = XCreateGC(dpy, dc.drawable, 0, 0);
1170
1171 XMapRaised(dpy, win);
1172 XSelectInput(dpy, win, SubstructureNotifyMask | FocusChangeMask |
1173 ButtonPressMask | ExposureMask | KeyPressMask |
1174 PropertyChangeMask | StructureNotifyMask |
1175 SubstructureRedirectMask);
1176 xerrorxlib = XSetErrorHandler(xerror);
1177
1178 class_hint.res_name = wmname;
1179 class_hint.res_class = "tabbed";
1180 XSetClassHint(dpy, win, &class_hint);
1181
1182 size_hint = XAllocSizeHints();
1183 if (!isfixed) {
1184 size_hint->flags = PSize | PMinSize;
1185 size_hint->height = wh;
1186 size_hint->width = ww;
1187 size_hint->min_height = bh + 1;
1188 } else {
1189 size_hint->flags = PMaxSize | PMinSize;
1190 size_hint->min_width = size_hint->max_width = ww;
1191 size_hint->min_height = size_hint->max_height = wh;
1192 }
1193 wmh = XAllocWMHints();
1194 XSetWMProperties(dpy, win, NULL, NULL, NULL, 0, size_hint, wmh, NULL);
1195 XFree(size_hint);
1196 XFree(wmh);
1197
1198 XSetWMProtocols(dpy, win, &wmatom[WMDelete], 1);
1199
1200 snprintf(winid, sizeof(winid), "%lu", win);
1201 setenv("XEMBED", winid, 1);
1202
1203 nextfocus = foreground;
1204 focus(-1);
1205}
1206
1207void
1208sigchld(int unused)
1209{
1210 if (signal(SIGCHLD, sigchld) == SIG_ERR)
1211 die("%s: cannot install SIGCHLD handler", argv0);
1212
1213 while (0 < waitpid(-1, NULL, WNOHANG));
1214}
1215
1216void
1217spawn(const Arg *arg)
1218{
1219 if (fork() == 0) {
1220 if(dpy)
1221 close(ConnectionNumber(dpy));
1222
1223 setsid();
1224 if (arg && arg->v) {
1225 execvp(((char **)arg->v)[0], (char **)arg->v);
1226 fprintf(stderr, "%s: execvp %s", argv0,
1227 ((char **)arg->v)[0]);
1228 } else {
1229 cmd[cmd_append_pos] = NULL;
1230 execvp(cmd[0], cmd);
1231 fprintf(stderr, "%s: execvp %s", argv0, cmd[0]);
1232 }
1233 perror(" failed");
1234 exit(0);
1235 }
1236}
1237
1238int
1239textnw(const char *text, unsigned int len)
1240{
1241 XGlyphInfo ext;
1242 XftTextExtentsUtf8(dpy, dc.font.xfont, (XftChar8 *) text, len, &ext);
1243 return ext.xOff;
1244}
1245
1246void
1247toggle(const Arg *arg)
1248{
1249 *(Bool*) arg->v = !*(Bool*) arg->v;
1250}
1251
1252void
1253unmanage(int c)
1254{
1255 if (c < 0 || c >= nclients) {
1256 drawbar();
1257 XSync(dpy, False);
1258 return;
1259 }
1260
1261 if (!nclients)
1262 return;
1263
1264 if (c == 0) {
1265 /* First client. */
1266 nclients--;
1267 free(clients[0]);
1268 memmove(&clients[0], &clients[1], sizeof(Client *) * nclients);
1269 } else if (c == nclients - 1) {
1270 /* Last client. */
1271 nclients--;
1272 free(clients[c]);
1273 clients = erealloc(clients, sizeof(Client *) * nclients);
1274 } else {
1275 /* Somewhere inbetween. */
1276 free(clients[c]);
1277 memmove(&clients[c], &clients[c+1],
1278 sizeof(Client *) * (nclients - (c + 1)));
1279 nclients--;
1280 }
1281
1282 if (nclients <= 0) {
1283 lastsel = sel = -1;
1284
1285 if (closelastclient)
1286 running = False;
1287 else if (fillagain && running)
1288 spawn(NULL);
1289 } else {
1290 if (lastsel >= nclients)
1291 lastsel = nclients - 1;
1292 else if (lastsel > c)
1293 lastsel--;
1294
1295 if (c == sel && lastsel >= 0) {
1296 focus(lastsel);
1297 } else {
1298 if (sel > c)
1299 sel--;
1300 if (sel >= nclients)
1301 sel = nclients - 1;
1302
1303 focus(sel);
1304 }
1305 }
1306
1307 drawbar();
1308 XSync(dpy, False);
1309}
1310
1311void
1312unmapnotify(const XEvent *e)
1313{
1314 const XUnmapEvent *ev = &e->xunmap;
1315 int c;
1316
1317 if ((c = getclient(ev->window)) > -1)
1318 unmanage(c);
1319}
1320
1321void
1322updatenumlockmask(void)
1323{
1324 unsigned int i, j;
1325 XModifierKeymap *modmap;
1326
1327 numlockmask = 0;
1328 modmap = XGetModifierMapping(dpy);
1329 for (i = 0; i < 8; i++) {
1330 for (j = 0; j < modmap->max_keypermod; j++) {
1331 if (modmap->modifiermap[i * modmap->max_keypermod + j]
1332 == XKeysymToKeycode(dpy, XK_Num_Lock))
1333 numlockmask = (1 << i);
1334 }
1335 }
1336 XFreeModifiermap(modmap);
1337}
1338
1339void
1340updatetitle(int c)
1341{
1342 if (!gettextprop(clients[c]->win, wmatom[WMName], clients[c]->name,
1343 sizeof(clients[c]->name)))
1344 gettextprop(clients[c]->win, XA_WM_NAME, clients[c]->name,
1345 sizeof(clients[c]->name));
1346 if (sel == c)
1347 xsettitle(win, clients[c]->name);
1348 drawbar();
1349}
1350
1351/* There's no way to check accesses to destroyed windows, thus those cases are
1352 * ignored (especially on UnmapNotify's). Other types of errors call Xlibs
1353 * default error handler, which may call exit. */
1354int
1355xerror(Display *dpy, XErrorEvent *ee)
1356{
1357 if (ee->error_code == BadWindow
1358 || (ee->request_code == X_SetInputFocus &&
1359 ee->error_code == BadMatch)
1360 || (ee->request_code == X_PolyText8 &&
1361 ee->error_code == BadDrawable)
1362 || (ee->request_code == X_PolyFillRectangle &&
1363 ee->error_code == BadDrawable)
1364 || (ee->request_code == X_PolySegment &&
1365 ee->error_code == BadDrawable)
1366 || (ee->request_code == X_ConfigureWindow &&
1367 ee->error_code == BadMatch)
1368 || (ee->request_code == X_GrabButton &&
1369 ee->error_code == BadAccess)
1370 || (ee->request_code == X_GrabKey &&
1371 ee->error_code == BadAccess)
1372 || (ee->request_code == X_CopyArea &&
1373 ee->error_code == BadDrawable))
1374 return 0;
1375
1376 fprintf(stderr, "%s: fatal error: request code=%d, error code=%d\n",
1377 argv0, ee->request_code, ee->error_code);
1378 return xerrorxlib(dpy, ee); /* may call exit */
1379}
1380
1381void
1382xsettitle(Window w, const char *str)
1383{
1384 XTextProperty xtp;
1385
1386 if (XmbTextListToTextProperty(dpy, (char **)&str, 1,
1387 XCompoundTextStyle, &xtp) == Success) {
1388 XSetTextProperty(dpy, w, &xtp, wmatom[WMName]);
1389 XSetTextProperty(dpy, w, &xtp, XA_WM_NAME);
1390 XFree(xtp.value);
1391 }
1392}
1393
1394void
1395usage(void)
1396{
1397 die("usage: %s [-dfksv] [-g geometry] [-n name] [-p [s+/-]pos]\n"
1398 " [-r narg] [-o color] [-O color] [-t color] [-T color]\n"
1399 " [-u color] [-U color] command...\n", argv0);
1400}
1401
1402int
1403main(int argc, char *argv[])
1404{
1405 Bool detach = False;
1406 int replace = 0;
1407 char *pstr;
1408
1409 ARGBEGIN {
1410 case 'c':
1411 closelastclient = True;
1412 fillagain = False;
1413 break;
1414 case 'd':
1415 detach = True;
1416 break;
1417 case 'f':
1418 fillagain = True;
1419 break;
1420 case 'g':
1421 geometry = EARGF(usage());
1422 break;
1423 case 'k':
1424 killclientsfirst = True;
1425 break;
1426 case 'n':
1427 wmname = EARGF(usage());
1428 break;
1429 case 'O':
1430 normfgcolor = EARGF(usage());
1431 break;
1432 case 'o':
1433 normbgcolor = EARGF(usage());
1434 break;
1435 case 'p':
1436 pstr = EARGF(usage());
1437 if (pstr[0] == 's') {
1438 npisrelative = True;
1439 newposition = atoi(&pstr[1]);
1440 } else {
1441 newposition = atoi(pstr);
1442 }
1443 break;
1444 case 'r':
1445 replace = atoi(EARGF(usage()));
1446 break;
1447 case 's':
1448 doinitspawn = False;
1449 break;
1450 case 'T':
1451 selfgcolor = EARGF(usage());
1452 break;
1453 case 't':
1454 selbgcolor = EARGF(usage());
1455 break;
1456 case 'U':
1457 urgfgcolor = EARGF(usage());
1458 break;
1459 case 'u':
1460 urgbgcolor = EARGF(usage());
1461 break;
1462 case 'v':
1463 die("tabbed-"VERSION", © 2009-2016 tabbed engineers, "
1464 "see LICENSE for details.\n");
1465 break;
1466 default:
1467 usage();
1468 break;
1469 } ARGEND;
1470
1471 if (argc < 1) {
1472 doinitspawn = False;
1473 fillagain = False;
1474 }
1475
1476 setcmd(argc, argv, replace);
1477
1478 if (!setlocale(LC_CTYPE, "") || !XSupportsLocale())
1479 fprintf(stderr, "%s: no locale support\n", argv0);
1480 if (!(dpy = XOpenDisplay(NULL)))
1481 die("%s: cannot open display\n", argv0);
1482
1483 config_init();
1484 setup();
1485 printf("0x%lx\n", win);
1486 fflush(NULL);
1487
1488 if (detach) {
1489 if (fork() == 0) {
1490 fclose(stdout);
1491 } else {
1492 if (dpy)
1493 close(ConnectionNumber(dpy));
1494 return EXIT_SUCCESS;
1495 }
1496 }
1497
1498 run();
1499 cleanup();
1500 XCloseDisplay(dpy);
1501
1502 return EXIT_SUCCESS;
1503}
diff --git a/xembed.1 b/xembed.1
new file mode 100644
index 0000000..5b0c28c
--- /dev/null
+++ b/xembed.1
@@ -0,0 +1,35 @@
1.TH XEMBED 1 tabbed\-VERSION
2.SH NAME
3xembed \- XEmbed foreground process
4.SH SYNOPSIS
5.B xembed
6.I flag command
7.RI [ "argument ..." ]
8.SH DESCRIPTION
9If the environment variable XEMBED is set, and
10.B xembed
11is in the foreground of its controlling tty, it will execute
12.IP
13command flag $XEMBED [argument ...]
14.LP
15Otherwise it will execute
16.IP
17command [argument ...]
18.LP
19.SH EXAMPLE
20In a terminal emulator within a
21.B tabbed
22session, the shell alias
23.IP
24$ alias surf='xembed -e surf'
25.LP
26will cause `surf' to open in a new tab, unless it is run in the background,
27i.e. `surf &', in which case it will instead open in a new window.
28.SH AUTHORS
29See the LICENSE file for the authors.
30.SH LICENSE
31See the LICENSE file for the terms of redistribution.
32.SH SEE ALSO
33.BR tabbed (1)
34.SH BUGS
35Please report them.
diff --git a/xembed.c b/xembed.c
new file mode 100644
index 0000000..cbb0e97
--- /dev/null
+++ b/xembed.c
@@ -0,0 +1,45 @@
1/*
2 * See LICENSE file for copyright and license details.
3 */
4
5#include <fcntl.h>
6#include <stdio.h>
7#include <stdlib.h>
8#include <unistd.h>
9
10int
11main(int argc, char *argv[])
12{
13 char *xembed;
14 int tty;
15 pid_t pgrp, tcpgrp;
16
17 if (argc < 3) {
18 fprintf(stderr, "usage: %s flag cmd ...\n", argv[0]);
19 return 2;
20 }
21
22 if (!(xembed = getenv("XEMBED")))
23 goto noembed;
24
25 if ((tty = open("/dev/tty", O_RDONLY)) < 0)
26 goto noembed;
27
28 pgrp = getpgrp();
29 tcpgrp = tcgetpgrp(tty);
30
31 close(tty);
32
33 if (pgrp == tcpgrp) { /* in foreground of tty */
34 argv[0] = argv[2];
35 argv[2] = xembed;
36 } else {
37noembed:
38 argv += 2;
39 }
40
41 execvp(argv[0], argv);
42
43 perror(argv[0]); /* failed to execute */
44 return 1;
45}