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 @@ Platform Rust License - Version + Version Tests Event Sourcing

@@ -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' - - - - - - - - - - - - - - - - - - - - - - - VN - -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' - - - - - - - - - - - - - - - - - - - - - - - VN - -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 - -

Zero-configuration Vietnamese input method engine supporting Telex and VNI input methods.

-
- MIT - MIT - https://github.com/vndangkhoa/vietc - vietc-daemon - Utility -
-XML -" - -# Finish -echo "" -echo "=== Finalizing build... ===" -flatpak build-finish build-dir \ - --socket=x11 \ - --socket=wayland \ - --socket=session-bus \ - --device=all \ - --share=ipc \ - --talk-name=org.freedesktop.Notifications \ - --talk-name=org.a11y.Bus \ - --talk-name=org.freedesktop.portal.Clipboard \ - --command=vietc-tray - -# Export -echo "" -echo "=== Exporting to repository... ===" -flatpak build-export repo build-dir - -# Bundle -echo "" -echo "=== Creating bundle... ===" -flatpak build-bundle repo "VietPlus-${VERSION}.flatpak" io.github.vietc.VietPlus - -echo "" -echo "=== Done ===" -echo "Package: $SCRIPT_DIR/VietPlus-${VERSION}.flatpak" -echo "Size: $(du -h "$SCRIPT_DIR/VietPlus-${VERSION}.flatpak" | cut -f1)" -echo "" -echo "Install: flatpak install --user --bundle VietPlus-${VERSION}.flatpak" -echo "Run: flatpak run io.github.vietc.VietPlus" -echo "Search: 'Viet+' in app menu" diff --git a/packaging/flatpak/io.github.vietc.VietPlus.json b/packaging/flatpak/io.github.vietc.VietPlus.json deleted file mode 100644 index fa5f0ef..0000000 --- a/packaging/flatpak/io.github.vietc.VietPlus.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "app-id": "io.github.vietc.VietPlus", - "runtime": "org.gnome.Platform", - "runtime-version": "50", - "sdk": "org.gnome.Sdk", - "sdk-extensions": [ - "org.freedesktop.Sdk.Extension.rust-stable" - ], - "command": "vietc-daemon", - "finish-args": [ - "--socket=x11", - "--socket=wayland", - "--socket=session-bus", - "--device=all", - "--share=ipc", - "--talk-name=org.freedesktop.Notifications", - "--talk-name=org.a11y.Bus", - "--talk-name=org.freedesktop.portal.Clipboard" - ], - "modules": [ - { - "name": "vietc", - "buildsystem": "simple", - "build-options": { - "append-path": "/usr/lib/sdk/rust-stable/bin", - "env": { - "CARGO_HOME": "/run/build/vietc/cargo" - } - }, - "build-commands": [ - "cargo build --release -p vietc-daemon -p vietc-cli -p vietc-uinputd --manifest-path /run/build/vietc/Cargo.toml", - - "install -Dm755 target/release/vietc /app/bin/vietc-daemon", - "install -Dm755 target/release/vietc-cli /app/bin/vietc-cli", - "install -Dm755 target/release/vietc-uinputd /app/bin/vietc-uinputd", - - "install -Dm644 packaging/icons/vietc.svg /app/share/icons/hicolor/scalable/apps/io.github.vietc.VietPlus.svg", - "install -Dm644 packaging/icons/vietc-vn.svg /app/share/icons/hicolor/scalable/apps/io.github.vietc.VietPlus.vietc-vn.svg", - "install -Dm644 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\n[Desktop Entry]\nName=Viet+\nComment=Vietnamese Input Method\nExec=/app/bin/vietc-daemon\nIcon=io.github.vietc.VietPlus\nTerminal=false\nType=Application\nCategories=Utility;\nEND", - - "mkdir -p /app/share/metainfo", - "cat > /app/share/metainfo/io.github.vietc.VietPlus.metainfo.xml << 'XML'\n\n\n io.github.vietc.VietPlus\n Viet+\n Vietnamese Input Method for Linux\n \n

