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