/* * 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; guint minimize_stopped_id; } ActorPrivate; typedef struct { ClutterActor *actor; MetaPlugin *plugin; gpointer effect_data; } EffectCompleteData; typedef struct _DisplayTilePreview { ClutterActor *actor; MtkRectangle 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) { ActorPrivate *actor_priv = data; g_clear_handle_id (&actor_priv->minimize_stopped_id, g_source_remove); 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; MtkRectangle 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 restore_scale_idle (gpointer user_data) { EffectCompleteData *data = user_data; MetaPlugin *plugin = data->plugin; MetaWindowActor *window_actor = META_WINDOW_ACTOR (data->actor); double original_scale = *(double *) data->effect_data; ActorPrivate *actor_priv; actor_priv = get_actor_private (META_WINDOW_ACTOR (data->actor)); actor_priv->minimize_timeline = NULL; actor_priv->minimize_stopped_id = 0; 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 on_minimize_effect_stopped (ClutterTimeline *timeline, gboolean is_finished, EffectCompleteData *data) { MetaWindowActor *window_actor = META_WINDOW_ACTOR (data->actor); ActorPrivate *actor_priv = get_actor_private (window_actor); actor_priv->minimize_stopped_id = g_idle_add_once (restore_scale_idle, 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); g_clear_handle_id (&actor_priv->minimize_stopped_id, g_source_remove); } 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, MtkRectangle *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."; }