Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a8b391f46 | ||
|
|
2530054770 |
+1
-1
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user