/* * See LICENSE file for copyright and license details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "arg.h" /* XEMBED messages */ #define XEMBED_EMBEDDED_NOTIFY 0 #define XEMBED_WINDOW_ACTIVATE 1 #define XEMBED_WINDOW_DEACTIVATE 2 #define XEMBED_REQUEST_FOCUS 3 #define XEMBED_FOCUS_IN 4 #define XEMBED_FOCUS_OUT 5 #define XEMBED_FOCUS_NEXT 6 #define XEMBED_FOCUS_PREV 7 /* 8-9 were used for XEMBED_GRAB_KEY/XEMBED_UNGRAB_KEY */ #define XEMBED_MODALITY_ON 10 #define XEMBED_MODALITY_OFF 11 #define XEMBED_REGISTER_ACCELERATOR 12 #define XEMBED_UNREGISTER_ACCELERATOR 13 #define XEMBED_ACTIVATE_ACCELERATOR 14 /* Details for XEMBED_FOCUS_IN: */ #define XEMBED_FOCUS_CURRENT 0 #define XEMBED_FOCUS_FIRST 1 #define XEMBED_FOCUS_LAST 2 /* Macros */ #define MAX(a, b) ((a) > (b) ? (a) : (b)) #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define LENGTH(x) (sizeof((x)) / sizeof(*(x))) #define CLEANMASK(mask) (mask & ~(numlockmask|LockMask)) #define TEXTW(x) (textnw(x, strlen(x)) + dc.font.height) enum { ColFG, ColBG, ColLast }; /* color */ enum { WMProtocols, WMDelete, WMName, WMState, WMFullscreen, XEmbed, WMSelectTab, WMLast }; /* default atoms */ typedef union { int i; const void *v; } Arg; typedef struct { unsigned int mod; KeySym keysym; void (*func)(const Arg *); const Arg arg; } Key; typedef struct { int x, y, w, h; unsigned long norm[ColLast]; unsigned long sel[ColLast]; unsigned long urg[ColLast]; Drawable drawable; GC gc; struct { int ascent; int descent; int height; XFontSet set; XFontStruct *xfont; } font; } DC; /* draw context */ typedef struct Client { char name[256]; Window win; int tabx; Bool urgent; Bool closed; } Client; /* function declarations */ static void buttonpress(const XEvent *e); static void cleanup(void); static void clientmessage(const XEvent *e); static void configurenotify(const XEvent *e); static void configurerequest(const XEvent *e); static void createnotify(const XEvent *e); static void unmapnotify(const XEvent *e); static void destroynotify(const XEvent *e); static void die(const char *errstr, ...); static void drawbar(void); static void drawtext(const char *text, unsigned long col[ColLast]); static void *ecalloc(size_t n, size_t size); static void *erealloc(void *o, size_t size); static void expose(const XEvent *e); static void focus(int c); static void focusin(const XEvent *e); static void focusonce(const Arg *arg); static void focusurgent(const Arg *); static void fullscreen(const Arg *arg); static char* getatom(int a); static int getclient(Window w); static unsigned long getcolor(const char *colstr); static int getfirsttab(void); static Bool gettextprop(Window w, Atom atom, char *text, unsigned int size); static void initfont(const char *fontstr); static Bool isprotodel(int c); static void keypress(const XEvent *e); static void killclient(const Arg *arg); static void manage(Window win); static void maprequest(const XEvent *e); static void move(const Arg *arg); static void movetab(const Arg *arg); static void propertynotify(const XEvent *e); static void resize(int c, int w, int h); static void rotate(const Arg *arg); static void run(void); static void sendxembed(int c, long msg, long detail, long d1, long d2); static void setup(void); static void setcmd(int argc, char *argv[], int); static void sigchld(int unused); static void spawn(const Arg *arg); static int textnw(const char *text, unsigned int len); static void toggle(const Arg *arg); static void unmanage(int c); static void updatenumlockmask(void); static void updatetitle(int c); static int xerror(Display *dpy, XErrorEvent *ee); static void xsettitle(Window w, const char *str); /* variables */ static int screen; static void (*handler[LASTEvent]) (const XEvent *) = { [ButtonPress] = buttonpress, [ClientMessage] = clientmessage, [ConfigureNotify] = configurenotify, [ConfigureRequest] = configurerequest, [CreateNotify] = createnotify, [UnmapNotify] = unmapnotify, [DestroyNotify] = destroynotify, [Expose] = expose, [FocusIn] = focusin, [KeyPress] = keypress, [MapRequest] = maprequest, [PropertyNotify] = propertynotify, }; static int bh, wx, wy, ww, wh; static unsigned int numlockmask = 0; static Bool running = True, nextfocus, doinitspawn = True, fillagain = False, closelastclient = False; static Display *dpy; static DC dc; static Atom wmatom[WMLast]; static Window root, win; static Client **clients = NULL; static int nclients = 0, sel = -1, lastsel = -1; static int (*xerrorxlib)(Display *, XErrorEvent *); static int cmd_append_pos = 0; static char winid[64]; static char **cmd = NULL; static char *wmname = "tabbed"; static const char *geometry = NULL; char *argv0; /* configuration, allows nested code to access above variables */ #include "config.h" void buttonpress(const XEvent *e) { const XButtonPressedEvent *ev = &e->xbutton; int i; int fc; Arg arg; if(ev->y < 0 || ev->y > bh) return; if(((fc = getfirsttab()) > 0 && ev->x < TEXTW(before)) || ev->x < 0) return; for(i = fc; i < nclients; i++) { if(clients[i]->tabx > ev->x) { switch(ev->button) { case Button1: focus(i); break; case Button2: focus(i); killclient(NULL); break; case Button4: case Button5: arg.i = ev->button == Button4 ? -1 : 1; rotate(&arg); break; } break; } } } void cleanup(void) { int i; for(i = 0; i < nclients; i++) { focus(i); killclient(NULL); killclient(NULL); XReparentWindow(dpy, clients[i]->win, root, 0, 0); unmanage(i); } free(clients); clients = NULL; if(dc.font.set) { XFreeFontSet(dpy, dc.font.set); } else { XFreeFont(dpy, dc.font.xfont); } XFreePixmap(dpy, dc.drawable); XFreeGC(dpy, dc.gc); XDestroyWindow(dpy, win); XSync(dpy, False); free(cmd); } void clientmessage(const XEvent *e) { const XClientMessageEvent *ev = &e->xclient; if(ev->message_type == wmatom[WMProtocols] && ev->data.l[0] == wmatom[WMDelete]) { running = False; } } void configurenotify(const XEvent *e) { const XConfigureEvent *ev = &e->xconfigure; if(ev->window == win && (ev->width != ww || ev->height != wh)) { ww = ev->width; wh = ev->height; XFreePixmap(dpy, dc.drawable); dc.drawable = XCreatePixmap(dpy, root, ww, wh, DefaultDepth(dpy, screen)); if(sel > -1) resize(sel, ww, wh - bh); XSync(dpy, False); } } void configurerequest(const XEvent *e) { const XConfigureRequestEvent *ev = &e->xconfigurerequest; XWindowChanges wc; int c; if((c = getclient(ev->window)) > -1) { wc.x = 0; wc.y = bh; wc.width = ww; wc.height = wh - bh; wc.border_width = 0; wc.sibling = ev->above; wc.stack_mode = ev->detail; XConfigureWindow(dpy, clients[c]->win, ev->value_mask, &wc); } } void createnotify(const XEvent *e) { const XCreateWindowEvent *ev = &e->xcreatewindow; if(ev->window != win && getclient(ev->window) < 0) manage(ev->window); } void unmapnotify(const XEvent *e) { const XUnmapEvent *ev = &e->xunmap; int c; if((c = getclient(ev->window)) > -1) unmanage(c); } void destroynotify(const XEvent *e) { const XDestroyWindowEvent *ev = &e->xdestroywindow; int c; if((c = getclient(ev->window)) > -1) unmanage(c); } void die(const char *errstr, ...) { va_list ap; va_start(ap, errstr); vfprintf(stderr, errstr, ap); va_end(ap); exit(EXIT_FAILURE); } void drawbar(void) { unsigned long *col; int c, cc, fc, width; char *name = NULL; if(nclients == 0) { dc.x = 0; dc.w = ww; XFetchName(dpy, win, &name); drawtext(name ? name : "", dc.norm); XCopyArea(dpy, dc.drawable, win, dc.gc, 0, 0, ww, bh, 0, 0); XSync(dpy, False); return; } width = ww; cc = ww / tabwidth; if(nclients > cc) cc = (ww - TEXTW(before) - TEXTW(after)) / tabwidth; if((fc = getfirsttab()) + cc < nclients) { dc.w = TEXTW(after); dc.x = width - dc.w; drawtext(after, dc.sel); width -= dc.w; } dc.x = 0; if(fc > 0) { dc.w = TEXTW(before); drawtext(before, dc.sel); dc.x += dc.w; width -= dc.w; } cc = MIN(cc, nclients); for(c = fc; c < fc + cc; c++) { dc.w = width / cc; if(c == sel) { col = dc.sel; dc.w += width % cc; } else { col = clients[c]->urgent ? dc.urg : dc.norm; } drawtext(clients[c]->name, col); dc.x += dc.w; clients[c]->tabx = dc.x; } XCopyArea(dpy, dc.drawable, win, dc.gc, 0, 0, ww, bh, 0, 0); XSync(dpy, False); } void drawtext(const char *text, unsigned long col[ColLast]) { int i, x, y, h, len, olen; char buf[256]; XRectangle r = { dc.x, dc.y, dc.w, dc.h }; XSetForeground(dpy, dc.gc, col[ColBG]); XFillRectangles(dpy, dc.drawable, dc.gc, &r, 1); if(!text) return; olen = strlen(text); h = dc.font.ascent + dc.font.descent; y = dc.y + (dc.h / 2) - (h / 2) + dc.font.ascent; x = dc.x + (h / 2); /* shorten text if necessary */ for(len = MIN(olen, sizeof(buf)); len && textnw(text, len) > dc.w - h; len--); if(!len) return; memcpy(buf, text, len); if(len < olen) { for(i = len; i && i > len - 3; buf[--i] = '.'); } XSetForeground(dpy, dc.gc, col[ColFG]); if(dc.font.set) { XmbDrawString(dpy, dc.drawable, dc.font.set, dc.gc, x, y, buf, len); } else { XDrawString(dpy, dc.drawable, dc.gc, x, y, buf, len); } } void * ecalloc(size_t n, size_t size) { void *p; if(!(p = calloc(n, size))) die("tabbed: cannot calloc\n"); return p; } void * erealloc(void *o, size_t size) { void *p; if(!(p = realloc(o, size))) die("tabbed: cannot realloc\n"); return p; } void expose(const XEvent *e) { const XExposeEvent *ev = &e->xexpose; if(ev->count == 0 && win == ev->window) drawbar(); } void focus(int c) { char buf[BUFSIZ] = "tabbed-"VERSION" ::"; size_t i, n; XWMHints* wmh; /* If c, sel and clients are -1, raise tabbed-win itself */ if(nclients == 0) { cmd[cmd_append_pos] = NULL; for(i = 0, n = strlen(buf); cmd[i] && n < sizeof(buf); i++) n += snprintf(&buf[n], sizeof(buf) - n, " %s", cmd[i]); xsettitle(win, buf); XRaiseWindow(dpy, win); return; } if(c < 0 || c >= nclients) return; resize(c, ww, wh - bh); XRaiseWindow(dpy, clients[c]->win); XSetInputFocus(dpy, clients[c]->win, RevertToParent, CurrentTime); sendxembed(c, XEMBED_FOCUS_IN, XEMBED_FOCUS_CURRENT, 0, 0); sendxembed(c, XEMBED_WINDOW_ACTIVATE, 0, 0, 0); xsettitle(win, clients[c]->name); if(sel != c) { lastsel = sel; sel = c; } if(clients[c]->urgent && (wmh = XGetWMHints(dpy, clients[c]->win))) { wmh->flags &= ~XUrgencyHint; XSetWMHints(dpy, clients[c]->win, wmh); clients[c]->urgent = False; XFree(wmh); } drawbar(); XSync(dpy, False); } void focusin(const XEvent *e) { const XFocusChangeEvent *ev = &e->xfocus; int dummy; Window focused; if(ev->mode != NotifyUngrab) { XGetInputFocus(dpy, &focused, &dummy); if(focused == win) focus(sel); } } void focusonce(const Arg *arg) { nextfocus = True; } void focusurgent(const Arg *args) { int c; for(c = (sel+1)%nclients; c != sel; c = (c+1)%nclients) { if(clients[c]->urgent) { focus(c); return; } } } void fullscreen(const Arg *arg) { XEvent e; e.type = ClientMessage; e.xclient.window = win; e.xclient.message_type = wmatom[WMState]; e.xclient.format = 32; e.xclient.data.l[0] = 2; e.xclient.data.l[1] = wmatom[WMFullscreen]; e.xclient.data.l[2] = 0; XSendEvent(dpy, root, False, SubstructureNotifyMask, &e); } char * getatom(int a) { static char buf[BUFSIZ]; Atom adummy; int idummy; unsigned long ldummy; unsigned char *p = NULL; XGetWindowProperty(dpy, win, wmatom[a], 0L, BUFSIZ, False, XA_STRING, &adummy, &idummy, &ldummy, &ldummy, &p); if(p) { strncpy(buf, (char *)p, LENGTH(buf)-1); } else { buf[0] = '\0'; } XFree(p); return buf; } int getclient(Window w) { int i; for(i = 0; i < nclients; i++) { if(clients[i]->win == w) return i; } return -1; } unsigned long getcolor(const char *colstr) { Colormap cmap = DefaultColormap(dpy, screen); XColor color; if(!XAllocNamedColor(dpy, cmap, colstr, &color, &color)) die("tabbed: cannot allocate color '%s'\n", colstr); return color.pixel; } int getfirsttab(void) { int cc, ret; if(sel < 0) return 0; cc = ww / tabwidth; if(nclients > cc) cc = (ww - TEXTW(before) - TEXTW(after)) / tabwidth; ret = sel - cc / 2 + (cc + 1) % 2; return ret < 0 ? 0 : (ret + cc > nclients ? MAX(0, nclients - cc) : ret); } Bool gettextprop(Window w, Atom atom, char *text, unsigned int size) { char **list = NULL; int n; XTextProperty name; if(!text || size == 0) return False; text[0] = '\0'; XGetTextProperty(dpy, w, &name, atom); if(!name.nitems) return False; if(name.encoding == XA_STRING) { strncpy(text, (char *)name.value, size - 1); } else { if(XmbTextPropertyToTextList(dpy, &name, &list, &n) >= Success && n > 0 && *list) { strncpy(text, *list, size - 1); XFreeStringList(list); } } text[size - 1] = '\0'; XFree(name.value); return True; } void initfont(const char *fontstr) { char *def, **missing, **font_names; int i, n; XFontStruct **xfonts; missing = NULL; if(dc.font.set) XFreeFontSet(dpy, dc.font.set); dc.font.set = XCreateFontSet(dpy, fontstr, &missing, &n, &def); if(missing) { while(n--) fprintf(stderr, "tabbed: missing fontset: %s\n", missing[n]); XFreeStringList(missing); } if(dc.font.set) { dc.font.ascent = dc.font.descent = 0; n = XFontsOfFontSet(dc.font.set, &xfonts, &font_names); for(i = 0, dc.font.ascent = 0, dc.font.descent = 0; i < n; i++) { dc.font.ascent = MAX(dc.font.ascent, (*xfonts)->ascent); dc.font.descent = MAX(dc.font.descent,(*xfonts)->descent); xfonts++; } } else { if(dc.font.xfont) XFreeFont(dpy, dc.font.xfont); dc.font.xfont = NULL; if(!(dc.font.xfont = XLoadQueryFont(dpy, fontstr)) && !(dc.font.xfont = XLoadQueryFont(dpy, "fixed"))) { die("tabbed: cannot load font: '%s'\n", fontstr); } dc.font.ascent = dc.font.xfont->ascent; dc.font.descent = dc.font.xfont->descent; } dc.font.height = dc.font.ascent + dc.font.descent; } Bool isprotodel(int c) { int i, n; Atom *protocols; Bool ret = False; if(XGetWMProtocols(dpy, clients[c]->win, &protocols, &n)) { for(i = 0; !ret && i < n; i++) { if(protocols[i] == wmatom[WMDelete]) ret = True; } XFree(protocols); } return ret; } void keypress(const XEvent *e) { const XKeyEvent *ev = &e->xkey; unsigned int i; KeySym keysym; keysym = XkbKeycodeToKeysym(dpy, (KeyCode)ev->keycode, 0, 0); for(i = 0; i < LENGTH(keys); i++) { if(keysym == keys[i].keysym && CLEANMASK(keys[i].mod) == CLEANMASK(ev->state) && keys[i].func) { keys[i].func(&(keys[i].arg)); } } } void killclient(const Arg *arg) { XEvent ev; if(sel < 0) return; if(isprotodel(sel) && !clients[sel]->closed) { ev.type = ClientMessage; ev.xclient.window = clients[sel]->win; ev.xclient.message_type = wmatom[WMProtocols]; ev.xclient.format = 32; ev.xclient.data.l[0] = wmatom[WMDelete]; ev.xclient.data.l[1] = CurrentTime; XSendEvent(dpy, clients[sel]->win, False, NoEventMask, &ev); clients[sel]->closed = True; } else { XKillClient(dpy, clients[sel]->win); } } void manage(Window w) { updatenumlockmask(); { int i, j, nextpos; unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask }; KeyCode code; Client *c; XEvent e; XWithdrawWindow(dpy, w, 0); XReparentWindow(dpy, w, win, 0, bh); XSelectInput(dpy, w, PropertyChangeMask |StructureNotifyMask|EnterWindowMask); XSync(dpy, False); for(i = 0; i < LENGTH(keys); i++) { if((code = XKeysymToKeycode(dpy, keys[i].keysym))) { for(j = 0; j < LENGTH(modifiers); j++) { XGrabKey(dpy, code, keys[i].mod | modifiers[j], w, True, GrabModeAsync, GrabModeAsync); } } } c = ecalloc(1, sizeof *c); c->win = w; nclients++; clients = erealloc(clients, sizeof(Client *) * nclients); if(npisrelative) { nextpos = sel + newposition; } else { if(newposition < 0) { nextpos = nclients - newposition; } else { nextpos = newposition; } } if(nextpos >= nclients) nextpos = nclients - 1; if(nextpos < 0) nextpos = 0; if(nclients > 1 && nextpos < nclients - 1) { memmove(&clients[nextpos + 1], &clients[nextpos], sizeof(Client *) * (nclients - nextpos - 1)); } clients[nextpos] = c; updatetitle(nextpos); XLowerWindow(dpy, w); XMapWindow(dpy, w); e.xclient.window = w; e.xclient.type = ClientMessage; e.xclient.message_type = wmatom[XEmbed]; e.xclient.format = 32; e.xclient.data.l[0] = CurrentTime; e.xclient.data.l[1] = XEMBED_EMBEDDED_NOTIFY; e.xclient.data.l[2] = 0; e.xclient.data.l[3] = win; e.xclient.data.l[4] = 0; XSendEvent(dpy, root, False, NoEventMask, &e); XSync(dpy, False); /* Adjust sel before focus does set it to lastsel. */ if(sel >= nextpos) sel++; focus((nextfocus)? nextpos : ((sel < 0)? 0 : sel)); nextfocus = foreground; } } void maprequest(const XEvent *e) { const XMapRequestEvent *ev = &e->xmaprequest; if(getclient(ev->window) < 0) manage(ev->window); } void move(const Arg *arg) { if(arg->i >= 0 && arg->i < nclients) focus(arg->i); } void movetab(const Arg *arg) { int c; Client *new; c = (sel + arg->i) % nclients; if(c < 0) c += nclients; if(sel < 0 || (c == sel)) return; new = clients[sel]; if(sel < c) memmove(&clients[sel], &clients[sel+1], sizeof(Client *) * (c - sel)); else memmove(&clients[c+1], &clients[c], sizeof(Client *) * (sel - c)); clients[c] = new; sel = c; drawbar(); } void propertynotify(const XEvent *e) { const XPropertyEvent *ev = &e->xproperty; XWMHints *wmh; int c; char* selection = NULL; Arg arg; if(ev->state == PropertyNewValue && ev->atom == wmatom[WMSelectTab]) { selection = getatom(WMSelectTab); if(!strncmp(selection, "0x", 2)) { arg.i = getclient(strtoul(selection, NULL, 0)); move(&arg); } else { cmd[cmd_append_pos] = selection; arg.v = cmd; spawn(&arg); } } else if(ev->state == PropertyNewValue && ev->atom == XA_WM_HINTS && (c = getclient(ev->window)) > -1 && (wmh = XGetWMHints(dpy, clients[c]->win))) { if(wmh->flags & XUrgencyHint) { XFree(wmh); wmh = XGetWMHints(dpy, win); if(c != sel) { if(urgentswitch && wmh && !(wmh->flags & XUrgencyHint)) { /* only switch, if tabbed was focused since last urgency hint * if WMHints could not be received, default to no switch */ focus(c); } else { /* if no switch should be performed, mark tab as urgent */ clients[c]->urgent = True; drawbar(); } } if(wmh && !(wmh->flags & XUrgencyHint)) { /* update tabbed urgency hint if not set already */ wmh->flags |= XUrgencyHint; XSetWMHints(dpy, win, wmh); } } XFree(wmh); } else if(ev->state != PropertyDelete && ev->atom == XA_WM_NAME && (c = getclient(ev->window)) > -1) { updatetitle(c); } } void resize(int c, int w, int h) { XConfigureEvent ce; XWindowChanges wc; ce.x = 0; ce.y = bh; ce.width = wc.width = w; ce.height = wc.height = h; ce.type = ConfigureNotify; ce.display = dpy; ce.event = clients[c]->win; ce.window = clients[c]->win; ce.above = None; ce.override_redirect = False; ce.border_width = 0; XConfigureWindow(dpy, clients[c]->win, CWWidth|CWHeight, &wc); XSendEvent(dpy, clients[c]->win, False, StructureNotifyMask, (XEvent *)&ce); } void rotate(const Arg *arg) { int nsel = -1; if(sel < 0) return; if(arg->i == 0) { if(lastsel > -1) focus(lastsel); } else if(sel > -1) { /* Rotating in an arg->i step around the clients. */ nsel = sel + arg->i; while(nsel >= nclients) nsel -= nclients; while(nsel < 0) nsel += nclients; focus(nsel); } } void run(void) { XEvent ev; /* main event loop */ XSync(dpy, False); drawbar(); if(doinitspawn == True) spawn(NULL); while(running) { XNextEvent(dpy, &ev); if(handler[ev.type]) (handler[ev.type])(&ev); /* call handler */ } } void sendxembed(int c, long msg, long detail, long d1, long d2) { XEvent e = { 0 }; e.xclient.window = clients[c]->win; e.xclient.type = ClientMessage; e.xclient.message_type = wmatom[XEmbed]; e.xclient.format = 32; e.xclient.data.l[0] = CurrentTime; e.xclient.data.l[1] = msg; e.xclient.data.l[2] = detail; e.xclient.data.l[3] = d1; e.xclient.data.l[4] = d2; XSendEvent(dpy, clients[c]->win, False, NoEventMask, &e); } void setcmd(int argc, char *argv[], int replace) { int i; cmd = ecalloc(argc + 3, sizeof *cmd); if (argc == 0) return; for(i = 0; i < argc; i++) cmd[i] = argv[i]; cmd[(replace > 0)? replace : argc] = winid; cmd_append_pos = argc + !replace; cmd[cmd_append_pos] = cmd[cmd_append_pos+1] = NULL; } void setup(void) { int bitm, tx, ty, tw, th, dh, dw, isfixed; XWMHints *wmh; XClassHint class_hint; XSizeHints *size_hint; /* clean up any zombies immediately */ sigchld(0); /* init screen */ screen = DefaultScreen(dpy); root = RootWindow(dpy, screen); initfont(font); bh = dc.h = dc.font.height + 2; /* init atoms */ wmatom[WMProtocols] = XInternAtom(dpy, "WM_PROTOCOLS", False); wmatom[WMDelete] = XInternAtom(dpy, "WM_DELETE_WINDOW", False); wmatom[XEmbed] = XInternAtom(dpy, "_XEMBED", False); wmatom[WMName] = XInternAtom(dpy, "_NET_WM_NAME", False); wmatom[WMState] = XInternAtom(dpy, "_NET_WM_STATE", False); wmatom[WMFullscreen] = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False); wmatom[WMSelectTab] = XInternAtom(dpy, "_TABBED_SELECT_TAB", False); /* init appearance */ wx = 0; wy = 0; ww = 800; wh = 600; isfixed = 0; if(geometry) { tx = ty = tw = th = 0; bitm = XParseGeometry(geometry, &tx, &ty, (unsigned *)&tw, (unsigned *)&th); if(bitm & XValue) wx = tx; if(bitm & YValue) wy = ty; if(bitm & WidthValue) ww = tw; if(bitm & HeightValue) wh = th; if(bitm & XNegative && wx == 0) wx = -1; if(bitm & YNegative && wy == 0) wy = -1; if(bitm & (HeightValue|WidthValue)) isfixed = 1; dw = DisplayWidth(dpy, screen); dh = DisplayHeight(dpy, screen); if(wx < 0) wx = dw + wx - ww - 1; if(wy < 0) wy = dh + wy - wh - 1; } dc.norm[ColBG] = getcolor(normbgcolor); dc.norm[ColFG] = getcolor(normfgcolor); dc.sel[ColBG] = getcolor(selbgcolor); dc.sel[ColFG] = getcolor(selfgcolor); dc.urg[ColBG] = getcolor(urgbgcolor); dc.urg[ColFG] = getcolor(urgfgcolor); dc.drawable = XCreatePixmap(dpy, root, ww, wh, DefaultDepth(dpy, screen)); dc.gc = XCreateGC(dpy, root, 0, 0); if(!dc.font.set) XSetFont(dpy, dc.gc, dc.font.xfont->fid); win = XCreateSimpleWindow(dpy, root, wx, wy, ww, wh, 0, dc.norm[ColFG], dc.norm[ColBG]); XMapRaised(dpy, win); XSelectInput(dpy, win, SubstructureNotifyMask|FocusChangeMask| ButtonPressMask|ExposureMask|KeyPressMask|PropertyChangeMask| StructureNotifyMask|SubstructureRedirectMask); xerrorxlib = XSetErrorHandler(xerror); class_hint.res_name = wmname; class_hint.res_class = "tabbed"; XSetClassHint(dpy, win, &class_hint); size_hint = XAllocSizeHints(); if(!isfixed) { size_hint->flags = PSize; size_hint->height = wh; size_hint->width = ww; } else { size_hint->flags = PMaxSize | PMinSize; size_hint->min_width = size_hint->max_width = ww; size_hint->min_height = size_hint->max_height = wh; } wmh = XAllocWMHints(); XSetWMProperties(dpy, win, NULL, NULL, NULL, 0, size_hint, wmh, NULL); XFree(size_hint); XFree(wmh); XSetWMProtocols(dpy, win, &wmatom[WMDelete], 1); snprintf(winid, sizeof(winid), "%lu", win); setenv("XEMBED", winid, 1); nextfocus = foreground; focus(-1); } void sigchld(int unused) { if(signal(SIGCHLD, sigchld) == SIG_ERR) die("tabbed: cannot install SIGCHLD handler"); while(0 < waitpid(-1, NULL, WNOHANG)); } void spawn(const Arg *arg) { if(fork() == 0) { if(dpy) close(ConnectionNumber(dpy)); setsid(); if(arg && arg->v) { execvp(((char **)arg->v)[0], (char **)arg->v); fprintf(stderr, "tabbed: execvp %s", ((char **)arg->v)[0]); } else { cmd[cmd_append_pos] = NULL; execvp(cmd[0], cmd); fprintf(stderr, "tabbed: execvp %s", cmd[0]); } perror(" failed"); exit(0); } } int textnw(const char *text, unsigned int len) { XRectangle r; if(dc.font.set) { XmbTextExtents(dc.font.set, text, len, NULL, &r); return r.width; } return XTextWidth(dc.font.xfont, text, len); } void toggle(const Arg *arg) { *(Bool*) arg->v = !*(Bool*) arg->v; } void unmanage(int c) { if(c < 0 || c >= nclients) { drawbar(); XSync(dpy, False); return; } if(!nclients) return; if(c == 0) { /* First client. */ nclients--; free(clients[0]); memmove(&clients[0], &clients[1], sizeof(Client *) * nclients); } else if(c == nclients - 1) { /* Last client. */ nclients--; free(clients[c]); clients = erealloc(clients, sizeof(Client *) * nclients); } else { /* Somewhere inbetween. */ free(clients[c]); memmove(&clients[c], &clients[c+1], sizeof(Client *) * (nclients - (c + 1))); nclients--; } if(nclients <= 0) { lastsel = sel = -1; if(closelastclient) { running = False; } else if(fillagain && running) { spawn(NULL); } } else { if(lastsel >= nclients) { lastsel = nclients - 1; } else if(lastsel > c) { lastsel--; } if(c == sel && lastsel >= 0) { focus(lastsel); } else { if(sel > c) sel--; if(sel >= nclients) sel = nclients - 1; focus(sel); } } drawbar(); XSync(dpy, False); } void updatenumlockmask(void) { unsigned int i, j; XModifierKeymap *modmap; numlockmask = 0; modmap = XGetModifierMapping(dpy); for(i = 0; i < 8; i++) { for(j = 0; j < modmap->max_keypermod; j++) { if(modmap->modifiermap[i * modmap->max_keypermod + j] == XKeysymToKeycode(dpy, XK_Num_Lock)) { numlockmask = (1 << i); } } } XFreeModifiermap(modmap); } void updatetitle(int c) { if(!gettextprop(clients[c]->win, wmatom[WMName], clients[c]->name, sizeof(clients[c]->name))) { gettextprop(clients[c]->win, XA_WM_NAME, clients[c]->name, sizeof(clients[c]->name)); } if(sel == c) xsettitle(win, clients[c]->name); drawbar(); } /* There's no way to check accesses to destroyed windows, thus those cases are * ignored (especially on UnmapNotify's). Other types of errors call Xlibs * default error handler, which may call exit. */ int xerror(Display *dpy, XErrorEvent *ee) { if(ee->error_code == BadWindow || (ee->request_code == X_SetInputFocus && ee->error_code == BadMatch) || (ee->request_code == X_PolyText8 && ee->error_code == BadDrawable) || (ee->request_code == X_PolyFillRectangle && ee->error_code == BadDrawable) || (ee->request_code == X_PolySegment && ee->error_code == BadDrawable) || (ee->request_code == X_ConfigureWindow && ee->error_code == BadMatch) || (ee->request_code == X_GrabButton && ee->error_code == BadAccess) || (ee->request_code == X_GrabKey && ee->error_code == BadAccess) || (ee->request_code == X_CopyArea && ee->error_code == BadDrawable)) { return 0; } fprintf(stderr, "tabbed: fatal error: request code=%d, error code=%d\n", ee->request_code, ee->error_code); return xerrorxlib(dpy, ee); /* may call exit */ } void xsettitle(Window w, const char *str) { XTextProperty xtp; if(XmbTextListToTextProperty(dpy, (char **)&str, 1, XCompoundTextStyle, &xtp) == Success) { XSetTextProperty(dpy, w, &xtp, wmatom[WMName]); XSetTextProperty(dpy, w, &xtp, XA_WM_NAME); XFree(xtp.value); } } char *argv0; void usage(void) { die("usage: %s [-dfhsv] [-g geometry] [-n name] [-p [s+/-]pos] [-r narg] " "[-o color] [-O color] [-t color] [-T color] [-u color] [-U color] " "command...\n", argv0); } int main(int argc, char *argv[]) { Bool detach = False; int replace = 0; char *pstr; ARGBEGIN { case 'c': closelastclient = True; fillagain = False; break; case 'd': detach = True; break; case 'f': fillagain = True; break; case 'g': geometry = EARGF(usage()); break; case 'n': wmname = EARGF(usage()); break; case 'p': pstr = EARGF(usage()); if(pstr[0] == 's') { npisrelative = True; newposition = atoi(&pstr[1]); } else { newposition = atoi(pstr); } break; case 'r': replace = atoi(EARGF(usage())); break; case 's': doinitspawn = False; break; case 'o': normbgcolor = EARGF(usage()); break; case 'O': normfgcolor = EARGF(usage()); break; case 't': selbgcolor = EARGF(usage()); break; case 'T': selfgcolor = EARGF(usage()); break; case 'u': urgbgcolor = EARGF(usage()); break; case 'U': urgfgcolor = EARGF(usage()); break; case 'v': die("tabbed-"VERSION", © 2009-2014" " tabbed engineers, see LICENSE" " for details.\n"); break; default: case 'h': usage(); } ARGEND; if(argc < 1) { doinitspawn = False; fillagain = False; } setcmd(argc, argv, replace); if(!setlocale(LC_CTYPE, "") || !XSupportsLocale()) fprintf(stderr, "tabbed: no locale support\n"); if(!(dpy = XOpenDisplay(NULL))) die("tabbed: cannot open display\n"); setup(); printf("0x%lx\n", win); fflush(NULL); if(detach) { if(fork() == 0) { fclose(stdout); } else { if(dpy) close(ConnectionNumber(dpy)); return EXIT_SUCCESS; } } run(); cleanup(); XCloseDisplay(dpy); return EXIT_SUCCESS; }