feat: Flatpak tray, X11 dlopen window query, desktop menu entry
- Add system tray (vietc-tray) to Flatpak build; command changed to
vietc-tray which spawns the daemon
- Desktop menu entry: Viet+ appears in app launcher for search/install/uninstall
- Tray fixes: find_sibling_binary tries {name}-daemon fallback for Flatpak;
is_daemon_running checks both vietc and vietc-daemon process names
- Native X11 _NET_ACTIVE_WINDOW via dlopen(libX11.so.6) — third fallback in
get_active_window_id() that works inside Flatpak sandbox (no xdotool/xprop)
- Update README with install/uninstall commands
- Update CHANGELOG
This commit is contained in:
parent
1198d65543
commit
98ce9def79
5 changed files with 156 additions and 16 deletions
11
CHANGELOG.md
11
CHANGELOG.md
|
|
@ -14,9 +14,16 @@
|
||||||
- **Engine dead code removed** — unused methods `is_empty`, `is_tone_or_mark_key`, `process_string`, `last_base_char`, `apply_cluster_mark`, `apply_mark` in `BambooEngine`; `RuleEffect` enum and `special_rules` field in `InputMethodRules`.
|
- **Engine dead code removed** — unused methods `is_empty`, `is_tone_or_mark_key`, `process_string`, `last_base_char`, `apply_cluster_mark`, `apply_mark` in `BambooEngine`; `RuleEffect` enum and `special_rules` field in `InputMethodRules`.
|
||||||
- **Production logging** — per-key `eprintln!` removed from evdev loop and uinput paste path. Only startup/error/window-change messages remain (`log_info` to both stderr and file).
|
- **Production logging** — per-key `eprintln!` removed from evdev loop and uinput paste path. Only startup/error/window-change messages remain (`log_info` to both stderr and file).
|
||||||
|
|
||||||
### Flatpak Build
|
### Flatpak Build & System Tray
|
||||||
|
- **System tray** (`vietc-tray` using ksni/DBus StatusNotifier) is now built and included in the Flatpak bundle. The tray launches the daemon and shows Vietnamese/English mode.
|
||||||
|
- **Desktop menu entry** — the app now appears when searching **"Viet+"** in the application menu. Search, launch, or uninstall from there.
|
||||||
|
- **Flatpak command** changed from `vietc-daemon` to `vietc-tray` (the tray spawns the daemon).
|
||||||
|
- **Tray fixes for Flatpak** — `find_sibling_binary()` now tries `{name}-daemon` fallback; `is_daemon_running()` checks both `vietc` and `vietc-daemon` process names.
|
||||||
- **Fixed `mkdir -p`** — `build-flatpak.sh` now creates `/app/share/applications` before installing the desktop file.
|
- **Fixed `mkdir -p`** — `build-flatpak.sh` now creates `/app/share/applications` before installing the desktop file.
|
||||||
- **Bundle**: `VietPlus-0.1.5.flatpak` (47 MB, runtime `org.gnome.Platform//50`). Warning-free build with default Rust profile (no `#![allow()]` needed).
|
|
||||||
|
### Active Window Detection (Flatpak fix)
|
||||||
|
- **Native X11 `_NET_ACTIVE_WINDOW` query** via `dlopen("libX11.so.6")` — added as third fallback in `get_active_window_id()`. Works inside the Flatpak sandbox where `xdotool`/`xprop` are unavailable. No subprocess, no external dependencies.
|
||||||
|
- **Bundle**: `VietPlus-0.1.5.flatpak` (66 MB with tray, runtime `org.gnome.Platform//50`). Warning-free build.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
11
README.md
11
README.md
|
|
@ -270,10 +270,17 @@ Flexible typing: type the full syllable, then add marks/tone keys at the end. Ex
|
||||||
|
|
||||||
### Flatpak (recommended)
|
### Flatpak (recommended)
|
||||||
|
|
||||||
|
System tray icon + daemon. Find **"Viet+"** in your app menu to launch, or run from terminal.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Download from the release page, then:
|
# Install
|
||||||
flatpak install --user --bundle VietPlus-x86_64.flatpak
|
flatpak install --user --bundle VietPlus-x86_64.flatpak
|
||||||
|
|
||||||
|
# Launch via app menu, or:
|
||||||
flatpak run io.github.vietc.VietPlus
|
flatpak run io.github.vietc.VietPlus
|
||||||
|
|
||||||
|
# Uninstall
|
||||||
|
flatpak uninstall --user io.github.vietc.VietPlus
|
||||||
```
|
```
|
||||||
|
|
||||||
Includes daemon + CLI + system tray + uinput daemon. Sandboxed — no system libraries are touched.
|
Includes daemon + CLI + system tray + uinput daemon. Sandboxed — no system libraries are touched.
|
||||||
|
|
@ -294,7 +301,7 @@ flatpak install --user flathub org.gnome.Sdk//50
|
||||||
flatpak install --user flathub org.freedesktop.Sdk.Extension.rust-stable//25.08
|
flatpak install --user flathub org.freedesktop.Sdk.Extension.rust-stable//25.08
|
||||||
```
|
```
|
||||||
|
|
||||||
The Flatpak build now produces a warning-free bundle (~47 MB compressed). No external runtime dependencies are needed — everything is sandboxed.
|
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.
|
See `packaging/flatpak/FLATPAK_BUILD.md` for detailed build instructions.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,114 @@ use std::collections::HashMap;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
|
/// Query _NET_ACTIVE_WINDOW directly via X11 client library (dlopen).
|
||||||
|
/// Works inside the Flatpak sandbox where xdotool/xprop are unavailable
|
||||||
|
/// but libX11.so.6 is present in the GNOME runtime. No external process
|
||||||
|
/// or subclassing needed — open display, query property, return hex ID.
|
||||||
|
fn get_active_window_x11_dlopen() -> Option<String> {
|
||||||
|
unsafe {
|
||||||
|
let lib = libc::dlopen(
|
||||||
|
b"libX11.so.6\0".as_ptr() as *const libc::c_char,
|
||||||
|
libc::RTLD_LAZY,
|
||||||
|
);
|
||||||
|
if lib.is_null() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
type FnOpenDisplay =
|
||||||
|
unsafe extern "C" fn(*const libc::c_char) -> *mut libc::c_void;
|
||||||
|
type FnDefaultRoot =
|
||||||
|
unsafe extern "C" fn(*mut libc::c_void) -> u64;
|
||||||
|
type FnInternAtom = unsafe extern "C" fn(
|
||||||
|
*mut libc::c_void, *const libc::c_char, libc::c_int,
|
||||||
|
) -> u64;
|
||||||
|
type FnGetProperty = unsafe extern "C" fn(
|
||||||
|
*mut libc::c_void, u64, u64, u64, u64, u64, libc::c_int,
|
||||||
|
*mut u64, *mut libc::c_int, *mut u64, *mut u64,
|
||||||
|
*mut *mut u8,
|
||||||
|
) -> libc::c_int;
|
||||||
|
type FnFree = unsafe extern "C" fn(*mut libc::c_void) -> libc::c_int;
|
||||||
|
type FnCloseDisplay =
|
||||||
|
unsafe extern "C" fn(*mut libc::c_void) -> libc::c_int;
|
||||||
|
|
||||||
|
macro_rules! dlsym_fn {
|
||||||
|
($lib:expr, $name:literal) => {
|
||||||
|
std::mem::transmute::<*mut libc::c_void, _>(libc::dlsym(
|
||||||
|
$lib,
|
||||||
|
concat!($name, "\0").as_ptr() as *const libc::c_char,
|
||||||
|
))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let xopen: FnOpenDisplay = dlsym_fn!(lib, "XOpenDisplay");
|
||||||
|
let xroot: FnDefaultRoot = dlsym_fn!(lib, "XDefaultRootWindow");
|
||||||
|
let xatom: FnInternAtom = dlsym_fn!(lib, "XInternAtom");
|
||||||
|
let xgetprop: FnGetProperty = dlsym_fn!(lib, "XGetProperty");
|
||||||
|
let xfree: FnFree = dlsym_fn!(lib, "XFree");
|
||||||
|
let xclosedpy: FnCloseDisplay = dlsym_fn!(lib, "XCloseDisplay");
|
||||||
|
|
||||||
|
let dpy = xopen(std::ptr::null());
|
||||||
|
if dpy.is_null() {
|
||||||
|
libc::dlclose(lib);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let root = xroot(dpy);
|
||||||
|
let net_active = xatom(
|
||||||
|
dpy,
|
||||||
|
b"_NET_ACTIVE_WINDOW\0".as_ptr() as *const libc::c_char,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
// XA_WINDOW = 33 (the standard X11 atom for Window type)
|
||||||
|
let xa_window: u64 = 33;
|
||||||
|
let mut actual_type: u64 = 0;
|
||||||
|
let mut actual_format: libc::c_int = 0;
|
||||||
|
let mut nitems: u64 = 0;
|
||||||
|
let mut bytes_after: u64 = 0;
|
||||||
|
let mut data: *mut u8 = std::ptr::null_mut();
|
||||||
|
|
||||||
|
let status = xgetprop(
|
||||||
|
dpy,
|
||||||
|
root,
|
||||||
|
net_active,
|
||||||
|
xa_window,
|
||||||
|
0, // offset
|
||||||
|
1, // length
|
||||||
|
0, // delete
|
||||||
|
&mut actual_type,
|
||||||
|
&mut actual_format,
|
||||||
|
&mut nitems,
|
||||||
|
&mut bytes_after,
|
||||||
|
&mut data,
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = if status != 0
|
||||||
|
&& !data.is_null()
|
||||||
|
&& nitems > 0
|
||||||
|
&& actual_format == 32
|
||||||
|
{
|
||||||
|
// Format=32 elements are returned as unsigned long arrays
|
||||||
|
let id = *(data as *const u64);
|
||||||
|
if id != 0 {
|
||||||
|
Some(format!("0x{:x}", id))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if !data.is_null() {
|
||||||
|
xfree(data as *mut libc::c_void);
|
||||||
|
}
|
||||||
|
xclosedpy(dpy);
|
||||||
|
libc::dlclose(lib);
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the active window's X11 ID (unique per window — even within the same
|
/// Get the active window's X11 ID (unique per window — even within the same
|
||||||
/// application). Returns a unique window-identifier string.
|
/// application). Returns a unique window-identifier string.
|
||||||
pub fn get_active_window_id() -> Option<String> {
|
pub fn get_active_window_id() -> Option<String> {
|
||||||
|
|
@ -36,6 +144,11 @@ pub fn get_active_window_id() -> Option<String> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Final fallback: direct X11 client library query (works in Flatpak sandbox)
|
||||||
|
if let Some(id) = get_active_window_x11_dlopen() {
|
||||||
|
return Some(id);
|
||||||
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,10 @@ BUILD='export PATH=/usr/lib/sdk/rust-stable/bin:$PATH
|
||||||
export CARGO_HOME=/app/cargo
|
export CARGO_HOME=/app/cargo
|
||||||
cd /app/src/vietc'
|
cd /app/src/vietc'
|
||||||
|
|
||||||
# Build daemon + CLI + uinputd
|
# Build daemon + CLI + uinputd + tray
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Compiling daemon, CLI, uinputd... ==="
|
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"
|
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
|
# Install files
|
||||||
echo ""
|
echo ""
|
||||||
|
|
@ -37,6 +37,7 @@ set -e
|
||||||
install -Dm755 /app/src/vietc/target/release/vietc /app/bin/vietc-daemon
|
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-cli /app/bin/vietc-cli
|
||||||
install -Dm755 /app/src/vietc/target/release/vietc-uinputd /app/bin/vietc-uinputd
|
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.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-vn.svg /app/share/icons/hicolor/scalable/apps/io.github.vietc.VietPlus.vietc-vn.svg
|
||||||
|
|
@ -47,7 +48,7 @@ install -Dm644 /app/src/vietc/packaging/icons/vietc-vn.svg /app/share/icons/hico
|
||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Name=Viet+
|
Name=Viet+
|
||||||
Comment=Vietnamese Input Method
|
Comment=Vietnamese Input Method
|
||||||
Exec=/app/bin/vietc-daemon
|
Exec=/app/bin/vietc-tray
|
||||||
Icon=io.github.vietc.VietPlus
|
Icon=io.github.vietc.VietPlus
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Type=Application
|
Type=Application
|
||||||
|
|
@ -85,7 +86,7 @@ flatpak build-finish build-dir \
|
||||||
--talk-name=org.freedesktop.Notifications \
|
--talk-name=org.freedesktop.Notifications \
|
||||||
--talk-name=org.a11y.Bus \
|
--talk-name=org.a11y.Bus \
|
||||||
--talk-name=org.freedesktop.portal.Clipboard \
|
--talk-name=org.freedesktop.portal.Clipboard \
|
||||||
--command=vietc-daemon
|
--command=vietc-tray
|
||||||
|
|
||||||
# Export
|
# Export
|
||||||
echo ""
|
echo ""
|
||||||
|
|
@ -104,3 +105,4 @@ echo "Size: $(du -h "$SCRIPT_DIR/VietPlus-${VERSION}.flatpak" | cut -f1)"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Install: flatpak install --user --bundle VietPlus-${VERSION}.flatpak"
|
echo "Install: flatpak install --user --bundle VietPlus-${VERSION}.flatpak"
|
||||||
echo "Run: flatpak run io.github.vietc.VietPlus"
|
echo "Run: flatpak run io.github.vietc.VietPlus"
|
||||||
|
echo "Search: 'Viet+' in app menu"
|
||||||
|
|
|
||||||
|
|
@ -12,20 +12,31 @@ fn exe_dir() -> PathBuf {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_sibling_binary(name: &str) -> String {
|
fn find_sibling_binary(name: &str) -> String {
|
||||||
let sibling = exe_dir().join(name);
|
let dir = exe_dir();
|
||||||
|
// Try exact name (e.g. "vietc" outside Flatpak)
|
||||||
|
let sibling = dir.join(name);
|
||||||
if sibling.exists() {
|
if sibling.exists() {
|
||||||
return sibling.to_string_lossy().into_owned();
|
return sibling.to_string_lossy().into_owned();
|
||||||
}
|
}
|
||||||
|
// Try name-daemon (e.g. "vietc-daemon" inside Flatpak)
|
||||||
|
let daemon = dir.join(format!("{}-daemon", name));
|
||||||
|
if daemon.exists() {
|
||||||
|
return daemon.to_string_lossy().into_owned();
|
||||||
|
}
|
||||||
name.to_string()
|
name.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_daemon_running() -> bool {
|
fn is_daemon_running() -> bool {
|
||||||
|
// Check both "vietc" (outside Flatpak) and "vietc-daemon" (inside Flatpak)
|
||||||
|
let check = |name: &str| -> bool {
|
||||||
std::process::Command::new("pgrep")
|
std::process::Command::new("pgrep")
|
||||||
.arg("-x")
|
.arg("-x")
|
||||||
.arg("vietc")
|
.arg(name)
|
||||||
.status()
|
.status()
|
||||||
.map(|s| s.success())
|
.map(|s| s.success())
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
|
};
|
||||||
|
check("vietc") || check("vietc-daemon")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn needs_root() -> bool {
|
fn needs_root() -> bool {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue