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