JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
3fb2318bfa064998e5e304d053200223820e5c1b
[dwm.git] / client.c
1 /*
2  * (C)opyright MMVI Anselm R. Garbe <garbeam at gmail dot com>
3  * See LICENSE file for license details.
4  */
5
6 #include <stdlib.h>
7 #include <stdio.h>
8 #include <string.h>
9 #include <X11/Xatom.h>
10 #include <X11/Xutil.h>
11
12 #include "dwm.h"
13
14 void (*arrange)(Arg *) = tiling;
15
16 static Rule rule[] = {
17         /* class                        instance        tags                                            floating */
18         { "Firefox-bin",        "Gecko",        { [Twww] = "www" },                     False },
19 };
20
21 static Client *
22 next(Client *c)
23 {
24         for(; c && !c->tags[tsel]; c = c->next);
25         return c;
26 }
27
28 void
29 zoom(Arg *arg)
30 {
31         Client **l, *old;
32
33         if(!(old = sel))
34                 return;
35
36         for(l = &clients; *l && *l != sel; l = &(*l)->next);
37         *l = sel->next;
38
39         old->next = clients; /* pop */
40         clients = old;
41         sel = old;
42         arrange(NULL);
43         focus(sel);
44 }
45
46 void
47 max(Arg *arg)
48 {
49         if(!sel)
50                 return;
51         sel->x = sx;
52         sel->y = sy;
53         sel->w = sw - 2 * sel->border;
54         sel->h = sh - 2 * sel->border;
55         craise(sel);
56         resize(sel, False);
57         discard_events(EnterWindowMask);
58 }
59
60 void
61 view(Arg *arg)
62 {
63         tsel = arg->i;
64         arrange(NULL);
65 }
66
67 void
68 tappend(Arg *arg)
69 {
70         if(!sel)
71                 return;
72
73         sel->tags[arg->i] = tags[arg->i];
74         arrange(NULL);
75 }
76
77 void
78 ttrunc(Arg *arg)
79 {
80         int i;
81         if(!sel)
82                 return;
83
84         for(i = 0; i < TLast; i++)
85                 sel->tags[i] = NULL;
86         tappend(arg);
87 }
88
89 static void
90 ban_client(Client *c)
91 {
92         XMoveWindow(dpy, c->win, c->x + 2 * sw, c->y);
93         XMoveWindow(dpy, c->title, c->tx + 2 * sw, c->ty);
94 }
95
96 void
97 floating(Arg *arg)
98 {
99         Client *c;
100
101         arrange = floating;
102         for(c = clients; c; c = c->next) {
103                 if(c->tags[tsel])
104                         resize(c, True);
105                 else
106                         ban_client(c);
107         }
108         if(sel && !sel->tags[tsel]) {
109                 if((sel = next(clients))) {
110                         craise(sel);
111                         focus(sel);
112                 }
113         }
114         discard_events(EnterWindowMask);
115 }
116
117 void
118 tiling(Arg *arg)
119 {
120         Client *c;
121         int n, i, w, h;
122
123         w = sw - mw;
124         arrange = tiling;
125         for(n = 0, c = clients; c; c = c->next)
126                 if(c->tags[tsel] && !c->floating)
127                         n++;
128
129         h = (n > 1) ? sh / (n - 1) : sh;
130
131         for(i = 0, c = clients; c; c = c->next) {
132                 if(c->tags[tsel]) {
133                         if(c->floating) {
134                                 craise(c);
135                                 resize(c, True);
136                                 continue;
137                         }
138                         if(n == 1) {
139                                 c->x = sx;
140                                 c->y = sy;
141                                 c->w = sw - 2 * c->border;
142                                 c->h = sh - 2 * c->border;
143                         }
144                         else if(i == 0) {
145                                 c->x = sx;
146                                 c->y = sy;
147                                 c->w = mw - 2 * c->border;
148                                 c->h = sh - 2 * c->border;
149                         }
150                         else {
151                                 c->x = sx + mw;
152                                 c->y = sy + (i - 1) * h;
153                                 c->w = w - 2 * c->border;
154                                 c->h = h - 2 * c->border;
155                         }
156                         resize(c, False);
157                         i++;
158                 }
159                 else
160                         ban_client(c);
161         }
162         if(sel && !sel->tags[tsel]) {
163                 if((sel = next(clients))) {
164                         craise(sel);
165                         focus(sel);
166                 }
167         }
168         discard_events(EnterWindowMask);
169 }
170
171 void
172 prevc(Arg *arg)
173 {
174         Client *c;
175
176         if(!sel)
177                 return;
178
179         if((c = sel->revert && sel->revert->tags[tsel] ? sel->revert : NULL)) {
180                 craise(c);
181                 focus(c);
182         }
183 }
184
185 void
186 nextc(Arg *arg)
187 {
188         Client *c;
189    
190         if(!sel)
191                 return;
192
193         if(!(c = next(sel->next)))
194                 c = next(clients);
195         if(c) {
196                 craise(c);
197                 c->revert = sel;
198                 focus(c);
199         }
200 }
201
202 void
203 ckill(Arg *arg)
204 {
205         if(!sel)
206                 return;
207         if(sel->proto & WM_PROTOCOL_DELWIN)
208                 send_message(sel->win, wm_atom[WMProtocols], wm_atom[WMDelete]);
209         else
210                 XKillClient(dpy, sel->win);
211 }
212
213 static void
214 resize_title(Client *c)
215 {
216         int i;
217
218         c->tw = 0;
219         for(i = 0; i < TLast; i++)
220                 if(c->tags[i])
221                         c->tw += textw(c->tags[i]) + dc.font.height;
222         c->tw += textw(c->name) + dc.font.height;
223         if(c->tw > c->w)
224                 c->tw = c->w + 2;
225         c->tx = c->x + c->w - c->tw + 2;
226         c->ty = c->y;
227         XMoveResizeWindow(dpy, c->title, c->tx, c->ty, c->tw, c->th);
228 }
229
230 void
231 update_name(Client *c)
232 {
233         XTextProperty name;
234         int n;
235         char **list = NULL;
236
237         name.nitems = 0;
238         c->name[0] = 0;
239         XGetTextProperty(dpy, c->win, &name, net_atom[NetWMName]);
240         if(!name.nitems)
241                 XGetWMName(dpy, c->win, &name);
242         if(!name.nitems)
243                 return;
244         if(name.encoding == XA_STRING)
245                 strncpy(c->name, (char *)name.value, sizeof(c->name));
246         else {
247                 if(XmbTextPropertyToTextList(dpy, &name, &list, &n) >= Success
248                                 && n > 0 && *list)
249                 {
250                         strncpy(c->name, *list, sizeof(c->name));
251                         XFreeStringList(list);
252                 }
253         }
254         XFree(name.value);
255         resize_title(c);
256 }
257
258 void
259 update_size(Client *c)
260 {
261         XSizeHints size;
262         long msize;
263         if(!XGetWMNormalHints(dpy, c->win, &size, &msize) || !size.flags)
264                 size.flags = PSize;
265         c->flags = size.flags;
266         if(c->flags & PBaseSize) {
267                 c->basew = size.base_width;
268                 c->baseh = size.base_height;
269         }
270         else
271                 c->basew = c->baseh = 0;
272         if(c->flags & PResizeInc) {
273                 c->incw = size.width_inc;
274                 c->inch = size.height_inc;
275         }
276         else
277                 c->incw = c->inch = 0;
278         if(c->flags & PMaxSize) {
279                 c->maxw = size.max_width;
280                 c->maxh = size.max_height;
281         }
282         else
283                 c->maxw = c->maxh = 0;
284         if(c->flags & PMinSize) {
285                 c->minw = size.min_width;
286                 c->minh = size.min_height;
287         }
288         else
289                 c->minw = c->minh = 0;
290         if(c->flags & PWinGravity)
291                 c->grav = size.win_gravity;
292         else
293                 c->grav = NorthWestGravity;
294 }
295
296 void
297 craise(Client *c)
298 {
299         XRaiseWindow(dpy, c->win);
300         XRaiseWindow(dpy, c->title);
301 }
302
303 void
304 lower(Client *c)
305 {
306         XLowerWindow(dpy, c->title);
307         XLowerWindow(dpy, c->win);
308 }
309
310 void
311 focus(Client *c)
312 {
313         Client *old = sel;
314
315         sel = c;
316         if(old && old != c)
317                 draw_client(old);
318         draw_client(c);
319         XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime);
320         XFlush(dpy);
321         discard_events(EnterWindowMask);
322 }
323
324 static void
325 init_tags(Client *c)
326 {
327         XClassHint ch;
328         static unsigned int len = rule ? sizeof(rule) / sizeof(rule[0]) : 0;
329         unsigned int i, j;
330         Bool matched = False;
331
332         if(!len) {
333                 c->tags[tsel] = tags[tsel];
334                 return;
335         }
336
337         if(XGetClassHint(dpy, c->win, &ch)) {
338                 if(ch.res_class && ch.res_name) {
339                         for(i = 0; i < len; i++)
340                                 if(!strncmp(rule[i].class, ch.res_class, sizeof(rule[i].class))
341                                         && !strncmp(rule[i].instance, ch.res_name, sizeof(rule[i].instance)))
342                                 {
343                                         for(j = 0; j < TLast; j++)
344                                                 c->tags[j] = rule[i].tags[j];
345                                         c->floating = rule[i].floating;
346                                         matched = True;
347                                         break;
348                                 }
349                 }
350                 if(ch.res_class)
351                         XFree(ch.res_class);
352                 if(ch.res_name)
353                         XFree(ch.res_name);
354         }
355
356         if(!matched)
357                 c->tags[tsel] = tags[tsel];
358 }
359
360 void
361 manage(Window w, XWindowAttributes *wa)
362 {
363         Client *c, **l;
364         XSetWindowAttributes twa;
365         Window trans;
366
367         c = emallocz(sizeof(Client));
368         c->win = w;
369         c->tx = c->x = wa->x;
370         c->ty = c->y = wa->y;
371         c->tw = c->w = wa->width;
372         c->h = wa->height;
373         c->th = th;
374         c->border = 1;
375         c->proto = win_proto(c->win);
376         update_size(c);
377         XSelectInput(dpy, c->win,
378                         StructureNotifyMask | PropertyChangeMask | EnterWindowMask);
379         XGetTransientForHint(dpy, c->win, &trans);
380         twa.override_redirect = 1;
381         twa.background_pixmap = ParentRelative;
382         twa.event_mask = ExposureMask;
383
384         c->title = XCreateWindow(dpy, root, c->tx, c->ty, c->tw, c->th,
385                         0, DefaultDepth(dpy, screen), CopyFromParent,
386                         DefaultVisual(dpy, screen),
387                         CWOverrideRedirect | CWBackPixmap | CWEventMask, &twa);
388
389         update_name(c);
390         init_tags(c);
391
392         for(l = &clients; *l; l = &(*l)->next);
393         c->next = *l; /* *l == nil */
394         *l = c;
395
396         XSetWindowBorderWidth(dpy, c->win, 1);
397         XMapRaised(dpy, c->win);
398         XMapRaised(dpy, c->title);
399         XGrabButton(dpy, Button1, Mod1Mask, c->win, False, ButtonPressMask,
400                         GrabModeAsync, GrabModeSync, None, None);
401         XGrabButton(dpy, Button2, Mod1Mask, c->win, False, ButtonPressMask,
402                         GrabModeAsync, GrabModeSync, None, None);
403         XGrabButton(dpy, Button3, Mod1Mask, c->win, False, ButtonPressMask,
404                         GrabModeAsync, GrabModeSync, None, None);
405
406         if(!c->floating)
407                 c->floating = trans
408                         || ((c->maxw == c->minw) && (c->maxh == c->minh));
409
410         arrange(NULL);
411         if(c->tags[tsel])
412                 focus(c);
413         else
414                 ban_client(c);
415 }
416
417 void
418 gravitate(Client *c, Bool invert)
419 {
420         int dx = 0, dy = 0;
421
422         switch(c->grav) {
423         case StaticGravity:
424         case NorthWestGravity:
425         case NorthGravity:
426         case NorthEastGravity:
427                 dy = c->border;
428                 break;
429         case EastGravity:
430         case CenterGravity:
431         case WestGravity:
432                 dy = -(c->h / 2) + c->border;
433                 break;
434         case SouthEastGravity:
435         case SouthGravity:
436         case SouthWestGravity:
437                 dy = -c->h;
438                 break;
439         default:
440                 break;
441         }
442
443         switch (c->grav) {
444         case StaticGravity:
445         case NorthWestGravity:
446         case WestGravity:
447         case SouthWestGravity:
448                 dx = c->border;
449                 break;
450         case NorthGravity:
451         case CenterGravity:
452         case SouthGravity:
453                 dx = -(c->w / 2) + c->border;
454                 break;
455         case NorthEastGravity:
456         case EastGravity:
457         case SouthEastGravity:
458                 dx = -(c->w + c->border);
459                 break;
460         default:
461                 break;
462         }
463
464         if(invert) {
465                 dx = -dx;
466                 dy = -dy;
467         }
468         c->x += dx;
469         c->y += dy;
470 }
471
472
473 void
474 resize(Client *c, Bool inc)
475 {
476         XConfigureEvent e;
477
478         if(inc) {
479                 if(c->incw)
480                         c->w -= (c->w - c->basew) % c->incw;
481                 if(c->inch)
482                         c->h -= (c->h - c->baseh) % c->inch;
483         }
484         if(c->minw && c->w < c->minw)
485                 c->w = c->minw;
486         if(c->minh && c->h < c->minh)
487                 c->h = c->minh;
488         if(c->maxw && c->w > c->maxw)
489                 c->w = c->maxw;
490         if(c->maxh && c->h > c->maxh)
491                 c->h = c->maxh;
492         resize_title(c);
493         XMoveResizeWindow(dpy, c->win, c->x, c->y, c->w, c->h);
494         e.type = ConfigureNotify;
495         e.event = c->win;
496         e.window = c->win;
497         e.x = c->x;
498         e.y = c->y;
499         e.width = c->w;
500         e.height = c->h;
501         e.border_width = c->border;
502         e.above = None;
503         e.override_redirect = False;
504         XSendEvent(dpy, c->win, False, StructureNotifyMask, (XEvent *)&e);
505         XFlush(dpy);
506 }
507
508 static int
509 dummy_error_handler(Display *dsply, XErrorEvent *err)
510 {
511         return 0;
512 }
513
514 void
515 unmanage(Client *c)
516 {
517         Client **l;
518
519         XGrabServer(dpy);
520         XSetErrorHandler(dummy_error_handler);
521
522         XUngrabButton(dpy, AnyButton, AnyModifier, c->win);
523         XDestroyWindow(dpy, c->title);
524
525         for(l = &clients; *l && *l != c; l = &(*l)->next);
526         *l = c->next;
527         for(l = &clients; *l; l = &(*l)->next)
528                 if((*l)->revert == c)
529                         (*l)->revert = NULL;
530         if(sel == c)
531                 sel = sel->revert ? sel->revert : clients;
532
533         free(c);
534
535         XFlush(dpy);
536         XSetErrorHandler(error_handler);
537         XUngrabServer(dpy);
538         arrange(NULL);
539         if(sel)
540                 focus(sel);
541 }
542
543 Client *
544 gettitle(Window w)
545 {
546         Client *c;
547         for(c = clients; c; c = c->next)
548                 if(c->title == w)
549                         return c;
550         return NULL;
551 }
552
553 Client *
554 getclient(Window w)
555 {
556         Client *c;
557         for(c = clients; c; c = c->next)
558                 if(c->win == w)
559                         return c;
560         return NULL;
561 }
562
563 void
564 draw_client(Client *c)
565 {
566         int i;
567         if(c == sel) {
568                 XUnmapWindow(dpy, c->title);
569                 XSetWindowBorder(dpy, c->win, dc.fg);
570                 return;
571         }
572
573         XSetWindowBorder(dpy, c->win, dc.bg);
574         XMapWindow(dpy, c->title);
575
576         dc.x = dc.y = 0;
577         dc.h = c->th;
578
579         dc.w = 0;
580         for(i = 0; i < TLast; i++) {
581                 if(c->tags[i]) {
582                         dc.x += dc.w;
583                         dc.w = textw(c->tags[i]) + dc.font.height;
584                         draw(True, c->tags[i]);
585                 }
586         }
587         dc.x += dc.w;
588         dc.w = textw(c->name) + dc.font.height;
589         draw(True, c->name);
590         XCopyArea(dpy, dc.drawable, c->title, dc.gc,
591                         0, 0, c->tw, c->th, 0, 0);
592         XFlush(dpy);
593 }