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`.
|
||||
- **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.
|
||||
- **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)
|
||||
|
||||
System tray icon + daemon. Find **"Viet+"** in your app menu to launch, or run from terminal.
|
||||
|
||||
```bash
|
||||
# Download from the release page, then:
|
||||
# Install
|
||||
flatpak install --user --bundle VietPlus-x86_64.flatpak
|
||||
|
||||
# Launch via app menu, or:
|
||||
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.
|
||||
|
|
@ -294,7 +301,7 @@ flatpak install --user flathub org.gnome.Sdk//50
|
|||
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.
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,114 @@ use std::collections::HashMap;
|
|||
use std::fs;
|
||||
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
|
||||
/// application). Returns a unique window-identifier 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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,10 +24,10 @@ BUILD='export PATH=/usr/lib/sdk/rust-stable/bin:$PATH
|
|||
export CARGO_HOME=/app/cargo
|
||||
cd /app/src/vietc'
|
||||
|
||||
# Build daemon + CLI + uinputd
|
||||
# Build daemon + CLI + uinputd + tray
|
||||
echo ""
|
||||
echo "=== Compiling daemon, CLI, uinputd... ==="
|
||||
flatpak build --share=network build-dir sh -c "$BUILD && cargo build --release -p vietc-daemon -p vietc-cli -p vietc-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 && cargo build --release --manifest-path ui/Cargo.toml"
|
||||
|
||||
# Install files
|
||||
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-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
|
||||
|
|
@ -47,7 +48,7 @@ install -Dm644 /app/src/vietc/packaging/icons/vietc-vn.svg /app/share/icons/hico
|
|||
[Desktop Entry]
|
||||
Name=Viet+
|
||||
Comment=Vietnamese Input Method
|
||||
Exec=/app/bin/vietc-daemon
|
||||
Exec=/app/bin/vietc-tray
|
||||
Icon=io.github.vietc.VietPlus
|
||||
Terminal=false
|
||||
Type=Application
|
||||
|
|
@ -85,7 +86,7 @@ flatpak build-finish build-dir \
|
|||
--talk-name=org.freedesktop.Notifications \
|
||||
--talk-name=org.a11y.Bus \
|
||||
--talk-name=org.freedesktop.portal.Clipboard \
|
||||
--command=vietc-daemon
|
||||
--command=vietc-tray
|
||||
|
||||
# Export
|
||||
echo ""
|
||||
|
|
@ -104,3 +105,4 @@ 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"
|
||||
|
|
|
|||
|
|
@ -12,20 +12,31 @@ fn exe_dir() -> PathBuf {
|
|||
}
|
||||
|
||||
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() {
|
||||
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()
|
||||
}
|
||||
|
||||
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")
|
||||
.arg("-x")
|
||||
.arg("vietc")
|
||||
.arg(name)
|
||||
.status()
|
||||
.map(|s| s.success())
|
||||
.unwrap_or(false)
|
||||
};
|
||||
check("vietc") || check("vietc-daemon")
|
||||
}
|
||||
|
||||
fn needs_root() -> bool {
|
||||
|
|
|
|||
Loading…
Reference in a new issue