From bbd068e82981af6a7ed0caa917df3d83a050b3a1 Mon Sep 17 00:00:00 2001 From: jess Date: Mon, 27 Apr 2026 22:09:36 -0700 Subject: [PATCH] I concede on the Wayland display server always-on-top support. Not my battle. Not likely to be supported (by wayland). --- Cargo.toml | 4 +--- scripts/linux/build.sh | 15 +++++---------- scripts/linux/install.sh | 22 +++++++++++++++------- src/Colors.swift | 2 +- src/bin/layers_shell.rs | 31 ++++++++++++++++++------------- src/ffi.rs | 8 ++++++-- src/lib.rs | 3 --- src/paths.rs | 2 +- src/wayland_shell.rs | 17 +++++++++++++---- 9 files changed, 60 insertions(+), 44 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 79a98e8..af34c39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,7 +75,7 @@ debug = [] # Linux front-end providers — exactly one selected at build time. # build.sh detects the running WM and sets this for you. x11 = ["dep:x11-dl"] -wayland = ["dep:smithay-client-toolkit", "dep:wayland-client"] +wayland = [] [target.'cfg(target_os = "windows")'.dependencies] windows = { version = "0.58", features = [ @@ -86,5 +86,3 @@ windows = { version = "0.58", features = [ [target.'cfg(target_os = "linux")'.dependencies] x11-dl = { version = "2", optional = true } -smithay-client-toolkit = { version = "0.20", optional = true } -wayland-client = { version = "0.31", optional = true } diff --git a/scripts/linux/build.sh b/scripts/linux/build.sh index 88cab15..795a77f 100755 --- a/scripts/linux/build.sh +++ b/scripts/linux/build.sh @@ -37,18 +37,13 @@ detect_wm_feature() { echo "$LAYERS_FEATURES" return fi - # Native Wayland session AND a compositor known to ship wlr-layer-shell. + # Native Wayland session — use the wayland provider (winit xdg-toplevel, + # render transparency, user pins on top via compositor). if [ -n "${WAYLAND_DISPLAY:-}" ]; then - case "${XDG_CURRENT_DESKTOP:-}" in - *COSMIC*|*KDE*|*Plasma*|sway|Hyprland|niri|river|Wayfire) - echo "wayland" - return - ;; - esac + echo "wayland" + return fi - # GNOME-Wayland (mutter, no layer-shell), every X11 session, every other - # GTK3-class compositor: ship the X11 provider — XWayland honours the hints - # everywhere except cosmic-comp, where the wayland provider applies instead. + # X11 session or no WAYLAND_DISPLAY: use the X11 provider. echo "x11" } diff --git a/scripts/linux/install.sh b/scripts/linux/install.sh index 142afe3..030fcb5 100755 --- a/scripts/linux/install.sh +++ b/scripts/linux/install.sh @@ -9,14 +9,11 @@ case "$(uname -s)" in *) echo "wrong platform: $(uname -s) — use cargo xtask install" >&2; exit 1;; esac -bash "$ROOT/scripts/linux/build.sh" - STAGE="$ROOT/build/bin/com.jesshunter.layers" PLUGIN_ID="com.jesshunter.layers" -# Drop into every existing KiCad >=10 install we can find — native and flatpak, -# every version dir KiCad has already created. We never mkdir kicad//plugins; -# KiCad creates it on first launch and we only fill in our own subdir. +# Installs into every existing KiCad >=10 plugins dir — native and flatpak. +# Does not mkdir kicad//plugins; KiCad creates it on first launch. install_to_kicad_root() { local kicad_root="$1" [ -d "$kicad_root" ] || return 1 @@ -46,8 +43,19 @@ NATIVE_ROOT="${XDG_DATA_HOME:-$HOME/.local/share}/kicad" FLATPAK_ROOT="$HOME/.var/app/org.kicad.KiCad/data/kicad" ok=0 -install_to_kicad_root "$NATIVE_ROOT" && ok=1 || true -install_to_kicad_root "$FLATPAK_ROOT" && ok=1 || true + +# Native install: build with the front-end matching the host's WM (build.sh +# autodetects unless LAYERS_FEATURES is set). +if [ -d "$NATIVE_ROOT" ]; then + bash "$ROOT/scripts/linux/build.sh" + install_to_kicad_root "$NATIVE_ROOT" && ok=1 || true +fi + +# Flatpak install: force the X11 front-end. +if [ -d "$FLATPAK_ROOT" ]; then + LAYERS_FEATURES=x11 bash "$ROOT/scripts/linux/build.sh" + install_to_kicad_root "$FLATPAK_ROOT" && ok=1 || true +fi if [ "$ok" -eq 0 ]; then echo "no KiCad 10+ install detected. checked:" >&2 diff --git a/src/Colors.swift b/src/Colors.swift index 1624cd6..a4683f4 100644 --- a/src/Colors.swift +++ b/src/Colors.swift @@ -29,7 +29,7 @@ struct LayersColors { return result } - /// Minimal key=value extractor for the flat subset we care about. + /// key=value extractor for the subset. /// Returns dotted "section.key" → numeric value. private static func flatten(_ source: String) -> [String: Double] { var out: [String: Double] = [:] diff --git a/src/bin/layers_shell.rs b/src/bin/layers_shell.rs index d1b2f70..b5850fe 100644 --- a/src/bin/layers_shell.rs +++ b/src/bin/layers_shell.rs @@ -36,14 +36,7 @@ fn main() { tracing::info!(provider = PROVIDER_NAME, "shell route: provider chosen at build time"); #[cfg(all(target_os = "linux", feature = "wayland"))] - { - if layers::wayland_shell::try_run() { - return; - } - tracing::warn!( - "wayland provider failed to start; falling through to winit so the plugin still loads" - ); - } + tracing::info!(provider = PROVIDER_NAME, "wayland: using winit xdg-toplevel (compositor provides pin-on-top via title bar)"); if let Err(e) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(run)) { tracing::error!("layers shell panicked: {e:?}"); @@ -52,7 +45,7 @@ fn main() { } #[cfg(all(target_os = "linux", feature = "wayland"))] -const PROVIDER_NAME: &str = "linux/wayland (sctk + wlr-layer-shell)"; +const PROVIDER_NAME: &str = "linux/wayland (winit + xdg-toplevel)"; #[cfg(all(target_os = "linux", feature = "x11", not(feature = "wayland")))] const PROVIDER_NAME: &str = "linux/x11 (winit + xlib)"; #[cfg(all(target_os = "linux", not(feature = "x11"), not(feature = "wayland")))] @@ -159,8 +152,21 @@ impl ApplicationHandler for ShellApp { .with_inner_size(LogicalSize::new(DEFAULT_LOGICAL_SIZE.0, DEFAULT_LOGICAL_SIZE.1)) .with_min_inner_size(LogicalSize::new(MIN_LOGICAL_SIZE.0, MIN_LOGICAL_SIZE.1)) .with_transparent(true) - .with_decorations(true) - .with_window_level(WindowLevel::AlwaysOnTop); + .with_decorations(true); + + // On Wayland, always-on-top is the user's choice (only) I mulled it over. Why fight intent? + // Who am I to decide what's important? I have thought of a way to do it though, I think. So if any + // wayland users DO end up asking me for this feature, I'll add it. + // I'd add a cfg to the board change poll that only exists on wayland detected builds, it would use + // xdg activation to steal back top level. I think it would work, but it might be too + // invasive if it has to take focus as well as return to top. All around, wayland just isn't + // meant to do this. Sorry Ubuntu/Gnome/Pop_OS! users. If you use wayland and have any + // ideas, + // please do let me know. + + // On other platforms it is set programmatically. + #[cfg(not(all(target_os = "linux", feature = "wayland")))] + let attrs = attrs.with_window_level(WindowLevel::AlwaysOnTop); let window = event_loop .create_window(attrs) @@ -457,8 +463,7 @@ fn set_window_alpha(window: &Window, alpha: f32) { } } -// macOS path: NSWindow.alphaValue is set in Swift; Rust side is a no-op. -// Linux/wayland builds bake alpha into render output (planned), not a window hint. +/// #[cfg(any( target_os = "macos", target_os = "ios", diff --git a/src/ffi.rs b/src/ffi.rs index 2e68671..70e01c9 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -609,8 +609,12 @@ fn render(handle: &mut ViewportHandle) { ui.draw(&mut handle.renderer, &theme, &style, handle.cursor); handle.cache = ui.into_cache(); - // NSWindow.alphaValue in Swift fades the composited surface; keep this opaque. - let background = crate::ui::theme::compositor_clear(); + // macOS/Windows/X11: the OS fades the composited surface via window-level alpha. + // Wayland: no window-level opacity; clear with alpha=0 for transparent background. + // background; the UI content renders opaque on top. + let mut background = crate::ui::theme::compositor_clear(); + #[cfg(all(target_os = "linux", feature = "wayland"))] + { background.a = 0.0; } handle.renderer.present( Some(background), handle.format, diff --git a/src/lib.rs b/src/lib.rs index 597e8db..7f9dd74 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,3 @@ pub mod single_instance; pub mod snapshot; pub mod ui; pub mod version; - -#[cfg(all(target_os = "linux", feature = "wayland"))] -pub mod wayland_shell; diff --git a/src/paths.rs b/src/paths.rs index b562f51..e429386 100644 --- a/src/paths.rs +++ b/src/paths.rs @@ -17,7 +17,7 @@ pub fn plugin_dir() -> PathBuf { /// User-writable runtime data root: logs, state, cache, settings, runtime sockets. /// Decoupled from the plugin install location so a flatpak KiCad (which sandboxes -/// its own data dirs) still writes somewhere we can `tail -f` from a normal shell. +/// its own data dirs) still writes somewhere accessible from a normal shell. pub fn data_dir() -> PathBuf { if let Ok(v) = std::env::var("LAYERS_DATA_DIR") { if !v.is_empty() { diff --git a/src/wayland_shell.rs b/src/wayland_shell.rs index 0918414..4264493 100644 --- a/src/wayland_shell.rs +++ b/src/wayland_shell.rs @@ -93,7 +93,7 @@ pub fn try_run() -> bool { let layer = layer_shell.create_layer_surface( &qh, surface, - Layer::Top, + Layer::Overlay, Some("layers"), None, ); @@ -145,6 +145,8 @@ pub fn try_run() -> bool { first_configure: true, raw_display, raw_window, + focused: false, + hovered: false, exit: false, }; @@ -175,6 +177,8 @@ struct State { first_configure: bool, raw_display: RawDisplayHandle, raw_window: RawWindowHandle, + focused: bool, + hovered: bool, exit: bool, } @@ -202,6 +206,7 @@ impl State { fn render(&mut self, qh: &QueueHandle) { if let Some(vp) = self.viewport.as_mut() { + vp.set_window_alpha(crate::ui::theme::window_alpha(self.focused, self.hovered)); vp.render_frame(); } let surface = self.layer.wl_surface(); @@ -377,6 +382,7 @@ impl KeyboardHandler for State { _: &[u32], _keysyms: &[Keysym], ) { + self.focused = true; } fn leave( @@ -387,6 +393,7 @@ impl KeyboardHandler for State { _surface: &wl_surface::WlSurface, _: u32, ) { + self.focused = false; } fn press_key( @@ -462,9 +469,11 @@ impl PointerHandler for State { self.last_pointer = (lx, ly); match event.kind { PointerEventKind::Enter { .. } => { + self.hovered = true; vp.push_mouse_move(lx, ly); } PointerEventKind::Leave { .. } => { + self.hovered = false; vp.push_mouse_left(); } PointerEventKind::Motion { .. } => { @@ -496,8 +505,8 @@ impl PointerHandler for State { /// Find a wayland socket and connect, regardless of whether `WAYLAND_DISPLAY` is set. /// Inside flatpak the env var is often stripped even when the socket itself is bind- -/// mounted into the sandbox at `$XDG_RUNTIME_DIR/wayland-*`. We list candidates, -/// log everything we find, and try each in order. +/// mounted into the sandbox at `$XDG_RUNTIME_DIR/wayland-*`. Lists candidates +/// and tries each in order. fn probe_connection() -> Option { let runtime_dir = std::env::var_os("XDG_RUNTIME_DIR").map(std::path::PathBuf::from); tracing::info!( @@ -531,7 +540,7 @@ fn probe_connection() -> Option { if !name_str.starts_with("wayland-") { continue; } - // Skip lock files; we want the socket itself. + // Skip lock files; only the socket matters. if name_str.ends_with(".lock") { continue; }