fix: Vietnamese mode default, Flatpak tray UX, Cinnamon menu entry
Some checks failed
Build & Release / Build & test (push) Has been cancelled
Build & Release / Build packages (push) Has been cancelled

- Change start_enabled default to true (Vietnamese active on launch)
- Tray: detect Flatpak sandbox, skip pointless password prompt
- Tray: write first-launch flag file always (not just after sudo)
- Desktop file: StartupNotify=true, wider categories for Cinnamon menu
- Update tests for new default, README config example
This commit is contained in:
Khoa Vo 2026-06-29 14:43:49 +07:00
parent 98ce9def79
commit 7d0b2e520c
5 changed files with 35 additions and 21 deletions

View file

@ -23,6 +23,14 @@
### Active Window Detection (Flatpak fix) ### 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. - **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.
### Default Mode
- **`start_enabled` now defaults to `true`** — Vietnamese mode is active immediately after launch. Press Ctrl+Space to toggle to English.
*(Existing users with a custom config.toml are unaffected — the explicit setting overrides the default.)*
### Tray & Desktop Entry
- **No password prompt inside Flatpak**`needs_root()` detects Flatpak sandbox (`FLATPAK_ID` or `/app/bin` presence) and skips sudo entirely; the sandbox already has device access via `--device=all`.
- **First-launch flag always written** — the `.first-launch-done` marker is created even when the password prompt is dismissed, preventing repeated prompts.
- **Desktop categories** widened to `Utility;TextTools;X-GNOME-Utilities;` for better visibility in Cinnamon/Mint app menu.
- **Bundle**: `VietPlus-0.1.5.flatpak` (66 MB with tray, runtime `org.gnome.Platform//50`). Warning-free build. - **Bundle**: `VietPlus-0.1.5.flatpak` (66 MB with tray, runtime `org.gnome.Platform//50`). Warning-free build.
--- ---

View file

@ -314,7 +314,7 @@ Config file: `~/.config/vietc/config.toml` or `./vietc.toml`
```toml ```toml
input_method = "vni" # "vni" or "telex" input_method = "vni" # "vni" or "telex"
toggle_key = "space" # Ctrl+Space to toggle toggle_key = "space" # Ctrl+Space to toggle
start_enabled = false # English by default start_enabled = true # Vietnamese by default
grab = true # grab keyboard (evdev) grab = true # grab keyboard (evdev)
[auto_restore] [auto_restore]

View file

@ -86,7 +86,7 @@ fn default_toggle_key() -> String {
"space".into() "space".into()
} }
fn default_start_enabled() -> bool { fn default_start_enabled() -> bool {
false true
} }
fn default_true() -> bool { fn default_true() -> bool {
true true
@ -262,7 +262,7 @@ vs = "với"
let config: Config = toml::from_str(toml).unwrap(); let config: Config = toml::from_str(toml).unwrap();
assert_eq!(config.input_method, "vni"); assert_eq!(config.input_method, "vni");
assert_eq!(config.toggle_key, "shift"); assert_eq!(config.toggle_key, "shift");
assert!(!config.start_enabled); assert!(!config.start_enabled); // explicitly set to false in test toml
assert!(!config.auto_restore.enabled); assert!(!config.auto_restore.enabled);
assert!(config.app_state.enabled); assert!(config.app_state.enabled);
assert_eq!(config.app_state.english_apps, vec!["code", "vim"]); assert_eq!(config.app_state.english_apps, vec!["code", "vim"]);
@ -281,7 +281,7 @@ vs = "với"
let config: Config = toml::from_str(toml).unwrap(); let config: Config = toml::from_str(toml).unwrap();
assert_eq!(config.input_method, "vni"); assert_eq!(config.input_method, "vni");
assert_eq!(config.toggle_key, "space"); assert_eq!(config.toggle_key, "space");
assert!(!config.start_enabled); assert!(config.start_enabled); // default changed to true
assert!(config.auto_restore.enabled); assert!(config.auto_restore.enabled);
assert!(config.app_state.enabled); assert!(config.app_state.enabled);
assert!(!config.app_state.english_apps.is_empty()); assert!(!config.app_state.english_apps.is_empty());
@ -296,7 +296,7 @@ input_method = "vni"
let config: Config = toml::from_str(toml).unwrap(); let config: Config = toml::from_str(toml).unwrap();
assert_eq!(config.input_method, "vni"); assert_eq!(config.input_method, "vni");
assert_eq!(config.toggle_key, "space"); // default assert_eq!(config.toggle_key, "space"); // default
assert!(!config.start_enabled); // default assert!(config.start_enabled); // default changed to true
} }
#[test] #[test]

