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