Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use wayland protocol for positioning #533

Merged
merged 5 commits into from
May 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ add_project_arguments([
language: 'c'
)

vala = meson.get_compiler('vala')

i18n = import('i18n')
gnome = import('gnome')
pkg = import('pkgconfig')
Expand All @@ -26,14 +28,17 @@ 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')
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')
Expand Down
32 changes: 32 additions & 0 deletions protocol/meson.build
Original file line number Diff line number Diff line change
@@ -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
)
115 changes: 115 additions & 0 deletions protocol/pantheon-desktop-shell-v1.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="pantheon_shell_v1">
<copyright><![CDATA[
SPDX-FileCopyrightText: 2023 Corentin Noël <[email protected]>

SPDX-License-Identifier: LGPL-2.1-or-later
]]></copyright>

<interface name="io_elementary_pantheon_shell_v1" version="1">
<description summary="create panel, widget and get more control">
This interface is used by the Pantheon Wayland shell to communicate with
the compositor.
</description>

<request name="get_panel">
<description summary="create a panel surface from a surface">
Create a panel surface from an existing surface.
</description>
<arg name="output" type="new_id" interface="io_elementary_pantheon_panel_v1"/>
<arg name="surface" type="object" interface="wl_surface"/>
</request>

<request name="get_widget">
<description summary="create a widget surface from a surface">
Create a desktop widget surface from an existing surface.
</description>
<arg name="output" type="new_id" interface="io_elementary_pantheon_widget_v1"/>
<arg name="surface" type="object" interface="wl_surface"/>
</request>

<request name="get_extended_behavior">
<description summary="create a desktop-specific surface from a surface">
Create a desktop-specific surface from an existing surface.
</description>
<arg name="output" type="new_id" interface="io_elementary_pantheon_extended_behavior_v1"/>
<arg name="surface" type="object" interface="wl_surface"/>
</request>
</interface>

<interface name="io_elementary_pantheon_panel_v1" version="1">
<request name="destroy" type="destructor"/>

<enum name="anchor">
<description summary="anchor">
The anchor is a placement hint to the compositor.
</description>
<entry name="top" value="0" summary="the top edge of the screen"/>
<entry name="bottom" value="1" summary="the bottom edge of the screen"/>
<entry name="left" value="2" summary="the left edge of the screen"/>
<entry name="right" value="3" summary="the right edge of the screen"/>
</enum>

<enum name="hide_mode">
<description summary="hide mode">
How the shell should handle the window.
</description>
<entry name="never" value="0" summary="make the surface exclusive"/>
<entry name="maximized_focus_window" value="1" summary="hide when the focused window is maximized"/>
<entry name="overlapping_focus_window" value="2" summary="hide when the focused window overlaps the surface"/>
<entry name="overlapping_window" value="3" summary="hide when any window overlaps the surface"/>
<entry name="always" value="4" summary="always hide and only show if requested by the user"/>
</enum>

<request name="set_anchor">
<description summary="set panel edge anchor">
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.
</description>

<arg name="anchor" type="uint" enum="anchor" summary="anchor"/>
</request>

<request name="focus">
<description summary="request keyboard 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.
</description>
</request>

<request name="set_size">
<description summary="set size">
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.
</description>

<arg name="width" type="int"/>
<arg name="height" type="int"/>
</request>

<request name="set_hide_mode">
<description summary="set panel hide mode">
Tell the shell when to hide the panel.
</description>

<arg name="hide_mode" type="uint" enum="hide_mode" summary="hide mode"/>
</request>
</interface>

<interface name="io_elementary_pantheon_widget_v1" version="1">
<request name="destroy" type="destructor"/>
</interface>

<interface name="io_elementary_pantheon_extended_behavior_v1" version="1">
<request name="destroy" type="destructor"/>
<request name="set_keep_above">
<description summary="set keep above">
Tell the shell to keep the surface above on all workspaces
</description>
</request>
</interface>
</protocol>
1 change: 1 addition & 0 deletions protocol/pantheon-desktop-shell.deps
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
wayland-client
72 changes: 72 additions & 0 deletions protocol/pantheon-desktop-shell.vapi
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright 2023 elementary, Inc. <https://elementary.io>
* Copyright 2023 Corentin Noël <[email protected]>
* 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 ();
}
}
2 changes: 1 addition & 1 deletion src/Application.vala
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
64 changes: 61 additions & 3 deletions src/PanelWindow.vala
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
Expand Down Expand Up @@ -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);
}
Expand All @@ -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 () {
Expand All @@ -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 ();
}
Expand Down Expand Up @@ -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<Pantheon.Desktop.Shell> (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;
}
}
}
}
5 changes: 4 additions & 1 deletion src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
16 changes: 16 additions & 0 deletions vapi/gdk-wayland-3.0.vapi
Original file line number Diff line number Diff line change
@@ -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 ();
}
}
Loading