Fixa omladdningsloopen + churn; CI/släpp flyttat till x64-byggnoden
Some checks failed
check / check (push) Failing after 1m4s

- KRITISKT: notify-watchern reagerade på Access-events, så vår egen
  läsning av configen triggade ny omladdning i all oändlighet (var
  155:e ms). Panelen revs och byggdes om konstant → blinkande hover,
  klick som aldrig landade. Nu filtreras eventen till riktiga
  skrivningar (Modify/Create/Remove/Close-Write).
- Titeländringar (webbläsare/terminaler) bygger inte längre om UI:t;
  tooltips slås i stället upp färskt vid hover (query-tooltip), och
  klick/skroll/menyer agerar på färskt fönster-state vid eventtillfället.
- hotspots::rebuild är idempotent (skippar om config+skärmar oförändrade).
- Settings: markup-escape i rubriker (Kant & skugga-varningen).
- CI: check + release körs på byggnoden brasse-linux01 (runs-on
  linux-amd64) — waydock byggs BARA för x86_64, aldrig på Pi5:ns
  arm-runner. Ny release.yaml: vX.Y.Z-tagg bygger x64-binären och
  laddar upp till Gitea-släppsidan + rullande latest. release.sh är
  nu bara tagga-och-pusha.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-29 23:51:27 +02:00
parent 0ded844b3a
commit e4e4d39405
10 changed files with 230 additions and 70 deletions

View File

@@ -415,6 +415,33 @@ pub fn recompute_dodge(ctx: &Rc<Ctx>) {
update_autohide(ctx);
}
/// Färska fönster för en ikon — slås upp vid klick-/tooltip-tillfället så
/// att closures aldrig agerar på inaktuellt state.
pub fn fresh_windows(ctx: &Ctx, connector: &str, key: &str) -> Vec<ToplevelInfo> {
let service = ctx.service.borrow();
let Some(service) = service.as_ref() else {
return Vec::new();
};
let only_here = ctx.cfg.borrow().only_this_output(connector);
service
.snapshot()
.into_iter()
.filter(|t| {
(!only_here || t.outputs.iter().any(|o| o == connector))
&& icons::lookup(&t.app_id).key == key
})
.collect()
}
/// Bygg en färsk kopia av en ikon vid interaktionstillfället.
fn live_item(ctx: &Ctx, connector: &str, item: &Item) -> Rc<Item> {
Rc::new(Item {
meta: item.meta.clone(),
windows: fresh_windows(ctx, connector, &item.meta.key),
pinned: item.pinned,
})
}
/// Gruppera snapshot → ikonrad för en viss skärm.
fn build_items(ctx: &Ctx, connector: &str, snapshot: &[ToplevelInfo]) -> Vec<Item> {
let cfg = ctx.cfg.borrow();
@@ -459,7 +486,7 @@ pub fn refresh_items(ctx: &Rc<Ctx>) {
}
add_special_buttons(ctx, dock);
for item in build_items(ctx, &dock.connector, &snapshot) {
let widget = build_item_widget(ctx, Rc::new(item));
let widget = build_item_widget(ctx, &dock.connector, Rc::new(item));
dock.panel.append(&widget);
}
}
@@ -496,7 +523,7 @@ fn add_special_buttons(ctx: &Rc<Ctx>, dock: &Rc<Dock>) {
}
}
fn build_item_widget(ctx: &Rc<Ctx>, item: Rc<Item>) -> gtk::Button {
fn build_item_widget(ctx: &Rc<Ctx>, connector: &str, item: Rc<Item>) -> gtk::Button {
let cfg = ctx.cfg.borrow();
let icon_size = cfg.appearance.icon_size;
let max_dots = cfg.appearance.indicator.max as usize;
@@ -543,15 +570,23 @@ fn build_item_widget(ctx: &Rc<Ctx>, item: Rc<Item>) -> gtk::Button {
btn.set_child(Some(&vbox));
if tooltips {
let tip = match item.windows.len() {
0 => item.meta.display_name.clone(),
1 => {
let t = &item.windows[0].title;
if t.is_empty() { item.meta.display_name.clone() } else { t.clone() }
}
n => format!("{}{n} fönster", item.meta.display_name),
};
btn.set_tooltip_text(Some(&tip));
btn.set_has_tooltip(true);
let ctx2 = ctx.clone();
let item2 = item.clone();
let conn2 = connector.to_string();
btn.connect_query_tooltip(move |_, _, _, _, tooltip| {
let windows = fresh_windows(&ctx2, &conn2, &item2.meta.key);
let tip = match windows.len() {
0 => item2.meta.display_name.clone(),
1 => {
let t = &windows[0].title;
if t.is_empty() { item2.meta.display_name.clone() } else { t.clone() }
}
n => format!("{}{n} fönster", item2.meta.display_name),
};
tooltip.set_text(Some(&tip));
true
});
}
// klick: vänster/mitt/höger
@@ -561,12 +596,14 @@ fn build_item_widget(ctx: &Rc<Ctx>, item: Rc<Item>) -> gtk::Button {
let ctx = ctx.clone();
let item = item.clone();
let btn2 = btn.clone();
let conn2 = connector.to_string();
click.connect_released(move |gesture, _, _, _| {
gesture.set_state(gtk::EventSequenceState::Claimed);
let live = live_item(&ctx, &conn2, &item);
match gesture.current_button() {
1 => primary_action(&ctx, &item, &btn2),
2 => middle_action(&ctx, &item),
3 => menu::popup_item_menu(&ctx, &item, &btn2),
1 => primary_action(&ctx, &live, &btn2),
2 => middle_action(&ctx, &live),
3 => menu::popup_item_menu(&ctx, &live, &btn2),
_ => {}
}
});
@@ -578,9 +615,11 @@ fn build_item_widget(ctx: &Rc<Ctx>, item: Rc<Item>) -> gtk::Button {
{
let ctx = ctx.clone();
let item = item.clone();
let conn2 = connector.to_string();
scroll.connect_scroll(move |_, _, dy| {
if ctx.cfg.borrow().behavior.scroll == ScrollAction::CycleWindows {
cycle_windows(&ctx, &item, dy > 0.0);
let live = live_item(&ctx, &conn2, &item);
cycle_windows(&ctx, &live, dy > 0.0);
glib::Propagation::Stop
} else {
glib::Propagation::Proceed