1 /* Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail>
2 * See LICENSE file for license details.
3 */
4 #include "dat.h"
5 #include <math.h>
6 #include "fns.h"
7
8 uint
frame_idx(Frame * f)9 frame_idx(Frame *f) {
10 Frame *fp;
11 uint i;
12
13 fp = f->area->frame;
14 for(i = 1; fp != f; fp = fp->anext)
15 i++;
16 return i;
17 }
18
19 Frame*
frame_create(Client * c,View * v)20 frame_create(Client *c, View *v) {
21 static ushort id = 1;
22 Frame *f;
23
24 f = emallocz(sizeof *f);
25 f->id = id++;
26 f->client = c;
27 f->view = v;
28
29 if(c->sel) {
30 f->floatr = c->sel->floatr;
31 f->r = c->sel->r;
32 }else {
33 f->r = client_grav(c, c->r);
34 f->floatr = f->r;
35 c->sel = f;
36 }
37 f->collapsed = false;
38 f->screen = -1;
39 f->oldarea = -1;
40 f->oldscreen = -1;
41
42 return f;
43 }
44
45 void
frame_remove(Frame * f)46 frame_remove(Frame *f) {
47 Area *a;
48
49 a = f->area;
50 if(f->aprev)
51 f->aprev->anext = f->anext;
52 if(f->anext)
53 f->anext->aprev = f->aprev;
54 if(f == a->frame)
55 a->frame = f->anext;
56
57 if(a->floating) {
58 if(f->sprev)
59 f->sprev->snext = f->snext;
60 if(f->snext)
61 f->snext->sprev = f->sprev;
62 if(f == a->stack)
63 a->stack = f->snext;
64 }
65 f->anext = f->aprev = f->snext = f->sprev = nil;
66 }
67
68 void
frame_insert(Frame * f,Frame * pos)69 frame_insert(Frame *f, Frame *pos) {
70 Area *a;
71
72 a = f->area;
73
74 if(pos) {
75 assert(pos != f);
76 f->aprev = pos;
77 f->anext = pos->anext;
78 }else {
79 assert(f->area->frame != f);
80 f->anext = f->area->frame;
81 f->area->frame = f;
82 }
83 if(f->aprev)
84 f->aprev->anext = f;
85 if(f->anext)
86 f->anext->aprev = f;
87
88 if(a->floating) {
89 assert(f->sprev == nil);
90 frame_restack(f, nil);
91 }
92 }
93
94 bool
frame_restack(Frame * f,Frame * above)95 frame_restack(Frame *f, Frame *above) {
96 Client *c;
97 Frame *fp;
98 Area *a;
99
100 c = f->client;
101 a = f->area;
102 if(!a->floating)
103 return false;
104 if(f == above)
105 return false;
106
107 if(above == nil && !(c->w.ewmh.type & TypeDock))
108 for(fp=a->stack; fp; fp=fp->snext)
109 if(fp->client->w.ewmh.type & TypeDock)
110 above = fp;
111 else
112 break;
113
114 if(f->sprev || f == a->stack)
115 if(f->sprev == above)
116 return false;
117
118 if(f->sprev)
119 f->sprev->snext = f->snext;
120 else if(f->snext)
121 a->stack = f->snext;
122 if(f->snext)
123 f->snext->sprev = f->sprev;
124
125 f->sprev = above;
126 if(above == nil) {
127 f->snext = a->stack;
128 a->stack = f;
129 }
130 else {
131 f->snext = above->snext;
132 above->snext = f;
133 }
134 if(f->snext)
135 f->snext->sprev = f;
136 assert(f->snext != f && f->sprev != f);
137
138 return true;
139 }
140
141 /* Handlers */
142 static void
bup_event(Window * w,XButtonEvent * e)143 bup_event(Window *w, XButtonEvent *e) {
144 if((e->state & def.mod) != def.mod)
145 XAllowEvents(display, ReplayPointer, e->time);
146 else
147 XUngrabPointer(display, e->time);
148 event("ClientClick %C %d\n", w->aux, e->button);
149 }
150
151 static void
bdown_event(Window * w,XButtonEvent * e)152 bdown_event(Window *w, XButtonEvent *e) {
153 Frame *f;
154 Client *c;
155
156 c = w->aux;
157 f = c->sel;
158
159 if((e->state & def.mod) == def.mod) {
160 switch(e->button) {
161 case Button1:
162 focus(c, false);
163 mouse_resize(c, Center, true);
164 break;
165 case Button2:
166 frame_restack(f, nil);
167 view_restack(f->view);
168 focus(c, false);
169 grabpointer(c->framewin, nil, cursor[CurNone], ButtonReleaseMask);
170 break;
171 case Button3:
172 focus(c, false);
173 mouse_resize(c, quadrant(f->r, Pt(e->x_root, e->y_root)), true);
174 break;
175 default:
176 XAllowEvents(display, ReplayPointer, e->time);
177 break;
178 }
179 }else {
180 if(e->button == Button1) {
181 if(!e->subwindow) {
182 frame_restack(f, nil);
183 view_restack(f->view);
184 mouse_checkresize(f, Pt(e->x, e->y), true);
185 }
186
187 if(f->client != selclient())
188 focus(c, false);
189 }
190 if(e->subwindow)
191 XAllowEvents(display, ReplayPointer, e->time);
192 else {
193 /* Ungrab so a menu can receive events before the button is released */
194 XUngrabPointer(display, e->time);
195 sync();
196
197 event("ClientMouseDown %C %d\n", f->client, e->button);
198 }
199 }
200 }
201
202 static void
config_event(Window * w,XConfigureEvent * e)203 config_event(Window *w, XConfigureEvent *e) {
204
205 USED(w, e);
206 }
207
208 static void
enter_event(Window * w,XCrossingEvent * e)209 enter_event(Window *w, XCrossingEvent *e) {
210 Client *c;
211 Frame *f;
212
213 c = w->aux;
214 f = c->sel;
215 if(disp.focus != c || selclient() != c) {
216 Dprint(DFocus, "enter_notify(f) => [%C]%s%s\n",
217 f->client, f->client->name,
218 ignoreenter == e->serial ? " (ignored)" : "");
219 if(e->detail != NotifyInferior)
220 if(e->serial != ignoreenter && (f->area->floating || !f->collapsed))
221 if(!(c->w.ewmh.type & TypeSplash))
222 focus(f->client, false);
223 }
224 mouse_checkresize(f, Pt(e->x, e->y), false);
225 }
226
227 static void
expose_event(Window * w,XExposeEvent * e)228 expose_event(Window *w, XExposeEvent *e) {
229 Client *c;
230
231 USED(e);
232
233 c = w->aux;
234 if(c->sel)
235 frame_draw(c->sel);
236 else
237 fprint(2, "Badness: Expose event on a client frame which shouldn't be visible: %C\n",
238 c);
239 }
240
241 static void
motion_event(Window * w,XMotionEvent * e)242 motion_event(Window *w, XMotionEvent *e) {
243 Client *c;
244
245 c = w->aux;
246 mouse_checkresize(c->sel, Pt(e->x, e->y), false);
247 }
248
249 Handlers framehandler = {
250 .bup = bup_event,
251 .bdown = bdown_event,
252 .config = config_event,
253 .enter = enter_event,
254 .expose = expose_event,
255 .motion = motion_event,
256 };
257
258 WinHints
frame_gethints(Frame * f)259 frame_gethints(Frame *f) {
260 WinHints h;
261 Client *c;
262 Rectangle r;
263 Point d;
264 int minh;
265
266 minh = labelh(def.font);
267
268 c = f->client;
269 h = *c->w.hints;
270
271 r = frame_client2rect(c, ZR, f->area->floating);
272 d = subpt(r.max, r.min);
273
274 if(!f->area->floating && def.incmode == IIgnore)
275 h.inc = Pt(1, 1);
276
277 if(h.min.x < 2*minh)
278 h.min.x = minh + (2*minh) % h.inc.x;
279 if(h.min.y < minh)
280 h.min.y = minh + minh % h.inc.y;
281
282 h.min.x += d.x;
283 h.min.y += d.y;
284 /* Guard against overflow. */
285 h.max.x = max(h.max.x + d.x, h.max.x);
286 h.max.y = max(h.max.y + d.y, h.max.y);
287
288 h.base.x += d.x;
289 h.base.y += d.y;
290 h.baspect.x += d.x;
291 h.baspect.y += d.y;
292
293 h.group = 0;
294 h.grav = ZP;
295 h.gravstatic = 0;
296 h.position = 0;
297 return h;
298 }
299
300 #define ADJ(PE, ME) \
301 if(c->fullscreen >= 0) \
302 return r; \
303 \
304 if(!floating) { \
305 r.min.x PE 1; \
306 r.min.y PE labelh(def.font); \
307 r.max.x ME 1; \
308 r.max.y ME 1; \
309 }else { \
310 if(!c->borderless) { \
311 r.min.x PE def.border; \
312 r.max.x ME def.border; \
313 r.max.y ME def.border; \
314 } \
315 if(!c->titleless) \
316 r.min.y PE labelh(def.font); \
317 } \
318
319 Rectangle
frame_rect2client(Client * c,Rectangle r,bool floating)320 frame_rect2client(Client *c, Rectangle r, bool floating) {
321
322 ADJ(+=, -=)
323
324 /* Force clients to be at least 1x1 */
325 r.max.x = max(r.max.x, r.min.x+1);
326 r.max.y = max(r.max.y, r.min.y+1);
327 return r;
328 }
329
330 Rectangle
frame_client2rect(Client * c,Rectangle r,bool floating)331 frame_client2rect(Client *c, Rectangle r, bool floating) {
332
333 ADJ(-=, +=)
334
335 return r;
336 }
337
338 #undef ADJ
339
340 void
frame_resize(Frame * f,Rectangle r)341 frame_resize(Frame *f, Rectangle r) {
342 Client *c;
343 Rectangle fr, cr;
344 int collapsed, dx;
345
346 if(btassert("8 full", Dx(r) <= 0 || Dy(r) < 0
347 || Dy(r) == 0 && (!f->area->max || resizing)
348 && !f->collapsed)) {
349 fprint(2, "Frame rect: %R\n", r);
350 r.max.x = min(r.min.x+1, r.max.x);
351 r.max.y = min(r.min.y+1, r.max.y);
352 }
353
354 c = f->client;
355 if(c->fullscreen >= 0) {
356 f->r = screens[c->fullscreen]->r;
357 f->crect = rectsetorigin(f->r, ZP);
358 return;
359 }
360
361 /*
362 if(f->area->floating)
363 f->collapsed = false;
364 */
365
366 fr = frame_hints(f, r, get_sticky(f->r, r));
367 if(f->area->floating && !c->strut)
368 fr = constrain(fr, -1);
369
370 /* Collapse managed frames which are too small */
371 /* XXX. */
372 collapsed = f->collapsed;
373 if(!f->area->floating && f->area->mode == Coldefault) {
374 f->collapsed = false;
375 if(Dy(r) < 2 * labelh(def.font))
376 f->collapsed = true;
377 }
378 if(collapsed != f->collapsed)
379 ewmh_updatestate(c);
380
381 fr.max.x = max(fr.max.x, fr.min.x + 2*labelh(def.font));
382 if(f->collapsed && f->area->floating)
383 fr.max.y = fr.min.y + labelh(def.font);
384
385 cr = frame_rect2client(c, fr, f->area->floating);
386 if(f->area->floating)
387 f->r = fr;
388 else {
389 f->r = r;
390 dx = Dx(r) - Dx(cr);
391 dx -= 2 * (cr.min.x - fr.min.x);
392 cr.min.x += dx / 2;
393 cr.max.x += dx / 2;
394 }
395 f->crect = rectsubpt(cr, f->r.min);
396
397 if(f->area->floating && !f->collapsed)
398 f->floatr = f->r;
399 }
400
401 static void
pushlabel(Image * img,Rectangle * rp,char * s,CTuple * col)402 pushlabel(Image *img, Rectangle *rp, char *s, CTuple *col) {
403 Rectangle r;
404 int w;
405
406 w = textwidth(def.font, s) + def.font->height;
407 w = min(w, Dx(*rp) - 30); /* Magic number. */
408 if(w > 0) {
409 r = *rp;
410 rp->max.x -= w;
411 if(0)
412 drawline(img, Pt(rp->max.x, r.min.y+2),
413 Pt(rp->max.x, r.max.y-2),
414 CapButt, 1, col->border);
415 drawstring(img, def.font, r, East,
416 s, col->fg);
417 }
418 }
419
420 void
frame_draw(Frame * f)421 frame_draw(Frame *f) {
422 Rectangle r, fr;
423 Client *c;
424 CTuple *col;
425 Image *img;
426 char *s;
427 uint w;
428 int n, m;
429
430 if(f->view != selview)
431 return;
432 if(f->area == nil) /* Blech. */
433 return;
434
435 c = f->client;
436 img = *c->ibuf;
437 fr = rectsetorigin(c->framewin->r, ZP);
438
439 /* Pick colors. */
440 if(c == selclient() || c == disp.focus)
441 col = &def.focuscolor;
442 else
443 col = &def.normcolor;
444
445 /* Background/border */
446 r = fr;
447 fill(img, r, col->bg);
448 border(img, r, 1, col->border);
449
450 /* Title border */
451 r.max.y = r.min.y + labelh(def.font);
452 border(img, r, 1, col->border);
453
454 f->titlebar = insetrect(r, 3);
455 f->titlebar.max.y += 3;
456
457 /* Odd focus. Unselected, with keyboard focus. */
458 /* Draw a border just inside the titlebar. */
459 if(c != selclient() && c == disp.focus) {
460 border(img, insetrect(r, 1), 1, def.normcolor.bg);
461 border(img, insetrect(r, 2), 1, def.focuscolor.border);
462 }
463
464 /* grabbox */
465 r.min = Pt(2, 2);
466 r.max.y -= 2;
467 r.max.x = r.min.x + Dy(r);
468 f->grabbox = r;
469
470 if(c->urgent)
471 fill(img, r, col->fg);
472 border(img, r, 1, col->border);
473
474 /* Odd focus. Selected, without keyboard focus. */
475 /* Draw a border around the grabbox. */
476 if(c != disp.focus && col == &def.focuscolor)
477 border(img, insetrect(r, -1), 1, def.normcolor.bg);
478
479 /* Draw a border on borderless+titleless selected apps. */
480 if(f->area->floating && c->borderless && c->titleless && !c->fullscreen && c == selclient())
481 setborder(c->framewin, def.border, def.focuscolor.border);
482 else
483 setborder(c->framewin, 0, def.focuscolor.border);
484
485 /* Label */
486 r.min.x = r.max.x;
487 r.max.x = fr.max.x;
488 r.min.y = 0;
489 r.max.y = labelh(def.font);
490 /* Draw count on frames in 'max' columns. */
491 if(f->area->max && !resizing) {
492 /* XXX */
493 n = stack_count(f, &m);
494 s = smprint("%d/%d", m, n);
495 pushlabel(img, &r, s, col);
496 free(s);
497 }
498 /* Label clients with extra tags. */
499 if((s = client_extratags(c))) {
500 pushlabel(img, &r, s, col);
501 free(s);
502 }else /* Make sure floating clients have room for their indicators. */
503 if(c->floating)
504 r.max.x -= Dx(f->grabbox);
505 w = drawstring(img, def.font, r, West,
506 c->name, col->fg);
507
508 /* Draw inner border on floating clients. */
509 if(f->area->floating) {
510 r.min.x = r.min.x + w + 10;
511 r.max.x += Dx(f->grabbox) - 2;
512 r.min.y = f->grabbox.min.y;
513 r.max.y = f->grabbox.max.y;
514 border(img, r, 1, col->border);
515 }
516
517 /* Border increment gaps... */
518 r.min.y = f->crect.min.y;
519 r.min.x = max(1, f->crect.min.x - 1);
520 r.max.x = min(fr.max.x - 1, f->crect.max.x + 1);
521 r.max.y = min(fr.max.y - 1, f->crect.max.y + 1);
522 border(img, r, 1, col->border);
523
524 /* Why? Because some non-ICCCM-compliant apps feel the need to
525 * change the background properties of all of their ancestor windows
526 * in order to implement pseudo-transparency.
527 * What's more, the designers of X11 felt that it would be unfair to
528 * implementers to make it possible to detect, or forbid, such changes.
529 */
530 XSetWindowBackgroundPixmap(display, c->framewin->xid, None);
531
532 copyimage(c->framewin, fr, img, ZP);
533 }
534
535 void
frame_draw_all(void)536 frame_draw_all(void) {
537 Client *c;
538
539 for(c=client; c; c=c->next)
540 if(c->sel && c->sel->view == selview)
541 frame_draw(c->sel);
542 }
543
544 void
frame_swap(Frame * fa,Frame * fb)545 frame_swap(Frame *fa, Frame *fb) {
546 Frame **fp;
547 Client *c;
548
549 if(fa == fb) return;
550
551 for(fp = &fa->client->frame; *fp; fp = &fp[0]->cnext)
552 if(*fp == fa) break;
553 fp[0] = fp[0]->cnext;
554
555 for(fp = &fb->client->frame; *fp; fp = &fp[0]->cnext)
556 if(*fp == fb) break;
557 fp[0] = fp[0]->cnext;
558
559 c = fa->client;
560 fa->client = fb->client;
561 fb->client = c;
562 fb->cnext = c->frame;
563 c->frame = fb;
564
565 c = fa->client;
566 fa->cnext = c->frame;
567 c->frame = fa;
568
569 if(c->sel)
570 view_update(c->sel->view);
571 }
572
573 void
move_focus(Frame * old_f,Frame * f)574 move_focus(Frame *old_f, Frame *f) {
575 int noinput;
576
577 noinput = (old_f && old_f->client->noinput) ||
578 (f && f->client->noinput) ||
579 disp.hasgrab != &c_root;
580 if(noinput) {
581 if(old_f)
582 frame_draw(old_f);
583 if(f)
584 frame_draw(f);
585 }
586 }
587
588 void
frame_focus(Frame * f)589 frame_focus(Frame *f) {
590 Frame *old_f, *ff;
591 View *v;
592 Area *a, *old_a;
593
594 v = f->view;
595 a = f->area;
596 old_a = v->sel;
597
598 if(0 && f->collapsed) {
599 for(ff=f; ff->collapsed && ff->anext; ff=ff->anext)
600 ;
601 for(; ff->collapsed && ff->aprev; ff=ff->aprev)
602 ;
603 /* XXX */
604 f->colr.max.y = f->colr.min.y + Dy(ff->colr);
605 ff->colr.max.y = ff->colr.min.y + labelh(def.font);
606 }else if(f->area->mode == Coldefault) {
607 for(; f->collapsed && f->anext; f=f->anext)
608 ;
609 for(; f->collapsed && f->aprev; f=f->aprev)
610 ;
611 }
612
613 old_f = old_a->sel;
614 a->sel = f;
615
616 if(a != old_a)
617 area_focus(f->area);
618 if(old_a != v->oldsel && f != old_f)
619 v->oldsel = nil;
620
621 if(v != selview || a != v->sel || resizing)
622 return;
623
624 move_focus(old_f, f);
625 if(a->floating)
626 float_arrange(a);
627 client_focus(f->client);
628
629 /*
630 if(!a->floating && ((a->mode == Colstack) || (a->mode == Colmax)))
631 */
632 column_arrange(a, false);
633 }
634
635 int
frame_delta_h(void)636 frame_delta_h(void) {
637 return def.border + labelh(def.font);
638 }
639
640 Rectangle
constrain(Rectangle r,int inset)641 constrain(Rectangle r, int inset) {
642 WMScreen **sp;
643 WMScreen *s, *sbest;
644 Rectangle isect;
645 Point p;
646 int best, n;
647
648 if(inset < 0)
649 inset = Dy(screen->brect);
650 /*
651 * FIXME: This will cause problems for windows with
652 * D(r) < 2 * inset
653 */
654
655 SET(best);
656 sbest = nil;
657 for(sp=screens; (s = *sp); sp++) {
658 if (!screen->showing)
659 continue;
660 isect = rect_intersection(r, insetrect(s->r, inset));
661 if(Dx(isect) >= 0 && Dy(isect) >= 0)
662 return r;
663 if(Dx(isect) <= 0 && Dy(isect) <= 0)
664 n = max(Dx(isect), Dy(isect));
665 else
666 n = min(Dx(isect), Dy(isect));
667 if(!sbest || n > best) {
668 sbest = s;
669 best = n;
670 }
671 }
672
673 isect = insetrect(sbest->r, inset);
674 p = ZP;
675 p.x -= min(r.max.x - isect.min.x, 0);
676 p.x -= max(r.min.x - isect.max.x, 0);
677 p.y -= min(r.max.y - isect.min.y, 0);
678 p.y -= max(r.min.y - isect.max.y, 0);
679 return rectaddpt(r, p);
680 }
681
682