/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2001 Havoc Pennington * Copyright (C) 2002, 2003, 2004 Red Hat, Inc. * Copyright (C) 2003, 2004 Rob Adams * Copyright (C) 2004-2006 Elijah Newren * * 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 of the * License, 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., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ /** * SECTION:display * @title: MetaDisplay * @short_description: Mutter X display handler * * The display is represented as a #MetaDisplay struct. */ #define _XOPEN_SOURCE 600 /* for gethostname() */ #include #include "display-private.h" #include "util-private.h" #include #include "screen-private.h" #include "window-private.h" #include "window-props.h" #include "group-props.h" #include "frame.h" #include #include "keybindings-private.h" #include #include "resizepopup.h" #include "xprops.h" #include "workspace-private.h" #include "bell.h" #include #include #include #include "mutter-enum-types.h" #include "meta-idle-monitor-private.h" #include "meta-cursor-tracker-private.h" #ifdef HAVE_RANDR #include #endif #ifdef HAVE_SHAPE #include #endif #ifdef HAVE_XKB #include #endif #include #include #include #include #include #include #include #include #include "meta-xwayland-private.h" #define GRAB_OP_IS_WINDOW_SWITCH(g) \ (g == META_GRAB_OP_KEYBOARD_TABBING_NORMAL || \ g == META_GRAB_OP_KEYBOARD_TABBING_DOCK || \ g == META_GRAB_OP_KEYBOARD_TABBING_GROUP || \ g == META_GRAB_OP_KEYBOARD_ESCAPING_NORMAL || \ g == META_GRAB_OP_KEYBOARD_ESCAPING_DOCK || \ g == META_GRAB_OP_KEYBOARD_ESCAPING_GROUP) G_DEFINE_TYPE(MetaDisplay, meta_display, G_TYPE_OBJECT); /* Signals */ enum { OVERLAY_KEY, ACCELERATOR_ACTIVATED, MODIFIERS_ACCELERATOR_ACTIVATED, FOCUS_WINDOW, WINDOW_CREATED, WINDOW_DEMANDS_ATTENTION, WINDOW_MARKED_URGENT, GRAB_OP_BEGIN, GRAB_OP_END, LAST_SIGNAL }; enum { PROP_0, PROP_FOCUS_WINDOW }; static guint display_signals [LAST_SIGNAL] = { 0 }; /* * The display we're managing. This is a singleton object. (Historically, * this was a list of displays, but there was never any way to add more * than one element to it.) The goofy name is because we don't want it * to shadow the parameter in its object methods. */ static MetaDisplay *the_display = NULL; static const char *gnome_wm_keybindings = "Mutter"; static const char *net_wm_name = "Mutter"; #ifdef WITH_VERBOSE_MODE static void meta_spew_event (MetaDisplay *display, XEvent *event); #endif static gboolean xevent_callback (XEvent *event, gpointer data); static gboolean event_callback (const ClutterEvent *event, gpointer data); static Window event_get_modified_window (MetaDisplay *display, XEvent *event); static Window xievent_get_modified_window (MetaDisplay *display, XIEvent *input_event); static guint32 event_get_time (MetaDisplay *display, XEvent *event); static void process_request_frame_extents (MetaDisplay *display, XEvent *event); static void process_selection_request (MetaDisplay *display, XEvent *event); static void process_selection_clear (MetaDisplay *display, XEvent *event); static void update_window_grab_modifiers (MetaDisplay *display); static void prefs_changed_callback (MetaPreference pref, void *data); static void sanity_check_timestamps (MetaDisplay *display, guint32 known_good_timestamp); MetaGroup* get_focussed_group (MetaDisplay *display); static void meta_display_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { MetaDisplay *display = META_DISPLAY (object); switch (prop_id) { case PROP_FOCUS_WINDOW: g_value_set_object (value, display->focus_window); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void meta_display_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { switch (prop_id) { default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void meta_display_class_init (MetaDisplayClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->get_property = meta_display_get_property; object_class->set_property = meta_display_set_property; display_signals[OVERLAY_KEY] = g_signal_new ("overlay-key", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); display_signals[ACCELERATOR_ACTIVATED] = g_signal_new ("accelerator-activated", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_UINT); /** * MetaDisplay::modifiers-accelerator-activated: * @display: the #MetaDisplay instance * * The ::modifiers-accelerator-activated signal will be emitted when * a special modifiers-only keybinding is activated. * * Returns: %TRUE means that the keyboard device should remain * frozen and %FALSE for the default behavior of unfreezing the * keyboard. */ display_signals[MODIFIERS_ACCELERATOR_ACTIVATED] = g_signal_new ("modifiers-accelerator-activated", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, g_signal_accumulator_first_wins, NULL, NULL, G_TYPE_BOOLEAN, 0); display_signals[WINDOW_CREATED] = g_signal_new ("window-created", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, META_TYPE_WINDOW); display_signals[WINDOW_DEMANDS_ATTENTION] = g_signal_new ("window-demands-attention", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, META_TYPE_WINDOW); display_signals[WINDOW_MARKED_URGENT] = g_signal_new ("window-marked-urgent", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, META_TYPE_WINDOW); display_signals[GRAB_OP_BEGIN] = g_signal_new ("grab-op-begin", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 3, META_TYPE_SCREEN, META_TYPE_WINDOW, META_TYPE_GRAB_OP); display_signals[GRAB_OP_END] = g_signal_new ("grab-op-end", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 3, META_TYPE_SCREEN, META_TYPE_WINDOW, META_TYPE_GRAB_OP); g_object_class_install_property (object_class, PROP_FOCUS_WINDOW, g_param_spec_object ("focus-window", "Focus window", "Currently focused window", META_TYPE_WINDOW, G_PARAM_READABLE)); } #ifdef HAVE_STARTUP_NOTIFICATION static void sn_error_trap_push (SnDisplay *sn_display, Display *xdisplay) { MetaDisplay *display; display = meta_display_for_x_display (xdisplay); if (display != NULL) meta_error_trap_push (display); } static void sn_error_trap_pop (SnDisplay *sn_display, Display *xdisplay) { MetaDisplay *display; display = meta_display_for_x_display (xdisplay); if (display != NULL) meta_error_trap_pop (display); } #endif static void enable_compositor (MetaDisplay *display) { GSList *list; if (!META_DISPLAY_HAS_COMPOSITE (display) || !META_DISPLAY_HAS_DAMAGE (display) || !META_DISPLAY_HAS_RENDER (display)) { meta_warning ("Missing %s extension required for compositing", !META_DISPLAY_HAS_COMPOSITE (display) ? "composite" : !META_DISPLAY_HAS_DAMAGE (display) ? "damage" : "render"); return; } if (!display->compositor) display->compositor = meta_compositor_new (display); if (!display->compositor) return; for (list = display->screens; list != NULL; list = list->next) { MetaScreen *screen = list->data; meta_compositor_manage_screen (screen->display->compositor, screen); } } static void meta_display_init (MetaDisplay *disp) { /* Some stuff could go in here that's currently in _open, * but it doesn't really matter. */ } /** * meta_set_wm_name: (skip) * @wm_name: value for _NET_WM_NAME * * Set the value to use for the _NET_WM_NAME property. To take effect, * it is necessary to call this function before meta_init(). */ void meta_set_wm_name (const char *wm_name) { g_return_if_fail (the_display == NULL); net_wm_name = wm_name; } /** * meta_set_gnome_wm_keybindings: (skip) * @wm_keybindings: value for _GNOME_WM_KEYBINDINGS * * Set the value to use for the _GNOME_WM_KEYBINDINGS property. To take * effect, it is necessary to call this function before meta_init(). */ void meta_set_gnome_wm_keybindings (const char *wm_keybindings) { g_return_if_fail (the_display == NULL); gnome_wm_keybindings = wm_keybindings; } /** * meta_display_open: * * Opens a new display, sets it up, initialises all the X extensions * we will need, and adds it to the list of displays. * * Returns: %TRUE if the display was opened successfully, and %FALSE * otherwise-- that is, if the display doesn't exist or it already * has a window manager. */ gboolean meta_display_open (void) { Display *xdisplay; GSList *screens; MetaScreen *screen; GSList *tmp; int i; guint32 timestamp; /* A list of all atom names, so that we can intern them in one go. */ char *atom_names[] = { #define item(x) #x, #include #undef item }; Atom atoms[G_N_ELEMENTS(atom_names)]; meta_verbose ("Opening display '%s'\n", XDisplayName (NULL)); xdisplay = meta_ui_get_display (); if (xdisplay == NULL) { meta_warning (_("Failed to open X Window System display '%s'\n"), XDisplayName (NULL)); return FALSE; } if (meta_is_wayland_compositor ()) meta_xwayland_complete_init (); if (meta_is_syncing ()) XSynchronize (xdisplay, True); g_assert (the_display == NULL); the_display = g_object_new (META_TYPE_DISPLAY, NULL); the_display->closing = 0; /* here we use XDisplayName which is what the user * probably put in, vs. DisplayString(display) which is * canonicalized by XOpenDisplay() */ the_display->name = g_strdup (XDisplayName (NULL)); the_display->xdisplay = xdisplay; the_display->error_trap_synced_at_last_pop = TRUE; the_display->error_traps = 0; the_display->error_trap_handler = NULL; the_display->server_grab_count = 0; the_display->display_opening = TRUE; the_display->autoraise_timeout_id = 0; the_display->autoraise_window = NULL; the_display->focus_window = NULL; the_display->focus_serial = 0; the_display->server_focus_window = None; the_display->server_focus_serial = 0; the_display->grab_old_window_stacking = NULL; the_display->mouse_mode = TRUE; /* Only relevant for mouse or sloppy focus */ the_display->allow_terminal_deactivation = TRUE; /* Only relevant for when a terminal has the focus */ /* FIXME copy the checks from GDK probably */ the_display->static_gravity_works = g_getenv ("MUTTER_USE_STATIC_GRAVITY") != NULL; meta_bell_init (the_display); meta_display_init_keys (the_display); update_window_grab_modifiers (the_display); meta_prefs_add_listener (prefs_changed_callback, the_display); meta_verbose ("Creating %d atoms\n", (int) G_N_ELEMENTS (atom_names)); XInternAtoms (the_display->xdisplay, atom_names, G_N_ELEMENTS (atom_names), False, atoms); { int i = 0; #define item(x) the_display->atom_##x = atoms[i++]; #include #undef item } the_display->prop_hooks = NULL; meta_display_init_window_prop_hooks (the_display); the_display->group_prop_hooks = NULL; meta_display_init_group_prop_hooks (the_display); /* Offscreen unmapped window used for _NET_SUPPORTING_WM_CHECK, * created in screen_new */ the_display->leader_window = None; the_display->timestamp_pinging_window = None; the_display->monitor_cache_invalidated = TRUE; the_display->groups_by_leader = NULL; the_display->window_with_menu = NULL; the_display->window_menu = NULL; the_display->screens = NULL; the_display->active_screen = NULL; #ifdef HAVE_STARTUP_NOTIFICATION the_display->sn_display = sn_display_new (the_display->xdisplay, sn_error_trap_push, sn_error_trap_pop); #endif /* Get events */ meta_ui_add_event_func (the_display->xdisplay, xevent_callback, the_display); the_display->clutter_event_filter = clutter_event_add_filter (NULL, event_callback, NULL, the_display); the_display->xids = g_hash_table_new (meta_unsigned_long_hash, meta_unsigned_long_equal); the_display->pending_pings = g_hash_table_new (g_int_hash, g_int_equal); the_display->wayland_windows = g_hash_table_new (NULL, NULL); i = 0; while (i < N_IGNORED_CROSSING_SERIALS) { the_display->ignored_crossing_serials[i] = 0; ++i; } the_display->ungrab_should_not_cause_focus_window = None; the_display->current_time = CurrentTime; the_display->sentinel_counter = 0; the_display->grab_resize_timeout_id = 0; the_display->grab_have_keyboard = FALSE; #ifdef HAVE_XKB the_display->last_bell_time = 0; #endif the_display->grab_op = META_GRAB_OP_NONE; the_display->grab_window = NULL; the_display->grab_screen = NULL; the_display->grab_resize_popup = NULL; the_display->grab_tile_mode = META_TILE_NONE; the_display->grab_tile_monitor_number = -1; the_display->grab_edge_resistance_data = NULL; #ifdef HAVE_XSYNC { int major, minor; the_display->have_xsync = FALSE; the_display->xsync_error_base = 0; the_display->xsync_event_base = 0; /* I don't think we really have to fill these in */ major = SYNC_MAJOR_VERSION; minor = SYNC_MINOR_VERSION; if (!XSyncQueryExtension (the_display->xdisplay, &the_display->xsync_event_base, &the_display->xsync_error_base) || !XSyncInitialize (the_display->xdisplay, &major, &minor)) { the_display->xsync_error_base = 0; the_display->xsync_event_base = 0; } else { the_display->have_xsync = TRUE; XSyncSetPriority (the_display->xdisplay, None, 10); } meta_verbose ("Attempted to init Xsync, found version %d.%d error base %d event base %d\n", major, minor, the_display->xsync_error_base, the_display->xsync_event_base); } #else /* HAVE_XSYNC */ meta_verbose ("Not compiled with Xsync support\n"); #endif /* !HAVE_XSYNC */ #ifdef HAVE_SHAPE { the_display->have_shape = FALSE; the_display->shape_error_base = 0; the_display->shape_event_base = 0; if (!XShapeQueryExtension (the_display->xdisplay, &the_display->shape_event_base, &the_display->shape_error_base)) { the_display->shape_error_base = 0; the_display->shape_event_base = 0; } else the_display->have_shape = TRUE; meta_verbose ("Attempted to init Shape, found error base %d event base %d\n", the_display->shape_error_base, the_display->shape_event_base); } #else /* HAVE_SHAPE */ meta_verbose ("Not compiled with Shape support\n"); #endif /* !HAVE_SHAPE */ { the_display->have_render = FALSE; the_display->render_error_base = 0; the_display->render_event_base = 0; if (!XRenderQueryExtension (the_display->xdisplay, &the_display->render_event_base, &the_display->render_error_base)) { the_display->render_error_base = 0; the_display->render_event_base = 0; } else the_display->have_render = TRUE; meta_verbose ("Attempted to init Render, found error base %d event base %d\n", the_display->render_error_base, the_display->render_event_base); } { the_display->have_composite = FALSE; the_display->composite_error_base = 0; the_display->composite_event_base = 0; if (!XCompositeQueryExtension (the_display->xdisplay, &the_display->composite_event_base, &the_display->composite_error_base)) { the_display->composite_error_base = 0; the_display->composite_event_base = 0; } else { the_display->composite_major_version = 0; the_display->composite_minor_version = 0; if (XCompositeQueryVersion (the_display->xdisplay, &the_display->composite_major_version, &the_display->composite_minor_version)) { the_display->have_composite = TRUE; } else { the_display->composite_major_version = 0; the_display->composite_minor_version = 0; } } meta_verbose ("Attempted to init Composite, found error base %d event base %d " "extn ver %d %d\n", the_display->composite_error_base, the_display->composite_event_base, the_display->composite_major_version, the_display->composite_minor_version); the_display->have_damage = FALSE; the_display->damage_error_base = 0; the_display->damage_event_base = 0; if (!XDamageQueryExtension (the_display->xdisplay, &the_display->damage_event_base, &the_display->damage_error_base)) { the_display->damage_error_base = 0; the_display->damage_event_base = 0; } else the_display->have_damage = TRUE; meta_verbose ("Attempted to init Damage, found error base %d event base %d\n", the_display->damage_error_base, the_display->damage_event_base); the_display->xfixes_error_base = 0; the_display->xfixes_event_base = 0; if (XFixesQueryExtension (the_display->xdisplay, &the_display->xfixes_event_base, &the_display->xfixes_error_base)) { int xfixes_major, xfixes_minor; XFixesQueryVersion (the_display->xdisplay, &xfixes_major, &xfixes_minor); if (xfixes_major * 100 + xfixes_minor < 500) meta_fatal ("Mutter requires XFixes 5.0"); } else { meta_fatal ("Mutter requires XFixes 5.0"); } meta_verbose ("Attempted to init XFixes, found error base %d event base %d\n", the_display->xfixes_error_base, the_display->xfixes_event_base); } { int major = 2, minor = 3; gboolean has_xi = FALSE; if (XQueryExtension (the_display->xdisplay, "XInputExtension", &the_display->xinput_opcode, &the_display->xinput_error_base, &the_display->xinput_event_base)) { if (XIQueryVersion (the_display->xdisplay, &major, &minor) == Success) { int version = (major * 10) + minor; if (version >= 22) has_xi = TRUE; #ifdef HAVE_XI23 if (version >= 23) the_display->have_xinput_23 = TRUE; #endif /* HAVE_XI23 */ } } if (!has_xi) meta_fatal ("X server doesn't have the XInput extension, version 2.2 or newer\n"); } { XcursorSetTheme (the_display->xdisplay, meta_prefs_get_cursor_theme ()); XcursorSetDefaultSize (the_display->xdisplay, meta_prefs_get_cursor_size ()); } /* Create the leader window here. Set its properties and * use the timestamp from one of the PropertyNotify events * that will follow. */ { gulong data[1]; XEvent event; /* We only care about the PropertyChangeMask in the next 30 or so lines of * code. Note that gdk will at some point unset the PropertyChangeMask for * this window, so we can't rely on it still being set later. See bug * 354213 for details. */ the_display->leader_window = meta_create_offscreen_window (the_display->xdisplay, DefaultRootWindow (the_display->xdisplay), PropertyChangeMask); meta_prop_set_utf8_string_hint (the_display, the_display->leader_window, the_display->atom__NET_WM_NAME, net_wm_name); meta_prop_set_utf8_string_hint (the_display, the_display->leader_window, the_display->atom__GNOME_WM_KEYBINDINGS, gnome_wm_keybindings); meta_prop_set_utf8_string_hint (the_display, the_display->leader_window, the_display->atom__MUTTER_VERSION, VERSION); data[0] = the_display->leader_window; XChangeProperty (the_display->xdisplay, the_display->leader_window, the_display->atom__NET_SUPPORTING_WM_CHECK, XA_WINDOW, 32, PropModeReplace, (guchar*) data, 1); XWindowEvent (the_display->xdisplay, the_display->leader_window, PropertyChangeMask, &event); timestamp = event.xproperty.time; /* Make it painfully clear that we can't rely on PropertyNotify events on * this window, as per bug 354213. */ XSelectInput(the_display->xdisplay, the_display->leader_window, NoEventMask); } /* Make a little window used only for pinging the server for timestamps; note * that meta_create_offscreen_window already selects for PropertyChangeMask. */ the_display->timestamp_pinging_window = meta_create_offscreen_window (the_display->xdisplay, DefaultRootWindow (the_display->xdisplay), PropertyChangeMask); the_display->last_focus_time = timestamp; the_display->last_user_time = timestamp; the_display->compositor = NULL; /* Mutter used to manage all X screens of the display in a single process, but * now it always manages exactly one screen as specified by the DISPLAY * environment variable. The screens GSList is left for simplicity. */ screens = NULL; i = meta_ui_get_screen_number (); screen = meta_screen_new (the_display, i, timestamp); if (screen) screens = g_slist_prepend (screens, screen); the_display->screens = screens; if (screens == NULL) { /* This would typically happen because all the screens already * have window managers. */ meta_display_close (the_display, timestamp); return FALSE; } enable_compositor (the_display); /* Now manage all existing windows */ tmp = the_display->screens; while (tmp != NULL) { MetaScreen *screen = tmp->data; if (meta_is_wayland_compositor ()) { /* Instead of explicitly enumerating all windows during * initialization, when we run as a wayland compositor we can rely on * xwayland notifying us of all top level windows so we create * MetaWindows when we get those notifications. * * We still want a guard window so we can avoid * unmapping/withdrawing minimized windows for live * thumbnails... */ if (screen->guard_window == None) screen->guard_window = meta_screen_create_guard_window (screen->display->xdisplay, screen); } else meta_screen_manage_all_windows (screen); tmp = tmp->next; } { Window focus; int ret_to; /* kinda bogus because GetInputFocus has no possible errors */ meta_error_trap_push (the_display); /* FIXME: This is totally broken; see comment 9 of bug 88194 about this */ focus = None; ret_to = RevertToPointerRoot; XGetInputFocus (the_display->xdisplay, &focus, &ret_to); /* Force a new FocusIn (does this work?) */ /* Use the same timestamp that was passed to meta_screen_new(), * as it is the most recent timestamp. */ if (focus == None || focus == PointerRoot) /* Just focus the no_focus_window on the first screen */ meta_display_focus_the_no_focus_window (the_display, the_display->screens->data, timestamp); else { MetaWindow * window; window = meta_display_lookup_x_window (the_display, focus); if (window) meta_display_set_input_focus_window (the_display, window, FALSE, timestamp); else /* Just focus the no_focus_window on the first screen */ meta_display_focus_the_no_focus_window (the_display, the_display->screens->data, timestamp); } meta_error_trap_pop (the_display); } meta_idle_monitor_init_dbus (); /* Done opening new display */ the_display->display_opening = FALSE; return TRUE; } static gint ptrcmp (gconstpointer a, gconstpointer b) { if (a < b) return -1; else if (a > b) return 1; else return 0; } /** * meta_display_list_windows: * @display: a #MetaDisplay * @flags: options for listing * * Lists windows for the display, the @flags parameter for * now determines whether override-redirect windows will be * included. * * Return value: (transfer container): the list of windows. */ GSList* meta_display_list_windows (MetaDisplay *display, MetaListWindowsFlags flags) { GSList *winlist; GSList *tmp; GSList *prev; GHashTableIter iter; gpointer key, value; winlist = NULL; g_hash_table_iter_init (&iter, display->xids); while (g_hash_table_iter_next (&iter, &key, &value)) { MetaWindow *window = value; if (!META_IS_WINDOW (window)) continue; if (!window->override_redirect || (flags & META_LIST_INCLUDE_OVERRIDE_REDIRECT) != 0) winlist = g_slist_prepend (winlist, window); } g_hash_table_iter_init (&iter, display->wayland_windows); while (g_hash_table_iter_next (&iter, &key, &value)) { MetaWindow *window = value; if (!META_IS_WINDOW (window)) continue; if (!window->override_redirect || (flags & META_LIST_INCLUDE_OVERRIDE_REDIRECT) != 0) winlist = g_slist_prepend (winlist, window); } /* Uniquify the list, since both frame windows and plain * windows are in the hash */ winlist = g_slist_sort (winlist, ptrcmp); prev = NULL; tmp = winlist; while (tmp != NULL) { GSList *next; next = tmp->next; if (next && next->data == tmp->data) { /* Delete tmp from list */ if (prev) prev->next = next; if (tmp == winlist) winlist = next; g_slist_free_1 (tmp); /* leave prev unchanged */ } else { prev = tmp; } tmp = next; } return winlist; } void meta_display_close (MetaDisplay *display, guint32 timestamp) { GSList *tmp; g_assert (display != NULL); if (display->closing != 0) { /* The display's already been closed. */ return; } if (display->error_traps > 0) meta_bug ("Display closed with error traps pending\n"); display->closing += 1; meta_prefs_remove_listener (prefs_changed_callback, display); meta_display_remove_autoraise_callback (display); if (display->focus_timeout_id) g_source_remove (display->focus_timeout_id); display->focus_timeout_id = 0; if (display->grab_old_window_stacking) g_list_free (display->grab_old_window_stacking); /* Stop caring about events */ meta_ui_remove_event_func (display->xdisplay, xevent_callback, display); clutter_event_remove_filter (display->clutter_event_filter); display->clutter_event_filter = 0; /* Free all screens */ tmp = display->screens; while (tmp != NULL) { MetaScreen *screen = tmp->data; meta_screen_free (screen, timestamp); tmp = tmp->next; } g_slist_free (display->screens); display->screens = NULL; #ifdef HAVE_STARTUP_NOTIFICATION if (display->sn_display) { sn_display_unref (display->sn_display); display->sn_display = NULL; } #endif /* Must be after all calls to meta_window_unmanage() since they * unregister windows */ g_hash_table_destroy (display->xids); if (display->leader_window != None) XDestroyWindow (display->xdisplay, display->leader_window); XFlush (display->xdisplay); meta_display_free_window_prop_hooks (display); meta_display_free_group_prop_hooks (display); g_free (display->name); meta_display_shutdown_keys (display); if (display->compositor) meta_compositor_destroy (display->compositor); g_object_unref (display); the_display = NULL; meta_quit (META_EXIT_SUCCESS); } /** * meta_display_screen_for_root: * @display: a #MetaDisplay * @xroot: a X window * * Return the #MetaScreen corresponding to a specified X root window ID. * * Return Value: (transfer none): the screen for the specified root window ID, or %NULL */ MetaScreen* meta_display_screen_for_root (MetaDisplay *display, Window xroot) { GSList *tmp; tmp = display->screens; while (tmp != NULL) { MetaScreen *screen = tmp->data; if (xroot == screen->xroot) return screen; tmp = tmp->next; } return NULL; } MetaScreen* meta_display_screen_for_xwindow (MetaDisplay *display, Window xwindow) { XWindowAttributes attr; int result; meta_error_trap_push (display); attr.screen = NULL; result = XGetWindowAttributes (display->xdisplay, xwindow, &attr); meta_error_trap_pop (display); /* Note, XGetWindowAttributes is on all kinds of crack * and returns 1 on success 0 on failure, rather than Success * on success. */ if (result == 0 || attr.screen == NULL) return NULL; return meta_display_screen_for_x_screen (display, attr.screen); } MetaScreen* meta_display_screen_for_x_screen (MetaDisplay *display, Screen *xscreen) { GSList *tmp; tmp = display->screens; while (tmp != NULL) { MetaScreen *screen = tmp->data; if (xscreen == screen->xscreen) return screen; tmp = tmp->next; } return NULL; } /* Grab/ungrab routines taken from fvwm. * Calling this function will cause X to ignore all other clients until * you ungrab. This may not be quite as bad as it sounds, yet there is * agreement that avoiding server grabs except when they are clearly needed * is a good thing. * * If you do use such grabs, please clearly explain the necessity for their * usage in a comment. Try to keep their scope extremely limited. In * particular, try to avoid emitting any signals or notifications while * a grab is active (if the signal receiver tries to block on an X request * from another client at this point, you will have a deadlock). */ void meta_display_grab (MetaDisplay *display) { if (display->server_grab_count == 0) { XGrabServer (display->xdisplay); } display->server_grab_count += 1; meta_verbose ("Grabbing display, grab count now %d\n", display->server_grab_count); } void meta_display_ungrab (MetaDisplay *display) { if (display->server_grab_count == 0) meta_bug ("Ungrabbed non-grabbed server\n"); display->server_grab_count -= 1; if (display->server_grab_count == 0) { /* FIXME we want to purge all pending "queued" stuff * at this point, such as window hide/show */ XUngrabServer (display->xdisplay); XFlush (display->xdisplay); } meta_verbose ("Ungrabbing display, grab count now %d\n", display->server_grab_count); } /** * meta_display_for_x_display: * @xdisplay: An X display * * Returns the singleton MetaDisplay if @xdisplay matches the X display it's * managing; otherwise gives a warning and returns %NULL. When we were claiming * to be able to manage multiple displays, this was supposed to find the * display out of the list which matched that display. Now it's merely an * extra sanity check. * * Returns: The singleton X display, or %NULL if @xdisplay isn't the one * we're managing. */ MetaDisplay* meta_display_for_x_display (Display *xdisplay) { if (the_display->xdisplay == xdisplay) return the_display; meta_warning ("Could not find display for X display %p, probably going to crash\n", xdisplay); return NULL; } /** * meta_get_display: * * Accessor for the singleton MetaDisplay. * * Returns: The only #MetaDisplay there is. This can be %NULL, but only * during startup. */ MetaDisplay* meta_get_display (void) { return the_display; } #ifdef WITH_VERBOSE_MODE static gboolean dump_events = TRUE; #endif static gboolean grab_op_is_mouse_only (MetaGrabOp op) { switch (op) { case META_GRAB_OP_MOVING: case META_GRAB_OP_RESIZING_SE: case META_GRAB_OP_RESIZING_S: case META_GRAB_OP_RESIZING_SW: case META_GRAB_OP_RESIZING_N: case META_GRAB_OP_RESIZING_NE: case META_GRAB_OP_RESIZING_NW: case META_GRAB_OP_RESIZING_W: case META_GRAB_OP_RESIZING_E: return TRUE; default: return FALSE; } } gboolean meta_grab_op_is_mouse (MetaGrabOp op) { switch (op) { case META_GRAB_OP_MOVING: case META_GRAB_OP_RESIZING_SE: case META_GRAB_OP_RESIZING_S: case META_GRAB_OP_RESIZING_SW: case META_GRAB_OP_RESIZING_N: case META_GRAB_OP_RESIZING_NE: case META_GRAB_OP_RESIZING_NW: case META_GRAB_OP_RESIZING_W: case META_GRAB_OP_RESIZING_E: case META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN: case META_GRAB_OP_KEYBOARD_RESIZING_S: case META_GRAB_OP_KEYBOARD_RESIZING_N: case META_GRAB_OP_KEYBOARD_RESIZING_W: case META_GRAB_OP_KEYBOARD_RESIZING_E: case META_GRAB_OP_KEYBOARD_RESIZING_SE: case META_GRAB_OP_KEYBOARD_RESIZING_NE: case META_GRAB_OP_KEYBOARD_RESIZING_SW: case META_GRAB_OP_KEYBOARD_RESIZING_NW: case META_GRAB_OP_KEYBOARD_MOVING: case META_GRAB_OP_COMPOSITOR: return TRUE; default: return FALSE; } } static gboolean grab_op_is_keyboard (MetaGrabOp op) { switch (op) { case META_GRAB_OP_KEYBOARD_MOVING: case META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN: case META_GRAB_OP_KEYBOARD_RESIZING_S: case META_GRAB_OP_KEYBOARD_RESIZING_N: case META_GRAB_OP_KEYBOARD_RESIZING_W: case META_GRAB_OP_KEYBOARD_RESIZING_E: case META_GRAB_OP_KEYBOARD_RESIZING_SE: case META_GRAB_OP_KEYBOARD_RESIZING_NE: case META_GRAB_OP_KEYBOARD_RESIZING_SW: case META_GRAB_OP_KEYBOARD_RESIZING_NW: case META_GRAB_OP_KEYBOARD_TABBING_NORMAL: case META_GRAB_OP_KEYBOARD_TABBING_DOCK: case META_GRAB_OP_KEYBOARD_TABBING_GROUP: case META_GRAB_OP_KEYBOARD_ESCAPING_NORMAL: case META_GRAB_OP_KEYBOARD_ESCAPING_DOCK: case META_GRAB_OP_KEYBOARD_ESCAPING_GROUP: case META_GRAB_OP_KEYBOARD_WORKSPACE_SWITCHING: case META_GRAB_OP_COMPOSITOR: return TRUE; default: return FALSE; } } gboolean meta_grab_op_is_resizing (MetaGrabOp op) { switch (op) { case META_GRAB_OP_RESIZING_SE: case META_GRAB_OP_RESIZING_S: case META_GRAB_OP_RESIZING_SW: case META_GRAB_OP_RESIZING_N: case META_GRAB_OP_RESIZING_NE: case META_GRAB_OP_RESIZING_NW: case META_GRAB_OP_RESIZING_W: case META_GRAB_OP_RESIZING_E: case META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN: case META_GRAB_OP_KEYBOARD_RESIZING_S: case META_GRAB_OP_KEYBOARD_RESIZING_N: case META_GRAB_OP_KEYBOARD_RESIZING_W: case META_GRAB_OP_KEYBOARD_RESIZING_E: case META_GRAB_OP_KEYBOARD_RESIZING_SE: case META_GRAB_OP_KEYBOARD_RESIZING_NE: case META_GRAB_OP_KEYBOARD_RESIZING_SW: case META_GRAB_OP_KEYBOARD_RESIZING_NW: return TRUE; default: return FALSE; } } gboolean meta_grab_op_is_moving (MetaGrabOp op) { switch (op) { case META_GRAB_OP_MOVING: case META_GRAB_OP_KEYBOARD_MOVING: return TRUE; default: return FALSE; } } /** * meta_display_xserver_time_is_before: * @display: a #MetaDisplay * @time1: An event timestamp * @time2: An event timestamp * * Xserver time can wraparound, thus comparing two timestamps needs to take * this into account. If no wraparound has occurred, this is equivalent to * time1 < time2 * Otherwise, we need to account for the fact that wraparound can occur * and the fact that a timestamp of 0 must be special-cased since it * means "older than anything else". * * Note that this is NOT an equivalent for time1 <= time2; if that's what * you need then you'll need to swap the order of the arguments and negate * the result. */ gboolean meta_display_xserver_time_is_before (MetaDisplay *display, guint32 time1, guint32 time2) { return XSERVER_TIME_IS_BEFORE(time1, time2); } /** * meta_display_get_last_user_time: * @display: a #MetaDisplay * * Returns: Timestamp of the last user interaction event with a window */ guint32 meta_display_get_last_user_time (MetaDisplay *display) { return display->last_user_time; } /* Get time of current event, or CurrentTime if none. */ guint32 meta_display_get_current_time (MetaDisplay *display) { return display->current_time; } static Bool find_timestamp_predicate (Display *xdisplay, XEvent *ev, XPointer arg) { MetaDisplay *display = (MetaDisplay *) arg; return (ev->type == PropertyNotify && ev->xproperty.atom == display->atom__MUTTER_TIMESTAMP_PING); } /* Get a timestamp, even if it means a roundtrip */ guint32 meta_display_get_current_time_roundtrip (MetaDisplay *display) { guint32 timestamp; timestamp = meta_display_get_current_time (display); if (timestamp == CurrentTime) { XEvent property_event; XChangeProperty (display->xdisplay, display->timestamp_pinging_window, display->atom__MUTTER_TIMESTAMP_PING, XA_STRING, 8, PropModeAppend, NULL, 0); XIfEvent (display->xdisplay, &property_event, find_timestamp_predicate, (XPointer) display); timestamp = property_event.xproperty.time; } sanity_check_timestamps (display, timestamp); return timestamp; } /** * meta_display_get_ignored_modifier_mask: * @display: a #MetaDisplay * * Returns: a mask of modifiers that should be ignored * when matching keybindings to events */ unsigned int meta_display_get_ignored_modifier_mask (MetaDisplay *display) { return display->ignored_modifier_mask; } /** * meta_display_add_ignored_crossing_serial: * @display: a #MetaDisplay * @serial: the serial to ignore * * Save the specified serial and ignore crossing events with that * serial for the purpose of focus-follows-mouse. This can be used * for certain changes to the window hierarchy that we don't want * to change the focus window, even if they cause the pointer to * end up in a new window. */ void meta_display_add_ignored_crossing_serial (MetaDisplay *display, unsigned long serial) { int i; /* don't add the same serial more than once */ if (display->ignored_crossing_serials[N_IGNORED_CROSSING_SERIALS-1] == serial) return; /* shift serials to the left */ i = 0; while (i < (N_IGNORED_CROSSING_SERIALS - 1)) { display->ignored_crossing_serials[i] = display->ignored_crossing_serials[i+1]; ++i; } /* put new one on the end */ display->ignored_crossing_serials[i] = serial; } static gboolean crossing_serial_is_ignored (MetaDisplay *display, unsigned long serial) { int i; i = 0; while (i < N_IGNORED_CROSSING_SERIALS) { if (display->ignored_crossing_serials[i] == serial) return TRUE; ++i; } return FALSE; } static gboolean window_raise_with_delay_callback (void *data) { MetaWindow *window = data; window->display->autoraise_timeout_id = 0; window->display->autoraise_window = NULL; /* If we aren't already on top, check whether the pointer is inside * the window and raise the window if so. */ if (meta_stack_get_top (window->screen->stack) != window) { int x, y, root_x, root_y; Window root, child; MetaRectangle frame_rect; unsigned int mask; gboolean same_screen; gboolean point_in_window; meta_error_trap_push (window->display); same_screen = XQueryPointer (window->display->xdisplay, window->xwindow, &root, &child, &root_x, &root_y, &x, &y, &mask); meta_error_trap_pop (window->display); meta_window_get_frame_rect (window, &frame_rect); point_in_window = POINT_IN_RECT (root_x, root_y, frame_rect); if (same_screen && point_in_window) meta_window_raise (window); else meta_topic (META_DEBUG_FOCUS, "Pointer not inside window, not raising %s\n", window->desc); } return FALSE; } void meta_display_queue_autoraise_callback (MetaDisplay *display, MetaWindow *window) { meta_topic (META_DEBUG_FOCUS, "Queuing an autoraise timeout for %s with delay %d\n", window->desc, meta_prefs_get_auto_raise_delay ()); if (display->autoraise_timeout_id != 0) g_source_remove (display->autoraise_timeout_id); display->autoraise_timeout_id = g_timeout_add_full (G_PRIORITY_DEFAULT, meta_prefs_get_auto_raise_delay (), window_raise_with_delay_callback, window, NULL); display->autoraise_window = window; } #if 0 static void handle_net_restack_window (MetaDisplay* display, XEvent *event) { MetaWindow *window; window = meta_display_lookup_x_window (display, event->xclient.window); if (window) { /* FIXME: The EWMH includes a sibling for the restack request, but we * (stupidly) don't currently support these types of raises. * * Also, unconditionally following these is REALLY stupid--we should * combine this code with the stuff in * meta_window_configure_request() which is smart about whether to * follow the request or do something else (though not smart enough * and is also too stupid to handle the sibling stuff). */ switch (event->xclient.data.l[2]) { case Above: meta_window_raise (window); break; case Below: meta_window_lower (window); break; case TopIf: case BottomIf: case Opposite: break; } } } #endif static MetaWindow * get_window_for_event (MetaDisplay *display, const ClutterEvent *event) { ClutterActor *source; if (display->grab_op != META_GRAB_OP_NONE) return display->grab_window; source = clutter_event_get_source (event); if (META_IS_WINDOW_ACTOR (source)) return meta_window_actor_get_meta_window (META_WINDOW_ACTOR (source)); return NULL; } static XIEvent * get_input_event (MetaDisplay *display, XEvent *event) { if (event->type == GenericEvent && event->xcookie.extension == display->xinput_opcode) { XIEvent *input_event; /* NB: GDK event filters already have generic events * allocated, so no need to do XGetEventData() on our own */ input_event = (XIEvent *) event->xcookie.data; switch (input_event->evtype) { case XI_Motion: case XI_ButtonPress: case XI_ButtonRelease: if (((XIDeviceEvent *) input_event)->deviceid == META_VIRTUAL_CORE_POINTER_ID) return input_event; break; case XI_KeyPress: case XI_KeyRelease: if (((XIDeviceEvent *) input_event)->deviceid == META_VIRTUAL_CORE_KEYBOARD_ID) return input_event; break; case XI_FocusIn: case XI_FocusOut: if (((XIEnterEvent *) input_event)->deviceid == META_VIRTUAL_CORE_KEYBOARD_ID) return input_event; break; case XI_Enter: case XI_Leave: if (((XIEnterEvent *) input_event)->deviceid == META_VIRTUAL_CORE_POINTER_ID) return input_event; break; #ifdef HAVE_XI23 case XI_BarrierHit: case XI_BarrierLeave: if (((XIBarrierEvent *) input_event)->deviceid == META_VIRTUAL_CORE_POINTER_ID) return input_event; break; #endif /* HAVE_XI23 */ default: break; } } return NULL; } static void update_focus_window (MetaDisplay *display, MetaFocusType type, MetaWindow *window, Window xwindow, gulong serial, gboolean focused_by_us) { MetaWaylandCompositor *compositor; display->focus_serial = serial; display->focused_by_us = focused_by_us; if (display->focus_xwindow == xwindow && display->focus_type == type && display->focus_window == window) return; if (display->focus_window) { MetaWindow *previous; meta_topic (META_DEBUG_FOCUS, "%s is now the previous focus window due to being focused out or unmapped\n", display->focus_window->desc); /* Make sure that signals handlers invoked by * meta_window_set_focused_internal() don't see * display->focus_window->has_focus == FALSE */ previous = display->focus_window; display->focus_window = NULL; display->focus_xwindow = None; meta_window_set_focused_internal (previous, FALSE); } display->focus_type = type; display->focus_window = window; display->focus_xwindow = xwindow; if (display->focus_window) { ClutterActor *window_actor; meta_topic (META_DEBUG_FOCUS, "* Focus --> %s with serial %lu\n", display->focus_window->desc, serial); meta_window_set_focused_internal (display->focus_window, TRUE); /* XXX -- this is sort of a layer violation, but because we * rely on the compositor for event delivery anyway, I don't * think it's too bad... */ window_actor = CLUTTER_ACTOR (display->focus_window->compositor_private); if (window_actor) clutter_actor_grab_key_focus (window_actor); } else meta_topic (META_DEBUG_FOCUS, "* Focus --> NULL with serial %lu\n", serial); if (meta_is_wayland_compositor ()) { compositor = meta_wayland_compositor_get_default (); if (display->focus_type == META_FOCUS_NO_FOCUS_WINDOW || display->focus_type == META_FOCUS_STAGE) meta_wayland_compositor_set_input_focus (compositor, NULL); else if (window && window->surface) meta_wayland_compositor_set_input_focus (compositor, window); else meta_topic (META_DEBUG_FOCUS, "Focus change has no effect, because there is no matching wayland surface"); } g_object_notify (G_OBJECT (display), "focus-window"); meta_display_update_active_window_hint (display); } static gboolean timestamp_too_old (MetaDisplay *display, guint32 *timestamp) { /* FIXME: If Soeren's suggestion in bug 151984 is implemented, it will allow * us to sanity check the timestamp here and ensure it doesn't correspond to * a future time (though we would want to rename to * timestamp_too_old_or_in_future). */ if (*timestamp == CurrentTime) { *timestamp = meta_display_get_current_time_roundtrip (display); return FALSE; } else if (XSERVER_TIME_IS_BEFORE (*timestamp, display->last_focus_time)) { if (XSERVER_TIME_IS_BEFORE (*timestamp, display->last_user_time)) return TRUE; else { *timestamp = display->last_focus_time; return FALSE; } } return FALSE; } static void request_xserver_input_focus_change (MetaDisplay *display, MetaScreen *screen, MetaFocusType type, MetaWindow *meta_window, Window xwindow, guint32 timestamp) { gulong serial; if (timestamp_too_old (display, ×tamp)) return; meta_error_trap_push (display); /* In order for mutter to know that the focus request succeeded, we track * the serial of the "focus request" we made, but if we take the serial * of the XSetInputFocus request, then there's no way to determine the * difference between focus events as a result of the SetInputFocus and * focus events that other clients send around the same time. Ensure that * we know which is which by making two requests that the server will * process at the same time. */ meta_display_grab (display); serial = XNextRequest (display->xdisplay); XSetInputFocus (display->xdisplay, xwindow, RevertToPointerRoot, timestamp); XChangeProperty (display->xdisplay, display->timestamp_pinging_window, display->atom__MUTTER_FOCUS_SET, XA_STRING, 8, PropModeAppend, NULL, 0); meta_display_ungrab (display); update_focus_window (display, type, meta_window, xwindow, serial, TRUE); meta_error_trap_pop (display); display->last_focus_time = timestamp; display->active_screen = screen; if (meta_window == NULL || meta_window != display->autoraise_window) meta_display_remove_autoraise_callback (display); } static void handle_window_focus_event (MetaDisplay *display, MetaWindow *window, XIEnterEvent *event, unsigned long serial) { MetaWindow *focus_window; MetaFocusType type; #ifdef WITH_VERBOSE_MODE const char *window_type; type = META_FOCUS_NONE; /* Note the event can be on either the window or the frame, * we focus the frame for shaded windows */ if (window) { if (event->event == window->xwindow) window_type = "client window"; else if (window->frame && event->event == window->frame->xwindow) window_type = "frame window"; else window_type = "unknown client window"; if (window->client_type == META_WINDOW_CLIENT_TYPE_WAYLAND) type = META_FOCUS_WAYLAND_CLIENT; else type = META_FOCUS_X_CLIENT; } else if (meta_display_xwindow_is_a_no_focus_window (display, event->event)) { window_type = "no_focus_window"; type = META_FOCUS_NO_FOCUS_WINDOW; } else if (meta_display_screen_for_root (display, event->event)) window_type = "root window"; else window_type = "unknown window"; /* Don't change type if we don't know the new window */ if (type == META_FOCUS_NONE) type = display->focus_type; meta_topic (META_DEBUG_FOCUS, "Focus %s event received on %s 0x%lx (%s) " "mode %s detail %s serial %lu\n", event->evtype == XI_FocusIn ? "in" : event->evtype == XI_FocusOut ? "out" : "???", window ? window->desc : "", event->event, window_type, meta_event_mode_to_string (event->mode), meta_event_detail_to_string (event->mode), event->serial); #endif /* FIXME our pointer tracking is broken; see how * gtk+/gdk/x11/gdkevents-x11.c or XFree86/xc/programs/xterm/misc.c * for how to handle it the correct way. In brief you need to track * pointer focus and regular focus, and handle EnterNotify in * PointerRoot mode with no window manager. However as noted above, * accurate focus tracking will break things because we want to keep * windows "focused" when using keybindings on them, and also we * sometimes "focus" a window by focusing its frame or * no_focus_window; so this all needs rethinking massively. * * My suggestion is to change it so that we clearly separate * actual keyboard focus tracking using the xterm algorithm, * and mutter's "pretend" focus window, and go through all * the code and decide which one should be used in each place; * a hard bit is deciding on a policy for that. * * http://bugzilla.gnome.org/show_bug.cgi?id=90382 */ /* We ignore grabs, though this is questionable. It may be better to * increase the intelligence of the focus window tracking. * * The problem is that keybindings for windows are done with * XGrabKey, which means focus_window disappears and the front of * the MRU list gets confused from what the user expects once a * keybinding is used. */ if (event->mode == XINotifyGrab || event->mode == XINotifyUngrab || /* From WindowMaker, ignore all funky pointer root events */ event->detail > XINotifyNonlinearVirtual) { meta_topic (META_DEBUG_FOCUS, "Ignoring focus event generated by a grab or other weirdness\n"); return; } if (event->evtype == XI_FocusIn) { display->server_focus_window = event->event; display->server_focus_serial = serial; focus_window = window; } else if (event->evtype == XI_FocusOut) { if (event->detail == XINotifyInferior) { /* This event means the client moved focus to a subwindow */ meta_topic (META_DEBUG_FOCUS, "Ignoring focus out with NotifyInferior\n"); return; } display->server_focus_window = None; display->server_focus_serial = serial; focus_window = NULL; } else g_return_if_reached (); /* If display->focused_by_us, then the focus_serial will be used only * for a focus change we made and have already accounted for. * (See request_xserver_input_focus_change().) Otherwise, we can get * multiple focus events with the same serial. */ if (display->server_focus_serial > display->focus_serial || (!display->focused_by_us && display->server_focus_serial == display->focus_serial)) { update_focus_window (display, type, focus_window, focus_window ? focus_window->xwindow : None, display->server_focus_serial, FALSE); } } static gboolean window_has_xwindow (MetaWindow *window, Window xwindow) { if (window->xwindow == xwindow) return TRUE; if (window->frame && window->frame->xwindow == xwindow) return TRUE; return FALSE; } static gboolean meta_display_handle_event (MetaDisplay *display, const ClutterEvent *event) { MetaWindow *window; /* XXX -- we need to fill this in properly at some point... */ gboolean frame_was_receiver = FALSE; #ifdef HAVE_WAYLAND MetaWaylandCompositor *compositor = NULL; if (meta_is_wayland_compositor ()) { compositor = meta_wayland_compositor_get_default (); meta_wayland_compositor_update (compositor, event); } #endif /* HAVE_WAYLAND */ window = get_window_for_event (display, event); display->current_time = event->any.time; if (window && !window->override_redirect && (event->type == CLUTTER_KEY_PRESS || event->type == CLUTTER_BUTTON_PRESS)) { if (CurrentTime == display->current_time) { /* We can't use missing (i.e. invalid) timestamps to set user time, * nor do we want to use them to sanity check other timestamps. * See bug 313490 for more details. */ meta_warning ("Event has no timestamp! You may be using a broken " "program such as xse. Please ask the authors of that " "program to fix it.\n"); } else { meta_window_set_user_time (window, display->current_time); sanity_check_timestamps (display, display->current_time); } } switch (event->type) { case CLUTTER_BUTTON_PRESS: if (display->grab_op == META_GRAB_OP_COMPOSITOR) break; display->overlay_key_only_pressed = FALSE; if ((window && meta_grab_op_is_mouse (display->grab_op) && (event->button.modifier_state & display->window_grab_modifiers) && display->grab_button != (int) event->button.button && display->grab_window == window) || grab_op_is_keyboard (display->grab_op)) { meta_topic (META_DEBUG_WINDOW_OPS, "Ending grab op %u on window %s due to button press\n", display->grab_op, (display->grab_window ? display->grab_window->desc : "none")); if (GRAB_OP_IS_WINDOW_SWITCH (display->grab_op)) { meta_topic (META_DEBUG_WINDOW_OPS, "Syncing to old stack positions.\n"); /* XXX: I'm not sure if this is the right thing to do. The pre-Wayland code was only calling meta_stack_set_positions if the modified window was a root window */ if (event->any.source == CLUTTER_ACTOR (event->any.stage) && window && window->screen) meta_stack_set_positions (window->screen->stack, display->grab_old_window_stacking); } meta_display_end_grab_op (display, event->any.time); } else if (window && display->grab_op == META_GRAB_OP_NONE) { gboolean begin_move = FALSE; ClutterModifierType grab_mask; gboolean unmodified; grab_mask = display->window_grab_modifiers; if (g_getenv ("MUTTER_DEBUG_BUTTON_GRABS")) grab_mask |= CLUTTER_CONTROL_MASK; /* Two possible sources of an unmodified event; one is a * client that's letting button presses pass through to the * frame, the other is our focus_window_grab on unmodified * button 1. So for all such events we focus the window. */ unmodified = (event->button.modifier_state & grab_mask) == 0; if (unmodified || event->button.button == 1) { /* don't focus if frame received, will be lowered in * frames.c or special-cased if the click was on a * minimize/close button. */ if (!frame_was_receiver) { if (meta_prefs_get_raise_on_click ()) meta_window_raise (window); else meta_topic (META_DEBUG_FOCUS, "Not raising window on click due to don't-raise-on-click option\n"); /* Don't focus panels--they must explicitly request focus. * See bug 160470 */ if (window->type != META_WINDOW_DOCK) { meta_topic (META_DEBUG_FOCUS, "Focusing %s due to unmodified button %u press (display.c)\n", window->desc, event->button.button); meta_window_focus (window, event->any.time); } else /* However, do allow terminals to lose focus due to new * window mappings after the user clicks on a panel. */ display->allow_terminal_deactivation = TRUE; } /* you can move on alt-click but not on * the click-to-focus */ if (!unmodified) begin_move = TRUE; } else if (!unmodified && ((int) event->button.button == meta_prefs_get_mouse_button_resize ())) { if (window->has_resize_func) { gboolean north, south; gboolean west, east; MetaRectangle frame_rect; MetaGrabOp op; meta_window_get_frame_rect (window, &frame_rect); west = event->button.x < (frame_rect.x + 1 * frame_rect.width / 3); east = event->button.x > (frame_rect.x + 2 * frame_rect.width / 3); north = event->button.y < (frame_rect.y + 1 * frame_rect.height / 3); south = event->button.y > (frame_rect.y + 2 * frame_rect.height / 3); if (north && west) op = META_GRAB_OP_RESIZING_NW; else if (north && east) op = META_GRAB_OP_RESIZING_NE; else if (south && west) op = META_GRAB_OP_RESIZING_SW; else if (south && east) op = META_GRAB_OP_RESIZING_SE; else if (north) op = META_GRAB_OP_RESIZING_N; else if (west) op = META_GRAB_OP_RESIZING_W; else if (east) op = META_GRAB_OP_RESIZING_E; else if (south) op = META_GRAB_OP_RESIZING_S; else /* Middle region is no-op to avoid user triggering wrong action */ op = META_GRAB_OP_NONE; if (op != META_GRAB_OP_NONE) meta_display_begin_grab_op (display, window->screen, window, op, TRUE, FALSE, event->button.button, 0, event->any.time, event->button.x, event->button.y); } } else if ((int) event->button.button == meta_prefs_get_mouse_button_menu ()) { if (meta_prefs_get_raise_on_click ()) meta_window_raise (window); meta_window_show_menu (window, event->button.x, event->button.y, event->button.button, event->any.time); } if (!frame_was_receiver && unmodified) { /* This is from our synchronous grab since * it has no modifiers and was on the client window */ meta_verbose ("Allowing events time %u\n", (unsigned int) event->any.time); /* XXX -- implement this in Wayland */ XIAllowEvents (display->xdisplay, META_VIRTUAL_CORE_POINTER_ID, XIReplayDevice, event->any.time); } if (begin_move && window->has_move_func) { meta_display_begin_grab_op (display, window->screen, window, META_GRAB_OP_MOVING, TRUE, FALSE, event->button.button, 0, event->any.time, event->button.x, event->button.y); } } break; case CLUTTER_BUTTON_RELEASE: if (display->grab_op == META_GRAB_OP_COMPOSITOR) break; display->overlay_key_only_pressed = FALSE; if (display->grab_window == window && meta_grab_op_is_mouse (display->grab_op)) meta_window_handle_mouse_grab_op_event (window, event); break; case CLUTTER_MOTION: if (display->grab_op == META_GRAB_OP_COMPOSITOR) break; if (display->grab_window == window && meta_grab_op_is_mouse (display->grab_op)) meta_window_handle_mouse_grab_op_event (window, event); break; case CLUTTER_KEY_PRESS: case CLUTTER_KEY_RELEASE: /* For key events, it's important to enforce single-handling, or * we can get into a confused state. So if a keybinding is * handled (because it's one of our hot-keys, or because we are * in a keyboard-grabbed mode like moving a window, we don't * want to pass the key event to the compositor or Wayland at all. */ if (meta_display_process_key_event (display, window, (ClutterKeyEvent *) event)) return TRUE; default: break; } #ifdef HAVE_WAYLAND if (compositor && (display->grab_op == META_GRAB_OP_NONE)) { if (meta_wayland_compositor_handle_event (compositor, event)) return TRUE; } #endif /* HAVE_WAYLAND */ return (display->grab_op != META_GRAB_OP_NONE && display->grab_op != META_GRAB_OP_COMPOSITOR); } static gboolean handle_input_xevent (MetaDisplay *display, XIEvent *input_event, gulong serial) { XIDeviceEvent *device_event = (XIDeviceEvent *) input_event; XIEnterEvent *enter_event = (XIEnterEvent *) input_event; Window modified; MetaWindow *window; gboolean frame_was_receiver; if (input_event == NULL) return FALSE; modified = xievent_get_modified_window (display, input_event); window = modified != None ? meta_display_lookup_x_window (display, modified) : NULL; frame_was_receiver = FALSE; if (window && window->frame && modified == window->frame->xwindow) { /* Note that if the frame and the client both have an * XGrabButton (as is normal with our setup), the event * goes to the frame. */ frame_was_receiver = TRUE; meta_topic (META_DEBUG_EVENTS, "Frame was receiver of event for %s\n", window->desc); } if (window && !window->override_redirect && (input_event->evtype == XI_KeyPress || input_event->evtype == XI_ButtonPress)) { if (CurrentTime == display->current_time) { /* We can't use missing (i.e. invalid) timestamps to set user time, * nor do we want to use them to sanity check other timestamps. * See bug 313490 for more details. */ meta_warning ("Event has no timestamp! You may be using a broken " "program such as xse. Please ask the authors of that " "program to fix it.\n"); } else { meta_window_set_user_time (window, display->current_time); sanity_check_timestamps (display, display->current_time); } } switch (input_event->evtype) { case XI_ButtonPress: if (display->grab_op == META_GRAB_OP_COMPOSITOR) break; display->overlay_key_only_pressed = FALSE; if (device_event->detail == 4 || device_event->detail == 5) /* Scrollwheel event, do nothing and deliver event to compositor below */ break; if ((window && meta_grab_op_is_mouse (display->grab_op) && (device_event->mods.effective & display->window_grab_modifiers) && display->grab_button != device_event->detail && display->grab_window == window) || grab_op_is_keyboard (display->grab_op)) { meta_topic (META_DEBUG_WINDOW_OPS, "Ending grab op %u on window %s due to button press\n", display->grab_op, (display->grab_window ? display->grab_window->desc : "none")); if (GRAB_OP_IS_WINDOW_SWITCH (display->grab_op)) { MetaScreen *screen; meta_topic (META_DEBUG_WINDOW_OPS, "Syncing to old stack positions.\n"); screen = meta_display_screen_for_root (display, device_event->event); if (screen!=NULL) meta_stack_set_positions (screen->stack, display->grab_old_window_stacking); } meta_display_end_grab_op (display, device_event->time); } else if (window && display->grab_op == META_GRAB_OP_NONE) { gboolean begin_move = FALSE; unsigned int grab_mask; gboolean unmodified; grab_mask = display->window_grab_modifiers; if (g_getenv ("MUTTER_DEBUG_BUTTON_GRABS")) grab_mask |= ControlMask; /* Two possible sources of an unmodified event; one is a * client that's letting button presses pass through to the * frame, the other is our focus_window_grab on unmodified * button 1. So for all such events we focus the window. */ unmodified = (device_event->mods.effective & grab_mask) == 0; if (unmodified || device_event->detail == 1) { /* don't focus if frame received, will be lowered in * frames.c or special-cased if the click was on a * minimize/close button. */ if (!frame_was_receiver) { if (meta_prefs_get_raise_on_click ()) meta_window_raise (window); else meta_topic (META_DEBUG_FOCUS, "Not raising window on click due to don't-raise-on-click option\n"); /* Don't focus panels--they must explicitly request focus. * See bug 160470 */ if (window->type != META_WINDOW_DOCK) { meta_topic (META_DEBUG_FOCUS, "Focusing %s due to unmodified button %u press (display.c)\n", window->desc, device_event->detail); meta_window_focus (window, device_event->time); } else /* However, do allow terminals to lose focus due to new * window mappings after the user clicks on a panel. */ display->allow_terminal_deactivation = TRUE; } /* you can move on alt-click but not on * the click-to-focus */ if (!unmodified) begin_move = TRUE; } else if (!unmodified && device_event->detail == meta_prefs_get_mouse_button_resize()) { if (window->has_resize_func) { gboolean north, south; gboolean west, east; MetaRectangle frame_rect; MetaGrabOp op; meta_window_get_frame_rect (window, &frame_rect); west = device_event->root_x < (frame_rect.x + 1 * frame_rect.width / 3); east = device_event->root_x > (frame_rect.x + 2 * frame_rect.width / 3); north = device_event->root_y < (frame_rect.y + 1 * frame_rect.height / 3); south = device_event->root_y > (frame_rect.y + 2 * frame_rect.height / 3); if (north && west) op = META_GRAB_OP_RESIZING_NW; else if (north && east) op = META_GRAB_OP_RESIZING_NE; else if (south && west) op = META_GRAB_OP_RESIZING_SW; else if (south && east) op = META_GRAB_OP_RESIZING_SE; else if (north) op = META_GRAB_OP_RESIZING_N; else if (west) op = META_GRAB_OP_RESIZING_W; else if (east) op = META_GRAB_OP_RESIZING_E; else if (south) op = META_GRAB_OP_RESIZING_S; else /* Middle region is no-op to avoid user triggering wrong action */ op = META_GRAB_OP_NONE; if (op != META_GRAB_OP_NONE) meta_display_begin_grab_op (display, window->screen, window, op, TRUE, FALSE, device_event->detail, 0, device_event->time, device_event->root_x, device_event->root_y); } } else if (device_event->detail == meta_prefs_get_mouse_button_menu()) { if (meta_prefs_get_raise_on_click ()) meta_window_raise (window); meta_window_show_menu (window, device_event->root_x, device_event->root_y, device_event->detail, device_event->time); } if (!frame_was_receiver && unmodified) { /* This is from our synchronous grab since * it has no modifiers and was on the client window */ meta_verbose ("Allowing events time %u\n", (unsigned int)device_event->time); XIAllowEvents (display->xdisplay, device_event->deviceid, XIReplayDevice, device_event->time); } if (begin_move && window->has_move_func) { meta_display_begin_grab_op (display, window->screen, window, META_GRAB_OP_MOVING, TRUE, FALSE, device_event->detail, 0, device_event->time, device_event->root_x, device_event->root_y); } } break; case XI_ButtonRelease: if (display->grab_op == META_GRAB_OP_COMPOSITOR) break; display->overlay_key_only_pressed = FALSE; if (display->grab_window == window && meta_grab_op_is_mouse (display->grab_op)) meta_window_handle_mouse_grab_op_xevent (window, device_event); break; case XI_Motion: if (display->grab_op == META_GRAB_OP_COMPOSITOR) break; if (display->grab_window == window && meta_grab_op_is_mouse (display->grab_op)) meta_window_handle_mouse_grab_op_xevent (window, device_event); break; case XI_Enter: if (display->grab_op == META_GRAB_OP_COMPOSITOR) break; /* If the mouse switches screens, active the default window on the new * screen; this will make keybindings and workspace-launched items * actually appear on the right screen. */ { MetaScreen *new_screen = meta_display_screen_for_root (display, enter_event->root); if (new_screen != NULL && display->active_screen != new_screen) meta_workspace_focus_default_window (new_screen->active_workspace, NULL, enter_event->time); } /* Check if we've entered a window; do this even if window->has_focus to * avoid races. */ if (window && !crossing_serial_is_ignored (display, serial) && enter_event->mode != XINotifyGrab && enter_event->mode != XINotifyUngrab && enter_event->detail != XINotifyInferior && meta_display_focus_sentinel_clear (display)) { meta_window_handle_enter (window, enter_event->time, enter_event->root_x, enter_event->root_y); if (window->type == META_WINDOW_DOCK) meta_window_raise (window); } break; case XI_Leave: if (display->grab_op == META_GRAB_OP_COMPOSITOR) break; if (window != NULL) { if (window->type == META_WINDOW_DOCK && enter_event->mode != XINotifyGrab && enter_event->mode != XINotifyUngrab && !window->has_focus) meta_window_lower (window); } break; case XI_FocusIn: case XI_FocusOut: handle_window_focus_event (display, window, enter_event, serial); if (!window) { /* Check if the window is a root window. */ MetaScreen *screen = meta_display_screen_for_root(display, enter_event->event); if (screen == NULL) break; if (enter_event->evtype == XI_FocusIn && enter_event->mode == XINotifyDetailNone) { meta_topic (META_DEBUG_FOCUS, "Focus got set to None, probably due to " "brain-damage in the X protocol (see bug " "125492). Setting the default focus window.\n"); meta_workspace_focus_default_window (screen->active_workspace, NULL, meta_display_get_current_time_roundtrip (display)); } else if (enter_event->evtype == XI_FocusIn && enter_event->mode == XINotifyNormal && enter_event->detail == XINotifyInferior) { meta_topic (META_DEBUG_FOCUS, "Focus got set to root window, probably due to " "gnome-session logout dialog usage (see bug " "153220). Setting the default focus window.\n"); meta_workspace_focus_default_window (screen->active_workspace, NULL, meta_display_get_current_time_roundtrip (display)); } } break; } return FALSE; } static void reload_xkb_rules (MetaScreen *screen) { MetaWaylandCompositor *compositor; char **names; int n_names; gboolean ok; const char *rules, *model, *layout, *variant, *options; compositor = meta_wayland_compositor_get_default (); ok = meta_prop_get_latin1_list (screen->display, screen->xroot, screen->display->atom__XKB_RULES_NAMES, &names, &n_names); if (!ok) return; if (n_names != 5) goto out; rules = names[0]; model = names[1]; layout = names[2]; variant = names[3]; options = names[4]; meta_wayland_keyboard_set_keymap_names (&compositor->seat->keyboard, rules, model, layout, variant, options, META_WAYLAND_KEYBOARD_SKIP_XCLIENTS); out: g_strfreev (names); } static gboolean handle_other_xevent (MetaDisplay *display, XEvent *event) { Window modified; MetaWindow *window; MetaWindow *property_for_window; gboolean frame_was_receiver; gboolean bypass_gtk = FALSE; modified = event_get_modified_window (display, event); window = modified != None ? meta_display_lookup_x_window (display, modified) : NULL; frame_was_receiver = (window && window->frame && modified == window->frame->xwindow); /* We only want to respond to _NET_WM_USER_TIME property notify * events on _NET_WM_USER_TIME_WINDOW windows; in particular, * responding to UnmapNotify events is kind of bad. */ property_for_window = NULL; if (window && modified == window->user_time_window) { property_for_window = window; window = NULL; } #ifdef HAVE_XSYNC if (META_DISPLAY_HAS_XSYNC (display) && event->type == (display->xsync_event_base + XSyncAlarmNotify)) { MetaWindow *alarm_window = meta_display_lookup_sync_alarm (display, ((XSyncAlarmNotifyEvent*)event)->alarm); if (alarm_window != NULL) { XSyncValue value = ((XSyncAlarmNotifyEvent*)event)->counter_value; gint64 new_counter_value; new_counter_value = XSyncValueLow32 (value) + ((gint64)XSyncValueHigh32 (value) << 32); meta_window_update_sync_request_counter (alarm_window, new_counter_value); bypass_gtk = TRUE; /* GTK doesn't want to see this really */ } else meta_idle_monitor_handle_xevent_all (event); goto out; } #endif /* HAVE_XSYNC */ #ifdef HAVE_SHAPE if (META_DISPLAY_HAS_SHAPE (display) && event->type == (display->shape_event_base + ShapeNotify)) { bypass_gtk = TRUE; /* GTK doesn't want to see this really */ if (window && !frame_was_receiver) { XShapeEvent *sev = (XShapeEvent*) event; if (sev->kind == ShapeBounding) meta_window_update_shape_region_x11 (window); else if (sev->kind == ShapeInput) meta_window_update_input_region_x11 (window); } else { meta_topic (META_DEBUG_SHAPES, "ShapeNotify not on a client window (window %s frame_was_receiver = %d)\n", window ? window->desc : "(none)", frame_was_receiver); } goto out; } #endif /* HAVE_SHAPE */ switch (event->type) { case KeymapNotify: break; case Expose: break; case GraphicsExpose: break; case NoExpose: break; case VisibilityNotify: break; case CreateNotify: { MetaScreen *screen; screen = meta_display_screen_for_root (display, event->xcreatewindow.parent); if (screen) meta_stack_tracker_create_event (screen->stack_tracker, &event->xcreatewindow); } break; case DestroyNotify: { MetaScreen *screen; screen = meta_display_screen_for_root (display, event->xdestroywindow.event); if (screen) meta_stack_tracker_destroy_event (screen->stack_tracker, &event->xdestroywindow); } if (window) { /* FIXME: It sucks that DestroyNotify events don't come with * a timestamp; could we do something better here? Maybe X * will change one day? */ guint32 timestamp; timestamp = meta_display_get_current_time_roundtrip (display); if (display->grab_op != META_GRAB_OP_NONE && display->grab_window == window) meta_display_end_grab_op (display, timestamp); if (frame_was_receiver) { meta_warning ("Unexpected destruction of frame 0x%lx, not sure if this should silently fail or be considered a bug\n", window->frame->xwindow); meta_error_trap_push (display); meta_window_destroy_frame (window->frame->window); meta_error_trap_pop (display); } else { /* Unmanage destroyed window */ meta_window_unmanage (window, timestamp); window = NULL; } } break; case UnmapNotify: if (window) { /* FIXME: It sucks that UnmapNotify events don't come with * a timestamp; could we do something better here? Maybe X * will change one day? */ guint32 timestamp; timestamp = meta_display_get_current_time_roundtrip (display); if (display->grab_op != META_GRAB_OP_NONE && display->grab_window == window && ((window->frame == NULL) || !window->frame->mapped)) meta_display_end_grab_op (display, timestamp); if (!frame_was_receiver) { if (window->unmaps_pending == 0) { meta_topic (META_DEBUG_WINDOW_STATE, "Window %s withdrawn\n", window->desc); /* Unmanage withdrawn window */ window->withdrawn = TRUE; meta_window_unmanage (window, timestamp); window = NULL; } else { window->unmaps_pending -= 1; meta_topic (META_DEBUG_WINDOW_STATE, "Received pending unmap, %d now pending\n", window->unmaps_pending); } } } break; case MapNotify: /* NB: override redirect windows wont cause a map request so we * watch out for map notifies against any root windows too if a * compositor is enabled: */ if (display->compositor && window == NULL && meta_display_screen_for_root (display, event->xmap.event)) { window = meta_window_new (display, event->xmap.window, FALSE, META_COMP_EFFECT_CREATE); } break; case MapRequest: if (window == NULL) { window = meta_window_new (display, event->xmaprequest.window, FALSE, META_COMP_EFFECT_CREATE); } /* if frame was receiver it's some malicious send event or something */ else if (!frame_was_receiver && window) { meta_verbose ("MapRequest on %s mapped = %d minimized = %d\n", window->desc, window->mapped, window->minimized); if (window->minimized) { meta_window_unminimize (window); if (window->workspace != window->screen->active_workspace) { meta_verbose ("Changing workspace due to MapRequest mapped = %d minimized = %d\n", window->mapped, window->minimized); meta_window_change_workspace (window, window->screen->active_workspace); } } } break; case ReparentNotify: { MetaScreen *screen; screen = meta_display_screen_for_root (display, event->xconfigure.event); if (screen) meta_stack_tracker_reparent_event (screen->stack_tracker, &event->xreparent); } break; case ConfigureNotify: if (event->xconfigure.event != event->xconfigure.window) { MetaScreen *screen; screen = meta_display_screen_for_root (display, event->xconfigure.event); if (screen) meta_stack_tracker_configure_event (screen->stack_tracker, &event->xconfigure); } if (window && window->override_redirect) meta_window_configure_notify (window, &event->xconfigure); break; case ConfigureRequest: /* This comment and code is found in both twm and fvwm */ /* * According to the July 27, 1988 ICCCM draft, we should ignore size and * position fields in the WM_NORMAL_HINTS property when we map a window. * Instead, we'll read the current geometry. Therefore, we should respond * to configuration requests for windows which have never been mapped. */ if (window == NULL) { unsigned int xwcm; XWindowChanges xwc; xwcm = event->xconfigurerequest.value_mask & (CWX | CWY | CWWidth | CWHeight | CWBorderWidth); xwc.x = event->xconfigurerequest.x; xwc.y = event->xconfigurerequest.y; xwc.width = event->xconfigurerequest.width; xwc.height = event->xconfigurerequest.height; xwc.border_width = event->xconfigurerequest.border_width; meta_verbose ("Configuring withdrawn window to %d,%d %dx%d border %d (some values may not be in mask)\n", xwc.x, xwc.y, xwc.width, xwc.height, xwc.border_width); meta_error_trap_push (display); XConfigureWindow (display->xdisplay, event->xconfigurerequest.window, xwcm, &xwc); meta_error_trap_pop (display); } else { if (!frame_was_receiver) meta_window_configure_request (window, event); } break; case GravityNotify: break; case ResizeRequest: break; case CirculateNotify: break; case CirculateRequest: break; case PropertyNotify: { MetaGroup *group; MetaScreen *screen; if (window && !frame_was_receiver) meta_window_property_notify (window, event); else if (property_for_window && !frame_was_receiver) meta_window_property_notify (property_for_window, event); group = meta_display_lookup_group (display, event->xproperty.window); if (group != NULL) meta_group_property_notify (group, event); screen = NULL; if (window == NULL && group == NULL) /* window/group != NULL means it wasn't a root window */ screen = meta_display_screen_for_root (display, event->xproperty.window); if (screen != NULL) { if (event->xproperty.atom == display->atom__NET_DESKTOP_LAYOUT) meta_screen_update_workspace_layout (screen); else if (event->xproperty.atom == display->atom__NET_DESKTOP_NAMES) meta_screen_update_workspace_names (screen); else if (meta_is_wayland_compositor () && event->xproperty.atom == display->atom__XKB_RULES_NAMES) reload_xkb_rules (screen); #if 0 else if (event->xproperty.atom == display->atom__NET_RESTACK_WINDOW) handle_net_restack_window (display, event); #endif /* we just use this property as a sentinel to avoid * certain race conditions. See the comment for the * sentinel_counter variable declaration in display.h */ if (event->xproperty.atom == display->atom__MUTTER_SENTINEL) { meta_display_decrement_focus_sentinel (display); } } } break; case SelectionClear: /* do this here instead of at end of function * so we can return */ /* FIXME: Clearing display->current_time here makes no sense to * me; who put this here and why? */ display->current_time = CurrentTime; process_selection_clear (display, event); /* Note that processing that may have resulted in * closing the display... so return right away. */ return FALSE; case SelectionRequest: process_selection_request (display, event); break; case SelectionNotify: break; case ColormapNotify: if (window && !frame_was_receiver) window->colormap = event->xcolormap.colormap; break; case ClientMessage: if (window) { if (!frame_was_receiver) meta_window_client_message (window, event); } else { MetaScreen *screen; screen = meta_display_screen_for_root (display, event->xclient.window); if (screen) { if (event->xclient.message_type == display->atom__NET_CURRENT_DESKTOP) { int space; MetaWorkspace *workspace; guint32 time; space = event->xclient.data.l[0]; time = event->xclient.data.l[1]; meta_verbose ("Request to change current workspace to %d with " "specified timestamp of %u\n", space, time); workspace = meta_screen_get_workspace_by_index (screen, space); /* Handle clients using the older version of the spec... */ if (time == 0 && workspace) { meta_warning ("Received a NET_CURRENT_DESKTOP message " "from a broken (outdated) client who sent " "a 0 timestamp\n"); time = meta_display_get_current_time_roundtrip (display); } if (workspace) meta_workspace_activate (workspace, time); else meta_verbose ("Don't know about workspace %d\n", space); } else if (event->xclient.message_type == display->atom__NET_NUMBER_OF_DESKTOPS) { int num_spaces; num_spaces = event->xclient.data.l[0]; meta_verbose ("Request to set number of workspaces to %d\n", num_spaces); meta_prefs_set_num_workspaces (num_spaces); } else if (event->xclient.message_type == display->atom__NET_SHOWING_DESKTOP) { gboolean showing_desktop; guint32 timestamp; showing_desktop = event->xclient.data.l[0] != 0; /* FIXME: Braindead protocol doesn't have a timestamp */ timestamp = meta_display_get_current_time_roundtrip (display); meta_verbose ("Request to %s desktop\n", showing_desktop ? "show" : "hide"); if (showing_desktop) meta_screen_show_desktop (screen, timestamp); else { meta_screen_unshow_desktop (screen); meta_workspace_focus_default_window (screen->active_workspace, NULL, timestamp); } } else if (event->xclient.message_type == display->atom_WM_PROTOCOLS) { meta_verbose ("Received WM_PROTOCOLS message\n"); if ((Atom)event->xclient.data.l[0] == display->atom__NET_WM_PING) { guint32 timestamp = event->xclient.data.l[1]; window = g_hash_table_lookup (display->pending_pings, ×tamp); if (window) meta_window_pong (window, timestamp); else meta_verbose ("Received invalid _NET_WM_PING for unknown timestamp %d\n", timestamp); /* We don't want ping reply events going into * the GTK+ event loop because gtk+ will treat * them as ping requests and send more replies. */ bypass_gtk = TRUE; } } } if (event->xclient.message_type == display->atom__NET_REQUEST_FRAME_EXTENTS) { meta_verbose ("Received _NET_REQUEST_FRAME_EXTENTS message\n"); process_request_frame_extents (display, event); } } break; case MappingNotify: { gboolean ignore_current; ignore_current = FALSE; /* Check whether the next event is an identical MappingNotify * event. If it is, ignore the current event, we'll update * when we get the next one. */ if (XPending (display->xdisplay)) { XEvent next_event; XPeekEvent (display->xdisplay, &next_event); if (next_event.type == MappingNotify && next_event.xmapping.request == event->xmapping.request) ignore_current = TRUE; } if (!ignore_current) { /* Let XLib know that there is a new keyboard mapping. */ XRefreshKeyboardMapping (&event->xmapping); meta_display_process_mapping_event (display, event); } } break; default: #ifdef HAVE_XKB if (event->type == display->xkb_base_event_type) { XkbAnyEvent *xkb_ev = (XkbAnyEvent *) event; switch (xkb_ev->xkb_type) { case XkbBellNotify: if (XSERVER_TIME_IS_BEFORE(display->last_bell_time, xkb_ev->time - 100)) { display->last_bell_time = xkb_ev->time; meta_bell_notify (display, xkb_ev); } break; case XkbNewKeyboardNotify: case XkbMapNotify: if (xkb_ev->device == META_VIRTUAL_CORE_KEYBOARD_ID) meta_display_process_mapping_event (display, event); break; } } #endif break; } out: return bypass_gtk; } /** * meta_display_handle_xevent: * @display: The MetaDisplay that events are coming from * @event: The event that just happened * * This is the most important function in the whole program. It is the heart, * it is the nexus, it is the Grand Central Station of Mutter's world. * When we create a #MetaDisplay, we ask GDK to pass *all* events for *all* * windows to this function. So every time anything happens that we might * want to know about, this function gets called. You see why it gets a bit * busy around here. Most of this function is a ginormous switch statement * dealing with all the kinds of events that might turn up. */ static gboolean meta_display_handle_xevent (MetaDisplay *display, XEvent *event) { Window modified; gboolean bypass_compositor = FALSE, bypass_gtk = FALSE; XIEvent *input_event; MetaMonitorManager *monitor; MetaScreen *screen; #ifdef WITH_VERBOSE_MODE if (dump_events) meta_spew_event (display, event); #endif #ifdef HAVE_STARTUP_NOTIFICATION sn_display_process_event (display->sn_display, event); #endif /* Intercept XRandR events early and don't attempt any processing for them. We still let them through to Gdk though, so it can update its own internal state. */ monitor = meta_monitor_manager_get (); if (meta_monitor_manager_handle_xevent (monitor, event)) { bypass_compositor = TRUE; goto out; } display->current_time = event_get_time (display, event); display->monitor_cache_invalidated = TRUE; if (display->focused_by_us && event->xany.serial > display->focus_serial && display->focus_window && !window_has_xwindow (display->focus_window, display->server_focus_window)) { meta_topic (META_DEBUG_FOCUS, "Earlier attempt to focus %s failed\n", display->focus_window->desc); update_focus_window (display, META_FOCUS_NONE, meta_display_lookup_x_window (display, display->server_focus_window), display->server_focus_window, display->server_focus_serial, FALSE); } screen = meta_display_screen_for_root (display, event->xany.window); if (screen) { if (meta_screen_handle_xevent (screen, event)) { bypass_gtk = bypass_compositor = TRUE; goto out; } } modified = event_get_modified_window (display, event); input_event = get_input_event (display, event); if (event->type == UnmapNotify) { if (meta_ui_window_should_not_cause_focus (display->xdisplay, modified)) { meta_display_add_ignored_crossing_serial (display, event->xany.serial); meta_topic (META_DEBUG_FOCUS, "Adding EnterNotify serial %lu to ignored focus serials\n", event->xany.serial); } } else if (input_event && input_event->evtype == XI_Leave && ((XILeaveEvent *)input_event)->mode == XINotifyUngrab && modified == display->ungrab_should_not_cause_focus_window) { meta_display_add_ignored_crossing_serial (display, event->xany.serial); meta_topic (META_DEBUG_FOCUS, "Adding LeaveNotify serial %lu to ignored focus serials\n", event->xany.serial); } #ifdef HAVE_XI23 if (meta_display_process_barrier_event (display, input_event)) { bypass_gtk = bypass_compositor = TRUE; goto out; } #endif /* HAVE_XI23 */ /* libXi does not properly copy the serial to XI2 events, so pull it * from the parent XAnyEvent and pass it to handle_input_xevent. * See: https://bugs.freedesktop.org/show_bug.cgi?id=64687 */ if (handle_input_xevent (display, input_event, event->xany.serial)) { bypass_gtk = bypass_compositor = TRUE; goto out; } if (handle_other_xevent (display, event)) { bypass_gtk = TRUE; goto out; } out: if (display->compositor && !bypass_compositor) { MetaWindow *window = modified != None ? meta_display_lookup_x_window (display, modified) : NULL; if (meta_compositor_process_event (display->compositor, event, window)) bypass_gtk = TRUE; } display->current_time = CurrentTime; return bypass_gtk; } static gboolean xevent_callback (XEvent *event, gpointer data) { MetaDisplay *display = data; return meta_display_handle_xevent (display, event); } static gboolean event_callback (const ClutterEvent *event, gpointer data) { MetaDisplay *display = data; return meta_display_handle_event (display, event); } static Window xievent_get_modified_window (MetaDisplay *display, XIEvent *input_event) { switch (input_event->evtype) { case XI_Motion: case XI_ButtonPress: case XI_ButtonRelease: case XI_KeyPress: case XI_KeyRelease: return ((XIDeviceEvent *) input_event)->event; case XI_FocusIn: case XI_FocusOut: case XI_Enter: case XI_Leave: return ((XIEnterEvent *) input_event)->event; #ifdef HAVE_XI23 case XI_BarrierHit: case XI_BarrierLeave: return ((XIBarrierEvent *) input_event)->event; #endif /* HAVE_XI23 */ } return None; } /* Return the window this has to do with, if any, rather * than the frame or root window that was selecting * for substructure */ static Window event_get_modified_window (MetaDisplay *display, XEvent *event) { XIEvent *input_event = get_input_event (display, event); if (input_event) return xievent_get_modified_window (display, input_event); switch (event->type) { case KeymapNotify: case Expose: case GraphicsExpose: case NoExpose: case VisibilityNotify: case ResizeRequest: case PropertyNotify: case SelectionClear: case SelectionRequest: case SelectionNotify: case ColormapNotify: case ClientMessage: return event->xany.window; case CreateNotify: return event->xcreatewindow.window; case DestroyNotify: return event->xdestroywindow.window; case UnmapNotify: return event->xunmap.window; case MapNotify: return event->xmap.window; case MapRequest: return event->xmaprequest.window; case ReparentNotify: return event->xreparent.window; case ConfigureNotify: return event->xconfigure.window; case ConfigureRequest: return event->xconfigurerequest.window; case GravityNotify: return event->xgravity.window; case CirculateNotify: return event->xcirculate.window; case CirculateRequest: return event->xcirculaterequest.window; case MappingNotify: return None; default: #ifdef HAVE_SHAPE if (META_DISPLAY_HAS_SHAPE (display) && event->type == (display->shape_event_base + ShapeNotify)) { XShapeEvent *sev = (XShapeEvent*) event; return sev->window; } #endif return None; } } static guint32 event_get_time (MetaDisplay *display, XEvent *event) { XIEvent *input_event = get_input_event (display, event); if (input_event) return input_event->time; switch (event->type) { case PropertyNotify: return event->xproperty.time; case SelectionClear: case SelectionRequest: case SelectionNotify: return event->xselection.time; case KeymapNotify: case Expose: case GraphicsExpose: case NoExpose: case MapNotify: case UnmapNotify: case VisibilityNotify: case ResizeRequest: case ColormapNotify: case ClientMessage: case CreateNotify: case DestroyNotify: case MapRequest: case ReparentNotify: case ConfigureNotify: case ConfigureRequest: case GravityNotify: case CirculateNotify: case CirculateRequest: case MappingNotify: default: return CurrentTime; } } #ifdef WITH_VERBOSE_MODE const char* meta_event_detail_to_string (int d) { const char *detail = "???"; switch (d) { /* We are an ancestor in the A<->B focus change relationship */ case XINotifyAncestor: detail = "NotifyAncestor"; break; case XINotifyDetailNone: detail = "NotifyDetailNone"; break; /* We are a descendant in the A<->B focus change relationship */ case XINotifyInferior: detail = "NotifyInferior"; break; case XINotifyNonlinear: detail = "NotifyNonlinear"; break; case XINotifyNonlinearVirtual: detail = "NotifyNonlinearVirtual"; break; case XINotifyPointer: detail = "NotifyPointer"; break; case XINotifyPointerRoot: detail = "NotifyPointerRoot"; break; case XINotifyVirtual: detail = "NotifyVirtual"; break; } return detail; } #endif /* WITH_VERBOSE_MODE */ #ifdef WITH_VERBOSE_MODE const char* meta_event_mode_to_string (int m) { const char *mode = "???"; switch (m) { case XINotifyNormal: mode = "NotifyNormal"; break; case XINotifyGrab: mode = "NotifyGrab"; break; case XINotifyUngrab: mode = "NotifyUngrab"; break; case XINotifyWhileGrabbed: mode = "NotifyWhileGrabbed"; break; } return mode; } #endif /* WITH_VERBOSE_MODE */ #ifdef WITH_VERBOSE_MODE static const char* stack_mode_to_string (int mode) { switch (mode) { case Above: return "Above"; case Below: return "Below"; case TopIf: return "TopIf"; case BottomIf: return "BottomIf"; case Opposite: return "Opposite"; } return "Unknown"; } #endif /* WITH_VERBOSE_MODE */ #ifdef HAVE_XSYNC #ifdef WITH_VERBOSE_MODE static gint64 sync_value_to_64 (const XSyncValue *value) { gint64 v; v = XSyncValueLow32 (*value); v |= (((gint64)XSyncValueHigh32 (*value)) << 32); return v; } #endif /* WITH_VERBOSE_MODE */ #ifdef WITH_VERBOSE_MODE static const char* alarm_state_to_string (XSyncAlarmState state) { switch (state) { case XSyncAlarmActive: return "Active"; case XSyncAlarmInactive: return "Inactive"; case XSyncAlarmDestroyed: return "Destroyed"; default: return "(unknown)"; } } #endif /* WITH_VERBOSE_MODE */ #endif /* HAVE_XSYNC */ #ifdef WITH_VERBOSE_MODE static void meta_spew_xi2_event (MetaDisplay *display, XIEvent *input_event, const char **name_p, char **extra_p) { const char *name = NULL; char *extra = NULL; XIDeviceEvent *device_event = (XIDeviceEvent *) input_event; XIEnterEvent *enter_event = (XIEnterEvent *) input_event; switch (input_event->evtype) { case XI_Motion: name = "XI_Motion"; break; case XI_ButtonPress: name = "XI_ButtonPress"; break; case XI_ButtonRelease: name = "XI_ButtonRelease"; break; case XI_KeyPress: name = "XI_KeyPress"; break; case XI_KeyRelease: name = "XI_KeyRelease"; break; case XI_FocusIn: name = "XI_FocusIn"; break; case XI_FocusOut: name = "XI_FocusOut"; break; case XI_Enter: name = "XI_Enter"; break; case XI_Leave: name = "XI_Leave"; break; #ifdef HAVE_XI23 case XI_BarrierHit: name = "XI_BarrierHit"; break; case XI_BarrierLeave: name = "XI_BarrierLeave"; break; #endif /* HAVE_XI23 */ } switch (input_event->evtype) { case XI_Motion: extra = g_strdup_printf ("win: 0x%lx x: %g y: %g", device_event->event, device_event->root_x, device_event->root_y); break; case XI_ButtonPress: case XI_ButtonRelease: extra = g_strdup_printf ("button %u x %g y %g root 0x%lx", device_event->detail, device_event->root_x, device_event->root_y, device_event->root); break; case XI_KeyPress: case XI_KeyRelease: { KeySym keysym; const char *str; keysym = XKeycodeToKeysym (display->xdisplay, device_event->detail, 0); str = XKeysymToString (keysym); extra = g_strdup_printf ("Key '%s' state 0x%x", str ? str : "none", device_event->mods.effective); } break; case XI_FocusIn: case XI_FocusOut: extra = g_strdup_printf ("detail: %s mode: %s\n", meta_event_detail_to_string (enter_event->detail), meta_event_mode_to_string (enter_event->mode)); break; case XI_Enter: case XI_Leave: extra = g_strdup_printf ("win: 0x%lx root: 0x%lx mode: %s detail: %s focus: %d x: %g y: %g", enter_event->event, enter_event->root, meta_event_mode_to_string (enter_event->mode), meta_event_detail_to_string (enter_event->detail), enter_event->focus, enter_event->root_x, enter_event->root_y); break; } *name_p = name; *extra_p = extra; } static void meta_spew_core_event (MetaDisplay *display, XEvent *event, const char **name_p, char **extra_p) { const char *name = NULL; char *extra = NULL; switch (event->type) { case KeymapNotify: name = "KeymapNotify"; break; case Expose: name = "Expose"; break; case GraphicsExpose: name = "GraphicsExpose"; break; case NoExpose: name = "NoExpose"; break; case VisibilityNotify: name = "VisibilityNotify"; break; case CreateNotify: name = "CreateNotify"; extra = g_strdup_printf ("parent: 0x%lx window: 0x%lx", event->xcreatewindow.parent, event->xcreatewindow.window); break; case DestroyNotify: name = "DestroyNotify"; extra = g_strdup_printf ("event: 0x%lx window: 0x%lx", event->xdestroywindow.event, event->xdestroywindow.window); break; case UnmapNotify: name = "UnmapNotify"; extra = g_strdup_printf ("event: 0x%lx window: 0x%lx from_configure: %d", event->xunmap.event, event->xunmap.window, event->xunmap.from_configure); break; case MapNotify: name = "MapNotify"; extra = g_strdup_printf ("event: 0x%lx window: 0x%lx override_redirect: %d", event->xmap.event, event->xmap.window, event->xmap.override_redirect); break; case MapRequest: name = "MapRequest"; extra = g_strdup_printf ("window: 0x%lx parent: 0x%lx\n", event->xmaprequest.window, event->xmaprequest.parent); break; case ReparentNotify: name = "ReparentNotify"; extra = g_strdup_printf ("window: 0x%lx parent: 0x%lx event: 0x%lx\n", event->xreparent.window, event->xreparent.parent, event->xreparent.event); break; case ConfigureNotify: name = "ConfigureNotify"; extra = g_strdup_printf ("x: %d y: %d w: %d h: %d above: 0x%lx override_redirect: %d", event->xconfigure.x, event->xconfigure.y, event->xconfigure.width, event->xconfigure.height, event->xconfigure.above, event->xconfigure.override_redirect); break; case ConfigureRequest: name = "ConfigureRequest"; extra = g_strdup_printf ("parent: 0x%lx window: 0x%lx x: %d %sy: %d %sw: %d %sh: %d %sborder: %d %sabove: %lx %sstackmode: %s %s", event->xconfigurerequest.parent, event->xconfigurerequest.window, event->xconfigurerequest.x, event->xconfigurerequest.value_mask & CWX ? "" : "(unset) ", event->xconfigurerequest.y, event->xconfigurerequest.value_mask & CWY ? "" : "(unset) ", event->xconfigurerequest.width, event->xconfigurerequest.value_mask & CWWidth ? "" : "(unset) ", event->xconfigurerequest.height, event->xconfigurerequest.value_mask & CWHeight ? "" : "(unset) ", event->xconfigurerequest.border_width, event->xconfigurerequest.value_mask & CWBorderWidth ? "" : "(unset)", event->xconfigurerequest.above, event->xconfigurerequest.value_mask & CWSibling ? "" : "(unset)", stack_mode_to_string (event->xconfigurerequest.detail), event->xconfigurerequest.value_mask & CWStackMode ? "" : "(unset)"); break; case GravityNotify: name = "GravityNotify"; break; case ResizeRequest: name = "ResizeRequest"; extra = g_strdup_printf ("width = %d height = %d", event->xresizerequest.width, event->xresizerequest.height); break; case CirculateNotify: name = "CirculateNotify"; break; case CirculateRequest: name = "CirculateRequest"; break; case PropertyNotify: { char *str; const char *state; name = "PropertyNotify"; meta_error_trap_push (display); str = XGetAtomName (display->xdisplay, event->xproperty.atom); meta_error_trap_pop (display); if (event->xproperty.state == PropertyNewValue) state = "PropertyNewValue"; else if (event->xproperty.state == PropertyDelete) state = "PropertyDelete"; else state = "???"; extra = g_strdup_printf ("atom: %s state: %s", str ? str : "(unknown atom)", state); meta_XFree (str); } break; case SelectionClear: name = "SelectionClear"; break; case SelectionRequest: name = "SelectionRequest"; break; case SelectionNotify: name = "SelectionNotify"; break; case ColormapNotify: name = "ColormapNotify"; break; case ClientMessage: { char *str; name = "ClientMessage"; meta_error_trap_push (display); str = XGetAtomName (display->xdisplay, event->xclient.message_type); meta_error_trap_pop (display); extra = g_strdup_printf ("type: %s format: %d\n", str ? str : "(unknown atom)", event->xclient.format); meta_XFree (str); } break; case MappingNotify: name = "MappingNotify"; break; default: #ifdef HAVE_XSYNC if (META_DISPLAY_HAS_XSYNC (display) && event->type == (display->xsync_event_base + XSyncAlarmNotify)) { XSyncAlarmNotifyEvent *aevent = (XSyncAlarmNotifyEvent*) event; name = "XSyncAlarmNotify"; extra = g_strdup_printf ("alarm: 0x%lx" " counter_value: %" G_GINT64_FORMAT " alarm_value: %" G_GINT64_FORMAT " time: %u alarm state: %s", aevent->alarm, (gint64) sync_value_to_64 (&aevent->counter_value), (gint64) sync_value_to_64 (&aevent->alarm_value), (unsigned int)aevent->time, alarm_state_to_string (aevent->state)); } else #endif /* HAVE_XSYNC */ #ifdef HAVE_SHAPE if (META_DISPLAY_HAS_SHAPE (display) && event->type == (display->shape_event_base + ShapeNotify)) { XShapeEvent *sev = (XShapeEvent*) event; name = "ShapeNotify"; extra = g_strdup_printf ("kind: %s " "x: %d y: %d w: %u h: %u " "shaped: %d", sev->kind == ShapeBounding ? "ShapeBounding" : (sev->kind == ShapeClip ? "ShapeClip" : "(unknown)"), sev->x, sev->y, sev->width, sev->height, sev->shaped); } else #endif /* HAVE_SHAPE */ { name = "(Unknown event)"; extra = g_strdup_printf ("type: %d", event->xany.type); } break; } *name_p = name; *extra_p = extra; } static void meta_spew_event (MetaDisplay *display, XEvent *event) { const char *name = NULL; char *extra = NULL; char *winname; MetaScreen *screen; XIEvent *input_event; if (!meta_is_verbose()) return; /* filter overnumerous events */ if (event->type == Expose || event->type == MotionNotify || event->type == NoExpose) return; if (event->type == (display->damage_event_base + XDamageNotify)) return; if (event->type == (display->xsync_event_base + XSyncAlarmNotify)) return; if (event->type == PropertyNotify && event->xproperty.atom == display->atom__NET_WM_USER_TIME) return; input_event = get_input_event (display, event); if (input_event) meta_spew_xi2_event (display, input_event, &name, &extra); else meta_spew_core_event (display, event, &name, &extra); screen = meta_display_screen_for_root (display, event->xany.window); if (screen) winname = g_strdup_printf ("root %d", screen->number); else winname = g_strdup_printf ("0x%lx", event->xany.window); meta_topic (META_DEBUG_EVENTS, "%s on %s%s %s %sserial %lu\n", name, winname, extra ? ":" : "", extra ? extra : "", event->xany.send_event ? "SEND " : "", event->xany.serial); g_free (winname); if (extra) g_free (extra); } #endif /* WITH_VERBOSE_MODE */ MetaWindow* meta_display_lookup_x_window (MetaDisplay *display, Window xwindow) { return g_hash_table_lookup (display->xids, &xwindow); } void meta_display_register_x_window (MetaDisplay *display, Window *xwindowp, MetaWindow *window) { g_return_if_fail (g_hash_table_lookup (display->xids, xwindowp) == NULL); g_hash_table_insert (display->xids, xwindowp, window); } void meta_display_unregister_x_window (MetaDisplay *display, Window xwindow) { g_return_if_fail (g_hash_table_lookup (display->xids, &xwindow) != NULL); g_hash_table_remove (display->xids, &xwindow); } void meta_display_register_wayland_window (MetaDisplay *display, MetaWindow *window) { g_hash_table_add (display->wayland_windows, window); } void meta_display_unregister_wayland_window (MetaDisplay *display, MetaWindow *window) { g_hash_table_remove (display->wayland_windows, window); } #ifdef HAVE_XSYNC /* We store sync alarms in the window ID hash table, because they are * just more types of XIDs in the same global space, but we have * typesafe functions to register/unregister for readability. */ MetaWindow* meta_display_lookup_sync_alarm (MetaDisplay *display, XSyncAlarm alarm) { return g_hash_table_lookup (display->xids, &alarm); } void meta_display_register_sync_alarm (MetaDisplay *display, XSyncAlarm *alarmp, MetaWindow *window) { g_return_if_fail (g_hash_table_lookup (display->xids, alarmp) == NULL); g_hash_table_insert (display->xids, alarmp, window); } void meta_display_unregister_sync_alarm (MetaDisplay *display, XSyncAlarm alarm) { g_return_if_fail (g_hash_table_lookup (display->xids, &alarm) != NULL); g_hash_table_remove (display->xids, &alarm); } #endif /* HAVE_XSYNC */ void meta_display_notify_window_created (MetaDisplay *display, MetaWindow *window) { g_signal_emit (display, display_signals[WINDOW_CREATED], 0, window); } /** * meta_display_xwindow_is_a_no_focus_window: * @display: A #MetaDisplay * @xwindow: An X11 window * * Returns: %TRUE iff window is one of mutter's internal "no focus" windows * (there is one per screen) which will have the focus when there is no * actual client window focused. */ gboolean meta_display_xwindow_is_a_no_focus_window (MetaDisplay *display, Window xwindow) { gboolean is_a_no_focus_window = FALSE; GSList *temp = display->screens; while (temp != NULL) { MetaScreen *screen = temp->data; if (screen->no_focus_window == xwindow) { is_a_no_focus_window = TRUE; break; } temp = temp->next; } return is_a_no_focus_window; } static MetaCursor meta_cursor_for_grab_op (MetaGrabOp op) { switch (op) { case META_GRAB_OP_RESIZING_SE: case META_GRAB_OP_KEYBOARD_RESIZING_SE: return META_CURSOR_SE_RESIZE; break; case META_GRAB_OP_RESIZING_S: case META_GRAB_OP_KEYBOARD_RESIZING_S: return META_CURSOR_SOUTH_RESIZE; break; case META_GRAB_OP_RESIZING_SW: case META_GRAB_OP_KEYBOARD_RESIZING_SW: return META_CURSOR_SW_RESIZE; break; case META_GRAB_OP_RESIZING_N: case META_GRAB_OP_KEYBOARD_RESIZING_N: return META_CURSOR_NORTH_RESIZE; break; case META_GRAB_OP_RESIZING_NE: case META_GRAB_OP_KEYBOARD_RESIZING_NE: return META_CURSOR_NE_RESIZE; break; case META_GRAB_OP_RESIZING_NW: case META_GRAB_OP_KEYBOARD_RESIZING_NW: return META_CURSOR_NW_RESIZE; break; case META_GRAB_OP_RESIZING_W: case META_GRAB_OP_KEYBOARD_RESIZING_W: return META_CURSOR_WEST_RESIZE; break; case META_GRAB_OP_RESIZING_E: case META_GRAB_OP_KEYBOARD_RESIZING_E: return META_CURSOR_EAST_RESIZE; break; case META_GRAB_OP_MOVING: case META_GRAB_OP_KEYBOARD_MOVING: case META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN: return META_CURSOR_MOVE_OR_RESIZE_WINDOW; break; default: break; } return META_CURSOR_DEFAULT; } void meta_display_set_grab_op_cursor (MetaDisplay *display, MetaScreen *screen, MetaGrabOp op, Window grab_xwindow, guint32 timestamp) { unsigned char mask_bits[XIMaskLen (XI_LASTEVENT)] = { 0 }; XIEventMask mask = { XIAllMasterDevices, sizeof (mask_bits), mask_bits }; MetaCursor cursor = meta_cursor_for_grab_op (op); XISetMask (mask.mask, XI_ButtonPress); XISetMask (mask.mask, XI_ButtonRelease); XISetMask (mask.mask, XI_Enter); XISetMask (mask.mask, XI_Leave); XISetMask (mask.mask, XI_Motion); g_assert (screen != NULL); meta_error_trap_push (display); if (XIGrabDevice (display->xdisplay, META_VIRTUAL_CORE_POINTER_ID, grab_xwindow, timestamp, meta_display_create_x_cursor (display, cursor), XIGrabModeAsync, XIGrabModeAsync, False, /* owner_events */ &mask) == Success) { display->grab_have_pointer = TRUE; meta_topic (META_DEBUG_WINDOW_OPS, "XIGrabDevice() returned GrabSuccess time %u\n", timestamp); } else { meta_topic (META_DEBUG_WINDOW_OPS, "XIGrabDevice() failed time %u\n", timestamp); } meta_error_trap_pop (display); meta_cursor_tracker_set_grab_cursor (screen->cursor_tracker, cursor); } gboolean meta_display_begin_grab_op (MetaDisplay *display, MetaScreen *screen, MetaWindow *window, MetaGrabOp op, gboolean pointer_already_grabbed, gboolean frame_action, int button, gulong modmask, guint32 timestamp, int root_x, int root_y) { MetaWindow *grab_window = NULL; Window grab_xwindow; meta_topic (META_DEBUG_WINDOW_OPS, "Doing grab op %u on window %s button %d pointer already grabbed: %d pointer pos %d,%d\n", op, window ? window->desc : "none", button, pointer_already_grabbed, root_x, root_y); if (display->grab_op != META_GRAB_OP_NONE) { if (window) meta_warning ("Attempt to perform window operation %u on window %s when operation %u on %s already in effect\n", op, window->desc, display->grab_op, display->grab_window ? display->grab_window->desc : "none"); return FALSE; } if (window && (meta_grab_op_is_moving (op) || meta_grab_op_is_resizing (op))) { if (meta_prefs_get_raise_on_click ()) meta_window_raise (window); else { display->grab_initial_x = root_x; display->grab_initial_y = root_y; display->grab_threshold_movement_reached = FALSE; } } /* If window is a modal dialog attached to its parent, * grab the parent instead for moving. */ if (window && meta_window_is_attached_dialog (window) && meta_grab_op_is_moving (op)) grab_window = meta_window_get_transient_for (window); if (grab_window == NULL) grab_window = window; /* FIXME: * If we have no MetaWindow we do our best * and try to do the grab on the RootWindow. * This will fail if anyone else has any * key grab on the RootWindow. */ if (grab_window) grab_xwindow = meta_window_get_toplevel_xwindow (grab_window); else grab_xwindow = screen->xroot; display->grab_have_pointer = FALSE; if (pointer_already_grabbed) display->grab_have_pointer = TRUE; meta_display_set_grab_op_cursor (display, screen, op, grab_xwindow, timestamp); if (!display->grab_have_pointer && !grab_op_is_keyboard (op)) { meta_topic (META_DEBUG_WINDOW_OPS, "XIGrabDevice() failed\n"); return FALSE; } /* Grab keys for keyboard ops and mouse move/resizes; see #126497 */ if (grab_op_is_keyboard (op) || grab_op_is_mouse_only (op)) { if (grab_window) display->grab_have_keyboard = meta_window_grab_all_keys (grab_window, timestamp); else display->grab_have_keyboard = meta_screen_grab_all_keys (screen, timestamp); if (!display->grab_have_keyboard) { meta_topic (META_DEBUG_WINDOW_OPS, "grabbing all keys failed, ungrabbing pointer\n"); XIUngrabDevice (display->xdisplay, META_VIRTUAL_CORE_POINTER_ID, timestamp); display->grab_have_pointer = FALSE; return FALSE; } } display->grab_op = op; display->grab_window = grab_window; display->grab_screen = screen; display->grab_xwindow = grab_xwindow; display->grab_button = button; display->grab_mask = modmask; if (window) { display->grab_tile_mode = window->tile_mode; display->grab_tile_monitor_number = window->tile_monitor_number; } else { display->grab_tile_mode = META_TILE_NONE; display->grab_tile_monitor_number = -1; } display->grab_anchor_root_x = root_x; display->grab_anchor_root_y = root_y; display->grab_latest_motion_x = root_x; display->grab_latest_motion_y = root_y; display->grab_last_moveresize_time.tv_sec = 0; display->grab_last_moveresize_time.tv_usec = 0; display->grab_motion_notify_time = 0; display->grab_old_window_stacking = NULL; #ifdef HAVE_XSYNC display->grab_last_user_action_was_snap = FALSE; #endif display->grab_frame_action = frame_action; display->grab_resize_unmaximize = 0; display->grab_timestamp = timestamp; if (display->grab_resize_timeout_id) { g_source_remove (display->grab_resize_timeout_id); display->grab_resize_timeout_id = 0; } if (display->grab_window) { meta_window_get_client_root_coords (display->grab_window, &display->grab_initial_window_pos); display->grab_anchor_window_pos = display->grab_initial_window_pos; #ifdef HAVE_XSYNC if ( meta_grab_op_is_resizing (display->grab_op) && display->grab_window->sync_request_counter != None) { meta_window_create_sync_request_alarm (display->grab_window); } #endif } meta_topic (META_DEBUG_WINDOW_OPS, "Grab op %u on window %s successful\n", display->grab_op, window ? window->desc : "(null)"); g_assert (display->grab_window != NULL || display->grab_screen != NULL); g_assert (display->grab_op != META_GRAB_OP_NONE); /* Save the old stacking */ if (GRAB_OP_IS_WINDOW_SWITCH (display->grab_op)) { meta_topic (META_DEBUG_WINDOW_OPS, "Saving old stack positions; old pointer was %p.\n", display->grab_old_window_stacking); display->grab_old_window_stacking = meta_stack_get_positions (screen->stack); } if (display->grab_window) { meta_window_refresh_resize_popup (display->grab_window); } g_signal_emit (display, display_signals[GRAB_OP_BEGIN], 0, screen, display->grab_window, display->grab_op); return TRUE; } void meta_display_end_grab_op (MetaDisplay *display, guint32 timestamp) { meta_topic (META_DEBUG_WINDOW_OPS, "Ending grab op %u at time %u\n", display->grab_op, timestamp); if (display->grab_op == META_GRAB_OP_NONE) return; g_signal_emit (display, display_signals[GRAB_OP_END], 0, display->grab_screen, display->grab_window, display->grab_op); if (display->grab_window != NULL) display->grab_window->shaken_loose = FALSE; if (display->grab_window != NULL && !meta_prefs_get_raise_on_click () && (meta_grab_op_is_moving (display->grab_op) || meta_grab_op_is_resizing (display->grab_op))) { /* Only raise the window in orthogonal raise * ('do-not-raise-on-click') mode if the user didn't try to move * or resize the given window by at least a threshold amount. * For raise on click mode, the window was raised at the * beginning of the grab_op. */ if (!display->grab_threshold_movement_reached) meta_window_raise (display->grab_window); } if (GRAB_OP_IS_WINDOW_SWITCH (display->grab_op) || display->grab_op == META_GRAB_OP_KEYBOARD_WORKSPACE_SWITCHING) { if (GRAB_OP_IS_WINDOW_SWITCH (display->grab_op)) meta_screen_tab_popup_destroy (display->grab_screen); else meta_screen_workspace_popup_destroy (display->grab_screen); /* If the ungrab here causes an EnterNotify, ignore it for * sloppy focus */ display->ungrab_should_not_cause_focus_window = display->grab_xwindow; } /* If this was a move or resize clear out the edge cache */ if (meta_grab_op_is_resizing (display->grab_op) || meta_grab_op_is_moving (display->grab_op)) { meta_topic (META_DEBUG_WINDOW_OPS, "Clearing out the edges for resistance/snapping"); meta_display_cleanup_edges (display); } if (display->grab_old_window_stacking != NULL) { meta_topic (META_DEBUG_WINDOW_OPS, "Clearing out the old stack position, which was %p.\n", display->grab_old_window_stacking); g_list_free (display->grab_old_window_stacking); display->grab_old_window_stacking = NULL; } if (display->grab_have_pointer) { meta_topic (META_DEBUG_WINDOW_OPS, "Ungrabbing pointer with timestamp %u\n", timestamp); XIUngrabDevice (display->xdisplay, META_VIRTUAL_CORE_POINTER_ID, timestamp); } if (display->grab_have_keyboard) { meta_topic (META_DEBUG_WINDOW_OPS, "Ungrabbing all keys timestamp %u\n", timestamp); if (display->grab_window) meta_window_ungrab_all_keys (display->grab_window, timestamp); else meta_screen_ungrab_all_keys (display->grab_screen, timestamp); } meta_cursor_tracker_set_grab_cursor (display->grab_screen->cursor_tracker, META_CURSOR_DEFAULT); display->grab_timestamp = 0; display->grab_window = NULL; display->grab_screen = NULL; display->grab_xwindow = None; display->grab_tile_mode = META_TILE_NONE; display->grab_tile_monitor_number = -1; display->grab_op = META_GRAB_OP_NONE; if (display->grab_resize_popup) { meta_ui_resize_popup_free (display->grab_resize_popup); display->grab_resize_popup = NULL; } if (display->grab_resize_timeout_id) { g_source_remove (display->grab_resize_timeout_id); display->grab_resize_timeout_id = 0; } } /** * meta_display_get_grab_op: * @display: The #MetaDisplay that the window is on * Gets the current grab operation, if any. * * Return value: the current grab operation, or %META_GRAB_OP_NONE if * Mutter doesn't currently have a grab. %META_GRAB_OP_COMPOSITOR will * be returned if a compositor-plugin modal operation is in effect * (See mutter_begin_modal_for_plugin()) */ MetaGrabOp meta_display_get_grab_op (MetaDisplay *display) { return display->grab_op; } void meta_display_check_threshold_reached (MetaDisplay *display, int x, int y) { /* Don't bother doing the check again if we've already reached the threshold */ if (meta_prefs_get_raise_on_click () || display->grab_threshold_movement_reached) return; if (ABS (display->grab_initial_x - x) >= 8 || ABS (display->grab_initial_y - y) >= 8) display->grab_threshold_movement_reached = TRUE; } static void meta_change_button_grab (MetaDisplay *display, Window xwindow, gboolean grab, gboolean sync, int button, int modmask) { unsigned int ignored_mask; unsigned char mask_bits[XIMaskLen (XI_LASTEVENT)] = { 0 }; XIEventMask mask = { XIAllMasterDevices, sizeof (mask_bits), mask_bits }; XISetMask (mask.mask, XI_ButtonPress); XISetMask (mask.mask, XI_ButtonRelease); XISetMask (mask.mask, XI_Motion); meta_verbose ("%s 0x%lx sync = %d button = %d modmask 0x%x\n", grab ? "Grabbing" : "Ungrabbing", xwindow, sync, button, modmask); meta_error_trap_push (display); ignored_mask = 0; while (ignored_mask <= display->ignored_modifier_mask) { XIGrabModifiers mods; if (ignored_mask & ~(display->ignored_modifier_mask)) { /* Not a combination of ignored modifiers * (it contains some non-ignored modifiers) */ ++ignored_mask; continue; } mods = (XIGrabModifiers) { modmask | ignored_mask, 0 }; if (meta_is_debugging ()) meta_error_trap_push_with_return (display); /* GrabModeSync means freeze until XAllowEvents */ if (grab) XIGrabButton (display->xdisplay, META_VIRTUAL_CORE_POINTER_ID, button, xwindow, None, sync ? XIGrabModeSync : XIGrabModeAsync, XIGrabModeAsync, False, &mask, 1, &mods); else XIUngrabButton (display->xdisplay, META_VIRTUAL_CORE_POINTER_ID, button, xwindow, 1, &mods); if (meta_is_debugging ()) { int result; result = meta_error_trap_pop_with_return (display); if (result != Success) meta_verbose ("Failed to %s button %d with mask 0x%x for window 0x%lx error code %d\n", grab ? "grab" : "ungrab", button, modmask | ignored_mask, xwindow, result); } ++ignored_mask; } meta_error_trap_pop (display); } void meta_display_grab_window_buttons (MetaDisplay *display, Window xwindow) { /* Grab Alt + button1 for moving window. * Grab Alt + button2 for resizing window. * Grab Alt + button3 for popping up window menu. * Grab Alt + Shift + button1 for snap-moving window. */ meta_verbose ("Grabbing window buttons for 0x%lx\n", xwindow); /* FIXME If we ignored errors here instead of spewing, we could * put one big error trap around the loop and avoid a bunch of * XSync() */ if (display->window_grab_modifiers != 0) { gboolean debug = g_getenv ("MUTTER_DEBUG_BUTTON_GRABS") != NULL; int i; for (i = 1; i < 4; i++) { meta_change_button_grab (display, xwindow, TRUE, FALSE, i, display->window_grab_modifiers); /* This is for debugging, since I end up moving the Xnest * otherwise ;-) */ if (debug) meta_change_button_grab (display, xwindow, TRUE, FALSE, i, ControlMask); } /* In addition to grabbing Alt+Button1 for moving the window, * grab Alt+Shift+Button1 for snap-moving the window. See bug * 112478. Unfortunately, this doesn't work with * Shift+Alt+Button1 for some reason; so at least part of the * order still matters, which sucks (please FIXME). */ meta_change_button_grab (display, xwindow, TRUE, FALSE, 1, display->window_grab_modifiers | ShiftMask); } } void meta_display_ungrab_window_buttons (MetaDisplay *display, Window xwindow) { gboolean debug; int i; if (display->window_grab_modifiers == 0) return; debug = g_getenv ("MUTTER_DEBUG_BUTTON_GRABS") != NULL; i = 1; while (i < 4) { meta_change_button_grab (display, xwindow, FALSE, FALSE, i, display->window_grab_modifiers); if (debug) meta_change_button_grab (display, xwindow, FALSE, FALSE, i, ControlMask); ++i; } } /* Grab buttons we only grab while unfocused in click-to-focus mode */ #define MAX_FOCUS_BUTTON 4 void meta_display_grab_focus_window_button (MetaDisplay *display, MetaWindow *window) { /* Grab button 1 for activating unfocused windows */ meta_verbose ("Grabbing unfocused window buttons for %s\n", window->desc); #if 0 /* FIXME:115072 */ /* Don't grab at all unless in click to focus mode. In click to * focus, we may sometimes be clever about intercepting and eating * the focus click. But in mouse focus, we never do that since the * focus window may not be raised, and who wants to think about * mouse focus anyway. */ if (meta_prefs_get_focus_mode () != G_DESKTOP_FOCUS_MODE_CLICK) { meta_verbose (" (well, not grabbing since not in click to focus mode)\n"); return; } #endif if (window->have_focus_click_grab) { meta_verbose (" (well, not grabbing since we already have the grab)\n"); return; } /* FIXME If we ignored errors here instead of spewing, we could * put one big error trap around the loop and avoid a bunch of * XSync() */ { int i = 1; while (i < MAX_FOCUS_BUTTON) { meta_change_button_grab (display, window->xwindow, TRUE, TRUE, i, 0); ++i; } window->have_focus_click_grab = TRUE; } } void meta_display_ungrab_focus_window_button (MetaDisplay *display, MetaWindow *window) { meta_verbose ("Ungrabbing unfocused window buttons for %s\n", window->desc); if (!window->have_focus_click_grab) return; { int i = 1; while (i < MAX_FOCUS_BUTTON) { meta_change_button_grab (display, window->xwindow, FALSE, FALSE, i, 0); ++i; } window->have_focus_click_grab = FALSE; } } void meta_display_increment_event_serial (MetaDisplay *display) { /* We just make some random X request */ XDeleteProperty (display->xdisplay, display->leader_window, display->atom__MOTIF_WM_HINTS); } void meta_display_update_active_window_hint (MetaDisplay *display) { GSList *tmp; gulong data[1]; if (display->focus_window) data[0] = display->focus_window->xwindow; else data[0] = None; tmp = display->screens; while (tmp != NULL) { MetaScreen *screen = tmp->data; meta_error_trap_push (display); XChangeProperty (display->xdisplay, screen->xroot, display->atom__NET_ACTIVE_WINDOW, XA_WINDOW, 32, PropModeReplace, (guchar*) data, 1); meta_error_trap_pop (display); tmp = tmp->next; } } void meta_display_queue_retheme_all_windows (MetaDisplay *display) { GSList* windows; GSList *tmp; windows = meta_display_list_windows (display, META_LIST_DEFAULT); tmp = windows; while (tmp != NULL) { MetaWindow *window = tmp->data; meta_window_queue (window, META_QUEUE_MOVE_RESIZE); meta_window_frame_size_changed (window); if (window->frame) { meta_frame_queue_draw (window->frame); } tmp = tmp->next; } g_slist_free (windows); } void meta_display_retheme_all (void) { meta_display_queue_retheme_all_windows (meta_get_display ()); } void meta_display_set_cursor_theme (const char *theme, int size) { GSList *tmp; MetaDisplay *display = meta_get_display (); XcursorSetTheme (display->xdisplay, theme); XcursorSetDefaultSize (display->xdisplay, size); tmp = display->screens; while (tmp != NULL) { MetaScreen *screen = tmp->data; meta_screen_update_cursor (screen); tmp = tmp->next; } } /* * Stores whether syncing is currently enabled. */ static gboolean is_syncing = FALSE; /** * meta_is_syncing: * * Returns whether X synchronisation is currently enabled. * * FIXME: This is *only* called by meta_display_open(), but by that time * we have already turned syncing on or off on startup, and we don't * have any way to do so while Mutter is running, so it's rather * pointless. * * Returns: %TRUE if we must wait for events whenever we send X requests; * %FALSE otherwise. */ gboolean meta_is_syncing (void) { return is_syncing; } /** * meta_set_syncing: * @setting: whether to turn syncing on or off * * A handy way to turn on synchronisation on or off for every display. */ void meta_set_syncing (gboolean setting) { if (setting != is_syncing) { is_syncing = setting; if (meta_get_display ()) XSynchronize (meta_get_display ()->xdisplay, is_syncing); } } static void process_request_frame_extents (MetaDisplay *display, XEvent *event) { /* The X window whose frame extents will be set. */ Window xwindow = event->xclient.window; unsigned long data[4] = { 0, 0, 0, 0 }; MotifWmHints *hints = NULL; gboolean hints_set = FALSE; meta_verbose ("Setting frame extents for 0x%lx\n", xwindow); /* See if the window is decorated. */ hints_set = meta_prop_get_motif_hints (display, xwindow, display->atom__MOTIF_WM_HINTS, &hints); if ((hints_set && hints->decorations) || !hints_set) { MetaFrameBorders borders; MetaScreen *screen; screen = meta_display_screen_for_xwindow (display, event->xclient.window); if (screen == NULL) { meta_warning ("Received request to set _NET_FRAME_EXTENTS " "on 0x%lx which is on a screen we are not managing\n", event->xclient.window); meta_XFree (hints); return; } /* Return estimated frame extents for a normal window. */ meta_ui_theme_get_frame_borders (screen->ui, META_FRAME_TYPE_NORMAL, 0, &borders); data[0] = borders.visible.left; data[1] = borders.visible.right; data[2] = borders.visible.top; data[3] = borders.visible.bottom; } meta_topic (META_DEBUG_GEOMETRY, "Setting _NET_FRAME_EXTENTS on unmanaged window 0x%lx " "to top = %lu, left = %lu, bottom = %lu, right = %lu\n", xwindow, data[0], data[1], data[2], data[3]); meta_error_trap_push (display); XChangeProperty (display->xdisplay, xwindow, display->atom__NET_FRAME_EXTENTS, XA_CARDINAL, 32, PropModeReplace, (guchar*) data, 4); meta_error_trap_pop (display); meta_XFree (hints); } MetaGroup* get_focussed_group (MetaDisplay *display) { if (display->focus_window) return display->focus_window->group; else return NULL; } #define IN_TAB_CHAIN(w,t) (((t) == META_TAB_LIST_NORMAL && META_WINDOW_IN_NORMAL_TAB_CHAIN (w)) \ || ((t) == META_TAB_LIST_DOCKS && META_WINDOW_IN_DOCK_TAB_CHAIN (w)) \ || ((t) == META_TAB_LIST_GROUP && META_WINDOW_IN_GROUP_TAB_CHAIN (w, get_focussed_group(w->display))) \ || ((t) == META_TAB_LIST_NORMAL_ALL && META_WINDOW_IN_NORMAL_TAB_CHAIN_TYPE (w))) static MetaWindow* find_tab_forward (MetaDisplay *display, MetaTabList type, MetaScreen *screen, MetaWorkspace *workspace, GList *start, gboolean skip_first) { GList *tmp; g_return_val_if_fail (start != NULL, NULL); g_return_val_if_fail (workspace != NULL, NULL); tmp = start; if (skip_first) tmp = tmp->next; while (tmp != NULL) { MetaWindow *window = tmp->data; if (window->screen == screen && IN_TAB_CHAIN (window, type)) return window; tmp = tmp->next; } tmp = workspace->mru_list; while (tmp != start) { MetaWindow *window = tmp->data; if (IN_TAB_CHAIN (window, type)) return window; tmp = tmp->next; } return NULL; } static MetaWindow* find_tab_backward (MetaDisplay *display, MetaTabList type, MetaScreen *screen, MetaWorkspace *workspace, GList *start, gboolean skip_last) { GList *tmp; g_return_val_if_fail (start != NULL, NULL); g_return_val_if_fail (workspace != NULL, NULL); tmp = start; if (skip_last) tmp = tmp->prev; while (tmp != NULL) { MetaWindow *window = tmp->data; if (window->screen == screen && IN_TAB_CHAIN (window, type)) return window; tmp = tmp->prev; } tmp = g_list_last (workspace->mru_list); while (tmp != start) { MetaWindow *window = tmp->data; if (IN_TAB_CHAIN (window, type)) return window; tmp = tmp->prev; } return NULL; } static int mru_cmp (gconstpointer a, gconstpointer b) { guint32 time_a, time_b; time_a = meta_window_get_user_time ((MetaWindow *)a); time_b = meta_window_get_user_time ((MetaWindow *)b); if (time_a > time_b) return -1; else if (time_a < time_b) return 1; else return 0; } /** * meta_display_get_tab_list: * @display: a #MetaDisplay * @type: type of tab list * @screen: a #MetaScreen * @workspace: (allow-none): origin workspace * * Determine the list of windows that should be displayed for Alt-TAB * functionality. The windows are returned in most recently used order. * If @workspace is not %NULL, the list only conains windows that are on * @workspace or have the demands-attention hint set; otherwise it contains * all windows on @screen. * * Returns: (transfer container) (element-type Meta.Window): List of windows */ GList* meta_display_get_tab_list (MetaDisplay *display, MetaTabList type, MetaScreen *screen, MetaWorkspace *workspace) { GList *tab_list = NULL; GList *global_mru_list = NULL; GList *mru_list, *tmp; GSList *windows = meta_display_list_windows (display, META_LIST_DEFAULT); GSList *w; if (workspace == NULL) { /* Yay for mixing GList and GSList in the API */ for (w = windows; w; w = w->next) global_mru_list = g_list_prepend (global_mru_list, w->data); global_mru_list = g_list_sort (global_mru_list, mru_cmp); } mru_list = workspace ? workspace->mru_list : global_mru_list; /* Windows sellout mode - MRU order. */ for (tmp = mru_list; tmp; tmp = tmp->next) { MetaWindow *window = tmp->data; if (window->screen == screen && IN_TAB_CHAIN (window, type)) tab_list = g_list_prepend (tab_list, window); } tab_list = g_list_reverse (tab_list); /* If filtering by workspace, include windows from * other workspaces that demand attention */ if (workspace) for (w = windows; w; w = w->next) { MetaWindow *l_window = w->data; if (l_window->wm_state_demands_attention && l_window->workspace != workspace && IN_TAB_CHAIN (l_window, type)) tab_list = g_list_prepend (tab_list, l_window); } g_list_free (global_mru_list); g_slist_free (windows); return tab_list; } /** * meta_display_get_tab_next: * @display: a #MetaDisplay * @type: type of tab list * @screen: a #MetaScreen * @workspace: origin workspace * @window: (allow-none): starting window * @backward: If %TRUE, look for the previous window. * * Determine the next window that should be displayed for Alt-TAB * functionality. * * Returns: (transfer none): Next window * */ MetaWindow* meta_display_get_tab_next (MetaDisplay *display, MetaTabList type, MetaScreen *screen, MetaWorkspace *workspace, MetaWindow *window, gboolean backward) { gboolean skip; GList *tab_list; MetaWindow *ret; tab_list = meta_display_get_tab_list(display, type, screen, workspace); if (tab_list == NULL) return NULL; if (window != NULL) { g_assert (window->display == display); if (backward) ret = find_tab_backward (display, type, screen, workspace, g_list_find (tab_list, window), TRUE); else ret = find_tab_forward (display, type, screen, workspace, g_list_find (tab_list, window), TRUE); } else { skip = display->focus_window != NULL && tab_list->data == display->focus_window; if (backward) ret = find_tab_backward (display, type, screen, workspace, tab_list, skip); else ret = find_tab_forward (display, type, screen, workspace, tab_list, skip); } g_list_free (tab_list); return ret; } /** * meta_display_get_tab_current: * @display: a #MetaDisplay * @type: type of tab list * @screen: a #MetaScreen * @workspace: origin workspace * * Determine the active window that should be displayed for Alt-TAB. * * Returns: (transfer none): Current window * */ MetaWindow* meta_display_get_tab_current (MetaDisplay *display, MetaTabList type, MetaScreen *screen, MetaWorkspace *workspace) { MetaWindow *window; window = display->focus_window; if (window != NULL && window->screen == screen && IN_TAB_CHAIN (window, type) && (workspace == NULL || meta_window_located_on_workspace (window, workspace))) return window; else return NULL; } int meta_resize_gravity_from_grab_op (MetaGrabOp op) { int gravity; gravity = -1; switch (op) { case META_GRAB_OP_RESIZING_SE: case META_GRAB_OP_KEYBOARD_RESIZING_SE: gravity = NorthWestGravity; break; case META_GRAB_OP_KEYBOARD_RESIZING_S: case META_GRAB_OP_RESIZING_S: gravity = NorthGravity; break; case META_GRAB_OP_KEYBOARD_RESIZING_SW: case META_GRAB_OP_RESIZING_SW: gravity = NorthEastGravity; break; case META_GRAB_OP_KEYBOARD_RESIZING_N: case META_GRAB_OP_RESIZING_N: gravity = SouthGravity; break; case META_GRAB_OP_KEYBOARD_RESIZING_NE: case META_GRAB_OP_RESIZING_NE: gravity = SouthWestGravity; break; case META_GRAB_OP_KEYBOARD_RESIZING_NW: case META_GRAB_OP_RESIZING_NW: gravity = SouthEastGravity; break; case META_GRAB_OP_KEYBOARD_RESIZING_E: case META_GRAB_OP_RESIZING_E: gravity = WestGravity; break; case META_GRAB_OP_KEYBOARD_RESIZING_W: case META_GRAB_OP_RESIZING_W: gravity = EastGravity; break; case META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN: gravity = CenterGravity; break; default: break; } return gravity; } static MetaScreen* find_screen_for_selection (MetaDisplay *display, Window owner, Atom selection) { GSList *tmp; tmp = display->screens; while (tmp != NULL) { MetaScreen *screen = tmp->data; if (screen->wm_sn_selection_window == owner && screen->wm_sn_atom == selection) return screen; tmp = tmp->next; } return NULL; } /* from fvwm2, Copyright Matthias Clasen, Dominik Vogt */ static gboolean convert_property (MetaDisplay *display, MetaScreen *screen, Window w, Atom target, Atom property) { #define N_TARGETS 4 Atom conversion_targets[N_TARGETS]; long icccm_version[] = { 2, 0 }; conversion_targets[0] = display->atom_TARGETS; conversion_targets[1] = display->atom_MULTIPLE; conversion_targets[2] = display->atom_TIMESTAMP; conversion_targets[3] = display->atom_VERSION; meta_error_trap_push_with_return (display); if (target == display->atom_TARGETS) XChangeProperty (display->xdisplay, w, property, XA_ATOM, 32, PropModeReplace, (unsigned char *)conversion_targets, N_TARGETS); else if (target == display->atom_TIMESTAMP) XChangeProperty (display->xdisplay, w, property, XA_INTEGER, 32, PropModeReplace, (unsigned char *)&screen->wm_sn_timestamp, 1); else if (target == display->atom_VERSION) XChangeProperty (display->xdisplay, w, property, XA_INTEGER, 32, PropModeReplace, (unsigned char *)icccm_version, 2); else { meta_error_trap_pop_with_return (display); return FALSE; } if (meta_error_trap_pop_with_return (display) != Success) return FALSE; /* Be sure the PropertyNotify has arrived so we * can send SelectionNotify */ /* FIXME the error trap pop synced anyway, right? */ meta_topic (META_DEBUG_SYNC, "Syncing on %s\n", G_STRFUNC); XSync (display->xdisplay, False); return TRUE; } /* from fvwm2, Copyright Matthias Clasen, Dominik Vogt */ static void process_selection_request (MetaDisplay *display, XEvent *event) { XSelectionEvent reply; MetaScreen *screen; screen = find_screen_for_selection (display, event->xselectionrequest.owner, event->xselectionrequest.selection); if (screen == NULL) { char *str; meta_error_trap_push (display); str = XGetAtomName (display->xdisplay, event->xselectionrequest.selection); meta_error_trap_pop (display); meta_verbose ("Selection request with selection %s window 0x%lx not a WM_Sn selection we recognize\n", str ? str : "(bad atom)", event->xselectionrequest.owner); meta_XFree (str); return; } reply.type = SelectionNotify; reply.display = display->xdisplay; reply.requestor = event->xselectionrequest.requestor; reply.selection = event->xselectionrequest.selection; reply.target = event->xselectionrequest.target; reply.property = None; reply.time = event->xselectionrequest.time; if (event->xselectionrequest.target == display->atom_MULTIPLE) { if (event->xselectionrequest.property != None) { Atom type, *adata; int i, format; unsigned long num, rest; unsigned char *data; meta_error_trap_push_with_return (display); if (XGetWindowProperty (display->xdisplay, event->xselectionrequest.requestor, event->xselectionrequest.property, 0, 256, False, display->atom_ATOM_PAIR, &type, &format, &num, &rest, &data) != Success) { meta_error_trap_pop_with_return (display); return; } if (meta_error_trap_pop_with_return (display) == Success) { /* FIXME: to be 100% correct, should deal with rest > 0, * but since we have 4 possible targets, we will hardly ever * meet multiple requests with a length > 8 */ adata = (Atom*)data; i = 0; while (i < (int) num) { if (!convert_property (display, screen, event->xselectionrequest.requestor, adata[i], adata[i+1])) adata[i+1] = None; i += 2; } meta_error_trap_push (display); XChangeProperty (display->xdisplay, event->xselectionrequest.requestor, event->xselectionrequest.property, display->atom_ATOM_PAIR, 32, PropModeReplace, data, num); meta_error_trap_pop (display); meta_XFree (data); } } } else { if (event->xselectionrequest.property == None) event->xselectionrequest.property = event->xselectionrequest.target; if (convert_property (display, screen, event->xselectionrequest.requestor, event->xselectionrequest.target, event->xselectionrequest.property)) reply.property = event->xselectionrequest.property; } XSendEvent (display->xdisplay, event->xselectionrequest.requestor, False, 0L, (XEvent*)&reply); meta_verbose ("Handled selection request\n"); } static void process_selection_clear (MetaDisplay *display, XEvent *event) { /* We need to unmanage the screen on which we lost the selection */ MetaScreen *screen; screen = find_screen_for_selection (display, event->xselectionclear.window, event->xselectionclear.selection); if (screen != NULL) { meta_verbose ("Got selection clear for screen %d on display %s\n", screen->number, display->name); meta_display_unmanage_screen (display, screen, event->xselectionclear.time); /* display and screen may both be invalid memory... */ return; } { char *str; meta_error_trap_push (display); str = XGetAtomName (display->xdisplay, event->xselectionclear.selection); meta_error_trap_pop (display); meta_verbose ("Selection clear with selection %s window 0x%lx not a WM_Sn selection we recognize\n", str ? str : "(bad atom)", event->xselectionclear.window); meta_XFree (str); } } void meta_display_unmanage_screen (MetaDisplay *display, MetaScreen *screen, guint32 timestamp) { meta_verbose ("Unmanaging screen %d on display %s\n", screen->number, display->name); g_return_if_fail (g_slist_find (display->screens, screen) != NULL); meta_screen_free (screen, timestamp); display->screens = g_slist_remove (display->screens, screen); if (display->screens == NULL) meta_display_close (display, timestamp); } void meta_display_unmanage_windows_for_screen (MetaDisplay *display, MetaScreen *screen, guint32 timestamp) { GSList *tmp; GSList *winlist; winlist = meta_display_list_windows (display, META_LIST_INCLUDE_OVERRIDE_REDIRECT); winlist = g_slist_sort (winlist, meta_display_stack_cmp); g_slist_foreach (winlist, (GFunc)g_object_ref, NULL); /* Unmanage all windows */ tmp = winlist; while (tmp != NULL) { MetaWindow *window = tmp->data; /* Check if already unmanaged for safety - in particular, catch * the case where unmanaging a parent window can cause attached * dialogs to be (temporarily) unmanaged. */ if (!window->unmanaging) meta_window_unmanage (window, timestamp); g_object_unref (window); tmp = tmp->next; } g_slist_free (winlist); } int meta_display_stack_cmp (const void *a, const void *b) { MetaWindow *aw = (void*) a; MetaWindow *bw = (void*) b; if (aw->screen == bw->screen) return meta_stack_windows_cmp (aw->screen->stack, aw, bw); /* Then assume screens are stacked by number */ else if (aw->screen->number < bw->screen->number) return -1; else if (aw->screen->number > bw->screen->number) return 1; else return 0; /* not reached in theory, if windows on same display */ } /** * meta_display_sort_windows_by_stacking: * @display: a #MetaDisplay * @windows: (element-type MetaWindow): Set of windows * * Sorts a set of windows according to their current stacking order. If windows * from multiple screens are present in the set of input windows, then all the * windows on screen 0 are sorted below all the windows on screen 1, and so forth. * Since the stacking order of override-redirect windows isn't controlled by * Metacity, if override-redirect windows are in the input, the result may not * correspond to the actual stacking order in the X server. * * An example of using this would be to sort the list of transient dialogs for a * window into their current stacking order. * * Returns: (transfer container) (element-type MetaWindow): Input windows sorted by stacking order, from lowest to highest */ GSList * meta_display_sort_windows_by_stacking (MetaDisplay *display, GSList *windows) { GSList *copy = g_slist_copy (windows); copy = g_slist_sort (copy, meta_display_stack_cmp); return copy; } void meta_display_devirtualize_modifiers (MetaDisplay *display, MetaVirtualModifier modifiers, unsigned int *mask) { *mask = 0; if (modifiers & META_VIRTUAL_SHIFT_MASK) *mask |= ShiftMask; if (modifiers & META_VIRTUAL_CONTROL_MASK) *mask |= ControlMask; if (modifiers & META_VIRTUAL_ALT_MASK) *mask |= Mod1Mask; if (modifiers & META_VIRTUAL_META_MASK) *mask |= display->meta_mask; if (modifiers & META_VIRTUAL_HYPER_MASK) *mask |= display->hyper_mask; if (modifiers & META_VIRTUAL_SUPER_MASK) *mask |= display->super_mask; if (modifiers & META_VIRTUAL_MOD2_MASK) *mask |= Mod2Mask; if (modifiers & META_VIRTUAL_MOD3_MASK) *mask |= Mod3Mask; if (modifiers & META_VIRTUAL_MOD4_MASK) *mask |= Mod4Mask; if (modifiers & META_VIRTUAL_MOD5_MASK) *mask |= Mod5Mask; } static void update_window_grab_modifiers (MetaDisplay *display) { MetaVirtualModifier virtual_mods; unsigned int mods; virtual_mods = meta_prefs_get_mouse_button_mods (); meta_display_devirtualize_modifiers (display, virtual_mods, &mods); display->window_grab_modifiers = mods; } static void prefs_changed_callback (MetaPreference pref, void *data) { MetaDisplay *display = data; /* It may not be obvious why we regrab on focus mode * change; it's because we handle focus clicks a * bit differently for the different focus modes. */ if (pref == META_PREF_MOUSE_BUTTON_MODS || pref == META_PREF_FOCUS_MODE) { MetaDisplay *display = data; GSList *windows; GSList *tmp; windows = meta_display_list_windows (display, META_LIST_DEFAULT); /* Ungrab all */ tmp = windows; while (tmp != NULL) { MetaWindow *w = tmp->data; meta_display_ungrab_window_buttons (display, w->xwindow); meta_display_ungrab_focus_window_button (display, w); tmp = tmp->next; } /* change our modifier */ if (pref == META_PREF_MOUSE_BUTTON_MODS) update_window_grab_modifiers (display); /* Grab all */ tmp = windows; while (tmp != NULL) { MetaWindow *w = tmp->data; if (w->type != META_WINDOW_DOCK) { meta_display_grab_focus_window_button (display, w); meta_display_grab_window_buttons (display, w->xwindow); } tmp = tmp->next; } g_slist_free (windows); } else if (pref == META_PREF_AUDIBLE_BELL) { meta_bell_set_audible (display, meta_prefs_bell_is_audible ()); } } void meta_display_increment_focus_sentinel (MetaDisplay *display) { unsigned long data[1]; data[0] = meta_display_get_current_time (display); XChangeProperty (display->xdisplay, ((MetaScreen*) display->screens->data)->xroot, display->atom__MUTTER_SENTINEL, XA_CARDINAL, 32, PropModeReplace, (guchar*) data, 1); display->sentinel_counter += 1; } void meta_display_decrement_focus_sentinel (MetaDisplay *display) { display->sentinel_counter -= 1; if (display->sentinel_counter < 0) display->sentinel_counter = 0; } gboolean meta_display_focus_sentinel_clear (MetaDisplay *display) { return (display->sentinel_counter == 0); } static void sanity_check_timestamps (MetaDisplay *display, guint32 timestamp) { if (XSERVER_TIME_IS_BEFORE (timestamp, display->last_focus_time)) { meta_warning ("last_focus_time (%u) is greater than comparison " "timestamp (%u). This most likely represents a buggy " "client sending inaccurate timestamps in messages such as " "_NET_ACTIVE_WINDOW. Trying to work around...\n", display->last_focus_time, timestamp); display->last_focus_time = timestamp; } if (XSERVER_TIME_IS_BEFORE (timestamp, display->last_user_time)) { GSList *windows; GSList *tmp; meta_warning ("last_user_time (%u) is greater than comparison " "timestamp (%u). This most likely represents a buggy " "client sending inaccurate timestamps in messages such as " "_NET_ACTIVE_WINDOW. Trying to work around...\n", display->last_user_time, timestamp); display->last_user_time = timestamp; windows = meta_display_list_windows (display, META_LIST_DEFAULT); tmp = windows; while (tmp != NULL) { MetaWindow *window = tmp->data; if (XSERVER_TIME_IS_BEFORE (timestamp, window->net_wm_user_time)) { meta_warning ("%s appears to be one of the offending windows " "with a timestamp of %u. Working around...\n", window->desc, window->net_wm_user_time); meta_window_set_user_time (window, timestamp); } tmp = tmp->next; } g_slist_free (windows); } } void meta_display_set_input_focus_window (MetaDisplay *display, MetaWindow *window, gboolean focus_frame, guint32 timestamp) { request_xserver_input_focus_change (display, window->screen, window->client_type == META_WINDOW_CLIENT_TYPE_WAYLAND ? META_FOCUS_WAYLAND_CLIENT : META_FOCUS_X_CLIENT, window, focus_frame ? window->frame->xwindow : window->xwindow, timestamp); } void meta_display_request_take_focus (MetaDisplay *display, MetaWindow *window, guint32 timestamp) { if (timestamp_too_old (display, ×tamp)) return; meta_topic (META_DEBUG_FOCUS, "WM_TAKE_FOCUS(%s, %u)\n", window->desc, timestamp); meta_window_send_icccm_message (window, display->atom_WM_TAKE_FOCUS, timestamp); } void meta_display_set_input_focus_xwindow (MetaDisplay *display, MetaScreen *screen, MetaFocusType type, Window window, guint32 timestamp) { request_xserver_input_focus_change (display, screen, type, NULL, window, timestamp); } void meta_display_focus_the_no_focus_window (MetaDisplay *display, MetaScreen *screen, guint32 timestamp) { request_xserver_input_focus_change (display, screen, META_FOCUS_NO_FOCUS_WINDOW, NULL, screen->no_focus_window, timestamp); } void meta_display_remove_autoraise_callback (MetaDisplay *display) { if (display->autoraise_timeout_id != 0) { g_source_remove (display->autoraise_timeout_id); display->autoraise_timeout_id = 0; display->autoraise_window = NULL; } } void meta_display_overlay_key_activate (MetaDisplay *display) { g_signal_emit (display, display_signals[OVERLAY_KEY], 0); } void meta_display_accelerator_activate (MetaDisplay *display, guint action, ClutterKeyEvent *event) { g_signal_emit (display, display_signals[ACCELERATOR_ACTIVATED], 0, action, clutter_input_device_get_device_id (event->device), event->time); } gboolean meta_display_modifiers_accelerator_activate (MetaDisplay *display) { gboolean freeze; g_signal_emit (display, display_signals[MODIFIERS_ACCELERATOR_ACTIVATED], 0, &freeze); return freeze; } void meta_display_get_compositor_version (MetaDisplay *display, int *major, int *minor) { *major = display->composite_major_version; *minor = display->composite_minor_version; } /** * meta_display_get_xinput_opcode: (skip) * @display: a #MetaDisplay * */ int meta_display_get_xinput_opcode (MetaDisplay *display) { return display->xinput_opcode; } /** * meta_display_supports_extended_barriers: * @display: a #MetaDisplay * * Returns: whether the X server supports extended barrier * features as defined in version 2.3 of the XInput 2 * specification. * * Clients should use this method to determine whether their * interfaces should depend on new barrier features. */ gboolean meta_display_supports_extended_barriers (MetaDisplay *display) { return META_DISPLAY_HAS_XINPUT_23 (display) && !meta_is_wayland_compositor (); } /** * meta_display_get_xdisplay: (skip) * @display: a #MetaDisplay * */ Display * meta_display_get_xdisplay (MetaDisplay *display) { return display->xdisplay; } /** * meta_display_get_compositor: (skip) * @display: a #MetaDisplay * */ MetaCompositor * meta_display_get_compositor (MetaDisplay *display) { return display->compositor; } /** * meta_display_get_screens: * @display: a #MetaDisplay * * Returns: (transfer none) (element-type Meta.Screen): Screens for this display */ GSList * meta_display_get_screens (MetaDisplay *display) { return display->screens; } gboolean meta_display_has_shape (MetaDisplay *display) { return META_DISPLAY_HAS_SHAPE (display); } /** * meta_display_get_focus_window: * @display: a #MetaDisplay * * Get our best guess as to the "currently" focused window (that is, * the window that we expect will be focused at the point when the X * server processes our next request). * * Return Value: (transfer none): The current focus window */ MetaWindow * meta_display_get_focus_window (MetaDisplay *display) { return display->focus_window; } int meta_display_get_damage_event_base (MetaDisplay *display) { return display->damage_event_base; } #ifdef HAVE_SHAPE int meta_display_get_shape_event_base (MetaDisplay *display) { return display->shape_event_base; } #endif /** * meta_display_get_leader_window: * @display: a #MetaDisplay * * Returns the window manager's leader window (as defined by the * _NET_SUPPORTING_WM_CHECK mechanism of EWMH). For use by plugins that wish * to attach additional custom properties to this window. * * Return value: (transfer none): xid of the leader window. **/ Window meta_display_get_leader_window (MetaDisplay *display) { return display->leader_window; } /** * meta_display_clear_mouse_mode: * @display: a #MetaDisplay * * Sets the mouse-mode flag to %FALSE, which means that motion events are * no longer ignored in mouse or sloppy focus. * This is an internal function. It should be used only for reimplementing * keybindings, and only in a manner compatible with core code. */ void meta_display_clear_mouse_mode (MetaDisplay *display) { display->mouse_mode = FALSE; }