Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions docs/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -1479,6 +1479,35 @@ shift_enter_newlines = true # Use Shift+Enter for newlines

**Note:** This only affects the wtype output driver. When combined with `auto_submit = true`, the final Enter (to submit) is still sent as a regular Enter after all Shift+Enter line breaks.

### wtype_shift_prefix

**Type:** Boolean
**Default:** `false`
**Required:** No
**Environment Variable:** `VOXTYPE_WTYPE_SHIFT_PREFIX`

Prefix wtype output with a Shift key press and release. This is a workaround for apps (notably Discord) that drop the first CJK character when wtype types text. The Shift press/release has no visible effect on the output but prevents the first character from being swallowed.

Only affects the wtype output driver. Has no effect when using dotool, ydotool, or clipboard modes.

**Example:**
```toml
[output]
wtype_shift_prefix = true
```

**CLI override:**
```bash
voxtype --wtype-shift-prefix daemon
```

**When to use:**
- First CJK (Chinese, Japanese, Korean) character is missing from output
- You're using the wtype driver (default on wlroots compositors)
- The problem happens in specific apps like Discord

See [Troubleshooting](TROUBLESHOOTING.md#first-cjk-character-dropped-wtype) for more details.

### pre_output_command

**Type:** String
Expand Down
17 changes: 17 additions & 0 deletions docs/TROUBLESHOOTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,23 @@ Some applications (terminals, games) may block simulated input.
mode = "clipboard"
```

### First CJK character dropped (wtype)

**Symptom:** When using wtype, the first Chinese, Japanese, or Korean character is missing from the output. This happens in some apps like Discord.

**Cause:** Some Wayland applications swallow the first character from wtype's virtual keyboard input, particularly with CJK text.

**Solution:** Enable the Shift prefix workaround:

```toml
[output]
wtype_shift_prefix = true
```

This prefixes each wtype command with a Shift key press and release (`-P Shift_L -p Shift_L`), which prevents the first character from being dropped. It only affects the wtype driver and has no visible effect on the output text.

You can also enable it via CLI flag (`--wtype-shift-prefix`) or environment variable (`VOXTYPE_WTYPE_SHIFT_PREFIX=true`).

### Characters dropped or garbled

**Cause:** Typing too fast for the application.
Expand Down
11 changes: 10 additions & 1 deletion docs/USER_MANUAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -1267,7 +1267,16 @@ shift_enter_newlines = true # Use Shift+Enter instead of Enter for line breaks

Many chat apps (Slack, Discord, Teams) and AI assistants (Cursor) use Enter to send and Shift+Enter for line breaks. Enable this when dictating multi-line messages to prevent premature submission.

**Combining both options:**
**Shift prefix for CJK character drop:**

```toml
[output]
wtype_shift_prefix = true # Prefix wtype output with Shift press/release
```

Some apps (notably Discord) drop the first CJK character when wtype types text. This option prefixes each wtype invocation with a Shift key press and release, which prevents the character from being swallowed. Only affects the wtype driver.

**Combining shift_enter_newlines and auto_submit:**

```toml
[output]
Expand Down
5 changes: 5 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ pub struct Cli {
#[arg(long, value_name = "TEXT")]
pub append_text: Option<String>,

/// Prefix wtype output with a Shift key press/release.
/// Workaround for apps (e.g., Discord) that drop the first CJK character.
#[arg(long)]
pub wtype_shift_prefix: bool,

/// Output driver order for type mode (comma-separated)
/// Overrides config driver_order. Available: wtype, dotool, ydotool, clipboard
/// Example: --driver=ydotool,wtype,clipboard
Expand Down
16 changes: 16 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,10 @@ type_delay_ms = 0
# Useful for applications where Enter submits (e.g., Cursor IDE, Slack, Discord)
# shift_enter_newlines = false

# Prefix wtype output with a Shift key press/release
# Workaround for apps (e.g., Discord) that drop the first CJK character
# wtype_shift_prefix = false

# Pre/post output hooks (optional)
# Commands to run before and after typing output. Useful for compositor integration.
# Example: Block modifier keys during typing with Hyprland submap:
Expand Down Expand Up @@ -1186,6 +1190,11 @@ pub struct OutputConfig {
#[serde(default)]
pub shift_enter_newlines: bool,

/// Prefix wtype output with a Shift key press/release
/// Workaround for apps (e.g., Discord) that drop the first CJK character
#[serde(default)]
pub wtype_shift_prefix: bool,

/// Command to run when recording starts (e.g., switch to compositor submap)
/// Useful for entering a mode where cancel keybindings are effective
#[serde(default)]
Expand Down Expand Up @@ -1386,6 +1395,7 @@ impl Default for Config {
auto_submit: false,
append_text: None,
shift_enter_newlines: false,
wtype_shift_prefix: false,
pre_recording_command: None,
pre_output_command: None,
post_output_command: None,
Expand Down Expand Up @@ -1557,6 +1567,12 @@ pub fn load_config(path: Option<&Path>) -> Result<Config, VoxtypeError> {
if let Ok(append_text) = std::env::var("VOXTYPE_APPEND_TEXT") {
config.output.append_text = Some(append_text);
}
if std::env::var("VOXTYPE_WTYPE_SHIFT_PREFIX")
.map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
.unwrap_or(false)
{
config.output.wtype_shift_prefix = true;
}

Ok(config)
}
Expand Down
3 changes: 3 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ async fn main() -> anyhow::Result<()> {
if let Some(append_text) = cli.append_text {
config.output.append_text = Some(append_text);
}
if cli.wtype_shift_prefix {
config.output.wtype_shift_prefix = true;
}
if let Some(ref driver_str) = cli.driver {
match parse_driver_order(driver_str) {
Ok(drivers) => {
Expand Down
1 change: 1 addition & 0 deletions src/output/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ fn create_driver_output(
config.type_delay_ms,
pre_type_delay_ms,
config.shift_enter_newlines,
config.wtype_shift_prefix,
)),
OutputDriver::Eitype => Box::new(eitype::EitypeOutput::new(
config.auto_submit,
Expand Down
26 changes: 21 additions & 5 deletions src/output/wtype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ pub struct WtypeOutput {
pre_type_delay_ms: u32,
/// Convert newlines to Shift+Enter (for apps where Enter submits)
shift_enter_newlines: bool,
/// Prefix output with Shift press/release (workaround for CJK first char drop)
shift_prefix: bool,
}

impl WtypeOutput {
Expand All @@ -36,13 +38,15 @@ impl WtypeOutput {
type_delay_ms: u32,
pre_type_delay_ms: u32,
shift_enter_newlines: bool,
shift_prefix: bool,
) -> Self {
Self {
auto_submit,
append_text,
type_delay_ms,
pre_type_delay_ms,
shift_enter_newlines,
shift_prefix,
}
}

Expand All @@ -67,6 +71,12 @@ impl WtypeOutput {
debug_args.push(format!("-d {}", self.type_delay_ms));
}

// Add Shift prefix to prevent first CJK character drop in some apps
if self.shift_prefix {
cmd.arg("-P").arg("Shift_L").arg("-p").arg("Shift_L");
debug_args.push("-P Shift_L -p Shift_L".to_string());
}

debug_args.push("--".to_string());
debug_args.push(format!("\"{}\"", text.chars().take(20).collect::<String>()));
tracing::debug!("Running: {}", debug_args.join(" "));
Expand Down Expand Up @@ -207,7 +217,7 @@ mod tests {

#[test]
fn test_new() {
let output = WtypeOutput::new(false, None, 0, 0, false);
let output = WtypeOutput::new(false, None, 0, 0, false, false);
assert!(!output.auto_submit);
assert_eq!(output.type_delay_ms, 0);
assert_eq!(output.pre_type_delay_ms, 0);
Expand All @@ -216,28 +226,34 @@ mod tests {

#[test]
fn test_new_with_enter() {
let output = WtypeOutput::new(true, None, 0, 0, false);
let output = WtypeOutput::new(true, None, 0, 0, false, false);
assert!(output.auto_submit);
}

#[test]
fn test_new_with_type_delay() {
let output = WtypeOutput::new(false, None, 50, 0, false);
let output = WtypeOutput::new(false, None, 50, 0, false, false);
assert!(!output.auto_submit);
assert_eq!(output.type_delay_ms, 50);
assert_eq!(output.pre_type_delay_ms, 0);
}

#[test]
fn test_new_with_pre_type_delay() {
let output = WtypeOutput::new(false, None, 0, 200, false);
let output = WtypeOutput::new(false, None, 0, 200, false, false);
assert_eq!(output.type_delay_ms, 0);
assert_eq!(output.pre_type_delay_ms, 200);
}

#[test]
fn test_new_with_shift_enter_newlines() {
let output = WtypeOutput::new(false, None, 0, 0, true);
let output = WtypeOutput::new(false, None, 0, 0, true, false);
assert!(output.shift_enter_newlines);
}

#[test]
fn test_new_with_shift_prefix() {
let output = WtypeOutput::new(false, None, 0, 0, false, true);
assert!(output.shift_prefix);
}
}