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