X-Git-Url: https://git.dogcows.com/gitweb?a=blobdiff_plain;f=openbox%2Fclient.c;h=4c88daf7fc3b78f57c83d5cda5ab71ccf636d805;hb=6593261f30d611ff3b71abdb9fd043851fdd2ca9;hp=c0323c2628d15f0b993f9b0cb587cfdbeba6a3fd;hpb=fce7d9b21ed039f32cde5f4fb6caa179e8c7e922;p=chaz%2Fopenbox diff --git a/openbox/client.c b/openbox/client.c index c0323c26..cc721830 100644 --- a/openbox/client.c +++ b/openbox/client.c @@ -1,5 +1,5 @@ /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*- - + client.c for the Openbox window manager Copyright (c) 2006 Mikael Magnusson Copyright (c) 2003-2007 Dana Jansens @@ -57,38 +57,43 @@ typedef struct { - ObClientDestructor func; + ObClientCallback func; gpointer data; -} Destructor; +} ClientCallback; -GList *client_list = NULL; +GList *client_list = NULL; -static GSList *client_destructors = NULL; +static GSList *client_destroy_notifies = NULL; -static void client_get_all(ObClient *self); -static void client_toggle_border(ObClient *self, gboolean show); +static void client_get_all(ObClient *self, gboolean real); static void client_get_startup_id(ObClient *self); +static void client_get_session_ids(ObClient *self); static void client_get_area(ObClient *self); static void client_get_desktop(ObClient *self); static void client_get_state(ObClient *self); -static void client_get_layer(ObClient *self); static void client_get_shaped(ObClient *self); static void client_get_mwm_hints(ObClient *self); -static void client_get_gravity(ObClient *self); -static void client_get_client_machine(ObClient *self); static void client_get_colormap(ObClient *self); -static void client_get_transientness(ObClient *self); static void client_change_allowed_actions(ObClient *self); static void client_change_state(ObClient *self); static void client_change_wm_state(ObClient *self); -static void client_apply_startup_state(ObClient *self, gint x, gint y); +static void client_apply_startup_state(ObClient *self, + gint x, gint y, gint w, gint h); static void client_restore_session_state(ObClient *self); -static void client_restore_session_stacking(ObClient *self); +static gboolean client_restore_session_stacking(ObClient *self); static ObAppSettings *client_get_settings_state(ObClient *self); static void client_update_transient_tree(ObClient *self, ObGroup *oldgroup, ObGroup *newgroup, + gboolean oldgtran, gboolean newgtran, ObClient* oldparent, ObClient *newparent); +static void client_present(ObClient *self, gboolean here, gboolean raise, + gboolean unshade); +static GSList *client_search_all_top_parents_internal(ObClient *self, + gboolean bylayer, + ObStackingLayer layer); +static void client_call_notifies(ObClient *self, GSList *list); + void client_startup(gboolean reconfig) { @@ -99,25 +104,37 @@ void client_startup(gboolean reconfig) void client_shutdown(gboolean reconfig) { + if (reconfig) return; +} + +static void client_call_notifies(ObClient *self, GSList *list) +{ + GSList *it; + + for (it = list; it; it = g_slist_next(it)) { + ClientCallback *d = it->data; + d->func(self, d->data); + } } -void client_add_destructor(ObClientDestructor func, gpointer data) +void client_add_destroy_notify(ObClientCallback func, gpointer data) { - Destructor *d = g_new(Destructor, 1); + ClientCallback *d = g_new(ClientCallback, 1); d->func = func; d->data = data; - client_destructors = g_slist_prepend(client_destructors, d); + client_destroy_notifies = g_slist_prepend(client_destroy_notifies, d); } -void client_remove_destructor(ObClientDestructor func) +void client_remove_destroy_notify(ObClientCallback func) { GSList *it; - for (it = client_destructors; it; it = g_slist_next(it)) { - Destructor *d = it->data; + for (it = client_destroy_notifies; it; it = g_slist_next(it)) { + ClientCallback *d = it->data; if (d->func == func) { g_free(d); - client_destructors = g_slist_delete_link(client_destructors, it); + client_destroy_notifies = + g_slist_delete_link(client_destroy_notifies, it); break; } } @@ -147,37 +164,6 @@ void client_set_list() stacking_set_list(); } -/* - void client_foreach_transient(ObClient *self, ObClientForeachFunc func, gpointer data) - { - GSList *it; - - for (it = self->transients; it; it = g_slist_next(it)) { - if (!func(it->data, data)) return; - client_foreach_transient(it->data, func, data); - } - } - - void client_foreach_ancestor(ObClient *self, ObClientForeachFunc func, gpointer data) - { - if (self->transient_for) { - if (self->transient_for != OB_TRAN_GROUP) { - if (!func(self->transient_for, data)) return; - client_foreach_ancestor(self->transient_for, func, data); - } else { - GSList *it; - - for (it = self->group->members; it; it = g_slist_next(it)) - if (it->data != self && - !((ObClient*)it->data)->transient_for) { - if (!func(it->data, data)) return; - client_foreach_ancestor(it->data, func, data); - } - } - } - } -*/ - void client_manage_all() { guint i, j, nchild; @@ -226,12 +212,14 @@ void client_manage(Window window) XWMHints *wmhint; gboolean activate = FALSE; ObAppSettings *settings; - gint newx, newy; + gboolean transient = FALSE; + Rect place, *monitor; + Time launch_time, map_time; grab_server(TRUE); - /* check if it has already been unmapped by the time we started mapping. - the grab does a sync so we don't have to here */ + /* check if it has already been unmapped by the time we started + mapping. the grab does a sync so we don't have to here */ if (XCheckTypedWindowEvent(ob_display, window, DestroyNotify, &e) || XCheckTypedWindowEvent(ob_display, window, UnmapNotify, &e)) { @@ -249,7 +237,7 @@ void client_manage(Window window) grab_server(FALSE); return; /* don't manage it */ } - + /* is the window a docking app */ if ((wmhint = XGetWMHints(ob_display, window))) { if ((wmhint->flags & StateHint) && @@ -263,7 +251,9 @@ void client_manage(Window window) XFree(wmhint); } - ob_debug("Managing window: %lx\n", window); + ob_debug("Managing window: 0x%lx\n", window); + + map_time = event_get_server_time(); /* choose the events we want to receive on the CLIENT window */ attrib_set.event_mask = CLIENT_EVENTMASK; @@ -271,7 +261,6 @@ void client_manage(Window window) XChangeWindowAttributes(ob_display, window, CWEventMask|CWDontPropagate, &attrib_set); - /* create the ObClient struct, and populate it from the hints on the window */ self = g_new0(ObClient, 1); @@ -280,80 +269,101 @@ void client_manage(Window window) /* non-zero defaults */ self->wmstate = WithdrawnState; /* make sure it gets updated first time */ - self->layer = -1; + self->gravity = NorthWestGravity; self->desktop = screen_num_desktops; /* always an invalid value */ - self->user_time = focus_client ? focus_client->user_time : CurrentTime; - - client_get_all(self); - /* per-app settings override stuff, and return the settings for other - uses too */ - settings = client_get_settings_state(self); - /* the session should get the last say */ - client_restore_session_state(self); - - client_calc_layer(self); - { - Time t = sn_app_started(self->startup_id, self->class); - if (t) self->user_time = t; - } + /* get all the stuff off the window */ + client_get_all(self, TRUE); - /* update the focus lists, do this before the call to change_state or - it can end up in the list twice! */ - focus_order_add_new(self); + ob_debug("Window type: %d\n", self->type); + ob_debug("Window group: 0x%x\n", self->group?self->group->leader:0); - /* remove the client's border (and adjust re gravity) */ - client_toggle_border(self, FALSE); - - /* specify that if we exit, the window should not be destroyed and should - be reparented back to root automatically */ + /* specify that if we exit, the window should not be destroyed and + should be reparented back to root automatically */ XChangeSaveSet(ob_display, window, SetModeInsert); /* create the decoration frame for the client window */ self->frame = frame_new(self); - frame_grab_client(self->frame, self); + frame_grab_client(self->frame); + + /* we've grabbed everything and set everything that we need to at mapping + time now */ + grab_server(FALSE); + + /* per-app settings override stuff from client_get_all, and return the + settings for other uses too. the returned settings is a shallow copy, + that needs to be freed with g_free(). */ + settings = client_get_settings_state(self); + /* the session should get the last say though */ + client_restore_session_state(self); + + /* now we have all of the window's information so we can set this up */ + client_setup_decor_and_functions(self, FALSE); + + /* tell startup notification that this app started */ + launch_time = sn_app_started(self->startup_id, self->class); /* do this after we have a frame.. it uses the frame to help determine the WM_STATE to apply. */ client_change_state(self); - grab_server(FALSE); + /* add ourselves to the focus order */ + focus_order_add_new(self); - stacking_add_nonintrusive(CLIENT_AS_WINDOW(self)); - client_restore_session_stacking(self); + /* do this to add ourselves to the stacking list in a non-intrusive way */ + client_calc_layer(self); /* focus the new window? */ if (ob_state() != OB_STATE_STARTING && - !self->iconic && + (!self->session || self->session->focused) && /* this means focus=true for window is same as config_focus_new=true */ ((config_focus_new || (settings && settings->focus == 1)) || - client_search_focus_parent(self)) && + client_search_focus_tree_full(self)) && /* this checks for focus=false for the window */ (!settings || settings->focus != 0) && - /* note the check against Type_Normal/Dialog, not client_normal(self), - which would also include other types. in this case we want more - strict rules for focus */ - (self->type == OB_CLIENT_TYPE_NORMAL || - self->type == OB_CLIENT_TYPE_DIALOG)) + focus_valid_target(self, FALSE, FALSE, TRUE, FALSE, FALSE)) { activate = TRUE; } - /* get the current position */ - newx = self->area.x; - newy = self->area.y; + /* remove the client's border */ + XSetWindowBorderWidth(ob_display, self->window, 0); - /* figure out placement for the window */ - if (ob_state() == OB_STATE_RUNNING) { - gboolean transient; + /* adjust the frame to the client's size before showing or placing + the window */ + frame_adjust_area(self->frame, FALSE, TRUE, FALSE); + frame_adjust_client_area(self->frame); + + /* where the frame was placed is where the window was originally */ + place = self->area; + monitor = screen_physical_area_monitor(screen_find_monitor(&place)); - transient = place_client(self, &newx, &newy, settings); + /* figure out placement for the window if the window is new */ + if (ob_state() == OB_STATE_RUNNING) { + ob_debug("Positioned: %s @ %d %d\n", + (!self->positioned ? "no" : + (self->positioned == PPosition ? "program specified" : + (self->positioned == USPosition ? "user specified" : + (self->positioned == (PPosition | USPosition) ? + "program + user specified" : + "BADNESS !?")))), place.x, place.y); + + ob_debug("Sized: %s @ %d %d\n", + (!self->sized ? "no" : + (self->sized == PSize ? "program specified" : + (self->sized == USSize ? "user specified" : + (self->sized == (PSize | USSize) ? + "program + user specified" : + "BADNESS !?")))), place.width, place.height); + + /* splash screens are also returned as TRUE for transient, + and so will be forced on screen below */ + transient = place_client(self, &place.x, &place.y, settings); /* make sure the window is visible. */ - client_find_onscreen(self, &newx, &newy, - self->area.width, - self->area.height, + client_find_onscreen(self, &place.x, &place.y, + place.width, place.height, /* non-normal clients has less rules, and windows that are being restored from a session do also. we can assume you want @@ -363,76 +373,172 @@ void client_manage(Window window) place.c or by the user are allowed partially off-screen and on xinerama divides (ie, it is up to the placement routines to avoid - the xinerama divides) */ - transient || - (((self->positioned & PPosition) && - !(self->positioned & USPosition)) && - client_normal(self) && - !self->session)); + the xinerama divides) + + splash screens get "transient" set to TRUE by + the place_client call + */ + ob_state() == OB_STATE_RUNNING && + (transient || + (!((self->positioned & USPosition) || + (settings && settings->pos_given)) && + client_normal(self) && + !self->session && + /* don't move oldschool fullscreen windows to + fit inside the struts (fixes Acroread, which + makes its fullscreen window fit the screen + but it is not USSize'd or USPosition'd) */ + !(self->decorations == 0 && + RECT_EQUAL(place, *monitor))))); + } + + /* if the window isn't user-sized, then make it fit inside + the visible screen area on its monitor. Use basically the same rules + for forcing the window on screen in the client_find_onscreen call. + + do this after place_client, it chooses the monitor! + + splash screens get "transient" set to TRUE by + the place_client call + */ + if (ob_state() == OB_STATE_RUNNING && + (transient || + (!(self->sized & USSize || self->positioned & USPosition) && + client_normal(self) && + !self->session && + /* don't shrink oldschool fullscreen windows to fit inside the + struts (fixes Acroread, which makes its fullscreen window + fit the screen but it is not USSize'd or USPosition'd) */ + !(self->decorations == 0 && RECT_EQUAL(place, *monitor))))) + { + Rect *a = screen_area(self->desktop, SCREEN_AREA_ONE_MONITOR, &place); + + /* get the size of the frame */ + place.width += self->frame->size.left + self->frame->size.right; + place.height += self->frame->size.top + self->frame->size.bottom; + + /* fit the window inside the area */ + place.width = MIN(place.width, a->width); + place.height = MIN(place.height, a->height); + + ob_debug("setting window size to %dx%d\n", place.width, place.height); + + /* get the size of the client back */ + place.width -= self->frame->size.left + self->frame->size.right; + place.height -= self->frame->size.top + self->frame->size.bottom; + + g_free(a); } + ob_debug("placing window 0x%x at %d, %d with size %d x %d. " + "some restrictions may apply\n", + self->window, place.x, place.y, place.width, place.height); + if (self->session) + ob_debug(" but session requested %d, %d %d x %d instead, " + "overriding\n", + self->session->x, self->session->y, + self->session->w, self->session->h); + /* do this after the window is placed, so the premax/prefullscreen numbers won't be all wacko!! - also, this moves the window to the position where it has been placed + + this also places the window */ - ob_debug("placing window 0x%x at %d, %d with size %d x %d\n", - self->window, newx, newy, self->area.width, self->area.height); - client_apply_startup_state(self, newx, newy); + client_apply_startup_state(self, place.x, place.y, + place.width, place.height); - mouse_grab_for_client(self, TRUE); + g_free(monitor); + monitor = NULL; if (activate) { - guint32 last_time = focus_client ? - focus_client->user_time : CurrentTime; + gboolean raise = FALSE; /* This is focus stealing prevention */ ob_debug_type(OB_DEBUG_FOCUS, - "Want to focus new window 0x%x with time %u " - "(last time %u)\n", - self->window, self->user_time, last_time); + "Want to focus new window 0x%x at time %u " + "launched at %u (last user interaction time %u)\n", + self->window, map_time, launch_time, + event_last_user_time); + + if (menu_frame_visible || moveresize_in_progress) { + activate = FALSE; + raise = TRUE; + ob_debug_type(OB_DEBUG_FOCUS, + "Not focusing the window because the user is inside " + "an Openbox menu or is move/resizing a window and " + "we don't want to interrupt them\n"); + } /* if it's on another desktop */ - if (!(self->desktop == screen_desktop || self->desktop == DESKTOP_ALL) - && /* the timestamp is from before you changed desktops */ - self->user_time && screen_desktop_user_time && - !event_time_after(self->user_time, screen_desktop_user_time)) + else if (!(self->desktop == screen_desktop || + self->desktop == DESKTOP_ALL) && + /* the timestamp is from before you changed desktops */ + launch_time && screen_desktop_user_time && + !event_time_after(launch_time, screen_desktop_user_time)) { activate = FALSE; + raise = TRUE; ob_debug_type(OB_DEBUG_FOCUS, "Not focusing the window because its on another " "desktop\n"); } - /* If something is focused, and it's not our parent... */ - else if (focus_client && client_search_focus_parent(self) == NULL) + /* If something is focused, and it's not our relative... */ + else if (focus_client && client_search_focus_tree_full(self) == NULL && + client_search_focus_group_full(self) == NULL) { - /* If time stamp is old, don't steal focus */ - if (self->user_time && last_time && - !event_time_after(self->user_time, last_time)) + /* If the user is working in another window right now, then don't + steal focus */ + if (event_last_user_time && launch_time && + event_time_after(event_last_user_time, launch_time) && + event_last_user_time != launch_time && + event_time_after(event_last_user_time, + map_time - OB_EVENT_USER_TIME_DELAY)) { activate = FALSE; ob_debug_type(OB_DEBUG_FOCUS, - "Not focusing the window because the time is " - "too old\n"); + "Not focusing the window because the user is " + "working in another window\n"); + } + /* If its a transient (and its parents aren't focused) */ + else if (client_has_parent(self)) { + activate = FALSE; + ob_debug_type(OB_DEBUG_FOCUS, + "Not focusing the window because it is a " + "transient, and its relatives aren't focused\n"); } /* Don't steal focus from globally active clients. I stole this idea from KWin. It seems nice. */ - if (!(focus_client->can_focus || focus_client->focus_notify)) { + else if (!(focus_client->can_focus || + focus_client->focus_notify)) + { activate = FALSE; ob_debug_type(OB_DEBUG_FOCUS, "Not focusing the window because a globally " "active client has focus\n"); } + /* Don't move focus if it's not going to go to this window + anyway */ + else if (client_focus_target(self) != self) { + activate = FALSE; + raise = TRUE; + ob_debug_type(OB_DEBUG_FOCUS, + "Not focusing the window because another window " + "would get the focus anyway\n"); + } } if (!activate) { ob_debug_type(OB_DEBUG_FOCUS, - "Focus stealing prevention activated for %s with " - "time %u (last time %u)\n", - self->title, self->user_time, last_time); + "Focus stealing prevention activated for %s at " + "time %u (last user interactioon time %u)\n", + self->title, map_time, event_last_user_time); /* if the client isn't focused, then hilite it so the user knows it is there */ client_hilite(self, TRUE); + /* we may want to raise it even tho we're not activating it */ + if (raise && !client_restore_session_stacking(self)) + stacking_raise(CLIENT_AS_WINDOW(self)); } } else { @@ -443,21 +549,30 @@ void client_manage(Window window) Also if you don't have focus_new enabled, then it's going to get raised to the top. Legacy begets legacy I guess? */ - client_raise(self); + if (!client_restore_session_stacking(self)) + stacking_raise(CLIENT_AS_WINDOW(self)); } + mouse_grab_for_client(self, TRUE); + /* this has to happen before we try focus the window, but we want it to happen after the client's stacking has been determined or it looks bad */ - client_show(self); + { + gulong ignore_start; + if (!config_focus_under_mouse) + ignore_start = event_start_ignore_all_enters(); + + client_show(self); + + if (!config_focus_under_mouse) + event_end_ignore_all_enters(ignore_start); + } - /* use client_focus instead of client_activate cuz client_activate does - stuff like switch desktops etc and I'm not interested in all that when - a window maps since its not based on an action from the user like - clicking a window to activate it. so keep the new window out of the way - but do focus it. */ - if (activate) - client_activate(self, FALSE, TRUE); + if (activate) { + gboolean stacked = client_restore_session_stacking(self); + client_present(self, FALSE, !stacked, TRUE); + } /* add to client list/map */ client_list = g_list_append(client_list, self); @@ -470,7 +585,47 @@ void client_manage(Window window) /* update the list hints */ client_set_list(); - ob_debug("Managed window 0x%lx (%s)\n", window, self->class); + /* free the ObAppSettings shallow copy */ + g_free(settings); + + ob_debug("Managed window 0x%lx plate 0x%x (%s)\n", + window, self->frame->window, self->class); + + return; +} + + +ObClient *client_fake_manage(Window window) +{ + ObClient *self; + ObAppSettings *settings; + + ob_debug("Pretend-managing window: %lx\n", window); + + /* do this minimal stuff to figure out the client's decorations */ + + self = g_new0(ObClient, 1); + self->window = window; + + client_get_all(self, FALSE); + /* per-app settings override stuff, and return the settings for other + uses too. this returns a shallow copy that needs to be freed */ + settings = client_get_settings_state(self); + + client_setup_decor_and_functions(self, FALSE); + + /* create the decoration frame for the client window and adjust its size */ + self->frame = frame_new(self); + frame_adjust_area(self->frame, FALSE, TRUE, TRUE); + + ob_debug("gave extents left %d right %d top %d bottom %d\n", + self->frame->size.left, self->frame->size.right, + self->frame->size.top, self->frame->size.bottom); + + /* free the ObAppSettings shallow copy */ + g_free(settings); + + return self; } void client_unmanage_all() @@ -483,9 +638,11 @@ void client_unmanage(ObClient *self) { guint j; GSList *it; + gulong ignore_start; - ob_debug("Unmanaging window: %lx (%s) (%s)\n", self->window, self->class, - self->title ? self->title : ""); + ob_debug("Unmanaging window: 0x%x plate 0x%x (%s) (%s)\n", + self->window, self->frame->window, + self->class, self->title ? self->title : ""); g_assert(self != NULL); @@ -493,12 +650,16 @@ void client_unmanage(ObClient *self) don't generate more events */ XSelectInput(ob_display, self->window, NoEventMask); + /* ignore enter events from the unmap so it doesnt mess with the focus */ + if (!config_focus_under_mouse) + ignore_start = event_start_ignore_all_enters(); + frame_hide(self->frame); /* flush to send the hide to the server quickly */ XFlush(ob_display); - /* ignore enter events from the unmap so it doesnt mess with the focus */ - event_ignore_queued_enters(); + if (!config_focus_under_mouse) + event_end_ignore_all_enters(ignore_start); mouse_grab_for_client(self, FALSE); @@ -521,28 +682,19 @@ void client_unmanage(ObClient *self) if (STRUT_EXISTS(self->strut)) screen_update_areas(); - for (it = client_destructors; it; it = g_slist_next(it)) { - Destructor *d = it->data; - d->func(self, d->data); - } + client_call_notifies(self, client_destroy_notifies); /* tell our parent(s) that we're gone */ - if (self->transient_for == OB_TRAN_GROUP) { /* transient of group */ - for (it = self->group->members; it; it = g_slist_next(it)) - if (it->data != self) - ((ObClient*)it->data)->transients = - g_slist_remove(((ObClient*)it->data)->transients, self); - } else if (self->transient_for) { /* transient of window */ - self->transient_for->transients = - g_slist_remove(self->transient_for->transients, self); - } + for (it = self->parents; it; it = g_slist_next(it)) + ((ObClient*)it->data)->transients = + g_slist_remove(((ObClient*)it->data)->transients,self); /* tell our transients that we're gone */ for (it = self->transients; it; it = g_slist_next(it)) { - if (((ObClient*)it->data)->transient_for != OB_TRAN_GROUP) { - ((ObClient*)it->data)->transient_for = NULL; - client_calc_layer(it->data); - } + ((ObClient*)it->data)->parents = + g_slist_remove(((ObClient*)it->data)->parents, self); + /* we could be keeping our children in a higher layer */ + client_calc_layer(it->data); } /* remove from its group */ @@ -553,7 +705,9 @@ void client_unmanage(ObClient *self) /* restore the window's original geometry so it is not lost */ { - Rect a = self->area; + Rect a; + + a = self->area; if (self->fullscreen) a = self->pre_fullscreen_area; @@ -568,17 +722,20 @@ void client_unmanage(ObClient *self) } } - /* give the client its border back */ - client_toggle_border(self, TRUE); - self->fullscreen = self->max_horz = self->max_vert = FALSE; + /* let it be moved and resized no matter what */ + self->functions = OB_CLIENT_FUNC_MOVE | OB_CLIENT_FUNC_RESIZE; self->decorations = 0; /* unmanaged windows have no decor */ + /* give the client its border back */ + XSetWindowBorderWidth(ob_display, self->window, self->border_width); + client_move_resize(self, a.x, a.y, a.width, a.height); } /* reparent the window out of the frame, and free the frame */ - frame_release_client(self->frame, self); + frame_release_client(self->frame); + frame_free(self->frame); self->frame = NULL; if (ob_state() != OB_STATE_EXITING) { @@ -588,11 +745,15 @@ void client_unmanage(ObClient *self) PROP_ERASE(self->window, net_wm_state); PROP_ERASE(self->window, wm_state); } else { - /* if we're left in an unmapped state, the client wont be mapped. this - is bad, since we will no longer be managing the window on restart */ + /* if we're left in an unmapped state, the client wont be mapped. + this is bad, since we will no longer be managing the window on + restart */ XMapWindow(ob_display, self->window); } + /* update the list hints */ + client_set_list(); + ob_debug("Unmanaged window 0x%lx\n", self->window); /* free all data allocated in the client struct */ @@ -601,6 +762,7 @@ void client_unmanage(ObClient *self) g_free(self->icons[j].data); if (self->nicons > 0) g_free(self->icons); + g_free(self->wm_command); g_free(self->title); g_free(self->icon_title); g_free(self->name); @@ -609,74 +771,92 @@ void client_unmanage(ObClient *self) g_free(self->client_machine); g_free(self->sm_client_id); g_free(self); - - /* update the list hints */ - client_set_list(); } +void client_fake_unmanage(ObClient *self) +{ + /* this is all that got allocated to get the decorations */ + + frame_free(self->frame); + g_free(self); +} + +/*! Returns a new structure containing the per-app settings for this client. + The returned structure needs to be freed with g_free. */ static ObAppSettings *client_get_settings_state(ObClient *self) { - ObAppSettings *settings = NULL; + ObAppSettings *settings; GSList *it; + settings = config_create_app_settings(); + for (it = config_per_app_settings; it; it = g_slist_next(it)) { ObAppSettings *app = it->data; - - if ((app->name && !app->class && !strcmp(app->name, self->name)) - || (app->class && !app->name && !strcmp(app->class, self->class)) - || (app->class && app->name && !strcmp(app->class, self->class) - && !strcmp(app->name, self->name))) - { + gboolean match = TRUE; + + g_assert(app->name != NULL || app->class != NULL); + + /* we know that either name or class is not NULL so it will have to + match to use the rule */ + if (app->name && + !g_pattern_match(app->name, strlen(self->name), self->name, NULL)) + match = FALSE; + else if (app->class && + !g_pattern_match(app->class, + strlen(self->class), self->class, NULL)) + match = FALSE; + else if (app->role && + !g_pattern_match(app->role, + strlen(self->role), self->role, NULL)) + match = FALSE; + + if (match) { ob_debug("Window matching: %s\n", app->name); - /* Match if no role was specified in the per app setting, or if the - * string matches the beginning of the role, since apps like to set - * the role to things like browser-window-23c4b2f */ - if (!app->role - || !strncmp(app->role, self->role, strlen(app->role))) - { - /* use this one */ - settings = app; - break; - } + + /* copy the settings to our struct, overriding the existing + settings if they are not defaults */ + config_app_settings_copy_non_defaults(app, settings); } } - if (settings) { - if (settings->shade != -1) - self->shaded = !!settings->shade; - if (settings->decor != -1) - self->undecorated = !settings->decor; - if (settings->iconic != -1) - self->iconic = !!settings->iconic; - if (settings->skip_pager != -1) - self->skip_pager = !!settings->skip_pager; - if (settings->skip_taskbar != -1) - self->skip_taskbar = !!settings->skip_taskbar; - - if (settings->max_vert != -1) - self->max_vert = !!settings->max_vert; - if (settings->max_horz != -1) - self->max_horz = !!settings->max_horz; - - if (settings->fullscreen != -1) - self->fullscreen = !!settings->fullscreen; - - if (settings->desktop < screen_num_desktops - || settings->desktop == DESKTOP_ALL) + if (settings->shade != -1) + self->shaded = !!settings->shade; + if (settings->decor != -1) + self->undecorated = !settings->decor; + if (settings->iconic != -1) + self->iconic = !!settings->iconic; + if (settings->skip_pager != -1) + self->skip_pager = !!settings->skip_pager; + if (settings->skip_taskbar != -1) + self->skip_taskbar = !!settings->skip_taskbar; + + if (settings->max_vert != -1) + self->max_vert = !!settings->max_vert; + if (settings->max_horz != -1) + self->max_horz = !!settings->max_horz; + + if (settings->fullscreen != -1) + self->fullscreen = !!settings->fullscreen; + + if (settings->desktop) { + if (settings->desktop == DESKTOP_ALL) self->desktop = settings->desktop; + else if (settings->desktop > 0 && + settings->desktop <= screen_num_desktops) + self->desktop = settings->desktop - 1; + } - if (settings->layer == -1) { - self->below = TRUE; - self->above = FALSE; - } - else if (settings->layer == 0) { - self->below = FALSE; - self->above = FALSE; - } - else if (settings->layer == 1) { - self->below = FALSE; - self->above = TRUE; - } + if (settings->layer == -1) { + self->below = TRUE; + self->above = FALSE; + } + else if (settings->layer == 0) { + self->below = FALSE; + self->above = FALSE; + } + else if (settings->layer == 1) { + self->below = FALSE; + self->above = TRUE; } return settings; } @@ -685,13 +865,23 @@ static void client_restore_session_state(ObClient *self) { GList *it; - if (!(it = session_state_find(self))) + ob_debug_type(OB_DEBUG_SM, + "Restore session for client %s\n", self->title); + + if (!(it = session_state_find(self))) { + ob_debug_type(OB_DEBUG_SM, + "Session data not found for client %s\n", self->title); return; + } self->session = it->data; + ob_debug_type(OB_DEBUG_SM, "Session data loaded for client %s\n", + self->title); + RECT_SET_POINT(self->area, self->session->x, self->session->y); - self->positioned = PPosition; + self->positioned = USPosition; + self->sized = USSize; if (self->session->w > 0) self->area.width = self->session->w; if (self->session->h > 0) @@ -713,28 +903,33 @@ static void client_restore_session_state(ObClient *self) self->below = self->session->below; self->max_horz = self->session->max_horz; self->max_vert = self->session->max_vert; + self->undecorated = self->session->undecorated; } -static void client_restore_session_stacking(ObClient *self) +static gboolean client_restore_session_stacking(ObClient *self) { - GList *it; + GList *it, *mypos; - if (!self->session) return; + if (!self->session) return FALSE; - it = g_list_find(session_saved_state, self->session); - for (it = g_list_previous(it); it; it = g_list_previous(it)) { + mypos = g_list_find(session_saved_state, self->session); + if (!mypos) return FALSE; + + /* start above me and look for the first client */ + for (it = g_list_previous(mypos); it; it = g_list_previous(it)) { GList *cit; - for (cit = client_list; cit; cit = g_list_next(cit)) - if (session_state_cmp(it->data, cit->data)) - break; - if (cit) { - client_calc_layer(self); - stacking_below(CLIENT_AS_WINDOW(self), - CLIENT_AS_WINDOW(cit->data)); - break; + for (cit = client_list; cit; cit = g_list_next(cit)) { + ObClient *c = cit->data; + /* found a client that was in the session, so go below it */ + if (c->session == it->data) { + stacking_below(CLIENT_AS_WINDOW(self), + CLIENT_AS_WINDOW(cit->data)); + return TRUE; + } } } + return FALSE; } void client_move_onscreen(ObClient *self, gboolean rude) @@ -751,49 +946,28 @@ void client_move_onscreen(ObClient *self, gboolean rude) gboolean client_find_onscreen(ObClient *self, gint *x, gint *y, gint w, gint h, gboolean rude) { - Rect *mon_a, *all_a; gint ox = *x, oy = *y; gboolean rudel = rude, ruder = rude, rudet = rude, rudeb = rude; gint fw, fh; + Rect desired; + guint i; - all_a = screen_area(self->desktop); - mon_a = screen_area_monitor(self->desktop, client_monitor(self)); + RECT_SET(desired, *x, *y, w, h); + frame_rect_to_frame(self->frame, &desired); /* get where the frame would be */ - frame_client_gravity(self->frame, x, y, w, h); + frame_client_gravity(self->frame, x, y); /* get the requested size of the window with decorations */ fw = self->frame->size.left + w + self->frame->size.right; fh = self->frame->size.top + h + self->frame->size.bottom; - /* This makes sure windows aren't entirely outside of the screen so you - can't see them at all. - It makes sure 10% of the window is on the screen at least. At don't let - it move itself off the top of the screen, which would hide the titlebar - on you. (The user can still do this if they want too, it's only limiting - the application. - - XXX watch for xinerama dead areas... - */ - if (client_normal(self)) { - if (!self->strut.right && *x + fw/10 >= all_a->x + all_a->width - 1) - *x = all_a->x + all_a->width - fw/10; - if (!self->strut.bottom && *y + fh/10 >= all_a->y + all_a->height - 1) - *y = all_a->y + all_a->height - fh/10; - if (!self->strut.left && *x + fw*9/10 - 1 < all_a->x) - *x = all_a->x - fw*9/10; - if (!self->strut.top && *y + fh*9/10 - 1 < all_a->y) - *y = all_a->y - fw*9/10; - } - - /* If rudeness wasn't requested, then figure out of the client is currently - entirely on the screen. If it is, and the position isn't changing by - request, and it is enlarging, then be rude even though it wasn't - requested */ + /* If rudeness wasn't requested, then still be rude in a given direction + if the client is not moving, only resizing in that direction */ if (!rude) { Point oldtl, oldtr, oldbl, oldbr; Point newtl, newtr, newbl, newbr; - gboolean stationary; + gboolean stationary_l, stationary_r, stationary_t, stationary_b; POINT_SET(oldtl, self->frame->area.x, self->frame->area.y); POINT_SET(oldbr, self->frame->area.x + self->frame->area.width - 1, @@ -807,140 +981,115 @@ gboolean client_find_onscreen(ObClient *self, gint *x, gint *y, gint w, gint h, POINT_SET(newbl, newtl.x, newbr.y); /* is it moving or just resizing from some corner? */ - stationary = (POINT_EQUAL(oldtl, newtl) || POINT_EQUAL(oldtr, newtr) || - POINT_EQUAL(oldbl, newbl) || POINT_EQUAL(oldbr, newbr)); + stationary_l = oldtl.x == newtl.x; + stationary_r = oldtr.x == newtr.x; + stationary_t = oldtl.y == newtl.y; + stationary_b = oldbl.y == newbl.y; - /* if left edge is growing */ - if (stationary && newtl.x < oldtl.x) + /* if left edge is growing and didnt move right edge */ + if (stationary_r && newtl.x < oldtl.x) rudel = TRUE; - /* if right edge is growing */ - if (stationary && newtr.x > oldtr.x) + /* if right edge is growing and didnt move left edge */ + if (stationary_l && newtr.x > oldtr.x) ruder = TRUE; - /* if top edge is growing */ - if (stationary && newtl.y < oldtl.y) + /* if top edge is growing and didnt move bottom edge */ + if (stationary_b && newtl.y < oldtl.y) rudet = TRUE; - /* if bottom edge is growing */ - if (stationary && newbl.y > oldbl.y) + /* if bottom edge is growing and didnt move top edge */ + if (stationary_t && newbl.y > oldbl.y) rudeb = TRUE; } - /* This here doesn't let windows even a pixel outside the struts/screen. - * When called from client_manage, programs placing themselves are - * forced completely onscreen, while things like - * xterm -geometry resolution-width/2 will work fine. Trying to - * place it completely offscreen will be handled in the above code. - * Sorry for this confused comment, i am tired. */ - if (fw <= mon_a->width) { - if (rudel && !self->strut.left && *x < mon_a->x) *x = mon_a->x; - if (ruder && !self->strut.right && *x + fw > mon_a->x + mon_a->width) - *x = mon_a->x + mon_a->width - fw; - } - if (fh <= mon_a->height) { - if (rudet && !self->strut.top && *y < mon_a->y) *y = mon_a->y; - if (rudeb && !self->strut.bottom && *y + fh > mon_a->y + mon_a->height) - *y = mon_a->y + mon_a->height - fh; - } + for (i = 0; i < screen_num_monitors; ++i) { + Rect *a; - /* get where the client should be */ - frame_frame_gravity(self->frame, x, y, w, h); + if (!screen_physical_area_monitor_contains(i, &desired)) { + if (i < screen_num_monitors - 1) + continue; - return ox != *x || oy != *y; -} + /* the window is not inside any monitor! so just use the first + one */ + a = screen_area(self->desktop, 0, NULL); + } else + a = screen_area(self->desktop, SCREEN_AREA_ONE_MONITOR, &desired); + + /* This makes sure windows aren't entirely outside of the screen so you + can't see them at all. + It makes sure 10% of the window is on the screen at least. At don't + let it move itself off the top of the screen, which would hide the + titlebar on you. (The user can still do this if they want too, it's + only limiting the application. + */ + if (client_normal(self)) { + if (!self->strut.right && *x + fw/10 >= a->x + a->width - 1) + *x = a->x + a->width - fw/10; + if (!self->strut.bottom && *y + fh/10 >= a->y + a->height - 1) + *y = a->y + a->height - fh/10; + if (!self->strut.left && *x + fw*9/10 - 1 < a->x) + *x = a->x - fw*9/10; + if (!self->strut.top && *y + fh*9/10 - 1 < a->y) + *y = a->y - fh*9/10; + } -static void client_toggle_border(ObClient *self, gboolean show) -{ - /* adjust our idea of where the client is, based on its border. When the - border is removed, the client should now be considered to be in a - different position. - when re-adding the border to the client, the same operation needs to be - reversed. */ - gint oldx = self->area.x, oldy = self->area.y; - gint x = oldx, y = oldy; - switch(self->gravity) { - default: - case NorthWestGravity: - case WestGravity: - case SouthWestGravity: - break; - case NorthEastGravity: - case EastGravity: - case SouthEastGravity: - if (show) x -= self->border_width * 2; - else x += self->border_width * 2; - break; - case NorthGravity: - case SouthGravity: - case CenterGravity: - case ForgetGravity: - case StaticGravity: - if (show) x -= self->border_width; - else x += self->border_width; - break; - } - switch(self->gravity) { - default: - case NorthWestGravity: - case NorthGravity: - case NorthEastGravity: - break; - case SouthWestGravity: - case SouthGravity: - case SouthEastGravity: - if (show) y -= self->border_width * 2; - else y += self->border_width * 2; - break; - case WestGravity: - case EastGravity: - case CenterGravity: - case ForgetGravity: - case StaticGravity: - if (show) y -= self->border_width; - else y += self->border_width; - break; + /* This here doesn't let windows even a pixel outside the + struts/screen. When called from client_manage, programs placing + themselves are forced completely onscreen, while things like + xterm -geometry resolution-width/2 will work fine. Trying to + place it completely offscreen will be handled in the above code. + Sorry for this confused comment, i am tired. */ + if (rudel && !self->strut.left && *x < a->x) *x = a->x; + if (ruder && !self->strut.right && *x + fw > a->x + a->width) + *x = a->x + MAX(0, a->width - fw); + + if (rudet && !self->strut.top && *y < a->y) *y = a->y; + if (rudeb && !self->strut.bottom && *y + fh > a->y + a->height) + *y = a->y + MAX(0, a->height - fh); + + g_free(a); } - self->area.x = x; - self->area.y = y; - if (show) { - XSetWindowBorderWidth(ob_display, self->window, self->border_width); + /* get where the client should be */ + frame_frame_gravity(self->frame, x, y); - /* set border_width to 0 because there is no border to add into - calculations anymore */ - self->border_width = 0; - } else - XSetWindowBorderWidth(ob_display, self->window, 0); + return ox != *x || oy != *y; } - -static void client_get_all(ObClient *self) +static void client_get_all(ObClient *self, gboolean real) { + /* this is needed for the frame to set itself up */ client_get_area(self); + + /* these things can change the decor and functions of the window */ + client_get_mwm_hints(self); + /* this can change the mwmhints for special cases */ + client_get_type_and_transientness(self); + client_get_state(self); + client_update_normal_hints(self); - /* The transient-ness of a window is used to pick a type, but the type can - also affect transiency. + /* get the session related properties, these can change decorations + from per-app settings */ + client_get_session_ids(self); - Dialogs are always made transients for their group if they have one. + /* now we got everything that can affect the decorations */ + if (!real) + return; - I also have made non-application type windows be transients for their - group (eg utility windows). - */ - client_get_transientness(self); - client_get_type(self);/* this can change the mwmhints for special cases */ - client_get_state(self); + /* get this early so we have it for debugging */ + client_update_title(self); + + client_update_protocols(self); client_update_wmhints(self); /* this may have already been called from client_update_wmhints */ - if (self->transient_for == NULL) + if (!self->parents && !self->transient_for_group) client_update_transient_for(self); + client_get_startup_id(self); client_get_desktop(self);/* uses transient data/group/startup id if a desktop is not specified */ client_get_shaped(self); - client_get_layer(self); /* if layer hasn't been specified, get it from - other sources if possible */ - { /* a couple type-based defaults for new windows */ @@ -949,27 +1098,14 @@ static void client_get_all(ObClient *self) self->desktop = DESKTOP_ALL; } - client_update_protocols(self); - - client_get_gravity(self); /* get the attribute gravity */ - client_update_normal_hints(self); /* this may override the attribute - gravity */ - - /* got the type, the mwmhints, the protocols, and the normal hints - (min/max sizes), so we're ready to set up the decorations/functions */ - client_setup_decor_and_functions(self); - #ifdef SYNC client_update_sync_request_counter(self); #endif - client_get_client_machine(self); + client_get_colormap(self); - client_update_title(self); - client_update_class(self); - client_update_sm_client_id(self); client_update_strut(self); client_update_icons(self); - client_update_user_time(self); + client_update_icon_geometry(self); } static void client_get_startup_id(ObClient *self) @@ -984,7 +1120,7 @@ static void client_get_area(ObClient *self) { XWindowAttributes wattrib; Status ret; - + ret = XGetWindowAttributes(ob_display, self->window, &wattrib); g_assert(ret != BadWindow); @@ -992,8 +1128,8 @@ static void client_get_area(ObClient *self) POINT_SET(self->root_pos, wattrib.x, wattrib.y); self->border_width = wattrib.border_width; - ob_debug("client area: %d %d %d %d\n", wattrib.x, wattrib.y, - wattrib.width, wattrib.height); + ob_debug("client area: %d %d %d %d bw %d\n", wattrib.x, wattrib.y, + wattrib.width, wattrib.height, wattrib.border_width); } static void client_get_desktop(ObClient *self) @@ -1005,69 +1141,45 @@ static void client_get_desktop(ObClient *self) self->desktop = screen_num_desktops - 1; else self->desktop = d; + ob_debug("client requested desktop 0x%x\n", self->desktop); } else { - gboolean trdesk = FALSE; + GSList *it; + gboolean first = TRUE; + guint all = screen_num_desktops; /* not a valid value */ - if (self->transient_for) { - if (self->transient_for != OB_TRAN_GROUP) { - self->desktop = self->transient_for->desktop; - trdesk = TRUE; - } else { - GSList *it; + /* if they are all on one desktop, then open it on the + same desktop */ + for (it = self->parents; it; it = g_slist_next(it)) { + ObClient *c = it->data; - for (it = self->group->members; it; it = g_slist_next(it)) - if (it->data != self && - !((ObClient*)it->data)->transient_for) { - self->desktop = ((ObClient*)it->data)->desktop; - trdesk = TRUE; - break; - } + if (c->desktop == DESKTOP_ALL) continue; + + if (first) { + all = c->desktop; + first = FALSE; } + else if (all != c->desktop) + all = screen_num_desktops; /* make it invalid */ } - if (!trdesk) { - /* try get from the startup-notification protocol */ - if (sn_get_desktop(self->startup_id, &self->desktop)) { - if (self->desktop >= screen_num_desktops && - self->desktop != DESKTOP_ALL) - self->desktop = screen_num_desktops - 1; - } else - /* defaults to the current desktop */ - self->desktop = screen_desktop; - } - } -} - -static void client_get_layer(ObClient *self) -{ - if (!(self->above || self->below)) { - if (self->group) { - /* apply stuff from the group */ - GSList *it; - gint layer = -2; + if (all != screen_num_desktops) { + self->desktop = all; - for (it = self->group->members; it; it = g_slist_next(it)) { - ObClient *c = it->data; - if (c != self && !client_search_transient(self, c) && - client_normal(self) && client_normal(c)) - { - layer = MAX(layer, - (c->above ? 1 : (c->below ? -1 : 0))); - } - } - switch (layer) { - case -1: - self->below = TRUE; - break; - case -2: - case 0: - break; - case 1: - self->above = TRUE; - break; - default: - g_assert_not_reached(); - break; - } + ob_debug("client desktop set from parents: 0x%x\n", + self->desktop); + } + /* try get from the startup-notification protocol */ + else if (sn_get_desktop(self->startup_id, &self->desktop)) { + if (self->desktop >= screen_num_desktops && + self->desktop != DESKTOP_ALL) + self->desktop = screen_num_desktops - 1; + ob_debug("client desktop set from startup-notification: 0x%x\n", + self->desktop); + } + /* defaults to the current desktop */ + else { + self->desktop = screen_desktop; + ob_debug("client desktop set to the current desktop: %d\n", + self->desktop); } } } @@ -1076,7 +1188,7 @@ static void client_get_state(ObClient *self) { guint32 *state; guint num; - + if (PROP_GETA32(self->window, net_wm_state, atom, &state, &num)) { gulong i; for (i = 0; i < num; ++i) { @@ -1129,20 +1241,13 @@ static void client_get_shaped(ObClient *self) #endif } -void client_get_transientness(ObClient *self) -{ - Window t; - if (XGetTransientForHint(ob_display, self->window, &t)) - self->transient = TRUE; -} - void client_update_transient_for(ObClient *self) { Window t = None; ObClient *target = NULL; + gboolean trangroup = FALSE; if (XGetTransientForHint(ob_display, self->window, &t)) { - self->transient = TRUE; if (t != self->window) { /* cant be transient to itself! */ target = g_hash_table_lookup(window_map, &t); /* if this happens then we need to check for it*/ @@ -1152,155 +1257,138 @@ void client_update_transient_for(ObClient *self) a dockapp, for example */ target = NULL; } - - /* THIS IS SO ANNOYING ! ! ! ! Let me explain.... have a seat.. - - Setting the transient_for to Root is actually illegal, however - applications from time have done this to specify transient for - their group. - - Now you can do that by being a TYPE_DIALOG and not setting - the transient_for hint at all on your window. But people still - use Root, and Kwin is very strange in this regard. - - KWin 3.0 will not consider windows with transient_for set to - Root as transient for their group *UNLESS* they are also modal. - In that case, it will make them transient for the group. This - leads to all sorts of weird behavior from KDE apps which are - only tested in KWin. I'd like to follow their behavior just to - make this work right with KDE stuff, but that seems wrong. - */ - if (!target && self->group) { - /* not transient to a client, see if it is transient for a - group */ - if (t == RootWindow(ob_display, ob_screen)) { - /* window is a transient for its group! */ - target = OB_TRAN_GROUP; - } - } } - } else if (self->type == OB_CLIENT_TYPE_DIALOG || - self->type == OB_CLIENT_TYPE_TOOLBAR || - self->type == OB_CLIENT_TYPE_MENU || - self->type == OB_CLIENT_TYPE_UTILITY) - { - self->transient = TRUE; - if (self->group) - target = OB_TRAN_GROUP; - } else - self->transient = FALSE; + + /* Setting the transient_for to Root is actually illegal, however + applications from time have done this to specify transient for + their group */ + if (!target && self->group && t == RootWindow(ob_display, ob_screen)) + trangroup = TRUE; + } else if (self->group && self->transient) + trangroup = TRUE; client_update_transient_tree(self, self->group, self->group, - self->transient_for, target); - self->transient_for = target; - + self->transient_for_group, trangroup, + client_direct_parent(self), target); + self->transient_for_group = trangroup; + } static void client_update_transient_tree(ObClient *self, ObGroup *oldgroup, ObGroup *newgroup, + gboolean oldgtran, gboolean newgtran, ObClient* oldparent, ObClient *newparent) { GSList *it, *next; ObClient *c; - /* No change has occured */ - if (oldgroup == newgroup && oldparent == newparent) return; + g_assert(!oldgtran || oldgroup); + g_assert(!newgtran || newgroup); + g_assert((!oldgtran && !oldparent) || + (oldgtran && !oldparent) || + (!oldgtran && oldparent)); + g_assert((!newgtran && !newparent) || + (newgtran && !newparent) || + (!newgtran && newparent)); - /** Remove the client from the transient tree wherever it has changed **/ + /* * * + Group transient windows are not allowed to have other group + transient windows as their children. + * * */ - /* If the window is becoming a direct transient for a window in its group - then that window can't be a child of this window anymore */ - if (oldparent != newparent && - newparent != NULL && newparent != OB_TRAN_GROUP && - newparent->transient_for == OB_TRAN_GROUP && - newgroup != NULL && newgroup == oldgroup) - { - self->transients = g_slist_remove(self->transients, newparent); - } - - /* If the group changed then we need to remove any old group transient - windows from our children. But if we're transient for the group, then - other group transients are not our children. */ - if (oldgroup != newgroup && oldgroup != NULL && - oldparent != OB_TRAN_GROUP) - { - for (it = self->transients; it; it = next) { - next = g_slist_next(it); - c = it->data; - if (c->group == oldgroup) - self->transients = g_slist_delete_link(self->transients, it); - } - } + /* No change has occured */ + if (oldgroup == newgroup && + oldgtran == newgtran && + oldparent == newparent) return; - /* If we used to be transient for a group and now we are not, or we're - transient for a new group, then we need to remove ourselves from all - our ex-parents */ - if (oldparent == OB_TRAN_GROUP && (oldgroup != newgroup || - oldparent != newparent)) - { - for (it = oldgroup->members; it; it = g_slist_next(it)) { - c = it->data; - if (c != self && (!c->transient_for || - c->transient_for != OB_TRAN_GROUP)) - c->transients = g_slist_remove(c->transients, self); - } - } - /* If we used to be transient for a single window and we are no longer - transient for it, then we need to remove ourself from its children */ - else if (oldparent != NULL && oldparent != OB_TRAN_GROUP && - oldparent != newparent) - oldparent->transients = g_slist_remove(oldparent->transients, self); + /** Remove the client from the transient tree **/ + for (it = self->transients; it; it = next) { + next = g_slist_next(it); + c = it->data; + self->transients = g_slist_delete_link(self->transients, it); + c->parents = g_slist_remove(c->parents, self); + } + for (it = self->parents; it; it = next) { + next = g_slist_next(it); + c = it->data; + self->parents = g_slist_delete_link(self->parents, it); + c->transients = g_slist_remove(c->transients, self); + } - /** Re-add the client to the transient tree wherever it has changed **/ + /** Re-add the client to the transient tree **/ - /* If we're now transient for a group and we weren't transient for it - before then we need to add ourselves to all our new parents */ - if (newparent == OB_TRAN_GROUP && (oldgroup != newgroup || - oldparent != newparent)) - { - for (it = oldgroup->members; it; it = g_slist_next(it)) { + /* If we're transient for a group then we need to add ourselves to all our + parents */ + if (newgtran) { + for (it = newgroup->members; it; it = g_slist_next(it)) { c = it->data; - if (c != self && (!c->transient_for || - c->transient_for != OB_TRAN_GROUP)) - c->transients = g_slist_append(c->transients, self); + if (c != self && + !client_search_top_direct_parent(c)->transient_for_group && + client_normal(c)) + { + c->transients = g_slist_prepend(c->transients, self); + self->parents = g_slist_prepend(self->parents, c); + } } } - /* If we are now transient for a single window which we weren't before, - we need to add ourselves to its children + + /* If we are now transient for a single window we need to add ourselves to + its children WARNING: Cyclical transient ness is possible if two windows are transient for eachother. */ - else if (newparent != NULL && newparent != OB_TRAN_GROUP && - newparent != oldparent && + else if (newparent && /* don't make ourself its child if it is already our child */ - !client_is_direct_child(self, newparent)) - newparent->transients = g_slist_append(newparent->transients, self); + !client_is_direct_child(self, newparent) && + client_normal(newparent)) + { + newparent->transients = g_slist_prepend(newparent->transients, self); + self->parents = g_slist_prepend(self->parents, newparent); + } - /* If the group changed then we need to add any new group transient - windows to our children. But if we're transient for the group, then - other group transients are not our children. + /* Add any group transient windows to our children. But if we're transient + for the group, then other group transients are not our children. WARNING: Cyclical transient-ness is possible. For e.g. if: A is transient for the group - B is a member of the group and transient for A + B is transient for A + C is transient for B + A can't be transient for C or we have a cycle */ - if (oldgroup != newgroup && newgroup != NULL && - newparent != OB_TRAN_GROUP) + if (!newgtran && newgroup && + (!newparent || + !client_search_top_direct_parent(newparent)->transient_for_group) && + client_normal(self)) { for (it = newgroup->members; it; it = g_slist_next(it)) { c = it->data; - if (c != self && c->transient_for == OB_TRAN_GROUP && + if (c != self && c->transient_for_group && /* Don't make it our child if it is already our parent */ !client_is_direct_child(c, self)) { - self->transients = g_slist_append(self->transients, c); + self->transients = g_slist_prepend(self->transients, c); + c->parents = g_slist_prepend(c->parents, self); } } } + + /** If we change our group transient-ness, our children change their + effect group transient-ness, which affects how they relate to other + group windows **/ + + for (it = self->transients; it; it = g_slist_next(it)) { + c = it->data; + if (!c->transient_for_group) + client_update_transient_tree(c, c->group, c->group, + c->transient_for_group, + c->transient_for_group, + client_direct_parent(c), + client_direct_parent(c)); + } } static void client_get_mwm_hints(ObClient *self) @@ -1321,13 +1409,15 @@ static void client_get_mwm_hints(ObClient *self) } } -void client_get_type(ObClient *self) +void client_get_type_and_transientness(ObClient *self) { guint num, i; guint32 *val; + Window t; self->type = -1; - + self->transient = FALSE; + if (PROP_GETA32(self->window, net_wm_window_type, atom, &val, &num)) { /* use the first value that we know about in the array */ for (i = 0; i < num; ++i) { @@ -1360,7 +1450,10 @@ void client_get_type(ObClient *self) } g_free(val); } - + + if (XGetTransientForHint(ob_display, self->window, &t)) + self->transient = TRUE; + if (self->type == (ObClientType) -1) { /*the window type hint was not set, which means we either classify ourself as a normal window or a dialog, depending on if we are a @@ -1370,6 +1463,15 @@ void client_get_type(ObClient *self) else self->type = OB_CLIENT_TYPE_NORMAL; } + + /* then, based on our type, we can update our transientness.. */ + if (self->type == OB_CLIENT_TYPE_DIALOG || + self->type == OB_CLIENT_TYPE_TOOLBAR || + self->type == OB_CLIENT_TYPE_MENU || + self->type == OB_CLIENT_TYPE_UTILITY) + { + self->transient = TRUE; + } } void client_update_protocols(ObClient *self) @@ -1390,7 +1492,7 @@ void client_update_protocols(ObClient *self) notified whenever we want it to receive focus */ self->focus_notify = TRUE; #ifdef SYNC - else if (proto[i] == prop_atoms.net_wm_sync_request) + else if (proto[i] == prop_atoms.net_wm_sync_request) /* if this protocol is requested, then resizing the window will be synchronized between the frame and the client */ @@ -1413,16 +1515,6 @@ void client_update_sync_request_counter(ObClient *self) } #endif -static void client_get_gravity(ObClient *self) -{ - XWindowAttributes wattrib; - Status ret; - - ret = XGetWindowAttributes(ob_display, self->window, &wattrib); - g_assert(ret != BadWindow); - self->gravity = wattrib.win_gravity; -} - void client_get_colormap(ObClient *self) { XWindowAttributes wa; @@ -1433,14 +1525,22 @@ void client_get_colormap(ObClient *self) void client_update_colormap(ObClient *self, Colormap colormap) { - self->colormap = colormap; + if (colormap == self->colormap) return; + + ob_debug("Setting client %s colormap: 0x%x\n", self->title, colormap); + + if (client_focused(self)) { + screen_install_colormap(self, FALSE); /* uninstall old one */ + self->colormap = colormap; + screen_install_colormap(self, FALSE); /* install new one */ + } else + self->colormap = colormap; } void client_update_normal_hints(ObClient *self) { XSizeHints size; glong ret; - gint oldgravity = self->gravity; /* defaults */ self->min_ratio = 0.0f; @@ -1456,20 +1556,10 @@ void client_update_normal_hints(ObClient *self) if (!client_normal(self)) */ self->positioned = (size.flags & (PPosition|USPosition)); + self->sized = (size.flags & (PSize|USSize)); - if (size.flags & PWinGravity) { + if (size.flags & PWinGravity) self->gravity = size.win_gravity; - - /* if the client has a frame, i.e. has already been mapped and - is changing its gravity */ - if (self->frame && self->gravity != oldgravity) { - /* move our idea of the client's position based on its new - gravity */ - client_convert_gravity(self, oldgravity, - &self->area.x, &self->area.y, - self->area.width, self->area.height); - } - } if (size.flags & PAspect) { if (size.min_aspect.y) @@ -1482,19 +1572,28 @@ void client_update_normal_hints(ObClient *self) if (size.flags & PMinSize) SIZE_SET(self->min_size, size.min_width, size.min_height); - + if (size.flags & PMaxSize) SIZE_SET(self->max_size, size.max_width, size.max_height); - + if (size.flags & PBaseSize) SIZE_SET(self->base_size, size.base_width, size.base_height); - + if (size.flags & PResizeInc && size.width_inc && size.height_inc) SIZE_SET(self->size_inc, size.width_inc, size.height_inc); + + ob_debug("Normal hints: min size (%d %d) max size (%d %d)\n " + "size inc (%d %d) base size (%d %d)\n", + self->min_size.width, self->min_size.height, + self->max_size.width, self->max_size.height, + self->size_inc.width, self->size_inc.height, + self->base_size.width, self->base_size.height); } + else + ob_debug("Normal hints: not set\n"); } -void client_setup_decor_and_functions(ObClient *self) +void client_setup_decor_and_functions(ObClient *self, gboolean reconfig) { /* start with everything (cept fullscreen) */ self->decorations = @@ -1514,7 +1613,10 @@ void client_setup_decor_and_functions(ObClient *self) OB_CLIENT_FUNC_ICONIFY | OB_CLIENT_FUNC_MAXIMIZE | OB_CLIENT_FUNC_SHADE | - OB_CLIENT_FUNC_CLOSE); + OB_CLIENT_FUNC_CLOSE | + OB_CLIENT_FUNC_BELOW | + OB_CLIENT_FUNC_ABOVE | + OB_CLIENT_FUNC_UNDECORATE); if (!(self->min_size.width < self->max_size.width || self->min_size.height < self->max_size.height)) @@ -1529,23 +1631,37 @@ void client_setup_decor_and_functions(ObClient *self) case OB_CLIENT_TYPE_DIALOG: case OB_CLIENT_TYPE_UTILITY: - /* these windows cannot be maximized */ - self->functions &= ~OB_CLIENT_FUNC_MAXIMIZE; + /* these windows don't have anything added or removed by default */ break; case OB_CLIENT_TYPE_MENU: case OB_CLIENT_TYPE_TOOLBAR: - /* these windows get less functionality */ - self->functions &= ~(OB_CLIENT_FUNC_ICONIFY | OB_CLIENT_FUNC_RESIZE); + /* these windows can't iconify or maximize */ + self->decorations &= ~(OB_FRAME_DECOR_ICONIFY | + OB_FRAME_DECOR_MAXIMIZE); + self->functions &= ~(OB_CLIENT_FUNC_ICONIFY | + OB_CLIENT_FUNC_MAXIMIZE); break; - case OB_CLIENT_TYPE_DESKTOP: - case OB_CLIENT_TYPE_DOCK: case OB_CLIENT_TYPE_SPLASH: - /* none of these windows are manipulated by the window manager */ + /* these don't get get any decorations, and the only thing you can + do with them is move them */ + self->decorations = 0; + self->functions = OB_CLIENT_FUNC_MOVE; + break; + + case OB_CLIENT_TYPE_DESKTOP: + /* these windows are not manipulated by the window manager */ self->decorations = 0; self->functions = 0; break; + + case OB_CLIENT_TYPE_DOCK: + /* these windows are not manipulated by the window manager, but they + can set below layer which has a special meaning */ + self->decorations = 0; + self->functions = OB_CLIENT_FUNC_BELOW; + break; } /* Mwm Hints are applied subtractively to what has already been chosen for @@ -1589,7 +1705,7 @@ void client_setup_decor_and_functions(ObClient *self) if (!(self->functions & OB_CLIENT_FUNC_ICONIFY)) self->decorations &= ~OB_FRAME_DECOR_ICONIFY; if (!(self->functions & OB_CLIENT_FUNC_RESIZE)) - self->decorations &= ~OB_FRAME_DECOR_GRIPS; + self->decorations &= ~(OB_FRAME_DECOR_GRIPS | OB_FRAME_DECOR_HANDLE); /* can't maximize without moving/resizing */ if (!((self->functions & OB_CLIENT_FUNC_MAXIMIZE) && @@ -1599,18 +1715,22 @@ void client_setup_decor_and_functions(ObClient *self) self->decorations &= ~OB_FRAME_DECOR_MAXIMIZE; } - /* kill the handle on fully maxed windows */ - if (self->max_vert && self->max_horz) - self->decorations &= ~OB_FRAME_DECOR_HANDLE; + if (self->max_horz && self->max_vert) { + /* you can't resize fully maximized windows */ + self->functions &= ~OB_CLIENT_FUNC_RESIZE; + /* kill the handle on fully maxed windows */ + self->decorations &= ~(OB_FRAME_DECOR_HANDLE | OB_FRAME_DECOR_GRIPS); + } + + /* If there are no decorations to remove, don't allow the user to try + toggle the state */ + if (self->decorations == 0) + self->functions &= ~OB_CLIENT_FUNC_UNDECORATE; /* finally, the user can have requested no decorations, which overrides everything (but doesnt give it a border if it doesnt have one) */ - if (self->undecorated) { - if (config_theme_keepborder) - self->decorations &= OB_FRAME_DECOR_BORDER; - else - self->decorations = 0; - } + if (self->undecorated) + self->decorations = 0; /* if we don't have a titlebar, then we cannot shade! */ if (!(self->decorations & OB_FRAME_DECOR_TITLEBAR)) @@ -1626,15 +1746,14 @@ void client_setup_decor_and_functions(ObClient *self) client_change_allowed_actions(self); - if (self->frame) { - /* adjust the client's decorations, etc. */ - client_reconfigure(self); - } + if (reconfig) + /* force reconfigure to make sure decorations are updated */ + client_reconfigure(self, TRUE); } static void client_change_allowed_actions(ObClient *self) { - gulong actions[9]; + gulong actions[12]; gint num = 0; /* desktop windows are kept on all desktops */ @@ -1657,6 +1776,12 @@ static void client_change_allowed_actions(ObClient *self) actions[num++] = prop_atoms.net_wm_action_maximize_horz; actions[num++] = prop_atoms.net_wm_action_maximize_vert; } + if (self->functions & OB_CLIENT_FUNC_ABOVE) + actions[num++] = prop_atoms.net_wm_action_above; + if (self->functions & OB_CLIENT_FUNC_BELOW) + actions[num++] = prop_atoms.net_wm_action_below; + if (self->functions & OB_CLIENT_FUNC_UNDECORATE) + actions[num++] = prop_atoms.ob_wm_action_undecorate; PROP_SETA32(self->window, net_wm_allowed_actions, atom, actions, num); @@ -1667,7 +1792,7 @@ static void client_change_allowed_actions(ObClient *self) else self->shaded = FALSE; } if (!(self->functions & OB_CLIENT_FUNC_ICONIFY) && self->iconic) { - if (self->frame) client_iconify(self, FALSE, TRUE); + if (self->frame) client_iconify(self, FALSE, TRUE, FALSE); else self->iconic = FALSE; } if (!(self->functions & OB_CLIENT_FUNC_FULLSCREEN) && self->fullscreen) { @@ -1681,22 +1806,13 @@ static void client_change_allowed_actions(ObClient *self) } } -void client_reconfigure(ObClient *self) -{ - /* by making this pass FALSE for user, we avoid the emacs event storm where - every configurenotify causes an update in its normal hints, i think this - is generally what we want anyways... */ - client_configure(self, self->area.x, self->area.y, - self->area.width, self->area.height, FALSE, TRUE); -} - void client_update_wmhints(ObClient *self) { XWMHints *hints; /* assume a window takes input if it doesnt specify */ self->can_focus = TRUE; - + if ((hints = XGetWMHints(ob_display, self->window)) != NULL) { gboolean ur; @@ -1739,8 +1855,10 @@ void client_update_wmhints(ObClient *self) /* Put ourselves into the new group's transient tree, and remove ourselves from the old group's */ client_update_transient_tree(self, oldgroup, self->group, - self->transient_for, - self->transient_for); + self->transient_for_group, + self->transient_for_group, + client_direct_parent(self), + client_direct_parent(self)); /* Lastly, being in a group, or not, can change if the window is transient for anything. @@ -1750,11 +1868,11 @@ void client_update_wmhints(ObClient *self) transient for something, even if transient_for was NULL because it wasn't in a group before. - If transient_for was NULL and oldgroup was NULL we can assume + If parents was NULL and oldgroup was NULL we can assume that when we add the new group, it will become transient for something. - If transient_for was OB_TRAN_GROUP, then it must have already + If transient_for_group is TRUE, then it must have already had a group. If it is getting a new group, the above call to client_update_transient_tree has already taken care of everything ! If it is losing all group status then it will @@ -1762,13 +1880,14 @@ void client_update_wmhints(ObClient *self) updated. */ if (self->transient && - ((self->transient_for == NULL && oldgroup == NULL) || - (self->transient_for == OB_TRAN_GROUP && !self->group))) + ((self->parents == NULL && oldgroup == NULL) || + (self->transient_for_group && !self->group))) client_update_transient_for(self); } /* the WM_HINTS can contain an icon */ - client_update_icons(self); + if (hints->flags & IconPixmapHint) + client_update_icons(self); XFree(hints); } @@ -1780,17 +1899,17 @@ void client_update_title(ObClient *self) gchar *visible = NULL; g_free(self->title); - + /* try netwm */ if (!PROP_GETS(self->window, net_wm_name, utf8, &data)) { /* try old x stuff */ if (!(PROP_GETS(self->window, wm_name, locale, &data) || PROP_GETS(self->window, wm_name, utf8, &data))) { if (self->transient) { - /* - GNOME alert windows are not given titles: - http://developer.gnome.org/projects/gup/hig/draft_hig_new/windows-alert.html - */ + /* + GNOME alert windows are not given titles: + http://developer.gnome.org/projects/gup/hig/draft_hig_new/windows-alert.html + */ data = g_strdup(""); } else data = g_strdup("Unnamed Window"); @@ -1820,36 +1939,14 @@ void client_update_title(ObClient *self) PROP_GETS(self->window, wm_icon_name, utf8, &data))) data = g_strdup(self->title); - PROP_SETS(self->window, net_wm_visible_icon_name, data); - self->icon_title = data; -} - -void client_update_class(ObClient *self) -{ - gchar **data; - gchar *s; - - if (self->name) g_free(self->name); - if (self->class) g_free(self->class); - if (self->role) g_free(self->role); - - self->name = self->class = self->role = NULL; - - if (PROP_GETSS(self->window, wm_class, locale, &data)) { - if (data[0]) { - self->name = g_strdup(data[0]); - if (data[1]) - self->class = g_strdup(data[1]); - } - g_strfreev(data); - } - - if (PROP_GETS(self->window, wm_window_role, locale, &s)) - self->role = s; + if (self->client_machine) { + visible = g_strdup_printf("%s (%s)", data, self->client_machine); + g_free(data); + } else + visible = data; - if (self->name == NULL) self->name = g_strdup(""); - if (self->class == NULL) self->class = g_strdup(""); - if (self->role == NULL) self->role = g_strdup(""); + PROP_SETS(self->window, net_wm_visible_icon_name, visible); + self->icon_title = visible; } void client_update_strut(ObClient *self) @@ -1874,12 +1971,12 @@ void client_update_strut(ObClient *self) if (!got && PROP_GETA32(self->window, net_wm_strut, cardinal, &data, &num)) { if (num == 4) { - const Rect *a; + Rect *a; got = TRUE; /* use the screen's width/height */ - a = screen_physical_area(); + a = screen_physical_area_all_monitors(); STRUT_PARTIAL_SET(strut, data[0], data[2], data[1], data[3], @@ -1887,6 +1984,7 @@ void client_update_strut(ObClient *self) a->x, a->x + a->width - 1, a->y, a->y + a->height - 1, a->x, a->x + a->width - 1); + g_free(a); } g_free(data); } @@ -1929,7 +2027,7 @@ void client_update_icons(ObClient *self) } self->icons = g_new(ObClientIcon, self->nicons); - + /* store the icons */ i = 0; for (j = 0; j < self->nicons; ++j) { @@ -1961,18 +2059,19 @@ void client_update_icons(ObClient *self) if ((hints = XGetWMHints(ob_display, self->window))) { if (hints->flags & IconPixmapHint) { - self->nicons++; + self->nicons = 1; self->icons = g_new(ObClientIcon, self->nicons); xerror_set_ignore(TRUE); if (!RrPixmapToRGBA(ob_rr_inst, hints->icon_pixmap, (hints->flags & IconMaskHint ? hints->icon_mask : None), - &self->icons[self->nicons-1].width, - &self->icons[self->nicons-1].height, - &self->icons[self->nicons-1].data)){ - g_free(&self->icons[self->nicons-1]); - self->nicons--; + &self->icons[0].width, + &self->icons[0].height, + &self->icons[0].data)) + { + g_free(self->icons); + self->nicons = 0; } xerror_set_ignore(FALSE); } @@ -1983,8 +2082,11 @@ void client_update_icons(ObClient *self) /* set the default icon onto the window in theory, this could be a race, but if a window doesn't set an icon or removes it entirely, it's not very likely it is going to set one - right away afterwards */ - if (self->nicons == 0) { + right away afterwards + + if it has parents, then one of them will have an icon already + */ + if (self->nicons == 0 && !self->parents) { RrPixel32 *icon = ob_rr_theme->def_win_icon; gulong *data; @@ -2003,37 +2105,113 @@ void client_update_icons(ObClient *self) frame_adjust_icon(self->frame); } -void client_update_user_time(ObClient *self) +void client_update_icon_geometry(ObClient *self) { - guint32 time; + guint num; + guint32 *data; - if (PROP_GET32(self->window, net_wm_user_time, cardinal, &time)) { - /* we set this every time, not just when it grows, because in practice - sometimes time goes backwards! (ntpdate.. yay....) so.. if it goes - backward we don't want all windows to stop focusing. we'll just - assume noone is setting times older than the last one, cuz that - would be pretty stupid anyways - */ - self->user_time = time; + RECT_SET(self->icon_geometry, 0, 0, 0, 0); - /* - ob_debug("window %s user time %u\n", self->title, time); - */ + if (PROP_GETA32(self->window, net_wm_icon_geometry, cardinal, &data, &num) + && num == 4) + { + /* don't let them set it with an area < 0 */ + RECT_SET(self->icon_geometry, data[0], data[1], + MAX(data[2],0), MAX(data[3],0)); } } -static void client_get_client_machine(ObClient *self) +static void client_get_session_ids(ObClient *self) { - gchar *data = NULL; - gchar localhost[128]; + guint32 leader; + gboolean got; + gchar *s; + gchar **ss; - g_free(self->client_machine); + if (!PROP_GET32(self->window, wm_client_leader, window, &leader)) + leader = None; + + /* get the SM_CLIENT_ID */ + got = FALSE; + if (leader) + got = PROP_GETS(leader, sm_client_id, locale, &self->sm_client_id); + if (!got) + PROP_GETS(self->window, sm_client_id, locale, &self->sm_client_id); + + /* get the WM_CLASS (name and class). make them "" if they are not + provided */ + got = FALSE; + if (leader) + got = PROP_GETSS(leader, wm_class, locale, &ss); + if (!got) + got = PROP_GETSS(self->window, wm_class, locale, &ss); + + if (got) { + if (ss[0]) { + self->name = g_strdup(ss[0]); + if (ss[1]) + self->class = g_strdup(ss[1]); + } + g_strfreev(ss); + } + + if (self->name == NULL) self->name = g_strdup(""); + if (self->class == NULL) self->class = g_strdup(""); + + /* get the WM_WINDOW_ROLE. make it "" if it is not provided */ + got = FALSE; + if (leader) + got = PROP_GETS(leader, wm_window_role, locale, &s); + if (!got) + got = PROP_GETS(self->window, wm_window_role, locale, &s); + + if (got) + self->role = s; + else + self->role = g_strdup(""); + + /* get the WM_COMMAND */ + got = FALSE; + + if (leader) + got = PROP_GETSS(leader, wm_command, locale, &ss); + if (!got) + got = PROP_GETSS(self->window, wm_command, locale, &ss); + + if (got) { + /* merge/mash them all together */ + gchar *merge = NULL; + gint i; + + for (i = 0; ss[i]; ++i) { + gchar *tmp = merge; + if (merge) + merge = g_strconcat(merge, ss[i], NULL); + else + merge = g_strconcat(ss[i], NULL); + g_free(tmp); + } + g_strfreev(ss); + + self->wm_command = merge; + } + + /* get the WM_CLIENT_MACHINE */ + got = FALSE; + if (leader) + got = PROP_GETS(leader, wm_client_machine, locale, &s); + if (!got) + got = PROP_GETS(self->window, wm_client_machine, locale, &s); + + if (got) { + gchar localhost[128]; - if (PROP_GETS(self->window, wm_client_machine, locale, &data)) { gethostname(localhost, 127); localhost[127] = '\0'; - if (strcmp(localhost, data)) - self->client_machine = data; + if (strcmp(localhost, s) != 0) + self->client_machine = s; + else + g_free(s); } } @@ -2044,9 +2222,11 @@ static void client_change_wm_state(ObClient *self) old = self->wmstate; - if (self->shaded || self->iconic || !self->frame->visible) + if (self->shaded || self->iconic || + (self->desktop != DESKTOP_ALL && self->desktop != screen_desktop)) + { self->wmstate = IconicState; - else + } else self->wmstate = NormalState; if (old != self->wmstate) { @@ -2061,7 +2241,7 @@ static void client_change_wm_state(ObClient *self) static void client_change_state(ObClient *self) { - gulong netstate[11]; + gulong netstate[12]; guint num; num = 0; @@ -2109,54 +2289,79 @@ ObClient *client_search_focus_tree(ObClient *self) ObClient *client_search_focus_tree_full(ObClient *self) { - if (self->transient_for) { - if (self->transient_for != OB_TRAN_GROUP) { - return client_search_focus_tree_full(self->transient_for); - } else { - GSList *it; - gboolean recursed = FALSE; - - for (it = self->group->members; it; it = g_slist_next(it)) - if (!((ObClient*)it->data)->transient_for) { - ObClient *c; - if ((c = client_search_focus_tree_full(it->data))) - return c; - recursed = TRUE; - } - if (recursed) - return NULL; + if (self->parents) { + GSList *it; + + for (it = self->parents; it; it = g_slist_next(it)) { + ObClient *c = it->data; + if ((c = client_search_focus_tree_full(it->data))) return c; } + + return NULL; + } + else { + /* this function checks the whole tree, the client_search_focus_tree + does not, so we need to check this window */ + if (client_focused(self)) + return self; + return client_search_focus_tree(self); } +} + +ObClient *client_search_focus_group_full(ObClient *self) +{ + GSList *it; + + if (self->group) { + for (it = self->group->members; it; it = g_slist_next(it)) { + ObClient *c = it->data; + + if (client_focused(c)) return c; + if ((c = client_search_focus_tree(it->data))) return c; + } + } else + if (client_focused(self)) return self; + return NULL; +} - /* this function checks the whole tree, the client_search_focus_tree~ - does not, so we need to check this window */ - if (client_focused(self)) - return self; - return client_search_focus_tree(self); +gboolean client_has_parent(ObClient *self) +{ + return self->parents != NULL; } static ObStackingLayer calc_layer(ObClient *self) { ObStackingLayer l; + Rect *monitor; - if (self->fullscreen && - (client_focused(self) || client_search_focus_tree(self))) - l = OB_STACKING_LAYER_FULLSCREEN; - else if (self->type == OB_CLIENT_TYPE_DESKTOP) + monitor = screen_physical_area_monitor(client_monitor(self)); + + if (self->type == OB_CLIENT_TYPE_DESKTOP) l = OB_STACKING_LAYER_DESKTOP; else if (self->type == OB_CLIENT_TYPE_DOCK) { if (self->below) l = OB_STACKING_LAYER_NORMAL; else l = OB_STACKING_LAYER_ABOVE; } + else if ((self->fullscreen || + /* No decorations and fills the monitor = oldskool fullscreen. + But not for maximized windows. + */ + (self->decorations == 0 && + !(self->max_horz && self->max_vert) && + RECT_EQUAL(self->area, *monitor))) && + (client_focused(self) || client_search_focus_tree(self))) + l = OB_STACKING_LAYER_FULLSCREEN; else if (self->above) l = OB_STACKING_LAYER_ABOVE; else if (self->below) l = OB_STACKING_LAYER_BELOW; else l = OB_STACKING_LAYER_NORMAL; + g_free(monitor); + return l; } static void client_calc_layer_recursive(ObClient *self, ObClient *orig, - ObStackingLayer min, gboolean raised) + ObStackingLayer min) { ObStackingLayer old, own; GSList *it; @@ -2165,16 +2370,14 @@ static void client_calc_layer_recursive(ObClient *self, ObClient *orig, own = calc_layer(self); self->layer = MAX(own, min); + if (self->layer != old) { + stacking_remove(CLIENT_AS_WINDOW(self)); + stacking_add_nonintrusive(CLIENT_AS_WINDOW(self)); + } + for (it = self->transients; it; it = g_slist_next(it)) client_calc_layer_recursive(it->data, orig, - self->layer, - raised ? raised : self->layer != old); - - if (!raised && self->layer != old) - if (orig->frame) { /* only restack if the original window is managed */ - stacking_remove(CLIENT_AS_WINDOW(self)); - stacking_add(CLIENT_AS_WINDOW(self)); - } + self->layer); } void client_calc_layer(ObClient *self) @@ -2188,7 +2391,7 @@ void client_calc_layer(ObClient *self) it = client_search_all_top_parents(self); for (; it; it = g_slist_next(it)) - client_calc_layer_recursive(it->data, orig, 0, FALSE); + client_calc_layer_recursive(it->data, orig, 0); } gboolean client_should_show(ObClient *self) @@ -2197,71 +2400,74 @@ gboolean client_should_show(ObClient *self) return FALSE; if (client_normal(self) && screen_showing_desktop) return FALSE; - /* - if (self->transient_for) { - if (self->transient_for != OB_TRAN_GROUP) - return client_should_show(self->transient_for); - else { - GSList *it; - - for (it = self->group->members; it; it = g_slist_next(it)) { - ObClient *c = it->data; - if (c != self && !c->transient_for) { - if (client_should_show(c)) - return TRUE; - } - } - } - } - */ if (self->desktop == screen_desktop || self->desktop == DESKTOP_ALL) return TRUE; - + return FALSE; } -void client_show(ObClient *self) +gboolean client_show(ObClient *self) { + gboolean show = FALSE; if (client_should_show(self)) { frame_show(self->frame); - } + show = TRUE; - /* According to the ICCCM (sec 4.1.3.1) when a window is not visible, it - needs to be in IconicState. This includes when it is on another - desktop! - */ - client_change_wm_state(self); + /* According to the ICCCM (sec 4.1.3.1) when a window is not visible, + it needs to be in IconicState. This includes when it is on another + desktop! + */ + client_change_wm_state(self); + } + return show; } -void client_hide(ObClient *self) +gboolean client_hide(ObClient *self) { + gboolean hide = FALSE; + if (!client_should_show(self)) { - frame_hide(self->frame); - } + if (self == focus_client) { + /* if there is a grab going on, then we need to cancel it. if we + move focus during the grab, applications will get + NotifyWhileGrabbed events and ignore them ! - /* According to the ICCCM (sec 4.1.3.1) when a window is not visible, it - needs to be in IconicState. This includes when it is on another - desktop! - */ - client_change_wm_state(self); -} + actions should not rely on being able to move focus during an + interactive grab. + */ + event_cancel_all_key_grabs(); + } -void client_showhide(ObClient *self) -{ + /* We don't need to ignore enter events here. + The window can hide/iconify in 3 different ways: + 1 - through an x message. in this case we ignore all enter events + caused by responding to the x message (unless underMouse) + 2 - by a keyboard action. in this case we ignore all enter events + caused by the action + 3 - by a mouse action. in this case they are doing stuff with the + mouse and focus _should_ move. + + Also in action_end, we simulate an enter event that can't be ignored + so trying to ignore them is futile in case 3 anyways + */ - if (client_should_show(self)) { - frame_show(self->frame); - } - else { frame_hide(self->frame); + hide = TRUE; + + /* According to the ICCCM (sec 4.1.3.1) when a window is not visible, + it needs to be in IconicState. This includes when it is on another + desktop! + */ + client_change_wm_state(self); } + return hide; +} - /* According to the ICCCM (sec 4.1.3.1) when a window is not visible, it - needs to be in IconicState. This includes when it is on another - desktop! - */ - client_change_wm_state(self); +void client_showhide(ObClient *self) +{ + if (!client_show(self)) + client_hide(self); } gboolean client_normal(ObClient *self) { @@ -2270,75 +2476,99 @@ gboolean client_normal(ObClient *self) { self->type == OB_CLIENT_TYPE_SPLASH); } -gboolean client_application(ObClient *self) +gboolean client_helper(ObClient *self) { - return (self->type == OB_CLIENT_TYPE_NORMAL || - self->type == OB_CLIENT_TYPE_DIALOG); + return (self->type == OB_CLIENT_TYPE_UTILITY || + self->type == OB_CLIENT_TYPE_MENU || + self->type == OB_CLIENT_TYPE_TOOLBAR); } -static void client_apply_startup_state(ObClient *self, gint x, gint y) +gboolean client_mouse_focusable(ObClient *self) { - gboolean pos = FALSE; /* has the window's position been configured? */ - gint ox, oy; + return !(self->type == OB_CLIENT_TYPE_MENU || + self->type == OB_CLIENT_TYPE_TOOLBAR || + self->type == OB_CLIENT_TYPE_SPLASH || + self->type == OB_CLIENT_TYPE_DOCK); +} - /* save the position, and set self->area for these to use */ - ox = self->area.x; - oy = self->area.y; - self->area.x = x; - self->area.y = y; +gboolean client_enter_focusable(ObClient *self) +{ + /* you can focus desktops but it shouldn't on enter */ + return (client_mouse_focusable(self) && + self->type != OB_CLIENT_TYPE_DESKTOP); +} - /* set the desktop hint, to make sure that it always exists */ - PROP_SET32(self->window, net_wm_desktop, cardinal, self->desktop); - /* these are in a carefully crafted order.. */ +static void client_apply_startup_state(ObClient *self, + gint x, gint y, gint w, gint h) +{ + /* save the states that we are going to apply */ + gboolean iconic = self->iconic; + gboolean fullscreen = self->fullscreen; + gboolean undecorated = self->undecorated; + gboolean shaded = self->shaded; + gboolean demands_attention = self->demands_attention; + gboolean max_horz = self->max_horz; + gboolean max_vert = self->max_vert; + Rect oldarea; + gint l; + + /* turn them all off in the client, so they won't affect the window + being placed */ + self->iconic = self->fullscreen = self->undecorated = self->shaded = + self->demands_attention = self->max_horz = self->max_vert = FALSE; - if (self->iconic) { - self->iconic = FALSE; - client_iconify(self, TRUE, FALSE); - } - if (self->fullscreen) { - self->fullscreen = FALSE; + /* move the client to its placed position, or it it's already there, + generate a ConfigureNotify telling the client where it is. + + do this after adjusting the frame. otherwise it gets all weird and + clients don't work right + + do this before applying the states so they have the correct + pre-max/pre-fullscreen values + */ + client_try_configure(self, &x, &y, &w, &h, &l, &l, FALSE); + ob_debug("placed window 0x%x at %d, %d with size %d x %d\n", + self->window, x, y, w, h); + /* save the area, and make it where it should be for the premax stuff */ + oldarea = self->area; + RECT_SET(self->area, x, y, w, h); + + /* apply the states. these are in a carefully crafted order.. */ + + if (iconic) + client_iconify(self, TRUE, FALSE, TRUE); + if (fullscreen) client_fullscreen(self, TRUE); - pos = TRUE; - } - if (self->undecorated) { - self->undecorated = FALSE; + if (undecorated) client_set_undecorated(self, TRUE); - } - if (self->shaded) { - self->shaded = FALSE; + if (shaded) client_shade(self, TRUE); - } - if (self->demands_attention) { - self->demands_attention = FALSE; + if (demands_attention) client_hilite(self, TRUE); - } - - if (self->max_vert && self->max_horz) { - self->max_vert = self->max_horz = FALSE; + + if (max_vert && max_horz) client_maximize(self, TRUE, 0); - pos = TRUE; - } else if (self->max_vert) { - self->max_vert = FALSE; + else if (max_vert) client_maximize(self, TRUE, 2); - pos = TRUE; - } else if (self->max_horz) { - self->max_horz = FALSE; + else if (max_horz) client_maximize(self, TRUE, 1); - pos = TRUE; - } - /* if the client didn't get positioned yet, then do so now - call client_move even if the window is not being moved anywhere, because - when we reparent it and decorate it, it is getting moved and we need to - be telling it so with a ConfigureNotify event. + /* if the window hasn't been configured yet, then do so now, in fact the + x,y,w,h may _not_ be the same as the area rect, which can end up + meaning that the client isn't properly moved/resized by the fullscreen + function + pho can cause this because it maps at size of the screen but not 0,0 + so openbox moves it on screen to 0,0 (thus x,y=0,0 and area.x,y don't). + then fullscreen'ing makes it go to 0,0 which it thinks it already is at + cuz thats where the pre-fullscreen will be. however the actual area is + not, so this needs to be called even if we have fullscreened/maxed */ - if (!pos) { - /* use the saved position */ - self->area.x = ox; - self->area.y = oy; - client_move(self, x, y); - } + self->area = oldarea; + client_configure(self, x, y, w, h, FALSE, TRUE, FALSE); + + /* set the desktop hint, to make sure that it always exists */ + PROP_SET32(self->window, net_wm_desktop, cardinal, self->desktop); /* nothing to do for the other states: skip_taskbar @@ -2349,34 +2579,138 @@ static void client_apply_startup_state(ObClient *self, gint x, gint y) */ } -void client_convert_gravity(ObClient *self, gint gravity, gint *x, gint *y, - gint w, gint h) +void client_gravity_resize_w(ObClient *self, gint *x, gint oldw, gint neww) { - gint oldg = self->gravity; + /* these should be the current values. this is for when you're not moving, + just resizing */ + g_assert(*x == self->area.x); + g_assert(oldw == self->area.width); + + /* horizontal */ + switch (self->gravity) { + default: + case NorthWestGravity: + case WestGravity: + case SouthWestGravity: + case StaticGravity: + case ForgetGravity: + break; + case NorthGravity: + case CenterGravity: + case SouthGravity: + *x -= (neww - oldw) / 2; + break; + case NorthEastGravity: + case EastGravity: + case SouthEastGravity: + *x -= neww - oldw; + break; + } +} - /* get the frame's position from the requested stuff */ - self->gravity = gravity; - frame_client_gravity(self->frame, x, y, w, h); - self->gravity = oldg; +void client_gravity_resize_h(ObClient *self, gint *y, gint oldh, gint newh) +{ + /* these should be the current values. this is for when you're not moving, + just resizing */ + g_assert(*y == self->area.y); + g_assert(oldh == self->area.height); - /* get the client's position in its true gravity from that */ - frame_frame_gravity(self->frame, x, y, w, h); + /* vertical */ + switch (self->gravity) { + default: + case NorthWestGravity: + case NorthGravity: + case NorthEastGravity: + case StaticGravity: + case ForgetGravity: + break; + case WestGravity: + case CenterGravity: + case EastGravity: + *y -= (newh - oldh) / 2; + break; + case SouthWestGravity: + case SouthGravity: + case SouthEastGravity: + *y -= newh - oldh; + break; + } } void client_try_configure(ObClient *self, gint *x, gint *y, gint *w, gint *h, gint *logicalw, gint *logicalh, gboolean user) { - Rect desired_area = {*x, *y, *w, *h}; + Rect desired = {*x, *y, *w, *h}; + frame_rect_to_frame(self->frame, &desired); /* make the frame recalculate its dimentions n shit without changing anything visible for real, this way the constraints below can work with the updated frame dimensions. */ - frame_adjust_area(self->frame, TRUE, TRUE, TRUE); + frame_adjust_area(self->frame, FALSE, TRUE, TRUE); + + /* gets the frame's position */ + frame_client_gravity(self->frame, x, y); + + /* these positions are frame positions, not client positions */ + + /* set the size and position if fullscreen */ + if (self->fullscreen) { + Rect *a; + guint i; + + i = screen_find_monitor(&desired); + a = screen_physical_area_monitor(i); + + *x = a->x; + *y = a->y; + *w = a->width; + *h = a->height; + + user = FALSE; /* ignore if the client can't be moved/resized when it + is fullscreening */ + + g_free(a); + } else if (self->max_horz || self->max_vert) { + Rect *a; + guint i; + + /* use all possible struts when maximizing to the full screen */ + i = screen_find_monitor(&desired); + a = screen_area(self->desktop, i, + (self->max_horz && self->max_vert ? NULL : &desired)); + + /* set the size and position if maximized */ + if (self->max_horz) { + *x = a->x; + *w = a->width - self->frame->size.left - self->frame->size.right; + } + if (self->max_vert) { + *y = a->y; + *h = a->height - self->frame->size.top - self->frame->size.bottom; + } + + user = FALSE; /* ignore if the client can't be moved/resized when it + is maximizing */ + + g_free(a); + } + + /* gets the client's position */ + frame_frame_gravity(self->frame, x, y); /* work within the prefered sizes given by the window */ if (!(*w == self->area.width && *h == self->area.height)) { gint basew, baseh, minw, minh; + gint incw, inch; + gfloat minratio, maxratio; + + incw = self->fullscreen || self->max_horz ? 1 : self->size_inc.width; + inch = self->fullscreen || self->max_vert ? 1 : self->size_inc.height; + minratio = self->fullscreen || (self->max_horz && self->max_vert) ? + 0 : self->min_ratio; + maxratio = self->fullscreen || (self->max_horz && self->max_vert) ? + 0 : self->max_ratio; /* base size is substituted with min size if not specified */ if (self->base_size.width || self->base_size.height) { @@ -2408,19 +2742,19 @@ void client_try_configure(ObClient *self, gint *x, gint *y, gint *w, gint *h, *h -= baseh; /* keep to the increments */ - *w /= self->size_inc.width; - *h /= self->size_inc.height; + *w /= incw; + *h /= inch; /* you cannot resize to nothing */ if (basew + *w < 1) *w = 1 - basew; if (baseh + *h < 1) *h = 1 - baseh; - + /* save the logical size */ - *logicalw = self->size_inc.width > 1 ? *w : *w + basew; - *logicalh = self->size_inc.height > 1 ? *h : *h + baseh; + *logicalw = incw > 1 ? *w : *w + basew; + *logicalh = inch > 1 ? *h : *h + baseh; - *w *= self->size_inc.width; - *h *= self->size_inc.height; + *w *= incw; + *h *= inch; *w += basew; *h += baseh; @@ -2428,78 +2762,32 @@ void client_try_configure(ObClient *self, gint *x, gint *y, gint *w, gint *h, /* adjust the height to match the width for the aspect ratios. for this, min size is not substituted for base size ever. */ *w -= self->base_size.width; - *h -= self->base_size.height; - - if (!self->fullscreen) { - if (self->min_ratio) - if (*h * self->min_ratio > *w) { - *h = (gint)(*w / self->min_ratio); - - /* you cannot resize to nothing */ - if (*h < 1) { - *h = 1; - *w = (gint)(*h * self->min_ratio); - } - } - if (self->max_ratio) - if (*h * self->max_ratio < *w) { - *h = (gint)(*w / self->max_ratio); - - /* you cannot resize to nothing */ - if (*h < 1) { - *h = 1; - *w = (gint)(*h * self->min_ratio); - } - } - } - - *w += self->base_size.width; - *h += self->base_size.height; - } - - /* gets the frame's position */ - frame_client_gravity(self->frame, x, y, *w, *h); - - /* these positions are frame positions, not client positions */ - - /* set the size and position if fullscreen */ - if (self->fullscreen) { - Rect *a; - guint i; - - i = screen_find_monitor(&desired_area); - a = screen_physical_area_monitor(i); - - *x = a->x; - *y = a->y; - *w = a->width; - *h = a->height; - - user = FALSE; /* ignore if the client can't be moved/resized when it - is entering fullscreen */ - } else if (self->max_horz || self->max_vert) { - Rect *a; - guint i; - - i = screen_find_monitor(&desired_area); - a = screen_area_monitor(self->desktop, i); - - /* set the size and position if maximized */ - if (self->max_horz) { - *x = a->x; - *w = a->width - self->frame->size.left - self->frame->size.right; - } - if (self->max_vert) { - *y = a->y; - *h = a->height - self->frame->size.top - self->frame->size.bottom; - } + *h -= self->base_size.height; - /* maximizing is not allowed if the user can't move+resize the window - */ - } + if (minratio) + if (*h * minratio > *w) { + *h = (gint)(*w / minratio); - /* gets the client's position */ - frame_frame_gravity(self->frame, x, y, *w, *h); + /* you cannot resize to nothing */ + if (*h < 1) { + *h = 1; + *w = (gint)(*h * minratio); + } + } + if (maxratio) + if (*h * maxratio < *w) { + *h = (gint)(*w / maxratio); + + /* you cannot resize to nothing */ + if (*h < 1) { + *h = 1; + *w = (gint)(*h * minratio); + } + } + + *w += self->base_size.width; + *h += self->base_size.height; + } /* these override the above states! if you cant move you can't move! */ if (user) { @@ -2518,15 +2806,16 @@ void client_try_configure(ObClient *self, gint *x, gint *y, gint *w, gint *h, } -void client_configure_full(ObClient *self, gint x, gint y, gint w, gint h, - gboolean user, gboolean final, - gboolean force_reply) +void client_configure(ObClient *self, gint x, gint y, gint w, gint h, + gboolean user, gboolean final, gboolean force_reply) { - gint oldw, oldh, oldrx, oldry; + gint oldw, oldh; gboolean send_resize_client; gboolean moved = FALSE, resized = FALSE, rootmoved = FALSE; + gboolean fmoved, fresized; guint fdecor = self->frame->decorations; gboolean fhorz = self->frame->max_horz; + gboolean fvert = self->frame->max_vert; gint logicalw, logicalh; /* find the new x, y, width, and height (and logical size) */ @@ -2537,8 +2826,8 @@ void client_configure_full(ObClient *self, gint x, gint y, gint w, gint h, SIZE_SET(self->logical_size, logicalw, logicalh); /* figure out if we moved or resized or what */ - moved = x != self->area.x || y != self->area.y; - resized = w != self->area.width || h != self->area.height; + moved = (x != self->area.x || y != self->area.y); + resized = (w != self->area.width || h != self->area.height); oldw = self->area.width; oldh = self->area.height; @@ -2552,61 +2841,97 @@ void client_configure_full(ObClient *self, gint x, gint y, gint w, gint h, (resized && config_resize_redraw)))); /* if the client is enlarging, then resize the client before the frame */ - if (send_resize_client && user && (w > oldw || h > oldh)) { - XResizeWindow(ob_display, self->window, MAX(w, oldw), MAX(h, oldh)); - /* resize the plate to show the client padding color underneath */ + if (send_resize_client && (w > oldw || h > oldh)) { + XMoveResizeWindow(ob_display, self->window, + self->frame->size.left, self->frame->size.top, + MAX(w, oldw), MAX(h, oldh)); frame_adjust_client_area(self->frame); } /* find the frame's dimensions and move/resize it */ - if (self->decorations != fdecor || self->max_horz != fhorz) - moved = resized = TRUE; - if (moved || resized) - frame_adjust_area(self->frame, moved, resized, FALSE); - - /* find the client's position relative to the root window */ - oldrx = self->root_pos.x; - oldry = self->root_pos.y; - rootmoved = (oldrx != (signed)(self->frame->area.x + - self->frame->size.left - - self->border_width) || - oldry != (signed)(self->frame->area.y + - self->frame->size.top - - self->border_width)); - - if (force_reply || ((!user || (user && final)) && rootmoved)) + fmoved = moved; + fresized = resized; + + /* if decorations changed, then readjust everything for the frame */ + if (self->decorations != fdecor || + self->max_horz != fhorz || self->max_vert != fvert) { - XEvent event; + fmoved = fresized = TRUE; + } + + /* adjust the frame */ + if (fmoved || fresized) { + gulong ignore_start; + if (!user) + ignore_start = event_start_ignore_all_enters(); + + frame_adjust_area(self->frame, fmoved, fresized, FALSE); + if (!user) + event_end_ignore_all_enters(ignore_start); + } + + if (!user || final) { + gint oldrx = self->root_pos.x; + gint oldry = self->root_pos.y; + /* we have reset the client to 0 border width, so don't include + it in these coords */ POINT_SET(self->root_pos, self->frame->area.x + self->frame->size.left - self->border_width, self->frame->area.y + self->frame->size.top - self->border_width); + if (self->root_pos.x != oldrx || self->root_pos.y != oldry) + rootmoved = TRUE; + } + + /* This is kinda tricky and should not be changed.. let me explain! + + When user = FALSE, then the request is coming from the application + itself, and we are more strict about when to send a synthetic + ConfigureNotify. We strictly follow the rules of the ICCCM sec 4.1.5 + in this case (if force_reply is true) + + When user = TRUE, then the request is coming from "us", like when we + maximize a window or something. In this case we are more lenient. We + used to follow the same rules as above, but _Java_ Swing can't handle + this. So just to appease Swing, when user = TRUE, we always send + a synthetic ConfigureNotify to give the window its root coordinates. + */ + if ((!user && !resized && (rootmoved || force_reply)) || + (user && final && rootmoved)) + { + XEvent event; event.type = ConfigureNotify; event.xconfigure.display = ob_display; event.xconfigure.event = self->window; event.xconfigure.window = self->window; + ob_debug("Sending ConfigureNotify to %s for %d,%d %dx%d\n", + self->title, self->root_pos.x, self->root_pos.y, w, h); + /* root window real coords */ event.xconfigure.x = self->root_pos.x; event.xconfigure.y = self->root_pos.y; event.xconfigure.width = w; event.xconfigure.height = h; - event.xconfigure.border_width = 0; - event.xconfigure.above = self->frame->plate; + event.xconfigure.border_width = self->border_width; + event.xconfigure.above = None; event.xconfigure.override_redirect = FALSE; XSendEvent(event.xconfigure.display, event.xconfigure.window, FALSE, StructureNotifyMask, &event); } - /* if the client is shrinking, then resize the frame before the client */ - if (send_resize_client && (!user || (w <= oldw || h <= oldh))) { - /* resize the plate to show the client padding color underneath */ - frame_adjust_client_area(self->frame); + /* if the client is shrinking, then resize the frame before the client. - XResizeWindow(ob_display, self->window, w, h); + both of these resize sections may run, because the top one only resizes + in the direction that is growing + */ + if (send_resize_client && (w <= oldw || h <= oldh)) { + frame_adjust_client_area(self->frame); + XMoveResizeWindow(ob_display, self->window, + self->frame->size.left, self->frame->size.top, w, h); } XFlush(ob_display); @@ -2621,7 +2946,6 @@ void client_fullscreen(ObClient *self, gboolean fs) self->fullscreen = fs; client_change_state(self); /* change the state hints on the client */ - client_calc_layer(self); /* and adjust out layer/stacking */ if (fs) { self->pre_fullscreen_area = self->area; @@ -2636,40 +2960,43 @@ void client_fullscreen(ObClient *self, gboolean fs) self->pre_fullscreen_area.height = self->pre_max_area.height; } - /* these are not actually used cuz client_configure will set them - as appropriate when the window is fullscreened */ - x = y = w = h = 0; + /* these will help configure_full figure out where to fullscreen + the window */ + x = self->area.x; + y = self->area.y; + w = self->area.width; + h = self->area.height; } else { - Rect *a; - - if (self->pre_fullscreen_area.width > 0 && - self->pre_fullscreen_area.height > 0) - { - x = self->pre_fullscreen_area.x; - y = self->pre_fullscreen_area.y; - w = self->pre_fullscreen_area.width; - h = self->pre_fullscreen_area.height; - RECT_SET(self->pre_fullscreen_area, 0, 0, 0, 0); - } else { - /* pick some fallbacks... */ - a = screen_area_monitor(self->desktop, 0); - x = a->x + a->width / 4; - y = a->y + a->height / 4; - w = a->width / 2; - h = a->height / 2; - } + g_assert(self->pre_fullscreen_area.width > 0 && + self->pre_fullscreen_area.height > 0); + + x = self->pre_fullscreen_area.x; + y = self->pre_fullscreen_area.y; + w = self->pre_fullscreen_area.width; + h = self->pre_fullscreen_area.height; + RECT_SET(self->pre_fullscreen_area, 0, 0, 0, 0); } - client_setup_decor_and_functions(self); + ob_debug("Window %s going fullscreen (%d)\n", + self->title, self->fullscreen); + client_setup_decor_and_functions(self, FALSE); client_move_resize(self, x, y, w, h); - /* try focus us when we go into fullscreen mode */ - client_focus(self); + /* and adjust our layer/stacking. do this after resizing the window, + and applying decorations, because windows which fill the screen are + considered "fullscreen" and it affects their layer */ + client_calc_layer(self); + + if (fs) { + /* try focus us when we go into fullscreen mode */ + client_focus(self); + } } static void client_iconify_recursive(ObClient *self, - gboolean iconic, gboolean curdesk) + gboolean iconic, gboolean curdesk, + gboolean hide_animation) { GSList *it; gboolean changed = FALSE; @@ -2680,13 +3007,14 @@ static void client_iconify_recursive(ObClient *self, self->window); if (iconic) { - if (self->functions & OB_CLIENT_FUNC_ICONIFY) { + /* don't let non-normal windows iconify along with their parents + or whatever */ + if (client_normal(self)) { self->iconic = iconic; /* update the focus lists.. iconic windows go to the bottom of - the list, put the new iconic window at the 'top of the - bottom'. */ - focus_order_to_top(self); + the list */ + focus_order_to_bottom(self); changed = TRUE; } @@ -2695,7 +3023,7 @@ static void client_iconify_recursive(ObClient *self, if (curdesk && self->desktop != screen_desktop && self->desktop != DESKTOP_ALL) - client_set_desktop(self, screen_desktop, FALSE); + client_set_desktop(self, screen_desktop, FALSE, FALSE); /* this puts it after the current focused window */ focus_order_remove(self); @@ -2707,9 +3035,10 @@ static void client_iconify_recursive(ObClient *self, if (changed) { client_change_state(self); + if (config_animate_iconify && !hide_animation) + frame_begin_iconify_animation(self->frame, iconic); + /* do this after starting the animation so it doesn't flash */ client_showhide(self); - if (STRUT_EXISTS(self->strut)) - screen_update_areas(); } /* iconify all direct transients, and deiconify all transients @@ -2717,20 +3046,24 @@ static void client_iconify_recursive(ObClient *self, for (it = self->transients; it; it = g_slist_next(it)) if (it->data != self) if (client_is_direct_child(self, it->data) || !iconic) - client_iconify_recursive(it->data, iconic, curdesk); + client_iconify_recursive(it->data, iconic, curdesk, + hide_animation); } -void client_iconify(ObClient *self, gboolean iconic, gboolean curdesk) +void client_iconify(ObClient *self, gboolean iconic, gboolean curdesk, + gboolean hide_animation) { - /* move up the transient chain as far as possible first */ - self = client_search_top_parent(self); - client_iconify_recursive(self, iconic, curdesk); + if (self->functions & OB_CLIENT_FUNC_ICONIFY || !iconic) { + /* move up the transient chain as far as possible first */ + self = client_search_top_direct_parent(self); + client_iconify_recursive(self, iconic, curdesk, hide_animation); + } } void client_maximize(ObClient *self, gboolean max, gint dir) { gint x, y, w, h; - + g_assert(dir == 0 || dir == 1 || dir == 2); if (!(self->functions & OB_CLIENT_FUNC_MAXIMIZE)) return; /* can't */ @@ -2745,8 +3078,8 @@ void client_maximize(ObClient *self, gboolean max, gint dir) if (dir == 2 && !self->max_vert) return; } - /* we just tell it to configure in the same place and client_configure - worries about filling the screen with the window */ + /* these will help configure_full figure out which screen to fill with + the window */ x = self->area.x; y = self->area.y; w = self->area.width; @@ -2764,34 +3097,23 @@ void client_maximize(ObClient *self, gboolean max, gint dir) self->pre_max_area.width, self->area.height); } } else { - Rect *a; - - a = screen_area_monitor(self->desktop, 0); if ((dir == 0 || dir == 1) && self->max_horz) { /* horz */ - if (self->pre_max_area.width > 0) { - x = self->pre_max_area.x; - w = self->pre_max_area.width; + g_assert(self->pre_max_area.width > 0); - RECT_SET(self->pre_max_area, 0, self->pre_max_area.y, - 0, self->pre_max_area.height); - } else { - /* pick some fallbacks... */ - x = a->x + a->width / 4; - w = a->width / 2; - } + x = self->pre_max_area.x; + w = self->pre_max_area.width; + + RECT_SET(self->pre_max_area, 0, self->pre_max_area.y, + 0, self->pre_max_area.height); } if ((dir == 0 || dir == 2) && self->max_vert) { /* vert */ - if (self->pre_max_area.height > 0) { - y = self->pre_max_area.y; - h = self->pre_max_area.height; + g_assert(self->pre_max_area.height > 0); - RECT_SET(self->pre_max_area, self->pre_max_area.x, 0, - self->pre_max_area.width, 0); - } else { - /* pick some fallbacks... */ - y = a->y + a->height / 4; - h = a->height / 2; - } + y = self->pre_max_area.y; + h = self->pre_max_area.height; + + RECT_SET(self->pre_max_area, self->pre_max_area.x, 0, + self->pre_max_area.width, 0); } } @@ -2802,8 +3124,7 @@ void client_maximize(ObClient *self, gboolean max, gint dir) client_change_state(self); /* change the state hints on the client */ - client_setup_decor_and_functions(self); - + client_setup_decor_and_functions(self, FALSE); client_move_resize(self, x, y, w, h); } @@ -2817,7 +3138,7 @@ void client_shade(ObClient *self, gboolean shade) client_change_state(self); client_change_wm_state(self); /* the window is being hidden/shown */ /* resize the frame to just the titlebar */ - frame_adjust_area(self->frame, FALSE, FALSE, FALSE); + frame_adjust_area(self->frame, FALSE, TRUE, FALSE); } void client_close(ObClient *self) @@ -2830,7 +3151,7 @@ void client_close(ObClient *self) close, we just kill it */ if (!self->delete_window) client_kill(self); - + /* XXX: itd be cool to do timeouts and shit here for killing the client's process off @@ -2874,20 +3195,19 @@ void client_hilite(ObClient *self, gboolean hilite) } void client_set_desktop_recursive(ObClient *self, - guint target, gboolean donthide) + guint target, + gboolean donthide, + gboolean dontraise) { guint old; GSList *it; - if (target != self->desktop) { + if (target != self->desktop && self->type != OB_CLIENT_TYPE_DESKTOP) { ob_debug("Setting desktop %u\n", target+1); g_assert(target < screen_num_desktops || target == DESKTOP_ALL); - /* remove from the old desktop(s) */ - focus_order_remove(self); - old = self->desktop; self->desktop = target; PROP_SET32(self->window, net_wm_desktop, cardinal, target); @@ -2895,38 +3215,37 @@ void client_set_desktop_recursive(ObClient *self, frame_adjust_state(self->frame); /* 'move' the window to the new desktop */ if (!donthide) - client_showhide(self); + client_hide(self); + client_show(self); /* raise if it was not already on the desktop */ - if (old != DESKTOP_ALL) - client_raise(self); + if (old != DESKTOP_ALL && !dontraise) + stacking_raise(CLIENT_AS_WINDOW(self)); if (STRUT_EXISTS(self->strut)) screen_update_areas(); - - /* add to the new desktop(s) */ - if (config_focus_new) - focus_order_to_top(self); else - focus_order_to_bottom(self); + /* the new desktop's geometry may be different, so we may need to + resize, for example if we are maximized */ + client_reconfigure(self, FALSE); } /* move all transients */ for (it = self->transients; it; it = g_slist_next(it)) if (it->data != self) if (client_is_direct_child(self, it->data)) - client_set_desktop_recursive(it->data, target, donthide); + client_set_desktop_recursive(it->data, target, + donthide, dontraise); } -void client_set_desktop(ObClient *self, guint target, gboolean donthide) +void client_set_desktop(ObClient *self, guint target, + gboolean donthide, gboolean dontraise) { - self = client_search_top_parent(self); - client_set_desktop_recursive(self, target, donthide); + self = client_search_top_direct_parent(self); + client_set_desktop_recursive(self, target, donthide, dontraise); } gboolean client_is_direct_child(ObClient *parent, ObClient *child) { - while (child != parent && - child->transient_for && child->transient_for != OB_TRAN_GROUP) - child = child->transient_for; + while (child != parent && (child = client_direct_parent(child))); return child == parent; } @@ -2934,7 +3253,7 @@ ObClient *client_search_modal_child(ObClient *self) { GSList *it; ObClient *ret; - + for (it = self->transients; it; it = g_slist_next(it)) { ObClient *c = it->data; if ((ret = client_search_modal_child(c))) return ret; @@ -2945,7 +3264,7 @@ ObClient *client_search_modal_child(ObClient *self) gboolean client_validate(ObClient *self) { - XEvent e; + XEvent e; XSync(ob_display, FALSE); /* get all events on the server */ @@ -2961,13 +3280,13 @@ gboolean client_validate(ObClient *self) void client_set_wm_state(ObClient *self, glong state) { if (state == self->wmstate) return; /* no change */ - + switch (state) { case IconicState: - client_iconify(self, TRUE, TRUE); + client_iconify(self, TRUE, TRUE, FALSE); break; case NormalState: - client_iconify(self, FALSE, TRUE); + client_iconify(self, FALSE, TRUE, FALSE); break; } } @@ -2982,17 +3301,19 @@ void client_set_state(ObClient *self, Atom action, glong data1, glong data2) gboolean modal = self->modal; gboolean iconic = self->iconic; gboolean demands_attention = self->demands_attention; + gboolean above = self->above; + gboolean below = self->below; gint i; if (!(action == prop_atoms.net_wm_state_add || action == prop_atoms.net_wm_state_remove || action == prop_atoms.net_wm_state_toggle)) /* an invalid action was passed to the client message, ignore it */ - return; + return; for (i = 0; i < 2; ++i) { Atom state = i == 0 ? data1 : data2; - + if (!state) continue; /* if toggling, then pick whether we're adding or removing */ @@ -3039,7 +3360,7 @@ void client_set_state(ObClient *self, Atom action, glong data1, glong data2) action = undecorated ? prop_atoms.net_wm_state_remove : prop_atoms.net_wm_state_add; } - + if (action == prop_atoms.net_wm_state_add) { if (state == prop_atoms.net_wm_state_modal) { modal = TRUE; @@ -3058,11 +3379,11 @@ void client_set_state(ObClient *self, Atom action, glong data1, glong data2) } else if (state == prop_atoms.net_wm_state_fullscreen) { fullscreen = TRUE; } else if (state == prop_atoms.net_wm_state_above) { - self->above = TRUE; - self->below = FALSE; + above = TRUE; + below = FALSE; } else if (state == prop_atoms.net_wm_state_below) { - self->above = FALSE; - self->below = TRUE; + above = FALSE; + below = TRUE; } else if (state == prop_atoms.net_wm_state_demands_attention) { demands_attention = TRUE; } else if (state == prop_atoms.ob_wm_state_undecorated) { @@ -3087,9 +3408,9 @@ void client_set_state(ObClient *self, Atom action, glong data1, glong data2) } else if (state == prop_atoms.net_wm_state_fullscreen) { fullscreen = FALSE; } else if (state == prop_atoms.net_wm_state_above) { - self->above = FALSE; + above = FALSE; } else if (state == prop_atoms.net_wm_state_below) { - self->below = FALSE; + below = FALSE; } else if (state == prop_atoms.net_wm_state_demands_attention) { demands_attention = FALSE; } else if (state == prop_atoms.ob_wm_state_undecorated) { @@ -3097,6 +3418,7 @@ void client_set_state(ObClient *self, Atom action, glong data1, glong data2) } } } + if (max_horz != self->max_horz || max_vert != self->max_vert) { if (max_horz != self->max_horz && max_vert != self->max_vert) { /* toggling both */ @@ -3122,14 +3444,26 @@ void client_set_state(ObClient *self, Atom action, glong data1, glong data2) client_shade(self, shaded); if (undecorated != self->undecorated) client_set_undecorated(self, undecorated); + if (above != self->above || below != self->below) { + self->above = above; + self->below = below; + client_calc_layer(self); + } + if (modal != self->modal) { self->modal = modal; /* when a window changes modality, then its stacking order with its transients needs to change */ - client_raise(self); + stacking_raise(CLIENT_AS_WINDOW(self)); + + /* it also may get focused. if something is focused that shouldn't + be focused anymore, then move the focus */ + if (focus_client && client_focus_target(focus_client) != focus_client) + client_focus(focus_client); } + if (iconic != self->iconic) - client_iconify(self, iconic, FALSE); + client_iconify(self, iconic, FALSE, FALSE); if (demands_attention != self->demands_attention) client_hilite(self, demands_attention); @@ -3148,8 +3482,6 @@ ObClient *client_focus_target(ObClient *self) gboolean client_can_focus(ObClient *self) { - XEvent ev; - /* choose the correct target */ self = client_focus_target(self); @@ -3159,51 +3491,49 @@ gboolean client_can_focus(ObClient *self) if (!(self->can_focus || self->focus_notify)) return FALSE; - /* do a check to see if the window has already been unmapped or destroyed - do this intelligently while watching out for unmaps we've generated - (ignore_unmaps > 0) */ - if (XCheckTypedWindowEvent(ob_display, self->window, - DestroyNotify, &ev)) { - XPutBackEvent(ob_display, &ev); - return FALSE; - } - while (XCheckTypedWindowEvent(ob_display, self->window, - UnmapNotify, &ev)) { - if (self->ignore_unmaps) { - self->ignore_unmaps--; - } else { - XPutBackEvent(ob_display, &ev); - return FALSE; - } - } - return TRUE; } gboolean client_focus(ObClient *self) { + /* we might not focus this window, so if we have modal children which would + be focused instead, bring them to this desktop */ + client_bring_modal_windows(self); + /* choose the correct target */ self = client_focus_target(self); if (!client_can_focus(self)) { - if (!self->frame->visible) { - /* update the focus lists */ - focus_order_to_top(self); - } + ob_debug_type(OB_DEBUG_FOCUS, + "Client %s can't be focused\n", self->title); return FALSE; } ob_debug_type(OB_DEBUG_FOCUS, - "Focusing client \"%s\" at time %u\n", - self->title, event_curtime); + "Focusing client \"%s\" (0x%x) at time %u\n", + self->title, self->window, event_curtime); + + /* if using focus_delay, stop the timer now so that focus doesn't + go moving on us */ + event_halt_focus_delay(); + + /* if there is a grab going on, then we need to cancel it. if we move + focus during the grab, applications will get NotifyWhileGrabbed events + and ignore them ! + + actions should not rely on being able to move focus during an + interactive grab. + */ + event_cancel_all_key_grabs(); + + xerror_set_ignore(TRUE); + xerror_occured = FALSE; if (self->can_focus) { /* This can cause a BadMatch error with CurrentTime, or if an app passed in a bad time for _NET_WM_ACTIVE_WINDOW. */ - xerror_set_ignore(TRUE); XSetInputFocus(ob_display, self->window, RevertToPointerRoot, event_curtime); - xerror_set_ignore(FALSE); } if (self->focus_notify) { @@ -3221,82 +3551,76 @@ gboolean client_focus(ObClient *self) XSendEvent(ob_display, self->window, FALSE, NoEventMask, &ce); } -#ifdef DEBUG_FOCUS - ob_debug("%sively focusing %lx at %d\n", - (self->can_focus ? "act" : "pass"), - self->window, (gint) event_curtime); -#endif + xerror_set_ignore(FALSE); - /* Cause the FocusIn to come back to us. Important for desktop switches, - since otherwise we'll have no FocusIn on the queue and send it off to - the focus_backup. */ - XSync(ob_display, FALSE); - return TRUE; + ob_debug_type(OB_DEBUG_FOCUS, "Error focusing? %d\n", xerror_occured); + return !xerror_occured; } -void client_activate(ObClient *self, gboolean here, gboolean user) +static void client_present(ObClient *self, gboolean here, gboolean raise, + gboolean unshade) { - guint32 last_time = focus_client ? focus_client->user_time : CurrentTime; + if (client_normal(self) && screen_showing_desktop) + screen_show_desktop(FALSE, self); + if (self->iconic) + client_iconify(self, FALSE, here, FALSE); + if (self->desktop != DESKTOP_ALL && + self->desktop != screen_desktop) + { + if (here) + client_set_desktop(self, screen_desktop, FALSE, TRUE); + else + screen_set_desktop(self->desktop, FALSE); + } else if (!self->frame->visible) + /* if its not visible for other reasons, then don't mess + with it */ + return; + if (self->shaded && unshade) + client_shade(self, FALSE); + if (raise) + stacking_raise(CLIENT_AS_WINDOW(self)); - /* XXX do some stuff here if user is false to determine if we really want - to activate it or not (a parent or group member is currently - active)? - */ - ob_debug_type(OB_DEBUG_FOCUS, - "Want to activate window 0x%x with time %u (last time %u), " - "source=%s\n", - self->window, event_curtime, last_time, - (user ? "user" : "application")); + client_focus(self); +} - if (!user && event_curtime && last_time && - !event_time_after(event_curtime, last_time)) - { - client_hilite(self, TRUE); - } else { - if (event_curtime != CurrentTime) - self->user_time = event_curtime; - - /* if using focus_delay, stop the timer now so that focus doesn't - go moving on us */ - event_halt_focus_delay(); - - if (client_normal(self) && screen_showing_desktop) - screen_show_desktop(FALSE); - if (self->iconic) - client_iconify(self, FALSE, here); - if (self->desktop != DESKTOP_ALL && - self->desktop != screen_desktop) - { - if (here) - client_set_desktop(self, screen_desktop, FALSE); - else - screen_set_desktop(self->desktop); - } else if (!self->frame->visible) - /* if its not visible for other reasons, then don't mess - with it */ - return; - if (self->shaded) - client_shade(self, FALSE); +void client_activate(ObClient *self, gboolean here, gboolean raise, + gboolean unshade, gboolean user) +{ + client_present(self, here, raise, unshade); +} - client_focus(self); +static void client_bring_windows_recursive(ObClient *self, + guint desktop, + gboolean helpers, + gboolean modals, + gboolean iconic) +{ + GSList *it; + + for (it = self->transients; it; it = g_slist_next(it)) + client_bring_windows_recursive(it->data, desktop, + helpers, modals, iconic); - /* we do this as an action here. this is rather important. this is - because we want the results from the focus change to take place - BEFORE we go about raising the window. when a fullscreen window - loses focus, we need this or else the raise wont be able to raise - above the to-lose-focus fullscreen window. */ - client_raise(self); + if (((helpers && client_helper(self)) || + (modals && self->modal)) && + ((self->desktop != desktop && self->desktop != DESKTOP_ALL) || + (iconic && self->iconic))) + { + if (iconic && self->iconic) + client_iconify(self, FALSE, TRUE, FALSE); + else + client_set_desktop(self, desktop, FALSE, FALSE); } } -void client_raise(ObClient *self) +void client_bring_helper_windows(ObClient *self) { - action_run_string("Raise", self, CurrentTime); + client_bring_windows_recursive(self, self->desktop, TRUE, FALSE, FALSE); } -void client_lower(ObClient *self) +void client_bring_modal_windows(ObClient *self) { - action_run_string("Lower", self, CurrentTime); + client_bring_windows_recursive(self, self->desktop, FALSE, TRUE, TRUE); } gboolean client_focused(ObClient *self) @@ -3307,45 +3631,38 @@ gboolean client_focused(ObClient *self) static ObClientIcon* client_icon_recursive(ObClient *self, gint w, gint h) { guint i; - /* si is the smallest image >= req */ - /* li is the largest image < req */ - gulong size, smallest = 0xffffffff, largest = 0, si = 0, li = 0; + gulong min_diff, min_i; if (!self->nicons) { ObClientIcon *parent = NULL; + GSList *it; - if (self->transient_for) { - if (self->transient_for != OB_TRAN_GROUP) - parent = client_icon_recursive(self->transient_for, w, h); - else { - GSList *it; - for (it = self->group->members; it; it = g_slist_next(it)) { - ObClient *c = it->data; - if (c != self && !c->transient_for) { - if ((parent = client_icon_recursive(c, w, h))) - break; - } - } - } + for (it = self->parents; it; it = g_slist_next(it)) { + ObClient *c = it->data; + if ((parent = client_icon_recursive(c, w, h))) + break; } - + return parent; } - for (i = 0; i < self->nicons; ++i) { - size = self->icons[i].width * self->icons[i].height; - if (size < smallest && size >= (unsigned)(w * h)) { - smallest = size; - si = i; - } - if (size > largest && size <= (unsigned)(w * h)) { - largest = size; - li = i; + /* some kind of crappy approximation to find the icon closest in size to + what we requested, but icons are generally all the same ratio as + eachother so it's good enough. */ + + min_diff = ABS(self->icons[0].width - w) + ABS(self->icons[0].height - h); + min_i = 0; + + for (i = 1; i < self->nicons; ++i) { + gulong diff; + + diff = ABS(self->icons[i].width - w) + ABS(self->icons[i].height - h); + if (diff < min_diff) { + min_diff = diff; + min_i = i; } } - if (largest == 0) /* didnt find one smaller than the requested size */ - return &self->icons[si]; - return &self->icons[li]; + return &self->icons[min_i]; } const ObClientIcon* client_icon(ObClient *self, gint w, gint h) @@ -3378,16 +3695,13 @@ void client_set_layer(ObClient *self, gint layer) void client_set_undecorated(ObClient *self, gboolean undecorated) { - if (self->undecorated != undecorated) { + if (self->undecorated != undecorated && + /* don't let it undecorate if the function is missing, but let + it redecorate */ + (self->functions & OB_CLIENT_FUNC_UNDECORATE || !undecorated)) + { self->undecorated = undecorated; - client_setup_decor_and_functions(self); - /* Make sure the client knows it might have moved. Maybe there is a - * better way of doing this so only one client_configure is sent, but - * since 125 of these are sent per second when moving the window (with - * user = FALSE) i doubt it matters much. - */ - client_configure(self, self->area.x, self->area.y, - self->area.width, self->area.height, TRUE, TRUE); + client_setup_decor_and_functions(self, TRUE); client_change_state(self); /* reflect this in the state hints */ } } @@ -3397,85 +3711,66 @@ guint client_monitor(ObClient *self) return screen_find_monitor(&self->frame->area); } -ObClient *client_search_top_parent(ObClient *self) +ObClient *client_direct_parent(ObClient *self) +{ + if (!self->parents) return NULL; + if (self->transient_for_group) return NULL; + return self->parents->data; +} + +ObClient *client_search_top_direct_parent(ObClient *self) { - while (self->transient_for && self->transient_for != OB_TRAN_GROUP && - client_normal(self)) - self = self->transient_for; + ObClient *p; + while ((p = client_direct_parent(self))) self = p; return self; } -GSList *client_search_all_top_parents(ObClient *self) +static GSList *client_search_all_top_parents_internal(ObClient *self, + gboolean bylayer, + ObStackingLayer layer) { - GSList *ret = NULL; + GSList *ret; + ObClient *p; /* move up the direct transient chain as far as possible */ - while (self->transient_for && self->transient_for != OB_TRAN_GROUP) - self = self->transient_for; - - if (!self->transient_for) - ret = g_slist_prepend(ret, self); - else { - GSList *it; - - g_assert(self->group); + while ((p = client_direct_parent(self)) && + (!bylayer || p->layer == layer)) + self = p; - for (it = self->group->members; it; it = g_slist_next(it)) { - ObClient *c = it->data; + if (!self->parents) + ret = g_slist_prepend(NULL, self); + else + ret = g_slist_copy(self->parents); - if (!c->transient_for && client_normal(c)) - ret = g_slist_prepend(ret, c); - } + return ret; +} - if (ret == NULL) /* no group parents */ - ret = g_slist_prepend(ret, self); - } +GSList *client_search_all_top_parents(ObClient *self) +{ + return client_search_all_top_parents_internal(self, FALSE, 0); +} - return ret; +GSList *client_search_all_top_parents_layer(ObClient *self) +{ + return client_search_all_top_parents_internal(self, TRUE, self->layer); } ObClient *client_search_focus_parent(ObClient *self) { - if (self->transient_for) { - if (self->transient_for != OB_TRAN_GROUP) { - if (client_focused(self->transient_for)) - return self->transient_for; - } else { - GSList *it; - - for (it = self->group->members; it; it = g_slist_next(it)) { - ObClient *c = it->data; + GSList *it; - /* checking transient_for prevents infinate loops! */ - if (c != self && !c->transient_for) - if (client_focused(c)) - return c; - } - } - } + for (it = self->parents; it; it = g_slist_next(it)) + if (client_focused(it->data)) return it->data; return NULL; } ObClient *client_search_parent(ObClient *self, ObClient *search) { - if (self->transient_for) { - if (self->transient_for != OB_TRAN_GROUP) { - if (self->transient_for == search) - return search; - } else { - GSList *it; - - for (it = self->group->members; it; it = g_slist_next(it)) { - ObClient *c = it->data; + GSList *it; - /* checking transient_for prevents infinate loops! */ - if (c != self && !c->transient_for) - if (c == search) - return search; - } - } - } + for (it = self->parents; it; it = g_slist_next(it)) + if (it->data == search) return search; return NULL; } @@ -3493,199 +3788,311 @@ ObClient *client_search_transient(ObClient *self, ObClient *search) return NULL; } -void client_update_sm_client_id(ObClient *self) +static void detect_edge(Rect area, ObDirection dir, + gint my_head, gint my_size, + gint my_edge_start, gint my_edge_size, + gint *dest, gboolean *near_edge) { - g_free(self->sm_client_id); - self->sm_client_id = NULL; - - if (!PROP_GETS(self->window, sm_client_id, locale, &self->sm_client_id) && - self->group) - PROP_GETS(self->group->leader, sm_client_id, locale, - &self->sm_client_id); -} - -#define WANT_EDGE(cur, c) \ - if(cur == c) \ - continue; \ - if(!client_normal(cur)) \ - continue; \ - if(screen_desktop != cur->desktop && cur->desktop != DESKTOP_ALL) \ - continue; \ - if(cur->iconic) \ - continue; \ - if(cur->layer < c->layer && !config_resist_layers_below) \ - continue; + gint edge_start, edge_size, head, tail; + gboolean skip_head = FALSE, skip_tail = FALSE; -#define HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end) \ - if ((his_edge_start >= my_edge_start && \ - his_edge_start <= my_edge_end) || \ - (my_edge_start >= his_edge_start && \ - my_edge_start <= his_edge_end)) \ - dest = his_offset; - -/* finds the nearest edge in the given direction from the current client - * note to self: the edge is the -frame- edge (the actual one), not the - * client edge. - */ -gint client_directional_edge_search(ObClient *c, ObDirection dir, gboolean hang) -{ - gint dest, monitor_dest; - gint my_edge_start, my_edge_end, my_offset; - GList *it; - Rect *a, *monitor; - - if(!client_list) - return -1; + switch(dir) { + case OB_DIRECTION_NORTH: + case OB_DIRECTION_SOUTH: + edge_start = area.x; + edge_size = area.width; + break; + case OB_DIRECTION_EAST: + case OB_DIRECTION_WEST: + edge_start = area.y; + edge_size = area.height; + break; + default: + g_assert_not_reached(); + } - a = screen_area(c->desktop); - monitor = screen_area_monitor(c->desktop, client_monitor(c)); + /* do we collide with this window? */ + if (!RANGES_INTERSECT(my_edge_start, my_edge_size, + edge_start, edge_size)) + return; switch(dir) { - case OB_DIRECTION_NORTH: - my_edge_start = c->frame->area.x; - my_edge_end = c->frame->area.x + c->frame->area.width; - my_offset = c->frame->area.y + (hang ? c->frame->area.height : 0); - - /* default: top of screen */ - dest = a->y + (hang ? c->frame->area.height : 0); - monitor_dest = monitor->y + (hang ? c->frame->area.height : 0); - /* if the monitor edge comes before the screen edge, */ - /* use that as the destination instead. (For xinerama) */ - if (monitor_dest != dest && my_offset > monitor_dest) - dest = monitor_dest; - - for(it = client_list; it && my_offset != dest; it = g_list_next(it)) { - gint his_edge_start, his_edge_end, his_offset; - ObClient *cur = it->data; - - WANT_EDGE(cur, c) - - his_edge_start = cur->frame->area.x; - his_edge_end = cur->frame->area.x + cur->frame->area.width; - his_offset = cur->frame->area.y + - (hang ? 0 : cur->frame->area.height); - - if(his_offset + 1 > my_offset) - continue; + case OB_DIRECTION_NORTH: + head = RECT_BOTTOM(area); + tail = RECT_TOP(area); + break; + case OB_DIRECTION_SOUTH: + head = RECT_TOP(area); + tail = RECT_BOTTOM(area); + break; + case OB_DIRECTION_EAST: + head = RECT_LEFT(area); + tail = RECT_RIGHT(area); + break; + case OB_DIRECTION_WEST: + head = RECT_RIGHT(area); + tail = RECT_LEFT(area); + break; + default: + g_assert_not_reached(); + } + switch(dir) { + case OB_DIRECTION_NORTH: + case OB_DIRECTION_WEST: + if (my_head <= head + 1) + skip_head = TRUE; + if (my_head + my_size - 1 <= tail) + skip_tail = TRUE; + if (head < *dest) + skip_head = TRUE; + if (tail - my_size < *dest) + skip_tail = TRUE; + break; + case OB_DIRECTION_SOUTH: + case OB_DIRECTION_EAST: + if (my_head >= head - 1) + skip_head = TRUE; + if (my_head - my_size + 1 >= tail) + skip_tail = TRUE; + if (head > *dest) + skip_head = TRUE; + if (tail + my_size > *dest) + skip_tail = TRUE; + break; + default: + g_assert_not_reached(); + } - if(his_offset < dest) - continue; + ob_debug("my head %d size %d\n", my_head, my_size); + ob_debug("head %d tail %d deest %d\n", head, tail, *dest); + if (!skip_head) { + ob_debug("using near edge %d\n", head); + *dest = head; + *near_edge = TRUE; + } + else if (!skip_tail) { + ob_debug("using far edge %d\n", tail); + *dest = tail; + *near_edge = FALSE; + } - HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end) - } +} + +void client_find_edge_directional(ObClient *self, ObDirection dir, + gint my_head, gint my_size, + gint my_edge_start, gint my_edge_size, + gint *dest, gboolean *near_edge) +{ + GList *it; + Rect *a, *mon; + Rect dock_area; + gint edge; + + a = screen_area(self->desktop, SCREEN_AREA_ALL_MONITORS, + &self->frame->area); + mon = screen_area(self->desktop, SCREEN_AREA_ONE_MONITOR, + &self->frame->area); + + switch(dir) { + case OB_DIRECTION_NORTH: + if (my_head >= RECT_TOP(*mon) + 1) + edge = RECT_TOP(*mon) - 1; + else + edge = RECT_TOP(*a) - 1; break; case OB_DIRECTION_SOUTH: - my_edge_start = c->frame->area.x; - my_edge_end = c->frame->area.x + c->frame->area.width; - my_offset = c->frame->area.y + (hang ? 0 : c->frame->area.height); + if (my_head <= RECT_BOTTOM(*mon) - 1) + edge = RECT_BOTTOM(*mon) + 1; + else + edge = RECT_BOTTOM(*a) + 1; + break; + case OB_DIRECTION_EAST: + if (my_head <= RECT_RIGHT(*mon) - 1) + edge = RECT_RIGHT(*mon) + 1; + else + edge = RECT_RIGHT(*a) + 1; + break; + case OB_DIRECTION_WEST: + if (my_head >= RECT_LEFT(*mon) + 1) + edge = RECT_LEFT(*mon) - 1; + else + edge = RECT_LEFT(*a) - 1; + break; + default: + g_assert_not_reached(); + } + /* default to the far edge, then narrow it down */ + *dest = edge; + *near_edge = TRUE; - /* default: bottom of screen */ - dest = a->y + a->height - (hang ? c->frame->area.height : 0); - monitor_dest = monitor->y + monitor->height - - (hang ? c->frame->area.height : 0); - /* if the monitor edge comes before the screen edge, */ - /* use that as the destination instead. (For xinerama) */ - if (monitor_dest != dest && my_offset < monitor_dest) - dest = monitor_dest; + for(it = client_list; it; it = g_list_next(it)) { + ObClient *cur = it->data; - for(it = client_list; it && my_offset != dest; it = g_list_next(it)) { - gint his_edge_start, his_edge_end, his_offset; - ObClient *cur = it->data; + /* skip windows to not bump into */ + if (cur == self) + continue; + if (cur->iconic) + continue; + if (self->desktop != cur->desktop && cur->desktop != DESKTOP_ALL && + cur->desktop != screen_desktop) + continue; - WANT_EDGE(cur, c) + ob_debug("trying window %s\n", cur->title); - his_edge_start = cur->frame->area.x; - his_edge_end = cur->frame->area.x + cur->frame->area.width; - his_offset = cur->frame->area.y + - (hang ? cur->frame->area.height : 0); + detect_edge(cur->frame->area, dir, my_head, my_size, my_edge_start, + my_edge_size, dest, near_edge); + } + dock_get_area(&dock_area); + detect_edge(dock_area, dir, my_head, my_size, my_edge_start, + my_edge_size, dest, near_edge); +} +void client_find_move_directional(ObClient *self, ObDirection dir, + gint *x, gint *y) +{ + gint head, size; + gint e, e_start, e_size; + gboolean near; - if(his_offset - 1 < my_offset) - continue; - - if(his_offset > dest) - continue; + switch (dir) { + case OB_DIRECTION_EAST: + head = RECT_RIGHT(self->frame->area); + size = self->frame->area.width; + e_start = RECT_TOP(self->frame->area); + e_size = self->frame->area.height; + break; + case OB_DIRECTION_WEST: + head = RECT_LEFT(self->frame->area); + size = self->frame->area.width; + e_start = RECT_TOP(self->frame->area); + e_size = self->frame->area.height; + break; + case OB_DIRECTION_NORTH: + head = RECT_TOP(self->frame->area); + size = self->frame->area.height; + e_start = RECT_LEFT(self->frame->area); + e_size = self->frame->area.width; + break; + case OB_DIRECTION_SOUTH: + head = RECT_BOTTOM(self->frame->area); + size = self->frame->area.height; + e_start = RECT_LEFT(self->frame->area); + e_size = self->frame->area.width; + break; + default: + g_assert_not_reached(); + } - HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end) - } + client_find_edge_directional(self, dir, head, size, + e_start, e_size, &e, &near); + *x = self->frame->area.x; + *y = self->frame->area.y; + switch (dir) { + case OB_DIRECTION_EAST: + if (near) e -= self->frame->area.width; + else e++; + *x = e; break; case OB_DIRECTION_WEST: - my_edge_start = c->frame->area.y; - my_edge_end = c->frame->area.y + c->frame->area.height; - my_offset = c->frame->area.x + (hang ? c->frame->area.width : 0); - - /* default: leftmost egde of screen */ - dest = a->x + (hang ? c->frame->area.width : 0); - monitor_dest = monitor->x + (hang ? c->frame->area.width : 0); - /* if the monitor edge comes before the screen edge, */ - /* use that as the destination instead. (For xinerama) */ - if (monitor_dest != dest && my_offset > monitor_dest) - dest = monitor_dest; - - for(it = client_list; it && my_offset != dest; it = g_list_next(it)) { - gint his_edge_start, his_edge_end, his_offset; - ObClient *cur = it->data; - - WANT_EDGE(cur, c) - - his_edge_start = cur->frame->area.y; - his_edge_end = cur->frame->area.y + cur->frame->area.height; - his_offset = cur->frame->area.x + - (hang ? 0 : cur->frame->area.width); - - if(his_offset + 1 > my_offset) - continue; + if (near) e++; + else e -= self->frame->area.width; + *x = e; + break; + case OB_DIRECTION_NORTH: + if (near) e++; + else e -= self->frame->area.height; + *y = e; + break; + case OB_DIRECTION_SOUTH: + if (near) e -= self->frame->area.height; + else e++; + *y = e; + break; + default: + g_assert_not_reached(); + } + frame_frame_gravity(self->frame, x, y); +} - if(his_offset < dest) - continue; +void client_find_resize_directional(ObClient *self, ObDirection side, + gboolean grow, + gint *x, gint *y, gint *w, gint *h) +{ + gint head; + gint e, e_start, e_size, delta; + gboolean near; + ObDirection dir; - HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end) - } - break; + switch (side) { case OB_DIRECTION_EAST: - my_edge_start = c->frame->area.y; - my_edge_end = c->frame->area.y + c->frame->area.height; - my_offset = c->frame->area.x + (hang ? 0 : c->frame->area.width); - - /* default: rightmost edge of screen */ - dest = a->x + a->width - (hang ? c->frame->area.width : 0); - monitor_dest = monitor->x + monitor->width - - (hang ? c->frame->area.width : 0); - /* if the monitor edge comes before the screen edge, */ - /* use that as the destination instead. (For xinerama) */ - if (monitor_dest != dest && my_offset < monitor_dest) - dest = monitor_dest; - - for(it = client_list; it && my_offset != dest; it = g_list_next(it)) { - gint his_edge_start, his_edge_end, his_offset; - ObClient *cur = it->data; - - WANT_EDGE(cur, c) - - his_edge_start = cur->frame->area.y; - his_edge_end = cur->frame->area.y + cur->frame->area.height; - his_offset = cur->frame->area.x + - (hang ? cur->frame->area.width : 0); - - if(his_offset - 1 < my_offset) - continue; - - if(his_offset > dest) - continue; + head = RECT_RIGHT(self->frame->area) + + (self->size_inc.width - 1) * (grow ? 1 : -1); + e_start = RECT_TOP(self->frame->area); + e_size = self->frame->area.height; + dir = grow ? OB_DIRECTION_EAST : OB_DIRECTION_WEST; + break; + case OB_DIRECTION_WEST: + head = RECT_LEFT(self->frame->area) - + (self->size_inc.width - 1) * (grow ? 1 : -1); + e_start = RECT_TOP(self->frame->area); + e_size = self->frame->area.height; + dir = grow ? OB_DIRECTION_WEST : OB_DIRECTION_EAST; + break; + case OB_DIRECTION_NORTH: + head = RECT_TOP(self->frame->area) - + (self->size_inc.height - 1) * (grow ? 1 : -1); + e_start = RECT_LEFT(self->frame->area); + e_size = self->frame->area.width; + dir = grow ? OB_DIRECTION_NORTH : OB_DIRECTION_SOUTH; + break; + case OB_DIRECTION_SOUTH: + head = RECT_BOTTOM(self->frame->area) + + (self->size_inc.height - 1) * (grow ? 1 : -1); + e_start = RECT_LEFT(self->frame->area); + e_size = self->frame->area.width; + dir = grow ? OB_DIRECTION_SOUTH : OB_DIRECTION_NORTH; + break; + default: + g_assert_not_reached(); + } - HIT_EDGE(my_edge_start, my_edge_end, his_edge_start, his_edge_end) - } + ob_debug("head %d dir %d\n", head, dir); + client_find_edge_directional(self, dir, head, 1, + e_start, e_size, &e, &near); + ob_debug("edge %d\n", e); + *x = self->frame->area.x; + *y = self->frame->area.y; + *w = self->frame->area.width; + *h = self->frame->area.height; + switch (side) { + case OB_DIRECTION_EAST: + if (grow == near) --e; + delta = e - RECT_RIGHT(self->frame->area); + *w += delta; + break; + case OB_DIRECTION_WEST: + if (grow == near) ++e; + delta = RECT_LEFT(self->frame->area) - e; + *x -= delta; + *w += delta; + break; + case OB_DIRECTION_NORTH: + if (grow == near) ++e; + delta = RECT_TOP(self->frame->area) - e; + *y -= delta; + *h += delta; + break; + case OB_DIRECTION_SOUTH: + if (grow == near) --e; + delta = e - RECT_BOTTOM(self->frame->area); + *h += delta; break; - case OB_DIRECTION_NORTHEAST: - case OB_DIRECTION_SOUTHEAST: - case OB_DIRECTION_NORTHWEST: - case OB_DIRECTION_SOUTHWEST: - /* not implemented */ default: g_assert_not_reached(); - dest = 0; /* suppress warning */ } - return dest; + frame_frame_gravity(self->frame, x, y); + *w -= self->frame->size.left + self->frame->size.right; + *h -= self->frame->size.top + self->frame->size.bottom; } ObClient* client_under_pointer() @@ -3699,7 +4106,15 @@ ObClient* client_under_pointer() if (WINDOW_IS_CLIENT(it->data)) { ObClient *c = WINDOW_AS_CLIENT(it->data); if (c->frame->visible && - RECT_CONTAINS(c->frame->area, x, y)) { + /* check the desktop, this is done during desktop + switching and windows are shown/hidden status is not + reliable */ + (c->desktop == screen_desktop || + c->desktop == DESKTOP_ALL) && + /* ignore all animating windows */ + !frame_iconify_animating(c->frame) && + RECT_CONTAINS(c->frame->area, x, y)) + { ret = c; break; } @@ -3713,17 +4128,3 @@ gboolean client_has_group_siblings(ObClient *self) { return self->group && self->group->members->next; } - -gboolean client_has_application_group_siblings(ObClient *self) -{ - GSList *it; - - if (!self->group) return FALSE; - - for (it = self->group->members; it; it = g_slist_next(it)) { - ObClient *c = it->data; - if (c != self && client_application(c)) - return TRUE; - } - return FALSE; -}