diff --git a/CMakeLists.txt b/CMakeLists.txt index d45c6877..73aa913f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,13 +28,24 @@ else() set(UIOHOOK_SOURCE_DIR "x11") endif() -add_library(uiohook - "src/logger.c" - "src/${UIOHOOK_SOURCE_DIR}/input_helper.c" - "src/${UIOHOOK_SOURCE_DIR}/input_hook.c" - "src/${UIOHOOK_SOURCE_DIR}/post_event.c" - "src/${UIOHOOK_SOURCE_DIR}/system_properties.c" -) +if (WIN32 OR WIN64) + add_library(uiohook + "src/logger.c" + "src/${UIOHOOK_SOURCE_DIR}/input_helper.c" + "src/${UIOHOOK_SOURCE_DIR}/input_hook.c" + "src/${UIOHOOK_SOURCE_DIR}/post_event.c" + "src/${UIOHOOK_SOURCE_DIR}/system_properties.c" + "src/${UIOHOOK_SOURCE_DIR}/monitor_helper.c" + ) +else() + add_library(uiohook + "src/logger.c" + "src/${UIOHOOK_SOURCE_DIR}/input_helper.c" + "src/${UIOHOOK_SOURCE_DIR}/input_hook.c" + "src/${UIOHOOK_SOURCE_DIR}/post_event.c" + "src/${UIOHOOK_SOURCE_DIR}/system_properties.c" + ) +endif () set_target_properties(uiohook PROPERTIES C_STANDARD 99 @@ -130,6 +141,10 @@ if(ENABLE_TEST) target_link_libraries(uiohook_tests uiohook "${CMAKE_THREAD_LIBS_INIT}") endif() +option(USE_EPOCH_TIME "Use Unix epoch time for event timestamps (default: OFF)" OFF) +if(USE_EPOCH_TIME) + add_compile_definitions(uiohook PRIVATE USE_EPOCH_TIME) +endif() if(UNIX AND NOT APPLE) find_package(PkgConfig REQUIRED) @@ -206,12 +221,6 @@ if(UNIX AND NOT APPLE) add_compile_definitions(uiohook PRIVATE USE_XRECORD_ASYNC) endif() - option(USE_XTEST "XTest API (default: ON)" ON) - if(USE_XTEST) - # XTest API is provided by Xtst - add_compile_definitions(uiohook PRIVATE USE_XTEST) - endif() - if(CMAKE_SYSTEM_NAME STREQUAL "Linux") option(USE_EVDEV "Generic Linux input driver (default: ON)" ON) if(USE_EVDEV) @@ -245,31 +254,13 @@ elseif(APPLE) target_link_libraries(uiohook "${IOKIT}") endif() - # FIXME Change USE_OBJC flag to USE_APPKIT - #option(USE_APPKIT "AppKit framework (default: ON)" ON) - option(USE_OBJC "Objective-C API (default: ON)" ON) - if(USE_OBJC) - # FIXME Drop USE_OBJC as it is included in AppKit - find_library(OBJC objc REQUIRED) - add_compile_definitions(USE_OBJC) - target_include_directories(uiohook PRIVATE "${OBJC}") - target_link_libraries(uiohook "${OBJC}") - + option(USE_APPKIT "AppKit framework (default: ON)" ON) + if(USE_APPKIT) find_library(APPKIT AppKit REQUIRED) add_compile_definitions(USE_APPKIT) target_include_directories(uiohook PRIVATE "${APPKIT}") target_link_libraries(uiohook "${APPKIT}") endif() - - option(USE_CARBON_LEGACY "Legacy Carbon framework functionality (default: OFF)" OFF) - if(USE_CARBON_LEGACY) - message(DEPRECATION "Legacy Carbon functionality has been deprecated.") - add_compile_definitions(USE_CARBON_LEGACY) - - if(USE_CARBON_LEGACY AND CMAKE_SIZEOF_VOID_P EQUAL 8) - message(WARNING "Legacy Carbon functionality should not be used with 64-bit targets.") - endif() - endif() elseif(WIN32) target_link_libraries(uiohook Advapi32) endif() diff --git a/README.md b/README.md index 220dbcaf..12d578f9 100644 --- a/README.md +++ b/README.md @@ -32,10 +32,10 @@ $ cmake --build . --parallel 2 --target install | __all__ | BUILD_DEMO:BOOL | demo applications | OFF | | | BUILD_SHARED_LIBS:BOOL | shared library | ON | | | ENABLE_TEST:BOOL | testing | OFF | +| | USE_EPOCH_TIME:BOOL | unix epch event times | OFF | | __OSX__ | USE_APPLICATION_SERVICES:BOOL | framework | ON | | | USE_IOKIT:BOOL | framework | ON | -| | USE_OBJC:BOOL | obj-c api | ON | -| | USE_CARBON_LEGACY:BOOL | legacy framework | OFF | +| | USE_APPKIT:BOOL | obj-c api | ON | | __Win32__ | | | | | __Linux__ | USE_EVDEV:BOOL | generic input driver | ON | | __*nix__ | USE_XF86MISC:BOOL | xfree86-misc extension | OFF | @@ -45,7 +45,6 @@ $ cmake --build . --parallel 2 --target install | | USE_XRANDR:BOOL | xrandt extension | OFF | | | USE_XRECORD_ASYNC:BOOL | xrecord async api | OFF | | | USE_XT:BOOL | x toolkit extension | ON | -| | USE_XTEST:BOOL | xtest extension | ON | ## Usage * [Hook Demo](demo/demo_hook.c) diff --git a/demo/demo_hook.c b/demo/demo_hook.c index ae2722bb..cdb190ac 100644 --- a/demo/demo_hook.c +++ b/demo/demo_hook.c @@ -28,26 +28,26 @@ #include #include -bool logger_proc(unsigned int level, const char *format, ...) { - bool status = false; - - va_list args; + +static void logger_proc(unsigned int level, void *user_data, const char *format, va_list args) { switch (level) { case LOG_LEVEL_INFO: - va_start(args, format); - status = vfprintf(stdout, format, args) >= 0; - va_end(args); + vfprintf(stdout, format, args); break; case LOG_LEVEL_WARN: case LOG_LEVEL_ERROR: - va_start(args, format); - status = vfprintf(stderr, format, args) >= 0; - va_end(args); + vfprintf(stderr, format, args); break; } - - return status; +} + +static void logger(unsigned int level, const char *format, ...) { + va_list args; + + va_start(args, format); + logger_proc(level, NULL, format, args); + va_end(args); } // NOTE: The following callback executes on the same thread that hook_run() is called @@ -56,7 +56,7 @@ bool logger_proc(unsigned int level, const char *format, ...) { // Furthermore, some operating systems may choose to disable your hook if it // takes too long to process. If you need to do any extended processing, please // do so by copying the event to your own queued dispatch thread. -void dispatch_proc(uiohook_event * const event) { +void dispatch_proc(uiohook_event * const event, void *user_data) { char buffer[256] = { 0 }; size_t length = snprintf(buffer, sizeof(buffer), "id=%i,when=%" PRIu64 ",mask=0x%X", @@ -74,18 +74,18 @@ void dispatch_proc(uiohook_event * const event) { // System level errors. case UIOHOOK_ERROR_OUT_OF_MEMORY: - logger_proc(LOG_LEVEL_ERROR, "Failed to allocate memory. (%#X)", status); + logger(LOG_LEVEL_ERROR, "Failed to allocate memory. (%#X)", status); break; case UIOHOOK_ERROR_X_RECORD_GET_CONTEXT: // NOTE This is the only platform specific error that occurs on hook_stop(). - logger_proc(LOG_LEVEL_ERROR, "Failed to get XRecord context. (%#X)", status); + logger(LOG_LEVEL_ERROR, "Failed to get XRecord context. (%#X)", status); break; // Default error. case UIOHOOK_FAILURE: default: - logger_proc(LOG_LEVEL_ERROR, "An unknown hook error occurred. (%#X)", status); + logger(LOG_LEVEL_ERROR, "An unknown hook error occurred. (%#X)", status); break; } } @@ -129,10 +129,10 @@ void dispatch_proc(uiohook_event * const event) { int main() { // Set the logger callback for library output. - hook_set_logger_proc(&logger_proc); + hook_set_logger_proc(&logger_proc, NULL); // Set the event callback for uiohook events. - hook_set_dispatch_proc(&dispatch_proc); + hook_set_dispatch_proc(&dispatch_proc, NULL); // Start the hook and block. // NOTE If EVENT_HOOK_ENABLED was delivered, the status will always succeed. @@ -144,63 +144,63 @@ int main() { // System level errors. case UIOHOOK_ERROR_OUT_OF_MEMORY: - logger_proc(LOG_LEVEL_ERROR, "Failed to allocate memory. (%#X)", status); + logger(LOG_LEVEL_ERROR, "Failed to allocate memory. (%#X)", status); break; // X11 specific errors. case UIOHOOK_ERROR_X_OPEN_DISPLAY: - logger_proc(LOG_LEVEL_ERROR, "Failed to open X11 display. (%#X)", status); + logger(LOG_LEVEL_ERROR, "Failed to open X11 display. (%#X)", status); break; case UIOHOOK_ERROR_X_RECORD_NOT_FOUND: - logger_proc(LOG_LEVEL_ERROR, "Unable to locate XRecord extension. (%#X)", status); + logger(LOG_LEVEL_ERROR, "Unable to locate XRecord extension. (%#X)", status); break; case UIOHOOK_ERROR_X_RECORD_ALLOC_RANGE: - logger_proc(LOG_LEVEL_ERROR, "Unable to allocate XRecord range. (%#X)", status); + logger(LOG_LEVEL_ERROR, "Unable to allocate XRecord range. (%#X)", status); break; case UIOHOOK_ERROR_X_RECORD_CREATE_CONTEXT: - logger_proc(LOG_LEVEL_ERROR, "Unable to allocate XRecord context. (%#X)", status); + logger(LOG_LEVEL_ERROR, "Unable to allocate XRecord context. (%#X)", status); break; case UIOHOOK_ERROR_X_RECORD_ENABLE_CONTEXT: - logger_proc(LOG_LEVEL_ERROR, "Failed to enable XRecord context. (%#X)", status); + logger(LOG_LEVEL_ERROR, "Failed to enable XRecord context. (%#X)", status); break; // Windows specific errors. case UIOHOOK_ERROR_SET_WINDOWS_HOOK_EX: - logger_proc(LOG_LEVEL_ERROR, "Failed to register low level windows hook. (%#X)", status); + logger(LOG_LEVEL_ERROR, "Failed to register low level windows hook. (%#X)", status); break; // Darwin specific errors. case UIOHOOK_ERROR_AXAPI_DISABLED: - logger_proc(LOG_LEVEL_ERROR, "Failed to enable access for assistive devices. (%#X)", status); + logger(LOG_LEVEL_ERROR, "Failed to enable access for assistive devices. (%#X)", status); break; case UIOHOOK_ERROR_CREATE_EVENT_PORT: - logger_proc(LOG_LEVEL_ERROR, "Failed to create apple event port. (%#X)", status); + logger(LOG_LEVEL_ERROR, "Failed to create apple event port. (%#X)", status); break; case UIOHOOK_ERROR_CREATE_RUN_LOOP_SOURCE: - logger_proc(LOG_LEVEL_ERROR, "Failed to create apple run loop source. (%#X)", status); + logger(LOG_LEVEL_ERROR, "Failed to create apple run loop source. (%#X)", status); break; case UIOHOOK_ERROR_GET_RUNLOOP: - logger_proc(LOG_LEVEL_ERROR, "Failed to acquire apple run loop. (%#X)", status); + logger(LOG_LEVEL_ERROR, "Failed to acquire apple run loop. (%#X)", status); break; case UIOHOOK_ERROR_CREATE_OBSERVER: - logger_proc(LOG_LEVEL_ERROR, "Failed to create apple run loop observer. (%#X)", status); + logger(LOG_LEVEL_ERROR, "Failed to create apple run loop observer. (%#X)", status); break; // Default error. case UIOHOOK_FAILURE: default: - logger_proc(LOG_LEVEL_ERROR, "An unknown hook error occurred. (%#X)", status); + logger(LOG_LEVEL_ERROR, "An unknown hook error occurred. (%#X)", status); break; } diff --git a/demo/demo_hook_async.c b/demo/demo_hook_async.c index b443cb55..096aad78 100644 --- a/demo/demo_hook_async.c +++ b/demo/demo_hook_async.c @@ -58,26 +58,25 @@ static pthread_cond_t hook_control_cond; #endif -bool logger_proc(unsigned int level, const char *format, ...) { - bool status = false; - - va_list args; +static void logger_proc(unsigned int level, void *user_data, const char *format, va_list args) { switch (level) { case LOG_LEVEL_INFO: - va_start(args, format); - status = vfprintf(stdout, format, args) >= 0; - va_end(args); + vfprintf(stdout, format, args); break; case LOG_LEVEL_WARN: case LOG_LEVEL_ERROR: - va_start(args, format); - status = vfprintf(stderr, format, args) >= 0; - va_end(args); + vfprintf(stderr, format, args); break; } - - return status; +} + +static void logger(unsigned int level, const char *format, ...) { + va_list args; + + va_start(args, format); + logger_proc(level, NULL, format, args); + va_end(args); } // NOTE: The following callback executes on the same thread that hook_run() is called @@ -86,7 +85,7 @@ bool logger_proc(unsigned int level, const char *format, ...) { // Furthermore, some operating systems may choose to disable your hook if it // takes too long to process. If you need to do any extended processing, please // do so by copying the event to your own queued dispatch thread. -void dispatch_proc(uiohook_event * const event) { +void dispatch_proc(uiohook_event * const event, void *user_data) { char buffer[256] = { 0 }; size_t length = snprintf(buffer, sizeof(buffer), "id=%i,when=%" PRIu64 ",mask=0x%X", @@ -143,18 +142,18 @@ void dispatch_proc(uiohook_event * const event) { // System level errors. case UIOHOOK_ERROR_OUT_OF_MEMORY: - logger_proc(LOG_LEVEL_ERROR, "Failed to allocate memory. (%#X)", status); + logger(LOG_LEVEL_ERROR, "Failed to allocate memory. (%#X)", status); break; case UIOHOOK_ERROR_X_RECORD_GET_CONTEXT: // NOTE This is the only platform specific error that occurs on hook_stop(). - logger_proc(LOG_LEVEL_ERROR, "Failed to get XRecord context. (%#X)", status); + logger(LOG_LEVEL_ERROR, "Failed to get XRecord context. (%#X)", status); break; // Default error. case UIOHOOK_FAILURE: default: - logger_proc(LOG_LEVEL_ERROR, "An unknown hook error occurred. (%#X)", status); + logger(LOG_LEVEL_ERROR, "An unknown hook error occurred. (%#X)", status); break; } } @@ -263,7 +262,7 @@ int hook_enable() { #if defined(_WIN32) // Attempt to set the thread priority to time critical. if (SetThreadPriority(hook_thread, THREAD_PRIORITY_TIME_CRITICAL) == 0) { - logger_proc(LOG_LEVEL_WARN, "%s [%u]: Could not set thread priority %li for thread %#p! (%#lX)\n", + logger(LOG_LEVEL_WARN, "%s [%u]: Could not set thread priority %li for thread %#p! (%#lX)\n", __FUNCTION__, __LINE__, (long) THREAD_PRIORITY_TIME_CRITICAL, hook_thread , (unsigned long) GetLastError()); } @@ -272,13 +271,13 @@ int hook_enable() { // use pthread_setschedparam instead. struct sched_param param = { .sched_priority = priority }; if (pthread_setschedparam(hook_thread, SCHED_OTHER, ¶m) != 0) { - logger_proc(LOG_LEVEL_WARN, "%s [%u]: Could not set thread priority %i for thread 0x%lX!\n", + logger(LOG_LEVEL_WARN, "%s [%u]: Could not set thread priority %i for thread 0x%lX!\n", __FUNCTION__, __LINE__, priority, (unsigned long) hook_thread); } #else // Raise the thread priority using glibc pthread_setschedprio. if (pthread_setschedprio(hook_thread, priority) != 0) { - logger_proc(LOG_LEVEL_WARN, "%s [%u]: Could not set thread priority %i for thread 0x%lX!\n", + logger(LOG_LEVEL_WARN, "%s [%u]: Could not set thread priority %i for thread 0x%lX!\n", __FUNCTION__, __LINE__, priority, (unsigned long) hook_thread); } #endif @@ -319,7 +318,7 @@ int hook_enable() { free(hook_thread_status); - logger_proc(LOG_LEVEL_DEBUG, "%s [%u]: Thread Result: (%#X).\n", + logger(LOG_LEVEL_DEBUG, "%s [%u]: Thread Result: (%#X).\n", __FUNCTION__, __LINE__, status); } else { @@ -352,10 +351,10 @@ int main() { #endif // Set the logger callback for library output. - hook_set_logger_proc(&logger_proc); + hook_set_logger_proc(&logger_proc, NULL); // Set the event callback for uiohook events. - hook_set_dispatch_proc(&dispatch_proc); + hook_set_dispatch_proc(&dispatch_proc, NULL); // Start the hook and block. // NOTE If EVENT_HOOK_ENABLED was delivered, the status will always succeed. @@ -377,63 +376,63 @@ int main() { // System level errors. case UIOHOOK_ERROR_OUT_OF_MEMORY: - logger_proc(LOG_LEVEL_ERROR, "Failed to allocate memory. (%#X)\n", status); + logger(LOG_LEVEL_ERROR, "Failed to allocate memory. (%#X)\n", status); break; // X11 specific errors. case UIOHOOK_ERROR_X_OPEN_DISPLAY: - logger_proc(LOG_LEVEL_ERROR, "Failed to open X11 display. (%#X)\n", status); + logger(LOG_LEVEL_ERROR, "Failed to open X11 display. (%#X)\n", status); break; case UIOHOOK_ERROR_X_RECORD_NOT_FOUND: - logger_proc(LOG_LEVEL_ERROR, "Unable to locate XRecord extension. (%#X)\n", status); + logger(LOG_LEVEL_ERROR, "Unable to locate XRecord extension. (%#X)\n", status); break; case UIOHOOK_ERROR_X_RECORD_ALLOC_RANGE: - logger_proc(LOG_LEVEL_ERROR, "Unable to allocate XRecord range. (%#X)\n", status); + logger(LOG_LEVEL_ERROR, "Unable to allocate XRecord range. (%#X)\n", status); break; case UIOHOOK_ERROR_X_RECORD_CREATE_CONTEXT: - logger_proc(LOG_LEVEL_ERROR, "Unable to allocate XRecord context. (%#X)\n", status); + logger(LOG_LEVEL_ERROR, "Unable to allocate XRecord context. (%#X)\n", status); break; case UIOHOOK_ERROR_X_RECORD_ENABLE_CONTEXT: - logger_proc(LOG_LEVEL_ERROR, "Failed to enable XRecord context. (%#X)\n", status); + logger(LOG_LEVEL_ERROR, "Failed to enable XRecord context. (%#X)\n", status); break; // Windows specific errors. case UIOHOOK_ERROR_SET_WINDOWS_HOOK_EX: - logger_proc(LOG_LEVEL_ERROR, "Failed to register low level windows hook. (%#X)\n", status); + logger(LOG_LEVEL_ERROR, "Failed to register low level windows hook. (%#X)\n", status); break; // Darwin specific errors. case UIOHOOK_ERROR_AXAPI_DISABLED: - logger_proc(LOG_LEVEL_ERROR, "Failed to enable access for assistive devices. (%#X)\n", status); + logger(LOG_LEVEL_ERROR, "Failed to enable access for assistive devices. (%#X)\n", status); break; case UIOHOOK_ERROR_CREATE_EVENT_PORT: - logger_proc(LOG_LEVEL_ERROR, "Failed to create apple event port. (%#X)\n", status); + logger(LOG_LEVEL_ERROR, "Failed to create apple event port. (%#X)\n", status); break; case UIOHOOK_ERROR_CREATE_RUN_LOOP_SOURCE: - logger_proc(LOG_LEVEL_ERROR, "Failed to create apple run loop source. (%#X)\n", status); + logger(LOG_LEVEL_ERROR, "Failed to create apple run loop source. (%#X)\n", status); break; case UIOHOOK_ERROR_GET_RUNLOOP: - logger_proc(LOG_LEVEL_ERROR, "Failed to acquire apple run loop. (%#X)\n", status); + logger(LOG_LEVEL_ERROR, "Failed to acquire apple run loop. (%#X)\n", status); break; case UIOHOOK_ERROR_CREATE_OBSERVER: - logger_proc(LOG_LEVEL_ERROR, "Failed to create apple run loop observer. (%#X)\n", status); + logger(LOG_LEVEL_ERROR, "Failed to create apple run loop observer. (%#X)\n", status); break; // Default error. case UIOHOOK_FAILURE: default: - logger_proc(LOG_LEVEL_ERROR, "An unknown hook error occurred. (%#X)\n", status); + logger(LOG_LEVEL_ERROR, "An unknown hook error occurred. (%#X)\n", status); break; } diff --git a/demo/demo_post.c b/demo/demo_post.c index 579741aa..32d05e55 100644 --- a/demo/demo_post.c +++ b/demo/demo_post.c @@ -35,33 +35,32 @@ static uiohook_event *event = NULL; -bool logger_proc(unsigned int level, const char *format, ...) { - bool status = false; - - va_list args; +static void logger_proc(unsigned int level, void *user_data, const char *format, va_list args) { switch (level) { case LOG_LEVEL_INFO: - va_start(args, format); - status = vfprintf(stdout, format, args) >= 0; - va_end(args); + vfprintf(stdout, format, args); break; case LOG_LEVEL_WARN: case LOG_LEVEL_ERROR: - va_start(args, format); - status = vfprintf(stderr, format, args) >= 0; - va_end(args); + vfprintf(stderr, format, args); break; } +} + +static void logger(unsigned int level, const char *format, ...) { + va_list args; - return status; + va_start(args, format); + logger_proc(level, NULL, format, args); + va_end(args); } // TODO Implement CLI options. //int main(int argc, char *argv[]) { int main() { // Set the logger callback for library output. - hook_set_logger_proc(&logger_proc); + hook_set_logger_proc(&logger_proc, NULL); // Allocate memory for the virtual events only once. event = (uiohook_event *) malloc(sizeof(uiohook_event)); diff --git a/demo/demo_properties.c b/demo/demo_properties.c index e40ce94b..c6acc066 100644 --- a/demo/demo_properties.c +++ b/demo/demo_properties.c @@ -25,90 +25,90 @@ #include #include -static bool logger_proc(unsigned int level, const char *format, ...) { - bool status = false; - - va_list args; + +static void logger_proc(unsigned int level, void *user_data, const char *format, va_list args) { switch (level) { case LOG_LEVEL_INFO: - va_start(args, format); - status = vfprintf(stdout, format, args) >= 0; - va_end(args); + vfprintf(stdout, format, args); break; case LOG_LEVEL_WARN: case LOG_LEVEL_ERROR: - va_start(args, format); - status = vfprintf(stderr, format, args) >= 0; - va_end(args); + vfprintf(stderr, format, args); break; } - - return status; +} + +static void logger(unsigned int level, const char *format, ...) { + va_list args; + + va_start(args, format); + logger_proc(level, NULL, format, args); + va_end(args); } int main() { // Disable the logger. - hook_set_logger_proc(&logger_proc); + hook_set_logger_proc(&logger_proc, NULL); // Retrieves current monitor layout and size. unsigned char count; screen_data* monitors = hook_create_screen_info(&count); - logger_proc(LOG_LEVEL_INFO, "Monitors Found:\t%u\n", count); + logger(LOG_LEVEL_INFO, "Monitors Found:\t%u\n", count); for (int i = 0; i < count; i++) { - logger_proc(LOG_LEVEL_INFO, "\t%3u) %4u x %-4u (%5d, %-5d)\n", + logger(LOG_LEVEL_INFO, "\t%3u) %4u x %-4u (%5d, %-5d)\n", monitors[i].number, monitors[i].width, monitors[i].height, monitors[i].x, monitors[i].y); } - logger_proc(LOG_LEVEL_INFO, "\n"); + logger(LOG_LEVEL_INFO, "\n"); // Retrieves the keyboard auto repeat rate. long int repeat_rate = hook_get_auto_repeat_rate(); if (repeat_rate >= 0) { - logger_proc(LOG_LEVEL_INFO, "Auto Repeat Rate:\t%ld\n", repeat_rate); + logger(LOG_LEVEL_INFO, "Auto Repeat Rate:\t%ld\n", repeat_rate); } else { - logger_proc(LOG_LEVEL_WARN, "Failed to acquire keyboard auto repeat rate!\n"); + logger(LOG_LEVEL_WARN, "Failed to acquire keyboard auto repeat rate!\n"); } // Retrieves the keyboard auto repeat delay. long int repeat_delay = hook_get_auto_repeat_delay(); if (repeat_delay >= 0) { - logger_proc(LOG_LEVEL_INFO, "Auto Repeat Delay:\t%ld\n", repeat_delay); + logger(LOG_LEVEL_INFO, "Auto Repeat Delay:\t%ld\n", repeat_delay); } else { - logger_proc(LOG_LEVEL_WARN, "Failed to acquire keyboard auto repeat delay!\n"); + logger(LOG_LEVEL_WARN, "Failed to acquire keyboard auto repeat delay!\n"); } // Retrieves the mouse acceleration multiplier. long int acceleration_multiplier = hook_get_pointer_acceleration_multiplier(); if (acceleration_multiplier >= 0) { - logger_proc(LOG_LEVEL_INFO, "Mouse Acceleration Multiplier:\t%ld\n", acceleration_multiplier); + logger(LOG_LEVEL_INFO, "Mouse Acceleration Multiplier:\t%ld\n", acceleration_multiplier); } else { - logger_proc(LOG_LEVEL_WARN, "Failed to acquire mouse acceleration multiplier!\n"); + logger(LOG_LEVEL_WARN, "Failed to acquire mouse acceleration multiplier!\n"); } // Retrieves the mouse acceleration threshold. long int acceleration_threshold = hook_get_pointer_acceleration_threshold(); if (acceleration_threshold >= 0) { - logger_proc(LOG_LEVEL_INFO, "Mouse Acceleration Threshold:\t%ld\n", acceleration_threshold); + logger(LOG_LEVEL_INFO, "Mouse Acceleration Threshold:\t%ld\n", acceleration_threshold); } else { - logger_proc(LOG_LEVEL_WARN, "Failed to acquire mouse acceleration threshold!\n"); + logger(LOG_LEVEL_WARN, "Failed to acquire mouse acceleration threshold!\n"); } // Retrieves the mouse sensitivity. long int sensitivity = hook_get_pointer_sensitivity(); if (sensitivity >= 0) { - logger_proc(LOG_LEVEL_INFO, "Mouse Sensitivity:\t%ld\n", sensitivity); + logger(LOG_LEVEL_INFO, "Mouse Sensitivity:\t%ld\n", sensitivity); } else { - logger_proc(LOG_LEVEL_WARN, "Failed to acquire mouse sensitivity value!\n"); + logger(LOG_LEVEL_WARN, "Failed to acquire mouse sensitivity value!\n"); } // Retrieves the double/triple click interval. long int click_time = hook_get_multi_click_time(); if (click_time >= 0) { - logger_proc(LOG_LEVEL_INFO, "Multi-Click Time:\t%ld\n", click_time); + logger(LOG_LEVEL_INFO, "Multi-Click Time:\t%ld\n", click_time); } else { - logger_proc(LOG_LEVEL_WARN, "Failed to acquire mouse multi-click time!\n"); + logger(LOG_LEVEL_WARN, "Failed to acquire mouse multi-click time!\n"); } return EXIT_SUCCESS; diff --git a/include/uiohook.h b/include/uiohook.h index b132c935..909acb9f 100644 --- a/include/uiohook.h +++ b/include/uiohook.h @@ -41,6 +41,7 @@ // Windows specific errors. #define UIOHOOK_ERROR_SET_WINDOWS_HOOK_EX 0x30 #define UIOHOOK_ERROR_GET_MODULE_HANDLE 0x31 +#define UIOHOOK_ERROR_CREATE_INVISIBLE_WINDOW 0x32 // Darwin specific errors. #define UIOHOOK_ERROR_AXAPI_DISABLED 0x40 @@ -59,7 +60,7 @@ typedef enum _log_level { } log_level; // Logger callback function prototype. -typedef bool (*logger_t)(unsigned int, const char *, ...); +typedef void (*logger_t)(unsigned int, void *, const char *, va_list); /* End Log Levels and Function Prototype */ /* Begin Virtual Event Types and Data Structures */ @@ -126,7 +127,7 @@ typedef struct _uiohook_event { } data; } uiohook_event; -typedef void (*dispatcher_t)(uiohook_event *const); +typedef void (*dispatcher_t)(uiohook_event * const, void *); /* End Virtual Event Types and Data Structures */ @@ -414,14 +415,14 @@ typedef void (*dispatcher_t)(uiohook_event *const); extern "C" { #endif - // Set the logger callback functions. - UIOHOOK_API void hook_set_logger_proc(logger_t logger_proc); - - // Send a virtual event back to the system. - UIOHOOK_API void hook_post_event(uiohook_event * const event); + // Set the logger callback function. + UIOHOOK_API void hook_set_logger_proc(logger_t logger_proc, void *user_data); // Set the event callback function. - UIOHOOK_API void hook_set_dispatch_proc(dispatcher_t dispatch_proc); + UIOHOOK_API void hook_set_dispatch_proc(dispatcher_t dispatch_proc, void *user_data); + + // Send a virtual event back to the system. + UIOHOOK_API int hook_post_event(uiohook_event * const event); // Insert the event hook. UIOHOOK_API int hook_run(); diff --git a/src/darwin/input_helper.c b/src/darwin/input_helper.c index df9c0b93..1bbd417f 100644 --- a/src/darwin/input_helper.c +++ b/src/darwin/input_helper.c @@ -28,15 +28,11 @@ #include "input_helper.h" #include "logger.h" +#ifdef USE_APPLICATION_SERVICES // Current dead key state. -#if defined(USE_CARBON_LEGACY) || defined(USE_APPLICATION_SERVICES) static UInt32 deadkey_state; -#endif // Input source data for the keyboard. -#if defined(USE_CARBON_LEGACY) -static KeyboardLayoutRef prev_keyboard_layout = NULL; -#elif defined(USE_APPLICATION_SERVICES) static TISInputSourceRef prev_keyboard_layout = NULL; #endif @@ -78,7 +74,7 @@ bool is_accessibility_enabled() { } logger(LOG_LEVEL_DEBUG, "%s [%u]: AXIsProcessTrustedWithOptions not found.\n", - __FUNCTION__, __LINE__); + __FUNCTION__, __LINE__); logger(LOG_LEVEL_DEBUG, "%s [%u]: Falling back to AXAPIEnabled().\n", __FUNCTION__, __LINE__); @@ -104,14 +100,7 @@ UniCharCount keycode_to_unicode(CGEventRef event_ref, UniChar *buffer, UniCharCo UniCharCount count = 0; CFDataRef inputData = NULL; - #if defined(USE_CARBON_LEGACY) - KeyboardLayoutRef curr_keyboard_layout; - if (KLGetCurrentKeyboardLayout(&curr_keyboard_layout) == noErr) { - if (KLGetKeyboardLayoutProperty(curr_keyboard_layout, kKLuchrData, (const void **) &inputData) != noErr) { - inputData = NULL; - } - } - #elif defined(USE_APPLICATION_SERVICES) + #ifdef USE_APPLICATION_SERVICES // TODO Try https://developer.apple.com/documentation/coregraphics/1456120-cgeventkeyboardgetunicodestring?language=objc if (CFEqual(CFRunLoopGetCurrent(), CFRunLoopGetMain())) { // NOTE The following block must execute on the main runloop, @@ -144,13 +133,9 @@ UniCharCount keycode_to_unicode(CGEventRef event_ref, UniChar *buffer, UniCharCo } #endif - #if defined(USE_CARBON_LEGACY) || defined(USE_APPLICATION_SERVICES) + #ifdef USE_APPLICATION_SERVICES if (inputData != NULL) { - #ifdef USE_CARBON_LEGACY - const UCKeyboardLayout *keyboard_layout = (const UCKeyboardLayout *) inputData; - #else const UCKeyboardLayout *keyboard_layout = (const UCKeyboardLayout*) CFDataGetBytePtr(inputData); - #endif if (keyboard_layout != NULL) { //Extract keycode and modifier information. @@ -518,14 +503,14 @@ UInt64 scancode_to_keycode(uint16_t scancode) { } void load_input_helper() { - #if defined(USE_CARBON_LEGACY) || defined(USE_APPLICATION_SERVICES) + #ifdef USE_APPLICATION_SERVICES // Start with a fresh dead key state. //curr_deadkey_state = 0; #endif } void unload_input_helper() { - #if defined(USE_CARBON_LEGACY) || defined(USE_APPLICATION_SERVICES) + #ifdef USE_APPLICATION_SERVICES if (prev_keyboard_layout != NULL) { // Cleanup tracking of the previous layout. CFRelease(prev_keyboard_layout); diff --git a/src/darwin/input_hook.c b/src/darwin/input_hook.c index 875deb62..d2a4a54f 100644 --- a/src/darwin/input_hook.c +++ b/src/darwin/input_hook.c @@ -19,7 +19,7 @@ #include #include -#ifdef USE_OBJC +#ifdef USE_APPKIT #include #include #endif @@ -32,6 +32,11 @@ #include "input_helper.h" #include "logger.h" +#ifdef USE_EPOCH_TIME +#define TIMER_RESOLUTION_MS 1 +#else +#define TIMER_RESOLUTION_MS 1000000 +#endif typedef struct _event_runloop_info { CFMachPortRef port; @@ -39,7 +44,7 @@ typedef struct _event_runloop_info { CFRunLoopObserverRef observer; } event_runloop_info; -#ifdef USE_OBJC +#ifdef USE_APPKIT static id auto_release_pool; typedef struct { @@ -75,7 +80,7 @@ typedef void* dispatch_queue_t; static struct dispatch_queue_s *dispatch_main_queue_s; static void (*dispatch_sync_f_f)(dispatch_queue_t, void *, void (*function)(void *)); -#if !defined(USE_CARBON_LEGACY) && defined(USE_APPLICATION_SERVICES) +#if defined(USE_APPLICATION_SERVICES) typedef struct _main_runloop_info { CFRunLoopSourceRef source; CFRunLoopObserverRef observer; @@ -93,29 +98,33 @@ static CGEventTimestamp click_time = 0; static unsigned short int click_button = MOUSE_NOBUTTON; static bool mouse_dragged = false; +#ifdef USE_EPOCH_TIME // Structure for the current Unix epoch in milliseconds. static struct timeval system_time; +#endif // Virtual event pointer. static uiohook_event event; // Event dispatch callback. -static dispatcher_t dispatcher = NULL; +static dispatcher_t dispatch = NULL; +static void *dispatch_data = NULL; -UIOHOOK_API void hook_set_dispatch_proc(dispatcher_t dispatch_proc) { +UIOHOOK_API void hook_set_dispatch_proc(dispatcher_t dispatch_proc, void *user_data) { logger(LOG_LEVEL_DEBUG, "%s [%u]: Setting new dispatch callback to %#p.\n", __FUNCTION__, __LINE__, dispatch_proc); - dispatcher = dispatch_proc; + dispatch = dispatch_proc; + dispatch_data = user_data; } // Send out an event if a dispatcher was set. -static inline void dispatch_event(uiohook_event *const event) { - if (dispatcher != NULL) { +static void dispatch_event(uiohook_event *const event) { + if (dispatch != NULL) { logger(LOG_LEVEL_DEBUG, "%s [%u]: Dispatching event type %u.\n", __FUNCTION__, __LINE__, event->type); - dispatcher(event); + dispatch(event, dispatch_data); } else { logger(LOG_LEVEL_WARN, "%s [%u]: No dispatch callback set!\n", __FUNCTION__, __LINE__); @@ -206,8 +215,24 @@ static void keycode_to_lookup(void *info) { } } +#ifdef USE_EPOCH_TIME +static uint64_t get_unix_timestamp() { + // Get the local system time in UTC. + gettimeofday(&system_time, NULL); + + // Convert the local system time to a Unix epoch in MS. + uint64_t timestamp = (system_time.tv_sec * 1000) + (system_time.tv_usec / 1000); + + return timestamp; +} +#endif + static void hook_status_proc(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) { + #ifdef USE_EPOCH_TIME + uint64_t timestamp = get_unix_timestamp(); + #else uint64_t timestamp = mach_absolute_time(); + #endif switch (activity) { case kCFRunLoopEntry: @@ -278,7 +303,7 @@ static inline void process_key_pressed(uint64_t timestamp, CGEventRef event_ref) __FUNCTION__, __LINE__); (*dispatch_sync_f_f)(dispatch_main_queue_s, tis_keycode_message, &keycode_to_lookup); } - #if !defined(USE_CARBON_LEGACY) && defined(USE_APPLICATION_SERVICES) + #ifdef USE_APPLICATION_SERVICES else if (!is_runloop_main) { logger(LOG_LEVEL_DEBUG, "%s [%u]: Using CFRunLoopWakeUp for key typed events.\n", __FUNCTION__, __LINE__); @@ -479,7 +504,7 @@ static inline void process_modifier_changed(uint64_t timestamp, CGEventRef event } } -#ifdef USE_OBJC +#ifdef USE_APPKIT static void obcj_message(void *info) { TISEventMessage *data = (TISEventMessage *) info; @@ -502,7 +527,7 @@ static inline void process_system_key(uint64_t timestamp, CGEventRef event_ref) UInt32 subtype = 0; UInt32 data1 = 0; - #ifdef USE_OBJC + #ifdef USE_APPKIT bool is_runloop_main = CFEqual(event_loop, CFRunLoopGetMain()); tis_event_message->event = event_ref; tis_event_message->subtype = 0; @@ -555,7 +580,7 @@ static inline void process_system_key(uint64_t timestamp, CGEventRef event_ref) free(buffer); CFRelease(data_ref); - #ifdef USE_OBJC + #ifdef USE_APPKIT } #endif @@ -685,7 +710,7 @@ static inline void process_system_key(uint64_t timestamp, CGEventRef event_ref) static inline void process_button_pressed(uint64_t timestamp, CGEventRef event_ref, uint16_t button) { // Track the number of clicks. - if (button == click_button && (long int) (timestamp - click_time) / 1000000 <= hook_get_multi_click_time()) { + if (button == click_button && (long int) (timestamp - click_time) / TIMER_RESOLUTION_MS <= hook_get_multi_click_time()) { if (click_count < USHRT_MAX) { click_count++; } @@ -771,7 +796,7 @@ static inline void process_button_released(uint64_t timestamp, CGEventRef event_ } // Reset the number of clicks. - if ((long int) (timestamp - click_time) / 1000000 > hook_get_multi_click_time()) { + if ((long int) (timestamp - click_time) / TIMER_RESOLUTION_MS > hook_get_multi_click_time()) { // Reset the click count. click_count = 0; } @@ -779,7 +804,7 @@ static inline void process_button_released(uint64_t timestamp, CGEventRef event_ static inline void process_mouse_moved(uint64_t timestamp, CGEventRef event_ref) { // Reset the click count. - if (click_count != 0 && (long int) (timestamp - click_time) / 1000000 > hook_get_multi_click_time()) { + if (click_count != 0 && (long int) (timestamp - click_time) / TIMER_RESOLUTION_MS > hook_get_multi_click_time()) { click_count = 0; } @@ -885,11 +910,11 @@ static inline void process_mouse_wheel(uint64_t timestamp, CGEventRef event_ref) } CGEventRef hook_event_proc(CGEventTapProxy tap_proxy, CGEventType type, CGEventRef event_ref, void *refcon) { - // Get the local system time in UTC. - gettimeofday(&system_time, NULL); - - // Grab the native event timestamp for use later.. + #ifdef USE_EPOCH_TIME + uint64_t timestamp = get_unix_timestamp(); + #else uint64_t timestamp = (uint64_t) CGEventGetTimestamp(event_ref); + #endif // Get the event class. switch (type) { @@ -1012,7 +1037,7 @@ CGEventRef hook_event_proc(CGEventTapProxy tap_proxy, CGEventType type, CGEventR } -#if !defined(USE_CARBON_LEGACY) && defined(USE_APPLICATION_SERVICES) +#ifdef USE_APPLICATION_SERVICES void main_runloop_status_proc(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) { switch (activity) { case kCFRunLoopExit: @@ -1293,16 +1318,16 @@ UIOHOOK_API int hook_run() { tis_keycode_message = (TISKeycodeMessage *) calloc(1, sizeof(TISKeycodeMessage)); if (tis_keycode_message == NULL) { logger(LOG_LEVEL_ERROR, "%s [%u]: Failed to allocate memory for TIS message structure!\n", - __FUNCTION__, __LINE__); + __FUNCTION__, __LINE__); return UIOHOOK_ERROR_OUT_OF_MEMORY; } - #ifdef USE_OBJC + #ifdef USE_APPKIT tis_event_message = (TISEventMessage *) calloc(1, sizeof(TISEventMessage)); if (tis_event_message == NULL) { logger(LOG_LEVEL_ERROR, "%s [%u]: Failed to allocate memory for TIS event structure!\n", - __FUNCTION__, __LINE__); + __FUNCTION__, __LINE__); return UIOHOOK_ERROR_OUT_OF_MEMORY; } @@ -1332,7 +1357,7 @@ UIOHOOK_API int hook_run() { logger(LOG_LEVEL_DEBUG, "%s [%u]: Failed to locate dispatch_sync_f() or dispatch_get_main_queue()!\n", __FUNCTION__, __LINE__); - #if !defined(USE_CARBON_LEGACY) && defined(USE_APPLICATION_SERVICES) + #ifdef USE_APPLICATION_SERVICES logger(LOG_LEVEL_DEBUG, "%s [%u]: Falling back to runloop signaling.\n", __FUNCTION__, __LINE__); @@ -1359,7 +1384,7 @@ UIOHOOK_API int hook_run() { } } - #ifdef USE_OBJC + #ifdef USE_APPKIT // Contributed by Alex // Create a garbage collector to handle Cocoa events correctly. Class NSAutoreleasePool_class = (Class) objc_getClass("NSAutoreleasePool"); @@ -1374,12 +1399,12 @@ UIOHOOK_API int hook_run() { CFRunLoopRun(); - #ifdef USE_OBJC + #ifdef USE_APPKIT // Contributed by Alex eventWithoutCGEvent(auto_release_pool, sel_registerName("release")); #endif - #if !defined(USE_CARBON_LEGACY) && defined(USE_APPLICATION_SERVICES) + #ifdef USE_APPLICATION_SERVICES pthread_mutex_lock(&main_runloop_mutex); if (!CFEqual(event_loop, CFRunLoopGetMain())) { if (dispatch_sync_f_f == NULL || dispatch_main_queue_s == NULL) { @@ -1389,7 +1414,7 @@ UIOHOOK_API int hook_run() { pthread_mutex_unlock(&main_runloop_mutex); #endif - #ifdef USE_OBJC + #ifdef USE_APPKIT free(tis_event_message); #endif free(tis_keycode_message); diff --git a/src/darwin/post_event.c b/src/darwin/post_event.c index 8bf8508c..59b34ebd 100644 --- a/src/darwin/post_event.c +++ b/src/darwin/post_event.c @@ -81,7 +81,7 @@ static int post_key_event(uiohook_event * const event, CGEventSourceRef src) { } } else { logger(LOG_LEVEL_DEBUG, "%s [%u]: Invalid event for keyboard post event: %#X.\n", - __FUNCTION__, __LINE__, event->type); + __FUNCTION__, __LINE__, event->type); return UIOHOOK_FAILURE; } @@ -126,7 +126,6 @@ static int post_key_event(uiohook_event * const event, CGEventSourceRef src) { if (cg_event == NULL) { logger(LOG_LEVEL_ERROR, "%s [%u]: CGEventCreateKeyboardEvent failed!\n", __FUNCTION__, __LINE__); - return UIOHOOK_ERROR_OUT_OF_MEMORY; } @@ -197,7 +196,7 @@ static int post_mouse_event(uiohook_event * const event, CGEventSourceRef src) { default: logger(LOG_LEVEL_DEBUG, "%s [%u]: Invalid mouse event: %#X.\n", - __FUNCTION__, __LINE__, event->type); + __FUNCTION__, __LINE__, event->type); return UIOHOOK_FAILURE; } @@ -257,25 +256,21 @@ static int post_mouse_wheel_event(uiohook_event * const event, CGEventSourceRef return UIOHOOK_SUCCESS; } - -// TODO This should return a status code, UIOHOOK_SUCCESS or otherwise. -UIOHOOK_API void hook_post_event(uiohook_event * const event) { - int status = UIOHOOK_FAILURE; - +UIOHOOK_API int hook_post_event(uiohook_event * const event) { CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); if (src == NULL) { logger(LOG_LEVEL_ERROR, "%s [%u]: CGEventSourceCreate failed!\n", __FUNCTION__, __LINE__); - return; // UIOHOOK_ERROR_OUT_OF_MEMORY + return UIOHOOK_ERROR_OUT_OF_MEMORY; } + int status = UIOHOOK_FAILURE; switch (event->type) { case EVENT_KEY_PRESSED: case EVENT_KEY_RELEASED: status = post_key_event(event, src); break; - case EVENT_MOUSE_PRESSED: case EVENT_MOUSE_RELEASED: @@ -296,8 +291,11 @@ UIOHOOK_API void hook_post_event(uiohook_event * const event) { default: logger(LOG_LEVEL_DEBUG, "%s [%u]: Ignoring post event: %#X.\n", - __FUNCTION__, __LINE__, event->type); + __FUNCTION__, __LINE__, event->type); + status = UIOHOOK_FAILURE; } CFRelease(src); + + return status; } diff --git a/src/darwin/system_properties.c b/src/darwin/system_properties.c index 74a1fe0d..57b87c12 100644 --- a/src/darwin/system_properties.c +++ b/src/darwin/system_properties.c @@ -16,10 +16,6 @@ * along with this program. If not, see . */ -#ifdef USE_CARBON_LEGACY -#include -#endif - #if defined(USE_APPLICATION_SERVICES) || defined(USE_IOKIT) #include #endif @@ -147,7 +143,7 @@ UIOHOOK_API screen_data* hook_create_screen_info(unsigned char *count) { * CharSec = 66 / (MS / 15) */ UIOHOOK_API long int hook_get_auto_repeat_rate() { - #if defined(USE_APPLICATION_SERVICES) || defined(USE_IOKIT) || defined(USE_CARBON_LEGACY) + #if defined(USE_APPLICATION_SERVICES) || defined(USE_IOKIT) bool successful = false; SInt64 rate; #endif @@ -207,29 +203,11 @@ UIOHOOK_API long int hook_get_auto_repeat_rate() { } #endif - #ifdef USE_CARBON_LEGACY - if (!successful) { - // Apple documentation states that value is in 'ticks'. I am not sure - // what that means, but it looks a lot like the arbitrary slider value. - rate = LMGetKeyRepThresh(); - if (rate > -1) { - /* This is the slider value, we must multiply by 15 to convert to - * milliseconds. - */ - value = (long) rate * 15; - successful = true; - - logger(LOG_LEVEL_DEBUG, "%s [%u]: LMGetKeyRepThresh: %li.\n", - __FUNCTION__, __LINE__, value); - } - } - #endif - return value; } UIOHOOK_API long int hook_get_auto_repeat_delay() { - #if defined(USE_APPLICATION_SERVICES) || defined(USE_IOKIT) || defined(USE_CARBON_LEGACY) + #if defined(USE_APPLICATION_SERVICES) || defined(USE_IOKIT) bool successful = false; SInt64 delay; #endif @@ -288,23 +266,6 @@ UIOHOOK_API long int hook_get_auto_repeat_delay() { } #endif - #ifdef USE_CARBON_LEGACY - if (!successful) { - // Apple documentation states that value is in 'ticks'. I am not sure - // what that means, but it looks a lot like the arbitrary slider value. - delay = LMGetKeyThresh(); - if (delay > -1) { - // This is the slider value, we must multiply by 15 to convert to - // milliseconds. - value = (long) delay * 15; - successful = true; - - logger(LOG_LEVEL_DEBUG, "%s [%u]: LMGetKeyThresh: %li.\n", - __FUNCTION__, __LINE__, value); - } - } - #endif - return value; } @@ -378,7 +339,7 @@ UIOHOOK_API long int hook_get_pointer_sensitivity() { } UIOHOOK_API long int hook_get_multi_click_time() { - #if defined(USE_APPLICATION_SERVICES) || defined(USE_IOKIT) || defined(USE_CARBON_LEGACY) + #if defined(USE_APPLICATION_SERVICES) || defined(USE_IOKIT) bool successful = false; Float64 time; #endif @@ -425,23 +386,6 @@ UIOHOOK_API long int hook_get_multi_click_time() { } #endif - #ifdef USE_CARBON_LEGACY - if (!successful) { - // Apple documentation states that value is in 'ticks'. I am not sure - // what that means, but it looks a lot like the arbitrary slider value. - time = GetDblTime(); - if (time > -1) { - // This is the slider value, we must multiply by 15 to convert to - // milliseconds. - value = (long) time * 15; - successful = true; - - logger(LOG_LEVEL_DEBUG, "%s [%u]: GetDblTime: %li.\n", - __FUNCTION__, __LINE__, value); - } - } - #endif - return value; } diff --git a/src/logger.c b/src/logger.c index 28dc9a98..34c06009 100644 --- a/src/logger.c +++ b/src/logger.c @@ -24,17 +24,20 @@ #include "logger.h" -static bool default_logger(unsigned int level, const char *format, ...) { - return false; -} +static logger_t callback = NULL; +static void *callback_data = NULL; -// Current logger function pointer, should never be null. -logger_t logger = &default_logger; +void logger(unsigned int level, const char *format, ...) { + if (callback != NULL) { + va_list args; -UIOHOOK_API void hook_set_logger_proc(logger_t logger_proc) { - if (logger_proc == NULL) { - logger = &default_logger; - } else { - logger = logger_proc; + va_start(args, format); + callback(level, callback_data, format, args); + va_end(args); } } + +UIOHOOK_API void hook_set_logger_proc(logger_t logger_proc, void *user_data) { + callback = logger_proc; + callback_data = user_data; +} diff --git a/src/logger.h b/src/logger.h index 8a64c467..1e1f5201 100644 --- a/src/logger.h +++ b/src/logger.h @@ -26,7 +26,6 @@ #define __FUNCTION__ __func__ #endif -// logger(level, message) -extern logger_t logger; +extern void logger(unsigned int level, const char *format, ...); #endif diff --git a/src/windows/input_helper.c b/src/windows/input_helper.c index 82429165..928f29ce 100644 --- a/src/windows/input_helper.c +++ b/src/windows/input_helper.c @@ -479,8 +479,7 @@ static int refresh_locale_list() { int new_size = GetKeyboardLayoutList(hkl_size, hkl_list); if (new_size > 0) { if (new_size != hkl_size) { - logger(LOG_LEVEL_WARN, "%s [%u]: Locale size mismatch! " - "Expected %i, received %i!\n", + logger(LOG_LEVEL_WARN, "%s [%u]: Locale size mismatch! Expected %i, received %i!\n", __FUNCTION__, __LINE__, hkl_size, new_size); } else { logger(LOG_LEVEL_DEBUG, "%s [%u]: Received %i locales.\n", @@ -612,8 +611,7 @@ static int refresh_locale_list() { count++; } else { - logger(LOG_LEVEL_ERROR, - "%s [%u]: GetProcAddress() failed for KbdLayerDescriptor!\n", + logger(LOG_LEVEL_ERROR, "%s [%u]: GetProcAddress() failed for KbdLayerDescriptor!\n", __FUNCTION__, __LINE__); FreeLibrary(locale_item->library); @@ -621,20 +619,17 @@ static int refresh_locale_list() { locale_item = NULL; } } else { - logger(LOG_LEVEL_ERROR, - "%s [%u]: GetSystemDirectory() failed!\n", + logger(LOG_LEVEL_ERROR, "%s [%u]: GetSystemDirectory() failed!\n", __FUNCTION__, __LINE__); } } else { - logger(LOG_LEVEL_ERROR, - "%s [%u]: Could not find keyboard map for locale %#p!\n", + logger(LOG_LEVEL_ERROR, "%s [%u]: Could not find keyboard map for locale %#p!\n", __FUNCTION__, __LINE__, hkl_list[i]); } } } } else { - logger(LOG_LEVEL_ERROR, - "%s [%u]: GetKeyboardLayoutList() failed!\n", + logger(LOG_LEVEL_ERROR, "%s [%u]: GetKeyboardLayoutList() failed!\n", __FUNCTION__, __LINE__); // TODO Try and recover by using the current layout. @@ -670,8 +665,7 @@ SIZE_T keycode_to_unicode(DWORD keycode, PWCHAR buffer, SIZE_T size) { // You may already be a winner! if (locale_item != NULL && locale_item->id == locale_id) { - logger(LOG_LEVEL_DEBUG, - "%s [%u]: Activating keyboard layout %#p.\n", + logger(LOG_LEVEL_DEBUG, "%s [%u]: Activating keyboard layout %#p.\n", __FUNCTION__, __LINE__, locale_item->id); // Switch the current locale. @@ -682,8 +676,7 @@ SIZE_T keycode_to_unicode(DWORD keycode, PWCHAR buffer, SIZE_T size) { // This is consistent with the way Windows handles locale changes. deadChar = WCH_NONE; } else { - logger(LOG_LEVEL_DEBUG, - "%s [%u]: Refreshing locale cache.\n", + logger(LOG_LEVEL_DEBUG, "%s [%u]: Refreshing locale cache.\n", __FUNCTION__, __LINE__); refresh_locale_list(); @@ -696,8 +689,7 @@ SIZE_T keycode_to_unicode(DWORD keycode, PWCHAR buffer, SIZE_T size) { // Check and make sure the Unicode helper was loaded. if (locale_current != NULL) { - logger(LOG_LEVEL_DEBUG, - "%s [%u]: Using keyboard layout %#p.\n", + logger(LOG_LEVEL_DEBUG, "%s [%u]: Using keyboard layout %#p.\n", __FUNCTION__, __LINE__, locale_current->id); int mod = 0; @@ -855,8 +847,7 @@ int load_input_helper() { count = refresh_locale_list(); - logger(LOG_LEVEL_DEBUG, - "%s [%u]: refresh_locale_list() found %i locale(s).\n", + logger(LOG_LEVEL_DEBUG, "%s [%u]: refresh_locale_list() found %i locale(s).\n", __FUNCTION__, __LINE__, count); return count; diff --git a/src/windows/input_hook.c b/src/windows/input_hook.c index b9e9161a..8e8725aa 100644 --- a/src/windows/input_hook.c +++ b/src/windows/input_hook.c @@ -22,11 +22,13 @@ #include "input_helper.h" #include "logger.h" +#include "monitor_helper.h" // Thread and hook handles. static DWORD hook_thread_id = 0; static HHOOK keyboard_event_hhook = NULL, mouse_event_hhook = NULL; static HWINEVENTHOOK win_event_hhook = NULL; +static HWND hWnd = NULL; // The handle to the DLL module pulled in DllMain on DLL_PROCESS_ATTACH. extern HINSTANCE hInst; @@ -34,9 +36,14 @@ extern HINSTANCE hInst; // Modifiers for tracking key masks. static unsigned short int current_modifiers = 0x0000; +#ifdef USE_EPOCH_TIME +// Structure for the current Unix epoch in milliseconds. +static FILETIME system_time; +#endif + // Click count globals. static unsigned short click_count = 0; -static DWORD click_time = 0; +static uint64_t click_time = 0; static unsigned short int click_button = MOUSE_NOBUTTON; static POINT last_click; @@ -44,22 +51,24 @@ static POINT last_click; static uiohook_event event; // Event dispatch callback. -static dispatcher_t dispatcher = NULL; +static dispatcher_t dispatch = NULL; +static void *dispatch_data = NULL; -UIOHOOK_API void hook_set_dispatch_proc(dispatcher_t dispatch_proc) { +UIOHOOK_API void hook_set_dispatch_proc(dispatcher_t dispatch_proc, void *user_data) { logger(LOG_LEVEL_DEBUG, "%s [%u]: Setting new dispatch callback to %#p.\n", __FUNCTION__, __LINE__, dispatch_proc); - dispatcher = dispatch_proc; + dispatch = dispatch_proc; + dispatch_data = user_data; } // Send out an event if a dispatcher was set. -static inline void dispatch_event(uiohook_event *const event) { - if (dispatcher != NULL) { +static void dispatch_event(uiohook_event *const event) { + if (dispatch != NULL) { logger(LOG_LEVEL_DEBUG, "%s [%u]: Dispatching event type %u.\n", __FUNCTION__, __LINE__, event->type); - dispatcher(event); + dispatch(event, dispatch_data); } else { logger(LOG_LEVEL_WARN, "%s [%u]: No dispatch callback set!\n", __FUNCTION__, __LINE__); @@ -142,6 +151,22 @@ static uint16_t get_scroll_wheel_amount() { return value; } +#ifdef USE_EPOCH_TIME +static inline uint64_t get_unix_timestamp() { + // Get the local system time in UTC. + GetSystemTimeAsFileTime(&system_time); + + // Convert the local system time to a Unix epoch in MS. + // milliseconds = 100-nanoseconds / 10000 + uint64_t timestamp = (((uint64_t) system_time.dwHighDateTime << 32) | system_time.dwLowDateTime) / 10000; + + // Convert Windows epoch to Unix epoch. (1970 - 1601 in milliseconds) + timestamp -= 11644473600000; + + return timestamp; +} +#endif + void unregister_running_hooks() { // Stop the event hook and any timer still running. if (win_event_hhook != NULL) { @@ -166,7 +191,11 @@ void hook_start_proc() { load_input_helper(); // Get the local system time in UNIX epoch form. + #ifdef USE_EPOCH_TIME + uint64_t timestamp = get_unix_timestamp(); + #else uint64_t timestamp = GetMessageTime(); + #endif // Populate the hook start event. event.time = timestamp; @@ -181,7 +210,11 @@ void hook_start_proc() { void hook_stop_proc() { // Get the local system time in UNIX epoch form. + #ifdef USE_EPOCH_TIME + uint64_t timestamp = get_unix_timestamp(); + #else uint64_t timestamp = GetMessageTime(); + #endif // Populate the hook stop event. event.time = timestamp; @@ -198,6 +231,12 @@ void hook_stop_proc() { } static void process_key_pressed(KBDLLHOOKSTRUCT *kbhook) { + #ifdef USE_EPOCH_TIME + uint64_t timestamp = get_unix_timestamp(); + #else + uint64_t timestamp = kbhook->time; + #endif + // Check and setup modifiers. if (kbhook->vkCode == VK_LSHIFT) { set_modifier_mask(MASK_SHIFT_L); } else if (kbhook->vkCode == VK_RSHIFT) { set_modifier_mask(MASK_SHIFT_R); } @@ -212,7 +251,7 @@ static void process_key_pressed(KBDLLHOOKSTRUCT *kbhook) { else if (kbhook->vkCode == VK_SCROLL) { set_modifier_mask(MASK_SCROLL_LOCK); } // Populate key pressed event. - event.time = kbhook->time; + event.time = timestamp; event.reserved = 0x00; event.type = EVENT_KEY_PRESSED; @@ -237,7 +276,7 @@ static void process_key_pressed(KBDLLHOOKSTRUCT *kbhook) { SIZE_T count = keycode_to_unicode(kbhook->vkCode, buffer, sizeof(buffer)); for (unsigned int i = 0; i < count; i++) { // Populate key typed event. - event.time = kbhook->time; + event.time = timestamp; event.reserved = 0x00; event.type = EVENT_KEY_TYPED; @@ -257,6 +296,12 @@ static void process_key_pressed(KBDLLHOOKSTRUCT *kbhook) { } static void process_key_released(KBDLLHOOKSTRUCT *kbhook) { + #ifdef USE_EPOCH_TIME + uint64_t timestamp = get_unix_timestamp(); + #else + uint64_t timestamp = kbhook->time; + #endif + // Check and setup modifiers. if (kbhook->vkCode == VK_LSHIFT) { unset_modifier_mask(MASK_SHIFT_L); } else if (kbhook->vkCode == VK_RSHIFT) { unset_modifier_mask(MASK_SHIFT_R); } @@ -271,7 +316,7 @@ static void process_key_released(KBDLLHOOKSTRUCT *kbhook) { else if (kbhook->vkCode == VK_SCROLL) { unset_modifier_mask(MASK_SCROLL_LOCK); } // Populate key pressed event. - event.time = kbhook->time; + event.time = timestamp; event.reserved = 0x00; event.type = EVENT_KEY_RELEASED; @@ -321,7 +366,11 @@ LRESULT CALLBACK keyboard_hook_event_proc(int nCode, WPARAM wParam, LPARAM lPara static void process_button_pressed(MSLLHOOKSTRUCT *mshook, uint16_t button) { - DWORD timestamp = mshook->time; + #ifdef USE_EPOCH_TIME + uint64_t timestamp = get_unix_timestamp(); + #else + uint64_t timestamp = mshook->time; + #endif // Track the number of clicks, the button must match the previous button. if (button == click_button && (long int) (timestamp - click_time) <= hook_get_multi_click_time()) { @@ -368,8 +417,14 @@ static void process_button_pressed(MSLLHOOKSTRUCT *mshook, uint16_t button) { } static void process_button_released(MSLLHOOKSTRUCT *mshook, uint16_t button) { + #ifdef USE_EPOCH_TIME + uint64_t timestamp = get_unix_timestamp(); + #else + uint64_t timestamp = mshook->time; + #endif + // Populate mouse released event. - event.time = mshook->time; + event.time = timestamp; event.reserved = 0x00; event.type = EVENT_MOUSE_RELEASED; @@ -392,7 +447,7 @@ static void process_button_released(MSLLHOOKSTRUCT *mshook, uint16_t button) { // If the pressed event was not consumed... if (event.reserved ^ 0x01 && last_click.x == mshook->pt.x && last_click.y == mshook->pt.y) { // Populate mouse clicked event. - event.time = mshook->time; + event.time = timestamp; event.reserved = 0x00; event.type = EVENT_MOUSE_CLICKED; @@ -412,14 +467,18 @@ static void process_button_released(MSLLHOOKSTRUCT *mshook, uint16_t button) { } // Reset the number of clicks. - if (button == click_button && (long int) (event.time - click_time) > hook_get_multi_click_time()) { + if (button == click_button && (long int) (timestamp - click_time) > hook_get_multi_click_time()) { // Reset the click count. click_count = 0; } } static void process_mouse_moved(MSLLHOOKSTRUCT *mshook) { + #ifdef USE_EPOCH_TIME + uint64_t timestamp = get_unix_timestamp(); + #else uint64_t timestamp = mshook->time; + #endif // We received a mouse move event with the mouse actually moving. // This verifies that the mouse was moved after being depressed. @@ -460,13 +519,19 @@ static void process_mouse_moved(MSLLHOOKSTRUCT *mshook) { } static void process_mouse_wheel(MSLLHOOKSTRUCT *mshook, uint8_t direction) { + #ifdef USE_EPOCH_TIME + uint64_t timestamp = get_unix_timestamp(); + #else + uint64_t timestamp = mshook->time; + #endif + // Track the number of clicks. // Reset the click count and previous button. click_count = 1; click_button = MOUSE_NOBUTTON; // Populate mouse wheel event. - event.time = mshook->time; + event.time = timestamp; event.reserved = 0x00; event.type = EVENT_MOUSE_WHEEL; @@ -658,6 +723,60 @@ void CALLBACK win_hook_event_proc(HWINEVENTHOOK hook, DWORD event, HWND hWnd, LO } } +static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) + { + case WM_DESTROY: + PostQuitMessage(0); + break; + case WM_DISPLAYCHANGE: + enumerate_displays(); + break; + default: + return DefWindowProc(hwnd, message, wParam, lParam); + } + return 0; +} + +static int create_invisible_window() +{ + WNDCLASSEX wcex = { 0 }; + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.style = WS_EX_NOACTIVATE; + wcex.lpfnWndProc = WndProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = hInst; + wcex.hIcon = NULL; + wcex.hCursor = NULL; + wcex.hbrBackground = NULL; + wcex.lpszMenuName = NULL; + wcex.lpszClassName = "Empty"; + wcex.hIconSm = NULL; + + if (!RegisterClassEx(&wcex)) return 0; + + hWnd = CreateWindowEx( + WS_EX_NOACTIVATE, + "Empty", + "Empty", + WS_DISABLED, + 0, + 0, + 1, + 1, + NULL, + NULL, + hInst, + NULL + ); + if (!hWnd) return 0; + + ShowWindow(hWnd, SW_HIDE); + + return 1; +} UIOHOOK_API int hook_run() { int status = UIOHOOK_FAILURE; @@ -683,6 +802,15 @@ UIOHOOK_API int hook_run() { } } + // Create invisible window to receive monitor change events + if(!create_invisible_window() || hWnd == NULL) + { + logger(LOG_LEVEL_ERROR, "%s [%u]: Create invisible window failed! (%#lX)\n", + __FUNCTION__, __LINE__, (unsigned long) GetLastError()); + + status = UIOHOOK_ERROR_CREATE_INVISIBLE_WINDOW; + } + // Create the native hooks. keyboard_event_hhook = SetWindowsHookEx(WH_KEYBOARD_LL, keyboard_hook_event_proc, hInst, 0); mouse_event_hhook = SetWindowsHookEx(WH_MOUSE_LL, mouse_hook_event_proc, hInst, 0); diff --git a/src/windows/monitor_helper.c b/src/windows/monitor_helper.c new file mode 100644 index 00000000..e18486a2 --- /dev/null +++ b/src/windows/monitor_helper.c @@ -0,0 +1,37 @@ +#include "monitor_helper.h" + +static LONG left = 0; +static LONG top = 0; + +static BOOL CALLBACK enum_monitor_proc(HMONITOR h_monitor, HDC hdc, LPRECT lp_rect, LPARAM dwData) { + MONITORINFO MonitorInfo = {0}; + MonitorInfo.cbSize = sizeof(MonitorInfo); + if (GetMonitorInfo(h_monitor, &MonitorInfo)) { + if (MonitorInfo.rcMonitor.left < left) { + left = MonitorInfo.rcMonitor.left; + } + if (MonitorInfo.rcMonitor.top < top) { + top = MonitorInfo.rcMonitor.top; + } + } + return TRUE; +} + +void enumerate_displays() +{ + // Reset coordinates because if a negative monitor moves to positive space, + // it will still look like there is some monitor in negative space. + left = 0; + top = 0; + + EnumDisplayMonitors(NULL, NULL, enum_monitor_proc, 0); +} + +LARGESTNEGATIVECOORDINATES get_largest_negative_coordinates() +{ + LARGESTNEGATIVECOORDINATES lnc = { + .left = left, + .top = top + }; + return lnc; +} \ No newline at end of file diff --git a/src/windows/monitor_helper.h b/src/windows/monitor_helper.h new file mode 100644 index 00000000..7c6d01a4 --- /dev/null +++ b/src/windows/monitor_helper.h @@ -0,0 +1,10 @@ +#include + +typedef struct { + LONG left; + LONG top; +} LARGESTNEGATIVECOORDINATES; + +extern void enumerate_displays(); + +extern LARGESTNEGATIVECOORDINATES get_largest_negative_coordinates(); diff --git a/src/windows/post_event.c b/src/windows/post_event.c index 275b67af..d00117bb 100644 --- a/src/windows/post_event.c +++ b/src/windows/post_event.c @@ -22,6 +22,7 @@ #include "input_helper.h" #include "logger.h" +#include "monitor_helper.h" // Some buggy versions of MinGW and MSys do not include these constants in winuser.h. #ifndef MAPVK_VK_TO_VSC @@ -64,11 +65,35 @@ static const uint16_t extend_key_table[10] = { VK_DELETE }; +typedef struct { + LONG x; + LONG y; +} normalized_coordinate; -static LONG convert_to_relative_position(int coordinate, int screen_size) { - // See https://stackoverflow.com/a/4555214 and its comments - int offset = (coordinate > 0 ? 1 : -1); // Negative coordinates appear when using multiple monitors - return ((coordinate * MAX_WINDOWS_COORD_VALUE) / screen_size) + offset; +static LONG get_absolute_coordinate(LONG coordinate, int screen_size) { + return MulDiv((int) coordinate, MAX_WINDOWS_COORD_VALUE, screen_size); +} + +static normalized_coordinate normalize_coordinates(LONG x, LONG y, int screen_width, int screen_height, LARGESTNEGATIVECOORDINATES lnc) { + x += abs(lnc.left); + y += abs(lnc.top); + + // Prevent clicking 0 coordinates to prevent monitor flicker + if (x == 0) + { + x++; + } + if (y == 0) + { + y++; + } + + normalized_coordinate nc = { + .x = get_absolute_coordinate(x, screen_width), + .y = get_absolute_coordinate(y, screen_height) + }; + + return nc; } static int map_keyboard_event(uiohook_event * const event, INPUT * const input) { @@ -87,7 +112,7 @@ static int map_keyboard_event(uiohook_event * const event, INPUT * const input) default: logger(LOG_LEVEL_DEBUG, "%s [%u]: Invalid event for keyboard event mapping: %#X.\n", - __FUNCTION__, __LINE__, event->type); + __FUNCTION__, __LINE__, event->type); return UIOHOOK_FAILURE; } @@ -110,17 +135,20 @@ static int map_keyboard_event(uiohook_event * const event, INPUT * const input) } static int map_mouse_event(uiohook_event * const event, INPUT * const input) { - // FIXME implement multiple monitor support - uint16_t screen_width = GetSystemMetrics(SM_CXSCREEN); - uint16_t screen_height = GetSystemMetrics(SM_CYSCREEN); + uint16_t screen_width = GetSystemMetrics(SM_CXVIRTUALSCREEN); + uint16_t screen_height = GetSystemMetrics(SM_CYVIRTUALSCREEN); input->type = INPUT_MOUSE; input->mi.mouseData = 0; input->mi.dwExtraInfo = 0; input->mi.time = 0; // GetSystemTime(); - input->mi.dx = convert_to_relative_position(event->data.mouse.x, screen_width); - input->mi.dy = convert_to_relative_position(event->data.mouse.y, screen_height); + LARGESTNEGATIVECOORDINATES lnc = get_largest_negative_coordinates(); + + normalized_coordinate nc = normalize_coordinates(event->data.mouse.x, event->data.mouse.y, screen_width, screen_height, lnc); + + input->mi.dx = nc.x; + input->mi.dy = nc.y; switch (event->type) { case EVENT_MOUSE_PRESSED: @@ -195,24 +223,22 @@ static int map_mouse_event(uiohook_event * const event, INPUT * const input) { default: logger(LOG_LEVEL_DEBUG, "%s [%u]: Invalid event for mouse event mapping: %#X.\n", - __FUNCTION__, __LINE__, event->type); + __FUNCTION__, __LINE__, event->type); return UIOHOOK_FAILURE; } return UIOHOOK_SUCCESS; } -// TODO This should return a status code, UIOHOOK_SUCCESS or otherwise. -UIOHOOK_API void hook_post_event(uiohook_event * const event) { - int status = UIOHOOK_FAILURE; - +UIOHOOK_API int hook_post_event(uiohook_event * const event) { INPUT *input = (INPUT *) calloc(1, sizeof(INPUT)) ; if (input == NULL) { logger(LOG_LEVEL_ERROR, "%s [%u]: failed to allocate memory: calloc!\n", __FUNCTION__, __LINE__); - return; // UIOHOOK_ERROR_OUT_OF_MEMORY + return UIOHOOK_ERROR_OUT_OF_MEMORY; } + int status = UIOHOOK_FAILURE; switch (event->type) { case EVENT_KEY_PRESSED: case EVENT_KEY_RELEASED: @@ -235,14 +261,17 @@ UIOHOOK_API void hook_post_event(uiohook_event * const event) { default: logger(LOG_LEVEL_DEBUG, "%s [%u]: Ignoring post event: %#X.\n", - __FUNCTION__, __LINE__, event->type); - + __FUNCTION__, __LINE__, event->type); + status = UIOHOOK_FAILURE; } - if (status != UIOHOOK_FAILURE && !SendInput(1, input, sizeof(INPUT))) { + if (status == UIOHOOK_SUCCESS && !SendInput(1, input, sizeof(INPUT))) { logger(LOG_LEVEL_ERROR, "%s [%u]: SendInput() failed! (%#lX)\n", __FUNCTION__, __LINE__, (unsigned long) GetLastError()); + status = UIOHOOK_FAILURE; } free(input); + + return status; } diff --git a/src/windows/system_properties.c b/src/windows/system_properties.c index fd351ce7..dd3d53cd 100644 --- a/src/windows/system_properties.c +++ b/src/windows/system_properties.c @@ -126,7 +126,7 @@ UIOHOOK_API long int hook_get_auto_repeat_rate() { if (SystemParametersInfo(SPI_GETKEYBOARDSPEED, 0, &rate, 0)) { logger(LOG_LEVEL_DEBUG, "%s [%u]: SPI_GETKEYBOARDSPEED: %li.\n", - __FUNCTION__, __LINE__, rate); + __FUNCTION__, __LINE__, rate); value = rate; } @@ -140,7 +140,7 @@ UIOHOOK_API long int hook_get_auto_repeat_delay() { if (SystemParametersInfo(SPI_GETKEYBOARDDELAY, 0, &delay, 0)) { logger(LOG_LEVEL_DEBUG, "%s [%u]: SPI_GETKEYBOARDDELAY: %li.\n", - __FUNCTION__, __LINE__, delay); + __FUNCTION__, __LINE__, delay); value = delay; } @@ -154,7 +154,7 @@ UIOHOOK_API long int hook_get_pointer_acceleration_multiplier() { if (SystemParametersInfo(SPI_GETMOUSE, 0, &mouse, 0)) { logger(LOG_LEVEL_DEBUG, "%s [%u]: SPI_GETMOUSE[2]: %i.\n", - __FUNCTION__, __LINE__, mouse[2]); + __FUNCTION__, __LINE__, mouse[2]); value = mouse[2]; } @@ -168,9 +168,9 @@ UIOHOOK_API long int hook_get_pointer_acceleration_threshold() { if (SystemParametersInfo(SPI_GETMOUSE, 0, &mouse, 0)) { logger(LOG_LEVEL_DEBUG, "%s [%u]: SPI_GETMOUSE[0]: %i.\n", - __FUNCTION__, __LINE__, mouse[0]); + __FUNCTION__, __LINE__, mouse[0]); logger(LOG_LEVEL_DEBUG, "%s [%u]: SPI_GETMOUSE[1]: %i.\n", - __FUNCTION__, __LINE__, mouse[1]); + __FUNCTION__, __LINE__, mouse[1]); // Average the x and y thresholds. value = (mouse[0] + mouse[1]) / 2; @@ -185,7 +185,7 @@ UIOHOOK_API long int hook_get_pointer_sensitivity() { if (SystemParametersInfo(SPI_GETMOUSESPEED, 0, &sensitivity, 0)) { logger(LOG_LEVEL_DEBUG, "%s [%u]: SPI_GETMOUSESPEED: %i.\n", - __FUNCTION__, __LINE__, sensitivity); + __FUNCTION__, __LINE__, sensitivity); value = sensitivity; } diff --git a/src/x11/input_helper.c b/src/x11/input_helper.c index a947029a..d076e629 100644 --- a/src/x11/input_helper.c +++ b/src/x11/input_helper.c @@ -1768,11 +1768,11 @@ unsigned int button_map_lookup(unsigned int button) { } } else { logger(LOG_LEVEL_WARN, "%s [%u]: Mouse button map memory is unavailable!\n", - __FUNCTION__, __LINE__); + __FUNCTION__, __LINE__); } } else { logger(LOG_LEVEL_WARN, "%s [%u]: XDisplay helper_disp is unavailable!\n", - __FUNCTION__, __LINE__); + __FUNCTION__, __LINE__); } // X11 numbers buttons 2 & 3 backwards from other platforms so we normalize them. @@ -1787,7 +1787,7 @@ void load_input_helper() { mouse_button_map = malloc(sizeof(unsigned char) * BUTTON_MAP_MAX); if (mouse_button_map == NULL) { logger(LOG_LEVEL_ERROR, "%s [%u]: Failed to allocate memory for mouse button map!\n", - __FUNCTION__, __LINE__); + __FUNCTION__, __LINE__); //return UIOHOOK_ERROR_OUT_OF_MEMORY; } diff --git a/src/x11/input_hook.c b/src/x11/input_hook.c index 3d009906..6a627493 100644 --- a/src/x11/input_hook.c +++ b/src/x11/input_hook.c @@ -26,6 +26,10 @@ #include #include +#ifdef USE_EPOCH_TIME +#include +#endif + #include #include @@ -96,26 +100,33 @@ typedef union { static struct xkb_state *state = NULL; #endif +#ifdef USE_EPOCH_TIME +// Structure for the current Unix epoch in milliseconds. +static struct timeval system_time; +#endif + // Virtual event pointer. static uiohook_event event; // Event dispatch callback. -static dispatcher_t dispatcher = NULL; +static dispatcher_t dispatch = NULL; +static void *dispatch_data = NULL; -UIOHOOK_API void hook_set_dispatch_proc(dispatcher_t dispatch_proc) { +UIOHOOK_API void hook_set_dispatch_proc(dispatcher_t dispatch_proc, void *user_data) { logger(LOG_LEVEL_DEBUG, "%s [%u]: Setting new dispatch callback to %#p.\n", __FUNCTION__, __LINE__, dispatch_proc); - dispatcher = dispatch_proc; + dispatch = dispatch_proc; + dispatch_data = user_data; } // Send out an event if a dispatcher was set. -static inline void dispatch_event(uiohook_event *const event) { - if (dispatcher != NULL) { +static void dispatch_event(uiohook_event *const event) { + if (dispatch != NULL) { logger(LOG_LEVEL_DEBUG, "%s [%u]: Dispatching event type %u.\n", __FUNCTION__, __LINE__, event->type); - dispatcher(event); + dispatch(event, dispatch_data); } else { logger(LOG_LEVEL_WARN, "%s [%u]: No dispatch callback set!\n", __FUNCTION__, __LINE__); @@ -192,7 +203,7 @@ static void initialize_modifiers() { char keymap[32]; XQueryKeymap(hook->ctrl.display, keymap); - Window unused_win; + Window unused_win; int unused_int; unsigned int mask; if (XQueryPointer(hook->ctrl.display, DefaultRootWindow(hook->ctrl.display), &unused_win, &unused_win, &unused_int, &unused_int, &unused_int, &unused_int, &mask)) { @@ -251,8 +262,24 @@ static void initialize_modifiers() { initialize_locks(); } +#ifdef USE_EPOCH_TIME +static inline uint64_t get_unix_timestamp() { + // Get the local system time in UTC. + gettimeofday(&system_time, NULL); + + // Convert the local system time to a Unix epoch in MS. + uint64_t timestamp = (system_time.tv_sec * 1000) + (system_time.tv_usec / 1000); + + return timestamp; +} +#endif + void hook_event_proc(XPointer closeure, XRecordInterceptData *recorded_data) { + #ifdef USE_EPOCH_TIME + uint64_t timestamp = get_unix_timestamp(); + #else uint64_t timestamp = (uint64_t) recorded_data->server_time; + #endif if (recorded_data->category == XRecordStartOfData) { // Initialize native input helper functions. @@ -866,7 +893,7 @@ static inline int xrecord_block() { #endif else { logger(LOG_LEVEL_ERROR, "%s [%u]: XRecordEnableContext failure!\n", - __FUNCTION__, __LINE__); + __FUNCTION__, __LINE__); #ifdef USE_XRECORD_ASYNC // Reset the running state. @@ -1043,7 +1070,7 @@ UIOHOOK_API int hook_run() { hook = malloc(sizeof(hook_info)); if (hook == NULL) { logger(LOG_LEVEL_ERROR, "%s [%u]: Failed to allocate memory for hook structure!\n", - __FUNCTION__, __LINE__); + __FUNCTION__, __LINE__); return UIOHOOK_ERROR_OUT_OF_MEMORY; } diff --git a/src/x11/post_event.c b/src/x11/post_event.c index 70e12b61..92aa2fa0 100644 --- a/src/x11/post_event.c +++ b/src/x11/post_event.c @@ -22,16 +22,11 @@ #include #include #include -#ifdef USE_XTEST #include -#endif #include "input_helper.h" #include "logger.h" -#ifndef USE_XTEST -static long current_modifier_mask = NoEventMask; -#endif static int post_key_event(uiohook_event * const event) { KeyCode keycode = scancode_to_keycode(event->data.keyboard.keycode); @@ -41,114 +36,22 @@ static int post_key_event(uiohook_event * const event) { return UIOHOOK_FAILURE; } - #ifdef USE_XTEST Bool is_pressed; - #else - long event_mask; - - XKeyEvent key_event = { - .serial = 0x00, - .time = CurrentTime, - .same_screen = True, - .send_event = False, - .display = helper_disp, - - .root = XDefaultRootWindow(helper_disp), - .window = None, - .subwindow = None, - - .x_root = 0, - .y_root = 0, - .x = 0, - .y = 0, - - .state = current_modifier_mask, - .keycode = keycode - }; - - int revert; - XGetInputFocus(helper_disp, &(key_event.window), &revert); - #endif - if (event->type == EVENT_KEY_PRESSED) { - #ifdef USE_XTEST is_pressed = True; - #else - key_event.type = KeyPress; - event_mask = KeyPressMask; - - switch (event->data.keyboard.keycode) { - case VC_SHIFT_L: - case VC_SHIFT_R: - current_modifier_mask |= ShiftMask; - break; - - case VC_CONTROL_L: - case VC_CONTROL_R: - current_modifier_mask |= ControlMask; - break; - - case VC_META_L: - case VC_META_R: - current_modifier_mask |= Mod4Mask; - break; - - case VC_ALT_L: - case VC_ALT_R: - current_modifier_mask |= Mod1Mask; - break; - } - #endif - } else if (event->type == EVENT_KEY_RELEASED) { - #ifdef USE_XTEST is_pressed = False; - #else - key_event.type = KeyRelease; - event_mask = KeyReleaseMask; - - switch (event->data.keyboard.keycode) { - case VC_SHIFT_L: - case VC_SHIFT_R: - current_modifier_mask &= ~ShiftMask; - break; - - case VC_CONTROL_L: - case VC_CONTROL_R: - current_modifier_mask &= ~ControlMask; - break; - - case VC_META_L: - case VC_META_R: - current_modifier_mask &= ~Mod4Mask; - break; - - case VC_ALT_L: - case VC_ALT_R: - current_modifier_mask &= ~Mod1Mask; - break; - } - #endif } else { logger(LOG_LEVEL_DEBUG, "%s [%u]: Invalid event for keyboard post event: %#X.\n", - __FUNCTION__, __LINE__, event->type); + __FUNCTION__, __LINE__, event->type); return UIOHOOK_FAILURE; } - #ifdef USE_XTEST if (XTestFakeKeyEvent(helper_disp, keycode, is_pressed, 0) != Success) { logger(LOG_LEVEL_ERROR, "%s [%u]: XTestFakeKeyEvent() failed!\n", - __FUNCTION__, __LINE__, event->type); - return UIOHOOK_FAILURE; - } - #else - XSelectInput(helper_disp, key_event.window, KeyPressMask | KeyReleaseMask); - if (XSendEvent(helper_disp, key_event.window, False, event_mask, (XEvent *) &key_event) == 0) { - logger(LOG_LEVEL_ERROR, "%s [%u]: XSendEvent() failed!\n", - __FUNCTION__, __LINE__, event->type); + __FUNCTION__, __LINE__, event->type); return UIOHOOK_FAILURE; } - #endif return UIOHOOK_SUCCESS; } @@ -159,254 +62,138 @@ static int post_mouse_button_event(uiohook_event * const event) { .send_event = False, .display = helper_disp, - .window = None, /* “event” window it is reported relative to */ - .root = None, /* root window that the event occurred on */ - .subwindow = XDefaultRootWindow(helper_disp), /* child window */ + .window = None, /* “event” window it is reported relative to */ + .root = None, /* root window that the event occurred on */ + .subwindow = XDefaultRootWindow(helper_disp), /* child window */ .time = CurrentTime, - .x = event->data.mouse.x, /* pointer x, y coordinates in event window */ + .x = event->data.mouse.x, /* pointer x, y coordinates in event window */ .y = event->data.mouse.y, - .x_root = 0, /* coordinates relative to root */ + .x_root = 0, /* coordinates relative to root */ .y_root = 0, - .state = 0x00, /* key or button mask */ + .state = 0x00, /* key or button mask */ .same_screen = True }; // Move the pointer to the specified position. - #ifdef USE_XTEST XTestFakeMotionEvent(btn_event.display, -1, btn_event.x, btn_event.y, 0); - #else - XWarpPointer(btn_event.display, None, btn_event.subwindow, 0, 0, 0, 0, btn_event.x, btn_event.y); - XFlush(btn_event.display); - #endif - - #ifndef USE_XTEST - // FIXME This is still not working correctly, clicking on other windows does not yield focus. - while (btn_event.subwindow != None) - { - btn_event.window = btn_event.subwindow; - XQueryPointer ( - btn_event.display, - btn_event.window, - &btn_event.root, - &btn_event.subwindow, - &btn_event.x_root, - &btn_event.y_root, - &btn_event.x, - &btn_event.y, - &btn_event.state - ); - } - #endif + int status = UIOHOOK_FAILURE; switch (event->type) { case EVENT_MOUSE_PRESSED: - #ifdef USE_XTEST if (event->data.mouse.button < MOUSE_BUTTON1 || event->data.mouse.button > MOUSE_BUTTON5) { logger(LOG_LEVEL_WARN, "%s [%u]: Invalid button specified for mouse pressed event! (%u)\n", __FUNCTION__, __LINE__, event->data.mouse.button); return UIOHOOK_FAILURE; } - XTestFakeButtonEvent(helper_disp, event->data.mouse.button, True, 0); - #else - if (event->data.mouse.button == MOUSE_BUTTON1) { - current_modifier_mask |= Button1MotionMask; - } else if (event->data.mouse.button == MOUSE_BUTTON2) { - current_modifier_mask |= Button2MotionMask; - } else if (event->data.mouse.button == MOUSE_BUTTON3) { - current_modifier_mask |= Button3MotionMask; - } else if (event->data.mouse.button == MOUSE_BUTTON4) { - current_modifier_mask |= Button4MotionMask; - } else if (event->data.mouse.button == MOUSE_BUTTON5) { - current_modifier_mask |= Button5MotionMask; - } else { - logger(LOG_LEVEL_WARN, "%s [%u]: Invalid button specified for mouse pressed event! (%u)\n", - __FUNCTION__, __LINE__, event->data.mouse.button); - return UIOHOOK_FAILURE; + if (XTestFakeButtonEvent(helper_disp, event->data.mouse.button, True, 0) != 0) { + status = UIOHOOK_SUCCESS; } - - btn_event.type = ButtonPress; - btn_event.button = event->data.mouse.button; - btn_event.state = current_modifier_mask; - XSendEvent(helper_disp, btn_event.window, False, ButtonPressMask, (XEvent *) &btn_event); - #endif break; case EVENT_MOUSE_RELEASED: - #ifdef USE_XTEST if (event->data.mouse.button < MOUSE_BUTTON1 || event->data.mouse.button > MOUSE_BUTTON5) { logger(LOG_LEVEL_WARN, "%s [%u]: Invalid button specified for mouse released event! (%u)\n", __FUNCTION__, __LINE__, event->data.mouse.button); return UIOHOOK_FAILURE; } - XTestFakeButtonEvent(helper_disp, event->data.mouse.button, False, 0); - #else - if (event->data.mouse.button == MOUSE_BUTTON1) { - current_modifier_mask &= ~Button1MotionMask; - } else if (event->data.mouse.button == MOUSE_BUTTON2) { - current_modifier_mask &= ~Button2MotionMask; - } else if (event->data.mouse.button == MOUSE_BUTTON3) { - current_modifier_mask &= ~Button3MotionMask; - } else if (event->data.mouse.button == MOUSE_BUTTON4) { - current_modifier_mask &= ~Button4MotionMask; - } else if (event->data.mouse.button == MOUSE_BUTTON5) { - current_modifier_mask &= ~Button5MotionMask; - } else { - logger(LOG_LEVEL_WARN, "%s [%u]: Invalid button specified for mouse released event! (%u)\n", - __FUNCTION__, __LINE__, event->data.mouse.button); - return UIOHOOK_FAILURE; + if (XTestFakeButtonEvent(helper_disp, event->data.mouse.button, False, 0) != 0) { + status = UIOHOOK_SUCCESS; } - - btn_event.type = ButtonRelease; - btn_event.button = event->data.mouse.button; - btn_event.state = current_modifier_mask; - XSendEvent(helper_disp, btn_event.window, False, ButtonReleaseMask, (XEvent *) &btn_event); - #endif break; default: logger(LOG_LEVEL_DEBUG, "%s [%u]: Invalid mouse button event: %#X.\n", - __FUNCTION__, __LINE__, event->type); - return UIOHOOK_FAILURE; + __FUNCTION__, __LINE__, event->type); + status = UIOHOOK_FAILURE; } - return UIOHOOK_SUCCESS; + return status; } static int post_mouse_wheel_event(uiohook_event * const event) { + int status = UIOHOOK_FAILURE; + XButtonEvent btn_event = { .serial = 0, .send_event = False, .display = helper_disp, - .window = None, /* “event” window it is reported relative to */ - .root = None, /* root window that the event occurred on */ - .subwindow = XDefaultRootWindow(helper_disp), /* child window */ + .window = None, /* “event” window it is reported relative to */ + .root = None, /* root window that the event occurred on */ + .subwindow = XDefaultRootWindow(helper_disp), /* child window */ .time = CurrentTime, - .x = event->data.wheel.x, /* pointer x, y coordinates in event window */ + .x = event->data.wheel.x, /* pointer x, y coordinates in event window */ .y = event->data.wheel.y, - .x_root = 0, /* coordinates relative to root */ + .x_root = 0, /* coordinates relative to root */ .y_root = 0, - .state = 0x00, /* key or button mask */ + .state = 0x00, /* key or button mask */ .same_screen = True }; - #ifndef USE_XTEST - // FIXME This is still not working correctly, clicking on other windows does not yield focus. - while (btn_event.subwindow != None) - { - btn_event.window = btn_event.subwindow; - XQueryPointer ( - btn_event.display, - btn_event.window, - &btn_event.root, - &btn_event.subwindow, - &btn_event.x_root, - &btn_event.y_root, - &btn_event.x, - &btn_event.y, - &btn_event.state - ); - } - #endif - // Wheel events should be the same as click events on X11. // type, amount and rotation unsigned int button = button_map_lookup(event->data.wheel.rotation < 0 ? WheelUp : WheelDown); - #ifdef USE_XTEST - XTestFakeButtonEvent(helper_disp, button, True, 0); - #else - btn_event.type = ButtonPress; - btn_event.button = button; - btn_event.state = current_modifier_mask; - XSendEvent(helper_disp, btn_event.window, False, ButtonPressMask, (XEvent *) &btn_event); - #endif - - #ifdef USE_XTEST - XTestFakeButtonEvent(helper_disp, button, False, 0); - #else - btn_event.type = ButtonRelease; - btn_event.button = button; - btn_event.state = current_modifier_mask; - XSendEvent(helper_disp, btn_event.window, False, ButtonReleaseMask, (XEvent *) &btn_event); - #endif + if (XTestFakeButtonEvent(helper_disp, button, True, 0) != 0) { + status = UIOHOOK_SUCCESS; + } + + if (status == UIOHOOK_SUCCESS && XTestFakeButtonEvent(helper_disp, button, False, 0) == 0) { + status = UIOHOOK_FAILURE; + } return UIOHOOK_SUCCESS; } -static void post_mouse_motion_event(uiohook_event * const event) { - #ifdef USE_XTEST - XTestFakeMotionEvent(helper_disp, -1, event->data.mouse.x, event->data.mouse.y, 0); - #else - XMotionEvent mov_event = { - .type = MotionNotify, - .serial = 0, - .send_event = False, - .display = helper_disp, - - .window = None, /* “event” window it is reported relative to */ - .root = XDefaultRootWindow(helper_disp), /* root window that the event occurred on */ - .subwindow = None, /* child window */ +static int post_mouse_motion_event(uiohook_event * const event) { + int status = UIOHOOK_FAILURE; - .time = CurrentTime, - - .x = event->data.mouse.x, /* pointer x, y coordinates in event window */ - .y = event->data.mouse.y, - - .x_root = event->data.mouse.x, /* coordinates relative to root */ - .y_root = event->data.mouse.x, - - .state = current_modifier_mask|MotionNotify, /* key or button mask */ - - .is_hint = NotifyNormal, - .same_screen = True - }; - - int revert; - XGetInputFocus(helper_disp, &(mov_event.window), &revert); + if (XTestFakeMotionEvent(helper_disp, -1, event->data.mouse.x, event->data.mouse.y, 0) != 0) { + status = UIOHOOK_SUCCESS; + } - XSendEvent(helper_disp, mov_event.window, False, mov_event.state, (XEvent *) &mov_event); - #endif + return status; } // TODO This should return a status code, UIOHOOK_SUCCESS or otherwise. -UIOHOOK_API void hook_post_event(uiohook_event * const event) { +UIOHOOK_API int hook_post_event(uiohook_event * const event) { if (helper_disp == NULL) { logger(LOG_LEVEL_ERROR, "%s [%u]: XDisplay helper_disp is unavailable!\n", - __FUNCTION__, __LINE__); - return; // UIOHOOK_ERROR_X_OPEN_DISPLAY + __FUNCTION__, __LINE__); + return UIOHOOK_ERROR_X_OPEN_DISPLAY; } XLockDisplay(helper_disp); + int status = UIOHOOK_FAILURE; switch (event->type) { case EVENT_KEY_PRESSED: case EVENT_KEY_RELEASED: - post_key_event(event); + status = post_key_event(event); break; case EVENT_MOUSE_PRESSED: case EVENT_MOUSE_RELEASED: - post_mouse_button_event(event); + status = post_mouse_button_event(event); break; case EVENT_MOUSE_WHEEL: - post_mouse_wheel_event(event); + status = post_mouse_wheel_event(event); break; case EVENT_MOUSE_MOVED: case EVENT_MOUSE_DRAGGED: - post_mouse_motion_event(event); + status = post_mouse_motion_event(event); break; case EVENT_KEY_TYPED: @@ -417,11 +204,13 @@ UIOHOOK_API void hook_post_event(uiohook_event * const event) { default: logger(LOG_LEVEL_WARN, "%s [%u]: Ignoring post event type %#X\n", - __FUNCTION__, __LINE__, event->type); - break; + __FUNCTION__, __LINE__, event->type); + status = UIOHOOK_FAILURE; } // Don't forget to flush! XSync(helper_disp, True); XUnlockDisplay(helper_disp); + + return status; } diff --git a/src/x11/system_properties.c b/src/x11/system_properties.c index b821b8a1..e9044a7b 100644 --- a/src/x11/system_properties.c +++ b/src/x11/system_properties.c @@ -214,7 +214,7 @@ UIOHOOK_API screen_data* hook_create_screen_info(unsigned char *count) { #endif } else { logger(LOG_LEVEL_WARN, "%s [%u]: XDisplay helper_disp is unavailable!\n", - __FUNCTION__, __LINE__); + __FUNCTION__, __LINE__); } return screens; @@ -253,7 +253,7 @@ UIOHOOK_API long int hook_get_auto_repeat_rate() { #endif } else { logger(LOG_LEVEL_WARN, "%s [%u]: XDisplay helper_disp is unavailable!\n", - __FUNCTION__, __LINE__); + __FUNCTION__, __LINE__); } if (successful) { @@ -296,7 +296,7 @@ UIOHOOK_API long int hook_get_auto_repeat_delay() { #endif } else { logger(LOG_LEVEL_WARN, "%s [%u]: XDisplay helper_disp is unavailable!\n", - __FUNCTION__, __LINE__); + __FUNCTION__, __LINE__); } if (successful) { @@ -321,7 +321,7 @@ UIOHOOK_API long int hook_get_pointer_acceleration_multiplier() { } } else { logger(LOG_LEVEL_WARN, "%s [%u]: XDisplay helper_disp is unavailable!\n", - __FUNCTION__, __LINE__); + __FUNCTION__, __LINE__); } return value; @@ -342,7 +342,7 @@ UIOHOOK_API long int hook_get_pointer_acceleration_threshold() { } } else { logger(LOG_LEVEL_WARN, "%s [%u]: XDisplay helper_disp is unavailable!\n", - __FUNCTION__, __LINE__); + __FUNCTION__, __LINE__); } return value; @@ -363,7 +363,7 @@ UIOHOOK_API long int hook_get_pointer_sensitivity() { } } else { logger(LOG_LEVEL_WARN, "%s [%u]: XDisplay helper_disp is unavailable!\n", - __FUNCTION__, __LINE__); + __FUNCTION__, __LINE__); } return value; @@ -418,7 +418,7 @@ UIOHOOK_API long int hook_get_multi_click_time() { } } else { logger(LOG_LEVEL_WARN, "%s [%u]: XDisplay helper_disp is unavailable!\n", - __FUNCTION__, __LINE__); + __FUNCTION__, __LINE__); } if (successful) {