release: v0.1.6 — uinput-first injection, window-switch fix, Telex disabled
Some checks are pending
Build & Release / Build & test (push) Waiting to run
Build & Release / Build .deb (push) Blocked by required conditions

- uinput injection is now primary on X11 (XTest fallback)
- X11 XTest keycode offset +8 fixed for all send_keycode paths
- Window switch detection on every keystroke (no more gap > 100ms guard)
- Telex greyed out in tray with '(next version)' label
- Flatpak and AppImage removed; only .deb packaging
- All Cargo.toml versions bumped to 0.1.6
This commit is contained in:
Khoa Vo 2026-06-29 16:07:15 +07:00
parent 7d0b2e520c
commit 88d39b4475
25 changed files with 260 additions and 927 deletions

View file

@ -1,9 +1,9 @@
name: Build & Release 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: # without compiling on a local machine:
# - every push to main / pull request -> packages uploaded as workflow artifacts # - 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: on:
push: push:
branches: [main] branches: [main]
@ -40,7 +40,7 @@ jobs:
run: cargo test --release run: cargo test --release
package: package:
name: Build packages name: Build .deb
needs: test needs: test
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -71,31 +71,19 @@ jobs:
echo "short_sha=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT" echo "short_sha=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT"
echo "Building version $VERSION" 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 - name: Build .deb
run: bash packaging/deb/build-deb.sh "${{ steps.ver.outputs.version }}" 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 - name: Collect artifacts
run: | run: |
mkdir -p dist mkdir -p dist
cp packaging/deb/*.deb dist/ cp packaging/deb/*.deb dist/
cp packaging/appimage/*.AppImage dist/
ls -la dist ls -la dist
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: 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/* path: dist/*
if-no-files-found: error if-no-files-found: error

View file

@ -1,5 +1,30 @@
# Changelog # 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) ## v0.1.5 (2026-06-29)
### Window-Switch Engine Reset ### Window-Switch Engine Reset

View file

@ -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 core crates
build: build:
@ -76,26 +76,15 @@ install-config:
cp vietc.toml ~/.config/vietc/config.toml cp vietc.toml ~/.config/vietc/config.toml
@echo "Config installed to ~/.config/vietc/config.toml" @echo "Config installed to ~/.config/vietc/config.toml"
# Build .deb package (requires dpkg-deb) # Build .deb package
deb: deb:
VERSION=$$(grep '^version' engine/Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/') && \ VERSION=$$(grep '^version' engine/Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/') && \
bash packaging/deb/build-deb.sh "$$VERSION" 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 build artifacts
clean: clean:
cargo clean cargo clean
cd ui && cargo clean cd ui && cargo clean
rm -rf packaging/appimage/AppDir packaging/appimage/*.AppImage
# Format code # Format code
fmt: fmt:
@ -107,10 +96,6 @@ lint:
cargo clippy -- -D warnings cargo clippy -- -D warnings
cd ui && cargo clippy -- -D warnings cd ui && cargo clippy -- -D warnings
# Flatpak build
flatpak:
cd packaging/flatpak && bash build-flatpak.sh
# Show project structure # Show project structure
tree: tree:
@find . -type f \( -name "*.rs" -o -name "*.toml" \) | grep -v target | sort @find . -type f \( -name "*.rs" -o -name "*.toml" \) | grep -v target | sort

View file

@ -2,7 +2,7 @@
<img src="https://img.shields.io/badge/Platform-Linux-blue?style=for-the-badge" alt="Platform"> <img src="https://img.shields.io/badge/Platform-Linux-blue?style=for-the-badge" alt="Platform">
<img src="https://img.shields.io/badge/Language-Rust-orange?style=for-the-badge" alt="Rust"> <img src="https://img.shields.io/badge/Language-Rust-orange?style=for-the-badge" alt="Rust">
<img src="https://img.shields.io/badge/License-MIT-green?style=for-the-badge" alt="License"> <img src="https://img.shields.io/badge/License-MIT-green?style=for-the-badge" alt="License">
<img src="https://img.shields.io/badge/Version-0.1.5-purple?style=for-the-badge" alt="Version"> <img src="https://img.shields.io/badge/Version-0.1.6-purple?style=for-the-badge" alt="Version">
<img src="https://img.shields.io/badge/Tests-106_passing-brightgreen?style=for-the-badge" alt="Tests"> <img src="https://img.shields.io/badge/Tests-106_passing-brightgreen?style=for-the-badge" alt="Tests">
<img src="https://img.shields.io/badge/Event_Sourcing-✓-blueviolet?style=for-the-badge" alt="Event Sourcing"> <img src="https://img.shields.io/badge/Event_Sourcing-✓-blueviolet?style=for-the-badge" alt="Event Sourcing">
</p> </p>
@ -50,8 +50,8 @@ Physical Keyboard
│ X11: XRecord passive monitoring (fallback) │ │ X11: XRecord passive monitoring (fallback) │
│ │ │ │
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────────┐ │ │ ┌─────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ evdev grab │ │ X11Capture │ │ FocusIn/FocusOut │ │ │ │ evdev grab │ │ X11Capture │ │ Window switch │ │
│ │ (libevdev) │ │ (XRecord) │ │ detection │ │ │ │ (libevdev) │ │ (XRecord) │ │ detection (250ms)│ │
│ └─────────────┘ └──────────────┘ └──────────────────┘ │ │ └─────────────┘ └──────────────┘ └──────────────────┘ │
└──────────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────────┘
@ -63,7 +63,7 @@ Physical Keyboard
│ Ctrl+Space → toggle Vietnamese ON/OFF │ │ Ctrl+Space → toggle Vietnamese ON/OFF │
│ Backspace → replay_backspace() │ │ Backspace → replay_backspace() │
│ Characters → replay_and_inject(ch) │ │ 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 │ │ 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 │ │ ASCII: direct Linux keycodes via /dev/uinput │
│ Backspace: Linux keycode 14 via uinput │ │ Backspace: Linux keycode 14 via uinput │
│ Vietnamese Unicode: clipboard paste + trailing ASCII via │ │ Vietnamese Unicode: clipboard paste + trailing ASCII via │
│ uinput (split only at whitespace/punctuation boundary) │ │ uinput (split only at whitespace/punctuation boundary) │
│ uinput Ctrl+V via /dev/uinput (no X11 dependency) │ │ 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() │ ├── engine.rs # Orchestrator + replay_events(), replay_events_to_commands()
│ ├── event.rs # Event Sourcing: InputEvent, EventStore, Command │ ├── event.rs # Event Sourcing: InputEvent, EventStore, Command
│ ├── bamboo.rs # Bamboo engine: transformation model, composition, tone placement │ ├── 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 │ └── spelling.rs # Vietnamese syllable validation
├── protocol/ # Keyboard capture & injection ├── protocol/ # Keyboard capture & injection
│ ├── inject.rs # KeyInjector trait │ ├── inject.rs # KeyInjector trait
│ ├── x11_capture.rs # XRecord keyboard capture via C helper │ ├── x11_capture.rs # XRecord keyboard capture via C helper
│ ├── x11_inject.rs # XTest injection + direct clipboard │ ├── x11_inject.rs # XTest injection (fallback)
│ ├── uinput_monitor.rs # /dev/uinput injection for ASCII + Unicode │ ├── uinput_monitor.rs # /dev/uinput injection (primary)
│ ├── uinput_client.rs # Unix socket client for vietc-uinputd │ ├── uinput_client.rs # Unix socket client for vietc-uinputd
│ └── wayland_im.rs # Wayland IM protocol │ └── wayland_im.rs # Wayland IM protocol
@ -157,7 +159,7 @@ vietc/
│ └── main.rs # Tray + daemon launcher │ └── main.rs # Tray + daemon launcher
├── cli/ # Interactive test harness ├── cli/ # Interactive test harness
├── packaging/ # AppImage + deb build scripts ├── packaging/ # .deb packaging scripts
└── vietc.toml # Default configuration └── vietc.toml # Default configuration
``` ```
@ -166,12 +168,12 @@ vietc/
``` ```
┌─────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────┐
│ vietc-tray │ │ vietc-tray │
│ (System tray icon, daemon launcher, password prompt) │ (System tray icon, daemon launcher)
└───────────────────────┬─────────────────────────────────────┘ └───────────────────────┬─────────────────────────────────────┘
│ starts │ starts
┌─────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────┐
│ vietc (daemon) │ vietc-daemon
│ │ │ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ Config │ │ App State │ │ Display │ │ │ │ Config │ │ App State │ │ Display │ │
@ -183,7 +185,7 @@ vietc/
│ ┌──────▼──────┐ │ │ ┌──────▼──────┐ │
│ │ Event Loop │ │ │ │ Event Loop │ │
│ │ │ │ │ │ │ │
│ │ X11: grab │ │ │ │ evdev: grab │ │
│ │ keyboard │ │ │ │ keyboard │ │
│ │ │ │ │ │ │ │
│ │ Process │ │ │ │ Process │ │
@ -198,12 +200,12 @@ vietc/
│ │ │ │
│ ┌────────────────────────────────────────────────────────┐ │ │ ┌────────────────────────────────────────────────────────┐ │
│ │ vietc-engine │ │ │ │ vietc-engine │ │
│ │ TelexEngine / VniEngine / EnglishDict / Spelling │ │ │ │ VniEngine / EnglishDict / Spelling │ │
│ └────────────────────────────────────────────────────────┘ │ │ └────────────────────────────────────────────────────────┘ │
│ │ │ │
│ ┌────────────────────────────────────────────────────────┐ │ │ ┌────────────────────────────────────────────────────────┐ │
│ │ vietc-protocol │ │ │ │ vietc-protocol │ │
│ │ X11Capture / X11Injector / UinputInjector / Wayland │ │ │ │ UinputInjector / X11Injector / X11Capture / Wayland │ │
│ └────────────────────────────────────────────────────────┘ │ │ └────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────┘
``` ```
@ -212,24 +214,7 @@ vietc/
## Input Methods ## Input Methods
### Telex ### VNI (default, Telex coming in next version)
| 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
| Key | Result | Example | | 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 | | 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 | | **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`) | | **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) | | **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 | | **Macro Expansion** | `ko``không`, `dc``được`, custom shortcuts |
| **Casing Preservation** | `Tieengs``Tiếng`, `TIEENGS``TIẾNG` | | **Casing Preservation** | `Tieengs``Tiếng`, `TIEENGS``TIẾNG` |
| **App Memory** | Per-app Vietnamese/English state, saved to `overrides.toml` | | **App Memory** | Per-app Vietnamese/English state, saved to `overrides.toml` |
| **Hot Reload** | Config changes apply without restart (polls mtime every 1.5s) | | **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 | | **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 ## 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 ```bash
# Install # Install
flatpak install --user --bundle VietPlus-x86_64.flatpak sudo dpkg -i vietc_0.1.6-1_amd64.deb
# Launch via app menu, or: # Log out and log back in (for input group membership to take effect)
flatpak run io.github.vietc.VietPlus # Then launch "Viet+" from your application menu
# Uninstall
flatpak uninstall --user io.github.vietc.VietPlus
``` ```
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 ```bash
git clone https://github.com/vndangkhoa/vietc.git git clone https://github.com/vndangkhoa/vietc.git
cd vietc/packaging/flatpak cd vietc
bash build-flatpak.sh [version] 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` Requires Rust toolchain, `pkg-config`, `libx11-dev`, `libxtst-dev`, `libevdev-dev`. See `packaging/deb/build-deb.sh` for details.
```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.
--- ---

View file

@ -3,7 +3,7 @@
## When to release ## When to release
- New feature or bugfix that should be distributed to users - New feature or bugfix that should be distributed to users
- Flatpak build changes validated - .deb packaging changes validated
- All tests passing (`cargo test`) - All tests passing (`cargo test`)
--- ---
@ -38,25 +38,29 @@ Add a new entry under the version heading:
- behavior changes... - behavior changes...
``` ```
### 3. Build the Flatpak ### 3. Build the .deb
```bash ```bash
cd packaging/flatpak make deb
bash build-flatpak.sh X.Y.Z
``` ```
Verify the bundle was created: Verify the package was created:
```bash
ls -lh VietPlus-X.Y.Z.flatpak
```
### 4. Test the Flatpak
```bash ```bash
flatpak install --user --bundle VietPlus-X.Y.Z.flatpak ls -lh packaging/deb/vietc_*.deb
flatpak run io.github.vietc.VietPlus
``` ```
### 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 ### 5. Commit and push
```bash ```bash
@ -67,12 +71,7 @@ git push origin main
### 6. Create a release on Forgejo/GitHub ### 6. Create a release on Forgejo/GitHub
Attach the Flatpak bundle (`VietPlus-X.Y.Z.flatpak`) as a release asset. Attach the .deb package (`vietc_X.Y.Z-1_amd64.deb`) as a release asset.
```bash
# Using forgejo-release (if configured)
# Or manually upload via the web UI
```
--- ---

View file

@ -1,6 +1,6 @@
[package] [package]
name = "vietc-cli" name = "vietc-cli"
version = "0.1.0" version = "0.1.6"
edition = "2021" edition = "2021"
description = "Viet+ CLI Test Harness" description = "Viet+ CLI Test Harness"

View file

@ -1,6 +1,6 @@
[package] [package]
name = "vietc-daemon" name = "vietc-daemon"
version = "0.1.0" version = "0.1.6"
edition = "2021" edition = "2021"
description = "Viet+ background daemon" description = "Viet+ background daemon"

View file

@ -196,7 +196,7 @@ impl Default for Config {
auto_restore: AutoRestoreConfig::default(), auto_restore: AutoRestoreConfig::default(),
app_state: AppStateConfig::default(), app_state: AppStateConfig::default(),
macros, macros,
grab: false, grab: false, // default false so daemon works without root (needs input group for uinput)
debug: false, debug: false,
} }
} }

View file

@ -1027,14 +1027,13 @@ fn run_with_evdev(
if active_window_id != last_active_window { if active_window_id != last_active_window {
new_window = Some(active_window_id.clone()); new_window = Some(active_window_id.clone());
} else if gap > std::time::Duration::from_millis(100) { } else {
// Background thread hasn't caught up yet — poll xdotool directly // 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 let Some(id) = app_state::get_active_window_id() {
if id != active_window_id { if id != active_window_id {
new_window = Some(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)); std::thread::sleep(std::time::Duration::from_millis(20));
} }
} }
fn create_injector( fn create_injector(
display: display::DisplayServer, display: display::DisplayServer,
) -> Result<Box<dyn vietc_protocol::KeyInjector>, Box<dyn std::error::Error>> { ) -> Result<Box<dyn vietc_protocol::KeyInjector>, Box<dyn std::error::Error>> {
// Try uinputd socket first // Prefer uinput injection — uses correct Linux keycodes for backspace
if vietc_protocol::uinput_client::UinputClient::is_available() { // and ASCII, works on both X11 and Wayland (uinput devices are routed
log_info("[vietc] Using uinputd socket injection"); // through libinput on modern X11).
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.
match vietc_protocol::uinput_monitor::UinputInjector::new("vietc") { match vietc_protocol::uinput_monitor::UinputInjector::new("vietc") {
Ok(injector) => { Ok(injector) => {
log_info("[vietc] Using uinput injection (primary)"); log_info("[vietc] Using uinput injection");
return Ok(Box::new(injector)); return Ok(Box::new(injector));
} }
Err(e) => { Err(e) => {
@ -1287,9 +1279,15 @@ 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")] #[cfg(feature = "x11")]
{
if display != display::DisplayServer::Wayland { if display != display::DisplayServer::Wayland {
match vietc_protocol::x11_inject::X11Injector::new() { match vietc_protocol::x11_inject::X11Injector::new() {
Ok(injector) => { Ok(injector) => {
@ -1301,7 +1299,6 @@ fn create_injector(
} }
} }
} }
}
Err("No injection backend available".into()) Err("No injection backend available".into())
} }

View file

@ -1,6 +1,6 @@
[package] [package]
name = "vietc-engine" name = "vietc-engine"
version = "0.1.0" version = "0.1.6"
edition = "2021" edition = "2021"
description = "Viet+ Vietnamese IME Core Engine" description = "Viet+ Vietnamese IME Core Engine"

View file

@ -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'
<?xml version="1.0" encoding="UTF-8"?>
<component type="console-application">
<id>io.github.anomalyco.vietc</id>
<name>Viet+</name>
<summary>Vietnamese Input Method for Linux</summary>
<description>
<p>Zero-configuration Vietnamese input method engine supporting Telex and VNI input methods. Works natively on both X11 and Wayland via evdev uinput injection.</p>
</description>
<metadata_license>MIT</metadata_license>
<project_license>MIT</project_license>
<url type="homepage">https://github.com/anomalyco/vietc</url>
<provides><binary>vietc</binary></provides>
<categories><category>Utility</category></categories>
</component>
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'
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="256" height="256">
<rect x="20" y="60" width="216" height="140" rx="16" fill="#2d2d2d" stroke="#1a1a1a" stroke-width="4"/>
<rect x="36" y="76" width="184" height="108" rx="8" fill="#3d3d3d"/>
<rect x="48" y="88" width="24" height="20" rx="3" fill="#f0f0f0"/>
<rect x="78" y="88" width="24" height="20" rx="3" fill="#f0f0f0"/>
<rect x="108" y="88" width="24" height="20" rx="3" fill="#f0f0f0"/>
<rect x="138" y="88" width="24" height="20" rx="3" fill="#f0f0f0"/>
<rect x="168" y="88" width="24" height="20" rx="3" fill="#f0f0f0"/>
<rect x="198" y="88" width="24" height="20" rx="3" fill="#f0f0f0"/>
<rect x="54" y="114" width="24" height="20" rx="3" fill="#f0f0f0"/>
<rect x="84" y="114" width="24" height="20" rx="3" fill="#f0f0f0"/>
<rect x="114" y="114" width="24" height="20" rx="3" fill="#f0f0f0"/>
<rect x="144" y="114" width="24" height="20" rx="3" fill="#f0f0f0"/>
<rect x="174" y="114" width="24" height="20" rx="3" fill="#f0f0f0"/>
<rect x="60" y="140" width="24" height="20" rx="3" fill="#f0f0f0"/>
<rect x="90" y="140" width="24" height="20" rx="3" fill="#f0f0f0"/>
<rect x="120" y="140" width="24" height="20" rx="3" fill="#f0f0f0"/>
<rect x="150" y="140" width="24" height="20" rx="3" fill="#f0f0f0"/>
<rect x="180" y="140" width="42" height="20" rx="3" fill="#f0f0f0"/>
<rect x="72" y="166" width="112" height="16" rx="3" fill="#f0f0f0"/>
<circle cx="216" cy="48" r="28" fill="#da251d"/>
<text x="216" y="56" text-anchor="middle" fill="white" font-size="18" font-weight="bold" font-family="sans-serif">VN</text>
</svg>
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

View file

@ -3,7 +3,7 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
VERSION="${1:-0.1.0}" VERSION="${1:-0.1.6}"
PACKAGE="vietc_${VERSION}-1_amd64" PACKAGE="vietc_${VERSION}-1_amd64"
STAGING="$SCRIPT_DIR/$PACKAGE" STAGING="$SCRIPT_DIR/$PACKAGE"
@ -12,7 +12,7 @@ echo "=== Building Viet+ .deb package v${VERSION} ==="
# Build binaries (all features: x11 + wayland) # Build binaries (all features: x11 + wayland)
echo "[1/5] Building binaries..." echo "[1/5] Building binaries..."
cargo build --release --features "x11,wayland" --manifest-path "$PROJECT_ROOT/Cargo.toml" 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." echo " Done."
# Clean and create staging # 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/icons/hicolor/256x256/apps"
mkdir -p "$STAGING/usr/share/doc/vietc" mkdir -p "$STAGING/usr/share/doc/vietc"
mkdir -p "$STAGING/usr/share/metainfo" mkdir -p "$STAGING/usr/share/metainfo"
mkdir -p "$STAGING/etc/xdg/autostart"
# Copy binaries # Copy binaries
echo "[3/5] Installing 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/" 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/" 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) # 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" "$SCRIPT_DIR/vietc-xrecord.c" -lX11 -lXtst
gcc -O2 -o "$STAGING/usr/bin/vietc-xrecord" "$PROJECT_ROOT/packaging/appimage/vietc-xrecord.c" -lX11 -lXtst \
&& echo " vietc-xrecord compiled" \ # Icons (main app icon + tray status icons)
|| echo " WARNING: vietc-xrecord compile failed (libX11/libXtst dev headers missing)" cp "$PROJECT_ROOT/packaging/icons/vietc.svg" "$STAGING/usr/share/icons/hicolor/256x256/apps/"
else cp "$PROJECT_ROOT/packaging/icons/vietc-vn.svg" "$STAGING/usr/share/icons/hicolor/256x256/apps/"
echo " WARNING: no gcc, vietc-xrecord not bundled" cp "$PROJECT_ROOT/packaging/icons/vietc-en.svg" "$STAGING/usr/share/icons/hicolor/256x256/apps/"
fi
# Desktop file # 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) # XDG autostart — launches tray on every login for all users
cat > "$STAGING/usr/share/icons/hicolor/256x256/apps/vietc.svg" << 'SVGEOF' cat > "$STAGING/etc/xdg/autostart/vietc-tray.desktop" << 'AUTOSTART'
<?xml version="1.0" encoding="UTF-8"?> [Desktop Entry]
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="256" height="256"> Type=Application
<rect x="20" y="60" width="216" height="140" rx="16" fill="#2d2d2d" stroke="#1a1a1a" stroke-width="4"/> Name=Viet+ Tray
<rect x="36" y="76" width="184" height="108" rx="8" fill="#3d3d3d"/> Comment=Vietnamese Input Method Tray
<rect x="48" y="88" width="24" height="20" rx="3" fill="#f0f0f0"/> Exec=vietc-tray
<rect x="78" y="88" width="24" height="20" rx="3" fill="#f0f0f0"/> Icon=vietc
<rect x="108" y="88" width="24" height="20" rx="3" fill="#f0f0f0"/> Terminal=false
<rect x="138" y="88" width="24" height="20" rx="3" fill="#f0f0f0"/> Categories=Utility;
<rect x="168" y="88" width="24" height="20" rx="3" fill="#f0f0f0"/> StartupNotify=false
<rect x="198" y="88" width="24" height="20" rx="3" fill="#f0f0f0"/> NoDisplay=true
<rect x="54" y="114" width="24" height="20" rx="3" fill="#f0f0f0"/> AUTOSTART
<rect x="84" y="114" width="24" height="20" rx="3" fill="#f0f0f0"/>
<rect x="114" y="114" width="24" height="20" rx="3" fill="#f0f0f0"/>
<rect x="144" y="114" width="24" height="20" rx="3" fill="#f0f0f0"/>
<rect x="174" y="114" width="24" height="20" rx="3" fill="#f0f0f0"/>
<rect x="60" y="140" width="24" height="20" rx="3" fill="#f0f0f0"/>
<rect x="90" y="140" width="24" height="20" rx="3" fill="#f0f0f0"/>
<rect x="120" y="140" width="24" height="20" rx="3" fill="#f0f0f0"/>
<rect x="150" y="140" width="24" height="20" rx="3" fill="#f0f0f0"/>
<rect x="180" y="140" width="42" height="20" rx="3" fill="#f0f0f0"/>
<rect x="72" y="166" width="112" height="16" rx="3" fill="#f0f0f0"/>
<circle cx="216" cy="48" r="28" fill="#da251d"/>
<text x="216" y="56" text-anchor="middle" fill="white" font-size="18" font-weight="bold" font-family="sans-serif">VN</text>
</svg>
SVGEOF
# Documentation # Documentation
cp "$PROJECT_ROOT/README.md" "$STAGING/usr/share/doc/vietc/" cp "$PROJECT_ROOT/README.md" "$STAGING/usr/share/doc/vietc/"
@ -82,8 +67,21 @@ cp "$PROJECT_ROOT/LICENSE" "$STAGING/usr/share/doc/vietc/"
# Config # Config
cp "$PROJECT_ROOT/vietc.toml" "$STAGING/etc/vietc/config.toml" cp "$PROJECT_ROOT/vietc.toml" "$STAGING/etc/vietc/config.toml"
# Systemd user service # Systemd user service — tray spawns the daemon internally
cp "$PROJECT_ROOT/vietc.service" "$STAGING/usr/lib/systemd/user/" 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 # AppStream metadata
cat > "$STAGING/usr/share/metainfo/io.github.anomalyco.vietc.appdata.xml" << 'XML' cat > "$STAGING/usr/share/metainfo/io.github.anomalyco.vietc.appdata.xml" << 'XML'
@ -124,7 +122,7 @@ Section: utils
Priority: optional Priority: optional
Architecture: amd64 Architecture: amd64
Depends: libc6 (>= 2.31), libevdev2 (>= 1.9.0) 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 <vndangkhoa@gmail.com> Maintainer: Khoa Vo <vndangkhoa@gmail.com>
Description: Viet+ — Vietnamese Input Method for Linux Description: Viet+ — Vietnamese Input Method for Linux
Zero-configuration Vietnamese input method engine supporting Zero-configuration Vietnamese input method engine supporting
@ -140,10 +138,73 @@ echo "/etc/vietc/config.toml" > "$STAGING/DEBIAN/conffiles"
cat > "$STAGING/DEBIAN/postinst" << 'POSTINST' cat > "$STAGING/DEBIAN/postinst" << 'POSTINST'
#!/bin/sh #!/bin/sh
set -e 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 case "$1" in
configure) 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 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 fi
;; ;;
esac esac
@ -156,7 +217,7 @@ set -e
case "$1" in case "$1" in
remove|upgrade|deconfigure) remove|upgrade|deconfigure)
if command -v systemctl >/dev/null 2>&1; then 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 fi
;; ;;
esac esac

View file

@ -3,9 +3,9 @@ Type=Application
Name=Viet+ Name=Viet+
GenericName=Vietnamese Input Method GenericName=Vietnamese Input Method
Comment=Vietnamese Input Method for Linux Zero underline, native Wayland/X11 Comment=Vietnamese Input Method for Linux Zero underline, native Wayland/X11
Exec=vietc Exec=vietc-tray
Icon=vietc Icon=vietc
Terminal=false Terminal=false
Categories=Utility; Categories=Utility;TextTools;X-GNOME-Utilities;
Keywords=vietnamese;input;ime;keyboard; Keywords=vietnamese;input;ime;keyboard;viet;gõ tiếng vit;
StartupNotify=false StartupNotify=true

View file

@ -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-<version>.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

View file

@ -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'
<?xml version='1.0' encoding='utf-8'?>
<component type='desktop-application'>
<id>io.github.vietc.VietPlus</id>
<name>Viet+</name>
<summary>Vietnamese Input Method for Linux</summary>
<description>
<p>Zero-configuration Vietnamese input method engine supporting Telex and VNI input methods.</p>
</description>
<metadata_license>MIT</metadata_license>
<project_license>MIT</project_license>
<url type='homepage'>https://github.com/vndangkhoa/vietc</url>
<provides><binary>vietc-daemon</binary></provides>
<categories><category>Utility</category></categories>
</component>
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"

View file

@ -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<?xml version='1.0' encoding='utf-8'?>\n<component type='desktop-application'>\n <id>io.github.vietc.VietPlus</id>\n <name>Viet+</name>\n <summary>Vietnamese Input Method for Linux</summary>\n <description>\n <p>Zero-configuration Vietnamese input method engine supporting Telex and VNI input methods.</p>\n </description>\n <metadata_license>MIT</metadata_license>\n <project_license>MIT</project_license>\n <url type='homepage'>https://github.com/vndangkhoa/vietc</url>\n <provides><binary>vietc-daemon</binary></provides>\n <categories><category>Utility</category></categories>\n</component>\nXML"
],
"sources": [
{
"type": "dir",
"path": "../.."
}
]
}
]
}

View file

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

View file

@ -1,6 +1,6 @@
[package] [package]
name = "vietc-protocol" name = "vietc-protocol"
version = "0.1.0" version = "0.1.6"
edition = "2021" edition = "2021"
description = "Viet+ keystroke injection backends (X11/Wayland)" description = "Viet+ keystroke injection backends (X11/Wayland)"

View file

@ -388,20 +388,19 @@ impl X11Injector {
// (unlikely at this point, but be safe) // (unlikely at this point, but be safe)
self.handle_pending_events(); self.handle_pending_events();
// Send backspaces via XTest // Send backspaces via XTest (X11 keycode 22 = backspace)
if backspaces > 0 { if backspaces > 0 {
for _ in 0..backspaces { 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 { unsafe {
// X11 keycodes: 37 = Ctrl_L, 55 = V (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, 37, 1, 0); (self.lib.x_test_fake_key_event)(self.display, 47 + 8, 1, 0); // V press
(self.lib.x_test_fake_key_event)(self.display, 55, 1, 0); (self.lib.x_test_fake_key_event)(self.display, 47 + 8, 0, 0); // V release
(self.lib.x_test_fake_key_event)(self.display, 55, 0, 0); (self.lib.x_test_fake_key_event)(self.display, 29 + 8, 0, 0); // Ctrl_L release
(self.lib.x_test_fake_key_event)(self.display, 37, 0, 0);
(self.lib.x_flush)(self.display); (self.lib.x_flush)(self.display);
} }
@ -416,15 +415,16 @@ impl X11Injector {
true true
} }
fn send_keycode(&self, keycode: u32, shift: bool) { fn send_keycode(&self, evdev_keycode: u32, shift: bool) {
let x11 = evdev_keycode + 8;
unsafe { unsafe {
if shift { 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, x11, 1, 0);
(self.lib.x_test_fake_key_event)(self.display, keycode, 0, 0); (self.lib.x_test_fake_key_event)(self.display, x11, 0, 0);
if shift { 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); (self.lib.x_flush)(self.display);
} }
@ -484,15 +484,17 @@ struct XSelectionNotifyEvent {
impl KeyInjector for X11Injector { impl KeyInjector for X11Injector {
fn send_key_event(&self, keycode: u16, value: i32) -> InjectResult { fn send_key_event(&self, keycode: u16, value: i32) -> InjectResult {
// X11 keycodes = Linux evdev keycodes + 8
let x11_keycode = keycode as u32 + 8;
unsafe { 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); (self.lib.x_flush)(self.display);
} }
InjectResult::Success InjectResult::Success
} }
fn send_backspace(&self) -> InjectResult { fn send_backspace(&self) -> InjectResult {
self.send_keycode(14, false); self.send_keycode(22, false); // X11 keycode 22 = backspace
InjectResult::Success InjectResult::Success
} }

View file

@ -1,6 +1,6 @@
[package] [package]
name = "vietc-tray" name = "vietc-tray"
version = "0.1.0" version = "0.1.6"
edition = "2021" edition = "2021"
description = "Viet+ system tray icon" description = "Viet+ system tray icon"

View file

@ -71,7 +71,7 @@ pub struct Config {
} }
fn default_input_method() -> String { fn default_input_method() -> String {
"telex".into() "vni".into()
} }
fn default_toggle_key() -> String { fn default_toggle_key() -> String {
"space".into() "space".into()
@ -80,7 +80,7 @@ fn default_start_enabled() -> bool {
true true
} }
fn default_grab() -> bool { fn default_grab() -> bool {
true false
} }
fn default_true() -> bool { fn default_true() -> bool {
true true
@ -150,6 +150,7 @@ fn config_paths() -> Vec<PathBuf> {
} }
paths.push(PathBuf::from("vietc.toml")); paths.push(PathBuf::from("vietc.toml"));
paths.push(PathBuf::from("/etc/vietc/config.toml"));
paths paths
} }

View file

@ -49,6 +49,14 @@ fn needs_root() -> bool {
// Inside Flatpak the sandbox already has device access; sudo won't work. // Inside Flatpak the sandbox already has device access; sudo won't work.
return false; 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(); let cfg = config::Config::load();
cfg.grab cfg.grab
} }

View file

@ -413,7 +413,9 @@ impl Tray for VietTray {
}), }),
options: vec![ options: vec![
RadioItem { RadioItem {
label: "Telex".into(), label: "Telex (next version)".into(),
enabled: false,
disposition: Disposition::Informative,
..Default::default() ..Default::default()
}, },
RadioItem { RadioItem {

View file

@ -1,6 +1,6 @@
[package] [package]
name = "vietc-uinputd" name = "vietc-uinputd"
version = "0.1.0" version = "0.1.6"
edition = "2021" edition = "2021"
description = "Viet+ privileged uinput backspace injection daemon" description = "Viet+ privileged uinput backspace injection daemon"