platform: fix macOS data path + add get_os_info + frontend osInfo helper
CROSS-PLATFORM AUDIT:
- Linux x86_64 (Fedora 43, KDE Wayland): HIGH confidence (the dev target)
- Linux other distros / X11: MEDIUM (tested patterns, untested distros)
- Windows 10/11: LOW — theoretically supported via Tauri + CPAL + whisper.cpp
+ tauri-plugin-global-shortcut (the custom evdev hotkey is no-op on
Windows); has had zero hands-on testing
- macOS Apple Silicon / Intel: LOW — same; macOS Info.plist needs
NSMicrophoneUsageDescription for the bundled app, and the path bug
fixed in this commit
REAL BUG FIXED — macOS app_data_dir was Unix-style:
crates/storage/src/file_storage.rs::app_data_dir() previously fell into
the "Unix" branch on macOS and wrote to ~/.kon/, which violates Apple
guidelines and confuses install/uninstall tooling. Now correctly:
- Windows: %LOCALAPPDATA%/kon (unchanged)
- macOS: ~/Library/Application Support/Kon/
- Linux: $XDG_DATA_HOME/kon or ~/.local/share/kon (XDG Base Directory),
with legacy ~/.kon/ fallback so existing installs keep working
- Other Unix: ~/.kon/ (unchanged)
OS DETECTION LAYER:
- New get_os_info Tauri command in commands/diagnostics.rs returns:
{os, arch, family, usesCmd, isWayland, customHotkeyBackend,
primaryModifierLabel}
- New src/lib/utils/osInfo.js helper:
- async loadOsInfo() warms a cache, called once at root layout mount
- then isMac() / isWindows() / isLinux() / modKeyLabel() / isWayland()
are synchronous
- browser-preview fallback reads navigator.platform
- Lets components localise hotkey labels (Cmd vs Ctrl), file-picker
copy ("Open Finder" vs "Open Explorer"), etc, without recomputing in
every place.
cargo check -p kon-storage clean. Updated HANDOVER-2026-04-17.md with
a per-platform confidence table.
This commit is contained in:
100
src/lib/utils/osInfo.js
Normal file
100
src/lib/utils/osInfo.js
Normal file
@@ -0,0 +1,100 @@
|
||||
// Cross-platform OS info for the Svelte frontend. Single async fetch,
|
||||
// cached in memory, exposes synchronous helpers after first await.
|
||||
//
|
||||
// Backed by the `get_os_info` Tauri command (commands/diagnostics.rs).
|
||||
// In browser-preview mode (no Tauri runtime), falls back to a sensible
|
||||
// default based on navigator.platform.
|
||||
//
|
||||
// Usage:
|
||||
// import { loadOsInfo, osInfo, modKeyLabel, isMac } from '$lib/utils/osInfo.js';
|
||||
// await loadOsInfo(); // call once at app startup
|
||||
// modKeyLabel(); // "Cmd" on macOS, "Ctrl" elsewhere
|
||||
// if (isMac()) { ... }
|
||||
//
|
||||
// The store version (osInfo) is reactive — Svelte components can read it
|
||||
// directly with `$osInfo` once Svelte 5 runes are everywhere, or via
|
||||
// destructuring on a non-reactive read.
|
||||
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { hasTauriRuntime } from './runtime.js';
|
||||
|
||||
let cached = null;
|
||||
|
||||
const FALLBACK_BROWSER_INFO = {
|
||||
os: detectBrowserOs(),
|
||||
arch: 'unknown',
|
||||
family: detectBrowserFamily(),
|
||||
usesCmd: detectBrowserOs() === 'macos',
|
||||
isWayland: false,
|
||||
customHotkeyBackend: false,
|
||||
primaryModifierLabel: detectBrowserOs() === 'macos' ? 'Cmd' : 'Ctrl',
|
||||
};
|
||||
|
||||
function detectBrowserOs() {
|
||||
if (typeof navigator === 'undefined') return 'unknown';
|
||||
const ua = (navigator.userAgent || '').toLowerCase();
|
||||
const platform = (navigator.platform || '').toLowerCase();
|
||||
if (platform.includes('mac') || ua.includes('mac os')) return 'macos';
|
||||
if (platform.includes('win') || ua.includes('windows')) return 'windows';
|
||||
if (platform.includes('linux') || ua.includes('linux')) return 'linux';
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
function detectBrowserFamily() {
|
||||
switch (detectBrowserOs()) {
|
||||
case 'macos': return 'macOS';
|
||||
case 'windows': return 'Windows';
|
||||
case 'linux': return 'Linux';
|
||||
default: return 'Unknown';
|
||||
}
|
||||
}
|
||||
|
||||
/** Fetch OS info from the Tauri backend. Idempotent — call multiple times,
|
||||
* only the first does the round-trip. Browser-preview mode returns the
|
||||
* navigator-detected fallback. */
|
||||
export async function loadOsInfo() {
|
||||
if (cached) return cached;
|
||||
if (!hasTauriRuntime()) {
|
||||
cached = FALLBACK_BROWSER_INFO;
|
||||
return cached;
|
||||
}
|
||||
try {
|
||||
cached = await invoke('get_os_info');
|
||||
} catch (err) {
|
||||
console.warn('loadOsInfo: get_os_info failed, using browser fallback', err);
|
||||
cached = FALLBACK_BROWSER_INFO;
|
||||
}
|
||||
return cached;
|
||||
}
|
||||
|
||||
/** Synchronous accessor. Returns null if loadOsInfo has not been awaited
|
||||
* yet. Components that need the value at first render should await
|
||||
* loadOsInfo() in onMount before reading. */
|
||||
export function osInfo() {
|
||||
return cached;
|
||||
}
|
||||
|
||||
export function isMac() {
|
||||
return cached?.os === 'macos';
|
||||
}
|
||||
|
||||
export function isWindows() {
|
||||
return cached?.os === 'windows';
|
||||
}
|
||||
|
||||
export function isLinux() {
|
||||
return cached?.os === 'linux';
|
||||
}
|
||||
|
||||
/** Localised label for the primary keyboard modifier. "Cmd" on macOS,
|
||||
* "Ctrl" everywhere else. Use in hotkey display strings:
|
||||
* `${modKeyLabel()}+Shift+R` */
|
||||
export function modKeyLabel() {
|
||||
return cached?.primaryModifierLabel ?? 'Ctrl';
|
||||
}
|
||||
|
||||
/** True if the current Linux session is Wayland. False on Windows/macOS.
|
||||
* Useful for Settings → Audio "PipeWire detected" status display. */
|
||||
export function isWayland() {
|
||||
return !!cached?.isWayland;
|
||||
}
|
||||
Reference in New Issue
Block a user