From b30a051cd190f06f0236339a5e24a391ce6f12ee Mon Sep 17 00:00:00 2001 From: GloriousEggroll Date: Fri, 23 Jun 2023 10:26:17 -0600 Subject: [PATCH 2/3] 3085 --- src/backends/meta-backend.c | 3 +- src/backends/meta-backend.c.orig | 1744 ++++++++++++++++++++++++++++++ 2 files changed, 1746 insertions(+), 1 deletion(-) create mode 100644 src/backends/meta-backend.c.orig diff --git a/src/backends/meta-backend.c b/src/backends/meta-backend.c index e77b961..7e64c4f 100644 --- a/src/backends/meta-backend.c +++ b/src/backends/meta-backend.c @@ -787,6 +787,8 @@ meta_backend_constructed (GObject *object) g_assert (priv->context); + priv->settings = meta_settings_new (backend); + #ifdef HAVE_LIBWACOM priv->wacom_db = libwacom_database_new (); if (!priv->wacom_db) @@ -1181,7 +1183,6 @@ meta_backend_initable_init (GInitable *initable, MetaBackend *backend = META_BACKEND (initable); MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); - priv->settings = meta_settings_new (backend); priv->orientation_manager = g_object_new (META_TYPE_ORIENTATION_MANAGER, NULL); priv->monitor_manager = meta_backend_create_monitor_manager (backend, error); diff --git a/src/backends/meta-backend.c.orig b/src/backends/meta-backend.c.orig new file mode 100644 index 0000000..e77b961 --- /dev/null +++ b/src/backends/meta-backend.c.orig @@ -0,0 +1,1744 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* + * Copyright (C) 2014 Red Hat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Written by: + * Jasper St. Pierre + */ + +/** + * SECTION:meta-backend + * @title: MetaBackend + * @short_description: Handles monitor config, modesetting, cursor sprites, ... + * + * MetaBackend is the abstraction that deals with several things like: + * - Modesetting (depending on the backend, this can be done either by X or KMS) + * - Initializing the #MetaSettings + * - Setting up Monitor configuration + * - Input device configuration (using the #ClutterDeviceManager) + * - Creating the #MetaRenderer + * - Setting up the stage of the scene graph (using #MetaStage) + * - Creating the object that deals with the cursor (using #MetaCursorTracker) + * and its possible pointer constraint (using #MetaPointerConstraint) + * - Setting the cursor sprite (using #MetaCursorRenderer) + * - Interacting with logind (using the appropriate D-Bus interface) + * - Querying UPower (over D-Bus) to know when the lid is closed + * - Setup Remote Desktop / Screencasting (#MetaRemoteDesktop) + * - Setup the #MetaEgl object + * + * Note that the #MetaBackend is not a subclass of #ClutterBackend. It is + * responsible for creating the correct one, based on the backend that is + * used (#MetaBackendNative or #MetaBackendX11). + */ + +#include "config.h" + +#include "backends/meta-backend-private.h" + +#include + +#include "backends/meta-barrier-private.h" +#include "backends/meta-cursor-renderer.h" +#include "backends/meta-cursor-tracker-private.h" +#include "backends/meta-dbus-session-watcher.h" +#include "backends/meta-idle-manager.h" +#include "backends/meta-idle-monitor-private.h" +#include "backends/meta-input-mapper-private.h" +#include "backends/meta-input-settings-private.h" +#include "backends/meta-logical-monitor.h" +#include "backends/meta-monitor-manager-dummy.h" +#include "backends/meta-remote-access-controller-private.h" +#include "backends/meta-settings-private.h" +#include "backends/meta-stage-private.h" +#include "backends/x11/meta-backend-x11.h" +#include "clutter/clutter-mutter.h" +#include "clutter/clutter-seat-private.h" +#include "compositor/meta-dnd-private.h" +#include "core/meta-context-private.h" +#include "meta/main.h" +#include "meta/meta-backend.h" +#include "meta/meta-context.h" +#include "meta/meta-enum-types.h" +#include "meta/util.h" + +#ifdef HAVE_REMOTE_DESKTOP +#include "backends/meta-remote-desktop.h" +#include "backends/meta-screen-cast.h" +#endif + +#ifdef HAVE_NATIVE_BACKEND +#include "backends/native/meta-backend-native.h" +#endif + +#ifdef HAVE_WAYLAND +#include "wayland/meta-wayland.h" +#endif + +enum +{ + PROP_0, + + PROP_CONTEXT, + PROP_CAPABILITIES, + + N_PROPS +}; + +static GParamSpec *obj_props[N_PROPS]; + +enum +{ + KEYMAP_CHANGED, + KEYMAP_LAYOUT_GROUP_CHANGED, + LAST_DEVICE_CHANGED, + LID_IS_CLOSED_CHANGED, + GPU_ADDED, + PREPARE_SHUTDOWN, + + N_SIGNALS +}; + +static guint signals[N_SIGNALS]; + +#define HIDDEN_POINTER_TIMEOUT 300 /* ms */ + +struct _MetaBackendPrivate +{ + MetaContext *context; + + MetaMonitorManager *monitor_manager; + MetaOrientationManager *orientation_manager; + MetaCursorTracker *cursor_tracker; + MetaInputMapper *input_mapper; + MetaIdleManager *idle_manager; + MetaRenderer *renderer; + MetaColorManager *color_manager; +#ifdef HAVE_EGL + MetaEgl *egl; +#endif + MetaSettings *settings; + MetaDbusSessionWatcher *dbus_session_watcher; + MetaRemoteAccessController *remote_access_controller; +#ifdef HAVE_REMOTE_DESKTOP + MetaScreenCast *screen_cast; + MetaRemoteDesktop *remote_desktop; +#endif + +#ifdef HAVE_LIBWACOM + WacomDeviceDatabase *wacom_db; +#endif +#ifdef HAVE_GNOME_DESKTOP + GnomePnpIds *pnp_ids; +#endif + + ClutterContext *clutter_context; + ClutterSeat *default_seat; + ClutterActor *stage; + + GList *gpus; + GList *hw_cursor_inhibitors; + + gboolean is_pointer_position_initialized; + + guint device_update_idle_id; + + ClutterInputDevice *current_device; + + MetaPointerConstraint *client_pointer_constraint; + MetaDnd *dnd; + + guint upower_watch_id; + GDBusProxy *upower_proxy; + gboolean lid_is_closed; + gboolean on_battery; + + guint sleep_signal_id; + GCancellable *cancellable; + GDBusConnection *system_bus; + + uint32_t last_pointer_motion; +}; +typedef struct _MetaBackendPrivate MetaBackendPrivate; + +typedef struct _MetaBackendSource MetaBackendSource; + +struct _MetaBackendSource +{ + GSource parent; + MetaBackend *backend; +}; + +static void +initable_iface_init (GInitableIface *initable_iface); + +G_DEFINE_ABSTRACT_TYPE_WITH_CODE (MetaBackend, meta_backend, G_TYPE_OBJECT, + G_ADD_PRIVATE (MetaBackend) + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, + initable_iface_init)); + +static void +meta_backend_dispose (GObject *object) +{ + MetaBackend *backend = META_BACKEND (object); + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + + g_clear_pointer (&priv->cursor_tracker, meta_cursor_tracker_destroy); + g_clear_object (&priv->current_device); + g_clear_object (&priv->color_manager); + g_clear_object (&priv->monitor_manager); + g_clear_object (&priv->orientation_manager); +#ifdef HAVE_REMOTE_DESKTOP + g_clear_object (&priv->remote_desktop); + g_clear_object (&priv->screen_cast); +#endif + g_clear_object (&priv->dbus_session_watcher); + g_clear_object (&priv->remote_access_controller); + +#ifdef HAVE_LIBWACOM + g_clear_pointer (&priv->wacom_db, libwacom_database_destroy); +#endif +#ifdef HAVE_GNOME_DESKTOP + g_clear_object (&priv->pnp_ids); +#endif + + if (priv->sleep_signal_id) + { + g_dbus_connection_signal_unsubscribe (priv->system_bus, priv->sleep_signal_id); + priv->sleep_signal_id = 0; + } + + if (priv->upower_watch_id) + { + g_bus_unwatch_name (priv->upower_watch_id); + priv->upower_watch_id = 0; + } + + g_cancellable_cancel (priv->cancellable); + g_clear_object (&priv->cancellable); + g_clear_object (&priv->system_bus); + g_clear_object (&priv->upower_proxy); + + g_clear_handle_id (&priv->device_update_idle_id, g_source_remove); + + g_clear_object (&priv->settings); + + g_clear_pointer (&priv->default_seat, clutter_seat_destroy); + g_clear_pointer (&priv->stage, clutter_actor_destroy); + g_clear_pointer (&priv->idle_manager, meta_idle_manager_free); + g_clear_object (&priv->renderer); + g_clear_pointer (&priv->clutter_context, clutter_context_free); + g_clear_list (&priv->gpus, g_object_unref); + + G_OBJECT_CLASS (meta_backend_parent_class)->dispose (object); +} + +void +meta_backend_destroy (MetaBackend *backend) +{ + g_object_run_dispose (G_OBJECT (backend)); + g_object_unref (backend); +} + +static void +meta_backend_sync_screen_size (MetaBackend *backend) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + int width, height; + + meta_monitor_manager_get_screen_size (priv->monitor_manager, &width, &height); + + META_BACKEND_GET_CLASS (backend)->update_screen_size (backend, width, height); +} + +static void +reset_pointer_position (MetaBackend *backend) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + MetaMonitorManager *monitor_manager = priv->monitor_manager; + ClutterSeat *seat = priv->default_seat; + MetaLogicalMonitor *primary; + + primary = + meta_monitor_manager_get_primary_logical_monitor (monitor_manager); + + /* Move the pointer out of the way to avoid hovering over reactive + * elements (e.g. users list at login) causing undesired behaviour. */ + clutter_seat_warp_pointer (seat, + primary->rect.x + primary->rect.width * 0.9, + primary->rect.y + primary->rect.height * 0.9); +} + +static gboolean +should_have_cursor_renderer (ClutterInputDevice *device) +{ + switch (clutter_input_device_get_device_type (device)) + { + case CLUTTER_POINTER_DEVICE: + if (clutter_input_device_get_device_mode (device) == + CLUTTER_INPUT_MODE_LOGICAL) + return TRUE; + + return FALSE; + case CLUTTER_TABLET_DEVICE: + return TRUE; + default: + return FALSE; + } +} + +static void +update_cursors (MetaBackend *backend) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + ClutterSeat *seat = priv->default_seat; + MetaCursorRenderer *cursor_renderer; + ClutterInputDevice *pointer, *device; + GList *devices, *l; + + pointer = clutter_seat_get_pointer (seat); + devices = clutter_seat_list_devices (seat); + devices = g_list_prepend (devices, pointer); + + for (l = devices; l; l = l->next) + { + device = l->data; + + if (!should_have_cursor_renderer (device)) + continue; + + cursor_renderer = meta_backend_get_cursor_renderer_for_device (backend, + device); + if (cursor_renderer) + meta_cursor_renderer_force_update (cursor_renderer); + } + + g_list_free (devices); +} + +void +meta_backend_monitors_changed (MetaBackend *backend) +{ + meta_backend_sync_screen_size (backend); + update_cursors (backend); +} + +static gboolean +update_last_device (MetaBackend *backend) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + + priv->device_update_idle_id = 0; + g_signal_emit (backend, signals[LAST_DEVICE_CHANGED], 0, + priv->current_device); + + return G_SOURCE_REMOVE; +} + +static void +meta_backend_update_last_device (MetaBackend *backend, + ClutterInputDevice *device) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + + if (priv->current_device == device) + return; + + if (!device || + clutter_input_device_get_device_mode (device) == CLUTTER_INPUT_MODE_LOGICAL) + return; + + g_set_object (&priv->current_device, device); + + if (priv->device_update_idle_id == 0) + { + priv->device_update_idle_id = + g_idle_add ((GSourceFunc) update_last_device, backend); + g_source_set_name_by_id (priv->device_update_idle_id, + "[mutter] update_last_device"); + } +} + +static inline gboolean +determine_hotplug_pointer_visibility (ClutterSeat *seat) +{ + g_autoptr (GList) devices = NULL; + const GList *l; + gboolean has_touchscreen = FALSE, has_pointer = FALSE, has_tablet = FALSE; + + devices = clutter_seat_list_devices (seat); + + for (l = devices; l; l = l->next) + { + ClutterInputDevice *device = l->data; + ClutterInputDeviceType device_type; + + device_type = clutter_input_device_get_device_type (device); + + if (device_type == CLUTTER_TOUCHSCREEN_DEVICE) + has_touchscreen = TRUE; + if (device_type == CLUTTER_POINTER_DEVICE || + device_type == CLUTTER_TOUCHPAD_DEVICE) + has_pointer = TRUE; + if (device_type == CLUTTER_TABLET_DEVICE || + device_type == CLUTTER_PEN_DEVICE || + device_type == CLUTTER_ERASER_DEVICE) + { + if (meta_is_wayland_compositor ()) + has_tablet = TRUE; + else + has_pointer = TRUE; + } + } + + return has_pointer && !has_touchscreen && !has_tablet; +} + +static void +on_device_added (ClutterSeat *seat, + ClutterInputDevice *device, + gpointer user_data) +{ + MetaBackend *backend = META_BACKEND (user_data); + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + ClutterInputDeviceType device_type; + + if (clutter_input_device_get_device_mode (device) == + CLUTTER_INPUT_MODE_LOGICAL) + return; + + device_type = clutter_input_device_get_device_type (device); + + if (device_type == CLUTTER_TOUCHSCREEN_DEVICE || + device_type == CLUTTER_POINTER_DEVICE) + { + meta_cursor_tracker_set_pointer_visible (priv->cursor_tracker, + determine_hotplug_pointer_visibility (seat)); + } + + if (device_type == CLUTTER_TOUCHSCREEN_DEVICE || + device_type == CLUTTER_TABLET_DEVICE || + device_type == CLUTTER_PEN_DEVICE || + device_type == CLUTTER_ERASER_DEVICE || + device_type == CLUTTER_CURSOR_DEVICE || + device_type == CLUTTER_PAD_DEVICE) + meta_input_mapper_add_device (priv->input_mapper, device); +} + +static void +on_device_removed (ClutterSeat *seat, + ClutterInputDevice *device, + gpointer user_data) +{ + MetaBackend *backend = META_BACKEND (user_data); + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + + if (clutter_input_device_get_device_mode (device) == + CLUTTER_INPUT_MODE_LOGICAL) + return; + + meta_input_mapper_remove_device (priv->input_mapper, device); + + /* If the device the user last interacted goes away, check again pointer + * visibility. + */ + if (priv->current_device == device) + { + MetaCursorTracker *cursor_tracker = priv->cursor_tracker; + + g_clear_object (&priv->current_device); + g_clear_handle_id (&priv->device_update_idle_id, g_source_remove); + + meta_cursor_tracker_set_pointer_visible (cursor_tracker, + determine_hotplug_pointer_visibility (seat)); + } + + if (priv->current_device == device) + meta_backend_update_last_device (backend, NULL); +} + +static void +input_mapper_device_mapped_cb (MetaInputMapper *mapper, + ClutterInputDevice *device, + float matrix[6], + MetaInputSettings *input_settings) +{ + meta_input_settings_set_device_matrix (input_settings, device, matrix); +} + +static void +input_mapper_device_enabled_cb (MetaInputMapper *mapper, + ClutterInputDevice *device, + gboolean enabled, + MetaInputSettings *input_settings) +{ + meta_input_settings_set_device_enabled (input_settings, device, enabled); +} + +static void +input_mapper_device_aspect_ratio_cb (MetaInputMapper *mapper, + ClutterInputDevice *device, + double aspect_ratio, + MetaInputSettings *input_settings) +{ + meta_input_settings_set_device_aspect_ratio (input_settings, device, aspect_ratio); +} + +static void +on_prepare_shutdown (MetaContext *context, + MetaBackend *backend) +{ + g_signal_emit (backend, signals[PREPARE_SHUTDOWN], 0); +} + +static void +on_started (MetaContext *context, + MetaBackend *backend) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + ClutterSeat *seat = priv->default_seat; + + meta_cursor_tracker_set_pointer_visible (priv->cursor_tracker, + determine_hotplug_pointer_visibility (seat)); +} + +static void +meta_backend_real_post_init (MetaBackend *backend) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + ClutterSeat *seat = priv->default_seat; + MetaInputSettings *input_settings; + + priv->stage = meta_stage_new (backend); + clutter_actor_realize (priv->stage); + META_BACKEND_GET_CLASS (backend)->select_stage_events (backend); + + meta_monitor_manager_setup (priv->monitor_manager); + + meta_backend_sync_screen_size (backend); + + priv->idle_manager = meta_idle_manager_new (backend); + + g_signal_connect_object (seat, "device-added", + G_CALLBACK (on_device_added), backend, 0); + g_signal_connect_object (seat, "device-removed", + G_CALLBACK (on_device_removed), backend, + G_CONNECT_AFTER); + + priv->input_mapper = meta_input_mapper_new (backend); + + input_settings = meta_backend_get_input_settings (backend); + + if (input_settings) + { + g_signal_connect (priv->input_mapper, "device-mapped", + G_CALLBACK (input_mapper_device_mapped_cb), + input_settings); + g_signal_connect (priv->input_mapper, "device-enabled", + G_CALLBACK (input_mapper_device_enabled_cb), + input_settings); + g_signal_connect (priv->input_mapper, "device-aspect-ratio", + G_CALLBACK (input_mapper_device_aspect_ratio_cb), + input_settings); + } + + priv->remote_access_controller = + meta_remote_access_controller_new (); + priv->dbus_session_watcher = + g_object_new (META_TYPE_DBUS_SESSION_WATCHER, NULL); + +#ifdef HAVE_REMOTE_DESKTOP + priv->screen_cast = meta_screen_cast_new (backend); + meta_remote_access_controller_add ( + priv->remote_access_controller, + META_DBUS_SESSION_MANAGER (priv->screen_cast)); + priv->remote_desktop = meta_remote_desktop_new (backend); + meta_remote_access_controller_add ( + priv->remote_access_controller, + META_DBUS_SESSION_MANAGER (priv->remote_desktop)); +#endif /* HAVE_REMOTE_DESKTOP */ + + if (!meta_monitor_manager_is_headless (priv->monitor_manager)) + { + reset_pointer_position (backend); + priv->is_pointer_position_initialized = TRUE; + } + + meta_monitor_manager_post_init (priv->monitor_manager); + + g_signal_connect (priv->context, "prepare-shutdown", + G_CALLBACK (on_prepare_shutdown), backend); + g_signal_connect (priv->context, "started", + G_CALLBACK (on_started), backend); +} + +static gboolean +meta_backend_real_grab_device (MetaBackend *backend, + int device_id, + uint32_t timestamp) +{ + /* Do nothing */ + return TRUE; +} + +static gboolean +meta_backend_real_ungrab_device (MetaBackend *backend, + int device_id, + uint32_t timestamp) +{ + /* Do nothing */ + return TRUE; +} + +static void +meta_backend_real_select_stage_events (MetaBackend *backend) +{ + /* Do nothing */ +} + +static gboolean +meta_backend_real_is_lid_closed (MetaBackend *backend) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + + return priv->lid_is_closed; +} + +static MetaCursorTracker * +meta_backend_real_create_cursor_tracker (MetaBackend *backend) +{ + return g_object_new (META_TYPE_CURSOR_TRACKER, + "backend", backend, + NULL); +} + +static gboolean +meta_backend_real_is_headless (MetaBackend *backend) +{ + return FALSE; +} + +gboolean +meta_backend_is_lid_closed (MetaBackend *backend) +{ + return META_BACKEND_GET_CLASS (backend)->is_lid_closed (backend); +} + +gboolean +meta_backend_is_headless (MetaBackend *backend) +{ + return META_BACKEND_GET_CLASS (backend)->is_headless (backend); +} + +static void +upower_properties_changed (GDBusProxy *proxy, + GVariant *changed_properties, + GStrv invalidated_properties, + gpointer user_data) +{ + MetaBackend *backend = user_data; + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + GVariant *v; + gboolean reset_idle_time = FALSE; + + v = g_variant_lookup_value (changed_properties, + "LidIsClosed", + G_VARIANT_TYPE_BOOLEAN); + if (v) + { + gboolean lid_is_closed; + + lid_is_closed = g_variant_get_boolean (v); + g_variant_unref (v); + + if (lid_is_closed != priv->lid_is_closed) + { + priv->lid_is_closed = lid_is_closed; + g_signal_emit (backend, signals[LID_IS_CLOSED_CHANGED], 0, + priv->lid_is_closed); + + if (!lid_is_closed) + reset_idle_time = TRUE; + } + } + + v = g_variant_lookup_value (changed_properties, + "OnBattery", + G_VARIANT_TYPE_BOOLEAN); + if (v) + { + gboolean on_battery; + + on_battery = g_variant_get_boolean (v); + g_variant_unref (v); + + if (on_battery != priv->on_battery) + { + priv->on_battery = on_battery; + reset_idle_time = TRUE; + } + } + + if (reset_idle_time) + meta_idle_manager_reset_idle_time (priv->idle_manager); +} + +static void +upower_ready_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + MetaBackend *backend; + MetaBackendPrivate *priv; + GDBusProxy *proxy; + GError *error = NULL; + GVariant *v; + + proxy = g_dbus_proxy_new_finish (res, &error); + if (!proxy) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Failed to create UPower proxy: %s", error->message); + g_error_free (error); + return; + } + + backend = META_BACKEND (user_data); + priv = meta_backend_get_instance_private (backend); + + priv->upower_proxy = proxy; + g_signal_connect (proxy, "g-properties-changed", + G_CALLBACK (upower_properties_changed), backend); + + v = g_dbus_proxy_get_cached_property (proxy, "LidIsClosed"); + if (v) + { + priv->lid_is_closed = g_variant_get_boolean (v); + g_variant_unref (v); + + if (priv->lid_is_closed) + { + g_signal_emit (backend, signals[LID_IS_CLOSED_CHANGED], 0, + priv->lid_is_closed); + } + } + + v = g_dbus_proxy_get_cached_property (proxy, "OnBattery"); + if (v) + { + priv->on_battery = g_variant_get_boolean (v); + g_variant_unref (v); + } +} + +static void +upower_appeared (GDBusConnection *connection, + const gchar *name, + const gchar *name_owner, + gpointer user_data) +{ + MetaBackend *backend = META_BACKEND (user_data); + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + + g_dbus_proxy_new (connection, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.freedesktop.UPower", + "/org/freedesktop/UPower", + "org.freedesktop.UPower", + priv->cancellable, + upower_ready_cb, + backend); +} + +static void +upower_vanished (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + MetaBackend *backend = META_BACKEND (user_data); + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + + g_clear_object (&priv->upower_proxy); +} + +static void +meta_backend_constructed (GObject *object) +{ + MetaBackend *backend = META_BACKEND (object); + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + MetaBackendClass *backend_class = + META_BACKEND_GET_CLASS (backend); + + g_assert (priv->context); + +#ifdef HAVE_LIBWACOM + priv->wacom_db = libwacom_database_new (); + if (!priv->wacom_db) + { + g_warning ("Could not create database of Wacom devices, " + "expect tablets to misbehave"); + } +#endif + + if (backend_class->is_lid_closed == meta_backend_real_is_lid_closed) + { + priv->upower_watch_id = g_bus_watch_name (G_BUS_TYPE_SYSTEM, + "org.freedesktop.UPower", + G_BUS_NAME_WATCHER_FLAGS_NONE, + upower_appeared, + upower_vanished, + backend, + NULL); + } + +#ifdef HAVE_EGL + priv->egl = g_object_new (META_TYPE_EGL, NULL); +#endif + + G_OBJECT_CLASS (meta_backend_parent_class)->constructed (object); +} + +static void +meta_backend_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + MetaBackend *backend = META_BACKEND (object); + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + + switch (prop_id) + { + case PROP_CONTEXT: + priv->context = g_value_get_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +meta_backend_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + MetaBackend *backend = META_BACKEND (object); + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + + switch (prop_id) + { + case PROP_CONTEXT: + g_value_set_object (value, priv->context); + break; + case PROP_CAPABILITIES: + g_value_set_flags (value, meta_backend_get_capabilities (backend)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +meta_backend_class_init (MetaBackendClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = meta_backend_dispose; + object_class->constructed = meta_backend_constructed; + object_class->set_property = meta_backend_set_property; + object_class->get_property = meta_backend_get_property; + + klass->post_init = meta_backend_real_post_init; + klass->grab_device = meta_backend_real_grab_device; + klass->ungrab_device = meta_backend_real_ungrab_device; + klass->select_stage_events = meta_backend_real_select_stage_events; + klass->is_lid_closed = meta_backend_real_is_lid_closed; + klass->create_cursor_tracker = meta_backend_real_create_cursor_tracker; + klass->is_headless = meta_backend_real_is_headless; + + obj_props[PROP_CONTEXT] = + g_param_spec_object ("context", + "context", + "MetaContext", + META_TYPE_CONTEXT, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + obj_props[PROP_CAPABILITIES] = + g_param_spec_flags ("capabilities", + "capabilities", + "Backend capabilities", + META_TYPE_BACKEND_CAPABILITIES, + META_BACKEND_CAPABILITY_NONE, + G_PARAM_READABLE | + G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (object_class, N_PROPS, obj_props); + + signals[KEYMAP_CHANGED] = + g_signal_new ("keymap-changed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 0); + signals[KEYMAP_LAYOUT_GROUP_CHANGED] = + g_signal_new ("keymap-layout-group-changed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 1, G_TYPE_UINT); + signals[LAST_DEVICE_CHANGED] = + g_signal_new ("last-device-changed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 1, CLUTTER_TYPE_INPUT_DEVICE); + signals[LID_IS_CLOSED_CHANGED] = + g_signal_new ("lid-is-closed-changed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 1, G_TYPE_BOOLEAN); + /** + * MetaBackend::gpu-added: (skip) + * @backend: the #MetaBackend + * @gpu: the #MetaGpu + */ + signals[GPU_ADDED] = + g_signal_new ("gpu-added", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 1, META_TYPE_GPU); + signals[PREPARE_SHUTDOWN] = + g_signal_new ("prepare-shutdown", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 0); +} + +static MetaMonitorManager * +meta_backend_create_monitor_manager (MetaBackend *backend, + GError **error) +{ + return META_BACKEND_GET_CLASS (backend)->create_monitor_manager (backend, + error); +} + +static MetaColorManager * +meta_backend_create_color_manager (MetaBackend *backend) +{ + return META_BACKEND_GET_CLASS (backend)->create_color_manager (backend); +} + +static MetaRenderer * +meta_backend_create_renderer (MetaBackend *backend, + GError **error) +{ + return META_BACKEND_GET_CLASS (backend)->create_renderer (backend, error); +} + +static void +prepare_for_sleep_cb (GDBusConnection *connection, + const gchar *sender_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *signal_name, + GVariant *parameters, + gpointer user_data) +{ + MetaBackend *backend = user_data; + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + gboolean suspending; + + g_variant_get (parameters, "(b)", &suspending); + if (suspending) + return; + + meta_idle_manager_reset_idle_time (priv->idle_manager); +} + +static void +system_bus_gotten_cb (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + MetaBackend *backend = user_data; + MetaBackendPrivate *priv; + GDBusConnection *bus; + + bus = g_bus_get_finish (res, NULL); + if (!bus) + return; + + priv = meta_backend_get_instance_private (backend); + priv->system_bus = bus; + priv->sleep_signal_id = + g_dbus_connection_signal_subscribe (priv->system_bus, + "org.freedesktop.login1", + "org.freedesktop.login1.Manager", + "PrepareForSleep", + "/org/freedesktop/login1", + NULL, + G_DBUS_SIGNAL_FLAGS_NONE, + prepare_for_sleep_cb, + backend, + NULL); +} + +static void +update_last_device_from_event (MetaBackend *backend, + ClutterEvent *event) +{ + ClutterInputDevice *source; + + /* Handled elsewhere */ + if (event->type == CLUTTER_DEVICE_ADDED || + event->type == CLUTTER_DEVICE_REMOVED) + return; + + source = clutter_event_get_source_device (event); + if (source) + meta_backend_update_last_device (backend, source); +} + +static void +update_pointer_visibility_from_event (MetaBackend *backend, + ClutterEvent *event) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + MetaCursorTracker *cursor_tracker = priv->cursor_tracker; + ClutterInputDevice *device; + ClutterInputDeviceType device_type; + uint32_t time_ms; + + device = clutter_event_get_source_device (event); + device_type = clutter_input_device_get_device_type (device); + time_ms = clutter_event_get_time (event); + + switch (device_type) + { + case CLUTTER_TOUCHSCREEN_DEVICE: + meta_cursor_tracker_set_pointer_visible (cursor_tracker, FALSE); + break; + case CLUTTER_POINTER_DEVICE: + case CLUTTER_TOUCHPAD_DEVICE: + priv->last_pointer_motion = time_ms; + meta_cursor_tracker_set_pointer_visible (cursor_tracker, TRUE); + break; + case CLUTTER_TABLET_DEVICE: + case CLUTTER_PEN_DEVICE: + case CLUTTER_ERASER_DEVICE: + case CLUTTER_CURSOR_DEVICE: + if (meta_is_wayland_compositor () && + time_ms > priv->last_pointer_motion + HIDDEN_POINTER_TIMEOUT) + meta_cursor_tracker_set_pointer_visible (cursor_tracker, FALSE); + break; + case CLUTTER_KEYBOARD_DEVICE: + case CLUTTER_PAD_DEVICE: + case CLUTTER_EXTENSION_DEVICE: + case CLUTTER_JOYSTICK_DEVICE: + default: + break; + } +} + +/* Mutter is responsible for pulling events off the X queue, so Clutter + * doesn't need (and shouldn't) run its normal event source which polls + * the X fd, but we do have to deal with dispatching events that accumulate + * in the clutter queue. This happens, for example, when clutter generate + * enter/leave events on mouse motion - several events are queued in the + * clutter queue but only one dispatched. It could also happen because of + * explicit calls to clutter_event_put(). We add a very simple custom + * event loop source which is simply responsible for pulling events off + * of the queue and dispatching them before we block for new events. + */ + +static gboolean +clutter_source_prepare (GSource *source, + int *timeout) +{ + *timeout = -1; + + return clutter_events_pending (); +} + +static gboolean +clutter_source_check (GSource *source) +{ + return clutter_events_pending (); +} + +static gboolean +clutter_source_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data) +{ + MetaBackendSource *backend_source = (MetaBackendSource *) source; + ClutterEvent *event = clutter_event_get (); + + if (event) + { + event->any.stage = + CLUTTER_STAGE (meta_backend_get_stage (backend_source->backend)); + clutter_do_event (event); + meta_backend_update_from_event (backend_source->backend, event); + clutter_event_free (event); + } + + return TRUE; +} + +static GSourceFuncs clutter_source_funcs = { + clutter_source_prepare, + clutter_source_check, + clutter_source_dispatch +}; + +static ClutterBackend * +meta_clutter_backend_constructor (gpointer user_data) +{ + MetaBackend *backend = META_BACKEND (user_data); + + return META_BACKEND_GET_CLASS (backend)->create_clutter_backend (backend); +} + +static ClutterSeat * +meta_backend_create_default_seat (MetaBackend *backend, + GError **error) +{ + return META_BACKEND_GET_CLASS (backend)->create_default_seat (backend, error); +} + +static gboolean +init_clutter (MetaBackend *backend, + GError **error) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + MetaBackendSource *backend_source; + GSource *source; + + priv->clutter_context = clutter_context_new (meta_clutter_backend_constructor, + backend, + error); + if (!priv->clutter_context) + return FALSE; + + priv->default_seat = meta_backend_create_default_seat (backend, error); + if (!priv->default_seat) + return FALSE; + + source = g_source_new (&clutter_source_funcs, sizeof (MetaBackendSource)); + g_source_set_name (source, "[mutter] Backend"); + backend_source = (MetaBackendSource *) source; + backend_source->backend = backend; + g_source_attach (source, NULL); + g_source_unref (source); + + return TRUE; +} + +static void +meta_backend_post_init (MetaBackend *backend) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + + META_BACKEND_GET_CLASS (backend)->post_init (backend); + + meta_settings_post_init (priv->settings); +} + +static gboolean +meta_backend_initable_init (GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + MetaBackend *backend = META_BACKEND (initable); + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + + priv->settings = meta_settings_new (backend); + priv->orientation_manager = g_object_new (META_TYPE_ORIENTATION_MANAGER, NULL); + + priv->monitor_manager = meta_backend_create_monitor_manager (backend, error); + if (!priv->monitor_manager) + return FALSE; + + priv->color_manager = meta_backend_create_color_manager (backend); + + priv->renderer = meta_backend_create_renderer (backend, error); + if (!priv->renderer) + return FALSE; + + priv->cursor_tracker = + META_BACKEND_GET_CLASS (backend)->create_cursor_tracker (backend); + + priv->dnd = meta_dnd_new (backend); + + priv->cancellable = g_cancellable_new (); + g_bus_get (G_BUS_TYPE_SYSTEM, + priv->cancellable, + system_bus_gotten_cb, + backend); + + if (!init_clutter (backend, error)) + return FALSE; + + meta_backend_post_init (backend); + + return TRUE; +} + +static void +initable_iface_init (GInitableIface *initable_iface) +{ + initable_iface->init = meta_backend_initable_init; +} + +static void +meta_backend_init (MetaBackend *backend) +{ +} + +/** + * meta_backend_get_idle_monitor: (skip) + */ +MetaIdleMonitor * +meta_backend_get_idle_monitor (MetaBackend *backend, + ClutterInputDevice *device) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + + return meta_idle_manager_get_monitor (priv->idle_manager, device); +} + +/** + * meta_backend_get_core_idle_monitor: + * + * Returns: (transfer none): the #MetaIdleMonitor that tracks server-global + * idle time for all devices. + */ +MetaIdleMonitor * +meta_backend_get_core_idle_monitor (MetaBackend *backend) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + + return meta_idle_manager_get_core_monitor (priv->idle_manager); +} + +/** + * meta_backend_get_idle_manager: (skip) + */ +MetaIdleManager * +meta_backend_get_idle_manager (MetaBackend *backend) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + + return priv->idle_manager; +} + +/** + * meta_backend_get_monitor_manager: + * + * Returns: (transfer none): A #MetaMonitorManager + */ +MetaMonitorManager * +meta_backend_get_monitor_manager (MetaBackend *backend) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + + return priv->monitor_manager; +} + +/** + * meta_backend_get_color_manager: (skip) + */ +MetaColorManager * +meta_backend_get_color_manager (MetaBackend *backend) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + + return priv->color_manager; +} + +/** + * meta_backend_get_orientation_manager: (skip) + */ +MetaOrientationManager * +meta_backend_get_orientation_manager (MetaBackend *backend) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + + return priv->orientation_manager; +} + +MetaCursorTracker * +meta_backend_get_cursor_tracker (MetaBackend *backend) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + + return priv->cursor_tracker; +} + +/** + * meta_backend_get_cursor_renderer: (skip) + */ +MetaCursorRenderer * +meta_backend_get_cursor_renderer (MetaBackend *backend) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + ClutterInputDevice *pointer; + + if (!priv->default_seat) + return NULL; + + pointer = clutter_seat_get_pointer (priv->default_seat); + + return meta_backend_get_cursor_renderer_for_device (backend, pointer); +} + +MetaCursorRenderer * +meta_backend_get_cursor_renderer_for_device (MetaBackend *backend, + ClutterInputDevice *device) +{ + g_return_val_if_fail (META_IS_BACKEND (backend), NULL); + g_return_val_if_fail (CLUTTER_IS_INPUT_DEVICE (device), NULL); + g_return_val_if_fail (clutter_input_device_get_device_type (device) != + CLUTTER_KEYBOARD_DEVICE, NULL); + + return META_BACKEND_GET_CLASS (backend)->get_cursor_renderer (backend, + device); +} + +/** + * meta_backend_get_renderer: (skip) + */ +MetaRenderer * +meta_backend_get_renderer (MetaBackend *backend) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + + return priv->renderer; +} + +#ifdef HAVE_EGL +/** + * meta_backend_get_egl: (skip) + */ +MetaEgl * +meta_backend_get_egl (MetaBackend *backend) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + + return priv->egl; +} +#endif /* HAVE_EGL */ + +/** + * meta_backend_get_settings: (skip) + */ +MetaSettings * +meta_backend_get_settings (MetaBackend *backend) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + + return priv->settings; +} + +MetaDbusSessionWatcher * +meta_backend_get_dbus_session_watcher (MetaBackend *backend) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + + return priv->dbus_session_watcher; +} + +#ifdef HAVE_REMOTE_DESKTOP +/** + * meta_backend_get_remote_desktop: (skip) + */ +MetaRemoteDesktop * +meta_backend_get_remote_desktop (MetaBackend *backend) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + + return priv->remote_desktop; +} + +/** + * meta_backend_get_screen_cast: (skip) + */ +MetaScreenCast * +meta_backend_get_screen_cast (MetaBackend *backend) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + + return priv->screen_cast; +} +#endif /* HAVE_REMOTE_DESKTOP */ + +/** + * meta_backend_get_remote_access_controller: + * @backend: A #MetaBackend + * + * Return Value: (transfer none): The #MetaRemoteAccessController + */ +MetaRemoteAccessController * +meta_backend_get_remote_access_controller (MetaBackend *backend) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + + return priv->remote_access_controller; +} + +/** + * meta_backend_is_rendering_hardware_accelerated: + * @backend: A #MetaBackend + * + * Returns: %TRUE if the rendering is hardware accelerated, otherwise + * %FALSE. + */ +gboolean +meta_backend_is_rendering_hardware_accelerated (MetaBackend *backend) +{ + MetaRenderer *renderer = meta_backend_get_renderer (backend); + + return meta_renderer_is_hardware_accelerated (renderer); +} + +/** + * meta_backend_grab_device: (skip) + */ +gboolean +meta_backend_grab_device (MetaBackend *backend, + int device_id, + uint32_t timestamp) +{ + return META_BACKEND_GET_CLASS (backend)->grab_device (backend, device_id, timestamp); +} + +/** + * meta_backend_get_context: + * @backend: the #MetaBackend + * + * Returns: (transfer none): The #MetaContext + */ +MetaContext * +meta_backend_get_context (MetaBackend *backend) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + + return priv->context; +} + +/** + * meta_backend_ungrab_device: (skip) + */ +gboolean +meta_backend_ungrab_device (MetaBackend *backend, + int device_id, + uint32_t timestamp) +{ + return META_BACKEND_GET_CLASS (backend)->ungrab_device (backend, device_id, timestamp); +} + +/** + * meta_backend_finish_touch_sequence: (skip) + */ +void +meta_backend_finish_touch_sequence (MetaBackend *backend, + ClutterEventSequence *sequence, + MetaSequenceState state) +{ + if (META_BACKEND_GET_CLASS (backend)->finish_touch_sequence) + META_BACKEND_GET_CLASS (backend)->finish_touch_sequence (backend, + sequence, + state); +} + +MetaLogicalMonitor * +meta_backend_get_current_logical_monitor (MetaBackend *backend) +{ + return META_BACKEND_GET_CLASS (backend)->get_current_logical_monitor (backend); +} + +void +meta_backend_set_keymap (MetaBackend *backend, + const char *layouts, + const char *variants, + const char *options) +{ + META_BACKEND_GET_CLASS (backend)->set_keymap (backend, layouts, variants, options); +} + +/** + * meta_backend_get_keymap: (skip) + */ +struct xkb_keymap * +meta_backend_get_keymap (MetaBackend *backend) + +{ + return META_BACKEND_GET_CLASS (backend)->get_keymap (backend); +} + +xkb_layout_index_t +meta_backend_get_keymap_layout_group (MetaBackend *backend) +{ + return META_BACKEND_GET_CLASS (backend)->get_keymap_layout_group (backend); +} + +void +meta_backend_lock_layout_group (MetaBackend *backend, + guint idx) +{ + META_BACKEND_GET_CLASS (backend)->lock_layout_group (backend, idx); +} + +/** + * meta_backend_get_stage: + * @backend: A #MetaBackend + * + * Gets the global #ClutterStage that's managed by this backend. + * + * Returns: (transfer none): the #ClutterStage + */ +ClutterActor * +meta_backend_get_stage (MetaBackend *backend) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + return priv->stage; +} + +ClutterSeat * +meta_backend_get_default_seat (MetaBackend *backend) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + + return priv->default_seat; +} + +MetaPointerConstraint * +meta_backend_get_client_pointer_constraint (MetaBackend *backend) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + + return priv->client_pointer_constraint; +} + +/** + * meta_backend_set_client_pointer_constraint: + * @backend: a #MetaBackend object. + * @constraint: (nullable): the client constraint to follow. + * + * Sets the current pointer constraint and removes (and unrefs) the previous + * one. If @constraint is %NULL, this means that there is no + * #MetaPointerConstraint active. + */ +void +meta_backend_set_client_pointer_constraint (MetaBackend *backend, + MetaPointerConstraint *constraint) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + + META_BACKEND_GET_CLASS (backend)->set_pointer_constraint (backend, constraint); + g_set_object (&priv->client_pointer_constraint, constraint); +} + +ClutterBackend * +meta_backend_get_clutter_backend (MetaBackend *backend) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + ClutterContext *clutter_context; + + clutter_context = priv->clutter_context; + if (!clutter_context) + return NULL; + + return clutter_context_get_backend (clutter_context); +} + +MetaBackendCapabilities +meta_backend_get_capabilities (MetaBackend *backend) +{ + return META_BACKEND_GET_CLASS (backend)->get_capabilities (backend); +} + +gboolean +meta_backend_is_stage_views_scaled (MetaBackend *backend) +{ + MetaMonitorManager *monitor_manager = + meta_backend_get_monitor_manager (backend); + MetaLogicalMonitorLayoutMode layout_mode; + + layout_mode = monitor_manager->layout_mode; + + return layout_mode == META_LOGICAL_MONITOR_LAYOUT_MODE_LOGICAL; +} + +MetaInputMapper * +meta_backend_get_input_mapper (MetaBackend *backend) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + + return priv->input_mapper; +} + +MetaInputSettings * +meta_backend_get_input_settings (MetaBackend *backend) +{ + return META_BACKEND_GET_CLASS (backend)->get_input_settings (backend); +} + +/** + * meta_backend_get_dnd: + * @backend: A #MetaDnd + * + * Gets the global #MetaDnd that's managed by this backend. + * + * Returns: (transfer none): the #MetaDnd + */ +MetaDnd * +meta_backend_get_dnd (MetaBackend *backend) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + + return priv->dnd; +} + +void +meta_backend_notify_keymap_changed (MetaBackend *backend) +{ + g_signal_emit (backend, signals[KEYMAP_CHANGED], 0); +} + +void +meta_backend_notify_keymap_layout_group_changed (MetaBackend *backend, + unsigned int locked_group) +{ + g_signal_emit (backend, signals[KEYMAP_LAYOUT_GROUP_CHANGED], 0, + locked_group); +} + +void +meta_backend_add_gpu (MetaBackend *backend, + MetaGpu *gpu) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + + priv->gpus = g_list_append (priv->gpus, gpu); + + g_signal_emit (backend, signals[GPU_ADDED], 0, gpu); +} + +GList * +meta_backend_get_gpus (MetaBackend *backend) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + + return priv->gpus; +} + +#ifdef HAVE_LIBWACOM +WacomDeviceDatabase * +meta_backend_get_wacom_database (MetaBackend *backend) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + + return priv->wacom_db; +} +#endif + +void +meta_backend_add_hw_cursor_inhibitor (MetaBackend *backend, + MetaHwCursorInhibitor *inhibitor) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + + priv->hw_cursor_inhibitors = g_list_prepend (priv->hw_cursor_inhibitors, + inhibitor); +} + +void +meta_backend_remove_hw_cursor_inhibitor (MetaBackend *backend, + MetaHwCursorInhibitor *inhibitor) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + + priv->hw_cursor_inhibitors = g_list_remove (priv->hw_cursor_inhibitors, + inhibitor); +} + +gboolean +meta_backend_is_hw_cursors_inhibited (MetaBackend *backend) +{ + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + GList *l; + + for (l = priv->hw_cursor_inhibitors; l; l = l->next) + { + MetaHwCursorInhibitor *inhibitor = l->data; + + if (meta_hw_cursor_inhibitor_is_cursor_inhibited (inhibitor)) + return TRUE; + } + + return FALSE; +} + +void +meta_backend_update_from_event (MetaBackend *backend, + ClutterEvent *event) +{ + update_last_device_from_event (backend, event); + update_pointer_visibility_from_event (backend, event); +} + +/** + * meta_backend_get_vendor_name: + * @backend: A #MetaBackend object + * @pnp_id: the PNP ID + * + * Find the full vendor name from the given PNP ID. + * + * Returns: (transfer full): A string containing the vendor name, + * or NULL when not found. + */ +char * +meta_backend_get_vendor_name (MetaBackend *backend, + const char *pnp_id) +{ +#ifdef HAVE_GNOME_DESKTOP + MetaBackendPrivate *priv = meta_backend_get_instance_private (backend); + + if (!priv->pnp_ids) + priv->pnp_ids = gnome_pnp_ids_new (); + + return gnome_pnp_ids_get_pnp_id (priv->pnp_ids, pnp_id); +#else + return g_strdup (pnp_id); +#endif +} -- 2.41.0 From 5b826df1ddac46cba47cb9cd94ab1011b9c0ff2d Mon Sep 17 00:00:00 2001 From: GloriousEggroll Date: Fri, 23 Jun 2023 10:34:12 -0600 Subject: [PATCH 3/3] 1154 --- clutter/clutter/clutter-frame-clock.c | 108 +- clutter/clutter/clutter-frame-clock.h | 10 + .../org.gnome.Mutter.DisplayConfig.xml | 7 + data/org.gnome.mutter.gschema.xml.in | 7 + src/backends/meta-monitor-config-manager.c | 5 +- src/backends/meta-monitor-config-manager.h | 1 + src/backends/meta-monitor-config-store.c | 43 +- src/backends/meta-monitor-manager-private.h | 1 + src/backends/meta-monitor-manager.c | 27 +- src/backends/meta-monitor.c | 19 + src/backends/meta-monitor.h | 4 + src/backends/meta-output.c | 20 + src/backends/meta-output.h | 7 + src/backends/meta-renderer-view.c | 27 + src/backends/meta-renderer-view.h | 2 + src/backends/meta-settings-private.h | 5 +- src/backends/meta-settings.c | 2 + src/backends/native/meta-backend-native.c | 3 + .../native/meta-backend-native.c.orig | 1027 +++ src/backends/native/meta-gpu-kms.c | 17 + src/backends/native/meta-gpu-kms.h | 1 + .../native/meta-kms-connector-private.h | 1 + src/backends/native/meta-kms-connector.c | 13 + src/backends/native/meta-kms-connector.h | 2 + src/backends/native/meta-kms-crtc-private.h | 8 + src/backends/native/meta-kms-crtc.c | 5 + .../native/meta-kms-impl-device-atomic.c | 43 + .../native/meta-kms-impl-device-simple.c | 78 + src/backends/native/meta-kms-types.h | 1 + src/backends/native/meta-kms-update-private.h | 13 + src/backends/native/meta-kms-update.c | 47 + src/backends/native/meta-kms-update.h | 4 + src/backends/native/meta-output-kms.c | 23 + src/backends/native/meta-output-kms.c.orig | 554 ++ src/backends/native/meta-output-kms.h | 4 + src/backends/native/meta-renderer-native.c | 16 + src/backends/native/meta-renderer-native.h | 5 + .../native/meta-renderer-view-native.c | 117 + .../native/meta-renderer-view-native.h | 8 + src/backends/native/meta-stage-native.c | 7 + src/backends/native/meta-udev.c | 7 + src/backends/native/meta-udev.c.orig | 301 + src/backends/native/meta-udev.h | 2 + src/backends/native/meta-udev.h.orig | 59 + .../x11/nested/meta-renderer-x11-nested.c | 1 + src/compositor/meta-compositor-native.c | 3 + src/compositor/meta-compositor-view-native.c | 212 + src/compositor/meta-compositor-view-native.h | 3 + src/compositor/meta-surface-actor.c | 27 + src/compositor/meta-surface-actor.c.orig | 622 ++ src/compositor/meta-surface-actor.h | 3 + src/core/window-private.h | 3 + src/core/window-private.h.orig | 894 ++ src/core/window.c | 8 + src/core/window.c.orig | 7939 +++++++++++++++++ src/tests/meta-monitor-test-utils.c | 5 + src/tests/meta-monitor-test-utils.h | 2 + src/tests/monitor-configs/vrr-allowed.xml | 23 + src/tests/monitor-store-unit-tests.c | 51 + src/tests/monitor-unit-tests.c | 195 + 60 files changed, 12634 insertions(+), 18 deletions(-) create mode 100644 src/backends/native/meta-backend-native.c.orig create mode 100644 src/backends/native/meta-output-kms.c.orig create mode 100644 src/backends/native/meta-udev.c.orig create mode 100644 src/backends/native/meta-udev.h.orig create mode 100644 src/compositor/meta-surface-actor.c.orig create mode 100644 src/core/window-private.h.orig create mode 100644 src/core/window.c.orig create mode 100644 src/tests/monitor-configs/vrr-allowed.xml diff --git a/clutter/clutter/clutter-frame-clock.c b/clutter/clutter/clutter-frame-clock.c index 4cde64c..cd0935d 100644 --- a/clutter/clutter/clutter-frame-clock.c +++ b/clutter/clutter/clutter-frame-clock.c @@ -37,6 +37,8 @@ static guint signals[N_SIGNALS]; #define SYNC_DELAY_FALLBACK_FRACTION 0.875 +#define MINIMUM_REFRESH_RATE 30 + typedef struct _ClutterFrameListener { const ClutterFrameListenerIface *iface; @@ -54,6 +56,7 @@ typedef enum _ClutterFrameClockState { CLUTTER_FRAME_CLOCK_STATE_INIT, CLUTTER_FRAME_CLOCK_STATE_IDLE, + CLUTTER_FRAME_CLOCK_STATE_IDLE_TIMEOUT, CLUTTER_FRAME_CLOCK_STATE_SCHEDULED, CLUTTER_FRAME_CLOCK_STATE_DISPATCHING, CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED, @@ -65,6 +68,8 @@ struct _ClutterFrameClock float refresh_rate; int64_t refresh_interval_us; + int64_t minimum_refresh_interval_us; + ClutterFrameListener listener; GSource *source; @@ -72,6 +77,8 @@ struct _ClutterFrameClock int64_t frame_count; ClutterFrameClockState state; + ClutterFrameClockMode mode; + int64_t last_dispatch_time_us; int64_t last_dispatch_lateness_us; int64_t last_presentation_time_us; @@ -379,6 +386,7 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock, { case CLUTTER_FRAME_CLOCK_STATE_INIT: case CLUTTER_FRAME_CLOCK_STATE_IDLE: + case CLUTTER_FRAME_CLOCK_STATE_IDLE_TIMEOUT: case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: g_warn_if_reached (); break; @@ -399,6 +407,7 @@ clutter_frame_clock_notify_ready (ClutterFrameClock *frame_clock) { case CLUTTER_FRAME_CLOCK_STATE_INIT: case CLUTTER_FRAME_CLOCK_STATE_IDLE: + case CLUTTER_FRAME_CLOCK_STATE_IDLE_TIMEOUT: case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: g_warn_if_reached (); break; @@ -618,6 +627,39 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock, *out_min_render_time_allowed_us = min_render_time_allowed_us; } +static void +calculate_next_idle_timeout_us (ClutterFrameClock *frame_clock, + int64_t *out_next_update_time_us) +{ + int64_t now_us; + int64_t last_presentation_time_us; + int64_t next_presentation_time_us; + int64_t timeout_interval_us; + + now_us = g_get_monotonic_time (); + + last_presentation_time_us = frame_clock->last_presentation_time_us; + + timeout_interval_us = frame_clock->minimum_refresh_interval_us; + + if (last_presentation_time_us == 0) + { + *out_next_update_time_us = + frame_clock->last_dispatch_time_us ? + ((frame_clock->last_dispatch_time_us - + frame_clock->last_dispatch_lateness_us) + timeout_interval_us) : + now_us; + return; + } + + next_presentation_time_us = last_presentation_time_us + timeout_interval_us; + + while (next_presentation_time_us < now_us) + next_presentation_time_us += timeout_interval_us; + + *out_next_update_time_us = next_presentation_time_us; +} + void clutter_frame_clock_inhibit (ClutterFrameClock *frame_clock) { @@ -630,6 +672,7 @@ clutter_frame_clock_inhibit (ClutterFrameClock *frame_clock) case CLUTTER_FRAME_CLOCK_STATE_INIT: case CLUTTER_FRAME_CLOCK_STATE_IDLE: break; + case CLUTTER_FRAME_CLOCK_STATE_IDLE_TIMEOUT: case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: frame_clock->pending_reschedule = TRUE; frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE; @@ -671,6 +714,7 @@ clutter_frame_clock_schedule_update_now (ClutterFrameClock *frame_clock) case CLUTTER_FRAME_CLOCK_STATE_INIT: case CLUTTER_FRAME_CLOCK_STATE_IDLE: case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: + case CLUTTER_FRAME_CLOCK_STATE_IDLE_TIMEOUT: next_update_time_us = g_get_monotonic_time (); break; case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: @@ -703,15 +747,12 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock) { case CLUTTER_FRAME_CLOCK_STATE_INIT: next_update_time_us = g_get_monotonic_time (); - break; + g_source_set_ready_time (frame_clock->source, next_update_time_us); + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; + return; case CLUTTER_FRAME_CLOCK_STATE_IDLE: - calculate_next_update_time_us (frame_clock, - &next_update_time_us, - &frame_clock->next_presentation_time_us, - &frame_clock->min_render_time_allowed_us); - frame_clock->is_next_presentation_time_valid = - (frame_clock->next_presentation_time_us != 0); break; + case CLUTTER_FRAME_CLOCK_STATE_IDLE_TIMEOUT: case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: return; case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: @@ -720,11 +761,56 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock) return; } + switch (frame_clock->mode) + { + case CLUTTER_FRAME_CLOCK_MODE_FIXED: + calculate_next_update_time_us (frame_clock, + &next_update_time_us, + &frame_clock->next_presentation_time_us, + &frame_clock->min_render_time_allowed_us); + frame_clock->is_next_presentation_time_valid = + (frame_clock->next_presentation_time_us != 0); + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; + break; + case CLUTTER_FRAME_CLOCK_MODE_VARIABLE: + calculate_next_idle_timeout_us (frame_clock, + &next_update_time_us); + frame_clock->is_next_presentation_time_valid = FALSE; + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE_TIMEOUT; + break; + } + g_warn_if_fail (next_update_time_us != -1); frame_clock->next_update_time_us = next_update_time_us; g_source_set_ready_time (frame_clock->source, next_update_time_us); - frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; +} + +void +clutter_frame_clock_set_mode (ClutterFrameClock *frame_clock, + ClutterFrameClockMode mode) +{ + if (frame_clock->mode == mode) + return; + + frame_clock->mode = mode; + + switch (frame_clock->state) + { + case CLUTTER_FRAME_CLOCK_STATE_INIT: + case CLUTTER_FRAME_CLOCK_STATE_IDLE: + break; + case CLUTTER_FRAME_CLOCK_STATE_IDLE_TIMEOUT: + case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE; + frame_clock->pending_reschedule = TRUE; + break; + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: + case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: + break; + } + + maybe_reschedule_update (frame_clock); } static void @@ -803,6 +889,7 @@ clutter_frame_clock_dispatch (ClutterFrameClock *frame_clock, g_warn_if_reached (); break; case CLUTTER_FRAME_CLOCK_STATE_IDLE: + case CLUTTER_FRAME_CLOCK_STATE_IDLE_TIMEOUT: case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: break; case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: @@ -945,6 +1032,10 @@ clutter_frame_clock_new (float refresh_rate, init_frame_clock_source (frame_clock); clutter_frame_clock_set_refresh_rate (frame_clock, refresh_rate); + + frame_clock->minimum_refresh_interval_us = + (int64_t) (0.5 + G_USEC_PER_SEC / MINIMUM_REFRESH_RATE); + frame_clock->vblank_duration_us = vblank_duration_us; return frame_clock; @@ -978,6 +1069,7 @@ static void clutter_frame_clock_init (ClutterFrameClock *frame_clock) { frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_INIT; + frame_clock->mode = CLUTTER_FRAME_CLOCK_MODE_FIXED; } static void diff --git a/clutter/clutter/clutter-frame-clock.h b/clutter/clutter/clutter-frame-clock.h index 6fd5de4..085c7a0 100644 --- a/clutter/clutter/clutter-frame-clock.h +++ b/clutter/clutter/clutter-frame-clock.h @@ -55,6 +55,12 @@ typedef struct _ClutterFrameListenerIface gpointer user_data); } ClutterFrameListenerIface; +typedef enum _ClutterFrameClockMode +{ + CLUTTER_FRAME_CLOCK_MODE_FIXED, + CLUTTER_FRAME_CLOCK_MODE_VARIABLE, +} ClutterFrameClockMode; + CLUTTER_EXPORT ClutterFrameClock * clutter_frame_clock_new (float refresh_rate, int64_t vblank_duration_us, @@ -64,6 +70,10 @@ ClutterFrameClock * clutter_frame_clock_new (float re CLUTTER_EXPORT void clutter_frame_clock_destroy (ClutterFrameClock *frame_clock); +CLUTTER_EXPORT +void clutter_frame_clock_set_mode (ClutterFrameClock *frame_clock, + ClutterFrameClockMode mode); + CLUTTER_EXPORT void clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock, ClutterFrameInfo *frame_info); diff --git a/data/dbus-interfaces/org.gnome.Mutter.DisplayConfig.xml b/data/dbus-interfaces/org.gnome.Mutter.DisplayConfig.xml index af78ec0..23ebb77 100644 --- a/data/dbus-interfaces/org.gnome.Mutter.DisplayConfig.xml +++ b/data/dbus-interfaces/org.gnome.Mutter.DisplayConfig.xml @@ -343,6 +343,10 @@ - "is-underscanning" (b): whether underscanning is enabled (absence of this means underscanning not being supported) + - "is-vrr-allowed" (b): whether variable refresh rate is allowed + (absence of this means variable refresh + rate not being supported) + - "max-screen-size" (ii): the maximum size a screen may have (absence of this means unlimited screen size) @@ -461,6 +465,9 @@ - "enable_underscanning" (b): enable monitor underscanning; may only be set when underscanning is supported (see GetCurrentState). + - "allow_vrr" (b): allow variable refresh rate; may only be set + when variable refresh rate is supported (see + GetCurrentState). @properties may effect the global monitor configuration state. Possible properties are: diff --git a/data/org.gnome.mutter.gschema.xml.in b/data/org.gnome.mutter.gschema.xml.in index b879809..34e2a22 100644 --- a/data/org.gnome.mutter.gschema.xml.in +++ b/data/org.gnome.mutter.gschema.xml.in @@ -5,6 +5,7 @@ + diff --git a/src/backends/meta-monitor-config-manager.c b/src/backends/meta-monitor-config-manager.c index bdafba4..a4c4974 100644 --- a/src/backends/meta-monitor-config-manager.c +++ b/src/backends/meta-monitor-config-manager.c @@ -287,6 +287,7 @@ assign_monitor_crtc (MetaMonitor *monitor, .is_primary = assign_output_as_primary, .is_presentation = assign_output_as_presentation, .is_underscanning = data->monitor_config->enable_underscanning, + .is_vrr_allowed = data->monitor_config->allow_vrr, .has_max_bpc = data->monitor_config->has_max_bpc, .max_bpc = data->monitor_config->max_bpc }; @@ -693,7 +694,8 @@ create_monitor_config (MetaMonitor *monitor, *monitor_config = (MetaMonitorConfig) { .monitor_spec = meta_monitor_spec_clone (monitor_spec), .mode_spec = g_memdup2 (mode_spec, sizeof (MetaMonitorModeSpec)), - .enable_underscanning = meta_monitor_is_underscanning (monitor) + .enable_underscanning = meta_monitor_is_underscanning (monitor), + .allow_vrr = meta_monitor_is_vrr_allowed (monitor), }; monitor_config->has_max_bpc = @@ -1047,6 +1049,7 @@ clone_monitor_config_list (GList *monitor_configs_in) .mode_spec = g_memdup2 (monitor_config_in->mode_spec, sizeof (MetaMonitorModeSpec)), .enable_underscanning = monitor_config_in->enable_underscanning, + .allow_vrr = monitor_config_in->allow_vrr, .has_max_bpc = monitor_config_in->has_max_bpc, .max_bpc = monitor_config_in->max_bpc }; diff --git a/src/backends/meta-monitor-config-manager.h b/src/backends/meta-monitor-config-manager.h index bf45b23..5d6ed80 100644 --- a/src/backends/meta-monitor-config-manager.h +++ b/src/backends/meta-monitor-config-manager.h @@ -34,6 +34,7 @@ typedef struct _MetaMonitorConfig MetaMonitorSpec *monitor_spec; MetaMonitorModeSpec *mode_spec; gboolean enable_underscanning; + gboolean allow_vrr; gboolean has_max_bpc; unsigned int max_bpc; } MetaMonitorConfig; diff --git a/src/backends/meta-monitor-config-store.c b/src/backends/meta-monitor-config-store.c index 7650334..d189281 100644 --- a/src/backends/meta-monitor-config-store.c +++ b/src/backends/meta-monitor-config-store.c @@ -167,6 +167,7 @@ typedef enum STATE_MONITOR_MODE_RATE, STATE_MONITOR_MODE_FLAG, STATE_MONITOR_UNDERSCANNING, + STATE_MONITOR_VRR_ALLOWED, STATE_MONITOR_MAXBPC, STATE_DISABLED, STATE_POLICY, @@ -452,6 +453,10 @@ handle_start_element (GMarkupParseContext *context, { parser->state = STATE_MONITOR_UNDERSCANNING; } + else if (g_str_equal (element_name, "vrr-allowed")) + { + parser->state = STATE_MONITOR_VRR_ALLOWED; + } else if (g_str_equal (element_name, "maxbpc")) { parser->state = STATE_MONITOR_MAXBPC; @@ -549,6 +554,13 @@ handle_start_element (GMarkupParseContext *context, return; } + case STATE_MONITOR_VRR_ALLOWED: + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, + "Invalid element '%s' under vrr-allowed", element_name); + return; + } + case STATE_MONITOR_MAXBPC: { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, @@ -830,6 +842,14 @@ handle_end_element (GMarkupParseContext *context, return; } + case STATE_MONITOR_VRR_ALLOWED: + { + g_assert (g_str_equal (element_name, "vrr-allowed")); + + parser->state = STATE_MONITOR; + return; + } + case STATE_MONITOR_MAXBPC: { g_assert (g_str_equal (element_name, "maxbpc")); @@ -1329,6 +1349,14 @@ handle_text (GMarkupParseContext *context, return; } + case STATE_MONITOR_VRR_ALLOWED: + { + read_bool (text, text_len, + &parser->current_monitor_config->allow_vrr, + error); + return; + } + case STATE_MONITOR_MAXBPC: { int signed_max_bpc; @@ -1463,6 +1491,12 @@ meta_monitor_config_store_lookup (MetaMonitorConfigStore *config_store, key)); } +static const char * +bool_to_string (gboolean value) +{ + return value ? "yes" : "no"; +} + static void append_monitor_spec (GString *buffer, MetaMonitorSpec *monitor_spec, @@ -1528,6 +1562,9 @@ append_monitors (GString *buffer, if (monitor_config->enable_underscanning) g_string_append (buffer, " yes\n"); + g_string_append_printf (buffer, " %s\n", + bool_to_string (monitor_config->allow_vrr)); + if (monitor_config->has_max_bpc) { g_string_append_printf (buffer, " %u\n", @@ -1537,12 +1574,6 @@ append_monitors (GString *buffer, } } -static const char * -bool_to_string (gboolean value) -{ - return value ? "yes" : "no"; -} - static void append_transform (GString *buffer, MetaMonitorTransform transform) diff --git a/src/backends/meta-monitor-manager-private.h b/src/backends/meta-monitor-manager-private.h index f9cd9ae..383f5f5 100644 --- a/src/backends/meta-monitor-manager-private.h +++ b/src/backends/meta-monitor-manager-private.h @@ -102,6 +102,7 @@ struct _MetaOutputAssignment gboolean is_primary; gboolean is_presentation; gboolean is_underscanning; + gboolean is_vrr_allowed; gboolean has_max_bpc; unsigned int max_bpc; }; diff --git a/src/backends/meta-monitor-manager.c b/src/backends/meta-monitor-manager.c index bd02c55..5ed2626 100644 --- a/src/backends/meta-monitor-manager.c +++ b/src/backends/meta-monitor-manager.c @@ -2111,6 +2111,15 @@ meta_monitor_manager_handle_get_current_state (MetaDBusDisplayConfig *skeleton, g_variant_new_boolean (is_underscanning)); } + if (meta_monitor_is_vrr_capable (monitor)) + { + gboolean vrr_allowed = meta_monitor_is_vrr_allowed (monitor); + + g_variant_builder_add (&monitor_properties_builder, "{sv}", + "is-vrr-allowed", + g_variant_new_boolean (vrr_allowed)); + } + is_builtin = meta_monitor_is_laptop_panel (monitor); g_variant_builder_add (&monitor_properties_builder, "{sv}", "is-builtin", @@ -2430,6 +2439,8 @@ create_monitor_config_from_variant (MetaMonitorManager *manager, g_autoptr (GVariant) properties_variant = NULL; gboolean enable_underscanning = FALSE; gboolean set_underscanning = FALSE; + gboolean allow_vrr = TRUE; + gboolean set_allow_vrr = FALSE; g_variant_get (monitor_config_variant, "(ss@a{sv})", &connector, @@ -2465,6 +2476,19 @@ create_monitor_config_from_variant (MetaMonitorManager *manager, } } + set_allow_vrr = + g_variant_lookup (properties_variant, "allow_vrr", "b", + &allow_vrr); + if (set_allow_vrr) + { + if (allow_vrr && !meta_monitor_is_vrr_capable (monitor)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Variable refresh rate requested but unsupported"); + return NULL; + } + } + monitor_spec = meta_monitor_spec_clone (meta_monitor_get_spec (monitor)); monitor_mode_spec = g_new0 (MetaMonitorModeSpec, 1); @@ -2474,7 +2498,8 @@ create_monitor_config_from_variant (MetaMonitorManager *manager, *monitor_config = (MetaMonitorConfig) { .monitor_spec = monitor_spec, .mode_spec = monitor_mode_spec, - .enable_underscanning = enable_underscanning + .enable_underscanning = enable_underscanning, + .allow_vrr = allow_vrr, }; return monitor_config; diff --git a/src/backends/meta-monitor.c b/src/backends/meta-monitor.c index 00502bb..c668f3f 100644 --- a/src/backends/meta-monitor.c +++ b/src/backends/meta-monitor.c @@ -372,6 +372,25 @@ meta_monitor_is_underscanning (MetaMonitor *monitor) return meta_output_is_underscanning (output); } +gboolean +meta_monitor_is_vrr_capable (MetaMonitor *monitor) +{ + const MetaOutputInfo *output_info = + meta_monitor_get_main_output_info (monitor); + + return output_info->vrr_capable; +} + +gboolean +meta_monitor_is_vrr_allowed (MetaMonitor *monitor) +{ + MetaOutput *output; + + output = meta_monitor_get_main_output (monitor); + + return meta_output_is_vrr_allowed (output); +} + gboolean meta_monitor_get_max_bpc (MetaMonitor *monitor, unsigned int *max_bpc) diff --git a/src/backends/meta-monitor.h b/src/backends/meta-monitor.h index c679f53..8069dfd 100644 --- a/src/backends/meta-monitor.h +++ b/src/backends/meta-monitor.h @@ -121,6 +121,10 @@ gboolean meta_monitor_supports_color_transform (MetaMonitor *monitor); gboolean meta_monitor_is_underscanning (MetaMonitor *monitor); +gboolean meta_monitor_is_vrr_capable (MetaMonitor *monitor); + +gboolean meta_monitor_is_vrr_allowed (MetaMonitor *monitor); + gboolean meta_monitor_get_max_bpc (MetaMonitor *monitor, unsigned int *max_bpc); diff --git a/src/backends/meta-output.c b/src/backends/meta-output.c index 2610b35..c64ca6f 100644 --- a/src/backends/meta-output.c +++ b/src/backends/meta-output.c @@ -66,6 +66,8 @@ typedef struct _MetaOutputPrivate gboolean is_underscanning; + gboolean is_vrr_allowed; + gboolean has_max_bpc; unsigned int max_bpc; @@ -199,6 +201,22 @@ meta_output_is_underscanning (MetaOutput *output) return priv->is_underscanning; } +gboolean +meta_output_is_vrr_capable (MetaOutput *output) +{ + const MetaOutputInfo *output_info = meta_output_get_info (output); + + return output_info->vrr_capable; +} + +gboolean +meta_output_is_vrr_allowed (MetaOutput *output) +{ + MetaOutputPrivate *priv = meta_output_get_instance_private (output); + + return priv->is_vrr_allowed; +} + gboolean meta_output_get_max_bpc (MetaOutput *output, unsigned int *max_bpc) @@ -270,6 +288,8 @@ meta_output_assign_crtc (MetaOutput *output, priv->is_presentation = output_assignment->is_presentation; priv->is_underscanning = output_assignment->is_underscanning; + priv->is_vrr_allowed = output_assignment->is_vrr_allowed; + priv->has_max_bpc = output_assignment->has_max_bpc; if (priv->has_max_bpc) priv->max_bpc = output_assignment->max_bpc; diff --git a/src/backends/meta-output.h b/src/backends/meta-output.h index 08e59dd..2743e4d 100644 --- a/src/backends/meta-output.h +++ b/src/backends/meta-output.h @@ -147,6 +147,8 @@ typedef struct _MetaOutputInfo gboolean supports_underscanning; gboolean supports_color_transform; + gboolean vrr_capable; + unsigned int max_bpc_min; unsigned int max_bpc_max; @@ -231,6 +233,11 @@ gboolean meta_output_is_presentation (MetaOutput *output); META_EXPORT_TEST gboolean meta_output_is_underscanning (MetaOutput *output); +gboolean meta_output_is_vrr_capable (MetaOutput *output); + +META_EXPORT_TEST +gboolean meta_output_is_vrr_allowed (MetaOutput *output); + META_EXPORT_TEST gboolean meta_output_get_max_bpc (MetaOutput *output, unsigned int *max_bpc); diff --git a/src/backends/meta-renderer-view.c b/src/backends/meta-renderer-view.c index b974fcb..7b07962 100644 --- a/src/backends/meta-renderer-view.c +++ b/src/backends/meta-renderer-view.c @@ -33,6 +33,7 @@ #include "backends/meta-renderer-view.h" #include "backends/meta-crtc.h" +#include "backends/meta-output.h" #include "backends/meta-renderer.h" #include "clutter/clutter-mutter.h" #include "compositor/region-utils.h" @@ -43,6 +44,7 @@ enum PROP_TRANSFORM, PROP_CRTC, + PROP_OUTPUT, PROP_LAST }; @@ -54,6 +56,7 @@ typedef struct _MetaRendererViewPrivate MetaMonitorTransform transform; MetaCrtc *crtc; + MetaOutput *output; } MetaRendererViewPrivate; G_DEFINE_TYPE_WITH_PRIVATE (MetaRendererView, meta_renderer_view, @@ -77,6 +80,15 @@ meta_renderer_view_get_crtc (MetaRendererView *view) return priv->crtc; } +MetaOutput * +meta_renderer_view_get_output (MetaRendererView *view) +{ + MetaRendererViewPrivate *priv = + meta_renderer_view_get_instance_private (view); + + return priv->output; +} + static void meta_renderer_view_get_offscreen_transformation_matrix (ClutterStageView *view, graphene_matrix_t *matrix) @@ -151,6 +163,9 @@ meta_renderer_view_get_property (GObject *object, case PROP_CRTC: g_value_set_object (value, priv->crtc); break; + case PROP_OUTPUT: + g_value_set_object (value, priv->output); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -175,6 +190,9 @@ meta_renderer_view_set_property (GObject *object, case PROP_CRTC: priv->crtc = g_value_get_object (value); break; + case PROP_OUTPUT: + priv->output = g_value_get_object (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -222,5 +240,14 @@ meta_renderer_view_class_init (MetaRendererViewClass *klass) G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + obj_props[PROP_OUTPUT] = + g_param_spec_object ("output", + "MetaOutput", + "MetaOutput", + META_TYPE_OUTPUT, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (object_class, PROP_LAST, obj_props); } diff --git a/src/backends/meta-renderer-view.h b/src/backends/meta-renderer-view.h index 7c4858a..2cab082 100644 --- a/src/backends/meta-renderer-view.h +++ b/src/backends/meta-renderer-view.h @@ -38,4 +38,6 @@ MetaMonitorTransform meta_renderer_view_get_transform (MetaRendererView *view); META_EXPORT_TEST MetaCrtc *meta_renderer_view_get_crtc (MetaRendererView *view); +MetaOutput *meta_renderer_view_get_output (MetaRendererView *view); + #endif /* META_RENDERER_VIEW_H */ diff --git a/src/backends/meta-settings-private.h b/src/backends/meta-settings-private.h index 87af215..83bb8a8 100644 --- a/src/backends/meta-settings-private.h +++ b/src/backends/meta-settings-private.h @@ -32,9 +32,10 @@ typedef enum _MetaExperimentalFeature { META_EXPERIMENTAL_FEATURE_NONE = 0, META_EXPERIMENTAL_FEATURE_SCALE_MONITOR_FRAMEBUFFER = (1 << 0), - META_EXPERIMENTAL_FEATURE_KMS_MODIFIERS = (1 << 1), + META_EXPERIMENTAL_FEATURE_KMS_MODIFIERS = (1 << 1), META_EXPERIMENTAL_FEATURE_RT_SCHEDULER = (1 << 2), - META_EXPERIMENTAL_FEATURE_AUTOCLOSE_XWAYLAND = (1 << 3), + META_EXPERIMENTAL_FEATURE_AUTOCLOSE_XWAYLAND = (1 << 3), + META_EXPERIMENTAL_FEATURE_VARIABLE_REFRESH_RATE = (1 << 4), } MetaExperimentalFeature; typedef enum _MetaXwaylandExtension diff --git a/src/backends/meta-settings.c b/src/backends/meta-settings.c index 730a8e8..9b38020 100644 --- a/src/backends/meta-settings.c +++ b/src/backends/meta-settings.c @@ -298,6 +298,8 @@ experimental_features_handler (GVariant *features_variant, feature = META_EXPERIMENTAL_FEATURE_RT_SCHEDULER; else if (g_str_equal (feature_str, "autoclose-xwayland")) feature = META_EXPERIMENTAL_FEATURE_AUTOCLOSE_XWAYLAND; + else if (g_str_equal (feature_str, "variable-refresh-rate")) + feature = META_EXPERIMENTAL_FEATURE_VARIABLE_REFRESH_RATE; if (feature) g_message ("Enabling experimental feature '%s'", feature_str); diff --git a/src/backends/native/meta-backend-native.c b/src/backends/native/meta-backend-native.c index 5c1690d..cdd7aff 100644 --- a/src/backends/native/meta-backend-native.c +++ b/src/backends/native/meta-backend-native.c @@ -595,6 +595,9 @@ add_drm_device (MetaBackendNative *backend_native, if (meta_is_udev_device_disable_client_modifiers (device)) flags |= META_KMS_DEVICE_FLAG_DISABLE_CLIENT_MODIFIERS; + if (meta_is_udev_device_disable_vrr (device)) + flags |= META_KMS_DEVICE_FLAG_DISABLE_VRR; + if (meta_is_udev_device_preferred_primary (device)) flags |= META_KMS_DEVICE_FLAG_PREFERRED_PRIMARY; diff --git a/src/backends/native/meta-backend-native.c.orig b/src/backends/native/meta-backend-native.c.orig new file mode 100644 index 0000000..5c1690d --- /dev/null +++ b/src/backends/native/meta-backend-native.c.orig @@ -0,0 +1,1027 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* + * Copyright (C) 2014 Red Hat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Written by: + * Jasper St. Pierre + */ + +/** + * SECTION:meta-backend-native + * @title: MetaBackendNative + * @short_description: A native (KMS/evdev) MetaBackend + * + * MetaBackendNative is an implementation of #MetaBackend that uses "native" + * technologies like DRM/KMS and libinput/evdev to perform the necessary + * functions. + */ + +#include "config.h" + +#include "backends/native/meta-backend-native.h" +#include "backends/native/meta-backend-native-private.h" +#include "backends/native/meta-input-thread.h" + +#include + +#include "backends/meta-color-manager.h" +#include "backends/meta-cursor-tracker-private.h" +#include "backends/meta-idle-manager.h" +#include "backends/meta-keymap-utils.h" +#include "backends/meta-logical-monitor.h" +#include "backends/meta-monitor-manager-private.h" +#include "backends/meta-pointer-constraint.h" +#include "backends/meta-settings-private.h" +#include "backends/meta-stage-private.h" +#include "backends/native/meta-clutter-backend-native.h" +#include "backends/native/meta-device-pool-private.h" +#include "backends/native/meta-kms.h" +#include "backends/native/meta-kms-device.h" +#include "backends/native/meta-launcher.h" +#include "backends/native/meta-monitor-manager-native.h" +#include "backends/native/meta-render-device-gbm.h" +#include "backends/native/meta-renderer-native.h" +#include "backends/native/meta-seat-native.h" +#include "backends/native/meta-stage-native.h" +#include "cogl/cogl.h" +#include "core/meta-border.h" +#include "meta/main.h" +#include "meta-dbus-rtkit1.h" + +#ifdef HAVE_REMOTE_DESKTOP +#include "backends/meta-screen-cast.h" +#endif + +#ifdef HAVE_EGL_DEVICE +#include "backends/native/meta-render-device-egl-stream.h" +#endif + +#include "meta-private-enum-types.h" + +enum +{ + PROP_0, + + PROP_MODE, + + N_PROPS +}; + +static GParamSpec *obj_props[N_PROPS]; + +struct _MetaBackendNative +{ + MetaBackend parent; + + MetaLauncher *launcher; + MetaDevicePool *device_pool; + MetaUdev *udev; + MetaKms *kms; + + GHashTable *startup_render_devices; + + MetaBackendNativeMode mode; +}; + +static GInitableIface *initable_parent_iface; + +static void +initable_iface_init (GInitableIface *initable_iface); + +G_DEFINE_TYPE_WITH_CODE (MetaBackendNative, meta_backend_native, META_TYPE_BACKEND, + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, + initable_iface_init)) + +static void +meta_backend_native_dispose (GObject *object) +{ + MetaBackendNative *native = META_BACKEND_NATIVE (object); + + G_OBJECT_CLASS (meta_backend_native_parent_class)->dispose (object); + + g_clear_pointer (&native->startup_render_devices, g_hash_table_unref); + g_clear_object (&native->kms); + g_clear_object (&native->udev); + g_clear_object (&native->device_pool); + g_clear_pointer (&native->launcher, meta_launcher_free); +} + +static ClutterBackend * +meta_backend_native_create_clutter_backend (MetaBackend *backend) +{ + return CLUTTER_BACKEND (meta_clutter_backend_native_new (backend)); +} + +static ClutterSeat * +meta_backend_native_create_default_seat (MetaBackend *backend, + GError **error) +{ + MetaBackendNative *backend_native = META_BACKEND_NATIVE (backend); + const char *seat_id = NULL; + MetaSeatNativeFlag flags; + + switch (backend_native->mode) + { + case META_BACKEND_NATIVE_MODE_DEFAULT: + case META_BACKEND_NATIVE_MODE_HEADLESS: + seat_id = meta_backend_native_get_seat_id (backend_native); + break; + case META_BACKEND_NATIVE_MODE_TEST: + seat_id = META_BACKEND_TEST_INPUT_SEAT; + break; + } + + if (meta_backend_is_headless (backend)) + flags = META_SEAT_NATIVE_FLAG_NO_LIBINPUT; + else + flags = META_SEAT_NATIVE_FLAG_NONE; + + return CLUTTER_SEAT (g_object_new (META_TYPE_SEAT_NATIVE, + "backend", backend, + "seat-id", seat_id, + "flags", flags, + NULL)); +} + +#ifdef HAVE_REMOTE_DESKTOP +static void +maybe_disable_screen_cast_dma_bufs (MetaBackendNative *native) +{ + MetaBackend *backend = META_BACKEND (native); + MetaRenderer *renderer = meta_backend_get_renderer (backend); + MetaScreenCast *screen_cast = meta_backend_get_screen_cast (backend); + ClutterBackend *clutter_backend = + meta_backend_get_clutter_backend (backend); + CoglContext *cogl_context = + clutter_backend_get_cogl_context (clutter_backend); + CoglRenderer *cogl_renderer = cogl_context_get_renderer (cogl_context); + g_autoptr (GError) error = NULL; + g_autoptr (CoglDmaBufHandle) dmabuf_handle = NULL; + + if (!meta_renderer_is_hardware_accelerated (renderer)) + { + g_message ("Disabling DMA buffer screen sharing " + "(not hardware accelerated)"); + meta_screen_cast_disable_dma_bufs (screen_cast); + } + + dmabuf_handle = cogl_renderer_create_dma_buf (cogl_renderer, + 1, 1, + &error); + if (!dmabuf_handle) + { + g_message ("Disabling DMA buffer screen sharing " + "(implicit modifiers not supported)"); + meta_screen_cast_disable_dma_bufs (screen_cast); + } +} +#endif /* HAVE_REMOTE_DESKTOP */ + +static void +update_viewports (MetaBackend *backend) +{ + MetaMonitorManager *monitor_manager = + meta_backend_get_monitor_manager (backend); + ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend); + MetaSeatNative *seat = + META_SEAT_NATIVE (clutter_backend_get_default_seat (clutter_backend)); + MetaViewportInfo *viewports; + + viewports = meta_monitor_manager_get_viewports (monitor_manager); + meta_seat_native_set_viewports (seat, viewports); + g_object_unref (viewports); +} + +static void +meta_backend_native_post_init (MetaBackend *backend) +{ + MetaBackendNative *backend_native = META_BACKEND_NATIVE (backend); + MetaSettings *settings = meta_backend_get_settings (backend); + + META_BACKEND_CLASS (meta_backend_native_parent_class)->post_init (backend); + + if (meta_settings_is_experimental_feature_enabled (settings, + META_EXPERIMENTAL_FEATURE_RT_SCHEDULER)) + { + g_autoptr (MetaDBusRealtimeKit1) rtkit_proxy = NULL; + g_autoptr (GError) error = NULL; + + rtkit_proxy = + meta_dbus_realtime_kit1_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | + G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS | + G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, + "org.freedesktop.RealtimeKit1", + "/org/freedesktop/RealtimeKit1", + NULL, + &error); + + if (rtkit_proxy) + { + uint32_t priority; + + priority = sched_get_priority_min (SCHED_RR); + meta_dbus_realtime_kit1_call_make_thread_realtime_sync (rtkit_proxy, + gettid (), + priority, + NULL, + &error); + } + + if (error) + { + g_dbus_error_strip_remote_error (error); + g_message ("Failed to set RT scheduler: %s", error->message); + } + } + +#ifdef HAVE_REMOTE_DESKTOP + maybe_disable_screen_cast_dma_bufs (backend_native); +#endif + + g_clear_pointer (&backend_native->startup_render_devices, + g_hash_table_unref); + + update_viewports (backend); +} + +static MetaBackendCapabilities +meta_backend_native_get_capabilities (MetaBackend *backend) +{ + return META_BACKEND_CAPABILITY_BARRIERS; +} + +static MetaMonitorManager * +meta_backend_native_create_monitor_manager (MetaBackend *backend, + GError **error) +{ + MetaBackendNative *backend_native = META_BACKEND_NATIVE (backend); + MetaMonitorManager *manager; + gboolean needs_outputs; + + needs_outputs = !(backend_native->mode & META_BACKEND_NATIVE_MODE_HEADLESS); + manager = g_initable_new (META_TYPE_MONITOR_MANAGER_NATIVE, NULL, error, + "backend", backend, + "needs-outputs", needs_outputs, + NULL); + if (!manager) + return NULL; + + g_signal_connect_swapped (manager, "monitors-changed-internal", + G_CALLBACK (update_viewports), backend); + + return manager; +} + +static MetaColorManager * +meta_backend_native_create_color_manager (MetaBackend *backend) +{ + return g_object_new (META_TYPE_COLOR_MANAGER, + "backend", backend, + NULL); +} + +static MetaCursorRenderer * +meta_backend_native_get_cursor_renderer (MetaBackend *backend, + ClutterInputDevice *device) +{ + ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend); + MetaSeatNative *seat_native = + META_SEAT_NATIVE (clutter_backend_get_default_seat (clutter_backend)); + + return meta_seat_native_maybe_ensure_cursor_renderer (seat_native, device); +} + +static MetaRenderer * +meta_backend_native_create_renderer (MetaBackend *backend, + GError **error) +{ + MetaBackendNative *native = META_BACKEND_NATIVE (backend); + MetaRendererNative *renderer_native; + + renderer_native = meta_renderer_native_new (native, error); + if (!renderer_native) + return NULL; + + return META_RENDERER (renderer_native); +} + +static MetaInputSettings * +meta_backend_native_get_input_settings (MetaBackend *backend) +{ + ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend); + MetaSeatNative *seat_native = + META_SEAT_NATIVE (clutter_backend_get_default_seat (clutter_backend)); + + return meta_seat_impl_get_input_settings (seat_native->impl); +} + +static MetaLogicalMonitor * +meta_backend_native_get_current_logical_monitor (MetaBackend *backend) +{ + MetaCursorTracker *cursor_tracker = meta_backend_get_cursor_tracker (backend); + MetaMonitorManager *monitor_manager = + meta_backend_get_monitor_manager (backend); + graphene_point_t point; + + meta_cursor_tracker_get_pointer (cursor_tracker, &point, NULL); + return meta_monitor_manager_get_logical_monitor_at (monitor_manager, + point.x, point.y); +} + +static void +meta_backend_native_set_keymap (MetaBackend *backend, + const char *layouts, + const char *variants, + const char *options) +{ + ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend); + ClutterSeat *seat; + + seat = clutter_backend_get_default_seat (clutter_backend); + meta_seat_native_set_keyboard_map (META_SEAT_NATIVE (seat), + layouts, variants, options); + + meta_backend_notify_keymap_changed (backend); +} + +static struct xkb_keymap * +meta_backend_native_get_keymap (MetaBackend *backend) +{ + ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend); + ClutterSeat *seat; + + seat = clutter_backend_get_default_seat (clutter_backend); + return meta_seat_native_get_keyboard_map (META_SEAT_NATIVE (seat)); +} + +static xkb_layout_index_t +meta_backend_native_get_keymap_layout_group (MetaBackend *backend) +{ + ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend); + ClutterSeat *seat; + + seat = clutter_backend_get_default_seat (clutter_backend); + return meta_seat_native_get_keyboard_layout_index (META_SEAT_NATIVE (seat)); +} + +static void +meta_backend_native_lock_layout_group (MetaBackend *backend, + guint idx) +{ + ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend); + xkb_layout_index_t old_idx; + ClutterSeat *seat; + + old_idx = meta_backend_native_get_keymap_layout_group (backend); + if (old_idx == idx) + return; + + seat = clutter_backend_get_default_seat (clutter_backend); + meta_seat_native_set_keyboard_layout_index (META_SEAT_NATIVE (seat), idx); + meta_backend_notify_keymap_layout_group_changed (backend, idx); +} + +const char * +meta_backend_native_get_seat_id (MetaBackendNative *backend_native) +{ + switch (backend_native->mode) + { + case META_BACKEND_NATIVE_MODE_DEFAULT: + case META_BACKEND_NATIVE_MODE_TEST: + return meta_launcher_get_seat_id (backend_native->launcher); + case META_BACKEND_NATIVE_MODE_HEADLESS: + return "seat0"; + } + g_assert_not_reached (); +} + +static gboolean +meta_backend_native_is_headless (MetaBackend *backend) +{ + MetaBackendNative *backend_native = META_BACKEND_NATIVE (backend); + + return backend_native->mode == META_BACKEND_NATIVE_MODE_HEADLESS; +} + +static void +meta_backend_native_set_pointer_constraint (MetaBackend *backend, + MetaPointerConstraint *constraint) +{ + ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend); + ClutterSeat *seat = clutter_backend_get_default_seat (clutter_backend); + MetaPointerConstraintImpl *constraint_impl = NULL; + cairo_region_t *region; + + if (constraint) + { + double min_edge_distance; + + region = meta_pointer_constraint_get_region (constraint); + min_edge_distance = + meta_pointer_constraint_get_min_edge_distance (constraint); + constraint_impl = meta_pointer_constraint_impl_native_new (constraint, + region, + min_edge_distance); + } + + meta_seat_native_set_pointer_constraint (META_SEAT_NATIVE (seat), + constraint_impl); +} + +static void +meta_backend_native_update_screen_size (MetaBackend *backend, + int width, int height) +{ + ClutterActor *stage = meta_backend_get_stage (backend); + ClutterStageWindow *stage_window = + _clutter_stage_get_window (CLUTTER_STAGE (stage)); + MetaStageNative *stage_native = META_STAGE_NATIVE (stage_window); + + meta_stage_native_rebuild_views (stage_native); + + clutter_actor_set_size (stage, width, height); +} + +static MetaRenderDevice * +create_render_device (MetaBackendNative *backend_native, + const char *device_path, + GError **error) +{ + MetaBackend *backend = META_BACKEND (backend_native); + MetaDevicePool *device_pool = + meta_backend_native_get_device_pool (backend_native); + g_autoptr (MetaDeviceFile) device_file = NULL; + MetaDeviceFileFlags device_file_flags; + g_autoptr (MetaRenderDeviceGbm) render_device_gbm = NULL; + g_autoptr (GError) gbm_error = NULL; +#ifdef HAVE_EGL_DEVICE + g_autoptr (MetaRenderDeviceEglStream) render_device_egl_stream = NULL; + g_autoptr (GError) egl_stream_error = NULL; +#endif + + if (meta_backend_is_headless (backend)) + device_file_flags = META_DEVICE_FILE_FLAG_NONE; + else + device_file_flags = META_DEVICE_FILE_FLAG_TAKE_CONTROL; + + device_file = meta_device_pool_open (device_pool, + device_path, + device_file_flags, + error); + if (!device_file) + return NULL; + + if (meta_backend_is_headless (backend)) + { + int fd; + g_autofree char *render_node_path = NULL; + g_autoptr (MetaDeviceFile) render_node_device_file = NULL; + + fd = meta_device_file_get_fd (device_file); + render_node_path = drmGetRenderDeviceNameFromFd (fd); + + if (!render_node_path) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Couldn't find render node device for '%s'", + meta_device_file_get_path (device_file)); + return NULL; + } + + meta_topic (META_DEBUG_KMS, "Found render node '%s' from '%s'", + render_node_path, + meta_device_file_get_path (device_file)); + + render_node_device_file = + meta_device_pool_open (device_pool, render_node_path, + META_DEVICE_FILE_FLAG_NONE, + error); + if (!render_node_device_file) + return NULL; + + g_clear_pointer (&device_file, meta_device_file_release); + device_file = g_steal_pointer (&render_node_device_file); + } + +#ifdef HAVE_EGL_DEVICE + if (g_strcmp0 (getenv ("MUTTER_DEBUG_FORCE_EGL_STREAM"), "1") != 0) +#endif + { + render_device_gbm = meta_render_device_gbm_new (backend, device_file, + &gbm_error); + if (render_device_gbm) + { + MetaRenderDevice *render_device = + META_RENDER_DEVICE (render_device_gbm); + + if (meta_render_device_is_hardware_accelerated (render_device)) + return META_RENDER_DEVICE (g_steal_pointer (&render_device_gbm)); + } + } +#ifdef HAVE_EGL_DEVICE + else + { + g_set_error (&gbm_error, G_IO_ERROR, G_IO_ERROR_FAILED, + "GBM backend was disabled using env var"); + } +#endif + +#ifdef HAVE_EGL_DEVICE + render_device_egl_stream = + meta_render_device_egl_stream_new (backend, + device_file, + &egl_stream_error); + if (render_device_egl_stream) + return META_RENDER_DEVICE (g_steal_pointer (&render_device_egl_stream)); +#endif + + if (render_device_gbm) + return META_RENDER_DEVICE (g_steal_pointer (&render_device_gbm)); + + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_FAILED, + "Failed to initialize render device for %s: " + "%s" +#ifdef HAVE_EGL_DEVICE + ", %s" +#endif + , device_path + , gbm_error->message +#ifdef HAVE_EGL_DEVICE + , egl_stream_error->message +#endif + ); + + return NULL; +} + +static gboolean +add_drm_device (MetaBackendNative *backend_native, + GUdevDevice *device, + GError **error) +{ + MetaKmsDeviceFlag flags = META_KMS_DEVICE_FLAG_NONE; + const char *device_path; + g_autoptr (MetaRenderDevice) render_device = NULL; + MetaKmsDevice *kms_device; + MetaGpuKms *gpu_kms; + + if (meta_is_udev_device_platform_device (device)) + flags |= META_KMS_DEVICE_FLAG_PLATFORM_DEVICE; + + if (meta_is_udev_device_boot_vga (device)) + flags |= META_KMS_DEVICE_FLAG_BOOT_VGA; + + if (meta_is_udev_device_disable_modifiers (device)) + flags |= META_KMS_DEVICE_FLAG_DISABLE_MODIFIERS; + + if (meta_is_udev_device_disable_client_modifiers (device)) + flags |= META_KMS_DEVICE_FLAG_DISABLE_CLIENT_MODIFIERS; + + if (meta_is_udev_device_preferred_primary (device)) + flags |= META_KMS_DEVICE_FLAG_PREFERRED_PRIMARY; + + device_path = g_udev_device_get_device_file (device); + + render_device = create_render_device (backend_native, device_path, error); + if (!render_device) + return FALSE; + +#ifdef HAVE_EGL_DEVICE + if (META_IS_RENDER_DEVICE_EGL_STREAM (render_device)) + flags |= META_KMS_DEVICE_FLAG_FORCE_LEGACY; +#endif + + kms_device = meta_kms_create_device (backend_native->kms, device_path, flags, + error); + if (!kms_device) + return FALSE; + + g_hash_table_insert (backend_native->startup_render_devices, + g_strdup (device_path), + g_steal_pointer (&render_device)); + + gpu_kms = meta_gpu_kms_new (backend_native, kms_device, error); + meta_backend_add_gpu (META_BACKEND (backend_native), META_GPU (gpu_kms)); + return TRUE; +} + +static gboolean +should_ignore_device (MetaBackendNative *backend_native, + GUdevDevice *device) +{ + switch (backend_native->mode) + { + case META_BACKEND_NATIVE_MODE_DEFAULT: + case META_BACKEND_NATIVE_MODE_HEADLESS: + return meta_is_udev_device_ignore (device); + case META_BACKEND_NATIVE_MODE_TEST: + return !meta_is_udev_test_device (device); + } + g_assert_not_reached (); +} + +static void +on_udev_device_added (MetaUdev *udev, + GUdevDevice *device, + MetaBackendNative *native) +{ + MetaBackend *backend = META_BACKEND (native); + g_autoptr (GError) error = NULL; + const char *device_path; + GList *gpus, *l; + + if (!meta_udev_is_drm_device (udev, device)) + return; + + device_path = g_udev_device_get_device_file (device); + + gpus = meta_backend_get_gpus (backend); + for (l = gpus; l; l = l->next) + { + MetaGpuKms *gpu_kms = l->data; + + if (!g_strcmp0 (device_path, meta_gpu_kms_get_file_path (gpu_kms))) + { + g_warning ("Failed to hotplug secondary gpu '%s': %s", + device_path, "device already present"); + return; + } + } + + if (should_ignore_device (native, device)) + { + g_message ("Ignoring DRM device '%s'", device_path); + return; + } + + if (!add_drm_device (native, device, &error)) + { + if (meta_backend_is_headless (backend) && + g_error_matches (error, G_IO_ERROR, + G_IO_ERROR_PERMISSION_DENIED)) + { + meta_topic (META_DEBUG_BACKEND, + "Ignoring unavailable secondary gpu '%s': %s", + device_path, error->message); + } + else + { + g_warning ("Failed to hotplug secondary gpu '%s': %s", + device_path, error->message); + } + } +} + +static gboolean +init_gpus (MetaBackendNative *native, + GError **error) +{ + MetaBackend *backend = META_BACKEND (native); + MetaUdev *udev = meta_backend_native_get_udev (native); + g_autoptr (GError) local_error = NULL; + GList *devices; + GList *l; + + devices = meta_udev_list_drm_devices (udev, &local_error); + if (local_error) + { + g_propagate_error (error, g_steal_pointer (&local_error)); + return FALSE; + } + + for (l = devices; l; l = l->next) + { + GUdevDevice *device = l->data; + GError *local_error = NULL; + + if (should_ignore_device (native, device)) + { + g_message ("Ignoring DRM device '%s'", + g_udev_device_get_device_file (device)); + continue; + } + + if (!add_drm_device (native, device, &local_error)) + { + if (meta_backend_is_headless (backend) && + g_error_matches (local_error, G_IO_ERROR, + G_IO_ERROR_PERMISSION_DENIED)) + { + meta_topic (META_DEBUG_BACKEND, + "Ignoring unavailable gpu '%s': %s'", + g_udev_device_get_device_file (device), + local_error->message); + } + else + { + g_warning ("Failed to open gpu '%s': %s", + g_udev_device_get_device_file (device), + local_error->message); + } + + g_clear_error (&local_error); + continue; + } + } + + g_list_free_full (devices, g_object_unref); + + if (!meta_backend_is_headless (backend) && + g_list_length (meta_backend_get_gpus (backend)) == 0) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "No GPUs found"); + return FALSE; + } + + g_signal_connect_object (native->udev, "device-added", + G_CALLBACK (on_udev_device_added), native, + 0); + + return TRUE; +} + +static gboolean +meta_backend_native_initable_init (GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + MetaBackendNative *native = META_BACKEND_NATIVE (initable); + MetaBackend *backend = META_BACKEND (native); + MetaKmsFlags kms_flags; + + if (!meta_backend_is_headless (backend)) + { + const char *session_id = NULL; + const char *seat_id = NULL; + + switch (native->mode) + { + case META_BACKEND_NATIVE_MODE_DEFAULT: + break; + case META_BACKEND_NATIVE_MODE_HEADLESS: + g_assert_not_reached (); + break; + case META_BACKEND_NATIVE_MODE_TEST: + session_id = "dummy"; + seat_id = "seat0"; + break; + } + + native->launcher = meta_launcher_new (backend, + session_id, seat_id, + error); + if (!native->launcher) + return FALSE; + } + + native->device_pool = meta_device_pool_new (native); + native->udev = meta_udev_new (native); + + kms_flags = META_KMS_FLAG_NONE; + if (meta_backend_is_headless (backend)) + kms_flags |= META_KMS_FLAG_NO_MODE_SETTING; + + native->kms = meta_kms_new (META_BACKEND (native), kms_flags, error); + if (!native->kms) + return FALSE; + + if (!init_gpus (native, error)) + return FALSE; + + return initable_parent_iface->init (initable, cancellable, error); +} + +static void +meta_backend_native_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + MetaBackendNative *backend_native = META_BACKEND_NATIVE (object); + + switch (prop_id) + { + case PROP_MODE: + backend_native->mode = g_value_get_enum (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +initable_iface_init (GInitableIface *initable_iface) +{ + initable_parent_iface = g_type_interface_peek_parent (initable_iface); + + initable_iface->init = meta_backend_native_initable_init; +} + +static void +meta_backend_native_class_init (MetaBackendNativeClass *klass) +{ + MetaBackendClass *backend_class = META_BACKEND_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = meta_backend_native_set_property; + object_class->dispose = meta_backend_native_dispose; + + backend_class->create_clutter_backend = meta_backend_native_create_clutter_backend; + backend_class->create_default_seat = meta_backend_native_create_default_seat; + + backend_class->post_init = meta_backend_native_post_init; + backend_class->get_capabilities = meta_backend_native_get_capabilities; + + backend_class->create_monitor_manager = meta_backend_native_create_monitor_manager; + backend_class->create_color_manager = meta_backend_native_create_color_manager; + backend_class->get_cursor_renderer = meta_backend_native_get_cursor_renderer; + backend_class->create_renderer = meta_backend_native_create_renderer; + backend_class->get_input_settings = meta_backend_native_get_input_settings; + + backend_class->get_current_logical_monitor = meta_backend_native_get_current_logical_monitor; + + backend_class->set_keymap = meta_backend_native_set_keymap; + backend_class->get_keymap = meta_backend_native_get_keymap; + backend_class->get_keymap_layout_group = meta_backend_native_get_keymap_layout_group; + backend_class->lock_layout_group = meta_backend_native_lock_layout_group; + backend_class->update_screen_size = meta_backend_native_update_screen_size; + + backend_class->set_pointer_constraint = meta_backend_native_set_pointer_constraint; + + backend_class->is_headless = meta_backend_native_is_headless; + + obj_props[PROP_MODE] = + g_param_spec_enum ("mode", + "mode", + "mode", + META_TYPE_BACKEND_NATIVE_MODE, + META_BACKEND_NATIVE_MODE_DEFAULT, + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (object_class, N_PROPS, obj_props); +} + +static void +meta_backend_native_init (MetaBackendNative *backend_native) +{ + backend_native->startup_render_devices = + g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, g_object_unref); +} + +MetaLauncher * +meta_backend_native_get_launcher (MetaBackendNative *native) +{ + return native->launcher; +} + +MetaDevicePool * +meta_backend_native_get_device_pool (MetaBackendNative *native) +{ + return native->device_pool; +} + +MetaUdev * +meta_backend_native_get_udev (MetaBackendNative *native) +{ + return native->udev; +} + +MetaKms * +meta_backend_native_get_kms (MetaBackendNative *native) +{ + return native->kms; +} + +gboolean +meta_backend_native_activate_vt (MetaBackendNative *backend_native, + int vt, + GError **error) +{ + MetaLauncher *launcher = meta_backend_native_get_launcher (backend_native); + + switch (backend_native->mode) + { + case META_BACKEND_NATIVE_MODE_DEFAULT: + return meta_launcher_activate_vt (launcher, vt, error); + case META_BACKEND_NATIVE_MODE_HEADLESS: + case META_BACKEND_NATIVE_MODE_TEST: + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Can't switch VT while headless"); + return FALSE; + } + + g_assert_not_reached (); +} + +void +meta_backend_native_pause (MetaBackendNative *native) +{ + MetaBackend *backend = META_BACKEND (native); + MetaMonitorManager *monitor_manager = + meta_backend_get_monitor_manager (backend); + MetaMonitorManagerNative *monitor_manager_native = + META_MONITOR_MANAGER_NATIVE (monitor_manager); + ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend); + MetaSeatNative *seat = + META_SEAT_NATIVE (clutter_backend_get_default_seat (clutter_backend)); + MetaRenderer *renderer = meta_backend_get_renderer (backend); + + COGL_TRACE_BEGIN_SCOPED (MetaBackendNativePause, + "Backend (pause)"); + + meta_seat_native_release_devices (seat); + meta_renderer_pause (renderer); + meta_udev_pause (native->udev); + + meta_monitor_manager_native_pause (monitor_manager_native); +} + +void meta_backend_native_resume (MetaBackendNative *native) +{ + MetaBackend *backend = META_BACKEND (native); + ClutterStage *stage = CLUTTER_STAGE (meta_backend_get_stage (backend)); + MetaMonitorManager *monitor_manager = + meta_backend_get_monitor_manager (backend); + MetaMonitorManagerNative *monitor_manager_native = + META_MONITOR_MANAGER_NATIVE (monitor_manager); + ClutterBackend *clutter_backend = meta_backend_get_clutter_backend (backend); + MetaSeatNative *seat = + META_SEAT_NATIVE (clutter_backend_get_default_seat (clutter_backend)); + MetaRenderer *renderer = meta_backend_get_renderer (backend); + MetaIdleManager *idle_manager; + MetaInputSettings *input_settings; + + COGL_TRACE_BEGIN_SCOPED (MetaBackendNativeResume, + "Backend (resume)"); + + meta_monitor_manager_native_resume (monitor_manager_native); + meta_udev_resume (native->udev); + meta_kms_resume (native->kms); + + meta_seat_native_reclaim_devices (seat); + meta_renderer_resume (renderer); + + clutter_actor_queue_redraw (CLUTTER_ACTOR (stage)); + + idle_manager = meta_backend_get_idle_manager (backend); + meta_idle_manager_reset_idle_time (idle_manager); + + input_settings = meta_backend_get_input_settings (backend); + meta_input_settings_maybe_restore_numlock_state (input_settings); + + clutter_seat_ensure_a11y_state (CLUTTER_SEAT (seat)); +} + +static MetaRenderDevice * +meta_backend_native_create_render_device (MetaBackendNative *backend_native, + const char *device_path, + GError **error) +{ + g_autoptr (MetaRenderDevice) render_device = NULL; + + render_device = create_render_device (backend_native, device_path, error); + return g_steal_pointer (&render_device); +} + +MetaRenderDevice * +meta_backend_native_take_render_device (MetaBackendNative *backend_native, + const char *device_path, + GError **error) +{ + MetaRenderDevice *render_device; + + if (g_hash_table_steal_extended (backend_native->startup_render_devices, + device_path, + NULL, + (gpointer *) &render_device)) + { + return render_device; + } + else + { + return meta_backend_native_create_render_device (backend_native, + device_path, error); + } +} diff --git a/src/backends/native/meta-gpu-kms.c b/src/backends/native/meta-gpu-kms.c index 7d46955..326eddf 100644 --- a/src/backends/native/meta-gpu-kms.c +++ b/src/backends/native/meta-gpu-kms.c @@ -134,6 +134,23 @@ meta_gpu_kms_is_platform_device (MetaGpuKms *gpu_kms) return !!(flags & META_KMS_DEVICE_FLAG_PLATFORM_DEVICE); } +gboolean +meta_gpu_kms_disable_vrr (MetaGpuKms *gpu_kms) +{ + MetaGpu *gpu = META_GPU (gpu_kms); + MetaBackend *backend = meta_gpu_get_backend (gpu); + MetaSettings *settings = meta_backend_get_settings (backend); + MetaKmsDeviceFlag flags; + + if (!meta_settings_is_experimental_feature_enabled ( + settings, + META_EXPERIMENTAL_FEATURE_VARIABLE_REFRESH_RATE)) + return TRUE; + + flags = meta_kms_device_get_flags (gpu_kms->kms_device); + return !!(flags & META_KMS_DEVICE_FLAG_DISABLE_VRR); +} + static int compare_outputs (gconstpointer one, gconstpointer two) diff --git a/src/backends/native/meta-gpu-kms.h b/src/backends/native/meta-gpu-kms.h index 7a890c8..71808ee 100644 --- a/src/backends/native/meta-gpu-kms.h +++ b/src/backends/native/meta-gpu-kms.h @@ -47,6 +47,7 @@ gboolean meta_gpu_kms_is_crtc_active (MetaGpuKms *gpu_kms, gboolean meta_gpu_kms_is_boot_vga (MetaGpuKms *gpu_kms); gboolean meta_gpu_kms_is_platform_device (MetaGpuKms *gpu_kms); +gboolean meta_gpu_kms_disable_vrr (MetaGpuKms *gpu_kms); MetaKmsDevice * meta_gpu_kms_get_kms_device (MetaGpuKms *gpu_kms); diff --git a/src/backends/native/meta-kms-connector-private.h b/src/backends/native/meta-kms-connector-private.h index c5ae9e2..8a10239 100644 --- a/src/backends/native/meta-kms-connector-private.h +++ b/src/backends/native/meta-kms-connector-private.h @@ -42,6 +42,7 @@ typedef enum _MetaKmsConnectorProp META_KMS_CONNECTOR_PROP_MAX_BPC, META_KMS_CONNECTOR_PROP_COLORSPACE, META_KMS_CONNECTOR_PROP_HDR_OUTPUT_METADATA, + META_KMS_CONNECTOR_PROP_VRR_CAPABLE, META_KMS_CONNECTOR_N_PROPS } MetaKmsConnectorProp; diff --git a/src/backends/native/meta-kms-connector.c b/src/backends/native/meta-kms-connector.c index 0f96a5c..385a45c 100644 --- a/src/backends/native/meta-kms-connector.c +++ b/src/backends/native/meta-kms-connector.c @@ -410,6 +410,10 @@ state_set_properties (MetaKmsConnectorState *state, state->colorspace.supported = supported_drm_color_spaces_to_output_color_spaces (prop->supported_variants); } + + prop = &props[META_KMS_CONNECTOR_PROP_VRR_CAPABLE]; + if (prop->prop_id) + state->vrr_capable = prop->value; } static CoglSubpixelOrder @@ -842,6 +846,7 @@ meta_kms_connector_state_new (void) state = g_new0 (MetaKmsConnectorState, 1); state->suggested_x = -1; state->suggested_y = -1; + state->vrr_capable = FALSE; return state; } @@ -1015,6 +1020,9 @@ meta_kms_connector_state_changes (MetaKmsConnectorState *state, !hdr_metadata_equal (&state->hdr.value, &new_state->hdr.value)) return META_KMS_RESOURCE_CHANGE_FULL; + if (state->vrr_capable != new_state->vrr_capable) + return META_KMS_RESOURCE_CHANGE_FULL; + if (state->privacy_screen_state != new_state->privacy_screen_state) return META_KMS_RESOURCE_CHANGE_PRIVACY_SCREEN; @@ -1359,6 +1367,11 @@ init_properties (MetaKmsConnector *connector, .name = "HDR_OUTPUT_METADATA", .type = DRM_MODE_PROP_BLOB, }, + [META_KMS_CONNECTOR_PROP_VRR_CAPABLE] = + { + .name = "vrr_capable", + .type = DRM_MODE_PROP_RANGE, + }, }, .dpms_enum = { [META_KMS_CONNECTOR_DPMS_ON] = diff --git a/src/backends/native/meta-kms-connector.h b/src/backends/native/meta-kms-connector.h index be946a5..ec5c61a 100644 --- a/src/backends/native/meta-kms-connector.h +++ b/src/backends/native/meta-kms-connector.h @@ -72,6 +72,8 @@ typedef struct _MetaKmsConnectorState gboolean supported; gboolean unknown; } hdr; + + gboolean vrr_capable; } MetaKmsConnectorState; META_EXPORT_TEST diff --git a/src/backends/native/meta-kms-crtc-private.h b/src/backends/native/meta-kms-crtc-private.h index e36202b..ce1bdc6 100644 --- a/src/backends/native/meta-kms-crtc-private.h +++ b/src/backends/native/meta-kms-crtc-private.h @@ -31,9 +31,17 @@ typedef enum _MetaKmsCrtcProp META_KMS_CRTC_PROP_ACTIVE, META_KMS_CRTC_PROP_GAMMA_LUT, META_KMS_CRTC_PROP_GAMMA_LUT_SIZE, + META_KMS_CRTC_PROP_VRR_ENABLED, META_KMS_CRTC_N_PROPS } MetaKmsCrtcProp; +typedef enum _MetaKmsCrtcVRRMode +{ + META_KMS_CRTC_VRR_MODE_DISABLED = 0, + META_KMS_CRTC_VRR_MODE_ENABLED, + META_KMS_CRTC_VRR_MODE_N_PROPS, +} MetaKmsCrtcVRRMode; + MetaKmsCrtc * meta_kms_crtc_new (MetaKmsImplDevice *impl_device, drmModeCrtc *drm_crtc, int idx, diff --git a/src/backends/native/meta-kms-crtc.c b/src/backends/native/meta-kms-crtc.c index e540542..719d185 100644 --- a/src/backends/native/meta-kms-crtc.c +++ b/src/backends/native/meta-kms-crtc.c @@ -425,6 +425,11 @@ init_properties (MetaKmsCrtc *crtc, .name = "GAMMA_LUT_SIZE", .type = DRM_MODE_PROP_RANGE, }, + [META_KMS_CRTC_PROP_VRR_ENABLED] = + { + .name = "VRR_ENABLED", + .type = DRM_MODE_PROP_RANGE, + }, } }; } diff --git a/src/backends/native/meta-kms-impl-device-atomic.c b/src/backends/native/meta-kms-impl-device-atomic.c index 5064cd0..1e58289 100644 --- a/src/backends/native/meta-kms-impl-device-atomic.c +++ b/src/backends/native/meta-kms-impl-device-atomic.c @@ -335,6 +335,39 @@ add_crtc_property (MetaKmsImplDevice *impl_device, return TRUE; } +static gboolean +process_crtc_update (MetaKmsImplDevice *impl_device, + MetaKmsUpdate *update, + drmModeAtomicReq *req, + GArray *blob_ids, + gpointer update_entry, + gpointer user_data, + GError **error) +{ + MetaKmsCrtcUpdate *crtc_update = update_entry; + MetaKmsCrtc *crtc = crtc_update->crtc; + + if (crtc_update->vrr_mode.has_update) + { + meta_topic (META_DEBUG_KMS, + "[atomic] Setting VRR mode to %d on CRTC %u (%s)", + crtc_update->vrr_mode.is_enabled ? + META_KMS_CRTC_VRR_MODE_ENABLED : + META_KMS_CRTC_VRR_MODE_DISABLED, + meta_kms_crtc_get_id (crtc), + meta_kms_impl_device_get_path (impl_device)); + + if (!add_crtc_property (impl_device, + crtc, req, + META_KMS_CRTC_PROP_VRR_ENABLED, + crtc_update->vrr_mode.is_enabled, + error)) + return FALSE; + } + + return TRUE; +} + static gboolean process_mode_set (MetaKmsImplDevice *impl_device, MetaKmsUpdate *update, @@ -987,6 +1020,16 @@ meta_kms_impl_device_atomic_process_update (MetaKmsImplDevice *impl_device, &error)) goto err; + if (!process_entries (impl_device, + update, + req, + blob_ids, + meta_kms_update_get_crtc_updates (update), + NULL, + process_crtc_update, + &error)) + goto err; + if (!process_entries (impl_device, update, req, diff --git a/src/backends/native/meta-kms-impl-device-simple.c b/src/backends/native/meta-kms-impl-device-simple.c index 341d54c..a4e3a81 100644 --- a/src/backends/native/meta-kms-impl-device-simple.c +++ b/src/backends/native/meta-kms-impl-device-simple.c @@ -180,6 +180,47 @@ set_connector_property (MetaKmsImplDevice *impl_device, return TRUE; } +static gboolean +set_crtc_property (MetaKmsImplDevice *impl_device, + MetaKmsCrtc *crtc, + MetaKmsCrtcProp prop, + uint64_t value, + GError **error) +{ + uint32_t prop_id; + int fd; + int ret; + + prop_id = meta_kms_crtc_get_prop_id (crtc, prop); + if (!prop_id) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "Property (%s) not found on CRTC %u", + meta_kms_crtc_get_prop_name (crtc, prop), + meta_kms_crtc_get_id (crtc)); + return FALSE; + } + + fd = meta_kms_impl_device_get_fd (impl_device); + + ret = drmModeObjectSetProperty (fd, + meta_kms_crtc_get_id (crtc), + DRM_MODE_OBJECT_CRTC, + prop_id, + value); + if (ret != 0) + { + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (-ret), + "Failed to set CRTC %u property %u: %s", + meta_kms_crtc_get_id (crtc), + prop_id, + g_strerror (-ret)); + return FALSE; + } + + return TRUE; +} + static gboolean process_connector_update (MetaKmsImplDevice *impl_device, MetaKmsUpdate *update, @@ -269,6 +310,36 @@ process_connector_update (MetaKmsImplDevice *impl_device, return TRUE; } +static gboolean +process_crtc_update (MetaKmsImplDevice *impl_device, + MetaKmsUpdate *update, + gpointer update_entry, + GError **error) +{ + MetaKmsCrtcUpdate *crtc_update = update_entry; + MetaKmsCrtc *crtc = crtc_update->crtc; + + if (crtc_update->vrr_mode.has_update) + { + meta_topic (META_DEBUG_KMS, + "[simple] Setting VRR mode to %d on CRTC %u (%s)", + crtc_update->vrr_mode.is_enabled ? + META_KMS_CRTC_VRR_MODE_ENABLED : + META_KMS_CRTC_VRR_MODE_DISABLED, + meta_kms_crtc_get_id (crtc), + meta_kms_impl_device_get_path (impl_device)); + + if (!set_crtc_property (impl_device, + crtc, + META_KMS_CRTC_PROP_VRR_ENABLED, + crtc_update->vrr_mode.is_enabled, + error)) + return FALSE; + } + + return TRUE; +} + static CachedModeSet * cached_mode_set_new (GList *connectors, const drmModeModeInfo *drm_mode, @@ -1521,6 +1592,13 @@ meta_kms_impl_device_simple_process_update (MetaKmsImplDevice *impl_device, &error)) goto err; + if (!process_entries (impl_device, + update, + meta_kms_update_get_crtc_updates (update), + process_crtc_update, + &error)) + goto err; + if (!process_plane_assignments (impl_device, update, &failed_planes, &error)) goto err; diff --git a/src/backends/native/meta-kms-types.h b/src/backends/native/meta-kms-types.h index 8e035ac..b25c855 100644 --- a/src/backends/native/meta-kms-types.h +++ b/src/backends/native/meta-kms-types.h @@ -65,6 +65,7 @@ typedef enum _MetaKmsDeviceFlag META_KMS_DEVICE_FLAG_HAS_ADDFB2 = 1 << 5, META_KMS_DEVICE_FLAG_FORCE_LEGACY = 1 << 6, META_KMS_DEVICE_FLAG_DISABLE_CLIENT_MODIFIERS = 1 << 7, + META_KMS_DEVICE_FLAG_DISABLE_VRR = 1 << 8, } MetaKmsDeviceFlag; typedef enum _MetaKmsResourceChanges diff --git a/src/backends/native/meta-kms-update-private.h b/src/backends/native/meta-kms-update-private.h index ab4ad13..b7a66fe 100644 --- a/src/backends/native/meta-kms-update-private.h +++ b/src/backends/native/meta-kms-update-private.h @@ -114,6 +114,16 @@ typedef struct _MetaKmsConnectorUpdate } hdr; } MetaKmsConnectorUpdate; +typedef struct _MetaKmsCrtcUpdate +{ + MetaKmsCrtc *crtc; + + struct { + gboolean has_update; + gboolean is_enabled; + } vrr_mode; +} MetaKmsCrtcUpdate; + typedef struct _MetaKmsPageFlipListener { gatomicrefcount ref_count; @@ -182,6 +192,9 @@ GList * meta_kms_update_get_page_flip_listeners (MetaKmsUpdate *update); META_EXPORT_TEST GList * meta_kms_update_get_connector_updates (MetaKmsUpdate *update); +META_EXPORT_TEST +GList * meta_kms_update_get_crtc_updates (MetaKmsUpdate *update); + META_EXPORT_TEST GList * meta_kms_update_get_crtc_color_updates (MetaKmsUpdate *update); diff --git a/src/backends/native/meta-kms-update.c b/src/backends/native/meta-kms-update.c index ceb298a..d28e8df 100644 --- a/src/backends/native/meta-kms-update.c +++ b/src/backends/native/meta-kms-update.c @@ -39,6 +39,7 @@ struct _MetaKmsUpdate GList *mode_sets; GList *plane_assignments; GList *connector_updates; + GList *crtc_updates; GList *crtc_color_updates; MetaKmsCustomPageFlip *custom_page_flip; @@ -492,6 +493,45 @@ meta_kms_crtc_color_updates_free (MetaKmsCrtcColorUpdate *color_update) g_clear_pointer (&color_update->gamma.state, meta_gamma_lut_free); } +static MetaKmsCrtcUpdate * +ensure_crtc_update (MetaKmsUpdate *update, + MetaKmsCrtc *crtc) +{ + GList *l; + MetaKmsCrtcUpdate *crtc_update; + + for (l = update->crtc_updates; l; l = l->next) + { + crtc_update = l->data; + + if (crtc_update->crtc == crtc) + return crtc_update; + } + + crtc_update = g_new0 (MetaKmsCrtcUpdate, 1); + crtc_update->crtc = crtc; + + update->crtc_updates = g_list_prepend (update->crtc_updates, + crtc_update); + + return crtc_update; +} + +void +meta_kms_update_set_vrr_mode (MetaKmsUpdate *update, + MetaKmsCrtc *crtc, + gboolean enabled) +{ + MetaKmsCrtcUpdate *crtc_update; + + g_assert (!meta_kms_update_is_sealed (update)); + g_assert (meta_kms_crtc_get_device (crtc) == update->device); + + crtc_update = ensure_crtc_update (update, crtc); + crtc_update->vrr_mode.has_update = TRUE; + crtc_update->vrr_mode.is_enabled = enabled; +} + void meta_kms_update_add_page_flip_listener (MetaKmsUpdate *update, MetaKmsCrtc *crtc, @@ -691,6 +731,12 @@ meta_kms_update_get_connector_updates (MetaKmsUpdate *update) return update->connector_updates; } +GList * +meta_kms_update_get_crtc_updates (MetaKmsUpdate *update) +{ + return update->crtc_updates; +} + GList * meta_kms_update_get_crtc_color_updates (MetaKmsUpdate *update) { @@ -1032,6 +1078,7 @@ meta_kms_update_free (MetaKmsUpdate *update) g_list_free_full (update->page_flip_listeners, (GDestroyNotify) meta_kms_page_flip_listener_unref); g_list_free_full (update->connector_updates, g_free); + g_list_free_full (update->crtc_updates, g_free); g_list_free_full (update->crtc_color_updates, (GDestroyNotify) meta_kms_crtc_color_updates_free); g_clear_pointer (&update->custom_page_flip, meta_kms_custom_page_flip_free); diff --git a/src/backends/native/meta-kms-update.h b/src/backends/native/meta-kms-update.h index 26da6ca..93bd8e4 100644 --- a/src/backends/native/meta-kms-update.h +++ b/src/backends/native/meta-kms-update.h @@ -146,6 +146,10 @@ void meta_kms_update_set_crtc_gamma (MetaKmsUpdate *update, MetaKmsCrtc *crtc, const MetaGammaLut *gamma); +void meta_kms_update_set_vrr_mode (MetaKmsUpdate *update, + MetaKmsCrtc *crtc, + gboolean enabled); + void meta_kms_plane_assignment_set_fb_damage (MetaKmsPlaneAssignment *plane_assignment, const int *rectangles, int n_rectangles); diff --git a/src/backends/native/meta-output-kms.c b/src/backends/native/meta-output-kms.c index f071e4d..2597ff6 100644 --- a/src/backends/native/meta-output-kms.c +++ b/src/backends/native/meta-output-kms.c @@ -97,6 +97,26 @@ meta_output_kms_set_underscan (MetaOutputKms *output_kms, } } +void +meta_output_kms_set_vrr_mode (MetaOutputKms *output_kms, + MetaKmsUpdate *kms_update, + gboolean enabled) +{ + MetaOutput *output = META_OUTPUT (output_kms); + const MetaOutputInfo *output_info = meta_output_get_info (output); + MetaCrtc *crtc; + MetaKmsCrtc *kms_crtc; + + g_assert (output_info->vrr_capable); + + crtc = meta_output_get_assigned_crtc (output); + kms_crtc = meta_crtc_kms_get_kms_crtc (META_CRTC_KMS (crtc)); + + meta_kms_update_set_vrr_mode (kms_update, + kms_crtc, + enabled); +} + void meta_output_kms_set_max_bpc (MetaOutputKms *output_kms, MetaKmsUpdate *kms_update) @@ -475,6 +495,9 @@ meta_output_kms_new (MetaGpuKms *gpu_kms, output_info->supports_underscanning = meta_kms_connector_is_underscanning_supported (kms_connector); + output_info->vrr_capable = (connector_state->vrr_capable && + !meta_gpu_kms_disable_vrr (gpu_kms)); + max_bpc_range = meta_kms_connector_get_max_bpc (kms_connector); if (max_bpc_range) { diff --git a/src/backends/native/meta-output-kms.c.orig b/src/backends/native/meta-output-kms.c.orig new file mode 100644 index 0000000..f071e4d --- /dev/null +++ b/src/backends/native/meta-output-kms.c.orig @@ -0,0 +1,554 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* + * Copyright (C) 2013-2017 Red Hat + * Copyright (C) 2018 DisplayLink (UK) Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +#include "config.h" + +#include "backends/native/meta-output-kms.h" + +#include +#include +#include + +#include "backends/meta-crtc.h" +#include "backends/native/meta-kms.h" +#include "backends/native/meta-kms-connector.h" +#include "backends/native/meta-kms-device.h" +#include "backends/native/meta-kms-mode.h" +#include "backends/native/meta-kms-update.h" +#include "backends/native/meta-kms-utils.h" +#include "backends/native/meta-crtc-kms.h" +#include "backends/native/meta-crtc-mode-kms.h" + +#define SYNC_TOLERANCE_HZ 0.001 + +struct _MetaOutputKms +{ + MetaOutputNative parent; + + MetaKmsConnector *kms_connector; +}; + +G_DEFINE_TYPE (MetaOutputKms, meta_output_kms, META_TYPE_OUTPUT_NATIVE) + +MetaKmsConnector * +meta_output_kms_get_kms_connector (MetaOutputKms *output_kms) +{ + return output_kms->kms_connector; +} + +void +meta_output_kms_set_underscan (MetaOutputKms *output_kms, + MetaKmsUpdate *kms_update) +{ + MetaOutput *output = META_OUTPUT (output_kms); + const MetaOutputInfo *output_info = meta_output_get_info (output); + + if (!output_info->supports_underscanning) + return; + + if (meta_output_is_underscanning (output)) + { + MetaCrtc *crtc; + const MetaCrtcConfig *crtc_config; + const MetaCrtcModeInfo *crtc_mode_info; + uint64_t hborder, vborder; + + crtc = meta_output_get_assigned_crtc (output); + crtc_config = meta_crtc_get_config (crtc); + crtc_mode_info = meta_crtc_mode_get_info (crtc_config->mode); + + hborder = MIN (128, (uint64_t) round (crtc_mode_info->width * 0.05)); + vborder = MIN (128, (uint64_t) round (crtc_mode_info->height * 0.05)); + + g_debug ("Setting underscan of connector %s to %" G_GUINT64_FORMAT " x %" G_GUINT64_FORMAT, + meta_kms_connector_get_name (output_kms->kms_connector), + hborder, vborder); + + meta_kms_update_set_underscanning (kms_update, + output_kms->kms_connector, + hborder, vborder); + } + else + { + g_debug ("Unsetting underscan of connector %s", + meta_kms_connector_get_name (output_kms->kms_connector)); + + meta_kms_update_unset_underscanning (kms_update, + output_kms->kms_connector); + } +} + +void +meta_output_kms_set_max_bpc (MetaOutputKms *output_kms, + MetaKmsUpdate *kms_update) +{ + MetaKmsConnector *kms_connector = output_kms->kms_connector; + const MetaKmsRange *range; + + range = meta_kms_connector_get_max_bpc (kms_connector); + if (range) + { + MetaOutput *output = META_OUTPUT (output_kms); + unsigned int max_bpc; + + if (!meta_output_get_max_bpc (output, &max_bpc)) + return; + + if (max_bpc >= range->min_value && max_bpc <= range->max_value) + { + meta_kms_update_set_max_bpc (kms_update, kms_connector, max_bpc); + } + else + { + g_warning ("Ignoring out of range value %u for max bpc (%u-%u)", + max_bpc, + (unsigned) range->min_value, + (unsigned) range->max_value); + } + } +} + +static MetaPrivacyScreenState +meta_output_kms_get_privacy_screen_state (MetaOutput *output) +{ + MetaOutputKms *output_kms = META_OUTPUT_KMS (output); + const MetaKmsConnectorState *connector_state; + + connector_state = + meta_kms_connector_get_current_state (output_kms->kms_connector); + + return connector_state->privacy_screen_state; +} + +static gboolean +meta_output_kms_is_color_space_supported (MetaOutput *output, + MetaOutputColorspace color_space) +{ + MetaOutputKms *output_kms = META_OUTPUT_KMS (output); + const MetaKmsConnectorState *connector_state; + const MetaOutputInfo *output_info; + + output_info = meta_output_get_info (output); + + if (!meta_output_info_is_color_space_supported (output_info, color_space)) + return FALSE; + + connector_state = + meta_kms_connector_get_current_state (output_kms->kms_connector); + + if (!(connector_state->colorspace.supported & (1 << color_space))) + return FALSE; + + return TRUE; +} + +static gboolean +meta_output_kms_is_hdr_metadata_supported (MetaOutput *output) +{ + MetaOutputKms *output_kms = META_OUTPUT_KMS (output); + const MetaKmsConnectorState *connector_state; + + connector_state = + meta_kms_connector_get_current_state (output_kms->kms_connector); + + return connector_state->hdr.supported; +} + +uint32_t +meta_output_kms_get_connector_id (MetaOutputKms *output_kms) +{ + return meta_kms_connector_get_id (output_kms->kms_connector); +} + +gboolean +meta_output_kms_can_clone (MetaOutputKms *output_kms, + MetaOutputKms *other_output_kms) +{ + return meta_kms_connector_can_clone (output_kms->kms_connector, + other_output_kms->kms_connector); +} + +static GBytes * +meta_output_kms_read_edid (MetaOutputNative *output_native) +{ + MetaOutputKms *output_kms = META_OUTPUT_KMS (output_native); + const MetaKmsConnectorState *connector_state; + GBytes *edid_data; + + connector_state = + meta_kms_connector_get_current_state (output_kms->kms_connector); + edid_data = connector_state->edid_data; + if (!edid_data) + return NULL; + + return g_bytes_new_from_bytes (edid_data, 0, g_bytes_get_size (edid_data)); +} + +static void +add_common_modes (MetaOutputInfo *output_info, + MetaGpuKms *gpu_kms) +{ + MetaCrtcMode *crtc_mode; + GPtrArray *array; + unsigned i; + unsigned max_hdisplay = 0; + unsigned max_vdisplay = 0; + float max_refresh_rate = 0.0; + uint32_t max_pixel_clock = 0; + MetaKmsDevice *kms_device; + MetaKmsModeFlag flag_filter; + GList *l; + + for (i = 0; i < output_info->n_modes; i++) + { + const MetaCrtcModeInfo *crtc_mode_info = + meta_crtc_mode_get_info (output_info->modes[i]); + + max_hdisplay = MAX (max_hdisplay, crtc_mode_info->width); + max_vdisplay = MAX (max_vdisplay, crtc_mode_info->height); + max_refresh_rate = MAX (max_refresh_rate, crtc_mode_info->refresh_rate); + max_pixel_clock = MAX (max_pixel_clock, crtc_mode_info->pixel_clock_khz); + } + + max_refresh_rate = MAX (max_refresh_rate, 60.0); + max_refresh_rate += SYNC_TOLERANCE_HZ; + + kms_device = meta_gpu_kms_get_kms_device (gpu_kms); + + array = g_ptr_array_new (); + + if (max_hdisplay > max_vdisplay) + flag_filter = META_KMS_MODE_FLAG_FALLBACK_LANDSCAPE; + else + flag_filter = META_KMS_MODE_FLAG_FALLBACK_PORTRAIT; + + for (l = meta_kms_device_get_fallback_modes (kms_device); l; l = l->next) + { + MetaKmsMode *fallback_mode = l->data; + const drmModeModeInfo *drm_mode; + float refresh_rate; + gboolean is_duplicate = FALSE; + + if (!(meta_kms_mode_get_flags (fallback_mode) & flag_filter)) + continue; + + drm_mode = meta_kms_mode_get_drm_mode (fallback_mode); + refresh_rate = meta_calculate_drm_mode_refresh_rate (drm_mode); + if (drm_mode->hdisplay > max_hdisplay || + drm_mode->vdisplay > max_vdisplay || + refresh_rate > max_refresh_rate || + drm_mode->clock > max_pixel_clock) + continue; + + for (i = 0; i < output_info->n_modes; i++) + { + const MetaCrtcModeInfo *crtc_mode_info = + meta_crtc_mode_get_info (output_info->modes[i]); + + if (drm_mode->hdisplay == crtc_mode_info->width && + drm_mode->vdisplay == crtc_mode_info->height && + (fabs (refresh_rate - crtc_mode_info->refresh_rate) < + SYNC_TOLERANCE_HZ)) + { + is_duplicate = TRUE; + break; + } + } + if (is_duplicate) + continue; + + crtc_mode = meta_gpu_kms_get_mode_from_kms_mode (gpu_kms, fallback_mode); + g_ptr_array_add (array, crtc_mode); + } + + output_info->modes = g_renew (MetaCrtcMode *, output_info->modes, + output_info->n_modes + array->len); + memcpy (output_info->modes + output_info->n_modes, array->pdata, + array->len * sizeof (MetaCrtcMode *)); + output_info->n_modes += array->len; + + g_ptr_array_free (array, TRUE); +} + +static int +compare_modes (const void *one, + const void *two) +{ + MetaCrtcMode *crtc_mode_one = *(MetaCrtcMode **) one; + MetaCrtcMode *crtc_mode_two = *(MetaCrtcMode **) two; + const MetaCrtcModeInfo *crtc_mode_info_one = + meta_crtc_mode_get_info (crtc_mode_one); + const MetaCrtcModeInfo *crtc_mode_info_two = + meta_crtc_mode_get_info (crtc_mode_two); + + if (crtc_mode_info_one->width != crtc_mode_info_two->width) + return crtc_mode_info_one->width > crtc_mode_info_two->width ? -1 : 1; + if (crtc_mode_info_one->height != crtc_mode_info_two->height) + return crtc_mode_info_one->height > crtc_mode_info_two->height ? -1 : 1; + if (crtc_mode_info_one->refresh_rate != crtc_mode_info_two->refresh_rate) + return (crtc_mode_info_one->refresh_rate > crtc_mode_info_two->refresh_rate + ? -1 : 1); + + return g_strcmp0 (meta_crtc_mode_get_name (crtc_mode_one), + meta_crtc_mode_get_name (crtc_mode_two)); +} + +static gboolean +are_all_modes_equally_sized (MetaOutputInfo *output_info) +{ + const MetaCrtcModeInfo *base = + meta_crtc_mode_get_info (output_info->modes[0]); + int i; + + for (i = 1; i < output_info->n_modes; i++) + { + const MetaCrtcModeInfo *mode_info = + meta_crtc_mode_get_info (output_info->modes[i]); + + if (base->width != mode_info->width || + base->height != mode_info->height) + return FALSE; + } + + return TRUE; +} + +static void +maybe_add_fallback_modes (const MetaKmsConnectorState *connector_state, + MetaOutputInfo *output_info, + MetaGpuKms *gpu_kms, + MetaKmsConnector *kms_connector) +{ + if (!connector_state->modes) + return; + + if (!connector_state->has_scaling) + return; + + if (output_info->connector_type == DRM_MODE_CONNECTOR_eDP && + !are_all_modes_equally_sized (output_info)) + return; + + meta_topic (META_DEBUG_KMS, "Adding common modes to connector %u on %s", + meta_kms_connector_get_id (kms_connector), + meta_gpu_kms_get_file_path (gpu_kms)); + add_common_modes (output_info, gpu_kms); +} + +static gboolean +init_output_modes (MetaOutputInfo *output_info, + MetaGpuKms *gpu_kms, + MetaKmsConnector *kms_connector, + GError **error) +{ + const MetaKmsConnectorState *connector_state; + GList *l; + int i; + + connector_state = meta_kms_connector_get_current_state (kms_connector); + + output_info->preferred_mode = NULL; + + output_info->n_modes = g_list_length (connector_state->modes); + output_info->modes = g_new0 (MetaCrtcMode *, output_info->n_modes); + for (l = connector_state->modes, i = 0; l; l = l->next, i++) + { + MetaKmsMode *kms_mode = l->data; + const drmModeModeInfo *drm_mode = meta_kms_mode_get_drm_mode (kms_mode); + MetaCrtcMode *crtc_mode; + + crtc_mode = meta_gpu_kms_get_mode_from_kms_mode (gpu_kms, kms_mode); + output_info->modes[i] = crtc_mode; + if (drm_mode->type & DRM_MODE_TYPE_PREFERRED) + output_info->preferred_mode = output_info->modes[i]; + } + + maybe_add_fallback_modes (connector_state, output_info, gpu_kms, kms_connector); + if (!output_info->modes) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "No modes available"); + return FALSE; + } + + qsort (output_info->modes, output_info->n_modes, + sizeof (MetaCrtcMode *), compare_modes); + + if (!output_info->preferred_mode) + output_info->preferred_mode = output_info->modes[0]; + + return TRUE; +} + +static MetaConnectorType +meta_kms_connector_type_from_drm (uint32_t drm_connector_type) +{ + g_warn_if_fail (drm_connector_type < META_CONNECTOR_TYPE_META); + + return (MetaConnectorType) drm_connector_type; +} + +MetaOutputKms * +meta_output_kms_new (MetaGpuKms *gpu_kms, + MetaKmsConnector *kms_connector, + MetaOutput *old_output, + GError **error) +{ + MetaGpu *gpu = META_GPU (gpu_kms); + uint32_t connector_id; + uint32_t gpu_id; + g_autoptr (MetaOutputInfo) output_info = NULL; + MetaOutput *output; + MetaOutputKms *output_kms; + uint32_t drm_connector_type; + const MetaKmsConnectorState *connector_state; + GArray *crtcs; + GList *l; + const MetaKmsRange *max_bpc_range; + + gpu_id = meta_gpu_kms_get_id (gpu_kms); + connector_id = meta_kms_connector_get_id (kms_connector); + + output_info = meta_output_info_new (); + output_info->name = g_strdup (meta_kms_connector_get_name (kms_connector)); + + connector_state = meta_kms_connector_get_current_state (kms_connector); + + output_info->panel_orientation_transform = + connector_state->panel_orientation_transform; + if (meta_monitor_transform_is_rotated (output_info->panel_orientation_transform)) + { + output_info->width_mm = connector_state->height_mm; + output_info->height_mm = connector_state->width_mm; + } + else + { + output_info->width_mm = connector_state->width_mm; + output_info->height_mm = connector_state->height_mm; + } + + drm_connector_type = meta_kms_connector_get_connector_type (kms_connector); + output_info->connector_type = + meta_kms_connector_type_from_drm (drm_connector_type); + + if (!init_output_modes (output_info, gpu_kms, kms_connector, error)) + return NULL; + + crtcs = g_array_new (FALSE, FALSE, sizeof (MetaCrtc *)); + + for (l = meta_gpu_get_crtcs (gpu); l; l = l->next) + { + MetaCrtcKms *crtc_kms = META_CRTC_KMS (l->data); + MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms); + uint32_t crtc_idx; + + crtc_idx = meta_kms_crtc_get_idx (kms_crtc); + if (connector_state->common_possible_crtcs & (1 << crtc_idx)) + g_array_append_val (crtcs, crtc_kms); + } + + output_info->n_possible_crtcs = crtcs->len; + output_info->possible_crtcs = (MetaCrtc **) g_array_free (crtcs, FALSE); + + output_info->suggested_x = connector_state->suggested_x; + output_info->suggested_y = connector_state->suggested_y; + output_info->hotplug_mode_update = connector_state->hotplug_mode_update; + output_info->supports_underscanning = + meta_kms_connector_is_underscanning_supported (kms_connector); + + max_bpc_range = meta_kms_connector_get_max_bpc (kms_connector); + if (max_bpc_range) + { + output_info->max_bpc_min = max_bpc_range->min_value; + output_info->max_bpc_max = max_bpc_range->max_value; + } + + if (connector_state->edid_data) + meta_output_info_parse_edid (output_info, connector_state->edid_data); + + output_info->tile_info = connector_state->tile_info; + + output = g_object_new (META_TYPE_OUTPUT_KMS, + "id", ((uint64_t) gpu_id << 32) | connector_id, + "gpu", gpu, + "info", output_info, + NULL); + output_kms = META_OUTPUT_KMS (output); + output_kms->kms_connector = kms_connector; + + if (connector_state->current_crtc_id) + { + for (l = meta_gpu_get_crtcs (gpu); l; l = l->next) + { + MetaCrtc *crtc = l->data; + + if (meta_crtc_get_id (crtc) == connector_state->current_crtc_id) + { + MetaOutputAssignment output_assignment; + + if (old_output) + { + output_assignment = (MetaOutputAssignment) { + .is_primary = meta_output_is_primary (old_output), + .is_presentation = meta_output_is_presentation (old_output), + }; + } + else + { + output_assignment = (MetaOutputAssignment) { + .is_primary = FALSE, + .is_presentation = FALSE, + }; + } + meta_output_assign_crtc (output, crtc, &output_assignment); + break; + } + } + } + else + { + meta_output_unassign_crtc (output); + } + + return output_kms; +} + +static void +meta_output_kms_init (MetaOutputKms *output_kms) +{ +} + +static void +meta_output_kms_class_init (MetaOutputKmsClass *klass) +{ + MetaOutputNativeClass *output_native_class = META_OUTPUT_NATIVE_CLASS (klass); + MetaOutputClass *output_class = META_OUTPUT_CLASS (klass); + + output_class->get_privacy_screen_state = + meta_output_kms_get_privacy_screen_state; + output_class->is_color_space_supported = + meta_output_kms_is_color_space_supported; + output_class->is_hdr_metadata_supported = + meta_output_kms_is_hdr_metadata_supported; + + output_native_class->read_edid = meta_output_kms_read_edid; +} diff --git a/src/backends/native/meta-output-kms.h b/src/backends/native/meta-output-kms.h index a9d8f99..0d41653 100644 --- a/src/backends/native/meta-output-kms.h +++ b/src/backends/native/meta-output-kms.h @@ -40,6 +40,10 @@ void meta_output_kms_set_power_save_mode (MetaOutputKms *output_kms, void meta_output_kms_set_underscan (MetaOutputKms *output_kms, MetaKmsUpdate *kms_update); +void meta_output_kms_set_vrr_mode (MetaOutputKms *output_kms, + MetaKmsUpdate *kms_update, + gboolean enabled); + void meta_output_kms_set_max_bpc (MetaOutputKms *output_kms, MetaKmsUpdate *kms_update); diff --git a/src/backends/native/meta-renderer-native.c b/src/backends/native/meta-renderer-native.c index 4da361e..148c18f 100644 --- a/src/backends/native/meta-renderer-native.c +++ b/src/backends/native/meta-renderer-native.c @@ -1421,6 +1421,7 @@ meta_renderer_native_create_view (MetaRenderer *renderer, "stage", meta_backend_get_stage (backend), "layout", &view_layout, "crtc", crtc, + "output", output, "scale", scale, "framebuffer", framebuffer, "offscreen", offscreen, @@ -1536,6 +1537,21 @@ meta_renderer_native_prepare_frame (MetaRendererNative *renderer_native, } } +void +meta_renderer_native_before_redraw (MetaRendererNative *renderer_native, + MetaRendererViewNative *view_native, + ClutterFrame *frame) +{ + CoglFramebuffer *framebuffer = + clutter_stage_view_get_onscreen (CLUTTER_STAGE_VIEW (view_native)); + + if (COGL_IS_ONSCREEN (framebuffer)) + { + meta_renderer_view_native_maybe_update_frame_sync_mode (view_native, + frame); + } +} + void meta_renderer_native_finish_frame (MetaRendererNative *renderer_native, MetaRendererView *view, diff --git a/src/backends/native/meta-renderer-native.h b/src/backends/native/meta-renderer-native.h index f348818..213e092 100644 --- a/src/backends/native/meta-renderer-native.h +++ b/src/backends/native/meta-renderer-native.h @@ -32,6 +32,7 @@ #include "backends/meta-renderer.h" #include "backends/native/meta-gpu-kms.h" #include "backends/native/meta-monitor-manager-native.h" +#include "backends/native/meta-renderer-view-native.h" #define META_TYPE_RENDERER_NATIVE (meta_renderer_native_get_type ()) META_EXPORT_TEST @@ -61,6 +62,10 @@ void meta_renderer_native_prepare_frame (MetaRendererNative *renderer_native, MetaRendererView *view, ClutterFrame *frame); +void meta_renderer_native_before_redraw (MetaRendererNative *renderer_native, + MetaRendererViewNative *view, + ClutterFrame *frame); + void meta_renderer_native_finish_frame (MetaRendererNative *renderer_native, MetaRendererView *view, ClutterFrame *frame); diff --git a/src/backends/native/meta-renderer-view-native.c b/src/backends/native/meta-renderer-view-native.c index 52872ff..bdfdb0a 100644 --- a/src/backends/native/meta-renderer-view-native.c +++ b/src/backends/native/meta-renderer-view-native.c @@ -24,11 +24,28 @@ #include "backends/native/meta-renderer-view-native.h" +#include "backends/meta-output.h" +#include "backends/native/meta-crtc-kms.h" #include "backends/native/meta-frame-native.h" +#include "backends/native/meta-kms.h" +#include "backends/native/meta-kms-device.h" +#include "backends/native/meta-output-kms.h" + +#include "clutter/clutter.h" + +typedef enum _MetaFrameSyncMode +{ + META_FRAME_SYNC_MODE_INIT, + META_FRAME_SYNC_MODE_ENABLED, + META_FRAME_SYNC_MODE_DISABLED, +} MetaFrameSyncMode; struct _MetaRendererViewNative { MetaRendererView parent; + + MetaFrameSyncMode requested_frame_sync_mode; + MetaFrameSyncMode frame_sync_mode; }; G_DEFINE_TYPE (MetaRendererViewNative, meta_renderer_view_native, @@ -40,6 +57,104 @@ meta_renderer_view_native_new_frame (ClutterStageView *stage_view) return (ClutterFrame *) meta_frame_native_new (); } +static void +update_frame_sync_mode (MetaRendererViewNative *view_native, + ClutterFrame *frame, + MetaOutput *output, + MetaFrameSyncMode sync_mode) +{ + MetaFrameNative *frame_native; + MetaCrtc *crtc; + MetaKmsCrtc *kms_crtc; + MetaKmsDevice *kms_device; + MetaKmsUpdate *kms_update; + ClutterFrameClock *frame_clock; + + frame_native = meta_frame_native_from_frame (frame); + + frame_clock = + clutter_stage_view_get_frame_clock (CLUTTER_STAGE_VIEW (view_native)); + + crtc = meta_output_get_assigned_crtc (output); + kms_crtc = meta_crtc_kms_get_kms_crtc (META_CRTC_KMS (crtc)); + kms_device = meta_kms_crtc_get_device (kms_crtc); + + kms_update = meta_frame_native_ensure_kms_update (frame_native, kms_device); + + switch (sync_mode) + { + case META_FRAME_SYNC_MODE_ENABLED: + clutter_frame_clock_set_mode (frame_clock, + CLUTTER_FRAME_CLOCK_MODE_VARIABLE); + meta_output_kms_set_vrr_mode (META_OUTPUT_KMS (output), + kms_update, + TRUE); + break; + case META_FRAME_SYNC_MODE_DISABLED: + clutter_frame_clock_set_mode (frame_clock, + CLUTTER_FRAME_CLOCK_MODE_FIXED); + meta_output_kms_set_vrr_mode (META_OUTPUT_KMS (output), + kms_update, + FALSE); + break; + case META_FRAME_SYNC_MODE_INIT: + g_assert_not_reached (); + } + + view_native->frame_sync_mode = sync_mode; +} + +static MetaFrameSyncMode +get_applicable_sync_mode (MetaRendererViewNative *view_native, + MetaOutput *output) +{ + if (!meta_output_is_vrr_allowed (output)) + return META_FRAME_SYNC_MODE_DISABLED; + + return view_native->requested_frame_sync_mode; +} + +void +meta_renderer_view_native_maybe_update_frame_sync_mode (MetaRendererViewNative *view_native, + ClutterFrame *frame) +{ + MetaRendererView *view = META_RENDERER_VIEW (view_native); + MetaOutput *output; + MetaFrameSyncMode applicable_sync_mode; + + output = meta_renderer_view_get_output (view); + + if (!meta_output_is_vrr_capable (output)) + return; + + applicable_sync_mode = + get_applicable_sync_mode (view_native, output); + + if (G_LIKELY (applicable_sync_mode == view_native->frame_sync_mode)) + return; + + update_frame_sync_mode (view_native, + frame, + output, + applicable_sync_mode); +} + +void +meta_renderer_view_native_request_frame_sync (MetaRendererViewNative *view_native, + gboolean enabled) +{ + view_native->requested_frame_sync_mode = + enabled + ? META_FRAME_SYNC_MODE_ENABLED + : META_FRAME_SYNC_MODE_DISABLED; +} + +gboolean +meta_renderer_view_native_is_frame_sync_enabled (MetaRendererViewNative *view_native) +{ + return view_native->frame_sync_mode == META_FRAME_SYNC_MODE_ENABLED; +} + static void meta_renderer_view_native_class_init (MetaRendererViewNativeClass *klass) { @@ -51,4 +166,6 @@ meta_renderer_view_native_class_init (MetaRendererViewNativeClass *klass) static void meta_renderer_view_native_init (MetaRendererViewNative *view_native) { + view_native->requested_frame_sync_mode = META_FRAME_SYNC_MODE_DISABLED; + view_native->frame_sync_mode = META_FRAME_SYNC_MODE_INIT; } diff --git a/src/backends/native/meta-renderer-view-native.h b/src/backends/native/meta-renderer-view-native.h index 34c3793..2e112b2 100644 --- a/src/backends/native/meta-renderer-view-native.h +++ b/src/backends/native/meta-renderer-view-native.h @@ -31,4 +31,12 @@ G_DECLARE_FINAL_TYPE (MetaRendererViewNative, meta_renderer_view_native, META, RENDERER_VIEW_NATIVE, MetaRendererView) +void meta_renderer_view_native_maybe_update_frame_sync_mode (MetaRendererViewNative *view_native, + ClutterFrame *frame); + +void meta_renderer_view_native_request_frame_sync (MetaRendererViewNative *view_native, + gboolean enabled); + +gboolean meta_renderer_view_native_is_frame_sync_enabled (MetaRendererViewNative *view_native); + #endif /* META_RENDERER_VIEW_NATIVE_H */ diff --git a/src/backends/native/meta-stage-native.c b/src/backends/native/meta-stage-native.c index 8cdf6cb..1777587 100644 --- a/src/backends/native/meta-stage-native.c +++ b/src/backends/native/meta-stage-native.c @@ -139,8 +139,15 @@ meta_stage_native_redraw_view (ClutterStageWindow *stage_window, ClutterStageView *view, ClutterFrame *frame) { + MetaStageImpl *stage_impl = META_STAGE_IMPL (stage_window); + MetaBackend *backend = meta_stage_impl_get_backend (stage_impl); + MetaRenderer *renderer = meta_backend_get_renderer (backend); MetaCrtc *crtc; + meta_renderer_native_before_redraw (META_RENDERER_NATIVE (renderer), + META_RENDERER_VIEW_NATIVE (view), + frame); + clutter_stage_window_parent_iface->redraw_view (stage_window, view, frame); crtc = meta_renderer_view_get_crtc (META_RENDERER_VIEW (view)); diff --git a/src/backends/native/meta-udev.c b/src/backends/native/meta-udev.c index c9b0fc7..a96b014 100644 --- a/src/backends/native/meta-udev.c +++ b/src/backends/native/meta-udev.c @@ -109,6 +109,13 @@ meta_is_udev_device_disable_client_modifiers (GUdevDevice *device) "mutter-device-disable-client-modifiers"); } +gboolean +meta_is_udev_device_disable_vrr (GUdevDevice *device) +{ + return meta_has_udev_device_tag (device, + "mutter-device-disable-vrr"); +} + gboolean meta_is_udev_device_ignore (GUdevDevice *device) { diff --git a/src/backends/native/meta-udev.c.orig b/src/backends/native/meta-udev.c.orig new file mode 100644 index 0000000..c9b0fc7 --- /dev/null +++ b/src/backends/native/meta-udev.c.orig @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2018 Red Hat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#include "config.h" + +#include "backends/native/meta-udev.h" + +#include "backends/native/meta-backend-native.h" +#include "backends/native/meta-launcher.h" + +#define DRM_CARD_UDEV_DEVICE_TYPE "drm_minor" + +enum +{ + HOTPLUG, + DEVICE_ADDED, + DEVICE_REMOVED, + + N_SIGNALS +}; + +static guint signals[N_SIGNALS]; + +struct _MetaUdev +{ + GObject parent; + + MetaBackendNative *backend_native; + + GUdevClient *gudev_client; + + gulong uevent_handler_id; +}; + +G_DEFINE_TYPE (MetaUdev, meta_udev, G_TYPE_OBJECT) + +gboolean +meta_is_udev_device_platform_device (GUdevDevice *device) +{ + g_autoptr (GUdevDevice) platform_device = NULL; + + platform_device = g_udev_device_get_parent_with_subsystem (device, + "platform", + NULL); + return !!platform_device; +} + +gboolean +meta_is_udev_device_boot_vga (GUdevDevice *device) +{ + g_autoptr (GUdevDevice) pci_device = NULL; + + pci_device = g_udev_device_get_parent_with_subsystem (device, "pci", NULL); + if (!pci_device) + return FALSE; + + return g_udev_device_get_sysfs_attr_as_int (pci_device, "boot_vga") == 1; +} + +static gboolean +meta_has_udev_device_tag (GUdevDevice *device, + const char *tag) +{ + const char * const * tags; + g_autoptr (GUdevDevice) platform_device = NULL; + + tags = g_udev_device_get_tags (device); + if (tags && g_strv_contains (tags, tag)) + return TRUE; + + platform_device = g_udev_device_get_parent_with_subsystem (device, + "platform", + NULL); + + if (platform_device) + return meta_has_udev_device_tag (platform_device, tag); + else + return FALSE; +} + +gboolean +meta_is_udev_device_disable_modifiers (GUdevDevice *device) +{ + return meta_has_udev_device_tag (device, + "mutter-device-disable-kms-modifiers"); +} + +gboolean +meta_is_udev_device_disable_client_modifiers (GUdevDevice *device) +{ + return meta_has_udev_device_tag (device, + "mutter-device-disable-client-modifiers"); +} + +gboolean +meta_is_udev_device_ignore (GUdevDevice *device) +{ + return meta_has_udev_device_tag (device, "mutter-device-ignore"); +} + +gboolean +meta_is_udev_test_device (GUdevDevice *device) +{ + return g_strcmp0 (g_udev_device_get_property (device, "ID_PATH"), + "platform-vkms") == 0; +} + +gboolean +meta_is_udev_device_preferred_primary (GUdevDevice *device) +{ + const char * const * tags; + + tags = g_udev_device_get_tags (device); + if (!tags) + return FALSE; + + return g_strv_contains (tags, "mutter-device-preferred-primary"); +} + +gboolean +meta_udev_is_drm_device (MetaUdev *udev, + GUdevDevice *device) +{ + const char *seat_id; + const char *device_type; + const char *device_seat; + + /* Filter out devices that are not character device, like card0-VGA-1. */ + if (g_udev_device_get_device_type (device) != G_UDEV_DEVICE_TYPE_CHAR) + return FALSE; + + device_type = g_udev_device_get_property (device, "DEVTYPE"); + if (g_strcmp0 (device_type, DRM_CARD_UDEV_DEVICE_TYPE) != 0) + return FALSE; + + device_seat = g_udev_device_get_property (device, "ID_SEAT"); + if (!device_seat) + { + /* When ID_SEAT is not set, it means seat0. */ + device_seat = "seat0"; + } + + /* Skip devices that do not belong to our seat. */ + seat_id = meta_backend_native_get_seat_id (udev->backend_native); + if (g_strcmp0 (seat_id, device_seat)) + return FALSE; + + return TRUE; +} + +GList * +meta_udev_list_drm_devices (MetaUdev *udev, + GError **error) +{ + g_autoptr (GUdevEnumerator) enumerator = NULL; + GList *devices; + GList *l; + + enumerator = g_udev_enumerator_new (udev->gudev_client); + + g_udev_enumerator_add_match_name (enumerator, "card*"); + g_udev_enumerator_add_match_tag (enumerator, "seat"); + + /* + * We need to explicitly match the subsystem for now. + * https://bugzilla.gnome.org/show_bug.cgi?id=773224 + */ + g_udev_enumerator_add_match_subsystem (enumerator, "drm"); + + devices = g_udev_enumerator_execute (enumerator); + if (!devices) + return NULL; + + for (l = devices; l;) + { + GUdevDevice *device = l->data; + GList *l_next = l->next; + + if (!meta_udev_is_drm_device (udev, device)) + { + g_object_unref (device); + devices = g_list_delete_link (devices, l); + } + + l = l_next; + } + + return devices; +} + +static void +on_uevent (GUdevClient *client, + const char *action, + GUdevDevice *device, + gpointer user_data) +{ + MetaUdev *udev = META_UDEV (user_data); + + if (!g_udev_device_get_device_file (device)) + return; + + if (g_str_equal (action, "add")) + g_signal_emit (udev, signals[DEVICE_ADDED], 0, device); + else if (g_str_equal (action, "remove")) + g_signal_emit (udev, signals[DEVICE_REMOVED], 0, device); + + if (g_udev_device_get_property_as_boolean (device, "HOTPLUG")) + g_signal_emit (udev, signals[HOTPLUG], 0, device); +} + +MetaUdev * +meta_udev_new (MetaBackendNative *backend_native) +{ + MetaUdev *udev; + + udev = g_object_new (META_TYPE_UDEV, NULL); + udev->backend_native = backend_native; + + return udev; +} + +void +meta_udev_pause (MetaUdev *udev) +{ + g_signal_handler_block (udev->gudev_client, udev->uevent_handler_id); +} + +void +meta_udev_resume (MetaUdev *udev) +{ + g_signal_handler_unblock (udev->gudev_client, udev->uevent_handler_id); +} + +static void +meta_udev_finalize (GObject *object) +{ + MetaUdev *udev = META_UDEV (object); + + g_clear_signal_handler (&udev->uevent_handler_id, udev->gudev_client); + g_clear_object (&udev->gudev_client); + + G_OBJECT_CLASS (meta_udev_parent_class)->finalize (object); +} + +static void +meta_udev_init (MetaUdev *udev) +{ + const char *subsystems[] = { "drm", NULL }; + + udev->gudev_client = g_udev_client_new (subsystems); + udev->uevent_handler_id = g_signal_connect (udev->gudev_client, + "uevent", + G_CALLBACK (on_uevent), udev); +} + +static void +meta_udev_class_init (MetaUdevClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = meta_udev_finalize; + + signals[HOTPLUG] = + g_signal_new ("hotplug", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 1, + G_UDEV_TYPE_DEVICE); + signals[DEVICE_ADDED] = + g_signal_new ("device-added", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 1, + G_UDEV_TYPE_DEVICE); + signals[DEVICE_REMOVED] = + g_signal_new ("device-removed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 1, + G_UDEV_TYPE_DEVICE); +} diff --git a/src/backends/native/meta-udev.h b/src/backends/native/meta-udev.h index 49e06f1..5f70057 100644 --- a/src/backends/native/meta-udev.h +++ b/src/backends/native/meta-udev.h @@ -37,6 +37,8 @@ gboolean meta_is_udev_device_disable_modifiers (GUdevDevice *device); gboolean meta_is_udev_device_disable_client_modifiers (GUdevDevice *device); +gboolean meta_is_udev_device_disable_vrr (GUdevDevice *device); + gboolean meta_is_udev_device_ignore (GUdevDevice *device); gboolean meta_is_udev_test_device (GUdevDevice *device); diff --git a/src/backends/native/meta-udev.h.orig b/src/backends/native/meta-udev.h.orig new file mode 100644 index 0000000..49e06f1 --- /dev/null +++ b/src/backends/native/meta-udev.h.orig @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2018 Red Hat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifndef META_UDEV_H +#define META_UDEV_H + +#include + +#include "backends/native/meta-backend-native-types.h" +#include "core/util-private.h" + +#define META_TYPE_UDEV (meta_udev_get_type ()) +G_DECLARE_FINAL_TYPE (MetaUdev, meta_udev, META, UDEV, GObject) + +gboolean meta_is_udev_device_platform_device (GUdevDevice *device); + +gboolean meta_is_udev_device_boot_vga (GUdevDevice *device); + +gboolean meta_is_udev_device_disable_modifiers (GUdevDevice *device); + +gboolean meta_is_udev_device_disable_client_modifiers (GUdevDevice *device); + +gboolean meta_is_udev_device_ignore (GUdevDevice *device); + +gboolean meta_is_udev_test_device (GUdevDevice *device); + +gboolean meta_is_udev_device_preferred_primary (GUdevDevice *device); + +gboolean meta_udev_is_drm_device (MetaUdev *udev, + GUdevDevice *device); + +META_EXPORT_TEST +GList * meta_udev_list_drm_devices (MetaUdev *udev, + GError **error); + +void meta_udev_pause (MetaUdev *udev); + +void meta_udev_resume (MetaUdev *udev); + +MetaUdev * meta_udev_new (MetaBackendNative *backend_native); + +#endif /* META_UDEV_H */ diff --git a/src/backends/x11/nested/meta-renderer-x11-nested.c b/src/backends/x11/nested/meta-renderer-x11-nested.c index 8f3745d..a1fc934 100644 --- a/src/backends/x11/nested/meta-renderer-x11-nested.c +++ b/src/backends/x11/nested/meta-renderer-x11-nested.c @@ -223,6 +223,7 @@ meta_renderer_x11_nested_create_view (MetaRenderer *renderer, "stage", meta_backend_get_stage (backend), "layout", &view_layout, "crtc", crtc, + "output", output, "refresh-rate", mode_info->refresh_rate, "framebuffer", COGL_FRAMEBUFFER (fake_onscreen), "offscreen", COGL_FRAMEBUFFER (offscreen), diff --git a/src/compositor/meta-compositor-native.c b/src/compositor/meta-compositor-native.c index e2e5124..2e79fce 100644 --- a/src/compositor/meta-compositor-native.c +++ b/src/compositor/meta-compositor-native.c @@ -45,6 +45,9 @@ meta_compositor_native_before_paint (MetaCompositor *compositor, compositor); #endif + meta_compositor_view_native_maybe_update_frame_sync_surface (compositor_view_native, + compositor); + parent_class = META_COMPOSITOR_CLASS (meta_compositor_native_parent_class); parent_class->before_paint (compositor, compositor_view); } diff --git a/src/compositor/meta-compositor-view-native.c b/src/compositor/meta-compositor-view-native.c index e9f1ac2..1e88ff7 100644 --- a/src/compositor/meta-compositor-view-native.c +++ b/src/compositor/meta-compositor-view-native.c @@ -28,14 +28,20 @@ #include "backends/meta-crtc.h" #include "backends/native/meta-crtc-kms.h" +#include "backends/native/meta-renderer-view-native.h" +#include "clutter/clutter.h" #include "compositor/compositor-private.h" #include "compositor/meta-window-actor-private.h" +#include "core/window-private.h" #ifdef HAVE_WAYLAND #include "compositor/meta-surface-actor-wayland.h" #include "wayland/meta-wayland-surface.h" #endif /* HAVE_WAYLAND */ +static void update_frame_sync_surface (MetaCompositorViewNative *view_native, + MetaSurfaceActor *surface_actor); + struct _MetaCompositorViewNative { MetaCompositorView parent; @@ -43,11 +49,52 @@ struct _MetaCompositorViewNative #ifdef HAVE_WAYLAND MetaWaylandSurface *scanout_candidate; #endif /* HAVE_WAYLAND */ + + MetaSurfaceActor *frame_sync_surface; + + gulong frame_sync_surface_repaint_scheduled_id; + gulong frame_sync_surface_frozen_id; + gulong frame_sync_surface_destroy_id; }; G_DEFINE_TYPE (MetaCompositorViewNative, meta_compositor_view_native, META_TYPE_COMPOSITOR_VIEW) +static void +on_frame_sync_surface_repaint_scheduled (MetaSurfaceActor *surface_actor, + MetaCompositorViewNative *view_native) +{ + MetaCompositorView *compositor_view = META_COMPOSITOR_VIEW (view_native); + ClutterStageView *stage_view; + MetaRendererViewNative *renderer_view_native; + + stage_view = meta_compositor_view_get_stage_view (compositor_view); + renderer_view_native = META_RENDERER_VIEW_NATIVE (stage_view); + + if (meta_renderer_view_native_is_frame_sync_enabled (renderer_view_native)) + { + ClutterFrameClock *frame_clock; + + frame_clock = clutter_stage_view_get_frame_clock (stage_view); + + clutter_frame_clock_schedule_update_now (frame_clock); + } +} + +static void +on_frame_sync_surface_frozen (MetaSurfaceActor *surface_actor, + MetaCompositorViewNative *view_native) +{ + update_frame_sync_surface (view_native, NULL); +} + +static void +on_frame_sync_surface_destroyed (MetaSurfaceActor *surface_actor, + MetaCompositorViewNative *view_native) +{ + update_frame_sync_surface (view_native, NULL); +} + #ifdef HAVE_WAYLAND static void update_scanout_candidate (MetaCompositorViewNative *view_native, @@ -271,6 +318,151 @@ meta_compositor_view_native_maybe_assign_scanout (MetaCompositorViewNative *view } #endif /* HAVE_WAYLAND */ +static MetaSurfaceActor * +find_frame_sync_candidate (MetaCompositorView *compositor_view, + MetaCompositor *compositor) +{ + MetaWindowActor *window_actor; + MetaWindow *window; + ClutterStageView *stage_view; + MetaRectangle view_layout; + MetaSurfaceActor *surface_actor; + + if (meta_compositor_is_unredirect_inhibited (compositor)) + { + meta_topic (META_DEBUG_RENDER, + "No frame sync candidate: unredirect inhibited"); + return NULL; + } + + window_actor = + meta_compositor_view_get_top_window_actor (compositor_view); + if (!window_actor) + { + meta_topic (META_DEBUG_RENDER, + "No frame sync candidate: no top window actor"); + return NULL; + } + + if (meta_window_actor_is_frozen (window_actor)) + { + meta_topic (META_DEBUG_RENDER, + "No frame sync candidate: window-actor is frozen"); + return NULL; + } + + if (meta_window_actor_effect_in_progress (window_actor)) + { + meta_topic (META_DEBUG_RENDER, + "No frame sync candidate: window-actor effects in progress"); + return NULL; + } + + if (clutter_actor_has_transitions (CLUTTER_ACTOR (window_actor))) + { + meta_topic (META_DEBUG_RENDER, + "No frame sync candidate: window-actor has transition"); + return NULL; + } + + window = meta_window_actor_get_meta_window (window_actor); + if (!window) + { + meta_topic (META_DEBUG_RENDER, + "No frame sync candidate: no meta-window"); + return NULL; + } + + stage_view = meta_compositor_view_get_stage_view (compositor_view); + + clutter_stage_view_get_layout (stage_view, &view_layout); + + if (!meta_window_frame_contains_rect (window, &view_layout)) + { + meta_topic (META_DEBUG_RENDER, + "No frame sync candidate: stage-view layout not covered " + "by meta-window frame"); + return NULL; + } + + surface_actor = meta_window_actor_get_scanout_candidate (window_actor); + if (!surface_actor) + { + meta_topic (META_DEBUG_RENDER, + "No frame sync candidate: window-actor has no scanout candidate"); + return NULL; + } + + if (!meta_surface_actor_contains_rect (surface_actor, + &view_layout)) + { + meta_topic (META_DEBUG_RENDER, + "No frame sync candidate: stage-view layout not covered " + "by surface-actor"); + return NULL; + } + + return surface_actor; +} + +static void +update_frame_sync_surface (MetaCompositorViewNative *view_native, + MetaSurfaceActor *surface_actor) +{ + MetaCompositorView *compositor_view = + META_COMPOSITOR_VIEW (view_native); + ClutterStageView *stage_view; + MetaRendererViewNative *renderer_view_native; + + g_clear_signal_handler (&view_native->frame_sync_surface_repaint_scheduled_id, + view_native->frame_sync_surface); + g_clear_signal_handler (&view_native->frame_sync_surface_frozen_id, + view_native->frame_sync_surface); + g_clear_signal_handler (&view_native->frame_sync_surface_destroy_id, + view_native->frame_sync_surface); + + if (surface_actor) + { + view_native->frame_sync_surface_repaint_scheduled_id = + g_signal_connect (surface_actor, "repaint-scheduled", + G_CALLBACK (on_frame_sync_surface_repaint_scheduled), + view_native); + view_native->frame_sync_surface_frozen_id = + g_signal_connect (surface_actor, "frozen", + G_CALLBACK (on_frame_sync_surface_frozen), + view_native); + view_native->frame_sync_surface_destroy_id = + g_signal_connect (surface_actor, "destroy", + G_CALLBACK (on_frame_sync_surface_destroyed), + view_native); + } + + view_native->frame_sync_surface = surface_actor; + + stage_view = meta_compositor_view_get_stage_view (compositor_view); + renderer_view_native = META_RENDERER_VIEW_NATIVE (stage_view); + + meta_renderer_view_native_request_frame_sync (renderer_view_native, + surface_actor != NULL); +} + +void +meta_compositor_view_native_maybe_update_frame_sync_surface (MetaCompositorViewNative *view_native, + MetaCompositor *compositor) +{ + MetaCompositorView *compositor_view = META_COMPOSITOR_VIEW (view_native); + MetaSurfaceActor *surface_actor; + + surface_actor = find_frame_sync_candidate (compositor_view, + compositor); + + if (G_LIKELY (surface_actor == view_native->frame_sync_surface)) + return; + + update_frame_sync_surface (view_native, + surface_actor); +} + MetaCompositorViewNative * meta_compositor_view_native_new (ClutterStageView *stage_view) { @@ -281,6 +473,25 @@ meta_compositor_view_native_new (ClutterStageView *stage_view) NULL); } +static void +meta_compositor_view_native_dispose (GObject *object) +{ + MetaCompositorViewNative *view_native = META_COMPOSITOR_VIEW_NATIVE (object); + + if (view_native->frame_sync_surface) + { + g_clear_signal_handler (&view_native->frame_sync_surface_repaint_scheduled_id, + view_native->frame_sync_surface); + g_clear_signal_handler (&view_native->frame_sync_surface_destroy_id, + view_native->frame_sync_surface); + g_clear_signal_handler (&view_native->frame_sync_surface_frozen_id, + view_native->frame_sync_surface); + view_native->frame_sync_surface = NULL; + } + + G_OBJECT_CLASS (meta_compositor_view_native_parent_class)->dispose (object); +} + static void meta_compositor_view_native_finalize (GObject *object) { @@ -298,6 +509,7 @@ meta_compositor_view_native_class_init (MetaCompositorViewNativeClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); + object_class->dispose = meta_compositor_view_native_dispose; object_class->finalize = meta_compositor_view_native_finalize; } diff --git a/src/compositor/meta-compositor-view-native.h b/src/compositor/meta-compositor-view-native.h index af55185..a4657f3 100644 --- a/src/compositor/meta-compositor-view-native.h +++ b/src/compositor/meta-compositor-view-native.h @@ -40,4 +40,7 @@ void meta_compositor_view_native_maybe_assign_scanout (MetaCompositorViewNative MetaCompositor *compositor); #endif /* HAVE_WAYLAND */ +void meta_compositor_view_native_maybe_update_frame_sync_surface (MetaCompositorViewNative *view_native, + MetaCompositor *compositor); + #endif /* META_COMPOSITOR_VIEW_NATIVE_H */ diff --git a/src/compositor/meta-surface-actor.c b/src/compositor/meta-surface-actor.c index fcd94b8..7ecef88 100644 --- a/src/compositor/meta-surface-actor.c +++ b/src/compositor/meta-surface-actor.c @@ -51,6 +51,7 @@ enum { REPAINT_SCHEDULED, SIZE_CHANGED, + FROZEN, LAST_SIGNAL, }; @@ -269,6 +270,13 @@ meta_surface_actor_class_init (MetaSurfaceActorClass *klass) 0, NULL, NULL, NULL, G_TYPE_NONE, 0); + + signals[FROZEN] = g_signal_new ("frozen", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 0); } gboolean @@ -513,6 +521,22 @@ meta_surface_actor_is_obscured_on_stage_view (MetaSurfaceActor *self, stage_view); } +gboolean +meta_surface_actor_contains_rect (MetaSurfaceActor *surface_actor, + MetaRectangle *rect) +{ + ClutterActor *actor = CLUTTER_ACTOR (surface_actor); + graphene_rect_t bounding_rect; + graphene_rect_t bound_rect; + + clutter_actor_get_transformed_extents (actor, &bounding_rect); + + _clutter_util_rect_from_rectangle (rect, &bound_rect); + + return graphene_rect_contains_rect (&bounding_rect, + &bound_rect); +} + void meta_surface_actor_set_input_region (MetaSurfaceActor *self, cairo_region_t *region) @@ -594,6 +618,9 @@ meta_surface_actor_set_frozen (MetaSurfaceActor *self, priv->frozen = frozen; + if (frozen) + g_signal_emit (self, signals[FROZEN], 0); + if (!frozen && priv->pending_damage) { int i, n_rects = cairo_region_num_rectangles (priv->pending_damage); diff --git a/src/compositor/meta-surface-actor.c.orig b/src/compositor/meta-surface-actor.c.orig new file mode 100644 index 0000000..fcd94b8 --- /dev/null +++ b/src/compositor/meta-surface-actor.c.orig @@ -0,0 +1,622 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/** + * SECTION:meta-surface-actor + * @title: MetaSurfaceActor + * @short_description: An actor representing a surface in the scene graph + * + * MetaSurfaceActor is an abstract class which represents a surface in the + * Clutter scene graph. A subclass can implement the specifics of a surface + * depending on the way it is handled by a display protocol. + * + * An important feature of #MetaSurfaceActor is that it allows you to set an + * "input region": all events that occur in the surface, but outside of the + * input region are to be explicitly ignored. By default, this region is to + * %NULL, which means events on the whole surface is allowed. + */ + +#include "config.h" + +#include "compositor/meta-surface-actor.h" + +#include "clutter/clutter.h" +#include "compositor/clutter-utils.h" +#include "compositor/meta-cullable.h" +#include "compositor/meta-shaped-texture-private.h" +#include "compositor/meta-window-actor-private.h" +#include "compositor/region-utils.h" +#include "meta/meta-shaped-texture.h" + +typedef struct _MetaSurfaceActorPrivate +{ + MetaShapedTexture *texture; + + cairo_region_t *input_region; + + /* MetaCullable regions, see that documentation for more details */ + cairo_region_t *unobscured_region; + + /* Freeze/thaw accounting */ + cairo_region_t *pending_damage; + guint frozen : 1; +} MetaSurfaceActorPrivate; + +static void cullable_iface_init (MetaCullableInterface *iface); + +G_DEFINE_ABSTRACT_TYPE_WITH_CODE (MetaSurfaceActor, meta_surface_actor, CLUTTER_TYPE_ACTOR, + G_ADD_PRIVATE (MetaSurfaceActor) + G_IMPLEMENT_INTERFACE (META_TYPE_CULLABLE, cullable_iface_init)); + +enum +{ + REPAINT_SCHEDULED, + SIZE_CHANGED, + + LAST_SIGNAL, +}; + +static guint signals[LAST_SIGNAL]; + +typedef enum +{ + IN_STAGE_PERSPECTIVE, + IN_ACTOR_PERSPECTIVE +} ScalePerspectiveType; + +static cairo_region_t * +effective_unobscured_region (MetaSurfaceActor *surface_actor) +{ + MetaSurfaceActorPrivate *priv = + meta_surface_actor_get_instance_private (surface_actor); + ClutterActor *actor = CLUTTER_ACTOR (surface_actor); + + /* Fail if we have any mapped clones. */ + if (clutter_actor_has_mapped_clones (actor)) + return NULL; + + return priv->unobscured_region; +} + +static cairo_region_t* +get_scaled_region (MetaSurfaceActor *surface_actor, + cairo_region_t *region, + ScalePerspectiveType scale_perspective) +{ + MetaWindowActor *window_actor; + cairo_region_t *scaled_region = NULL; + int geometry_scale; + float x, y; + + window_actor = meta_window_actor_from_actor (CLUTTER_ACTOR (surface_actor)); + geometry_scale = meta_window_actor_get_geometry_scale (window_actor); + + clutter_actor_get_position (CLUTTER_ACTOR (surface_actor), &x, &y); + cairo_region_translate (region, x, y); + + switch (scale_perspective) + { + case IN_STAGE_PERSPECTIVE: + scaled_region = meta_region_scale_double (region, + geometry_scale, + META_ROUNDING_STRATEGY_GROW); + break; + case IN_ACTOR_PERSPECTIVE: + scaled_region = meta_region_scale_double (region, + 1.0 / geometry_scale, + META_ROUNDING_STRATEGY_GROW); + break; + } + + g_assert (scaled_region != NULL); + cairo_region_translate (region, -x, -y); + cairo_region_translate (scaled_region, -x, -y); + + return scaled_region; +} + +static void +set_unobscured_region (MetaSurfaceActor *surface_actor, + cairo_region_t *unobscured_region) +{ + MetaSurfaceActorPrivate *priv = + meta_surface_actor_get_instance_private (surface_actor); + + g_clear_pointer (&priv->unobscured_region, cairo_region_destroy); + if (unobscured_region) + { + if (cairo_region_is_empty (unobscured_region)) + { + priv->unobscured_region = cairo_region_reference (unobscured_region); + } + else + { + cairo_rectangle_int_t bounds = { 0, }; + float width, height; + + clutter_content_get_preferred_size (CLUTTER_CONTENT (priv->texture), + &width, + &height); + bounds = (cairo_rectangle_int_t) { + .width = width, + .height = height, + }; + + priv->unobscured_region = get_scaled_region (surface_actor, + unobscured_region, + IN_ACTOR_PERSPECTIVE); + + cairo_region_intersect_rectangle (priv->unobscured_region, &bounds); + } + } +} + +static void +set_clip_region (MetaSurfaceActor *surface_actor, + cairo_region_t *clip_region) +{ + MetaSurfaceActorPrivate *priv = + meta_surface_actor_get_instance_private (surface_actor); + MetaShapedTexture *stex = priv->texture; + + if (clip_region && !cairo_region_is_empty (clip_region)) + { + cairo_region_t *region; + + region = get_scaled_region (surface_actor, + clip_region, + IN_ACTOR_PERSPECTIVE); + meta_shaped_texture_set_clip_region (stex, region); + + cairo_region_destroy (region); + } + else + { + meta_shaped_texture_set_clip_region (stex, clip_region); + } +} + +static void +meta_surface_actor_pick (ClutterActor *actor, + ClutterPickContext *pick_context) +{ + MetaSurfaceActor *self = META_SURFACE_ACTOR (actor); + MetaSurfaceActorPrivate *priv = + meta_surface_actor_get_instance_private (self); + ClutterActorIter iter; + ClutterActor *child; + + if (!clutter_actor_should_pick (actor, pick_context)) + return; + + /* If there is no region then use the regular pick */ + if (priv->input_region == NULL) + { + ClutterActorClass *actor_class = + CLUTTER_ACTOR_CLASS (meta_surface_actor_parent_class); + + actor_class->pick (actor, pick_context); + } + else + { + int n_rects; + int i; + + n_rects = cairo_region_num_rectangles (priv->input_region); + + for (i = 0; i < n_rects; i++) + { + cairo_rectangle_int_t rect; + ClutterActorBox box; + + cairo_region_get_rectangle (priv->input_region, i, &rect); + + box.x1 = rect.x; + box.y1 = rect.y; + box.x2 = rect.x + rect.width; + box.y2 = rect.y + rect.height; + clutter_actor_pick_box (actor, pick_context, &box); + } + } + + clutter_actor_iter_init (&iter, actor); + + while (clutter_actor_iter_next (&iter, &child)) + clutter_actor_pick (child, pick_context); +} + +static gboolean +meta_surface_actor_get_paint_volume (ClutterActor *actor, + ClutterPaintVolume *volume) +{ + return clutter_paint_volume_set_from_allocation (volume, actor); +} + +static void +meta_surface_actor_dispose (GObject *object) +{ + MetaSurfaceActor *self = META_SURFACE_ACTOR (object); + MetaSurfaceActorPrivate *priv = + meta_surface_actor_get_instance_private (self); + + g_clear_pointer (&priv->input_region, cairo_region_destroy); + g_clear_object (&priv->texture); + + set_unobscured_region (self, NULL); + + G_OBJECT_CLASS (meta_surface_actor_parent_class)->dispose (object); +} + +static void +meta_surface_actor_class_init (MetaSurfaceActorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); + + object_class->dispose = meta_surface_actor_dispose; + actor_class->pick = meta_surface_actor_pick; + actor_class->get_paint_volume = meta_surface_actor_get_paint_volume; + + signals[REPAINT_SCHEDULED] = g_signal_new ("repaint-scheduled", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 0); + + signals[SIZE_CHANGED] = g_signal_new ("size-changed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 0); +} + +gboolean +meta_surface_actor_is_opaque (MetaSurfaceActor *self) +{ + return META_SURFACE_ACTOR_GET_CLASS (self)->is_opaque (self); +} + +static void +meta_surface_actor_cull_out (MetaCullable *cullable, + cairo_region_t *unobscured_region, + cairo_region_t *clip_region) +{ + MetaSurfaceActor *surface_actor = META_SURFACE_ACTOR (cullable); + MetaSurfaceActorPrivate *priv = + meta_surface_actor_get_instance_private (surface_actor); + uint8_t opacity = clutter_actor_get_opacity (CLUTTER_ACTOR (cullable)); + + set_unobscured_region (surface_actor, unobscured_region); + set_clip_region (surface_actor, clip_region); + + if (opacity == 0xff) + { + cairo_region_t *opaque_region; + cairo_region_t *scaled_opaque_region; + + opaque_region = meta_shaped_texture_get_opaque_region (priv->texture); + + if (!opaque_region) + return; + + scaled_opaque_region = get_scaled_region (surface_actor, + opaque_region, + IN_STAGE_PERSPECTIVE); + + if (unobscured_region) + cairo_region_subtract (unobscured_region, scaled_opaque_region); + if (clip_region) + cairo_region_subtract (clip_region, scaled_opaque_region); + + cairo_region_destroy (scaled_opaque_region); + } +} + +static gboolean +meta_surface_actor_is_untransformed (MetaCullable *cullable) +{ + ClutterActor *actor = CLUTTER_ACTOR (cullable); + MetaWindowActor *window_actor; + float width, height; + graphene_point3d_t verts[4]; + int geometry_scale; + + clutter_actor_get_size (actor, &width, &height); + clutter_actor_get_abs_allocation_vertices (actor, verts); + + window_actor = meta_window_actor_from_actor (actor); + geometry_scale = meta_window_actor_get_geometry_scale (window_actor); + + return meta_actor_vertices_are_untransformed (verts, + width * geometry_scale, + height * geometry_scale, + NULL); +} + +static void +meta_surface_actor_reset_culling (MetaCullable *cullable) +{ + MetaSurfaceActor *surface_actor = META_SURFACE_ACTOR (cullable); + + set_clip_region (surface_actor, NULL); +} + +static void +cullable_iface_init (MetaCullableInterface *iface) +{ + iface->cull_out = meta_surface_actor_cull_out; + iface->is_untransformed = meta_surface_actor_is_untransformed; + iface->reset_culling = meta_surface_actor_reset_culling; +} + +static void +texture_size_changed (MetaShapedTexture *texture, + gpointer user_data) +{ + MetaSurfaceActor *actor = META_SURFACE_ACTOR (user_data); + g_signal_emit (actor, signals[SIZE_CHANGED], 0); +} + +static void +meta_surface_actor_init (MetaSurfaceActor *self) +{ + MetaSurfaceActorPrivate *priv = + meta_surface_actor_get_instance_private (self); + + priv->texture = meta_shaped_texture_new (); + g_signal_connect_object (priv->texture, "size-changed", + G_CALLBACK (texture_size_changed), self, 0); + clutter_actor_set_content (CLUTTER_ACTOR (self), + CLUTTER_CONTENT (priv->texture)); + clutter_actor_set_request_mode (CLUTTER_ACTOR (self), + CLUTTER_REQUEST_CONTENT_SIZE); +} + +MetaShapedTexture * +meta_surface_actor_get_texture (MetaSurfaceActor *self) +{ + MetaSurfaceActorPrivate *priv = + meta_surface_actor_get_instance_private (self); + + return priv->texture; +} + +void +meta_surface_actor_update_area (MetaSurfaceActor *self, + int x, + int y, + int width, + int height) +{ + MetaSurfaceActorPrivate *priv = + meta_surface_actor_get_instance_private (self); + gboolean repaint_scheduled = FALSE; + cairo_rectangle_int_t clip; + + if (meta_shaped_texture_update_area (priv->texture, x, y, width, height, &clip)) + { + cairo_region_t *unobscured_region; + + unobscured_region = effective_unobscured_region (self); + + if (unobscured_region) + { + cairo_region_t *intersection; + + if (cairo_region_is_empty (unobscured_region)) + return; + + intersection = cairo_region_copy (unobscured_region); + cairo_region_intersect_rectangle (intersection, &clip); + + if (!cairo_region_is_empty (intersection)) + { + cairo_rectangle_int_t damage_rect; + + cairo_region_get_extents (intersection, &damage_rect); + clutter_actor_queue_redraw_with_clip (CLUTTER_ACTOR (self), &damage_rect); + repaint_scheduled = TRUE; + } + + cairo_region_destroy (intersection); + } + else + { + clutter_actor_queue_redraw_with_clip (CLUTTER_ACTOR (self), &clip); + repaint_scheduled = TRUE; + } + } + + if (repaint_scheduled) + g_signal_emit (self, signals[REPAINT_SCHEDULED], 0); +} + +gboolean +meta_surface_actor_is_obscured (MetaSurfaceActor *self) +{ + cairo_region_t *unobscured_region; + + unobscured_region = effective_unobscured_region (self); + + if (unobscured_region) + return cairo_region_is_empty (unobscured_region); + else + return FALSE; +} + +gboolean +meta_surface_actor_is_obscured_on_stage_view (MetaSurfaceActor *self, + ClutterStageView *stage_view, + float *unobscurred_fraction) +{ + cairo_region_t *unobscured_region; + + unobscured_region = effective_unobscured_region (self); + + if (unobscured_region) + { + MetaSurfaceActorPrivate *priv = + meta_surface_actor_get_instance_private (self); + cairo_region_t *intersection_region; + cairo_rectangle_int_t stage_rect; + float x, y; + float bounds_width, bounds_height; + float bounds_size; + int intersection_size = 0; + int n_rects, i; + + if (cairo_region_is_empty (unobscured_region)) + return TRUE; + + intersection_region = cairo_region_copy (unobscured_region); + clutter_actor_get_transformed_position (CLUTTER_ACTOR (self), &x, &y); + cairo_region_translate (intersection_region, x, y); + + clutter_stage_view_get_layout (stage_view, &stage_rect); + cairo_region_intersect_rectangle (intersection_region, + &stage_rect); + + if (cairo_region_is_empty (intersection_region)) + { + cairo_region_destroy (intersection_region); + return TRUE; + } + else if (!unobscurred_fraction) + { + cairo_region_destroy (intersection_region); + return FALSE; + } + + clutter_content_get_preferred_size (CLUTTER_CONTENT (priv->texture), + &bounds_width, + &bounds_height); + bounds_size = bounds_width * bounds_height; + + n_rects = cairo_region_num_rectangles (intersection_region); + for (i = 0; i < n_rects; i++) + { + cairo_rectangle_int_t rect; + + cairo_region_get_rectangle (intersection_region, i, &rect); + intersection_size += rect.width * rect.height; + } + cairo_region_destroy (intersection_region); + + g_return_val_if_fail (bounds_size > 0, FALSE); + + *unobscurred_fraction = CLAMP (intersection_size / bounds_size, 0, 1); + return FALSE; + } + + return !clutter_actor_is_effectively_on_stage_view (CLUTTER_ACTOR (self), + stage_view); +} + +void +meta_surface_actor_set_input_region (MetaSurfaceActor *self, + cairo_region_t *region) +{ + MetaSurfaceActorPrivate *priv = + meta_surface_actor_get_instance_private (self); + + if (priv->input_region) + cairo_region_destroy (priv->input_region); + + if (region) + priv->input_region = cairo_region_reference (region); + else + priv->input_region = NULL; +} + +void +meta_surface_actor_set_opaque_region (MetaSurfaceActor *self, + cairo_region_t *region) +{ + MetaSurfaceActorPrivate *priv = + meta_surface_actor_get_instance_private (self); + + meta_shaped_texture_set_opaque_region (priv->texture, region); +} + +cairo_region_t * +meta_surface_actor_get_opaque_region (MetaSurfaceActor *self) +{ + MetaSurfaceActorPrivate *priv = + meta_surface_actor_get_instance_private (self); + + return meta_shaped_texture_get_opaque_region (priv->texture); +} + +void +meta_surface_actor_process_damage (MetaSurfaceActor *self, + int x, int y, int width, int height) +{ + MetaSurfaceActorPrivate *priv = + meta_surface_actor_get_instance_private (self); + + if (meta_surface_actor_is_frozen (self)) + { + /* The window is frozen due to an effect in progress: we ignore damage + * here on the off chance that this will stop the corresponding + * texture_from_pixmap from being update. + * + * pending_damage tracks any damage that happened while the window was + * frozen so that when can apply it when the window becomes unfrozen. + * + * It should be noted that this is an unreliable mechanism since it's + * quite likely that drivers will aim to provide a zero-copy + * implementation of the texture_from_pixmap extension and in those cases + * any drawing done to the window is always immediately reflected in the + * texture regardless of damage event handling. + */ + cairo_rectangle_int_t rect = { .x = x, .y = y, .width = width, .height = height }; + + if (!priv->pending_damage) + priv->pending_damage = cairo_region_create_rectangle (&rect); + else + cairo_region_union_rectangle (priv->pending_damage, &rect); + return; + } + + META_SURFACE_ACTOR_GET_CLASS (self)->process_damage (self, x, y, width, height); +} + +void +meta_surface_actor_set_frozen (MetaSurfaceActor *self, + gboolean frozen) +{ + MetaSurfaceActorPrivate *priv = + meta_surface_actor_get_instance_private (self); + + if (priv->frozen == frozen) + return; + + priv->frozen = frozen; + + if (!frozen && priv->pending_damage) + { + int i, n_rects = cairo_region_num_rectangles (priv->pending_damage); + cairo_rectangle_int_t rect; + + /* Since we ignore damage events while a window is frozen for certain effects + * we need to apply the tracked damage now. */ + + for (i = 0; i < n_rects; i++) + { + cairo_region_get_rectangle (priv->pending_damage, i, &rect); + meta_surface_actor_process_damage (self, rect.x, rect.y, + rect.width, rect.height); + } + g_clear_pointer (&priv->pending_damage, cairo_region_destroy); + } +} + +gboolean +meta_surface_actor_is_frozen (MetaSurfaceActor *self) +{ + MetaSurfaceActorPrivate *priv = + meta_surface_actor_get_instance_private (self); + + return priv->frozen; +} diff --git a/src/compositor/meta-surface-actor.h b/src/compositor/meta-surface-actor.h index f69cb15..3128f51 100644 --- a/src/compositor/meta-surface-actor.h +++ b/src/compositor/meta-surface-actor.h @@ -40,6 +40,9 @@ gboolean meta_surface_actor_is_obscured_on_stage_view (MetaSurfaceActor *self, ClutterStageView *stage_view, float *unobscurred_fraction); +gboolean meta_surface_actor_contains_rect (MetaSurfaceActor *surface_actor, + MetaRectangle *rect); + void meta_surface_actor_set_input_region (MetaSurfaceActor *self, cairo_region_t *region); void meta_surface_actor_set_opaque_region (MetaSurfaceActor *self, diff --git a/src/core/window-private.h b/src/core/window-private.h index 1c12159..bb7fc1e 100644 --- a/src/core/window-private.h +++ b/src/core/window-private.h @@ -695,6 +695,9 @@ void meta_window_get_session_geometry (MetaWindow *window, int *width, int *height); +gboolean meta_window_frame_contains_rect (MetaWindow *window, + MetaRectangle *rect); + void meta_window_update_unfocused_button_grabs (MetaWindow *window); void meta_window_update_appears_focused (MetaWindow *window); diff --git a/src/core/window-private.h.orig b/src/core/window-private.h.orig new file mode 100644 index 0000000..1c12159 --- /dev/null +++ b/src/core/window-private.h.orig @@ -0,0 +1,894 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/** + * \file window-private.h Windows which Mutter manages + * + * Managing X windows. + * This file contains methods on this class which are available to + * routines in core but not outside it. (See window.h for the routines + * which the rest of the world is allowed to use.) + */ + +/* + * Copyright (C) 2001 Havoc Pennington + * Copyright (C) 2002 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 . + */ + +#ifndef META_WINDOW_PRIVATE_H +#define META_WINDOW_PRIVATE_H + +#include +#include +#include + +#include "backends/meta-logical-monitor.h" +#include "clutter/clutter.h" +#include "core/stack.h" +#include "meta/compositor.h" +#include "meta/meta-close-dialog.h" +#include "meta/util.h" +#include "meta/window.h" +#include "wayland/meta-wayland-types.h" +#include "x11/group-private.h" + +typedef struct _MetaWindowQueue MetaWindowQueue; + +typedef enum +{ + META_CLIENT_TYPE_UNKNOWN = 0, + META_CLIENT_TYPE_APPLICATION = 1, + META_CLIENT_TYPE_PAGER = 2, + META_CLIENT_TYPE_MAX_RECOGNIZED = 2 +} MetaClientType; + +#define META_N_QUEUE_TYPES 2 + +typedef enum +{ + META_MOVE_RESIZE_CONFIGURE_REQUEST = 1 << 0, + META_MOVE_RESIZE_USER_ACTION = 1 << 1, + META_MOVE_RESIZE_MOVE_ACTION = 1 << 2, + META_MOVE_RESIZE_RESIZE_ACTION = 1 << 3, + META_MOVE_RESIZE_WAYLAND_FINISH_MOVE_RESIZE = 1 << 4, + META_MOVE_RESIZE_STATE_CHANGED = 1 << 5, + META_MOVE_RESIZE_UNMAXIMIZE = 1 << 6, + META_MOVE_RESIZE_UNFULLSCREEN = 1 << 7, + META_MOVE_RESIZE_FORCE_MOVE = 1 << 8, + META_MOVE_RESIZE_WAYLAND_STATE_CHANGED = 1 << 9, + META_MOVE_RESIZE_FORCE_UPDATE_MONITOR = 1 << 10, + META_MOVE_RESIZE_PLACEMENT_CHANGED = 1 << 11, + META_MOVE_RESIZE_WAYLAND_CLIENT_RESIZE = 1 << 12, + META_MOVE_RESIZE_CONSTRAIN = 1 << 13, +} MetaMoveResizeFlags; + +typedef enum +{ + META_MOVE_RESIZE_RESULT_MOVED = 1 << 0, + META_MOVE_RESIZE_RESULT_RESIZED = 1 << 1, + META_MOVE_RESIZE_RESULT_FRAME_SHAPE_CHANGED = 1 << 2, + META_MOVE_RESIZE_RESULT_STATE_CHANGED = 1 << 3, +} MetaMoveResizeResultFlags; + +typedef enum +{ + META_PLACEMENT_GRAVITY_NONE = 0, + META_PLACEMENT_GRAVITY_TOP = 1 << 0, + META_PLACEMENT_GRAVITY_BOTTOM = 1 << 1, + META_PLACEMENT_GRAVITY_LEFT = 1 << 2, + META_PLACEMENT_GRAVITY_RIGHT = 1 << 3, +} MetaPlacementGravity; + +typedef enum +{ + META_PLACEMENT_ANCHOR_NONE = 0, + META_PLACEMENT_ANCHOR_TOP = 1 << 0, + META_PLACEMENT_ANCHOR_BOTTOM = 1 << 1, + META_PLACEMENT_ANCHOR_LEFT = 1 << 2, + META_PLACEMENT_ANCHOR_RIGHT = 1 << 3, +} MetaPlacementAnchor; + +typedef enum +{ + META_PLACEMENT_CONSTRAINT_ADJUSTMENT_NONE = 0, + META_PLACEMENT_CONSTRAINT_ADJUSTMENT_SLIDE_X = 1 << 0, + META_PLACEMENT_CONSTRAINT_ADJUSTMENT_SLIDE_Y = 1 << 1, + META_PLACEMENT_CONSTRAINT_ADJUSTMENT_FLIP_X = 1 << 2, + META_PLACEMENT_CONSTRAINT_ADJUSTMENT_FLIP_Y = 1 << 3, + META_PLACEMENT_CONSTRAINT_ADJUSTMENT_RESIZE_X = 1 << 4, + META_PLACEMENT_CONSTRAINT_ADJUSTMENT_RESIZE_Y = 1 << 5, +} MetaPlacementConstraintAdjustment; + +typedef enum _MetaWindowUpdateMonitorFlags +{ + META_WINDOW_UPDATE_MONITOR_FLAGS_NONE = 0, + META_WINDOW_UPDATE_MONITOR_FLAGS_USER_OP = 1 << 0, + META_WINDOW_UPDATE_MONITOR_FLAGS_FORCE = 1 << 1, +} MetaWindowUpdateMonitorFlags; + +typedef struct _MetaPlacementRule +{ + MetaRectangle anchor_rect; + MetaPlacementGravity gravity; + MetaPlacementAnchor anchor; + MetaPlacementConstraintAdjustment constraint_adjustment; + int offset_x; + int offset_y; + int width; + int height; + + gboolean is_reactive; + + MetaRectangle parent_rect; +} MetaPlacementRule; + +typedef enum _MetaPlacementState +{ + META_PLACEMENT_STATE_UNCONSTRAINED, + META_PLACEMENT_STATE_CONSTRAINED_PENDING, + META_PLACEMENT_STATE_CONSTRAINED_CONFIGURED, + META_PLACEMENT_STATE_CONSTRAINED_FINISHED, + META_PLACEMENT_STATE_INVALIDATED, +} MetaPlacementState; + +typedef enum +{ + META_EDGE_CONSTRAINT_NONE = 0, + META_EDGE_CONSTRAINT_WINDOW = 1, + META_EDGE_CONSTRAINT_MONITOR = 2, +} MetaEdgeConstraint; + +typedef enum +{ + META_EDGE_RESISTANCE_DEFAULT = 0, + META_EDGE_RESISTANCE_SNAP = 1 << 0, + META_EDGE_RESISTANCE_KEYBOARD_OP = 1 << 1, + META_EDGE_RESISTANCE_WINDOWS = 1 << 2, +} MetaEdgeResistanceFlags; + +struct _MetaWindow +{ + GObject parent_instance; + + MetaDisplay *display; + uint64_t id; + guint64 stamp; + MetaLogicalMonitor *monitor; + MetaWorkspace *workspace; + MetaWindowClientType client_type; + Window xwindow; + /* may be NULL! not all windows get decorated */ + MetaFrame *frame; + int depth; + Visual *xvisual; + char *desc; /* used in debug spew */ + char *title; + + MetaWindowType type; + + /* NOTE these five are not in UTF-8, we just treat them as random + * binary data + */ + char *res_class; + char *res_name; + char *role; + char *sm_client_id; + char *wm_client_machine; + + char *startup_id; + char *mutter_hints; + char *sandboxed_app_id; + char *gtk_theme_variant; + char *gtk_application_id; + char *gtk_unique_bus_name; + char *gtk_application_object_path; + char *gtk_window_object_path; + char *gtk_app_menu_object_path; + char *gtk_menubar_object_path; + + Window xtransient_for; + Window xgroup_leader; + Window xclient_leader; + MetaWindow *transient_for; + + /* Initial workspace property */ + int initial_workspace; + + /* Initial timestamp property */ + guint32 initial_timestamp; + + /* The current tile mode */ + MetaTileMode tile_mode; + + int tile_monitor_number; + + struct { + MetaEdgeConstraint top; + MetaEdgeConstraint right; + MetaEdgeConstraint bottom; + MetaEdgeConstraint left; + } edge_constraints; + + double tile_hfraction; + + uint64_t preferred_output_winsys_id; + + /* Area to cover when in fullscreen mode. If _NET_WM_FULLSCREEN_MONITORS has + * been overridden (via a client message), the window will cover the union of + * these monitors. If not, this is the single monitor which the window's + * origin is on. */ + struct { + MetaLogicalMonitor *top; + MetaLogicalMonitor *bottom; + MetaLogicalMonitor *left; + MetaLogicalMonitor *right; + } fullscreen_monitors; + + /* if non-NULL, the bounds of the window frame */ + cairo_region_t *frame_bounds; + + /* if non-NULL, the bounding shape region of the window. Relative to + * the server-side client window. */ + cairo_region_t *shape_region; + + /* if non-NULL, the opaque region _NET_WM_OPAQUE_REGION */ + cairo_region_t *opaque_region; + + /* the input shape region for picking */ + cairo_region_t *input_region; + + /* _NET_WM_WINDOW_OPACITY rescaled to 0xFF */ + guint8 opacity; + + /* Note: can be NULL */ + GSList *struts; + + /* Number of UnmapNotify that are caused by us, if + * we get UnmapNotify with none pending then the client + * is withdrawing the window. + */ + int unmaps_pending; + + /* Number of XReparentWindow requests that we have queued. + */ + int reparents_pending; + + /* See docs for meta_window_get_stable_sequence() */ + guint32 stable_sequence; + + /* set to the most recent user-interaction event timestamp that we + know about for this window */ + guint32 net_wm_user_time; + + /* window that gets updated net_wm_user_time values */ + Window user_time_window; + + gboolean has_custom_frame_extents; + MetaFrameBorder custom_frame_extents; + + /* The rectangles here are in "frame rect" coordinates. See the + * comment at the top of meta_window_move_resize_internal() for more + * information. */ + + /* The current window geometry of the window. */ + MetaRectangle rect; + + /* The geometry to restore when we unmaximize. */ + MetaRectangle saved_rect; + + /* The geometry to restore when we unfullscreen. */ + MetaRectangle saved_rect_fullscreen; + + /* This is the geometry the window will have if no constraints have + * applied. We use this whenever we are moving implicitly (for example, + * if we move to avoid a panel, we can snap back to this position if + * the panel moves again). + */ + MetaRectangle unconstrained_rect; + + /* The rectangle of the "server-side" geometry of the buffer, + * in root coordinates. + * + * For X11 windows, this matches XGetGeometry of the toplevel. + * + * For Wayland windows, the position matches the position of the + * surface associated with shell surface (xdg_surface, etc.) + * The size matches the size surface size as displayed in the stage. + */ + MetaRectangle buffer_rect; + + /* Cached net_wm_icon_geometry */ + MetaRectangle icon_geometry; + + /* x/y/w/h here get filled with ConfigureRequest values */ + XSizeHints size_hints; + + /* Managed by stack.c */ + MetaStackLayer layer; + int stack_position; /* see comment in stack.h */ + + /* Managed by delete.c */ + MetaCloseDialog *close_dialog; + + /* maintained by group.c */ + MetaGroup *group; + + GObject *compositor_private; + + /* Focused window that is (directly or indirectly) attached to this one */ + MetaWindow *attached_focus_window; + + /* The currently complementary tiled window, if any */ + MetaWindow *tile_match; + + struct { + MetaPlacementRule *rule; + MetaPlacementState state; + + struct { + int x; + int y; + int rel_x; + int rel_y; + } pending; + + struct { + int rel_x; + int rel_y; + } current; + } placement; + + guint unmanage_idle_id; + guint close_dialog_timeout_id; + + pid_t client_pid; + + gboolean has_valid_cgroup; + GFile *cgroup_path; + + unsigned int events_during_ping; + + /* Whether this is an override redirect window or not */ + guint override_redirect : 1; + + /* Whether we're maximized */ + guint maximized_horizontally : 1; + guint maximized_vertically : 1; + + /* Whether we have to maximize/minimize after placement */ + guint maximize_horizontally_after_placement : 1; + guint maximize_vertically_after_placement : 1; + guint minimize_after_placement : 1; + + /* The last "full" maximized/unmaximized state. We need to keep track of + * that to toggle between normal/tiled or maximized/tiled states. */ + guint saved_maximize : 1; + + /* Whether we're fullscreen */ + guint fullscreen : 1; + + /* Whether the window is marked as urgent */ + guint urgent : 1; + + /* Whether we're trying to constrain the window to be fully onscreen */ + guint require_fully_onscreen : 1; + + /* Whether we're trying to constrain the window to be on a single monitor */ + guint require_on_single_monitor : 1; + + /* Whether we're trying to constrain the window's titlebar to be onscreen */ + guint require_titlebar_visible : 1; + + /* Whether we're sticky in the multi-workspace sense + * (vs. the not-scroll-with-viewport sense, we don't + * have no stupid viewports) + */ + guint on_all_workspaces : 1; + + /* This is true if the client requested sticky, and implies on_all_workspaces == TRUE, + * however on_all_workspaces can be set TRUE for other internal reasons too, such as + * being override_redirect or being on the non-primary monitor. */ + guint on_all_workspaces_requested : 1; + + /* Minimize is the state controlled by the minimize button */ + guint minimized : 1; + guint tab_unminimized : 1; + + /* Whether the window is mapped; actual server-side state + * see also unmaps_pending + */ + guint mapped : 1; + + /* Whether window has been hidden from view by lowering it to the bottom + * of window stack. + */ + guint hidden : 1; + + /* Whether the compositor thinks the window is visible. + * This should match up with calls to meta_compositor_show_window / + * meta_compositor_hide_window. + */ + guint visible_to_compositor : 1; + + /* Whether the compositor knows about the window. + * This should match up with calls to meta_compositor_add_window / + * meta_compositor_remove_window. + */ + guint known_to_compositor : 1; + + /* When we next show or hide the window, what effect we should + * tell the compositor to perform. + */ + guint pending_compositor_effect : 4; /* MetaCompEffect */ + + /* Iconic is the state in WM_STATE; happens for workspaces/shading + * in addition to minimize + */ + guint iconic : 1; + /* initially_iconic is the WM_HINTS setting when we first manage + * the window. It's taken to mean initially minimized. + */ + guint initially_iconic : 1; + + /* whether an initial workspace was explicitly set */ + guint initial_workspace_set : 1; + + /* whether an initial timestamp was explicitly set */ + guint initial_timestamp_set : 1; + + /* whether net_wm_user_time has been set yet */ + guint net_wm_user_time_set : 1; + + /* whether net_wm_icon_geometry has been set */ + guint icon_geometry_set : 1; + + /* Globally active / No input */ + guint input : 1; + + /* MWM hints about features of window */ + guint mwm_decorated : 1; + guint mwm_border_only : 1; + guint mwm_has_close_func : 1; + guint mwm_has_minimize_func : 1; + guint mwm_has_maximize_func : 1; + guint mwm_has_move_func : 1; + guint mwm_has_resize_func : 1; + + /* Computed features of window */ + guint decorated : 1; + guint border_only : 1; + guint always_sticky : 1; + guint has_close_func : 1; + guint has_minimize_func : 1; + guint has_maximize_func : 1; + guint has_move_func : 1; + guint has_resize_func : 1; + guint has_fullscreen_func : 1; + + /* Computed whether to skip taskbar or not */ + guint skip_taskbar : 1; + guint skip_pager : 1; + guint skip_from_window_list : 1; + + /* TRUE if client set these */ + guint wm_state_above : 1; + guint wm_state_below : 1; + + /* EWHH demands attention flag */ + guint wm_state_demands_attention : 1; + + /* TRUE iff window == window->display->focus_window */ + guint has_focus : 1; + + /* TRUE if window appears focused at the moment */ + guint appears_focused : 1; + + /* Have we placed this window? */ + guint placed : 1; + + /* Is this not a transient of the focus window which is being denied focus? */ + guint denied_focus_and_not_transient : 1; + + /* Has this window not ever been shown yet? */ + guint showing_for_first_time : 1; + + /* Are we in meta_window_unmanage()? */ + guint unmanaging : 1; + + /* Are we in meta_window_new()? */ + guint constructing : 1; + + /* Used by keybindings.c */ + guint keys_grabbed : 1; /* normal keybindings grabbed */ + guint grab_on_frame : 1; /* grabs are on the frame */ + + /* Set if the reason for unmanaging the window is that + * it was withdrawn + */ + guint withdrawn : 1; + + /* TRUE if constrain_position should calc placement. + * only relevant if !window->placed + */ + guint calc_placement : 1; + + /* if TRUE we have a grab on the focus click buttons */ + guint have_focus_click_grab : 1; + + /* if TRUE, window is attached to its parent */ + guint attached : 1; + + /* whether or not the window is from a program running on another machine */ + guint is_remote : 1; + + /* whether focus should be restored on map */ + guint restore_focus_on_map : 1; + + /* Whether the window is alive */ + guint is_alive : 1; + + guint in_workspace_change : 1; +}; + +struct _MetaWindowClass +{ + GObjectClass parent_class; + + void (*manage) (MetaWindow *window); + void (*unmanage) (MetaWindow *window); + void (*ping) (MetaWindow *window, + guint32 serial); + void (*delete) (MetaWindow *window, + guint32 timestamp); + void (*kill) (MetaWindow *window); + void (*focus) (MetaWindow *window, + guint32 timestamp); + void (*grab_op_began) (MetaWindow *window, + MetaGrabOp op); + void (*grab_op_ended) (MetaWindow *window, + MetaGrabOp op); + void (*current_workspace_changed) (MetaWindow *window); + void (*move_resize_internal) (MetaWindow *window, + MetaGravity gravity, + MetaRectangle unconstrained_rect, + MetaRectangle constrained_rect, + MetaRectangle temporary_rect, + int rel_x, + int rel_y, + MetaMoveResizeFlags flags, + MetaMoveResizeResultFlags *result); + gboolean (*update_struts) (MetaWindow *window); + void (*get_default_skip_hints) (MetaWindow *window, + gboolean *skip_taskbar_out, + gboolean *skip_pager_out); + + cairo_surface_t * (*get_icon) (MetaWindow *window); + cairo_surface_t * (*get_mini_icon) (MetaWindow *window); + + pid_t (*get_client_pid) (MetaWindow *window); + void (*update_main_monitor) (MetaWindow *window, + MetaWindowUpdateMonitorFlags flags); + void (*main_monitor_changed) (MetaWindow *window, + const MetaLogicalMonitor *old); + void (*adjust_fullscreen_monitor_rect) (MetaWindow *window, + MetaRectangle *monitor_rect); + void (*force_restore_shortcuts) (MetaWindow *window, + ClutterInputDevice *source); + gboolean (*shortcuts_inhibited) (MetaWindow *window, + ClutterInputDevice *source); + gboolean (*is_focusable) (MetaWindow *window); + gboolean (*is_stackable) (MetaWindow *window); + gboolean (*can_ping) (MetaWindow *window); + gboolean (*are_updates_frozen) (MetaWindow *window); + gboolean (*is_focus_async) (MetaWindow *window); + + MetaStackLayer (*calculate_layer) (MetaWindow *window); + +#ifdef HAVE_WAYLAND + MetaWaylandSurface * (*get_wayland_surface) (MetaWindow *window); +#endif + + void (* map) (MetaWindow *window); + void (* unmap) (MetaWindow *window); +}; + +/* These differ from window->has_foo_func in that they consider + * the dynamic window state such as "maximized", not just the + * window's type + */ +#define META_WINDOW_MAXIMIZED(w) ((w)->maximized_horizontally && \ + (w)->maximized_vertically) +#define META_WINDOW_MAXIMIZED_VERTICALLY(w) ((w)->maximized_vertically) +#define META_WINDOW_MAXIMIZED_HORIZONTALLY(w) ((w)->maximized_horizontally) +#define META_WINDOW_TILED_SIDE_BY_SIDE(w) ((w)->maximized_vertically && \ + !(w)->maximized_horizontally && \ + (w)->tile_mode != META_TILE_NONE) +#define META_WINDOW_TILED_LEFT(w) (META_WINDOW_TILED_SIDE_BY_SIDE(w) && \ + (w)->tile_mode == META_TILE_LEFT) +#define META_WINDOW_TILED_RIGHT(w) (META_WINDOW_TILED_SIDE_BY_SIDE(w) && \ + (w)->tile_mode == META_TILE_RIGHT) +#define META_WINDOW_TILED_MAXIMIZED(w)(META_WINDOW_MAXIMIZED(w) && \ + (w)->tile_mode == META_TILE_MAXIMIZED) +#define META_WINDOW_ALLOWS_MOVE(w) ((w)->has_move_func && !(w)->fullscreen) +#define META_WINDOW_ALLOWS_RESIZE_EXCEPT_HINTS(w) ((w)->has_resize_func && !META_WINDOW_MAXIMIZED (w) && !(w)->fullscreen) +#define META_WINDOW_ALLOWS_RESIZE(w) (META_WINDOW_ALLOWS_RESIZE_EXCEPT_HINTS (w) && \ + (((w)->size_hints.min_width < (w)->size_hints.max_width) || \ + ((w)->size_hints.min_height < (w)->size_hints.max_height))) +#define META_WINDOW_ALLOWS_HORIZONTAL_RESIZE(w) (META_WINDOW_ALLOWS_RESIZE_EXCEPT_HINTS (w) && (w)->size_hints.min_width < (w)->size_hints.max_width) +#define META_WINDOW_ALLOWS_VERTICAL_RESIZE(w) (META_WINDOW_ALLOWS_RESIZE_EXCEPT_HINTS (w) && (w)->size_hints.min_height < (w)->size_hints.max_height) + +void meta_window_unmanage (MetaWindow *window, + guint32 timestamp); +void meta_window_unmanage_on_idle (MetaWindow *window); +void meta_window_queue (MetaWindow *window, + MetaQueueType queue_types); +META_EXPORT_TEST +void meta_window_untile (MetaWindow *window); + +META_EXPORT_TEST +void meta_window_tile (MetaWindow *window, + MetaTileMode mode); +MetaTileMode meta_window_get_tile_mode (MetaWindow *window); +void meta_window_restore_tile (MetaWindow *window, + MetaTileMode mode, + int width, + int height); +void meta_window_maximize_internal (MetaWindow *window, + MetaMaximizeFlags directions, + MetaRectangle *saved_rect); + +void meta_window_make_fullscreen_internal (MetaWindow *window); +void meta_window_update_fullscreen_monitors (MetaWindow *window, + MetaLogicalMonitor *top, + MetaLogicalMonitor *bottom, + MetaLogicalMonitor *left, + MetaLogicalMonitor *right); + +gboolean meta_window_has_fullscreen_monitors (MetaWindow *window); + +void meta_window_adjust_fullscreen_monitor_rect (MetaWindow *window, + MetaRectangle *monitor_rect); + +void meta_window_resize_frame_with_gravity (MetaWindow *window, + gboolean user_op, + int w, + int h, + MetaGravity gravity); + +gboolean meta_window_should_be_showing_on_workspace (MetaWindow *window, + MetaWorkspace *workspace); + +/* Return whether the window should be currently mapped */ +gboolean meta_window_should_be_showing (MetaWindow *window); + +void meta_window_update_struts (MetaWindow *window); + +/* gets position we need to set to stay in current position, + * assuming position will be gravity-compensated. i.e. + * this is the position a client would send in a configure + * request. + */ +void meta_window_get_gravity_position (MetaWindow *window, + MetaGravity gravity, + int *x, + int *y); +/* Get geometry for saving in the session; x/y are gravity + * position, and w/h are in resize inc above the base size. + */ +void meta_window_get_session_geometry (MetaWindow *window, + int *x, + int *y, + int *width, + int *height); + +void meta_window_update_unfocused_button_grabs (MetaWindow *window); + +void meta_window_update_appears_focused (MetaWindow *window); + +void meta_window_set_focused_internal (MetaWindow *window, + gboolean focused); + +gboolean meta_window_is_focusable (MetaWindow *window); + +gboolean meta_window_can_ping (MetaWindow *window); + +MetaStackLayer meta_window_calculate_layer (MetaWindow *window); + +#ifdef HAVE_WAYLAND +META_EXPORT_TEST +MetaWaylandSurface * meta_window_get_wayland_surface (MetaWindow *window); +#endif + +void meta_window_current_workspace_changed (MetaWindow *window); + +void meta_window_show_menu (MetaWindow *window, + MetaWindowMenuType menu, + int x, + int y); + +void meta_window_show_menu_for_rect (MetaWindow *window, + MetaWindowMenuType menu, + MetaRectangle *rect); + +GList* meta_window_get_workspaces (MetaWindow *window); + +void meta_window_get_work_area_for_logical_monitor (MetaWindow *window, + MetaLogicalMonitor *logical_monitor, + MetaRectangle *area); + +int meta_window_get_current_tile_monitor_number (MetaWindow *window); +void meta_window_get_tile_area (MetaWindow *window, + MetaTileMode mode, + MetaRectangle *tile_area); + + +gboolean meta_window_same_application (MetaWindow *window, + MetaWindow *other_window); + +#define META_WINDOW_IN_NORMAL_TAB_CHAIN_TYPE(w) \ + ((w)->type != META_WINDOW_DOCK && (w)->type != META_WINDOW_DESKTOP) +#define META_WINDOW_IN_NORMAL_TAB_CHAIN(w) \ + (meta_window_is_focusable (w) && META_WINDOW_IN_NORMAL_TAB_CHAIN_TYPE (w) && (!(w)->skip_taskbar)) +#define META_WINDOW_IN_DOCK_TAB_CHAIN(w) \ + (meta_window_is_focusable (w) && (! META_WINDOW_IN_NORMAL_TAB_CHAIN_TYPE (w) || (w)->skip_taskbar)) +#define META_WINDOW_IN_GROUP_TAB_CHAIN(w, g) \ + (meta_window_is_focusable (w) && (!g || meta_window_get_group(w)==g)) + +void meta_window_free_delete_dialog (MetaWindow *window); + +MetaStackLayer meta_window_get_default_layer (MetaWindow *window); +void meta_window_update_layer (MetaWindow *window); + +void meta_window_recalc_features (MetaWindow *window); + +void meta_window_set_type (MetaWindow *window, + MetaWindowType type); + +void meta_window_frame_size_changed (MetaWindow *window); + +gboolean meta_window_is_in_stack (MetaWindow *window); + +void meta_window_stack_just_below (MetaWindow *window, + MetaWindow *below_this_one); + +void meta_window_stack_just_above (MetaWindow *window, + MetaWindow *above_this_one); + +void meta_window_set_user_time (MetaWindow *window, + guint32 timestamp); + +void meta_window_update_for_monitors_changed (MetaWindow *window); +void meta_window_on_all_workspaces_changed (MetaWindow *window); + +gboolean meta_window_should_attach_to_parent (MetaWindow *window); +gboolean meta_window_can_tile_side_by_side (MetaWindow *window); + +void meta_window_compute_tile_match (MetaWindow *window); + +gboolean meta_window_updates_are_frozen (MetaWindow *window); + +void meta_window_set_title (MetaWindow *window, + const char *title); +void meta_window_set_wm_class (MetaWindow *window, + const char *wm_class, + const char *wm_instance); +void meta_window_set_gtk_dbus_properties (MetaWindow *window, + const char *application_id, + const char *unique_bus_name, + const char *appmenu_path, + const char *menubar_path, + const char *application_object_path, + const char *window_object_path); + +gboolean meta_window_has_transient_type (MetaWindow *window); +gboolean meta_window_has_modals (MetaWindow *window); + +void meta_window_set_transient_for (MetaWindow *window, + MetaWindow *parent); + +void meta_window_set_opacity (MetaWindow *window, + guint8 opacity); + +void meta_window_handle_enter (MetaWindow *window, + guint32 timestamp, + guint root_x, + guint root_y); +void meta_window_handle_leave (MetaWindow *window); + +void meta_window_handle_ungrabbed_event (MetaWindow *window, + const ClutterEvent *event); + +void meta_window_get_client_area_rect (const MetaWindow *window, + cairo_rectangle_int_t *rect); +void meta_window_get_titlebar_rect (MetaWindow *window, + MetaRectangle *titlebar_rect); + +void meta_window_activate_full (MetaWindow *window, + guint32 timestamp, + MetaClientType source_indication, + MetaWorkspace *workspace); + +META_EXPORT_TEST +MetaLogicalMonitor * meta_window_find_monitor_from_frame_rect (MetaWindow *window); + +MetaLogicalMonitor * meta_window_find_monitor_from_id (MetaWindow *window); + +MetaLogicalMonitor * meta_window_get_main_logical_monitor (MetaWindow *window); +void meta_window_update_monitor (MetaWindow *window, + MetaWindowUpdateMonitorFlags flags); + +cairo_surface_t * meta_window_get_icon (MetaWindow *window); + +cairo_surface_t * meta_window_get_mini_icon (MetaWindow *window); + +void meta_window_set_urgent (MetaWindow *window, + gboolean urgent); + +void meta_window_move_resize_internal (MetaWindow *window, + MetaMoveResizeFlags flags, + MetaGravity gravity, + MetaRectangle frame_rect); + +void meta_window_grab_op_began (MetaWindow *window, MetaGrabOp op); +void meta_window_grab_op_ended (MetaWindow *window, MetaGrabOp op); + +void meta_window_set_alive (MetaWindow *window, gboolean is_alive); +gboolean meta_window_get_alive (MetaWindow *window); + +void meta_window_show_close_dialog (MetaWindow *window); +void meta_window_hide_close_dialog (MetaWindow *window); +void meta_window_ensure_close_dialog_timeout (MetaWindow *window); + +void meta_window_emit_size_changed (MetaWindow *window); + +MetaPlacementRule *meta_window_get_placement_rule (MetaWindow *window); + +void meta_window_force_placement (MetaWindow *window, + gboolean force_move); + +void meta_window_force_restore_shortcuts (MetaWindow *window, + ClutterInputDevice *source); + +gboolean meta_window_shortcuts_inhibited (MetaWindow *window, + ClutterInputDevice *source); +gboolean meta_window_is_stackable (MetaWindow *window); +gboolean meta_window_is_focus_async (MetaWindow *window); + +GFile *meta_window_get_unit_cgroup (MetaWindow *window); +gboolean meta_window_unit_cgroup_equal (MetaWindow *window1, + MetaWindow *window2); + +void meta_window_check_alive_on_event (MetaWindow *window, + uint32_t timestamp); + +void meta_window_update_visibility (MetaWindow *window); + +void meta_window_clear_queued (MetaWindow *window); + +void meta_window_update_layout (MetaWindow *window); + +gboolean meta_window_calculate_bounds (MetaWindow *window, + int *bounds_width, + int *bounds_height); + +void meta_window_set_frame_xwindow (MetaWindow *window, + Window xframe); + +void meta_window_maybe_apply_size_hints (MetaWindow *window, + MetaRectangle *target_rect); + +#endif diff --git a/src/core/window.c b/src/core/window.c index 6c92477..01ab19f 100644 --- a/src/core/window.c +++ b/src/core/window.c @@ -4271,6 +4271,14 @@ meta_window_get_session_geometry (MetaWindow *window, window->size_hints.height_inc; } +gboolean +meta_window_frame_contains_rect (MetaWindow *window, + MetaRectangle *rect) +{ + return meta_rectangle_contains_rect (&window->rect, + rect); +} + /** * meta_window_get_buffer_rect: * @window: a #MetaWindow diff --git a/src/core/window.c.orig b/src/core/window.c.orig new file mode 100644 index 0000000..6c92477 --- /dev/null +++ b/src/core/window.c.orig @@ -0,0 +1,7939 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* + * Copyright (C) 2001 Havoc Pennington, Anders Carlsson + * Copyright (C) 2002, 2003 Red Hat, Inc. + * Copyright (C) 2003 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:meta-window + * @title: MetaWindow + * @short_description: A display-agnostic abstraction for a window. + * + * #MetaWindow is the core abstraction in Mutter of a window. It has the + * properties you'd expect, such as a title, whether it's fullscreen, + * has decorations, etc. + * + * Since a lot of different kinds of windows exist, each window also a + * #MetaWindowType which denotes which kind of window we're exactly dealing + * with. For example, one expects slightly different behaviour from a dialog + * than a "normal" window. The type of a window can be queried with + * meta_window_get_type(). + * + * Common API for windows include: + * - Minimizing: meta_window_minimize() / meta_window_unminimize() + * - Maximizing: meta_window_maximize() / meta_window_unmaximize() + * - Fullscreen: meta_window_make_fullscreen() / meta_window_unmake_fullscreen() + * / meta_window_is_fullscreen() + * + * Each #MetaWindow is part of either one or all #MetaWorkspaces of the + * desktop. You can activate a window on a certain workspace using + * meta_window_activate_with_workspace(), and query on which workspace it is + * located using meta_window_located_on_workspace(). The workspace it is part + * of can be obtained using meta_window_get_workspace(). + * + * Each display protocol should make a subclass to be compatible with that + * protocols' specifics, for example #MetaWindowX11 and #MetaWindowWayland. + * This is independent of the protocol that the client uses, which is modeled + * using the #MetaWindowClientType enum. + * + * To integrate within the Clutter scene graph, which deals with the actual + * rendering, each #MetaWindow will be part of a #MetaWindowActor. + */ + +#include "config.h" + +#include "core/window-private.h" + +#include +#include +#include +#include + +#include "backends/meta-backend-private.h" +#include "backends/meta-logical-monitor.h" +#include "cogl/cogl.h" +#include "compositor/compositor-private.h" +#include "core/boxes-private.h" +#include "core/constraints.h" +#include "core/frame.h" +#include "core/keybindings-private.h" +#include "core/meta-workspace-manager-private.h" +#include "core/place.h" +#include "core/stack.h" +#include "core/util-private.h" +#include "core/workspace-private.h" +#include "meta/compositor-mutter.h" +#include "meta/group.h" +#include "meta/meta-cursor-tracker.h" +#include "meta/meta-enum-types.h" +#include "meta/meta-x11-errors.h" +#include "meta/prefs.h" +#include "x11/meta-x11-display-private.h" +#include "x11/window-props.h" +#include "x11/window-x11.h" +#include "x11/xprops.h" + +#ifdef HAVE_WAYLAND +#include "wayland/meta-wayland-private.h" +#include "wayland/meta-wayland-surface.h" +#include "wayland/meta-window-wayland.h" +#endif + +#ifdef HAVE_XWAYLAND +#include "wayland/meta-window-xwayland.h" +#endif + +#ifdef HAVE_LIBSYSTEMD +#include +#endif + +/* Windows that unmaximize to a size bigger than that fraction of the workarea + * will be scaled down to that size (while maintaining aspect ratio). + * Windows that cover an area greater then this size are automaximized on map. + */ +#define MAX_UNMAXIMIZED_WINDOW_AREA .8 + +#define SNAP_SECURITY_LABEL_PREFIX "snap." + +/* Each window has a "stamp" which is a non-recycled 64-bit ID. They + * start after the end of the XID space so that, for stacking + * we can keep a guint64 that represents one or the other + */ +static guint64 next_window_stamp = G_GUINT64_CONSTANT(0x100000000); + +static void invalidate_work_areas (MetaWindow *window); +static void set_wm_state (MetaWindow *window); +static void set_net_wm_state (MetaWindow *window); +static void meta_window_set_above (MetaWindow *window, + gboolean new_value); + +static void meta_window_constructed (GObject *object); +static void meta_window_show (MetaWindow *window); +static void meta_window_hide (MetaWindow *window); + +static void meta_window_save_rect (MetaWindow *window); + +static void ensure_mru_position_after (MetaWindow *window, + MetaWindow *after_this_one); + +static void meta_window_unqueue (MetaWindow *window, + MetaQueueType queuebits); + +static gboolean should_be_on_all_workspaces (MetaWindow *window); + +static void meta_window_flush_calc_showing (MetaWindow *window); + +static gboolean queue_calc_showing_func (MetaWindow *window, + void *data); + +static void meta_window_move_between_rects (MetaWindow *window, + MetaMoveResizeFlags move_resize_flags, + const MetaRectangle *old_area, + const MetaRectangle *new_area); + +static void unmaximize_window_before_freeing (MetaWindow *window); +static void unminimize_window_and_all_transient_parents (MetaWindow *window); + +static void meta_window_propagate_focus_appearance (MetaWindow *window, + gboolean focused); +static void set_workspace_state (MetaWindow *window, + gboolean on_all_workspaces, + MetaWorkspace *workspace); + +static MetaWindow * meta_window_find_tile_match (MetaWindow *window, + MetaTileMode mode); +static void update_edge_constraints (MetaWindow *window); + +static void initable_iface_init (GInitableIface *initable_iface); + +typedef struct _MetaWindowPrivate +{ + MetaQueueType queued_types; +} MetaWindowPrivate; + +G_DEFINE_ABSTRACT_TYPE_WITH_CODE (MetaWindow, meta_window, G_TYPE_OBJECT, + G_ADD_PRIVATE (MetaWindow) + G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, + initable_iface_init)) + +enum +{ + PROP_0, + + PROP_TITLE, + PROP_ICON, + PROP_MINI_ICON, + PROP_DECORATED, + PROP_FULLSCREEN, + PROP_MAXIMIZED_HORIZONTALLY, + PROP_MAXIMIZED_VERTICALLY, + PROP_MINIMIZED, + PROP_WINDOW_TYPE, + PROP_USER_TIME, + PROP_DEMANDS_ATTENTION, + PROP_URGENT, + PROP_SKIP_TASKBAR, + PROP_MUTTER_HINTS, + PROP_APPEARS_FOCUSED, + PROP_RESIZEABLE, + PROP_ABOVE, + PROP_WM_CLASS, + PROP_GTK_APPLICATION_ID, + PROP_GTK_UNIQUE_BUS_NAME, + PROP_GTK_APPLICATION_OBJECT_PATH, + PROP_GTK_WINDOW_OBJECT_PATH, + PROP_GTK_APP_MENU_OBJECT_PATH, + PROP_GTK_MENUBAR_OBJECT_PATH, + PROP_ON_ALL_WORKSPACES, + PROP_IS_ALIVE, + PROP_DISPLAY, + PROP_EFFECT, + PROP_XWINDOW, + + PROP_LAST, +}; + +static GParamSpec *obj_props[PROP_LAST]; + +enum +{ + WORKSPACE_CHANGED, + FOCUS, + RAISED, + UNMANAGING, + UNMANAGED, + SIZE_CHANGED, + POSITION_CHANGED, + SHOWN, + + LAST_SIGNAL +}; + +static guint window_signals[LAST_SIGNAL] = { 0 }; + +static MetaBackend * +backend_from_window (MetaWindow *window) +{ + MetaDisplay *display = meta_window_get_display (window); + MetaContext *context = meta_display_get_context (display); + + return meta_context_get_backend (context); +} + +static void +prefs_changed_callback (MetaPreference pref, + gpointer data) +{ + MetaWindow *window = data; + + if (pref == META_PREF_WORKSPACES_ONLY_ON_PRIMARY) + { + meta_window_on_all_workspaces_changed (window); + } + else if (pref == META_PREF_ATTACH_MODAL_DIALOGS && + window->type == META_WINDOW_MODAL_DIALOG) + { + window->attached = meta_window_should_attach_to_parent (window); + meta_window_recalc_features (window); + meta_window_queue (window, META_QUEUE_MOVE_RESIZE); + } + else if (pref == META_PREF_FOCUS_MODE) + { + meta_window_update_appears_focused (window); + } +} + +static void +meta_window_real_grab_op_began (MetaWindow *window, + MetaGrabOp op) +{ +} + +static void +meta_window_real_grab_op_ended (MetaWindow *window, + MetaGrabOp op) +{ +} + +static void +meta_window_real_current_workspace_changed (MetaWindow *window) +{ +} + +static gboolean +meta_window_real_update_struts (MetaWindow *window) +{ + return FALSE; +} + +static void +meta_window_real_get_default_skip_hints (MetaWindow *window, + gboolean *skip_taskbar_out, + gboolean *skip_pager_out) +{ + *skip_taskbar_out = FALSE; + *skip_pager_out = FALSE; +} + +static pid_t +meta_window_real_get_client_pid (MetaWindow *window) +{ + return 0; +} + +static void +meta_window_finalize (GObject *object) +{ + MetaWindow *window = META_WINDOW (object); + + if (window->frame_bounds) + cairo_region_destroy (window->frame_bounds); + + if (window->shape_region) + cairo_region_destroy (window->shape_region); + + if (window->opaque_region) + cairo_region_destroy (window->opaque_region); + + if (window->input_region) + cairo_region_destroy (window->input_region); + + if (window->transient_for) + g_object_unref (window->transient_for); + + if (window->cgroup_path) + g_object_unref (window->cgroup_path); + + g_free (window->sm_client_id); + g_free (window->wm_client_machine); + g_free (window->startup_id); + g_free (window->role); + g_free (window->res_class); + g_free (window->res_name); + g_free (window->title); + g_free (window->desc); + g_free (window->sandboxed_app_id); + g_free (window->gtk_theme_variant); + g_free (window->gtk_application_id); + g_free (window->gtk_unique_bus_name); + g_free (window->gtk_application_object_path); + g_free (window->gtk_window_object_path); + g_free (window->gtk_app_menu_object_path); + g_free (window->gtk_menubar_object_path); + g_free (window->placement.rule); + + G_OBJECT_CLASS (meta_window_parent_class)->finalize (object); +} + +static void +meta_window_get_property(GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + MetaWindow *win = META_WINDOW (object); + + switch (prop_id) + { + case PROP_TITLE: + g_value_set_string (value, win->title); + break; + case PROP_ICON: + g_value_set_pointer (value, meta_window_get_icon (win)); + break; + case PROP_MINI_ICON: + g_value_set_pointer (value, meta_window_get_mini_icon (win)); + break; + case PROP_DECORATED: + g_value_set_boolean (value, win->decorated); + break; + case PROP_FULLSCREEN: + g_value_set_boolean (value, win->fullscreen); + break; + case PROP_MAXIMIZED_HORIZONTALLY: + g_value_set_boolean (value, win->maximized_horizontally); + break; + case PROP_MAXIMIZED_VERTICALLY: + g_value_set_boolean (value, win->maximized_vertically); + break; + case PROP_MINIMIZED: + g_value_set_boolean (value, win->minimized); + break; + case PROP_WINDOW_TYPE: + g_value_set_enum (value, win->type); + break; + case PROP_USER_TIME: + g_value_set_uint (value, win->net_wm_user_time); + break; + case PROP_DEMANDS_ATTENTION: + g_value_set_boolean (value, win->wm_state_demands_attention); + break; + case PROP_URGENT: + g_value_set_boolean (value, win->urgent); + break; + case PROP_SKIP_TASKBAR: + g_value_set_boolean (value, win->skip_taskbar); + break; + case PROP_MUTTER_HINTS: + g_value_set_string (value, win->mutter_hints); + break; + case PROP_APPEARS_FOCUSED: + g_value_set_boolean (value, win->appears_focused); + break; + case PROP_WM_CLASS: + g_value_set_string (value, win->res_class); + break; + case PROP_RESIZEABLE: + g_value_set_boolean (value, win->has_resize_func); + break; + case PROP_ABOVE: + g_value_set_boolean (value, win->wm_state_above); + break; + case PROP_GTK_APPLICATION_ID: + g_value_set_string (value, win->gtk_application_id); + break; + case PROP_GTK_UNIQUE_BUS_NAME: + g_value_set_string (value, win->gtk_unique_bus_name); + break; + case PROP_GTK_APPLICATION_OBJECT_PATH: + g_value_set_string (value, win->gtk_application_object_path); + break; + case PROP_GTK_WINDOW_OBJECT_PATH: + g_value_set_string (value, win->gtk_window_object_path); + break; + case PROP_GTK_APP_MENU_OBJECT_PATH: + g_value_set_string (value, win->gtk_app_menu_object_path); + break; + case PROP_GTK_MENUBAR_OBJECT_PATH: + g_value_set_string (value, win->gtk_menubar_object_path); + break; + case PROP_ON_ALL_WORKSPACES: + g_value_set_boolean (value, win->on_all_workspaces); + break; + case PROP_DISPLAY: + g_value_set_object (value, win->display); + break; + case PROP_EFFECT: + g_value_set_int (value, win->pending_compositor_effect); + break; + case PROP_XWINDOW: + g_value_set_ulong (value, win->xwindow); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +meta_window_set_property(GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + MetaWindow *win = META_WINDOW (object); + + switch (prop_id) + { + case PROP_DISPLAY: + win->display = g_value_get_object (value); + break; + case PROP_EFFECT: + win->pending_compositor_effect = g_value_get_int (value); + break; + case PROP_XWINDOW: + win->xwindow = g_value_get_ulong (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +meta_window_class_init (MetaWindowClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = meta_window_constructed; + object_class->finalize = meta_window_finalize; + + object_class->get_property = meta_window_get_property; + object_class->set_property = meta_window_set_property; + + klass->grab_op_began = meta_window_real_grab_op_began; + klass->grab_op_ended = meta_window_real_grab_op_ended; + klass->current_workspace_changed = meta_window_real_current_workspace_changed; + klass->update_struts = meta_window_real_update_struts; + klass->get_default_skip_hints = meta_window_real_get_default_skip_hints; + klass->get_client_pid = meta_window_real_get_client_pid; + + obj_props[PROP_TITLE] = + g_param_spec_string ("title", + "Title", + "The title of the window", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_props[PROP_ICON] = + g_param_spec_pointer ("icon", + "Icon", + "Normal icon, usually 96x96 pixels", + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_props[PROP_MINI_ICON] = + g_param_spec_pointer ("mini-icon", + "Mini Icon", + "Mini icon, usually 16x16 pixels", + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_props[PROP_DECORATED] = + g_param_spec_boolean ("decorated", + "Decorated", + "Whether window is decorated", + TRUE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_props[PROP_FULLSCREEN] = + g_param_spec_boolean ("fullscreen", + "Fullscreen", + "Whether window is fullscreened", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_props[PROP_MAXIMIZED_HORIZONTALLY] = + g_param_spec_boolean ("maximized-horizontally", + "Maximized horizontally", + "Whether window is maximized horizontally", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_props[PROP_MAXIMIZED_VERTICALLY] = + g_param_spec_boolean ("maximized-vertically", + "Maximizing vertically", + "Whether window is maximized vertically", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_props[PROP_MINIMIZED] = + g_param_spec_boolean ("minimized", + "Minimizing", + "Whether window is minimized", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_props[PROP_WINDOW_TYPE] = + g_param_spec_enum ("window-type", + "Window Type", + "The type of the window", + META_TYPE_WINDOW_TYPE, + META_WINDOW_NORMAL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_props[PROP_USER_TIME] = + g_param_spec_uint ("user-time", + "User time", + "Timestamp of last user interaction", + 0, + G_MAXUINT, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_props[PROP_DEMANDS_ATTENTION] = + g_param_spec_boolean ("demands-attention", + "Demands Attention", + "Whether the window has _NET_WM_STATE_DEMANDS_ATTENTION set", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_props[PROP_URGENT] = + g_param_spec_boolean ("urgent", + "Urgent", + "Whether the urgent flag of WM_HINTS is set", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_props[PROP_SKIP_TASKBAR] = + g_param_spec_boolean ("skip-taskbar", + "Skip taskbar", + "Whether the skip-taskbar flag of WM_HINTS is set", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_props[PROP_MUTTER_HINTS] = + g_param_spec_string ("mutter-hints", + "_MUTTER_HINTS", + "Contents of the _MUTTER_HINTS property of this window", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_props[PROP_APPEARS_FOCUSED] = + g_param_spec_boolean ("appears-focused", + "Appears focused", + "Whether the window is drawn as being focused", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_props[PROP_RESIZEABLE] = + g_param_spec_boolean ("resizeable", + "Resizeable", + "Whether the window can be resized", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_props[PROP_ABOVE] = + g_param_spec_boolean ("above", + "Above", + "Whether the window is shown as always-on-top", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_props[PROP_WM_CLASS] = + g_param_spec_string ("wm-class", + "WM_CLASS", + "Contents of the WM_CLASS property of this window", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_props[PROP_GTK_APPLICATION_ID] = + g_param_spec_string ("gtk-application-id", + "_GTK_APPLICATION_ID", + "Contents of the _GTK_APPLICATION_ID property of this window", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_props[PROP_GTK_UNIQUE_BUS_NAME] = + g_param_spec_string ("gtk-unique-bus-name", + "_GTK_UNIQUE_BUS_NAME", + "Contents of the _GTK_UNIQUE_BUS_NAME property of this window", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_props[PROP_GTK_APPLICATION_OBJECT_PATH] = + g_param_spec_string ("gtk-application-object-path", + "_GTK_APPLICATION_OBJECT_PATH", + "Contents of the _GTK_APPLICATION_OBJECT_PATH property of this window", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_props[PROP_GTK_WINDOW_OBJECT_PATH] = + g_param_spec_string ("gtk-window-object-path", + "_GTK_WINDOW_OBJECT_PATH", + "Contents of the _GTK_WINDOW_OBJECT_PATH property of this window", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_props[PROP_GTK_APP_MENU_OBJECT_PATH] = + g_param_spec_string ("gtk-app-menu-object-path", + "_GTK_APP_MENU_OBJECT_PATH", + "Contents of the _GTK_APP_MENU_OBJECT_PATH property of this window", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_props[PROP_GTK_MENUBAR_OBJECT_PATH] = + g_param_spec_string ("gtk-menubar-object-path", + "_GTK_MENUBAR_OBJECT_PATH", + "Contents of the _GTK_MENUBAR_OBJECT_PATH property of this window", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + obj_props[PROP_ON_ALL_WORKSPACES] = + g_param_spec_boolean ("on-all-workspaces", + "On all workspaces", + "Whether the window is set to appear on all workspaces", + FALSE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_props[PROP_IS_ALIVE] = + g_param_spec_boolean ("is-alive", + "Is alive", + "Whether the window responds to pings", + TRUE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + obj_props[PROP_DISPLAY] = + g_param_spec_object ("display", + "Display", + "The display the window is attached to", + META_TYPE_DISPLAY, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); + + obj_props[PROP_EFFECT] = + g_param_spec_int ("effect", + "Compositor effect", + "The compositor effect", + META_COMP_EFFECT_CREATE, + META_COMP_EFFECT_NONE, + META_COMP_EFFECT_NONE, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); + + obj_props[PROP_XWINDOW] = + g_param_spec_ulong ("xwindow", + "X Window", + "The corresponding X Window", + 0, G_MAXULONG, 0, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); + + g_object_class_install_properties (object_class, PROP_LAST, obj_props); + + window_signals[WORKSPACE_CHANGED] = + g_signal_new ("workspace-changed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 0); + + window_signals[FOCUS] = + g_signal_new ("focus", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 0); + + window_signals[RAISED] = + g_signal_new ("raised", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 0); + + window_signals[UNMANAGING] = + g_signal_new ("unmanaging", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 0); + + window_signals[UNMANAGED] = + g_signal_new ("unmanaged", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 0); + + /** + * MetaWindow::position-changed: + * @window: a #MetaWindow + * + * This is emitted when the position of a window might + * have changed. Specifically, this is emitted when the + * position of the toplevel window has changed, or when + * the position of the client window has changed. + */ + window_signals[POSITION_CHANGED] = + g_signal_new ("position-changed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 0); + + /** + * MetaWindow::shown: + * @window: a #MetaWindow + * + * This is emitted after a window has been shown. + */ + window_signals[SHOWN] = + g_signal_new ("shown", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 0); + + /** + * MetaWindow::size-changed: + * @window: a #MetaWindow + * + * This is emitted when the size of a window might + * have changed. Specifically, this is emitted when the + * size of the toplevel window has changed, or when the + * size of the client window has changed. + */ + window_signals[SIZE_CHANGED] = + g_signal_new ("size-changed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 0); +} + +static void +meta_window_init (MetaWindow *self) +{ + self->stamp = next_window_stamp++; + meta_prefs_add_listener (prefs_changed_callback, self); + self->is_alive = TRUE; +} + +static gboolean +is_desktop_or_dock_foreach (MetaWindow *window, + void *data) +{ + gboolean *result = data; + + *result = + window->type == META_WINDOW_DESKTOP || + window->type == META_WINDOW_DOCK || + window->skip_from_window_list; + if (*result) + return FALSE; /* stop as soon as we find one */ + else + return TRUE; +} + +/* window is the window that's newly mapped provoking + * the possible change + */ +static void +maybe_leave_show_desktop_mode (MetaWindow *window) +{ + MetaWorkspaceManager *workspace_manager = window->display->workspace_manager; + gboolean is_desktop_or_dock; + + if (!workspace_manager->active_workspace->showing_desktop) + return; + + /* If the window is a transient for the dock or desktop, don't + * leave show desktop mode when the window opens. That's + * so you can e.g. hide all windows, manipulate a file on + * the desktop via a dialog, then unshow windows again. + */ + is_desktop_or_dock = FALSE; + is_desktop_or_dock_foreach (window, + &is_desktop_or_dock); + + meta_window_foreach_ancestor (window, is_desktop_or_dock_foreach, + &is_desktop_or_dock); + + if (!is_desktop_or_dock) + { + meta_workspace_manager_minimize_all_on_active_workspace_except (workspace_manager, + window); + meta_workspace_manager_unshow_desktop (workspace_manager); + } +} + +gboolean +meta_window_should_attach_to_parent (MetaWindow *window) +{ + MetaWindow *parent; + + if (!meta_prefs_get_attach_modal_dialogs () || + window->type != META_WINDOW_MODAL_DIALOG) + return FALSE; + + parent = meta_window_get_transient_for (window); + if (!parent) + return FALSE; + + switch (parent->type) + { + case META_WINDOW_NORMAL: + case META_WINDOW_DIALOG: + case META_WINDOW_MODAL_DIALOG: + return TRUE; + + default: + return FALSE; + } +} + +static gboolean +client_window_should_be_mapped (MetaWindow *window) +{ +#ifdef HAVE_WAYLAND + if (window->client_type == META_WINDOW_CLIENT_TYPE_WAYLAND) + { + MetaWaylandSurface *surface = meta_window_get_wayland_surface (window); + if (!meta_wayland_surface_get_buffer (surface)) + return FALSE; + } +#endif + + if (window->client_type == META_WINDOW_CLIENT_TYPE_X11 && + window->decorated && !window->frame) + return FALSE; + + return TRUE; +} + +static void +sync_client_window_mapped (MetaWindow *window) +{ + gboolean should_be_mapped = client_window_should_be_mapped (window); + + g_return_if_fail (!window->override_redirect); + + if (window->mapped == should_be_mapped) + return; + + window->mapped = should_be_mapped; + + if (window->mapped) + META_WINDOW_GET_CLASS (window)->map (window); + else + META_WINDOW_GET_CLASS (window)->unmap (window); +} + +static gboolean +meta_window_update_flatpak_id (MetaWindow *window, + uint32_t pid) +{ + g_autoptr(GKeyFile) key_file = NULL; + g_autofree char *info_filename = NULL; + + g_return_val_if_fail (pid != 0, FALSE); + g_return_val_if_fail (window->sandboxed_app_id == NULL, FALSE); + + key_file = g_key_file_new (); + info_filename = g_strdup_printf ("/proc/%u/root/.flatpak-info", pid); + + if (!g_key_file_load_from_file (key_file, info_filename, G_KEY_FILE_NONE, NULL)) + return FALSE; + + window->sandboxed_app_id = g_key_file_get_string (key_file, "Application", "name", NULL); + + return TRUE; +} + +static gboolean +meta_window_update_snap_id (MetaWindow *window, + uint32_t pid) +{ + g_autofree char *security_label_filename = NULL; + g_autofree char *security_label_contents = NULL; + gsize i, security_label_contents_size = 0; + char *contents_start; + char *contents_end; + char *sandboxed_app_id; + + g_return_val_if_fail (pid != 0, FALSE); + g_return_val_if_fail (window->sandboxed_app_id == NULL, FALSE); + + security_label_filename = g_strdup_printf ("/proc/%u/attr/current", pid); + + if (!g_file_get_contents (security_label_filename, + &security_label_contents, + &security_label_contents_size, + NULL)) + return FALSE; + + if (!g_str_has_prefix (security_label_contents, SNAP_SECURITY_LABEL_PREFIX)) + return FALSE; + + /* We need to translate the security profile into the desktop-id. + * The profile is in the form of 'snap.name-space.binary-name (current)' + * while the desktop id will be name-space_binary-name. + */ + security_label_contents_size -= sizeof (SNAP_SECURITY_LABEL_PREFIX) - 1; + contents_start = security_label_contents + sizeof (SNAP_SECURITY_LABEL_PREFIX) - 1; + contents_end = strchr (contents_start, ' '); + + if (contents_end) + security_label_contents_size = contents_end - contents_start; + + for (i = 0; i < security_label_contents_size; ++i) + { + if (contents_start[i] == '.') + contents_start[i] = '_'; + } + + sandboxed_app_id = g_malloc0 (security_label_contents_size + 1); + memcpy (sandboxed_app_id, contents_start, security_label_contents_size); + + window->sandboxed_app_id = sandboxed_app_id; + + return TRUE; +} + +static void +meta_window_update_sandboxed_app_id (MetaWindow *window) +{ + pid_t pid; + + g_clear_pointer (&window->sandboxed_app_id, g_free); + + pid = meta_window_get_pid (window); + + if (pid < 1) + return; + + if (meta_window_update_flatpak_id (window, pid)) + return; + + if (meta_window_update_snap_id (window, pid)) + return; +} + +static void +meta_window_update_desc (MetaWindow *window) +{ + g_clear_pointer (&window->desc, g_free); + + if (window->client_type == META_WINDOW_CLIENT_TYPE_X11) + window->desc = g_strdup_printf ("0x%lx", window->xwindow); + else + { + guint64 small_stamp = window->stamp - G_GUINT64_CONSTANT(0x100000000); + + window->desc = g_strdup_printf ("W%" G_GUINT64_FORMAT , small_stamp); + } +} + +static void +meta_window_main_monitor_changed (MetaWindow *window, + const MetaLogicalMonitor *old) +{ + META_WINDOW_GET_CLASS (window)->main_monitor_changed (window, old); + + if (old) + g_signal_emit_by_name (window->display, "window-left-monitor", + old->number, window); + if (window->monitor) + g_signal_emit_by_name (window->display, "window-entered-monitor", + window->monitor->number, window); +} + +MetaLogicalMonitor * +meta_window_find_monitor_from_frame_rect (MetaWindow *window) +{ + MetaBackend *backend = backend_from_window (window); + MetaMonitorManager *monitor_manager = + meta_backend_get_monitor_manager (backend); + MetaRectangle window_rect; + + meta_window_get_frame_rect (window, &window_rect); + return meta_monitor_manager_get_logical_monitor_from_rect (monitor_manager, + &window_rect); +} + +static void +meta_window_manage (MetaWindow *window) +{ + COGL_TRACE_BEGIN_SCOPED (MetaWindowManage, + "Window (manage)"); + + META_WINDOW_GET_CLASS (window)->manage (window); +} + +static void +meta_window_constructed (GObject *object) +{ + MetaWindow *window = META_WINDOW (object); + MetaDisplay *display = window->display; + MetaContext *context = meta_display_get_context (display); + MetaBackend *backend = meta_context_get_backend (context); + MetaWorkspaceManager *workspace_manager = display->workspace_manager; + + COGL_TRACE_BEGIN_SCOPED (MetaWindowSharedInit, + "Window (init)"); + + window->constructing = TRUE; + + meta_display_register_stamp (display, &window->stamp, window); + + window->workspace = NULL; + + meta_window_update_sandboxed_app_id (window); + meta_window_update_desc (window); + + /* avoid tons of stack updates */ + meta_stack_freeze (display->stack); + + /* initialize the remaining size_hints as if size_hints.flags were zero */ + meta_set_normal_hints (window, NULL); + + /* And this is our unmaximized size */ + window->saved_rect = window->rect; + window->saved_rect_fullscreen = window->rect; + window->unconstrained_rect = window->rect; + + window->title = NULL; + + window->frame = NULL; + window->has_focus = FALSE; + window->attached_focus_window = NULL; + + window->maximized_horizontally = FALSE; + window->maximized_vertically = FALSE; + window->maximize_horizontally_after_placement = FALSE; + window->maximize_vertically_after_placement = FALSE; + window->minimize_after_placement = FALSE; + window->fullscreen = FALSE; + window->require_fully_onscreen = TRUE; + window->require_on_single_monitor = TRUE; + window->require_titlebar_visible = TRUE; + window->on_all_workspaces = FALSE; + window->on_all_workspaces_requested = FALSE; + window->tile_mode = META_TILE_NONE; + window->tile_monitor_number = -1; + window->tile_hfraction = -1.; + window->initially_iconic = FALSE; + window->minimized = FALSE; + window->tab_unminimized = FALSE; + window->iconic = FALSE; + window->known_to_compositor = FALSE; + window->visible_to_compositor = FALSE; + /* if already mapped, no need to worry about focus-on-first-time-showing */ + window->showing_for_first_time = !window->mapped; + /* if already mapped we don't want to do the placement thing; + * override-redirect windows are placed by the app */ + window->placed = ((window->mapped && !window->hidden) || window->override_redirect); + window->denied_focus_and_not_transient = FALSE; + window->unmanaging = FALSE; + window->keys_grabbed = FALSE; + window->grab_on_frame = FALSE; + window->withdrawn = FALSE; + window->initial_workspace_set = FALSE; + window->initial_timestamp_set = FALSE; + window->net_wm_user_time_set = FALSE; + window->user_time_window = None; + window->input = TRUE; + window->calc_placement = FALSE; + window->have_focus_click_grab = FALSE; + + window->unmaps_pending = 0; + window->reparents_pending = 0; + + window->mwm_decorated = TRUE; + window->mwm_border_only = FALSE; + window->mwm_has_close_func = TRUE; + window->mwm_has_minimize_func = TRUE; + window->mwm_has_maximize_func = TRUE; + window->mwm_has_move_func = TRUE; + window->mwm_has_resize_func = TRUE; + + window->has_close_func = TRUE; + window->has_minimize_func = TRUE; + window->has_maximize_func = TRUE; + window->has_move_func = TRUE; + window->has_resize_func = TRUE; + + window->has_fullscreen_func = TRUE; + + window->always_sticky = FALSE; + + window->skip_taskbar = FALSE; + window->skip_pager = FALSE; + window->skip_from_window_list = FALSE; + window->wm_state_above = FALSE; + window->wm_state_below = FALSE; + window->wm_state_demands_attention = FALSE; + + window->res_class = NULL; + window->res_name = NULL; + window->role = NULL; + window->sm_client_id = NULL; + window->wm_client_machine = NULL; + window->is_remote = FALSE; + window->startup_id = NULL; + + window->client_pid = 0; + + window->has_valid_cgroup = TRUE; + window->cgroup_path = NULL; + + window->xtransient_for = None; + window->xclient_leader = None; + + window->type = META_WINDOW_NORMAL; + + window->struts = NULL; + + window->layer = META_LAYER_LAST; /* invalid value */ + window->stack_position = -1; + window->initial_workspace = 0; /* not used */ + window->initial_timestamp = 0; /* not used */ + + window->compositor_private = NULL; + + if (window->rect.width > 0 && window->rect.height > 0) + window->monitor = meta_window_find_monitor_from_frame_rect (window); + else + window->monitor = meta_backend_get_current_logical_monitor (backend); + + if (window->monitor) + window->preferred_output_winsys_id = window->monitor->winsys_id; + else + window->preferred_output_winsys_id = UINT_MAX; + + window->tile_match = NULL; + + /* Assign this #MetaWindow a sequence number which can be used + * for sorting. + */ + window->stable_sequence = ++display->window_sequence_counter; + + window->opacity = 0xFF; + + if (window->override_redirect) + { + window->decorated = FALSE; + window->always_sticky = TRUE; + window->has_close_func = FALSE; + window->has_move_func = FALSE; + window->has_resize_func = FALSE; + } + + window->id = meta_display_generate_window_id (display); + + meta_window_manage (window); + + if (window->initially_iconic) + { + /* WM_HINTS said minimized */ + window->minimized = TRUE; + meta_verbose ("Window %s asked to start out minimized", window->desc); + } + + /* Apply any window attributes such as initial workspace + * based on startup notification + */ + meta_display_apply_startup_properties (display, window); + + /* Try to get a "launch timestamp" for the window. If the window is + * a transient, we'd like to be able to get a last-usage timestamp + * from the parent window. If the window has no parent, there isn't + * much we can do...except record the current time so that any children + * can use this time as a fallback. + */ + if (!window->override_redirect && !window->net_wm_user_time_set) { + /* First, maybe the app was launched with startup notification using an + * obsolete version of the spec; use that timestamp if it exists. + */ + if (window->initial_timestamp_set) + /* NOTE: Do NOT toggle net_wm_user_time_set to true; this is just + * being recorded as a fallback for potential transients + */ + window->net_wm_user_time = window->initial_timestamp; + else if (window->transient_for != NULL) + meta_window_set_user_time (window, window->transient_for->net_wm_user_time); + else + /* NOTE: Do NOT toggle net_wm_user_time_set to true; this is just + * being recorded as a fallback for potential transients + */ + window->net_wm_user_time = + meta_display_get_current_time_roundtrip (display); + } + + window->attached = meta_window_should_attach_to_parent (window); + if (window->attached) + meta_window_recalc_features (window); + + if (window->type == META_WINDOW_DESKTOP || + window->type == META_WINDOW_DOCK) + { + /* Change the default, but don't enforce this if the user + * focuses the dock/desktop and unsticks it using key shortcuts. + * Need to set this before adding to the workspaces so the MRU + * lists will be updated. + */ + window->on_all_workspaces_requested = TRUE; + } + + window->on_all_workspaces = should_be_on_all_workspaces (window); + + /* For the workspace, first honor hints, + * if that fails put transients with parents, + * otherwise put window on active space + */ + + if (window->initial_workspace_set) + { + gboolean on_all_workspaces = window->on_all_workspaces; + MetaWorkspace *workspace = NULL; + + if (window->initial_workspace == (int) 0xFFFFFFFF) + { + meta_topic (META_DEBUG_PLACEMENT, + "Window %s is initially on all spaces", + window->desc); + + /* need to set on_all_workspaces first so that it will be + * added to all the MRU lists + */ + window->on_all_workspaces_requested = TRUE; + + on_all_workspaces = TRUE; + } + else if (!on_all_workspaces) + { + meta_topic (META_DEBUG_PLACEMENT, + "Window %s is initially on space %d", + window->desc, window->initial_workspace); + + workspace = meta_workspace_manager_get_workspace_by_index (workspace_manager, + window->initial_workspace); + } + + /* Ignore when a window requests to be placed on a non-existent workspace + */ + if (on_all_workspaces || workspace != NULL) + set_workspace_state (window, on_all_workspaces, workspace); + } + + /* override-redirect windows are subtly different from other windows + * with window->on_all_workspaces == TRUE. Other windows are part of + * some workspace (so they can return to that if the flag is turned off), + * but appear on other workspaces. override-redirect windows are part + * of no workspace. + */ + if (!window->override_redirect && window->workspace == NULL) + { + if (window->transient_for != NULL) + { + meta_topic (META_DEBUG_PLACEMENT, + "Putting window %s on same workspace as parent %s", + window->desc, window->transient_for->desc); + + g_warn_if_fail (!window->transient_for->override_redirect); + set_workspace_state (window, + window->transient_for->on_all_workspaces, + window->transient_for->workspace); + } + else if (window->on_all_workspaces) + { + meta_topic (META_DEBUG_PLACEMENT, + "Putting window %s on all workspaces", + window->desc); + + set_workspace_state (window, TRUE, NULL); + } + else + { + meta_topic (META_DEBUG_PLACEMENT, + "Putting window %s on active workspace", + window->desc); + + set_workspace_state (window, FALSE, workspace_manager->active_workspace); + } + + meta_window_update_struts (window); + } + + meta_window_main_monitor_changed (window, NULL); + + /* Must add window to stack before doing move/resize, since the + * window might have fullscreen size (i.e. should have been + * fullscreen'd; acrobat is one such braindead case; it withdraws + * and remaps its window whenever trying to become fullscreen...) + * and thus constraints may try to auto-fullscreen it which also + * means restacking it. + */ + if (meta_window_is_stackable (window)) + meta_stack_add (display->stack, window); + else if (window->override_redirect) + window->layer = META_LAYER_OVERRIDE_REDIRECT; /* otherwise set by MetaStack */ + + if (!window->override_redirect) + { + /* FIXME we have a tendency to set this then immediately + * change it again. + */ + set_wm_state (window); + set_net_wm_state (window); + } + + meta_compositor_add_window (display->compositor, window); + window->known_to_compositor = TRUE; + + /* Sync stack changes */ + meta_stack_thaw (display->stack); + + /* Usually the we'll have queued a stack sync anyways, because we've + * added a new frame window or restacked. But if an undecorated + * window is mapped, already stacked in the right place, then we + * might need to do this explicitly. + */ + meta_stack_tracker_queue_sync_stack (display->stack_tracker); + + /* disable show desktop mode unless we're a desktop component */ + maybe_leave_show_desktop_mode (window); + + meta_window_queue (window, META_QUEUE_CALC_SHOWING); + /* See bug 303284; a transient of the given window can already exist, in which + * case we think it should probably be shown. + */ + meta_window_foreach_transient (window, + queue_calc_showing_func, + NULL); + /* See bug 334899; the window may have minimized ancestors + * which need to be shown. + * + * However, we shouldn't unminimize windows here when opening + * a new display because that breaks passing _NET_WM_STATE_HIDDEN + * between window managers when replacing them; see bug 358042. + * + * And we shouldn't unminimize windows if they were initially + * iconic. + */ + if (!window->override_redirect && + !display->display_opening && + !window->initially_iconic) + unminimize_window_and_all_transient_parents (window); + + window->constructing = FALSE; +} + +static gboolean +meta_window_initable_init (GInitable *initable, + GCancellable *cancellable, + GError **error) +{ + MetaWindow *window = META_WINDOW (initable); + MetaDisplay *display = window->display; + + meta_display_notify_window_created (display, window); + + if (window->wm_state_demands_attention) + g_signal_emit_by_name (display, "window-demands-attention", window); + + return TRUE; +} + +static void +initable_iface_init (GInitableIface *initable_iface) +{ + initable_iface->init = meta_window_initable_init; +} + +static gboolean +detach_foreach_func (MetaWindow *window, + void *data) +{ + GList **children = data; + MetaWindow *parent; + + if (window->attached) + { + /* Only return the immediate children of the window being unmanaged */ + parent = meta_window_get_transient_for (window); + if (parent->unmanaging) + *children = g_list_prepend (*children, window); + } + + return TRUE; +} + +void +meta_window_unmanage (MetaWindow *window, + guint32 timestamp) +{ + MetaWorkspaceManager *workspace_manager = window->display->workspace_manager; + GList *tmp; + + meta_verbose ("Unmanaging %s", window->desc); + window->unmanaging = TRUE; + + g_clear_handle_id (&window->unmanage_idle_id, g_source_remove); + g_clear_handle_id (&window->close_dialog_timeout_id, g_source_remove); + + g_signal_emit (window, window_signals[UNMANAGING], 0); + + meta_window_free_delete_dialog (window); + + if (window->visible_to_compositor) + { + window->visible_to_compositor = FALSE; + meta_compositor_hide_window (window->display->compositor, window, + META_COMP_EFFECT_DESTROY); + } + + meta_compositor_remove_window (window->display->compositor, window); + window->known_to_compositor = FALSE; + + meta_display_unregister_stamp (window->display, window->stamp); + + if (meta_prefs_get_attach_modal_dialogs ()) + { + GList *attached_children = NULL, *iter; + + /* Detach any attached dialogs by unmapping and letting them + * be remapped after @window is destroyed. + */ + meta_window_foreach_transient (window, + detach_foreach_func, + &attached_children); + for (iter = attached_children; iter; iter = iter->next) + meta_window_unmanage (iter->data, timestamp); + g_list_free (attached_children); + } + + /* Make sure to only show window on all workspaces if requested, to + * not confuse other window managers that may take over + */ + if (meta_prefs_get_workspaces_only_on_primary ()) + meta_window_on_all_workspaces_changed (window); + + if (window->fullscreen) + { + MetaGroup *group; + + /* If the window is fullscreen, it may be forcing + * other windows in its group to a higher layer + */ + + meta_stack_freeze (window->display->stack); + group = meta_window_get_group (window); + if (group) + meta_group_update_layers (group); + meta_stack_thaw (window->display->stack); + } + + meta_display_remove_pending_pings_for_window (window->display, window); + + /* safe to do this early as group.c won't re-add to the + * group if window->unmanaging */ + meta_window_shutdown_group (window); + + /* If we have the focus, focus some other window. + * This is done first, so that if the unmap causes + * an EnterNotify the EnterNotify will have final say + * on what gets focused, maintaining sloppy focus + * invariants. + */ + if (window->appears_focused) + meta_window_propagate_focus_appearance (window, FALSE); + if (window->has_focus) + { + meta_topic (META_DEBUG_FOCUS, + "Focusing default window since we're unmanaging %s", + window->desc); + meta_workspace_focus_default_window (workspace_manager->active_workspace, + window, + timestamp); + } + else + { + meta_topic (META_DEBUG_FOCUS, + "Unmanaging window %s which doesn't currently have focus", + window->desc); + } + + g_assert (window->display->focus_window != window); + + if (window->struts) + { + g_slist_free_full (window->struts, g_free); + window->struts = NULL; + + meta_topic (META_DEBUG_WORKAREA, + "Unmanaging window %s which has struts, so invalidating work areas", + window->desc); + invalidate_work_areas (window); + } + + if (window->maximized_horizontally || window->maximized_vertically) + unmaximize_window_before_freeing (window); + + meta_window_unqueue (window, + (META_QUEUE_CALC_SHOWING | + META_QUEUE_MOVE_RESIZE)); + + set_workspace_state (window, FALSE, NULL); + + g_assert (window->workspace == NULL); + +#ifndef G_DISABLE_CHECKS + tmp = workspace_manager->workspaces; + while (tmp != NULL) + { + MetaWorkspace *workspace = tmp->data; + + g_assert (g_list_find (workspace->windows, window) == NULL); + g_assert (g_list_find (workspace->mru_list, window) == NULL); + + tmp = tmp->next; + } +#endif + + if (window->monitor) + { + const MetaLogicalMonitor *old = window->monitor; + + window->monitor = NULL; + meta_window_main_monitor_changed (window, old); + } + + if (meta_window_is_in_stack (window)) + meta_stack_remove (window->display->stack, window); + + /* If an undecorated window is being withdrawn, that will change the + * stack as presented to the compositing manager, without actually + * changing the stacking order of X windows. + */ + meta_stack_tracker_queue_sync_stack (window->display->stack_tracker); + + if (window->display->autoraise_window == window) + meta_display_remove_autoraise_callback (window->display); + + META_WINDOW_GET_CLASS (window)->unmanage (window); + + meta_prefs_remove_listener (prefs_changed_callback, window); + meta_display_queue_check_fullscreen (window->display); + + g_signal_emit (window, window_signals[UNMANAGED], 0); + + g_object_unref (window); +} + +static void +set_wm_state (MetaWindow *window) +{ + if (window->client_type == META_WINDOW_CLIENT_TYPE_X11) + meta_window_x11_set_wm_state (window); +} + +static void +set_net_wm_state (MetaWindow *window) +{ + if (window->client_type == META_WINDOW_CLIENT_TYPE_X11) + meta_window_x11_set_net_wm_state (window); +} + +static void +set_allowed_actions_hint (MetaWindow *window) +{ + if (window->client_type == META_WINDOW_CLIENT_TYPE_X11) + meta_window_x11_set_allowed_actions_hint (window); +} + +/** + * meta_window_located_on_workspace: + * @window: a #MetaWindow + * @workspace: a #MetaWorkspace + * + * Returns: whether @window is displayed on @workspace, or whether it + * will be displayed on all workspaces. + */ +gboolean +meta_window_located_on_workspace (MetaWindow *window, + MetaWorkspace *workspace) +{ + return (window->on_all_workspaces) || (window->workspace == workspace); +} + +static gboolean +is_minimized_foreach (MetaWindow *window, + void *data) +{ + gboolean *result = data; + + *result = window->minimized; + if (*result) + return FALSE; /* stop as soon as we find one */ + else + return TRUE; +} + +static gboolean +ancestor_is_minimized (MetaWindow *window) +{ + gboolean is_minimized; + + is_minimized = FALSE; + + meta_window_foreach_ancestor (window, is_minimized_foreach, &is_minimized); + + return is_minimized; +} + +/** + * meta_window_showing_on_its_workspace: + * @window: A #MetaWindow + * + * Returns: %TRUE if window would be visible, if its workspace was current + */ +gboolean +meta_window_showing_on_its_workspace (MetaWindow *window) +{ + gboolean showing; + gboolean is_desktop_or_dock; + MetaWorkspace* workspace_of_window; + + showing = TRUE; + + /* 1. See if we're minimized */ + if (window->minimized) + showing = FALSE; + + /* 2. See if we're in "show desktop" mode */ + is_desktop_or_dock = FALSE; + is_desktop_or_dock_foreach (window, + &is_desktop_or_dock); + + meta_window_foreach_ancestor (window, is_desktop_or_dock_foreach, + &is_desktop_or_dock); + + workspace_of_window = meta_window_get_workspace (window); + + if (showing && + workspace_of_window && workspace_of_window->showing_desktop && + !is_desktop_or_dock) + { + meta_verbose ("We're showing the desktop on the workspace(s) that window %s is on", + window->desc); + showing = FALSE; + } + + /* 3. See if an ancestor is minimized (note that + * ancestor's "mapped" field may not be up to date + * since it's being computed in this same idle queue) + */ + + if (showing) + { + if (ancestor_is_minimized (window)) + showing = FALSE; + } + + return showing; +} + +static gboolean +window_has_buffer (MetaWindow *window) +{ +#ifdef HAVE_WAYLAND + if (meta_is_wayland_compositor ()) + { + MetaWaylandSurface *surface = meta_window_get_wayland_surface (window); + if (!surface || !meta_wayland_surface_get_buffer (surface)) + return FALSE; + } +#endif + + return TRUE; +} + +gboolean +meta_window_should_be_showing_on_workspace (MetaWindow *window, + MetaWorkspace *workspace) +{ + if (!window_has_buffer (window)) + return FALSE; + + if (window->client_type == META_WINDOW_CLIENT_TYPE_X11 && + window->decorated && !window->frame) + return FALSE; + + /* Windows should be showing if they're located on the + * workspace and they're showing on their own workspace. */ + return (meta_window_located_on_workspace (window, workspace) && + meta_window_showing_on_its_workspace (window)); +} + +gboolean +meta_window_should_be_showing (MetaWindow *window) +{ + MetaWorkspaceManager *workspace_manager = window->display->workspace_manager; + MetaWorkspace *active_workspace = workspace_manager->active_workspace; + + return meta_window_should_be_showing_on_workspace (window, active_workspace); +} + +static void +implement_showing (MetaWindow *window, + gboolean showing) +{ + /* Actually show/hide the window */ + meta_verbose ("Implement showing = %d for window %s", + showing, window->desc); + + /* Some windows are not stackable until being showed, so add those now. */ + if (meta_window_is_stackable (window) && !meta_window_is_in_stack (window)) + meta_stack_add (window->display->stack, window); + + if (!showing) + { + /* When we manage a new window, we normally delay placing it + * until it is is first shown, but if we're previewing hidden + * windows we might want to know where they are on the screen, + * so we should place the window even if we're hiding it rather + * than showing it. + * Force placing windows only when they should be already mapped, + * see #751887 + */ + if (!window->placed && window_has_buffer (window)) + meta_window_force_placement (window, FALSE); + + meta_window_hide (window); + + if (!window->override_redirect) + sync_client_window_mapped (window); + } + else + { + if (!window->override_redirect) + sync_client_window_mapped (window); + + meta_window_show (window); + } +} + +void +meta_window_update_visibility (MetaWindow *window) +{ + implement_showing (window, meta_window_should_be_showing (window)); +} + +void +meta_window_clear_queued (MetaWindow *window) +{ + MetaWindowPrivate *priv = meta_window_get_instance_private (window); + + priv->queued_types &= ~META_QUEUE_CALC_SHOWING; +} + +static void +meta_window_unqueue (MetaWindow *window, + MetaQueueType queue_types) +{ + MetaWindowPrivate *priv = meta_window_get_instance_private (window); + + queue_types &= priv->queued_types; + + if (!queue_types) + return; + + meta_display_unqueue_window (window->display, window, queue_types); + priv->queued_types &= ~queue_types; +} + +static void +meta_window_flush_calc_showing (MetaWindow *window) +{ + MetaWindowPrivate *priv = meta_window_get_instance_private (window); + + if (!(priv->queued_types & META_QUEUE_CALC_SHOWING)) + return; + + meta_display_flush_queued_window (window->display, window, + META_QUEUE_CALC_SHOWING); + + priv->queued_types &= ~META_QUEUE_CALC_SHOWING; +} + +void +meta_window_queue (MetaWindow *window, + MetaQueueType queue_types) +{ + MetaWindowPrivate *priv = meta_window_get_instance_private (window); + + g_return_if_fail (!window->override_redirect || + (queue_types & META_QUEUE_MOVE_RESIZE) == 0); + + if (window->unmanaging) + return; + + queue_types &= ~priv->queued_types; + if (!queue_types) + return; + + priv->queued_types |= queue_types; + meta_display_queue_window (window->display, window, queue_types); +} + +static gboolean +intervening_user_event_occurred (MetaWindow *window) +{ + guint32 compare; + MetaWindow *focus_window; + + focus_window = window->display->focus_window; + + meta_topic (META_DEBUG_STARTUP, + "COMPARISON:\n" + " net_wm_user_time_set : %d\n" + " net_wm_user_time : %u\n" + " initial_timestamp_set: %d\n" + " initial_timestamp : %u", + window->net_wm_user_time_set, + window->net_wm_user_time, + window->initial_timestamp_set, + window->initial_timestamp); + if (focus_window != NULL) + { + meta_topic (META_DEBUG_STARTUP, + "COMPARISON (continued):\n" + " focus_window : %s\n" + " fw->net_wm_user_time_set : %d\n" + " fw->net_wm_user_time : %u", + focus_window->desc, + focus_window->net_wm_user_time_set, + focus_window->net_wm_user_time); + } + + /* We expect the most common case for not focusing a new window + * to be when a hint to not focus it has been set. Since we can + * deal with that case rapidly, we use special case it--this is + * merely a preliminary optimization. :) + */ + if ( ((window->net_wm_user_time_set == TRUE) && + (window->net_wm_user_time == 0)) + || + ((window->initial_timestamp_set == TRUE) && + (window->initial_timestamp == 0))) + { + meta_topic (META_DEBUG_STARTUP, + "window %s explicitly requested no focus", + window->desc); + return TRUE; + } + + if (!(window->net_wm_user_time_set) && !(window->initial_timestamp_set)) + { + meta_topic (META_DEBUG_STARTUP, + "no information about window %s found", + window->desc); + return FALSE; + } + + if (focus_window != NULL && + !focus_window->net_wm_user_time_set) + { + meta_topic (META_DEBUG_STARTUP, + "focus window, %s, doesn't have a user time set yet!", + window->desc); + return FALSE; + } + + /* To determine the "launch" time of an application, + * startup-notification can set the TIMESTAMP and the + * application (usually via its toolkit such as gtk or qt) can + * set the _NET_WM_USER_TIME. If both are set, we need to be + * using the newer of the two values. + * + * See http://bugzilla.gnome.org/show_bug.cgi?id=573922 + */ + compare = 0; + if (window->net_wm_user_time_set && + window->initial_timestamp_set) + compare = + XSERVER_TIME_IS_BEFORE (window->net_wm_user_time, + window->initial_timestamp) ? + window->initial_timestamp : window->net_wm_user_time; + else if (window->net_wm_user_time_set) + compare = window->net_wm_user_time; + else if (window->initial_timestamp_set) + compare = window->initial_timestamp; + + if ((focus_window != NULL) && + XSERVER_TIME_IS_BEFORE (compare, focus_window->net_wm_user_time)) + { + meta_topic (META_DEBUG_STARTUP, + "window %s focus prevented by other activity; %u < %u", + window->desc, + compare, + focus_window->net_wm_user_time); + return TRUE; + } + else + { + meta_topic (META_DEBUG_STARTUP, + "new window %s with no intervening events", + window->desc); + return FALSE; + } +} + +/* This function is an ugly hack. It's experimental in nature and ought to be + * replaced by a real hint from the app to the WM if we decide the experimental + * behavior is worthwhile. The basic idea is to get more feedback about how + * usage scenarios of "strict" focus users and what they expect. See #326159. + */ +static gboolean +window_is_terminal (MetaWindow *window) +{ + if (window == NULL || window->res_class == NULL) + return FALSE; + + /* + * Compare res_class, which is not user-settable, and thus theoretically + * a more-reliable indication of term-ness. + */ + + /* gnome-terminal -- if you couldn't guess */ + if (strcmp (window->res_class, "Gnome-terminal") == 0) + return TRUE; + /* xterm, rxvt, aterm */ + else if (strcmp (window->res_class, "XTerm") == 0) + return TRUE; + /* konsole, KDE's terminal program */ + else if (strcmp (window->res_class, "Konsole") == 0) + return TRUE; + /* rxvt-unicode */ + else if (strcmp (window->res_class, "URxvt") == 0) + return TRUE; + /* eterm */ + else if (strcmp (window->res_class, "Eterm") == 0) + return TRUE; + /* KTerm -- some terminal not KDE based; so not like Konsole */ + else if (strcmp (window->res_class, "KTerm") == 0) + return TRUE; + /* Multi-gnome-terminal */ + else if (strcmp (window->res_class, "Multi-gnome-terminal") == 0) + return TRUE; + /* mlterm ("multi lingual terminal emulator on X") */ + else if (strcmp (window->res_class, "mlterm") == 0) + return TRUE; + /* Terminal -- XFCE Terminal */ + else if (strcmp (window->res_class, "Terminal") == 0) + return TRUE; + + return FALSE; +} + +/* This function determines what state the window should have assuming that it + * and the focus_window have no relation + */ +static void +window_state_on_map (MetaWindow *window, + gboolean *takes_focus, + gboolean *places_on_top) +{ + gboolean intervening_events; + + intervening_events = intervening_user_event_occurred (window); + + *takes_focus = !intervening_events; + *places_on_top = *takes_focus; + + /* don't initially focus windows that are intended to not accept + * focus + */ + if (!meta_window_is_focusable (window)) + { + *takes_focus = FALSE; + return; + } + + /* Do not focus window on map if input is already taken by the + * compositor. + */ + if (!meta_display_windows_are_interactable (window->display)) + { + *takes_focus = FALSE; + return; + } + + /* Terminal usage may be different; some users intend to launch + * many apps in quick succession or to just view things in the new + * window while still interacting with the terminal. In that case, + * apps launched from the terminal should not take focus. This + * isn't quite the same as not allowing focus to transfer from + * terminals due to new window map, but the latter is a much easier + * approximation to enforce so we do that. + */ + if (*takes_focus && + meta_prefs_get_focus_new_windows () == G_DESKTOP_FOCUS_NEW_WINDOWS_STRICT && + !window->display->allow_terminal_deactivation && + window_is_terminal (window->display->focus_window) && + !meta_window_is_ancestor_of_transient (window->display->focus_window, + window)) + { + meta_topic (META_DEBUG_FOCUS, + "focus_window is terminal; not focusing new window."); + *takes_focus = FALSE; + *places_on_top = FALSE; + } + + switch (window->type) + { + case META_WINDOW_UTILITY: + case META_WINDOW_TOOLBAR: + *takes_focus = FALSE; + *places_on_top = FALSE; + break; + case META_WINDOW_DOCK: + case META_WINDOW_DESKTOP: + case META_WINDOW_SPLASHSCREEN: + case META_WINDOW_MENU: + /* override redirect types: */ + case META_WINDOW_DROPDOWN_MENU: + case META_WINDOW_POPUP_MENU: + case META_WINDOW_TOOLTIP: + case META_WINDOW_NOTIFICATION: + case META_WINDOW_COMBO: + case META_WINDOW_DND: + case META_WINDOW_OVERRIDE_OTHER: + /* don't focus any of these; places_on_top may be irrelevant for some of + * these (e.g. dock)--but you never know--the focus window might also be + * of the same type in some weird situation... + */ + *takes_focus = FALSE; + break; + case META_WINDOW_NORMAL: + case META_WINDOW_DIALOG: + case META_WINDOW_MODAL_DIALOG: + /* The default is correct for these */ + break; + } +} + +static gboolean +windows_overlap (const MetaWindow *w1, const MetaWindow *w2) +{ + MetaRectangle w1rect, w2rect; + meta_window_get_frame_rect (w1, &w1rect); + meta_window_get_frame_rect (w2, &w2rect); + return meta_rectangle_overlap (&w1rect, &w2rect); +} + +/* Returns whether a new window would be covered by any + * existing window on the same workspace that is set + * to be "above" ("always on top"). A window that is not + * set "above" would be underneath the new window anyway. + * + * We take "covered" to mean even partially covered, but + * some people might prefer entirely covered. I think it + * is more useful to behave this way if any part of the + * window is covered, because a partial coverage could be + * (say) ninety per cent and almost indistinguishable from total. + */ +static gboolean +window_would_be_covered (const MetaWindow *newbie) +{ + MetaWorkspace *workspace = meta_window_get_workspace ((MetaWindow *)newbie); + GList *tmp, *windows; + + windows = meta_workspace_list_windows (workspace); + + tmp = windows; + while (tmp != NULL) + { + MetaWindow *w = tmp->data; + + if (w->wm_state_above && w != newbie) + { + /* We have found a window that is "above". Perhaps it overlaps. */ + if (windows_overlap (w, newbie)) + { + g_list_free (windows); /* clean up... */ + return TRUE; /* yes, it does */ + } + } + + tmp = tmp->next; + } + + g_list_free (windows); + return FALSE; /* none found */ +} + +void +meta_window_force_placement (MetaWindow *window, + gboolean force_move) +{ + MetaMoveResizeFlags flags; + + if (window->placed) + return; + + /* We have to recalc the placement here since other windows may + * have been mapped/placed since we last did constrain_position + */ + + /* calc_placement is an efficiency hack to avoid + * multiple placement calculations before we finally + * show the window. + */ + window->calc_placement = TRUE; + + flags = (META_MOVE_RESIZE_MOVE_ACTION | + META_MOVE_RESIZE_RESIZE_ACTION | + META_MOVE_RESIZE_CONSTRAIN); + if (force_move) + flags |= META_MOVE_RESIZE_FORCE_MOVE; + + meta_window_move_resize_internal (window, + flags, + META_GRAVITY_NORTH_WEST, + window->unconstrained_rect); + window->calc_placement = FALSE; + + /* don't ever do the initial position constraint thing again. + * This is toggled here so that initially-iconified windows + * still get placed when they are ultimately shown. + */ + window->placed = TRUE; + + /* Don't want to accidentally reuse the fact that we had been denied + * focus in any future constraints unless we're denied focus again. + */ + window->denied_focus_and_not_transient = FALSE; +} + +static void +meta_window_show (MetaWindow *window) +{ + gboolean did_show; + gboolean takes_focus_on_map; + gboolean place_on_top_on_map; + gboolean needs_stacking_adjustment; + MetaWindow *focus_window; + gboolean notify_demands_attention = FALSE; + MetaDisplay *display = window->display; + + meta_topic (META_DEBUG_WINDOW_STATE, + "Showing window %s, iconic: %d placed: %d", + window->desc, window->iconic, window->placed); + + focus_window = window->display->focus_window; /* May be NULL! */ + did_show = FALSE; + window_state_on_map (window, &takes_focus_on_map, &place_on_top_on_map); + needs_stacking_adjustment = FALSE; + + meta_topic (META_DEBUG_WINDOW_STATE, + "Window %s %s focus on map, and %s place on top on map.", + window->desc, + takes_focus_on_map ? "does" : "does not", + place_on_top_on_map ? "does" : "does not"); + + /* Now, in some rare cases we should *not* put a new window on top. + * These cases include certain types of windows showing for the first + * time, and any window which would be covered because of another window + * being set "above" ("always on top"). + * + * FIXME: Although "place_on_top_on_map" and "takes_focus_on_map" are + * generally based on the window type, there is a special case when the + * focus window is a terminal for them both to be false; this should + * probably rather be a term in the "if" condition below. + */ + + if ( focus_window != NULL && window->showing_for_first_time && + ( (!place_on_top_on_map && !takes_focus_on_map) || + window_would_be_covered (window) ) + ) { + if (!meta_window_is_ancestor_of_transient (focus_window, window)) + { + needs_stacking_adjustment = TRUE; + if (!window->placed) + window->denied_focus_and_not_transient = TRUE; + } + } + + if (!window->placed) + { + if (window->monitor && + meta_prefs_get_auto_maximize() && + window->showing_for_first_time && + window->has_maximize_func) + { + MetaRectangle work_area; + meta_window_get_work_area_for_monitor (window, window->monitor->number, &work_area); + /* Automaximize windows that map with a size > MAX_UNMAXIMIZED_WINDOW_AREA of the work area */ + if (window->rect.width * window->rect.height > work_area.width * work_area.height * MAX_UNMAXIMIZED_WINDOW_AREA) + { + window->maximize_horizontally_after_placement = TRUE; + window->maximize_vertically_after_placement = TRUE; + } + } + meta_window_force_placement (window, FALSE); + } + + if (needs_stacking_adjustment) + { + gboolean overlap; + + /* This window isn't getting focus on map. We may need to do some + * special handing with it in regards to + * - the stacking of the window + * - the MRU position of the window + * - the demands attention setting of the window + * + * Firstly, set the flag so we don't give the window focus anyway + * and confuse people. + */ + + takes_focus_on_map = FALSE; + + overlap = windows_overlap (window, focus_window); + + /* We want alt tab to go to the denied-focus window */ + ensure_mru_position_after (window, focus_window); + + /* We don't want the denied-focus window to obscure the focus + * window, and if we're in both click-to-focus mode and + * raise-on-click mode then we want to maintain the invariant + * that MRU order == stacking order. The need for this if + * comes from the fact that in sloppy/mouse focus the focus + * window may not overlap other windows and also can be + * considered "below" them; this combination means that + * placing the denied-focus window "below" the focus window + * in the stack when it doesn't overlap it confusingly places + * that new window below a lot of other windows. + */ + if (overlap || + (meta_prefs_get_focus_mode () == G_DESKTOP_FOCUS_MODE_CLICK && + meta_prefs_get_raise_on_click ())) + meta_window_stack_just_below (window, focus_window); + + /* If the window will be obscured by the focus window, then the + * user might not notice the window appearing so set the + * demands attention hint. + * + * We set the hint ourselves rather than calling + * meta_window_set_demands_attention() because that would cause + * a recalculation of overlap, and a call to set_net_wm_state() + * which we are going to call ourselves here a few lines down. + */ + if (overlap) + { + if (!window->wm_state_demands_attention) + { + window->wm_state_demands_attention = TRUE; + notify_demands_attention = TRUE; + } + } + } + + if (window->hidden) + { + meta_stack_freeze (window->display->stack); + window->hidden = FALSE; + meta_stack_thaw (window->display->stack); + did_show = TRUE; + } + + if (window->iconic) + { + window->iconic = FALSE; + set_wm_state (window); + } + + if (!window->visible_to_compositor) + { + MetaCompEffect effect = META_COMP_EFFECT_NONE; + + window->visible_to_compositor = TRUE; + + switch (window->pending_compositor_effect) + { + case META_COMP_EFFECT_CREATE: + case META_COMP_EFFECT_UNMINIMIZE: + effect = window->pending_compositor_effect; + break; + case META_COMP_EFFECT_NONE: + case META_COMP_EFFECT_DESTROY: + case META_COMP_EFFECT_MINIMIZE: + break; + } + + meta_compositor_show_window (window->display->compositor, + window, effect); + window->pending_compositor_effect = META_COMP_EFFECT_NONE; + } + + /* We don't want to worry about all cases from inside + * implement_showing(); we only want to worry about focus if this + * window has not been shown before. + */ + if (window->showing_for_first_time) + { + window->showing_for_first_time = FALSE; + if (takes_focus_on_map) + { + guint32 timestamp; + + timestamp = meta_display_get_current_time_roundtrip (window->display); + + meta_window_focus (window, timestamp); + } + else if (display->x11_display) + { + /* Prevent EnterNotify events in sloppy/mouse focus from + * erroneously focusing the window that had been denied + * focus. FIXME: This introduces a race; I have a couple + * ideas for a better way to accomplish the same thing, but + * they're more involved so do it this way for now. + */ + meta_x11_display_increment_focus_sentinel (display->x11_display); + } + } + + set_net_wm_state (window); + + if (did_show && window->struts) + { + meta_topic (META_DEBUG_WORKAREA, + "Mapped window %s with struts, so invalidating work areas", + window->desc); + invalidate_work_areas (window); + } + + if (did_show) + meta_display_queue_check_fullscreen (window->display); + + /* + * Now that we have shown the window, we no longer want to consider the + * initial timestamp in any subsequent deliberations whether to focus this + * window or not, so clear the flag. + * + * See http://bugzilla.gnome.org/show_bug.cgi?id=573922 + */ + window->initial_timestamp_set = FALSE; + + if (notify_demands_attention) + { + g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_DEMANDS_ATTENTION]); + g_signal_emit_by_name (window->display, "window-demands-attention", + window); + } + + if (did_show) + g_signal_emit (window, window_signals[SHOWN], 0); +} + +static void +meta_window_hide (MetaWindow *window) +{ + MetaWorkspaceManager *workspace_manager = window->display->workspace_manager; + gboolean did_hide; + + meta_topic (META_DEBUG_WINDOW_STATE, + "Hiding window %s", window->desc); + + if (window->visible_to_compositor) + { + MetaCompEffect effect = META_COMP_EFFECT_NONE; + + window->visible_to_compositor = FALSE; + + switch (window->pending_compositor_effect) + { + case META_COMP_EFFECT_CREATE: + case META_COMP_EFFECT_UNMINIMIZE: + case META_COMP_EFFECT_NONE: + break; + case META_COMP_EFFECT_DESTROY: + case META_COMP_EFFECT_MINIMIZE: + effect = window->pending_compositor_effect; + break; + } + + meta_compositor_hide_window (window->display->compositor, window, effect); + window->pending_compositor_effect = META_COMP_EFFECT_NONE; + } + + did_hide = FALSE; + + if (!window->hidden) + { + meta_stack_freeze (window->display->stack); + window->hidden = TRUE; + meta_stack_thaw (window->display->stack); + + did_hide = TRUE; + } + + if (!window->iconic) + { + window->iconic = TRUE; + set_wm_state (window); + } + + set_net_wm_state (window); + + if (did_hide && window->struts) + { + meta_topic (META_DEBUG_WORKAREA, + "Unmapped window %s with struts, so invalidating work areas", + window->desc); + invalidate_work_areas (window); + } + + if (window->has_focus) + { + MetaWindow *not_this_one = NULL; + MetaWorkspace *my_workspace = meta_window_get_workspace (window); + guint32 timestamp = meta_display_get_current_time_roundtrip (window->display); + + /* + * If this window is modal, passing the not_this_one window to + * _focus_default_window() makes the focus to be given to this window's + * ancestor. This can only be the case if the window is on the currently + * active workspace; when it is not, we need to pass in NULL, so as to + * focus the default window for the active workspace (this scenario + * arises when we are switching workspaces). + * We also pass in NULL if we are in the process of hiding all non-desktop + * windows to avoid unexpected changes to the stacking order. + */ + if (my_workspace == workspace_manager->active_workspace && + !my_workspace->showing_desktop) + not_this_one = window; + + meta_workspace_focus_default_window (workspace_manager->active_workspace, + not_this_one, + timestamp); + } + + if (did_hide) + meta_display_queue_check_fullscreen (window->display); +} + +static gboolean +queue_calc_showing_func (MetaWindow *window, + void *data) +{ + meta_window_queue(window, META_QUEUE_CALC_SHOWING); + return TRUE; +} + +void +meta_window_minimize (MetaWindow *window) +{ + g_return_if_fail (META_IS_WINDOW (window)); + g_return_if_fail (!window->override_redirect); + + if (!window->has_minimize_func) + { + g_warning ("Window %s cannot be minimized, but something tried " + "anyways. Not having it!", window->desc); + return; + } + + if (!window->minimized) + { + window->minimized = TRUE; + window->pending_compositor_effect = META_COMP_EFFECT_MINIMIZE; + meta_window_queue(window, META_QUEUE_CALC_SHOWING); + + meta_window_foreach_transient (window, + queue_calc_showing_func, + NULL); + + if (window->has_focus) + { + meta_topic (META_DEBUG_FOCUS, + "Focusing default window due to minimization of focus window %s", + window->desc); + } + else + { + meta_topic (META_DEBUG_FOCUS, + "Minimizing window %s which doesn't have the focus", + window->desc); + } + + g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_MINIMIZED]); + } +} + +void +meta_window_unminimize (MetaWindow *window) +{ + g_return_if_fail (!window->override_redirect); + + if (window->minimized) + { + window->minimized = FALSE; + window->pending_compositor_effect = META_COMP_EFFECT_UNMINIMIZE; + meta_window_queue(window, META_QUEUE_CALC_SHOWING); + + meta_window_foreach_transient (window, + queue_calc_showing_func, + NULL); + + g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_MINIMIZED]); + } +} + +static void +ensure_size_hints_satisfied (MetaRectangle *rect, + const XSizeHints *size_hints) +{ + int minw, minh, maxw, maxh; /* min/max width/height */ + int basew, baseh, winc, hinc; /* base width/height, width/height increment */ + int extra_width, extra_height; + + minw = size_hints->min_width; minh = size_hints->min_height; + maxw = size_hints->max_width; maxh = size_hints->max_height; + basew = size_hints->base_width; baseh = size_hints->base_height; + winc = size_hints->width_inc; hinc = size_hints->height_inc; + + /* First, enforce min/max size constraints */ + rect->width = CLAMP (rect->width, minw, maxw); + rect->height = CLAMP (rect->height, minh, maxh); + + /* Now, verify size increment constraints are satisfied, or make them be */ + extra_width = (rect->width - basew) % winc; + extra_height = (rect->height - baseh) % hinc; + + rect->width -= extra_width; + rect->height -= extra_height; + + /* Adjusting width/height down, as done above, may violate minimum size + * constraints, so one last fix. + */ + if (rect->width < minw) + rect->width += ((minw - rect->width)/winc + 1)*winc; + if (rect->height < minh) + rect->height += ((minh - rect->height)/hinc + 1)*hinc; +} + +static void +meta_window_save_rect (MetaWindow *window) +{ + if (!(META_WINDOW_MAXIMIZED (window) || META_WINDOW_TILED_SIDE_BY_SIDE (window) || window->fullscreen)) + { + /* save size/pos as appropriate args for move_resize */ + if (!window->maximized_horizontally) + { + window->saved_rect.x = window->rect.x; + window->saved_rect.width = window->rect.width; + } + if (!window->maximized_vertically) + { + window->saved_rect.y = window->rect.y; + window->saved_rect.height = window->rect.height; + } + } +} + +void +meta_window_maximize_internal (MetaWindow *window, + MetaMaximizeFlags directions, + MetaRectangle *saved_rect) +{ + /* At least one of the two directions ought to be set */ + gboolean maximize_horizontally, maximize_vertically; + maximize_horizontally = directions & META_MAXIMIZE_HORIZONTAL; + maximize_vertically = directions & META_MAXIMIZE_VERTICAL; + g_assert (maximize_horizontally || maximize_vertically); + + meta_topic (META_DEBUG_WINDOW_OPS, + "Maximizing %s%s", + window->desc, + maximize_horizontally && maximize_vertically ? "" : + maximize_horizontally ? " horizontally" : + maximize_vertically ? " vertically" : "BUGGGGG"); + + if (saved_rect != NULL) + window->saved_rect = *saved_rect; + else + meta_window_save_rect (window); + + if (maximize_horizontally && maximize_vertically) + window->saved_maximize = TRUE; + + window->maximized_horizontally = + window->maximized_horizontally || maximize_horizontally; + window->maximized_vertically = + window->maximized_vertically || maximize_vertically; + + /* Update the edge constraints */ + update_edge_constraints (window); + + meta_window_recalc_features (window); + set_net_wm_state (window); + + if (window->monitor && window->monitor->in_fullscreen) + meta_display_queue_check_fullscreen (window->display); + + g_object_freeze_notify (G_OBJECT (window)); + g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_MAXIMIZED_HORIZONTALLY]); + g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_MAXIMIZED_VERTICALLY]); + g_object_thaw_notify (G_OBJECT (window)); +} + +void +meta_window_maximize (MetaWindow *window, + MetaMaximizeFlags directions) +{ + MetaRectangle *saved_rect = NULL; + gboolean maximize_horizontally, maximize_vertically; + + g_return_if_fail (META_IS_WINDOW (window)); + g_return_if_fail (!window->override_redirect); + + /* At least one of the two directions ought to be set */ + maximize_horizontally = directions & META_MAXIMIZE_HORIZONTAL; + maximize_vertically = directions & META_MAXIMIZE_VERTICAL; + g_assert (maximize_horizontally || maximize_vertically); + + /* Only do something if the window isn't already maximized in the + * given direction(s). + */ + if ((maximize_horizontally && !window->maximized_horizontally) || + (maximize_vertically && !window->maximized_vertically)) + { + /* if the window hasn't been placed yet, we'll maximize it then + */ + if (!window->placed) + { + window->maximize_horizontally_after_placement = + window->maximize_horizontally_after_placement || + maximize_horizontally; + window->maximize_vertically_after_placement = + window->maximize_vertically_after_placement || + maximize_vertically; + return; + } + + if (window->tile_mode != META_TILE_NONE) + { + saved_rect = &window->saved_rect; + + window->maximized_vertically = FALSE; + window->tile_mode = META_TILE_NONE; + } + + meta_window_maximize_internal (window, + directions, + saved_rect); + + MetaRectangle old_frame_rect, old_buffer_rect; + + meta_window_get_frame_rect (window, &old_frame_rect); + meta_window_get_buffer_rect (window, &old_buffer_rect); + + meta_compositor_size_change_window (window->display->compositor, window, + META_SIZE_CHANGE_MAXIMIZE, + &old_frame_rect, &old_buffer_rect); + + meta_window_move_resize_internal (window, + (META_MOVE_RESIZE_MOVE_ACTION | + META_MOVE_RESIZE_RESIZE_ACTION | + META_MOVE_RESIZE_STATE_CHANGED | + META_MOVE_RESIZE_CONSTRAIN), + META_GRAVITY_NORTH_WEST, + window->unconstrained_rect); + } +} + +/** + * meta_window_get_maximized: + * @window: a #MetaWindow + * + * Gets the current maximization state of the window, as combination + * of the %META_MAXIMIZE_HORIZONTAL and %META_MAXIMIZE_VERTICAL flags; + * + * Return value: current maximization state + */ +MetaMaximizeFlags +meta_window_get_maximized (MetaWindow *window) +{ + return ((window->maximized_horizontally ? META_MAXIMIZE_HORIZONTAL : 0) | + (window->maximized_vertically ? META_MAXIMIZE_VERTICAL : 0)); +} + +/** + * meta_window_is_fullscreen: + * @window: a #MetaWindow + * + * Return value: %TRUE if the window is currently fullscreen + */ +gboolean +meta_window_is_fullscreen (MetaWindow *window) +{ + return window->fullscreen; +} + +/** + * meta_window_is_screen_sized: + * @window: A #MetaWindow + * + * Return value: %TRUE if the window is occupies the + * the whole screen (all monitors). + */ +gboolean +meta_window_is_screen_sized (MetaWindow *window) +{ + MetaRectangle window_rect; + int screen_width, screen_height; + + meta_display_get_size (window->display, &screen_width, &screen_height); + meta_window_get_frame_rect (window, &window_rect); + + if (window_rect.x == 0 && window_rect.y == 0 && + window_rect.width == screen_width && window_rect.height == screen_height) + return TRUE; + + return FALSE; +} + +/** + * meta_window_is_monitor_sized: + * @window: a #MetaWindow + * + * Return value: %TRUE if the window is occupies an entire monitor or + * the whole screen. + */ +gboolean +meta_window_is_monitor_sized (MetaWindow *window) +{ + if (!window->monitor) + return FALSE; + + if (window->fullscreen) + return TRUE; + + if (meta_window_is_screen_sized (window)) + return TRUE; + + if (window->override_redirect) + { + MetaRectangle window_rect, monitor_rect; + + meta_window_get_frame_rect (window, &window_rect); + meta_display_get_monitor_geometry (window->display, window->monitor->number, &monitor_rect); + + if (meta_rectangle_equal (&window_rect, &monitor_rect)) + return TRUE; + } + + return FALSE; +} + +/** + * meta_window_is_on_primary_monitor: + * @window: a #MetaWindow + * + * Return value: %TRUE if the window is on the primary monitor + */ +gboolean +meta_window_is_on_primary_monitor (MetaWindow *window) +{ + g_return_val_if_fail (window->monitor, FALSE); + + return window->monitor->is_primary; +} + +static void +meta_window_get_tile_fraction (MetaWindow *window, + MetaTileMode tile_mode, + double *fraction) +{ + MetaWindow *tile_match; + + /* Make sure the tile match is up-to-date and matches the + * passed in mode rather than the current state + */ + tile_match = meta_window_find_tile_match (window, tile_mode); + + if (tile_mode == META_TILE_NONE) + *fraction = -1.; + else if (tile_mode == META_TILE_MAXIMIZED) + *fraction = 1.; + else if (tile_match) + *fraction = 1. - tile_match->tile_hfraction; + else if (META_WINDOW_TILED_SIDE_BY_SIDE (window)) + { + if (window->tile_mode != tile_mode) + *fraction = 1. - window->tile_hfraction; + else + *fraction = window->tile_hfraction; + } + else + *fraction = .5; +} + +static void +meta_window_update_tile_fraction (MetaWindow *window, + int new_w, + int new_h) +{ + MetaWindow *tile_match = window->tile_match; + MetaRectangle work_area; + MetaWindowDrag *window_drag; + + if (!META_WINDOW_TILED_SIDE_BY_SIDE (window)) + return; + + meta_window_get_work_area_for_monitor (window, + window->tile_monitor_number, + &work_area); + window->tile_hfraction = (double)new_w / work_area.width; + + window_drag = + meta_compositor_get_current_window_drag (window->display->compositor); + + if (tile_match && + window_drag && + meta_window_drag_get_window (window_drag) == window) + meta_window_tile (tile_match, tile_match->tile_mode); +} + +static void +update_edge_constraints (MetaWindow *window) +{ + switch (window->tile_mode) + { + case META_TILE_NONE: + window->edge_constraints.top = META_EDGE_CONSTRAINT_NONE; + window->edge_constraints.right = META_EDGE_CONSTRAINT_NONE; + window->edge_constraints.bottom = META_EDGE_CONSTRAINT_NONE; + window->edge_constraints.left = META_EDGE_CONSTRAINT_NONE; + break; + + case META_TILE_MAXIMIZED: + window->edge_constraints.top = META_EDGE_CONSTRAINT_MONITOR; + window->edge_constraints.right = META_EDGE_CONSTRAINT_MONITOR; + window->edge_constraints.bottom = META_EDGE_CONSTRAINT_MONITOR; + window->edge_constraints.left = META_EDGE_CONSTRAINT_MONITOR; + break; + + case META_TILE_LEFT: + window->edge_constraints.top = META_EDGE_CONSTRAINT_MONITOR; + + if (window->tile_match) + window->edge_constraints.right = META_EDGE_CONSTRAINT_WINDOW; + else + window->edge_constraints.right = META_EDGE_CONSTRAINT_NONE; + + window->edge_constraints.bottom = META_EDGE_CONSTRAINT_MONITOR; + window->edge_constraints.left = META_EDGE_CONSTRAINT_MONITOR; + break; + + case META_TILE_RIGHT: + window->edge_constraints.top = META_EDGE_CONSTRAINT_MONITOR; + window->edge_constraints.right = META_EDGE_CONSTRAINT_MONITOR; + window->edge_constraints.bottom = META_EDGE_CONSTRAINT_MONITOR; + + if (window->tile_match) + window->edge_constraints.left = META_EDGE_CONSTRAINT_WINDOW; + else + window->edge_constraints.left = META_EDGE_CONSTRAINT_NONE; + break; + } + + /* h/vmaximize also modify the edge constraints */ + if (window->maximized_vertically) + { + window->edge_constraints.top = META_EDGE_CONSTRAINT_MONITOR; + window->edge_constraints.bottom = META_EDGE_CONSTRAINT_MONITOR; + } + + if (window->maximized_horizontally) + { + window->edge_constraints.right = META_EDGE_CONSTRAINT_MONITOR; + window->edge_constraints.left = META_EDGE_CONSTRAINT_MONITOR; + } +} + +void +meta_window_untile (MetaWindow *window) +{ + g_return_if_fail (META_IS_WINDOW (window)); + + window->tile_monitor_number = + window->saved_maximize ? window->monitor->number + : -1; + window->tile_mode = + window->saved_maximize ? META_TILE_MAXIMIZED + : META_TILE_NONE; + + if (window->saved_maximize) + meta_window_maximize (window, META_MAXIMIZE_BOTH); + else + meta_window_unmaximize (window, META_MAXIMIZE_BOTH); +} + +void +meta_window_tile (MetaWindow *window, + MetaTileMode tile_mode) +{ + MetaMaximizeFlags directions; + MetaWindowDrag *window_drag; + + g_return_if_fail (META_IS_WINDOW (window)); + + meta_window_get_tile_fraction (window, tile_mode, &window->tile_hfraction); + window->tile_mode = tile_mode; + + /* Don't do anything if no tiling is requested */ + if (window->tile_mode == META_TILE_NONE) + { + window->tile_monitor_number = -1; + return; + } + else if (window->tile_monitor_number < 0) + { + window->tile_monitor_number = window->monitor->number; + } + + if (window->tile_mode == META_TILE_MAXIMIZED) + directions = META_MAXIMIZE_BOTH; + else + directions = META_MAXIMIZE_VERTICAL; + + meta_window_maximize_internal (window, directions, NULL); + + window_drag = + meta_compositor_get_current_window_drag (window->display->compositor); + + if (!window->tile_match || + !window_drag || + window->tile_match != meta_window_drag_get_window (window_drag)) + { + MetaRectangle old_frame_rect, old_buffer_rect; + + meta_window_get_frame_rect (window, &old_frame_rect); + meta_window_get_buffer_rect (window, &old_buffer_rect); + + meta_compositor_size_change_window (window->display->compositor, window, + META_SIZE_CHANGE_MAXIMIZE, + &old_frame_rect, &old_buffer_rect); + } + + meta_window_move_resize_internal (window, + (META_MOVE_RESIZE_MOVE_ACTION | + META_MOVE_RESIZE_RESIZE_ACTION | + META_MOVE_RESIZE_STATE_CHANGED | + META_MOVE_RESIZE_CONSTRAIN), + META_GRAVITY_NORTH_WEST, + window->unconstrained_rect); +} + +MetaTileMode +meta_window_get_tile_mode (MetaWindow *window) +{ + return window->tile_mode; +} + +void +meta_window_restore_tile (MetaWindow *window, + MetaTileMode mode, + int width, + int height) +{ + meta_window_update_tile_fraction (window, width, height); + meta_window_tile (window, mode); +} + +static gboolean +meta_window_can_tile_maximized (MetaWindow *window) +{ + return window->has_maximize_func; +} + +gboolean +meta_window_can_tile_side_by_side (MetaWindow *window) +{ + int monitor; + MetaRectangle tile_area; + MetaRectangle client_rect; + + if (!meta_window_can_tile_maximized (window)) + return FALSE; + + monitor = meta_display_get_current_monitor (window->display); + meta_window_get_work_area_for_monitor (window, monitor, &tile_area); + + /* Do not allow tiling in portrait orientation */ + if (tile_area.height > tile_area.width) + return FALSE; + + tile_area.width /= 2; + + meta_window_frame_rect_to_client_rect (window, &tile_area, &client_rect); + + return client_rect.width >= window->size_hints.min_width && + client_rect.height >= window->size_hints.min_height; +} + +static void +unmaximize_window_before_freeing (MetaWindow *window) +{ + meta_topic (META_DEBUG_WINDOW_OPS, + "Unmaximizing %s just before freeing", + window->desc); + + window->maximized_horizontally = FALSE; + window->maximized_vertically = FALSE; + + if (window->withdrawn) /* See bug #137185 */ + { + window->rect = window->saved_rect; + set_net_wm_state (window); + } +#ifdef HAVE_WAYLAND + else if (!meta_is_wayland_compositor ()) + { + /* Do NOT update net_wm_state: this screen is closing, + * it likely will be managed by another window manager + * that will need the current _NET_WM_STATE atoms. + * Moreover, it will need to know the unmaximized geometry, + * therefore move_resize the window to saved_rect here + * before closing it. */ + meta_window_move_resize_frame (window, + FALSE, + window->saved_rect.x, + window->saved_rect.y, + window->saved_rect.width, + window->saved_rect.height); + } +#endif +} + +void +meta_window_maybe_apply_size_hints (MetaWindow *window, + MetaRectangle *target_rect) +{ + meta_window_frame_rect_to_client_rect (window, target_rect, target_rect); + ensure_size_hints_satisfied (target_rect, &window->size_hints); + meta_window_client_rect_to_frame_rect (window, target_rect, target_rect); +} + +void +meta_window_unmaximize (MetaWindow *window, + MetaMaximizeFlags directions) +{ + gboolean unmaximize_horizontally, unmaximize_vertically; + + g_return_if_fail (META_IS_WINDOW (window)); + g_return_if_fail (!window->override_redirect); + + /* At least one of the two directions ought to be set */ + unmaximize_horizontally = directions & META_MAXIMIZE_HORIZONTAL; + unmaximize_vertically = directions & META_MAXIMIZE_VERTICAL; + g_assert (unmaximize_horizontally || unmaximize_vertically); + + if (unmaximize_horizontally && unmaximize_vertically) + window->saved_maximize = FALSE; + + /* Only do something if the window isn't already maximized in the + * given direction(s). + */ + if ((unmaximize_horizontally && window->maximized_horizontally) || + (unmaximize_vertically && window->maximized_vertically)) + { + MetaRectangle *desired_rect; + MetaRectangle target_rect; + MetaRectangle work_area; + MetaRectangle old_frame_rect, old_buffer_rect; + gboolean has_target_size; + + meta_window_get_work_area_for_monitor (window, window->monitor->number, &work_area); + meta_window_get_frame_rect (window, &old_frame_rect); + meta_window_get_buffer_rect (window, &old_buffer_rect); + + if (unmaximize_vertically) + window->tile_mode = META_TILE_NONE; + + meta_topic (META_DEBUG_WINDOW_OPS, + "Unmaximizing %s%s", + window->desc, + unmaximize_horizontally && unmaximize_vertically ? "" : + unmaximize_horizontally ? " horizontally" : + unmaximize_vertically ? " vertically" : "BUGGGGG"); + + window->maximized_horizontally = + window->maximized_horizontally && !unmaximize_horizontally; + window->maximized_vertically = + window->maximized_vertically && !unmaximize_vertically; + + /* Update the edge constraints */ + update_edge_constraints (window); + + /* recalc_features() will eventually clear the cached frame + * extents, but we need the correct frame extents in the code below, + * so invalidate the old frame extents manually up front. + */ + meta_window_frame_size_changed (window); + + desired_rect = &window->saved_rect; + + /* Unmaximize to the saved_rect position in the direction(s) + * being unmaximized. + */ + target_rect = old_frame_rect; + + /* Avoid unmaximizing to "almost maximized" size when the previous size + * is greater then 80% of the work area use MAX_UNMAXIMIZED_WINDOW_AREA of the work area as upper limit + * while maintaining the aspect ratio. + */ + if (unmaximize_horizontally && unmaximize_vertically && + desired_rect->width * desired_rect->height > work_area.width * work_area.height * MAX_UNMAXIMIZED_WINDOW_AREA) + { + if (desired_rect->width > desired_rect->height) + { + float aspect = (float)desired_rect->height / (float)desired_rect->width; + desired_rect->width = MAX (work_area.width * sqrt (MAX_UNMAXIMIZED_WINDOW_AREA), window->size_hints.min_width); + desired_rect->height = MAX (desired_rect->width * aspect, window->size_hints.min_height); + } + else + { + float aspect = (float)desired_rect->width / (float)desired_rect->height; + desired_rect->height = MAX (work_area.height * sqrt (MAX_UNMAXIMIZED_WINDOW_AREA), window->size_hints.min_height); + desired_rect->width = MAX (desired_rect->height * aspect, window->size_hints.min_width); + } + } + + if (unmaximize_horizontally) + { + target_rect.x = desired_rect->x; + target_rect.width = desired_rect->width; + } + if (unmaximize_vertically) + { + target_rect.y = desired_rect->y; + target_rect.height = desired_rect->height; + } + + /* Window's size hints may have changed while maximized, making + * saved_rect invalid. #329152 + * Do not enforce limits, if no previous 'saved_rect' has been stored. + */ + has_target_size = (target_rect.width > 0 && target_rect.height > 0); + if (has_target_size) + meta_window_maybe_apply_size_hints (window, &target_rect); + + meta_compositor_size_change_window (window->display->compositor, window, + META_SIZE_CHANGE_UNMAXIMIZE, + &old_frame_rect, &old_buffer_rect); + + meta_window_move_resize_internal (window, + (META_MOVE_RESIZE_MOVE_ACTION | + META_MOVE_RESIZE_RESIZE_ACTION | + META_MOVE_RESIZE_STATE_CHANGED | + META_MOVE_RESIZE_UNMAXIMIZE), + META_GRAVITY_NORTH_WEST, + target_rect); + + meta_window_recalc_features (window); + set_net_wm_state (window); + if (!window->monitor->in_fullscreen) + meta_display_queue_check_fullscreen (window->display); + } + + g_object_freeze_notify (G_OBJECT (window)); + g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_MAXIMIZED_HORIZONTALLY]); + g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_MAXIMIZED_VERTICALLY]); + g_object_thaw_notify (G_OBJECT (window)); +} + +void +meta_window_make_above (MetaWindow *window) +{ + g_return_if_fail (!window->override_redirect); + + meta_window_set_above (window, TRUE); + meta_window_raise (window); +} + +void +meta_window_unmake_above (MetaWindow *window) +{ + g_return_if_fail (!window->override_redirect); + + meta_window_set_above (window, FALSE); + meta_window_raise (window); +} + +static void +meta_window_set_above (MetaWindow *window, + gboolean new_value) +{ + new_value = new_value != FALSE; + if (new_value == window->wm_state_above) + return; + + window->wm_state_above = new_value; + meta_window_update_layer (window); + set_net_wm_state (window); + meta_window_frame_size_changed (window); + g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_ABOVE]); +} + +void +meta_window_make_fullscreen_internal (MetaWindow *window) +{ + if (!window->fullscreen) + { + meta_topic (META_DEBUG_WINDOW_OPS, + "Fullscreening %s", window->desc); + + window->saved_rect_fullscreen = window->rect; + + window->fullscreen = TRUE; + + meta_stack_freeze (window->display->stack); + + meta_window_raise (window); + meta_stack_thaw (window->display->stack); + + meta_window_recalc_features (window); + set_net_wm_state (window); + + /* For the auto-minimize feature, if we fail to get focus */ + meta_display_queue_check_fullscreen (window->display); + + g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_FULLSCREEN]); + } +} + +void +meta_window_make_fullscreen (MetaWindow *window) +{ + g_return_if_fail (META_IS_WINDOW (window)); + g_return_if_fail (!window->override_redirect); + + if (!window->fullscreen) + { + MetaRectangle old_frame_rect, old_buffer_rect; + + meta_window_get_frame_rect (window, &old_frame_rect); + meta_window_get_buffer_rect (window, &old_buffer_rect); + + meta_compositor_size_change_window (window->display->compositor, + window, META_SIZE_CHANGE_FULLSCREEN, + &old_frame_rect, &old_buffer_rect); + + meta_window_make_fullscreen_internal (window); + meta_window_move_resize_internal (window, + (META_MOVE_RESIZE_MOVE_ACTION | + META_MOVE_RESIZE_RESIZE_ACTION | + META_MOVE_RESIZE_STATE_CHANGED | + META_MOVE_RESIZE_CONSTRAIN), + META_GRAVITY_NORTH_WEST, + window->unconstrained_rect); + } +} + +void +meta_window_unmake_fullscreen (MetaWindow *window) +{ + g_return_if_fail (META_IS_WINDOW (window)); + g_return_if_fail (!window->override_redirect); + + if (window->fullscreen) + { + MetaRectangle old_frame_rect, old_buffer_rect, target_rect; + gboolean has_target_size; + + meta_topic (META_DEBUG_WINDOW_OPS, + "Unfullscreening %s", window->desc); + + window->fullscreen = FALSE; + target_rect = window->saved_rect_fullscreen; + + meta_window_frame_size_changed (window); + meta_window_get_frame_rect (window, &old_frame_rect); + meta_window_get_buffer_rect (window, &old_buffer_rect); + + /* Window's size hints may have changed while maximized, making + * saved_rect invalid. #329152 + * Do not enforce limits, if no previous 'saved_rect' has been stored. + */ + has_target_size = (target_rect.width > 0 && target_rect.height > 0); + if (has_target_size) + meta_window_maybe_apply_size_hints (window, &target_rect); + + /* Need to update window->has_resize_func before we move_resize() + */ + meta_window_recalc_features (window); + set_net_wm_state (window); + + meta_compositor_size_change_window (window->display->compositor, + window, META_SIZE_CHANGE_UNFULLSCREEN, + &old_frame_rect, &old_buffer_rect); + + meta_window_move_resize_internal (window, + (META_MOVE_RESIZE_MOVE_ACTION | + META_MOVE_RESIZE_RESIZE_ACTION | + META_MOVE_RESIZE_STATE_CHANGED | + META_MOVE_RESIZE_UNFULLSCREEN), + META_GRAVITY_NORTH_WEST, + target_rect); + + meta_display_queue_check_fullscreen (window->display); + + g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_FULLSCREEN]); + } +} + +static void +meta_window_clear_fullscreen_monitors (MetaWindow *window) +{ + window->fullscreen_monitors.top = NULL; + window->fullscreen_monitors.bottom = NULL; + window->fullscreen_monitors.left = NULL; + window->fullscreen_monitors.right = NULL; +} + +void +meta_window_update_fullscreen_monitors (MetaWindow *window, + MetaLogicalMonitor *top, + MetaLogicalMonitor *bottom, + MetaLogicalMonitor *left, + MetaLogicalMonitor *right) +{ + if (top && bottom && left && right) + { + window->fullscreen_monitors.top = top; + window->fullscreen_monitors.bottom = bottom; + window->fullscreen_monitors.left = left; + window->fullscreen_monitors.right = right; + } + else + { + meta_window_clear_fullscreen_monitors (window); + } + + if (window->fullscreen) + { + meta_window_queue(window, META_QUEUE_MOVE_RESIZE); + } +} + +gboolean +meta_window_has_fullscreen_monitors (MetaWindow *window) +{ + return window->fullscreen_monitors.top != NULL; +} + +void +meta_window_adjust_fullscreen_monitor_rect (MetaWindow *window, + MetaRectangle *monitor_rect) +{ + MetaWindowClass *window_class = META_WINDOW_GET_CLASS (window); + + if (window_class->adjust_fullscreen_monitor_rect) + window_class->adjust_fullscreen_monitor_rect (window, monitor_rect); +} + +static gboolean +unminimize_func (MetaWindow *window, + void *data) +{ + meta_window_unminimize (window); + return TRUE; +} + +static void +unminimize_window_and_all_transient_parents (MetaWindow *window) +{ + meta_window_unminimize (window); + meta_window_foreach_ancestor (window, unminimize_func, NULL); +} + +void +meta_window_activate_full (MetaWindow *window, + guint32 timestamp, + MetaClientType source_indication, + MetaWorkspace *workspace) +{ + MetaWorkspaceManager *workspace_manager = window->display->workspace_manager; + gboolean allow_workspace_switch; + + if (window->unmanaging) + { + g_warning ("Trying to activate unmanaged window '%s'", window->desc); + return; + } + + meta_topic (META_DEBUG_FOCUS, + "_NET_ACTIVE_WINDOW message sent for %s at time %u " + "by client type %u.", + window->desc, timestamp, source_indication); + + allow_workspace_switch = (timestamp != 0); + if (timestamp != 0 && + XSERVER_TIME_IS_BEFORE (timestamp, window->display->last_user_time)) + { + meta_topic (META_DEBUG_FOCUS, + "last_user_time (%u) is more recent; ignoring " + " _NET_ACTIVE_WINDOW message.", + window->display->last_user_time); + meta_window_set_demands_attention(window); + return; + } + + if (timestamp == 0) + timestamp = meta_display_get_current_time_roundtrip (window->display); + + meta_window_set_user_time (window, timestamp); + + /* disable show desktop mode unless we're a desktop component */ + maybe_leave_show_desktop_mode (window); + + /* Get window on current or given workspace */ + if (workspace == NULL) + workspace = workspace_manager->active_workspace; + + /* For non-transient windows, we just set up a pulsing indicator, + rather than move windows or workspaces. + See http://bugzilla.gnome.org/show_bug.cgi?id=482354 */ + if (window->transient_for == NULL && + !allow_workspace_switch && + !meta_window_located_on_workspace (window, workspace)) + { + meta_window_set_demands_attention (window); + /* We've marked it as demanding, don't need to do anything else. */ + return; + } + else if (window->transient_for != NULL) + { + /* Move transients to current workspace - preference dialogs should appear over + the source window. */ + meta_window_change_workspace (window, workspace); + } + + unminimize_window_and_all_transient_parents (window); + + if (meta_prefs_get_raise_on_click () || + source_indication == META_CLIENT_TYPE_PAGER) + meta_window_raise (window); + + meta_topic (META_DEBUG_FOCUS, + "Focusing window %s due to activation", + window->desc); + + if (meta_window_located_on_workspace (window, workspace)) + meta_window_focus (window, timestamp); + else + meta_workspace_activate_with_focus (window->workspace, window, timestamp); + + meta_window_check_alive (window, timestamp); +} + +/* This function exists since most of the functionality in window_activate + * is useful for Mutter, but Mutter shouldn't need to specify a client + * type for itself. ;-) + */ +void +meta_window_activate (MetaWindow *window, + guint32 timestamp) +{ + g_return_if_fail (!window->override_redirect); + + /* We're not really a pager, but the behavior we want is the same as if + * we were such. If we change the pager behavior later, we could revisit + * this and just add extra flags to window_activate. + */ + meta_window_activate_full (window, timestamp, META_CLIENT_TYPE_PAGER, NULL); +} + +void +meta_window_activate_with_workspace (MetaWindow *window, + guint32 timestamp, + MetaWorkspace *workspace) +{ + g_return_if_fail (!window->override_redirect); + + meta_window_activate_full (window, timestamp, META_CLIENT_TYPE_APPLICATION, workspace); +} + +/** + * meta_window_updates_are_frozen: + * @window: a #MetaWindow + * + * Gets whether the compositor should be updating the window contents; + * window content updates may be frozen at client request by setting + * an odd value in the extended _NET_WM_SYNC_REQUEST_COUNTER counter + * by the window manager during a resize operation while waiting for + * the client to redraw. + * + * Return value: %TRUE if updates are currently frozen + */ +gboolean +meta_window_updates_are_frozen (MetaWindow *window) +{ + return META_WINDOW_GET_CLASS (window)->are_updates_frozen (window); +} + +static void +meta_window_reposition (MetaWindow *window) +{ + meta_window_move_resize_internal (window, + (META_MOVE_RESIZE_MOVE_ACTION | + META_MOVE_RESIZE_RESIZE_ACTION | + META_MOVE_RESIZE_CONSTRAIN), + META_GRAVITY_NORTH_WEST, + window->rect); +} + +static gboolean +maybe_move_attached_window (MetaWindow *window, + void *data) +{ + if (window->hidden) + return G_SOURCE_CONTINUE; + + if (meta_window_is_attached_dialog (window) || + meta_window_get_placement_rule (window)) + meta_window_reposition (window); + + return G_SOURCE_CONTINUE; +} + +/** + * meta_window_get_monitor: + * @window: a #MetaWindow + * + * Gets index of the monitor that this window is on. + * + * Return Value: The index of the monitor in the screens monitor list, or -1 + * if the window has been recently unmanaged and does not have a monitor. + */ +int +meta_window_get_monitor (MetaWindow *window) +{ + if (!window->monitor) + return -1; + + return window->monitor->number; +} + +MetaLogicalMonitor * +meta_window_get_main_logical_monitor (MetaWindow *window) +{ + return window->monitor; +} + +static MetaLogicalMonitor * +find_monitor_by_winsys_id (MetaWindow *window, + uint64_t winsys_id) +{ + MetaBackend *backend = backend_from_window (window); + MetaMonitorManager *monitor_manager = + meta_backend_get_monitor_manager (backend); + GList *logical_monitors, *l; + + logical_monitors = + meta_monitor_manager_get_logical_monitors (monitor_manager); + + for (l = logical_monitors; l; l = l->next) + { + MetaLogicalMonitor *logical_monitor = l->data; + + if (logical_monitor->winsys_id == winsys_id) + return logical_monitor; + } + + return NULL; +} + +MetaLogicalMonitor * +meta_window_find_monitor_from_id (MetaWindow *window) +{ + MetaContext *context = meta_display_get_context (window->display); + MetaBackend *backend = meta_context_get_backend (context); + MetaMonitorManager *monitor_manager = + meta_backend_get_monitor_manager (backend); + MetaLogicalMonitor *old_monitor = window->monitor; + MetaLogicalMonitor *new_monitor; + + new_monitor = find_monitor_by_winsys_id (window, + window->preferred_output_winsys_id); + + if (old_monitor && !new_monitor) + new_monitor = find_monitor_by_winsys_id (window, old_monitor->winsys_id); + + if (!new_monitor) + { + new_monitor = + meta_monitor_manager_get_primary_logical_monitor (monitor_manager); + } + + return new_monitor; +} + +/* This is called when the monitor setup has changed. The window->monitor + * reference is still "valid", but refer to the previous monitor setup */ +void +meta_window_update_for_monitors_changed (MetaWindow *window) +{ + MetaContext *context = meta_display_get_context (window->display); + MetaBackend *backend = meta_context_get_backend (context); + MetaMonitorManager *monitor_manager = + meta_backend_get_monitor_manager (backend); + const MetaLogicalMonitor *old, *new; + + if (meta_window_has_fullscreen_monitors (window)) + meta_window_clear_fullscreen_monitors (window); + + if (window->override_redirect || window->type == META_WINDOW_DESKTOP) + { + meta_window_update_monitor (window, + META_WINDOW_UPDATE_MONITOR_FLAGS_FORCE); + goto out; + } + + old = window->monitor; + new = meta_window_find_monitor_from_id (window); + + if (window->tile_mode != META_TILE_NONE) + { + if (new) + window->tile_monitor_number = new->number; + else + window->tile_monitor_number = -1; + } + + if (new && old) + { + /* This will eventually reach meta_window_update_monitor that + * will send leave/enter-monitor events. The old != new monitor + * check will always fail (due to the new logical_monitors set) so + * we will always send the events, even if the new and old monitor + * index is the same. That is right, since the enumeration of the + * monitors changed and the same index could be refereing + * to a different monitor. */ + meta_window_move_between_rects (window, + META_MOVE_RESIZE_FORCE_UPDATE_MONITOR, + &old->rect, + &new->rect); + } + else + { + meta_window_update_monitor (window, + META_WINDOW_UPDATE_MONITOR_FLAGS_FORCE); + } + +out: + g_assert (!window->monitor || + g_list_find (meta_monitor_manager_get_logical_monitors (monitor_manager), + window->monitor)); +} + +void +meta_window_update_monitor (MetaWindow *window, + MetaWindowUpdateMonitorFlags flags) +{ + MetaWorkspaceManager *workspace_manager = window->display->workspace_manager; + const MetaLogicalMonitor *old; + + old = window->monitor; + META_WINDOW_GET_CLASS (window)->update_main_monitor (window, flags); + if (old != window->monitor) + { + meta_window_on_all_workspaces_changed (window); + + /* If workspaces only on primary and we moved back to primary due to a user action, + * ensure that the window is now in that workspace. We do this because while + * the window is on a non-primary monitor it is always visible, so it would be + * very jarring if it disappeared when it crossed the monitor border. + * The one time we want it to both change to the primary monitor and a non-active + * workspace is when dropping the window on some other workspace thumbnail directly. + * That should be handled by explicitly moving the window before changing the + * workspace. + */ + if (meta_prefs_get_workspaces_only_on_primary () && + flags & META_WINDOW_UPDATE_MONITOR_FLAGS_USER_OP && + meta_window_is_on_primary_monitor (window) && + workspace_manager->active_workspace != window->workspace) + meta_window_change_workspace (window, workspace_manager->active_workspace); + + meta_window_main_monitor_changed (window, old); + + /* If we're changing monitors, we need to update the has_maximize_func flag, + * as the working area has changed. */ + meta_window_recalc_features (window); + + meta_display_queue_check_fullscreen (window->display); + } +} + +void +meta_window_move_resize_internal (MetaWindow *window, + MetaMoveResizeFlags flags, + MetaGravity gravity, + MetaRectangle frame_rect) +{ + /* The rectangle here that's passed in *always* in "frame rect" + * coordinates. That means the position of the frame's visible bounds, + * with x and y being absolute (root window) coordinates. + * + * For an X11 framed window, the client window's server rectangle is + * inset from this rectangle by the frame's visible borders, and the + * frame window's server rectangle is outset by the invisible borders. + * + * For an X11 unframed window, the rectangle here directly matches + * the server's rectangle, since the visible and invisible borders + * are both 0. + * + * For an X11 CSD window, the client window's server rectangle is + * outset from this rectagle by the client-specified frame extents. + * + * For a Wayland window, this rectangle can simply be sent directly + * to the client. + */ + + MetaWorkspaceManager *workspace_manager = window->display->workspace_manager; + gboolean did_placement; + MetaRectangle unconstrained_rect; + MetaRectangle constrained_rect; + MetaRectangle temporary_rect; + int rel_x = 0; + int rel_y = 0; + MetaMoveResizeResultFlags result = 0; + gboolean moved_or_resized = FALSE; + MetaWindowUpdateMonitorFlags update_monitor_flags; + + g_return_if_fail (!window->override_redirect); + + /* The action has to be a move, a resize or the wayland client + * acking our choice of size. + */ + g_assert (flags & (META_MOVE_RESIZE_MOVE_ACTION | + META_MOVE_RESIZE_RESIZE_ACTION | + META_MOVE_RESIZE_WAYLAND_FINISH_MOVE_RESIZE)); + + did_placement = !window->placed && window->calc_placement; + + /* We don't need it in the idle queue anymore. */ + meta_window_unqueue (window, META_QUEUE_MOVE_RESIZE); + + if ((flags & META_MOVE_RESIZE_RESIZE_ACTION) && (flags & META_MOVE_RESIZE_MOVE_ACTION)) + { + /* We're both moving and resizing. Just use the passed in rect. */ + unconstrained_rect = frame_rect; + } + else if ((flags & META_MOVE_RESIZE_RESIZE_ACTION)) + { + /* If this is only a resize, then ignore the position given in + * the parameters and instead calculate the new position from + * resizing the old rectangle with the given gravity. */ + meta_rectangle_resize_with_gravity (&window->rect, + &unconstrained_rect, + gravity, + frame_rect.width, + frame_rect.height); + } + else if ((flags & META_MOVE_RESIZE_MOVE_ACTION)) + { + /* If this is only a move, then ignore the passed in size and + * just use the existing size of the window. */ + unconstrained_rect.x = frame_rect.x; + unconstrained_rect.y = frame_rect.y; + unconstrained_rect.width = window->rect.width; + unconstrained_rect.height = window->rect.height; + } + else if ((flags & META_MOVE_RESIZE_WAYLAND_FINISH_MOVE_RESIZE)) + { + /* This is a Wayland buffer acking our size. The new rect is + * just the existing one we have. Ignore the passed-in rect + * completely. */ + unconstrained_rect = window->rect; + } + else + g_assert_not_reached (); + + constrained_rect = unconstrained_rect; + temporary_rect = window->rect; + if (flags & META_MOVE_RESIZE_CONSTRAIN && window->monitor) + { + MetaRectangle old_rect; + meta_window_get_frame_rect (window, &old_rect); + + meta_window_constrain (window, + flags, + gravity, + &old_rect, + &constrained_rect, + &temporary_rect, + &rel_x, + &rel_y); + } + else if (window->placement.rule) + { + rel_x = window->placement.pending.rel_x; + rel_y = window->placement.pending.rel_y; + } + + /* If we did placement, then we need to save the position that the window + * was placed at to make sure that meta_window_update_layout() places the + * window correctly. + */ + if (did_placement) + { + unconstrained_rect.x = constrained_rect.x; + unconstrained_rect.y = constrained_rect.y; + } + + /* Do the protocol-specific move/resize logic */ + META_WINDOW_GET_CLASS (window)->move_resize_internal (window, + gravity, + unconstrained_rect, + constrained_rect, + temporary_rect, + rel_x, + rel_y, + flags, &result); + + if (result & META_MOVE_RESIZE_RESULT_MOVED) + { + moved_or_resized = TRUE; + g_signal_emit (window, window_signals[POSITION_CHANGED], 0); + } + + if (result & META_MOVE_RESIZE_RESULT_RESIZED) + { + moved_or_resized = TRUE; + g_signal_emit (window, window_signals[SIZE_CHANGED], 0); + } + + /* Only update the stored size when requested but not when a + * (potentially outdated) request completes */ + if (!(flags & META_MOVE_RESIZE_WAYLAND_FINISH_MOVE_RESIZE) || + flags & META_MOVE_RESIZE_WAYLAND_CLIENT_RESIZE) + { + window->unconstrained_rect = unconstrained_rect; + } + + if ((moved_or_resized || + did_placement || + (result & META_MOVE_RESIZE_RESULT_STATE_CHANGED) != 0) && + window->known_to_compositor) + { + meta_compositor_sync_window_geometry (window->display->compositor, + window, + did_placement); + } + + update_monitor_flags = META_WINDOW_UPDATE_MONITOR_FLAGS_NONE; + if (flags & META_MOVE_RESIZE_USER_ACTION) + update_monitor_flags |= META_WINDOW_UPDATE_MONITOR_FLAGS_USER_OP; + if (flags & META_MOVE_RESIZE_FORCE_UPDATE_MONITOR) + update_monitor_flags |= META_WINDOW_UPDATE_MONITOR_FLAGS_FORCE; + + if (window->monitor) + { + uint64_t old_output_winsys_id; + + old_output_winsys_id = window->monitor->winsys_id; + + meta_window_update_monitor (window, update_monitor_flags); + + if (old_output_winsys_id != window->monitor->winsys_id && + flags & META_MOVE_RESIZE_MOVE_ACTION && flags & META_MOVE_RESIZE_USER_ACTION) + window->preferred_output_winsys_id = window->monitor->winsys_id; + } + else + { + meta_window_update_monitor (window, update_monitor_flags); + } + + if ((result & META_MOVE_RESIZE_RESULT_FRAME_SHAPE_CHANGED) && window->frame_bounds) + { + cairo_region_destroy (window->frame_bounds); + window->frame_bounds = NULL; + } + + meta_window_foreach_transient (window, maybe_move_attached_window, NULL); + + meta_stack_update_window_tile_matches (window->display->stack, + workspace_manager->active_workspace); + + if (flags & META_MOVE_RESIZE_WAYLAND_CLIENT_RESIZE) + meta_window_queue (window, META_QUEUE_MOVE_RESIZE); +} + +/** + * meta_window_move_frame: + * @window: a #MetaWindow + * @user_op: bool to indicate whether or not this is a user operation + * @root_x_nw: desired x pos + * @root_y_nw: desired y pos + * + * Moves the window to the desired location on window's assigned + * workspace, using the northwest edge of the frame as the reference, + * instead of the actual window's origin, but only if a frame is present. + * Otherwise, acts identically to meta_window_move(). + */ +void +meta_window_move_frame (MetaWindow *window, + gboolean user_op, + int root_x_nw, + int root_y_nw) +{ + MetaMoveResizeFlags flags; + MetaRectangle rect = { root_x_nw, root_y_nw, 0, 0 }; + + g_return_if_fail (!window->override_redirect); + + flags = ((user_op ? META_MOVE_RESIZE_USER_ACTION : 0) | + META_MOVE_RESIZE_MOVE_ACTION | + META_MOVE_RESIZE_CONSTRAIN); + meta_window_move_resize_internal (window, flags, META_GRAVITY_NORTH_WEST, rect); +} + +static void +meta_window_move_between_rects (MetaWindow *window, + MetaMoveResizeFlags move_resize_flags, + const MetaRectangle *old_area, + const MetaRectangle *new_area) +{ + double rel_x, rel_y; + int new_x, new_y; + + if (!old_area) + { + new_x = new_area->x; + new_y = new_area->y; + } + else if (meta_rectangle_contains_rect (old_area, &window->unconstrained_rect) && + old_area->width > window->unconstrained_rect.width && + old_area->height > window->unconstrained_rect.height && + new_area->width >= window->unconstrained_rect.width && + new_area->height >= window->unconstrained_rect.height) + { + rel_x = (double)(window->unconstrained_rect.x - old_area->x) / + (old_area->width - window->unconstrained_rect.width); + rel_y = (double)(window->unconstrained_rect.y - old_area->y) / + (old_area->height - window->unconstrained_rect.height); + + g_warn_if_fail (rel_x >= 0.0 && rel_x <= 1.0 && + rel_y >= 0.0 && rel_y <= 1.0); + + new_x = new_area->x + + rel_x * (new_area->width - window->unconstrained_rect.width); + new_y = new_area->y + + rel_y * (new_area->height - window->unconstrained_rect.height); + } + else + { + rel_x = (float)(window->unconstrained_rect.x - old_area->x + + (window->unconstrained_rect.width / 2)) / old_area->width; + rel_y = (float)(window->unconstrained_rect.y - old_area->y + + (window->unconstrained_rect.height / 2)) / old_area->height; + + rel_x = CLAMP (rel_x, FLT_EPSILON, 1.0 - FLT_EPSILON); + rel_y = CLAMP (rel_y, FLT_EPSILON, 1.0 - FLT_EPSILON); + + new_x = new_area->x - (window->unconstrained_rect.width / 2) + + (rel_x * new_area->width); + new_y = new_area->y - (window->unconstrained_rect.height / 2) + + (rel_y * new_area->height); + } + + window->unconstrained_rect.x = new_x; + window->unconstrained_rect.y = new_y; + + meta_window_move_resize_internal (window, + (move_resize_flags | + META_MOVE_RESIZE_MOVE_ACTION | + META_MOVE_RESIZE_RESIZE_ACTION | + META_MOVE_RESIZE_CONSTRAIN), + META_GRAVITY_NORTH_WEST, + window->unconstrained_rect); +} + +/** + * meta_window_move_resize_frame: + * @window: a #MetaWindow + * @user_op: bool to indicate whether or not this is a user operation + * @root_x_nw: new x + * @root_y_nw: new y + * @w: desired width + * @h: desired height + * + * Resizes the window so that its outer bounds (including frame) + * fit within the given rect + */ +void +meta_window_move_resize_frame (MetaWindow *window, + gboolean user_op, + int root_x_nw, + int root_y_nw, + int w, + int h) +{ + MetaMoveResizeFlags flags; + MetaRectangle rect = { root_x_nw, root_y_nw, w, h }; + + g_return_if_fail (!window->override_redirect); + + flags = ((user_op ? META_MOVE_RESIZE_USER_ACTION : 0) | + META_MOVE_RESIZE_MOVE_ACTION | + META_MOVE_RESIZE_RESIZE_ACTION | + META_MOVE_RESIZE_CONSTRAIN); + + meta_window_move_resize_internal (window, flags, META_GRAVITY_NORTH_WEST, rect); +} + +/** + * meta_window_move_to_monitor: + * @window: a #MetaWindow + * @monitor: desired monitor index + * + * Moves the window to the monitor with index @monitor, keeping + * the relative position of the window's top left corner. + */ +void +meta_window_move_to_monitor (MetaWindow *window, + int monitor) +{ + MetaRectangle old_area, new_area; + + if (window->tile_mode != META_TILE_NONE) + window->tile_monitor_number = monitor; + + meta_window_get_work_area_for_monitor (window, + window->monitor->number, + &old_area); + meta_window_get_work_area_for_monitor (window, + monitor, + &new_area); + + if (window->unconstrained_rect.width == 0 || + window->unconstrained_rect.height == 0 || + !meta_rectangle_overlap (&window->unconstrained_rect, &old_area)) + { + meta_window_move_between_rects (window, 0, NULL, &new_area); + } + else + { + MetaRectangle old_frame_rect, old_buffer_rect; + + if (monitor == window->monitor->number) + return; + + meta_window_get_frame_rect (window, &old_frame_rect); + meta_window_get_buffer_rect (window, &old_buffer_rect); + + meta_compositor_size_change_window (window->display->compositor, window, + META_SIZE_CHANGE_MONITOR_MOVE, + &old_frame_rect, &old_buffer_rect); + + meta_window_move_between_rects (window, 0, &old_area, &new_area); + } + + window->preferred_output_winsys_id = window->monitor->winsys_id; + + if (window->fullscreen || window->override_redirect) + meta_display_queue_check_fullscreen (window->display); +} + +static void +adjust_size_for_tile_match (MetaWindow *window, + int *new_w, + int *new_h) +{ + MetaRectangle work_area, rect; + MetaWindow *tile_match = window->tile_match; + + if (!META_WINDOW_TILED_SIDE_BY_SIDE (window) || !tile_match) + return; + + meta_window_get_work_area_for_monitor (window, window->tile_monitor_number, &work_area); + + /* Make sure the resize does not break minimum sizes */ + rect = work_area; + rect.width = *new_w; + + meta_window_frame_rect_to_client_rect (window, &rect, &rect); + *new_w += MAX(0, window->size_hints.min_width - rect.width); + + /* Make sure we're not resizing the tile match below its min width */ + rect = work_area; + rect.width = work_area.width - *new_w; + + meta_window_frame_rect_to_client_rect (tile_match, &rect, &rect); + *new_w -= MAX(0, tile_match->size_hints.min_width - rect.width); +} + +void +meta_window_resize_frame_with_gravity (MetaWindow *window, + gboolean user_op, + int w, + int h, + MetaGravity gravity) +{ + MetaMoveResizeFlags flags; + MetaRectangle rect; + + rect.width = w; + rect.height = h; + + if (user_op) + { + MetaWindowDrag *window_drag; + + window_drag = + meta_compositor_get_current_window_drag (window->display->compositor); + + /* When resizing in-tandem with a tile match, we need to respect + * its minimum width + */ + if (window_drag && + meta_window_drag_get_window (window_drag) == window) + adjust_size_for_tile_match (window, &w, &h); + meta_window_update_tile_fraction (window, w, h); + } + + flags = ((user_op ? META_MOVE_RESIZE_USER_ACTION : 0) | + META_MOVE_RESIZE_RESIZE_ACTION | + META_MOVE_RESIZE_CONSTRAIN); + meta_window_move_resize_internal (window, flags, gravity, rect); +} + +void +meta_window_update_layout (MetaWindow *window) +{ + meta_window_move_resize_frame (window, FALSE, + window->unconstrained_rect.x, + window->unconstrained_rect.y, + window->unconstrained_rect.width, + window->unconstrained_rect.height); +} + +void +meta_window_get_gravity_position (MetaWindow *window, + MetaGravity gravity, + int *root_x, + int *root_y) +{ + MetaRectangle frame_extents; + int w, h; + int x, y; + + w = window->rect.width; + h = window->rect.height; + + if (gravity == META_GRAVITY_STATIC) + { + frame_extents = window->rect; + if (window->frame) + { + frame_extents.x = window->frame->rect.x + window->frame->child_x; + frame_extents.y = window->frame->rect.y + window->frame->child_y; + } + } + else + { + if (window->frame == NULL) + frame_extents = window->rect; + else + frame_extents = window->frame->rect; + } + + x = frame_extents.x; + y = frame_extents.y; + + switch (gravity) + { + case META_GRAVITY_NORTH: + case META_GRAVITY_CENTER: + case META_GRAVITY_SOUTH: + /* Find center of frame. */ + x += frame_extents.width / 2; + /* Center client window on that point. */ + x -= w / 2; + break; + + case META_GRAVITY_SOUTH_EAST: + case META_GRAVITY_EAST: + case META_GRAVITY_NORTH_EAST: + /* Find right edge of frame */ + x += frame_extents.width; + /* Align left edge of client at that point. */ + x -= w; + break; + default: + break; + } + + switch (gravity) + { + case META_GRAVITY_WEST: + case META_GRAVITY_CENTER: + case META_GRAVITY_EAST: + /* Find center of frame. */ + y += frame_extents.height / 2; + /* Center client window there. */ + y -= h / 2; + break; + case META_GRAVITY_SOUTH_WEST: + case META_GRAVITY_SOUTH: + case META_GRAVITY_SOUTH_EAST: + /* Find south edge of frame */ + y += frame_extents.height; + /* Place bottom edge of client there */ + y -= h; + break; + default: + break; + } + + if (root_x) + *root_x = x; + if (root_y) + *root_y = y; +} + +void +meta_window_get_session_geometry (MetaWindow *window, + int *x, + int *y, + int *width, + int *height) +{ + meta_window_get_gravity_position (window, + window->size_hints.win_gravity, + x, y); + + *width = (window->rect.width - window->size_hints.base_width) / + window->size_hints.width_inc; + *height = (window->rect.height - window->size_hints.base_height) / + window->size_hints.height_inc; +} + +/** + * meta_window_get_buffer_rect: + * @window: a #MetaWindow + * @rect: (out): pointer to an allocated #MetaRectangle + * + * Gets the rectangle that the pixmap or buffer of @window occupies. + * + * For X11 windows, this is the server-side geometry of the toplevel + * window. + * + * For Wayland windows, this is the bounding rectangle of the attached + * buffer. + */ +void +meta_window_get_buffer_rect (const MetaWindow *window, + MetaRectangle *rect) +{ + *rect = window->buffer_rect; +} + +/** + * meta_window_client_rect_to_frame_rect: + * @window: a #MetaWindow + * @client_rect: client rectangle in root coordinates + * @frame_rect: (out): location to store the computed corresponding frame bounds. + * + * Converts a desired bounds of the client window into the corresponding bounds + * of the window frame (excluding invisible borders and client side shadows.) + */ +void +meta_window_client_rect_to_frame_rect (MetaWindow *window, + MetaRectangle *client_rect, + MetaRectangle *frame_rect) +{ + if (!frame_rect) + return; + + *frame_rect = *client_rect; + + /* The support for G_MAXINT here to mean infinity is a convenience for + * constraints.c:get_size_limits() and not something that we provide + * in other locations or document. + */ + if (window->frame) + { + MetaFrameBorders borders; + meta_frame_calc_borders (window->frame, &borders); + + frame_rect->x -= borders.visible.left; + frame_rect->y -= borders.visible.top; + if (frame_rect->width != G_MAXINT) + frame_rect->width += borders.visible.left + borders.visible.right; + if (frame_rect->height != G_MAXINT) + frame_rect->height += borders.visible.top + borders.visible.bottom; + } + else + { + const MetaFrameBorder *extents = &window->custom_frame_extents; + frame_rect->x += extents->left; + frame_rect->y += extents->top; + if (frame_rect->width != G_MAXINT) + frame_rect->width -= extents->left + extents->right; + if (frame_rect->height != G_MAXINT) + frame_rect->height -= extents->top + extents->bottom; + } +} + +/** + * meta_window_frame_rect_to_client_rect: + * @window: a #MetaWindow + * @frame_rect: desired frame bounds for the window + * @client_rect: (out): location to store the computed corresponding client rectangle. + * + * Converts a desired frame bounds for a window into the bounds of the client + * window. + */ +void +meta_window_frame_rect_to_client_rect (MetaWindow *window, + MetaRectangle *frame_rect, + MetaRectangle *client_rect) +{ + if (!client_rect) + return; + + *client_rect = *frame_rect; + + if (window->frame) + { + MetaFrameBorders borders; + meta_frame_calc_borders (window->frame, &borders); + + client_rect->x += borders.visible.left; + client_rect->y += borders.visible.top; + client_rect->width -= borders.visible.left + borders.visible.right; + client_rect->height -= borders.visible.top + borders.visible.bottom; + } + else + { + const MetaFrameBorder *extents = &window->custom_frame_extents; + client_rect->x -= extents->left; + client_rect->y -= extents->top; + client_rect->width += extents->left + extents->right; + client_rect->height += extents->top + extents->bottom; + } +} + +/** + * meta_window_get_frame_rect: + * @window: a #MetaWindow + * @rect: (out): pointer to an allocated #MetaRectangle + * + * Gets the rectangle that bounds @window that is what the user thinks of + * as the edge of the window. This doesn't include any extra reactive + * area that we or the client adds to the window, or any area that the + * client adds to draw a client-side shadow. + */ +void +meta_window_get_frame_rect (const MetaWindow *window, + MetaRectangle *rect) +{ + *rect = window->rect; +} + +/** + * meta_window_get_client_area_rect: + * @window: a #MetaWindow + * @rect: (out): pointer to a cairo rectangle + * + * Gets the rectangle for the boundaries of the client area, relative + * to the buffer rect. + */ +void +meta_window_get_client_area_rect (const MetaWindow *window, + cairo_rectangle_int_t *rect) +{ + MetaFrameBorders borders; + + meta_frame_calc_borders (window->frame, &borders); + + rect->x = borders.total.left; + rect->y = borders.total.top; + + rect->width = window->buffer_rect.width - borders.total.left - borders.total.right; + rect->height = window->buffer_rect.height - borders.total.top - borders.total.bottom; +} + +void +meta_window_get_titlebar_rect (MetaWindow *window, + MetaRectangle *rect) +{ + meta_window_get_frame_rect (window, rect); + + /* The returned rectangle is relative to the frame rect. */ + rect->x = 0; + rect->y = 0; + + if (window->frame) + { + rect->height = window->frame->child_y; + } + else + { + /* Pick an arbitrary height for a titlebar. We might want to + * eventually have CSD windows expose their borders to us. */ + rect->height = 50; + } +} + +/** + * meta_window_get_startup_id: + * @window: a #MetaWindow + * + * Gets the startup id of the given #MetaWindow + * + * Returns: (nullable): the startup id + */ +const char* +meta_window_get_startup_id (MetaWindow *window) +{ + if (window->startup_id == NULL) + { + MetaGroup *group; + + group = meta_window_get_group (window); + + if (group != NULL) + return meta_group_get_startup_id (group); + } + + return window->startup_id; +} + +static MetaWindow* +get_modal_transient (MetaWindow *window) +{ + GSList *windows; + GSList *tmp; + MetaWindow *modal_transient; + + /* A window can't be the transient of itself, but this is just for + * convenience in the loop below; we manually fix things up at the + * end if no real modal transient was found. + */ + modal_transient = window; + + windows = meta_display_list_windows (window->display, META_LIST_DEFAULT); + tmp = windows; + while (tmp != NULL) + { + MetaWindow *transient = tmp->data; + + if (transient->transient_for == modal_transient && + transient->type == META_WINDOW_MODAL_DIALOG) + { + modal_transient = transient; + tmp = windows; + continue; + } + + tmp = tmp->next; + } + + g_slist_free (windows); + + if (window == modal_transient) + modal_transient = NULL; + + return modal_transient; +} + +static gboolean +meta_window_transient_can_focus (MetaWindow *window) +{ +#ifdef HAVE_WAYLAND + if (window->client_type == META_WINDOW_CLIENT_TYPE_WAYLAND) + { + MetaWaylandSurface *surface = meta_window_get_wayland_surface (window); + return meta_wayland_surface_get_buffer (surface) != NULL; + } +#endif + + return TRUE; +} + +static void +meta_window_make_most_recent (MetaWindow *window) +{ + MetaWorkspaceManager *workspace_manager = window->display->workspace_manager; + GList *l; + + for (l = workspace_manager->workspaces; l != NULL; l = l->next) + { + MetaWorkspace *workspace = l->data; + GList *link; + + link = g_list_find (workspace->mru_list, window); + if (!link) + continue; + + workspace->mru_list = g_list_delete_link (workspace->mru_list, link); + workspace->mru_list = g_list_prepend (workspace->mru_list, window); + } +} + +/* XXX META_EFFECT_FOCUS */ +void +meta_window_focus (MetaWindow *window, + guint32 timestamp) +{ + MetaWorkspaceManager *workspace_manager = window->display->workspace_manager; + MetaWindow *modal_transient; + MetaBackend *backend; + ClutterStage *stage; + MetaWindowDrag *window_drag; + MetaWindow *grab_window = NULL; + + g_return_if_fail (!window->override_redirect); + + /* This is a oneshot flag */ + window->restore_focus_on_map = FALSE; + + meta_topic (META_DEBUG_FOCUS, + "Setting input focus to window %s, input: %d focusable: %d", + window->desc, window->input, meta_window_is_focusable (window)); + + if (window->in_workspace_change) + { + meta_topic (META_DEBUG_FOCUS, + "Window %s is currently changing workspaces, not focusing it after all", + window->desc); + return; + } + + window_drag = + meta_compositor_get_current_window_drag (window->display->compositor); + if (window_drag) + grab_window = meta_window_drag_get_window (window_drag); + + if (grab_window && + grab_window != window && + !grab_window->unmanaging) + { + meta_topic (META_DEBUG_FOCUS, + "Current focus window %s has global keygrab, not focusing window %s after all", + grab_window->desc, window->desc); + return; + } + + modal_transient = get_modal_transient (window); + if (modal_transient != NULL && + !modal_transient->unmanaging && + meta_window_transient_can_focus (modal_transient)) + { + meta_topic (META_DEBUG_FOCUS, + "%s has %s as a modal transient, so focusing it instead.", + window->desc, modal_transient->desc); + if (!meta_window_located_on_workspace (modal_transient, workspace_manager->active_workspace)) + meta_window_change_workspace (modal_transient, workspace_manager->active_workspace); + window = modal_transient; + } + + meta_window_flush_calc_showing (window); + + if (!window->mapped || window->hidden) + { + meta_topic (META_DEBUG_FOCUS, + "Window %s is not showing, not focusing after all", + window->desc); + return; + } + + META_WINDOW_GET_CLASS (window)->focus (window, timestamp); + + /* Move to the front of all workspaces' MRU lists the window + * is on. We should only be "removing" it from the MRU list if + * it's already there. Note that it's possible that we might + * be processing this FocusIn after we've changed to a + * different workspace; we should therefore update the MRU + * list only if the window is actually on the active + * workspace. + */ + if (workspace_manager->active_workspace && + meta_window_located_on_workspace (window, + workspace_manager->active_workspace)) + meta_window_make_most_recent (window); + + backend = backend_from_window (window); + stage = CLUTTER_STAGE (meta_backend_get_stage (backend)); + + if (clutter_stage_get_grab_actor (stage) == NULL) + clutter_stage_set_key_focus (stage, NULL); + + if (window->close_dialog && + meta_close_dialog_is_visible (window->close_dialog)) + meta_close_dialog_focus (window->close_dialog); + + if (window->wm_state_demands_attention) + meta_window_unset_demands_attention(window); + +/* meta_effect_run_focus(window, NULL, NULL); */ +} + +/* Workspace management. Invariants: + * + * - window->workspace describes the workspace the window is on. + * + * - workspace->windows is a list of windows that is located on + * that workspace. + * + * - If the window is on_all_workspaces, then + * window->workspace == NULL, but workspace->windows contains + * the window. + */ + +static void +set_workspace_state (MetaWindow *window, + gboolean on_all_workspaces, + MetaWorkspace *workspace) +{ + MetaWorkspaceManager *workspace_manager = window->display->workspace_manager; + + /* If we're on all workspaces, then our new workspace must be NULL, + * otherwise it must be set, unless we're unmanaging. */ + if (on_all_workspaces) + g_assert_null (workspace); + else + g_assert_true (window->unmanaging || workspace != NULL); + + /* If this is an override-redirect window, ensure that the only + * times we're setting the workspace state is either during construction + * to mark as on_all_workspaces, or when unmanaging to remove all the + * workspaces. */ + if (window->override_redirect) + g_return_if_fail ((window->constructing && on_all_workspaces) || window->unmanaging); + + if (on_all_workspaces == window->on_all_workspaces && + workspace == window->workspace && + !window->constructing) + return; + + window->in_workspace_change = TRUE; + + if (window->workspace) + meta_workspace_remove_window (window->workspace, window); + else if (window->on_all_workspaces) + { + GList *l; + for (l = workspace_manager->workspaces; l != NULL; l = l->next) + { + MetaWorkspace *ws = l->data; + meta_workspace_remove_window (ws, window); + } + } + + window->on_all_workspaces = on_all_workspaces; + window->workspace = workspace; + + if (window->workspace) + meta_workspace_add_window (window->workspace, window); + else if (window->on_all_workspaces) + { + GList *l; + for (l = workspace_manager->workspaces; l != NULL; l = l->next) + { + MetaWorkspace *ws = l->data; + meta_workspace_add_window (ws, window); + } + } + + window->in_workspace_change = FALSE; + + if (!window->constructing) + meta_window_update_appears_focused (window); + + /* queue a move_resize since changing workspaces may change + * the relevant struts + */ + if (!window->override_redirect) + meta_window_queue (window, META_QUEUE_MOVE_RESIZE); + meta_window_queue (window, META_QUEUE_CALC_SHOWING); + meta_window_current_workspace_changed (window); + g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_ON_ALL_WORKSPACES]); + g_signal_emit (window, window_signals[WORKSPACE_CHANGED], 0); +} + +static gboolean +should_be_on_all_workspaces (MetaWindow *window) +{ + if (window->always_sticky) + return TRUE; + + if (window->on_all_workspaces_requested) + return TRUE; + + if (window->override_redirect) + return TRUE; + + if (meta_prefs_get_workspaces_only_on_primary () && + !window->unmanaging && + window->monitor && + !meta_window_is_on_primary_monitor (window)) + return TRUE; + + return FALSE; +} + +void +meta_window_on_all_workspaces_changed (MetaWindow *window) +{ + MetaWorkspaceManager *workspace_manager = window->display->workspace_manager; + gboolean on_all_workspaces = should_be_on_all_workspaces (window); + + if (window->on_all_workspaces == on_all_workspaces) + return; + + MetaWorkspace *workspace; + + if (on_all_workspaces) + { + workspace = NULL; + } + else + { + /* We're coming out of the sticky state. Put the window on + * the currently active workspace. */ + workspace = workspace_manager->active_workspace; + } + + set_workspace_state (window, on_all_workspaces, workspace); +} + +static void +meta_window_change_workspace_without_transients (MetaWindow *window, + MetaWorkspace *workspace) +{ + if (window->unmanaging) + return; + + /* Try to unstick the window if it's stuck. This doesn't + * have any guarantee that we'll actually unstick the + * window, since it could be stuck for other reasons. */ + if (window->on_all_workspaces_requested) + meta_window_unstick (window); + + /* We failed to unstick the window. */ + if (window->on_all_workspaces) + return; + + if (window->workspace == workspace) + return; + + set_workspace_state (window, FALSE, workspace); +} + +static gboolean +change_workspace_foreach (MetaWindow *window, + void *data) +{ + meta_window_change_workspace_without_transients (window, data); + return TRUE; +} + +void +meta_window_change_workspace (MetaWindow *window, + MetaWorkspace *workspace) +{ + g_return_if_fail (!window->override_redirect); + + meta_window_change_workspace_without_transients (window, workspace); + + meta_window_foreach_transient (window, change_workspace_foreach, + workspace); + meta_window_foreach_ancestor (window, change_workspace_foreach, + workspace); +} + +static void +window_stick_impl (MetaWindow *window) +{ + meta_verbose ("Sticking window %s current on_all_workspaces = %d", + window->desc, window->on_all_workspaces); + + if (window->on_all_workspaces_requested) + return; + + /* We don't change window->workspaces, because we revert + * to that original workspace list if on_all_workspaces is + * toggled back off. + */ + window->on_all_workspaces_requested = TRUE; + meta_window_on_all_workspaces_changed (window); +} + +static void +window_unstick_impl (MetaWindow *window) +{ + if (!window->on_all_workspaces_requested) + return; + + /* Revert to window->workspaces */ + + window->on_all_workspaces_requested = FALSE; + meta_window_on_all_workspaces_changed (window); +} + +static gboolean +stick_foreach_func (MetaWindow *window, + void *data) +{ + gboolean stick; + + stick = *(gboolean*)data; + if (stick) + window_stick_impl (window); + else + window_unstick_impl (window); + return TRUE; +} + +void +meta_window_stick (MetaWindow *window) +{ + gboolean stick = TRUE; + + g_return_if_fail (!window->override_redirect); + + window_stick_impl (window); + meta_window_foreach_transient (window, + stick_foreach_func, + &stick); +} + +void +meta_window_unstick (MetaWindow *window) +{ + gboolean stick = FALSE; + + g_return_if_fail (!window->override_redirect); + + window_unstick_impl (window); + meta_window_foreach_transient (window, + stick_foreach_func, + &stick); +} + +void +meta_window_current_workspace_changed (MetaWindow *window) +{ + META_WINDOW_GET_CLASS (window)->current_workspace_changed (window); +} + +static gboolean +find_root_ancestor (MetaWindow *window, + void *data) +{ + MetaWindow **ancestor = data; + + /* Overwrite the previously "most-root" ancestor with the new one found */ + *ancestor = window; + + /* We want this to continue until meta_window_foreach_ancestor quits because + * there are no more valid ancestors. + */ + return TRUE; +} + +/** + * meta_window_find_root_ancestor: + * @window: a #MetaWindow + * + * Follow the chain of parents of @window, skipping transient windows, + * and return the "root" window which has no non-transient parent. + * + * Returns: (transfer none): The root ancestor window + */ +MetaWindow * +meta_window_find_root_ancestor (MetaWindow *window) +{ + MetaWindow *ancestor; + ancestor = window; + meta_window_foreach_ancestor (window, find_root_ancestor, &ancestor); + return ancestor; +} + +void +meta_window_raise (MetaWindow *window) +{ + MetaWindow *ancestor; + + g_return_if_fail (!window->override_redirect); + + ancestor = meta_window_find_root_ancestor (window); + + meta_topic (META_DEBUG_WINDOW_OPS, + "Raising window %s, ancestor of %s", + ancestor->desc, window->desc); + + /* Raise the ancestor of the window (if the window has no ancestor, + * then ancestor will be set to the window itself); do this because + * it's weird to see windows from other apps stacked between a child + * and parent window of the currently active app. The stacking + * constraints in stack.c then magically take care of raising all + * the child windows appropriately. + */ + if (window->display->stack == ancestor->display->stack) + { + meta_stack_raise (window->display->stack, ancestor); + } + else + { + meta_warning ( + "Either stacks aren't per screen or some window has a weird " + "transient_for hint; window->display->stack != " + "ancestor->screen->stack. window = %s, ancestor = %s.", + window->desc, ancestor->desc); + /* We could raise the window here, but don't want to do that twice and + * so we let the case below handle that. + */ + } + + /* Okay, so stacking constraints misses one case: If a window has + * two children and we want to raise one of those children, then + * raising the ancestor isn't enough; we need to also raise the + * correct child. See bug 307875. + */ + if (window != ancestor) + meta_stack_raise (window->display->stack, window); + + g_signal_emit (window, window_signals[RAISED], 0); +} + +void +meta_window_raise_and_make_recent (MetaWindow *window) +{ + g_return_if_fail (META_IS_WINDOW (window)); + + meta_window_raise (window); + meta_window_make_most_recent (window); +} + +void +meta_window_lower (MetaWindow *window) +{ + g_return_if_fail (!window->override_redirect); + + meta_topic (META_DEBUG_WINDOW_OPS, + "Lowering window %s", window->desc); + + meta_stack_lower (window->display->stack, window); +} + +static gboolean +lower_window_and_transients (MetaWindow *window, + gpointer user_data) +{ + MetaWorkspaceManager *workspace_manager = window->display->workspace_manager; + + meta_window_lower (window); + + meta_window_foreach_transient (window, lower_window_and_transients, NULL); + + if (meta_prefs_get_raise_on_click ()) + { + /* Move window to the back of the focusing workspace's MRU list. + * Do extra sanity checks to avoid possible race conditions. + * (Borrowed from window.c.) + */ + if (workspace_manager->active_workspace && + meta_window_located_on_workspace (window, + workspace_manager->active_workspace)) + { + GList *link; + link = g_list_find (workspace_manager->active_workspace->mru_list, + window); + g_assert (link); + + workspace_manager->active_workspace->mru_list = + g_list_remove_link (workspace_manager->active_workspace->mru_list, + link); + g_list_free (link); + + workspace_manager->active_workspace->mru_list = + g_list_append (workspace_manager->active_workspace->mru_list, + window); + } + } + + return FALSE; +} + +void +meta_window_lower_with_transients (MetaWindow *window, + uint32_t timestamp) +{ + MetaWorkspaceManager *workspace_manager = window->display->workspace_manager; + + lower_window_and_transients (window, NULL); + + /* Rather than try to figure that out whether we just lowered + * the focus window, assume that's always the case. (Typically, + * this will be invoked via keyboard action or by a mouse action; + * in either case the window or a modal child will have been focused.) */ + meta_workspace_focus_default_window (workspace_manager->active_workspace, + NULL, + timestamp); +} + +/* + * Move window to the requested workspace; append controls whether new WS + * should be created if one does not exist. + */ +void +meta_window_change_workspace_by_index (MetaWindow *window, + gint space_index, + gboolean append) +{ + MetaWorkspaceManager *workspace_manager; + MetaWorkspace *workspace; + MetaDisplay *display; + + g_return_if_fail (!window->override_redirect); + + if (space_index == -1) + { + meta_window_stick (window); + return; + } + + display = window->display; + workspace_manager = display->workspace_manager; + + workspace = + meta_workspace_manager_get_workspace_by_index (workspace_manager, space_index); + + if (!workspace && append) + workspace = meta_workspace_manager_append_new_workspace (workspace_manager, FALSE, META_CURRENT_TIME); + + if (workspace) + meta_window_change_workspace (window, workspace); +} + +void +meta_window_update_appears_focused (MetaWindow *window) +{ + MetaWorkspaceManager *workspace_manager; + MetaWorkspace *workspace; + gboolean appears_focused; + + workspace_manager = window->display->workspace_manager; + workspace = meta_window_get_workspace (window); + + if (workspace && workspace != workspace_manager->active_workspace) + { + appears_focused = + window == meta_workspace_get_default_focus_window (workspace, NULL) && + meta_prefs_get_focus_mode () == G_DESKTOP_FOCUS_MODE_CLICK; + } + else + { + appears_focused = window->has_focus || window->attached_focus_window; + } + + if (window->appears_focused == appears_focused) + return; + + window->appears_focused = appears_focused; + + set_net_wm_state (window); + meta_window_frame_size_changed (window); + + g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_APPEARS_FOCUSED]); +} + +static gboolean +should_propagate_focus_appearance (MetaWindow *window) +{ + /* Parents of attached modal dialogs should appear focused. */ + if (meta_window_is_attached_dialog (window)) + return TRUE; + + /* Parents of these sorts of override-redirect windows should + * appear focused. */ + switch (window->type) + { + case META_WINDOW_DROPDOWN_MENU: + case META_WINDOW_POPUP_MENU: + case META_WINDOW_COMBO: + case META_WINDOW_TOOLTIP: + case META_WINDOW_NOTIFICATION: + case META_WINDOW_DND: + case META_WINDOW_OVERRIDE_OTHER: + return TRUE; + default: + break; + } + + return FALSE; +} + +/** + * meta_window_propagate_focus_appearance: + * @window: the window to start propagating from + * @focused: %TRUE if @window's ancestors should appear focused, + * %FALSE if they should not. + * + * Adjusts the value of #MetaWindow:appears-focused on @window's + * ancestors (but not @window itself). If @focused is %TRUE, each of + * @window's ancestors will have its %attached_focus_window field set + * to the current %focus_window. If @focused if %FALSE, each of + * @window's ancestors will have its %attached_focus_window field + * cleared if it is currently %focus_window. + */ +static void +meta_window_propagate_focus_appearance (MetaWindow *window, + gboolean focused) +{ + MetaWindow *child, *parent, *focus_window; + + focus_window = window->display->focus_window; + + child = window; + parent = meta_window_get_transient_for (child); + while (parent && (!focused || should_propagate_focus_appearance (child))) + { + gboolean child_focus_state_changed; + + if (focused) + { + if (parent->attached_focus_window == focus_window) + break; + child_focus_state_changed = (parent->attached_focus_window == NULL); + parent->attached_focus_window = focus_window; + } + else + { + if (parent->attached_focus_window != focus_window) + break; + child_focus_state_changed = (parent->attached_focus_window != NULL); + parent->attached_focus_window = NULL; + } + + if (child_focus_state_changed && !parent->has_focus) + { + meta_window_update_appears_focused (parent); + } + + child = parent; + parent = meta_window_get_transient_for (child); + } +} + +void +meta_window_set_focused_internal (MetaWindow *window, + gboolean focused) +{ + if (focused) + { + window->has_focus = TRUE; + if (window->override_redirect) + return; + + /* Ungrab click to focus button since the sync grab can interfere + * with some things you might do inside the focused window, by + * causing the client to get funky enter/leave events. + * + * The reason we usually have a passive grab on the window is + * so that we can intercept clicks and raise the window in + * response. For click-to-focus we don't need that since the + * focused window is already raised. When raise_on_click is + * FALSE we also don't need that since we don't do anything + * when the window is clicked. + * + * There is dicussion in bugs 102209, 115072, and 461577 + */ + if (meta_prefs_get_focus_mode () == G_DESKTOP_FOCUS_MODE_CLICK || + !meta_prefs_get_raise_on_click()) + { + meta_display_ungrab_focus_window_button (window->display, window); + /* Since we ungrab with XIAnyModifier above, all button + grabs go way so we need to re-grab the window buttons. */ + meta_display_grab_window_buttons (window->display, window); + } + + g_signal_emit (window, window_signals[FOCUS], 0); + + if (!window->attached_focus_window) + meta_window_update_appears_focused (window); + + meta_window_propagate_focus_appearance (window, TRUE); + } + else + { + window->has_focus = FALSE; + if (window->override_redirect) + return; + + meta_window_propagate_focus_appearance (window, FALSE); + + if (!window->attached_focus_window) + meta_window_update_appears_focused (window); + + /* Re-grab for click to focus and raise-on-click, if necessary */ + if (meta_prefs_get_focus_mode () == G_DESKTOP_FOCUS_MODE_CLICK || + !meta_prefs_get_raise_on_click ()) + meta_display_grab_focus_window_button (window->display, window); + } +} + +/** + * meta_window_get_icon_geometry: + * @window: a #MetaWindow + * @rect: (out): rectangle into which to store the returned geometry. + * + * Gets the location of the icon corresponding to the window. The location + * will be provided set by the task bar or other user interface element + * displaying the icon, and is relative to the root window. + * + * Return value: %TRUE if the icon geometry was successfully retrieved. + */ +gboolean +meta_window_get_icon_geometry (MetaWindow *window, + MetaRectangle *rect) +{ + g_return_val_if_fail (!window->override_redirect, FALSE); + + if (window->icon_geometry_set) + { + if (rect) + *rect = window->icon_geometry; + + return TRUE; + } + + return FALSE; +} + +/** + * meta_window_set_icon_geometry: + * @window: a #MetaWindow + * @rect: (nullable): rectangle with the desired geometry or %NULL. + * + * Sets or unsets the location of the icon corresponding to the window. If + * set, the location should correspond to a dock, task bar or other user + * interface element displaying the icon, and is relative to the root window. + */ +void +meta_window_set_icon_geometry (MetaWindow *window, + MetaRectangle *rect) +{ + if (rect) + { + window->icon_geometry = *rect; + window->icon_geometry_set = TRUE; + } + else + { + window->icon_geometry_set = FALSE; + } +} + +cairo_surface_t * +meta_window_get_icon (MetaWindow *window) +{ + MetaWindowClass *klass = META_WINDOW_GET_CLASS (window); + + if (klass->get_icon) + return klass->get_icon (window); + else + return NULL; +} + +cairo_surface_t * +meta_window_get_mini_icon (MetaWindow *window) +{ + MetaWindowClass *klass = META_WINDOW_GET_CLASS (window); + + if (klass->get_mini_icon) + return klass->get_mini_icon (window); + else + return NULL; +} + +GList* +meta_window_get_workspaces (MetaWindow *window) +{ + MetaWorkspaceManager *workspace_manager = window->display->workspace_manager; + + if (window->on_all_workspaces) + return workspace_manager->workspaces; + else if (window->workspace != NULL) + return window->workspace->list_containing_self; + else if (window->constructing) + return NULL; + else + g_assert_not_reached (); + return NULL; +} + +static void +invalidate_work_areas (MetaWindow *window) +{ + GList *tmp; + + tmp = meta_window_get_workspaces (window); + + while (tmp != NULL) + { + meta_workspace_invalidate_work_area (tmp->data); + tmp = tmp->next; + } +} + +void +meta_window_update_struts (MetaWindow *window) +{ + if (META_WINDOW_GET_CLASS (window)->update_struts (window)) + invalidate_work_areas (window); +} + +static void +meta_window_type_changed (MetaWindow *window) +{ + gboolean old_decorated = window->decorated; + GObject *object = G_OBJECT (window); + + window->attached = meta_window_should_attach_to_parent (window); + meta_window_recalc_features (window); + + if (!window->override_redirect) + set_net_wm_state (window); + + /* Update frame */ + if (window->decorated) + meta_window_ensure_frame (window); + else + meta_window_destroy_frame (window); + + /* update stacking constraints */ + meta_window_update_layer (window); + + meta_window_grab_keys (window); + + g_object_freeze_notify (object); + + if (old_decorated != window->decorated) + g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_DECORATED]); + + g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_WINDOW_TYPE]); + + g_object_thaw_notify (object); +} + +void +meta_window_set_type (MetaWindow *window, + MetaWindowType type) +{ + if (window->type == type) + return; + + window->type = type; + meta_window_type_changed (window); +} + +void +meta_window_frame_size_changed (MetaWindow *window) +{ + if (window->frame) + meta_frame_clear_cached_borders (window->frame); +} + +static void +meta_window_get_default_skip_hints (MetaWindow *window, + gboolean *skip_taskbar_out, + gboolean *skip_pager_out) +{ + META_WINDOW_GET_CLASS (window)->get_default_skip_hints (window, skip_taskbar_out, skip_pager_out); +} + +static void +meta_window_recalc_skip_features (MetaWindow *window) +{ + switch (window->type) + { + /* Force skip taskbar/pager on these window types */ + case META_WINDOW_DESKTOP: + case META_WINDOW_DOCK: + case META_WINDOW_TOOLBAR: + case META_WINDOW_MENU: + case META_WINDOW_UTILITY: + case META_WINDOW_SPLASHSCREEN: + case META_WINDOW_DROPDOWN_MENU: + case META_WINDOW_POPUP_MENU: + case META_WINDOW_TOOLTIP: + case META_WINDOW_NOTIFICATION: + case META_WINDOW_COMBO: + case META_WINDOW_DND: + case META_WINDOW_OVERRIDE_OTHER: + window->skip_taskbar = TRUE; + window->skip_pager = TRUE; + break; + + case META_WINDOW_DIALOG: + case META_WINDOW_MODAL_DIALOG: + /* only skip taskbar if we have a real transient parent + (and ignore the application hints) */ + if (window->transient_for != NULL) + window->skip_taskbar = TRUE; + else + window->skip_taskbar = window->skip_from_window_list; + break; + + case META_WINDOW_NORMAL: + { + gboolean skip_taskbar_hint, skip_pager_hint; + meta_window_get_default_skip_hints (window, &skip_taskbar_hint, &skip_pager_hint); + window->skip_taskbar = skip_taskbar_hint | window->skip_from_window_list; + window->skip_pager = skip_pager_hint | window->skip_from_window_list; + } + break; + } +} + +void +meta_window_recalc_features (MetaWindow *window) +{ + gboolean old_has_close_func; + gboolean old_has_minimize_func; + gboolean old_has_move_func; + gboolean old_has_resize_func; + gboolean old_always_sticky; + gboolean old_skip_taskbar; + + old_has_close_func = window->has_close_func; + old_has_minimize_func = window->has_minimize_func; + old_has_move_func = window->has_move_func; + old_has_resize_func = window->has_resize_func; + old_always_sticky = window->always_sticky; + old_skip_taskbar = window->skip_taskbar; + + /* Use MWM hints initially */ + if (window->client_type == META_WINDOW_CLIENT_TYPE_X11) + window->decorated = window->mwm_decorated; + else + window->decorated = FALSE; + window->border_only = window->mwm_border_only; + window->has_close_func = window->mwm_has_close_func; + window->has_minimize_func = window->mwm_has_minimize_func; + window->has_maximize_func = window->mwm_has_maximize_func; + window->has_move_func = window->mwm_has_move_func; + + window->has_resize_func = TRUE; + + /* If min_size == max_size, then don't allow resize */ + if (window->size_hints.min_width == window->size_hints.max_width && + window->size_hints.min_height == window->size_hints.max_height) + window->has_resize_func = FALSE; + else if (!window->mwm_has_resize_func) + { + /* We ignore mwm_has_resize_func because WM_NORMAL_HINTS is the + * authoritative source for that info. Some apps such as mplayer or + * xine disable resize via MWM but not WM_NORMAL_HINTS, but that + * leads to e.g. us not fullscreening their windows. Apps that set + * MWM but not WM_NORMAL_HINTS are basically broken. We complain + * about these apps but make them work. + */ + + meta_warning ("Window %s sets an MWM hint indicating it isn't resizable, but sets min size %d x %d and max size %d x %d; this doesn't make much sense.", + window->desc, + window->size_hints.min_width, + window->size_hints.min_height, + window->size_hints.max_width, + window->size_hints.max_height); + } + + window->has_fullscreen_func = TRUE; + + window->always_sticky = FALSE; + + /* Semantic category overrides the MWM hints */ + if (window->type == META_WINDOW_TOOLBAR) + window->decorated = FALSE; + + if (window->type == META_WINDOW_DESKTOP || + window->type == META_WINDOW_DOCK || + window->override_redirect) + window->always_sticky = TRUE; + + if (window->override_redirect || + meta_window_get_frame_type (window) == META_FRAME_TYPE_LAST) + { + window->decorated = FALSE; + window->has_close_func = FALSE; + + /* FIXME this keeps panels and things from using + * NET_WM_MOVERESIZE; the problem is that some + * panels (edge panels) have fixed possible locations, + * and others ("floating panels") do not. + * + * Perhaps we should require edge panels to explicitly + * disable movement? + */ + window->has_move_func = FALSE; + window->has_resize_func = FALSE; + } + + if (window->type != META_WINDOW_NORMAL) + { + window->has_minimize_func = FALSE; + window->has_maximize_func = FALSE; + window->has_fullscreen_func = FALSE; + } + + if (!window->has_resize_func) + { + window->has_maximize_func = FALSE; + MetaRectangle display_rect = { 0 }; + + meta_display_get_size (window->display, &display_rect.width, + &display_rect.height); + + /* don't allow fullscreen if we can't resize, unless the size + * is entire screen size (kind of broken, because we + * actually fullscreen to monitor size not screen size) + */ + if (window->size_hints.min_width == display_rect.width && + window->size_hints.min_height == display_rect.height) + ; /* leave fullscreen available */ + else + window->has_fullscreen_func = FALSE; + } + + /* We leave fullscreen windows decorated, just push the frame outside + * the screen. This avoids flickering to unparent them. + * + * Note that setting has_resize_func = FALSE here must come after the + * above code that may disable fullscreen, because if the window + * is not resizable purely due to fullscreen, we don't want to + * disable fullscreen mode. + */ + if (window->fullscreen) + { + window->has_move_func = FALSE; + window->has_resize_func = FALSE; + window->has_maximize_func = FALSE; + } + + if (window->has_maximize_func && window->monitor) + { + MetaRectangle work_area, client_rect; + + meta_window_get_work_area_current_monitor (window, &work_area); + meta_window_frame_rect_to_client_rect (window, &work_area, &client_rect); + + if (window->size_hints.min_width > client_rect.width || + window->size_hints.min_height > client_rect.height) + window->has_maximize_func = FALSE; + } + + meta_topic (META_DEBUG_WINDOW_OPS, + "Window %s fullscreen = %d not resizable, maximizable = %d fullscreenable = %d min size %dx%d max size %dx%d", + window->desc, + window->fullscreen, + window->has_maximize_func, window->has_fullscreen_func, + window->size_hints.min_width, + window->size_hints.min_height, + window->size_hints.max_width, + window->size_hints.max_height); + + meta_window_recalc_skip_features (window); + + /* To prevent users from losing windows, let's prevent users from + * minimizing skip-taskbar windows through the window decorations. */ + if (window->skip_taskbar) + window->has_minimize_func = FALSE; + + meta_topic (META_DEBUG_WINDOW_OPS, + "Window %s decorated = %d border_only = %d has_close = %d has_minimize = %d has_maximize = %d has_move = %d skip_taskbar = %d skip_pager = %d", + window->desc, + window->decorated, + window->border_only, + window->has_close_func, + window->has_minimize_func, + window->has_maximize_func, + window->has_move_func, + window->skip_taskbar, + window->skip_pager); + + if (old_skip_taskbar != window->skip_taskbar) + g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_SKIP_TASKBAR]); + + /* FIXME: + * Lame workaround for recalc_features being used overzealously. + * The fix is to only recalc_features when something has + * actually changed. + */ + if (window->constructing || + old_has_close_func != window->has_close_func || + old_has_minimize_func != window->has_minimize_func || + old_has_move_func != window->has_move_func || + old_has_resize_func != window->has_resize_func || + old_always_sticky != window->always_sticky) + set_allowed_actions_hint (window); + + if (window->has_resize_func != old_has_resize_func) + g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_RESIZEABLE]); + + meta_window_frame_size_changed (window); +} + +void +meta_window_show_menu (MetaWindow *window, + MetaWindowMenuType menu, + int x, + int y) +{ + g_return_if_fail (!window->override_redirect); + meta_compositor_show_window_menu (window->display->compositor, window, menu, x, y); +} + +void +meta_window_show_menu_for_rect (MetaWindow *window, + MetaWindowMenuType menu, + MetaRectangle *rect) +{ + g_return_if_fail (!window->override_redirect); + meta_compositor_show_window_menu_for_rect (window->display->compositor, window, menu, rect); +} + +void +meta_window_shove_titlebar_onscreen (MetaWindow *window) +{ + MetaWorkspaceManager *workspace_manager = window->display->workspace_manager; + MetaRectangle frame_rect; + GList *onscreen_region; + int horiz_amount, vert_amount; + + g_return_if_fail (!window->override_redirect); + + /* If there's no titlebar, don't bother */ + if (!window->frame) + return; + + /* Get the basic info we need */ + meta_window_get_frame_rect (window, &frame_rect); + onscreen_region = workspace_manager->active_workspace->screen_region; + + /* Extend the region (just in case the window is too big to fit on the + * screen), then shove the window on screen, then return the region to + * normal. + */ + horiz_amount = frame_rect.width; + vert_amount = frame_rect.height; + meta_rectangle_expand_region (onscreen_region, + horiz_amount, + horiz_amount, + 0, + vert_amount); + meta_rectangle_shove_into_region(onscreen_region, + FIXED_DIRECTION_X, + &frame_rect); + meta_rectangle_expand_region (onscreen_region, + -horiz_amount, + -horiz_amount, + 0, + -vert_amount); + + meta_window_move_frame (window, FALSE, frame_rect.x, frame_rect.y); +} + +gboolean +meta_window_titlebar_is_onscreen (MetaWindow *window) +{ + MetaWorkspaceManager *workspace_manager = window->display->workspace_manager; + MetaRectangle titlebar_rect, frame_rect; + GList *onscreen_region; + gboolean is_onscreen; + + const int min_height_needed = 8; + const float min_width_percent = 0.5; + const int min_width_absolute = 50; + + /* Titlebar can't be offscreen if there is no titlebar... */ + if (!window->frame) + return TRUE; + + /* Get the rectangle corresponding to the titlebar */ + meta_window_get_titlebar_rect (window, &titlebar_rect); + + /* Translate into screen coordinates */ + meta_window_get_frame_rect (window, &frame_rect); + titlebar_rect.x = frame_rect.x; + titlebar_rect.y = frame_rect.y; + + /* Run through the spanning rectangles for the screen and see if one of + * them overlaps with the titlebar sufficiently to consider it onscreen. + */ + is_onscreen = FALSE; + onscreen_region = workspace_manager->active_workspace->screen_region; + while (onscreen_region) + { + MetaRectangle *spanning_rect = onscreen_region->data; + MetaRectangle overlap; + + meta_rectangle_intersect (&titlebar_rect, spanning_rect, &overlap); + if (overlap.height > MIN (titlebar_rect.height, min_height_needed) && + overlap.width > MIN (titlebar_rect.width * min_width_percent, + min_width_absolute)) + { + is_onscreen = TRUE; + break; + } + + onscreen_region = onscreen_region->next; + } + + return is_onscreen; +} + +void +meta_window_get_work_area_for_logical_monitor (MetaWindow *window, + MetaLogicalMonitor *logical_monitor, + MetaRectangle *area) +{ + GList *tmp; + + g_assert (logical_monitor); + + /* Initialize to the whole monitor */ + *area = logical_monitor->rect; + + tmp = meta_window_get_workspaces (window); + while (tmp != NULL) + { + MetaRectangle workspace_work_area; + meta_workspace_get_work_area_for_logical_monitor (tmp->data, + logical_monitor, + &workspace_work_area); + meta_rectangle_intersect (area, + &workspace_work_area, + area); + tmp = tmp->next; + } + + meta_topic (META_DEBUG_WORKAREA, + "Window %s monitor %d has work area %d,%d %d x %d", + window->desc, logical_monitor->number, + area->x, area->y, area->width, area->height); +} + +/** + * meta_window_get_work_area_current_monitor: + * @window: a #MetaWindow + * @area: (out): a location to store the work area + * + * Get the work area for the monitor @window is currently on. + */ +void +meta_window_get_work_area_current_monitor (MetaWindow *window, + MetaRectangle *area) +{ + meta_window_get_work_area_for_monitor (window, + window->monitor->number, + area); +} + +/** + * meta_window_get_work_area_for_monitor: + * @window: a #MetaWindow + * @which_monitor: a moniotr to get the work area for + * @area: (out): a location to store the work area + * + * Get the work area for @window, given the monitor index + * @which_monitor. + */ +void +meta_window_get_work_area_for_monitor (MetaWindow *window, + int which_monitor, + MetaRectangle *area) +{ + MetaBackend *backend = backend_from_window (window); + MetaMonitorManager *monitor_manager = meta_backend_get_monitor_manager (backend); + MetaLogicalMonitor *logical_monitor; + + g_return_if_fail (which_monitor >= 0); + + logical_monitor = + meta_monitor_manager_get_logical_monitor_from_number (monitor_manager, + which_monitor); + + meta_window_get_work_area_for_logical_monitor (window, logical_monitor, area); +} + +/** + * meta_window_get_work_area_all_monitors: + * @window: a #MetaWindow + * @area: (out): a location to store the work area + * + * Get the work area for all monitors for @window. + */ +void +meta_window_get_work_area_all_monitors (MetaWindow *window, + MetaRectangle *area) +{ + GList *tmp; + MetaRectangle display_rect = { 0 }; + + meta_display_get_size (window->display, + &display_rect.width, + &display_rect.height); + + /* Initialize to the whole display */ + *area = display_rect; + + tmp = meta_window_get_workspaces (window); + while (tmp != NULL) + { + MetaRectangle workspace_work_area; + meta_workspace_get_work_area_all_monitors (tmp->data, + &workspace_work_area); + meta_rectangle_intersect (area, + &workspace_work_area, + area); + tmp = tmp->next; + } + + meta_topic (META_DEBUG_WORKAREA, + "Window %s has whole-screen work area %d,%d %d x %d", + window->desc, area->x, area->y, area->width, area->height); +} + +int +meta_window_get_current_tile_monitor_number (MetaWindow *window) +{ + int tile_monitor_number = window->tile_monitor_number; + + if (tile_monitor_number < 0) + { + meta_warning ("%s called with an invalid monitor number; using 0 instead", G_STRFUNC); + tile_monitor_number = 0; + } + + return tile_monitor_number; +} + +void +meta_window_get_tile_area (MetaWindow *window, + MetaTileMode tile_mode, + MetaRectangle *tile_area) +{ + MetaRectangle work_area; + int tile_monitor_number; + double fraction; + + g_return_if_fail (tile_mode != META_TILE_NONE); + + tile_monitor_number = meta_window_get_current_tile_monitor_number (window); + + meta_window_get_work_area_for_monitor (window, tile_monitor_number, &work_area); + meta_window_get_tile_fraction (window, tile_mode, &fraction); + + *tile_area = work_area; + tile_area->width = round (tile_area->width * fraction); + + if (tile_mode == META_TILE_RIGHT) + tile_area->x += work_area.width - tile_area->width; +} + +gboolean +meta_window_same_application (MetaWindow *window, + MetaWindow *other_window) +{ + MetaGroup *group = meta_window_get_group (window); + MetaGroup *other_group = meta_window_get_group (other_window); + + return + group!=NULL && + other_group!=NULL && + group==other_group; +} + +/** + * meta_window_is_client_decorated: + * + * Check if if the window has decorations drawn by the client. + * (window->decorated refers only to whether we should add decorations) + */ +gboolean +meta_window_is_client_decorated (MetaWindow *window) +{ + if (window->client_type == META_WINDOW_CLIENT_TYPE_WAYLAND) + { + /* Assume all Wayland clients draw decorations - not strictly + * true but good enough for current purposes. + */ + return TRUE; + } + else + { + /* Currently the implementation here is hackish - + * has_custom_frame_extents() is set if _GTK_FRAME_EXTENTS is set + * to any value even 0. GTK+ always sets _GTK_FRAME_EXTENTS for + * client-side-decorated window, even if the value is 0 because + * the window is maxized and has no invisible borders or shadows. + */ + return window->has_custom_frame_extents; + } +} + +/** + * meta_window_foreach_transient: + * @window: a #MetaWindow + * @func: (scope call) (closure user_data): Called for each window which is a transient of @window (transitively) + * @user_data: User data + * + * Call @func for every window which is either transient for @window, or is + * a transient of a window which is in turn transient for @window. + * The order of window enumeration is not defined. + * + * Iteration will stop if @func at any point returns %FALSE. + */ +void +meta_window_foreach_transient (MetaWindow *window, + MetaWindowForeachFunc func, + void *user_data) +{ + GSList *windows; + GSList *tmp; + + windows = meta_display_list_windows (window->display, META_LIST_DEFAULT); + + tmp = windows; + while (tmp != NULL) + { + MetaWindow *transient = tmp->data; + + if (meta_window_is_ancestor_of_transient (window, transient)) + { + if (!(* func) (transient, user_data)) + break; + } + + tmp = tmp->next; + } + + g_slist_free (windows); +} + +/** + * meta_window_foreach_ancestor: + * @window: a #MetaWindow + * @func: (scope call) (closure user_data): Called for each window which is a transient parent of @window + * @user_data: User data + * + * If @window is transient, call @func with the window for which it's transient, + * repeatedly until either we find a non-transient window, or @func returns %FALSE. + */ +void +meta_window_foreach_ancestor (MetaWindow *window, + MetaWindowForeachFunc func, + void *user_data) +{ + MetaWindow *w; + + w = window; + do + { + if (w->transient_for == NULL) + break; + + w = w->transient_for; + } + while (w && (* func) (w, user_data)); +} + +typedef struct +{ + MetaWindow *ancestor; + gboolean found; +} FindAncestorData; + +static gboolean +find_ancestor_func (MetaWindow *window, + void *data) +{ + FindAncestorData *d = data; + + if (window == d->ancestor) + { + d->found = TRUE; + return FALSE; + } + + return TRUE; +} + +/** + * meta_window_is_ancestor_of_transient: + * @window: a #MetaWindow + * @transient: a #MetaWindow + * + * The function determines whether @window is an ancestor of @transient; it does + * so by traversing the @transient's ancestors until it either locates @window + * or reaches an ancestor that is not transient. + * + * Return Value: %TRUE if window is an ancestor of transient. + */ +gboolean +meta_window_is_ancestor_of_transient (MetaWindow *window, + MetaWindow *transient) +{ + FindAncestorData d; + + d.ancestor = window; + d.found = FALSE; + + meta_window_foreach_ancestor (transient, find_ancestor_func, &d); + + return d.found; +} + +/** + * meta_window_begin_grab_op: + * @window: + * @op: + * @device: (nullable): + * @sequence: (nullable): + * @timestamp: + **/ +gboolean +meta_window_begin_grab_op (MetaWindow *window, + MetaGrabOp op, + ClutterInputDevice *device, + ClutterEventSequence *sequence, + guint32 timestamp) +{ + return meta_compositor_drag_window (window->display->compositor, + window, op, + device, sequence, + timestamp); +} + +MetaStackLayer +meta_window_get_default_layer (MetaWindow *window) +{ + if (window->wm_state_below) + return META_LAYER_BOTTOM; + else if (window->wm_state_above && !META_WINDOW_MAXIMIZED (window)) + return META_LAYER_TOP; + else + return META_LAYER_NORMAL; +} + +void +meta_window_update_layer (MetaWindow *window) +{ + MetaGroup *group; + + meta_stack_freeze (window->display->stack); + group = meta_window_get_group (window); + if (group) + meta_group_update_layers (group); + else + meta_stack_update_layer (window->display->stack, window); + meta_stack_thaw (window->display->stack); +} + +/* ensure_mru_position_after ensures that window appears after + * below_this_one in the active_workspace's mru_list (i.e. it treats + * window as having been less recently used than below_this_one) + */ +static void +ensure_mru_position_after (MetaWindow *window, + MetaWindow *after_this_one) +{ + /* This is sort of slow since it runs through the entire list more + * than once (especially considering the fact that we expect the + * windows of interest to be the first two elements in the list), + * but it doesn't matter while we're only using it on new window + * map. + */ + + MetaWorkspaceManager *workspace_manager = window->display->workspace_manager; + GList* active_mru_list; + GList* window_position; + GList* after_this_one_position; + + active_mru_list = workspace_manager->active_workspace->mru_list; + window_position = g_list_find (active_mru_list, window); + after_this_one_position = g_list_find (active_mru_list, after_this_one); + + /* after_this_one_position is NULL when we switch workspaces, but in + * that case we don't need to do any MRU shuffling so we can simply + * return. + */ + if (after_this_one_position == NULL) + return; + + if (g_list_length (window_position) > g_list_length (after_this_one_position)) + { + workspace_manager->active_workspace->mru_list = + g_list_delete_link (workspace_manager->active_workspace->mru_list, + window_position); + + workspace_manager->active_workspace->mru_list = + g_list_insert_before (workspace_manager->active_workspace->mru_list, + after_this_one_position->next, + window); + } +} + +gboolean +meta_window_is_in_stack (MetaWindow *window) +{ + return window->stack_position >= 0; +} + +void +meta_window_stack_just_below (MetaWindow *window, + MetaWindow *below_this_one) +{ + g_return_if_fail (window != NULL); + g_return_if_fail (below_this_one != NULL); + + if (window->stack_position > below_this_one->stack_position) + { + meta_topic (META_DEBUG_STACK, + "Setting stack position of window %s to %d (making it below window %s).", + window->desc, + below_this_one->stack_position, + below_this_one->desc); + meta_window_set_stack_position (window, below_this_one->stack_position); + } + else + { + meta_topic (META_DEBUG_STACK, + "Window %s was already below window %s.", + window->desc, below_this_one->desc); + } +} + +void +meta_window_stack_just_above (MetaWindow *window, + MetaWindow *above_this_one) +{ + g_return_if_fail (window != NULL); + g_return_if_fail (above_this_one != NULL); + + if (window->stack_position < above_this_one->stack_position) + { + meta_topic (META_DEBUG_STACK, + "Setting stack position of window %s to %d (making it above window %s).", + window->desc, + above_this_one->stack_position, + above_this_one->desc); + meta_window_set_stack_position (window, above_this_one->stack_position); + } + else + { + meta_topic (META_DEBUG_STACK, + "Window %s was already above window %s.", + window->desc, above_this_one->desc); + } +} + +/** + * meta_window_get_user_time: + * @window: a #MetaWindow + * + * The user time represents a timestamp for the last time the user + * interacted with this window. Note this property is only available + * for non-override-redirect windows. + * + * The property is set by Mutter initially upon window creation, + * and updated thereafter on input events (key and button presses) seen by Mutter, + * client updates to the _NET_WM_USER_TIME property (if later than the current time) + * and when focusing the window. + * + * Returns: The last time the user interacted with this window. + */ +guint32 +meta_window_get_user_time (MetaWindow *window) +{ + return window->net_wm_user_time; +} + +void +meta_window_set_user_time (MetaWindow *window, + 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. + */ + + g_return_if_fail (!window->override_redirect); + + /* Only update the time if this timestamp is newer... */ + if (window->net_wm_user_time_set && + XSERVER_TIME_IS_BEFORE (timestamp, window->net_wm_user_time)) + { + meta_topic (META_DEBUG_STARTUP, + "Window %s _NET_WM_USER_TIME not updated to %u, because it " + "is less than %u", + window->desc, timestamp, window->net_wm_user_time); + } + else + { + meta_topic (META_DEBUG_STARTUP, + "Window %s has _NET_WM_USER_TIME of %u", + window->desc, timestamp); + window->net_wm_user_time_set = TRUE; + window->net_wm_user_time = timestamp; + if (XSERVER_TIME_IS_BEFORE (window->display->last_user_time, timestamp)) + window->display->last_user_time = timestamp; + + /* If this is a terminal, user interaction with it means the user likely + * doesn't want to have focus transferred for now due to new windows. + */ + if (meta_prefs_get_focus_new_windows () == G_DESKTOP_FOCUS_NEW_WINDOWS_STRICT && + window_is_terminal (window)) + window->display->allow_terminal_deactivation = FALSE; + + g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_USER_TIME]); + } +} + +/** + * meta_window_get_stable_sequence: + * @window: A #MetaWindow + * + * The stable sequence number is a monotonicially increasing + * unique integer assigned to each #MetaWindow upon creation. + * + * This number can be useful for sorting windows in a stable + * fashion. + * + * Returns: Internal sequence number for this window + */ +guint32 +meta_window_get_stable_sequence (MetaWindow *window) +{ + g_return_val_if_fail (META_IS_WINDOW (window), 0); + + return window->stable_sequence; +} + +/* Sets the demands_attention hint on a window, but only + * if it's at least partially obscured (see #305882). + */ +void +meta_window_set_demands_attention (MetaWindow *window) +{ + MetaWorkspaceManager *workspace_manager = window->display->workspace_manager; + MetaRectangle candidate_rect, other_rect; + GList *stack = window->display->stack->sorted; + MetaWindow *other_window; + gboolean obscured = FALSE; + + MetaWorkspace *workspace = workspace_manager->active_workspace; + + if (window->wm_state_demands_attention) + return; + + if (!meta_window_located_on_workspace (window, workspace)) + { + /* windows on other workspaces are necessarily obscured */ + obscured = TRUE; + } + else if (window->minimized) + { + obscured = TRUE; + } + else + { + meta_window_get_frame_rect (window, &candidate_rect); + + /* The stack is sorted with the top windows first. */ + + while (stack != NULL && stack->data != window) + { + other_window = stack->data; + stack = stack->next; + + if (meta_window_located_on_workspace (other_window, workspace)) + { + meta_window_get_frame_rect (other_window, &other_rect); + + if (meta_rectangle_overlap (&candidate_rect, &other_rect)) + { + obscured = TRUE; + break; + } + } + } + } + + if (obscured) + { + meta_topic (META_DEBUG_WINDOW_OPS, + "Marking %s as needing attention", + window->desc); + + window->wm_state_demands_attention = TRUE; + set_net_wm_state (window); + g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_DEMANDS_ATTENTION]); + g_signal_emit_by_name (window->display, "window-demands-attention", + window); + } + else + { + /* If the window's in full view, there's no point setting the flag. */ + + meta_topic (META_DEBUG_WINDOW_OPS, + "Not marking %s as needing attention because " + "it's in full view", + window->desc); + } +} + +void +meta_window_unset_demands_attention (MetaWindow *window) +{ + meta_topic (META_DEBUG_WINDOW_OPS, + "Marking %s as not needing attention", window->desc); + + if (window->wm_state_demands_attention) + { + window->wm_state_demands_attention = FALSE; + set_net_wm_state (window); + g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_DEMANDS_ATTENTION]); + } +} + +/** + * meta_window_get_frame: (skip) + * @window: a #MetaWindow + * + */ +MetaFrame * +meta_window_get_frame (MetaWindow *window) +{ + return window->frame; +} + +/** + * meta_window_appears_focused: + * @window: a #MetaWindow + * + * Determines if the window should be drawn with a focused appearance. This is + * true for focused windows but also true for windows with a focused modal + * dialog attached. + * + * Return value: %TRUE if the window should be drawn with a focused frame + */ +gboolean +meta_window_appears_focused (MetaWindow *window) +{ + return window->appears_focused; +} + +gboolean +meta_window_has_focus (MetaWindow *window) +{ + return window->has_focus; +} + +/** + * meta_window_is_override_redirect: + * @window: A #MetaWindow + * + * Returns: %TRUE if this window isn't managed by mutter; it will + * control its own positioning and mutter won't draw decorations + * among other things. In X terminology this is "override redirect". + */ +gboolean +meta_window_is_override_redirect (MetaWindow *window) +{ + return window->override_redirect; +} + +/** + * meta_window_is_skip_taskbar: + * @window: A #MetaWindow + * + * Gets whether this window should be ignored by task lists. + * + * Return value: %TRUE if the skip bar hint is set. + */ +gboolean +meta_window_is_skip_taskbar (MetaWindow *window) +{ + g_return_val_if_fail (META_IS_WINDOW (window), FALSE); + + return window->skip_taskbar; +} + +/** + * meta_window_get_display: + * @window: A #MetaWindow + * + * Returns: (transfer none): The display for @window + */ +MetaDisplay * +meta_window_get_display (MetaWindow *window) +{ + return window->display; +} + +/** + * meta_window_get_xwindow: (skip) + * @window: a #MetaWindow + * + */ +Window +meta_window_get_xwindow (MetaWindow *window) +{ + return window->xwindow; +} + +MetaWindowType +meta_window_get_window_type (MetaWindow *window) +{ + return window->type; +} + +/** + * meta_window_get_workspace: + * @window: a #MetaWindow + * + * Gets the #MetaWorkspace that the window is currently displayed on. + * If the window is on all workspaces, returns the currently active + * workspace. + * + * Return value: (transfer none): the #MetaWorkspace for the window + */ +MetaWorkspace * +meta_window_get_workspace (MetaWindow *window) +{ + MetaWorkspaceManager *workspace_manager = window->display->workspace_manager; + + if (window->on_all_workspaces) + return workspace_manager->active_workspace; + else + return window->workspace; +} + +gboolean +meta_window_is_on_all_workspaces (MetaWindow *window) +{ + return window->on_all_workspaces; +} + +gboolean +meta_window_is_hidden (MetaWindow *window) +{ + return window->hidden; +} + +const char * +meta_window_get_description (MetaWindow *window) +{ + if (!window) + return NULL; + + return window->desc; +} + +/** + * meta_window_get_wm_class: + * @window: a #MetaWindow + * + * Return the current value of the name part of WM_CLASS X property. + * + * Returns: (nullable): the current value of the name part of WM_CLASS X + * property + */ +const char * +meta_window_get_wm_class (MetaWindow *window) +{ + if (!window) + return NULL; + + return window->res_class; +} + +/** + * meta_window_get_wm_class_instance: + * @window: a #MetaWindow + * + * Return the current value of the instance part of WM_CLASS X property. + * + * Returns: (nullable): the current value of the instance part of WM_CLASS X + * property. + */ +const char * +meta_window_get_wm_class_instance (MetaWindow *window) +{ + if (!window) + return NULL; + + return window->res_name; +} + +/** + * meta_window_get_sandboxed_app_id: + * @window: a #MetaWindow + * + * Gets an unique id for a sandboxed app (currently flatpaks and snaps are + * supported). + * + * Returns: (transfer none) (nullable): the sandboxed application ID or %NULL + **/ +const char * +meta_window_get_sandboxed_app_id (MetaWindow *window) +{ + /* We're abusing this API here not to break the gnome shell assumptions + * or adding a new function, to be renamed to generic names in new versions */ + return window->sandboxed_app_id; +} + +/** + * meta_window_get_gtk_theme_variant: + * @window: a #MetaWindow + * + * Returns: (transfer none) (nullable): the theme variant or %NULL + **/ +const char * +meta_window_get_gtk_theme_variant (MetaWindow *window) +{ + return window->gtk_theme_variant; +} + +/** + * meta_window_get_gtk_application_id: + * @window: a #MetaWindow + * + * Returns: (transfer none) (nullable): the application ID + **/ +const char * +meta_window_get_gtk_application_id (MetaWindow *window) +{ + return window->gtk_application_id; +} + +/** + * meta_window_get_gtk_unique_bus_name: + * @window: a #MetaWindow + * + * Returns: (transfer none) (nullable): the unique name + **/ +const char * +meta_window_get_gtk_unique_bus_name (MetaWindow *window) +{ + return window->gtk_unique_bus_name; +} + +/** + * meta_window_get_gtk_application_object_path: + * @window: a #MetaWindow + * + * Returns: (transfer none) (nullable): the object path + **/ +const char * +meta_window_get_gtk_application_object_path (MetaWindow *window) +{ + return window->gtk_application_object_path; +} + +/** + * meta_window_get_gtk_window_object_path: + * @window: a #MetaWindow + * + * Returns: (transfer none) (nullable): the object path + **/ +const char * +meta_window_get_gtk_window_object_path (MetaWindow *window) +{ + return window->gtk_window_object_path; +} + +/** + * meta_window_get_gtk_app_menu_object_path: + * @window: a #MetaWindow + * + * Returns: (transfer none) (nullable): the object path + **/ +const char * +meta_window_get_gtk_app_menu_object_path (MetaWindow *window) +{ + return window->gtk_app_menu_object_path; +} + +/** + * meta_window_get_gtk_menubar_object_path: + * @window: a #MetaWindow + * + * Returns: (transfer none) (nullable): the object path + **/ +const char * +meta_window_get_gtk_menubar_object_path (MetaWindow *window) +{ + return window->gtk_menubar_object_path; +} + +/** + * meta_window_get_compositor_private: + * @window: a #MetaWindow + * + * Gets the compositor's wrapper object for @window. + * + * Return value: (transfer none): the wrapper object. + **/ +GObject * +meta_window_get_compositor_private (MetaWindow *window) +{ + if (!window) + return NULL; + return window->compositor_private; +} + +void +meta_window_set_compositor_private (MetaWindow *window, GObject *priv) +{ + if (!window) + return; + window->compositor_private = priv; +} + +const char * +meta_window_get_role (MetaWindow *window) +{ + if (!window) + return NULL; + + return window->role; +} + +/** + * meta_window_get_title: + * @window: a #MetaWindow + * + * Returns: the current title of the window. + */ +const char * +meta_window_get_title (MetaWindow *window) +{ + g_return_val_if_fail (META_IS_WINDOW (window), NULL); + + return window->title; +} + +MetaStackLayer +meta_window_get_layer (MetaWindow *window) +{ + return window->layer; +} + +/** + * meta_window_get_transient_for: + * @window: a #MetaWindow + * + * Returns the #MetaWindow for the window that is pointed to by the + * WM_TRANSIENT_FOR hint on this window (see XGetTransientForHint() + * or XSetTransientForHint()). Mutter keeps transient windows above their + * parents. A typical usage of this hint is for a dialog that wants to stay + * above its associated window. + * + * Returns: (transfer none) (nullable): the window this window is transient for, + * or %NULL if the WM_TRANSIENT_FOR hint is unset or does not point to a + * toplevel window that Mutter knows about. + */ +MetaWindow * +meta_window_get_transient_for (MetaWindow *window) +{ + g_return_val_if_fail (META_IS_WINDOW (window), NULL); + + if (window->transient_for) + return window->transient_for; + else if (window->xtransient_for) + return meta_x11_display_lookup_x_window (window->display->x11_display, + window->xtransient_for); + else + return NULL; +} + +/** + * meta_window_get_pid: + * @window: a #MetaWindow + * + * Returns the pid of the process that created this window, if available + * to the windowing system. + * + * Note that the value returned by this is vulnerable to spoofing attacks + * by the client. + * + * Return value: the pid, or 0 if not known. + */ +pid_t +meta_window_get_pid (MetaWindow *window) +{ + g_return_val_if_fail (META_IS_WINDOW (window), 0); + + if (window->client_pid == 0) + window->client_pid = META_WINDOW_GET_CLASS (window)->get_client_pid (window); + + return window->client_pid; +} + +/** + * meta_window_get_unit_cgroup: + * @window: a #MetaWindow + * + * Returns: (nullable): a #GFile for the cgroup path, or %NULL. + */ +GFile * +meta_window_get_unit_cgroup (MetaWindow *window) +{ +#ifdef HAVE_LIBSYSTEMD + g_autofree char *contents = NULL; + g_autofree char *complete_path = NULL; + g_autofree char *unit_name = NULL; + g_autofree char *unit_path = NULL; + char *unit_end; + pid_t pid; + + if (!window->has_valid_cgroup) + return NULL; + + if (window->cgroup_path) + return window->cgroup_path; + + pid = meta_window_get_pid (window); + if (pid < 1) + return NULL; + + if (sd_pid_get_cgroup (pid, &contents) < 0) + { + window->has_valid_cgroup = FALSE; + return NULL; + } + g_strstrip (contents); + + complete_path = g_strdup_printf ("%s%s", "/sys/fs/cgroup", contents); + + if (sd_pid_get_user_unit (pid, &unit_name) < 0) + { + window->has_valid_cgroup = FALSE; + return NULL; + } + g_strstrip (unit_name); + + unit_end = strstr (complete_path, unit_name) + strlen (unit_name); + *unit_end = '\0'; + + window->cgroup_path = g_file_new_for_path (complete_path); + + return window->cgroup_path; +#else + return NULL; +#endif +} + +gboolean +meta_window_unit_cgroup_equal (MetaWindow *window1, + MetaWindow *window2) +{ + GFile *window1_file, *window2_file; + + window1_file = meta_window_get_unit_cgroup (window1); + window2_file = meta_window_get_unit_cgroup (window2); + + if (!window1_file || !window2_file) + return FALSE; + + return g_file_equal (window1_file, window2_file); +} + +/** + * meta_window_get_client_machine: + * @window: a #MetaWindow + * + * Returns name of the client machine from which this windows was created, + * if known (obtained from the WM_CLIENT_MACHINE property). + * + * Returns: (transfer none) (nullable): the machine name, or %NULL; the string + * is owned by the window manager and should not be freed or modified by the + * caller. + */ +const char * +meta_window_get_client_machine (MetaWindow *window) +{ + g_return_val_if_fail (META_IS_WINDOW (window), NULL); + + return window->wm_client_machine; +} + +/** + * meta_window_is_remote: + * @window: a #MetaWindow + * + * Returns: %TRUE if this window originates from a host + * different from the one running mutter. + */ +gboolean +meta_window_is_remote (MetaWindow *window) +{ + return window->is_remote; +} + +/** + * meta_window_get_mutter_hints: + * @window: a #MetaWindow + * + * Gets the current value of the _MUTTER_HINTS property. + * + * The purpose of the hints is to allow fine-tuning of the Window Manager and + * Compositor behaviour on per-window basis, and is intended primarily for + * hints that are plugin-specific. + * + * The property is a list of colon-separated key=value pairs. The key names for + * any plugin-specific hints must be suitably namespaced to allow for shared + * use; 'mutter-' key prefix is reserved for internal use, and must not be used + * by plugins. + * + * Returns: (transfer none) (nullable): the _MUTTER_HINTS string, or %NULL if no + * hints are set. + */ +const char * +meta_window_get_mutter_hints (MetaWindow *window) +{ + g_return_val_if_fail (META_IS_WINDOW (window), NULL); + + return window->mutter_hints; +} + +/** + * meta_window_get_frame_type: + * @window: a #MetaWindow + * + * Gets the type of window decorations that should be used for this window. + * + * Return value: the frame type + */ +MetaFrameType +meta_window_get_frame_type (MetaWindow *window) +{ + MetaFrameType base_type = META_FRAME_TYPE_LAST; + + switch (window->type) + { + case META_WINDOW_NORMAL: + base_type = META_FRAME_TYPE_NORMAL; + break; + + case META_WINDOW_DIALOG: + base_type = META_FRAME_TYPE_DIALOG; + break; + + case META_WINDOW_MODAL_DIALOG: + if (meta_window_is_attached_dialog (window)) + base_type = META_FRAME_TYPE_ATTACHED; + else + base_type = META_FRAME_TYPE_MODAL_DIALOG; + break; + + case META_WINDOW_MENU: + base_type = META_FRAME_TYPE_MENU; + break; + + case META_WINDOW_UTILITY: + base_type = META_FRAME_TYPE_UTILITY; + break; + + case META_WINDOW_DESKTOP: + case META_WINDOW_DOCK: + case META_WINDOW_TOOLBAR: + case META_WINDOW_SPLASHSCREEN: + case META_WINDOW_DROPDOWN_MENU: + case META_WINDOW_POPUP_MENU: + case META_WINDOW_TOOLTIP: + case META_WINDOW_NOTIFICATION: + case META_WINDOW_COMBO: + case META_WINDOW_DND: + case META_WINDOW_OVERRIDE_OTHER: + /* No frame */ + base_type = META_FRAME_TYPE_LAST; + break; + } + + if (base_type == META_FRAME_TYPE_LAST) + { + /* can't add border if undecorated */ + return META_FRAME_TYPE_LAST; + } + else if (window->border_only) + { + /* override base frame type */ + return META_FRAME_TYPE_BORDER; + } + else + { + return base_type; + } +} + +/** + * meta_window_get_frame_bounds: + * @window: a #MetaWindow + * + * Gets a region representing the outer bounds of the window's frame. + * + * Return value: (transfer none) (nullable): a #cairo_region_t + * holding the outer bounds of the window, or %NULL if the window + * doesn't have a frame. + */ +cairo_region_t * +meta_window_get_frame_bounds (MetaWindow *window) +{ + if (!window->frame_bounds) + { + if (window->frame) + window->frame_bounds = meta_frame_get_frame_bounds (window->frame); + } + + return window->frame_bounds; +} + +/** + * meta_window_is_attached_dialog: + * @window: a #MetaWindow + * + * Tests if @window should be attached to its parent window. + * (If the "attach_modal_dialogs" option is not enabled, this will + * always return %FALSE.) + * + * Return value: whether @window should be attached to its parent + */ +gboolean +meta_window_is_attached_dialog (MetaWindow *window) +{ + return window->attached; +} + +static gboolean +has_attached_foreach_func (MetaWindow *window, + void *data) +{ + gboolean *is_attached = data; + + *is_attached = window->attached && !window->unmanaging; + + if (*is_attached) + return FALSE; + + return TRUE; +} + + +/** + * meta_window_has_attached_dialogs: + * @window: a #MetaWindow + * + * Tests if @window has any transients attached to it. + * (If the "attach_modal_dialogs" option is not enabled, this will + * always return %FALSE.) + * + * Return value: whether @window has attached transients + */ +gboolean +meta_window_has_attached_dialogs (MetaWindow *window) +{ + gboolean has_attached = FALSE; + + meta_window_foreach_transient (window, + has_attached_foreach_func, + &has_attached); + return has_attached; +} + +static gboolean +has_modals_foreach_func (MetaWindow *window, + void *data) +{ + gboolean *is_modal = data; + + *is_modal = window->type == META_WINDOW_MODAL_DIALOG && !window->unmanaging; + + if (*is_modal) + return FALSE; + + return TRUE; +} + +/** + * meta_window_has_modals: + * @window: a #MetaWindow + * + * Return value: whether @window has any modal transients + */ +gboolean +meta_window_has_modals (MetaWindow *window) +{ + gboolean has_modals = FALSE; + + meta_window_foreach_transient (window, has_modals_foreach_func, &has_modals); + return has_modals; +} + +/** + * meta_window_get_tile_match: + * @window: a #MetaWindow + * + * Returns the matching tiled window on the same monitor as @window. This is + * the topmost tiled window in a complementary tile mode that is: + * + * - on the same monitor; + * - on the same workspace; + * - spanning the remaining monitor width; + * - there is no 3rd window stacked between both tiled windows that's + * partially visible in the common edge. + * + * Return value: (transfer none) (nullable): the matching tiled window or + * %NULL if it doesn't exist. + */ +MetaWindow * +meta_window_get_tile_match (MetaWindow *window) +{ + return window->tile_match; +} + +void +meta_window_compute_tile_match (MetaWindow *window) +{ + window->tile_match = meta_window_find_tile_match (window, window->tile_mode); +} + +static MetaWindow * +meta_window_find_tile_match (MetaWindow *window, + MetaTileMode current_mode) +{ + MetaWindow *match; + MetaStack *stack; + MetaTileMode match_tile_mode = META_TILE_NONE; + + if (window->minimized) + return NULL; + + if (current_mode == META_TILE_LEFT) + match_tile_mode = META_TILE_RIGHT; + else if (current_mode == META_TILE_RIGHT) + match_tile_mode = META_TILE_LEFT; + else + return NULL; + + stack = window->display->stack; + + for (match = meta_stack_get_top (stack); + match; + match = meta_stack_get_below (stack, match, FALSE)) + { + if (!match->minimized && + match->tile_mode == match_tile_mode && + match->tile_monitor_number == window->tile_monitor_number && + meta_window_get_workspace (match) == meta_window_get_workspace (window)) + break; + } + + if (match) + { + MetaWindow *above, *bottommost, *topmost; + MetaRectangle above_rect, bottommost_rect, topmost_rect; + MetaWindowDrag *window_drag; + + if (meta_stack_windows_cmp (window->display->stack, match, window) > 0) + { + topmost = match; + bottommost = window; + } + else + { + topmost = window; + bottommost = match; + } + + meta_window_get_frame_rect (bottommost, &bottommost_rect); + meta_window_get_frame_rect (topmost, &topmost_rect); + + window_drag = + meta_compositor_get_current_window_drag (window->display->compositor); + + /* + * If we are looking for a tile match while actually being tiled, + * rather than a match for a potential tile mode, then discard + * windows with too much gap or overlap + */ + if (window->tile_mode == current_mode && + !(window_drag && + meta_grab_op_is_resizing (meta_window_drag_get_grab_op (window_drag)) && + meta_window_drag_get_window (window_drag) == window && + window->tile_match != NULL)) + { + int threshold = meta_prefs_get_drag_threshold (); + if (ABS (topmost_rect.x - bottommost_rect.x - bottommost_rect.width) > threshold && + ABS (bottommost_rect.x - topmost_rect.x - topmost_rect.width) > threshold) + return NULL; + } + + /* + * If there's a window stacked in between which is partially visible + * behind the topmost tile we don't consider the tiles to match. + */ + for (above = meta_stack_get_above (stack, bottommost, FALSE); + above && above != topmost; + above = meta_stack_get_above (stack, above, FALSE)) + { + if (above->minimized || + above->monitor != window->monitor || + meta_window_get_workspace (above) != meta_window_get_workspace (window)) + continue; + + meta_window_get_frame_rect (above, &above_rect); + + if (meta_rectangle_overlap (&above_rect, &bottommost_rect) && + meta_rectangle_overlap (&above_rect, &topmost_rect)) + return NULL; + } + } + + return match; +} + +void +meta_window_set_title (MetaWindow *window, + const char *title) +{ + g_free (window->title); + window->title = g_strdup (title); + + meta_window_update_desc (window); + + g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_TITLE]); +} + +void +meta_window_set_wm_class (MetaWindow *window, + const char *wm_class, + const char *wm_instance) +{ + g_free (window->res_class); + g_free (window->res_name); + + window->res_name = g_strdup (wm_instance); + window->res_class = g_strdup (wm_class); + + g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_WM_CLASS]); +} + +void +meta_window_set_gtk_dbus_properties (MetaWindow *window, + const char *application_id, + const char *unique_bus_name, + const char *appmenu_path, + const char *menubar_path, + const char *application_object_path, + const char *window_object_path) +{ + g_object_freeze_notify (G_OBJECT (window)); + + g_free (window->gtk_application_id); + window->gtk_application_id = g_strdup (application_id); + g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_GTK_APPLICATION_ID]); + + g_free (window->gtk_unique_bus_name); + window->gtk_unique_bus_name = g_strdup (unique_bus_name); + g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_GTK_UNIQUE_BUS_NAME]); + + g_free (window->gtk_app_menu_object_path); + window->gtk_app_menu_object_path = g_strdup (appmenu_path); + g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_GTK_APP_MENU_OBJECT_PATH]); + + g_free (window->gtk_menubar_object_path); + window->gtk_menubar_object_path = g_strdup (menubar_path); + g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_GTK_MENUBAR_OBJECT_PATH]); + + g_free (window->gtk_application_object_path); + window->gtk_application_object_path = g_strdup (application_object_path); + g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_GTK_APPLICATION_OBJECT_PATH]); + + g_free (window->gtk_window_object_path); + window->gtk_window_object_path = g_strdup (window_object_path); + g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_GTK_WINDOW_OBJECT_PATH]); + + g_object_thaw_notify (G_OBJECT (window)); +} + +static gboolean +check_transient_for_loop (MetaWindow *window, + MetaWindow *parent) +{ + while (parent) + { + if (parent == window) + return TRUE; + parent = parent->transient_for; + } + + return FALSE; +} + +gboolean +meta_window_has_transient_type (MetaWindow *window) +{ + return (window->type == META_WINDOW_DIALOG || + window->type == META_WINDOW_MODAL_DIALOG || + window->type == META_WINDOW_TOOLBAR || + window->type == META_WINDOW_MENU || + window->type == META_WINDOW_UTILITY); +} + +void +meta_window_set_transient_for (MetaWindow *window, + MetaWindow *parent) +{ + if (check_transient_for_loop (window, parent)) + { + meta_warning ("Setting %s transient for %s would create a loop.", + window->desc, parent->desc); + return; + } + + if (window->appears_focused && window->transient_for != NULL) + meta_window_propagate_focus_appearance (window, FALSE); + + /* may now be a dialog */ + if (window->client_type == META_WINDOW_CLIENT_TYPE_X11) + { + meta_window_x11_recalc_window_type (window); + + if (!window->constructing) + { + /* If the window attaches, detaches, or changes attached + * parents, we need to destroy the MetaWindow and let a new one + * be created (which happens as a side effect of + * meta_window_unmanage()). The condition below is correct + * because we know window->transient_for has changed. + */ + if (window->attached || meta_window_should_attach_to_parent (window)) + { + guint32 timestamp; + + timestamp = + meta_display_get_current_time_roundtrip (window->display); + meta_window_unmanage (window, timestamp); + return; + } + } + } + else if (window->attached && parent == NULL) + { + guint32 timestamp; + + timestamp = + meta_display_get_current_time_roundtrip (window->display); + meta_window_delete (window, timestamp); + return; + } + + g_set_object (&window->transient_for, parent); + + if (window->client_type == META_WINDOW_CLIENT_TYPE_WAYLAND && + window->attached != meta_window_should_attach_to_parent (window)) + { + window->attached = meta_window_should_attach_to_parent (window); + meta_window_recalc_features (window); + } + + /* update stacking constraints */ + if (!window->override_redirect) + meta_stack_update_transient (window->display->stack, window); + + /* possibly change its group. We treat being a window's transient as + * equivalent to making it your group leader, to work around shortcomings + * in programs such as xmms-- see #328211. + */ + if (window->xtransient_for != None && + window->xgroup_leader != None && + window->xtransient_for != window->xgroup_leader) + meta_window_group_leader_changed (window); + + if (!window->constructing && !window->override_redirect) + meta_window_queue (window, META_QUEUE_MOVE_RESIZE | META_QUEUE_CALC_SHOWING); + + if (window->appears_focused && window->transient_for != NULL) + meta_window_propagate_focus_appearance (window, TRUE); +} + +void +meta_window_set_opacity (MetaWindow *window, + guint8 opacity) +{ + window->opacity = opacity; + + meta_compositor_window_opacity_changed (window->display->compositor, window); +} + +typedef struct +{ + MetaWindow *window; + int pointer_x; + int pointer_y; +} MetaFocusData; + +static void +mouse_mode_focus (MetaWindow *window, + guint32 timestamp) +{ + MetaDisplay *display = window->display; + + if (window->override_redirect) + return; + + if (window->type != META_WINDOW_DESKTOP) + { + meta_topic (META_DEBUG_FOCUS, + "Focusing %s at time %u.", window->desc, timestamp); + + meta_window_focus (window, timestamp); + + if (meta_prefs_get_auto_raise ()) + meta_display_queue_autoraise_callback (display, window); + else + meta_topic (META_DEBUG_FOCUS, "Auto raise is disabled"); + } + else + { + /* In mouse focus mode, we defocus when the mouse *enters* + * the DESKTOP window, instead of defocusing on LeaveNotify. + * This is because having the mouse enter override-redirect + * child windows unfortunately causes LeaveNotify events that + * we can't distinguish from the mouse actually leaving the + * toplevel window as we expect. But, since we filter out + * EnterNotify events on override-redirect windows, this + * alternative mechanism works great. + */ + if (meta_prefs_get_focus_mode() == G_DESKTOP_FOCUS_MODE_MOUSE && + display->focus_window != NULL) + { + meta_topic (META_DEBUG_FOCUS, + "Unsetting focus from %s due to mouse entering " + "the DESKTOP window", + display->focus_window->desc); + meta_display_unset_input_focus (display, timestamp); + } + } +} + +static gboolean +window_has_pointer_wayland (MetaWindow *window) +{ + ClutterSeat *seat; + ClutterInputDevice *dev; + ClutterStage *stage; + ClutterActor *pointer_actor, *window_actor; + + seat = clutter_backend_get_default_seat (clutter_get_default_backend ()); + dev = clutter_seat_get_pointer (seat); + stage = CLUTTER_STAGE (meta_backend_get_stage (backend_from_window (window))); + pointer_actor = clutter_stage_get_device_actor (stage, dev, NULL); + window_actor = CLUTTER_ACTOR (meta_window_get_compositor_private (window)); + + return pointer_actor && clutter_actor_contains (window_actor, pointer_actor); +} + +static gboolean +window_has_pointer_x11 (MetaWindow *window) +{ + MetaX11Display *x11_display = window->display->x11_display; + Window root, child; + double root_x, root_y, x, y; + XIButtonState buttons; + XIModifierState mods; + XIGroupState group; + + meta_x11_error_trap_push (x11_display); + XIQueryPointer (x11_display->xdisplay, + META_VIRTUAL_CORE_POINTER_ID, + x11_display->xroot, + &root, &child, + &root_x, &root_y, &x, &y, + &buttons, &mods, &group); + meta_x11_error_trap_pop (x11_display); + free (buttons.mask); + + return meta_x11_display_lookup_x_window (x11_display, child) == window; +} + +gboolean +meta_window_has_pointer (MetaWindow *window) +{ + if (meta_is_wayland_compositor ()) + return window_has_pointer_wayland (window); + else + return window_has_pointer_x11 (window); +} + +static gboolean +window_focus_on_pointer_rest_callback (gpointer data) +{ + MetaFocusData *focus_data = data; + MetaWindow *window = focus_data->window; + MetaDisplay *display = window->display; + MetaBackend *backend = backend_from_window (window); + MetaCursorTracker *cursor_tracker = meta_backend_get_cursor_tracker (backend); + graphene_point_t point; + guint32 timestamp; + + if (meta_prefs_get_focus_mode () == G_DESKTOP_FOCUS_MODE_CLICK) + goto out; + + meta_cursor_tracker_get_pointer (cursor_tracker, &point, NULL); + + if ((int) point.x != focus_data->pointer_x || + (int) point.y != focus_data->pointer_y) + { + focus_data->pointer_x = point.x; + focus_data->pointer_y = point.y; + return G_SOURCE_CONTINUE; + } + + if (!meta_window_has_pointer (window)) + goto out; + + timestamp = meta_display_get_current_time_roundtrip (display); + mouse_mode_focus (window, timestamp); + + out: + display->focus_timeout_id = 0; + return G_SOURCE_REMOVE; +} + +/* The interval, in milliseconds, we use in focus-follows-mouse + * mode to check whether the pointer has stopped moving after a + * crossing event. + */ +#define FOCUS_TIMEOUT_DELAY 25 + +static void +queue_focus_callback (MetaDisplay *display, + MetaWindow *window, + int pointer_x, + int pointer_y) +{ + MetaFocusData *focus_data; + + focus_data = g_new (MetaFocusData, 1); + focus_data->window = window; + focus_data->pointer_x = pointer_x; + focus_data->pointer_y = pointer_y; + + g_clear_handle_id (&display->focus_timeout_id, g_source_remove); + + display->focus_timeout_id = + g_timeout_add_full (G_PRIORITY_DEFAULT, + FOCUS_TIMEOUT_DELAY, + window_focus_on_pointer_rest_callback, + focus_data, + g_free); + g_source_set_name_by_id (display->focus_timeout_id, + "[mutter] window_focus_on_pointer_rest_callback"); +} + +void +meta_window_handle_enter (MetaWindow *window, + guint32 timestamp, + guint root_x, + guint root_y) +{ + MetaDisplay *display = window->display; + + switch (meta_prefs_get_focus_mode ()) + { + case G_DESKTOP_FOCUS_MODE_SLOPPY: + case G_DESKTOP_FOCUS_MODE_MOUSE: + display->mouse_mode = TRUE; + if (window->type != META_WINDOW_DOCK) + { + if (meta_prefs_get_focus_change_on_pointer_rest()) + queue_focus_callback (display, window, root_x, root_y); + else + mouse_mode_focus (window, timestamp); + } + break; + case G_DESKTOP_FOCUS_MODE_CLICK: + break; + } + + if (window->type == META_WINDOW_DOCK) + meta_window_raise (window); +} + +void +meta_window_handle_leave (MetaWindow *window) +{ + if (window->type == META_WINDOW_DOCK && !window->has_focus) + meta_window_lower (window); +} + +void +meta_window_handle_ungrabbed_event (MetaWindow *window, + const ClutterEvent *event) +{ + MetaDisplay *display = window->display; + gboolean unmodified; + gboolean is_window_grab; + gboolean is_window_button_grab_allowed; + ClutterModifierType grab_mods, event_mods; + ClutterInputDevice *source; + gfloat x, y; + guint button; + + if (window->unmanaging) + return; + + if (event->type != CLUTTER_BUTTON_PRESS && + event->type != CLUTTER_TOUCH_BEGIN) + return; + + if (event->type == CLUTTER_TOUCH_BEGIN) + { + ClutterEventSequence *sequence; + + button = 1; + sequence = clutter_event_get_event_sequence (event); + if (!meta_display_is_pointer_emulating_sequence (window->display, sequence)) + return; + } + else + button = clutter_event_get_button (event); + + /* Some windows might not ask for input, in which case we might be here + * because we selected for ButtonPress on the root window. In that case, + * we have to take special care not to act for an override-redirect window. + */ + if (window->override_redirect) + return; + + /* Don't focus panels--they must explicitly request focus. + * See bug 160470 + */ + if (window->type != META_WINDOW_DOCK) + { + meta_topic (META_DEBUG_FOCUS, + "Focusing %s due to button %u press (display.c)", + window->desc, button); + meta_window_focus (window, event->any.time); + meta_window_check_alive (window, event->any.time); + } + else + /* However, do allow terminals to lose focus due to new + * window mappings after the user clicks on a panel. + */ + display->allow_terminal_deactivation = TRUE; + + /* We have three passive button grabs: + * - on any button, without modifiers => focuses and maybe raises the window + * - on resize button, with modifiers => start an interactive resizing + * (normally middle) + * - on move button, with modifiers => start an interactive move + * (normally left) + * - on menu button, with modifiers => show the window menu + * (normally right) + * + * We may get here because we actually have a button + * grab on the window, or because we're a wayland + * compositor and thus we see all the events, so we + * need to check if the event is interesting. + * We want an event that is not modified for a window. + * + * We may have other events on the window, for example + * a click on a frame button, but that's not for us to + * care about. Just let the event through. + */ + + grab_mods = meta_display_get_compositor_modifiers (display); + event_mods = clutter_event_get_state (event); + unmodified = (event_mods & grab_mods) == 0; + source = clutter_event_get_source_device (event); + is_window_button_grab_allowed = !display->focus_window || + !meta_window_shortcuts_inhibited (display->focus_window, source); + is_window_grab = (is_window_button_grab_allowed && + ((event_mods & grab_mods) == grab_mods)); + + clutter_event_get_coords (event, &x, &y); + + if (unmodified) + { + if (meta_prefs_get_raise_on_click ()) + meta_window_raise (window); + else + meta_topic (META_DEBUG_FOCUS, + "Not raising window on click due to don't-raise-on-click option"); + } + else if (is_window_grab && (int) button == meta_prefs_get_mouse_button_resize ()) + { + if (window->has_resize_func) + { + gboolean north, south; + gboolean west, east; + MetaRectangle frame_rect; + MetaGrabOp op = META_GRAB_OP_WINDOW_BASE; + + meta_window_get_frame_rect (window, &frame_rect); + + west = x < (frame_rect.x + 1 * frame_rect.width / 3); + east = x > (frame_rect.x + 2 * frame_rect.width / 3); + north = y < (frame_rect.y + 1 * frame_rect.height / 3); + south = y > (frame_rect.y + 2 * frame_rect.height / 3); + + if (west) + op |= META_GRAB_OP_WINDOW_DIR_WEST; + if (east) + op |= META_GRAB_OP_WINDOW_DIR_EAST; + if (north) + op |= META_GRAB_OP_WINDOW_DIR_NORTH; + if (south) + op |= META_GRAB_OP_WINDOW_DIR_SOUTH; + + if (op != META_GRAB_OP_WINDOW_BASE) + { + op |= META_GRAB_OP_WINDOW_FLAG_UNCONSTRAINED; + meta_window_begin_grab_op (window, + op, + clutter_event_get_device (event), + clutter_event_get_event_sequence (event), + event->any.time); + } + } + } + else if (is_window_grab && (int) button == meta_prefs_get_mouse_button_menu ()) + { + if (meta_prefs_get_raise_on_click ()) + meta_window_raise (window); + meta_window_show_menu (window, + META_WINDOW_MENU_WM, + x, y); + } + else if (is_window_grab && (int) button == 1) + { + if (window->has_move_func) + { + meta_window_begin_grab_op (window, + META_GRAB_OP_MOVING | + META_GRAB_OP_WINDOW_FLAG_UNCONSTRAINED, + clutter_event_get_device (event), + clutter_event_get_event_sequence (event), + event->any.time); + } + } +} + +gboolean +meta_window_can_maximize (MetaWindow *window) +{ + return window->has_maximize_func; +} + +gboolean +meta_window_can_minimize (MetaWindow *window) +{ + return window->has_minimize_func; +} + +gboolean +meta_window_can_close (MetaWindow *window) +{ + return window->has_close_func; +} + +gboolean +meta_window_is_always_on_all_workspaces (MetaWindow *window) +{ + return window->always_sticky; +} + +gboolean +meta_window_is_above (MetaWindow *window) +{ + return window->wm_state_above; +} + +gboolean +meta_window_allows_move (MetaWindow *window) +{ + return META_WINDOW_ALLOWS_MOVE (window); +} + +gboolean +meta_window_allows_resize (MetaWindow *window) +{ + return META_WINDOW_ALLOWS_RESIZE (window); +} + +void +meta_window_set_urgent (MetaWindow *window, + gboolean urgent) +{ + if (window->urgent == urgent) + return; + + window->urgent = urgent; + g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_URGENT]); + + if (urgent) + g_signal_emit_by_name (window->display, "window-marked-urgent", window); +} + +void +meta_window_grab_op_began (MetaWindow *window, + MetaGrabOp op) +{ + META_WINDOW_GET_CLASS (window)->grab_op_began (window, op); +} + +void +meta_window_grab_op_ended (MetaWindow *window, + MetaGrabOp op) +{ + META_WINDOW_GET_CLASS (window)->grab_op_ended (window, op); +} + +void +meta_window_emit_size_changed (MetaWindow *window) +{ + g_signal_emit (window, window_signals[SIZE_CHANGED], 0); +} + +MetaPlacementRule * +meta_window_get_placement_rule (MetaWindow *window) +{ + return window->placement.rule; +} + +void +meta_window_force_restore_shortcuts (MetaWindow *window, + ClutterInputDevice *source) +{ + META_WINDOW_GET_CLASS (window)->force_restore_shortcuts (window, source); +} + +gboolean +meta_window_shortcuts_inhibited (MetaWindow *window, + ClutterInputDevice *source) +{ + return META_WINDOW_GET_CLASS (window)->shortcuts_inhibited (window, source); +} + +gboolean +meta_window_is_focusable (MetaWindow *window) +{ + g_return_val_if_fail (!window->unmanaging, FALSE); + + return META_WINDOW_GET_CLASS (window)->is_focusable (window); +} + +gboolean +meta_window_can_ping (MetaWindow *window) +{ + g_return_val_if_fail (!window->unmanaging, FALSE); + + return META_WINDOW_GET_CLASS (window)->can_ping (window); +} + +gboolean +meta_window_is_stackable (MetaWindow *window) +{ + return META_WINDOW_GET_CLASS (window)->is_stackable (window); +} + +gboolean +meta_window_is_focus_async (MetaWindow *window) +{ + return META_WINDOW_GET_CLASS (window)->is_focus_async (window); +} + +MetaStackLayer +meta_window_calculate_layer (MetaWindow *window) +{ + return META_WINDOW_GET_CLASS (window)->calculate_layer (window); +} + +#ifdef HAVE_WAYLAND +MetaWaylandSurface * +meta_window_get_wayland_surface (MetaWindow *window) +{ + MetaWindowClass *klass = META_WINDOW_GET_CLASS (window); + g_return_val_if_fail (klass->get_wayland_surface != NULL, NULL); + + return klass->get_wayland_surface (window); +} +#endif + +/** + * meta_window_get_id: + * @window: a #MetaWindow + * + * Returns the window id associated with window. + * + * Returns: The window id + */ +uint64_t +meta_window_get_id (MetaWindow *window) +{ + return window->id; +} + +/** + * meta_window_get_client_type: + * @window: a #MetaWindow + * + * Returns the #MetaWindowClientType of the window. + * + * Returns: The root ancestor window + */ +MetaWindowClientType +meta_window_get_client_type (MetaWindow *window) +{ + return window->client_type; +} + +static gboolean +meta_window_close_dialog_timeout (MetaWindow *window) +{ + meta_window_show_close_dialog (window); + window->close_dialog_timeout_id = 0; + + return G_SOURCE_REMOVE; +} + +void +meta_window_ensure_close_dialog_timeout (MetaWindow *window) +{ + guint check_alive_timeout = meta_prefs_get_check_alive_timeout (); + + if (window->is_alive) + return; + if (window->close_dialog_timeout_id != 0) + return; + if (check_alive_timeout == 0) + return; + + window->close_dialog_timeout_id = + g_timeout_add (check_alive_timeout, + (GSourceFunc) meta_window_close_dialog_timeout, + window); + g_source_set_name_by_id (window->close_dialog_timeout_id, + "[mutter] meta_window_close_dialog_timeout"); +} + +void +meta_window_set_alive (MetaWindow *window, + gboolean is_alive) +{ + if (window->is_alive == is_alive) + return; + + window->is_alive = is_alive; + g_object_notify_by_pspec (G_OBJECT (window), obj_props[PROP_IS_ALIVE]); + + if (is_alive) + { + g_clear_handle_id (&window->close_dialog_timeout_id, g_source_remove); + meta_window_hide_close_dialog (window); + } +} + +gboolean +meta_window_get_alive (MetaWindow *window) +{ + return window->is_alive; +} + +gboolean +meta_window_calculate_bounds (MetaWindow *window, + int *bounds_width, + int *bounds_height) +{ + MetaLogicalMonitor *main_monitor; + + main_monitor = meta_window_get_main_logical_monitor (window); + if (main_monitor) + { + MetaRectangle work_area; + + meta_window_get_work_area_for_logical_monitor (window, + main_monitor, + &work_area); + + *bounds_width = work_area.width; + *bounds_height = work_area.height; + return TRUE; + } + else + { + return FALSE; + } +} diff --git a/src/tests/meta-monitor-test-utils.c b/src/tests/meta-monitor-test-utils.c index 98ff445..130b1ea 100644 --- a/src/tests/meta-monitor-test-utils.c +++ b/src/tests/meta-monitor-test-utils.c @@ -394,6 +394,10 @@ meta_check_monitor_configuration (MetaContext *context, ==, meta_output_is_underscanning (output)); + g_assert_cmpint (expect->monitors[i].is_vrr_allowed, + ==, + meta_output_is_vrr_allowed (output)); + if (!meta_output_get_max_bpc (output, &output_max_bpc)) output_max_bpc = 0; @@ -796,6 +800,7 @@ meta_create_monitor_test_setup (MetaBackend *backend, output_assignment = (MetaOutputAssignment) { .is_underscanning = setup->outputs[i].is_underscanning, + .is_vrr_allowed = setup->outputs[i].is_vrr_allowed, .has_max_bpc = !!setup->outputs[i].max_bpc, .max_bpc = setup->outputs[i].max_bpc, }; diff --git a/src/tests/meta-monitor-test-utils.h b/src/tests/meta-monitor-test-utils.h index 278a089..88573ad 100644 --- a/src/tests/meta-monitor-test-utils.h +++ b/src/tests/meta-monitor-test-utils.h @@ -106,6 +106,7 @@ typedef struct _MonitorTestCaseOutput float scale; gboolean is_laptop_panel; gboolean is_underscanning; + gboolean is_vrr_allowed; unsigned int max_bpc; const char *serial; MetaMonitorTransform panel_orientation_transform; @@ -161,6 +162,7 @@ typedef struct _MonitorTestCaseMonitor int width_mm; int height_mm; gboolean is_underscanning; + gboolean is_vrr_allowed; unsigned int max_bpc; } MonitorTestCaseMonitor; diff --git a/src/tests/monitor-configs/vrr-allowed.xml b/src/tests/monitor-configs/vrr-allowed.xml new file mode 100644 index 0000000..36846e0 --- /dev/null +++ b/src/tests/monitor-configs/vrr-allowed.xml @@ -0,0 +1,23 @@ + + + + 0 + 0 + yes + + + DP-1 + MetaProduct's Inc. + MetaMonitor + 0x123456 + + + 1024 + 768 + 60.000495910644531 + + yes + + + + diff --git a/src/tests/monitor-store-unit-tests.c b/src/tests/monitor-store-unit-tests.c index 7633f24..fb7cbe5 100644 --- a/src/tests/monitor-store-unit-tests.c +++ b/src/tests/monitor-store-unit-tests.c @@ -48,6 +48,7 @@ typedef struct _MonitorStoreTestCaseMonitor const char *serial; MonitorStoreTestCaseMonitorMode mode; gboolean is_underscanning; + gboolean is_vrr_allowed; unsigned int max_bpc; } MonitorStoreTestCaseMonitor; @@ -197,6 +198,9 @@ check_monitor_store_configuration (MetaMonitorConfigStore *config_store, g_assert_cmpint (monitor_config->enable_underscanning, ==, test_monitor->is_underscanning); + g_assert_cmpint (monitor_config->allow_vrr, + ==, + test_monitor->is_vrr_allowed); g_assert_cmpint (monitor_config->has_max_bpc, ==, !!test_monitor->max_bpc); @@ -453,6 +457,51 @@ meta_test_monitor_store_underscanning (void) check_monitor_store_configurations (&expect); } +static void +meta_test_monitor_store_vrr_allowed (void) +{ + MonitorStoreTestExpect expect = { + .configurations = { + { + .logical_monitors = { + { + .layout = { + .x = 0, + .y = 0, + .width = 1024, + .height = 768 + }, + .scale = 1, + .is_primary = TRUE, + .is_presentation = FALSE, + .monitors = { + { + .connector = "DP-1", + .vendor = "MetaProduct's Inc.", + .product = "MetaMonitor", + .serial = "0x123456", + .is_vrr_allowed = TRUE, + .mode = { + .width = 1024, + .height = 768, + .refresh_rate = 60.000495910644531 + } + } + }, + .n_monitors = 1, + }, + }, + .n_logical_monitors = 1 + } + }, + .n_configurations = 1 + }; + + meta_set_custom_monitor_config (test_context, "vrr-allowed.xml"); + + check_monitor_store_configurations (&expect); +} + static void meta_test_monitor_store_max_bpc (void) { @@ -1047,6 +1096,8 @@ init_monitor_store_tests (void) meta_test_monitor_store_primary); g_test_add_func ("/backends/monitor-store/underscanning", meta_test_monitor_store_underscanning); + g_test_add_func ("/backends/monitor-store/vrr-allowed", + meta_test_monitor_store_vrr_allowed); g_test_add_func ("/backends/monitor-store/max-bpc", meta_test_monitor_store_max_bpc); g_test_add_func ("/backends/monitor-store/scale", diff --git a/src/tests/monitor-unit-tests.c b/src/tests/monitor-unit-tests.c index 99355f6..86ce19a 100644 --- a/src/tests/monitor-unit-tests.c +++ b/src/tests/monitor-unit-tests.c @@ -3254,6 +3254,100 @@ meta_test_monitor_underscanning_config (void) check_monitor_test_clients_state (); } +static void +meta_test_monitor_vrr_allowed_config (void) +{ + MonitorTestCase test_case = { + .setup = { + .modes = { + { + .width = 1024, + .height = 768, + .refresh_rate = 60.0 + } + }, + .n_modes = 1, + .outputs = { + { + .crtc = 0, + .modes = { 0 }, + .n_modes = 1, + .preferred_mode = 0, + .possible_crtcs = { 0 }, + .n_possible_crtcs = 1, + .width_mm = 222, + .height_mm = 125, + .is_vrr_allowed = TRUE, + } + }, + .n_outputs = 1, + .crtcs = { + { + .current_mode = 0 + } + }, + .n_crtcs = 1 + }, + + .expect = { + .monitors = { + { + .outputs = { 0 }, + .n_outputs = 1, + .modes = { + { + .width = 1024, + .height = 768, + .refresh_rate = 60.0, + .crtc_modes = { + { + .output = 0, + .crtc_mode = 0 + } + } + } + }, + .n_modes = 1, + .current_mode = 0, + .width_mm = 222, + .height_mm = 125, + .is_vrr_allowed = TRUE, + } + }, + .n_monitors = 1, + .logical_monitors = { + { + .monitors = { 0 }, + .n_monitors = 1, + .layout = { .x = 0, .y = 0, .width = 1024, .height = 768 }, + .scale = 1 + } + }, + .n_logical_monitors = 1, + .primary_logical_monitor = 0, + .n_outputs = 1, + .crtcs = { + { + .current_mode = 0, + } + }, + .n_crtcs = 1, + .screen_width = 1024, + .screen_height = 768 + } + }; + MetaMonitorTestSetup *test_setup; + + test_setup = meta_create_monitor_test_setup (test_backend, + &test_case.setup, + MONITOR_TEST_FLAG_NO_STORED); + emulate_hotplug (test_setup); + META_TEST_LOG_CALL ("Checking monitor configuration", + meta_check_monitor_configuration (test_context, + &test_case.expect)); + check_monitor_test_clients_state (); +} + static void meta_test_monitor_max_bpc_config (void) { @@ -5839,6 +5933,103 @@ meta_test_monitor_custom_underscanning_config (void) check_monitor_test_clients_state (); } +static void +meta_test_monitor_custom_vrr_allowed_config (void) +{ + MonitorTestCase test_case = { + .setup = { + .modes = { + { + .width = 1024, + .height = 768, + .refresh_rate = 60.000495910644531 + } + }, + .n_modes = 1, + .outputs = { + { + .crtc = 0, + .modes = { 0 }, + .n_modes = 1, + .preferred_mode = 0, + .possible_crtcs = { 0 }, + .n_possible_crtcs = 1, + .width_mm = 222, + .height_mm = 125, + .serial = "0x123456", + }, + }, + .n_outputs = 1, + .crtcs = { + { + .current_mode = 0 + }, + }, + .n_crtcs = 1 + }, + + .expect = { + .monitors = { + { + .outputs = { 0 }, + .n_outputs = 1, + .modes = { + { + .width = 1024, + .height = 768, + .refresh_rate = 60.000495910644531, + .crtc_modes = { + { + .output = 0, + .crtc_mode = 0 + } + } + } + }, + .n_modes = 1, + .current_mode = 0, + .width_mm = 222, + .height_mm = 125, + .is_vrr_allowed = TRUE, + } + }, + .n_monitors = 1, + .logical_monitors = { + { + .monitors = { 0 }, + .n_monitors = 1, + .layout = { .x = 0, .y = 0, .width = 1024, .height = 768 }, + .scale = 1 + } + }, + .n_logical_monitors = 1, + .primary_logical_monitor = 0, + .n_outputs = 1, + .crtcs = { + { + .current_mode = 0, + } + }, + .n_crtcs = 1, + .n_tiled_monitors = 0, + .screen_width = 1024, + .screen_height = 768 + } + }; + MetaMonitorTestSetup *test_setup; + + test_setup = meta_create_monitor_test_setup (test_backend, + &test_case.setup, + MONITOR_TEST_FLAG_NONE); + meta_set_custom_monitor_config (test_context, "vrr-allowed.xml"); + emulate_hotplug (test_setup); + + META_TEST_LOG_CALL ("Checking monitor configuration", + meta_check_monitor_configuration (test_context, + &test_case.expect)); + check_monitor_test_clients_state (); +} + static void meta_test_monitor_custom_scale_config (void) { @@ -9624,6 +9815,8 @@ init_monitor_tests (void) meta_test_monitor_no_outputs); add_monitor_test ("/backends/monitor/underscanning-config", meta_test_monitor_underscanning_config); + add_monitor_test ("/backends/monitor/vrr-allowed-config", + meta_test_monitor_vrr_allowed_config); add_monitor_test ("/backends/monitor/max-bpc-config", meta_test_monitor_max_bpc_config); add_monitor_test ("/backends/monitor/preferred-non-first-mode", @@ -9658,6 +9851,8 @@ init_monitor_tests (void) meta_test_monitor_custom_primary_config); add_monitor_test ("/backends/monitor/custom/underscanning-config", meta_test_monitor_custom_underscanning_config); + add_monitor_test ("/backends/monitor/custom/vrr-allowed-config", + meta_test_monitor_custom_vrr_allowed_config); add_monitor_test ("/backends/monitor/custom/scale-config", meta_test_monitor_custom_scale_config); add_monitor_test ("/backends/monitor/custom/fractional-scale-config", -- 2.41.0 diff --git a/data/org.gnome.mutter.gschema.xml.in b/data/org.gnome.mutter.gschema.xml.in index 34e2a22..ec134e5 100644 --- a/data/org.gnome.mutter.gschema.xml.in +++ b/data/org.gnome.mutter.gschema.xml.in @@ -104,7 +104,7 @@ - [] + ['variable-refresh-rate'] Enable experimental features To enable experimental features, add the feature keyword to the list.