diff --git a/meson.build b/meson.build index 5d26b37..dac49ef 100644 --- a/meson.build +++ b/meson.build @@ -8,7 +8,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) @@ -110,6 +115,7 @@ includes = include_directories('.', 'libx52', 'libx52io', 'libx52util') subdir('libx52') subdir('libx52io') subdir('libx52util') +subdir('vkm') subdir('bugreport') subdir('cli') subdir('joytest') diff --git a/vkm/meson.build b/vkm/meson.build new file mode 100644 index 0000000..b8a09cc --- /dev/null +++ b/vkm/meson.build @@ -0,0 +1,35 @@ +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_platform_files = vkm_stub_files +endif + +lib_vkm = library('vkm', vkm_files + vkm_platform_files, + install: true, + version: vkm_version, + dependencies: [vkm_dep], + include_directories: [includes]) + +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) diff --git a/vkm/vkm-internal.h b/vkm/vkm-internal.h new file mode 100644 index 0000000..b93acae --- /dev/null +++ b/vkm/vkm-internal.h @@ -0,0 +1,22 @@ +/* + * VKM common functions + * + * Copyright (C) 2026 Nirenjan Krishnan + * + * SPDX-LicenseIdentifier: GPL-2.0-only WITH Classpath-exception-2.0 + */ + +#include +#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 diff --git a/vkm/vkm.h b/vkm/vkm.h index 673186d..6c8ea8a 100644 --- a/vkm/vkm.h +++ b/vkm/vkm.h @@ -21,6 +21,7 @@ #define VKM_H #include +#include #ifdef __cplusplus extern "C" { @@ -45,6 +46,149 @@ typedef struct vkm_context vkm_context; */ typedef int32_t vkm_result; +/** + * @brief Feature identifiers + * + * These are used to check for platform support of the relevant feature. + */ +typedef enum { + /** Check if mouse is supported on this platform */ + VKM_FEAT_MOUSE = (1 << 0), + + /** Check if US keyboard is supported on this platform */ + VKM_FEAT_KEYBOARD_US = (1 << 1), + + /* 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 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 true. + */ + 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 Initialize the VKM library * @@ -82,6 +226,135 @@ vkm_result vkm_init(vkm_context **ctx); */ void vkm_exit(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 + */ +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 + */ +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 + * + * @param[in] ctx Context pointer + * @param[in] dir Scroll direction + * @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_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 + */ +vkm_result vkm_mouse_scroll(vkm_context *ctx, vkm_mouse_scroll_direction dir); + +/** + * @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 + */ +vkm_result vkm_sync(vkm_context *ctx); + #ifdef __cplusplus } #endif diff --git a/vkm/vkm_common.c b/vkm/vkm_common.c new file mode 100644 index 0000000..d01ad27 --- /dev/null +++ b/vkm/vkm_common.c @@ -0,0 +1,27 @@ +/* + * VKM common functions + * + * Copyright (C) 2026 Nirenjan Krishnan + * + * SPDX-LicenseIdentifier: GPL-2.0-only WITH Classpath-exception-2.0 + */ + +#include + +#include "vkm-internal.h" + +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; + } +} diff --git a/vkm/vkm_linux_evdev.c b/vkm/vkm_linux_evdev.c new file mode 100644 index 0000000..a3a4cdc --- /dev/null +++ b/vkm/vkm_linux_evdev.c @@ -0,0 +1,373 @@ +/* + * VKM Linux evdev implementation + * + * Copyright (C) 2026 Nirenjan Krishnan + * + * SPDX-LicenseIdentifier: GPL-2.0-only WITH Classpath-exception-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "libevdev/libevdev.h" +#include "libevdev/libevdev-uinput.h" + +#include "vkm-internal.h" + +struct vkm_context { + struct libevdev *dev; + struct libevdev_uinput *uidev; + + char *name; + bool mouse_hi_res_scroll; + bool mouse_horizontal_scroll; + + // 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, +}; + +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 + +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; + } + + 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); +} + +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); + } + + 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_US: + default: + return false; + } + + 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_X, dx); + 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; + } + + 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 axis; + int val; + int rc; + + 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; + } + axis = REL_HWHEEL; + val = (dir == VKM_MOUSE_SCROLL_LEFT) ? -1 : +1; + } else { + axis = REL_WHEEL; + val = (dir == VKM_MOUSE_SCROLL_UP) ? +1 : -1; + } + + if (val == 0) { + return VKM_ERROR_NO_CHANGE; + } + + rc = libevdev_uinput_write_event(ctx->uidev, EV_REL, axis, val); + if (rc != 0) { + return VKM_ERROR_EVENT; + } + + return VKM_SUCCESS; +} + +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; +} diff --git a/vkm/vkm_stub.c b/vkm/vkm_stub.c new file mode 100644 index 0000000..62557cd --- /dev/null +++ b/vkm/vkm_stub.c @@ -0,0 +1,129 @@ +/* + * VKM stub implementation + * + * Copyright (C) 2026 Nirenjan Krishnan + * + * SPDX-LicenseIdentifier: GPL-2.0-only WITH Classpath-exception-2.0 + */ + +#include +#include + +#include "vkm.h" + +// Dummy struct +struct vkm_context { +}; + +#define DUMMY_CTX (vkm_context *)0xDEADBEEF + +vkm_result vkm_init(vkm_context **ctx) +{ + if (ctx != NULL) { + *ctx = DUMMY_CTX; + return VKM_SUCCESS; + } + + return VKM_ERROR_INVALID_PARAM; +} + +void vkm_exit(vkm_context *ctx) +{ + (void)ctx; +} + +vkm_result vkm_start(vkm_context *ctx) +{ + if (ctx != DUMMY_CTX) { + return VKM_ERROR_INVALID_PARAM; + } + + return VKM_SUCCESS; +} + +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 != DUMMY_CTX) { + return VKM_ERROR_INVALID_PARAM; + } + + va_start(ap, option); + rc = VKM_SUCCESS; + switch (option) { + case VKM_OPT_HI_RES_SCROLL: + 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); + (void)flag; + break; + + case VKM_OPT_DEVICE_NAME: + name = va_arg(ap, char *); + (void)name; + 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) +{ + (void)dx; + (void)dy; + if (ctx != DUMMY_CTX) { + return VKM_ERROR_INVALID_PARAM; + } + + return VKM_SUCCESS; +} + +vkm_result vkm_mouse_click(vkm_context *ctx, vkm_mouse_button button, vkm_button_state state) +{ + if (ctx != DUMMY_CTX) { + 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; + } + + return VKM_SUCCESS; +} + +vkm_result vkm_mouse_scroll(vkm_context *ctx, vkm_mouse_scroll_direction dir) +{ + if (ctx != DUMMY_CTX) { + return VKM_ERROR_INVALID_PARAM; + } + + if (dir >= VKM_MOUSE_SCROLL_MAX) { + return VKM_ERROR_INVALID_PARAM; + } + + return VKM_SUCCESS; +}