diff --git a/apps/desktop/src-tauri/icons/128x128.png b/apps/desktop/src-tauri/icons/128x128.png
new file mode 100644
index 0000000..5b209ad
Binary files /dev/null and b/apps/desktop/src-tauri/icons/128x128.png differ
diff --git a/apps/desktop/src-tauri/icons/128x128@2x.png b/apps/desktop/src-tauri/icons/128x128@2x.png
new file mode 100644
index 0000000..6cae2ba
Binary files /dev/null and b/apps/desktop/src-tauri/icons/128x128@2x.png differ
diff --git a/apps/desktop/src-tauri/icons/32x32.png b/apps/desktop/src-tauri/icons/32x32.png
new file mode 100644
index 0000000..fd892fc
Binary files /dev/null and b/apps/desktop/src-tauri/icons/32x32.png differ
diff --git a/apps/desktop/src-tauri/icons/README.md b/apps/desktop/src-tauri/icons/README.md
index c942e81..c3296e9 100644
--- a/apps/desktop/src-tauri/icons/README.md
+++ b/apps/desktop/src-tauri/icons/README.md
@@ -1,5 +1,14 @@
-# Bootstrap icon
+# BLCVoice application icon
-`icon.png` is a temporary, non-branding build asset required by Tauri's compile-time context generation.
+`app-icon.svg` is the canonical BLCVoice application icon source.
-It is **not** the final BLCVoice logo or visual identity. Product branding and the complete platform icon set will be designed and reviewed separately before distributable bundles are enabled.
+The generated platform assets are committed so Tauri bundle builds are deterministic and do not require icon-generation tooling:
+
+- `32x32.png` — Linux small icon
+- `128x128.png` — Linux standard icon
+- `128x128@2x.png` — Linux HiDPI icon
+- `icon.icns` — macOS
+- `icon.ico` — Windows
+- `icon.png` — high-resolution source/fallback asset used by local tooling
+
+The mark intentionally reuses BLCVoice's waveform visual language: a dark rounded-square field, violet voice bars, and a small local/private-state accent. Changes to the visual identity should start from `app-icon.svg` and regenerate the platform set rather than editing generated binaries independently.
diff --git a/apps/desktop/src-tauri/icons/app-icon.svg b/apps/desktop/src-tauri/icons/app-icon.svg
new file mode 100644
index 0000000..b62a063
--- /dev/null
+++ b/apps/desktop/src-tauri/icons/app-icon.svg
@@ -0,0 +1,31 @@
+
diff --git a/apps/desktop/src-tauri/icons/icon.icns b/apps/desktop/src-tauri/icons/icon.icns
new file mode 100644
index 0000000..b5131b8
Binary files /dev/null and b/apps/desktop/src-tauri/icons/icon.icns differ
diff --git a/apps/desktop/src-tauri/icons/icon.ico b/apps/desktop/src-tauri/icons/icon.ico
index d950879..d968c11 100644
Binary files a/apps/desktop/src-tauri/icons/icon.ico and b/apps/desktop/src-tauri/icons/icon.ico differ
diff --git a/apps/desktop/src-tauri/icons/icon.png b/apps/desktop/src-tauri/icons/icon.png
index 7e05488..8f8b7f0 100644
Binary files a/apps/desktop/src-tauri/icons/icon.png and b/apps/desktop/src-tauri/icons/icon.png differ
diff --git a/apps/desktop/src-tauri/src/shortcut.rs b/apps/desktop/src-tauri/src/shortcut.rs
index 147c1c0..1002f55 100644
--- a/apps/desktop/src-tauri/src/shortcut.rs
+++ b/apps/desktop/src-tauri/src/shortcut.rs
@@ -28,6 +28,7 @@ pub const SHORTCUT_DECISION_EVENT: &str = "blcvoice://shortcut-decision";
enum ShortcutRegistrationState {
Pending,
Registering,
+ #[cfg(any(target_os = "linux", test))]
ConfigurationRequired,
Registered,
Failed,
@@ -39,6 +40,7 @@ impl ShortcutRegistrationState {
match self {
Self::Pending => "pending",
Self::Registering => "registering",
+ #[cfg(any(target_os = "linux", test))]
Self::ConfigurationRequired => "configuration required",
Self::Registered => "registered",
Self::Failed => "failed",
@@ -102,6 +104,7 @@ impl ShortcutService {
}
}
+ #[cfg(any(target_os = "linux", test))]
fn mark_configuration_required(&self) {
let mut state = self.lock_state();
if state.backend.is_some() {
@@ -380,6 +383,7 @@ fn shortcut_capability_for(state: &ShortcutServiceState) -> ShortcutCapabilityDt
DesktopPlatform::Windows | DesktopPlatform::MacOs | DesktopPlatform::Other => None,
};
let registration_state = match state.registration_state {
+ #[cfg(any(target_os = "linux", test))]
ShortcutRegistrationState::ConfigurationRequired => {
"configuration required · KDE shortcut settings opened".to_owned()
}
diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json
index 498e6b3..6175589 100644
--- a/apps/desktop/src-tauri/tauri.conf.json
+++ b/apps/desktop/src-tauri/tauri.conf.json
@@ -45,8 +45,12 @@
"bundle": {
"active": true,
"targets": "all",
+ "category": "Productivity",
"icon": [
- "icons/icon.png",
+ "icons/32x32.png",
+ "icons/128x128.png",
+ "icons/128x128@2x.png",
+ "icons/icon.icns",
"icons/icon.ico"
],
"shortDescription": "Private local desktop voice dictation",
diff --git a/crates/blcvoice-insertion-eis/src/lib.rs b/crates/blcvoice-insertion-eis/src/lib.rs
index 0196d9c..6ba7397 100644
--- a/crates/blcvoice-insertion-eis/src/lib.rs
+++ b/crates/blcvoice-insertion-eis/src/lib.rs
@@ -11,6 +11,7 @@ const EI_TEXT_MAX_UTF8_BYTES: usize = 254;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum SubmissionMode {
Text,
+ #[cfg(any(target_os = "linux", test))]
ClipboardKeyboard,
}
@@ -34,6 +35,7 @@ impl WaylandEisOptions {
const fn capability_for_mode(mode: SubmissionMode) -> InsertionCapability {
let backend = match mode {
SubmissionMode::Text => InsertionBackend::XdgRemoteDesktopEis,
+ #[cfg(any(target_os = "linux", test))]
SubmissionMode::ClipboardKeyboard => InsertionBackend::XdgRemoteDesktopEisClipboard,
};
InsertionCapability::new(backend, InsertionAuthorization::XdgRemoteDesktop)
diff --git a/scripts/install-local-linux.sh b/scripts/install-local-linux.sh
new file mode 100644
index 0000000..88e9881
--- /dev/null
+++ b/scripts/install-local-linux.sh
@@ -0,0 +1,65 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+if [[ "$(uname -s)" != "Linux" ]]; then
+ echo "BLCVoice local installer only supports Linux." >&2
+ exit 1
+fi
+
+repo_root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
+if [[ -z "$repo_root" ]]; then
+ echo "Run this script from inside the BLCVoice repository." >&2
+ exit 1
+fi
+
+cd "$repo_root"
+
+cargo build --release -p blcvoice-desktop
+
+binary_src="$repo_root/target/release/blcvoice"
+binary_dst="$HOME/.local/bin/blcvoice"
+applications_dir="$HOME/.local/share/applications"
+icons_root="$HOME/.local/share/icons/hicolor"
+icon_src="$repo_root/apps/desktop/src-tauri/icons"
+desktop_file="$applications_dir/blcvoice.desktop"
+
+install -Dm755 "$binary_src" "$binary_dst"
+install -Dm644 "$icon_src/32x32.png" "$icons_root/32x32/apps/blcvoice.png"
+install -Dm644 "$icon_src/128x128.png" "$icons_root/128x128/apps/blcvoice.png"
+install -Dm644 "$icon_src/128x128@2x.png" "$icons_root/256x256/apps/blcvoice.png"
+
+mkdir -p "$applications_dir"
+cat > "$desktop_file" </dev/null 2>&1; then
+ update-desktop-database "$applications_dir" >/dev/null 2>&1 || true
+fi
+if command -v gtk-update-icon-cache >/dev/null 2>&1; then
+ gtk-update-icon-cache -f -t "$HOME/.local/share/icons/hicolor" >/dev/null 2>&1 || true
+fi
+if command -v kbuildsycoca6 >/dev/null 2>&1; then
+ kbuildsycoca6 >/dev/null 2>&1 || true
+elif command -v kbuildsycoca5 >/dev/null 2>&1; then
+ kbuildsycoca5 >/dev/null 2>&1 || true
+fi
+
+echo "BLCVoice installed for this user."
+echo "Launcher: $desktop_file"
+echo "Binary: $binary_dst"
+echo "Open BLCVoice from your application launcher instead of Konsole to validate desktop identity."