给坛主捧个场,发一个叔自己用的私房代码。

回复
头像
xexz
童生
帖子: 33
注册时间: 周二 4月 07, 2026 4:05 pm

给坛主捧个场,发一个叔自己用的私房代码。

帖子 xexz »

一个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;
}
上次由 xexz 在 周四 4月 09, 2026 3:23 am,总共编辑 1 次。
头像
xexz
童生
帖子: 33
注册时间: 周二 4月 07, 2026 4:05 pm

Re: 给坛主捧个场,发一个叔自己用的私房代码。

帖子 xexz »

需要input组权限和uinput核心模块。
--------规则文件---------------

/etc/udev/rules.d/15-uinput.rules
SUBSYSTEM=="misc", KERNEL=="uinput", MODE="0660", GROUP="input"

---------配置文件------------

.config/vboard/settings.conf
[DEFAULT]
bg_color = 55,55,55
opacity = 0.75
text_color = #FFFFFF
width = 709
height = 173
cursor = pointer
font_size = 15px

图片

上次由 xexz 在 周四 4月 09, 2026 3:21 am,总共编辑 3 次。
头像
hci
秀才
帖子: 205
注册时间: 周一 4月 06, 2026 6:31 pm
昵称: 海螺子
34

Re: 给坛主捧个场,发一个叔自己用的私房代码。

帖子 hci »

我这是个PHP的网站,用不了你这高大上的代码。😳

心领了💕

头像
xexz
童生
帖子: 33
注册时间: 周二 4月 07, 2026 4:05 pm

Re: 给坛主捧个场,发一个叔自己用的私房代码。

帖子 xexz »

hci 写了: 周四 4月 09, 2026 1:12 am

我这是个PHP的网站,用不了你这高大上的代码。😳

心领了💕

误会了,误会了,

论坛本来就有</>,看到了,这就改过来。

头像
hci
秀才
帖子: 205
注册时间: 周一 4月 06, 2026 6:31 pm
昵称: 海螺子
34

Re: 给坛主捧个场,发一个叔自己用的私房代码。

帖子 hci »

你这个搞得很高级的样子。👍

xexz 写了: 周四 4月 09, 2026 3:20 am

误会了,误会了,

论坛本来就有</>,看到了,这就改过来。

头像
xexz
童生
帖子: 33
注册时间: 周二 4月 07, 2026 4:05 pm

Re: 给坛主捧个场,发一个叔自己用的私房代码。

帖子 xexz »

hci 写了: 周四 4月 09, 2026 3:23 am

你这个搞得很高级的样子。👍

谢谢,新论坛建设,需要大家彼此互相吹捧。 :mrgreen: :mrgreen: :mrgreen:

🤣 1 ❤️ 1
头像
zeami
秀才
帖子: 234
注册时间: 周一 4月 06, 2026 10:57 pm
昵称: 贼阿米
14

Re: 给坛主捧个场,发一个叔自己用的私房代码。

帖子 zeami »

xexz 写了: 周四 4月 09, 2026 3:25 am

谢谢,新论坛建设,需要大家彼此互相吹捧。 :mrgreen: :mrgreen: :mrgreen:

牛叉啊,虽然我只会写matplotlib 。。

巫咸上天 识者其谁

头像
hci
秀才
帖子: 205
注册时间: 周一 4月 06, 2026 6:31 pm
昵称: 海螺子
34

Re: 给坛主捧个场,发一个叔自己用的私房代码。

帖子 hci »

现在谁还古法编程,都是上AI,没学过的编程语言也可以上。

zeami 写了: 周四 4月 09, 2026 2:54 pm

牛叉啊,虽然我只会写matplotlib 。。

头像
zeami
秀才
帖子: 234
注册时间: 周一 4月 06, 2026 10:57 pm
昵称: 贼阿米
14

Re: 给坛主捧个场,发一个叔自己用的私房代码。

帖子 zeami »

hci 写了: 周四 4月 09, 2026 9:05 pm

现在谁还古法编程,都是上AI,没学过的编程语言也可以上。

PY matplotlib+numpy+scipy 只是免费 matlab。手写几十行就够用了。。 :roll:

巫咸上天 识者其谁

头像
xexz
童生
帖子: 33
注册时间: 周二 4月 07, 2026 4:05 pm

Re: 给坛主捧个场,发一个叔自己用的私房代码。

帖子 xexz »

zeami 写了: 周四 4月 09, 2026 2:54 pm

牛叉啊,虽然我只会写matplotlib 。。

平均deepseek水平。 :mrgreen: :mrgreen: :mrgreen:

回复