Fixa GTK-grab-låsningen: en popover åt gången, grab-fria fönsterlistor
All checks were successful
check / check (push) Successful in 1m35s
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:
13
src/dock.rs
13
src/dock.rs
@@ -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()
|
||||||
|
|||||||
@@ -155,6 +155,9 @@ fn activate(app: >k::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
|
||||||
|
|||||||
132
src/menu.rs
132
src/menu.rs
@@ -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: >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<Ctx>, widget: >k::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: >k::Button) {
|
pub fn popup_item_menu(ctx: &Rc<Ctx>, item: &Rc<Item>, anchor: >k::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: >k::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: >k::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: >k::Button) {
|
pub fn popup_group_list(ctx: &Rc<Ctx>, item: &Rc<Item>, anchor: >k::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: >k::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: >k::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: >k::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: >k::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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user