diff --git a/src/PanelWindow.vala b/src/PanelWindow.vala index 88c23192..5c691e28 100644 --- a/src/PanelWindow.vala +++ b/src/PanelWindow.vala @@ -56,19 +56,57 @@ public class Wingpanel.PanelWindow : Gtk.Window { return; } else { Services.BackgroundManager.get_default ().remember_window (); + + // Panels are never granted keyboard focus automatically by the + // compositor; it must be requested explicitly. Without it a + // popover opened from a keybinding has no keyboard focus and + // typing (e.g. in the Applications Menu search field) does not + // reach it. + if (desktop_panel != null) { + desktop_panel.focus (); + // focus () is an asynchronous request. The popover is + // popped up immediately after this returns, so without + // waiting for the compositor to actually grant focus the + // popover comes up unfocused and never gets mapped. + unowned var display = Gdk.Display.get_default (); + if (display is Gdk.Wayland.Display) { + ((Gdk.Wayland.Display) display).get_wl_display ().roundtrip (); + } + } } }); Services.BackgroundManager.get_default ().background_state_changed.connect (update_background); } + private bool realize_handled = false; private void on_realize () { + // A widget can be realized more than once. Connecting the surface + // signals again would run them multiple times per frame, and init_wl () + // would re-request our panel object and get us killed by the + // compositor, so only ever do this once. + if (realize_handled) { + return; + } + + realize_handled = true; + unowned var surface = (Gdk.Toplevel) get_surface (); surface.compute_size.connect (on_compute_size); surface.layout.connect (on_layout); surface.enter_monitor.connect (on_enter_monitor); + // The popover takes no Wayland grab (see PopoverManager) so it will not + // dismiss itself. Watch the toplevel's focus state instead and close it + // when the panel loses keyboard focus. + surface.notify["state"].connect (() => { + var focused = (surface.state & Gdk.ToplevelState.FOCUSED) != 0; + if (!focused && popover_manager.indicator_open) { + popover_manager.close (); + } + }); + init_wl (); } @@ -103,6 +141,15 @@ public class Wingpanel.PanelWindow : Gtk.Window { public void registry_handle_global (Wl.Registry wl_registry, uint32 name, string @interface, uint32 version) { if (@interface == "io_elementary_pantheon_shell_v1") { + // get_panel () may only be requested once per wl_surface: asking + // twice makes the compositor kill us with a protocol error + // ("get_panel already requested"), which takes the whole panel + // down. realize can fire more than once, so bail out if we already + // have our panel object. + if (desktop_panel != null) { + return; + } + desktop_shell = wl_registry.bind (name, ref Pantheon.Desktop.Shell.iface, uint32.min (version, 1)); unowned var window = get_surface (); if (window is Gdk.Wayland.Surface) { diff --git a/src/Services/PopoverManager.vala b/src/Services/PopoverManager.vala index ea74b347..859af810 100644 --- a/src/Services/PopoverManager.vala +++ b/src/Services/PopoverManager.vala @@ -48,12 +48,24 @@ public class Wingpanel.Services.PopoverManager : Object { _current_indicator.display_widget.has_tooltip = true; _current_indicator.base_indicator.closed (); _current_indicator = value; - popover.unparent (); } if (_current_indicator != null) { popover.child = _current_indicator.indicator_widget; - popover.set_parent (_current_indicator); + // Only reparent when the popover actually needs to move to a + // different indicator. Unparenting destroys the popover's + // surface and set_parent () builds a new one; doing that on + // every open/close cycle eventually leaves the popover + // unmapped (visible but never drawn) and ends in a Wayland + // protocol error. + if (popover.parent != _current_indicator) { + if (popover.parent != null) { + popover.unparent (); + } + + popover.set_parent (_current_indicator); + } + popover.popup (); _current_indicator.set_state_flags (CHECKED, true); @@ -63,16 +75,33 @@ public class Wingpanel.Services.PopoverManager : Object { } } + public void close () { + current_indicator = null; + } + construct { popover = new Gtk.Popover () { has_arrow = false, - position = BOTTOM + position = BOTTOM, + // autohide makes GTK request an xdg_popup grab. Wayland only grants + // that grab against a recent input event serial, and a popover + // opened from a keybinding has received no input at all, so the + // compositor refuses the popup and it is never mapped. Dismissal + // is handled by PanelWindow watching the toplevel focus state. + autohide = false }; popover.add_css_class ("indicator"); popover.closed.connect (() => { - current_indicator = null; - popover.unparent (); + // Fires for closes the setter already handled (it nulls + // _current_indicator before the asynchronous popdown), so guard + // against dereferencing null. Deliberately does NOT unparent: the + // popover stays parented to its indicator and is only moved when a + // different indicator is opened. + if (_current_indicator != null) { + _current_indicator.set_state_flags (NORMAL, true); + current_indicator = null; + } }); } }