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