M6: autohide/dodge, hot corners/edges, Wayfire-eventström, urgency
Some checks failed
check / check (push) Failing after 13s

Autohide med Revealer-animation + sensorremsa vid kanten; dodge räknar
fönsteröverlapp via Wayfire-IPC-geometri, helt händelsestyrt via
window-rules/events/watch (ingen polling). Hotspot-ytor för hörn/kanter
med dwell + exec/ipc-actions. Urgency-puls via view-hints-changed.
IPC-socketen hittas även när pluginen aktiverats efter sessionsstart.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-29 23:34:32 +02:00
parent bc2fa65558
commit 95d342ae9e
5 changed files with 596 additions and 34 deletions

162
src/hotspots.rs Normal file
View File

@@ -0,0 +1,162 @@
// Hot corners & hot edges: små osynliga layer-ytor vid skärmens hörn
// och kanter. Pekaren måste vila där i dwell_ms innan actionen körs
// (exec:<kommando> eller ipc:<metod>), och måste lämna ytan innan den
// kan trigga igen.
use std::cell::Cell;
use std::rc::Rc;
use std::time::Duration;
use gtk4 as gtk;
use gtk4::gdk;
use gtk4::glib;
use gtk4::prelude::*;
use gtk4_layer_shell::{Edge, KeyboardMode, Layer, LayerShell};
use log::{info, warn};
use crate::config::HotspotAction;
use crate::dock::Ctx;
use crate::wayfire_ipc;
const CORNER_PX: i32 = 6;
const EDGE_PX: i32 = 2;
fn run_action(action: &HotspotAction) {
match action {
HotspotAction::Exec(cmd) => {
info!("hotspot: kör {cmd}");
let cmd = cmd.clone();
if let Err(e) = glib::spawn_command_line_async(&cmd) {
warn!("hotspot-kommando misslyckades: {e}");
}
}
HotspotAction::Ipc(method) => {
info!("hotspot: ipc {method}");
if let Err(e) = wayfire_ipc::call(method, serde_json::Value::Null) {
warn!("hotspot-ipc {method}: {e}");
}
}
}
}
/// En hotspot-yta: `anchors` anger vilka kanter den fästs mot
/// (två = hörn, en/tre = kant), `size` (w, h) där -1 = sträck ut.
fn build_hotspot(
ctx: &Rc<Ctx>,
monitor: &gdk::Monitor,
anchors: &[Edge],
size: (i32, i32),
action: HotspotAction,
dwell_ms: u32,
) -> gtk::ApplicationWindow {
let win = gtk::ApplicationWindow::new(&ctx.app);
win.add_css_class("waydock-hotspot");
win.init_layer_shell();
win.set_namespace(Some("waydock-hotspot"));
win.set_monitor(Some(monitor));
win.set_layer(Layer::Overlay);
win.set_keyboard_mode(KeyboardMode::None);
win.set_exclusive_zone(0);
for edge in anchors {
win.set_anchor(*edge, true);
}
let area = gtk::Box::new(gtk::Orientation::Horizontal, 0);
area.set_size_request(size.0, size.1);
win.set_child(Some(&area));
let armed = Rc::new(Cell::new(true));
let inside = Rc::new(Cell::new(false));
let motion = gtk::EventControllerMotion::new();
{
let armed = armed.clone();
let inside = inside.clone();
motion.connect_enter(move |_, _, _| {
inside.set(true);
if !armed.get() {
return;
}
let armed = armed.clone();
let inside = inside.clone();
let action = action.clone();
glib::timeout_add_local_once(Duration::from_millis(dwell_ms as u64), move || {
if inside.get() && armed.get() {
armed.set(false); // återarmeras när pekaren lämnar
run_action(&action);
}
});
});
}
{
let armed = armed.clone();
let inside = inside.clone();
motion.connect_leave(move |_| {
inside.set(false);
armed.set(true);
});
}
area.add_controller(motion);
win.present();
win
}
/// Bygg om alla hotspot-ytor enligt configen (körs vid start + configbyte).
pub fn rebuild(ctx: &Rc<Ctx>) {
for win in ctx.hotspot_windows.borrow_mut().drain(..) {
win.destroy();
}
let cfg = ctx.cfg.borrow();
let dwell = cfg.hotspots.dwell_ms;
let c = cfg.hotspots.corners.clone();
let e = cfg.hotspots.edges.clone();
drop(cfg);
let corner_defs: [(&Option<HotspotAction>, [Edge; 2]); 4] = [
(&c.top_left, [Edge::Top, Edge::Left]),
(&c.top_right, [Edge::Top, Edge::Right]),
(&c.bottom_left, [Edge::Bottom, Edge::Left]),
(&c.bottom_right, [Edge::Bottom, Edge::Right]),
];
// kanterna lämnar hörnen fria (hörn-ytor ligger ovanpå ändå — overlay
// med senare mappade ytor hamnar överst, så det kvittar i praktiken)
#[allow(clippy::type_complexity)]
let edge_defs: [(&Option<HotspotAction>, [Edge; 3], (i32, i32)); 4] = [
(&e.top, [Edge::Top, Edge::Left, Edge::Right], (-1, EDGE_PX)),
(&e.bottom, [Edge::Bottom, Edge::Left, Edge::Right], (-1, EDGE_PX)),
(&e.left, [Edge::Left, Edge::Top, Edge::Bottom], (EDGE_PX, -1)),
(&e.right, [Edge::Right, Edge::Top, Edge::Bottom], (EDGE_PX, -1)),
];
let display = gdk::Display::default().expect("ingen display");
let monitors = display.monitors();
let mut windows = Vec::new();
for i in 0..monitors.n_items() {
let Some(monitor) = monitors.item(i).and_then(|o| o.downcast::<gdk::Monitor>().ok())
else {
continue;
};
for (action, edges) in &corner_defs {
if let Some(a) = action {
windows.push(build_hotspot(
ctx,
&monitor,
edges,
(CORNER_PX, CORNER_PX),
a.clone(),
dwell,
));
}
}
for (action, edges, size) in &edge_defs {
if let Some(a) = action {
windows.push(build_hotspot(ctx, &monitor, edges, *size, a.clone(), dwell));
}
}
}
if !windows.is_empty() {
info!("{} hotspot-ytor aktiva", windows.len());
}
*ctx.hotspot_windows.borrow_mut() = windows;
}