diff --git a/clutter/clutter/clutter-stage.c b/clutter/clutter/clutter-stage.c index 14774f6f0b0887599f5ba8ffb942729efd15579e..edb31c99dbacc4c862a92d8ebf6a4cbd6b1a8ab3 100644 --- a/clutter/clutter/clutter-stage.c +++ b/clutter/clutter/clutter-stage.c @@ -168,6 +168,8 @@ enum PROP_PERSPECTIVE, PROP_TITLE, PROP_KEY_FOCUS, + PROP_IS_GRABBED, + PROP_LAST }; @@ -1187,6 +1189,10 @@ clutter_stage_get_property (GObject *gobject, g_value_set_object (value, priv->key_focused_actor); break; + case PROP_IS_GRABBED: + g_value_set_boolean (value, !!priv->topmost_grab); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); break; @@ -1381,6 +1387,17 @@ clutter_stage_class_init (ClutterStageClass *klass) CLUTTER_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + /** + * ClutterStage:is-grabbed: + * + * %TRUE if there is currently an active grab on the stage. + */ + obj_props[PROP_IS_GRABBED] = + g_param_spec_boolean ("is-grabbed", NULL, NULL, + FALSE, + CLUTTER_PARAM_READABLE | + G_PARAM_EXPLICIT_NOTIFY); + g_object_class_install_properties (gobject_class, PROP_LAST, obj_props); /** @@ -4150,6 +4167,7 @@ clutter_stage_grab (ClutterStage *stage, { ClutterStagePrivate *priv; ClutterGrab *grab; + gboolean was_grabbed; g_return_val_if_fail (CLUTTER_IS_STAGE (stage), NULL); g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), NULL); @@ -4178,6 +4196,8 @@ clutter_stage_grab (ClutterStage *stage, grab->prev = NULL; grab->next = priv->topmost_grab; + was_grabbed = !!priv->topmost_grab; + if (priv->topmost_grab) priv->topmost_grab->prev = grab; @@ -4199,6 +4219,9 @@ clutter_stage_grab (ClutterStage *stage, clutter_actor_attach_grab (actor, grab); clutter_stage_notify_grab (stage, grab, grab->next); + if (was_grabbed != !!priv->topmost_grab) + g_object_notify_by_pspec (G_OBJECT (stage), obj_props[PROP_IS_GRABBED]); + return grab; } @@ -4208,6 +4231,7 @@ clutter_stage_unlink_grab (ClutterStage *stage, { ClutterStagePrivate *priv = stage->priv; ClutterGrab *prev, *next; + gboolean was_grabbed; /* This grab is already detached */ if (!grab->prev && !grab->next && priv->topmost_grab != grab) @@ -4221,6 +4245,8 @@ clutter_stage_unlink_grab (ClutterStage *stage, if (next) next->prev = prev; + was_grabbed = !!priv->topmost_grab; + if (priv->topmost_grab == grab) { /* This is the active grab */ @@ -4243,6 +4269,9 @@ clutter_stage_unlink_grab (ClutterStage *stage, priv->grab_state = CLUTTER_GRAB_STATE_NONE; } + if (was_grabbed != !!priv->topmost_grab) + g_object_notify_by_pspec (G_OBJECT (stage), obj_props[PROP_IS_GRABBED]); + if (G_UNLIKELY (clutter_debug_flags & CLUTTER_DEBUG_GRABS)) { unsigned int n_grabs = 0; diff --git a/src/compositor/compositor.c b/src/compositor/compositor.c index 90c9e817e1ff266f28a20eb87a5b2532cd1d02a9..dd98aa0d30959178466dc1a181d29eedc3075ebc 100644 --- a/src/compositor/compositor.c +++ b/src/compositor/compositor.c @@ -109,6 +109,7 @@ typedef struct _MetaCompositorPrivate gulong after_paint_handler_id; gulong window_visibility_updated_id; gulong monitors_changed_internal_id; + gulong grabbed_notify_handler_id; int64_t server_time_query_time; int64_t server_time_offset; @@ -1208,6 +1209,17 @@ on_monitors_changed_internal (MetaMonitorManager *monitor_manager, meta_compositor_ensure_compositor_views (compositor); } +static void +on_is_grabbed_changed_cb (ClutterStage *stage, + GParamSpec *pspec, + MetaCompositor *compositor) +{ + if (clutter_stage_get_grab_actor (stage) != NULL) + meta_compositor_grab_begin (compositor); + else + meta_compositor_grab_end (compositor); +} + static void meta_compositor_set_property (GObject *object, guint prop_id, @@ -1284,6 +1296,11 @@ meta_compositor_constructed (GObject *object) "after-paint", G_CALLBACK (on_after_paint), compositor); + priv->grabbed_notify_handler_id = + g_signal_connect (stage, + "notify::is-grabbed", + G_CALLBACK (on_is_grabbed_changed_cb), + compositor); priv->window_visibility_updated_id = g_signal_connect (priv->display, @@ -1317,6 +1334,7 @@ meta_compositor_dispose (GObject *object) g_clear_signal_handler (&priv->stage_presented_id, stage); g_clear_signal_handler (&priv->before_paint_handler_id, stage); g_clear_signal_handler (&priv->after_paint_handler_id, stage); + g_clear_signal_handler (&priv->grabbed_notify_handler_id, stage); g_clear_signal_handler (&priv->window_visibility_updated_id, priv->display); g_clear_pointer (&priv->windows, g_list_free); diff --git a/src/core/display-private.h b/src/core/display-private.h index 4eff0b7cbd5c4444bc9a698d0e4c5a75be64f65f..a37b97e95dcaee19305ac8ed1abe2e8d1628affa 100644 --- a/src/core/display-private.h +++ b/src/core/display-private.h @@ -130,7 +130,6 @@ struct _MetaDisplay /* Opening the display */ unsigned int display_opening : 1; - unsigned int grabbed_in_clutter : 1; /* Closing down the display */ int closing; @@ -304,6 +303,9 @@ void meta_display_cancel_touch (MetaDisplay *display); gboolean meta_display_windows_are_interactable (MetaDisplay *display); +void meta_display_queue_focus (MetaDisplay *display, + MetaWindow *window); + void meta_display_show_tablet_mapping_notification (MetaDisplay *display, ClutterInputDevice *pad, const gchar *pretty_name); diff --git a/src/core/display.c b/src/core/display.c index eca1c0fcc496ac1cbc591bfc352d6c0a4656c773..55fc9e184fd57c2eee669497b398fb08145ba1c8 100644 --- a/src/core/display.c +++ b/src/core/display.c @@ -135,6 +135,11 @@ typedef struct _MetaDisplayPrivate guint queue_later_ids[META_N_QUEUE_TYPES]; GList *queue_windows[META_N_QUEUE_TYPES]; + + struct { + MetaWindow *window; + gulong unmanaging_handler_id; + } focus_on_grab_dismissed; } MetaDisplayPrivate; G_DEFINE_TYPE_WITH_PRIVATE (MetaDisplay, meta_display, G_TYPE_OBJECT) @@ -203,6 +208,10 @@ meta_display_show_osd (MetaDisplay *display, const gchar *icon_name, const gchar *message); +static void on_is_grabbed_changed (ClutterStage *stage, + GParamSpec *pspec, + MetaDisplay *display); + static MetaBackend * backend_from_display (MetaDisplay *display) { @@ -854,6 +863,7 @@ meta_display_new (MetaContext *context, GError **error) { MetaBackend *backend = meta_context_get_backend (context); + ClutterActor *stage = meta_backend_get_stage (backend); MetaDisplay *display; MetaDisplayPrivate *priv; guint32 timestamp; @@ -1005,6 +1015,9 @@ meta_display_new (MetaContext *context, meta_display_unset_input_focus (display, timestamp); } + g_signal_connect (stage, "notify::is-grabbed", + G_CALLBACK (on_is_grabbed_changed), display); + display->sound_player = g_object_new (META_TYPE_SOUND_PLAYER, NULL); /* Done opening new display */ @@ -1226,6 +1239,52 @@ meta_display_windows_are_interactable (MetaDisplay *display) return TRUE; } +static void +on_is_grabbed_changed (ClutterStage *stage, + GParamSpec *pspec, + MetaDisplay *display) +{ + MetaDisplayPrivate *priv = meta_display_get_instance_private (display); + + if (!priv->focus_on_grab_dismissed.window) + return; + + meta_window_focus (priv->focus_on_grab_dismissed.window, META_CURRENT_TIME); + + g_clear_signal_handler (&priv->focus_on_grab_dismissed.unmanaging_handler_id, + priv->focus_on_grab_dismissed.window); + priv->focus_on_grab_dismissed.window = NULL; +} + +static void +focus_on_grab_dismissed_unmanaging_cb (MetaWindow *window, + MetaDisplay *display) +{ + MetaDisplayPrivate *priv = meta_display_get_instance_private (display); + + g_return_if_fail (priv->focus_on_grab_dismissed.window == window); + + g_clear_signal_handler (&priv->focus_on_grab_dismissed.unmanaging_handler_id, + priv->focus_on_grab_dismissed.window); + priv->focus_on_grab_dismissed.window = NULL; +} + +void +meta_display_queue_focus (MetaDisplay *display, + MetaWindow *window) +{ + MetaDisplayPrivate *priv = meta_display_get_instance_private (display); + + g_clear_signal_handler (&priv->focus_on_grab_dismissed.unmanaging_handler_id, + priv->focus_on_grab_dismissed.window); + + priv->focus_on_grab_dismissed.window = window; + priv->focus_on_grab_dismissed.unmanaging_handler_id = + g_signal_connect (window, "unmanaging", + G_CALLBACK (focus_on_grab_dismissed_unmanaging_cb), + display); +} + /** * meta_display_xserver_time_is_before: * @display: a #MetaDisplay diff --git a/src/core/events.c b/src/core/events.c index 1767dded390b578e251932f1a504f97ac313a3e6..8396f867bf425c4c7f8e9780edc863864743c415 100644 --- a/src/core/events.c +++ b/src/core/events.c @@ -229,20 +229,6 @@ meta_display_handle_event (MetaDisplay *display, has_grab = stage_has_grab (display); - if (display->grabbed_in_clutter != has_grab) - { - if (!display->grabbed_in_clutter && has_grab) - { - display->grabbed_in_clutter = TRUE; - meta_compositor_grab_begin (compositor); - } - else if (display->grabbed_in_clutter && !has_grab) - { - display->grabbed_in_clutter = FALSE; - meta_compositor_grab_end (compositor); - } - } - device = clutter_event_get_device (event); clutter_input_pointer_a11y_update (device, event); diff --git a/src/core/window.c b/src/core/window.c index 6c9247743ace69c5326cbce962c253a7d7133d88..abe87e787a36caa43485d1e3c8e09ef981b34be0 100644 --- a/src/core/window.c +++ b/src/core/window.c @@ -2007,15 +2007,6 @@ window_state_on_map (MetaWindow *window, return; } - /* Do not focus window on map if input is already taken by the - * compositor. - */ - if (!meta_display_windows_are_interactable (window->display)) - { - *takes_focus = FALSE; - return; - } - /* Terminal usage may be different; some users intend to launch * many apps in quick succession or to just view things in the new * window while still interacting with the terminal. In that case, @@ -2337,7 +2328,10 @@ meta_window_show (MetaWindow *window) timestamp = meta_display_get_current_time_roundtrip (window->display); - meta_window_focus (window, timestamp); + if (meta_display_windows_are_interactable (window->display)) + meta_window_focus (window, timestamp); + else + meta_display_queue_focus (window->display, window); } else if (display->x11_display) { diff --git a/src/tests/clutter/conform/meson.build b/src/tests/clutter/conform/meson.build index b10299e7a71b5c757ee0a7fbc4385bbad869beb7..9c89cce6df1412a3c4aa66535136e18809e5d2dd 100644 --- a/src/tests/clutter/conform/meson.build +++ b/src/tests/clutter/conform/meson.build @@ -57,7 +57,6 @@ test_env.set('G_TEST_SRCDIR', meson.current_source_dir()) test_env.set('G_TEST_BUILDDIR', meson.current_build_dir()) test_env.set('G_ENABLE_DIAGNOSTIC', '0') test_env.set('CLUTTER_ENABLE_DIAGNOSTIC', '0') -test_env.set('MUTTER_TEST_PLUGIN_PATH', '@0@'.format(default_plugin.full_path())) foreach test : clutter_conform_tests test_executable = executable('@0@'.format(test), diff --git a/src/tests/cogl/conform/meson.build b/src/tests/cogl/conform/meson.build index e4e6618e3f0b78d4c881a93fe9fb1fac4884a29b..23541ec0bb72210ea6bfea87cb143e9f33a5b7bd 100644 --- a/src/tests/cogl/conform/meson.build +++ b/src/tests/cogl/conform/meson.build @@ -70,7 +70,6 @@ test_env = environment() test_env.set('G_TEST_SRCDIR', meson.current_source_dir()) test_env.set('G_TEST_BUILDDIR', meson.current_build_dir()) test_env.set('G_ENABLE_DIAGNOSTIC', '0') -test_env.set('MUTTER_TEST_PLUGIN_PATH', '@0@'.format(default_plugin.full_path())) cogl_test_variants = [ 'gl3', 'gles2' ] diff --git a/src/tests/cogl/unit/meson.build b/src/tests/cogl/unit/meson.build index a11f06dbaa6c2cba8afef8364856e56bdacfd806..bb1f418876e71424ffb5e426b534ecdfd73a9aed 100644 --- a/src/tests/cogl/unit/meson.build +++ b/src/tests/cogl/unit/meson.build @@ -14,7 +14,6 @@ test_env = environment() test_env.set('G_TEST_SRCDIR', meson.current_source_dir()) test_env.set('G_TEST_BUILDDIR', meson.current_build_dir()) test_env.set('G_ENABLE_DIAGNOSTIC', '0') -test_env.set('MUTTER_TEST_PLUGIN_PATH', '@0@'.format(default_plugin.full_path())) foreach unit_test: cogl_unit_tests test_name = 'cogl-' + unit_test[0] diff --git a/src/tests/meson.build b/src/tests/meson.build index 11838970bfa4bacb77d8fde3fb7d2bcae7826e21..8be1657dbd6d589277d88ac0f7f33106737ea831 100644 --- a/src/tests/meson.build +++ b/src/tests/meson.build @@ -13,6 +13,8 @@ mutter_test_sources = [ 'meta-sensors-proxy-mock.c', 'meta-sensors-proxy-mock.h', 'meta-test-monitor.c', + 'meta-test-shell.c', + 'meta-test-shell.h', 'meta-test-utils.c', 'meta-test-utils.h', ] @@ -575,6 +577,7 @@ stacking_tests = [ 'workspace-unmanaging-window', 'click-to-focus-and-raise', 'workspace-only-on-primary-focus', + 'overview-focus', ] foreach stacking_test: stacking_tests diff --git a/src/tests/meta-context-test.c b/src/tests/meta-context-test.c index 0387c5855795b2f85a479213d1a1474c197a5b7d..10cd954c453d7a5fc50a8702f5605d45f0d6c76d 100644 --- a/src/tests/meta-context-test.c +++ b/src/tests/meta-context-test.c @@ -29,6 +29,7 @@ #include "core/meta-context-private.h" #include "tests/meta-backend-test.h" +#include "tests/meta-test-shell.h" #include "tests/meta-test-utils-private.h" #include "wayland/meta-wayland.h" #include "wayland/meta-xwayland.h" @@ -84,7 +85,6 @@ meta_context_test_configure (MetaContext *context, meta_context_test_get_instance_private (context_test); MetaContextClass *context_class = META_CONTEXT_CLASS (meta_context_test_parent_class); - const char *plugin_name; g_test_init (argc, argv, NULL); @@ -101,10 +101,7 @@ meta_context_test_configure (MetaContext *context, meta_xwayland_override_display_number (512); #endif - plugin_name = g_getenv ("MUTTER_TEST_PLUGIN_PATH"); - if (!plugin_name) - plugin_name = "libdefault"; - meta_context_set_plugin_name (context, plugin_name); + meta_context_set_plugin_gtype (context, META_TYPE_TEST_SHELL); ensure_gsettings_memory_backend (); diff --git a/src/tests/meta-test-shell.c b/src/tests/meta-test-shell.c new file mode 100644 index 0000000000000000000000000000000000000000..0a396b3c5953c804bac73f6745e65d4e0f77a362 --- /dev/null +++ b/src/tests/meta-test-shell.c @@ -0,0 +1,799 @@ +/* + * Copyright (c) 2008 Intel Corp. + * Copyright (c) 2023 Red Hat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include "config.h" + +#include "tests/meta-test-shell.h" + +#include + +#include "clutter/clutter.h" +#include "meta/meta-backend.h" +#include "meta/meta-background-actor.h" +#include "meta/meta-background-content.h" +#include "meta/meta-background-group.h" +#include "meta/meta-context.h" +#include "meta/meta-monitor-manager.h" +#include "meta/meta-plugin.h" +#include "meta/util.h" +#include "meta/window.h" + +typedef enum +{ + ANIMATION_DESTROY, + ANIMATION_MINIMIZE, + ANIMATION_MAP, + ANIMATION_SWITCH, +} Animation; + +static unsigned int animation_durations[] = { + 100, /* destroy */ + 250, /* minimize */ + 250, /* map */ + 500, /* switch */ +}; + +#define ACTOR_DATA_KEY "-test-shell-actor-data" +#define DISPLAY_TILE_PREVIEW_DATA_KEY "-test-shell-display-tile-preview-data" + +struct _MetaTestShell +{ + MetaPlugin parent; + + ClutterTimeline *switch_workspace1_timeline; + ClutterTimeline *switch_workspace2_timeline; + ClutterActor *desktop1; + ClutterActor *desktop2; + + ClutterActor *background_group; + + MetaPluginInfo info; + + struct { + ClutterGrab *grab; + ClutterActor *prev_focus; + } overview; +}; + +typedef struct _ActorPrivate +{ + ClutterActor *orig_parent; + + ClutterTimeline *minimize_timeline; + ClutterTimeline *destroy_timeline; + ClutterTimeline *map_timeline; +} ActorPrivate; + +typedef struct +{ + ClutterActor *actor; + MetaPlugin *plugin; + gpointer effect_data; +} EffectCompleteData; + +typedef struct _DisplayTilePreview +{ + ClutterActor *actor; + + MetaRectangle tile_rect; +} DisplayTilePreview; + +G_DEFINE_TYPE (MetaTestShell, meta_test_shell, META_TYPE_PLUGIN) + +static GQuark actor_data_quark = 0; +static GQuark display_tile_preview_data_quark = 0; + +static void +free_actor_private (gpointer data) +{ + g_free (data); +} + +static ActorPrivate * +get_actor_private (MetaWindowActor *actor) +{ + ActorPrivate *actor_priv = g_object_get_qdata (G_OBJECT (actor), actor_data_quark); + + if (G_UNLIKELY (actor_data_quark == 0)) + actor_data_quark = g_quark_from_static_string (ACTOR_DATA_KEY); + + if (G_UNLIKELY (!actor_priv)) + { + actor_priv = g_new0 (ActorPrivate, 1); + + g_object_set_qdata_full (G_OBJECT (actor), + actor_data_quark, actor_priv, + free_actor_private); + } + + return actor_priv; +} + +static gboolean +is_animations_disabled (void) +{ + static gboolean is_animations_disabled_set; + static gboolean is_animations_disabled; + + if (!is_animations_disabled_set) + { + if (g_strcmp0 (getenv ("MUTTER_DEBUG_DISABLE_ANIMATIONS"), "1") == 0) + is_animations_disabled = TRUE; + else + is_animations_disabled = FALSE; + + is_animations_disabled_set = TRUE; + } + + return is_animations_disabled; +} + +static unsigned int +get_animation_duration (Animation animation) +{ + if (is_animations_disabled ()) + return 0; + + return animation_durations[animation]; +} + +static ClutterTimeline * +actor_animate (ClutterActor *actor, + ClutterAnimationMode mode, + Animation animation, + const char *first_property, + ...) +{ + va_list args; + ClutterTransition *transition; + + clutter_actor_save_easing_state (actor); + clutter_actor_set_easing_mode (actor, mode); + clutter_actor_set_easing_duration (actor, get_animation_duration (animation)); + + va_start (args, first_property); + g_object_set_valist (G_OBJECT (actor), first_property, args); + va_end (args); + + transition = clutter_actor_get_transition (actor, first_property); + + clutter_actor_restore_easing_state (actor); + + return CLUTTER_TIMELINE (transition); +} + +static void +finish_timeline (ClutterTimeline *timeline) +{ + g_object_ref (timeline); + clutter_timeline_stop (timeline); + g_object_unref (timeline); +} + +static void +kill_workspace_switch_animation (MetaTestShell *test_shell) +{ + if (test_shell->switch_workspace1_timeline) + { + g_autoptr (ClutterTimeline) timeline1 = NULL; + g_autoptr (ClutterTimeline) timeline2 = NULL; + + timeline1 = g_object_ref (test_shell->switch_workspace1_timeline); + timeline2 = g_object_ref (test_shell->switch_workspace2_timeline); + + finish_timeline (timeline1); + finish_timeline (timeline2); + } +} + +static void +on_switch_workspace_effect_stopped (ClutterTimeline *timeline, + gboolean is_finished, + gpointer data) +{ + MetaPlugin *plugin = META_PLUGIN (data); + MetaTestShell *test_shell = META_TEST_SHELL (plugin); + MetaDisplay *display = meta_plugin_get_display (plugin); + GList *l = meta_get_window_actors (display); + + while (l) + { + ClutterActor *a = l->data; + MetaWindowActor *window_actor = META_WINDOW_ACTOR (a); + ActorPrivate *actor_priv = get_actor_private (window_actor); + + if (actor_priv->orig_parent) + { + g_object_ref (a); + clutter_actor_remove_child (clutter_actor_get_parent (a), a); + clutter_actor_add_child (actor_priv->orig_parent, a); + g_object_unref (a); + actor_priv->orig_parent = NULL; + } + + l = l->next; + } + + clutter_actor_destroy (test_shell->desktop1); + clutter_actor_destroy (test_shell->desktop2); + + test_shell->switch_workspace1_timeline = NULL; + test_shell->switch_workspace2_timeline = NULL; + test_shell->desktop1 = NULL; + test_shell->desktop2 = NULL; + + meta_plugin_switch_workspace_completed (plugin); +} + +static void +on_monitors_changed (MetaMonitorManager *monitor_manager, + MetaPlugin *plugin) +{ + MetaTestShell *test_shell = META_TEST_SHELL (plugin); + MetaDisplay *display = meta_plugin_get_display (plugin); + GRand *rand; + int i, n; + + rand = g_rand_new_with_seed (123456); + clutter_actor_destroy_all_children (test_shell->background_group); + + n = meta_display_get_n_monitors (display); + for (i = 0; i < n; i++) + { + MetaBackgroundContent *background_content; + ClutterContent *content; + MetaRectangle rect; + ClutterActor *background_actor; + MetaBackground *background; + uint8_t red; + uint8_t green; + uint8_t blue; + ClutterColor color; + + meta_display_get_monitor_geometry (display, i, &rect); + + background_actor = meta_background_actor_new (display, i); + content = clutter_actor_get_content (background_actor); + background_content = META_BACKGROUND_CONTENT (content); + + clutter_actor_set_position (background_actor, rect.x, rect.y); + clutter_actor_set_size (background_actor, rect.width, rect.height); + + blue = g_rand_int_range (rand, 0, 255); + green = g_rand_int_range (rand, 0, 255); + red = g_rand_int_range (rand, 0, 255); + clutter_color_init (&color, red, green, blue, 255); + + background = meta_background_new (display); + meta_background_set_color (background, &color); + meta_background_content_set_background (background_content, background); + g_object_unref (background); + + meta_background_content_set_vignette (background_content, TRUE, 0.5, 0.5); + + clutter_actor_add_child (test_shell->background_group, background_actor); + } + + g_rand_free (rand); +} + +static void +on_overlay_key (MetaDisplay *display, + MetaTestShell *test_shell) +{ + MetaContext *context = meta_display_get_context (display); + MetaBackend *backend = meta_context_get_backend (context); + ClutterStage *stage = CLUTTER_STAGE (meta_backend_get_stage (backend)); + + if (!test_shell->overview.grab) + { + test_shell->overview.grab = clutter_stage_grab (stage, CLUTTER_ACTOR (stage)); + test_shell->overview.prev_focus = clutter_stage_get_key_focus (stage); + clutter_stage_set_key_focus (stage, CLUTTER_ACTOR (stage)); + } + else + { + g_clear_pointer (&test_shell->overview.grab, clutter_grab_dismiss); + clutter_stage_set_key_focus (stage, + g_steal_pointer (&test_shell->overview.prev_focus)); + } +} + +static void +prepare_shutdown (MetaBackend *backend, + MetaTestShell *test_shell) +{ + kill_workspace_switch_animation (test_shell); +} + +static void +meta_test_shell_start (MetaPlugin *plugin) +{ + MetaTestShell *test_shell = META_TEST_SHELL (plugin); + MetaDisplay *display = meta_plugin_get_display (plugin); + MetaContext *context = meta_display_get_context (display); + MetaBackend *backend = meta_context_get_backend (context); + MetaMonitorManager *monitor_manager = + meta_backend_get_monitor_manager (backend); + + test_shell->background_group = meta_background_group_new (); + clutter_actor_insert_child_below (meta_get_window_group_for_display (display), + test_shell->background_group, NULL); + + g_signal_connect (monitor_manager, "monitors-changed", + G_CALLBACK (on_monitors_changed), plugin); + on_monitors_changed (monitor_manager, plugin); + + g_signal_connect (display, "overlay-key", + G_CALLBACK (on_overlay_key), plugin); + + g_signal_connect (backend, "prepare-shutdown", + G_CALLBACK (prepare_shutdown), + test_shell); + + clutter_actor_show (meta_get_stage_for_display (display)); +} + +static void +meta_test_shell_switch_workspace (MetaPlugin *plugin, + int from, + int to, + MetaMotionDirection direction) +{ + MetaTestShell *test_shell = META_TEST_SHELL (plugin); + MetaDisplay *display; + ClutterActor *stage; + ClutterActor *workspace1, *workspace2; + int screen_width, screen_height; + GList *l; + + if (from == to) + { + meta_plugin_switch_workspace_completed (plugin); + return; + } + + display = meta_plugin_get_display (plugin); + stage = meta_get_stage_for_display (display); + + meta_display_get_size (display, + &screen_width, + &screen_height); + + workspace1 = clutter_actor_new (); + workspace2 = clutter_actor_new (); + + clutter_actor_set_pivot_point (workspace1, 1.0, 1.0); + clutter_actor_set_size (workspace1, + screen_width, + screen_height); + clutter_actor_set_size (workspace2, + screen_width, + screen_height); + + clutter_actor_set_scale (workspace1, 0.0, 0.0); + + clutter_actor_add_child (stage, workspace1); + clutter_actor_add_child (stage, workspace2); + + for (l = g_list_last (meta_get_window_actors (display)); l; l = l->prev) + { + MetaWindowActor *window_actor = l->data; + ActorPrivate *actor_priv = get_actor_private (window_actor); + ClutterActor *actor = CLUTTER_ACTOR (window_actor); + MetaWindow *window; + MetaWorkspace *workspace; + int workspace_idx; + + window = meta_window_actor_get_meta_window (window_actor); + workspace = meta_window_get_workspace (window); + + if (!workspace) + { + clutter_actor_hide (actor); + actor_priv->orig_parent = NULL; + continue; + } + + if (meta_window_is_on_all_workspaces (window)) + { + actor_priv->orig_parent = NULL; + continue; + } + + workspace_idx = meta_workspace_index (workspace); + + if (workspace_idx == to || workspace_idx == from) + { + ClutterActor *parent = workspace_idx == to ? workspace1 + : workspace2; + actor_priv->orig_parent = clutter_actor_get_parent (actor); + + g_object_ref (actor); + clutter_actor_remove_child (clutter_actor_get_parent (actor), + actor); + clutter_actor_add_child (parent, actor); + clutter_actor_set_child_below_sibling (parent, actor, NULL); + g_object_unref (actor); + continue; + } + + clutter_actor_hide (actor); + actor_priv->orig_parent = NULL; + } + + test_shell->desktop1 = workspace1; + test_shell->desktop2 = workspace2; + + test_shell->switch_workspace1_timeline = + actor_animate (workspace1, CLUTTER_EASE_IN_SINE, + ANIMATION_SWITCH, + "scale-x", 1.0, + "scale-y", 1.0, + NULL); + g_signal_connect (test_shell->switch_workspace1_timeline, + "stopped", + G_CALLBACK (on_switch_workspace_effect_stopped), + plugin); + + test_shell->switch_workspace2_timeline = + actor_animate (workspace2, CLUTTER_EASE_IN_SINE, + ANIMATION_SWITCH, + "scale-x", 0.0, + "scale-y", 0.0, + NULL); +} + +static void +on_minimize_effect_stopped (ClutterTimeline *timeline, + gboolean is_finished, + EffectCompleteData *data) +{ + MetaPlugin *plugin = data->plugin; + ActorPrivate *actor_priv; + MetaWindowActor *window_actor = META_WINDOW_ACTOR (data->actor); + double original_scale = *(double *) data->effect_data; + + actor_priv = get_actor_private (META_WINDOW_ACTOR (data->actor)); + actor_priv->minimize_timeline = NULL; + + clutter_actor_hide (data->actor); + clutter_actor_set_scale (data->actor, original_scale, original_scale); + + meta_plugin_minimize_completed (plugin, window_actor); + + g_free (data->effect_data); + g_free (data); +} + +static void +meta_test_shell_minimize (MetaPlugin *plugin, + MetaWindowActor *window_actor) +{ + MetaWindowType type; + MetaWindow *window = meta_window_actor_get_meta_window (window_actor); + ClutterTimeline *timeline = NULL; + ClutterActor *actor = CLUTTER_ACTOR (window_actor); + + type = meta_window_get_window_type (window); + + if (type == META_WINDOW_NORMAL) + { + timeline = actor_animate (actor, + CLUTTER_EASE_IN_SINE, + ANIMATION_MINIMIZE, + "scale-x", 0.0, + "scale-y", 0.0, + "x", (double) 0, + "y", (double) 0, + NULL); + } + + if (timeline) + { + EffectCompleteData *data; + ActorPrivate *actor_priv = get_actor_private (window_actor); + double scale_x, scale_y; + + data = g_new0 (EffectCompleteData, 1); + actor_priv->minimize_timeline = timeline; + data->plugin = plugin; + data->actor = actor; + data->effect_data = g_new0 (double, 1); + clutter_actor_get_scale (actor, &scale_x, &scale_y); + g_assert (scale_x == scale_y); + *((double *) data->effect_data) = scale_x; + g_signal_connect (actor_priv->minimize_timeline, "stopped", + G_CALLBACK (on_minimize_effect_stopped), + data); + } + else + { + meta_plugin_minimize_completed (plugin, window_actor); + } +} + +static void +on_map_effect_stopped (ClutterTimeline *timeline, + gboolean is_finished, + EffectCompleteData *data) +{ + MetaPlugin *plugin = data->plugin; + MetaWindowActor *window_actor = META_WINDOW_ACTOR (data->actor); + ActorPrivate *actor_priv = get_actor_private (window_actor); + + actor_priv->map_timeline = NULL; + + meta_plugin_map_completed (plugin, window_actor); + + g_free (data); +} + +static void +meta_test_shell_map (MetaPlugin *plugin, + MetaWindowActor *window_actor) +{ + ClutterActor *actor = CLUTTER_ACTOR (window_actor); + MetaWindow *window = meta_window_actor_get_meta_window (window_actor); + MetaWindowType type; + + type = meta_window_get_window_type (window); + + if (type == META_WINDOW_NORMAL) + { + EffectCompleteData *data = g_new0 (EffectCompleteData, 1); + ActorPrivate *actor_priv = get_actor_private (window_actor); + + clutter_actor_set_pivot_point (actor, 0.5, 0.5); + clutter_actor_set_opacity (actor, 0); + clutter_actor_set_scale (actor, 0.5, 0.5); + clutter_actor_show (actor); + + actor_priv->map_timeline = actor_animate (actor, + CLUTTER_EASE_OUT_QUAD, + ANIMATION_MAP, + "opacity", 255, + "scale-x", 1.0, + "scale-y", 1.0, + NULL); + if (actor_priv->map_timeline) + { + data->actor = actor; + data->plugin = plugin; + g_signal_connect (actor_priv->map_timeline, "stopped", + G_CALLBACK (on_map_effect_stopped), + data); + } + else + { + g_free (data); + meta_plugin_map_completed (plugin, window_actor); + } + } + else + { + meta_plugin_map_completed (plugin, window_actor); + } +} + +static void +on_destroy_effect_stopped (ClutterTimeline *timeline, + gboolean is_finished, + EffectCompleteData *data) +{ + MetaPlugin *plugin = data->plugin; + MetaWindowActor *window_actor = META_WINDOW_ACTOR (data->actor); + ActorPrivate *actor_priv = get_actor_private (window_actor); + + actor_priv->destroy_timeline = NULL; + + meta_plugin_destroy_completed (plugin, window_actor); +} + +static void +meta_test_shell_destroy (MetaPlugin *plugin, + MetaWindowActor *window_actor) +{ + ClutterActor *actor = CLUTTER_ACTOR (window_actor); + MetaWindow *window = meta_window_actor_get_meta_window (window_actor); + MetaWindowType type; + ClutterTimeline *timeline = NULL; + + type = meta_window_get_window_type (window); + + if (type == META_WINDOW_NORMAL) + { + timeline = actor_animate (actor, + CLUTTER_EASE_OUT_QUAD, + ANIMATION_DESTROY, + "opacity", 0, + "scale-x", 0.8, + "scale-y", 0.8, + NULL); + } + + if (timeline) + { + EffectCompleteData *data = g_new0 (EffectCompleteData, 1); + ActorPrivate *actor_priv = get_actor_private (window_actor); + + actor_priv->destroy_timeline = timeline; + data->plugin = plugin; + data->actor = actor; + g_signal_connect (actor_priv->destroy_timeline, "stopped", + G_CALLBACK (on_destroy_effect_stopped), + data); + } + else + { + meta_plugin_destroy_completed (plugin, window_actor); + } +} + +static void +free_display_tile_preview (DisplayTilePreview *preview) +{ + + if (preview) + { + clutter_actor_destroy (preview->actor); + g_free (preview); + } +} + +static void +on_display_closing (MetaDisplay *display, + DisplayTilePreview *preview) +{ + free_display_tile_preview (preview); +} + +static DisplayTilePreview * +get_display_tile_preview (MetaDisplay *display) +{ + DisplayTilePreview *preview; + + if (!display_tile_preview_data_quark) + { + display_tile_preview_data_quark = + g_quark_from_static_string (DISPLAY_TILE_PREVIEW_DATA_KEY); + } + + preview = g_object_get_qdata (G_OBJECT (display), + display_tile_preview_data_quark); + if (!preview) + { + preview = g_new0 (DisplayTilePreview, 1); + + preview->actor = clutter_actor_new (); + clutter_actor_set_background_color (preview->actor, CLUTTER_COLOR_Blue); + clutter_actor_set_opacity (preview->actor, 100); + + clutter_actor_add_child (meta_get_window_group_for_display (display), + preview->actor); + g_signal_connect (display, + "closing", + G_CALLBACK (on_display_closing), + preview); + g_object_set_qdata (G_OBJECT (display), + display_tile_preview_data_quark, + preview); + } + + return preview; +} + +static void +meta_test_shell_show_tile_preview (MetaPlugin *plugin, + MetaWindow *window, + MetaRectangle *tile_rect, + int tile_monitor_number) +{ + MetaDisplay *display = meta_plugin_get_display (plugin); + DisplayTilePreview *preview = get_display_tile_preview (display); + ClutterActor *window_actor; + + if (clutter_actor_is_visible (preview->actor) && + preview->tile_rect.x == tile_rect->x && + preview->tile_rect.y == tile_rect->y && + preview->tile_rect.width == tile_rect->width && + preview->tile_rect.height == tile_rect->height) + return; + + clutter_actor_set_position (preview->actor, tile_rect->x, tile_rect->y); + clutter_actor_set_size (preview->actor, tile_rect->width, tile_rect->height); + + clutter_actor_show (preview->actor); + + window_actor = CLUTTER_ACTOR (meta_window_get_compositor_private (window)); + clutter_actor_set_child_below_sibling (clutter_actor_get_parent (preview->actor), + preview->actor, + window_actor); + + preview->tile_rect = *tile_rect; +} + +static void +meta_test_shell_hide_tile_preview (MetaPlugin *plugin) +{ + MetaDisplay *display = meta_plugin_get_display (plugin); + DisplayTilePreview *preview = get_display_tile_preview (display); + + clutter_actor_hide (preview->actor); +} + +static void +meta_test_shell_kill_switch_workspace (MetaPlugin *plugin) +{ + MetaTestShell *test_shell = META_TEST_SHELL (plugin); + + kill_workspace_switch_animation (test_shell); +} + +static void +meta_test_shell_kill_window_effects (MetaPlugin *plugin, + MetaWindowActor *window_actor) +{ + ActorPrivate *actor_priv; + + actor_priv = get_actor_private (window_actor); + + if (actor_priv->minimize_timeline) + finish_timeline (actor_priv->minimize_timeline); + + if (actor_priv->map_timeline) + finish_timeline (actor_priv->map_timeline); + + if (actor_priv->destroy_timeline) + finish_timeline (actor_priv->destroy_timeline); +} + +static const MetaPluginInfo * +meta_test_shell_plugin_info (MetaPlugin *plugin) +{ + MetaTestShell *test_shell = META_TEST_SHELL (plugin); + + return &test_shell->info; +} + +static void +meta_test_shell_class_init (MetaTestShellClass *klass) +{ + MetaPluginClass *plugin_class = META_PLUGIN_CLASS (klass); + + plugin_class->start = meta_test_shell_start; + plugin_class->map = meta_test_shell_map; + plugin_class->minimize = meta_test_shell_minimize; + plugin_class->destroy = meta_test_shell_destroy; + plugin_class->switch_workspace = meta_test_shell_switch_workspace; + plugin_class->show_tile_preview = meta_test_shell_show_tile_preview; + plugin_class->hide_tile_preview = meta_test_shell_hide_tile_preview; + plugin_class->kill_window_effects = meta_test_shell_kill_window_effects; + plugin_class->kill_switch_workspace = meta_test_shell_kill_switch_workspace; + plugin_class->plugin_info = meta_test_shell_plugin_info; +} + +static void +meta_test_shell_init (MetaTestShell *test_shell) +{ + test_shell->info.name = "Test Shell"; + test_shell->info.version = VERSION; + test_shell->info.author = "Mutter developers"; + test_shell->info.license = "GPL"; + test_shell->info.description = "This is test shell plugin implementation."; +} diff --git a/src/tests/meta-test-shell.h b/src/tests/meta-test-shell.h new file mode 100644 index 0000000000000000000000000000000000000000..01f4c8d0eb528c96f0429788f909170077f533a6 --- /dev/null +++ b/src/tests/meta-test-shell.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 Red Hat + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#pragma once + +#include "meta/meta-plugin.h" + +#define META_TYPE_TEST_SHELL (meta_test_shell_get_type ()) +META_EXPORT +G_DECLARE_FINAL_TYPE (MetaTestShell, meta_test_shell, + META, TEST_SHELL, + MetaPlugin) diff --git a/src/tests/meta-test-utils.c b/src/tests/meta-test-utils.c index 416dc0b648e9b1d15489b6f4e8fe8f9292082371..65b052f5803e9e8a00f78b637ed17781b0b82242 100644 --- a/src/tests/meta-test-utils.c +++ b/src/tests/meta-test-utils.c @@ -751,18 +751,6 @@ meta_test_client_destroy (MetaTestClient *client) g_free (client); } -const char * -meta_test_get_plugin_name (void) -{ - const char *name; - - name = g_getenv ("MUTTER_TEST_PLUGIN_PATH"); - if (name) - return name; - else - return "libdefault"; -} - void meta_set_custom_monitor_config_full (MetaBackend *backend, const char *filename, diff --git a/src/tests/meta-test-utils.h b/src/tests/meta-test-utils.h index 0232276a7e61fbd1eda937015182b349f70ab823..7cc046bf07c364feae527c868a1362eb7de92606 100644 --- a/src/tests/meta-test-utils.h +++ b/src/tests/meta-test-utils.h @@ -109,9 +109,6 @@ MetaTestClient * meta_test_client_new (MetaContext *context, META_EXPORT void meta_test_client_destroy (MetaTestClient *client); -META_EXPORT -const char * meta_test_get_plugin_name (void); - META_EXPORT void meta_set_custom_monitor_config_full (MetaBackend *backend, const char *filename, diff --git a/src/tests/native-persistent-virtual-monitor.c b/src/tests/native-persistent-virtual-monitor.c index c5803858b379f3544e9e6911697e3d0d29fe5814..e3b3acff6d6795247327705d2f6554e55375190b 100644 --- a/src/tests/native-persistent-virtual-monitor.c +++ b/src/tests/native-persistent-virtual-monitor.c @@ -24,6 +24,7 @@ #include "backends/meta-monitor-manager-private.h" #include "meta/meta-context.h" #include "meta/meta-backend.h" +#include "tests/meta-test-shell.h" #include "tests/meta-test-utils.h" static MetaContext *test_context; @@ -90,7 +91,7 @@ main (int argc, context = meta_create_context ("Persistent virtual monitor test"); g_assert (meta_context_configure (context, &fake_argc, &fake_argv, &error)); - meta_context_set_plugin_name (context, meta_test_get_plugin_name ()); + meta_context_set_plugin_gtype (context, META_TYPE_TEST_SHELL); g_assert (meta_context_setup (context, &error)); g_assert (meta_context_start (context, &error)); diff --git a/src/tests/stacking/overview-focus.metatest b/src/tests/stacking/overview-focus.metatest new file mode 100644 index 0000000000000000000000000000000000000000..07dfe5ac4818e3c013fce93cc8f65dbd9a524766 --- /dev/null +++ b/src/tests/stacking/overview-focus.metatest @@ -0,0 +1,44 @@ +new_client w wayland + +create w/1 +show w/1 +wait +assert_focused w/1 +hide w/1 +destroy w/1 + +toggle_overview + +create w/1 +show w/1 +wait +assert_focused none + +toggle_overview + +assert_focused w/1 +hide w/1 +destroy w/1 + + +new_client x x11 + +create x/1 +show x/1 +wait +assert_focused x/1 +hide x/1 +destroy x/1 + +toggle_overview + +create x/1 +show x/1 +wait +assert_focused none + +toggle_overview + +assert_focused x/1 +hide x/1 +destroy x/1 diff --git a/src/tests/test-runner.c b/src/tests/test-runner.c index 282c4ec6f85657b6ecefa92afa80aee392976641..ceccdbb26cd4ca3be8dc1b47e7753dbdb8998b2e 100644 --- a/src/tests/test-runner.c +++ b/src/tests/test-runner.c @@ -1301,6 +1301,15 @@ test_case_do (TestCase *test, BAD_COMMAND("Unknown preference %s", argv[1]); } } + else if (strcmp (argv[0], "toggle_overview") == 0) + { + MetaDisplay *display = meta_context_get_display (test->context); + + if (argc != 1) + BAD_COMMAND ("usage: %s", argv[0]); + + g_signal_emit_by_name (display, "overlay-key", 0); + } else { BAD_COMMAND("Unknown command %s", argv[0]);