Fixa GTK-grab-låsningen: en popover åt gången, grab-fria fönsterlistor
All checks were successful
check / check (push) Successful in 1m35s

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 <noreply@anthropic.com>
This commit is contained in:
2026-08-30 00:48:04 +02:00
parent 558c178ec4
commit 6be8c6fe27
3 changed files with 122 additions and 26 deletions

View File

@@ -41,6 +41,12 @@ pub struct Ctx {
pub urgent: RefCell<HashSet<String>>, pub urgent: RefCell<HashSet<String>>,
/// hot corner/edge-fönster (hotspots.rs äger innehållet) /// hot corner/edge-fönster (hotspots.rs äger innehållet)
pub hotspot_windows: RefCell<Vec<gtk::ApplicationWindow>>, pub hotspot_windows: RefCell<Vec<gtk::ApplicationWindow>>,
/// 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<u32>,
pub refresh_pending: Cell<bool>,
/// den enda popover som får vara öppen åt gången
pub current_popover: RefCell<Option<gtk::Popover>>,
} }
pub struct Dock { pub struct Dock {
@@ -544,7 +550,14 @@ fn build_items(ctx: &Ctx, connector: &str, snapshot: &[ToplevelInfo]) -> Vec<Ite
} }
/// Uppdatera ikonraderna i alla dockor (körs vid varje fönsterhändelse). /// Uppdatera ikonraderna i alla dockor (körs vid varje fönsterhändelse).
/// Skjuts upp medan en popover är öppen — annars rivs popoverns förälder
/// och GTK:s grab fastnar (inga klick landar någonstans i appen).
pub fn refresh_items(ctx: &Rc<Ctx>) { pub fn refresh_items(ctx: &Rc<Ctx>) {
if ctx.open_popovers.get() > 0 {
ctx.refresh_pending.set(true);
return;
}
ctx.refresh_pending.set(false);
let snapshot = ctx let snapshot = ctx
.service .service
.borrow() .borrow()

View File

@@ -155,6 +155,9 @@ fn activate(app: &gtk::Application, cfg_path: PathBuf) {
save_generation: Cell::new(0), save_generation: Cell::new(0),
urgent: RefCell::new(Default::default()), urgent: RefCell::new(Default::default()),
hotspot_windows: RefCell::new(Vec::new()), 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 // CSS + rebuild/refresh-logik

View File

@@ -152,15 +152,64 @@ fn toggle_pin(ctx: &Rc<Ctx>, connector: &str) {
crate::dock::refresh_items(ctx); crate::dock::refresh_items(ctx);
} }
/// Popover som städar bort sig själv när den stängs. /// Registrera en popover: stänger ev. redan öppen (bara en åt gången —
fn autodispose(popover: &impl IsA<gtk::Popover>) { /// 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<Ctx>, popover: &impl IsA<gtk::Popover>) {
if let Some(old) = ctx.current_popover.borrow_mut().take() {
old.popdown();
}
let p = popover.as_ref().clone(); 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(); 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<Ctx>, anchor: &gtk::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<Ctx>, widget: &gtk::Button) -> String {
ctx.docks
.borrow()
.iter()
.find(|d| {
widget
.ancestor(gtk::ApplicationWindow::static_type())
.map(|w| w == *d.window.upcast_ref::<gtk::Widget>())
.unwrap_or(false)
})
.map(|d| d.connector.clone())
.unwrap_or_default()
}
/// Högerklicksmenyn på en app-ikon. /// Högerklicksmenyn på en app-ikon.
pub fn popup_item_menu(ctx: &Rc<Ctx>, item: &Rc<Item>, anchor: &gtk::Button) { pub fn popup_item_menu(ctx: &Rc<Ctx>, item: &Rc<Item>, anchor: &gtk::Button) {
let cfg = ctx.cfg.borrow(); let cfg = ctx.cfg.borrow();
@@ -211,15 +260,7 @@ pub fn popup_item_menu(ctx: &Rc<Ctx>, item: &Rc<Item>, anchor: &gtk::Button) {
drop(cfg); drop(cfg);
// hitta skärmens connector via dockan som äger knappen // hitta skärmens connector via dockan som äger knappen
let connector = ctx let connector = connector_of(ctx, anchor);
.docks
.borrow()
.iter()
.find(|d| anchor.ancestor(gtk::ApplicationWindow::static_type())
.map(|w| w == *d.window.upcast_ref::<gtk::Widget>())
.unwrap_or(false))
.map(|d| d.connector.clone())
.unwrap_or_default();
*ctx.menu_target.borrow_mut() = Some(MenuTarget { *ctx.menu_target.borrow_mut() = Some(MenuTarget {
key: item.meta.key.clone(), key: item.meta.key.clone(),
@@ -230,8 +271,9 @@ pub fn popup_item_menu(ctx: &Rc<Ctx>, item: &Rc<Item>, anchor: &gtk::Button) {
let popover = gtk::PopoverMenu::from_model(Some(&menu)); let popover = gtk::PopoverMenu::from_model(Some(&menu));
popover.add_css_class("waydock-menu"); popover.add_css_class("waydock-menu");
popover.set_position(popover_position(ctx, anchor));
popover.set_parent(anchor); popover.set_parent(anchor);
autodispose(&popover); register_popover(ctx, &popover);
popover.popup(); popover.popup();
} }
@@ -275,12 +317,28 @@ fn peek_restore(dimmed: &[i64]) {
pub fn popup_group_list(ctx: &Rc<Ctx>, item: &Rc<Item>, anchor: &gtk::Button) { pub fn popup_group_list(ctx: &Rc<Ctx>, item: &Rc<Item>, anchor: &gtk::Button) {
let popover = gtk::Popover::new(); let popover = gtk::Popover::new();
popover.add_css_class("waydock-menu"); popover.add_css_class("waydock-menu");
popover.set_position(popover_position(ctx, anchor));
let list = gtk::Box::new(gtk::Orientation::Vertical, 2); let list = gtk::Box::new(gtk::Orientation::Vertical, 2);
let connector = connector_of(ctx, anchor);
let (list_hover, hover_mode) = { 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) (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(); let peek_possible = list_hover == ListHover::Peek && wayfire_ipc::available();
// fönster som tonats ner av peek — återställs vid radbyte/stängning // fönster som tonats ner av peek — återställs vid radbyte/stängning
let dimmed: Rc<std::cell::RefCell<Vec<i64>>> = Rc::new(std::cell::RefCell::new(Vec::new())); let dimmed: Rc<std::cell::RefCell<Vec<i64>>> = Rc::new(std::cell::RefCell::new(Vec::new()));
@@ -313,7 +371,11 @@ pub fn popup_group_list(ctx: &Rc<Ctx>, item: &Rc<Item>, anchor: &gtk::Button) {
let mc = gtk::EventControllerMotion::new(); let mc = gtk::EventControllerMotion::new();
let w3 = w.clone(); let w3 = w.clone();
let dimmed3 = dimmed.clone(); let dimmed3 = dimmed.clone();
let armed3 = armed.clone();
mc.connect_enter(move |_, _, _| { mc.connect_enter(move |_, _, _| {
if !armed3.get() {
return;
}
peek_restore(&dimmed3.borrow()); peek_restore(&dimmed3.borrow());
*dimmed3.borrow_mut() = peek_apply(&w3); *dimmed3.borrow_mut() = peek_apply(&w3);
}); });
@@ -328,7 +390,11 @@ pub fn popup_group_list(ctx: &Rc<Ctx>, item: &Rc<Item>, anchor: &gtk::Button) {
let mc = gtk::EventControllerMotion::new(); let mc = gtk::EventControllerMotion::new();
let ctx3 = ctx.clone(); let ctx3 = ctx.clone();
let w3 = w.clone(); let w3 = w.clone();
let armed3 = armed.clone();
mc.connect_enter(move |_, _, _| { mc.connect_enter(move |_, _, _| {
if !armed3.get() {
return;
}
if let Some(s) = ctx3.service.borrow().as_ref() { if let Some(s) = ctx3.service.borrow().as_ref() {
s.activate(&w3); s.activate(&w3);
} }
@@ -351,23 +417,37 @@ pub fn popup_group_list(ctx: &Rc<Ctx>, item: &Rc<Item>, anchor: &gtk::Button) {
}); });
} }
// i hover-läge: stäng listan när pekaren lämnar den // stäng när pekaren lämnar listan (snabbare i hover-läge)
if hover_mode { let leave_delay = if hover_mode { 400 } else { 700 };
let mc = gtk::EventControllerMotion::new(); let mc = gtk::EventControllerMotion::new();
{
let pop3 = popover.clone(); let pop3 = popover.clone();
mc.connect_leave(move |ctl| { mc.connect_leave(move |ctl| {
let pop4 = pop3.clone(); let pop4 = pop3.clone();
let ctl2 = ctl.clone(); let ctl2 = ctl.clone();
glib::timeout_add_local_once(std::time::Duration::from_millis(400), move || { glib::timeout_add_local_once(
if !ctl2.contains_pointer() { std::time::Duration::from_millis(leave_delay),
pop4.popdown(); 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(); popover.popup();
} }
@@ -412,7 +492,7 @@ pub fn popup_panel_menu(ctx: &Rc<Ctx>, panel: &gtk::Box, x: f64, y: f64) {
popover.add_css_class("waydock-menu"); popover.add_css_class("waydock-menu");
popover.set_parent(panel); popover.set_parent(panel);
popover.set_pointing_to(Some(&gdk::Rectangle::new(x as i32, y as i32, 1, 1))); popover.set_pointing_to(Some(&gdk::Rectangle::new(x as i32, y as i32, 1, 1)));
autodispose(&popover); register_popover(ctx, &popover);
popover.popup(); popover.popup();
} }