2 Commits
7 changed files with 126 additions and 18 deletions
+1 -1
View File
@@ -27,4 +27,4 @@ vad_min_speech_ms=250
# hard cap for one speech segment
vad_max_speech_ms=15000
autotype_enabled=0
autotype_enabled=1
+1 -1
View File
@@ -36,7 +36,7 @@ void config_set_defaults(voice_config_t *cfg) {
cfg->vad_preroll_ms = 500;
cfg->vad_min_speech_ms = 250;
cfg->vad_max_speech_ms = 15000;
cfg->autotype_enabled = 0;
cfg->autotype_enabled = 1;
}
int config_load_file(const char *path, voice_config_t *cfg) {
+12 -1
View File
@@ -175,6 +175,12 @@ static void process_segment(gui_state_t *s, float *samples, size_t count) {
return;
}
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s->type_check))) {
if (typer_capture_focus() != 0) {
fprintf(stderr, "gui: failed to capture focus target for autotype\n");
}
}
s->transcribing = 1;
update_record_button(s);
set_status(s, "Transcribing... (please wait)");
@@ -194,7 +200,12 @@ static void process_segment(gui_state_t *s, float *samples, size_t count) {
append_transcript(s, text);
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(s->type_check))) {
typer_type_text(text);
if (typer_type_text(text) != 0) {
fprintf(stderr, "gui: autotype injection failed\n");
set_status(s, "Autotype failed (see terminal logs)");
free(text);
return;
}
}
set_status(s, audio_msg);
+109 -13
View File
@@ -11,6 +11,16 @@
static Display *g_dpy = NULL;
static int g_delay_us = 8000;
static int g_key_hold_us = 4000;
static Window g_target_window = None;
static void sleep_us(int us) {
if (us <= 0) return;
struct timespec ts;
ts.tv_sec = us / 1000000;
ts.tv_nsec = (long)(us % 1000000) * 1000L;
nanosleep(&ts, NULL);
}
static int type_keysym(KeySym ks, int need_shift) {
KeyCode kc = XKeysymToKeycode(g_dpy, ks);
@@ -19,21 +29,21 @@ static int type_keysym(KeySym ks, int need_shift) {
KeyCode shift_kc = XKeysymToKeycode(g_dpy, XK_Shift_L);
if (need_shift && shift_kc) {
XTestFakeKeyEvent(g_dpy, shift_kc, True, CurrentTime);
if (!XTestFakeKeyEvent(g_dpy, shift_kc, True, CurrentTime)) return -1;
}
XTestFakeKeyEvent(g_dpy, kc, True, CurrentTime);
XTestFakeKeyEvent(g_dpy, kc, False, CurrentTime);
if (!XTestFakeKeyEvent(g_dpy, kc, True, CurrentTime)) return -1;
XSync(g_dpy, False);
sleep_us(g_key_hold_us);
if (!XTestFakeKeyEvent(g_dpy, kc, False, CurrentTime)) return -1;
if (need_shift && shift_kc) {
XTestFakeKeyEvent(g_dpy, shift_kc, False, CurrentTime);
if (!XTestFakeKeyEvent(g_dpy, shift_kc, False, CurrentTime)) return -1;
}
XFlush(g_dpy);
struct timespec ts;
ts.tv_sec = g_delay_us / 1000000;
ts.tv_nsec = (long)(g_delay_us % 1000000) * 1000L;
nanosleep(&ts, NULL);
XSync(g_dpy, False);
sleep_us(g_delay_us);
return 0;
}
@@ -62,32 +72,118 @@ static int type_char(char c) {
case '-': return type_keysym(XK_minus, 0);
case '_': return type_keysym(XK_minus, 1);
case '/': return type_keysym(XK_slash, 0);
case '\\': return type_keysym(XK_backslash, 0);
case '|': return type_keysym(XK_backslash, 1);
case '=': return type_keysym(XK_equal, 0);
case '+': return type_keysym(XK_equal, 1);
case '[': return type_keysym(XK_bracketleft, 0);
case '{': return type_keysym(XK_bracketleft, 1);
case ']': return type_keysym(XK_bracketright, 0);
case '}': return type_keysym(XK_bracketright, 1);
case '`': return type_keysym(XK_grave, 0);
case '~': return type_keysym(XK_grave, 1);
case '@': return type_keysym(XK_2, 1);
case '#': return type_keysym(XK_3, 1);
case '$': return type_keysym(XK_4, 1);
case '%': return type_keysym(XK_5, 1);
case '^': return type_keysym(XK_6, 1);
case '&': return type_keysym(XK_7, 1);
case '*': return type_keysym(XK_8, 1);
case '(': return type_keysym(XK_9, 1);
case ')': return type_keysym(XK_0, 1);
case '<': return type_keysym(XK_comma, 1);
case '>': return type_keysym(XK_period, 1);
default: return 0;
}
}
int typer_init(int type_delay_us) {
int ev_base = 0;
int err_base = 0;
int major = 0;
int minor = 0;
g_dpy = XOpenDisplay(NULL);
if (!g_dpy) return -1;
if (!g_dpy) {
fprintf(stderr, "typer: XOpenDisplay failed\n");
return -1;
}
if (!XTestQueryExtension(g_dpy, &ev_base, &err_base, &major, &minor)) {
fprintf(stderr, "typer: XTEST extension unavailable\n");
XCloseDisplay(g_dpy);
g_dpy = NULL;
return -1;
}
g_delay_us = type_delay_us > 0 ? type_delay_us : 8000;
g_key_hold_us = g_delay_us / 2;
if (g_key_hold_us < 4000) g_key_hold_us = 4000;
if (g_key_hold_us > 20000) g_key_hold_us = 20000;
g_target_window = None;
return 0;
}
int typer_capture_focus(void) {
if (!g_dpy) return -1;
Window focused = None;
int revert = 0;
XGetInputFocus(g_dpy, &focused, &revert);
if (focused == None || focused == PointerRoot) {
return -1;
}
g_target_window = focused;
return 0;
}
int typer_type_text(const char *text) {
if (!g_dpy || !text) return -1;
for (size_t i = 0; i < strlen(text); ++i) {
Window previous_focus = None;
int previous_revert = 0;
XGetInputFocus(g_dpy, &previous_focus, &previous_revert);
Window target = g_target_window;
if (target == None || target == PointerRoot) {
target = previous_focus;
}
if (target == None || target == PointerRoot) {
fprintf(stderr, "typer: no valid target window for autotype\n");
return -1;
}
if (previous_focus != target) {
XSetInputFocus(g_dpy, target, RevertToParent, CurrentTime);
XSync(g_dpy, False);
sleep_us(20000);
}
int failures = 0;
const size_t n = strlen(text);
for (size_t i = 0; i < n; ++i) {
if (type_char(text[i]) != 0) {
fprintf(stderr, "typer: failed at char '%c'\n", text[i]);
fprintf(stderr, "typer: failed at char 0x%02x ('%c')\n",
(unsigned char)text[i], isprint((unsigned char)text[i]) ? text[i] : '?');
failures++;
}
}
return 0;
if (previous_focus != None && previous_focus != PointerRoot && previous_focus != target) {
XSetInputFocus(g_dpy, previous_focus, RevertToParent, CurrentTime);
XSync(g_dpy, False);
}
if (failures > 0) {
fprintf(stderr, "typer: %d/%zu characters failed to inject\n", failures, n);
}
return failures == 0 ? 0 : -1;
}
void typer_cleanup(void) {
g_target_window = None;
if (g_dpy) {
XCloseDisplay(g_dpy);
g_dpy = NULL;
+1
View File
@@ -2,6 +2,7 @@
#define VOICE_TYPER_H
int typer_init(int type_delay_us);
int typer_capture_focus(void);
int typer_type_text(const char *text);
void typer_cleanup(void);
+2 -2
View File
@@ -1,9 +1,9 @@
#ifndef VOICE_VERSION_H
#define VOICE_VERSION_H
#define VOICE_LINUX_VERSION "v0.0.2"
#define VOICE_LINUX_VERSION "v0.0.3"
#define VOICE_LINUX_VERSION_MAJOR 0
#define VOICE_LINUX_VERSION_MINOR 0
#define VOICE_LINUX_VERSION_PATCH 2
#define VOICE_LINUX_VERSION_PATCH 3
#endif
BIN
View File
Binary file not shown.