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