From c4e0444fc60b2681c8607a50dad7470a6f0afe72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Fri, 16 Aug 2024 14:35:58 -0700 Subject: [PATCH 1/6] Add ShellClients support --- compositor/HotCorners/Barrier.vala | 94 +++++ compositor/PantheonShell.vala | 373 ++++++++++++++++++ compositor/ShellClients/CenteredWindow.vala | 55 +++ compositor/ShellClients/HideTracker.vala | 201 ++++++++++ compositor/ShellClients/ManagedClient.vala | 87 ++++ .../ShellClients/NotificationsClient.vala | 34 ++ compositor/ShellClients/PanelClone.vala | 153 +++++++ compositor/ShellClients/PanelWindow.vala | 272 +++++++++++++ .../ShellClients/ShellClientsManager.vala | 209 ++++++++++ compositor/Widgets/SafeWindowClone.vala | 66 ++++ compositor/WindowManager.vala | 2 +- compositor/meson.build | 12 +- meson.build | 2 + protocol/meson.build | 32 ++ protocol/pantheon-desktop-shell-v1.xml | 131 ++++++ protocol/pantheon-desktop-shell.deps | 1 + protocol/pantheon-desktop-shell.vapi | 83 ++++ vapi/wayland-server.deps | 1 + vapi/wayland-server.vapi | 125 ++++++ 19 files changed, 1931 insertions(+), 2 deletions(-) create mode 100644 compositor/HotCorners/Barrier.vala create mode 100644 compositor/PantheonShell.vala create mode 100644 compositor/ShellClients/CenteredWindow.vala create mode 100644 compositor/ShellClients/HideTracker.vala create mode 100644 compositor/ShellClients/ManagedClient.vala create mode 100644 compositor/ShellClients/NotificationsClient.vala create mode 100644 compositor/ShellClients/PanelClone.vala create mode 100644 compositor/ShellClients/PanelWindow.vala create mode 100644 compositor/ShellClients/ShellClientsManager.vala create mode 100644 compositor/Widgets/SafeWindowClone.vala create mode 100644 protocol/meson.build create mode 100644 protocol/pantheon-desktop-shell-v1.xml create mode 100644 protocol/pantheon-desktop-shell.deps create mode 100644 protocol/pantheon-desktop-shell.vapi create mode 100644 vapi/wayland-server.deps create mode 100644 vapi/wayland-server.vapi diff --git a/compositor/HotCorners/Barrier.vala b/compositor/HotCorners/Barrier.vala new file mode 100644 index 000000000..580d976a0 --- /dev/null +++ b/compositor/HotCorners/Barrier.vala @@ -0,0 +1,94 @@ +/* + * Copyright 2024 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: GPL-3.0-or-later + */ + + /** + * A pointer barrier supporting pressured activation. + */ +public class GreeterCompositor.Barrier : Object { + public signal void trigger (); + + public bool triggered { get; set; default = false; } + + public int trigger_pressure_threshold { get; construct; } + public int release_pressure_threshold { get; construct; } + public int retrigger_pressure_threshold { get; construct; } + public int retrigger_delay { get; construct; } + + private Meta.Barrier barrier; + + private uint32 triggered_time; + private double pressure; + + /** + * @param trigger_pressure_threshold The amount of pixels to travel additionally for + * the barrier to trigger. Set to 0 to immediately activate. + * @param retrigger_pressure_threshold The amount of pixels to travel additionally for + * the barrier to trigger again. Set to int.MAX to disallow retrigger. + */ + public Barrier ( + Meta.Backend backend, + int x1, + int y1, + int x2, + int y2, + Meta.BarrierDirection directions, + int trigger_pressure_threshold, + int release_pressure_threshold, + int retrigger_pressure_threshold, + int retrigger_delay + ) { + Object ( + trigger_pressure_threshold: trigger_pressure_threshold, + release_pressure_threshold: release_pressure_threshold, + retrigger_pressure_threshold: retrigger_pressure_threshold, + retrigger_delay: retrigger_delay + ); + + try { + barrier = new Meta.Barrier (backend, x1, y1, x2, y2, directions, Meta.BarrierFlags.NONE); + barrier.hit.connect (on_hit); + barrier.left.connect (on_left); + } catch (Error e) { + warning ("Failed to create Meta Barrier"); + } + } + + ~Barrier () { + barrier.destroy (); + } + + private void on_hit (Meta.BarrierEvent event) { + if (POSITIVE_X in barrier.directions || NEGATIVE_X in barrier.directions) { + pressure += event.dx.abs (); + } else { + pressure += event.dy.abs (); + } + + if (!triggered && pressure > trigger_pressure_threshold) { + emit_trigger (event.time); + } + + if (!triggered && pressure > release_pressure_threshold) { + barrier.release (event); + } + + if (triggered && pressure.abs () > retrigger_pressure_threshold && event.time > retrigger_delay + triggered_time) { + emit_trigger (event.time); + } + } + + private void emit_trigger (uint32 time) { + triggered = true; + pressure = 0; + triggered_time = time; + + trigger (); + } + + private void on_left () { + pressure = 0; + triggered = false; + } +} diff --git a/compositor/PantheonShell.vala b/compositor/PantheonShell.vala new file mode 100644 index 000000000..3488d25bf --- /dev/null +++ b/compositor/PantheonShell.vala @@ -0,0 +1,373 @@ +/* + * Copyright 2023 elementary, Inc. + * Copyright 2023 Corentin Noël + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +namespace GreeterCompositor { +#if !HAS_MUTTER45 + [Compact] + public class FakeMetaWaylandCompositor : GLib.Object { + // It is the third field and Vala adds a FakeMetaWaylandCompositorPrivate *priv + public Wl.Display wayland_display; + + [CCode (cname = "meta_context_get_wayland_compositor")] + public extern static unowned Gala.FakeMetaWaylandCompositor from_context (Meta.Context context); + } +#endif + public static inline unowned Wl.Display? get_display_from_context (Meta.Context context) { +#if HAS_MUTTER45 + unowned Meta.WaylandCompositor? compositor = context.get_wayland_compositor (); + if (compositor == null) { + return null; + } + + return (Wl.Display) compositor.get_wayland_display (); +#else + unowned FakeMetaWaylandCompositor compositor = Gala.FakeMetaWaylandCompositor.from_context (context); + if (compositor == null) { + return null; + } + + return compositor.wayland_display; +#endif + } + + private static Pantheon.Desktop.ShellInterface wayland_pantheon_shell_interface; + private static Pantheon.Desktop.PanelInterface wayland_pantheon_panel_interface; + private static Pantheon.Desktop.WidgetInterface wayland_pantheon_widget_interface; + private static Pantheon.Desktop.ExtendedBehaviorInterface wayland_pantheon_extended_behavior_interface; + private static Wl.Global shell_global; + + public void init_pantheon_shell (Meta.Context context) { + unowned Wl.Display? wl_disp = get_display_from_context (context); + if (wl_disp == null) { + debug ("Not running under Wayland, no Pantheon Shell protocol"); + return; + } + + wayland_pantheon_shell_interface = { + get_panel, + get_widget, + get_extended_behavior, + }; + + wayland_pantheon_panel_interface = { + destroy_panel_surface, + set_anchor, + focus_panel, + set_size, + set_hide_mode, + }; + + wayland_pantheon_widget_interface = { + destroy_widget_surface, + }; + + wayland_pantheon_extended_behavior_interface = { + destroy_extended_behavior_surface, + set_keep_above, + make_centered, + focus_extended_behavior, + }; + + PanelSurface.quark = GLib.Quark.from_string ("-gala-wayland-panel-surface-data"); + WidgetSurface.quark = GLib.Quark.from_string ("-gala-wayland-widget-surface-data"); + ExtendedBehaviorSurface.quark = GLib.Quark.from_string ("-gala-wayland-extended-behavior-surface-data"); + + shell_global = Wl.Global.create (wl_disp, ref Pantheon.Desktop.ShellInterface.iface, 1, (client, version, id) => { + unowned var resource = client.create_resource (ref Pantheon.Desktop.ShellInterface.iface, (int) version, id); + resource.set_implementation (&wayland_pantheon_shell_interface, null, (res) => {}); + }); + } + + public class PanelSurface : GLib.Object { + public static GLib.Quark quark = 0; + public unowned GLib.Object? wayland_surface; + + public PanelSurface (GLib.Object wayland_surface) { + this.wayland_surface = wayland_surface; + } + + ~PanelSurface () { + if (wayland_surface != null) { + wayland_surface.steal_qdata (quark); + } + } + + public void on_wayland_surface_disposed () { + wayland_surface = null; + } + } + + public class WidgetSurface : GLib.Object { + public static GLib.Quark quark = 0; + public unowned GLib.Object? wayland_surface; + + public WidgetSurface (GLib.Object wayland_surface) { + this.wayland_surface = wayland_surface; + } + + ~WidgetSurface () { + if (wayland_surface != null) { + wayland_surface.steal_qdata (quark); + } + } + + public void on_wayland_surface_disposed () { + wayland_surface = null; + } + } + + public class ExtendedBehaviorSurface : GLib.Object { + public static GLib.Quark quark = 0; + public unowned GLib.Object? wayland_surface; + + public ExtendedBehaviorSurface (GLib.Object wayland_surface) { + this.wayland_surface = wayland_surface; + } + + ~ExtendedBehaviorSurface () { + if (wayland_surface != null) { + wayland_surface.steal_qdata (quark); + } + } + + public void on_wayland_surface_disposed () { + wayland_surface = null; + } + } + + static void unref_obj_on_destroy (Wl.Resource resource) { + resource.get_user_data ().unref (); + } + + internal static void get_panel (Wl.Client client, Wl.Resource resource, uint32 output, Wl.Resource surface_resource) { + unowned GLib.Object? wayland_surface = surface_resource.get_user_data (); + PanelSurface? panel_surface = wayland_surface.get_qdata (PanelSurface.quark); + if (panel_surface != null) { + surface_resource.post_error ( + Wl.DisplayError.INVALID_OBJECT, + "io_elementary_pantheon_shell_v1_interface::get_panel already requested" + ); + return; + } + + panel_surface = new PanelSurface (wayland_surface); + unowned var panel_resource = client.create_resource ( + ref Pantheon.Desktop.PanelInterface.iface, + resource.get_version (), + output + ); + panel_resource.set_implementation ( + &wayland_pantheon_panel_interface, + panel_surface.ref (), + unref_obj_on_destroy + ); + wayland_surface.set_qdata_full ( + PanelSurface.quark, + panel_surface, + (GLib.DestroyNotify) PanelSurface.on_wayland_surface_disposed + ); + } + + internal static void get_widget (Wl.Client client, Wl.Resource resource, uint32 output, Wl.Resource surface_resource) { + unowned GLib.Object? wayland_surface = surface_resource.get_user_data (); + WidgetSurface? widget_surface = wayland_surface.get_qdata (WidgetSurface.quark); + if (widget_surface != null) { + surface_resource.post_error ( + Wl.DisplayError.INVALID_OBJECT, + "io_elementary_pantheon_shell_v1_interface::get_widget already requested" + ); + return; + } + + widget_surface = new WidgetSurface (wayland_surface); + unowned var widget_resource = client.create_resource ( + ref Pantheon.Desktop.WidgetInterface.iface, + resource.get_version (), + output + ); + widget_resource.set_implementation ( + &wayland_pantheon_widget_interface, + widget_surface.ref (), + unref_obj_on_destroy + ); + wayland_surface.set_qdata_full ( + WidgetSurface.quark, + widget_surface, + (GLib.DestroyNotify) WidgetSurface.on_wayland_surface_disposed + ); + } + + internal static void get_extended_behavior (Wl.Client client, Wl.Resource resource, uint32 output, Wl.Resource surface_resource) { + unowned GLib.Object? wayland_surface = surface_resource.get_user_data (); + ExtendedBehaviorSurface? eb_surface = wayland_surface.get_qdata (ExtendedBehaviorSurface.quark); + if (eb_surface != null) { + surface_resource.post_error ( + Wl.DisplayError.INVALID_OBJECT, + "io_elementary_pantheon_shell_v1_interface::get_extended_behavior already requested" + ); + return; + } + + eb_surface = new ExtendedBehaviorSurface (wayland_surface); + unowned var eb_resource = client.create_resource ( + ref Pantheon.Desktop.ExtendedBehaviorInterface.iface, + resource.get_version (), + output + ); + eb_resource.set_implementation ( + &wayland_pantheon_extended_behavior_interface, + eb_surface.ref (), + unref_obj_on_destroy + ); + wayland_surface.set_qdata_full ( + ExtendedBehaviorSurface.quark, + eb_surface, + (GLib.DestroyNotify) ExtendedBehaviorSurface.on_wayland_surface_disposed + ); + } + + internal static void set_anchor (Wl.Client client, Wl.Resource resource, [CCode (type = "uint32_t")] Pantheon.Desktop.Anchor anchor) { + unowned PanelSurface? panel_surface = resource.get_user_data (); + if (panel_surface.wayland_surface == null) { + warning ("Window tried to set anchor but wayland surface is null."); + return; + } + + Meta.Window? window; + panel_surface.wayland_surface.get ("window", out window, null); + if (window == null) { + warning ("Window tried to set anchor but wayland surface had no associated window."); + return; + } + + Meta.Side side = TOP; + switch (anchor) { + case TOP: + break; + + case BOTTOM: + side = BOTTOM; + break; + + case LEFT: + side = LEFT; + break; + + case RIGHT: + side = RIGHT; + break; + } + + ShellClientsManager.get_instance ().set_anchor (window, side); + } + + internal static void focus_panel (Wl.Client client, Wl.Resource resource) { + unowned PanelSurface? panel_surface = resource.get_user_data (); + if (panel_surface.wayland_surface == null) { + warning ("Window tried to focus but wayland surface is null."); + return; + } + + focus (panel_surface.wayland_surface); + } + + internal static void focus_extended_behavior (Wl.Client client, Wl.Resource resource) { + unowned ExtendedBehaviorSurface? extended_behavior_surface = resource.get_user_data (); + if (extended_behavior_surface.wayland_surface == null) { + warning ("Window tried to focus but wayland surface is null."); + return; + } + + focus (extended_behavior_surface.wayland_surface); + } + + internal static void focus (Object wayland_surface) { + Meta.Window? window; + wayland_surface.get ("window", out window, null); + if (window == null) { + warning ("Window tried to focus but wayland surface had no associated window."); + return; + } + + window.focus (window.get_display ().get_current_time ()); + } + + internal static void set_size (Wl.Client client, Wl.Resource resource, int width, int height) { + unowned PanelSurface? panel_surface = resource.get_user_data (); + if (panel_surface.wayland_surface == null) { + warning ("Window tried to set size but wayland surface is null."); + return; + } + + Meta.Window? window; + panel_surface.wayland_surface.get ("window", out window, null); + if (window == null) { + warning ("Window tried to set size but wayland surface had no associated window."); + return; + } + + ShellClientsManager.get_instance ().set_size (window, width, height); + } + + internal static void set_hide_mode (Wl.Client client, Wl.Resource resource, [CCode (type = "uint32_t")] Pantheon.Desktop.HideMode hide_mode) { + unowned PanelSurface? panel_surface = resource.get_user_data (); + if (panel_surface.wayland_surface == null) { + warning ("Window tried to set hide mode but wayland surface is null."); + return; + } + + Meta.Window? window; + panel_surface.wayland_surface.get ("window", out window, null); + if (window == null) { + warning ("Window tried to set hide mode but wayland surface had no associated window."); + return; + } + + ShellClientsManager.get_instance ().set_hide_mode (window, hide_mode); + } + + internal static void set_keep_above (Wl.Client client, Wl.Resource resource) { + unowned ExtendedBehaviorSurface? eb_surface = resource.get_user_data (); + if (eb_surface.wayland_surface == null) { + return; + } + + Meta.Window? window; + eb_surface.wayland_surface.get ("window", out window, null); + if (window == null) { + return; + } + + window.make_above (); + } + + internal static void make_centered (Wl.Client client, Wl.Resource resource) { + unowned ExtendedBehaviorSurface? eb_surface = resource.get_user_data (); + if (eb_surface.wayland_surface == null) { + return; + } + + Meta.Window? window; + eb_surface.wayland_surface.get ("window", out window, null); + if (window == null) { + return; + } + + ShellClientsManager.get_instance ().make_centered (window); + } + + internal static void destroy_panel_surface (Wl.Client client, Wl.Resource resource) { + resource.destroy (); + } + + internal static void destroy_widget_surface (Wl.Client client, Wl.Resource resource) { + resource.destroy (); + } + + internal static void destroy_extended_behavior_surface (Wl.Client client, Wl.Resource resource) { + resource.destroy (); + } +} diff --git a/compositor/ShellClients/CenteredWindow.vala b/compositor/ShellClients/CenteredWindow.vala new file mode 100644 index 000000000..7b75b27ab --- /dev/null +++ b/compositor/ShellClients/CenteredWindow.vala @@ -0,0 +1,55 @@ +/* + * Copyright 2024 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Authored by: Leonhard Kargl + */ + +public class GreeterCompositor.CenteredWindow : Object { + public WindowManager wm { get; construct; } + public Meta.Window window { get; construct; } + + private uint idle_move_id = 0; + + public CenteredWindow (WindowManager wm, Meta.Window window) { + Object (wm: wm, window: window); + } + + construct { + window.size_changed.connect (position_window); + window.stick (); + + var monitor_manager = wm.get_display ().get_context ().get_backend ().get_monitor_manager (); + monitor_manager.monitors_changed.connect (() => position_window ()); + + position_window (); + + window.shown.connect (() => window.focus (wm.get_display ().get_current_time ())); + + window.unmanaging.connect (() => { + if (idle_move_id != 0) { + Source.remove (idle_move_id); + } + }); + } + + private void position_window () { + var display = wm.get_display (); + var monitor_geom = display.get_monitor_geometry (display.get_primary_monitor ()); + var window_rect = window.get_frame_rect (); + + var x = monitor_geom.x + (monitor_geom.width - window_rect.width) / 2; + var y = monitor_geom.y + (monitor_geom.height - window_rect.height) / 2; + + if (idle_move_id != 0) { + Source.remove (idle_move_id); + } + + idle_move_id = Idle.add (() => { + window.move_frame (false, x, y); + + idle_move_id = 0; + return Source.REMOVE; + }); + } +} diff --git a/compositor/ShellClients/HideTracker.vala b/compositor/ShellClients/HideTracker.vala new file mode 100644 index 000000000..428f72223 --- /dev/null +++ b/compositor/ShellClients/HideTracker.vala @@ -0,0 +1,201 @@ +/* + * Copyright 2024 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Authored by: Leonhard Kargl + */ + +public class GreeterCompositor.HideTracker : Object { + private const uint UPDATE_TIMEOUT = 200; + + public signal void hide (); + public signal void show (); + + public Meta.Display display { get; construct; } + public unowned PanelWindow panel { get; construct; } + public Pantheon.Desktop.HideMode hide_mode { get; set; default = NEVER; } + + private bool hovered = false; + + private bool overlap = false; + private bool focus_overlap = false; + private bool focus_maximized_overlap = false; + + private Meta.Window current_focus_window; + + private uint update_timeout_id = 0; + + public HideTracker (Meta.Display display, PanelWindow panel) { + Object (display: display, panel: panel); + } + + construct { + // Can't be local otherwise we get a memory leak :( + // See https://gitlab.gnome.org/GNOME/vala/-/issues/1548 + current_focus_window = display.focus_window; + track_focus_window (current_focus_window); + display.notify["focus-window"].connect (() => { + untrack_focus_window (current_focus_window); + current_focus_window = display.focus_window; + track_focus_window (current_focus_window); + }); + + display.window_created.connect ((window) => { + schedule_update (); + window.unmanaged.connect (schedule_update); + }); + + var cursor_tracker = display.get_cursor_tracker (); + cursor_tracker.position_invalidated.connect (() => { +#if HAS_MUTTER45 + var has_pointer = panel.window.has_pointer (); +#else + var has_pointer = window_has_pointer (); +#endif + + if (hovered != has_pointer) { + hovered = has_pointer; + schedule_update (); + } + }); + + display.get_workspace_manager ().active_workspace_changed.connect (schedule_update); + } + + //Can be removed with mutter > 45 + private bool window_has_pointer () { + var cursor_tracker = display.get_cursor_tracker (); + Graphene.Point pointer_pos; + cursor_tracker.get_pointer (out pointer_pos, null); + + var window_rect = panel.get_custom_window_rect (); + Graphene.Rect graphene_window_rect = { + { + window_rect.x, + window_rect.y + }, + { + window_rect.width, + window_rect.height + } + }; + return graphene_window_rect.contains_point (pointer_pos); + } + + private void track_focus_window (Meta.Window? window) { + if (window == null) { + return; + } + + window.position_changed.connect (schedule_update); + window.size_changed.connect (schedule_update); + schedule_update (); + } + + private void untrack_focus_window (Meta.Window? window) { + if (window == null) { + return; + } + + window.position_changed.disconnect (schedule_update); + window.size_changed.disconnect (schedule_update); + schedule_update (); + } + + public void schedule_update () { + if (update_timeout_id != 0) { + return; + } + + update_timeout_id = Timeout.add (UPDATE_TIMEOUT, () => { + update_overlap (); + update_timeout_id = 0; + return Source.REMOVE; + }); + } + + private void update_overlap () { + overlap = false; + focus_overlap = false; + focus_maximized_overlap = false; + + foreach (var window in display.get_workspace_manager ().get_active_workspace ().list_windows ()) { + if (window == panel.window) { + continue; + } + + if (window.minimized) { + continue; + } + + var type = window.get_window_type (); + if (type == DESKTOP || type == DOCK || type == MENU || type == SPLASHSCREEN) { + continue; + } + + if (!panel.get_custom_window_rect ().overlap (window.get_frame_rect ())) { + continue; + } + + overlap = true; + + if (window != display.focus_window) { + continue; + } + + focus_overlap = true; + focus_maximized_overlap = window.get_maximized () == BOTH; + } + + update_hidden (); + } + + private void update_hidden () { + switch (hide_mode) { + case NEVER: + toggle_display (false); + break; + + case MAXIMIZED_FOCUS_WINDOW: + toggle_display (focus_maximized_overlap); + break; + + case OVERLAPPING_FOCUS_WINDOW: + toggle_display (focus_overlap); + break; + + case OVERLAPPING_WINDOW: + toggle_display (overlap); + break; + + case ALWAYS: + toggle_display (true); + break; + } + } + + private void toggle_display (bool should_hide) { +#if HAS_MUTTER45 + hovered = panel.window.has_pointer (); +#else + hovered = window_has_pointer (); +#endif + + if (should_hide && !hovered) { + // Don't hide if we have transients, e.g. an open popover, dialog, etc. + var has_transients = false; + panel.window.foreach_transient (() => { + has_transients = true; + return false; + }); + + if (has_transients) { + return; + } + + hide (); + } else { + show (); + } + } +} diff --git a/compositor/ShellClients/ManagedClient.vala b/compositor/ShellClients/ManagedClient.vala new file mode 100644 index 000000000..51f579204 --- /dev/null +++ b/compositor/ShellClients/ManagedClient.vala @@ -0,0 +1,87 @@ +/* + * Copyright 2024 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Authored by: Leonhard Kargl + */ + +/** + * Utility class that takes care of launching and restarting a subprocess. + * On wayland this uses a WaylandClient and emits window_created if a window for the client was created. + * On X this just launches a normal subprocess and never emits window_created. + */ +public class GreeterCompositor.ManagedClient : Object { + public signal void window_created (Meta.Window window); + + public Meta.Display display { get; construct; } + public string[] args { get; construct; } + + public Meta.WaylandClient? wayland_client { get; private set; } + + private Subprocess? subprocess; + + public ManagedClient (Meta.Display display, string[] args) { + Object (display: display, args: args); + } + + construct { + if (Meta.Util.is_wayland_compositor ()) { + start_wayland.begin (); + + display.window_created.connect ((window) => { + if (wayland_client != null && wayland_client.owns_window (window)) { + window_created (window); + + // We have to manage is alive manually since windows created by WaylandClients have our pid + // and we don't want to end our own process + window.notify["is-alive"].connect (() => { + if (!window.is_alive && subprocess != null) { + subprocess.force_exit (); + warning ("WaylandClient window became unresponsive, killing the client."); + } + }); + } + }); + } else { + start_x.begin (); + } + } + + private async void start_wayland () { + var subprocess_launcher = new GLib.SubprocessLauncher (STDERR_PIPE | STDOUT_PIPE); + try { +#if HAS_MUTTER44 + wayland_client = new Meta.WaylandClient (display.get_context (), subprocess_launcher); +#else + wayland_client = new Meta.WaylandClient (subprocess_launcher); +#endif + subprocess = wayland_client.spawnv (display, args); + + yield subprocess.wait_async (); + + //Restart the daemon if it crashes + Timeout.add_seconds (1, () => { + start_wayland.begin (); + return Source.REMOVE; + }); + } catch (Error e) { + warning ("Failed to create dock client: %s", e.message); + return; + } + } + + private async void start_x () { + try { + subprocess = new Subprocess.newv (args, NONE); + yield subprocess.wait_async (); + + //Restart the daemon if it crashes + Timeout.add_seconds (1, () => { + start_x.begin (); + return Source.REMOVE; + }); + } catch (Error e) { + warning ("Failed to create daemon subprocess with x: %s", e.message); + } + } +} diff --git a/compositor/ShellClients/NotificationsClient.vala b/compositor/ShellClients/NotificationsClient.vala new file mode 100644 index 000000000..b0819cc4d --- /dev/null +++ b/compositor/ShellClients/NotificationsClient.vala @@ -0,0 +1,34 @@ +/* + * Copyright 2024 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Authored by: Leonhard Kargl + */ + +/** + * Used as a key for Object.set_data on Meta.Windows that should be + * treated as notifications. Has to be set before the window is mapped. + */ +public const string NOTIFICATION_DATA_KEY = "elementary-notification"; + +public class GreeterCompositor.NotificationsClient : Object { + public Meta.Display display { get; construct; } + + private ManagedClient client; + + public NotificationsClient (Meta.Display display) { + Object (display: display); + } + + construct { + client = new ManagedClient (display, { "io.elementary.notifications" }); + + client.window_created.connect ((window) => { + window.set_data (NOTIFICATION_DATA_KEY, true); + window.make_above (); +#if HAS_MUTTER46 + client.wayland_client.make_dock (window); +#endif + }); + } +} diff --git a/compositor/ShellClients/PanelClone.vala b/compositor/ShellClients/PanelClone.vala new file mode 100644 index 000000000..1ac3fc407 --- /dev/null +++ b/compositor/ShellClients/PanelClone.vala @@ -0,0 +1,153 @@ +/* + * Copyright 2024 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Authored by: Leonhard Kargl + */ + +public class GreeterCompositor.PanelClone : Object { + private const int ANIMATION_DURATION = 250; + + public WindowManager wm { get; construct; } + public unowned PanelWindow panel { get; construct; } + + public Pantheon.Desktop.HideMode hide_mode { + get { + return hide_tracker == null ? Pantheon.Desktop.HideMode.NEVER : hide_tracker.hide_mode; + } + set { + if (value == NEVER) { + hide_tracker = null; + show (); + return; + } else if (hide_tracker == null) { + hide_tracker = new HideTracker (wm.get_display (), panel); + hide_tracker.hide.connect (hide); + hide_tracker.show.connect (show); + } + + hide_tracker.hide_mode = value; + } + } + + public bool panel_hidden { get; private set; default = true; } + + private SafeWindowClone clone; + private Meta.WindowActor actor; + + private HideTracker? hide_tracker; + + public PanelClone (WindowManager wm, PanelWindow panel) { + Object (wm: wm, panel: panel); + } + + construct { + clone = new SafeWindowClone (panel.window, true); + wm.ui_group.add_child (clone); + + actor = (Meta.WindowActor) panel.window.get_compositor_private (); + // WindowActor position and Window position aren't necessarily the same. + // The clone needs the actor position + actor.notify["x"].connect (update_clone_position); + actor.notify["y"].connect (update_clone_position); + // Actor visibility might be changed by something else e.g. workspace switch + // but we want to keep it in sync with us + actor.notify["visible"].connect (update_visible); + + notify["panel-hidden"].connect (() => { + update_visible (); + // When hidden changes schedule an update to make sure it's actually + // correct since things might have changed during the animation + if (hide_tracker != null) { + hide_tracker.schedule_update (); + } + }); + + update_visible (); + update_clone_position (); + + Idle.add_once (() => { + if (hide_mode == NEVER) { + show (); + } else { + hide_tracker.schedule_update (); + } + }); + } + + private void update_visible () { + actor.visible = !panel_hidden; + } + + private void update_clone_position () { + clone.set_position (calculate_clone_x (panel_hidden), calculate_clone_y (panel_hidden)); + } + + private float calculate_clone_x (bool hidden) { + switch (panel.anchor) { + case TOP: + case BOTTOM: + return actor.x; + default: + return 0; + } + } + + private float calculate_clone_y (bool hidden) { + switch (panel.anchor) { + case TOP: + return hidden ? actor.y - actor.height : actor.y; + case BOTTOM: + return hidden ? actor.y + actor.height : actor.y; + default: + return 0; + } + } + + private int get_animation_duration () { + var fullscreen = wm.get_display ().get_monitor_in_fullscreen (panel.window.get_monitor ()); + var should_animate = !fullscreen; + return should_animate ? ANIMATION_DURATION : 0; + } + + private void hide () { + if (panel_hidden) { + return; + } + + panel_hidden = true; + + if (panel.anchor != TOP && panel.anchor != BOTTOM) { + warning ("Animated hide not supported for side yet."); + return; + } + + clone.visible = true; + + clone.save_easing_state (); + clone.set_easing_mode (Clutter.AnimationMode.EASE_OUT_QUAD); + clone.set_easing_duration (get_animation_duration ()); + clone.y = calculate_clone_y (true); + clone.restore_easing_state (); + } + + public void show () { + if (!panel_hidden) { + return; + } + + var animation_duration = get_animation_duration (); + + clone.save_easing_state (); + clone.set_easing_mode (Clutter.AnimationMode.EASE_OUT_QUAD); + clone.set_easing_duration (animation_duration); + clone.y = calculate_clone_y (false); + clone.restore_easing_state (); + + Timeout.add (animation_duration, () => { + clone.visible = false; + panel_hidden = false; + return Source.REMOVE; + }); + } +} diff --git a/compositor/ShellClients/PanelWindow.vala b/compositor/ShellClients/PanelWindow.vala new file mode 100644 index 000000000..ae8be427e --- /dev/null +++ b/compositor/ShellClients/PanelWindow.vala @@ -0,0 +1,272 @@ +/* + * Copyright 2024 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Authored by: Leonhard Kargl + */ + +public class GreeterCompositor.PanelWindow : Object { + private const int BARRIER_OFFSET = 50; // Allow hot corner trigger + + private static HashTable window_struts = new HashTable (null, null); + + public WindowManager wm { get; construct; } + public Meta.Window window { get; construct; } + + public bool hidden { get; private set; default = false; } + + public Meta.Side anchor; + + private Barrier? barrier; + + private PanelClone clone; + + private uint idle_move_id = 0; + + private int width = -1; + private int height = -1; + + public PanelWindow (WindowManager wm, Meta.Window window, Meta.Side anchor) { + Object (wm: wm, window: window); + + // Meta.Side seems to be currently not supported as GLib.Object property ...? + // At least it always crashed for me with some paramspec, g_type_fundamental backtrace + this.anchor = anchor; + } + + construct { + window.size_changed.connect (position_window); + + window.unmanaging.connect (() => { + if (idle_move_id != 0) { + Source.remove (idle_move_id); + } + + destroy_barrier (); + + if (window_struts.remove (window)) { + update_struts (); + } + }); + + window.stick (); + + clone = new PanelClone (wm, this); + + var monitor_manager = wm.get_display ().get_context ().get_backend ().get_monitor_manager (); + monitor_manager.monitors_changed.connect (() => update_anchor (anchor)); + + var workspace_manager = wm.get_display ().get_workspace_manager (); + workspace_manager.workspace_added.connect (update_strut); + workspace_manager.workspace_removed.connect (update_strut); + } + +#if HAS_MUTTER46 + public Mtk.Rectangle get_custom_window_rect () { +#else + public Meta.Rectangle get_custom_window_rect () { +#endif + var window_rect = window.get_frame_rect (); + + if (width > 0) { + window_rect.width = width; + } + + if (height > 0) { + window_rect.height = height; + } + + return window_rect; + } + + public void set_size (int width, int height) { + this.width = width; + this.height = height; + + position_window (); + set_hide_mode (clone.hide_mode); // Resetup barriers etc. + } + + public void update_anchor (Meta.Side anchor) { + this.anchor = anchor; + + position_window (); + set_hide_mode (clone.hide_mode); // Resetup barriers etc. + } + + private void position_window () { + var display = wm.get_display (); + var monitor_geom = display.get_monitor_geometry (display.get_primary_monitor ()); + var window_rect = get_custom_window_rect (); + + switch (anchor) { + case TOP: + position_window_top (monitor_geom, window_rect); + break; + + case BOTTOM: + position_window_bottom (monitor_geom, window_rect); + break; + + default: + warning ("Side not supported yet"); + break; + } + + update_strut (); + } + +#if HAS_MUTTER45 + private void position_window_top (Mtk.Rectangle monitor_geom, Mtk.Rectangle window_rect) { +#else + private void position_window_top (Meta.Rectangle monitor_geom, Meta.Rectangle window_rect) { +#endif + var x = monitor_geom.x + (monitor_geom.width - window_rect.width) / 2; + + move_window_idle (x, monitor_geom.y); + } + +#if HAS_MUTTER45 + private void position_window_bottom (Mtk.Rectangle monitor_geom, Mtk.Rectangle window_rect) { +#else + private void position_window_bottom (Meta.Rectangle monitor_geom, Meta.Rectangle window_rect) { +#endif + var x = monitor_geom.x + (monitor_geom.width - window_rect.width) / 2; + var y = monitor_geom.y + monitor_geom.height - window_rect.height; + + move_window_idle (x, y); + } + + private void move_window_idle (int x, int y) { + if (idle_move_id != 0) { + Source.remove (idle_move_id); + } + + idle_move_id = Idle.add (() => { + window.move_frame (false, x, y); + + idle_move_id = 0; + return Source.REMOVE; + }); + } + + public void set_hide_mode (Pantheon.Desktop.HideMode hide_mode) { + clone.hide_mode = hide_mode; + + destroy_barrier (); + + if (hide_mode == NEVER) { + make_exclusive (); + } else { + unmake_exclusive (); + setup_barrier (); + } + } + + private void make_exclusive () { + update_strut (); + } + + private void update_strut () { + if (clone.hide_mode != NEVER) { + return; + } + + var rect = get_custom_window_rect (); + + Meta.Strut strut = { + rect, + anchor + }; + + window_struts[window] = strut; + + update_struts (); + } + + private void update_struts () { + var list = new SList (); + + foreach (var window_strut in window_struts.get_values ()) { + list.append (window_strut); + } + + foreach (var workspace in wm.get_display ().get_workspace_manager ().get_workspaces ()) { + workspace.set_builtin_struts (list); + } + } + + private void unmake_exclusive () { + if (window in window_struts) { + window_struts.remove (window); + update_struts (); + } + } + + private void destroy_barrier () { + barrier = null; + } + + private void setup_barrier () { + var display = wm.get_display (); + var monitor_geom = display.get_monitor_geometry (display.get_primary_monitor ()); + var scale = display.get_monitor_scale (display.get_primary_monitor ()); + var offset = Utils.scale_to_int (BARRIER_OFFSET, scale); + + switch (anchor) { + case TOP: + setup_barrier_top (monitor_geom, offset); + break; + + case BOTTOM: + setup_barrier_bottom (monitor_geom, offset); + break; + + default: + warning ("Barrier side not supported yet"); + break; + } + } + +#if HAS_MUTTER45 + private void setup_barrier_top (Mtk.Rectangle monitor_geom, int offset) { +#else + private void setup_barrier_top (Meta.Rectangle monitor_geom, int offset) { +#endif + barrier = new Barrier ( + wm.get_display ().get_context ().get_backend (), + monitor_geom.x + offset, + monitor_geom.y, + monitor_geom.x + monitor_geom.width - offset, + monitor_geom.y, + POSITIVE_Y, + 0, + 0, + int.MAX, + int.MAX + ); + + barrier.trigger.connect (clone.show); + } + +#if HAS_MUTTER45 + private void setup_barrier_bottom (Mtk.Rectangle monitor_geom, int offset) { +#else + private void setup_barrier_bottom (Meta.Rectangle monitor_geom, int offset) { +#endif + barrier = new Barrier ( + wm.get_display ().get_context ().get_backend (), + monitor_geom.x + offset, + monitor_geom.y + monitor_geom.height, + monitor_geom.x + monitor_geom.width - offset, + monitor_geom.y + monitor_geom.height, + NEGATIVE_Y, + 0, + 0, + int.MAX, + int.MAX + ); + + barrier.trigger.connect (clone.show); + } +} diff --git a/compositor/ShellClients/ShellClientsManager.vala b/compositor/ShellClients/ShellClientsManager.vala new file mode 100644 index 000000000..4256c7b81 --- /dev/null +++ b/compositor/ShellClients/ShellClientsManager.vala @@ -0,0 +1,209 @@ +/* + * Copyright 2024 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Authored by: Leonhard Kargl + */ + +public class GreeterCompositor.ShellClientsManager : Object { + private static ShellClientsManager instance; + + public static void init (WindowManager wm) { + if (instance != null) { + return; + } + + instance = new ShellClientsManager (wm); + } + + public static ShellClientsManager? get_instance () { + return instance; + } + + public WindowManager wm { get; construct; } + + private NotificationsClient notifications_client; + private ManagedClient[] protocol_clients = {}; + + private GLib.HashTable windows = new GLib.HashTable (null, null); + private GLib.HashTable centered_windows = new GLib.HashTable (null, null); + + private ShellClientsManager (WindowManager wm) { + Object (wm: wm); + } + + construct { + notifications_client = new NotificationsClient (wm.get_display ()); + + start_clients.begin (); + + if (!Meta.Util.is_wayland_compositor ()) { + wm.get_display ().window_created.connect ((window) => { + window.notify["mutter-hints"].connect ((obj, pspec) => parse_mutter_hints ((Meta.Window) obj)); + parse_mutter_hints (window); + }); + } + } + + private async void start_clients () { + protocol_clients += new ManagedClient (wm.get_display (), { "io.elementary.wingpanel", "-g" }); + } + + public void make_dock (Meta.Window window) { + if (Meta.Util.is_wayland_compositor ()) { + make_dock_wayland (window); + } else { + make_dock_x11 (window); + } + } + + private void make_dock_wayland (Meta.Window window) requires (Meta.Util.is_wayland_compositor ()) { + foreach (var client in protocol_clients) { + if (client.wayland_client.owns_window (window)) { + client.wayland_client.make_dock (window); + break; + } + } + } + + private void make_dock_x11 (Meta.Window window) requires (!Meta.Util.is_wayland_compositor ()) { + unowned var x11_display = wm.get_display ().get_x11_display (); + +#if HAS_MUTTER46 + var x_window = x11_display.lookup_xwindow (window); +#else + var x_window = window.get_xwindow (); +#endif + // gtk3's gdk_x11_window_set_type_hint() is used as a reference + unowned var xdisplay = x11_display.get_xdisplay (); + var atom = xdisplay.intern_atom ("_NET_WM_WINDOW_TYPE", false); + var dock_atom = xdisplay.intern_atom ("_NET_WM_WINDOW_TYPE_DOCK", false); + + // (X.Atom) 4 is XA_ATOM + // 32 is format + // 0 means replace + xdisplay.change_property (x_window, atom, (X.Atom) 4, 32, 0, (uchar[]) dock_atom, 1); + } + + public void set_anchor (Meta.Window window, Meta.Side side) { + if (window in windows) { + windows[window].update_anchor (side); + return; + } + + make_dock (window); + // TODO: Return if requested by window that's not a trusted client? + + windows[window] = new PanelWindow (wm, window, side); + + // connect_after so we make sure the PanelWindow can destroy its barriers and struts + window.unmanaging.connect_after (() => windows.remove (window)); + } + + /** + * The size given here is only used for the hide mode. I.e. struts + * and collision detection with other windows use this size. By default + * or if set to -1 the size of the window is used. + * + * TODO: Maybe use for strut only? + */ + public void set_size (Meta.Window window, int width, int height) { + if (!(window in windows)) { + warning ("Set anchor for window before size."); + return; + } + + windows[window].set_size (width, height); + } + + public void set_hide_mode (Meta.Window window, Pantheon.Desktop.HideMode hide_mode) { + if (!(window in windows)) { + warning ("Set anchor for window before hide mode."); + return; + } + + windows[window].set_hide_mode (hide_mode); + } + + public void make_centered (Meta.Window window) { + if (window in centered_windows) { + return; + } + + centered_windows[window] = new CenteredWindow (wm, window); + + window.unmanaging.connect_after (() => centered_windows.remove (window)); + } + + public bool is_positioned_window (Meta.Window window) { + bool positioned = (window in centered_windows) || (window in windows); + window.foreach_ancestor ((ancestor) => { + if (ancestor in centered_windows || ancestor in windows) { + positioned = true; + } + + return !positioned; + }); + + return positioned; + } + + //X11 only + private void parse_mutter_hints (Meta.Window window) requires (!Meta.Util.is_wayland_compositor ()) { + if (window.mutter_hints == null) { + return; + } + + var mutter_hints = window.mutter_hints.split (":"); + foreach (var mutter_hint in mutter_hints) { + var split = mutter_hint.split ("="); + + if (split.length != 2) { + continue; + } + + var key = split[0]; + var val = split[1]; + + switch (key) { + case "anchor": + int parsed; // Will be used as Meta.Side which is a 4 value bitfield so check bounds for that + if (int.try_parse (val, out parsed) && 0 <= parsed && parsed <= 15) { + set_anchor (window, parsed); + } else { + warning ("Failed to parse %s as anchor", val); + } + break; + + case "hide-mode": + int parsed; // Will be used as Pantheon.Desktop.HideMode which is a 5 value enum so check bounds for that + if (int.try_parse (val, out parsed) && 0 <= parsed && parsed <= 4) { + set_hide_mode (window, parsed); + } else { + warning ("Failed to parse %s as hide mode", val); + } + break; + + case "size": + var split_val = val.split (","); + if (split_val.length != 2) { + break; + } + int parsed_width, parsed_height = 0; //set to 0 because vala doesn't realize height will be set too + if (int.try_parse (split_val[0], out parsed_width) && int.try_parse (split_val[1], out parsed_height)) { + set_size (window, parsed_width, parsed_height); + } else { + warning ("Failed to parse %s as width and height", val); + } + break; + + case "centered": + make_centered (window); + break; + + default: + break; + } + } + } +} diff --git a/compositor/Widgets/SafeWindowClone.vala b/compositor/Widgets/SafeWindowClone.vala new file mode 100644 index 000000000..ff11abf72 --- /dev/null +++ b/compositor/Widgets/SafeWindowClone.vala @@ -0,0 +1,66 @@ +// +// Copyright (C) 2014 Tom Beckmann +// +// 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 3 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 . +// + +namespace GreeterCompositor { + /** + * A clone for a MetaWindowActor that will guard against the + * meta_window_appears_focused crash by disabling painting the clone + * as soon as it gets unavailable. + */ + public class SafeWindowClone : Clutter.Clone { + public Meta.Window window { get; construct; } + + /** + * If set to true, the SafeWindowClone will destroy itself when the connected + * window is unmanaged + */ + public bool destroy_on_unmanaged { get; construct set; default = false; } + + /** + * Creates a new SafeWindowClone + * + * @param window The window to clone from + * @param destroy_on_unmanaged see destroy_on_unmanaged property + */ + public SafeWindowClone (Meta.Window window, bool destroy_on_unmanaged = false) { + var actor = (Meta.WindowActor) window.get_compositor_private (); + + Object (window: window, + source: actor, + destroy_on_unmanaged: destroy_on_unmanaged); + } + + construct { + if (source != null) + window.unmanaged.connect (reset_source); + } + + ~SafeWindowClone () { + window.unmanaged.disconnect (reset_source); + } + + private void reset_source () { + // actually destroying the clone will be handled somewhere else (unless we were + // requested to destroy it), we just need to make sure the clone doesn't attempt + // to draw a clone of a window that has been destroyed + source = null; + + if (destroy_on_unmanaged) + destroy (); + } + } +} diff --git a/compositor/WindowManager.vala b/compositor/WindowManager.vala index 6071ba419..7cf75e3ec 100644 --- a/compositor/WindowManager.vala +++ b/compositor/WindowManager.vala @@ -211,7 +211,7 @@ namespace GreeterCompositor { // let the session manager move to the next phase display.get_context ().notify_ready (); start_command.begin ({ "io.elementary.greeter" }); - start_command.begin ({ "io.elementary.wingpanel", "-g" }); + ShellClientsManager.init (this); return GLib.Source.REMOVE; }); } diff --git a/compositor/meson.build b/compositor/meson.build index db72ec7f0..729d44274 100644 --- a/compositor/meson.build +++ b/compositor/meson.build @@ -84,6 +84,16 @@ compositor_files = files( 'Background/BackgroundSource.vala', 'Background/BlurEffect.vala', 'Background/SystemBackground.vala', + 'HotCorners/Barrier.vala', + 'PantheonShell.vala', + 'ShellClients/CenteredWindow.vala', + 'ShellClients/HideTracker.vala', + 'ShellClients/ManagedClient.vala', + 'ShellClients/NotificationsClient.vala', + 'ShellClients/PanelClone.vala', + 'ShellClients/PanelWindow.vala', + 'ShellClients/ShellClientsManager.vala', + 'Widgets/SafeWindowClone.vala', 'WingpanelManager/WingpanelManager.vala', 'WingpanelManager/DBusWingpanelManager.vala', 'WingpanelManager/FocusManager.vala', @@ -105,7 +115,7 @@ executable( compositor_files, compositor_resources, config_header, - dependencies: [glib_dep, gtk_dep, gee_dep, m_dep, posix_dep, mutter_dep, gnome_desktop_dep], + dependencies: [glib_dep, gtk_dep, gee_dep, m_dep, posix_dep, mutter_dep, gnome_desktop_dep, pantheon_desktop_shell_dep], vala_args: vala_flags, c_args: compositor_c_args, build_rpath: mutter_typelib_dir, diff --git a/meson.build b/meson.build index 647178008..4820b6fc0 100644 --- a/meson.build +++ b/meson.build @@ -7,6 +7,7 @@ project( gnome = import('gnome') i18n = import('i18n') +vala = meson.get_compiler('vala') conf_data = configuration_data() conf_data.set('CONF_DIR', join_paths(get_option('sysconfdir'), 'lightdm')) @@ -47,6 +48,7 @@ compositor_resources = gnome.compile_resources( source_dir: 'data' ) +subdir('protocol') subdir('src') subdir('compositor') subdir('data') diff --git a/protocol/meson.build b/protocol/meson.build new file mode 100644 index 000000000..771af1a0f --- /dev/null +++ b/protocol/meson.build @@ -0,0 +1,32 @@ +dep_scanner = dependency('wayland-scanner', native: true) +prog_scanner = find_program(dep_scanner.get_variable(pkgconfig: 'wayland_scanner')) + +protocol_file = files('pantheon-desktop-shell-v1.xml') + +pantheon_desktop_shell_sources = [] +pantheon_desktop_shell_sources += custom_target( + 'pantheon-desktop-shell-server-protocol.h', + command: [ prog_scanner, 'server-header', '@INPUT@', '@OUTPUT@' ], + input: protocol_file, + output: 'pantheon-desktop-shell-server-protocol.h', +) + +output_type = 'private-code' +if dep_scanner.version().version_compare('< 1.14.91') + output_type = 'code' +endif +pantheon_desktop_shell_sources += custom_target( + 'pantheon-desktop-shell-protocol.c', + command: [ prog_scanner, output_type, '@INPUT@', '@OUTPUT@' ], + input: protocol_file, + output: 'pantheon-desktop-shell-protocol.c', +) + +pantheon_desktop_shell_dep = declare_dependency( + dependencies: [ + vala.find_library('pantheon-desktop-shell', dirs: meson.current_source_dir()), + dependency('wayland-server'), + ], + include_directories: include_directories('.'), + sources: pantheon_desktop_shell_sources +) diff --git a/protocol/pantheon-desktop-shell-v1.xml b/protocol/pantheon-desktop-shell-v1.xml new file mode 100644 index 000000000..63d9d5982 --- /dev/null +++ b/protocol/pantheon-desktop-shell-v1.xml @@ -0,0 +1,131 @@ + + + + + SPDX-License-Identifier: LGPL-2.1-or-later + ]]> + + + + This interface is used by the Pantheon Wayland shell to communicate with + the compositor. + + + + + Create a panel surface from an existing surface. + + + + + + + + Create a desktop widget surface from an existing surface. + + + + + + + + Create a desktop-specific surface from an existing surface. + + + + + + + + + + + + The anchor is a placement hint to the compositor. + + + + + + + + + + How the shell should handle the window. + + + + + + + + + + + Tell the shell which side of the screen the panel is + located. This is so that new windows do not overlap the panel + and maximized windows maximize properly. + + + + + + + + Request keyboard focus, taking it away from any other window. + Keyboard focus must always be manually be requested and is + - in contrast to normal windows - never automatically granted + by the compositor. + + + + + + The given size is only used for exclusive zones and + collision tracking for auto hide. By default and if set + to -1 the size of the surface is used. + + + + + + + + + Tell the shell when to hide the panel. + + + + + + + + + + + + + + + Tell the shell to keep the surface above on all workspaces + + + + + + Request to keep the surface centered. This will cause keyboard focus + to not be granted automatically but having to be requested via focus. + + + + + + Request keyboard focus, taking it away from any other window. + Keyboard focus must always be manually be requested and is + - in contrast to normal windows - never automatically granted + by the compositor. + + + + diff --git a/protocol/pantheon-desktop-shell.deps b/protocol/pantheon-desktop-shell.deps new file mode 100644 index 000000000..62acb1e0a --- /dev/null +++ b/protocol/pantheon-desktop-shell.deps @@ -0,0 +1 @@ +wayland-server diff --git a/protocol/pantheon-desktop-shell.vapi b/protocol/pantheon-desktop-shell.vapi new file mode 100644 index 000000000..4c57de974 --- /dev/null +++ b/protocol/pantheon-desktop-shell.vapi @@ -0,0 +1,83 @@ +/* + * Copyright 2023 elementary, Inc. + * Copyright 2023 Corentin Noël + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +namespace Pantheon.Desktop { + [CCode (cheader_filename = "pantheon-desktop-shell-server-protocol.h", cname = "struct io_elementary_pantheon_shell_v1_interface")] + public struct ShellInterface { + [CCode (cheader_filename = "pantheon-desktop-shell-server-protocol.h", cname = "io_elementary_pantheon_shell_v1_interface")] + public static Wl.Interface iface; + public Pantheon.Desktop.GetPanel get_panel; + public Pantheon.Desktop.GetWidget get_widget; + public Pantheon.Desktop.GetExtendedBehavior get_extended_behavior; + + } + + [CCode (cheader_filename = "pantheon-desktop-shell-server-protocol.h", cname = "enum io_elementary_pantheon_panel_v1_anchor", cprefix="IO_ELEMENTARY_PANTHEON_PANEL_V1_ANCHOR_", has_type_id = false)] + public enum Anchor { + TOP, + BOTTOM, + LEFT, + RIGHT, + } + + [CCode (cheader_filename = "pantheon-desktop-shell-server-protocol.h", cname = "enum io_elementary_pantheon_panel_v1_hide_mode", cprefix="IO_ELEMENTARY_PANTHEON_PANEL_V1_HIDE_MODE_", has_type_id = false)] + public enum HideMode { + NEVER, + MAXIMIZED_FOCUS_WINDOW, + OVERLAPPING_FOCUS_WINDOW, + OVERLAPPING_WINDOW, + ALWAYS + } + + [CCode (cheader_filename = "pantheon-desktop-shell-server-protocol.h", cname = "struct io_elementary_pantheon_panel_v1_interface")] + public struct PanelInterface { + [CCode (cheader_filename = "pantheon-desktop-shell-server-protocol.h", cname = "io_elementary_pantheon_panel_v1_interface")] + public static Wl.Interface iface; + public Destroy destroy; + public SetAnchor set_anchor; + public Focus focus; + public SetSize set_size; + public SetHideMode set_hide_mode; + } + + [CCode (cheader_filename = "pantheon-desktop-shell-server-protocol.h", cname = "struct io_elementary_pantheon_widget_v1_interface")] + public struct WidgetInterface { + [CCode (cheader_filename = "pantheon-desktop-shell-server-protocol.h", cname = "io_elementary_pantheon_widget_v1_interface")] + public static Wl.Interface iface; + public Destroy destroy; + } + + [CCode (cheader_filename = "pantheon-desktop-shell-server-protocol.h", cname = "struct io_elementary_pantheon_extended_behavior_v1_interface")] + public struct ExtendedBehaviorInterface { + [CCode (cheader_filename = "pantheon-desktop-shell-server-protocol.h", cname = "io_elementary_pantheon_extended_behavior_v1_interface")] + public static Wl.Interface iface; + public Destroy destroy; + public SetKeepAbove set_keep_above; + public MakeCentered make_centered; + public Focus focus; + } + + [CCode (has_target = false, has_typedef = false)] + public delegate void GetPanel (Wl.Client client, Wl.Resource resource, uint32 output, Wl.Resource surface); + [CCode (has_target = false, has_typedef = false)] + public delegate void GetWidget (Wl.Client client, Wl.Resource resource, uint32 output, Wl.Resource surface); + [CCode (has_target = false, has_typedef = false)] + public delegate void GetExtendedBehavior (Wl.Client client, Wl.Resource resource, uint32 output, Wl.Resource surface); + [CCode (has_target = false, has_typedef = false)] + public delegate void SetAnchor (Wl.Client client, Wl.Resource resource, [CCode (type = "uint32_t")] Anchor anchor); + [CCode (has_target = false, has_typedef = false)] + public delegate void Focus (Wl.Client client, Wl.Resource resource); + [CCode (has_target = false, has_typedef = false)] + public delegate void SetSize (Wl.Client client, Wl.Resource resource, int width, int height); + [CCode (has_target = false, has_typedef = false)] + public delegate void SetHideMode (Wl.Client client, Wl.Resource resource, [CCode (type = "uint32_t")] HideMode hide_mode); + [CCode (has_target = false, has_typedef = false)] + public delegate void SetKeepAbove (Wl.Client client, Wl.Resource resource); + [CCode (has_target = false, has_typedef = false)] + public delegate void MakeCentered (Wl.Client client, Wl.Resource resource); + [CCode (has_target = false, has_typedef = false)] + public delegate void Destroy (Wl.Client client, Wl.Resource resource); +} diff --git a/vapi/wayland-server.deps b/vapi/wayland-server.deps new file mode 100644 index 000000000..b3188f742 --- /dev/null +++ b/vapi/wayland-server.deps @@ -0,0 +1 @@ +posix diff --git a/vapi/wayland-server.vapi b/vapi/wayland-server.vapi new file mode 100644 index 000000000..6fa1885b3 --- /dev/null +++ b/vapi/wayland-server.vapi @@ -0,0 +1,125 @@ +/* wayland-server.vapi + * + * Copyright 2022 Corentin Noël + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Authors: + * Corentin Noël + */ + +[CCode (cprefix = "wl_", lower_case_cprefix = "wl_", cheader_filename = "wayland-server.h")] +namespace Wl { + [Compact] + [CCode (cname = "struct wl_display", free_function = "wl_display_destroy")] + public class Display { + [CCode (cname = "wl_display_create")] + public Display (); + public int add_socket (string name); + public unowned string add_socket_auto (); + public int add_socket_fd (int sock_fd); + public void terminate (); + public void run (); + public void flush_clients (); + public void destroy_clients (); + public uint32 get_serial (); + public uint32 next_serial (); + } + + [Compact] + [CCode (cname = "struct wl_client", free_function = "wl_client_destroy")] + public class Client { + [CCode (cname = "wl_client_create")] + public Client (Wl.Display display, int fd); + public void flush (); + public void get_credentials (out Posix.pid_t pid, out Posix.uid_t uid, out Posix.gid_t gid); + public int get_fd (); + public unowned Wl.Display get_display (); + [CCode (cname = "wl_resource_create")] + public unowned Wl.Resource? create_resource (ref Wl.Interface interface, int version, uint32 id); + } + + [Compact] + [CCode (cname = "struct wl_resource", free_function = "wl_resource_destroy")] + public class Resource { + public uint32 get_id (); + public unowned Wl.Client get_client (); + [CCode (simple_generics = true)] + public void set_user_data (T? data); + [CCode (simple_generics = true)] + public unowned T? get_user_data (); + public int get_version (); + public unowned string get_class (); + public void destroy (); + public void set_implementation (void* implementation, void* data, [CCode (delegate_target = false)] ResourceDestroyFunc destroy); + [PrintfFormat] + public void post_error(uint32 code, string format, ...); + } + [Compact] + [CCode (cname = "struct wl_interface")] + public class Interface { + public string name; + public int version; + [CCode (array_length = "method_count")] + public Wl.Message[] methods; + [CCode (array_length = "event_count")] + public Wl.Message[] events; + } + + [Compact] + [CCode (cname = "struct wl_message")] + public class Message { + public string name; + public string signature; + [CCode (array_length = false)] + public Wl.Interface?[] types; + } + + [Compact] + [CCode (cname = "struct wl_global", free_function = "wl_global_destroy")] + public class Global { + [CCode (cname = "wl_global_create")] + public static Wl.Global? create (Wl.Display display, ref Wl.Interface interface, int version, [CCode (delegate_target_pos = 3.9) ] Wl.GlobalBindFunc bind); + } + + [CCode (cheader_filename = "wayland-server-protocol.h", cname = "enum wl_display_error", cprefix="WL_DISPLAY_ERROR_", has_type_id = false)] + public enum DisplayError { + INVALID_OBJECT, + INVALID_METHOD, + NO_MEMORY, + IMPLEMENTATION, + } + + [CCode (cname = "wl_global_bind_func_t", instance_pos = 1.9)] + public delegate void GlobalBindFunc (Wl.Client client, uint32 version, uint32 id); + [CCode (cname = "wl_resource_destroy_func_t", has_target = false)] + public delegate void ResourceDestroyFunc (Wl.Resource resource); + [CCode (cname = "WAYLAND_VERSION_MAJOR")] + public const int VERSION_MAJOR; + [CCode (cname = "WAYLAND_VERSION_MINOR")] + public const int VERSION_MINOR; + [CCode (cname = "WAYLAND_VERSION_MICRO")] + public const int VERSION_MICRO; + [CCode (cname = "WAYLAND_VERSION")] + public const string VERSION; +} + From ba444820033fdf5ffd88882a41a965559c4c317b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Tue, 27 Aug 2024 10:21:55 -0700 Subject: [PATCH 2/6] Remove unused HideTracker --- compositor/ShellClients/HideTracker.vala | 201 ----------------------- compositor/ShellClients/PanelClone.vala | 27 +-- compositor/meson.build | 1 - 3 files changed, 3 insertions(+), 226 deletions(-) delete mode 100644 compositor/ShellClients/HideTracker.vala diff --git a/compositor/ShellClients/HideTracker.vala b/compositor/ShellClients/HideTracker.vala deleted file mode 100644 index 428f72223..000000000 --- a/compositor/ShellClients/HideTracker.vala +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright 2024 elementary, Inc. (https://elementary.io) - * SPDX-License-Identifier: GPL-3.0-or-later - * - * Authored by: Leonhard Kargl - */ - -public class GreeterCompositor.HideTracker : Object { - private const uint UPDATE_TIMEOUT = 200; - - public signal void hide (); - public signal void show (); - - public Meta.Display display { get; construct; } - public unowned PanelWindow panel { get; construct; } - public Pantheon.Desktop.HideMode hide_mode { get; set; default = NEVER; } - - private bool hovered = false; - - private bool overlap = false; - private bool focus_overlap = false; - private bool focus_maximized_overlap = false; - - private Meta.Window current_focus_window; - - private uint update_timeout_id = 0; - - public HideTracker (Meta.Display display, PanelWindow panel) { - Object (display: display, panel: panel); - } - - construct { - // Can't be local otherwise we get a memory leak :( - // See https://gitlab.gnome.org/GNOME/vala/-/issues/1548 - current_focus_window = display.focus_window; - track_focus_window (current_focus_window); - display.notify["focus-window"].connect (() => { - untrack_focus_window (current_focus_window); - current_focus_window = display.focus_window; - track_focus_window (current_focus_window); - }); - - display.window_created.connect ((window) => { - schedule_update (); - window.unmanaged.connect (schedule_update); - }); - - var cursor_tracker = display.get_cursor_tracker (); - cursor_tracker.position_invalidated.connect (() => { -#if HAS_MUTTER45 - var has_pointer = panel.window.has_pointer (); -#else - var has_pointer = window_has_pointer (); -#endif - - if (hovered != has_pointer) { - hovered = has_pointer; - schedule_update (); - } - }); - - display.get_workspace_manager ().active_workspace_changed.connect (schedule_update); - } - - //Can be removed with mutter > 45 - private bool window_has_pointer () { - var cursor_tracker = display.get_cursor_tracker (); - Graphene.Point pointer_pos; - cursor_tracker.get_pointer (out pointer_pos, null); - - var window_rect = panel.get_custom_window_rect (); - Graphene.Rect graphene_window_rect = { - { - window_rect.x, - window_rect.y - }, - { - window_rect.width, - window_rect.height - } - }; - return graphene_window_rect.contains_point (pointer_pos); - } - - private void track_focus_window (Meta.Window? window) { - if (window == null) { - return; - } - - window.position_changed.connect (schedule_update); - window.size_changed.connect (schedule_update); - schedule_update (); - } - - private void untrack_focus_window (Meta.Window? window) { - if (window == null) { - return; - } - - window.position_changed.disconnect (schedule_update); - window.size_changed.disconnect (schedule_update); - schedule_update (); - } - - public void schedule_update () { - if (update_timeout_id != 0) { - return; - } - - update_timeout_id = Timeout.add (UPDATE_TIMEOUT, () => { - update_overlap (); - update_timeout_id = 0; - return Source.REMOVE; - }); - } - - private void update_overlap () { - overlap = false; - focus_overlap = false; - focus_maximized_overlap = false; - - foreach (var window in display.get_workspace_manager ().get_active_workspace ().list_windows ()) { - if (window == panel.window) { - continue; - } - - if (window.minimized) { - continue; - } - - var type = window.get_window_type (); - if (type == DESKTOP || type == DOCK || type == MENU || type == SPLASHSCREEN) { - continue; - } - - if (!panel.get_custom_window_rect ().overlap (window.get_frame_rect ())) { - continue; - } - - overlap = true; - - if (window != display.focus_window) { - continue; - } - - focus_overlap = true; - focus_maximized_overlap = window.get_maximized () == BOTH; - } - - update_hidden (); - } - - private void update_hidden () { - switch (hide_mode) { - case NEVER: - toggle_display (false); - break; - - case MAXIMIZED_FOCUS_WINDOW: - toggle_display (focus_maximized_overlap); - break; - - case OVERLAPPING_FOCUS_WINDOW: - toggle_display (focus_overlap); - break; - - case OVERLAPPING_WINDOW: - toggle_display (overlap); - break; - - case ALWAYS: - toggle_display (true); - break; - } - } - - private void toggle_display (bool should_hide) { -#if HAS_MUTTER45 - hovered = panel.window.has_pointer (); -#else - hovered = window_has_pointer (); -#endif - - if (should_hide && !hovered) { - // Don't hide if we have transients, e.g. an open popover, dialog, etc. - var has_transients = false; - panel.window.foreach_transient (() => { - has_transients = true; - return false; - }); - - if (has_transients) { - return; - } - - hide (); - } else { - show (); - } - } -} diff --git a/compositor/ShellClients/PanelClone.vala b/compositor/ShellClients/PanelClone.vala index 1ac3fc407..a45d806bd 100644 --- a/compositor/ShellClients/PanelClone.vala +++ b/compositor/ShellClients/PanelClone.vala @@ -13,20 +13,10 @@ public class GreeterCompositor.PanelClone : Object { public Pantheon.Desktop.HideMode hide_mode { get { - return hide_tracker == null ? Pantheon.Desktop.HideMode.NEVER : hide_tracker.hide_mode; + return NEVER; } set { - if (value == NEVER) { - hide_tracker = null; - show (); - return; - } else if (hide_tracker == null) { - hide_tracker = new HideTracker (wm.get_display (), panel); - hide_tracker.hide.connect (hide); - hide_tracker.show.connect (show); - } - - hide_tracker.hide_mode = value; + show (); } } @@ -35,8 +25,6 @@ public class GreeterCompositor.PanelClone : Object { private SafeWindowClone clone; private Meta.WindowActor actor; - private HideTracker? hide_tracker; - public PanelClone (WindowManager wm, PanelWindow panel) { Object (wm: wm, panel: panel); } @@ -56,22 +44,13 @@ public class GreeterCompositor.PanelClone : Object { notify["panel-hidden"].connect (() => { update_visible (); - // When hidden changes schedule an update to make sure it's actually - // correct since things might have changed during the animation - if (hide_tracker != null) { - hide_tracker.schedule_update (); - } }); update_visible (); update_clone_position (); Idle.add_once (() => { - if (hide_mode == NEVER) { - show (); - } else { - hide_tracker.schedule_update (); - } + show (); }); } diff --git a/compositor/meson.build b/compositor/meson.build index 729d44274..d6a01c0f1 100644 --- a/compositor/meson.build +++ b/compositor/meson.build @@ -87,7 +87,6 @@ compositor_files = files( 'HotCorners/Barrier.vala', 'PantheonShell.vala', 'ShellClients/CenteredWindow.vala', - 'ShellClients/HideTracker.vala', 'ShellClients/ManagedClient.vala', 'ShellClients/NotificationsClient.vala', 'ShellClients/PanelClone.vala', From c77c62bad452526158bba8b7c6629a316352941d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Tue, 27 Aug 2024 10:23:15 -0700 Subject: [PATCH 3/6] Remove unused barrier --- compositor/HotCorners/Barrier.vala | 94 ------------------------ compositor/ShellClients/PanelWindow.vala | 74 ------------------- compositor/meson.build | 1 - 3 files changed, 169 deletions(-) delete mode 100644 compositor/HotCorners/Barrier.vala diff --git a/compositor/HotCorners/Barrier.vala b/compositor/HotCorners/Barrier.vala deleted file mode 100644 index 580d976a0..000000000 --- a/compositor/HotCorners/Barrier.vala +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2024 elementary, Inc. (https://elementary.io) - * SPDX-License-Identifier: GPL-3.0-or-later - */ - - /** - * A pointer barrier supporting pressured activation. - */ -public class GreeterCompositor.Barrier : Object { - public signal void trigger (); - - public bool triggered { get; set; default = false; } - - public int trigger_pressure_threshold { get; construct; } - public int release_pressure_threshold { get; construct; } - public int retrigger_pressure_threshold { get; construct; } - public int retrigger_delay { get; construct; } - - private Meta.Barrier barrier; - - private uint32 triggered_time; - private double pressure; - - /** - * @param trigger_pressure_threshold The amount of pixels to travel additionally for - * the barrier to trigger. Set to 0 to immediately activate. - * @param retrigger_pressure_threshold The amount of pixels to travel additionally for - * the barrier to trigger again. Set to int.MAX to disallow retrigger. - */ - public Barrier ( - Meta.Backend backend, - int x1, - int y1, - int x2, - int y2, - Meta.BarrierDirection directions, - int trigger_pressure_threshold, - int release_pressure_threshold, - int retrigger_pressure_threshold, - int retrigger_delay - ) { - Object ( - trigger_pressure_threshold: trigger_pressure_threshold, - release_pressure_threshold: release_pressure_threshold, - retrigger_pressure_threshold: retrigger_pressure_threshold, - retrigger_delay: retrigger_delay - ); - - try { - barrier = new Meta.Barrier (backend, x1, y1, x2, y2, directions, Meta.BarrierFlags.NONE); - barrier.hit.connect (on_hit); - barrier.left.connect (on_left); - } catch (Error e) { - warning ("Failed to create Meta Barrier"); - } - } - - ~Barrier () { - barrier.destroy (); - } - - private void on_hit (Meta.BarrierEvent event) { - if (POSITIVE_X in barrier.directions || NEGATIVE_X in barrier.directions) { - pressure += event.dx.abs (); - } else { - pressure += event.dy.abs (); - } - - if (!triggered && pressure > trigger_pressure_threshold) { - emit_trigger (event.time); - } - - if (!triggered && pressure > release_pressure_threshold) { - barrier.release (event); - } - - if (triggered && pressure.abs () > retrigger_pressure_threshold && event.time > retrigger_delay + triggered_time) { - emit_trigger (event.time); - } - } - - private void emit_trigger (uint32 time) { - triggered = true; - pressure = 0; - triggered_time = time; - - trigger (); - } - - private void on_left () { - pressure = 0; - triggered = false; - } -} diff --git a/compositor/ShellClients/PanelWindow.vala b/compositor/ShellClients/PanelWindow.vala index ae8be427e..422013b0d 100644 --- a/compositor/ShellClients/PanelWindow.vala +++ b/compositor/ShellClients/PanelWindow.vala @@ -17,8 +17,6 @@ public class GreeterCompositor.PanelWindow : Object { public Meta.Side anchor; - private Barrier? barrier; - private PanelClone clone; private uint idle_move_id = 0; @@ -42,8 +40,6 @@ public class GreeterCompositor.PanelWindow : Object { Source.remove (idle_move_id); } - destroy_barrier (); - if (window_struts.remove (window)) { update_struts (); } @@ -153,13 +149,10 @@ public class GreeterCompositor.PanelWindow : Object { public void set_hide_mode (Pantheon.Desktop.HideMode hide_mode) { clone.hide_mode = hide_mode; - destroy_barrier (); - if (hide_mode == NEVER) { make_exclusive (); } else { unmake_exclusive (); - setup_barrier (); } } @@ -202,71 +195,4 @@ public class GreeterCompositor.PanelWindow : Object { update_struts (); } } - - private void destroy_barrier () { - barrier = null; - } - - private void setup_barrier () { - var display = wm.get_display (); - var monitor_geom = display.get_monitor_geometry (display.get_primary_monitor ()); - var scale = display.get_monitor_scale (display.get_primary_monitor ()); - var offset = Utils.scale_to_int (BARRIER_OFFSET, scale); - - switch (anchor) { - case TOP: - setup_barrier_top (monitor_geom, offset); - break; - - case BOTTOM: - setup_barrier_bottom (monitor_geom, offset); - break; - - default: - warning ("Barrier side not supported yet"); - break; - } - } - -#if HAS_MUTTER45 - private void setup_barrier_top (Mtk.Rectangle monitor_geom, int offset) { -#else - private void setup_barrier_top (Meta.Rectangle monitor_geom, int offset) { -#endif - barrier = new Barrier ( - wm.get_display ().get_context ().get_backend (), - monitor_geom.x + offset, - monitor_geom.y, - monitor_geom.x + monitor_geom.width - offset, - monitor_geom.y, - POSITIVE_Y, - 0, - 0, - int.MAX, - int.MAX - ); - - barrier.trigger.connect (clone.show); - } - -#if HAS_MUTTER45 - private void setup_barrier_bottom (Mtk.Rectangle monitor_geom, int offset) { -#else - private void setup_barrier_bottom (Meta.Rectangle monitor_geom, int offset) { -#endif - barrier = new Barrier ( - wm.get_display ().get_context ().get_backend (), - monitor_geom.x + offset, - monitor_geom.y + monitor_geom.height, - monitor_geom.x + monitor_geom.width - offset, - monitor_geom.y + monitor_geom.height, - NEGATIVE_Y, - 0, - 0, - int.MAX, - int.MAX - ); - - barrier.trigger.connect (clone.show); - } } diff --git a/compositor/meson.build b/compositor/meson.build index d6a01c0f1..549cbf840 100644 --- a/compositor/meson.build +++ b/compositor/meson.build @@ -84,7 +84,6 @@ compositor_files = files( 'Background/BackgroundSource.vala', 'Background/BlurEffect.vala', 'Background/SystemBackground.vala', - 'HotCorners/Barrier.vala', 'PantheonShell.vala', 'ShellClients/CenteredWindow.vala', 'ShellClients/ManagedClient.vala', From 24b710705adcd73a53f96b6f5e8ff110846e54dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Tue, 27 Aug 2024 10:24:40 -0700 Subject: [PATCH 4/6] Remove unused panelclone.hide() --- compositor/ShellClients/PanelClone.vala | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/compositor/ShellClients/PanelClone.vala b/compositor/ShellClients/PanelClone.vala index a45d806bd..f639b4668 100644 --- a/compositor/ShellClients/PanelClone.vala +++ b/compositor/ShellClients/PanelClone.vala @@ -89,27 +89,6 @@ public class GreeterCompositor.PanelClone : Object { return should_animate ? ANIMATION_DURATION : 0; } - private void hide () { - if (panel_hidden) { - return; - } - - panel_hidden = true; - - if (panel.anchor != TOP && panel.anchor != BOTTOM) { - warning ("Animated hide not supported for side yet."); - return; - } - - clone.visible = true; - - clone.save_easing_state (); - clone.set_easing_mode (Clutter.AnimationMode.EASE_OUT_QUAD); - clone.set_easing_duration (get_animation_duration ()); - clone.y = calculate_clone_y (true); - clone.restore_easing_state (); - } - public void show () { if (!panel_hidden) { return; From cc0e18193f3c3d554096b619c3266d6d4f70d94f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Thu, 5 Sep 2024 11:49:37 -0700 Subject: [PATCH 5/6] Remove set hide_mode from protocol --- compositor/PantheonShell.vala | 18 ------------------ protocol/pantheon-desktop-shell-v1.xml | 8 -------- 2 files changed, 26 deletions(-) diff --git a/compositor/PantheonShell.vala b/compositor/PantheonShell.vala index 3488d25bf..561cb08ba 100644 --- a/compositor/PantheonShell.vala +++ b/compositor/PantheonShell.vala @@ -57,7 +57,6 @@ namespace GreeterCompositor { set_anchor, focus_panel, set_size, - set_hide_mode, }; wayland_pantheon_widget_interface = { @@ -312,23 +311,6 @@ namespace GreeterCompositor { ShellClientsManager.get_instance ().set_size (window, width, height); } - internal static void set_hide_mode (Wl.Client client, Wl.Resource resource, [CCode (type = "uint32_t")] Pantheon.Desktop.HideMode hide_mode) { - unowned PanelSurface? panel_surface = resource.get_user_data (); - if (panel_surface.wayland_surface == null) { - warning ("Window tried to set hide mode but wayland surface is null."); - return; - } - - Meta.Window? window; - panel_surface.wayland_surface.get ("window", out window, null); - if (window == null) { - warning ("Window tried to set hide mode but wayland surface had no associated window."); - return; - } - - ShellClientsManager.get_instance ().set_hide_mode (window, hide_mode); - } - internal static void set_keep_above (Wl.Client client, Wl.Resource resource) { unowned ExtendedBehaviorSurface? eb_surface = resource.get_user_data (); if (eb_surface.wayland_surface == null) { diff --git a/protocol/pantheon-desktop-shell-v1.xml b/protocol/pantheon-desktop-shell-v1.xml index 63d9d5982..df118a4c0 100644 --- a/protocol/pantheon-desktop-shell-v1.xml +++ b/protocol/pantheon-desktop-shell-v1.xml @@ -90,14 +90,6 @@ - - - - Tell the shell when to hide the panel. - - - - From deaba976d70d3b6d40d205d641a38de0d7331fbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Wed, 11 Sep 2024 13:26:31 -0700 Subject: [PATCH 6/6] Revert changes to protocol file --- protocol/pantheon-desktop-shell-v1.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/protocol/pantheon-desktop-shell-v1.xml b/protocol/pantheon-desktop-shell-v1.xml index df118a4c0..63d9d5982 100644 --- a/protocol/pantheon-desktop-shell-v1.xml +++ b/protocol/pantheon-desktop-shell-v1.xml @@ -90,6 +90,14 @@ + + + + Tell the shell when to hide the panel. + + + +