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 ();
+ }
+}