2 * See LICENSE file for copyright and license details.
13 #include <X11/Xatom.h>
15 #include <X11/Xproto.h>
16 #include <X11/Xutil.h>
17 #include <X11/XKBlib.h>
22 #define XEMBED_EMBEDDED_NOTIFY 0
23 #define XEMBED_WINDOW_ACTIVATE 1
24 #define XEMBED_WINDOW_DEACTIVATE 2
25 #define XEMBED_REQUEST_FOCUS 3
26 #define XEMBED_FOCUS_IN 4
27 #define XEMBED_FOCUS_OUT 5
28 #define XEMBED_FOCUS_NEXT 6
29 #define XEMBED_FOCUS_PREV 7
30 /* 8-9 were used for XEMBED_GRAB_KEY/XEMBED_UNGRAB_KEY */
31 #define XEMBED_MODALITY_ON 10
32 #define XEMBED_MODALITY_OFF 11
33 #define XEMBED_REGISTER_ACCELERATOR 12
34 #define XEMBED_UNREGISTER_ACCELERATOR 13
35 #define XEMBED_ACTIVATE_ACCELERATOR 14
37 /* Details for XEMBED_FOCUS_IN: */
38 #define XEMBED_FOCUS_CURRENT 0
39 #define XEMBED_FOCUS_FIRST 1
40 #define XEMBED_FOCUS_LAST 2
43 #define MAX(a, b) ((a) > (b) ? (a) : (b))
44 #define MIN(a, b) ((a) < (b) ? (a) : (b))
45 #define LENGTH(x) (sizeof((x)) / sizeof(*(x)))
46 #define CLEANMASK(mask) (mask & ~(numlockmask | LockMask))
47 #define TEXTW(x) (textnw(x, strlen(x)) + dc.font.height)
49 enum { ColFG
, ColBG
, ColLast
}; /* color */
50 enum { WMProtocols
, WMDelete
, WMName
, WMState
, WMFullscreen
,
51 XEmbed
, WMSelectTab
, WMLast
}; /* default atoms */
61 void (*func
)(const Arg
*);
67 unsigned long norm
[ColLast
];
68 unsigned long sel
[ColLast
];
69 unsigned long urg
[ColLast
];
79 } DC
; /* draw context */
89 /* function declarations */
90 static void buttonpress(const XEvent
*e
);
91 static void cleanup(void);
92 static void clientmessage(const XEvent
*e
);
93 static void configurenotify(const XEvent
*e
);
94 static void configurerequest(const XEvent
*e
);
95 static void createnotify(const XEvent
*e
);
96 static void destroynotify(const XEvent
*e
);
97 static void die(const char *errstr
, ...);
98 static void drawbar(void);
99 static void drawtext(const char *text
, unsigned long col
[ColLast
]);
100 static void *ecalloc(size_t n
, size_t size
);
101 static void *erealloc(void *o
, size_t size
);
102 static void expose(const XEvent
*e
);
103 static void focus(int c
);
104 static void focusin(const XEvent
*e
);
105 static void focusonce(const Arg
*arg
);
106 static void focusurgent(const Arg
*arg
);
107 static void fullscreen(const Arg
*arg
);
108 static char *getatom(int a
);
109 static int getclient(Window w
);
110 static unsigned long getcolor(const char *colstr
);
111 static int getfirsttab(void);
112 static Bool
gettextprop(Window w
, Atom atom
, char *text
, unsigned int size
);
113 static void initfont(const char *fontstr
);
114 static Bool
isprotodel(int c
);
115 static void keypress(const XEvent
*e
);
116 static void killclient(const Arg
*arg
);
117 static void manage(Window win
);
118 static void maprequest(const XEvent
*e
);
119 static void move(const Arg
*arg
);
120 static void movetab(const Arg
*arg
);
121 static void propertynotify(const XEvent
*e
);
122 static void resize(int c
, int w
, int h
);
123 static void rotate(const Arg
*arg
);
124 static void run(void);
125 static void sendxembed(int c
, long msg
, long detail
, long d1
, long d2
);
126 static void setcmd(int argc
, char *argv
[], int);
127 static void setup(void);
128 static void sigchld(int unused
);
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
);
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
,
151 [KeyPress
] = keypress
,
152 [MapRequest
] = maprequest
,
153 [PropertyNotify
] = propertynotify
,
155 static int bh
, wx
, wy
, ww
, wh
;
156 static unsigned int numlockmask
;
157 static Bool running
= True
, nextfocus
, doinitspawn
= True
,
158 fillagain
= False
, closelastclient
= False
;
161 static Atom wmatom
[WMLast
];
162 static Window root
, win
;
163 static Client
**clients
;
164 static int nclients
, sel
= -1, lastsel
= -1;
165 static int (*xerrorxlib
)(Display
*, XErrorEvent
*);
166 static int cmd_append_pos
;
167 static char winid
[64];
169 static char *wmname
= "tabbed";
170 static const char *geometry
;
174 /* configuration, allows nested code to access above variables */
178 buttonpress(const XEvent
*e
)
180 const XButtonPressedEvent
*ev
= &e
->xbutton
;
184 if (ev
->y
< 0 || ev
->y
> bh
)
187 if (((fc
= getfirsttab()) > 0 && ev
->x
< TEXTW(before
)) || ev
->x
< 0)
190 for (i
= fc
; i
< nclients
; i
++) {
191 if (clients
[i
]->tabx
> ev
->x
) {
192 switch (ev
->button
) {
200 case Button4
: /* FALLTHROUGH */
202 arg
.i
= ev
->button
== Button4
? -1 : 1;
216 for (i
= 0; i
< nclients
; i
++) {
220 XReparentWindow(dpy
, clients
[i
]->win
, root
, 0, 0);
227 XFreeFontSet(dpy
, dc
.font
.set
);
229 XFreeFont(dpy
, dc
.font
.xfont
);
231 XFreePixmap(dpy
, dc
.drawable
);
233 XDestroyWindow(dpy
, win
);
239 clientmessage(const XEvent
*e
)
241 const XClientMessageEvent
*ev
= &e
->xclient
;
243 if (ev
->message_type
== wmatom
[WMProtocols
] &&
244 ev
->data
.l
[0] == wmatom
[WMDelete
])
249 configurenotify(const XEvent
*e
)
251 const XConfigureEvent
*ev
= &e
->xconfigure
;
253 if (ev
->window
== win
&& (ev
->width
!= ww
|| ev
->height
!= wh
)) {
256 XFreePixmap(dpy
, dc
.drawable
);
257 dc
.drawable
= XCreatePixmap(dpy
, root
, ww
, wh
,
258 DefaultDepth(dpy
, screen
));
260 resize(sel
, ww
, wh
- bh
);
266 configurerequest(const XEvent
*e
)
268 const XConfigureRequestEvent
*ev
= &e
->xconfigurerequest
;
272 if ((c
= getclient(ev
->window
)) > -1) {
278 wc
.sibling
= ev
->above
;
279 wc
.stack_mode
= ev
->detail
;
280 XConfigureWindow(dpy
, clients
[c
]->win
, ev
->value_mask
, &wc
);
285 createnotify(const XEvent
*e
)
287 const XCreateWindowEvent
*ev
= &e
->xcreatewindow
;
289 if (ev
->window
!= win
&& getclient(ev
->window
) < 0)
294 destroynotify(const XEvent
*e
)
296 const XDestroyWindowEvent
*ev
= &e
->xdestroywindow
;
299 if ((c
= getclient(ev
->window
)) > -1)
304 die(const char *errstr
, ...)
308 va_start(ap
, errstr
);
309 vfprintf(stderr
, errstr
, ap
);
318 int c
, cc
, fc
, width
;
324 XFetchName(dpy
, win
, &name
);
325 drawtext(name
? name
: "", dc
.norm
);
326 XCopyArea(dpy
, dc
.drawable
, win
, dc
.gc
, 0, 0, ww
, bh
, 0, 0);
335 cc
= (ww
- TEXTW(before
) - TEXTW(after
)) / tabwidth
;
337 if ((fc
= getfirsttab()) + cc
< nclients
) {
340 drawtext(after
, dc
.sel
);
346 dc
.w
= TEXTW(before
);
347 drawtext(before
, dc
.sel
);
352 cc
= MIN(cc
, nclients
);
353 for (c
= fc
; c
< fc
+ cc
; c
++) {
359 col
= clients
[c
]->urgent
? dc
.urg
: dc
.norm
;
361 drawtext(clients
[c
]->name
, col
);
363 clients
[c
]->tabx
= dc
.x
;
365 XCopyArea(dpy
, dc
.drawable
, win
, dc
.gc
, 0, 0, ww
, bh
, 0, 0);
370 drawtext(const char *text
, unsigned long col
[ColLast
])
372 int i
, j
, x
, y
, h
, len
, olen
;
374 XRectangle r
= { dc
.x
, dc
.y
, dc
.w
, dc
.h
};
376 XSetForeground(dpy
, dc
.gc
, col
[ColBG
]);
377 XFillRectangles(dpy
, dc
.drawable
, dc
.gc
, &r
, 1);
382 h
= dc
.font
.ascent
+ dc
.font
.descent
;
383 y
= dc
.y
+ (dc
.h
/ 2) - (h
/ 2) + dc
.font
.ascent
;
386 /* shorten text if necessary */
387 for (len
= MIN(olen
, sizeof(buf
));
388 len
&& textnw(text
, len
) > dc
.w
- h
; len
--);
393 memcpy(buf
, text
, len
);
395 for (i
= len
, j
= strlen(titletrim
); j
&& i
;
396 buf
[--i
] = titletrim
[--j
])
400 XSetForeground(dpy
, dc
.gc
, col
[ColFG
]);
402 XmbDrawString(dpy
, dc
.drawable
, dc
.font
.set
,
403 dc
.gc
, x
, y
, buf
, len
);
405 XDrawString(dpy
, dc
.drawable
, dc
.gc
, x
, y
, buf
, len
);
409 ecalloc(size_t n
, size_t size
)
413 if (!(p
= calloc(n
, size
)))
414 die("%s: cannot calloc\n", argv0
);
419 erealloc(void *o
, size_t size
)
423 if (!(p
= realloc(o
, size
)))
424 die("%s: cannot realloc\n", argv0
);
429 expose(const XEvent
*e
)
431 const XExposeEvent
*ev
= &e
->xexpose
;
433 if (ev
->count
== 0 && win
== ev
->window
)
440 char buf
[BUFSIZ
] = "tabbed-"VERSION
" ::";
444 /* If c, sel and clients are -1, raise tabbed-win itself */
446 cmd
[cmd_append_pos
] = NULL
;
447 for(i
= 0, n
= strlen(buf
); cmd
[i
] && n
< sizeof(buf
); i
++)
448 n
+= snprintf(&buf
[n
], sizeof(buf
) - n
, " %s", cmd
[i
]);
451 XRaiseWindow(dpy
, win
);
456 if (c
< 0 || c
>= nclients
)
459 resize(c
, ww
, wh
- bh
);
460 XRaiseWindow(dpy
, clients
[c
]->win
);
461 XSetInputFocus(dpy
, clients
[c
]->win
, RevertToParent
, CurrentTime
);
462 sendxembed(c
, XEMBED_FOCUS_IN
, XEMBED_FOCUS_CURRENT
, 0, 0);
463 sendxembed(c
, XEMBED_WINDOW_ACTIVATE
, 0, 0, 0);
464 xsettitle(win
, clients
[c
]->name
);
471 if (clients
[c
]->urgent
&& (wmh
= XGetWMHints(dpy
, clients
[c
]->win
))) {
472 wmh
->flags
&= ~XUrgencyHint
;
473 XSetWMHints(dpy
, clients
[c
]->win
, wmh
);
474 clients
[c
]->urgent
= False
;
483 focusin(const XEvent
*e
)
485 const XFocusChangeEvent
*ev
= &e
->xfocus
;
489 if (ev
->mode
!= NotifyUngrab
) {
490 XGetInputFocus(dpy
, &focused
, &dummy
);
497 focusonce(const Arg
*arg
)
503 focusurgent(const Arg
*arg
)
507 for (c
= (sel
+ 1) % nclients
; c
!= sel
; c
= (c
+ 1) % nclients
) {
508 if (clients
[c
]->urgent
) {
516 fullscreen(const Arg
*arg
)
520 e
.type
= ClientMessage
;
521 e
.xclient
.window
= win
;
522 e
.xclient
.message_type
= wmatom
[WMState
];
523 e
.xclient
.format
= 32;
524 e
.xclient
.data
.l
[0] = 2;
525 e
.xclient
.data
.l
[1] = wmatom
[WMFullscreen
];
526 e
.xclient
.data
.l
[2] = 0;
527 XSendEvent(dpy
, root
, False
, SubstructureNotifyMask
, &e
);
533 static char buf
[BUFSIZ
];
536 unsigned long ldummy
;
537 unsigned char *p
= NULL
;
539 XGetWindowProperty(dpy
, win
, wmatom
[a
], 0L, BUFSIZ
, False
, XA_STRING
,
540 &adummy
, &idummy
, &ldummy
, &ldummy
, &p
);
542 strncpy(buf
, (char *)p
, LENGTH(buf
)-1);
555 for (i
= 0; i
< nclients
; i
++) {
556 if (clients
[i
]->win
== w
)
564 getcolor(const char *colstr
)
566 Colormap cmap
= DefaultColormap(dpy
, screen
);
569 if (!XAllocNamedColor(dpy
, cmap
, colstr
, &color
, &color
))
570 die("%s: cannot allocate color '%s'\n", argv0
, colstr
);
585 cc
= (ww
- TEXTW(before
) - TEXTW(after
)) / tabwidth
;
587 ret
= sel
- cc
/ 2 + (cc
+ 1) % 2;
589 ret
+ cc
> nclients
? MAX(0, nclients
- cc
) :
594 gettextprop(Window w
, Atom atom
, char *text
, unsigned int size
)
600 if (!text
|| size
== 0)
604 XGetTextProperty(dpy
, w
, &name
, atom
);
608 if (name
.encoding
== XA_STRING
) {
609 strncpy(text
, (char *)name
.value
, size
- 1);
610 } else if (XmbTextPropertyToTextList(dpy
, &name
, &list
, &n
) >= Success
612 strncpy(text
, *list
, size
- 1);
613 XFreeStringList(list
);
615 text
[size
- 1] = '\0';
622 initfont(const char *fontstr
)
624 char *def
, **missing
, **font_names
;
625 XFontStruct
**xfonts
;
630 XFreeFontSet(dpy
, dc
.font
.set
);
632 dc
.font
.set
= XCreateFontSet(dpy
, fontstr
, &missing
, &n
, &def
);
635 fprintf(stderr
, "%s: missing fontset: %s\n",
637 XFreeStringList(missing
);
641 dc
.font
.ascent
= dc
.font
.descent
= 0;
642 n
= XFontsOfFontSet(dc
.font
.set
, &xfonts
, &font_names
);
643 for (i
= 0, dc
.font
.ascent
= 0, dc
.font
.descent
= 0;
645 dc
.font
.ascent
= MAX(dc
.font
.ascent
, (*xfonts
)->ascent
);
646 dc
.font
.descent
= MAX(dc
.font
.descent
,(*xfonts
)->descent
);
651 XFreeFont(dpy
, dc
.font
.xfont
);
652 dc
.font
.xfont
= NULL
;
653 if (!(dc
.font
.xfont
= XLoadQueryFont(dpy
, fontstr
)) &&
654 !(dc
.font
.xfont
= XLoadQueryFont(dpy
, "fixed")))
655 die("%s: cannot load font: '%s'\n", argv0
, fontstr
);
657 dc
.font
.ascent
= dc
.font
.xfont
->ascent
;
658 dc
.font
.descent
= dc
.font
.xfont
->descent
;
660 dc
.font
.height
= dc
.font
.ascent
+ dc
.font
.descent
;
670 if (XGetWMProtocols(dpy
, clients
[c
]->win
, &protocols
, &n
)) {
671 for (i
= 0; !ret
&& i
< n
; i
++) {
672 if (protocols
[i
] == wmatom
[WMDelete
])
682 keypress(const XEvent
*e
)
684 const XKeyEvent
*ev
= &e
->xkey
;
688 keysym
= XkbKeycodeToKeysym(dpy
, (KeyCode
)ev
->keycode
, 0, 0);
689 for (i
= 0; i
< LENGTH(keys
); i
++) {
690 if (keysym
== keys
[i
].keysym
&&
691 CLEANMASK(keys
[i
].mod
) == CLEANMASK(ev
->state
) &&
693 keys
[i
].func(&(keys
[i
].arg
));
698 killclient(const Arg
*arg
)
705 if (isprotodel(sel
) && !clients
[sel
]->closed
) {
706 ev
.type
= ClientMessage
;
707 ev
.xclient
.window
= clients
[sel
]->win
;
708 ev
.xclient
.message_type
= wmatom
[WMProtocols
];
709 ev
.xclient
.format
= 32;
710 ev
.xclient
.data
.l
[0] = wmatom
[WMDelete
];
711 ev
.xclient
.data
.l
[1] = CurrentTime
;
712 XSendEvent(dpy
, clients
[sel
]->win
, False
, NoEventMask
, &ev
);
713 clients
[sel
]->closed
= True
;
715 XKillClient(dpy
, clients
[sel
]->win
);
725 unsigned int modifiers
[] = { 0, LockMask
, numlockmask
,
726 numlockmask
| LockMask
};
731 XWithdrawWindow(dpy
, w
, 0);
732 XReparentWindow(dpy
, w
, win
, 0, bh
);
733 XSelectInput(dpy
, w
, PropertyChangeMask
|
734 StructureNotifyMask
| EnterWindowMask
);
737 for (i
= 0; i
< LENGTH(keys
); i
++) {
738 if ((code
= XKeysymToKeycode(dpy
, keys
[i
].keysym
))) {
739 for (j
= 0; j
< LENGTH(modifiers
); j
++) {
740 XGrabKey(dpy
, code
, keys
[i
].mod
|
741 modifiers
[j
], w
, True
,
742 GrabModeAsync
, GrabModeAsync
);
747 c
= ecalloc(1, sizeof *c
);
751 clients
= erealloc(clients
, sizeof(Client
*) * nclients
);
754 nextpos
= sel
+ newposition
;
757 nextpos
= nclients
- newposition
;
759 nextpos
= newposition
;
761 if (nextpos
>= nclients
)
762 nextpos
= nclients
- 1;
766 if (nclients
> 1 && nextpos
< nclients
- 1)
767 memmove(&clients
[nextpos
+ 1], &clients
[nextpos
],
768 sizeof(Client
*) * (nclients
- nextpos
- 1));
770 clients
[nextpos
] = c
;
771 updatetitle(nextpos
);
773 XLowerWindow(dpy
, w
);
776 e
.xclient
.window
= w
;
777 e
.xclient
.type
= ClientMessage
;
778 e
.xclient
.message_type
= wmatom
[XEmbed
];
779 e
.xclient
.format
= 32;
780 e
.xclient
.data
.l
[0] = CurrentTime
;
781 e
.xclient
.data
.l
[1] = XEMBED_EMBEDDED_NOTIFY
;
782 e
.xclient
.data
.l
[2] = 0;
783 e
.xclient
.data
.l
[3] = win
;
784 e
.xclient
.data
.l
[4] = 0;
785 XSendEvent(dpy
, root
, False
, NoEventMask
, &e
);
789 /* Adjust sel before focus does set it to lastsel. */
792 focus(nextfocus
? nextpos
:
795 nextfocus
= foreground
;
800 maprequest(const XEvent
*e
)
802 const XMapRequestEvent
*ev
= &e
->xmaprequest
;
804 if (getclient(ev
->window
) < 0)
811 if (arg
->i
>= 0 && arg
->i
< nclients
)
816 movetab(const Arg
*arg
)
821 c
= (sel
+ arg
->i
) % nclients
;
825 if (sel
< 0 || c
== sel
)
830 memmove(&clients
[sel
], &clients
[sel
+1],
831 sizeof(Client
*) * (c
- sel
));
833 memmove(&clients
[c
+1], &clients
[c
],
834 sizeof(Client
*) * (sel
- c
));
842 propertynotify(const XEvent
*e
)
844 const XPropertyEvent
*ev
= &e
->xproperty
;
847 char* selection
= NULL
;
850 if (ev
->state
== PropertyNewValue
&& ev
->atom
== wmatom
[WMSelectTab
]) {
851 selection
= getatom(WMSelectTab
);
852 if (!strncmp(selection
, "0x", 2)) {
853 arg
.i
= getclient(strtoul(selection
, NULL
, 0));
856 cmd
[cmd_append_pos
] = selection
;
860 } else if (ev
->state
== PropertyNewValue
&& ev
->atom
== XA_WM_HINTS
&&
861 (c
= getclient(ev
->window
)) > -1 &&
862 (wmh
= XGetWMHints(dpy
, clients
[c
]->win
))) {
863 if (wmh
->flags
& XUrgencyHint
) {
865 wmh
= XGetWMHints(dpy
, win
);
867 if (urgentswitch
&& wmh
&&
868 !(wmh
->flags
& XUrgencyHint
)) {
869 /* only switch, if tabbed was focused
870 * since last urgency hint if WMHints
871 * could not be received,
872 * default to no switch */
875 /* if no switch should be performed,
876 * mark tab as urgent */
877 clients
[c
]->urgent
= True
;
881 if (wmh
&& !(wmh
->flags
& XUrgencyHint
)) {
882 /* update tabbed urgency hint
883 * if not set already */
884 wmh
->flags
|= XUrgencyHint
;
885 XSetWMHints(dpy
, win
, wmh
);
889 } else if (ev
->state
!= PropertyDelete
&& ev
->atom
== XA_WM_NAME
&&
890 (c
= getclient(ev
->window
)) > -1) {
896 resize(int c
, int w
, int h
)
903 ce
.width
= wc
.width
= w
;
904 ce
.height
= wc
.height
= h
;
905 ce
.type
= ConfigureNotify
;
907 ce
.event
= clients
[c
]->win
;
908 ce
.window
= clients
[c
]->win
;
910 ce
.override_redirect
= False
;
913 XConfigureWindow(dpy
, clients
[c
]->win
, CWWidth
| CWHeight
, &wc
);
914 XSendEvent(dpy
, clients
[c
]->win
, False
, StructureNotifyMask
,
919 rotate(const Arg
*arg
)
929 } else if (sel
> -1) {
930 /* Rotating in an arg->i step around the clients. */
932 while (nsel
>= nclients
)
945 /* main event loop */
948 if (doinitspawn
== True
)
952 XNextEvent(dpy
, &ev
);
953 if (handler
[ev
.type
])
954 (handler
[ev
.type
])(&ev
); /* call handler */
959 sendxembed(int c
, long msg
, long detail
, long d1
, long d2
)
963 e
.xclient
.window
= clients
[c
]->win
;
964 e
.xclient
.type
= ClientMessage
;
965 e
.xclient
.message_type
= wmatom
[XEmbed
];
966 e
.xclient
.format
= 32;
967 e
.xclient
.data
.l
[0] = CurrentTime
;
968 e
.xclient
.data
.l
[1] = msg
;
969 e
.xclient
.data
.l
[2] = detail
;
970 e
.xclient
.data
.l
[3] = d1
;
971 e
.xclient
.data
.l
[4] = d2
;
972 XSendEvent(dpy
, clients
[c
]->win
, False
, NoEventMask
, &e
);
976 setcmd(int argc
, char *argv
[], int replace
)
980 cmd
= ecalloc(argc
+ 3, sizeof(*cmd
));
983 for (i
= 0; i
< argc
; i
++)
985 cmd
[replace
> 0 ? replace
: argc
] = winid
;
986 cmd_append_pos
= argc
+ !replace
;
987 cmd
[cmd_append_pos
] = cmd
[cmd_append_pos
+ 1] = NULL
;
993 int bitm
, tx
, ty
, tw
, th
, dh
, dw
, isfixed
;
995 XClassHint class_hint
;
996 XSizeHints
*size_hint
;
998 /* clean up any zombies immediately */
1002 screen
= DefaultScreen(dpy
);
1003 root
= RootWindow(dpy
, screen
);
1005 bh
= dc
.h
= dc
.font
.height
+ 2;
1008 wmatom
[WMDelete
] = XInternAtom(dpy
, "WM_DELETE_WINDOW", False
);
1009 wmatom
[WMFullscreen
] = XInternAtom(dpy
, "_NET_WM_STATE_FULLSCREEN",
1011 wmatom
[WMName
] = XInternAtom(dpy
, "_NET_WM_NAME", False
);
1012 wmatom
[WMProtocols
] = XInternAtom(dpy
, "WM_PROTOCOLS", False
);
1013 wmatom
[WMSelectTab
] = XInternAtom(dpy
, "_TABBED_SELECT_TAB", False
);
1014 wmatom
[WMState
] = XInternAtom(dpy
, "_NET_WM_STATE", False
);
1015 wmatom
[XEmbed
] = XInternAtom(dpy
, "_XEMBED", False
);
1017 /* init appearance */
1025 tx
= ty
= tw
= th
= 0;
1026 bitm
= XParseGeometry(geometry
, &tx
, &ty
, (unsigned *)&tw
,
1032 if (bitm
& WidthValue
)
1034 if (bitm
& HeightValue
)
1036 if (bitm
& XNegative
&& wx
== 0)
1038 if (bitm
& YNegative
&& wy
== 0)
1040 if (bitm
& (HeightValue
| WidthValue
))
1043 dw
= DisplayWidth(dpy
, screen
);
1044 dh
= DisplayHeight(dpy
, screen
);
1046 wx
= dw
+ wx
- ww
- 1;
1048 wy
= dh
+ wy
- wh
- 1;
1051 dc
.norm
[ColBG
] = getcolor(normbgcolor
);
1052 dc
.norm
[ColFG
] = getcolor(normfgcolor
);
1053 dc
.sel
[ColBG
] = getcolor(selbgcolor
);
1054 dc
.sel
[ColFG
] = getcolor(selfgcolor
);
1055 dc
.urg
[ColBG
] = getcolor(urgbgcolor
);
1056 dc
.urg
[ColFG
] = getcolor(urgfgcolor
);
1057 dc
.drawable
= XCreatePixmap(dpy
, root
, ww
, wh
,
1058 DefaultDepth(dpy
, screen
));
1059 dc
.gc
= XCreateGC(dpy
, root
, 0, 0);
1061 XSetFont(dpy
, dc
.gc
, dc
.font
.xfont
->fid
);
1063 win
= XCreateSimpleWindow(dpy
, root
, wx
, wy
, ww
, wh
, 0,
1064 dc
.norm
[ColFG
], dc
.norm
[ColBG
]);
1065 XMapRaised(dpy
, win
);
1066 XSelectInput(dpy
, win
, SubstructureNotifyMask
| FocusChangeMask
|
1067 ButtonPressMask
| ExposureMask
| KeyPressMask
|
1068 PropertyChangeMask
| StructureNotifyMask
|
1069 SubstructureRedirectMask
);
1070 xerrorxlib
= XSetErrorHandler(xerror
);
1072 class_hint
.res_name
= wmname
;
1073 class_hint
.res_class
= "tabbed";
1074 XSetClassHint(dpy
, win
, &class_hint
);
1076 size_hint
= XAllocSizeHints();
1078 size_hint
->flags
= PSize
;
1079 size_hint
->height
= wh
;
1080 size_hint
->width
= ww
;
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
;
1086 wmh
= XAllocWMHints();
1087 XSetWMProperties(dpy
, win
, NULL
, NULL
, NULL
, 0, size_hint
, wmh
, NULL
);
1091 XSetWMProtocols(dpy
, win
, &wmatom
[WMDelete
], 1);
1093 snprintf(winid
, sizeof(winid
), "%lu", win
);
1094 setenv("XEMBED", winid
, 1);
1096 nextfocus
= foreground
;
1103 if (signal(SIGCHLD
, sigchld
) == SIG_ERR
)
1104 die("%s: cannot install SIGCHLD handler", argv0
);
1106 while (0 < waitpid(-1, NULL
, WNOHANG
));
1110 spawn(const Arg
*arg
)
1114 close(ConnectionNumber(dpy
));
1117 if (arg
&& arg
->v
) {
1118 execvp(((char **)arg
->v
)[0], (char **)arg
->v
);
1119 fprintf(stderr
, "%s: execvp %s", argv0
,
1120 ((char **)arg
->v
)[0]);
1122 cmd
[cmd_append_pos
] = NULL
;
1123 execvp(cmd
[0], cmd
);
1124 fprintf(stderr
, "%s: execvp %s", argv0
, cmd
[0]);
1132 textnw(const char *text
, unsigned int len
)
1137 XmbTextExtents(dc
.font
.set
, text
, len
, NULL
, &r
);
1141 return XTextWidth(dc
.font
.xfont
, text
, len
);
1145 toggle(const Arg
*arg
)
1147 *(Bool
*) arg
->v
= !*(Bool
*) arg
->v
;
1153 if (c
< 0 || c
>= nclients
) {
1166 memmove(&clients
[0], &clients
[1], sizeof(Client
*) * nclients
);
1167 } else if (c
== nclients
- 1) {
1171 clients
= erealloc(clients
, sizeof(Client
*) * nclients
);
1173 /* Somewhere inbetween. */
1175 memmove(&clients
[c
], &clients
[c
+1],
1176 sizeof(Client
*) * (nclients
- (c
+ 1)));
1180 if (nclients
<= 0) {
1183 if (closelastclient
)
1185 else if (fillagain
&& running
)
1188 if (lastsel
>= nclients
)
1189 lastsel
= nclients
- 1;
1190 else if (lastsel
> c
)
1193 if (c
== sel
&& lastsel
>= 0) {
1198 if (sel
>= nclients
)
1210 unmapnotify(const XEvent
*e
)
1212 const XUnmapEvent
*ev
= &e
->xunmap
;
1215 if ((c
= getclient(ev
->window
)) > -1)
1220 updatenumlockmask(void)
1223 XModifierKeymap
*modmap
;
1226 modmap
= XGetModifierMapping(dpy
);
1227 for (i
= 0; i
< 8; i
++) {
1228 for (j
= 0; j
< modmap
->max_keypermod
; j
++) {
1229 if (modmap
->modifiermap
[i
* modmap
->max_keypermod
+ j
]
1230 == XKeysymToKeycode(dpy
, XK_Num_Lock
))
1231 numlockmask
= (1 << i
);
1234 XFreeModifiermap(modmap
);
1240 if (!gettextprop(clients
[c
]->win
, wmatom
[WMName
], clients
[c
]->name
,
1241 sizeof(clients
[c
]->name
)))
1242 gettextprop(clients
[c
]->win
, XA_WM_NAME
, clients
[c
]->name
,
1243 sizeof(clients
[c
]->name
));
1245 xsettitle(win
, clients
[c
]->name
);
1249 /* There's no way to check accesses to destroyed windows, thus those cases are
1250 * ignored (especially on UnmapNotify's). Other types of errors call Xlibs
1251 * default error handler, which may call exit. */
1253 xerror(Display
*dpy
, XErrorEvent
*ee
)
1255 if (ee
->error_code
== BadWindow
1256 || (ee
->request_code
== X_SetInputFocus
&&
1257 ee
->error_code
== BadMatch
)
1258 || (ee
->request_code
== X_PolyText8
&&
1259 ee
->error_code
== BadDrawable
)
1260 || (ee
->request_code
== X_PolyFillRectangle
&&
1261 ee
->error_code
== BadDrawable
)
1262 || (ee
->request_code
== X_PolySegment
&&
1263 ee
->error_code
== BadDrawable
)
1264 || (ee
->request_code
== X_ConfigureWindow
&&
1265 ee
->error_code
== BadMatch
)
1266 || (ee
->request_code
== X_GrabButton
&&
1267 ee
->error_code
== BadAccess
)
1268 || (ee
->request_code
== X_GrabKey
&&
1269 ee
->error_code
== BadAccess
)
1270 || (ee
->request_code
== X_CopyArea
&&
1271 ee
->error_code
== BadDrawable
))
1274 fprintf(stderr
, "%s: fatal error: request code=%d, error code=%d\n",
1275 argv0
, ee
->request_code
, ee
->error_code
);
1276 return xerrorxlib(dpy
, ee
); /* may call exit */
1280 xsettitle(Window w
, const char *str
)
1284 if (XmbTextListToTextProperty(dpy
, (char **)&str
, 1,
1285 XCompoundTextStyle
, &xtp
) == Success
) {
1286 XSetTextProperty(dpy
, w
, &xtp
, wmatom
[WMName
]);
1287 XSetTextProperty(dpy
, w
, &xtp
, XA_WM_NAME
);
1295 die("usage: %s [-dfsv] [-g geometry] [-n name] [-p [s+/-]pos]\n"
1296 " [-r narg] [-o color] [-O color] [-t color] [-T color]\n"
1297 " [-u color] [-U color] command...\n", argv0
);
1301 main(int argc
, char *argv
[])
1303 Bool detach
= False
;
1309 closelastclient
= True
;
1319 geometry
= EARGF(usage());
1322 wmname
= EARGF(usage());
1325 normfgcolor
= EARGF(usage());
1328 normbgcolor
= EARGF(usage());
1331 pstr
= EARGF(usage());
1332 if (pstr
[0] == 's') {
1333 npisrelative
= True
;
1334 newposition
= atoi(&pstr
[1]);
1336 newposition
= atoi(pstr
);
1340 replace
= atoi(EARGF(usage()));
1343 doinitspawn
= False
;
1346 selfgcolor
= EARGF(usage());
1349 selbgcolor
= EARGF(usage());
1352 urgfgcolor
= EARGF(usage());
1355 urgbgcolor
= EARGF(usage());
1358 die("tabbed-"VERSION
", © 2009-2016 tabbed engineers, "
1359 "see LICENSE for details.\n");
1367 doinitspawn
= False
;
1371 setcmd(argc
, argv
, replace
);
1373 if (!setlocale(LC_CTYPE
, "") || !XSupportsLocale())
1374 fprintf(stderr
, "%s: no locale support\n", argv0
);
1375 if (!(dpy
= XOpenDisplay(NULL
)))
1376 die("%s: cannot open display\n", argv0
);
1379 printf("0x%lx\n", win
);
1387 close(ConnectionNumber(dpy
));
1388 return EXIT_SUCCESS
;
1396 return EXIT_SUCCESS
;