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