Zero-configuration Vietnamese input method engine supporting Telex and VNI input methods.

\n
\n MIT\n MIT\n https://github.com/vndangkhoa/vietc\n vietc-daemon\n Utility\n
\nXML" - ], - "sources": [ - { - "type": "dir", - "path": "../.." - } - ] - } - ] -} diff --git a/packaging/flatpak/vietc-wrapper.sh b/packaging/flatpak/vietc-wrapper.sh deleted file mode 100644 index 3ceba3e..0000000 --- a/packaging/flatpak/vietc-wrapper.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/sh -# Viet+ Flatpak entry point -# Starts the daemon and optionally the system tray interface - -HERE="$(dirname "$(readlink -f "${0}")")" -export PATH="$HERE:$PATH" - -CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/vietc" -mkdir -p "$CONFIG_DIR" "$HOME/.vietc" - -# Kill old processes -pkill -x vietc 2>/dev/null || true -pkill -x vietc-xrecord 2>/dev/null || true - -# Start daemon in background -"$HERE/vietc" > "$CONFIG_DIR/vietc-daemon.log" 2>&1 & -DAEMON_PID=$! - -cleanup() { - if [ -n "$DAEMON_PID" ]; then - kill "$DAEMON_PID" 2>/dev/null - wait "$DAEMON_PID" 2>/dev/null - fi -} -trap cleanup EXIT INT TERM - -# Start tray if available -if [ -f "$HERE/vietc-tray" ]; then - "$HERE/vietc-tray" "$@" - exit $? -fi - -# No tray: show notification if available -if command -v notify-send >/dev/null 2>&1; then - notify-send "Viet+" "Input method running in background" -t 3000 -fi - -echo "[vietc] Running (PID=$DAEMON_PID). Ctrl+C to stop." -wait $DAEMON_PID diff --git a/protocol/Cargo.toml b/protocol/Cargo.toml index 8c189a9..b5ce723 100644 --- a/protocol/Cargo.toml +++ b/protocol/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vietc-protocol" -version = "0.1.0" +version = "0.1.6" edition = "2021" description = "Viet+ keystroke injection backends (X11/Wayland)" diff --git a/protocol/src/x11_inject.rs b/protocol/src/x11_inject.rs index 077c6ee..2ad2371 100644 --- a/protocol/src/x11_inject.rs +++ b/protocol/src/x11_inject.rs @@ -388,20 +388,19 @@ impl X11Injector { // (unlikely at this point, but be safe) self.handle_pending_events(); - // Send backspaces via XTest + // Send backspaces via XTest (X11 keycode 22 = backspace) if backspaces > 0 { for _ in 0..backspaces { - self.send_keycode(14, false); // KEY_BACKSPACE + self.send_keycode(22, false); } } - // Send Ctrl+V via XTest to paste + // Send Ctrl+V via XTest to paste (evdev codes + 8 = X11) unsafe { - // X11 keycodes: 37 = Ctrl_L, 55 = V - (self.lib.x_test_fake_key_event)(self.display, 37, 1, 0); - (self.lib.x_test_fake_key_event)(self.display, 55, 1, 0); - (self.lib.x_test_fake_key_event)(self.display, 55, 0, 0); - (self.lib.x_test_fake_key_event)(self.display, 37, 0, 0); + (self.lib.x_test_fake_key_event)(self.display, 29 + 8, 1, 0); // Ctrl_L press + (self.lib.x_test_fake_key_event)(self.display, 47 + 8, 1, 0); // V press + (self.lib.x_test_fake_key_event)(self.display, 47 + 8, 0, 0); // V release + (self.lib.x_test_fake_key_event)(self.display, 29 + 8, 0, 0); // Ctrl_L release (self.lib.x_flush)(self.display); } @@ -416,15 +415,16 @@ impl X11Injector { true } - fn send_keycode(&self, keycode: u32, shift: bool) { + fn send_keycode(&self, evdev_keycode: u32, shift: bool) { + let x11 = evdev_keycode + 8; unsafe { if shift { - (self.lib.x_test_fake_key_event)(self.display, 50, 1, 0); + (self.lib.x_test_fake_key_event)(self.display, 42 + 8, 1, 0); // Shift_L } - (self.lib.x_test_fake_key_event)(self.display, keycode, 1, 0); - (self.lib.x_test_fake_key_event)(self.display, keycode, 0, 0); + (self.lib.x_test_fake_key_event)(self.display, x11, 1, 0); + (self.lib.x_test_fake_key_event)(self.display, x11, 0, 0); if shift { - (self.lib.x_test_fake_key_event)(self.display, 50, 0, 0); + (self.lib.x_test_fake_key_event)(self.display, 42 + 8, 0, 0); } (self.lib.x_flush)(self.display); } @@ -484,15 +484,17 @@ struct XSelectionNotifyEvent { impl KeyInjector for X11Injector { fn send_key_event(&self, keycode: u16, value: i32) -> InjectResult { + // X11 keycodes = Linux evdev keycodes + 8 + let x11_keycode = keycode as u32 + 8; unsafe { - (self.lib.x_test_fake_key_event)(self.display, keycode as u32, value, 0); + (self.lib.x_test_fake_key_event)(self.display, x11_keycode, value, 0); (self.lib.x_flush)(self.display); } InjectResult::Success } fn send_backspace(&self) -> InjectResult { - self.send_keycode(14, false); + self.send_keycode(22, false); // X11 keycode 22 = backspace InjectResult::Success } diff --git a/ui/Cargo.toml b/ui/Cargo.toml index b9dbbbb..08a930b 100644 --- a/ui/Cargo.toml +++ b/ui/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vietc-tray" -version = "0.1.0" +version = "0.1.6" edition = "2021" description = "Viet+ system tray icon" diff --git a/ui/src/config.rs b/ui/src/config.rs index 1951f31..fb188f8 100644 --- a/ui/src/config.rs +++ b/ui/src/config.rs @@ -71,7 +71,7 @@ pub struct Config { } fn default_input_method() -> String { - "telex".into() + "vni".into() } fn default_toggle_key() -> String { "space".into() @@ -80,7 +80,7 @@ fn default_start_enabled() -> bool { true } fn default_grab() -> bool { - true + false } fn default_true() -> bool { true @@ -150,6 +150,7 @@ fn config_paths() -> Vec { } paths.push(PathBuf::from("vietc.toml")); + paths.push(PathBuf::from("/etc/vietc/config.toml")); paths } diff --git a/ui/src/main.rs b/ui/src/main.rs index 5f73ef1..3fb5211 100644 --- a/ui/src/main.rs +++ b/ui/src/main.rs @@ -49,6 +49,14 @@ fn needs_root() -> bool { // Inside Flatpak the sandbox already has device access; sudo won't work. return false; } + // Check if we can access /dev/uinput directly (user in input group or has ACL) + let uinput = std::fs::OpenOptions::new() + .read(true) + .write(true) + .open("/dev/uinput"); + if uinput.is_ok() { + return false; // Can grab + inject without root + } let cfg = config::Config::load(); cfg.grab } diff --git a/ui/src/tray.rs b/ui/src/tray.rs index 74ea532..b5f21cc 100644 --- a/ui/src/tray.rs +++ b/ui/src/tray.rs @@ -413,7 +413,9 @@ impl Tray for VietTray { }), options: vec![ RadioItem { - label: "Telex".into(), + label: "Telex (next version)".into(), + enabled: false, + disposition: Disposition::Informative, ..Default::default() }, RadioItem { diff --git a/uinputd/Cargo.toml b/uinputd/Cargo.toml index 3df7d0b..ce2dc4b 100644 --- a/uinputd/Cargo.toml +++ b/uinputd/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vietc-uinputd" -version = "0.1.0" +version = "0.1.6" edition = "2021" description = "Viet+ privileged uinput backspace injection daemon"