diff --git a/meson.build b/meson.build index af97a9db..8d4d5e7f 100644 --- a/meson.build +++ b/meson.build @@ -18,6 +18,8 @@ add_project_arguments([ language: 'c' ) +vala = meson.get_compiler('vala') + i18n = import('i18n') gnome = import('gnome') pkg = import('pkgconfig') @@ -26,6 +28,7 @@ glib_dep = dependency('glib-2.0', version: '>=2.32') gio_dep = dependency('gio-2.0') gio_unix_dep = dependency('gio-unix-2.0') gmodule_dep = dependency('gmodule-2.0') +gdk_wl_dep = dependency('gdk-wayland-3.0') # GDK X11 dep is for detecting whether we're on Wayland or not ONLY, we don't actually have # a hard X11 dependency here gdk_x11_dep = dependency('gdk-x11-3.0') @@ -33,7 +36,9 @@ gtk_dep = dependency('gtk+-3.0', version: '>=3.10') gee_dep = dependency('gee-0.8') granite_dep = dependency('granite', version: '>=5.4.0') posix_dep = meson.get_compiler('vala').find_library('posix') +wl_client_dep = dependency('wayland-client') +subdir('protocol') subdir('data') subdir('lib') subdir('schemas') diff --git a/protocol/meson.build b/protocol/meson.build new file mode 100644 index 00000000..56b537f9 --- /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-client-protocol.h', + command: [ prog_scanner, 'client-header', '@INPUT@', '@OUTPUT@' ], + input: protocol_file, + output: 'pantheon-desktop-shell-client-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-client'), + ], + 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 00000000..18a7da64 --- /dev/null +++ b/protocol/pantheon-desktop-shell-v1.xml @@ -0,0 +1,115 @@ + + + + + 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 + + + + diff --git a/protocol/pantheon-desktop-shell.deps b/protocol/pantheon-desktop-shell.deps new file mode 100644 index 00000000..8289bf8e --- /dev/null +++ b/protocol/pantheon-desktop-shell.deps @@ -0,0 +1 @@ +wayland-client diff --git a/protocol/pantheon-desktop-shell.vapi b/protocol/pantheon-desktop-shell.vapi new file mode 100644 index 00000000..8c515b76 --- /dev/null +++ b/protocol/pantheon-desktop-shell.vapi @@ -0,0 +1,72 @@ +/* + * 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-client-protocol.h", cname = "struct io_elementary_pantheon_shell_v1", cprefix = "io_elementary_pantheon_shell_v1_")] + public class Shell : Wl.Proxy { + [CCode (cheader_filename = "pantheon-desktop-shell-client-protocol.h", cname = "io_elementary_pantheon_shell_v1_interface")] + public static Wl.Interface iface; + public void set_user_data (void* user_data); + public void* get_user_data (); + public uint32 get_version (); + public void destroy (); + public Pantheon.Desktop.Panel get_panel (Wl.Surface surface); + public Pantheon.Desktop.Widget get_widget (Wl.Surface surface); + public Pantheon.Desktop.ExtendedBehavior get_extended_behavior (Wl.Surface surface); + + } + [CCode (cheader_filename = "pantheon-desktop-shell-client-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-client-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-client-protocol.h", cname = "struct io_elementary_pantheon_panel_v1", cprefix = "io_elementary_pantheon_panel_v1_")] + public class Panel : Wl.Proxy { + [CCode (cheader_filename = "pantheon-desktop-shell-client-protocol.h", cname = "io_elementary_pantheon_panel_v1_interface")] + public static Wl.Interface iface; + public void set_user_data (void* user_data); + public void* get_user_data (); + public uint32 get_version (); + public void destroy (); + public void set_anchor (Pantheon.Desktop.Anchor anchor); + public void focus (); + public void set_size (int width, int height); + public void set_hide_mode (Pantheon.Desktop.HideMode hide_mode); + } + + [CCode (cheader_filename = "pantheon-desktop-shell-client-protocol.h", cname = "struct io_elementary_pantheon_widget_v1", cprefix = "io_elementary_pantheon_widget_v1_")] + public class Widget : Wl.Proxy { + [CCode (cheader_filename = "pantheon-desktop-shell-client-protocol.h", cname = "io_elementary_pantheon_widget_v1_interface")] + public static Wl.Interface iface; + public void set_user_data (void* user_data); + public void* get_user_data (); + public uint32 get_version (); + public void destroy (); + } + + [CCode (cheader_filename = "pantheon-desktop-shell-client-protocol.h", cname = "struct io_elementary_pantheon_extended_behavior_v1", cprefix = "io_elementary_pantheon_extended_behavior_v1_")] + public class ExtendedBehavior : Wl.Proxy { + [CCode (cheader_filename = "pantheon-desktop-shell-client-protocol.h", cname = "io_elementary_pantheon_extended_behavior_v1_interface")] + public static Wl.Interface iface; + public void set_user_data (void* user_data); + public void* get_user_data (); + public uint32 get_version (); + public void destroy (); + public void set_keep_above (); + } +} diff --git a/src/Application.vala b/src/Application.vala index cd3d6868..25e65f95 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -161,7 +161,7 @@ public class Wingpanel.Application : Gtk.Application { return; } - panel_window.popover_manager.toggle_popover_visible (parameter.get_string ()); + panel_window.toggle_indicator (parameter.get_string ()); }); this.add_action (list_indicators_action); diff --git a/src/PanelWindow.vala b/src/PanelWindow.vala index b02aaabd..bee88094 100644 --- a/src/PanelWindow.vala +++ b/src/PanelWindow.vala @@ -22,6 +22,7 @@ public class Wingpanel.PanelWindow : Gtk.Window { private Widgets.Panel panel; private Gtk.EventControllerKey key_controller; // For keeping in memory + private Gtk.GestureMultiPress gesture_controller; // For keeping in memory private Gtk.Revealer revealer; private int monitor_number; private int monitor_width; @@ -31,6 +32,9 @@ public class Wingpanel.PanelWindow : Gtk.Window { private int panel_height; private bool expanded = false; + private Pantheon.Desktop.Shell? desktop_shell; + private Pantheon.Desktop.Panel? desktop_panel; + public PanelWindow (Gtk.Application application) { Object ( application: application, @@ -82,6 +86,16 @@ public class Wingpanel.PanelWindow : Gtk.Window { key_controller = new Gtk.EventControllerKey (this); key_controller.key_pressed.connect (on_key_pressed); + gesture_controller = new Gtk.GestureMultiPress (this) { + propagation_phase = CAPTURE + }; + + gesture_controller.pressed.connect (() => { + if (desktop_panel != null) { + desktop_panel.focus (); + } + }); + if (!Utils.is_wayland ()) { panel.size_allocate.connect (update_panel_dimensions); } @@ -95,6 +109,9 @@ public class Wingpanel.PanelWindow : Gtk.Window { Services.BackgroundManager.initialize (this.monitor_number, panel_height); revealer.transition_type = SLIDE_DOWN; revealer.reveal_child = true; + + // We have to wrap in Idle otherwise the Meta.Window of the WaylandSurface in Gala is still null + Idle.add_once (init_wl); } private void update_panel_dimensions () { @@ -103,9 +120,8 @@ public class Wingpanel.PanelWindow : Gtk.Window { monitor_number = screen.get_primary_monitor (); Gdk.Rectangle monitor_dimensions; if (Utils.is_wayland ()) { - // TODO: Wayland doesn't have a concept of a primary monitor and so the GDK - // call doesn't work, so we need to write some kind of WM interface to get this - monitor_dimensions = get_display ().get_monitor (0).get_geometry (); + // We just use our monitor because Gala makes sure we are always on the primary one + monitor_dimensions = get_display ().get_monitor_at_window (get_window ()).get_geometry (); } else { monitor_dimensions = get_display ().get_primary_monitor ().get_geometry (); } @@ -214,4 +230,46 @@ public class Wingpanel.PanelWindow : Gtk.Window { this.resize (monitor_width, 1); } } + + public void toggle_indicator (string name) { + popover_manager.toggle_popover_visible (name); + + if (desktop_panel != null) { + desktop_panel.focus (); + } + } + + public void registry_handle_global (Wl.Registry wl_registry, uint32 name, string @interface, uint32 version) { + if (@interface == "io_elementary_pantheon_shell_v1") { + desktop_shell = wl_registry.bind (name, ref Pantheon.Desktop.Shell.iface, uint32.min (version, 1)); + unowned var window = get_window (); + if (window is Gdk.Wayland.Window) { + unowned var wl_surface = ((Gdk.Wayland.Window) window).get_wl_surface (); + desktop_panel = desktop_shell.get_panel (wl_surface); + desktop_panel.set_anchor (TOP); + desktop_panel.set_hide_mode (NEVER); + desktop_panel.set_size (-1, get_allocated_height ()); + + Idle.add_once (update_panel_dimensions); // Update again since we now can be 100% sure that we are on the primary monitor + } + } + } + + private static Wl.RegistryListener registry_listener; + private void init_wl () { + registry_listener.global = registry_handle_global; + unowned var display = Gdk.Display.get_default (); + if (display is Gdk.Wayland.Display) { + unowned var wl_display = ((Gdk.Wayland.Display) display).get_wl_display (); + var wl_registry = wl_display.get_registry (); + wl_registry.add_listener ( + registry_listener, + this + ); + + if (wl_display.roundtrip () < 0) { + return; + } + } + } } diff --git a/src/meson.build b/src/meson.build index 1ad0597d..d9ca1250 100644 --- a/src/meson.build +++ b/src/meson.build @@ -15,8 +15,11 @@ wingpanel_files = files( wingpanel_deps = [ libwingpanel_dep, granite_dep, + gdk_wl_dep, gdk_x11_dep, - posix_dep + posix_dep, + wl_client_dep, + pantheon_desktop_shell_dep ] executable(meson.project_name(), diff --git a/vapi/gdk-wayland-3.0.vapi b/vapi/gdk-wayland-3.0.vapi new file mode 100644 index 00000000..f38aab43 --- /dev/null +++ b/vapi/gdk-wayland-3.0.vapi @@ -0,0 +1,16 @@ +[CCode (cheader_filename = "gdk/gdkwayland.h")] +namespace Gdk.Wayland { + [CCode (type_id = "GDK_TYPE_WAYLAND_WINDOW", type_check_function = "GDK_IS_WAYLAND_WINDOW")] + public class Window : Gdk.Window { + protected Window (); + + public unowned Wl.Surface get_wl_surface (); + } + + [CCode (type_id = "GDK_TYPE_WAYLAND_DISPLAY", type_check_function = "GDK_IS_WAYLAND_DISPLAY")] + public class Display : Gdk.Display { + protected Display (); + + public unowned Wl.Display get_wl_display (); + } +}