commit cace2ce2d02eaeaa6b86db245e1378059c994a5f Author: CentOS Sources Date: Tue May 17 04:41:21 2022 -0400 import mutter-40.9-1.el9 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..56b1812 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +SOURCES/mutter-40.9.tar.xz diff --git a/.mutter.metadata b/.mutter.metadata new file mode 100644 index 0000000..4e26344 --- /dev/null +++ b/.mutter.metadata @@ -0,0 +1 @@ +f9b20f8330ecdb76fc887c4a99c03b406e26fd66 SOURCES/mutter-40.9.tar.xz diff --git a/SOURCES/0001-Revert-build-Do-not-provide-built-sources-as-libmutt.patch b/SOURCES/0001-Revert-build-Do-not-provide-built-sources-as-libmutt.patch new file mode 100644 index 0000000..bd18e8a --- /dev/null +++ b/SOURCES/0001-Revert-build-Do-not-provide-built-sources-as-libmutt.patch @@ -0,0 +1,26 @@ +From 3899a01cd6cb00ca686946d3065d58f59f5c2099 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Tue, 17 Nov 2020 14:00:02 +0100 +Subject: [PATCH] Revert "build: Do not provide built sources as libmutter_dep + sources" + +This reverts commit 4e9a2e479969973bf3063c740ceff149036b3af4. +--- + src/meson.build | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/meson.build b/src/meson.build +index e7c99caee..8fe484ec2 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -955,6 +955,7 @@ libmutter = shared_library(libmutter_name, + libmutter_dep = declare_dependency( + link_with: libmutter, + include_directories: mutter_includes, ++ sources: mutter_built_sources, + dependencies: [ + libmutter_cogl_dep, + libmutter_clutter_dep, +-- +2.28.0 + diff --git a/SOURCES/0001-Test-deny-atomic-KMS-for-tegra-RHBZ-1936991.patch b/SOURCES/0001-Test-deny-atomic-KMS-for-tegra-RHBZ-1936991.patch new file mode 100644 index 0000000..d847397 --- /dev/null +++ b/SOURCES/0001-Test-deny-atomic-KMS-for-tegra-RHBZ-1936991.patch @@ -0,0 +1,35 @@ +From 9d0ded3178777cd6afcdd5fff7b6f0f39a0d5236 Mon Sep 17 00:00:00 2001 +From: Adam Williamson +Date: Tue, 9 Mar 2021 17:21:59 -0800 +Subject: [PATCH] Test: deny atomic KMS for "tegra" (RHBZ #1936991) + +Signed-off-by: Adam Williamson +--- + data/61-mutter.rules | 1 + + src/backends/native/meta-kms-device.c | 1 + + 2 files changed, 2 insertions(+) + +diff --git a/data/61-mutter.rules b/data/61-mutter.rules +index edc03e6c1..d8e3c5f00 100644 +--- a/data/61-mutter.rules ++++ b/data/61-mutter.rules +@@ -3,3 +3,4 @@ DRIVERS=="nouveau", SUBSYSTEM=="drm", TAG+="mutter-device-disable-kms-modifiers" + DRIVERS=="amdgpu", SUBSYSTEM=="drm", TAG+="mutter-device-disable-kms-modifiers" + DRIVERS=="radeon", SUBSYSTEM=="drm", TAG+="mutter-device-disable-kms-modifiers" + ENV{ID_PATH}=="platform-vkms", TAG+="mutter-device-ignore" ++DRIVER=="tegra", SUBSYSTEM=="platform", TAG+="mutter-device-disable-atomic-kms" +diff --git a/src/backends/native/meta-kms-device.c b/src/backends/native/meta-kms-device.c +index 85aded9a6..e749ab6b9 100644 +--- a/src/backends/native/meta-kms-device.c ++++ b/src/backends/native/meta-kms-device.c +@@ -253,6 +253,7 @@ is_atomic_allowed (const char *driver_name) + "vboxvideo", + "nvidia-drm", + "virtio_gpu", ++ "tegra", + NULL, + }; + +-- +2.32.0 + diff --git a/SOURCES/0001-backend-Clean-up-renderer-after-clutter-backendm.patch b/SOURCES/0001-backend-Clean-up-renderer-after-clutter-backendm.patch new file mode 100644 index 0000000..e1d9c4d --- /dev/null +++ b/SOURCES/0001-backend-Clean-up-renderer-after-clutter-backendm.patch @@ -0,0 +1,92 @@ +From ff4dc8cc8274dc5f6ed11515e05a341e4e2cec28 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Thu, 12 Aug 2021 14:13:23 -0400 +Subject: [PATCH] backend: Clean up renderer after clutter backendm + +commit c4a73e795020722eda3e2bec0c16d96f9f37333b added +code to cleanup the renderer when the meta backend is +disposed. Unfortunately, this introduced a crash when +the window manager is replaced. + +This is because cleaning up the renderer involves talking +to the X server over a display connection that's closed +two lines higher as part of the clutter_backend_destroy +call. + +This commit fixes the crash by swapping their order. +--- + src/backends/meta-backend.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/backends/meta-backend.c b/src/backends/meta-backend.c +index ff84bfe6a..7e8b4ee95 100644 +--- a/src/backends/meta-backend.c ++++ b/src/backends/meta-backend.c +@@ -216,63 +216,63 @@ meta_backend_dispose (GObject *object) + + 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_pointer (&priv->device_monitors, g_hash_table_destroy); + + g_clear_object (&priv->settings); + + #ifdef HAVE_PROFILER + g_clear_object (&priv->profiler); + #endif + + g_clear_pointer (&priv->default_seat, clutter_seat_destroy); + g_clear_pointer (&priv->stage, clutter_actor_destroy); +- g_clear_pointer (&priv->clutter_backend, clutter_backend_destroy); + g_clear_object (&priv->renderer); + g_clear_list (&priv->gpus, g_object_unref); ++ g_clear_pointer (&priv->clutter_backend, clutter_backend_destroy); + + G_OBJECT_CLASS (meta_backend_parent_class)->dispose (object); + } + + static 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 = clutter_backend_get_default_seat (priv->clutter_backend); + MetaLogicalMonitor *primary; + +-- +2.31.1 + diff --git a/SOURCES/0001-backends-x11-Fix-key-repeat-of-on-screen-keyboard-fo.patch b/SOURCES/0001-backends-x11-Fix-key-repeat-of-on-screen-keyboard-fo.patch new file mode 100644 index 0000000..1099461 --- /dev/null +++ b/SOURCES/0001-backends-x11-Fix-key-repeat-of-on-screen-keyboard-fo.patch @@ -0,0 +1,260 @@ +From a1f33bdac95ba4fd0599f164ef893c05d8be123b Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Wed, 6 Oct 2021 15:31:30 -0400 +Subject: [PATCH] backends/x11: Fix key repeat of on-screen keyboard for second + level keysyms + +Certains keys (such as ~ and |) are in the keyboard map behind the +second shift level. This means in order for them to be input, the +shift key needs to be held down by the user. + +The GNOME Shell on-screen keyboard presents these keys separately on +a page of keys that has no shift key. Instead, it relies on mutter +to set a shift latch before the key event is emitted. A shift latch +is a virtual press of the shift key that automatically gets released +after the next key press (in our case the ~ or | key). + +The problem is using a shift latch doesn't work very well in the face +of key repeat. The latch is automatically released after the first +press, and subsequent repeats of that press no longer have shift +latched to them. + +This commit fixes the problem by using a shift lock instead of a shift +latch. A shift lock is never implicitly released, so it remains +in place for the duration of key repeat. +--- + src/backends/x11/meta-keymap-x11.c | 12 ++++++------ + src/backends/x11/meta-keymap-x11.h | 6 +++--- + src/backends/x11/meta-virtual-input-device-x11.c | 4 ++-- + 3 files changed, 11 insertions(+), 11 deletions(-) + +diff --git a/src/backends/x11/meta-keymap-x11.c b/src/backends/x11/meta-keymap-x11.c +index da5d064e7..1192cc387 100644 +--- a/src/backends/x11/meta-keymap-x11.c ++++ b/src/backends/x11/meta-keymap-x11.c +@@ -829,85 +829,85 @@ meta_keymap_x11_reserve_keycode (MetaKeymapX11 *keymap_x11, + g_warning ("Cannot reserve a keycode for keyval %d: no available keycode", keyval); + return FALSE; + } + + if (!meta_keymap_x11_replace_keycode (keymap_x11, *keycode_out, keyval)) + { + g_warning ("Failed to remap keycode %d to keyval %d", *keycode_out, keyval); + return FALSE; + } + + g_hash_table_insert (keymap_x11->reserved_keycodes, GUINT_TO_POINTER (*keycode_out), GUINT_TO_POINTER (keyval)); + g_queue_remove (keymap_x11->available_keycodes, GUINT_TO_POINTER (*keycode_out)); + + return TRUE; + } + + void + meta_keymap_x11_release_keycode_if_needed (MetaKeymapX11 *keymap_x11, + uint32_t keycode) + { + g_return_if_fail (META_IS_KEYMAP_X11 (keymap_x11)); + + if (!g_hash_table_contains (keymap_x11->reserved_keycodes, GUINT_TO_POINTER (keycode)) || + g_queue_index (keymap_x11->available_keycodes, GUINT_TO_POINTER (keycode)) != -1) + return; + + g_queue_push_tail (keymap_x11->available_keycodes, GUINT_TO_POINTER (keycode)); + } + + void +-meta_keymap_x11_latch_modifiers (MetaKeymapX11 *keymap_x11, +- uint32_t level, +- gboolean enable) ++meta_keymap_x11_lock_modifiers (MetaKeymapX11 *keymap_x11, ++ uint32_t level, ++ gboolean enable) + { + uint32_t modifiers[] = { + 0, + ShiftMask, + keymap_x11->level3_shift_mask, + keymap_x11->level3_shift_mask | ShiftMask, + }; + uint32_t value = 0; + + if (!keymap_x11->use_xkb) + return; + + level = CLAMP (level, 0, G_N_ELEMENTS (modifiers) - 1); + + if (enable) + value = modifiers[level]; + else + value = 0; + +- XkbLatchModifiers (clutter_x11_get_default_display (), +- XkbUseCoreKbd, modifiers[level], +- value); ++ XkbLockModifiers (clutter_x11_get_default_display (), ++ XkbUseCoreKbd, modifiers[level], ++ value); + } + + static uint32_t + meta_keymap_x11_get_current_group (MetaKeymapX11 *keymap_x11) + { + XkbStateRec state_rec; + + if (keymap_x11->current_group >= 0) + return keymap_x11->current_group; + + XkbGetState (clutter_x11_get_default_display (), + XkbUseCoreKbd, &state_rec); + return XkbStateGroup (&state_rec); + } + + gboolean + meta_keymap_x11_keycode_for_keyval (MetaKeymapX11 *keymap_x11, + uint32_t keyval, + uint32_t *keycode_out, + uint32_t *level_out) + { + ClutterKeymapKey *keys; + int i, n_keys, group; + gboolean found = FALSE; + + g_return_val_if_fail (keycode_out != NULL, FALSE); + g_return_val_if_fail (level_out != NULL, FALSE); + + group = meta_keymap_x11_get_current_group (keymap_x11); + +diff --git a/src/backends/x11/meta-keymap-x11.h b/src/backends/x11/meta-keymap-x11.h +index 67a5f8eb9..2f93acdbc 100644 +--- a/src/backends/x11/meta-keymap-x11.h ++++ b/src/backends/x11/meta-keymap-x11.h +@@ -17,45 +17,45 @@ + * Author: Emmanuele Bassi + */ + + #ifndef META_KEYMAP_X11_H + #define META_KEYMAP_X11_H + + #include + #include + + #include "clutter/clutter.h" + + G_BEGIN_DECLS + + #define META_TYPE_KEYMAP_X11 (meta_keymap_x11_get_type ()) + G_DECLARE_FINAL_TYPE (MetaKeymapX11, meta_keymap_x11, + META, KEYMAP_X11, ClutterKeymap) + + int meta_keymap_x11_get_key_group (MetaKeymapX11 *keymap, + ClutterModifierType state); + int meta_keymap_x11_translate_key_state (MetaKeymapX11 *keymap, + guint hardware_keycode, + ClutterModifierType *modifier_state_p, + ClutterModifierType *mods_p); + gboolean meta_keymap_x11_get_is_modifier (MetaKeymapX11 *keymap, + int keycode); + + gboolean meta_keymap_x11_keycode_for_keyval (MetaKeymapX11 *keymap_x11, + guint keyval, + guint *keycode_out, + guint *level_out); +-void meta_keymap_x11_latch_modifiers (MetaKeymapX11 *keymap_x11, +- uint32_t level, +- gboolean enable); ++void meta_keymap_x11_lock_modifiers (MetaKeymapX11 *keymap_x11, ++ uint32_t level, ++ gboolean enable); + gboolean meta_keymap_x11_reserve_keycode (MetaKeymapX11 *keymap_x11, + guint keyval, + guint *keycode_out); + void meta_keymap_x11_release_keycode_if_needed (MetaKeymapX11 *keymap_x11, + guint keycode); + + gboolean meta_keymap_x11_handle_event (MetaKeymapX11 *keymap_x11, + XEvent *xevent); + + G_END_DECLS + + #endif /* META_KEYMAP_X11_H */ +diff --git a/src/backends/x11/meta-virtual-input-device-x11.c b/src/backends/x11/meta-virtual-input-device-x11.c +index fe6040859..1a5cdfc2e 100644 +--- a/src/backends/x11/meta-virtual-input-device-x11.c ++++ b/src/backends/x11/meta-virtual-input-device-x11.c +@@ -159,71 +159,71 @@ meta_virtual_input_device_x11_notify_key (ClutterVirtualInputDevice *virtual_dev + ClutterKeyState key_state) + { + XTestFakeKeyEvent (clutter_x11_get_default_display (), + key + 8, key_state == CLUTTER_KEY_STATE_PRESSED, 0); + } + + static void + meta_virtual_input_device_x11_notify_keyval (ClutterVirtualInputDevice *virtual_device, + uint64_t time_us, + uint32_t keyval, + ClutterKeyState key_state) + { + ClutterBackend *backend = clutter_get_default_backend (); + ClutterSeat *seat = clutter_backend_get_default_seat (backend); + MetaKeymapX11 *keymap = META_KEYMAP_X11 (clutter_seat_get_keymap (seat)); + uint32_t keycode, level; + + if (!meta_keymap_x11_keycode_for_keyval (keymap, keyval, &keycode, &level)) + { + level = 0; + + if (!meta_keymap_x11_reserve_keycode (keymap, keyval, &keycode)) + { + g_warning ("No keycode found for keyval %x in current group", keyval); + return; + } + } + + if (!meta_keymap_x11_get_is_modifier (keymap, keycode) && + key_state == CLUTTER_KEY_STATE_PRESSED) +- meta_keymap_x11_latch_modifiers (keymap, level, TRUE); ++ meta_keymap_x11_lock_modifiers (keymap, level, TRUE); + + XTestFakeKeyEvent (clutter_x11_get_default_display (), + (KeyCode) keycode, + key_state == CLUTTER_KEY_STATE_PRESSED, 0); + + + if (key_state == CLUTTER_KEY_STATE_RELEASED) + { + if (!meta_keymap_x11_get_is_modifier (keymap, keycode)) +- meta_keymap_x11_latch_modifiers (keymap, level, FALSE); ++ meta_keymap_x11_lock_modifiers (keymap, level, FALSE); + meta_keymap_x11_release_keycode_if_needed (keymap, keycode); + } + } + + static void + meta_virtual_input_device_x11_notify_touch_down (ClutterVirtualInputDevice *virtual_device, + uint64_t time_us, + int device_slot, + double x, + double y) + { + g_warning ("Virtual touch motion not implemented under X11"); + } + + static void + meta_virtual_input_device_x11_notify_touch_motion (ClutterVirtualInputDevice *virtual_device, + uint64_t time_us, + int device_slot, + double x, + double y) + { + g_warning ("Virtual touch motion not implemented under X11"); + } + + static void + meta_virtual_input_device_x11_notify_touch_up (ClutterVirtualInputDevice *virtual_device, + uint64_t time_us, + int device_slot) + { + g_warning ("Virtual touch motion not implemented under X11"); +-- +2.33.1 + diff --git a/SOURCES/0001-constraints-Enforce-X11-size-limits.patch b/SOURCES/0001-constraints-Enforce-X11-size-limits.patch new file mode 100644 index 0000000..a09a40b --- /dev/null +++ b/SOURCES/0001-constraints-Enforce-X11-size-limits.patch @@ -0,0 +1,85 @@ +From 1ab51efc968d7d3c6244d9b7efcdf4bae4fc0a9d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 12 Mar 2014 02:04:13 +0100 +Subject: [PATCH] constraints: Enforce X11 size limits + +X11 limits windows to a maximum of 32767x32767, enforce that restriction +to keep insanely huge windows from crashing the WM. +--- + src/core/constraints.c | 42 ++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 42 insertions(+) + +diff --git a/src/core/constraints.c b/src/core/constraints.c +index 4b1d95338a..eee16dc48f 100644 +--- a/src/core/constraints.c ++++ b/src/core/constraints.c +@@ -109,6 +109,7 @@ typedef enum + PRIORITY_TITLEBAR_VISIBLE = 4, + PRIORITY_PARTIALLY_VISIBLE_ON_WORKAREA = 4, + PRIORITY_CUSTOM_RULE = 4, ++ PRIORITY_XLIMITS = 4, + PRIORITY_MAXIMUM = 4 /* Dummy value used for loop end = max(all priorities) */ + } ConstraintPriority; + +@@ -204,6 +205,10 @@ static gboolean constrain_partially_onscreen (MetaWindow *window, + ConstraintInfo *info, + ConstraintPriority priority, + gboolean check_only); ++static gboolean constrain_xlimits (MetaWindow *window, ++ ConstraintInfo *info, ++ ConstraintPriority priority, ++ gboolean check_only); + + static void setup_constraint_info (ConstraintInfo *info, + MetaWindow *window, +@@ -239,6 +244,7 @@ static const Constraint all_constraints[] = { + {constrain_fully_onscreen, "constrain_fully_onscreen"}, + {constrain_titlebar_visible, "constrain_titlebar_visible"}, + {constrain_partially_onscreen, "constrain_partially_onscreen"}, ++ {constrain_xlimits, "constrain_xlimits"}, + {NULL, NULL} + }; + +@@ -1876,3 +1882,39 @@ constrain_partially_onscreen (MetaWindow *window, + + return retval; + } ++ ++ ++#define MAX_WINDOW_SIZE 32767 ++ ++static gboolean ++constrain_xlimits (MetaWindow *window, ++ ConstraintInfo *info, ++ ConstraintPriority priority, ++ gboolean check_only) ++{ ++ int max_w, max_h; ++ gboolean constraint_already_satisfied; ++ ++ if (priority > PRIORITY_XLIMITS) ++ return TRUE; ++ ++ max_w = max_h = MAX_WINDOW_SIZE; ++ ++ if (window->frame) ++ { ++ MetaFrameBorders borders; ++ meta_frame_calc_borders (window->frame, &borders); ++ ++ max_w -= (borders.total.left + borders.total.right); ++ max_h -= (borders.total.top + borders.total.bottom); ++ } ++ ++ constraint_already_satisfied = info->current.width < max_w && info->current.height < max_h; ++ if (check_only || constraint_already_satisfied) ++ return constraint_already_satisfied; ++ ++ info->current.width = MIN (info->current.width, max_w); ++ info->current.height = MIN (info->current.height, max_h); ++ ++ return TRUE; ++} +-- +2.31.1 + diff --git a/SOURCES/0001-events-Don-t-move-sloppy-focus-while-buttons-are-pre.patch b/SOURCES/0001-events-Don-t-move-sloppy-focus-while-buttons-are-pre.patch new file mode 100644 index 0000000..2ff5f1b --- /dev/null +++ b/SOURCES/0001-events-Don-t-move-sloppy-focus-while-buttons-are-pre.patch @@ -0,0 +1,42 @@ +From 7ac5b7bad8f2d0e61700610f68282f6687cc9d2e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 21 Jul 2016 15:43:12 +0200 +Subject: [PATCH] events: Don't move (sloppy) focus while buttons are pressed + +(https://bugzilla.redhat.com/show_bug.cgi?id=1358535) +--- + src/x11/events.c | 11 +++++++++++ + 1 file changed, 11 insertions(+) + +diff --git a/src/x11/events.c b/src/x11/events.c +index efa8f9856b..388eff0ac7 100644 +--- a/src/x11/events.c ++++ b/src/x11/events.c +@@ -839,6 +839,16 @@ crossing_serial_is_ignored (MetaX11Display *x11_display, + return FALSE; + } + ++static gboolean ++event_has_button_mask (XIEnterEvent *enter_event) ++{ ++ int i; ++ for (i = 0; i < enter_event->buttons.mask_len; i++) ++ if (enter_event->buttons.mask[i] != '\0') ++ return TRUE; ++ return FALSE; ++} ++ + static gboolean + handle_input_xevent (MetaX11Display *x11_display, + XIEvent *input_event, +@@ -883,6 +893,7 @@ handle_input_xevent (MetaX11Display *x11_display, + * avoid races. + */ + if (window && !crossing_serial_is_ignored (x11_display, serial) && ++ !event_has_button_mask (enter_event) && + enter_event->mode != XINotifyGrab && + enter_event->mode != XINotifyUngrab && + enter_event->detail != XINotifyInferior && +-- +2.31.1 + diff --git a/SOURCES/0001-main-be-more-aggressive-in-assuming-X11-backend.patch b/SOURCES/0001-main-be-more-aggressive-in-assuming-X11-backend.patch new file mode 100644 index 0000000..806ad38 --- /dev/null +++ b/SOURCES/0001-main-be-more-aggressive-in-assuming-X11-backend.patch @@ -0,0 +1,49 @@ +From 99c74360451a85fca9dacad531ed22adbc1b0805 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Tue, 13 Feb 2018 09:44:50 -0500 +Subject: [PATCH] main: be more aggressive in assuming X11 backend + +If the session is started by vncserver right now, the +XDG_SESSION_TYPE won't be X11. Ideally that would be +fixed, but for backward compatibility we should default +to X11 if the session type isn't set to wayland explicitly. +--- + src/core/main.c | 8 +++----- + 1 file changed, 3 insertions(+), 5 deletions(-) + +diff --git a/src/core/main.c b/src/core/main.c +index a07dda9ecc..0d241f952b 100644 +--- a/src/core/main.c ++++ b/src/core/main.c +@@ -407,7 +407,6 @@ find_session_type (void) + char *session_id; + char *session_type; + const char *session_type_env; +- gboolean is_tty = FALSE; + int ret, i; + + ret = sd_pid_get_session (0, &session_id); +@@ -420,8 +419,7 @@ find_session_type (void) + { + if (session_type_is_supported (session_type)) + goto out; +- else +- is_tty = g_strcmp0 (session_type, "tty") == 0; ++ + free (session_type); + } + } +@@ -453,8 +451,8 @@ find_session_type (void) + goto out; + } + +- /* Legacy support for starting through xinit */ +- if (is_tty && (g_getenv ("MUTTER_DISPLAY") || g_getenv ("DISPLAY"))) ++ /* Legacy support for starting through xinit or vncserver */ ++ if (g_getenv ("MUTTER_DISPLAY") || g_getenv ("DISPLAY")) + { + session_type = strdup ("x11"); + goto out; +-- +2.31.1 + diff --git a/SOURCES/0001-wayland-Allow-Xwayland-grabs-on-selected-apps.patch b/SOURCES/0001-wayland-Allow-Xwayland-grabs-on-selected-apps.patch new file mode 100644 index 0000000..14c9f10 --- /dev/null +++ b/SOURCES/0001-wayland-Allow-Xwayland-grabs-on-selected-apps.patch @@ -0,0 +1,35 @@ +From 6e2ef652cd58136aa668d0c1bd843fe83f11a0ab Mon Sep 17 00:00:00 2001 +From: Olivier Fourdan +Date: Fri, 26 Oct 2018 08:49:39 +0200 +Subject: [PATCH] wayland: Allow Xwayland grabs on selected apps + +Allow Xwayland grabs on a selected set of X11 applications. +--- + data/org.gnome.mutter.wayland.gschema.xml.in | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/data/org.gnome.mutter.wayland.gschema.xml.in b/data/org.gnome.mutter.wayland.gschema.xml.in +index 8a1878e105..5527a46bc6 100644 +--- a/data/org.gnome.mutter.wayland.gschema.xml.in ++++ b/data/org.gnome.mutter.wayland.gschema.xml.in +@@ -66,7 +66,7 @@ + gettext-domain="@GETTEXT_DOMAIN@"> + + +- false ++ true + Allow X11 grabs to lock keyboard focus with Xwayland + + Allow all keyboard events to be routed to X11 “override redirect” +@@ -86,7 +86,7 @@ + + + +- [] ++ ['@XWAYLAND_GRAB_DEFAULT_ACCESS_RULES@'] + Xwayland applications allowed to issue keyboard grabs + + List the resource names or resource class of X11 windows either +-- +2.31.1 + diff --git a/SOURCES/0001-wayland-Avoid-a-race-in-wl_seat-capabilities.patch b/SOURCES/0001-wayland-Avoid-a-race-in-wl_seat-capabilities.patch new file mode 100644 index 0000000..5a9783b --- /dev/null +++ b/SOURCES/0001-wayland-Avoid-a-race-in-wl_seat-capabilities.patch @@ -0,0 +1,192 @@ +From 5e4a1290ce75ed94e3f0f457d35a225f2ef3878c Mon Sep 17 00:00:00 2001 +From: Olivier Fourdan +Date: Tue, 28 Nov 2017 10:54:08 +0100 +Subject: [PATCH] wayland: Avoid a race in wl_seat capabilities + +The way wl_seat capabilities work, by notifying clients of capabilities +changes, and clients consequently requesting the relevant interface +objects (pointer, keyboard, touch) is inherently racy. + +On quick VT changes for example, capabilities on the seat will be added +and removed, and by the time the client receives the capability change +notification and requests the relevant keyboard, pointer or touch, +another VT switch might have occurred and the wl_pointer, wl_keyboard or +wl_touch already destroyed, leading to a protocol error which kills the +client. + +To avoid this, create the objects when requested regardless of the +capabilities. + +Closes: https://gitlab.gnome.org/GNOME/mutter/-/issues/1797 +Related: https://bugzilla.gnome.org/show_bug.cgi?id=790932 +--- + src/wayland/meta-wayland-pointer.c | 45 ++++++++++++++++++++++++------ + src/wayland/meta-wayland-seat.c | 9 ++---- + src/wayland/meta-wayland-touch.c | 8 ------ + 3 files changed, 40 insertions(+), 22 deletions(-) + +diff --git a/src/wayland/meta-wayland-pointer.c b/src/wayland/meta-wayland-pointer.c +index 3132abfd2..abd779ad7 100644 +--- a/src/wayland/meta-wayland-pointer.c ++++ b/src/wayland/meta-wayland-pointer.c +@@ -109,7 +109,7 @@ meta_wayland_pointer_client_new (void) + } + + static void +-meta_wayland_pointer_client_free (MetaWaylandPointerClient *pointer_client) ++meta_wayland_pointer_make_resources_inert (MetaWaylandPointerClient *pointer_client) + { + struct wl_resource *resource, *next; + +@@ -141,10 +141,25 @@ meta_wayland_pointer_client_free (MetaWaylandPointerClient *pointer_client) + wl_list_init (wl_resource_get_link (resource)); + wl_resource_set_user_data (resource, NULL); + } ++} + ++static void ++meta_wayland_pointer_client_free (MetaWaylandPointerClient *pointer_client) ++{ ++ meta_wayland_pointer_make_resources_inert (pointer_client); + g_free (pointer_client); + } + ++static void ++make_resources_inert_foreach (gpointer key, ++ gpointer value, ++ gpointer data) ++{ ++ MetaWaylandPointerClient *pointer_client = value; ++ ++ meta_wayland_pointer_make_resources_inert (pointer_client); ++} ++ + static gboolean + meta_wayland_pointer_client_is_empty (MetaWaylandPointerClient *pointer_client) + { +@@ -158,8 +173,6 @@ MetaWaylandPointerClient * + meta_wayland_pointer_get_pointer_client (MetaWaylandPointer *pointer, + struct wl_client *client) + { +- if (!pointer->pointer_clients) +- return NULL; + return g_hash_table_lookup (pointer->pointer_clients, client); + } + +@@ -475,10 +488,6 @@ meta_wayland_pointer_enable (MetaWaylandPointer *pointer) + MetaCursorTracker *cursor_tracker = meta_backend_get_cursor_tracker (backend); + ClutterSeat *clutter_seat; + +- pointer->pointer_clients = +- g_hash_table_new_full (NULL, NULL, NULL, +- (GDestroyNotify) meta_wayland_pointer_client_free); +- + pointer->cursor_surface = NULL; + + clutter_seat = clutter_backend_get_default_seat (clutter_get_default_backend ()); +@@ -508,6 +517,10 @@ meta_wayland_pointer_disable (MetaWaylandPointer *pointer) + ClutterBackend *clutter_backend = clutter_get_default_backend (); + ClutterSeat *clutter_seat = clutter_backend_get_default_seat (clutter_backend); + ++ g_hash_table_foreach (pointer->pointer_clients, ++ make_resources_inert_foreach, ++ NULL); ++ + g_signal_handlers_disconnect_by_func (cursor_tracker, + (gpointer) meta_wayland_pointer_on_cursor_changed, + pointer); +@@ -531,7 +544,6 @@ meta_wayland_pointer_disable (MetaWaylandPointer *pointer) + meta_wayland_pointer_set_focus (pointer, NULL); + meta_wayland_pointer_set_current (pointer, NULL); + +- g_clear_pointer (&pointer->pointer_clients, g_hash_table_unref); + pointer->cursor_surface = NULL; + } + +@@ -1356,11 +1368,28 @@ meta_wayland_pointer_init (MetaWaylandPointer *pointer) + pointer->default_grab.interface = &default_pointer_grab_interface; + pointer->default_grab.pointer = pointer; + pointer->grab = &pointer->default_grab; ++ pointer->pointer_clients = ++ g_hash_table_new_full (NULL, NULL, NULL, ++ (GDestroyNotify) meta_wayland_pointer_client_free); ++} ++ ++static void ++meta_wayland_pointer_finalize (GObject *object) ++{ ++ MetaWaylandPointer *pointer = META_WAYLAND_POINTER (object); ++ ++ g_clear_pointer (&pointer->pointer_clients, g_hash_table_unref); ++ ++ G_OBJECT_CLASS (meta_wayland_pointer_parent_class)->finalize (object); + } + + static void + meta_wayland_pointer_class_init (MetaWaylandPointerClass *klass) + { ++ GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ ++ object_class->finalize = meta_wayland_pointer_finalize; ++ + signals[FOCUS_SURFACE_CHANGED] = g_signal_new ("focus-surface-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, +diff --git a/src/wayland/meta-wayland-seat.c b/src/wayland/meta-wayland-seat.c +index c6390dde7..efce6d6d6 100644 +--- a/src/wayland/meta-wayland-seat.c ++++ b/src/wayland/meta-wayland-seat.c +@@ -46,8 +46,7 @@ seat_get_pointer (struct wl_client *client, + MetaWaylandSeat *seat = wl_resource_get_user_data (resource); + MetaWaylandPointer *pointer = seat->pointer; + +- if (meta_wayland_seat_has_pointer (seat)) +- meta_wayland_pointer_create_new_resource (pointer, client, resource, id); ++ meta_wayland_pointer_create_new_resource (pointer, client, resource, id); + } + + static void +@@ -58,8 +57,7 @@ seat_get_keyboard (struct wl_client *client, + MetaWaylandSeat *seat = wl_resource_get_user_data (resource); + MetaWaylandKeyboard *keyboard = seat->keyboard; + +- if (meta_wayland_seat_has_keyboard (seat)) +- meta_wayland_keyboard_create_new_resource (keyboard, client, resource, id); ++ meta_wayland_keyboard_create_new_resource (keyboard, client, resource, id); + } + + static void +@@ -70,8 +68,7 @@ seat_get_touch (struct wl_client *client, + MetaWaylandSeat *seat = wl_resource_get_user_data (resource); + MetaWaylandTouch *touch = seat->touch; + +- if (meta_wayland_seat_has_touch (seat)) +- meta_wayland_touch_create_new_resource (touch, client, resource, id); ++ meta_wayland_touch_create_new_resource (touch, client, resource, id); + } + + static void +diff --git a/src/wayland/meta-wayland-touch.c b/src/wayland/meta-wayland-touch.c +index 002ff16f7..15f0312eb 100644 +--- a/src/wayland/meta-wayland-touch.c ++++ b/src/wayland/meta-wayland-touch.c +@@ -521,16 +521,8 @@ meta_wayland_touch_create_new_resource (MetaWaylandTouch *touch, + struct wl_resource *seat_resource, + uint32_t id) + { +- MetaWaylandSeat *seat = wl_resource_get_user_data (seat_resource); + struct wl_resource *cr; + +- if (!meta_wayland_seat_has_touch (seat)) +- { +- wl_resource_post_error (seat_resource, WL_DISPLAY_ERROR_INVALID_METHOD, +- "Cannot retrieve touch interface without touch capability"); +- return; +- } +- + cr = wl_resource_create (client, &wl_touch_interface, wl_resource_get_version (seat_resource), id); + wl_resource_set_implementation (cr, &touch_interface, touch, unbind_resource); + wl_list_insert (&touch->resource_list, wl_resource_get_link (cr)); +-- +2.31.1 + diff --git a/SOURCES/0001-window-actor-Special-case-shaped-Java-windows.patch b/SOURCES/0001-window-actor-Special-case-shaped-Java-windows.patch new file mode 100644 index 0000000..36d50b6 --- /dev/null +++ b/SOURCES/0001-window-actor-Special-case-shaped-Java-windows.patch @@ -0,0 +1,35 @@ +From 9efcc35102b4c41265e93461b35a1193b3d5822d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 12 May 2017 13:40:31 +0200 +Subject: [PATCH] window-actor: Special-case shaped Java windows + +OpenJDK wrongly assumes that shaping a window implies no shadows. +They got lucky until commit b975676c changed the fallback case, +but now their compliance tests are broken. Make them happy again +by special-casing shaped Java windows. +--- + src/compositor/meta-window-actor-x11.c | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/src/compositor/meta-window-actor-x11.c b/src/compositor/meta-window-actor-x11.c +index b7032e0ba..b05d5e158 100644 +--- a/src/compositor/meta-window-actor-x11.c ++++ b/src/compositor/meta-window-actor-x11.c +@@ -528,6 +528,14 @@ has_shadow (MetaWindowActorX11 *actor_x11) + */ + if (window->has_custom_frame_extents) + return FALSE; ++ ++ /* ++ * OpenJDK wrongly assumes that shaping a window implies no compositor ++ * shadows; make its compliance tests happy to give it what it wants ... ++ */ ++ if (g_strcmp0 (window->res_name, "sun-awt-X11-XWindowPeer") == 0 && ++ window->shape_region != NULL) ++ return FALSE; + + /* + * Generate shadows for all other windows. +-- +2.23.0 + diff --git a/SOURCES/eglstream-overview-fixes.patch b/SOURCES/eglstream-overview-fixes.patch new file mode 100644 index 0000000..55b06be --- /dev/null +++ b/SOURCES/eglstream-overview-fixes.patch @@ -0,0 +1,324 @@ +From d3d8ab8eabc3178f3c31ee71dcc926297ff1c1b0 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Tue, 8 Feb 2022 17:14:06 +0100 +Subject: [PATCH 1/2] shaped-texture: Pass along the snippet to the texture + tower + +The snippet is used make sure the right source is sampled in the shader. +This wasn't done in the texture tower, meaning the textures from the +tower were not correct. + +Related: https://gitlab.gnome.org/GNOME/mutter/-/issues/528 +--- + src/compositor/meta-shaped-texture.c | 2 ++ + src/compositor/meta-texture-tower.c | 27 +++++++++++++++++++++++++++ + src/compositor/meta-texture-tower.h | 3 +++ + 3 files changed, 32 insertions(+) + +diff --git a/src/compositor/meta-shaped-texture.c b/src/compositor/meta-shaped-texture.c +index 095dd246f0c0..68919c5f1c5c 100644 +--- a/src/compositor/meta-shaped-texture.c ++++ b/src/compositor/meta-shaped-texture.c +@@ -1242,6 +1242,8 @@ meta_shaped_texture_set_snippet (MetaShapedTexture *stex, + g_clear_pointer (&stex->snippet, cogl_object_unref); + if (snippet) + stex->snippet = cogl_object_ref (snippet); ++ ++ meta_texture_tower_set_snippet (stex->paint_tower, snippet); + } + + /** +diff --git a/src/compositor/meta-texture-tower.c b/src/compositor/meta-texture-tower.c +index 1fc4623e5e54..0ae717abe4d4 100644 +--- a/src/compositor/meta-texture-tower.c ++++ b/src/compositor/meta-texture-tower.c +@@ -62,6 +62,7 @@ struct _MetaTextureTower + CoglOffscreen *fbos[MAX_TEXTURE_LEVELS]; + Box invalid[MAX_TEXTURE_LEVELS]; + CoglPipeline *pipeline_template; ++ CoglSnippet *snippet; + }; + + /** +@@ -97,6 +98,7 @@ meta_texture_tower_free (MetaTextureTower *tower) + cogl_object_unref (tower->pipeline_template); + + meta_texture_tower_set_base_texture (tower, NULL); ++ cogl_clear_object (&tower->snippet); + + g_free (tower); + } +@@ -216,6 +218,28 @@ meta_texture_tower_update_area (MetaTextureTower *tower, + } + } + ++void ++meta_texture_tower_set_snippet (MetaTextureTower *tower, ++ CoglSnippet *snippet) ++{ ++ int i; ++ ++ if (tower->snippet == snippet) ++ return; ++ ++ g_clear_pointer (&tower->snippet, cogl_object_unref); ++ ++ if (snippet) ++ tower->snippet = cogl_object_ref (snippet); ++ ++ for (i = 1; i < tower->n_levels; i++) ++ { ++ cogl_clear_object (&tower->textures[i]); ++ g_clear_object (&tower->fbos[i]); ++ } ++ cogl_clear_object (&tower->pipeline_template); ++} ++ + /* It generally looks worse if we scale up a window texture by even a + * small amount than if we scale it down using bilinear filtering, so + * we always pick the *larger* adjacent level. */ +@@ -408,6 +432,9 @@ texture_tower_revalidate (MetaTextureTower *tower, + pipeline = cogl_pipeline_copy (tower->pipeline_template); + cogl_pipeline_set_layer_texture (pipeline, 0, tower->textures[level - 1]); + ++ if (tower->snippet && level == 1) ++ cogl_pipeline_add_layer_snippet (pipeline, 0, tower->snippet); ++ + cogl_framebuffer_draw_textured_rectangle (fb, pipeline, + invalid->x1, invalid->y1, + invalid->x2, invalid->y2, +diff --git a/src/compositor/meta-texture-tower.h b/src/compositor/meta-texture-tower.h +index 1f5b371467c9..5522dfbb16ac 100644 +--- a/src/compositor/meta-texture-tower.h ++++ b/src/compositor/meta-texture-tower.h +@@ -63,6 +63,9 @@ void meta_texture_tower_update_area (MetaTextureTower *tower, + CoglTexture *meta_texture_tower_get_paint_texture (MetaTextureTower *tower, + ClutterPaintContext *paint_context); + ++void meta_texture_tower_set_snippet (MetaTextureTower *tower, ++ CoglSnippet *snippet); ++ + G_END_DECLS + + #endif /* __META_TEXTURE_TOWER_H__ */ +-- +2.34.1 + + +From b78a24dfebf56b04538058ff731890ee997d0d10 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Wed, 9 Feb 2022 12:41:14 +0100 +Subject: [PATCH 2/2] shaped-texture: Paint with the right layer snippet + +When we get passed a "snippet" to the shaped texture, it's added as a +pipeline layer snippet to change how the source texture is sampled. When +we draw from a texture tower however we have allocated regular textures +which doesn't need any special layer snippet, so create separate +pipelines for those that doesn't use that snippet. + +Closes: https://gitlab.gnome.org/GNOME/mutter/-/issues/528 +--- + src/compositor/meta-shaped-texture.c | 126 +++++++++++++++++++++------ + 1 file changed, 98 insertions(+), 28 deletions(-) + +diff --git a/src/compositor/meta-shaped-texture.c b/src/compositor/meta-shaped-texture.c +index 68919c5f1c5c..0d09f2f4e164 100644 +--- a/src/compositor/meta-shaped-texture.c ++++ b/src/compositor/meta-shaped-texture.c +@@ -91,8 +91,12 @@ struct _MetaShapedTexture + CoglSnippet *snippet; + + CoglPipeline *base_pipeline; ++ CoglPipeline *unmasked_pipeline; ++ CoglPipeline *unmasked_tower_pipeline; + CoglPipeline *masked_pipeline; ++ CoglPipeline *masked_tower_pipeline; + CoglPipeline *unblended_pipeline; ++ CoglPipeline *unblended_tower_pipeline; + + gboolean is_y_inverted; + +@@ -243,8 +247,12 @@ static void + meta_shaped_texture_reset_pipelines (MetaShapedTexture *stex) + { + g_clear_pointer (&stex->base_pipeline, cogl_object_unref); ++ g_clear_pointer (&stex->unmasked_pipeline, cogl_object_unref); ++ g_clear_pointer (&stex->unmasked_tower_pipeline, cogl_object_unref); + g_clear_pointer (&stex->masked_pipeline, cogl_object_unref); ++ g_clear_pointer (&stex->masked_tower_pipeline, cogl_object_unref); + g_clear_pointer (&stex->unblended_pipeline, cogl_object_unref); ++ g_clear_pointer (&stex->unblended_tower_pipeline, cogl_object_unref); + } + + static void +@@ -381,9 +389,6 @@ get_base_pipeline (MetaShapedTexture *stex, + + cogl_pipeline_set_layer_matrix (pipeline, 0, &matrix); + +- if (stex->snippet) +- cogl_pipeline_add_layer_snippet (pipeline, 0, stex->snippet); +- + stex->base_pipeline = pipeline; + + return stex->base_pipeline; +@@ -391,47 +396,112 @@ get_base_pipeline (MetaShapedTexture *stex, + + static CoglPipeline * + get_unmasked_pipeline (MetaShapedTexture *stex, +- CoglContext *ctx) ++ CoglContext *ctx, ++ CoglTexture *tex) + { +- return get_base_pipeline (stex, ctx); ++ if (stex->texture == tex) ++ { ++ CoglPipeline *pipeline; ++ ++ if (stex->unmasked_pipeline) ++ return stex->unmasked_pipeline; ++ ++ pipeline = cogl_pipeline_copy (get_base_pipeline (stex, ctx)); ++ if (stex->snippet) ++ cogl_pipeline_add_layer_snippet (pipeline, 0, stex->snippet); ++ ++ stex->unmasked_pipeline = pipeline; ++ return pipeline; ++ } ++ else ++ { ++ CoglPipeline *pipeline; ++ ++ if (stex->unmasked_tower_pipeline) ++ return stex->unmasked_tower_pipeline; ++ ++ pipeline = cogl_pipeline_copy (get_base_pipeline (stex, ctx)); ++ stex->unmasked_tower_pipeline = pipeline; ++ return pipeline; ++ } + } + + static CoglPipeline * + get_masked_pipeline (MetaShapedTexture *stex, +- CoglContext *ctx) ++ CoglContext *ctx, ++ CoglTexture *tex) + { +- CoglPipeline *pipeline; ++ if (stex->texture == tex) ++ { ++ CoglPipeline *pipeline; + +- if (stex->masked_pipeline) +- return stex->masked_pipeline; ++ if (stex->masked_pipeline) ++ return stex->masked_pipeline; + +- pipeline = cogl_pipeline_copy (get_base_pipeline (stex, ctx)); +- cogl_pipeline_set_layer_combine (pipeline, 1, +- "RGBA = MODULATE (PREVIOUS, TEXTURE[A])", +- NULL); ++ pipeline = cogl_pipeline_copy (get_base_pipeline (stex, ctx)); ++ cogl_pipeline_set_layer_combine (pipeline, 1, ++ "RGBA = MODULATE (PREVIOUS, TEXTURE[A])", ++ NULL); ++ if (stex->snippet) ++ cogl_pipeline_add_layer_snippet (pipeline, 0, stex->snippet); + +- stex->masked_pipeline = pipeline; ++ stex->masked_pipeline = pipeline; ++ return pipeline; ++ } ++ else ++ { ++ CoglPipeline *pipeline; + +- return pipeline; ++ if (stex->masked_tower_pipeline) ++ return stex->masked_tower_pipeline; ++ ++ pipeline = cogl_pipeline_copy (get_base_pipeline (stex, ctx)); ++ cogl_pipeline_set_layer_combine (pipeline, 1, ++ "RGBA = MODULATE (PREVIOUS, TEXTURE[A])", ++ NULL); ++ ++ stex->masked_tower_pipeline = pipeline; ++ return pipeline; ++ } + } + + static CoglPipeline * + get_unblended_pipeline (MetaShapedTexture *stex, +- CoglContext *ctx) ++ CoglContext *ctx, ++ CoglTexture *tex) + { +- CoglPipeline *pipeline; ++ if (stex->texture == tex) ++ { ++ CoglPipeline *pipeline; + +- if (stex->unblended_pipeline) +- return stex->unblended_pipeline; ++ if (stex->unblended_pipeline) ++ return stex->unblended_pipeline; + +- pipeline = cogl_pipeline_copy (get_base_pipeline (stex, ctx)); +- cogl_pipeline_set_layer_combine (pipeline, 0, +- "RGBA = REPLACE (TEXTURE)", +- NULL); ++ pipeline = cogl_pipeline_copy (get_base_pipeline (stex, ctx)); ++ cogl_pipeline_set_layer_combine (pipeline, 0, ++ "RGBA = REPLACE (TEXTURE)", ++ NULL); ++ if (stex->snippet) ++ cogl_pipeline_add_layer_snippet (pipeline, 0, stex->snippet); + +- stex->unblended_pipeline = pipeline; ++ stex->unblended_pipeline = pipeline; ++ return pipeline; ++ } ++ else ++ { ++ CoglPipeline *pipeline; + +- return pipeline; ++ if (stex->unblended_tower_pipeline) ++ return stex->unblended_tower_pipeline; ++ ++ pipeline = cogl_pipeline_copy (get_base_pipeline (stex, ctx)); ++ cogl_pipeline_set_layer_combine (pipeline, 0, ++ "RGBA = REPLACE (TEXTURE)", ++ NULL); ++ ++ stex->unblended_tower_pipeline = pipeline; ++ return pipeline; ++ } + } + + static CoglPipeline * +@@ -742,7 +812,7 @@ do_paint_content (MetaShapedTexture *stex, + { + CoglPipeline *opaque_pipeline; + +- opaque_pipeline = get_unblended_pipeline (stex, ctx); ++ opaque_pipeline = get_unblended_pipeline (stex, ctx, paint_tex); + cogl_pipeline_set_layer_texture (opaque_pipeline, 0, paint_tex); + cogl_pipeline_set_layer_filters (opaque_pipeline, 0, filter, filter); + +@@ -786,11 +856,11 @@ do_paint_content (MetaShapedTexture *stex, + + if (stex->mask_texture == NULL) + { +- blended_pipeline = get_unmasked_pipeline (stex, ctx); ++ blended_pipeline = get_unmasked_pipeline (stex, ctx, paint_tex); + } + else + { +- blended_pipeline = get_masked_pipeline (stex, ctx); ++ blended_pipeline = get_masked_pipeline (stex, ctx, paint_tex); + cogl_pipeline_set_layer_texture (blended_pipeline, 1, stex->mask_texture); + cogl_pipeline_set_layer_filters (blended_pipeline, 1, filter, filter); + } +-- +2.34.1 + diff --git a/SOURCES/glx-stereo-support.patch b/SOURCES/glx-stereo-support.patch new file mode 100644 index 0000000..8083db4 --- /dev/null +++ b/SOURCES/glx-stereo-support.patch @@ -0,0 +1,1242 @@ +From 768818c8d071f066a2ab68f83381829838b5bf29 Mon Sep 17 00:00:00 2001 +From: "Owen W. Taylor" +Date: Thu, 8 May 2014 18:44:15 -0400 +Subject: [PATCH 1/2] Add support for quad-buffer stereo + +Track the stereo status of windows using the new EXT_stereo_tree +GLX extension. + +When stereo is enabled or disabled, a restart is triggered via +meta_restart() after a timeout, setting a _META_ENABLE_STEREO +property on the root window to indicate whether we should +turn on a stereo stage for clutter. The property avoids a loop, +since we need to enable stereo *before* initializing Clutter and GL, +but we need GL to figure out whether we have stereo windows. + +Stereo windows are drawn to the stage using new functionality +in Cogl to setup a stereo context, select which buffer to draw +to, and draw either the left or right buffer of a stereo +texture_from_pixmap. +--- + clutter/clutter/clutter-paint-nodes.c | 103 +++++++++++ + clutter/clutter/clutter-paint-nodes.h | 13 ++ + src/compositor/compositor.c | 8 + + src/compositor/meta-compositor-x11.c | 127 +++++++++++++ + src/compositor/meta-compositor-x11.h | 6 + + src/compositor/meta-shaped-texture-private.h | 5 +- + src/compositor/meta-shaped-texture.c | 176 +++++++++++++++---- + src/compositor/meta-surface-actor-wayland.c | 2 +- + src/compositor/meta-surface-actor-x11.c | 55 +++++- + src/compositor/meta-surface-actor-x11.h | 5 + + src/compositor/meta-window-actor-private.h | 5 + + src/compositor/meta-window-actor.c | 22 +++ + src/core/main.c | 4 + + src/core/stereo.c | 154 ++++++++++++++++ + src/core/stereo.h | 28 +++ + src/meson.build | 2 + + src/wayland/meta-wayland-actor-surface.c | 4 +- + 17 files changed, 667 insertions(+), 52 deletions(-) + create mode 100644 src/core/stereo.c + create mode 100644 src/core/stereo.h + +diff --git a/clutter/clutter/clutter-paint-nodes.c b/clutter/clutter/clutter-paint-nodes.c +index f1f7fce318..29a673e9c7 100644 +--- a/clutter/clutter/clutter-paint-nodes.c ++++ b/clutter/clutter/clutter-paint-nodes.c +@@ -1970,3 +1970,106 @@ clutter_blur_node_new (unsigned int width, + out: + return (ClutterPaintNode *) blur_node; + } ++ ++/* ++ * ClutterStereoNode ++ */ ++ ++struct _ClutterStereoNode ++{ ++ ClutterPaintNode parent_instance; ++ ++ CoglStereoMode stereo_mode; ++}; ++ ++struct _ClutterStereoNodeClass ++{ ++ ClutterPaintNodeClass parent_class; ++}; ++ ++G_DEFINE_TYPE (ClutterStereoNode, clutter_stereo_node, CLUTTER_TYPE_PAINT_NODE) ++ ++static gboolean ++clutter_stereo_node_pre_draw (ClutterPaintNode *node, ++ ClutterPaintContext *paint_context) ++{ ++ ClutterStereoNode *stereo_node = CLUTTER_STEREO_NODE (node); ++ CoglFramebuffer *fb = ++ clutter_paint_context_get_framebuffer (paint_context); ++ ++ g_warn_if_fail (cogl_framebuffer_get_is_stereo (fb)); ++ ++ cogl_framebuffer_set_stereo_mode (fb, stereo_node->stereo_mode); ++ ++ return TRUE; ++} ++ ++static void ++clutter_stereo_node_post_draw (ClutterPaintNode *node, ++ ClutterPaintContext *paint_context) ++{ ++ CoglFramebuffer *fb = ++ clutter_paint_context_get_framebuffer (paint_context); ++ ++ cogl_framebuffer_set_stereo_mode (fb, COGL_STEREO_BOTH); ++} ++ ++static const char * ++stereo_mode_to_string (CoglStereoMode stereo_mode) ++{ ++ switch (stereo_mode) ++ { ++ case COGL_STEREO_BOTH: ++ return "both"; ++ case COGL_STEREO_LEFT: ++ return "left"; ++ case COGL_STEREO_RIGHT: ++ return "right"; ++ } ++ ++ g_assert_not_reached (); ++} ++ ++static JsonNode * ++clutter_stereo_node_serialize (ClutterPaintNode *node) ++{ ++ ClutterStereoNode *stereo_node = CLUTTER_STEREO_NODE (node); ++ g_autoptr (JsonBuilder) builder = NULL; ++ const char *stereo_mode_str; ++ ++ builder = json_builder_new (); ++ json_builder_begin_object (builder); ++ json_builder_set_member_name (builder, "stereo-mode"); ++ stereo_mode_str = stereo_mode_to_string (stereo_node->stereo_mode); ++ json_builder_add_string_value (builder, stereo_mode_str); ++ json_builder_end_object (builder); ++ ++ return json_builder_get_root (builder); ++} ++ ++static void ++clutter_stereo_node_class_init (ClutterStereoNodeClass *klass) ++{ ++ ClutterPaintNodeClass *node_class; ++ ++ node_class = CLUTTER_PAINT_NODE_CLASS (klass); ++ node_class->pre_draw = clutter_stereo_node_pre_draw; ++ node_class->post_draw = clutter_stereo_node_post_draw; ++ node_class->serialize = clutter_stereo_node_serialize; ++} ++ ++static void ++clutter_stereo_node_init (ClutterStereoNode *stereo_node) ++{ ++} ++ ++ClutterPaintNode * ++clutter_stereo_node_new (CoglStereoMode stereo_mode) ++{ ++ ClutterStereoNode *stereo_node; ++ ++ stereo_node = _clutter_paint_node_create (CLUTTER_TYPE_STEREO_NODE); ++ stereo_node->stereo_mode = stereo_mode; ++ ++ return CLUTTER_PAINT_NODE (stereo_node); ++} +diff --git a/clutter/clutter/clutter-paint-nodes.h b/clutter/clutter/clutter-paint-nodes.h +index 7f0d12857a..77d1ab05b6 100644 +--- a/clutter/clutter/clutter-paint-nodes.h ++++ b/clutter/clutter/clutter-paint-nodes.h +@@ -284,6 +284,19 @@ ClutterPaintNode * clutter_blur_node_new (unsigned int width, + unsigned int height, + float sigma); + ++#define CLUTTER_TYPE_STEREO_NODE (clutter_stereo_node_get_type ()) ++#define CLUTTER_STEREO_NODE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CLUTTER_TYPE_STEREO_NODE, ClutterStereoNode)) ++#define CLUTTER_IS_STEREO_NODE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CLUTTER_TYPE_STEREO_NODE)) ++ ++typedef struct _ClutterStereoNode ClutterStereoNode; ++typedef struct _ClutterStereoNodeClass ClutterStereoNodeClass; ++ ++CLUTTER_EXPORT ++GType clutter_stereo_node_get_type (void) G_GNUC_CONST; ++ ++CLUTTER_EXPORT ++ClutterPaintNode * clutter_stereo_node_new (CoglStereoMode stereo_mode); ++ + G_END_DECLS + + #endif /* __CLUTTER_PAINT_NODES_H__ */ +diff --git a/src/compositor/compositor.c b/src/compositor/compositor.c +index 1770550d4c..a4bd1252ae 100644 +--- a/src/compositor/compositor.c ++++ b/src/compositor/compositor.c +@@ -66,6 +66,7 @@ + #include "compositor/meta-window-actor-private.h" + #include "compositor/meta-window-group-private.h" + #include "core/frame.h" ++#include "core/stereo.h" + #include "core/util-private.h" + #include "core/window-private.h" + #include "meta/compositor-mutter.h" +@@ -910,6 +911,7 @@ meta_compositor_sync_stack (MetaCompositor *compositor, + meta_compositor_get_instance_private (compositor); + MetaWindowActor *top_window_actor; + GList *old_stack; ++ int stereo_window_count = 0; + + /* This is painful because hidden windows that we are in the process + * of animating out of existence. They'll be at the bottom of the +@@ -986,12 +988,18 @@ meta_compositor_sync_stack (MetaCompositor *compositor, + */ + priv->windows = g_list_prepend (priv->windows, actor); + ++ if (meta_window_actor_is_stereo (actor)) ++ stereo_window_count++; ++ + stack = g_list_remove (stack, window); + old_stack = g_list_remove (old_stack, actor); + } + + sync_actor_stacking (compositor); + ++ if (!meta_is_wayland_compositor ()) ++ meta_stereo_set_have_stereo_windows (stereo_window_count > 0); ++ + top_window_actor = get_top_visible_window_actor (compositor); + + if (priv->top_window_actor == top_window_actor) +diff --git a/src/compositor/meta-compositor-x11.c b/src/compositor/meta-compositor-x11.c +index 1d0ba4c8d8..afbe3f57e2 100644 +--- a/src/compositor/meta-compositor-x11.c ++++ b/src/compositor/meta-compositor-x11.c +@@ -31,6 +31,8 @@ + #include "compositor/meta-sync-ring.h" + #include "compositor/meta-window-actor-x11.h" + #include "core/display-private.h" ++#include "core/stack-tracker.h" ++#include "core/stereo.h" + #include "x11/meta-x11-display-private.h" + + struct _MetaCompositorX11 +@@ -50,8 +52,24 @@ struct _MetaCompositorX11 + gboolean xserver_uses_monotonic_clock; + int64_t xserver_time_query_time_us; + int64_t xserver_time_offset_us; ++ ++ int glx_opcode; ++ gboolean stereo_tree_ext; ++ gboolean have_stereo_windows; + }; + ++typedef struct ++{ ++ int type; ++ unsigned long serial; ++ Bool send_event; ++ Display *display; ++ int extension; ++ int evtype; ++ Drawable window; ++ Bool stereo_tree; ++} StereoNotifyEvent; ++ + G_DEFINE_TYPE (MetaCompositorX11, meta_compositor_x11, META_TYPE_COMPOSITOR) + + static void +@@ -95,6 +113,27 @@ meta_compositor_x11_process_xevent (MetaCompositorX11 *compositor_x11, + if (window) + process_damage (compositor_x11, (XDamageNotifyEvent *) xevent, window); + } ++ else if (xevent->type == GenericEvent && ++ xevent->xcookie.extension == compositor_x11->glx_opcode) ++ { ++ if (xevent->xcookie.evtype == GLX_STEREO_NOTIFY_EXT) ++ { ++ StereoNotifyEvent *stereo_event = ++ (StereoNotifyEvent *) (xevent->xcookie.data); ++ ++ window = meta_x11_display_lookup_x_window (x11_display, ++ stereo_event->window); ++ if (window) ++ { ++ MetaWindowActor *window_actor = meta_window_actor_from_window (window); ++ MetaDisplay *display = meta_window_get_display (window); ++ ++ meta_window_actor_stereo_notify (window_actor, ++ stereo_event->stereo_tree); ++ meta_stack_tracker_queue_sync_stack (display->stack_tracker); ++ } ++ } ++ } + + if (compositor_x11->have_x11_sync_object) + meta_sync_ring_handle_event (xevent); +@@ -107,6 +146,85 @@ meta_compositor_x11_process_xevent (MetaCompositorX11 *compositor_x11, + meta_x11_handle_event (xevent); + } + ++#define GLX_STEREO_TREE_EXT 0x20F5 ++#define GLX_STEREO_NOTIFY_MASK_EXT 0x00000001 ++#define GLX_STEREO_NOTIFY_EXT 0x00000000 ++ ++static gboolean ++display_has_stereo_tree_ext (MetaX11Display *x11_display) ++{ ++ Display *xdisplay = x11_display->xdisplay; ++ const char *extensions_string; ++ ++ static const char * (*query_extensions_string) (Display *display, ++ int screen); ++ ++ if (query_extensions_string == NULL) ++ query_extensions_string = ++ (const char * (*) (Display *, int)) ++ cogl_get_proc_address ("glXQueryExtensionsString"); ++ ++ extensions_string = query_extensions_string (xdisplay, ++ meta_x11_display_get_screen_number (x11_display)); ++ ++ return extensions_string && strstr (extensions_string, "EXT_stereo_tree") != 0; ++} ++ ++#include ++ ++gboolean ++meta_compositor_x11_window_is_stereo (MetaCompositorX11 *compositor_x11, ++ Window xwindow) ++{ ++ MetaCompositor *compositor = META_COMPOSITOR (compositor_x11); ++ MetaDisplay *display = meta_compositor_get_display (compositor); ++ Display *xdisplay = meta_x11_display_get_xdisplay (display->x11_display); ++ ++ static int (*query_drawable) (Display *dpy, ++ Drawable draw, ++ int attribute, ++ unsigned int *value); ++ ++ if (compositor_x11->stereo_tree_ext) ++ { ++ unsigned int stereo_tree = 0; ++ ++ if (query_drawable == NULL) ++ query_drawable = ++ (int (*) (Display *, Drawable, int, unsigned int *)) ++ cogl_get_proc_address ("glXQueryDrawable"); ++ ++ query_drawable (xdisplay, xwindow, GLX_STEREO_TREE_EXT, &stereo_tree); ++ ++ return stereo_tree != 0; ++ } ++ else ++ return FALSE; ++} ++ ++void ++meta_compositor_x11_select_stereo_notify (MetaCompositorX11 *compositor_x11, ++ Window xwindow) ++{ ++ MetaCompositor *compositor = META_COMPOSITOR (compositor_x11); ++ MetaDisplay *display = meta_compositor_get_display (compositor); ++ Display *xdisplay = meta_x11_display_get_xdisplay (display->x11_display); ++ ++ static void (*select_event) (Display *dpy, ++ Drawable draw, ++ unsigned long event_mask); ++ ++ if (compositor_x11->stereo_tree_ext) ++ { ++ if (select_event == NULL) ++ select_event = ++ (void (*) (Display *, Drawable, unsigned long)) ++ cogl_get_proc_address ("glXSelectEvent"); ++ ++ select_event (xdisplay, xwindow, GLX_STEREO_NOTIFY_MASK_EXT); ++ } ++} ++ + static void + determine_server_clock_source (MetaCompositorX11 *compositor_x11) + { +@@ -142,6 +260,7 @@ meta_compositor_x11_manage (MetaCompositor *compositor, + MetaX11Display *x11_display = display->x11_display; + Display *xdisplay = meta_x11_display_get_xdisplay (x11_display); + int composite_version; ++ int glx_major_opcode, glx_first_event, glx_first_error; + MetaBackend *backend = meta_get_backend (); + Window xwindow; + +@@ -166,10 +285,18 @@ meta_compositor_x11_manage (MetaCompositor *compositor, + return FALSE; + } + ++ if (XQueryExtension (xdisplay, ++ "GLX", ++ &glx_major_opcode, &glx_first_event, &glx_first_error)) ++ compositor_x11->glx_opcode = glx_major_opcode; ++ + determine_server_clock_source (compositor_x11); + + meta_x11_display_set_cm_selection (display->x11_display); + ++ compositor_x11->stereo_tree_ext = ++ display_has_stereo_tree_ext (display->x11_display); ++ + compositor_x11->output = display->x11_display->composite_overlay_window; + + xwindow = meta_backend_x11_get_xwindow (META_BACKEND_X11 (backend)); +diff --git a/src/compositor/meta-compositor-x11.h b/src/compositor/meta-compositor-x11.h +index 42554feb39..61f3cd5950 100644 +--- a/src/compositor/meta-compositor-x11.h ++++ b/src/compositor/meta-compositor-x11.h +@@ -36,4 +36,10 @@ void meta_compositor_x11_process_xevent (MetaCompositorX11 *compositor_x11, + + Window meta_compositor_x11_get_output_xwindow (MetaCompositorX11 *compositor_x11); + ++gboolean meta_compositor_x11_window_is_stereo (MetaCompositorX11 *compositor_x11, ++ Window xwindow); ++ ++void meta_compositor_x11_select_stereo_notify (MetaCompositorX11 *compositor_x11, ++ Window xwindow); ++ + #endif /* META_COMPOSITOR_X11_H */ +diff --git a/src/compositor/meta-shaped-texture-private.h b/src/compositor/meta-shaped-texture-private.h +index 2fe1b8ea48..fadad07d69 100644 +--- a/src/compositor/meta-shaped-texture-private.h ++++ b/src/compositor/meta-shaped-texture-private.h +@@ -31,8 +31,9 @@ + #include "meta/meta-shaped-texture.h" + + MetaShapedTexture *meta_shaped_texture_new (void); +-void meta_shaped_texture_set_texture (MetaShapedTexture *stex, +- CoglTexture *texture); ++void meta_shaped_texture_set_textures (MetaShapedTexture *stex, ++ CoglTexture *texture, ++ CoglTexture *texture_right); + void meta_shaped_texture_set_is_y_inverted (MetaShapedTexture *stex, + gboolean is_y_inverted); + void meta_shaped_texture_set_snippet (MetaShapedTexture *stex, +diff --git a/src/compositor/meta-shaped-texture.c b/src/compositor/meta-shaped-texture.c +index 6a8af828f0..43170a195d 100644 +--- a/src/compositor/meta-shaped-texture.c ++++ b/src/compositor/meta-shaped-texture.c +@@ -83,8 +83,10 @@ struct _MetaShapedTexture + GObject parent; + + MetaTextureTower *paint_tower; ++ MetaTextureTower *paint_tower_right; + + CoglTexture *texture; ++ CoglTexture *texture_right; + CoglTexture *mask_texture; + CoglSnippet *snippet; + +@@ -151,6 +153,7 @@ static void + meta_shaped_texture_init (MetaShapedTexture *stex) + { + stex->paint_tower = meta_texture_tower_new (); ++ stex->paint_tower_right = NULL; + + stex->buffer_scale = 1; + stex->texture = NULL; +@@ -251,11 +254,11 @@ meta_shaped_texture_dispose (GObject *object) + + g_clear_handle_id (&stex->remipmap_timeout_id, g_source_remove); + +- if (stex->paint_tower) +- meta_texture_tower_free (stex->paint_tower); +- stex->paint_tower = NULL; ++ g_clear_pointer (&stex->paint_tower, meta_texture_tower_free); ++ g_clear_pointer (&stex->paint_tower_right, meta_texture_tower_free); + + g_clear_pointer (&stex->texture, cogl_object_unref); ++ g_clear_pointer (&stex->texture_right, cogl_object_unref); + + meta_shaped_texture_set_mask_texture (stex, NULL); + meta_shaped_texture_reset_pipelines (stex); +@@ -521,14 +524,19 @@ paint_clipped_rectangle_node (MetaShapedTexture *stex, + } + + static void +-set_cogl_texture (MetaShapedTexture *stex, +- CoglTexture *cogl_tex) ++set_cogl_textures (MetaShapedTexture *stex, ++ CoglTexture *cogl_tex, ++ CoglTexture *cogl_tex_right) + { + int width, height; + + cogl_clear_object (&stex->texture); ++ cogl_clear_object (&stex->texture_right); + +- if (cogl_tex != NULL) ++ stex->texture = cogl_tex; ++ stex->texture_right = cogl_tex_right; ++ ++ if (cogl_tex) + { + stex->texture = cogl_object_ref (cogl_tex); + width = cogl_texture_get_width (COGL_TEXTURE (cogl_tex)); +@@ -540,6 +548,9 @@ set_cogl_texture (MetaShapedTexture *stex, + height = 0; + } + ++ if (cogl_tex_right) ++ cogl_object_ref (cogl_tex_right); ++ + if (stex->tex_width != width || + stex->tex_height != height) + { +@@ -553,8 +564,23 @@ set_cogl_texture (MetaShapedTexture *stex, + * previous buffer. We only queue a redraw in response to surface + * damage. */ + ++ if (cogl_tex_right) ++ { ++ if (!stex->paint_tower_right) ++ stex->paint_tower_right = meta_texture_tower_new (); ++ } ++ else ++ { ++ g_clear_pointer (&stex->paint_tower_right, meta_texture_tower_free); ++ } ++ + if (stex->create_mipmaps) +- meta_texture_tower_set_base_texture (stex->paint_tower, cogl_tex); ++ { ++ meta_texture_tower_set_base_texture (stex->paint_tower, cogl_tex); ++ ++ if (stex->paint_tower_right) ++ meta_texture_tower_set_base_texture (stex->paint_tower_right, cogl_tex_right); ++ } + } + + static gboolean +@@ -582,6 +608,19 @@ flip_ints (int *x, + *y = tmp; + } + ++static CoglFramebuffer * ++get_target_framebuffer (ClutterPaintNode *root_node, ++ ClutterPaintContext *paint_context) ++{ ++ CoglFramebuffer *framebuffer; ++ ++ framebuffer = clutter_paint_node_get_framebuffer (root_node); ++ if (!framebuffer) ++ framebuffer = clutter_paint_context_get_framebuffer (paint_context); ++ ++ return framebuffer; ++} ++ + static void + do_paint_content (MetaShapedTexture *stex, + ClutterPaintNode *root_node, +@@ -622,9 +661,7 @@ do_paint_content (MetaShapedTexture *stex, + * improves performance, especially with software rendering. + */ + +- framebuffer = clutter_paint_node_get_framebuffer (root_node); +- if (!framebuffer) +- framebuffer = clutter_paint_context_get_framebuffer (paint_context); ++ framebuffer = get_target_framebuffer (root_node, paint_context); + + if (stex->has_viewport_src_rect) + { +@@ -826,13 +863,27 @@ do_paint_content (MetaShapedTexture *stex, + + static CoglTexture * + select_texture_for_paint (MetaShapedTexture *stex, +- ClutterPaintContext *paint_context) ++ ClutterPaintContext *paint_context, ++ CoglStereoMode stereo_mode) + { + CoglTexture *texture = NULL; + int64_t now; ++ gboolean use_right_texture = FALSE; + +- if (!stex->texture) +- return NULL; ++ switch (stereo_mode) ++ { ++ case COGL_STEREO_LEFT: ++ case COGL_STEREO_BOTH: ++ if (!stex->texture) ++ return NULL; ++ use_right_texture = FALSE; ++ break; ++ case COGL_STEREO_RIGHT: ++ if (!stex->texture_right) ++ return NULL; ++ use_right_texture = TRUE; ++ break; ++ } + + now = g_get_monotonic_time (); + +@@ -843,14 +894,24 @@ select_texture_for_paint (MetaShapedTexture *stex, + if (age >= MIN_MIPMAP_AGE_USEC || + stex->fast_updates < MIN_FAST_UPDATES_BEFORE_UNMIPMAP) + { +- texture = meta_texture_tower_get_paint_texture (stex->paint_tower, ++ MetaTextureTower *paint_tower; ++ ++ if (use_right_texture) ++ paint_tower = stex->paint_tower_right; ++ else ++ paint_tower = stex->paint_tower; ++ ++ texture = meta_texture_tower_get_paint_texture (paint_tower, + paint_context); + } + } + + if (!texture) + { +- texture = stex->texture; ++ if (use_right_texture) ++ texture = stex->texture_right; ++ else ++ texture = stex->texture; + + if (stex->create_mipmaps) + { +@@ -876,35 +937,57 @@ meta_shaped_texture_paint_content (ClutterContent *content, + { + MetaShapedTexture *stex = META_SHAPED_TEXTURE (content); + ClutterActorBox alloc; +- CoglTexture *paint_tex = NULL; + uint8_t opacity; ++ CoglFramebuffer *framebuffer; ++ gboolean is_stereo; + + if (stex->clip_region && cairo_region_is_empty (stex->clip_region)) + return; + +- /* The GL EXT_texture_from_pixmap extension does allow for it to be +- * used together with SGIS_generate_mipmap, however this is very +- * rarely supported. Also, even when it is supported there +- * are distinct performance implications from: +- * +- * - Updating mipmaps that we don't need +- * - Having to reallocate pixmaps on the server into larger buffers +- * +- * So, we just unconditionally use our mipmap emulation code. If we +- * wanted to use SGIS_generate_mipmap, we'd have to query COGL to +- * see if it was supported (no API currently), and then if and only +- * if that was the case, set the clutter texture quality to HIGH. +- * Setting the texture quality to high without SGIS_generate_mipmap +- * support for TFP textures will result in fallbacks to XGetImage. +- */ +- paint_tex = select_texture_for_paint (stex, paint_context); +- if (!paint_tex) ++ if (!stex->texture) + return; + + opacity = clutter_actor_get_paint_opacity (actor); + clutter_actor_get_content_box (actor, &alloc); + +- do_paint_content (stex, root_node, paint_context, paint_tex, &alloc, opacity); ++ framebuffer = get_target_framebuffer (root_node, paint_context); ++ is_stereo = (stex->texture_right && ++ cogl_framebuffer_get_is_stereo (framebuffer)); ++ ++ if (is_stereo) ++ { ++ CoglTexture *texture_left; ++ CoglTexture *texture_right; ++ g_autoptr (ClutterPaintNode) left_node = NULL; ++ g_autoptr (ClutterPaintNode) right_node = NULL; ++ ++ texture_left = select_texture_for_paint (stex, paint_context, ++ COGL_STEREO_LEFT); ++ texture_right = select_texture_for_paint (stex, paint_context, ++ COGL_STEREO_RIGHT); ++ ++ left_node = clutter_stereo_node_new (COGL_STEREO_LEFT); ++ clutter_paint_node_set_static_name (left_node, "MetaShapedTexture (left)"); ++ right_node = clutter_stereo_node_new (COGL_STEREO_RIGHT); ++ clutter_paint_node_set_static_name (right_node, "MetaShapedTexture (right)"); ++ ++ clutter_paint_node_add_child (root_node, left_node); ++ clutter_paint_node_add_child (root_node, right_node); ++ ++ do_paint_content (stex, left_node, paint_context, ++ texture_left, &alloc, opacity); ++ do_paint_content (stex, right_node, paint_context, ++ texture_right, &alloc, opacity); ++ } ++ else ++ { ++ CoglTexture *texture; ++ ++ texture = select_texture_for_paint (stex, paint_context, ++ COGL_STEREO_BOTH); ++ do_paint_content (stex, root_node, paint_context, ++ texture, &alloc, opacity); ++ } + } + + static gboolean +@@ -946,6 +1029,12 @@ meta_shaped_texture_set_create_mipmaps (MetaShapedTexture *stex, + stex->create_mipmaps = create_mipmaps; + base_texture = create_mipmaps ? stex->texture : NULL; + meta_texture_tower_set_base_texture (stex->paint_tower, base_texture); ++ ++ if (stex->paint_tower_right) ++ { ++ base_texture = create_mipmaps ? stex->texture_right : NULL; ++ meta_texture_tower_set_base_texture (stex->paint_tower_right, base_texture); ++ } + } + } + +@@ -1079,6 +1168,14 @@ meta_shaped_texture_update_area (MetaShapedTexture *stex, + y, + width, + height); ++ if (stex->paint_tower_right) ++ { ++ meta_texture_tower_update_area (stex->paint_tower_right, ++ x, ++ y, ++ width, ++ height); ++ } + + stex->prev_invalidation = stex->last_invalidation; + stex->last_invalidation = g_get_monotonic_time (); +@@ -1098,20 +1195,21 @@ meta_shaped_texture_update_area (MetaShapedTexture *stex, + } + + /** +- * meta_shaped_texture_set_texture: ++ * meta_shaped_texture_set_textures: + * @stex: The #MetaShapedTexture + * @pixmap: The #CoglTexture to display + */ + void +-meta_shaped_texture_set_texture (MetaShapedTexture *stex, +- CoglTexture *texture) ++meta_shaped_texture_set_textures (MetaShapedTexture *stex, ++ CoglTexture *texture, ++ CoglTexture *texture_right) + { + g_return_if_fail (META_IS_SHAPED_TEXTURE (stex)); + +- if (stex->texture == texture) ++ if (stex->texture == texture && stex->texture_right == texture_right) + return; + +- set_cogl_texture (stex, texture); ++ set_cogl_textures (stex, texture, texture_right); + } + + /** +diff --git a/src/compositor/meta-surface-actor-wayland.c b/src/compositor/meta-surface-actor-wayland.c +index a182ad8513..1ddc83db2b 100644 +--- a/src/compositor/meta-surface-actor-wayland.c ++++ b/src/compositor/meta-surface-actor-wayland.c +@@ -148,7 +148,7 @@ meta_surface_actor_wayland_dispose (GObject *object) + + stex = meta_surface_actor_get_texture (META_SURFACE_ACTOR (self)); + if (stex) +- meta_shaped_texture_set_texture (stex, NULL); ++ meta_shaped_texture_set_textures (stex, NULL, NULL); + + if (self->surface) + { +diff --git a/src/compositor/meta-surface-actor-x11.c b/src/compositor/meta-surface-actor-x11.c +index 41ae2dffbc..c7c3d08c36 100644 +--- a/src/compositor/meta-surface-actor-x11.c ++++ b/src/compositor/meta-surface-actor-x11.c +@@ -30,6 +30,7 @@ + #include + + #include "cogl/winsys/cogl-texture-pixmap-x11.h" ++#include "compositor/meta-compositor-x11.h" + #include "compositor/meta-cullable.h" + #include "compositor/meta-shaped-texture-private.h" + #include "compositor/meta-window-actor-private.h" +@@ -47,6 +48,7 @@ struct _MetaSurfaceActorX11 + MetaDisplay *display; + + CoglTexture *texture; ++ CoglTexture *texture_right; + Pixmap pixmap; + Damage damage; + +@@ -62,6 +64,8 @@ struct _MetaSurfaceActorX11 + guint size_changed : 1; + + guint unredirected : 1; ++ ++ guint stereo : 1; + }; + + G_DEFINE_TYPE (MetaSurfaceActorX11, +@@ -101,7 +105,7 @@ detach_pixmap (MetaSurfaceActorX11 *self) + * you are supposed to be able to free a GLXPixmap after freeing the underlying + * pixmap, but it certainly doesn't work with current DRI/Mesa + */ +- meta_shaped_texture_set_texture (stex, NULL); ++ meta_shaped_texture_set_textures (stex, NULL, NULL); + cogl_flush (); + + meta_x11_error_trap_push (display->x11_display); +@@ -110,6 +114,7 @@ detach_pixmap (MetaSurfaceActorX11 *self) + meta_x11_error_trap_pop (display->x11_display); + + g_clear_pointer (&self->texture, cogl_object_unref); ++ g_clear_pointer (&self->texture_right, cogl_object_unref); + } + + static void +@@ -119,23 +124,37 @@ set_pixmap (MetaSurfaceActorX11 *self, + CoglContext *ctx = clutter_backend_get_cogl_context (clutter_get_default_backend ()); + MetaShapedTexture *stex = meta_surface_actor_get_texture (META_SURFACE_ACTOR (self)); + GError *error = NULL; +- CoglTexture *texture; ++ CoglTexturePixmapX11 *texture; ++ CoglTexturePixmapX11 *texture_right; + + g_assert (self->pixmap == None); + self->pixmap = pixmap; + +- texture = COGL_TEXTURE (cogl_texture_pixmap_x11_new (ctx, self->pixmap, FALSE, &error)); ++ if (self->stereo) ++ texture = cogl_texture_pixmap_x11_new_left (ctx, pixmap, FALSE, &error); ++ else ++ texture = cogl_texture_pixmap_x11_new (ctx, pixmap, FALSE, &error); ++ ++ if (self->stereo) ++ texture_right = cogl_texture_pixmap_x11_new_right (texture); ++ else ++ texture_right = NULL; + + if (error != NULL) + { + g_warning ("Failed to allocate stex texture: %s", error->message); + g_error_free (error); + } +- else if (G_UNLIKELY (!cogl_texture_pixmap_x11_is_using_tfp_extension (COGL_TEXTURE_PIXMAP_X11 (texture)))) ++ else if (G_UNLIKELY (!cogl_texture_pixmap_x11_is_using_tfp_extension (texture))) + g_warning ("NOTE: Not using GLX TFP!"); + +- self->texture = texture; +- meta_shaped_texture_set_texture (stex, texture); ++ self->texture = COGL_TEXTURE (texture); ++ if (self->stereo) ++ self->texture_right = COGL_TEXTURE (texture_right); ++ ++ meta_shaped_texture_set_textures (stex, ++ COGL_TEXTURE (texture), ++ COGL_TEXTURE (texture_right));; + } + + static void +@@ -372,8 +391,8 @@ reset_texture (MetaSurfaceActorX11 *self) + /* Setting the texture to NULL will cause all the FBO's cached by the + * shaped texture's MetaTextureTower to be discarded and recreated. + */ +- meta_shaped_texture_set_texture (stex, NULL); +- meta_shaped_texture_set_texture (stex, self->texture); ++ meta_shaped_texture_set_textures (stex, NULL, NULL); ++ meta_shaped_texture_set_textures (stex, self->texture, self->texture_right); + } + + MetaSurfaceActor * +@@ -381,12 +400,18 @@ meta_surface_actor_x11_new (MetaWindow *window) + { + MetaSurfaceActorX11 *self = g_object_new (META_TYPE_SURFACE_ACTOR_X11, NULL); + MetaDisplay *display = meta_window_get_display (window); ++ MetaCompositorX11 *compositor_x11 = META_COMPOSITOR_X11 (display->compositor); ++ Window xwindow; + + g_assert (!meta_is_wayland_compositor ()); + + self->window = window; + self->display = display; + ++ xwindow = meta_window_x11_get_toplevel_xwindow (window); ++ self->stereo = meta_compositor_x11_window_is_stereo (compositor_x11, xwindow); ++ meta_compositor_x11_select_stereo_notify (compositor_x11, xwindow); ++ + g_signal_connect_object (self->display, "gl-video-memory-purged", + G_CALLBACK (reset_texture), self, G_CONNECT_SWAPPED); + +@@ -420,3 +445,17 @@ meta_surface_actor_x11_set_size (MetaSurfaceActorX11 *self, + self->last_height = height; + meta_shaped_texture_set_fallback_size (stex, width, height); + } ++ ++void ++meta_surface_actor_x11_stereo_notify (MetaSurfaceActorX11 *self, ++ gboolean stereo_tree) ++{ ++ self->stereo = stereo_tree != FALSE; ++ detach_pixmap (self); ++} ++ ++gboolean ++meta_surface_actor_x11_is_stereo (MetaSurfaceActorX11 *self) ++{ ++ return self->stereo; ++} +diff --git a/src/compositor/meta-surface-actor-x11.h b/src/compositor/meta-surface-actor-x11.h +index 0a8517236a..369f631ae0 100644 +--- a/src/compositor/meta-surface-actor-x11.h ++++ b/src/compositor/meta-surface-actor-x11.h +@@ -57,6 +57,11 @@ gboolean meta_surface_actor_x11_is_visible (MetaSurfaceActorX11 *self); + + void meta_surface_actor_x11_handle_updates (MetaSurfaceActorX11 *self); + ++void meta_surface_actor_x11_stereo_notify (MetaSurfaceActorX11 *self, ++ gboolean stereo_tree); ++ ++gboolean meta_surface_actor_x11_is_stereo (MetaSurfaceActorX11 *self); ++ + G_END_DECLS + + #endif /* __META_SURFACE_ACTOR_X11_H__ */ +diff --git a/src/compositor/meta-window-actor-private.h b/src/compositor/meta-window-actor-private.h +index 64741e4167..d498879902 100644 +--- a/src/compositor/meta-window-actor-private.h ++++ b/src/compositor/meta-window-actor-private.h +@@ -99,4 +99,9 @@ void meta_window_actor_update_regions (MetaWindowActor *self); + + gboolean meta_window_actor_can_freeze_commits (MetaWindowActor *self); + ++void meta_window_actor_stereo_notify (MetaWindowActor *actor, ++ gboolean stereo_tree); ++ ++gboolean meta_window_actor_is_stereo (MetaWindowActor *actor); ++ + #endif /* META_WINDOW_ACTOR_PRIVATE_H */ +diff --git a/src/compositor/meta-window-actor.c b/src/compositor/meta-window-actor.c +index d4fc9a43a0..56c85e1788 100644 +--- a/src/compositor/meta-window-actor.c ++++ b/src/compositor/meta-window-actor.c +@@ -1557,3 +1557,25 @@ out: + clutter_actor_uninhibit_culling (actor); + return surface; + } ++ ++void ++meta_window_actor_stereo_notify (MetaWindowActor *self, ++ gboolean stereo_tree) ++{ ++ MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); ++ ++ if (META_IS_SURFACE_ACTOR_X11 (priv->surface)) ++ meta_surface_actor_x11_stereo_notify (META_SURFACE_ACTOR_X11 (priv->surface), ++ stereo_tree); ++} ++ ++gboolean ++meta_window_actor_is_stereo (MetaWindowActor *self) ++{ ++ MetaWindowActorPrivate *priv = meta_window_actor_get_instance_private (self); ++ ++ if (META_IS_SURFACE_ACTOR_X11 (priv->surface)) ++ return meta_surface_actor_x11_is_stereo (META_SURFACE_ACTOR_X11 (priv->surface)); ++ else ++ return FALSE; ++} +diff --git a/src/core/main.c b/src/core/main.c +index 6dabcfe73e..a07dda9ecc 100644 +--- a/src/core/main.c ++++ b/src/core/main.c +@@ -88,6 +88,7 @@ + #include "meta/meta-backend.h" + #include "meta/meta-x11-errors.h" + #include "meta/prefs.h" ++#include "stereo.h" + #include "ui/ui.h" + #include "x11/session.h" + +@@ -848,6 +849,9 @@ meta_init (void) + if (!meta_is_wayland_compositor ()) + meta_select_display (opt_display_name); + ++ if (!meta_is_wayland_compositor ()) ++ meta_stereo_init (); ++ + meta_init_backend (backend_gtype, n_properties, prop_names, prop_values); + + for (i = 0; i < n_properties; i++) +diff --git a/src/core/stereo.c b/src/core/stereo.c +new file mode 100644 +index 0000000000..817056527f +--- /dev/null ++++ b/src/core/stereo.c +@@ -0,0 +1,154 @@ ++/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ ++ ++/* ++ * Copyright (C) 2014 Red Hat, Inc. ++ * ++ * 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:stereo ++ * @short_description: Keep track of whether we are a stereo compositor ++ * ++ * With GLX, we need to use a different GL context for stereo and ++ * non-stereo support. Support for multiple GL contexts is unfinished ++ * in Cogl and entirely lacking in Clutter, so it's by far easier ++ * to just restart Mutter when we detect a stereo window. ++ * ++ * A property _MUTTER_ENABLE_STEREO is maintained on the root window ++ * to know whether we should initialize clutter for stereo or not. ++ * When the presence or absence of stereo windows mismatches the ++ * stereo-enabled state for a sufficiently long period of time, ++ * we restart Mutter. ++ */ ++ ++#include ++ ++#include ++#include ++#include ++ ++#include "display-private.h" ++#include ++#include ++#include ++#include "stereo.h" ++#include "ui/ui.h" ++#include "util-private.h" ++ ++static guint stereo_switch_id = 0; ++static gboolean stereo_enabled = FALSE; ++/* -1 so the first time meta_stereo_set_have_stereo_windows() is called ++ * we avoid the short-circuit and set up a timeout to restart ++ * if necessary */ ++static gboolean stereo_have_windows = (gboolean)-1; ++static gboolean stereo_restart = FALSE; ++ ++#define STEREO_ENABLE_WAIT 1000 ++#define STEREO_DISABLE_WAIT 5000 ++ ++void ++meta_stereo_init (void) ++{ ++ Display *xdisplay; ++ Window root; ++ Atom atom_enable_stereo; ++ Atom type; ++ int format; ++ unsigned long n_items, bytes_after; ++ guchar *data; ++ ++ xdisplay = XOpenDisplay (NULL); ++ if (xdisplay == NULL) ++ meta_fatal ("Unable to open X display %s\n", XDisplayName (NULL)); ++ ++ root = DefaultRootWindow (xdisplay); ++ atom_enable_stereo = XInternAtom (xdisplay, "_MUTTER_ENABLE_STEREO", False); ++ ++ XGetWindowProperty (xdisplay, root, atom_enable_stereo, ++ 0, 1, False, XA_INTEGER, ++ &type, &format, &n_items, &bytes_after, &data); ++ if (type == XA_INTEGER) ++ { ++ if (format == 32 && n_items == 1 && bytes_after == 0) ++ { ++ stereo_enabled = *(long *)data; ++ } ++ else ++ { ++ meta_warning ("Bad value for _MUTTER_ENABLE_STEREO property\n"); ++ } ++ ++ XFree (data); ++ } ++ else if (type != None) ++ { ++ meta_warning ("Bad type for _MUTTER_ENABLE_STEREO property\n"); ++ } ++ ++ meta_verbose ("On startup, _MUTTER_ENABLE_STEREO=%s", ++ stereo_enabled ? "yes" : "no"); ++ clutter_x11_set_use_stereo_stage (stereo_enabled); ++ XCloseDisplay (xdisplay); ++} ++ ++static gboolean ++meta_stereo_switch (gpointer data) ++{ ++ stereo_switch_id = 0; ++ stereo_restart = TRUE; ++ ++ meta_restart (stereo_have_windows ? ++ _("Enabling stereo...") : ++ _("Disabling stereo...")); ++ ++ return FALSE; ++} ++ ++void ++meta_stereo_set_have_stereo_windows (gboolean have_windows) ++{ ++ have_windows = have_windows != FALSE; ++ ++ if (!stereo_restart && have_windows != stereo_have_windows) ++ { ++ MetaDisplay *display = meta_get_display (); ++ Display *xdisplay = meta_x11_display_get_xdisplay (display->x11_display); ++ Window root = DefaultRootWindow (xdisplay); ++ Atom atom_enable_stereo = XInternAtom (xdisplay, "_MUTTER_ENABLE_STEREO", False); ++ long value; ++ ++ stereo_have_windows = have_windows; ++ ++ if (stereo_have_windows) ++ meta_verbose ("Detected stereo windows\n"); ++ else ++ meta_verbose ("No stereo windows detected\n"); ++ ++ value = stereo_have_windows; ++ XChangeProperty (xdisplay, root, ++ atom_enable_stereo, XA_INTEGER, 32, ++ PropModeReplace, (guchar *)&value, 1); ++ ++ if (stereo_switch_id != 0) ++ { ++ g_source_remove (stereo_switch_id); ++ stereo_switch_id = 0; ++ } ++ ++ if (stereo_have_windows != stereo_enabled) ++ stereo_switch_id = g_timeout_add (stereo_have_windows ? STEREO_ENABLE_WAIT : STEREO_DISABLE_WAIT, ++ meta_stereo_switch, NULL); ++ } ++} +diff --git a/src/core/stereo.h b/src/core/stereo.h +new file mode 100644 +index 0000000000..ccd1d702a1 +--- /dev/null ++++ b/src/core/stereo.h +@@ -0,0 +1,28 @@ ++/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ ++ ++/* ++ * Copyright (C) 2014 Red Hat, Inc. ++ * ++ * 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_STEREO_H ++#define META_STEREO_H ++ ++void meta_stereo_init (void); ++void meta_stereo_set_have_stereo_windows (gboolean have_windows); ++gboolean meta_stereo_is_restart (void); ++void meta_stereo_finish_restart (void); ++ ++#endif +diff --git a/src/meson.build b/src/meson.build +index 284bdf5220..c56438fbbe 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -394,6 +394,8 @@ mutter_sources = [ + 'core/stack.h', + 'core/stack-tracker.c', + 'core/stack-tracker.h', ++ 'core/stereo.c', ++ 'core/stereo.h', + 'core/startup-notification.c', + 'core/startup-notification-private.h', + 'core/util.c', +diff --git a/src/wayland/meta-wayland-actor-surface.c b/src/wayland/meta-wayland-actor-surface.c +index 797795f861..d8d1f3ce16 100644 +--- a/src/wayland/meta-wayland-actor-surface.c ++++ b/src/wayland/meta-wayland-actor-surface.c +@@ -193,7 +193,7 @@ meta_wayland_actor_surface_real_sync_actor_state (MetaWaylandActorSurface *actor + snippet = meta_wayland_buffer_create_snippet (buffer); + is_y_inverted = meta_wayland_buffer_is_y_inverted (buffer); + +- meta_shaped_texture_set_texture (stex, surface->texture); ++ meta_shaped_texture_set_textures (stex, surface->texture, NULL); + meta_shaped_texture_set_snippet (stex, snippet); + meta_shaped_texture_set_is_y_inverted (stex, is_y_inverted); + meta_shaped_texture_set_buffer_scale (stex, surface->scale); +@@ -201,7 +201,7 @@ meta_wayland_actor_surface_real_sync_actor_state (MetaWaylandActorSurface *actor + } + else + { +- meta_shaped_texture_set_texture (stex, NULL); ++ meta_shaped_texture_set_textures (stex, NULL, NULL); + } + + surface_rect = (cairo_rectangle_int_t) { +-- +2.31.1 + + +From 36517cd245584c5bcd3b730efaf6c3d2e7c9472d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Wed, 2 Jun 2021 16:55:45 +0200 +Subject: [PATCH 2/2] compositor: Only check for stereo when using GLX + +If EGL Xlib is used, we'll get bogus return value and crash. +--- + src/compositor/meta-compositor-x11.c | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/src/compositor/meta-compositor-x11.c b/src/compositor/meta-compositor-x11.c +index afbe3f57e2..4345f38b6f 100644 +--- a/src/compositor/meta-compositor-x11.c ++++ b/src/compositor/meta-compositor-x11.c +@@ -153,9 +153,17 @@ meta_compositor_x11_process_xevent (MetaCompositorX11 *compositor_x11, + static gboolean + display_has_stereo_tree_ext (MetaX11Display *x11_display) + { ++ MetaBackend *backend = meta_get_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); + Display *xdisplay = x11_display->xdisplay; + const char *extensions_string; + ++ if (cogl_renderer_get_winsys_id (cogl_renderer) != COGL_WINSYS_ID_GLX) ++ return FALSE; ++ + static const char * (*query_extensions_string) (Display *display, + int screen); + +-- +2.31.1 + diff --git a/SOURCES/hw-cursor-dumb-buffer.patch b/SOURCES/hw-cursor-dumb-buffer.patch new file mode 100644 index 0000000..a2fc4e5 --- /dev/null +++ b/SOURCES/hw-cursor-dumb-buffer.patch @@ -0,0 +1,490 @@ +From acb3e966b26ea55019a148d6482f5aa4c05275a9 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Tue, 8 Feb 2022 15:58:33 +0100 +Subject: [PATCH 1/4] drm-buffer: Keep track of handle as well + +This handle is used by the legacy KMS API; lets avoid having to have GBM +specific code where this is done by letting the MetaDrmBuffer API, that +already has this information, expose it. +--- + src/backends/native/meta-drm-buffer-dumb.c | 1 + + src/backends/native/meta-drm-buffer-gbm.c | 1 + + src/backends/native/meta-drm-buffer-import.c | 1 + + src/backends/native/meta-drm-buffer-private.h | 1 + + src/backends/native/meta-drm-buffer.c | 10 ++++++++++ + src/backends/native/meta-drm-buffer.h | 2 ++ + 6 files changed, 16 insertions(+) + +diff --git a/src/backends/native/meta-drm-buffer-dumb.c b/src/backends/native/meta-drm-buffer-dumb.c +index 373eb5c73581..b5e15528a1c6 100644 +--- a/src/backends/native/meta-drm-buffer-dumb.c ++++ b/src/backends/native/meta-drm-buffer-dumb.c +@@ -194,6 +194,7 @@ init_dumb_buffer_in_impl (MetaKmsImpl *impl, + .width = data->width, + .height = data->height, + .format = data->format, ++ .handle = create_arg.handle, + .handles = { create_arg.handle }, + .strides = { create_arg.pitch }, + }; +diff --git a/src/backends/native/meta-drm-buffer-gbm.c b/src/backends/native/meta-drm-buffer-gbm.c +index 48ee9eb048ba..fa45dd98b91e 100644 +--- a/src/backends/native/meta-drm-buffer-gbm.c ++++ b/src/backends/native/meta-drm-buffer-gbm.c +@@ -119,6 +119,7 @@ init_fb_id (MetaDrmBufferGbm *buffer_gbm, + fb_args.width = gbm_bo_get_width (bo); + fb_args.height = gbm_bo_get_height (bo); + fb_args.format = gbm_bo_get_format (bo); ++ fb_args.handle = gbm_bo_get_handle (bo).u32; + + if (!meta_drm_buffer_ensure_fb_id (META_DRM_BUFFER (buffer_gbm), + use_modifiers, &fb_args, error)) +diff --git a/src/backends/native/meta-drm-buffer-import.c b/src/backends/native/meta-drm-buffer-import.c +index 1e5a38246172..ea744953cb9b 100644 +--- a/src/backends/native/meta-drm-buffer-import.c ++++ b/src/backends/native/meta-drm-buffer-import.c +@@ -125,6 +125,7 @@ import_gbm_buffer (MetaDrmBufferImport *buffer_import, + fb_args.width = gbm_bo_get_width (primary_bo); + fb_args.height = gbm_bo_get_height (primary_bo); + fb_args.format = gbm_bo_get_format (primary_bo); ++ fb_args.handle = gbm_bo_get_handle (primary_bo).u32; + + imported_bo = dmabuf_to_gbm_bo (importer, + dmabuf_fd, +diff --git a/src/backends/native/meta-drm-buffer-private.h b/src/backends/native/meta-drm-buffer-private.h +index 2c77eb957948..f660d3a7f363 100644 +--- a/src/backends/native/meta-drm-buffer-private.h ++++ b/src/backends/native/meta-drm-buffer-private.h +@@ -33,6 +33,7 @@ typedef struct _MetaDrmFbArgs + uint32_t offsets[4]; + uint32_t strides[4]; + uint64_t modifiers[4]; ++ uint32_t handle; + } MetaDrmFbArgs; + + struct _MetaDrmBufferClass +diff --git a/src/backends/native/meta-drm-buffer.c b/src/backends/native/meta-drm-buffer.c +index 81a36196e528..a0f8055b3107 100644 +--- a/src/backends/native/meta-drm-buffer.c ++++ b/src/backends/native/meta-drm-buffer.c +@@ -50,6 +50,7 @@ typedef struct _MetaDrmBufferPrivate + { + MetaKmsDevice *device; + uint32_t fb_id; ++ uint32_t handle; + } MetaDrmBufferPrivate; + + G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (MetaDrmBuffer, meta_drm_buffer, +@@ -139,6 +140,7 @@ meta_drm_buffer_ensure_fb_in_impl (MetaDrmBuffer *buffer, + } + + priv->fb_id = fb_id; ++ priv->handle = fb_args->handle; + + return TRUE; + } +@@ -242,6 +244,14 @@ meta_drm_buffer_get_fb_id (MetaDrmBuffer *buffer) + return priv->fb_id; + } + ++uint32_t ++meta_drm_buffer_get_handle (MetaDrmBuffer *buffer) ++{ ++ MetaDrmBufferPrivate *priv = meta_drm_buffer_get_instance_private (buffer); ++ ++ return priv->handle; ++} ++ + int + meta_drm_buffer_get_width (MetaDrmBuffer *buffer) + { +diff --git a/src/backends/native/meta-drm-buffer.h b/src/backends/native/meta-drm-buffer.h +index 1647d399e7f2..476264ce753e 100644 +--- a/src/backends/native/meta-drm-buffer.h ++++ b/src/backends/native/meta-drm-buffer.h +@@ -34,6 +34,8 @@ G_DECLARE_DERIVABLE_TYPE (MetaDrmBuffer, + + uint32_t meta_drm_buffer_get_fb_id (MetaDrmBuffer *buffer); + ++uint32_t meta_drm_buffer_get_handle (MetaDrmBuffer *buffer); ++ + int meta_drm_buffer_get_width (MetaDrmBuffer *buffer); + + int meta_drm_buffer_get_height (MetaDrmBuffer *buffer); +-- +2.34.1 + + +From bdd2f1dd0a67743085c84f6e060160fbd28122df Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Tue, 8 Feb 2022 16:00:17 +0100 +Subject: [PATCH 2/4] kms/impl-device/simple: Get the buffer handle from + MetaDrmBuffer + +This avoids buffer implementation specific code where it shouldn't +matter. +--- + src/backends/native/meta-kms-impl-device-simple.c | 15 ++------------- + 1 file changed, 2 insertions(+), 13 deletions(-) + +diff --git a/src/backends/native/meta-kms-impl-device-simple.c b/src/backends/native/meta-kms-impl-device-simple.c +index 1519962b8ed7..1663a2e9cacd 100644 +--- a/src/backends/native/meta-kms-impl-device-simple.c ++++ b/src/backends/native/meta-kms-impl-device-simple.c +@@ -1182,20 +1182,9 @@ process_cursor_plane_assignment (MetaKmsImplDevice *impl_device, + height = plane_assignment->dst_rect.height; + + if (plane_assignment->buffer) +- { +- MetaDrmBufferGbm *buffer_gbm = +- META_DRM_BUFFER_GBM (plane_assignment->buffer); +- struct gbm_bo *bo; +- union gbm_bo_handle handle; +- +- bo = meta_drm_buffer_gbm_get_bo (buffer_gbm); +- handle = gbm_bo_get_handle (bo); +- handle_u32 = handle.u32; +- } ++ handle_u32 = meta_drm_buffer_get_handle (plane_assignment->buffer); + else +- { +- handle_u32 = 0; +- } ++ handle_u32 = 0; + + meta_topic (META_DEBUG_KMS, + "[simple] Setting HW cursor of CRTC %u (%s) to %u " +-- +2.34.1 + + +From d781f140b7197d3c9cd67f8fded4e2b58a4eda92 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Tue, 8 Feb 2022 15:05:32 +0100 +Subject: [PATCH 3/4] cursor-renderer/native: Move buffer creation to helper + +--- + .../native/meta-cursor-renderer-native.c | 134 ++++++++++++------ + 1 file changed, 89 insertions(+), 45 deletions(-) + +diff --git a/src/backends/native/meta-cursor-renderer-native.c b/src/backends/native/meta-cursor-renderer-native.c +index 098ef24bdf2e..2bcbe6b2c8b0 100644 +--- a/src/backends/native/meta-cursor-renderer-native.c ++++ b/src/backends/native/meta-cursor-renderer-native.c +@@ -1215,6 +1215,81 @@ ensure_cursor_priv (MetaCursorSprite *cursor_sprite) + return cursor_priv; + } + ++static MetaDrmBuffer * ++create_cursor_drm_buffer_gbm (MetaGpuKms *gpu_kms, ++ uint8_t *pixels, ++ int width, ++ int height, ++ int stride, ++ int cursor_width, ++ int cursor_height, ++ uint32_t format, ++ GError **error) ++{ ++ MetaKmsDevice *kms_device = meta_gpu_kms_get_kms_device (gpu_kms); ++ struct gbm_device *gbm_device; ++ struct gbm_bo *bo; ++ uint8_t buf[4 * cursor_width * cursor_height]; ++ int i; ++ MetaDrmBufferGbm *buffer_gbm; ++ ++ gbm_device = meta_gbm_device_from_gpu (gpu_kms); ++ if (!gbm_device_is_format_supported (gbm_device, format, ++ GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE)) ++ { ++ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, ++ "Buffer format not supported"); ++ return NULL; ++ } ++ ++ bo = gbm_bo_create (gbm_device, cursor_width, cursor_height, ++ format, GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE); ++ if (!bo) ++ { ++ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, ++ "Failed to allocate gbm_bo: %s", g_strerror (errno)); ++ return NULL; ++ } ++ ++ memset (buf, 0, sizeof (buf)); ++ for (i = 0; i < height; i++) ++ memcpy (buf + i * 4 * cursor_width, pixels + i * stride, width * 4); ++ if (gbm_bo_write (bo, buf, cursor_width * cursor_height * 4) != 0) ++ { ++ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, ++ "Failed write to gbm_bo: %s", g_strerror (errno)); ++ gbm_bo_destroy (bo); ++ return NULL; ++ } ++ ++ buffer_gbm = meta_drm_buffer_gbm_new_take (kms_device, bo, FALSE, error); ++ if (!buffer_gbm) ++ { ++ gbm_bo_destroy (bo); ++ return NULL; ++ } ++ ++ return META_DRM_BUFFER (buffer_gbm); ++} ++ ++static MetaDrmBuffer * ++create_cursor_drm_buffer (MetaGpuKms *gpu_kms, ++ uint8_t *pixels, ++ int width, ++ int height, ++ int stride, ++ int cursor_width, ++ int cursor_height, ++ uint32_t format, ++ GError **error) ++{ ++ return create_cursor_drm_buffer_gbm (gpu_kms, pixels, ++ width, height, stride, ++ cursor_width, cursor_height, ++ format, ++ error); ++} ++ + static void + load_cursor_sprite_gbm_buffer_for_gpu (MetaCursorRendererNative *native, + MetaGpuKms *gpu_kms, +@@ -1226,8 +1301,9 @@ load_cursor_sprite_gbm_buffer_for_gpu (MetaCursorRendererNative *native, + uint32_t gbm_format) + { + uint64_t cursor_width, cursor_height; ++ MetaDrmBuffer *buffer; + MetaCursorRendererNativeGpuData *cursor_renderer_gpu_data; +- struct gbm_device *gbm_device; ++ g_autoptr (GError) error = NULL; + + cursor_renderer_gpu_data = + meta_cursor_renderer_native_gpu_data_from_gpu (gpu_kms); +@@ -1244,52 +1320,20 @@ load_cursor_sprite_gbm_buffer_for_gpu (MetaCursorRendererNative *native, + return; + } + +- gbm_device = meta_gbm_device_from_gpu (gpu_kms); +- if (gbm_device_is_format_supported (gbm_device, gbm_format, +- GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE)) +- { +- MetaKmsDevice *kms_device = meta_gpu_kms_get_kms_device (gpu_kms); +- struct gbm_bo *bo; +- uint8_t buf[4 * cursor_width * cursor_height]; +- uint i; +- g_autoptr (GError) error = NULL; +- MetaDrmBufferGbm *buffer_gbm; +- +- bo = gbm_bo_create (gbm_device, cursor_width, cursor_height, +- gbm_format, GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE); +- if (!bo) +- { +- meta_warning ("Failed to allocate HW cursor buffer"); +- return; +- } +- +- memset (buf, 0, sizeof(buf)); +- for (i = 0; i < height; i++) +- memcpy (buf + i * 4 * cursor_width, pixels + i * rowstride, width * 4); +- if (gbm_bo_write (bo, buf, cursor_width * cursor_height * 4) != 0) +- { +- meta_warning ("Failed to write cursors buffer data: %s", +- g_strerror (errno)); +- gbm_bo_destroy (bo); +- return; +- } +- +- buffer_gbm = meta_drm_buffer_gbm_new_take (kms_device, bo, FALSE, &error); +- if (!buffer_gbm) +- { +- meta_warning ("Failed to create DRM buffer wrapper: %s", +- error->message); +- gbm_bo_destroy (bo); +- return; +- } +- +- set_pending_cursor_sprite_buffer (cursor_sprite, gpu_kms, +- META_DRM_BUFFER (buffer_gbm)); +- } +- else ++ buffer = create_cursor_drm_buffer (gpu_kms, ++ pixels, ++ width, height, rowstride, ++ cursor_width, ++ cursor_height, ++ gbm_format, ++ &error); ++ if (!buffer) + { +- meta_warning ("HW cursor for format %d not supported", gbm_format); ++ g_warning ("Realizing HW cursor failed: %s", error->message); ++ return; + } ++ ++ set_pending_cursor_sprite_buffer (cursor_sprite, gpu_kms, buffer); + } + + static gboolean +-- +2.34.1 + + +From 717baf8dc1dd2f043350dd354bc4905285ba3337 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Tue, 8 Feb 2022 15:38:37 +0100 +Subject: [PATCH 4/4] cursor-renderer/native: Support allocating dumb buffers + +--- + .../native/meta-cursor-renderer-native.c | 88 ++++++++++++++----- + 1 file changed, 67 insertions(+), 21 deletions(-) + +diff --git a/src/backends/native/meta-cursor-renderer-native.c b/src/backends/native/meta-cursor-renderer-native.c +index 2bcbe6b2c8b0..18ffdf37dac4 100644 +--- a/src/backends/native/meta-cursor-renderer-native.c ++++ b/src/backends/native/meta-cursor-renderer-native.c +@@ -39,6 +39,7 @@ + #include "backends/meta-monitor-manager-private.h" + #include "backends/meta-output.h" + #include "backends/native/meta-crtc-kms.h" ++#include "backends/native/meta-drm-buffer-dumb.h" + #include "backends/native/meta-drm-buffer-gbm.h" + #include "backends/native/meta-kms-device.h" + #include "backends/native/meta-kms-plane.h" +@@ -1216,24 +1217,23 @@ ensure_cursor_priv (MetaCursorSprite *cursor_sprite) + } + + static MetaDrmBuffer * +-create_cursor_drm_buffer_gbm (MetaGpuKms *gpu_kms, +- uint8_t *pixels, +- int width, +- int height, +- int stride, +- int cursor_width, +- int cursor_height, +- uint32_t format, +- GError **error) ++create_cursor_drm_buffer_gbm (MetaGpuKms *gpu_kms, ++ struct gbm_device *gbm_device, ++ uint8_t *pixels, ++ int width, ++ int height, ++ int stride, ++ int cursor_width, ++ int cursor_height, ++ uint32_t format, ++ GError **error) + { + MetaKmsDevice *kms_device = meta_gpu_kms_get_kms_device (gpu_kms); +- struct gbm_device *gbm_device; + struct gbm_bo *bo; + uint8_t buf[4 * cursor_width * cursor_height]; + int i; + MetaDrmBufferGbm *buffer_gbm; + +- gbm_device = meta_gbm_device_from_gpu (gpu_kms); + if (!gbm_device_is_format_supported (gbm_device, format, + GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE)) + { +@@ -1272,6 +1272,38 @@ create_cursor_drm_buffer_gbm (MetaGpuKms *gpu_kms, + return META_DRM_BUFFER (buffer_gbm); + } + ++static MetaDrmBuffer * ++create_cursor_drm_buffer_dumb (MetaGpuKms *gpu_kms, ++ uint8_t *pixels, ++ int width, ++ int height, ++ int stride, ++ int cursor_width, ++ int cursor_height, ++ uint32_t format, ++ GError **error) ++{ ++ MetaKmsDevice *kms_device = meta_gpu_kms_get_kms_device (gpu_kms); ++ MetaDrmBufferDumb *buffer_dumb; ++ int i; ++ uint8_t *data; ++ ++ buffer_dumb = meta_drm_buffer_dumb_new (kms_device, ++ cursor_width, cursor_height, ++ format, ++ error); ++ if (!buffer_dumb) ++ return NULL; ++ ++ data = meta_drm_buffer_dumb_get_data (buffer_dumb); ++ ++ memset (data, 0, cursor_width * cursor_height * 4); ++ for (i = 0; i < height; i++) ++ memcpy (data + i * 4 * cursor_width, pixels + i * stride, width * 4); ++ ++ return META_DRM_BUFFER (buffer_dumb); ++} ++ + static MetaDrmBuffer * + create_cursor_drm_buffer (MetaGpuKms *gpu_kms, + uint8_t *pixels, +@@ -1283,11 +1315,27 @@ create_cursor_drm_buffer (MetaGpuKms *gpu_kms, + uint32_t format, + GError **error) + { +- return create_cursor_drm_buffer_gbm (gpu_kms, pixels, +- width, height, stride, +- cursor_width, cursor_height, +- format, +- error); ++ struct gbm_device *gbm_device; ++ ++ gbm_device = meta_gbm_device_from_gpu (gpu_kms); ++ if (gbm_device) ++ { ++ return create_cursor_drm_buffer_gbm (gpu_kms, gbm_device, ++ pixels, ++ width, height, stride, ++ cursor_width, cursor_height, ++ format, ++ error); ++ } ++ else ++ { ++ return create_cursor_drm_buffer_dumb (gpu_kms, ++ pixels, ++ width, height, stride, ++ cursor_width, cursor_height, ++ format, ++ error); ++ } + } + + static void +@@ -1629,6 +1677,9 @@ realize_cursor_sprite_from_wl_buffer_for_gpu (MetaCursorRenderer *renderer, + } + + gbm_device = meta_gbm_device_from_gpu (gpu_kms); ++ if (!gbm_device) ++ return; ++ + bo = gbm_bo_import (gbm_device, + GBM_BO_IMPORT_WL_BUFFER, + buffer, +@@ -1807,13 +1858,8 @@ init_hw_cursor_support_for_gpu (MetaGpuKms *gpu_kms) + { + MetaKmsDevice *kms_device = meta_gpu_kms_get_kms_device (gpu_kms); + MetaCursorRendererNativeGpuData *cursor_renderer_gpu_data; +- struct gbm_device *gbm_device; + uint64_t width, height; + +- gbm_device = meta_gbm_device_from_gpu (gpu_kms); +- if (!gbm_device) +- return; +- + cursor_renderer_gpu_data = + meta_create_cursor_renderer_native_gpu_data (gpu_kms); + +-- +2.34.1 + diff --git a/SOURCES/legacy-x11-input-configuration.patch b/SOURCES/legacy-x11-input-configuration.patch new file mode 100644 index 0000000..597e0d7 --- /dev/null +++ b/SOURCES/legacy-x11-input-configuration.patch @@ -0,0 +1,819 @@ +From 705818340dec181335b48ab73d6411e639daaeae Mon Sep 17 00:00:00 2001 +From: Carlos Garnacho +Date: Tue, 1 Jun 2021 11:44:20 +0200 +Subject: [PATCH 1/6] backends/x11: Support synaptics configuration + +The code is taken mostly as-is from g-s-d, so we can drag the +dead horse a bit longer. +--- + src/backends/x11/meta-input-settings-x11.c | 275 +++++++++++++++++++++ + 1 file changed, 275 insertions(+) + +diff --git a/src/backends/x11/meta-input-settings-x11.c b/src/backends/x11/meta-input-settings-x11.c +index 96390285a6..0631fd2fee 100644 +--- a/src/backends/x11/meta-input-settings-x11.c ++++ b/src/backends/x11/meta-input-settings-x11.c +@@ -26,6 +26,7 @@ + #include "backends/x11/meta-input-settings-x11.h" + + #include ++#include + #include + #include + #include +@@ -165,6 +166,184 @@ change_property (ClutterInputDevice *device, + meta_XFree (data_ret); + } + ++static gboolean ++is_device_synaptics (ClutterInputDevice *device) ++{ ++ guchar *has_setting; ++ ++ /* We just need looking for a synaptics-specific property */ ++ has_setting = get_property (device, "Synaptics Off", XA_INTEGER, 8, 1); ++ if (!has_setting) ++ return FALSE; ++ ++ meta_XFree (has_setting); ++ return TRUE; ++} ++ ++static void ++change_synaptics_tap_left_handed (ClutterInputDevice *device, ++ gboolean tap_enabled, ++ gboolean left_handed) ++{ ++ MetaDisplay *display = meta_get_display (); ++ MetaX11Display *x11_display = display ? display->x11_display : NULL; ++ MetaBackend *backend = meta_get_backend (); ++ Display *xdisplay = meta_backend_x11_get_xdisplay (META_BACKEND_X11 (backend)); ++ int device_id; ++ XDevice *xdevice; ++ guchar *tap_action, *buttons; ++ guint buttons_capacity = 16, n_buttons; ++ ++ device_id = meta_input_device_x11_get_device_id (device); ++ xdevice = XOpenDevice (xdisplay, device_id); ++ if (!xdevice) ++ return; ++ ++ tap_action = get_property (device, "Synaptics Tap Action", ++ XA_INTEGER, 8, 7); ++ if (!tap_action) ++ goto out; ++ ++ tap_action[4] = tap_enabled ? (left_handed ? 3 : 1) : 0; ++ tap_action[5] = tap_enabled ? (left_handed ? 1 : 3) : 0; ++ tap_action[6] = tap_enabled ? 2 : 0; ++ ++ change_property (device, "Synaptics Tap Action", ++ XA_INTEGER, 8, tap_action, 7); ++ meta_XFree (tap_action); ++ ++ if (x11_display) ++ meta_x11_error_trap_push (x11_display); ++ buttons = g_new (guchar, buttons_capacity); ++ n_buttons = XGetDeviceButtonMapping (xdisplay, xdevice, ++ buttons, buttons_capacity); ++ ++ while (n_buttons > buttons_capacity) ++ { ++ buttons_capacity = n_buttons; ++ buttons = (guchar *) g_realloc (buttons, ++ buttons_capacity * sizeof (guchar)); ++ ++ n_buttons = XGetDeviceButtonMapping (xdisplay, xdevice, ++ buttons, buttons_capacity); ++ } ++ ++ buttons[0] = left_handed ? 3 : 1; ++ buttons[2] = left_handed ? 1 : 3; ++ XSetDeviceButtonMapping (xdisplay, xdevice, buttons, n_buttons); ++ g_free (buttons); ++ ++ if (x11_display && meta_x11_error_trap_pop_with_return (x11_display)) ++ { ++ g_warning ("Could not set synaptics touchpad left-handed for %s", ++ clutter_input_device_get_device_name (device)); ++ } ++ ++ out: ++ XCloseDevice (xdisplay, xdevice); ++} ++ ++static void ++change_synaptics_speed (ClutterInputDevice *device, ++ gdouble speed) ++{ ++ MetaDisplay *display = meta_get_display (); ++ MetaX11Display *x11_display = display ? display->x11_display : NULL; ++ MetaBackend *backend = meta_get_backend (); ++ Display *xdisplay = meta_backend_x11_get_xdisplay (META_BACKEND_X11 (backend)); ++ int device_id; ++ XDevice *xdevice; ++ XPtrFeedbackControl feedback; ++ XFeedbackState *states, *state; ++ int i, num_feedbacks, motion_threshold, numerator, denominator; ++ gfloat motion_acceleration; ++ ++ device_id = meta_input_device_x11_get_device_id (device); ++ xdevice = XOpenDevice (xdisplay, device_id); ++ if (!xdevice) ++ return; ++ /* Get the list of feedbacks for the device */ ++ states = XGetFeedbackControl (xdisplay, xdevice, &num_feedbacks); ++ if (!states) ++ return; ++ ++ /* Calculate acceleration and threshold */ ++ motion_acceleration = (speed + 1) * 5; /* speed is [-1..1], map to [0..10] */ ++ motion_threshold = CLAMP (10 - floor (motion_acceleration), 1, 10); ++ ++ if (motion_acceleration >= 1.0) ++ { ++ /* we want to get the acceleration, with a resolution of 0.5 ++ */ ++ if ((motion_acceleration - floor (motion_acceleration)) < 0.25) ++ { ++ numerator = floor (motion_acceleration); ++ denominator = 1; ++ } ++ else if ((motion_acceleration - floor (motion_acceleration)) < 0.5) ++ { ++ numerator = ceil (2.0 * motion_acceleration); ++ denominator = 2; ++ } ++ else if ((motion_acceleration - floor (motion_acceleration)) < 0.75) ++ { ++ numerator = floor (2.0 *motion_acceleration); ++ denominator = 2; ++ } ++ else ++ { ++ numerator = ceil (motion_acceleration); ++ denominator = 1; ++ } ++ } ++ else if (motion_acceleration < 1.0 && motion_acceleration > 0) ++ { ++ /* This we do to 1/10ths */ ++ numerator = floor (motion_acceleration * 10) + 1; ++ denominator= 10; ++ } ++ else ++ { ++ numerator = -1; ++ denominator = -1; ++ } ++ ++ if (x11_display) ++ meta_x11_error_trap_push (x11_display); ++ ++ state = (XFeedbackState *) states; ++ ++ for (i = 0; i < num_feedbacks; i++) ++ { ++ if (state->class == PtrFeedbackClass) ++ { ++ /* And tell the device */ ++ feedback.class = PtrFeedbackClass; ++ feedback.length = sizeof (XPtrFeedbackControl); ++ feedback.id = state->id; ++ feedback.threshold = motion_threshold; ++ feedback.accelNum = numerator; ++ feedback.accelDenom = denominator; ++ ++ XChangeFeedbackControl (xdisplay, xdevice, ++ DvAccelNum | DvAccelDenom | DvThreshold, ++ (XFeedbackControl *) &feedback); ++ break; ++ } ++ ++ state = (XFeedbackState *) ((char *) state + state->length); ++ } ++ ++ if (x11_display && meta_x11_error_trap_pop_with_return (x11_display)) ++ { ++ g_warning ("Could not set synaptics touchpad acceleration for %s", ++ clutter_input_device_get_device_name (device)); ++ } ++ ++ XFreeFeedbackList (states); ++ XCloseDevice (xdisplay, xdevice); ++} ++ + static void + meta_input_settings_x11_set_send_events (MetaInputSettings *settings, + ClutterInputDevice *device, +@@ -173,6 +352,13 @@ meta_input_settings_x11_set_send_events (MetaInputSettings *settings, + guchar values[2] = { 0 }; /* disabled, disabled-on-external-mouse */ + guchar *available; + ++ if (is_device_synaptics (device)) ++ { ++ values[0] = mode != G_DESKTOP_DEVICE_SEND_EVENTS_ENABLED; ++ change_property (device, "Synaptics Off", XA_INTEGER, 8, &values, 1); ++ return; ++ } ++ + available = get_property (device, "libinput Send Events Modes Available", + XA_INTEGER, 8, 2); + if (!available) +@@ -225,6 +411,12 @@ meta_input_settings_x11_set_speed (MetaInputSettings *settings, + Display *xdisplay = meta_backend_x11_get_xdisplay (META_BACKEND_X11 (backend)); + gfloat value = speed; + ++ if (is_device_synaptics (device)) ++ { ++ change_synaptics_speed (device, speed); ++ return; ++ } ++ + change_property (device, "libinput Accel Speed", + XInternAtom (xdisplay, "FLOAT", False), + 32, &value, 1); +@@ -251,6 +443,19 @@ meta_input_settings_x11_set_left_handed (MetaInputSettings *settings, + else + { + value = enabled ? 1 : 0; ++ ++ if (is_device_synaptics (device)) ++ { ++ GSettings *settings; ++ ++ settings = g_settings_new ("org.gnome.desktop.peripherals.touchpad"); ++ change_synaptics_tap_left_handed (device, ++ g_settings_get_boolean (settings, "tap-to-click"), ++ enabled); ++ g_object_unref (settings); ++ return; ++ } ++ + change_property (device, "libinput Left Handed Enabled", + XA_INTEGER, 8, &value, 1); + } +@@ -274,6 +479,20 @@ meta_input_settings_x11_set_tap_enabled (MetaInputSettings *settings, + { + guchar value = (enabled) ? 1 : 0; + ++ if (is_device_synaptics (device)) ++ { ++ GDesktopTouchpadHandedness handedness; ++ GSettings *settings; ++ ++ settings = g_settings_new ("org.gnome.desktop.peripherals.touchpad"); ++ handedness = g_settings_get_enum (settings, "left-handed"); ++ g_object_unref (settings); ++ ++ change_synaptics_tap_left_handed (device, enabled, ++ handedness == G_DESKTOP_TOUCHPAD_HANDEDNESS_LEFT); ++ return; ++ } ++ + change_property (device, "libinput Tapping Enabled", + XA_INTEGER, 8, &value, 1); + } +@@ -307,6 +526,27 @@ meta_input_settings_x11_set_invert_scroll (MetaInputSettings *settings, + { + guchar value = (inverted) ? 1 : 0; + ++ if (is_device_synaptics (device)) ++ { ++ gint32 *scrolling_distance; ++ ++ scrolling_distance = get_property (device, "Synaptics Scrolling Distance", ++ XA_INTEGER, 32, 2); ++ if (scrolling_distance) ++ { ++ scrolling_distance[0] = inverted ? ++ -abs (scrolling_distance[0]) : abs (scrolling_distance[0]); ++ scrolling_distance[1] = inverted ? ++ -abs (scrolling_distance[1]) : abs (scrolling_distance[1]); ++ ++ change_property (device, "Synaptics Scrolling Distance", ++ XA_INTEGER, 32, scrolling_distance, 2); ++ meta_XFree (scrolling_distance); ++ } ++ ++ return; ++ } ++ + change_property (device, "libinput Natural Scrolling Enabled", + XA_INTEGER, 8, &value, 1); + } +@@ -320,6 +560,41 @@ change_scroll_method (ClutterInputDevice *device, + guchar *current = NULL; + guchar *available = NULL; + ++ if (is_device_synaptics (device)) ++ { ++ switch (method) ++ { ++ case SCROLL_METHOD_FIELD_EDGE: ++ current = get_property (device, "Synaptics Edge Scrolling", ++ XA_INTEGER, 8, 3); ++ if (current) ++ { ++ current[0] = enabled; ++ current[1] = enabled; ++ change_property (device, "Synaptics Edge Scrolling", ++ XA_INTEGER, 8, current, 3); ++ meta_XFree (current); ++ } ++ break; ++ case SCROLL_METHOD_FIELD_2FG: ++ current = get_property (device, "Synaptics Two-Finger Scrolling", ++ XA_INTEGER, 8, 2); ++ if (current) ++ { ++ current[0] = enabled; ++ current[1] = enabled; ++ change_property (device, "Synaptics Two-Finger Scrolling", ++ XA_INTEGER, 8, current, 2); ++ meta_XFree (current); ++ } ++ break; ++ default: ++ break; ++ } ++ ++ return; ++ } ++ + available = get_property (device, "libinput Scroll Methods Available", + XA_INTEGER, 8, SCROLL_METHOD_NUM_FIELDS); + if (!available || !available[method]) +-- +2.31.1 + + +From 50c4733acf56b3b67a2706d32f5c455cb51f9458 Mon Sep 17 00:00:00 2001 +From: Carlos Garnacho +Date: Tue, 13 Feb 2018 11:44:40 +0100 +Subject: [PATCH 2/6] clutter: Extend touchpad device property check for + Synaptics + +So we reliably get CLUTTER_TOUCHPAD_DEVICE for those. The other heuristics +to get the device type may fall short. +--- + src/backends/x11/meta-seat-x11.c | 17 +++++++++++++++-- + 1 file changed, 15 insertions(+), 2 deletions(-) + +diff --git a/src/backends/x11/meta-seat-x11.c b/src/backends/x11/meta-seat-x11.c +index d43834bd7b..73938e22e0 100644 +--- a/src/backends/x11/meta-seat-x11.c ++++ b/src/backends/x11/meta-seat-x11.c +@@ -246,7 +246,8 @@ is_touch_device (XIAnyClassInfo **classes, + } + + static gboolean +-is_touchpad_device (XIDeviceInfo *info) ++query_exists_device_property (XIDeviceInfo *info, ++ const char *property) + { + gulong nitems, bytes_after; + uint32_t *data = NULL; +@@ -254,7 +255,7 @@ is_touchpad_device (XIDeviceInfo *info) + Atom type; + Atom prop; + +- prop = XInternAtom (clutter_x11_get_default_display (), "libinput Tapping Enabled", True); ++ prop = XInternAtom (clutter_x11_get_default_display (), property, True); + if (prop == None) + return FALSE; + +@@ -275,6 +276,18 @@ is_touchpad_device (XIDeviceInfo *info) + return TRUE; + } + ++static gboolean ++is_touchpad_device (XIDeviceInfo *info) ++{ ++ if (query_exists_device_property (info, "libinput Tapping Enabled")) ++ return TRUE; ++ ++ if (query_exists_device_property (info, "Synaptics Off")) ++ return TRUE; ++ ++ return FALSE; ++} ++ + static gboolean + get_device_ids (XIDeviceInfo *info, + char **vendor_id, +-- +2.31.1 + + +From 307970305d11cdca1b97c53c85bda8b809ff4f0f Mon Sep 17 00:00:00 2001 +From: Rui Matos +Date: Mon, 9 Oct 2017 18:39:52 +0200 +Subject: [PATCH 3/6] backends/x11: Add a synaptics check for two finger scroll + availability + +Commit "backends/x11: Support synaptics configuration" added support +for synaptics two finger scrolling but didn't add the code to check +that it is available resulting in the upper layer always assuming it +isn't. +--- + src/backends/x11/meta-input-settings-x11.c | 11 +++++++++++ + 1 file changed, 11 insertions(+) + +diff --git a/src/backends/x11/meta-input-settings-x11.c b/src/backends/x11/meta-input-settings-x11.c +index 0631fd2fee..2ac080127c 100644 +--- a/src/backends/x11/meta-input-settings-x11.c ++++ b/src/backends/x11/meta-input-settings-x11.c +@@ -638,6 +638,17 @@ meta_input_settings_x11_has_two_finger_scroll (MetaInputSettings *settings, + guchar *available = NULL; + gboolean has_two_finger = TRUE; + ++ if (is_device_synaptics (device)) ++ { ++ available = get_property (device, "Synaptics Capabilities", ++ XA_INTEGER, 8, 4); ++ if (!available || !available[3]) ++ has_two_finger = FALSE; ++ ++ meta_XFree (available); ++ return has_two_finger; ++ } ++ + available = get_property (device, "libinput Scroll Methods Available", + XA_INTEGER, 8, SCROLL_METHOD_NUM_FIELDS); + if (!available || !available[SCROLL_METHOD_FIELD_2FG]) +-- +2.31.1 + + +From cba31f88ddbfb7355de1daa34397aba8e8607765 Mon Sep 17 00:00:00 2001 +From: Rui Matos +Date: Mon, 9 Oct 2017 18:55:56 +0200 +Subject: [PATCH 4/6] backends/x11: Add disable while typing support for + synaptics + +This is basically a copy of the old g-s-d mouse plugin code to manage +syndaemon when the synaptics driver is being used. +--- + src/backends/x11/meta-input-settings-x11.c | 112 +++++++++++++++++++++ + 1 file changed, 112 insertions(+) + +diff --git a/src/backends/x11/meta-input-settings-x11.c b/src/backends/x11/meta-input-settings-x11.c +index 2ac080127c..2658b82172 100644 +--- a/src/backends/x11/meta-input-settings-x11.c ++++ b/src/backends/x11/meta-input-settings-x11.c +@@ -35,6 +35,9 @@ + #ifdef HAVE_LIBGUDEV + #include + #endif ++#ifdef __linux ++#include ++#endif + + #include "backends/x11/meta-backend-x11.h" + #include "backends/x11/meta-input-device-x11.h" +@@ -46,6 +49,8 @@ typedef struct _MetaInputSettingsX11Private + #ifdef HAVE_LIBGUDEV + GUdevClient *udev_client; + #endif ++ gboolean syndaemon_spawned; ++ GPid syndaemon_pid; + } MetaInputSettingsX11Private; + + G_DEFINE_TYPE_WITH_PRIVATE (MetaInputSettingsX11, meta_input_settings_x11, +@@ -344,6 +349,107 @@ change_synaptics_speed (ClutterInputDevice *device, + XCloseDevice (xdisplay, xdevice); + } + ++/* Ensure that syndaemon dies together with us, to avoid running several of ++ * them */ ++static void ++setup_syndaemon (gpointer user_data) ++{ ++#ifdef __linux ++ prctl (PR_SET_PDEATHSIG, SIGHUP); ++#endif ++} ++ ++static gboolean ++have_program_in_path (const char *name) ++{ ++ gchar *path; ++ gboolean result; ++ ++ path = g_find_program_in_path (name); ++ result = (path != NULL); ++ g_free (path); ++ return result; ++} ++ ++static void ++syndaemon_died (GPid pid, ++ gint status, ++ gpointer user_data) ++{ ++ MetaInputSettingsX11 *settings_x11 = META_INPUT_SETTINGS_X11 (user_data); ++ MetaInputSettingsX11Private *priv = ++ meta_input_settings_x11_get_instance_private (settings_x11); ++ GError *error = NULL; ++ ++ if (!g_spawn_check_exit_status (status, &error)) ++ { ++ if ((WIFSIGNALED (status) && WTERMSIG (status) != SIGHUP) || ++ error->domain == G_SPAWN_EXIT_ERROR) ++ g_warning ("Syndaemon exited unexpectedly: %s", error->message); ++ g_error_free (error); ++ } ++ ++ g_spawn_close_pid (pid); ++ priv->syndaemon_spawned = FALSE; ++} ++ ++static void ++set_synaptics_disable_w_typing (MetaInputSettings *settings, ++ gboolean state) ++{ ++ MetaInputSettingsX11 *settings_x11 = META_INPUT_SETTINGS_X11 (settings); ++ MetaInputSettingsX11Private *priv = ++ meta_input_settings_x11_get_instance_private (settings_x11); ++ ++ if (state) ++ { ++ GError *error = NULL; ++ GPtrArray *args; ++ ++ if (priv->syndaemon_spawned) ++ return; ++ ++ if (!have_program_in_path ("syndaemon")) ++ return; ++ ++ args = g_ptr_array_new (); ++ ++ g_ptr_array_add (args, (gpointer)"syndaemon"); ++ g_ptr_array_add (args, (gpointer)"-i"); ++ g_ptr_array_add (args, (gpointer)"1.0"); ++ g_ptr_array_add (args, (gpointer)"-t"); ++ g_ptr_array_add (args, (gpointer)"-K"); ++ g_ptr_array_add (args, (gpointer)"-R"); ++ g_ptr_array_add (args, NULL); ++ ++ /* we must use G_SPAWN_DO_NOT_REAP_CHILD to avoid ++ * double-forking, otherwise syndaemon will immediately get ++ * killed again through (PR_SET_PDEATHSIG when the intermediate ++ * process dies */ ++ g_spawn_async (g_get_home_dir (), (char **) args->pdata, NULL, ++ G_SPAWN_SEARCH_PATH|G_SPAWN_DO_NOT_REAP_CHILD, setup_syndaemon, NULL, ++ &priv->syndaemon_pid, &error); ++ ++ priv->syndaemon_spawned = (error == NULL); ++ g_ptr_array_free (args, TRUE); ++ ++ if (error) ++ { ++ g_warning ("Failed to launch syndaemon: %s", error->message); ++ g_error_free (error); ++ } ++ else ++ { ++ g_child_watch_add (priv->syndaemon_pid, syndaemon_died, settings); ++ } ++ } ++ else if (priv->syndaemon_spawned) ++ { ++ kill (priv->syndaemon_pid, SIGHUP); ++ priv->syndaemon_spawned = FALSE; ++ } ++} ++ + static void + meta_input_settings_x11_set_send_events (MetaInputSettings *settings, + ClutterInputDevice *device, +@@ -468,6 +574,12 @@ meta_input_settings_x11_set_disable_while_typing (MetaInputSettings *settings, + { + guchar value = (enabled) ? 1 : 0; + ++ if (is_device_synaptics (device)) ++ { ++ set_synaptics_disable_w_typing (settings, enabled); ++ return; ++ } ++ + change_property (device, "libinput Disable While Typing Enabled", + XA_INTEGER, 8, &value, 1); + } +-- +2.31.1 + + +From 354d34263534d0c7a5c7f7169d8b4a3dba79491c Mon Sep 17 00:00:00 2001 +From: Carlos Garnacho +Date: Wed, 13 Jun 2018 13:48:24 +0200 +Subject: [PATCH 5/6] clutter: Only reset scroll axes on slave devices + +As a plus, unknown source device IDs will just warn instead of crash. +--- + src/backends/x11/meta-seat-x11.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/src/backends/x11/meta-seat-x11.c b/src/backends/x11/meta-seat-x11.c +index 73938e22e0..6d2c7d3740 100644 +--- a/src/backends/x11/meta-seat-x11.c ++++ b/src/backends/x11/meta-seat-x11.c +@@ -2362,7 +2362,9 @@ meta_seat_x11_translate_event (MetaSeatX11 *seat, + seat->has_pointer_focus = FALSE; + } + +- meta_input_device_x11_reset_scroll_info (source_device); ++ if (clutter_input_device_get_device_mode (source_device) == ++ CLUTTER_INPUT_MODE_PHYSICAL) ++ meta_input_device_x11_reset_scroll_info (source_device); + + clutter_event_set_device (event, device); + clutter_event_set_source_device (event, source_device); +-- +2.31.1 + + +From b7f94b5dd09953d5a4c8aee1b79491d71f8c1e0e Mon Sep 17 00:00:00 2001 +From: Rui Matos +Date: Tue, 10 Oct 2017 19:07:27 +0200 +Subject: [PATCH 6/6] backends/x11: Support plain old X device configuration + +We re-use part of the code added to support synaptics and add a few +bits specific for xorg-x11-drv-evdev devices. +--- + src/backends/x11/meta-input-settings-x11.c | 98 +++++++++++++++++----- + 1 file changed, 75 insertions(+), 23 deletions(-) + +diff --git a/src/backends/x11/meta-input-settings-x11.c b/src/backends/x11/meta-input-settings-x11.c +index 2658b82172..80bc33c6b5 100644 +--- a/src/backends/x11/meta-input-settings-x11.c ++++ b/src/backends/x11/meta-input-settings-x11.c +@@ -185,10 +185,23 @@ is_device_synaptics (ClutterInputDevice *device) + return TRUE; + } + ++static gboolean ++is_device_libinput (ClutterInputDevice *device) ++{ ++ guchar *has_setting; ++ ++ /* We just need looking for a synaptics-specific property */ ++ has_setting = get_property (device, "libinput Send Events Modes Available", XA_INTEGER, 8, 2); ++ if (!has_setting) ++ return FALSE; ++ ++ meta_XFree (has_setting); ++ return TRUE; ++} ++ + static void +-change_synaptics_tap_left_handed (ClutterInputDevice *device, +- gboolean tap_enabled, +- gboolean left_handed) ++change_x_device_left_handed (ClutterInputDevice *device, ++ gboolean left_handed) + { + MetaDisplay *display = meta_get_display (); + MetaX11Display *x11_display = display ? display->x11_display : NULL; +@@ -196,7 +209,7 @@ change_synaptics_tap_left_handed (ClutterInputDevice *device, + Display *xdisplay = meta_backend_x11_get_xdisplay (META_BACKEND_X11 (backend)); + int device_id; + XDevice *xdevice; +- guchar *tap_action, *buttons; ++ guchar *buttons; + guint buttons_capacity = 16, n_buttons; + + device_id = meta_input_device_x11_get_device_id (device); +@@ -204,19 +217,6 @@ change_synaptics_tap_left_handed (ClutterInputDevice *device, + if (!xdevice) + return; + +- tap_action = get_property (device, "Synaptics Tap Action", +- XA_INTEGER, 8, 7); +- if (!tap_action) +- goto out; +- +- tap_action[4] = tap_enabled ? (left_handed ? 3 : 1) : 0; +- tap_action[5] = tap_enabled ? (left_handed ? 1 : 3) : 0; +- tap_action[6] = tap_enabled ? 2 : 0; +- +- change_property (device, "Synaptics Tap Action", +- XA_INTEGER, 8, tap_action, 7); +- meta_XFree (tap_action); +- + if (x11_display) + meta_x11_error_trap_push (x11_display); + buttons = g_new (guchar, buttons_capacity); +@@ -240,17 +240,39 @@ change_synaptics_tap_left_handed (ClutterInputDevice *device, + + if (x11_display && meta_x11_error_trap_pop_with_return (x11_display)) + { +- g_warning ("Could not set synaptics touchpad left-handed for %s", ++ g_warning ("Could not set left-handed for %s", + clutter_input_device_get_device_name (device)); + } + +- out: + XCloseDevice (xdisplay, xdevice); + } + + static void +-change_synaptics_speed (ClutterInputDevice *device, +- gdouble speed) ++change_synaptics_tap_left_handed (ClutterInputDevice *device, ++ gboolean tap_enabled, ++ gboolean left_handed) ++{ ++ guchar *tap_action; ++ ++ tap_action = get_property (device, "Synaptics Tap Action", ++ XA_INTEGER, 8, 7); ++ if (!tap_action) ++ return; ++ ++ tap_action[4] = tap_enabled ? (left_handed ? 3 : 1) : 0; ++ tap_action[5] = tap_enabled ? (left_handed ? 1 : 3) : 0; ++ tap_action[6] = tap_enabled ? 2 : 0; ++ ++ change_property (device, "Synaptics Tap Action", ++ XA_INTEGER, 8, tap_action, 7); ++ meta_XFree (tap_action); ++ ++ change_x_device_left_handed (device, left_handed); ++} ++ ++static void ++change_x_device_speed (ClutterInputDevice *device, ++ gdouble speed) + { + MetaDisplay *display = meta_get_display (); + MetaX11Display *x11_display = display ? display->x11_display : NULL; +@@ -349,6 +371,23 @@ change_synaptics_speed (ClutterInputDevice *device, + XCloseDevice (xdisplay, xdevice); + } + ++static void ++change_x_device_scroll_button (ClutterInputDevice *device, ++ guint button) ++{ ++ guchar value; ++ ++ value = button > 0 ? 1 : 0; ++ change_property (device, "Evdev Wheel Emulation", ++ XA_INTEGER, 8, &value, 1); ++ if (button > 0) ++ { ++ value = button; ++ change_property (device, "Evdev Wheel Emulation Button", ++ XA_INTEGER, 8, &value, 1); ++ } ++} ++ + /* Ensure that syndaemon dies together with us, to avoid running several of + * them */ + static void +@@ -517,9 +556,10 @@ meta_input_settings_x11_set_speed (MetaInputSettings *settings, + Display *xdisplay = meta_backend_x11_get_xdisplay (META_BACKEND_X11 (backend)); + gfloat value = speed; + +- if (is_device_synaptics (device)) ++ if (is_device_synaptics (device) || ++ !is_device_libinput (device)) + { +- change_synaptics_speed (device, speed); ++ change_x_device_speed (device, speed); + return; + } + +@@ -561,6 +601,11 @@ meta_input_settings_x11_set_left_handed (MetaInputSettings *settings, + g_object_unref (settings); + return; + } ++ else if (!is_device_libinput (device) && device_type != CLUTTER_PAD_DEVICE) ++ { ++ change_x_device_left_handed (device, enabled); ++ return; ++ } + + change_property (device, "libinput Left Handed Enabled", + XA_INTEGER, 8, &value, 1); +@@ -778,7 +823,14 @@ meta_input_settings_x11_set_scroll_button (MetaInputSettings *settings, + { + gchar lock = button_lock; + ++ if (!is_device_libinput (device)) ++ { ++ change_x_device_scroll_button (device, button); ++ return; ++ } ++ + change_scroll_method (device, SCROLL_METHOD_FIELD_BUTTON, button != 0); ++ + change_property (device, "libinput Button Scrolling Button", + XA_CARDINAL, 32, &button, 1); + change_property (device, "libinput Button Scrolling Button Lock Enabled", +-- +2.31.1 + diff --git a/SOURCES/monitor-config-policy.patch b/SOURCES/monitor-config-policy.patch new file mode 100644 index 0000000..55c367a --- /dev/null +++ b/SOURCES/monitor-config-policy.patch @@ -0,0 +1,2343 @@ +From f1c80e0962c36b3e7e3d304ec7abec0c69f5523b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Fri, 14 Jan 2022 22:11:17 +0100 +Subject: [PATCH 1/9] test/utils: Add helper to set custom monitors config + +Make the existing implementation a wrapper to avoid changing monitor +config tests. + +Part-of: + +Cherry-picked from 57d1d82ead6392a104a9e9d6c7f1f4f14ad54e48 +--- + src/tests/monitor-test-utils.c | 18 +----------------- + src/tests/test-utils.c | 23 +++++++++++++++++++++++ + src/tests/test-utils.h | 3 +++ + 3 files changed, 27 insertions(+), 17 deletions(-) + +diff --git a/src/tests/monitor-test-utils.c b/src/tests/monitor-test-utils.c +index 705201810abc..98958a5042ee 100644 +--- a/src/tests/monitor-test-utils.c ++++ b/src/tests/monitor-test-utils.c +@@ -39,23 +39,7 @@ test_get_gpu (void) + void + set_custom_monitor_config (const char *filename) + { +- MetaBackend *backend = meta_get_backend (); +- MetaMonitorManager *monitor_manager = +- meta_backend_get_monitor_manager (backend); +- MetaMonitorConfigManager *config_manager = monitor_manager->config_manager; +- MetaMonitorConfigStore *config_store; +- GError *error = NULL; +- const char *path; +- +- g_assert_nonnull (config_manager); +- +- config_store = meta_monitor_config_manager_get_store (config_manager); +- +- path = g_test_get_filename (G_TEST_DIST, "tests", "monitor-configs", +- filename, NULL); +- if (!meta_monitor_config_store_set_custom (config_store, path, NULL, +- &error)) +- g_error ("Failed to set custom config: %s", error->message); ++ meta_set_custom_monitor_config (meta_get_backend (), filename); + } + + char * +diff --git a/src/tests/test-utils.c b/src/tests/test-utils.c +index ca332a0b918e..bf326ef27105 100644 +--- a/src/tests/test-utils.c ++++ b/src/tests/test-utils.c +@@ -24,6 +24,7 @@ + #include + #include + ++#include "backends/meta-monitor-config-store.h" + #include "core/display-private.h" + #include "core/window-private.h" + #include "wayland/meta-wayland.h" +@@ -575,3 +576,25 @@ test_wait_for_x11_display (void) + + g_assert_nonnull (display->x11_display); + } ++ ++void ++meta_set_custom_monitor_config (MetaBackend *backend, ++ const char *filename) ++{ ++ MetaMonitorManager *monitor_manager = ++ meta_backend_get_monitor_manager (backend); ++ MetaMonitorConfigManager *config_manager = monitor_manager->config_manager; ++ MetaMonitorConfigStore *config_store; ++ GError *error = NULL; ++ const char *path; ++ ++ g_assert_nonnull (config_manager); ++ ++ config_store = meta_monitor_config_manager_get_store (config_manager); ++ ++ path = g_test_get_filename (G_TEST_DIST, "tests", "monitor-configs", ++ filename, NULL); ++ if (!meta_monitor_config_store_set_custom (config_store, path, NULL, ++ &error)) ++ g_error ("Failed to set custom config: %s", error->message); ++} +diff --git a/src/tests/test-utils.h b/src/tests/test-utils.h +index 1710b98e0e80..c8a0d16aebec 100644 +--- a/src/tests/test-utils.h ++++ b/src/tests/test-utils.h +@@ -86,4 +86,7 @@ const char * test_get_plugin_name (void); + + void test_wait_for_x11_display (void); + ++void meta_set_custom_monitor_config (MetaBackend *backend, ++ const char *filename); ++ + #endif /* TEST_UTILS_H */ +-- +2.33.1 + + +From 9fdf146e7bf04b71dafd67388345b694e8bfac77 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Fri, 14 Jan 2022 22:12:36 +0100 +Subject: [PATCH 2/9] tests/utils: Add meta_wait_for_paint() helper + +This function queues a full stage redraw, then waits for every view to +receive the "presented" signal before returning. + +Part-of: +(cherry picked from commit d84f7971e476a1e2d727310d9a33ac4080137f58) +--- + src/tests/test-utils.c | 27 +++++++++++++++++++++++++++ + src/tests/test-utils.h | 2 ++ + 2 files changed, 29 insertions(+) + +diff --git a/src/tests/test-utils.c b/src/tests/test-utils.c +index bf326ef27105..bee6aa102c0e 100644 +--- a/src/tests/test-utils.c ++++ b/src/tests/test-utils.c +@@ -598,3 +598,30 @@ meta_set_custom_monitor_config (MetaBackend *backend, + &error)) + g_error ("Failed to set custom config: %s", error->message); + } ++ ++static void ++on_view_presented (ClutterStage *stage, ++ ClutterStageView *view, ++ ClutterFrameInfo *frame_info, ++ GList **presented_views) ++{ ++ *presented_views = g_list_remove (*presented_views, view); ++} ++ ++void ++meta_wait_for_paint (MetaBackend *backend) ++{ ++ ClutterActor *stage = meta_backend_get_stage (backend); ++ MetaRenderer *renderer = meta_backend_get_renderer (backend); ++ GList *views; ++ gulong handler_id; ++ ++ clutter_actor_queue_redraw (stage); ++ ++ views = g_list_copy (meta_renderer_get_views (renderer)); ++ handler_id = g_signal_connect (stage, "presented", ++ G_CALLBACK (on_view_presented), &views); ++ while (views) ++ g_main_context_iteration (NULL, TRUE); ++ g_signal_handler_disconnect (stage, handler_id); ++} +diff --git a/src/tests/test-utils.h b/src/tests/test-utils.h +index c8a0d16aebec..4b6aa34e8998 100644 +--- a/src/tests/test-utils.h ++++ b/src/tests/test-utils.h +@@ -89,4 +89,6 @@ void test_wait_for_x11_display (void); + void meta_set_custom_monitor_config (MetaBackend *backend, + const char *filename); + ++void meta_wait_for_paint (MetaBackend *backend); ++ + #endif /* TEST_UTILS_H */ +-- +2.33.1 + + +From 570c234f6e4cab567cd329d45347565100d5494d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Thu, 30 Sep 2021 08:59:03 +0200 +Subject: [PATCH 3/9] monitor-config-store: Make parsing a bit more forgiving + +Allow unknown XML elements inside . This makes extending in +the future easier. + +(cherry picked from commit 3cd666c657fa716f06dee69df59356b53b6c5d72) +--- + src/backends/meta-monitor-config-store.c | 54 +++++++++++++++--- + .../monitor-configs/unknown-elements.xml | 31 ++++++++++ + src/tests/monitor-store-unit-tests.c | 56 +++++++++++++++++++ + 3 files changed, 133 insertions(+), 8 deletions(-) + create mode 100644 src/tests/monitor-configs/unknown-elements.xml + +diff --git a/src/backends/meta-monitor-config-store.c b/src/backends/meta-monitor-config-store.c +index 4dd357a15164..3c69157eae40 100644 +--- a/src/backends/meta-monitor-config-store.c ++++ b/src/backends/meta-monitor-config-store.c +@@ -136,6 +136,7 @@ G_DEFINE_QUARK (meta-monitor-config-store-error-quark, + typedef enum + { + STATE_INITIAL, ++ STATE_UNKNOWN, + STATE_MONITORS, + STATE_CONFIGURATION, + STATE_MIGRATED, +@@ -180,12 +181,28 @@ typedef struct + MetaLogicalMonitorConfig *current_logical_monitor_config; + GList *current_disabled_monitor_specs; + ++ ParserState unknown_state_root; ++ int unknown_level; ++ + MetaMonitorsConfigFlag extra_config_flags; + } ConfigParser; + + G_DEFINE_TYPE (MetaMonitorConfigStore, meta_monitor_config_store, + G_TYPE_OBJECT) + ++static void ++enter_unknown_element (ConfigParser *parser, ++ const char *element_name, ++ const char *root_element_name, ++ ParserState root_state) ++{ ++ parser->state = STATE_UNKNOWN; ++ parser->unknown_level = 1; ++ parser->unknown_state_root = root_state; ++ g_warning ("Unknown element <%s> under <%s>, ignoring", ++ element_name, root_element_name); ++} ++ + static void + handle_start_element (GMarkupParseContext *context, + const char *element_name, +@@ -242,8 +259,8 @@ handle_start_element (GMarkupParseContext *context, + { + if (!g_str_equal (element_name, "configuration")) + { +- g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, +- "Invalid toplevel element '%s'", element_name); ++ enter_unknown_element (parser, element_name, ++ "monitors", STATE_MONITORS); + return; + } + +@@ -253,6 +270,13 @@ handle_start_element (GMarkupParseContext *context, + return; + } + ++ case STATE_UNKNOWN: ++ { ++ parser->unknown_level++; ++ ++ return; ++ } ++ + case STATE_CONFIGURATION: + { + if (g_str_equal (element_name, "logicalmonitor")) +@@ -274,9 +298,8 @@ handle_start_element (GMarkupParseContext *context, + } + else + { +- g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, +- "Invalid configuration element '%s'", element_name); +- return; ++ enter_unknown_element (parser, element_name, ++ "configuration", STATE_CONFIGURATION); + } + + return; +@@ -323,9 +346,8 @@ handle_start_element (GMarkupParseContext *context, + } + else + { +- g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, +- "Invalid monitor logicalmonitor element '%s'", element_name); +- return; ++ enter_unknown_element (parser, element_name, ++ "logicalmonitor", STATE_LOGICAL_MONITOR); + } + + return; +@@ -793,6 +815,18 @@ handle_end_element (GMarkupParseContext *context, + return; + } + ++ case STATE_UNKNOWN: ++ { ++ parser->unknown_level--; ++ if (parser->unknown_level == 0) ++ { ++ g_assert (parser->unknown_state_root >= 0); ++ parser->state = parser->unknown_state_root; ++ parser->unknown_state_root = -1; ++ } ++ return; ++ } ++ + case STATE_MONITORS: + { + g_assert (g_str_equal (element_name, "monitors")); +@@ -912,6 +946,9 @@ handle_text (GMarkupParseContext *context, + + switch (parser->state) + { ++ case STATE_UNKNOWN: ++ return; ++ + case STATE_INITIAL: + case STATE_MONITORS: + case STATE_CONFIGURATION: +@@ -1099,6 +1136,7 @@ read_config_file (MetaMonitorConfigStore *config_store, + .state = STATE_INITIAL, + .config_store = config_store, + .extra_config_flags = extra_config_flags, ++ .unknown_state_root = -1, + }; + + parse_context = g_markup_parse_context_new (&config_parser, +diff --git a/src/tests/monitor-configs/unknown-elements.xml b/src/tests/monitor-configs/unknown-elements.xml +new file mode 100644 +index 000000000000..f81be95dd9df +--- /dev/null ++++ b/src/tests/monitor-configs/unknown-elements.xml +@@ -0,0 +1,31 @@ ++ ++ ++ text ++ ++ ++ ++ text ++ ++ ++ ++ text ++ ++ 0 ++ 0 ++ yes ++ ++ ++ DP-1 ++ MetaProduct's Inc. ++ MetaMonitor ++ 0x123456 ++ ++ ++ 1920 ++ 1080 ++ 60.000495910644531 ++ ++ ++ ++ ++ +diff --git a/src/tests/monitor-store-unit-tests.c b/src/tests/monitor-store-unit-tests.c +index b9d5622b7ae3..64618174f4b3 100644 +--- a/src/tests/monitor-store-unit-tests.c ++++ b/src/tests/monitor-store-unit-tests.c +@@ -836,6 +836,60 @@ meta_test_monitor_store_interlaced (void) + check_monitor_store_configurations (&expect); + } + ++static void ++meta_test_monitor_store_unknown_elements (void) ++{ ++ MonitorStoreTestExpect expect = { ++ .configurations = { ++ { ++ .logical_monitors = { ++ { ++ .layout = { ++ .x = 0, ++ .y = 0, ++ .width = 1920, ++ .height = 1080 ++ }, ++ .scale = 1, ++ .is_primary = TRUE, ++ .is_presentation = FALSE, ++ .monitors = { ++ { ++ .connector = "DP-1", ++ .vendor = "MetaProduct's Inc.", ++ .product = "MetaMonitor", ++ .serial = "0x123456", ++ .mode = { ++ .width = 1920, ++ .height = 1080, ++ .refresh_rate = 60.000495910644531 ++ } ++ } ++ }, ++ .n_monitors = 1, ++ } ++ }, ++ .n_logical_monitors = 1 ++ } ++ }, ++ .n_configurations = 1 ++ }; ++ ++ g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, ++ "Unknown element " ++ "under , ignoring"); ++ g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, ++ "Unknown element " ++ "under , ignoring"); ++ g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, ++ "Unknown element " ++ "under , ignoring"); ++ set_custom_monitor_config ("unknown-elements.xml"); ++ g_test_assert_expected_messages (); ++ ++ check_monitor_store_configurations (&expect); ++} ++ + void + init_monitor_store_tests (void) + { +@@ -861,4 +915,6 @@ init_monitor_store_tests (void) + meta_test_monitor_store_second_rotated); + g_test_add_func ("/backends/monitor-store/interlaced", + meta_test_monitor_store_interlaced); ++ g_test_add_func ("/backends/monitor-store/unknown-elements", ++ meta_test_monitor_store_unknown_elements); + } +-- +2.33.1 + + +From 48a259017f0f59abb80fa6fe7c9d6b864f129267 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Fri, 24 Sep 2021 16:29:47 +0200 +Subject: [PATCH 4/9] monitor-config-store: Fix incorrect string comparison + with empty string + +strncmp() always return 0 if the passed length is 0. What this means is +that whatever the first string check happens to be, if the parsed XML +cdata was empty (e.g. if we got ), the first +condition would evaluate to true, which is rather unexpected. + +Fix this by making sure the string length is correct first. Also move it +into a helper so we don't need to repeat the same strlen() check every +time. + +(cherry picked from commit f798e49502313dd3e7dd67143513a7a6a91b49f8) +--- + src/backends/meta-monitor-config-store.c | 25 +++++++++++++++++------- + 1 file changed, 18 insertions(+), 7 deletions(-) + +diff --git a/src/backends/meta-monitor-config-store.c b/src/backends/meta-monitor-config-store.c +index 3c69157eae40..d2572fa06ab0 100644 +--- a/src/backends/meta-monitor-config-store.c ++++ b/src/backends/meta-monitor-config-store.c +@@ -190,6 +190,17 @@ typedef struct + G_DEFINE_TYPE (MetaMonitorConfigStore, meta_monitor_config_store, + G_TYPE_OBJECT) + ++static gboolean ++text_equals (const char *text, ++ int len, ++ const char *expect) ++{ ++ if (strlen (expect) != len) ++ return FALSE; ++ ++ return strncmp (text, expect, len) == 0; ++} ++ + static void + enter_unknown_element (ConfigParser *parser, + const char *element_name, +@@ -904,12 +915,12 @@ read_bool (const char *text, + gboolean *out_value, + GError **error) + { +- if (strncmp (text, "no", text_len) == 0) ++ if (text_equals (text, text_len, "no")) + { + *out_value = FALSE; + return TRUE; + } +- else if (strncmp (text, "yes", text_len) == 0) ++ else if (text_equals (text, text_len, "yes")) + { + *out_value = TRUE; + return TRUE; +@@ -1039,13 +1050,13 @@ handle_text (GMarkupParseContext *context, + + case STATE_TRANSFORM_ROTATION: + { +- if (strncmp (text, "normal", text_len) == 0) ++ if (text_equals (text, text_len, "normal")) + parser->current_transform = META_MONITOR_TRANSFORM_NORMAL; +- else if (strncmp (text, "left", text_len) == 0) ++ else if (text_equals (text, text_len, "left")) + parser->current_transform = META_MONITOR_TRANSFORM_90; +- else if (strncmp (text, "upside_down", text_len) == 0) ++ else if (text_equals (text, text_len, "upside_down")) + parser->current_transform = META_MONITOR_TRANSFORM_180; +- else if (strncmp (text, "right", text_len) == 0) ++ else if (text_equals (text, text_len, "right")) + parser->current_transform = META_MONITOR_TRANSFORM_270; + else + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, +@@ -1088,7 +1099,7 @@ handle_text (GMarkupParseContext *context, + + case STATE_MONITOR_MODE_FLAG: + { +- if (strncmp (text, "interlace", text_len) == 0) ++ if (text_equals (text, text_len, "interlace")) + { + parser->current_monitor_mode_spec->flags |= + META_CRTC_MODE_FLAG_INTERLACE; +-- +2.33.1 + + +From a67835d2a9f41194d29089e8b4deb0d2af05203a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Mon, 17 Jan 2022 11:45:53 +0100 +Subject: [PATCH 5/9] monitor-config-store: Add way to define config store + loading policy + +This adds a way to define a way, at the system level, to define a policy +of how monitor configuration files are loaded. + +The intended use case is to e.g. either prefer system level monitor +configurations before user levels, or only allow system level +configurations. + +Examples: + +Prefer system over user level configurations: + + + + + system + user + + + + ... + + + +Only allow system level configurations: + + + + + system + + + + ... + + + +(cherry picked from commit b747884c1eaf309bb2d9395a655c85c968bd1829) +--- + src/backends/meta-backend-types.h | 2 + + src/backends/meta-monitor-config-manager.h | 4 +- + src/backends/meta-monitor-config-store.c | 421 ++++++++++++++---- + src/backends/meta-monitor-config-store.h | 21 +- + .../monitor-config-migration-unit-tests.c | 1 + + src/tests/monitor-configs/policy.xml | 27 ++ + src/tests/monitor-store-unit-tests.c | 33 ++ + src/tests/monitor-test-utils.c | 16 +- + src/tests/monitor-test-utils.h | 2 + + src/tests/monitor-unit-tests.c | 3 + + src/tests/test-utils.c | 8 +- + src/tests/test-utils.h | 6 +- + 12 files changed, 450 insertions(+), 94 deletions(-) + create mode 100644 src/tests/monitor-configs/policy.xml + +diff --git a/src/backends/meta-backend-types.h b/src/backends/meta-backend-types.h +index eae62c02f244..5133ccb6131d 100644 +--- a/src/backends/meta-backend-types.h ++++ b/src/backends/meta-backend-types.h +@@ -29,6 +29,8 @@ typedef struct _MetaMonitorConfigManager MetaMonitorConfigManager; + typedef struct _MetaMonitorConfigStore MetaMonitorConfigStore; + typedef struct _MetaMonitorsConfig MetaMonitorsConfig; + ++typedef enum _MetaMonitorsConfigFlag MetaMonitorsConfigFlag; ++ + typedef struct _MetaMonitor MetaMonitor; + typedef struct _MetaMonitorNormal MetaMonitorNormal; + typedef struct _MetaMonitorTiled MetaMonitorTiled; +diff --git a/src/backends/meta-monitor-config-manager.h b/src/backends/meta-monitor-config-manager.h +index 641ed1bc1afb..a35b3e2e1f95 100644 +--- a/src/backends/meta-monitor-config-manager.h ++++ b/src/backends/meta-monitor-config-manager.h +@@ -51,12 +51,12 @@ typedef struct _MetaMonitorsConfigKey + GList *monitor_specs; + } MetaMonitorsConfigKey; + +-typedef enum _MetaMonitorsConfigFlag ++enum _MetaMonitorsConfigFlag + { + META_MONITORS_CONFIG_FLAG_NONE = 0, + META_MONITORS_CONFIG_FLAG_MIGRATED = (1 << 0), + META_MONITORS_CONFIG_FLAG_SYSTEM_CONFIG = (1 << 1), +-} MetaMonitorsConfigFlag; ++}; + + struct _MetaMonitorsConfig + { +diff --git a/src/backends/meta-monitor-config-store.c b/src/backends/meta-monitor-config-store.c +index d2572fa06ab0..93a494c79b80 100644 +--- a/src/backends/meta-monitor-config-store.c ++++ b/src/backends/meta-monitor-config-store.c +@@ -120,6 +120,9 @@ struct _MetaMonitorConfigStore + GFile *user_file; + GFile *custom_read_file; + GFile *custom_write_file; ++ ++ gboolean has_stores_policy; ++ GList *stores_policy; + }; + + #define META_MONITOR_CONFIG_STORE_ERROR (meta_monitor_config_store_error_quark ()) +@@ -162,12 +165,18 @@ typedef enum + STATE_MONITOR_MODE_FLAG, + STATE_MONITOR_UNDERSCANNING, + STATE_DISABLED, ++ STATE_POLICY, ++ STATE_STORES, ++ STATE_STORE, + } ParserState; + + typedef struct + { + ParserState state; + MetaMonitorConfigStore *config_store; ++ GFile *file; ++ ++ GHashTable *pending_configs; + + ParserState monitor_spec_parent_state; + +@@ -180,6 +189,10 @@ typedef struct + MetaMonitorConfig *current_monitor_config; + MetaLogicalMonitorConfig *current_logical_monitor_config; + GList *current_disabled_monitor_specs; ++ gboolean seen_policy; ++ gboolean seen_stores; ++ MetaConfigStore pending_store; ++ GList *stores; + + ParserState unknown_state_root; + int unknown_level; +@@ -268,16 +281,31 @@ handle_start_element (GMarkupParseContext *context, + + case STATE_MONITORS: + { +- if (!g_str_equal (element_name, "configuration")) ++ if (g_str_equal (element_name, "configuration")) ++ { ++ parser->state = STATE_CONFIGURATION; ++ parser->current_was_migrated = FALSE; ++ } ++ else if (g_str_equal (element_name, "policy")) ++ { ++ if (parser->seen_policy) ++ { ++ g_set_error (error, ++ G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, ++ "Multiple policy definitions"); ++ return; ++ } ++ ++ parser->seen_policy = TRUE; ++ parser->state = STATE_POLICY; ++ } ++ else + { + enter_unknown_element (parser, element_name, + "monitors", STATE_MONITORS); + return; + } + +- parser->state = STATE_CONFIGURATION; +- parser->current_was_migrated = FALSE; +- + return; + } + +@@ -523,6 +551,59 @@ handle_start_element (GMarkupParseContext *context, + + return; + } ++ ++ case STATE_POLICY: ++ { ++ if (!(parser->extra_config_flags & ++ META_MONITORS_CONFIG_FLAG_SYSTEM_CONFIG)) ++ { ++ g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, ++ "Policy can only be defined in system level configurations"); ++ return; ++ } ++ ++ if (g_str_equal (element_name, "stores")) ++ { ++ if (parser->seen_stores) ++ { ++ g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, ++ "Multiple stores elements under policy"); ++ return; ++ } ++ ++ parser->seen_stores = TRUE; ++ parser->state = STATE_STORES; ++ } ++ else ++ { ++ enter_unknown_element (parser, element_name, ++ "policy", STATE_POLICY); ++ } ++ ++ return; ++ } ++ ++ case STATE_STORES: ++ { ++ if (g_str_equal (element_name, "store")) ++ { ++ parser->state = STATE_STORE; ++ } ++ else ++ { ++ enter_unknown_element (parser, element_name, ++ "stores", STATE_STORES); ++ } ++ ++ return; ++ } ++ ++ case STATE_STORE: ++ { ++ g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, ++ "Invalid store sub element '%s'", element_name); ++ return; ++ } + } + } + +@@ -819,13 +900,65 @@ handle_end_element (GMarkupParseContext *context, + return; + } + +- g_hash_table_replace (parser->config_store->configs, ++ g_hash_table_replace (parser->pending_configs, + config->key, config); + + parser->state = STATE_MONITORS; + return; + } + ++ case STATE_STORE: ++ g_assert (g_str_equal (element_name, "store")); ++ ++ if (parser->pending_store == -1) ++ { ++ g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, ++ "Got an empty store"); ++ return; ++ } ++ ++ if (g_list_find (parser->stores, ++ GINT_TO_POINTER (parser->pending_store))) ++ { ++ g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, ++ "Multiple identical stores in policy"); ++ return; ++ } ++ ++ parser->stores = ++ g_list_append (parser->stores, ++ GINT_TO_POINTER (parser->pending_store)); ++ parser->pending_store = -1; ++ ++ parser->state = STATE_STORES; ++ return; ++ ++ case STATE_STORES: ++ g_assert (g_str_equal (element_name, "stores")); ++ ++ if (parser->config_store->has_stores_policy) ++ { ++ g_warning ("Ignoring stores policy from '%s', " ++ "it has already been configured", ++ g_file_peek_path (parser->file)); ++ g_clear_pointer (&parser->stores, g_list_free); ++ } ++ else ++ { ++ parser->config_store->stores_policy = ++ g_steal_pointer (&parser->stores); ++ parser->config_store->has_stores_policy = TRUE; ++ } ++ ++ parser->state = STATE_POLICY; ++ return; ++ ++ case STATE_POLICY: ++ g_assert (g_str_equal (element_name, "policy")); ++ ++ parser->state = STATE_MONITORS; ++ return; ++ + case STATE_UNKNOWN: + { + parser->unknown_level--; +@@ -970,6 +1103,8 @@ handle_text (GMarkupParseContext *context, + case STATE_MONITOR_MODE: + case STATE_TRANSFORM: + case STATE_DISABLED: ++ case STATE_POLICY: ++ case STATE_STORES: + { + if (!is_all_whitespace (text, text_len)) + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, +@@ -1120,6 +1255,36 @@ handle_text (GMarkupParseContext *context, + error); + return; + } ++ ++ case STATE_STORE: ++ { ++ MetaConfigStore store; ++ ++ if (parser->pending_store != -1) ++ { ++ g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, ++ "Multiple store strings"); ++ return; ++ } ++ ++ if (text_equals (text, text_len, "system")) ++ { ++ store = META_CONFIG_STORE_SYSTEM; ++ } ++ else if (text_equals (text, text_len, "user")) ++ { ++ store = META_CONFIG_STORE_USER; ++ } ++ else ++ { ++ g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, ++ "Invalid store %.*s", (int) text_len, text); ++ return; ++ } ++ ++ parser->pending_store = store; ++ return; ++ } + } + } + +@@ -1133,6 +1298,7 @@ static gboolean + read_config_file (MetaMonitorConfigStore *config_store, + GFile *file, + MetaMonitorsConfigFlag extra_config_flags, ++ GHashTable **out_configs, + GError **error) + { + char *buffer; +@@ -1145,9 +1311,15 @@ read_config_file (MetaMonitorConfigStore *config_store, + + parser = (ConfigParser) { + .state = STATE_INITIAL, ++ .file = file, + .config_store = config_store, ++ .pending_configs = g_hash_table_new_full (meta_monitors_config_key_hash, ++ meta_monitors_config_key_equal, ++ NULL, ++ g_object_unref), + .extra_config_flags = extra_config_flags, + .unknown_state_root = -1, ++ .pending_store = -1, + }; + + parse_context = g_markup_parse_context_new (&config_parser, +@@ -1165,9 +1337,13 @@ read_config_file (MetaMonitorConfigStore *config_store, + meta_monitor_config_free); + g_clear_pointer (&parser.current_logical_monitor_config, + meta_logical_monitor_config_free); ++ g_list_free (parser.stores); ++ g_hash_table_unref (parser.pending_configs); + return FALSE; + } + ++ *out_configs = g_steal_pointer (&parser.pending_configs); ++ + g_markup_parse_context_free (parse_context); + g_free (buffer); + +@@ -1526,23 +1702,34 @@ meta_monitor_config_store_remove (MetaMonitorConfigStore *config_store, + } + + gboolean +-meta_monitor_config_store_set_custom (MetaMonitorConfigStore *config_store, +- const char *read_path, +- const char *write_path, +- GError **error) ++meta_monitor_config_store_set_custom (MetaMonitorConfigStore *config_store, ++ const char *read_path, ++ const char *write_path, ++ MetaMonitorsConfigFlag config_flags, ++ GError **error) + { ++ GHashTable *new_configs = NULL; ++ + g_clear_object (&config_store->custom_read_file); + g_clear_object (&config_store->custom_write_file); +- g_hash_table_remove_all (config_store->configs); + + config_store->custom_read_file = g_file_new_for_path (read_path); + if (write_path) + config_store->custom_write_file = g_file_new_for_path (write_path); + +- return read_config_file (config_store, +- config_store->custom_read_file, +- META_MONITORS_CONFIG_FLAG_NONE, +- error); ++ g_clear_pointer (&config_store->stores_policy, g_list_free); ++ config_store->has_stores_policy = FALSE; ++ ++ if (!read_config_file (config_store, ++ config_store->custom_read_file, ++ config_flags, ++ &new_configs, ++ error)) ++ return FALSE; ++ ++ g_clear_pointer (&config_store->configs, g_hash_table_unref); ++ config_store->configs = g_steal_pointer (&new_configs); ++ return TRUE; + } + + int +@@ -1551,6 +1738,12 @@ meta_monitor_config_store_get_config_count (MetaMonitorConfigStore *config_store + return (int) g_hash_table_size (config_store->configs); + } + ++GList * ++meta_monitor_config_store_get_stores_policy (MetaMonitorConfigStore *config_store) ++{ ++ return config_store->stores_policy; ++} ++ + MetaMonitorManager * + meta_monitor_config_store_get_monitor_manager (MetaMonitorConfigStore *config_store) + { +@@ -1569,75 +1762,8 @@ static void + meta_monitor_config_store_constructed (GObject *object) + { + MetaMonitorConfigStore *config_store = META_MONITOR_CONFIG_STORE (object); +- const char * const *system_dirs; +- char *user_file_path; +- GError *error = NULL; +- +- for (system_dirs = g_get_system_config_dirs (); +- system_dirs && *system_dirs; +- system_dirs++) +- { +- g_autofree char *system_file_path = NULL; +- +- system_file_path = g_build_filename (*system_dirs, "monitors.xml", NULL); +- if (g_file_test (system_file_path, G_FILE_TEST_EXISTS)) +- { +- g_autoptr (GFile) system_file = NULL; +- +- system_file = g_file_new_for_path (system_file_path); +- if (!read_config_file (config_store, +- system_file, +- META_MONITORS_CONFIG_FLAG_SYSTEM_CONFIG, +- &error)) +- { +- if (g_error_matches (error, +- META_MONITOR_CONFIG_STORE_ERROR, +- META_MONITOR_CONFIG_STORE_ERROR_NEEDS_MIGRATION)) +- g_warning ("System monitor configuration file (%s) is " +- "incompatible; ask your administrator to migrate " +- "the system monitor configuration.", +- system_file_path); +- else +- g_warning ("Failed to read monitors config file '%s': %s", +- system_file_path, error->message); +- g_clear_error (&error); +- } +- } +- } + +- user_file_path = g_build_filename (g_get_user_config_dir (), +- "monitors.xml", +- NULL); +- config_store->user_file = g_file_new_for_path (user_file_path); +- +- if (g_file_test (user_file_path, G_FILE_TEST_EXISTS)) +- { +- if (!read_config_file (config_store, +- config_store->user_file, +- META_MONITORS_CONFIG_FLAG_NONE, +- &error)) +- { +- if (error->domain == META_MONITOR_CONFIG_STORE_ERROR && +- error->code == META_MONITOR_CONFIG_STORE_ERROR_NEEDS_MIGRATION) +- { +- g_clear_error (&error); +- if (!meta_migrate_old_user_monitors_config (config_store, &error)) +- { +- g_warning ("Failed to migrate old monitors config file: %s", +- error->message); +- g_error_free (error); +- } +- } +- else +- { +- g_warning ("Failed to read monitors config file '%s': %s", +- user_file_path, error->message); +- g_error_free (error); +- } +- } +- } +- +- g_free (user_file_path); ++ meta_monitor_config_store_reset (config_store); + + G_OBJECT_CLASS (meta_monitor_config_store_parent_class)->constructed (object); + } +@@ -1660,6 +1786,7 @@ meta_monitor_config_store_dispose (GObject *object) + g_clear_object (&config_store->user_file); + g_clear_object (&config_store->custom_read_file); + g_clear_object (&config_store->custom_write_file); ++ g_clear_pointer (&config_store->stores_policy, g_list_free); + + G_OBJECT_CLASS (meta_monitor_config_store_parent_class)->dispose (object); + } +@@ -1730,3 +1857,133 @@ meta_monitor_config_store_class_init (MetaMonitorConfigStoreClass *klass) + + g_object_class_install_properties (object_class, PROP_LAST, obj_props); + } ++ ++static void ++replace_configs (MetaMonitorConfigStore *config_store, ++ GHashTable *configs) ++{ ++ GHashTableIter iter; ++ MetaMonitorsConfigKey *key; ++ MetaMonitorsConfig *config; ++ ++ g_hash_table_iter_init (&iter, configs); ++ while (g_hash_table_iter_next (&iter, ++ (gpointer *) &key, ++ (gpointer *) &config)) ++ { ++ g_hash_table_iter_steal (&iter); ++ g_hash_table_replace (config_store->configs, key, config); ++ } ++} ++ ++void ++meta_monitor_config_store_reset (MetaMonitorConfigStore *config_store) ++{ ++ g_autoptr (GHashTable) system_configs = NULL; ++ g_autoptr (GHashTable) user_configs = NULL; ++ const char * const *system_dirs; ++ char *user_file_path; ++ GError *error = NULL; ++ ++ g_clear_object (&config_store->user_file); ++ g_clear_object (&config_store->custom_read_file); ++ g_clear_object (&config_store->custom_write_file); ++ g_hash_table_remove_all (config_store->configs); ++ ++ for (system_dirs = g_get_system_config_dirs (); ++ system_dirs && *system_dirs; ++ system_dirs++) ++ { ++ g_autofree char *system_file_path = NULL; ++ ++ system_file_path = g_build_filename (*system_dirs, "monitors.xml", NULL); ++ if (g_file_test (system_file_path, G_FILE_TEST_EXISTS)) ++ { ++ g_autoptr (GFile) system_file = NULL; ++ ++ system_file = g_file_new_for_path (system_file_path); ++ if (!read_config_file (config_store, ++ system_file, ++ META_MONITORS_CONFIG_FLAG_SYSTEM_CONFIG, ++ &system_configs, ++ &error)) ++ { ++ if (g_error_matches (error, ++ META_MONITOR_CONFIG_STORE_ERROR, ++ META_MONITOR_CONFIG_STORE_ERROR_NEEDS_MIGRATION)) ++ g_warning ("System monitor configuration file (%s) is " ++ "incompatible; ask your administrator to migrate " ++ "the system monitor configuration.", ++ system_file_path); ++ else ++ g_warning ("Failed to read monitors config file '%s': %s", ++ system_file_path, error->message); ++ g_clear_error (&error); ++ } ++ } ++ } ++ ++ user_file_path = g_build_filename (g_get_user_config_dir (), ++ "monitors.xml", ++ NULL); ++ config_store->user_file = g_file_new_for_path (user_file_path); ++ ++ if (g_file_test (user_file_path, G_FILE_TEST_EXISTS)) ++ { ++ if (!read_config_file (config_store, ++ config_store->user_file, ++ META_MONITORS_CONFIG_FLAG_NONE, ++ &user_configs, ++ &error)) ++ { ++ if (error->domain == META_MONITOR_CONFIG_STORE_ERROR && ++ error->code == META_MONITOR_CONFIG_STORE_ERROR_NEEDS_MIGRATION) ++ { ++ g_clear_error (&error); ++ if (!meta_migrate_old_user_monitors_config (config_store, &error)) ++ { ++ g_warning ("Failed to migrate old monitors config file: %s", ++ error->message); ++ g_error_free (error); ++ } ++ } ++ else ++ { ++ g_warning ("Failed to read monitors config file '%s': %s", ++ user_file_path, error->message); ++ g_error_free (error); ++ } ++ } ++ } ++ ++ if (config_store->has_stores_policy) ++ { ++ GList *l; ++ ++ for (l = g_list_last (config_store->stores_policy); l; l = l->prev) ++ { ++ MetaConfigStore store = GPOINTER_TO_INT (l->data); ++ ++ switch (store) ++ { ++ case META_CONFIG_STORE_SYSTEM: ++ if (system_configs) ++ replace_configs (config_store, system_configs); ++ break; ++ case META_CONFIG_STORE_USER: ++ if (user_configs) ++ replace_configs (config_store, user_configs); ++ } ++ } ++ } ++ else ++ { ++ if (system_configs) ++ replace_configs (config_store, system_configs); ++ if (user_configs) ++ replace_configs (config_store, user_configs); ++ } ++ ++ ++ g_free (user_file_path); ++} +diff --git a/src/backends/meta-monitor-config-store.h b/src/backends/meta-monitor-config-store.h +index 92c24ecaa8b6..cb6759dca00f 100644 +--- a/src/backends/meta-monitor-config-store.h ++++ b/src/backends/meta-monitor-config-store.h +@@ -26,6 +26,12 @@ + + #include "backends/meta-monitor-config-manager.h" + ++typedef enum _MetaConfigStore ++{ ++ META_CONFIG_STORE_SYSTEM, ++ META_CONFIG_STORE_USER, ++} MetaConfigStore; ++ + #define META_TYPE_MONITOR_CONFIG_STORE (meta_monitor_config_store_get_type ()) + G_DECLARE_FINAL_TYPE (MetaMonitorConfigStore, meta_monitor_config_store, + META, MONITOR_CONFIG_STORE, GObject) +@@ -46,10 +52,14 @@ void meta_monitor_config_store_remove (MetaMonitorConfigStore *config_store, + MetaMonitorsConfig *config); + + META_EXPORT_TEST +-gboolean meta_monitor_config_store_set_custom (MetaMonitorConfigStore *config_store, +- const char *read_path, +- const char *write_path, +- GError **error); ++gboolean meta_monitor_config_store_set_custom (MetaMonitorConfigStore *config_store, ++ const char *read_path, ++ const char *write_path, ++ MetaMonitorsConfigFlag flags, ++ GError **error); ++ ++META_EXPORT_TEST ++GList * meta_monitor_config_store_get_stores_policy (MetaMonitorConfigStore *config_store); + + META_EXPORT_TEST + int meta_monitor_config_store_get_config_count (MetaMonitorConfigStore *config_store); +@@ -57,4 +67,7 @@ int meta_monitor_config_store_get_config_count (MetaMonitorConfigStore *config_s + META_EXPORT_TEST + MetaMonitorManager * meta_monitor_config_store_get_monitor_manager (MetaMonitorConfigStore *config_store); + ++META_EXPORT_TEST ++void meta_monitor_config_store_reset (MetaMonitorConfigStore *config_store); ++ + #endif /* META_MONITOR_CONFIG_STORE_H */ +diff --git a/src/tests/monitor-config-migration-unit-tests.c b/src/tests/monitor-config-migration-unit-tests.c +index bb2ac62ccdbc..df22ee3a8d39 100644 +--- a/src/tests/monitor-config-migration-unit-tests.c ++++ b/src/tests/monitor-config-migration-unit-tests.c +@@ -55,6 +55,7 @@ test_migration (const char *old_config, + NULL); + if (!meta_monitor_config_store_set_custom (config_store, "/dev/null", + migrated_path, ++ META_MONITORS_CONFIG_FLAG_NONE, + &error)) + g_error ("Failed to set custom config store: %s", error->message); + +diff --git a/src/tests/monitor-configs/policy.xml b/src/tests/monitor-configs/policy.xml +new file mode 100644 +index 000000000000..760046513e6e +--- /dev/null ++++ b/src/tests/monitor-configs/policy.xml +@@ -0,0 +1,27 @@ ++ ++ ++ ++ system ++ ++ ++ ++ ++ 0 ++ 0 ++ yes ++ ++ ++ DP-1 ++ MetaProduct's Inc. ++ MetaMonitor ++ 0x123456 ++ ++ ++ 1920 ++ 1080 ++ 60 ++ ++ ++ ++ ++ +diff --git a/src/tests/monitor-store-unit-tests.c b/src/tests/monitor-store-unit-tests.c +index 64618174f4b3..f7efa894b479 100644 +--- a/src/tests/monitor-store-unit-tests.c ++++ b/src/tests/monitor-store-unit-tests.c +@@ -890,6 +890,35 @@ meta_test_monitor_store_unknown_elements (void) + check_monitor_store_configurations (&expect); + } + ++static void ++meta_test_monitor_store_policy_not_allowed (void) ++{ ++ g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, ++ "*Policy can only be defined in system level " ++ "configurations*"); ++ set_custom_monitor_config ("policy.xml"); ++ g_test_assert_expected_messages (); ++} ++ ++static void ++meta_test_monitor_store_policy (void) ++{ ++ MetaBackend *backend = meta_get_backend (); ++ MetaMonitorManager *monitor_manager = ++ meta_backend_get_monitor_manager (backend); ++ MetaMonitorConfigManager *config_manager = monitor_manager->config_manager; ++ MetaMonitorConfigStore *config_store = ++ meta_monitor_config_manager_get_store (config_manager); ++ GList *stores_policy; ++ ++ set_custom_monitor_system_config ("policy.xml"); ++ stores_policy = meta_monitor_config_store_get_stores_policy (config_store); ++ g_assert_cmpuint (g_list_length (stores_policy), ==, 1); ++ g_assert_cmpint (GPOINTER_TO_INT (stores_policy->data), ++ ==, ++ META_CONFIG_STORE_SYSTEM); ++} ++ + void + init_monitor_store_tests (void) + { +@@ -917,4 +946,8 @@ init_monitor_store_tests (void) + meta_test_monitor_store_interlaced); + g_test_add_func ("/backends/monitor-store/unknown-elements", + meta_test_monitor_store_unknown_elements); ++ g_test_add_func ("/backends/monitor-store/policy-not-allowed", ++ meta_test_monitor_store_policy_not_allowed); ++ g_test_add_func ("/backends/monitor-store/policy", ++ meta_test_monitor_store_policy); + } +diff --git a/src/tests/monitor-test-utils.c b/src/tests/monitor-test-utils.c +index 98958a5042ee..38401e54c847 100644 +--- a/src/tests/monitor-test-utils.c ++++ b/src/tests/monitor-test-utils.c +@@ -36,10 +36,24 @@ test_get_gpu (void) + return META_GPU (meta_backend_get_gpus (meta_get_backend ())->data); + } + ++static void ++set_custom_monitor_config_common (const char *filename, ++ MetaMonitorsConfigFlag configs_flags) ++{ ++ meta_set_custom_monitor_config (meta_get_backend (), filename, configs_flags); ++} ++ + void + set_custom_monitor_config (const char *filename) + { +- meta_set_custom_monitor_config (meta_get_backend (), filename); ++ set_custom_monitor_config_common (filename, META_MONITORS_CONFIG_FLAG_NONE); ++} ++ ++void ++set_custom_monitor_system_config (const char *filename) ++{ ++ set_custom_monitor_config_common (filename, ++ META_MONITORS_CONFIG_FLAG_SYSTEM_CONFIG); + } + + char * +diff --git a/src/tests/monitor-test-utils.h b/src/tests/monitor-test-utils.h +index 3ebf1ff796ac..e5d16c438c2f 100644 +--- a/src/tests/monitor-test-utils.h ++++ b/src/tests/monitor-test-utils.h +@@ -196,6 +196,8 @@ MetaGpu * test_get_gpu (void); + + void set_custom_monitor_config (const char *filename); + ++void set_custom_monitor_system_config (const char *filename); ++ + char * read_file (const char *file_path); + + void check_monitor_configuration (MonitorTestCaseExpect *expect); +diff --git a/src/tests/monitor-unit-tests.c b/src/tests/monitor-unit-tests.c +index f05bdb2773a8..ce332b7d4dcd 100644 +--- a/src/tests/monitor-unit-tests.c ++++ b/src/tests/monitor-unit-tests.c +@@ -5277,6 +5277,7 @@ meta_test_monitor_migrated_rotated (void) + if (!meta_monitor_config_store_set_custom (config_store, + "/dev/null", + migrated_path, ++ META_MONITORS_CONFIG_FLAG_NONE, + &error)) + g_error ("Failed to set custom config store files: %s", error->message); + +@@ -5418,6 +5419,7 @@ meta_test_monitor_migrated_wiggle_discard (void) + if (!meta_monitor_config_store_set_custom (config_store, + "/dev/null", + migrated_path, ++ META_MONITORS_CONFIG_FLAG_NONE, + &error)) + g_error ("Failed to set custom config store files: %s", error->message); + +@@ -5684,6 +5686,7 @@ meta_test_monitor_migrated_wiggle (void) + if (!meta_monitor_config_store_set_custom (config_store, + "/dev/null", + migrated_path, ++ META_MONITORS_CONFIG_FLAG_NONE, + &error)) + g_error ("Failed to set custom config store files: %s", error->message); + +diff --git a/src/tests/test-utils.c b/src/tests/test-utils.c +index bee6aa102c0e..49cef3b4c37f 100644 +--- a/src/tests/test-utils.c ++++ b/src/tests/test-utils.c +@@ -578,8 +578,9 @@ test_wait_for_x11_display (void) + } + + void +-meta_set_custom_monitor_config (MetaBackend *backend, +- const char *filename) ++meta_set_custom_monitor_config (MetaBackend *backend, ++ const char *filename, ++ MetaMonitorsConfigFlag configs_flags) + { + MetaMonitorManager *monitor_manager = + meta_backend_get_monitor_manager (backend); +@@ -595,8 +596,9 @@ meta_set_custom_monitor_config (MetaBackend *backend, + path = g_test_get_filename (G_TEST_DIST, "tests", "monitor-configs", + filename, NULL); + if (!meta_monitor_config_store_set_custom (config_store, path, NULL, ++ configs_flags, + &error)) +- g_error ("Failed to set custom config: %s", error->message); ++ g_warning ("Failed to set custom config: %s", error->message); + } + + static void +diff --git a/src/tests/test-utils.h b/src/tests/test-utils.h +index 4b6aa34e8998..3b20e27f66e1 100644 +--- a/src/tests/test-utils.h ++++ b/src/tests/test-utils.h +@@ -24,6 +24,7 @@ + #include + #include + ++#include "backends/meta-backend-types.h" + #include "meta/window.h" + + #define TEST_RUNNER_ERROR test_runner_error_quark () +@@ -86,8 +87,9 @@ const char * test_get_plugin_name (void); + + void test_wait_for_x11_display (void); + +-void meta_set_custom_monitor_config (MetaBackend *backend, +- const char *filename); ++void meta_set_custom_monitor_config (MetaBackend *backend, ++ const char *filename, ++ MetaMonitorsConfigFlag configs_flags); + + void meta_wait_for_paint (MetaBackend *backend); + +-- +2.33.1 + + +From f79a90d0e366eee7669013448becf7dfcb96a6cb Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Fri, 24 Sep 2021 19:07:32 +0200 +Subject: [PATCH 6/9] tests: Add more monitor config policy parsing tests + +(cherry picked from commit 48a3ff845fabf0f23568d3c798e047e9b303bffd) +--- + .../monitor-configs/policy-duplicate.xml | 8 ++++ + src/tests/monitor-configs/policy-empty.xml | 7 +++ + src/tests/monitor-configs/policy-invalid.xml | 8 ++++ + src/tests/monitor-configs/policy-multiple.xml | 12 +++++ + src/tests/monitor-store-unit-tests.c | 44 +++++++++++++++++++ + 5 files changed, 79 insertions(+) + create mode 100644 src/tests/monitor-configs/policy-duplicate.xml + create mode 100644 src/tests/monitor-configs/policy-empty.xml + create mode 100644 src/tests/monitor-configs/policy-invalid.xml + create mode 100644 src/tests/monitor-configs/policy-multiple.xml + +diff --git a/src/tests/monitor-configs/policy-duplicate.xml b/src/tests/monitor-configs/policy-duplicate.xml +new file mode 100644 +index 000000000000..d93cc81a4906 +--- /dev/null ++++ b/src/tests/monitor-configs/policy-duplicate.xml +@@ -0,0 +1,8 @@ ++ ++ ++ ++ user ++ user ++ ++ ++ +diff --git a/src/tests/monitor-configs/policy-empty.xml b/src/tests/monitor-configs/policy-empty.xml +new file mode 100644 +index 000000000000..f56026b66846 +--- /dev/null ++++ b/src/tests/monitor-configs/policy-empty.xml +@@ -0,0 +1,7 @@ ++ ++ ++ ++ ++ ++ ++ +diff --git a/src/tests/monitor-configs/policy-invalid.xml b/src/tests/monitor-configs/policy-invalid.xml +new file mode 100644 +index 000000000000..fc4552fbefc7 +--- /dev/null ++++ b/src/tests/monitor-configs/policy-invalid.xml +@@ -0,0 +1,8 @@ ++ ++ ++ ++ user ++ not-a-store ++ ++ ++ +diff --git a/src/tests/monitor-configs/policy-multiple.xml b/src/tests/monitor-configs/policy-multiple.xml +new file mode 100644 +index 000000000000..ffeb79aafe8a +--- /dev/null ++++ b/src/tests/monitor-configs/policy-multiple.xml +@@ -0,0 +1,12 @@ ++ ++ ++ ++ user ++ system ++ ++ ++ system ++ user ++ ++ ++ +diff --git a/src/tests/monitor-store-unit-tests.c b/src/tests/monitor-store-unit-tests.c +index f7efa894b479..37b0675d0bf9 100644 +--- a/src/tests/monitor-store-unit-tests.c ++++ b/src/tests/monitor-store-unit-tests.c +@@ -919,6 +919,42 @@ meta_test_monitor_store_policy (void) + META_CONFIG_STORE_SYSTEM); + } + ++static void ++meta_test_monitor_store_policy_empty (void) ++{ ++ g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, ++ "*Invalid store*"); ++ set_custom_monitor_system_config ("policy-empty.xml"); ++ g_test_assert_expected_messages (); ++} ++ ++static void ++meta_test_monitor_store_policy_duplicate (void) ++{ ++ g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, ++ "*Multiple identical stores*"); ++ set_custom_monitor_system_config ("policy-duplicate.xml"); ++ g_test_assert_expected_messages (); ++} ++ ++static void ++meta_test_monitor_store_policy_invalid (void) ++{ ++ g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, ++ "*Invalid store*"); ++ set_custom_monitor_system_config ("policy-invalid.xml"); ++ g_test_assert_expected_messages (); ++} ++ ++static void ++meta_test_monitor_store_policy_multiple (void) ++{ ++ g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, ++ "*Multiple stores elements under policy*"); ++ set_custom_monitor_system_config ("policy-multiple.xml"); ++ g_test_assert_expected_messages (); ++} ++ + void + init_monitor_store_tests (void) + { +@@ -950,4 +986,12 @@ init_monitor_store_tests (void) + meta_test_monitor_store_policy_not_allowed); + g_test_add_func ("/backends/monitor-store/policy", + meta_test_monitor_store_policy); ++ g_test_add_func ("/backends/monitor-store/policy-empty", ++ meta_test_monitor_store_policy_empty); ++ g_test_add_func ("/backends/monitor-store/policy-duplicate", ++ meta_test_monitor_store_policy_duplicate); ++ g_test_add_func ("/backends/monitor-store/policy-invalid", ++ meta_test_monitor_store_policy_invalid); ++ g_test_add_func ("/backends/monitor-store/policy-multiple", ++ meta_test_monitor_store_policy_multiple); + } +-- +2.33.1 + + +From b884aa26afbfc6a90d9777fd077e885373200e45 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Thu, 30 Sep 2021 17:32:31 +0200 +Subject: [PATCH 7/9] monitor-config-store: Add test for monitor configuration + policy + +The test aims to verify that setting the following policy + + + + system + + + +only applies monitor configurations from the system level. + +(cherry picked from commit 9969a8aa25623dbff51e120d85ab202026571bb1) +--- + src/tests/monitor-configs/system/monitors.xml | 27 ++++ + src/tests/monitor-configs/user/monitors.xml | 22 +++ + src/tests/monitor-store-unit-tests.c | 17 +++ + src/tests/monitor-unit-tests.c | 132 ++++++++++++++++++ + 4 files changed, 198 insertions(+) + create mode 100644 src/tests/monitor-configs/system/monitors.xml + create mode 100644 src/tests/monitor-configs/user/monitors.xml + +diff --git a/src/tests/monitor-configs/system/monitors.xml b/src/tests/monitor-configs/system/monitors.xml +new file mode 100644 +index 000000000000..4d2eafec1327 +--- /dev/null ++++ b/src/tests/monitor-configs/system/monitors.xml +@@ -0,0 +1,27 @@ ++ ++ ++ ++ system ++ ++ ++ ++ ++ 0 ++ 0 ++ yes ++ ++ ++ DP-1 ++ MetaProduct's Inc. ++ MetaMonitor ++ 0x123456 ++ ++ ++ 640 ++ 480 ++ 60 ++ ++ ++ ++ ++ +diff --git a/src/tests/monitor-configs/user/monitors.xml b/src/tests/monitor-configs/user/monitors.xml +new file mode 100644 +index 000000000000..f125972e01e7 +--- /dev/null ++++ b/src/tests/monitor-configs/user/monitors.xml +@@ -0,0 +1,22 @@ ++ ++ ++ ++ 0 ++ 0 ++ yes ++ ++ ++ DP-1 ++ MetaProduct's Inc. ++ MetaMonitor ++ 0x123456 ++ ++ ++ 800 ++ 600 ++ 60 ++ ++ ++ ++ ++ +diff --git a/src/tests/monitor-store-unit-tests.c b/src/tests/monitor-store-unit-tests.c +index 37b0675d0bf9..3ea3d94dbe35 100644 +--- a/src/tests/monitor-store-unit-tests.c ++++ b/src/tests/monitor-store-unit-tests.c +@@ -958,6 +958,23 @@ meta_test_monitor_store_policy_multiple (void) + void + init_monitor_store_tests (void) + { ++ char *path; ++ ++ path = g_test_build_filename (G_TEST_DIST, ++ "tests", ++ "monitor-configs", ++ "system", ++ NULL); ++ g_setenv ("XDG_CONFIG_DIRS", path, TRUE); ++ g_free (path); ++ path = g_test_build_filename (G_TEST_DIST, ++ "tests", ++ "monitor-configs", ++ "user", ++ NULL); ++ g_setenv ("XDG_CONFIG_HOME", path, TRUE); ++ g_free (path); ++ + g_test_add_func ("/backends/monitor-store/single", + meta_test_monitor_store_single); + g_test_add_func ("/backends/monitor-store/vertical", +diff --git a/src/tests/monitor-unit-tests.c b/src/tests/monitor-unit-tests.c +index ce332b7d4dcd..91d88d0325d9 100644 +--- a/src/tests/monitor-unit-tests.c ++++ b/src/tests/monitor-unit-tests.c +@@ -5722,6 +5722,135 @@ meta_test_monitor_migrated_wiggle (void) + g_error ("Failed to remove test data output file: %s", error->message); + } + ++static void ++meta_test_monitor_policy_system_only (void) ++{ ++ MetaMonitorTestSetup *test_setup; ++ MonitorTestCase test_case = { ++ .setup = { ++ .modes = { ++ { ++ .width = 1024, ++ .height = 768, ++ .refresh_rate = 60.0 ++ }, ++ { ++ .width = 800, ++ .height = 600, ++ .refresh_rate = 60.0 ++ }, ++ { ++ .width = 640, ++ .height = 480, ++ .refresh_rate = 60.0 ++ } ++ }, ++ .n_modes = 3, ++ .outputs = { ++ { ++ .crtc = 0, ++ .modes = { 0, 1, 2 }, ++ .n_modes = 3, ++ .preferred_mode = 0, ++ .possible_crtcs = { 0 }, ++ .n_possible_crtcs = 1, ++ .width_mm = 222, ++ .height_mm = 125 ++ }, ++ }, ++ .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 ++ } ++ } ++ }, ++ { ++ .width = 800, ++ .height = 600, ++ .refresh_rate = 60.0, ++ .crtc_modes = { ++ { ++ .output = 0, ++ .crtc_mode = 1 ++ } ++ } ++ }, ++ { ++ .width = 640, ++ .height = 480, ++ .refresh_rate = 60.0, ++ .crtc_modes = { ++ { ++ .output = 0, ++ .crtc_mode = 2 ++ } ++ } ++ } ++ }, ++ .n_modes = 3, ++ .current_mode = 2, ++ .width_mm = 222, ++ .height_mm = 125 ++ }, ++ }, ++ .n_monitors = 1, ++ .logical_monitors = { ++ { ++ .monitors = { 0 }, ++ .n_monitors = 1, ++ .layout = { .x = 0, .y = 0, .width = 640, .height = 480 }, ++ .scale = 1 ++ }, ++ }, ++ .n_logical_monitors = 1, ++ .primary_logical_monitor = 0, ++ .n_outputs = 1, ++ .crtcs = { ++ { ++ .current_mode = 2, ++ .x = 0, ++ } ++ }, ++ .n_crtcs = 1, ++ .screen_width = 640, ++ .screen_height = 480, ++ } ++ }; ++ MetaBackend *backend = meta_get_backend (); ++ MetaMonitorManager *monitor_manager = ++ meta_backend_get_monitor_manager (backend); ++ MetaMonitorConfigManager *config_manager = monitor_manager->config_manager; ++ MetaMonitorConfigStore *config_store = ++ meta_monitor_config_manager_get_store (config_manager); ++ ++ test_setup = create_monitor_test_setup (&test_case.setup, ++ MONITOR_TEST_FLAG_NONE); ++ ++ meta_monitor_config_store_reset (config_store); ++ emulate_hotplug (test_setup); ++ check_monitor_configuration (&test_case.expect); ++} ++ + static void + test_case_setup (void **fixture, + const void *data) +@@ -5848,6 +5977,9 @@ init_monitor_tests (void) + + add_monitor_test ("/backends/monitor/wm/tiling", + meta_test_monitor_wm_tiling); ++ ++ add_monitor_test ("/backends/monitor/policy/system-only", ++ meta_test_monitor_policy_system_only); + } + + void +-- +2.33.1 + + +From cc88250c8a4e201c643ec7ada344323104549eb2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Thu, 30 Sep 2021 21:06:38 +0200 +Subject: [PATCH 8/9] monitor-config-store: Allow changing D-Bus configuration + policy + +Adding a element containing a boolean (yes/no) determines +whether org.gnome.Mutter.DisplayConfig ApplyMonitorsConfig will be +callable. The state is also introspectable via the +ApplyMonitorsConfigAllowed property on the same interface. + +For example + + + + no + + + +(cherry picked from commit f2c7ae821b7af7e732e588e7548238dd814e5f84) +--- + src/backends/meta-monitor-config-store.c | 68 +++++++++++++++++++ + src/backends/meta-monitor-config-store.h | 8 +++ + src/backends/meta-monitor-manager.c | 24 +++++++ + src/org.gnome.Mutter.DisplayConfig.xml | 7 ++ + .../monitor-configs/policy-dbus-invalid.xml | 6 ++ + src/tests/monitor-configs/policy-dbus.xml | 5 ++ + src/tests/monitor-store-unit-tests.c | 49 +++++++++++++ + 7 files changed, 167 insertions(+) + create mode 100644 src/tests/monitor-configs/policy-dbus-invalid.xml + create mode 100644 src/tests/monitor-configs/policy-dbus.xml + +diff --git a/src/backends/meta-monitor-config-store.c b/src/backends/meta-monitor-config-store.c +index 93a494c79b80..5d48ec2ea5cf 100644 +--- a/src/backends/meta-monitor-config-store.c ++++ b/src/backends/meta-monitor-config-store.c +@@ -123,6 +123,9 @@ struct _MetaMonitorConfigStore + + gboolean has_stores_policy; + GList *stores_policy; ++ ++ gboolean has_dbus_policy; ++ MetaMonitorConfigPolicy policy; + }; + + #define META_MONITOR_CONFIG_STORE_ERROR (meta_monitor_config_store_error_quark ()) +@@ -168,6 +171,7 @@ typedef enum + STATE_POLICY, + STATE_STORES, + STATE_STORE, ++ STATE_DBUS, + } ParserState; + + typedef struct +@@ -191,9 +195,13 @@ typedef struct + GList *current_disabled_monitor_specs; + gboolean seen_policy; + gboolean seen_stores; ++ gboolean seen_dbus; + MetaConfigStore pending_store; + GList *stores; + ++ gboolean enable_dbus_set; ++ gboolean enable_dbus; ++ + ParserState unknown_state_root; + int unknown_level; + +@@ -574,6 +582,19 @@ handle_start_element (GMarkupParseContext *context, + parser->seen_stores = TRUE; + parser->state = STATE_STORES; + } ++ else if (g_str_equal (element_name, "dbus")) ++ { ++ if (parser->seen_dbus) ++ { ++ g_set_error (error, ++ G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, ++ "Multiple dbus elements under policy"); ++ return; ++ } ++ ++ parser->seen_dbus = TRUE; ++ parser->state = STATE_DBUS; ++ } + else + { + enter_unknown_element (parser, element_name, +@@ -604,6 +625,13 @@ handle_start_element (GMarkupParseContext *context, + "Invalid store sub element '%s'", element_name); + return; + } ++ ++ case STATE_DBUS: ++ { ++ g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, ++ "Invalid dbus sub element '%s'", element_name); ++ return; ++ } + } + } + +@@ -953,6 +981,23 @@ handle_end_element (GMarkupParseContext *context, + parser->state = STATE_POLICY; + return; + ++ case STATE_DBUS: ++ if (!parser->config_store->has_dbus_policy) ++ { ++ parser->config_store->has_dbus_policy = TRUE; ++ parser->config_store->policy.enable_dbus = parser->enable_dbus; ++ parser->enable_dbus_set = FALSE; ++ } ++ else ++ { ++ g_warning ("Policy for monitor configuration via D-Bus " ++ "has already been set, ignoring policy from '%s'", ++ g_file_get_path (parser->file)); ++ } ++ parser->state = STATE_POLICY; ++ ++ return; ++ + case STATE_POLICY: + g_assert (g_str_equal (element_name, "policy")); + +@@ -1285,6 +1330,15 @@ handle_text (GMarkupParseContext *context, + parser->pending_store = store; + return; + } ++ ++ case STATE_DBUS: ++ { ++ parser->enable_dbus_set = TRUE; ++ read_bool (text, text_len, ++ &parser->enable_dbus, ++ error); ++ return; ++ } + } + } + +@@ -1643,6 +1697,11 @@ meta_monitor_config_store_save (MetaMonitorConfigStore *config_store) + return; + } + ++ if (config_store->has_stores_policy && ++ !g_list_find (config_store->stores_policy, ++ GINT_TO_POINTER (META_CONFIG_STORE_USER))) ++ return; ++ + config_store->save_cancellable = g_cancellable_new (); + + buffer = generate_config_xml (config_store); +@@ -1719,6 +1778,8 @@ meta_monitor_config_store_set_custom (MetaMonitorConfigStore *config_store, + + g_clear_pointer (&config_store->stores_policy, g_list_free); + config_store->has_stores_policy = FALSE; ++ config_store->policy.enable_dbus = TRUE; ++ config_store->has_dbus_policy = FALSE; + + if (!read_config_file (config_store, + config_store->custom_read_file, +@@ -1834,6 +1895,7 @@ meta_monitor_config_store_init (MetaMonitorConfigStore *config_store) + meta_monitors_config_key_equal, + NULL, + g_object_unref); ++ config_store->policy.enable_dbus = TRUE; + } + + static void +@@ -1987,3 +2049,9 @@ meta_monitor_config_store_reset (MetaMonitorConfigStore *config_store) + + g_free (user_file_path); + } ++ ++const MetaMonitorConfigPolicy * ++meta_monitor_config_store_get_policy (MetaMonitorConfigStore *config_store) ++{ ++ return &config_store->policy; ++} +diff --git a/src/backends/meta-monitor-config-store.h b/src/backends/meta-monitor-config-store.h +index cb6759dca00f..a255e370baaf 100644 +--- a/src/backends/meta-monitor-config-store.h ++++ b/src/backends/meta-monitor-config-store.h +@@ -32,6 +32,11 @@ typedef enum _MetaConfigStore + META_CONFIG_STORE_USER, + } MetaConfigStore; + ++typedef struct _MetaMonitorConfigPolicy ++{ ++ gboolean enable_dbus; ++} MetaMonitorConfigPolicy; ++ + #define META_TYPE_MONITOR_CONFIG_STORE (meta_monitor_config_store_get_type ()) + G_DECLARE_FINAL_TYPE (MetaMonitorConfigStore, meta_monitor_config_store, + META, MONITOR_CONFIG_STORE, GObject) +@@ -70,4 +75,7 @@ MetaMonitorManager * meta_monitor_config_store_get_monitor_manager (MetaMonitorC + META_EXPORT_TEST + void meta_monitor_config_store_reset (MetaMonitorConfigStore *config_store); + ++META_EXPORT_TEST ++const MetaMonitorConfigPolicy * meta_monitor_config_store_get_policy (MetaMonitorConfigStore *config_store); ++ + #endif /* META_MONITOR_CONFIG_STORE_H */ +diff --git a/src/backends/meta-monitor-manager.c b/src/backends/meta-monitor-manager.c +index 9e57db94cddd..75146950c38a 100644 +--- a/src/backends/meta-monitor-manager.c ++++ b/src/backends/meta-monitor-manager.c +@@ -51,6 +51,7 @@ + #include "backends/meta-logical-monitor.h" + #include "backends/meta-monitor.h" + #include "backends/meta-monitor-config-manager.h" ++#include "backends/meta-monitor-config-store.h" + #include "backends/meta-orientation-manager.h" + #include "backends/meta-output.h" + #include "backends/meta-virtual-monitor.h" +@@ -954,9 +955,18 @@ update_panel_orientation_managed (MetaMonitorManager *manager) + void + meta_monitor_manager_setup (MetaMonitorManager *manager) + { ++ MetaMonitorConfigStore *config_store; ++ const MetaMonitorConfigPolicy *policy; ++ + manager->in_init = TRUE; + + manager->config_manager = meta_monitor_config_manager_new (manager); ++ config_store = ++ meta_monitor_config_manager_get_store (manager->config_manager); ++ policy = meta_monitor_config_store_get_policy (config_store); ++ meta_dbus_display_config_set_apply_monitors_config_allowed (manager->display_config, ++ policy->enable_dbus); ++ + + meta_monitor_manager_read_current_state (manager); + +@@ -2192,6 +2202,8 @@ meta_monitor_manager_handle_apply_monitors_config (MetaDBusDisplayConfig *skelet + GVariant *properties_variant, + MetaMonitorManager *manager) + { ++ MetaMonitorConfigStore *config_store; ++ const MetaMonitorConfigPolicy *policy; + MetaMonitorManagerCapability capabilities; + GVariant *layout_mode_variant = NULL; + MetaLogicalMonitorLayoutMode layout_mode; +@@ -2208,6 +2220,18 @@ meta_monitor_manager_handle_apply_monitors_config (MetaDBusDisplayConfig *skelet + return TRUE; + } + ++ config_store = ++ meta_monitor_config_manager_get_store (manager->config_manager); ++ policy = meta_monitor_config_store_get_policy (config_store); ++ ++ if (!policy->enable_dbus) ++ { ++ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, ++ G_DBUS_ERROR_ACCESS_DENIED, ++ "Monitor configuration via D-Bus is disabled"); ++ return TRUE; ++ } ++ + capabilities = meta_monitor_manager_get_capabilities (manager); + + if (properties_variant) +diff --git a/src/org.gnome.Mutter.DisplayConfig.xml b/src/org.gnome.Mutter.DisplayConfig.xml +index 7522652dc05a..c6859c2c09c9 100644 +--- a/src/org.gnome.Mutter.DisplayConfig.xml ++++ b/src/org.gnome.Mutter.DisplayConfig.xml +@@ -290,6 +290,13 @@ + --> + + ++ ++ ++ +