M6: autohide/dodge, hot corners/edges, Wayfire-eventström, urgency
Some checks failed
check / check (push) Failing after 13s
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:
304
src/dock.rs
304
src/dock.rs
@@ -1,9 +1,12 @@
|
||||
// Dockfönstren: ett layer-shell-fönster per skärm. Bygger ikonraden av
|
||||
// pins + körande fönstergrupper och kopplar klick/skroll/menyer.
|
||||
// pins + körande fönstergrupper, kopplar klick/skroll/menyer och sköter
|
||||
// autohide (alltid-göm eller dodge via Wayfire-IPC-geometri).
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::collections::HashSet;
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
use gtk4 as gtk;
|
||||
use gtk4::gdk;
|
||||
@@ -13,7 +16,8 @@ use gtk4_layer_shell::{Edge, KeyboardMode, Layer, LayerShell};
|
||||
use log::{debug, info};
|
||||
|
||||
use crate::config::{
|
||||
ClickAction, Layer as CfgLayer, MiddleClick, Position, ScrollAction, SharedConfig,
|
||||
AutohideMode, ClickAction, Layer as CfgLayer, MiddleClick, Position, ScrollAction,
|
||||
SharedConfig,
|
||||
};
|
||||
use crate::icons::{self, AppMeta};
|
||||
use crate::menu;
|
||||
@@ -25,20 +29,35 @@ pub struct Ctx {
|
||||
pub cfg: SharedConfig,
|
||||
pub cfg_path: PathBuf,
|
||||
pub service: RefCell<Option<Rc<ToplevelService>>>,
|
||||
pub docks: RefCell<Vec<Dock>>,
|
||||
pub docks: RefCell<Vec<Rc<Dock>>>,
|
||||
/// målet för den öppna kontextmenyn (menu.rs läser)
|
||||
pub menu_target: RefCell<Option<menu::MenuTarget>>,
|
||||
pub app: gtk::Application,
|
||||
/// körs när configen ändrats (sätts av main; applicerar CSS/rebuild)
|
||||
pub on_config_changed: RefCell<Option<Rc<dyn Fn()>>>,
|
||||
/// generationsräknare för settings-GUI:ts debouncade sparningar
|
||||
pub save_generation: std::cell::Cell<u64>,
|
||||
pub save_generation: Cell<u64>,
|
||||
/// app-nycklar vars fönster kräver uppmärksamhet (via Wayfire-events)
|
||||
pub urgent: RefCell<HashSet<String>>,
|
||||
/// hot corner/edge-fönster (hotspots.rs äger innehållet)
|
||||
pub hotspot_windows: RefCell<Vec<gtk::ApplicationWindow>>,
|
||||
}
|
||||
|
||||
pub struct Dock {
|
||||
pub window: gtk::ApplicationWindow,
|
||||
pub panel: gtk::Box,
|
||||
pub revealer: gtk::Revealer,
|
||||
pub connector: String,
|
||||
/// pekaren är över dockan just nu
|
||||
pub hover: Cell<bool>,
|
||||
/// dockan är undan-gömd av autohide
|
||||
pub hidden: Cell<bool>,
|
||||
/// något fönster överlappar dockans kantzon (dodge)
|
||||
pub dodge_overlap: Cell<bool>,
|
||||
/// generationsräknare för fördröjd göm-timer
|
||||
hide_generation: Cell<u64>,
|
||||
/// sensorremsan som väcker dockan när den är gömd
|
||||
sensor: RefCell<Option<gtk::ApplicationWindow>>,
|
||||
}
|
||||
|
||||
/// En ikon i docken: en pin och/eller en grupp körande fönster.
|
||||
@@ -55,6 +74,22 @@ fn orientation(pos: &Position) -> gtk::Orientation {
|
||||
}
|
||||
}
|
||||
|
||||
fn main_edge(pos: &Position) -> Edge {
|
||||
match pos {
|
||||
Position::Bottom => Edge::Bottom,
|
||||
Position::Top => Edge::Top,
|
||||
Position::Left => Edge::Left,
|
||||
Position::Right => Edge::Right,
|
||||
}
|
||||
}
|
||||
|
||||
fn side_edges(pos: &Position) -> (Edge, Edge) {
|
||||
match pos {
|
||||
Position::Bottom | Position::Top => (Edge::Left, Edge::Right),
|
||||
_ => (Edge::Top, Edge::Bottom),
|
||||
}
|
||||
}
|
||||
|
||||
fn configure_layer(win: >k::ApplicationWindow, ctx: &Ctx, monitor: &gdk::Monitor) {
|
||||
let cfg = ctx.cfg.borrow();
|
||||
win.init_layer_shell();
|
||||
@@ -67,20 +102,15 @@ fn configure_layer(win: >k::ApplicationWindow, ctx: &Ctx, monitor: &gdk::Monit
|
||||
});
|
||||
win.set_keyboard_mode(KeyboardMode::None);
|
||||
|
||||
let (main_edge, side_a, side_b) = match cfg.position {
|
||||
Position::Bottom => (Edge::Bottom, Edge::Left, Edge::Right),
|
||||
Position::Top => (Edge::Top, Edge::Left, Edge::Right),
|
||||
Position::Left => (Edge::Left, Edge::Top, Edge::Bottom),
|
||||
Position::Right => (Edge::Right, Edge::Top, Edge::Bottom),
|
||||
};
|
||||
win.set_anchor(main_edge, true);
|
||||
win.set_margin(main_edge, cfg.margin);
|
||||
let edge = main_edge(&cfg.position);
|
||||
let (side_a, side_b) = side_edges(&cfg.position);
|
||||
win.set_anchor(edge, true);
|
||||
win.set_margin(edge, cfg.margin);
|
||||
if !cfg.centered {
|
||||
// full bredd/höjd längs kanten
|
||||
win.set_anchor(side_a, true);
|
||||
win.set_anchor(side_b, true);
|
||||
}
|
||||
if cfg.avoid_windows {
|
||||
if cfg.avoid_windows && !cfg.autohide.enabled {
|
||||
win.auto_exclusive_zone_enable();
|
||||
} else {
|
||||
win.set_exclusive_zone(0);
|
||||
@@ -90,6 +120,9 @@ fn configure_layer(win: >k::ApplicationWindow, ctx: &Ctx, monitor: &gdk::Monit
|
||||
/// Bygg om alla dockfönster från grunden (vid start, configbyte, skärmbyte).
|
||||
pub fn rebuild_all(ctx: &Rc<Ctx>) {
|
||||
for dock in ctx.docks.borrow_mut().drain(..) {
|
||||
if let Some(sensor) = dock.sensor.borrow_mut().take() {
|
||||
sensor.destroy();
|
||||
}
|
||||
dock.window.destroy();
|
||||
}
|
||||
|
||||
@@ -113,9 +146,11 @@ pub fn rebuild_all(ctx: &Rc<Ctx>) {
|
||||
}
|
||||
*ctx.docks.borrow_mut() = docks;
|
||||
refresh_items(ctx);
|
||||
recompute_dodge(ctx);
|
||||
update_autohide(ctx);
|
||||
}
|
||||
|
||||
fn build_dock(ctx: &Rc<Ctx>, monitor: &gdk::Monitor, connector: String) -> Dock {
|
||||
fn build_dock(ctx: &Rc<Ctx>, monitor: &gdk::Monitor, connector: String) -> Rc<Dock> {
|
||||
let cfg = ctx.cfg.borrow();
|
||||
let win = gtk::ApplicationWindow::new(&ctx.app);
|
||||
win.add_css_class("waydock");
|
||||
@@ -138,12 +173,41 @@ fn build_dock(ctx: &Rc<Ctx>, monitor: &gdk::Monitor, connector: String) -> Dock
|
||||
outer.set_margin_start(pad);
|
||||
outer.set_margin_end(pad);
|
||||
outer.append(&panel);
|
||||
win.set_child(Some(&outer));
|
||||
|
||||
// revealern ger autohide-animationen
|
||||
let revealer = gtk::Revealer::new();
|
||||
revealer.set_transition_type(match cfg.position {
|
||||
Position::Bottom => gtk::RevealerTransitionType::SlideUp,
|
||||
Position::Top => gtk::RevealerTransitionType::SlideDown,
|
||||
Position::Left => gtk::RevealerTransitionType::SlideRight,
|
||||
Position::Right => gtk::RevealerTransitionType::SlideLeft,
|
||||
});
|
||||
revealer.set_transition_duration(if cfg.appearance.animations {
|
||||
cfg.autohide.animation_ms
|
||||
} else {
|
||||
0
|
||||
});
|
||||
revealer.set_reveal_child(true);
|
||||
revealer.set_child(Some(&outer));
|
||||
win.set_child(Some(&revealer));
|
||||
let autohide_enabled = cfg.autohide.enabled;
|
||||
drop(cfg);
|
||||
|
||||
configure_layer(&win, ctx, monitor);
|
||||
menu::setup_dock_actions(ctx, &win);
|
||||
|
||||
let dock = Rc::new(Dock {
|
||||
window: win.clone(),
|
||||
panel: panel.clone(),
|
||||
revealer,
|
||||
connector,
|
||||
hover: Cell::new(false),
|
||||
hidden: Cell::new(false),
|
||||
dodge_overlap: Cell::new(false),
|
||||
hide_generation: Cell::new(0),
|
||||
sensor: RefCell::new(None),
|
||||
});
|
||||
|
||||
// högerklick på tom panelyta → panelmenyn
|
||||
let empty_click = gtk::GestureClick::new();
|
||||
empty_click.set_button(3);
|
||||
@@ -157,9 +221,198 @@ fn build_dock(ctx: &Rc<Ctx>, monitor: &gdk::Monitor, connector: String) -> Dock
|
||||
}
|
||||
panel.add_controller(empty_click);
|
||||
|
||||
// hover-spårning för autohide
|
||||
let motion = gtk::EventControllerMotion::new();
|
||||
{
|
||||
let dock2 = dock.clone();
|
||||
motion.connect_enter(move |_, _, _| {
|
||||
dock2.hover.set(true);
|
||||
dock2.hide_generation.set(dock2.hide_generation.get() + 1);
|
||||
});
|
||||
}
|
||||
{
|
||||
let ctx = ctx.clone();
|
||||
let dock2 = dock.clone();
|
||||
motion.connect_leave(move |_| {
|
||||
dock2.hover.set(false);
|
||||
schedule_hide(&ctx, &dock2);
|
||||
});
|
||||
}
|
||||
win.add_controller(motion);
|
||||
|
||||
if autohide_enabled {
|
||||
build_sensor(ctx, &dock, monitor);
|
||||
}
|
||||
|
||||
win.present();
|
||||
info!("dock uppe på {connector}");
|
||||
Dock { window: win, panel, connector }
|
||||
info!("dock uppe på {}", dock.connector);
|
||||
dock
|
||||
}
|
||||
|
||||
/// Den tunna remsan vid skärmkanten som väcker en gömd dock.
|
||||
fn build_sensor(ctx: &Rc<Ctx>, dock: &Rc<Dock>, monitor: &gdk::Monitor) {
|
||||
let cfg = ctx.cfg.borrow();
|
||||
let sensor = gtk::ApplicationWindow::new(&ctx.app);
|
||||
sensor.add_css_class("waydock-sensor");
|
||||
sensor.init_layer_shell();
|
||||
sensor.set_namespace(Some("waydock-sensor"));
|
||||
sensor.set_monitor(Some(monitor));
|
||||
sensor.set_layer(Layer::Overlay);
|
||||
sensor.set_keyboard_mode(KeyboardMode::None);
|
||||
sensor.set_exclusive_zone(0);
|
||||
|
||||
let edge = main_edge(&cfg.position);
|
||||
let (side_a, side_b) = side_edges(&cfg.position);
|
||||
sensor.set_anchor(edge, true);
|
||||
sensor.set_anchor(side_a, true);
|
||||
sensor.set_anchor(side_b, true);
|
||||
|
||||
let strip = gtk::Box::new(gtk::Orientation::Horizontal, 0);
|
||||
let px = cfg.autohide.reveal_px.max(1);
|
||||
match cfg.position {
|
||||
Position::Bottom | Position::Top => strip.set_size_request(-1, px),
|
||||
_ => strip.set_size_request(px, -1),
|
||||
}
|
||||
drop(cfg);
|
||||
sensor.set_child(Some(&strip));
|
||||
|
||||
let motion = gtk::EventControllerMotion::new();
|
||||
{
|
||||
let ctx = ctx.clone();
|
||||
let dock2 = dock.clone();
|
||||
motion.connect_enter(move |_, _, _| {
|
||||
if dock2.hidden.get() {
|
||||
set_dock_hidden(&dock2, false);
|
||||
// om pekaren aldrig når dockan: göm igen efter en stund
|
||||
let ctx2 = ctx.clone();
|
||||
let dock3 = dock2.clone();
|
||||
glib::timeout_add_local_once(Duration::from_millis(1500), move || {
|
||||
if !dock3.hover.get() {
|
||||
update_autohide(&ctx2);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
strip.add_controller(motion);
|
||||
|
||||
sensor.present();
|
||||
*dock.sensor.borrow_mut() = Some(sensor);
|
||||
}
|
||||
|
||||
fn set_dock_hidden(dock: &Rc<Dock>, hidden: bool) {
|
||||
if dock.hidden.get() == hidden {
|
||||
return;
|
||||
}
|
||||
dock.hidden.set(hidden);
|
||||
if hidden {
|
||||
let duration = dock.revealer.transition_duration();
|
||||
dock.revealer.set_reveal_child(false);
|
||||
let dock2 = dock.clone();
|
||||
glib::timeout_add_local_once(Duration::from_millis(duration as u64 + 30), move || {
|
||||
if dock2.hidden.get() {
|
||||
dock2.window.set_visible(false);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
dock.window.set_visible(true);
|
||||
dock.revealer.set_reveal_child(true);
|
||||
}
|
||||
}
|
||||
|
||||
fn should_hide(ctx: &Ctx, dock: &Dock) -> bool {
|
||||
let cfg = ctx.cfg.borrow();
|
||||
if !cfg.autohide.enabled || dock.hover.get() {
|
||||
return false;
|
||||
}
|
||||
match cfg.autohide.mode {
|
||||
AutohideMode::Always => true,
|
||||
AutohideMode::Dodge => {
|
||||
if wayfire_ipc::available() {
|
||||
dock.dodge_overlap.get()
|
||||
} else {
|
||||
true // ingen geometri att gå på — bete dig som always
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Applicera göm/visa-läget på alla dockor.
|
||||
pub fn update_autohide(ctx: &Rc<Ctx>) {
|
||||
for dock in ctx.docks.borrow().iter() {
|
||||
set_dock_hidden(dock, should_hide(ctx, dock));
|
||||
}
|
||||
}
|
||||
|
||||
fn schedule_hide(ctx: &Rc<Ctx>, dock: &Rc<Dock>) {
|
||||
let delay = ctx.cfg.borrow().autohide.hide_delay_ms;
|
||||
let generation = dock.hide_generation.get() + 1;
|
||||
dock.hide_generation.set(generation);
|
||||
let ctx = ctx.clone();
|
||||
let dock = dock.clone();
|
||||
glib::timeout_add_local_once(Duration::from_millis(delay as u64), move || {
|
||||
if dock.hide_generation.get() == generation && should_hide(&ctx, &dock) {
|
||||
set_dock_hidden(&dock, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Räkna om dodge-överlapp för alla dockor via Wayfire-IPC-geometri.
|
||||
pub fn recompute_dodge(ctx: &Rc<Ctx>) {
|
||||
{
|
||||
let cfg = ctx.cfg.borrow();
|
||||
if !cfg.autohide.enabled || cfg.autohide.mode != AutohideMode::Dodge {
|
||||
return;
|
||||
}
|
||||
}
|
||||
let Some(views) = wayfire_ipc::list_views() else { return };
|
||||
let Some(outputs) = wayfire_ipc::list_outputs() else { return };
|
||||
let position = ctx.cfg.borrow().position.clone();
|
||||
let margin = ctx.cfg.borrow().margin;
|
||||
|
||||
for dock in ctx.docks.borrow().iter() {
|
||||
let Some(output) = outputs
|
||||
.iter()
|
||||
.find(|o| o["name"].as_str() == Some(dock.connector.as_str()))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let ow = output["geometry"]["width"].as_f64().unwrap_or(0.0);
|
||||
let oh = output["geometry"]["height"].as_f64().unwrap_or(0.0);
|
||||
// zonen dockan upptar vid sin kant (fönstrets storlek + marginal)
|
||||
let depth = match position {
|
||||
Position::Bottom | Position::Top => dock.window.height().max(40) + margin,
|
||||
_ => dock.window.width().max(40) + margin,
|
||||
} as f64;
|
||||
|
||||
let overlap = views.iter().any(|v| {
|
||||
if v["role"].as_str() != Some("toplevel")
|
||||
|| !v["mapped"].as_bool().unwrap_or(false)
|
||||
|| v["minimized"].as_bool().unwrap_or(false)
|
||||
|| v["output-name"].as_str() != Some(dock.connector.as_str())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
let g = &v["geometry"];
|
||||
let (x, y) = (g["x"].as_f64().unwrap_or(0.0), g["y"].as_f64().unwrap_or(0.0));
|
||||
let (w, h) = (
|
||||
g["width"].as_f64().unwrap_or(0.0),
|
||||
g["height"].as_f64().unwrap_or(0.0),
|
||||
);
|
||||
// utanför aktuell arbetsyta → ingen kollision
|
||||
if x + w <= 0.0 || y + h <= 0.0 || x >= ow || y >= oh {
|
||||
return false;
|
||||
}
|
||||
match position {
|
||||
Position::Bottom => y + h >= oh - depth,
|
||||
Position::Top => y <= depth,
|
||||
Position::Left => x <= depth,
|
||||
Position::Right => x + w >= ow - depth,
|
||||
}
|
||||
});
|
||||
dock.dodge_overlap.set(overlap);
|
||||
}
|
||||
update_autohide(ctx);
|
||||
}
|
||||
|
||||
/// Gruppera snapshot → ikonrad för en viss skärm.
|
||||
@@ -223,12 +476,8 @@ fn special_button(icon_name: &str, tooltip: &str) -> gtk::Button {
|
||||
btn
|
||||
}
|
||||
|
||||
fn add_special_buttons(ctx: &Rc<Ctx>, dock: &Dock) {
|
||||
let cfg = ctx.cfg.borrow();
|
||||
let specials = cfg.special_buttons.clone();
|
||||
let icon_size = cfg.appearance.icon_size;
|
||||
drop(cfg);
|
||||
let _ = icon_size;
|
||||
fn add_special_buttons(ctx: &Rc<Ctx>, dock: &Rc<Dock>) {
|
||||
let specials = ctx.cfg.borrow().special_buttons.clone();
|
||||
|
||||
if specials.overview && wayfire_ipc::available() {
|
||||
let btn = special_button("view-grid-symbolic", "Översikt (scale)");
|
||||
@@ -252,12 +501,17 @@ fn build_item_widget(ctx: &Rc<Ctx>, item: Rc<Item>) -> gtk::Button {
|
||||
let icon_size = cfg.appearance.icon_size;
|
||||
let max_dots = cfg.appearance.indicator.max as usize;
|
||||
let tooltips = cfg.behavior.tooltips;
|
||||
let urgent_pulse = cfg.behavior.urgent_pulse;
|
||||
drop(cfg);
|
||||
|
||||
let btn = gtk::Button::new();
|
||||
btn.add_css_class("dock-item");
|
||||
btn.set_has_frame(false);
|
||||
|
||||
if urgent_pulse && ctx.urgent.borrow().contains(&item.meta.key) {
|
||||
btn.add_css_class("urgent");
|
||||
}
|
||||
|
||||
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 2);
|
||||
let overlay = gtk::Overlay::new();
|
||||
overlay.set_child(Some(&icons::image_for(&item.meta, icon_size)));
|
||||
|
||||
Reference in New Issue
Block a user