一个wayland兼容的虚拟键盘,借鉴自vboard.py,由叔和deepseek共同改写成Gtk3。
代码: 全选
/*
gcc -o vboard vboard.c `pkg-config --cflags --libs gtk+-3.0` -lglib-2.0
*/
#include <gtk/gtk.h>
#include <gdk/gdk.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <linux/uinput.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
/* 键名到 uinput 键码的映射 */
typedef struct {
const char *name;
int code;
} KeyMapping;
static const KeyMapping key_mappings[] = {
{"Esc", KEY_ESC}, {"1", KEY_1}, {"2", KEY_2}, {"3", KEY_3}, {"4", KEY_4},
{"5", KEY_5}, {"6", KEY_6}, {"7", KEY_7}, {"8", KEY_8}, {"9", KEY_9},
{"0", KEY_0}, {"-", KEY_MINUS}, {"=", KEY_EQUAL}, {"Backspace", KEY_BACKSPACE},
{"Tab", KEY_TAB}, {"Q", KEY_Q}, {"W", KEY_W}, {"E", KEY_E}, {"R", KEY_R},
{"T", KEY_T}, {"Y", KEY_Y}, {"U", KEY_U}, {"I", KEY_I}, {"O", KEY_O},
{"P", KEY_P}, {"[", KEY_LEFTBRACE}, {"]", KEY_RIGHTBRACE}, {"Enter", KEY_ENTER},
{"Ctrl_L", KEY_LEFTCTRL}, {"A", KEY_A}, {"S", KEY_S}, {"D", KEY_D}, {"F", KEY_F},
{"G", KEY_G}, {"H", KEY_H}, {"J", KEY_J}, {"K", KEY_K}, {"L", KEY_L},
{";", KEY_SEMICOLON}, {"'", KEY_APOSTROPHE}, {"`", KEY_GRAVE}, {"Shift_L", KEY_LEFTSHIFT},
{"\\", KEY_BACKSLASH}, {"Z", KEY_Z}, {"X", KEY_X}, {"C", KEY_C}, {"V", KEY_V},
{"B", KEY_B}, {"N", KEY_N}, {"M", KEY_M}, {",", KEY_COMMA}, {".", KEY_DOT},
{"/", KEY_SLASH}, {"Shift_R", KEY_RIGHTSHIFT}, {"Alt_L", KEY_LEFTALT}, {"Alt_R", KEY_RIGHTALT},
{"Space", KEY_SPACE}, {"CapsLock", KEY_CAPSLOCK}, {"F1", KEY_F1}, {"F2", KEY_F2},
{"F3", KEY_F3}, {"F4", KEY_F4}, {"F5", KEY_F5}, {"F6", KEY_F6}, {"F7", KEY_F7},
{"F8", KEY_F8}, {"F9", KEY_F9}, {"F10", KEY_F10}, {"F11", KEY_F11}, {"F12", KEY_F12},
{"ScrollLock", KEY_SCROLLLOCK}, {"Pause", KEY_PAUSE}, {"Insert", KEY_INSERT},
{"Home", KEY_HOME}, {"PageUp", KEY_PAGEUP}, {"Delete", KEY_DELETE}, {"End", KEY_END},
{"PageDown", KEY_PAGEDOWN}, {"→", KEY_RIGHT}, {"←", KEY_LEFT}, {"↓", KEY_DOWN},
{"↑", KEY_UP}, {"NumLock", KEY_NUMLOCK}, {"Ctrl_R", KEY_RIGHTCTRL}, {"Super_L", KEY_LEFTMETA},
{"Super_R", KEY_RIGHTMETA}, {NULL, 0}
};
/* 键盘布局 */
static const char *keyboard_rows[][20] = {
{"`", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", "Backspace", NULL},
{"Tab", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "[", "]", "\\", NULL},
{"CapsLock", "A", "S", "D", "F", "G", "H", "J", "K", "L", ";", "'", "Enter", NULL},
{"Shift_L", "Z", "X", "C", "V", "B", "N", "M", ",", ".", "/", "Shift_R", "↑", NULL},
{"Ctrl_L", "Super_L", "Alt_L", "Space", "Alt_R", "Super_R", "Ctrl_R", "←", "→", "↓", NULL},
{NULL}
};
static int get_button_width(const char *key) {
if (g_str_equal(key, "Space")) return 12;
if (g_str_equal(key, "CapsLock")) return 3;
if (g_str_equal(key, "Shift_L") || g_str_equal(key, "Shift_R")) return 4;
if (g_str_equal(key, "Backspace")) return 5;
if (g_str_equal(key, "`")) return 1;
if (g_str_equal(key, "\\")) return 4;
if (g_str_equal(key, "Enter")) return 5;
return 2;
}
typedef struct {
GtkWidget *window;
GtkWidget *grid;
GtkCssProvider *css_provider;
int uinput_fd;
GHashTable *modifier_buttons;
GHashTable *modifier_active;
guint delay_source;
guint repeat_source;
int current_keycode;
char *bg_color;
double opacity;
char *text_color;
int width, height;
char *cursor_type;
char *font_size;
char config_dir[PATH_MAX];
char config_file[PATH_MAX];
} VirtualKeyboard;
/* 函数声明 */
static int find_keycode(const char *name);
static gboolean init_uinput(VirtualKeyboard *vk);
static void emit_key(VirtualKeyboard *vk, int keycode);
static void update_modifier(VirtualKeyboard *vk, int keycode, gboolean active);
static void update_label(VirtualKeyboard *vk, gboolean shift_active);
static void apply_css(VirtualKeyboard *vk);
static void load_settings(VirtualKeyboard *vk);
static void save_settings(VirtualKeyboard *vk);
static void create_keyboard(VirtualKeyboard *vk);
static void set_cursor_for_window(VirtualKeyboard *vk);
static void cleanup_timeouts(VirtualKeyboard *vk);
static gboolean on_button_press(GtkWidget *widget, GdkEventButton *event, gpointer data);
static gboolean on_button_release(GtkWidget *widget, GdkEventButton *event, gpointer data);
static gboolean start_repeat(gpointer user_data);
static gboolean on_key_repeat(gpointer user_data);
static void on_resize(GtkWidget *widget, GdkEventConfigure *event, gpointer data);
static void on_realize(GtkWidget *widget, gpointer data);
static void destroy_cb(GtkWidget *widget, gpointer data);
static int find_keycode(const char *name) {
for (int i = 0; key_mappings[i].name != NULL; i++) {
if (g_str_equal(name, key_mappings[i].name))
return key_mappings[i].code;
}
return -1;
}
static gboolean init_uinput(VirtualKeyboard *vk) {
g_print("[调试] 正在打开 /dev/uinput...\n");
int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
if (fd < 0) {
g_printerr("无法打开 /dev/uinput: %s\n", strerror(errno));
return FALSE;
}
g_print("[调试] /dev/uinput 打开成功,fd = %d\n", fd);
// 显式启用 EV_KEY 和 EV_SYN 事件类型
if (ioctl(fd, UI_SET_EVBIT, EV_KEY) < 0) {
g_printerr("设置 EV_KEY 事件类型失败: %s\n", strerror(errno));
close(fd);
return FALSE;
}
if (ioctl(fd, UI_SET_EVBIT, EV_SYN) < 0) {
g_printerr("设置 EV_SYN 事件类型失败: %s\n", strerror(errno));
close(fd);
return FALSE;
}
// 启用所有需要用到的键
for (int i = 0; key_mappings[i].name != NULL; i++) {
if (ioctl(fd, UI_SET_KEYBIT, key_mappings[i].code) < 0) {
g_printerr("设置键位 %d 失败: %s\n", key_mappings[i].code, strerror(errno));
close(fd);
return FALSE;
}
}
g_print("[调试] 已设置所有键位\n");
struct uinput_setup usetup;
memset(&usetup, 0, sizeof(usetup));
usetup.id.bustype = BUS_USB;
usetup.id.vendor = 0x1234;
usetup.id.product = 0x5678;
strcpy(usetup.name, "Virtual Keyboard");
if (ioctl(fd, UI_DEV_SETUP, &usetup) < 0) {
g_printerr("设置 uinput 设备失败: %s\n", strerror(errno));
close(fd);
return FALSE;
}
g_print("[调试] UI_DEV_SETUP 成功\n");
if (ioctl(fd, UI_DEV_CREATE) < 0) {
g_printerr("创建 uinput 设备失败: %s\n", strerror(errno));
close(fd);
return FALSE;
}
g_print("[调试] UI_DEV_CREATE 成功,虚拟键盘已创建\n");
vk->uinput_fd = fd;
return TRUE;
}
static void emit_key(VirtualKeyboard *vk, int keycode) {
struct input_event ev;
memset(&ev, 0, sizeof(ev));
ev.type = EV_KEY;
g_print("[调试] emit_key: keycode=%d\n", keycode);
// 收集当前活跃的修饰键
GList *active_mods = NULL;
GHashTableIter iter;
gpointer key, value;
g_hash_table_iter_init(&iter, vk->modifier_active);
while (g_hash_table_iter_next(&iter, &key, &value)) {
if (GPOINTER_TO_INT(value)) {
active_mods = g_list_append(active_mods, key);
}
}
// 按下所有活跃修饰键
for (GList *l = active_mods; l; l = l->next) {
int mod = GPOINTER_TO_INT(l->data);
ev.code = mod;
ev.value = 1;
if (write(vk->uinput_fd, &ev, sizeof(ev)) != sizeof(ev))
g_printerr("写入修饰键按下失败 (mod=%d): %s\n", mod, strerror(errno));
else
g_print("[调试] 修饰键按下: %d\n", mod);
}
// 按下主键
ev.code = keycode;
ev.value = 1;
if (write(vk->uinput_fd, &ev, sizeof(ev)) != sizeof(ev))
g_printerr("写入主键按下失败: %s\n", strerror(errno));
else
g_print("[调试] 主键按下: %d\n", keycode);
// 释放主键
ev.value = 0;
if (write(vk->uinput_fd, &ev, sizeof(ev)) != sizeof(ev))
g_printerr("写入主键释放失败: %s\n", strerror(errno));
else
g_print("[调试] 主键释放: %d\n", keycode);
// 释放所有修饰键,并重置状态
for (GList *l = active_mods; l; l = l->next) {
int mod = GPOINTER_TO_INT(l->data);
ev.code = mod;
ev.value = 0;
if (write(vk->uinput_fd, &ev, sizeof(ev)) != sizeof(ev))
g_printerr("写入修饰键释放失败 (mod=%d): %s\n", mod, strerror(errno));
else
g_print("[调试] 修饰键释放: %d\n", mod);
update_modifier(vk, mod, FALSE);
}
g_list_free(active_mods);
// 同步事件
ev.type = EV_SYN;
ev.code = SYN_REPORT;
ev.value = 0;
if (write(vk->uinput_fd, &ev, sizeof(ev)) != sizeof(ev))
g_printerr("写入同步事件失败: %s\n", strerror(errno));
else
g_print("[调试] 同步事件发送\n");
// 重置标签(Shift 已被重置)
update_label(vk, FALSE);
}
static void update_modifier(VirtualKeyboard *vk, int keycode, gboolean active) {
g_print("[调试] update_modifier: keycode=%d active=%d\n", keycode, active);
g_hash_table_insert(vk->modifier_active, GINT_TO_POINTER(keycode), GINT_TO_POINTER(active));
GtkWidget *button = g_hash_table_lookup(vk->modifier_buttons, GINT_TO_POINTER(keycode));
if (button) {
GtkStyleContext *context = gtk_widget_get_style_context(button);
if (active)
gtk_style_context_add_class(context, "pressed");
else
gtk_style_context_remove_class(context, "pressed");
}
}
static void update_label(VirtualKeyboard *vk, gboolean shift_active) {
typedef struct {
const char *normal;
const char *shift;
} LabelSwap;
static LabelSwap swaps[] = {
{"`", "~"}, {"1", "!"}, {"2", "@"}, {"3", "#"}, {"4", "$"}, {"5", "%"},
{"6", "^"}, {"7", "&"}, {"8", "*"}, {"9", "("}, {"0", ")"}, {"-", "_"},
{"=", "+"}, {"[", "{"}, {"]", "}"}, {"\\", "|"}, {";", ":"}, {"'", "\""},
{",", "<"}, {".", ">"}, {"/", "?"}, {NULL, NULL}
};
GList *children = gtk_container_get_children(GTK_CONTAINER(vk->grid));
for (GList *l = children; l; l = l->next) {
GtkWidget *button = l->data;
const char *label = gtk_button_get_label(GTK_BUTTON(button));
for (int i = 0; swaps[i].normal != NULL; i++) {
if (g_str_equal(label, swaps[i].normal) || g_str_equal(label, swaps[i].shift)) {
gtk_button_set_label(GTK_BUTTON(button),
shift_active ? swaps[i].shift : swaps[i].normal);
break;
}
}
}
g_list_free(children);
}
static void apply_css(VirtualKeyboard *vk) {
if (!vk->css_provider)
vk->css_provider = gtk_css_provider_new();
gboolean is_white = (g_str_equal(vk->text_color, "white") ||
g_str_equal(vk->text_color, "#ffffff") ||
g_str_equal(vk->text_color, "rgb(255,255,255)"));
const char *border_color = is_white ? "#333333" : "#cccccc";
const char *pressed_border = is_white ? "#ffffff" : "#000000";
char css[1024];
snprintf(css, sizeof(css),
"#toplevel { background-color: rgba(%s, %f); }"
"#grid button { border: none; background-image: none; padding: 0px; margin: 0px; "
"background-color: transparent; font-family: sans; font-size: %s; color: %s; }"
"#grid button:hover { border: 1px solid #00CACB; }"
"#grid button.pressed { border: 1px solid #FFFFFF; }",
vk->bg_color, vk->opacity, vk->font_size, vk->text_color);
GError *error = NULL;
gtk_css_provider_load_from_data(vk->css_provider, css, -1, &error);
if (error) {
g_printerr("CSS 错误: %s\n", error->message);
g_error_free(error);
return;
}
GdkScreen *screen = gtk_widget_get_screen(vk->window);
gtk_style_context_add_provider_for_screen(screen, GTK_STYLE_PROVIDER(vk->css_provider),
GTK_STYLE_PROVIDER_PRIORITY_USER);
}
static void load_settings(VirtualKeyboard *vk) {
const char *home = g_get_home_dir();
g_snprintf(vk->config_dir, sizeof(vk->config_dir), "%s/.config/vboard", home);
g_snprintf(vk->config_file, sizeof(vk->config_file), "%s/settings.conf", vk->config_dir);
g_mkdir_with_parents(vk->config_dir, 0755);
GKeyFile *keyfile = g_key_file_new();
if (g_file_test(vk->config_file, G_FILE_TEST_EXISTS)) {
GError *error = NULL;
if (!g_key_file_load_from_file(keyfile, vk->config_file, G_KEY_FILE_NONE, &error)) {
g_warning("无法读取配置文件: %s", error->message);
g_error_free(error);
} else {
char *bg = g_key_file_get_string(keyfile, "DEFAULT", "bg_color", NULL);
if (bg) vk->bg_color = bg; else vk->bg_color = g_strdup("0,0,0");
double op = g_key_file_get_double(keyfile, "DEFAULT", "opacity", NULL);
if (op >= 0 && op <= 1) vk->opacity = op; else vk->opacity = 0.90;
char *txt = g_key_file_get_string(keyfile, "DEFAULT", "text_color", NULL);
if (txt) vk->text_color = txt; else vk->text_color = g_strdup("white");
vk->width = g_key_file_get_integer(keyfile, "DEFAULT", "width", NULL);
vk->height = g_key_file_get_integer(keyfile, "DEFAULT", "height", NULL);
char *cursor = g_key_file_get_string(keyfile, "DEFAULT", "cursor", NULL);
if (cursor) vk->cursor_type = cursor; else vk->cursor_type = g_strdup("default");
char *font = g_key_file_get_string(keyfile, "DEFAULT", "font_size", NULL);
if (font) vk->font_size = font; else vk->font_size = g_strdup("13px");
g_key_file_free(keyfile);
return;
}
}
vk->bg_color = g_strdup("0,0,0");
vk->opacity = 0.90;
vk->text_color = g_strdup("white");
vk->width = 0;
vk->height = 0;
vk->cursor_type = g_strdup("default");
vk->font_size = g_strdup("13px");
g_key_file_free(keyfile);
}
static void save_settings(VirtualKeyboard *vk) {
GKeyFile *keyfile = g_key_file_new();
g_key_file_set_string(keyfile, "DEFAULT", "bg_color", vk->bg_color);
g_key_file_set_double(keyfile, "DEFAULT", "opacity", vk->opacity);
g_key_file_set_string(keyfile, "DEFAULT", "text_color", vk->text_color);
g_key_file_set_integer(keyfile, "DEFAULT", "width", vk->width);
g_key_file_set_integer(keyfile, "DEFAULT", "height", vk->height);
g_key_file_set_string(keyfile, "DEFAULT", "cursor", vk->cursor_type);
g_key_file_set_string(keyfile, "DEFAULT", "font_size", vk->font_size);
GError *error = NULL;
gsize len;
char *data = g_key_file_to_data(keyfile, &len, &error);
if (!error) {
if (!g_file_set_contents(vk->config_file, data, len, &error))
g_warning("无法写入配置文件: %s", error->message);
g_free(data);
} else {
g_warning("无法生成配置数据: %s", error->message);
g_error_free(error);
}
g_key_file_free(keyfile);
}
static void create_keyboard(VirtualKeyboard *vk) {
vk->grid = gtk_grid_new();
gtk_grid_set_row_homogeneous(GTK_GRID(vk->grid), TRUE);
gtk_grid_set_column_homogeneous(GTK_GRID(vk->grid), TRUE);
gtk_widget_set_margin_start(vk->grid, 3);
gtk_widget_set_margin_end(vk->grid, 3);
gtk_widget_set_margin_top(vk->grid, 0);
gtk_widget_set_margin_bottom(vk->grid, 3);
gtk_widget_set_name(vk->grid, "grid");
gtk_container_add(GTK_CONTAINER(vk->window), vk->grid);
int row = 0;
while (keyboard_rows[row][0] != NULL) {
int col = 0;
for (int i = 0; keyboard_rows[row][i] != NULL; i++) {
const char *key_name = keyboard_rows[row][i];
int keycode = find_keycode(key_name);
if (keycode == -1) {
g_warning("未知键名: %s", key_name);
continue;
}
GtkWidget *button;
if (g_str_has_suffix(key_name, "_L") || g_str_has_suffix(key_name, "_R")) {
char *display_name = g_strndup(key_name, strlen(key_name)-2);
button = gtk_button_new_with_label(display_name);
g_free(display_name);
} else {
button = gtk_button_new_with_label(key_name);
}
gtk_widget_set_can_focus(button, FALSE);
g_object_set_data(G_OBJECT(button), "keycode", GINT_TO_POINTER(keycode));
g_signal_connect(button, "button-press-event", G_CALLBACK(on_button_press), vk);
g_signal_connect(button, "button-release-event", G_CALLBACK(on_button_release), vk);
// g_signal_connect(button, "leave-notify-event", G_CALLBACK(on_button_release), vk);
if (keycode == KEY_LEFTSHIFT || keycode == KEY_RIGHTSHIFT ||
keycode == KEY_LEFTCTRL || keycode == KEY_RIGHTCTRL ||
keycode == KEY_LEFTALT || keycode == KEY_RIGHTALT ||
keycode == KEY_LEFTMETA || keycode == KEY_RIGHTMETA) {
g_hash_table_insert(vk->modifier_buttons, GINT_TO_POINTER(keycode), button);
g_hash_table_insert(vk->modifier_active, GINT_TO_POINTER(keycode), GINT_TO_POINTER(0));
}
int width = get_button_width(key_name);
gtk_grid_attach(GTK_GRID(vk->grid), button, col, row, width, 1);
col += width;
}
row++;
}
}
static void set_cursor_for_window(VirtualKeyboard *vk) {
GdkWindow *window = gtk_widget_get_window(vk->window);
if (!window) return;
GdkDisplay *display = gdk_window_get_display(window);
GdkCursor *cursor = gdk_cursor_new_from_name(display, vk->cursor_type);
if (!cursor)
cursor = gdk_cursor_new_from_name(display, "default");
if (cursor) {
gdk_window_set_cursor(window, cursor);
g_object_unref(cursor);
}
}
static void cleanup_timeouts(VirtualKeyboard *vk) {
if (vk->delay_source != 0) {
g_source_remove(vk->delay_source);
vk->delay_source = 0;
}
if (vk->repeat_source != 0) {
g_source_remove(vk->repeat_source);
vk->repeat_source = 0;
}
vk->current_keycode = 0;
}
static gboolean on_button_press(GtkWidget *widget, GdkEventButton *event, gpointer data) {
(void)event;
VirtualKeyboard *vk = (VirtualKeyboard*)data;
int keycode = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "keycode"));
g_print("[调试] 按钮按下,keycode=%d\n", keycode);
if (g_hash_table_contains(vk->modifier_buttons, GINT_TO_POINTER(keycode))) {
gboolean active = GPOINTER_TO_INT(g_hash_table_lookup(vk->modifier_active, GINT_TO_POINTER(keycode)));
update_modifier(vk, keycode, !active);
if (keycode == KEY_LEFTSHIFT || keycode == KEY_RIGHTSHIFT) {
gboolean left = GPOINTER_TO_INT(g_hash_table_lookup(vk->modifier_active, GINT_TO_POINTER(KEY_LEFTSHIFT)));
gboolean right = GPOINTER_TO_INT(g_hash_table_lookup(vk->modifier_active, GINT_TO_POINTER(KEY_RIGHTSHIFT)));
if (left && right) {
update_modifier(vk, KEY_LEFTSHIFT, FALSE);
update_modifier(vk, KEY_RIGHTSHIFT, FALSE);
}
}
gboolean shift_active = (GPOINTER_TO_INT(g_hash_table_lookup(vk->modifier_active, GINT_TO_POINTER(KEY_LEFTSHIFT))) ||
GPOINTER_TO_INT(g_hash_table_lookup(vk->modifier_active, GINT_TO_POINTER(KEY_RIGHTSHIFT))));
update_label(vk, shift_active);
return TRUE;
}
vk->current_keycode = keycode;
emit_key(vk, keycode);
if (vk->delay_source != 0)
g_source_remove(vk->delay_source);
vk->delay_source = g_timeout_add(155, start_repeat, vk);
return TRUE;
}
static gboolean on_button_release(GtkWidget *widget, GdkEventButton *event, gpointer data) {
(void)widget;
(void)event;
VirtualKeyboard *vk = (VirtualKeyboard*)data;
g_print("[调试] 按钮释放\n");
cleanup_timeouts(vk);
return TRUE;
}
static gboolean start_repeat(gpointer user_data) {
VirtualKeyboard *vk = (VirtualKeyboard*)user_data;
if (vk->repeat_source != 0)
g_source_remove(vk->repeat_source);
vk->repeat_source = g_timeout_add(155, on_key_repeat, vk);
return FALSE;
}
static gboolean on_key_repeat(gpointer user_data) {
VirtualKeyboard *vk = (VirtualKeyboard*)user_data;
if (vk->current_keycode != 0) {
g_print("[调试] 重复按键, keycode=%d\n", vk->current_keycode);
emit_key(vk, vk->current_keycode);
}
return TRUE;
}
static void on_resize(GtkWidget *widget, GdkEventConfigure *event, gpointer data) {
(void)widget;
(void)event;
VirtualKeyboard *vk = (VirtualKeyboard*)data;
gtk_window_get_size(GTK_WINDOW(vk->window), &vk->width, &vk->height);
apply_css(vk);
}
static void on_realize(GtkWidget *widget, gpointer data) {
(void)widget;
VirtualKeyboard *vk = (VirtualKeyboard*)data;
apply_css(vk);
set_cursor_for_window(vk);
gtk_widget_queue_resize(vk->window);
}
static void destroy_cb(GtkWidget *widget, gpointer data) {
(void)widget;
VirtualKeyboard *vk = (VirtualKeyboard*)data;
g_print("[调试] 程序退出,保存配置并清理...\n");
save_settings(vk);
cleanup_timeouts(vk);
if (vk->uinput_fd >= 0) {
ioctl(vk->uinput_fd, UI_DEV_DESTROY);
close(vk->uinput_fd);
g_print("[调试] uinput 设备已销毁\n");
}
g_hash_table_destroy(vk->modifier_buttons);
g_hash_table_destroy(vk->modifier_active);
g_free(vk->bg_color);
g_free(vk->text_color);
g_free(vk->cursor_type);
g_free(vk->font_size);
g_free(vk);
gtk_main_quit();
}
int main(int argc, char *argv[]) {
/* 强制使用 X11 后端,兼容 XWayland 环境 */
setenv("GDK_BACKEND", "x11", 1);
gtk_init(&argc, &argv);
VirtualKeyboard *vk = g_new0(VirtualKeyboard, 1);
vk->uinput_fd = -1;
vk->delay_source = 0;
vk->repeat_source = 0;
vk->current_keycode = 0;
vk->css_provider = gtk_css_provider_new();
vk->modifier_buttons = g_hash_table_new(g_direct_hash, g_direct_equal);
vk->modifier_active = g_hash_table_new(g_direct_hash, g_direct_equal);
load_settings(vk);
vk->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_widget_set_name(vk->window, "toplevel");
gtk_window_set_title(GTK_WINDOW(vk->window), "Virtual Keyboard");
gtk_window_set_role(GTK_WINDOW(vk->window), "virtual-keyboard");
gtk_container_set_border_width(GTK_CONTAINER(vk->window), 0);
gtk_window_set_resizable(GTK_WINDOW(vk->window), TRUE);
gtk_window_set_keep_above(GTK_WINDOW(vk->window), TRUE);
gtk_window_set_modal(GTK_WINDOW(vk->window), FALSE);
gtk_window_set_focus_on_map(GTK_WINDOW(vk->window), FALSE);
gtk_window_set_accept_focus(GTK_WINDOW(vk->window), FALSE);
gtk_window_set_type_hint(GTK_WINDOW(vk->window), GDK_WINDOW_TYPE_HINT_NORMAL);
if (vk->width != 0)
gtk_window_set_default_size(GTK_WINDOW(vk->window), vk->width, vk->height);
GdkScreen *screen = gtk_widget_get_screen(vk->window);
GdkVisual *visual = gdk_screen_get_rgba_visual(screen);
if (visual)
gtk_widget_set_visual(vk->window, visual);
create_keyboard(vk);
if (!init_uinput(vk)) {
g_printerr("无法初始化 uinput 设备,程序退出\n");
g_free(vk);
return 1;
}
g_signal_connect(vk->window, "destroy", G_CALLBACK(destroy_cb), vk);
g_signal_connect(vk->window, "configure-event", G_CALLBACK(on_resize), vk);
g_signal_connect(vk->window, "realize", G_CALLBACK(on_realize), vk);
gtk_widget_show_all(vk->window);
if (vk->width > 0 && vk->height > 0) {
gtk_window_resize(GTK_WINDOW(vk->window), vk->width, vk->height);
g_print("[调试] 窗口显示后设置大小: %d x %d\n", vk->width, vk->height);
}
gtk_main();
return 0;
}
