Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c6b69dcec2 | |||
| 5b5a802fda | |||
| 976d311f94 | |||
| bd3d62c11c |
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -1042,7 +1042,7 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "waydock"
|
||||
version = "0.1.1"
|
||||
version = "0.1.5"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"gtk4",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "waydock"
|
||||
version = "0.1.1"
|
||||
version = "0.1.5"
|
||||
edition = "2021"
|
||||
description = "Modern, resurssnål dock för Wayfire — transparent, JSON-konfigurerad, per-skärm"
|
||||
license = "MIT"
|
||||
|
||||
@@ -32,6 +32,12 @@ Samma dock, sex olika configar — bara JSON-filen ändrades mellan bilderna:
|
||||
(minimera/maximera/flytta till skärm/pinna/stäng)
|
||||
- **Per skärm**: vilken inställning som helst kan överridas per skärm —
|
||||
egna pins, position, färger, filter för bara-den-skärmens-fönster
|
||||
- **Reservera plats**: maximerade fönster stannar vid docklinjen i stället
|
||||
för att täcka dockan — finjustera avståndet med `reserve_margin`
|
||||
(0 = kant i kant, minus = ännu närmare). Fungerar ihop med autohide:
|
||||
|
||||

|
||||
|
||||
- **Autohide**: dodge (göm vid fönsteröverlapp) eller göm alltid, med
|
||||
sensor vid kanten och animation
|
||||
- **Hotspots**: hot corners/edges som kör kommandon eller Wayfire-IPC
|
||||
|
||||
BIN
assets/screenshots/reserve-demo.png
Normal file
BIN
assets/screenshots/reserve-demo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
@@ -64,6 +64,9 @@ gruppering, fönsterlistans öppning och hover-effekt), **menyinnehåll**,
|
||||
// skuggutrymme läggs aldrig på förankrade sidor,
|
||||
// så 0 = verkligen kant i kant
|
||||
"layer": "top", // top | overlay | bottom (always-on-top = overlay)
|
||||
"reserve_margin": 0, // extra px ovanpå reservationen: 0 = fönster ända
|
||||
// till panelkanten, minus = ännu närmare (in i
|
||||
// skuggan), plus = luft mellan panel och fönster
|
||||
"avoid_windows": false, // true = reservera plats: maximerade fönster
|
||||
// stannar vid dockan. Funkar IHOP med autohide —
|
||||
// då gömmer dodge bara för fönster du själv
|
||||
|
||||
@@ -13,9 +13,11 @@ command -v gtk4-launch >/dev/null 2>&1 || \
|
||||
|
||||
mkdir -p "$DEST"
|
||||
echo "Hämtar waydock (latest)…"
|
||||
curl -fSL -o "$DEST/waydock" \
|
||||
# temp + mv: atomiskt byte som funkar även när waydock kör (ETXTBSY)
|
||||
curl -fSL -o "$DEST/waydock.new" \
|
||||
"$BASE/releases/download/latest/waydock-latest-linux-x64"
|
||||
chmod +x "$DEST/waydock"
|
||||
chmod +x "$DEST/waydock.new"
|
||||
mv -f "$DEST/waydock.new" "$DEST/waydock"
|
||||
|
||||
# ikon + .desktop-fil
|
||||
mkdir -p "$HOME/.local/share/icons/hicolor/scalable/apps" "$HOME/.local/share/applications"
|
||||
|
||||
@@ -312,6 +312,10 @@ pub struct Config {
|
||||
pub expand: bool,
|
||||
/// avstånd från kanten baren ligger mot (px)
|
||||
pub margin: i32,
|
||||
/// extra marginal ovanpå det reserverade området (px). 0 = fönster
|
||||
/// maximeras ända till panelens kant; minus låter dem gå närmare
|
||||
/// (in i skuggan), plus ger luft mellan panel och fönster
|
||||
pub reserve_margin: i32,
|
||||
/// marginal i början av kanten: vänster (topp/botten) resp. topp (vänster/höger)
|
||||
pub margin_start: i32,
|
||||
/// marginal i slutet av kanten: höger resp. botten
|
||||
@@ -336,6 +340,7 @@ impl Default for Config {
|
||||
alignment: Alignment::Center,
|
||||
expand: false,
|
||||
margin: 8,
|
||||
reserve_margin: 0,
|
||||
margin_start: 0,
|
||||
margin_end: 0,
|
||||
layer: Layer::Top,
|
||||
|
||||
198
src/dock.rs
198
src/dock.rs
@@ -53,15 +53,21 @@ pub struct Ctx {
|
||||
|
||||
pub struct Dock {
|
||||
pub window: gtk::ApplicationWindow,
|
||||
pub panel: gtk::Box,
|
||||
pub items_box: gtk::Box,
|
||||
pub revealer: gtk::Revealer,
|
||||
pub connector: String,
|
||||
/// pekaren är över dockan just nu
|
||||
pub hover: Cell<bool>,
|
||||
/// pekaren är på väcknings-sensorn vid kanten
|
||||
pub sensor_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>,
|
||||
/// senaste synliga storlek längs huvudaxeln (px) — används för
|
||||
/// exclusive zone och dodge-djup även när docken är gömd
|
||||
pub main_size: Cell<i32>,
|
||||
/// 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
|
||||
@@ -113,14 +119,17 @@ fn configure_layer(win: >k::ApplicationWindow, cfg: &Config, monitor: &gdk::Mo
|
||||
let (side_start, side_end) = side_edges(&cfg.position);
|
||||
win.set_anchor(edge, true);
|
||||
win.set_margin(edge, cfg.margin);
|
||||
if cfg.expand {
|
||||
// täck hela kanten; start/slut-marginalerna drar in baren
|
||||
// exclusive zone honoreras bara för ytor som är förankrade i EN kant
|
||||
// eller spänner kanten (spec + Wayfires tolkning) — så med reservera
|
||||
// plats spänner ytan alltid kanten; ön ritas ändå som en ö via
|
||||
// panelens alignment (flankerna är transparenta)
|
||||
if cfg.expand || cfg.avoid_windows {
|
||||
win.set_anchor(side_start, true);
|
||||
win.set_anchor(side_end, true);
|
||||
win.set_margin(side_start, cfg.margin_start);
|
||||
win.set_margin(side_end, cfg.margin_end);
|
||||
win.set_margin(side_start, cfg.margin_start.max(0));
|
||||
win.set_margin(side_end, cfg.margin_end.max(0));
|
||||
} else {
|
||||
// ö: förankra bara i vald ände (ingen sidoförankring = centrerad)
|
||||
// ö utan reservation: förankra bara i vald ände
|
||||
match cfg.alignment {
|
||||
Alignment::Center => {}
|
||||
Alignment::Start => {
|
||||
@@ -133,13 +142,38 @@ fn configure_layer(win: >k::ApplicationWindow, cfg: &Config, monitor: &gdk::Mo
|
||||
}
|
||||
}
|
||||
}
|
||||
// reservera plats fungerar ihop med autohide: maximerade fönster
|
||||
// stannar vid dockan (och triggar då aldrig dodge) — bara fönster
|
||||
// man själv drar in i zonen gömmer den
|
||||
if cfg.avoid_windows {
|
||||
win.auto_exclusive_zone_enable();
|
||||
// exclusive zone sätts explicit efter map (auto-zonen i
|
||||
// gtk4-layer-shell fungerar bara för ytor som spänner hela kanten)
|
||||
win.set_exclusive_zone(0);
|
||||
}
|
||||
|
||||
/// Reservera plats: zon = dockans uppmätta storlek + marginal. Zonen
|
||||
/// ligger kvar även när docken är dodge-gömd (fönstret hålls mappat),
|
||||
/// så maximerade fönster alltid stannar vid docklinjen.
|
||||
pub fn update_exclusive_zone(ctx: &Rc<Ctx>, dock: &Rc<Dock>) {
|
||||
let cfg = ctx.cfg.borrow().for_output(&dock.connector);
|
||||
if !cfg.avoid_windows {
|
||||
if !dock.hidden.get() {
|
||||
dock.window.set_exclusive_zone(0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
let horizontal = matches!(cfg.position, Position::Bottom | Position::Top);
|
||||
// mät den SYNLIGA panelen — fönstret innehåller även skuggutrymmet,
|
||||
// som inte ska räknas in i reservationen
|
||||
let size = if horizontal {
|
||||
dock.panel.height()
|
||||
} else {
|
||||
win.set_exclusive_zone(0);
|
||||
dock.panel.width()
|
||||
};
|
||||
if size > 1 && !dock.hidden.get() {
|
||||
dock.main_size.set(size);
|
||||
}
|
||||
if dock.main_size.get() > 1 {
|
||||
let zone =
|
||||
(dock.main_size.get() + cfg.margin.max(0) + cfg.reserve_margin).max(1);
|
||||
debug!("{}: exclusive zone {}px", dock.connector, zone);
|
||||
dock.window.set_exclusive_zone(zone);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,9 +238,17 @@ fn build_dock(ctx: &Rc<Ctx>, monitor: &gdk::Monitor, connector: String) -> Rc<Do
|
||||
items_box.set_vexpand(true);
|
||||
items_box.set_valign(items_align);
|
||||
}
|
||||
} else {
|
||||
panel.set_halign(gtk::Align::Center);
|
||||
} else if orient == gtk::Orientation::Horizontal {
|
||||
// ö: när ytan spänner kanten (reservera plats) placerar
|
||||
// panel-alignmenten ön; expand krävs för att alignment ska
|
||||
// verka i hela ytans bredd (annars packas panelen först)
|
||||
panel.set_hexpand(true);
|
||||
panel.set_halign(items_align);
|
||||
panel.set_valign(gtk::Align::Center);
|
||||
} else {
|
||||
panel.set_vexpand(true);
|
||||
panel.set_halign(gtk::Align::Center);
|
||||
panel.set_valign(items_align);
|
||||
}
|
||||
panel.append(&items_box);
|
||||
|
||||
@@ -275,12 +317,15 @@ fn build_dock(ctx: &Rc<Ctx>, monitor: &gdk::Monitor, connector: String) -> Rc<Do
|
||||
|
||||
let dock = Rc::new(Dock {
|
||||
window: win.clone(),
|
||||
panel: panel.clone(),
|
||||
items_box: items_box.clone(),
|
||||
revealer,
|
||||
connector,
|
||||
hover: Cell::new(false),
|
||||
sensor_hover: Cell::new(false),
|
||||
hidden: Cell::new(false),
|
||||
dodge_overlap: Cell::new(false),
|
||||
main_size: Cell::new(0),
|
||||
hide_generation: Cell::new(0),
|
||||
sensor: RefCell::new(None),
|
||||
});
|
||||
@@ -290,13 +335,13 @@ fn build_dock(ctx: &Rc<Ctx>, monitor: &gdk::Monitor, connector: String) -> Rc<Do
|
||||
empty_click.set_button(3);
|
||||
{
|
||||
let ctx = ctx.clone();
|
||||
let panel2 = panel.clone();
|
||||
let outer2 = outer.clone();
|
||||
empty_click.connect_pressed(move |gesture, _, x, y| {
|
||||
gesture.set_state(gtk::EventSequenceState::Claimed);
|
||||
menu::popup_panel_menu(&ctx, &panel2, x, y);
|
||||
menu::popup_panel_menu(&ctx, &outer2, x, y);
|
||||
});
|
||||
}
|
||||
panel.add_controller(empty_click);
|
||||
outer.add_controller(empty_click);
|
||||
|
||||
// hover-spårning för autohide
|
||||
let motion = gtk::EventControllerMotion::new();
|
||||
@@ -358,39 +403,63 @@ fn build_sensor(ctx: &Rc<Ctx>, dock: &Rc<Dock>, monitor: &gdk::Monitor) {
|
||||
let ctx = ctx.clone();
|
||||
let dock2 = dock.clone();
|
||||
motion.connect_enter(move |_, _, _| {
|
||||
dock2.sensor_hover.set(true);
|
||||
dock2.hide_generation.set(dock2.hide_generation.get() + 1);
|
||||
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);
|
||||
}
|
||||
});
|
||||
set_dock_hidden(&ctx, &dock2, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
{
|
||||
let ctx = ctx.clone();
|
||||
let dock2 = dock.clone();
|
||||
motion.connect_leave(move |_| {
|
||||
dock2.sensor_hover.set(false);
|
||||
// göm-fördröjningen börjar först NÄR man lämnar kantområdet
|
||||
schedule_hide(&ctx, &dock2);
|
||||
});
|
||||
}
|
||||
strip.add_controller(motion);
|
||||
|
||||
sensor.present();
|
||||
*dock.sensor.borrow_mut() = Some(sensor);
|
||||
}
|
||||
|
||||
fn set_dock_hidden(dock: &Rc<Dock>, hidden: bool) {
|
||||
fn set_dock_hidden(ctx: &Ctx, dock: &Rc<Dock>, hidden: bool) {
|
||||
if dock.hidden.get() == hidden {
|
||||
return;
|
||||
}
|
||||
let cfg = ctx.cfg.borrow().for_output(&dock.connector);
|
||||
let horizontal = matches!(cfg.position, Position::Bottom | Position::Top);
|
||||
// kom ihåg synlig storlek innan vi gömmer
|
||||
if hidden {
|
||||
let size = if horizontal { dock.panel.height() } else { dock.panel.width() };
|
||||
if size > 1 {
|
||||
dock.main_size.set(size);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
});
|
||||
if cfg.avoid_windows {
|
||||
// reservera plats även gömd: håll fönstret mappat (kollapsat)
|
||||
// med frusen zon — maximerade fönster stannar vid docklinjen
|
||||
dock.window.set_exclusive_zone(
|
||||
(dock.main_size.get().max(1) + cfg.margin.max(0) + cfg.reserve_margin)
|
||||
.max(1),
|
||||
);
|
||||
} else {
|
||||
let duration = dock.revealer.transition_duration();
|
||||
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);
|
||||
@@ -399,7 +468,13 @@ fn set_dock_hidden(dock: &Rc<Dock>, hidden: bool) {
|
||||
|
||||
fn should_hide(ctx: &Ctx, dock: &Dock) -> bool {
|
||||
let cfg = ctx.cfg.borrow().for_output(&dock.connector);
|
||||
if !cfg.autohide.enabled || dock.hover.get() {
|
||||
// en öppen meny/fönsterlista håller dockan uppe — pekaren i en popup
|
||||
// (egen yta) räknas annars som "lämnat dockan"
|
||||
if !cfg.autohide.enabled
|
||||
|| dock.hover.get()
|
||||
|| dock.sensor_hover.get()
|
||||
|| ctx.open_popovers.get() > 0
|
||||
{
|
||||
return false;
|
||||
}
|
||||
match cfg.autohide.mode {
|
||||
@@ -417,7 +492,14 @@ fn should_hide(ctx: &Ctx, dock: &Dock) -> bool {
|
||||
/// 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));
|
||||
set_dock_hidden(ctx, dock, should_hide(ctx, dock));
|
||||
}
|
||||
}
|
||||
|
||||
/// Starta göm-fördröjningen för alla dockor (körs när sista menyn stängts).
|
||||
pub fn schedule_hide_all(ctx: &Rc<Ctx>) {
|
||||
for dock in ctx.docks.borrow().iter() {
|
||||
schedule_hide(ctx, dock);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,7 +511,7 @@ fn schedule_hide(ctx: &Rc<Ctx>, dock: &Rc<Dock>) {
|
||||
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);
|
||||
set_dock_hidden(&ctx, &dock, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -462,11 +544,19 @@ pub fn recompute_dodge(ctx: &Rc<Ctx>) {
|
||||
};
|
||||
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;
|
||||
// zonen dockan upptar vid sin kant: senaste SYNLIGA storlek —
|
||||
// den kollapsade storleken skulle få hidden/synlig att flappa
|
||||
if !dock.hidden.get() {
|
||||
let current = match position {
|
||||
Position::Bottom | Position::Top => dock.panel.height(),
|
||||
_ => dock.panel.width(),
|
||||
};
|
||||
if current > 1 {
|
||||
dock.main_size.set(current);
|
||||
}
|
||||
}
|
||||
let extra = if eff.avoid_windows { eff.reserve_margin } else { 0 };
|
||||
let depth = (dock.main_size.get().max(24) + margin + extra).max(1) as f64;
|
||||
|
||||
let overlap = views.iter().any(|v| {
|
||||
if v["role"].as_str() != Some("toplevel")
|
||||
@@ -486,11 +576,14 @@ pub fn recompute_dodge(ctx: &Rc<Ctx>) {
|
||||
if x + w <= 0.0 || y + h <= 0.0 || x >= ow || y >= oh {
|
||||
return false;
|
||||
}
|
||||
// fönstret måste tränga IN i zonen — ett maximerat fönster
|
||||
// som slutar exakt på docklinjen (reserverad plats) räknas
|
||||
// inte som överlapp (0.5 px tolerans för avrundning)
|
||||
match position {
|
||||
Position::Bottom => y + h >= oh - depth,
|
||||
Position::Top => y <= depth,
|
||||
Position::Left => x <= depth,
|
||||
Position::Right => x + w >= ow - depth,
|
||||
Position::Bottom => y + h > oh - depth + 0.5,
|
||||
Position::Top => y < depth - 0.5,
|
||||
Position::Left => x < depth - 0.5,
|
||||
Position::Right => x + w > ow - depth + 0.5,
|
||||
}
|
||||
});
|
||||
dock.dodge_overlap.set(overlap);
|
||||
@@ -579,6 +672,21 @@ pub fn refresh_items(ctx: &Rc<Ctx>) {
|
||||
let widget = build_item_widget(ctx, &dock.connector, Rc::new(item));
|
||||
dock.items_box.append(&widget);
|
||||
}
|
||||
let ctx2 = ctx.clone();
|
||||
let dock2 = dock.clone();
|
||||
// första configure-rundan är asynkron — mät tills storleken finns
|
||||
let mut attempts = 0;
|
||||
glib::timeout_add_local(Duration::from_millis(120), move |
|
||||
| {
|
||||
attempts += 1;
|
||||
update_exclusive_zone(&ctx2, &dock2);
|
||||
let done = dock2.main_size.get() > 1 || attempts > 20;
|
||||
if done {
|
||||
glib::ControlFlow::Break
|
||||
} else {
|
||||
glib::ControlFlow::Continue
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -198,8 +198,12 @@ fn register_popover(ctx: &Rc<Ctx>, popover: &impl IsA<gtk::Popover>) {
|
||||
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);
|
||||
if ctx2.open_popovers.get() == 0 {
|
||||
if ctx2.refresh_pending.get() {
|
||||
crate::dock::refresh_items(&ctx2);
|
||||
}
|
||||
// sista menyn stängd: autohide får ta vid igen (med delay)
|
||||
crate::dock::schedule_hide_all(&ctx2);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -468,6 +468,7 @@ fn build_layout_page(ctx: &Rc<Ctx>) -> gtk::ScrolledWindow {
|
||||
};
|
||||
}));
|
||||
row(&list, "Reservera plats (maximerade fönster stannar vid dockan)", &switch(ctx, cfg.avoid_windows, |c, v| c.avoid_windows = v));
|
||||
row(&list, "Extra reservationsmarginal (px)", &spin(ctx, cfg.reserve_margin as f64, (-64.0, 128.0), 1.0, |c, v| c.reserve_margin = v as i32));
|
||||
|
||||
heading(&list, "Marginaler");
|
||||
list.append(&margin_illustration(ctx));
|
||||
|
||||
Reference in New Issue
Block a user