summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksey Samoilov <[email protected]>2026-09-26 01:17:45 +0400
committerGitHub <[email protected]>2026-09-25 21:17:45 +0000
commit58ac6c101bdf2e2975c0395723ad16e9c9764f5c (patch)
treeceffc5a4020f8e9df799f428efe9098c9d46b457
parent5aba4cf3447a3a6d44957107f8b0f75ddb1e71d5 (diff)
downloadmate-panel-master.tar.bz2
mate-panel-master.tar.xz
[Wayland] Add support for autohide (#1584)HEADmaster
* wayland/panel: add autohide support * Fix autohide with multiple panels * wayland/autohide: track exclusive zone from live struts on hide * wayland/autohide: release exclusive zone while panel is hidden
-rw-r--r--mate-panel/panel-menu-bar.c47
-rw-r--r--mate-panel/panel-toplevel.c325
-rw-r--r--mate-panel/wayland-backend.c297
-rw-r--r--mate-panel/wayland-backend.h11
4 files changed, 636 insertions, 44 deletions
diff --git a/mate-panel/panel-menu-bar.c b/mate-panel/panel-menu-bar.c
index 28ef9084..93ef978f 100644
--- a/mate-panel/panel-menu-bar.c
+++ b/mate-panel/panel-menu-bar.c
@@ -64,6 +64,8 @@ struct _PanelMenuBarPrivate {
GSettings* settings;
PanelOrientation orientation;
+
+ gboolean disabler_setup;
};
enum {
@@ -227,6 +229,20 @@ static void panel_menu_bar_set_property(GObject* object, guint prop_id, const GV
}
}
+static void panel_menu_bar_menu_shown (GtkWidget *menu, PanelMenuBar *menubar)
+{
+ (void) menu;
+
+ panel_toplevel_push_autohide_disabler (menubar->priv->panel->toplevel);
+}
+
+static void panel_menu_bar_menu_deactivated (GtkWidget *menu, PanelMenuBar *menubar)
+{
+ (void) menu;
+
+ panel_toplevel_pop_autohide_disabler (menubar->priv->panel->toplevel);
+}
+
static void panel_menu_bar_parent_set(GtkWidget* widget, GtkWidget* previous_parent)
{
PanelMenuBar* menubar = PANEL_MENU_BAR(widget);
@@ -252,6 +268,37 @@ static void panel_menu_bar_parent_set(GtkWidget* widget, GtkWidget* previous_par
{
panel_desktop_menu_item_set_panel(menubar->priv->desktop_item, menubar->priv->panel);
}
+
+ if (!menubar->priv->disabler_setup)
+ {
+ GtkWidget *menu;
+
+ menubar->priv->disabler_setup = TRUE;
+
+ menu = menubar->priv->applications_menu;
+ if (menu) {
+ g_signal_connect(menu, "show",
+ G_CALLBACK (panel_menu_bar_menu_shown), menubar);
+ g_signal_connect(menu, "deactivate",
+ G_CALLBACK (panel_menu_bar_menu_deactivated), menubar);
+ }
+
+ menu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(menubar->priv->places_item));
+ if (menu) {
+ g_signal_connect(menu, "show",
+ G_CALLBACK (panel_menu_bar_menu_shown), menubar);
+ g_signal_connect(menu, "deactivate",
+ G_CALLBACK (panel_menu_bar_menu_deactivated), menubar);
+ }
+
+ menu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(menubar->priv->desktop_item));
+ if (menu) {
+ g_signal_connect(menu, "show",
+ G_CALLBACK (panel_menu_bar_menu_shown), menubar);
+ g_signal_connect(menu, "deactivate",
+ G_CALLBACK (panel_menu_bar_menu_deactivated), menubar);
+ }
+ }
}
static void panel_menu_bar_size_allocate(GtkWidget* widget, GtkAllocation* allocation)
diff --git a/mate-panel/panel-toplevel.c b/mate-panel/panel-toplevel.c
index 3892cfaf..8a6fdabd 100644
--- a/mate-panel/panel-toplevel.c
+++ b/mate-panel/panel-toplevel.c
@@ -77,6 +77,7 @@
#define HANDLE_SIZE 10
#define N_ATTACH_TOPLEVEL_SIGNALS 5
#define N_ATTACH_WIDGET_SIGNALS 5
+#define AUTO_HIDE_HIT_MARGIN 4
typedef enum {
PANEL_GRAB_OP_NONE,
@@ -210,6 +211,16 @@ struct _PanelToplevelPrivate {
guint updated_geometry_initial : 1;
/* flag to see if we have done the initial animation */
guint initial_animation_done : 1;
+ /* whether the pointer is currently over this toplevel, as
+ reported by enter/leave notifications (authoritative on
+ Wayland where the window frame may not match the surface) */
+ guint pointer_inside : 1;
+
+ /* Wayland autohide drop-zone surface: a transparent layer-shell
+ * window overlaid on the hidden strip that receives enter/leave
+ * events reliably, independently of the animated panel surface */
+ GtkWidget *autohide_window;
+ GdkRectangle autohide_window_geometry;
};
enum {
@@ -262,6 +273,8 @@ static void panel_toplevel_set_monitor_internal(PanelToplevel* toplevel, int mon
static void panel_toplevel_drag_threshold_changed (PanelToplevel *toplevel);
static void panel_toplevel_on_monitors_changed (GdkScreen *screen, gpointer user_data);
+static void panel_toplevel_update_autohide_window (PanelToplevel *toplevel);
+
static void
update_style_classes (PanelToplevel *toplevel)
{
@@ -1119,8 +1132,20 @@ void panel_toplevel_push_autohide_disabler(PanelToplevel* toplevel)
{
g_return_if_fail (toplevel != NULL);
- if (!toplevel->priv->n_autohide_disablers++)
- panel_toplevel_queue_auto_hide (toplevel);
+ /* While a disabler is held (menu/dialog open) we don't just refuse
+ * to start a new hide: we also cancel any pending hide and force the
+ * panel visible, so transient pointer crossings or menu interactions
+ * can never leave the panel hidden. */
+ if (toplevel->priv->n_autohide_disablers++ == 0) {
+ if (toplevel->priv->hide_timeout) {
+ g_source_remove (toplevel->priv->hide_timeout);
+ toplevel->priv->hide_timeout = 0;
+ }
+
+ if (toplevel->priv->auto_hide &&
+ toplevel->priv->state == PANEL_STATE_AUTO_HIDDEN)
+ panel_toplevel_queue_auto_unhide (toplevel);
+ }
}
void panel_toplevel_pop_autohide_disabler(PanelToplevel* toplevel)
@@ -1128,7 +1153,7 @@ void panel_toplevel_pop_autohide_disabler(PanelToplevel* toplevel)
g_return_if_fail (toplevel != NULL);
g_return_if_fail (toplevel->priv->n_autohide_disablers > 0);
- if (!--toplevel->priv->n_autohide_disablers)
+ if (--toplevel->priv->n_autohide_disablers == 0)
panel_toplevel_queue_auto_hide (toplevel);
}
@@ -1422,6 +1447,29 @@ static gboolean panel_toplevel_contains_pointer(PanelToplevel* toplevel)
if (x == -1 || y == -1)
return FALSE;
+#ifdef HAVE_WAYLAND
+ if (GDK_IS_WAYLAND_DISPLAY (gtk_widget_get_display (widget))) {
+ GdkWindow *pointer_window = gdk_device_get_window_at_position (pointer,
+ NULL, NULL);
+ gboolean result;
+
+ if (pointer_window != NULL) {
+ GdkWindow *panel_window = gtk_widget_get_window (widget);
+ GdkWindow *strip_window = toplevel->priv->autohide_window ?
+ gtk_widget_get_window (GTK_WIDGET (toplevel->priv->autohide_window)) :
+ NULL;
+
+ pointer_window = gdk_window_get_effective_toplevel (pointer_window);
+ result = (pointer_window == panel_window) ||
+ (strip_window != NULL && pointer_window == strip_window);
+
+ return result;
+ }
+
+ return toplevel->priv->pointer_inside;
+ }
+#endif /* HAVE_WAYLAND */
+
if (x < toplevel->priv->geometry.x || x >= (toplevel->priv->geometry.x + toplevel->priv->geometry.width) ||
y < toplevel->priv->geometry.y || y >= (toplevel->priv->geometry.y + toplevel->priv->geometry.height))
return FALSE;
@@ -1567,7 +1615,24 @@ static gboolean panel_toplevel_update_struts(PanelToplevel* toplevel, gboolean e
#ifdef HAVE_WAYLAND
if (GDK_IS_WAYLAND_DISPLAY (gtk_widget_get_display (GTK_WIDGET (toplevel)))) {
+ int exclusive;
+
wayland_panel_toplevel_update_placement (toplevel);
+
+ /* While the panel is visible it reserves its edge (strut) so that
+ * maximized windows never overlap it. A hidden panel must not
+ * reserve anything: on X11 the gap this would leave is invisible
+ * because the wallpaper/root background is drawn everywhere, but on
+ * Wayland desktop wallpaper services size their surfaces to the
+ * reserved work area, so a reserved strip while hidden shows the
+ * compositor's native background instead of the wallpaper. */
+ if (strut == 0 ||
+ toplevel->priv->state != PANEL_STATE_NORMAL)
+ exclusive = 0;
+ else
+ exclusive = strut;
+
+ wayland_panel_toplevel_update_exclusive_zone (toplevel, exclusive);
}
#endif /* HAVE_WAYLAND */
return geometry_changed;
@@ -2272,6 +2337,8 @@ panel_toplevel_update_position (PanelToplevel *toplevel)
toplevel->priv->geometry.x = x;
toplevel->priv->geometry.y = y;
+
+ panel_toplevel_update_autohide_window (toplevel);
}
static int
@@ -2919,20 +2986,36 @@ panel_toplevel_move_resize_window (PanelToplevel *toplevel,
g_assert (gtk_widget_get_realized (widget));
- if (move && resize)
- gdk_window_move_resize (gtk_widget_get_window (widget),
- toplevel->priv->geometry.x,
- toplevel->priv->geometry.y,
- toplevel->priv->geometry.width,
- toplevel->priv->geometry.height);
- else if (move)
- gdk_window_move (gtk_widget_get_window (widget),
- toplevel->priv->geometry.x,
- toplevel->priv->geometry.y);
- else if (resize)
- gdk_window_resize (gtk_widget_get_window (widget),
- toplevel->priv->geometry.width,
- toplevel->priv->geometry.height);
+#ifdef HAVE_WAYLAND
+ if (GDK_IS_WAYLAND_DISPLAY (gtk_widget_get_display (widget))) {
+ /* Layer-shell windows are positioned by the compositor using
+ * anchors and margins, so we translate the geometry to margins
+ * relative to the monitor the panel is attached to. */
+ if (toplevel->priv->updated_geometry_initial) {
+ GdkRectangle monitor_geom;
+ panel_toplevel_get_monitor_geometry (toplevel, &monitor_geom);
+ wayland_panel_toplevel_move_resize (toplevel,
+ &toplevel->priv->geometry,
+ &monitor_geom);
+ }
+ } else
+#endif /* HAVE_WAYLAND */
+ {
+ if (move && resize)
+ gdk_window_move_resize (gtk_widget_get_window (widget),
+ toplevel->priv->geometry.x,
+ toplevel->priv->geometry.y,
+ toplevel->priv->geometry.width,
+ toplevel->priv->geometry.height);
+ else if (move)
+ gdk_window_move (gtk_widget_get_window (widget),
+ toplevel->priv->geometry.x,
+ toplevel->priv->geometry.y);
+ else if (resize)
+ gdk_window_resize (gtk_widget_get_window (widget),
+ toplevel->priv->geometry.width,
+ toplevel->priv->geometry.height);
+ }
if (resize || move) {
for (list = toplevel->priv->panel_widget->applet_list; list != NULL; list = g_list_next (list)) {
@@ -2963,6 +3046,17 @@ static void
panel_toplevel_initially_hide (PanelToplevel *toplevel)
{
if (!toplevel->priv->attached) {
+#ifdef HAVE_WAYLAND
+ if (GDK_IS_WAYLAND_DISPLAY (gtk_widget_get_display (GTK_WIDGET (toplevel)))) {
+ toplevel->priv->initial_animation_done = TRUE;
+ if (!toplevel->priv->auto_hide)
+ toplevel->priv->state = PANEL_STATE_NORMAL;
+ else
+ toplevel->priv->state = PANEL_STATE_AUTO_HIDDEN;
+ gtk_widget_queue_resize (GTK_WIDGET (toplevel));
+ return;
+ }
+#endif /* HAVE_WAYLAND */
toplevel->priv->initial_animation_done = FALSE;
/* We start the panel off hidden until all the applets are
@@ -3050,6 +3144,8 @@ panel_toplevel_realize (GtkWidget *widget)
panel_toplevel_initially_hide (toplevel);
panel_toplevel_move_resize_window (toplevel, TRUE, TRUE);
+
+ panel_toplevel_update_autohide_window (toplevel);
}
static void
@@ -3091,6 +3187,11 @@ panel_toplevel_dispose (GObject *widget)
{
PanelToplevel *toplevel = (PanelToplevel *) widget;
+ if (toplevel->priv->autohide_window) {
+ gtk_widget_destroy (toplevel->priv->autohide_window);
+ toplevel->priv->autohide_window = NULL;
+ }
+
g_clear_pointer (&toplevel->priv->settings_path, g_free);
if (toplevel->settings) {
@@ -3546,7 +3647,18 @@ panel_toplevel_motion_notify_event (GtkWidget *widget,
static gboolean
panel_toplevel_animation_timeout (PanelToplevel *toplevel)
{
- gtk_widget_queue_resize (GTK_WIDGET (toplevel));
+#ifdef HAVE_WAYLAND
+ if (GDK_IS_WAYLAND_DISPLAY (gtk_widget_get_display (GTK_WIDGET (toplevel)))) {
+ if (toplevel->priv->animating) {
+ panel_toplevel_update_position (toplevel);
+ panel_toplevel_update_struts (toplevel, FALSE);
+ panel_toplevel_move_resize_window (toplevel, TRUE, TRUE);
+ }
+ } else
+#endif /* HAVE_WAYLAND */
+ {
+ gtk_widget_queue_resize (GTK_WIDGET (toplevel));
+ }
if (!toplevel->priv->animating) {
toplevel->priv->animation_end_x = 0xdead;
@@ -3666,7 +3778,19 @@ panel_toplevel_start_animation (PanelToplevel *toplevel)
#endif /* HAVE_X11 */
panel_toplevel_update_struts (toplevel, FALSE);
- gdk_window_get_origin (gtk_widget_get_window (GTK_WIDGET (toplevel)), &cur_x, &cur_y);
+ #ifdef HAVE_WAYLAND
+ if (GDK_IS_WAYLAND_DISPLAY (gtk_widget_get_display (GTK_WIDGET (toplevel)))) {
+ /* gdk_window_get_origin() always reports (0,0) for layer-shell
+ * windows, since the compositor controls their position. Use
+ * the geometry we track ourselves instead, otherwise the
+ * initial hide/unhide animation would be skipped. */
+ cur_x = toplevel->priv->geometry.x;
+ cur_y = toplevel->priv->geometry.y;
+ } else
+#endif /* HAVE_WAYLAND */
+ {
+ gdk_window_get_origin (gtk_widget_get_window (GTK_WIDGET (toplevel)), &cur_x, &cur_y);
+ }
cur_x -= panel_multimonitor_x (toplevel->priv->monitor);
cur_y -= panel_multimonitor_y (toplevel->priv->monitor);
@@ -3775,6 +3899,8 @@ panel_toplevel_hide (PanelToplevel *toplevel,
}
gtk_widget_queue_resize (GTK_WIDGET (toplevel));
+
+ panel_toplevel_update_autohide_window (toplevel);
}
static gboolean
@@ -3787,6 +3913,18 @@ panel_toplevel_auto_hide_timeout_handler (PanelToplevel *toplevel)
return FALSE;
}
+#ifdef HAVE_WAYLAND
+ /* Re-check the pointer when the hide finally commits: a leave event
+ * may have been lost or reported late, and the pointer may actually
+ * be back over the panel (or its autohide strip). Never hide while
+ * the pointer is engaged. */
+ if (GDK_IS_WAYLAND_DISPLAY (gtk_widget_get_display (GTK_WIDGET (toplevel))) &&
+ panel_toplevel_contains_pointer (toplevel)) {
+ toplevel->priv->hide_timeout = 0;
+ return FALSE;
+ }
+#endif /* HAVE_WAYLAND */
+
/* keep coming back until the animation has finished.
* FIXME: we should really remove the timeout/idle
* completely and re-instate it when the
@@ -3829,6 +3967,8 @@ panel_toplevel_unhide (PanelToplevel *toplevel)
gtk_widget_queue_resize (GTK_WIDGET (toplevel));
+ panel_toplevel_update_autohide_window (toplevel);
+
if (!toplevel->priv->animate)
g_signal_emit (toplevel, toplevel_signals [UNHIDE_SIGNAL], 0);
}
@@ -3879,8 +4019,9 @@ panel_toplevel_queue_auto_hide (PanelToplevel *toplevel)
if (!toplevel->priv->auto_hide ||
panel_toplevel_contains_pointer (toplevel) ||
- panel_toplevel_get_autohide_disabled (toplevel))
- return;
+ panel_toplevel_get_autohide_disabled (toplevel)) {
+ return;
+ }
if (toplevel->priv->unhide_timeout)
g_source_remove (toplevel->priv->unhide_timeout);
@@ -3953,8 +4094,11 @@ panel_toplevel_enter_notify_event (GtkWidget *widget,
toplevel = PANEL_TOPLEVEL (widget);
- if (toplevel->priv->auto_hide && event->detail != GDK_NOTIFY_INFERIOR)
+ if (toplevel->priv->auto_hide &&
+ event->detail != GDK_NOTIFY_INFERIOR) {
+ toplevel->priv->pointer_inside = TRUE;
panel_toplevel_queue_auto_unhide (toplevel);
+ }
if (GTK_WIDGET_CLASS (panel_toplevel_parent_class)->enter_notify_event)
return GTK_WIDGET_CLASS (panel_toplevel_parent_class)->enter_notify_event (widget, event);
@@ -3972,8 +4116,11 @@ panel_toplevel_leave_notify_event (GtkWidget *widget,
toplevel = PANEL_TOPLEVEL (widget);
- if (toplevel->priv->auto_hide && event->detail != GDK_NOTIFY_INFERIOR)
+ if (toplevel->priv->auto_hide &&
+ event->detail != GDK_NOTIFY_INFERIOR) {
+ toplevel->priv->pointer_inside = FALSE;
panel_toplevel_queue_auto_hide (toplevel);
+ }
if (GTK_WIDGET_CLASS (panel_toplevel_parent_class)->leave_notify_event)
return GTK_WIDGET_CLASS (panel_toplevel_parent_class)->leave_notify_event (widget, event);
@@ -4775,6 +4922,124 @@ background_changed (PanelBackground *background,
panel_widget_emit_background_changed (toplevel->priv->panel_widget);
}
+#ifdef HAVE_WAYLAND
+static gboolean
+panel_toplevel_autohide_window_enter_notify (GtkWidget *widget,
+ GdkEventCrossing *event,
+ PanelToplevel *toplevel)
+{
+ if (event->detail != GDK_NOTIFY_INFERIOR) {
+ toplevel->priv->pointer_inside = TRUE;
+ panel_toplevel_queue_auto_unhide (toplevel);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+panel_toplevel_autohide_window_leave_notify (GtkWidget *widget,
+ GdkEventCrossing *event,
+ PanelToplevel *toplevel)
+{
+ if (event->detail != GDK_NOTIFY_INFERIOR) {
+ toplevel->priv->pointer_inside = FALSE;
+ panel_toplevel_queue_auto_hide (toplevel);
+ }
+
+ return FALSE;
+}
+
+/* Keep the autohide drop-zone window glued to the strip that the panel
+ * leaves visible while hidden, so the strip is an always-available input
+ * surface regardless of where the animated panel surface currently is. */
+static void
+panel_toplevel_update_autohide_window (PanelToplevel *toplevel)
+{
+ GdkRectangle monitor_geom;
+ GdkRectangle new_g;
+ GdkRectangle *g;
+ int auto_hide_size;
+ int hit_size;
+
+ if (!toplevel->priv->autohide_window)
+ return;
+
+ if (!toplevel->priv->auto_hide ||
+ toplevel->priv->state != PANEL_STATE_AUTO_HIDDEN ||
+ toplevel->priv->geometry.width <= 0 ||
+ toplevel->priv->geometry.height <= 0) {
+ /* reset the cache so a later show (possibly at the same
+ * geometry) is always performed */
+ toplevel->priv->autohide_window_geometry.x = -1;
+ toplevel->priv->autohide_window_geometry.y = -1;
+ toplevel->priv->autohide_window_geometry.width = 0;
+ toplevel->priv->autohide_window_geometry.height = 0;
+
+ if (gtk_widget_get_visible (toplevel->priv->autohide_window)) {
+ wayland_panel_toplevel_autohide_window_hide (
+ GTK_WINDOW (toplevel->priv->autohide_window));
+ }
+ return;
+ }
+
+ /* The drop-zone is the input surface for the hidden strip. Make it
+ * a few pixels thicker than the visible strip, growing into the
+ * screen, so the pointer does not have to land on the exact edge
+ * pixel to unhide the panel. */
+ panel_toplevel_get_monitor_geometry (toplevel, &monitor_geom);
+ auto_hide_size = panel_toplevel_get_effective_auto_hide_size (toplevel);
+ hit_size = auto_hide_size + AUTO_HIDE_HIT_MARGIN;
+
+ g = &new_g;
+
+ switch (toplevel->priv->orientation) {
+ case PANEL_ORIENTATION_TOP:
+ g->x = toplevel->priv->geometry.x;
+ g->width = toplevel->priv->geometry.width;
+ g->y = monitor_geom.y;
+ g->height = hit_size;
+ break;
+ case PANEL_ORIENTATION_BOTTOM:
+ g->x = toplevel->priv->geometry.x;
+ g->width = toplevel->priv->geometry.width;
+ g->y = monitor_geom.y + monitor_geom.height - hit_size;
+ g->height = hit_size;
+ break;
+ case PANEL_ORIENTATION_LEFT:
+ g->x = monitor_geom.x;
+ g->width = hit_size;
+ g->y = toplevel->priv->geometry.y;
+ g->height = toplevel->priv->geometry.height;
+ break;
+ case PANEL_ORIENTATION_RIGHT:
+ g->x = monitor_geom.x + monitor_geom.width - hit_size;
+ g->width = hit_size;
+ g->y = toplevel->priv->geometry.y;
+ g->height = toplevel->priv->geometry.height;
+ break;
+ default:
+ return;
+ }
+
+ /* re-place the surface only when something actually changed */
+ if (new_g.x != toplevel->priv->autohide_window_geometry.x ||
+ new_g.y != toplevel->priv->autohide_window_geometry.y ||
+ new_g.width != toplevel->priv->autohide_window_geometry.width ||
+ new_g.height != toplevel->priv->autohide_window_geometry.height) {
+ toplevel->priv->autohide_window_geometry = new_g;
+
+ wayland_panel_toplevel_autohide_window_show (
+ GTK_WINDOW (toplevel->priv->autohide_window),
+ toplevel, g, &monitor_geom);
+ }
+}
+#else /* !HAVE_WAYLAND */
+static void
+panel_toplevel_update_autohide_window (PanelToplevel *toplevel)
+{
+}
+#endif /* HAVE_WAYLAND */
+
static void
panel_toplevel_init (PanelToplevel *toplevel)
{
@@ -4783,6 +5048,7 @@ panel_toplevel_init (PanelToplevel *toplevel)
toplevel->priv = panel_toplevel_get_instance_private (toplevel);
+ toplevel->priv->pointer_inside = FALSE;
toplevel->priv->expand = TRUE;
toplevel->priv->orientation = PANEL_ORIENTATION_BOTTOM;
toplevel->priv->size = DEFAULT_SIZE;
@@ -4890,6 +5156,17 @@ panel_toplevel_init (PanelToplevel *toplevel)
#ifdef HAVE_WAYLAND
if (GDK_IS_WAYLAND_DISPLAY (gdk_display_get_default ())) {
wayland_panel_toplevel_init (toplevel);
+
+ toplevel->priv->autohide_window =
+ GTK_WIDGET (wayland_panel_toplevel_create_autohide_window (toplevel));
+ g_signal_connect (toplevel->priv->autohide_window,
+ "enter-notify-event",
+ G_CALLBACK (panel_toplevel_autohide_window_enter_notify),
+ toplevel);
+ g_signal_connect (toplevel->priv->autohide_window,
+ "leave-notify-event",
+ G_CALLBACK (panel_toplevel_autohide_window_leave_notify),
+ toplevel);
}
#endif /* HAVE_WAYLAND */
}
@@ -5427,6 +5704,8 @@ panel_toplevel_set_auto_hide (PanelToplevel *toplevel,
else
panel_toplevel_queue_auto_unhide (toplevel);
+ panel_toplevel_update_autohide_window (toplevel);
+
if (panel_toplevel_update_struts (toplevel, FALSE))
gtk_widget_queue_resize (GTK_WIDGET (toplevel));
diff --git a/mate-panel/wayland-backend.c b/mate-panel/wayland-backend.c
index fc1b7484..68cd1cc3 100644
--- a/mate-panel/wayland-backend.c
+++ b/mate-panel/wayland-backend.c
@@ -2,7 +2,7 @@
* wayland-backend.c: Support for running on Wayland compositors
*
* Copyright (C) 2019 William Wold
- * Copyright (C) 2019-2021 MATE Developers
+ * Copyright (C) 2019-2026 MATE Developers
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -20,39 +20,30 @@
* 02110-1301, USA.
*
* Authors:
- * William Wold <[email protected]>
+ * William Wold <[email protected]>
*/
+#ifdef HAVE_CONFIG_H
#include <config.h>
+#endif
+#include <glib.h>
+
+#include <gtk/gtk.h>
#include <gtk-layer-shell.h>
#include "wayland-backend.h"
-void
-wayland_panel_toplevel_init (PanelToplevel* toplevel)
-{
- GtkWindow* window;
-
- window = GTK_WINDOW (toplevel);
- gtk_layer_init_for_window (window);
- gtk_layer_set_layer (window, GTK_LAYER_SHELL_LAYER_TOP);
- gtk_layer_set_namespace (window, "panel");
- gtk_layer_auto_exclusive_zone_enable (window);
- wayland_panel_toplevel_update_placement (toplevel);
-}
-
-void
-wayland_panel_toplevel_update_placement (PanelToplevel* toplevel)
+static void
+wayland_panel_toplevel_apply_anchors (PanelToplevel* toplevel,
+ PanelOrientation orientation,
+ gboolean expand)
{
GtkWindow* window;
- gboolean expand;
- PanelOrientation orientation;
gboolean anchor[GTK_LAYER_SHELL_EDGE_ENTRY_NUMBER];
window = GTK_WINDOW (toplevel);
- expand = panel_toplevel_get_expand (toplevel);
- orientation = panel_toplevel_get_orientation (toplevel);
+
for (int i = 0; i < GTK_LAYER_SHELL_EDGE_ENTRY_NUMBER; i++)
anchor[i] = expand;
@@ -80,3 +71,267 @@ wayland_panel_toplevel_update_placement (PanelToplevel* toplevel)
for (int i = 0; i < GTK_LAYER_SHELL_EDGE_ENTRY_NUMBER; i++)
gtk_layer_set_anchor (window, i, anchor[i]);
}
+
+/* size-allocate handler restoring the real anchors after the size change */
+static void
+wayland_panel_toplevel_set_anchor (GtkWidget *widget,
+ GtkAllocation *alloc,
+ gpointer data)
+{
+ PanelToplevel* toplevel = data;
+
+ gulong id = GPOINTER_TO_SIZE (g_object_get_data (G_OBJECT (toplevel),
+ "wayland-set-anchor-id"));
+ if (id != 0)
+ g_signal_handler_disconnect (G_OBJECT (toplevel), id);
+ g_object_set_data (G_OBJECT (toplevel), "wayland-set-anchor-id", NULL);
+
+ wayland_panel_toplevel_apply_anchors (toplevel,
+ panel_toplevel_get_orientation (toplevel),
+ panel_toplevel_get_expand (toplevel));
+}
+
+/* idle handler switching to the two-anchor {top,left} corner state */
+static void
+wayland_panel_toplevel_set_anchor_default (gpointer data)
+{
+ PanelToplevel* toplevel = data;
+ GtkWindow* window;
+ GtkWidget *widget;
+
+ if (g_object_get_data (G_OBJECT (toplevel), "wayland-set-anchor-id") == NULL) {
+ window = GTK_WINDOW (toplevel);
+ gtk_layer_set_anchor (window, GTK_LAYER_SHELL_EDGE_TOP, TRUE);
+ gtk_layer_set_anchor (window, GTK_LAYER_SHELL_EDGE_BOTTOM, FALSE);
+ gtk_layer_set_anchor (window, GTK_LAYER_SHELL_EDGE_LEFT, TRUE);
+ gtk_layer_set_anchor (window, GTK_LAYER_SHELL_EDGE_RIGHT, FALSE);
+
+ widget = GTK_WIDGET (toplevel);
+ gulong id = g_signal_connect (widget, "size-allocate",
+ G_CALLBACK (wayland_panel_toplevel_set_anchor),
+ toplevel);
+ g_object_set_data (G_OBJECT (toplevel), "wayland-set-anchor-id",
+ GSIZE_TO_POINTER (id));
+ }
+
+ g_object_set_data (G_OBJECT (toplevel), "wayland-set-anchor-default-id", NULL);
+}
+
+/* schedule the corner state switch; must be called before a size change */
+static void
+wayland_panel_toplevel_queue_anchor_default (PanelToplevel* toplevel)
+{
+ guint id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (toplevel),
+ "wayland-set-anchor-default-id"));
+
+ if (id != 0)
+ g_source_remove (id);
+
+ id = g_idle_add ((GSourceFunc) wayland_panel_toplevel_set_anchor_default, toplevel);
+ g_object_set_data (G_OBJECT (toplevel), "wayland-set-anchor-default-id",
+ GUINT_TO_POINTER (id));
+}
+
+static gboolean
+wayland_panel_toplevel_placement_changed (PanelToplevel* toplevel,
+ PanelOrientation orientation,
+ gboolean expand)
+{
+ gpointer prev = g_object_get_data (G_OBJECT (toplevel),
+ "wayland-panel-placement");
+ PanelOrientation prev_orientation =
+ prev != NULL ? (PanelOrientation) GPOINTER_TO_INT (prev) : orientation;
+ gboolean prev_expand = g_object_get_data (G_OBJECT (toplevel),
+ "wayland-panel-expand") != NULL;
+ gboolean changed = prev != NULL &&
+ (prev_orientation != orientation || prev_expand != expand);
+
+ g_object_set_data (G_OBJECT (toplevel), "wayland-panel-placement",
+ GINT_TO_POINTER (orientation));
+ g_object_set_data (G_OBJECT (toplevel), "wayland-panel-expand",
+ expand ? GINT_TO_POINTER (1) : NULL);
+
+ return changed;
+}
+
+void
+wayland_panel_toplevel_update_placement (PanelToplevel* toplevel)
+{
+ gboolean expand;
+ PanelOrientation orientation;
+
+ orientation = panel_toplevel_get_orientation (toplevel);
+ expand = panel_toplevel_get_expand (toplevel);
+
+ if (wayland_panel_toplevel_placement_changed (toplevel, orientation, expand)) {
+ /* the panel size is going to change: let it resize in the
+ * two-anchor corner state first */
+ g_object_set_data (G_OBJECT (toplevel), "wayland-placement-orient",
+ GINT_TO_POINTER (orientation));
+ if (expand)
+ g_object_set_data (G_OBJECT (toplevel), "wayland-placement-expand",
+ GINT_TO_POINTER (1));
+ else
+ g_object_set_data (G_OBJECT (toplevel), "wayland-placement-expand", NULL);
+
+ wayland_panel_toplevel_queue_anchor_default (toplevel);
+ return;
+ }
+
+ wayland_panel_toplevel_apply_anchors (toplevel, orientation, expand);
+}
+
+void
+wayland_panel_toplevel_init (PanelToplevel* toplevel)
+{
+ GtkWindow* window;
+
+ window = GTK_WINDOW (toplevel);
+ gtk_layer_init_for_window (window);
+ gtk_layer_set_layer (window, GTK_LAYER_SHELL_LAYER_TOP);
+ gtk_layer_set_namespace (window, "panel");
+ wayland_panel_toplevel_update_placement (toplevel);
+}
+
+void
+wayland_panel_toplevel_move_resize (PanelToplevel *toplevel,
+ const GdkRectangle *geometry,
+ const GdkRectangle *monitor_geom)
+{
+ GtkWindow* window;
+ gint margin[GTK_LAYER_SHELL_EDGE_ENTRY_NUMBER];
+
+ window = GTK_WINDOW (toplevel);
+
+ margin[GTK_LAYER_SHELL_EDGE_LEFT] = geometry->x - monitor_geom->x;
+ margin[GTK_LAYER_SHELL_EDGE_TOP] = geometry->y - monitor_geom->y;
+ margin[GTK_LAYER_SHELL_EDGE_RIGHT] =
+ monitor_geom->x + monitor_geom->width - (geometry->x + geometry->width);
+ margin[GTK_LAYER_SHELL_EDGE_BOTTOM] =
+ monitor_geom->y + monitor_geom->height - (geometry->y + geometry->height);
+
+ /*
+ * gtk-layer-shell driven layer surfaces never resize the underlying
+ * GDK window themselves; if the widget was reallocated to a different
+ * size (e.g. after an orientation change) the compositor keeps showing
+ * the stale surface footprint. Force the GDK window to the geometry
+ * size and invalidate it, so a correctly sized buffer is committed.
+ */
+ {
+ GdkWindow *gdk = gtk_widget_get_window (GTK_WIDGET (window));
+ if (gdk && (gdk_window_get_width (gdk) != geometry->width ||
+ gdk_window_get_height (gdk) != geometry->height)) {
+ gdk_window_resize (gdk, geometry->width, geometry->height);
+ gdk_window_invalidate_rect (gdk, NULL, FALSE);
+ }
+ }
+
+ for (int i = 0; i < GTK_LAYER_SHELL_EDGE_ENTRY_NUMBER; i++)
+ gtk_layer_set_margin (window, i, margin[i]);
+}
+
+void
+wayland_panel_toplevel_update_exclusive_zone (PanelToplevel* toplevel,
+ gint exclusive_zone)
+{
+ gtk_layer_set_exclusive_zone (GTK_WINDOW (toplevel), exclusive_zone);
+}
+
+/* The autohide drop-zone is only an input surface: clear the surface so the
+ * underlying panel strip stays visible. */
+static gboolean
+wayland_panel_toplevel_autohide_window_draw (GtkWidget *widget,
+ cairo_t *cr)
+{
+ cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
+ cairo_paint (cr);
+ return FALSE;
+}
+
+GtkWindow *
+wayland_panel_toplevel_create_autohide_window (PanelToplevel *toplevel)
+{
+ GtkWindow *window;
+ GdkScreen *screen;
+
+ window = GTK_WINDOW (gtk_window_new (GTK_WINDOW_TOPLEVEL));
+
+ gtk_widget_set_name (GTK_WIDGET (window), "MatePanelWindowHidden");
+ gtk_window_set_title (window, "MatePanelWindowHidden");
+ gtk_window_set_decorated (window, FALSE);
+ gtk_window_set_type_hint (window, GDK_WINDOW_TYPE_HINT_UTILITY);
+ gtk_window_set_skip_taskbar_hint (window, TRUE);
+ gtk_window_set_skip_pager_hint (window, TRUE);
+ gtk_window_set_accept_focus (window, FALSE);
+ gtk_window_set_focus_on_map (window, FALSE);
+ gtk_widget_set_app_paintable (GTK_WIDGET (window), TRUE);
+
+ /* grab crossing events on this surface; they drive the autohide
+ * state machine independently of the (animated) panel surface */
+ gtk_widget_add_events (GTK_WIDGET (window),
+ GDK_ENTER_NOTIFY_MASK |
+ GDK_LEAVE_NOTIFY_MASK |
+ GDK_POINTER_MOTION_MASK);
+
+ /* visually transparent: this window is only an input area */
+ screen = gtk_widget_get_screen (GTK_WIDGET (window));
+ if (gdk_screen_get_rgba_visual (screen))
+ gtk_widget_set_visual (GTK_WIDGET (window),
+ gdk_screen_get_rgba_visual (screen));
+
+ gtk_layer_init_for_window (window);
+ gtk_layer_set_layer (window, GTK_LAYER_SHELL_LAYER_TOP);
+ gtk_layer_set_namespace (window, "panel");
+ gtk_layer_set_exclusive_zone (window, -1);
+ gtk_layer_set_keyboard_interactivity (window, FALSE);
+
+ /* place it with the {left, top} corner margins, like the panel */
+ gtk_layer_set_anchor (window, GTK_LAYER_SHELL_EDGE_LEFT, TRUE);
+ gtk_layer_set_anchor (window, GTK_LAYER_SHELL_EDGE_RIGHT, FALSE);
+ gtk_layer_set_anchor (window, GTK_LAYER_SHELL_EDGE_TOP, TRUE);
+ gtk_layer_set_anchor (window, GTK_LAYER_SHELL_EDGE_BOTTOM, FALSE);
+
+ g_signal_connect (window, "draw",
+ G_CALLBACK (wayland_panel_toplevel_autohide_window_draw),
+ NULL);
+
+ return window;
+}
+
+void
+wayland_panel_toplevel_autohide_window_show (GtkWindow *window,
+ PanelToplevel *toplevel,
+ const GdkRectangle *geometry,
+ const GdkRectangle *monitor_geom)
+{
+ GdkDisplay *display;
+ int monitor;
+ int n_monitors;
+
+ display = gdk_display_get_default ();
+ n_monitors = gdk_display_get_n_monitors (display);
+ monitor = panel_toplevel_get_monitor (toplevel);
+ if (monitor < 0 || monitor >= n_monitors)
+ monitor = 0;
+
+ gtk_layer_set_monitor (window, gdk_display_get_monitor (display, monitor));
+
+ gtk_widget_set_size_request (GTK_WIDGET (window),
+ geometry->width, geometry->height);
+ gtk_window_resize (window, geometry->width, geometry->height);
+
+ gtk_layer_set_margin (window, GTK_LAYER_SHELL_EDGE_LEFT,
+ geometry->x - monitor_geom->x);
+ gtk_layer_set_margin (window, GTK_LAYER_SHELL_EDGE_TOP,
+ geometry->y - monitor_geom->y);
+
+ if (!gtk_widget_get_visible (GTK_WIDGET (window)))
+ gtk_widget_show (GTK_WIDGET (window));
+}
+
+void
+wayland_panel_toplevel_autohide_window_hide (GtkWindow *window)
+{
+ if (gtk_widget_get_visible (GTK_WIDGET (window)))
+ gtk_widget_hide (GTK_WIDGET (window));
+}
diff --git a/mate-panel/wayland-backend.h b/mate-panel/wayland-backend.h
index 0c597127..f001fa57 100644
--- a/mate-panel/wayland-backend.h
+++ b/mate-panel/wayland-backend.h
@@ -39,5 +39,16 @@
void wayland_panel_toplevel_init (PanelToplevel* toplevel);
void wayland_panel_toplevel_update_placement (PanelToplevel* toplevel);
+void wayland_panel_toplevel_move_resize (PanelToplevel* toplevel,
+ const GdkRectangle *geometry,
+ const GdkRectangle *monitor_geom);
+void wayland_panel_toplevel_update_exclusive_zone (PanelToplevel* toplevel,
+ gint exclusive_zone);
+GtkWindow *wayland_panel_toplevel_create_autohide_window (PanelToplevel *toplevel);
+void wayland_panel_toplevel_autohide_window_show (GtkWindow *window,
+ PanelToplevel *toplevel,
+ const GdkRectangle *geometry,
+ const GdkRectangle *monitor_geom);
+void wayland_panel_toplevel_autohide_window_hide (GtkWindow *window);
#endif /* __WAYLAND_BACKEND_H__ */