1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
3 client.c for the Openbox window manager
4 Copyright (c) 2006 Mikael Magnusson
5 Copyright (c) 2003-2007 Dana Jansens
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.
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.
17 See the COPYING file for a copy of the GNU General Public License.
22 #include "startupnotify.h"
26 #include "moveresize.h"
29 #include "extensions.h"
39 #include "menuframe.h"
42 #include "render/render.h"
49 #include <X11/Xutil.h>
51 /*! The event mask to grab on client windows */
52 #define CLIENT_EVENTMASK (PropertyChangeMask | StructureNotifyMask | \
55 #define CLIENT_NOPROPAGATEMASK (ButtonPressMask | ButtonReleaseMask | \
60 ObClientCallback func;
64 GList *client_list = NULL;
65 GHashTable *client_user_time_window_map;
67 static GSList *client_destructors = NULL;
69 static void client_get_all(ObClient *self);
70 static void client_toggle_border(ObClient *self, gboolean show);
71 static void client_get_startup_id(ObClient *self);
72 static void client_get_session_ids(ObClient *self);
73 static void client_get_area(ObClient *self);
74 static void client_get_desktop(ObClient *self);
75 static void client_get_state(ObClient *self);
76 static void client_get_layer(ObClient *self);
77 static void client_get_shaped(ObClient *self);
78 static void client_get_mwm_hints(ObClient *self);
79 static void client_get_gravity(ObClient *self);
80 static void client_get_colormap(ObClient *self);
81 static void client_get_transientness(ObClient *self);
82 static void client_change_allowed_actions(ObClient *self);
83 static void client_change_state(ObClient *self);
84 static void client_change_wm_state(ObClient *self);
85 static void client_apply_startup_state(ObClient *self, gint x, gint y);
86 static void client_restore_session_state(ObClient *self);
87 static gboolean client_restore_session_stacking(ObClient *self);
88 static ObAppSettings *client_get_settings_state(ObClient *self);
89 static void client_update_transient_tree(ObClient *self,
90 ObGroup *oldgroup, ObGroup *newgroup,
93 static void client_present(ObClient *self, gboolean here, gboolean raise);
94 static GSList *client_search_all_top_parents_internal(ObClient *self,
96 ObStackingLayer layer);
98 static guint window_hash(Window *w) { return *w; }
99 static gboolean window_comp(Window *w1, Window *w2) { return *w1 == *w2; }
101 void client_startup(gboolean reconfig)
103 if (reconfig) return;
105 client_user_time_window_map = g_hash_table_new((GHashFunc)window_hash,
106 (GEqualFunc)window_comp);
110 void client_shutdown(gboolean reconfig)
112 if (reconfig) return;
114 g_hash_table_destroy(client_user_time_window_map);
117 void client_add_destructor(ObClientCallback func, gpointer data)
119 ClientCallback *d = g_new(ClientCallback, 1);
122 client_destructors = g_slist_prepend(client_destructors, d);
125 void client_remove_destructor(ObClientCallback func)
129 for (it = client_destructors; it; it = g_slist_next(it)) {
130 ClientCallback *d = it->data;
131 if (d->func == func) {
133 client_destructors = g_slist_delete_link(client_destructors, it);
139 void client_set_list()
141 Window *windows, *win_it;
143 guint size = g_list_length(client_list);
145 /* create an array of the window ids */
147 windows = g_new(Window, size);
149 for (it = client_list; it; it = g_list_next(it), ++win_it)
150 *win_it = ((ObClient*)it->data)->window;
154 PROP_SETA32(RootWindow(ob_display, ob_screen),
155 net_client_list, window, (gulong*)windows, size);
164 void client_foreach_transient(ObClient *self, ObClientForeachFunc func, gpointer data)
168 for (it = self->transients; it; it = g_slist_next(it)) {
169 if (!func(it->data, data)) return;
170 client_foreach_transient(it->data, func, data);
174 void client_foreach_ancestor(ObClient *self, ObClientForeachFunc func, gpointer data)
176 if (self->transient_for) {
177 if (self->transient_for != OB_TRAN_GROUP) {
178 if (!func(self->transient_for, data)) return;
179 client_foreach_ancestor(self->transient_for, func, data);
183 for (it = self->group->members; it; it = g_slist_next(it))
184 if (it->data != self &&
185 !((ObClient*)it->data)->transient_for) {
186 if (!func(it->data, data)) return;
187 client_foreach_ancestor(it->data, func, data);
194 void client_manage_all()
199 XWindowAttributes attrib;
201 XQueryTree(ob_display, RootWindow(ob_display, ob_screen),
202 &w, &w, &children, &nchild);
204 /* remove all icon windows from the list */
205 for (i = 0; i < nchild; i++) {
206 if (children[i] == None) continue;
207 wmhints = XGetWMHints(ob_display, children[i]);
209 if ((wmhints->flags & IconWindowHint) &&
210 (wmhints->icon_window != children[i]))
211 for (j = 0; j < nchild; j++)
212 if (children[j] == wmhints->icon_window) {
220 for (i = 0; i < nchild; ++i) {
221 if (children[i] == None)
223 if (XGetWindowAttributes(ob_display, children[i], &attrib)) {
224 if (attrib.override_redirect) continue;
226 if (attrib.map_state != IsUnmapped)
227 client_manage(children[i]);
233 void client_manage(Window window)
237 XWindowAttributes attrib;
238 XSetWindowAttributes attrib_set;
240 gboolean activate = FALSE;
241 ObAppSettings *settings;
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))
251 XPutBackEvent(ob_display, &e);
253 ob_debug("Trying to manage unmapped window. Aborting that.\n");
255 return; /* don't manage it */
258 /* make sure it isn't an override-redirect window */
259 if (!XGetWindowAttributes(ob_display, window, &attrib) ||
260 attrib.override_redirect)
263 return; /* don't manage it */
266 /* is the window a docking app */
267 if ((wmhint = XGetWMHints(ob_display, window))) {
268 if ((wmhint->flags & StateHint) &&
269 wmhint->initial_state == WithdrawnState)
271 dock_add(window, wmhint);
279 ob_debug("Managing window: %lx\n", window);
281 /* choose the events we want to receive on the CLIENT window */
282 attrib_set.event_mask = CLIENT_EVENTMASK;
283 attrib_set.do_not_propagate_mask = CLIENT_NOPROPAGATEMASK;
284 XChangeWindowAttributes(ob_display, window,
285 CWEventMask|CWDontPropagate, &attrib_set);
288 /* create the ObClient struct, and populate it from the hints on the
290 self = g_new0(ObClient, 1);
291 self->obwin.type = Window_Client;
292 self->window = window;
294 /* non-zero defaults */
295 self->wmstate = WithdrawnState; /* make sure it gets updated first time */
297 self->desktop = screen_num_desktops; /* always an invalid value */
298 self->user_time = focus_client ? focus_client->user_time : CurrentTime;
300 client_get_all(self);
301 /* per-app settings override stuff, and return the settings for other
303 settings = client_get_settings_state(self);
304 /* the session should get the last say */
305 client_restore_session_state(self);
307 client_calc_layer(self);
310 Time t = sn_app_started(self->startup_id, self->class);
311 if (t) self->user_time = t;
314 /* update the focus lists, do this before the call to change_state or
315 it can end up in the list twice! */
316 focus_order_add_new(self);
318 /* remove the client's border (and adjust re gravity) */
319 client_toggle_border(self, FALSE);
321 /* specify that if we exit, the window should not be destroyed and should
322 be reparented back to root automatically */
323 XChangeSaveSet(ob_display, window, SetModeInsert);
325 /* create the decoration frame for the client window */
326 self->frame = frame_new(self);
328 frame_grab_client(self->frame, self);
330 /* do this after we have a frame.. it uses the frame to help determine the
331 WM_STATE to apply. */
332 client_change_state(self);
336 stacking_add_nonintrusive(CLIENT_AS_WINDOW(self));
338 /* focus the new window? */
339 if (ob_state() != OB_STATE_STARTING &&
340 (!self->session || self->session->focused) &&
342 /* this means focus=true for window is same as config_focus_new=true */
343 ((config_focus_new || (settings && settings->focus == 1)) ||
344 client_search_focus_parent(self)) &&
345 /* this checks for focus=false for the window */
346 (!settings || settings->focus != 0) &&
347 /* note the check against Type_Normal/Dialog, not client_normal(self),
348 which would also include other types. in this case we want more
349 strict rules for focus */
350 (self->type == OB_CLIENT_TYPE_NORMAL ||
351 self->type == OB_CLIENT_TYPE_DIALOG))
356 /* get the current position */
360 /* figure out placement for the window */
361 if (ob_state() == OB_STATE_RUNNING) {
364 transient = place_client(self, &newx, &newy, settings);
366 /* make sure the window is visible. */
367 client_find_onscreen(self, &newx, &newy,
370 /* non-normal clients has less rules, and
371 windows that are being restored from a
372 session do also. we can assume you want
373 it back where you saved it. Clients saying
374 they placed themselves are subjected to
375 harder rules, ones that are placed by
376 place.c or by the user are allowed partially
377 off-screen and on xinerama divides (ie,
378 it is up to the placement routines to avoid
379 the xinerama divides) */
381 (((self->positioned & PPosition) &&
382 !(self->positioned & USPosition)) &&
383 client_normal(self) &&
387 /* do this after the window is placed, so the premax/prefullscreen numbers
389 also, this moves the window to the position where it has been placed
391 ob_debug("placing window 0x%x at %d, %d with size %d x %d\n",
392 self->window, newx, newy, self->area.width, self->area.height);
394 ob_debug("session requested %d %d\n",
395 self->session->x, self->session->y);
397 client_apply_startup_state(self, newx, newy);
399 mouse_grab_for_client(self, TRUE);
402 guint32 last_time = focus_client ?
403 focus_client->user_time : CurrentTime;
405 /* This is focus stealing prevention */
406 ob_debug_type(OB_DEBUG_FOCUS,
407 "Want to focus new window 0x%x with time %u "
409 self->window, self->user_time, last_time);
411 /* if it's on another desktop */
412 if (!(self->desktop == screen_desktop || self->desktop == DESKTOP_ALL)
413 && /* the timestamp is from before you changed desktops */
414 self->user_time && screen_desktop_user_time &&
415 !event_time_after(self->user_time, screen_desktop_user_time))
418 ob_debug_type(OB_DEBUG_FOCUS,
419 "Not focusing the window because its on another "
422 /* If something is focused, and it's not our parent... */
423 else if (focus_client && client_search_focus_parent(self) == NULL)
425 /* If time stamp is old, don't steal focus */
426 if (self->user_time && last_time &&
427 !event_time_after(self->user_time, last_time))
430 ob_debug_type(OB_DEBUG_FOCUS,
431 "Not focusing the window because the time is "
434 /* Don't steal focus from globally active clients.
435 I stole this idea from KWin. It seems nice.
437 if (!(focus_client->can_focus || focus_client->focus_notify)) {
439 ob_debug_type(OB_DEBUG_FOCUS,
440 "Not focusing the window because a globally "
441 "active client has focus\n");
446 ob_debug_type(OB_DEBUG_FOCUS,
447 "Focus stealing prevention activated for %s with "
448 "time %u (last time %u)\n",
449 self->title, self->user_time, last_time);
450 /* if the client isn't focused, then hilite it so the user
452 client_hilite(self, TRUE);
456 /* This may look rather odd. Well it's because new windows are added
457 to the stacking order non-intrusively. If we're not going to focus
458 the new window or hilite it, then we raise it to the top. This will
459 take affect for things that don't get focused like splash screens.
460 Also if you don't have focus_new enabled, then it's going to get
461 raised to the top. Legacy begets legacy I guess?
463 if (!client_restore_session_stacking(self))
467 /* this has to happen before we try focus the window, but we want it to
468 happen after the client's stacking has been determined or it looks bad
472 /* use client_focus instead of client_activate cuz client_activate does
473 stuff like switch desktops etc and I'm not interested in all that when
474 a window maps since its not based on an action from the user like
475 clicking a window to activate it. so keep the new window out of the way
478 gboolean stacked = client_restore_session_stacking(self);
479 client_present(self, FALSE, !stacked);
482 /* add to client list/map */
483 client_list = g_list_append(client_list, self);
484 g_hash_table_insert(window_map, &self->window, self);
486 /* this has to happen after we're in the client_list */
487 if (STRUT_EXISTS(self->strut))
488 screen_update_areas();
490 /* update the list hints */
493 ob_debug("Managed window 0x%lx (%s)\n", window, self->class);
496 void client_unmanage_all()
498 while (client_list != NULL)
499 client_unmanage(client_list->data);
502 void client_unmanage(ObClient *self)
507 ob_debug("Unmanaging window: %lx (%s) (%s)\n", self->window, self->class,
508 self->title ? self->title : "");
510 g_assert(self != NULL);
512 /* we dont want events no more. do this before hiding the frame so we
513 don't generate more events */
514 XSelectInput(ob_display, self->window, NoEventMask);
515 if (self->user_time_window) {
516 XSelectInput(ob_display, self->user_time_window, NoEventMask);
517 g_hash_table_remove(client_user_time_window_map, &w);
520 frame_hide(self->frame);
521 /* flush to send the hide to the server quickly */
524 /* ignore enter events from the unmap so it doesnt mess with the focus */
525 event_ignore_queued_enters();
527 mouse_grab_for_client(self, FALSE);
529 /* remove the window from our save set */
530 XChangeSaveSet(ob_display, self->window, SetModeDelete);
532 /* update the focus lists */
533 focus_order_remove(self);
534 if (client_focused(self)) {
535 /* don't leave an invalid focus_client */
539 client_list = g_list_remove(client_list, self);
540 stacking_remove(self);
541 g_hash_table_remove(window_map, &self->window);
543 /* once the client is out of the list, update the struts to remove its
545 if (STRUT_EXISTS(self->strut))
546 screen_update_areas();
548 for (it = client_destructors; it; it = g_slist_next(it)) {
549 ClientCallback *d = it->data;
550 d->func(self, d->data);
553 /* tell our parent(s) that we're gone */
554 if (self->transient_for == OB_TRAN_GROUP) { /* transient of group */
555 for (it = self->group->members; it; it = g_slist_next(it))
556 if (it->data != self)
557 ((ObClient*)it->data)->transients =
558 g_slist_remove(((ObClient*)it->data)->transients, self);
559 } else if (self->transient_for) { /* transient of window */
560 self->transient_for->transients =
561 g_slist_remove(self->transient_for->transients, self);
564 /* tell our transients that we're gone */
565 for (it = self->transients; it; it = g_slist_next(it)) {
566 if (((ObClient*)it->data)->transient_for != OB_TRAN_GROUP) {
567 ((ObClient*)it->data)->transient_for = NULL;
568 client_calc_layer(it->data);
572 /* remove from its group */
574 group_remove(self->group, self);
578 /* restore the window's original geometry so it is not lost */
582 if (self->fullscreen)
583 a = self->pre_fullscreen_area;
584 else if (self->max_horz || self->max_vert) {
585 if (self->max_horz) {
586 a.x = self->pre_max_area.x;
587 a.width = self->pre_max_area.width;
589 if (self->max_vert) {
590 a.y = self->pre_max_area.y;
591 a.height = self->pre_max_area.height;
595 /* give the client its border back */
596 client_toggle_border(self, TRUE);
598 self->fullscreen = self->max_horz = self->max_vert = FALSE;
599 self->decorations = 0; /* unmanaged windows have no decor */
601 client_move_resize(self, a.x, a.y, a.width, a.height);
604 /* reparent the window out of the frame, and free the frame */
605 frame_release_client(self->frame, self);
608 if (ob_state() != OB_STATE_EXITING) {
609 /* these values should not be persisted across a window
611 PROP_ERASE(self->window, net_wm_desktop);
612 PROP_ERASE(self->window, net_wm_state);
613 PROP_ERASE(self->window, wm_state);
615 /* if we're left in an unmapped state, the client wont be mapped. this
616 is bad, since we will no longer be managing the window on restart */
617 XMapWindow(ob_display, self->window);
620 ob_debug("Unmanaged window 0x%lx\n", self->window);
622 /* free all data allocated in the client struct */
623 g_slist_free(self->transients);
624 for (j = 0; j < self->nicons; ++j)
625 g_free(self->icons[j].data);
626 if (self->nicons > 0)
628 g_free(self->wm_command);
630 g_free(self->icon_title);
634 g_free(self->client_machine);
635 g_free(self->sm_client_id);
638 /* update the list hints */
642 static ObAppSettings *client_get_settings_state(ObClient *self)
644 ObAppSettings *settings = NULL;
647 for (it = config_per_app_settings; it; it = g_slist_next(it)) {
648 ObAppSettings *app = it->data;
650 if ((app->name && !app->class && !strcmp(app->name, self->name))
651 || (app->class && !app->name && !strcmp(app->class, self->class))
652 || (app->class && app->name && !strcmp(app->class, self->class)
653 && !strcmp(app->name, self->name)))
655 /* Match if no role was specified in the per app setting, or if the
656 * string matches the beginning of the role, since apps like to set
657 * the role to things like browser-window-23c4b2f */
659 || !strncmp(app->role, self->role, strlen(app->role)))
661 ob_debug("Window matching: %s\n", app->name);
670 if (settings->shade != -1)
671 self->shaded = !!settings->shade;
672 if (settings->decor != -1)
673 self->undecorated = !settings->decor;
674 if (settings->iconic != -1)
675 self->iconic = !!settings->iconic;
676 if (settings->skip_pager != -1)
677 self->skip_pager = !!settings->skip_pager;
678 if (settings->skip_taskbar != -1)
679 self->skip_taskbar = !!settings->skip_taskbar;
681 if (settings->max_vert != -1)
682 self->max_vert = !!settings->max_vert;
683 if (settings->max_horz != -1)
684 self->max_horz = !!settings->max_horz;
686 if (settings->fullscreen != -1)
687 self->fullscreen = !!settings->fullscreen;
689 if (settings->desktop) {
690 if (settings->desktop == DESKTOP_ALL)
691 self->desktop = settings->desktop;
692 else if (settings->desktop > 0 &&
693 settings->desktop <= screen_num_desktops)
694 self->desktop = settings->desktop - 1;
697 if (settings->layer == -1) {
701 else if (settings->layer == 0) {
705 else if (settings->layer == 1) {
713 static void client_restore_session_state(ObClient *self)
717 ob_debug_type(OB_DEBUG_SM,
718 "Restore session for client %s\n", self->title);
720 if (!(it = session_state_find(self))) {
721 ob_debug_type(OB_DEBUG_SM,
722 "Session data not found for client %s\n", self->title);
726 self->session = it->data;
728 ob_debug_type(OB_DEBUG_SM, "Session data loaded for client %s\n",
731 RECT_SET_POINT(self->area, self->session->x, self->session->y);
732 self->positioned = USPosition;
733 if (self->session->w > 0)
734 self->area.width = self->session->w;
735 if (self->session->h > 0)
736 self->area.height = self->session->h;
737 XResizeWindow(ob_display, self->window,
738 self->area.width, self->area.height);
740 self->desktop = (self->session->desktop == DESKTOP_ALL ?
741 self->session->desktop :
742 MIN(screen_num_desktops - 1, self->session->desktop));
743 PROP_SET32(self->window, net_wm_desktop, cardinal, self->desktop);
745 self->shaded = self->session->shaded;
746 self->iconic = self->session->iconic;
747 self->skip_pager = self->session->skip_pager;
748 self->skip_taskbar = self->session->skip_taskbar;
749 self->fullscreen = self->session->fullscreen;
750 self->above = self->session->above;
751 self->below = self->session->below;
752 self->max_horz = self->session->max_horz;
753 self->max_vert = self->session->max_vert;
754 self->undecorated = self->session->undecorated;
757 static gboolean client_restore_session_stacking(ObClient *self)
761 if (!self->session) return FALSE;
763 mypos = g_list_find(session_saved_state, self->session);
764 if (!mypos) return FALSE;
766 /* start above me and look for the first client */
767 for (it = g_list_previous(mypos); it; it = g_list_previous(it)) {
770 for (cit = client_list; cit; cit = g_list_next(cit)) {
771 ObClient *c = cit->data;
772 /* found a client that was in the session, so go below it */
773 if (c->session == it->data) {
774 stacking_below(CLIENT_AS_WINDOW(self),
775 CLIENT_AS_WINDOW(cit->data));
783 void client_move_onscreen(ObClient *self, gboolean rude)
785 gint x = self->area.x;
786 gint y = self->area.y;
787 if (client_find_onscreen(self, &x, &y,
789 self->area.height, rude)) {
790 client_move(self, x, y);
794 gboolean client_find_onscreen(ObClient *self, gint *x, gint *y, gint w, gint h,
798 gint ox = *x, oy = *y;
799 gboolean rudel = rude, ruder = rude, rudet = rude, rudeb = rude;
802 all_a = screen_area(self->desktop);
803 mon_a = screen_area_monitor(self->desktop, client_monitor(self));
805 /* get where the frame would be */
806 frame_client_gravity(self->frame, x, y, w, h);
808 /* get the requested size of the window with decorations */
809 fw = self->frame->size.left + w + self->frame->size.right;
810 fh = self->frame->size.top + h + self->frame->size.bottom;
812 /* This makes sure windows aren't entirely outside of the screen so you
813 can't see them at all.
814 It makes sure 10% of the window is on the screen at least. At don't let
815 it move itself off the top of the screen, which would hide the titlebar
816 on you. (The user can still do this if they want too, it's only limiting
819 XXX watch for xinerama dead areas...
821 if (client_normal(self)) {
822 if (!self->strut.right && *x + fw/10 >= all_a->x + all_a->width - 1)
823 *x = all_a->x + all_a->width - fw/10;
824 if (!self->strut.bottom && *y + fh/10 >= all_a->y + all_a->height - 1)
825 *y = all_a->y + all_a->height - fh/10;
826 if (!self->strut.left && *x + fw*9/10 - 1 < all_a->x)
827 *x = all_a->x - fw*9/10;
828 if (!self->strut.top && *y + fh*9/10 - 1 < all_a->y)
829 *y = all_a->y - fw*9/10;
832 /* If rudeness wasn't requested, then figure out of the client is currently
833 entirely on the screen. If it is, and the position isn't changing by
834 request, and it is enlarging, then be rude even though it wasn't
837 Point oldtl, oldtr, oldbl, oldbr;
838 Point newtl, newtr, newbl, newbr;
839 gboolean stationary_l, stationary_r, stationary_t, stationary_b;
841 POINT_SET(oldtl, self->frame->area.x, self->frame->area.y);
842 POINT_SET(oldbr, self->frame->area.x + self->frame->area.width - 1,
843 self->frame->area.y + self->frame->area.height - 1);
844 POINT_SET(oldtr, oldbr.x, oldtl.y);
845 POINT_SET(oldbl, oldtl.x, oldbr.y);
847 POINT_SET(newtl, *x, *y);
848 POINT_SET(newbr, *x + fw - 1, *y + fh - 1);
849 POINT_SET(newtr, newbr.x, newtl.y);
850 POINT_SET(newbl, newtl.x, newbr.y);
852 /* is it moving or just resizing from some corner? */
853 stationary_l = oldtl.x == oldtl.x;
854 stationary_r = oldtr.x == oldtr.x;
855 stationary_t = oldtl.y == oldtl.y;
856 stationary_b = oldbl.y == oldbl.y;
858 /* if left edge is growing and didnt move right edge */
859 if (stationary_r && newtl.x < oldtl.x)
861 /* if right edge is growing and didnt move left edge */
862 if (stationary_l && newtr.x > oldtr.x)
864 /* if top edge is growing and didnt move bottom edge */
865 if (stationary_b && newtl.y < oldtl.y)
867 /* if bottom edge is growing and didnt move top edge */
868 if (stationary_t && newbl.y > oldbl.y)
872 /* This here doesn't let windows even a pixel outside the struts/screen.
873 * When called from client_manage, programs placing themselves are
874 * forced completely onscreen, while things like
875 * xterm -geometry resolution-width/2 will work fine. Trying to
876 * place it completely offscreen will be handled in the above code.
877 * Sorry for this confused comment, i am tired. */
878 if (fw <= mon_a->width) {
879 if (rudel && !self->strut.left && *x < mon_a->x) *x = mon_a->x;
880 if (ruder && !self->strut.right && *x + fw > mon_a->x + mon_a->width)
881 *x = mon_a->x + mon_a->width - fw;
883 if (fh <= mon_a->height) {
884 if (rudet && !self->strut.top && *y < mon_a->y) *y = mon_a->y;
885 if (rudeb && !self->strut.bottom && *y + fh > mon_a->y + mon_a->height)
886 *y = mon_a->y + mon_a->height - fh;
889 /* get where the client should be */
890 frame_frame_gravity(self->frame, x, y, w, h);
892 return ox != *x || oy != *y;
895 static void client_toggle_border(ObClient *self, gboolean show)
897 /* adjust our idea of where the client is, based on its border. When the
898 border is removed, the client should now be considered to be in a
900 when re-adding the border to the client, the same operation needs to be
902 gint oldx = self->area.x, oldy = self->area.y;
903 gint x = oldx, y = oldy;
904 switch(self->gravity) {
906 case NorthWestGravity:
908 case SouthWestGravity:
910 case NorthEastGravity:
912 case SouthEastGravity:
913 if (show) x -= self->border_width * 2;
914 else x += self->border_width * 2;
921 if (show) x -= self->border_width;
922 else x += self->border_width;
925 switch(self->gravity) {
927 case NorthWestGravity:
929 case NorthEastGravity:
931 case SouthWestGravity:
933 case SouthEastGravity:
934 if (show) y -= self->border_width * 2;
935 else y += self->border_width * 2;
942 if (show) y -= self->border_width;
943 else y += self->border_width;
950 XSetWindowBorderWidth(ob_display, self->window, self->border_width);
952 /* set border_width to 0 because there is no border to add into
953 calculations anymore */
954 self->border_width = 0;
956 XSetWindowBorderWidth(ob_display, self->window, 0);
960 static void client_get_all(ObClient *self)
962 client_get_area(self);
963 client_get_mwm_hints(self);
965 /* The transient-ness of a window is used to pick a type, but the type can
966 also affect transiency.
968 Dialogs are always made transients for their group if they have one.
970 I also have made non-application type windows be transients for their
971 group (eg utility windows).
973 client_get_transientness(self);
974 client_get_type(self);/* this can change the mwmhints for special cases */
975 client_get_state(self);
977 client_update_wmhints(self);
978 /* this may have already been called from client_update_wmhints */
979 if (self->transient_for == NULL)
980 client_update_transient_for(self);
981 client_get_startup_id(self);
982 client_get_desktop(self);/* uses transient data/group/startup id if a
983 desktop is not specified */
984 client_get_shaped(self);
986 client_get_layer(self); /* if layer hasn't been specified, get it from
987 other sources if possible */
990 /* a couple type-based defaults for new windows */
992 /* this makes sure that these windows appear on all desktops */
993 if (self->type == OB_CLIENT_TYPE_DESKTOP)
994 self->desktop = DESKTOP_ALL;
997 client_update_protocols(self);
999 client_get_gravity(self); /* get the attribute gravity */
1000 client_update_normal_hints(self); /* this may override the attribute
1003 /* got the type, the mwmhints, the protocols, and the normal hints
1004 (min/max sizes), so we're ready to set up the decorations/functions */
1005 client_setup_decor_and_functions(self);
1008 client_update_sync_request_counter(self);
1011 /* get the session related properties */
1012 client_get_session_ids(self);
1014 client_get_colormap(self);
1015 client_update_title(self);
1016 client_update_strut(self);
1017 client_update_icons(self);
1018 client_update_user_time_window(self);
1019 if (!self->user_time_window) /* check if this would have been called */
1020 client_update_user_time(self);
1021 client_update_icon_geometry(self);
1024 static void client_get_startup_id(ObClient *self)
1026 if (!(PROP_GETS(self->window, net_startup_id, utf8, &self->startup_id)))
1028 PROP_GETS(self->group->leader,
1029 net_startup_id, utf8, &self->startup_id);
1032 static void client_get_area(ObClient *self)
1034 XWindowAttributes wattrib;
1037 ret = XGetWindowAttributes(ob_display, self->window, &wattrib);
1038 g_assert(ret != BadWindow);
1040 RECT_SET(self->area, wattrib.x, wattrib.y, wattrib.width, wattrib.height);
1041 POINT_SET(self->root_pos, wattrib.x, wattrib.y);
1042 self->border_width = wattrib.border_width;
1044 ob_debug("client area: %d %d %d %d\n", wattrib.x, wattrib.y,
1045 wattrib.width, wattrib.height);
1048 static void client_get_desktop(ObClient *self)
1050 guint32 d = screen_num_desktops; /* an always-invalid value */
1052 if (PROP_GET32(self->window, net_wm_desktop, cardinal, &d)) {
1053 if (d >= screen_num_desktops && d != DESKTOP_ALL)
1054 self->desktop = screen_num_desktops - 1;
1058 gboolean trdesk = FALSE;
1060 if (self->transient_for) {
1061 if (self->transient_for != OB_TRAN_GROUP) {
1062 self->desktop = self->transient_for->desktop;
1067 for (it = self->group->members; it; it = g_slist_next(it))
1068 if (it->data != self &&
1069 !((ObClient*)it->data)->transient_for) {
1070 self->desktop = ((ObClient*)it->data)->desktop;
1077 /* try get from the startup-notification protocol */
1078 if (sn_get_desktop(self->startup_id, &self->desktop)) {
1079 if (self->desktop >= screen_num_desktops &&
1080 self->desktop != DESKTOP_ALL)
1081 self->desktop = screen_num_desktops - 1;
1083 /* defaults to the current desktop */
1084 self->desktop = screen_desktop;
1089 static void client_get_layer(ObClient *self)
1091 if (!(self->above || self->below)) {
1093 /* apply stuff from the group */
1097 for (it = self->group->members; it; it = g_slist_next(it)) {
1098 ObClient *c = it->data;
1099 if (c != self && !client_search_transient(self, c) &&
1100 client_normal(self) && client_normal(c))
1103 (c->above ? 1 : (c->below ? -1 : 0)));
1117 g_assert_not_reached();
1124 static void client_get_state(ObClient *self)
1129 if (PROP_GETA32(self->window, net_wm_state, atom, &state, &num)) {
1131 for (i = 0; i < num; ++i) {
1132 if (state[i] == prop_atoms.net_wm_state_modal)
1134 else if (state[i] == prop_atoms.net_wm_state_shaded)
1135 self->shaded = TRUE;
1136 else if (state[i] == prop_atoms.net_wm_state_hidden)
1137 self->iconic = TRUE;
1138 else if (state[i] == prop_atoms.net_wm_state_skip_taskbar)
1139 self->skip_taskbar = TRUE;
1140 else if (state[i] == prop_atoms.net_wm_state_skip_pager)
1141 self->skip_pager = TRUE;
1142 else if (state[i] == prop_atoms.net_wm_state_fullscreen)
1143 self->fullscreen = TRUE;
1144 else if (state[i] == prop_atoms.net_wm_state_maximized_vert)
1145 self->max_vert = TRUE;
1146 else if (state[i] == prop_atoms.net_wm_state_maximized_horz)
1147 self->max_horz = TRUE;
1148 else if (state[i] == prop_atoms.net_wm_state_above)
1150 else if (state[i] == prop_atoms.net_wm_state_below)
1152 else if (state[i] == prop_atoms.net_wm_state_demands_attention)
1153 self->demands_attention = TRUE;
1154 else if (state[i] == prop_atoms.openbox_wm_state_undecorated)
1155 self->undecorated = TRUE;
1162 static void client_get_shaped(ObClient *self)
1164 self->shaped = FALSE;
1166 if (extensions_shape) {
1171 XShapeSelectInput(ob_display, self->window, ShapeNotifyMask);
1173 XShapeQueryExtents(ob_display, self->window, &s, &foo,
1174 &foo, &ufoo, &ufoo, &foo, &foo, &foo, &ufoo,
1176 self->shaped = (s != 0);
1181 void client_get_transientness(ObClient *self)
1184 if (XGetTransientForHint(ob_display, self->window, &t))
1185 self->transient = TRUE;
1188 void client_update_transient_for(ObClient *self)
1191 ObClient *target = NULL;
1193 if (XGetTransientForHint(ob_display, self->window, &t)) {
1194 self->transient = TRUE;
1195 if (t != self->window) { /* cant be transient to itself! */
1196 target = g_hash_table_lookup(window_map, &t);
1197 /* if this happens then we need to check for it*/
1198 g_assert(target != self);
1199 if (target && !WINDOW_IS_CLIENT(target)) {
1200 /* this can happen when a dialog is a child of
1201 a dockapp, for example */
1205 /* THIS IS SO ANNOYING ! ! ! ! Let me explain.... have a seat..
1207 Setting the transient_for to Root is actually illegal, however
1208 applications from time have done this to specify transient for
1211 Now you can do that by being a TYPE_DIALOG and not setting
1212 the transient_for hint at all on your window. But people still
1213 use Root, and Kwin is very strange in this regard.
1215 KWin 3.0 will not consider windows with transient_for set to
1216 Root as transient for their group *UNLESS* they are also modal.
1217 In that case, it will make them transient for the group. This
1218 leads to all sorts of weird behavior from KDE apps which are
1219 only tested in KWin. I'd like to follow their behavior just to
1220 make this work right with KDE stuff, but that seems wrong.
1222 if (!target && self->group) {
1223 /* not transient to a client, see if it is transient for a
1225 if (t == RootWindow(ob_display, ob_screen)) {
1226 /* window is a transient for its group! */
1227 target = OB_TRAN_GROUP;
1231 } else if (self->type == OB_CLIENT_TYPE_DIALOG ||
1232 self->type == OB_CLIENT_TYPE_TOOLBAR ||
1233 self->type == OB_CLIENT_TYPE_MENU ||
1234 self->type == OB_CLIENT_TYPE_UTILITY)
1236 self->transient = TRUE;
1238 target = OB_TRAN_GROUP;
1240 self->transient = FALSE;
1242 client_update_transient_tree(self, self->group, self->group,
1243 self->transient_for, target);
1244 self->transient_for = target;
1248 static void client_update_transient_tree(ObClient *self,
1249 ObGroup *oldgroup, ObGroup *newgroup,
1250 ObClient* oldparent,
1251 ObClient *newparent)
1256 /* No change has occured */
1257 if (oldgroup == newgroup && oldparent == newparent) return;
1259 /** Remove the client from the transient tree wherever it has changed **/
1261 /* If the window is becoming a direct transient for a window in its group
1262 then that window can't be a child of this window anymore */
1263 if (oldparent != newparent &&
1264 newparent != NULL && newparent != OB_TRAN_GROUP &&
1265 newparent->transient_for == OB_TRAN_GROUP &&
1266 newgroup != NULL && newgroup == oldgroup)
1268 self->transients = g_slist_remove(self->transients, newparent);
1272 /* If the group changed, or if we are just becoming transient for the
1273 group, then we need to remove any old group transient windows
1274 from our children. But if we were already transient for the group, then
1275 other group transients are not our children. */
1276 if ((oldgroup != newgroup ||
1277 (newparent == OB_TRAN_GROUP && oldparent != newparent)) &&
1278 oldgroup != NULL && oldparent != OB_TRAN_GROUP)
1280 for (it = self->transients; it; it = next) {
1281 next = g_slist_next(it);
1283 if (c->group == oldgroup)
1284 self->transients = g_slist_delete_link(self->transients, it);
1288 /* If we used to be transient for a group and now we are not, or we're
1289 transient for a new group, then we need to remove ourselves from all
1291 if (oldparent == OB_TRAN_GROUP && (oldgroup != newgroup ||
1292 oldparent != newparent))
1294 for (it = oldgroup->members; it; it = g_slist_next(it)) {
1296 if (c != self && (!c->transient_for ||
1297 c->transient_for != OB_TRAN_GROUP))
1298 c->transients = g_slist_remove(c->transients, self);
1301 /* If we used to be transient for a single window and we are no longer
1302 transient for it, then we need to remove ourself from its children */
1303 else if (oldparent != NULL && oldparent != OB_TRAN_GROUP &&
1304 oldparent != newparent)
1305 oldparent->transients = g_slist_remove(oldparent->transients, self);
1308 /** Re-add the client to the transient tree wherever it has changed **/
1310 /* If we're now transient for a group and we weren't transient for it
1311 before then we need to add ourselves to all our new parents */
1312 if (newparent == OB_TRAN_GROUP && (oldgroup != newgroup ||
1313 oldparent != newparent))
1315 for (it = oldgroup->members; it; it = g_slist_next(it)) {
1317 if (c != self && (!c->transient_for ||
1318 c->transient_for != OB_TRAN_GROUP))
1319 c->transients = g_slist_prepend(c->transients, self);
1322 /* If we are now transient for a single window which we weren't before,
1323 we need to add ourselves to its children
1325 WARNING: Cyclical transient ness is possible if two windows are
1326 transient for eachother.
1328 else if (newparent != NULL && newparent != OB_TRAN_GROUP &&
1329 newparent != oldparent &&
1330 /* don't make ourself its child if it is already our child */
1331 !client_is_direct_child(self, newparent))
1332 newparent->transients = g_slist_prepend(newparent->transients, self);
1334 /* If the group changed then we need to add any new group transient
1335 windows to our children. But if we're transient for the group, then
1336 other group transients are not our children.
1338 WARNING: Cyclical transient-ness is possible. For e.g. if:
1339 A is transient for the group
1340 B is a member of the group and transient for A
1342 if (oldgroup != newgroup && newgroup != NULL &&
1343 newparent != OB_TRAN_GROUP)
1345 for (it = newgroup->members; it; it = g_slist_next(it)) {
1347 if (c != self && c->transient_for == OB_TRAN_GROUP &&
1348 /* Don't make it our child if it is already our parent */
1349 !client_is_direct_child(c, self))
1351 self->transients = g_slist_prepend(self->transients, c);
1357 static void client_get_mwm_hints(ObClient *self)
1362 self->mwmhints.flags = 0; /* default to none */
1364 if (PROP_GETA32(self->window, motif_wm_hints, motif_wm_hints,
1366 if (num >= OB_MWM_ELEMENTS) {
1367 self->mwmhints.flags = hints[0];
1368 self->mwmhints.functions = hints[1];
1369 self->mwmhints.decorations = hints[2];
1375 void client_get_type(ObClient *self)
1382 if (PROP_GETA32(self->window, net_wm_window_type, atom, &val, &num)) {
1383 /* use the first value that we know about in the array */
1384 for (i = 0; i < num; ++i) {
1385 if (val[i] == prop_atoms.net_wm_window_type_desktop)
1386 self->type = OB_CLIENT_TYPE_DESKTOP;
1387 else if (val[i] == prop_atoms.net_wm_window_type_dock)
1388 self->type = OB_CLIENT_TYPE_DOCK;
1389 else if (val[i] == prop_atoms.net_wm_window_type_toolbar)
1390 self->type = OB_CLIENT_TYPE_TOOLBAR;
1391 else if (val[i] == prop_atoms.net_wm_window_type_menu)
1392 self->type = OB_CLIENT_TYPE_MENU;
1393 else if (val[i] == prop_atoms.net_wm_window_type_utility)
1394 self->type = OB_CLIENT_TYPE_UTILITY;
1395 else if (val[i] == prop_atoms.net_wm_window_type_splash)
1396 self->type = OB_CLIENT_TYPE_SPLASH;
1397 else if (val[i] == prop_atoms.net_wm_window_type_dialog)
1398 self->type = OB_CLIENT_TYPE_DIALOG;
1399 else if (val[i] == prop_atoms.net_wm_window_type_normal)
1400 self->type = OB_CLIENT_TYPE_NORMAL;
1401 else if (val[i] == prop_atoms.kde_net_wm_window_type_override) {
1402 /* prevent this window from getting any decor or
1404 self->mwmhints.flags &= (OB_MWM_FLAG_FUNCTIONS |
1405 OB_MWM_FLAG_DECORATIONS);
1406 self->mwmhints.decorations = 0;
1407 self->mwmhints.functions = 0;
1409 if (self->type != (ObClientType) -1)
1410 break; /* grab the first legit type */
1415 if (self->type == (ObClientType) -1) {
1416 /*the window type hint was not set, which means we either classify
1417 ourself as a normal window or a dialog, depending on if we are a
1419 if (self->transient)
1420 self->type = OB_CLIENT_TYPE_DIALOG;
1422 self->type = OB_CLIENT_TYPE_NORMAL;
1426 void client_update_protocols(ObClient *self)
1429 guint num_return, i;
1431 self->focus_notify = FALSE;
1432 self->delete_window = FALSE;
1434 if (PROP_GETA32(self->window, wm_protocols, atom, &proto, &num_return)) {
1435 for (i = 0; i < num_return; ++i) {
1436 if (proto[i] == prop_atoms.wm_delete_window)
1437 /* this means we can request the window to close */
1438 self->delete_window = TRUE;
1439 else if (proto[i] == prop_atoms.wm_take_focus)
1440 /* if this protocol is requested, then the window will be
1441 notified whenever we want it to receive focus */
1442 self->focus_notify = TRUE;
1444 else if (proto[i] == prop_atoms.net_wm_sync_request)
1445 /* if this protocol is requested, then resizing the
1446 window will be synchronized between the frame and the
1448 self->sync_request = TRUE;
1456 void client_update_sync_request_counter(ObClient *self)
1460 if (PROP_GET32(self->window, net_wm_sync_request_counter, cardinal, &i)) {
1461 self->sync_counter = i;
1463 self->sync_counter = None;
1467 static void client_get_gravity(ObClient *self)
1469 XWindowAttributes wattrib;
1472 ret = XGetWindowAttributes(ob_display, self->window, &wattrib);
1473 g_assert(ret != BadWindow);
1474 self->gravity = wattrib.win_gravity;
1477 void client_get_colormap(ObClient *self)
1479 XWindowAttributes wa;
1481 if (XGetWindowAttributes(ob_display, self->window, &wa))
1482 client_update_colormap(self, wa.colormap);
1485 void client_update_colormap(ObClient *self, Colormap colormap)
1487 self->colormap = colormap;
1490 void client_update_normal_hints(ObClient *self)
1494 gint oldgravity = self->gravity;
1497 self->min_ratio = 0.0f;
1498 self->max_ratio = 0.0f;
1499 SIZE_SET(self->size_inc, 1, 1);
1500 SIZE_SET(self->base_size, 0, 0);
1501 SIZE_SET(self->min_size, 0, 0);
1502 SIZE_SET(self->max_size, G_MAXINT, G_MAXINT);
1504 /* get the hints from the window */
1505 if (XGetWMNormalHints(ob_display, self->window, &size, &ret)) {
1506 /* normal windows can't request placement! har har
1507 if (!client_normal(self))
1509 self->positioned = (size.flags & (PPosition|USPosition));
1511 if (size.flags & PWinGravity) {
1512 self->gravity = size.win_gravity;
1514 /* if the client has a frame, i.e. has already been mapped and
1515 is changing its gravity */
1516 if (self->frame && self->gravity != oldgravity) {
1517 /* move our idea of the client's position based on its new
1519 client_convert_gravity(self, oldgravity,
1520 &self->area.x, &self->area.y,
1521 self->area.width, self->area.height);
1525 if (size.flags & PAspect) {
1526 if (size.min_aspect.y)
1528 (gfloat) size.min_aspect.x / size.min_aspect.y;
1529 if (size.max_aspect.y)
1531 (gfloat) size.max_aspect.x / size.max_aspect.y;
1534 if (size.flags & PMinSize)
1535 SIZE_SET(self->min_size, size.min_width, size.min_height);
1537 if (size.flags & PMaxSize)
1538 SIZE_SET(self->max_size, size.max_width, size.max_height);
1540 if (size.flags & PBaseSize)
1541 SIZE_SET(self->base_size, size.base_width, size.base_height);
1543 if (size.flags & PResizeInc && size.width_inc && size.height_inc)
1544 SIZE_SET(self->size_inc, size.width_inc, size.height_inc);
1548 void client_setup_decor_and_functions(ObClient *self)
1550 /* start with everything (cept fullscreen) */
1552 (OB_FRAME_DECOR_TITLEBAR |
1553 OB_FRAME_DECOR_HANDLE |
1554 OB_FRAME_DECOR_GRIPS |
1555 OB_FRAME_DECOR_BORDER |
1556 OB_FRAME_DECOR_ICON |
1557 OB_FRAME_DECOR_ALLDESKTOPS |
1558 OB_FRAME_DECOR_ICONIFY |
1559 OB_FRAME_DECOR_MAXIMIZE |
1560 OB_FRAME_DECOR_SHADE |
1561 OB_FRAME_DECOR_CLOSE);
1563 (OB_CLIENT_FUNC_RESIZE |
1564 OB_CLIENT_FUNC_MOVE |
1565 OB_CLIENT_FUNC_ICONIFY |
1566 OB_CLIENT_FUNC_MAXIMIZE |
1567 OB_CLIENT_FUNC_SHADE |
1568 OB_CLIENT_FUNC_CLOSE);
1570 if (!(self->min_size.width < self->max_size.width ||
1571 self->min_size.height < self->max_size.height))
1572 self->functions &= ~OB_CLIENT_FUNC_RESIZE;
1574 switch (self->type) {
1575 case OB_CLIENT_TYPE_NORMAL:
1576 /* normal windows retain all of the possible decorations and
1577 functionality, and are the only windows that you can fullscreen */
1578 self->functions |= OB_CLIENT_FUNC_FULLSCREEN;
1581 case OB_CLIENT_TYPE_DIALOG:
1582 case OB_CLIENT_TYPE_UTILITY:
1583 /* these windows cannot be maximized */
1584 self->functions &= ~OB_CLIENT_FUNC_MAXIMIZE;
1587 case OB_CLIENT_TYPE_MENU:
1588 case OB_CLIENT_TYPE_TOOLBAR:
1589 /* these windows get less functionality */
1590 self->functions &= ~(OB_CLIENT_FUNC_ICONIFY | OB_CLIENT_FUNC_RESIZE);
1593 case OB_CLIENT_TYPE_SPLASH:
1594 /* these don't get get any decorations, and the only thing you can
1595 do with them is move them */
1596 self->decorations = 0;
1597 self->functions = OB_CLIENT_FUNC_MOVE;
1599 case OB_CLIENT_TYPE_DESKTOP:
1600 case OB_CLIENT_TYPE_DOCK:
1601 /* these windows are not manipulated by the window manager */
1602 self->decorations = 0;
1603 self->functions = 0;
1607 /* Mwm Hints are applied subtractively to what has already been chosen for
1608 decor and functionality */
1609 if (self->mwmhints.flags & OB_MWM_FLAG_DECORATIONS) {
1610 if (! (self->mwmhints.decorations & OB_MWM_DECOR_ALL)) {
1611 if (! ((self->mwmhints.decorations & OB_MWM_DECOR_HANDLE) ||
1612 (self->mwmhints.decorations & OB_MWM_DECOR_TITLE)))
1614 /* if the mwm hints request no handle or title, then all
1615 decorations are disabled, but keep the border if that's
1617 if (self->mwmhints.decorations & OB_MWM_DECOR_BORDER)
1618 self->decorations = OB_FRAME_DECOR_BORDER;
1620 self->decorations = 0;
1625 if (self->mwmhints.flags & OB_MWM_FLAG_FUNCTIONS) {
1626 if (! (self->mwmhints.functions & OB_MWM_FUNC_ALL)) {
1627 if (! (self->mwmhints.functions & OB_MWM_FUNC_RESIZE))
1628 self->functions &= ~OB_CLIENT_FUNC_RESIZE;
1629 if (! (self->mwmhints.functions & OB_MWM_FUNC_MOVE))
1630 self->functions &= ~OB_CLIENT_FUNC_MOVE;
1631 /* dont let mwm hints kill any buttons
1632 if (! (self->mwmhints.functions & OB_MWM_FUNC_ICONIFY))
1633 self->functions &= ~OB_CLIENT_FUNC_ICONIFY;
1634 if (! (self->mwmhints.functions & OB_MWM_FUNC_MAXIMIZE))
1635 self->functions &= ~OB_CLIENT_FUNC_MAXIMIZE;
1637 /* dont let mwm hints kill the close button
1638 if (! (self->mwmhints.functions & MwmFunc_Close))
1639 self->functions &= ~OB_CLIENT_FUNC_CLOSE; */
1643 if (!(self->functions & OB_CLIENT_FUNC_SHADE))
1644 self->decorations &= ~OB_FRAME_DECOR_SHADE;
1645 if (!(self->functions & OB_CLIENT_FUNC_ICONIFY))
1646 self->decorations &= ~OB_FRAME_DECOR_ICONIFY;
1647 if (!(self->functions & OB_CLIENT_FUNC_RESIZE))
1648 self->decorations &= ~OB_FRAME_DECOR_GRIPS;
1650 /* can't maximize without moving/resizing */
1651 if (!((self->functions & OB_CLIENT_FUNC_MAXIMIZE) &&
1652 (self->functions & OB_CLIENT_FUNC_MOVE) &&
1653 (self->functions & OB_CLIENT_FUNC_RESIZE))) {
1654 self->functions &= ~OB_CLIENT_FUNC_MAXIMIZE;
1655 self->decorations &= ~OB_FRAME_DECOR_MAXIMIZE;
1658 /* kill the handle on fully maxed windows */
1659 if (self->max_vert && self->max_horz)
1660 self->decorations &= ~OB_FRAME_DECOR_HANDLE;
1662 /* finally, the user can have requested no decorations, which overrides
1663 everything (but doesnt give it a border if it doesnt have one) */
1664 if (self->undecorated) {
1665 if (config_theme_keepborder)
1666 self->decorations &= OB_FRAME_DECOR_BORDER;
1668 self->decorations = 0;
1671 /* if we don't have a titlebar, then we cannot shade! */
1672 if (!(self->decorations & OB_FRAME_DECOR_TITLEBAR))
1673 self->functions &= ~OB_CLIENT_FUNC_SHADE;
1675 /* now we need to check against rules for the client's current state */
1676 if (self->fullscreen) {
1677 self->functions &= (OB_CLIENT_FUNC_CLOSE |
1678 OB_CLIENT_FUNC_FULLSCREEN |
1679 OB_CLIENT_FUNC_ICONIFY);
1680 self->decorations = 0;
1683 client_change_allowed_actions(self);
1686 /* adjust the client's decorations, etc. */
1687 client_reconfigure(self);
1691 static void client_change_allowed_actions(ObClient *self)
1696 /* desktop windows are kept on all desktops */
1697 if (self->type != OB_CLIENT_TYPE_DESKTOP)
1698 actions[num++] = prop_atoms.net_wm_action_change_desktop;
1700 if (self->functions & OB_CLIENT_FUNC_SHADE)
1701 actions[num++] = prop_atoms.net_wm_action_shade;
1702 if (self->functions & OB_CLIENT_FUNC_CLOSE)
1703 actions[num++] = prop_atoms.net_wm_action_close;
1704 if (self->functions & OB_CLIENT_FUNC_MOVE)
1705 actions[num++] = prop_atoms.net_wm_action_move;
1706 if (self->functions & OB_CLIENT_FUNC_ICONIFY)
1707 actions[num++] = prop_atoms.net_wm_action_minimize;
1708 if (self->functions & OB_CLIENT_FUNC_RESIZE)
1709 actions[num++] = prop_atoms.net_wm_action_resize;
1710 if (self->functions & OB_CLIENT_FUNC_FULLSCREEN)
1711 actions[num++] = prop_atoms.net_wm_action_fullscreen;
1712 if (self->functions & OB_CLIENT_FUNC_MAXIMIZE) {
1713 actions[num++] = prop_atoms.net_wm_action_maximize_horz;
1714 actions[num++] = prop_atoms.net_wm_action_maximize_vert;
1717 PROP_SETA32(self->window, net_wm_allowed_actions, atom, actions, num);
1719 /* make sure the window isn't breaking any rules now */
1721 if (!(self->functions & OB_CLIENT_FUNC_SHADE) && self->shaded) {
1722 if (self->frame) client_shade(self, FALSE);
1723 else self->shaded = FALSE;
1725 if (!(self->functions & OB_CLIENT_FUNC_ICONIFY) && self->iconic) {
1726 if (self->frame) client_iconify(self, FALSE, TRUE);
1727 else self->iconic = FALSE;
1729 if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) && self->fullscreen) {
1730 if (self->frame) client_fullscreen(self, FALSE);
1731 else self->fullscreen = FALSE;
1733 if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE) && (self->max_horz ||
1735 if (self->frame) client_maximize(self, FALSE, 0);
1736 else self->max_vert = self->max_horz = FALSE;
1740 void client_reconfigure(ObClient *self)
1742 /* by making this pass FALSE for user, we avoid the emacs event storm where
1743 every configurenotify causes an update in its normal hints, i think this
1744 is generally what we want anyways... */
1745 client_configure(self, self->area.x, self->area.y,
1746 self->area.width, self->area.height, FALSE, TRUE);
1749 void client_update_wmhints(ObClient *self)
1753 /* assume a window takes input if it doesnt specify */
1754 self->can_focus = TRUE;
1756 if ((hints = XGetWMHints(ob_display, self->window)) != NULL) {
1759 if (hints->flags & InputHint)
1760 self->can_focus = hints->input;
1762 /* only do this when first managing the window *AND* when we aren't
1764 if (ob_state() != OB_STATE_STARTING && self->frame == NULL)
1765 if (hints->flags & StateHint)
1766 self->iconic = hints->initial_state == IconicState;
1769 self->urgent = (hints->flags & XUrgencyHint);
1770 if (self->urgent && !ur)
1771 client_hilite(self, TRUE);
1772 else if (!self->urgent && ur && self->demands_attention)
1773 client_hilite(self, FALSE);
1775 if (!(hints->flags & WindowGroupHint))
1776 hints->window_group = None;
1778 /* did the group state change? */
1779 if (hints->window_group !=
1780 (self->group ? self->group->leader : None))
1782 ObGroup *oldgroup = self->group;
1784 /* remove from the old group if there was one */
1785 if (self->group != NULL) {
1786 group_remove(self->group, self);
1790 /* add ourself to the group if we have one */
1791 if (hints->window_group != None) {
1792 self->group = group_add(hints->window_group, self);
1795 /* Put ourselves into the new group's transient tree, and remove
1796 ourselves from the old group's */
1797 client_update_transient_tree(self, oldgroup, self->group,
1798 self->transient_for,
1799 self->transient_for);
1801 /* Lastly, being in a group, or not, can change if the window is
1802 transient for anything.
1804 The logic for this is:
1805 self->transient = TRUE always if the window wants to be
1806 transient for something, even if transient_for was NULL because
1807 it wasn't in a group before.
1809 If transient_for was NULL and oldgroup was NULL we can assume
1810 that when we add the new group, it will become transient for
1813 If transient_for was OB_TRAN_GROUP, then it must have already
1814 had a group. If it is getting a new group, the above call to
1815 client_update_transient_tree has already taken care of
1816 everything ! If it is losing all group status then it will
1817 no longer be transient for anything and that needs to be
1820 if (self->transient &&
1821 ((self->transient_for == NULL && oldgroup == NULL) ||
1822 (self->transient_for == OB_TRAN_GROUP && !self->group)))
1823 client_update_transient_for(self);
1826 /* the WM_HINTS can contain an icon */
1827 client_update_icons(self);
1833 void client_update_title(ObClient *self)
1836 gchar *visible = NULL;
1838 g_free(self->title);
1841 if (!PROP_GETS(self->window, net_wm_name, utf8, &data)) {
1842 /* try old x stuff */
1843 if (!(PROP_GETS(self->window, wm_name, locale, &data)
1844 || PROP_GETS(self->window, wm_name, utf8, &data))) {
1845 if (self->transient) {
1847 GNOME alert windows are not given titles:
1848 http://developer.gnome.org/projects/gup/hig/draft_hig_new/windows-alert.html
1850 data = g_strdup("");
1852 data = g_strdup("Unnamed Window");
1856 if (self->client_machine) {
1857 visible = g_strdup_printf("%s (%s)", data, self->client_machine);
1862 PROP_SETS(self->window, net_wm_visible_name, visible);
1863 self->title = visible;
1866 frame_adjust_title(self->frame);
1868 /* update the icon title */
1870 g_free(self->icon_title);
1873 if (!PROP_GETS(self->window, net_wm_icon_name, utf8, &data))
1874 /* try old x stuff */
1875 if (!(PROP_GETS(self->window, wm_icon_name, locale, &data) ||
1876 PROP_GETS(self->window, wm_icon_name, utf8, &data)))
1877 data = g_strdup(self->title);
1879 PROP_SETS(self->window, net_wm_visible_icon_name, data);
1880 self->icon_title = data;
1883 void client_update_strut(ObClient *self)
1887 gboolean got = FALSE;
1890 if (PROP_GETA32(self->window, net_wm_strut_partial, cardinal,
1894 STRUT_PARTIAL_SET(strut,
1895 data[0], data[2], data[1], data[3],
1896 data[4], data[5], data[8], data[9],
1897 data[6], data[7], data[10], data[11]);
1903 PROP_GETA32(self->window, net_wm_strut, cardinal, &data, &num)) {
1909 /* use the screen's width/height */
1910 a = screen_physical_area();
1912 STRUT_PARTIAL_SET(strut,
1913 data[0], data[2], data[1], data[3],
1914 a->y, a->y + a->height - 1,
1915 a->x, a->x + a->width - 1,
1916 a->y, a->y + a->height - 1,
1917 a->x, a->x + a->width - 1);
1923 STRUT_PARTIAL_SET(strut, 0, 0, 0, 0,
1924 0, 0, 0, 0, 0, 0, 0, 0);
1926 if (!STRUT_EQUAL(strut, self->strut)) {
1927 self->strut = strut;
1929 /* updating here is pointless while we're being mapped cuz we're not in
1930 the client list yet */
1932 screen_update_areas();
1936 void client_update_icons(ObClient *self)
1942 for (i = 0; i < self->nicons; ++i)
1943 g_free(self->icons[i].data);
1944 if (self->nicons > 0)
1945 g_free(self->icons);
1948 if (PROP_GETA32(self->window, net_wm_icon, cardinal, &data, &num)) {
1949 /* figure out how many valid icons are in here */
1951 while (num - i > 2) {
1955 if (i > num || w*h == 0) break;
1959 self->icons = g_new(ObClientIcon, self->nicons);
1961 /* store the icons */
1963 for (j = 0; j < self->nicons; ++j) {
1966 w = self->icons[j].width = data[i++];
1967 h = self->icons[j].height = data[i++];
1969 if (w*h == 0) continue;
1971 self->icons[j].data = g_new(RrPixel32, w * h);
1972 for (x = 0, y = 0, t = 0; t < w * h; ++t, ++x, ++i) {
1977 self->icons[j].data[t] =
1978 (((data[i] >> 24) & 0xff) << RrDefaultAlphaOffset) +
1979 (((data[i] >> 16) & 0xff) << RrDefaultRedOffset) +
1980 (((data[i] >> 8) & 0xff) << RrDefaultGreenOffset) +
1981 (((data[i] >> 0) & 0xff) << RrDefaultBlueOffset);
1990 if ((hints = XGetWMHints(ob_display, self->window))) {
1991 if (hints->flags & IconPixmapHint) {
1993 self->icons = g_new(ObClientIcon, self->nicons);
1994 xerror_set_ignore(TRUE);
1995 if (!RrPixmapToRGBA(ob_rr_inst,
1997 (hints->flags & IconMaskHint ?
1998 hints->icon_mask : None),
1999 &self->icons[self->nicons-1].width,
2000 &self->icons[self->nicons-1].height,
2001 &self->icons[self->nicons-1].data)){
2002 g_free(&self->icons[self->nicons-1]);
2005 xerror_set_ignore(FALSE);
2011 /* set the default icon onto the window
2012 in theory, this could be a race, but if a window doesn't set an icon
2013 or removes it entirely, it's not very likely it is going to set one
2014 right away afterwards */
2015 if (self->nicons == 0) {
2016 RrPixel32 *icon = ob_rr_theme->def_win_icon;
2019 data = g_new(gulong, 48*48+2);
2020 data[0] = data[1] = 48;
2021 for (i = 0; i < 48*48; ++i)
2022 data[i+2] = (((icon[i] >> RrDefaultAlphaOffset) & 0xff) << 24) +
2023 (((icon[i] >> RrDefaultRedOffset) & 0xff) << 16) +
2024 (((icon[i] >> RrDefaultGreenOffset) & 0xff) << 8) +
2025 (((icon[i] >> RrDefaultBlueOffset) & 0xff) << 0);
2026 PROP_SETA32(self->window, net_wm_icon, cardinal, data, 48*48+2);
2028 } else if (self->frame)
2029 /* don't draw the icon empty if we're just setting one now anyways,
2030 we'll get the property change any second */
2031 frame_adjust_icon(self->frame);
2034 void client_update_user_time(ObClient *self)
2037 gboolean got = FALSE;
2039 if (self->user_time_window)
2040 got = PROP_GET32(self->user_time_window,
2041 net_wm_user_time, cardinal, &time);
2043 got = PROP_GET32(self->window, net_wm_user_time, cardinal, &time);
2046 /* we set this every time, not just when it grows, because in practice
2047 sometimes time goes backwards! (ntpdate.. yay....) so.. if it goes
2048 backward we don't want all windows to stop focusing. we'll just
2049 assume noone is setting times older than the last one, cuz that
2050 would be pretty stupid anyways
2052 self->user_time = time;
2054 /*ob_debug("window %s user time %u\n", self->title, time);*/
2058 void client_update_user_time_window(ObClient *self)
2063 if (!PROP_GET32(self->window, net_wm_user_time_window, window, &w))
2066 if (w != self->user_time_window) {
2067 if (self->user_time_window) {
2068 XSelectInput(ob_display, self->user_time_window, NoEventMask);
2069 g_hash_table_remove(client_user_time_window_map, &w);
2070 self->user_time_window = None;
2073 if (self->group && self->group->leader == w) {
2074 ob_debug_type(OB_DEBUG_APP_BUGS, "Window is setting its "
2075 "_NET_WM_USER_TYPE_WINDOW to its group leader\n");
2076 /* do it anyways..? */
2078 else if (w == self->window) {
2079 ob_debug_type(OB_DEBUG_APP_BUGS, "Window is setting its "
2080 "_NET_WM_USER_TIME_WINDOW to itself\n");
2081 w = None; /* don't do it */
2083 else if (((c = g_hash_table_lookup(client_user_time_window_map,&w)))) {
2084 ob_debug_type(OB_DEBUG_APP_BUGS, "Client %s is trying to use "
2085 "the _NET_WM_USER_TIME_WINDOW of %s\n",
2086 self->title, c->title);
2087 w = None; /* don't do it */
2090 self->user_time_window = w;
2091 if (self->user_time_window != None) {
2092 XSelectInput(ob_display,self->user_time_window,PropertyChangeMask);
2093 g_hash_table_insert(client_user_time_window_map,
2094 &self->user_time_window, self);
2097 client_update_user_time(self);
2101 void client_update_icon_geometry(ObClient *self)
2106 RECT_SET(self->icon_geometry, 0, 0, 0, 0);
2108 if (PROP_GETA32(self->window, net_wm_icon_geometry, cardinal, &data, &num)
2111 /* don't let them set it with an area < 0 */
2112 RECT_SET(self->icon_geometry, data[0], data[1],
2113 MAX(data[2],0), MAX(data[3],0));
2117 static void client_get_session_ids(ObClient *self)
2124 if (!PROP_GET32(self->window, wm_client_leader, window, &leader))
2127 /* get the SM_CLIENT_ID */
2130 got = PROP_GETS(leader, sm_client_id, locale, &self->sm_client_id);
2132 PROP_GETS(self->window, sm_client_id, locale, &self->sm_client_id);
2134 /* get the WM_CLASS (name and class). make them "" if they are not
2138 got = PROP_GETSS(leader, wm_class, locale, &ss);
2140 got = PROP_GETSS(self->window, wm_class, locale, &ss);
2144 self->name = g_strdup(ss[0]);
2146 self->class = g_strdup(ss[1]);
2151 if (self->name == NULL) self->name = g_strdup("");
2152 if (self->class == NULL) self->class = g_strdup("");
2154 /* get the WM_WINDOW_ROLE. make it "" if it is not provided */
2157 got = PROP_GETS(leader, wm_window_role, locale, &s);
2159 got = PROP_GETS(self->window, wm_window_role, locale, &s);
2164 self->role = g_strdup("");
2166 /* get the WM_COMMAND */
2170 got = PROP_GETSS(leader, wm_command, locale, &ss);
2172 got = PROP_GETSS(self->window, wm_command, locale, &ss);
2175 /* merge/mash them all together */
2176 gchar *merge = NULL;
2179 for (i = 0; ss[i]; ++i) {
2182 merge = g_strconcat(merge, ss[i], NULL);
2184 merge = g_strconcat(ss[i], NULL);
2189 self->wm_command = merge;
2192 /* get the WM_CLIENT_MACHINE */
2195 got = PROP_GETS(leader, wm_client_machine, locale, &s);
2197 got = PROP_GETS(self->window, wm_client_machine, locale, &s);
2200 gchar localhost[128];
2202 gethostname(localhost, 127);
2203 localhost[127] = '\0';
2204 if (strcmp(localhost, s) != 0)
2205 self->client_machine = s;
2209 static void client_change_wm_state(ObClient *self)
2214 old = self->wmstate;
2216 if (self->shaded || self->iconic ||
2217 (self->desktop != DESKTOP_ALL && self->desktop != screen_desktop))
2219 self->wmstate = IconicState;
2221 self->wmstate = NormalState;
2223 if (old != self->wmstate) {
2224 PROP_MSG(self->window, kde_wm_change_state,
2225 self->wmstate, 1, 0, 0);
2227 state[0] = self->wmstate;
2229 PROP_SETA32(self->window, wm_state, wm_state, state, 2);
2233 static void client_change_state(ObClient *self)
2235 gulong netstate[11];
2240 netstate[num++] = prop_atoms.net_wm_state_modal;
2242 netstate[num++] = prop_atoms.net_wm_state_shaded;
2244 netstate[num++] = prop_atoms.net_wm_state_hidden;
2245 if (self->skip_taskbar)
2246 netstate[num++] = prop_atoms.net_wm_state_skip_taskbar;
2247 if (self->skip_pager)
2248 netstate[num++] = prop_atoms.net_wm_state_skip_pager;
2249 if (self->fullscreen)
2250 netstate[num++] = prop_atoms.net_wm_state_fullscreen;
2252 netstate[num++] = prop_atoms.net_wm_state_maximized_vert;
2254 netstate[num++] = prop_atoms.net_wm_state_maximized_horz;
2256 netstate[num++] = prop_atoms.net_wm_state_above;
2258 netstate[num++] = prop_atoms.net_wm_state_below;
2259 if (self->demands_attention)
2260 netstate[num++] = prop_atoms.net_wm_state_demands_attention;
2261 if (self->undecorated)
2262 netstate[num++] = prop_atoms.openbox_wm_state_undecorated;
2263 PROP_SETA32(self->window, net_wm_state, atom, netstate, num);
2266 frame_adjust_state(self->frame);
2269 ObClient *client_search_focus_tree(ObClient *self)
2274 for (it = self->transients; it; it = g_slist_next(it)) {
2275 if (client_focused(it->data)) return it->data;
2276 if ((ret = client_search_focus_tree(it->data))) return ret;
2281 ObClient *client_search_focus_tree_full(ObClient *self)
2283 if (self->transient_for) {
2284 if (self->transient_for != OB_TRAN_GROUP) {
2285 return client_search_focus_tree_full(self->transient_for);
2288 gboolean recursed = FALSE;
2290 for (it = self->group->members; it; it = g_slist_next(it))
2291 if (!((ObClient*)it->data)->transient_for) {
2293 if ((c = client_search_focus_tree_full(it->data)))
2302 /* this function checks the whole tree, the client_search_focus_tree~
2303 does not, so we need to check this window */
2304 if (client_focused(self))
2306 return client_search_focus_tree(self);
2309 static ObStackingLayer calc_layer(ObClient *self)
2313 if (self->type == OB_CLIENT_TYPE_DESKTOP)
2314 l = OB_STACKING_LAYER_DESKTOP;
2315 else if (self->type == OB_CLIENT_TYPE_DOCK) {
2316 if (self->below) l = OB_STACKING_LAYER_NORMAL;
2317 else l = OB_STACKING_LAYER_ABOVE;
2319 else if ((self->fullscreen ||
2320 /* no decorations and fills the monitor = oldskool fullscreen */
2321 (self->frame != NULL &&
2322 (self->frame->size.right == 0 && self->frame->size.left == 0 &&
2323 self->frame->size.bottom == 0 && self->frame->size.top == 0 &&
2324 RECT_EQUAL(self->area,
2325 *screen_physical_area_monitor
2326 (client_monitor(self)))))) &&
2327 (client_focused(self) || client_search_focus_tree(self)))
2328 l = OB_STACKING_LAYER_FULLSCREEN;
2329 else if (self->above) l = OB_STACKING_LAYER_ABOVE;
2330 else if (self->below) l = OB_STACKING_LAYER_BELOW;
2331 else l = OB_STACKING_LAYER_NORMAL;
2336 static void client_calc_layer_recursive(ObClient *self, ObClient *orig,
2337 ObStackingLayer min, gboolean raised)
2339 ObStackingLayer old, own;
2343 own = calc_layer(self);
2344 self->layer = MAX(own, min);
2346 for (it = self->transients; it; it = g_slist_next(it))
2347 client_calc_layer_recursive(it->data, orig,
2349 raised ? raised : self->layer != old);
2351 if (!raised && self->layer != old)
2352 if (orig->frame) { /* only restack if the original window is managed */
2353 stacking_remove(CLIENT_AS_WINDOW(self));
2354 stacking_add(CLIENT_AS_WINDOW(self));
2358 void client_calc_layer(ObClient *self)
2365 /* transients take on the layer of their parents */
2366 it = client_search_all_top_parents(self);
2368 for (; it; it = g_slist_next(it))
2369 client_calc_layer_recursive(it->data, orig, 0, FALSE);
2372 gboolean client_should_show(ObClient *self)
2376 if (client_normal(self) && screen_showing_desktop)
2378 if (self->desktop == screen_desktop || self->desktop == DESKTOP_ALL)
2384 void client_show(ObClient *self)
2387 if (client_should_show(self)) {
2388 frame_show(self->frame);
2391 /* According to the ICCCM (sec 4.1.3.1) when a window is not visible, it
2392 needs to be in IconicState. This includes when it is on another
2395 client_change_wm_state(self);
2398 void client_hide(ObClient *self)
2400 if (!client_should_show(self)) {
2401 frame_hide(self->frame);
2404 /* According to the ICCCM (sec 4.1.3.1) when a window is not visible, it
2405 needs to be in IconicState. This includes when it is on another
2408 client_change_wm_state(self);
2411 void client_showhide(ObClient *self)
2414 if (client_should_show(self)) {
2415 frame_show(self->frame);
2418 frame_hide(self->frame);
2421 /* According to the ICCCM (sec 4.1.3.1) when a window is not visible, it
2422 needs to be in IconicState. This includes when it is on another
2425 client_change_wm_state(self);
2428 gboolean client_normal(ObClient *self) {
2429 return ! (self->type == OB_CLIENT_TYPE_DESKTOP ||
2430 self->type == OB_CLIENT_TYPE_DOCK ||
2431 self->type == OB_CLIENT_TYPE_SPLASH);
2434 gboolean client_helper(ObClient *self)
2436 return (self->type == OB_CLIENT_TYPE_UTILITY ||
2437 self->type == OB_CLIENT_TYPE_MENU ||
2438 self->type == OB_CLIENT_TYPE_TOOLBAR);
2441 gboolean client_mouse_focusable(ObClient *self)
2443 return !(self->type == OB_CLIENT_TYPE_MENU ||
2444 self->type == OB_CLIENT_TYPE_TOOLBAR ||
2445 self->type == OB_CLIENT_TYPE_SPLASH ||
2446 self->type == OB_CLIENT_TYPE_DOCK);
2449 gboolean client_enter_focusable(ObClient *self)
2451 /* you can focus desktops but it shouldn't on enter */
2452 return (client_mouse_focusable(self) &&
2453 self->type != OB_CLIENT_TYPE_DESKTOP);
2457 static void client_apply_startup_state(ObClient *self, gint x, gint y)
2459 gboolean pos = FALSE; /* has the window's position been configured? */
2462 /* save the position, and set self->area for these to use */
2468 /* set the desktop hint, to make sure that it always exists */
2469 PROP_SET32(self->window, net_wm_desktop, cardinal, self->desktop);
2471 /* these are in a carefully crafted order.. */
2474 self->iconic = FALSE;
2475 client_iconify(self, TRUE, FALSE);
2477 if (self->fullscreen) {
2478 self->fullscreen = FALSE;
2479 client_fullscreen(self, TRUE);
2482 if (self->undecorated) {
2483 self->undecorated = FALSE;
2484 client_set_undecorated(self, TRUE);
2487 self->shaded = FALSE;
2488 client_shade(self, TRUE);
2490 if (self->demands_attention) {
2491 self->demands_attention = FALSE;
2492 client_hilite(self, TRUE);
2495 if (self->max_vert && self->max_horz) {
2496 self->max_vert = self->max_horz = FALSE;
2497 client_maximize(self, TRUE, 0);
2499 } else if (self->max_vert) {
2500 self->max_vert = FALSE;
2501 client_maximize(self, TRUE, 2);
2503 } else if (self->max_horz) {
2504 self->max_horz = FALSE;
2505 client_maximize(self, TRUE, 1);
2509 /* if the client didn't get positioned yet, then do so now.
2510 call client_move even if the window is not being moved anywhere, because
2511 when we reparent it and decorate it, it is getting moved and we need to
2512 be telling it so with a ConfigureNotify event.
2515 /* use the saved position */
2518 client_move(self, x, y);
2521 /* nothing to do for the other states:
2530 void client_convert_gravity(ObClient *self, gint gravity, gint *x, gint *y,
2533 gint oldg = self->gravity;
2535 /* get the frame's position from the requested stuff */
2536 self->gravity = gravity;
2537 frame_client_gravity(self->frame, x, y, w, h);
2538 self->gravity = oldg;
2540 /* get the client's position in its true gravity from that */
2541 frame_frame_gravity(self->frame, x, y, w, h);
2544 void client_try_configure(ObClient *self, gint *x, gint *y, gint *w, gint *h,
2545 gint *logicalw, gint *logicalh,
2548 Rect desired_area = {*x, *y, *w, *h};
2550 /* make the frame recalculate its dimentions n shit without changing
2551 anything visible for real, this way the constraints below can work with
2552 the updated frame dimensions. */
2553 frame_adjust_area(self->frame, TRUE, TRUE, TRUE);
2555 /* work within the prefered sizes given by the window */
2556 if (!(*w == self->area.width && *h == self->area.height)) {
2557 gint basew, baseh, minw, minh;
2559 /* base size is substituted with min size if not specified */
2560 if (self->base_size.width || self->base_size.height) {
2561 basew = self->base_size.width;
2562 baseh = self->base_size.height;
2564 basew = self->min_size.width;
2565 baseh = self->min_size.height;
2567 /* min size is substituted with base size if not specified */
2568 if (self->min_size.width || self->min_size.height) {
2569 minw = self->min_size.width;
2570 minh = self->min_size.height;
2572 minw = self->base_size.width;
2573 minh = self->base_size.height;
2576 /* if this is a user-requested resize, then check against min/max
2579 /* smaller than min size or bigger than max size? */
2580 if (*w > self->max_size.width) *w = self->max_size.width;
2581 if (*w < minw) *w = minw;
2582 if (*h > self->max_size.height) *h = self->max_size.height;
2583 if (*h < minh) *h = minh;
2588 /* keep to the increments */
2589 *w /= self->size_inc.width;
2590 *h /= self->size_inc.height;
2592 /* you cannot resize to nothing */
2593 if (basew + *w < 1) *w = 1 - basew;
2594 if (baseh + *h < 1) *h = 1 - baseh;
2596 /* save the logical size */
2597 *logicalw = self->size_inc.width > 1 ? *w : *w + basew;
2598 *logicalh = self->size_inc.height > 1 ? *h : *h + baseh;
2600 *w *= self->size_inc.width;
2601 *h *= self->size_inc.height;
2606 /* adjust the height to match the width for the aspect ratios.
2607 for this, min size is not substituted for base size ever. */
2608 *w -= self->base_size.width;
2609 *h -= self->base_size.height;
2611 if (!self->fullscreen) {
2612 if (self->min_ratio)
2613 if (*h * self->min_ratio > *w) {
2614 *h = (gint)(*w / self->min_ratio);
2616 /* you cannot resize to nothing */
2619 *w = (gint)(*h * self->min_ratio);
2622 if (self->max_ratio)
2623 if (*h * self->max_ratio < *w) {
2624 *h = (gint)(*w / self->max_ratio);
2626 /* you cannot resize to nothing */
2629 *w = (gint)(*h * self->min_ratio);
2634 *w += self->base_size.width;
2635 *h += self->base_size.height;
2638 /* gets the frame's position */
2639 frame_client_gravity(self->frame, x, y, *w, *h);
2641 /* these positions are frame positions, not client positions */
2643 /* set the size and position if fullscreen */
2644 if (self->fullscreen) {
2648 i = screen_find_monitor(&desired_area);
2649 a = screen_physical_area_monitor(i);
2656 user = FALSE; /* ignore if the client can't be moved/resized when it
2657 is entering fullscreen */
2658 } else if (self->max_horz || self->max_vert) {
2662 i = screen_find_monitor(&desired_area);
2663 a = screen_area_monitor(self->desktop, i);
2665 /* set the size and position if maximized */
2666 if (self->max_horz) {
2668 *w = a->width - self->frame->size.left - self->frame->size.right;
2670 if (self->max_vert) {
2672 *h = a->height - self->frame->size.top - self->frame->size.bottom;
2675 /* maximizing is not allowed if the user can't move+resize the window
2679 /* gets the client's position */
2680 frame_frame_gravity(self->frame, x, y, *w, *h);
2682 /* these override the above states! if you cant move you can't move! */
2684 if (!(self->functions & OB_CLIENT_FUNC_MOVE)) {
2688 if (!(self->functions & OB_CLIENT_FUNC_RESIZE)) {
2689 *w = self->area.width;
2690 *h = self->area.height;
2699 void client_configure_full(ObClient *self, gint x, gint y, gint w, gint h,
2700 gboolean user, gboolean final,
2701 gboolean force_reply)
2703 gint oldw, oldh, oldrx, oldry;
2704 gboolean send_resize_client;
2705 gboolean moved = FALSE, resized = FALSE, rootmoved = FALSE;
2706 guint fdecor = self->frame->decorations;
2707 gboolean fhorz = self->frame->max_horz;
2708 gint logicalw, logicalh;
2710 /* find the new x, y, width, and height (and logical size) */
2711 client_try_configure(self, &x, &y, &w, &h, &logicalw, &logicalh, user);
2713 /* set the logical size if things changed */
2714 if (!(w == self->area.width && h == self->area.height))
2715 SIZE_SET(self->logical_size, logicalw, logicalh);
2717 /* figure out if we moved or resized or what */
2718 moved = x != self->area.x || y != self->area.y;
2719 resized = w != self->area.width || h != self->area.height;
2721 oldw = self->area.width;
2722 oldh = self->area.height;
2723 RECT_SET(self->area, x, y, w, h);
2725 /* for app-requested resizes, always resize if 'resized' is true.
2726 for user-requested ones, only resize if final is true, or when
2727 resizing in redraw mode */
2728 send_resize_client = ((!user && resized) ||
2730 (resized && config_resize_redraw))));
2732 /* if the client is enlarging, then resize the client before the frame */
2733 if (send_resize_client && user && (w > oldw || h > oldh)) {
2734 XResizeWindow(ob_display, self->window, MAX(w, oldw), MAX(h, oldh));
2735 /* resize the plate to show the client padding color underneath */
2736 frame_adjust_client_area(self->frame);
2739 /* find the frame's dimensions and move/resize it */
2740 if (self->decorations != fdecor || self->max_horz != fhorz)
2741 moved = resized = TRUE;
2742 if (moved || resized)
2743 frame_adjust_area(self->frame, moved, resized, FALSE);
2745 /* find the client's position relative to the root window */
2746 oldrx = self->root_pos.x;
2747 oldry = self->root_pos.y;
2748 rootmoved = (oldrx != (signed)(self->frame->area.x +
2749 self->frame->size.left -
2750 self->border_width) ||
2751 oldry != (signed)(self->frame->area.y +
2752 self->frame->size.top -
2753 self->border_width));
2755 if (force_reply || ((!user || (user && final)) && rootmoved))
2759 POINT_SET(self->root_pos,
2760 self->frame->area.x + self->frame->size.left -
2762 self->frame->area.y + self->frame->size.top -
2763 self->border_width);
2765 event.type = ConfigureNotify;
2766 event.xconfigure.display = ob_display;
2767 event.xconfigure.event = self->window;
2768 event.xconfigure.window = self->window;
2770 /* root window real coords */
2771 event.xconfigure.x = self->root_pos.x;
2772 event.xconfigure.y = self->root_pos.y;
2773 event.xconfigure.width = w;
2774 event.xconfigure.height = h;
2775 event.xconfigure.border_width = 0;
2776 event.xconfigure.above = self->frame->plate;
2777 event.xconfigure.override_redirect = FALSE;
2778 XSendEvent(event.xconfigure.display, event.xconfigure.window,
2779 FALSE, StructureNotifyMask, &event);
2782 /* if the client is shrinking, then resize the frame before the client */
2783 if (send_resize_client && (!user || (w <= oldw || h <= oldh))) {
2784 /* resize the plate to show the client padding color underneath */
2785 frame_adjust_client_area(self->frame);
2787 XResizeWindow(ob_display, self->window, w, h);
2793 void client_fullscreen(ObClient *self, gboolean fs)
2797 if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) || /* can't */
2798 self->fullscreen == fs) return; /* already done */
2800 self->fullscreen = fs;
2801 client_change_state(self); /* change the state hints on the client */
2802 client_calc_layer(self); /* and adjust out layer/stacking */
2805 self->pre_fullscreen_area = self->area;
2806 /* if the window is maximized, its area isn't all that meaningful.
2807 save it's premax area instead. */
2808 if (self->max_horz) {
2809 self->pre_fullscreen_area.x = self->pre_max_area.x;
2810 self->pre_fullscreen_area.width = self->pre_max_area.width;
2812 if (self->max_vert) {
2813 self->pre_fullscreen_area.y = self->pre_max_area.y;
2814 self->pre_fullscreen_area.height = self->pre_max_area.height;
2817 /* these are not actually used cuz client_configure will set them
2818 as appropriate when the window is fullscreened */
2823 if (self->pre_fullscreen_area.width > 0 &&
2824 self->pre_fullscreen_area.height > 0)
2826 x = self->pre_fullscreen_area.x;
2827 y = self->pre_fullscreen_area.y;
2828 w = self->pre_fullscreen_area.width;
2829 h = self->pre_fullscreen_area.height;
2830 RECT_SET(self->pre_fullscreen_area, 0, 0, 0, 0);
2832 /* pick some fallbacks... */
2833 a = screen_area_monitor(self->desktop, 0);
2834 x = a->x + a->width / 4;
2835 y = a->y + a->height / 4;
2841 client_setup_decor_and_functions(self);
2843 client_move_resize(self, x, y, w, h);
2845 /* try focus us when we go into fullscreen mode */
2849 static void client_iconify_recursive(ObClient *self,
2850 gboolean iconic, gboolean curdesk)
2853 gboolean changed = FALSE;
2856 if (self->iconic != iconic) {
2857 ob_debug("%sconifying window: 0x%lx\n", (iconic ? "I" : "Uni"),
2861 /* don't let non-normal windows iconify along with their parents
2863 if (client_normal(self)) {
2864 self->iconic = iconic;
2866 /* update the focus lists.. iconic windows go to the bottom of
2867 the list, put the new iconic window at the 'top of the
2869 focus_order_to_top(self);
2874 self->iconic = iconic;
2876 if (curdesk && self->desktop != screen_desktop &&
2877 self->desktop != DESKTOP_ALL)
2878 client_set_desktop(self, screen_desktop, FALSE);
2880 /* this puts it after the current focused window */
2881 focus_order_remove(self);
2882 focus_order_add_new(self);
2889 client_change_state(self);
2890 if (ob_state() != OB_STATE_STARTING && config_animate_iconify)
2891 frame_begin_iconify_animation(self->frame, iconic);
2892 /* do this after starting the animation so it doesn't flash */
2893 client_showhide(self);
2896 /* iconify all direct transients, and deiconify all transients
2898 for (it = self->transients; it; it = g_slist_next(it))
2899 if (it->data != self)
2900 if (client_is_direct_child(self, it->data) || !iconic)
2901 client_iconify_recursive(it->data, iconic, curdesk);
2904 void client_iconify(ObClient *self, gboolean iconic, gboolean curdesk)
2906 if (self->functions & OB_CLIENT_FUNC_ICONIFY || !iconic) {
2907 /* move up the transient chain as far as possible first */
2908 self = client_search_top_normal_parent(self);
2909 client_iconify_recursive(self, iconic, curdesk);
2913 void client_maximize(ObClient *self, gboolean max, gint dir)
2917 g_assert(dir == 0 || dir == 1 || dir == 2);
2918 if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE)) return; /* can't */
2920 /* check if already done */
2922 if (dir == 0 && self->max_horz && self->max_vert) return;
2923 if (dir == 1 && self->max_horz) return;
2924 if (dir == 2 && self->max_vert) return;
2926 if (dir == 0 && !self->max_horz && !self->max_vert) return;
2927 if (dir == 1 && !self->max_horz) return;
2928 if (dir == 2 && !self->max_vert) return;
2931 /* we just tell it to configure in the same place and client_configure
2932 worries about filling the screen with the window */
2935 w = self->area.width;
2936 h = self->area.height;
2939 if ((dir == 0 || dir == 1) && !self->max_horz) { /* horz */
2940 RECT_SET(self->pre_max_area,
2941 self->area.x, self->pre_max_area.y,
2942 self->area.width, self->pre_max_area.height);
2944 if ((dir == 0 || dir == 2) && !self->max_vert) { /* vert */
2945 RECT_SET(self->pre_max_area,
2946 self->pre_max_area.x, self->area.y,
2947 self->pre_max_area.width, self->area.height);
2952 a = screen_area_monitor(self->desktop, 0);
2953 if ((dir == 0 || dir == 1) && self->max_horz) { /* horz */
2954 if (self->pre_max_area.width > 0) {
2955 x = self->pre_max_area.x;
2956 w = self->pre_max_area.width;
2958 RECT_SET(self->pre_max_area, 0, self->pre_max_area.y,
2959 0, self->pre_max_area.height);
2961 /* pick some fallbacks... */
2962 x = a->x + a->width / 4;
2966 if ((dir == 0 || dir == 2) && self->max_vert) { /* vert */
2967 if (self->pre_max_area.height > 0) {
2968 y = self->pre_max_area.y;
2969 h = self->pre_max_area.height;
2971 RECT_SET(self->pre_max_area, self->pre_max_area.x, 0,
2972 self->pre_max_area.width, 0);
2974 /* pick some fallbacks... */
2975 y = a->y + a->height / 4;
2981 if (dir == 0 || dir == 1) /* horz */
2982 self->max_horz = max;
2983 if (dir == 0 || dir == 2) /* vert */
2984 self->max_vert = max;
2986 client_change_state(self); /* change the state hints on the client */
2988 client_setup_decor_and_functions(self);
2990 client_move_resize(self, x, y, w, h);
2993 void client_shade(ObClient *self, gboolean shade)
2995 if ((!(self->functions & OB_CLIENT_FUNC_SHADE) &&
2996 shade) || /* can't shade */
2997 self->shaded == shade) return; /* already done */
2999 self->shaded = shade;
3000 client_change_state(self);
3001 client_change_wm_state(self); /* the window is being hidden/shown */
3002 /* resize the frame to just the titlebar */
3003 frame_adjust_area(self->frame, FALSE, FALSE, FALSE);
3006 void client_close(ObClient *self)
3010 if (!(self->functions & OB_CLIENT_FUNC_CLOSE)) return;
3012 /* in the case that the client provides no means to requesting that it
3013 close, we just kill it */
3014 if (!self->delete_window)
3018 XXX: itd be cool to do timeouts and shit here for killing the client's
3020 like... if the window is around after 5 seconds, then the close button
3021 turns a nice red, and if this function is called again, the client is
3025 ce.xclient.type = ClientMessage;
3026 ce.xclient.message_type = prop_atoms.wm_protocols;
3027 ce.xclient.display = ob_display;
3028 ce.xclient.window = self->window;
3029 ce.xclient.format = 32;
3030 ce.xclient.data.l[0] = prop_atoms.wm_delete_window;
3031 ce.xclient.data.l[1] = event_curtime;
3032 ce.xclient.data.l[2] = 0l;
3033 ce.xclient.data.l[3] = 0l;
3034 ce.xclient.data.l[4] = 0l;
3035 XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
3038 void client_kill(ObClient *self)
3040 XKillClient(ob_display, self->window);
3043 void client_hilite(ObClient *self, gboolean hilite)
3045 if (self->demands_attention == hilite)
3046 return; /* no change */
3048 /* don't allow focused windows to hilite */
3049 self->demands_attention = hilite && !client_focused(self);
3050 if (self->frame != NULL) { /* if we're mapping, just set the state */
3051 if (self->demands_attention)
3052 frame_flash_start(self->frame);
3054 frame_flash_stop(self->frame);
3055 client_change_state(self);
3059 void client_set_desktop_recursive(ObClient *self,
3066 if (target != self->desktop) {
3068 ob_debug("Setting desktop %u\n", target+1);
3070 g_assert(target < screen_num_desktops || target == DESKTOP_ALL);
3072 old = self->desktop;
3073 self->desktop = target;
3074 PROP_SET32(self->window, net_wm_desktop, cardinal, target);
3075 /* the frame can display the current desktop state */
3076 frame_adjust_state(self->frame);
3077 /* 'move' the window to the new desktop */
3079 client_showhide(self);
3080 /* raise if it was not already on the desktop */
3081 if (old != DESKTOP_ALL)
3083 if (STRUT_EXISTS(self->strut))
3084 screen_update_areas();
3087 /* move all transients */
3088 for (it = self->transients; it; it = g_slist_next(it))
3089 if (it->data != self)
3090 if (client_is_direct_child(self, it->data))
3091 client_set_desktop_recursive(it->data, target, donthide);
3094 void client_set_desktop(ObClient *self, guint target,
3097 self = client_search_top_normal_parent(self);
3098 client_set_desktop_recursive(self, target, donthide);
3101 gboolean client_is_direct_child(ObClient *parent, ObClient *child)
3103 while (child != parent &&
3104 child->transient_for && child->transient_for != OB_TRAN_GROUP)
3105 child = child->transient_for;
3106 return child == parent;
3109 ObClient *client_search_modal_child(ObClient *self)
3114 for (it = self->transients; it; it = g_slist_next(it)) {
3115 ObClient *c = it->data;
3116 if ((ret = client_search_modal_child(c))) return ret;
3117 if (c->modal) return c;
3122 gboolean client_validate(ObClient *self)
3126 XSync(ob_display, FALSE); /* get all events on the server */
3128 if (XCheckTypedWindowEvent(ob_display, self->window, DestroyNotify, &e) ||
3129 XCheckTypedWindowEvent(ob_display, self->window, UnmapNotify, &e)) {
3130 XPutBackEvent(ob_display, &e);
3137 void client_set_wm_state(ObClient *self, glong state)
3139 if (state == self->wmstate) return; /* no change */
3143 client_iconify(self, TRUE, TRUE);
3146 client_iconify(self, FALSE, TRUE);
3151 void client_set_state(ObClient *self, Atom action, glong data1, glong data2)
3153 gboolean shaded = self->shaded;
3154 gboolean fullscreen = self->fullscreen;
3155 gboolean undecorated = self->undecorated;
3156 gboolean max_horz = self->max_horz;
3157 gboolean max_vert = self->max_vert;
3158 gboolean modal = self->modal;
3159 gboolean iconic = self->iconic;
3160 gboolean demands_attention = self->demands_attention;
3161 gboolean above = self->above;
3162 gboolean below = self->below;
3165 if (!(action == prop_atoms.net_wm_state_add ||
3166 action == prop_atoms.net_wm_state_remove ||
3167 action == prop_atoms.net_wm_state_toggle))
3168 /* an invalid action was passed to the client message, ignore it */
3171 for (i = 0; i < 2; ++i) {
3172 Atom state = i == 0 ? data1 : data2;
3174 if (!state) continue;
3176 /* if toggling, then pick whether we're adding or removing */
3177 if (action == prop_atoms.net_wm_state_toggle) {
3178 if (state == prop_atoms.net_wm_state_modal)
3179 action = modal ? prop_atoms.net_wm_state_remove :
3180 prop_atoms.net_wm_state_add;
3181 else if (state == prop_atoms.net_wm_state_maximized_vert)
3182 action = self->max_vert ? prop_atoms.net_wm_state_remove :
3183 prop_atoms.net_wm_state_add;
3184 else if (state == prop_atoms.net_wm_state_maximized_horz)
3185 action = self->max_horz ? prop_atoms.net_wm_state_remove :
3186 prop_atoms.net_wm_state_add;
3187 else if (state == prop_atoms.net_wm_state_shaded)
3188 action = shaded ? prop_atoms.net_wm_state_remove :
3189 prop_atoms.net_wm_state_add;
3190 else if (state == prop_atoms.net_wm_state_skip_taskbar)
3191 action = self->skip_taskbar ?
3192 prop_atoms.net_wm_state_remove :
3193 prop_atoms.net_wm_state_add;
3194 else if (state == prop_atoms.net_wm_state_skip_pager)
3195 action = self->skip_pager ?
3196 prop_atoms.net_wm_state_remove :
3197 prop_atoms.net_wm_state_add;
3198 else if (state == prop_atoms.net_wm_state_hidden)
3199 action = self->iconic ?
3200 prop_atoms.net_wm_state_remove :
3201 prop_atoms.net_wm_state_add;
3202 else if (state == prop_atoms.net_wm_state_fullscreen)
3203 action = fullscreen ?
3204 prop_atoms.net_wm_state_remove :
3205 prop_atoms.net_wm_state_add;
3206 else if (state == prop_atoms.net_wm_state_above)
3207 action = self->above ? prop_atoms.net_wm_state_remove :
3208 prop_atoms.net_wm_state_add;
3209 else if (state == prop_atoms.net_wm_state_below)
3210 action = self->below ? prop_atoms.net_wm_state_remove :
3211 prop_atoms.net_wm_state_add;
3212 else if (state == prop_atoms.net_wm_state_demands_attention)
3213 action = self->demands_attention ?
3214 prop_atoms.net_wm_state_remove :
3215 prop_atoms.net_wm_state_add;
3216 else if (state == prop_atoms.openbox_wm_state_undecorated)
3217 action = undecorated ? prop_atoms.net_wm_state_remove :
3218 prop_atoms.net_wm_state_add;
3221 if (action == prop_atoms.net_wm_state_add) {
3222 if (state == prop_atoms.net_wm_state_modal) {
3224 } else if (state == prop_atoms.net_wm_state_maximized_vert) {
3226 } else if (state == prop_atoms.net_wm_state_maximized_horz) {
3228 } else if (state == prop_atoms.net_wm_state_shaded) {
3230 } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
3231 self->skip_taskbar = TRUE;
3232 } else if (state == prop_atoms.net_wm_state_skip_pager) {
3233 self->skip_pager = TRUE;
3234 } else if (state == prop_atoms.net_wm_state_hidden) {
3236 } else if (state == prop_atoms.net_wm_state_fullscreen) {
3238 } else if (state == prop_atoms.net_wm_state_above) {
3241 } else if (state == prop_atoms.net_wm_state_below) {
3244 } else if (state == prop_atoms.net_wm_state_demands_attention) {
3245 demands_attention = TRUE;
3246 } else if (state == prop_atoms.openbox_wm_state_undecorated) {
3250 } else { /* action == prop_atoms.net_wm_state_remove */
3251 if (state == prop_atoms.net_wm_state_modal) {
3253 } else if (state == prop_atoms.net_wm_state_maximized_vert) {
3255 } else if (state == prop_atoms.net_wm_state_maximized_horz) {
3257 } else if (state == prop_atoms.net_wm_state_shaded) {
3259 } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
3260 self->skip_taskbar = FALSE;
3261 } else if (state == prop_atoms.net_wm_state_skip_pager) {
3262 self->skip_pager = FALSE;
3263 } else if (state == prop_atoms.net_wm_state_hidden) {
3265 } else if (state == prop_atoms.net_wm_state_fullscreen) {
3267 } else if (state == prop_atoms.net_wm_state_above) {
3269 } else if (state == prop_atoms.net_wm_state_below) {
3271 } else if (state == prop_atoms.net_wm_state_demands_attention) {
3272 demands_attention = FALSE;
3273 } else if (state == prop_atoms.openbox_wm_state_undecorated) {
3274 undecorated = FALSE;
3278 if (max_horz != self->max_horz || max_vert != self->max_vert) {
3279 if (max_horz != self->max_horz && max_vert != self->max_vert) {
3281 if (max_horz == max_vert) { /* both going the same way */
3282 client_maximize(self, max_horz, 0);
3284 client_maximize(self, max_horz, 1);
3285 client_maximize(self, max_vert, 2);
3289 if (max_horz != self->max_horz)
3290 client_maximize(self, max_horz, 1);
3292 client_maximize(self, max_vert, 2);
3295 /* change fullscreen state before shading, as it will affect if the window
3297 if (fullscreen != self->fullscreen)
3298 client_fullscreen(self, fullscreen);
3299 if (shaded != self->shaded)
3300 client_shade(self, shaded);
3301 if (undecorated != self->undecorated)
3302 client_set_undecorated(self, undecorated);
3303 if (modal != self->modal) {
3304 self->modal = modal;
3305 /* when a window changes modality, then its stacking order with its
3306 transients needs to change */
3309 if (iconic != self->iconic)
3310 client_iconify(self, iconic, FALSE);
3312 if (demands_attention != self->demands_attention)
3313 client_hilite(self, demands_attention);
3315 if (above != self->above || below != self->below) {
3316 self->above = above;
3317 self->below = below;
3318 client_calc_layer(self);
3321 client_change_state(self); /* change the hint to reflect these changes */
3324 ObClient *client_focus_target(ObClient *self)
3326 ObClient *child = NULL;
3328 child = client_search_modal_child(self);
3329 if (child) return child;
3333 gboolean client_can_focus(ObClient *self)
3337 /* choose the correct target */
3338 self = client_focus_target(self);
3340 if (!self->frame->visible)
3343 if (!(self->can_focus || self->focus_notify))
3346 /* do a check to see if the window has already been unmapped or destroyed
3347 do this intelligently while watching out for unmaps we've generated
3348 (ignore_unmaps > 0) */
3349 if (XCheckTypedWindowEvent(ob_display, self->window,
3350 DestroyNotify, &ev)) {
3351 XPutBackEvent(ob_display, &ev);
3354 while (XCheckTypedWindowEvent(ob_display, self->window,
3355 UnmapNotify, &ev)) {
3356 if (self->ignore_unmaps) {
3357 self->ignore_unmaps--;
3359 XPutBackEvent(ob_display, &ev);
3367 gboolean client_focus(ObClient *self)
3369 /* choose the correct target */
3370 self = client_focus_target(self);
3372 if (!client_can_focus(self)) {
3373 if (!self->frame->visible) {
3374 /* update the focus lists */
3375 focus_order_to_top(self);
3380 ob_debug_type(OB_DEBUG_FOCUS,
3381 "Focusing client \"%s\" at time %u\n",
3382 self->title, event_curtime);
3384 if (self->can_focus) {
3385 /* This can cause a BadMatch error with CurrentTime, or if an app
3386 passed in a bad time for _NET_WM_ACTIVE_WINDOW. */
3387 xerror_set_ignore(TRUE);
3388 XSetInputFocus(ob_display, self->window, RevertToPointerRoot,
3390 xerror_set_ignore(FALSE);
3393 if (self->focus_notify) {
3395 ce.xclient.type = ClientMessage;
3396 ce.xclient.message_type = prop_atoms.wm_protocols;
3397 ce.xclient.display = ob_display;
3398 ce.xclient.window = self->window;
3399 ce.xclient.format = 32;
3400 ce.xclient.data.l[0] = prop_atoms.wm_take_focus;
3401 ce.xclient.data.l[1] = event_curtime;
3402 ce.xclient.data.l[2] = 0l;
3403 ce.xclient.data.l[3] = 0l;
3404 ce.xclient.data.l[4] = 0l;
3405 XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
3409 ob_debug("%sively focusing %lx at %d\n",
3410 (self->can_focus ? "act" : "pass"),
3411 self->window, (gint) event_curtime);
3414 /* Cause the FocusIn to come back to us. Important for desktop switches,
3415 since otherwise we'll have no FocusIn on the queue and send it off to
3416 the focus_backup. */
3417 XSync(ob_display, FALSE);
3421 /*! Present the client to the user.
3422 @param raise If the client should be raised or not. You should only set
3423 raise to false if you don't care if the window is completely
3426 static void client_present(ObClient *self, gboolean here, gboolean raise)
3428 /* if using focus_delay, stop the timer now so that focus doesn't
3430 event_halt_focus_delay();
3432 if (client_normal(self) && screen_showing_desktop)
3433 screen_show_desktop(FALSE, FALSE);
3435 client_iconify(self, FALSE, here);
3436 if (self->desktop != DESKTOP_ALL &&
3437 self->desktop != screen_desktop)
3440 client_set_desktop(self, screen_desktop, FALSE);
3442 screen_set_desktop(self->desktop, FALSE);
3443 } else if (!self->frame->visible)
3444 /* if its not visible for other reasons, then don't mess
3448 client_shade(self, FALSE);
3453 /* we do this as an action here. this is rather important. this is
3454 because we want the results from the focus change to take place
3455 BEFORE we go about raising the window. when a fullscreen window
3456 loses focus, we need this or else the raise wont be able to raise
3457 above the to-lose-focus fullscreen window. */
3462 void client_activate(ObClient *self, gboolean here, gboolean user)
3464 guint32 last_time = focus_client ? focus_client->user_time : CurrentTime;
3465 gboolean allow = FALSE;
3467 /* if the request came from the user, or if nothing is focused, then grant
3469 if the currently focused app doesn't set a user_time, then it can't
3470 benefit from any focus stealing prevention.
3472 if (user || !focus_client || !last_time)
3474 /* otherwise, if they didn't give a time stamp or if it is too old, they
3477 allow = event_curtime && event_time_after(event_curtime, last_time);
3479 ob_debug_type(OB_DEBUG_FOCUS,
3480 "Want to activate window 0x%x with time %u (last time %u), "
3481 "source=%s allowing? %d\n",
3482 self->window, event_curtime, last_time,
3483 (user ? "user" : "application"), allow);
3486 if (event_curtime != CurrentTime)
3487 self->user_time = event_curtime;
3489 client_present(self, here, TRUE);
3491 /* don't focus it but tell the user it wants attention */
3492 client_hilite(self, TRUE);
3495 static void client_bring_helper_windows_recursive(ObClient *self,
3500 for (it = self->transients; it; it = g_slist_next(it))
3501 client_bring_helper_windows_recursive(it->data, desktop);
3503 if (client_helper(self) &&
3504 self->desktop != desktop && self->desktop != DESKTOP_ALL)
3506 client_set_desktop(self, desktop, FALSE);
3510 void client_bring_helper_windows(ObClient *self)
3512 client_bring_helper_windows_recursive(self, self->desktop);
3515 void client_raise(ObClient *self)
3517 action_run_string("Raise", self, CurrentTime);
3520 void client_lower(ObClient *self)
3522 action_run_string("Lower", self, CurrentTime);
3525 gboolean client_focused(ObClient *self)
3527 return self == focus_client;
3530 static ObClientIcon* client_icon_recursive(ObClient *self, gint w, gint h)
3533 /* si is the smallest image >= req */
3534 /* li is the largest image < req */
3535 gulong size, smallest = 0xffffffff, largest = 0, si = 0, li = 0;
3537 if (!self->nicons) {
3538 ObClientIcon *parent = NULL;
3540 if (self->transient_for) {
3541 if (self->transient_for != OB_TRAN_GROUP)
3542 parent = client_icon_recursive(self->transient_for, w, h);
3545 for (it = self->group->members; it; it = g_slist_next(it)) {
3546 ObClient *c = it->data;
3547 if (c != self && !c->transient_for) {
3548 if ((parent = client_icon_recursive(c, w, h)))
3558 for (i = 0; i < self->nicons; ++i) {
3559 size = self->icons[i].width * self->icons[i].height;
3560 if (size < smallest && size >= (unsigned)(w * h)) {
3564 if (size > largest && size <= (unsigned)(w * h)) {
3569 if (largest == 0) /* didnt find one smaller than the requested size */
3570 return &self->icons[si];
3571 return &self->icons[li];
3574 const ObClientIcon* client_icon(ObClient *self, gint w, gint h)
3577 static ObClientIcon deficon;
3579 if (!(ret = client_icon_recursive(self, w, h))) {
3580 deficon.width = deficon.height = 48;
3581 deficon.data = ob_rr_theme->def_win_icon;
3587 void client_set_layer(ObClient *self, gint layer)
3591 self->above = FALSE;
3592 } else if (layer == 0) {
3593 self->below = self->above = FALSE;
3595 self->below = FALSE;
3598 client_calc_layer(self);
3599 client_change_state(self); /* reflect this in the state hints */
3602 void client_set_undecorated(ObClient *self, gboolean undecorated)
3604 if (self->undecorated != undecorated) {
3605 self->undecorated = undecorated;
3606 client_setup_decor_and_functions(self);
3607 /* Make sure the client knows it might have moved. Maybe there is a
3608 * better way of doing this so only one client_configure is sent, but
3609 * since 125 of these are sent per second when moving the window (with
3610 * user = FALSE) i doubt it matters much.
3612 client_configure(self, self->area.x, self->area.y,
3613 self->area.width, self->area.height, TRUE, TRUE);
3614 client_change_state(self); /* reflect this in the state hints */
3618 guint client_monitor(ObClient *self)
3620 return screen_find_monitor(&self->frame->area);
3623 ObClient *client_search_top_normal_parent(ObClient *self)
3625 while (self->transient_for && self->transient_for != OB_TRAN_GROUP &&
3626 client_normal(self->transient_for))
3627 self = self->transient_for;
3631 static GSList *client_search_all_top_parents_internal(ObClient *self,
3633 ObStackingLayer layer)
3637 /* move up the direct transient chain as far as possible */
3638 while (self->transient_for && self->transient_for != OB_TRAN_GROUP &&
3639 (!bylayer || self->transient_for->layer == layer) &&
3640 client_normal(self->transient_for))
3641 self = self->transient_for;
3643 if (!self->transient_for)
3644 ret = g_slist_prepend(ret, self);
3648 g_assert(self->group);
3650 for (it = self->group->members; it; it = g_slist_next(it)) {
3651 ObClient *c = it->data;
3653 if (!c->transient_for && client_normal(c) &&
3654 (!bylayer || c->layer == layer))
3656 ret = g_slist_prepend(ret, c);
3660 if (ret == NULL) /* no group parents */
3661 ret = g_slist_prepend(ret, self);
3667 GSList *client_search_all_top_parents(ObClient *self)
3669 return client_search_all_top_parents_internal(self, FALSE, 0);
3672 GSList *client_search_all_top_parents_layer(ObClient *self)
3674 return client_search_all_top_parents_internal(self, TRUE, self->layer);
3677 ObClient *client_search_focus_parent(ObClient *self)
3679 if (self->transient_for) {
3680 if (self->transient_for != OB_TRAN_GROUP) {
3681 if (client_focused(self->transient_for))
3682 return self->transient_for;
3686 for (it = self->group->members; it; it = g_slist_next(it)) {
3687 ObClient *c = it->data;
3689 /* checking transient_for prevents infinate loops! */
3690 if (c != self && !c->transient_for)
3691 if (client_focused(c))
3700 ObClient *client_search_parent(ObClient *self, ObClient *search)
3702 if (self->transient_for) {
3703 if (self->transient_for != OB_TRAN_GROUP) {
3704 if (self->transient_for == search)
3709 for (it = self->group->members; it; it = g_slist_next(it)) {
3710 ObClient *c = it->data;
3712 /* checking transient_for prevents infinate loops! */
3713 if (c != self && !c->transient_for)
3723 ObClient *client_search_transient(ObClient *self, ObClient *search)
3727 for (sit = self->transients; sit; sit = g_slist_next(sit)) {
3728 if (sit->data == search)
3730 if (client_search_transient(sit->data, search))
3736 #define WANT_EDGE(cur, c) \
3739 if(!client_normal(cur)) \
3741 if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL) \
3745 if(cur->layer < c->layer && !config_resist_layers_below) \
3748 #define HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end) \
3749 if ((his_edge_start >= my_edge_start && \
3750 his_edge_start <= my_edge_end) || \
3751 (my_edge_start >= his_edge_start && \
3752 my_edge_start <= his_edge_end)) \
3755 /* finds the nearest edge in the given direction from the current client
3756 * note to self: the edge is the -frame- edge (the actual one), not the
3759 gint client_directional_edge_search(ObClient *c, ObDirection dir, gboolean hang)
3761 gint dest, monitor_dest;
3762 gint my_edge_start, my_edge_end, my_offset;
3769 a = screen_area(c->desktop);
3770 monitor = screen_area_monitor(c->desktop, client_monitor(c));
3773 case OB_DIRECTION_NORTH:
3774 my_edge_start = c->frame->area.x;
3775 my_edge_end = c->frame->area.x + c->frame->area.width;
3776 my_offset = c->frame->area.y + (hang ? c->frame->area.height : 0);
3778 /* default: top of screen */
3779 dest = a->y + (hang ? c->frame->area.height : 0);
3780 monitor_dest = monitor->y + (hang ? c->frame->area.height : 0);
3781 /* if the monitor edge comes before the screen edge, */
3782 /* use that as the destination instead. (For xinerama) */
3783 if (monitor_dest != dest && my_offset > monitor_dest)
3784 dest = monitor_dest;
3786 for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3787 gint his_edge_start, his_edge_end, his_offset;
3788 ObClient *cur = it->data;
3792 his_edge_start = cur->frame->area.x;
3793 his_edge_end = cur->frame->area.x + cur->frame->area.width;
3794 his_offset = cur->frame->area.y +
3795 (hang ? 0 : cur->frame->area.height);
3797 if(his_offset + 1 > my_offset)
3800 if(his_offset < dest)
3803 HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3806 case OB_DIRECTION_SOUTH:
3807 my_edge_start = c->frame->area.x;
3808 my_edge_end = c->frame->area.x + c->frame->area.width;
3809 my_offset = c->frame->area.y + (hang ? 0 : c->frame->area.height);
3811 /* default: bottom of screen */
3812 dest = a->y + a->height - (hang ? c->frame->area.height : 0);
3813 monitor_dest = monitor->y + monitor->height -
3814 (hang ? c->frame->area.height : 0);
3815 /* if the monitor edge comes before the screen edge, */
3816 /* use that as the destination instead. (For xinerama) */
3817 if (monitor_dest != dest && my_offset < monitor_dest)
3818 dest = monitor_dest;
3820 for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3821 gint his_edge_start, his_edge_end, his_offset;
3822 ObClient *cur = it->data;
3826 his_edge_start = cur->frame->area.x;
3827 his_edge_end = cur->frame->area.x + cur->frame->area.width;
3828 his_offset = cur->frame->area.y +
3829 (hang ? cur->frame->area.height : 0);
3832 if(his_offset - 1 < my_offset)
3835 if(his_offset > dest)
3838 HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3841 case OB_DIRECTION_WEST:
3842 my_edge_start = c->frame->area.y;
3843 my_edge_end = c->frame->area.y + c->frame->area.height;
3844 my_offset = c->frame->area.x + (hang ? c->frame->area.width : 0);
3846 /* default: leftmost egde of screen */
3847 dest = a->x + (hang ? c->frame->area.width : 0);
3848 monitor_dest = monitor->x + (hang ? c->frame->area.width : 0);
3849 /* if the monitor edge comes before the screen edge, */
3850 /* use that as the destination instead. (For xinerama) */
3851 if (monitor_dest != dest && my_offset > monitor_dest)
3852 dest = monitor_dest;
3854 for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3855 gint his_edge_start, his_edge_end, his_offset;
3856 ObClient *cur = it->data;
3860 his_edge_start = cur->frame->area.y;
3861 his_edge_end = cur->frame->area.y + cur->frame->area.height;
3862 his_offset = cur->frame->area.x +
3863 (hang ? 0 : cur->frame->area.width);
3865 if(his_offset + 1 > my_offset)
3868 if(his_offset < dest)
3871 HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3874 case OB_DIRECTION_EAST:
3875 my_edge_start = c->frame->area.y;
3876 my_edge_end = c->frame->area.y + c->frame->area.height;
3877 my_offset = c->frame->area.x + (hang ? 0 : c->frame->area.width);
3879 /* default: rightmost edge of screen */
3880 dest = a->x + a->width - (hang ? c->frame->area.width : 0);
3881 monitor_dest = monitor->x + monitor->width -
3882 (hang ? c->frame->area.width : 0);
3883 /* if the monitor edge comes before the screen edge, */
3884 /* use that as the destination instead. (For xinerama) */
3885 if (monitor_dest != dest && my_offset < monitor_dest)
3886 dest = monitor_dest;
3888 for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3889 gint his_edge_start, his_edge_end, his_offset;
3890 ObClient *cur = it->data;
3894 his_edge_start = cur->frame->area.y;
3895 his_edge_end = cur->frame->area.y + cur->frame->area.height;
3896 his_offset = cur->frame->area.x +
3897 (hang ? cur->frame->area.width : 0);
3899 if(his_offset - 1 < my_offset)
3902 if(his_offset > dest)
3905 HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3908 case OB_DIRECTION_NORTHEAST:
3909 case OB_DIRECTION_SOUTHEAST:
3910 case OB_DIRECTION_NORTHWEST:
3911 case OB_DIRECTION_SOUTHWEST:
3912 /* not implemented */
3914 g_assert_not_reached();
3915 dest = 0; /* suppress warning */
3920 ObClient* client_under_pointer()
3924 ObClient *ret = NULL;
3926 if (screen_pointer_pos(&x, &y)) {
3927 for (it = stacking_list; it; it = g_list_next(it)) {
3928 if (WINDOW_IS_CLIENT(it->data)) {
3929 ObClient *c = WINDOW_AS_CLIENT(it->data);
3930 if (c->frame->visible &&
3931 /* ignore all animating windows */
3932 !frame_iconify_animating(c->frame) &&
3933 RECT_CONTAINS(c->frame->area, x, y))
3944 gboolean client_has_group_siblings(ObClient *self)
3946 return self->group && self->group->members->next;