tabbed.c 29 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367
  1. /*
  2. * See LICENSE file for copyright and license details.
  3. */
  4. #include <sys/wait.h>
  5. #include <locale.h>
  6. #include <signal.h>
  7. #include <stdarg.h>
  8. #include <stdio.h>
  9. #include <stdlib.h>
  10. #include <string.h>
  11. #include <unistd.h>
  12. #include <X11/Xatom.h>
  13. #include <X11/Xlib.h>
  14. #include <X11/Xproto.h>
  15. #include <X11/Xutil.h>
  16. #include <X11/XKBlib.h>
  17. #include <X11/Xft/Xft.h>
  18. #include "arg.h"
  19. /* XEMBED messages */
  20. #define XEMBED_EMBEDDED_NOTIFY 0
  21. #define XEMBED_WINDOW_ACTIVATE 1
  22. #define XEMBED_WINDOW_DEACTIVATE 2
  23. #define XEMBED_REQUEST_FOCUS 3
  24. #define XEMBED_FOCUS_IN 4
  25. #define XEMBED_FOCUS_OUT 5
  26. #define XEMBED_FOCUS_NEXT 6
  27. #define XEMBED_FOCUS_PREV 7
  28. /* 8-9 were used for XEMBED_GRAB_KEY/XEMBED_UNGRAB_KEY */
  29. #define XEMBED_MODALITY_ON 10
  30. #define XEMBED_MODALITY_OFF 11
  31. #define XEMBED_REGISTER_ACCELERATOR 12
  32. #define XEMBED_UNREGISTER_ACCELERATOR 13
  33. #define XEMBED_ACTIVATE_ACCELERATOR 14
  34. /* Details for XEMBED_FOCUS_IN: */
  35. #define XEMBED_FOCUS_CURRENT 0
  36. #define XEMBED_FOCUS_FIRST 1
  37. #define XEMBED_FOCUS_LAST 2
  38. /* Macros */
  39. #define MAX(a, b) ((a) > (b) ? (a) : (b))
  40. #define MIN(a, b) ((a) < (b) ? (a) : (b))
  41. #define LENGTH(x) (sizeof((x)) / sizeof(*(x)))
  42. #define CLEANMASK(mask) (mask & ~(numlockmask | LockMask))
  43. #define TEXTW(x) (textnw(x, strlen(x)) + dc.font.height)
  44. enum { ColFG, ColBG, ColLast }; /* color */
  45. enum { WMProtocols, WMDelete, WMName, WMState, WMFullscreen,
  46. XEmbed, WMSelectTab, WMLast }; /* default atoms */
  47. typedef union {
  48. int i;
  49. const void *v;
  50. } Arg;
  51. typedef struct {
  52. unsigned int mod;
  53. KeySym keysym;
  54. void (*func)(const Arg *);
  55. const Arg arg;
  56. } Key;
  57. typedef struct {
  58. int x, y, w, h;
  59. XftColor norm[ColLast];
  60. XftColor sel[ColLast];
  61. XftColor urg[ColLast];
  62. Drawable drawable;
  63. GC gc;
  64. struct {
  65. int ascent;
  66. int descent;
  67. int height;
  68. XftFont *xfont;
  69. } font;
  70. } DC; /* draw context */
  71. typedef struct {
  72. char name[256];
  73. Window win;
  74. int tabx;
  75. Bool urgent;
  76. Bool closed;
  77. } Client;
  78. /* function declarations */
  79. static void buttonpress(const XEvent *e);
  80. static void cleanup(void);
  81. static void clientmessage(const XEvent *e);
  82. static void configurenotify(const XEvent *e);
  83. static void configurerequest(const XEvent *e);
  84. static void createnotify(const XEvent *e);
  85. static void destroynotify(const XEvent *e);
  86. static void die(const char *errstr, ...);
  87. static void drawbar(void);
  88. static void drawtext(const char *text, XftColor col[ColLast]);
  89. static void *ecalloc(size_t n, size_t size);
  90. static void *erealloc(void *o, size_t size);
  91. static void expose(const XEvent *e);
  92. static void focus(int c);
  93. static void focusin(const XEvent *e);
  94. static void focusonce(const Arg *arg);
  95. static void focusurgent(const Arg *arg);
  96. static void fullscreen(const Arg *arg);
  97. static char *getatom(int a);
  98. static int getclient(Window w);
  99. static XftColor getcolor(const char *colstr);
  100. static int getfirsttab(void);
  101. static Bool gettextprop(Window w, Atom atom, char *text, unsigned int size);
  102. static void initfont(const char *fontstr);
  103. static Bool isprotodel(int c);
  104. static void keypress(const XEvent *e);
  105. static void killclient(const Arg *arg);
  106. static void manage(Window win);
  107. static void maprequest(const XEvent *e);
  108. static void move(const Arg *arg);
  109. static void movetab(const Arg *arg);
  110. static void propertynotify(const XEvent *e);
  111. static void resize(int c, int w, int h);
  112. static void rotate(const Arg *arg);
  113. static void run(void);
  114. static void sendxembed(int c, long msg, long detail, long d1, long d2);
  115. static void setcmd(int argc, char *argv[], int);
  116. static void setup(void);
  117. static void sigchld(int unused);
  118. static void spawn(const Arg *arg);
  119. static int textnw(const char *text, unsigned int len);
  120. static void toggle(const Arg *arg);
  121. static void unmanage(int c);
  122. static void unmapnotify(const XEvent *e);
  123. static void updatenumlockmask(void);
  124. static void updatetitle(int c);
  125. static int xerror(Display *dpy, XErrorEvent *ee);
  126. static void xsettitle(Window w, const char *str);
  127. /* variables */
  128. static int screen;
  129. static void (*handler[LASTEvent]) (const XEvent *) = {
  130. [ButtonPress] = buttonpress,
  131. [ClientMessage] = clientmessage,
  132. [ConfigureNotify] = configurenotify,
  133. [ConfigureRequest] = configurerequest,
  134. [CreateNotify] = createnotify,
  135. [UnmapNotify] = unmapnotify,
  136. [DestroyNotify] = destroynotify,
  137. [Expose] = expose,
  138. [FocusIn] = focusin,
  139. [KeyPress] = keypress,
  140. [MapRequest] = maprequest,
  141. [PropertyNotify] = propertynotify,
  142. };
  143. static int bh, wx, wy, ww, wh;
  144. static unsigned int numlockmask;
  145. static Bool running = True, nextfocus, doinitspawn = True,
  146. fillagain = False, closelastclient = False,
  147. killclientsfirst = False;
  148. static Display *dpy;
  149. static DC dc;
  150. static Atom wmatom[WMLast];
  151. static Window root, win;
  152. static Client **clients;
  153. static int nclients, sel = -1, lastsel = -1;
  154. static int (*xerrorxlib)(Display *, XErrorEvent *);
  155. static int cmd_append_pos;
  156. static char winid[64];
  157. static char **cmd;
  158. static char *wmname = "tabbed";
  159. static const char *geometry;
  160. char *argv0;
  161. /* configuration, allows nested code to access above variables */
  162. #include "config.h"
  163. void
  164. buttonpress(const XEvent *e)
  165. {
  166. const XButtonPressedEvent *ev = &e->xbutton;
  167. int i, fc;
  168. Arg arg;
  169. if (ev->y < 0 || ev->y > bh)
  170. return;
  171. if (((fc = getfirsttab()) > 0 && ev->x < TEXTW(before)) || ev->x < 0)
  172. return;
  173. for (i = fc; i < nclients; i++) {
  174. if (clients[i]->tabx > ev->x) {
  175. switch (ev->button) {
  176. case Button1:
  177. focus(i);
  178. break;
  179. case Button2:
  180. focus(i);
  181. killclient(NULL);
  182. break;
  183. case Button4: /* FALLTHROUGH */
  184. case Button5:
  185. arg.i = ev->button == Button4 ? -1 : 1;
  186. rotate(&arg);
  187. break;
  188. }
  189. break;
  190. }
  191. }
  192. }
  193. void
  194. cleanup(void)
  195. {
  196. int i;
  197. for (i = 0; i < nclients; i++) {
  198. focus(i);
  199. killclient(NULL);
  200. XReparentWindow(dpy, clients[i]->win, root, 0, 0);
  201. unmanage(i);
  202. }
  203. free(clients);
  204. clients = NULL;
  205. XFreePixmap(dpy, dc.drawable);
  206. XFreeGC(dpy, dc.gc);
  207. XDestroyWindow(dpy, win);
  208. XSync(dpy, False);
  209. free(cmd);
  210. }
  211. void
  212. clientmessage(const XEvent *e)
  213. {
  214. const XClientMessageEvent *ev = &e->xclient;
  215. if (ev->message_type == wmatom[WMProtocols] &&
  216. ev->data.l[0] == wmatom[WMDelete]) {
  217. if (nclients > 1 && killclientsfirst) {
  218. killclient(0);
  219. return;
  220. }
  221. running = False;
  222. }
  223. }
  224. void
  225. configurenotify(const XEvent *e)
  226. {
  227. const XConfigureEvent *ev = &e->xconfigure;
  228. if (ev->window == win && (ev->width != ww || ev->height != wh)) {
  229. ww = ev->width;
  230. wh = ev->height;
  231. XFreePixmap(dpy, dc.drawable);
  232. dc.drawable = XCreatePixmap(dpy, root, ww, wh,
  233. DefaultDepth(dpy, screen));
  234. if (sel > -1)
  235. resize(sel, ww, wh - bh);
  236. XSync(dpy, False);
  237. }
  238. }
  239. void
  240. configurerequest(const XEvent *e)
  241. {
  242. const XConfigureRequestEvent *ev = &e->xconfigurerequest;
  243. XWindowChanges wc;
  244. int c;
  245. if ((c = getclient(ev->window)) > -1) {
  246. wc.x = 0;
  247. wc.y = bh;
  248. wc.width = ww;
  249. wc.height = wh - bh;
  250. wc.border_width = 0;
  251. wc.sibling = ev->above;
  252. wc.stack_mode = ev->detail;
  253. XConfigureWindow(dpy, clients[c]->win, ev->value_mask, &wc);
  254. }
  255. }
  256. void
  257. createnotify(const XEvent *e)
  258. {
  259. const XCreateWindowEvent *ev = &e->xcreatewindow;
  260. if (ev->window != win && getclient(ev->window) < 0)
  261. manage(ev->window);
  262. }
  263. void
  264. destroynotify(const XEvent *e)
  265. {
  266. const XDestroyWindowEvent *ev = &e->xdestroywindow;
  267. int c;
  268. if ((c = getclient(ev->window)) > -1)
  269. unmanage(c);
  270. }
  271. void
  272. die(const char *errstr, ...)
  273. {
  274. va_list ap;
  275. va_start(ap, errstr);
  276. vfprintf(stderr, errstr, ap);
  277. va_end(ap);
  278. exit(EXIT_FAILURE);
  279. }
  280. void
  281. drawbar(void)
  282. {
  283. XftColor *col;
  284. int c, cc, fc, width;
  285. char *name = NULL;
  286. if (nclients == 0) {
  287. dc.x = 0;
  288. dc.w = ww;
  289. XFetchName(dpy, win, &name);
  290. drawtext(name ? name : "", dc.norm);
  291. XCopyArea(dpy, dc.drawable, win, dc.gc, 0, 0, ww, bh, 0, 0);
  292. XSync(dpy, False);
  293. return;
  294. }
  295. width = ww;
  296. cc = ww / tabwidth;
  297. if (nclients > cc)
  298. cc = (ww - TEXTW(before) - TEXTW(after)) / tabwidth;
  299. if ((fc = getfirsttab()) + cc < nclients) {
  300. dc.w = TEXTW(after);
  301. dc.x = width - dc.w;
  302. drawtext(after, dc.sel);
  303. width -= dc.w;
  304. }
  305. dc.x = 0;
  306. if (fc > 0) {
  307. dc.w = TEXTW(before);
  308. drawtext(before, dc.sel);
  309. dc.x += dc.w;
  310. width -= dc.w;
  311. }
  312. cc = MIN(cc, nclients);
  313. for (c = fc; c < fc + cc; c++) {
  314. dc.w = width / cc;
  315. if (c == sel) {
  316. col = dc.sel;
  317. dc.w += width % cc;
  318. } else {
  319. col = clients[c]->urgent ? dc.urg : dc.norm;
  320. }
  321. drawtext(clients[c]->name, col);
  322. dc.x += dc.w;
  323. clients[c]->tabx = dc.x;
  324. }
  325. XCopyArea(dpy, dc.drawable, win, dc.gc, 0, 0, ww, bh, 0, 0);
  326. XSync(dpy, False);
  327. }
  328. void
  329. drawtext(const char *text, XftColor col[ColLast])
  330. {
  331. int i, j, x, y, h, len, olen;
  332. char buf[256];
  333. XftDraw *d;
  334. XRectangle r = { dc.x, dc.y, dc.w, dc.h };
  335. XSetForeground(dpy, dc.gc, col[ColBG].pixel);
  336. XFillRectangles(dpy, dc.drawable, dc.gc, &r, 1);
  337. if (!text)
  338. return;
  339. olen = strlen(text);
  340. h = dc.font.ascent + dc.font.descent;
  341. y = dc.y + (dc.h / 2) - (h / 2) + dc.font.ascent;
  342. x = dc.x + (h / 2);
  343. /* shorten text if necessary */
  344. for (len = MIN(olen, sizeof(buf));
  345. len && textnw(text, len) > dc.w - h; len--);
  346. if (!len)
  347. return;
  348. memcpy(buf, text, len);
  349. if (len < olen) {
  350. for (i = len, j = strlen(titletrim); j && i;
  351. buf[--i] = titletrim[--j])
  352. ;
  353. }
  354. d = XftDrawCreate(dpy, dc.drawable, DefaultVisual(dpy, screen), DefaultColormap(dpy, screen));
  355. XftDrawStringUtf8(d, &col[ColFG], dc.font.xfont, x, y, (XftChar8 *) buf, len);
  356. XftDrawDestroy(d);
  357. }
  358. void *
  359. ecalloc(size_t n, size_t size)
  360. {
  361. void *p;
  362. if (!(p = calloc(n, size)))
  363. die("%s: cannot calloc\n", argv0);
  364. return p;
  365. }
  366. void *
  367. erealloc(void *o, size_t size)
  368. {
  369. void *p;
  370. if (!(p = realloc(o, size)))
  371. die("%s: cannot realloc\n", argv0);
  372. return p;
  373. }
  374. void
  375. expose(const XEvent *e)
  376. {
  377. const XExposeEvent *ev = &e->xexpose;
  378. if (ev->count == 0 && win == ev->window)
  379. drawbar();
  380. }
  381. void
  382. focus(int c)
  383. {
  384. char buf[BUFSIZ] = "tabbed-"VERSION" ::";
  385. size_t i, n;
  386. XWMHints* wmh;
  387. /* If c, sel and clients are -1, raise tabbed-win itself */
  388. if (nclients == 0) {
  389. cmd[cmd_append_pos] = NULL;
  390. for(i = 0, n = strlen(buf); cmd[i] && n < sizeof(buf); i++)
  391. n += snprintf(&buf[n], sizeof(buf) - n, " %s", cmd[i]);
  392. xsettitle(win, buf);
  393. XRaiseWindow(dpy, win);
  394. return;
  395. }
  396. if (c < 0 || c >= nclients)
  397. return;
  398. resize(c, ww, wh - bh);
  399. XRaiseWindow(dpy, clients[c]->win);
  400. XSetInputFocus(dpy, clients[c]->win, RevertToParent, CurrentTime);
  401. sendxembed(c, XEMBED_FOCUS_IN, XEMBED_FOCUS_CURRENT, 0, 0);
  402. sendxembed(c, XEMBED_WINDOW_ACTIVATE, 0, 0, 0);
  403. xsettitle(win, clients[c]->name);
  404. if (sel != c) {
  405. lastsel = sel;
  406. sel = c;
  407. }
  408. if (clients[c]->urgent && (wmh = XGetWMHints(dpy, clients[c]->win))) {
  409. wmh->flags &= ~XUrgencyHint;
  410. XSetWMHints(dpy, clients[c]->win, wmh);
  411. clients[c]->urgent = False;
  412. XFree(wmh);
  413. }
  414. drawbar();
  415. XSync(dpy, False);
  416. }
  417. void
  418. focusin(const XEvent *e)
  419. {
  420. const XFocusChangeEvent *ev = &e->xfocus;
  421. int dummy;
  422. Window focused;
  423. if (ev->mode != NotifyUngrab) {
  424. XGetInputFocus(dpy, &focused, &dummy);
  425. if (focused == win)
  426. focus(sel);
  427. }
  428. }
  429. void
  430. focusonce(const Arg *arg)
  431. {
  432. nextfocus = True;
  433. }
  434. void
  435. focusurgent(const Arg *arg)
  436. {
  437. int c;
  438. if (sel < 0)
  439. return;
  440. for (c = (sel + 1) % nclients; c != sel; c = (c + 1) % nclients) {
  441. if (clients[c]->urgent) {
  442. focus(c);
  443. return;
  444. }
  445. }
  446. }
  447. void
  448. fullscreen(const Arg *arg)
  449. {
  450. XEvent e;
  451. e.type = ClientMessage;
  452. e.xclient.window = win;
  453. e.xclient.message_type = wmatom[WMState];
  454. e.xclient.format = 32;
  455. e.xclient.data.l[0] = 2;
  456. e.xclient.data.l[1] = wmatom[WMFullscreen];
  457. e.xclient.data.l[2] = 0;
  458. XSendEvent(dpy, root, False, SubstructureNotifyMask, &e);
  459. }
  460. char *
  461. getatom(int a)
  462. {
  463. static char buf[BUFSIZ];
  464. Atom adummy;
  465. int idummy;
  466. unsigned long ldummy;
  467. unsigned char *p = NULL;
  468. XGetWindowProperty(dpy, win, wmatom[a], 0L, BUFSIZ, False, XA_STRING,
  469. &adummy, &idummy, &ldummy, &ldummy, &p);
  470. if (p)
  471. strncpy(buf, (char *)p, LENGTH(buf)-1);
  472. else
  473. buf[0] = '\0';
  474. XFree(p);
  475. return buf;
  476. }
  477. int
  478. getclient(Window w)
  479. {
  480. int i;
  481. for (i = 0; i < nclients; i++) {
  482. if (clients[i]->win == w)
  483. return i;
  484. }
  485. return -1;
  486. }
  487. XftColor
  488. getcolor(const char *colstr)
  489. {
  490. XftColor color;
  491. if (!XftColorAllocName(dpy, DefaultVisual(dpy, screen), DefaultColormap(dpy, screen), colstr, &color))
  492. die("%s: cannot allocate color '%s'\n", argv0, colstr);
  493. return color;
  494. }
  495. int
  496. getfirsttab(void)
  497. {
  498. int cc, ret;
  499. if (sel < 0)
  500. return 0;
  501. cc = ww / tabwidth;
  502. if (nclients > cc)
  503. cc = (ww - TEXTW(before) - TEXTW(after)) / tabwidth;
  504. ret = sel - cc / 2 + (cc + 1) % 2;
  505. return ret < 0 ? 0 :
  506. ret + cc > nclients ? MAX(0, nclients - cc) :
  507. ret;
  508. }
  509. Bool
  510. gettextprop(Window w, Atom atom, char *text, unsigned int size)
  511. {
  512. char **list = NULL;
  513. int n;
  514. XTextProperty name;
  515. if (!text || size == 0)
  516. return False;
  517. text[0] = '\0';
  518. XGetTextProperty(dpy, w, &name, atom);
  519. if (!name.nitems)
  520. return False;
  521. if (name.encoding == XA_STRING) {
  522. strncpy(text, (char *)name.value, size - 1);
  523. } else if (XmbTextPropertyToTextList(dpy, &name, &list, &n) >= Success
  524. && n > 0 && *list) {
  525. strncpy(text, *list, size - 1);
  526. XFreeStringList(list);
  527. }
  528. text[size - 1] = '\0';
  529. XFree(name.value);
  530. return True;
  531. }
  532. void
  533. initfont(const char *fontstr)
  534. {
  535. if (!(dc.font.xfont = XftFontOpenName(dpy, screen, fontstr))
  536. && !(dc.font.xfont = XftFontOpenName(dpy, screen, "fixed")))
  537. die("error, cannot load font: '%s'\n", fontstr);
  538. dc.font.ascent = dc.font.xfont->ascent;
  539. dc.font.descent = dc.font.xfont->descent;
  540. dc.font.height = dc.font.ascent + dc.font.descent;
  541. }
  542. Bool
  543. isprotodel(int c)
  544. {
  545. int i, n;
  546. Atom *protocols;
  547. Bool ret = False;
  548. if (XGetWMProtocols(dpy, clients[c]->win, &protocols, &n)) {
  549. for (i = 0; !ret && i < n; i++) {
  550. if (protocols[i] == wmatom[WMDelete])
  551. ret = True;
  552. }
  553. XFree(protocols);
  554. }
  555. return ret;
  556. }
  557. void
  558. keypress(const XEvent *e)
  559. {
  560. const XKeyEvent *ev = &e->xkey;
  561. unsigned int i;
  562. KeySym keysym;
  563. keysym = XkbKeycodeToKeysym(dpy, (KeyCode)ev->keycode, 0, 0);
  564. for (i = 0; i < LENGTH(keys); i++) {
  565. if (keysym == keys[i].keysym &&
  566. CLEANMASK(keys[i].mod) == CLEANMASK(ev->state) &&
  567. keys[i].func)
  568. keys[i].func(&(keys[i].arg));
  569. }
  570. }
  571. void
  572. killclient(const Arg *arg)
  573. {
  574. XEvent ev;
  575. if (sel < 0)
  576. return;
  577. if (isprotodel(sel) && !clients[sel]->closed) {
  578. ev.type = ClientMessage;
  579. ev.xclient.window = clients[sel]->win;
  580. ev.xclient.message_type = wmatom[WMProtocols];
  581. ev.xclient.format = 32;
  582. ev.xclient.data.l[0] = wmatom[WMDelete];
  583. ev.xclient.data.l[1] = CurrentTime;
  584. XSendEvent(dpy, clients[sel]->win, False, NoEventMask, &ev);
  585. clients[sel]->closed = True;
  586. } else {
  587. XKillClient(dpy, clients[sel]->win);
  588. }
  589. }
  590. void
  591. manage(Window w)
  592. {
  593. updatenumlockmask();
  594. {
  595. int i, j, nextpos;
  596. unsigned int modifiers[] = { 0, LockMask, numlockmask,
  597. numlockmask | LockMask };
  598. KeyCode code;
  599. Client *c;
  600. XEvent e;
  601. XWithdrawWindow(dpy, w, 0);
  602. XReparentWindow(dpy, w, win, 0, bh);
  603. XSelectInput(dpy, w, PropertyChangeMask |
  604. StructureNotifyMask | EnterWindowMask);
  605. XSync(dpy, False);
  606. for (i = 0; i < LENGTH(keys); i++) {
  607. if ((code = XKeysymToKeycode(dpy, keys[i].keysym))) {
  608. for (j = 0; j < LENGTH(modifiers); j++) {
  609. XGrabKey(dpy, code, keys[i].mod |
  610. modifiers[j], w, True,
  611. GrabModeAsync, GrabModeAsync);
  612. }
  613. }
  614. }
  615. c = ecalloc(1, sizeof *c);
  616. c->win = w;
  617. nclients++;
  618. clients = erealloc(clients, sizeof(Client *) * nclients);
  619. if(npisrelative) {
  620. nextpos = sel + newposition;
  621. } else {
  622. if (newposition < 0)
  623. nextpos = nclients - newposition;
  624. else
  625. nextpos = newposition;
  626. }
  627. if (nextpos >= nclients)
  628. nextpos = nclients - 1;
  629. if (nextpos < 0)
  630. nextpos = 0;
  631. if (nclients > 1 && nextpos < nclients - 1)
  632. memmove(&clients[nextpos + 1], &clients[nextpos],
  633. sizeof(Client *) * (nclients - nextpos - 1));
  634. clients[nextpos] = c;
  635. updatetitle(nextpos);
  636. XLowerWindow(dpy, w);
  637. XMapWindow(dpy, w);
  638. e.xclient.window = w;
  639. e.xclient.type = ClientMessage;
  640. e.xclient.message_type = wmatom[XEmbed];
  641. e.xclient.format = 32;
  642. e.xclient.data.l[0] = CurrentTime;
  643. e.xclient.data.l[1] = XEMBED_EMBEDDED_NOTIFY;
  644. e.xclient.data.l[2] = 0;
  645. e.xclient.data.l[3] = win;
  646. e.xclient.data.l[4] = 0;
  647. XSendEvent(dpy, root, False, NoEventMask, &e);
  648. XSync(dpy, False);
  649. /* Adjust sel before focus does set it to lastsel. */
  650. if (sel >= nextpos)
  651. sel++;
  652. focus(nextfocus ? nextpos :
  653. sel < 0 ? 0 :
  654. sel);
  655. nextfocus = foreground;
  656. }
  657. }
  658. void
  659. maprequest(const XEvent *e)
  660. {
  661. const XMapRequestEvent *ev = &e->xmaprequest;
  662. if (getclient(ev->window) < 0)
  663. manage(ev->window);
  664. }
  665. void
  666. move(const Arg *arg)
  667. {
  668. if (arg->i >= 0 && arg->i < nclients)
  669. focus(arg->i);
  670. }
  671. void
  672. movetab(const Arg *arg)
  673. {
  674. int c;
  675. Client *new;
  676. if (sel < 0)
  677. return;
  678. c = (sel + arg->i) % nclients;
  679. if (c < 0)
  680. c += nclients;
  681. if (c == sel)
  682. return;
  683. new = clients[sel];
  684. if (sel < c)
  685. memmove(&clients[sel], &clients[sel+1],
  686. sizeof(Client *) * (c - sel));
  687. else
  688. memmove(&clients[c+1], &clients[c],
  689. sizeof(Client *) * (sel - c));
  690. clients[c] = new;
  691. sel = c;
  692. drawbar();
  693. }
  694. void
  695. propertynotify(const XEvent *e)
  696. {
  697. const XPropertyEvent *ev = &e->xproperty;
  698. XWMHints *wmh;
  699. int c;
  700. char* selection = NULL;
  701. Arg arg;
  702. if (ev->state == PropertyNewValue && ev->atom == wmatom[WMSelectTab]) {
  703. selection = getatom(WMSelectTab);
  704. if (!strncmp(selection, "0x", 2)) {
  705. arg.i = getclient(strtoul(selection, NULL, 0));
  706. move(&arg);
  707. } else {
  708. cmd[cmd_append_pos] = selection;
  709. arg.v = cmd;
  710. spawn(&arg);
  711. }
  712. } else if (ev->state == PropertyNewValue && ev->atom == XA_WM_HINTS &&
  713. (c = getclient(ev->window)) > -1 &&
  714. (wmh = XGetWMHints(dpy, clients[c]->win))) {
  715. if (wmh->flags & XUrgencyHint) {
  716. XFree(wmh);
  717. wmh = XGetWMHints(dpy, win);
  718. if (c != sel) {
  719. if (urgentswitch && wmh &&
  720. !(wmh->flags & XUrgencyHint)) {
  721. /* only switch, if tabbed was focused
  722. * since last urgency hint if WMHints
  723. * could not be received,
  724. * default to no switch */
  725. focus(c);
  726. } else {
  727. /* if no switch should be performed,
  728. * mark tab as urgent */
  729. clients[c]->urgent = True;
  730. drawbar();
  731. }
  732. }
  733. if (wmh && !(wmh->flags & XUrgencyHint)) {
  734. /* update tabbed urgency hint
  735. * if not set already */
  736. wmh->flags |= XUrgencyHint;
  737. XSetWMHints(dpy, win, wmh);
  738. }
  739. }
  740. XFree(wmh);
  741. } else if (ev->state != PropertyDelete && ev->atom == XA_WM_NAME &&
  742. (c = getclient(ev->window)) > -1) {
  743. updatetitle(c);
  744. }
  745. }
  746. void
  747. resize(int c, int w, int h)
  748. {
  749. XConfigureEvent ce;
  750. XWindowChanges wc;
  751. ce.x = 0;
  752. ce.y = bh;
  753. ce.width = wc.width = w;
  754. ce.height = wc.height = h;
  755. ce.type = ConfigureNotify;
  756. ce.display = dpy;
  757. ce.event = clients[c]->win;
  758. ce.window = clients[c]->win;
  759. ce.above = None;
  760. ce.override_redirect = False;
  761. ce.border_width = 0;
  762. XConfigureWindow(dpy, clients[c]->win, CWWidth | CWHeight, &wc);
  763. XSendEvent(dpy, clients[c]->win, False, StructureNotifyMask,
  764. (XEvent *)&ce);
  765. }
  766. void
  767. rotate(const Arg *arg)
  768. {
  769. int nsel = -1;
  770. if (sel < 0)
  771. return;
  772. if (arg->i == 0) {
  773. if (lastsel > -1)
  774. focus(lastsel);
  775. } else if (sel > -1) {
  776. /* Rotating in an arg->i step around the clients. */
  777. nsel = sel + arg->i;
  778. while (nsel >= nclients)
  779. nsel -= nclients;
  780. while (nsel < 0)
  781. nsel += nclients;
  782. focus(nsel);
  783. }
  784. }
  785. void
  786. run(void)
  787. {
  788. XEvent ev;
  789. /* main event loop */
  790. XSync(dpy, False);
  791. drawbar();
  792. if (doinitspawn == True)
  793. spawn(NULL);
  794. while (running) {
  795. XNextEvent(dpy, &ev);
  796. if (handler[ev.type])
  797. (handler[ev.type])(&ev); /* call handler */
  798. }
  799. }
  800. void
  801. sendxembed(int c, long msg, long detail, long d1, long d2)
  802. {
  803. XEvent e = { 0 };
  804. e.xclient.window = clients[c]->win;
  805. e.xclient.type = ClientMessage;
  806. e.xclient.message_type = wmatom[XEmbed];
  807. e.xclient.format = 32;
  808. e.xclient.data.l[0] = CurrentTime;
  809. e.xclient.data.l[1] = msg;
  810. e.xclient.data.l[2] = detail;
  811. e.xclient.data.l[3] = d1;
  812. e.xclient.data.l[4] = d2;
  813. XSendEvent(dpy, clients[c]->win, False, NoEventMask, &e);
  814. }
  815. void
  816. setcmd(int argc, char *argv[], int replace)
  817. {
  818. int i;
  819. cmd = ecalloc(argc + 3, sizeof(*cmd));
  820. if (argc == 0)
  821. return;
  822. for (i = 0; i < argc; i++)
  823. cmd[i] = argv[i];
  824. cmd[replace > 0 ? replace : argc] = winid;
  825. cmd_append_pos = argc + !replace;
  826. cmd[cmd_append_pos] = cmd[cmd_append_pos + 1] = NULL;
  827. }
  828. void
  829. setup(void)
  830. {
  831. int bitm, tx, ty, tw, th, dh, dw, isfixed;
  832. XWMHints *wmh;
  833. XClassHint class_hint;
  834. XSizeHints *size_hint;
  835. /* clean up any zombies immediately */
  836. sigchld(0);
  837. /* init screen */
  838. screen = DefaultScreen(dpy);
  839. root = RootWindow(dpy, screen);
  840. initfont(font);
  841. bh = dc.h = dc.font.height + 2;
  842. /* init atoms */
  843. wmatom[WMDelete] = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
  844. wmatom[WMFullscreen] = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN",
  845. False);
  846. wmatom[WMName] = XInternAtom(dpy, "_NET_WM_NAME", False);
  847. wmatom[WMProtocols] = XInternAtom(dpy, "WM_PROTOCOLS", False);
  848. wmatom[WMSelectTab] = XInternAtom(dpy, "_TABBED_SELECT_TAB", False);
  849. wmatom[WMState] = XInternAtom(dpy, "_NET_WM_STATE", False);
  850. wmatom[XEmbed] = XInternAtom(dpy, "_XEMBED", False);
  851. /* init appearance */
  852. wx = 0;
  853. wy = 0;
  854. ww = 800;
  855. wh = 600;
  856. isfixed = 0;
  857. if (geometry) {
  858. tx = ty = tw = th = 0;
  859. bitm = XParseGeometry(geometry, &tx, &ty, (unsigned *)&tw,
  860. (unsigned *)&th);
  861. if (bitm & XValue)
  862. wx = tx;
  863. if (bitm & YValue)
  864. wy = ty;
  865. if (bitm & WidthValue)
  866. ww = tw;
  867. if (bitm & HeightValue)
  868. wh = th;
  869. if (bitm & XNegative && wx == 0)
  870. wx = -1;
  871. if (bitm & YNegative && wy == 0)
  872. wy = -1;
  873. if (bitm & (HeightValue | WidthValue))
  874. isfixed = 1;
  875. dw = DisplayWidth(dpy, screen);
  876. dh = DisplayHeight(dpy, screen);
  877. if (wx < 0)
  878. wx = dw + wx - ww - 1;
  879. if (wy < 0)
  880. wy = dh + wy - wh - 1;
  881. }
  882. dc.norm[ColBG] = getcolor(normbgcolor);
  883. dc.norm[ColFG] = getcolor(normfgcolor);
  884. dc.sel[ColBG] = getcolor(selbgcolor);
  885. dc.sel[ColFG] = getcolor(selfgcolor);
  886. dc.urg[ColBG] = getcolor(urgbgcolor);
  887. dc.urg[ColFG] = getcolor(urgfgcolor);
  888. dc.drawable = XCreatePixmap(dpy, root, ww, wh,
  889. DefaultDepth(dpy, screen));
  890. dc.gc = XCreateGC(dpy, root, 0, 0);
  891. win = XCreateSimpleWindow(dpy, root, wx, wy, ww, wh, 0,
  892. dc.norm[ColFG].pixel, dc.norm[ColBG].pixel);
  893. XMapRaised(dpy, win);
  894. XSelectInput(dpy, win, SubstructureNotifyMask | FocusChangeMask |
  895. ButtonPressMask | ExposureMask | KeyPressMask |
  896. PropertyChangeMask | StructureNotifyMask |
  897. SubstructureRedirectMask);
  898. xerrorxlib = XSetErrorHandler(xerror);
  899. class_hint.res_name = wmname;
  900. class_hint.res_class = "tabbed";
  901. XSetClassHint(dpy, win, &class_hint);
  902. size_hint = XAllocSizeHints();
  903. if (!isfixed) {
  904. size_hint->flags = PSize;
  905. size_hint->height = wh;
  906. size_hint->width = ww;
  907. } else {
  908. size_hint->flags = PMaxSize | PMinSize;
  909. size_hint->min_width = size_hint->max_width = ww;
  910. size_hint->min_height = size_hint->max_height = wh;
  911. }
  912. wmh = XAllocWMHints();
  913. XSetWMProperties(dpy, win, NULL, NULL, NULL, 0, size_hint, wmh, NULL);
  914. XFree(size_hint);
  915. XFree(wmh);
  916. XSetWMProtocols(dpy, win, &wmatom[WMDelete], 1);
  917. snprintf(winid, sizeof(winid), "%lu", win);
  918. setenv("XEMBED", winid, 1);
  919. nextfocus = foreground;
  920. focus(-1);
  921. }
  922. void
  923. sigchld(int unused)
  924. {
  925. if (signal(SIGCHLD, sigchld) == SIG_ERR)
  926. die("%s: cannot install SIGCHLD handler", argv0);
  927. while (0 < waitpid(-1, NULL, WNOHANG));
  928. }
  929. void
  930. spawn(const Arg *arg)
  931. {
  932. if (fork() == 0) {
  933. if(dpy)
  934. close(ConnectionNumber(dpy));
  935. setsid();
  936. if (arg && arg->v) {
  937. execvp(((char **)arg->v)[0], (char **)arg->v);
  938. fprintf(stderr, "%s: execvp %s", argv0,
  939. ((char **)arg->v)[0]);
  940. } else {
  941. cmd[cmd_append_pos] = NULL;
  942. execvp(cmd[0], cmd);
  943. fprintf(stderr, "%s: execvp %s", argv0, cmd[0]);
  944. }
  945. perror(" failed");
  946. exit(0);
  947. }
  948. }
  949. int
  950. textnw(const char *text, unsigned int len)
  951. {
  952. XGlyphInfo ext;
  953. XftTextExtentsUtf8(dpy, dc.font.xfont, (XftChar8 *) text, len, &ext);
  954. return ext.xOff;
  955. }
  956. void
  957. toggle(const Arg *arg)
  958. {
  959. *(Bool*) arg->v = !*(Bool*) arg->v;
  960. }
  961. void
  962. unmanage(int c)
  963. {
  964. if (c < 0 || c >= nclients) {
  965. drawbar();
  966. XSync(dpy, False);
  967. return;
  968. }
  969. if (!nclients)
  970. return;
  971. if (c == 0) {
  972. /* First client. */
  973. nclients--;
  974. free(clients[0]);
  975. memmove(&clients[0], &clients[1], sizeof(Client *) * nclients);
  976. } else if (c == nclients - 1) {
  977. /* Last client. */
  978. nclients--;
  979. free(clients[c]);
  980. clients = erealloc(clients, sizeof(Client *) * nclients);
  981. } else {
  982. /* Somewhere inbetween. */
  983. free(clients[c]);
  984. memmove(&clients[c], &clients[c+1],
  985. sizeof(Client *) * (nclients - (c + 1)));
  986. nclients--;
  987. }
  988. if (nclients <= 0) {
  989. lastsel = sel = -1;
  990. if (closelastclient)
  991. running = False;
  992. else if (fillagain && running)
  993. spawn(NULL);
  994. } else {
  995. if (lastsel >= nclients)
  996. lastsel = nclients - 1;
  997. else if (lastsel > c)
  998. lastsel--;
  999. if (c == sel && lastsel >= 0) {
  1000. focus(lastsel);
  1001. } else {
  1002. if (sel > c)
  1003. sel--;
  1004. if (sel >= nclients)
  1005. sel = nclients - 1;
  1006. focus(sel);
  1007. }
  1008. }
  1009. drawbar();
  1010. XSync(dpy, False);
  1011. }
  1012. void
  1013. unmapnotify(const XEvent *e)
  1014. {
  1015. const XUnmapEvent *ev = &e->xunmap;
  1016. int c;
  1017. if ((c = getclient(ev->window)) > -1)
  1018. unmanage(c);
  1019. }
  1020. void
  1021. updatenumlockmask(void)
  1022. {
  1023. unsigned int i, j;
  1024. XModifierKeymap *modmap;
  1025. numlockmask = 0;
  1026. modmap = XGetModifierMapping(dpy);
  1027. for (i = 0; i < 8; i++) {
  1028. for (j = 0; j < modmap->max_keypermod; j++) {
  1029. if (modmap->modifiermap[i * modmap->max_keypermod + j]
  1030. == XKeysymToKeycode(dpy, XK_Num_Lock))
  1031. numlockmask = (1 << i);
  1032. }
  1033. }
  1034. XFreeModifiermap(modmap);
  1035. }
  1036. void
  1037. updatetitle(int c)
  1038. {
  1039. if (!gettextprop(clients[c]->win, wmatom[WMName], clients[c]->name,
  1040. sizeof(clients[c]->name)))
  1041. gettextprop(clients[c]->win, XA_WM_NAME, clients[c]->name,
  1042. sizeof(clients[c]->name));
  1043. if (sel == c)
  1044. xsettitle(win, clients[c]->name);
  1045. drawbar();
  1046. }
  1047. /* There's no way to check accesses to destroyed windows, thus those cases are
  1048. * ignored (especially on UnmapNotify's). Other types of errors call Xlibs
  1049. * default error handler, which may call exit. */
  1050. int
  1051. xerror(Display *dpy, XErrorEvent *ee)
  1052. {
  1053. if (ee->error_code == BadWindow
  1054. || (ee->request_code == X_SetInputFocus &&
  1055. ee->error_code == BadMatch)
  1056. || (ee->request_code == X_PolyText8 &&
  1057. ee->error_code == BadDrawable)
  1058. || (ee->request_code == X_PolyFillRectangle &&
  1059. ee->error_code == BadDrawable)
  1060. || (ee->request_code == X_PolySegment &&
  1061. ee->error_code == BadDrawable)
  1062. || (ee->request_code == X_ConfigureWindow &&
  1063. ee->error_code == BadMatch)
  1064. || (ee->request_code == X_GrabButton &&
  1065. ee->error_code == BadAccess)
  1066. || (ee->request_code == X_GrabKey &&
  1067. ee->error_code == BadAccess)
  1068. || (ee->request_code == X_CopyArea &&
  1069. ee->error_code == BadDrawable))
  1070. return 0;
  1071. fprintf(stderr, "%s: fatal error: request code=%d, error code=%d\n",
  1072. argv0, ee->request_code, ee->error_code);
  1073. return xerrorxlib(dpy, ee); /* may call exit */
  1074. }
  1075. void
  1076. xsettitle(Window w, const char *str)
  1077. {
  1078. XTextProperty xtp;
  1079. if (XmbTextListToTextProperty(dpy, (char **)&str, 1,
  1080. XCompoundTextStyle, &xtp) == Success) {
  1081. XSetTextProperty(dpy, w, &xtp, wmatom[WMName]);
  1082. XSetTextProperty(dpy, w, &xtp, XA_WM_NAME);
  1083. XFree(xtp.value);
  1084. }
  1085. }
  1086. void
  1087. usage(void)
  1088. {
  1089. die("usage: %s [-dfksv] [-g geometry] [-n name] [-p [s+/-]pos]\n"
  1090. " [-r narg] [-o color] [-O color] [-t color] [-T color]\n"
  1091. " [-u color] [-U color] command...\n", argv0);
  1092. }
  1093. int
  1094. main(int argc, char *argv[])
  1095. {
  1096. Bool detach = False;
  1097. int replace = 0;
  1098. char *pstr;
  1099. ARGBEGIN {
  1100. case 'c':
  1101. closelastclient = True;
  1102. fillagain = False;
  1103. break;
  1104. case 'd':
  1105. detach = True;
  1106. break;
  1107. case 'f':
  1108. fillagain = True;
  1109. break;
  1110. case 'g':
  1111. geometry = EARGF(usage());
  1112. break;
  1113. case 'k':
  1114. killclientsfirst = True;
  1115. break;
  1116. case 'n':
  1117. wmname = EARGF(usage());
  1118. break;
  1119. case 'O':
  1120. normfgcolor = EARGF(usage());
  1121. break;
  1122. case 'o':
  1123. normbgcolor = EARGF(usage());
  1124. break;
  1125. case 'p':
  1126. pstr = EARGF(usage());
  1127. if (pstr[0] == 's') {
  1128. npisrelative = True;
  1129. newposition = atoi(&pstr[1]);
  1130. } else {
  1131. newposition = atoi(pstr);
  1132. }
  1133. break;
  1134. case 'r':
  1135. replace = atoi(EARGF(usage()));
  1136. break;
  1137. case 's':
  1138. doinitspawn = False;
  1139. break;
  1140. case 'T':
  1141. selfgcolor = EARGF(usage());
  1142. break;
  1143. case 't':
  1144. selbgcolor = EARGF(usage());
  1145. break;
  1146. case 'U':
  1147. urgfgcolor = EARGF(usage());
  1148. break;
  1149. case 'u':
  1150. urgbgcolor = EARGF(usage());
  1151. break;
  1152. case 'v':
  1153. die("tabbed-"VERSION", © 2009-2016 tabbed engineers, "
  1154. "see LICENSE for details.\n");
  1155. break;
  1156. default:
  1157. usage();
  1158. break;
  1159. } ARGEND;
  1160. if (argc < 1) {
  1161. doinitspawn = False;
  1162. fillagain = False;
  1163. }
  1164. setcmd(argc, argv, replace);
  1165. if (!setlocale(LC_CTYPE, "") || !XSupportsLocale())
  1166. fprintf(stderr, "%s: no locale support\n", argv0);
  1167. if (!(dpy = XOpenDisplay(NULL)))
  1168. die("%s: cannot open display\n", argv0);
  1169. setup();
  1170. printf("0x%lx\n", win);
  1171. fflush(NULL);
  1172. if (detach) {
  1173. if (fork() == 0) {
  1174. fclose(stdout);
  1175. } else {
  1176. if (dpy)
  1177. close(ConnectionNumber(dpy));
  1178. return EXIT_SUCCESS;
  1179. }
  1180. }
  1181. run();
  1182. cleanup();
  1183. XCloseDisplay(dpy);
  1184. return EXIT_SUCCESS;
  1185. }