From 3b686d5c884c225d52262a6b4cfa7302532420ad Mon Sep 17 00:00:00 2001 From: David Budnick Date: Sun, 5 Jul 2026 23:56:53 -0500 Subject: [PATCH 1/9] feat(windows): embed the app icon and version info in both exes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Windows exes shipped with no resource section at all: Explorer, the taskbar, and Task Manager showed the generic blank-binary icon, and the VersionInfo pane was empty. Generate a .rc at build time (icon + VERSIONINFO tracking CARGO_PKG_VERSION) and compile it with embed-resource — pinned to the exact version already in Cargo.lock as gpui's own build-dependency, so the resolve adds an edge, not a crate, and the pinned gpui rev cannot move. The new design/icon/openlogi.ico (16-256px, PNG-compressed entries) is rendered from design/icon/openlogi.png. The MSI gets an Add/Remove Programs icon via ARPPRODUCTICON; the Start Menu shortcut needs nothing — it picks up the exe's embedded icon automatically. Co-Authored-By: Claude Opus 4.8 (1M context) --- Cargo.lock | 2 + crates/openlogi-agent/Cargo.toml | 6 +++ crates/openlogi-agent/build.rs | 77 +++++++++++++++++++++++++++++ crates/openlogi-gui/Cargo.toml | 6 +++ crates/openlogi-gui/build.rs | 82 +++++++++++++++++++++++++++++-- design/icon/openlogi.ico | Bin 0 -> 92772 bytes packaging/windows/OpenLogi.wxs | 14 ++++++ 7 files changed, 183 insertions(+), 4 deletions(-) create mode 100644 crates/openlogi-agent/build.rs create mode 100644 design/icon/openlogi.ico diff --git a/Cargo.lock b/Cargo.lock index 9b1b68fc..6f1cf65f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4837,6 +4837,7 @@ dependencies = [ name = "openlogi-agent" version = "0.6.19" dependencies = [ + "embed-resource", "futures", "interprocess", "objc2", @@ -4923,6 +4924,7 @@ dependencies = [ "anyhow", "backon", "disclaim", + "embed-resource", "fluent-langneg", "gpui", "gpui-component", diff --git a/crates/openlogi-agent/Cargo.toml b/crates/openlogi-agent/Cargo.toml index c5d4387b..82ac4729 100644 --- a/crates/openlogi-agent/Cargo.toml +++ b/crates/openlogi-agent/Cargo.toml @@ -28,6 +28,12 @@ tracing-subscriber = { workspace = true } sysinfo = { version = "0.38.4", default-features = false, features = ["system"] } opener = "0.8.5" +# Windows exe resources (app icon + VERSIONINFO) — see build.rs. The exact +# version already in Cargo.lock as gpui's own build-dependency, so the resolve +# adds an edge, not a crate, and the pinned gpui rev cannot move. +[build-dependencies] +embed-resource = "3.0.9" + # Menu-bar status item (NSStatusItem) — the agent hosts the tray now. objc2 # gives Retained-owned AppKit bindings (no #99-style leaks); versions match # the GUI's so the resolve doesn't move the gpui Cargo.lock pin. diff --git a/crates/openlogi-agent/build.rs b/crates/openlogi-agent/build.rs new file mode 100644 index 00000000..32bde880 --- /dev/null +++ b/crates/openlogi-agent/build.rs @@ -0,0 +1,77 @@ +//! Build script for openlogi-agent: embeds the Windows exe resources — the +//! app icon and a VERSIONINFO block — so Task Manager and Explorer identify +//! the background agent instead of showing a generic blank binary. +//! +//! Kept in sync with the twin in `crates/openlogi-gui/build.rs` — only the +//! description/filename strings differ. `embed-resource` is pinned to the +//! exact version already in Cargo.lock as gpui's own build-dependency, so it +//! adds an edge, not a crate, and cannot move the pinned gpui rev. + +// A build script fails by panicking, so `expect` (with a message that surfaces +// in the build log) is the idiomatic error path here — exempt it from the +// workspace's strict runtime lints. +#![allow(clippy::expect_used)] + +use std::path::PathBuf; +use std::{env, fs}; + +fn main() { + if env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("windows") { + return; + } + let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR")); + // rc.exe treats `\` in string literals as escapes; forward slashes are + // accepted by every resource compiler embed-resource can drive. + let icon = manifest_dir + .join("../../design/icon/openlogi.ico") + .display() + .to_string() + .replace('\\', "/"); + println!("cargo:rerun-if-changed={icon}"); + + let (major, minor, patch) = ( + env::var("CARGO_PKG_VERSION_MAJOR").expect("CARGO_PKG_VERSION_MAJOR"), + env::var("CARGO_PKG_VERSION_MINOR").expect("CARGO_PKG_VERSION_MINOR"), + env::var("CARGO_PKG_VERSION_PATCH").expect("CARGO_PKG_VERSION_PATCH"), + ); + let version = env::var("CARGO_PKG_VERSION").expect("CARGO_PKG_VERSION"); + let rc = format!( + r#"1 ICON "{icon}" + +1 VERSIONINFO +FILEVERSION {major},{minor},{patch},0 +PRODUCTVERSION {major},{minor},{patch},0 +FILEOS 0x40004L +FILETYPE 0x1L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "AprilNEA" + VALUE "FileDescription", "OpenLogi Background Agent" + VALUE "FileVersion", "{version}" + VALUE "InternalName", "openlogi-agent" + VALUE "OriginalFilename", "openlogi-agent.exe" + VALUE "ProductName", "OpenLogi" + VALUE "ProductVersion", "{version}" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END +"# + ); + let out = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR")); + let rc_path = out.join("openlogi-agent.rc"); + fs::write(&rc_path, rc).expect("write generated .rc into OUT_DIR"); + // manifest_optional: a missing resource compiler downgrades to a cargo + // warning (icon-less but working exe) instead of failing dev builds on + // machines without the Windows SDK; release builds run on CI runners that + // always carry rc.exe. + embed_resource::compile(&rc_path, embed_resource::NONE) + .manifest_optional() + .expect("compile Windows resources"); +} diff --git a/crates/openlogi-gui/Cargo.toml b/crates/openlogi-gui/Cargo.toml index 0c6575f6..e89fc8b4 100644 --- a/crates/openlogi-gui/Cargo.toml +++ b/crates/openlogi-gui/Cargo.toml @@ -48,6 +48,12 @@ fluent-langneg = "0.14.2" backon.workspace = true opener = "0.8.5" +# Windows exe resources (app icon + VERSIONINFO) — see build.rs. The exact +# version already in Cargo.lock as gpui's own build-dependency, so the resolve +# adds an edge, not a crate, and the pinned gpui rev cannot move. +[build-dependencies] +embed-resource = "3.0.9" + # Menu-bar (NSStatusItem) FFI — GPUI has no status-bar API. objc2 gives typed, # `Retained`-owned AppKit bindings so the status-item code can't leak (#99). [target.'cfg(target_os = "macos")'.dependencies] diff --git a/crates/openlogi-gui/build.rs b/crates/openlogi-gui/build.rs index 1479b669..c3e7a1c9 100644 --- a/crates/openlogi-gui/build.rs +++ b/crates/openlogi-gui/build.rs @@ -11,10 +11,13 @@ //! `OPENLOGI_THEMES_DIR` overrides the lookup with an explicit path to the //! gpui-component `themes/` directory, as an escape hatch. //! -//! Uses only `std` on purpose: adding a build-dependency would re-resolve the -//! lockfile and bump the precisely-pinned (Cargo.lock-only) gpui rev — so the -//! `cargo metadata` JSON is scanned for `manifest_path` values directly rather -//! than parsed with serde. +//! Uses only `std` plus `embed-resource` on purpose: a build-dependency the +//! resolver hasn't seen would re-resolve the lockfile and bump the +//! precisely-pinned (Cargo.lock-only) gpui rev — so the `cargo metadata` JSON +//! is scanned for `manifest_path` values directly rather than parsed with +//! serde, and `embed-resource` (for [`embed_windows_resources`]) is pinned to +//! the exact version already in the lock as gpui's own build-dependency, which +//! adds an edge, not a crate. // A build script fails by panicking, so `expect` (with a message that surfaces // in the build log) is the idiomatic error path here — exempt it from the @@ -56,6 +59,8 @@ fn main() { println!("cargo:rerun-if-env-changed=OPENLOGI_UPDATE_MANIFEST_URL"); println!("cargo:rerun-if-env-changed=OPENLOGI_THEMES_DIR"); + embed_windows_resources(); + let out = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR")); let src_dir = locate_themes_dir(); let dest = out.join("themes"); @@ -81,6 +86,75 @@ fn main() { fs::write(out.join("builtin_themes.rs"), generated).expect("write builtin_themes.rs"); } +/// Embed the Windows exe resources — the app icon and a VERSIONINFO block — +/// so Explorer, the taskbar, and Task Manager stop showing the generic blank +/// binary. The `.rc` is generated here rather than committed so the version +/// block tracks `CARGO_PKG_VERSION` (a literal would go stale the moment +/// release-plz bumps). No-op off Windows targets. +/// +/// Kept in sync with the twin in `crates/openlogi-agent/build.rs` — only the +/// description/filename strings differ. +fn embed_windows_resources() { + if env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("windows") { + return; + } + let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR")); + // rc.exe treats `\` in string literals as escapes; forward slashes are + // accepted by every resource compiler embed-resource can drive. + let icon = manifest_dir + .join("../../design/icon/openlogi.ico") + .display() + .to_string() + .replace('\\', "/"); + println!("cargo:rerun-if-changed={icon}"); + + let (major, minor, patch) = ( + env::var("CARGO_PKG_VERSION_MAJOR").expect("CARGO_PKG_VERSION_MAJOR"), + env::var("CARGO_PKG_VERSION_MINOR").expect("CARGO_PKG_VERSION_MINOR"), + env::var("CARGO_PKG_VERSION_PATCH").expect("CARGO_PKG_VERSION_PATCH"), + ); + let version = env::var("CARGO_PKG_VERSION").expect("CARGO_PKG_VERSION"); + let rc = format!( + r#"1 ICON "{icon}" + +1 VERSIONINFO +FILEVERSION {major},{minor},{patch},0 +PRODUCTVERSION {major},{minor},{patch},0 +FILEOS 0x40004L +FILETYPE 0x1L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "AprilNEA" + VALUE "FileDescription", "OpenLogi" + VALUE "FileVersion", "{version}" + VALUE "InternalName", "openlogi-gui" + VALUE "OriginalFilename", "OpenLogi.exe" + VALUE "ProductName", "OpenLogi" + VALUE "ProductVersion", "{version}" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END +"# + ); + let out = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR")); + let rc_path = out.join("openlogi-gui.rc"); + fs::write(&rc_path, rc).expect("write generated .rc into OUT_DIR"); + // manifest_optional: a missing resource compiler downgrades to a cargo + // warning (icon-less but working exe) instead of failing dev builds on + // machines without the Windows SDK; release builds run on CI runners that + // always carry rc.exe. + embed_resource::compile(&rc_path, embed_resource::NONE) + .manifest_optional() + .expect("compile Windows resources"); +} + /// Find the gpui-component `themes/` directory: an explicit override first, else /// the dependency's real source location as reported by `cargo metadata`. fn locate_themes_dir() -> PathBuf { diff --git a/design/icon/openlogi.ico b/design/icon/openlogi.ico new file mode 100644 index 0000000000000000000000000000000000000000..a604ce2149b3ce450786a605ef1ad92048095207 GIT binary patch literal 92772 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

SqUyPHR17>>0xEcZ3-Ms`!QT@d$Cfj| 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

  • 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

    ~u2l>EX`+<0UHTP;#-SgK5HF&}tbyzpj+VUZJ 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 O0000 + + + + + + + + From afecb21b026c9225d912fa55d8ebb920a9136d04 Mon Sep 17 00:00:00 2001 From: David Budnick Date: Mon, 6 Jul 2026 00:04:02 -0500 Subject: [PATCH 2/9] feat(updater): publish Windows assets in latest.json; notify-only checks on Windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Windows checks failed with "no release asset matched the current platform (windows/x86_64)" — for two stacked reasons (#347 PR 4): latest.json only ever listed the DMGs, and the client asked for format "exe", which no release ships. Now: - xtask release latest-json classifies the per-arch Windows .msi/.zip (minisign-required, like the DMGs) into the manifest alongside them; the Linux packages stay out on purpose — those update through the distro package manager. - The R2 publish step stops excluding the zip/msi + minisigs from the immutable releases/ prefix the manifest URLs point into. - The client requests format "msi" so a check can resolve on both x86_64 and arm64. Windows stays notify-only for now: gpui-updater's install strategy there is rename-in-place for a bare exe, and it would clobber OpenLogi.exe with the MSI's bytes if handed one. INSTALL_SUPPORTED gates auto-install off, hides the auto-install switch, and turns a found update into a "Download from GitHub" action until an msiexec flow lands upstream. Co-Authored-By: Claude Opus 4.8 (1M context) --- crates/openlogi-gui/src/platform/updater.rs | 22 ++- .../src/windows/settings/updates.rs | 59 +++++--- xtask/src/commands/release/latest_json.rs | 128 ++++++++++++++++-- 3 files changed, 168 insertions(+), 41 deletions(-) diff --git a/crates/openlogi-gui/src/platform/updater.rs b/crates/openlogi-gui/src/platform/updater.rs index 6a1f0375..e38442b2 100644 --- a/crates/openlogi-gui/src/platform/updater.rs +++ b/crates/openlogi-gui/src/platform/updater.rs @@ -74,6 +74,15 @@ fn minisign_public_key() -> Option<&'static str> { .filter(|key| !key.is_empty()) } +/// Whether this platform's install flow is wired end to end. gpui-updater's +/// Windows strategy is rename-in-place for a bare `.exe`; handing it the MSI +/// the manifest serves would clobber `OpenLogi.exe` with installer bytes. +/// Until an msiexec flow lands upstream, Windows checks are notify-only: a +/// check still resolves and surfaces "update available", but nothing +/// downloads or installs — the Updates page routes the user to the release +/// instead. +pub const INSTALL_SUPPORTED: bool = !cfg!(target_os = "windows"); + fn release_arch() -> &'static str { match std::env::consts::ARCH { "aarch64" => "arm64", @@ -84,7 +93,11 @@ fn release_arch() -> &'static str { fn release_format() -> &'static str { match std::env::consts::OS { "macos" => "dmg", - "windows" => "exe", + // Matches the `format` field `xtask release latest-json` emits for the + // per-arch MSIs. No bare exe ships anymore, so "exe" could never + // match; with [`INSTALL_SUPPORTED`] false the format only has to let a + // *check* resolve. + "windows" => "msi", _ => "tar.gz", } } @@ -100,9 +113,10 @@ pub fn install(cx: &mut App, settings: &AppSettings) { // later manual check — is honoured. Installed unconditionally; it's inert // until both the flag is on and a check resolves to `Available`. let auto_install = cx.observe(&updater, |updater, cx| { - let opted_in = cx - .try_global::() - .is_some_and(|s| s.app_settings().auto_install_updates); + let opted_in = INSTALL_SUPPORTED + && cx + .try_global::() + .is_some_and(|s| s.app_settings().auto_install_updates); if opted_in && matches!(updater.read(cx).status(), UpdateStatus::Available(_)) { updater.update(cx, Updater::download_and_install); } diff --git a/crates/openlogi-gui/src/windows/settings/updates.rs b/crates/openlogi-gui/src/windows/settings/updates.rs index 4318aa8a..79dfa21e 100644 --- a/crates/openlogi-gui/src/windows/settings/updates.rs +++ b/crates/openlogi-gui/src/windows/settings/updates.rs @@ -15,28 +15,31 @@ pub(super) fn updates_page(updater: Entity, pal: Palette) -> SettingPag update_hero(&updater, pal, cx) })); - let toggles = SettingGroup::new() - .item( - SettingItem::new( - tr!("Check for updates"), - SettingField::switch( - |cx| { - cx.try_global::() - .is_some_and(|s| s.app_settings().check_for_updates) - }, - |enabled, cx| { - cx.update_global::(move |s, _| { - s.set_check_for_updates(enabled); - }); - cx.refresh_windows(); - }, - ), - ) - .description(tr!( - "Check once per launch for a new version (query only — no automatic download)." - )), + let toggles = SettingGroup::new().item( + SettingItem::new( + tr!("Check for updates"), + SettingField::switch( + |cx| { + cx.try_global::() + .is_some_and(|s| s.app_settings().check_for_updates) + }, + |enabled, cx| { + cx.update_global::(move |s, _| { + s.set_check_for_updates(enabled); + }); + cx.refresh_windows(); + }, + ), ) - .item( + .description(tr!( + "Check once per launch for a new version (query only — no automatic download)." + )), + ); + // Offering the auto-install switch on a platform whose install flow isn't + // wired (Windows, today) would be a control that silently does nothing — + // hide it instead; checks there are notify-only. + let toggles = if crate::platform::updater::INSTALL_SUPPORTED { + toggles.item( SettingItem::new( tr!("Automatically download and install"), SettingField::switch( @@ -55,7 +58,10 @@ pub(super) fn updates_page(updater: Entity, pal: Palette) -> SettingPag .description(tr!( "Download updates in the background and apply them the next time OpenLogi restarts." )), - ); + ) + } else { + toggles + }; let source = SettingGroup::new().item(SettingItem::render(move |_, _, _| update_source(pal))); @@ -108,6 +114,15 @@ fn update_hero(updater: &Entity, pal: Palette, cx: &mut App) -> AnyElem let action = { let u = updater.clone(); match &status { + // No wired install flow (Windows): a found update routes to the + // GitHub release for a manual download instead of feeding + // gpui-updater an artifact its installer can't apply. + UpdateStatus::Available(_) if !crate::platform::updater::INSTALL_SUPPORTED => { + Button::new("update-download") + .outline() + .label(tr!("Download from GitHub")) + .on_click(|_, _, cx| cx.open_url(RELEASES_URL)) + } UpdateStatus::Available(_) => Button::new("update-install") .outline() .label(tr!("Download & Install")) diff --git a/xtask/src/commands/release/latest_json.rs b/xtask/src/commands/release/latest_json.rs index 31b67031..f98e0cf5 100644 --- a/xtask/src/commands/release/latest_json.rs +++ b/xtask/src/commands/release/latest_json.rs @@ -9,7 +9,10 @@ use time::{OffsetDateTime, format_description::well_known::Rfc3339}; const APP_ID: &str = "org.openlogi.openlogi"; const CHANNEL: &str = "stable"; -const MINIMUM_OS_VERSION: &str = "13.0"; +const MACOS_MINIMUM_OS_VERSION: &str = "13.0"; +/// Windows 10+. Informational — the client updater doesn't gate on it today, +/// and everything that can run OpenLogi reports at least 10.0. +const WINDOWS_MINIMUM_OS_VERSION: &str = "10.0"; #[derive(Parser)] pub(crate) struct Args { @@ -53,6 +56,17 @@ struct Asset { minimum_os_version: &'static str, } +/// The per-OS constants of an updater-relevant artifact, derived from its file +/// name. The Linux packages (`.deb`/`.rpm`) are deliberately absent: those +/// installs update through the distro package manager, not the in-app updater. +struct Classified { + os: &'static str, + arch: String, + format: &'static str, + content_type: &'static str, + minimum_os_version: &'static str, +} + pub(crate) fn run(args: &Args) -> Result<()> { let version = args.tag.strip_prefix('v').unwrap_or(&args.tag).to_string(); let release_base = format!( @@ -61,7 +75,10 @@ pub(crate) fn run(args: &Args) -> Result<()> { args.tag ); let assets = collect_assets(&args.dist, &release_base)?; - if assets.is_empty() { + // The DMGs are the publish gate's guaranteed artifact set; the Windows + // legs are best-effort per arch (a failed leg publishes without them), so + // their absence must not sink the whole manifest. + if !assets.iter().any(|asset| asset.os == "macos") { bail!("no architecture-specific DMG assets found for manifest"); } @@ -98,13 +115,10 @@ fn collect_assets(dist: &Path, release_base: &str) -> Result> { .with_context(|| format!("could not read artifact directory {}", dist.display()))? { let path = entry?.path(); - if path.extension().and_then(|ext| ext.to_str()) != Some("dmg") { - continue; - } let Some(name) = path.file_name().and_then(|name| name.to_str()) else { continue; }; - let Some(arch) = dmg_arch(name) else { + let Some(classified) = classify(name) else { continue; }; let signature_name = format!("{name}.minisig"); @@ -120,10 +134,10 @@ fn collect_assets(dist: &Path, release_base: &str) -> Result> { name: name.to_string(), url: format!("{release_base}/{name}"), signature_url: format!("{release_base}/{signature_name}"), - os: "macos", - arch: arch.to_string(), - format: "dmg", - content_type: "application/x-apple-diskimage", + os: classified.os, + arch: classified.arch, + format: classified.format, + content_type: classified.content_type, size: path .metadata() .with_context(|| format!("could not stat {}", path.display()))? @@ -131,17 +145,52 @@ fn collect_assets(dist: &Path, release_base: &str) -> Result> { sha256: path .sha256() .with_context(|| format!("could not hash artifact {}", path.display()))?, - minimum_os_version: MINIMUM_OS_VERSION, + minimum_os_version: classified.minimum_os_version, }); } assets.sort_by(|a, b| a.name.cmp(&b.name)); Ok(assets) } -fn dmg_arch(name: &str) -> Option<&str> { - let stem = name.strip_suffix(".dmg")?; - let (_, arch) = stem.rsplit_once("-macos-")?; - matches!(arch, "arm64" | "x86_64").then_some(arch) +/// Map an artifact file name onto its manifest constants; `None` for anything +/// the updater can't consume (SHA256SUMS, the Linux packages, the minisigs +/// themselves). +fn classify(name: &str) -> Option { + if let Some(stem) = name.strip_suffix(".dmg") { + return Some(Classified { + os: "macos", + arch: platform_arch(stem, "-macos-")?, + format: "dmg", + content_type: "application/x-apple-diskimage", + minimum_os_version: MACOS_MINIMUM_OS_VERSION, + }); + } + if let Some(stem) = name.strip_suffix(".msi") { + return Some(Classified { + os: "windows", + arch: platform_arch(stem, "-windows-")?, + format: "msi", + content_type: "application/x-msi", + minimum_os_version: WINDOWS_MINIMUM_OS_VERSION, + }); + } + if let Some(stem) = name.strip_suffix(".zip") { + return Some(Classified { + os: "windows", + arch: platform_arch(stem, "-windows-")?, + format: "zip", + content_type: "application/zip", + minimum_os_version: WINDOWS_MINIMUM_OS_VERSION, + }); + } + None +} + +/// The `arm64`/`x86_64` suffix after the `--` marker, or `None` when the +/// stem doesn't carry one (which also filters out non-artifact archives). +fn platform_arch(stem: &str, marker: &str) -> Option { + let (_, arch) = stem.rsplit_once(marker)?; + matches!(arch, "arm64" | "x86_64").then(|| arch.to_string()) } #[cfg(test)] @@ -175,4 +224,53 @@ mod tests { "https://updates.example/releases/v1.2.3/OpenLogi-v1.2.3-macos-arm64.dmg.minisig" ); } + + #[test] + fn collect_assets_includes_windows_msi_and_zip_per_arch() { + let dist = tempfile::tempdir().unwrap(); + for name in [ + "OpenLogi-v1.2.3-windows-x86_64.msi", + "OpenLogi-v1.2.3-windows-arm64.msi", + "OpenLogi-v1.2.3-windows-x86_64.zip", + ] { + fs_err::write(dist.path().join(name), b"artifact").unwrap(); + fs_err::write(dist.path().join(format!("{name}.minisig")), b"signature").unwrap(); + } + + let assets = + collect_assets(dist.path(), "https://updates.example/releases/v1.2.3").unwrap(); + + assert_eq!(assets.len(), 3); + assert!(assets.iter().all(|a| a.os == "windows")); + let msi = assets + .iter() + .find(|a| a.name.ends_with("x86_64.msi")) + .unwrap(); + assert_eq!((msi.arch.as_str(), msi.format), ("x86_64", "msi")); + let zip = assets + .iter() + .find(|a| a.name.ends_with("x86_64.zip")) + .unwrap(); + assert_eq!((zip.arch.as_str(), zip.format), ("x86_64", "zip")); + assert!(assets.iter().any(|a| a.arch == "arm64")); + } + + #[test] + fn collect_assets_skips_linux_packages_and_foreign_archives() { + let dist = tempfile::tempdir().unwrap(); + for name in [ + "openlogi-v1.2.3-linux-amd64.deb", + "openlogi-v1.2.3-linux-amd64.rpm", + "not-an-artifact.zip", + "SHA256SUMS", + ] { + fs_err::write(dist.path().join(name), b"artifact").unwrap(); + fs_err::write(dist.path().join(format!("{name}.minisig")), b"signature").unwrap(); + } + + let assets = + collect_assets(dist.path(), "https://updates.example/releases/v1.2.3").unwrap(); + + assert!(assets.is_empty()); + } } From 002ea7f4ac00b0fadced01f98e89ab849acf90ee Mon Sep 17 00:00:00 2001 From: David Budnick Date: Mon, 6 Jul 2026 00:13:04 -0500 Subject: [PATCH 3/9] fix(settings): stop clipping row controls; make the Assets actions visibly work MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three intertwined Settings problems, worst on Windows: - Custom rows (Updates hero, update source, About config path, the Appearance filter row) built h_flex().justify_between() with a non-shrinking left block, so long content — or just Segoe UI being wider than the macOS system font — shoved the trailing button past the window edge with no horizontal scroll to save it. Give the left blocks flex_1().min_w_0() with truncate() on the long line and pin the controls with flex_shrink_0(), mirroring what the library's own SettingItem rows already do; widen Settings to 920x640 and give aux windows a window_min_size floor like the main window's. - Settings → Assets "Open" was silently dead off macOS: open_in_file_manager was #[cfg(target_os = "macos")] with an empty stub elsewhere, even though the macOS arm already used the cross-platform opener crate. Drop the split. - "Clear" and "Refresh" worked but looked inert: the cache-size blurb is a construction-time snapshot, so the row kept quoting the pre-Clear number until the window reopened. Recompute it shortly after each manual action (the wipe/fetch runs on the main loop's channel arm, not synchronously in the click handler). Co-Authored-By: Claude Opus 4.8 (1M context) --- crates/openlogi-gui/src/asset/mod.rs | 12 ++--- crates/openlogi-gui/src/windows/mod.rs | 4 ++ crates/openlogi-gui/src/windows/settings.rs | 11 ++++- .../src/windows/settings/about.rs | 35 ++++++++----- .../src/windows/settings/appearance.rs | 8 ++- .../src/windows/settings/assets.rs | 49 ++++++++++++++++--- .../src/windows/settings/updates.rs | 28 ++++++++--- 7 files changed, 113 insertions(+), 34 deletions(-) diff --git a/crates/openlogi-gui/src/asset/mod.rs b/crates/openlogi-gui/src/asset/mod.rs index 633468c6..d801d565 100644 --- a/crates/openlogi-gui/src/asset/mod.rs +++ b/crates/openlogi-gui/src/asset/mod.rs @@ -91,19 +91,15 @@ pub fn reveal_cache_in_file_manager() { open_in_file_manager(&root); } -/// Open `path` in the platform file manager. Only macOS has a reveal command -/// wired up today; elsewhere this is a no-op. (Split out so the early return -/// above isn't the function's last statement on non-macOS — `needless_return`.) -#[cfg(target_os = "macos")] +/// Open `path` in the platform file manager. `opener` dispatches per OS +/// (Finder / Explorer / xdg-open), so no `#[cfg]` split — the old macOS-only +/// gating left the Settings → Assets "Open" button silently dead elsewhere. fn open_in_file_manager(path: &Path) { if let Err(e) = opener::open(path) { - warn!(error = %e, "could not open cache dir in Finder"); + warn!(error = %e, "could not open cache dir in the file manager"); } } -#[cfg(not(target_os = "macos"))] -fn open_in_file_manager(_path: &Path) {} - #[derive(Debug, Clone)] pub struct ResolvedAsset { #[allow( diff --git a/crates/openlogi-gui/src/windows/mod.rs b/crates/openlogi-gui/src/windows/mod.rs index 9d6053cb..ea6f4a60 100644 --- a/crates/openlogi-gui/src/windows/mod.rs +++ b/crates/openlogi-gui/src/windows/mod.rs @@ -72,6 +72,10 @@ pub fn open_or_focus( let bounds = Bounds::centered(None, size, cx); let options = WindowOptions { window_bounds: Some(WindowBounds::Windowed(bounds)), + // Aux windows are fixed-content dialogs authored for `size`; that + // makes it the floor too, the way the main window sets one in + // `main_window_options` — below it rows clip their trailing controls. + window_min_size: Some(size), app_id: Some("openlogi".to_string()), titlebar: Some(TitlebarOptions { title: Some(title.clone()), diff --git a/crates/openlogi-gui/src/windows/settings.rs b/crates/openlogi-gui/src/windows/settings.rs index 37b42964..460c10dc 100644 --- a/crates/openlogi-gui/src/windows/settings.rs +++ b/crates/openlogi-gui/src/windows/settings.rs @@ -248,7 +248,10 @@ pub fn open_at(page: SettingsPage, cx: &mut App) { windows::open_or_focus( |reg| &mut reg.settings, "Settings", - Size::new(px(840.), px(600.)), + // Wide enough that the pages' custom rows keep slack under fonts wider + // than the macOS system font (Segoe UI tipped the old 840 into + // clipping the hero rows' trailing buttons on Windows). + Size::new(px(920.), px(640.)), move |window, cx| SettingsView::new(page, window, cx), cx, ); @@ -288,7 +291,11 @@ impl Render for SettingsView { self.language_select.clone(), pal, )) - .page(assets::assets_page(pal, self.asset_cache_desc.clone())) + .page(assets::assets_page( + view.clone(), + pal, + self.asset_cache_desc.clone(), + )) .page(about::about_page(view, self.copied, pal)), ) } diff --git a/crates/openlogi-gui/src/windows/settings/about.rs b/crates/openlogi-gui/src/windows/settings/about.rs index c621a2d4..aeed617e 100644 --- a/crates/openlogi-gui/src/windows/settings/about.rs +++ b/crates/openlogi-gui/src/windows/settings/about.rs @@ -148,22 +148,35 @@ fn about_config(pal: Palette) -> AnyElement { .justify_between() .gap_3() .child( + // The absolute path can be arbitrarily long (deep home dirs, + // Windows profiles) — ellipsize it rather than letting it shove + // the reveal button past the window edge. v_flex() .gap_1() + .flex_1() + .min_w_0() .child(div().font_weight(FontWeight::MEDIUM).child("config.toml")) - .child(div().text_xs().text_color(pal.text_muted).child(path)), + .child( + div() + .text_xs() + .text_color(pal.text_muted) + .truncate() + .child(path), + ), ) .child( - Button::new("about-reveal-config") - .outline() - .label(tr!("Show in file manager")) - .on_click(|_, _, cx| { - if let Ok(dir) = openlogi_core::paths::config_dir() - && let Ok(url) = url::Url::from_file_path(&dir) - { - cx.open_url(url.as_str()); - } - }), + div().flex_shrink_0().child( + Button::new("about-reveal-config") + .outline() + .label(tr!("Show in file manager")) + .on_click(|_, _, cx| { + if let Ok(dir) = openlogi_core::paths::config_dir() + && let Ok(url) = url::Url::from_file_path(&dir) + { + cx.open_url(url.as_str()); + } + }), + ), ) .into_any_element() } diff --git a/crates/openlogi-gui/src/windows/settings/appearance.rs b/crates/openlogi-gui/src/windows/settings/appearance.rs index a4d5766b..9e26162a 100644 --- a/crates/openlogi-gui/src/windows/settings/appearance.rs +++ b/crates/openlogi-gui/src/windows/settings/appearance.rs @@ -368,8 +368,14 @@ fn theme_picker( .justify_between() .gap_3() .child( + // Translated chip labels vary in width; let the chip row + // yield (wrapping onto a second line if it must) so the + // fixed-width search input is never pushed out of view. h_flex() .gap_2() + .flex_1() + .min_w_0() + .flex_wrap() .child(filter_chip( view, "filter-all", @@ -396,7 +402,7 @@ fn theme_picker( )), ) .child( - div().w(px(200.)).child( + div().w(px(200.)).flex_shrink_0().child( Input::new(theme_search) .small() .cleanable(true) diff --git a/crates/openlogi-gui/src/windows/settings/assets.rs b/crates/openlogi-gui/src/windows/settings/assets.rs index 8df1866c..e62cdd13 100644 --- a/crates/openlogi-gui/src/windows/settings/assets.rs +++ b/crates/openlogi-gui/src/windows/settings/assets.rs @@ -1,12 +1,19 @@ //! Assets (device-image cache) settings page. +use std::time::Duration; + use super::{ - App, AppState, AssetCommand, AssetControl, BorrowAppContext, IconName, InteractiveElement, - IntoElement, Palette, ParentElement, SettingField, SettingGroup, SettingItem, SettingPage, - SharedString, StatefulInteractiveElement, Styled, div, + App, AppState, AssetCommand, AssetControl, BorrowAppContext, Entity, IconName, + InteractiveElement, IntoElement, Palette, ParentElement, SettingField, SettingGroup, + SettingItem, SettingPage, SettingsView, SharedString, StatefulInteractiveElement, Styled, div, }; -pub(super) fn assets_page(pal: Palette, cache_desc: SharedString) -> SettingPage { +pub(super) fn assets_page( + view: Entity, + pal: Palette, + cache_desc: SharedString, +) -> SettingPage { + let refresh_view = view.clone(); let group = SettingGroup::new() .item( SettingItem::new( @@ -37,8 +44,14 @@ pub(super) fn assets_page(pal: Palette, cache_desc: SharedString) -> SettingPage SettingItem::new( tr!("Refresh assets"), SettingField::render(move |_, _, _| { - action_button("assets-refresh", tr!("Refresh"), pal, |cx| { + let view = refresh_view.clone(); + action_button("assets-refresh", tr!("Refresh"), pal, move |cx| { send_asset_command(cx, AssetCommand::Refresh); + // Give the spawned sync a moment to land small fetches, + // then re-quote the size row so the click visibly did + // something. Best-effort — a longer sync is caught by + // the next action or window reopen. + refresh_cache_desc_after(&view, Duration::from_secs(2), cx); }) }), ) @@ -48,9 +61,15 @@ pub(super) fn assets_page(pal: Palette, cache_desc: SharedString) -> SettingPage SettingItem::new( tr!("Clear cache"), SettingField::render(move |_, _, _| { - action_button("assets-clear", tr!("Clear"), pal, |cx| { + let view = view.clone(); + action_button("assets-clear", tr!("Clear"), pal, move |cx| { send_asset_command(cx, AssetCommand::ClearCache); cx.refresh_windows(); + // The wipe runs on the main loop's channel arm, not + // synchronously here — without a recompute the row + // keeps quoting the pre-Clear size until the window + // reopens, which reads as the button doing nothing. + refresh_cache_desc_after(&view, Duration::from_millis(750), cx); }) }), ) @@ -74,6 +93,24 @@ pub(super) fn assets_page(pal: Palette, cache_desc: SharedString) -> SettingPage .group(group) } +/// Re-walk the cache and swap the size blurb into the view after `delay`. The +/// manual actions run on the main loop's channel arm, not synchronously in the +/// click handler, so an immediate recompute would race the wipe/fetch. +fn refresh_cache_desc_after(view: &Entity, delay: Duration, cx: &mut App) { + // Weak: the window can close before the timer fires; a strong handle + // would keep the dead view alive just to update it. + let view = view.downgrade(); + cx.spawn(async move |cx| { + cx.background_executor().timer(delay).await; + view.update(cx, |this, cx| { + this.asset_cache_desc = cache_size_description(); + cx.notify(); + }) + .ok(); + }) + .detach(); +} + /// Human-readable size of the on-disk asset cache, for the "Clear cache" row. /// Computed once when the Settings window opens (`asset_cache_desc`), not per /// render. diff --git a/crates/openlogi-gui/src/windows/settings/updates.rs b/crates/openlogi-gui/src/windows/settings/updates.rs index 79dfa21e..e8ce1683 100644 --- a/crates/openlogi-gui/src/windows/settings/updates.rs +++ b/crates/openlogi-gui/src/windows/settings/updates.rs @@ -150,13 +150,21 @@ fn update_hero(updater: &Entity, pal: Palette, cx: &mut App) -> AnyElem .justify_between() .gap_4() .child( + // The left block yields and ellipsizes; the action button never + // shrinks — mirrors the library's own SettingItem rows, which + // otherwise protect themselves the same way. Without this a long + // status line (or a wide UI font) shoves the button past the + // window edge. h_flex() .items_center() .gap_3() + .flex_1() + .min_w_0() .child(img(crate::app_assets::LOGO).w(px(52.)).h(px(52.))) .child( v_flex() .gap_1() + .min_w_0() .child( h_flex() .items_center() @@ -172,11 +180,12 @@ fn update_hero(updater: &Entity, pal: Palette, cx: &mut App) -> AnyElem div() .text_xs() .text_color(pal.text_muted) + .truncate() .child(message.unwrap_or_else(|| tr!("Stable channel"))), ), ), ) - .child(action.disabled(busy)) + .child(div().flex_shrink_0().child(action.disabled(busy))) .into_any_element() } @@ -192,8 +201,12 @@ fn update_source(pal: Palette) -> AnyElement { .justify_between() .gap_3() .child( + // Shrink-safe like the hero row above: the text yields, + // the button stays whole. v_flex() .gap_1() + .flex_1() + .min_w_0() .child( div() .font_weight(FontWeight::MEDIUM) @@ -203,15 +216,18 @@ fn update_source(pal: Palette) -> AnyElement { div() .text_xs() .text_color(pal.text_muted) + .truncate() .child("github.com/AprilNEA/OpenLogi/releases"), ), ) .child( - Button::new("update-changelog") - .ghost() - .icon(IconName::ExternalLink) - .label(tr!("View changelog")) - .on_click(|_, _, cx| cx.open_url(RELEASES_URL)), + div().flex_shrink_0().child( + Button::new("update-changelog") + .ghost() + .icon(IconName::ExternalLink) + .label(tr!("View changelog")) + .on_click(|_, _, cx| cx.open_url(RELEASES_URL)), + ), ), ) .child( From fd294115e86ebd2d642e4cad88a8ca3bb23d52ec Mon Sep 17 00:00:00 2001 From: David Budnick Date: Mon, 6 Jul 2026 00:13:05 -0500 Subject: [PATCH 4/9] docs(readme): recommend the MSI, explain the portable zip, promote Windows out of preview The Windows section listed both artifacts without saying which to pick or why; the intro and roadmap still called the port an untested preview even though the README itself records end-to-end validation on Windows 11 (and the roadmap row still advertised a bare .exe that no release ships). Recommend the per-user MSI, frame the zip as the portable/no-install path, and align the status lines with the recorded validation. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 8d293dbf..be53455c 100644 --- a/README.md +++ b/README.md @@ -45,8 +45,9 @@ two binaries: Everything is local: bindings live in a plain TOML file, button presses are remapped through the OS event tap, and DPI / SmartShift changes are written straight to the device over HID++. -macOS and Linux are supported. Windows is an early, untested preview — signed -builds ship with each release; see [Roadmap](#roadmap). +macOS, Linux, and Windows are supported. Windows is the newest port — +validated end-to-end on Windows 11 with real hardware; signed builds ship with +each release. See [Roadmap](#roadmap). ## Beyond Options+ @@ -85,7 +86,7 @@ Things OpenLogi does that Options+ won't: | Linux packaging: udev rules, systemd unit, `.deb` / `.rpm` | ✅ Linux | | Gesture-button per-direction bindings | 🟡 configurable; hardware capture pending | | Middle / mode-shift / thumbwheel button capture | 🟡 configurable; hook owns side buttons only | -| Windows (agent, GUI, event hook) | 🟡 untested preview — signed `.exe` / `.msi` ship per release | +| Windows (agent, GUI, event hook, tray) | ✅ validated end-to-end on Windows 11 — signed `.zip` / `.msi` ship per release | ¹ Media key actions use D-Bus MPRIS on Linux; a handful of macOS-specific actions (e.g. Launchpad) have no Linux equivalent and are no-ops. @@ -143,11 +144,18 @@ and distros without systemd. ### Windows -Signed portable `.zip` archives and per-user `.msi` installers (x86_64 and -arm64) are attached to each release. Both ship the GUI (`OpenLogi.exe`) -together with the background agent (`openlogi-agent.exe`), which owns all -device I/O — keep the two files side by side when using the portable zip, or -the GUI has nothing to connect to. +Each release attaches two signed Windows artifacts per architecture (x86_64 +and arm64), carrying the same two binaries — the GUI (`OpenLogi.exe`) and the +background agent (`openlogi-agent.exe`), which owns all device I/O: + +- **`.msi` (recommended)** — a per-user installer: no admin prompt, a Start + Menu shortcut, a clean entry in *Installed apps*, and in-place upgrades that + close the running app/agent safely. If you're unsure which to grab, grab + this one. +- **`.zip` (portable)** — unzip and run, no install; suited to machines where + you can't (or don't want to) install software. Keep the two exes side by + side — the GUI spawns its sibling agent, and without it there's nothing to + connect to — and update by replacing the folder yourself. Windows support works and has been validated end-to-end on Windows 11 with real hardware — a wired keyboard and a Unifying-receiver mouse, including From ee3c16eccc703efe01c895f8f83fa54eb3439603 Mon Sep 17 00:00:00 2001 From: David Budnick Date: Mon, 6 Jul 2026 00:16:52 -0500 Subject: [PATCH 5/9] fix(settings): surface the tray toggle on Windows; drop the empty Permissions page Two parity gaps from a macOS-to-Windows feature audit: the notification-area icon honors show_in_menu_bar on Windows (tray_windows.rs) but the switch was compiled out as macOS-only, leaving config.toml surgery as the only way to hide the icon; and the Permissions sidebar entry rendered an empty body on Windows because both of its groups are cfg-gated to macOS/Linux - Windows needs no privacy grants for the WH_MOUSE_LL hook or raw HID access, so the page now only registers where it has content (SettingsPage::index tracks the shift). Co-Authored-By: Claude Opus 4.8 (1M context) --- crates/openlogi-gui/src/windows/settings.rs | 66 ++++++++++++------- .../src/windows/settings/general.rs | 22 +++++-- 2 files changed, 59 insertions(+), 29 deletions(-) diff --git a/crates/openlogi-gui/src/windows/settings.rs b/crates/openlogi-gui/src/windows/settings.rs index 460c10dc..bfcc3114 100644 --- a/crates/openlogi-gui/src/windows/settings.rs +++ b/crates/openlogi-gui/src/windows/settings.rs @@ -54,6 +54,10 @@ mod appearance; mod assets; mod general; mod language; +// Windows needs no privacy grants — the WH_MOUSE_LL hook and raw HID access +// work without one — so there the page would render empty; register it only +// where it has content. `SettingsPage::index` tracks the shift. +#[cfg(any(target_os = "macos", target_os = "linux"))] mod permissions; mod updates; @@ -73,7 +77,15 @@ impl SettingsPage { match self { Self::General => 0, Self::Updates => 1, - Self::About => 5, + // One lower on Windows: the Permissions page isn't registered + // there (see the `mod permissions` cfg). + Self::About => { + if cfg!(any(target_os = "macos", target_os = "linux")) { + 5 + } else { + 4 + } + } } } } @@ -274,29 +286,35 @@ impl Render for SettingsView { // Outline group boxes give every page bordered cards (depth / // definition that the flat Fill variant lacked); the hero / // source / config blocks are custom rows inside them. - Settings::new("settings") - .with_group_variant(GroupBoxVariant::Outline) - .sidebar_width(px(210.)) - .default_selected_index(SelectIndex { - page_ix: self.initial_page.index(), - group_ix: None, - }) - .page(general::general_page(self.sensitivity_slider.clone())) - .page(updates::updates_page(self.updater.clone(), pal)) - .page(permissions::permissions_page(pal)) - .page(appearance::appearance_page( - view.clone(), - self.theme_filter, - self.theme_search.clone(), - self.language_select.clone(), - pal, - )) - .page(assets::assets_page( - view.clone(), - pal, - self.asset_cache_desc.clone(), - )) - .page(about::about_page(view, self.copied, pal)), + { + let settings = Settings::new("settings") + .with_group_variant(GroupBoxVariant::Outline) + .sidebar_width(px(210.)) + .default_selected_index(SelectIndex { + page_ix: self.initial_page.index(), + group_ix: None, + }) + .page(general::general_page(self.sensitivity_slider.clone())) + .page(updates::updates_page(self.updater.clone(), pal)); + // Registered only where grants exist to manage — see the + // `mod permissions` cfg for why Windows skips it. + #[cfg(any(target_os = "macos", target_os = "linux"))] + let settings = settings.page(permissions::permissions_page(pal)); + settings + .page(appearance::appearance_page( + view.clone(), + self.theme_filter, + self.theme_search.clone(), + self.language_select.clone(), + pal, + )) + .page(assets::assets_page( + view.clone(), + pal, + self.asset_cache_desc.clone(), + )) + .page(about::about_page(view, self.copied, pal)) + }, ) } } diff --git a/crates/openlogi-gui/src/windows/settings/general.rs b/crates/openlogi-gui/src/windows/settings/general.rs index 7b6adc78..3d2094d3 100644 --- a/crates/openlogi-gui/src/windows/settings/general.rs +++ b/crates/openlogi-gui/src/windows/settings/general.rs @@ -40,10 +40,18 @@ pub(super) fn general_page(sensitivity_slider: Entity) -> SettingPa )), ); - #[cfg(target_os = "macos")] + // The same `show_in_menu_bar` setting drives the macOS status item and + // the Windows notification-area icon (the agent honors it on both; next + // launch, see tray.rs / tray_windows.rs) — so both platforms get the + // switch, with platform-fitting wording. Linux has no tray; no switch. + #[cfg(any(target_os = "macos", target_os = "windows"))] let group = group.item( SettingItem::new( - tr!("Show in menu bar"), + if cfg!(target_os = "macos") { + tr!("Show in menu bar") + } else { + tr!("Show in the notification area") + }, SettingField::switch( |cx| { cx.try_global::() @@ -57,9 +65,13 @@ pub(super) fn general_page(sensitivity_slider: Entity) -> SettingPa }, ), ) - .description(tr!( - "Keep OpenLogi's icon in the menu bar. When off, it stays in the Dock instead." - )), + .description(if cfg!(target_os = "macos") { + tr!("Keep OpenLogi's icon in the menu bar. When off, it stays in the Dock instead.") + } else { + tr!( + "Keep OpenLogi's icon in the taskbar notification area. Takes effect the next time the background agent starts." + ) + }), ); SettingPage::new(tr!("General")) From d4df92154cc36c829cd60fe3daba20efd0acf53b Mon Sep 17 00:00:00 2001 From: David Budnick Date: Mon, 6 Jul 2026 00:47:15 -0500 Subject: [PATCH 6/9] test(hook): stop expecting Unsupported from the Windows hook The unsupported-target cfg predates the Windows port (#167), so cargo test failed on every real Windows box: Hook::start succeeds there now. Windows joins the supported-platform test (which also exercises real WH_MOUSE_LL install/stop), and the Unsupported expectation is left for platforms that genuinely have no hook. CI never caught this because Windows runs clippy only (#347 PR 3). Co-Authored-By: Claude Opus 4.8 (1M context) --- crates/openlogi-hook/src/tests.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/crates/openlogi-hook/src/tests.rs b/crates/openlogi-hook/src/tests.rs index fe6efa6a..faa8b925 100644 --- a/crates/openlogi-hook/src/tests.rs +++ b/crates/openlogi-hook/src/tests.rs @@ -53,23 +53,27 @@ fn event_disposition_equality() { assert_ne!(EventDisposition::PassThrough, EventDisposition::Suppress); } -/// On unsupported targets (not macOS, not Linux), `Hook::start` returns `Unsupported`. -#[cfg(not(any(target_os = "macos", target_os = "linux")))] +/// On unsupported targets (not macOS, Linux, or Windows), `Hook::start` +/// returns `Unsupported`. The cfg predates the Windows port (#167) — Windows +/// belongs with the supported targets below, where this stale form made +/// `cargo test` fail on every real Windows box. +#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))] #[test] fn unsupported_start_returns_unsupported() { let result = Hook::start(|_| EventDisposition::PassThrough); assert!(matches!(result, Err(HookError::Unsupported))); } -/// On Linux, `Hook::start` never returns `Unsupported` — it either succeeds or -/// returns a Linux-specific error (e.g. `NoDeviceFound` in a headless CI env). -#[cfg(target_os = "linux")] +/// On Linux and Windows, `Hook::start` never returns `Unsupported` — it either +/// succeeds (`WH_MOUSE_LL` needs no grant on Windows) or returns a +/// platform-specific error (e.g. `NoDeviceFound` in a headless Linux CI env). +#[cfg(any(target_os = "linux", target_os = "windows"))] #[test] -fn linux_start_does_not_return_unsupported() { +fn supported_start_does_not_return_unsupported() { let result = Hook::start(|_| EventDisposition::PassThrough); assert!( !matches!(result, Err(HookError::Unsupported)), - "Hook::start returned Unsupported on Linux" + "Hook::start returned Unsupported on a supported platform" ); // Clean up if a hook was actually installed. if let Ok(hook) = result { From b2a891c59fa6c2b8552fb36e190f5b3bd200bca2 Mon Sep 17 00:00:00 2001 From: David Budnick Date: Mon, 6 Jul 2026 00:55:09 -0500 Subject: [PATCH 7/9] i18n: translate the new tray and update strings in all 20 locales The notification-area toggle, its description, and the notify-only "Download from GitHub" action were en-only literals; the locale key-consistency test requires every file to carry them, and Crowdin only surfaces keys that exist. Co-Authored-By: Claude Opus 4.8 (1M context) --- crates/openlogi-gui/locales/da.yml | 3 +++ crates/openlogi-gui/locales/de.yml | 3 +++ crates/openlogi-gui/locales/el.yml | 3 +++ crates/openlogi-gui/locales/en.yml | 3 +++ crates/openlogi-gui/locales/es.yml | 3 +++ crates/openlogi-gui/locales/fi.yml | 3 +++ crates/openlogi-gui/locales/fr.yml | 3 +++ crates/openlogi-gui/locales/it.yml | 3 +++ crates/openlogi-gui/locales/ja.yml | 3 +++ crates/openlogi-gui/locales/ko.yml | 3 +++ crates/openlogi-gui/locales/nb.yml | 3 +++ crates/openlogi-gui/locales/nl.yml | 3 +++ crates/openlogi-gui/locales/pl.yml | 3 +++ crates/openlogi-gui/locales/pt-BR.yml | 3 +++ crates/openlogi-gui/locales/pt-PT.yml | 3 +++ crates/openlogi-gui/locales/ru.yml | 3 +++ crates/openlogi-gui/locales/sv.yml | 3 +++ crates/openlogi-gui/locales/zh-CN.yml | 3 +++ crates/openlogi-gui/locales/zh-HK.yml | 3 +++ crates/openlogi-gui/locales/zh-TW.yml | 3 +++ 20 files changed, 60 insertions(+) diff --git a/crates/openlogi-gui/locales/da.yml b/crates/openlogi-gui/locales/da.yml index 2e2f041f..872b8c83 100644 --- a/crates/openlogi-gui/locales/da.yml +++ b/crates/openlogi-gui/locales/da.yml @@ -106,6 +106,9 @@ _version: 1 "Check once per launch for a new version (query only — no automatic download).": "Søg én gang per start efter en ny version (kun forespørgsel — ingen automatisk download)." "Show in menu bar": "Vis i menulinjen" "Keep OpenLogi's icon in the menu bar. When off, it stays in the Dock instead.": "Behold OpenLogis ikon i menulinjen. Når den er slået fra, forbliver det i Dock'en." +"Show in the notification area": "Vis i meddelelsesområdet" +"Keep OpenLogi's icon in the taskbar notification area. Takes effect the next time the background agent starts.": "Behold OpenLogis ikon i proceslinjens meddelelsesområde. Træder i kraft, næste gang baggrundsagenten starter." +"Download from GitHub": "Download fra GitHub" "Assets": "Ressourcer" "Automatically download device images": "Download enhedsbilleder automatisk" "Fetch device renders from assets.openlogi.org when a device connects. When off, OpenLogi makes no asset network requests; bundled art and the silhouette still show.": "Hent enhedsbilleder fra assets.openlogi.org, når en enhed forbindes. Når det er slået fra, sender OpenLogi ingen netværksanmodninger efter ressourcer; medfølgende billeder og silhuetten vises stadig." diff --git a/crates/openlogi-gui/locales/de.yml b/crates/openlogi-gui/locales/de.yml index 99480ebe..16febd4f 100644 --- a/crates/openlogi-gui/locales/de.yml +++ b/crates/openlogi-gui/locales/de.yml @@ -106,6 +106,9 @@ _version: 1 "Check once per launch for a new version (query only — no automatic download).": "Bei jedem Start einmal auf eine neue Version prüfen (nur Abfrage – kein automatischer Download)." "Show in menu bar": "In der Menüleiste anzeigen" "Keep OpenLogi's icon in the menu bar. When off, it stays in the Dock instead.": "OpenLogi-Symbol in der Menüleiste behalten. Wenn deaktiviert, bleibt es stattdessen im Dock." +"Show in the notification area": "Im Infobereich anzeigen" +"Keep OpenLogi's icon in the taskbar notification area. Takes effect the next time the background agent starts.": "OpenLogi-Symbol im Infobereich der Taskleiste anzeigen. Wird beim nächsten Start des Hintergrund-Agenten wirksam." +"Download from GitHub": "Von GitHub herunterladen" "Assets": "Ressourcen" "Automatically download device images": "Gerätebilder automatisch herunterladen" "Fetch device renders from assets.openlogi.org when a device connects. When off, OpenLogi makes no asset network requests; bundled art and the silhouette still show.": "Gerätebilder beim Verbinden eines Geräts von assets.openlogi.org laden. Wenn deaktiviert, stellt OpenLogi keine Netzwerkanfragen für Ressourcen; mitgelieferte Bilder und die Silhouette werden weiterhin angezeigt." diff --git a/crates/openlogi-gui/locales/el.yml b/crates/openlogi-gui/locales/el.yml index 3b96ae1c..82b1d95c 100644 --- a/crates/openlogi-gui/locales/el.yml +++ b/crates/openlogi-gui/locales/el.yml @@ -106,6 +106,9 @@ _version: 1 "Check once per launch for a new version (query only — no automatic download).": "Έλεγχος μία φορά ανά εκκίνηση για νέα έκδοση (μόνο αναζήτηση — χωρίς αυτόματη λήψη)." "Show in menu bar": "Εμφάνιση στη γραμμή μενού" "Keep OpenLogi's icon in the menu bar. When off, it stays in the Dock instead.": "Διατήρηση του εικονιδίου του OpenLogi στη γραμμή μενού. Όταν είναι ανενεργό, παραμένει στο Dock." +"Show in the notification area": "Εμφάνιση στην περιοχή ειδοποιήσεων" +"Keep OpenLogi's icon in the taskbar notification area. Takes effect the next time the background agent starts.": "Διατηρεί το εικονίδιο του OpenLogi στην περιοχή ειδοποιήσεων της γραμμής εργασιών. Ισχύει από την επόμενη εκκίνηση της υπηρεσίας παρασκηνίου." +"Download from GitHub": "Λήψη από το GitHub" "Assets": "Πόροι" "Automatically download device images": "Αυτόματη λήψη εικόνων συσκευών" "Fetch device renders from assets.openlogi.org when a device connects. When off, OpenLogi makes no asset network requests; bundled art and the silhouette still show.": "Λήψη εικόνων συσκευών από το assets.openlogi.org όταν συνδέεται μια συσκευή. Όταν είναι απενεργοποιημένο, το OpenLogi δεν πραγματοποιεί αιτήματα δικτύου για πόρους· οι ενσωματωμένες εικόνες και η σιλουέτα εξακολουθούν να εμφανίζονται." diff --git a/crates/openlogi-gui/locales/en.yml b/crates/openlogi-gui/locales/en.yml index 7b567aa7..5124f43a 100644 --- a/crates/openlogi-gui/locales/en.yml +++ b/crates/openlogi-gui/locales/en.yml @@ -106,6 +106,9 @@ _version: 1 "Check once per launch for a new version (query only — no automatic download).": "Check once per launch for a new version (query only — no automatic download)." "Show in menu bar": "Show in menu bar" "Keep OpenLogi's icon in the menu bar. When off, it stays in the Dock instead.": "Keep OpenLogi's icon in the menu bar. When off, it stays in the Dock instead." +"Show in the notification area": "Show in the notification area" +"Keep OpenLogi's icon in the taskbar notification area. Takes effect the next time the background agent starts.": "Keep OpenLogi's icon in the taskbar notification area. Takes effect the next time the background agent starts." +"Download from GitHub": "Download from GitHub" "Assets": "Assets" "Automatically download device images": "Automatically download device images" "Fetch device renders from assets.openlogi.org when a device connects. When off, OpenLogi makes no asset network requests; bundled art and the silhouette still show.": "Fetch device renders from assets.openlogi.org when a device connects. When off, OpenLogi makes no asset network requests; bundled art and the silhouette still show." diff --git a/crates/openlogi-gui/locales/es.yml b/crates/openlogi-gui/locales/es.yml index 9374362f..7365d9cc 100644 --- a/crates/openlogi-gui/locales/es.yml +++ b/crates/openlogi-gui/locales/es.yml @@ -106,6 +106,9 @@ _version: 1 "Check once per launch for a new version (query only — no automatic download).": "Busca una versión nueva una vez por inicio (solo consulta: sin descarga automática)." "Show in menu bar": "Mostrar en la barra de menús" "Keep OpenLogi's icon in the menu bar. When off, it stays in the Dock instead.": "Mantén el icono de OpenLogi en la barra de menús. Si se desactiva, permanece en el Dock." +"Show in the notification area": "Mostrar en el área de notificación" +"Keep OpenLogi's icon in the taskbar notification area. Takes effect the next time the background agent starts.": "Mantiene el icono de OpenLogi en el área de notificación de la barra de tareas. Surte efecto la próxima vez que se inicie el agente en segundo plano." +"Download from GitHub": "Descargar desde GitHub" "Assets": "Recursos" "Automatically download device images": "Descargar imágenes de dispositivos automáticamente" "Fetch device renders from assets.openlogi.org when a device connects. When off, OpenLogi makes no asset network requests; bundled art and the silhouette still show.": "Obtén las imágenes de los dispositivos desde assets.openlogi.org cuando se conecta un dispositivo. Si está desactivado, OpenLogi no hace ninguna solicitud de red de recursos; las imágenes incluidas y la silueta se siguen mostrando." diff --git a/crates/openlogi-gui/locales/fi.yml b/crates/openlogi-gui/locales/fi.yml index 44fe3b8c..7b90c6f6 100644 --- a/crates/openlogi-gui/locales/fi.yml +++ b/crates/openlogi-gui/locales/fi.yml @@ -106,6 +106,9 @@ _version: 1 "Check once per launch for a new version (query only — no automatic download).": "Tarkista uusi versio kerran käynnistyksen yhteydessä (vain tarkistus — ei automaattista lataamista)." "Show in menu bar": "Näytä valikkorivillä" "Keep OpenLogi's icon in the menu bar. When off, it stays in the Dock instead.": "Pidä OpenLogin kuvake valikkorivillä. Kun pois päältä, se pysyy Dockissa." +"Show in the notification area": "Näytä ilmoitusalueella" +"Keep OpenLogi's icon in the taskbar notification area. Takes effect the next time the background agent starts.": "Pitää OpenLogin kuvakkeen tehtäväpalkin ilmoitusalueella. Tulee voimaan, kun taustaprosessi käynnistyy seuraavan kerran." +"Download from GitHub": "Lataa GitHubista" "Assets": "Resurssit" "Automatically download device images": "Lataa laitekuvat automaattisesti" "Fetch device renders from assets.openlogi.org when a device connects. When off, OpenLogi makes no asset network requests; bundled art and the silhouette still show.": "Hae laitteiden kuvat osoitteesta assets.openlogi.org, kun laite yhdistetään. Kun tämä on pois päältä, OpenLogi ei tee lainkaan resurssien verkkopyyntöjä; mukana toimitetut kuvat ja siluetti näkyvät silti." diff --git a/crates/openlogi-gui/locales/fr.yml b/crates/openlogi-gui/locales/fr.yml index 1317cd65..50fbb7b3 100644 --- a/crates/openlogi-gui/locales/fr.yml +++ b/crates/openlogi-gui/locales/fr.yml @@ -106,6 +106,9 @@ _version: 1 "Check once per launch for a new version (query only — no automatic download).": "Vérifier une nouvelle version une fois par lancement (vérification seulement — aucun téléchargement automatique)." "Show in menu bar": "Afficher dans la barre des menus" "Keep OpenLogi's icon in the menu bar. When off, it stays in the Dock instead.": "Conserver l'icône d'OpenLogi dans la barre des menus. Si désactivé, elle reste dans le Dock." +"Show in the notification area": "Afficher dans la zone de notification" +"Keep OpenLogi's icon in the taskbar notification area. Takes effect the next time the background agent starts.": "Garde l'icône d'OpenLogi dans la zone de notification de la barre des tâches. Prend effet au prochain démarrage de l'agent en arrière-plan." +"Download from GitHub": "Télécharger depuis GitHub" "Assets": "Ressources" "Automatically download device images": "Télécharger automatiquement les images des appareils" "Fetch device renders from assets.openlogi.org when a device connects. When off, OpenLogi makes no asset network requests; bundled art and the silhouette still show.": "Récupérer les images des appareils depuis assets.openlogi.org lorsqu'un appareil se connecte. Si désactivé, OpenLogi n'effectue aucune requête réseau pour les ressources ; les images intégrées et la silhouette restent affichées." diff --git a/crates/openlogi-gui/locales/it.yml b/crates/openlogi-gui/locales/it.yml index 08b290b7..05a67c49 100644 --- a/crates/openlogi-gui/locales/it.yml +++ b/crates/openlogi-gui/locales/it.yml @@ -106,6 +106,9 @@ _version: 1 "Check once per launch for a new version (query only — no automatic download).": "Controlla la presenza di una nuova versione ad ogni avvio (solo verifica — nessun download automatico)." "Show in menu bar": "Mostra nella barra dei menu" "Keep OpenLogi's icon in the menu bar. When off, it stays in the Dock instead.": "Mantieni l'icona di OpenLogi nella barra dei menu. Quando è disattivata, rimarrà invece nel Dock." +"Show in the notification area": "Mostra nell'area di notifica" +"Keep OpenLogi's icon in the taskbar notification area. Takes effect the next time the background agent starts.": "Mantiene l'icona di OpenLogi nell'area di notifica della barra delle applicazioni. Ha effetto al prossimo avvio dell'agente in background." +"Download from GitHub": "Scarica da GitHub" "Assets": "Risorse" "Automatically download device images": "Scarica automaticamente le immagini dei dispositivi" "Fetch device renders from assets.openlogi.org when a device connects. When off, OpenLogi makes no asset network requests; bundled art and the silhouette still show.": "Recupera le immagini dei dispositivi da assets.openlogi.org quando un dispositivo si connette. Se disattivato, OpenLogi non effettua alcuna richiesta di rete per le risorse; le immagini incluse e la sagoma vengono comunque mostrate." diff --git a/crates/openlogi-gui/locales/ja.yml b/crates/openlogi-gui/locales/ja.yml index 61c9d9d0..04d59d87 100644 --- a/crates/openlogi-gui/locales/ja.yml +++ b/crates/openlogi-gui/locales/ja.yml @@ -106,6 +106,9 @@ _version: 1 "Check once per launch for a new version (query only — no automatic download).": "起動ごとに新しいバージョンを一度だけ確認します(確認のみ — 自動ダウンロードはしません)。" "Show in menu bar": "メニューバーに表示" "Keep OpenLogi's icon in the menu bar. When off, it stays in the Dock instead.": "OpenLogi のアイコンをメニューバーに表示します。オフにすると Dock に残ります。" +"Show in the notification area": "通知領域に表示" +"Keep OpenLogi's icon in the taskbar notification area. Takes effect the next time the background agent starts.": "タスクバーの通知領域に OpenLogi のアイコンを表示します。バックグラウンドエージェントの次回起動時に反映されます。" +"Download from GitHub": "GitHub からダウンロード" "Assets": "アセット" "Automatically download device images": "デバイス画像を自動的にダウンロード" "Fetch device renders from assets.openlogi.org when a device connects. When off, OpenLogi makes no asset network requests; bundled art and the silhouette still show.": "デバイス接続時に assets.openlogi.org からデバイス画像を取得します。オフにすると、OpenLogi はアセットのネットワーク要求を一切行わず、バンドルされた画像とシルエットのみが表示されます。" diff --git a/crates/openlogi-gui/locales/ko.yml b/crates/openlogi-gui/locales/ko.yml index c4504c0e..ed0d8cb9 100644 --- a/crates/openlogi-gui/locales/ko.yml +++ b/crates/openlogi-gui/locales/ko.yml @@ -106,6 +106,9 @@ _version: 1 "Check once per launch for a new version (query only — no automatic download).": "실행할 때마다 새 버전을 한 번 확인합니다 (확인만 — 자동 다운로드 없음)." "Show in menu bar": "메뉴 막대에 표시" "Keep OpenLogi's icon in the menu bar. When off, it stays in the Dock instead.": "OpenLogi 아이콘을 메뉴 막대에 유지합니다. 끄면 대신 Dock에 남습니다." +"Show in the notification area": "알림 영역에 표시" +"Keep OpenLogi's icon in the taskbar notification area. Takes effect the next time the background agent starts.": "작업 표시줄 알림 영역에 OpenLogi 아이콘을 유지합니다. 백그라운드 에이전트가 다음에 시작될 때 적용됩니다." +"Download from GitHub": "GitHub에서 다운로드" "Assets": "리소스" "Automatically download device images": "기기 이미지 자동 다운로드" "Fetch device renders from assets.openlogi.org when a device connects. When off, OpenLogi makes no asset network requests; bundled art and the silhouette still show.": "기기가 연결되면 assets.openlogi.org에서 기기 이미지를 가져옵니다. 끄면 OpenLogi는 리소스 네트워크 요청을 전혀 하지 않으며, 내장 이미지와 실루엣은 계속 표시됩니다." diff --git a/crates/openlogi-gui/locales/nb.yml b/crates/openlogi-gui/locales/nb.yml index 2f0e55c4..9a2d65b9 100644 --- a/crates/openlogi-gui/locales/nb.yml +++ b/crates/openlogi-gui/locales/nb.yml @@ -106,6 +106,9 @@ _version: 1 "Check once per launch for a new version (query only — no automatic download).": "Sjekk én gang per oppstart om det finnes en ny versjon (kun forespørsel — ingen automatisk nedlasting)." "Show in menu bar": "Vis i menylinjen" "Keep OpenLogi's icon in the menu bar. When off, it stays in the Dock instead.": "Behold OpenLogi-ikonet i menylinjen. Når av forblir det i Dock-en i stedet." +"Show in the notification area": "Vis i varslingsområdet" +"Keep OpenLogi's icon in the taskbar notification area. Takes effect the next time the background agent starts.": "Beholder OpenLogi-ikonet i varslingsområdet på oppgavelinjen. Trer i kraft neste gang bakgrunnsagenten starter." +"Download from GitHub": "Last ned fra GitHub" "Assets": "Ressurser" "Automatically download device images": "Last ned enhetsbilder automatisk" "Fetch device renders from assets.openlogi.org when a device connects. When off, OpenLogi makes no asset network requests; bundled art and the silhouette still show.": "Hent enhetsbilder fra assets.openlogi.org når en enhet kobles til. Når dette er av, sender OpenLogi ingen nettverksforespørsler etter ressurser; medfølgende bilder og silhuetten vises fortsatt." diff --git a/crates/openlogi-gui/locales/nl.yml b/crates/openlogi-gui/locales/nl.yml index 51f32483..95c25a83 100644 --- a/crates/openlogi-gui/locales/nl.yml +++ b/crates/openlogi-gui/locales/nl.yml @@ -106,6 +106,9 @@ _version: 1 "Check once per launch for a new version (query only — no automatic download).": "Controleer eenmaal per start op een nieuwe versie (alleen opvragen — geen automatische download)." "Show in menu bar": "Weergeven in menubalk" "Keep OpenLogi's icon in the menu bar. When off, it stays in the Dock instead.": "Houd het OpenLogi-pictogram in de menubalk. Indien uit, blijft het in het Dock." +"Show in the notification area": "Weergeven in het systeemvak" +"Keep OpenLogi's icon in the taskbar notification area. Takes effect the next time the background agent starts.": "Houdt het OpenLogi-pictogram in het systeemvak van de taakbalk. Wordt van kracht wanneer de achtergrondagent opnieuw start." +"Download from GitHub": "Downloaden van GitHub" "Assets": "Bronnen" "Automatically download device images": "Apparaatafbeeldingen automatisch downloaden" "Fetch device renders from assets.openlogi.org when a device connects. When off, OpenLogi makes no asset network requests; bundled art and the silhouette still show.": "Haal apparaatafbeeldingen op van assets.openlogi.org wanneer een apparaat verbinding maakt. Indien uitgeschakeld doet OpenLogi geen enkel netwerkverzoek voor bronnen; meegeleverde afbeeldingen en het silhouet worden nog steeds getoond." diff --git a/crates/openlogi-gui/locales/pl.yml b/crates/openlogi-gui/locales/pl.yml index fbec6b45..50bf6bed 100644 --- a/crates/openlogi-gui/locales/pl.yml +++ b/crates/openlogi-gui/locales/pl.yml @@ -106,6 +106,9 @@ _version: 1 "Check once per launch for a new version (query only — no automatic download).": "Sprawdzaj raz przy każdym uruchomieniu, czy jest nowa wersja (tylko zapytanie — bez automatycznego pobierania)." "Show in menu bar": "Pokaż na pasku menu" "Keep OpenLogi's icon in the menu bar. When off, it stays in the Dock instead.": "Zachowaj ikonę OpenLogi na pasku menu. Po wyłączeniu pozostanie w Docku." +"Show in the notification area": "Pokaż w obszarze powiadomień" +"Keep OpenLogi's icon in the taskbar notification area. Takes effect the next time the background agent starts.": "Utrzymuje ikonę OpenLogi w obszarze powiadomień paska zadań. Zacznie obowiązywać przy następnym uruchomieniu agenta w tle." +"Download from GitHub": "Pobierz z GitHuba" "Assets": "Zasoby" "Automatically download device images": "Automatycznie pobieraj obrazy urządzeń" "Fetch device renders from assets.openlogi.org when a device connects. When off, OpenLogi makes no asset network requests; bundled art and the silhouette still show.": "Pobieraj obrazy urządzeń z assets.openlogi.org po podłączeniu urządzenia. Gdy wyłączone, OpenLogi nie wysyła żadnych żądań sieciowych po zasoby; dołączone obrazy i sylwetka nadal są wyświetlane." diff --git a/crates/openlogi-gui/locales/pt-BR.yml b/crates/openlogi-gui/locales/pt-BR.yml index 4508e5ae..0d301b30 100644 --- a/crates/openlogi-gui/locales/pt-BR.yml +++ b/crates/openlogi-gui/locales/pt-BR.yml @@ -106,6 +106,9 @@ _version: 1 "Check once per launch for a new version (query only — no automatic download).": "Verificar uma vez por inicialização se há uma nova versão (apenas consulta — sem download automático)." "Show in menu bar": "Mostrar na barra de menus" "Keep OpenLogi's icon in the menu bar. When off, it stays in the Dock instead.": "Manter o ícone do OpenLogi na barra de menus. Quando desativado, permanece no Dock." +"Show in the notification area": "Mostrar na área de notificação" +"Keep OpenLogi's icon in the taskbar notification area. Takes effect the next time the background agent starts.": "Mantém o ícone do OpenLogi na área de notificação da barra de tarefas. Entra em vigor na próxima vez que o agente em segundo plano iniciar." +"Download from GitHub": "Baixar do GitHub" "Assets": "Recursos" "Automatically download device images": "Baixar imagens dos dispositivos automaticamente" "Fetch device renders from assets.openlogi.org when a device connects. When off, OpenLogi makes no asset network requests; bundled art and the silhouette still show.": "Buscar as imagens dos dispositivos em assets.openlogi.org quando um dispositivo se conecta. Quando desativado, o OpenLogi não faz nenhuma solicitação de rede de recursos; as imagens incluídas e a silhueta continuam sendo exibidas." diff --git a/crates/openlogi-gui/locales/pt-PT.yml b/crates/openlogi-gui/locales/pt-PT.yml index 0a13f08c..8083e2fb 100644 --- a/crates/openlogi-gui/locales/pt-PT.yml +++ b/crates/openlogi-gui/locales/pt-PT.yml @@ -106,6 +106,9 @@ _version: 1 "Check once per launch for a new version (query only — no automatic download).": "Procurar uma nova versão uma vez por arranque (apenas consulta — sem transferência automática)." "Show in menu bar": "Mostrar na barra de menus" "Keep OpenLogi's icon in the menu bar. When off, it stays in the Dock instead.": "Manter o ícone do OpenLogi na barra de menus. Quando desativado, permanece no Dock." +"Show in the notification area": "Mostrar na área de notificação" +"Keep OpenLogi's icon in the taskbar notification area. Takes effect the next time the background agent starts.": "Mantém o ícone do OpenLogi na área de notificação da barra de tarefas. Produz efeito no próximo arranque do agente em segundo plano." +"Download from GitHub": "Transferir do GitHub" "Assets": "Recursos" "Automatically download device images": "Transferir imagens dos dispositivos automaticamente" "Fetch device renders from assets.openlogi.org when a device connects. When off, OpenLogi makes no asset network requests; bundled art and the silhouette still show.": "Obter as imagens dos dispositivos de assets.openlogi.org quando um dispositivo se liga. Quando desativado, o OpenLogi não faz quaisquer pedidos de rede de recursos; as imagens incluídas e a silhueta continuam a ser apresentadas." diff --git a/crates/openlogi-gui/locales/ru.yml b/crates/openlogi-gui/locales/ru.yml index 32e1be75..fc924df5 100644 --- a/crates/openlogi-gui/locales/ru.yml +++ b/crates/openlogi-gui/locales/ru.yml @@ -106,6 +106,9 @@ _version: 1 "Check once per launch for a new version (query only — no automatic download).": "Один раз при запуске проверять наличие новой версии (только проверка — без автоматической загрузки)." "Show in menu bar": "Показывать в строке меню" "Keep OpenLogi's icon in the menu bar. When off, it stays in the Dock instead.": "Оставлять значок OpenLogi в строке меню. Если выключено, приложение остаётся в Dock." +"Show in the notification area": "Показывать в области уведомлений" +"Keep OpenLogi's icon in the taskbar notification area. Takes effect the next time the background agent starts.": "Значок OpenLogi останется в области уведомлений панели задач. Вступает в силу при следующем запуске фонового агента." +"Download from GitHub": "Скачать с GitHub" "Assets": "Ресурсы" "Automatically download device images": "Автоматически загружать изображения устройств" "Fetch device renders from assets.openlogi.org when a device connects. When off, OpenLogi makes no asset network requests; bundled art and the silhouette still show.": "Загружать изображения устройства с assets.openlogi.org при подключении. Когда выключено, OpenLogi не делает сетевых запросов за ресурсами; по-прежнему показываются встроенные изображения и силуэт." diff --git a/crates/openlogi-gui/locales/sv.yml b/crates/openlogi-gui/locales/sv.yml index cc6cf27f..209c4b84 100644 --- a/crates/openlogi-gui/locales/sv.yml +++ b/crates/openlogi-gui/locales/sv.yml @@ -106,6 +106,9 @@ _version: 1 "Check once per launch for a new version (query only — no automatic download).": "Sök efter en ny version en gång per start (endast förfrågan — ingen automatisk hämtning)." "Show in menu bar": "Visa i menyraden" "Keep OpenLogi's icon in the menu bar. When off, it stays in the Dock instead.": "Behåll OpenLogis ikon i menyraden. När av stannar den i Dock i stället." +"Show in the notification area": "Visa i meddelandefältet" +"Keep OpenLogi's icon in the taskbar notification area. Takes effect the next time the background agent starts.": "Behåller OpenLogis ikon i aktivitetsfältets meddelandefält. Träder i kraft nästa gång bakgrundsagenten startar." +"Download from GitHub": "Ladda ner från GitHub" "Assets": "Resurser" "Automatically download device images": "Hämta enhetsbilder automatiskt" "Fetch device renders from assets.openlogi.org when a device connects. When off, OpenLogi makes no asset network requests; bundled art and the silhouette still show.": "Hämta enhetsbilder från assets.openlogi.org när en enhet ansluts. När det är avstängt gör OpenLogi inga nätverksanrop efter resurser; medföljande bilder och siluetten visas ändå." diff --git a/crates/openlogi-gui/locales/zh-CN.yml b/crates/openlogi-gui/locales/zh-CN.yml index aa217b46..077e72a1 100644 --- a/crates/openlogi-gui/locales/zh-CN.yml +++ b/crates/openlogi-gui/locales/zh-CN.yml @@ -106,6 +106,9 @@ _version: 1 "Check once per launch for a new version (query only — no automatic download).": "每次启动检查一次新版本(仅查询,不自动下载)。" "Show in menu bar": "显示在菜单栏" "Keep OpenLogi's icon in the menu bar. When off, it stays in the Dock instead.": "在菜单栏保留 OpenLogi 图标;关闭后改为停留在程序坞中。" +"Show in the notification area": "在通知区域显示" +"Keep OpenLogi's icon in the taskbar notification area. Takes effect the next time the background agent starts.": "在任务栏通知区域保留 OpenLogi 图标。将在后台代理下次启动时生效。" +"Download from GitHub": "从 GitHub 下载" "Assets": "资源" "Automatically download device images": "自动下载设备图片" "Fetch device renders from assets.openlogi.org when a device connects. When off, OpenLogi makes no asset network requests; bundled art and the silhouette still show.": "设备连接时从 assets.openlogi.org 获取设备渲染图。关闭后,OpenLogi 不发起任何资源网络请求,仍会显示内置图片与剪影。" diff --git a/crates/openlogi-gui/locales/zh-HK.yml b/crates/openlogi-gui/locales/zh-HK.yml index e11ad21a..f4e7ef66 100644 --- a/crates/openlogi-gui/locales/zh-HK.yml +++ b/crates/openlogi-gui/locales/zh-HK.yml @@ -106,6 +106,9 @@ _version: 1 "Check once per launch for a new version (query only — no automatic download).": "每次啟動檢查一次新版本(僅查詢,不自動下載)。" "Show in menu bar": "顯示在選單列" "Keep OpenLogi's icon in the menu bar. When off, it stays in the Dock instead.": "在選單列保留 OpenLogi 圖示;關閉後改為停留在 Dock 中。" +"Show in the notification area": "在通知區域顯示" +"Keep OpenLogi's icon in the taskbar notification area. Takes effect the next time the background agent starts.": "在工作列通知區域保留 OpenLogi 圖示。將於背景代理程式下次啟動時生效。" +"Download from GitHub": "從 GitHub 下載" "Assets": "資源" "Automatically download device images": "自動下載裝置圖片" "Fetch device renders from assets.openlogi.org when a device connects. When off, OpenLogi makes no asset network requests; bundled art and the silhouette still show.": "裝置連線時從 assets.openlogi.org 取得裝置渲染圖。關閉後,OpenLogi 不會發出任何資源網路請求,仍會顯示內建圖片與剪影。" diff --git a/crates/openlogi-gui/locales/zh-TW.yml b/crates/openlogi-gui/locales/zh-TW.yml index 5d51239b..f8a84ea2 100644 --- a/crates/openlogi-gui/locales/zh-TW.yml +++ b/crates/openlogi-gui/locales/zh-TW.yml @@ -106,6 +106,9 @@ _version: 1 "Check once per launch for a new version (query only — no automatic download).": "每次啟動時檢查一次新版本(僅查詢,不自動下載)。" "Show in menu bar": "顯示在選單列" "Keep OpenLogi's icon in the menu bar. When off, it stays in the Dock instead.": "在選單列保留 OpenLogi 圖示;關閉後改為停留在 Dock 中。" +"Show in the notification area": "在通知區域顯示" +"Keep OpenLogi's icon in the taskbar notification area. Takes effect the next time the background agent starts.": "在工作列通知區域保留 OpenLogi 圖示。將於背景代理程式下次啟動時生效。" +"Download from GitHub": "從 GitHub 下載" "Assets": "資源" "Automatically download device images": "自動下載裝置圖片" "Fetch device renders from assets.openlogi.org when a device connects. When off, OpenLogi makes no asset network requests; bundled art and the silhouette still show.": "裝置連線時從 assets.openlogi.org 取得裝置算圖。關閉後,OpenLogi 不會發出任何資源網路請求,仍會顯示內建圖片與剪影。" From 58555d299345e1295fb66e710d017ae0e01807a3 Mon Sep 17 00:00:00 2001 From: David Budnick Date: Mon, 6 Jul 2026 00:54:28 -0500 Subject: [PATCH 8/9] xtask: gate Windows manifest entries behind --include-windows Review feedback (Greptile P1): the release workflow R2 upload step still excludes the zip/msi from the releases/ prefix, and this PR cannot modify workflow files - so unconditional Windows entries would publish URLs the bucket does not serve on the next tag. Off by default, the manifest stays byte-identical dmg-only until the same release.yml change that drops the exclusions also passes --include-windows: the two halves can never disagree. Co-Authored-By: Claude Opus 4.8 (1M context) --- xtask/src/commands/release/latest_json.rs | 44 +++++++++++++++++++---- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/xtask/src/commands/release/latest_json.rs b/xtask/src/commands/release/latest_json.rs index f98e0cf5..0d0f0d7c 100644 --- a/xtask/src/commands/release/latest_json.rs +++ b/xtask/src/commands/release/latest_json.rs @@ -28,6 +28,12 @@ pub(crate) struct Args { /// Public update base URL, for example `https://updates.openlogi.org`. #[arg(long, env = "OPENLOGI_UPDATE_BASE_URL")] base_url: String, + /// Also emit the per-arch Windows `.msi`/`.zip` entries. Off by default so + /// the manifest can never reference objects the release workflow's R2 + /// upload step doesn't ship: flip this in the same workflow change that + /// stops excluding the zip/msi from the `releases/` prefix (#347 PR 4). + #[arg(long)] + include_windows: bool, } #[derive(Serialize)] @@ -74,7 +80,7 @@ pub(crate) fn run(args: &Args) -> Result<()> { args.base_url.trim_end_matches('/'), args.tag ); - let assets = collect_assets(&args.dist, &release_base)?; + let assets = collect_assets(&args.dist, &release_base, args.include_windows)?; // The DMGs are the publish gate's guaranteed artifact set; the Windows // legs are best-effort per arch (a failed leg publishes without them), so // their absence must not sink the whole manifest. @@ -109,7 +115,7 @@ pub(crate) fn run(args: &Args) -> Result<()> { .with_context(|| format!("could not write manifest to {}", args.output.display())) } -fn collect_assets(dist: &Path, release_base: &str) -> Result> { +fn collect_assets(dist: &Path, release_base: &str, include_windows: bool) -> Result> { let mut assets = Vec::new(); for entry in fs_err::read_dir(dist) .with_context(|| format!("could not read artifact directory {}", dist.display()))? @@ -121,6 +127,11 @@ fn collect_assets(dist: &Path, release_base: &str) -> Result> { let Some(classified) = classify(name) else { continue; }; + // Gated so the manifest and the R2 upload step can never disagree + // about the Windows artifacts — see the `include_windows` arg doc. + if classified.os == "windows" && !include_windows { + continue; + } let signature_name = format!("{name}.minisig"); let signature_path = dist.join(&signature_name); if !signature_path.is_file() { @@ -203,7 +214,9 @@ mod tests { let dist = tempfile::tempdir().unwrap(); fs_err::write(dist.path().join("OpenLogi-v1.2.3-macos-arm64.dmg"), b"dmg").unwrap(); - assert!(collect_assets(dist.path(), "https://updates.example/releases/v1.2.3").is_err()); + assert!( + collect_assets(dist.path(), "https://updates.example/releases/v1.2.3", false).is_err() + ); } #[test] @@ -217,7 +230,7 @@ mod tests { .unwrap(); let assets = - collect_assets(dist.path(), "https://updates.example/releases/v1.2.3").unwrap(); + collect_assets(dist.path(), "https://updates.example/releases/v1.2.3", false).unwrap(); assert_eq!( assets[0].signature_url, @@ -225,6 +238,25 @@ mod tests { ); } + #[test] + fn collect_assets_skips_windows_artifacts_unless_opted_in() { + // Off by default: the manifest must never reference Windows objects + // the release workflow's R2 upload step doesn't ship. + let dist = tempfile::tempdir().unwrap(); + for name in [ + "OpenLogi-v1.2.3-windows-x86_64.msi", + "OpenLogi-v1.2.3-windows-x86_64.zip", + ] { + fs_err::write(dist.path().join(name), b"artifact").unwrap(); + fs_err::write(dist.path().join(format!("{name}.minisig")), b"signature").unwrap(); + } + + let assets = + collect_assets(dist.path(), "https://updates.example/releases/v1.2.3", false).unwrap(); + + assert!(assets.is_empty()); + } + #[test] fn collect_assets_includes_windows_msi_and_zip_per_arch() { let dist = tempfile::tempdir().unwrap(); @@ -238,7 +270,7 @@ mod tests { } let assets = - collect_assets(dist.path(), "https://updates.example/releases/v1.2.3").unwrap(); + collect_assets(dist.path(), "https://updates.example/releases/v1.2.3", true).unwrap(); assert_eq!(assets.len(), 3); assert!(assets.iter().all(|a| a.os == "windows")); @@ -269,7 +301,7 @@ mod tests { } let assets = - collect_assets(dist.path(), "https://updates.example/releases/v1.2.3").unwrap(); + collect_assets(dist.path(), "https://updates.example/releases/v1.2.3", true).unwrap(); assert!(assets.is_empty()); } From 028f62bb35addb2ee7aaf5fcdde7496d8f75a269 Mon Sep 17 00:00:00 2001 From: David Budnick Date: Mon, 6 Jul 2026 00:59:43 -0500 Subject: [PATCH 9/9] style: rustfmt --- xtask/src/commands/release/latest_json.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/xtask/src/commands/release/latest_json.rs b/xtask/src/commands/release/latest_json.rs index 0d0f0d7c..7fb4dad4 100644 --- a/xtask/src/commands/release/latest_json.rs +++ b/xtask/src/commands/release/latest_json.rs @@ -215,7 +215,12 @@ mod tests { fs_err::write(dist.path().join("OpenLogi-v1.2.3-macos-arm64.dmg"), b"dmg").unwrap(); assert!( - collect_assets(dist.path(), "https://updates.example/releases/v1.2.3", false).is_err() + collect_assets( + dist.path(), + "https://updates.example/releases/v1.2.3", + false + ) + .is_err() ); } @@ -229,8 +234,12 @@ mod tests { ) .unwrap(); - let assets = - collect_assets(dist.path(), "https://updates.example/releases/v1.2.3", false).unwrap(); + let assets = collect_assets( + dist.path(), + "https://updates.example/releases/v1.2.3", + false, + ) + .unwrap(); assert_eq!( assets[0].signature_url, @@ -251,8 +260,12 @@ mod tests { fs_err::write(dist.path().join(format!("{name}.minisig")), b"signature").unwrap(); } - let assets = - collect_assets(dist.path(), "https://updates.example/releases/v1.2.3", false).unwrap(); + let assets = collect_assets( + dist.path(), + "https://updates.example/releases/v1.2.3", + false, + ) + .unwrap(); assert!(assets.is_empty()); }