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"
40 #include "menuframe.h"
43 #include "render/render.h"
50 #include <X11/Xutil.h>
52 /*! The event mask to grab on client windows */
53 #define CLIENT_EVENTMASK (PropertyChangeMask | StructureNotifyMask | \
56 #define CLIENT_NOPROPAGATEMASK (ButtonPressMask | ButtonReleaseMask | \
61 ObClientCallback func;
65 GList *client_list = NULL;
67 static GSList *client_destroy_notifies = NULL;
69 static void client_get_all(ObClient *self, gboolean real);
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_shaped(ObClient *self);
77 static void client_get_mwm_hints(ObClient *self);
78 static void client_get_colormap(ObClient *self);
79 static void client_change_allowed_actions(ObClient *self);
80 static void client_change_state(ObClient *self);
81 static void client_change_wm_state(ObClient *self);
82 static void client_apply_startup_state(ObClient *self);
83 static void client_restore_session_state(ObClient *self);
84 static gboolean client_restore_session_stacking(ObClient *self);
85 static ObAppSettings *client_get_settings_state(ObClient *self);
86 static void client_update_transient_tree(ObClient *self,
87 ObGroup *oldgroup, ObGroup *newgroup,
90 static void client_present(ObClient *self, gboolean here, gboolean raise);
91 static GSList *client_search_all_top_parents_internal(ObClient *self,
93 ObStackingLayer layer);
94 static void client_call_notifies(ObClient *self, GSList *list);
96 void client_startup(gboolean reconfig)
103 void client_shutdown(gboolean reconfig)
105 if (reconfig) return;
108 static void client_call_notifies(ObClient *self, GSList *list)
112 for (it = list; it; it = g_slist_next(it)) {
113 ClientCallback *d = it->data;
114 d->func(self, d->data);
118 void client_add_destroy_notify(ObClientCallback func, gpointer data)
120 ClientCallback *d = g_new(ClientCallback, 1);
123 client_destroy_notifies = g_slist_prepend(client_destroy_notifies, d);
126 void client_remove_destroy_notify(ObClientCallback func)
130 for (it = client_destroy_notifies; it; it = g_slist_next(it)) {
131 ClientCallback *d = it->data;
132 if (d->func == func) {
134 client_destroy_notifies =
135 g_slist_delete_link(client_destroy_notifies, it);
141 void client_set_list()
143 Window *windows, *win_it;
145 guint size = g_list_length(client_list);
147 /* create an array of the window ids */
149 windows = g_new(Window, size);
151 for (it = client_list; it; it = g_list_next(it), ++win_it)
152 *win_it = ((ObClient*)it->data)->window;
156 PROP_SETA32(RootWindow(ob_display, ob_screen),
157 net_client_list, window, (gulong*)windows, size);
166 void client_foreach_transient(ObClient *self, ObClientForeachFunc func, gpointer data)
170 for (it = self->transients; it; it = g_slist_next(it)) {
171 if (!func(it->data, data)) return;
172 client_foreach_transient(it->data, func, data);
176 void client_foreach_ancestor(ObClient *self, ObClientForeachFunc func, gpointer data)
178 if (self->transient_for) {
179 if (self->transient_for != OB_TRAN_GROUP) {
180 if (!func(self->transient_for, data)) return;
181 client_foreach_ancestor(self->transient_for, func, data);
185 for (it = self->group->members; it; it = g_slist_next(it))
186 if (it->data != self &&
187 !((ObClient*)it->data)->transient_for) {
188 if (!func(it->data, data)) return;
189 client_foreach_ancestor(it->data, func, data);
196 void client_manage_all()
201 XWindowAttributes attrib;
203 XQueryTree(ob_display, RootWindow(ob_display, ob_screen),
204 &w, &w, &children, &nchild);
206 /* remove all icon windows from the list */
207 for (i = 0; i < nchild; i++) {
208 if (children[i] == None) continue;
209 wmhints = XGetWMHints(ob_display, children[i]);
211 if ((wmhints->flags & IconWindowHint) &&
212 (wmhints->icon_window != children[i]))
213 for (j = 0; j < nchild; j++)
214 if (children[j] == wmhints->icon_window) {
222 for (i = 0; i < nchild; ++i) {
223 if (children[i] == None)
225 if (XGetWindowAttributes(ob_display, children[i], &attrib)) {
226 if (attrib.override_redirect) continue;
228 if (attrib.map_state != IsUnmapped)
229 client_manage(children[i]);
235 void client_manage(Window window)
239 XWindowAttributes attrib;
240 XSetWindowAttributes attrib_set;
242 gboolean activate = FALSE;
243 ObAppSettings *settings;
248 /* check if it has already been unmapped by the time we started
249 mapping. the grab does a sync so we don't have to here */
250 if (XCheckTypedWindowEvent(ob_display, window, DestroyNotify, &e) ||
251 XCheckTypedWindowEvent(ob_display, window, UnmapNotify, &e))
253 XPutBackEvent(ob_display, &e);
255 ob_debug("Trying to manage unmapped window. Aborting that.\n");
257 return; /* don't manage it */
260 /* make sure it isn't an override-redirect window */
261 if (!XGetWindowAttributes(ob_display, window, &attrib) ||
262 attrib.override_redirect)
265 return; /* don't manage it */
268 /* is the window a docking app */
269 if ((wmhint = XGetWMHints(ob_display, window))) {
270 if ((wmhint->flags & StateHint) &&
271 wmhint->initial_state == WithdrawnState)
273 dock_add(window, wmhint);
281 ob_debug("Managing window: %lx\n", window);
283 /* choose the events we want to receive on the CLIENT window */
284 attrib_set.event_mask = CLIENT_EVENTMASK;
285 attrib_set.do_not_propagate_mask = CLIENT_NOPROPAGATEMASK;
286 XChangeWindowAttributes(ob_display, window,
287 CWEventMask|CWDontPropagate, &attrib_set);
289 /* create the ObClient struct, and populate it from the hints on the
291 self = g_new0(ObClient, 1);
292 self->obwin.type = Window_Client;
293 self->window = window;
295 /* non-zero defaults */
296 self->wmstate = WithdrawnState; /* make sure it gets updated first time */
297 self->gravity = NorthWestGravity;
298 self->desktop = screen_num_desktops; /* always an invalid value */
299 self->user_time = focus_client ? focus_client->user_time : CurrentTime;
301 /* get all the stuff off the window */
302 client_get_all(self, TRUE);
304 /* specify that if we exit, the window should not be destroyed and
305 should be reparented back to root automatically */
306 XChangeSaveSet(ob_display, window, SetModeInsert);
308 /* create the decoration frame for the client window */
309 self->frame = frame_new(self);
311 frame_grab_client(self->frame);
313 /* we've grabbed everything and set everything that we need to at mapping
317 /* per-app settings override stuff from client_get_all, and return the
318 settings for other uses too. the returned settings is a shallow copy,
319 that needs to be freed with g_free(). */
320 settings = client_get_settings_state(self);
321 /* the session should get the last say thought */
322 client_restore_session_state(self);
324 /* now we have all of the window's information so we can set this up */
325 client_setup_decor_and_functions(self);
327 /* remove the client's border (and adjust re gravity) */
328 client_toggle_border(self, FALSE);
331 Time t = sn_app_started(self->startup_id, self->class);
332 if (t) self->user_time = t;
335 /* do this after we have a frame.. it uses the frame to help determine the
336 WM_STATE to apply. */
337 client_change_state(self);
339 /* add ourselves to the focus order */
340 focus_order_add_new(self);
342 /* do this to add ourselves to the stacking list in a non-intrusive way */
343 client_calc_layer(self);
345 /* focus the new window? */
346 if (ob_state() != OB_STATE_STARTING &&
347 (!self->session || self->session->focused) &&
349 /* this means focus=true for window is same as config_focus_new=true */
350 ((config_focus_new || (settings && settings->focus == 1)) ||
351 client_search_focus_parent(self)) &&
352 /* this checks for focus=false for the window */
353 (!settings || settings->focus != 0) &&
354 /* note the check against Type_Normal/Dialog, not client_normal(self),
355 which would also include other types. in this case we want more
356 strict rules for focus */
357 (self->type == OB_CLIENT_TYPE_NORMAL ||
358 self->type == OB_CLIENT_TYPE_DIALOG))
363 /* adjust the frame to the client's size before showing or placing
365 frame_adjust_area(self->frame, FALSE, TRUE, FALSE);
366 frame_adjust_client_area(self->frame);
368 /* where the frame was placed is where the window was originally */
369 placex = self->area.x;
370 placey = self->area.y;
372 /* figure out placement for the window if the window is new */
373 if (ob_state() == OB_STATE_RUNNING) {
376 ob_debug("Positioned: %s @ %d %d\n",
377 (!self->positioned ? "no" :
378 (self->positioned == PPosition ? "program specified" :
379 (self->positioned == USPosition ? "user specified" :
380 "BADNESS !?"))), self->area.x, self->area.y);
382 transient = place_client(self, &placex, &placey, settings);
384 /* if the window isn't user-positioned, then make it fit inside
385 the visible screen area on its monitor.
387 the monitor is chosen by place_client! */
388 if (!(self->positioned & USPosition)) {
389 /* make a copy to modify */
390 Rect a = *screen_area_monitor(self->desktop, client_monitor(self));
392 /* shrink by the frame's area */
393 a.width -= self->frame->size.left + self->frame->size.right;
394 a.height -= self->frame->size.top + self->frame->size.bottom;
396 /* fit the window inside the area */
397 self->area.width = MIN(self->area.width, a.width);
398 self->area.height = MIN(self->area.height, a.height);
400 ob_debug("setting window size to %dx%d\n",
401 self->area.width, self->area.height);
403 /* adjust the frame to the client's new size */
404 frame_adjust_area(self->frame, FALSE, TRUE, FALSE);
405 frame_adjust_client_area(self->frame);
408 /* make sure the window is visible. */
409 client_find_onscreen(self, &placex, &placey,
410 self->area.width, self->area.height,
411 /* non-normal clients has less rules, and
412 windows that are being restored from a
413 session do also. we can assume you want
414 it back where you saved it. Clients saying
415 they placed themselves are subjected to
416 harder rules, ones that are placed by
417 place.c or by the user are allowed partially
418 off-screen and on xinerama divides (ie,
419 it is up to the placement routines to avoid
420 the xinerama divides) */
422 (((self->positioned & PPosition) &&
423 !(self->positioned & USPosition)) &&
424 client_normal(self) &&
428 ob_debug("placing window 0x%x at %d, %d with size %d x %d\n",
429 self->window, placex, placey,
430 self->area.width, self->area.height);
432 ob_debug(" but session requested %d %d instead, overriding\n",
433 self->session->x, self->session->y);
435 /* do this after the window is placed, so the premax/prefullscreen numbers
437 also, this moves the window to the position where it has been placed
439 client_apply_startup_state(self);
441 /* move the client to its placed position, or it it's already there,
442 generate a ConfigureNotify telling the client where it is.
444 do this after adjusting the frame. otherwise it gets all weird and
445 clients don't work right
447 also do this after applying the startup state so maximize and fullscreen
448 will get the right sizes and positions if the client is starting with
451 client_configure(self, placex, placey,
452 self->area.width, self->area.height,
457 guint32 last_time = focus_client ?
458 focus_client->user_time : CurrentTime;
460 /* This is focus stealing prevention */
461 ob_debug_type(OB_DEBUG_FOCUS,
462 "Want to focus new window 0x%x with time %u "
464 self->window, self->user_time, last_time);
466 /* if it's on another desktop */
467 if (!(self->desktop == screen_desktop || self->desktop == DESKTOP_ALL)
468 && /* the timestamp is from before you changed desktops */
469 self->user_time && screen_desktop_user_time &&
470 !event_time_after(self->user_time, screen_desktop_user_time))
473 ob_debug_type(OB_DEBUG_FOCUS,
474 "Not focusing the window because its on another "
477 /* If something is focused, and it's not our parent... */
478 else if (focus_client && client_search_focus_parent(self) == NULL)
480 /* If time stamp is old, don't steal focus */
481 if (self->user_time && last_time &&
482 !event_time_after(self->user_time, last_time))
485 ob_debug_type(OB_DEBUG_FOCUS,
486 "Not focusing the window because the time is "
489 /* Don't steal focus from globally active clients.
490 I stole this idea from KWin. It seems nice.
492 if (!(focus_client->can_focus || focus_client->focus_notify)) {
494 ob_debug_type(OB_DEBUG_FOCUS,
495 "Not focusing the window because a globally "
496 "active client has focus\n");
501 ob_debug_type(OB_DEBUG_FOCUS,
502 "Focus stealing prevention activated for %s with "
503 "time %u (last time %u)\n",
504 self->title, self->user_time, last_time);
505 /* if the client isn't focused, then hilite it so the user
507 client_hilite(self, TRUE);
511 /* This may look rather odd. Well it's because new windows are added
512 to the stacking order non-intrusively. If we're not going to focus
513 the new window or hilite it, then we raise it to the top. This will
514 take affect for things that don't get focused like splash screens.
515 Also if you don't have focus_new enabled, then it's going to get
516 raised to the top. Legacy begets legacy I guess?
518 if (!client_restore_session_stacking(self))
519 stacking_raise(CLIENT_AS_WINDOW(self));
522 mouse_grab_for_client(self, TRUE);
524 /* this has to happen before we try focus the window, but we want it to
525 happen after the client's stacking has been determined or it looks bad
530 gboolean stacked = client_restore_session_stacking(self);
531 client_present(self, FALSE, !stacked);
534 /* add to client list/map */
535 client_list = g_list_append(client_list, self);
536 g_hash_table_insert(window_map, &self->window, self);
538 /* this has to happen after we're in the client_list */
539 if (STRUT_EXISTS(self->strut))
540 screen_update_areas();
542 /* update the list hints */
545 /* free the ObAppSettings shallow copy */
548 ob_debug("Managed window 0x%lx plate 0x%x (%s)\n",
549 window, self->frame->plate, self->class);
555 ObClient *client_fake_manage(Window window)
558 ObAppSettings *settings;
560 ob_debug("Pretend-managing window: %lx\n", window);
562 /* do this minimal stuff to figure out the client's decorations */
564 self = g_new0(ObClient, 1);
565 self->window = window;
567 client_get_all(self, FALSE);
568 /* per-app settings override stuff, and return the settings for other
569 uses too. this returns a shallow copy that needs to be freed */
570 settings = client_get_settings_state(self);
572 client_setup_decor_and_functions(self);
574 /* create the decoration frame for the client window and adjust its size */
575 self->frame = frame_new(self);
576 frame_adjust_area(self->frame, FALSE, TRUE, TRUE);
578 /* free the ObAppSettings shallow copy */
584 void client_unmanage_all()
586 while (client_list != NULL)
587 client_unmanage(client_list->data);
590 void client_unmanage(ObClient *self)
595 ob_debug("Unmanaging window: 0x%x plate 0x%x (%s) (%s)\n",
596 self->window, self->frame->plate,
597 self->class, self->title ? self->title : "");
599 g_assert(self != NULL);
601 /* we dont want events no more. do this before hiding the frame so we
602 don't generate more events */
603 XSelectInput(ob_display, self->window, NoEventMask);
605 frame_hide(self->frame);
606 /* flush to send the hide to the server quickly */
609 /* ignore enter events from the unmap so it doesnt mess with the
611 event_ignore_all_queued_enters();
613 mouse_grab_for_client(self, FALSE);
615 /* remove the window from our save set */
616 XChangeSaveSet(ob_display, self->window, SetModeDelete);
618 /* kill the property windows */
619 propwin_remove(self->user_time_window, OB_PROPWIN_USER_TIME, self);
621 /* update the focus lists */
622 focus_order_remove(self);
623 if (client_focused(self)) {
624 /* don't leave an invalid focus_client */
628 client_list = g_list_remove(client_list, self);
629 stacking_remove(self);
630 g_hash_table_remove(window_map, &self->window);
632 /* once the client is out of the list, update the struts to remove its
634 if (STRUT_EXISTS(self->strut))
635 screen_update_areas();
637 client_call_notifies(self, client_destroy_notifies);
639 /* tell our parent(s) that we're gone */
640 if (self->transient_for == OB_TRAN_GROUP) { /* transient of group */
641 for (it = self->group->members; it; it = g_slist_next(it))
642 if (it->data != self)
643 ((ObClient*)it->data)->transients =
644 g_slist_remove(((ObClient*)it->data)->transients,self);
645 } else if (self->transient_for) { /* transient of window */
646 self->transient_for->transients =
647 g_slist_remove(self->transient_for->transients, self);
650 /* tell our transients that we're gone */
651 for (it = self->transients; it; it = g_slist_next(it)) {
652 if (((ObClient*)it->data)->transient_for != OB_TRAN_GROUP) {
653 ((ObClient*)it->data)->transient_for = NULL;
654 client_calc_layer(it->data);
658 /* remove from its group */
660 group_remove(self->group, self);
664 /* restore the window's original geometry so it is not lost */
668 /* give the client its border back */
669 client_toggle_border(self, TRUE);
673 if (self->fullscreen)
674 a = self->pre_fullscreen_area;
675 else if (self->max_horz || self->max_vert) {
676 if (self->max_horz) {
677 a.x = self->pre_max_area.x;
678 a.width = self->pre_max_area.width;
680 if (self->max_vert) {
681 a.y = self->pre_max_area.y;
682 a.height = self->pre_max_area.height;
686 self->fullscreen = self->max_horz = self->max_vert = FALSE;
687 /* let it be moved and resized no matter what */
688 self->functions = OB_CLIENT_FUNC_MOVE | OB_CLIENT_FUNC_RESIZE;
689 self->decorations = 0; /* unmanaged windows have no decor */
691 client_move_resize(self, a.x, a.y, a.width, a.height);
694 /* reparent the window out of the frame, and free the frame */
695 frame_release_client(self->frame);
696 frame_free(self->frame);
699 if (ob_state() != OB_STATE_EXITING) {
700 /* these values should not be persisted across a window
702 PROP_ERASE(self->window, net_wm_desktop);
703 PROP_ERASE(self->window, net_wm_state);
704 PROP_ERASE(self->window, wm_state);
706 /* if we're left in an unmapped state, the client wont be mapped.
707 this is bad, since we will no longer be managing the window on
709 XMapWindow(ob_display, self->window);
712 /* update the list hints */
715 ob_debug("Unmanaged window 0x%lx\n", self->window);
717 /* free all data allocated in the client struct */
718 g_slist_free(self->transients);
719 for (j = 0; j < self->nicons; ++j)
720 g_free(self->icons[j].data);
721 if (self->nicons > 0)
723 g_free(self->wm_command);
725 g_free(self->icon_title);
729 g_free(self->client_machine);
730 g_free(self->sm_client_id);
734 void client_fake_unmanage(ObClient *self)
736 /* this is all that got allocated to get the decorations */
738 frame_free(self->frame);
742 /*! Returns a new structure containing the per-app settings for this client.
743 The returned structure needs to be freed with g_free. */
744 static ObAppSettings *client_get_settings_state(ObClient *self)
746 ObAppSettings *settings;
749 settings = config_create_app_settings();
751 for (it = config_per_app_settings; it; it = g_slist_next(it)) {
752 ObAppSettings *app = it->data;
753 gboolean match = TRUE;
755 g_assert(app->name != NULL || app->class != NULL);
757 /* we know that either name or class is not NULL so it will have to
758 match to use the rule */
760 !g_pattern_match(app->name, strlen(self->name), self->name, NULL))
762 else if (app->class &&
763 !g_pattern_match(app->class,
764 strlen(self->class), self->class, NULL))
766 else if (app->role &&
767 !g_pattern_match(app->role,
768 strlen(self->role), self->role, NULL))
772 ob_debug("Window matching: %s\n", app->name);
774 /* copy the settings to our struct, overriding the existing
775 settings if they are not defaults */
776 config_app_settings_copy_non_defaults(app, settings);
780 if (settings->shade != -1)
781 self->shaded = !!settings->shade;
782 if (settings->decor != -1)
783 self->undecorated = !settings->decor;
784 if (settings->iconic != -1)
785 self->iconic = !!settings->iconic;
786 if (settings->skip_pager != -1)
787 self->skip_pager = !!settings->skip_pager;
788 if (settings->skip_taskbar != -1)
789 self->skip_taskbar = !!settings->skip_taskbar;
791 if (settings->max_vert != -1)
792 self->max_vert = !!settings->max_vert;
793 if (settings->max_horz != -1)
794 self->max_horz = !!settings->max_horz;
796 if (settings->fullscreen != -1)
797 self->fullscreen = !!settings->fullscreen;
799 if (settings->desktop) {
800 if (settings->desktop == DESKTOP_ALL)
801 self->desktop = settings->desktop;
802 else if (settings->desktop > 0 &&
803 settings->desktop <= screen_num_desktops)
804 self->desktop = settings->desktop - 1;
807 if (settings->layer == -1) {
811 else if (settings->layer == 0) {
815 else if (settings->layer == 1) {
822 static void client_restore_session_state(ObClient *self)
826 ob_debug_type(OB_DEBUG_SM,
827 "Restore session for client %s\n", self->title);
829 if (!(it = session_state_find(self))) {
830 ob_debug_type(OB_DEBUG_SM,
831 "Session data not found for client %s\n", self->title);
835 self->session = it->data;
837 ob_debug_type(OB_DEBUG_SM, "Session data loaded for client %s\n",
840 RECT_SET_POINT(self->area, self->session->x, self->session->y);
841 self->positioned = USPosition;
842 if (self->session->w > 0)
843 self->area.width = self->session->w;
844 if (self->session->h > 0)
845 self->area.height = self->session->h;
846 XResizeWindow(ob_display, self->window,
847 self->area.width, self->area.height);
849 self->desktop = (self->session->desktop == DESKTOP_ALL ?
850 self->session->desktop :
851 MIN(screen_num_desktops - 1, self->session->desktop));
852 PROP_SET32(self->window, net_wm_desktop, cardinal, self->desktop);
854 self->shaded = self->session->shaded;
855 self->iconic = self->session->iconic;
856 self->skip_pager = self->session->skip_pager;
857 self->skip_taskbar = self->session->skip_taskbar;
858 self->fullscreen = self->session->fullscreen;
859 self->above = self->session->above;
860 self->below = self->session->below;
861 self->max_horz = self->session->max_horz;
862 self->max_vert = self->session->max_vert;
863 self->undecorated = self->session->undecorated;
866 static gboolean client_restore_session_stacking(ObClient *self)
870 if (!self->session) return FALSE;
872 mypos = g_list_find(session_saved_state, self->session);
873 if (!mypos) return FALSE;
875 /* start above me and look for the first client */
876 for (it = g_list_previous(mypos); it; it = g_list_previous(it)) {
879 for (cit = client_list; cit; cit = g_list_next(cit)) {
880 ObClient *c = cit->data;
881 /* found a client that was in the session, so go below it */
882 if (c->session == it->data) {
883 stacking_below(CLIENT_AS_WINDOW(self),
884 CLIENT_AS_WINDOW(cit->data));
892 void client_move_onscreen(ObClient *self, gboolean rude)
894 gint x = self->area.x;
895 gint y = self->area.y;
896 if (client_find_onscreen(self, &x, &y,
898 self->area.height, rude)) {
899 client_move(self, x, y);
903 gboolean client_find_onscreen(ObClient *self, gint *x, gint *y, gint w, gint h,
907 gint ox = *x, oy = *y;
908 gboolean rudel = rude, ruder = rude, rudet = rude, rudeb = rude;
912 RECT_SET(desired, *x, *y, w, h);
913 all_a = screen_area(self->desktop);
914 mon_a = screen_area_monitor(self->desktop, screen_find_monitor(&desired));
916 /* get where the frame would be */
917 frame_client_gravity(self->frame, x, y, w, h);
919 /* get the requested size of the window with decorations */
920 fw = self->frame->size.left + w + self->frame->size.right;
921 fh = self->frame->size.top + h + self->frame->size.bottom;
923 /* This makes sure windows aren't entirely outside of the screen so you
924 can't see them at all.
925 It makes sure 10% of the window is on the screen at least. At don't let
926 it move itself off the top of the screen, which would hide the titlebar
927 on you. (The user can still do this if they want too, it's only limiting
930 XXX watch for xinerama dead areas...
932 if (client_normal(self)) {
933 if (!self->strut.right && *x + fw/10 >= all_a->x + all_a->width - 1)
934 *x = all_a->x + all_a->width - fw/10;
935 if (!self->strut.bottom && *y + fh/10 >= all_a->y + all_a->height - 1)
936 *y = all_a->y + all_a->height - fh/10;
937 if (!self->strut.left && *x + fw*9/10 - 1 < all_a->x)
938 *x = all_a->x - fw*9/10;
939 if (!self->strut.top && *y + fh*9/10 - 1 < all_a->y)
940 *y = all_a->y - fw*9/10;
943 /* If rudeness wasn't requested, then figure out of the client is currently
944 entirely on the screen. If it is, and the position isn't changing by
945 request, and it is enlarging, then be rude even though it wasn't
948 Point oldtl, oldtr, oldbl, oldbr;
949 Point newtl, newtr, newbl, newbr;
950 gboolean stationary_l, stationary_r, stationary_t, stationary_b;
952 POINT_SET(oldtl, self->frame->area.x, self->frame->area.y);
953 POINT_SET(oldbr, self->frame->area.x + self->frame->area.width - 1,
954 self->frame->area.y + self->frame->area.height - 1);
955 POINT_SET(oldtr, oldbr.x, oldtl.y);
956 POINT_SET(oldbl, oldtl.x, oldbr.y);
958 POINT_SET(newtl, *x, *y);
959 POINT_SET(newbr, *x + fw - 1, *y + fh - 1);
960 POINT_SET(newtr, newbr.x, newtl.y);
961 POINT_SET(newbl, newtl.x, newbr.y);
963 /* is it moving or just resizing from some corner? */
964 stationary_l = oldtl.x == newtl.x;
965 stationary_r = oldtr.x == newtr.x;
966 stationary_t = oldtl.y == newtl.y;
967 stationary_b = oldbl.y == newbl.y;
969 /* if left edge is growing and didnt move right edge */
970 if (stationary_r && newtl.x < oldtl.x)
972 /* if right edge is growing and didnt move left edge */
973 if (stationary_l && newtr.x > oldtr.x)
975 /* if top edge is growing and didnt move bottom edge */
976 if (stationary_b && newtl.y < oldtl.y)
978 /* if bottom edge is growing and didnt move top edge */
979 if (stationary_t && newbl.y > oldbl.y)
983 /* This here doesn't let windows even a pixel outside the struts/screen.
984 * When called from client_manage, programs placing themselves are
985 * forced completely onscreen, while things like
986 * xterm -geometry resolution-width/2 will work fine. Trying to
987 * place it completely offscreen will be handled in the above code.
988 * Sorry for this confused comment, i am tired. */
989 if (rudel && !self->strut.left && *x < mon_a->x) *x = mon_a->x;
990 if (ruder && !self->strut.right && *x + fw > mon_a->x + mon_a->width)
991 *x = mon_a->x + MAX(0, mon_a->width - fw);
993 if (rudet && !self->strut.top && *y < mon_a->y) *y = mon_a->y;
994 if (rudeb && !self->strut.bottom && *y + fh > mon_a->y + mon_a->height)
995 *y = mon_a->y + MAX(0, mon_a->height - fh);
997 /* get where the client should be */
998 frame_frame_gravity(self->frame, x, y, w, h);
1000 return ox != *x || oy != *y;
1003 static void client_toggle_border(ObClient *self, gboolean show)
1005 /* adjust our idea of where the client is, based on its border. When the
1006 border is removed, the client should now be considered to be in a
1008 when re-adding the border to the client, the same operation needs to be
1010 gint oldx = self->area.x, oldy = self->area.y;
1011 gint x = oldx, y = oldy;
1012 switch(self->gravity) {
1014 case NorthWestGravity:
1016 case SouthWestGravity:
1018 case NorthEastGravity:
1020 case SouthEastGravity:
1021 if (show) x -= self->border_width * 2;
1022 else x += self->border_width * 2;
1029 if (show) x -= self->border_width;
1030 else x += self->border_width;
1033 switch(self->gravity) {
1035 case NorthWestGravity:
1037 case NorthEastGravity:
1039 case SouthWestGravity:
1041 case SouthEastGravity:
1042 if (show) y -= self->border_width * 2;
1043 else y += self->border_width * 2;
1050 if (show) y -= self->border_width;
1051 else y += self->border_width;
1058 XSetWindowBorderWidth(ob_display, self->window, self->border_width);
1060 /* set border_width to 0 because there is no border to add into
1061 calculations anymore */
1062 self->border_width = 0;
1064 XSetWindowBorderWidth(ob_display, self->window, 0);
1068 static void client_get_all(ObClient *self, gboolean real)
1070 /* this is needed for the frame to set itself up */
1071 client_get_area(self);
1073 /* these things can change the decor and functions of the window */
1075 client_get_mwm_hints(self);
1076 /* this can change the mwmhints for special cases */
1077 client_get_type_and_transientness(self);
1078 client_get_state(self);
1079 client_update_normal_hints(self);
1081 /* get the session related properties, these can change decorations
1082 from per-app settings */
1083 client_get_session_ids(self);
1085 /* now we got everything that can affect the decorations */
1089 /* get this early so we have it for debugging */
1090 client_update_title(self);
1092 client_update_protocols(self);
1094 client_update_wmhints(self);
1095 /* this may have already been called from client_update_wmhints */
1096 if (self->transient_for == NULL)
1097 client_update_transient_for(self);
1099 client_get_startup_id(self);
1100 client_get_desktop(self);/* uses transient data/group/startup id if a
1101 desktop is not specified */
1102 client_get_shaped(self);
1105 /* a couple type-based defaults for new windows */
1107 /* this makes sure that these windows appear on all desktops */
1108 if (self->type == OB_CLIENT_TYPE_DESKTOP)
1109 self->desktop = DESKTOP_ALL;
1113 client_update_sync_request_counter(self);
1116 client_get_colormap(self);
1117 client_update_strut(self);
1118 client_update_icons(self);
1119 client_update_user_time_window(self);
1120 if (!self->user_time_window) /* check if this would have been called */
1121 client_update_user_time(self);
1122 client_update_icon_geometry(self);
1125 static void client_get_startup_id(ObClient *self)
1127 if (!(PROP_GETS(self->window, net_startup_id, utf8, &self->startup_id)))
1129 PROP_GETS(self->group->leader,
1130 net_startup_id, utf8, &self->startup_id);
1133 static void client_get_area(ObClient *self)
1135 XWindowAttributes wattrib;
1138 ret = XGetWindowAttributes(ob_display, self->window, &wattrib);
1139 g_assert(ret != BadWindow);
1141 RECT_SET(self->area, wattrib.x, wattrib.y, wattrib.width, wattrib.height);
1142 POINT_SET(self->root_pos, wattrib.x, wattrib.y);
1143 self->border_width = wattrib.border_width;
1145 ob_debug("client area: %d %d %d %d\n", wattrib.x, wattrib.y,
1146 wattrib.width, wattrib.height);
1149 static void client_get_desktop(ObClient *self)
1151 guint32 d = screen_num_desktops; /* an always-invalid value */
1153 if (PROP_GET32(self->window, net_wm_desktop, cardinal, &d)) {
1154 if (d >= screen_num_desktops && d != DESKTOP_ALL)
1155 self->desktop = screen_num_desktops - 1;
1159 gboolean trdesk = FALSE;
1161 if (self->transient_for) {
1162 if (self->transient_for != OB_TRAN_GROUP) {
1163 self->desktop = self->transient_for->desktop;
1166 /* if all the group is on one desktop, then open it on the
1169 gboolean first = TRUE;
1170 guint all = screen_num_desktops; /* not a valid value */
1172 for (it = self->group->members; it; it = g_slist_next(it)) {
1173 ObClient *c = it->data;
1179 else if (all != c->desktop)
1180 all = screen_num_desktops; /* make it invalid */
1183 if (all != screen_num_desktops) {
1184 self->desktop = all;
1190 /* try get from the startup-notification protocol */
1191 if (sn_get_desktop(self->startup_id, &self->desktop)) {
1192 if (self->desktop >= screen_num_desktops &&
1193 self->desktop != DESKTOP_ALL)
1194 self->desktop = screen_num_desktops - 1;
1196 /* defaults to the current desktop */
1197 self->desktop = screen_desktop;
1202 static void client_get_state(ObClient *self)
1207 if (PROP_GETA32(self->window, net_wm_state, atom, &state, &num)) {
1209 for (i = 0; i < num; ++i) {
1210 if (state[i] == prop_atoms.net_wm_state_modal)
1212 else if (state[i] == prop_atoms.net_wm_state_shaded)
1213 self->shaded = TRUE;
1214 else if (state[i] == prop_atoms.net_wm_state_hidden)
1215 self->iconic = TRUE;
1216 else if (state[i] == prop_atoms.net_wm_state_skip_taskbar)
1217 self->skip_taskbar = TRUE;
1218 else if (state[i] == prop_atoms.net_wm_state_skip_pager)
1219 self->skip_pager = TRUE;
1220 else if (state[i] == prop_atoms.net_wm_state_fullscreen)
1221 self->fullscreen = TRUE;
1222 else if (state[i] == prop_atoms.net_wm_state_maximized_vert)
1223 self->max_vert = TRUE;
1224 else if (state[i] == prop_atoms.net_wm_state_maximized_horz)
1225 self->max_horz = TRUE;
1226 else if (state[i] == prop_atoms.net_wm_state_above)
1228 else if (state[i] == prop_atoms.net_wm_state_below)
1230 else if (state[i] == prop_atoms.net_wm_state_demands_attention)
1231 self->demands_attention = TRUE;
1232 else if (state[i] == prop_atoms.ob_wm_state_undecorated)
1233 self->undecorated = TRUE;
1240 static void client_get_shaped(ObClient *self)
1242 self->shaped = FALSE;
1244 if (extensions_shape) {
1249 XShapeSelectInput(ob_display, self->window, ShapeNotifyMask);
1251 XShapeQueryExtents(ob_display, self->window, &s, &foo,
1252 &foo, &ufoo, &ufoo, &foo, &foo, &foo, &ufoo,
1254 self->shaped = (s != 0);
1259 void client_update_transient_for(ObClient *self)
1262 ObClient *target = NULL;
1264 if (XGetTransientForHint(ob_display, self->window, &t)) {
1265 if (t != self->window) { /* cant be transient to itself! */
1266 target = g_hash_table_lookup(window_map, &t);
1267 /* if this happens then we need to check for it*/
1268 g_assert(target != self);
1269 if (target && !WINDOW_IS_CLIENT(target)) {
1270 /* this can happen when a dialog is a child of
1271 a dockapp, for example */
1275 /* THIS IS SO ANNOYING ! ! ! ! Let me explain.... have a seat..
1277 Setting the transient_for to Root is actually illegal, however
1278 applications from time have done this to specify transient for
1281 Now you can do that by being a TYPE_DIALOG and not setting
1282 the transient_for hint at all on your window. But people still
1283 use Root, and Kwin is very strange in this regard.
1285 KWin 3.0 will not consider windows with transient_for set to
1286 Root as transient for their group *UNLESS* they are also modal.
1287 In that case, it will make them transient for the group. This
1288 leads to all sorts of weird behavior from KDE apps which are
1289 only tested in KWin. I'd like to follow their behavior just to
1290 make this work right with KDE stuff, but that seems wrong.
1292 if (!target && self->group) {
1293 /* not transient to a client, see if it is transient for a
1295 if (t == RootWindow(ob_display, ob_screen)) {
1296 /* window is a transient for its group! */
1297 target = OB_TRAN_GROUP;
1301 } else if (self->transient && self->group)
1302 target = OB_TRAN_GROUP;
1304 client_update_transient_tree(self, self->group, self->group,
1305 self->transient_for, target);
1306 self->transient_for = target;
1310 static void client_update_transient_tree(ObClient *self,
1311 ObGroup *oldgroup, ObGroup *newgroup,
1312 ObClient* oldparent,
1313 ObClient *newparent)
1319 Group transient windows are not allowed to have other group
1320 transient windows as their children.
1324 /* No change has occured */
1325 if (oldgroup == newgroup && oldparent == newparent) return;
1327 /** Remove the client from the transient tree wherever it has changed **/
1329 /* If the window is becoming a direct transient for a window in its group
1330 then any group transients which were our children and are now becoming
1331 our parents need to stop being our children.
1333 Group transients can't be children of group transients already, but
1334 we could have any number of direct parents above up, any of which could
1335 be transient for the group, and we need to remove it from our children.
1337 if (oldparent != newparent &&
1338 newparent != NULL && newparent != OB_TRAN_GROUP &&
1339 newgroup != NULL && newgroup == oldgroup)
1341 ObClient *look = newparent;
1343 self->transients = g_slist_remove(self->transients, look);
1344 look = look->transient_for;
1345 } while (look != NULL && look != OB_TRAN_GROUP);
1349 /* If the group changed, or if we are just becoming transient for the
1350 group, then we need to remove any old group transient windows
1351 from our children. But if we were already transient for the group, then
1352 other group transients are not our children. */
1353 if ((oldgroup != newgroup ||
1354 (newparent == OB_TRAN_GROUP && oldparent != newparent)) &&
1355 oldgroup != NULL && oldparent != OB_TRAN_GROUP)
1357 for (it = self->transients; it; it = next) {
1358 next = g_slist_next(it);
1360 if (c->group == oldgroup)
1361 self->transients = g_slist_delete_link(self->transients, it);
1365 /* If we used to be transient for a group and now we are not, or we're
1366 transient for a new group, then we need to remove ourselves from all
1368 if (oldparent == OB_TRAN_GROUP && (oldgroup != newgroup ||
1369 oldparent != newparent))
1371 for (it = oldgroup->members; it; it = g_slist_next(it)) {
1373 if (c != self && (!c->transient_for ||
1374 c->transient_for != OB_TRAN_GROUP))
1375 c->transients = g_slist_remove(c->transients, self);
1378 /* If we used to be transient for a single window and we are no longer
1379 transient for it, then we need to remove ourself from its children */
1380 else if (oldparent != NULL && oldparent != OB_TRAN_GROUP &&
1381 oldparent != newparent)
1382 oldparent->transients = g_slist_remove(oldparent->transients, self);
1385 /** Re-add the client to the transient tree wherever it has changed **/
1387 /* If we're now transient for a group and we weren't transient for it
1388 before then we need to add ourselves to all our new parents */
1389 if (newparent == OB_TRAN_GROUP && (oldgroup != newgroup ||
1390 oldparent != newparent))
1392 for (it = oldgroup->members; it; it = g_slist_next(it)) {
1394 if (c != self && (!c->transient_for ||
1395 c->transient_for != OB_TRAN_GROUP))
1396 c->transients = g_slist_prepend(c->transients, self);
1399 /* If we are now transient for a single window which we weren't before,
1400 we need to add ourselves to its children
1402 WARNING: Cyclical transient ness is possible if two windows are
1403 transient for eachother.
1405 else if (newparent != NULL && newparent != OB_TRAN_GROUP &&
1406 newparent != oldparent &&
1407 /* don't make ourself its child if it is already our child */
1408 !client_is_direct_child(self, newparent))
1409 newparent->transients = g_slist_prepend(newparent->transients, self);
1411 /* If the group changed then we need to add any new group transient
1412 windows to our children. But if we're transient for the group, then
1413 other group transients are not our children.
1415 WARNING: Cyclical transient-ness is possible. For e.g. if:
1416 A is transient for the group
1417 B is transient for A
1418 C is transient for B
1419 A can't be transient for C or we have a cycle
1421 if (oldgroup != newgroup && newgroup != NULL &&
1422 newparent != OB_TRAN_GROUP)
1424 for (it = newgroup->members; it; it = g_slist_next(it)) {
1426 if (c != self && c->transient_for == OB_TRAN_GROUP &&
1427 /* Don't make it our child if it is already our parent */
1428 !client_is_direct_child(c, self))
1430 self->transients = g_slist_prepend(self->transients, c);
1436 static void client_get_mwm_hints(ObClient *self)
1441 self->mwmhints.flags = 0; /* default to none */
1443 if (PROP_GETA32(self->window, motif_wm_hints, motif_wm_hints,
1445 if (num >= OB_MWM_ELEMENTS) {
1446 self->mwmhints.flags = hints[0];
1447 self->mwmhints.functions = hints[1];
1448 self->mwmhints.decorations = hints[2];
1454 void client_get_type_and_transientness(ObClient *self)
1461 self->transient = FALSE;
1463 if (PROP_GETA32(self->window, net_wm_window_type, atom, &val, &num)) {
1464 /* use the first value that we know about in the array */
1465 for (i = 0; i < num; ++i) {
1466 if (val[i] == prop_atoms.net_wm_window_type_desktop)
1467 self->type = OB_CLIENT_TYPE_DESKTOP;
1468 else if (val[i] == prop_atoms.net_wm_window_type_dock)
1469 self->type = OB_CLIENT_TYPE_DOCK;
1470 else if (val[i] == prop_atoms.net_wm_window_type_toolbar)
1471 self->type = OB_CLIENT_TYPE_TOOLBAR;
1472 else if (val[i] == prop_atoms.net_wm_window_type_menu)
1473 self->type = OB_CLIENT_TYPE_MENU;
1474 else if (val[i] == prop_atoms.net_wm_window_type_utility)
1475 self->type = OB_CLIENT_TYPE_UTILITY;
1476 else if (val[i] == prop_atoms.net_wm_window_type_splash)
1477 self->type = OB_CLIENT_TYPE_SPLASH;
1478 else if (val[i] == prop_atoms.net_wm_window_type_dialog)
1479 self->type = OB_CLIENT_TYPE_DIALOG;
1480 else if (val[i] == prop_atoms.net_wm_window_type_normal)
1481 self->type = OB_CLIENT_TYPE_NORMAL;
1482 else if (val[i] == prop_atoms.kde_net_wm_window_type_override) {
1483 /* prevent this window from getting any decor or
1485 self->mwmhints.flags &= (OB_MWM_FLAG_FUNCTIONS |
1486 OB_MWM_FLAG_DECORATIONS);
1487 self->mwmhints.decorations = 0;
1488 self->mwmhints.functions = 0;
1490 if (self->type != (ObClientType) -1)
1491 break; /* grab the first legit type */
1496 if (XGetTransientForHint(ob_display, self->window, &t))
1497 self->transient = TRUE;
1499 if (self->type == (ObClientType) -1) {
1500 /*the window type hint was not set, which means we either classify
1501 ourself as a normal window or a dialog, depending on if we are a
1503 if (self->transient)
1504 self->type = OB_CLIENT_TYPE_DIALOG;
1506 self->type = OB_CLIENT_TYPE_NORMAL;
1509 /* then, based on our type, we can update our transientness.. */
1510 if (self->type == OB_CLIENT_TYPE_DIALOG ||
1511 self->type == OB_CLIENT_TYPE_TOOLBAR ||
1512 self->type == OB_CLIENT_TYPE_MENU ||
1513 self->type == OB_CLIENT_TYPE_UTILITY)
1515 self->transient = TRUE;
1519 void client_update_protocols(ObClient *self)
1522 guint num_return, i;
1524 self->focus_notify = FALSE;
1525 self->delete_window = FALSE;
1527 if (PROP_GETA32(self->window, wm_protocols, atom, &proto, &num_return)) {
1528 for (i = 0; i < num_return; ++i) {
1529 if (proto[i] == prop_atoms.wm_delete_window)
1530 /* this means we can request the window to close */
1531 self->delete_window = TRUE;
1532 else if (proto[i] == prop_atoms.wm_take_focus)
1533 /* if this protocol is requested, then the window will be
1534 notified whenever we want it to receive focus */
1535 self->focus_notify = TRUE;
1537 else if (proto[i] == prop_atoms.net_wm_sync_request)
1538 /* if this protocol is requested, then resizing the
1539 window will be synchronized between the frame and the
1541 self->sync_request = TRUE;
1549 void client_update_sync_request_counter(ObClient *self)
1553 if (PROP_GET32(self->window, net_wm_sync_request_counter, cardinal, &i)) {
1554 self->sync_counter = i;
1556 self->sync_counter = None;
1560 void client_get_colormap(ObClient *self)
1562 XWindowAttributes wa;
1564 if (XGetWindowAttributes(ob_display, self->window, &wa))
1565 client_update_colormap(self, wa.colormap);
1568 void client_update_colormap(ObClient *self, Colormap colormap)
1570 self->colormap = colormap;
1573 void client_update_normal_hints(ObClient *self)
1579 self->min_ratio = 0.0f;
1580 self->max_ratio = 0.0f;
1581 SIZE_SET(self->size_inc, 1, 1);
1582 SIZE_SET(self->base_size, 0, 0);
1583 SIZE_SET(self->min_size, 0, 0);
1584 SIZE_SET(self->max_size, G_MAXINT, G_MAXINT);
1586 /* get the hints from the window */
1587 if (XGetWMNormalHints(ob_display, self->window, &size, &ret)) {
1588 /* normal windows can't request placement! har har
1589 if (!client_normal(self))
1591 self->positioned = (size.flags & (PPosition|USPosition));
1593 if (size.flags & PWinGravity)
1594 self->gravity = size.win_gravity;
1596 if (size.flags & PAspect) {
1597 if (size.min_aspect.y)
1599 (gfloat) size.min_aspect.x / size.min_aspect.y;
1600 if (size.max_aspect.y)
1602 (gfloat) size.max_aspect.x / size.max_aspect.y;
1605 if (size.flags & PMinSize)
1606 SIZE_SET(self->min_size, size.min_width, size.min_height);
1608 if (size.flags & PMaxSize)
1609 SIZE_SET(self->max_size, size.max_width, size.max_height);
1611 if (size.flags & PBaseSize)
1612 SIZE_SET(self->base_size, size.base_width, size.base_height);
1614 if (size.flags & PResizeInc && size.width_inc && size.height_inc)
1615 SIZE_SET(self->size_inc, size.width_inc, size.height_inc);
1619 /*! This needs to be followed by a call to client_configure to make
1621 void client_setup_decor_and_functions(ObClient *self)
1623 /* start with everything (cept fullscreen) */
1625 (OB_FRAME_DECOR_TITLEBAR |
1626 OB_FRAME_DECOR_HANDLE |
1627 OB_FRAME_DECOR_GRIPS |
1628 OB_FRAME_DECOR_BORDER |
1629 OB_FRAME_DECOR_ICON |
1630 OB_FRAME_DECOR_ALLDESKTOPS |
1631 OB_FRAME_DECOR_ICONIFY |
1632 OB_FRAME_DECOR_MAXIMIZE |
1633 OB_FRAME_DECOR_SHADE |
1634 OB_FRAME_DECOR_CLOSE);
1636 (OB_CLIENT_FUNC_RESIZE |
1637 OB_CLIENT_FUNC_MOVE |
1638 OB_CLIENT_FUNC_ICONIFY |
1639 OB_CLIENT_FUNC_MAXIMIZE |
1640 OB_CLIENT_FUNC_SHADE |
1641 OB_CLIENT_FUNC_CLOSE |
1642 OB_CLIENT_FUNC_BELOW |
1643 OB_CLIENT_FUNC_ABOVE |
1644 OB_CLIENT_FUNC_UNDECORATE);
1646 if (!(self->min_size.width < self->max_size.width ||
1647 self->min_size.height < self->max_size.height))
1648 self->functions &= ~OB_CLIENT_FUNC_RESIZE;
1650 switch (self->type) {
1651 case OB_CLIENT_TYPE_NORMAL:
1652 /* normal windows retain all of the possible decorations and
1653 functionality, and are the only windows that you can fullscreen */
1654 self->functions |= OB_CLIENT_FUNC_FULLSCREEN;
1657 case OB_CLIENT_TYPE_DIALOG:
1658 case OB_CLIENT_TYPE_UTILITY:
1659 /* these windows don't have anything added or removed by default */
1662 case OB_CLIENT_TYPE_MENU:
1663 case OB_CLIENT_TYPE_TOOLBAR:
1664 /* these windows can't iconify or maximize */
1665 self->decorations &= ~(OB_FRAME_DECOR_ICONIFY |
1666 OB_FRAME_DECOR_MAXIMIZE);
1667 self->functions &= ~(OB_CLIENT_FUNC_ICONIFY |
1668 OB_CLIENT_FUNC_MAXIMIZE);
1671 case OB_CLIENT_TYPE_SPLASH:
1672 /* these don't get get any decorations, and the only thing you can
1673 do with them is move them */
1674 self->decorations = 0;
1675 self->functions = OB_CLIENT_FUNC_MOVE;
1678 case OB_CLIENT_TYPE_DESKTOP:
1679 /* these windows are not manipulated by the window manager */
1680 self->decorations = 0;
1681 self->functions = 0;
1684 case OB_CLIENT_TYPE_DOCK:
1685 /* these windows are not manipulated by the window manager, but they
1686 can set below layer which has a special meaning */
1687 self->decorations = 0;
1688 self->functions = OB_CLIENT_FUNC_BELOW;
1692 /* Mwm Hints are applied subtractively to what has already been chosen for
1693 decor and functionality */
1694 if (self->mwmhints.flags & OB_MWM_FLAG_DECORATIONS) {
1695 if (! (self->mwmhints.decorations & OB_MWM_DECOR_ALL)) {
1696 if (! ((self->mwmhints.decorations & OB_MWM_DECOR_HANDLE) ||
1697 (self->mwmhints.decorations & OB_MWM_DECOR_TITLE)))
1699 /* if the mwm hints request no handle or title, then all
1700 decorations are disabled, but keep the border if that's
1702 if (self->mwmhints.decorations & OB_MWM_DECOR_BORDER)
1703 self->decorations = OB_FRAME_DECOR_BORDER;
1705 self->decorations = 0;
1710 if (self->mwmhints.flags & OB_MWM_FLAG_FUNCTIONS) {
1711 if (! (self->mwmhints.functions & OB_MWM_FUNC_ALL)) {
1712 if (! (self->mwmhints.functions & OB_MWM_FUNC_RESIZE))
1713 self->functions &= ~OB_CLIENT_FUNC_RESIZE;
1714 if (! (self->mwmhints.functions & OB_MWM_FUNC_MOVE))
1715 self->functions &= ~OB_CLIENT_FUNC_MOVE;
1716 /* dont let mwm hints kill any buttons
1717 if (! (self->mwmhints.functions & OB_MWM_FUNC_ICONIFY))
1718 self->functions &= ~OB_CLIENT_FUNC_ICONIFY;
1719 if (! (self->mwmhints.functions & OB_MWM_FUNC_MAXIMIZE))
1720 self->functions &= ~OB_CLIENT_FUNC_MAXIMIZE;
1722 /* dont let mwm hints kill the close button
1723 if (! (self->mwmhints.functions & MwmFunc_Close))
1724 self->functions &= ~OB_CLIENT_FUNC_CLOSE; */
1728 if (!(self->functions & OB_CLIENT_FUNC_SHADE))
1729 self->decorations &= ~OB_FRAME_DECOR_SHADE;
1730 if (!(self->functions & OB_CLIENT_FUNC_ICONIFY))
1731 self->decorations &= ~OB_FRAME_DECOR_ICONIFY;
1732 if (!(self->functions & OB_CLIENT_FUNC_RESIZE))
1733 self->decorations &= ~(OB_FRAME_DECOR_GRIPS | OB_FRAME_DECOR_HANDLE);
1735 /* can't maximize without moving/resizing */
1736 if (!((self->functions & OB_CLIENT_FUNC_MAXIMIZE) &&
1737 (self->functions & OB_CLIENT_FUNC_MOVE) &&
1738 (self->functions & OB_CLIENT_FUNC_RESIZE))) {
1739 self->functions &= ~OB_CLIENT_FUNC_MAXIMIZE;
1740 self->decorations &= ~OB_FRAME_DECOR_MAXIMIZE;
1743 if (self->max_horz && self->max_vert) {
1744 /* you can't resize fully maximized windows */
1745 self->functions &= ~OB_CLIENT_FUNC_RESIZE;
1746 /* kill the handle on fully maxed windows */
1747 self->decorations &= ~(OB_FRAME_DECOR_HANDLE | OB_FRAME_DECOR_GRIPS);
1750 /* If there are no decorations to remove, don't allow the user to try
1752 if (self->decorations == 0)
1753 self->functions &= ~OB_CLIENT_FUNC_UNDECORATE;
1755 /* finally, the user can have requested no decorations, which overrides
1756 everything (but doesnt give it a border if it doesnt have one) */
1757 if (self->undecorated) {
1758 if (config_theme_keepborder)
1759 self->decorations &= OB_FRAME_DECOR_BORDER;
1761 self->decorations = 0;
1764 /* if we don't have a titlebar, then we cannot shade! */
1765 if (!(self->decorations & OB_FRAME_DECOR_TITLEBAR))
1766 self->functions &= ~OB_CLIENT_FUNC_SHADE;
1768 /* now we need to check against rules for the client's current state */
1769 if (self->fullscreen) {
1770 self->functions &= (OB_CLIENT_FUNC_CLOSE |
1771 OB_CLIENT_FUNC_FULLSCREEN |
1772 OB_CLIENT_FUNC_ICONIFY);
1773 self->decorations = 0;
1776 client_change_allowed_actions(self);
1779 static void client_change_allowed_actions(ObClient *self)
1784 /* desktop windows are kept on all desktops */
1785 if (self->type != OB_CLIENT_TYPE_DESKTOP)
1786 actions[num++] = prop_atoms.net_wm_action_change_desktop;
1788 if (self->functions & OB_CLIENT_FUNC_SHADE)
1789 actions[num++] = prop_atoms.net_wm_action_shade;
1790 if (self->functions & OB_CLIENT_FUNC_CLOSE)
1791 actions[num++] = prop_atoms.net_wm_action_close;
1792 if (self->functions & OB_CLIENT_FUNC_MOVE)
1793 actions[num++] = prop_atoms.net_wm_action_move;
1794 if (self->functions & OB_CLIENT_FUNC_ICONIFY)
1795 actions[num++] = prop_atoms.net_wm_action_minimize;
1796 if (self->functions & OB_CLIENT_FUNC_RESIZE)
1797 actions[num++] = prop_atoms.net_wm_action_resize;
1798 if (self->functions & OB_CLIENT_FUNC_FULLSCREEN)
1799 actions[num++] = prop_atoms.net_wm_action_fullscreen;
1800 if (self->functions & OB_CLIENT_FUNC_MAXIMIZE) {
1801 actions[num++] = prop_atoms.net_wm_action_maximize_horz;
1802 actions[num++] = prop_atoms.net_wm_action_maximize_vert;
1804 if (self->functions & OB_CLIENT_FUNC_ABOVE)
1805 actions[num++] = prop_atoms.net_wm_action_above;
1806 if (self->functions & OB_CLIENT_FUNC_BELOW)
1807 actions[num++] = prop_atoms.net_wm_action_below;
1808 if (self->functions & OB_CLIENT_FUNC_UNDECORATE)
1809 actions[num++] = prop_atoms.ob_wm_action_undecorate;
1811 PROP_SETA32(self->window, net_wm_allowed_actions, atom, actions, num);
1813 /* make sure the window isn't breaking any rules now */
1815 if (!(self->functions & OB_CLIENT_FUNC_SHADE) && self->shaded) {
1816 if (self->frame) client_shade(self, FALSE);
1817 else self->shaded = FALSE;
1819 if (!(self->functions & OB_CLIENT_FUNC_ICONIFY) && self->iconic) {
1820 if (self->frame) client_iconify(self, FALSE, TRUE, FALSE);
1821 else self->iconic = FALSE;
1823 if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) && self->fullscreen) {
1824 if (self->frame) client_fullscreen(self, FALSE);
1825 else self->fullscreen = FALSE;
1827 if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE) && (self->max_horz ||
1829 if (self->frame) client_maximize(self, FALSE, 0);
1830 else self->max_vert = self->max_horz = FALSE;
1834 void client_reconfigure(ObClient *self)
1836 /* by making this pass FALSE for user, we avoid the emacs event storm where
1837 every configurenotify causes an update in its normal hints, i think this
1838 is generally what we want anyways... */
1839 client_configure(self, self->area.x, self->area.y,
1840 self->area.width, self->area.height, FALSE, TRUE);
1843 void client_update_wmhints(ObClient *self)
1847 /* assume a window takes input if it doesnt specify */
1848 self->can_focus = TRUE;
1850 if ((hints = XGetWMHints(ob_display, self->window)) != NULL) {
1853 if (hints->flags & InputHint)
1854 self->can_focus = hints->input;
1856 /* only do this when first managing the window *AND* when we aren't
1858 if (ob_state() != OB_STATE_STARTING && self->frame == NULL)
1859 if (hints->flags & StateHint)
1860 self->iconic = hints->initial_state == IconicState;
1863 self->urgent = (hints->flags & XUrgencyHint);
1864 if (self->urgent && !ur)
1865 client_hilite(self, TRUE);
1866 else if (!self->urgent && ur && self->demands_attention)
1867 client_hilite(self, FALSE);
1869 if (!(hints->flags & WindowGroupHint))
1870 hints->window_group = None;
1872 /* did the group state change? */
1873 if (hints->window_group !=
1874 (self->group ? self->group->leader : None))
1876 ObGroup *oldgroup = self->group;
1878 /* remove from the old group if there was one */
1879 if (self->group != NULL) {
1880 group_remove(self->group, self);
1884 /* add ourself to the group if we have one */
1885 if (hints->window_group != None) {
1886 self->group = group_add(hints->window_group, self);
1889 /* Put ourselves into the new group's transient tree, and remove
1890 ourselves from the old group's */
1891 client_update_transient_tree(self, oldgroup, self->group,
1892 self->transient_for,
1893 self->transient_for);
1895 /* Lastly, being in a group, or not, can change if the window is
1896 transient for anything.
1898 The logic for this is:
1899 self->transient = TRUE always if the window wants to be
1900 transient for something, even if transient_for was NULL because
1901 it wasn't in a group before.
1903 If transient_for was NULL and oldgroup was NULL we can assume
1904 that when we add the new group, it will become transient for
1907 If transient_for was OB_TRAN_GROUP, then it must have already
1908 had a group. If it is getting a new group, the above call to
1909 client_update_transient_tree has already taken care of
1910 everything ! If it is losing all group status then it will
1911 no longer be transient for anything and that needs to be
1914 if (self->transient &&
1915 ((self->transient_for == NULL && oldgroup == NULL) ||
1916 (self->transient_for == OB_TRAN_GROUP && !self->group)))
1917 client_update_transient_for(self);
1920 /* the WM_HINTS can contain an icon */
1921 if (hints->flags & IconPixmapHint)
1922 client_update_icons(self);
1928 void client_update_title(ObClient *self)
1931 gchar *visible = NULL;
1933 g_free(self->title);
1936 if (!PROP_GETS(self->window, net_wm_name, utf8, &data)) {
1937 /* try old x stuff */
1938 if (!(PROP_GETS(self->window, wm_name, locale, &data)
1939 || PROP_GETS(self->window, wm_name, utf8, &data))) {
1940 if (self->transient) {
1942 GNOME alert windows are not given titles:
1943 http://developer.gnome.org/projects/gup/hig/draft_hig_new/windows-alert.html
1945 data = g_strdup("");
1947 data = g_strdup("Unnamed Window");
1951 if (self->client_machine) {
1952 visible = g_strdup_printf("%s (%s)", data, self->client_machine);
1957 PROP_SETS(self->window, net_wm_visible_name, visible);
1958 self->title = visible;
1961 frame_adjust_title(self->frame);
1963 /* update the icon title */
1965 g_free(self->icon_title);
1968 if (!PROP_GETS(self->window, net_wm_icon_name, utf8, &data))
1969 /* try old x stuff */
1970 if (!(PROP_GETS(self->window, wm_icon_name, locale, &data) ||
1971 PROP_GETS(self->window, wm_icon_name, utf8, &data)))
1972 data = g_strdup(self->title);
1974 PROP_SETS(self->window, net_wm_visible_icon_name, data);
1975 self->icon_title = data;
1978 void client_update_strut(ObClient *self)
1982 gboolean got = FALSE;
1985 if (PROP_GETA32(self->window, net_wm_strut_partial, cardinal,
1989 STRUT_PARTIAL_SET(strut,
1990 data[0], data[2], data[1], data[3],
1991 data[4], data[5], data[8], data[9],
1992 data[6], data[7], data[10], data[11]);
1998 PROP_GETA32(self->window, net_wm_strut, cardinal, &data, &num)) {
2004 /* use the screen's width/height */
2005 a = screen_physical_area();
2007 STRUT_PARTIAL_SET(strut,
2008 data[0], data[2], data[1], data[3],
2009 a->y, a->y + a->height - 1,
2010 a->x, a->x + a->width - 1,
2011 a->y, a->y + a->height - 1,
2012 a->x, a->x + a->width - 1);
2018 STRUT_PARTIAL_SET(strut, 0, 0, 0, 0,
2019 0, 0, 0, 0, 0, 0, 0, 0);
2021 if (!STRUT_EQUAL(strut, self->strut)) {
2022 self->strut = strut;
2024 /* updating here is pointless while we're being mapped cuz we're not in
2025 the client list yet */
2027 screen_update_areas();
2031 void client_update_icons(ObClient *self)
2037 for (i = 0; i < self->nicons; ++i)
2038 g_free(self->icons[i].data);
2039 if (self->nicons > 0)
2040 g_free(self->icons);
2043 if (PROP_GETA32(self->window, net_wm_icon, cardinal, &data, &num)) {
2044 /* figure out how many valid icons are in here */
2046 while (num - i > 2) {
2050 if (i > num || w*h == 0) break;
2054 self->icons = g_new(ObClientIcon, self->nicons);
2056 /* store the icons */
2058 for (j = 0; j < self->nicons; ++j) {
2061 w = self->icons[j].width = data[i++];
2062 h = self->icons[j].height = data[i++];
2064 if (w*h == 0) continue;
2066 self->icons[j].data = g_new(RrPixel32, w * h);
2067 for (x = 0, y = 0, t = 0; t < w * h; ++t, ++x, ++i) {
2072 self->icons[j].data[t] =
2073 (((data[i] >> 24) & 0xff) << RrDefaultAlphaOffset) +
2074 (((data[i] >> 16) & 0xff) << RrDefaultRedOffset) +
2075 (((data[i] >> 8) & 0xff) << RrDefaultGreenOffset) +
2076 (((data[i] >> 0) & 0xff) << RrDefaultBlueOffset);
2085 if ((hints = XGetWMHints(ob_display, self->window))) {
2086 if (hints->flags & IconPixmapHint) {
2088 self->icons = g_new(ObClientIcon, self->nicons);
2089 xerror_set_ignore(TRUE);
2090 if (!RrPixmapToRGBA(ob_rr_inst,
2092 (hints->flags & IconMaskHint ?
2093 hints->icon_mask : None),
2094 &self->icons[self->nicons-1].width,
2095 &self->icons[self->nicons-1].height,
2096 &self->icons[self->nicons-1].data)){
2097 g_free(&self->icons[self->nicons-1]);
2100 xerror_set_ignore(FALSE);
2106 /* set the default icon onto the window
2107 in theory, this could be a race, but if a window doesn't set an icon
2108 or removes it entirely, it's not very likely it is going to set one
2109 right away afterwards */
2110 if (self->nicons == 0) {
2111 RrPixel32 *icon = ob_rr_theme->def_win_icon;
2114 data = g_new(gulong, 48*48+2);
2115 data[0] = data[1] = 48;
2116 for (i = 0; i < 48*48; ++i)
2117 data[i+2] = (((icon[i] >> RrDefaultAlphaOffset) & 0xff) << 24) +
2118 (((icon[i] >> RrDefaultRedOffset) & 0xff) << 16) +
2119 (((icon[i] >> RrDefaultGreenOffset) & 0xff) << 8) +
2120 (((icon[i] >> RrDefaultBlueOffset) & 0xff) << 0);
2121 PROP_SETA32(self->window, net_wm_icon, cardinal, data, 48*48+2);
2123 } else if (self->frame)
2124 /* don't draw the icon empty if we're just setting one now anyways,
2125 we'll get the property change any second */
2126 frame_adjust_icon(self->frame);
2129 void client_update_user_time(ObClient *self)
2132 gboolean got = FALSE;
2134 if (self->user_time_window)
2135 got = PROP_GET32(self->user_time_window,
2136 net_wm_user_time, cardinal, &time);
2138 got = PROP_GET32(self->window, net_wm_user_time, cardinal, &time);
2141 /* we set this every time, not just when it grows, because in practice
2142 sometimes time goes backwards! (ntpdate.. yay....) so.. if it goes
2143 backward we don't want all windows to stop focusing. we'll just
2144 assume noone is setting times older than the last one, cuz that
2145 would be pretty stupid anyways
2147 self->user_time = time;
2149 /*ob_debug("window %s user time %u\n", self->title, time);*/
2153 void client_update_user_time_window(ObClient *self)
2157 if (!PROP_GET32(self->window, net_wm_user_time_window, window, &w))
2160 if (w != self->user_time_window) {
2161 /* remove the old window */
2162 propwin_remove(self->user_time_window, OB_PROPWIN_USER_TIME, self);
2163 self->user_time_window = None;
2165 if (self->group && self->group->leader == w) {
2166 ob_debug_type(OB_DEBUG_APP_BUGS, "Window is setting its "
2167 "_NET_WM_USER_TYPE_WINDOW to its group leader\n");
2168 /* do it anyways..? */
2170 else if (w == self->window) {
2171 ob_debug_type(OB_DEBUG_APP_BUGS, "Window is setting its "
2172 "_NET_WM_USER_TIME_WINDOW to itself\n");
2173 w = None; /* don't do it */
2176 /* add the new window */
2177 propwin_add(w, OB_PROPWIN_USER_TIME, self);
2178 self->user_time_window = w;
2180 /* and update from it */
2181 client_update_user_time(self);
2185 void client_update_icon_geometry(ObClient *self)
2190 RECT_SET(self->icon_geometry, 0, 0, 0, 0);
2192 if (PROP_GETA32(self->window, net_wm_icon_geometry, cardinal, &data, &num)
2195 /* don't let them set it with an area < 0 */
2196 RECT_SET(self->icon_geometry, data[0], data[1],
2197 MAX(data[2],0), MAX(data[3],0));
2201 static void client_get_session_ids(ObClient *self)
2208 if (!PROP_GET32(self->window, wm_client_leader, window, &leader))
2211 /* get the SM_CLIENT_ID */
2214 got = PROP_GETS(leader, sm_client_id, locale, &self->sm_client_id);
2216 PROP_GETS(self->window, sm_client_id, locale, &self->sm_client_id);
2218 /* get the WM_CLASS (name and class). make them "" if they are not
2222 got = PROP_GETSS(leader, wm_class, locale, &ss);
2224 got = PROP_GETSS(self->window, wm_class, locale, &ss);
2228 self->name = g_strdup(ss[0]);
2230 self->class = g_strdup(ss[1]);
2235 if (self->name == NULL) self->name = g_strdup("");
2236 if (self->class == NULL) self->class = g_strdup("");
2238 /* get the WM_WINDOW_ROLE. make it "" if it is not provided */
2241 got = PROP_GETS(leader, wm_window_role, locale, &s);
2243 got = PROP_GETS(self->window, wm_window_role, locale, &s);
2248 self->role = g_strdup("");
2250 /* get the WM_COMMAND */
2254 got = PROP_GETSS(leader, wm_command, locale, &ss);
2256 got = PROP_GETSS(self->window, wm_command, locale, &ss);
2259 /* merge/mash them all together */
2260 gchar *merge = NULL;
2263 for (i = 0; ss[i]; ++i) {
2266 merge = g_strconcat(merge, ss[i], NULL);
2268 merge = g_strconcat(ss[i], NULL);
2273 self->wm_command = merge;
2276 /* get the WM_CLIENT_MACHINE */
2279 got = PROP_GETS(leader, wm_client_machine, locale, &s);
2281 got = PROP_GETS(self->window, wm_client_machine, locale, &s);
2284 gchar localhost[128];
2286 gethostname(localhost, 127);
2287 localhost[127] = '\0';
2288 if (strcmp(localhost, s) != 0)
2289 self->client_machine = s;
2295 static void client_change_wm_state(ObClient *self)
2300 old = self->wmstate;
2302 if (self->shaded || self->iconic ||
2303 (self->desktop != DESKTOP_ALL && self->desktop != screen_desktop))
2305 self->wmstate = IconicState;
2307 self->wmstate = NormalState;
2309 if (old != self->wmstate) {
2310 PROP_MSG(self->window, kde_wm_change_state,
2311 self->wmstate, 1, 0, 0);
2313 state[0] = self->wmstate;
2315 PROP_SETA32(self->window, wm_state, wm_state, state, 2);
2316 ob_debug("setting wm_state %d\n", self->wmstate);
2320 static void client_change_state(ObClient *self)
2322 gulong netstate[11];
2327 netstate[num++] = prop_atoms.net_wm_state_modal;
2329 netstate[num++] = prop_atoms.net_wm_state_shaded;
2331 netstate[num++] = prop_atoms.net_wm_state_hidden;
2332 if (self->skip_taskbar)
2333 netstate[num++] = prop_atoms.net_wm_state_skip_taskbar;
2334 if (self->skip_pager)
2335 netstate[num++] = prop_atoms.net_wm_state_skip_pager;
2336 if (self->fullscreen)
2337 netstate[num++] = prop_atoms.net_wm_state_fullscreen;
2339 netstate[num++] = prop_atoms.net_wm_state_maximized_vert;
2341 netstate[num++] = prop_atoms.net_wm_state_maximized_horz;
2343 netstate[num++] = prop_atoms.net_wm_state_above;
2345 netstate[num++] = prop_atoms.net_wm_state_below;
2346 if (self->demands_attention)
2347 netstate[num++] = prop_atoms.net_wm_state_demands_attention;
2348 if (self->undecorated)
2349 netstate[num++] = prop_atoms.ob_wm_state_undecorated;
2350 PROP_SETA32(self->window, net_wm_state, atom, netstate, num);
2353 frame_adjust_state(self->frame);
2356 ObClient *client_search_focus_tree(ObClient *self)
2361 for (it = self->transients; it; it = g_slist_next(it)) {
2362 if (client_focused(it->data)) return it->data;
2363 if ((ret = client_search_focus_tree(it->data))) return ret;
2368 ObClient *client_search_focus_tree_full(ObClient *self)
2370 if (self->transient_for) {
2371 if (self->transient_for != OB_TRAN_GROUP) {
2372 return client_search_focus_tree_full(self->transient_for);
2375 gboolean recursed = FALSE;
2377 for (it = self->group->members; it; it = g_slist_next(it))
2378 if (!((ObClient*)it->data)->transient_for) {
2380 if ((c = client_search_focus_tree_full(it->data)))
2389 /* this function checks the whole tree, the client_search_focus_tree~
2390 does not, so we need to check this window */
2391 if (client_focused(self))
2393 return client_search_focus_tree(self);
2396 static ObStackingLayer calc_layer(ObClient *self)
2400 if (self->type == OB_CLIENT_TYPE_DESKTOP)
2401 l = OB_STACKING_LAYER_DESKTOP;
2402 else if (self->type == OB_CLIENT_TYPE_DOCK) {
2403 if (self->below) l = OB_STACKING_LAYER_NORMAL;
2404 else l = OB_STACKING_LAYER_ABOVE;
2406 else if ((self->fullscreen ||
2407 /* No decorations and fills the monitor = oldskool fullscreen.
2408 But not for maximized windows.
2410 (self->decorations == 0 &&
2411 !(self->max_horz && self->max_vert) &&
2412 RECT_EQUAL(self->area,
2413 *screen_physical_area_monitor
2414 (client_monitor(self))))) &&
2415 (client_focused(self) || client_search_focus_tree(self)))
2416 l = OB_STACKING_LAYER_FULLSCREEN;
2417 else if (self->above) l = OB_STACKING_LAYER_ABOVE;
2418 else if (self->below) l = OB_STACKING_LAYER_BELOW;
2419 else l = OB_STACKING_LAYER_NORMAL;
2424 static void client_calc_layer_recursive(ObClient *self, ObClient *orig,
2425 ObStackingLayer min)
2427 ObStackingLayer old, own;
2431 own = calc_layer(self);
2432 self->layer = MAX(own, min);
2434 if (self->layer != old) {
2435 stacking_remove(CLIENT_AS_WINDOW(self));
2436 stacking_add_nonintrusive(CLIENT_AS_WINDOW(self));
2439 for (it = self->transients; it; it = g_slist_next(it))
2440 client_calc_layer_recursive(it->data, orig,
2444 void client_calc_layer(ObClient *self)
2451 /* transients take on the layer of their parents */
2452 it = client_search_all_top_parents(self);
2454 for (; it; it = g_slist_next(it))
2455 client_calc_layer_recursive(it->data, orig, 0);
2458 gboolean client_should_show(ObClient *self)
2462 if (client_normal(self) && screen_showing_desktop)
2464 if (self->desktop == screen_desktop || self->desktop == DESKTOP_ALL)
2470 gboolean client_show(ObClient *self)
2472 gboolean show = FALSE;
2474 if (client_should_show(self)) {
2475 frame_show(self->frame);
2478 /* According to the ICCCM (sec 4.1.3.1) when a window is not visible,
2479 it needs to be in IconicState. This includes when it is on another
2482 client_change_wm_state(self);
2487 gboolean client_hide(ObClient *self)
2489 gboolean hide = FALSE;
2491 if (!client_should_show(self)) {
2492 if (self == focus_client) {
2493 /* if there is a grab going on, then we need to cancel it. if we
2494 move focus during the grab, applications will get
2495 NotifyWhileGrabbed events and ignore them !
2497 actions should not rely on being able to move focus during an
2500 if (keyboard_interactively_grabbed())
2501 keyboard_interactive_cancel();
2504 frame_hide(self->frame);
2507 /* According to the ICCCM (sec 4.1.3.1) when a window is not visible,
2508 it needs to be in IconicState. This includes when it is on another
2511 client_change_wm_state(self);
2516 void client_showhide(ObClient *self)
2518 if (!client_show(self))
2522 gboolean client_normal(ObClient *self) {
2523 return ! (self->type == OB_CLIENT_TYPE_DESKTOP ||
2524 self->type == OB_CLIENT_TYPE_DOCK ||
2525 self->type == OB_CLIENT_TYPE_SPLASH);
2528 gboolean client_helper(ObClient *self)
2530 return (self->type == OB_CLIENT_TYPE_UTILITY ||
2531 self->type == OB_CLIENT_TYPE_MENU ||
2532 self->type == OB_CLIENT_TYPE_TOOLBAR);
2535 gboolean client_mouse_focusable(ObClient *self)
2537 return !(self->type == OB_CLIENT_TYPE_MENU ||
2538 self->type == OB_CLIENT_TYPE_TOOLBAR ||
2539 self->type == OB_CLIENT_TYPE_SPLASH ||
2540 self->type == OB_CLIENT_TYPE_DOCK);
2543 gboolean client_enter_focusable(ObClient *self)
2545 /* you can focus desktops but it shouldn't on enter */
2546 return (client_mouse_focusable(self) &&
2547 self->type != OB_CLIENT_TYPE_DESKTOP);
2551 static void client_apply_startup_state(ObClient *self)
2553 /* set the desktop hint, to make sure that it always exists */
2554 PROP_SET32(self->window, net_wm_desktop, cardinal, self->desktop);
2556 /* these are in a carefully crafted order.. */
2559 self->iconic = FALSE;
2560 client_iconify(self, TRUE, FALSE, TRUE);
2562 if (self->fullscreen) {
2563 self->fullscreen = FALSE;
2564 client_fullscreen(self, TRUE);
2566 if (self->undecorated) {
2567 self->undecorated = FALSE;
2568 client_set_undecorated(self, TRUE);
2571 self->shaded = FALSE;
2572 client_shade(self, TRUE);
2574 if (self->demands_attention) {
2575 self->demands_attention = FALSE;
2576 client_hilite(self, TRUE);
2579 if (self->max_vert && self->max_horz) {
2580 self->max_vert = self->max_horz = FALSE;
2581 client_maximize(self, TRUE, 0);
2582 } else if (self->max_vert) {
2583 self->max_vert = FALSE;
2584 client_maximize(self, TRUE, 2);
2585 } else if (self->max_horz) {
2586 self->max_horz = FALSE;
2587 client_maximize(self, TRUE, 1);
2590 /* nothing to do for the other states:
2599 void client_gravity_resize_w(ObClient *self, gint *x, gint oldw, gint neww)
2601 /* these should be the current values. this is for when you're not moving,
2603 g_assert(*x == self->area.x);
2604 g_assert(oldw == self->area.width);
2607 switch (self->gravity) {
2609 case NorthWestGravity:
2611 case SouthWestGravity:
2618 *x -= (neww - oldw) / 2;
2620 case NorthEastGravity:
2622 case SouthEastGravity:
2628 void client_gravity_resize_h(ObClient *self, gint *y, gint oldh, gint newh)
2630 /* these should be the current values. this is for when you're not moving,
2632 g_assert(*y == self->area.y);
2633 g_assert(oldh == self->area.height);
2636 switch (self->gravity) {
2638 case NorthWestGravity:
2640 case NorthEastGravity:
2647 *y -= (newh - oldh) / 2;
2649 case SouthWestGravity:
2651 case SouthEastGravity:
2657 void client_try_configure(ObClient *self, gint *x, gint *y, gint *w, gint *h,
2658 gint *logicalw, gint *logicalh,
2661 Rect desired_area = {*x, *y, *w, *h};
2663 /* make the frame recalculate its dimentions n shit without changing
2664 anything visible for real, this way the constraints below can work with
2665 the updated frame dimensions. */
2666 frame_adjust_area(self->frame, FALSE, TRUE, TRUE);
2668 /* work within the prefered sizes given by the window */
2669 if (!(*w == self->area.width && *h == self->area.height)) {
2670 gint basew, baseh, minw, minh;
2672 /* base size is substituted with min size if not specified */
2673 if (self->base_size.width || self->base_size.height) {
2674 basew = self->base_size.width;
2675 baseh = self->base_size.height;
2677 basew = self->min_size.width;
2678 baseh = self->min_size.height;
2680 /* min size is substituted with base size if not specified */
2681 if (self->min_size.width || self->min_size.height) {
2682 minw = self->min_size.width;
2683 minh = self->min_size.height;
2685 minw = self->base_size.width;
2686 minh = self->base_size.height;
2689 /* if this is a user-requested resize, then check against min/max
2692 /* smaller than min size or bigger than max size? */
2693 if (*w > self->max_size.width) *w = self->max_size.width;
2694 if (*w < minw) *w = minw;
2695 if (*h > self->max_size.height) *h = self->max_size.height;
2696 if (*h < minh) *h = minh;
2701 /* keep to the increments */
2702 *w /= self->size_inc.width;
2703 *h /= self->size_inc.height;
2705 /* you cannot resize to nothing */
2706 if (basew + *w < 1) *w = 1 - basew;
2707 if (baseh + *h < 1) *h = 1 - baseh;
2709 /* save the logical size */
2710 *logicalw = self->size_inc.width > 1 ? *w : *w + basew;
2711 *logicalh = self->size_inc.height > 1 ? *h : *h + baseh;
2713 *w *= self->size_inc.width;
2714 *h *= self->size_inc.height;
2719 /* adjust the height to match the width for the aspect ratios.
2720 for this, min size is not substituted for base size ever. */
2721 *w -= self->base_size.width;
2722 *h -= self->base_size.height;
2724 if (!self->fullscreen) {
2725 if (self->min_ratio)
2726 if (*h * self->min_ratio > *w) {
2727 *h = (gint)(*w / self->min_ratio);
2729 /* you cannot resize to nothing */
2732 *w = (gint)(*h * self->min_ratio);
2735 if (self->max_ratio)
2736 if (*h * self->max_ratio < *w) {
2737 *h = (gint)(*w / self->max_ratio);
2739 /* you cannot resize to nothing */
2742 *w = (gint)(*h * self->min_ratio);
2747 *w += self->base_size.width;
2748 *h += self->base_size.height;
2751 /* gets the frame's position */
2752 frame_client_gravity(self->frame, x, y, *w, *h);
2754 /* these positions are frame positions, not client positions */
2756 /* set the size and position if fullscreen */
2757 if (self->fullscreen) {
2761 i = screen_find_monitor(&desired_area);
2762 a = screen_physical_area_monitor(i);
2769 user = FALSE; /* ignore if the client can't be moved/resized when it
2771 } else if (self->max_horz || self->max_vert) {
2775 i = screen_find_monitor(&desired_area);
2776 a = screen_area_monitor(self->desktop, i);
2778 /* set the size and position if maximized */
2779 if (self->max_horz) {
2781 *w = a->width - self->frame->size.left - self->frame->size.right;
2783 if (self->max_vert) {
2785 *h = a->height - self->frame->size.top - self->frame->size.bottom;
2788 user = FALSE; /* ignore if the client can't be moved/resized when it
2792 /* gets the client's position */
2793 frame_frame_gravity(self->frame, x, y, *w, *h);
2795 /* these override the above states! if you cant move you can't move! */
2797 if (!(self->functions & OB_CLIENT_FUNC_MOVE)) {
2801 if (!(self->functions & OB_CLIENT_FUNC_RESIZE)) {
2802 *w = self->area.width;
2803 *h = self->area.height;
2812 void client_configure(ObClient *self, gint x, gint y, gint w, gint h,
2813 gboolean user, gboolean final)
2816 gboolean send_resize_client;
2817 gboolean moved = FALSE, resized = FALSE;
2818 gboolean fmoved, fresized;
2819 guint fdecor = self->frame->decorations;
2820 gboolean fhorz = self->frame->max_horz;
2821 gboolean fvert = self->frame->max_vert;
2822 gint logicalw, logicalh;
2824 /* find the new x, y, width, and height (and logical size) */
2825 client_try_configure(self, &x, &y, &w, &h, &logicalw, &logicalh, user);
2827 /* set the logical size if things changed */
2828 if (!(w == self->area.width && h == self->area.height))
2829 SIZE_SET(self->logical_size, logicalw, logicalh);
2831 /* figure out if we moved or resized or what */
2832 moved = x != self->area.x || y != self->area.y;
2833 resized = w != self->area.width || h != self->area.height;
2835 oldw = self->area.width;
2836 oldh = self->area.height;
2837 RECT_SET(self->area, x, y, w, h);
2839 /* for app-requested resizes, always resize if 'resized' is true.
2840 for user-requested ones, only resize if final is true, or when
2841 resizing in redraw mode */
2842 send_resize_client = ((!user && resized) ||
2844 (resized && config_resize_redraw))));
2846 /* if the client is enlarging, then resize the client before the frame */
2847 if (send_resize_client && (w > oldw || h > oldh)) {
2848 XResizeWindow(ob_display, self->window,
2849 MAX(w, oldw), MAX(h, oldh));
2850 /* resize the plate to show the client padding color underneath */
2851 frame_adjust_client_area(self->frame);
2854 /* find the frame's dimensions and move/resize it */
2858 /* if decorations changed, then readjust everything for the frame */
2859 if (self->decorations != fdecor ||
2860 self->max_horz != fhorz || self->max_vert != fvert)
2862 fmoved = fresized = TRUE;
2865 /* adjust the frame */
2866 if (fmoved || fresized)
2867 frame_adjust_area(self->frame, fmoved, fresized, FALSE);
2869 if ((!user || (user && final)) && !resized)
2873 POINT_SET(self->root_pos,
2874 self->frame->area.x + self->frame->size.left -
2876 self->frame->area.y + self->frame->size.top -
2877 self->border_width);
2879 event.type = ConfigureNotify;
2880 event.xconfigure.display = ob_display;
2881 event.xconfigure.event = self->window;
2882 event.xconfigure.window = self->window;
2884 ob_debug("Sending ConfigureNotify to %s for %d,%d %dx%d\n",
2885 self->title, self->root_pos.x, self->root_pos.y, w, h);
2887 /* root window real coords */
2888 event.xconfigure.x = self->root_pos.x;
2889 event.xconfigure.y = self->root_pos.y;
2890 event.xconfigure.width = w;
2891 event.xconfigure.height = h;
2892 event.xconfigure.border_width = 0;
2893 event.xconfigure.above = self->frame->plate;
2894 event.xconfigure.override_redirect = FALSE;
2895 XSendEvent(event.xconfigure.display, event.xconfigure.window,
2896 FALSE, StructureNotifyMask, &event);
2899 /* if the client is shrinking, then resize the frame before the client */
2900 if (send_resize_client && (w <= oldw || h <= oldh)) {
2901 /* resize the plate to show the client padding color underneath */
2902 frame_adjust_client_area(self->frame);
2904 if (send_resize_client)
2905 XResizeWindow(ob_display, self->window, w, h);
2911 void client_fullscreen(ObClient *self, gboolean fs)
2915 if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) || /* can't */
2916 self->fullscreen == fs) return; /* already done */
2918 self->fullscreen = fs;
2919 client_change_state(self); /* change the state hints on the client */
2922 self->pre_fullscreen_area = self->area;
2923 /* if the window is maximized, its area isn't all that meaningful.
2924 save it's premax area instead. */
2925 if (self->max_horz) {
2926 self->pre_fullscreen_area.x = self->pre_max_area.x;
2927 self->pre_fullscreen_area.width = self->pre_max_area.width;
2929 if (self->max_vert) {
2930 self->pre_fullscreen_area.y = self->pre_max_area.y;
2931 self->pre_fullscreen_area.height = self->pre_max_area.height;
2934 /* these will help configure_full figure out where to fullscreen
2938 w = self->area.width;
2939 h = self->area.height;
2941 g_assert(self->pre_fullscreen_area.width > 0 &&
2942 self->pre_fullscreen_area.height > 0);
2944 x = self->pre_fullscreen_area.x;
2945 y = self->pre_fullscreen_area.y;
2946 w = self->pre_fullscreen_area.width;
2947 h = self->pre_fullscreen_area.height;
2948 RECT_SET(self->pre_fullscreen_area, 0, 0, 0, 0);
2951 client_setup_decor_and_functions(self);
2953 client_move_resize(self, x, y, w, h);
2955 /* and adjust our layer/stacking. do this after resizing the window,
2956 and applying decorations, because windows which fill the screen are
2957 considered "fullscreen" and it affects their layer */
2958 client_calc_layer(self);
2961 /* try focus us when we go into fullscreen mode */
2966 static void client_iconify_recursive(ObClient *self,
2967 gboolean iconic, gboolean curdesk,
2968 gboolean hide_animation)
2971 gboolean changed = FALSE;
2974 if (self->iconic != iconic) {
2975 ob_debug("%sconifying window: 0x%lx\n", (iconic ? "I" : "Uni"),
2979 /* don't let non-normal windows iconify along with their parents
2981 if (client_normal(self)) {
2982 self->iconic = iconic;
2984 /* update the focus lists.. iconic windows go to the bottom of
2985 the list, put the new iconic window at the 'top of the
2987 focus_order_to_top(self);
2992 self->iconic = iconic;
2994 if (curdesk && self->desktop != screen_desktop &&
2995 self->desktop != DESKTOP_ALL)
2996 client_set_desktop(self, screen_desktop, FALSE);
2998 /* this puts it after the current focused window */
2999 focus_order_remove(self);
3000 focus_order_add_new(self);
3007 client_change_state(self);
3008 if (config_animate_iconify && !hide_animation)
3009 frame_begin_iconify_animation(self->frame, iconic);
3010 /* do this after starting the animation so it doesn't flash */
3011 client_showhide(self);
3014 /* iconify all direct transients, and deiconify all transients
3016 for (it = self->transients; it; it = g_slist_next(it))
3017 if (it->data != self)
3018 if (client_is_direct_child(self, it->data) || !iconic)
3019 client_iconify_recursive(it->data, iconic, curdesk,
3023 void client_iconify(ObClient *self, gboolean iconic, gboolean curdesk,
3024 gboolean hide_animation)
3026 if (self->functions & OB_CLIENT_FUNC_ICONIFY || !iconic) {
3027 /* move up the transient chain as far as possible first */
3028 self = client_search_top_normal_parent(self);
3029 client_iconify_recursive(self, iconic, curdesk, hide_animation);
3033 void client_maximize(ObClient *self, gboolean max, gint dir)
3037 g_assert(dir == 0 || dir == 1 || dir == 2);
3038 if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE)) return; /* can't */
3040 /* check if already done */
3042 if (dir == 0 && self->max_horz && self->max_vert) return;
3043 if (dir == 1 && self->max_horz) return;
3044 if (dir == 2 && self->max_vert) return;
3046 if (dir == 0 && !self->max_horz && !self->max_vert) return;
3047 if (dir == 1 && !self->max_horz) return;
3048 if (dir == 2 && !self->max_vert) return;
3051 /* these will help configure_full figure out which screen to fill with
3055 w = self->area.width;
3056 h = self->area.height;
3059 if ((dir == 0 || dir == 1) && !self->max_horz) { /* horz */
3060 RECT_SET(self->pre_max_area,
3061 self->area.x, self->pre_max_area.y,
3062 self->area.width, self->pre_max_area.height);
3064 if ((dir == 0 || dir == 2) && !self->max_vert) { /* vert */
3065 RECT_SET(self->pre_max_area,
3066 self->pre_max_area.x, self->area.y,
3067 self->pre_max_area.width, self->area.height);
3070 if ((dir == 0 || dir == 1) && self->max_horz) { /* horz */
3071 g_assert(self->pre_max_area.width > 0);
3073 x = self->pre_max_area.x;
3074 w = self->pre_max_area.width;
3076 RECT_SET(self->pre_max_area, 0, self->pre_max_area.y,
3077 0, self->pre_max_area.height);
3079 if ((dir == 0 || dir == 2) && self->max_vert) { /* vert */
3080 g_assert(self->pre_max_area.height > 0);
3082 y = self->pre_max_area.y;
3083 h = self->pre_max_area.height;
3085 RECT_SET(self->pre_max_area, self->pre_max_area.x, 0,
3086 self->pre_max_area.width, 0);
3090 if (dir == 0 || dir == 1) /* horz */
3091 self->max_horz = max;
3092 if (dir == 0 || dir == 2) /* vert */
3093 self->max_vert = max;
3095 client_change_state(self); /* change the state hints on the client */
3097 client_setup_decor_and_functions(self);
3099 client_move_resize(self, x, y, w, h);
3102 void client_shade(ObClient *self, gboolean shade)
3104 if ((!(self->functions & OB_CLIENT_FUNC_SHADE) &&
3105 shade) || /* can't shade */
3106 self->shaded == shade) return; /* already done */
3108 self->shaded = shade;
3109 client_change_state(self);
3110 client_change_wm_state(self); /* the window is being hidden/shown */
3111 /* resize the frame to just the titlebar */
3112 frame_adjust_area(self->frame, FALSE, FALSE, FALSE);
3115 void client_close(ObClient *self)
3119 if (!(self->functions & OB_CLIENT_FUNC_CLOSE)) return;
3121 /* in the case that the client provides no means to requesting that it
3122 close, we just kill it */
3123 if (!self->delete_window)
3127 XXX: itd be cool to do timeouts and shit here for killing the client's
3129 like... if the window is around after 5 seconds, then the close button
3130 turns a nice red, and if this function is called again, the client is
3134 ce.xclient.type = ClientMessage;
3135 ce.xclient.message_type = prop_atoms.wm_protocols;
3136 ce.xclient.display = ob_display;
3137 ce.xclient.window = self->window;
3138 ce.xclient.format = 32;
3139 ce.xclient.data.l[0] = prop_atoms.wm_delete_window;
3140 ce.xclient.data.l[1] = event_curtime;
3141 ce.xclient.data.l[2] = 0l;
3142 ce.xclient.data.l[3] = 0l;
3143 ce.xclient.data.l[4] = 0l;
3144 XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
3147 void client_kill(ObClient *self)
3149 XKillClient(ob_display, self->window);
3152 void client_hilite(ObClient *self, gboolean hilite)
3154 if (self->demands_attention == hilite)
3155 return; /* no change */
3157 /* don't allow focused windows to hilite */
3158 self->demands_attention = hilite && !client_focused(self);
3159 if (self->frame != NULL) { /* if we're mapping, just set the state */
3160 if (self->demands_attention)
3161 frame_flash_start(self->frame);
3163 frame_flash_stop(self->frame);
3164 client_change_state(self);
3168 void client_set_desktop_recursive(ObClient *self,
3175 if (target != self->desktop && self->type != OB_CLIENT_TYPE_DESKTOP) {
3177 ob_debug("Setting desktop %u\n", target+1);
3179 g_assert(target < screen_num_desktops || target == DESKTOP_ALL);
3181 old = self->desktop;
3182 self->desktop = target;
3183 PROP_SET32(self->window, net_wm_desktop, cardinal, target);
3184 /* the frame can display the current desktop state */
3185 frame_adjust_state(self->frame);
3186 /* 'move' the window to the new desktop */
3188 client_showhide(self);
3189 /* raise if it was not already on the desktop */
3190 if (old != DESKTOP_ALL)
3191 stacking_raise(CLIENT_AS_WINDOW(self));
3192 if (STRUT_EXISTS(self->strut))
3193 screen_update_areas();
3196 /* move all transients */
3197 for (it = self->transients; it; it = g_slist_next(it))
3198 if (it->data != self)
3199 if (client_is_direct_child(self, it->data))
3200 client_set_desktop_recursive(it->data, target, donthide);
3203 void client_set_desktop(ObClient *self, guint target,
3206 self = client_search_top_normal_parent(self);
3207 client_set_desktop_recursive(self, target, donthide);
3210 gboolean client_is_direct_child(ObClient *parent, ObClient *child)
3212 while (child != parent &&
3213 child->transient_for && child->transient_for != OB_TRAN_GROUP)
3214 child = child->transient_for;
3215 return child == parent;
3218 ObClient *client_search_modal_child(ObClient *self)
3223 for (it = self->transients; it; it = g_slist_next(it)) {
3224 ObClient *c = it->data;
3225 if ((ret = client_search_modal_child(c))) return ret;
3226 if (c->modal) return c;
3231 gboolean client_validate(ObClient *self)
3235 XSync(ob_display, FALSE); /* get all events on the server */
3237 if (XCheckTypedWindowEvent(ob_display, self->window, DestroyNotify, &e) ||
3238 XCheckTypedWindowEvent(ob_display, self->window, UnmapNotify, &e)) {
3239 XPutBackEvent(ob_display, &e);
3246 void client_set_wm_state(ObClient *self, glong state)
3248 if (state == self->wmstate) return; /* no change */
3252 client_iconify(self, TRUE, TRUE, FALSE);
3255 client_iconify(self, FALSE, TRUE, FALSE);
3260 void client_set_state(ObClient *self, Atom action, glong data1, glong data2)
3262 gboolean shaded = self->shaded;
3263 gboolean fullscreen = self->fullscreen;
3264 gboolean undecorated = self->undecorated;
3265 gboolean max_horz = self->max_horz;
3266 gboolean max_vert = self->max_vert;
3267 gboolean modal = self->modal;
3268 gboolean iconic = self->iconic;
3269 gboolean demands_attention = self->demands_attention;
3270 gboolean above = self->above;
3271 gboolean below = self->below;
3274 if (!(action == prop_atoms.net_wm_state_add ||
3275 action == prop_atoms.net_wm_state_remove ||
3276 action == prop_atoms.net_wm_state_toggle))
3277 /* an invalid action was passed to the client message, ignore it */
3280 for (i = 0; i < 2; ++i) {
3281 Atom state = i == 0 ? data1 : data2;
3283 if (!state) continue;
3285 /* if toggling, then pick whether we're adding or removing */
3286 if (action == prop_atoms.net_wm_state_toggle) {
3287 if (state == prop_atoms.net_wm_state_modal)
3288 action = modal ? prop_atoms.net_wm_state_remove :
3289 prop_atoms.net_wm_state_add;
3290 else if (state == prop_atoms.net_wm_state_maximized_vert)
3291 action = self->max_vert ? prop_atoms.net_wm_state_remove :
3292 prop_atoms.net_wm_state_add;
3293 else if (state == prop_atoms.net_wm_state_maximized_horz)
3294 action = self->max_horz ? prop_atoms.net_wm_state_remove :
3295 prop_atoms.net_wm_state_add;
3296 else if (state == prop_atoms.net_wm_state_shaded)
3297 action = shaded ? prop_atoms.net_wm_state_remove :
3298 prop_atoms.net_wm_state_add;
3299 else if (state == prop_atoms.net_wm_state_skip_taskbar)
3300 action = self->skip_taskbar ?
3301 prop_atoms.net_wm_state_remove :
3302 prop_atoms.net_wm_state_add;
3303 else if (state == prop_atoms.net_wm_state_skip_pager)
3304 action = self->skip_pager ?
3305 prop_atoms.net_wm_state_remove :
3306 prop_atoms.net_wm_state_add;
3307 else if (state == prop_atoms.net_wm_state_hidden)
3308 action = self->iconic ?
3309 prop_atoms.net_wm_state_remove :
3310 prop_atoms.net_wm_state_add;
3311 else if (state == prop_atoms.net_wm_state_fullscreen)
3312 action = fullscreen ?
3313 prop_atoms.net_wm_state_remove :
3314 prop_atoms.net_wm_state_add;
3315 else if (state == prop_atoms.net_wm_state_above)
3316 action = self->above ? prop_atoms.net_wm_state_remove :
3317 prop_atoms.net_wm_state_add;
3318 else if (state == prop_atoms.net_wm_state_below)
3319 action = self->below ? prop_atoms.net_wm_state_remove :
3320 prop_atoms.net_wm_state_add;
3321 else if (state == prop_atoms.net_wm_state_demands_attention)
3322 action = self->demands_attention ?
3323 prop_atoms.net_wm_state_remove :
3324 prop_atoms.net_wm_state_add;
3325 else if (state == prop_atoms.ob_wm_state_undecorated)
3326 action = undecorated ? prop_atoms.net_wm_state_remove :
3327 prop_atoms.net_wm_state_add;
3330 if (action == prop_atoms.net_wm_state_add) {
3331 if (state == prop_atoms.net_wm_state_modal) {
3333 } else if (state == prop_atoms.net_wm_state_maximized_vert) {
3335 } else if (state == prop_atoms.net_wm_state_maximized_horz) {
3337 } else if (state == prop_atoms.net_wm_state_shaded) {
3339 } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
3340 self->skip_taskbar = TRUE;
3341 } else if (state == prop_atoms.net_wm_state_skip_pager) {
3342 self->skip_pager = TRUE;
3343 } else if (state == prop_atoms.net_wm_state_hidden) {
3345 } else if (state == prop_atoms.net_wm_state_fullscreen) {
3347 } else if (state == prop_atoms.net_wm_state_above) {
3350 } else if (state == prop_atoms.net_wm_state_below) {
3353 } else if (state == prop_atoms.net_wm_state_demands_attention) {
3354 demands_attention = TRUE;
3355 } else if (state == prop_atoms.ob_wm_state_undecorated) {
3359 } else { /* action == prop_atoms.net_wm_state_remove */
3360 if (state == prop_atoms.net_wm_state_modal) {
3362 } else if (state == prop_atoms.net_wm_state_maximized_vert) {
3364 } else if (state == prop_atoms.net_wm_state_maximized_horz) {
3366 } else if (state == prop_atoms.net_wm_state_shaded) {
3368 } else if (state == prop_atoms.net_wm_state_skip_taskbar) {
3369 self->skip_taskbar = FALSE;
3370 } else if (state == prop_atoms.net_wm_state_skip_pager) {
3371 self->skip_pager = FALSE;
3372 } else if (state == prop_atoms.net_wm_state_hidden) {
3374 } else if (state == prop_atoms.net_wm_state_fullscreen) {
3376 } else if (state == prop_atoms.net_wm_state_above) {
3378 } else if (state == prop_atoms.net_wm_state_below) {
3380 } else if (state == prop_atoms.net_wm_state_demands_attention) {
3381 demands_attention = FALSE;
3382 } else if (state == prop_atoms.ob_wm_state_undecorated) {
3383 undecorated = FALSE;
3388 if (max_horz != self->max_horz || max_vert != self->max_vert) {
3389 if (max_horz != self->max_horz && max_vert != self->max_vert) {
3391 if (max_horz == max_vert) { /* both going the same way */
3392 client_maximize(self, max_horz, 0);
3394 client_maximize(self, max_horz, 1);
3395 client_maximize(self, max_vert, 2);
3399 if (max_horz != self->max_horz)
3400 client_maximize(self, max_horz, 1);
3402 client_maximize(self, max_vert, 2);
3405 /* change fullscreen state before shading, as it will affect if the window
3407 if (fullscreen != self->fullscreen)
3408 client_fullscreen(self, fullscreen);
3409 if (shaded != self->shaded)
3410 client_shade(self, shaded);
3411 if (undecorated != self->undecorated)
3412 client_set_undecorated(self, undecorated);
3413 if (above != self->above || below != self->below) {
3414 self->above = above;
3415 self->below = below;
3416 client_calc_layer(self);
3419 if (modal != self->modal) {
3420 self->modal = modal;
3421 /* when a window changes modality, then its stacking order with its
3422 transients needs to change */
3423 stacking_raise(CLIENT_AS_WINDOW(self));
3425 /* it also may get focused. if something is focused that shouldn't
3426 be focused anymore, then move the focus */
3427 if (focus_client && client_focus_target(focus_client) != focus_client)
3428 client_focus(focus_client);
3431 if (iconic != self->iconic)
3432 client_iconify(self, iconic, FALSE, FALSE);
3434 if (demands_attention != self->demands_attention)
3435 client_hilite(self, demands_attention);
3437 client_change_state(self); /* change the hint to reflect these changes */
3440 ObClient *client_focus_target(ObClient *self)
3442 ObClient *child = NULL;
3444 child = client_search_modal_child(self);
3445 if (child) return child;
3449 gboolean client_can_focus(ObClient *self)
3451 /* choose the correct target */
3452 self = client_focus_target(self);
3454 if (!self->frame->visible)
3457 if (!(self->can_focus || self->focus_notify))
3463 gboolean client_focus(ObClient *self)
3465 /* choose the correct target */
3466 self = client_focus_target(self);
3468 if (!client_can_focus(self)) {
3469 if (!self->frame->visible) {
3470 /* update the focus lists */
3471 focus_order_to_top(self);
3476 ob_debug_type(OB_DEBUG_FOCUS,
3477 "Focusing client \"%s\" at time %u\n",
3478 self->title, event_curtime);
3480 /* if there is a grab going on, then we need to cancel it. if we move
3481 focus during the grab, applications will get NotifyWhileGrabbed events
3484 actions should not rely on being able to move focus during an
3487 if (keyboard_interactively_grabbed())
3488 keyboard_interactive_cancel();
3490 xerror_set_ignore(TRUE);
3491 xerror_occured = FALSE;
3493 if (self->can_focus) {
3494 /* This can cause a BadMatch error with CurrentTime, or if an app
3495 passed in a bad time for _NET_WM_ACTIVE_WINDOW. */
3496 XSetInputFocus(ob_display, self->window, RevertToPointerRoot,
3500 if (self->focus_notify) {
3502 ce.xclient.type = ClientMessage;
3503 ce.xclient.message_type = prop_atoms.wm_protocols;
3504 ce.xclient.display = ob_display;
3505 ce.xclient.window = self->window;
3506 ce.xclient.format = 32;
3507 ce.xclient.data.l[0] = prop_atoms.wm_take_focus;
3508 ce.xclient.data.l[1] = event_curtime;
3509 ce.xclient.data.l[2] = 0l;
3510 ce.xclient.data.l[3] = 0l;
3511 ce.xclient.data.l[4] = 0l;
3512 XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce);
3515 xerror_set_ignore(FALSE);
3517 return !xerror_occured;
3520 /*! Present the client to the user.
3521 @param raise If the client should be raised or not. You should only set
3522 raise to false if you don't care if the window is completely
3525 static void client_present(ObClient *self, gboolean here, gboolean raise)
3527 /* if using focus_delay, stop the timer now so that focus doesn't
3529 event_halt_focus_delay();
3531 if (client_normal(self) && screen_showing_desktop)
3532 screen_show_desktop(FALSE, self);
3534 client_iconify(self, FALSE, here, FALSE);
3535 if (self->desktop != DESKTOP_ALL &&
3536 self->desktop != screen_desktop)
3539 client_set_desktop(self, screen_desktop, FALSE);
3541 screen_set_desktop(self->desktop, FALSE);
3542 } else if (!self->frame->visible)
3543 /* if its not visible for other reasons, then don't mess
3547 client_shade(self, FALSE);
3549 stacking_raise(CLIENT_AS_WINDOW(self));
3554 void client_activate(ObClient *self, gboolean here, gboolean user)
3556 guint32 last_time = focus_client ? focus_client->user_time : CurrentTime;
3557 gboolean allow = FALSE;
3559 /* if the request came from the user, or if nothing is focused, then grant
3561 if the currently focused app doesn't set a user_time, then it can't
3562 benefit from any focus stealing prevention.
3564 if (user || !focus_client || !last_time)
3566 /* otherwise, if they didn't give a time stamp or if it is too old, they
3569 allow = event_curtime && event_time_after(event_curtime, last_time);
3571 ob_debug_type(OB_DEBUG_FOCUS,
3572 "Want to activate window 0x%x with time %u (last time %u), "
3573 "source=%s allowing? %d\n",
3574 self->window, event_curtime, last_time,
3575 (user ? "user" : "application"), allow);
3578 client_present(self, here, TRUE);
3580 /* don't focus it but tell the user it wants attention */
3581 client_hilite(self, TRUE);
3584 static void client_bring_helper_windows_recursive(ObClient *self,
3589 for (it = self->transients; it; it = g_slist_next(it))
3590 client_bring_helper_windows_recursive(it->data, desktop);
3592 if (client_helper(self) &&
3593 self->desktop != desktop && self->desktop != DESKTOP_ALL)
3595 client_set_desktop(self, desktop, FALSE);
3599 void client_bring_helper_windows(ObClient *self)
3601 client_bring_helper_windows_recursive(self, self->desktop);
3604 gboolean client_focused(ObClient *self)
3606 return self == focus_client;
3609 static ObClientIcon* client_icon_recursive(ObClient *self, gint w, gint h)
3612 gulong min_diff, min_i;
3614 if (!self->nicons) {
3615 ObClientIcon *parent = NULL;
3617 if (self->transient_for) {
3618 if (self->transient_for != OB_TRAN_GROUP)
3619 parent = client_icon_recursive(self->transient_for, w, h);
3622 for (it = self->group->members; it; it = g_slist_next(it)) {
3623 ObClient *c = it->data;
3624 if (c != self && !c->transient_for) {
3625 if ((parent = client_icon_recursive(c, w, h)))
3635 /* some kind of crappy approximation to find the icon closest in size to
3636 what we requested, but icons are generally all the same ratio as
3637 eachother so it's good enough. */
3639 min_diff = ABS(self->icons[0].width - w) + ABS(self->icons[0].height - h);
3642 for (i = 1; i < self->nicons; ++i) {
3645 diff = ABS(self->icons[i].width - w) + ABS(self->icons[i].height - h);
3646 if (diff < min_diff) {
3651 return &self->icons[min_i];
3654 const ObClientIcon* client_icon(ObClient *self, gint w, gint h)
3657 static ObClientIcon deficon;
3659 if (!(ret = client_icon_recursive(self, w, h))) {
3660 deficon.width = deficon.height = 48;
3661 deficon.data = ob_rr_theme->def_win_icon;
3667 void client_set_layer(ObClient *self, gint layer)
3671 self->above = FALSE;
3672 } else if (layer == 0) {
3673 self->below = self->above = FALSE;
3675 self->below = FALSE;
3678 client_calc_layer(self);
3679 client_change_state(self); /* reflect this in the state hints */
3682 void client_set_undecorated(ObClient *self, gboolean undecorated)
3684 if (self->undecorated != undecorated &&
3685 /* don't let it undecorate if the function is missing, but let
3687 (self->functions & OB_CLIENT_FUNC_UNDECORATE || !undecorated))
3689 self->undecorated = undecorated;
3690 client_setup_decor_and_functions(self);
3691 client_reconfigure(self); /* show the lack of decorations */
3692 client_change_state(self); /* reflect this in the state hints */
3696 guint client_monitor(ObClient *self)
3698 return screen_find_monitor(&self->frame->area);
3701 ObClient *client_search_top_normal_parent(ObClient *self)
3703 while (self->transient_for && self->transient_for != OB_TRAN_GROUP &&
3704 client_normal(self->transient_for))
3705 self = self->transient_for;
3709 static GSList *client_search_all_top_parents_internal(ObClient *self,
3711 ObStackingLayer layer)
3715 /* move up the direct transient chain as far as possible */
3716 while (self->transient_for && self->transient_for != OB_TRAN_GROUP &&
3717 (!bylayer || self->transient_for->layer == layer) &&
3718 client_normal(self->transient_for))
3719 self = self->transient_for;
3721 if (!self->transient_for)
3722 ret = g_slist_prepend(ret, self);
3726 g_assert(self->group);
3728 for (it = self->group->members; it; it = g_slist_next(it)) {
3729 ObClient *c = it->data;
3731 if (!c->transient_for && client_normal(c) &&
3732 (!bylayer || c->layer == layer))
3734 ret = g_slist_prepend(ret, c);
3738 if (ret == NULL) /* no group parents */
3739 ret = g_slist_prepend(ret, self);
3745 GSList *client_search_all_top_parents(ObClient *self)
3747 return client_search_all_top_parents_internal(self, FALSE, 0);
3750 GSList *client_search_all_top_parents_layer(ObClient *self)
3752 return client_search_all_top_parents_internal(self, TRUE, self->layer);
3755 ObClient *client_search_focus_parent(ObClient *self)
3757 if (self->transient_for) {
3758 if (self->transient_for != OB_TRAN_GROUP) {
3759 if (client_focused(self->transient_for))
3760 return self->transient_for;
3764 for (it = self->group->members; it; it = g_slist_next(it)) {
3765 ObClient *c = it->data;
3767 /* checking transient_for prevents infinate loops! */
3768 if (c != self && !c->transient_for)
3769 if (client_focused(c))
3778 ObClient *client_search_parent(ObClient *self, ObClient *search)
3780 if (self->transient_for) {
3781 if (self->transient_for != OB_TRAN_GROUP) {
3782 if (self->transient_for == search)
3787 for (it = self->group->members; it; it = g_slist_next(it)) {
3788 ObClient *c = it->data;
3790 /* checking transient_for prevents infinate loops! */
3791 if (c != self && !c->transient_for)
3801 ObClient *client_search_transient(ObClient *self, ObClient *search)
3805 for (sit = self->transients; sit; sit = g_slist_next(sit)) {
3806 if (sit->data == search)
3808 if (client_search_transient(sit->data, search))
3814 #define WANT_EDGE(cur, c) \
3817 if(!client_normal(cur)) \
3819 if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL) \
3824 #define HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end) \
3825 if ((his_edge_start >= my_edge_start && \
3826 his_edge_start <= my_edge_end) || \
3827 (my_edge_start >= his_edge_start && \
3828 my_edge_start <= his_edge_end)) \
3831 /* finds the nearest edge in the given direction from the current client
3832 * note to self: the edge is the -frame- edge (the actual one), not the
3835 gint client_directional_edge_search(ObClient *c, ObDirection dir, gboolean hang)
3837 gint dest, monitor_dest;
3838 gint my_edge_start, my_edge_end, my_offset;
3845 a = screen_area(c->desktop);
3846 monitor = screen_area_monitor(c->desktop, client_monitor(c));
3849 case OB_DIRECTION_NORTH:
3850 my_edge_start = c->frame->area.x;
3851 my_edge_end = c->frame->area.x + c->frame->area.width;
3852 my_offset = c->frame->area.y + (hang ? c->frame->area.height : 0);
3854 /* default: top of screen */
3855 dest = a->y + (hang ? c->frame->area.height : 0);
3856 monitor_dest = monitor->y + (hang ? c->frame->area.height : 0);
3857 /* if the monitor edge comes before the screen edge, */
3858 /* use that as the destination instead. (For xinerama) */
3859 if (monitor_dest != dest && my_offset > monitor_dest)
3860 dest = monitor_dest;
3862 for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3863 gint his_edge_start, his_edge_end, his_offset;
3864 ObClient *cur = it->data;
3868 his_edge_start = cur->frame->area.x;
3869 his_edge_end = cur->frame->area.x + cur->frame->area.width;
3870 his_offset = cur->frame->area.y +
3871 (hang ? 0 : cur->frame->area.height);
3873 if(his_offset + 1 > my_offset)
3876 if(his_offset < dest)
3879 HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3882 case OB_DIRECTION_SOUTH:
3883 my_edge_start = c->frame->area.x;
3884 my_edge_end = c->frame->area.x + c->frame->area.width;
3885 my_offset = c->frame->area.y + (hang ? 0 : c->frame->area.height);
3887 /* default: bottom of screen */
3888 dest = a->y + a->height - (hang ? c->frame->area.height : 0);
3889 monitor_dest = monitor->y + monitor->height -
3890 (hang ? c->frame->area.height : 0);
3891 /* if the monitor edge comes before the screen edge, */
3892 /* use that as the destination instead. (For xinerama) */
3893 if (monitor_dest != dest && my_offset < monitor_dest)
3894 dest = monitor_dest;
3896 for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3897 gint his_edge_start, his_edge_end, his_offset;
3898 ObClient *cur = it->data;
3902 his_edge_start = cur->frame->area.x;
3903 his_edge_end = cur->frame->area.x + cur->frame->area.width;
3904 his_offset = cur->frame->area.y +
3905 (hang ? cur->frame->area.height : 0);
3908 if(his_offset - 1 < my_offset)
3911 if(his_offset > dest)
3914 HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3917 case OB_DIRECTION_WEST:
3918 my_edge_start = c->frame->area.y;
3919 my_edge_end = c->frame->area.y + c->frame->area.height;
3920 my_offset = c->frame->area.x + (hang ? c->frame->area.width : 0);
3922 /* default: leftmost egde of screen */
3923 dest = a->x + (hang ? c->frame->area.width : 0);
3924 monitor_dest = monitor->x + (hang ? c->frame->area.width : 0);
3925 /* if the monitor edge comes before the screen edge, */
3926 /* use that as the destination instead. (For xinerama) */
3927 if (monitor_dest != dest && my_offset > monitor_dest)
3928 dest = monitor_dest;
3930 for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3931 gint his_edge_start, his_edge_end, his_offset;
3932 ObClient *cur = it->data;
3936 his_edge_start = cur->frame->area.y;
3937 his_edge_end = cur->frame->area.y + cur->frame->area.height;
3938 his_offset = cur->frame->area.x +
3939 (hang ? 0 : cur->frame->area.width);
3941 if(his_offset + 1 > my_offset)
3944 if(his_offset < dest)
3947 HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3950 case OB_DIRECTION_EAST:
3951 my_edge_start = c->frame->area.y;
3952 my_edge_end = c->frame->area.y + c->frame->area.height;
3953 my_offset = c->frame->area.x + (hang ? 0 : c->frame->area.width);
3955 /* default: rightmost edge of screen */
3956 dest = a->x + a->width - (hang ? c->frame->area.width : 0);
3957 monitor_dest = monitor->x + monitor->width -
3958 (hang ? c->frame->area.width : 0);
3959 /* if the monitor edge comes before the screen edge, */
3960 /* use that as the destination instead. (For xinerama) */
3961 if (monitor_dest != dest && my_offset < monitor_dest)
3962 dest = monitor_dest;
3964 for(it = client_list; it && my_offset != dest; it = g_list_next(it)) {
3965 gint his_edge_start, his_edge_end, his_offset;
3966 ObClient *cur = it->data;
3970 his_edge_start = cur->frame->area.y;
3971 his_edge_end = cur->frame->area.y + cur->frame->area.height;
3972 his_offset = cur->frame->area.x +
3973 (hang ? cur->frame->area.width : 0);
3975 if(his_offset - 1 < my_offset)
3978 if(his_offset > dest)
3981 HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end)
3984 case OB_DIRECTION_NORTHEAST:
3985 case OB_DIRECTION_SOUTHEAST:
3986 case OB_DIRECTION_NORTHWEST:
3987 case OB_DIRECTION_SOUTHWEST:
3988 /* not implemented */
3990 g_assert_not_reached();
3991 dest = 0; /* suppress warning */
3996 ObClient* client_under_pointer()
4000 ObClient *ret = NULL;
4002 if (screen_pointer_pos(&x, &y)) {
4003 for (it = stacking_list; it; it = g_list_next(it)) {
4004 if (WINDOW_IS_CLIENT(it->data)) {
4005 ObClient *c = WINDOW_AS_CLIENT(it->data);
4006 if (c->frame->visible &&
4007 /* ignore all animating windows */
4008 !frame_iconify_animating(c->frame) &&
4009 RECT_CONTAINS(c->frame->area, x, y))
4020 gboolean client_has_group_siblings(ObClient *self)
4022 return self->group && self->group->members->next;