mirror of https://github.com/nirenjan/libx52.git
feat: Add virtual keyboard/mouse library support
Previously, x52d had conditional compilation flags linking to libevdev on Linux. However, I wanted to change this so that we use an abstraction layer that will link to the appropriate backend (evdev on Linux only for now). The idea is that we get rid of all conditional compilation blocks and deal with the backend through the vkm library. This new library handles the mouse scrolling, clicking, as well as adding the ability to include keyboard events so that we can support the profiles feature in a future commit.mouse-isometric-mode
parent
230951a232
commit
75e6f253c9
|
|
@ -986,6 +986,7 @@ INPUT_FILE_ENCODING =
|
|||
FILE_PATTERNS = libx52.h \
|
||||
libx52io.h \
|
||||
libx52util.h \
|
||||
vkm.h \
|
||||
x52_cli.c \
|
||||
x52ctl.c \
|
||||
x52dcomm.h \
|
||||
|
|
|
|||
|
|
@ -26,19 +26,14 @@ x52d_sources = [
|
|||
'x52d_notify.c',
|
||||
'x52d_led.c',
|
||||
'x52d_command.c',
|
||||
'x52d_io.c',
|
||||
'x52d_mouse_handler.c',
|
||||
]
|
||||
|
||||
dep_threads = dependency('threads')
|
||||
x52d_linkwith = [lib_libx52, lib_libx52dcomm]
|
||||
x52d_linkwith = [lib_libx52, lib_libx52dcomm, lib_vkm, lib_libx52io]
|
||||
x52d_deps = [dep_pinelog, dep_inih, dep_threads, dep_intl]
|
||||
x52d_cflags = []
|
||||
if dep_evdev.found()
|
||||
x52d_sources += 'x52d_io.c'
|
||||
x52d_sources += 'x52d_mouse_evdev.c'
|
||||
x52d_cflags += '-DHAVE_EVDEV'
|
||||
x52d_linkwith += lib_libx52io
|
||||
x52d_deps += dep_evdev
|
||||
endif
|
||||
|
||||
exe_x52d = executable('x52d', x52d_sources,
|
||||
install: true,
|
||||
|
|
|
|||
|
|
@ -329,10 +329,8 @@ int main(int argc, char **argv)
|
|||
goto cleanup;
|
||||
}
|
||||
x52d_notify_init(notify_sock);
|
||||
#if defined(HAVE_EVDEV)
|
||||
x52d_io_init();
|
||||
x52d_mouse_evdev_init();
|
||||
#endif
|
||||
x52d_mouse_handler_init();
|
||||
|
||||
// Re-enable signals
|
||||
rc = pthread_sigmask(SIG_UNBLOCK, &sigblockset, NULL);
|
||||
|
|
@ -371,10 +369,8 @@ cleanup:
|
|||
x52d_dev_exit();
|
||||
x52d_command_exit();
|
||||
x52d_notify_exit();
|
||||
#if defined(HAVE_EVDEV)
|
||||
x52d_mouse_evdev_exit();
|
||||
x52d_mouse_handler_exit();
|
||||
x52d_io_exit();
|
||||
#endif
|
||||
|
||||
// Remove the PID file
|
||||
PINELOG_TRACE("Removing PID file %s", pid_file);
|
||||
|
|
|
|||
|
|
@ -30,9 +30,7 @@ void x52d_cfg_set_Mouse_Enabled(bool enabled)
|
|||
{
|
||||
PINELOG_DEBUG(_("Setting mouse enable to %s"),
|
||||
enabled ? _("on") : _("off"));
|
||||
#if defined HAVE_EVDEV
|
||||
x52d_mouse_evdev_thread_control(enabled);
|
||||
#endif
|
||||
x52d_mouse_thread_control(enabled);
|
||||
}
|
||||
|
||||
void x52d_cfg_set_Mouse_Speed(int speed)
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ extern volatile int mouse_scroll_dir;
|
|||
|
||||
#define MOUSE_MULT_FACTOR 4
|
||||
|
||||
void x52d_mouse_evdev_thread_control(bool enabled);
|
||||
void x52d_mouse_evdev_init(void);
|
||||
void x52d_mouse_evdev_exit(void);
|
||||
void x52d_mouse_thread_control(bool enabled);
|
||||
void x52d_mouse_handler_init(void);
|
||||
void x52d_mouse_handler_exit(void);
|
||||
void x52d_mouse_report_event(libx52io_report *report);
|
||||
|
||||
#endif // !defined X52D_MOUSE_H
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* Saitek X52 Pro MFD & LED driver - Mouse driver
|
||||
*
|
||||
* Copyright (C) 2021 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
* Copyright (C) 2021-2026 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
|
@ -12,10 +12,10 @@
|
|||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "libevdev/libevdev.h"
|
||||
#include "libevdev/libevdev-uinput.h"
|
||||
#include "libx52io.h"
|
||||
#include "vkm.h"
|
||||
|
||||
#define PINELOG_MODULE X52D_MOD_MOUSE
|
||||
#include "pinelog.h"
|
||||
#include "x52d_config.h"
|
||||
#include "x52d_const.h"
|
||||
|
|
@ -24,36 +24,47 @@
|
|||
static pthread_t mouse_thr;
|
||||
static bool mouse_thr_enabled = false;
|
||||
|
||||
static struct libevdev_uinput *mouse_uidev;
|
||||
static bool mouse_uidev_created = false;
|
||||
static vkm_context *mouse_context;
|
||||
|
||||
static volatile libx52io_report old_report;
|
||||
static volatile libx52io_report new_report;
|
||||
|
||||
static int report_button_change(int button, int index)
|
||||
static int report_button_change(vkm_mouse_button button, int index)
|
||||
{
|
||||
int rc = 1;
|
||||
vkm_result rc;
|
||||
bool old_button = old_report.button[index];
|
||||
bool new_button = new_report.button[index];
|
||||
vkm_button_state state;
|
||||
|
||||
if (old_button != new_button) {
|
||||
rc = libevdev_uinput_write_event(mouse_uidev, EV_KEY, button,
|
||||
(int)new_button);
|
||||
if (rc != 0) {
|
||||
PINELOG_ERROR(_("Error writing mouse button event (button %d, state %d)"),
|
||||
button, (int)new_button);
|
||||
state = new_button ? VKM_BUTTON_PRESSED : VKM_BUTTON_RELEASED;
|
||||
rc = vkm_mouse_click(mouse_context, button, state);
|
||||
if (rc != VKM_SUCCESS && rc != VKM_ERROR_NO_CHANGE) {
|
||||
PINELOG_ERROR(_("Error %d writing mouse button event (button %d, state %d)"),
|
||||
rc, button, (int)new_button);
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
return (rc == VKM_SUCCESS);
|
||||
}
|
||||
|
||||
static int report_wheel(void)
|
||||
{
|
||||
int rc = 1;
|
||||
vkm_result rc;
|
||||
int wheel = 0;
|
||||
bool scroll_up = new_report.button[LIBX52IO_BTN_MOUSE_SCROLL_UP];
|
||||
bool scroll_dn = new_report.button[LIBX52IO_BTN_MOUSE_SCROLL_DN];
|
||||
bool old_scroll_up = old_report.button[LIBX52IO_BTN_MOUSE_SCROLL_UP];
|
||||
bool old_scroll_dn = old_report.button[LIBX52IO_BTN_MOUSE_SCROLL_DN];
|
||||
vkm_mouse_scroll_direction dir;
|
||||
|
||||
/*
|
||||
* Handle multiple scroll button presses in sequence. This happens if a
|
||||
* hardware axis is very noisy and the firmware sends a sequence of reports
|
||||
* with button down, even though this is technically a momentary button.
|
||||
*/
|
||||
scroll_up = (scroll_up ^ old_scroll_up) & scroll_up;
|
||||
scroll_dn = (scroll_dn ^ old_scroll_dn) & scroll_dn;
|
||||
|
||||
if (scroll_up) {
|
||||
// Scroll up event
|
||||
|
|
@ -64,19 +75,18 @@ static int report_wheel(void)
|
|||
}
|
||||
|
||||
if (wheel != 0) {
|
||||
rc = libevdev_uinput_write_event(mouse_uidev, EV_REL, REL_WHEEL, wheel);
|
||||
if (rc != 0) {
|
||||
PINELOG_ERROR(_("Error writing mouse wheel event %d"), wheel);
|
||||
dir = (wheel == 1) ? VKM_MOUSE_SCROLL_UP : VKM_MOUSE_SCROLL_DOWN;
|
||||
rc = vkm_mouse_scroll(mouse_context, dir);
|
||||
if (rc != VKM_SUCCESS) {
|
||||
PINELOG_ERROR(_("Error writing mouse wheel event %d"), dir);
|
||||
}
|
||||
}
|
||||
|
||||
return rc;
|
||||
return (rc == VKM_SUCCESS);
|
||||
}
|
||||
|
||||
static int report_axis(int axis, int index)
|
||||
static int get_axis_val(int index)
|
||||
{
|
||||
int rc = 1;
|
||||
|
||||
int axis_val = new_report.axis[index];
|
||||
|
||||
/*
|
||||
|
|
@ -96,22 +106,29 @@ static int report_axis(int axis, int index)
|
|||
*/
|
||||
axis_val = (axis_val * mouse_mult) / MOUSE_MULT_FACTOR;
|
||||
|
||||
if (axis_val) {
|
||||
rc = libevdev_uinput_write_event(mouse_uidev, EV_REL, axis, axis_val);
|
||||
if (rc != 0) {
|
||||
PINELOG_ERROR(_("Error writing mouse axis event (axis %d, value %d)"),
|
||||
axis, axis_val);
|
||||
}
|
||||
return axis_val;
|
||||
}
|
||||
|
||||
static int report_axis(void)
|
||||
{
|
||||
vkm_result rc;
|
||||
int dx = get_axis_val(LIBX52IO_AXIS_THUMBX);
|
||||
int dy = get_axis_val(LIBX52IO_AXIS_THUMBY);
|
||||
|
||||
rc = vkm_mouse_move(mouse_context, dx, dy);
|
||||
if (rc != VKM_SUCCESS && rc != VKM_ERROR_NO_CHANGE) {
|
||||
PINELOG_ERROR(_("Error %d writing mouse axis event (dx %d, dy %d)"),
|
||||
rc, dx, dy);
|
||||
}
|
||||
|
||||
return rc;
|
||||
return (rc == VKM_SUCCESS);
|
||||
}
|
||||
|
||||
static void report_sync(void)
|
||||
{
|
||||
int rc;
|
||||
rc = libevdev_uinput_write_event(mouse_uidev, EV_SYN, SYN_REPORT, 0);
|
||||
if (rc != 0) {
|
||||
vkm_result rc;
|
||||
rc = vkm_sync(mouse_context);
|
||||
if (rc != VKM_SUCCESS) {
|
||||
PINELOG_ERROR(_("Error writing mouse sync event"));
|
||||
} else {
|
||||
memcpy((void *)&old_report, (void *)&new_report, sizeof(old_report));
|
||||
|
|
@ -129,16 +146,11 @@ static void reset_reports(void)
|
|||
|
||||
static void * x52_mouse_thr(void *param)
|
||||
{
|
||||
bool state_changed;
|
||||
(void)param;
|
||||
|
||||
PINELOG_INFO(_("Starting X52 virtual mouse driver thread"));
|
||||
for (;;) {
|
||||
state_changed = false;
|
||||
state_changed |= (0 == report_axis(REL_X, LIBX52IO_AXIS_THUMBX));
|
||||
state_changed |= (0 == report_axis(REL_Y, LIBX52IO_AXIS_THUMBY));
|
||||
|
||||
if (state_changed) {
|
||||
if (report_axis()) {
|
||||
report_sync();
|
||||
}
|
||||
|
||||
|
|
@ -166,9 +178,9 @@ static void x52d_mouse_thr_exit(void)
|
|||
pthread_cancel(mouse_thr);
|
||||
}
|
||||
|
||||
void x52d_mouse_evdev_thread_control(bool enabled)
|
||||
void x52d_mouse_thread_control(bool enabled)
|
||||
{
|
||||
if (!mouse_uidev_created) {
|
||||
if (!vkm_is_ready(mouse_context)) {
|
||||
PINELOG_INFO(_("Virtual mouse not created. Ignoring thread state change"));
|
||||
return;
|
||||
}
|
||||
|
|
@ -198,13 +210,13 @@ void x52d_mouse_report_event(libx52io_report *report)
|
|||
if (report) {
|
||||
memcpy((void *)&new_report, report, sizeof(new_report));
|
||||
|
||||
if (!mouse_uidev_created || !mouse_thr_enabled) {
|
||||
if (!vkm_is_ready(mouse_context) || !mouse_thr_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
state_changed = false;
|
||||
state_changed |= (0 == report_button_change(BTN_LEFT, LIBX52IO_BTN_MOUSE_PRIMARY));
|
||||
state_changed |= (0 == report_button_change(BTN_RIGHT, LIBX52IO_BTN_MOUSE_SECONDARY));
|
||||
state_changed |= (0 == report_button_change(VKM_MOUSE_BTN_LEFT, LIBX52IO_BTN_MOUSE_PRIMARY));
|
||||
state_changed |= (0 == report_button_change(VKM_MOUSE_BTN_RIGHT, LIBX52IO_BTN_MOUSE_SECONDARY));
|
||||
state_changed |= (0 == report_wheel());
|
||||
|
||||
if (state_changed) {
|
||||
|
|
@ -215,35 +227,27 @@ void x52d_mouse_report_event(libx52io_report *report)
|
|||
}
|
||||
}
|
||||
|
||||
void x52d_mouse_evdev_init(void)
|
||||
void x52d_mouse_handler_init(void)
|
||||
{
|
||||
int rc;
|
||||
struct libevdev *dev;
|
||||
vkm_result rc;
|
||||
|
||||
/* Create a new mouse device */
|
||||
dev = libevdev_new();
|
||||
libevdev_set_name(dev, "X52 virtual mouse");
|
||||
libevdev_enable_event_type(dev, EV_REL);
|
||||
libevdev_enable_event_code(dev, EV_REL, REL_X, NULL);
|
||||
libevdev_enable_event_code(dev, EV_REL, REL_Y, NULL);
|
||||
libevdev_enable_event_code(dev, EV_REL, REL_WHEEL, NULL);
|
||||
libevdev_enable_event_type(dev, EV_KEY);
|
||||
libevdev_enable_event_code(dev, EV_KEY, BTN_LEFT, NULL);
|
||||
libevdev_enable_event_code(dev, EV_KEY, BTN_RIGHT, NULL);
|
||||
rc = vkm_init(&mouse_context);
|
||||
if (rc != VKM_SUCCESS) {
|
||||
PINELOG_ERROR(_("Error %d creating X52 virtual mouse"), rc);
|
||||
return;
|
||||
}
|
||||
|
||||
rc = libevdev_uinput_create_from_device(dev, LIBEVDEV_UINPUT_OPEN_MANAGED,
|
||||
&mouse_uidev);
|
||||
if (rc != 0) {
|
||||
PINELOG_ERROR(_("Error %d creating X52 virtual mouse: %s"),
|
||||
-rc, strerror(-rc));
|
||||
} else {
|
||||
mouse_uidev_created = true;
|
||||
vkm_set_option(mouse_context, VKM_OPT_DEVICE_NAME, "X52 virtual mouse");
|
||||
|
||||
rc = vkm_start(mouse_context);
|
||||
if (rc != VKM_SUCCESS) {
|
||||
PINELOG_ERROR(_("Error %d creating X52 virtual mouse"), rc);
|
||||
}
|
||||
}
|
||||
|
||||
void x52d_mouse_evdev_exit(void)
|
||||
void x52d_mouse_handler_exit(void)
|
||||
{
|
||||
x52d_mouse_evdev_thread_control(false);
|
||||
mouse_uidev_created = false;
|
||||
libevdev_uinput_destroy(mouse_uidev);
|
||||
x52d_mouse_thread_control(false);
|
||||
vkm_exit(mouse_context);
|
||||
mouse_context = NULL;
|
||||
}
|
||||
|
|
@ -19,22 +19,18 @@
|
|||
#include "x52d_const.h"
|
||||
#include "x52d_mouse.h"
|
||||
|
||||
#if defined HAVE_EVDEV
|
||||
/* Stub for evdev */
|
||||
void x52d_mouse_evdev_thread_control(bool enabled)
|
||||
/* Stub for handler */
|
||||
void x52d_mouse_thread_control(bool enabled)
|
||||
{
|
||||
function_called();
|
||||
check_expected(enabled);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void test_mouse_thread_enabled(void **state)
|
||||
{
|
||||
(void)state;
|
||||
#if defined HAVE_EVDEV
|
||||
expect_function_calls(x52d_mouse_evdev_thread_control, 1);
|
||||
expect_value(x52d_mouse_evdev_thread_control, enabled, true);
|
||||
#endif
|
||||
expect_function_calls(x52d_mouse_thread_control, 1);
|
||||
expect_value(x52d_mouse_thread_control, enabled, true);
|
||||
|
||||
x52d_cfg_set_Mouse_Enabled(true);
|
||||
}
|
||||
|
|
@ -42,10 +38,8 @@ static void test_mouse_thread_enabled(void **state)
|
|||
static void test_mouse_thread_disabled(void **state)
|
||||
{
|
||||
(void)state;
|
||||
#if defined HAVE_EVDEV
|
||||
expect_function_calls(x52d_mouse_evdev_thread_control, 1);
|
||||
expect_value(x52d_mouse_evdev_thread_control, enabled, false);
|
||||
#endif
|
||||
expect_function_calls(x52d_mouse_thread_control, 1);
|
||||
expect_value(x52d_mouse_thread_control, enabled, false);
|
||||
|
||||
x52d_cfg_set_Mouse_Enabled(false);
|
||||
}
|
||||
|
|
|
|||
10
meson.build
10
meson.build
|
|
@ -9,7 +9,12 @@ if not dep_hidapi.found()
|
|||
dep_hidapi = dependency('hidapi', required: true)
|
||||
endif
|
||||
|
||||
dep_evdev = dependency('libevdev', required: false)
|
||||
if host_machine.system() == 'linux'
|
||||
dep_evdev = dependency('libevdev', required: false)
|
||||
else
|
||||
# Create a dummy dependency
|
||||
dep_evdev = dependency('', required: false)
|
||||
endif
|
||||
|
||||
dep_systemd = dependency('systemd', required: false)
|
||||
dep_udev = dependency('udev', required: false)
|
||||
|
|
@ -108,11 +113,12 @@ dep_inih = dependency('inih')
|
|||
# Shared libraries and programs
|
||||
#######################################################################
|
||||
# Includes
|
||||
includes = include_directories('.', 'libx52', 'libx52io', 'libx52util')
|
||||
includes = include_directories('.', 'libx52', 'libx52io', 'libx52util', 'vkm')
|
||||
|
||||
subdir('libx52')
|
||||
subdir('libx52io')
|
||||
subdir('libx52util')
|
||||
subdir('vkm')
|
||||
subdir('bugreport')
|
||||
subdir('cli')
|
||||
subdir('joytest')
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ libx52/x52_stringify.c
|
|||
|
||||
libx52io/io_strings.c
|
||||
|
||||
vkm/vkm_common.c
|
||||
|
||||
evtest/ev_test.c
|
||||
|
||||
joytest/x52_test.c
|
||||
|
|
@ -22,6 +24,6 @@ daemon/x52d_config_parser.c
|
|||
daemon/x52d_device.c
|
||||
daemon/x52d_io.c
|
||||
daemon/x52d_mouse.c
|
||||
daemon/x52d_mouse_evdev.c
|
||||
daemon/x52d_mouse_handler.c
|
||||
daemon/x52d_notify.c
|
||||
daemon/x52ctl.c
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: libx52 0.3.3\n"
|
||||
"Report-Msgid-Bugs-To: https://github.com/nirenjan/libx52/issues\n"
|
||||
"POT-Creation-Date: 2026-03-19 00:09-0700\n"
|
||||
"POT-Creation-Date: 2026-03-27 20:52-0700\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
|
@ -17,7 +17,7 @@ msgstr ""
|
|||
"Content-Type: text/plain; charset=CHARSET\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: libx52/x52_strerror.c:23 libx52io/io_strings.c:101
|
||||
#: libx52/x52_strerror.c:23 libx52io/io_strings.c:101 vkm/vkm_common.c:25
|
||||
msgid "Success"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ msgstr ""
|
|||
msgid "Insufficient memory"
|
||||
msgstr ""
|
||||
|
||||
#: libx52/x52_strerror.c:26
|
||||
#: libx52/x52_strerror.c:26 vkm/vkm_common.c:29
|
||||
msgid "Invalid parameter"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -85,7 +85,7 @@ msgstr ""
|
|||
msgid "System call interrupted"
|
||||
msgstr ""
|
||||
|
||||
#: libx52/x52_strerror.c:66 libx52io/io_strings.c:125
|
||||
#: libx52/x52_strerror.c:66 libx52io/io_strings.c:125 vkm/vkm_common.c:52
|
||||
#, c-format
|
||||
msgid "Unknown error %d"
|
||||
msgstr ""
|
||||
|
|
@ -143,12 +143,12 @@ msgid "Unknown LED state %d"
|
|||
msgstr ""
|
||||
|
||||
#: libx52/x52_stringify.c:47 daemon/x52d_clock.c:29 daemon/x52d_mouse.c:32
|
||||
#: daemon/x52d_mouse.c:68
|
||||
#: daemon/x52d_mouse.c:66
|
||||
msgid "off"
|
||||
msgstr ""
|
||||
|
||||
#: libx52/x52_stringify.c:48 daemon/x52d_clock.c:29 daemon/x52d_mouse.c:32
|
||||
#: daemon/x52d_mouse.c:68
|
||||
#: daemon/x52d_mouse.c:66
|
||||
msgid "on"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -233,6 +233,34 @@ msgstr ""
|
|||
msgid "Read timeout"
|
||||
msgstr ""
|
||||
|
||||
#: vkm/vkm_common.c:26
|
||||
msgid "Unknown error"
|
||||
msgstr ""
|
||||
|
||||
#: vkm/vkm_common.c:27
|
||||
msgid "Not ready"
|
||||
msgstr ""
|
||||
|
||||
#: vkm/vkm_common.c:28
|
||||
msgid "Out of memory"
|
||||
msgstr ""
|
||||
|
||||
#: vkm/vkm_common.c:30
|
||||
msgid "Not supported"
|
||||
msgstr ""
|
||||
|
||||
#: vkm/vkm_common.c:31
|
||||
msgid "Virtual device failure"
|
||||
msgstr ""
|
||||
|
||||
#: vkm/vkm_common.c:32
|
||||
msgid "Unable to write event"
|
||||
msgstr ""
|
||||
|
||||
#: vkm/vkm_common.c:33
|
||||
msgid "No state change"
|
||||
msgstr ""
|
||||
|
||||
#: evtest/ev_test.c:110
|
||||
#, c-format
|
||||
msgid "Device ID: vendor 0x%04x product 0x%04x version 0x%04x\n"
|
||||
|
|
@ -576,25 +604,25 @@ msgstr ""
|
|||
msgid "Error %d blocking signals on child threads: %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_main.c:340
|
||||
#: daemon/x52d_main.c:338
|
||||
#, c-format
|
||||
msgid "Error %d unblocking signals on child threads: %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_main.c:353
|
||||
#: daemon/x52d_main.c:351
|
||||
msgid "Reloading X52 configuration"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_main.c:360
|
||||
#: daemon/x52d_main.c:358
|
||||
msgid "Saving X52 configuration to disk"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_main.c:366
|
||||
#: daemon/x52d_main.c:364
|
||||
#, c-format
|
||||
msgid "Received termination signal %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_main.c:383
|
||||
#: daemon/x52d_main.c:379
|
||||
msgid "Shutting down X52 daemon"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -841,60 +869,60 @@ msgstr ""
|
|||
msgid "Setting mouse enable to %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_mouse.c:47
|
||||
#: daemon/x52d_mouse.c:45
|
||||
#, c-format
|
||||
msgid "Ignoring mouse speed %d outside supported range (0-%d)"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_mouse.c:59
|
||||
#: daemon/x52d_mouse.c:57
|
||||
#, c-format
|
||||
msgid "Setting mouse speed to %d (delay %d ms, multiplier %f)"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_mouse.c:67
|
||||
#: daemon/x52d_mouse.c:65
|
||||
#, c-format
|
||||
msgid "Setting mouse reverse scroll to %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_mouse_evdev.c:43
|
||||
#: daemon/x52d_mouse_handler.c:43
|
||||
#, c-format
|
||||
msgid "Error writing mouse button event (button %d, state %d)"
|
||||
msgid "Error %d writing mouse button event (button %d, state %d)"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_mouse_evdev.c:69
|
||||
#: daemon/x52d_mouse_handler.c:81
|
||||
#, c-format
|
||||
msgid "Error writing mouse wheel event %d"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_mouse_evdev.c:102
|
||||
#: daemon/x52d_mouse_handler.c:120
|
||||
#, c-format
|
||||
msgid "Error writing mouse axis event (axis %d, value %d)"
|
||||
msgid "Error %d writing mouse axis event (dx %d, dy %d)"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_mouse_evdev.c:115
|
||||
#: daemon/x52d_mouse_handler.c:132
|
||||
msgid "Error writing mouse sync event"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_mouse_evdev.c:135
|
||||
#: daemon/x52d_mouse_handler.c:151
|
||||
msgid "Starting X52 virtual mouse driver thread"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_mouse_evdev.c:158
|
||||
#: daemon/x52d_mouse_handler.c:170
|
||||
#, c-format
|
||||
msgid "Error %d initializing mouse thread: %s"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_mouse_evdev.c:165
|
||||
#: daemon/x52d_mouse_handler.c:177
|
||||
msgid "Shutting down X52 virtual mouse driver thread"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_mouse_evdev.c:172
|
||||
#: daemon/x52d_mouse_handler.c:184
|
||||
msgid "Virtual mouse not created. Ignoring thread state change"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_mouse_evdev.c:237
|
||||
#: daemon/x52d_mouse_handler.c:236 daemon/x52d_mouse_handler.c:244
|
||||
#, c-format
|
||||
msgid "Error %d creating X52 virtual mouse: %s"
|
||||
msgid "Error %d creating X52 virtual mouse"
|
||||
msgstr ""
|
||||
|
||||
#: daemon/x52d_notify.c:46
|
||||
|
|
|
|||
93
po/xx_PL.po
93
po/xx_PL.po
|
|
@ -7,17 +7,17 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: libx52 0.2.3\n"
|
||||
"Report-Msgid-Bugs-To: https://github.com/nirenjan/libx52/issues\n"
|
||||
"POT-Creation-Date: 2026-03-19 00:09-0700\n"
|
||||
"PO-Revision-Date: 2023-01-04 08:40-0800\n"
|
||||
"POT-Creation-Date: 2026-03-27 20:52-0700\n"
|
||||
"PO-Revision-Date: 2026-03-27 08:33-0700\n"
|
||||
"Last-Translator: Nirenjan Krishnan <nirenjan@gmail.com>\n"
|
||||
"Language-Team: Dummy Language for testing i18n\n"
|
||||
"Language: xx_PL\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 3.0.1\n"
|
||||
"X-Generator: Poedit 3.4.2\n"
|
||||
|
||||
#: libx52/x52_strerror.c:23 libx52io/io_strings.c:101
|
||||
#: libx52/x52_strerror.c:23 libx52io/io_strings.c:101 vkm/vkm_common.c:25
|
||||
msgid "Success"
|
||||
msgstr "Uccesssay"
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ msgstr "Initializationay ailurefay"
|
|||
msgid "Insufficient memory"
|
||||
msgstr "Insufficientay emorymay"
|
||||
|
||||
#: libx52/x52_strerror.c:26
|
||||
#: libx52/x52_strerror.c:26 vkm/vkm_common.c:29
|
||||
msgid "Invalid parameter"
|
||||
msgstr "Invaliday arameterpay"
|
||||
|
||||
|
|
@ -85,7 +85,7 @@ msgstr "Ipepay erroray"
|
|||
msgid "System call interrupted"
|
||||
msgstr "Ystemsay allcay interrupteday"
|
||||
|
||||
#: libx52/x52_strerror.c:66 libx52io/io_strings.c:125
|
||||
#: libx52/x52_strerror.c:66 libx52io/io_strings.c:125 vkm/vkm_common.c:52
|
||||
#, c-format
|
||||
msgid "Unknown error %d"
|
||||
msgstr "Unknownay erroray %d"
|
||||
|
|
@ -143,12 +143,12 @@ msgid "Unknown LED state %d"
|
|||
msgstr "Unknownay EDLay atestay %d"
|
||||
|
||||
#: libx52/x52_stringify.c:47 daemon/x52d_clock.c:29 daemon/x52d_mouse.c:32
|
||||
#: daemon/x52d_mouse.c:68
|
||||
#: daemon/x52d_mouse.c:66
|
||||
msgid "off"
|
||||
msgstr "offay"
|
||||
|
||||
#: libx52/x52_stringify.c:48 daemon/x52d_clock.c:29 daemon/x52d_mouse.c:32
|
||||
#: daemon/x52d_mouse.c:68
|
||||
#: daemon/x52d_mouse.c:66
|
||||
msgid "on"
|
||||
msgstr "onay"
|
||||
|
||||
|
|
@ -233,6 +233,36 @@ msgstr "I/O erroray"
|
|||
msgid "Read timeout"
|
||||
msgstr "Eadray imeouttay"
|
||||
|
||||
#: vkm/vkm_common.c:26
|
||||
#, fuzzy
|
||||
msgid "Unknown error"
|
||||
msgstr "Unknownay erroray %d"
|
||||
|
||||
#: vkm/vkm_common.c:27
|
||||
msgid "Not ready"
|
||||
msgstr ""
|
||||
|
||||
#: vkm/vkm_common.c:28
|
||||
msgid "Out of memory"
|
||||
msgstr ""
|
||||
|
||||
#: vkm/vkm_common.c:30
|
||||
#, fuzzy
|
||||
msgid "Not supported"
|
||||
msgstr "Operationay otnay upportedsay"
|
||||
|
||||
#: vkm/vkm_common.c:31
|
||||
msgid "Virtual device failure"
|
||||
msgstr ""
|
||||
|
||||
#: vkm/vkm_common.c:32
|
||||
msgid "Unable to write event"
|
||||
msgstr ""
|
||||
|
||||
#: vkm/vkm_common.c:33
|
||||
msgid "No state change"
|
||||
msgstr ""
|
||||
|
||||
#: evtest/ev_test.c:110
|
||||
#, c-format
|
||||
msgid "Device ID: vendor 0x%04x product 0x%04x version 0x%04x\n"
|
||||
|
|
@ -625,25 +655,25 @@ msgstr "Otifynay ocketsay = %s"
|
|||
msgid "Error %d blocking signals on child threads: %s"
|
||||
msgstr "Erroray %d ockingblay ignalssay onay ildchay eadsthray: %s"
|
||||
|
||||
#: daemon/x52d_main.c:340
|
||||
#: daemon/x52d_main.c:338
|
||||
#, c-format
|
||||
msgid "Error %d unblocking signals on child threads: %s"
|
||||
msgstr "Erroray %d unblockingay ignalssay onay ildchay eadsthray: %s"
|
||||
|
||||
#: daemon/x52d_main.c:353
|
||||
#: daemon/x52d_main.c:351
|
||||
msgid "Reloading X52 configuration"
|
||||
msgstr "Eloadingray X52 onfigurationcay"
|
||||
|
||||
#: daemon/x52d_main.c:360
|
||||
#: daemon/x52d_main.c:358
|
||||
msgid "Saving X52 configuration to disk"
|
||||
msgstr "Avingsay X52 onfigurationcay otay iskday"
|
||||
|
||||
#: daemon/x52d_main.c:366
|
||||
#: daemon/x52d_main.c:364
|
||||
#, c-format
|
||||
msgid "Received termination signal %s"
|
||||
msgstr "Eceivedray erminationtay ignalsay %s"
|
||||
|
||||
#: daemon/x52d_main.c:383
|
||||
#: daemon/x52d_main.c:379
|
||||
msgid "Shutting down X52 daemon"
|
||||
msgstr "Uttingshay ownday X52 aemonday"
|
||||
|
||||
|
|
@ -894,61 +924,62 @@ msgstr "Uttingshay ownday X52 I/O iverdray eadthray"
|
|||
msgid "Setting mouse enable to %s"
|
||||
msgstr "Ettingsay ousemay enableay otay %s"
|
||||
|
||||
#: daemon/x52d_mouse.c:47
|
||||
#: daemon/x52d_mouse.c:45
|
||||
#, c-format
|
||||
msgid "Ignoring mouse speed %d outside supported range (0-%d)"
|
||||
msgstr "Ignoringay ousemay eedspay %d outsideay upportedsay angeray (0-%d)"
|
||||
|
||||
#: daemon/x52d_mouse.c:59
|
||||
#: daemon/x52d_mouse.c:57
|
||||
#, c-format
|
||||
msgid "Setting mouse speed to %d (delay %d ms, multiplier %f)"
|
||||
msgstr "Ettingsay ousemay eedspay otay %d (elayday %d ms, ultipliermay %f)"
|
||||
|
||||
#: daemon/x52d_mouse.c:67
|
||||
#: daemon/x52d_mouse.c:65
|
||||
#, c-format
|
||||
msgid "Setting mouse reverse scroll to %s"
|
||||
msgstr "Ettingsay ousemay everseray ollscray otay %s"
|
||||
|
||||
#: daemon/x52d_mouse_evdev.c:43
|
||||
#: daemon/x52d_mouse_handler.c:43
|
||||
#, c-format
|
||||
msgid "Error writing mouse button event (button %d, state %d)"
|
||||
msgstr "Erroray itingwray ousemay uttonbay eventay (uttonbay %d, atestay %d)"
|
||||
msgid "Error %d writing mouse button event (button %d, state %d)"
|
||||
msgstr ""
|
||||
"Erroray %d itingwray ousemay uttonbay eventay (uttonbay %d, atestay %d)"
|
||||
|
||||
#: daemon/x52d_mouse_evdev.c:69
|
||||
#: daemon/x52d_mouse_handler.c:81
|
||||
#, c-format
|
||||
msgid "Error writing mouse wheel event %d"
|
||||
msgstr "Erroray itingwray ousemay eelwhay eventay %d"
|
||||
|
||||
#: daemon/x52d_mouse_evdev.c:102
|
||||
#: daemon/x52d_mouse_handler.c:120
|
||||
#, c-format
|
||||
msgid "Error writing mouse axis event (axis %d, value %d)"
|
||||
msgstr "Erroray itingwray ousemay axisay eventay (axisay %d, aluevay %d)"
|
||||
msgid "Error %d writing mouse axis event (dx %d, dy %d)"
|
||||
msgstr "Erroray %d itingwray ousemay axisay eventay (xday %d, yday %d)"
|
||||
|
||||
#: daemon/x52d_mouse_evdev.c:115
|
||||
#: daemon/x52d_mouse_handler.c:132
|
||||
msgid "Error writing mouse sync event"
|
||||
msgstr "Erroray itingwray ousemay yncsay eventay"
|
||||
|
||||
#: daemon/x52d_mouse_evdev.c:135
|
||||
#: daemon/x52d_mouse_handler.c:151
|
||||
msgid "Starting X52 virtual mouse driver thread"
|
||||
msgstr "Artingstay X52 irtualvay ousemay iverdray eadthray"
|
||||
|
||||
#: daemon/x52d_mouse_evdev.c:158
|
||||
#: daemon/x52d_mouse_handler.c:170
|
||||
#, c-format
|
||||
msgid "Error %d initializing mouse thread: %s"
|
||||
msgstr "Erroray %d initializingay ousemay eadthray: %s"
|
||||
|
||||
#: daemon/x52d_mouse_evdev.c:165
|
||||
#: daemon/x52d_mouse_handler.c:177
|
||||
msgid "Shutting down X52 virtual mouse driver thread"
|
||||
msgstr "Uttingshay ownday X52 irtualvay ousemay iverdray eadthray"
|
||||
|
||||
#: daemon/x52d_mouse_evdev.c:172
|
||||
#: daemon/x52d_mouse_handler.c:184
|
||||
msgid "Virtual mouse not created. Ignoring thread state change"
|
||||
msgstr "Irtualvay ousemay otnay eatedcray. Ignoringa eadthray atestay angechay"
|
||||
|
||||
#: daemon/x52d_mouse_evdev.c:237
|
||||
#: daemon/x52d_mouse_handler.c:236 daemon/x52d_mouse_handler.c:244
|
||||
#, c-format
|
||||
msgid "Error %d creating X52 virtual mouse: %s"
|
||||
msgstr "Erroray %d eatingcray X52 irtualvay ousemay: %s"
|
||||
msgid "Error %d creating X52 virtual mouse"
|
||||
msgstr "Erroray %d eatingcray X52 irtualvay ousemay"
|
||||
|
||||
#: daemon/x52d_notify.c:46
|
||||
#, c-format
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
Virtual keyboard/mouse infrastructure
|
||||
=====================================
|
||||
|
||||
The virtual keyboard/mouse infrastructure (or VKM), is an API used by an
|
||||
application to inject keyboard and mouse events into the OS stack. The advantage
|
||||
of using a separate API for this is so that this can be handled in a
|
||||
cross-platform manner without having to sprinkle `#ifdef`'s throughout the
|
||||
program.
|
||||
|
||||
Base API
|
||||
========
|
||||
|
||||
The API is based around a context, which is an opaque pointer returned by
|
||||
`vkm_init`. All subsequent VKM calls will take in this pointer as the first
|
||||
argument, and return a signed 32-bit status code. Once done, `vkm_exit` will
|
||||
clean up any data structures and close any file descriptors that were opened as
|
||||
part of the VKM calls.
|
||||
|
||||
VKM can also be configured through the `vkm_config` API call.
|
||||
|
||||
Device handling
|
||||
===============
|
||||
|
||||
`vkm_new_device` is the API to use when creating a new VKM device. While VKM
|
||||
will support both keyboard and mouse events from a single device, there may be
|
||||
cases where the application needs to separate out keyboard and mouse events into
|
||||
different devices. The flags will enable keyboard and/or mouse support.
|
||||
|
||||
Note that the supported event codes (on Linux) are fixed, and cannot be updated.
|
||||
The keyboard will emulate a standard US keyboard, while the mouse will emulate a
|
||||
standard 3 button mouse with a scroll wheel.
|
||||
|
||||
Mouse handling
|
||||
==============
|
||||
|
||||
The mouse is handled as a single API call that passes in dx and dy to move the
|
||||
mouse in the relative X and Y axes. VKM will take care of internally translating
|
||||
the calls to the appropriate framework. This is handled in `vkm_mouse_move`
|
||||
|
||||
The scroll wheel is handled through `vkm_mouse_scroll`. By default, the mouse
|
||||
motion uses standard scrolling, but high resolution scrolling may be enabled.
|
||||
|
||||
The buttons are handled by `vkm_mouse_click`. The API will send the state,
|
||||
depending on the input (pressed or not)
|
||||
|
||||
Keyboard handling
|
||||
=================
|
||||
|
||||
The keyboard is handled through a single call `vkm_keyboard_send`. This sends a
|
||||
single key event, with modifiers enabled (Ctrl, Shift, Alt, GUI).
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
vkm_version = '0.1.0'
|
||||
|
||||
vkm_files = files(
|
||||
'vkm_common.c'
|
||||
)
|
||||
|
||||
vkm_stub_files = files(
|
||||
'vkm_stub.c'
|
||||
)
|
||||
|
||||
if host_machine.system() == 'linux'
|
||||
vkm_dep = dep_evdev
|
||||
if dep_evdev.found()
|
||||
vkm_platform_files = files(
|
||||
'vkm_linux_evdev.c'
|
||||
)
|
||||
else
|
||||
vkm_platform_files = vkm_stub_files
|
||||
endif
|
||||
else
|
||||
vkm_dep = dependency('', required: false)
|
||||
vkm_platform_files = vkm_stub_files
|
||||
endif
|
||||
|
||||
lib_vkm = library('vkm', vkm_files + vkm_platform_files,
|
||||
install: true,
|
||||
version: vkm_version,
|
||||
dependencies: [vkm_dep, dep_intl],
|
||||
include_directories: [includes])
|
||||
|
||||
vkm_strerror_test = executable('vkm-strerror-test',
|
||||
'test_strerror.c',
|
||||
'vkm_common.c',
|
||||
build_by_default: false,
|
||||
dependencies: [dep_cmocka, dep_intl],
|
||||
include_directories: [includes],
|
||||
)
|
||||
|
||||
test('vkm-strerror', vkm_strerror_test, protocol: 'tap')
|
||||
|
||||
if host_machine.system() == 'linux' and dep_evdev.found()
|
||||
dep_evdev_headers = dep_evdev.partial_dependency(compile_args: true, link_args: false)
|
||||
vkm_linux_evdev_test = executable('vkm_linux_evdev_test',
|
||||
files(
|
||||
'vkm_linux_evdev_test.c',
|
||||
'vkm_linux_evdev.c',
|
||||
'vkm_common.c',
|
||||
),
|
||||
include_directories: [includes],
|
||||
dependencies: [dep_cmocka, dep_evdev_headers, dep_intl],
|
||||
)
|
||||
test('vkm_linux_evdev', vkm_linux_evdev_test)
|
||||
endif
|
||||
|
||||
install_headers('vkm.h', subdir: 'vkm')
|
||||
pkgconfig.generate(lib_vkm,
|
||||
name: 'vkm',
|
||||
description: 'Linux/Unix library to control Saitek X52/X52Pro joystick extended functionality.',
|
||||
subdirs: meson.project_name(),
|
||||
version: vkm_version)
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* VKM — strerror unit tests
|
||||
*
|
||||
* Copyright (C) 2026 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <setjmp.h>
|
||||
#include <cmocka.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "vkm.h"
|
||||
|
||||
static void test_strerror(void **state)
|
||||
{
|
||||
(void)state;
|
||||
|
||||
static const char *error_map[VKM_ERROR_MAX] = {
|
||||
[VKM_SUCCESS] = "Success",
|
||||
[VKM_ERROR_UNKNOWN] = "Unknown error",
|
||||
[VKM_ERROR_NOT_READY] = "Not ready",
|
||||
[VKM_ERROR_OUT_OF_MEMORY] = "Out of memory",
|
||||
[VKM_ERROR_INVALID_PARAM] = "Invalid parameter",
|
||||
[VKM_ERROR_NOT_SUPPORTED] = "Not supported",
|
||||
[VKM_ERROR_DEV_FAILURE] = "Virtual device failure",
|
||||
[VKM_ERROR_EVENT] = "Unable to write event",
|
||||
[VKM_ERROR_NO_CHANGE] = "No state change",
|
||||
};
|
||||
|
||||
static const char *unknown_fmt = "Unknown error %d";
|
||||
|
||||
char expected[256];
|
||||
|
||||
for (int i = -1; i <= (int)VKM_ERROR_MAX + 1; i++) {
|
||||
if (i < 0 || i >= (int)VKM_ERROR_MAX || error_map[i] == NULL) {
|
||||
snprintf(expected, sizeof(expected), unknown_fmt, i);
|
||||
} else {
|
||||
strncpy(expected, error_map[i], sizeof(expected));
|
||||
}
|
||||
assert_string_equal(expected, vkm_strerror(i));
|
||||
}
|
||||
}
|
||||
|
||||
static const struct CMUnitTest tests[] = {
|
||||
cmocka_unit_test(test_strerror),
|
||||
};
|
||||
|
||||
int main(void)
|
||||
{
|
||||
cmocka_set_message_output(CM_OUTPUT_TAP);
|
||||
cmocka_run_group_tests(tests, NULL, NULL);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* VKM common functions
|
||||
*
|
||||
* Copyright (C) 2026 Nirenjan Krishnan <nirenjan@nirenjan.org>
|
||||
*
|
||||
* SPDX-LicenseIdentifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "vkm.h"
|
||||
|
||||
#ifndef VKM_INTERNAL_H
|
||||
#define VKM_INTERNAL_H
|
||||
|
||||
struct vkm_mouse_button_state {
|
||||
vkm_button_state pressed[VKM_MOUSE_BTN_MAX];
|
||||
};
|
||||
|
||||
vkm_button_state _vkm_get_mouse_button_state(struct vkm_mouse_button_state *state, vkm_mouse_button button);
|
||||
void _vkm_set_mouse_button_state(struct vkm_mouse_button_state *sstate, vkm_mouse_button button, vkm_button_state state);
|
||||
|
||||
#endif // !defined VKM_INTERNAL_H
|
||||
|
|
@ -0,0 +1,609 @@
|
|||
/*
|
||||
* Virtual keyboard/mouse interface
|
||||
*
|
||||
* Copyright (C) 2026 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file vkm.h
|
||||
* @brief Functions, structures and enumerations for the virtual
|
||||
* keyboard/mouse interface library (VKM).
|
||||
*
|
||||
* This file contains the type, enum and function prototypes for VKM.
|
||||
* These functions allow an application to inject keyboard/mouse events
|
||||
* into the host OS, as long as it has the necessary permissions.
|
||||
*
|
||||
* @author Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||
*/
|
||||
#ifndef VKM_H
|
||||
#define VKM_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Opaque structure used by the VKM framework
|
||||
*/
|
||||
struct vkm_context;
|
||||
|
||||
/**
|
||||
* @brief Virtual device context structure used by the VKM framework
|
||||
*
|
||||
* All VKM API functions require the application to pass in a pointer to
|
||||
* a valid context structure. A pointer can be obtained by calling
|
||||
* \ref vkm_init
|
||||
*/
|
||||
typedef struct vkm_context vkm_context;
|
||||
|
||||
/**
|
||||
* @brief Return type used by VKM API functions
|
||||
*/
|
||||
typedef int32_t vkm_result;
|
||||
|
||||
/**
|
||||
* @brief Feature identifiers for \ref vkm_feature_supported
|
||||
*
|
||||
* Bit flags describing optional VKM capabilities on the current platform.
|
||||
* Pass one enumerator at a time to \ref vkm_feature_supported.
|
||||
*/
|
||||
typedef enum {
|
||||
VKM_FEAT_MOUSE = (1 << 0), /**< Relative mouse move, buttons, and wheel */
|
||||
|
||||
/** Full \ref vkm_keyboard_send key map (platform virtual keyboard) */
|
||||
VKM_FEAT_KEYBOARD = (1 << 1),
|
||||
|
||||
/** Separate left/right logical modifiers in \ref vkm_keyboard_send */
|
||||
VKM_FEAT_KEYBOARD_MODIFIERS = (1 << 2),
|
||||
|
||||
/* Additional flags may be added in the future */
|
||||
} vkm_feature;
|
||||
|
||||
/**
|
||||
* @brief Error code list
|
||||
*/
|
||||
typedef enum {
|
||||
/** No error, indicates success */
|
||||
VKM_SUCCESS = 0,
|
||||
|
||||
/** Unknown error, used as a catch-all error state */
|
||||
VKM_ERROR_UNKNOWN,
|
||||
|
||||
/** Not started, must call vkm_start first */
|
||||
VKM_ERROR_NOT_READY,
|
||||
|
||||
/** Out of memory */
|
||||
VKM_ERROR_OUT_OF_MEMORY,
|
||||
|
||||
/** Invalid parameter(s) */
|
||||
VKM_ERROR_INVALID_PARAM,
|
||||
|
||||
/** Not supported */
|
||||
VKM_ERROR_NOT_SUPPORTED,
|
||||
|
||||
/** Unable to create virtual devices */
|
||||
VKM_ERROR_DEV_FAILURE,
|
||||
|
||||
/** Unable to write event */
|
||||
VKM_ERROR_EVENT,
|
||||
|
||||
/** No state change in the event, please retry */
|
||||
VKM_ERROR_NO_CHANGE,
|
||||
|
||||
/* Maximum error states, do not use in external code*/
|
||||
VKM_ERROR_MAX,
|
||||
} vkm_error_code;
|
||||
|
||||
/**
|
||||
* @brief Return a short description for a \ref vkm_result / \ref vkm_error_code value
|
||||
*
|
||||
* The returned pointer refers to static storage and must not be freed. For
|
||||
* unrecognized codes, the same static buffer may be overwritten by a later call.
|
||||
*
|
||||
* When native language support (NLS) is enabled at build time, these messages
|
||||
* are translated like \ref libx52_strerror. Bind the \c libx52 text domain and
|
||||
* set \c LC_MESSAGES as for other libx52 components.
|
||||
*
|
||||
* @param[in] code Value returned from a VKM API function
|
||||
*
|
||||
* @returns Pointer to a NUL-terminated description string
|
||||
*/
|
||||
const char *vkm_strerror(vkm_result code);
|
||||
|
||||
/**
|
||||
* @brief Option list
|
||||
*/
|
||||
typedef enum {
|
||||
/**
|
||||
* @brief Set the high resolution scrolling behavior of the mouse
|
||||
*
|
||||
* This option must be passed a boolean which lets VKM know whether to
|
||||
* enable or disable high resolution scrolling.
|
||||
*
|
||||
* Defaults to false. When enabled together with \ref vkm_start,
|
||||
* \ref vkm_mouse_scroll emits high-resolution REL_*_HI_RES events (120 units
|
||||
* per step) in addition to discrete REL_WHEEL / REL_HWHEEL ticks.
|
||||
*/
|
||||
VKM_OPT_HI_RES_SCROLL,
|
||||
|
||||
/**
|
||||
* @brief Enable or disable horizontal scrolling of the mouse
|
||||
*
|
||||
* This option must be passed in a boolean which lets VKM know whether to
|
||||
* enable or disable horizontal scrolling. If horizontal scrolling is
|
||||
* disabled, then any requests to \ref vkm_mouse_scroll with
|
||||
* \ref VKM_MOUSE_SCROLL_LEFT or \ref VKM_MOUSE_SCROLL_RIGHT will return
|
||||
* \ref VKM_ERROR_INVALID_PARAM.
|
||||
*
|
||||
* Defaults to false.
|
||||
*/
|
||||
VKM_OPT_HORIZONTAL_SCROLL,
|
||||
|
||||
/**
|
||||
* @brief Set the virtual device name in the system.
|
||||
*
|
||||
* This option sets the name of the virtual input device in the system.
|
||||
* If not set, the virtual device will have a name determined by the
|
||||
* timestamp at which it was initialized.
|
||||
*
|
||||
* Only applicable on Linux.
|
||||
*/
|
||||
VKM_OPT_DEVICE_NAME,
|
||||
|
||||
/* Max number of options, do not use in external code */
|
||||
VKM_OPT_MAX
|
||||
} vkm_option;
|
||||
|
||||
/**
|
||||
* @brief Button state
|
||||
*/
|
||||
typedef enum {
|
||||
/** Button is released */
|
||||
VKM_BUTTON_RELEASED,
|
||||
|
||||
/** Button is pressed */
|
||||
VKM_BUTTON_PRESSED,
|
||||
|
||||
/* Max number of button states, do not use in external code */
|
||||
VKM_BUTTON_STATE_MAX
|
||||
} vkm_button_state;
|
||||
|
||||
/**
|
||||
* @brief Mouse button identifiers
|
||||
*/
|
||||
typedef enum {
|
||||
/** Mouse left button */
|
||||
VKM_MOUSE_BTN_LEFT,
|
||||
|
||||
/** Mouse right button */
|
||||
VKM_MOUSE_BTN_RIGHT,
|
||||
|
||||
/** Mouse middle button */
|
||||
VKM_MOUSE_BTN_MIDDLE,
|
||||
|
||||
/* Max number of mouse buttons, do not use in external code */
|
||||
VKM_MOUSE_BTN_MAX
|
||||
} vkm_mouse_button;
|
||||
|
||||
/**
|
||||
* @brief Scroll directions
|
||||
*/
|
||||
typedef enum {
|
||||
/** Scroll up */
|
||||
VKM_MOUSE_SCROLL_UP,
|
||||
|
||||
/** Scroll down */
|
||||
VKM_MOUSE_SCROLL_DOWN,
|
||||
|
||||
/** Scroll left (horizontal scrolling) */
|
||||
VKM_MOUSE_SCROLL_LEFT,
|
||||
|
||||
/** Scroll right (horizontal scrolling) */
|
||||
VKM_MOUSE_SCROLL_RIGHT,
|
||||
|
||||
/* Maximum number of scroll states, do not use in external code */
|
||||
VKM_MOUSE_SCROLL_MAX
|
||||
} vkm_mouse_scroll_direction;
|
||||
|
||||
/**
|
||||
* @brief Physical key identifiers (105-key ISO / “international” PC set)
|
||||
*
|
||||
* Logical key codes for \ref vkm_keyboard_send. Identifiers follow the USB HID
|
||||
* Usage Tables (keyboard/keypad page) in meaning; enumerator values are
|
||||
* arbitrary and are not HID usage IDs.
|
||||
*
|
||||
* Shift keys are listed with the alphanumeric block; other modifiers are grouped
|
||||
* at the start of the enumerator list.
|
||||
*/
|
||||
typedef enum {
|
||||
VKM_KEY_NONE = 0, /**< Sentinel: no key (e.g. modifier-only update) */
|
||||
|
||||
VKM_KEY_LEFT_CTRL, /**< Left Control */
|
||||
VKM_KEY_LEFT_ALT, /**< Left Alt */
|
||||
VKM_KEY_LEFT_GUI, /**< Left GUI / Meta / Windows key */
|
||||
VKM_KEY_RIGHT_CTRL, /**< Right Control */
|
||||
VKM_KEY_RIGHT_ALT, /**< Right Alt / AltGr */
|
||||
VKM_KEY_RIGHT_GUI, /**< Right GUI / Meta */
|
||||
VKM_KEY_APPLICATION, /**< Application / Menu key */
|
||||
|
||||
VKM_KEY_ESCAPE, /**< Escape */
|
||||
VKM_KEY_F1, /**< F1 */
|
||||
VKM_KEY_F2, /**< F2 */
|
||||
VKM_KEY_F3, /**< F3 */
|
||||
VKM_KEY_F4, /**< F4 */
|
||||
VKM_KEY_F5, /**< F5 */
|
||||
VKM_KEY_F6, /**< F6 */
|
||||
VKM_KEY_F7, /**< F7 */
|
||||
VKM_KEY_F8, /**< F8 */
|
||||
VKM_KEY_F9, /**< F9 */
|
||||
VKM_KEY_F10, /**< F10 */
|
||||
VKM_KEY_F11, /**< F11 */
|
||||
VKM_KEY_F12, /**< F12 */
|
||||
|
||||
VKM_KEY_GRAVE_ACCENT, /**< Grave accent / tilde (` and ~ on US) */
|
||||
VKM_KEY_1, /**< \c 1 / ! */
|
||||
VKM_KEY_2, /**< \c 2 / @ */
|
||||
VKM_KEY_3, /**< \c 3 / # */
|
||||
VKM_KEY_4, /**< \c 4 / $ */
|
||||
VKM_KEY_5, /**< \c 5 / % */
|
||||
VKM_KEY_6, /**< \c 6 / ^ */
|
||||
VKM_KEY_7, /**< \c 7 / & */
|
||||
VKM_KEY_8, /**< \c 8 / * */
|
||||
VKM_KEY_9, /**< \c 9 / ( */
|
||||
VKM_KEY_0, /**< \c 0 / ) */
|
||||
VKM_KEY_MINUS, /**< Minus / underscore */
|
||||
VKM_KEY_EQUAL, /**< Equals / plus */
|
||||
VKM_KEY_BACKSPACE, /**< Backspace */
|
||||
|
||||
VKM_KEY_TAB, /**< Tab */
|
||||
VKM_KEY_Q, /**< \c Q */
|
||||
VKM_KEY_W, /**< \c W */
|
||||
VKM_KEY_E, /**< \c E */
|
||||
VKM_KEY_R, /**< \c R */
|
||||
VKM_KEY_T, /**< \c T */
|
||||
VKM_KEY_Y, /**< \c Y */
|
||||
VKM_KEY_U, /**< \c U */
|
||||
VKM_KEY_I, /**< \c I */
|
||||
VKM_KEY_O, /**< \c O */
|
||||
VKM_KEY_P, /**< \c P */
|
||||
VKM_KEY_LEFT_BRACKET, /**< Left bracket / brace */
|
||||
VKM_KEY_RIGHT_BRACKET, /**< Right bracket / brace */
|
||||
VKM_KEY_BACKSLASH, /**< Backslash / pipe (US placement; JIS Yen) */
|
||||
|
||||
VKM_KEY_CAPS_LOCK, /**< Caps Lock */
|
||||
VKM_KEY_A, /**< \c A */
|
||||
VKM_KEY_S, /**< \c S */
|
||||
VKM_KEY_D, /**< \c D */
|
||||
VKM_KEY_F, /**< \c F */
|
||||
VKM_KEY_G, /**< \c G */
|
||||
VKM_KEY_H, /**< \c H */
|
||||
VKM_KEY_J, /**< \c J */
|
||||
VKM_KEY_K, /**< \c K */
|
||||
VKM_KEY_L, /**< \c L */
|
||||
VKM_KEY_SEMICOLON, /**< Semicolon / colon */
|
||||
VKM_KEY_APOSTROPHE, /**< Apostrophe / quote */
|
||||
VKM_KEY_NONUS_HASH, /**< ISO non-US # / ~ (HID usage) */
|
||||
VKM_KEY_ENTER, /**< Return / Enter */
|
||||
|
||||
VKM_KEY_LEFT_SHIFT, /**< Left Shift */
|
||||
VKM_KEY_INTL_BACKSLASH, /**< ISO extra key (e.g. \| between left Shift and Z) */
|
||||
VKM_KEY_Z, /**< \c Z */
|
||||
VKM_KEY_X, /**< \c X */
|
||||
VKM_KEY_C, /**< \c C */
|
||||
VKM_KEY_V, /**< \c V */
|
||||
VKM_KEY_B, /**< \c B */
|
||||
VKM_KEY_N, /**< \c N */
|
||||
VKM_KEY_M, /**< \c M */
|
||||
VKM_KEY_COMMA, /**< Comma / less-than */
|
||||
VKM_KEY_PERIOD, /**< Period / greater-than */
|
||||
VKM_KEY_SLASH, /**< Slash / question */
|
||||
VKM_KEY_RIGHT_SHIFT, /**< Right Shift */
|
||||
|
||||
VKM_KEY_SPACE, /**< Space bar */
|
||||
|
||||
VKM_KEY_PRINT_SCREEN, /**< Print Screen */
|
||||
VKM_KEY_SCROLL_LOCK, /**< Scroll Lock */
|
||||
VKM_KEY_PAUSE, /**< Pause / Break */
|
||||
|
||||
VKM_KEY_INSERT, /**< Insert */
|
||||
VKM_KEY_HOME, /**< Home */
|
||||
VKM_KEY_PAGE_UP, /**< Page Up */
|
||||
VKM_KEY_DELETE_FORWARD, /**< Delete (forward) */
|
||||
VKM_KEY_END, /**< End */
|
||||
VKM_KEY_PAGE_DOWN, /**< Page Down */
|
||||
|
||||
VKM_KEY_RIGHT_ARROW, /**< Arrow right */
|
||||
VKM_KEY_LEFT_ARROW, /**< Arrow left */
|
||||
VKM_KEY_DOWN_ARROW, /**< Arrow down */
|
||||
VKM_KEY_UP_ARROW, /**< Arrow up */
|
||||
|
||||
VKM_KEY_KEYPAD_NUM_LOCK, /**< Keypad Num Lock */
|
||||
VKM_KEY_KEYPAD_DIVIDE, /**< Keypad \c / */
|
||||
VKM_KEY_KEYPAD_MULTIPLY,/**< Keypad \c * */
|
||||
VKM_KEY_KEYPAD_MINUS, /**< Keypad \c - */
|
||||
VKM_KEY_KEYPAD_PLUS, /**< Keypad \c + */
|
||||
VKM_KEY_KEYPAD_ENTER, /**< Keypad Enter */
|
||||
VKM_KEY_KEYPAD_1, /**< Keypad \c 1 / End */
|
||||
VKM_KEY_KEYPAD_2, /**< Keypad \c 2 / Down */
|
||||
VKM_KEY_KEYPAD_3, /**< Keypad \c 3 / Page Down */
|
||||
VKM_KEY_KEYPAD_4, /**< Keypad \c 4 / Left */
|
||||
VKM_KEY_KEYPAD_5, /**< Keypad \c 5 */
|
||||
VKM_KEY_KEYPAD_6, /**< Keypad \c 6 / Right */
|
||||
VKM_KEY_KEYPAD_7, /**< Keypad \c 7 / Home */
|
||||
VKM_KEY_KEYPAD_8, /**< Keypad \c 8 / Up */
|
||||
VKM_KEY_KEYPAD_9, /**< Keypad \c 9 / Page Up */
|
||||
VKM_KEY_KEYPAD_0, /**< Keypad \c 0 / Insert */
|
||||
VKM_KEY_KEYPAD_DECIMAL, /**< Keypad decimal / Delete */
|
||||
VKM_KEY_KEYPAD_COMMA, /**< Keypad comma (locale-specific layouts) */
|
||||
|
||||
VKM_KEY_MAX /**< Past last key; do not use in application code */
|
||||
} vkm_key;
|
||||
|
||||
/**
|
||||
* @brief Modifier bitmask for \ref vkm_keyboard_send
|
||||
*
|
||||
* Left and right modifier keys use the same bit layout as the USB HID keyboard
|
||||
* modifier byte. Combine values with bitwise OR. These are separate from
|
||||
* physical modifier key events in \ref vkm_key.
|
||||
*
|
||||
* Convenience macros \c VKM_KEY_MOD_CTRL, \c VKM_KEY_MOD_SHIFT, \c VKM_KEY_MOD_ALT,
|
||||
* and \c VKM_KEY_MOD_GUI alias the left-hand modifiers only; use
|
||||
* \c VKM_KEY_MOD_L* / \c VKM_KEY_MOD_R* when a specific side is required.
|
||||
*/
|
||||
typedef enum {
|
||||
VKM_KEY_MOD_NONE = 0, /**< No modifiers */
|
||||
|
||||
VKM_KEY_MOD_LCTRL = (1 << 0), /**< Left Control (HID modifier byte bit 0) */
|
||||
VKM_KEY_MOD_LSHIFT = (1 << 1), /**< Left Shift (bit 1) */
|
||||
VKM_KEY_MOD_LALT = (1 << 2), /**< Left Alt (bit 2) */
|
||||
VKM_KEY_MOD_LGUI = (1 << 3), /**< Left GUI / Meta (bit 3) */
|
||||
VKM_KEY_MOD_RCTRL = (1 << 4), /**< Right Control (bit 4) */
|
||||
VKM_KEY_MOD_RSHIFT = (1 << 5), /**< Right Shift (bit 5) */
|
||||
VKM_KEY_MOD_RALT = (1 << 6), /**< Right Alt / AltGr (bit 6) */
|
||||
VKM_KEY_MOD_RGUI = (1 << 7), /**< Right GUI / Meta (bit 7) */
|
||||
} vkm_key_modifiers;
|
||||
|
||||
/** Convenience alias for \ref VKM_KEY_MOD_LCTRL */
|
||||
#define VKM_KEY_MOD_CTRL VKM_KEY_MOD_LCTRL
|
||||
/** Convenience alias for \ref VKM_KEY_MOD_LSHIFT */
|
||||
#define VKM_KEY_MOD_SHIFT VKM_KEY_MOD_LSHIFT
|
||||
/** Convenience alias for \ref VKM_KEY_MOD_LALT */
|
||||
#define VKM_KEY_MOD_ALT VKM_KEY_MOD_LALT
|
||||
/** Convenience alias for \ref VKM_KEY_MOD_LGUI */
|
||||
#define VKM_KEY_MOD_GUI VKM_KEY_MOD_LGUI
|
||||
|
||||
/**
|
||||
* @brief Physical key action (press or release) for \ref vkm_keyboard_send
|
||||
*
|
||||
* Determines the \c value sent with the non-modifier \ref vkm_key (and, on
|
||||
* release, how the modifier mask is reconciled after the key event).
|
||||
*/
|
||||
typedef enum {
|
||||
VKM_KEY_STATE_RELEASED, /**< Key or button is up */
|
||||
VKM_KEY_STATE_PRESSED, /**< Key or button is down */
|
||||
VKM_KEY_STATE_MAX /**< Sentinel; do not use in application code */
|
||||
} vkm_key_state;
|
||||
|
||||
/**
|
||||
* @brief Initialize the VKM library
|
||||
*
|
||||
* This function initializes the VKM library, sets up any internal data
|
||||
* structures to send input events, and returns a \ref vkm_context pointer
|
||||
* in the output parameter. All calls to VKM use the returned pointer to
|
||||
* inject keyboard/mouse events.
|
||||
*
|
||||
* @par Example
|
||||
* @code
|
||||
* vkm_result rc;
|
||||
* vkm_context *ctx;
|
||||
* rc = vkm_init(&ctx);
|
||||
* if (rc != LIBX52_SUCCESS) {
|
||||
* // Error handling omitted for brevity
|
||||
* }
|
||||
* // Save ctx for use later
|
||||
* @endcode
|
||||
*
|
||||
* @param[out] ctx Pointer to a \ref vkm_context *. This function will
|
||||
* allocate a context and return the pointer to the context in this variable.
|
||||
*
|
||||
* @returns \ref vkm_error_code indicating status
|
||||
*/
|
||||
vkm_result vkm_init(vkm_context **ctx);
|
||||
|
||||
/**
|
||||
* @brief Exit the VKM library and free up any resources used
|
||||
*
|
||||
* This function calls \ref vkm_reset, releases any resources allocated by
|
||||
* \ref vkm_init, and terminates the library. Using the freed context after
|
||||
* this returns is invalid and can cause errors.
|
||||
*
|
||||
* @param[in] ctx Context pointer
|
||||
*/
|
||||
void vkm_exit(vkm_context *ctx);
|
||||
|
||||
/**
|
||||
* @brief Release all virtual keys and mouse buttons that are still down
|
||||
*
|
||||
* Synthesizes release events for any keys pressed through \ref vkm_keyboard_send,
|
||||
* clears the logical modifier mask from that API, and releases mouse buttons that
|
||||
* are still pressed. Internal bookkeeping is cleared even if the virtual device
|
||||
* is not ready (no events are written until \ref vkm_start succeeds).
|
||||
*
|
||||
* \ref vkm_exit calls this automatically before freeing the context.
|
||||
*
|
||||
* @param[in] ctx Context pointer
|
||||
*
|
||||
* @returns
|
||||
* - \ref VKM_SUCCESS on success or if there was nothing to release
|
||||
* - \ref VKM_ERROR_INVALID_PARAM on bad pointer
|
||||
* - \ref VKM_ERROR_EVENT if writing a release event failed
|
||||
*/
|
||||
vkm_result vkm_reset(vkm_context *ctx);
|
||||
|
||||
/**
|
||||
* @brief Start any virtual keyboard/mouse devices on the platform
|
||||
*
|
||||
* This must be done before injecting any events, and after setting all
|
||||
* options through \ref vkm_set_option.
|
||||
*
|
||||
* @param[in] ctx Context pointer
|
||||
*
|
||||
* @returns
|
||||
* - \ref VKM_SUCCESS on successful start
|
||||
* - \ref VKM_ERROR_INVALID_PARAM on bad pointer
|
||||
* - \ref VKM_ERROR_UNKNOWN on other errors
|
||||
*/
|
||||
vkm_result vkm_start(vkm_context *ctx);
|
||||
|
||||
/**
|
||||
* @brief check if VKM is started and ready
|
||||
*
|
||||
* @param[in] ctx Context pointer
|
||||
*
|
||||
* @returns boolean indicating if ready or not.
|
||||
*/
|
||||
bool vkm_is_ready(vkm_context *ctx);
|
||||
|
||||
/**
|
||||
* @brief Check if VKM is supported on this platform
|
||||
*
|
||||
* On some platforms, there is no support yet for the virtual keyboard/mouse.
|
||||
* This function will return a boolean indicating if it is supported or not.
|
||||
*
|
||||
* @returns boolean indicating support.
|
||||
*/
|
||||
bool vkm_platform_supported(void);
|
||||
|
||||
/**
|
||||
* @brief Check if a particular feature is enabled on this platform
|
||||
*
|
||||
* Features may be limited on a per-platform basis.
|
||||
*
|
||||
* @param[in] feat Feature identifier
|
||||
*
|
||||
* @returns boolean indicating if feature is supported or not.
|
||||
*/
|
||||
bool vkm_feature_supported(vkm_feature feat);
|
||||
|
||||
/**
|
||||
* @brief Set an option flag for VKM.
|
||||
*
|
||||
* Option flags control the behavior of VKM. All options must be set before
|
||||
* calling \ref vkm_start.
|
||||
*
|
||||
* @param[in] ctx Context pointer
|
||||
* @param[in] option Which option to set
|
||||
* @param[in] ... Any required arguments for the specified option
|
||||
*
|
||||
* @returns
|
||||
* - \ref VKM_SUCCESS on success
|
||||
* - \ref VKM_ERROR_INVALID_PARAM if the option or arguments are invalid
|
||||
* - \ref VKM_ERROR_NOT_SUPPORTED if the option is valid but not supported on this platform
|
||||
*/
|
||||
vkm_result vkm_set_option(vkm_context *ctx, vkm_option option, ...);
|
||||
|
||||
/**
|
||||
* @brief Move the mouse by the specified amount
|
||||
*
|
||||
* The move mouse takes in a delta of x and y coordinates that tell the system
|
||||
* to move the mouse by those relative numbers.
|
||||
*
|
||||
* @param[in] ctx Context pointer
|
||||
* @param[in] dx Delta by which to move the mouse in the horizontal axis
|
||||
* @param[in] dy Delta by which to move the mouse in the vertical axis
|
||||
*
|
||||
* @returns
|
||||
* - \ref VKM_SUCCESS on successful move
|
||||
* - \ref VKM_ERROR_UNKNOWN on a generic error
|
||||
* - \ref VKM_ERROR_NOT_SUPPORTED if the mouse move is not supported on this platform
|
||||
* - \ref VKM_ERROR_NOT_READY if VKM is not started
|
||||
*/
|
||||
vkm_result vkm_mouse_move(vkm_context *ctx, int dx, int dy);
|
||||
|
||||
/**
|
||||
* @brief Click the mouse button
|
||||
*
|
||||
* Send a mouse button event, this may be either a button down or button up event.
|
||||
*
|
||||
* @param[in] ctx Context pointer
|
||||
* @param[in] button Button identifier
|
||||
* @param[in] state Button state (press or release)
|
||||
*
|
||||
* @returns
|
||||
* - \ref VKM_SUCCESS on successful move
|
||||
* - \ref VKM_ERROR_UNKNOWN on a generic error
|
||||
* - \ref VKM_ERROR_NOT_SUPPORTED if the mouse button click is not supported on this platform
|
||||
* - \ref VKM_ERROR_NOT_READY if VKM is not started
|
||||
*/
|
||||
vkm_result vkm_mouse_click(vkm_context *ctx, vkm_mouse_button button, vkm_button_state state);
|
||||
|
||||
/**
|
||||
* @brief Scroll the mouse wheel
|
||||
*
|
||||
* Send a single scroll event to the mouse wheel (one detent in the chosen
|
||||
* direction). If \ref VKM_OPT_HI_RES_SCROLL was enabled before \ref vkm_start,
|
||||
* also emits REL_WHEEL_HI_RES / REL_HWHEEL_HI_RES using the standard 120
|
||||
* units per detent scale before the corresponding discrete REL_WHEEL /
|
||||
* REL_HWHEEL event.
|
||||
*
|
||||
* @param[in] ctx Context pointer
|
||||
* @param[in] dir Scroll direction
|
||||
*
|
||||
* @returns
|
||||
* - \ref VKM_SUCCESS on successful move
|
||||
* - \ref VKM_ERROR_UNKNOWN on a generic error
|
||||
* - \ref VKM_ERROR_INVALID_PARAM if horizontal scrolling is not enabled
|
||||
* and \p dir is \ref VKM_MOUSE_SCROLL_LEFT or \ref VKM_MOUSE_SCROLL_RIGHT
|
||||
* - \ref VKM_ERROR_NOT_SUPPORTED if the mouse scrolling is not supported on this platform
|
||||
* - \ref VKM_ERROR_NOT_READY if VKM is not started
|
||||
*/
|
||||
vkm_result vkm_mouse_scroll(vkm_context *ctx, vkm_mouse_scroll_direction dir);
|
||||
|
||||
/**
|
||||
* @brief Send a single keyboard event
|
||||
*
|
||||
* Send a single keyboard event to the OS. This will send a single key event,
|
||||
* with modifiers enabled (Ctrl, Shift, Alt, GUI).
|
||||
*
|
||||
* @param[in] ctx Context pointer
|
||||
* @param[in] key Key identifier
|
||||
* @param[in] modifiers Modifier keys to enable (Ctrl, Shift, Alt, GUI)
|
||||
* @param[in] state Key state (press or release)
|
||||
*
|
||||
* @returns
|
||||
* - \ref VKM_SUCCESS on successful send
|
||||
* - \ref VKM_ERROR_UNKNOWN on a generic error
|
||||
* - \ref VKM_ERROR_INVALID_PARAM if parameters are invalid
|
||||
* - \ref VKM_ERROR_NOT_SUPPORTED if the keyboard event is not enabled or
|
||||
* supported on this platform
|
||||
* - \ref VKM_ERROR_NOT_READY if VKM is not started
|
||||
*/
|
||||
vkm_result vkm_keyboard_send(vkm_context *ctx, vkm_key key, vkm_key_modifiers modifiers, vkm_key_state state);
|
||||
|
||||
/**
|
||||
* @brief Send a sync packet to the OS
|
||||
*
|
||||
* On some platforms, a sync packet is necessary for the previously injected
|
||||
* events to actually get reflected in the system. For platforms where this
|
||||
* is not needed, this is a noop.
|
||||
*
|
||||
* @param[in] ctx Context pointer
|
||||
*
|
||||
* @returns
|
||||
* - \ref VKM_SUCCESS on successful move
|
||||
* - \ref VKM_ERROR_UNKNOWN on a generic error
|
||||
* - \ref VKM_ERROR_INVALID_PARAM if parameters are invalid
|
||||
* - \ref VKM_ERROR_NOT_READY if VKM is not started
|
||||
*/
|
||||
vkm_result vkm_sync(vkm_context *ctx);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // !defined VKM_H
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* VKM common functions
|
||||
*
|
||||
* Copyright (C) 2026 Nirenjan Krishnan <nirenjan@nirenjan.org>
|
||||
*
|
||||
* SPDX-LicenseIdentifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "gettext.h"
|
||||
|
||||
#include "vkm-internal.h"
|
||||
|
||||
#define N_(str) gettext_noop(str)
|
||||
#define _(str) dgettext(PACKAGE, str)
|
||||
|
||||
/* Error buffer used for building custom error strings */
|
||||
static char error_buffer[256];
|
||||
|
||||
/* List of error strings (indices must match \ref vkm_error_code) */
|
||||
static const char *error_string[] = {
|
||||
N_("Success"),
|
||||
N_("Unknown error"),
|
||||
N_("Not ready"),
|
||||
N_("Out of memory"),
|
||||
N_("Invalid parameter"),
|
||||
N_("Not supported"),
|
||||
N_("Virtual device failure"),
|
||||
N_("Unable to write event"),
|
||||
N_("No state change"),
|
||||
};
|
||||
|
||||
const char *vkm_strerror(vkm_result code)
|
||||
{
|
||||
switch ((vkm_error_code)code) {
|
||||
case VKM_SUCCESS:
|
||||
case VKM_ERROR_UNKNOWN:
|
||||
case VKM_ERROR_NOT_READY:
|
||||
case VKM_ERROR_OUT_OF_MEMORY:
|
||||
case VKM_ERROR_INVALID_PARAM:
|
||||
case VKM_ERROR_NOT_SUPPORTED:
|
||||
case VKM_ERROR_DEV_FAILURE:
|
||||
case VKM_ERROR_EVENT:
|
||||
case VKM_ERROR_NO_CHANGE:
|
||||
return _(error_string[code]);
|
||||
|
||||
default:
|
||||
snprintf(error_buffer, sizeof(error_buffer),
|
||||
_("Unknown error %d"), (int)code);
|
||||
return error_buffer;
|
||||
}
|
||||
}
|
||||
|
||||
vkm_button_state _vkm_get_mouse_button_state(struct vkm_mouse_button_state *state, vkm_mouse_button button)
|
||||
{
|
||||
if (state == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return state->pressed[button];
|
||||
}
|
||||
|
||||
void _vkm_set_mouse_button_state(struct vkm_mouse_button_state *sstate, vkm_mouse_button button, vkm_button_state state)
|
||||
{
|
||||
if (sstate != NULL) {
|
||||
sstate->pressed[button] = state;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,708 @@
|
|||
/*
|
||||
* VKM Linux evdev implementation
|
||||
*
|
||||
* Copyright (C) 2026 Nirenjan Krishnan <nirenjan@nirenjan.org>
|
||||
*
|
||||
* SPDX-LicenseIdentifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "libevdev/libevdev.h"
|
||||
#include "libevdev/libevdev-uinput.h"
|
||||
|
||||
#include "vkm-internal.h"
|
||||
|
||||
/** evdev codes; index by \ref vkm_key. \c -1 for \ref VKM_KEY_NONE only. */
|
||||
static const int vkm_key_to_evdev[VKM_KEY_MAX] = {
|
||||
[VKM_KEY_NONE] = -1,
|
||||
|
||||
[VKM_KEY_LEFT_CTRL] = KEY_LEFTCTRL,
|
||||
[VKM_KEY_LEFT_ALT] = KEY_LEFTALT,
|
||||
[VKM_KEY_LEFT_GUI] = KEY_LEFTMETA,
|
||||
[VKM_KEY_RIGHT_CTRL] = KEY_RIGHTCTRL,
|
||||
[VKM_KEY_RIGHT_ALT] = KEY_RIGHTALT,
|
||||
[VKM_KEY_RIGHT_GUI] = KEY_RIGHTMETA,
|
||||
[VKM_KEY_APPLICATION] = KEY_MENU,
|
||||
|
||||
[VKM_KEY_ESCAPE] = KEY_ESC,
|
||||
[VKM_KEY_F1] = KEY_F1,
|
||||
[VKM_KEY_F2] = KEY_F2,
|
||||
[VKM_KEY_F3] = KEY_F3,
|
||||
[VKM_KEY_F4] = KEY_F4,
|
||||
[VKM_KEY_F5] = KEY_F5,
|
||||
[VKM_KEY_F6] = KEY_F6,
|
||||
[VKM_KEY_F7] = KEY_F7,
|
||||
[VKM_KEY_F8] = KEY_F8,
|
||||
[VKM_KEY_F9] = KEY_F9,
|
||||
[VKM_KEY_F10] = KEY_F10,
|
||||
[VKM_KEY_F11] = KEY_F11,
|
||||
[VKM_KEY_F12] = KEY_F12,
|
||||
|
||||
[VKM_KEY_GRAVE_ACCENT] = KEY_GRAVE,
|
||||
[VKM_KEY_1] = KEY_1,
|
||||
[VKM_KEY_2] = KEY_2,
|
||||
[VKM_KEY_3] = KEY_3,
|
||||
[VKM_KEY_4] = KEY_4,
|
||||
[VKM_KEY_5] = KEY_5,
|
||||
[VKM_KEY_6] = KEY_6,
|
||||
[VKM_KEY_7] = KEY_7,
|
||||
[VKM_KEY_8] = KEY_8,
|
||||
[VKM_KEY_9] = KEY_9,
|
||||
[VKM_KEY_0] = KEY_0,
|
||||
[VKM_KEY_MINUS] = KEY_MINUS,
|
||||
[VKM_KEY_EQUAL] = KEY_EQUAL,
|
||||
[VKM_KEY_BACKSPACE] = KEY_BACKSPACE,
|
||||
|
||||
[VKM_KEY_TAB] = KEY_TAB,
|
||||
[VKM_KEY_Q] = KEY_Q,
|
||||
[VKM_KEY_W] = KEY_W,
|
||||
[VKM_KEY_E] = KEY_E,
|
||||
[VKM_KEY_R] = KEY_R,
|
||||
[VKM_KEY_T] = KEY_T,
|
||||
[VKM_KEY_Y] = KEY_Y,
|
||||
[VKM_KEY_U] = KEY_U,
|
||||
[VKM_KEY_I] = KEY_I,
|
||||
[VKM_KEY_O] = KEY_O,
|
||||
[VKM_KEY_P] = KEY_P,
|
||||
[VKM_KEY_LEFT_BRACKET] = KEY_LEFTBRACE,
|
||||
[VKM_KEY_RIGHT_BRACKET] = KEY_RIGHTBRACE,
|
||||
[VKM_KEY_BACKSLASH] = KEY_BACKSLASH,
|
||||
|
||||
[VKM_KEY_CAPS_LOCK] = KEY_CAPSLOCK,
|
||||
[VKM_KEY_A] = KEY_A,
|
||||
[VKM_KEY_S] = KEY_S,
|
||||
[VKM_KEY_D] = KEY_D,
|
||||
[VKM_KEY_F] = KEY_F,
|
||||
[VKM_KEY_G] = KEY_G,
|
||||
[VKM_KEY_H] = KEY_H,
|
||||
[VKM_KEY_J] = KEY_J,
|
||||
[VKM_KEY_K] = KEY_K,
|
||||
[VKM_KEY_L] = KEY_L,
|
||||
[VKM_KEY_SEMICOLON] = KEY_SEMICOLON,
|
||||
[VKM_KEY_APOSTROPHE] = KEY_APOSTROPHE,
|
||||
[VKM_KEY_NONUS_HASH] = KEY_NUMERIC_POUND,
|
||||
[VKM_KEY_ENTER] = KEY_ENTER,
|
||||
|
||||
[VKM_KEY_LEFT_SHIFT] = KEY_LEFTSHIFT,
|
||||
[VKM_KEY_INTL_BACKSLASH] = KEY_102ND,
|
||||
[VKM_KEY_Z] = KEY_Z,
|
||||
[VKM_KEY_X] = KEY_X,
|
||||
[VKM_KEY_C] = KEY_C,
|
||||
[VKM_KEY_V] = KEY_V,
|
||||
[VKM_KEY_B] = KEY_B,
|
||||
[VKM_KEY_N] = KEY_N,
|
||||
[VKM_KEY_M] = KEY_M,
|
||||
[VKM_KEY_COMMA] = KEY_COMMA,
|
||||
[VKM_KEY_PERIOD] = KEY_DOT,
|
||||
[VKM_KEY_SLASH] = KEY_SLASH,
|
||||
[VKM_KEY_RIGHT_SHIFT] = KEY_RIGHTSHIFT,
|
||||
|
||||
[VKM_KEY_SPACE] = KEY_SPACE,
|
||||
|
||||
[VKM_KEY_PRINT_SCREEN] = KEY_PRINT,
|
||||
[VKM_KEY_SCROLL_LOCK] = KEY_SCROLLLOCK,
|
||||
[VKM_KEY_PAUSE] = KEY_PAUSE,
|
||||
|
||||
[VKM_KEY_INSERT] = KEY_INSERT,
|
||||
[VKM_KEY_HOME] = KEY_HOME,
|
||||
[VKM_KEY_PAGE_UP] = KEY_PAGEUP,
|
||||
[VKM_KEY_DELETE_FORWARD] = KEY_DELETE,
|
||||
[VKM_KEY_END] = KEY_END,
|
||||
[VKM_KEY_PAGE_DOWN] = KEY_PAGEDOWN,
|
||||
|
||||
[VKM_KEY_RIGHT_ARROW] = KEY_RIGHT,
|
||||
[VKM_KEY_LEFT_ARROW] = KEY_LEFT,
|
||||
[VKM_KEY_DOWN_ARROW] = KEY_DOWN,
|
||||
[VKM_KEY_UP_ARROW] = KEY_UP,
|
||||
|
||||
[VKM_KEY_KEYPAD_NUM_LOCK] = KEY_NUMLOCK,
|
||||
[VKM_KEY_KEYPAD_DIVIDE] = KEY_KPSLASH,
|
||||
[VKM_KEY_KEYPAD_MULTIPLY] = KEY_KPASTERISK,
|
||||
[VKM_KEY_KEYPAD_MINUS] = KEY_KPMINUS,
|
||||
[VKM_KEY_KEYPAD_PLUS] = KEY_KPPLUS,
|
||||
[VKM_KEY_KEYPAD_ENTER] = KEY_KPENTER,
|
||||
[VKM_KEY_KEYPAD_1] = KEY_KP1,
|
||||
[VKM_KEY_KEYPAD_2] = KEY_KP2,
|
||||
[VKM_KEY_KEYPAD_3] = KEY_KP3,
|
||||
[VKM_KEY_KEYPAD_4] = KEY_KP4,
|
||||
[VKM_KEY_KEYPAD_5] = KEY_KP5,
|
||||
[VKM_KEY_KEYPAD_6] = KEY_KP6,
|
||||
[VKM_KEY_KEYPAD_7] = KEY_KP7,
|
||||
[VKM_KEY_KEYPAD_8] = KEY_KP8,
|
||||
[VKM_KEY_KEYPAD_9] = KEY_KP9,
|
||||
[VKM_KEY_KEYPAD_0] = KEY_KP0,
|
||||
[VKM_KEY_KEYPAD_DECIMAL] = KEY_KPDOT,
|
||||
[VKM_KEY_KEYPAD_COMMA] = KEY_KPCOMMA,
|
||||
};
|
||||
|
||||
struct vkm_context {
|
||||
struct libevdev *dev;
|
||||
struct libevdev_uinput *uidev;
|
||||
|
||||
char *name;
|
||||
bool mouse_hi_res_scroll;
|
||||
bool mouse_horizontal_scroll;
|
||||
|
||||
/** Last modifier byte emitted (\ref vkm_key_modifiers bits 0–7). */
|
||||
uint8_t keyboard_modifier_mask;
|
||||
|
||||
/** Per-key depressed state from \ref vkm_keyboard_send (non-\c NONE keys). */
|
||||
bool keyboard_key_pressed[VKM_KEY_MAX];
|
||||
|
||||
// Existing state of buttons
|
||||
struct vkm_mouse_button_state mouse_buttons;
|
||||
};
|
||||
|
||||
static const int mouse_button_map[VKM_MOUSE_BTN_MAX] = {
|
||||
[VKM_MOUSE_BTN_LEFT] = BTN_LEFT,
|
||||
[VKM_MOUSE_BTN_RIGHT] = BTN_RIGHT,
|
||||
[VKM_MOUSE_BTN_MIDDLE] = BTN_MIDDLE,
|
||||
};
|
||||
|
||||
static const int button_value_map[VKM_BUTTON_STATE_MAX] = {
|
||||
[VKM_BUTTON_PRESSED] = 1,
|
||||
[VKM_BUTTON_RELEASED] = 0,
|
||||
};
|
||||
|
||||
/*
|
||||
* High-resolution scroll uses the same scale as REL_WHEEL_HI_RES in the kernel
|
||||
* (120 units per “click” / detent; aligns with libinput and the Windows convention).
|
||||
*/
|
||||
#define VKM_SCROLL_HI_RES_PER_DETENT 120
|
||||
|
||||
vkm_result vkm_init(vkm_context **ctx)
|
||||
{
|
||||
char name_buf[32];
|
||||
char *name;
|
||||
vkm_result rc;
|
||||
if (ctx == NULL) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
*ctx = calloc(1, sizeof(**ctx));
|
||||
if (*ctx == NULL) {
|
||||
return VKM_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
rc = VKM_SUCCESS;
|
||||
// Create a default name for the virtual device, this will be replaced when
|
||||
// the user calls vkm_set_option with the appropriate name field.
|
||||
snprintf(name_buf, sizeof(name_buf), "VKM virtual device @%08" PRIx64,
|
||||
(uint64_t)(time(NULL)));
|
||||
name = strdup(name_buf);
|
||||
if (name == NULL) {
|
||||
rc = VKM_ERROR_OUT_OF_MEMORY;
|
||||
goto error;
|
||||
}
|
||||
(*ctx)->name = name;
|
||||
|
||||
// Set defaults for the flags
|
||||
return VKM_SUCCESS;
|
||||
|
||||
error:
|
||||
if (name) {
|
||||
free(name);
|
||||
}
|
||||
free(*ctx);
|
||||
*ctx = NULL;
|
||||
return rc;
|
||||
}
|
||||
|
||||
void vkm_exit(vkm_context *ctx)
|
||||
{
|
||||
char *name;
|
||||
struct libevdev_uinput *uidev;
|
||||
struct libevdev *dev;
|
||||
|
||||
if (ctx == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
(void)vkm_reset(ctx);
|
||||
|
||||
dev = ctx->dev;
|
||||
uidev = ctx->uidev;
|
||||
name = ctx->name;
|
||||
|
||||
memset(ctx, 0, sizeof(*ctx));
|
||||
|
||||
free(name);
|
||||
|
||||
if (uidev) {
|
||||
libevdev_uinput_destroy(uidev);
|
||||
}
|
||||
|
||||
if (dev) {
|
||||
libevdev_free(dev);
|
||||
}
|
||||
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
// Enable mouse events
|
||||
static void enable_mouse_events(vkm_context *ctx)
|
||||
{
|
||||
libevdev_enable_event_type(ctx->dev, EV_REL);
|
||||
libevdev_enable_event_code(ctx->dev, EV_REL, REL_X, NULL);
|
||||
libevdev_enable_event_code(ctx->dev, EV_REL, REL_Y, NULL);
|
||||
libevdev_enable_event_code(ctx->dev, EV_REL, REL_WHEEL, NULL);
|
||||
if (ctx->mouse_hi_res_scroll) {
|
||||
libevdev_enable_event_code(ctx->dev, EV_REL, REL_WHEEL_HI_RES, NULL);
|
||||
}
|
||||
|
||||
if (ctx->mouse_horizontal_scroll) {
|
||||
libevdev_enable_event_code(ctx->dev, EV_REL, REL_HWHEEL, NULL);
|
||||
if (ctx->mouse_hi_res_scroll) {
|
||||
libevdev_enable_event_code(ctx->dev, EV_REL, REL_HWHEEL_HI_RES, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
libevdev_enable_event_type(ctx->dev, EV_KEY);
|
||||
libevdev_enable_event_code(ctx->dev, EV_KEY, BTN_LEFT, NULL);
|
||||
libevdev_enable_event_code(ctx->dev, EV_KEY, BTN_RIGHT, NULL);
|
||||
libevdev_enable_event_code(ctx->dev, EV_KEY, BTN_MIDDLE, NULL);
|
||||
}
|
||||
|
||||
static void enable_keyboard_events(vkm_context *ctx)
|
||||
{
|
||||
vkm_key k;
|
||||
|
||||
for (k = (vkm_key)0; k < VKM_KEY_MAX; k++) {
|
||||
int code = vkm_key_to_evdev[k];
|
||||
|
||||
if (code >= 0) {
|
||||
libevdev_enable_event_code(ctx->dev, EV_KEY, (unsigned int)code, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const int vkm_modifier_evdev[8] = {
|
||||
KEY_LEFTCTRL,
|
||||
KEY_LEFTSHIFT,
|
||||
KEY_LEFTALT,
|
||||
KEY_LEFTMETA,
|
||||
KEY_RIGHTCTRL,
|
||||
KEY_RIGHTSHIFT,
|
||||
KEY_RIGHTALT,
|
||||
KEY_RIGHTMETA,
|
||||
};
|
||||
|
||||
static vkm_result apply_modifier_mask(vkm_context *ctx, uint32_t want)
|
||||
{
|
||||
uint32_t have = ctx->keyboard_modifier_mask;
|
||||
uint32_t diff = want ^ have;
|
||||
unsigned bit;
|
||||
|
||||
if (diff == 0) {
|
||||
return VKM_SUCCESS;
|
||||
}
|
||||
|
||||
for (bit = 0; bit < 8; bit++) {
|
||||
uint32_t b = (uint32_t)1 << bit;
|
||||
|
||||
if (diff & b) {
|
||||
int val = (want & b) ? 1 : 0;
|
||||
int rc;
|
||||
|
||||
rc = libevdev_uinput_write_event(ctx->uidev, EV_KEY, vkm_modifier_evdev[bit], val);
|
||||
if (rc != 0) {
|
||||
return VKM_ERROR_EVENT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx->keyboard_modifier_mask = (uint8_t)want;
|
||||
return VKM_SUCCESS;
|
||||
}
|
||||
|
||||
vkm_result vkm_start(vkm_context *ctx)
|
||||
{
|
||||
int rc;
|
||||
if (ctx == NULL) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (ctx->uidev) {
|
||||
// Already initialized, no need to do anything else
|
||||
return VKM_SUCCESS;
|
||||
}
|
||||
|
||||
if (!(ctx->dev)) {
|
||||
// This will happen on the first time this function is called, but
|
||||
// will get cleared up if the uinput device is not created, so this
|
||||
// is just a safety check.
|
||||
ctx->dev = libevdev_new();
|
||||
if (ctx->dev == NULL) {
|
||||
return VKM_ERROR_DEV_FAILURE;
|
||||
}
|
||||
|
||||
libevdev_set_name(ctx->dev, ctx->name);
|
||||
enable_mouse_events(ctx);
|
||||
enable_keyboard_events(ctx);
|
||||
}
|
||||
|
||||
rc = libevdev_uinput_create_from_device(ctx->dev, LIBEVDEV_UINPUT_OPEN_MANAGED,
|
||||
&(ctx->uidev));
|
||||
if (rc != 0) {
|
||||
goto error;
|
||||
}
|
||||
return VKM_SUCCESS;
|
||||
|
||||
error:
|
||||
libevdev_free(ctx->dev);
|
||||
ctx->dev = NULL;
|
||||
return VKM_ERROR_DEV_FAILURE;
|
||||
}
|
||||
|
||||
bool vkm_is_ready(vkm_context *ctx) {
|
||||
if (ctx == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (ctx->dev != NULL && ctx->uidev != NULL);
|
||||
}
|
||||
|
||||
bool vkm_platform_supported(void)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool vkm_feature_supported(vkm_feature feat)
|
||||
{
|
||||
switch (feat) {
|
||||
case VKM_FEAT_MOUSE:
|
||||
return true;
|
||||
|
||||
case VKM_FEAT_KEYBOARD:
|
||||
case VKM_FEAT_KEYBOARD_MODIFIERS:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
vkm_result vkm_set_option(vkm_context *ctx, vkm_option option, ...)
|
||||
{
|
||||
va_list ap;
|
||||
bool flag;
|
||||
char *name;
|
||||
char *name2;
|
||||
vkm_result rc;
|
||||
|
||||
if (ctx == NULL) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
va_start(ap, option);
|
||||
rc = VKM_SUCCESS;
|
||||
switch (option) {
|
||||
case VKM_OPT_HI_RES_SCROLL:
|
||||
// read as int, as bool is promoted to int when passed as a va_arg
|
||||
flag = va_arg(ap, int);
|
||||
ctx->mouse_hi_res_scroll = (bool)flag;
|
||||
break;
|
||||
|
||||
case VKM_OPT_HORIZONTAL_SCROLL:
|
||||
// read as int, as bool is promoted to int when passed as a va_arg
|
||||
flag = va_arg(ap, int);
|
||||
ctx->mouse_horizontal_scroll = (bool)flag;
|
||||
break;
|
||||
|
||||
case VKM_OPT_DEVICE_NAME:
|
||||
name = va_arg(ap, char *);
|
||||
name2 = strdup(name);
|
||||
if (name2 == NULL) {
|
||||
rc = VKM_ERROR_OUT_OF_MEMORY;
|
||||
} else {
|
||||
free(ctx->name);
|
||||
ctx->name = name2;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
rc = VKM_ERROR_INVALID_PARAM;
|
||||
break;
|
||||
}
|
||||
|
||||
va_end(ap);
|
||||
return rc;
|
||||
}
|
||||
|
||||
vkm_result vkm_mouse_move(vkm_context *ctx, int dx, int dy)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (ctx == NULL) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (!vkm_is_ready(ctx)) {
|
||||
return VKM_ERROR_NOT_READY;
|
||||
}
|
||||
|
||||
if (!dx && !dy) {
|
||||
return VKM_ERROR_NO_CHANGE;
|
||||
}
|
||||
|
||||
if (dx) {
|
||||
rc = libevdev_uinput_write_event(ctx->uidev, EV_REL, REL_X, dx);
|
||||
if (rc != 0) {
|
||||
return VKM_ERROR_EVENT;
|
||||
}
|
||||
}
|
||||
|
||||
if (dy) {
|
||||
rc = libevdev_uinput_write_event(ctx->uidev, EV_REL, REL_Y, dy);
|
||||
if (rc != 0) {
|
||||
return VKM_ERROR_EVENT;
|
||||
}
|
||||
}
|
||||
|
||||
return VKM_SUCCESS;
|
||||
}
|
||||
|
||||
vkm_result vkm_mouse_click(vkm_context *ctx, vkm_mouse_button button, vkm_button_state state)
|
||||
{
|
||||
int rc;
|
||||
vkm_button_state existing;
|
||||
|
||||
if (ctx == NULL) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (button >= VKM_MOUSE_BTN_MAX) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (state >= VKM_BUTTON_STATE_MAX) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (!vkm_is_ready(ctx)) {
|
||||
return VKM_ERROR_NOT_READY;
|
||||
}
|
||||
|
||||
existing = _vkm_get_mouse_button_state(&(ctx->mouse_buttons), button);
|
||||
if (existing == state) {
|
||||
return VKM_ERROR_NO_CHANGE;
|
||||
}
|
||||
_vkm_set_mouse_button_state(&(ctx->mouse_buttons), button, state);
|
||||
|
||||
rc = libevdev_uinput_write_event(ctx->uidev, EV_KEY,
|
||||
mouse_button_map[button], button_value_map[state]);
|
||||
if (rc != 0) {
|
||||
return VKM_ERROR_EVENT;
|
||||
}
|
||||
|
||||
return VKM_SUCCESS;
|
||||
}
|
||||
|
||||
vkm_result vkm_mouse_scroll(vkm_context *ctx, vkm_mouse_scroll_direction dir)
|
||||
{
|
||||
int rc;
|
||||
int sign;
|
||||
|
||||
if (ctx == NULL) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (dir >= VKM_MOUSE_SCROLL_MAX) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (!vkm_is_ready(ctx)) {
|
||||
return VKM_ERROR_NOT_READY;
|
||||
}
|
||||
|
||||
if (dir == VKM_MOUSE_SCROLL_LEFT || dir == VKM_MOUSE_SCROLL_RIGHT) {
|
||||
if (!ctx->mouse_horizontal_scroll) {
|
||||
return VKM_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
sign = (dir == VKM_MOUSE_SCROLL_LEFT) ? -1 : 1;
|
||||
if (ctx->mouse_hi_res_scroll) {
|
||||
rc = libevdev_uinput_write_event(ctx->uidev, EV_REL, REL_HWHEEL_HI_RES,
|
||||
sign * VKM_SCROLL_HI_RES_PER_DETENT);
|
||||
if (rc != 0) {
|
||||
return VKM_ERROR_EVENT;
|
||||
}
|
||||
}
|
||||
rc = libevdev_uinput_write_event(ctx->uidev, EV_REL, REL_HWHEEL, sign);
|
||||
if (rc != 0) {
|
||||
return VKM_ERROR_EVENT;
|
||||
}
|
||||
return VKM_SUCCESS;
|
||||
}
|
||||
|
||||
sign = (dir == VKM_MOUSE_SCROLL_UP) ? 1 : -1;
|
||||
if (ctx->mouse_hi_res_scroll) {
|
||||
rc = libevdev_uinput_write_event(ctx->uidev, EV_REL, REL_WHEEL_HI_RES,
|
||||
sign * VKM_SCROLL_HI_RES_PER_DETENT);
|
||||
if (rc != 0) {
|
||||
return VKM_ERROR_EVENT;
|
||||
}
|
||||
}
|
||||
rc = libevdev_uinput_write_event(ctx->uidev, EV_REL, REL_WHEEL, sign);
|
||||
if (rc != 0) {
|
||||
return VKM_ERROR_EVENT;
|
||||
}
|
||||
|
||||
return VKM_SUCCESS;
|
||||
}
|
||||
|
||||
vkm_result vkm_keyboard_send(vkm_context *ctx, vkm_key key, vkm_key_modifiers modifiers,
|
||||
vkm_key_state state)
|
||||
{
|
||||
vkm_result res;
|
||||
int evcode;
|
||||
int val;
|
||||
uint32_t modmask = (uint32_t)modifiers & 0xffu;
|
||||
|
||||
if (ctx == NULL) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (key >= VKM_KEY_MAX) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (state >= VKM_KEY_STATE_MAX) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (!vkm_is_ready(ctx)) {
|
||||
return VKM_ERROR_NOT_READY;
|
||||
}
|
||||
|
||||
val = (state == VKM_KEY_STATE_PRESSED) ? 1 : 0;
|
||||
|
||||
if (state == VKM_KEY_STATE_PRESSED) {
|
||||
res = apply_modifier_mask(ctx, modmask);
|
||||
if (res != VKM_SUCCESS) {
|
||||
return res;
|
||||
}
|
||||
|
||||
if (key == VKM_KEY_NONE) {
|
||||
return VKM_SUCCESS;
|
||||
}
|
||||
|
||||
evcode = vkm_key_to_evdev[key];
|
||||
if (evcode < 0) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (libevdev_uinput_write_event(ctx->uidev, EV_KEY, (unsigned int)evcode, val) != 0) {
|
||||
return VKM_ERROR_EVENT;
|
||||
}
|
||||
|
||||
ctx->keyboard_key_pressed[key] = true;
|
||||
} else {
|
||||
if (key != VKM_KEY_NONE) {
|
||||
evcode = vkm_key_to_evdev[key];
|
||||
if (evcode < 0) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (libevdev_uinput_write_event(ctx->uidev, EV_KEY, (unsigned int)evcode, val) != 0) {
|
||||
return VKM_ERROR_EVENT;
|
||||
}
|
||||
|
||||
ctx->keyboard_key_pressed[key] = false;
|
||||
}
|
||||
|
||||
res = apply_modifier_mask(ctx, modmask);
|
||||
if (res != VKM_SUCCESS) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
return VKM_SUCCESS;
|
||||
}
|
||||
|
||||
static void keyboard_state_clear_tracking(vkm_context *ctx)
|
||||
{
|
||||
vkm_mouse_button b;
|
||||
|
||||
memset(ctx->keyboard_key_pressed, 0, sizeof(ctx->keyboard_key_pressed));
|
||||
ctx->keyboard_modifier_mask = 0;
|
||||
for (b = 0; b < VKM_MOUSE_BTN_MAX; b++) {
|
||||
_vkm_set_mouse_button_state(&ctx->mouse_buttons, b, VKM_BUTTON_RELEASED);
|
||||
}
|
||||
}
|
||||
|
||||
vkm_result vkm_reset(vkm_context *ctx)
|
||||
{
|
||||
vkm_key k;
|
||||
vkm_mouse_button b;
|
||||
int rc;
|
||||
|
||||
if (ctx == NULL) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (!vkm_is_ready(ctx)) {
|
||||
keyboard_state_clear_tracking(ctx);
|
||||
return VKM_SUCCESS;
|
||||
}
|
||||
|
||||
for (k = (vkm_key)0; k < VKM_KEY_MAX; k++) {
|
||||
if (ctx->keyboard_key_pressed[k]) {
|
||||
int code = vkm_key_to_evdev[k];
|
||||
|
||||
if (code >= 0) {
|
||||
rc = libevdev_uinput_write_event(ctx->uidev, EV_KEY, (unsigned int)code, 0);
|
||||
if (rc != 0) {
|
||||
return VKM_ERROR_EVENT;
|
||||
}
|
||||
}
|
||||
|
||||
ctx->keyboard_key_pressed[k] = false;
|
||||
}
|
||||
}
|
||||
|
||||
rc = apply_modifier_mask(ctx, 0);
|
||||
if (rc != VKM_SUCCESS) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
for (b = 0; b < VKM_MOUSE_BTN_MAX; b++) {
|
||||
if (_vkm_get_mouse_button_state(&ctx->mouse_buttons, b) == VKM_BUTTON_PRESSED) {
|
||||
rc = libevdev_uinput_write_event(ctx->uidev, EV_KEY,
|
||||
mouse_button_map[b], button_value_map[VKM_BUTTON_RELEASED]);
|
||||
if (rc != 0) {
|
||||
return VKM_ERROR_EVENT;
|
||||
}
|
||||
|
||||
_vkm_set_mouse_button_state(&ctx->mouse_buttons, b, VKM_BUTTON_RELEASED);
|
||||
}
|
||||
}
|
||||
|
||||
return vkm_sync(ctx);
|
||||
}
|
||||
|
||||
vkm_result vkm_sync(vkm_context *ctx)
|
||||
{
|
||||
int rc;
|
||||
if (ctx == NULL) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (!vkm_is_ready(ctx)) {
|
||||
return VKM_ERROR_NOT_READY;
|
||||
}
|
||||
|
||||
rc = libevdev_uinput_write_event(ctx->uidev, EV_SYN, SYN_REPORT, 0);
|
||||
if (rc != 0) {
|
||||
return VKM_ERROR_EVENT;
|
||||
}
|
||||
|
||||
return VKM_SUCCESS;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,222 @@
|
|||
/*
|
||||
* VKM stub implementation
|
||||
*
|
||||
* Copyright (C) 2026 Nirenjan Krishnan <nirenjan@nirenjan.org>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "vkm.h"
|
||||
|
||||
struct vkm_context {
|
||||
bool started;
|
||||
bool mouse_hi_res_scroll;
|
||||
bool mouse_horizontal_scroll;
|
||||
};
|
||||
|
||||
vkm_result vkm_init(vkm_context **ctx)
|
||||
{
|
||||
if (ctx == NULL) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
*ctx = calloc(1, sizeof(**ctx));
|
||||
if (*ctx == NULL) {
|
||||
return VKM_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
return VKM_SUCCESS;
|
||||
}
|
||||
|
||||
vkm_result vkm_reset(vkm_context *ctx)
|
||||
{
|
||||
if (ctx == NULL) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (vkm_is_ready(ctx)) {
|
||||
return vkm_sync(ctx);
|
||||
}
|
||||
|
||||
return VKM_SUCCESS;
|
||||
}
|
||||
|
||||
void vkm_exit(vkm_context *ctx)
|
||||
{
|
||||
if (ctx == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
(void)vkm_reset(ctx);
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
vkm_result vkm_start(vkm_context *ctx)
|
||||
{
|
||||
if (ctx == NULL) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
ctx->started = true;
|
||||
return VKM_SUCCESS;
|
||||
}
|
||||
|
||||
bool vkm_is_ready(vkm_context *ctx)
|
||||
{
|
||||
if (ctx == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ctx->started;
|
||||
}
|
||||
|
||||
bool vkm_platform_supported(void)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool vkm_feature_supported(vkm_feature feat)
|
||||
{
|
||||
(void)feat;
|
||||
return false;
|
||||
}
|
||||
|
||||
vkm_result vkm_set_option(vkm_context *ctx, vkm_option option, ...)
|
||||
{
|
||||
va_list ap;
|
||||
bool flag;
|
||||
char *name;
|
||||
vkm_result rc;
|
||||
|
||||
if (ctx == NULL) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
va_start(ap, option);
|
||||
rc = VKM_SUCCESS;
|
||||
switch (option) {
|
||||
case VKM_OPT_HI_RES_SCROLL:
|
||||
flag = (bool)va_arg(ap, int);
|
||||
ctx->mouse_hi_res_scroll = flag;
|
||||
break;
|
||||
|
||||
case VKM_OPT_HORIZONTAL_SCROLL:
|
||||
flag = (bool)va_arg(ap, int);
|
||||
ctx->mouse_horizontal_scroll = flag;
|
||||
break;
|
||||
|
||||
case VKM_OPT_DEVICE_NAME:
|
||||
name = va_arg(ap, char *);
|
||||
(void)name;
|
||||
rc = VKM_ERROR_NOT_SUPPORTED;
|
||||
break;
|
||||
|
||||
default:
|
||||
rc = VKM_ERROR_INVALID_PARAM;
|
||||
break;
|
||||
}
|
||||
|
||||
va_end(ap);
|
||||
return rc;
|
||||
}
|
||||
|
||||
vkm_result vkm_mouse_move(vkm_context *ctx, int dx, int dy)
|
||||
{
|
||||
if (ctx == NULL) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (!vkm_is_ready(ctx)) {
|
||||
return VKM_ERROR_NOT_READY;
|
||||
}
|
||||
|
||||
if (dx == 0 && dy == 0) {
|
||||
return VKM_ERROR_NO_CHANGE;
|
||||
}
|
||||
|
||||
return VKM_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
vkm_result vkm_mouse_click(vkm_context *ctx, vkm_mouse_button button, vkm_button_state state)
|
||||
{
|
||||
if (ctx == NULL) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (button >= VKM_MOUSE_BTN_MAX) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (state >= VKM_BUTTON_STATE_MAX) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (!vkm_is_ready(ctx)) {
|
||||
return VKM_ERROR_NOT_READY;
|
||||
}
|
||||
|
||||
return VKM_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
vkm_result vkm_mouse_scroll(vkm_context *ctx, vkm_mouse_scroll_direction dir)
|
||||
{
|
||||
if (ctx == NULL) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (dir >= VKM_MOUSE_SCROLL_MAX) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (!vkm_is_ready(ctx)) {
|
||||
return VKM_ERROR_NOT_READY;
|
||||
}
|
||||
|
||||
if (dir == VKM_MOUSE_SCROLL_LEFT || dir == VKM_MOUSE_SCROLL_RIGHT) {
|
||||
if (!ctx->mouse_horizontal_scroll) {
|
||||
return VKM_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
}
|
||||
|
||||
return VKM_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
vkm_result vkm_sync(vkm_context *ctx)
|
||||
{
|
||||
if (ctx == NULL) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (!vkm_is_ready(ctx)) {
|
||||
return VKM_ERROR_NOT_READY;
|
||||
}
|
||||
|
||||
return VKM_SUCCESS;
|
||||
}
|
||||
|
||||
vkm_result vkm_keyboard_send(vkm_context *ctx, vkm_key key, vkm_key_modifiers modifiers,
|
||||
vkm_key_state state)
|
||||
{
|
||||
(void)modifiers;
|
||||
|
||||
if (ctx == NULL) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (key >= VKM_KEY_MAX) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (state >= VKM_KEY_STATE_MAX) {
|
||||
return VKM_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (!vkm_is_ready(ctx)) {
|
||||
return VKM_ERROR_NOT_READY;
|
||||
}
|
||||
|
||||
return VKM_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
Loading…
Reference in New Issue