Per-skärm-inställningar med skärmväljare, kant-i-kant-marginaler
All checks were successful
check / check (push) Successful in 1m39s

- outputs{} är nu generiska JSON-överridar: vilket configfält som helst
  kan sättas per skärm och deep-mergas över den globala configen
  (Config::for_output). Docka, autohide, dodge, beteende, hotspots och
  CSS (scopat på window.o-<connector>) läser skärmens effektiva config.
- Settings har skärmväljare överst: klickbar förhandsgranskning av
  skärmarna + deras barer, 'Alla skärmar' (standard) eller enskild
  skärm. Bara ändrade fält sparas som override (json-diff);
  återställ-knapp tar bort skärmens överridar. * = har egna.
- Marginaler: skuggutrymmet läggs inte längre på förankrade sidor så
  0 = kant i kant; minusvärden tillåts (A ner till -64, B/C -256).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-30 00:25:05 +02:00
parent 04d999642c
commit 558c178ec4
8 changed files with 609 additions and 167 deletions

View File

@@ -16,8 +16,8 @@ use gtk4_layer_shell::{Edge, KeyboardMode, Layer, LayerShell};
use log::{debug, info};
use crate::config::{
Alignment, AutohideMode, ClickAction, GroupPopup, Layer as CfgLayer, MiddleClick,
Position, ScrollAction, SharedConfig,
Alignment, AutohideMode, ClickAction, Config, GroupPopup, Layer as CfgLayer,
MiddleClick, Position, ScrollAction, SharedConfig,
};
use crate::icons::{self, AppMeta};
use crate::menu;
@@ -90,8 +90,7 @@ fn side_edges(pos: &Position) -> (Edge, Edge) {
}
}
fn configure_layer(win: &gtk::ApplicationWindow, ctx: &Ctx, monitor: &gdk::Monitor) {
let cfg = ctx.cfg.borrow();
fn configure_layer(win: &gtk::ApplicationWindow, cfg: &Config, monitor: &gdk::Monitor) {
win.init_layer_shell();
win.set_namespace(Some("waydock"));
win.set_monitor(Some(monitor));
@@ -167,9 +166,10 @@ pub fn rebuild_all(ctx: &Rc<Ctx>) {
}
fn build_dock(ctx: &Rc<Ctx>, monitor: &gdk::Monitor, connector: String) -> Rc<Dock> {
let cfg = ctx.cfg.borrow();
let cfg = ctx.cfg.borrow().for_output(&connector);
let win = gtk::ApplicationWindow::new(&ctx.app);
win.add_css_class("waydock");
win.add_css_class(&crate::style::output_class(&connector));
let orient = orientation(&cfg.position);
let panel = gtk::Box::new(orient, 0);
@@ -199,17 +199,46 @@ fn build_dock(ctx: &Rc<Ctx>, monitor: &gdk::Monitor, connector: String) -> Rc<Do
}
panel.append(&items_box);
// yttre marginal = plats för skuggan att rita
// yttre marginal = plats för skuggan att rita — men INTE på sidor som
// är förankrade mot skärmkanten, så att margin = 0 verkligen når kanten
let outer = gtk::Box::new(orient, 0);
let pad = if cfg.appearance.shadow.enabled {
cfg.appearance.shadow.blur as i32
} else {
0
};
outer.set_margin_top(pad);
outer.set_margin_bottom(pad);
outer.set_margin_start(pad);
outer.set_margin_end(pad);
let anchored_start = cfg.expand || cfg.alignment == Alignment::Start;
let anchored_end = cfg.expand || cfg.alignment == Alignment::End;
let (top, bottom, start, end) = match cfg.position {
Position::Bottom => (
pad,
0,
if anchored_start { 0 } else { pad },
if anchored_end { 0 } else { pad },
),
Position::Top => (
0,
pad,
if anchored_start { 0 } else { pad },
if anchored_end { 0 } else { pad },
),
Position::Left => (
if anchored_start { 0 } else { pad },
if anchored_end { 0 } else { pad },
0,
pad,
),
Position::Right => (
if anchored_start { 0 } else { pad },
if anchored_end { 0 } else { pad },
pad,
0,
),
};
outer.set_margin_top(top);
outer.set_margin_bottom(bottom);
outer.set_margin_start(start);
outer.set_margin_end(end);
outer.append(&panel);
// revealern ger autohide-animationen
@@ -229,9 +258,8 @@ fn build_dock(ctx: &Rc<Ctx>, monitor: &gdk::Monitor, connector: String) -> Rc<Do
revealer.set_child(Some(&outer));
win.set_child(Some(&revealer));
let autohide_enabled = cfg.autohide.enabled;
drop(cfg);
configure_layer(&win, ctx, monitor);
configure_layer(&win, &cfg, monitor);
menu::setup_dock_actions(ctx, &win);
let dock = Rc::new(Dock {
@@ -289,7 +317,7 @@ fn build_dock(ctx: &Rc<Ctx>, monitor: &gdk::Monitor, connector: String) -> Rc<Do
/// 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 cfg = ctx.cfg.borrow().for_output(&dock.connector);
let sensor = gtk::ApplicationWindow::new(&ctx.app);
sensor.add_css_class("waydock-sensor");
sensor.init_layer_shell();
@@ -359,7 +387,7 @@ fn set_dock_hidden(dock: &Rc<Dock>, hidden: bool) {
}
fn should_hide(ctx: &Ctx, dock: &Dock) -> bool {
let cfg = ctx.cfg.borrow();
let cfg = ctx.cfg.borrow().for_output(&dock.connector);
if !cfg.autohide.enabled || dock.hover.get() {
return false;
}
@@ -383,7 +411,7 @@ pub fn update_autohide(ctx: &Rc<Ctx>) {
}
fn schedule_hide(ctx: &Rc<Ctx>, dock: &Rc<Dock>) {
let delay = ctx.cfg.borrow().autohide.hide_delay_ms;
let delay = ctx.cfg.borrow().for_output(&dock.connector).autohide.hide_delay_ms;
let generation = dock.hide_generation.get() + 1;
dock.hide_generation.set(generation);
let ctx = ctx.clone();
@@ -397,18 +425,24 @@ fn schedule_hide(ctx: &Rc<Ctx>, dock: &Rc<Dock>) {
/// 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;
}
// hämta bara geometri om någon dock alls kör dodge
let any_dodge = ctx.docks.borrow().iter().any(|d| {
let c = ctx.cfg.borrow().for_output(&d.connector);
c.autohide.enabled && c.autohide.mode == AutohideMode::Dodge
});
if !any_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 eff = ctx.cfg.borrow().for_output(&dock.connector);
if !eff.autohide.enabled || eff.autohide.mode != AutohideMode::Dodge {
dock.dodge_overlap.set(false);
continue;
}
let (position, margin) = (eff.position.clone(), eff.margin);
let Some(output) = outputs
.iter()
.find(|o| o["name"].as_str() == Some(dock.connector.as_str()))
@@ -542,7 +576,11 @@ fn special_button(icon_name: &str, tooltip: &str) -> gtk::Button {
}
fn add_special_buttons(ctx: &Rc<Ctx>, dock: &Rc<Dock>) {
let specials = ctx.cfg.borrow().special_buttons.clone();
let specials = ctx
.cfg
.borrow()
.for_output(&dock.connector)
.special_buttons;
if specials.overview && wayfire_ipc::available() {
let btn = special_button("view-grid-symbolic", "Översikt (scale)");
@@ -562,12 +600,11 @@ fn add_special_buttons(ctx: &Rc<Ctx>, dock: &Rc<Dock>) {
}
fn build_item_widget(ctx: &Rc<Ctx>, connector: &str, item: Rc<Item>) -> gtk::Button {
let cfg = ctx.cfg.borrow();
let cfg = ctx.cfg.borrow().for_output(connector);
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");
@@ -639,8 +676,8 @@ fn build_item_widget(ctx: &Rc<Ctx>, connector: &str, item: Rc<Item>) -> gtk::But
gesture.set_state(gtk::EventSequenceState::Claimed);
let live = live_item(&ctx, &conn2, &item);
match gesture.current_button() {
1 => primary_action(&ctx, &live, &btn2),
2 => middle_action(&ctx, &live),
1 => primary_action(&ctx, &conn2, &live, &btn2),
2 => middle_action(&ctx, &conn2, &live),
3 => menu::popup_item_menu(&ctx, &live, &btn2),
_ => {}
}
@@ -649,9 +686,7 @@ fn build_item_widget(ctx: &Rc<Ctx>, connector: &str, item: Rc<Item>) -> gtk::But
btn.add_controller(click);
// hover-popup för grupperade appar (om valt i configen)
if item.windows.len() > 1
&& ctx.cfg.borrow().behavior.group_popup == GroupPopup::Hover
{
if item.windows.len() > 1 && cfg.behavior.group_popup == GroupPopup::Hover {
let hover_ctl = gtk::EventControllerMotion::new();
let ctx2 = ctx.clone();
let item2 = item.clone();
@@ -682,7 +717,7 @@ fn build_item_widget(ctx: &Rc<Ctx>, connector: &str, item: Rc<Item>) -> gtk::But
let item = item.clone();
let conn2 = connector.to_string();
scroll.connect_scroll(move |_, _, dy| {
if ctx.cfg.borrow().behavior.scroll == ScrollAction::CycleWindows {
if ctx.cfg.borrow().for_output(&conn2).behavior.scroll == ScrollAction::CycleWindows {
let live = live_item(&ctx, &conn2, &item);
cycle_windows(&ctx, &live, dy > 0.0);
glib::Propagation::Stop
@@ -696,28 +731,25 @@ fn build_item_widget(ctx: &Rc<Ctx>, connector: &str, item: Rc<Item>) -> gtk::But
btn
}
fn primary_action(ctx: &Rc<Ctx>, item: &Rc<Item>, anchor: &gtk::Button) {
fn primary_action(ctx: &Rc<Ctx>, connector: &str, item: &Rc<Item>, anchor: &gtk::Button) {
let service = ctx.service.borrow();
let Some(service) = service.as_ref() else {
icons::launch(&item.meta);
return;
};
let behavior = ctx.cfg.borrow().for_output(connector).behavior;
match item.windows.len() {
0 => icons::launch(&item.meta),
1 => {
let w = &item.windows[0];
let click = ctx.cfg.borrow().behavior.click.clone();
if w.activated && !w.minimized && click == ClickAction::FocusOrMinimize {
if w.activated && !w.minimized && behavior.click == ClickAction::FocusOrMinimize {
service.set_minimized(w, true);
} else {
service.activate(w);
}
}
_ => {
let (click, popup) = {
let b = &ctx.cfg.borrow().behavior;
(b.click.clone(), b.group_popup.clone())
};
let (click, popup) = (behavior.click.clone(), behavior.group_popup.clone());
if click == ClickAction::Cycle || popup == GroupPopup::Hover {
cycle_windows(ctx, item, true);
} else {
@@ -727,8 +759,8 @@ fn primary_action(ctx: &Rc<Ctx>, item: &Rc<Item>, anchor: &gtk::Button) {
}
}
fn middle_action(ctx: &Rc<Ctx>, item: &Rc<Item>) {
let action = ctx.cfg.borrow().behavior.middle_click.clone();
fn middle_action(ctx: &Rc<Ctx>, connector: &str, item: &Rc<Item>) {
let action = ctx.cfg.borrow().for_output(connector).behavior.middle_click.clone();
match action {
MiddleClick::LaunchNew => icons::launch(&item.meta),
MiddleClick::Close => {