diff options
| author | Aleksey Samoilov <[email protected]> | 2026-09-26 01:17:45 +0400 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-09-25 21:17:45 +0000 |
| commit | 58ac6c101bdf2e2975c0395723ad16e9c9764f5c (patch) | |
| tree | ceffc5a4020f8e9df799f428efe9098c9d46b457 | |
| parent | 5aba4cf3447a3a6d44957107f8b0f75ddb1e71d5 (diff) | |
| download | mate-panel-master.tar.bz2 mate-panel-master.tar.xz | |
* 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.c | 47 | ||||
| -rw-r--r-- | mate-panel/panel-toplevel.c | 325 | ||||
| -rw-r--r-- | mate-panel/wayland-backend.c | 297 | ||||
| -rw-r--r-- | mate-panel/wayland-backend.h | 11 |
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__ */ |
