/* $Id$ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. oroborus - (c) 2001 Ken Lynch xfwm4 - (c) 2002-2007 Olivier Fourdan */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include <X11/X.h> #include <X11/Xlib.h> #include <X11/Xutil.h> #include <X11/Xatom.h> #include <X11/extensions/shape.h> #include <glib.h> #include <gdk/gdk.h> #include <gdk/gdkx.h> #include <gtk/gtk.h> #include <libxfce4util/libxfce4util.h> #include "screen.h" #include "focus.h" #include "misc.h" #include "client.h" #include "frame.h" #include "stacking.h" #include "transients.h" #include "workspaces.h" #include "hints.h" #include "netwm.h" typedef struct _ClientPair ClientPair; struct _ClientPair { Client *prefered; Client *highest; }; static Client *client_focus = NULL; static Client *pending_focus = NULL; static Client *user_focus = NULL; static Client *last_ungrab = NULL; static Client *delayed_focus = NULL; static guint focus_timeout = 0; static ClientPair clientGetTopMostFocusable (ScreenInfo *screen_info, int layer, Client * exclude) { ClientPair top_client; Client *c; GList *index; TRACE ("entering clientGetTopMostFocusable"); top_client.prefered = top_client.highest = NULL; for (index = screen_info->windows_stack; index; index = g_list_next (index)) { c = (Client *) index->data; TRACE ("*** stack window \"%s\" (0x%lx), layer %i", c->name, c->window, (int) c->win_layer); if (!clientAcceptFocus (c) || (c->type & WINDOW_TYPE_DONT_FOCUS)) { continue; } if (!exclude || (c != exclude)) { if ((c->win_layer <= layer) && FLAG_TEST (c->xfwm_flags, XFWM_FLAG_VISIBLE)) { if (clientSelectMask (c, 0, WINDOW_REGULAR_FOCUSABLE)) { top_client.prefered = c; } top_client.highest = c; } else if (c->win_layer > layer) { break; } } } return top_client; } void clientFocusTop (ScreenInfo *screen_info, int layer, Time timestamp) { ClientPair top_client; DisplayInfo *display_info; display_info = screen_info->display_info; top_client = clientGetTopMostFocusable (screen_info, layer, NULL); if (top_client.prefered) { clientSetFocus (screen_info, top_client.prefered, timestamp, NO_FOCUS_FLAG); } else { clientSetFocus (screen_info, top_client.highest, timestamp, NO_FOCUS_FLAG); } } gboolean clientFocusNew(Client * c) { ScreenInfo *screen_info; DisplayInfo *display_info; gboolean give_focus; gboolean prevent_focus_stealing; gboolean prevented; g_return_val_if_fail (c != NULL, FALSE); screen_info = c->screen_info; display_info = screen_info->display_info; give_focus = (c-> type & WINDOW_REGULAR_FOCUSABLE) && (screen_info->params->focus_new); prevent_focus_stealing = screen_info->params->prevent_focus_stealing; prevented = FALSE; /* Try to avoid focus stealing */ if (!clientAcceptFocus (c) || (c->type & WINDOW_TYPE_DONT_FOCUS)) { give_focus = FALSE; } else if ((client_focus) && (prevent_focus_stealing)) { if (FLAG_TEST (c->flags, CLIENT_FLAG_HAS_STARTUP_TIME) && (c->user_time == (Time) CurrentTime)) { TRACE ("Given startup time is 0, not focusing"); give_focus = FALSE; prevented = TRUE; } else if (FLAG_TEST (c->flags, CLIENT_FLAG_HAS_STARTUP_TIME | CLIENT_FLAG_HAS_USER_TIME)) { if (TIMESTAMP_IS_BEFORE (c->user_time, client_focus->user_time)) { TRACE ("Current %u, new %u", (unsigned int) client_focus->user_time, (unsigned int) c->user_time); give_focus = FALSE; prevented = TRUE; } } } if ((give_focus) || FLAG_TEST(c->flags, CLIENT_FLAG_STATE_MODAL)) { give_focus = TRUE; if ((client_focus) && !(clientIsTransientOrModalFor (c, client_focus))) { clientAdjustFullscreenLayer (client_focus, FALSE); } clientRaise (c, None); clientShow (c, TRUE); clientSetFocus (screen_info, c, myDisplayGetCurrentTime (display_info), FOCUS_IGNORE_MODAL); } else { Client *c2 = clientGetFocus(); if ((prevented) && (c2 != NULL) && (c2->win_layer == c->win_layer)) { TRACE ("clientFocusNew: Setting WM_STATE_DEMANDS_ATTENTION flag on \"%s\" (0x%lx)", c->name, c->window); FLAG_SET (c->flags, CLIENT_FLAG_DEMANDS_ATTENTION); clientSortRing(c); clientLower (c, c2->frame); clientSortRing(c2); clientSetOpacity (c, c->opacity, 0, 0); } else { clientRaise (c, None); clientSortRing(c); } clientShow (c, TRUE); clientSetNetState (c); clientGrabMouseButton (c); } return (give_focus); } gboolean clientSelectMask (Client * c, int mask, int type) { g_return_val_if_fail (c != NULL, FALSE); TRACE ("entering clientSelectMask"); if ((!clientAcceptFocus (c)) && !(mask & INCLUDE_SKIP_FOCUS)) { return FALSE; } if (FLAG_TEST (c->flags, CLIENT_FLAG_ICONIFIED) && !(mask & INCLUDE_HIDDEN)) { return FALSE; } if (FLAG_TEST (c->flags, CLIENT_FLAG_SKIP_PAGER) && !(mask & INCLUDE_SKIP_PAGER)) { return FALSE; } if (FLAG_TEST (c->flags, CLIENT_FLAG_SKIP_TASKBAR) && !(mask & INCLUDE_SKIP_TASKBAR)) { return FALSE; } if ((c->win_workspace != c->screen_info->current_ws) && !(mask & INCLUDE_ALL_WORKSPACES)) { return FALSE; } if (c->type & type) { return TRUE; } return FALSE; } Client * clientGetNext (Client * c, int mask) { Client *c2; unsigned int i; TRACE ("entering clientGetNext"); if (c) { ScreenInfo *screen_info = c->screen_info; for (c2 = c->next, i = 0; (c2) && (i < screen_info->client_count - 1); c2 = c2->next, i++) { if (clientSelectMask (c2, mask, WINDOW_REGULAR_FOCUSABLE)) { return c2; } } } return NULL; } Client * clientGetPrevious (Client * c, int mask) { Client *c2; unsigned int i; TRACE ("entering clientGetPrevious"); if (c) { ScreenInfo *screen_info = c->screen_info; for (c2 = c->prev, i = 0; (c2) && (i < screen_info->client_count); c2 = c2->prev, i++) { if (clientSelectMask (c2, mask, WINDOW_REGULAR_FOCUSABLE)) { return c2; } } } return NULL; } void clientPassFocus (ScreenInfo *screen_info, Client *c, Client *exclude) { DisplayInfo *display_info; ClientPair top_most; Client *new_focus; Client *current_focus; Client *c2; Window dr, window; unsigned int mask; int rx, ry, wx, wy; int look_in_layer; TRACE ("entering clientPassFocus"); look_in_layer = (c ? c->win_layer : WIN_LAYER_NORMAL); new_focus = NULL; current_focus = client_focus; c2 = NULL; if (pending_focus) { current_focus = pending_focus; } if ((c || current_focus) && (c != current_focus)) { return; } if (current_focus == last_ungrab) { clientPassGrabMouseButton (NULL); } display_info = screen_info->display_info; top_most = clientGetTopMostFocusable (screen_info, look_in_layer, exclude); if (screen_info->params->click_to_focus) { if (c) { if (clientIsModal (c)) { /* If the window is a modal, send focus back to its parent window. Modals are transients, and we aren't interested in modal for group, so it safe to use clientGetTransient because it's really what we want... */ c2 = clientGetTransient (c); if (c2 && FLAG_TEST(c2->xfwm_flags, XFWM_FLAG_VISIBLE)) { new_focus = c2; /* Usability: raise the parent, to grab user's attention */ clientRaise (c2, None); } } else { c2 = clientGetNext (c, 0); if ((c2) && (c2->win_layer >= c->win_layer)) { new_focus = c2; } } } } else if (XQueryPointer (myScreenGetXDisplay (screen_info), screen_info->xroot, &dr, &window, &rx, &ry, &wx, &wy, &mask)) { new_focus = clientAtPosition (screen_info, rx, ry, exclude); } if (!new_focus) { new_focus = top_most.prefered ? top_most.prefered : top_most.highest; } clientSetFocus (screen_info, new_focus, myDisplayGetCurrentTime (display_info), FOCUS_IGNORE_MODAL | FOCUS_FORCE); } gboolean clientAcceptFocus (Client * c) { g_return_val_if_fail (c != NULL, FALSE); TRACE ("entering clientAcceptFocus"); /* Modal dialogs *always* accept focus */ if (FLAG_TEST(c->flags, CLIENT_FLAG_STATE_MODAL)) { return TRUE; } /* First check GNOME protocol */ if (c->win_hints & WIN_HINTS_SKIP_FOCUS) { return FALSE; } if ((c->screen_info->params->focus_hint) && !FLAG_TEST (c->wm_flags, WM_FLAG_INPUT | WM_FLAG_TAKEFOCUS)) { return FALSE; } return TRUE; } void clientSortRing(Client *c) { ScreenInfo *screen_info; g_return_if_fail (c != NULL); TRACE ("Sorting..."); screen_info = c->screen_info; if ((screen_info->client_count > 2) && (c != screen_info->clients)) { c->prev->next = c->next; c->next->prev = c->prev; c->prev = screen_info->clients->prev; c->next = screen_info->clients; screen_info->clients->prev->next = c; screen_info->clients->prev = c; } screen_info->clients = c; } void clientUpdateFocus (ScreenInfo *screen_info, Client * c, unsigned short flags) { DisplayInfo *display_info; Client *c2; unsigned long data[2]; TRACE ("entering clientUpdateFocus"); c2 = ((client_focus != c) ? client_focus : NULL); display_info = screen_info->display_info; pending_focus = NULL; if ((c) && !clientAcceptFocus (c)) { TRACE ("SKIP_FOCUS set for client \"%s\" (0x%lx)", c->name, c->window); return; } if ((c) && (c == client_focus) && !(flags & FOCUS_FORCE)) { TRACE ("client \"%s\" (0x%lx) is already focused, ignoring request", c->name, c->window); return; } /* We can release the button mouse grab if we don't raise on click or if the focused window is the one that has been raised at last. */ if (!(screen_info->params->raise_on_click) || (c == clientGetLastRaise (screen_info))) { clientPassGrabMouseButton (c); } client_focus = c; if (c) { user_focus = c; clientInstallColormaps (c); if (flags & FOCUS_SORT) { clientSortRing(c); } if (FLAG_TEST(c->flags, CLIENT_FLAG_DEMANDS_ATTENTION)) { TRACE ("Un-setting WM_STATE_DEMANDS_ATTENTION flag on \"%s\" (0x%lx)", c->name, c->window); FLAG_UNSET (c->flags, CLIENT_FLAG_DEMANDS_ATTENTION); clientSetNetState (c); } data[0] = c->window; clientAdjustFullscreenLayer (c, TRUE); frameDraw (c, FALSE); } else { data[0] = None; } if (c2) { if (c) { clientAdjustFullscreenLayer (c2, FALSE); /* clientRaise (c, None); */ } frameDraw (c2, FALSE); } data[1] = None; XChangeProperty (display_info->dpy, screen_info->xroot, display_info->atoms[NET_ACTIVE_WINDOW], XA_WINDOW, 32, PropModeReplace, (unsigned char *) data, 2); clientClearDelayedFocus (); clientUpdateOpacity (screen_info, c); } void clientSetFocus (ScreenInfo *screen_info, Client *c, Time timestamp, unsigned short flags) { DisplayInfo *display_info; Client *c2; TRACE ("entering clientSetFocus"); display_info = screen_info->display_info; c2 = NULL; if ((c) && !(flags & FOCUS_IGNORE_MODAL)) { c2 = clientGetModalFor (c); if (c2) { c = c2; } } c2 = ((client_focus != c) ? client_focus : NULL); if ((c) && FLAG_TEST (c->xfwm_flags, XFWM_FLAG_VISIBLE)) { TRACE ("setting focus to client \"%s\" (0x%lx) with timestamp %u", c->name, c->window, (unsigned int) timestamp); user_focus = c; if (FLAG_TEST(c->flags, CLIENT_FLAG_DEMANDS_ATTENTION)) { TRACE ("Un-setting WM_STATE_DEMANDS_ATTENTION flag on \"%s\" (0x%lx)", c->name, c->window); FLAG_UNSET (c->flags, CLIENT_FLAG_DEMANDS_ATTENTION); clientSetNetState (c); } if ((c == client_focus) && !(flags & FOCUS_FORCE)) { TRACE ("client \"%s\" (0x%lx) is already focused, ignoring request", c->name, c->window); return; } if (!clientAcceptFocus (c)) { TRACE ("SKIP_FOCUS set for client \"%s\" (0x%lx)", c->name, c->window); return; } if (FLAG_TEST (c->wm_flags, WM_FLAG_INPUT) || !(screen_info->params->focus_hint)) { pending_focus = c; /* * When shaded, the client window is unmapped, so it can not be focused. * Instead, we focus the frame that is still mapped. */ if (FLAG_TEST (c->flags, CLIENT_FLAG_SHADED)) { XSetInputFocus (myScreenGetXDisplay (screen_info), c->frame, RevertToPointerRoot, timestamp); } else { XSetInputFocus (myScreenGetXDisplay (screen_info), c->window, RevertToPointerRoot, timestamp); } clientUpdateOpacity (screen_info, c); } else if (!client_focus) { /* Hack to prevent loosing focus when all remaining windows won't accept focus, see bug #1853 */ XSetInputFocus (myScreenGetXDisplay (screen_info), screen_info->xfwm4_win, RevertToPointerRoot, timestamp); } if (FLAG_TEST(c->wm_flags, WM_FLAG_TAKEFOCUS)) { pending_focus = c; sendClientMessage (c->screen_info, c->window, WM_TAKE_FOCUS, timestamp); } } else { unsigned long data[2]; TRACE ("setting focus to none"); data[0] = data[1] = None; client_focus = NULL; pending_focus = NULL; if (c2) { frameDraw (c2, FALSE); XChangeProperty (clientGetXDisplay (c2), c2->screen_info->xroot, display_info->atoms[NET_ACTIVE_WINDOW], XA_WINDOW, 32, PropModeReplace, (unsigned char *) data, 2); } XChangeProperty (myScreenGetXDisplay (screen_info), screen_info->xroot, display_info->atoms[NET_ACTIVE_WINDOW], XA_WINDOW, 32, PropModeReplace, (unsigned char *) data, 2); XSetInputFocus (myScreenGetXDisplay (screen_info), screen_info->xfwm4_win, RevertToPointerRoot, timestamp); clientClearDelayedFocus (); clientUpdateOpacity (screen_info, c); } } void clientInitFocusFlag (Client * c) { ScreenInfo *screen_info; Client *c2; GList *index; int workspace; g_return_if_fail (c != NULL); TRACE ("entering clientSetFocus"); if (!clientAcceptFocus (c) || (c->type & WINDOW_TYPE_DONT_FOCUS)) { return; } screen_info = c->screen_info; workspace = c->win_workspace; for (index = screen_info->windows_stack; index; index = g_list_next (index)) { c2 = (Client *) index->data; if ((c2->win_workspace == workspace) && FLAG_TEST (c2->xfwm_flags, XFWM_FLAG_FOCUS)) { FLAG_UNSET (c2->xfwm_flags, XFWM_FLAG_FOCUS); } } FLAG_SET (c->xfwm_flags, XFWM_FLAG_FOCUS); } Client * clientGetFocus (void) { return (client_focus); } Client * clientGetFocusPending (void) { return (pending_focus); } Client * clientGetFocusOrPending (void) { if (client_focus) { return (client_focus); } return (pending_focus); } Client * clientGetUserFocus (void) { return (user_focus); } void clientClearFocus (Client *c) { if ((c == NULL) || (c == client_focus)) { client_focus = NULL; } if ((c == NULL) || (c == pending_focus)) { pending_focus = NULL; } if ((c == NULL) || (c == user_focus)) { user_focus = NULL; } } void clientGrabMouseButton (Client * c) { ScreenInfo *screen_info; g_return_if_fail (c != NULL); TRACE ("entering clientGrabMouseButton"); TRACE ("grabbing buttons for client \"%s\" (0x%lx)", c->name, c->window); screen_info = c->screen_info; if (screen_info->params->raise_with_any_button) { grabButton(clientGetXDisplay (c), AnyButton, AnyModifier, c->window); } else { grabButton(clientGetXDisplay (c), Button1, AnyModifier, c->window); } } void clientUngrabMouseButton (Client * c) { ScreenInfo *screen_info; g_return_if_fail (c != NULL); TRACE ("entering clientUngrabMouseButton"); TRACE ("ungrabing buttons for client \"%s\" (0x%lx)", c->name, c->window); screen_info = c->screen_info; ungrabButton(clientGetXDisplay (c), AnyButton, AnyModifier, c->window); /* We've ungrabbed way too much, so regrab the regular buttons/modifiers */ clientGrabButtons (c); } void clientGrabMouseButtonForAll (ScreenInfo *screen_info) { Client *c; int i; g_return_if_fail (screen_info != NULL); TRACE ("entering clientGrabMouseButtonForAll"); for (c = screen_info->clients, i = 0; (c) && (i < screen_info->client_count); c = c->next, i++) { clientGrabMouseButton (c); } clientClearLastUngrab (); } void clientUngrabMouseButtonForAll (ScreenInfo *screen_info) { Client *c; int i; g_return_if_fail (screen_info != NULL); TRACE ("entering clientUngrabMouseButtonForAll"); for (c = screen_info->clients, i = 0; (c) && (i < screen_info->client_count); c = c->next, i++) { clientUngrabMouseButton (c); } clientClearLastUngrab (); } void clientPassGrabMouseButton (Client * c) { TRACE ("entering clientPassMouseGrabButton"); if (c == NULL) { if (last_ungrab) { clientGrabMouseButton (last_ungrab); } last_ungrab = NULL; return; } TRACE ("ungrabing buttons for client \"%s\" (0x%lx)", c->name, c->window); if (last_ungrab == c) { return; } if (last_ungrab) { clientGrabMouseButton (last_ungrab); } clientUngrabMouseButton (c); last_ungrab = c; } Client * clientGetLastUngrab (void) { return last_ungrab; } void clientClearLastUngrab (void) { last_ungrab = NULL; } static gboolean delayed_focus_cb (gpointer data) { ScreenInfo *screen_info; DisplayInfo *display_info; TRACE ("entering delayed_focus_cb"); g_return_val_if_fail (delayed_focus != NULL, FALSE); screen_info = delayed_focus->screen_info; display_info = screen_info->display_info; clientSetFocus (screen_info, delayed_focus, myDisplayGetCurrentTime (display_info), NO_FOCUS_FLAG); focus_timeout = 0; delayed_focus = NULL; return (FALSE); } void clientClearDelayedFocus (void) { if(focus_timeout) { g_source_remove (focus_timeout); focus_timeout = 0; } delayed_focus = NULL; } void clientAddDelayedFocus (Client *c) { ScreenInfo *screen_info; screen_info = c->screen_info; delayed_focus = c; focus_timeout = g_timeout_add_full (G_PRIORITY_DEFAULT, screen_info->params->focus_delay, (GSourceFunc) delayed_focus_cb, NULL, NULL); } Client * clientGetDelayedFocus (void) { return delayed_focus; }