waydock v0.1: panel, pins, fönstergrupper, menyer, settings-GUI, live-config
Some checks failed
check / check (push) Failing after 52s

M0–M5 ur doc/plan.md: layer-shell-dock per skärm med transparent
#222222-ö, pins + körande appar (gruppering, badges, indikatorer),
klick/skroll/mittklick, konfigurerbar högerklicksmeny, tomyta-meny,
settings-GUI som redigerar JSON-configen live, filewatcher,
Wayfire-IPC-stubbar (scale, flytta-till-skärm), CI + release-flöde.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-29 23:24:29 +02:00
commit bc2fa65558
21 changed files with 4579 additions and 0 deletions

232
src/main.rs Normal file
View File

@@ -0,0 +1,232 @@
// waydock — modern, resurssnål dock för Wayfire.
// Se doc/architecture.md för helheten.
mod config;
mod dock;
mod icons;
mod menu;
mod settings;
mod style;
mod toplevel;
mod wayfire_ipc;
use std::cell::{Cell, RefCell};
use std::path::PathBuf;
use std::rc::Rc;
use std::time::Duration;
use gtk4 as gtk;
use gtk4::gdk;
use gtk4::glib;
use gtk4::prelude::*;
use log::{error, info, warn, LevelFilter};
const VERSION: &str = env!("CARGO_PKG_VERSION");
struct Cli {
config: Option<PathBuf>,
log: Option<String>,
log_file: Option<String>,
}
fn parse_cli() -> Cli {
let mut cli = Cli { config: None, log: None, log_file: None };
let mut args = std::env::args().skip(1);
while let Some(arg) = args.next() {
match arg.as_str() {
"--config" => cli.config = args.next().map(PathBuf::from),
"--log" => cli.log = args.next(),
"--log-file" => cli.log_file = args.next(),
"--version" | "-V" => {
println!("waydock {VERSION}");
std::process::exit(0);
}
"--help" | "-h" => {
println!(
"waydock {VERSION} — dock för Wayfire\n\n\
FLAGGOR:\n \
--config <fil> annan configfil (default ~/.config/waydock/config.json)\n \
--log <nivå> error|warn|info|debug|trace (även env WAYDOCK_LOG)\n \
--log-file <fil> skriv logg till fil\n \
--version visa version"
);
std::process::exit(0);
}
other => {
eprintln!("okänd flagga: {other} (se --help)");
std::process::exit(2);
}
}
}
cli
}
fn init_logging(cli: &Cli, cfg: &config::Config) {
let level_str = cli
.log
.clone()
.or_else(|| std::env::var("WAYDOCK_LOG").ok())
.unwrap_or_else(|| cfg.log.level.clone());
let level = match level_str.to_lowercase().as_str() {
"error" => LevelFilter::Error,
"warn" => LevelFilter::Warn,
"debug" => LevelFilter::Debug,
"trace" => LevelFilter::Trace,
_ => LevelFilter::Info,
};
let log_cfg = simplelog::ConfigBuilder::new()
.set_time_format_rfc3339()
.build();
let mut loggers: Vec<Box<dyn simplelog::SharedLogger>> = vec![simplelog::TermLogger::new(
level,
log_cfg.clone(),
simplelog::TerminalMode::Stderr,
simplelog::ColorChoice::Auto,
)];
let file_path = cli.log_file.clone().or_else(|| cfg.log.file.clone());
if let Some(path) = file_path {
let expanded = if let Some(rest) = path.strip_prefix("~/") {
glib::home_dir().join(rest)
} else {
PathBuf::from(&path)
};
match std::fs::File::create(&expanded) {
Ok(f) => loggers.push(simplelog::WriteLogger::new(level, log_cfg, f)),
Err(e) => eprintln!("kunde inte öppna loggfil {path}: {e}"),
}
}
let _ = simplelog::CombinedLogger::init(loggers);
}
/// Delar av configen som kräver att fönstren byggs om (inte bara CSS).
fn layout_signature(cfg: &config::Config) -> String {
format!(
"{:?}|{}|{}|{:?}|{}|{}|{}|{:?}",
cfg.position,
cfg.centered,
cfg.margin,
cfg.layer,
cfg.avoid_windows,
cfg.appearance.shadow.enabled,
cfg.appearance.shadow.blur,
cfg.outputs.keys().collect::<Vec<_>>(),
)
}
fn main() -> glib::ExitCode {
let cli = parse_cli();
let cfg_path = cli.config.clone().unwrap_or_else(config::default_path);
let initial_cfg = config::load_or_create(&cfg_path);
init_logging(&cli, &initial_cfg);
info!("waydock {VERSION} — config: {}", cfg_path.display());
let app = gtk::Application::new(Some("eu.brassepc.waydock"), Default::default());
let started = Rc::new(Cell::new(false));
let cfg_path2 = cfg_path.clone();
app.connect_activate(move |app| {
if started.get() {
info!("waydock kör redan — ignorerar andra instansen");
return;
}
started.set(true);
activate(app, cfg_path2.clone());
});
// ge inte GTK våra egna flaggor
app.run_with_args::<&str>(&[])
}
fn activate(app: &gtk::Application, cfg_path: PathBuf) {
let cfg: config::SharedConfig = Rc::new(RefCell::new(config::load_or_create(&cfg_path)));
let ctx = Rc::new(dock::Ctx {
cfg: cfg.clone(),
cfg_path: cfg_path.clone(),
service: RefCell::new(None),
docks: RefCell::new(Vec::new()),
menu_target: RefCell::new(None),
app: app.clone(),
on_config_changed: RefCell::new(None),
save_generation: Cell::new(0),
});
// CSS + rebuild/refresh-logik
let css_provider: Rc<RefCell<Option<gtk::CssProvider>>> = Rc::new(RefCell::new(None));
let last_signature = Rc::new(RefCell::new(String::new()));
{
let ctx2 = ctx.clone();
let css_provider = css_provider.clone();
let last_signature = last_signature.clone();
let apply: Rc<dyn Fn()> = Rc::new(move || {
let cfg = ctx2.cfg.borrow();
let provider = style::apply(&cfg, css_provider.borrow().as_ref());
*css_provider.borrow_mut() = Some(provider);
let sig = layout_signature(&cfg);
drop(cfg);
if *last_signature.borrow() != sig {
*last_signature.borrow_mut() = sig;
dock::rebuild_all(&ctx2);
} else {
dock::refresh_items(&ctx2);
}
});
*ctx.on_config_changed.borrow_mut() = Some(apply);
}
// fönsterspårningen
{
let ctx2 = ctx.clone();
let on_update: Rc<dyn Fn()> = Rc::new(move || dock::refresh_items(&ctx2));
match toplevel::start(on_update) {
Ok(service) => *ctx.service.borrow_mut() = Some(service),
Err(e) => {
error!("{e} — docken visar bara pins");
}
}
}
if !wayfire_ipc::available() {
warn!("WAYFIRE_SOCKET saknas — översikt/flytta-till-skärm/dodge avstängda (aktivera ipc-pluginen i wayfire.ini)");
}
// första uppritningen
if let Some(cb) = ctx.on_config_changed.borrow().as_ref() {
cb();
}
// live-omladdning när configfilen ändras
{
let ctx2 = ctx.clone();
config::watch(
&cfg_path,
Rc::new(move || {
info!("configfilen ändrad — laddar om");
let fresh = config::load_or_create(&ctx2.cfg_path);
*ctx2.cfg.borrow_mut() = fresh;
if let Some(cb) = ctx2.on_config_changed.borrow().as_ref() {
cb();
}
}),
);
}
// skärmar in/ut → bygg om (debouncat, skärmar kommer i klungor)
{
let ctx2 = ctx.clone();
let pending = Rc::new(Cell::new(0u64));
let display = gdk::Display::default().expect("ingen display");
display.monitors().connect_items_changed(move |_, _, _, _| {
let generation = pending.get() + 1;
pending.set(generation);
let ctx3 = ctx2.clone();
let pending2 = pending.clone();
glib::timeout_add_local_once(Duration::from_millis(500), move || {
if pending2.get() == generation {
info!("skärmuppsättningen ändrad — bygger om dockorna");
dock::rebuild_all(&ctx3);
}
});
});
}
}