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