View file

@ -52,7 +52,8 @@ Exec=/app/bin/vietc-tray
Icon=io.github.vietc.VietPlus Icon=io.github.vietc.VietPlus
Terminal=false Terminal=false
Type=Application Type=Application
Categories=Utility; StartupNotify=true
Categories=Utility;TextTools;X-GNOME-Utilities;
END END
mkdir -p /app/share/metainfo mkdir -p /app/share/metainfo

View file

@ -39,7 +39,16 @@ fn is_daemon_running() -> bool {
check("vietc") || check("vietc-daemon") check("vietc") || check("vietc-daemon")
} }
fn is_flatpak() -> bool {
std::env::var("FLATPAK_ID").is_ok()
|| std::path::Path::new("/app/bin").exists()
}
fn needs_root() -> bool { fn needs_root() -> bool {
if is_flatpak() {
// Inside Flatpak the sandbox already has device access; sudo won't work.
return false;
}
let cfg = config::Config::load(); let cfg = config::Config::load();
cfg.grab cfg.grab
} }
@ -114,18 +123,14 @@ fn prompt_password() -> String {
fn start_daemon() { fn start_daemon() {
let daemon_bin = find_sibling_binary("vietc"); let daemon_bin = find_sibling_binary("vietc");
if needs_root() && !is_daemon_running() { let flag_path = config_path().join(".first-launch-done");
// Mark that we've attempted first launch
let flag_path = config_path().join(".first-launch-done");
if !flag_path.exists() {
let password = prompt_password();
if password.is_empty() {
eprintln!("[vietc-tray] No password provided, starting daemon without root");
let _ = std::process::Command::new(&daemon_bin).spawn();
return;
}
if needs_root() && !is_daemon_running() && !flag_path.exists() {
let password = prompt_password();
if password.is_empty() {
eprintln!("[vietc-tray] No password provided, starting daemon without root");
let _ = std::process::Command::new(&daemon_bin).spawn();
} else {
// Start daemon with sudo // Start daemon with sudo
let mut child = match std::process::Command::new("sudo") let mut child = match std::process::Command::new("sudo")
.args(["-S", &daemon_bin]) .args(["-S", &daemon_bin])
@ -138,6 +143,7 @@ fn start_daemon() {
Err(e) => { Err(e) => {
eprintln!("[vietc-tray] Failed to start daemon with sudo: {}", e); eprintln!("[vietc-tray] Failed to start daemon with sudo: {}", e);
let _ = std::process::Command::new(&daemon_bin).spawn(); let _ = std::process::Command::new(&daemon_bin).spawn();
let _ = std::fs::write(&flag_path, "1");
return; return;
} }
}; };
@ -147,11 +153,10 @@ fn start_daemon() {
let _ = stdin.write_all(format!("{}\n", password).as_bytes()); let _ = stdin.write_all(format!("{}\n", password).as_bytes());
} }
let _ = child.wait(); let _ = child.wait();
// Mark first launch as done
let _ = std::fs::write(&flag_path, "1");
return;
} }
let _ = std::fs::write(&flag_path, "1");
return;
} }
if !is_daemon_running() { if !is_daemon_running() {