/* -*- 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, see . */ /** * SECTION:display * @title: MetaDisplay * @short_description: Mutter display representation * * The display is represented as a #MetaDisplay struct. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include "backends/meta-backend-private.h" #include "backends/meta-cursor-sprite-xcursor.h" #include "backends/meta-cursor-tracker-private.h" #include "backends/meta-input-device-private.h" #include "backends/meta-input-mapper-private.h" #include "backends/meta-stage-private.h" #include "backends/x11/meta-backend-x11.h" #include "backends/x11/meta-event-x11.h" #include "backends/x11/cm/meta-backend-x11-cm.h" #include "backends/x11/nested/meta-backend-x11-nested.h" #include "clutter/x11/clutter-x11.h" #include "compositor/compositor-private.h" #include "compositor/meta-compositor-x11.h" #include "cogl/cogl.h" #include "core/bell.h" #include "core/boxes-private.h" #include "core/display-private.h" #include "core/events.h" #include "core/frame.h" #include "core/keybindings-private.h" #include "core/meta-clipboard-manager.h" #include "core/meta-workspace-manager-private.h" #include "core/util-private.h" #include "core/window-private.h" #include "core/workspace-private.h" #include "meta/compositor-mutter.h" #include "meta/compositor.h" #include "meta/main.h" #include "meta/meta-backend.h" #include "meta/meta-enum-types.h" #include "meta/meta-sound-player.h" #include "meta/meta-x11-errors.h" #include "meta/prefs.h" #include "x11/meta-startup-notification-x11.h" #include "x11/meta-x11-display-private.h" #include "x11/window-x11.h" #include "x11/xprops.h" #ifdef HAVE_WAYLAND #include "compositor/meta-compositor-native.h" #include "compositor/meta-compositor-server.h" #include "wayland/meta-xwayland-private.h" #include "wayland/meta-wayland-tablet-seat.h" #include "wayland/meta-wayland-tablet-pad.h" #endif #ifdef HAVE_NATIVE_BACKEND #include "backends/native/meta-backend-native.h" #endif /* * SECTION:pings * * Sometimes we want to see whether a window is responding, * so we send it a "ping" message and see whether it sends us back a "pong" * message within a reasonable time. Here we have a system which lets us * nominate one function to be called if we get the pong in time and another * function if we don't. The system is rather more complicated than it needs * to be, since we only ever use it to destroy windows which are asked to * close themselves and don't do so within a reasonable amount of time, and * therefore we always use the same callbacks. It's possible that we might * use it for other things in future, or on the other hand we might decide * that we're never going to do so and simplify it a bit. */ /** * MetaPingData: * * Describes a ping on a window. When we send a ping to a window, we build * one of these structs, and it eventually gets passed to the timeout function * or to the function which handles the response from the window. If the window * does or doesn't respond to the ping, we use this information to deal with * these facts; we have a handler function for each. */ typedef struct { MetaWindow *window; guint32 serial; guint ping_timeout_id; } MetaPingData; typedef struct _MetaDisplayPrivate { MetaContext *context; } MetaDisplayPrivate; G_DEFINE_TYPE_WITH_PRIVATE (MetaDisplay, meta_display, G_TYPE_OBJECT) /* Signals */ enum { CURSOR_UPDATED, X11_DISPLAY_SETUP, X11_DISPLAY_OPENED, X11_DISPLAY_CLOSING, OVERLAY_KEY, ACCELERATOR_ACTIVATED, MODIFIERS_ACCELERATOR_ACTIVATED, FOCUS_WINDOW, WINDOW_CREATED, WINDOW_DEMANDS_ATTENTION, WINDOW_MARKED_URGENT, GRAB_OP_BEGIN, GRAB_OP_END, SHOW_RESTART_MESSAGE, RESTART, SHOW_RESIZE_POPUP, GL_VIDEO_MEMORY_PURGED, SHOW_PAD_OSD, SHOW_OSD, PAD_MODE_SWITCH, WINDOW_ENTERED_MONITOR, WINDOW_LEFT_MONITOR, WORKSPACE_ADDED, WORKSPACE_REMOVED, WORKSPACE_SWITCHED, ACTIVE_WORKSPACE_CHANGED, IN_FULLSCREEN_CHANGED, SHOWING_DESKTOP_CHANGED, RESTACKED, WORKAREAS_CHANGED, CLOSING, INIT_XSERVER, LAST_SIGNAL }; enum { PROP_0, PROP_COMPOSITOR_MODIFIERS, PROP_FOCUS_WINDOW }; static guint display_signals [LAST_SIGNAL] = { 0 }; #define META_GRAB_OP_GET_BASE_TYPE(op) (op & 0x00FF) /* * 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 void on_monitors_changed_internal (MetaMonitorManager *monitor_manager, MetaDisplay *display); static void prefs_changed_callback (MetaPreference pref, void *data); static int mru_cmp (gconstpointer a, gconstpointer b); 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_COMPOSITOR_MODIFIERS: g_value_set_flags (value, meta_display_get_compositor_modifiers (display)); break; 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[CURSOR_UPDATED] = g_signal_new ("cursor-updated", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); display_signals[X11_DISPLAY_SETUP] = g_signal_new ("x11-display-setup", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); display_signals[X11_DISPLAY_OPENED] = g_signal_new ("x11-display-opened", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); display_signals[X11_DISPLAY_CLOSING] = g_signal_new ("x11-display-closing", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); 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, CLUTTER_TYPE_INPUT_DEVICE, 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, 2, 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, 2, META_TYPE_WINDOW, META_TYPE_GRAB_OP); /** * MetaDisplay::show-restart-message: * @display: the #MetaDisplay instance * @message: (allow-none): The message to display, or %NULL * to clear a previous restart message. * * The ::show-restart-message signal will be emitted to indicate * that the compositor should show a message during restart. This is * emitted when meta_restart() is called, either by Mutter * internally or by the embedding compositor. The message should be * immediately added to the Clutter stage in its final form - * ::restart will be emitted to exit the application and leave the * stage contents frozen as soon as the the stage is painted again. * * On case of failure to restart, this signal will be emitted again * with %NULL for @message. * * Returns: %TRUE means the message was added to the stage; %FALSE * indicates that the compositor did not show the message. */ display_signals[SHOW_RESTART_MESSAGE] = g_signal_new ("show-restart-message", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, g_signal_accumulator_true_handled, NULL, NULL, G_TYPE_BOOLEAN, 1, G_TYPE_STRING); /** * MetaDisplay::restart: * @display: the #MetaDisplay instance * * The ::restart signal is emitted to indicate that compositor * should reexec the process. This is * emitted when meta_restart() is called, either by Mutter * internally or by the embedding compositor. See also * ::show-restart-message. * * Returns: %FALSE to indicate that the compositor could not * be restarted. When the compositor is restarted, the signal * should not return. */ display_signals[RESTART] = g_signal_new ("restart", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, g_signal_accumulator_true_handled, NULL, NULL, G_TYPE_BOOLEAN, 0); display_signals[SHOW_RESIZE_POPUP] = g_signal_new ("show-resize-popup", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, g_signal_accumulator_true_handled, NULL, NULL, G_TYPE_BOOLEAN, 4, G_TYPE_BOOLEAN, META_TYPE_RECTANGLE, G_TYPE_INT, G_TYPE_INT); display_signals[GL_VIDEO_MEMORY_PURGED] = g_signal_new ("gl-video-memory-purged", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); /** * MetaDisplay::show-pad-osd: * @display: the #MetaDisplay instance * @pad: the pad device * @settings: the pad device settings * @layout_path: path to the layout image * @edition_mode: Whether the OSD should be shown in edition mode * @monitor_idx: Monitor to show the OSD on * * Requests the pad button mapping OSD to be shown. * * Returns: (transfer none) (nullable): The OSD actor */ display_signals[SHOW_PAD_OSD] = g_signal_new ("show-pad-osd", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, CLUTTER_TYPE_ACTOR, 5, CLUTTER_TYPE_INPUT_DEVICE, G_TYPE_SETTINGS, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_INT); display_signals[SHOW_OSD] = g_signal_new ("show-osd", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 3, G_TYPE_INT, G_TYPE_STRING, G_TYPE_STRING); display_signals[PAD_MODE_SWITCH] = g_signal_new ("pad-mode-switch", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 3, CLUTTER_TYPE_INPUT_DEVICE, G_TYPE_UINT, G_TYPE_UINT); display_signals[WINDOW_ENTERED_MONITOR] = g_signal_new ("window-entered-monitor", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_INT, META_TYPE_WINDOW); display_signals[WINDOW_LEFT_MONITOR] = g_signal_new ("window-left-monitor", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_INT, META_TYPE_WINDOW); display_signals[IN_FULLSCREEN_CHANGED] = g_signal_new ("in-fullscreen-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); display_signals[SHOWING_DESKTOP_CHANGED] = g_signal_new ("showing-desktop-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); display_signals[RESTACKED] = g_signal_new ("restacked", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); display_signals[WORKAREAS_CHANGED] = g_signal_new ("workareas-changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); display_signals[CLOSING] = g_signal_new ("closing", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); display_signals[INIT_XSERVER] = g_signal_new ("init-xserver", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, g_signal_accumulator_first_wins, NULL, NULL, G_TYPE_BOOLEAN, 1, G_TYPE_TASK); g_object_class_install_property (object_class, PROP_COMPOSITOR_MODIFIERS, g_param_spec_flags ("compositor-modifiers", "Compositor modifiers", "Modifiers reserved for compositor actions", CLUTTER_TYPE_MODIFIER_TYPE, 0, G_PARAM_READABLE)); 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)); } /** * ping_data_free: * * Destructor for #MetaPingData structs. Will destroy the * event source for the struct as well. */ static void ping_data_free (MetaPingData *ping_data) { /* Remove the timeout */ g_clear_handle_id (&ping_data->ping_timeout_id, g_source_remove); g_free (ping_data); } void meta_display_remove_pending_pings_for_window (MetaDisplay *display, MetaWindow *window) { GSList *tmp; GSList *dead; /* could obviously be more efficient, don't care */ /* build list to be removed */ dead = NULL; for (tmp = display->pending_pings; tmp; tmp = tmp->next) { MetaPingData *ping_data = tmp->data; if (ping_data->window == window) dead = g_slist_prepend (dead, ping_data); } /* remove what we found */ for (tmp = dead; tmp; tmp = tmp->next) { MetaPingData *ping_data = tmp->data; display->pending_pings = g_slist_remove (display->pending_pings, ping_data); ping_data_free (ping_data); } g_slist_free (dead); } static MetaCompositor * create_compositor (MetaDisplay *display) { MetaBackend *backend = meta_get_backend (); #ifdef HAVE_WAYLAND #ifdef HAVE_NATIVE_BACKEND if (META_IS_BACKEND_NATIVE (backend)) return META_COMPOSITOR (meta_compositor_native_new (display, backend)); #endif if (META_IS_BACKEND_X11_NESTED (backend)) return META_COMPOSITOR (meta_compositor_server_new (display, backend)); #endif return META_COMPOSITOR (meta_compositor_x11_new (display, backend)); } static void meta_display_init (MetaDisplay *disp) { /* Some stuff could go in here that's currently in _open, * but it doesn't really matter. */ } void meta_display_cancel_touch (MetaDisplay *display) { #ifdef HAVE_WAYLAND MetaWaylandCompositor *compositor; if (!meta_is_wayland_compositor ()) return; compositor = meta_wayland_compositor_get_default (); meta_wayland_touch_cancel (compositor->seat->touch); #endif } static void gesture_tracker_state_changed (MetaGestureTracker *tracker, ClutterEventSequence *sequence, MetaSequenceState state, MetaDisplay *display) { switch (state) { case META_SEQUENCE_NONE: case META_SEQUENCE_PENDING_END: return; case META_SEQUENCE_ACCEPTED: meta_display_cancel_touch (display); G_GNUC_FALLTHROUGH; case META_SEQUENCE_REJECTED: { MetaBackend *backend; backend = meta_get_backend (); meta_backend_finish_touch_sequence (backend, sequence, state); break; } } } static void on_ui_scaling_factor_changed (MetaSettings *settings, MetaDisplay *display) { meta_display_reload_cursor (display); } static gboolean meta_display_init_x11_display (MetaDisplay *display, GError **error) { MetaX11Display *x11_display; x11_display = meta_x11_display_new (display, error); if (!x11_display) return FALSE; display->x11_display = x11_display; g_signal_emit (display, display_signals[X11_DISPLAY_SETUP], 0); meta_x11_display_create_guard_window (x11_display); if (!display->display_opening) { g_signal_emit (display, display_signals[X11_DISPLAY_OPENED], 0); meta_display_manage_all_xwindows (display); meta_compositor_redirect_x11_windows (display->compositor); } return TRUE; } #ifdef HAVE_WAYLAND gboolean meta_display_init_x11_finish (MetaDisplay *display, GAsyncResult *result, GError **error) { MetaX11Display *x11_display; g_assert (g_task_get_source_tag (G_TASK (result)) == meta_display_init_x11); if (!g_task_propagate_boolean (G_TASK (result), error)) { if (*error == NULL) g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Unknown error"); return FALSE; } if (display->x11_display) return TRUE; x11_display = meta_x11_display_new (display, error); if (!x11_display) return FALSE; display->x11_display = x11_display; g_signal_emit (display, display_signals[X11_DISPLAY_SETUP], 0); meta_x11_display_create_guard_window (x11_display); if (!display->display_opening) { g_signal_emit (display, display_signals[X11_DISPLAY_OPENED], 0); meta_x11_display_set_cm_selection (x11_display); meta_display_manage_all_xwindows (display); meta_compositor_redirect_x11_windows (display->compositor); } return TRUE; } static void on_xserver_started (MetaXWaylandManager *manager, GAsyncResult *result, gpointer user_data) { g_autoptr (GTask) task = user_data; MetaDisplay *display = g_task_get_source_object (task); GError *error = NULL; gboolean retval = FALSE; if (!meta_xwayland_start_xserver_finish (manager, result, &error)) { if (error) g_task_return_error (task, error); else g_task_return_boolean (task, FALSE); return; } g_signal_emit (display, display_signals[INIT_XSERVER], 0, task, &retval); if (!retval) { /* No handlers for this signal, proceed right away */ g_task_return_boolean (task, TRUE); } } void meta_display_init_x11 (MetaDisplay *display, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default (); g_autoptr (GTask) task = NULL; task = g_task_new (display, cancellable, callback, user_data); g_task_set_source_tag (task, meta_display_init_x11); meta_xwayland_start_xserver (&compositor->xwayland_manager, cancellable, (GAsyncReadyCallback) on_xserver_started, g_steal_pointer (&task)); } static void on_x11_initialized (MetaDisplay *display, GAsyncResult *result, gpointer user_data) { g_autoptr (GError) error = NULL; if (!meta_display_init_x11_finish (display, result, &error)) g_critical ("Failed to init X11 display: %s", error->message); } #endif void meta_display_shutdown_x11 (MetaDisplay *display) { if (!display->x11_display) return; g_signal_emit (display, display_signals[X11_DISPLAY_CLOSING], 0); g_object_run_dispose (G_OBJECT (display->x11_display)); g_clear_object (&display->x11_display); } MetaDisplay * meta_display_new (MetaContext *context, GError **error) { MetaDisplay *display; MetaDisplayPrivate *priv; int i; guint32 timestamp; Window old_active_xwindow = None; MetaBackend *backend = meta_get_backend (); MetaMonitorManager *monitor_manager; MetaSettings *settings; g_assert (the_display == NULL); display = the_display = g_object_new (META_TYPE_DISPLAY, NULL); priv = meta_display_get_instance_private (display); priv->context = context; display->closing = 0; display->display_opening = TRUE; display->pending_pings = NULL; display->autoraise_timeout_id = 0; display->autoraise_window = NULL; display->focus_window = NULL; display->workspace_manager = NULL; display->x11_display = NULL; display->current_cursor = -1; /* invalid/unset */ display->tile_preview_timeout_id = 0; display->check_fullscreen_later = 0; display->work_area_later = 0; display->mouse_mode = TRUE; /* Only relevant for mouse or sloppy focus */ display->allow_terminal_deactivation = TRUE; /* Only relevant for when a terminal has the focus */ i = 0; while (i < N_IGNORED_CROSSING_SERIALS) { display->ignored_crossing_serials[i] = 0; ++i; } display->current_time = META_CURRENT_TIME; display->grab_resize_timeout_id = 0; display->grab_have_keyboard = FALSE; display->grab_op = META_GRAB_OP_NONE; display->grab_window = NULL; display->grab_tile_mode = META_TILE_NONE; display->grab_tile_monitor_number = -1; meta_display_cleanup_edges (display); meta_display_init_keys (display); meta_prefs_add_listener (prefs_changed_callback, display); /* Get events */ meta_display_init_events (display); display->stamps = g_hash_table_new (g_int64_hash, g_int64_equal); display->wayland_windows = g_hash_table_new (NULL, NULL); monitor_manager = meta_backend_get_monitor_manager (backend); g_signal_connect (monitor_manager, "monitors-changed-internal", G_CALLBACK (on_monitors_changed_internal), display); display->pad_action_mapper = meta_pad_action_mapper_new (monitor_manager); settings = meta_backend_get_settings (backend); g_signal_connect (settings, "ui-scaling-factor-changed", G_CALLBACK (on_ui_scaling_factor_changed), display); display->compositor = create_compositor (display); meta_display_set_cursor (display, META_CURSOR_DEFAULT); display->stack = meta_stack_new (display); display->stack_tracker = meta_stack_tracker_new (display); display->workspace_manager = meta_workspace_manager_new (display); display->startup_notification = meta_startup_notification_new (display); display->bell = meta_bell_new (display); display->selection = meta_selection_new (display); meta_clipboard_manager_init (display); #ifdef HAVE_WAYLAND if (meta_is_wayland_compositor ()) { MetaX11DisplayPolicy x11_display_policy; x11_display_policy = meta_context_get_x11_display_policy (context); if (x11_display_policy == META_X11_DISPLAY_POLICY_MANDATORY) { meta_display_init_x11 (display, NULL, (GAsyncReadyCallback) on_x11_initialized, NULL); } timestamp = meta_display_get_current_time_roundtrip (display); } else #endif { if (!meta_display_init_x11_display (display, error)) { g_object_unref (display); return NULL; } timestamp = display->x11_display->timestamp; } display->last_focus_time = timestamp; display->last_user_time = timestamp; if (!meta_is_wayland_compositor ()) meta_prop_get_window (display->x11_display, display->x11_display->xroot, display->x11_display->atom__NET_ACTIVE_WINDOW, &old_active_xwindow); if (!meta_compositor_do_manage (display->compositor, error)) { g_object_unref (display); return NULL; } if (display->x11_display) { g_signal_emit (display, display_signals[X11_DISPLAY_OPENED], 0); meta_x11_display_restore_active_workspace (display->x11_display); meta_x11_display_create_guard_window (display->x11_display); } /* Set up touch support */ display->gesture_tracker = meta_gesture_tracker_new (); g_signal_connect (display->gesture_tracker, "state-changed", G_CALLBACK (gesture_tracker_state_changed), display); /* We know that if mutter is running as a Wayland compositor, * we start out with no windows. */ if (!meta_is_wayland_compositor ()) meta_display_manage_all_xwindows (display); if (old_active_xwindow != None) { MetaWindow *old_active_window; old_active_window = meta_x11_display_lookup_x_window (display->x11_display, old_active_xwindow); if (old_active_window) meta_window_focus (old_active_window, timestamp); else meta_display_unset_input_focus (display, timestamp); } else { meta_display_unset_input_focus (display, timestamp); } display->sound_player = g_object_new (META_TYPE_SOUND_PLAYER, NULL); /* Done opening new display */ display->display_opening = FALSE; return display; } 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 *prev; GSList *tmp; GHashTableIter iter; gpointer key, value; winlist = NULL; if (display->x11_display) { g_hash_table_iter_init (&iter, display->x11_display->xids); while (g_hash_table_iter_next (&iter, &key, &value)) { MetaWindow *window = value; if (!META_IS_WINDOW (window) || window->unmanaging) 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) || window->unmanaging) 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; } if (flags & META_LIST_SORTED) winlist = g_slist_sort (winlist, mru_cmp); return winlist; } void meta_display_close (MetaDisplay *display, guint32 timestamp) { g_assert (display != NULL); if (display->closing != 0) { /* The display's already been closed. */ return; } g_assert (display == the_display); display->closing += 1; g_signal_emit (display, display_signals[CLOSING], 0); meta_compositor_unmanage (display->compositor); meta_display_unmanage_windows (display, timestamp); meta_prefs_remove_listener (prefs_changed_callback, display); meta_display_remove_autoraise_callback (display); g_clear_object (&display->gesture_tracker); g_clear_handle_id (&display->focus_timeout_id, g_source_remove); g_clear_handle_id (&display->tile_preview_timeout_id, g_source_remove); if (display->work_area_later != 0) meta_later_remove (display->work_area_later); if (display->check_fullscreen_later != 0) meta_later_remove (display->check_fullscreen_later); /* Stop caring about events */ meta_display_free_events (display); g_clear_pointer (&display->compositor, meta_compositor_destroy); meta_display_shutdown_x11 (display); g_clear_object (&display->stack); g_clear_pointer (&display->stack_tracker, meta_stack_tracker_free); /* Must be after all calls to meta_window_unmanage() since they * unregister windows */ g_hash_table_destroy (display->wayland_windows); g_hash_table_destroy (display->stamps); meta_display_shutdown_keys (display); g_clear_object (&display->bell); g_clear_object (&display->startup_notification); g_clear_object (&display->workspace_manager); g_clear_object (&display->sound_player); meta_clipboard_manager_shutdown (display); g_clear_object (&display->selection); g_clear_object (&display->pad_action_mapper); the_display = NULL; } /** * 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->x11_display->xdisplay == xdisplay) return the_display; meta_warning ("Could not find display for X display %p, probably going to crash", 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; } static inline gboolean grab_op_is_window (MetaGrabOp op) { return META_GRAB_OP_GET_BASE_TYPE (op) == META_GRAB_OP_WINDOW_BASE; } gboolean meta_grab_op_is_mouse (MetaGrabOp op) { if (!grab_op_is_window (op)) return FALSE; return (op & META_GRAB_OP_WINDOW_FLAG_KEYBOARD) == 0; } gboolean meta_grab_op_is_keyboard (MetaGrabOp op) { if (!grab_op_is_window (op)) return FALSE; return (op & META_GRAB_OP_WINDOW_FLAG_KEYBOARD) != 0; } gboolean meta_grab_op_is_resizing (MetaGrabOp op) { if (!grab_op_is_window (op)) return FALSE; return (op & META_GRAB_OP_WINDOW_DIR_MASK) != 0 || op == META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN; } gboolean meta_grab_op_is_moving (MetaGrabOp op) { if (!grab_op_is_window (op)) return FALSE; return !meta_grab_op_is_resizing (op); } /** * meta_display_windows_are_interactable: * @op: A #MetaGrabOp * * Whether windows can be interacted with. */ gboolean meta_display_windows_are_interactable (MetaDisplay *display) { switch (display->event_route) { case META_EVENT_ROUTE_NORMAL: case META_EVENT_ROUTE_WAYLAND_POPUP: 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; } guint32 meta_display_get_current_time_roundtrip (MetaDisplay *display) { if (meta_is_wayland_compositor ()) /* Xwayland uses monotonic clock, so lets use it here as well */ return (guint32) (g_get_monotonic_time () / 1000); else return meta_x11_display_get_current_time_roundtrip (display->x11_display); } /** * 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 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->display->stack) != window) { if (meta_window_has_pointer (window)) meta_window_raise (window); else meta_topic (META_DEBUG_FOCUS, "Pointer not inside window, not raising %s", window->desc); } return G_SOURCE_REMOVE; } void meta_display_queue_autoraise_callback (MetaDisplay *display, MetaWindow *window) { meta_topic (META_DEBUG_FOCUS, "Queuing an autoraise timeout for %s with delay %d", window->desc, meta_prefs_get_auto_raise_delay ()); g_clear_handle_id (&display->autoraise_timeout_id, g_source_remove); display->autoraise_timeout_id = g_timeout_add_full (G_PRIORITY_DEFAULT, meta_prefs_get_auto_raise_delay (), window_raise_with_delay_callback, window, NULL); g_source_set_name_by_id (display->autoraise_timeout_id, "[mutter] window_raise_with_delay_callback"); display->autoraise_window = window; } void meta_display_sync_wayland_input_focus (MetaDisplay *display) { #ifdef HAVE_WAYLAND MetaWaylandCompositor *compositor = meta_wayland_compositor_get_default (); MetaWindow *focus_window = NULL; MetaBackend *backend = meta_get_backend (); ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend); ClutterSeat *seat = clutter_backend_get_default_seat (clutter_backend); MetaStage *stage = META_STAGE (meta_backend_get_stage (backend)); gboolean is_no_focus_xwindow = FALSE; if (display->x11_display) is_no_focus_xwindow = meta_x11_display_xwindow_is_a_no_focus_window (display->x11_display, display->x11_display->focus_xwindow); if (!meta_display_windows_are_interactable (display)) focus_window = NULL; else if (is_no_focus_xwindow) focus_window = NULL; else if (display->focus_window && display->focus_window->surface) focus_window = display->focus_window; else meta_topic (META_DEBUG_FOCUS, "Focus change has no effect, because there is no matching wayland surface"); meta_stage_set_active (stage, focus_window == NULL); meta_wayland_compositor_set_input_focus (compositor, focus_window); clutter_stage_repick_device (CLUTTER_STAGE (stage), clutter_seat_get_pointer (seat)); #endif } void meta_display_update_focus_window (MetaDisplay *display, MetaWindow *window) { if (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", 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; meta_window_set_focused_internal (previous, FALSE); } display->focus_window = window; if (display->focus_window) { meta_topic (META_DEBUG_FOCUS, "* Focus --> %s", display->focus_window->desc); meta_window_set_focused_internal (display->focus_window, TRUE); } else meta_topic (META_DEBUG_FOCUS, "* Focus --> NULL"); if (meta_is_wayland_compositor ()) meta_display_sync_wayland_input_focus (display); g_object_notify (G_OBJECT (display), "focus-window"); } gboolean meta_display_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 == META_CURRENT_TIME) { *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; } void meta_display_set_input_focus (MetaDisplay *display, MetaWindow *window, gboolean focus_frame, guint32 timestamp) { if (meta_display_timestamp_too_old (display, ×tamp)) return; if (display->x11_display) { meta_x11_display_set_input_focus (display->x11_display, window, focus_frame, timestamp); } meta_display_update_focus_window (display, window); display->last_focus_time = timestamp; if (window == NULL || window != display->autoraise_window) meta_display_remove_autoraise_callback (display); } void meta_display_unset_input_focus (MetaDisplay *display, guint32 timestamp) { meta_display_set_input_focus (display, NULL, FALSE, timestamp); } 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); } MetaWindow* meta_display_lookup_stamp (MetaDisplay *display, guint64 stamp) { return g_hash_table_lookup (display->stamps, &stamp); } void meta_display_register_stamp (MetaDisplay *display, guint64 *stampp, MetaWindow *window) { g_return_if_fail (g_hash_table_lookup (display->stamps, stampp) == NULL); g_hash_table_insert (display->stamps, stampp, window); } void meta_display_unregister_stamp (MetaDisplay *display, guint64 stamp) { g_return_if_fail (g_hash_table_lookup (display->stamps, &stamp) != NULL); g_hash_table_remove (display->stamps, &stamp); } MetaWindow* meta_display_lookup_stack_id (MetaDisplay *display, guint64 stack_id) { if (META_STACK_ID_IS_X11 (stack_id)) { if (!display->x11_display) return NULL; return meta_x11_display_lookup_x_window (display->x11_display, (Window)stack_id); } else { return meta_display_lookup_stamp (display, stack_id); } } /* We return a pointer into a ring of static buffers. This is to make * using this function for debug-logging convenient and avoid temporary * strings that must be freed. */ const char * meta_display_describe_stack_id (MetaDisplay *display, guint64 stack_id) { /* 0x<64-bit: 16 characters> (<10 characters of title>)\0' */ static char buffer[5][32]; MetaWindow *window; static int pos = 0; char *result; result = buffer[pos]; pos = (pos + 1) % 5; window = meta_display_lookup_stack_id (display, stack_id); if (window && window->title) snprintf (result, sizeof(buffer[0]), "%#" G_GINT64_MODIFIER "x (%.10s)", stack_id, window->title); else snprintf (result, sizeof(buffer[0]), "%#" G_GINT64_MODIFIER "x", stack_id); return result; } void meta_display_notify_window_created (MetaDisplay *display, MetaWindow *window) { COGL_TRACE_BEGIN_SCOPED (MetaDisplayNotifyWindowCreated, "Display (notify window created)"); g_signal_emit (display, display_signals[WINDOW_CREATED], 0, 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; } static void root_cursor_prepare_at (MetaCursorSpriteXcursor *sprite_xcursor, float best_scale, int x, int y, MetaDisplay *display) { MetaCursorSprite *cursor_sprite = META_CURSOR_SPRITE (sprite_xcursor); MetaBackend *backend = meta_get_backend (); if (meta_is_stage_views_scaled ()) { if (best_scale != 0.0f) { float ceiled_scale; ceiled_scale = ceilf (best_scale); meta_cursor_sprite_xcursor_set_theme_scale (sprite_xcursor, (int) ceiled_scale); meta_cursor_sprite_set_texture_scale (cursor_sprite, 1.0 / ceiled_scale); } } else { MetaMonitorManager *monitor_manager = meta_backend_get_monitor_manager (backend); MetaLogicalMonitor *logical_monitor; logical_monitor = meta_monitor_manager_get_logical_monitor_at (monitor_manager, x, y); /* Reload the cursor texture if the scale has changed. */ if (logical_monitor) { meta_cursor_sprite_xcursor_set_theme_scale (sprite_xcursor, logical_monitor->scale); meta_cursor_sprite_set_texture_scale (cursor_sprite, 1.0); } } } static void manage_root_cursor_sprite_scale (MetaDisplay *display, MetaCursorSpriteXcursor *sprite_xcursor) { g_signal_connect_object (sprite_xcursor, "prepare-at", G_CALLBACK (root_cursor_prepare_at), display, 0); } void meta_display_reload_cursor (MetaDisplay *display) { MetaCursor cursor = display->current_cursor; MetaCursorSpriteXcursor *sprite_xcursor; MetaBackend *backend = meta_get_backend (); MetaCursorTracker *cursor_tracker = meta_backend_get_cursor_tracker (backend); sprite_xcursor = meta_cursor_sprite_xcursor_new (cursor); if (meta_is_wayland_compositor ()) manage_root_cursor_sprite_scale (display, sprite_xcursor); meta_cursor_tracker_set_root_cursor (cursor_tracker, META_CURSOR_SPRITE (sprite_xcursor)); g_object_unref (sprite_xcursor); g_signal_emit (display, display_signals[CURSOR_UPDATED], 0, display); } void meta_display_set_cursor (MetaDisplay *display, MetaCursor cursor) { if (cursor == display->current_cursor) return; display->current_cursor = cursor; meta_display_reload_cursor (display); } void meta_display_update_cursor (MetaDisplay *display) { meta_display_set_cursor (display, meta_cursor_for_grab_op (display->grab_op)); } static MetaWindow * get_first_freefloating_window (MetaWindow *window) { while (meta_window_is_attached_dialog (window)) window = meta_window_get_transient_for (window); /* Attached dialogs should always have a non-NULL transient-for */ g_assert (window != NULL); return window; } static MetaEventRoute get_event_route_from_grab_op (MetaGrabOp op) { switch (META_GRAB_OP_GET_BASE_TYPE (op)) { case META_GRAB_OP_NONE: /* begin_grab_op shouldn't be called with META_GRAB_OP_NONE. */ g_assert_not_reached (); case META_GRAB_OP_WINDOW_BASE: return META_EVENT_ROUTE_WINDOW_OP; case META_GRAB_OP_COMPOSITOR: /* begin_grab_op shouldn't be called with META_GRAB_OP_COMPOSITOR. */ g_assert_not_reached (); case META_GRAB_OP_WAYLAND_POPUP: return META_EVENT_ROUTE_WAYLAND_POPUP; case META_GRAB_OP_FRAME_BUTTON: return META_EVENT_ROUTE_FRAME_BUTTON; default: g_assert_not_reached (); return 0; } } gboolean meta_display_begin_grab_op (MetaDisplay *display, MetaWindow *window, MetaGrabOp op, gboolean pointer_already_grabbed, gboolean frame_action, int button, gulong modmask, /* XXX - ignored */ guint32 timestamp, int root_x, int root_y) { MetaBackend *backend = meta_get_backend (); MetaWindow *grab_window = NULL; MetaEventRoute event_route; g_assert (window != NULL); meta_topic (META_DEBUG_WINDOW_OPS, "Doing grab op %u on window %s button %d pointer already grabbed: %d pointer pos %d,%d", op, window->desc, button, pointer_already_grabbed, root_x, root_y); if (display->grab_op != META_GRAB_OP_NONE) { meta_warning ("Attempt to perform window operation %u on window %s when operation %u on %s already in effect", op, window->desc, display->grab_op, display->grab_window ? display->grab_window->desc : "none"); return FALSE; } event_route = get_event_route_from_grab_op (op); if (event_route == META_EVENT_ROUTE_WINDOW_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; } } grab_window = window; /* If we're trying to move a window, move the first * non-attached dialog instead. */ if (meta_grab_op_is_moving (op)) grab_window = get_first_freefloating_window (window); g_assert (grab_window != NULL); g_assert (op != META_GRAB_OP_NONE); display->grab_have_pointer = FALSE; if (pointer_already_grabbed) display->grab_have_pointer = TRUE; if (META_IS_BACKEND_X11 (meta_get_backend ()) && display->x11_display) { /* Since grab operations often happen as a result of implicit * pointer operations on the display X11 connection, we need * to ungrab here to ensure that the backend's X11 can take * the device grab. */ XIUngrabDevice (display->x11_display->xdisplay, META_VIRTUAL_CORE_POINTER_ID, timestamp); XSync (display->x11_display->xdisplay, False); } if (meta_backend_grab_device (backend, META_VIRTUAL_CORE_POINTER_ID, timestamp)) display->grab_have_pointer = TRUE; if (!display->grab_have_pointer && !meta_grab_op_is_keyboard (op)) { meta_topic (META_DEBUG_WINDOW_OPS, "XIGrabDevice() failed"); return FALSE; } /* Grab keys when beginning window ops; see #126497 */ if (event_route == META_EVENT_ROUTE_WINDOW_OP) { display->grab_have_keyboard = meta_window_grab_all_keys (grab_window, timestamp); if (!display->grab_have_keyboard) { meta_topic (META_DEBUG_WINDOW_OPS, "grabbing all keys failed, ungrabbing pointer"); meta_backend_ungrab_device (backend, META_VIRTUAL_CORE_POINTER_ID, timestamp); display->grab_have_pointer = FALSE; return FALSE; } } display->event_route = event_route; display->grab_op = op; display->grab_window = grab_window; display->grab_button = button; display->grab_tile_mode = grab_window->tile_mode; display->grab_tile_monitor_number = grab_window->tile_monitor_number; 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 = 0; display->grab_last_edge_resistance_flags = META_EDGE_RESISTANCE_DEFAULT; display->grab_frame_action = frame_action; meta_display_update_cursor (display); g_clear_handle_id (&display->grab_resize_timeout_id, g_source_remove); meta_topic (META_DEBUG_WINDOW_OPS, "Grab op %u on window %s successful", display->grab_op, window ? window->desc : "(null)"); meta_window_get_frame_rect (display->grab_window, &display->grab_initial_window_pos); display->grab_anchor_window_pos = display->grab_initial_window_pos; if (meta_is_wayland_compositor ()) { meta_display_sync_wayland_input_focus (display); meta_display_cancel_touch (display); } g_signal_emit (display, display_signals[GRAB_OP_BEGIN], 0, display->grab_window, display->grab_op); if (display->event_route == META_EVENT_ROUTE_WINDOW_OP) meta_window_grab_op_began (display->grab_window, display->grab_op); return TRUE; } void meta_display_end_grab_op (MetaDisplay *display, guint32 timestamp) { MetaWindow *grab_window = display->grab_window; MetaGrabOp grab_op = display->grab_op; meta_topic (META_DEBUG_WINDOW_OPS, "Ending grab op %u at time %u", grab_op, timestamp); if (display->event_route == META_EVENT_ROUTE_NORMAL || display->event_route == META_EVENT_ROUTE_COMPOSITOR_GRAB) return; g_assert (grab_window != NULL); /* We need to reset this early, since the * meta_window_grab_op_ended callback relies on this being * up to date. */ display->grab_op = META_GRAB_OP_NONE; if (display->event_route == META_EVENT_ROUTE_WINDOW_OP) { /* Clear out the edge cache */ meta_display_cleanup_edges (display); /* 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 (!meta_prefs_get_raise_on_click () && !display->grab_threshold_movement_reached) meta_window_raise (display->grab_window); meta_window_grab_op_ended (grab_window, grab_op); } if (display->grab_have_pointer) { MetaBackend *backend = meta_get_backend (); meta_backend_ungrab_device (backend, META_VIRTUAL_CORE_POINTER_ID, timestamp); } if (display->grab_have_keyboard) { meta_topic (META_DEBUG_WINDOW_OPS, "Ungrabbing all keys timestamp %u", timestamp); meta_window_ungrab_all_keys (grab_window, timestamp); } display->event_route = META_EVENT_ROUTE_NORMAL; display->grab_window = NULL; display->grab_button = 0; display->grab_tile_mode = META_TILE_NONE; display->grab_tile_monitor_number = -1; display->grab_anchor_root_x = 0; display->grab_anchor_root_y = 0; display->grab_latest_motion_x = 0; display->grab_latest_motion_y = 0; display->grab_last_moveresize_time = 0; display->grab_last_edge_resistance_flags = META_EDGE_RESISTANCE_DEFAULT; display->grab_frame_action = FALSE; meta_display_update_cursor (display); g_clear_handle_id (&display->grab_resize_timeout_id, g_source_remove); if (meta_is_wayland_compositor ()) meta_display_sync_wayland_input_focus (display); g_signal_emit (display, display_signals[GRAB_OP_END], 0, grab_window, grab_op); } /** * 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; } 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); } /* * 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 ()->x11_display->xdisplay, is_syncing); } } /** * meta_display_ping_timeout: * @data: All the information about this ping. It is a #MetaPingData * cast to a #gpointer in order to be passable to a timeout function. * This function will also free this parameter. * * Does whatever it is we decided to do when a window didn't respond * to a ping. We also remove the ping from the display's list of * pending pings. This function is called by the event loop when the timeout * times out which we created at the start of the ping. * * Returns: Always returns %FALSE, because this function is called as a * timeout and we don't want to run the timer again. */ static gboolean meta_display_ping_timeout (gpointer data) { MetaPingData *ping_data = data; MetaWindow *window = ping_data->window; MetaDisplay *display = window->display; meta_window_set_alive (window, FALSE); ping_data->ping_timeout_id = 0; meta_topic (META_DEBUG_PING, "Ping %u on window %s timed out", ping_data->serial, ping_data->window->desc); display->pending_pings = g_slist_remove (display->pending_pings, ping_data); ping_data_free (ping_data); return FALSE; } /** * meta_display_ping_window: * @display: The #MetaDisplay that the window is on * @window: The #MetaWindow to send the ping to * @timestamp: The timestamp of the ping. Used for uniqueness. * Cannot be CurrentTime; use a real timestamp! * * Sends a ping request to a window. The window must respond to * the request within a certain amount of time. If it does, we * will call one callback; if the time passes and we haven't had * a response, we call a different callback. The window must have * the hint showing that it can respond to a ping; if it doesn't, * we call the "got a response" callback immediately and return. * This function returns straight away after setting things up; * the callbacks will be called from the event loop. */ void meta_display_ping_window (MetaWindow *window, guint32 serial) { GSList *l; MetaDisplay *display = window->display; MetaPingData *ping_data; unsigned int check_alive_timeout; check_alive_timeout = meta_prefs_get_check_alive_timeout (); if (check_alive_timeout == 0) return; if (serial == 0) { meta_warning ("Tried to ping window %s with a bad serial! Not allowed.", window->desc); return; } if (!meta_window_can_ping (window)) return; for (l = display->pending_pings; l; l = l->next) { MetaPingData *ping_data = l->data; if (window == ping_data->window) { meta_topic (META_DEBUG_PING, "Window %s already is being pinged with serial %u", window->desc, ping_data->serial); return; } if (serial == ping_data->serial) { meta_warning ("Ping serial %u was reused for window %s, " "previous use was for window %s.", serial, window->desc, ping_data->window->desc); return; } } ping_data = g_new (MetaPingData, 1); ping_data->window = window; ping_data->serial = serial; ping_data->ping_timeout_id = g_timeout_add (check_alive_timeout, meta_display_ping_timeout, ping_data); g_source_set_name_by_id (ping_data->ping_timeout_id, "[mutter] meta_display_ping_timeout"); display->pending_pings = g_slist_prepend (display->pending_pings, ping_data); meta_topic (META_DEBUG_PING, "Sending ping with serial %u to window %s", serial, window->desc); META_WINDOW_GET_CLASS (window)->ping (window, serial); } /** * meta_display_pong_for_serial: * @display: the display we got the pong from * @serial: the serial in the pong response * * Process the pong (the response message) from the ping we sent * to the window. This involves removing the timeout, calling the * reply handler function, and freeing memory. */ void meta_display_pong_for_serial (MetaDisplay *display, guint32 serial) { GSList *tmp; meta_topic (META_DEBUG_PING, "Received a pong with serial %u", serial); for (tmp = display->pending_pings; tmp; tmp = tmp->next) { MetaPingData *ping_data = tmp->data; if (serial == ping_data->serial) { meta_topic (META_DEBUG_PING, "Matching ping found for pong %u", ping_data->serial); /* Remove the ping data from the list */ display->pending_pings = g_slist_remove (display->pending_pings, ping_data); /* Remove the timeout */ g_clear_handle_id (&ping_data->ping_timeout_id, g_source_remove); meta_window_set_alive (ping_data->window, TRUE); ping_data_free (ping_data); break; } } } static MetaGroup * get_focused_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_focused_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, 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 (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, 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 (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 * @workspace: (nullable): 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 contains windows that are on * @workspace or have the demands-attention hint set; otherwise it contains * all windows. * * Returns: (transfer container) (element-type Meta.Window): List of windows */ GList* meta_display_get_tab_list (MetaDisplay *display, MetaTabList type, 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. Collect unminimized windows * then minimized so minimized windows aren't in the way so much. */ for (tmp = mru_list; tmp; tmp = tmp->next) { MetaWindow *window = tmp->data; if (!window->minimized && IN_TAB_CHAIN (window, type)) tab_list = g_list_prepend (tab_list, window); } for (tmp = mru_list; tmp; tmp = tmp->next) { MetaWindow *window = tmp->data; if (window->minimized && 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 && !meta_window_located_on_workspace (l_window, 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 * @workspace: origin workspace * @window: (nullable): 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, MetaWorkspace *workspace, MetaWindow *window, gboolean backward) { gboolean skip; GList *tab_list; MetaWindow *ret; tab_list = meta_display_get_tab_list (display, type, workspace); if (tab_list == NULL) return NULL; if (window != NULL) { g_assert (window->display == display); if (backward) ret = find_tab_backward (display, type, workspace, g_list_find (tab_list, window), TRUE); else ret = find_tab_forward (display, type, 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, workspace, tab_list, skip); else ret = find_tab_forward (display, type, workspace, tab_list, skip); } g_list_free (tab_list); return ret; } /** * meta_display_get_tab_current: * @display: a #MetaDisplay * @type: type of tab list * @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, MetaWorkspace *workspace) { MetaWindow *window; window = display->focus_window; if (window != NULL && IN_TAB_CHAIN (window, type) && (workspace == NULL || meta_window_located_on_workspace (window, workspace))) return window; else return NULL; } MetaGravity meta_resize_gravity_from_grab_op (MetaGrabOp op) { MetaGravity gravity; gravity = -1; switch (op) { case META_GRAB_OP_RESIZING_SE: case META_GRAB_OP_KEYBOARD_RESIZING_SE: gravity = META_GRAVITY_NORTH_WEST; break; case META_GRAB_OP_KEYBOARD_RESIZING_S: case META_GRAB_OP_RESIZING_S: gravity = META_GRAVITY_NORTH; break; case META_GRAB_OP_KEYBOARD_RESIZING_SW: case META_GRAB_OP_RESIZING_SW: gravity = META_GRAVITY_NORTH_EAST; break; case META_GRAB_OP_KEYBOARD_RESIZING_N: case META_GRAB_OP_RESIZING_N: gravity = META_GRAVITY_SOUTH; break; case META_GRAB_OP_KEYBOARD_RESIZING_NE: case META_GRAB_OP_RESIZING_NE: gravity = META_GRAVITY_SOUTH_WEST; break; case META_GRAB_OP_KEYBOARD_RESIZING_NW: case META_GRAB_OP_RESIZING_NW: gravity = META_GRAVITY_SOUTH_EAST; break; case META_GRAB_OP_KEYBOARD_RESIZING_E: case META_GRAB_OP_RESIZING_E: gravity = META_GRAVITY_WEST; break; case META_GRAB_OP_KEYBOARD_RESIZING_W: case META_GRAB_OP_RESIZING_W: gravity = META_GRAVITY_EAST; break; case META_GRAB_OP_KEYBOARD_RESIZING_UNKNOWN: gravity = META_GRAVITY_CENTER; break; default: break; } return gravity; } void meta_display_manage_all_xwindows (MetaDisplay *display) { guint64 *_children; guint64 *children; int n_children, i; meta_stack_freeze (display->stack); meta_stack_tracker_get_stack (display->stack_tracker, &_children, &n_children); /* Copy the stack as it will be modified as part of the loop */ children = g_memdup2 (_children, sizeof (uint64_t) * n_children); for (i = 0; i < n_children; ++i) { if (!META_STACK_ID_IS_X11 (children[i])) continue; meta_window_x11_new (display, children[i], TRUE, META_COMP_EFFECT_NONE); } g_free (children); meta_stack_thaw (display->stack); } void meta_display_unmanage_windows (MetaDisplay *display, 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; return meta_stack_windows_cmp (aw->display->stack, aw, bw); } /** * 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; } static void prefs_changed_callback (MetaPreference pref, void *data) { MetaDisplay *display = data; switch (pref) { case META_PREF_DRAGGABLE_BORDER_WIDTH: meta_display_queue_retheme_all_windows (display); break; case META_PREF_CURSOR_THEME: case META_PREF_CURSOR_SIZE: meta_display_reload_cursor (display); break; default: break; } } void meta_display_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...", 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...", 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...", window->desc, window->net_wm_user_time); meta_window_set_user_time (window, timestamp); } tmp = tmp->next; } g_slist_free (windows); } } void meta_display_remove_autoraise_callback (MetaDisplay *display) { g_clear_handle_id (&display->autoraise_timeout_id, g_source_remove); 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_event_get_source_device ((ClutterEvent *) event), 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; } /** * meta_display_supports_extended_barriers: * @display: a #MetaDisplay * * Returns: whether pointer barriers can be supported. * * When running as an X compositor the X server needs XInput 2 * version 2.3. When running as a display server it is supported * when running on the native backend. * * Clients should use this method to determine whether their * interfaces should depend on new barrier features. */ gboolean meta_display_supports_extended_barriers (MetaDisplay *display) { #ifdef HAVE_NATIVE_BACKEND if (META_IS_BACKEND_NATIVE (meta_get_backend ())) return TRUE; #endif if (META_IS_BACKEND_X11_CM (meta_get_backend ())) { if (meta_is_wayland_compositor()) return FALSE; return META_X11_DISPLAY_HAS_XINPUT_23 (display->x11_display); } return FALSE; } /** * meta_display_get_context: * @display: a #MetaDisplay * * Returns: (transfer none): the #MetaContext */ MetaContext * meta_display_get_context (MetaDisplay *display) { MetaDisplayPrivate *priv = meta_display_get_instance_private (display); return priv->context; } /** * meta_display_get_compositor: (skip) * @display: a #MetaDisplay * */ MetaCompositor * meta_display_get_compositor (MetaDisplay *display) { return display->compositor; } /** * meta_display_get_x11_display: (skip) * @display: a #MetaDisplay * */ MetaX11Display * meta_display_get_x11_display (MetaDisplay *display) { return display->x11_display; } /** * meta_display_get_size: * @display: A #MetaDisplay * @width: (out): The width of the screen * @height: (out): The height of the screen * * Retrieve the size of the display. */ void meta_display_get_size (MetaDisplay *display, int *width, int *height) { MetaBackend *backend = meta_get_backend (); MetaMonitorManager *monitor_manager = meta_backend_get_monitor_manager (backend); int display_width, display_height; meta_monitor_manager_get_screen_size (monitor_manager, &display_width, &display_height); if (width != NULL) *width = display_width; if (height != NULL) *height = display_height; } /** * 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; } /** * 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; } MetaGestureTracker * meta_display_get_gesture_tracker (MetaDisplay *display) { return display->gesture_tracker; } gboolean meta_display_show_restart_message (MetaDisplay *display, const char *message) { gboolean result = FALSE; g_signal_emit (display, display_signals[SHOW_RESTART_MESSAGE], 0, message, &result); return result; } gboolean meta_display_request_restart (MetaDisplay *display) { gboolean result = FALSE; g_signal_emit (display, display_signals[RESTART], 0, &result); return result; } gboolean meta_display_show_resize_popup (MetaDisplay *display, gboolean show, MetaRectangle *rect, int display_w, int display_h) { gboolean result = FALSE; g_signal_emit (display, display_signals[SHOW_RESIZE_POPUP], 0, show, rect, display_w, display_h, &result); return result; } /** * meta_display_is_pointer_emulating_sequence: * @display: the display * @sequence: (nullable): a #ClutterEventSequence * * Tells whether the event sequence is the used for pointer emulation * and single-touch interaction. * * Returns: #TRUE if the sequence emulates pointer behavior **/ gboolean meta_display_is_pointer_emulating_sequence (MetaDisplay *display, ClutterEventSequence *sequence) { if (!sequence) return FALSE; return display->pointer_emulating_sequence == sequence; } void meta_display_request_pad_osd (MetaDisplay *display, ClutterInputDevice *pad, gboolean edition_mode) { MetaBackend *backend = meta_get_backend (); MetaInputMapper *input_mapper; const gchar *layout_path = NULL; ClutterActor *osd; MetaLogicalMonitor *logical_monitor; GSettings *settings; #ifdef HAVE_LIBWACOM WacomDevice *wacom_device; #endif /* Avoid emitting the signal while there is an OSD being currently * displayed, the first OSD will have to be dismissed before showing * any other one. */ if (display->current_pad_osd) return; input_mapper = meta_backend_get_input_mapper (meta_get_backend ()); if (input_mapper) { settings = meta_input_mapper_get_tablet_settings (input_mapper, pad); logical_monitor = meta_input_mapper_get_device_logical_monitor (input_mapper, pad); #ifdef HAVE_LIBWACOM wacom_device = meta_input_device_get_wacom_device (META_INPUT_DEVICE (pad)); layout_path = libwacom_get_layout_filename (wacom_device); #endif } if (!layout_path || !settings) return; if (!logical_monitor) logical_monitor = meta_backend_get_current_logical_monitor (backend); g_signal_emit (display, display_signals[SHOW_PAD_OSD], 0, pad, settings, layout_path, edition_mode, logical_monitor->number, &osd); if (osd) { display->current_pad_osd = osd; g_object_add_weak_pointer (G_OBJECT (display->current_pad_osd), (gpointer *) &display->current_pad_osd); } } gchar * meta_display_get_pad_action_label (MetaDisplay *display, ClutterInputDevice *pad, MetaPadActionType action_type, guint action_number) { gchar *label; /* First, lookup the action, as imposed by settings */ label = meta_pad_action_mapper_get_action_label (display->pad_action_mapper, pad, action_type, action_number); if (label) return label; #ifdef HAVE_WAYLAND /* Second, if this wayland, lookup the actions set by the clients */ if (meta_is_wayland_compositor ()) { MetaWaylandCompositor *compositor; MetaWaylandTabletSeat *tablet_seat; MetaWaylandTabletPad *tablet_pad = NULL; compositor = meta_wayland_compositor_get_default (); tablet_seat = meta_wayland_tablet_manager_ensure_seat (compositor->tablet_manager, compositor->seat); if (tablet_seat) tablet_pad = meta_wayland_tablet_seat_lookup_pad (tablet_seat, pad); if (tablet_pad) { label = meta_wayland_tablet_pad_get_label (tablet_pad, action_type, action_number); } if (label) return label; } #endif return NULL; } static void meta_display_show_osd (MetaDisplay *display, gint monitor_idx, const gchar *icon_name, const gchar *message) { g_signal_emit (display, display_signals[SHOW_OSD], 0, monitor_idx, icon_name, message); } static gint lookup_tablet_monitor (MetaDisplay *display, ClutterInputDevice *device) { MetaInputMapper *input_mapper; MetaLogicalMonitor *monitor; gint monitor_idx = -1; input_mapper = meta_backend_get_input_mapper (meta_get_backend ()); if (!input_mapper) return -1; monitor = meta_input_mapper_get_device_logical_monitor (input_mapper, device); if (monitor) { monitor_idx = meta_display_get_monitor_index_for_rect (display, &monitor->rect); } return monitor_idx; } void meta_display_show_tablet_mapping_notification (MetaDisplay *display, ClutterInputDevice *pad, const gchar *pretty_name) { if (!pretty_name) pretty_name = clutter_input_device_get_device_name (pad); meta_display_show_osd (display, lookup_tablet_monitor (display, pad), "input-tablet-symbolic", pretty_name); } void meta_display_notify_pad_group_switch (MetaDisplay *display, ClutterInputDevice *pad, const gchar *pretty_name, guint n_group, guint n_mode, guint n_modes) { GString *message; guint i; if (!pretty_name) pretty_name = clutter_input_device_get_device_name (pad); message = g_string_new (pretty_name); g_string_append_c (message, '\n'); for (i = 0; i < n_modes; i++) g_string_append (message, (i == n_mode) ? "⚫" : "⚪"); meta_display_show_osd (display, lookup_tablet_monitor (display, pad), "input-tablet-symbolic", message->str); g_signal_emit (display, display_signals[PAD_MODE_SWITCH], 0, pad, n_group, n_mode); g_string_free (message, TRUE); } void meta_display_foreach_window (MetaDisplay *display, MetaListWindowsFlags flags, MetaDisplayWindowFunc func, gpointer data) { GSList *windows; /* If we end up doing this often, just keeping a list * of windows might be sensible. */ windows = meta_display_list_windows (display, flags); g_slist_foreach (windows, (GFunc) func, data); g_slist_free (windows); } static void meta_display_resize_func (MetaWindow *window, gpointer user_data) { if (window->struts) { meta_window_update_struts (window); } meta_window_queue (window, META_QUEUE_MOVE_RESIZE); meta_window_recalc_features (window); } static void on_monitors_changed_internal (MetaMonitorManager *monitor_manager, MetaDisplay *display) { meta_workspace_manager_reload_work_areas (display->workspace_manager); /* Fix up monitor for all windows on this display */ meta_display_foreach_window (display, META_LIST_INCLUDE_OVERRIDE_REDIRECT, (MetaDisplayWindowFunc) meta_window_update_for_monitors_changed, 0); /* Queue a resize on all the windows */ meta_display_foreach_window (display, META_LIST_DEFAULT, meta_display_resize_func, 0); meta_display_queue_check_fullscreen (display); } void meta_display_restacked (MetaDisplay *display) { g_signal_emit (display, display_signals[RESTACKED], 0); } static gboolean meta_display_update_tile_preview_timeout (gpointer data) { MetaDisplay *display = data; MetaWindow *window = display->grab_window; gboolean needs_preview = FALSE; display->tile_preview_timeout_id = 0; if (window) { switch (display->preview_tile_mode) { case META_TILE_LEFT: case META_TILE_RIGHT: if (!META_WINDOW_TILED_SIDE_BY_SIDE (window)) needs_preview = TRUE; break; case META_TILE_MAXIMIZED: if (!META_WINDOW_MAXIMIZED (window)) needs_preview = TRUE; break; default: needs_preview = FALSE; break; } } if (needs_preview) { MetaRectangle tile_rect; int monitor; monitor = meta_window_get_current_tile_monitor_number (window); meta_window_get_tile_area (window, display->preview_tile_mode, &tile_rect); meta_compositor_show_tile_preview (display->compositor, window, &tile_rect, monitor); } else meta_compositor_hide_tile_preview (display->compositor); return FALSE; } #define TILE_PREVIEW_TIMEOUT_MS 200 void meta_display_update_tile_preview (MetaDisplay *display, gboolean delay) { if (delay) { if (display->tile_preview_timeout_id > 0) return; display->tile_preview_timeout_id = g_timeout_add (TILE_PREVIEW_TIMEOUT_MS, meta_display_update_tile_preview_timeout, display); g_source_set_name_by_id (display->tile_preview_timeout_id, "[mutter] meta_display_update_tile_preview_timeout"); } else { g_clear_handle_id (&display->tile_preview_timeout_id, g_source_remove); meta_display_update_tile_preview_timeout ((gpointer)display); } } void meta_display_hide_tile_preview (MetaDisplay *display) { g_clear_handle_id (&display->tile_preview_timeout_id, g_source_remove); display->preview_tile_mode = META_TILE_NONE; meta_compositor_hide_tile_preview (display->compositor); } static MetaStartupSequence * find_startup_sequence_by_wmclass (MetaDisplay *display, MetaWindow *window) { GSList *startup_sequences, *l; startup_sequences = meta_startup_notification_get_sequences (display->startup_notification); for (l = startup_sequences; l; l = l->next) { MetaStartupSequence *sequence = l->data; const char *wmclass; wmclass = meta_startup_sequence_get_wmclass (sequence); if (wmclass != NULL && ((window->res_class && strcmp (wmclass, window->res_class) == 0) || (window->res_name && strcmp (wmclass, window->res_name) == 0))) return sequence; } return NULL; } /* Sets the initial_timestamp and initial_workspace properties * of a window according to information given us by the * startup-notification library. * * Returns TRUE if startup properties have been applied, and * FALSE if they have not (for example, if they had already * been applied.) */ gboolean meta_display_apply_startup_properties (MetaDisplay *display, MetaWindow *window) { const char *startup_id; MetaStartupSequence *sequence = NULL; /* Does the window have a startup ID stored? */ startup_id = meta_window_get_startup_id (window); meta_topic (META_DEBUG_STARTUP, "Applying startup props to %s id \"%s\"", window->desc, startup_id ? startup_id : "(none)"); if (!startup_id) { /* No startup ID stored for the window. Let's ask the * startup-notification library whether there's anything * stored for the resource name or resource class hints. */ sequence = find_startup_sequence_by_wmclass (display, window); if (sequence) { g_assert (window->startup_id == NULL); window->startup_id = g_strdup (meta_startup_sequence_get_id (sequence)); startup_id = window->startup_id; meta_topic (META_DEBUG_STARTUP, "Ending legacy sequence %s due to window %s", meta_startup_sequence_get_id (sequence), window->desc); meta_startup_sequence_complete (sequence); } } /* Still no startup ID? Bail. */ if (!startup_id) return FALSE; /* We might get this far and not know the sequence ID (if the window * already had a startup ID stored), so let's look for one if we don't * already know it. */ if (sequence == NULL) { sequence = meta_startup_notification_lookup_sequence (display->startup_notification, startup_id); } if (sequence != NULL) { gboolean changed_something = FALSE; meta_topic (META_DEBUG_STARTUP, "Found startup sequence for window %s ID \"%s\"", window->desc, startup_id); if (!window->initial_workspace_set) { int space = meta_startup_sequence_get_workspace (sequence); if (space >= 0) { meta_topic (META_DEBUG_STARTUP, "Setting initial window workspace to %d based on startup info", space); window->initial_workspace_set = TRUE; window->initial_workspace = space; changed_something = TRUE; } } if (!window->initial_timestamp_set) { guint32 timestamp = meta_startup_sequence_get_timestamp (sequence); meta_topic (META_DEBUG_STARTUP, "Setting initial window timestamp to %u based on startup info", timestamp); window->initial_timestamp_set = TRUE; window->initial_timestamp = timestamp; changed_something = TRUE; } return changed_something; } else { meta_topic (META_DEBUG_STARTUP, "Did not find startup sequence for window %s ID \"%s\"", window->desc, startup_id); } return FALSE; } static gboolean set_work_area_later_func (MetaDisplay *display) { meta_topic (META_DEBUG_WORKAREA, "Running work area hint computation function"); display->work_area_later = 0; g_signal_emit (display, display_signals[WORKAREAS_CHANGED], 0); return FALSE; } void meta_display_queue_workarea_recalc (MetaDisplay *display) { /* Recompute work area later before redrawing */ if (display->work_area_later == 0) { meta_topic (META_DEBUG_WORKAREA, "Adding work area hint computation function"); display->work_area_later = meta_later_add (META_LATER_BEFORE_REDRAW, (GSourceFunc) set_work_area_later_func, display, NULL); } } static gboolean check_fullscreen_func (gpointer data) { MetaDisplay *display = data; MetaBackend *backend = meta_get_backend (); MetaMonitorManager *monitor_manager = meta_backend_get_monitor_manager (backend); GList *logical_monitors, *l; MetaWindow *window; GSList *fullscreen_monitors = NULL; GSList *obscured_monitors = NULL; gboolean in_fullscreen_changed = FALSE; display->check_fullscreen_later = 0; logical_monitors = meta_monitor_manager_get_logical_monitors (monitor_manager); /* We consider a monitor in fullscreen if it contains a fullscreen window; * however we make an exception for maximized windows above the fullscreen * one, as in that case window+chrome fully obscure the fullscreen window. */ for (window = meta_stack_get_top (display->stack); window; window = meta_stack_get_below (display->stack, window, FALSE)) { gboolean covers_monitors = FALSE; if (window->hidden) continue; if (window->fullscreen) { covers_monitors = TRUE; } else if (window->override_redirect) { /* We want to handle the case where an application is creating an * override-redirect window the size of the screen (monitor) and treat * it similarly to a fullscreen window, though it doesn't have fullscreen * window management behavior. (Being O-R, it's not managed at all.) */ if (meta_window_is_monitor_sized (window)) covers_monitors = TRUE; } else if (window->maximized_horizontally && window->maximized_vertically) { MetaLogicalMonitor *logical_monitor; logical_monitor = meta_window_get_main_logical_monitor (window); if (!g_slist_find (obscured_monitors, logical_monitor)) obscured_monitors = g_slist_prepend (obscured_monitors, logical_monitor); } if (covers_monitors) { MetaRectangle window_rect; meta_window_get_frame_rect (window, &window_rect); for (l = logical_monitors; l; l = l->next) { MetaLogicalMonitor *logical_monitor = l->data; if (meta_rectangle_overlap (&window_rect, &logical_monitor->rect) && !g_slist_find (fullscreen_monitors, logical_monitor) && !g_slist_find (obscured_monitors, logical_monitor)) fullscreen_monitors = g_slist_prepend (fullscreen_monitors, logical_monitor); } } } g_slist_free (obscured_monitors); for (l = logical_monitors; l; l = l->next) { MetaLogicalMonitor *logical_monitor = l->data; gboolean in_fullscreen; in_fullscreen = g_slist_find (fullscreen_monitors, logical_monitor) != NULL; if (in_fullscreen != logical_monitor->in_fullscreen) { logical_monitor->in_fullscreen = in_fullscreen; in_fullscreen_changed = TRUE; } } g_slist_free (fullscreen_monitors); if (in_fullscreen_changed) { /* DOCK window stacking depends on the monitor's fullscreen status so we need to trigger a re-layering. */ MetaWindow *window = meta_stack_get_top (display->stack); if (window) meta_stack_update_layer (display->stack, window); g_signal_emit (display, display_signals[IN_FULLSCREEN_CHANGED], 0, NULL); } return FALSE; } void meta_display_queue_check_fullscreen (MetaDisplay *display) { if (!display->check_fullscreen_later) display->check_fullscreen_later = meta_later_add (META_LATER_CHECK_FULLSCREEN, check_fullscreen_func, display, NULL); } int meta_display_get_monitor_index_for_rect (MetaDisplay *display, MetaRectangle *rect) { MetaBackend *backend = meta_get_backend (); MetaMonitorManager *monitor_manager = meta_backend_get_monitor_manager (backend); MetaLogicalMonitor *logical_monitor; logical_monitor = meta_monitor_manager_get_logical_monitor_from_rect (monitor_manager, rect); if (!logical_monitor) return -1; return logical_monitor->number; } int meta_display_get_monitor_neighbor_index (MetaDisplay *display, int which_monitor, MetaDisplayDirection direction) { MetaBackend *backend = meta_get_backend (); MetaMonitorManager *monitor_manager = meta_backend_get_monitor_manager (backend); MetaLogicalMonitor *logical_monitor; MetaLogicalMonitor *neighbor; logical_monitor = meta_monitor_manager_get_logical_monitor_from_number (monitor_manager, which_monitor); neighbor = meta_monitor_manager_get_logical_monitor_neighbor (monitor_manager, logical_monitor, direction); return neighbor ? neighbor->number : -1; } /** * meta_display_get_current_monitor: * @display: a #MetaDisplay * * Gets the index of the monitor that currently has the mouse pointer. * * Return value: a monitor index */ int meta_display_get_current_monitor (MetaDisplay *display) { MetaBackend *backend = meta_get_backend (); MetaLogicalMonitor *logical_monitor; logical_monitor = meta_backend_get_current_logical_monitor (backend); /* Pretend its the first when there is no actual current monitor. */ if (!logical_monitor) return 0; return logical_monitor->number; } /** * meta_display_get_n_monitors: * @display: a #MetaDisplay * * Gets the number of monitors that are joined together to form @display. * * Return value: the number of monitors */ int meta_display_get_n_monitors (MetaDisplay *display) { MetaBackend *backend = meta_get_backend (); MetaMonitorManager *monitor_manager = meta_backend_get_monitor_manager (backend); g_return_val_if_fail (META_IS_DISPLAY (display), 0); return meta_monitor_manager_get_num_logical_monitors (monitor_manager); } /** * meta_display_get_primary_monitor: * @display: a #MetaDisplay * * Gets the index of the primary monitor on this @display. * * Return value: a monitor index */ int meta_display_get_primary_monitor (MetaDisplay *display) { MetaBackend *backend = meta_get_backend (); MetaMonitorManager *monitor_manager = meta_backend_get_monitor_manager (backend); MetaLogicalMonitor *logical_monitor; g_return_val_if_fail (META_IS_DISPLAY (display), 0); logical_monitor = meta_monitor_manager_get_primary_logical_monitor (monitor_manager); if (logical_monitor) return logical_monitor->number; else return 0; } /** * meta_display_get_monitor_geometry: * @display: a #MetaDisplay * @monitor: the monitor number * @geometry: (out): location to store the monitor geometry * * Stores the location and size of the indicated @monitor in @geometry. */ void meta_display_get_monitor_geometry (MetaDisplay *display, int monitor, MetaRectangle *geometry) { MetaBackend *backend = meta_get_backend (); MetaMonitorManager *monitor_manager = meta_backend_get_monitor_manager (backend); MetaLogicalMonitor *logical_monitor; #ifndef G_DISABLE_CHECKS int n_logical_monitors = meta_monitor_manager_get_num_logical_monitors (monitor_manager); #endif g_return_if_fail (META_IS_DISPLAY (display)); g_return_if_fail (monitor >= 0 && monitor < n_logical_monitors); g_return_if_fail (geometry != NULL); logical_monitor = meta_monitor_manager_get_logical_monitor_from_number (monitor_manager, monitor); *geometry = logical_monitor->rect; } /** * meta_display_get_monitor_scale: * @display: a #MetaDisplay * @monitor: the monitor number * * Gets the monitor scaling value for the given @monitor. * * Return value: the monitor scaling value */ float meta_display_get_monitor_scale (MetaDisplay *display, int monitor) { MetaBackend *backend = meta_get_backend (); MetaMonitorManager *monitor_manager = meta_backend_get_monitor_manager (backend); MetaLogicalMonitor *logical_monitor; #ifndef G_DISABLE_CHECKS int n_logical_monitors = meta_monitor_manager_get_num_logical_monitors (monitor_manager); #endif g_return_val_if_fail (META_IS_DISPLAY (display), 1.0f); g_return_val_if_fail (monitor >= 0 && monitor < n_logical_monitors, 1.0f); logical_monitor = meta_monitor_manager_get_logical_monitor_from_number (monitor_manager, monitor); return logical_monitor->scale; } /** * meta_display_get_monitor_in_fullscreen: * @display: a #MetaDisplay * @monitor: the monitor number * * Determines whether there is a fullscreen window obscuring the specified * monitor. If there is a fullscreen window, the desktop environment will * typically hide any controls that might obscure the fullscreen window. * * You can get notification when this changes by connecting to * MetaDisplay::in-fullscreen-changed. * * Returns: %TRUE if there is a fullscreen window covering the specified monitor. */ gboolean meta_display_get_monitor_in_fullscreen (MetaDisplay *display, int monitor) { MetaBackend *backend = meta_get_backend (); MetaMonitorManager *monitor_manager = meta_backend_get_monitor_manager (backend); MetaLogicalMonitor *logical_monitor; #ifndef G_DISABLE_CHECKS int n_logical_monitors = meta_monitor_manager_get_num_logical_monitors (monitor_manager); #endif g_return_val_if_fail (META_IS_DISPLAY (display), FALSE); g_return_val_if_fail (monitor >= 0 && monitor < n_logical_monitors, FALSE); logical_monitor = meta_monitor_manager_get_logical_monitor_from_number (monitor_manager, monitor); /* We use -1 as a flag to mean "not known yet" for notification purposes */ return logical_monitor->in_fullscreen == TRUE; } MetaWindow * meta_display_get_pointer_window (MetaDisplay *display, MetaWindow *not_this_one) { MetaWorkspaceManager *workspace_manager = display->workspace_manager; MetaBackend *backend = meta_get_backend (); MetaCursorTracker *cursor_tracker = meta_backend_get_cursor_tracker (backend); MetaWindow *window; graphene_point_t point; if (not_this_one) meta_topic (META_DEBUG_FOCUS, "Focusing mouse window excluding %s", not_this_one->desc); meta_cursor_tracker_get_pointer (cursor_tracker, &point, NULL); window = meta_stack_get_default_focus_window_at_point (display->stack, workspace_manager->active_workspace, not_this_one, point.x, point.y); return window; } void meta_display_focus_default_window (MetaDisplay *display, guint32 timestamp) { MetaWorkspaceManager *workspace_manager = display->workspace_manager; meta_workspace_focus_default_window (workspace_manager->active_workspace, NULL, timestamp); } /** * meta_display_get_workspace_manager: * @display: a #MetaDisplay * * Returns: (transfer none): The workspace manager of the display */ MetaWorkspaceManager * meta_display_get_workspace_manager (MetaDisplay *display) { return display->workspace_manager; } MetaStartupNotification * meta_display_get_startup_notification (MetaDisplay *display) { return display->startup_notification; } MetaWindow * meta_display_get_window_from_id (MetaDisplay *display, uint64_t window_id) { g_autoptr (GSList) windows = NULL; GSList *l; windows = meta_display_list_windows (display, META_LIST_DEFAULT); for (l = windows; l; l = l->next) { MetaWindow *window = l->data; if (window->id == window_id) return window; } return NULL; } uint64_t meta_display_generate_window_id (MetaDisplay *display) { static uint64_t base_window_id; static uint64_t last_window_id; if (!base_window_id) base_window_id = g_random_int () + 1; /* We can overflow here, that's fine */ return (base_window_id + last_window_id++); } /** * meta_display_get_sound_player: * @display: a #MetaDisplay * * Returns: (transfer none): The sound player of the display */ MetaSoundPlayer * meta_display_get_sound_player (MetaDisplay *display) { return display->sound_player; } /** * meta_display_get_selection: * @display: a #MetaDisplay * * Returns: (transfer none): The selection manager of the display */ MetaSelection * meta_display_get_selection (MetaDisplay *display) { return display->selection; }