From 6be8c6fe272d5f4427d25863962d8739e69e591e Mon Sep 17 00:00:00 2001 From: claude Date: Sun, 30 Aug 2026 00:48:04 +0200 Subject: [PATCH] =?UTF-8?q?Fixa=20GTK-grab-l=C3=A5sningen:=20en=20popover?= =?UTF-8?q?=20=C3=A5t=20g=C3=A5ngen,=20grab-fria=20f=C3=B6nsterlistor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rotorsak till att alla klick slutade fungera (även i settings): grabbing-popovers i snabb följd + ikon-ombyggen som rev en öppen popovers förälder gav 'Tried to map a grabbing popup with a non-top most parent' i mängder och en fastnad pekar-grab på Wayland. - refresh_items skjuts upp medan en popover är öppen (räknare i Ctx) och körs när den stängts — föräldern rivs aldrig under en öppen popup. - Bara en popover åt gången; ny popup stänger den gamla först. - Fönsterlistan grabbar aldrig (autohide av): stängs på pointer-leave (400 ms hover-läge / 700 ms klick-läge) + watchdog om pekaren aldrig når listan. Därmed kan man öppna nästa grupps lista direkt utan att första klicket bara stänger den förra. - Listan öppnas riktad bort från dockans kant och radernas hover-effekter armeras efter 250 ms, så raden under pekaren vid öppning inte triggas av misstag. Co-Authored-By: Claude Fable 5 --- src/dock.rs | 13 ++++++ src/main.rs | 3 ++ src/menu.rs | 132 +++++++++++++++++++++++++++++++++++++++++----------- 3 files changed, 122 insertions(+), 26 deletions(-) diff --git a/src/dock.rs b/src/dock.rs index e3aa74d..45e18af 100644 --- a/src/dock.rs +++ b/src/dock.rs @@ -41,6 +41,12 @@ pub struct Ctx { pub urgent: RefCell>, /// hot corner/edge-fönster (hotspots.rs äger innehållet) pub hotspot_windows: RefCell>, + /// antal öppna popovers — medan > 0 skjuts ikon-ombyggen upp + /// (att riva en öppen popovers förälder låser GTK:s pekar-grab) + pub open_popovers: Cell, + pub refresh_pending: Cell, + /// den enda popover som får vara öppen åt gången + pub current_popover: RefCell>, } pub struct Dock { @@ -544,7 +550,14 @@ fn build_items(ctx: &Ctx, connector: &str, snapshot: &[ToplevelInfo]) -> Vec) { + if ctx.open_popovers.get() > 0 { + ctx.refresh_pending.set(true); + return; + } + ctx.refresh_pending.set(false); let snapshot = ctx .service .borrow() diff --git a/src/main.rs b/src/main.rs index 203ab7d..6c12996 100644 --- a/src/main.rs +++ b/src/main.rs @@ -155,6 +155,9 @@ fn activate(app: >k::Application, cfg_path: PathBuf) { save_generation: Cell::new(0), urgent: RefCell::new(Default::default()), hotspot_windows: RefCell::new(Vec::new()), + open_popovers: Cell::new(0), + refresh_pending: Cell::new(false), + current_popover: RefCell::new(None), }); // CSS + rebuild/refresh-logik diff --git a/src/menu.rs b/src/menu.rs index 54515d1..2367015 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -152,15 +152,64 @@ fn toggle_pin(ctx: &Rc, connector: &str) { crate::dock::refresh_items(ctx); } -/// Popover som städar bort sig själv när den stängs. -fn autodispose(popover: &impl IsA) { +/// Registrera en popover: stänger ev. redan öppen (bara en åt gången — +/// två grabbing-popups samtidigt låser GTK:s pekar-grab på Wayland), +/// bokför öppen-räknaren, kör uppskjuten ikon-refresh när den stängs +/// och städar bort sig själv. +fn register_popover(ctx: &Rc, popover: &impl IsA) { + if let Some(old) = ctx.current_popover.borrow_mut().take() { + old.popdown(); + } let p = popover.as_ref().clone(); - p.connect_closed(|p| { + *ctx.current_popover.borrow_mut() = Some(p.clone()); + ctx.open_popovers.set(ctx.open_popovers.get() + 1); + let ctx = ctx.clone(); + p.connect_closed(move |p| { + ctx.open_popovers + .set(ctx.open_popovers.get().saturating_sub(1)); + { + let mut cur = ctx.current_popover.borrow_mut(); + if cur.as_ref() == Some(p) { + *cur = None; + } + } let p = p.clone(); - glib::idle_add_local_once(move || p.unparent()); + let ctx2 = ctx.clone(); + glib::idle_add_local_once(move || { + p.unparent(); + if ctx2.open_popovers.get() == 0 && ctx2.refresh_pending.get() { + crate::dock::refresh_items(&ctx2); + } + }); }); } +/// Popover-riktning bort från dockans kant (så listan inte öppnas rakt +/// under muspekaren och råkar hovra första raden direkt). +fn popover_position(ctx: &Rc, anchor: >k::Button) -> gtk::PositionType { + let connector = connector_of(ctx, anchor); + match ctx.cfg.borrow().for_output(&connector).position { + crate::config::Position::Bottom => gtk::PositionType::Top, + crate::config::Position::Top => gtk::PositionType::Bottom, + crate::config::Position::Left => gtk::PositionType::Right, + crate::config::Position::Right => gtk::PositionType::Left, + } +} + +fn connector_of(ctx: &Rc, widget: >k::Button) -> String { + ctx.docks + .borrow() + .iter() + .find(|d| { + widget + .ancestor(gtk::ApplicationWindow::static_type()) + .map(|w| w == *d.window.upcast_ref::()) + .unwrap_or(false) + }) + .map(|d| d.connector.clone()) + .unwrap_or_default() +} + /// Högerklicksmenyn på en app-ikon. pub fn popup_item_menu(ctx: &Rc, item: &Rc, anchor: >k::Button) { let cfg = ctx.cfg.borrow(); @@ -211,15 +260,7 @@ pub fn popup_item_menu(ctx: &Rc, item: &Rc, anchor: >k::Button) { drop(cfg); // hitta skärmens connector via dockan som äger knappen - let connector = ctx - .docks - .borrow() - .iter() - .find(|d| anchor.ancestor(gtk::ApplicationWindow::static_type()) - .map(|w| w == *d.window.upcast_ref::()) - .unwrap_or(false)) - .map(|d| d.connector.clone()) - .unwrap_or_default(); + let connector = connector_of(ctx, anchor); *ctx.menu_target.borrow_mut() = Some(MenuTarget { key: item.meta.key.clone(), @@ -230,8 +271,9 @@ pub fn popup_item_menu(ctx: &Rc, item: &Rc, anchor: >k::Button) { let popover = gtk::PopoverMenu::from_model(Some(&menu)); popover.add_css_class("waydock-menu"); + popover.set_position(popover_position(ctx, anchor)); popover.set_parent(anchor); - autodispose(&popover); + register_popover(ctx, &popover); popover.popup(); } @@ -275,12 +317,28 @@ fn peek_restore(dimmed: &[i64]) { pub fn popup_group_list(ctx: &Rc, item: &Rc, anchor: >k::Button) { let popover = gtk::Popover::new(); popover.add_css_class("waydock-menu"); + popover.set_position(popover_position(ctx, anchor)); let list = gtk::Box::new(gtk::Orientation::Vertical, 2); + let connector = connector_of(ctx, anchor); let (list_hover, hover_mode) = { - let b = &ctx.cfg.borrow().behavior; + let b = ctx.cfg.borrow().for_output(&connector).behavior; (b.list_hover.clone(), b.group_popup == GroupPopup::Hover) }; + // fönsterlistan grabbar ALDRIG: grabben gör att första klicket + // utanför bara stänger listan (man kan inte öppna nästa grupp + // direkt) och grabbing-popups i följd låser GTK på Wayland. + // Stängs i stället på pointer-leave + watchdog. + popover.set_autohide(false); + // hover-effekterna armeras först efter en stund så att raden som + // råkar hamna under pekaren vid öppning inte triggar direkt + let armed = Rc::new(std::cell::Cell::new(false)); + { + let armed = armed.clone(); + glib::timeout_add_local_once(std::time::Duration::from_millis(250), move || { + armed.set(true); + }); + } let peek_possible = list_hover == ListHover::Peek && wayfire_ipc::available(); // fönster som tonats ner av peek — återställs vid radbyte/stängning let dimmed: Rc>> = Rc::new(std::cell::RefCell::new(Vec::new())); @@ -313,7 +371,11 @@ pub fn popup_group_list(ctx: &Rc, item: &Rc, anchor: >k::Button) { let mc = gtk::EventControllerMotion::new(); let w3 = w.clone(); let dimmed3 = dimmed.clone(); + let armed3 = armed.clone(); mc.connect_enter(move |_, _, _| { + if !armed3.get() { + return; + } peek_restore(&dimmed3.borrow()); *dimmed3.borrow_mut() = peek_apply(&w3); }); @@ -328,7 +390,11 @@ pub fn popup_group_list(ctx: &Rc, item: &Rc, anchor: >k::Button) { let mc = gtk::EventControllerMotion::new(); let ctx3 = ctx.clone(); let w3 = w.clone(); + let armed3 = armed.clone(); mc.connect_enter(move |_, _, _| { + if !armed3.get() { + return; + } if let Some(s) = ctx3.service.borrow().as_ref() { s.activate(&w3); } @@ -351,23 +417,37 @@ pub fn popup_group_list(ctx: &Rc, item: &Rc, anchor: >k::Button) { }); } - // i hover-läge: stäng listan när pekaren lämnar den - if hover_mode { - let mc = gtk::EventControllerMotion::new(); + // stäng när pekaren lämnar listan (snabbare i hover-läge) + let leave_delay = if hover_mode { 400 } else { 700 }; + let mc = gtk::EventControllerMotion::new(); + { let pop3 = popover.clone(); mc.connect_leave(move |ctl| { let pop4 = pop3.clone(); let ctl2 = ctl.clone(); - glib::timeout_add_local_once(std::time::Duration::from_millis(400), move || { - if !ctl2.contains_pointer() { - pop4.popdown(); - } - }); + glib::timeout_add_local_once( + std::time::Duration::from_millis(leave_delay), + move || { + if !ctl2.contains_pointer() { + pop4.popdown(); + } + }, + ); + }); + } + popover.add_controller(mc.clone()); + + // watchdog: om pekaren aldrig ens når listan, stäng efter en stund + { + let pop3 = popover.clone(); + glib::timeout_add_local_once(std::time::Duration::from_millis(1500), move || { + if !mc.contains_pointer() { + pop3.popdown(); + } }); - popover.add_controller(mc); } - autodispose(&popover); + register_popover(ctx, &popover); popover.popup(); } @@ -412,7 +492,7 @@ pub fn popup_panel_menu(ctx: &Rc, panel: >k::Box, x: f64, y: f64) { popover.add_css_class("waydock-menu"); popover.set_parent(panel); popover.set_pointing_to(Some(&gdk::Rectangle::new(x as i32, y as i32, 1, 1))); - autodispose(&popover); + register_popover(ctx, &popover); popover.popup(); }