I concede on the Wayland display server always-on-top support. Not my battle. Not likely to be supported (by wayland).

This commit is contained in:
jess 2026-04-27 22:09:36 -07:00
parent 06c7ec0f4d
commit bbd068e829
9 changed files with 60 additions and 44 deletions

View File

@ -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 }

View File

@ -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
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"
}

View File

@ -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/<ver>/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/<ver>/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

View File

@ -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] = [:]

View File

@ -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",

View File

@ -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,

View File

@ -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;

View File

@ -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() {

View File

@ -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<Self>) {
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<Connection> {
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<Connection> {
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;
}