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