]> Dogcows Code - chaz/openbox/blob - openbox/client.c
changing props need client_change_state
[chaz/openbox] / openbox / client.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3 client.c for the Openbox window manager
4 Copyright (c) 2004 Mikael Magnusson
5 Copyright (c) 2003 Ben Jansens
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "client.h"
21 #include "debug.h"
22 #include "startupnotify.h"
23 #include "dock.h"
24 #include "xerror.h"
25 #include "screen.h"
26 #include "moveresize.h"
27 #include "place.h"
28 #include "prop.h"
29 #include "extensions.h"
30 #include "frame.h"
31 #include "session.h"
32 #include "event.h"
33 #include "grab.h"
34 #include "focus.h"
35 #include "stacking.h"
36 #include "openbox.h"
37 #include "group.h"
38 #include "config.h"
39 #include "menuframe.h"
40 #include "keyboard.h"
41 #include "mouse.h"
42 #include "render/render.h"
43
44 #include <glib.h>
45 #include <X11/Xutil.h>
46
47 /*! The event mask to grab on client windows */
48 #define CLIENT_EVENTMASK (PropertyChangeMask | FocusChangeMask | \
49 StructureNotifyMask)
50
51 #define CLIENT_NOPROPAGATEMASK (ButtonPressMask | ButtonReleaseMask | \
52 ButtonMotionMask)
53
54 typedef struct
55 {
56 ObClientDestructor func;
57 gpointer data;
58 } Destructor;
59
60 GList *client_list = NULL;
61 GSList *client_destructors = NULL;
62
63 static void client_get_all(ObClient *self);
64 static void client_toggle_border(ObClient *self, gboolean show);
65 static void client_get_startup_id(ObClient *self);
66 static void client_get_area(ObClient *self);
67 static void client_get_desktop(ObClient *self);
68 static void client_get_state(ObClient *self);
69 static void client_get_shaped(ObClient *self);
70 static void client_get_mwm_hints(ObClient *self);
71 static void client_get_gravity(ObClient *self);
72 static void client_showhide(ObClient *self);
73 static void client_change_allowed_actions(ObClient *self);
74 static void client_change_state(ObClient *self);
75 static void client_apply_startup_state(ObClient *self);
76 static void client_restore_session_state(ObClient *self);
77 static void client_restore_session_stacking(ObClient *self);
78 static void client_urgent_notify(ObClient *self);
79
80 void client_startup(gboolean reconfig)
81 {
82 if (reconfig) return;
83
84 client_set_list();
85 }
86
87 void client_shutdown(gboolean reconfig)
88 {
89 }
90
91 void client_add_destructor(ObClientDestructor func, gpointer data)
92 {
93 Destructor *d = g_new(Destructor, 1);
94 d->func = func;
95 d->data = data;
96 client_destructors = g_slist_prepend(client_destructors, d);
97 }
98
99 void client_remove_destructor(ObClientDestructor func)
100 {
101 GSList *it;
102
103 for (it = client_destructors; it; it = g_slist_next(it)) {
104 Destructor *d = it->data;
105 if (d->func == func) {
106 g_free(d);
107 client_destructors = g_slist_delete_link(client_destructors, it);
108 break;
109 }
110 }
111 }
112
113 void client_set_list()
114 {
115 Window *windows, *win_it;
116 GList *it;
117 guint size = g_list_length(client_list);
118
119 /* create an array of the window ids */
120 if (size > 0) {
121 windows = g_new(Window, size);
122 win_it = windows;
123 for (it = client_list; it; it = g_list_next(it), ++win_it)
124 *win_it = ((ObClient*)it->data)->window;
125 } else
126 windows = NULL;
127
128 PROP_SETA32(RootWindow(ob_display, ob_screen),
129 net_client_list, window, (gulong*)windows, size);
130
131 if (windows)
132 g_free(windows);
133
134 stacking_set_list();
135 }
136
137 /*
138 void client_foreach_transient(ObClient *self, ObClientForeachFunc func, gpointer data)
139 {
140 GSList *it;
141
142 for (it = self->transients; it; it = g_slist_next(it)) {
143 if (!func(it->data, data)) return;
144 client_foreach_transient(it->data, func, data);
145 }
146 }
147
148 void client_foreach_ancestor(ObClient *self, ObClientForeachFunc func, gpointer data)
149 {
150 if (self->transient_for) {
151 if (self->transient_for != OB_TRAN_GROUP) {
152 if (!func(self->transient_for, data)) return;
153 client_foreach_ancestor(self->transient_for, func, data);
154 } else {
155 GSList *it;
156
157 for (it = self->group->members; it; it = g_slist_next(it))
158 if (it->data != self &&
159 !((ObClient*)it->data)->transient_for) {
160 if (!func(it->data, data)) return;
161 client_foreach_ancestor(it->data, func, data);
162 }
163 }
164 }
165 }
166 */
167
168 void client_manage_all()
169 {
170 guint i, j, nchild;
171 Window w, *children;
172 XWMHints *wmhints;
173 XWindowAttributes attrib;
174
175 XQueryTree(ob_display, RootWindow(ob_display, ob_screen),
176 &w, &w, &children, &nchild);
177
178 /* remove all icon windows from the list */
179 for (i = 0; i < nchild; i++) {
180 if (children[i] == None) continue;
181 wmhints = XGetWMHints(ob_display, children[i]);
182 if (wmhints) {
183 if ((wmhints->flags & IconWindowHint) &&
184 (wmhints->icon_window != children[i]))
185 for (j = 0; j < nchild; j++)
186 if (children[j] == wmhints->icon_window) {
187 children[j] = None;
188 break;
189 }
190 XFree(wmhints);
191 }
192 }
193
194 for (i = 0; i < nchild; ++i) {
195 if (children[i] == None)
196 continue;
197 if (XGetWindowAttributes(ob_display, children[i], &attrib)) {
198 if (attrib.override_redirect) continue;
199
200 if (attrib.map_state != IsUnmapped)
201 client_manage(children[i]);
202 }
203 }
204 XFree(children);
205 }
206
207 /* This should possibly do something more interesting than just match
208 * against WM_CLASS literally. */
209 static ObAppSettings *get_settings(ObClient *client)
210 {
211 GSList *a = config_per_app_settings;
212
213 while (a) {
214 ObAppSettings *app = (ObAppSettings *) a->data;
215
216 if (!strcmp(app->name, client->name)) {
217 ob_debug("Window matching: %s\n", app->name);
218 /* Match if no role was specified in the per app setting, or if the string
219 * matches the beginning of the role, since apps like to set the role to
220 * things like browser-window-23c4b2f */
221 if (!app->role || !strncmp(app->role, client->role, strlen(app->role)))
222 return app;
223 }
224
225 a = a->next;
226 }
227 return NULL;
228 }
229
230 void client_manage(Window window)
231 {
232 ObClient *self;
233 XEvent e;
234 XWindowAttributes attrib;
235 XSetWindowAttributes attrib_set;
236 XWMHints *wmhint;
237 gboolean activate = FALSE;
238 ObAppSettings *settings;
239
240 grab_server(TRUE);
241
242 /* check if it has already been unmapped by the time we started mapping
243 the grab does a sync so we don't have to here */
244 if (XCheckTypedWindowEvent(ob_display, window, DestroyNotify, &e) ||
245 XCheckTypedWindowEvent(ob_display, window, UnmapNotify, &e)) {
246 XPutBackEvent(ob_display, &e);
247
248 grab_server(FALSE);
249 return; /* don't manage it */
250 }
251
252 /* make sure it isn't an override-redirect window */
253 if (!XGetWindowAttributes(ob_display, window, &attrib) ||
254 attrib.override_redirect) {
255 grab_server(FALSE);
256 return; /* don't manage it */
257 }
258
259 /* is the window a docking app */
260 if ((wmhint = XGetWMHints(ob_display, window))) {
261 if ((wmhint->flags & StateHint) &&
262 wmhint->initial_state == WithdrawnState) {
263 dock_add(window, wmhint);
264 grab_server(FALSE);
265 XFree(wmhint);
266 return;
267 }
268 XFree(wmhint);
269 }
270
271 ob_debug("Managing window: %lx\n", window);
272
273 /* choose the events we want to receive on the CLIENT window */
274 attrib_set.event_mask = CLIENT_EVENTMASK;
275 attrib_set.do_not_propagate_mask = CLIENT_NOPROPAGATEMASK;
276 XChangeWindowAttributes(ob_display, window,
277 CWEventMask|CWDontPropagate, &attrib_set);
278
279
280 /* create the ObClient struct, and populate it from the hints on the
281 window */
282 self = g_new0(ObClient, 1);
283 self->obwin.type = Window_Client;
284 self->window = window;
285
286 /* non-zero defaults */
287 self->title_count = 1;
288 self->wmstate = NormalState;
289 self->layer = -1;
290 self->desktop = screen_num_desktops; /* always an invalid value */
291
292 client_get_all(self);
293 client_restore_session_state(self);
294
295 sn_app_started(self->class);
296
297 /* update the focus lists, do this before the call to change_state or
298 it can end up in the list twice! */
299 focus_order_add_new(self);
300
301 client_change_state(self);
302
303 /* remove the client's border (and adjust re gravity) */
304 client_toggle_border(self, FALSE);
305
306 /* specify that if we exit, the window should not be destroyed and should
307 be reparented back to root automatically */
308 XChangeSaveSet(ob_display, window, SetModeInsert);
309
310 /* create the decoration frame for the client window */
311 self->frame = frame_new();
312
313 frame_grab_client(self->frame, self);
314
315 grab_server(FALSE);
316
317 client_apply_startup_state(self);
318
319 /* get and set application level settings */
320 settings = get_settings(self);
321
322 if (settings) {
323 /* Don't worry, we won't actually both shade and undecorate the
324 * window when push comes to shove. */
325 if (settings->shade != -1)
326 client_shade(self, settings->shade);
327 if (settings->decor != -1)
328 client_set_undecorated(self, !settings->decor);
329 if (settings->iconic != -1)
330 client_iconify(self, settings->iconic, FALSE);
331 if (settings->skip_pager != -1) {
332 self->skip_pager = !!settings->skip_pager;
333 client_change_state(self);
334 }
335 if (settings->skip_taskbar != -1) {
336 self->skip_taskbar = !!settings->skip_taskbar;
337 client_change_state(self);
338 }
339
340 /* 1 && -1 shouldn't be possible by the code in config.c */
341 if (settings->max_vert == 1 && self->max_horz == 1)
342 client_maximize(self, TRUE, 0, TRUE);
343 else if (settings->max_vert == 0 && self->max_horz == 0)
344 client_maximize(self, FALSE, 0, TRUE);
345 else if (settings->max_vert == 1 && self->max_horz == 0) {
346 client_maximize(self, TRUE, 2, TRUE);
347 client_maximize(self, FALSE, 1, TRUE);
348 } else if (settings->max_vert == 0 && self->max_horz == 1) {
349 client_maximize(self, TRUE, 1, TRUE);
350 client_maximize(self, FALSE, 2, TRUE);
351 }
352
353 if (settings->fullscreen != -1)
354 client_fullscreen(self, !!settings->fullscreen, TRUE);
355
356 if (settings->desktop < screen_num_desktops)
357 client_set_desktop(self, settings->desktop, FALSE);
358
359 if (settings->layer > -2 && settings->layer < 2)
360 client_set_layer(self, settings->layer);
361
362 }
363
364 stacking_add(CLIENT_AS_WINDOW(self));
365 client_restore_session_stacking(self);
366
367 /* focus the new window? */
368 if (ob_state() != OB_STATE_STARTING &&
369 (config_focus_new || client_search_focus_parent(self)) ||
370 (settings && settings->focus == TRUE) &&
371 /* note the check against Type_Normal/Dialog, not client_normal(self),
372 which would also include other types. in this case we want more
373 strict rules for focus */
374 (self->type == OB_CLIENT_TYPE_NORMAL ||
375 self->type == OB_CLIENT_TYPE_DIALOG))
376 {
377 activate = TRUE;
378 #if 0
379 if (self->desktop != screen_desktop) {
380 /* activate the window */
381 activate = TRUE;
382 } else {
383 gboolean group_foc = FALSE;
384
385 if (self->group) {
386 GSList *it;
387
388 for (it = self->group->members; it; it = g_slist_next(it))
389 {
390 if (client_focused(it->data))
391 {
392 group_foc = TRUE;
393 break;
394 }
395 }
396 }
397 if ((group_foc ||
398 (!self->transient_for && (!self->group ||
399 !self->group->members->next))) ||
400 client_search_focus_tree_full(self) ||
401 !focus_client ||
402 !client_normal(focus_client))
403 {
404 /* activate the window */
405 activate = TRUE;
406 }
407 }
408 #endif
409 }
410
411 if (ob_state() == OB_STATE_RUNNING) {
412 gint x = self->area.x, ox = x;
413 gint y = self->area.y, oy = y;
414
415 place_client(self, &x, &y, settings);
416
417 /* make sure the window is visible. */
418 client_find_onscreen(self, &x, &y,
419 self->frame->area.width,
420 self->frame->area.height,
421 /* non-normal clients has less rules, and
422 windows that are being restored from a
423 session do also. we can assume you want
424 it back where you saved it. Clients saying
425 they placed themselves are subjected to
426 harder rules, ones that are placed by
427 place.c or by the user are allowed partially
428 off-screen and on xinerama divides (ie,
429 it is up to the placement routines to avoid
430 the xinerama divides) */
431 ((self->positioned & PPosition) &&
432 !(self->positioned & USPosition)) &&
433 client_normal(self) &&
434 !self->session);
435 if (x != ox || y != oy)
436 client_move(self, x, y);
437 }
438
439 keyboard_grab_for_client(self, TRUE);
440 mouse_grab_for_client(self, TRUE);
441
442 client_showhide(self);
443
444 /* use client_focus instead of client_activate cuz client_activate does
445 stuff like switch desktops etc and I'm not interested in all that when
446 a window maps since its not based on an action from the user like
447 clicking a window to activate is. so keep the new window out of the way
448 but do focus it. */
449 if (activate) {
450 /* if using focus_delay, stop the timer now so that focus doesn't go
451 moving on us */
452 event_halt_focus_delay();
453
454 client_focus(self);
455 /* since focus can change the stacking orders, if we focus the window
456 then the standard raise it gets is not enough, we need to queue one
457 for after the focus change takes place */
458 client_raise(self);
459 }
460
461 /* client_activate does this but we aret using it so we have to do it
462 here as well */
463 if (screen_showing_desktop)
464 screen_show_desktop(FALSE);
465
466 /* add to client list/map */
467 client_list = g_list_append(client_list, self);
468 g_hash_table_insert(window_map, &self->window, self);
469
470 /* this has to happen after we're in the client_list */
471 screen_update_areas();
472
473 /* update the list hints */
474 client_set_list();
475
476 ob_debug("Managed window 0x%lx (%s)\n", window, self->class);
477 }
478
479 void client_unmanage_all()
480 {
481 while (client_list != NULL)
482 client_unmanage(client_list->data);
483 }
484
485 void client_unmanage(ObClient *self)
486 {
487 guint j;
488 GSList *it;
489
490 ob_debug("Unmanaging window: %lx (%s)\n", self->window, self->class);
491
492 g_assert(self != NULL);
493
494 keyboard_grab_for_client(self, FALSE);
495 mouse_grab_for_client(self, FALSE);
496
497 /* potentially fix focusLast */
498 if (config_focus_last)
499 grab_pointer(TRUE, OB_CURSOR_NONE);
500
501 /* remove the window from our save set */
502 XChangeSaveSet(ob_display, self->window, SetModeDelete);
503
504 /* we dont want events no more */
505 XSelectInput(ob_display, self->window, NoEventMask);
506
507 frame_hide(self->frame);
508
509 client_list = g_list_remove(client_list, self);
510 stacking_remove(self);
511 g_hash_table_remove(window_map, &self->window);
512
513 /* update the focus lists */
514 focus_order_remove(self);
515
516 /* once the client is out of the list, update the struts to remove it's
517 influence */
518 screen_update_areas();
519
520 for (it = client_destructors; it; it = g_slist_next(it)) {
521 Destructor *d = it->data;
522 d->func(self, d->data);
523 }
524
525 if (focus_client == self) {
526 XEvent e;
527
528 /* focus the last focused window on the desktop, and ignore enter
529 events from the unmap so it doesnt mess with the focus */
530 while (XCheckTypedEvent(ob_display, EnterNotify, &e));
531 /* remove these flags so we don't end up getting focused in the
532 fallback! */
533 self->can_focus = FALSE;
534 self->focus_notify = FALSE;
535 self->modal = FALSE;
536 client_unfocus(self);
537 }
538
539 /* tell our parent(s) that we're gone */
540 if (self->transient_for == OB_TRAN_GROUP) { /* transient of group */
541 for (it = self->group->members; it; it = g_slist_next(it))
542 if (it->data != self)
543 ((ObClient*)it->data)->transients =
544 g_slist_remove(((ObClient*)it->data)->transients, self);
545 } else if (self->transient_for) { /* transient of window */
546 self->transient_for->transients =
547 g_slist_remove(self->transient_for->transients, self);
548 }
549
550 /* tell our transients that we're gone */
551 for (it = self->transients; it; it = g_slist_next(it)) {
552 if (((ObClient*)it->data)->transient_for != OB_TRAN_GROUP) {
553 ((ObClient*)it->data)->transient_for = NULL;
554 client_calc_layer(it->data);
555 }
556 }
557
558 /* remove from its group */
559 if (self->group) {
560 group_remove(self->group, self);
561 self->group = NULL;
562 }
563
564 /* give the client its border back */
565 client_toggle_border(self, TRUE);
566
567 /* reparent the window out of the frame, and free the frame */
568 frame_release_client(self->frame, self);
569 self->frame = NULL;
570
571 if (ob_state() != OB_STATE_EXITING) {
572 /* these values should not be persisted across a window
573 unmapping/mapping */
574 PROP_ERASE(self->window, net_wm_desktop);
575 PROP_ERASE(self->window, net_wm_state);
576 PROP_ERASE(self->window, wm_state);
577 } else {
578 /* if we're left in an unmapped state, the client wont be mapped. this
579 is bad, since we will no longer be managing the window on restart */
580 XMapWindow(ob_display, self->window);
581 }
582
583
584 ob_debug("Unmanaged window 0x%lx\n", self->window);
585
586 /* free all data allocated in the client struct */
587 g_slist_free(self->transients);
588 for (j = 0; j < self->nicons; ++j)
589 g_free(self->icons[j].data);
590 if (self->nicons > 0)
591 g_free(self->icons);
592 g_free(self->title);
593 g_free(self->icon_title);
594 g_free(self->name);
595 g_free(self->class);
596 g_free(self->role);
597 g_free(self->sm_client_id);
598 g_free(self);
599
600 /* update the list hints */
601 client_set_list();
602
603 if (config_focus_last)
604 grab_pointer(FALSE, OB_CURSOR_NONE);
605 }
606
607 static void client_urgent_notify(ObClient *self)
608 {
609 if (self->urgent)
610 frame_flash_start(self->frame);
611 else
612 frame_flash_stop(self->frame);
613 }
614
615 static void client_restore_session_state(ObClient *self)
616 {
617 GList *it;
618
619 if (!(it = session_state_find(self)))
620 return;
621
622 self->session = it->data;
623
624 RECT_SET_POINT(self->area, self->session->x, self->session->y);
625 self->positioned = PPosition;
626 if (self->session->w > 0)
627 self->area.width = self->session->w;
628 if (self->session->h > 0)
629 self->area.height = self->session->h;
630 XResizeWindow(ob_display, self->window,
631 self->area.width, self->area.height);
632
633 self->desktop = (self->session->desktop == DESKTOP_ALL ?
634 self->session->desktop :
635 MIN(screen_num_desktops - 1, self->session->desktop));
636 PROP_SET32(self->window, net_wm_desktop, cardinal, self->desktop);
637
638 self->shaded = self->session->shaded;
639 self->iconic = self->session->iconic;
640 self->skip_pager = self->session->skip_pager;
641 self->skip_taskbar = self->session->skip_taskbar;
642 self->fullscreen = self->session->fullscreen;
643 self->above = self->session->above;
644 self->below = self->session->below;
645 self->max_horz = self->session->max_horz;
646 self->max_vert = self->session->max_vert;
647 }
648
649 static void client_restore_session_stacking(ObClient *self)
650 {
651 GList *it;
652
653 if (!self->session) return;
654
655 it = g_list_find(session_saved_state, self->session);
656 for (it = g_list_previous(it); it; it = g_list_previous(it)) {
657 GList *cit;
658
659 for (cit = client_list; cit; cit = g_list_next(cit))
660 if (session_state_cmp(it->data, cit->data))
661 break;
662 if (cit) {
663 client_calc_layer(self);
664 stacking_below(CLIENT_AS_WINDOW(self),
665 CLIENT_AS_WINDOW(cit->data));
666 break;
667 }
668 }
669 }
670
671 void client_move_onscreen(ObClient *self, gboolean rude)
672 {
673 gint x = self->area.x;
674 gint y = self->area.y;
675 if (client_find_onscreen(self, &x, &y,
676 self->frame->area.width,
677 self->frame->area.height, rude)) {
678 client_move(self, x, y);
679 }
680 }
681
682 gboolean client_find_onscreen(ObClient *self, gint *x, gint *y, gint w, gint h,
683 gboolean rude)
684 {
685 Rect *a;
686 gint ox = *x, oy = *y;
687
688 frame_client_gravity(self->frame, x, y); /* get where the frame
689 would be */
690
691 /* XXX watch for xinerama dead areas */
692 /* This makes sure windows aren't entirely outside of the screen so you
693 * can't see them at all */
694 if (client_normal(self)) {
695 a = screen_area(self->desktop);
696 if (!self->strut.right && *x >= a->x + a->width - 1)
697 *x = a->x + a->width - self->frame->area.width;
698 if (!self->strut.bottom && *y >= a->y + a->height - 1)
699 *y = a->y + a->height - self->frame->area.height;
700 if (!self->strut.left && *x + self->frame->area.width - 1 < a->x)
701 *x = a->x;
702 if (!self->strut.top && *y + self->frame->area.height - 1 < a->y)
703 *y = a->y;
704 }
705
706 /* This here doesn't let windows even a pixel outside the screen,
707 * when called from client_manage, programs placing themselves are
708 * forced completely onscreen, while things like
709 * xterm -geometry resolution-width/2 will work fine. Trying to
710 * place it completely offscreen will be handled in the above code.
711 * Sorry for this confused comment, i am tired. */
712 if (rude) {
713 /* avoid the xinerama monitor divide while we're at it,
714 * remember to fix the placement stuff to avoid it also and
715 * then remove this XXX */
716 a = screen_physical_area_monitor(client_monitor(self));
717 /* dont let windows map/move into the strut unless they
718 are bigger than the available area */
719 if (w <= a->width) {
720 if (!self->strut.left && *x < a->x) *x = a->x;
721 if (!self->strut.right && *x + w > a->x + a->width)
722 *x = a->x + a->width - w;
723 }
724 if (h <= a->height) {
725 if (!self->strut.top && *y < a->y) *y = a->y;
726 if (!self->strut.bottom && *y + h > a->y + a->height)
727 *y = a->y + a->height - h;
728 }
729 }
730
731 frame_frame_gravity(self->frame, x, y); /* get where the client
732 should be */
733
734 return ox != *x || oy != *y;
735 }
736
737 static void client_toggle_border(ObClient *self, gboolean show)
738 {
739 /* adjust our idea of where the client is, based on its border. When the
740 border is removed, the client should now be considered to be in a
741 different position.
742 when re-adding the border to the client, the same operation needs to be
743 reversed. */
744 gint oldx = self->area.x, oldy = self->area.y;
745 gint x = oldx, y = oldy;
746 switch(self->gravity) {
747 default:
748 case NorthWestGravity:
749 case WestGravity:
750 case SouthWestGravity:
751 break;
752 case NorthEastGravity:
753 case EastGravity:
754 case SouthEastGravity:
755 if (show) x -= self->border_width * 2;
756 else x += self->border_width * 2;
757 break;
758 case NorthGravity:
759 case SouthGravity:
760 case CenterGravity:
761 case ForgetGravity:
762 case StaticGravity:
763 if (show) x -= self->border_width;
764 else x += self->border_width;
765 break;
766 }
767 switch(self->gravity) {
768 default:
769 case NorthWestGravity:
770 case NorthGravity:
771 case NorthEastGravity:
772 break;
773 case SouthWestGravity:
774 case SouthGravity:
775 case SouthEastGravity:
776 if (show) y -= self->border_width * 2;
777 else y += self->border_width * 2;
778 break;
779 case WestGravity:
780 case EastGravity:
781 case CenterGravity:
782 case ForgetGravity:
783 case StaticGravity:
784 if (show) y -= self->border_width;
785 else y += self->border_width;
786 break;
787 }
788 self->area.x = x;
789 self->area.y = y;
790
791 if (show) {
792 XSetWindowBorderWidth(ob_display, self->window, self->border_width);
793
794 /* move the client so it is back it the right spot _with_ its
795 border! */
796 if (x != oldx || y != oldy)
797 XMoveWindow(ob_display, self->window, x, y);
798 } else
799 XSetWindowBorderWidth(ob_display, self->window, 0);
800 }
801
802
803 static void client_get_all(ObClient *self)
804 {
805 client_get_area(self);
806 client_update_transient_for(self);
807 client_update_wmhints(self);
808 client_get_startup_id(self);
809 client_get_desktop(self);
810 client_get_shaped(self);
811
812 client_get_mwm_hints(self);
813 client_get_type(self);/* this can change the mwmhints for special cases */
814
815 /* The transient hint is used to pick a type, but the type can also affect
816 transiency (dialogs are always made transients). This is Havoc's idea,
817 but it is needed to make some apps work right (eg tsclient). */
818 client_update_transient_for(self);
819
820 client_get_state(self);
821
822 {
823 /* a couple type-based defaults for new windows */
824
825 /* this makes sure that these windows appear on all desktops */
826 if (self->type == OB_CLIENT_TYPE_DESKTOP)
827 self->desktop = DESKTOP_ALL;
828 }
829
830 client_update_protocols(self);
831
832 client_get_gravity(self); /* get the attribute gravity */
833 client_update_normal_hints(self); /* this may override the attribute
834 gravity */
835
836 /* got the type, the mwmhints, the protocols, and the normal hints
837 (min/max sizes), so we're ready to set up the decorations/functions */
838 client_setup_decor_and_functions(self);
839
840 client_update_title(self);
841 client_update_class(self);
842 client_update_sm_client_id(self);
843 client_update_strut(self);
844 client_update_icons(self);
845 }
846
847 static void client_get_startup_id(ObClient *self)
848 {
849 if (!(PROP_GETS(self->window, net_startup_id, utf8, &self->startup_id)))
850 if (self->group)
851 PROP_GETS(self->group->leader,
852 net_startup_id, utf8, &self->startup_id);
853 }
854
855 static void client_get_area(ObClient *self)
856 {
857 XWindowAttributes wattrib;
858 Status ret;
859
860 ret = XGetWindowAttributes(ob_display, self->window, &wattrib);
861 g_assert(ret != BadWindow);
862
863 RECT_SET(self->area, wattrib.x, wattrib.y, wattrib.width, wattrib.height);
864 self->border_width = wattrib.border_width;
865 }
866
867 static void client_get_desktop(ObClient *self)
868 {
869 guint32 d = screen_num_desktops; /* an always-invalid value */
870
871 if (PROP_GET32(self->window, net_wm_desktop, cardinal, &d)) {
872 if (d >= screen_num_desktops && d != DESKTOP_ALL)
873 self->desktop = screen_num_desktops - 1;
874 else
875 self->desktop = d;
876 } else {
877 gboolean trdesk = FALSE;
878
879 if (self->transient_for) {
880 if (self->transient_for != OB_TRAN_GROUP) {
881 self->desktop = self->transient_for->desktop;
882 trdesk = TRUE;
883 } else {
884 GSList *it;
885
886 for (it = self->group->members; it; it = g_slist_next(it))
887 if (it->data != self &&
888 !((ObClient*)it->data)->transient_for) {
889 self->desktop = ((ObClient*)it->data)->desktop;
890 trdesk = TRUE;
891 break;
892 }
893 }
894 }
895 if (!trdesk) {
896 /* try get from the startup-notification protocol */
897 if (sn_get_desktop(self->startup_id, &self->desktop)) {
898 if (self->desktop >= screen_num_desktops &&
899 self->desktop != DESKTOP_ALL)
900 self->desktop = screen_num_desktops - 1;
901 } else
902 /* defaults to the current desktop */
903 self->desktop = screen_desktop;
904 }
905 }
906 if (self->desktop != d) {
907 /* set the desktop hint, to make sure that it always exists */
908 PROP_SET32(self->window, net_wm_desktop, cardinal, self->desktop);
909 }
910 }
911
912 static void client_get_state(ObClient *self)
913 {
914 guint32 *state;
915 guint num;
916
917 if (PROP_GETA32(self->window, net_wm_state, atom, &state, &num)) {
918 gulong i;
919 for (i = 0; i < num; ++i) {
920 if (state[i] == prop_atoms.net_wm_state_modal)
921 self->modal = TRUE;
922 else if (state[i] == prop_atoms.net_wm_state_shaded)
923 self->shaded = TRUE;
924 else if (state[i] == prop_atoms.net_wm_state_hidden)
925 self->iconic = TRUE;
926 else if (state[i] == prop_atoms.net_wm_state_skip_taskbar)
927 self->skip_taskbar = TRUE;
928 else if (state[i] == prop_atoms.net_wm_state_skip_pager)
929 self->skip_pager = TRUE;
930 else if (state[i] == prop_atoms.net_wm_state_fullscreen)
931 self->fullscreen = TRUE;
932 else if (state[i] == prop_atoms.net_wm_state_maximized_vert)
933 self->max_vert = TRUE;
934 else if (state[i] == prop_atoms.net_wm_state_maximized_horz)
935 self->max_horz = TRUE;
936 else if (state[i] == prop_atoms.net_wm_state_above)
937 self->above = TRUE;
938 else if (state[i] == prop_atoms.net_wm_state_below)
939 self->below = TRUE;
940 else if (state[i] == prop_atoms.ob_wm_state_undecorated)
941 self->undecorated = TRUE;
942 }
943
944 g_free(state);
945 }
946
947 if (!(self->above || self->below)) {
948 if (self->group) {
949 /* apply stuff from the group */
950 GSList *it;
951 gint layer = -2;
952
953 for (it = self->group->members; it; it = g_slist_next(it)) {
954 ObClient *c = it->data;
955 if (c != self && !client_search_transient(self, c) &&
956 client_normal(self) && client_normal(c))
957 {
958 layer = MAX(layer,
959 (c->above ? 1 : (c->below ? -1 : 0)));
960 }
961 }
962 switch (layer) {
963 case -1:
964 self->below = TRUE;
965 break;
966 case -2:
967 case 0:
968 break;
969 case 1:
970 self->above = TRUE;
971 break;
972 default:
973 g_assert_not_reached();
974 break;
975 }
976 }
977 }
978 }
979
980 static void client_get_shaped(ObClient *self)
981 {
982 self->shaped = FALSE;
983 #ifdef SHAPE
984 if (extensions_shape) {
985 gint foo;
986 guint ufoo;
987 gint s;
988
989 XShapeSelectInput(ob_display, self->window, ShapeNotifyMask);
990
991 XShapeQueryExtents(ob_display, self->window, &s, &foo,
992 &foo, &ufoo, &ufoo, &foo, &foo, &foo, &ufoo,
993 &ufoo);
994 self->shaped = (s != 0);
995 }
996 #endif
997 }
998
999 void client_update_transient_for(ObClient *self)
1000 {
1001 Window t = None;
1002 ObClient *target = NULL;
1003
1004 if (XGetTransientForHint(ob_display, self->window, &t)) {
1005 self->transient = TRUE;
1006 if (t != self->window) { /* cant be transient to itself! */
1007 target = g_hash_table_lookup(window_map, &t);
1008 /* if this happens then we need to check for it*/
1009 g_assert(target != self);
1010 if (target && !WINDOW_IS_CLIENT(target)) {
1011 /* this can happen when a dialog is a child of
1012 a dockapp, for example */
1013 target = NULL;
1014 }
1015
1016 if (!target && self->group) {
1017 /* not transient to a client, see if it is transient for a
1018 group */
1019 if (t == self->group->leader ||
1020 t == None ||
1021 t == RootWindow(ob_display, ob_screen))
1022 {
1023 /* window is a transient for its group! */
1024 target = OB_TRAN_GROUP;
1025 }
1026 }
1027 }
1028 } else if (self->type == OB_CLIENT_TYPE_DIALOG && self->group) {
1029 self->transient = TRUE;
1030 target = OB_TRAN_GROUP;
1031 } else
1032 self->transient = FALSE;
1033
1034 /* if anything has changed... */
1035 if (target != self->transient_for) {
1036 if (self->transient_for == OB_TRAN_GROUP) { /* transient of group */
1037 GSList *it;
1038
1039 /* remove from old parents */
1040 for (it = self->group->members; it; it = g_slist_next(it)) {
1041 ObClient *c = it->data;
1042 if (c != self && !c->transient_for)
1043 c->transients = g_slist_remove(c->transients, self);
1044 }
1045 } else if (self->transient_for != NULL) { /* transient of window */
1046 /* remove from old parent */
1047 self->transient_for->transients =
1048 g_slist_remove(self->transient_for->transients, self);
1049 }
1050 self->transient_for = target;
1051 if (self->transient_for == OB_TRAN_GROUP) { /* transient of group */
1052 GSList *it;
1053
1054 /* add to new parents */
1055 for (it = self->group->members; it; it = g_slist_next(it)) {
1056 ObClient *c = it->data;
1057 if (c != self && !c->transient_for)
1058 c->transients = g_slist_append(c->transients, self);
1059 }
1060
1061 /* remove all transients which are in the group, that causes
1062 circlular pointer hell of doom */
1063 for (it = self->group->members; it; it = g_slist_next(it)) {
1064 GSList *sit, *next;
1065 for (sit = self->transients; sit; sit = next) {
1066 next = g_slist_next(sit);
1067 if (sit->data == it->data)
1068 self->transients =
1069 g_slist_delete_link(self->transients, sit);
1070 }
1071 }
1072 } else if (self->transient_for != NULL) { /* transient of window */
1073 /* add to new parent */
1074 self->transient_for->transients =
1075 g_slist_append(self->transient_for->transients, self);
1076 }
1077 }
1078 }
1079
1080 static void client_get_mwm_hints(ObClient *self)
1081 {
1082 guint num;
1083 guint32 *hints;
1084
1085 self->mwmhints.flags = 0; /* default to none */
1086
1087 if (PROP_GETA32(self->window, motif_wm_hints, motif_wm_hints,
1088 &hints, &num)) {
1089 if (num >= OB_MWM_ELEMENTS) {
1090 self->mwmhints.flags = hints[0];
1091 self->mwmhints.functions = hints[1];
1092 self->mwmhints.decorations = hints[2];
1093 }
1094 g_free(hints);
1095 }
1096 }
1097
1098 void client_get_type(ObClient *self)
1099 {
1100 guint num, i;
1101 guint32 *val;
1102
1103 self->type = -1;
1104
1105 if (PROP_GETA32(self->window, net_wm_window_type, atom, &val, &num)) {
1106 /* use the first value that we know about in the array */
1107 for (i = 0; i < num; ++i) {
1108 if (val[i] == prop_atoms.net_wm_window_type_desktop)
1109 self->type = OB_CLIENT_TYPE_DESKTOP;
1110 else if (val[i] == prop_atoms.net_wm_window_type_dock)
1111 self->type = OB_CLIENT_TYPE_DOCK;
1112 else if (val[i] == prop_atoms.net_wm_window_type_toolbar)
1113 self->type = OB_CLIENT_TYPE_TOOLBAR;
1114 else if (val[i] == prop_atoms.net_wm_window_type_menu)
1115 self->type = OB_CLIENT_TYPE_MENU;
1116 else if (val[i] == prop_atoms.net_wm_window_type_utility)
1117 self->type = OB_CLIENT_TYPE_UTILITY;
1118 else if (val[i] == prop_atoms.net_wm_window_type_splash)
1119 self->type = OB_CLIENT_TYPE_SPLASH;
1120 else if (val[i] == prop_atoms.net_wm_window_type_dialog)
1121 self->type = OB_CLIENT_TYPE_DIALOG;
1122 else if (val[i] == prop_atoms.net_wm_window_type_normal)
1123 self->type = OB_CLIENT_TYPE_NORMAL;
1124 else if (val[i] == prop_atoms.kde_net_wm_window_type_override) {
1125 /* prevent this window from getting any decor or
1126 functionality */
1127 self->mwmhints.flags &= (OB_MWM_FLAG_FUNCTIONS |
1128 OB_MWM_FLAG_DECORATIONS);
1129 self->mwmhints.decorations = 0;
1130 self->mwmhints.functions = 0;
1131 }
1132 if (self->type != (ObClientType) -1)
1133 break; /* grab the first legit type */
1134 }
1135 g_free(val);
1136 }
1137
1138 if (self->type == (ObClientType) -1) {
1139 /*the window type hint was not set, which means we either classify
1140 ourself as a normal window or a dialog, depending on if we are a
1141 transient. */
1142 if (self->transient)
1143 self->type = OB_CLIENT_TYPE_DIALOG;
1144 else
1145 self->type = OB_CLIENT_TYPE_NORMAL;
1146 }
1147 }
1148
1149 void client_update_protocols(ObClient *self)
1150 {
1151 guint32 *proto;
1152 guint num_return, i;
1153
1154 self->focus_notify = FALSE;
1155 self->delete_window = FALSE;
1156
1157 if (PROP_GETA32(self->window, wm_protocols, atom, &proto, &num_return)) {
1158 for (i = 0; i < num_return; ++i) {
1159 if (proto[i] == prop_atoms.wm_delete_window) {
1160 /* this means we can request the window to close */
1161 self->delete_window = TRUE;
1162 } else if (proto[i] == prop_atoms.wm_take_focus)
1163 /* if this protocol is requested, then the window will be
1164 notified whenever we want it to receive focus */
1165 self->focus_notify = TRUE;
1166 }
1167 g_free(proto);
1168 }
1169 }
1170
1171 static void client_get_gravity(ObClient *self)
1172 {
1173 XWindowAttributes wattrib;
1174 Status ret;
1175
1176 ret = XGetWindowAttributes(ob_display, self->window, &wattrib);
1177 g_assert(ret != BadWindow);
1178 self->gravity = wattrib.win_gravity;
1179 }
1180
1181 void client_update_normal_hints(ObClient *self)
1182 {
1183 XSizeHints size;
1184 glong ret;
1185 gint oldgravity = self->gravity;
1186
1187 /* defaults */
1188 self->min_ratio = 0.0f;
1189 self->max_ratio = 0.0f;
1190 SIZE_SET(self->size_inc, 1, 1);
1191 SIZE_SET(self->base_size, 0, 0);
1192 SIZE_SET(self->min_size, 0, 0);
1193 SIZE_SET(self->max_size, G_MAXINT, G_MAXINT);
1194
1195 /* get the hints from the window */
1196 if (XGetWMNormalHints(ob_display, self->window, &size, &ret)) {
1197 /* normal windows can't request placement! har har
1198 if (!client_normal(self))
1199 */
1200 self->positioned = (size.flags & (PPosition|USPosition));
1201
1202 if (size.flags & PWinGravity) {
1203 self->gravity = size.win_gravity;
1204
1205 /* if the client has a frame, i.e. has already been mapped and
1206 is changing its gravity */
1207 if (self->frame && self->gravity != oldgravity) {
1208 /* move our idea of the client's position based on its new
1209 gravity */
1210 self->area.x = self->frame->area.x;
1211 self->area.y = self->frame->area.y;
1212 frame_frame_gravity(self->frame, &self->area.x, &self->area.y);
1213 }
1214 }
1215
1216 if (size.flags & PAspect) {
1217 if (size.min_aspect.y)
1218 self->min_ratio =
1219 (gfloat) size.min_aspect.x / size.min_aspect.y;
1220 if (size.max_aspect.y)
1221 self->max_ratio =
1222 (gfloat) size.max_aspect.x / size.max_aspect.y;
1223 }
1224
1225 if (size.flags & PMinSize)
1226 SIZE_SET(self->min_size, size.min_width, size.min_height);
1227
1228 if (size.flags & PMaxSize)
1229 SIZE_SET(self->max_size, size.max_width, size.max_height);
1230
1231 if (size.flags & PBaseSize)
1232 SIZE_SET(self->base_size, size.base_width, size.base_height);
1233
1234 if (size.flags & PResizeInc)
1235 SIZE_SET(self->size_inc, size.width_inc, size.height_inc);
1236 }
1237 }
1238
1239 void client_setup_decor_and_functions(ObClient *self)
1240 {
1241 /* start with everything (cept fullscreen) */
1242 self->decorations =
1243 (OB_FRAME_DECOR_TITLEBAR |
1244 (ob_rr_theme->show_handle ? OB_FRAME_DECOR_HANDLE : 0) |
1245 OB_FRAME_DECOR_GRIPS |
1246 OB_FRAME_DECOR_BORDER |
1247 OB_FRAME_DECOR_ICON |
1248 OB_FRAME_DECOR_ALLDESKTOPS |
1249 OB_FRAME_DECOR_ICONIFY |
1250 OB_FRAME_DECOR_MAXIMIZE |
1251 OB_FRAME_DECOR_SHADE |
1252 OB_FRAME_DECOR_CLOSE);
1253 self->functions =
1254 (OB_CLIENT_FUNC_RESIZE |
1255 OB_CLIENT_FUNC_MOVE |
1256 OB_CLIENT_FUNC_ICONIFY |
1257 OB_CLIENT_FUNC_MAXIMIZE |
1258 OB_CLIENT_FUNC_SHADE |
1259 OB_CLIENT_FUNC_CLOSE);
1260
1261 if (!(self->min_size.width < self->max_size.width ||
1262 self->min_size.height < self->max_size.height))
1263 self->functions &= ~OB_CLIENT_FUNC_RESIZE;
1264
1265 switch (self->type) {
1266 case OB_CLIENT_TYPE_NORMAL:
1267 /* normal windows retain all of the possible decorations and
1268 functionality, and are the only windows that you can fullscreen */
1269 self->functions |= OB_CLIENT_FUNC_FULLSCREEN;
1270 break;
1271
1272 case OB_CLIENT_TYPE_DIALOG:
1273 case OB_CLIENT_TYPE_UTILITY:
1274 /* these windows cannot be maximized */
1275 self->functions &= ~OB_CLIENT_FUNC_MAXIMIZE;
1276 break;
1277
1278 case OB_CLIENT_TYPE_MENU:
1279 case OB_CLIENT_TYPE_TOOLBAR:
1280 /* these windows get less functionality */
1281 self->functions &= ~(OB_CLIENT_FUNC_ICONIFY | OB_CLIENT_FUNC_RESIZE);
1282 break;
1283
1284 case OB_CLIENT_TYPE_DESKTOP:
1285 case OB_CLIENT_TYPE_DOCK:
1286 case OB_CLIENT_TYPE_SPLASH:
1287 /* none of these windows are manipulated by the window manager */
1288 self->decorations = 0;
1289 self->functions = 0;
1290 break;
1291 }
1292
1293 /* Mwm Hints are applied subtractively to what has already been chosen for
1294 decor and functionality */
1295 if (self->mwmhints.flags & OB_MWM_FLAG_DECORATIONS) {
1296 if (! (self->mwmhints.decorations & OB_MWM_DECOR_ALL)) {
1297 if (! ((self->mwmhints.decorations & OB_MWM_DECOR_HANDLE) ||
1298 (self->mwmhints.decorations & OB_MWM_DECOR_TITLE)))
1299 /* if the mwm hints request no handle or title, then all
1300 decorations are disabled */
1301 self->decorations = config_theme_keepborder ? OB_FRAME_DECOR_BORDER : 0;
1302 }
1303 }
1304
1305 if (self->mwmhints.flags & OB_MWM_FLAG_FUNCTIONS) {
1306 if (! (self->mwmhints.functions & OB_MWM_FUNC_ALL)) {
1307 if (! (self->mwmhints.functions & OB_MWM_FUNC_RESIZE))
1308 self->functions &= ~OB_CLIENT_FUNC_RESIZE;
1309 if (! (self->mwmhints.functions & OB_MWM_FUNC_MOVE))
1310 self->functions &= ~OB_CLIENT_FUNC_MOVE;
1311 /* dont let mwm hints kill any buttons
1312 if (! (self->mwmhints.functions & OB_MWM_FUNC_ICONIFY))
1313 self->functions &= ~OB_CLIENT_FUNC_ICONIFY;
1314 if (! (self->mwmhints.functions & OB_MWM_FUNC_MAXIMIZE))
1315 self->functions &= ~OB_CLIENT_FUNC_MAXIMIZE;
1316 */
1317 /* dont let mwm hints kill the close button
1318 if (! (self->mwmhints.functions & MwmFunc_Close))
1319 self->functions &= ~OB_CLIENT_FUNC_CLOSE; */
1320 }
1321 }
1322
1323 if (!(self->functions & OB_CLIENT_FUNC_SHADE))
1324 self->decorations &= ~OB_FRAME_DECOR_SHADE;
1325 if (!(self->functions & OB_CLIENT_FUNC_ICONIFY))
1326 self->decorations &= ~OB_FRAME_DECOR_ICONIFY;
1327 if (!(self->functions & OB_CLIENT_FUNC_RESIZE))
1328 self->decorations &= ~OB_FRAME_DECOR_GRIPS;
1329
1330 /* can't maximize without moving/resizing */
1331 if (!((self->functions & OB_CLIENT_FUNC_MAXIMIZE) &&
1332 (self->functions & OB_CLIENT_FUNC_MOVE) &&
1333 (self->functions & OB_CLIENT_FUNC_RESIZE))) {
1334 self->functions &= ~OB_CLIENT_FUNC_MAXIMIZE;
1335 self->decorations &= ~OB_FRAME_DECOR_MAXIMIZE;
1336 }
1337
1338 /* kill the handle on fully maxed windows */
1339 if (self->max_vert && self->max_horz)
1340 self->decorations &= ~OB_FRAME_DECOR_HANDLE;
1341
1342 /* finally, the user can have requested no decorations, which overrides
1343 everything (but doesnt give it a border if it doesnt have one) */
1344 if (self->undecorated) {
1345 if (config_theme_keepborder)
1346 self->decorations &= OB_FRAME_DECOR_BORDER;
1347 else
1348 self->decorations = 0;
1349 }
1350
1351 /* if we don't have a titlebar, then we cannot shade! */
1352 if (!(self->decorations & OB_FRAME_DECOR_TITLEBAR))
1353 self->functions &= ~OB_CLIENT_FUNC_SHADE;
1354
1355 /* now we need to check against rules for the client's current state */
1356 if (self->fullscreen) {
1357 self->functions &= (OB_CLIENT_FUNC_CLOSE |
1358 OB_CLIENT_FUNC_FULLSCREEN |
1359 OB_CLIENT_FUNC_ICONIFY);
1360 self->decorations = 0;
1361 }
1362
1363 client_change_allowed_actions(self);
1364
1365 if (self->frame) {
1366 /* adjust the client's decorations, etc. */
1367 client_reconfigure(self);
1368 }
1369 }
1370
1371 static void client_change_allowed_actions(ObClient *self)
1372 {
1373 gulong actions[9];
1374 gint num = 0;
1375
1376 /* desktop windows are kept on all desktops */
1377 if (self->type != OB_CLIENT_TYPE_DESKTOP)
1378 actions[num++] = prop_atoms.net_wm_action_change_desktop;
1379
1380 if (self->functions & OB_CLIENT_FUNC_SHADE)
1381 actions[num++] = prop_atoms.net_wm_action_shade;
1382 if (self->functions & OB_CLIENT_FUNC_CLOSE)
1383 actions[num++] = prop_atoms.net_wm_action_close;
1384 if (self->functions & OB_CLIENT_FUNC_MOVE)
1385 actions[num++] = prop_atoms.net_wm_action_move;
1386 if (self->functions & OB_CLIENT_FUNC_ICONIFY)
1387 actions[num++] = prop_atoms.net_wm_action_minimize;
1388 if (self->functions & OB_CLIENT_FUNC_RESIZE)
1389 actions[num++] = prop_atoms.net_wm_action_resize;
1390 if (self->functions & OB_CLIENT_FUNC_FULLSCREEN)
1391 actions[num++] = prop_atoms.net_wm_action_fullscreen;
1392 if (self->functions & OB_CLIENT_FUNC_MAXIMIZE) {
1393 actions[num++] = prop_atoms.net_wm_action_maximize_horz;
1394 actions[num++] = prop_atoms.net_wm_action_maximize_vert;
1395 }
1396
1397 PROP_SETA32(self->window, net_wm_allowed_actions, atom, actions, num);
1398
1399 /* make sure the window isn't breaking any rules now */
1400
1401 if (!(self->functions & OB_CLIENT_FUNC_SHADE) && self->shaded) {
1402 if (self->frame) client_shade(self, FALSE);
1403 else self->shaded = FALSE;
1404 }
1405 if (!(self->functions & OB_CLIENT_FUNC_ICONIFY) && self->iconic) {
1406 if (self->frame) client_iconify(self, FALSE, TRUE);
1407 else self->iconic = FALSE;
1408 }
1409 if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) && self->fullscreen) {
1410 if (self->frame) client_fullscreen(self, FALSE, TRUE);
1411 else self->fullscreen = FALSE;
1412 }
1413 if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE) && (self->max_horz ||
1414 self->max_vert)) {
1415 if (self->frame) client_maximize(self, FALSE, 0, TRUE);
1416 else self->max_vert = self->max_horz = FALSE;
1417 }
1418 }
1419
1420 void client_reconfigure(ObClient *self)
1421 {
1422 /* by making this pass FALSE for user, we avoid the emacs event storm where
1423 every configurenotify causes an update in its normal hints, i think this
1424 is generally what we want anyways... */
1425 client_configure(self, OB_CORNER_TOPLEFT, self->area.x, self->area.y,
1426 self->area.width, self->area.height, FALSE, TRUE);
1427 }
1428
1429 void client_update_wmhints(ObClient *self)
1430 {
1431 XWMHints *hints;
1432 gboolean ur = FALSE;
1433 GSList *it;
1434
1435 /* assume a window takes input if it doesnt specify */
1436 self->can_focus = TRUE;
1437
1438 if ((hints = XGetWMHints(ob_display, self->window)) != NULL) {
1439 if (hints->flags & InputHint)
1440 self->can_focus = hints->input;
1441
1442 /* only do this when first managing the window *AND* when we aren't
1443 starting up! */
1444 if (ob_state() != OB_STATE_STARTING && self->frame == NULL)
1445 if (hints->flags & StateHint)
1446 self->iconic = hints->initial_state == IconicState;
1447
1448 if (hints->flags & XUrgencyHint)
1449 ur = TRUE;
1450
1451 if (!(hints->flags & WindowGroupHint))
1452 hints->window_group = None;
1453
1454 /* did the group state change? */
1455 if (hints->window_group !=
1456 (self->group ? self->group->leader : None)) {
1457 /* remove from the old group if there was one */
1458 if (self->group != NULL) {
1459 /* remove transients of the group */
1460 for (it = self->group->members; it; it = g_slist_next(it))
1461 self->transients = g_slist_remove(self->transients,
1462 it->data);
1463
1464 /* remove myself from parents in the group */
1465 if (self->transient_for == OB_TRAN_GROUP) {
1466 for (it = self->group->members; it;
1467 it = g_slist_next(it))
1468 {
1469 ObClient *c = it->data;
1470
1471 if (c != self && !c->transient_for)
1472 c->transients = g_slist_remove(c->transients,
1473 self);
1474 }
1475 }
1476
1477 group_remove(self->group, self);
1478 self->group = NULL;
1479 }
1480 if (hints->window_group != None) {
1481 self->group = group_add(hints->window_group, self);
1482
1483 /* i can only have transients from the group if i am not
1484 transient myself */
1485 if (!self->transient_for) {
1486 /* add other transients of the group that are already
1487 set up */
1488 for (it = self->group->members; it;
1489 it = g_slist_next(it))
1490 {
1491 ObClient *c = it->data;
1492 if (c != self && c->transient_for == OB_TRAN_GROUP)
1493 self->transients =
1494 g_slist_append(self->transients, c);
1495 }
1496 }
1497 }
1498
1499 /* because the self->transient flag wont change from this call,
1500 we don't need to update the window's type and such, only its
1501 transient_for, and the transients lists of other windows in
1502 the group may be affected */
1503 client_update_transient_for(self);
1504 }
1505
1506 /* the WM_HINTS can contain an icon */
1507 client_update_icons(self);
1508
1509 XFree(hints);
1510 }
1511
1512 if (ur != self->urgent) {
1513 self->urgent = ur;
1514 /* fire the urgent callback if we're mapped, otherwise, wait until
1515 after we're mapped */
1516 if (self->frame)
1517 client_urgent_notify(self);
1518 }
1519 }
1520
1521 void client_update_title(ObClient *self)
1522 {
1523 GList *it;
1524 guint32 nums;
1525 guint i;
1526 gchar *data = NULL;
1527 gboolean read_title;
1528 gchar *old_title;
1529
1530 old_title = self->title;
1531
1532 /* try netwm */
1533 if (!PROP_GETS(self->window, net_wm_name, utf8, &data)) {
1534 /* try old x stuff */
1535 if (!PROP_GETS(self->window, wm_name, locale, &data)) {
1536 // http://developer.gnome.org/projects/gup/hig/draft_hig_new/windows-alert.html
1537 if (self->transient) {
1538 data = g_strdup("");
1539 goto no_number;
1540 } else
1541 data = g_strdup("Unnamed Window");
1542 }
1543 }
1544
1545 /* did the title change? then reset the title_count */
1546 if (old_title && 0 != strncmp(old_title, data, strlen(data)))
1547 self->title_count = 1;
1548
1549 /* look for duplicates and append a number */
1550 nums = 0;
1551 for (it = client_list; it; it = g_list_next(it))
1552 if (it->data != self) {
1553 ObClient *c = it->data;
1554 if (0 == strncmp(c->title, data, strlen(data)))
1555 nums |= 1 << c->title_count;
1556 }
1557 /* find first free number */
1558 for (i = 1; i <= 32; ++i)
1559 if (!(nums & (1 << i))) {
1560 if (self->title_count == 1 || i == 1)
1561 self->title_count = i;
1562 break;
1563 }
1564 /* dont display the number for the first window */
1565 if (self->title_count > 1) {
1566 gchar *ndata;
1567 ndata = g_strdup_printf("%s - [%u]", data, self->title_count);
1568 g_free(data);
1569 data = ndata;
1570 }
1571
1572 PROP_SETS(self->window, net_wm_visible_name, data);
1573 no_number:
1574 self->title = data;
1575
1576 if (self->frame)
1577 frame_adjust_title(self->frame);
1578
1579 g_free(old_title);
1580
1581 /* update the icon title */
1582 data = NULL;
1583 g_free(self->icon_title);
1584
1585 read_title = TRUE;
1586 /* try netwm */
1587 if (!PROP_GETS(self->window, net_wm_icon_name, utf8, &data))
1588 /* try old x stuff */
1589 if (!PROP_GETS(self->window, wm_icon_name, locale, &data)) {
1590 data = g_strdup(self->title);
1591 read_title = FALSE;
1592 }
1593
1594 /* append the title count, dont display the number for the first window */
1595 if (read_title && self->title_count > 1) {
1596 gchar *vdata, *ndata;
1597 ndata = g_strdup_printf(" - [%u]", self->title_count);
1598 vdata = g_strconcat(data, ndata, NULL);
1599 g_free(ndata);
1600 g_free(data);
1601 data = vdata;
1602 }
1603
1604 PROP_SETS(self->window, net_wm_visible_icon_name, data);
1605
1606 self->icon_title = data;
1607 }
1608
1609 void client_update_class(ObClient *self)
1610 {
1611 gchar **data;
1612 gchar *s;
1613
1614 if (self->name) g_free(self->name);
1615 if (self->class) g_free(self->class);
1616 if (self->role) g_free(self->role);
1617
1618 self->name = self->class = self->role = NULL;
1619
1620 if (PROP_GETSS(self->window, wm_class, locale, &data)) {
1621 if (data[0]) {
1622 self->name = g_strdup(data[0]);
1623 if (data[1])
1624 self->class = g_strdup(data[1]);
1625 }
1626 g_strfreev(data);
1627 }
1628
1629 if (PROP_GETS(self->window, wm_window_role, locale, &s))
1630 self->role = s;
1631
1632 if (self->name == NULL) self->name = g_strdup("");
1633 if (self->class == NULL) self->class = g_strdup("");
1634 if (self->role == NULL) self->role = g_strdup("");
1635 }
1636
1637 void client_update_strut(ObClient *self)
1638 {
1639 guint num;
1640 guint32 *data;
1641 gboolean got = FALSE;
1642 StrutPartial strut;
1643
1644 if (PROP_GETA32(self->window, net_wm_strut_partial, cardinal,
1645 &data, &num)) {
1646 if (num == 12) {
1647 got = TRUE;
1648 STRUT_PARTIAL_SET(strut,
1649 data[0], data[2], data[1], data[3],
1650 data[4], data[5], data[8], data[9],
1651 data[6], data[7], data[10], data[11]);
1652 }
1653 g_free(data);
1654 }
1655
1656 if (!got &&
1657 PROP_GETA32(self->window, net_wm_strut, cardinal, &data, &num)) {
1658 if (num == 4) {
1659 const Rect *a;
1660
1661 got = TRUE;
1662
1663 /* use the screen's width/height */
1664 a = screen_physical_area();
1665
1666 STRUT_PARTIAL_SET(strut,
1667 data[0], data[2], data[1], data[3],
1668 a->y, a->y + a->height - 1,
1669 a->x, a->x + a->width - 1,
1670 a->y, a->y + a->height - 1,
1671 a->x, a->x + a->width - 1);
1672 }
1673 g_free(data);
1674 }
1675
1676 if (!got)
1677 STRUT_PARTIAL_SET(strut, 0, 0, 0, 0,
1678 0, 0, 0, 0, 0, 0, 0, 0);
1679
1680 if (!STRUT_EQUAL(strut, self->strut)) {
1681 self->strut = strut;
1682
1683 /* updating here is pointless while we're being mapped cuz we're not in
1684 the client list yet */
1685 if (self->frame)
1686 screen_update_areas();
1687 }
1688 }
1689
1690 void client_update_icons(ObClient *self)
1691 {
1692 guint num;
1693 guint32 *data;
1694 guint w, h, i, j;
1695
1696 for (i = 0; i < self->nicons; ++i)
1697 g_free(self->icons[i].data);
1698 if (self->nicons > 0)
1699 g_free(self->icons);
1700 self->nicons = 0;
1701
1702 if (PROP_GETA32(self->window, net_wm_icon, cardinal, &data, &num)) {
1703 /* figure out how many valid icons are in here */
1704 i = 0;
1705 while (num - i > 2) {
1706 w = data[i++];
1707 h = data[i++];
1708 i += w * h;
1709 if (i > num || w*h == 0) break;
1710 ++self->nicons;
1711 }
1712
1713 self->icons = g_new(ObClientIcon, self->nicons);
1714
1715 /* store the icons */
1716 i = 0;
1717 for (j = 0; j < self->nicons; ++j) {
1718 guint x, y, t;
1719
1720 w = self->icons[j].width = data[i++];
1721 h = self->icons[j].height = data[i++];
1722
1723 if (w*h == 0) continue;
1724
1725 self->icons[j].data = g_new(RrPixel32, w * h);
1726 for (x = 0, y = 0, t = 0; t < w * h; ++t, ++x, ++i) {
1727 if (x >= w) {
1728 x = 0;
1729 ++y;
1730 }
1731 self->icons[j].data[t] =
1732 (((data[i] >> 24) & 0xff) << RrDefaultAlphaOffset) +
1733 (((data[i] >> 16) & 0xff) << RrDefaultRedOffset) +
1734 (((data[i] >> 8) & 0xff) << RrDefaultGreenOffset) +
1735 (((data[i] >> 0) & 0xff) << RrDefaultBlueOffset);
1736 }
1737 g_assert(i <= num);
1738 }
1739
1740 g_free(data);
1741 } else if (PROP_GETA32(self->window, kwm_win_icon,
1742 kwm_win_icon, &data, &num)) {
1743 if (num == 2) {
1744 self->nicons++;
1745 self->icons = g_new(ObClientIcon, self->nicons);
1746 xerror_set_ignore(TRUE);
1747 if (!RrPixmapToRGBA(ob_rr_inst,
1748 data[0], data[1],
1749 &self->icons[self->nicons-1].width,
1750 &self->icons[self->nicons-1].height,
1751 &self->icons[self->nicons-1].data)) {
1752 g_free(&self->icons[self->nicons-1]);
1753 self->nicons--;
1754 }
1755 xerror_set_ignore(FALSE);
1756 }
1757 g_free(data);
1758 } else {
1759 XWMHints *hints;
1760
1761 if ((hints = XGetWMHints(ob_display, self->window))) {
1762 if (hints->flags & IconPixmapHint) {
1763 self->nicons++;
1764 self->icons = g_new(ObClientIcon, self->nicons);
1765 xerror_set_ignore(TRUE);
1766 if (!RrPixmapToRGBA(ob_rr_inst,
1767 hints->icon_pixmap,
1768 (hints->flags & IconMaskHint ?
1769 hints->icon_mask : None),
1770 &self->icons[self->nicons-1].width,
1771 &self->icons[self->nicons-1].height,
1772 &self->icons[self->nicons-1].data)){
1773 g_free(&self->icons[self->nicons-1]);
1774 self->nicons--;
1775 }
1776 xerror_set_ignore(FALSE);
1777 }
1778 XFree(hints);
1779 }
1780 }
1781
1782 if (self->frame)
1783 frame_adjust_icon(self->frame);
1784 }
1785
1786 static void client_change_state(ObClient *self)
1787 {
1788 gulong state[2];
1789 gulong netstate[11];
1790 guint num;
1791
1792 state[0] = self->wmstate;
1793 state[1] = None;
1794 PROP_SETA32(self->window, wm_state, wm_state, state, 2);
1795
1796 num = 0;
1797 if (self->modal)
1798 netstate[num++] = prop_atoms.net_wm_state_modal;
1799 if (self->shaded)
1800 netstate[num++] = prop_atoms.net_wm_state_shaded;
1801 if (self->iconic)
1802 netstate[num++] = prop_atoms.net_wm_state_hidden;
1803 if (self->skip_taskbar)
1804 netstate[num++] = prop_atoms.net_wm_state_skip_taskbar;
1805 if (self->skip_pager)
1806 netstate[num++] = prop_atoms.net_wm_state_skip_pager;
1807 if (self->fullscreen)
1808 netstate[num++] = prop_atoms.net_wm_state_fullscreen;
1809 if (self->max_vert)
1810 netstate[num++] = prop_atoms.net_wm_state_maximized_vert;
1811 if (self->max_horz)
1812 netstate[num++] = prop_atoms.net_wm_state_maximized_horz;
1813 if (self->above)
1814 netstate[num++] = prop_atoms.net_wm_state_above;
1815 if (self->below)
1816 netstate[num++] = prop_atoms.net_wm_state_below;
1817 if (self->undecorated)
1818 netstate[num++] = prop_atoms.ob_wm_state_undecorated;
1819 PROP_SETA32(self->window, net_wm_state, atom, netstate, num);
1820
1821 client_calc_layer(self);
1822
1823 if (self->frame)
1824 frame_adjust_state(self->frame);
1825 }
1826
1827 ObClient *client_search_focus_tree(ObClient *self)
1828 {
1829 GSList *it;
1830 ObClient *ret;
1831
1832 for (it = self->transients; it; it = g_slist_next(it)) {
1833 if (client_focused(it->data)) return it->data;
1834 if ((ret = client_search_focus_tree(it->data))) return ret;
1835 }
1836 return NULL;
1837 }
1838
1839 ObClient *client_search_focus_tree_full(ObClient *self)
1840 {
1841 if (self->transient_for) {
1842 if (self->transient_for != OB_TRAN_GROUP) {
1843 return client_search_focus_tree_full(self->transient_for);
1844 } else {
1845 GSList *it;
1846 gboolean recursed = FALSE;
1847
1848 for (it = self->group->members; it; it = g_slist_next(it))
1849 if (!((ObClient*)it->data)->transient_for) {
1850 ObClient *c;
1851 if ((c = client_search_focus_tree_full(it->data)))
1852 return c;
1853 recursed = TRUE;
1854 }
1855 if (recursed)
1856 return NULL;
1857 }
1858 }
1859
1860 /* this function checks the whole tree, the client_search_focus_tree~
1861 does not, so we need to check this window */
1862 if (client_focused(self))
1863 return self;
1864 return client_search_focus_tree(self);
1865 }
1866
1867 static ObStackingLayer calc_layer(ObClient *self)
1868 {
1869 ObStackingLayer l;
1870
1871 if (self->fullscreen &&
1872 (client_focused(self) || client_search_focus_tree(self)))
1873 l = OB_STACKING_LAYER_FULLSCREEN;
1874 else if (self->type == OB_CLIENT_TYPE_DESKTOP)
1875 l = OB_STACKING_LAYER_DESKTOP;
1876 else if (self->type == OB_CLIENT_TYPE_DOCK) {
1877 if (self->below) l = OB_STACKING_LAYER_NORMAL;
1878 else l = OB_STACKING_LAYER_ABOVE;
1879 }
1880 else if (self->above) l = OB_STACKING_LAYER_ABOVE;
1881 else if (self->below) l = OB_STACKING_LAYER_BELOW;
1882 else l = OB_STACKING_LAYER_NORMAL;
1883
1884 return l;
1885 }
1886
1887 static void client_calc_layer_recursive(ObClient *self, ObClient *orig,
1888 ObStackingLayer l, gboolean raised)
1889 {
1890 ObStackingLayer old, own;
1891 GSList *it;
1892
1893 old = self->layer;
1894 own = calc_layer(self);
1895 self->layer = l > own ? l : own;
1896
1897 for (it = self->transients; it; it = g_slist_next(it))
1898 client_calc_layer_recursive(it->data, orig,
1899 l, raised ? raised : l != old);
1900
1901 if (!raised && l != old)
1902 if (orig->frame) { /* only restack if the original window is managed */
1903 stacking_remove(CLIENT_AS_WINDOW(self));
1904 stacking_add(CLIENT_AS_WINDOW(self));
1905 }
1906 }
1907
1908 void client_calc_layer(ObClient *self)
1909 {
1910 ObStackingLayer l;
1911 ObClient *orig;
1912
1913 orig = self;
1914
1915 /* transients take on the layer of their parents */
1916 self = client_search_top_transient(self);
1917
1918 l = calc_layer(self);
1919
1920 client_calc_layer_recursive(self, orig, l, FALSE);
1921 }
1922
1923 gboolean client_should_show(ObClient *self)
1924 {
1925 if (self->iconic)
1926 return FALSE;
1927 if (client_normal(self) && screen_showing_desktop)
1928 return FALSE;
1929 /*
1930 if (self->transient_for) {
1931 if (self->transient_for != OB_TRAN_GROUP)
1932 return client_should_show(self->transient_for);
1933 else {
1934 GSList *it;
1935
1936 for (it = self->group->members; it; it = g_slist_next(it)) {
1937 ObClient *c = it->data;
1938 if (c != self && !c->transient_for) {
1939 if (client_should_show(c))
1940 return TRUE;
1941 }
1942 }
1943 }
1944 }
1945 */
1946 if (self->desktop == screen_desktop || self->desktop == DESKTOP_ALL)
1947 return TRUE;
1948
1949 return FALSE;
1950 }
1951
1952 static void client_showhide(ObClient *self)
1953 {
1954
1955 if (client_should_show(self))
1956 frame_show(self->frame);
1957 else
1958 frame_hide(self->frame);
1959 }
1960
1961 gboolean client_normal(ObClient *self) {
1962 return ! (self->type == OB_CLIENT_TYPE_DESKTOP ||
1963 self->type == OB_CLIENT_TYPE_DOCK ||
1964 self->type == OB_CLIENT_TYPE_SPLASH);
1965 }
1966
1967 static void client_apply_startup_state(ObClient *self)
1968 {
1969 /* these are in a carefully crafted order.. */
1970
1971 if (self->iconic) {
1972 self->iconic = FALSE;
1973 client_iconify(self, TRUE, FALSE);
1974 }
1975 if (self->fullscreen) {
1976 self->fullscreen = FALSE;
1977 client_fullscreen(self, TRUE, FALSE);
1978 }
1979 if (self->undecorated) {
1980 self->undecorated = FALSE;
1981 client_set_undecorated(self, TRUE);
1982 }
1983 if (self->shaded) {
1984 self->shaded = FALSE;
1985 client_shade(self, TRUE);
1986 }
1987 if (self->urgent)
1988 client_urgent_notify(self);
1989
1990 if (self->max_vert && self->max_horz) {
1991 self->max_vert = self->max_horz = FALSE;
1992 client_maximize(self, TRUE, 0, FALSE);
1993 } else if (self->max_vert) {
1994 self->max_vert = FALSE;
1995 client_maximize(self, TRUE, 2, FALSE);
1996 } else if (self->max_horz) {
1997 self->max_horz = FALSE;
1998 client_maximize(self, TRUE, 1, FALSE);
1999 }
2000
2001 /* nothing to do for the other states:
2002 skip_taskbar
2003 skip_pager
2004 modal
2005 above
2006 below
2007 */
2008 }
2009
2010 void client_configure_full(ObClient *self, ObCorner anchor,
2011 gint x, gint y, gint w, gint h,
2012 gboolean user, gboolean final,
2013 gboolean force_reply)
2014 {
2015 gint oldw, oldh;
2016 gboolean send_resize_client;
2017 gboolean moved = FALSE, resized = FALSE;
2018 guint fdecor = self->frame->decorations;
2019 gboolean fhorz = self->frame->max_horz;
2020
2021 /* make the frame recalculate its dimentions n shit without changing
2022 anything visible for real, this way the constraints below can work with
2023 the updated frame dimensions. */
2024 frame_adjust_area(self->frame, TRUE, TRUE, TRUE);
2025
2026 /* gets the frame's position */
2027 frame_client_gravity(self->frame, &x, &y);
2028
2029 /* these positions are frame positions, not client positions */
2030
2031 /* set the size and position if fullscreen */
2032 if (self->fullscreen) {
2033 #ifdef VIDMODE
2034 gint dot;
2035 XF86VidModeModeLine mode;
2036 #endif
2037 Rect *a;
2038 guint i;
2039
2040 i = client_monitor(self);
2041 a = screen_physical_area_monitor(i);
2042
2043 #ifdef VIDMODE
2044 if (i == 0 && /* primary head */
2045 extensions_vidmode &&
2046 XF86VidModeGetViewPort(ob_display, ob_screen, &x, &y) &&
2047 /* get the mode last so the mode.privsize isnt freed incorrectly */
2048 XF86VidModeGetModeLine(ob_display, ob_screen, &dot, &mode)) {
2049 x += a->x;
2050 y += a->y;
2051 w = mode.hdisplay;
2052 h = mode.vdisplay;
2053 if (mode.privsize) XFree(mode.private);
2054 } else
2055 #endif
2056 {
2057 x = a->x;
2058 y = a->y;
2059 w = a->width;
2060 h = a->height;
2061 }
2062
2063 user = FALSE; /* ignore that increment etc shit when in fullscreen */
2064 } else {
2065 Rect *a;
2066
2067 a = screen_area_monitor(self->desktop, client_monitor(self));
2068
2069 /* set the size and position if maximized */
2070 if (self->max_horz) {
2071 x = a->x;
2072 w = a->width - self->frame->size.left - self->frame->size.right;
2073 }
2074 if (self->max_vert) {
2075 y = a->y;
2076 h = a->height - self->frame->size.top - self->frame->size.bottom;
2077 }
2078 }
2079
2080 /* gets the client's position */
2081 frame_frame_gravity(self->frame, &x, &y);
2082
2083 /* these override the above states! if you cant move you can't move! */
2084 if (user) {
2085 if (!(self->functions & OB_CLIENT_FUNC_MOVE)) {
2086 x = self->area.x;
2087 y = self->area.y;
2088 }
2089 if (!(self->functions & OB_CLIENT_FUNC_RESIZE)) {
2090 w = self->area.width;
2091 h = self->area.height;
2092 }
2093 }
2094
2095 if (!(w == self->area.width && h == self->area.height)) {
2096 gint basew, baseh, minw, minh;
2097
2098 /* base size is substituted with min size if not specified */
2099 if (self->base_size.width || self->base_size.height) {
2100 basew = self->base_size.width;
2101 baseh = self->base_size.height;
2102 } else {
2103 basew = self->min_size.width;
2104 baseh = self->min_size.height;
2105 }
2106 /* min size is substituted with base size if not specified */
2107 if (self->min_size.width || self->min_size.height) {
2108 minw = self->min_size.width;
2109 minh = self->min_size.height;
2110 } else {
2111 minw = self->base_size.width;
2112 minh = self->base_size.height;
2113 }
2114
2115 /* if this is a user-requested resize, then check against min/max
2116 sizes */
2117
2118 /* smaller than min size or bigger than max size? */
2119 if (w > self->max_size.width) w = self->max_size.width;
2120 if (w < minw) w = minw;
2121 if (h > self->max_size.height) h = self->max_size.height;
2122 if (h < minh) h = minh;
2123
2124 w -= basew;
2125 h -= baseh;
2126
2127 /* keep to the increments */
2128 w /= self->size_inc.width;
2129 h /= self->size_inc.height;
2130
2131 /* you cannot resize to nothing */
2132 if (basew + w < 1) w = 1 - basew;
2133 if (baseh + h < 1) h = 1 - baseh;
2134
2135 /* store the logical size */
2136 SIZE_SET(self->logical_size,
2137 self->size_inc.width > 1 ? w : w + basew,
2138 self->size_inc.height > 1 ? h : h + baseh);
2139
2140 w *= self->size_inc.width;
2141 h *= self->size_inc.height;
2142
2143 w += basew;
2144 h += baseh;
2145
2146 /* adjust the height to match the width for the aspect ratios.
2147 for this, min size is not substituted for base size ever. */
2148 w -= self->base_size.width;
2149 h -= self->base_size.height;
2150
2151 if (!self->fullscreen) {
2152 if (self->min_ratio)
2153 if (h * self->min_ratio > w) {
2154 h = (gint)(w / self->min_ratio);
2155
2156 /* you cannot resize to nothing */
2157 if (h < 1) {
2158 h = 1;
2159 w = (gint)(h * self->min_ratio);
2160 }
2161 }
2162 if (self->max_ratio)
2163 if (h * self->max_ratio < w) {
2164 h = (gint)(w / self->max_ratio);
2165
2166 /* you cannot resize to nothing */
2167 if (h < 1) {
2168 h = 1;
2169 w = (gint)(h * self->min_ratio);
2170 }
2171 }
2172 }
2173
2174 w += self->base_size.width;
2175 h += self->base_size.height;
2176 }
2177
2178 g_assert(w > 0);
2179 g_assert(h > 0);
2180
2181 switch (anchor) {
2182 case OB_CORNER_TOPLEFT:
2183 break;
2184 case OB_CORNER_TOPRIGHT:
2185 x -= w - self->area.width;
2186 break;
2187 case OB_CORNER_BOTTOMLEFT:
2188 y -= h - self->area.height;
2189 break;
2190 case OB_CORNER_BOTTOMRIGHT:
2191 x -= w - self->area.width;
2192 y -= h - self->area.height;
2193 break;
2194 }
2195
2196 moved = x != self->area.x || y != self->area.y;
2197 resized = w != self->area.width || h != self->area.height;
2198
2199 oldw = self->area.width;
2200 oldh = self->area.height;
2201 RECT_SET(self->area, x, y, w, h);
2202
2203 /* for app-requested resizes, always resize if 'resized' is true.
2204 for user-requested ones, only resize if final is true, or when
2205 resizing in redraw mode */
2206 send_resize_client = ((!user && resized) ||
2207 (user && (final ||
2208 (resized && config_resize_redraw))));
2209
2210 /* if the client is enlarging, then resize the client before the frame */
2211 if (send_resize_client && user && (w > oldw || h > oldh))
2212 XResizeWindow(ob_display, self->window, MAX(w, oldw), MAX(h, oldh));
2213
2214 /* move/resize the frame to match the request */
2215 if (self->frame) {
2216 if (self->decorations != fdecor || self->max_horz != fhorz)
2217 moved = resized = TRUE;
2218
2219 if (moved || resized)
2220 frame_adjust_area(self->frame, moved, resized, FALSE);
2221
2222 if (!resized && (force_reply || ((!user && moved) || (user && final))))
2223 {
2224 XEvent event;
2225 event.type = ConfigureNotify;
2226 event.xconfigure.display = ob_display;
2227 event.xconfigure.event = self->window;
2228 event.xconfigure.window = self->window;
2229
2230 /* root window real coords */
2231 event.xconfigure.x = self->frame->area.x + self->frame->size.left -
2232 self->border_width;
2233 event.xconfigure.y = self->frame->area.y + self->frame->size.top -
2234 self->border_width;
2235 event.xconfigure.width = w;
2236 event.xconfigure.height = h;
2237 event.xconfigure.border_width = 0;
2238 event.xconfigure.above = self->frame->plate;
2239 event.xconfigure.override_redirect = FALSE;
2240 XSendEvent(event.xconfigure.display, event.xconfigure.window,
2241 FALSE, StructureNotifyMask, &event);
2242 }
2243 }
2244
2245 /* if the client is shrinking, then resize the frame before the client */
2246 if (send_resize_client && (!user || (w <= oldw || h <= oldh)))
2247 XResizeWindow(ob_display, self->window, w, h);
2248
2249 XFlush(ob_display);
2250 }
2251
2252 void client_fullscreen(ObClient *self, gboolean fs, gboolean savearea)
2253 {
2254 gint x, y, w, h;
2255
2256 if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) || /* can't */
2257 self->fullscreen == fs) return; /* already done */
2258
2259 self->fullscreen = fs;
2260 client_change_state(self); /* change the state hints on the client,
2261 and adjust out layer/stacking */
2262
2263 if (fs) {
2264 if (savearea)
2265 self->pre_fullscreen_area = self->area;
2266
2267 /* these are not actually used cuz client_configure will set them
2268 as appropriate when the window is fullscreened */
2269 x = y = w = h = 0;
2270 } else {
2271 Rect *a;
2272
2273 if (self->pre_fullscreen_area.width > 0 &&
2274 self->pre_fullscreen_area.height > 0)
2275 {
2276 x = self->pre_fullscreen_area.x;
2277 y = self->pre_fullscreen_area.y;
2278 w = self->pre_fullscreen_area.width;
2279 h = self->pre_fullscreen_area.height;
2280 RECT_SET(self->pre_fullscreen_area, 0, 0, 0, 0);
2281 } else {
2282 /* pick some fallbacks... */
2283 a = screen_area_monitor(self->desktop, 0);
2284 x = a->x + a->width / 4;
2285 y = a->y + a->height / 4;
2286 w = a->width / 2;
2287 h = a->height / 2;
2288 }
2289 }
2290
2291 client_setup_decor_and_functions(self);
2292
2293 client_move_resize(self, x, y, w, h);
2294
2295 /* try focus us when we go into fullscreen mode */
2296 client_focus(self);
2297 }
2298
2299 static void client_iconify_recursive(ObClient *self,
2300 gboolean iconic, gboolean curdesk)
2301 {
2302 GSList *it;
2303 gboolean changed = FALSE;
2304
2305
2306 if (self->iconic != iconic) {
2307 ob_debug("%sconifying window: 0x%lx\n", (iconic ? "I" : "Uni"),
2308 self->window);
2309
2310 self->iconic = iconic;
2311
2312 if (iconic) {
2313 if (self->functions & OB_CLIENT_FUNC_ICONIFY) {
2314 glong old;
2315
2316 old = self->wmstate;
2317 self->wmstate = IconicState;
2318 if (old != self->wmstate)
2319 PROP_MSG(self->window, kde_wm_change_state,
2320 self->wmstate, 1, 0, 0);
2321
2322 /* update the focus lists.. iconic windows go to the bottom of
2323 the list, put the new iconic window at the 'top of the
2324 bottom'. */
2325 focus_order_to_top(self);
2326
2327 changed = TRUE;
2328 }
2329 } else {
2330 glong old;
2331
2332 if (curdesk)
2333 client_set_desktop(self, screen_desktop, FALSE);
2334
2335 old = self->wmstate;
2336 self->wmstate = self->shaded ? IconicState : NormalState;
2337 if (old != self->wmstate)
2338 PROP_MSG(self->window, kde_wm_change_state,
2339 self->wmstate, 1, 0, 0);
2340
2341 /* this puts it after the current focused window */
2342 focus_order_remove(self);
2343 focus_order_add_new(self);
2344
2345 /* this is here cuz with the VIDMODE extension, the viewport can
2346 change while a fullscreen window is iconic, and when it
2347 uniconifies, it would be nice if it did so to the new position
2348 of the viewport */
2349 client_reconfigure(self);
2350
2351 changed = TRUE;
2352 }
2353 }
2354
2355 if (changed) {
2356 client_change_state(self);
2357 client_showhide(self);
2358 screen_update_areas();
2359 }
2360
2361 /* iconify all transients */
2362 for (it = self->transients; it; it = g_slist_next(it))
2363 if (it->data != self) client_iconify_recursive(it->data,
2364 iconic, curdesk);
2365 }
2366
2367 void client_iconify(ObClient *self, gboolean iconic, gboolean curdesk)
2368 {
2369 /* move up the transient chain as far as possible first */
2370 self = client_search_top_transient(self);
2371
2372 client_iconify_recursive(client_search_top_transient(self),
2373 iconic, curdesk);
2374 }
2375
2376 void client_maximize(ObClient *self, gboolean max, gint dir, gboolean savearea)
2377 {
2378 gint x, y, w, h;
2379
2380 g_assert(dir == 0 || dir == 1 || dir == 2);
2381 if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE)) return; /* can't */
2382
2383 /* check if already done */
2384 if (max) {
2385 if (dir == 0 && self->max_horz && self->max_vert) return;
2386 if (dir == 1 && self->max_horz) return;
2387 if (dir == 2 && self->max_vert) return;
2388 } else {
2389 if (dir == 0 && !self->max_horz && !self->max_vert) return;
2390 if (dir == 1 && !self->max_horz) return;
2391 if (dir == 2 && !self->max_vert) return;
2392 }
2393
2394 /* we just tell it to configure in the same place and client_configure
2395 worries about filling the screen with the window */
2396 x = self->area.x;
2397 y = self->area.y;
2398 w = self->area.width;
2399 h = self->area.height;
2400
2401 if (max) {
2402 if (savearea) {
2403 if ((dir == 0 || dir == 1) && !self->max_horz) { /* horz */
2404 RECT_SET(self->pre_max_area,
2405 self->area.x, self->pre_max_area.y,
2406 self->area.width, self->pre_max_area.height);
2407 }
2408 if ((dir == 0 || dir == 2) && !self->max_vert) { /* vert */
2409 RECT_SET(self->pre_max_area,
2410 self->pre_max_area.x, self->area.y,
2411 self->pre_max_area.width, self->area.height);
2412 }
2413 }
2414 } else {
2415 Rect *a;
2416
2417 a = screen_area_monitor(self->desktop, 0);
2418 if ((dir == 0 || dir == 1) && self->max_horz) { /* horz */
2419 if (self->pre_max_area.width > 0) {
2420 x = self->pre_max_area.x;
2421 w = self->pre_max_area.width;
2422
2423 RECT_SET(self->pre_max_area, 0, self->pre_max_area.y,
2424 0, self->pre_max_area.height);
2425 } else {
2426 /* pick some fallbacks... */
2427 x = a->x + a->width / 4;
2428 w = a->width / 2;
2429 }
2430 }
2431 if ((dir == 0 || dir == 2) && self->max_vert) { /* vert */
2432 if (self->pre_max_area.height > 0) {
2433 y = self->pre_max_area.y;
2434 h = self->pre_max_area.height;
2435
2436 RECT_SET(self->pre_max_area, self->pre_max_area.x, 0,
2437 self->pre_max_area.width, 0);
2438 } else {
2439 /* pick some fallbacks... */
2440 y = a->y + a->height / 4;
2441 h = a->height / 2;
2442 }
2443 }
2444 }
2445
2446 if (dir == 0 || dir == 1) /* horz */
2447 self->max_horz = max;
2448 if (dir == 0 || dir == 2) /* vert */
2449 self->max_vert = max;
2450
2451 client_change_state(self); /* change the state hints on the client */
2452
2453 client_setup_decor_and_functions(self);
2454
2455 client_move_resize(self, x, y, w, h);
2456 }
2457
2458 void client_shade(ObClient *self, gboolean shade)
2459 {
2460 if ((!(self->functions & OB_CLIENT_FUNC_SHADE) &&
2461 shade) || /* can't shade */
2462 self->shaded == shade) return; /* already done */
2463
2464 /* when we're iconic, don't change the wmstate */
2465 if (!self->iconic) {
2466 glong old;
2467
2468 old = self->wmstate;
2469 self->wmstate = shade ? IconicState : NormalState;
2470 if (old != self->wmstate)
2471 PROP_MSG(self->window, kde_wm_change_state,
2472 self->wmstate, 1, 0, 0);
2473 }
2474
2475 self->shaded = shade;
2476 client_change_state(self);
2477 /* resize the frame to just the titlebar */
2478 frame_adjust_area(self->frame, FALSE, FALSE, FALSE);
2479 }
2480
2481 void client_close(ObClient *self)
2482 {
2483 XEvent ce;
2484
2485 if (!(self->functions & OB_CLIENT_FUNC_CLOSE)) return;
2486
2487 /* in the case that the client provides no means to requesting that it
2488 close, we just kill it */
2489 if (!self->delete_window)
2490 client_kill(self);
2491
2492 /*
2493 XXX: itd be cool to do timeouts and shit here for killing the client's
2494 process off
2495 like... if the window is around after 5 seconds, then the close button
2496 turns a nice red, and if this function is called again, the client is
2497 explicitly killed.
2498 */
2499
2500 ce.xclient.type = ClientMessage;
2501 ce.xclient.message_type = prop_atoms.wm_protocols;
2502 ce.xclient.display = ob_display;
2503 ce.xclient.window = self->window;
2504 ce.xclient.format = 32;
2505 ce.xclient.data.l[0] = prop_atoms.wm_delete_window;
2506 ce.xclient.data.l[1] = event_lasttime;
2507 ce.xclient.data.l[2] = 0l;
2508 ce.xclient.data.l[3] = 0l;
2509 ce.xclient.data.l[4] = 0l;
2510 XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
2511 }
2512
2513 void client_kill(ObClient *self)
2514 {
2515 XKillClient(ob_display, self->window);
2516 }
2517
2518 void client_set_desktop_recursive(ObClient *self,
2519 guint target, gboolean donthide)
2520 {
2521 guint old;
2522 GSList *it;
2523
2524 if (target != self->desktop) {
2525
2526 ob_debug("Setting desktop %u\n", target+1);
2527
2528 g_assert(target < screen_num_desktops || target == DESKTOP_ALL);
2529
2530 /* remove from the old desktop(s) */
2531 focus_order_remove(self);
2532
2533 old = self->desktop;
2534 self->desktop = target;
2535 PROP_SET32(self->window, net_wm_desktop, cardinal, target);
2536 /* the frame can display the current desktop state */
2537 frame_adjust_state(self->frame);
2538 /* 'move' the window to the new desktop */
2539 if (!donthide)
2540 client_showhide(self);
2541 /* raise if it was not already on the desktop */
2542 if (old != DESKTOP_ALL)
2543 client_raise(self);
2544 screen_update_areas();
2545
2546 /* add to the new desktop(s) */
2547 if (config_focus_new)
2548 focus_order_to_top(self);
2549 else
2550 focus_order_to_bottom(self);
2551 }
2552
2553 /* move all transients */
2554 for (it = self->transients; it; it = g_slist_next(it))
2555 if (it->data != self) client_set_desktop_recursive(it->data,
2556 target, donthide);
2557 }
2558
2559 void client_set_desktop(ObClient *self, guint target, gboolean donthide)
2560 {
2561 client_set_desktop_recursive(client_search_top_transient(self),
2562 target, donthide);
2563 }
2564
2565 ObClient *client_search_modal_child(ObClient *self)
2566 {
2567 GSList *it;
2568 ObClient *ret;
2569
2570 for (it = self->transients; it; it = g_slist_next(it)) {
2571 ObClient *c = it->data;
2572 if ((ret = client_search_modal_child(c))) return ret;
2573 if (c->modal) return c;
2574 }
2575 return NULL;
2576 }
2577
2578 gboolean client_validate(ObClient *self)
2579 {
2580 XEvent e;
2581
2582 XSync(ob_display, FALSE); /* get all events on the server */
2583
2584 if (XCheckTypedWindowEvent(ob_display, self->window, DestroyNotify, &e) ||
2585 XCheckTypedWindowEvent(ob_display, self->window, UnmapNotify, &e)) {
2586 XPutBackEvent(ob_display, &e);
2587 return FALSE;
2588 }
2589
2590 return TRUE;
2591 }
2592
2593 void client_set_wm_state(ObClient *self, glong state)
2594 {
2595 if (state == self->wmstate) return; /* no change */
2596
2597 switch (state) {
2598 case IconicState:
2599 client_iconify(self, TRUE, TRUE);
2600 break;
2601 case NormalState:
2602 client_iconify(self, FALSE, TRUE);
2603 break;
2604 }
2605 }
2606
2607 void client_set_state(ObClient *self, Atom action, glong data1, glong data2)
2608 {
2609 gboolean shaded = self->shaded;
2610 gboolean fullscreen = self->fullscreen;
2611 gboolean undecorated = self->undecorated;
2612 gboolean max_horz = self->max_horz;
2613 gboolean max_vert = self->max_vert;
2614 gboolean modal = self->modal;
2615 gboolean iconic = self->iconic;
2616 gint i;
2617
2618 if (!(action == prop_atoms.net_wm_state_add ||
2619 action == prop_atoms.net_wm_state_remove ||
2620 action == prop_atoms.net_wm_state_toggle))
2621 /* an invalid action was passed to the client message, ignore it */
2622 return;
2623
2624 for (i = 0; i < 2; ++i) {
2625 Atom state = i == 0 ? data1 : data2;
2626
2627 if (!state) continue;
2628
2629 /* if toggling, then pick whether we're adding or removing */
2630 if (action == prop_atoms.net_wm_state_toggle) {
2631 if (state == prop_atoms.net_wm_state_modal)
2632 action = modal ? prop_atoms.net_wm_state_remove :
2633 prop_atoms.net_wm_state_add;
2634 else if (state == prop_atoms.net_wm_state_maximized_vert)
2635 action = self->max_vert ? prop_atoms.net_wm_state_remove :
2636 prop_atoms.net_wm_state_add;
2637 else if (state == prop_atoms.net_wm_state_maximized_horz)
2638 action = self->max_horz ? prop_atoms.net_wm_state_remove :
2639 prop_atoms.net_wm_state_add;
2640 else if (state == prop_atoms.net_wm_state_shaded)
2641 action = shaded ? prop_atoms.net_wm_state_remove :
2642 prop_atoms.net_wm_state_add;
2643 else if (state == prop_atoms.net_wm_state_skip_taskbar)
2644 action = self->skip_taskbar ?
2645 prop_atoms.net_wm_state_remove :
2646 prop_atoms.net_wm_state_add;
2647 else if (state == prop_atoms.net_wm_state_skip_pager)
2648 action = self->skip_pager ?
2649 prop_atoms.net_wm_state_remove :
2650 prop_atoms.net_wm_state_add;
2651 else if (state == prop_atoms.net_wm_state_hidden)
2652 action = self->iconic ?
2653 prop_atoms.net_wm_state_remove :
2654 prop_atoms.net_wm_state_add;
2655 else if (state == prop_atoms.net_wm_state_fullscreen)
2656 action = fullscreen ?
2657 prop_atoms.net_wm_state_remove :
2658 prop_atoms.net_wm_state_add;
2659 else if (state == prop_atoms.net_wm_state_above)
2660 action = self->above ? prop_atoms.net_wm_state_remove :
2661 prop_atoms.net_wm_state_add;
2662 else if (state == prop_atoms.net_wm_state_below)
2663 action = self->below ? prop_atoms.net_wm_state_remove :
2664 prop_atoms.net_wm_state_add;
2665 else if (state == prop_atoms.ob_wm_state_undecorated)
2666 action = undecorated ? prop_atoms.net_wm_state_remove :
2667 prop_atoms.net_wm_state_add;
2668 }
2669
2670 if (action == prop_atoms.net_wm_state_add) {
2671 if (state == prop_atoms.net_wm_state_modal) {
2672 modal = TRUE;
2673 } else if (state == prop_atoms.net_wm_state_maximized_vert) {
2674 max_vert = TRUE;
2675 } else if (state == prop_atoms.net_wm_state_maximized_horz) {
2676 max_horz = TRUE;
2677 } else if (state == prop_atoms.net_wm_state_shaded) {
2678 shaded = TRUE;
2679 } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
2680 self->skip_taskbar = TRUE;
2681 } else if (state == prop_atoms.net_wm_state_skip_pager) {
2682 self->skip_pager = TRUE;
2683 } else if (state == prop_atoms.net_wm_state_hidden) {
2684 iconic = TRUE;
2685 } else if (state == prop_atoms.net_wm_state_fullscreen) {
2686 fullscreen = TRUE;
2687 } else if (state == prop_atoms.net_wm_state_above) {
2688 self->above = TRUE;
2689 self->below = FALSE;
2690 } else if (state == prop_atoms.net_wm_state_below) {
2691 self->above = FALSE;
2692 self->below = TRUE;
2693 } else if (state == prop_atoms.ob_wm_state_undecorated) {
2694 undecorated = TRUE;
2695 }
2696
2697 } else { /* action == prop_atoms.net_wm_state_remove */
2698 if (state == prop_atoms.net_wm_state_modal) {
2699 modal = FALSE;
2700 } else if (state == prop_atoms.net_wm_state_maximized_vert) {
2701 max_vert = FALSE;
2702 } else if (state == prop_atoms.net_wm_state_maximized_horz) {
2703 max_horz = FALSE;
2704 } else if (state == prop_atoms.net_wm_state_shaded) {
2705 shaded = FALSE;
2706 } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
2707 self->skip_taskbar = FALSE;
2708 } else if (state == prop_atoms.net_wm_state_skip_pager) {
2709 self->skip_pager = FALSE;
2710 } else if (state == prop_atoms.net_wm_state_hidden) {
2711 iconic = FALSE;
2712 } else if (state == prop_atoms.net_wm_state_fullscreen) {
2713 fullscreen = FALSE;
2714 } else if (state == prop_atoms.net_wm_state_above) {
2715 self->above = FALSE;
2716 } else if (state == prop_atoms.net_wm_state_below) {
2717 self->below = FALSE;
2718 } else if (state == prop_atoms.ob_wm_state_undecorated) {
2719 undecorated = FALSE;
2720 }
2721 }
2722 }
2723 if (max_horz != self->max_horz || max_vert != self->max_vert) {
2724 if (max_horz != self->max_horz && max_vert != self->max_vert) {
2725 /* toggling both */
2726 if (max_horz == max_vert) { /* both going the same way */
2727 client_maximize(self, max_horz, 0, TRUE);
2728 } else {
2729 client_maximize(self, max_horz, 1, TRUE);
2730 client_maximize(self, max_vert, 2, TRUE);
2731 }
2732 } else {
2733 /* toggling one */
2734 if (max_horz != self->max_horz)
2735 client_maximize(self, max_horz, 1, TRUE);
2736 else
2737 client_maximize(self, max_vert, 2, TRUE);
2738 }
2739 }
2740 /* change fullscreen state before shading, as it will affect if the window
2741 can shade or not */
2742 if (fullscreen != self->fullscreen)
2743 client_fullscreen(self, fullscreen, TRUE);
2744 if (shaded != self->shaded)
2745 client_shade(self, shaded);
2746 if (undecorated != self->undecorated)
2747 client_set_undecorated(self, undecorated);
2748 if (modal != self->modal) {
2749 self->modal = modal;
2750 /* when a window changes modality, then its stacking order with its
2751 transients needs to change */
2752 client_raise(self);
2753 }
2754 if (iconic != self->iconic)
2755 client_iconify(self, iconic, FALSE);
2756
2757 client_calc_layer(self);
2758 client_change_state(self); /* change the hint to reflect these changes */
2759 }
2760
2761 ObClient *client_focus_target(ObClient *self)
2762 {
2763 ObClient *child;
2764
2765 /* if we have a modal child, then focus it, not us */
2766 child = client_search_modal_child(client_search_top_transient(self));
2767 if (child) return child;
2768 return self;
2769 }
2770
2771 gboolean client_can_focus(ObClient *self)
2772 {
2773 XEvent ev;
2774
2775 /* choose the correct target */
2776 self = client_focus_target(self);
2777
2778 if (!self->frame->visible)
2779 return FALSE;
2780
2781 if (!(self->can_focus || self->focus_notify))
2782 return FALSE;
2783
2784 /* do a check to see if the window has already been unmapped or destroyed
2785 do this intelligently while watching out for unmaps we've generated
2786 (ignore_unmaps > 0) */
2787 if (XCheckTypedWindowEvent(ob_display, self->window,
2788 DestroyNotify, &ev)) {
2789 XPutBackEvent(ob_display, &ev);
2790 return FALSE;
2791 }
2792 while (XCheckTypedWindowEvent(ob_display, self->window,
2793 UnmapNotify, &ev)) {
2794 if (self->ignore_unmaps) {
2795 self->ignore_unmaps--;
2796 } else {
2797 XPutBackEvent(ob_display, &ev);
2798 return FALSE;
2799 }
2800 }
2801
2802 return TRUE;
2803 }
2804
2805 gboolean client_focus(ObClient *self)
2806 {
2807 /* choose the correct target */
2808 self = client_focus_target(self);
2809
2810 if (!client_can_focus(self)) {
2811 if (!self->frame->visible) {
2812 /* update the focus lists */
2813 focus_order_to_top(self);
2814 }
2815 return FALSE;
2816 }
2817
2818 if (self->can_focus) {
2819 /* RevertToPointerRoot causes much more headache than RevertToNone, so
2820 I choose to use it always, hopefully to find errors quicker, if any
2821 are left. (I hate X. I hate focus events.)
2822
2823 Update: Changing this to RevertToNone fixed a bug with mozilla (bug
2824 #799. So now it is RevertToNone again.
2825 */
2826 XSetInputFocus(ob_display, self->window, RevertToNone,
2827 event_lasttime);
2828 }
2829
2830 if (self->focus_notify) {
2831 XEvent ce;
2832 ce.xclient.type = ClientMessage;
2833 ce.xclient.message_type = prop_atoms.wm_protocols;
2834 ce.xclient.display = ob_display;
2835 ce.xclient.window = self->window;
2836 ce.xclient.format = 32;
2837 ce.xclient.data.l[0] = prop_atoms.wm_take_focus;
2838 ce.xclient.data.l[1] = event_lasttime;
2839 ce.xclient.data.l[2] = 0l;
2840 ce.xclient.data.l[3] = 0l;
2841 ce.xclient.data.l[4] = 0l;
2842 XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
2843 }
2844
2845 #ifdef DEBUG_FOCUS
2846 ob_debug("%sively focusing %lx at %d\n",
2847 (self->can_focus ? "act" : "pass"),
2848 self->window, (gint) event_lasttime);
2849 #endif
2850
2851 /* Cause the FocusIn to come back to us. Important for desktop switches,
2852 since otherwise we'll have no FocusIn on the queue and send it off to
2853 the focus_backup. */
2854 XSync(ob_display, FALSE);
2855 return TRUE;
2856 }
2857
2858 void client_unfocus(ObClient *self)
2859 {
2860 if (focus_client == self) {
2861 #ifdef DEBUG_FOCUS
2862 ob_debug("client_unfocus for %lx\n", self->window);
2863 #endif
2864 focus_fallback(OB_FOCUS_FALLBACK_UNFOCUSING);
2865 }
2866 }
2867
2868 void client_activate(ObClient *self, gboolean here)
2869 {
2870 /* This check is for the client_list_menu trying to activate
2871 * a closed client. */
2872 if (!g_list_find(client_list, self)) return;
2873 if (client_normal(self) && screen_showing_desktop)
2874 screen_show_desktop(FALSE);
2875 if (self->iconic)
2876 client_iconify(self, FALSE, here);
2877 if (self->desktop != DESKTOP_ALL &&
2878 self->desktop != screen_desktop) {
2879 if (here)
2880 client_set_desktop(self, screen_desktop, FALSE);
2881 else
2882 screen_set_desktop(self->desktop);
2883 } else if (!self->frame->visible)
2884 /* if its not visible for other reasons, then don't mess
2885 with it */
2886 return;
2887 if (self->shaded)
2888 client_shade(self, FALSE);
2889
2890 client_focus(self);
2891
2892 /* we do this an action here. this is rather important. this is because
2893 we want the results from the focus change to take place BEFORE we go
2894 about raising the window. when a fullscreen window loses focus, we need
2895 this or else the raise wont be able to raise above the to-lose-focus
2896 fullscreen window. */
2897 client_raise(self);
2898 }
2899
2900 void client_raise(ObClient *self)
2901 {
2902 action_run_string("Raise", self);
2903 }
2904
2905 void client_lower(ObClient *self)
2906 {
2907 action_run_string("Lower", self);
2908 }
2909
2910 gboolean client_focused(ObClient *self)
2911 {
2912 return self == focus_client;
2913 }
2914
2915 static ObClientIcon* client_icon_recursive(ObClient *self, gint w, gint h)
2916 {
2917 guint i;
2918 /* si is the smallest image >= req */
2919 /* li is the largest image < req */
2920 gulong size, smallest = 0xffffffff, largest = 0, si = 0, li = 0;
2921
2922 if (!self->nicons) {
2923 ObClientIcon *parent = NULL;
2924
2925 if (self->transient_for) {
2926 if (self->transient_for != OB_TRAN_GROUP)
2927 parent = client_icon_recursive(self->transient_for, w, h);
2928 else {
2929 GSList *it;
2930 for (it = self->group->members; it; it = g_slist_next(it)) {
2931 ObClient *c = it->data;
2932 if (c != self && !c->transient_for) {
2933 if ((parent = client_icon_recursive(c, w, h)))
2934 break;
2935 }
2936 }
2937 }
2938 }
2939
2940 return parent;
2941 }
2942
2943 for (i = 0; i < self->nicons; ++i) {
2944 size = self->icons[i].width * self->icons[i].height;
2945 if (size < smallest && size >= (unsigned)(w * h)) {
2946 smallest = size;
2947 si = i;
2948 }
2949 if (size > largest && size <= (unsigned)(w * h)) {
2950 largest = size;
2951 li = i;
2952 }
2953 }
2954 if (largest == 0) /* didnt find one smaller than the requested size */
2955 return &self->icons[si];
2956 return &self->icons[li];
2957 }
2958
2959 const ObClientIcon* client_icon(ObClient *self, gint w, gint h)
2960 {
2961 ObClientIcon *ret;
2962 static ObClientIcon deficon;
2963
2964 if (!(ret = client_icon_recursive(self, w, h))) {
2965 deficon.width = deficon.height = 48;
2966 deficon.data = ob_rr_theme->def_win_icon;
2967 ret = &deficon;
2968 }
2969 return ret;
2970 }
2971
2972 /* this be mostly ripped from fvwm */
2973 ObClient *client_find_directional(ObClient *c, ObDirection dir)
2974 {
2975 gint my_cx, my_cy, his_cx, his_cy;
2976 gint offset = 0;
2977 gint distance = 0;
2978 gint score, best_score;
2979 ObClient *best_client, *cur;
2980 GList *it;
2981
2982 if(!client_list)
2983 return NULL;
2984
2985 /* first, find the centre coords of the currently focused window */
2986 my_cx = c->frame->area.x + c->frame->area.width / 2;
2987 my_cy = c->frame->area.y + c->frame->area.height / 2;
2988
2989 best_score = -1;
2990 best_client = NULL;
2991
2992 for(it = g_list_first(client_list); it; it = g_list_next(it)) {
2993 cur = it->data;
2994
2995 /* the currently selected window isn't interesting */
2996 if(cur == c)
2997 continue;
2998 if (!client_normal(cur))
2999 continue;
3000 /* using c->desktop instead of screen_desktop doesn't work if the
3001 * current window was omnipresent, hope this doesn't have any other
3002 * side effects */
3003 if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3004 continue;
3005 if(cur->iconic)
3006 continue;
3007 if(client_focus_target(cur) == cur &&
3008 !(cur->can_focus || cur->focus_notify))
3009 continue;
3010
3011 /* find the centre coords of this window, from the
3012 * currently focused window's point of view */
3013 his_cx = (cur->frame->area.x - my_cx)
3014 + cur->frame->area.width / 2;
3015 his_cy = (cur->frame->area.y - my_cy)
3016 + cur->frame->area.height / 2;
3017
3018 if(dir == OB_DIRECTION_NORTHEAST || dir == OB_DIRECTION_SOUTHEAST ||
3019 dir == OB_DIRECTION_SOUTHWEST || dir == OB_DIRECTION_NORTHWEST) {
3020 gint tx;
3021 /* Rotate the diagonals 45 degrees counterclockwise.
3022 * To do this, multiply the matrix /+h +h\ with the
3023 * vector (x y). \-h +h/
3024 * h = sqrt(0.5). We can set h := 1 since absolute
3025 * distance doesn't matter here. */
3026 tx = his_cx + his_cy;
3027 his_cy = -his_cx + his_cy;
3028 his_cx = tx;
3029 }
3030
3031 switch(dir) {
3032 case OB_DIRECTION_NORTH:
3033 case OB_DIRECTION_SOUTH:
3034 case OB_DIRECTION_NORTHEAST:
3035 case OB_DIRECTION_SOUTHWEST:
3036 offset = (his_cx < 0) ? -his_cx : his_cx;
3037 distance = ((dir == OB_DIRECTION_NORTH ||
3038 dir == OB_DIRECTION_NORTHEAST) ?
3039 -his_cy : his_cy);
3040 break;
3041 case OB_DIRECTION_EAST:
3042 case OB_DIRECTION_WEST:
3043 case OB_DIRECTION_SOUTHEAST:
3044 case OB_DIRECTION_NORTHWEST:
3045 offset = (his_cy < 0) ? -his_cy : his_cy;
3046 distance = ((dir == OB_DIRECTION_WEST ||
3047 dir == OB_DIRECTION_NORTHWEST) ?
3048 -his_cx : his_cx);
3049 break;
3050 }
3051
3052 /* the target must be in the requested direction */
3053 if(distance <= 0)
3054 continue;
3055
3056 /* Calculate score for this window. The smaller the better. */
3057 score = distance + offset;
3058
3059 /* windows more than 45 degrees off the direction are
3060 * heavily penalized and will only be chosen if nothing
3061 * else within a million pixels */
3062 if(offset > distance)
3063 score += 1000000;
3064
3065 if(best_score == -1 || score < best_score)
3066 best_client = cur,
3067 best_score = score;
3068 }
3069
3070 return best_client;
3071 }
3072
3073 void client_set_layer(ObClient *self, gint layer)
3074 {
3075 if (layer < 0) {
3076 self->below = TRUE;
3077 self->above = FALSE;
3078 } else if (layer == 0) {
3079 self->below = self->above = FALSE;
3080 } else {
3081 self->below = FALSE;
3082 self->above = TRUE;
3083 }
3084 client_calc_layer(self);
3085 client_change_state(self); /* reflect this in the state hints */
3086 }
3087
3088 void client_set_undecorated(ObClient *self, gboolean undecorated)
3089 {
3090 if (self->undecorated != undecorated) {
3091 self->undecorated = undecorated;
3092 client_setup_decor_and_functions(self);
3093 /* Make sure the client knows it might have moved. Maybe there is a
3094 * better way of doing this so only one client_configure is sent, but
3095 * since 125 of these are sent per second when moving the window (with
3096 * user = FALSE) i doubt it matters much.
3097 */
3098 client_configure(self, OB_CORNER_TOPLEFT, self->area.x, self->area.y,
3099 self->area.width, self->area.height, TRUE, TRUE);
3100 client_change_state(self); /* reflect this in the state hints */
3101 }
3102 }
3103
3104 /* Determines which physical monitor a client is on by calculating the
3105 area of the part of the client on each monitor. The number of the
3106 monitor containing the greatest area of the client is returned.*/
3107 guint client_monitor(ObClient *self)
3108 {
3109 guint i;
3110 guint most = 0;
3111 guint mostv = 0;
3112
3113 for (i = 0; i < screen_num_monitors; ++i) {
3114 Rect *area = screen_physical_area_monitor(i);
3115 if (RECT_INTERSECTS_RECT(*area, self->frame->area)) {
3116 Rect r;
3117 guint v;
3118
3119 RECT_SET_INTERSECTION(r, *area, self->frame->area);
3120 v = r.width * r.height;
3121
3122 if (v > mostv) {
3123 mostv = v;
3124 most = i;
3125 }
3126 }
3127 }
3128 return most;
3129 }
3130
3131 ObClient *client_search_top_transient(ObClient *self)
3132 {
3133 /* move up the transient chain as far as possible */
3134 if (self->transient_for) {
3135 if (self->transient_for != OB_TRAN_GROUP) {
3136 return client_search_top_transient(self->transient_for);
3137 } else {
3138 GSList *it;
3139
3140 g_assert(self->group);
3141
3142 for (it = self->group->members; it; it = g_slist_next(it)) {
3143 ObClient *c = it->data;
3144
3145 /* checking transient_for prevents infinate loops! */
3146 if (c != self && !c->transient_for)
3147 break;
3148 }
3149 if (it)
3150 return it->data;
3151 }
3152 }
3153
3154 return self;
3155 }
3156
3157 ObClient *client_search_focus_parent(ObClient *self)
3158 {
3159 if (self->transient_for) {
3160 if (self->transient_for != OB_TRAN_GROUP) {
3161 if (client_focused(self->transient_for))
3162 return self->transient_for;
3163 } else {
3164 GSList *it;
3165
3166 for (it = self->group->members; it; it = g_slist_next(it)) {
3167 ObClient *c = it->data;
3168
3169 /* checking transient_for prevents infinate loops! */
3170 if (c != self && !c->transient_for)
3171 if (client_focused(c))
3172 return c;
3173 }
3174 }
3175 }
3176
3177 return NULL;
3178 }
3179
3180 ObClient *client_search_parent(ObClient *self, ObClient *search)
3181 {
3182 if (self->transient_for) {
3183 if (self->transient_for != OB_TRAN_GROUP) {
3184 if (self->transient_for == search)
3185 return search;
3186 } else {
3187 GSList *it;
3188
3189 for (it = self->group->members; it; it = g_slist_next(it)) {
3190 ObClient *c = it->data;
3191
3192 /* checking transient_for prevents infinate loops! */
3193 if (c != self && !c->transient_for)
3194 if (c == search)
3195 return search;
3196 }
3197 }
3198 }
3199
3200 return NULL;
3201 }
3202
3203 ObClient *client_search_transient(ObClient *self, ObClient *search)
3204 {
3205 GSList *sit;
3206
3207 for (sit = self->transients; sit; sit = g_slist_next(sit)) {
3208 if (sit->data == search)
3209 return search;
3210 if (client_search_transient(sit->data, search))
3211 return search;
3212 }
3213 return NULL;
3214 }
3215
3216 void client_update_sm_client_id(ObClient *self)
3217 {
3218 g_free(self->sm_client_id);
3219 self->sm_client_id = NULL;
3220
3221 if (!PROP_GETS(self->window, sm_client_id, locale, &self->sm_client_id) &&
3222 self->group)
3223 PROP_GETS(self->group->leader, sm_client_id, locale,
3224 &self->sm_client_id);
3225 }
3226
3227 /* finds the nearest edge in the given direction from the current client
3228 * note to self: the edge is the -frame- edge (the actual one), not the
3229 * client edge.
3230 */
3231 gint client_directional_edge_search(ObClient *c, ObDirection dir)
3232 {
3233 gint dest, monitor_dest;
3234 gint my_edge_start, my_edge_end, my_offset;
3235 GList *it;
3236 Rect *a, *monitor;
3237
3238 if(!client_list)
3239 return -1;
3240
3241 a = screen_area(c->desktop);
3242 monitor = screen_area_monitor(c->desktop, client_monitor(c));
3243
3244 switch(dir) {
3245 case OB_DIRECTION_NORTH:
3246 my_edge_start = c->frame->area.x;
3247 my_edge_end = c->frame->area.x + c->frame->area.width;
3248 my_offset = c->frame->area.y;
3249
3250 /* default: top of screen */
3251 dest = a->y;
3252 monitor_dest = monitor->y;
3253 /* if the monitor edge comes before the screen edge, */
3254 /* use that as the destination instead. (For xinerama) */
3255 if (monitor_dest != dest && my_offset > monitor_dest)
3256 dest = monitor_dest;
3257
3258 for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3259 gint his_edge_start, his_edge_end, his_offset;
3260 ObClient *cur = it->data;
3261
3262 if(cur == c)
3263 continue;
3264 if(!client_normal(cur))
3265 continue;
3266 if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3267 continue;
3268 if(cur->iconic)
3269 continue;
3270 if(cur->layer < c->layer && !config_resist_layers_below)
3271 continue;
3272
3273 his_edge_start = cur->frame->area.x;
3274 his_edge_end = cur->frame->area.x + cur->frame->area.width;
3275 his_offset = cur->frame->area.y + cur->frame->area.height;
3276
3277 if(his_offset + 1 > my_offset)
3278 continue;
3279
3280 if(his_offset < dest)
3281 continue;
3282
3283 if(his_edge_start >= my_edge_start &&
3284 his_edge_start <= my_edge_end)
3285 dest = his_offset;
3286
3287 if(my_edge_start >= his_edge_start &&
3288 my_edge_start <= his_edge_end)
3289 dest = his_offset;
3290
3291 }
3292 break;
3293 case OB_DIRECTION_SOUTH:
3294 my_edge_start = c->frame->area.x;
3295 my_edge_end = c->frame->area.x + c->frame->area.width;
3296 my_offset = c->frame->area.y + c->frame->area.height;
3297
3298 /* default: bottom of screen */
3299 dest = a->y + a->height;
3300 monitor_dest = monitor->y + monitor->height;
3301 /* if the monitor edge comes before the screen edge, */
3302 /* use that as the destination instead. (For xinerama) */
3303 if (monitor_dest != dest && my_offset < monitor_dest)
3304 dest = monitor_dest;
3305
3306 for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3307 gint his_edge_start, his_edge_end, his_offset;
3308 ObClient *cur = it->data;
3309
3310 if(cur == c)
3311 continue;
3312 if(!client_normal(cur))
3313 continue;
3314 if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3315 continue;
3316 if(cur->iconic)
3317 continue;
3318 if(cur->layer < c->layer && !config_resist_layers_below)
3319 continue;
3320
3321 his_edge_start = cur->frame->area.x;
3322 his_edge_end = cur->frame->area.x + cur->frame->area.width;
3323 his_offset = cur->frame->area.y;
3324
3325
3326 if(his_offset - 1 < my_offset)
3327 continue;
3328
3329 if(his_offset > dest)
3330 continue;
3331
3332 if(his_edge_start >= my_edge_start &&
3333 his_edge_start <= my_edge_end)
3334 dest = his_offset;
3335
3336 if(my_edge_start >= his_edge_start &&
3337 my_edge_start <= his_edge_end)
3338 dest = his_offset;
3339
3340 }
3341 break;
3342 case OB_DIRECTION_WEST:
3343 my_edge_start = c->frame->area.y;
3344 my_edge_end = c->frame->area.y + c->frame->area.height;
3345 my_offset = c->frame->area.x;
3346
3347 /* default: leftmost egde of screen */
3348 dest = a->x;
3349 monitor_dest = monitor->x;
3350 /* if the monitor edge comes before the screen edge, */
3351 /* use that as the destination instead. (For xinerama) */
3352 if (monitor_dest != dest && my_offset > monitor_dest)
3353 dest = monitor_dest;
3354
3355 for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3356 gint his_edge_start, his_edge_end, his_offset;
3357 ObClient *cur = it->data;
3358
3359 if(cur == c)
3360 continue;
3361 if(!client_normal(cur))
3362 continue;
3363 if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3364 continue;
3365 if(cur->iconic)
3366 continue;
3367 if(cur->layer < c->layer && !config_resist_layers_below)
3368 continue;
3369
3370 his_edge_start = cur->frame->area.y;
3371 his_edge_end = cur->frame->area.y + cur->frame->area.height;
3372 his_offset = cur->frame->area.x + cur->frame->area.width;
3373
3374 if(his_offset + 1 > my_offset)
3375 continue;
3376
3377 if(his_offset < dest)
3378 continue;
3379
3380 if(his_edge_start >= my_edge_start &&
3381 his_edge_start <= my_edge_end)
3382 dest = his_offset;
3383
3384 if(my_edge_start >= his_edge_start &&
3385 my_edge_start <= his_edge_end)
3386 dest = his_offset;
3387
3388
3389 }
3390 break;
3391 case OB_DIRECTION_EAST:
3392 my_edge_start = c->frame->area.y;
3393 my_edge_end = c->frame->area.y + c->frame->area.height;
3394 my_offset = c->frame->area.x + c->frame->area.width;
3395
3396 /* default: rightmost edge of screen */
3397 dest = a->x + a->width;
3398 monitor_dest = monitor->x + monitor->width;
3399 /* if the monitor edge comes before the screen edge, */
3400 /* use that as the destination instead. (For xinerama) */
3401 if (monitor_dest != dest && my_offset < monitor_dest)
3402 dest = monitor_dest;
3403
3404 for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3405 gint his_edge_start, his_edge_end, his_offset;
3406 ObClient *cur = it->data;
3407
3408 if(cur == c)
3409 continue;
3410 if(!client_normal(cur))
3411 continue;
3412 if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL)
3413 continue;
3414 if(cur->iconic)
3415 continue;
3416 if(cur->layer < c->layer && !config_resist_layers_below)
3417 continue;
3418
3419 his_edge_start = cur->frame->area.y;
3420 his_edge_end = cur->frame->area.y + cur->frame->area.height;
3421 his_offset = cur->frame->area.x;
3422
3423 if(his_offset - 1 < my_offset)
3424 continue;
3425
3426 if(his_offset > dest)
3427 continue;
3428
3429 if(his_edge_start >= my_edge_start &&
3430 his_edge_start <= my_edge_end)
3431 dest = his_offset;
3432
3433 if(my_edge_start >= his_edge_start &&
3434 my_edge_start <= his_edge_end)
3435 dest = his_offset;
3436
3437 }
3438 break;
3439 case OB_DIRECTION_NORTHEAST:
3440 case OB_DIRECTION_SOUTHEAST:
3441 case OB_DIRECTION_NORTHWEST:
3442 case OB_DIRECTION_SOUTHWEST:
3443 /* not implemented */
3444 default:
3445 g_assert_not_reached();
3446 dest = 0; /* suppress warning */
3447 }
3448 return dest;
3449 }
3450
3451 ObClient* client_under_pointer()
3452 {
3453 gint x, y;
3454 GList *it;
3455 ObClient *ret = NULL;
3456
3457 if (screen_pointer_pos(&x, &y)) {
3458 for (it = stacking_list; it; it = g_list_next(it)) {
3459 if (WINDOW_IS_CLIENT(it->data)) {
3460 ObClient *c = WINDOW_AS_CLIENT(it->data);
3461 if (c->frame->visible &&
3462 RECT_CONTAINS(c->frame->area, x, y)) {
3463 ret = c;
3464 break;
3465 }
3466 }
3467 }
3468 }
3469 return ret;
3470 }
3471
3472 gboolean client_has_group_siblings(ObClient *self)
3473 {
3474 return self->group && self->group->members->next;
3475 }
This page took 0.19116 seconds and 4 git commands to generate.