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