From 8418f9f0ad9ed108d792fc7f4d3c4becf30afe66 Mon Sep 17 00:00:00 2001 From: nirenjan Date: Fri, 27 Mar 2026 20:48:25 -0700 Subject: [PATCH] Add tests for VKM evdev implementation --- vkm/meson.build | 14 + vkm/vkm.h | 10 +- vkm/vkm_linux_evdev.c | 39 +- vkm/vkm_linux_evdev_test.c | 896 +++++++++++++++++++++++++++++++++++++ 4 files changed, 946 insertions(+), 13 deletions(-) create mode 100644 vkm/vkm_linux_evdev_test.c diff --git a/vkm/meson.build b/vkm/meson.build index c61c5c6..c1fa769 100644 --- a/vkm/meson.build +++ b/vkm/meson.build @@ -28,6 +28,20 @@ lib_vkm = library('vkm', vkm_files + vkm_platform_files, dependencies: [vkm_dep], include_directories: [includes]) +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], + ) + test('vkm_linux_evdev', vkm_linux_evdev_test) +endif + install_headers('vkm.h', subdir: 'vkm') pkgconfig.generate(lib_vkm, name: 'vkm', diff --git a/vkm/vkm.h b/vkm/vkm.h index a2e92f9..d40c1d5 100644 --- a/vkm/vkm.h +++ b/vkm/vkm.h @@ -106,7 +106,9 @@ typedef enum { * This option must be passed a boolean which lets VKM know whether to * enable or disable high resolution scrolling. * - * Defaults to true. + * 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, @@ -324,7 +326,11 @@ vkm_result vkm_mouse_click(vkm_context *ctx, vkm_mouse_button button, vkm_button /** * @brief Scroll the mouse wheel * - * Send a single scroll event to 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 diff --git a/vkm/vkm_linux_evdev.c b/vkm/vkm_linux_evdev.c index 4b517e0..c002d03 100644 --- a/vkm/vkm_linux_evdev.c +++ b/vkm/vkm_linux_evdev.c @@ -42,6 +42,12 @@ static const int button_value_map[VKM_BUTTON_STATE_MAX] = { [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]; @@ -316,9 +322,8 @@ vkm_result vkm_mouse_click(vkm_context *ctx, vkm_mouse_button button, vkm_button vkm_result vkm_mouse_scroll(vkm_context *ctx, vkm_mouse_scroll_direction dir) { - int axis; - int val; int rc; + int sign; if (ctx == NULL) { return VKM_ERROR_INVALID_PARAM; @@ -336,18 +341,30 @@ vkm_result vkm_mouse_scroll(vkm_context *ctx, vkm_mouse_scroll_direction dir) if (!ctx->mouse_horizontal_scroll) { return VKM_ERROR_NOT_SUPPORTED; } - axis = REL_HWHEEL; - val = (dir == VKM_MOUSE_SCROLL_LEFT) ? -1 : +1; - } else { - axis = REL_WHEEL; - val = (dir == VKM_MOUSE_SCROLL_UP) ? +1 : -1; + 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; } - if (val == 0) { - return VKM_ERROR_NO_CHANGE; + 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, axis, val); + rc = libevdev_uinput_write_event(ctx->uidev, EV_REL, REL_WHEEL, sign); if (rc != 0) { return VKM_ERROR_EVENT; } diff --git a/vkm/vkm_linux_evdev_test.c b/vkm/vkm_linux_evdev_test.c new file mode 100644 index 0000000..9e10e00 --- /dev/null +++ b/vkm/vkm_linux_evdev_test.c @@ -0,0 +1,896 @@ +/* + * Saitek virtual keyboard mouse - Linux evdev unit tests + * + * Copyright (C) 2026 Nirenjan Krishnan + * + * SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "vkm-internal.h" + +/********************************************************************** + * libevdev stubs (linked instead of libevdev) + *********************************************************************/ +struct libevdev { + int dummy; +}; + +struct libevdev_uinput { + int dummy; +}; + +static struct libevdev dummy_evdev_device; +static struct libevdev_uinput dummy_uinput_device; + +/* When zero, uinput creation "succeeds" and *uidev is set; otherwise that rc is returned. */ +static int vkm_test_uinput_create_rc; + +struct libevdev *libevdev_new(void) +{ + function_called(); + return mock_ptr_type(struct libevdev *); +} + +void libevdev_free(struct libevdev *dev) +{ + check_expected_ptr(dev); +} + +int libevdev_uinput_create_from_device(const struct libevdev *dev, int uinput_fd, + struct libevdev_uinput **uidev) +{ + function_called(); + check_expected_ptr(dev); + check_expected(uinput_fd); + if (vkm_test_uinput_create_rc != 0) { + return vkm_test_uinput_create_rc; + } + *uidev = &dummy_uinput_device; + return 0; +} + +void libevdev_uinput_destroy(struct libevdev_uinput *uinput_dev) +{ + check_expected_ptr(uinput_dev); +} + +int libevdev_enable_event_type(struct libevdev *dev, unsigned int type) +{ + function_called(); + check_expected_ptr(dev); + check_expected(type); + return 0; +} + +int libevdev_enable_event_code(struct libevdev *dev, unsigned int type, unsigned int code, + const void *data) +{ + function_called(); + check_expected_ptr(dev); + check_expected(type); + check_expected(code); + check_expected_ptr(data); + return 0; +} + +void libevdev_set_name(struct libevdev *dev, const char *name) +{ + function_called(); + check_expected_ptr(dev); + check_expected(name); +} + +int libevdev_uinput_write_event(const struct libevdev_uinput *uidev, unsigned int type, + unsigned int code, int value) +{ + function_called(); + check_expected_ptr(uidev); + check_expected(type); + check_expected(code); + check_expected(value); + return mock_type(int); +} + +time_t time(time_t *t) +{ + const time_t tv = (time_t)0xdeadbeef; + + if (t) { + *t = tv; + } + return tv; +} + +/********************************************************************** + * Expectation helpers — mirror enable_mouse_events() order + *********************************************************************/ +static void expect_enable_mouse_events(bool hi_res, bool horiz) +{ + expect_function_call(libevdev_enable_event_type); + expect_value(libevdev_enable_event_type, dev, &dummy_evdev_device); + expect_value(libevdev_enable_event_type, type, EV_REL); + +#define EXPECT_REL_CODE(evcode) \ + do { \ + expect_function_call(libevdev_enable_event_code); \ + expect_value(libevdev_enable_event_code, dev, &dummy_evdev_device); \ + expect_value(libevdev_enable_event_code, type, EV_REL); \ + expect_value(libevdev_enable_event_code, code, (evcode)); \ + expect_value(libevdev_enable_event_code, data, (void *)NULL); \ + } while (0) + + EXPECT_REL_CODE(REL_X); + EXPECT_REL_CODE(REL_Y); + EXPECT_REL_CODE(REL_WHEEL); + if (hi_res) { + EXPECT_REL_CODE(REL_WHEEL_HI_RES); + } + if (horiz) { + EXPECT_REL_CODE(REL_HWHEEL); + if (hi_res) { + EXPECT_REL_CODE(REL_HWHEEL_HI_RES); + } + } +#undef EXPECT_REL_CODE + + expect_function_call(libevdev_enable_event_type); + expect_value(libevdev_enable_event_type, dev, &dummy_evdev_device); + expect_value(libevdev_enable_event_type, type, EV_KEY); + +#define EXPECT_KEY_CODE(keycode) \ + do { \ + expect_function_call(libevdev_enable_event_code); \ + expect_value(libevdev_enable_event_code, dev, &dummy_evdev_device); \ + expect_value(libevdev_enable_event_code, type, EV_KEY); \ + expect_value(libevdev_enable_event_code, code, (keycode)); \ + expect_value(libevdev_enable_event_code, data, (void *)NULL); \ + } while (0) + + EXPECT_KEY_CODE(BTN_LEFT); + EXPECT_KEY_CODE(BTN_RIGHT); + EXPECT_KEY_CODE(BTN_MIDDLE); +#undef EXPECT_KEY_CODE +} + +static void expect_uinput_create_ok(void) +{ + vkm_test_uinput_create_rc = 0; + expect_function_call(libevdev_uinput_create_from_device); + expect_value(libevdev_uinput_create_from_device, dev, &dummy_evdev_device); + expect_value(libevdev_uinput_create_from_device, uinput_fd, LIBEVDEV_UINPUT_OPEN_MANAGED); +} + +/* First successful vkm_start: create dev, name, enable events, uinput. */ +static void expect_first_start_sequence(const char *device_name, bool hi_res, bool horiz) +{ + expect_function_call(libevdev_new); + will_return(libevdev_new, &dummy_evdev_device); + + expect_function_call(libevdev_set_name); + expect_value(libevdev_set_name, dev, &dummy_evdev_device); + expect_string(libevdev_set_name, name, device_name); + + expect_enable_mouse_events(hi_res, horiz); + expect_uinput_create_ok(); +} + +static int teardown_ctx(void **state) +{ + vkm_context *ctx = *state; + + vkm_test_uinput_create_rc = 0; + if (ctx != NULL) { + if (vkm_is_ready(ctx)) { + expect_value(libevdev_uinput_destroy, uinput_dev, &dummy_uinput_device); + expect_value(libevdev_free, dev, &dummy_evdev_device); + } + vkm_exit(ctx); + } + *state = NULL; + return 0; +} + +/********************************************************************** + * Tests: init / exit / queries + *********************************************************************/ +static void test_init_null_out(void **state) +{ + (void)state; + assert_int_equal(vkm_init(NULL), VKM_ERROR_INVALID_PARAM); +} + +static void test_exit_null(void **state) +{ + (void)state; + vkm_exit(NULL); +} + +static void test_platform_supported(void **state) +{ + (void)state; + assert_true(vkm_platform_supported()); +} + +static void test_feature_supported(void **state) +{ + (void)state; + assert_true(vkm_feature_supported(VKM_FEAT_MOUSE)); + assert_false(vkm_feature_supported(VKM_FEAT_KEYBOARD_US)); + assert_false(vkm_feature_supported((vkm_feature)99)); +} + +static void test_is_ready_null(void **state) +{ + (void)state; + assert_false(vkm_is_ready(NULL)); +} + +static void test_is_ready_after_init_only(void **state) +{ + vkm_context *ctx = NULL; + + (void)state; + assert_int_equal(vkm_init(&ctx), VKM_SUCCESS); + assert_non_null(ctx); + assert_false(vkm_is_ready(ctx)); + vkm_exit(ctx); +} + +/********************************************************************** + * Tests: vkm_set_option + *********************************************************************/ +static void test_set_option_null_ctx(void **state) +{ + (void)state; + assert_int_equal(vkm_set_option(NULL, VKM_OPT_HI_RES_SCROLL, 1), VKM_ERROR_INVALID_PARAM); +} + +static void test_set_option_unknown(void **state) +{ + vkm_context *ctx = NULL; + + assert_int_equal(vkm_init(&ctx), VKM_SUCCESS); + *state = ctx; + assert_int_equal(vkm_set_option(ctx, (vkm_option)999, 0), VKM_ERROR_INVALID_PARAM); +} + +static void test_set_option_flags_and_name(void **state) +{ + vkm_context *ctx = NULL; + + assert_int_equal(vkm_init(&ctx), VKM_SUCCESS); + *state = ctx; + + assert_int_equal(vkm_set_option(ctx, VKM_OPT_HI_RES_SCROLL, 1), VKM_SUCCESS); + assert_int_equal(vkm_set_option(ctx, VKM_OPT_HORIZONTAL_SCROLL, 1), VKM_SUCCESS); + assert_int_equal(vkm_set_option(ctx, VKM_OPT_DEVICE_NAME, "custom-name"), VKM_SUCCESS); + + expect_first_start_sequence("custom-name", true, true); + assert_int_equal(vkm_start(ctx), VKM_SUCCESS); +} + +/********************************************************************** + * Tests: vkm_start + *********************************************************************/ +static void test_start_null(void **state) +{ + (void)state; + assert_int_equal(vkm_start(NULL), VKM_ERROR_INVALID_PARAM); +} + +static void test_start_libevdev_new_fails(void **state) +{ + vkm_context *ctx = NULL; + + assert_int_equal(vkm_init(&ctx), VKM_SUCCESS); + *state = ctx; + + expect_function_call(libevdev_new); + will_return(libevdev_new, (struct libevdev *)(NULL)); + + assert_int_equal(vkm_start(ctx), VKM_ERROR_DEV_FAILURE); +} + +static void test_start_uinput_fails(void **state) +{ + vkm_context *ctx = NULL; + + assert_int_equal(vkm_init(&ctx), VKM_SUCCESS); + *state = ctx; + + vkm_test_uinput_create_rc = -1; + + expect_function_call(libevdev_new); + will_return(libevdev_new, &dummy_evdev_device); + + expect_function_call(libevdev_set_name); + expect_value(libevdev_set_name, dev, &dummy_evdev_device); + expect_string(libevdev_set_name, name, "VKM virtual device @deadbeef"); + + expect_enable_mouse_events(false, false); + + expect_function_call(libevdev_uinput_create_from_device); + expect_value(libevdev_uinput_create_from_device, dev, &dummy_evdev_device); + expect_value(libevdev_uinput_create_from_device, uinput_fd, LIBEVDEV_UINPUT_OPEN_MANAGED); + + expect_value(libevdev_free, dev, &dummy_evdev_device); + + assert_int_equal(vkm_start(ctx), VKM_ERROR_DEV_FAILURE); +} + +static void test_start_default_name_success(void **state) +{ + vkm_context *ctx = NULL; + + assert_int_equal(vkm_init(&ctx), VKM_SUCCESS); + *state = ctx; + + expect_first_start_sequence("VKM virtual device @deadbeef", false, false); + assert_int_equal(vkm_start(ctx), VKM_SUCCESS); + assert_true(vkm_is_ready(ctx)); +} + +static void test_start_hi_res_scroll_only(void **state) +{ + vkm_context *ctx = NULL; + + assert_int_equal(vkm_init(&ctx), VKM_SUCCESS); + assert_int_equal(vkm_set_option(ctx, VKM_OPT_HI_RES_SCROLL, 1), VKM_SUCCESS); + *state = ctx; + + expect_first_start_sequence("VKM virtual device @deadbeef", true, false); + assert_int_equal(vkm_start(ctx), VKM_SUCCESS); +} + +static void test_start_horizontal_scroll_only(void **state) +{ + vkm_context *ctx = NULL; + + assert_int_equal(vkm_init(&ctx), VKM_SUCCESS); + assert_int_equal(vkm_set_option(ctx, VKM_OPT_HORIZONTAL_SCROLL, 1), VKM_SUCCESS); + *state = ctx; + + expect_first_start_sequence("VKM virtual device @deadbeef", false, true); + assert_int_equal(vkm_start(ctx), VKM_SUCCESS); +} + +static void test_start_twice_idempotent(void **state) +{ + vkm_context *ctx = NULL; + + assert_int_equal(vkm_init(&ctx), VKM_SUCCESS); + *state = ctx; + + expect_first_start_sequence("VKM virtual device @deadbeef", false, false); + assert_int_equal(vkm_start(ctx), VKM_SUCCESS); + assert_int_equal(vkm_start(ctx), VKM_SUCCESS); +} + +/********************************************************************** + * Tests: mouse move + *********************************************************************/ +static void test_mouse_move_param_and_ready(void **state) +{ + assert_int_equal(vkm_mouse_move(NULL, 1, 1), VKM_ERROR_INVALID_PARAM); + + vkm_context *ctx = NULL; + assert_int_equal(vkm_init(&ctx), VKM_SUCCESS); + *state = ctx; + assert_int_equal(vkm_mouse_move(ctx, 1, 1), VKM_ERROR_NOT_READY); +} + +static void test_mouse_move_no_change(void **state) +{ + vkm_context *ctx = NULL; + + assert_int_equal(vkm_init(&ctx), VKM_SUCCESS); + *state = ctx; + expect_first_start_sequence("VKM virtual device @deadbeef", false, false); + assert_int_equal(vkm_start(ctx), VKM_SUCCESS); + + assert_int_equal(vkm_mouse_move(ctx, 0, 0), VKM_ERROR_NO_CHANGE); +} + +static void test_mouse_move_dx_then_dy(void **state) +{ + vkm_context *ctx = NULL; + + assert_int_equal(vkm_init(&ctx), VKM_SUCCESS); + *state = ctx; + expect_first_start_sequence("VKM virtual device @deadbeef", false, false); + assert_int_equal(vkm_start(ctx), VKM_SUCCESS); + + expect_function_call(libevdev_uinput_write_event); + expect_value(libevdev_uinput_write_event, uidev, &dummy_uinput_device); + expect_value(libevdev_uinput_write_event, type, EV_REL); + expect_value(libevdev_uinput_write_event, code, REL_X); + expect_value(libevdev_uinput_write_event, value, 3); + will_return(libevdev_uinput_write_event, 0); + + expect_function_call(libevdev_uinput_write_event); + expect_value(libevdev_uinput_write_event, uidev, &dummy_uinput_device); + expect_value(libevdev_uinput_write_event, type, EV_REL); + expect_value(libevdev_uinput_write_event, code, REL_Y); + expect_value(libevdev_uinput_write_event, value, -2); + will_return(libevdev_uinput_write_event, 0); + + assert_int_equal(vkm_mouse_move(ctx, 3, -2), VKM_SUCCESS); +} + +static void test_mouse_move_dx_only(void **state) +{ + vkm_context *ctx = NULL; + + assert_int_equal(vkm_init(&ctx), VKM_SUCCESS); + *state = ctx; + expect_first_start_sequence("VKM virtual device @deadbeef", false, false); + assert_int_equal(vkm_start(ctx), VKM_SUCCESS); + + expect_function_call(libevdev_uinput_write_event); + expect_value(libevdev_uinput_write_event, uidev, &dummy_uinput_device); + expect_value(libevdev_uinput_write_event, type, EV_REL); + expect_value(libevdev_uinput_write_event, code, REL_X); + expect_value(libevdev_uinput_write_event, value, -9); + will_return(libevdev_uinput_write_event, 0); + assert_int_equal(vkm_mouse_move(ctx, -9, 0), VKM_SUCCESS); +} + +static void test_mouse_move_write_dx_fails(void **state) +{ + vkm_context *ctx = NULL; + + assert_int_equal(vkm_init(&ctx), VKM_SUCCESS); + *state = ctx; + expect_first_start_sequence("VKM virtual device @deadbeef", false, false); + assert_int_equal(vkm_start(ctx), VKM_SUCCESS); + + expect_function_call(libevdev_uinput_write_event); + expect_value(libevdev_uinput_write_event, uidev, &dummy_uinput_device); + expect_value(libevdev_uinput_write_event, type, EV_REL); + expect_value(libevdev_uinput_write_event, code, REL_X); + expect_value(libevdev_uinput_write_event, value, 5); + will_return(libevdev_uinput_write_event, -1); + + assert_int_equal(vkm_mouse_move(ctx, 5, 0), VKM_ERROR_EVENT); +} + +static void test_mouse_move_write_dy_fails(void **state) +{ + vkm_context *ctx = NULL; + + assert_int_equal(vkm_init(&ctx), VKM_SUCCESS); + *state = ctx; + expect_first_start_sequence("VKM virtual device @deadbeef", false, false); + assert_int_equal(vkm_start(ctx), VKM_SUCCESS); + + expect_function_call(libevdev_uinput_write_event); + expect_value(libevdev_uinput_write_event, uidev, &dummy_uinput_device); + expect_value(libevdev_uinput_write_event, type, EV_REL); + expect_value(libevdev_uinput_write_event, code, REL_Y); + expect_value(libevdev_uinput_write_event, value, 7); + will_return(libevdev_uinput_write_event, -1); + + assert_int_equal(vkm_mouse_move(ctx, 0, 7), VKM_ERROR_EVENT); +} + +/********************************************************************** + * Tests: mouse click + *********************************************************************/ +static void test_mouse_click_invalid(void **state) +{ + vkm_context *ctx = NULL; + assert_int_equal(vkm_init(&ctx), VKM_SUCCESS); + *state = ctx; + expect_first_start_sequence("VKM virtual device @deadbeef", false, false); + assert_int_equal(vkm_start(ctx), VKM_SUCCESS); + + assert_int_equal(vkm_mouse_click(NULL, VKM_MOUSE_BTN_LEFT, VKM_BUTTON_PRESSED), + VKM_ERROR_INVALID_PARAM); + assert_int_equal(vkm_mouse_click(ctx, VKM_MOUSE_BTN_MAX, VKM_BUTTON_PRESSED), + VKM_ERROR_INVALID_PARAM); + assert_int_equal(vkm_mouse_click(ctx, VKM_MOUSE_BTN_LEFT, VKM_BUTTON_STATE_MAX), + VKM_ERROR_INVALID_PARAM); +} + +static void test_mouse_click_not_ready(void **state) +{ + vkm_context *ctx = NULL; + assert_int_equal(vkm_init(&ctx), VKM_SUCCESS); + *state = ctx; + assert_int_equal(vkm_mouse_click(ctx, VKM_MOUSE_BTN_LEFT, VKM_BUTTON_PRESSED), + VKM_ERROR_NOT_READY); +} + +static void test_mouse_click_no_change(void **state) +{ + vkm_context *ctx = NULL; + + assert_int_equal(vkm_init(&ctx), VKM_SUCCESS); + *state = ctx; + expect_first_start_sequence("VKM virtual device @deadbeef", false, false); + assert_int_equal(vkm_start(ctx), VKM_SUCCESS); + + expect_function_call(libevdev_uinput_write_event); + expect_value(libevdev_uinput_write_event, uidev, &dummy_uinput_device); + expect_value(libevdev_uinput_write_event, type, EV_KEY); + expect_value(libevdev_uinput_write_event, code, BTN_LEFT); + expect_value(libevdev_uinput_write_event, value, 1); + will_return(libevdev_uinput_write_event, 0); + assert_int_equal(vkm_mouse_click(ctx, VKM_MOUSE_BTN_LEFT, VKM_BUTTON_PRESSED), VKM_SUCCESS); + + assert_int_equal(vkm_mouse_click(ctx, VKM_MOUSE_BTN_LEFT, VKM_BUTTON_PRESSED), + VKM_ERROR_NO_CHANGE); +} + +static void test_mouse_click_success_release(void **state) +{ + vkm_context *ctx = NULL; + + assert_int_equal(vkm_init(&ctx), VKM_SUCCESS); + *state = ctx; + expect_first_start_sequence("VKM virtual device @deadbeef", false, false); + assert_int_equal(vkm_start(ctx), VKM_SUCCESS); + + expect_function_call(libevdev_uinput_write_event); + expect_value(libevdev_uinput_write_event, uidev, &dummy_uinput_device); + expect_value(libevdev_uinput_write_event, type, EV_KEY); + expect_value(libevdev_uinput_write_event, code, BTN_RIGHT); + expect_value(libevdev_uinput_write_event, value, 1); + will_return(libevdev_uinput_write_event, 0); + assert_int_equal(vkm_mouse_click(ctx, VKM_MOUSE_BTN_RIGHT, VKM_BUTTON_PRESSED), VKM_SUCCESS); + + expect_function_call(libevdev_uinput_write_event); + expect_value(libevdev_uinput_write_event, uidev, &dummy_uinput_device); + expect_value(libevdev_uinput_write_event, type, EV_KEY); + expect_value(libevdev_uinput_write_event, code, BTN_RIGHT); + expect_value(libevdev_uinput_write_event, value, 0); + will_return(libevdev_uinput_write_event, 0); + assert_int_equal(vkm_mouse_click(ctx, VKM_MOUSE_BTN_RIGHT, VKM_BUTTON_RELEASED), VKM_SUCCESS); +} + +static void test_mouse_click_write_fails(void **state) +{ + vkm_context *ctx = NULL; + + assert_int_equal(vkm_init(&ctx), VKM_SUCCESS); + *state = ctx; + expect_first_start_sequence("VKM virtual device @deadbeef", false, false); + assert_int_equal(vkm_start(ctx), VKM_SUCCESS); + + expect_function_call(libevdev_uinput_write_event); + expect_value(libevdev_uinput_write_event, uidev, &dummy_uinput_device); + expect_value(libevdev_uinput_write_event, type, EV_KEY); + expect_value(libevdev_uinput_write_event, code, BTN_MIDDLE); + expect_value(libevdev_uinput_write_event, value, 1); + will_return(libevdev_uinput_write_event, -1); + assert_int_equal(vkm_mouse_click(ctx, VKM_MOUSE_BTN_MIDDLE, VKM_BUTTON_PRESSED), + VKM_ERROR_EVENT); +} + +/********************************************************************** + * Tests: scroll + *********************************************************************/ +static void test_mouse_scroll_param_ready(void **state) +{ + assert_int_equal(vkm_mouse_scroll(NULL, VKM_MOUSE_SCROLL_UP), VKM_ERROR_INVALID_PARAM); + + vkm_context *ctx = NULL; + assert_int_equal(vkm_init(&ctx), VKM_SUCCESS); + *state = ctx; + assert_int_equal(vkm_mouse_scroll(ctx, VKM_MOUSE_SCROLL_MAX), VKM_ERROR_INVALID_PARAM); + assert_int_equal(vkm_mouse_scroll(ctx, VKM_MOUSE_SCROLL_UP), VKM_ERROR_NOT_READY); +} + +static void test_mouse_scroll_horizontal_disabled(void **state) +{ + vkm_context *ctx = NULL; + + assert_int_equal(vkm_init(&ctx), VKM_SUCCESS); + *state = ctx; + expect_first_start_sequence("VKM virtual device @deadbeef", false, false); + assert_int_equal(vkm_start(ctx), VKM_SUCCESS); + + assert_int_equal(vkm_mouse_scroll(ctx, VKM_MOUSE_SCROLL_LEFT), VKM_ERROR_NOT_SUPPORTED); + assert_int_equal(vkm_mouse_scroll(ctx, VKM_MOUSE_SCROLL_RIGHT), VKM_ERROR_NOT_SUPPORTED); +} + +static void test_mouse_scroll_vertical(void **state) +{ + vkm_context *ctx = NULL; + + assert_int_equal(vkm_init(&ctx), VKM_SUCCESS); + *state = ctx; + expect_first_start_sequence("VKM virtual device @deadbeef", false, false); + assert_int_equal(vkm_start(ctx), VKM_SUCCESS); + + expect_function_call(libevdev_uinput_write_event); + expect_value(libevdev_uinput_write_event, uidev, &dummy_uinput_device); + expect_value(libevdev_uinput_write_event, type, EV_REL); + expect_value(libevdev_uinput_write_event, code, REL_WHEEL); + expect_value(libevdev_uinput_write_event, value, 1); + will_return(libevdev_uinput_write_event, 0); + assert_int_equal(vkm_mouse_scroll(ctx, VKM_MOUSE_SCROLL_UP), VKM_SUCCESS); + + expect_function_call(libevdev_uinput_write_event); + expect_value(libevdev_uinput_write_event, uidev, &dummy_uinput_device); + expect_value(libevdev_uinput_write_event, type, EV_REL); + expect_value(libevdev_uinput_write_event, code, REL_WHEEL); + expect_value(libevdev_uinput_write_event, value, -1); + will_return(libevdev_uinput_write_event, 0); + assert_int_equal(vkm_mouse_scroll(ctx, VKM_MOUSE_SCROLL_DOWN), VKM_SUCCESS); +} + +static void test_mouse_scroll_vertical_hi_res(void **state) +{ + vkm_context *ctx = NULL; + + assert_int_equal(vkm_init(&ctx), VKM_SUCCESS); + assert_int_equal(vkm_set_option(ctx, VKM_OPT_HI_RES_SCROLL, 1), VKM_SUCCESS); + *state = ctx; + expect_first_start_sequence("VKM virtual device @deadbeef", true, false); + assert_int_equal(vkm_start(ctx), VKM_SUCCESS); + + expect_function_call(libevdev_uinput_write_event); + expect_value(libevdev_uinput_write_event, uidev, &dummy_uinput_device); + expect_value(libevdev_uinput_write_event, type, EV_REL); + expect_value(libevdev_uinput_write_event, code, REL_WHEEL_HI_RES); + expect_value(libevdev_uinput_write_event, value, 120); + will_return(libevdev_uinput_write_event, 0); + expect_function_call(libevdev_uinput_write_event); + expect_value(libevdev_uinput_write_event, uidev, &dummy_uinput_device); + expect_value(libevdev_uinput_write_event, type, EV_REL); + expect_value(libevdev_uinput_write_event, code, REL_WHEEL); + expect_value(libevdev_uinput_write_event, value, 1); + will_return(libevdev_uinput_write_event, 0); + assert_int_equal(vkm_mouse_scroll(ctx, VKM_MOUSE_SCROLL_UP), VKM_SUCCESS); + + expect_function_call(libevdev_uinput_write_event); + expect_value(libevdev_uinput_write_event, uidev, &dummy_uinput_device); + expect_value(libevdev_uinput_write_event, type, EV_REL); + expect_value(libevdev_uinput_write_event, code, REL_WHEEL_HI_RES); + expect_value(libevdev_uinput_write_event, value, -120); + will_return(libevdev_uinput_write_event, 0); + expect_function_call(libevdev_uinput_write_event); + expect_value(libevdev_uinput_write_event, uidev, &dummy_uinput_device); + expect_value(libevdev_uinput_write_event, type, EV_REL); + expect_value(libevdev_uinput_write_event, code, REL_WHEEL); + expect_value(libevdev_uinput_write_event, value, -1); + will_return(libevdev_uinput_write_event, 0); + assert_int_equal(vkm_mouse_scroll(ctx, VKM_MOUSE_SCROLL_DOWN), VKM_SUCCESS); +} + +static void test_mouse_scroll_horizontal_enabled(void **state) +{ + vkm_context *ctx = NULL; + + assert_int_equal(vkm_init(&ctx), VKM_SUCCESS); + assert_int_equal(vkm_set_option(ctx, VKM_OPT_HORIZONTAL_SCROLL, 1), VKM_SUCCESS); + *state = ctx; + expect_first_start_sequence("VKM virtual device @deadbeef", false, true); + assert_int_equal(vkm_start(ctx), VKM_SUCCESS); + + expect_function_call(libevdev_uinput_write_event); + expect_value(libevdev_uinput_write_event, uidev, &dummy_uinput_device); + expect_value(libevdev_uinput_write_event, type, EV_REL); + expect_value(libevdev_uinput_write_event, code, REL_HWHEEL); + expect_value(libevdev_uinput_write_event, value, -1); + will_return(libevdev_uinput_write_event, 0); + assert_int_equal(vkm_mouse_scroll(ctx, VKM_MOUSE_SCROLL_LEFT), VKM_SUCCESS); + + expect_function_call(libevdev_uinput_write_event); + expect_value(libevdev_uinput_write_event, uidev, &dummy_uinput_device); + expect_value(libevdev_uinput_write_event, type, EV_REL); + expect_value(libevdev_uinput_write_event, code, REL_HWHEEL); + expect_value(libevdev_uinput_write_event, value, 1); + will_return(libevdev_uinput_write_event, 0); + assert_int_equal(vkm_mouse_scroll(ctx, VKM_MOUSE_SCROLL_RIGHT), VKM_SUCCESS); +} + +static void test_mouse_scroll_horizontal_hi_res(void **state) +{ + vkm_context *ctx = NULL; + + assert_int_equal(vkm_init(&ctx), VKM_SUCCESS); + assert_int_equal(vkm_set_option(ctx, VKM_OPT_HI_RES_SCROLL, 1), VKM_SUCCESS); + assert_int_equal(vkm_set_option(ctx, VKM_OPT_HORIZONTAL_SCROLL, 1), VKM_SUCCESS); + *state = ctx; + expect_first_start_sequence("VKM virtual device @deadbeef", true, true); + assert_int_equal(vkm_start(ctx), VKM_SUCCESS); + + expect_function_call(libevdev_uinput_write_event); + expect_value(libevdev_uinput_write_event, uidev, &dummy_uinput_device); + expect_value(libevdev_uinput_write_event, type, EV_REL); + expect_value(libevdev_uinput_write_event, code, REL_HWHEEL_HI_RES); + expect_value(libevdev_uinput_write_event, value, -120); + will_return(libevdev_uinput_write_event, 0); + expect_function_call(libevdev_uinput_write_event); + expect_value(libevdev_uinput_write_event, uidev, &dummy_uinput_device); + expect_value(libevdev_uinput_write_event, type, EV_REL); + expect_value(libevdev_uinput_write_event, code, REL_HWHEEL); + expect_value(libevdev_uinput_write_event, value, -1); + will_return(libevdev_uinput_write_event, 0); + assert_int_equal(vkm_mouse_scroll(ctx, VKM_MOUSE_SCROLL_LEFT), VKM_SUCCESS); + + expect_function_call(libevdev_uinput_write_event); + expect_value(libevdev_uinput_write_event, uidev, &dummy_uinput_device); + expect_value(libevdev_uinput_write_event, type, EV_REL); + expect_value(libevdev_uinput_write_event, code, REL_HWHEEL_HI_RES); + expect_value(libevdev_uinput_write_event, value, 120); + will_return(libevdev_uinput_write_event, 0); + expect_function_call(libevdev_uinput_write_event); + expect_value(libevdev_uinput_write_event, uidev, &dummy_uinput_device); + expect_value(libevdev_uinput_write_event, type, EV_REL); + expect_value(libevdev_uinput_write_event, code, REL_HWHEEL); + expect_value(libevdev_uinput_write_event, value, 1); + will_return(libevdev_uinput_write_event, 0); + assert_int_equal(vkm_mouse_scroll(ctx, VKM_MOUSE_SCROLL_RIGHT), VKM_SUCCESS); +} + +static void test_mouse_scroll_write_fails(void **state) +{ + vkm_context *ctx = NULL; + + assert_int_equal(vkm_init(&ctx), VKM_SUCCESS); + *state = ctx; + expect_first_start_sequence("VKM virtual device @deadbeef", false, false); + assert_int_equal(vkm_start(ctx), VKM_SUCCESS); + + expect_function_call(libevdev_uinput_write_event); + expect_value(libevdev_uinput_write_event, uidev, &dummy_uinput_device); + expect_value(libevdev_uinput_write_event, type, EV_REL); + expect_value(libevdev_uinput_write_event, code, REL_WHEEL); + expect_value(libevdev_uinput_write_event, value, 1); + will_return(libevdev_uinput_write_event, -1); + assert_int_equal(vkm_mouse_scroll(ctx, VKM_MOUSE_SCROLL_UP), VKM_ERROR_EVENT); +} + +static void test_mouse_scroll_write_fails_hi_res(void **state) +{ + vkm_context *ctx = NULL; + + assert_int_equal(vkm_init(&ctx), VKM_SUCCESS); + assert_int_equal(vkm_set_option(ctx, VKM_OPT_HI_RES_SCROLL, 1), VKM_SUCCESS); + *state = ctx; + expect_first_start_sequence("VKM virtual device @deadbeef", true, false); + assert_int_equal(vkm_start(ctx), VKM_SUCCESS); + + expect_function_call(libevdev_uinput_write_event); + expect_value(libevdev_uinput_write_event, uidev, &dummy_uinput_device); + expect_value(libevdev_uinput_write_event, type, EV_REL); + expect_value(libevdev_uinput_write_event, code, REL_WHEEL_HI_RES); + expect_value(libevdev_uinput_write_event, value, 120); + will_return(libevdev_uinput_write_event, -1); + assert_int_equal(vkm_mouse_scroll(ctx, VKM_MOUSE_SCROLL_UP), VKM_ERROR_EVENT); +} + +/********************************************************************** + * Tests: sync + *********************************************************************/ +static void test_sync_null(void **state) +{ + (void)state; + assert_int_equal(vkm_sync(NULL), VKM_ERROR_INVALID_PARAM); +} + +static void test_sync_not_ready(void **state) +{ + vkm_context *ctx = NULL; + assert_int_equal(vkm_init(&ctx), VKM_SUCCESS); + *state = ctx; + assert_int_equal(vkm_sync(ctx), VKM_ERROR_NOT_READY); +} + +static void test_sync_success(void **state) +{ + vkm_context *ctx = NULL; + + assert_int_equal(vkm_init(&ctx), VKM_SUCCESS); + *state = ctx; + expect_first_start_sequence("VKM virtual device @deadbeef", false, false); + assert_int_equal(vkm_start(ctx), VKM_SUCCESS); + + expect_function_call(libevdev_uinput_write_event); + expect_value(libevdev_uinput_write_event, uidev, &dummy_uinput_device); + expect_value(libevdev_uinput_write_event, type, EV_SYN); + expect_value(libevdev_uinput_write_event, code, SYN_REPORT); + expect_value(libevdev_uinput_write_event, value, 0); + will_return(libevdev_uinput_write_event, 0); + assert_int_equal(vkm_sync(ctx), VKM_SUCCESS); +} + +static void test_sync_write_fails(void **state) +{ + vkm_context *ctx = NULL; + + assert_int_equal(vkm_init(&ctx), VKM_SUCCESS); + *state = ctx; + expect_first_start_sequence("VKM virtual device @deadbeef", false, false); + assert_int_equal(vkm_start(ctx), VKM_SUCCESS); + + expect_function_call(libevdev_uinput_write_event); + expect_value(libevdev_uinput_write_event, uidev, &dummy_uinput_device); + expect_value(libevdev_uinput_write_event, type, EV_SYN); + expect_value(libevdev_uinput_write_event, code, SYN_REPORT); + expect_value(libevdev_uinput_write_event, value, 0); + will_return(libevdev_uinput_write_event, -1); + assert_int_equal(vkm_sync(ctx), VKM_ERROR_EVENT); +} + +/********************************************************************** + * Tests: vkm_exit destroys uinput + dev + *********************************************************************/ +static void test_exit_after_start(void **state) +{ + vkm_context *ctx = NULL; + + assert_int_equal(vkm_init(&ctx), VKM_SUCCESS); + *state = ctx; + expect_first_start_sequence("VKM virtual device @deadbeef", false, false); + assert_int_equal(vkm_start(ctx), VKM_SUCCESS); + + expect_value(libevdev_uinput_destroy, uinput_dev, &dummy_uinput_device); + expect_value(libevdev_free, dev, &dummy_evdev_device); + + vkm_exit(ctx); + *state = NULL; +} + +int main(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_init_null_out), + cmocka_unit_test(test_exit_null), + cmocka_unit_test(test_platform_supported), + cmocka_unit_test(test_feature_supported), + cmocka_unit_test(test_is_ready_null), + cmocka_unit_test(test_is_ready_after_init_only), + cmocka_unit_test(test_set_option_null_ctx), + cmocka_unit_test_setup_teardown(test_set_option_unknown, NULL, teardown_ctx), + cmocka_unit_test_setup_teardown(test_set_option_flags_and_name, NULL, teardown_ctx), + cmocka_unit_test(test_start_null), + cmocka_unit_test_setup_teardown(test_start_libevdev_new_fails, NULL, teardown_ctx), + cmocka_unit_test_setup_teardown(test_start_uinput_fails, NULL, teardown_ctx), + cmocka_unit_test_setup_teardown(test_start_default_name_success, NULL, teardown_ctx), + cmocka_unit_test_setup_teardown(test_start_hi_res_scroll_only, NULL, teardown_ctx), + cmocka_unit_test_setup_teardown(test_start_horizontal_scroll_only, NULL, teardown_ctx), + cmocka_unit_test_setup_teardown(test_start_twice_idempotent, NULL, teardown_ctx), + cmocka_unit_test_setup_teardown(test_mouse_move_param_and_ready, NULL, teardown_ctx), + cmocka_unit_test_setup_teardown(test_mouse_move_no_change, NULL, teardown_ctx), + cmocka_unit_test_setup_teardown(test_mouse_move_dx_then_dy, NULL, teardown_ctx), + cmocka_unit_test_setup_teardown(test_mouse_move_dx_only, NULL, teardown_ctx), + cmocka_unit_test_setup_teardown(test_mouse_move_write_dx_fails, NULL, teardown_ctx), + cmocka_unit_test_setup_teardown(test_mouse_move_write_dy_fails, NULL, teardown_ctx), + cmocka_unit_test_setup_teardown(test_mouse_click_invalid, NULL, teardown_ctx), + cmocka_unit_test_setup_teardown(test_mouse_click_not_ready, NULL, teardown_ctx), + cmocka_unit_test_setup_teardown(test_mouse_click_no_change, NULL, teardown_ctx), + cmocka_unit_test_setup_teardown(test_mouse_click_success_release, NULL, teardown_ctx), + cmocka_unit_test_setup_teardown(test_mouse_click_write_fails, NULL, teardown_ctx), + cmocka_unit_test_setup_teardown(test_mouse_scroll_param_ready, NULL, teardown_ctx), + cmocka_unit_test_setup_teardown(test_mouse_scroll_horizontal_disabled, NULL, teardown_ctx), + cmocka_unit_test_setup_teardown(test_mouse_scroll_vertical, NULL, teardown_ctx), + cmocka_unit_test_setup_teardown(test_mouse_scroll_vertical_hi_res, NULL, teardown_ctx), + cmocka_unit_test_setup_teardown(test_mouse_scroll_horizontal_enabled, NULL, teardown_ctx), + cmocka_unit_test_setup_teardown(test_mouse_scroll_horizontal_hi_res, NULL, teardown_ctx), + cmocka_unit_test_setup_teardown(test_mouse_scroll_write_fails, NULL, teardown_ctx), + cmocka_unit_test_setup_teardown(test_mouse_scroll_write_fails_hi_res, NULL, teardown_ctx), + cmocka_unit_test(test_sync_null), + cmocka_unit_test_setup_teardown(test_sync_not_ready, NULL, teardown_ctx), + cmocka_unit_test_setup_teardown(test_sync_success, NULL, teardown_ctx), + cmocka_unit_test_setup_teardown(test_sync_write_fails, NULL, teardown_ctx), + cmocka_unit_test(test_exit_after_start), + }; + + cmocka_set_message_output(CM_OUTPUT_TAP); + return cmocka_run_group_tests(tests, NULL, NULL); +}