]> Dogcows Code - chaz/openbox/blob - openbox/client.c
check for new windows always being onscreen.
[chaz/openbox] / openbox / client.c
1 #include "client.h"
2 #include "screen.h"
3 #include "prop.h"
4 #include "extensions.h"
5 #include "frame.h"
6 #include "engine.h"
7 #include "event.h"
8 #include "grab.h"
9 #include "focus.h"
10 #include "stacking.h"
11 #include "dispatch.h"
12 #include "group.h"
13
14 #include <glib.h>
15 #include <X11/Xutil.h>
16
17 /*! The event mask to grab on client windows */
18 #define CLIENT_EVENTMASK (PropertyChangeMask | FocusChangeMask | \
19 StructureNotifyMask)
20
21 #define CLIENT_NOPROPAGATEMASK (ButtonPressMask | ButtonReleaseMask | \
22 ButtonMotionMask)
23
24 GList *client_list = NULL;
25 GHashTable *client_map = NULL;
26
27 static Window *client_startup_stack_order = NULL;
28 static gulong client_startup_stack_size = 0;
29
30 static void client_get_all(Client *self);
31 static void client_toggle_border(Client *self, gboolean show);
32 static void client_get_area(Client *self);
33 static void client_get_desktop(Client *self);
34 static void client_get_state(Client *self);
35 static void client_get_shaped(Client *self);
36 static void client_get_mwm_hints(Client *self);
37 static void client_get_gravity(Client *self);
38 static void client_showhide(Client *self);
39 static void client_change_allowed_actions(Client *self);
40 static void client_change_state(Client *self);
41 static void client_move_onscreen(Client *self);
42 static Client *search_focus_tree(Client *node, Client *skip);
43 static void client_apply_startup_state(Client *self);
44 static Client *search_modal_tree(Client *node, Client *skip);
45
46 static guint map_hash(Window *w) { return *w; }
47 static gboolean map_key_comp(Window *w1, Window *w2) { return *w1 == *w2; }
48
49 void client_startup()
50 {
51 client_map = g_hash_table_new((GHashFunc)map_hash,
52 (GEqualFunc)map_key_comp);
53
54 /* save the stacking order on startup! */
55 PROP_GET32U(ob_root, net_client_list_stacking, window,
56 client_startup_stack_order, client_startup_stack_size);
57
58 client_set_list();
59 }
60
61 void client_shutdown()
62 {
63 g_hash_table_destroy(client_map);
64 }
65
66 void client_set_list()
67 {
68 Window *windows, *win_it;
69 GList *it;
70 guint size = g_list_length(client_list);
71
72 /* create an array of the window ids */
73 if (size > 0) {
74 windows = g_new(Window, size);
75 win_it = windows;
76 for (it = client_list; it != NULL; it = it->next, ++win_it)
77 *win_it = ((Client*)it->data)->window;
78 } else
79 windows = NULL;
80
81 PROP_SET32A(ob_root, net_client_list, window, windows, size);
82
83 if (windows)
84 g_free(windows);
85
86 stacking_set_list();
87 }
88
89 void client_manage_all()
90 {
91 unsigned int i, j, nchild;
92 Window w, *children;
93 XWMHints *wmhints;
94 XWindowAttributes attrib;
95
96 XQueryTree(ob_display, ob_root, &w, &w, &children, &nchild);
97
98 /* remove all icon windows from the list */
99 for (i = 0; i < nchild; i++) {
100 if (children[i] == None) continue;
101 wmhints = XGetWMHints(ob_display, children[i]);
102 if (wmhints) {
103 if ((wmhints->flags & IconWindowHint) &&
104 (wmhints->icon_window != children[i]))
105 for (j = 0; j < nchild; j++)
106 if (children[j] == wmhints->icon_window) {
107 children[j] = None;
108 break;
109 }
110 XFree(wmhints);
111 }
112 }
113
114 for (i = 0; i < nchild; ++i) {
115 if (children[i] == None)
116 continue;
117 if (XGetWindowAttributes(ob_display, children[i], &attrib)) {
118 if (attrib.override_redirect) continue;
119
120 if (attrib.map_state != IsUnmapped)
121 client_manage(children[i]);
122 }
123 }
124 XFree(children);
125
126 /* stack them as they were on startup!
127 why with stacking_lower? Why, because then windows who aren't in the
128 stacking list are on the top where you can see them instead of buried
129 at the bottom! */
130 for (i = client_startup_stack_size; i > 0; --i) {
131 Client *c;
132
133 w = client_startup_stack_order[i-1];
134 c = g_hash_table_lookup(client_map, &w);
135 if (c) stacking_lower(c);
136 }
137 g_free(client_startup_stack_order);
138 client_startup_stack_order = NULL;
139 client_startup_stack_size = 0;
140
141 if (focus_new)
142 focus_fallback(FALSE);
143 }
144
145 void client_manage(Window window)
146 {
147 Client *self;
148 XEvent e;
149 XWindowAttributes attrib;
150 XSetWindowAttributes attrib_set;
151 /* XWMHints *wmhint; */
152 guint i;
153
154 grab_server(TRUE);
155
156 /* check if it has already been unmapped by the time we started mapping
157 the grab does a sync so we don't have to here */
158 if (XCheckTypedWindowEvent(ob_display, window, DestroyNotify, &e) ||
159 XCheckTypedWindowEvent(ob_display, window, UnmapNotify, &e)) {
160 XPutBackEvent(ob_display, &e);
161
162 grab_server(FALSE);
163 return; /* don't manage it */
164 }
165
166 /* make sure it isn't an override-redirect window */
167 if (!XGetWindowAttributes(ob_display, window, &attrib) ||
168 attrib.override_redirect) {
169 grab_server(FALSE);
170 return; /* don't manage it */
171 }
172
173 /* /\* is the window a docking app *\/
174 if ((wmhint = XGetWMHints(ob_display, window))) {
175 if ((wmhint->flags & StateHint) &&
176 wmhint->initial_state == WithdrawnState) {
177 /\* XXX: make dock apps work! *\/
178 grab_server(FALSE);
179 XFree(wmhint);
180 return;
181 }
182 XFree(wmhint);
183 }
184 */
185
186 /* choose the events we want to receive on the CLIENT window */
187 attrib_set.event_mask = CLIENT_EVENTMASK;
188 attrib_set.do_not_propagate_mask = CLIENT_NOPROPAGATEMASK;
189 XChangeWindowAttributes(ob_display, window,
190 CWEventMask|CWDontPropagate, &attrib_set);
191
192
193 /* create the Client struct, and populate it from the hints on the
194 window */
195 self = g_new(Client, 1);
196 self->window = window;
197 client_get_all(self);
198
199 /* remove the client's border (and adjust re gravity) */
200 client_toggle_border(self, FALSE);
201
202 /* specify that if we exit, the window should not be destroyed and should
203 be reparented back to root automatically */
204 XChangeSaveSet(ob_display, window, SetModeInsert);
205
206 /* create the decoration frame for the client window */
207 self->frame = engine_frame_new();
208
209 engine_frame_grab_client(self->frame, self);
210
211 client_apply_startup_state(self);
212
213 grab_server(FALSE);
214
215 client_list = g_list_append(client_list, self);
216 stacking_list = g_list_append(stacking_list, self);
217 g_assert(!g_hash_table_lookup(client_map, &self->window));
218 g_hash_table_insert(client_map, &self->window, self);
219
220 /* update the focus lists */
221 if (self->desktop == DESKTOP_ALL) {
222 for (i = 0; i < screen_num_desktops; ++i)
223 focus_order[i] = g_list_append(focus_order[i], self);
224 } else {
225 i = self->desktop;
226 focus_order[i] = g_list_append(focus_order[i], self);
227 }
228
229 stacking_raise(self);
230
231 screen_update_struts();
232
233 dispatch_client(Event_Client_New, self, 0, 0);
234
235 client_showhide(self);
236
237 dispatch_client(Event_Client_Mapped, self, 0, 0);
238
239 if (ob_state != State_Starting && focus_new)
240 client_focus(self);
241
242 /* update the list hints */
243 client_set_list();
244
245 /* make sure the window is visible */
246 client_move_onscreen(self);
247
248 /* g_message("Managed window 0x%lx", window);*/
249 }
250
251 void client_unmanage_all()
252 {
253 while (client_list != NULL)
254 client_unmanage(client_list->data);
255 }
256
257 void client_unmanage(Client *self)
258 {
259 guint i;
260 int j;
261 GSList *it;
262
263 /* g_message("Unmanaging window: %lx", self->window);*/
264
265 dispatch_client(Event_Client_Destroy, self, 0, 0);
266 g_assert(self != NULL);
267
268 /* remove the window from our save set */
269 XChangeSaveSet(ob_display, self->window, SetModeDelete);
270
271 /* we dont want events no more */
272 XSelectInput(ob_display, self->window, NoEventMask);
273
274 engine_frame_hide(self->frame);
275
276 client_list = g_list_remove(client_list, self);
277 stacking_list = g_list_remove(stacking_list, self);
278 g_hash_table_remove(client_map, &self->window);
279
280 /* update the focus lists */
281 if (self->desktop == DESKTOP_ALL) {
282 for (i = 0; i < screen_num_desktops; ++i)
283 focus_order[i] = g_list_remove(focus_order[i], self);
284 } else {
285 i = self->desktop;
286 focus_order[i] = g_list_remove(focus_order[i], self);
287 }
288
289 /* once the client is out of the list, update the struts to remove it's
290 influence */
291 screen_update_struts();
292
293 /* tell our parent(s) that we're gone */
294 if (self->transient_for == TRAN_GROUP) { /* transient of group */
295 GSList *it;
296
297 for (it = self->group->members; it; it = it->next)
298 if (it->data != self)
299 ((Client*)it->data)->transients =
300 g_slist_remove(((Client*)it->data)->transients, self);
301 } else if (self->transient_for) { /* transient of window */
302 self->transient_for->transients =
303 g_slist_remove(self->transient_for->transients, self);
304 }
305
306 /* tell our transients that we're gone */
307 for (it = self->transients; it != NULL; it = it->next) {
308 if (((Client*)it->data)->transient_for != TRAN_GROUP) {
309 ((Client*)it->data)->transient_for = NULL;
310 client_calc_layer(it->data);
311 }
312 }
313
314 /* remove from its group */
315 if (self->group) {
316 group_remove(self->group, self);
317 self->group = NULL;
318 }
319
320 /* dispatch the unmapped event */
321 dispatch_client(Event_Client_Unmapped, self, 0, 0);
322 g_assert(self != NULL);
323
324 /* give the client its border back */
325 client_toggle_border(self, TRUE);
326
327 /* reparent the window out of the frame, and free the frame */
328 engine_frame_release_client(self->frame, self);
329 self->frame = NULL;
330
331 if (ob_state != State_Exiting) {
332 /* these values should not be persisted across a window
333 unmapping/mapping */
334 prop_erase(self->window, prop_atoms.net_wm_desktop);
335 prop_erase(self->window, prop_atoms.net_wm_state);
336 } else {
337 /* if we're left in an iconic state, the client wont be mapped. this is
338 bad, since we will no longer be managing the window on restart */
339 if (self->iconic)
340 XMapWindow(ob_display, self->window);
341 }
342
343 /* free all data allocated in the client struct */
344 g_slist_free(self->transients);
345 for (j = 0; j < self->nicons; ++j)
346 g_free(self->icons[j].data);
347 if (self->nicons > 0)
348 g_free(self->icons);
349 g_free(self->title);
350 g_free(self->icon_title);
351 g_free(self->name);
352 g_free(self->class);
353 g_free(self->role);
354 g_free(self);
355
356 /* update the list hints */
357 client_set_list();
358 }
359
360 static void client_move_onscreen(Client *self)
361 {
362 Rect *a;
363 int x = self->frame->area.x, y = self->frame->area.y;
364
365 a = screen_area(self->desktop);
366 if (x >= a->x + a->width - 1)
367 x = a->x + a->width - self->frame->area.width;
368 if (y >= a->y + a->height - 1)
369 y = a->y + a->height - self->frame->area.height;
370 if (x + self->frame->area.width - 1 < a->x)
371 x = a->x;
372 if (y + self->frame->area.height - 1< a->y)
373 y = a->y;
374
375 frame_frame_gravity(self->frame, &x, &y); /* get where the client
376 should be */
377 client_configure(self , Corner_TopLeft, x, y,
378 self->area.width, self->area.height,
379 TRUE, TRUE);
380 }
381
382 static void client_toggle_border(Client *self, gboolean show)
383 {
384 /* adjust our idea of where the client is, based on its border. When the
385 border is removed, the client should now be considered to be in a
386 different position.
387 when re-adding the border to the client, the same operation needs to be
388 reversed. */
389 int oldx = self->area.x, oldy = self->area.y;
390 int x = oldx, y = oldy;
391 switch(self->gravity) {
392 default:
393 case NorthWestGravity:
394 case WestGravity:
395 case SouthWestGravity:
396 break;
397 case NorthEastGravity:
398 case EastGravity:
399 case SouthEastGravity:
400 if (show) x -= self->border_width * 2;
401 else x += self->border_width * 2;
402 break;
403 case NorthGravity:
404 case SouthGravity:
405 case CenterGravity:
406 case ForgetGravity:
407 case StaticGravity:
408 if (show) x -= self->border_width;
409 else x += self->border_width;
410 break;
411 }
412 switch(self->gravity) {
413 default:
414 case NorthWestGravity:
415 case NorthGravity:
416 case NorthEastGravity:
417 break;
418 case SouthWestGravity:
419 case SouthGravity:
420 case SouthEastGravity:
421 if (show) y -= self->border_width * 2;
422 else y += self->border_width * 2;
423 break;
424 case WestGravity:
425 case EastGravity:
426 case CenterGravity:
427 case ForgetGravity:
428 case StaticGravity:
429 if (show) y -= self->border_width;
430 else y += self->border_width;
431 break;
432 }
433 self->area.x = x;
434 self->area.y = y;
435
436 if (show) {
437 XSetWindowBorderWidth(ob_display, self->window, self->border_width);
438
439 /* move the client so it is back it the right spot _with_ its
440 border! */
441 if (x != oldx || y != oldy)
442 XMoveWindow(ob_display, self->window, x, y);
443 } else
444 XSetWindowBorderWidth(ob_display, self->window, 0);
445 }
446
447
448 static void client_get_all(Client *self)
449 {
450 /* update EVERYTHING!! */
451
452 self->ignore_unmaps = 0;
453
454 /* defaults */
455 self->frame = NULL;
456 self->title = self->icon_title = NULL;
457 self->name = self->class = self->role = NULL;
458 self->wmstate = NormalState;
459 self->transient = FALSE;
460 self->transients = NULL;
461 self->transient_for = NULL;
462 self->layer = -1;
463 self->urgent = FALSE;
464 self->positioned = FALSE;
465 self->disabled_decorations = 0;
466 self->group = NULL;
467 self->nicons = 0;
468
469 client_get_area(self);
470 client_get_desktop(self);
471 client_get_state(self);
472 client_get_shaped(self);
473
474 client_update_transient_for(self);
475 client_get_mwm_hints(self);
476 client_get_type(self);/* this can change the mwmhints for special cases */
477
478 client_update_protocols(self);
479
480 client_get_gravity(self); /* get the attribute gravity */
481 client_update_normal_hints(self); /* this may override the attribute
482 gravity */
483
484 /* got the type, the mwmhints, the protocols, and the normal hints
485 (min/max sizes), so we're ready to set up the decorations/functions */
486 client_setup_decor_and_functions(self);
487
488 client_update_wmhints(self);
489 client_update_title(self);
490 client_update_icon_title(self);
491 client_update_class(self);
492 client_update_strut(self);
493 client_update_icons(self);
494 client_update_kwm_icon(self);
495
496 /* this makes sure that these windows appear on all desktops */
497 if (self->type == Type_Desktop)
498 self->desktop = DESKTOP_ALL;
499
500 /* set the desktop hint, to make sure that it always exists, and to
501 reflect any changes we've made here */
502 PROP_SET32(self->window, net_wm_desktop, cardinal, self->desktop);
503
504 client_change_state(self);
505 }
506
507 static void client_get_area(Client *self)
508 {
509 XWindowAttributes wattrib;
510 Status ret;
511
512 ret = XGetWindowAttributes(ob_display, self->window, &wattrib);
513 g_assert(ret != BadWindow);
514
515 RECT_SET(self->area, wattrib.x, wattrib.y, wattrib.width, wattrib.height);
516 self->border_width = wattrib.border_width;
517 }
518
519 static void client_get_desktop(Client *self)
520 {
521 unsigned int d;
522
523 if (PROP_GET32(self->window, net_wm_desktop, cardinal, d)) {
524 if (d >= screen_num_desktops && d != DESKTOP_ALL)
525 d = screen_num_desktops - 1;
526 self->desktop = d;
527 } else {
528 /* defaults to the current desktop */
529 self->desktop = screen_desktop;
530 }
531 }
532
533 static void client_get_state(Client *self)
534 {
535 gulong *state;
536 gulong num;
537
538 self->modal = self->shaded = self->max_horz = self->max_vert =
539 self->fullscreen = self->above = self->below = self->iconic =
540 self->skip_taskbar = self->skip_pager = FALSE;
541
542 if (PROP_GET32U(self->window, net_wm_state, atom, state, num)) {
543 gulong i;
544 for (i = 0; i < num; ++i) {
545 if (state[i] == prop_atoms.net_wm_state_modal)
546 self->modal = TRUE;
547 else if (state[i] == prop_atoms.net_wm_state_shaded)
548 self->shaded = TRUE;
549 else if (state[i] == prop_atoms.net_wm_state_hidden)
550 self->iconic = TRUE;
551 else if (state[i] == prop_atoms.net_wm_state_skip_taskbar)
552 self->skip_taskbar = TRUE;
553 else if (state[i] == prop_atoms.net_wm_state_skip_pager)
554 self->skip_pager = TRUE;
555 else if (state[i] == prop_atoms.net_wm_state_fullscreen)
556 self->fullscreen = TRUE;
557 else if (state[i] == prop_atoms.net_wm_state_maximized_vert)
558 self->max_vert = TRUE;
559 else if (state[i] == prop_atoms.net_wm_state_maximized_horz)
560 self->max_horz = TRUE;
561 else if (state[i] == prop_atoms.net_wm_state_above)
562 self->above = TRUE;
563 else if (state[i] == prop_atoms.net_wm_state_below)
564 self->below = TRUE;
565 }
566
567 g_free(state);
568 }
569 }
570
571 static void client_get_shaped(Client *self)
572 {
573 self->shaped = FALSE;
574 #ifdef SHAPE
575 if (extensions_shape) {
576 int foo;
577 guint ufoo;
578 int s;
579
580 XShapeSelectInput(ob_display, self->window, ShapeNotifyMask);
581
582 XShapeQueryExtents(ob_display, self->window, &s, &foo,
583 &foo, &ufoo, &ufoo, &foo, &foo, &foo, &ufoo,
584 &ufoo);
585 self->shaped = (s != 0);
586 }
587 #endif
588 }
589
590 void client_update_transient_for(Client *self)
591 {
592 Window t = None;
593 Client *c = NULL;
594
595 if (XGetTransientForHint(ob_display, self->window, &t) &&
596 t != self->window) { /* cant be transient to itself! */
597 self->transient = TRUE;
598 c = g_hash_table_lookup(client_map, &t);
599 g_assert(c != self);/* if this happens then we need to check for it*/
600
601 if (!c && self->group) {
602 /* not transient to a client, see if it is transient for a
603 group */
604 if (t == self->group->leader ||
605 t == None ||
606 t == ob_root) {
607 /* window is a transient for its group! */
608 c = TRAN_GROUP;
609 }
610 }
611 } else
612 self->transient = FALSE;
613
614 /* if anything has changed... */
615 if (c != self->transient_for) {
616 if (self->transient_for == TRAN_GROUP) { /* transient of group */
617 GSList *it;
618
619 /* remove from old parents */
620 for (it = self->group->members; it; it = it->next)
621 if (it->data != self)
622 ((Client*)it->data)->transients =
623 g_slist_remove(((Client*)it->data)->transients, self);
624 } else if (self->transient_for != NULL) { /* transient of window */
625 /* remove from old parent */
626 self->transient_for->transients =
627 g_slist_remove(self->transient_for->transients, self);
628 }
629 self->transient_for = c;
630 if (self->transient_for == TRAN_GROUP) { /* transient of group */
631 GSList *it;
632
633 /* add to new parents */
634 for (it = self->group->members; it; it = it->next)
635 if (it->data != self)
636 ((Client*)it->data)->transients =
637 g_slist_append(((Client*)it->data)->transients, self);
638 } else if (self->transient_for != NULL) { /* transient of window */
639 /* add to new parent */
640 self->transient_for->transients =
641 g_slist_append(self->transient_for->transients, self);
642 }
643 }
644 }
645
646 static void client_get_mwm_hints(Client *self)
647 {
648 unsigned long num;
649 unsigned long *hints;
650
651 self->mwmhints.flags = 0; /* default to none */
652
653 if (PROP_GET32U(self->window, motif_wm_hints, motif_wm_hints, hints, num)) {
654 if (num >= MWM_ELEMENTS) {
655 self->mwmhints.flags = hints[0];
656 self->mwmhints.functions = hints[1];
657 self->mwmhints.decorations = hints[2];
658 }
659 g_free(hints);
660 }
661 }
662
663 void client_get_type(Client *self)
664 {
665 gulong *val, num, i;
666
667 self->type = -1;
668
669 if (PROP_GET32U(self->window, net_wm_window_type, atom, val, num)) {
670 /* use the first value that we know about in the array */
671 for (i = 0; i < num; ++i) {
672 if (val[i] == prop_atoms.net_wm_window_type_desktop)
673 self->type = Type_Desktop;
674 else if (val[i] == prop_atoms.net_wm_window_type_dock)
675 self->type = Type_Dock;
676 else if (val[i] == prop_atoms.net_wm_window_type_toolbar)
677 self->type = Type_Toolbar;
678 else if (val[i] == prop_atoms.net_wm_window_type_menu)
679 self->type = Type_Menu;
680 else if (val[i] == prop_atoms.net_wm_window_type_utility)
681 self->type = Type_Utility;
682 else if (val[i] == prop_atoms.net_wm_window_type_splash)
683 self->type = Type_Splash;
684 else if (val[i] == prop_atoms.net_wm_window_type_dialog)
685 self->type = Type_Dialog;
686 else if (val[i] == prop_atoms.net_wm_window_type_normal)
687 self->type = Type_Normal;
688 else if (val[i] == prop_atoms.kde_net_wm_window_type_override) {
689 /* prevent this window from getting any decor or
690 functionality */
691 self->mwmhints.flags &= (MwmFlag_Functions |
692 MwmFlag_Decorations);
693 self->mwmhints.decorations = 0;
694 self->mwmhints.functions = 0;
695 }
696 if (self->type != (WindowType) -1)
697 break; /* grab the first legit type */
698 }
699 g_free(val);
700 }
701
702 if (self->type == (WindowType) -1) {
703 /*the window type hint was not set, which means we either classify
704 ourself as a normal window or a dialog, depending on if we are a
705 transient. */
706 if (self->transient)
707 self->type = Type_Dialog;
708 else
709 self->type = Type_Normal;
710 }
711 }
712
713 void client_update_protocols(Client *self)
714 {
715 Atom *proto;
716 gulong num_return, i;
717
718 self->focus_notify = FALSE;
719 self->delete_window = FALSE;
720
721 if (PROP_GET32U(self->window, wm_protocols, atom, proto, num_return)) {
722 for (i = 0; i < num_return; ++i) {
723 if (proto[i] == prop_atoms.wm_delete_window) {
724 /* this means we can request the window to close */
725 self->delete_window = TRUE;
726 } else if (proto[i] == prop_atoms.wm_take_focus)
727 /* if this protocol is requested, then the window will be
728 notified whenever we want it to receive focus */
729 self->focus_notify = TRUE;
730 }
731 g_free(proto);
732 }
733 }
734
735 static void client_get_gravity(Client *self)
736 {
737 XWindowAttributes wattrib;
738 Status ret;
739
740 ret = XGetWindowAttributes(ob_display, self->window, &wattrib);
741 g_assert(ret != BadWindow);
742 self->gravity = wattrib.win_gravity;
743 }
744
745 void client_update_normal_hints(Client *self)
746 {
747 XSizeHints size;
748 long ret;
749 int oldgravity = self->gravity;
750
751 /* defaults */
752 self->min_ratio = 0.0f;
753 self->max_ratio = 0.0f;
754 SIZE_SET(self->size_inc, 1, 1);
755 SIZE_SET(self->base_size, 0, 0);
756 SIZE_SET(self->min_size, 0, 0);
757 SIZE_SET(self->max_size, G_MAXINT, G_MAXINT);
758
759 /* get the hints from the window */
760 if (XGetWMNormalHints(ob_display, self->window, &size, &ret)) {
761 self->positioned = !!(size.flags & (PPosition|USPosition));
762
763 if (size.flags & PWinGravity) {
764 self->gravity = size.win_gravity;
765
766 /* if the client has a frame, i.e. has already been mapped and
767 is changing its gravity */
768 if (self->frame && self->gravity != oldgravity) {
769 /* move our idea of the client's position based on its new
770 gravity */
771 self->area.x = self->frame->area.x;
772 self->area.y = self->frame->area.y;
773 frame_frame_gravity(self->frame, &self->area.x, &self->area.y);
774 }
775 }
776
777 if (size.flags & PAspect) {
778 if (size.min_aspect.y)
779 self->min_ratio = (float)size.min_aspect.x / size.min_aspect.y;
780 if (size.max_aspect.y)
781 self->max_ratio = (float)size.max_aspect.x / size.max_aspect.y;
782 }
783
784 if (size.flags & PMinSize)
785 SIZE_SET(self->min_size, size.min_width, size.min_height);
786
787 if (size.flags & PMaxSize)
788 SIZE_SET(self->max_size, size.max_width, size.max_height);
789
790 if (size.flags & PBaseSize)
791 SIZE_SET(self->base_size, size.base_width, size.base_height);
792
793 if (size.flags & PResizeInc)
794 SIZE_SET(self->size_inc, size.width_inc, size.height_inc);
795 }
796 }
797
798 void client_setup_decor_and_functions(Client *self)
799 {
800 /* start with everything (cept fullscreen) */
801 self->decorations = Decor_Titlebar | Decor_Handle | Decor_Border |
802 Decor_Icon | Decor_AllDesktops | Decor_Iconify | Decor_Maximize |
803 Decor_Shade;
804 self->functions = Func_Resize | Func_Move | Func_Iconify | Func_Maximize |
805 Func_Shade;
806 if (self->delete_window) {
807 self->decorations |= Decor_Close;
808 self->functions |= Func_Close;
809 }
810
811 if (!(self->min_size.width < self->max_size.width ||
812 self->min_size.height < self->max_size.height)) {
813 self->decorations &= ~(Decor_Maximize | Decor_Handle);
814 self->functions &= ~(Func_Resize | Func_Maximize);
815 }
816
817 switch (self->type) {
818 case Type_Normal:
819 /* normal windows retain all of the possible decorations and
820 functionality, and are the only windows that you can fullscreen */
821 self->functions |= Func_Fullscreen;
822 break;
823
824 case Type_Dialog:
825 /* dialogs cannot be maximized */
826 self->decorations &= ~Decor_Maximize;
827 self->functions &= ~Func_Maximize;
828 break;
829
830 case Type_Menu:
831 case Type_Toolbar:
832 case Type_Utility:
833 /* these windows get less functionality */
834 self->decorations &= ~(Decor_Iconify | Decor_Handle);
835 self->functions &= ~(Func_Iconify | Func_Resize);
836 break;
837
838 case Type_Desktop:
839 case Type_Dock:
840 case Type_Splash:
841 /* none of these windows are manipulated by the window manager */
842 self->decorations = 0;
843 self->functions = 0;
844 break;
845 }
846
847 /* Mwm Hints are applied subtractively to what has already been chosen for
848 decor and functionality */
849 if (self->mwmhints.flags & MwmFlag_Decorations) {
850 if (! (self->mwmhints.decorations & MwmDecor_All)) {
851 if (! (self->mwmhints.decorations & MwmDecor_Border))
852 self->decorations &= ~Decor_Border;
853 if (! (self->mwmhints.decorations & MwmDecor_Handle))
854 self->decorations &= ~Decor_Handle;
855 if (! (self->mwmhints.decorations & MwmDecor_Title))
856 self->decorations &= ~Decor_Titlebar;
857 if (! (self->mwmhints.decorations & MwmDecor_Iconify))
858 self->decorations &= ~Decor_Iconify;
859 if (! (self->mwmhints.decorations & MwmDecor_Maximize))
860 self->decorations &= ~Decor_Maximize;
861 }
862 }
863
864 if (self->mwmhints.flags & MwmFlag_Functions) {
865 if (! (self->mwmhints.functions & MwmFunc_All)) {
866 if (! (self->mwmhints.functions & MwmFunc_Resize))
867 self->functions &= ~Func_Resize;
868 if (! (self->mwmhints.functions & MwmFunc_Move))
869 self->functions &= ~Func_Move;
870 if (! (self->mwmhints.functions & MwmFunc_Iconify))
871 self->functions &= ~Func_Iconify;
872 if (! (self->mwmhints.functions & MwmFunc_Maximize))
873 self->functions &= ~Func_Maximize;
874 /* dont let mwm hints kill the close button
875 if (! (self->mwmhints.functions & MwmFunc_Close))
876 self->functions &= ~Func_Close; */
877 }
878 }
879
880 /* can't maximize without moving/resizing */
881 if (!((self->functions & Func_Move) && (self->functions & Func_Resize)))
882 self->functions &= ~(Func_Maximize | Func_Fullscreen);
883
884 /* finally, user specified disabled decorations are applied to subtract
885 decorations */
886 if (self->disabled_decorations & Decor_Titlebar)
887 self->decorations &= ~Decor_Titlebar;
888 if (self->disabled_decorations & Decor_Handle)
889 self->decorations &= ~Decor_Handle;
890 if (self->disabled_decorations & Decor_Border)
891 self->decorations &= ~Decor_Border;
892 if (self->disabled_decorations & Decor_Iconify)
893 self->decorations &= ~Decor_Iconify;
894 if (self->disabled_decorations & Decor_Maximize)
895 self->decorations &= ~Decor_Maximize;
896 if (self->disabled_decorations & Decor_AllDesktops)
897 self->decorations &= ~Decor_AllDesktops;
898 if (self->disabled_decorations & Decor_Shade)
899 self->decorations &= ~Decor_Shade;
900 if (self->disabled_decorations & Decor_Close)
901 self->decorations &= ~Decor_Close;
902
903 /* if we don't have a titlebar, then we cannot shade! */
904 if (!(self->decorations & Decor_Titlebar))
905 self->functions &= ~Func_Shade;
906
907 client_change_allowed_actions(self);
908
909 if (self->frame) {
910 /* change the decors on the frame, and with more/less decorations,
911 we may also need to be repositioned */
912 engine_frame_adjust_area(self->frame, TRUE, TRUE);
913 /* with new decor, the window's maximized size may change */
914 client_remaximize(self);
915 }
916 }
917
918 static void client_change_allowed_actions(Client *self)
919 {
920 Atom actions[9];
921 int num = 0;
922
923 actions[num++] = prop_atoms.net_wm_action_change_desktop;
924
925 if (self->functions & Func_Shade)
926 actions[num++] = prop_atoms.net_wm_action_shade;
927 if (self->functions & Func_Close)
928 actions[num++] = prop_atoms.net_wm_action_close;
929 if (self->functions & Func_Move)
930 actions[num++] = prop_atoms.net_wm_action_move;
931 if (self->functions & Func_Iconify)
932 actions[num++] = prop_atoms.net_wm_action_minimize;
933 if (self->functions & Func_Resize)
934 actions[num++] = prop_atoms.net_wm_action_resize;
935 if (self->functions & Func_Fullscreen)
936 actions[num++] = prop_atoms.net_wm_action_fullscreen;
937 if (self->functions & Func_Maximize) {
938 actions[num++] = prop_atoms.net_wm_action_maximize_horz;
939 actions[num++] = prop_atoms.net_wm_action_maximize_vert;
940 }
941
942 PROP_SET32A(self->window, net_wm_allowed_actions, atom, actions, num);
943
944 /* make sure the window isn't breaking any rules now */
945
946 if (!(self->functions & Func_Shade) && self->shaded) {
947 if (self->frame) client_shade(self, FALSE);
948 else self->shaded = FALSE;
949 }
950 if (!(self->functions & Func_Iconify) && self->iconic) {
951 g_message("UNSETTING ICONIC");
952 if (self->frame) client_iconify(self, FALSE, TRUE);
953 else self->iconic = FALSE;
954 }
955 if (!(self->functions & Func_Fullscreen) && self->fullscreen) {
956 if (self->frame) client_fullscreen(self, FALSE, TRUE);
957 else self->fullscreen = FALSE;
958 }
959 if (!(self->functions & Func_Maximize) && (self->max_horz ||
960 self->max_vert)) {
961 if (self->frame) client_maximize(self, FALSE, 0, TRUE);
962 else self->max_vert = self->max_horz = FALSE;
963 }
964 }
965
966 void client_remaximize(Client *self)
967 {
968 int dir;
969 if (self->max_horz && self->max_vert)
970 dir = 0;
971 else if (self->max_horz)
972 dir = 1;
973 else if (self->max_vert)
974 dir = 2;
975 else
976 return; /* not maximized */
977 self->max_horz = self->max_vert = FALSE;
978 client_maximize(self, TRUE, dir, FALSE);
979 }
980
981 void client_update_wmhints(Client *self)
982 {
983 XWMHints *hints;
984 gboolean ur = FALSE;
985
986 /* assume a window takes input if it doesnt specify */
987 self->can_focus = TRUE;
988
989 if ((hints = XGetWMHints(ob_display, self->window)) != NULL) {
990 if (hints->flags & InputHint)
991 self->can_focus = hints->input;
992
993 /* only do this when first managing the window *AND* when we aren't
994 starting up! */
995 if (ob_state != State_Starting && self->frame == NULL)
996 if (hints->flags & StateHint)
997 self->iconic = hints->initial_state == IconicState;
998
999 if (hints->flags & XUrgencyHint)
1000 ur = TRUE;
1001
1002 if (!(hints->flags & WindowGroupHint))
1003 hints->window_group = None; /* no group */
1004 /* did the group state change? */
1005 if (hints->window_group != (self->group ? self->group->leader : None)){
1006 /* remove from the old group if there was one */
1007 if (self->group != NULL)
1008 group_remove(self->group, self);
1009 if (hints->window_group != None)
1010 self->group = group_add(hints->window_group, self);
1011
1012 /* because the self->transient flag wont change from this call,
1013 we don't need to update the window's type and such, only its
1014 transient_for, and the transients lists of other windows in the
1015 group may be affected */
1016 client_update_transient_for(self);
1017 }
1018
1019 if (hints->flags & IconPixmapHint) {
1020 client_update_kwm_icon(self);
1021 /* try get the kwm icon first, this is a fallback only */
1022 if (self->pixmap_icon == None) {
1023 self->pixmap_icon = hints->icon_pixmap;
1024 if (hints->flags & IconMaskHint)
1025 self->pixmap_icon_mask = hints->icon_mask;
1026 else
1027 self->pixmap_icon_mask = None;
1028
1029 if (self->frame)
1030 engine_frame_adjust_icon(self->frame);
1031 }
1032 }
1033
1034 XFree(hints);
1035 }
1036
1037 if (ur != self->urgent) {
1038 self->urgent = ur;
1039 g_message("Urgent Hint for 0x%lx: %s", self->window,
1040 ur ? "ON" : "OFF");
1041 /* fire the urgent callback if we're mapped, otherwise, wait until
1042 after we're mapped */
1043 if (self->frame)
1044 dispatch_client(Event_Client_Urgent, self, self->urgent, 0);
1045 }
1046 }
1047
1048 void client_update_title(Client *self)
1049 {
1050 gchar *data = NULL;
1051
1052 g_free(self->title);
1053
1054 /* try netwm */
1055 if (!PROP_GETS(self->window, net_wm_name, utf8, data)) {
1056 /* try old x stuff */
1057 if (PROP_GETS(self->window, wm_name, string, data)) {
1058 /* convert it to UTF-8 */
1059 gsize r, w;
1060 gchar *u;
1061
1062 u = g_locale_to_utf8(data, -1, &r, &w, NULL);
1063 if (u == NULL) {
1064 g_warning("Unable to convert string to UTF-8");
1065 } else {
1066 g_free(data);
1067 data = u;
1068 }
1069 }
1070 if (data == NULL)
1071 data = g_strdup("Unnamed Window");
1072
1073 PROP_SETS(self->window, net_wm_visible_name, utf8, data);
1074 }
1075
1076 self->title = data;
1077
1078 if (self->frame)
1079 engine_frame_adjust_title(self->frame);
1080 }
1081
1082 void client_update_icon_title(Client *self)
1083 {
1084 gchar *data = NULL;
1085
1086 g_free(self->icon_title);
1087
1088 /* try netwm */
1089 if (!PROP_GETS(self->window, net_wm_icon_name, utf8, data)) {
1090 /* try old x stuff */
1091 if (PROP_GETS(self->window, wm_icon_name, string, data)) {
1092 /* convert it to UTF-8 */
1093 gsize r, w;
1094 gchar *u;
1095
1096 u = g_locale_to_utf8(data, -1, &r, &w, NULL);
1097 if (u == NULL) {
1098 g_warning("Unable to convert string to UTF-8");
1099 } else {
1100 g_free(data);
1101 data = u;
1102 }
1103 }
1104 if (data == NULL)
1105 data = g_strdup("Unnamed Window");
1106
1107 PROP_SETS(self->window, net_wm_visible_icon_name, utf8, data);
1108 }
1109
1110 self->icon_title = data;
1111 }
1112
1113 void client_update_class(Client *self)
1114 {
1115 GPtrArray *data;
1116 gchar *s;
1117 guint i;
1118
1119 if (self->name) g_free(self->name);
1120 if (self->class) g_free(self->class);
1121 if (self->role) g_free(self->role);
1122
1123 self->name = self->class = self->role = NULL;
1124
1125 data = g_ptr_array_new();
1126
1127 if (PROP_GETSA(self->window, wm_class, string, data)) {
1128 if (data->len > 0)
1129 self->name = g_strdup(g_ptr_array_index(data, 0));
1130 if (data->len > 1)
1131 self->class = g_strdup(g_ptr_array_index(data, 1));
1132 }
1133
1134 for (i = 0; i < data->len; ++i)
1135 g_free(g_ptr_array_index(data, i));
1136 g_ptr_array_free(data, TRUE);
1137
1138 if (PROP_GETS(self->window, wm_window_role, string, s))
1139 self->role = g_strdup(s);
1140
1141 if (self->name == NULL) self->name = g_strdup("");
1142 if (self->class == NULL) self->class = g_strdup("");
1143 if (self->role == NULL) self->role = g_strdup("");
1144 }
1145
1146 void client_update_strut(Client *self)
1147 {
1148 gulong *data;
1149
1150 if (PROP_GET32A(self->window, net_wm_strut, cardinal, data, 4)) {
1151 STRUT_SET(self->strut, data[0], data[2], data[1], data[3]);
1152 g_free(data);
1153 } else
1154 STRUT_SET(self->strut, 0, 0, 0, 0);
1155
1156 /* updating here is pointless while we're being mapped cuz we're not in
1157 the client list yet */
1158 if (self->frame)
1159 screen_update_struts();
1160 }
1161
1162 void client_update_icons(Client *self)
1163 {
1164 unsigned long num;
1165 unsigned long *data;
1166 unsigned long w, h, i;
1167 int j;
1168
1169 for (j = 0; j < self->nicons; ++j)
1170 g_free(self->icons[j].data);
1171 if (self->nicons > 0)
1172 g_free(self->icons);
1173 self->nicons = 0;
1174
1175 if (PROP_GET32U(self->window, net_wm_icon, cardinal, data, num)) {
1176 /* figure out how many valid icons are in here */
1177 i = 0;
1178 while (num - i > 2) {
1179 w = data[i++];
1180 h = data[i++];
1181 i += w * h;
1182 if (i > num) break;
1183 ++self->nicons;
1184 }
1185
1186 self->icons = g_new(Icon, self->nicons);
1187
1188 /* store the icons */
1189 i = 0;
1190 for (j = 0; j < self->nicons; ++j) {
1191 w = self->icons[j].width = data[i++];
1192 h = self->icons[j].height = data[i++];
1193 self->icons[j].data =
1194 g_memdup(&data[i], w * h * sizeof(gulong));
1195 i += w * h;
1196 g_assert(i <= num);
1197 }
1198
1199 g_free(data);
1200 }
1201
1202 if (self->frame)
1203 engine_frame_adjust_icon(self->frame);
1204 }
1205
1206 void client_update_kwm_icon(Client *self)
1207 {
1208 Pixmap *data;
1209
1210 if (PROP_GET32A(self->window, kwm_win_icon, kwm_win_icon, data, 2)) {
1211 self->pixmap_icon = data[0];
1212 self->pixmap_icon_mask = data[1];
1213 g_free(data);
1214 } else {
1215 self->pixmap_icon = self->pixmap_icon_mask = None;
1216 }
1217 if (self->frame)
1218 engine_frame_adjust_icon(self->frame);
1219 }
1220
1221 static void client_change_state(Client *self)
1222 {
1223 unsigned long state[2];
1224 Atom netstate[10];
1225 int num;
1226
1227 state[0] = self->wmstate;
1228 state[1] = None;
1229 PROP_SET32A(self->window, wm_state, wm_state, state, 2);
1230
1231 num = 0;
1232 if (self->modal)
1233 netstate[num++] = prop_atoms.net_wm_state_modal;
1234 if (self->shaded)
1235 netstate[num++] = prop_atoms.net_wm_state_shaded;
1236 if (self->iconic)
1237 netstate[num++] = prop_atoms.net_wm_state_hidden;
1238 if (self->skip_taskbar)
1239 netstate[num++] = prop_atoms.net_wm_state_skip_taskbar;
1240 if (self->skip_pager)
1241 netstate[num++] = prop_atoms.net_wm_state_skip_pager;
1242 if (self->fullscreen)
1243 netstate[num++] = prop_atoms.net_wm_state_fullscreen;
1244 if (self->max_vert)
1245 netstate[num++] = prop_atoms.net_wm_state_maximized_vert;
1246 if (self->max_horz)
1247 netstate[num++] = prop_atoms.net_wm_state_maximized_horz;
1248 if (self->above)
1249 netstate[num++] = prop_atoms.net_wm_state_above;
1250 if (self->below)
1251 netstate[num++] = prop_atoms.net_wm_state_below;
1252 PROP_SET32A(self->window, net_wm_state, atom, netstate, num);
1253
1254 client_calc_layer(self);
1255
1256 if (self->frame)
1257 engine_frame_adjust_state(self->frame);
1258 }
1259
1260 static Client *search_focus_tree(Client *node, Client *skip)
1261 {
1262 GSList *it;
1263 Client *ret;
1264
1265 for (it = node->transients; it != NULL; it = it->next) {
1266 Client *c = it->data;
1267 if (c == skip) continue; /* circular? */
1268 if ((ret = search_focus_tree(c, skip))) return ret;
1269 if (client_focused(c)) return c;
1270 }
1271 return NULL;
1272 }
1273
1274 void client_calc_layer(Client *self)
1275 {
1276 StackLayer l;
1277 gboolean fs;
1278 Client *c;
1279
1280 /* are we fullscreen, or do we have a fullscreen transient parent? */
1281 c = self;
1282 fs = FALSE;
1283 while (c && c != TRAN_GROUP) { /* XXX do smthng with the TRAN_GROUP case?*/
1284 if (c->fullscreen) {
1285 fs = TRUE;
1286 break;
1287 }
1288 c = c->transient_for;
1289 }
1290 if (!fs && self->fullscreen) {
1291 /* is one of our transients focused? */
1292 c = search_focus_tree(self, self);
1293 if (c != NULL) fs = TRUE;
1294 }
1295
1296 if (self->iconic) l = Layer_Icon;
1297 else if (fs) l = Layer_Fullscreen;
1298 else if (self->type == Type_Desktop) l = Layer_Desktop;
1299 else if (self->type == Type_Dock) {
1300 if (!self->below) l = Layer_Top;
1301 else l = Layer_Normal;
1302 }
1303 else if (self->above) l = Layer_Above;
1304 else if (self->below) l = Layer_Below;
1305 else l = Layer_Normal;
1306
1307 if (l != self->layer) {
1308 self->layer = l;
1309 if (self->frame)
1310 stacking_raise(self);
1311 }
1312 }
1313
1314 gboolean client_should_show(Client *self)
1315 {
1316 if (self->iconic) return FALSE;
1317 else if (!(self->desktop == screen_desktop ||
1318 self->desktop == DESKTOP_ALL)) return FALSE;
1319 else if (client_normal(self) && screen_showing_desktop) return FALSE;
1320
1321 return TRUE;
1322 }
1323
1324 static void client_showhide(Client *self)
1325 {
1326
1327 if (client_should_show(self))
1328 engine_frame_show(self->frame);
1329 else
1330 engine_frame_hide(self->frame);
1331 }
1332
1333 gboolean client_normal(Client *self) {
1334 return ! (self->type == Type_Desktop || self->type == Type_Dock ||
1335 self->type == Type_Splash);
1336 }
1337
1338 static void client_apply_startup_state(Client *self)
1339 {
1340 /* these are in a carefully crafted order.. */
1341
1342 if (self->iconic) {
1343 self->iconic = FALSE;
1344 client_iconify(self, TRUE, FALSE);
1345 }
1346 if (self->fullscreen) {
1347 self->fullscreen = FALSE;
1348 client_fullscreen(self, TRUE, FALSE);
1349 }
1350 if (self->shaded) {
1351 self->shaded = FALSE;
1352 client_shade(self, TRUE);
1353 }
1354 if (self->urgent)
1355 dispatch_client(Event_Client_Urgent, self, self->urgent, 0);
1356
1357 if (self->max_vert && self->max_horz) {
1358 self->max_vert = self->max_horz = FALSE;
1359 client_maximize(self, TRUE, 0, FALSE);
1360 } else if (self->max_vert) {
1361 self->max_vert = FALSE;
1362 client_maximize(self, TRUE, 2, FALSE);
1363 } else if (self->max_horz) {
1364 self->max_horz = FALSE;
1365 client_maximize(self, TRUE, 1, FALSE);
1366 }
1367
1368 /* nothing to do for the other states:
1369 skip_taskbar
1370 skip_pager
1371 modal
1372 above
1373 below
1374 */
1375 }
1376
1377 void client_configure(Client *self, Corner anchor, int x, int y, int w, int h,
1378 gboolean user, gboolean final)
1379 {
1380 gboolean moved = FALSE, resized = FALSE;
1381
1382 /* gets the frame's position */
1383 frame_client_gravity(self->frame, &x, &y);
1384
1385 /* these positions are frame positions, not client positions */
1386
1387 /* set the size and position if fullscreen */
1388 if (self->fullscreen) {
1389 x = 0;
1390 y = 0;
1391 w = screen_physical_size.width;
1392 h = screen_physical_size.height;
1393 } else {
1394 /* set the size and position if maximized */
1395 if (self->max_horz) {
1396 x = screen_area(self->desktop)->x - self->frame->size.left;
1397 w = screen_area(self->desktop)->width;
1398 }
1399 if (self->max_vert) {
1400 y = screen_area(self->desktop)->y;
1401 h = screen_area(self->desktop)->height -
1402 self->frame->size.top - self->frame->size.bottom;
1403 }
1404 }
1405
1406 /* gets the client's position */
1407 frame_frame_gravity(self->frame, &x, &y);
1408
1409 /* these override the above states! if you cant move you can't move! */
1410 if (user) {
1411 if (!(self->functions & Func_Move)) {
1412 x = self->area.x;
1413 y = self->area.y;
1414 }
1415 if (!(self->functions & Func_Resize)) {
1416 w = self->area.width;
1417 h = self->area.height;
1418 }
1419 }
1420
1421 if (!(w == self->area.width && h == self->area.height)) {
1422 w -= self->base_size.width;
1423 h -= self->base_size.height;
1424
1425 if (user) {
1426 /* for interactive resizing. have to move half an increment in each
1427 direction. */
1428
1429 /* how far we are towards the next size inc */
1430 int mw = w % self->size_inc.width;
1431 int mh = h % self->size_inc.height;
1432 /* amount to add */
1433 int aw = self->size_inc.width / 2;
1434 int ah = self->size_inc.height / 2;
1435 /* don't let us move into a new size increment */
1436 if (mw + aw >= self->size_inc.width)
1437 aw = self->size_inc.width - mw - 1;
1438 if (mh + ah >= self->size_inc.height)
1439 ah = self->size_inc.height - mh - 1;
1440 w += aw;
1441 h += ah;
1442
1443 /* if this is a user-requested resize, then check against min/max
1444 sizes and aspect ratios */
1445
1446 /* smaller than min size or bigger than max size? */
1447 if (w > self->max_size.width) w = self->max_size.width;
1448 if (w < self->min_size.width) w = self->min_size.width;
1449 if (h > self->max_size.height) h = self->max_size.height;
1450 if (h < self->min_size.height) h = self->min_size.height;
1451
1452 /* adjust the height ot match the width for the aspect ratios */
1453 if (self->min_ratio)
1454 if (h * self->min_ratio > w) h = (int)(w / self->min_ratio);
1455 if (self->max_ratio)
1456 if (h * self->max_ratio < w) h = (int)(w / self->max_ratio);
1457 }
1458
1459 /* keep to the increments */
1460 w /= self->size_inc.width;
1461 h /= self->size_inc.height;
1462
1463 /* you cannot resize to nothing */
1464 if (w < 1) w = 1;
1465 if (h < 1) h = 1;
1466
1467 /* store the logical size */
1468 SIZE_SET(self->logical_size, w, h);
1469
1470 w *= self->size_inc.width;
1471 h *= self->size_inc.height;
1472
1473 w += self->base_size.width;
1474 h += self->base_size.height;
1475 }
1476
1477 switch (anchor) {
1478 case Corner_TopLeft:
1479 break;
1480 case Corner_TopRight:
1481 x -= w - self->area.width;
1482 break;
1483 case Corner_BottomLeft:
1484 y -= h - self->area.height;
1485 break;
1486 case Corner_BottomRight:
1487 x -= w - self->area.width;
1488 y -= h - self->area.height;
1489 break;
1490 }
1491
1492 moved = x != self->area.x || y != self->area.y;
1493 resized = w != self->area.width || h != self->area.height;
1494
1495 RECT_SET(self->area, x, y, w, h);
1496
1497 if (resized)
1498 XResizeWindow(ob_display, self->window, w, h);
1499
1500 /* move/resize the frame to match the request */
1501 if (self->frame) {
1502 if (moved || resized)
1503 engine_frame_adjust_area(self->frame, moved, resized);
1504
1505 if (!user || final) {
1506 XEvent event;
1507 event.type = ConfigureNotify;
1508 event.xconfigure.display = ob_display;
1509 event.xconfigure.event = self->window;
1510 event.xconfigure.window = self->window;
1511
1512 /* root window coords with border in mind */
1513 event.xconfigure.x = x - self->border_width +
1514 self->frame->size.left;
1515 event.xconfigure.y = y - self->border_width +
1516 self->frame->size.top;
1517
1518 event.xconfigure.width = self->area.width;
1519 event.xconfigure.height = self->area.height;
1520 event.xconfigure.border_width = self->border_width;
1521 event.xconfigure.above = self->frame->plate;
1522 event.xconfigure.override_redirect = FALSE;
1523 XSendEvent(event.xconfigure.display, event.xconfigure.window,
1524 FALSE, StructureNotifyMask, &event);
1525 }
1526 }
1527 }
1528
1529 void client_fullscreen(Client *self, gboolean fs, gboolean savearea)
1530 {
1531 int x, y, w, h;
1532
1533 if (!(self->functions & Func_Fullscreen) || /* can't */
1534 self->fullscreen == fs) return; /* already done */
1535
1536 self->fullscreen = fs;
1537 client_change_state(self); /* change the state hints on the client */
1538
1539 if (fs) {
1540 /* save the functions and remove them */
1541 self->pre_fs_func = self->functions;
1542 self->functions &= (Func_Close | Func_Fullscreen |
1543 Func_Iconify);
1544 /* save the decorations and remove them */
1545 self->pre_fs_decor = self->decorations;
1546 self->decorations = 0;
1547 if (savearea) {
1548 long dimensions[4];
1549 dimensions[0] = self->area.x;
1550 dimensions[1] = self->area.y;
1551 dimensions[2] = self->area.width;
1552 dimensions[3] = self->area.height;
1553
1554 PROP_SET32A(self->window, openbox_premax, cardinal,
1555 dimensions, 4);
1556 }
1557
1558 /* these are not actually used cuz client_configure will set them
1559 as appropriate when the window is fullscreened */
1560 x = y = w = h = 0;
1561 } else {
1562 long *dimensions;
1563
1564 self->functions = self->pre_fs_func;
1565 self->decorations = self->pre_fs_decor;
1566
1567 if (PROP_GET32A(self->window, openbox_premax, cardinal,
1568 dimensions, 4)) {
1569 x = dimensions[0];
1570 y = dimensions[1];
1571 w = dimensions[2];
1572 h = dimensions[3];
1573 g_free(dimensions);
1574 } else {
1575 /* pick some fallbacks... */
1576 x = screen_area(self->desktop)->x +
1577 screen_area(self->desktop)->width / 4;
1578 y = screen_area(self->desktop)->y +
1579 screen_area(self->desktop)->height / 4;
1580 w = screen_area(self->desktop)->width / 2;
1581 h = screen_area(self->desktop)->height / 2;
1582 }
1583 }
1584
1585 client_change_allowed_actions(self); /* based on the new _functions */
1586
1587 /* when fullscreening, don't obey things like increments, fill the
1588 screen */
1589 client_configure(self, Corner_TopLeft, x, y, w, h, !fs, TRUE);
1590
1591 /* raise (back) into our stacking layer */
1592 stacking_raise(self);
1593
1594 /* try focus us when we go into fullscreen mode */
1595 client_focus(self);
1596 }
1597
1598 void client_iconify(Client *self, gboolean iconic, gboolean curdesk)
1599 {
1600 if (self->iconic == iconic) return; /* nothing to do */
1601
1602 g_message("%sconifying window: 0x%lx", (iconic ? "I" : "Uni"),
1603 self->window);
1604
1605 self->iconic = iconic;
1606
1607 if (iconic) {
1608 self->wmstate = IconicState;
1609 self->ignore_unmaps++;
1610 /* we unmap the client itself so that we can get MapRequest events,
1611 and because the ICCCM tells us to! */
1612 XUnmapWindow(ob_display, self->window);
1613 } else {
1614 if (curdesk)
1615 client_set_desktop(self, screen_desktop, FALSE);
1616 self->wmstate = self->shaded ? IconicState : NormalState;
1617 XMapWindow(ob_display, self->window);
1618 }
1619 client_change_state(self);
1620 client_showhide(self);
1621 screen_update_struts();
1622
1623 dispatch_client(iconic ? Event_Client_Unmapped : Event_Client_Mapped,
1624 self, 0, 0);
1625
1626 /* iconify all transients */
1627 if (self->transients) {
1628 GSList *it;
1629
1630 for (it = self->transients; it != NULL; it = it->next)
1631 if (it->data != self) client_iconify(it->data, iconic, curdesk);
1632 }
1633 }
1634
1635 void client_maximize(Client *self, gboolean max, int dir, gboolean savearea)
1636 {
1637 int x, y, w, h;
1638
1639 g_assert(dir == 0 || dir == 1 || dir == 2);
1640 if (!(self->functions & Func_Maximize)) return; /* can't */
1641
1642 /* check if already done */
1643 if (max) {
1644 if (dir == 0 && self->max_horz && self->max_vert) return;
1645 if (dir == 1 && self->max_horz) return;
1646 if (dir == 2 && self->max_vert) return;
1647 } else {
1648 if (dir == 0 && !self->max_horz && !self->max_vert) return;
1649 if (dir == 1 && !self->max_horz) return;
1650 if (dir == 2 && !self->max_vert) return;
1651 }
1652
1653 /* work with the frame's coords */
1654 x = self->frame->area.x;
1655 y = self->frame->area.y;
1656 w = self->area.width;
1657 h = self->area.height;
1658
1659 if (max) {
1660 if (savearea) {
1661 long dimensions[4];
1662 long *readdim;
1663
1664 dimensions[0] = x;
1665 dimensions[1] = y;
1666 dimensions[2] = w;
1667 dimensions[3] = h;
1668
1669 /* get the property off the window and use it for the dimensions
1670 we are already maxed on */
1671 if (PROP_GET32A(self->window, openbox_premax, cardinal,
1672 readdim, 4)) {
1673 if (self->max_horz) {
1674 dimensions[0] = readdim[0];
1675 dimensions[2] = readdim[2];
1676 }
1677 if (self->max_vert) {
1678 dimensions[1] = readdim[1];
1679 dimensions[3] = readdim[3];
1680 }
1681 g_free(readdim);
1682 }
1683
1684 PROP_SET32A(self->window, openbox_premax, cardinal,
1685 dimensions, 4);
1686 }
1687 } else {
1688 long *dimensions;
1689
1690 if (PROP_GET32A(self->window, openbox_premax, cardinal,
1691 dimensions, 4)) {
1692 if (dir == 0 || dir == 1) { /* horz */
1693 x = dimensions[0];
1694 w = dimensions[2];
1695 }
1696 if (dir == 0 || dir == 2) { /* vert */
1697 y = dimensions[1];
1698 h = dimensions[3];
1699 }
1700 g_free(dimensions);
1701 } else {
1702 /* pick some fallbacks... */
1703 if (dir == 0 || dir == 1) { /* horz */
1704 x = screen_area(self->desktop)->x +
1705 screen_area(self->desktop)->width / 4;
1706 w = screen_area(self->desktop)->width / 2;
1707 }
1708 if (dir == 0 || dir == 2) { /* vert */
1709 y = screen_area(self->desktop)->y +
1710 screen_area(self->desktop)->height / 4;
1711 h = screen_area(self->desktop)->height / 2;
1712 }
1713 }
1714 }
1715
1716 if (dir == 0 || dir == 1) /* horz */
1717 self->max_horz = max;
1718 if (dir == 0 || dir == 2) /* vert */
1719 self->max_vert = max;
1720
1721 if (!self->max_horz && !self->max_vert)
1722 PROP_ERASE(self->window, openbox_premax);
1723
1724 client_change_state(self); /* change the state hints on the client */
1725
1726 /* figure out where the client should be going */
1727 frame_frame_gravity(self->frame, &x, &y);
1728 client_configure(self, Corner_TopLeft, x, y, w, h, TRUE, TRUE);
1729 }
1730
1731 void client_shade(Client *self, gboolean shade)
1732 {
1733 if ((!(self->functions & Func_Shade) && shade) || /* can't shade */
1734 self->shaded == shade) return; /* already done */
1735
1736 /* when we're iconic, don't change the wmstate */
1737 if (!self->iconic)
1738 self->wmstate = shade ? IconicState : NormalState;
1739 self->shaded = shade;
1740 client_change_state(self);
1741 /* resize the frame to just the titlebar */
1742 engine_frame_adjust_area(self->frame, FALSE, FALSE);
1743 }
1744
1745 void client_close(Client *self)
1746 {
1747 XEvent ce;
1748
1749 if (!(self->functions & Func_Close)) return;
1750
1751 /*
1752 XXX: itd be cool to do timeouts and shit here for killing the client's
1753 process off
1754 like... if the window is around after 5 seconds, then the close button
1755 turns a nice red, and if this function is called again, the client is
1756 explicitly killed.
1757 */
1758
1759 ce.xclient.type = ClientMessage;
1760 ce.xclient.message_type = prop_atoms.wm_protocols;
1761 ce.xclient.display = ob_display;
1762 ce.xclient.window = self->window;
1763 ce.xclient.format = 32;
1764 ce.xclient.data.l[0] = prop_atoms.wm_delete_window;
1765 ce.xclient.data.l[1] = event_lasttime;
1766 ce.xclient.data.l[2] = 0l;
1767 ce.xclient.data.l[3] = 0l;
1768 ce.xclient.data.l[4] = 0l;
1769 XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
1770 }
1771
1772 void client_kill(Client *self)
1773 {
1774 XKillClient(ob_display, self->window);
1775 }
1776
1777 void client_set_desktop(Client *self, guint target, gboolean donthide)
1778 {
1779 guint old, i;
1780
1781 if (target == self->desktop) return;
1782
1783 g_message("Setting desktop %u", target);
1784
1785 g_assert(target < screen_num_desktops || target == DESKTOP_ALL);
1786
1787 old = self->desktop;
1788 self->desktop = target;
1789 PROP_SET32(self->window, net_wm_desktop, cardinal, target);
1790 /* the frame can display the current desktop state */
1791 engine_frame_adjust_state(self->frame);
1792 /* 'move' the window to the new desktop */
1793 if (!donthide)
1794 client_showhide(self);
1795 /* raise if it was not already on the desktop */
1796 if (old != DESKTOP_ALL)
1797 stacking_raise(self);
1798 screen_update_struts();
1799
1800 /* update the focus lists */
1801 if (old == DESKTOP_ALL) {
1802 for (i = 0; i < screen_num_desktops; ++i)
1803 focus_order[i] = g_list_remove(focus_order[i], self);
1804 } else
1805 focus_order[old] = g_list_remove(focus_order[old], self);
1806 if (target == DESKTOP_ALL) {
1807 for (i = 0; i < screen_num_desktops; ++i) {
1808 if (focus_new)
1809 focus_order[i] = g_list_prepend(focus_order[i], self);
1810 else
1811 focus_order[i] = g_list_append(focus_order[i], self);
1812 }
1813 } else {
1814 if (focus_new)
1815 focus_order[target] = g_list_prepend(focus_order[target], self);
1816 else
1817 focus_order[target] = g_list_append(focus_order[target], self);
1818 }
1819
1820 dispatch_client(Event_Client_Desktop, self, target, old);
1821 }
1822
1823 static Client *search_modal_tree(Client *node, Client *skip)
1824 {
1825 GSList *it;
1826 Client *ret;
1827
1828 for (it = node->transients; it != NULL; it = it->next) {
1829 Client *c = it->data;
1830 if (c == skip) continue; /* circular? */
1831 if ((ret = search_modal_tree(c, skip))) return ret;
1832 if (c->modal) return c;
1833 }
1834 return NULL;
1835 }
1836
1837 Client *client_find_modal_child(Client *self)
1838 {
1839 return search_modal_tree(self, self);
1840 }
1841
1842 gboolean client_validate(Client *self)
1843 {
1844 XEvent e;
1845
1846 XSync(ob_display, FALSE); /* get all events on the server */
1847
1848 if (XCheckTypedWindowEvent(ob_display, self->window, DestroyNotify, &e) ||
1849 XCheckTypedWindowEvent(ob_display, self->window, UnmapNotify, &e)) {
1850 XPutBackEvent(ob_display, &e);
1851 return FALSE;
1852 }
1853
1854 return TRUE;
1855 }
1856
1857 void client_set_wm_state(Client *self, long state)
1858 {
1859 if (state == self->wmstate) return; /* no change */
1860
1861 switch (state) {
1862 case IconicState:
1863 client_iconify(self, TRUE, TRUE);
1864 break;
1865 case NormalState:
1866 client_iconify(self, FALSE, TRUE);
1867 break;
1868 }
1869 }
1870
1871 void client_set_state(Client *self, Atom action, long data1, long data2)
1872 {
1873 gboolean shaded = self->shaded;
1874 gboolean fullscreen = self->fullscreen;
1875 gboolean max_horz = self->max_horz;
1876 gboolean max_vert = self->max_vert;
1877 int i;
1878
1879 if (!(action == prop_atoms.net_wm_state_add ||
1880 action == prop_atoms.net_wm_state_remove ||
1881 action == prop_atoms.net_wm_state_toggle))
1882 /* an invalid action was passed to the client message, ignore it */
1883 return;
1884
1885 for (i = 0; i < 2; ++i) {
1886 Atom state = i == 0 ? data1 : data2;
1887
1888 if (!state) continue;
1889
1890 /* if toggling, then pick whether we're adding or removing */
1891 if (action == prop_atoms.net_wm_state_toggle) {
1892 if (state == prop_atoms.net_wm_state_modal)
1893 action = self->modal ? prop_atoms.net_wm_state_remove :
1894 prop_atoms.net_wm_state_add;
1895 else if (state == prop_atoms.net_wm_state_maximized_vert)
1896 action = self->max_vert ? prop_atoms.net_wm_state_remove :
1897 prop_atoms.net_wm_state_add;
1898 else if (state == prop_atoms.net_wm_state_maximized_horz)
1899 action = self->max_horz ? prop_atoms.net_wm_state_remove :
1900 prop_atoms.net_wm_state_add;
1901 else if (state == prop_atoms.net_wm_state_shaded)
1902 action = self->shaded ? prop_atoms.net_wm_state_remove :
1903 prop_atoms.net_wm_state_add;
1904 else if (state == prop_atoms.net_wm_state_skip_taskbar)
1905 action = self->skip_taskbar ?
1906 prop_atoms.net_wm_state_remove :
1907 prop_atoms.net_wm_state_add;
1908 else if (state == prop_atoms.net_wm_state_skip_pager)
1909 action = self->skip_pager ?
1910 prop_atoms.net_wm_state_remove :
1911 prop_atoms.net_wm_state_add;
1912 else if (state == prop_atoms.net_wm_state_fullscreen)
1913 action = self->fullscreen ?
1914 prop_atoms.net_wm_state_remove :
1915 prop_atoms.net_wm_state_add;
1916 else if (state == prop_atoms.net_wm_state_above)
1917 action = self->above ? prop_atoms.net_wm_state_remove :
1918 prop_atoms.net_wm_state_add;
1919 else if (state == prop_atoms.net_wm_state_below)
1920 action = self->below ? prop_atoms.net_wm_state_remove :
1921 prop_atoms.net_wm_state_add;
1922 }
1923
1924 if (action == prop_atoms.net_wm_state_add) {
1925 if (state == prop_atoms.net_wm_state_modal) {
1926 /* XXX raise here or something? */
1927 self->modal = TRUE;
1928 } else if (state == prop_atoms.net_wm_state_maximized_vert) {
1929 max_vert = TRUE;
1930 } else if (state == prop_atoms.net_wm_state_maximized_horz) {
1931 max_horz = TRUE;
1932 } else if (state == prop_atoms.net_wm_state_shaded) {
1933 shaded = TRUE;
1934 } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
1935 self->skip_taskbar = TRUE;
1936 } else if (state == prop_atoms.net_wm_state_skip_pager) {
1937 self->skip_pager = TRUE;
1938 } else if (state == prop_atoms.net_wm_state_fullscreen) {
1939 fullscreen = TRUE;
1940 } else if (state == prop_atoms.net_wm_state_above) {
1941 self->above = TRUE;
1942 } else if (state == prop_atoms.net_wm_state_below) {
1943 self->below = TRUE;
1944 }
1945
1946 } else { /* action == prop_atoms.net_wm_state_remove */
1947 if (state == prop_atoms.net_wm_state_modal) {
1948 self->modal = FALSE;
1949 } else if (state == prop_atoms.net_wm_state_maximized_vert) {
1950 max_vert = FALSE;
1951 } else if (state == prop_atoms.net_wm_state_maximized_horz) {
1952 max_horz = FALSE;
1953 } else if (state == prop_atoms.net_wm_state_shaded) {
1954 shaded = FALSE;
1955 } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
1956 self->skip_taskbar = FALSE;
1957 } else if (state == prop_atoms.net_wm_state_skip_pager) {
1958 self->skip_pager = FALSE;
1959 } else if (state == prop_atoms.net_wm_state_fullscreen) {
1960 fullscreen = FALSE;
1961 } else if (state == prop_atoms.net_wm_state_above) {
1962 self->above = FALSE;
1963 } else if (state == prop_atoms.net_wm_state_below) {
1964 self->below = FALSE;
1965 }
1966 }
1967 }
1968 if (max_horz != self->max_horz || max_vert != self->max_vert) {
1969 if (max_horz != self->max_horz && max_vert != self->max_vert) {
1970 /* toggling both */
1971 if (max_horz == max_vert) { /* both going the same way */
1972 client_maximize(self, max_horz, 0, TRUE);
1973 } else {
1974 client_maximize(self, max_horz, 1, TRUE);
1975 client_maximize(self, max_vert, 2, TRUE);
1976 }
1977 } else {
1978 /* toggling one */
1979 if (max_horz != self->max_horz)
1980 client_maximize(self, max_horz, 1, TRUE);
1981 else
1982 client_maximize(self, max_vert, 2, TRUE);
1983 }
1984 }
1985 /* change fullscreen state before shading, as it will affect if the window
1986 can shade or not */
1987 if (fullscreen != self->fullscreen)
1988 client_fullscreen(self, fullscreen, TRUE);
1989 if (shaded != self->shaded)
1990 client_shade(self, shaded);
1991 client_calc_layer(self);
1992 client_change_state(self); /* change the hint to relect these changes */
1993 }
1994
1995 Client *client_focus_target(Client *self)
1996 {
1997 Client *child;
1998
1999 /* if we have a modal child, then focus it, not us */
2000 child = client_find_modal_child(self);
2001 if (child) return child;
2002 return self;
2003 }
2004
2005 gboolean client_focusable(Client *self)
2006 {
2007 /* won't try focus if the client doesn't want it, or if the window isn't
2008 visible on the screen */
2009 return self->frame->visible &&
2010 (self->can_focus || self->focus_notify);
2011 }
2012
2013 gboolean client_focus(Client *self)
2014 {
2015 XEvent ev;
2016
2017 /* choose the correct target */
2018 self = client_focus_target(self);
2019
2020 if (!client_focusable(self))
2021 return FALSE;
2022
2023 /* do a check to see if the window has already been unmapped or destroyed
2024 do this intelligently while watching out for unmaps we've generated
2025 (ignore_unmaps > 0) */
2026 if (XCheckTypedWindowEvent(ob_display, self->window,
2027 DestroyNotify, &ev)) {
2028 XPutBackEvent(ob_display, &ev);
2029 return FALSE;
2030 }
2031 while (XCheckTypedWindowEvent(ob_display, self->window,
2032 UnmapNotify, &ev)) {
2033 if (self->ignore_unmaps) {
2034 self->ignore_unmaps--;
2035 } else {
2036 XPutBackEvent(ob_display, &ev);
2037 return FALSE;
2038 }
2039 }
2040
2041 if (self->can_focus)
2042 /* RevertToPointerRoot causes much more headache than TevertToNone, so
2043 I choose to use it always, hopefully to find errors quicker, if any
2044 are left. (I hate X. I hate focus events.) */
2045 XSetInputFocus(ob_display, self->window, RevertToPointerRoot,
2046 event_lasttime);
2047
2048 if (self->focus_notify) {
2049 XEvent ce;
2050 ce.xclient.type = ClientMessage;
2051 ce.xclient.message_type = prop_atoms.wm_protocols;
2052 ce.xclient.display = ob_display;
2053 ce.xclient.window = self->window;
2054 ce.xclient.format = 32;
2055 ce.xclient.data.l[0] = prop_atoms.wm_take_focus;
2056 ce.xclient.data.l[1] = event_lasttime;
2057 ce.xclient.data.l[2] = 0l;
2058 ce.xclient.data.l[3] = 0l;
2059 ce.xclient.data.l[4] = 0l;
2060 XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
2061 }
2062
2063 #ifdef DEBUG_FOCUS
2064 g_message("focusing %lx", self->window);
2065 #endif
2066
2067 /* Cause the FocusIn to come back to us. Important for desktop switches,
2068 since otherwise we'll have no FocusIn on the queue and send it off to
2069 the focus_backup. */
2070 XSync(ob_display, FALSE);
2071 return TRUE;
2072 }
2073
2074 void client_unfocus(Client *self)
2075 {
2076 g_assert(focus_client == self);
2077 g_message("client_unfocus");
2078 focus_fallback(FALSE);
2079 }
2080
2081 gboolean client_focused(Client *self)
2082 {
2083 return self == focus_client;
2084 }
2085
2086 Icon *client_icon(Client *self, int w, int h)
2087 {
2088 int i;
2089 /* si is the smallest image >= req */
2090 /* li is the largest image < req */
2091 unsigned long size, smallest = 0xffffffff, largest = 0, si = 0, li = 0;
2092
2093 if (!self->nicons) return NULL;
2094
2095 for (i = 0; i < self->nicons; ++i) {
2096 size = self->icons[i].width * self->icons[i].height;
2097 if (size < smallest && size >= (unsigned)(w * h)) {
2098 smallest = size;
2099 si = i;
2100 }
2101 if (size > largest && size <= (unsigned)(w * h)) {
2102 largest = size;
2103 li = i;
2104 }
2105 }
2106 if (largest == 0) /* didnt find one smaller than the requested size */
2107 return &self->icons[si];
2108 return &self->icons[li];
2109 }
This page took 0.125806 seconds and 5 git commands to generate.