feat: terminal VNI input — force VNI in terminals, remove from bypass_apps
- Add terminal_apps / terminal_input_method to config - AppStateManager tracks global vs effective method - Engine uses effective method (VNI in terminals, global elsewhere) - Terminals removed from bypass_apps, moved to terminal_apps - Tray still shows global method (user's setting) - NOTE: NOTES/terminal-vni.md documents the design
This commit is contained in:
parent
7e5281244b
commit
3ccf243f52
6 changed files with 316 additions and 31 deletions
110
NOTES/terminal-vni.md
Normal file
110
NOTES/terminal-vni.md
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
# Terminal VNI Input — Design & Implementation
|
||||
|
||||
## Goal
|
||||
|
||||
Make Vietnamese input work in terminal emulators without breaking TUI keyboard shortcuts.
|
||||
|
||||
## Approach: A + C
|
||||
|
||||
### A — Remove terminals from `bypass_apps`
|
||||
|
||||
All terminals are currently in `bypass_apps` (default config), which skips ALL engine
|
||||
processing when the active window is a terminal. Removing them lets keystrokes flow
|
||||
through the bamboo engine.
|
||||
|
||||
### C — Force VNI when terminal detected
|
||||
|
||||
When the active window is a terminal, the engine automatically uses VNI rules
|
||||
(`1-9` for tones/marks) regardless of the global VNI/Telex setting.
|
||||
This avoids key conflicts with TUI apps (vim's `j`, less's `s`, shell's `x`, etc.).
|
||||
|
||||
## How It Works
|
||||
|
||||
```
|
||||
User config: input_method = "telex"
|
||||
Terminal window focused → effective method = "vni" (forced by terminal_apps)
|
||||
GUI window focused → effective method = "telex" (user's global setting)
|
||||
```
|
||||
|
||||
- **Engine** runs with effective method
|
||||
- **Tray** shows global method (so user sees their configured setting)
|
||||
- **Ctrl+LeftShift** toggles global method, recomputes effective method
|
||||
- **Ctrl+Space** toggles VN/EN as before
|
||||
|
||||
## Config Changes
|
||||
|
||||
```toml
|
||||
[app_state]
|
||||
terminal_apps = ["kitty", "alacritty", "foot", "wezterm", "konsole",
|
||||
"gnome-terminal", "gnome-terminal-server", "kgx", "st", "urxvt", "xterm",
|
||||
"termite", "terminator", "tilix", "deepin-terminal", "pantheon-terminal"]
|
||||
terminal_input_method = "vni"
|
||||
```
|
||||
|
||||
`bypass_apps` reduced to: `["steam", "dota", "csgo", "minecraft", "factorio"]`
|
||||
|
||||
## Implementation
|
||||
|
||||
### 1. `daemon/src/config.rs`
|
||||
|
||||
- Add `terminal_apps` (`Vec<String>`) and `terminal_input_method` (`String`) to `AppStateConfig`
|
||||
- Add `default_terminal_apps()` returning the terminal list
|
||||
- Add `default_terminal_method()` returning `"vni"`
|
||||
- Remove all terminal names from `default_bypass_apps()`
|
||||
|
||||
### 2. `daemon/src/app_state.rs`
|
||||
|
||||
- Add fields: `terminal_apps`, `terminal_input_method`, `global_method`, `effective_method`
|
||||
- `new()` accepts `terminal_apps`, `terminal_input_method`, `global_method`
|
||||
- `update_effective_method()`: if current_app matches any terminal, effective = terminal method; else effective = global method. Called on window change.
|
||||
- `set_terminal_config()`: updates terminal_apps/terminal_input_method from config reload
|
||||
- `set_global_method()`: updates global_method, recomputes effective
|
||||
- `effective_method()` getter
|
||||
- `is_terminal_app()` — checks if current_app is a terminal
|
||||
- `update_with_app()` calls `update_effective_method()` internally
|
||||
- `update_lists()` also handles terminal_apps
|
||||
|
||||
### 3. `daemon/src/main.rs`
|
||||
|
||||
- `Daemon::new()` — pass terminal config to `AppStateManager`, call `update_effective_method()`
|
||||
- `toggle_method()` — after toggling global method, call `app_state.set_global_method()` then `engine.set_method(app_state.effective_method())`
|
||||
- `check_app_change_with()` — after app change, if effective method changed from engine's current, call `engine.set_method(effective)`
|
||||
- `is_vn_control_key()` calls — change from `daemon.config.input_method` to `daemon.app_state.effective_method()`
|
||||
- Config reload — update `update_lists()` call to include terminal fields
|
||||
- Method status file — still writes **global** method (for tray display)
|
||||
|
||||
### 4. `install.sh` — Update default config block
|
||||
|
||||
### 5. `README.md` — Update config example
|
||||
|
||||
### 6. `NOTES/terminal-vni.md` — This file
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
### Linux Mint (X11)
|
||||
|
||||
- [ ] Type VNI in shell: `viet1 nam` → `viết nam`
|
||||
- [ ] Type Telex in shell: `vieets nam` → `vieets nam` (Telex NOT active in terminal)
|
||||
- [ ] Ctrl+Space toggles VN/EN
|
||||
- [ ] Ctrl+LeftShift toggles global method (terminal unaffected, tray shows global)
|
||||
- [ ] Vim insert mode: VNI works, `j`/`x`/`s` pass through as regular keys
|
||||
- [ ] Gemini-cli: VNI typed text appears correctly
|
||||
- [ ] sudo passwd: engine auto-disables
|
||||
- [ ] Switch terminal ↔ GUI: method resets per app
|
||||
- [ ] Tray icon shows global method, not terminal override
|
||||
|
||||
### Ubuntu 24.04+ (Wayland)
|
||||
|
||||
- [ ] Same VNI typing tests
|
||||
- [ ] GNOME Shell D-Bus window detection
|
||||
- [ ] wl-copy paste-once path for Unicode chars
|
||||
|
||||
## Edge Cases
|
||||
|
||||
| Case | Behavior |
|
||||
|------|----------|
|
||||
| Terminal in bypass_apps | No IME at all (configurable override for power users) |
|
||||
| User wants Telex in terminals | Set `terminal_input_method = "telex"` in config |
|
||||
| Multiple terminals open | Each follows the same rule |
|
||||
| IDE integrated terminal | Window class is "code", not terminal. Needs manual config |
|
||||
| Password prompt in terminal | Process-tree detection still disables engine regardless of method |
|
||||
|
|
@ -208,9 +208,12 @@ password_apps = ["pinentry", "pinentry-gtk-2", "pinentry-qt",
|
|||
|
||||
[app_state]
|
||||
enabled = true
|
||||
english_apps = ["code", "vim", "kitty", "foot"]
|
||||
english_apps = ["code", "vim"]
|
||||
vietnamese_apps = ["telegram", "discord", "firefox"]
|
||||
bypass_apps = ["kitty", "alacritty", "steam"]
|
||||
bypass_apps = ["steam"]
|
||||
terminal_apps = ["kitty", "alacritty", "gnome-terminal", "konsole", "foot",
|
||||
"wezterm", "st", "urxvt", "xterm"]
|
||||
terminal_input_method = "vni"
|
||||
|
||||
[macros]
|
||||
ko = "không"
|
||||
|
|
|
|||
|
|
@ -495,6 +495,14 @@ pub struct AppStateManager {
|
|||
vietnamese_apps: Vec<String>,
|
||||
/// Bypass apps from config
|
||||
bypass_apps: Vec<String>,
|
||||
/// Terminal emulator class names (force VNI mode)
|
||||
terminal_apps: Vec<String>,
|
||||
/// Input method forced in terminals
|
||||
terminal_input_method: String,
|
||||
/// User's global input method (VNI/Telex)
|
||||
global_method: String,
|
||||
/// Effective method after terminal override
|
||||
effective_method: String,
|
||||
/// Global enabled state
|
||||
global_enabled: bool,
|
||||
/// Password detection config
|
||||
|
|
@ -514,14 +522,27 @@ impl AppStateManager {
|
|||
english_apps: Vec<String>,
|
||||
vietnamese_apps: Vec<String>,
|
||||
bypass_apps: Vec<String>,
|
||||
terminal_apps: Vec<String>,
|
||||
terminal_input_method: String,
|
||||
global_method: String,
|
||||
global_enabled: bool,
|
||||
) -> Self {
|
||||
let effective_method = Self::compute_effective_method(
|
||||
&global_method,
|
||||
&terminal_input_method,
|
||||
&terminal_apps,
|
||||
"",
|
||||
);
|
||||
Self {
|
||||
current_app: String::new(),
|
||||
overrides: HashMap::new(),
|
||||
english_apps: english_apps.iter().map(|s| s.to_lowercase()).collect(),
|
||||
vietnamese_apps: vietnamese_apps.iter().map(|s| s.to_lowercase()).collect(),
|
||||
bypass_apps: bypass_apps.iter().map(|s| s.to_lowercase()).collect(),
|
||||
terminal_apps: terminal_apps.iter().map(|s| s.to_lowercase()).collect(),
|
||||
terminal_input_method,
|
||||
global_method,
|
||||
effective_method,
|
||||
global_enabled,
|
||||
password_enabled: false,
|
||||
check_atspi2: true,
|
||||
|
|
@ -533,6 +554,22 @@ impl AppStateManager {
|
|||
}
|
||||
}
|
||||
|
||||
/// Compute effective method: use terminal override if current_app is a terminal,
|
||||
/// otherwise use the global method.
|
||||
fn compute_effective_method(
|
||||
global_method: &str,
|
||||
terminal_method: &str,
|
||||
terminal_apps: &[String],
|
||||
current_app: &str,
|
||||
) -> String {
|
||||
for pattern in terminal_apps {
|
||||
if current_app.contains(pattern.as_str()) {
|
||||
return terminal_method.to_string();
|
||||
}
|
||||
}
|
||||
global_method.to_string()
|
||||
}
|
||||
|
||||
/// Update password detection config
|
||||
pub fn set_password_config(
|
||||
&mut self,
|
||||
|
|
@ -549,6 +586,48 @@ impl AppStateManager {
|
|||
self.password_apps = password_apps.iter().map(|s| s.to_lowercase()).collect();
|
||||
}
|
||||
|
||||
/// Update terminal detection config
|
||||
pub fn set_terminal_config(
|
||||
&mut self,
|
||||
terminal_apps: Vec<String>,
|
||||
terminal_input_method: String,
|
||||
) {
|
||||
self.terminal_apps = terminal_apps.iter().map(|s| s.to_lowercase()).collect();
|
||||
self.terminal_input_method = terminal_input_method;
|
||||
self.update_effective_method();
|
||||
}
|
||||
|
||||
/// Set the user's global input method and recompute effective method
|
||||
pub fn set_global_method(&mut self, method: &str) {
|
||||
self.global_method = method.to_string();
|
||||
self.update_effective_method();
|
||||
}
|
||||
|
||||
/// Recompute effective method based on terminal override
|
||||
pub fn update_effective_method(&mut self) {
|
||||
self.effective_method = Self::compute_effective_method(
|
||||
&self.global_method,
|
||||
&self.terminal_input_method,
|
||||
&self.terminal_apps,
|
||||
&self.current_app,
|
||||
);
|
||||
}
|
||||
|
||||
/// Get the effective input method (terminal override applied)
|
||||
pub fn effective_method(&self) -> &str {
|
||||
&self.effective_method
|
||||
}
|
||||
|
||||
/// Check if the current app is a terminal emulator
|
||||
pub fn is_terminal_app(&self) -> bool {
|
||||
for pattern in &self.terminal_apps {
|
||||
if self.current_app.contains(pattern.as_str()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Check if the current focused widget is a password field
|
||||
/// Returns true if password detected, forcing English mode
|
||||
pub fn check_password_field(&mut self) -> bool {
|
||||
|
|
@ -615,12 +694,22 @@ impl AppStateManager {
|
|||
}
|
||||
|
||||
let old_app = self.current_app.clone();
|
||||
let old_is_terminal = self.is_terminal_app();
|
||||
self.current_app = new_class;
|
||||
|
||||
eprintln!("[vietc] App: {} → {}", old_app, self.current_app);
|
||||
|
||||
// Recompute effective method on window change
|
||||
self.update_effective_method();
|
||||
let new_is_terminal = self.is_terminal_app();
|
||||
let method_changed = old_is_terminal != new_is_terminal;
|
||||
|
||||
let should_enable = self.get_default_state();
|
||||
Some(should_enable)
|
||||
if method_changed {
|
||||
Some(should_enable) // signal caller that method might have changed
|
||||
} else {
|
||||
Some(should_enable)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the default Vietnamese state for the current app
|
||||
|
|
@ -680,15 +769,21 @@ impl AppStateManager {
|
|||
english_apps: Vec<String>,
|
||||
vietnamese_apps: Vec<String>,
|
||||
bypass_apps: Vec<String>,
|
||||
terminal_apps: Vec<String>,
|
||||
terminal_input_method: String,
|
||||
) -> &Self {
|
||||
self.english_apps = english_apps.iter().map(|s| s.to_lowercase()).collect();
|
||||
self.vietnamese_apps = vietnamese_apps.iter().map(|s| s.to_lowercase()).collect();
|
||||
self.bypass_apps = bypass_apps.iter().map(|s| s.to_lowercase()).collect();
|
||||
self.terminal_apps = terminal_apps.iter().map(|s| s.to_lowercase()).collect();
|
||||
self.terminal_input_method = terminal_input_method;
|
||||
self.update_effective_method();
|
||||
eprintln!(
|
||||
"[vietc] App lists updated: {} English, {} Vietnamese, {} Bypass",
|
||||
"[vietc] App lists updated: {} English, {} Vietnamese, {} Bypass, {} Terminal",
|
||||
self.english_apps.len(),
|
||||
self.vietnamese_apps.len(),
|
||||
self.bypass_apps.len()
|
||||
self.bypass_apps.len(),
|
||||
self.terminal_apps.len()
|
||||
);
|
||||
self
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,6 +94,12 @@ pub struct AppStateConfig {
|
|||
|
||||
#[serde(default = "default_bypass_apps")]
|
||||
pub bypass_apps: Vec<String>,
|
||||
|
||||
#[serde(default = "default_terminal_apps")]
|
||||
pub terminal_apps: Vec<String>,
|
||||
|
||||
#[serde(default = "default_terminal_method")]
|
||||
pub terminal_input_method: String,
|
||||
}
|
||||
|
||||
impl Default for AutoRestoreConfig {
|
||||
|
|
@ -112,6 +118,8 @@ impl Default for AppStateConfig {
|
|||
english_apps: default_english_apps(),
|
||||
vietnamese_apps: default_vietnamese_apps(),
|
||||
bypass_apps: default_bypass_apps(),
|
||||
terminal_apps: default_terminal_apps(),
|
||||
terminal_input_method: default_terminal_method(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -175,6 +183,16 @@ fn default_english_apps() -> Vec<String> {
|
|||
}
|
||||
|
||||
fn default_bypass_apps() -> Vec<String> {
|
||||
vec![
|
||||
"steam".into(),
|
||||
"dota".into(),
|
||||
"csgo".into(),
|
||||
"minecraft".into(),
|
||||
"factorio".into(),
|
||||
]
|
||||
}
|
||||
|
||||
fn default_terminal_apps() -> Vec<String> {
|
||||
vec![
|
||||
"terminal".into(),
|
||||
"kitty".into(),
|
||||
|
|
@ -183,17 +201,26 @@ fn default_bypass_apps() -> Vec<String> {
|
|||
"wezterm".into(),
|
||||
"konsole".into(),
|
||||
"gnome-terminal".into(),
|
||||
"gnome-terminal-server".into(),
|
||||
"kgx".into(),
|
||||
"st".into(),
|
||||
"urxvt".into(),
|
||||
"xterm".into(),
|
||||
"steam".into(),
|
||||
"dota".into(),
|
||||
"csgo".into(),
|
||||
"minecraft".into(),
|
||||
"factorio".into(),
|
||||
"termite".into(),
|
||||
"terminator".into(),
|
||||
"tilix".into(),
|
||||
"deepin-terminal".into(),
|
||||
"pantheon-terminal".into(),
|
||||
"blackbox".into(),
|
||||
"contour".into(),
|
||||
"cool-retro-term".into(),
|
||||
]
|
||||
}
|
||||
|
||||
fn default_terminal_method() -> String {
|
||||
"vni".into()
|
||||
}
|
||||
|
||||
fn default_vietnamese_apps() -> Vec<String> {
|
||||
vec![
|
||||
"telegram".into(),
|
||||
|
|
@ -393,12 +420,16 @@ foo = "bar"
|
|||
[app_state]
|
||||
english_apps = ["vim", "neovim"]
|
||||
vietnamese_apps = ["zalo", "messenger"]
|
||||
bypass_apps = ["kitty"]
|
||||
bypass_apps = ["steam"]
|
||||
terminal_apps = ["kitty"]
|
||||
terminal_input_method = "telex"
|
||||
"#;
|
||||
let config: Config = toml::from_str(toml).unwrap();
|
||||
assert_eq!(config.app_state.english_apps, vec!["vim", "neovim"]);
|
||||
assert_eq!(config.app_state.vietnamese_apps, vec!["zalo", "messenger"]);
|
||||
assert_eq!(config.app_state.bypass_apps, vec!["kitty"]);
|
||||
assert_eq!(config.app_state.bypass_apps, vec!["steam"]);
|
||||
assert_eq!(config.app_state.terminal_apps, vec!["kitty"]);
|
||||
assert_eq!(config.app_state.terminal_input_method, "telex");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -420,11 +451,31 @@ bypass_apps = ["kitty"]
|
|||
#[test]
|
||||
fn default_config_bypass_apps() {
|
||||
let config = Config::default();
|
||||
assert!(config.app_state.bypass_apps.contains(&"kitty".to_string()));
|
||||
assert!(config
|
||||
assert!(config.app_state.bypass_apps.contains(&"steam".to_string()));
|
||||
assert!(!config
|
||||
.app_state
|
||||
.bypass_apps
|
||||
.contains(&"alacritty".to_string()));
|
||||
.contains(&"kitty".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_config_terminal_apps() {
|
||||
let config = Config::default();
|
||||
assert!(config.app_state.terminal_apps.contains(&"kitty".to_string()));
|
||||
assert!(config.app_state.terminal_apps.contains(&"gnome-terminal".to_string()));
|
||||
assert_eq!(config.app_state.terminal_input_method, "vni");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_terminal_config() {
|
||||
let toml = r#"
|
||||
[app_state]
|
||||
terminal_apps = ["foot", "alacritty"]
|
||||
terminal_input_method = "telex"
|
||||
"#;
|
||||
let config: Config = toml::from_str(toml).unwrap();
|
||||
assert_eq!(config.app_state.terminal_apps, vec!["foot", "alacritty"]);
|
||||
assert_eq!(config.app_state.terminal_input_method, "telex");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -142,6 +142,9 @@ impl Daemon {
|
|||
config.app_state.english_apps.clone(),
|
||||
config.app_state.vietnamese_apps.clone(),
|
||||
config.app_state.bypass_apps.clone(),
|
||||
config.app_state.terminal_apps.clone(),
|
||||
config.app_state.terminal_input_method.clone(),
|
||||
config.input_method.clone(),
|
||||
config.start_enabled,
|
||||
);
|
||||
app_state.load_overrides();
|
||||
|
|
@ -189,17 +192,23 @@ impl Daemon {
|
|||
}
|
||||
|
||||
fn toggle_method(&mut self) {
|
||||
let new_method = match self.config.input_method.as_str() {
|
||||
"vni" => InputMethod::Telex,
|
||||
_ => InputMethod::Vni,
|
||||
let new_global = match self.config.input_method.as_str() {
|
||||
"vni" => "telex",
|
||||
_ => "vni",
|
||||
};
|
||||
self.config.input_method = match new_method {
|
||||
InputMethod::Vni => "vni".into(),
|
||||
InputMethod::Telex => "telex".into(),
|
||||
self.config.input_method = new_global.into();
|
||||
self.app_state.set_global_method(new_global);
|
||||
let effective = self.app_state.effective_method();
|
||||
let engine_method = match effective {
|
||||
"vni" => InputMethod::Vni,
|
||||
_ => InputMethod::Telex,
|
||||
};
|
||||
self.engine.set_method(new_method);
|
||||
self.engine.set_method(engine_method);
|
||||
self.write_method_status();
|
||||
log_info(&format!("[vietc] Input method toggled to: {}", self.config.input_method));
|
||||
log_info(&format!(
|
||||
"[vietc] Input method toggled: global={}, effective={}",
|
||||
self.config.input_method, effective
|
||||
));
|
||||
}
|
||||
|
||||
fn sync_status_file(&mut self) {
|
||||
|
|
@ -226,11 +235,6 @@ impl Daemon {
|
|||
|
||||
match Config::load_from(&self.config_path) {
|
||||
Ok(new_config) => {
|
||||
let method = match new_config.input_method.as_str() {
|
||||
"vni" => InputMethod::Vni,
|
||||
_ => InputMethod::Telex,
|
||||
};
|
||||
self.engine.set_method(method);
|
||||
self.engine
|
||||
.set_auto_restore(new_config.auto_restore.enabled);
|
||||
|
||||
|
|
@ -239,12 +243,23 @@ impl Daemon {
|
|||
self.engine.add_macro(shortcut.clone(), expansion.clone());
|
||||
}
|
||||
|
||||
self.app_state.set_global_method(&new_config.input_method);
|
||||
self.app_state.update_lists(
|
||||
new_config.app_state.english_apps.clone(),
|
||||
new_config.app_state.vietnamese_apps.clone(),
|
||||
new_config.app_state.bypass_apps.clone(),
|
||||
new_config.app_state.terminal_apps.clone(),
|
||||
new_config.app_state.terminal_input_method.clone(),
|
||||
);
|
||||
|
||||
// Apply effective method (terminal override considered)
|
||||
let effective = self.app_state.effective_method();
|
||||
let engine_method = match effective {
|
||||
"vni" => InputMethod::Vni,
|
||||
_ => InputMethod::Telex,
|
||||
};
|
||||
self.engine.set_method(engine_method);
|
||||
|
||||
self.app_state.set_password_config(
|
||||
new_config.password_detection.enabled,
|
||||
new_config.password_detection.check_atspi2,
|
||||
|
|
@ -464,6 +479,14 @@ impl Daemon {
|
|||
self.engine.set_enabled(should_enable);
|
||||
self.write_status();
|
||||
}
|
||||
// Apply effective method (terminal override)
|
||||
let effective = self.app_state.effective_method();
|
||||
let engine_method = match effective {
|
||||
"vni" => InputMethod::Vni,
|
||||
_ => InputMethod::Telex,
|
||||
};
|
||||
// set_method also resets the engine buffer (safe — window already changed)
|
||||
self.engine.set_method(engine_method);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1238,7 +1261,7 @@ fn run_with_evdev(
|
|||
if let Some(ch) = key_to_char(key) {
|
||||
let mut commands = daemon.process_key(ch);
|
||||
if !commands.is_empty()
|
||||
&& is_vn_control_key(&daemon.config.input_method, ch)
|
||||
&& is_vn_control_key(daemon.app_state.effective_method(), ch)
|
||||
{
|
||||
for cmd in &mut commands {
|
||||
if let OutputCommand::Backspace(ref mut n) = cmd {
|
||||
|
|
@ -1388,7 +1411,7 @@ fn run_with_evdev(
|
|||
}
|
||||
// Skip upcoming auto-repeat pile-up from injection delay
|
||||
skip_count = 3;
|
||||
} else if is_vn_control_key(&daemon.config.input_method, ch)
|
||||
} else if is_vn_control_key(daemon.app_state.effective_method(), ch)
|
||||
&& daemon.engine.buffer().chars().count() <= buf_before
|
||||
{
|
||||
// Tone/mark key truly absorbed with no effect (no
|
||||
|
|
|
|||
|
|
@ -109,7 +109,10 @@ password_apps = ["pinentry", "pinentry-gtk-2", "pinentry-qt", "kwallet"]
|
|||
enabled = true
|
||||
english_apps = ["code", "vim"]
|
||||
vietnamese_apps = ["telegram", "discord", "firefox"]
|
||||
bypass_apps = ["kitty", "alacritty", "steam"]
|
||||
bypass_apps = ["steam"]
|
||||
terminal_apps = ["kitty", "alacritty", "gnome-terminal", "konsole", "foot",
|
||||
"wezterm", "st", "urxvt", "xterm"]
|
||||
terminal_input_method = "vni"
|
||||
EOF
|
||||
|
||||
echo -e "${GREEN}=== Done! ===${NC}"
|
||||
|
|
|
|||
Loading…
Reference in a new issue