diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index c763641..ada4b8f 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,9 +1,9 @@
name: Build & Release
-# Builds the .deb and AppImage on the CI runner so artifacts are produced
+# Builds the .deb on the CI runner so artifacts are produced
# without compiling on a local machine:
# - every push to main / pull request -> packages uploaded as workflow artifacts
-# - pushing a `v*` tag -> a GitHub Release with the .deb + AppImage
+# - pushing a `v*` tag -> a GitHub Release with the .deb
on:
push:
branches: [main]
@@ -40,7 +40,7 @@ jobs:
run: cargo test --release
package:
- name: Build packages
+ name: Build .deb
needs: test
runs-on: ubuntu-latest
steps:
@@ -71,31 +71,19 @@ jobs:
echo "short_sha=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT"
echo "Building version $VERSION"
- - name: Fetch appimagetool
- run: |
- curl -fsSL -o packaging/appimage/appimagetool \
- https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage
- chmod +x packaging/appimage/appimagetool
-
- name: Build .deb
run: bash packaging/deb/build-deb.sh "${{ steps.ver.outputs.version }}"
- - name: Build AppImage
- # appimagetool is invoked with --appimage-extract-and-run by the build
- # script, so no FUSE is required on the runner.
- run: bash packaging/appimage/build-appimage.sh "${{ steps.ver.outputs.version }}"
-
- name: Collect artifacts
run: |
mkdir -p dist
cp packaging/deb/*.deb dist/
- cp packaging/appimage/*.AppImage dist/
ls -la dist
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
- name: vietc-packages-${{ steps.ver.outputs.version }}-${{ steps.ver.outputs.short_sha }}
+ name: vietc-deb-${{ steps.ver.outputs.version }}-${{ steps.ver.outputs.short_sha }}
path: dist/*
if-no-files-found: error
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cd390e0..c337cee 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,30 @@
# Changelog
+## v0.1.6 (2026-06-29)
+
+### uinput-First Injection
+
+- **Injection priority reversed**: uinput (`/dev/uinput`) is now the primary injection backend on X11, with X11 XTest as fallback. uinput sends evdev keycodes that route correctly through libinput — no X11 keycode offset needed.
+- **X11 XTest keycode fix**: X11 injector was sending evdev keycodes directly to `XTestFakeKeyEvent`, which expects X11 keycodes (evdev + 8). Backspace sent keycode 14 (evdev) = X11 keycode 14 = "5" key. Fixed by adding +8 offset in all `send_keycode` paths.
+- **`paste_via_clipboard()` backspace fixed**: was hardcoded to X11 keycode 14 (actually "5"), now uses evdev 14 + 8 = 22 (correct X11 backspace).
+
+### Window-Switch Detection
+
+- **Active window ID verified on every keystroke**: removed the `gap > 100ms` guard — the daemon now polls `xdotool`/`xprop` directly for every character keypress. This catches window switches that complete in under 100ms, preventing old engine buffer from leaking into the new window.
+
+### Input Method
+
+- **Telex disabled in tray**: greyed out with "(next version)" label and `Disposition::Informative`. Only VNI is functional.
+- **Default input method changed** from `"telex"` to `"vni"` in config fallback.
+
+### Packaging
+
+- **Flatpak and AppImage removed**: only `.deb` packaging is maintained. `packaging/flatpak/` and `packaging/appimage/` directories deleted.
+- **Postinst improvements**: removes stale `/usr/local/bin/vietc*` binaries, deletes old `~/.config/vietc/config.toml` + `overrides.toml` + `.first-launch-done`, shows logout popup (notify-send + zenity).
+- **CI workflow**: only `.deb` artifact collected (no AppImage).
+
+---
+
## v0.1.5 (2026-06-29)
### Window-Switch Engine Reset
diff --git a/Makefile b/Makefile
index c488cf8..f764809 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-.PHONY: build build-x11 build-wayland build-all build-ui test test-cli run run-x11 run-wayland clean install install-x11 install-wayland install-ui install-config appimage deb fmt lint tree
+.PHONY: build build-x11 build-wayland build-all build-ui test test-cli run run-x11 run-wayland clean install install-x11 install-wayland install-ui install-config deb fmt lint tree
# Build core crates
build:
@@ -76,26 +76,15 @@ install-config:
cp vietc.toml ~/.config/vietc/config.toml
@echo "Config installed to ~/.config/vietc/config.toml"
-# Build .deb package (requires dpkg-deb)
+# Build .deb package
deb:
VERSION=$$(grep '^version' engine/Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/') && \
bash packaging/deb/build-deb.sh "$$VERSION"
-# Build AppImage (requires appimagetool or linuxdeploy)
-appimage:
- VERSION=$$(grep '^version' engine/Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/') && \
- bash packaging/appimage/build-appimage.sh "$$VERSION"
-
-# Build Debian package
-deb:
- VERSION=$$(grep '^version' engine/Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/') && \
- bash packaging/build-deb.sh "$$VERSION"
-
# Clean build artifacts
clean:
cargo clean
cd ui && cargo clean
- rm -rf packaging/appimage/AppDir packaging/appimage/*.AppImage
# Format code
fmt:
@@ -107,10 +96,6 @@ lint:
cargo clippy -- -D warnings
cd ui && cargo clippy -- -D warnings
-# Flatpak build
-flatpak:
- cd packaging/flatpak && bash build-flatpak.sh
-
# Show project structure
tree:
@find . -type f \( -name "*.rs" -o -name "*.toml" \) | grep -v target | sort
diff --git a/README.md b/README.md
index 5b30f81..f7183af 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
-
+
@@ -47,11 +47,11 @@ Physical Keyboard
│ Stage 1: KEY CAPTURE │
│ │
│ evdev: /dev/input/event* grabs keyboard (primary, reliable) │
-│ X11: XRecord passive monitoring (fallback) │
+│ X11: XRecord passive monitoring (fallback) │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────────┐ │
-│ │ evdev grab │ │ X11Capture │ │ FocusIn/FocusOut │ │
-│ │ (libevdev) │ │ (XRecord) │ │ detection │ │
+│ │ evdev grab │ │ X11Capture │ │ Window switch │ │
+│ │ (libevdev) │ │ (XRecord) │ │ detection (250ms)│ │
│ └─────────────┘ └──────────────┘ └──────────────────┘ │
└──────────────────────────────────────────────────────────────┘
│
@@ -63,7 +63,7 @@ Physical Keyboard
│ Ctrl+Space → toggle Vietnamese ON/OFF │
│ Backspace → replay_backspace() │
│ Characters → replay_and_inject(ch) │
-│ VNI/Telex control keys → consume when no match │
+│ VNI control keys → consume when no match │
└──────────────────────────────────────────────────────────────┘
│
▼
@@ -81,13 +81,15 @@ Physical Keyboard
┌──────────────────────────────────────────────────────────────┐
│ Stage 4: KEY INJECTION │
│ │
+│ Primary: uinput injection (evdev keycodes, correct on all │
+│ display servers — routed through libinput on modern X11) │
│ ASCII: direct Linux keycodes via /dev/uinput │
- │ Backspace: Linux keycode 14 via uinput │
- │ Vietnamese Unicode: clipboard paste + trailing ASCII via │
- │ uinput (split only at whitespace/punctuation boundary) │
- │ uinput Ctrl+V via /dev/uinput (no X11 dependency) │
+│ Backspace: Linux keycode 14 via uinput │
+│ Vietnamese Unicode: clipboard paste + trailing ASCII via │
+│ uinput (split only at whitespace/punctuation boundary) │
+│ uinput Ctrl+V via /dev/uinput (no X11 dependency) │
│ │
-│ Fallback: vietc-uinputd Unix socket daemon (privileged) │
+│ Fallback: X11 XTest injection (X11 keycodes = evdev + 8) │
└──────────────────────────────────────────────────────────────┘
│
▼
@@ -133,14 +135,14 @@ vietc/
│ ├── engine.rs # Orchestrator + replay_events(), replay_events_to_commands()
│ ├── event.rs # Event Sourcing: InputEvent, EventStore, Command
│ ├── bamboo.rs # Bamboo engine: transformation model, composition, tone placement
-│ ├── input_method.rs # Telex/VNI rule definitions
+│ ├── input_method.rs # VNI rule definitions
│ └── spelling.rs # Vietnamese syllable validation
│
├── protocol/ # Keyboard capture & injection
│ ├── inject.rs # KeyInjector trait
│ ├── x11_capture.rs # XRecord keyboard capture via C helper
-│ ├── x11_inject.rs # XTest injection + direct clipboard
-│ ├── uinput_monitor.rs # /dev/uinput injection for ASCII + Unicode
+│ ├── x11_inject.rs # XTest injection (fallback)
+│ ├── uinput_monitor.rs # /dev/uinput injection (primary)
│ ├── uinput_client.rs # Unix socket client for vietc-uinputd
│ └── wayland_im.rs # Wayland IM protocol
│
@@ -157,7 +159,7 @@ vietc/
│ └── main.rs # Tray + daemon launcher
│
├── cli/ # Interactive test harness
-├── packaging/ # AppImage + deb build scripts
+├── packaging/ # .deb packaging scripts
└── vietc.toml # Default configuration
```
@@ -166,12 +168,12 @@ vietc/
```
┌─────────────────────────────────────────────────────────────┐
│ vietc-tray │
-│ (System tray icon, daemon launcher, password prompt) │
+│ (System tray icon, daemon launcher) │
└───────────────────────┬─────────────────────────────────────┘
│ starts
▼
┌─────────────────────────────────────────────────────────────┐
-│ vietc (daemon) │
+│ vietc-daemon │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ Config │ │ App State │ │ Display │ │
@@ -183,7 +185,7 @@ vietc/
│ ┌──────▼──────┐ │
│ │ Event Loop │ │
│ │ │ │
-│ │ X11: grab │ │
+│ │ evdev: grab │ │
│ │ keyboard │ │
│ │ │ │
│ │ Process │ │
@@ -198,12 +200,12 @@ vietc/
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ vietc-engine │ │
-│ │ TelexEngine / VniEngine / EnglishDict / Spelling │ │
+│ │ VniEngine / EnglishDict / Spelling │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ vietc-protocol │ │
-│ │ X11Capture / X11Injector / UinputInjector / Wayland │ │
+│ │ UinputInjector / X11Injector / X11Capture / Wayland │ │
│ └────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
@@ -212,24 +214,7 @@ vietc/
## Input Methods
-### Telex
-
-| Key | Result | Example |
-|-----|--------|---------|
-| `aa` | â | `tan` → `tân` |
-| `aw` | ă | `tan` → `tăn` |
-| `ee` | ê | `men` → `mên` |
-| `oo` | ô | `to` → `tô` |
-| `ow` | ơ | `to` → `tơ` |
-| `uw` | ư | `tu` → `tư` |
-| `s` | á (sắc) | `as` → `á` |
-| `f` | à (huyền) | `af` → `à` |
-| `r` | ả (hỏi) | `ar` → `ả` |
-| `x` | ã (ngã) | `ax` → `ã` |
-| `j` | ạ (nặng) | `aj` → `ạ` |
-| `dd` | đ | `dd` → `đ` |
-
-### VNI
+### VNI (default, Telex coming in next version)
| Key | Result | Example |
|-----|--------|---------|
@@ -251,59 +236,51 @@ Flexible typing: type the full syllable, then add marks/tone keys at the end. Ex
| Feature | How It Works |
|---------|-------------|
-| **Direct Input** | No pre-edit buffer. Keystrokes instantly become text via uinput/XTest injection |
+| **Direct Input** | No pre-edit buffer. Keystrokes instantly become text via uinput injection |
| **Bamboo Engine** | Transformation model ported from bamboo-core — composition, marks, tones, flexible backtracking |
-| **Flexible Backtrack** | Type tone/modifier at end of syllable (`tranaf` → `trần`). Scans up to 5 chars backward |
+| **Flexible Backtrack** | Type tone/modifier at end of syllable (`tran5` → `trạn`). Scans up to 5 chars backward |
| **Smart Clusters** | `uo` → `ươ` with backtrack (`chuong7` → `chương`) |
| **Tone Placement** | Correct tone positioning for all Vietnamese diphthongs (io→gió, uâ→xuất, yê→nguyễn) |
| **Macro Expansion** | `ko` → `không`, `dc` → `được`, custom shortcuts |
| **Casing Preservation** | `Tieengs` → `Tiếng`, `TIEENGS` → `TIẾNG` |
| **App Memory** | Per-app Vietnamese/English state, saved to `overrides.toml` |
| **Hot Reload** | Config changes apply without restart (polls mtime every 1.5s) |
-| **Window-Switch Reset** | Alt+Tab clears engine state — no stale composition across apps, even when focus events are missed. Uses `xdotool` or `xprop` fallback to detect window changes |
+| **Window-Switch Reset** | Active window ID verified on every keystroke — Alt+Tab instantly clears engine state. No stale composition across apps |
| **CPU Priority** | Pins daemon to P-cores (0-3) + nice(-10) for low-latency input |
-| **Uinput Daemon** | Privileged `vietc-uinputd` for clean backspace injection (Unix socket, VMK-style) |
+| **Uinput Injection** | Uses `/dev/uinput` for reliable keyboard injection without X11 dependency. Falls back to XTest on systems without uinput access |
---
## Installation
-### Flatpak (recommended)
+### Debian Package (recommended)
-System tray icon + daemon. Find **"Viet+"** in your app menu to launch, or run from terminal.
+System tray icon + daemon + desktop entry. Requires user to be in the `input` group for keyboard capture.
```bash
# Install
-flatpak install --user --bundle VietPlus-x86_64.flatpak
+sudo dpkg -i vietc_0.1.6-1_amd64.deb
-# Launch via app menu, or:
-flatpak run io.github.vietc.VietPlus
-
-# Uninstall
-flatpak uninstall --user io.github.vietc.VietPlus
+# Log out and log back in (for input group membership to take effect)
+# Then launch "Viet+" from your application menu
```
-Includes daemon + CLI + system tray + uinput daemon. Sandboxed — no system libraries are touched.
+The post-install script will:
+- Kill any running tray/daemon processes
+- Remove stale binaries from `/usr/local/bin/`
+- Add your user to the `input` group
+- Prompt you to log out and back in
-### Build from Source (Flatpak)
+### Build from Source
```bash
git clone https://github.com/vndangkhoa/vietc.git
-cd vietc/packaging/flatpak
-bash build-flatpak.sh [version]
+cd vietc
+make deb
+sudo dpkg -i packaging/deb/vietc_0.1.6-1_amd64.deb
```
-Requires Flatpak runtimes: `org.gnome.Platform//50`, `org.gnome.Sdk//50`, `org.freedesktop.Sdk.Extension.rust-stable//25.08`
-
-```bash
-flatpak install --user flathub org.gnome.Platform//50
-flatpak install --user flathub org.gnome.Sdk//50
-flatpak install --user flathub org.freedesktop.Sdk.Extension.rust-stable//25.08
-```
-
-The Flatpak bundle includes the system tray and desktop menu entry. Find **"Viet+"** in your app launcher to start it, or search for it to uninstall. Warning-free build — no `#![allow()]` needed.
-
-See `packaging/flatpak/FLATPAK_BUILD.md` for detailed build instructions.
+Requires Rust toolchain, `pkg-config`, `libx11-dev`, `libxtst-dev`, `libevdev-dev`. See `packaging/deb/build-deb.sh` for details.
---
diff --git a/RELEASE_CHECKLIST.md b/RELEASE_CHECKLIST.md
index 488d892..d05b86a 100644
--- a/RELEASE_CHECKLIST.md
+++ b/RELEASE_CHECKLIST.md
@@ -3,7 +3,7 @@
## When to release
- New feature or bugfix that should be distributed to users
-- Flatpak build changes validated
+- .deb packaging changes validated
- All tests passing (`cargo test`)
---
@@ -38,25 +38,29 @@ Add a new entry under the version heading:
- behavior changes...
```
-### 3. Build the Flatpak
+### 3. Build the .deb
```bash
-cd packaging/flatpak
-bash build-flatpak.sh X.Y.Z
+make deb
```
-Verify the bundle was created:
-```bash
-ls -lh VietPlus-X.Y.Z.flatpak
-```
-
-### 4. Test the Flatpak
+Verify the package was created:
```bash
-flatpak install --user --bundle VietPlus-X.Y.Z.flatpak
-flatpak run io.github.vietc.VietPlus
+ls -lh packaging/deb/vietc_*.deb
```
+### 4. Install & test
+
+```bash
+sudo dpkg -i packaging/deb/vietc_X.Y.Z-1_amd64.deb
+```
+
+Test:
+- Search "Viet+" in the application menu — the tray icon entry should appear
+- Launch from menu — tray icon should show, Vietnamese input should work (VNI, Ctrl+Space to toggle)
+- The tray should autostart on next login (XDG autostart installed)
+
### 5. Commit and push
```bash
@@ -67,12 +71,7 @@ git push origin main
### 6. Create a release on Forgejo/GitHub
-Attach the Flatpak bundle (`VietPlus-X.Y.Z.flatpak`) as a release asset.
-
-```bash
-# Using forgejo-release (if configured)
-# Or manually upload via the web UI
-```
+Attach the .deb package (`vietc_X.Y.Z-1_amd64.deb`) as a release asset.
---
diff --git a/cli/Cargo.toml b/cli/Cargo.toml
index 10698d3..1a784bf 100644
--- a/cli/Cargo.toml
+++ b/cli/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "vietc-cli"
-version = "0.1.0"
+version = "0.1.6"
edition = "2021"
description = "Viet+ CLI Test Harness"
diff --git a/daemon/Cargo.toml b/daemon/Cargo.toml
index e16082a..8dfc33a 100644
--- a/daemon/Cargo.toml
+++ b/daemon/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "vietc-daemon"
-version = "0.1.0"
+version = "0.1.6"
edition = "2021"
description = "Viet+ background daemon"
diff --git a/daemon/src/config.rs b/daemon/src/config.rs
index f8dde81..cbdc5b2 100644
--- a/daemon/src/config.rs
+++ b/daemon/src/config.rs
@@ -196,7 +196,7 @@ impl Default for Config {
auto_restore: AutoRestoreConfig::default(),
app_state: AppStateConfig::default(),
macros,
- grab: false,
+ grab: false, // default false so daemon works without root (needs input group for uinput)
debug: false,
}
}
diff --git a/daemon/src/main.rs b/daemon/src/main.rs
index 80bf682..0dd4291 100644
--- a/daemon/src/main.rs
+++ b/daemon/src/main.rs
@@ -1027,14 +1027,13 @@ fn run_with_evdev(
if active_window_id != last_active_window {
new_window = Some(active_window_id.clone());
- } else if gap > std::time::Duration::from_millis(100) {
- // Background thread hasn't caught up yet — poll xdotool directly
+ } else {
+ // Always verify active window on every keypress — window
+ // switches under 100ms can leak the old engine buffer.
if let Some(id) = app_state::get_active_window_id() {
if id != active_window_id {
new_window = Some(id);
}
- } else {
- log_info(&format!("[vietc] gap poll: window ID query failed (gap={:?}, shared='{}')", gap, active_window_id));
}
}
@@ -1264,22 +1263,15 @@ fn execute_commands(
std::thread::sleep(std::time::Duration::from_millis(20));
}
}
-
fn create_injector(
display: display::DisplayServer,
) -> Result, Box> {
- // Try uinputd socket first
- if vietc_protocol::uinput_client::UinputClient::is_available() {
- log_info("[vietc] Using uinputd socket injection");
- return Ok(Box::new(vietc_protocol::uinput_client::UinputClient));
- }
-
- // Use uinput as primary — correct Linux keycodes for backspace + ASCII.
- // For Unicode (Vietnamese diacritics), falls back to X11 clipboard via
- // direct X11 API (not subprocesses), making it work in Flatpak sandboxes.
+ // Prefer uinput injection — uses correct Linux keycodes for backspace
+ // and ASCII, works on both X11 and Wayland (uinput devices are routed
+ // through libinput on modern X11).
match vietc_protocol::uinput_monitor::UinputInjector::new("vietc") {
Ok(injector) => {
- log_info("[vietc] Using uinput injection (primary)");
+ log_info("[vietc] Using uinput injection");
return Ok(Box::new(injector));
}
Err(e) => {
@@ -1287,18 +1279,23 @@ fn create_injector(
}
}
- // Fall back to X11 injection (only if uinput fails)
+ // Try uinputd socket
+ if vietc_protocol::uinput_client::UinputClient::is_available() {
+ log_info("[vietc] Using uinputd socket injection");
+ return Ok(Box::new(vietc_protocol::uinput_client::UinputClient));
+ }
+
+ // Fall back to X11 injection (XTest) — uses X11 keycodes, only for
+ // systems where uinput/unix socket injection is unavailable.
#[cfg(feature = "x11")]
- {
- if display != display::DisplayServer::Wayland {
- match vietc_protocol::x11_inject::X11Injector::new() {
- Ok(injector) => {
- log_info("[vietc] Using X11 injection (fallback)");
- return Ok(Box::new(injector));
- }
- Err(e) => {
- log_info(&format!("[vietc] X11 not available: {}", e));
- }
+ if display != display::DisplayServer::Wayland {
+ match vietc_protocol::x11_inject::X11Injector::new() {
+ Ok(injector) => {
+ log_info("[vietc] Using X11 injection (fallback)");
+ return Ok(Box::new(injector));
+ }
+ Err(e) => {
+ log_info(&format!("[vietc] X11 not available: {}", e));
}
}
}
diff --git a/engine/Cargo.toml b/engine/Cargo.toml
index bbc88c3..1440024 100644
--- a/engine/Cargo.toml
+++ b/engine/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "vietc-engine"
-version = "0.1.0"
+version = "0.1.6"
edition = "2021"
description = "Viet+ Vietnamese IME Core Engine"
diff --git a/packaging/appimage/build-appimage.sh b/packaging/appimage/build-appimage.sh
deleted file mode 100644
index caecd03..0000000
--- a/packaging/appimage/build-appimage.sh
+++ /dev/null
@@ -1,397 +0,0 @@
-#!/usr/bin/env bash
-set -euo pipefail
-
-# Ensure cargo is in PATH
-if ! command -v cargo &>/dev/null; then
- if [ -f "$HOME/.cargo/bin/cargo" ]; then
- export PATH="$HOME/.cargo/bin:$PATH"
- fi
-fi
-
-SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
-APPDIR="$SCRIPT_DIR/AppDir"
-VERSION="${1:-0.1.1}"
-
-echo "=== Building Viet+ AppImage v${VERSION} ==="
-
-# Clean
-rm -rf "$APPDIR"
-mkdir -p "$APPDIR/usr/bin"
-mkdir -p "$APPDIR/usr/share/applications"
-mkdir -p "$APPDIR/usr/share/icons/hicolor/256x256/apps"
-mkdir -p "$APPDIR/usr/share/doc/vietc"
-mkdir -p "$APPDIR/etc/vietc"
-mkdir -p "$APPDIR/usr/lib/systemd/user"
-mkdir -p "$APPDIR/usr/share/metainfo"
-
-# Build binaries
-echo "[1/5] Building binaries..."
-if [ ! -f "target/release/vietc" ]; then
- cargo build --release
- cd "$PROJECT_ROOT/ui" && cargo build --release && cd "$PROJECT_ROOT"
-fi
-echo " Built with x11 + wayland"
-
-# Copy binaries from deb-build if they exist, otherwise from target/release
-echo "[2/5] Installing binaries..."
-if [ -d "deb-build/usr/bin" ]; then
- cp -r deb-build/usr/bin/* "$APPDIR/usr/bin/"
-else
- cp target/release/vietc "$APPDIR/usr/bin/"
- cp target/release/vietc-cli "$APPDIR/usr/bin/"
- cp target/release/vietc-uinputd "$APPDIR/usr/bin/"
- [ -f ui/target/release/vietc-tray ] && cp ui/target/release/vietc-tray "$APPDIR/usr/bin/"
-fi
-
-# Bundle xclip as fallback for clipboard operations
-echo " Bundling xclip..."
-if command -v xclip &>/dev/null; then
- cp "$(which xclip)" "$APPDIR/usr/bin/"
- echo " xclip bundled"
-else
- echo " xclip not found on system, skipping"
-fi
-
-# Bundle xdotool for reliable Unicode text injection
-echo " Bundling xdotool..."
-if command -v xdotool &>/dev/null; then
- cp "$(which xdotool)" "$APPDIR/usr/bin/"
- echo " xdotool bundled"
-elif [ -f /tmp/xdotool-extract/usr/bin/xdotool ]; then
- cp /tmp/xdotool-extract/usr/bin/xdotool "$APPDIR/usr/bin/"
- echo " xdotool bundled (from extract)"
-else
- echo " xdotool not found, Unicode falls back to clipboard"
-fi
-
-# Compile and bundle vietc-xrecord (C helper for XRecord keyboard capture)
-echo " Compiling vietc-xrecord..."
-if command -v gcc &>/dev/null; then
- gcc -O2 -o "$APPDIR/usr/bin/vietc-xrecord" "$SCRIPT_DIR/vietc-xrecord.c" -lX11 -lXtst
- echo " vietc-xrecord bundled"
-elif command -v cc &>/dev/null; then
- cc -O2 -o "$APPDIR/usr/bin/vietc-xrecord" "$SCRIPT_DIR/vietc-xrecord.c" -lX11 -lXtst
- echo " vietc-xrecord bundled"
-else
- echo " WARNING: No C compiler found, vietc-xrecord not bundled — X11 capture will fail"
-fi
-
-# Desktop integration
-echo "[3/5] Installing desktop integration..."
-if [ -f "deb-build/vietc.desktop" ]; then
- cp deb-build/vietc.desktop "$APPDIR/usr/share/applications/"
-else
- cp "$SCRIPT_DIR/vietc.desktop" "$APPDIR/usr/share/applications/"
-fi
-
-# Icons
-if [ -f "deb-build/vietc.svg" ]; then
- cp deb-build/vietc.svg "$APPDIR/usr/share/icons/hicolor/256x256/apps/"
- cp deb-build/vietc.png "$APPDIR/usr/share/icons/hicolor/256x256/apps/"
- cp deb-build/vietc.png "$APPDIR/"
-fi
-
-# AppStream metadata
-if [ -f "deb-build/usr/share/metainfo/io.github.anomalyco.vietc.appdata.xml" ]; then
- cp deb-build/usr/share/metainfo/io.github.anomalyco.vietc.appdata.xml "$APPDIR/usr/share/metainfo/"
-else
- cat > "$APPDIR/usr/share/metainfo/io.github.anomalyco.vietc.appdata.xml" << 'XML'
-
-
- io.github.anomalyco.vietc
- Viet+
- Vietnamese Input Method for Linux
-
-
Zero-configuration Vietnamese input method engine supporting Telex and VNI input methods. Works natively on both X11 and Wayland via evdev uinput injection.
-
- MIT
- MIT
- https://github.com/anomalyco/vietc
- vietc
- Utility
-
-XML
-fi
-
-# Config
-echo "[4/5] Installing config..."
-if [ -f "deb-build/etc/vietc/config.toml" ]; then
- cp deb-build/etc/vietc/config.toml "$APPDIR/etc/vietc/"
-else
- sed 's/^grab = false/grab = true/' "$PROJECT_ROOT/vietc.toml" > "$APPDIR/etc/vietc/config.toml"
-fi
-
-# Docs
-if [ -f "deb-build/usr/share/doc/vietc/README.md" ]; then
- cp deb-build/usr/share/doc/vietc/README.md "$APPDIR/usr/share/doc/vietc/"
-else
- cp "$PROJECT_ROOT/README.md" "$APPDIR/usr/share/doc/vietc/"
-fi
-
-# Systemd service
-if [ -f "deb-build/usr/lib/systemd/user/vietc.service" ]; then
- cp deb-build/usr/lib/systemd/user/vietc.service "$APPDIR/usr/lib/systemd/user/"
-else
- cp "$PROJECT_ROOT/vietc.service" "$APPDIR/usr/lib/systemd/user/"
-fi
-
-# Desktop file in AppDir root
-if [ -f "deb-build/vietc.desktop" ]; then
- cp deb-build/vietc.desktop "$APPDIR/"
-else
- cp "$APPDIR/usr/share/applications/vietc.desktop" "$APPDIR/"
-fi
-
-# Icon — required by appimagetool (desktop file has Icon=vietc)
-# Use SVG from deb build if available, otherwise generate a keyboard icon
-if [ -f "deb-build/usr/share/icons/hicolor/256x256/apps/vietc.svg" ]; then
- cp "deb-build/usr/share/icons/hicolor/256x256/apps/vietc.svg" "$APPDIR/vietc.svg"
-elif [ -f "deb-build/usr/share/icons/hicolor/256x256/apps/vietc.png" ]; then
- cp "deb-build/usr/share/icons/hicolor/256x256/apps/vietc.png" "$APPDIR/vietc.png"
-else
- # Generate a proper keyboard+VN icon as SVG
- cat > "$APPDIR/vietc.svg" << 'SVGEOF'
-
-
-SVGEOF
-fi
-
-# Convert SVG to PNG for appimagetool (it prefers PNG for the root icon).
-# This is best-effort: if no converter works, appimagetool falls back to the
-# SVG, so a conversion failure must never abort the build.
-if [ -f "$APPDIR/vietc.svg" ] && ! [ -f "$APPDIR/vietc.png" ]; then
- if command -v rsvg-convert &>/dev/null; then
- rsvg-convert -w 256 -h 256 "$APPDIR/vietc.svg" -o "$APPDIR/vietc.png" || true
- elif command -v inkscape &>/dev/null; then
- inkscape -w 256 -h 256 "$APPDIR/vietc.svg" --export-filename="$APPDIR/vietc.png" 2>/dev/null || true
- elif command -v convert &>/dev/null; then
- convert -background none "$APPDIR/vietc.svg" -resize 256x256 "$APPDIR/vietc.png" 2>/dev/null || true
- elif command -v python3 &>/dev/null; then
- python3 -c "
-import subprocess, sys
-try:
- subprocess.check_call(['rsvg-convert', '-w', '256', '-h', '256', '$APPDIR/vietc.svg', '-o', '$APPDIR/vietc.png'])
-except Exception:
- pass
-" 2>/dev/null || true
- fi
- # If no converter, appimagetool can use SVG directly
-fi
-
-# Also put icon in hicolor for system installs via AppImage
-mkdir -p "$APPDIR/usr/share/icons/hicolor/256x256/apps"
-[ -f "$APPDIR/vietc.svg" ] && cp "$APPDIR/vietc.svg" "$APPDIR/usr/share/icons/hicolor/256x256/apps/"
-[ -f "$APPDIR/vietc.png" ] && cp "$APPDIR/vietc.png" "$APPDIR/usr/share/icons/hicolor/256x256/apps/"
-
-# Create custom AppRun script
-cat > "$APPDIR/AppRun" << 'EOF'
-#!/bin/sh
-HERE="$(dirname "$(readlink -f "${0}")")"
-
-# Handle --update flag: download latest AppImage from GitHub
-if [ "$1" = "--update" ]; then
- echo "Viet+ Self-Updater"
- RELEASE_URL="https://github.com/vndangkhoa/vietc/releases/latest/download/Viet+-x86_64.AppImage"
- TEMP="/tmp/Viet+-update.AppImage"
- echo "Downloading latest release..."
- if command -v curl >/dev/null 2>&1; then
- curl -L -o "$TEMP" "$RELEASE_URL" 2>/dev/null
- elif command -v wget >/dev/null 2>&1; then
- wget -q -O "$TEMP" "$RELEASE_URL" 2>/dev/null
- else
- echo "ERROR: curl or wget required for updates" >&2
- exit 1
- fi
- if [ -s "$TEMP" ]; then
- chmod +x "$TEMP"
- mv "$TEMP" "$(readlink -f "${0}")"
- echo "Updated! Restart the AppImage."
- else
- echo "ERROR: Download failed" >&2
- fi
- exit 0
-fi
-
-# Handle --quit flag: stop daemon and uinputd
-if [ "$1" = "--quit" ]; then
- echo "Stopping Viet+..."
- pkill -x vietc-tray 2>/dev/null
- pkill -x vietc 2>/dev/null
- pkill -x vietc-uinputd 2>/dev/null
- pkill -x vietc-xrecord 2>/dev/null
- sleep 0.5
- echo "Viet+ stopped."
- exit 0
-fi
-
-# Handle --restart flag
-if [ "$1" = "--restart" ]; then
- pkill -x vietc-tray 2>/dev/null
- pkill -x vietc 2>/dev/null
- pkill -x vietc-uinputd 2>/dev/null
- pkill -x vietc-xrecord 2>/dev/null
- sleep 0.5
- # Fall through to normal start below
-fi
-
-# Export our bin dir on PATH so child processes can find sibling binaries
-export PATH="$HERE/usr/bin:$PATH"
-
-# Build display env prefix for elevation commands.
-# Capture from current user env (DISPLAY, XAUTHORITY, WAYLAND_DISPLAY, XDG_RUNTIME_DIR)
-# so they are available inside the root daemon. Without this, xdotool/xclip/wtype
-# fail silently because sudo/pkexec strip display env vars.
-ENV_PREFIX="env"
-[ -n "$DISPLAY" ] && ENV_PREFIX="$ENV_PREFIX DISPLAY=$DISPLAY"
-[ -n "$XAUTHORITY" ] && ENV_PREFIX="$ENV_PREFIX XAUTHORITY=$XAUTHORITY"
-[ -n "$WAYLAND_DISPLAY" ] && ENV_PREFIX="$ENV_PREFIX WAYLAND_DISPLAY=$WAYLAND_DISPLAY"
-[ -n "$XDG_RUNTIME_DIR" ] && ENV_PREFIX="$ENV_PREFIX XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR"
-
-# Ensure system library paths are available for dlopen (libX11, libXtst, etc.)
-# AppImage runtime may override LD_LIBRARY_PATH; append system paths as fallback
-SYSLIB_PATHS="/usr/lib/x86_64-linux-gnu:/usr/lib64:/usr/lib:/lib/x86_64-linux-gnu:/lib64:/lib"
-if [ -n "$LD_LIBRARY_PATH" ]; then
- export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$SYSLIB_PATHS"
-else
- export LD_LIBRARY_PATH="$SYSLIB_PATHS"
-fi
-
-# Start daemon (kill old non-root one first if we have root)
-# On X11 we can run without root (XGrabKeyboard + XTest injection needs no special permissions).
-# On Wayland, evdev requires root (input group) or uinput.
-NEED_ROOT=""
-if [ -n "$WAYLAND_DISPLAY" ]; then
- NEED_ROOT="yes"
-fi
-
-if [ -z "$NEED_ROOT" ]; then
- # X11: no root needed for capture, but uinputd needs root for injection
- pkill -x vietc-uinputd 2>/dev/null
- pkill -x vietc 2>/dev/null; sleep 0.3
- mkdir -p "$HOME/.config/vietc" "$HOME/.vietc"
-
- # Try to start the uinputd daemon (preferred injection path)
- if command -v pkexec >/dev/null 2>&1; then
- pkexec "$HERE/usr/bin/vietc-uinputd" >/dev/null 2>&1 &
- UINPUTD_PID=$!
- sleep 0.3
- elif command -v sudo >/dev/null 2>&1; then
- if sudo -n true 2>/dev/null; then
- sudo "$HERE/usr/bin/vietc-uinputd" >/dev/null 2>&1 &
- UINPUTD_PID=$!
- sleep 0.3
- fi
- fi
-
- "$HERE/usr/bin/vietc" >"$HOME/.config/vietc/vietc-daemon.log" 2>&1 &
- DAEMON_PID=$!
- echo "[vietc] Daemon started (PID=$DAEMON_PID), log: $HOME/.config/vietc/vietc-daemon.log"
-else
- # Fix Wayland env for root: sudo resets XDG_RUNTIME_DIR, breaking wtype/wl-copy.
- if [ "$(id -u)" = "0" ] && [ -z "$XDG_RUNTIME_DIR" ] && [ -n "$SUDO_USER" ]; then
- USER_UID=$(id -u "$SUDO_USER" 2>/dev/null || echo 1000)
- export XDG_RUNTIME_DIR="/run/user/$USER_UID"
- if [ -d "/run/user/$USER_UID" ] && ls "/run/user/$USER_UID/wayland-*" >/dev/null 2>&1; then
- export WAYLAND_DISPLAY="${WAYLAND_DISPLAY:-wayland-0}"
- fi
- fi
-
- if command -v pkexec >/dev/null; then
- pkill -x vietc 2>/dev/null; sleep 0.5
- pkexec $ENV_PREFIX "$HERE/usr/bin/vietc" >/dev/null &
- DAEMON_PID=$!
- elif [ -n "$WAYLAND_DISPLAY" ]; then
- password=""
- if command -v kdialog >/dev/null; then
- password=$(kdialog --password "Viet+ needs root privileges to grab the keyboard.") || password=""
- elif command -v zenity >/dev/null; then
- password=$(zenity --password --title="Viet+ needs root") || password=""
- elif command -v ssh-askpass >/dev/null; then
- password=$(ssh-askpass "Viet+ needs root privileges") || password=""
- fi
- if [ -n "$password" ]; then
- pkill -x vietc 2>/dev/null; sleep 0.5
- echo "$password" | sudo -S $ENV_PREFIX "$HERE/usr/bin/vietc" >/dev/null &
- DAEMON_PID=$!
- fi
- elif command -v sudo >/dev/null; then
- pkill -x vietc 2>/dev/null; sleep 0.5
- sudo $ENV_PREFIX "$HERE/usr/bin/vietc" >/dev/null &
- DAEMON_PID=$!
- fi
-fi
-
-if [ -z "$DAEMON_PID" ] && ! pgrep -x vietc >/dev/null; then
- mkdir -p "$HOME/.config/vietc"
- "$HERE/usr/bin/vietc" >"$HOME/.config/vietc/vietc-daemon.log" 2>&1 &
- DAEMON_PID=$!
- echo "[vietc] Daemon fallback started (PID=$DAEMON_PID), log: $HOME/.config/vietc/vietc-daemon.log"
-fi
-
-# Keep the AppImage alive with a tray or settings UI.
-# Run as a child (not exec) so daemon cleanup works on exit.
-cleanup_daemon() {
- if [ -n "$DAEMON_PID" ]; then
- kill "$DAEMON_PID" 2>/dev/null
- wait "$DAEMON_PID" 2>/dev/null
- fi
- if [ -n "$UINPUTD_PID" ]; then
- kill "$UINPUTD_PID" 2>/dev/null
- wait "$UINPUTD_PID" 2>/dev/null
- fi
-}
-trap cleanup_daemon EXIT INT TERM
-
-if [ -f "$HERE/usr/bin/vietc-tray" ]; then
- "$HERE/usr/bin/vietc-tray" "$@"
-else
- echo "[vietc] Daemon running (PID=$DAEMON_PID)."
- echo "[vietc] To stop: Vietnam+-*.AppImage --quit"
- # Show a quit dialog if DE is available
- if command -v zenity >/dev/null 2>&1; then
- zenity --info --text="Viet+ is running in the background.\n\nTo stop: run this AppImage with --quit" \
- --title="Viet+ Input Method" --width=350 2>/dev/null &
- fi
- # Keep alive until daemon dies or user quits via --quit
- wait $DAEMON_PID 2>/dev/null
-fi
-EOF
-chmod +x "$APPDIR/AppRun"
-
-echo "[5/5] AppDir ready at: $APPDIR"
-echo ""
-
-# Auto build if appimagetool exists
-if [ -f "$SCRIPT_DIR/appimagetool" ]; then
- echo "=== Running appimagetool FUSE build ==="
- ARCH=x86_64 "$SCRIPT_DIR/appimagetool" --appimage-extract-and-run "$APPDIR" "$SCRIPT_DIR/Viet+-${VERSION}-x86_64.AppImage"
-elif command -v appimagetool &>/dev/null; then
- echo "=== Running system appimagetool ==="
- ARCH=x86_64 appimagetool "$APPDIR" "$SCRIPT_DIR/Viet+-${VERSION}-x86_64.AppImage"
-else
- echo "To build AppImage:"
- echo " appimagetool $APPDIR Viet+-${VERSION}-x86_64.AppImage"
-fi
diff --git a/packaging/deb/build-deb.sh b/packaging/deb/build-deb.sh
index 7c4e01e..75480bb 100755
--- a/packaging/deb/build-deb.sh
+++ b/packaging/deb/build-deb.sh
@@ -3,7 +3,7 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
-VERSION="${1:-0.1.0}"
+VERSION="${1:-0.1.6}"
PACKAGE="vietc_${VERSION}-1_amd64"
STAGING="$SCRIPT_DIR/$PACKAGE"
@@ -12,7 +12,7 @@ echo "=== Building Viet+ .deb package v${VERSION} ==="
# Build binaries (all features: x11 + wayland)
echo "[1/5] Building binaries..."
cargo build --release --features "x11,wayland" --manifest-path "$PROJECT_ROOT/Cargo.toml"
-(cd "$PROJECT_ROOT/ui" && export PKG_CONFIG_PATH="/tmp/dbus-dev/extracted/usr/lib/x86_64-linux-gnu/pkgconfig:${PKG_CONFIG_PATH:-}" && export RUSTFLAGS="-L /tmp/dbus-dev/lib" && cargo build --release) || echo " Warning: UI tray not built (libdbus-1-dev may be missing)"
+(cd "$PROJECT_ROOT/ui" && cargo build --release)
echo " Done."
# Clean and create staging
@@ -26,54 +26,39 @@ mkdir -p "$STAGING/usr/share/applications"
mkdir -p "$STAGING/usr/share/icons/hicolor/256x256/apps"
mkdir -p "$STAGING/usr/share/doc/vietc"
mkdir -p "$STAGING/usr/share/metainfo"
+mkdir -p "$STAGING/etc/xdg/autostart"
# Copy binaries
echo "[3/5] Installing binaries..."
-cp "$PROJECT_ROOT/target/release/vietc" "$STAGING/usr/bin/"
+cp "$PROJECT_ROOT/target/release/vietc" "$STAGING/usr/bin/vietc-daemon"
cp "$PROJECT_ROOT/target/release/vietc-cli" "$STAGING/usr/bin/"
-# Privileged uinput injection daemon — required for Unicode (Vietnamese) output.
cp "$PROJECT_ROOT/target/release/vietc-uinputd" "$STAGING/usr/bin/"
-[ -f "$PROJECT_ROOT/ui/target/release/vietc-tray" ] && cp "$PROJECT_ROOT/ui/target/release/vietc-tray" "$STAGING/usr/bin/"
+cp "$PROJECT_ROOT/ui/target/release/vietc-tray" "$STAGING/usr/bin/"
# Compile and bundle vietc-xrecord (C helper for X11 XRecord keyboard capture)
-if command -v gcc &>/dev/null; then
- gcc -O2 -o "$STAGING/usr/bin/vietc-xrecord" "$PROJECT_ROOT/packaging/appimage/vietc-xrecord.c" -lX11 -lXtst \
- && echo " vietc-xrecord compiled" \
- || echo " WARNING: vietc-xrecord compile failed (libX11/libXtst dev headers missing)"
-else
- echo " WARNING: no gcc, vietc-xrecord not bundled"
-fi
+gcc -O2 -o "$STAGING/usr/bin/vietc-xrecord" "$SCRIPT_DIR/vietc-xrecord.c" -lX11 -lXtst
+
+# Icons (main app icon + tray status icons)
+cp "$PROJECT_ROOT/packaging/icons/vietc.svg" "$STAGING/usr/share/icons/hicolor/256x256/apps/"
+cp "$PROJECT_ROOT/packaging/icons/vietc-vn.svg" "$STAGING/usr/share/icons/hicolor/256x256/apps/"
+cp "$PROJECT_ROOT/packaging/icons/vietc-en.svg" "$STAGING/usr/share/icons/hicolor/256x256/apps/"
# Desktop file
-cp "$PROJECT_ROOT/packaging/appimage/vietc.desktop" "$STAGING/usr/share/applications/"
+cp "$SCRIPT_DIR/vietc.desktop" "$STAGING/usr/share/applications/"
-# Icon (SVG from AppImage build script)
-cat > "$STAGING/usr/share/icons/hicolor/256x256/apps/vietc.svg" << 'SVGEOF'
-
-
-SVGEOF
+# XDG autostart — launches tray on every login for all users
+cat > "$STAGING/etc/xdg/autostart/vietc-tray.desktop" << 'AUTOSTART'
+[Desktop Entry]
+Type=Application
+Name=Viet+ Tray
+Comment=Vietnamese Input Method Tray
+Exec=vietc-tray
+Icon=vietc
+Terminal=false
+Categories=Utility;
+StartupNotify=false
+NoDisplay=true
+AUTOSTART
# Documentation
cp "$PROJECT_ROOT/README.md" "$STAGING/usr/share/doc/vietc/"
@@ -82,8 +67,21 @@ cp "$PROJECT_ROOT/LICENSE" "$STAGING/usr/share/doc/vietc/"
# Config
cp "$PROJECT_ROOT/vietc.toml" "$STAGING/etc/vietc/config.toml"
-# Systemd user service
-cp "$PROJECT_ROOT/vietc.service" "$STAGING/usr/lib/systemd/user/"
+# Systemd user service — tray spawns the daemon internally
+cat > "$STAGING/usr/lib/systemd/user/vietc.service" << 'SERVICE'
+[Unit]
+Description=Viet+ Vietnamese IME Tray
+PartOf=graphical-session.target
+
+[Service]
+Type=simple
+ExecStart=/usr/bin/vietc-tray
+Restart=on-failure
+RestartSec=5
+
+[Install]
+WantedBy=default.target
+SERVICE
# AppStream metadata
cat > "$STAGING/usr/share/metainfo/io.github.anomalyco.vietc.appdata.xml" << 'XML'
@@ -124,7 +122,7 @@ Section: utils
Priority: optional
Architecture: amd64
Depends: libc6 (>= 2.31), libevdev2 (>= 1.9.0)
-Recommends: libwayland-client0 (>= 1.20), libx11-6, libxtst6, xclip
+Recommends: libwayland-client0 (>= 1.20), libx11-6, libxtst6, libdbus-1-3, xclip
Maintainer: Khoa Vo
Description: Viet+ — Vietnamese Input Method for Linux
Zero-configuration Vietnamese input method engine supporting
@@ -140,10 +138,73 @@ echo "/etc/vietc/config.toml" > "$STAGING/DEBIAN/conffiles"
cat > "$STAGING/DEBIAN/postinst" << 'POSTINST'
#!/bin/sh
set -e
+
+show_popup() {
+ local user="$1" msg="$2"
+ local display="${DISPLAY:-:0}"
+ local xauth=""
+ if [ -n "$user" ]; then
+ local home
+ home="$(getent passwd "$user" 2>/dev/null | cut -d: -f6 || true)"
+ if [ -n "$home" ]; then
+ xauth="$home/.Xauthority"
+ fi
+ fi
+ # Try zenity (modal dialog)
+ if command -v zenity >/dev/null 2>&1 && [ -n "$user" ]; then
+ su "$user" -c "DISPLAY='$display' XAUTHORITY='$xauth' \
+ zenity --info --title='Viet+' --text='$msg' --width=400" 2>/dev/null || true
+ fi
+ # Also try notify-send (desktop notification)
+ if command -v notify-send >/dev/null 2>&1 && [ -n "$user" ]; then
+ su "$user" -c "DISPLAY='$display' XAUTHORITY='$xauth' \
+ notify-send 'Viet+' '$msg' -t 10000 -i vietc" 2>/dev/null || true
+ fi
+}
+
+cleanup_old_install() {
+ # Remove old binaries from /usr/local/bin/ (shadowed the new /usr/bin/ ones)
+ rm -f /usr/local/bin/vietc-tray /usr/local/bin/vietc /usr/local/bin/vietc-daemon \
+ /usr/local/bin/vietc-cli /usr/local/bin/vietc-uinputd /usr/local/bin/vietc-xrecord 2>/dev/null || true
+}
+
case "$1" in
configure)
+ # Kill old running daemon/tray so new binaries take effect
+ pkill -x vietc-tray 2>/dev/null || true
+ pkill -x vietc-daemon 2>/dev/null || true
+ pkill -x vietc 2>/dev/null || true
+
+ # Remove old /usr/local/bin/ binaries that shadowed the new ones
+ cleanup_old_install
+
+ # Reload systemd
if command -v systemctl >/dev/null 2>&1; then
- systemctl --system daemon-reload >/dev/null 2>&1 || true
+ systemctl --global daemon-reload >/dev/null 2>&1 || true
+ fi
+
+ # Add installing user to input group (needed for /dev/uinput access)
+ INSTALLING_USER="${SUDO_USER:-${USER:-}}"
+ if [ -n "$INSTALLING_USER" ] && [ "$INSTALLING_USER" != "root" ]; then
+ if ! groups "$INSTALLING_USER" 2>/dev/null | grep -qw input; then
+ adduser "$INSTALLING_USER" input 2>/dev/null || true
+ fi
+ # Remove stale user config from previous installs
+ USER_HOME="$(getent passwd "$INSTALLING_USER" 2>/dev/null | cut -d: -f6 || true)"
+ if [ -n "$USER_HOME" ]; then
+ rm -f "$USER_HOME/.config/vietc/config.toml" 2>/dev/null || true
+ rm -f "$USER_HOME/.config/vietc/overrides.toml" 2>/dev/null || true
+ rm -f "$USER_HOME/.config/vietc/.first-launch-done" 2>/dev/null || true
+ fi
+
+ # Show popup
+ show_popup "$INSTALLING_USER" \
+ "Viet+ installed! Please LOG OUT and LOG BACK IN to start typing Vietnamese."
+ fi
+
+ # Update icon cache so the app icon appears in the menu
+ if command -v gtk-update-icon-cache >/dev/null 2>&1; then
+ gtk-update-icon-cache -f /usr/share/icons/hicolor/ >/dev/null 2>&1 || true
fi
;;
esac
@@ -156,7 +217,7 @@ set -e
case "$1" in
remove|upgrade|deconfigure)
if command -v systemctl >/dev/null 2>&1; then
- systemctl --system daemon-reload >/dev/null 2>&1 || true
+ systemctl --global daemon-reload >/dev/null 2>&1 || true
fi
;;
esac
diff --git a/packaging/appimage/vietc-xrecord.c b/packaging/deb/vietc-xrecord.c
similarity index 100%
rename from packaging/appimage/vietc-xrecord.c
rename to packaging/deb/vietc-xrecord.c
diff --git a/packaging/appimage/vietc.desktop b/packaging/deb/vietc.desktop
similarity index 55%
rename from packaging/appimage/vietc.desktop
rename to packaging/deb/vietc.desktop
index d6a47f9..a560cb1 100644
--- a/packaging/appimage/vietc.desktop
+++ b/packaging/deb/vietc.desktop
@@ -3,9 +3,9 @@ Type=Application
Name=Viet+
GenericName=Vietnamese Input Method
Comment=Vietnamese Input Method for Linux — Zero underline, native Wayland/X11
-Exec=vietc
+Exec=vietc-tray
Icon=vietc
Terminal=false
-Categories=Utility;
-Keywords=vietnamese;input;ime;keyboard;
-StartupNotify=false
+Categories=Utility;TextTools;X-GNOME-Utilities;
+Keywords=vietnamese;input;ime;keyboard;viet;gõ tiếng việt;
+StartupNotify=true
diff --git a/packaging/flatpak/FLATPAK_BUILD.md b/packaging/flatpak/FLATPAK_BUILD.md
deleted file mode 100644
index 206b41b..0000000
--- a/packaging/flatpak/FLATPAK_BUILD.md
+++ /dev/null
@@ -1,112 +0,0 @@
-# Building the Viet+ Flatpak
-
-## Prerequisites
-
-- Flatpak installed with Flathub remote configured
-- `org.gnome.Platform//50` runtime installed
-- `org.gnome.Sdk//50` SDK installed
-- `org.freedesktop.Sdk.Extension.rust-stable//25.08` installed
-
-### Install dependencies
-
-```bash
-flatpak install --user flathub org.gnome.Platform//50
-flatpak install --user flathub org.gnome.Sdk//50
-flatpak install --user flathub org.freedesktop.Sdk.Extension.rust-stable//25.08
-```
-
----
-
-## Method 1: Quick build script
-
-```bash
-cd packaging/flatpak
-bash build-flatpak.sh [version]
-# e.g. bash build-flatpak.sh 0.1.5
-```
-
-Output: `packaging/flatpak/VietPlus-.flatpak`
-
----
-
-## Method 2: Manual step-by-step
-
-```bash
-cd packaging/flatpak
-
-# 1. Clean previous artifacts
-rm -rf build-dir repo VietPlus-*.flatpak
-
-# 2. Initialize build directory
-# NOTE: arg order is flatpak build-init DIR APPNAME SDK RUNTIME
-flatpak build-init build-dir io.github.vietc.VietPlus \
- org.gnome.Sdk//50 org.gnome.Platform//50
-
-# 3. Copy source code
-mkdir -p build-dir/files/src/vietc
-rsync -a /path/to/vietc/ build-dir/files/src/vietc/ --exclude=target --exclude=.git
-
-# 4. Build Rust binaries
-flatpak build --share=network build-dir sh -c '
- export PATH=/usr/lib/sdk/rust-stable/bin:$PATH
- export CARGO_HOME=/app/cargo
- cd /app/src/vietc
- cargo build --release -p vietc-daemon -p vietc-cli -p vietc-uinputd
-'
-
-# 5. Install binaries and icons
-flatpak build build-dir sh -c '
- install -Dm755 /app/src/vietc/target/release/vietc /app/bin/vietc-daemon
- install -Dm755 /app/src/vietc/target/release/vietc-cli /app/bin/vietc-cli
- install -Dm755 /app/src/vietc/target/release/vietc-uinputd /app/bin/vietc-uinputd
-
- install -Dm644 /app/src/vietc/packaging/icons/vietc.svg \
- /app/share/icons/hicolor/scalable/apps/io.github.vietc.VietPlus.svg
- install -Dm644 /app/src/vietc/packaging/icons/vietc-vn.svg \
- /app/share/icons/hicolor/scalable/apps/io.github.vietc.VietPlus.vietc-vn.svg
- install -Dm644 /app/src/vietc/packaging/icons/vietc-en.svg \
- /app/share/icons/hicolor/scalable/apps/io.github.vietc.VietPlus.vietc-en.svg
-'
-
-# 6. Finish (set permissions + command)
-flatpak build-finish build-dir \
- --socket=x11 \
- --socket=wayland \
- --filesystem=home \
- --share=ipc \
- --talk-name=org.freedesktop.Notifications \
- --talk-name=org.a11y.Bus \
- --command=vietc-daemon
-
-# 7. Export to local repo
-flatpak build-export repo build-dir
-
-# 8. Create bundle
-flatpak build-bundle repo VietPlus-0.1.5.flatpak io.github.vietc.VietPlus
-```
-
----
-
-## Installation
-
-```bash
-# From bundle
-flatpak install --user --bundle VietPlus-0.1.5.flatpak
-
-# From local repo
-flatpak --user remote-add --no-gpg-verify vietc-repo repo
-flatpak --user install vietc-repo io.github.vietc.VietPlus
-
-# Run
-flatpak run io.github.vietc.VietPlus
-```
-
----
-
-## Key Notes
-
-- **SDK/RUNTIME order**: `flatpak build-init` takes `SDK` first, then `RUNTIME` (counterintuitive but important — getting this wrong means `/usr/lib/sdk/` won't be mounted)
-- **Rust SDK**: must be installed as `org.freedesktop.Sdk.Extension.rust-stable//25.08`; it mounts automatically at `/usr/lib/sdk/rust-stable/`
-- **Icons**: all icon files in Flatpak must be prefixed with the app ID (`io.github.vietc.VietPlus.*`) or `flatpak build-export` will skip them
-- **Daemon binary name**: Cargo builds the daemon binary as `vietc` (not `vietc-daemon`) in `target/release/`; rename on install to match the desktop file
-- **Desktop Categories**: only use registered categories (`Utility`); `InputMethod` is not registered
diff --git a/packaging/flatpak/build-flatpak.sh b/packaging/flatpak/build-flatpak.sh
deleted file mode 100644
index 0f7d821..0000000
--- a/packaging/flatpak/build-flatpak.sh
+++ /dev/null
@@ -1,109 +0,0 @@
-#!/usr/bin/env bash
-set -euo pipefail
-
-SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
-VERSION="${1:-0.1.4}"
-
-echo "=== Building Viet+ Flatpak v${VERSION} ==="
-cd "$SCRIPT_DIR"
-
-# Clean previous build
-rm -rf build-dir repo VietPlus-*.flatpak
-
-# Initialize build directory
-# NOTE: arg order is flatpak build-init DIR APPNAME SDK RUNTIME
-flatpak build-init build-dir io.github.vietc.VietPlus \
- org.gnome.Sdk//50 org.gnome.Platform//50
-
-# Copy source code
-mkdir -p build-dir/files/src/vietc
-rsync -a "$PROJECT_ROOT/" build-dir/files/src/vietc/ --exclude=target --exclude=.git
-
-BUILD='export PATH=/usr/lib/sdk/rust-stable/bin:$PATH
-export CARGO_HOME=/app/cargo
-cd /app/src/vietc'
-
-# Build daemon + CLI + uinputd + tray
-echo ""
-echo "=== Compiling daemon, CLI, uinputd, tray... ==="
-flatpak build --share=network build-dir sh -c "$BUILD && cargo build --release -p vietc-daemon -p vietc-cli -p vietc-uinputd && cargo build --release --manifest-path ui/Cargo.toml"
-
-# Install files
-echo ""
-echo "=== Installing files... ==="
-flatpak build build-dir sh -c "
-set -e
-install -Dm755 /app/src/vietc/target/release/vietc /app/bin/vietc-daemon
-install -Dm755 /app/src/vietc/target/release/vietc-cli /app/bin/vietc-cli
-install -Dm755 /app/src/vietc/target/release/vietc-uinputd /app/bin/vietc-uinputd
-install -Dm755 /app/src/vietc/ui/target/release/vietc-tray /app/bin/vietc-tray
-
-install -Dm644 /app/src/vietc/packaging/icons/vietc.svg /app/share/icons/hicolor/scalable/apps/io.github.vietc.VietPlus.svg
-install -Dm644 /app/src/vietc/packaging/icons/vietc-vn.svg /app/share/icons/hicolor/scalable/apps/io.github.vietc.VietPlus.vietc-vn.svg
- install -Dm644 /app/src/vietc/packaging/icons/vietc-en.svg /app/share/icons/hicolor/scalable/apps/io.github.vietc.VietPlus.vietc-en.svg
-
- mkdir -p /app/share/applications
- cat > /app/share/applications/io.github.vietc.VietPlus.desktop << END
-[Desktop Entry]
-Name=Viet+
-Comment=Vietnamese Input Method
-Exec=/app/bin/vietc-tray
-Icon=io.github.vietc.VietPlus
-Terminal=false
-Type=Application
-StartupNotify=true
-Categories=Utility;TextTools;X-GNOME-Utilities;
-END
-
-mkdir -p /app/share/metainfo
-cat > /app/share/metainfo/io.github.vietc.VietPlus.metainfo.xml << 'XML'
-
-
- io.github.vietc.VietPlus
- Viet+
- Vietnamese Input Method for Linux
-
-