From ed2f908b00d05b84c6680a4927b973a0ed29c844 Mon Sep 17 00:00:00 2001 From: zhouqing <93741833@qq.com> Date: Sun, 16 Jun 2024 04:44:20 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix=20=E4=BF=AE=E5=A4=8DKeyUp=E4=BA=8B?= =?UTF-8?q?=E4=BB=B6=E6=97=A0=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/issue_template.md | 28 + .github/pull_request_template.md | 25 + .github/workflows/go.yml | 29 + .gitignore | 30 + LICENSE | 21 + README.md | 66 + appveyor.yml | 80 ++ chan/eb_chan.h | 1447 ++++++++++++++++++++++ circle.yml | 21 + event.go | 150 +++ event/dispatch_proc.h | 220 ++++ event/goEvent.h | 173 +++ event/os.h | 48 + event/pub.h | 101 ++ examples/event/main.go | 113 ++ examples/main.go | 80 ++ extern.go | 40 + go.mod | 3 + go.sum | 4 + hook.go | 294 +++++ hook/LICENSE | 674 ++++++++++ hook/darwin/event_c.h | 233 ++++ hook/darwin/hook_c.h | 1316 ++++++++++++++++++++ hook/darwin/input.h | 110 ++ hook/darwin/input_c.h | 546 +++++++++ hook/darwin/properties_c.h | 528 ++++++++ hook/iohook.h | 450 +++++++ hook/logger.h | 15 + hook/logger_c.h | 60 + hook/windows/event_c.h | 333 +++++ hook/windows/hook_c.h | 739 +++++++++++ hook/windows/input.h | 112 ++ hook/windows/input_c.h | 823 +++++++++++++ hook/windows/properties_c.h | 217 ++++ hook/x11/event_c.h | 385 ++++++ hook/x11/hook_c.h | 1106 +++++++++++++++++ hook/x11/input.h | 93 ++ hook/x11/input_c.h | 1958 ++++++++++++++++++++++++++++++ hook/x11/properties_c.h | 492 ++++++++ hook_test.go | 23 + keycode.go | 22 + tables.go | 354 ++++++ test/main.go | 40 + 43 files changed, 13602 insertions(+) create mode 100644 .github/issue_template.md create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/go.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 appveyor.yml create mode 100644 chan/eb_chan.h create mode 100644 circle.yml create mode 100644 event.go create mode 100644 event/dispatch_proc.h create mode 100644 event/goEvent.h create mode 100644 event/os.h create mode 100644 event/pub.h create mode 100644 examples/event/main.go create mode 100644 examples/main.go create mode 100644 extern.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 hook.go create mode 100644 hook/LICENSE create mode 100644 hook/darwin/event_c.h create mode 100644 hook/darwin/hook_c.h create mode 100644 hook/darwin/input.h create mode 100644 hook/darwin/input_c.h create mode 100644 hook/darwin/properties_c.h create mode 100644 hook/iohook.h create mode 100644 hook/logger.h create mode 100644 hook/logger_c.h create mode 100644 hook/windows/event_c.h create mode 100644 hook/windows/hook_c.h create mode 100644 hook/windows/input.h create mode 100644 hook/windows/input_c.h create mode 100644 hook/windows/properties_c.h create mode 100644 hook/x11/event_c.h create mode 100644 hook/x11/hook_c.h create mode 100644 hook/x11/input.h create mode 100644 hook/x11/input_c.h create mode 100644 hook/x11/properties_c.h create mode 100644 hook_test.go create mode 100644 keycode.go create mode 100644 tables.go create mode 100644 test/main.go diff --git a/.github/issue_template.md b/.github/issue_template.md new file mode 100644 index 0000000..72812fe --- /dev/null +++ b/.github/issue_template.md @@ -0,0 +1,28 @@ +1. Please **speak English (English only)**, this is the language everybody of us can speak and write. +2. Please take a moment to **search** that an issue **doesn't already exist**. +3. Please make sure `Golang, GCC` is installed correctly before installing Gohook. + +5. Please give all relevant information below for bug reports, incomplete details will be handled as an invalid report. + +**You MUST delete the content above including this line before posting, otherwise your issue will be invalid.** + + +- Gohook version (or commit ref): +- Go version: +- Gcc version: +- Operating system and bit: +- Resolution: +- Can you reproduce the bug at [Examples](https://github.com/robotn/gohook/blob/master/examples/main.go): + - [ ] Yes (provide example code) + - [ ] No + - [ ] Not relevant +- Provide example code: + +```Go + +``` +- Log gist: + +## Description + +... diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..264f0aa --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,25 @@ +The pull request will be closed without any reasons if it does not satisfy any of following requirements: + +1. Make sure you are targeting the `master` branch, pull requests on release branches are only allowed for bug fixes. +2. Add new features, please provide the reasons and test code. +3. Please read contributing guidelines: [CONTRIBUTING](https://github.com/robotn/gohook/blob/master/CONTRIBUTING.md) +4. Describe what your pull request does and which issue you're targeting (if any and **Please use English**) +5. ... if it is not related to any particular issues, explain why we should not reject your pull request. +6. The Commits must **use English**, must be test and No useless submissions. + +**You MUST delete the content above including this line before posting, otherwise your pull request will be invalid.** + + +**Please provide Issues links to:** + +- Issues: #1 + +**Provide test code:** + +```Go + +``` + +## Description + +... diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 0000000..4a83c51 --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,29 @@ +name: Go +on: [push] +jobs: + test: + # name: build + strategy: + matrix: + # go: [1.12.x, 1.13.x] + os: [macOS-latest, windows-latest] # ubuntu-latest + runs-on: ${{ matrix.os }} + + steps: + - name: Set up Go 1.22.0 + uses: actions/setup-go@v1 + with: + go-version: 1.22.0 + id: go + + - name: Check out code into the Go module directory + uses: actions/checkout@v1 + + - name: Get dependencies + run: | + go get -v -t -d ./... + + - name: Build + run: go build -v . + - name: Test + run: go test -v . diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6d4051e --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# Object files +.DS_Store +.vscode +.idea + +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof +*.idea \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cc87081 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 go-ego Project Developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..350b281 --- /dev/null +++ b/README.md @@ -0,0 +1,66 @@ +# gohook + +[![Build Status](https://github.com/robotn/gohook/workflows/Go/badge.svg)](https://github.com/robotn/gohook/commits/master) +[![CircleCI Status](https://circleci.com/gh/robotn/gohook.svg?style=shield)](https://circleci.com/gh/robotn/gohook) +![Appveyor](https://ci.appveyor.com/api/projects/status/github/robotn/gohook?branch=master&svg=true) +[![Go Report Card](https://goreportcard.com/badge/github.com/robotn/gohook)](https://goreportcard.com/report/github.com/robotn/gohook) +[![GoDoc](https://godoc.org/github.com/robotn/gohook?status.svg)](https://godoc.org/github.com/robotn/gohook) + + +## Requirements (Linux): + +[Robotgo-requirements-event](https://github.com/go-vgo/robotgo#requirements) + +## Install: + +With Go module support (Go 1.11+), just import: + +```go +import "github.com/robotn/gohook" +``` + +## Examples: + +```Go +package main + +import ( + "fmt" + + hook "github.com/robotn/gohook" +) + +func main() { + add() + + low() +} + +func add() { + fmt.Println("--- Please press ctrl + shift + q to stop hook ---") + hook.Register(hook.KeyDown, []string{"q", "ctrl", "shift"}, func(e hook.Event) { + fmt.Println("ctrl-shift-q") + hook.End() + }) + + fmt.Println("--- Please press w---") + hook.Register(hook.KeyDown, []string{"w"}, func(e hook.Event) { + fmt.Println("w") + }) + + s := hook.Start() + <-hook.Process(s) +} + +func low() { + evChan := hook.Start() + defer hook.End() + + for ev := range evChan { + fmt.Println("hook: ", ev) + } +} + +``` + +Based on [libuiohook](https://github.com/kwhat/libuiohook). \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..0e05864 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,80 @@ +# version format +version: "{build}" + +# Operating system (build VM template) +# os: Windows Server 2012 R2 +os: Visual Studio 2017 + +# Platform. +# platform: +# - x64 +# - x86 + +clone_folder: c:\gopath\src\github.com\robotn\gohook + +# Environment variables +environment: + global: + GOPATH: C:\gopath + CC: gcc.exe + matrix: + - GOARCH: amd64 + # GOVERSION: 1.9.3 + GETH_ARCH: amd64 + MSYS2_ARCH: x86_64 + MSYS2_BITS: 64 + MSYSTEM: MINGW64 + PATH: C:\msys64\mingw64\bin\;C:\Program Files (x86)\NSIS\;%PATH% + - GOARCH: 386 + # GOVERSION: 1.9.3 + GETH_ARCH: 386 + MSYS2_ARCH: i686 + MSYS2_BITS: 32 + MSYSTEM: MINGW32 + PATH: C:\msys64\mingw32\bin\;C:\Program Files (x86)\NSIS\;%PATH% + # - COMPILER: MINGW_W64 + # ARCHITECTURE: x64 + GOVERSION: 1.22.0 + # GOPATH: c:\gopath + +# scripts that run after cloning repository +# install: +# - set PATH=%GOPATH%\bin;c:\go\bin;%PATH% +# - go version +# - go env +# - gcc --version +# - python --version + +install: + - set PATH=%GOPATH%\bin;c:\go\bin;%PATH% + - git submodule update --init + - rmdir C:\go /s /q + - appveyor DownloadFile https://storage.googleapis.com/golang/go%GOVERSION%.windows-%GETH_ARCH%.zip + - 7z x go%GOVERSION%.windows-%GETH_ARCH%.zip -y -oC:\ > NUL + - go version + - go env + - gcc --version + +# To run your custom scripts instead of automatic MSBuild +build_script: + # We need to disable firewall - https://github.com/appveyor/ci/issues/1579#issuecomment-309830648 + - ps: Disable-NetFirewallRule -DisplayName 'File and Printer Sharing (SMB-Out)' + - cd c:\gopath\src\github.com\robotn\gohook + - git branch + - go get -t ./... + +# To run your custom scripts instead of automatic tests +test_script: + # Unit tests + - ps: Add-AppveyorTest "Unit Tests" -Outcome Running + - go test -v github.com/robotn/gohook... + - ps: Update-AppveyorTest "Unit Tests" -Outcome Passed + +# notifications: +# - provider: Email +# to: +# - .io +# on_build_failure: true +# on_build_status_changed: true +# to disable deployment +deploy: off diff --git a/chan/eb_chan.h b/chan/eb_chan.h new file mode 100644 index 0000000..8ac4fc9 --- /dev/null +++ b/chan/eb_chan.h @@ -0,0 +1,1447 @@ +// ####################################################### +// ## Generated by merge_src from the following files: +// ## eb_assert.c +// ## eb_assert.h +// ## eb_atomic.h +// ## eb_chan.c +// ## eb_chan.h +// ## eb_nsec.h +// ## eb_port.c +// ## eb_port.h +// ## eb_spinlock.h +// ## eb_sys.c +// ## eb_sys.h +// ## eb_time.c +// ## eb_time.h +// ####################################################### + +// ####################################################### +// ## eb_chan.h +// ####################################################### + +#ifndef EB_CHAN_H +#define EB_CHAN_H + +#include +#include +// ####################################################### +// ## eb_nsec.h +// ####################################################### + +#include + +#if (defined(__MINGW32__) || defined(__CYGWIN__)) && !defined(_POSIX_PRIORITY_SCHEDULING) +#include + +int sched_yield() { + return SwitchToThread(); +} +#endif + +typedef uint64_t eb_nsec; /* Units of nanoseconds */ +#define eb_nsec_zero UINT64_C(0) +#define eb_nsec_forever UINT64_MAX +#define eb_nsec_per_sec UINT64_C(1000000000) + +/* ## Types */ +typedef enum { + eb_chan_res_ok, /* Success */ + eb_chan_res_closed, /* Failed because the channel is closed */ + eb_chan_res_stalled, /* Failed because the send/recv couldn't proceed without blocking (applies to _try_send()/_try_recv()) */ +} eb_chan_res; + +typedef struct eb_chan *eb_chan; +typedef struct { + eb_chan chan; /* The applicable channel, where NULL channels block forever */ + bool send; /* True if sending, false if receiving */ + eb_chan_res res; /* _ok if the op completed due to a successful send/recv operation, _closed if the op completed because the channel is closed. */ + const void *val; /* The value to be sent/the value that was received */ +} eb_chan_op; + +/* ## Channel creation/lifecycle */ +eb_chan eb_chan_create(size_t buf_cap); +eb_chan eb_chan_retain(eb_chan c); +void eb_chan_release(eb_chan c); + +/* ## Channel closing */ +/* Returns _ok on success, or _closed if the channel was already closed. */ +eb_chan_res eb_chan_close(eb_chan c); + +/* ## Getters */ +size_t eb_chan_buf_cap(eb_chan c); +size_t eb_chan_buf_len(eb_chan c); + +/* ## Sending/receiving */ +/* Send/receive a value on a channel (where _send()/_recv() are blocking and _try_send()/_try_recv() are non-blocking) */ +eb_chan_res eb_chan_send(eb_chan c, const void *val); +eb_chan_res eb_chan_try_send(eb_chan c, const void *val); +eb_chan_res eb_chan_recv(eb_chan c, const void **val); +eb_chan_res eb_chan_try_recv(eb_chan c, const void **val); + +/* ## Multiplexing */ +/* _select_list() performs at most one of the operations in the supplied list, and returns the one that was performed. + It returns NULL if no operation was performed before the timeout. */ +eb_chan_op *eb_chan_select_list(eb_nsec timeout, eb_chan_op *const ops[], size_t nops); + +/* _select() is a convenience macro that wraps _select_list() to avoid having to manually create an array of ops on the stack. + For example: + eb_chan_op op1 = eb_chan_op_send(c1, NULL); + eb_chan_op op2 = eb_chan_op_recv(c2); + eb_chan_op *result = eb_chan_select(timeout, &op1, &op2); + ... +*/ +#define eb_chan_select(timeout, ...) ({ \ + eb_chan_op *const eb_chan_select_ops[] = {__VA_ARGS__}; \ + eb_chan_select_list(timeout, eb_chan_select_ops, (sizeof(eb_chan_select_ops) / sizeof(*eb_chan_select_ops))); \ +}) + +/* Return initialized send/recv ops for use with _select() */ +static inline eb_chan_op eb_chan_op_send(eb_chan c, const void *val) { + return (eb_chan_op){.chan = c, .send = true, .res = eb_chan_res_closed, .val = val}; +} + +static inline eb_chan_op eb_chan_op_recv(eb_chan c) { + return (eb_chan_op){.chan = c, .send = false, .res = eb_chan_res_closed, .val = NULL}; +} + +// ####################################################### +// ## eb_chan.c +// ####################################################### +#include +#include +#include +#include +#include +// ####################################################### +// ## eb_assert.h +// ####################################################### + +#include +#include + +#define eb_no_op + +#define eb_assert_or_recover(cond, action) ({ \ + if (!(cond)) { \ + eb_assert_print("Assertion failed", #cond, __FILE__, (uintmax_t)__LINE__, __PRETTY_FUNCTION__); \ + action; \ + } \ +}) + +#define eb_assert_or_bail(cond, msg) ({ \ + if (!(cond)) { \ + eb_assert_print(msg, #cond, __FILE__, (uintmax_t)__LINE__, __PRETTY_FUNCTION__); \ + abort(); \ + } \ +}) + +void eb_assert_print(const char *msg, const char *cond, const char *file, uintmax_t line, const char *func); +// ####################################################### +// ## eb_assert.c +// ####################################################### + +#include + +void eb_assert_print(const char *msg, const char *cond, const char *file, uintmax_t line, const char *func) { + fprintf(stderr, "=== %s ===\n" + " Assertion: %s\n" + " File: %s:%ju\n" + " Function: %s\n", msg, cond, file, line, func); +} +// ####################################################### +// ## eb_port.h +// ####################################################### + +#include +#include + +typedef struct eb_port *eb_port; + +eb_port eb_port_create(); +eb_port eb_port_retain(eb_port p); +void eb_port_release(eb_port p); + +void eb_port_signal(eb_port p); +bool eb_port_wait(eb_port p, eb_nsec timeout); +// ####################################################### +// ## eb_port.c +// ####################################################### + +#include +#include +#include +#include +// ####################################################### +// ## eb_sys.h +// ####################################################### + +#include + +#if __MACH__ + #define EB_SYS_DARWIN 1 +#elif __linux__ + #define EB_SYS_LINUX 1 +#else +// #error Unsupported system +#endif + +/* ## Variables */ +/* Returns the number of logical cores on the machine. _init must be called for this to be valid! */ +size_t eb_sys_ncores; + +/* ## Functions */ +void eb_sys_init(); +// ####################################################### +// ## eb_sys.c +// ####################################################### + +// ####################################################### +// ## eb_atomic.h +// ####################################################### + + +#define eb_atomic_add(ptr, delta) __sync_add_and_fetch(ptr, delta) /* Returns the new value */ +#define eb_atomic_compare_and_swap(ptr, old, new) __sync_bool_compare_and_swap(ptr, old, new) +#define eb_atomic_barrier() __sync_synchronize() + +#if EB_SYS_DARWIN + #include +#elif EB_SYS_LINUX + #include +#endif + +size_t ncores() { + #if EB_SYS_DARWIN + host_basic_info_data_t info; + mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT; + kern_return_t r = host_info(mach_host_self(), HOST_BASIC_INFO, (host_info_t)&info, &count); + eb_assert_or_recover(r == KERN_SUCCESS, return 0); + eb_assert_or_recover(count == HOST_BASIC_INFO_COUNT, return 0); + eb_assert_or_recover(info.logical_cpu > 0 && info.logical_cpu <= SIZE_MAX, return 0); + + return (size_t)info.logical_cpu; + #elif EB_SYS_LINUX + long ncores = sysconf(_SC_NPROCESSORS_ONLN); + eb_assert_or_recover(ncores > 0 && ncores <= SIZE_MAX, return 0); + + return (size_t)ncores; + #endif +} + +void eb_sys_init() { + if (!eb_sys_ncores) { + eb_atomic_compare_and_swap(&eb_sys_ncores, 0, ncores()); + } +} +#if EB_SYS_DARWIN + #include +#elif EB_SYS_LINUX + #include + #include +#endif +// ####################################################### +// ## eb_spinlock.h +// ####################################################### + +#include +#include + +/* ## Types */ +typedef int eb_spinlock; +#define EB_SPINLOCK_INIT 0 + +/* ## Functions */ +#define eb_spinlock_try(l) eb_atomic_compare_and_swap(l, 0, 1) + +#define eb_spinlock_lock(l) ({ \ + if (eb_sys_ncores > 1) { \ + while (!eb_spinlock_try(l)); \ + } else { \ + while (!eb_spinlock_try(l)) { \ + sched_yield(); \ + } \ + } \ +}) + +#define eb_spinlock_unlock(l) eb_atomic_compare_and_swap(l, 1, 0) + +//#define eb_spinlock_try(l) __sync_lock_test_and_set(l, 1) == 0 +//#define eb_spinlock_lock(l) while (!eb_spinlock_try(l)) +//#define eb_spinlock_unlock(l) __sync_lock_release(l) +// +//typedef OSSpinLock eb_spinlock; +//#define eb_spinlock_try(l) OSSpinLockTry(l) +//#define eb_spinlock_lock(l) OSSpinLockLock(l) +//#define eb_spinlock_unlock(l) OSSpinLockUnlock(l) +// ####################################################### +// ## eb_time.h +// ####################################################### + + +/* Returns the number of nanoseconds since an arbitrary point in time (usually the machine's boot time) */ +eb_nsec eb_time_now(); +// ####################################################### +// ## eb_time.c +// ####################################################### + +#include +#include +#if EB_SYS_DARWIN + #include +#elif EB_SYS_LINUX + #include +#endif + +eb_nsec eb_time_now() { +#if EB_SYS_DARWIN + /* Initialize k_timebase_info, thread-safely */ + static mach_timebase_info_t k_timebase_info = NULL; + if (!k_timebase_info) { + mach_timebase_info_t timebase_info = malloc(sizeof(*timebase_info)); + kern_return_t r = mach_timebase_info(timebase_info); + eb_assert_or_recover(r == KERN_SUCCESS, return 0); + + /* Make sure the writes to 'timebase_info' are complete before we assign k_timebase_info */ + eb_atomic_barrier(); + + if (!eb_atomic_compare_and_swap(&k_timebase_info, NULL, timebase_info)) { + free(timebase_info); + timebase_info = NULL; + } + } + + return ((mach_absolute_time() * k_timebase_info->numer) / k_timebase_info->denom); +#elif EB_SYS_LINUX + struct timespec ts; + int r = clock_gettime(CLOCK_MONOTONIC, &ts); + eb_assert_or_recover(!r, return 0); + + return ((uint64_t)ts.tv_sec * eb_nsec_per_sec) + ts.tv_nsec; +#endif +} + +#define PORT_POOL_CAP 0x10 +static eb_spinlock g_port_pool_lock = EB_SPINLOCK_INIT; +static eb_port g_port_pool[PORT_POOL_CAP]; +static size_t g_port_pool_len = 0; + +struct eb_port { + unsigned int retain_count; + bool sem_valid; + bool signaled; + #if EB_SYS_DARWIN + semaphore_t sem; + #elif EB_SYS_LINUX + sem_t sem; + #endif +}; + +static void eb_port_free(eb_port p) { + /* Allowing p==NULL so that this function can be called unconditionally on failure from eb_port_create() */ + if (!p) { + return; + } + + bool added_to_pool = false; + if (p->sem_valid) { + /* Determine whether we should clear the reset the port because we're going to try adding the port to our pool. */ + bool reset = false; + eb_spinlock_lock(&g_port_pool_lock); + reset = (g_port_pool_len < PORT_POOL_CAP); + eb_spinlock_unlock(&g_port_pool_lock); + + if (reset) { + eb_port_wait(p, eb_nsec_zero); + } + + /* Now that the port's reset, add it to the pool as long as it'll still fit. */ + eb_spinlock_lock(&g_port_pool_lock); + if (g_port_pool_len < PORT_POOL_CAP) { + g_port_pool[g_port_pool_len] = p; + g_port_pool_len++; + added_to_pool = true; + } + eb_spinlock_unlock(&g_port_pool_lock); + + /* If we couldn't add the port to the pool, destroy the underlying semaphore. */ + if (!added_to_pool) { + #if EB_SYS_DARWIN + kern_return_t r = semaphore_destroy(mach_task_self(), p->sem); + eb_assert_or_recover(r == KERN_SUCCESS, eb_no_op); + #elif EB_SYS_LINUX + int r = sem_destroy(&p->sem); + eb_assert_or_recover(!r, eb_no_op); + #endif + + p->sem_valid = false; + } + } + + if (!added_to_pool) { + free(p); + p = NULL; + } +} + +eb_port eb_port_create() { + eb_port p = NULL; + + /* First try to pop a port out of the pool */ + eb_spinlock_lock(&g_port_pool_lock); + if (g_port_pool_len) { + g_port_pool_len--; + p = g_port_pool[g_port_pool_len]; + } + eb_spinlock_unlock(&g_port_pool_lock); + + if (p) { + /* We successfully popped a port out of the pool */ + eb_assert_or_bail(!p->retain_count, "Sanity-check failed"); + } else { + /* We couldn't get a port out of the pool */ + /* Using calloc so that bytes are zeroed */ + p = calloc(1, sizeof(*p)); + eb_assert_or_recover(p, goto failed); + + /* Create the semaphore */ + #if EB_SYS_DARWIN + kern_return_t r = semaphore_create(mach_task_self(), &p->sem, SYNC_POLICY_FIFO, 0); + eb_assert_or_recover(r == KERN_SUCCESS, goto failed); + #elif EB_SYS_LINUX + int r = sem_init(&p->sem, 0, 0); + eb_assert_or_recover(!r, goto failed); + #endif + } + + p->sem_valid = true; + p->retain_count = 1; + return p; + failed: { + eb_port_free(p); + return NULL; + } +} + +eb_port eb_port_retain(eb_port p) { + assert(p); + eb_atomic_add(&p->retain_count, 1); + return p; +} + +void eb_port_release(eb_port p) { + assert(p); + if (eb_atomic_add(&p->retain_count, -1) == 0) { + eb_port_free(p); + } +} + +void eb_port_signal(eb_port p) { + assert(p); + + if (eb_atomic_compare_and_swap(&p->signaled, false, true)) { + #if EB_SYS_DARWIN + kern_return_t r = semaphore_signal(p->sem); + eb_assert_or_recover(r == KERN_SUCCESS, eb_no_op); + #elif EB_SYS_LINUX + int r = sem_post(&p->sem); + eb_assert_or_recover(!r, eb_no_op); + #endif + } +} + +bool eb_port_wait(eb_port p, eb_nsec timeout) { + assert(p); + + bool result = false; + if (timeout == eb_nsec_zero) { + /* ## Non-blocking */ + #if EB_SYS_DARWIN + kern_return_t r = semaphore_timedwait(p->sem, (mach_timespec_t){0, 0}); + eb_assert_or_recover(r == KERN_SUCCESS || r == KERN_OPERATION_TIMED_OUT, eb_no_op); + + result = (r == KERN_SUCCESS); + #elif EB_SYS_LINUX + int r = 0; + while ((r = sem_trywait(&p->sem)) == -1 && errno == EINTR); + eb_assert_or_recover(!r || (r == -1 && errno == EAGAIN), eb_no_op); + + result = !r; + #endif + } else if (timeout == eb_nsec_forever) { + /* ## Blocking */ + #if EB_SYS_DARWIN + kern_return_t r; + while ((r = semaphore_wait(p->sem)) == KERN_ABORTED); + eb_assert_or_recover(r == KERN_SUCCESS, eb_no_op); + + result = (r == KERN_SUCCESS); + #elif EB_SYS_LINUX + int r; + while ((r = sem_wait(&p->sem)) == -1 && errno == EINTR); + eb_assert_or_recover(!r, eb_no_op); + + result = !r; + #endif + } else { + /* ## Actual timeout */ + eb_nsec start_time = eb_time_now(); + eb_nsec remaining_timeout = timeout; + for (;;) { + #if EB_SYS_DARWIN + /* This needs to be in a loop because semaphore_timedwait() can return KERN_ABORTED, e.g. if the process receives a signal. */ + mach_timespec_t ts = {.tv_sec = (unsigned int)(remaining_timeout / eb_nsec_per_sec), .tv_nsec = (clock_res_t)(remaining_timeout % eb_nsec_per_sec)}; + kern_return_t r = semaphore_timedwait(p->sem, ts); + eb_assert_or_recover(r == KERN_SUCCESS || r == KERN_OPERATION_TIMED_OUT || r == KERN_ABORTED, eb_no_op); + + if (r == KERN_SUCCESS) { + result = true; + break; + } + #elif EB_SYS_LINUX + /* Because sem_timedwait() uses the system's _REALTIME clock instead of the _MONOTONIC clock, we'll time out when + the system's time changes. For that reason, we check for the timeout case ourself (instead of relying on errno + after calling sem_timedwait()) condition ourself, using our own monotonic clock APIs (eb_time_now()), and + restart sem_timedwait() if we determine independently that we haven't timed-out. */ + struct timespec ts; + int r = clock_gettime(CLOCK_REALTIME, &ts); + eb_assert_or_recover(!r, break); + + ts.tv_sec += (remaining_timeout / eb_nsec_per_sec); + ts.tv_nsec += (remaining_timeout % eb_nsec_per_sec); + r = sem_timedwait(&p->sem, &ts); + /* The allowed return cases are: success (r==0), timed-out (r==-1, errno==ETIMEDOUT), (r==-1, errno==EINTR) */ + eb_assert_or_recover(!r || (r == -1 && (errno == ETIMEDOUT || errno == EINTR)), break); + + /* If we acquired the semaphore, set our flag and break! */ + if (!r) { + result = true; + break; + } + #endif + + /* Determine whether we timed-out, and if not, update 'remaining_timeout' with the amount of time to go. */ + eb_nsec elapsed = eb_time_now() - start_time; + if (elapsed < timeout) { + remaining_timeout = timeout - elapsed; + } else { + break; + } + } + } + + if (result) { + assert(eb_atomic_compare_and_swap(&p->signaled, true, false)); + } + + return result; +} + +#pragma mark - Types - +typedef struct { + eb_spinlock lock; + size_t cap; + size_t len; + eb_port *ports; +} *port_list; + +static inline void port_list_free(port_list l); + +/* Creates a new empty list */ +static inline port_list port_list_alloc(size_t cap) { + assert(cap > 0); + + port_list result = malloc(sizeof(*result)); + eb_assert_or_recover(result, goto failed); + + result->lock = EB_SPINLOCK_INIT; + result->cap = cap; + result->len = 0; + result->ports = malloc(cap * sizeof(*(result->ports))); + eb_assert_or_recover(result->ports, goto failed); + + return result; + failed: { + port_list_free(result); + return NULL; + } +} + +/* Releases every port in the list, and frees the list itself */ +static inline void port_list_free(port_list l) { + /* Intentionally allowing l==NULL */ + if (!l) { + return; + } + + /* Release each port in our list */ + size_t i; + for (i = 0; i < l->len; i++) { + eb_port_release(l->ports[i]); + } + + free(l->ports); + l->ports = NULL; + + free(l); + l = NULL; +} + +/* Add a port to the end of the list, expanding the buffer as necessary */ +static inline void port_list_add(port_list l, eb_port p) { + assert(l); + assert(p); + + /* First retain the port! */ + eb_port_retain(p); + + eb_spinlock_lock(&l->lock); + /* Sanity-check that the list's length is less than its capacity */ + eb_assert_or_bail(l->len <= l->cap, "Sanity check failed"); + + /* Expand the list's buffer if it's full */ + if (l->len == l->cap) { + l->cap *= 2; + // TODO: reimplement as a linked list, where the port nodes are just on the stacks of the _select_list() calls. that way the number of ports is unbounded, and we don't have to allocate anything on the heap! + l->ports = realloc(l->ports, l->cap * sizeof(*(l->ports))); + eb_assert_or_bail(l->ports, "Allocation failed"); + } + + l->ports[l->len] = p; + l->len++; + eb_spinlock_unlock(&l->lock); +} + +/* Remove the first occurence of 'p' in the list. Returns whether a port was actually removed. */ +static inline bool port_list_rm(port_list l, eb_port p) { + assert(l); + assert(p); + + bool result = false; + eb_spinlock_lock(&l->lock); + /* Sanity-check that the list's length is less than its capacity */ + eb_assert_or_bail(l->len <= l->cap, "Sanity-check failed"); + + /* Search for first occurence of the given port. If we find it, release it and move the last port in the list into the hole. */ + size_t i; + for (i = 0; i < l->len; i++) { + if (l->ports[i] == p) { + /* Move the last element in the port list into the now-vacant spot */ + l->ports[i] = l->ports[l->len-1]; + /* Decrement the buffer length */ + l->len--; + result = true; + break; + } + } + eb_spinlock_unlock(&l->lock); + + if (result) { + /* Release the port, but do so outside of the spinlock because releasing does some stuff that might not be quick. */ + eb_port_release(p); + } + + return result; +} + +/* Signal the first port in the list that isn't 'ignore' */ +static inline void port_list_signal_first(const port_list l, eb_port ignore) { + assert(l); + + eb_port p = NULL; + eb_spinlock_lock(&l->lock); + size_t i; + for (i = 0; i < l->len; i++) { + if (l->ports[i] != ignore) { + p = eb_port_retain(l->ports[i]); + break; + } + } + eb_spinlock_unlock(&l->lock); + + if (p) { + eb_port_signal(p); + eb_port_release(p); + p = NULL; + } +} + +enum { + /* Buffered/unbuffered channel states */ + chanstate_open, + chanstate_closed, + /* Unbuffered channel states */ + chanstate_send, + chanstate_recv, + chanstate_ack, + chanstate_done, + chanstate_cancelled +}; typedef int32_t chanstate; + +typedef struct { + eb_chan_op *const *ops; + size_t nops; + bool *cleanup_ops; + + eb_nsec timeout; + eb_port port; +} do_state; + +struct eb_chan { + unsigned int retain_count; + eb_spinlock lock; + chanstate state; + + port_list sends; + port_list recvs; + + /* Buffered ivars */ + size_t buf_cap; + size_t buf_len; + size_t buf_idx; + const void **buf; + + /* Unbuffered ivars */ + const do_state *unbuf_state; + eb_chan_op *unbuf_op; + eb_port unbuf_port; +}; + +#pragma mark - Channel creation/lifecycle - +static inline void eb_chan_free(eb_chan c) { + /* Intentionally allowing c==NULL so that this function can be called from eb_chan_create() */ + if (!c) { + return; + } + + if (c->buf_cap) { + /* ## Buffered */ + free(c->buf); + c->buf = NULL; + } + + port_list_free(c->recvs); + c->recvs = NULL; + + port_list_free(c->sends); + c->sends = NULL; + + free(c); + c = NULL; +} + +eb_chan eb_chan_create(size_t buf_cap) { + static const size_t k_init_buf_cap = 16; + + /* Initialize eb_sys so that eb_sys_ncores is valid. */ + eb_sys_init(); + + /* Using calloc so that the bytes are zeroed. */ + eb_chan c = calloc(1, sizeof(*c)); + eb_assert_or_recover(c, goto failed); + + c->retain_count = 1; + c->lock = EB_SPINLOCK_INIT; + c->state = chanstate_open; + + c->sends = port_list_alloc(k_init_buf_cap); + eb_assert_or_recover(c->sends, goto failed); + c->recvs = port_list_alloc(k_init_buf_cap); + eb_assert_or_recover(c->recvs, goto failed); + + if (buf_cap) { + /* ## Buffered */ + c->buf_cap = buf_cap; + c->buf_len = 0; + c->buf_idx = 0; + c->buf = malloc(c->buf_cap * sizeof(*(c->buf))); + eb_assert_or_recover(c->buf, goto failed); + } else { + /* ## Unbuffered */ + c->unbuf_state = NULL; + c->unbuf_op = NULL; + c->unbuf_port = NULL; + } + + /* Issue a memory barrier since we didn't have the lock acquired for our set up (and this channel could theoretically + be passed to another thread without a barrier, and that'd be bad news...) */ + eb_atomic_barrier(); + + return c; + failed: { + eb_chan_free(c); + return NULL; + } +} + +eb_chan eb_chan_retain(eb_chan c) { + assert(c); + eb_atomic_add(&c->retain_count, 1); + return c; +} + +void eb_chan_release(eb_chan c) { + assert(c); + if (eb_atomic_add(&c->retain_count, -1) == 0) { + eb_chan_free(c); + } +} + +#pragma mark - Channel closing - +eb_chan_res eb_chan_close(eb_chan c) { + assert(c); + + eb_chan_res result = eb_chan_res_stalled; + while (result == eb_chan_res_stalled) { + eb_port signal_port = NULL; + eb_spinlock_lock(&c->lock); + if (c->state == chanstate_open) { + c->state = chanstate_closed; + result = eb_chan_res_ok; + } else if (c->state == chanstate_closed) { + result = eb_chan_res_closed; + } else if (c->state == chanstate_send || c->state == chanstate_recv) { + if (c->unbuf_port) { + signal_port = eb_port_retain(c->unbuf_port); + } + c->state = chanstate_closed; + result = eb_chan_res_ok; + } + eb_spinlock_unlock(&c->lock); + + /* Wake up the send/recv */ + if (signal_port) { + eb_port_signal(signal_port); + eb_port_release(signal_port); + signal_port = NULL; + } + } + + if (result == eb_chan_res_ok) { + /* Wake up the sends/recvs so that they see the channel's now closed */ + port_list_signal_first(c->sends, NULL); + port_list_signal_first(c->recvs, NULL); + } + + return result; +} + +#pragma mark - Getters - +size_t eb_chan_buf_cap(eb_chan c) { + assert(c); + return c->buf_cap; +} + +size_t eb_chan_buf_len(eb_chan c) { + assert(c); + + /* buf_len is only valid if the channel's buffered */ + if (!c->buf_cap) { + return 0; + } + + size_t r = 0; + eb_spinlock_lock(&c->lock); + r = c->buf_len; + eb_spinlock_unlock(&c->lock); + return r; +} + +#pragma mark - Performing operations - +enum { + op_result_complete, /* The op completed and the caller should return */ + op_result_next, /* The op couldn't make any progress and the caller should move on to the next op */ + op_result_retry, /* The channel's busy and we should try the op again */ +}; typedef unsigned int op_result; + +static inline void cleanup_ops(const do_state *state) { + assert(state); + + size_t i; + for (i = 0; i < state->nops; i++) { + if (state->cleanup_ops[i]) { + eb_chan_op *op = state->ops[i]; + eb_chan c = op->chan; + bool signal_send = false; + bool signal_recv = false; + eb_spinlock_lock(&c->lock); + if (c->state == chanstate_send && c->unbuf_op == op) { + /* 'op' was in the process of an unbuffered send on the channel, but no recv had arrived + yet, so reset state to _open. */ + c->state = chanstate_open; + signal_send = true; + } else if (c->state == chanstate_recv && c->unbuf_op == op) { + /* 'op' was in the process of an unbuffered recv on the channel, but no send had arrived + yet, so reset state to _open. */ + c->state = chanstate_open; + signal_recv = true; + } else if (c->state == chanstate_ack && c->unbuf_op == op) { + /* A counterpart acknowledged 'op' but, but 'op' isn't the one that completed in our select() call, so we're cancelling. */ + c->state = chanstate_cancelled; + } + eb_spinlock_unlock(&c->lock); + + if (signal_send) { + port_list_signal_first(c->sends, state->port); + } + + if (signal_recv) { + port_list_signal_first(c->recvs, state->port); + } + + state->cleanup_ops[i] = false; + } + } +} + +static inline op_result send_buf(const do_state *state, eb_chan_op *op, size_t op_idx) { + assert(state); + assert(op); + assert(op->chan); + + eb_chan c = op->chan; + op_result result = op_result_next; + + if (c->buf_len < c->buf_cap || c->state == chanstate_closed) { + /* It looks like our channel's in an acceptable state, so try to acquire the lock */ + if (eb_spinlock_try(&c->lock)) { + /* Sanity-check the channel's state */ + eb_assert_or_bail(c->state == chanstate_open || c->state == chanstate_closed, "Invalid channel state"); + + bool signal_recv = false; + if (c->state == chanstate_closed) { + /* ## Sending, buffered, channel closed */ + /* Set our op's state and our return value */ + op->res = eb_chan_res_closed; + result = op_result_complete; + } else if (c->buf_len < c->buf_cap) { + /* ## Sending, buffered, channel open, buffer has space */ + /* Notify the channel's recvs if our buffer is going from empty to non-empty */ + signal_recv = (!c->buf_len); + /* Add the value to the buffer */ + size_t idx = (c->buf_idx + c->buf_len) % c->buf_cap; + c->buf[idx] = op->val; + c->buf_len++; + /* Set our op's state and our return value */ + op->res = eb_chan_res_ok; + result = op_result_complete; + } + + eb_spinlock_unlock(&c->lock); + + if (signal_recv) { + port_list_signal_first(c->recvs, state->port); + } + } else { + result = op_result_retry; + } + } + + return result; +} + +static inline op_result recv_buf(const do_state *state, eb_chan_op *op, size_t op_idx) { + assert(state); + assert(op); + assert(op->chan); + + eb_chan c = op->chan; + op_result result = op_result_next; + + if (c->buf_len || c->state == chanstate_closed) { + if (eb_spinlock_try(&c->lock)) { + /* Sanity-check the channel's state */ + eb_assert_or_bail(c->state == chanstate_open || c->state == chanstate_closed, "Invalid channel state"); + + bool signal_send = false; + if (c->buf_len) { + /* ## Receiving, buffered, buffer non-empty */ + /* Notify the channel's sends if our buffer is going from full to not-full */ + signal_send = (c->buf_len == c->buf_cap); + /* Set our op's state and our return value */ + op->res = eb_chan_res_ok; + op->val = c->buf[c->buf_idx]; + result = op_result_complete; + /* Update chan's buffer. (Updating buf_idx needs to come after we use it!) */ + c->buf_len--; + c->buf_idx = (c->buf_idx + 1) % c->buf_cap; + } else if (c->state == chanstate_closed) { + /* ## Receiving, buffered, buffer empty, channel closed */ + /* Set our op's state and our return value */ + op->res = eb_chan_res_closed; + op->val = NULL; + result = op_result_complete; + } + + eb_spinlock_unlock(&c->lock); + + if (signal_send) { + port_list_signal_first(c->sends, state->port); + } + } else { + result = op_result_retry; + } + } + + return result; +} + +static inline op_result send_unbuf(const do_state *state, eb_chan_op *op, size_t op_idx) { + assert(state); + assert(op); + assert(op->chan); + + eb_chan c = op->chan; + op_result result = op_result_next; + + if ((c->state == chanstate_open && state->timeout != eb_nsec_zero) || + c->state == chanstate_closed || + (c->state == chanstate_send && c->unbuf_op == op) || + (c->state == chanstate_recv && c->unbuf_state != state) || + (c->state == chanstate_ack && c->unbuf_op == op)) { + + /* It looks like our channel's in an acceptable state, so try to acquire the lock */ + if (eb_spinlock_try(&c->lock)) { + /* Reset the cleanup state since we acquired the lock and are actually getting a look at the channel's state */ + state->cleanup_ops[op_idx] = false; + + bool signal_recv = false; + if (c->state == chanstate_open && state->timeout != eb_nsec_zero) { + c->state = chanstate_send; + c->unbuf_state = state; + c->unbuf_op = op; + c->unbuf_port = state->port; + /* We need to cleanup after this since we put it in the _send state! */ + state->cleanup_ops[op_idx] = true; + /* Signal a recv since one of them can continue now */ + signal_recv = true; + } else if (c->state == chanstate_closed) { + /* Set our op's state and our return value */ + op->res = eb_chan_res_closed; + result = op_result_complete; + } else if (c->state == chanstate_send && c->unbuf_op == op) { + /* We own the send op that's in progress, so assign chan's unbuf_port */ + /* Verify that the unbuf_state matches our 'id' parameter. If this assertion fails, it means there's likely + one eb_chan_op being shared by multiple threads, which isn't allowed. */ + eb_assert_or_bail(c->unbuf_state == state, "unbuf_state invalid"); + /* Assign the port */ + c->unbuf_port = state->port; + /* We need to cleanup after this since we put it in the _send state! */ + state->cleanup_ops[op_idx] = true; + } else if (c->state == chanstate_recv && c->unbuf_state != state) { + /* We verified (immediately above) that the recv isn't part of the same op pool (we can't do unbuffered + sends/recvs from the same _do() call) */ + + /* Sanity check -- make sure the op is a recv */ + eb_assert_or_bail(!c->unbuf_op->send, "Op isn't a recv as expected"); + + /* Set the recv op's value. This needs to happen before we transition out of the _recv state, otherwise the unbuf_op may no longer be valid! */ + c->unbuf_op->val = op->val; + /* Acknowledge the receive */ + c->state = chanstate_ack; + /* Get a reference to the unbuf_port that needs to be signaled */ + eb_port signal_port = (c->unbuf_port ? eb_port_retain(c->unbuf_port) : NULL); + eb_spinlock_unlock(&c->lock); + + /* Wake up the recv */ + if (signal_port) { + eb_port_signal(signal_port); + eb_port_release(signal_port); + signal_port = NULL; + } + + /* We have to cleanup all our ops here to cancel any outstanding unbuffered send/recvs, to avoid a deadlock + situation that arises when another _do() is waiting on our _do() to complete, but it never does because + we're about to wait for the other _do() to complete. */ + cleanup_ops(state); + + for (;;) { + if (*((volatile chanstate *)&c->state) != chanstate_ack) { + eb_spinlock_lock(&c->lock); + if (c->state == chanstate_done) { + /* Reset the channel state back to _open */ + c->state = chanstate_open; + /* We reset our state to _open, so signal a send since it can proceed now. */ + signal_recv = true; + /* Set our op's state and our return value */ + op->res = eb_chan_res_ok; + result = op_result_complete; + /* Breaking here so that we skip the _unlock() call, because we unlock the spinlock outside + of our large if-statement. */ + break; + } else if (c->state == chanstate_cancelled) { + /* Reset the channel state back to _open */ + c->state = chanstate_open; + /* As long as we're not polling, we should try the op again */ + if (state->timeout != eb_nsec_zero) { + result = op_result_retry; + } else { + /* We're not telling the caller to retry, so signal a send since it can proceed now. */ + signal_recv = true; + } + /* Breaking here so that we skip the _unlock() call, because we unlock the spinlock outside + of our large if-statement. */ + break; + } + eb_spinlock_unlock(&c->lock); + } else if (eb_sys_ncores == 1) { + /* On uniprocessor machines, yield to the scheduler because we can't continue until another + thread updates the channel's state. */ + sched_yield(); + } + } + } else if (c->state == chanstate_ack && c->unbuf_op == op) { + /* A recv acknowledged our send! */ + /* Verify that the unbuf_state matches our 'id' parameter. If this assertion fails, it means there's likely + one eb_chan_op being shared by multiple threads, which isn't allowed. */ + eb_assert_or_bail(c->unbuf_state == state, "unbuf_state invalid"); + /* A recv is polling for chan's state to change, so update it to signal that we're done sending! */ + c->state = chanstate_done; + /* Set our op's state and our return value */ + op->res = eb_chan_res_ok; + result = op_result_complete; + } + + eb_spinlock_unlock(&c->lock); + + if (signal_recv) { + port_list_signal_first(c->recvs, state->port); + } + } else { + result = op_result_retry; + } + } + + return result; +} + +static inline op_result recv_unbuf(const do_state *state, eb_chan_op *op, size_t op_idx) { + assert(state); + assert(op); + assert(op->chan); + + eb_chan c = op->chan; + op_result result = op_result_next; + + if ((c->state == chanstate_open && state->timeout != eb_nsec_zero) || + c->state == chanstate_closed || + (c->state == chanstate_send && c->unbuf_state != state) || + (c->state == chanstate_recv && c->unbuf_op == op) || + (c->state == chanstate_ack && c->unbuf_op == op)) { + + /* It looks like our channel's in an acceptable state, so try to acquire the lock */ + if (eb_spinlock_try(&c->lock)) { + /* Reset the cleanup state since we acquired the lock and are actually getting a look at the channel's state */ + state->cleanup_ops[op_idx] = false; + + bool signal_send = false; + if (c->state == chanstate_open && state->timeout != eb_nsec_zero) { + c->state = chanstate_recv; + c->unbuf_state = state; + c->unbuf_op = op; + c->unbuf_port = state->port; + /* We need to cleanup after this since we put it in the _send state! */ + state->cleanup_ops[op_idx] = true; + /* Signal a send since one of them can continue now */ + signal_send = true; + } else if (c->state == chanstate_closed) { + /* Set our op's state and our return value */ + op->res = eb_chan_res_closed; + op->val = NULL; + result = op_result_complete; + } else if (c->state == chanstate_send && c->unbuf_state != state) { + /* We verified (immediately above) that the send isn't part of the same op pool (we can't do unbuffered + sends/recvs from the same _do() call) */ + + /* Sanity check -- make sure the op is a send */ + eb_assert_or_bail(c->unbuf_op->send, "Op isn't a send as expected"); + + /* Get the op's value. This needs to happen before we transition out of the _send state, otherwise the unbuf_op may no longer be valid! */ + op->val = c->unbuf_op->val; + /* Acknowledge the send */ + c->state = chanstate_ack; + /* Get a reference to the unbuf_port that needs to be signaled */ + eb_port signal_port = (c->unbuf_port ? eb_port_retain(c->unbuf_port) : NULL); + eb_spinlock_unlock(&c->lock); + + /* Wake up the send */ + if (signal_port) { + eb_port_signal(signal_port); + eb_port_release(signal_port); + signal_port = NULL; + } + + /* We have to cleanup all our ops here to cancel any outstanding unbuffered send/recvs, to avoid a deadlock + situation that arises when another _do() is waiting on our _do() to complete, but it never does because + we're about to wait for the other _do() to complete. */ + cleanup_ops(state); + + for (;;) { + if (*((volatile chanstate *)&c->state) != chanstate_ack) { + eb_spinlock_lock(&c->lock); + if (c->state == chanstate_done) { + /* Reset the channel state back to _open */ + c->state = chanstate_open; + /* We reset our state to _open, so signal a recv since it can proceed now. */ + signal_send = true; + /* Set our op's state and our return value */ + op->res = eb_chan_res_ok; + result = op_result_complete; + /* Breaking here so that we skip the _unlock() call, because we unlock the spinlock outside + of our large if-statement. */ + break; + } else if (c->state == chanstate_cancelled) { + /* Reset the channel state back to _open */ + c->state = chanstate_open; + /* As long as we're not polling, we should try the op again */ + if (state->timeout != eb_nsec_zero) { + result = op_result_retry; + } else { + /* We're not telling the caller to retry, so signal a recv since it can proceed now. */ + signal_send = true; + } + /* Breaking here so that we skip the _unlock() call, because we unlock the spinlock outside + of our large if-statement. */ + break; + } + eb_spinlock_unlock(&c->lock); + } else if (eb_sys_ncores == 1) { + /* On uniprocessor machines, yield to the scheduler because we can't continue until another + thread updates the channel's state. */ + sched_yield(); + } + } + } else if (c->state == chanstate_recv && c->unbuf_op == op) { + /* We own the recv op that's in progress, so assign chan's unbuf_port */ + /* Verify that the _recv_id matches our 'id' parameter. If this assertion fails, it means there's likely + one eb_chan_op being shared by multiple threads, which isn't allowed. */ + eb_assert_or_bail(c->unbuf_state == state, "unbuf_state invalid"); + /* Assign the port */ + c->unbuf_port = state->port; + /* We need to cleanup after this since we put it in the _send state! */ + state->cleanup_ops[op_idx] = true; + } else if (c->state == chanstate_ack && c->unbuf_op == op) { + /* A send acknowledged our recv! */ + /* Verify that the unbuf_state matches our 'id' parameter. If this assertion fails, it means there's likely + one eb_chan_op being shared by multiple threads, which isn't allowed. */ + eb_assert_or_bail(c->unbuf_state == state, "unbuf_state invalid"); + /* A send is polling for chan's state to change, so update it to signal that we're done sending! */ + c->state = chanstate_done; + /* Set our op's state and our return value */ + op->res = eb_chan_res_ok; + result = op_result_complete; + } + + eb_spinlock_unlock(&c->lock); + + if (signal_send) { + port_list_signal_first(c->sends, state->port); + } + } else { + result = op_result_retry; + } + } + + return result; +} + +static inline op_result try_op(const do_state *state, eb_chan_op *op, size_t op_idx) { + assert(state); + assert(op); + + eb_chan c = op->chan; + if (c) { + if (op->send) { + /* ## Send */ + return (c->buf_cap ? send_buf(state, op, op_idx) : send_unbuf(state, op, op_idx)); + } else { + /* ## Receive */ + return (c->buf_cap ? recv_buf(state, op, op_idx) : recv_unbuf(state, op, op_idx)); + } + } + return op_result_next; +} + +eb_chan_res eb_chan_send(eb_chan c, const void *val) { + eb_chan_op op = eb_chan_op_send(c, val); + eb_assert_or_bail(eb_chan_select(eb_nsec_forever, &op) == &op, "Invalid select() return value"); + return op.res; +} + +eb_chan_res eb_chan_try_send(eb_chan c, const void *val) { + eb_chan_op op = eb_chan_op_send(c, val); + eb_chan_op *r = eb_chan_select(eb_nsec_zero, &op); + eb_assert_or_bail(r == NULL || r == &op, "Invalid select() return value"); + return (r ? op.res : eb_chan_res_stalled); +} + +eb_chan_res eb_chan_recv(eb_chan c, const void **val) { + eb_chan_op op = eb_chan_op_recv(c); + eb_assert_or_bail(eb_chan_select(eb_nsec_forever, &op) == &op, "Invalid select() return value"); + if (op.res == eb_chan_res_ok && val) { + *val = op.val; + } + return op.res; +} + +eb_chan_res eb_chan_try_recv(eb_chan c, const void **val) { + eb_chan_op op = eb_chan_op_recv(c); + eb_chan_op *r = eb_chan_select(eb_nsec_zero, &op); + eb_assert_or_bail(r == NULL || r == &op, "Invalid select() return value"); + if (r && op.res == eb_chan_res_ok && val) { + *val = op.val; + } + return (r ? op.res : eb_chan_res_stalled); +} + +#pragma mark - Multiplexing - +#define next_idx(nops, delta, idx) (delta == 1 && idx == nops-1 ? 0 : ((delta == -1 && idx == 0) ? nops-1 : idx+delta)) +eb_chan_op *eb_chan_select_list(eb_nsec timeout, eb_chan_op *const ops[], size_t nops) { + assert(!nops || ops); + + const size_t k_attempt_multiplier = (eb_sys_ncores == 1 ? 1 : 500); + eb_nsec start_time = 0; + size_t idx_start = 0; + int8_t idx_delta = 0; + if (nops > 1) { + /* Assign idx_start/idx_delta, which control the op pseudo-randomization */ + start_time = eb_time_now(); + idx_start = (start_time/1000)%nops; + idx_delta = (!((start_time/10000)%2) ? 1 : -1); + } + + bool co[nops]; + memset(co, 0, sizeof(co)); + + eb_chan_op *result = NULL; + do_state state = { + .ops = ops, + .nops = nops, + .cleanup_ops = co, + .timeout = timeout, + .port = NULL}; + + if (timeout == eb_nsec_zero) { + /* ## timeout == 0: try every op exactly once; if none of them can proceed, return NULL. */ + size_t i, idx; + for (i = 0, idx = idx_start; i < nops; i++, idx = next_idx(nops, idx_delta, idx)) { + eb_chan_op *op = ops[idx]; + op_result r; + while ((r = try_op(&state, op, idx)) == op_result_retry) { + if (eb_sys_ncores == 1) { + /* On uniprocessor machines, yield to the scheduler because we can't continue until another + thread updates the channel's state. */ + sched_yield(); + } + } + + /* If the op completed, we need to exit! */ + if (r == op_result_complete) { + result = op; + goto cleanup; + } + } + } else { + /* ## timeout != 0 */ + if (timeout != eb_nsec_forever && !start_time) { + start_time = eb_time_now(); + } + + for (;;) { + /* ## Fast path: loop over our operations to see if one of them was able to send/receive. (If not, + we'll enter the slow path where we put our thread to sleep until we're signaled.) */ + size_t i, idx; + for (i = 0, idx = idx_start; i < k_attempt_multiplier*nops; i++, idx = next_idx(nops, idx_delta, idx)) { + eb_chan_op *op = ops[idx]; + op_result r = try_op(&state, op, idx); + /* If the op completed, we need to exit! */ + if (r == op_result_complete) { + result = op; + goto cleanup; + } + } + + /* ## Slow path: we weren't able to find an operation that could send/receive, so we'll create a + port to receive notifications on and put this thread to sleep until someone wakes us up. */ + if (!state.port) { + /* Create our port that we'll attach to channels so that we can be notified when events occur. */ + state.port = eb_port_create(); + eb_assert_or_recover(state.port, goto cleanup); + + /* Register our port for the appropriate notifications on every channel. */ + /* This adds 'port' to the channel's sends/recvs (depending on the op), which we clean up at the + end of this function. */ + size_t i; + for (i = 0; i < nops; i++) { + eb_chan_op *op = ops[i]; + eb_chan c = op->chan; + if (c) { + port_list_add((op->send ? c->sends : c->recvs), state.port); + } + } + } + + /* Before we go to sleep, call try_op() for every op until we get a non-busy return value. This way we'll ensure + that no op is actually able to be performed, and we'll also ensure that 'port' is registered as the 'unbuf_port' + for the necessary channels. */ + // size_t i, idx; + for (i = 0, idx = idx_start; i < nops; i++, idx = next_idx(nops, idx_delta, idx)) { + eb_chan_op *op = ops[idx]; + op_result r; + while ((r = try_op(&state, op, idx)) == op_result_retry) { + if (eb_sys_ncores == 1) { + /* On uniprocessor machines, yield to the scheduler because we can't continue until another + thread updates the channel's state. */ + sched_yield(); + } + } + + /* If the op completed, we need to exit! */ + if (r == op_result_complete) { + result = op; + goto cleanup; + } + } + + eb_nsec wait_timeout = eb_nsec_forever; + if (timeout != eb_nsec_forever) { + /* If we have a timeout, determine how much time has elapsed, because we may have timed-out. */ + eb_nsec elapsed = eb_time_now() - start_time; + /* Check if we timed-out */ + if (elapsed < timeout) { + wait_timeout = timeout - elapsed; + } else { + goto cleanup; + } + } + + /* Put our thread to sleep until someone alerts us of an event */ + eb_port_wait(state.port, wait_timeout); + } + } + + /* Cleanup! */ + cleanup: { + if (state.port) { + size_t i; + for (i = 0; i < nops; i++) { + eb_chan_op *op = ops[i]; + eb_chan c = op->chan; + if (c) { + port_list ports = (op->send ? c->sends : c->recvs); + port_list_rm(ports, state.port); + port_list_signal_first(ports, state.port); + } + } + } + + cleanup_ops(&state); + + if (state.port) { + eb_port_release(state.port); + state.port = NULL; + } + } + + return result; +} +#endif /* EB_CHAN_H */ diff --git a/circle.yml b/circle.yml new file mode 100644 index 0000000..2e965ff --- /dev/null +++ b/circle.yml @@ -0,0 +1,21 @@ +version: 2 + +jobs: + build: + docker: + # using custom image, see .circleci/images/primary/Dockerfile + - image: govgo/robotgoci:1.10.3 + working_directory: /gopath/src/github.com/robotn/gohook + steps: + - checkout + # specify any bash command here prefixed with `run: ` + - run: apt-get update + - run: apt-get -y install xvfb + # override: + # './...' is a relative pattern which means all subdirectories + # - run: go get -u golang.org/x/sys/unix + - run: go get -v -t -d ./... + - run: xvfb-run go test -v ./... + # codecov.io + # - run: go test -v -covermode=count -coverprofile=coverage.out + # - run: bash <(curl -s https://codecov.io/bash) diff --git a/event.go b/event.go new file mode 100644 index 0000000..3c8f01c --- /dev/null +++ b/event.go @@ -0,0 +1,150 @@ +// Copyright 2016 The go-vgo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// https://github.com/go-vgo/robotgo/blob/master/LICENSE +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +package hook + +import "strconv" + +/* + ___________ ____ _______ .__ __. .___________. +| ____\ \ / / | ____|| \ | | | | +| |__ \ \/ / | |__ | \| | `---| |----` +| __| \ / | __| | . ` | | | +| |____ \ / | |____ | |\ | | | +|_______| \__/ |_______||__| \__| |__| +*/ + +// AddEvent add event listener, +// +// parameters for the string type, +// the keyboard corresponding key parameters, +// +// mouse arguments: mleft, center, mright, wheelDown, wheelUp, +// wheelLeft, wheelRight. +// +// Use "hook.AddEvents()" or "gohook" add asynchronous event listener +func AddEvent(key string) bool { + var ( + // cs *C.char + mArr = []string{"mleft", "center", "mright", "wheelDown", + "wheelUp", "wheelLeft", "wheelRight"} + mouseBool bool + ) + + for i := 0; i < len(mArr); i++ { + if key == mArr[i] { + mouseBool = true + } + } + + if len(key) > 1 && !mouseBool { + key = strconv.Itoa(int(Keycode[key])) + } + + geve := addEvent(key) + // defer C.free(unsafe.Pointer(cs)) + return geve == 0 +} + +// AddEvents add global event hook +// +// hook.AddEvents("q") +// hook.AddEvents("q", "ctrl") +// hook.AddEvents("q", "ctrl", "shift") +func AddEvents(key string, arr ...string) bool { + s := Start() + // defer End() + + ct := false + k := 0 + for { + e := <-s + + l := len(arr) + if l > 0 { + for i := 0; i < l; i++ { + ukey := Keycode[arr[i]] + + if e.Kind == KeyHold && e.Keycode == ukey { + k++ + } + + if k == l { + ct = true + } + + if e.Kind == KeyUp && e.Keycode == ukey { + if k > 0 { + k-- + } + // time.Sleep(10 * time.Microsecond) + ct = false + } + } + } else { + ct = true + } + + if ct && e.Kind == KeyUp && e.Keycode == Keycode[key] { + End() + // k = 0 + break + } + } + + return true +} + +// AddMouse add mouse event hook +// +// mouse arguments: left, center, right, wheelDown, wheelUp, +// wheelLeft, wheelRight. +// +// hook.AddMouse("left") +// hook.AddMouse("left", 100, 100) +func AddMouse(btn string, x ...int16) bool { + s := Start() + ukey := MouseMap[btn] + + ct := false + for { + e := <-s + + if len(x) > 1 { + if e.Kind == MouseMove && e.X == x[0] && e.Y == x[1] { + ct = true + } + } else { + ct = true + } + + if ct && e.Kind == MouseDown && e.Button == ukey { + End() + break + } + } + + return true +} + +// AddMousePos add listen mouse event pos hook +func AddMousePos(x, y int16) bool { + s := Start() + + for { + e := <-s + if e.Kind == MouseMove && e.X == x && e.Y == y { + End() + break + } + } + + return true +} diff --git a/event/dispatch_proc.h b/event/dispatch_proc.h new file mode 100644 index 0000000..be18d0d --- /dev/null +++ b/event/dispatch_proc.h @@ -0,0 +1,220 @@ +// Copyright 2016 The go-vgo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// https://github.com/go-vgo/robotgo/blob/master/LICENSE +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#ifndef dispatch_proc_h +#define dispatch_proc_h + +// #include "pub.h" +// #include "../chan/eb_chan.h" + +void dispatch_proc(iohook_event * const event) { + if (!sending) { return; } + + // leaking memory? hope not + char* buffer = calloc(200, sizeof(char)); + + switch (event->type) { + case EVENT_HOOK_ENABLED: + case EVENT_HOOK_DISABLED: + sprintf(buffer, + "{\"id\":%i,\"time\":%" PRIu64 ",\"mask\":%hu,\"reserved\":%hu}", + event->type, event->time, event->mask,event->reserved); + break; // send it? + case EVENT_KEY_PRESSED: + case EVENT_KEY_RELEASED: + case EVENT_KEY_TYPED: + sprintf(buffer, + "{\"id\":%i,\"time\":%" PRIu64 ",\"mask\":%hu,\"reserved\":%hu,\"keycode\":%hu,\"rawcode\":%hu,\"keychar\":%d}", + event->type, event->time, event->mask,event->reserved, + event->data.keyboard.keycode, + event->data.keyboard.rawcode, + event->data.keyboard.keychar); + break; + case EVENT_MOUSE_PRESSED: + case EVENT_MOUSE_RELEASED: + case EVENT_MOUSE_CLICKED: + case EVENT_MOUSE_MOVED: + case EVENT_MOUSE_DRAGGED: + sprintf(buffer, + "{\"id\":%i,\"time\":%" PRIu64 ",\"mask\":%hu,\"reserved\":%hu,\"x\":%hd,\"y\":%hd,\"button\":%u,\"clicks\":%u}", + event->type, event->time, event->mask,event->reserved, + event->data.mouse.x, + event->data.mouse.y, + event->data.mouse.button, + event->data.mouse.clicks); + break; + case EVENT_MOUSE_WHEEL: + sprintf(buffer, + "{\"id\":%i,\"time\":%" PRIu64 ",\"mask\":%hu,\"reserved\":%hu,\"clicks\":%hu,\"x\":%hd,\"y\":%hd,\"type\":%d,\"ammount\":%hu,\"rotation\":%d,\"direction\":%d}", + event->type, event->time, event->mask, event->reserved, + event->data.wheel.clicks, + event->data.wheel.x, + event->data.wheel.y, + event->data.wheel.type, + event->data.wheel.amount, + event->data.wheel.rotation, + event->data.wheel.direction); + break; + default: + fprintf(stderr,"\nError on file: %s, unusual event->type: %i\n",__FILE__,event->type); + return; + } + + // to-do remove this for + int i; + for (i = 0; i < 5; i++) { + switch (eb_chan_try_send(events, buffer)) { // never block the hook callback + case eb_chan_res_ok: + i=5; + break; + default: + if (i == 4) { // let's not leak memory + free(buffer); + } + continue; + } + } + + // fprintf(stdout, "----%s\n", buffer); +} + +void dispatch_proc_end(iohook_event * const event) { + char buffer[256] = { 0 }; + size_t length = snprintf(buffer, sizeof(buffer), + "id=%i,when=%" PRIu64 ",mask=0x%X", + event->type, event->time, event->mask); + + switch (event->type) { + case EVENT_KEY_PRESSED: + // If the escape key is pressed, naturally terminate the program. + if (event->data.keyboard.keycode == VC_ESCAPE) { + // int status = hook_stop(); + // switch (status) { + // // System level errors. + // case IOHOOK_ERROR_OUT_OF_MEMORY: + // loggerProc(LOG_LEVEL_ERROR, "Failed to allocate memory. (%#X)", status); + // break; + + // case IOHOOK_ERROR_X_RECORD_GET_CONTEXT: + // // NOTE This is the only platform specific error that occurs on hook_stop(). + // loggerProc(LOG_LEVEL_ERROR, "Failed to get XRecord context. (%#X)", status); + // break; + + // // Default error. + // case IOHOOK_FAILURE: + // default: + // loggerProc(LOG_LEVEL_ERROR, "An unknown hook error occurred. (%#X)", status); + // break; + // } + } + case EVENT_KEY_RELEASED: + snprintf(buffer + length, sizeof(buffer) - length, + ",keycode=%u,rawcode=0x%X", + event->data.keyboard.keycode, event->data.keyboard.rawcode); + int key_code = (uint16_t) event->data.keyboard.keycode; + + if (event->data.keyboard.keycode == VC_ESCAPE + && atoi(cevent) == 11) { + int stopEvent = stop_event(); + // printf("stop_event%d\n", stopEvent); + cstatus = 0; + } + + // printf("atoi(str)---%d\n", atoi(cevent)); + if (key_code == atoi(cevent)) { + int stopEvent = stop_event(); + // printf("%d\n", stopEvent); + cstatus = 0; + } + break; + + case EVENT_KEY_TYPED: + snprintf(buffer + length, sizeof(buffer) - length, + ",keychar=%lc,rawcode=%u", + (uint16_t) event->data.keyboard.keychar, + event->data.keyboard.rawcode); + + #ifdef WE_REALLY_WANT_A_POINTER + char *buf = malloc (6); + #else + char buf[6]; + #endif + + sprintf(buf, "%lc", (uint16_t) event->data.keyboard.keychar); + + #ifdef WE_REALLY_WANT_A_POINTER + free (buf); + #endif + + if (strcmp(buf, cevent) == 0) { + int stopEvent = stop_event(); + // printf("%d\n", stopEvent); + cstatus = 0; + } + // return (char*) event->data.keyboard.keychar; + break; + + case EVENT_MOUSE_PRESSED: + case EVENT_MOUSE_RELEASED: + case EVENT_MOUSE_CLICKED: + case EVENT_MOUSE_MOVED: + case EVENT_MOUSE_DRAGGED: + snprintf(buffer + length, sizeof(buffer) - length, + ",x=%i,y=%i,button=%i,clicks=%i", + event->data.mouse.x, event->data.mouse.y, + event->data.mouse.button, event->data.mouse.clicks); + + int abutton = event->data.mouse.button; + int aclicks = event->data.mouse.clicks; + int amouse = -1; + + if (strcmp(cevent, "mleft") == 0) { + amouse = 1; + } + if (strcmp(cevent, "mright") == 0) { + amouse = 2; + } + if (strcmp(cevent, "center") == 0) { + amouse = 3; + } + if (strcmp(cevent, "wheelDown") == 0) { + amouse = 4; + } + if (strcmp(cevent, "wheelUp") == 0) { + amouse = 5; + } + if (strcmp(cevent, "wheelLeft") == 0) { + amouse = 6; + } + if (strcmp(cevent, "wheelRight") == 0) { + amouse = 7; + } + if (abutton == amouse && aclicks == 1) { + int stopEvent = stop_event(); + cstatus = 0; + } + + break; + + case EVENT_MOUSE_WHEEL: + snprintf(buffer + length, sizeof(buffer) - length, + ",type=%i,amount=%i,rotation=%i", + event->data.wheel.type, event->data.wheel.amount, + event->data.wheel.rotation); + break; + + default: + break; + } + + // fprintf(stdout, "----%s\n", buffer); +} + +#endif \ No newline at end of file diff --git a/event/goEvent.h b/event/goEvent.h new file mode 100644 index 0000000..e0db196 --- /dev/null +++ b/event/goEvent.h @@ -0,0 +1,173 @@ +// Copyright 2016 The go-vgo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// https://github.com/go-vgo/robotgo/blob/master/LICENSE +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#ifndef goevent_h +#define goevent_h +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include "pub.h" +// #include "../chan/eb_chan.h" +#include "dispatch_proc.h" + +void go_send(char*); +void go_sleep(void); + +void start_ev(){ + events = eb_chan_create(1024); + eb_chan_retain(events); + sending = true; + // add_event("q"); + add_event_async(); +} + +void pollEv(){ + if (events == NULL) { return; } + + for (;eb_chan_buf_len(events)!=0;) { + char* tmp; + if (eb_chan_try_recv(events, (const void**) &tmp) + == eb_chan_res_ok) { + // send a char + go_send(tmp); + free(tmp); + } else { + // + } + } +} + +void endPoll(){ + sending = false; + pollEv(); // remove last things from channel + eb_chan_release(events); +} + +int add_event(char *key_event) { + // (uint16_t *) + cevent = key_event; + add_hook(&dispatch_proc_end); + + return cstatus; +} + +void add_event_async(){ + add_hook(&dispatch_proc); +} + +int add_hook(dispatcher_t dispatch) { + // Set the logger callback for library output. + hook_set_logger(&loggerProc); + + // Set the event callback for IOhook events. + hook_set_dispatch_proc(dispatch); + + // Start the hook and block. + // NOTE If EVENT_HOOK_ENABLED was delivered, the status will always succeed. + int status = hook_run(); + + switch (status) { + case IOHOOK_SUCCESS: + // Everything is ok. + break; + + // System level errors. + case IOHOOK_ERROR_OUT_OF_MEMORY: + loggerProc(LOG_LEVEL_ERROR, "Failed to allocate memory. (%#X)", status); + break; + + + // X11 specific errors. + case IOHOOK_ERROR_X_OPEN_DISPLAY: + loggerProc(LOG_LEVEL_ERROR, "Failed to open X11 display. (%#X)", status); + break; + + case IOHOOK_ERROR_X_RECORD_NOT_FOUND: + loggerProc(LOG_LEVEL_ERROR, "Unable to locate XRecord extension. (%#X)", status); + break; + + case IOHOOK_ERROR_X_RECORD_ALLOC_RANGE: + loggerProc(LOG_LEVEL_ERROR, "Unable to allocate XRecord range. (%#X)", status); + break; + + case IOHOOK_ERROR_X_RECORD_CREATE_CONTEXT: + loggerProc(LOG_LEVEL_ERROR, "Unable to allocate XRecord context. (%#X)", status); + break; + + case IOHOOK_ERROR_X_RECORD_ENABLE_CONTEXT: + loggerProc(LOG_LEVEL_ERROR, "Failed to enable XRecord context. (%#X)", status); + break; + + + // Windows specific errors. + case IOHOOK_ERROR_SET_WINDOWS_HOOK_EX: + loggerProc(LOG_LEVEL_ERROR, "Failed to register low level windows hook. (%#X)", status); + break; + + + // Darwin specific errors. + case IOHOOK_ERROR_AXAPI_DISABLED: + loggerProc(LOG_LEVEL_ERROR, "Failed to enable access for assistive devices. (%#X)", status); + break; + + case IOHOOK_ERROR_CREATE_EVENT_PORT: + loggerProc(LOG_LEVEL_ERROR, "Failed to create apple event port. (%#X)", status); + break; + + case IOHOOK_ERROR_CREATE_RUN_LOOP_SOURCE: + loggerProc(LOG_LEVEL_ERROR, "Failed to create apple run loop source. (%#X)", status); + break; + + case IOHOOK_ERROR_GET_RUNLOOP: + loggerProc(LOG_LEVEL_ERROR, "Failed to acquire apple run loop. (%#X)", status); + break; + + case IOHOOK_ERROR_CREATE_OBSERVER: + loggerProc(LOG_LEVEL_ERROR, "Failed to create apple run loop observer. (%#X)", status); + break; + + // Default error. + case IOHOOK_FAILURE: + default: + loggerProc(LOG_LEVEL_ERROR, "An unknown hook error occurred. (%#X)", status); + break; + } + + return status; + // printf("%d\n", status); + // return cstatus; +} + +int stop_event(){ + int status = hook_stop(); + switch (status) { + // System level errors. + case IOHOOK_ERROR_OUT_OF_MEMORY: + loggerProc(LOG_LEVEL_ERROR, "Failed to allocate memory. (%#X)", status); + break; + + case IOHOOK_ERROR_X_RECORD_GET_CONTEXT: + // NOTE This is the only platform specific error that occurs on hook_stop(). + loggerProc(LOG_LEVEL_ERROR, "Failed to get XRecord context. (%#X)", status); + break; + + // Default error. + case IOHOOK_FAILURE: + default: + // loggerProc(LOG_LEVEL_ERROR, "An unknown hook error occurred. (%#X)", status); + break; + } + + return status; +} + +#endif \ No newline at end of file diff --git a/event/os.h b/event/os.h new file mode 100644 index 0000000..25bd8d7 --- /dev/null +++ b/event/os.h @@ -0,0 +1,48 @@ +#pragma once +#ifndef OS_H +#define OS_H + +/* Python versions under 2.5 don't support this macro, but it's not + * terribly difficult to replicate: */ +#ifndef PyModule_AddIntMacro + #define PyModule_AddIntMacro(module, macro) PyModule_AddIntConstant(module, #macro, macro) +#endif /* PyModule_AddIntMacro */ + +#if !defined(IS_MACOSX) && defined(__APPLE__) && defined(__MACH__) + #define IS_MACOSX +#endif /* IS_MACOSX */ + +#if !defined(IS_WINDOWS) && (defined(WIN32) || defined(_WIN32) || \ + defined(__WIN32__) || defined(__WINDOWS__) || defined(__CYGWIN__)) + #define IS_WINDOWS +#endif /* IS_WINDOWS */ + +#if !defined(USE_X11) && !defined(NUSE_X11) && !defined(IS_MACOSX) && !defined(IS_WINDOWS) + #define USE_X11 +#endif /* USE_X11 */ + +#if defined(IS_WINDOWS) +// #define STRICT /* Require use of exact types. */ + #define WIN32_LEAN_AND_MEAN 1 /* Speed up compilation. */ + #include +#elif !defined(IS_MACOSX) && !defined(USE_X11) + #error "Sorry, this platform isn't supported yet!" +#endif + +/* Interval to align by for large buffers (e.g. bitmaps). */ +/* Must be a power of 2. */ +#ifndef BYTE_ALIGN + #define BYTE_ALIGN 4 /* Bytes to align pixel buffers to. */ + /* #include */ + /* #define BYTE_ALIGN (sizeof(size_t)) */ +#endif /* BYTE_ALIGN */ + +#if BYTE_ALIGN == 0 + /* No alignment needed. */ + #define ADD_PADDING(width) (width) +#else + /* Aligns given width to padding. */ + #define ADD_PADDING(width) (BYTE_ALIGN + (((width) - 1) & ~(BYTE_ALIGN - 1))) +#endif + +#endif /* OS_H */ diff --git a/event/pub.h b/event/pub.h new file mode 100644 index 0000000..fc87386 --- /dev/null +++ b/event/pub.h @@ -0,0 +1,101 @@ +// Copyright 2016 The go-vgo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// https://github.com/go-vgo/robotgo/blob/master/LICENSE +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#include "os.h" + +#if defined(IS_MACOSX) + #include "../hook/darwin/input_c.h" + #include "../hook/darwin/hook_c.h" + #include "../hook/darwin/event_c.h" + #include "../hook/darwin/properties_c.h" +#elif defined(USE_X11) + //#define USE_XKBCOMMON 0 + #include "../hook/x11/input_c.h" + #include "../hook/x11/hook_c.h" + #include "../hook/x11/event_c.h" + #include "../hook/x11/properties_c.h" +#elif defined(IS_WINDOWS) + #include "../hook/windows/input_c.h" + #include "../hook/windows/hook_c.h" + #include "../hook/windows/event_c.h" + #include "../hook/windows/properties_c.h" +#endif + +#include +#include +#include +#include +#include + +#include "../hook/iohook.h" +#include "../chan/eb_chan.h" + +eb_chan events; +bool sending = false; + +int vccode[100]; +int codesz; + +char *cevent; +// uint16_t *cevent; +int cstatus = 1; +int event_status; +int rrevent; + +int add_hook(dispatcher_t dispatch); +void add_event_async(); +int add_event(char *key_event); +int stop_event(); + +void dispatch_proc_end(iohook_event * const event); +// int allEvent(char *key_event); +int allEvent(char *key_event, int vcode[], int size); + +// NOTE: The following callback executes on the same thread that hook_run() is called +// from. + +struct _MEvent { + uint8_t id; + size_t mask; + uint16_t keychar; + // char *keychar; + size_t x; + uint8_t y; + uint8_t bytesPerPixel; +}; + +typedef struct _MEvent MEvent; +// typedef MMBitmap *MMBitmapRef; +MEvent mEvent; + +bool loggerProc(unsigned int level, const char *format, ...) { + bool status = false; + + va_list args; + switch (level) { + #ifdef USE_DEBUG + case LOG_LEVEL_DEBUG: + case LOG_LEVEL_INFO: + va_start(args, format); + status = vfprintf(stdout, format, args) >= 0; + va_end(args); + break; + #endif + + case LOG_LEVEL_WARN: + case LOG_LEVEL_ERROR: + va_start(args, format); + status = vfprintf(stderr, format, args) >= 0; + va_end(args); + break; + } + + return status; +} \ No newline at end of file diff --git a/examples/event/main.go b/examples/event/main.go new file mode 100644 index 0000000..404aec0 --- /dev/null +++ b/examples/event/main.go @@ -0,0 +1,113 @@ +// Copyright 2016 The go-vgo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// https://github.com/go-vgo/robotgo/blob/master/LICENSE +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +package main + +import ( + "fmt" + + hook "github.com/robotn/gohook" +) + +func addEvent() { + fmt.Println("--- Please press ctrl + shift + q ---") + ok := hook.AddEvents("q", "ctrl", "shift") + if ok { + fmt.Println("add events...") + } + + fmt.Println("--- Please press w---") + ok = hook.AddEvents("w") + if ok { + fmt.Println("add events") + } + + // start hook + s := hook.Start() + // end hook + defer hook.End() + + for ev := range s { + fmt.Println("hook: ", ev) + } +} + +func addMouse() { + fmt.Println("--- Please press left mouse button ---") + ok := hook.AddMouse("left") + if ok { + fmt.Println("add mouse...") + } + + fmt.Println("--- Please press left mouse button and move mosue to 100,100 ---") + ok = hook.AddMouse("left", 100, 100) + if ok { + fmt.Println("add mouse and move to 100,100 ...") + } + + fmt.Println("--- Please move mosue to 100,100 ---") + ok = hook.AddMousePos(100, 100) + if ok { + fmt.Println(" move mouse to 100,100 ...") + } +} + +func add() { + fmt.Println("--- Please press v---") + eve := hook.AddEvent("v") + + if eve { + fmt.Println("--- You press v---", "v") + } + + fmt.Println("--- Please press k---") + keve := hook.AddEvent("k") + if keve { + fmt.Println("--- You press k---", "k") + } + + fmt.Println("--- Please press f1---") + feve := hook.AddEvent("f1") + if feve { + fmt.Println("You press...", "f1") + } +} + +func event() { + //////////////////////////////////////////////////////////////////////////////// + // Global event listener + //////////////////////////////////////////////////////////////////////////////// + + add() + + fmt.Println("--- Please press left mouse button---") + mleft := hook.AddEvent("mleft") + if mleft { + fmt.Println("--- You press left mouse button---", "mleft") + } + + mright := hook.AddEvent("mright") + if mright { + fmt.Println("--- You press right mouse button---", "mright") + } + + // stop AddEvent + // hook.StopEvent() +} + +func main() { + fmt.Println("test begin...") + + addEvent() + + addMouse() + + event() +} diff --git a/examples/main.go b/examples/main.go new file mode 100644 index 0000000..2a1ab8b --- /dev/null +++ b/examples/main.go @@ -0,0 +1,80 @@ +package main + +import ( + "fmt" + + hook "github.com/robotn/gohook" +) + +func registerEvent() { + fmt.Println("--- Please press ctrl + shift + q to stop hook ---") + hook.Register(hook.KeyDown, []string{"q", "ctrl", "shift"}, func(e hook.Event) { + fmt.Println("ctrl-shift-q") + hook.End() + }) + + fmt.Println("--- Please press w ---") + hook.Register(hook.KeyDown, []string{"w"}, func(e hook.Event) { + fmt.Println("w-") + }) + + s := hook.Start() + <-hook.Process(s) +} + +func addMouse() { + fmt.Println("--- Please press left mouse button to see it's position and the right mouse button to exit ---") + hook.Register(hook.MouseDown, []string{}, func(e hook.Event) { + if e.Button == hook.MouseMap["left"] { + fmt.Printf("mouse left @ %v - %v\n", e.X, e.Y) + } else if e.Button == hook.MouseMap["right"] { + hook.End() + } + }) + + s := hook.Start() + <-hook.Process(s) +} + +// hook listen and return values using detailed examples +func add() { + fmt.Println("hook add...") + s := hook.Start() + defer hook.End() + + ct := false + for { + i := <-s + + if i.Kind == hook.KeyHold && i.Rawcode == 59 { + ct = true + } + + if ct && i.Rawcode == 12 { + break + } + } +} + +// base hook example +func base() { + fmt.Println("hook start...") + evChan := hook.Start() + defer hook.End() + + for ev := range evChan { + fmt.Println("hook: ", ev) + if ev.Keychar == 'q' { + break + } + } +} + +func main() { + registerEvent() + + base() + + add() + addMouse() +} diff --git a/extern.go b/extern.go new file mode 100644 index 0000000..0e12a83 --- /dev/null +++ b/extern.go @@ -0,0 +1,40 @@ +package hook + +/* + +// #include "event/hook_async.h" +*/ +import "C" + +import ( + "log" + "time" + + "encoding/json" +) + +//export go_send +func go_send(s *C.char) { + str := []byte(C.GoString(s)) + out := Event{} + + err := json.Unmarshal(str, &out) + if err != nil { + log.Fatal("json.Unmarshal error is: ", err) + } + + if out.Keychar != CharUndefined { + lck.Lock() + raw2key[out.Rawcode] = string([]rune{out.Keychar}) + lck.Unlock() + } + + // todo bury this deep into the C lib so that the time is correct + out.When = time.Now() // at least it's consistent + if err != nil { + log.Fatal("json.Unmarshal error is: ", err) + } + + // todo: maybe make non-bloking + ev <- out +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b1cdc6b --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module gohook + +go 1.22 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..eb3eede --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +github.com/vcaesar/keycode v0.10.1 h1:0DesGmMAPWpYTCYddOFiCMKCDKgNnwiQa2QXindVUHw= +github.com/vcaesar/keycode v0.10.1/go.mod h1:JNlY7xbKsh+LAGfY2j4M3znVrGEm5W1R8s/Uv6BJcfQ= +github.com/vcaesar/tt v0.20.0 h1:9t2Ycb9RNHcP0WgQgIaRKJBB+FrRdejuaL6uWIHuoBA= +github.com/vcaesar/tt v0.20.0/go.mod h1:GHPxQYhn+7OgKakRusH7KJ0M5MhywoeLb8Fcffs/Gtg= diff --git a/hook.go b/hook.go new file mode 100644 index 0000000..fc625ec --- /dev/null +++ b/hook.go @@ -0,0 +1,294 @@ +// Copyright 2016 The go-vgo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// https://github.com/go-vgo/robotgo/blob/master/LICENSE +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +package hook + +/* +#cgo darwin CFLAGS: -x objective-c -Wno-deprecated-declarations +#cgo darwin LDFLAGS: -framework Cocoa + +#cgo linux CFLAGS:-I/usr/src +#cgo linux LDFLAGS: -L/usr/src -lX11 -lXtst +#cgo linux LDFLAGS: -lX11-xcb -lxcb -lxcb-xkb -lxkbcommon -lxkbcommon-x11 +//#cgo windows LDFLAGS: -lgdi32 -luser32 + +#include "event/goEvent.h" +*/ +import "C" + +import ( + "fmt" + "sync" + "time" + "unsafe" +) + +const ( + // Version get the gohook version + Version = "v0.40.0.123, Sierra Nevada!" + + // HookEnabled honk enable status + HookEnabled = 1 // iota + HookDisabled = 2 + + KeyDown = 3 + KeyHold = 4 + KeyUp = 5 + + MouseUp = 6 + MouseHold = 7 + MouseDown = 8 + MouseMove = 9 + MouseDrag = 10 + MouseWheel = 11 + + FakeEvent = 12 + + // Keychar could be v + CharUndefined = 0xFFFF + WheelUp = -1 + WheelDown = 1 +) + +// Event Holds a system event +// +// If it's a Keyboard event the relevant fields are: +// Mask, Keycode, Rawcode, and Keychar, +// Keychar is probably what you want. +// +// If it's a Mouse event the relevant fields are: +// Button, Clicks, X, Y, Amount, Rotation and Direction +type Event struct { + Kind uint8 `json:"id"` + When time.Time + Mask uint16 `json:"mask"` + Reserved uint16 `json:"reserved"` + + Keycode uint16 `json:"keycode"` + Rawcode uint16 `json:"rawcode"` + Keychar rune `json:"keychar"` + + Button uint16 `json:"button"` + Clicks uint16 `json:"clicks"` + + X int16 `json:"x"` + Y int16 `json:"y"` + + Amount uint16 `json:"amount"` + Rotation int32 `json:"rotation"` + Direction uint8 `json:"direction"` +} + +var ( + ev = make(chan Event, 1024) + asyncon = false + + lck sync.RWMutex + + pressed = make(map[uint16]bool, 256) + uppressed = make(map[uint16]bool, 256) + used = []int{} + + keys = map[int][]uint16{} + upkeys = map[int][]uint16{} + cbs = map[int]func(Event){} + events = map[uint8][]int{} +) + +func allPressed(pressed map[uint16]bool, keys ...uint16) bool { + for _, i := range keys { + // fmt.Println(i) + if !pressed[i] { + return false + } + } + + return true +} + +// Register register gohook event +func Register(when uint8, cmds []string, cb func(Event)) { + key := len(used) + used = append(used, key) + tmp := []uint16{} + + uptmp := []uint16{} + + for _, v := range cmds { + if when == KeyUp { + uptmp = append(uptmp, Keycode[v]) + } + tmp = append(tmp, Keycode[v]) + } + + keys[key] = tmp + upkeys[key] = uptmp + cbs[key] = cb + events[when] = append(events[when], key) + // return +} + +// Process return go hook process +func Process(evChan <-chan Event) (out chan bool) { + out = make(chan bool) + go func() { + for ev := range evChan { + if ev.Kind == KeyDown || ev.Kind == KeyHold { + pressed[ev.Keycode] = true + uppressed[ev.Keycode] = true + } else if ev.Kind == KeyUp { + pressed[ev.Keycode] = false + } + + for _, v := range events[ev.Kind] { + if !asyncon { + break + } + + if allPressed(pressed, keys[v]...) { + cbs[v](ev) + } else if ev.Kind == KeyUp { + //uppressed[ev.Keycode] = true + if allPressed(uppressed, upkeys[v]...) { + uppressed = make(map[uint16]bool, 256) + cbs[v](ev) + } + + } + } + } + + // fmt.Println("exiting after end (process)") + out <- true + }() + + return +} + +// String return formatted hook kind string +func (e Event) String() string { + switch e.Kind { + case HookEnabled: + return fmt.Sprintf("%v - Event: {Kind: HookEnabled}", e.When) + case HookDisabled: + return fmt.Sprintf("%v - Event: {Kind: HookDisabled}", e.When) + case KeyUp: + return fmt.Sprintf("%v - Event: {Kind: KeyUp, Rawcode: %v, Keychar: %v}", + e.When, e.Rawcode, e.Keychar) + case KeyHold: + return fmt.Sprintf( + "%v - Event: {Kind: KeyHold, Rawcode: %v, Keychar: %v}", + e.When, e.Rawcode, e.Keychar) + case KeyDown: + return fmt.Sprintf( + "%v - Event: {Kind: KeyDown, Rawcode: %v, Keychar: %v}", + e.When, e.Rawcode, e.Keychar) + case MouseUp: + return fmt.Sprintf( + "%v - Event: {Kind: MouseUp, Button: %v, X: %v, Y: %v, Clicks: %v}", + e.When, e.Button, e.X, e.Y, e.Clicks) + case MouseHold: + return fmt.Sprintf( + "%v - Event: {Kind: MouseHold, Button: %v, X: %v, Y: %v, Clicks: %v}", + e.When, e.Button, e.X, e.Y, e.Clicks) + case MouseDown: + return fmt.Sprintf( + "%v - Event: {Kind: MouseDown, Button: %v, X: %v, Y: %v, Clicks: %v}", + e.When, e.Button, e.X, e.Y, e.Clicks) + case MouseMove: + return fmt.Sprintf( + "%v - Event: {Kind: MouseMove, Button: %v, X: %v, Y: %v, Clicks: %v}", + e.When, e.Button, e.X, e.Y, e.Clicks) + case MouseDrag: + return fmt.Sprintf( + "%v - Event: {Kind: MouseDrag, Button: %v, X: %v, Y: %v, Clicks: %v}", + e.When, e.Button, e.X, e.Y, e.Clicks) + case MouseWheel: + return fmt.Sprintf( + "%v - Event: {Kind: MouseWheel, Amount: %v, Rotation: %v, Direction: %v}", + e.When, e.Amount, e.Rotation, e.Direction) + case FakeEvent: + return fmt.Sprintf("%v - Event: {Kind: FakeEvent}", e.When) + } + + return "Unknown event, contact the mantainers." +} + +// RawcodetoKeychar rawcode to keychar +func RawcodetoKeychar(r uint16) string { + lck.RLock() + defer lck.RUnlock() + + return raw2key[r] +} + +// KeychartoRawcode key char to rawcode +func KeychartoRawcode(kc string) uint16 { + return keytoraw[kc] +} + +// Start adds global event hook to OS +// returns event channel +func Start() chan Event { + ev = make(chan Event, 1024) + go C.start_ev() + + asyncon = true + go func() { + for { + if !asyncon { + return + } + + C.pollEv() + time.Sleep(time.Millisecond * 50) + //todo: find smallest time that does not destroy the cpu utilization + } + }() + + return ev +} + +// End removes global event hook +func End() { + asyncon = false + C.endPoll() + C.stop_event() + time.Sleep(time.Millisecond * 10) + + for len(ev) != 0 { + <-ev + } + close(ev) + + pressed = make(map[uint16]bool, 256) + uppressed = make(map[uint16]bool, 256) + used = []int{} + + keys = map[int][]uint16{} + cbs = map[int]func(Event){} + events = map[uint8][]int{} +} + +// AddEvent add the block event listener +func addEvent(key string) int { + cs := C.CString(key) + defer C.free(unsafe.Pointer(cs)) + + eve := C.add_event(cs) + geve := int(eve) + + return geve +} + +// StopEvent stop the block event listener +func StopEvent() { + C.stop_event() +} diff --git a/hook/LICENSE b/hook/LICENSE new file mode 100644 index 0000000..9cecc1d --- /dev/null +++ b/hook/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) {year} {name of author} + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + {project} Copyright (C) {year} {fullname} + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/hook/darwin/event_c.h b/hook/darwin/event_c.h new file mode 100644 index 0000000..e6ab219 --- /dev/null +++ b/hook/darwin/event_c.h @@ -0,0 +1,233 @@ +/* Copyright (C) 2006-2017 Alexander Barker. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. +*/ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include +#include +#include +#include "../iohook.h" + +// #include "../logger_c.h" +#include "input.h" + +// TODO Possibly relocate to input helper. +static inline CGEventFlags get_key_event_mask(iohook_event * const event) { + CGEventFlags native_mask = 0x00; + + if (event->mask & (MASK_SHIFT)) { native_mask |= kCGEventFlagMaskShift; } + if (event->mask & (MASK_CTRL)) { native_mask |= kCGEventFlagMaskControl; } + if (event->mask & (MASK_META)) { native_mask |= kCGEventFlagMaskControl; } + if (event->mask & (MASK_ALT)) { native_mask |= kCGEventFlagMaskAlternate; } + + if (event->type == EVENT_KEY_PRESSED || event->type == EVENT_KEY_RELEASED || event->type == EVENT_KEY_TYPED) { + switch (event->data.keyboard.keycode) { + case VC_KP_0: + case VC_KP_1: + case VC_KP_2: + case VC_KP_3: + case VC_KP_4: + case VC_KP_5: + case VC_KP_6: + case VC_KP_7: + case VC_KP_8: + case VC_KP_9: + + case VC_NUM_LOCK: + case VC_KP_ENTER: + case VC_KP_MULTIPLY: + case VC_KP_ADD: + case VC_KP_SEPARATOR: + case VC_KP_SUBTRACT: + case VC_KP_DIVIDE: + case VC_KP_COMMA: + native_mask |= kCGEventFlagMaskNumericPad; + break; + } + } + + return native_mask; +} + +static inline void post_key_event(iohook_event * const event) { + bool is_pressed = event->type == EVENT_KEY_PRESSED; + + CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + CGEventRef cg_event = CGEventCreateKeyboardEvent(src, + (CGKeyCode) scancode_to_keycode(event->data.keyboard.keycode), + is_pressed); + + CGEventSetFlags(cg_event, get_key_event_mask(event)); + CGEventPost(kCGHIDEventTap, cg_event); // kCGSessionEventTap also works. + CFRelease(cg_event); + CFRelease(src); +} + +static inline void post_mouse_button_event(iohook_event * const event, bool is_pressed) { + CGMouseButton mouse_button; + CGEventType mouse_type; + if (event->data.mouse.button == MOUSE_BUTTON1) { + if (is_pressed) { + mouse_type = kCGEventLeftMouseDown; + } else { + mouse_type = kCGEventLeftMouseUp; + } + mouse_button = kCGMouseButtonLeft; + } else if (event->data.mouse.button == MOUSE_BUTTON2) { + if (is_pressed) { + mouse_type = kCGEventRightMouseDown; + } else { + mouse_type = kCGEventRightMouseUp; + } + mouse_button = kCGMouseButtonRight; + } else { + if (is_pressed) { + mouse_type = kCGEventOtherMouseDown; + } else { + mouse_type = kCGEventOtherMouseUp; + } + mouse_button = event->data.mouse.button - 1; + } + + CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + CGEventRef cg_event = CGEventCreateMouseEvent(src, + mouse_type, + CGPointMake( + (CGFloat) event->data.mouse.x, + (CGFloat) event->data.mouse.y + ), + mouse_button + ); + CGEventPost(kCGHIDEventTap, cg_event); // kCGSessionEventTap also works. + CFRelease(cg_event); + CFRelease(src); +} + +static inline void post_mouse_wheel_event(iohook_event * const event) { + // FIXME Should I create a source event with the coords? + // It seems to automagically use the current location of the cursor. + // Two options: Query the mouse, move it to x/y, scroll, then move back + // OR disable x/y for scroll events on Windows & X11. + CGScrollEventUnit scroll_unit; + if (event->data.wheel.type == WHEEL_BLOCK_SCROLL) { + // Scrolling data is line-based. + scroll_unit = kCGScrollEventUnitLine; + } else { + // Scrolling data is pixel-based. + scroll_unit = kCGScrollEventUnitPixel; + } + + CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + CGEventRef cg_event = CGEventCreateScrollWheelEvent(src, + kCGScrollEventUnitLine, + // TODO Currently only support 1 wheel axis. + (CGWheelCount) 1, // 1 for Y-only, 2 for Y-X, 3 for Y-X-Z + event->data.wheel.amount * event->data.wheel.rotation); + + CGEventPost(kCGHIDEventTap, cg_event); // kCGSessionEventTap also works. + CFRelease(cg_event); + CFRelease(src); +} + +static inline void post_mouse_motion_event(iohook_event * const event) { + CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + CGEventRef cg_event; + if (event->mask >> 8 == 0x00) { + // No mouse flags. + cg_event = CGEventCreateMouseEvent(src, + kCGEventMouseMoved, + CGPointMake( + (CGFloat) event->data.mouse.x, + (CGFloat) event->data.mouse.y + ), + 0 + ); + } else if (event->mask & MASK_BUTTON1) { + cg_event = CGEventCreateMouseEvent(src, + kCGEventLeftMouseDragged, + CGPointMake( + (CGFloat) event->data.mouse.x, + (CGFloat) event->data.mouse.y + ), + kCGMouseButtonLeft + ); + } else if (event->mask & MASK_BUTTON2) { + cg_event = CGEventCreateMouseEvent(src, + kCGEventRightMouseDragged, + CGPointMake( + (CGFloat) event->data.mouse.x, + (CGFloat) event->data.mouse.y + ), + kCGMouseButtonRight + ); + } else { + cg_event = CGEventCreateMouseEvent(src, + kCGEventOtherMouseDragged, + CGPointMake( + (CGFloat) event->data.mouse.x, + (CGFloat) event->data.mouse.y + ), + (event->mask >> 8) - 1 + ); + } + + // kCGSessionEventTap also works. + CGEventPost(kCGHIDEventTap, cg_event); + CFRelease(cg_event); + CFRelease(src); +} + +IOHOOK_API void hook_post_event(iohook_event * const event) { + switch (event->type) { + case EVENT_KEY_PRESSED: + case EVENT_KEY_RELEASED: + post_key_event(event); + break; + + + case EVENT_MOUSE_PRESSED: + post_mouse_button_event(event, true); + break; + + case EVENT_MOUSE_RELEASED: + post_mouse_button_event(event, false); + break; + + case EVENT_MOUSE_CLICKED: + post_mouse_button_event(event, true); + post_mouse_button_event(event, false); + break; + + case EVENT_MOUSE_WHEEL: + post_mouse_wheel_event(event); + break; + + + case EVENT_MOUSE_MOVED: + case EVENT_MOUSE_DRAGGED: + post_mouse_motion_event(event); + break; + + + case EVENT_KEY_TYPED: + // FIXME Ignoreing EVENT_KEY_TYPED events. + + case EVENT_HOOK_ENABLED: + case EVENT_HOOK_DISABLED: + // Ignore hook enabled / disabled events. + + default: + // Ignore any other garbage. + logger(LOG_LEVEL_WARN, "%s [%u]: Ignoring post event type %#X\n", + __FUNCTION__, __LINE__, event->type); + break; + } +} diff --git a/hook/darwin/hook_c.h b/hook/darwin/hook_c.h new file mode 100644 index 0000000..f4da3c1 --- /dev/null +++ b/hook/darwin/hook_c.h @@ -0,0 +1,1316 @@ +/* Copyright (C) 2006-2017 Alexander Barker. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. +*/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#ifndef USE_WEAK_IMPORT + #include +#endif +#include +#ifdef USE_OBJC + #include + #include +#endif + +#include +#include +#include +#include "../iohook.h" +// #include "../logger_c.h" +#include "input.h" + +typedef struct _hook_info { + CFMachPortRef port; + CFRunLoopSourceRef source; + CFRunLoopObserverRef observer; +} hook_info; + +#ifdef USE_OBJC +static id auto_release_pool; +#endif + +// Event runloop reference. +CFRunLoopRef event_loop; + +// Flag to restart the event tap incase of timeout. +static Boolean restart_tap = false; + +// Modifiers for tracking key masks. +static uint16_t current_modifiers = 0x0000; + +// Required to transport messages between the main runloop and our thread for +// Unicode lookups. +#define KEY_BUFFER_SIZE 4 +typedef struct { + CGEventRef event; + UniChar buffer[KEY_BUFFER_SIZE]; + UniCharCount length; +} TISMessage; +TISMessage *tis_message; + +#ifdef USE_WEAK_IMPORT +// Required to dynamically check for AXIsProcessTrustedWithOptions availability. +extern void dispatch_get_main_queue() __attribute__((weak_import)); +extern void dispatch_sync_f(dispatch_queue_t queue, void *context, void (*function)(void *)) __attribute__((weak_import)); +#else +#if __MAC_OS_X_VERSION_MAX_ALLOWED <= 1050 +typedef void* dispatch_queue_t; +#endif +static dispatch_queue_t (*dispatch_get_main_queue_f)(); +static void (*dispatch_sync_f_f)(dispatch_queue_t, void *, void (*function)(void *)); +#endif + +#if ! defined(USE_CARBON_LEGACY) && defined(USE_COREFOUNDATION) +static CFRunLoopSourceRef src_msg_port; +static CFRunLoopObserverRef observer; + +static pthread_cond_t msg_port_cond = PTHREAD_COND_INITIALIZER; +static pthread_mutex_t msg_port_mutex = PTHREAD_MUTEX_INITIALIZER; +#endif + +// Click count globals. +static unsigned short click_count = 0; +static CGEventTimestamp click_time = 0; +static unsigned short int click_button = MOUSE_NOBUTTON; +static bool mouse_dragged = false; + +// Structure for the current Unix epoch in milliseconds. +static struct timeval system_time; + +// Virtual event pointer. +static iohook_event event; + +// Event dispatch callback. +static dispatcher_t dispatcher = NULL; + +IOHOOK_API void hook_set_dispatch_proc(dispatcher_t dispatch_proc) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: Setting new dispatch callback to %#p.\n", + __FUNCTION__, __LINE__, dispatch_proc); + + dispatcher = dispatch_proc; +} + +// Send out an event if a dispatcher was set. +static inline void dispatch_event(iohook_event *const event) { + if (dispatcher != NULL) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: Dispatching event type %u.\n", + __FUNCTION__, __LINE__, event->type); + + dispatcher(event); + } else { + logger(LOG_LEVEL_WARN, "%s [%u]: No dispatch callback set!\n", + __FUNCTION__, __LINE__); + } +} + + +// Set the native modifier mask for future events. +static inline void set_modifier_mask(uint16_t mask) { + current_modifiers |= mask; +} + +// Unset the native modifier mask for future events. +static inline void unset_modifier_mask(uint16_t mask) { + current_modifiers ^= mask; +} + +// Get the current native modifier mask state. +static inline uint16_t get_modifiers() { + return current_modifiers; +} + +// Initialize the modifier mask to the current modifiers. +static void initialize_modifiers() { + current_modifiers = 0x0000; + + if (CGEventSourceKeyState(kCGEventSourceStateCombinedSessionState, kVK_Shift)) { + set_modifier_mask(MASK_SHIFT_L); + } + if (CGEventSourceKeyState(kCGEventSourceStateCombinedSessionState, kVK_RightShift)) { + set_modifier_mask(MASK_SHIFT_R); + } + if (CGEventSourceKeyState(kCGEventSourceStateCombinedSessionState, kVK_Control)) { + set_modifier_mask(MASK_CTRL_L); + } + if (CGEventSourceKeyState(kCGEventSourceStateCombinedSessionState, kVK_RightControl)) { + set_modifier_mask(MASK_CTRL_R); + } + if (CGEventSourceKeyState(kCGEventSourceStateCombinedSessionState, kVK_Option)) { + set_modifier_mask(MASK_ALT_L); + } + if (CGEventSourceKeyState(kCGEventSourceStateCombinedSessionState, kVK_RightOption)) { + set_modifier_mask(MASK_ALT_R); + } + if (CGEventSourceKeyState(kCGEventSourceStateCombinedSessionState, kVK_Command)) { + set_modifier_mask(MASK_META_L); + } + if (CGEventSourceKeyState(kCGEventSourceStateCombinedSessionState, kVK_RightCommand)) { + set_modifier_mask(MASK_META_R); + } + + if (CGEventSourceButtonState(kCGEventSourceStateCombinedSessionState, kVK_LBUTTON)) { + set_modifier_mask(MASK_BUTTON1); + } + if (CGEventSourceButtonState(kCGEventSourceStateCombinedSessionState, kVK_RBUTTON)) { + set_modifier_mask(MASK_BUTTON2); + } + if (CGEventSourceButtonState(kCGEventSourceStateCombinedSessionState, kVK_MBUTTON)) { + set_modifier_mask(MASK_BUTTON3); + } + if (CGEventSourceButtonState(kCGEventSourceStateCombinedSessionState, kVK_XBUTTON1)) { + set_modifier_mask(MASK_BUTTON4); + } + if (CGEventSourceButtonState(kCGEventSourceStateCombinedSessionState, kVK_XBUTTON2)) { + set_modifier_mask(MASK_BUTTON5); + } + + if (CGEventSourceFlagsState(kCGEventSourceStateCombinedSessionState) & kCGEventFlagMaskAlphaShift) { + set_modifier_mask(MASK_CAPS_LOCK); + } + // Best I can tell, OS X does not support Num or Scroll lock. + unset_modifier_mask(MASK_NUM_LOCK); + unset_modifier_mask(MASK_SCROLL_LOCK); +} + + +// Wrap keycode_to_unicode with some null checks. +static void keycode_to_lookup(void *info) { + TISMessage *data = (TISMessage *) info; + + if (data != NULL && data->event != NULL) { + // Preform Unicode lookup. + data->length = keycode_to_unicode(data->event, data->buffer, KEY_BUFFER_SIZE); + } +} + +#if ! defined(USE_CARBON_LEGACY) && defined(USE_COREFOUNDATION) +void message_port_status_proc(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) { + switch (activity) { + case kCFRunLoopExit: + // Acquire a lock on the msg_port and signal that anyone waiting + // should continue. + pthread_mutex_lock(&msg_port_mutex); + pthread_cond_broadcast(&msg_port_cond); + pthread_mutex_unlock(&msg_port_mutex); + break; + + default: + logger(LOG_LEVEL_WARN, "%s [%u]: Unhandled RunLoop activity! (%#X)\n", + __FUNCTION__, __LINE__, (unsigned int) activity); + break; + } +} + +// Runloop to execute KeyCodeToString on the "Main" runloop due to an +// undocumented thread safety requirement. +static void message_port_proc(void *info) { + // Lock the msg_port mutex as we enter the main runloop. + pthread_mutex_lock(&msg_port_mutex); + + keycode_to_lookup(info); + + // Unlock the msg_port mutex to signal to the hook_thread that we have + // finished on the main runloop. + pthread_cond_broadcast(&msg_port_cond); + pthread_mutex_unlock(&msg_port_mutex); +} + +static int start_message_port_runloop() { + int status = IOHOOK_FAILURE; + + if (tis_message != NULL) { + // Create a runloop observer for the main runloop. + observer = CFRunLoopObserverCreate( + kCFAllocatorDefault, + kCFRunLoopExit, //kCFRunLoopEntry | kCFRunLoopExit, //kCFRunLoopAllActivities, + true, + 0, + message_port_status_proc, + NULL + ); + + if (observer != NULL) { + pthread_mutex_lock(&msg_port_mutex); + + CFRunLoopSourceContext context = { + .version = 0, + .info = tis_message, + .retain = NULL, + .release = NULL, + .copyDescription = NULL, + .equal = NULL, + .hash = NULL, + .schedule = NULL, + .cancel = NULL, + .perform = message_port_proc + }; + + CFRunLoopRef main_loop = CFRunLoopGetMain(); + + src_msg_port = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context); + if (src_msg_port != NULL) { + CFRunLoopAddSource(main_loop, src_msg_port, kCFRunLoopDefaultMode); + CFRunLoopAddObserver(main_loop, observer, kCFRunLoopDefaultMode); + + logger(LOG_LEVEL_DEBUG, "%s [%u]: Successful.\n", + __FUNCTION__, __LINE__); + + status = IOHOOK_SUCCESS; + } else { + logger(LOG_LEVEL_ERROR, "%s [%u]: CFRunLoopSourceCreate failure!\n", + __FUNCTION__, __LINE__); + + status = IOHOOK_ERROR_CREATE_RUN_LOOP_SOURCE; + } + + pthread_mutex_unlock(&msg_port_mutex); + } else { + logger(LOG_LEVEL_ERROR, "%s [%u]: CFRunLoopObserverCreate failure!\n", + __FUNCTION__, __LINE__); + + status = IOHOOK_ERROR_CREATE_OBSERVER; + } + } else { + logger(LOG_LEVEL_ERROR, "%s [%u]: No available TIS Message pointer.\n", + __FUNCTION__, __LINE__); + } + + return status; +} + +static void stop_message_port_runloop() { + CFRunLoopRef main_loop = CFRunLoopGetMain(); + + if (CFRunLoopContainsObserver(main_loop, observer, kCFRunLoopDefaultMode)) { + CFRunLoopRemoveObserver(main_loop, observer, kCFRunLoopDefaultMode); + CFRunLoopObserverInvalidate(observer); + } + + if (CFRunLoopContainsSource(main_loop, src_msg_port, kCFRunLoopDefaultMode)) { + CFRunLoopRemoveSource(main_loop, src_msg_port, kCFRunLoopDefaultMode); + CFRelease(src_msg_port); + } + + observer = NULL; + src_msg_port = NULL; + + logger(LOG_LEVEL_DEBUG, "%s [%u]: Successful.\n", + __FUNCTION__, __LINE__); +} +#endif + +static void hook_status_proc(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) { + uint64_t timestamp = mach_absolute_time(); + + switch (activity) { + case kCFRunLoopEntry: + // Populate the hook start event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_HOOK_ENABLED; + event.mask = 0x00; + + // Fire the hook start event. + dispatch_event(&event); + break; + + case kCFRunLoopExit: + // Populate the hook stop event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_HOOK_DISABLED; + event.mask = 0x00; + + // Fire the hook stop event. + dispatch_event(&event); + break; + + default: + logger(LOG_LEVEL_WARN, "%s [%u]: Unhandled RunLoop activity! (%#X)\n", + __FUNCTION__, __LINE__, (unsigned int) activity); + break; + } +} + +static inline void process_key_pressed(uint64_t timestamp, CGEventRef event_ref) { + UInt64 keycode = CGEventGetIntegerValueField(event_ref, kCGKeyboardEventKeycode); + + // Populate key pressed event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_KEY_PRESSED; + event.mask = get_modifiers(); + + event.data.keyboard.keycode = keycode_to_scancode(keycode); + event.data.keyboard.rawcode = keycode; + event.data.keyboard.keychar = CHAR_UNDEFINED; + + logger(LOG_LEVEL_INFO, "%s [%u]: Key %#X pressed. (%#X)\n", + __FUNCTION__, __LINE__, event.data.keyboard.keycode, event.data.keyboard.rawcode); + + // Fire key pressed event. + dispatch_event(&event); + + // If the pressed event was not consumed... + if (event.reserved ^ 0x01) { + tis_message->event = event_ref; + tis_message->length = 0; + bool is_runloop_main = CFEqual(event_loop, CFRunLoopGetMain()); + + #ifdef USE_WEAK_IMPORT + if (dispatch_sync_f != NULL && dispatch_get_main_queue != NULL && !is_runloop_main) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: Using dispatch_sync_f for key typed events.\n", __FUNCTION__, __LINE__); + dispatch_sync_f(dispatch_get_main_queue(), tis_message, &keycode_to_lookup); + } + #else + if (dispatch_sync_f_f != NULL && dispatch_get_main_queue_f != NULL && !is_runloop_main) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: Using *dispatch_sync_f_f for key typed events.\n", __FUNCTION__, __LINE__); + (*dispatch_sync_f_f)((*dispatch_get_main_queue_f)(), tis_message, &keycode_to_lookup); + } + #endif + #if ! defined(USE_CARBON_LEGACY) && defined(USE_COREFOUNDATION) + else if (!is_runloop_main) { + // Lock for code dealing with the main runloop. + pthread_mutex_lock(&msg_port_mutex); + + // Check to see if the main runloop is still running. + // TOOD I would rather this be a check on hook_enable(), + // but it makes the usage complicated by requiring a separate + // thread for the main runloop and hook registration. + CFStringRef mode = CFRunLoopCopyCurrentMode(CFRunLoopGetMain()); + if (mode != NULL) { + CFRelease(mode); + + // Lookup the Unicode representation for this event. + //CFRunLoopSourceContext context = { .version = 0 }; + //CFRunLoopSourceGetContext(src_msg_port, &context); + + // Get the run loop context info pointer. + //TISMessage *info = (TISMessage *) context.info; + + // Set the event pointer. + //info->event = event_ref; + + // Signal the custom source and wakeup the main runloop. + CFRunLoopSourceSignal(src_msg_port); + CFRunLoopWakeUp(CFRunLoopGetMain()); + + // Wait for a lock while the main runloop processes they key typed event. + pthread_cond_wait(&msg_port_cond, &msg_port_mutex); + } else { + logger(LOG_LEVEL_WARN, "%s [%u]: Failed to signal RunLoop main!\n", + __FUNCTION__, __LINE__); + } + + // Unlock for code dealing with the main runloop. + pthread_mutex_unlock(&msg_port_mutex); + } + #endif + else { + keycode_to_lookup(tis_message); + } + unsigned int i; + for (i= 0; i < tis_message->length; i++) { + // Populate key typed event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_KEY_TYPED; + event.mask = get_modifiers(); + + event.data.keyboard.keycode = VC_UNDEFINED; + event.data.keyboard.rawcode = keycode; + event.data.keyboard.keychar = tis_message->buffer[i]; + + logger(LOG_LEVEL_INFO, "%s [%u]: Key %#X typed. (%lc)\n", + __FUNCTION__, __LINE__, event.data.keyboard.keycode, + (wint_t) event.data.keyboard.keychar); + + // Populate key typed event. + dispatch_event(&event); + } + } +} + +static inline void process_key_released(uint64_t timestamp, CGEventRef event_ref) { + UInt64 keycode = CGEventGetIntegerValueField(event_ref, kCGKeyboardEventKeycode); + + // Populate key released event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_KEY_RELEASED; + event.mask = get_modifiers(); + + event.data.keyboard.keycode = keycode_to_scancode(keycode); + event.data.keyboard.rawcode = keycode; + event.data.keyboard.keychar = CHAR_UNDEFINED; + + logger(LOG_LEVEL_INFO, "%s [%u]: Key %#X released. (%#X)\n", + __FUNCTION__, __LINE__, event.data.keyboard.keycode, event.data.keyboard.rawcode); + + // Fire key released event. + dispatch_event(&event); +} + +static inline void process_modifier_changed(uint64_t timestamp, CGEventRef event_ref) { + CGEventFlags event_mask = CGEventGetFlags(event_ref); + UInt64 keycode = CGEventGetIntegerValueField(event_ref, kCGKeyboardEventKeycode); + + logger(LOG_LEVEL_INFO, "%s [%u]: Modifiers Changed for key %#X. (%#X)\n", + __FUNCTION__, __LINE__, (unsigned long) keycode, (unsigned int) event_mask); + + /* Because Apple treats modifier keys differently than normal key + * events, any changes to the modifier keys will require a key state + * change to be fired manually. + * + * NOTE Left and right keyboard masks like NX_NEXTLSHIFTKEYMASK exist and + * appear to be in use on Darwin, however they are removed by comment or + * preprocessor with a note that reads "device-dependent (really?)." To + * ensure compatability, we will do this the verbose way. + * + * NOTE The masks for scroll and number lock are set in the key event. + */ + if (keycode == kVK_Shift) { + if (event_mask & kCGEventFlagMaskShift) { + // Process as a key pressed event. + set_modifier_mask(MASK_SHIFT_L); + process_key_pressed(timestamp, event_ref); + } else { + // Process as a key released event. + unset_modifier_mask(MASK_SHIFT_L); + process_key_released(timestamp, event_ref); + } + } else if (keycode == kVK_Control) { + if (event_mask & kCGEventFlagMaskControl) { + // Process as a key pressed event. + set_modifier_mask(MASK_CTRL_L); + process_key_pressed(timestamp, event_ref); + } else { + // Process as a key released event. + unset_modifier_mask(MASK_CTRL_L); + process_key_released(timestamp, event_ref); + } + } else if (keycode == kVK_Command) { + if (event_mask & kCGEventFlagMaskCommand) { + // Process as a key pressed event. + set_modifier_mask(MASK_META_L); + process_key_pressed(timestamp, event_ref); + } else { + // Process as a key released event. + unset_modifier_mask(MASK_META_L); + process_key_released(timestamp, event_ref); + } + } else if (keycode == kVK_Option) { + if (event_mask & kCGEventFlagMaskAlternate) { + // Process as a key pressed event. + set_modifier_mask(MASK_ALT_L); + process_key_pressed(timestamp, event_ref); + } else { + // Process as a key released event. + unset_modifier_mask(MASK_ALT_L); + process_key_released(timestamp, event_ref); + } + } else if (keycode == kVK_RightShift) { + if (event_mask & kCGEventFlagMaskShift) { + // Process as a key pressed event. + set_modifier_mask(MASK_SHIFT_R); + process_key_pressed(timestamp, event_ref); + } else { + // Process as a key released event. + unset_modifier_mask(MASK_SHIFT_R); + process_key_released(timestamp, event_ref); + } + } else if (keycode == kVK_RightControl) { + if (event_mask & kCGEventFlagMaskControl) { + // Process as a key pressed event. + set_modifier_mask(MASK_CTRL_R); + process_key_pressed(timestamp, event_ref); + } else { + // Process as a key released event. + unset_modifier_mask(MASK_CTRL_R); + process_key_released(timestamp, event_ref); + } + } else if (keycode == kVK_RightCommand) { + if (event_mask & kCGEventFlagMaskCommand) { + // Process as a key pressed event. + set_modifier_mask(MASK_META_R); + process_key_pressed(timestamp, event_ref); + } else { + // Process as a key released event. + unset_modifier_mask(MASK_META_R); + process_key_released(timestamp, event_ref); + } + } else if (keycode == kVK_RightOption) { + if (event_mask & kCGEventFlagMaskAlternate) { + // Process as a key pressed event. + set_modifier_mask(MASK_ALT_R); + process_key_pressed(timestamp, event_ref); + } else { + // Process as a key released event. + unset_modifier_mask(MASK_ALT_R); + process_key_released(timestamp, event_ref); + } + } else if (keycode == kVK_CapsLock) { + if (current_modifiers & MASK_CAPS_LOCK) { + // Process as a key pressed event. + unset_modifier_mask(MASK_CAPS_LOCK); + process_key_released(timestamp, event_ref); + } else { + // Process as a key released event. + set_modifier_mask(MASK_CAPS_LOCK); + process_key_pressed(timestamp, event_ref); + } + } +} + +/* These events are totally undocumented for the CGEvent type, but are required to grab media and caps-lock keys. + */ +static inline void process_system_key(uint64_t timestamp, CGEventRef event_ref) { + if( CGEventGetType(event_ref) == NX_SYSDEFINED) { + #ifdef USE_OBJC + // Contributed by Iván Munsuri Ibáñez + id event_data = objc_msgSend((id) objc_getClass("NSEvent"), sel_registerName("eventWithCGEvent:"), event_ref); + int subtype = (int) objc_msgSend(event_data, sel_registerName("subtype")); + #else + CFDataRef data = CGEventCreateData(kCFAllocatorDefault, event_ref); + //CFIndex len = CFDataGetLength(data); + UInt8 *buffer = malloc(12); + // CFDataRef cf_data = CFDataCreate(NULL, [nsData bytes], [nsData length]); + CFDataGetBytes(data, CFRangeMake(108, 12), buffer); + UInt32 subtype = CFSwapInt32BigToHost(*((UInt32 *) buffer)); + #endif + if (subtype == 8) { + #ifdef USE_OBJC + // int data = (int) objc_msgSend(event_data, sel_registerName("data1")); + uint16_t data = (uint16_t) objc_msgSend(event_data, sel_registerName("data1")) + #endif + + // int + uint64_t key_code = ((uint64_t)data & 0xFFFF0000) >> 16; + uint64_t key_flags = ((uint64_t)data & 0xFFFF); + //int key_state = (key_flags & 0xFF00) >> 8; + bool key_down = (key_flags & 0x1) > 0; + + if (key_code == NX_KEYTYPE_CAPS_LOCK) { + // It doesn't appear like we can modify the event coming in, so we will fabricate a new event. + CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + CGEventRef ns_event = CGEventCreateKeyboardEvent(src, kVK_CapsLock, key_down); + CGEventSetFlags(ns_event, CGEventGetFlags(event_ref)); + + if (key_down) { + process_key_pressed(timestamp, ns_event); + } else { + process_key_released(timestamp, ns_event); + } + + CFRelease(ns_event); + CFRelease(src); + } else if (key_code == NX_KEYTYPE_SOUND_UP) { + // It doesn't appear like we can modify the event coming in, so we will fabricate a new event. + CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + CGEventRef ns_event = CGEventCreateKeyboardEvent(src, kVK_VolumeUp, key_down); + CGEventSetFlags(ns_event, CGEventGetFlags(event_ref)); + + if (key_down) { + process_key_pressed(timestamp, ns_event); + } else { + process_key_released(timestamp, ns_event); + } + + CFRelease(ns_event); + CFRelease(src); + } else if (key_code == NX_KEYTYPE_SOUND_DOWN) { + // It doesn't appear like we can modify the event coming in, so we will fabricate a new event. + CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + CGEventRef ns_event = CGEventCreateKeyboardEvent(src, kVK_VolumeDown, key_down); + CGEventSetFlags(ns_event, CGEventGetFlags(event_ref)); + + if (key_down) { + process_key_pressed(timestamp, ns_event); + } else { + process_key_released(timestamp, ns_event); + } + + CFRelease(ns_event); + CFRelease(src); + } else if (key_code == NX_KEYTYPE_MUTE) { + // It doesn't appear like we can modify the event coming in, so we will fabricate a new event. + CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + CGEventRef ns_event = CGEventCreateKeyboardEvent(src, kVK_Mute, key_down); + CGEventSetFlags(ns_event, CGEventGetFlags(event_ref)); + + if (key_down) { + process_key_pressed(timestamp, ns_event); + } else { + process_key_released(timestamp, ns_event); + } + + CFRelease(ns_event); + CFRelease(src); + } else if (key_code == NX_KEYTYPE_EJECT) { + // It doesn't appear like we can modify the event coming in, so we will fabricate a new event. + CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + CGEventRef ns_event = CGEventCreateKeyboardEvent(src, kVK_NX_Eject, key_down); + CGEventSetFlags(ns_event, CGEventGetFlags(event_ref)); + + if (key_down) { + process_key_pressed(timestamp, ns_event); + } else { + process_key_released(timestamp, ns_event); + } + + CFRelease(ns_event); + CFRelease(src); + } else if (key_code == NX_KEYTYPE_PLAY) { + // It doesn't appear like we can modify the event coming in, so we will fabricate a new event. + CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + CGEventRef ns_event = CGEventCreateKeyboardEvent(src, kVK_MEDIA_Play, key_down); + CGEventSetFlags(ns_event, CGEventGetFlags(event_ref)); + + if (key_down) { + process_key_pressed(timestamp, ns_event); + } else { + process_key_released(timestamp, ns_event); + } + + CFRelease(ns_event); + CFRelease(src); + } else if (key_code == NX_KEYTYPE_FAST) { + // It doesn't appear like we can modify the event coming in, so we will fabricate a new event. + CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + CGEventRef ns_event = CGEventCreateKeyboardEvent(src, kVK_MEDIA_Next, key_down); + CGEventSetFlags(ns_event, CGEventGetFlags(event_ref)); + + if (key_down) { + process_key_pressed(timestamp, ns_event); + } else { + process_key_released(timestamp, ns_event); + } + + CFRelease(ns_event); + CFRelease(src); + } else if (key_code == NX_KEYTYPE_REWIND) { + // It doesn't appear like we can modify the event coming in, so we will fabricate a new event. + CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); + CGEventRef ns_event = CGEventCreateKeyboardEvent(src, kVK_MEDIA_Previous, key_down); + CGEventSetFlags(ns_event, CGEventGetFlags(event_ref)); + + if (key_down) { + process_key_pressed(timestamp, ns_event); + } else { + process_key_released(timestamp, ns_event); + } + + CFRelease(ns_event); + CFRelease(src); + } + } + + #ifndef USE_OBJC + free(buffer); + CFRelease(data); + #endif + } +} + + +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) <= hook_get_multi_click_time()) { + if (click_count < USHRT_MAX) { + click_count++; + } else { + logger(LOG_LEVEL_WARN, "%s [%u]: Click count overflow detected!\n", + __FUNCTION__, __LINE__); + } + } else { + // Reset the click count. + click_count = 1; + + // Set the previous button. + click_button = button; + } + + // Save this events time to calculate the click_count. + click_time = timestamp; + + CGPoint event_point = CGEventGetLocation(event_ref); + + // Populate mouse pressed event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_MOUSE_PRESSED; + event.mask = get_modifiers(); + + event.data.mouse.button = button; + event.data.mouse.clicks = click_count; + event.data.mouse.x = event_point.x; + event.data.mouse.y = event_point.y; + + logger(LOG_LEVEL_INFO, "%s [%u]: Button %u pressed %u time(s). (%u, %u)\n", + __FUNCTION__, __LINE__, event.data.mouse.button, event.data.mouse.clicks, + event.data.mouse.x, event.data.mouse.y); + + // Fire mouse pressed event. + dispatch_event(&event); +} + +static inline void process_button_released(uint64_t timestamp, CGEventRef event_ref, uint16_t button) { + CGPoint event_point = CGEventGetLocation(event_ref); + + // Populate mouse released event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_MOUSE_RELEASED; + event.mask = get_modifiers(); + + event.data.mouse.button = button; + event.data.mouse.clicks = click_count; + event.data.mouse.x = event_point.x; + event.data.mouse.y = event_point.y; + + logger(LOG_LEVEL_INFO, "%s [%u]: Button %u released %u time(s). (%u, %u)\n", + __FUNCTION__, __LINE__, event.data.mouse.button, event.data.mouse.clicks, + event.data.mouse.x, event.data.mouse.y); + + // Fire mouse released event. + dispatch_event(&event); + + // If the pressed event was not consumed... + if (event.reserved ^ 0x01 && mouse_dragged != true) { + // Populate mouse clicked event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_MOUSE_CLICKED; + event.mask = get_modifiers(); + + event.data.mouse.button = button; + event.data.mouse.clicks = click_count; + event.data.mouse.x = event_point.x; + event.data.mouse.y = event_point.y; + + logger(LOG_LEVEL_INFO, "%s [%u]: Button %u clicked %u time(s). (%u, %u)\n", + __FUNCTION__, __LINE__, event.data.mouse.button, event.data.mouse.clicks, + event.data.mouse.x, event.data.mouse.y); + + // Fire mouse clicked event. + dispatch_event(&event); + } + + // Reset the number of clicks. + if (button == click_button && (long int) (event.time - click_time) > hook_get_multi_click_time()) { + // Reset the click count. + click_count = 0; + } +} + +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) > hook_get_multi_click_time()) { + click_count = 0; + } + + CGPoint event_point = CGEventGetLocation(event_ref); + + // Populate mouse motion event. + event.time = timestamp; + event.reserved = 0x00; + + if (mouse_dragged) { + event.type = EVENT_MOUSE_DRAGGED; + } else { + event.type = EVENT_MOUSE_MOVED; + } + event.mask = get_modifiers(); + + event.data.mouse.button = MOUSE_NOBUTTON; + event.data.mouse.clicks = click_count; + event.data.mouse.x = event_point.x; + event.data.mouse.y = event_point.y; + + logger(LOG_LEVEL_INFO, "%s [%u]: Mouse %s to %u, %u.\n", + __FUNCTION__, __LINE__, mouse_dragged ? "dragged" : "moved", + event.data.mouse.x, event.data.mouse.y); + + // Fire mouse motion event. + dispatch_event(&event); +} + +static inline void process_mouse_wheel(uint64_t timestamp, CGEventRef event_ref) { + // Reset the click count and previous button. + click_count = 1; + click_button = MOUSE_NOBUTTON; + + // Check to see what axis was rotated, we only care about axis 1 for vertical rotation. + // TODO Implement horizontal scrolling by examining axis 2. + // NOTE kCGScrollWheelEventDeltaAxis3 is currently unused. + if (CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis1) != 0 + || CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis2) != 0) { + CGPoint event_point = CGEventGetLocation(event_ref); + + // Populate mouse wheel event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_MOUSE_WHEEL; + event.mask = get_modifiers(); + + event.data.wheel.clicks = click_count; + event.data.wheel.x = event_point.x; + event.data.wheel.y = event_point.y; + + // TODO Figure out if kCGScrollWheelEventDeltaAxis2 causes mouse events with zero rotation. + if (CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventIsContinuous) == 0) { + // Scrolling data is line-based. + event.data.wheel.type = WHEEL_BLOCK_SCROLL; + } else { + // Scrolling data is pixel-based. + event.data.wheel.type = WHEEL_UNIT_SCROLL; + } + + // TODO The result of kCGScrollWheelEventIsContinuous may effect this value. + // Calculate the amount based on the Point Delta / Event Delta. Integer sign should always be homogeneous resulting in a positive result. + // NOTE kCGScrollWheelEventFixedPtDeltaAxis1 a floating point value (+0.1/-0.1) that takes acceleration into account. + // NOTE kCGScrollWheelEventPointDeltaAxis1 will not build on OS X < 10.5 + + if(CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis1) != 0) { + event.data.wheel.amount = CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventPointDeltaAxis1) / CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis1); + + // Scrolling data uses a fixed-point 16.16 signed integer format (Ex: 1.0 = 0x00010000). + event.data.wheel.rotation = CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis1) * -1; + + } else if(CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis2) != 0) { + event.data.wheel.amount = CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventPointDeltaAxis2) / CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis2); + + // Scrolling data uses a fixed-point 16.16 signed integer format (Ex: 1.0 = 0x00010000). + event.data.wheel.rotation = CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis2) * -1; + } else { + //Fail Silently if a 3rd axis gets added without changing this section of code. + event.data.wheel.amount = 0; + event.data.wheel.rotation = 0; + } + + + + if (CGEventGetIntegerValueField(event_ref, kCGScrollWheelEventDeltaAxis1) != 0) { + // Wheel Rotated Up or Down. + event.data.wheel.direction = WHEEL_VERTICAL_DIRECTION; + } else { + // data->event.u.u.detail == WheelLeft || data->event.u.u.detail == WheelRight + // Wheel Rotated Left or Right. + event.data.wheel.direction = WHEEL_HORIZONTAL_DIRECTION; + } + + logger(LOG_LEVEL_INFO, "%s [%u]: Mouse wheel type %u, rotated %i units in the %u direction at %u, %u.\n", + __FUNCTION__, __LINE__, event.data.wheel.type, + event.data.wheel.amount * event.data.wheel.rotation, + event.data.wheel.direction, + event.data.wheel.x, event.data.wheel.y); + + // Fire mouse wheel event. + dispatch_event(&event); + } +} + +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 timestap for use later.. + uint64_t timestamp = (uint64_t) CGEventGetTimestamp(event_ref); + + // Get the event class. + switch (type) { + case kCGEventKeyDown: + process_key_pressed(timestamp, event_ref); + break; + + case kCGEventKeyUp: + process_key_released(timestamp, event_ref); + break; + + case kCGEventFlagsChanged: + process_modifier_changed(timestamp, event_ref); + break; + + // case NX_SYSDEFINED: + // process_system_key(timestamp, event_ref); + // break; + + case kCGEventLeftMouseDown: + set_modifier_mask(MASK_BUTTON1); + process_button_pressed(timestamp, event_ref, MOUSE_BUTTON1); + break; + + case kCGEventRightMouseDown: + set_modifier_mask(MASK_BUTTON2); + process_button_pressed(timestamp, event_ref, MOUSE_BUTTON2); + break; + + case kCGEventOtherMouseDown: + // Extra mouse buttons. + if (CGEventGetIntegerValueField(event_ref, kCGMouseEventButtonNumber) < UINT16_MAX) { + uint16_t button = (uint16_t) CGEventGetIntegerValueField(event_ref, kCGMouseEventButtonNumber) + 1; + + // Add support for mouse 4 & 5. + if (button == 4) { + set_modifier_mask(MOUSE_BUTTON4); + } else if (button == 5) { + set_modifier_mask(MOUSE_BUTTON5); + } + + process_button_pressed(timestamp, event_ref, button); + } + break; + + case kCGEventLeftMouseUp: + unset_modifier_mask(MASK_BUTTON1); + process_button_released(timestamp, event_ref, MOUSE_BUTTON1); + break; + + case kCGEventRightMouseUp: + unset_modifier_mask(MASK_BUTTON2); + process_button_released(timestamp, event_ref, MOUSE_BUTTON2); + break; + + case kCGEventOtherMouseUp: + // Extra mouse buttons. + if (CGEventGetIntegerValueField(event_ref, kCGMouseEventButtonNumber) < UINT16_MAX) { + uint16_t button = (uint16_t) CGEventGetIntegerValueField(event_ref, kCGMouseEventButtonNumber) + 1; + + // Add support for mouse 4 & 5. + if (button == 4) { + unset_modifier_mask(MOUSE_BUTTON4); + } else if (button == 5) { + unset_modifier_mask(MOUSE_BUTTON5); + } + + process_button_pressed(timestamp, event_ref, button); + } + break; + + + case kCGEventLeftMouseDragged: + case kCGEventRightMouseDragged: + case kCGEventOtherMouseDragged: + // FIXME The drag flag is confusing. Use prev x,y to determine click. + // Set the mouse dragged flag. + mouse_dragged = true; + process_mouse_moved(timestamp, event_ref); + break; + + case kCGEventMouseMoved: + // Set the mouse dragged flag. + mouse_dragged = false; + process_mouse_moved(timestamp, event_ref); + break; + + + case kCGEventScrollWheel: + process_mouse_wheel(timestamp, event_ref); + break; + + + #ifdef USE_DEBUG + case kCGEventNull: + logger(LOG_LEVEL_DEBUG, "%s [%u]: Ignoring kCGEventNull.\n", + __FUNCTION__, __LINE__); + break; + #endif + + default: + // Check for an old OS X bug where the tap seems to timeout for no reason. + // See: http://stackoverflow.com/questions/2969110/cgeventtapcreate-breaks-down-mysteriously-with-key-down-events#2971217 + if (type == (CGEventType) kCGEventTapDisabledByTimeout) { + logger(LOG_LEVEL_WARN, "%s [%u]: CGEventTap timeout!\n", + __FUNCTION__, __LINE__); + + // We need to restart the tap! + restart_tap = true; + CFRunLoopStop(CFRunLoopGetCurrent()); + } else { + // In theory this *should* never execute. + logger(LOG_LEVEL_DEBUG, "%s [%u]: Unhandled Darwin event: %#X.\n", + __FUNCTION__, __LINE__, (unsigned int) type); + } + break; + } + + CGEventRef result_ref = NULL; + if (event.reserved ^ 0x01) { + result_ref = event_ref; + } else { + logger(LOG_LEVEL_DEBUG, "%s [%u]: Consuming the current event. (%#X) (%#p)\n", + __FUNCTION__, __LINE__, type, event_ref); + } + + return result_ref; +} + +IOHOOK_API int hook_run() { + int status = IOHOOK_SUCCESS; + + // Check for accessibility each time we start the loop. + if (is_accessibility_enabled()) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: Accessibility API is enabled.\n", + __FUNCTION__, __LINE__); + + do { + // Reset the restart flag... + restart_tap = false; + + // Initialize starting modifiers. + initialize_modifiers(); + + // Try and allocate memory for hook_info. + hook_info *hook = malloc(sizeof(hook_info)); + if (hook != NULL) { + // Setup the event mask to listen for. + #ifdef USE_DEBUG + CGEventMask event_mask = kCGEventMaskForAllEvents; + #else + CGEventMask event_mask = CGEventMaskBit(kCGEventKeyDown) | + CGEventMaskBit(kCGEventKeyUp) | + CGEventMaskBit(kCGEventFlagsChanged) | + + CGEventMaskBit(kCGEventLeftMouseDown) | + CGEventMaskBit(kCGEventLeftMouseUp) | + CGEventMaskBit(kCGEventLeftMouseDragged) | + + CGEventMaskBit(kCGEventRightMouseDown) | + CGEventMaskBit(kCGEventRightMouseUp) | + CGEventMaskBit(kCGEventRightMouseDragged) | + + CGEventMaskBit(kCGEventOtherMouseDown) | + CGEventMaskBit(kCGEventOtherMouseUp) | + CGEventMaskBit(kCGEventOtherMouseDragged) | + + CGEventMaskBit(kCGEventMouseMoved) | + CGEventMaskBit(kCGEventScrollWheel) | + + // NOTE This event is undocumented and used + // for caps-lock release and multi-media keys. + CGEventMaskBit(NX_SYSDEFINED); + #endif + + // Create the event tap. + hook->port = CGEventTapCreate( + kCGSessionEventTap, // kCGHIDEventTap + kCGHeadInsertEventTap, // kCGTailAppendEventTap + kCGEventTapOptionDefault, // kCGEventTapOptionListenOnly See Bug #22 + event_mask, + hook_event_proc, + NULL); + + if (hook->port != NULL) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: CGEventTapCreate Successful.\n", + __FUNCTION__, __LINE__); + + // Create the runloop event source from the event tap. + hook->source = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, hook->port, 0); + if (hook->source != NULL) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: CFMachPortCreateRunLoopSource successful.\n", + __FUNCTION__, __LINE__); + + event_loop = CFRunLoopGetCurrent(); + if (event_loop != NULL) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: CFRunLoopGetCurrent successful.\n", + __FUNCTION__, __LINE__); + + // Create run loop observers. + hook->observer = CFRunLoopObserverCreate( + kCFAllocatorDefault, + kCFRunLoopEntry | kCFRunLoopExit, //kCFRunLoopAllActivities, + true, + 0, + hook_status_proc, + NULL); + + if (hook->observer != NULL) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: CFRunLoopObserverCreate successful.\n", + __FUNCTION__, __LINE__); + + tis_message = (TISMessage *) calloc(1, sizeof(TISMessage)); + if (tis_message != NULL) { + if (! CFEqual(event_loop, CFRunLoopGetMain())) { + #ifdef USE_WEAK_IMPORT + if (dispatch_sync_f == NULL || dispatch_get_main_queue == NULL) { + #else + *(void **) (&dispatch_sync_f_f) = dlsym(RTLD_DEFAULT, "dispatch_sync_f"); + const char *dlError = dlerror(); + if (dlError != NULL) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: %s.\n", + __FUNCTION__, __LINE__, dlError); + } + + *(void **) (&dispatch_get_main_queue_f) = dlsym(RTLD_DEFAULT, "dispatch_get_main_queue"); + dlError = dlerror(); + if (dlError != NULL) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: %s.\n", + __FUNCTION__, __LINE__, dlError); + } + + if (dispatch_sync_f_f == NULL || dispatch_get_main_queue_f == NULL) { + #endif + 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_COREFOUNDATION) + logger(LOG_LEVEL_DEBUG, "%s [%u]: Falling back to runloop signaling.\n", + __FUNCTION__, __LINE__); + + int runloop_status = start_message_port_runloop(); + if (runloop_status != IOHOOK_SUCCESS) { + return runloop_status; + } + #endif + } + } + + // Add the event source and observer to the runloop mode. + CFRunLoopAddSource(event_loop, hook->source, kCFRunLoopDefaultMode); + CFRunLoopAddObserver(event_loop, hook->observer, kCFRunLoopDefaultMode); + + #ifdef USE_OBJC + // Create a garbage collector to handle Cocoa events correctly. + Class NSAutoreleasePool_class = (Class) objc_getClass("NSAutoreleasePool"); + id pool = class_createInstance(NSAutoreleasePool_class, 0); + auto_release_pool = objc_msgSend(pool, sel_registerName("init")); + #endif + + // Start the hook thread runloop. + CFRunLoopRun(); + + + #ifdef USE_OBJC + //objc_msgSend(auto_release_pool, sel_registerName("drain")); + objc_msgSend(auto_release_pool, sel_registerName("release")); + #endif + + // Lock back up until we are done processing the exit. + if (CFRunLoopContainsObserver(event_loop, hook->observer, kCFRunLoopDefaultMode)) { + CFRunLoopRemoveObserver(event_loop, hook->observer, kCFRunLoopDefaultMode); + } + + if (CFRunLoopContainsSource(event_loop, hook->source, kCFRunLoopDefaultMode)) { + CFRunLoopRemoveSource(event_loop, hook->source, kCFRunLoopDefaultMode); + } + + #if ! defined(USE_CARBON_LEGACY) && defined(USE_COREFOUNDATION) + if (! CFEqual(event_loop, CFRunLoopGetMain())) { + #ifdef USE_WEAK_IMPORT + if (dispatch_sync_f == NULL || dispatch_get_main_queue == NULL) { + #else + if (dispatch_sync_f_f == NULL || dispatch_get_main_queue_f == NULL) { + #endif + stop_message_port_runloop(); + } + } + #endif + + // Free the TIS Message. + free(tis_message); + } else { + logger(LOG_LEVEL_ERROR, "%s [%u]: Failed to allocate memory for TIS message structure!\n", + __FUNCTION__, __LINE__); + + // Set the exit status. + status = IOHOOK_ERROR_OUT_OF_MEMORY; + } + + // Invalidate and free hook observer. + CFRunLoopObserverInvalidate(hook->observer); + CFRelease(hook->observer); + } else { + // We cant do a whole lot of anything if we cant + // create run loop observer. + logger(LOG_LEVEL_ERROR, "%s [%u]: CFRunLoopObserverCreate failure!\n", + __FUNCTION__, __LINE__); + + // Set the exit status. + status = IOHOOK_ERROR_CREATE_OBSERVER; + } + } else { + logger(LOG_LEVEL_ERROR, "%s [%u]: CFRunLoopGetCurrent failure!\n", + __FUNCTION__, __LINE__); + + // Set the exit status. + status = IOHOOK_ERROR_GET_RUNLOOP; + } + + // Clean up the event source. + CFRelease(hook->source); + } else { + logger(LOG_LEVEL_ERROR, "%s [%u]: CFMachPortCreateRunLoopSource failure!\n", + __FUNCTION__, __LINE__); + + // Set the exit status. + status = IOHOOK_ERROR_CREATE_RUN_LOOP_SOURCE; + } + + // Stop the CFMachPort from receiving any more messages. + CFMachPortInvalidate(hook->port); + CFRelease(hook->port); + } else { + logger(LOG_LEVEL_ERROR, "%s [%u]: Failed to create event port!\n", + __FUNCTION__, __LINE__); + + // Set the exit status. + status = IOHOOK_ERROR_CREATE_EVENT_PORT; + } + + // Free the hook structure. + free(hook); + } else { + status = IOHOOK_ERROR_OUT_OF_MEMORY; + } + } while (restart_tap); + } else { + logger(LOG_LEVEL_ERROR, "%s [%u]: Accessibility API is disabled!\n", + __FUNCTION__, __LINE__); + + // Set the exit status. + status = IOHOOK_ERROR_AXAPI_DISABLED; + } + + logger(LOG_LEVEL_DEBUG, "%s [%u]: Something, something, something, complete.\n", + __FUNCTION__, __LINE__); + + return status; +} + +IOHOOK_API int hook_stop() { + int status = IOHOOK_FAILURE; + + CFStringRef mode = CFRunLoopCopyCurrentMode(event_loop); + if (mode != NULL) { + CFRelease(mode); + + // Make sure the tap doesn't restart. + restart_tap = false; + + // Stop the run loop. + CFRunLoopStop(event_loop); + + status = IOHOOK_SUCCESS; + } + + logger(LOG_LEVEL_DEBUG, "%s [%u]: Status: %#X.\n", + __FUNCTION__, __LINE__, status); + + return status; +} diff --git a/hook/darwin/input.h b/hook/darwin/input.h new file mode 100644 index 0000000..9037515 --- /dev/null +++ b/hook/darwin/input.h @@ -0,0 +1,110 @@ +/* Copyright (C) 2006-2017 Alexander Barker. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. +*/ + +#ifndef _included_input_helper +#define _included_input_helper + +#include +#include // For HIToolbox kVK_ keycodes and TIS funcitons. +#ifdef USE_IOKIT +#include +#endif +#include + + +#ifndef USE_IOKIT +// Some of the system key codes that are needed from IOKit. +#define NX_KEYTYPE_SOUND_UP 0x00 +#define NX_KEYTYPE_SOUND_DOWN 0x01 +#define NX_KEYTYPE_MUTE 0x07 + +/* Display controls... +#define NX_KEYTYPE_BRIGHTNESS_UP 0x02 +#define NX_KEYTYPE_BRIGHTNESS_DOWN 0x03 +#define NX_KEYTYPE_CONTRAST_UP 0x0B +#define NX_KEYTYPE_CONTRAST_DOWN 0x0C +#define NX_KEYTYPE_ILLUMINATION_UP 0x15 +#define NX_KEYTYPE_ILLUMINATION_DOWN 0x16 +#define NX_KEYTYPE_ILLUMINATION_TOGGLE 0x17 +*/ + +#define NX_KEYTYPE_CAPS_LOCK 0x04 +//#define NX_KEYTYPE_HELP 0x05 +#define NX_POWER_KEY 0x06 + +#define NX_KEYTYPE_EJECT 0x0E +#define NX_KEYTYPE_PLAY 0x10 +#define NX_KEYTYPE_NEXT 0x12 +#define NX_KEYTYPE_PREVIOUS 0x13 + +/* There is no official fast-forward or rewind scan code support.*/ +#define NX_KEYTYPE_FAST 0x14 +#define NX_KEYTYPE_REWIND 0x15 + +#endif + +// These virtual key codes do not appear to be defined anywhere by Apple. +#define kVK_NX_Power 0xE0 | NX_POWER_KEY /* 0xE6 */ +#define kVK_NX_Eject 0xE0 | NX_KEYTYPE_EJECT /* 0xEE */ + +#define kVK_MEDIA_Play 0xE0 | NX_KEYTYPE_PLAY /* 0xF0 */ +#define kVK_MEDIA_Next 0xE0 | NX_KEYTYPE_NEXT /* 0xF1 */ +#define kVK_MEDIA_Previous 0xE0 | NX_KEYTYPE_PREVIOUS /* 0xF2 */ + +#define kVK_RightCommand 0x36 +#define kVK_ContextMenu 0x6E // AKA kMenuPowerGlyph +#define kVK_Undefined 0xFF + +// These button codes do not appear to be defined anywhere by Apple. +#define kVK_LBUTTON kCGMouseButtonLeft +#define kVK_RBUTTON kCGMouseButtonRight +#define kVK_MBUTTON kCGMouseButtonCenter +#define kVK_XBUTTON1 3 +#define kVK_XBUTTON2 4 + +// These button masks do not appear to be defined anywhere by Apple. +#define kCGEventFlagMaskButtonLeft 1 << 0 +#define kCGEventFlagMaskButtonRight 1 << 1 +#define kCGEventFlagMaskButtonCenter 1 << 2 +#define kCGEventFlagMaskXButton1 1 << 3 +#define kCGEventFlagMaskXButton2 1 << 4 + + +/* Check for access to Apples accessibility API. + */ +extern bool is_accessibility_enabled(); + +/* Converts an OSX key code and event mask to the appropriate Unicode character + * representation. + */ +extern UniCharCount keycode_to_unicode(CGEventRef event_ref, UniChar *buffer, UniCharCount size); + +/* Converts an OSX keycode to the appropriate IOHook scancode constant. + */ +extern uint16_t keycode_to_scancode(UInt64 keycode); + +/* Converts a IOHook scancode constant to the appropriate OSX keycode. + */ +extern UInt64 scancode_to_keycode(uint16_t keycode); + + +/* Initialize items required for KeyCodeToKeySym() and KeySymToUnicode() + * functionality. This method is called by OnLibraryLoad() and may need to be + * called in combination with UnloadInputHelper() if the native keyboard layout + * is changed. + */ +extern void load_input_helper(); + +/* De-initialize items required for KeyCodeToKeySym() and KeySymToUnicode() + * functionality. This method is called by OnLibraryUnload() and may need to be + * called in combination with LoadInputHelper() if the native keyboard layout + * is changed. + */ +extern void unload_input_helper(); + +#endif diff --git a/hook/darwin/input_c.h b/hook/darwin/input_c.h new file mode 100644 index 0000000..f045fe2 --- /dev/null +++ b/hook/darwin/input_c.h @@ -0,0 +1,546 @@ +/* Copyright (C) 2006-2017 Alexander Barker. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. +*/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#ifdef USE_COREFOUNDATION + #include +#endif +#ifndef USE_WEAK_IMPORT + #include +#endif + +#include +#include "input.h" +#include "../iohook.h" +#include "../logger_c.h" + +// Current dead key state. +#if defined(USE_CARBON_LEGACY) || defined(USE_COREFOUNDATION) +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_COREFOUNDATION) +static TISInputSourceRef prev_keyboard_layout = NULL; +#endif + +#ifdef USE_WEAK_IMPORT +// Required to dynamically check for AXIsProcessTrustedWithOptions availability. +extern Boolean AXIsProcessTrustedWithOptions(CFDictionaryRef options) __attribute__((weak_import)); +extern CFStringRef kAXTrustedCheckOptionPrompt __attribute__((weak_import)); +#else +static Boolean (*AXIsProcessTrustedWithOptions_t)(CFDictionaryRef); +#endif + +bool is_accessibility_enabled() { + bool is_enabled = false; + + #ifdef USE_WEAK_IMPORT + // Check and make sure assistive devices is enabled. + if (AXIsProcessTrustedWithOptions != NULL) { + // New accessibility API 10.9 and later. + const void * keys[] = { kAXTrustedCheckOptionPrompt }; + const void * values[] = { kCFBooleanTrue }; + + CFDictionaryRef options = CFDictionaryCreate( + kCFAllocatorDefault, + keys, + values, + sizeof(keys) / sizeof(*keys), + &kCFCopyStringDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + is_enabled = AXIsProcessTrustedWithOptions(options); + } + #else + // Dynamically load the application services framework for examination. + *(void **) (&AXIsProcessTrustedWithOptions_t) = dlsym(RTLD_DEFAULT, "AXIsProcessTrustedWithOptions"); + const char *dlError = dlerror(); + if (AXIsProcessTrustedWithOptions_t != NULL && dlError == NULL) { + // Check for property CFStringRef kAXTrustedCheckOptionPrompt + void ** kAXTrustedCheckOptionPrompt_t = dlsym(RTLD_DEFAULT, "kAXTrustedCheckOptionPrompt"); + + dlError = dlerror(); + if (kAXTrustedCheckOptionPrompt_t != NULL && dlError == NULL) { + // New accessibility API 10.9 and later. + const void * keys[] = { *kAXTrustedCheckOptionPrompt_t }; + const void * values[] = { kCFBooleanTrue }; + + CFDictionaryRef options = CFDictionaryCreate( + kCFAllocatorDefault, + keys, + values, + sizeof(keys) / sizeof(*keys), + &kCFCopyStringDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + is_enabled = (*AXIsProcessTrustedWithOptions_t)(options); + } + } + #endif + else { + #ifndef USE_WEAK_IMPORT + if (dlError != NULL) { + // Could not load the AXIsProcessTrustedWithOptions function! + logger(LOG_LEVEL_DEBUG, "%s [%u]: %s.\n", + __FUNCTION__, __LINE__, dlError); + } + #endif + + logger(LOG_LEVEL_DEBUG, "%s [%u]: Weak import AXIsProcessTrustedWithOptions not found.\n", + __FUNCTION__, __LINE__, dlError); + + logger(LOG_LEVEL_DEBUG, "%s [%u]: Falling back to AXAPIEnabled().\n", + __FUNCTION__, __LINE__, dlError); + + // Old accessibility check 10.8 and older. + is_enabled = AXAPIEnabled(); + } + + return is_enabled; +} + + +UniCharCount keycode_to_unicode(CGEventRef event_ref, UniChar *buffer, UniCharCount size) { + UniCharCount count = 0; + + #if defined(USE_CARBON_LEGACY) + KeyboardLayoutRef curr_keyboard_layout; + void *inputData = NULL; + if (KLGetCurrentKeyboardLayout(&curr_keyboard_layout) == noErr) { + if (KLGetKeyboardLayoutProperty(curr_keyboard_layout, kKLuchrData, (const void **) &inputData) != noErr) { + inputData = NULL; + } + } + #elif defined(USE_COREFOUNDATION) + CFDataRef inputData = NULL; + if (CFEqual(CFRunLoopGetCurrent(), CFRunLoopGetMain())) { + // NOTE The following block must execute on the main runloop, + // Ex: CFEqual(CFRunLoopGetCurrent(), CFRunLoopGetMain()) to avoid + // Exception detected while handling key input and TSMProcessRawKeyCode failed + // (-192) errors. + TISInputSourceRef curr_keyboard_layout = TISCopyCurrentKeyboardLayoutInputSource(); + if (curr_keyboard_layout != NULL && CFGetTypeID(curr_keyboard_layout) == TISInputSourceGetTypeID()) { + CFDataRef data = (CFDataRef) TISGetInputSourceProperty(curr_keyboard_layout, kTISPropertyUnicodeKeyLayoutData); + if (data != NULL && CFGetTypeID(data) == CFDataGetTypeID() && CFDataGetLength(data) > 0) { + inputData = (CFDataRef) data; + } + } + + // Check if the keyboard layout has changed to see if the dead key state needs to be discarded. + if (prev_keyboard_layout != NULL && curr_keyboard_layout != NULL && CFEqual(curr_keyboard_layout, prev_keyboard_layout) == false) { + deadkey_state = 0x00; + } + + // Release the previous keyboard layout. + if (prev_keyboard_layout != NULL) { + CFRelease(prev_keyboard_layout); + prev_keyboard_layout = NULL; + } + + // Set the previous keyboard layout to the current layout. + if (curr_keyboard_layout != NULL) { + prev_keyboard_layout = curr_keyboard_layout; + } + } + #endif + + #if defined(USE_CARBON_LEGACY) || defined(USE_COREFOUNDATION) + 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. + CGKeyCode keycode = CGEventGetIntegerValueField(event_ref, kCGKeyboardEventKeycode); + CGEventFlags modifiers = CGEventGetFlags(event_ref); + + // Disable all command modifiers for translation. This is required + // so UCKeyTranslate will provide a keysym for the separate event. + static const CGEventFlags cmd_modifiers = kCGEventFlagMaskCommand | + kCGEventFlagMaskControl | kCGEventFlagMaskAlternate; + modifiers &= ~cmd_modifiers; + + // I don't know why but UCKeyTranslate does not process the + // kCGEventFlagMaskAlphaShift (A.K.A. Caps Lock Mask) correctly. + // We need to basically turn off the mask and process the capital + // letters after UCKeyTranslate(). + bool is_caps_lock = modifiers & kCGEventFlagMaskAlphaShift; + modifiers &= ~kCGEventFlagMaskAlphaShift; + + // Run the translation with the saved deadkey_state. + OSStatus status = UCKeyTranslate( + keyboard_layout, + keycode, + kUCKeyActionDown, //kUCKeyActionDisplay, + (modifiers >> 16) & 0xFF, //(modifiers >> 16) & 0xFF, || (modifiers >> 8) & 0xFF, + LMGetKbdType(), + kNilOptions, //kNilOptions, //kUCKeyTranslateNoDeadKeysMask + &deadkey_state, + size, + &count, + buffer); + + if (status == noErr && count > 0) { + if (is_caps_lock) { + // We *had* a caps lock mask so we need to convert to uppercase. + CFMutableStringRef keytxt = CFStringCreateMutableWithExternalCharactersNoCopy(kCFAllocatorDefault, buffer, count, size, kCFAllocatorNull); + if (keytxt != NULL) { + CFLocaleRef locale = CFLocaleCopyCurrent(); + CFStringUppercase(keytxt, locale); + CFRelease(locale); + CFRelease(keytxt); + } else { + // There was an problem creating the CFMutableStringRef. + count = 0; + } + } + } else { + // Make sure the buffer count is zero if an error occurred. + count = 0; + } + } + } + #else + CGEventKeyboardGetUnicodeString(event_ref, size, &count, buffer); + #endif + + // The following codes should not be processed because they are invalid. + if (count == 1) { + switch (buffer[0]) { + case 0x01: // Home + case 0x04: // End + case 0x05: // Help Key + case 0x10: // Function Keys + case 0x0B: // Page Up + case 0x0C: // Page Down + case 0x1F: // Volume Up + count = 0; + } + } + + return count; +} + + +static const uint16_t keycode_scancode_table[][2] = { + /* idx { keycode, scancode }, */ + /* 0 */ { VC_A, kVK_Undefined }, // 0x00 + /* 1 */ { VC_S, kVK_Escape }, // 0x01 + /* 2 */ { VC_D, kVK_ANSI_1 }, // 0x02 + /* 3 */ { VC_F, kVK_ANSI_2 }, // 0x03 + /* 4 */ { VC_H, kVK_ANSI_3 }, // 0x04 + /* 5 */ { VC_G, kVK_ANSI_4 }, // 0x05 + /* 6 */ { VC_Z, kVK_ANSI_5 }, // 0x07 + /* 7 */ { VC_X, kVK_ANSI_6 }, // 0x08 + /* 8 */ { VC_C, kVK_ANSI_7 }, // 0x09 + /* 9 */ { VC_V, kVK_ANSI_8 }, // 0x0A + /* 10 */ { VC_UNDEFINED, kVK_ANSI_9 }, // 0x0B + /* 11 */ { VC_B, kVK_ANSI_0 }, // 0x0C + /* 12 */ { VC_Q, kVK_ANSI_Minus }, // 0x0D + /* 13 */ { VC_W, kVK_ANSI_Equal }, // 0x0E + /* 14 */ { VC_E, kVK_Delete }, // 0x0F + /* 15 */ { VC_R, kVK_Tab }, // 0x10 + /* 16 */ { VC_Y, kVK_ANSI_Q }, // 0x11 + /* 17 */ { VC_T, kVK_ANSI_W }, // 0x12 + /* 18 */ { VC_1, kVK_ANSI_E }, // 0x13 + /* 19 */ { VC_2, kVK_ANSI_R }, // 0x14 + /* 20 */ { VC_3, kVK_ANSI_T }, // 0x15 + /* 21 */ { VC_4, kVK_ANSI_Y }, // 0x16 + /* 22 */ { VC_6, kVK_ANSI_U }, // 0x17 + /* 23 */ { VC_5, kVK_ANSI_I }, // 0x18 + /* 24 */ { VC_EQUALS, kVK_ANSI_O }, // 0x19 + /* 25 */ { VC_9, kVK_ANSI_P }, // 0x19 + /* 26 */ { VC_7, kVK_ANSI_LeftBracket }, // 0x1A + /* 27 */ { VC_MINUS, kVK_ANSI_RightBracket }, // 0x1B + /* 28 */ { VC_8, kVK_Return }, // 0x1C + /* 29 */ { VC_0, kVK_Control }, // 0x1D + /* 30 */ { VC_CLOSE_BRACKET, kVK_ANSI_A }, // 0x1E + /* 31 */ { VC_O, kVK_ANSI_S }, // 0x1F + /* 32 */ { VC_U, kVK_ANSI_D }, // 0x20 + /* 33 */ { VC_OPEN_BRACKET, kVK_ANSI_F }, // 0x21 + /* 34 */ { VC_I, kVK_ANSI_G }, // 0x22 + /* 35 */ { VC_P, kVK_ANSI_H }, // 0x23 + /* 36 */ { VC_ENTER, kVK_ANSI_J }, // 0x24 + /* 37 */ { VC_L, kVK_ANSI_K }, // 0x25 + /* 38 */ { VC_J, kVK_ANSI_L }, // 0x26 + /* 39 */ { VC_QUOTE, kVK_ANSI_Semicolon }, // 0x27 + /* 40 */ { VC_K, kVK_ANSI_Quote }, // 0x28 + /* 41 */ { VC_SEMICOLON, kVK_ANSI_Grave }, // 0x29 + /* 42 */ { VC_BACK_SLASH, kVK_Shift }, // 0x2A + /* 43 */ { VC_COMMA, kVK_ANSI_Backslash }, // 0x2B + /* 44 */ { VC_SLASH, kVK_ANSI_Z }, // 0x2C + /* 45 */ { VC_N, kVK_ANSI_X }, // 0x2D + /* 46 */ { VC_M, kVK_ANSI_C }, // 0x2E + /* 47 */ { VC_PERIOD, kVK_ANSI_V }, // 0x2F + /* 48 */ { VC_TAB, kVK_ANSI_B }, // 0x30 + /* 49 */ { VC_SPACE, kVK_ANSI_N }, // 0x31 + /* 50 */ { VC_BACKQUOTE, kVK_ANSI_M }, // 0x32 + /* 51 */ { VC_BACKSPACE, kVK_ANSI_Comma }, // 0x33 + /* 52 */ { VC_UNDEFINED, kVK_ANSI_Period }, // 0x34 + /* 53 */ { VC_ESCAPE, kVK_ANSI_Slash }, // 0x35 + /* 54 */ { VC_META_R, kVK_RightShift }, // 0x36 + /* 55 */ { VC_META_L, kVK_ANSI_KeypadMultiply }, // 0x37 + /* 56 */ { VC_SHIFT_L, kVK_Option }, // 0x38 + /* 57 */ { VC_CAPS_LOCK, kVK_Space }, // 0x39 + /* 58 */ { VC_ALT_L, kVK_CapsLock }, // 0x3A + /* 59 */ { VC_CONTROL_L, kVK_F1 }, // 0x3B + /* 60 */ { VC_SHIFT_R, kVK_F2 }, // 0x3C + /* 61 */ { VC_ALT_R, kVK_F3 }, // 0x3D + /* 62 */ { VC_CONTROL_R, kVK_F4 }, // 0x3E + /* 63 */ { VC_UNDEFINED, kVK_F5 }, // 0x3F + /* 64 */ { VC_F17, kVK_F6 }, // 0x40 + /* 65 */ { VC_KP_SEPARATOR, kVK_F7 }, // 0x41 + /* 66 */ { VC_UNDEFINED, kVK_F8 }, // 0x42 + /* 67 */ { VC_KP_MULTIPLY, kVK_F9 }, // 0x43 + /* 68 */ { VC_UNDEFINED, kVK_F10 }, // 0x44 + /* 69 */ { VC_KP_ADD, kVK_ANSI_KeypadClear }, // 0x45 + /* 70 */ { VC_UNDEFINED, kVK_Undefined }, // 0x46 + /* 71 */ { VC_NUM_LOCK, kVK_ANSI_Keypad7 }, // 0x47 + /* 72 */ { VC_VOLUME_UP, kVK_ANSI_Keypad8 }, // 0x48 + /* 73 */ { VC_VOLUME_DOWN, kVK_ANSI_Keypad9 }, // 0x49 + /* 74 */ { VC_VOLUME_MUTE, kVK_ANSI_KeypadMinus }, // 0x4A + /* 75 */ { VC_KP_DIVIDE, kVK_ANSI_Keypad4 }, // 0x4B + /* 76 */ { VC_KP_ENTER, kVK_ANSI_Keypad5 }, // 0x4C + /* 77 */ { VC_UNDEFINED, kVK_ANSI_Keypad6 }, // 0x4D + /* 78 */ { VC_KP_SUBTRACT, kVK_ANSI_KeypadPlus }, // 0x4E + /* 79 */ { VC_F18, kVK_ANSI_Keypad1 }, // 0x4F + /* 80 */ { VC_F19, kVK_ANSI_Keypad2 }, // 0x50 + /* 81 */ { VC_KP_EQUALS, kVK_ANSI_Keypad3 }, // 0x51 + /* 82 */ { VC_KP_0, kVK_ANSI_Keypad0 }, // 0x52 + /* 83 */ { VC_KP_1, kVK_ANSI_KeypadDecimal }, // 0x53 + /* 84 */ { VC_KP_2, kVK_Undefined }, // 0x54 + /* 85 */ { VC_KP_3, kVK_Undefined }, // 0x55 + /* 86 */ { VC_KP_4, kVK_Undefined }, // 0x56 + /* 87 */ { VC_KP_5, kVK_F11 }, // 0x57 + /* 88 */ { VC_KP_6, kVK_F12 }, // 0x58 + /* 89 */ { VC_KP_7, kVK_Undefined }, // 0x59 + /* 90 */ { VC_F20, kVK_Undefined }, // 0x5A + /* 91 */ { VC_KP_8, kVK_F13 }, // 0x5B + /* 92 */ { VC_KP_9, kVK_F14 }, // 0x5C + /* 93 */ { VC_YEN, kVK_F15 }, // 0x5D + /* 94 */ { VC_UNDERSCORE, kVK_Undefined }, // 0x5E + /* 95 */ { VC_KP_COMMA, kVK_Undefined }, // 0x5F + /* 96 */ { VC_F5, kVK_Undefined }, // 0x60 + /* 97 */ { VC_F6, kVK_Undefined }, // 0x61 + /* 98 */ { VC_F7, kVK_Undefined }, // 0x62 + /* 99 */ { VC_F3, kVK_F16 }, // 0x63 + /* 100 */ { VC_F8, kVK_F17 }, // 0x64 + /* 101 */ { VC_F9, kVK_F18 }, // 0x65 + /* 102 */ { VC_UNDEFINED, kVK_F19 }, // 0x66 + /* 103 */ { VC_F11, kVK_F20 }, // 0x67 + /* 104 */ { VC_KATAKANA, kVK_Undefined }, // 0x68 + /* 105 */ { VC_F13, kVK_Undefined }, // 0x69 + /* 106 */ { VC_F16, kVK_Undefined }, // 0x6A + /* 107 */ { VC_F14, kVK_Undefined }, // 0x6B + /* 108 */ { VC_UNDEFINED, kVK_Undefined }, // 0x6C FIXME kVK_JIS_Eisu same as Caps Lock ? + /* 109 */ { VC_F10, kVK_Undefined }, // 0x6D + /* 110 */ { VC_UNDEFINED, kVK_Undefined }, // 0x6E + /* 111 */ { VC_F12, kVK_Undefined }, // 0x6F + /* 112 */ { VC_UNDEFINED, kVK_JIS_Kana }, // 0x70 + /* 113 */ { VC_F15, kVK_Undefined }, // 0x71 + /* 114 */ { VC_INSERT, kVK_Undefined }, // 0x72 + /* 115 */ { VC_HOME, kVK_JIS_Underscore }, // 0x73 + /* 116 */ { VC_PAGE_UP, kVK_Undefined }, // 0x74 + /* 117 */ { VC_DELETE, kVK_Undefined }, // 0x75 + /* 118 */ { VC_F4, kVK_Undefined }, // 0x76 + /* 119 */ { VC_END, kVK_Undefined }, // 0x77 + /* 120 */ { VC_F2, kVK_Undefined }, // 0x78 + /* 121 */ { VC_PAGE_DOWN, kVK_Undefined }, // 0x79 + /* 122 */ { VC_F1, kVK_Undefined }, // 0x7A + /* 123 */ { VC_LEFT, kVK_Undefined }, // 0x7B + /* 124 */ { VC_RIGHT, kVK_Undefined }, // 0x7C + /* 125 */ { VC_DOWN, kVK_JIS_Yen }, // 0x7D + /* 126 */ { VC_UP, kVK_JIS_KeypadComma }, // 0x7E + /* 127 */ { VC_UNDEFINED, kVK_Undefined }, // 0x7F + + // No Offset Offset (i & 0x007F) + 128 + + /* 128 */ { VC_UNDEFINED, kVK_Undefined }, // 0x80 + /* 129 */ { VC_UNDEFINED, kVK_Undefined }, // 0x81 + /* 130 */ { VC_UNDEFINED, kVK_Undefined }, // 0x82 + /* 131 */ { VC_UNDEFINED, kVK_Undefined }, // 0x83 + /* 132 */ { VC_UNDEFINED, kVK_Undefined }, // 0x84 + /* 133 */ { VC_UNDEFINED, kVK_Undefined }, // 0x85 + /* 134 */ { VC_UNDEFINED, kVK_Undefined }, // 0x86 + /* 135 */ { VC_UNDEFINED, kVK_Undefined }, // 0x87 + /* 136 */ { VC_UNDEFINED, kVK_Undefined }, // 0x88 + /* 137 */ { VC_UNDEFINED, kVK_Undefined }, // 0x89 + /* 138 */ { VC_UNDEFINED, kVK_Undefined }, // 0x8A + /* 139 */ { VC_UNDEFINED, kVK_Undefined }, // 0x8B + /* 140 */ { VC_UNDEFINED, kVK_Undefined }, // 0x8C + /* 141 */ { VC_UNDEFINED, kVK_ANSI_KeypadEquals }, // 0x8D + /* 142 */ { VC_UNDEFINED, kVK_Undefined }, // 0x8E + /* 143 */ { VC_UNDEFINED, kVK_Undefined }, // 0x8F + /* 144 */ { VC_UNDEFINED, kVK_MEDIA_Previous }, // 0x90 + /* 145 */ { VC_UNDEFINED, kVK_Undefined }, // 0x91 + /* 146 */ { VC_UNDEFINED, kVK_Undefined }, // 0x92 + /* 147 */ { VC_UNDEFINED, kVK_Undefined }, // 0x93 + /* 148 */ { VC_UNDEFINED, kVK_Undefined }, // 0x94 + /* 149 */ { VC_UNDEFINED, kVK_Undefined }, // 0x95 + /* 150 */ { VC_UNDEFINED, kVK_Undefined }, // 0x96 + /* 151 */ { VC_UNDEFINED, kVK_Undefined }, // 0x97 + /* 152 */ { VC_UNDEFINED, kVK_Undefined }, // 0x98 + /* 153 */ { VC_UNDEFINED, kVK_MEDIA_Next }, // 0x99 + /* 154 */ { VC_UNDEFINED, kVK_Undefined }, // 0x9A + /* 155 */ { VC_UNDEFINED, kVK_Undefined }, // 0x9B + /* 156 */ { VC_UNDEFINED, kVK_ANSI_KeypadEnter }, // 0x9C + /* 157 */ { VC_UNDEFINED, kVK_RightControl }, // 0x9D + /* 158 */ { VC_UNDEFINED, kVK_Undefined }, // 0x9E + /* 159 */ { VC_UNDEFINED, kVK_Undefined }, // 0x9F + /* 160 */ { VC_UNDEFINED, kVK_Mute }, // 0xA0 + /* 161 */ { VC_UNDEFINED, kVK_Undefined }, // 0xA1 + /* 162 */ { VC_UNDEFINED, kVK_MEDIA_Play }, // 0xA2 + /* 163 */ { VC_UNDEFINED, kVK_Undefined }, // 0xA3 + /* 164 */ { VC_UNDEFINED, kVK_Undefined }, // 0xA4 + /* 165 */ { VC_UNDEFINED, kVK_Undefined }, // 0xA5 + /* 166 */ { VC_UNDEFINED, kVK_Undefined }, // 0xA6 + /* 167 */ { VC_UNDEFINED, kVK_Undefined }, // 0xA7 + /* 168 */ { VC_UNDEFINED, kVK_Undefined }, // 0xA8 + /* 169 */ { VC_UNDEFINED, kVK_Undefined }, // 0xA9 + /* 170 */ { VC_UNDEFINED, kVK_Undefined }, // 0xAA + /* 171 */ { VC_UNDEFINED, kVK_Undefined }, // 0xAB + /* 172 */ { VC_UNDEFINED, kVK_NX_Eject }, // 0xAC + /* 173 */ { VC_UNDEFINED, kVK_Undefined }, // 0xAD + /* 174 */ { VC_UNDEFINED, kVK_VolumeDown }, // 0xAE + /* 175 */ { VC_UNDEFINED, kVK_Undefined }, // 0xAF + /* 176 */ { VC_UNDEFINED, kVK_VolumeUp }, // 0xB0 + /* 177 */ { VC_UNDEFINED, kVK_Undefined }, // 0xB1 + /* 178 */ { VC_UNDEFINED, kVK_Undefined }, // 0xB2 + /* 179 */ { VC_UNDEFINED, kVK_Undefined }, // 0xB3 + /* 180 */ { VC_UNDEFINED, kVK_Undefined }, // 0xB4 + /* 181 */ { VC_UNDEFINED, kVK_ANSI_KeypadDivide }, // 0xB5 + /* 182 */ { VC_UNDEFINED, kVK_Undefined }, // 0xB6 + /* 183 */ { VC_UNDEFINED, kVK_Undefined }, // 0xB7 + /* 184 */ { VC_UNDEFINED, kVK_RightOption }, // 0xB8 + /* 185 */ { VC_UNDEFINED, kVK_Undefined }, // 0xB9 + /* 186 */ { VC_UNDEFINED, kVK_Undefined }, // 0xBA + /* 187 */ { VC_UNDEFINED, kVK_Undefined }, // 0xBB + /* 188 */ { VC_UNDEFINED, kVK_Undefined }, // 0xBC + /* 189 */ { VC_UNDEFINED, kVK_Undefined }, // 0xBD + /* 190 */ { VC_UNDEFINED, kVK_Undefined }, // 0xBE + /* 191 */ { VC_UNDEFINED, kVK_Undefined }, // 0xBF + /* 192 */ { VC_UNDEFINED, kVK_Undefined }, // 0xC0 + /* 193 */ { VC_UNDEFINED, kVK_Undefined }, // 0xC1 + /* 194 */ { VC_UNDEFINED, kVK_Undefined }, // 0xC2 + /* 195 */ { VC_UNDEFINED, kVK_Undefined }, // 0xC3 + /* 196 */ { VC_UNDEFINED, kVK_Undefined }, // 0xC4 + /* 197 */ { VC_UNDEFINED, kVK_Undefined }, // 0xC5 + /* 198 */ { VC_UNDEFINED, kVK_Undefined }, // 0xC6 + /* 199 */ { VC_UNDEFINED, kVK_Home }, // 0xC7 + /* 200 */ { VC_UNDEFINED, kVK_UpArrow }, // 0xC8 + /* 201 */ { VC_UNDEFINED, kVK_PageUp }, // 0xC9 + /* 202 */ { VC_UNDEFINED, kVK_Undefined }, // 0xCA + /* 203 */ { VC_UNDEFINED, kVK_LeftArrow }, // 0xCB + /* 204 */ { VC_UNDEFINED, kVK_Undefined }, // 0xCC + /* 205 */ { VC_UNDEFINED, kVK_RightArrow }, // 0xCD + /* 206 */ { VC_UNDEFINED, kVK_Undefined }, // 0xCE + /* 207 */ { VC_UNDEFINED, kVK_End }, // 0xCF + /* 208 */ { VC_UNDEFINED, kVK_DownArrow }, // 0xD0 + /* 209 */ { VC_UNDEFINED, kVK_PageDown }, // 0xD1 + /* 210 */ { VC_UNDEFINED, kVK_Help }, // 0xD2 + /* 211 */ { VC_UNDEFINED, kVK_ForwardDelete }, // 0xD3 + /* 212 */ { VC_UNDEFINED, kVK_Undefined }, // 0xD4 + /* 213 */ { VC_UNDEFINED, kVK_Undefined }, // 0xD5 + /* 214 */ { VC_UNDEFINED, kVK_Undefined }, // 0xD6 + /* 215 */ { VC_UNDEFINED, kVK_Undefined }, // 0xD7 + /* 216 */ { VC_UNDEFINED, kVK_Undefined }, // 0xD8 + /* 217 */ { VC_UNDEFINED, kVK_Undefined }, // 0xD9 + /* 218 */ { VC_UNDEFINED, kVK_Undefined }, // 0xDA + /* 219 */ { VC_UNDEFINED, kVK_Command }, // 0xDB + /* 220 */ { VC_UNDEFINED, kVK_RightCommand }, // 0xDC + /* 221 */ { VC_UNDEFINED, kVK_Undefined }, // 0xDD + /* 222 */ { VC_UNDEFINED, kVK_NX_Power }, // 0xDE + /* 223 */ { VC_UNDEFINED, kVK_Undefined }, // 0xDF + /* 224 */ { VC_UNDEFINED, kVK_Undefined }, // 0xE0 + /* 225 */ { VC_UNDEFINED, kVK_Undefined }, // 0xE1 + /* 226 */ { VC_UNDEFINED, kVK_Undefined }, // 0xE2 + /* 227 */ { VC_UNDEFINED, kVK_Undefined }, // 0xE3 + /* 228 */ { VC_UNDEFINED, kVK_Undefined }, // 0xE4 + /* 229 */ { VC_UNDEFINED, kVK_Undefined }, // 0xE5 + /* 230 */ { VC_POWER, kVK_Undefined }, // 0xE6 + /* 231 */ { VC_UNDEFINED, kVK_Undefined }, // 0xE7 + /* 232 */ { VC_UNDEFINED, kVK_Undefined }, // 0xE8 + /* 233 */ { VC_UNDEFINED, kVK_Undefined }, // 0xE9 + /* 234 */ { VC_UNDEFINED, kVK_Undefined }, // 0xEA + /* 235 */ { VC_UNDEFINED, kVK_Undefined }, // 0xEB + /* 236 */ { VC_UNDEFINED, kVK_Undefined }, // 0xEC + /* 237 */ { VC_UNDEFINED, kVK_Undefined }, // 0xED + /* 238 */ { VC_MEDIA_EJECT, kVK_Undefined }, // 0xEE + /* 239 */ { VC_UNDEFINED, kVK_Undefined }, // 0xEF + /* 240 */ { VC_MEDIA_PLAY, kVK_Undefined }, // 0xF0 + /* 241 */ { VC_MEDIA_NEXT, kVK_Undefined }, // 0xF1 + /* 242 */ { VC_MEDIA_PREVIOUS, kVK_Undefined }, // 0xF2 + /* 243 */ { VC_UNDEFINED, kVK_Undefined }, // 0xF3 + /* 244 */ { VC_UNDEFINED, kVK_Undefined }, // 0xF4 + /* 245 */ { VC_UNDEFINED, kVK_Undefined }, // 0xF5 + /* 246 */ { VC_UNDEFINED, kVK_Undefined }, // 0xF6 + /* 247 */ { VC_UNDEFINED, kVK_Undefined }, // 0xF7 + /* 248 */ { VC_UNDEFINED, kVK_Undefined }, // 0xF8 + /* 249 */ { VC_UNDEFINED, kVK_Undefined }, // 0xF9 + /* 250 */ { VC_UNDEFINED, kVK_Undefined }, // 0xFA + /* 251 */ { VC_UNDEFINED, kVK_Undefined }, // 0xFB + /* 252 */ { VC_UNDEFINED, kVK_Undefined }, // 0xFC + /* 253 */ { VC_UNDEFINED, kVK_Undefined }, // 0xFD + /* 254 */ { VC_UNDEFINED, kVK_Undefined }, // 0xFE + /* 255 */ { VC_UNDEFINED, kVK_Undefined }, // 0xFF +}; + +uint16_t keycode_to_scancode(UInt64 keycode) { + uint16_t scancode = VC_UNDEFINED; + + // Bound check 0 <= keycode < 256 + if (keycode < sizeof(keycode_scancode_table) / sizeof(keycode_scancode_table[0])) { + scancode = keycode_scancode_table[keycode][0]; + } + + return scancode; +} + +UInt64 scancode_to_keycode(uint16_t scancode) { + UInt64 keycode = kVK_Undefined; + + // Bound check 0 <= keycode < 128 + if (scancode < 128) { + keycode = keycode_scancode_table[scancode][1]; + } else { + // Calculate the upper offset. + unsigned short i = (scancode & 0x007F) | 0x80; + + if (i < sizeof(keycode_scancode_table) / sizeof(keycode_scancode_table[1])) { + keycode = keycode_scancode_table[i][1]; + } + } + + return keycode; +} + +void load_input_helper() { + #if defined(USE_CARBON_LEGACY) || defined(USE_COREFOUNDATION) + // Start with a fresh dead key state. + //curr_deadkey_state = 0; + #endif +} + +void unload_input_helper() { + #if defined(USE_CARBON_LEGACY) || defined(USE_COREFOUNDATION) + if (prev_keyboard_layout != NULL) { + // Cleanup tracking of the previous layout. + CFRelease(prev_keyboard_layout); + prev_keyboard_layout = NULL; + } + #endif +} diff --git a/hook/darwin/properties_c.h b/hook/darwin/properties_c.h new file mode 100644 index 0000000..9dc8ebb --- /dev/null +++ b/hook/darwin/properties_c.h @@ -0,0 +1,528 @@ +/* Copyright (C) 2006-2017 Alexander Barker. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. +*/ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#ifdef USE_CARBON_LEGACY + #include +#endif +#ifdef USE_COREFOUNDATION + #include +#endif +#ifdef USE_IOKIT + #include + #include +#endif + +#include +#include "../iohook.h" +#include "input.h" +// #include "../logger_c.h" + +IOHOOK_API screen_data* hook_create_screen_info(unsigned char *count) { + CGError status = kCGErrorFailure; + screen_data* screens = NULL; + + // Initialize count to zero. + *count = 0; + + // Allocate memory to hold each display id. We will just allocate our MAX + // because its only about 1K of memory. + // TODO This can probably be realistically cut to something like 16 or 32.... + // If you have more than 32 monitors, send me a picture and make a donation ;) + CGDirectDisplayID *display_ids = malloc(sizeof(CGDirectDisplayID) * UCHAR_MAX); + if (display_ids != NULL) { + // NOTE Pass UCHAR_MAX to make sure uint32_t doesn't overflow uint8_t. + // TOOD Test/Check whether CGGetOnlineDisplayList is more suitable... + status = CGGetActiveDisplayList(UCHAR_MAX, display_ids, (uint32_t *) count); + + // If there is no error and at least one monitor. + if (status == kCGErrorSuccess && *count > 0) { + logger(LOG_LEVEL_INFO, "%s [%u]: CGGetActiveDisplayList: %li.\n", + __FUNCTION__, __LINE__, *count); + + // Allocate memory for the number of screens found. + screens = malloc(sizeof(screen_data) * (*count)); + if (screens != NULL) { + uint8_t i; + for (i = 0; i < *count; i++) { + //size_t width = CGDisplayPixelsWide(display_ids[i]); + //size_t height = CGDisplayPixelsHigh(display_ids[i]); + CGRect boundsDisp = CGDisplayBounds(display_ids[i]); + if (boundsDisp.size.width > 0 && boundsDisp.size.height > 0) { + screens[i] = (screen_data) { + .number = i + 1, + //TODO: make sure we follow the same convention for the origin + //in all other platform implementations (upper-left) + //TODO: document the approach with examples in order to show different + //cases -> different resolutions (secondary monitors origin might be + //negative) + .x = boundsDisp.origin.x, + .y = boundsDisp.origin.y, + .width = boundsDisp.size.width, + .height = boundsDisp.size.height + }; + } + } + } + } else { + logger(LOG_LEVEL_INFO, "%s [%u]: multiple_get_screen_info failed: %ld. Fallback.\n", + __FUNCTION__, __LINE__, status); + + size_t width = CGDisplayPixelsWide(CGMainDisplayID()); + size_t height = CGDisplayPixelsHigh(CGMainDisplayID()); + + if (width > 0 && height > 0) { + screens = malloc(sizeof(screen_data)); + + if (screens != NULL) { + *count = 1; + screens[0] = (screen_data) { + .number = 1, + .x = 0, + .y = 0, + .width = width, + .height = height + }; + } + } + } + + // Free the id's after we are done. + free(display_ids); + } + + return screens; +} + +/* + * Apple's documentation is not very good. I was finally able to find this + * information after many hours of googling. Value is the slider value in the + * system preferences. That value * 15 is the rate in MS. 66 / the value is the + * chars per second rate. + * + * Value MS Char/Sec + * + * 1 15 66 * Out of standard range * + * + * 2 30 33 + * 6 90 11 + * 12 180 5.5 + * 30 450 2.2 + * 60 900 1.1 + * 90 1350 0.73 + * 120 1800 0.55 + * + * V = MS / 15 + * V = 66 / CharSec + * + * MS = V * 15 + * MS = (66 / CharSec) * 15 + * + * CharSec = 66 / V + * CharSec = 66 / (MS / 15) + */ + +IOHOOK_API long int hook_get_auto_repeat_rate() { + #if defined USE_IOKIT || defined USE_COREFOUNDATION || defined USE_CARBON_LEGACY + bool successful = false; + SInt64 rate; + #endif + + long int value = -1; + + #ifdef USE_IOKIT + if (!successful) { + io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching(kIOHIDSystemClass)); + if (service) { + kern_return_t kren_ret = kIOReturnError; + io_connect_t connection; + + kren_ret = IOServiceOpen(service, mach_task_self(), kIOHIDParamConnectType, &connection); + if (kren_ret == kIOReturnSuccess) { + IOByteCount size = sizeof(rate); + + kren_ret = IOHIDGetParameter(connection, CFSTR(kIOHIDKeyRepeatKey), (IOByteCount) sizeof(rate), &rate, &size); + if (kren_ret == kIOReturnSuccess) { + /* This is in some undefined unit of time that if we happen + * to multiply by 900 gives us the time in milliseconds. We + * add 0.5 to the result so that when we cast to long we + * actually get a rounded result. Saves the math.h depend. + * + * 33,333,333.0 / 1000.0 / 1000.0 / 1000.0 == 0.033333333 * Fast * + * 100,000,000.0 / 1000.0 / 1000.0 / 1000.0 == 0.1 + * 200,000,000.0 / 1000.0 / 1000.0 / 1000.0 == 0.2 + * 500,000,000.0 / 1000.0 / 1000.0 / 1000.0 == 0.5 + * 1,000,000,000.0 / 1000.0 / 1000.0 / 1000.0 == 1 + * 1,500,000,000.0 / 1000.0 / 1000.0 / 1000.0 == 1.5 + * 2,000,000,000.0 / 1000.0 / 1000.0 / 1000.0 == 2 * Slow * + */ + value = (long) (900.0 * ((double) rate) / 1000.0 / 1000.0 / 1000.0 + 0.5); + successful = true; + + logger(LOG_LEVEL_INFO, "%s [%u]: IOHIDGetParameter: %li.\n", + __FUNCTION__, __LINE__, value); + } + } + } + } + #endif + + #ifdef USE_COREFOUNDATION + if (!successful) { + CFTypeRef pref_val = CFPreferencesCopyValue(CFSTR("KeyRepeat"), kCFPreferencesAnyApplication, kCFPreferencesCurrentUser, kCFPreferencesAnyHost); + if (pref_val != NULL && CFGetTypeID(pref_val) == CFNumberGetTypeID()) { + if (CFNumberGetValue((CFNumberRef) pref_val, kCFNumberSInt32Type, &rate)) { + // This is the slider value, we must multiply by 15 to convert to milliseconds. + value = (long) rate * 15; + successful = true; + + logger(LOG_LEVEL_INFO, "%s [%u]: CFPreferencesCopyValue: %li.\n", + __FUNCTION__, __LINE__, value); + } + } + } + #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_INFO, "%s [%u]: LMGetKeyRepThresh: %li.\n", + __FUNCTION__, __LINE__, value); + } + } + #endif + + return value; +} + +IOHOOK_API long int hook_get_auto_repeat_delay() { + #if defined USE_IOKIT || defined USE_COREFOUNDATION || defined USE_CARBON_LEGACY + bool successful = false; + SInt64 delay; + #endif + + long int value = -1; + + #ifdef USE_IOKIT + if (!successful) { + io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching(kIOHIDSystemClass)); + if (service) { + kern_return_t kren_ret = kIOReturnError; + io_connect_t connection; + + kren_ret = IOServiceOpen(service, mach_task_self(), kIOHIDParamConnectType, &connection); + if (kren_ret == kIOReturnSuccess) { + IOByteCount size = sizeof(delay); + + kren_ret = IOHIDGetParameter(connection, CFSTR(kIOHIDInitialKeyRepeatKey), (IOByteCount) sizeof(delay), &delay, &size); + if (kren_ret == kIOReturnSuccess) { + /* This is in some undefined unit of time that if we happen + * to multiply by 900 gives us the time in milliseconds. We + * add 0.5 to the result so that when we cast to long we + * actually get a rounded result. Saves the math.h depend. + * + * 33,333,333.0 / 1000.0 / 1000.0 / 1000.0 == 0.033333333 * Fast * + * 100,000,000.0 / 1000.0 / 1000.0 / 1000.0 == 0.1 + * 200,000,000.0 / 1000.0 / 1000.0 / 1000.0 == 0.2 + * 500,000,000.0 / 1000.0 / 1000.0 / 1000.0 == 0.5 + * 1,000,000,000.0 / 1000.0 / 1000.0 / 1000.0 == 1 + * 1,500,000,000.0 / 1000.0 / 1000.0 / 1000.0 == 1.5 + * 2,000,000,000.0 / 1000.0 / 1000.0 / 1000.0 == 2 * Slow * + */ + value = (long) (900.0 * ((double) delay) / 1000.0 / 1000.0 / 1000.0 + 0.5); + successful = true; + + logger(LOG_LEVEL_INFO, "%s [%u]: IOHIDGetParameter: %li.\n", + __FUNCTION__, __LINE__, value); + } + } + } + } + #endif + + #ifdef USE_COREFOUNDATION + if (!successful) { + CFTypeRef pref_val = CFPreferencesCopyValue(CFSTR("InitialKeyRepeat"), kCFPreferencesAnyApplication, kCFPreferencesCurrentUser, kCFPreferencesAnyHost); + if (pref_val != NULL && CFGetTypeID(pref_val) == CFNumberGetTypeID()) { + if (CFNumberGetValue((CFNumberRef) pref_val, kCFNumberSInt32Type, &delay)) { + // This is the slider value, we must multiply by 15 to convert to + // milliseconds. + value = (long) delay * 15; + successful = true; + + logger(LOG_LEVEL_INFO, "%s [%u]: CFPreferencesCopyValue: %li.\n", + __FUNCTION__, __LINE__, value); + } + } + } + #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_INFO, "%s [%u]: LMGetKeyThresh: %li.\n", + __FUNCTION__, __LINE__, value); + } + } + #endif + + return value; +} + +IOHOOK_API long int hook_get_pointer_acceleration_multiplier() { + #if defined USE_IOKIT || defined USE_COREFOUNDATION + bool successful = false; + double multiplier; + #endif + + long int value = -1; + + #ifdef USE_IOKIT + if (!successful) { + io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching(kIOHIDSystemClass)); + + if (service) { + kern_return_t kren_ret = kIOReturnError; + io_connect_t connection; + + kren_ret = IOServiceOpen(service, mach_task_self(), kIOHIDParamConnectType, &connection); + if (kren_ret == kIOReturnSuccess) { + // IOByteCount size = sizeof(multiplier); + + kren_ret = IOHIDGetAccelerationWithKey(connection, CFSTR(kIOHIDMouseAccelerationType), &multiplier); + if (kren_ret == kIOReturnSuccess) { + // Calculate the greatest common factor. + + unsigned long denominator = 1000000, d = denominator; + unsigned long numerator = multiplier * denominator, gcf = numerator; + + while (d != 0) { + unsigned long i = gcf % d; + gcf = d; + d = i; + } + + value = denominator / gcf; + successful = true; + + logger(LOG_LEVEL_INFO, "%s [%u]: IOHIDGetAccelerationWithKey: %li.\n", + __FUNCTION__, __LINE__, value); + } + } + } + } + #endif + + #ifdef USE_COREFOUNDATION + if (!successful) { + CFTypeRef pref_val = CFPreferencesCopyValue(CFSTR("com.apple.mouse.scaling"), kCFPreferencesAnyApplication, kCFPreferencesCurrentUser, kCFPreferencesAnyHost); + if (pref_val != NULL && CFGetTypeID(pref_val) == CFNumberGetTypeID()) { + if (CFNumberGetValue((CFNumberRef) pref_val, kCFNumberSInt32Type, &multiplier)) { + value = (long) multiplier; + + logger(LOG_LEVEL_INFO, "%s [%u]: CFPreferencesCopyValue: %li.\n", + __FUNCTION__, __LINE__, value); + } + } + } + #endif + + return value; +} + +IOHOOK_API long int hook_get_pointer_acceleration_threshold() { + #if defined USE_COREFOUNDATION + bool successful = false; + SInt32 threshold; + #endif + + long int value = -1; + + #ifdef USE_COREFOUNDATION + if (!successful) { + CFTypeRef pref_val = CFPreferencesCopyValue(CFSTR("mouseDriverMaxSpeed"), CFSTR("com.apple.universalaccess"), kCFPreferencesCurrentUser, kCFPreferencesAnyHost); + if (pref_val != NULL && CFGetTypeID(pref_val) == CFNumberGetTypeID()) { + if (CFNumberGetValue((CFNumberRef) pref_val, kCFNumberSInt32Type, &threshold)) { + value = (long) threshold; + + logger(LOG_LEVEL_INFO, "%s [%u]: CFPreferencesCopyValue: %li.\n", + __FUNCTION__, __LINE__, value); + } + } + } + #endif + + return value; +} + +IOHOOK_API long int hook_get_pointer_sensitivity() { + #ifdef USE_IOKIT + bool successful = false; + double sensitivity; + #endif + + long int value = -1; + + #ifdef USE_IOKIT + if (!successful) { + io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching(kIOHIDSystemClass)); + + if (service) { + kern_return_t kren_ret = kIOReturnError; + io_connect_t connection; + + kren_ret = IOServiceOpen(service, mach_task_self(), kIOHIDParamConnectType, &connection); + if (kren_ret == kIOReturnSuccess) { + // IOByteCount size = sizeof(multiplier); + + kren_ret = IOHIDGetAccelerationWithKey(connection, CFSTR(kIOHIDMouseAccelerationType), &sensitivity); + if (kren_ret == kIOReturnSuccess) { + // Calculate the greatest common factor. + + unsigned long denominator = 1000000, d = denominator; + unsigned long numerator = sensitivity * denominator, gcf = numerator; + + while (d != 0) { + unsigned long i = gcf % d; + gcf = d; + d = i; + } + + value = numerator / gcf; + successful = true; + + logger(LOG_LEVEL_INFO, "%s [%u]: IOHIDGetAccelerationWithKey: %li.\n", + __FUNCTION__, __LINE__, value); + } + } + } + } + #endif + + return value; +} + +IOHOOK_API long int hook_get_multi_click_time() { + #if defined USE_IOKIT || defined USE_COREFOUNDATION || defined USE_CARBON_LEGACY + bool successful = false; + #if defined USE_IOKIT || defined USE_CARBON_LEGACY + // This needs to be defined only if we have USE_IOKIT or USE_CARBON_LEGACY. + SInt64 time; + #endif + #endif + + long int value = -1; + + #ifdef USE_IOKIT + if (!successful) { + io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching(kIOHIDSystemClass)); + if (service) { + kern_return_t kren_ret = kIOReturnError; + io_connect_t connection; + + kren_ret = IOServiceOpen(service, mach_task_self(), kIOHIDParamConnectType, &connection); + if (kren_ret == kIOReturnSuccess) { + IOByteCount size = sizeof(time); + + kren_ret = IOHIDGetParameter(connection, CFSTR(kIOHIDClickTimeKey), (IOByteCount) sizeof(time), &time, &size); + if (kren_ret == kIOReturnSuccess) { + /* This is in some undefined unit of time that if we happen + * to multiply by 900 gives us the time in milliseconds. We + * add 0.5 to the result so that when we cast to long we + * actually get a rounded result. Saves the math.h depend. + */ + value = (long) (900.0 * ((double) time) / 1000.0 / 1000.0 / 1000.0 + 0.5); + successful = true; + + logger(LOG_LEVEL_INFO, "%s [%u]: IOHIDGetParameter: %li.\n", + __FUNCTION__, __LINE__, value); + } + } + } + } + #endif + + #ifdef USE_COREFOUNDATION + if (!successful) { + Float32 clicktime; + CFTypeRef pref_val = CFPreferencesCopyValue(CFSTR("com.apple.mouse.doubleClickThreshold"), kCFPreferencesAnyApplication, kCFPreferencesCurrentUser, kCFPreferencesAnyHost); + if (pref_val != NULL && CFGetTypeID(pref_val) == CFNumberGetTypeID()) { + if (CFNumberGetValue((CFNumberRef) pref_val, kCFNumberFloat32Type, &clicktime)) { + /* This is in some undefined unit of time that if we happen + * to multiply by 900 gives us the time in milliseconds. It is + * completely possible that this value is in seconds and should be + * multiplied by 1000 but because IOKit values are undocumented and + * I have no idea what a Carbon 'tick' is so there really is no way + * to confirm this. + */ + value = (long) (clicktime * 900); + + logger(LOG_LEVEL_INFO, "%s [%u]: CFPreferencesCopyValue: %li.\n", + __FUNCTION__, __LINE__, value); + } + } + } + #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_INFO, "%s [%u]: GetDblTime: %li.\n", + __FUNCTION__, __LINE__, value); + } + } + #endif + + return value; +} + + +// Create a shared object constructor. +__attribute__ ((constructor)) +void on_library_load() { + // Initialize Native Input Functions. + load_input_helper(); +} + +// Create a shared object destructor. +__attribute__ ((destructor)) +void on_library_unload() { + // Disable the event hook. + //hook_stop(); + + // Cleanup native input functions. + unload_input_helper(); +} diff --git a/hook/iohook.h b/hook/iohook.h new file mode 100644 index 0000000..6fd3471 --- /dev/null +++ b/hook/iohook.h @@ -0,0 +1,450 @@ +/* Copyright (C) 2006-2017 Alexander Barker. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. +*/ + +#ifndef __IOHOOK_H +#define __IOHOOK_H + +// #include "../../base/os.h" +#include +#include +#include + +/* Begin Error Codes */ +#define IOHOOK_SUCCESS 0x00 +#define IOHOOK_FAILURE 0x01 + +// System level errors. +#define IOHOOK_ERROR_OUT_OF_MEMORY 0x02 + +// Unix specific errors. +#define IOHOOK_ERROR_X_OPEN_DISPLAY 0x20 +#define IOHOOK_ERROR_X_RECORD_NOT_FOUND 0x21 +#define IOHOOK_ERROR_X_RECORD_ALLOC_RANGE 0x22 +#define IOHOOK_ERROR_X_RECORD_CREATE_CONTEXT 0x23 +#define IOHOOK_ERROR_X_RECORD_ENABLE_CONTEXT 0x24 +#define IOHOOK_ERROR_X_RECORD_GET_CONTEXT 0x25 + +// Windows specific errors. +#define IOHOOK_ERROR_SET_WINDOWS_HOOK_EX 0x30 +#define IOHOOK_ERROR_GET_MODULE_HANDLE 0x31 + +// Darwin specific errors. +#define IOHOOK_ERROR_AXAPI_DISABLED 0x40 +#define IOHOOK_ERROR_CREATE_EVENT_PORT 0x41 +#define IOHOOK_ERROR_CREATE_RUN_LOOP_SOURCE 0x42 +#define IOHOOK_ERROR_GET_RUNLOOP 0x43 +#define IOHOOK_ERROR_CREATE_OBSERVER 0x44 +/* End Error Codes */ + +/* Begin Log Levels and Function Prototype */ +typedef enum _log_level { + LOG_LEVEL_DEBUG = 1, + LOG_LEVEL_INFO, + LOG_LEVEL_WARN, + LOG_LEVEL_ERROR +} log_level; + +// Logger callback function prototype. +typedef bool (*logger_t)(unsigned int, const char *, ...); +/* End Log Levels and Function Prototype */ + +/* Begin Virtual Event Types and Data Structures */ +typedef enum _event_type { + EVENT_HOOK_ENABLED = 1, + EVENT_HOOK_DISABLED, + EVENT_KEY_TYPED, + EVENT_KEY_PRESSED, + EVENT_KEY_RELEASED, + EVENT_MOUSE_CLICKED, + EVENT_MOUSE_PRESSED, + EVENT_MOUSE_RELEASED, + EVENT_MOUSE_MOVED, + EVENT_MOUSE_DRAGGED, + EVENT_MOUSE_WHEEL +} event_type; + +typedef struct _screen_data { + uint8_t number; + int16_t x; + int16_t y; + uint16_t width; + uint16_t height; +} screen_data; + +typedef struct _keyboard_event_data { + uint16_t keycode; + uint16_t rawcode; + // uint16_t keychar; + uint32_t keychar; + // char *keychar; +} keyboard_event_data, + key_pressed_event_data, + key_released_event_data, + key_typed_event_data; + +typedef struct _mouse_event_data { + uint16_t button; + uint16_t clicks; + int16_t x; + int16_t y; +} mouse_event_data, + mouse_pressed_event_data, + mouse_released_event_data, + mouse_clicked_event_data; + +typedef struct _mouse_wheel_event_data { + uint16_t clicks; + int16_t x; + int16_t y; + uint8_t type; + uint16_t amount; + int32_t rotation; + // int16_t rotation; + uint8_t direction; +} mouse_wheel_event_data; + +typedef struct _iohook_event { + event_type type; + uint64_t time; + uint16_t mask; + uint16_t reserved; + union { + keyboard_event_data keyboard; + mouse_event_data mouse; + mouse_wheel_event_data wheel; + } data; +} iohook_event; + +typedef void (*dispatcher_t)(iohook_event *const); +/* End Virtual Event Types and Data Structures */ + + +/* Begin Virtual Key Codes */ +#define VC_ESCAPE 0x0001 + +// Begin Function Keys +#define VC_F1 0x003B +#define VC_F2 0x003C +#define VC_F3 0x003D +#define VC_F4 0x003E +#define VC_F5 0x003F +#define VC_F6 0x0040 +#define VC_F7 0x0041 +#define VC_F8 0x0042 +#define VC_F9 0x0043 +#define VC_F10 0x0044 +#define VC_F11 0x0057 +#define VC_F12 0x0058 + +#define VC_F13 0x005B +#define VC_F14 0x005C +#define VC_F15 0x005D +#define VC_F16 0x0063 +#define VC_F17 0x0064 +#define VC_F18 0x0065 +#define VC_F19 0x0066 +#define VC_F20 0x0067 +#define VC_F21 0x0068 +#define VC_F22 0x0069 +#define VC_F23 0x006A +#define VC_F24 0x006B +// End Function Keys + + +// Begin Alphanumeric Zone +#define VC_BACKQUOTE 0x0029 + +#define VC_1 0x0002 +#define VC_2 0x0003 +#define VC_3 0x0004 +#define VC_4 0x0005 +#define VC_5 0x0006 +#define VC_6 0x0007 +#define VC_7 0x0008 +#define VC_8 0x0009 +#define VC_9 0x000A +#define VC_0 0x000B + +#define VC_MINUS 0x000C // '-' +#define VC_EQUALS 0x000D // '=' +#define VC_BACKSPACE 0x000E + +#define VC_TAB 0x000F +#define VC_CAPS_LOCK 0x003A + +#define VC_A 0x001E +#define VC_B 0x0030 +#define VC_C 0x002E +#define VC_D 0x0020 +#define VC_E 0x0012 +#define VC_F 0x0021 +#define VC_G 0x0022 +#define VC_H 0x0023 +#define VC_I 0x0017 +#define VC_J 0x0024 +#define VC_K 0x0025 +#define VC_L 0x0026 +#define VC_M 0x0032 +#define VC_N 0x0031 +#define VC_O 0x0018 +#define VC_P 0x0019 +#define VC_Q 0x0010 +#define VC_R 0x0013 +#define VC_S 0x001F +#define VC_T 0x0014 +#define VC_U 0x0016 +#define VC_V 0x002F +#define VC_W 0x0011 +#define VC_X 0x002D +#define VC_Y 0x0015 +#define VC_Z 0x002C + +#define VC_OPEN_BRACKET 0x001A // '[' +#define VC_CLOSE_BRACKET 0x001B // ']' +#define VC_BACK_SLASH 0x002B // '\' + +#define VC_SEMICOLON 0x0027 // ';' +#define VC_QUOTE 0x0028 +#define VC_ENTER 0x001C + +#define VC_COMMA 0x0033 // ',' +#define VC_PERIOD 0x0034 // '.' +#define VC_SLASH 0x0035 // '/' + +#define VC_SPACE 0x0039 +// End Alphanumeric Zone + + +#define VC_PRINTSCREEN 0x0E37 +#define VC_SCROLL_LOCK 0x0046 +#define VC_PAUSE 0x0E45 + + +// Begin Edit Key Zone +#define VC_INSERT 0x0E52 +#define VC_DELETE 0x0E53 +#define VC_HOME 0x0E47 +#define VC_END 0x0E4F +#define VC_PAGE_UP 0x0E49 +#define VC_PAGE_DOWN 0x0E51 +// End Edit Key Zone + + +// Begin Cursor Key Zone +#define VC_UP 0xE048 +#define VC_LEFT 0xE04B +#define VC_CLEAR 0xE04C +#define VC_RIGHT 0xE04D +#define VC_DOWN 0xE050 +// End Cursor Key Zone + + +// Begin Numeric Zone +#define VC_NUM_LOCK 0x0045 +#define VC_KP_DIVIDE 0x0E35 +#define VC_KP_MULTIPLY 0x0037 +#define VC_KP_SUBTRACT 0x004A +#define VC_KP_EQUALS 0x0E0D +#define VC_KP_ADD 0x004E +#define VC_KP_ENTER 0x0E1C +#define VC_KP_SEPARATOR 0x0053 + +#define VC_KP_1 0x004F +#define VC_KP_2 0x0050 +#define VC_KP_3 0x0051 +#define VC_KP_4 0x004B +#define VC_KP_5 0x004C +#define VC_KP_6 0x004D +#define VC_KP_7 0x0047 +#define VC_KP_8 0x0048 +#define VC_KP_9 0x0049 +#define VC_KP_0 0x0052 + +#define VC_KP_END 0xEE00 | VC_KP_1 +#define VC_KP_DOWN 0xEE00 | VC_KP_2 +#define VC_KP_PAGE_DOWN 0xEE00 | VC_KP_3 +#define VC_KP_LEFT 0xEE00 | VC_KP_4 +#define VC_KP_CLEAR 0xEE00 | VC_KP_5 +#define VC_KP_RIGHT 0xEE00 | VC_KP_6 +#define VC_KP_HOME 0xEE00 | VC_KP_7 +#define VC_KP_UP 0xEE00 | VC_KP_8 +#define VC_KP_PAGE_UP 0xEE00 | VC_KP_9 +#define VC_KP_INSERT 0xEE00 | VC_KP_0 +#define VC_KP_DELETE 0xEE00 | VC_KP_SEPARATOR +// End Numeric Zone + + +// Begin Modifier and Control Keys +#define VC_SHIFT_L 0x002A +#define VC_SHIFT_R 0x0036 +#define VC_CONTROL_L 0x001D +#define VC_CONTROL_R 0x0E1D +#define VC_ALT_L 0x0038 // Option or Alt Key +#define VC_ALT_R 0x0E38 // Option or Alt Key +#define VC_META_L 0x0E5B // Windows or Command Key +#define VC_META_R 0x0E5C // Windows or Command Key +#define VC_CONTEXT_MENU 0x0E5D +// End Modifier and Control Keys + + +// Begin Media Control Keys +#define VC_POWER 0xE05E +#define VC_SLEEP 0xE05F +#define VC_WAKE 0xE063 + +#define VC_MEDIA_PLAY 0xE022 +#define VC_MEDIA_STOP 0xE024 +#define VC_MEDIA_PREVIOUS 0xE010 +#define VC_MEDIA_NEXT 0xE019 +#define VC_MEDIA_SELECT 0xE06D +#define VC_MEDIA_EJECT 0xE02C + +#define VC_VOLUME_MUTE 0xE020 +#define VC_VOLUME_UP 0xE030 +#define VC_VOLUME_DOWN 0xE02E + +#define VC_APP_MAIL 0xE06C +#define VC_APP_CALCULATOR 0xE021 +#define VC_APP_MUSIC 0xE03C +#define VC_APP_PICTURES 0xE064 + +#define VC_BROWSER_SEARCH 0xE065 +#define VC_BROWSER_HOME 0xE032 +#define VC_BROWSER_BACK 0xE06A +#define VC_BROWSER_FORWARD 0xE069 +#define VC_BROWSER_STOP 0xE068 +#define VC_BROWSER_REFRESH 0xE067 +#define VC_BROWSER_FAVORITES 0xE066 +// End Media Control Keys + +// Begin Japanese Language Keys +#define VC_KATAKANA 0x0070 +#define VC_UNDERSCORE 0x0073 +#define VC_FURIGANA 0x0077 +#define VC_KANJI 0x0079 +#define VC_HIRAGANA 0x007B +#define VC_YEN 0x007D +#define VC_KP_COMMA 0x007E +// End Japanese Language Keys + +// Begin Sun keyboards +#define VC_SUN_HELP 0xFF75 + +#define VC_SUN_STOP 0xFF78 +#define VC_SUN_PROPS 0xFF76 +#define VC_SUN_FRONT 0xFF77 +#define VC_SUN_OPEN 0xFF74 +#define VC_SUN_FIND 0xFF7E +#define VC_SUN_AGAIN 0xFF79 +#define VC_SUN_UNDO 0xFF7A +#define VC_SUN_COPY 0xFF7C +#define VC_SUN_INSERT 0xFF7D +#define VC_SUN_CUT 0xFF7B +// End Sun keyboards + +#define VC_UNDEFINED 0x0000 // KeyCode Unknown + +#define CHAR_UNDEFINED 0xFFFF // CharCode Unknown +/* End Virtual Key Codes */ + + +/* Begin Virtual Modifier Masks */ +#define MASK_SHIFT_L 1 << 0 +#define MASK_CTRL_L 1 << 1 +#define MASK_META_L 1 << 2 +#define MASK_ALT_L 1 << 3 + +#define MASK_SHIFT_R 1 << 4 +#define MASK_CTRL_R 1 << 5 +#define MASK_META_R 1 << 6 +#define MASK_ALT_R 1 << 7 + +#define MASK_SHIFT MASK_SHIFT_L | MASK_SHIFT_R +#define MASK_CTRL MASK_CTRL_L | MASK_CTRL_R +#define MASK_META MASK_META_L | MASK_META_R +#define MASK_ALT MASK_ALT_L | MASK_ALT_R + +#define MASK_BUTTON1 1 << 8 +#define MASK_BUTTON2 1 << 9 +#define MASK_BUTTON3 1 << 10 +#define MASK_BUTTON4 1 << 11 +#define MASK_BUTTON5 1 << 12 + +#define MASK_NUM_LOCK 1 << 13 +#define MASK_CAPS_LOCK 1 << 14 +#define MASK_SCROLL_LOCK 1 << 15 +/* End Virtual Modifier Masks */ + + +/* Begin Virtual Mouse Buttons */ +#define MOUSE_NOBUTTON 0 // Any Button +#define MOUSE_BUTTON1 1 // Left Button +#define MOUSE_BUTTON2 2 // Right Button +#define MOUSE_BUTTON3 3 // Middle Button +#define MOUSE_BUTTON4 4 // Extra Mouse Button +#define MOUSE_BUTTON5 5 // Extra Mouse Button + +#define WHEEL_UNIT_SCROLL 1 +#define WHEEL_BLOCK_SCROLL 2 + +#define WHEEL_VERTICAL_DIRECTION 3 +#define WHEEL_HORIZONTAL_DIRECTION 4 +/* End Virtual Mouse Buttons */ + + +#ifdef _WIN32 +#define IOHOOK_API __declspec(dllexport) +#else +#define IOHOOK_API +#endif + +#ifdef __cplusplus +extern "C" { +#endif + + // Set the logger callback functions. + IOHOOK_API void hook_set_logger_proc(logger_t logger_proc); + + // Send a virtual event back to the system. + IOHOOK_API void hook_post_event(iohook_event * const event); + + // Set the event callback function. + IOHOOK_API void hook_set_dispatch_proc(dispatcher_t dispatch_proc); + + // Insert the event hook. + IOHOOK_API int hook_run(); + + // Withdraw the event hook. + IOHOOK_API int hook_stop(); + + // Retrieves an array of screen data for each available monitor. + IOHOOK_API screen_data* hook_create_screen_info(unsigned char *count); + + // Retrieves the keyboard auto repeat rate. + IOHOOK_API long int hook_get_auto_repeat_rate(); + + // Retrieves the keyboard auto repeat delay. + IOHOOK_API long int hook_get_auto_repeat_delay(); + + // Retrieves the mouse acceleration multiplier. + IOHOOK_API long int hook_get_pointer_acceleration_multiplier(); + + // Retrieves the mouse acceleration threshold. + IOHOOK_API long int hook_get_pointer_acceleration_threshold(); + + // Retrieves the mouse sensitivity. + IOHOOK_API long int hook_get_pointer_sensitivity(); + + // Retrieves the double/triple click interval. + IOHOOK_API long int hook_get_multi_click_time(); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/hook/logger.h b/hook/logger.h new file mode 100644 index 0000000..0314e0f --- /dev/null +++ b/hook/logger.h @@ -0,0 +1,15 @@ + +#ifndef _included_logger +#define _included_logger + +#include "iohook.h" +#include + +#ifndef __FUNCTION__ +#define __FUNCTION__ __func__ +#endif + +// logger(level, message) +extern logger_t logger; + +#endif diff --git a/hook/logger_c.h b/hook/logger_c.h new file mode 100644 index 0000000..0c7e648 --- /dev/null +++ b/hook/logger_c.h @@ -0,0 +1,60 @@ +/* Copyright (C) 2006-2017 Alexander Barker. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. +*/ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include +#include +#include + +#include "iohook.h" +#include "logger.h" + +static bool default_logger(unsigned int level, const char *format, ...) { + bool status = false; + + #ifndef USE_QUIET + va_list args; + switch (level) { + #ifdef USE_DEBUG + case LOG_LEVEL_DEBUG: + #endif + case LOG_LEVEL_INFO: + va_start(args, format); + status = vfprintf(stdout, format, args) >= 0; + va_end(args); + break; + + case LOG_LEVEL_WARN: + case LOG_LEVEL_ERROR: + va_start(args, format); + status = vfprintf(stderr, format, args) >= 0; + va_end(args); + break; + } + #endif + + return status; +} + +// Current logger function pointer, this should never be null. +// FIXME This should be static and wrapped with a public facing function. +logger_t logger = &default_logger; + + +IOHOOK_API void hook_set_logger(logger_t logger_proc) { + if (logger_proc == NULL) { + logger = &default_logger; + } + else { + logger = logger_proc; + } +} diff --git a/hook/windows/event_c.h b/hook/windows/event_c.h new file mode 100644 index 0000000..622749b --- /dev/null +++ b/hook/windows/event_c.h @@ -0,0 +1,333 @@ +/* Copyright (C) 2006-2017 Alexander Barker. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. +*/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include "../iohook.h" +#include + +// #include "logger.h" +#include "input.h" + +// Some buggy versions of MinGW and MSys do not include these constants in winuser.h. +#ifndef MAPVK_VK_TO_VSC +#define MAPVK_VK_TO_VSC 0 +#define MAPVK_VSC_TO_VK 1 +#define MAPVK_VK_TO_CHAR 2 +#define MAPVK_VSC_TO_VK_EX 3 +#endif +// Some buggy versions of MinGW and MSys only define this value for Windows +// versions >= 0x0600 (Windows Vista) when it should be 0x0500 (Windows 2000). +#ifndef MAPVK_VK_TO_VSC_EX +#define MAPVK_VK_TO_VSC_EX 4 +#endif + +#ifndef KEYEVENTF_SCANCODE +#define KEYEVENTF_EXTENDEDKEY 0x0001 +#define KEYEVENTF_KEYUP 0x0002 +#define KEYEVENTF_UNICODE 0x0004 +#define KEYEVENTF_SCANCODE 0x0008 +#endif + +#ifndef KEYEVENTF_KEYDOWN +#define KEYEVENTF_KEYDOWN 0x0000 +#endif + +#define MAX_WINDOWS_COORD_VALUE 65535 + +static UINT keymask_lookup[8] = { + VK_LSHIFT, + VK_LCONTROL, + VK_LWIN, + VK_LMENU, + + VK_RSHIFT, + VK_RCONTROL, + VK_RWIN, + VK_RMENU +}; + +IOHOOK_API void hook_post_event(iohook_event * const event) { + //FIXME implement multiple monitor support + uint16_t screen_width = GetSystemMetrics( SM_CXSCREEN ); + uint16_t screen_height = GetSystemMetrics( SM_CYSCREEN ); + + unsigned char events_size = 0, events_max = 28; + INPUT *events = malloc(sizeof(INPUT) * events_max); + + if (event->mask & (MASK_SHIFT | MASK_CTRL | MASK_META | MASK_ALT)) { + unsigned int i; + for (i = 0; i < sizeof(keymask_lookup) / sizeof(UINT); i++) { + if (event->mask & 1 << i) { + events[events_size].type = INPUT_KEYBOARD; + events[events_size].ki.wVk = keymask_lookup[i]; + events[events_size].ki.dwFlags = KEYEVENTF_KEYDOWN; + events[events_size].ki.time = 0; // Use current system time. + events_size++; + } + } + } + + if (event->mask & (MASK_BUTTON1 | MASK_BUTTON2 | MASK_BUTTON3 | MASK_BUTTON4 | MASK_BUTTON5)) { + events[events_size].type = INPUT_MOUSE; + events[events_size].mi.dx = 0; // Relative mouse movement due to + events[events_size].mi.dy = 0; // MOUSEEVENTF_ABSOLUTE not being set. + events[events_size].mi.mouseData = 0x00; + events[events_size].mi.time = 0; // Use current system time. + + if (event->mask & MASK_BUTTON1) { + events[events_size].mi.mouseData |= MOUSEEVENTF_LEFTDOWN; + } + + if (event->mask & MASK_BUTTON2) { + events[events_size].mi.mouseData |= MOUSEEVENTF_RIGHTDOWN; + } + + if (event->mask & MASK_BUTTON3) { + events[events_size].mi.mouseData |= MOUSEEVENTF_MIDDLEDOWN; + } + + if (event->mask & MASK_BUTTON4) { + events[events_size].mi.mouseData = XBUTTON1; + events[events_size].mi.mouseData |= MOUSEEVENTF_XDOWN; + } + + if (event->mask & MASK_BUTTON5) { + events[events_size].mi.mouseData = XBUTTON2; + events[events_size].mi.dwFlags |= MOUSEEVENTF_XDOWN; + } + + events_size++; + } + + + switch (event->type) { + case EVENT_KEY_PRESSED: + events[events_size].ki.wVk = scancode_to_keycode(event->data.keyboard.keycode); + if (events[events_size].ki.wVk != 0x0000) { + events[events_size].type = INPUT_KEYBOARD; + events[events_size].ki.dwFlags = KEYEVENTF_KEYDOWN; // |= KEYEVENTF_SCANCODE; + events[events_size].ki.wScan = 0; // event->data.keyboard.keycode; + events[events_size].ki.time = 0; // GetSystemTime() + events_size++; + } else { + logger(LOG_LEVEL_INFO, "%s [%u]: Unable to lookup scancode: %li\n", + __FUNCTION__, __LINE__, + event->data.keyboard.keycode); + } + break; + + case EVENT_KEY_RELEASED: + events[events_size].ki.wVk = scancode_to_keycode(event->data.keyboard.keycode); + if (events[events_size].ki.wVk != 0x0000) { + events[events_size].type = INPUT_KEYBOARD; + events[events_size].ki.dwFlags = KEYEVENTF_KEYUP; // |= KEYEVENTF_SCANCODE; + events[events_size].ki.wVk = scancode_to_keycode(event->data.keyboard.keycode); + events[events_size].ki.wScan = 0; // event->data.keyboard.keycode; + events[events_size].ki.time = 0; // GetSystemTime() + events_size++; + } else { + logger(LOG_LEVEL_INFO, "%s [%u]: Unable to lookup scancode: %li\n", + __FUNCTION__, __LINE__, + event->data.keyboard.keycode); + } + break; + + + case EVENT_MOUSE_PRESSED: + events[events_size].type = INPUT_MOUSE; + events[events_size].mi.dwFlags = MOUSEEVENTF_XDOWN; + + switch (event->data.mouse.button) { + case MOUSE_BUTTON1: + events[events_size].mi.dwFlags = MOUSEEVENTF_LEFTDOWN; + break; + + case MOUSE_BUTTON2: + events[events_size].mi.dwFlags = MOUSEEVENTF_RIGHTDOWN; + break; + + case MOUSE_BUTTON3: + events[events_size].mi.dwFlags = MOUSEEVENTF_MIDDLEDOWN; + break; + + case MOUSE_BUTTON4: + events[events_size].mi.mouseData = XBUTTON1; + break; + + case MOUSE_BUTTON5: + events[events_size].mi.mouseData = XBUTTON2; + break; + + default: + // Extra buttons. + if (event->data.mouse.button > 3) { + events[events_size].mi.mouseData = event->data.mouse.button - 3; + } + } + + events[events_size].mi.dx = event->data.mouse.x * (MAX_WINDOWS_COORD_VALUE / screen_width) + 1; + events[events_size].mi.dy = event->data.mouse.y * (MAX_WINDOWS_COORD_VALUE / screen_height) + 1; + + events[events_size].mi.dwFlags |= MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE; + events[events_size].mi.time = 0; // GetSystemTime() + + events_size++; + break; + + case EVENT_MOUSE_RELEASED: + events[events_size].type = INPUT_MOUSE; + events[events_size].mi.dwFlags = MOUSEEVENTF_XUP; + + switch (event->data.mouse.button) { + case MOUSE_BUTTON1: + events[events_size].mi.dwFlags = MOUSEEVENTF_LEFTUP; + break; + + case MOUSE_BUTTON2: + events[events_size].mi.dwFlags = MOUSEEVENTF_RIGHTUP; + break; + + case MOUSE_BUTTON3: + events[events_size].mi.dwFlags = MOUSEEVENTF_MIDDLEUP; + break; + + case MOUSE_BUTTON4: + events[events_size].mi.mouseData = XBUTTON1; + break; + + case MOUSE_BUTTON5: + events[events_size].mi.mouseData = XBUTTON2; + break; + + default: + // Extra buttons. + if (event->data.mouse.button > 3) { + events[events_size].mi.mouseData = event->data.mouse.button - 3; + } + } + + events[events_size].mi.dx = event->data.mouse.x * (MAX_WINDOWS_COORD_VALUE / screen_width) + 1; + events[events_size].mi.dy = event->data.mouse.y * (MAX_WINDOWS_COORD_VALUE / screen_height) + 1; + + events[events_size].mi.dwFlags |= MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE; + events[events_size].mi.time = 0; // GetSystemTime() + events_size++; + break; + + + case EVENT_MOUSE_WHEEL: + events[events_size].type = INPUT_MOUSE; + events[events_size].mi.dwFlags = MOUSEEVENTF_WHEEL; + + // type, amount and rotation? + events[events_size].mi.mouseData = event->data.wheel.amount * event->data.wheel.rotation * WHEEL_DELTA; + + events[events_size].mi.dx = event->data.wheel.x * (MAX_WINDOWS_COORD_VALUE / screen_width) + 1; + events[events_size].mi.dy = event->data.wheel.y * (MAX_WINDOWS_COORD_VALUE / screen_height) + 1; + + events[events_size].mi.dwFlags |= MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE; + events[events_size].mi.time = 0; // GetSystemTime() + events_size++; + break; + + + case EVENT_MOUSE_DRAGGED: + // The button masks are all applied with the modifier masks. + + case EVENT_MOUSE_MOVED: + events[events_size].type = INPUT_MOUSE; + events[events_size].mi.dwFlags = MOUSEEVENTF_MOVE; + + events[events_size].mi.dx = event->data.mouse.x * (MAX_WINDOWS_COORD_VALUE / screen_width) + 1; + events[events_size].mi.dy = event->data.mouse.y * (MAX_WINDOWS_COORD_VALUE / screen_height) + 1; + + events[events_size].mi.dwFlags |= MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE; + events[events_size].mi.time = 0; // GetSystemTime() + events_size++; + break; + + + case EVENT_MOUSE_CLICKED: + case EVENT_KEY_TYPED: + // Ignore clicked and typed events. + + case EVENT_HOOK_ENABLED: + case EVENT_HOOK_DISABLED: + // Ignore hook enabled / disabled events. + + default: + // Ignore any other garbage. + logger(LOG_LEVEL_WARN, "%s [%u]: Ignoring post event type %#X\n", + __FUNCTION__, __LINE__, event->type); + break; + } + + // Release the previously held modifier keys used to fake the event mask. + if (event->mask & (MASK_SHIFT | MASK_CTRL | MASK_META | MASK_ALT)) { + unsigned int i; + for (i = 0; i < sizeof(keymask_lookup) / sizeof(UINT); i++) { + if (event->mask & 1 << i) { + events[events_size].type = INPUT_KEYBOARD; + events[events_size].ki.wVk = keymask_lookup[i]; + events[events_size].ki.dwFlags = KEYEVENTF_KEYUP; + events[events_size].ki.time = 0; // Use current system time. + events_size++; + } + } + } + + if (event->mask & (MASK_BUTTON1 | MASK_BUTTON2 | MASK_BUTTON3 | MASK_BUTTON4 | MASK_BUTTON5)) { + events[events_size].type = INPUT_MOUSE; + events[events_size].mi.dx = 0; // Relative mouse movement due to + events[events_size].mi.dy = 0; // MOUSEEVENTF_ABSOLUTE not being set. + events[events_size].mi.mouseData = 0x00; + events[events_size].mi.time = 0; // Use current system time. + + // If dwFlags does not contain MOUSEEVENTF_WHEEL, MOUSEEVENTF_XDOWN, or MOUSEEVENTF_XUP, + // then mouseData should be zero. + // http://msdn.microsoft.com/en-us/library/windows/desktop/ms646273%28v=vs.85%29.aspx + if (event->mask & MASK_BUTTON1) { + events[events_size].mi.dwFlags |= MOUSEEVENTF_LEFTUP; + } + + if (event->mask & MASK_BUTTON2) { + events[events_size].mi.dwFlags |= MOUSEEVENTF_RIGHTUP; + } + + if (event->mask & MASK_BUTTON3) { + events[events_size].mi.dwFlags |= MOUSEEVENTF_MIDDLEUP; + } + + if (event->mask & MASK_BUTTON4) { + events[events_size].mi.mouseData = XBUTTON1; + events[events_size].mi.dwFlags |= MOUSEEVENTF_XUP; + } + + if (event->mask & MASK_BUTTON5) { + events[events_size].mi.mouseData = XBUTTON2; + events[events_size].mi.dwFlags |= MOUSEEVENTF_XUP; + } + + events_size++; + } + + // Create the key release input + // memcpy(key_events + 1, key_events, sizeof(INPUT)); + // key_events[1].ki.dwFlags |= KEYEVENTF_KEYUP; + + if (! SendInput(events_size, events, sizeof(INPUT)) ) { + logger(LOG_LEVEL_ERROR, "%s [%u]: SendInput() failed! (%#lX)\n", + __FUNCTION__, __LINE__, (unsigned long) GetLastError()); + } + + free(events); +} diff --git a/hook/windows/hook_c.h b/hook/windows/hook_c.h new file mode 100644 index 0000000..26cd0b2 --- /dev/null +++ b/hook/windows/hook_c.h @@ -0,0 +1,739 @@ +/* Copyright (C) 2006-2017 Alexander Barker. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. +*/ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include + +#include "../iohook.h" +#include "input.h" +// #include "logger.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; + +// The handle to the DLL module pulled in DllMain on DLL_PROCESS_ATTACH. +extern HINSTANCE hInst; + +// Modifiers for tracking key masks. +static unsigned short int current_modifiers = 0x0000; + +// Click count globals. +static unsigned short click_count = 0; +static DWORD click_time = 0; +static unsigned short int click_button = MOUSE_NOBUTTON; +static POINT last_click; + +// Static event memory. +static iohook_event event; + +// Event dispatch callback. +static dispatcher_t dispatcher = NULL; + +IOHOOK_API void hook_set_dispatch_proc(dispatcher_t dispatch_proc) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: Setting new dispatch callback to %#p.\n", + __FUNCTION__, __LINE__, dispatch_proc); + + dispatcher = dispatch_proc; +} + +// Send out an event if a dispatcher was set. +static inline void dispatch_event(iohook_event *const event) { + if (dispatcher != NULL) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: Dispatching event type %u.\n", + __FUNCTION__, __LINE__, event->type); + + dispatcher(event); + } else { + logger(LOG_LEVEL_WARN, "%s [%u]: No dispatch callback set!\n", + __FUNCTION__, __LINE__); + } +} + + +// Set the native modifier mask for future events. +static inline void set_modifier_mask(unsigned short int mask) { + current_modifiers |= mask; +} + +// Unset the native modifier mask for future events. +static inline void unset_modifier_mask(unsigned short int mask) { + current_modifiers ^= mask; +} + +// Get the current native modifier mask state. +static inline unsigned short int get_modifiers() { + return current_modifiers; +} + +// Initialize the modifier mask to the current modifiers. +static void initialize_modifiers() { + current_modifiers = 0x0000; + + // NOTE We are checking the high order bit, so it will be < 0 for a singed short. + if (GetKeyState(VK_LSHIFT) < 0) { set_modifier_mask(MASK_SHIFT_L); } + if (GetKeyState(VK_RSHIFT) < 0) { set_modifier_mask(MASK_SHIFT_R); } + if (GetKeyState(VK_LCONTROL) < 0) { set_modifier_mask(MASK_CTRL_L); } + if (GetKeyState(VK_RCONTROL) < 0) { set_modifier_mask(MASK_CTRL_R); } + if (GetKeyState(VK_LMENU) < 0) { set_modifier_mask(MASK_ALT_L); } + if (GetKeyState(VK_RMENU) < 0) { set_modifier_mask(MASK_ALT_R); } + if (GetKeyState(VK_LWIN) < 0) { set_modifier_mask(MASK_META_L); } + if (GetKeyState(VK_RWIN) < 0) { set_modifier_mask(MASK_META_R); } + + if (GetKeyState(VK_LBUTTON) < 0) { set_modifier_mask(MASK_BUTTON1); } + if (GetKeyState(VK_RBUTTON) < 0) { set_modifier_mask(MASK_BUTTON2); } + if (GetKeyState(VK_MBUTTON) < 0) { set_modifier_mask(MASK_BUTTON3); } + if (GetKeyState(VK_XBUTTON1) < 0) { set_modifier_mask(MASK_BUTTON4); } + if (GetKeyState(VK_XBUTTON2) < 0) { set_modifier_mask(MASK_BUTTON5); } + + if (GetKeyState(VK_NUMLOCK) < 0) { set_modifier_mask(MASK_NUM_LOCK); } + if (GetKeyState(VK_CAPITAL) < 0) { set_modifier_mask(MASK_CAPS_LOCK); } + if (GetKeyState(VK_SCROLL) < 0) { set_modifier_mask(MASK_SCROLL_LOCK); } +} + + +/* Retrieves the mouse wheel scroll type. This function cannot be included as + * part of the input.h due to platform specific calling restrictions. + */ +static unsigned short int get_scroll_wheel_type() { + unsigned short int value; + UINT wheel_type; + + SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &wheel_type, 0); + if (wheel_type == WHEEL_PAGESCROLL) { + value = WHEEL_BLOCK_SCROLL; + } else { + value = WHEEL_UNIT_SCROLL; + } + + return value; +} + +/* Retrieves the mouse wheel scroll amount. This function cannot be included as + * part of the input.h due to platform specific calling restrictions. + */ +static unsigned short int get_scroll_wheel_amount() { + unsigned short int value; + UINT wheel_amount; + + SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &wheel_amount, 0); + if (wheel_amount == WHEEL_PAGESCROLL) { + value = 1; + } else { + value = (unsigned short int) wheel_amount; + } + + return value; +} + +void unregister_running_hooks() { + // Stop the event hook and any timer still running. + if (win_event_hhook != NULL) { + UnhookWinEvent(win_event_hhook); + win_event_hhook = NULL; + } + + // Destroy the native hooks. + if (keyboard_event_hhook != NULL) { + UnhookWindowsHookEx(keyboard_event_hhook); + keyboard_event_hhook = NULL; + } + + if (mouse_event_hhook != NULL) { + UnhookWindowsHookEx(mouse_event_hhook); + mouse_event_hhook = NULL; + } +} + +void hook_start_proc() { + // Get the local system time in UNIX epoch form. + uint64_t timestamp = GetMessageTime(); + + // Populate the hook start event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_HOOK_ENABLED; + event.mask = 0x00; + + // Fire the hook start event. + dispatch_event(&event); +} + +void hook_stop_proc() { + // Get the local system time in UNIX epoch form. + uint64_t timestamp = GetMessageTime(); + + // Populate the hook stop event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_HOOK_DISABLED; + event.mask = 0x00; + + // Fire the hook stop event. + dispatch_event(&event); +} + +static void process_key_pressed(KBDLLHOOKSTRUCT *kbhook) { + // 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); } + else if (kbhook->vkCode == VK_LCONTROL) { set_modifier_mask(MASK_CTRL_L); } + else if (kbhook->vkCode == VK_RCONTROL) { set_modifier_mask(MASK_CTRL_R); } + else if (kbhook->vkCode == VK_LMENU) { set_modifier_mask(MASK_ALT_L); } + else if (kbhook->vkCode == VK_RMENU) { set_modifier_mask(MASK_ALT_R); } + else if (kbhook->vkCode == VK_LWIN) { set_modifier_mask(MASK_META_L); } + else if (kbhook->vkCode == VK_RWIN) { set_modifier_mask(MASK_META_R); } + else if (kbhook->vkCode == VK_NUMLOCK) { set_modifier_mask(MASK_NUM_LOCK); } + else if (kbhook->vkCode == VK_CAPITAL) { set_modifier_mask(MASK_CAPS_LOCK); } + else if (kbhook->vkCode == VK_SCROLL) { set_modifier_mask(MASK_SCROLL_LOCK); } + + // Populate key pressed event. + event.time = kbhook->time; + event.reserved = 0x00; + + event.type = EVENT_KEY_PRESSED; + event.mask = get_modifiers(); + + event.data.keyboard.keycode = keycode_to_scancode(kbhook->vkCode, kbhook->flags); + event.data.keyboard.rawcode = kbhook->vkCode; + event.data.keyboard.keychar = CHAR_UNDEFINED; + + logger(LOG_LEVEL_INFO, "%s [%u]: Key %#X pressed. (%#X)\n", + __FUNCTION__, __LINE__, event.data.keyboard.keycode, event.data.keyboard.rawcode); + + // Populate key pressed event. + dispatch_event(&event); + + // If the pressed event was not consumed... + if (event.reserved ^ 0x01) { + // Buffer for unicode typed chars. No more than 2 needed. + WCHAR buffer[2]; // = { WCH_NONE }; + + // If the pressed event was not consumed and a unicode char exists... + SIZE_T count = keycode_to_unicode(kbhook->vkCode, buffer, sizeof(buffer)); + unsigned int i; + for (i = 0; i < count; i++) { + // Populate key typed event. + event.time = kbhook->time; + event.reserved = 0x00; + + event.type = EVENT_KEY_TYPED; + event.mask = get_modifiers(); + + event.data.keyboard.keycode = VC_UNDEFINED; + event.data.keyboard.rawcode = kbhook->vkCode; + event.data.keyboard.keychar = buffer[i]; + + logger(LOG_LEVEL_INFO, "%s [%u]: Key %#X typed. (%lc)\n", + __FUNCTION__, __LINE__, event.data.keyboard.keycode, (wint_t) event.data.keyboard.keychar); + + // Fire key typed event. + dispatch_event(&event); + } + } +} + +static void process_key_released(KBDLLHOOKSTRUCT *kbhook) { + // 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); } + else if (kbhook->vkCode == VK_LCONTROL) { unset_modifier_mask(MASK_CTRL_L); } + else if (kbhook->vkCode == VK_RCONTROL) { unset_modifier_mask(MASK_CTRL_R); } + else if (kbhook->vkCode == VK_LMENU) { unset_modifier_mask(MASK_ALT_L); } + else if (kbhook->vkCode == VK_RMENU) { unset_modifier_mask(MASK_ALT_R); } + else if (kbhook->vkCode == VK_LWIN) { unset_modifier_mask(MASK_META_L); } + else if (kbhook->vkCode == VK_RWIN) { unset_modifier_mask(MASK_META_R); } + else if (kbhook->vkCode == VK_NUMLOCK) { unset_modifier_mask(MASK_NUM_LOCK); } + else if (kbhook->vkCode == VK_CAPITAL) { unset_modifier_mask(MASK_CAPS_LOCK); } + else if (kbhook->vkCode == VK_SCROLL) { unset_modifier_mask(MASK_SCROLL_LOCK); } + + // Populate key pressed event. + event.time = kbhook->time; + event.reserved = 0x00; + + event.type = EVENT_KEY_RELEASED; + event.mask = get_modifiers(); + + event.data.keyboard.keycode = keycode_to_scancode(kbhook->vkCode, kbhook->flags); + event.data.keyboard.rawcode = kbhook->vkCode; + event.data.keyboard.keychar = CHAR_UNDEFINED; + + logger(LOG_LEVEL_INFO, "%s [%u]: Key %#X released. (%#X)\n", + __FUNCTION__, __LINE__, event.data.keyboard.keycode, event.data.keyboard.rawcode); + + // Fire key released event. + dispatch_event(&event); +} + +LRESULT CALLBACK keyboard_hook_event_proc(int nCode, WPARAM wParam, LPARAM lParam) { + KBDLLHOOKSTRUCT *kbhook = (KBDLLHOOKSTRUCT *) lParam; + switch (wParam) { + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + process_key_pressed(kbhook); + break; + + case WM_KEYUP: + case WM_SYSKEYUP: + process_key_released(kbhook); + break; + + default: + // In theory this *should* never execute. + logger(LOG_LEVEL_DEBUG, "%s [%u]: Unhandled Windows keyboard event: %#X.\n", + __FUNCTION__, __LINE__, (unsigned int) wParam); + break; + } + + LRESULT hook_result = -1; + if (nCode < 0 || event.reserved ^ 0x01) { + hook_result = CallNextHookEx(keyboard_event_hhook, nCode, wParam, lParam); + } else { + logger(LOG_LEVEL_DEBUG, "%s [%u]: Consuming the current event. (%li)\n", + __FUNCTION__, __LINE__, (long) hook_result); + } + + return hook_result; +} + + +static void process_button_pressed(MSLLHOOKSTRUCT *mshook, uint16_t button) { + // uint64_t timestamp = GetMessageTime(); + uint64_t timestamp = mshook->time; + + // 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()) { + if (click_count < USHRT_MAX) { + click_count++; + } else { + logger(LOG_LEVEL_WARN, "%s [%u]: Click count overflow detected!\n", + __FUNCTION__, __LINE__); + } + } else { + // Reset the click count. + click_count = 1; + + // Set the previous button. + click_button = button; + } + + // Save this events time to calculate the click_count. + click_time = timestamp; + + // Store the last click point. + last_click.x = mshook->pt.x; + last_click.y = mshook->pt.y; + + // Populate mouse pressed event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_MOUSE_PRESSED; + event.mask = get_modifiers(); + + event.data.mouse.button = button; + event.data.mouse.clicks = click_count; + + event.data.mouse.x = mshook->pt.x; + event.data.mouse.y = mshook->pt.y; + + logger(LOG_LEVEL_INFO, "%s [%u]: Button %u pressed %u time(s). (%u, %u)\n", + __FUNCTION__, __LINE__, event.data.mouse.button, event.data.mouse.clicks, + event.data.mouse.x, event.data.mouse.y); + + // Fire mouse pressed event. + dispatch_event(&event); +} + +static void process_button_released(MSLLHOOKSTRUCT *mshook, uint16_t button) { + // Populate mouse released event. + event.time = mshook->time; + event.reserved = 0x00; + + event.type = EVENT_MOUSE_RELEASED; + event.mask = get_modifiers(); + + event.data.mouse.button = button; + event.data.mouse.clicks = click_count; + + event.data.mouse.x = mshook->pt.x; + event.data.mouse.y = mshook->pt.y; + + logger(LOG_LEVEL_INFO, "%s [%u]: Button %u released %u time(s). (%u, %u)\n", + __FUNCTION__, __LINE__, event.data.mouse.button, + event.data.mouse.clicks, + event.data.mouse.x, event.data.mouse.y); + + // Fire mouse released event. + dispatch_event(&event); + + // 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.reserved = 0x00; + + event.type = EVENT_MOUSE_CLICKED; + event.mask = get_modifiers(); + + event.data.mouse.button = button; + event.data.mouse.clicks = click_count; + event.data.mouse.x = mshook->pt.x; + event.data.mouse.y = mshook->pt.y; + + logger(LOG_LEVEL_INFO, "%s [%u]: Button %u clicked %u time(s). (%u, %u)\n", + __FUNCTION__, __LINE__, event.data.mouse.button, event.data.mouse.clicks, + event.data.mouse.x, event.data.mouse.y); + + // Fire mouse clicked event. + dispatch_event(&event); + } + + // Reset the number of clicks. + if (button == click_button && (long int) (event.time - click_time) > hook_get_multi_click_time()) { + // Reset the click count. + click_count = 0; + } +} + +static void process_mouse_moved(MSLLHOOKSTRUCT *mshook) { + uint64_t timestamp = mshook->time; + + // We received a mouse move event with the mouse actually moving. + // This verifies that the mouse was moved after being depressed. + if (last_click.x != mshook->pt.x || last_click.y != mshook->pt.y) { + // Reset the click count. + if (click_count != 0 && (long) (timestamp - click_time) > hook_get_multi_click_time()) { + click_count = 0; + } + + // Populate mouse move event. + event.time = timestamp; + event.reserved = 0x00; + + event.mask = get_modifiers(); + + // Check the modifier mask range for MASK_BUTTON1 - 5. + bool mouse_dragged = event.mask & (MASK_BUTTON1 | MASK_BUTTON2 | MASK_BUTTON3 | MASK_BUTTON4 | MASK_BUTTON5); + if (mouse_dragged) { + // Create Mouse Dragged event. + event.type = EVENT_MOUSE_DRAGGED; + } else { + // Create a Mouse Moved event. + event.type = EVENT_MOUSE_MOVED; + } + + event.data.mouse.button = MOUSE_NOBUTTON; + event.data.mouse.clicks = click_count; + event.data.mouse.x = mshook->pt.x; + event.data.mouse.y = mshook->pt.y; + + logger(LOG_LEVEL_INFO, "%s [%u]: Mouse %s to %u, %u.\n", + __FUNCTION__, __LINE__, mouse_dragged ? "dragged" : "moved", + event.data.mouse.x, event.data.mouse.y); + + // Fire mouse move event. + dispatch_event(&event); + } +} + +static void process_mouse_wheel(MSLLHOOKSTRUCT *mshook, uint8_t direction) { + // 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.reserved = 0x00; + + event.type = EVENT_MOUSE_WHEEL; + event.mask = get_modifiers(); + + event.data.wheel.clicks = click_count; + event.data.wheel.x = mshook->pt.x; + event.data.wheel.y = mshook->pt.y; + + event.data.wheel.type = get_scroll_wheel_type(); + event.data.wheel.amount = get_scroll_wheel_amount(); + + /* Delta HIWORD(mshook->mouseData) + * A positive value indicates that the wheel was rotated + * forward, away from the user; a negative value indicates that + * the wheel was rotated backward, toward the user. One wheel + * click is defined as WHEEL_DELTA, which is 120. */ + event.data.wheel.rotation = ((int16_t) HIWORD(mshook->mouseData) / WHEEL_DELTA) * -1; + + // Set the direction based on what event was received. + event.data.wheel.direction = direction; + + logger(LOG_LEVEL_INFO, "%s [%u]: Mouse wheel type %u, rotated %i units in the %u direction at %u, %u.\n", + __FUNCTION__, __LINE__, event.data.wheel.type, + event.data.wheel.amount * event.data.wheel.rotation, + event.data.wheel.direction, + event.data.wheel.x, event.data.wheel.y); + + // Fire mouse wheel event. + dispatch_event(&event); +} + +LRESULT CALLBACK mouse_hook_event_proc(int nCode, WPARAM wParam, LPARAM lParam) { + MSLLHOOKSTRUCT *mshook = (MSLLHOOKSTRUCT *) lParam; + switch (wParam) { + case WM_LBUTTONDOWN: + set_modifier_mask(MASK_BUTTON1); + process_button_pressed(mshook, MOUSE_BUTTON1); + break; + + case WM_RBUTTONDOWN: + set_modifier_mask(MASK_BUTTON2); + process_button_pressed(mshook, MOUSE_BUTTON2); + break; + + case WM_MBUTTONDOWN: + set_modifier_mask(MASK_BUTTON3); + process_button_pressed(mshook, MOUSE_BUTTON3); + break; + + case WM_XBUTTONDOWN: + case WM_NCXBUTTONDOWN: + if (HIWORD(mshook->mouseData) == XBUTTON1) { + set_modifier_mask(MASK_BUTTON4); + process_button_pressed(mshook, MOUSE_BUTTON4); + } else if (HIWORD(mshook->mouseData) == XBUTTON2) { + set_modifier_mask(MASK_BUTTON5); + process_button_pressed(mshook, MOUSE_BUTTON5); + } else { + // Extra mouse buttons. + uint16_t button = HIWORD(mshook->mouseData); + + // Add support for mouse 4 & 5. + if (button == 4) { + set_modifier_mask(MOUSE_BUTTON4); + } else if (button == 5) { + set_modifier_mask(MOUSE_BUTTON5); + } + + process_button_pressed(mshook, button); + } + break; + + + case WM_LBUTTONUP: + unset_modifier_mask(MASK_BUTTON1); + process_button_released(mshook, MOUSE_BUTTON1); + break; + + case WM_RBUTTONUP: + unset_modifier_mask(MASK_BUTTON2); + process_button_released(mshook, MOUSE_BUTTON2); + break; + + case WM_MBUTTONUP: + unset_modifier_mask(MASK_BUTTON3); + process_button_released(mshook, MOUSE_BUTTON3); + break; + + case WM_XBUTTONUP: + case WM_NCXBUTTONUP: + if (HIWORD(mshook->mouseData) == XBUTTON1) { + unset_modifier_mask(MASK_BUTTON4); + process_button_released(mshook, MOUSE_BUTTON4); + } else if (HIWORD(mshook->mouseData) == XBUTTON2) { + unset_modifier_mask(MASK_BUTTON5); + process_button_released(mshook, MOUSE_BUTTON5); + } else { + // Extra mouse buttons. + uint16_t button = HIWORD(mshook->mouseData); + + // Add support for mouse 4 & 5. + if (button == 4) { + unset_modifier_mask(MOUSE_BUTTON4); + } else if (button == 5) { + unset_modifier_mask(MOUSE_BUTTON5); + } + + process_button_released(mshook, MOUSE_BUTTON5); + } + break; + + case WM_MOUSEMOVE: + process_mouse_moved(mshook); + break; + + case WM_MOUSEWHEEL: + process_mouse_wheel(mshook, WHEEL_VERTICAL_DIRECTION); + break; + + /* For horizontal scroll wheel support. + * NOTE Windows >= Vista + * case 0x020E: + */ + case WM_MOUSEHWHEEL: + process_mouse_wheel(mshook, WHEEL_HORIZONTAL_DIRECTION); + break; + + default: + // In theory this *should* never execute. + logger(LOG_LEVEL_DEBUG, "%s [%u]: Unhandled Windows mouse event: %#X.\n", + __FUNCTION__, __LINE__, (unsigned int) wParam); + break; + } + + LRESULT hook_result = -1; + if (nCode < 0 || event.reserved ^ 0x01) { + hook_result = CallNextHookEx(mouse_event_hhook, nCode, wParam, lParam); + } else { + logger(LOG_LEVEL_DEBUG, "%s [%u]: Consuming the current event. (%li)\n", + __FUNCTION__, __LINE__, (long) hook_result); + } + + return hook_result; +} + + +// Callback function that handles events. +void CALLBACK win_hook_event_proc(HWINEVENTHOOK hook, DWORD event, HWND hWnd, LONG idObject, LONG idChild, DWORD dwEventThread, DWORD dwmsEventTime) { + switch (event) { + case EVENT_OBJECT_NAMECHANGE: + logger(LOG_LEVEL_INFO, "%s [%u]: Restarting Windows input hook on window event: %#X.\n", + __FUNCTION__, __LINE__, event); + + // Remove any keyboard or mouse hooks that are still running. + if (keyboard_event_hhook != NULL) { + UnhookWindowsHookEx(keyboard_event_hhook); + } + + if (mouse_event_hhook != NULL) { + UnhookWindowsHookEx(mouse_event_hhook); + } + + // Restart the event 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); + + // Re-initialize modifier masks. + initialize_modifiers(); + + // FIXME We should compare the modifier mask before and after the restart + // to determine if we should synthesize missing events. + + // Check for event hook error. + if (keyboard_event_hhook == NULL || mouse_event_hhook == NULL) { + logger(LOG_LEVEL_ERROR, "%s [%u]: SetWindowsHookEx() failed! (%#lX)\n", + __FUNCTION__, __LINE__, (unsigned long) GetLastError()); + } + break; + + default: + logger(LOG_LEVEL_INFO, "%s [%u]: Unhandled Windows window event: %#X.\n", + __FUNCTION__, __LINE__, event); + } +} + + +IOHOOK_API int hook_run() { + int status = IOHOOK_FAILURE; + + // Set the thread id we want to signal later. + hook_thread_id = GetCurrentThreadId(); + + // Spot check the hInst incase the library was statically linked and DllMain + // did not receive a pointer on load. + if (hInst == NULL) { + logger(LOG_LEVEL_INFO, "%s [%u]: hInst was not set by DllMain().\n", + __FUNCTION__, __LINE__); + + hInst = GetModuleHandle(NULL); + if (hInst != NULL) { + // Initialize native input helper functions. + load_input_helper(); + } else { + logger(LOG_LEVEL_ERROR, "%s [%u]: Could not determine hInst for SetWindowsHookEx()! (%#lX)\n", + __FUNCTION__, __LINE__, (unsigned long) GetLastError()); + + status = IOHOOK_ERROR_GET_MODULE_HANDLE; + } + } + + // 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); + + // Create a window event hook to listen for capture change. + win_event_hhook = SetWinEventHook( + EVENT_OBJECT_NAMECHANGE, EVENT_OBJECT_NAMECHANGE, + NULL, + win_hook_event_proc, + 0, 0, + WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS); + + // If we did not encounter a problem, start processing events. + if (keyboard_event_hhook != NULL && mouse_event_hhook != NULL) { + if (win_event_hhook == NULL) { + logger(LOG_LEVEL_WARN, "%s [%u]: SetWinEventHook() failed!\n", + __FUNCTION__, __LINE__); + } + + logger(LOG_LEVEL_DEBUG, "%s [%u]: SetWindowsHookEx() successful.\n", + __FUNCTION__, __LINE__); + + // Check and setup modifiers. + initialize_modifiers(); + + // Set the exit status. + status = IOHOOK_SUCCESS; + + // Windows does not have a hook start event or callback so we need to + // manually fake it. + hook_start_proc(); + + // Block until the thread receives an WM_QUIT request. + MSG message; + while (GetMessage(&message, (HWND) NULL, 0, 0) > 0) { + TranslateMessage(&message); + DispatchMessage(&message); + } + } else { + logger(LOG_LEVEL_ERROR, "%s [%u]: SetWindowsHookEx() failed! (%#lX)\n", + __FUNCTION__, __LINE__, (unsigned long) GetLastError()); + + status = IOHOOK_ERROR_SET_WINDOWS_HOOK_EX; + } + + + // Unregister any hooks that may still be installed. + unregister_running_hooks(); + + // We must explicitly call the cleanup handler because Windows does not + // provide a thread cleanup method like POSIX pthread_cleanup_push/pop. + hook_stop_proc(); + + return status; +} + +IOHOOK_API int hook_stop() { + int status = IOHOOK_FAILURE; + + // Try to exit the thread naturally. + if (PostThreadMessage(hook_thread_id, WM_QUIT, (WPARAM) NULL, (LPARAM) NULL)) { + status = IOHOOK_SUCCESS; + } + + logger(LOG_LEVEL_DEBUG, "%s [%u]: Status: %#X.\n", + __FUNCTION__, __LINE__, status); + + return status; +} diff --git a/hook/windows/input.h b/hook/windows/input.h new file mode 100644 index 0000000..8420d9a --- /dev/null +++ b/hook/windows/input.h @@ -0,0 +1,112 @@ +/* Copyright (C) 2006-2017 Alexander Barker. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. +*/ + +/*********************************************************************** + Input + ***********************************************************************/ + +#ifndef _included_input_helper +#define _included_input_helper + +#include +#include + +#ifndef LPFN_ISWOW64PROCESS +typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL); +#endif + +typedef void* (CALLBACK *KbdLayerDescriptor) (VOID); + +#define CAPLOK 0x01 +#define WCH_NONE 0xF000 +#define WCH_DEAD 0xF001 + +#ifndef WM_MOUSEHWHEEL +#define WM_MOUSEHWHEEL 0x020E +#endif + +typedef struct _VK_TO_WCHARS { + BYTE VirtualKey; + BYTE Attributes; + WCHAR wch[]; +} VK_TO_WCHARS, *PVK_TO_WCHARS; + +typedef struct _LIGATURE { + BYTE VirtualKey; + WORD ModificationNumber; + WCHAR wch[]; +} LIGATURE, *PLIGATURE; + +typedef struct _VK_TO_BIT { + BYTE Vk; + BYTE ModBits; +} VK_TO_BIT, *PVK_TO_BIT; + +typedef struct _MODIFIERS { + PVK_TO_BIT pVkToBit; // __ptr64 + WORD wMaxModBits; + BYTE ModNumber[]; +} MODIFIERS, *PMODIFIERS; + +typedef struct _VSC_VK { + BYTE Vsc; + USHORT Vk; +} VSC_VK, *PVSC_VK; + +typedef struct _VK_TO_WCHAR_TABLE { + PVK_TO_WCHARS pVkToWchars; // __ptr64 + BYTE nModifications; + BYTE cbSize; +} VK_TO_WCHAR_TABLE, *PVK_TO_WCHAR_TABLE; + +typedef struct _DEADKEY { + DWORD dwBoth; + WCHAR wchComposed; + USHORT uFlags; +} DEADKEY, *PDEADKEY; + +typedef struct _VSC_LPWSTR { + BYTE vsc; + WCHAR *pwsz; // __ptr64 +} VSC_LPWSTR, *PVSC_LPWSTR; + +typedef struct tagKbdLayer { + PMODIFIERS pCharModifiers; // __ptr64 + PVK_TO_WCHAR_TABLE pVkToWcharTable; // __ptr64 + PDEADKEY pDeadKey; // __ptr64 + PVSC_LPWSTR pKeyNames; // __ptr64 + PVSC_LPWSTR pKeyNamesExt; // __ptr64 + WCHAR **pKeyNamesDead; // __ptr64 + USHORT *pusVSCtoVK; // __ptr64 + BYTE bMaxVSCtoVK; + PVSC_VK pVSCtoVK_E0; // __ptr64 + PVSC_VK pVSCtoVK_E1; // __ptr64 + DWORD fLocaleFlags; + BYTE nLgMax; + BYTE cbLgEntry; + PLIGATURE pLigature; // __ptr64 + DWORD dwType; + DWORD dwSubType; +} KBDTABLES, *PKBDTABLES; // __ptr64 + + +extern SIZE_T keycode_to_unicode(DWORD keycode, PWCHAR buffer, SIZE_T size); + +//extern DWORD unicode_to_keycode(wchar_t unicode); + +extern unsigned short keycode_to_scancode(DWORD vk_code, DWORD flags); + +extern DWORD scancode_to_keycode(unsigned short scancode); + +// Initialize the locale list and wow64 pointer size. +extern int load_input_helper(); + +// Cleanup the initialized locales. +extern int unload_input_helper(); + +#endif diff --git a/hook/windows/input_c.h b/hook/windows/input_c.h new file mode 100644 index 0000000..561708a --- /dev/null +++ b/hook/windows/input_c.h @@ -0,0 +1,823 @@ +/* Copyright (C) 2006-2017 Alexander Barker. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. +*/ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include +#include +#include +#include +#include + +#include "../iohook.h" +#include "../logger_c.h" +#include "input.h" + +static const uint16_t keycode_scancode_table[][2] = { + /* idx { vk_code, scancode }, */ + /* 0 */ { VC_UNDEFINED, 0x0000 }, // 0x00 + /* 1 */ { MOUSE_BUTTON1, VK_ESCAPE }, // 0x01 + /* 2 */ { MOUSE_BUTTON2, 0x0031 }, // 0x02 + /* 3 */ { VC_UNDEFINED, 0x0032 }, // 0x03 VK_CANCEL + /* 4 */ { MOUSE_BUTTON3, 0x0033 }, // 0x04 + /* 5 */ { MOUSE_BUTTON4, 0x0034 }, // 0x05 + /* 6 */ { MOUSE_BUTTON5, 0x0035 }, // 0x06 + /* 7 */ { VC_UNDEFINED, 0x0036 }, // 0x07 Undefined + /* 8 */ { VC_BACKSPACE, 0x0037 }, // 0x08 VK_BACK + /* 9 */ { VC_TAB, 0x0038 }, // 0x09 VK_TAB + /* 10 */ { VC_UNDEFINED, 0x0039 }, // 0x0A Reserved + /* 11 */ { VC_UNDEFINED, 0x0030 }, // 0x0B Reserved + /* 12 */ { VC_CLEAR, VK_OEM_MINUS }, // 0x0C VK_CLEAR + /* 13 */ { VC_ENTER, VK_OEM_PLUS }, // 0x0D VK_RETURN + /* 14 */ { VC_UNDEFINED, VK_BACK }, // 0x0E Undefined + /* 15 */ { VC_UNDEFINED, VK_TAB }, // 0x0F Undefined + /* 16 */ { VC_SHIFT_L, 0x0051 }, // 0x10 VK_SHIFT + /* 17 */ { VC_CONTROL_L, 0x0057 }, // 0x11 VK_CONTROL + /* 18 */ { VC_ALT_L, 0x0045 }, // 0x12 VK_MENU ALT key + /* 19 */ { VC_PAUSE, 0x0052 }, // 0x13 VK_PAUSE + /* 20 */ { VC_CAPS_LOCK, 0x0054 }, // 0x14 VK_CAPITAL CAPS LOCK key + /* 21 */ { VC_KATAKANA, 0x0059 }, // 0x15 VK_KANA IME Kana mode + /* 22 */ { VC_UNDEFINED, 0x0055 }, // 0x16 Undefined + /* 23 */ { VC_UNDEFINED, 0x0049 }, // 0x17 VK_JUNJA IME Junja mode + /* 24 */ { VC_UNDEFINED, 0x004F }, // 0x18 VK_FINAL + /* 25 */ { VC_KANJI, 0x0050 }, // 0x19 VK_KANJI / VK_HANJA IME Kanji / Hanja mode + /* 26 */ { VC_UNDEFINED, 0x00DB }, // 0x1A Undefined + /* 27 */ { VC_ESCAPE, 0x00DD }, // 0x1B VK_ESCAPE ESC key + /* 28 */ { VC_UNDEFINED, VK_RETURN }, // 0x1C VK_CONVERT IME convert// 0x1C + /* 29 */ { VC_UNDEFINED, VK_LCONTROL }, // 0x1D VK_NONCONVERT IME nonconvert + /* 30 */ { VC_UNDEFINED, 0x0041 }, // 0x1E VK_ACCEPT IME accept + /* 31 */ { VC_UNDEFINED, 0x0053 }, // 0x1F VK_MODECHANGE IME mode change request + /* 32 */ { VC_SPACE, 0x0044 }, // 0x20 VK_SPACE SPACEBAR + /* 33 */ { VC_PAGE_UP, 0x0046 }, // 0x21 VK_PRIOR PAGE UP key + /* 34 */ { VC_PAGE_DOWN, 0x0047 }, // 0x22 VK_NEXT PAGE DOWN key + /* 35 */ { VC_END, 0x0048 }, // 0x23 VK_END END key + /* 36 */ { VC_HOME, 0x004A }, // 0x24 VK_HOME HOME key + /* 37 */ { VC_LEFT, 0x004B }, // 0x25 VK_LEFT LEFT ARROW key + /* 38 */ { VC_UP, 0x004C }, // 0x26 VK_UP UP ARROW key + /* 39 */ { VC_RIGHT, VK_OEM_1 }, // 0x27 VK_RIGHT RIGHT ARROW key + /* 40 */ { VC_DOWN, VK_OEM_7 }, // 0x28 VK_DOWN DOWN ARROW key + /* 41 */ { VC_UNDEFINED, VK_OEM_3 }, // 0x29 VK_SELECT SELECT key + /* 42 */ { VC_UNDEFINED, VK_LSHIFT }, // 0x2A VK_PRINT PRINT key + /* 43 */ { VC_UNDEFINED, VK_OEM_5 }, // 0x2B VK_EXECUTE EXECUTE key + /* 44 */ { VC_PRINTSCREEN, 0x005A }, // 0x2C VK_SNAPSHOT PRINT SCREEN key + /* 45 */ { VC_INSERT, 0x0058 }, // 0x2D VK_INSERT INS key + /* 46 */ { VC_DELETE, 0x0043 }, // 0x2E VK_DELETE DEL key + /* 47 */ { VC_UNDEFINED, 0x0056 }, // 0x2F VK_HELP HELP key + /* 48 */ { VC_0, 0x0042 }, // 0x30 0 key + /* 49 */ { VC_1, 0x004E }, // 0x31 1 key + /* 50 */ { VC_2, 0x004D }, // 0x32 2 key + /* 51 */ { VC_3, VK_OEM_COMMA }, // 0x33 3 key + /* 52 */ { VC_4, VK_OEM_PERIOD }, // 0x34 4 key + /* 53 */ { VC_5, VK_OEM_2 }, // 0x35 5 key + /* 54 */ { VC_6, VK_RSHIFT }, // 0x36 6 key + /* 55 */ { VC_7, VK_MULTIPLY }, // 0x37 7 key + /* 56 */ { VC_8, VK_LMENU }, // 0x38 8 key + /* 57 */ { VC_9, VK_SPACE }, // 0x39 9 key + /* 58 */ { VC_UNDEFINED, VK_CAPITAL }, // 0x3A Undefined + /* 59 */ { VC_UNDEFINED, VK_F1 }, // 0x3B Undefined + /* 60 */ { VC_UNDEFINED, VK_F2 }, // 0x3C Undefined + /* 61 */ { VC_UNDEFINED, VK_F3 }, // 0x3D Undefined + /* 62 */ { VC_UNDEFINED, VK_F4 }, // 0x3E Undefined + /* 63 */ { VC_UNDEFINED, VK_F5 }, // 0x3F Undefined + /* 64 */ { VC_UNDEFINED, VK_F6 }, // 0x40 Undefined + /* 65 */ { VC_A, VK_F7 }, // 0x41 A key + /* 66 */ { VC_B, VK_F8 }, // 0x42 B key + /* 67 */ { VC_C, VK_F9 }, // 0x43 C key + /* 68 */ { VC_D, VK_F10 }, // 0x44 D key + /* 69 */ { VC_E, VK_NUMLOCK }, // 0x45 E key + /* 70 */ { VC_F, VK_SCROLL }, // 0x46 F key + /* 71 */ { VC_G, VK_NUMPAD7 }, // 0x47 G key + /* 72 */ { VC_H, VK_NUMPAD8 }, // 0x48 H key + /* 73 */ { VC_I, VK_NUMPAD9 }, // 0x49 I key + /* 74 */ { VC_J, VK_SUBTRACT }, // 0x4A J key + /* 75 */ { VC_K, VK_NUMPAD4 }, // 0x4B K key + /* 76 */ { VC_L, VK_NUMPAD5 }, // 0x4C L key + /* 77 */ { VC_M, VK_NUMPAD6 }, // 0x4D M key + /* 78 */ { VC_N, VK_ADD }, // 0x4E N key + /* 79 */ { VC_O, VK_NUMPAD1 }, // 0x4F O key + /* 80 */ { VC_P, VK_NUMPAD2 }, // 0x50 P key + /* 81 */ { VC_Q, VK_NUMPAD3 }, // 0x51 Q key + /* 82 */ { VC_R, VK_NUMPAD0 }, // 0x52 R key + /* 83 */ { VC_S, VK_DECIMAL }, // 0x53 S key + /* 84 */ { VC_T, 0x0000 }, // 0x54 T key + /* 85 */ { VC_U, 0x0000 }, // 0x55 U key + /* 86 */ { VC_V, 0x0000 }, // 0x56 V key + /* 87 */ { VC_W, VK_F11 }, // 0x57 W key + /* 88 */ { VC_X, VK_F12 }, // 0x58 X key + /* 89 */ { VC_Y, 0x0000 }, // 0x59 Y key + /* 90 */ { VC_Z, 0x0000 }, // 0x5A Z key + /* 91 */ { VC_META_L, VK_F13 }, // 0x5B VK_LWIN Left Windows key (Natural keyboard) + /* 92 */ { VC_META_R, VK_F14 }, // 0x5C VK_RWIN Right Windows key (Natural keyboard) + /* 93 */ { VC_CONTEXT_MENU, VK_F15 }, // 0x5D VK_APPS Applications key (Natural keyboard) + /* 94 */ { VC_UNDEFINED, 0x0000 }, // 0x5E Reserved + /* 95 */ { VC_SLEEP, 0x0000 }, // 0x5F VK_SLEEP Computer Sleep key + /* 96 */ { VC_KP_0, 0x0000 }, // 0x60 VK_NUMPAD0 Numeric keypad 0 key + /* 97 */ { VC_KP_1, 0x0000 }, // 0x61 VK_NUMPAD1 Numeric keypad 1 key + /* 98 */ { VC_KP_2, 0x0000 }, // 0x62 VK_NUMPAD2 Numeric keypad 2 key + /* 99 */ { VC_KP_3, VK_F16 }, // 0x63 VK_NUMPAD3 Numeric keypad 3 key + /* 100 */ { VC_KP_4, VK_F17 }, // 0x64 VK_NUMPAD4 Numeric keypad 4 key + /* 101 */ { VC_KP_5, VK_F18 }, // 0x65 VK_NUMPAD5 Numeric keypad 5 key + /* 102 */ { VC_KP_6, VK_F19 }, // 0x66 VK_NUMPAD6 Numeric keypad 6 key + /* 103 */ { VC_KP_7, VK_F20 }, // 0x67 VK_NUMPAD7 Numeric keypad 7 key + /* 104 */ { VC_KP_8, VK_F21 }, // 0x68 VK_NUMPAD8 Numeric keypad 8 key + /* 105 */ { VC_KP_9, VK_F22 }, // 0x69 VK_NUMPAD9 Numeric keypad 9 key + /* 106 */ { VC_KP_MULTIPLY, VK_F23 }, // 0x6A VK_MULTIPLY Multiply key + /* 107 */ { VC_KP_ADD, VK_F24 }, // 0x6B VK_ADD Add key + /* 108 */ { VC_UNDEFINED, 0x0000 }, // 0x6C VK_SEPARATOR Separator key + /* 109 */ { VC_KP_SUBTRACT, 0x0000 }, // 0x6D VK_SUBTRACT Subtract key + /* 110 */ { VC_KP_SEPARATOR, 0x0000 }, // 0x6E VK_DECIMAL Decimal key + /* 111 */ { VC_KP_DIVIDE, 0x0000 }, // 0x6F VK_DIVIDE Divide key + /* 112 */ { VC_F1, VK_KANA }, // 0x70 VK_F1 F1 key + /* 113 */ { VC_F2, 0x0000 }, // 0x71 VK_F2 F2 key + /* 114 */ { VC_F3, 0x0000 }, // 0x72 VK_F3 F3 key + /* 115 */ { VC_F4, 0x0000 }, // 0x73 VK_F4 F4 key + /* 116 */ { VC_F5, 0x0000 }, // 0x74 VK_F5 F5 key + /* 117 */ { VC_F6, 0x0000 }, // 0x75 VK_F6 F6 key + /* 118 */ { VC_F7, 0x0000 }, // 0x76 VK_F7 F7 key + /* 119 */ { VC_F8, 0x0000 }, // 0x77 VK_F8 F8 key + /* 120 */ { VC_F9, 0x0000 }, // 0x78 VK_F9 F9 key + /* 121 */ { VC_F10, VK_KANJI }, // 0x79 VK_F10 F10 key + /* 122 */ { VC_F11, 0x0000 }, // 0x7A VK_F11 F11 key + /* 123 */ { VC_F12, 0x0000 }, // 0x7B VK_F12 F12 key + /* 124 */ { VC_F13, 0x0000 }, // 0x7C VK_F13 F13 key + /* 125 */ { VC_F14, VK_OEM_8 }, // 0x7D VK_F14 F14 key + /* 126 */ { VC_F15, 0x0000 }, // 0x7E VK_F15 F15 key + /* 127 */ { VC_F16, 0x0000 }, // 0x7F VK_F16 F16 key + + // No Offset Offset (i & 0x007F) | 0x80 + + /* 128 */ { VC_F17, 0x0000 }, // 0x80 VK_F17 F17 key + /* 129 */ { VC_F18, 0x0000 }, // 0x81 VK_F18 F18 key + /* 130 */ { VC_F19, 0x0000 }, // 0x82 VK_F19 F19 key + /* 131 */ { VC_F20, 0x0000 }, // 0x83 VK_F20 F20 key + /* 132 */ { VC_F21, 0x0000 }, // 0x84 VK_F21 F21 key + /* 133 */ { VC_F22, 0x0000 }, // 0x85 VK_F22 F22 key + /* 134 */ { VC_F23, 0x0000 }, // 0x86 VK_F23 F23 key + /* 135 */ { VC_F24, 0x0000 }, // 0x87 VK_F24 F24 key + /* 136 */ { VC_UNDEFINED, 0x0000 }, // 0x88 Unassigned + /* 137 */ { VC_UNDEFINED, 0x0000 }, // 0x89 Unassigned + /* 138 */ { VC_UNDEFINED, 0x0000 }, // 0x8A Unassigned + /* 139 */ { VC_UNDEFINED, 0x0000 }, // 0x8B Unassigned + /* 140 */ { VC_UNDEFINED, 0x0000 }, // 0x8C Unassigned + /* 141 */ { VC_UNDEFINED, 0x0000 }, // 0x8D Unassigned + /* 142 */ { VC_UNDEFINED, 0x0000 }, // 0x8E Unassigned + /* 143 */ { VC_UNDEFINED, 0x0000 }, // 0x8F Unassigned + /* 144 */ { VC_NUM_LOCK, VK_MEDIA_PREV_TRACK }, // 0x90 VK_NUMLOCK NUM LOCK key + /* 145 */ { VC_SCROLL_LOCK, 0x0000 }, // 0x91 VK_SCROLL SCROLL LOCK key + /* 146 */ { VC_UNDEFINED, 0x0000 }, // 0x92 OEM specific + /* 147 */ { VC_UNDEFINED, 0x0000 }, // 0x93 OEM specific + /* 148 */ { VC_UNDEFINED, 0x0000 }, // 0x94 OEM specific + /* 149 */ { VC_UNDEFINED, 0x0000 }, // 0x95 OEM specific + /* 150 */ { VC_UNDEFINED, 0x0000 }, // 0x96 OEM specific + /* 151 */ { VC_UNDEFINED, 0x0000 }, // 0x97 Unassigned + /* 152 */ { VC_UNDEFINED, 0x0000 }, // 0x98 Unassigned + /* 153 */ { VC_UNDEFINED, VK_MEDIA_NEXT_TRACK }, // 0x99 Unassigned + /* 154 */ { VC_UNDEFINED, 0x0000 }, // 0x9A Unassigned + /* 155 */ { VC_UNDEFINED, 0x0000 }, // 0x9B Unassigned + /* 156 */ { VC_UNDEFINED, 0x0000 }, // 0x9C Unassigned + /* 157 */ { VC_UNDEFINED, VK_RCONTROL }, // 0x9D Unassigned + /* 158 */ { VC_UNDEFINED, 0x0000 }, // 0x9E Unassigned + /* 159 */ { VC_UNDEFINED, 0x0000 }, // 0x9F Unassigned + /* 160 */ { VC_SHIFT_L, VK_VOLUME_MUTE }, // 0xA0 VK_LSHIFT Left SHIFT key + /* 161 */ { VC_SHIFT_R, VK_LAUNCH_APP2 }, // 0xA1 VK_RSHIFT Right SHIFT key + /* 162 */ { VC_CONTROL_L, VK_MEDIA_PLAY_PAUSE }, // 0xA2 VK_LCONTROL Left CONTROL key + /* 163 */ { VC_CONTROL_R, 0x0000 }, // 0xA3 VK_RCONTROL Right CONTROL key + /* 164 */ { VC_ALT_L, VK_MEDIA_STOP }, // 0xA4 VK_LMENU Left MENU key + /* 165 */ { VC_ALT_R, 0x0000 }, // 0xA5 VK_RMENU Right MENU key + /* 166 */ { VC_BROWSER_BACK, 0x0000 }, // 0xA6 VK_BROWSER_BACK Browser Back key + /* 167 */ { VC_BROWSER_FORWARD, 0x0000 }, // 0xA7 VK_BROWSER_FORWARD Browser Forward key + /* 168 */ { VC_BROWSER_REFRESH, 0x0000 }, // 0xA8 VK_BROWSER_REFRESH Browser Refresh key + /* 169 */ { VC_BROWSER_STOP, 0x0000 }, // 0xA9 VK_BROWSER_STOP Browser Stop key + /* 170 */ { VC_BROWSER_SEARCH, 0x0000 }, // 0xAA VK_BROWSER_SEARCH Browser Search key + /* 171 */ { VC_BROWSER_FAVORITES, 0x0000 }, // 0xAB VK_BROWSER_FAVORITES Browser Favorites key + /* 172 */ { VC_BROWSER_HOME, 0x0000 }, // 0xAC VK_BROWSER_HOME Browser Start and Home key + /* 173 */ { VC_VOLUME_MUTE, 0x0000 }, // 0xAD VK_VOLUME_MUTE Volume Mute key + /* 174 */ { VC_VOLUME_DOWN, VK_VOLUME_DOWN }, // 0xAE VK_VOLUME_DOWN Volume Down key + /* 175 */ { VC_VOLUME_UP, 0x0000 }, // 0xAF VK_VOLUME_UP Volume Up key + /* 176 */ { VC_MEDIA_NEXT, VK_VOLUME_UP }, // 0xB0 VK_MEDIA_NEXT_TRACK Next Track key + /* 177 */ { VC_MEDIA_PREVIOUS, 0x0000 }, // 0xB1 VK_MEDIA_PREV_TRACK Previous Track key + /* 178 */ { VC_MEDIA_STOP, VK_BROWSER_HOME }, // 0xB2 VK_MEDIA_STOP Stop Media key + /* 179 */ { VC_MEDIA_PLAY, 0x0000 }, // 0xB3 VK_MEDIA_PLAY_PAUSE Play/Pause Media key + /* 180 */ { VC_UNDEFINED, 0x0000 }, // 0xB4 VK_LAUNCH_MAIL Start Mail key + /* 181 */ { VC_MEDIA_SELECT, VK_DIVIDE }, // 0xB5 VK_LAUNCH_MEDIA_SELECT Select Media key + /* 182 */ { VC_APP_MAIL, 0x0000 }, // 0xB6 VK_LAUNCH_APP1 Start Application 1 key + /* 183 */ { VC_APP_CALCULATOR, VK_SNAPSHOT }, // 0xB7 VK_LAUNCH_APP2 Start Application 2 key + /* 184 */ { VC_UNDEFINED, VK_RMENU }, // 0xB8 Reserved + /* 185 */ { VC_UNDEFINED, 0x0000 }, // 0xB9 Reserved + /* 186 */ { VC_SEMICOLON, 0x0000 }, // 0xBA VK_OEM_1 Varies by keyboard. For the US standard keyboard, the ';:' key + /* 187 */ { VC_EQUALS, 0x0000 }, // 0xBB VK_OEM_PLUS For any country/region, the '+' key + /* 188 */ { VC_COMMA, 0x00E6 }, // 0xBC VK_OEM_COMMA For any country/region, the ',' key + /* 189 */ { VC_MINUS, 0x0000 }, // 0xBD VK_OEM_MINUS For any country/region, the '-' key + /* 190 */ { VC_PERIOD, 0x0000 }, // 0xBE VK_OEM_PERIOD For any country/region, the '.' key + /* 191 */ { VC_SLASH, 0x0000 }, // 0xBF VK_OEM_2 Varies by keyboard. For the US standard keyboard, the '/?' key + /* 192 */ { VC_BACKQUOTE, 0x0000 }, // 0xC0 VK_OEM_3 Varies by keyboard. For the US standard keyboard, the '`~' key + /* 193 */ { VC_UNDEFINED, 0x0000 }, // 0xC1 Reserved + /* 194 */ { VC_UNDEFINED, 0x0000 }, // 0xC2 Reserved + /* 195 */ { VC_UNDEFINED, 0x0000 }, // 0xC3 Reserved + /* 196 */ { VC_UNDEFINED, 0x0000 }, // 0xC4 Reserved + /* 197 */ { VC_UNDEFINED, VK_PAUSE }, // 0xC5 Reserved + /* 198 */ { VC_UNDEFINED, 0x0000 }, // 0xC6 Reserved + /* 199 */ { VC_UNDEFINED, VK_HOME }, // 0xC7 Reserved + /* 200 */ { VC_UNDEFINED, VK_UP }, // 0xC8 Reserved + /* 201 */ { VC_UNDEFINED, VK_PRIOR }, // 0xC9 Reserved + /* 202 */ { VC_UNDEFINED, 0x0000 }, // 0xCA Reserved + /* 203 */ { VC_UNDEFINED, VK_LEFT }, // 0xCB Reserved + /* 204 */ { VC_UNDEFINED, VK_CLEAR }, // 0xCC Reserved + /* 205 */ { VC_UNDEFINED, VK_RIGHT }, // 0xCD Reserved + /* 206 */ { VC_UNDEFINED, 0x0000 }, // 0xCE Reserved + /* 207 */ { VC_UNDEFINED, VK_END }, // 0xCF Reserved + /* 208 */ { VC_UNDEFINED, VK_DOWN }, // 0xD0 Reserved + /* 209 */ { VC_UNDEFINED, VK_NEXT }, // 0xD1 Reserved + /* 210 */ { VC_UNDEFINED, VK_INSERT }, // 0xD2 Reserved + /* 211 */ { VC_UNDEFINED, VK_DELETE }, // 0xD3 Reserved + /* 212 */ { VC_UNDEFINED, 0x0000 }, // 0xD4 Reserved + /* 213 */ { VC_UNDEFINED, 0x0000 }, // 0xD5 Reserved + /* 214 */ { VC_UNDEFINED, 0x0000 }, // 0xD6 Reserved + /* 215 */ { VC_UNDEFINED, 0x0000 }, // 0xD7 Reserved + /* 216 */ { VC_UNDEFINED, 0x0000 }, // 0xD8 Unassigned + /* 217 */ { VC_UNDEFINED, 0x0000 }, // 0xD9 Unassigned + /* 218 */ { VC_UNDEFINED, 0x0000 }, // 0xDA Unassigned + /* 219 */ { VC_OPEN_BRACKET, VK_LWIN }, // 0xDB VK_OEM_4 Varies by keyboard. For the US standard keyboard, the '[{' key + /* 220 */ { VC_BACK_SLASH, VK_RWIN }, // 0xDC VK_OEM_5 Varies by keyboard. For the US standard keyboard, the '\|' key + /* 221 */ { VC_CLOSE_BRACKET, VK_APPS }, // 0xDD VK_OEM_6 Varies by keyboard. For the US standard keyboard, the ']}' key + /* 222 */ { VC_QUOTE, 0x0000 }, // 0xDE VK_OEM_7 Varies by keyboard. For the US standard keyboard, the 'single-quote/double-quote' key + /* 223 */ { VC_YEN, VK_SLEEP }, // 0xDF VK_OEM_8 Varies by keyboard. + /* 224 */ { VC_UNDEFINED, 0x0000 }, // 0xE0 Reserved + /* 225 */ { VC_UNDEFINED, 0x0000 }, // 0xE1 OEM specific + /* 226 */ { VC_UNDEFINED, 0x0000 }, // 0xE2 VK_OEM_102 Either the angle bracket key or the backslash key on the RT 102-key keyboard + /* 227 */ { VC_UNDEFINED, 0x0000 }, // 0xE3 OEM specific + /* 228 */ { VC_UNDEFINED, 0x00E5 }, // 0xE4 VC_APP_PICTURES OEM specific + /* 229 */ { VC_APP_PICTURES, VK_BROWSER_SEARCH }, // 0xE5 VK_PROCESSKEY IME PROCESS key + /* 230 */ { VC_APP_MUSIC, VK_BROWSER_FAVORITES }, // 0xE6 OEM specific + /* 231 */ { VC_UNDEFINED, VK_BROWSER_REFRESH }, // 0xE7 VK_PACKET Used to pass Unicode characters as if they were keystrokes. The VK_PACKET key is the low word of a 32-bit Virtual Key value used for non-keyboard input methods. + /* 232 */ { VC_UNDEFINED, VK_BROWSER_STOP }, // 0xE8 Unassigned + /* 233 */ { VC_UNDEFINED, VK_BROWSER_FORWARD }, // 0xE9 OEM specific + /* 234 */ { VC_UNDEFINED, VK_BROWSER_BACK }, // 0xEA OEM specific + /* 235 */ { VC_UNDEFINED, 0x0000 }, // 0xEB OEM specific + /* 236 */ { VC_UNDEFINED, VK_LAUNCH_APP1 }, // 0xEC OEM specific + /* 237 */ { VC_UNDEFINED, VK_LAUNCH_MEDIA_SELECT }, // 0xED OEM specific + /* 238 */ { VC_UNDEFINED, 0x0000 }, // 0xEE OEM specific + /* 239 */ { VC_UNDEFINED, 0x0000 }, // 0xEF OEM specific + /* 240 */ { VC_UNDEFINED, 0x0000 }, // 0xF0 OEM specific + /* 241 */ { VC_UNDEFINED, 0x0000 }, // 0xF1 OEM specific + /* 242 */ { VC_UNDEFINED, 0x0000 }, // 0xF2 OEM specific + /* 243 */ { VC_UNDEFINED, 0x0000 }, // 0xF3 OEM specific + /* 244 */ { VC_UNDEFINED, 0x0000 }, // 0xF4 OEM specific + /* 245 */ { VC_UNDEFINED, 0x0000 }, // 0xF5 OEM specific + /* 246 */ { VC_UNDEFINED, 0x0000 }, // 0xF6 VK_ATTN Attn key + /* 247 */ { VC_UNDEFINED, 0x0000 }, // 0xF7 VK_CRSEL CrSel key + /* 248 */ { VC_UNDEFINED, 0x0000 }, // 0xF8 VK_EXSEL ExSel key + /* 249 */ { VC_UNDEFINED, 0x0000 }, // 0xF9 VK_EREOF Erase EOF key + /* 250 */ { VC_UNDEFINED, 0x0000 }, // 0xFA VK_PLAY Play key + /* 251 */ { VC_UNDEFINED, 0x0000 }, // 0xFB VK_ZOOM Zoom key + /* 252 */ { VC_UNDEFINED, 0x0000 }, // 0xFC VK_NONAME Reserved + /* 253 */ { VC_UNDEFINED, 0x0000 }, // 0xFD + /* 254 */ { VC_CLEAR, 0x0000 }, // 0xFE VK_OEM_CLEAR Clear key + /* 255 */ { VC_UNDEFINED, 0x0000 } // 0xFE Unassigned +}; + +unsigned short keycode_to_scancode(DWORD vk_code, DWORD flags) { + unsigned short scancode = VC_UNDEFINED; + + // Check the vk_code is in range. + // NOTE vk_code >= 0 is assumed because DWORD is unsigned. + if (vk_code < sizeof(keycode_scancode_table) / sizeof(keycode_scancode_table[0])) { + scancode = keycode_scancode_table[vk_code][0]; + + if (flags & LLKHF_EXTENDED) { +// logger(LOG_LEVEL_WARN, "%s [%u]: EXTD2, vk_code %li\n", +// __FUNCTION__, __LINE__, vk_code); + + switch (vk_code) { + case VK_PRIOR: + case VK_NEXT: + case VK_END: + case VK_HOME: + case VK_LEFT: + case VK_UP: + case VK_RIGHT: + case VK_DOWN: + + case VK_INSERT: + case VK_DELETE: + scancode |= 0xEE00; + break; + + case VK_RETURN: + scancode |= 0x0E00; + break; + } + } else { + // logger(LOG_LEVEL_WARN, "%s [%u]: Test2, vk_code %li\n", + // __FUNCTION__, __LINE__, vk_code); + } + } + + return scancode; +} + +DWORD scancode_to_keycode(unsigned short scancode) { + unsigned short keycode = 0x0000; + + // Check the vk_code is in range. + // NOTE vk_code >= 0 is assumed because the scancode is unsigned. + if (scancode < 128) { + keycode = keycode_scancode_table[scancode][1]; + } else { + // Calculate the upper offset based on the lower half of the scancode + 128. + unsigned short int i = (scancode & 0x007F) | 0x80; + + if (i < sizeof(keycode_scancode_table) / sizeof(keycode_scancode_table[1])) { + keycode = keycode_scancode_table[i][1]; + } + } + + return keycode; +} + + +/************************************************************************/ + +// Structure and pointers for the keyboard locale cache. +typedef struct _KeyboardLocale { + HKL id; // Locale ID + HINSTANCE library; // Keyboard DLL instance. + PVK_TO_BIT pVkToBit; // Pointers struct arrays. + PVK_TO_WCHAR_TABLE pVkToWcharTable; + PDEADKEY pDeadKey; + struct _KeyboardLocale* next; +} KeyboardLocale; + +static KeyboardLocale* locale_first = NULL; +static KeyboardLocale* locale_current = NULL; +static WCHAR deadChar = WCH_NONE; + +// Amount of pointer padding to apply for Wow64 instances. +static unsigned short int ptr_padding = 0; + +#if defined(_WIN32) && !defined(_WIN64) +// Small function to check and see if we are executing under Wow64. +static BOOL is_wow64() { + BOOL status = FALSE; + + LPFN_ISWOW64PROCESS pIsWow64Process = (LPFN_ISWOW64PROCESS) + GetProcAddress(GetModuleHandle("kernel32"), "IsWow64Process"); + + if (pIsWow64Process != NULL) { + HANDLE current_proc = GetCurrentProcess(); + + if (!pIsWow64Process(current_proc, &status)) { + status = FALSE; + + logger(LOG_LEVEL_DEBUG, "%s [%u]: pIsWow64Process(%#p, %#p) failed!\n", + __FUNCTION__, __LINE__, current_proc, &status); + } + } + + return status; +} +#endif + +// Locate the DLL that contains the current keyboard layout. +static int get_keyboard_layout_file(char *layoutFile, DWORD bufferSize) { + int status = IOHOOK_FAILURE; + HKEY hKey; + DWORD varType = REG_SZ; + + char kbdName[KL_NAMELENGTH]; + if (GetKeyboardLayoutName(kbdName)) { + char kbdKeyPath[51 + KL_NAMELENGTH]; + snprintf(kbdKeyPath, 51 + KL_NAMELENGTH, "SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts\\%s", kbdName); + + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, (LPCTSTR) kbdKeyPath, 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS) { + if (RegQueryValueEx(hKey, "Layout File", NULL, &varType, (LPBYTE) layoutFile, &bufferSize) == ERROR_SUCCESS) { + RegCloseKey(hKey); + status = IOHOOK_SUCCESS; + } + } + } + + return status; +} + +static int refresh_locale_list() { + int count = 0; + + // Get the number of layouts the user has activated. + int hkl_size = GetKeyboardLayoutList(0, NULL); + if (hkl_size > 0) { + logger(LOG_LEVEL_INFO, "%s [%u]: GetKeyboardLayoutList(0, NULL) found %i layouts.\n", + __FUNCTION__, __LINE__, hkl_size); + + // Get the thread id that currently has focus for our default. + DWORD focus_pid = GetWindowThreadProcessId(GetForegroundWindow(), NULL); + HKL hlk_focus = GetKeyboardLayout(focus_pid); + HKL hlk_default = GetKeyboardLayout(0); + HKL *hkl_list = malloc(sizeof(HKL) * hkl_size); + + 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", + __FUNCTION__, __LINE__, hkl_size, new_size); + } else { + logger(LOG_LEVEL_INFO, "%s [%u]: Received %i locales.\n", + __FUNCTION__, __LINE__, new_size); + } + + KeyboardLocale* locale_previous = NULL; + KeyboardLocale* locale_item = locale_first; + + // Go though the linked list and remove KeyboardLocale's that are + // no longer loaded. + while (locale_item != NULL) { + // Check to see if the old HKL is in the new list. + bool is_loaded = false; + int i; + for (i = 0; i < new_size && !is_loaded; i++) { + if (locale_item->id == hkl_list[i]) { + // Flag and jump out of the loop. + hkl_list[i] = NULL; + is_loaded = true; + } + } + + + if (is_loaded) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: Found locale ID %#p in the cache.\n", + __FUNCTION__, __LINE__, locale_item->id); + + // Set the previous local to the current locale. + locale_previous = locale_item; + + // Check and see if the locale is our current active locale. + if (locale_item->id == hlk_focus) { + locale_current = locale_item; + } + + count++; + } else { + logger(LOG_LEVEL_DEBUG, "%s [%u]: Removing locale ID %#p from the cache.\n", + __FUNCTION__, __LINE__, locale_item->id); + + // If the old id is not in the new list, remove it. + locale_previous->next = locale_item->next; + + // Make sure the locale_current points NULL or something valid. + if (locale_item == locale_current) { + locale_current = NULL; + } + + // Free the memory used by locale_item; + free(locale_item); + + // Set the item to the pervious item to guarantee a next. + locale_item = locale_previous; + } + + // Iterate to the next linked list item. + locale_item = locale_item->next; + } + + + // Insert anything new into the linked list. + int i; + for (i = 0; i < new_size; i++) { + // Check to see if the item was already in the list. + if (hkl_list[i] != NULL) { + // Set the active keyboard layout for this thread to the HKL. + ActivateKeyboardLayout(hkl_list[i], 0x00); + + // Try to pull the current keyboard layout DLL from the registry. + char layoutFile[MAX_PATH]; + if (get_keyboard_layout_file(layoutFile, sizeof(layoutFile)) == IOHOOK_SUCCESS) { + // You can't trust the %SYSPATH%, look it up manually. + char systemDirectory[MAX_PATH]; + if (GetSystemDirectory(systemDirectory, MAX_PATH) != 0) { + char kbdLayoutFilePath[MAX_PATH]; + snprintf(kbdLayoutFilePath, MAX_PATH, "%s\\%s", systemDirectory, layoutFile); + + logger(LOG_LEVEL_DEBUG, "%s [%u]: Loading layout for %#p: %s.\n", + __FUNCTION__, __LINE__, hkl_list[i], layoutFile); + + // Create the new locale item. + locale_item = malloc(sizeof(KeyboardLocale)); + locale_item->id = hkl_list[i]; + locale_item->library = LoadLibrary(kbdLayoutFilePath); + + // Get the function pointer from the library to get the keyboard layer descriptor. + KbdLayerDescriptor pKbdLayerDescriptor = (KbdLayerDescriptor) GetProcAddress(locale_item->library, "KbdLayerDescriptor"); + if (pKbdLayerDescriptor != NULL) { + PKBDTABLES pKbd = pKbdLayerDescriptor(); + + // Store the memory address of the following 3 structures. + BYTE *base = (BYTE *) pKbd; + + // First element of each structure, no offset adjustment needed. + locale_item->pVkToBit = pKbd->pCharModifiers->pVkToBit; + + // Second element of pKbd, +4 byte offset on wow64. + locale_item->pVkToWcharTable = *((PVK_TO_WCHAR_TABLE *) (base + offsetof(KBDTABLES, pVkToWcharTable) + ptr_padding)); + + // Third element of pKbd, +8 byte offset on wow64. + locale_item->pDeadKey = *((PDEADKEY *) (base + offsetof(KBDTABLES, pDeadKey) + (ptr_padding * 2))); + + // This will always be added to the end of the list. + locale_item->next = NULL; + + // Insert the item into the linked list. + if (locale_previous == NULL) { + // If nothing came before, the list is empty. + locale_first = locale_item; + } else { + // Append the new locale to the end of the list. + locale_previous->next = locale_item; + } + + // Check and see if the locale is our current active locale. + if (locale_item->id == hlk_focus) { + locale_current = locale_item; + } + + // Set the pervious locale item to the new one. + locale_previous = locale_item; + + count++; + } else { + logger(LOG_LEVEL_ERROR, + "%s [%u]: GetProcAddress() failed for KbdLayerDescriptor!\n", + __FUNCTION__, __LINE__); + + FreeLibrary(locale_item->library); + free(locale_item); + locale_item = NULL; + } + } else { + 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", + __FUNCTION__, __LINE__, hkl_list[i]); + } + } + } + } else { + logger(LOG_LEVEL_ERROR, + "%s [%u]: GetKeyboardLayoutList() failed!\n", + __FUNCTION__, __LINE__); + + // TODO Try and recover by using the current layout. + // Hint: Use locale_id instead of hkl_list[i] in the loop above. + } + + free(hkl_list); + ActivateKeyboardLayout(hlk_default, 0x00); + } + + return count; +} + +SIZE_T keycode_to_unicode(DWORD keycode, PWCHAR buffer, SIZE_T size) { + // Get the thread id that currently has focus and ask for its current + // locale. + DWORD focus_pid = GetWindowThreadProcessId(GetForegroundWindow(), NULL); + HKL locale_id = GetKeyboardLayout(focus_pid); + + // If the current Locale is not the new locale, search the linked list. + if (locale_current == NULL || locale_current->id != locale_id) { + locale_current = NULL; + KeyboardLocale* locale_item = locale_first; + + // Search the linked list... + while (locale_item != NULL && locale_item->id != locale_id) { + locale_item = locale_item->next; + } + + // You may already be a winner! + if (locale_item != NULL && locale_item->id != locale_id) { + logger(LOG_LEVEL_INFO, + "%s [%u]: Activating keyboard layout %#p.\n", + __FUNCTION__, __LINE__, locale_item->id); + + // Switch the current locale. + locale_current = locale_item; + locale_item = NULL; + + // If they layout changes the dead key state needs to be reset. + // This is consistent with the way Windows handles locale changes. + deadChar = WCH_NONE; + } else { + logger(LOG_LEVEL_DEBUG, + "%s [%u]: Refreshing locale cache.\n", + __FUNCTION__, __LINE__); + + refresh_locale_list(); + } + } + + // Initialize to empty. + SIZE_T charCount = 0; + // buffer[i] = WCH_NONE; + + // Check and make sure the Unicode helper was loaded. + if (locale_current != NULL) { + logger(LOG_LEVEL_INFO, + "%s [%u]: Using keyboard layout %#p.\n", + __FUNCTION__, __LINE__, locale_current->id); + + int mod = 0; + + int capsLock = (GetKeyState(VK_CAPITAL) & 0x01); + + PVK_TO_BIT pVkToBit = locale_current->pVkToBit; + PVK_TO_WCHAR_TABLE pVkToWcharTable = locale_current->pVkToWcharTable; + PDEADKEY pDeadKey = locale_current->pDeadKey; + + /* Loop over the modifier keys for this locale and determine what is + * currently depressed. Because this is only a structure of two + * bytes, we don't need to worry about the structure padding of __ptr64 + * offsets on Wow64. + */ + bool is_shift = false, is_ctrl = false, is_alt = false; + int i; + for (i = 0; pVkToBit[i].Vk != 0; i++) { + short state = GetAsyncKeyState(pVkToBit[i].Vk); + + // Check to see if the most significant bit is active. + if (state & ~SHRT_MAX) { + if (pVkToBit[i].Vk == VK_SHIFT) { + is_shift = true; + } else if (pVkToBit[i].Vk == VK_CONTROL) { + is_ctrl = true; + } else if (pVkToBit[i].Vk == VK_MENU) { + is_alt = true; + } + } + } + + // Check the Shift modifier. + if (is_shift) { + mod = 1; + } + + // Check for the AltGr modifier. + if (is_ctrl && is_alt) { + mod += 3; + } + + // Default 32 bit structure size should be 6 bytes (4 for the pointer and 2 + // additional byte fields) that are padded out to 8 bytes by the compiler. + unsigned short sizeVkToWcharTable = sizeof(VK_TO_WCHAR_TABLE); + #if defined(_WIN32) && !defined(_WIN64) + if (is_wow64()) { + // If we are running under Wow64 the size of the first pointer will be + // 8 bringing the total size to 10 bytes padded out to 16. + sizeVkToWcharTable = (sizeVkToWcharTable + ptr_padding + 7) & -8; + } + #endif + + BYTE *ptrCurrentVkToWcharTable = (BYTE *) pVkToWcharTable; + + int cbSize, n; + do { + // cbSize is used to calculate n, and n is used for the size of pVkToWchars[j].wch[n] + cbSize = *(ptrCurrentVkToWcharTable + offsetof(VK_TO_WCHAR_TABLE, cbSize) + ptr_padding); + n = (cbSize - 2) / 2; + + // Same as VK_TO_WCHARS pVkToWchars[] = pVkToWcharTable[i].pVkToWchars + PVK_TO_WCHARS pVkToWchars = (PVK_TO_WCHARS) ((PVK_TO_WCHAR_TABLE) ptrCurrentVkToWcharTable)->pVkToWchars; + + if (pVkToWchars != NULL && mod < n) { + // pVkToWchars[j].VirtualKey + BYTE *pCurrentVkToWchars = (BYTE *) pVkToWchars; + + do { + if (((PVK_TO_WCHARS) pCurrentVkToWchars)->VirtualKey == keycode) { + if ((((PVK_TO_WCHARS) pCurrentVkToWchars)->Attributes == CAPLOK) && capsLock) { + if (is_shift && mod > 0) { + mod -= 1; + } else { + mod += 1; + } + } + + // Set the initial unicode char. + WCHAR unicode = ((PVK_TO_WCHARS) pCurrentVkToWchars)->wch[mod]; + + // Increment the pCurrentVkToWchars by the size of wch[n]. + pCurrentVkToWchars += sizeof(VK_TO_WCHARS) + (sizeof(WCHAR) * n); + + + if (unicode == WCH_DEAD) { + // The current unicode char is a dead key... + if (deadChar == WCH_NONE) { + // No previous dead key was set so cache the next + // wchar so we know what to do next time its pressed. + deadChar = ((PVK_TO_WCHARS) pCurrentVkToWchars)->wch[mod]; + } else { + if (size >= 2) { + // Received a second dead key. + memset(buffer, deadChar, 2); + //buffer[0] = deadChar; + //buffer[1] = deadChar; + + deadChar = WCH_NONE; + charCount = 2; + } + } + } else if (unicode != WCH_NONE) { + // We are not WCH_NONE or WCH_DEAD + if (size >= 1) { + buffer[0] = unicode; + charCount = 1; + } + } + + break; + } else { + // Add sizeof WCHAR because we are really an array of WCHAR[n] not WCHAR[] + pCurrentVkToWchars += sizeof(VK_TO_WCHARS) + (sizeof(WCHAR) * n); + } + } while ( ((PVK_TO_WCHARS) pCurrentVkToWchars)->VirtualKey != 0 ); + } + + // This is effectively the same as: ptrCurrentVkToWcharTable = pVkToWcharTable[++i]; + ptrCurrentVkToWcharTable += sizeVkToWcharTable; + } while (cbSize != 0); + + + // If the current local has a dead key set. + if (deadChar != WCH_NONE) { + // Loop over the pDeadKey lookup table for the locale. + int i; + for (i = 0; pDeadKey[i].dwBoth != 0; i++) { + WCHAR baseChar = (WCHAR) pDeadKey[i].dwBoth; + WCHAR diacritic = (WCHAR) (pDeadKey[i].dwBoth >> 16); + + // If we locate an extended dead char, set it. + if (size >= 1 && baseChar == buffer[0] && diacritic == deadChar) { + deadChar = WCH_NONE; + + if (charCount <= size) { + memset(buffer, (WCHAR) pDeadKey[i].wchComposed, charCount); + //buffer[i] = (WCHAR) pDeadKey[i].wchComposed; + } + } + } + } + } + + return charCount; +} + +int load_input_helper() { + int count = 0; + + #if defined(_WIN32) && !defined(_WIN64) + if (is_wow64()) { + ptr_padding = sizeof(void *); + } + #endif + + count = refresh_locale_list(); + + logger(LOG_LEVEL_INFO, + "%s [%u]: refresh_locale_list() found %i locale(s).\n", + __FUNCTION__, __LINE__, count); + + return count; +} + +// This returns the number of locales that were removed. +int unload_input_helper() { + int count = 0; + + // Cleanup and free memory from the old list. + KeyboardLocale* locale_item = locale_first; + while (locale_item != NULL) { + // Remove the first item from the linked list. + FreeLibrary(locale_item->library); + locale_first = locale_item->next; + free(locale_item); + locale_item = locale_first; + + count++; + } + + // Reset the current local. + locale_current = NULL; + + return count; +} diff --git a/hook/windows/properties_c.h b/hook/windows/properties_c.h new file mode 100644 index 0000000..c66930c --- /dev/null +++ b/hook/windows/properties_c.h @@ -0,0 +1,217 @@ +/* Copyright (C) 2006-2017 Alexander Barker. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. +*/ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include "../iohook.h" +#include "input.h" +// #include "logger.h" + +// The handle to the DLL module pulled in DllMain on DLL_PROCESS_ATTACH. +HINSTANCE hInst; + +// input_hook.c +extern void unregister_running_hooks(); + + +// Structure for the monitor_enum_proc() callback so we can track the count. +typedef struct _screen_info { + uint8_t count; + screen_data *data; +} screen_info; + + +static BOOL CALLBACK monitor_enum_proc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { + int width = lprcMonitor->right - lprcMonitor->left; + int height = lprcMonitor->bottom - lprcMonitor->top; + int origin_x = lprcMonitor->left; + int origin_y = lprcMonitor->top; + + if (width > 0 && height > 0) { + screen_info *screens = (screen_info *) dwData; + + if (screens->data == NULL) { + screens->data = (screen_data *) malloc(sizeof(screen_data)); + } else { + screens->data = (screen_data *) realloc(screens, sizeof(screen_data) * screens->count); + } + + screens->data[screens->count++] = (screen_data) { + // Should monitor count start @ zero? Currently it starts at 1. + .number = screens->count, + .x = origin_x, + .y = origin_y, + .width = width, + .height = height + }; + + logger(LOG_LEVEL_INFO, "%s [%u]: Monitor %d: %ldx%ld (%ld, %ld)\n", + __FUNCTION__, __LINE__, screens->count, width, height, origin_x, origin_y); + } + + return TRUE; +} + +IOHOOK_API screen_data* hook_create_screen_info(unsigned char *count) { + // Initialize count to zero. + *count = 0; + + // Create a simple structure to make working with monitor_enum_proc easier. + screen_info screens = { + .count = 0, + .data = NULL + }; + + BOOL status = EnumDisplayMonitors(NULL, NULL, monitor_enum_proc, (LPARAM) &screens); + + if (!status || screens.count == 0) { + // Fallback in case EnumDisplayMonitors fails. + logger(LOG_LEVEL_INFO, "%s [%u]: EnumDisplayMonitors failed. Fallback.\n", + __FUNCTION__, __LINE__); + + int width = GetSystemMetrics(SM_CXSCREEN); + int height = GetSystemMetrics(SM_CYSCREEN); + + if (width > 0 && height > 0) { + screens.data = (screen_data *) malloc(sizeof(screen_data)); + + if (screens.data != NULL) { + *count = 1; + screens.data[0] = (screen_data) { + .number = 1, + .x = 0, + .y = 0, + .width = width, + .height = height + }; + } + } + } else { + // Populate the count. + *count = screens.count; + } + + return screens.data; +} + +IOHOOK_API long int hook_get_auto_repeat_rate() { + long int value = -1; + long int rate; + + if (SystemParametersInfo(SPI_GETKEYBOARDSPEED, 0, &rate, 0)) { + logger(LOG_LEVEL_INFO, "%s [%u]: SPI_GETKEYBOARDSPEED: %li.\n", + __FUNCTION__, __LINE__, rate); + + value = rate; + } + + return value; +} + +IOHOOK_API long int hook_get_auto_repeat_delay() { + long int value = -1; + long int delay; + + if (SystemParametersInfo(SPI_GETKEYBOARDDELAY, 0, &delay, 0)) { + logger(LOG_LEVEL_INFO, "%s [%u]: SPI_GETKEYBOARDDELAY: %li.\n", + __FUNCTION__, __LINE__, delay); + + value = delay; + } + + return value; +} + +IOHOOK_API long int hook_get_pointer_acceleration_multiplier() { + long int value = -1; + int mouse[3]; // 0-Threshold X, 1-Threshold Y and 2-Speed. + + if (SystemParametersInfo(SPI_GETMOUSE, 0, &mouse, 0)) { + logger(LOG_LEVEL_INFO, "%s [%u]: SPI_GETMOUSE[2]: %i.\n", + __FUNCTION__, __LINE__, mouse[2]); + + value = mouse[2]; + } + + return value; +} + +IOHOOK_API long int hook_get_pointer_acceleration_threshold() { + long int value = -1; + int mouse[3]; // 0-Threshold X, 1-Threshold Y and 2-Speed. + + if (SystemParametersInfo(SPI_GETMOUSE, 0, &mouse, 0)) { + logger(LOG_LEVEL_INFO, "%s [%u]: SPI_GETMOUSE[0]: %i.\n", + __FUNCTION__, __LINE__, mouse[0]); + logger(LOG_LEVEL_INFO, "%s [%u]: SPI_GETMOUSE[1]: %i.\n", + __FUNCTION__, __LINE__, mouse[1]); + + // Average the x and y thresholds. + value = (mouse[0] + mouse[1]) / 2; + } + + return value; +} + +IOHOOK_API long int hook_get_pointer_sensitivity() { + long int value = -1; + int sensitivity; + + if (SystemParametersInfo(SPI_GETMOUSESPEED, 0, &sensitivity, 0)) { + logger(LOG_LEVEL_INFO, "%s [%u]: SPI_GETMOUSESPEED: %i.\n", + __FUNCTION__, __LINE__, sensitivity); + + value = sensitivity; + } + + return value; +} + +IOHOOK_API long int hook_get_multi_click_time() { + long int value = -1; + UINT clicktime; + + clicktime = GetDoubleClickTime(); + logger(LOG_LEVEL_INFO, "%s [%u]: GetDoubleClickTime: %u.\n", + __FUNCTION__, __LINE__, (unsigned int) clicktime); + + value = (long int) clicktime; + + return value; +} + +// DLL Entry point. +BOOL WINAPI DllMain(HINSTANCE hInstDLL, DWORD fdwReason, LPVOID lpReserved) { + switch (fdwReason) { + case DLL_PROCESS_ATTACH: + // Save the DLL address. + hInst = hInstDLL; + + // Initialize native input helper functions. + load_input_helper(); + break; + + case DLL_PROCESS_DETACH: + // Unregister any hooks that may still be installed. + unregister_running_hooks(); + + // Deinitialize native input helper functions. + unload_input_helper(); + break; + + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + // Do Nothing. + break; + } + + return TRUE; +} diff --git a/hook/x11/event_c.h b/hook/x11/event_c.h new file mode 100644 index 0000000..9d628c9 --- /dev/null +++ b/hook/x11/event_c.h @@ -0,0 +1,385 @@ +/* Copyright (C) 2006-2017 Alexander Barker. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. +*/ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include +#include +#include +#include +#ifdef USE_XTEST + #include +#endif + +#include "../iohook.h" +#include "input.h" +// #include "../logger.h" + +extern Display *properties_disp; + +// This lookup table must be in the same order the masks are defined. +#ifdef USE_XTEST +static KeySym keymask_lookup[8] = { + XK_Shift_L, + XK_Control_L, + XK_Meta_L, + XK_Alt_L, + + XK_Shift_R, + XK_Control_R, + XK_Meta_R, + XK_Alt_R +}; + +static unsigned int btnmask_lookup[5] = { + MASK_BUTTON1, + MASK_BUTTON2, + MASK_BUTTON3, + MASK_BUTTON4, + MASK_BUTTON5 +}; +#else +// TODO Possibly relocate to input helper. +static unsigned int convert_to_native_mask(unsigned int mask) { + unsigned int native_mask = 0x00; + + if (mask & (MASK_SHIFT)) { native_mask |= ShiftMask; } + if (mask & (MASK_CTRL)) { native_mask |= ControlMask; } + if (mask & (MASK_META)) { native_mask |= Mod4Mask; } + if (mask & (MASK_ALT)) { native_mask |= Mod1Mask; } + + if (mask & MASK_BUTTON1) { native_mask |= Button1Mask; } + if (mask & MASK_BUTTON2) { native_mask |= Button2Mask; } + if (mask & MASK_BUTTON3) { native_mask |= Button3Mask; } + if (mask & MASK_BUTTON4) { native_mask |= Button4Mask; } + if (mask & MASK_BUTTON5) { native_mask |= Button5Mask; } + + return native_mask; +} +#endif + +static inline void post_key_event(iohook_event * const event) { + #ifdef USE_XTEST + // FIXME Currently ignoring EVENT_KEY_TYPED. + if (event->type == EVENT_KEY_PRESSED) { + XTestFakeKeyEvent( + properties_disp, + scancode_to_keycode(event->data.keyboard.keycode), + True, + 0); + } else if (event->type == EVENT_KEY_RELEASED) { + XTestFakeKeyEvent( + properties_disp, + scancode_to_keycode(event->data.keyboard.keycode), + False, + 0); + } + #else + XKeyEvent key_event; + + key_event.serial = 0x00; + key_event.send_event = False; + key_event.display = properties_disp; + key_event.time = CurrentTime; + key_event.same_screen = True; + + unsigned int mask; + if (!XQueryPointer(properties_disp, DefaultRootWindow(properties_disp), &(key_event.root), &(key_event.subwindow), &(key_event.x_root), &(key_event.y_root), &(key_event.x), &(key_event.y), &mask)) { + key_event.root = DefaultRootWindow(properties_disp); + key_event.window = key_event.root; + key_event.subwindow = None; + + key_event.x_root = 0; + key_event.y_root = 0; + key_event.x = 0; + key_event.y = 0; + } + + key_event.state = convert_to_native_mask(event->mask); + key_event.keycode = XKeysymToKeycode(properties_disp, scancode_to_keycode(event->data.keyboard.keycode)); + + // FIXME Currently ignoring typed events. + if (event->type == EVENT_KEY_PRESSED) { + key_event.type = KeyPress; + XSendEvent(properties_disp, InputFocus, False, KeyPressMask, (XEvent *) &key_event); + } else if (event->type == EVENT_KEY_RELEASED) { + key_event.type = KeyRelease; + XSendEvent(properties_disp, InputFocus, False, KeyReleaseMask, (XEvent *) &key_event); + } + #endif +} + +static inline void post_mouse_button_event(iohook_event * const event) { + #ifdef USE_XTEST + Window ret_root; + Window ret_child; + int root_x; + int root_y; + int win_x; + int win_y; + unsigned int mask; + + Window win_root = XDefaultRootWindow(properties_disp); + Bool query_status = XQueryPointer(properties_disp, win_root, &ret_root, &ret_child, &root_x, &root_y, &win_x, &win_y, &mask); + if (query_status) { + if (event->data.mouse.x != root_x || event->data.mouse.y != root_y) { + // Move the pointer to the specified position. + XTestFakeMotionEvent(properties_disp, -1, event->data.mouse.x, event->data.mouse.y, 0); + } else { + query_status = False; + } + } + + if (event->type == EVENT_MOUSE_WHEEL) { + // Wheel events should be the same as click events on X11. + // type, amount and rotation + if (event->data.wheel.rotation < 0) { + XTestFakeButtonEvent(properties_disp, WheelUp, True, 0); + XTestFakeButtonEvent(properties_disp, WheelUp, False, 0); + } else { + XTestFakeButtonEvent(properties_disp, WheelDown, True, 0); + XTestFakeButtonEvent(properties_disp, WheelDown, False, 0); + } + } else if (event->type == EVENT_MOUSE_PRESSED) { + XTestFakeButtonEvent(properties_disp, event->data.mouse.button, True, 0); + } else if (event->type == EVENT_MOUSE_RELEASED) { + XTestFakeButtonEvent(properties_disp, event->data.mouse.button, False, 0); + } else if (event->type == EVENT_MOUSE_CLICKED) { + XTestFakeButtonEvent(properties_disp, event->data.mouse.button, True, 0); + XTestFakeButtonEvent(properties_disp, event->data.mouse.button, False, 0); + } + + if (query_status) { + // Move the pointer back to the original position. + XTestFakeMotionEvent(properties_disp, -1, root_x, root_y, 0); + } + #else + XButtonEvent btn_event; + + btn_event.serial = 0x00; + btn_event.send_event = False; + btn_event.display = properties_disp; + btn_event.time = CurrentTime; + btn_event.same_screen = True; + + btn_event.root = DefaultRootWindow(properties_disp); + btn_event.window = btn_event.root; + btn_event.subwindow = None; + + btn_event.type = 0x00; + btn_event.state = 0x00; + btn_event.x_root = 0; + btn_event.y_root = 0; + btn_event.x = 0; + btn_event.y = 0; + btn_event.button = 0x00; + + btn_event.state = convert_to_native_mask(event->mask); + + btn_event.x = event->data.mouse.x; + btn_event.y = event->data.mouse.y; + + #if defined(USE_XINERAMA) || defined(USE_XRANDR) + uint8_t screen_count; + screen_data *screens = hook_create_screen_info(&screen_count); + if (screen_count > 1) { + btn_event.x += screens[0].x; + btn_event.y += screens[0].y; + } + + if (screens != NULL) { + free(screens); + } + #endif + + // These are the same because Window == Root Window. + btn_event.x_root = btn_event.x; + btn_event.y_root = btn_event.y; + + if (event->type == EVENT_MOUSE_WHEEL) { + // type, amount and rotation + if (event->data.wheel.rotation < 0) { + btn_event.button = WheelUp; + } else { + btn_event.button = WheelDown; + } + } + + if (event->type != EVENT_MOUSE_RELEASED) { + // FIXME Where do we set event->button? + btn_event.type = ButtonPress; + XSendEvent(properties_disp, InputFocus, False, ButtonPressMask, (XEvent *) &btn_event); + } + + if (event->type != EVENT_MOUSE_PRESSED) { + btn_event.type = ButtonRelease; + XSendEvent(properties_disp, InputFocus, False, ButtonReleaseMask, (XEvent *) &btn_event); + } + #endif +} + +static inline void post_mouse_motion_event(iohook_event * const event) { + #ifdef USE_XTEST + XTestFakeMotionEvent(properties_disp, -1, event->data.mouse.x, event->data.mouse.y, 0); + #else + XMotionEvent mov_event; + + mov_event.serial = MotionNotify; + mov_event.send_event = False; + mov_event.display = properties_disp; + mov_event.time = CurrentTime; + mov_event.same_screen = True; + mov_event.is_hint = NotifyNormal, + mov_event.root = DefaultRootWindow(properties_disp); + mov_event.window = mov_event.root; + mov_event.subwindow = None; + + mov_event.type = 0x00; + mov_event.state = 0x00; + mov_event.x_root = 0; + mov_event.y_root = 0; + mov_event.x = 0; + mov_event.y = 0; + + mov_event.state = convert_to_native_mask(event->mask); + + mov_event.x = event->data.mouse.x; + mov_event.y = event->data.mouse.y; + + #if defined(USE_XINERAMA) || defined(USE_XRANDR) + uint8_t screen_count; + screen_data *screens = hook_create_screen_info(&screen_count); + if (screen_count > 1) { + mov_event.x += screens[0].x; + mov_event.y += screens[0].y; + } + + if (screens != NULL) { + free(screens); + } + #endif + + // These are the same because Window == Root Window. + mov_event.x_root = mov_event.x; + mov_event.y_root = mov_event.y; + + long int event_mask = NoEventMask; + if (event->type == EVENT_MOUSE_DRAGGED) { + #if Button1Mask == Button1MotionMask && \ + Button2Mask == Button2MotionMask && \ + Button3Mask == Button3MotionMask && \ + Button4Mask == Button4MotionMask && \ + Button5Mask == Button5MotionMask + // This little trick only works if Button#MotionMasks align with + // the Button#Masks. + event_mask = mov_event.state & + (Button1MotionMask | Button2MotionMask | + Button2MotionMask | Button3MotionMask | Button5MotionMask); + #else + // Fallback to some slightly larger... + if (event->state & Button1Mask) { + event_mask |= Button1MotionMask; + } + + if (event->state & Button2Mask) { + event_mask |= Button2MotionMask; + } + + if (event->state & Button3Mask) { + event_mask |= Button3MotionMask; + } + + if (event->state & Button4Mask) { + event_mask |= Button4MotionMask; + } + + if (event->state & Button5Mask) { + event_mask |= Button5MotionMask; + } + #endif + } + + // NOTE x_mask = NoEventMask. + XSendEvent(properties_disp, InputFocus, False, event_mask, (XEvent *) &mov_event); + #endif +} + +IOHOOK_API void hook_post_event(iohook_event * const event) { + XLockDisplay(properties_disp); + + #ifdef USE_XTEST + // XTest does not have modifier support, so we fake it by depressing the + // appropriate modifier keys. + unsigned int i; + for (i = 0; i < sizeof(keymask_lookup) / sizeof(KeySym); i++) { + if (event->mask & 1 << i) { + XTestFakeKeyEvent(properties_disp, XKeysymToKeycode(properties_disp, keymask_lookup[i]), True, 0); + } + } + + unsigned int i; + for (i = 0; i < sizeof(btnmask_lookup) / sizeof(unsigned int); i++) { + if (event->mask & btnmask_lookup[i]) { + XTestFakeButtonEvent(properties_disp, i + 1, True, 0); + } + } + #endif + + switch (event->type) { + case EVENT_KEY_PRESSED: + case EVENT_KEY_RELEASED: + case EVENT_KEY_TYPED: + post_key_event(event); + break; + + case EVENT_MOUSE_PRESSED: + case EVENT_MOUSE_RELEASED: + case EVENT_MOUSE_WHEEL: + case EVENT_MOUSE_CLICKED: + post_mouse_button_event(event); + break; + + case EVENT_MOUSE_DRAGGED: + case EVENT_MOUSE_MOVED: + post_mouse_motion_event(event); + break; + + case EVENT_HOOK_ENABLED: + case EVENT_HOOK_DISABLED: + // Ignore hook enabled / disabled events. + + default: + // Ignore any other garbage. + logger(LOG_LEVEL_WARN, "%s [%u]: Ignoring post event type %#X\n", + __FUNCTION__, __LINE__, event->type); + break; + } + + #ifdef USE_XTEST + // Release the previously held modifier keys used to fake the event mask. + unsigned int i ; + for (i= 0; i < sizeof(keymask_lookup) / sizeof(KeySym); i++) { + if (event->mask & 1 << i) { + XTestFakeKeyEvent(properties_disp, XKeysymToKeycode(properties_disp, keymask_lookup[i]), False, 0); + } + } + unsigned int i; + for (i = 0; i < sizeof(btnmask_lookup) / sizeof(unsigned int); i++) { + if (event->mask & btnmask_lookup[i]) { + XTestFakeButtonEvent(properties_disp, i + 1, False, 0); + } + } + #endif + + // Don't forget to flush! + XSync(properties_disp, True); + XUnlockDisplay(properties_disp); +} diff --git a/hook/x11/hook_c.h b/hook/x11/hook_c.h new file mode 100644 index 0000000..11d1451 --- /dev/null +++ b/hook/x11/hook_c.h @@ -0,0 +1,1106 @@ +/* Copyright (C) 2006-2017 Alexander Barker. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. +*/ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#define USE_XKB 0 +#define USE_XKBCOMMON 0 +#include +#include +#ifdef USE_XRECORD_ASYNC + #include +#endif +#include + +#include +#include +#include +#include +// #ifdef USE_XKB +#include +#include +// #endif +#if defined(USE_XINERAMA) && !defined(USE_XRANDR) + #include +#elif defined(USE_XRANDR) + #include +#else +// TODO We may need to fallback to the xf86vm extension for things like TwinView. +// #pragma message("*** Warning: Xinerama or XRandR support is required to produce cross-platform mouse coordinates for multi-head configurations!") +// #pragma message("... Assuming single-head display.") +#endif + +#include "../iohook.h" +// #include "../logger.h" +#include "input.h" + +// Thread and hook handles. +#ifdef USE_XRECORD_ASYNC +static bool running; + +static pthread_cond_t hook_xrecord_cond = PTHREAD_COND_INITIALIZER; +static pthread_mutex_t hook_xrecord_mutex = PTHREAD_MUTEX_INITIALIZER; +#endif + +typedef struct _hook_info { + struct _data { + Display *display; + XRecordRange *range; + } data; + struct _ctrl { + Display *display; + XRecordContext context; + } ctrl; + struct _input { + #ifdef USE_XKBCOMMON + xcb_connection_t *connection; + struct xkb_context *context; + #endif + uint16_t mask; + struct _mouse { + bool is_dragged; + struct _click { + unsigned short int count; + long int time; + unsigned short int button; + } click; + } mouse; + } input; +} hook_info; +static hook_info *hook; + +// For this struct, refer to libxnee, requires Xlibint.h +typedef union { + unsigned char type; + xEvent event; + xResourceReq req; + xGenericReply reply; + xError error; + xConnSetupPrefix setup; +} XRecordDatum; + +#if defined(USE_XKBCOMMON) +//struct xkb_keymap *keymap; +//struct xkb_state *state = xkb_state_new(keymap); +static struct xkb_state *state = NULL; +#endif + +// Virtual event pointer. +static iohook_event event; + +// Event dispatch callback. +static dispatcher_t dispatcher = NULL; + +IOHOOK_API void hook_set_dispatch_proc(dispatcher_t dispatch_proc) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: Setting new dispatch callback to %#p.\n", + __FUNCTION__, __LINE__, dispatch_proc); + + dispatcher = dispatch_proc; +} + +// Send out an event if a dispatcher was set. +static inline void dispatch_event(iohook_event *const event) { + if (dispatcher != NULL) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: Dispatching event type %u.\n", + __FUNCTION__, __LINE__, event->type); + + dispatcher(event); + } else { + logger(LOG_LEVEL_WARN, "%s [%u]: No dispatch callback set!\n", + __FUNCTION__, __LINE__); + } +} + +// Set the native modifier mask for future events. +static inline void set_modifier_mask(uint16_t mask) { + hook->input.mask |= mask; +} + +// Unset the native modifier mask for future events. +static inline void unset_modifier_mask(uint16_t mask) { + hook->input.mask &= ~mask; +} + +// Get the current native modifier mask state. +static inline uint16_t get_modifiers() { + return hook->input.mask; +} + +// Initialize the modifier lock masks. +static void initialize_locks() { + #ifdef USE_XKBCOMMON + + if (xkb_state_led_name_is_active(state, XKB_LED_NAME_CAPS)) { + set_modifier_mask(MASK_CAPS_LOCK); + } else { + unset_modifier_mask(MASK_CAPS_LOCK); + } + + if (xkb_state_led_name_is_active(state, XKB_LED_NAME_NUM)) { + set_modifier_mask(MASK_NUM_LOCK); + } else { + unset_modifier_mask(MASK_NUM_LOCK); + } + + if (xkb_state_led_name_is_active(state, XKB_LED_NAME_SCROLL)) { + set_modifier_mask(MASK_SCROLL_LOCK); + } else { + unset_modifier_mask(MASK_SCROLL_LOCK); + } + #else + unsigned int led_mask = 0x00; + if (XkbGetIndicatorState(hook->ctrl.display, XkbUseCoreKbd, &led_mask) == Success) { + if (led_mask & 0x01) { + set_modifier_mask(MASK_CAPS_LOCK); + } else { + unset_modifier_mask(MASK_CAPS_LOCK); + } + + if (led_mask & 0x02) { + set_modifier_mask(MASK_NUM_LOCK); + } else { + unset_modifier_mask(MASK_NUM_LOCK); + } + + if (led_mask & 0x04) { + set_modifier_mask(MASK_SCROLL_LOCK); + } else { + unset_modifier_mask(MASK_SCROLL_LOCK); + } + } else { + logger(LOG_LEVEL_WARN, "%s [%u]: XkbGetIndicatorState failed to get current led mask!\n", + __FUNCTION__, __LINE__); + } + #endif +} + +// Initialize the modifier mask to the current modifiers. +static void initialize_modifiers() { + hook->input.mask = 0x0000; + + KeyCode keycode; + char keymap[32]; + XQueryKeymap(hook->ctrl.display, keymap); + + 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)) { + if (mask & ShiftMask) { + keycode = XKeysymToKeycode(hook->ctrl.display, XK_Shift_L); + if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_SHIFT_L); } + keycode = XKeysymToKeycode(hook->ctrl.display, XK_Shift_R); + if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_SHIFT_R); } + } + if (mask & ControlMask) { + keycode = XKeysymToKeycode(hook->ctrl.display, XK_Control_L); + if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_CTRL_L); } + keycode = XKeysymToKeycode(hook->ctrl.display, XK_Control_R); + if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_CTRL_R); } + } + if (mask & Mod1Mask) { + keycode = XKeysymToKeycode(hook->ctrl.display, XK_Alt_L); + if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_ALT_L); } + keycode = XKeysymToKeycode(hook->ctrl.display, XK_Alt_R); + if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_ALT_R); } + } + if (mask & Mod4Mask) { + keycode = XKeysymToKeycode(hook->ctrl.display, XK_Super_L); + if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_META_L); } + keycode = XKeysymToKeycode(hook->ctrl.display, XK_Super_R); + if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_META_R); } + } + + if (mask & Button1Mask) { set_modifier_mask(MASK_BUTTON1); } + if (mask & Button2Mask) { set_modifier_mask(MASK_BUTTON2); } + if (mask & Button3Mask) { set_modifier_mask(MASK_BUTTON3); } + if (mask & Button4Mask) { set_modifier_mask(MASK_BUTTON4); } + if (mask & Button5Mask) { set_modifier_mask(MASK_BUTTON5); } + } else { + logger(LOG_LEVEL_WARN, "%s [%u]: XQueryPointer failed to get current modifiers!\n", + __FUNCTION__, __LINE__); + + keycode = XKeysymToKeycode(hook->ctrl.display, XK_Shift_L); + if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_SHIFT_L); } + keycode = XKeysymToKeycode(hook->ctrl.display, XK_Shift_R); + if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_SHIFT_R); } + keycode = XKeysymToKeycode(hook->ctrl.display, XK_Control_L); + if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_CTRL_L); } + keycode = XKeysymToKeycode(hook->ctrl.display, XK_Control_R); + if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_CTRL_R); } + keycode = XKeysymToKeycode(hook->ctrl.display, XK_Alt_L); + if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_ALT_L); } + keycode = XKeysymToKeycode(hook->ctrl.display, XK_Alt_R); + if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_ALT_R); } + keycode = XKeysymToKeycode(hook->ctrl.display, XK_Super_L); + if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_META_L); } + keycode = XKeysymToKeycode(hook->ctrl.display, XK_Super_R); + if (keymap[keycode / 8] & (1 << (keycode % 8))) { set_modifier_mask(MASK_META_R); } + } + + initialize_locks(); +} + +void hook_event_proc(XPointer closeure, XRecordInterceptData *recorded_data) { + uint64_t timestamp = (uint64_t) recorded_data->server_time; + + if (recorded_data->category == XRecordStartOfData) { + // Populate the hook start event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_HOOK_ENABLED; + event.mask = 0x00; + + // Fire the hook start event. + dispatch_event(&event); + } else if (recorded_data->category == XRecordEndOfData) { + // Populate the hook stop event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_HOOK_DISABLED; + event.mask = 0x00; + + // Fire the hook stop event. + dispatch_event(&event); + } else if (recorded_data->category == XRecordFromServer || recorded_data->category == XRecordFromClient) { + // Get XRecord data. + XRecordDatum *data = (XRecordDatum *) recorded_data->data; + + if (data->type == KeyPress) { + // The X11 KeyCode associated with this event. + KeyCode keycode = (KeyCode) data->event.u.u.detail; + KeySym keysym = 0x00; + #if defined(USE_XKBCOMMON) + if (state != NULL) { + keysym = xkb_state_key_get_one_sym(state, keycode); + } + #else + keysym = keycode_to_keysym(keycode, data->event.u.keyButtonPointer.state); + #endif + + unsigned short int scancode = keycode_to_scancode(keycode); + + // TODO If you have a better suggestion for this ugly, let me know. + if (scancode == VC_SHIFT_L) { set_modifier_mask(MASK_SHIFT_L); } + else if (scancode == VC_SHIFT_R) { set_modifier_mask(MASK_SHIFT_R); } + else if (scancode == VC_CONTROL_L) { set_modifier_mask(MASK_CTRL_L); } + else if (scancode == VC_CONTROL_R) { set_modifier_mask(MASK_CTRL_R); } + else if (scancode == VC_ALT_L) { set_modifier_mask(MASK_ALT_L); } + else if (scancode == VC_ALT_R) { set_modifier_mask(MASK_ALT_R); } + else if (scancode == VC_META_L) { set_modifier_mask(MASK_META_L); } + else if (scancode == VC_META_R) { set_modifier_mask(MASK_META_R); } + xkb_state_update_key(state, keycode, XKB_KEY_DOWN); + initialize_locks(); + + if ((get_modifiers() & MASK_NUM_LOCK) == 0) { + switch (scancode) { + case VC_KP_SEPARATOR: + case VC_KP_1: + case VC_KP_2: + case VC_KP_3: + case VC_KP_4: + case VC_KP_5: + case VC_KP_6: + case VC_KP_7: + case VC_KP_8: + case VC_KP_0: + case VC_KP_9: + scancode |= 0xEE00; + break; + } + } + + // Populate key pressed event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_KEY_PRESSED; + event.mask = get_modifiers(); + + event.data.keyboard.keycode = scancode; + event.data.keyboard.rawcode = keysym; + event.data.keyboard.keychar = CHAR_UNDEFINED; + + logger(LOG_LEVEL_INFO, "%s [%u]: Key %#X pressed. (%#X)\n", + __FUNCTION__, __LINE__, event.data.keyboard.keycode, event.data.keyboard.rawcode); + + // Fire key pressed event. + dispatch_event(&event); + + // If the pressed event was not consumed... + if (event.reserved ^ 0x01) { + uint16_t buffer[2]; + size_t count = 0; + + // Check to make sure the key is printable. + #ifdef USE_XKBCOMMON + if (state != NULL) { + count = keycode_to_unicode(state, keycode, buffer, sizeof(buffer) / sizeof(uint16_t)); + } + #else + count = keysym_to_unicode(keysym, buffer, sizeof(buffer) / sizeof(uint16_t)); + #endif + + unsigned int i; + for (i = 0; i < count; i++) { + // Populate key typed event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_KEY_TYPED; + event.mask = get_modifiers(); + + event.data.keyboard.keycode = VC_UNDEFINED; + event.data.keyboard.rawcode = keysym; + event.data.keyboard.keychar = buffer[i]; + + logger(LOG_LEVEL_INFO, "%s [%u]: Key %#X typed. (%lc)\n", + __FUNCTION__, __LINE__, event.data.keyboard.keycode, (uint16_t) event.data.keyboard.keychar); + + // Fire key typed event. + dispatch_event(&event); + } + } + } else if (data->type == KeyRelease) { + // The X11 KeyCode associated with this event. + KeyCode keycode = (KeyCode) data->event.u.u.detail; + KeySym keysym = 0x00; + #ifdef USE_XKBCOMMON + if (state != NULL) { + keysym = xkb_state_key_get_one_sym(state, keycode); + } + #else + keysym = keycode_to_keysym(keycode, data->event.u.keyButtonPointer.state); + #endif + + unsigned short int scancode = keycode_to_scancode(keycode); + + // TODO If you have a better suggestion for this ugly, let me know. + if (scancode == VC_SHIFT_L) { unset_modifier_mask(MASK_SHIFT_L); } + else if (scancode == VC_SHIFT_R) { unset_modifier_mask(MASK_SHIFT_R); } + else if (scancode == VC_CONTROL_L) { unset_modifier_mask(MASK_CTRL_L); } + else if (scancode == VC_CONTROL_R) { unset_modifier_mask(MASK_CTRL_R); } + else if (scancode == VC_ALT_L) { unset_modifier_mask(MASK_ALT_L); } + else if (scancode == VC_ALT_R) { unset_modifier_mask(MASK_ALT_R); } + else if (scancode == VC_META_L) { unset_modifier_mask(MASK_META_L); } + else if (scancode == VC_META_R) { unset_modifier_mask(MASK_META_R); } + xkb_state_update_key(state, keycode, XKB_KEY_UP); + initialize_locks(); + + if ((get_modifiers() & MASK_NUM_LOCK) == 0) { + switch (scancode) { + case VC_KP_SEPARATOR: + case VC_KP_1: + case VC_KP_2: + case VC_KP_3: + case VC_KP_4: + case VC_KP_5: + case VC_KP_6: + case VC_KP_7: + case VC_KP_8: + case VC_KP_0: + case VC_KP_9: + scancode |= 0xEE00; + break; + } + } + + // Populate key released event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_KEY_RELEASED; + event.mask = get_modifiers(); + + event.data.keyboard.keycode = scancode; + event.data.keyboard.rawcode = keysym; + event.data.keyboard.keychar = CHAR_UNDEFINED; + + logger(LOG_LEVEL_INFO, "%s [%u]: Key %#X released. (%#X)\n", + __FUNCTION__, __LINE__, event.data.keyboard.keycode, event.data.keyboard.rawcode); + + // Fire key released event. + dispatch_event(&event); + } else if (data->type == ButtonPress) { + // X11 handles wheel events as button events. + if (data->event.u.u.detail == WheelUp || data->event.u.u.detail == WheelDown + || data->event.u.u.detail == WheelLeft || data->event.u.u.detail == WheelRight) { + + // Reset the click count and previous button. + hook->input.mouse.click.count = 1; + hook->input.mouse.click.button = MOUSE_NOBUTTON; + + /* Scroll wheel release events. + * Scroll type: WHEEL_UNIT_SCROLL + * Scroll amount: 3 unit increments per notch + * Units to scroll: 3 unit increments + * Vertical unit increment: 15 pixels + */ + + // Populate mouse wheel event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_MOUSE_WHEEL; + event.mask = get_modifiers(); + + event.data.wheel.clicks = hook->input.mouse.click.count; + event.data.wheel.x = data->event.u.keyButtonPointer.rootX; + event.data.wheel.y = data->event.u.keyButtonPointer.rootY; + + #if defined(USE_XINERAMA) || defined(USE_XRANDR) + uint8_t count; + screen_data *screens = hook_create_screen_info(&count); + if (count > 1) { + event.data.wheel.x -= screens[0].x; + event.data.wheel.y -= screens[0].y; + } + + if (screens != NULL) { + free(screens); + } + #endif + + /* X11 does not have an API call for acquiring the mouse scroll type. This + * maybe part of the XInput2 (XI2) extention but I will wont know until it + * is available on my platform. For the time being we will just use the + * unit scroll value. + */ + event.data.wheel.type = WHEEL_UNIT_SCROLL; + + /* Some scroll wheel properties are available via the new XInput2 (XI2) + * extension. Unfortunately the extension is not available on my + * development platform at this time. For the time being we will just + * use the Windows default value of 3. + */ + event.data.wheel.amount = 3; + + if (data->event.u.u.detail == WheelUp || data->event.u.u.detail == WheelLeft) { + // Wheel Rotated Up and Away. + event.data.wheel.rotation = -1; + } else { // data->event.u.u.detail == WheelDown + // Wheel Rotated Down and Towards. + event.data.wheel.rotation = 1; + } + + if (data->event.u.u.detail == WheelUp || data->event.u.u.detail == WheelDown) { + // Wheel Rotated Up or Down. + event.data.wheel.direction = WHEEL_VERTICAL_DIRECTION; + } else { // data->event.u.u.detail == WheelLeft || data->event.u.u.detail == WheelRight + // Wheel Rotated Left or Right. + event.data.wheel.direction = WHEEL_HORIZONTAL_DIRECTION; + } + + logger(LOG_LEVEL_INFO, "%s [%u]: Mouse wheel type %u, rotated %i units in the %u direction at %u, %u.\n", + __FUNCTION__, __LINE__, event.data.wheel.type, + event.data.wheel.amount * event.data.wheel.rotation, + event.data.wheel.direction, + event.data.wheel.x, event.data.wheel.y); + + // Fire mouse wheel event. + dispatch_event(&event); + } else { + /* This information is all static for X11, its up to the WM to + * decide how to interpret the wheel events. + */ + uint16_t button = MOUSE_NOBUTTON; + switch (data->event.u.u.detail) { + // FIXME This should use a lookup table to handle button remapping. + case Button1: + button = MOUSE_BUTTON1; + set_modifier_mask(MASK_BUTTON1); + break; + + case Button2: + button = MOUSE_BUTTON2; + set_modifier_mask(MASK_BUTTON2); + break; + + case Button3: + button = MOUSE_BUTTON3; + set_modifier_mask(MASK_BUTTON3); + break; + + case XButton1: + button = MOUSE_BUTTON4; + set_modifier_mask(MASK_BUTTON5); + break; + + case XButton2: + button = MOUSE_BUTTON5; + set_modifier_mask(MASK_BUTTON5); + break; + + default: + // Do not set modifier masks past button MASK_BUTTON5. + break; + } + + + // Track the number of clicks, the button must match the previous button. + if (button == hook->input.mouse.click.button && (long int) (timestamp - hook->input.mouse.click.time) <= hook_get_multi_click_time()) { + if (hook->input.mouse.click.count < USHRT_MAX) { + hook->input.mouse.click.count++; + } else { + logger(LOG_LEVEL_WARN, "%s [%u]: Click count overflow detected!\n", + __FUNCTION__, __LINE__); + } + } else { + // Reset the click count. + hook->input.mouse.click.count = 1; + + // Set the previous button. + hook->input.mouse.click.button = button; + } + + // Save this events time to calculate the hook->input.mouse.click.count. + hook->input.mouse.click.time = timestamp; + + + // Populate mouse pressed event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_MOUSE_PRESSED; + event.mask = get_modifiers(); + + event.data.mouse.button = button; + event.data.mouse.clicks = hook->input.mouse.click.count; + event.data.mouse.x = data->event.u.keyButtonPointer.rootX; + event.data.mouse.y = data->event.u.keyButtonPointer.rootY; + + #if defined(USE_XINERAMA) || defined(USE_XRANDR) + uint8_t count; + screen_data *screens = hook_create_screen_info(&count); + if (count > 1) { + event.data.mouse.x -= screens[0].x; + event.data.mouse.y -= screens[0].y; + } + + if (screens != NULL) { + free(screens); + } + #endif + + logger(LOG_LEVEL_INFO, "%s [%u]: Button %u pressed %u time(s). (%u, %u)\n", + __FUNCTION__, __LINE__, event.data.mouse.button, event.data.mouse.clicks, + event.data.mouse.x, event.data.mouse.y); + + // Fire mouse pressed event. + dispatch_event(&event); + } + } else if (data->type == ButtonRelease) { + // X11 handles wheel events as button events. + if (data->event.u.u.detail != WheelUp && data->event.u.u.detail != WheelDown) { + /* This information is all static for X11, its up to the WM to + * decide how to interpret the wheel events. + */ + uint16_t button = MOUSE_NOBUTTON; + switch (data->event.u.u.detail) { + // FIXME This should use a lookup table to handle button remapping. + case Button1: + button = MOUSE_BUTTON1; + unset_modifier_mask(MASK_BUTTON1); + break; + + case Button2: + button = MOUSE_BUTTON2; + unset_modifier_mask(MASK_BUTTON2); + break; + + case Button3: + button = MOUSE_BUTTON3; + unset_modifier_mask(MASK_BUTTON3); + break; + + case XButton1: + button = MOUSE_BUTTON4; + unset_modifier_mask(MASK_BUTTON5); + break; + + case XButton2: + button = MOUSE_BUTTON5; + unset_modifier_mask(MASK_BUTTON5); + break; + + default: + // Do not set modifier masks past button MASK_BUTTON5. + break; + } + + // Populate mouse released event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_MOUSE_RELEASED; + event.mask = get_modifiers(); + + event.data.mouse.button = button; + event.data.mouse.clicks = hook->input.mouse.click.count; + event.data.mouse.x = data->event.u.keyButtonPointer.rootX; + event.data.mouse.y = data->event.u.keyButtonPointer.rootY; + + #if defined(USE_XINERAMA) || defined(USE_XRANDR) + uint8_t count; + screen_data *screens = hook_create_screen_info(&count); + if (count > 1) { + event.data.mouse.x -= screens[0].x; + event.data.mouse.y -= screens[0].y; + } + + if (screens != NULL) { + free(screens); + } + #endif + + logger(LOG_LEVEL_INFO, "%s [%u]: Button %u released %u time(s). (%u, %u)\n", + __FUNCTION__, __LINE__, event.data.mouse.button, + event.data.mouse.clicks, + event.data.mouse.x, event.data.mouse.y); + + // Fire mouse released event. + dispatch_event(&event); + + // If the pressed event was not consumed... + if (event.reserved ^ 0x01 && hook->input.mouse.is_dragged != true) { + // Populate mouse clicked event. + event.time = timestamp; + event.reserved = 0x00; + + event.type = EVENT_MOUSE_CLICKED; + event.mask = get_modifiers(); + + event.data.mouse.button = button; + event.data.mouse.clicks = hook->input.mouse.click.count; + event.data.mouse.x = data->event.u.keyButtonPointer.rootX; + event.data.mouse.y = data->event.u.keyButtonPointer.rootY; + + #if defined(USE_XINERAMA) || defined(USE_XRANDR) + uint8_t count; + screen_data *screens = hook_create_screen_info(&count); + if (count > 1) { + event.data.mouse.x -= screens[0].x; + event.data.mouse.y -= screens[0].y; + } + + if (screens != NULL) { + free(screens); + } + #endif + + logger(LOG_LEVEL_INFO, "%s [%u]: Button %u clicked %u time(s). (%u, %u)\n", + __FUNCTION__, __LINE__, event.data.mouse.button, + event.data.mouse.clicks, + event.data.mouse.x, event.data.mouse.y); + + // Fire mouse clicked event. + dispatch_event(&event); + } + + // Reset the number of clicks. + if (button == hook->input.mouse.click.button && (long int) (event.time - hook->input.mouse.click.time) > hook_get_multi_click_time()) { + // Reset the click count. + hook->input.mouse.click.count = 0; + } + } + } else if (data->type == MotionNotify) { + // Reset the click count. + if (hook->input.mouse.click.count != 0 && (long int) (timestamp - hook->input.mouse.click.time) > hook_get_multi_click_time()) { + hook->input.mouse.click.count = 0; + } + + // Populate mouse move event. + event.time = timestamp; + event.reserved = 0x00; + + event.mask = get_modifiers(); + + // Check the upper half of virtual modifiers for non-zero + // values and set the mouse dragged flag. + hook->input.mouse.is_dragged = ((event.mask & 0x1F00) > 0); + if (hook->input.mouse.is_dragged) { + // Create Mouse Dragged event. + event.type = EVENT_MOUSE_DRAGGED; + } else { + // Create a Mouse Moved event. + event.type = EVENT_MOUSE_MOVED; + } + + event.data.mouse.button = MOUSE_NOBUTTON; + event.data.mouse.clicks = hook->input.mouse.click.count; + event.data.mouse.x = data->event.u.keyButtonPointer.rootX; + event.data.mouse.y = data->event.u.keyButtonPointer.rootY; + + #if defined(USE_XINERAMA) || defined(USE_XRANDR) + uint8_t count; + screen_data *screens = hook_create_screen_info(&count); + if (count > 1) { + event.data.mouse.x -= screens[0].x; + event.data.mouse.y -= screens[0].y; + } + + if (screens != NULL) { + free(screens); + } + #endif + + logger(LOG_LEVEL_INFO, "%s [%u]: Mouse %s to %i, %i. (%#X)\n", + __FUNCTION__, __LINE__, hook->input.mouse.is_dragged ? "dragged" : "moved", + event.data.mouse.x, event.data.mouse.y, event.mask); + + // Fire mouse move event. + dispatch_event(&event); + } else { + // In theory this *should* never execute. + logger(LOG_LEVEL_DEBUG, "%s [%u]: Unhandled X11 event: %#X.\n", + __FUNCTION__, __LINE__, (unsigned int) data->type); + } + } else { + logger(LOG_LEVEL_WARN, "%s [%u]: Unhandled X11 hook category! (%#X)\n", + __FUNCTION__, __LINE__, recorded_data->category); + } + + // TODO There is no way to consume the XRecord event. + + XRecordFreeData(recorded_data); +} + + +static inline bool enable_key_repeate() { + // Attempt to setup detectable autorepeat. + // NOTE: is_auto_repeat is NOT stdbool! + Bool is_auto_repeat = False; + #ifdef USE_XKB + // Enable detectable auto-repeat. + XkbSetDetectableAutoRepeat(hook->ctrl.display, True, &is_auto_repeat); + #else + XAutoRepeatOn(hook->ctrl.display); + + XKeyboardState kb_state; + XGetKeyboardControl(hook->ctrl.display, &kb_state); + + is_auto_repeat = (kb_state.global_auto_repeat == AutoRepeatModeOn); + #endif + + return is_auto_repeat; +} + + +static inline int xrecord_block() { + int status = IOHOOK_FAILURE; + + // Save the data display associated with this hook so it is passed to each event. + //XPointer closeure = (XPointer) (ctrl_display); + XPointer closeure = NULL; + + #ifdef USE_XRECORD_ASYNC + // Async requires that we loop so that our thread does not return. + if (XRecordEnableContextAsync(hook->data.display, context, hook_event_proc, closeure) != 0) { + // Time in MS to sleep the runloop. + int timesleep = 100; + + // Allow the thread loop to block. + pthread_mutex_lock(&hook_xrecord_mutex); + running = true; + + do { + // Unlock the mutex from the previous iteration. + pthread_mutex_unlock(&hook_xrecord_mutex); + + XRecordProcessReplies(hook->data.display); + + // Prevent 100% CPU utilization. + struct timeval tv; + gettimeofday(&tv, NULL); + + struct timespec ts; + ts.tv_sec = time(NULL) + timesleep / 1000; + ts.tv_nsec = tv.tv_usec * 1000 + 1000 * 1000 * (timesleep % 1000); + ts.tv_sec += ts.tv_nsec / (1000 * 1000 * 1000); + ts.tv_nsec %= (1000 * 1000 * 1000); + + pthread_mutex_lock(&hook_xrecord_mutex); + pthread_cond_timedwait(&hook_xrecord_cond, &hook_xrecord_mutex, &ts); + } while (running); + + // Unlock after loop exit. + pthread_mutex_unlock(&hook_xrecord_mutex); + + // Set the exit status. + status = NULL; + } + #else + // Sync blocks until XRecordDisableContext() is called. + if (XRecordEnableContext(hook->data.display, hook->ctrl.context, hook_event_proc, closeure) != 0) { + status = IOHOOK_SUCCESS; + } + #endif + else { + logger(LOG_LEVEL_ERROR, "%s [%u]: XRecordEnableContext failure!\n", + __FUNCTION__, __LINE__); + + #ifdef USE_XRECORD_ASYNC + // Reset the running state. + pthread_mutex_lock(&hook_xrecord_mutex); + running = false; + pthread_mutex_unlock(&hook_xrecord_mutex); + #endif + + // Set the exit status. + status = IOHOOK_ERROR_X_RECORD_ENABLE_CONTEXT; + } + + return status; +} + +static int xrecord_alloc() { + int status = IOHOOK_FAILURE; + + // Make sure the data display is synchronized to prevent late event delivery! + // See Bug 42356 for more information. + // https://bugs.freedesktop.org/show_bug.cgi?id=42356#c4 + XSynchronize(hook->data.display, True); + + // Setup XRecord range. + XRecordClientSpec clients = XRecordAllClients; + + hook->data.range = XRecordAllocRange(); + if (hook->data.range != NULL) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: XRecordAllocRange successful.\n", + __FUNCTION__, __LINE__); + + hook->data.range->device_events.first = KeyPress; + hook->data.range->device_events.last = MotionNotify; + + // Note that the documentation for this function is incorrect, + // hook->data.display should be used! + // See: http://www.x.org/releases/X11R7.6/doc/libXtst/recordlib.txt + hook->ctrl.context = XRecordCreateContext(hook->data.display, XRecordFromServerTime, &clients, 1, &hook->data.range, 1); + if (hook->ctrl.context != 0) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: XRecordCreateContext successful.\n", + __FUNCTION__, __LINE__); + + // Block until hook_stop() is called. + status = xrecord_block(); + + // Free up the context if it was set. + XRecordFreeContext(hook->data.display, hook->ctrl.context); + } else { + logger(LOG_LEVEL_ERROR, "%s [%u]: XRecordCreateContext failure!\n", + __FUNCTION__, __LINE__); + + // Set the exit status. + status = IOHOOK_ERROR_X_RECORD_CREATE_CONTEXT; + } + + // Free the XRecord range. + XFree(hook->data.range); + } else { + logger(LOG_LEVEL_ERROR, "%s [%u]: XRecordAllocRange failure!\n", + __FUNCTION__, __LINE__); + + // Set the exit status. + status = IOHOOK_ERROR_X_RECORD_ALLOC_RANGE; + } + + return status; +} + +static int xrecord_query() { + int status = IOHOOK_FAILURE; + + // Check to make sure XRecord is installed and enabled. + int major, minor; + if (XRecordQueryVersion(hook->ctrl.display, &major, &minor) != 0) { + logger(LOG_LEVEL_INFO, "%s [%u]: XRecord version: %i.%i.\n", + __FUNCTION__, __LINE__, major, minor); + + status = xrecord_alloc(); + } else { + logger(LOG_LEVEL_ERROR, "%s [%u]: XRecord is not currently available!\n", + __FUNCTION__, __LINE__); + + status = IOHOOK_ERROR_X_RECORD_NOT_FOUND; + } + + return status; +} + +static int xrecord_start() { + int status = IOHOOK_FAILURE; + + // Open the control display for XRecord. + hook->ctrl.display = XOpenDisplay(NULL); + + // Open a data display for XRecord. + // NOTE This display must be opened on the same thread as XRecord. + hook->data.display = XOpenDisplay(NULL); + if (hook->ctrl.display != NULL && hook->data.display != NULL) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: XOpenDisplay successful.\n", + __FUNCTION__, __LINE__); + + bool is_auto_repeat = enable_key_repeate(); + if (is_auto_repeat) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: Successfully enabled detectable autorepeat.\n", + __FUNCTION__, __LINE__); + } else { + logger(LOG_LEVEL_WARN, "%s [%u]: Could not enable detectable auto-repeat!\n", + __FUNCTION__, __LINE__); + } + + #if defined(USE_XKBCOMMON) + // Open XCB Connection + hook->input.connection = XGetXCBConnection(hook->ctrl.display); + int xcb_status = xcb_connection_has_error(hook->input.connection); + if (xcb_status <= 0) { + // Initialize xkbcommon context. + struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + + if (context != NULL) { + hook->input.context = xkb_context_ref(context); + } else { + logger(LOG_LEVEL_ERROR, "%s [%u]: xkb_context_new failure!\n", + __FUNCTION__, __LINE__); + } + } else { + logger(LOG_LEVEL_ERROR, "%s [%u]: xcb_connect failure! (%d)\n", + __FUNCTION__, __LINE__, xcb_status); + } + #endif + + #ifdef USE_XKBCOMMON + state = create_xkb_state(hook->input.context, hook->input.connection); + #endif + + // Initialize starting modifiers. + initialize_modifiers(); + + status = xrecord_query(); + + #ifdef USE_XKBCOMMON + if (state != NULL) { + destroy_xkb_state(state); + } + + if (hook->input.context != NULL) { + xkb_context_unref(hook->input.context); + hook->input.context = NULL; + } + + if (hook->input.connection != NULL) { + // xcb_disconnect(hook->input.connection); + hook->input.connection = NULL; + } + #endif + } else { + logger(LOG_LEVEL_ERROR, "%s [%u]: XOpenDisplay failure!\n", + __FUNCTION__, __LINE__); + + status = IOHOOK_ERROR_X_OPEN_DISPLAY; + } + + // Close down the XRecord data display. + if (hook->data.display != NULL) { + XCloseDisplay(hook->data.display); + hook->data.display = NULL; + } + + // Close down the XRecord control display. + if (hook->ctrl.display) { + XCloseDisplay(hook->ctrl.display); + hook->ctrl.display = NULL; + } + + return status; +} + +IOHOOK_API int hook_run() { + int status = IOHOOK_FAILURE; + + // Hook data for future cleanup. + hook = malloc(sizeof(hook_info)); + if (hook != NULL) { + hook->input.mask = 0x0000; + hook->input.mouse.is_dragged = false; + hook->input.mouse.click.count = 0; + hook->input.mouse.click.time = 0; + hook->input.mouse.click.button = MOUSE_NOBUTTON; + + status = xrecord_start(); + + // Free data associated with this hook. + free(hook); + hook = NULL; + } else { + logger(LOG_LEVEL_ERROR, "%s [%u]: Failed to allocate memory for hook structure!\n", + __FUNCTION__, __LINE__); + + status = IOHOOK_ERROR_OUT_OF_MEMORY; + } + + logger(LOG_LEVEL_DEBUG, "%s [%u]: Something, something, something, complete.\n", + __FUNCTION__, __LINE__); + + return status; +} + +IOHOOK_API int hook_stop() { + int status = IOHOOK_FAILURE; + + if (hook != NULL && hook->ctrl.display != NULL && hook->ctrl.context != 0) { + // We need to make sure the context is still valid. + XRecordState *state = malloc(sizeof(XRecordState)); + if (state != NULL) { + if (XRecordGetContext(hook->ctrl.display, hook->ctrl.context, &state) != 0) { + // Try to exit the thread naturally. + if (state->enabled && XRecordDisableContext(hook->ctrl.display, hook->ctrl.context) != 0) { + #ifdef USE_XRECORD_ASYNC + pthread_mutex_lock(&hook_xrecord_mutex); + running = false; + pthread_cond_signal(&hook_xrecord_cond); + pthread_mutex_unlock(&hook_xrecord_mutex); + #endif + + // See Bug 42356 for more information. + // https://bugs.freedesktop.org/show_bug.cgi?id=42356#c4 + //XFlush(hook->ctrl.display); + XSync(hook->ctrl.display, False); + if (hook->ctrl.display) { + XCloseDisplay(hook->ctrl.display); + hook->ctrl.display = NULL; + } + + status = IOHOOK_SUCCESS; + } + } else { + logger(LOG_LEVEL_ERROR, "%s [%u]: XRecordGetContext failure!\n", + __FUNCTION__, __LINE__); + + status = IOHOOK_ERROR_X_RECORD_GET_CONTEXT; + } + + free(state); + } else { + logger(LOG_LEVEL_ERROR, "%s [%u]: Failed to allocate memory for XRecordState!\n", + __FUNCTION__, __LINE__); + + status = IOHOOK_ERROR_OUT_OF_MEMORY; + } + + return status; + } + + logger(LOG_LEVEL_DEBUG, "%s [%u]: Status: %#X.\n", + __FUNCTION__, __LINE__, status); + + return status; +} diff --git a/hook/x11/input.h b/hook/x11/input.h new file mode 100644 index 0000000..49940de --- /dev/null +++ b/hook/x11/input.h @@ -0,0 +1,93 @@ +/* Copyright (C) 2006-2017 Alexander Barker. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. +*/ + +#define USE_XKBCOMMON 0 +//#define _included_input_helper 0 +#ifndef _included_input_helper +#define _included_input_helper + +#include +#include + +#ifdef USE_XKBCOMMON + #include + #include + #include +#endif + + +// Virtual button codes that are not defined by X11. +#define Button1 1 +#define Button2 2 +#define Button3 3 +#define WheelUp 4 +#define WheelDown 5 +#define WheelLeft 6 +#define WheelRight 7 +#define XButton1 8 +#define XButton2 9 + +/* Converts an X11 key symbol to a single Unicode character. No direct X11 + * functionality exists to provide this information. + */ +extern size_t keysym_to_unicode(KeySym keysym, uint16_t *buffer, size_t size); + +/* Convert a single Unicode character to an X11 key symbol. This function + * provides a better translation than XStringToKeysym() for Unicode characters. + */ +extern KeySym unicode_to_keysym(uint16_t unicode); + +/* Converts an X11 key code to the appropriate keyboard scan code. + */ +extern uint16_t keycode_to_scancode(KeyCode keycode); + +/* Converts a keyboard scan code to the appropriate X11 key code. + */ +extern KeyCode scancode_to_keycode(uint16_t scancode); + + +#ifdef USE_XKBCOMMON + +/* Converts an X11 key code to a Unicode character sequence. libXKBCommon support + * is required for this method. + */ +extern size_t keycode_to_unicode(struct xkb_state* state, KeyCode keycode, uint16_t *buffer, size_t size); + +/* Create a xkb_state structure and return a pointer to it. + */ +extern struct xkb_state * create_xkb_state(struct xkb_context *context, xcb_connection_t *connection); + +/* Release xkb_state structure created by create_xkb_state(). + */ +extern void destroy_xkb_state(struct xkb_state* state); + +#else + +/* Converts an X11 key code and event mask to the appropriate X11 key symbol. + * This functions in much the same way as XKeycodeToKeysym() but allows for a + * faster and more flexible lookup. + */ +extern KeySym keycode_to_keysym(KeyCode keycode, unsigned int modifier_mask); + +#endif + +/* Initialize items required for KeyCodeToKeySym() and KeySymToUnicode() + * functionality. This method is called by OnLibraryLoad() and may need to be + * called in combination with UnloadInputHelper() if the native keyboard layout + * is changed. + */ +extern void load_input_helper(); + +/* De-initialize items required for KeyCodeToKeySym() and KeySymToUnicode() + * functionality. This method is called by OnLibraryUnload() and may need to be + * called in combination with LoadInputHelper() if the native keyboard layout + * is changed. + */ +extern void unload_input_helper(); + +#endif diff --git a/hook/x11/input_c.h b/hook/x11/input_c.h new file mode 100644 index 0000000..84e08ee --- /dev/null +++ b/hook/x11/input_c.h @@ -0,0 +1,1958 @@ +/* Copyright (C) 2006-2017 Alexander Barker. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. +*/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define USE_XKB 0 +#define USE_XKBCOMMON 0 +#include +#include +#include +#include +#include +#include + +#ifdef USE_XKB +#ifdef USE_EVDEV + #include + static bool is_evdev = false; +#endif + +#include +static XkbDescPtr keyboard_map; +#else + #include + static KeySym *keyboard_map; + static int keysym_per_keycode; + static bool is_caps_lock = false, is_shift_lock = false; +#endif + +#ifdef USE_XKBCOMMON +#include +#include +#include + +#ifdef USE_XKBFILE +#include + +static struct xkb_rule_names xkb_names = { + .rules = "base", + .model = "us", + .layout = "pc105", + .variant = NULL, + .options = NULL +}; +#endif + +#endif + +#include "../logger_c.h" + +/* The follwoing two tables are based on QEMU's x_keymap.c, under the following + * terms: + * + * Copyright (C) 2003 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#if defined(USE_EVDEV) && defined(USE_XKB) +/* This table is generated based off the evdev -> scancode mapping above + * and the keycode mappings in the following files: + * /usr/include/linux/input.h + * /usr/share/X11/xkb/keycodes/evdev + * + * NOTE This table only works for Linux. + */ +static const uint16_t evdev_scancode_table[][2] = { + /* idx { keycode, scancode }, idx evdev code */ + /* 0 */ { VC_UNDEFINED, 0x00 }, /* 0x00 KEY_RESERVED */ + /* 1 */ { VC_UNDEFINED, 0x09 }, /* 0x01 KEY_ESC */ + /* 2 */ { VC_UNDEFINED, 0x0A }, /* 0x02 KEY_1 */ + /* 3 */ { VC_UNDEFINED, 0x0B }, /* 0x03 KEY_2 */ + /* 4 */ { VC_UNDEFINED, 0x0C }, /* 0x04 KEY_3 */ + /* 5 */ { VC_UNDEFINED, 0x0D }, /* 0x05 KEY_4 */ + /* 6 */ { VC_UNDEFINED, 0x0E }, /* 0x06 KEY_5 */ + /* 7 */ { VC_UNDEFINED, 0x0F }, /* 0x07 KEY_6 */ + /* 8 */ { VC_UNDEFINED, 0x10 }, /* 0x08 KEY_7 */ + /* 9 */ { VC_ESCAPE, 0x11 }, /* 0x09 KEY_8 */ + /* 10 */ { VC_1, 0x12 }, /* 0x0A KEY_9 */ + /* 11 */ { VC_2, 0x13 }, /* 0x0B KEY_0 */ + /* 12 */ { VC_3, 0x14 }, /* 0x0C KEY_MINUS */ + /* 13 */ { VC_4, 0x15 }, /* 0x0D KEY_EQUAL */ + /* 14 */ { VC_5, 0x16 }, /* 0x0E KEY_BACKSPACE */ + /* 15 */ { VC_6, 0x17 }, /* 0x0F KEY_TAB */ + /* 16 */ { VC_7, 0x18 }, /* 0x10 KEY_Q */ + /* 17 */ { VC_8, 0x19 }, /* 0x11 KEY_W */ + /* 18 */ { VC_9, 0x1A }, /* 0x12 KEY_E */ + /* 19 */ { VC_0, 0x1B }, /* 0x13 KEY_T */ + /* 20 */ { VC_MINUS, 0x1C }, /* 0x14 KEY_R */ + /* 21 */ { VC_EQUALS, 0x1D }, /* 0x15 KEY_Y */ + /* 22 */ { VC_BACKSPACE, 0x1E }, /* 0x16 KEY_U */ + /* 23 */ { VC_TAB, 0x1F }, /* 0x17 KEY_I */ + /* 24 */ { VC_Q, 0x20 }, /* 0x18 KEY_O */ + /* 25 */ { VC_W, 0x21 }, /* 0x19 KEY_P */ + /* 26 */ { VC_E, 0x22 }, /* 0x1A KEY_LEFTBRACE */ + /* 27 */ { VC_R, 0x23 }, /* 0x1B KEY_RIGHTBRACE */ + /* 28 */ { VC_T, 0x24 }, /* 0x1C KEY_ENTER */ + /* 29 */ { VC_Y, 0x25 }, /* 0x1D KEY_LEFTCTRL */ + /* 30 */ { VC_U, 0x26 }, /* 0x1E KEY_A */ + /* 31 */ { VC_I, 0x27 }, /* 0x1F KEY_S */ + /* 32 */ { VC_O, 0x28 }, /* 0x20 KEY_D */ + /* 33 */ { VC_P, 0x29 }, /* 0x21 KEY_F */ + /* 34 */ { VC_OPEN_BRACKET, 0x2A }, /* 0x22 KEY_G */ + /* 35 */ { VC_CLOSE_BRACKET, 0x2B }, /* 0x23 KEY_H */ + /* 36 */ { VC_ENTER, 0x2C }, /* 0x24 KEY_J */ + /* 37 */ { VC_CONTROL_L, 0x2D }, /* 0x25 KEY_K */ + /* 38 */ { VC_A, 0x2E }, /* 0x26 KEY_L */ + /* 39 */ { VC_S, 0x2F }, /* 0x27 KEY_SEMICOLON */ + /* 40 */ { VC_D, 0x30 }, /* 0x28 KEY_APOSTROPHE */ + /* 41 */ { VC_F, 0x31 }, /* 0x29 KEY_GRAVE */ + /* 42 */ { VC_G, 0x32 }, /* 0x2A KEY_LEFTSHIFT */ + /* 43 */ { VC_H, 0x33 }, /* 0x2B KEY_BACKSLASH */ + /* 44 */ { VC_J, 0x34 }, /* 0x2C KEY_Z */ + /* 45 */ { VC_K, 0x35 }, /* 0x2D KEY_X */ + /* 46 */ { VC_L, 0x36 }, /* 0x2E KEY_C */ + /* 47 */ { VC_SEMICOLON, 0x37 }, /* 0x2F KEY_V */ + /* 48 */ { VC_QUOTE, 0x38 }, /* 0x30 KEY_B */ + /* 49 */ { VC_BACKQUOTE, 0x39 }, /* 0x31 KEY_N */ + /* 50 */ { VC_SHIFT_L, 0x3A }, /* 0x32 KEY_M */ + /* 51 */ { VC_BACK_SLASH, 0x3B }, /* 0x33 KEY_COMMA */ + /* 52 */ { VC_Z, 0x3C }, /* 0x34 KEY_DOT */ + /* 53 */ { VC_X, 0x3D }, /* 0x35 KEY_SLASH */ + /* 54 */ { VC_C, 0x3E }, /* 0x36 KEY_RIGHTSHIFT */ + /* 55 */ { VC_V, 0x3F }, /* 0x37 KEY_KPASTERISK */ + /* 56 */ { VC_B, 0x40 }, /* 0x38 KEY_LEFTALT */ + /* 57 */ { VC_N, 0x41 }, /* 0x39 KEY_SPACE */ + /* 58 */ { VC_M, 0x42 }, /* 0x3A KEY_CAPSLOCK */ + /* 59 */ { VC_COMMA, 0x43 }, /* 0x3B KEY_F1 */ + /* 60 */ { VC_PERIOD, 0x44 }, /* 0x3C KEY_F2 */ + /* 61 */ { VC_SLASH, 0x45 }, /* 0x3D KEY_F3 */ + /* 62 */ { VC_SHIFT_R, 0x46 }, /* 0x3E KEY_F4 */ + /* 63 */ { VC_KP_MULTIPLY, 0x47 }, /* 0x3F KEY_F5 */ + /* 64 */ { VC_ALT_L, 0x48 }, /* 0x40 KEY_F6 */ + /* 65 */ { VC_SPACE, 0x49 }, /* 0x41 KEY_F7 */ + /* 66 */ { VC_CAPS_LOCK, 0x4A }, /* 0x42 KEY_F8 */ + /* 67 */ { VC_F1, 0x4B }, /* 0x43 KEY_F9 */ + /* 68 */ { VC_F2, 0x4C }, /* 0x44 KEY_F10 */ + /* 69 */ { VC_F3, 0x4D }, /* 0x45 KEY_NUMLOCK */ + /* 70 */ { VC_F4, 0x4E }, /* 0x46 KEY_SCROLLLOCK */ + /* 71 */ { VC_F5, 0x4F }, /* 0x47 KEY_KP7 */ + /* 72 */ { VC_F6, 0x50 }, /* 0x48 KEY_KP8 */ + /* 73 */ { VC_F7, 0x51 }, /* 0x49 KEY_KP9 */ + /* 74 */ { VC_F8, 0x52 }, /* 0x4A KEY_KPMINUS */ + /* 75 */ { VC_F9, 0x53 }, /* 0x4B KEY_KP4 */ + /* 76 */ { VC_F10, 0x54 }, /* 0x4C KEY_KP5 */ + /* 77 */ { VC_NUM_LOCK, 0x55 }, /* 0x4D KEY_KP6 */ + /* 78 */ { VC_SCROLL_LOCK, 0x56 }, /* 0x4E KEY_KPPLUS */ + /* 79 */ { VC_KP_7, 0x57 }, /* 0x4F KEY_KP1 */ + /* 80 */ { VC_KP_8, 0x58 }, /* 0x50 KEY_KP2 */ + /* 81 */ { VC_KP_9, 0x59 }, /* 0x51 KEY_KP3 */ + /* 82 */ { VC_KP_SUBTRACT, 0x5A }, /* 0x52 KEY_KP0 */ + /* 83 */ { VC_KP_4, 0x5B }, /* 0x53 KEY_KPDOT */ + /* 84 */ { VC_KP_5, 0x00 }, /* 0x54 */ + /* 85 */ { VC_KP_6, 0x00 }, /* 0x55 TODO [KEY_ZENKAKUHANKAKU][0] == [VC_?][1] */ + /* 86 */ { VC_KP_ADD, 0x00 }, /* 0x56 TODO [KEY_102ND][0] == [VC_?][1] */ + /* 87 */ { VC_KP_1, 0x5F }, /* 0x57 KEY_F11 */ + /* 88 */ { VC_KP_2, 0x60 }, /* 0x58 KEY_F12 */ + /* 89 */ { VC_KP_3, 0x00 }, /* 0x59 TODO [KEY_RO][0] == [VC_?][1] */ + /* 90 */ { VC_KP_0, 0x00 }, /* 0x5A */ + /* 91 */ { VC_KP_SEPARATOR, 0xBF }, /* 0x5B KEY_F13 */ + /* 92 */ { VC_UNDEFINED, 0xC0 }, /* 0x5C KEY_F14 */ + /* 93 */ { VC_UNDEFINED, 0xC1 }, /* 0x5D KEY_F15 */ + /* 94 */ { VC_UNDEFINED, 0x00 }, /* 0x5E TODO [KEY_MUHENKAN][0] == [VC_?][1] */ + /* 95 */ { VC_F11, 0x00 }, /* 0x5F */ + /* 96 */ { VC_F12, 0x00 }, /* 0x60 */ + + /* First 97 chars are identical to XFree86! */ + + /* 97 */ { VC_UNDEFINED, 0x00 }, /* 0x61 */ + /* 98 */ { VC_KATAKANA, 0x00 }, /* 0x62 */ + /* 99 */ { VC_HIRAGANA, 0xC2 }, /* 0x63 KEY_F16 */ + /* 100 */ { VC_KANJI, 0xC3 }, /* 0x64 KEY_F17 */ + /* 101 */ { VC_UNDEFINED, 0xC4 }, /* 0x65 KEY_F18 */ + /* 102 */ { VC_UNDEFINED, 0xC5 }, /* 0x66 KEY_F19 */ + /* 103 */ { VC_KP_COMMA, 0xC6 }, /* 0x67 KEY_F20 */ + /* 104 */ { VC_KP_ENTER, 0xC7 }, /* 0x68 KEY_F21 */ + /* 105 */ { VC_CONTROL_R, 0xC8 }, /* 0x69 KEY_F22 */ + /* 106 */ { VC_KP_DIVIDE, 0xC9 }, /* 0x6A KEY_F23 */ + /* 107 */ { VC_PRINTSCREEN, 0xCA }, /* 0x6B KEY_F24 */ + /* 108 */ { VC_ALT_R, 0x00 }, /* 0x6C */ + /* 109 */ { VC_UNDEFINED, 0x00 }, /* 0x6D */ + /* 110 */ { VC_HOME, 0x00 }, /* 0x6E */ + /* 111 */ { VC_UP, 0x00 }, /* 0x6F */ + /* 112 */ { VC_PAGE_UP, 0x62 }, /* 0x70 KEY_KATAKANA */ + /* 113 */ { VC_LEFT, 0x00 }, /* 0x71 */ + /* 114 */ { VC_RIGHT, 0x00 }, /* 0x72 */ + /* 115 */ { VC_END, 0x00 }, /* 0x73 TODO KEY_? = [VC_UNDERSCORE][1] */ + /* 116 */ { VC_DOWN, 0x00 }, /* 0x74 TODO KEY_? = [VC_FURIGANA][1] */ + /* 117 */ { VC_PAGE_DOWN, 0x00 }, /* 0x75 */ + /* 118 */ { VC_INSERT, 0x00 }, /* 0x76 TODO [KEY_KPPLUSMINUS][0] = [VC_?][1] */ + /* 119 */ { VC_DELETE, 0x00 }, /* 0x77 */ + /* 120 */ { VC_UNDEFINED, 0x00 }, /* 0x78 TODO [KEY_SCALE][0] = [VC_?][1] */ + /* 121 */ { VC_VOLUME_MUTE, 0x64 }, /* 0x79 KEY_HENKAN */ + /* 122 */ { VC_VOLUME_DOWN, 0x00 }, /* 0x7A */ + /* 123 */ { VC_VOLUME_UP, 0x63 }, /* 0x7B KEY_HIRAGANA */ + /* 124 */ { VC_POWER, 0x00 }, /* 0x7C */ + /* 125 */ { VC_KP_EQUALS, 0x84 }, /* 0x7D KEY_YEN */ + /* 126 */ { VC_UNDEFINED, 0x67 }, /* 0x7E KEY_KPJPCOMMA */ + /* 127 */ { VC_PAUSE, 0x00 }, /* 0x7F */ + + /* No Offset Offset (i & 0x007F) + 128 */ + + /* 128 */ { VC_UNDEFINED, 0 }, /* 0x80 */ + /* 129 */ { VC_UNDEFINED, 0 }, /* 0x81 */ + /* 130 */ { VC_UNDEFINED, 0 }, /* 0x82 */ + /* 131 */ { VC_UNDEFINED, 0 }, /* 0x83 */ + /* 132 */ { VC_YEN, 0 }, /* 0x84 */ + /* 133 */ { VC_META_L, 0 }, /* 0x85 */ + /* 134 */ { VC_META_R, 0 }, /* 0x86 */ + /* 135 */ { VC_CONTEXT_MENU, 0 }, /* 0x87 */ + /* 136 */ { VC_SUN_STOP, 0 }, /* 0x88 */ + /* 137 */ { VC_SUN_AGAIN, 0 }, /* 0x89 */ + /* 138 */ { VC_SUN_PROPS, 0 }, /* 0x8A */ + /* 139 */ { VC_SUN_UNDO, 0 }, /* 0x8B */ + /* 140 */ { VC_SUN_FRONT, 0 }, /* 0x8C */ + /* 141 */ { VC_SUN_COPY, 0x7D }, /* 0x8D KEY_KPEQUAL */ + /* 142 */ { VC_SUN_OPEN, 0 }, /* 0x8E */ + /* 143 */ { VC_SUN_INSERT, 0 }, /* 0x8F */ + /* 144 */ { VC_SUN_FIND, 0 }, /* 0x90 */ + /* 145 */ { VC_SUN_CUT, 0 }, /* 0x91 */ + /* 146 */ { VC_SUN_HELP, 0 }, /* 0x92 */ + /* 147 */ { VC_UNDEFINED, 0 }, /* 0x93 */ + /* 148 */ { VC_APP_CALCULATOR, 0 }, /* 0x94 */ + /* 149 */ { VC_UNDEFINED, 0 }, /* 0x95 */ + /* 150 */ { VC_SLEEP, 0 }, /* 0x96 */ + /* 151 */ { VC_UNDEFINED, 0 }, /* 0x97 */ + /* 152 */ { VC_UNDEFINED, 0 }, /* 0x98 */ + /* 153 */ { VC_UNDEFINED, 0 }, /* 0x99 */ + /* 154 */ { VC_UNDEFINED, 0 }, /* 0x9A */ + /* 155 */ { VC_UNDEFINED, 0 }, /* 0x9B */ + /* 156 */ { VC_UNDEFINED, 0x68 }, /* 0x9C KEY_KPENTER */ + /* 157 */ { VC_UNDEFINED, 0x69 }, /* 0x9D KEY_RIGHTCTRL */ + /* 158 */ { VC_UNDEFINED, 0 }, /* 0x9E */ + /* 159 */ { VC_UNDEFINED, 0 }, /* 0x9F */ + /* 160 */ { VC_UNDEFINED, 0x79 }, /* 0xA0 KEY_MUTE */ + /* 161 */ { VC_UNDEFINED, 0x94 }, /* 0xA1 KEY_CALC */ + /* 162 */ { VC_UNDEFINED, 0xA7 }, /* 0xA2 KEY_FORWARD */ + /* 163 */ { VC_UNDEFINED, 0 }, /* 0xA3 */ + /* 164 */ { VC_UNDEFINED, 0 }, /* 0xA4 */ + /* 165 */ { VC_UNDEFINED, 0 }, /* 0xA5 */ + /* 166 */ { VC_APP_MAIL, 0 }, /* 0xA6 */ + /* 167 */ { VC_MEDIA_PLAY, 0 }, /* 0xA7 */ + /* 168 */ { VC_UNDEFINED, 0 }, /* 0xA8 */ + /* 169 */ { VC_UNDEFINED, 0 }, /* 0xA9 */ + /* 170 */ { VC_UNDEFINED, 0 }, /* 0xAA */ + /* 171 */ { VC_UNDEFINED, 0 }, /* 0xAB */ + /* 172 */ { VC_UNDEFINED, 0 }, /* 0xAC */ + /* 173 */ { VC_UNDEFINED, 0 }, /* 0xAD */ + /* 174 */ { VC_UNDEFINED, 0x7A }, /* 0xAE KEY_VOLUMEDOWN */ + /* 175 */ { VC_UNDEFINED, 0 }, /* 0xAF */ + /* 176 */ { VC_UNDEFINED, 0x7B }, /* 0xB0 KEY_VOLUMEUP */ + /* 177 */ { VC_UNDEFINED, 0x00 }, /* 0xB1 */ + /* 178 */ { VC_UNDEFINED, 0xBA }, /* 0xB2 KEY_SCROLLUP */ + /* 179 */ { VC_UNDEFINED, 0x00 }, /* 0xB3 */ + /* 180 */ { VC_UNDEFINED, 0x00 }, /* 0xB4 */ + /* 181 */ { VC_UNDEFINED, 0x6A }, /* 0xB5 KEY_KPSLASH */ + /* 182 */ { VC_UNDEFINED, 0x00 }, /* 0xB6 */ + /* 183 */ { VC_UNDEFINED, 0x6B }, /* 0xB7 KEY_SYSRQ */ + /* 184 */ { VC_UNDEFINED, 0x6C }, /* 0xB8 KEY_RIGHTALT */ + /* 185 */ { VC_UNDEFINED, 0x00 }, /* 0xB9 */ + /* 186 */ { VC_BROWSER_HOME, 0x00 }, /* 0xBA */ + /* 187 */ { VC_UNDEFINED, 0x00 }, /* 0xBB */ + /* 188 */ { VC_UNDEFINED, 0x00 }, /* 0xBC */ + /* 189 */ { VC_UNDEFINED, 0x00 }, /* 0xBD */ + /* 190 */ { VC_UNDEFINED, 0x00 }, /* 0xBE */ + /* 191 */ { VC_F13, 0x00 }, /* 0xBF */ + /* 192 */ { VC_F14, 0x00 }, /* 0xC0 */ + /* 193 */ { VC_F15, 0x00 }, /* 0xC1 */ + /* 194 */ { VC_F16, 0x00 }, /* 0xC2 */ + /* 195 */ { VC_F17, 0x00 }, /* 0xC3 */ + /* 196 */ { VC_F18, 0x00 }, /* 0xC4 */ + /* 197 */ { VC_F19, 0x7F }, /* 0xC5 KEY_PAUSE */ + /* 198 */ { VC_F20, 0x00 }, /* 0xC6 */ + /* 199 */ { VC_F21, 0x6E }, /* 0xC7 KEY_HOME */ + /* 200 */ { VC_F22, 0x6F }, /* 0xC8 KEY_UP */ + /* 201 */ { VC_F23, 0x70 }, /* 0xC9 KEY_PAGEUP */ + /* 202 */ { VC_F24, 0x00 }, /* 0xCA */ + /* 203 */ { VC_UNDEFINED, 0x71 }, /* 0xCB KEY_LEFT */ + /* 204 */ { VC_UNDEFINED, 0x00 }, /* 0xCC */ + /* 205 */ { VC_UNDEFINED, 0x72 }, /* 0xCD KEY_RIGHT */ + /* 206 */ { VC_UNDEFINED, 0x00 }, /* 0xCE */ + /* 207 */ { VC_UNDEFINED, 0x73 }, /* 0xCF KEY_END */ + /* 208 */ { VC_UNDEFINED, 0x74 }, /* 0xD0 KEY_DOWN */ + /* 209 */ { VC_UNDEFINED, 0x75 }, /* 0xD1 KEY_PAGEDOWN */ + /* 210 */ { VC_UNDEFINED, 0x76 }, /* 0xD2 KEY_INSERT */ + /* 211 */ { VC_UNDEFINED, 0x77 }, /* 0xD3 KEY_DELETE */ + /* 212 */ { VC_UNDEFINED, 0x00 }, /* 0xD4 */ + /* 213 */ { VC_UNDEFINED, 0x00 }, /* 0xD5 */ + /* 214 */ { VC_UNDEFINED, 0x00 }, /* 0xD6 */ + /* 215 */ { VC_UNDEFINED, 0x00 }, /* 0xD7 */ + /* 216 */ { VC_UNDEFINED, 0x00 }, /* 0xD8 */ + /* 217 */ { VC_UNDEFINED, 0x00 }, /* 0xD9 */ + /* 218 */ { VC_UNDEFINED, 0x00 }, /* 0xDA */ + /* 219 */ { VC_UNDEFINED, 0x85 }, /* 0xDB KEY_LEFTMETA */ + /* 220 */ { VC_UNDEFINED, 0x86 }, /* 0xDC KEY_RIGHTMETA */ + /* 221 */ { VC_UNDEFINED, 0x87 }, /* 0xDD KEY_COMPOSE */ + /* 222 */ { VC_UNDEFINED, 0x7C }, /* 0xDE KEY_POWER */ + /* 223 */ { VC_UNDEFINED, 0x96 }, /* 0xDF KEY_SLEEP */ + /* 224 */ { VC_UNDEFINED, 0x00 }, /* 0xE0 */ + /* 225 */ { VC_BROWSER_SEARCH, 0x00 }, /* 0xE1 */ + /* 226 */ { VC_UNDEFINED, 0x00 }, /* 0xE2 */ + /* 227 */ { VC_UNDEFINED, 0x00 }, /* 0xE3 */ + /* 228 */ { VC_UNDEFINED, 0x00 }, /* 0xE4 */ + /* 229 */ { VC_UNDEFINED, 0xE1 }, /* 0xE5 KEY_SEARCH */ + /* 230 */ { VC_UNDEFINED, 0x00 }, /* 0xE6 */ + /* 231 */ { VC_UNDEFINED, 0x00 }, /* 0xE7 */ + /* 232 */ { VC_UNDEFINED, 0x00 }, /* 0xE8 */ + /* 233 */ { VC_UNDEFINED, 0x00 }, /* 0xE9 */ + /* 234 */ { VC_UNDEFINED, 0x00 }, /* 0xEA */ + /* 235 */ { VC_UNDEFINED, 0x00 }, /* 0xEB */ + /* 236 */ { VC_UNDEFINED, 0xA6 }, /* 0xEC KEY_BACK */ + /* 237 */ { VC_UNDEFINED, 0x00 }, /* 0xED */ + /* 238 */ { VC_UNDEFINED, 0x00 }, /* 0xEE */ + /* 239 */ { VC_UNDEFINED, 0x00 }, /* 0xEF */ + /* 240 */ { VC_UNDEFINED, 0x00 }, /* 0xF0 */ + /* 241 */ { VC_UNDEFINED, 0x00 }, /* 0xF1 */ + /* 242 */ { VC_UNDEFINED, 0x00 }, /* 0xF2 */ + /* 243 */ { VC_UNDEFINED, 0x00 }, /* 0xF3 */ + /* 244 */ { VC_UNDEFINED, 0x8E }, /* 0xF4 KEY_OPEN */ + /* 245 */ { VC_UNDEFINED, 0x92 }, /* 0xF5 KEY_HELP */ + /* 246 */ { VC_UNDEFINED, 0x8A }, /* 0xF6 KEY_PROPS */ + /* 247 */ { VC_UNDEFINED, 0x8C }, /* 0xF7 KEY_FRONT */ + /* 248 */ { VC_UNDEFINED, 0x88 }, /* 0xF8 KEY_STOP */ + /* 249 */ { VC_UNDEFINED, 0x89 }, /* 0xF9 KEY_AGAIN */ + /* 250 */ { VC_UNDEFINED, 0x8B }, /* 0xFA KEY_UNDO */ + /* 251 */ { VC_UNDEFINED, 0x91 }, /* 0xFB KEY_CUT */ + /* 252 */ { VC_UNDEFINED, 0x8D }, /* 0xFC KEY_COPY */ + /* 253 */ { VC_UNDEFINED, 0x8F }, /* 0xFD KEY_PASTE */ + /* 254 */ { VC_UNDEFINED, 0x90 }, /* 0xFE KEY_FIND */ + /* 255 */ { VC_UNDEFINED, 0x00 }, /* 0xFF */ +}; +#endif + + +/* This table is generated based off the xfree86 -> scancode mapping above + * and the keycode mappings in the following files: + * /usr/share/X11/xkb/keycodes/xfree86 + * + * TODO Everything after 157 needs to be populated with scancodes for media + * controls and internet keyboards. + */ +static const uint16_t xfree86_scancode_table[][2] = { + /* idx { keycode, scancode }, */ + /* 0 */ { VC_UNDEFINED, 0 /* */ }, // Unused + /* 1 */ { VC_UNDEFINED, 9 /* */ }, // + /* 2 */ { VC_UNDEFINED, 10 /* */ }, // + /* 3 */ { VC_UNDEFINED, 11 /* */ }, // + /* 4 */ { VC_UNDEFINED, 12 /* */ }, // + /* 5 */ { VC_UNDEFINED, 13 /* */ }, // + /* 6 */ { VC_UNDEFINED, 14 /* */ }, // + /* 7 */ { VC_UNDEFINED, 15 /* */ }, // + /* 8 */ { VC_UNDEFINED, 16 /* */ }, // + /* 9 */ { VC_ESCAPE, 17 /* */ }, // + /* 10 */ { VC_1, 18 /* */ }, // + /* 11 */ { VC_2, 19 /* */ }, // + /* 12 */ { VC_3, 20 /* */ }, // + /* 13 */ { VC_4, 21 /* */ }, // + /* 14 */ { VC_5, 22 /* */ }, // + /* 15 */ { VC_6, 23 /* */ }, // + /* 16 */ { VC_7, 24 /* */ }, // + /* 17 */ { VC_8, 25 /* */ }, // + /* 18 */ { VC_9, 26 /* */ }, // + /* 19 */ { VC_0, 27 /* */ }, // + /* 20 */ { VC_MINUS, 28 /* */ }, // + /* 21 */ { VC_EQUALS, 29 /* */ }, // + /* 22 */ { VC_BACKSPACE, 30 /* */ }, // + /* 23 */ { VC_TAB, 31 /* */ }, // + /* 24 */ { VC_Q, 32 /* */ }, // + /* 25 */ { VC_W, 33 /* */ }, // + /* 26 */ { VC_E, 34 /* */ }, // + /* 27 */ { VC_R, 35 /* */ }, // + /* 28 */ { VC_T, 36 /* */ }, // + /* 29 */ { VC_Y, 37 /* */ }, // + /* 30 */ { VC_U, 38 /* */ }, // + /* 31 */ { VC_I, 39 /* */ }, // + /* 32 */ { VC_O, 40 /* */ }, // + /* 33 */ { VC_P, 41 /* */ }, // + /* 34 */ { VC_OPEN_BRACKET, 42 /* */ }, // + /* 35 */ { VC_CLOSE_BRACKET, 43 /* */ }, // + /* 36 */ { VC_ENTER, 44 /* */ }, // + /* 37 */ { VC_CONTROL_L, 45 /* */ }, // + /* 38 */ { VC_A, 46 /* */ }, // + /* 39 */ { VC_S, 47 /* */ }, // + /* 40 */ { VC_D, 48 /* */ }, // + /* 41 */ { VC_F, 49 /* */ }, // + /* 42 */ { VC_G, 50 /* */ }, // + /* 43 */ { VC_H, 51 /* */ }, // + /* 44 */ { VC_J, 52 /* */ }, // + /* 45 */ { VC_K, 53 /* */ }, // + /* 46 */ { VC_L, 54 /* */ }, // + /* 47 */ { VC_SEMICOLON, 55 /* */ }, // + /* 48 */ { VC_QUOTE, 56 /* */ }, // + /* 49 */ { VC_BACKQUOTE, 57 /* */ }, // + /* 50 */ { VC_SHIFT_L, 58 /* */ }, // + /* 51 */ { VC_BACK_SLASH, 59 /* */ }, // + /* 52 */ { VC_Z, 60 /* */ }, // + /* 53 */ { VC_X, 61 /* */ }, // + /* 54 */ { VC_C, 62 /* */ }, // + /* 55 */ { VC_V, 63 /* */ }, // + /* 56 */ { VC_B, 64 /* */ }, // + /* 57 */ { VC_N, 65 /* */ }, // + /* 58 */ { VC_M, 66 /* */ }, // + /* 59 */ { VC_COMMA, 67 /* */ }, // + /* 60 */ { VC_PERIOD, 68 /* */ }, // + /* 61 */ { VC_SLASH, 69 /* */ }, // + /* 62 */ { VC_SHIFT_R, 70 /* */ }, // + /* 63 */ { VC_KP_MULTIPLY, 71 /* */ }, // + /* 64 */ { VC_ALT_L, 72 /* */ }, // + /* 65 */ { VC_SPACE, 73 /* */ }, // + /* 66 */ { VC_CAPS_LOCK, 74 /* */ }, // + /* 67 */ { VC_F1, 75 /* */ }, // + /* 68 */ { VC_F2, 76 /* */ }, // + /* 69 */ { VC_F3, 77 /* */ }, // + /* 70 */ { VC_F4, 78 /* */ }, // + /* 71 */ { VC_F5, 79 /* */ }, // + /* 72 */ { VC_F6, 80 /* */ }, // + /* 73 */ { VC_F7, 81 /* */ }, // + /* 74 */ { VC_F8, 82 /* */ }, // + /* 75 */ { VC_F9, 83 /* */ }, // + /* 76 */ { VC_F10, 84 /* */ }, // + /* 77 */ { VC_NUM_LOCK, 85 /* */ }, // + /* 78 */ { VC_SCROLL_LOCK, 86 /* */ }, // + /* 79 */ { VC_KP_7, 87 /* */ }, // + /* 80 */ { VC_KP_8, 88 /* */ }, // + /* 81 */ { VC_KP_9, 89 /* */ }, // + /* 82 */ { VC_KP_SUBTRACT, 90 /* */ }, // + /* 83 */ { VC_KP_4, 91 /* */ }, // + /* 84 */ { VC_KP_5, 0 }, // + /* 85 */ { VC_KP_6, 0 }, // + /* 86 */ { VC_KP_ADD, 0 }, // + /* 87 */ { VC_KP_1, 95 /* */ }, // + /* 88 */ { VC_KP_2, 96 /* */ }, + /* 89 */ { VC_KP_3, 0 }, + /* 90 */ { VC_KP_0, 0 }, + /* 91 */ { VC_KP_SEPARATOR, 118 /* */ }, + /* 92 */ { VC_UNDEFINED, 119 /* */ }, + /* 93 */ { VC_UNDEFINED, 120 /* */ }, + /* 94 */ { VC_UNDEFINED, 0 }, + /* 95 */ { VC_F11, 0 }, + /* 96 */ { VC_F12, 0 }, + + /* First 97 chars are identical to XFree86! */ + + /* 97 */ { VC_HOME, 0 }, + /* 98 */ { VC_UP, 0 }, + /* 99 */ { VC_PAGE_UP, 121 /* */ }, + /* 100 */ { VC_LEFT, 122 /* */ }, + /* 101 */ { VC_UNDEFINED, 0 }, // TODO lower brightness key? + /* 102 */ { VC_RIGHT, 0 }, + /* 103 */ { VC_END, 0 }, + /* 104 */ { VC_DOWN, 0 }, + /* 105 */ { VC_PAGE_DOWN, 0 }, + /* 106 */ { VC_INSERT, 0 }, + /* 107 */ { VC_DELETE, 0 }, + /* 108 */ { VC_KP_ENTER, 0 }, + /* 109 */ { VC_CONTROL_R, 0 }, + /* 110 */ { VC_PAUSE, 0 }, + /* 111 */ { VC_PRINTSCREEN, 0 }, + /* 112 */ { VC_KP_DIVIDE, 0 }, + /* 113 */ { VC_ALT_R, 0 }, + /* 114 */ { VC_UNDEFINED, 0 }, // VC_BREAK? + /* 115 */ { VC_META_L, 0 }, + /* 116 */ { VC_META_R, 0 }, + /* 117 */ { VC_CONTEXT_MENU, 0 }, + /* 118 */ { VC_F13, 0 }, + /* 119 */ { VC_F14, 0 }, + /* 120 */ { VC_F15, 0 }, + /* 121 */ { VC_F16, 0 }, + /* 122 */ { VC_F17, 0 }, + /* 123 */ { VC_UNDEFINED, 0 }, // FIXME What is this key? + /* 124 */ { VC_UNDEFINED, 0 }, // Never Generated + /* 125 */ { VC_UNDEFINED, 133 /* */ }, // Never Generated + /* 126 */ { VC_KP_EQUALS, 0 }, + /* 127 */ { VC_UNDEFINED, 0 }, // Never Generated + /* 128 */ { VC_UNDEFINED, 0 }, // Never Generated + /* 129 */ { VC_UNDEFINED, 0 }, // Henkan + /* 130 */ { VC_UNDEFINED, 0 }, // Some extended Internet key + /* 131 */ { VC_UNDEFINED, 0 }, // Muhenkan + /* 132 */ { VC_UNDEFINED, 0 }, // + /* 133 */ { VC_YEN, 0 }, // + /* 134 */ { VC_UNDEFINED, 0 }, // + /* 135 */ { VC_UNDEFINED, 0 }, // + /* 136 */ { VC_UNDEFINED, 0 }, // + /* 137 */ { VC_UNDEFINED, 0 }, // + /* 138 */ { VC_UNDEFINED, 0 }, // + /* 139 */ { VC_UNDEFINED, 0 }, // + /* 140 */ { VC_UNDEFINED, 0 }, // + /* 141 */ { VC_UNDEFINED, 126 }, // + /* 142 */ { VC_UNDEFINED, 0 }, // + /* 143 */ { VC_UNDEFINED, 0 }, // + /* 144 */ { VC_UNDEFINED, 0 }, // + /* 145 */ { VC_UNDEFINED, 0 }, // + /* 146 */ { VC_UNDEFINED, 0 }, // + /* 147 */ { VC_UNDEFINED, 0 }, // + /* 148 */ { VC_UNDEFINED, 0 }, // + /* 149 */ { VC_UNDEFINED, 0 }, // + /* 150 */ { VC_UNDEFINED, 0 }, // + /* 151 */ { VC_UNDEFINED, 0 }, // + /* 152 */ { VC_UNDEFINED, 0 }, // + /* 153 */ { VC_UNDEFINED, 0 }, // + /* 154 */ { VC_UNDEFINED, 0 }, // + /* 155 */ { VC_UNDEFINED, 0 }, // + /* 156 */ { VC_UNDEFINED, 108 /* */ }, // Never Generated + /* 157 */ { VC_UNDEFINED, 109 /* */ }, // + /* 158 */ { VC_UNDEFINED, 0 }, // + /* 159 */ { VC_UNDEFINED, 0 }, // + /* 160 */ { VC_UNDEFINED, 0 }, // + /* 161 */ { VC_UNDEFINED, 0 }, // + /* 162 */ { VC_UNDEFINED, 0 }, // + /* 163 */ { VC_UNDEFINED, 0 }, // + /* 164 */ { VC_UNDEFINED, 0 }, // + /* 165 */ { VC_UNDEFINED, 0 }, // + /* 166 */ { VC_UNDEFINED, 0 }, // + /* 167 */ { VC_UNDEFINED, 0 }, // + /* 168 */ { VC_UNDEFINED, 0 }, // + /* 169 */ { VC_UNDEFINED, 0 }, // + /* 170 */ { VC_UNDEFINED, 0 }, // + /* 171 */ { VC_UNDEFINED, 0 }, // + /* 172 */ { VC_UNDEFINED, 0 }, // + /* 173 */ { VC_UNDEFINED, 0 }, // + /* 174 */ { VC_UNDEFINED, 0 }, // + /* 175 */ { VC_UNDEFINED, 0 }, // + /* 176 */ { VC_UNDEFINED, 0 }, // + /* 177 */ { VC_UNDEFINED, 0 }, // + /* 178 */ { VC_UNDEFINED, 0 }, // + /* 179 */ { VC_UNDEFINED, 0 }, // + /* 180 */ { VC_UNDEFINED, 0 }, // + /* 181 */ { VC_UNDEFINED, 112 }, // + /* 182 */ { VC_UNDEFINED, 0 }, // + /* 183 */ { VC_UNDEFINED, 111 }, // + /* 184 */ { VC_UNDEFINED, 113 }, // + /* 185 */ { VC_UNDEFINED, 0 }, // + /* 186 */ { VC_UNDEFINED, 0 }, // + /* 187 */ { VC_UNDEFINED, 0 }, // + /* 188 */ { VC_UNDEFINED, 0 }, // + /* 189 */ { VC_UNDEFINED, 0 }, // + /* 190 */ { VC_UNDEFINED, 0 }, // + /* 191 */ { VC_UNDEFINED, 0 }, // + /* 192 */ { VC_UNDEFINED, 0 }, // + /* 193 */ { VC_UNDEFINED, 0 }, // + /* 194 */ { VC_UNDEFINED, 0 }, // + /* 195 */ { VC_UNDEFINED, 0 }, // + /* 196 */ { VC_UNDEFINED, 0 }, // // 114 ? + /* 197 */ { VC_UNDEFINED, 110 }, // + /* 198 */ { VC_UNDEFINED, 0 }, // + /* 199 */ { VC_UNDEFINED, 97 /* */ }, // + /* 200 */ { VC_UNDEFINED, 98 }, // + /* 201 */ { VC_UNDEFINED, 99 }, // + /* 202 */ { VC_UNDEFINED, 0 }, // + /* 204 */ { VC_UNDEFINED, 0 }, // + /* 205 */ { VC_UNDEFINED, 102 }, // + /* 206 */ { VC_UNDEFINED, 0 }, // + /* 207 */ { VC_UNDEFINED, 103 }, // + /* 208 */ { VC_UNDEFINED, 104 }, // + /* 209 */ { VC_UNDEFINED, 105 }, // + /* 210 */ { VC_UNDEFINED, 106 }, // + /* 211 */ { VC_UNDEFINED, 107 }, // + /* 212 */ { VC_UNDEFINED, 0 }, // + /* 213 */ { VC_UNDEFINED, 0 }, // + /* 214 */ { VC_UNDEFINED, 0 }, // + /* 215 */ { VC_UNDEFINED, 0 }, // + /* 216 */ { VC_UNDEFINED, 0 }, // + /* 217 */ { VC_UNDEFINED, 0 }, // + /* 218 */ { VC_UNDEFINED, 0 }, // + /* 219 */ { VC_UNDEFINED, 115 /* */ }, // + /* 220 */ { VC_UNDEFINED, 116 /* */ }, // + /* 221 */ { VC_UNDEFINED, 117 /* */ }, // + /* 222 */ { VC_UNDEFINED, 0 }, // + /* 223 */ { VC_UNDEFINED, 0 }, // + /* 224 */ { VC_UNDEFINED, 0 }, // + /* 225 */ { VC_UNDEFINED, 0 }, // + /* 226 */ { VC_UNDEFINED, 0 }, // + /* 227 */ { VC_UNDEFINED, 0 }, // + /* 228 */ { VC_UNDEFINED, 0 }, // + /* 229 */ { VC_UNDEFINED, 0 }, // + /* 230 */ { VC_UNDEFINED, 0 }, // + /* 231 */ { VC_UNDEFINED, 0 }, // + /* 232 */ { VC_UNDEFINED, 0 }, // + /* 233 */ { VC_UNDEFINED, 0 }, // + /* 234 */ { VC_UNDEFINED, 0 }, // + /* 235 */ { VC_UNDEFINED, 0 }, // + /* 236 */ { VC_UNDEFINED, 0 }, // + /* 237 */ { VC_UNDEFINED, 0 }, // + /* 238 */ { VC_UNDEFINED, 0 }, // + /* 239 */ { VC_UNDEFINED, 0 }, // + /* 240 */ { VC_UNDEFINED, 0 }, // + /* 241 */ { VC_UNDEFINED, 0 }, // + /* 242 */ { VC_UNDEFINED, 0 }, // + /* 243 */ { VC_UNDEFINED, 0 }, // + /* 244 */ { VC_UNDEFINED, 0 }, // + /* 245 */ { VC_UNDEFINED, 0 }, // + /* 246 */ { VC_UNDEFINED, 0 }, // + /* 247 */ { VC_UNDEFINED, 0 }, // + /* 248 */ { VC_UNDEFINED, 0 }, // + /* 249 */ { VC_UNDEFINED, 0 }, // + /* 250 */ { VC_UNDEFINED, 0 }, // + /* 251 */ { VC_UNDEFINED, 0 }, // + /* 252 */ { VC_UNDEFINED, 0 }, // + /* 253 */ { VC_UNDEFINED, 0 }, // + /* 254 */ { VC_UNDEFINED, 0 }, // + /* 255 */ { VC_UNDEFINED, 0 }, // + }; + + +/*********************************************************************** + * The following table contains pairs of X11 keysym values for graphical + * characters and the corresponding Unicode value. The function + * keysym_to_unicode() maps a keysym onto a Unicode value using a binary + * search, therefore keysym_unicode_table[] must remain SORTED by KeySym + * value. + * + * We allow to represent any UCS character in the range U+00000000 to + * U+00FFFFFF by a keysym value in the range 0x01000000 to 0x01FFFFFF. + * This admittedly does not cover the entire 31-bit space of UCS, but + * it does cover all of the characters up to U+10FFFF, which can be + * represented by UTF-16, and more, and it is very unlikely that higher + * UCS codes will ever be assigned by ISO. So to get Unicode character + * U+ABCD you can directly use keysym 0x1000ABCD. + * + * NOTE: The comments in the table below contain the actual character + * encoded in UTF-8, so for viewing and editing best use an editor in + * UTF-8 mode. + * + * Author: Markus G. Kuhn , University of Cambridge, + * June 1999 + * + * Special thanks to Richard Verhoeven for preparing + * an initial draft of the mapping table. + * + * This table is in the public domain. Share and enjoy! + ***********************************************************************/ +static struct codepair { + uint16_t keysym; + uint16_t unicode; +} keysym_unicode_table[] = { + { 0x01A1, 0x0104 }, /* Aogonek Ą LATIN CAPITAL LETTER A WITH OGONEK */ + { 0x01A2, 0x02D8 }, /* breve ˘ BREVE */ + { 0x01A3, 0x0141 }, /* Lstroke Ł LATIN CAPITAL LETTER L WITH STROKE */ + { 0x01A5, 0x013D }, /* Lcaron Ľ LATIN CAPITAL LETTER L WITH CARON */ + { 0x01A6, 0x015A }, /* Sacute Ś LATIN CAPITAL LETTER S WITH ACUTE */ + { 0x01A9, 0x0160 }, /* Scaron Š LATIN CAPITAL LETTER S WITH CARON */ + { 0x01AA, 0x015E }, /* Scedilla Ş LATIN CAPITAL LETTER S WITH CEDILLA */ + { 0x01AB, 0x0164 }, /* Tcaron Ť LATIN CAPITAL LETTER T WITH CARON */ + { 0x01AC, 0x0179 }, /* Zacute Ź LATIN CAPITAL LETTER Z WITH ACUTE */ + { 0x01AE, 0x017D }, /* Zcaron Ž LATIN CAPITAL LETTER Z WITH CARON */ + { 0x01AF, 0x017B }, /* Zabovedot Ż LATIN CAPITAL LETTER Z WITH DOT ABOVE */ + { 0x01B1, 0x0105 }, /* aogonek ą LATIN SMALL LETTER A WITH OGONEK */ + { 0x01B2, 0x02DB }, /* ogonek ˛ OGONEK */ + { 0x01B3, 0x0142 }, /* lstroke ł LATIN SMALL LETTER L WITH STROKE */ + { 0x01B5, 0x013E }, /* lcaron ľ LATIN SMALL LETTER L WITH CARON */ + { 0x01B6, 0x015B }, /* sacute ś LATIN SMALL LETTER S WITH ACUTE */ + { 0x01B7, 0x02C7 }, /* caron ˇ CARON */ + { 0x01B9, 0x0161 }, /* scaron š LATIN SMALL LETTER S WITH CARON */ + { 0x01BA, 0x015F }, /* scedilla ş LATIN SMALL LETTER S WITH CEDILLA */ + { 0x01BB, 0x0165 }, /* tcaron ť LATIN SMALL LETTER T WITH CARON */ + { 0x01BC, 0x017A }, /* zacute ź LATIN SMALL LETTER Z WITH ACUTE */ + { 0x01BD, 0x02DD }, /* doubleacute ˝ DOUBLE ACUTE ACCENT */ + { 0x01BE, 0x017E }, /* zcaron ž LATIN SMALL LETTER Z WITH CARON */ + { 0x01BF, 0x017C }, /* zabovedot ż LATIN SMALL LETTER Z WITH DOT ABOVE */ + { 0x01C0, 0x0154 }, /* Racute Ŕ LATIN CAPITAL LETTER R WITH ACUTE */ + { 0x01C3, 0x0102 }, /* Abreve Ă LATIN CAPITAL LETTER A WITH BREVE */ + { 0x01C5, 0x0139 }, /* Lacute Ĺ LATIN CAPITAL LETTER L WITH ACUTE */ + { 0x01C6, 0x0106 }, /* Cacute Ć LATIN CAPITAL LETTER C WITH ACUTE */ + { 0x01C8, 0x010C }, /* Ccaron Č LATIN CAPITAL LETTER C WITH CARON */ + { 0x01CA, 0x0118 }, /* Eogonek Ę LATIN CAPITAL LETTER E WITH OGONEK */ + { 0x01CC, 0x011A }, /* Ecaron Ě LATIN CAPITAL LETTER E WITH CARON */ + { 0x01CF, 0x010E }, /* Dcaron Ď LATIN CAPITAL LETTER D WITH CARON */ + { 0x01D0, 0x0110 }, /* Dstroke Đ LATIN CAPITAL LETTER D WITH STROKE */ + { 0x01D1, 0x0143 }, /* Nacute Ń LATIN CAPITAL LETTER N WITH ACUTE */ + { 0x01D2, 0x0147 }, /* Ncaron Ň LATIN CAPITAL LETTER N WITH CARON */ + { 0x01D5, 0x0150 }, /* Odoubleacute Ő LATIN CAPITAL LETTER O WITH DOUBLE ACUTE */ + { 0x01D8, 0x0158 }, /* Rcaron Ř LATIN CAPITAL LETTER R WITH CARON */ + { 0x01D9, 0x016E }, /* Uring Ů LATIN CAPITAL LETTER U WITH RING ABOVE */ + { 0x01DB, 0x0170 }, /* Udoubleacute Ű LATIN CAPITAL LETTER U WITH DOUBLE ACUTE */ + { 0x01DE, 0x0162 }, /* Tcedilla Ţ LATIN CAPITAL LETTER T WITH CEDILLA */ + { 0x01E0, 0x0155 }, /* racute ŕ LATIN SMALL LETTER R WITH ACUTE */ + { 0x01E3, 0x0103 }, /* abreve ă LATIN SMALL LETTER A WITH BREVE */ + { 0x01E5, 0x013A }, /* lacute ĺ LATIN SMALL LETTER L WITH ACUTE */ + { 0x01E6, 0x0107 }, /* cacute ć LATIN SMALL LETTER C WITH ACUTE */ + { 0x01E8, 0x010D }, /* ccaron č LATIN SMALL LETTER C WITH CARON */ + { 0x01EA, 0x0119 }, /* eogonek ę LATIN SMALL LETTER E WITH OGONEK */ + { 0x01EC, 0x011B }, /* ecaron ě LATIN SMALL LETTER E WITH CARON */ + { 0x01EF, 0x010F }, /* dcaron ď LATIN SMALL LETTER D WITH CARON */ + { 0x01F0, 0x0111 }, /* dstroke đ LATIN SMALL LETTER D WITH STROKE */ + { 0x01F1, 0x0144 }, /* nacute ń LATIN SMALL LETTER N WITH ACUTE */ + { 0x01F2, 0x0148 }, /* ncaron ň LATIN SMALL LETTER N WITH CARON */ + { 0x01F5, 0x0151 }, /* odoubleacute ő LATIN SMALL LETTER O WITH DOUBLE ACUTE */ + { 0x01F8, 0x0159 }, /* rcaron ř LATIN SMALL LETTER R WITH CARON */ + { 0x01F9, 0x016F }, /* uring ů LATIN SMALL LETTER U WITH RING ABOVE */ + { 0x01FB, 0x0171 }, /* udoubleacute ű LATIN SMALL LETTER U WITH DOUBLE ACUTE */ + { 0x01FE, 0x0163 }, /* tcedilla ţ LATIN SMALL LETTER T WITH CEDILLA */ + { 0x01FF, 0x02D9 }, /* abovedot ˙ DOT ABOVE */ + { 0x02A1, 0x0126 }, /* Hstroke Ħ LATIN CAPITAL LETTER H WITH STROKE */ + { 0x02A6, 0x0124 }, /* Hcircumflex Ĥ LATIN CAPITAL LETTER H WITH CIRCUMFLEX */ + { 0x02A9, 0x0130 }, /* Iabovedot İ LATIN CAPITAL LETTER I WITH DOT ABOVE */ + { 0x02AB, 0x011E }, /* Gbreve Ğ LATIN CAPITAL LETTER G WITH BREVE */ + { 0x02AC, 0x0134 }, /* Jcircumflex Ĵ LATIN CAPITAL LETTER J WITH CIRCUMFLEX */ + { 0x02B1, 0x0127 }, /* hstroke ħ LATIN SMALL LETTER H WITH STROKE */ + { 0x02B6, 0x0125 }, /* hcircumflex ĥ LATIN SMALL LETTER H WITH CIRCUMFLEX */ + { 0x02B9, 0x0131 }, /* idotless ı LATIN SMALL LETTER DOTLESS I */ + { 0x02BB, 0x011F }, /* gbreve ğ LATIN SMALL LETTER G WITH BREVE */ + { 0x02BC, 0x0135 }, /* jcircumflex ĵ LATIN SMALL LETTER J WITH CIRCUMFLEX */ + { 0x02C5, 0x010A }, /* Cabovedot Ċ LATIN CAPITAL LETTER C WITH DOT ABOVE */ + { 0x02C6, 0x0108 }, /* Ccircumflex Ĉ LATIN CAPITAL LETTER C WITH CIRCUMFLEX */ + { 0x02D5, 0x0120 }, /* Gabovedot Ġ LATIN CAPITAL LETTER G WITH DOT ABOVE */ + { 0x02D8, 0x011C }, /* Gcircumflex Ĝ LATIN CAPITAL LETTER G WITH CIRCUMFLEX */ + { 0x02DD, 0x016C }, /* Ubreve Ŭ LATIN CAPITAL LETTER U WITH BREVE */ + { 0x02DE, 0x015C }, /* Scircumflex Ŝ LATIN CAPITAL LETTER S WITH CIRCUMFLEX */ + { 0x02E5, 0x010B }, /* cabovedot ċ LATIN SMALL LETTER C WITH DOT ABOVE */ + { 0x02E6, 0x0109 }, /* ccircumflex ĉ LATIN SMALL LETTER C WITH CIRCUMFLEX */ + { 0x02F5, 0x0121 }, /* gabovedot ġ LATIN SMALL LETTER G WITH DOT ABOVE */ + { 0x02F8, 0x011D }, /* gcircumflex ĝ LATIN SMALL LETTER G WITH CIRCUMFLEX */ + { 0x02FD, 0x016D }, /* ubreve ŭ LATIN SMALL LETTER U WITH BREVE */ + { 0x02FE, 0x015D }, /* scircumflex ŝ LATIN SMALL LETTER S WITH CIRCUMFLEX */ + { 0x03A2, 0x0138 }, /* kra ĸ LATIN SMALL LETTER KRA */ + { 0x03A3, 0x0156 }, /* Rcedilla Ŗ LATIN CAPITAL LETTER R WITH CEDILLA */ + { 0x03A5, 0x0128 }, /* Itilde Ĩ LATIN CAPITAL LETTER I WITH TILDE */ + { 0x03A6, 0x013B }, /* Lcedilla Ļ LATIN CAPITAL LETTER L WITH CEDILLA */ + { 0x03AA, 0x0112 }, /* Emacron Ē LATIN CAPITAL LETTER E WITH MACRON */ + { 0x03AB, 0x0122 }, /* Gcedilla Ģ LATIN CAPITAL LETTER G WITH CEDILLA */ + { 0x03AC, 0x0166 }, /* Tslash Ŧ LATIN CAPITAL LETTER T WITH STROKE */ + { 0x03B3, 0x0157 }, /* rcedilla ŗ LATIN SMALL LETTER R WITH CEDILLA */ + { 0x03B5, 0x0129 }, /* itilde ĩ LATIN SMALL LETTER I WITH TILDE */ + { 0x03B6, 0x013C }, /* lcedilla ļ LATIN SMALL LETTER L WITH CEDILLA */ + { 0x03BA, 0x0113 }, /* emacron ē LATIN SMALL LETTER E WITH MACRON */ + { 0x03BB, 0x0123 }, /* gcedilla ģ LATIN SMALL LETTER G WITH CEDILLA */ + { 0x03BC, 0x0167 }, /* tslash ŧ LATIN SMALL LETTER T WITH STROKE */ + { 0x03BD, 0x014A }, /* ENG Ŋ LATIN CAPITAL LETTER ENG */ + { 0x03BF, 0x014B }, /* eng ŋ LATIN SMALL LETTER ENG */ + { 0x03C0, 0x0100 }, /* Amacron Ā LATIN CAPITAL LETTER A WITH MACRON */ + { 0x03C7, 0x012E }, /* Iogonek Į LATIN CAPITAL LETTER I WITH OGONEK */ + { 0x03CC, 0x0116 }, /* Eabovedot Ė LATIN CAPITAL LETTER E WITH DOT ABOVE */ + { 0x03CF, 0x012A }, /* Imacron Ī LATIN CAPITAL LETTER I WITH MACRON */ + { 0x03D1, 0x0145 }, /* Ncedilla Ņ LATIN CAPITAL LETTER N WITH CEDILLA */ + { 0x03D2, 0x014C }, /* Omacron Ō LATIN CAPITAL LETTER O WITH MACRON */ + { 0x03D3, 0x0136 }, /* Kcedilla Ķ LATIN CAPITAL LETTER K WITH CEDILLA */ + { 0x03D9, 0x0172 }, /* Uogonek Ų LATIN CAPITAL LETTER U WITH OGONEK */ + { 0x03DD, 0x0168 }, /* Utilde Ũ LATIN CAPITAL LETTER U WITH TILDE */ + { 0x03DE, 0x016A }, /* Umacron Ū LATIN CAPITAL LETTER U WITH MACRON */ + { 0x03E0, 0x0101 }, /* amacron ā LATIN SMALL LETTER A WITH MACRON */ + { 0x03E7, 0x012F }, /* iogonek į LATIN SMALL LETTER I WITH OGONEK */ + { 0x03EC, 0x0117 }, /* eabovedot ė LATIN SMALL LETTER E WITH DOT ABOVE */ + { 0x03EF, 0x012B }, /* imacron ī LATIN SMALL LETTER I WITH MACRON */ + { 0x03F1, 0x0146 }, /* ncedilla ņ LATIN SMALL LETTER N WITH CEDILLA */ + { 0x03F2, 0x014D }, /* omacron ō LATIN SMALL LETTER O WITH MACRON */ + { 0x03F3, 0x0137 }, /* kcedilla ķ LATIN SMALL LETTER K WITH CEDILLA */ + { 0x03F9, 0x0173 }, /* uogonek ų LATIN SMALL LETTER U WITH OGONEK */ + { 0x03FD, 0x0169 }, /* utilde ũ LATIN SMALL LETTER U WITH TILDE */ + { 0x03FE, 0x016B }, /* umacron ū LATIN SMALL LETTER U WITH MACRON */ + { 0x047E, 0x203E }, /* overline ‾ OVERLINE */ + { 0x04A1, 0x3002 }, /* kana_fullstop 。 IDEOGRAPHIC FULL STOP */ + { 0x04A2, 0x300C }, /* kana_openingbracket 「 LEFT CORNER BRACKET */ + { 0x04A3, 0x300D }, /* kana_closingbracket 」 RIGHT CORNER BRACKET */ + { 0x04A4, 0x3001 }, /* kana_comma 、 IDEOGRAPHIC COMMA */ + { 0x04A5, 0x30FB }, /* kana_conjunctive ・ KATAKANA MIDDLE DOT */ + { 0x04A6, 0x30F2 }, /* kana_WO ヲ KATAKANA LETTER WO */ + { 0x04A7, 0x30A1 }, /* kana_a ァ KATAKANA LETTER SMALL A */ + { 0x04A8, 0x30A3 }, /* kana_i ィ KATAKANA LETTER SMALL I */ + { 0x04A9, 0x30A5 }, /* kana_u ゥ KATAKANA LETTER SMALL U */ + { 0x04AA, 0x30A7 }, /* kana_e ェ KATAKANA LETTER SMALL E */ + { 0x04AB, 0x30A9 }, /* kana_o ォ KATAKANA LETTER SMALL O */ + { 0x04AC, 0x30E3 }, /* kana_ya ャ KATAKANA LETTER SMALL YA */ + { 0x04AD, 0x30E5 }, /* kana_yu ュ KATAKANA LETTER SMALL YU */ + { 0x04AE, 0x30E7 }, /* kana_yo ョ KATAKANA LETTER SMALL YO */ + { 0x04AF, 0x30C3 }, /* kana_tsu ッ KATAKANA LETTER SMALL TU */ + { 0x04B0, 0x30FC }, /* prolongedsound ー KATAKANA-HIRAGANA PROLONGED SOUND MARK */ + { 0x04B1, 0x30A2 }, /* kana_A ア KATAKANA LETTER A */ + { 0x04B2, 0x30A4 }, /* kana_I イ KATAKANA LETTER I */ + { 0x04B3, 0x30A6 }, /* kana_U ウ KATAKANA LETTER U */ + { 0x04B4, 0x30A8 }, /* kana_E エ KATAKANA LETTER E */ + { 0x04B5, 0x30AA }, /* kana_O オ KATAKANA LETTER O */ + { 0x04B6, 0x30AB }, /* kana_KA カ KATAKANA LETTER KA */ + { 0x04B7, 0x30AD }, /* kana_KI キ KATAKANA LETTER KI */ + { 0x04B8, 0x30AF }, /* kana_KU ク KATAKANA LETTER KU */ + { 0x04B9, 0x30B1 }, /* kana_KE ケ KATAKANA LETTER KE */ + { 0x04BA, 0x30B3 }, /* kana_KO コ KATAKANA LETTER KO */ + { 0x04BB, 0x30B5 }, /* kana_SA サ KATAKANA LETTER SA */ + { 0x04BC, 0x30B7 }, /* kana_SHI シ KATAKANA LETTER SI */ + { 0x04BD, 0x30B9 }, /* kana_SU ス KATAKANA LETTER SU */ + { 0x04BE, 0x30BB }, /* kana_SE セ KATAKANA LETTER SE */ + { 0x04BF, 0x30BD }, /* kana_SO ソ KATAKANA LETTER SO */ + { 0x04C0, 0x30BF }, /* kana_TA タ KATAKANA LETTER TA */ + { 0x04C1, 0x30C1 }, /* kana_CHI チ KATAKANA LETTER TI */ + { 0x04C2, 0x30C4 }, /* kana_TSU ツ KATAKANA LETTER TU */ + { 0x04C3, 0x30C6 }, /* kana_TE テ KATAKANA LETTER TE */ + { 0x04C4, 0x30C8 }, /* kana_TO ト KATAKANA LETTER TO */ + { 0x04C5, 0x30CA }, /* kana_NA ナ KATAKANA LETTER NA */ + { 0x04C6, 0x30CB }, /* kana_NI ニ KATAKANA LETTER NI */ + { 0x04C7, 0x30CC }, /* kana_NU ヌ KATAKANA LETTER NU */ + { 0x04C8, 0x30CD }, /* kana_NE ネ KATAKANA LETTER NE */ + { 0x04C9, 0x30CE }, /* kana_NO ノ KATAKANA LETTER NO */ + { 0x04CA, 0x30CF }, /* kana_HA ハ KATAKANA LETTER HA */ + { 0x04CB, 0x30D2 }, /* kana_HI ヒ KATAKANA LETTER HI */ + { 0x04CC, 0x30D5 }, /* kana_FU フ KATAKANA LETTER HU */ + { 0x04CD, 0x30D8 }, /* kana_HE ヘ KATAKANA LETTER HE */ + { 0x04CE, 0x30DB }, /* kana_HO ホ KATAKANA LETTER HO */ + { 0x04CF, 0x30DE }, /* kana_MA マ KATAKANA LETTER MA */ + { 0x04D0, 0x30DF }, /* kana_MI ミ KATAKANA LETTER MI */ + { 0x04D1, 0x30E0 }, /* kana_MU ム KATAKANA LETTER MU */ + { 0x04D2, 0x30E1 }, /* kana_ME メ KATAKANA LETTER ME */ + { 0x04D3, 0x30E2 }, /* kana_MO モ KATAKANA LETTER MO */ + { 0x04D4, 0x30E4 }, /* kana_YA ヤ KATAKANA LETTER YA */ + { 0x04D5, 0x30E6 }, /* kana_YU ユ KATAKANA LETTER YU */ + { 0x04D6, 0x30E8 }, /* kana_YO ヨ KATAKANA LETTER YO */ + { 0x04D7, 0x30E9 }, /* kana_RA ラ KATAKANA LETTER RA */ + { 0x04D8, 0x30EA }, /* kana_RI リ KATAKANA LETTER RI */ + { 0x04D9, 0x30EB }, /* kana_RU ル KATAKANA LETTER RU */ + { 0x04DA, 0x30EC }, /* kana_RE レ KATAKANA LETTER RE */ + { 0x04DB, 0x30ED }, /* kana_RO ロ KATAKANA LETTER RO */ + { 0x04DC, 0x30EF }, /* kana_WA ワ KATAKANA LETTER WA */ + { 0x04DD, 0x30F3 }, /* kana_N ン KATAKANA LETTER N */ + { 0x04DE, 0x309B }, /* voicedsound ゛ KATAKANA-HIRAGANA VOICED SOUND MARK */ + { 0x04DF, 0x309C }, /* semivoicedsound ゜ KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK */ + { 0x05AC, 0x060C }, /* Arabic_comma ، ARABIC COMMA */ + { 0x05BB, 0x061B }, /* Arabic_semicolon ؛ ARABIC SEMICOLON */ + { 0x05BF, 0x061F }, /* Arabic_question_mark ؟ ARABIC QUESTION MARK */ + { 0x05C1, 0x0621 }, /* Arabic_hamza ء ARABIC LETTER HAMZA */ + { 0x05C2, 0x0622 }, /* Arabic_maddaonalef آ ARABIC LETTER ALEF WITH MADDA ABOVE */ + { 0x05C3, 0x0623 }, /* Arabic_hamzaonalef أ ARABIC LETTER ALEF WITH HAMZA ABOVE */ + { 0x05C4, 0x0624 }, /* Arabic_hamzaonwaw ؤ ARABIC LETTER WAW WITH HAMZA ABOVE */ + { 0x05C5, 0x0625 }, /* Arabic_hamzaunderalef إ ARABIC LETTER ALEF WITH HAMZA BELOW */ + { 0x05C6, 0x0626 }, /* Arabic_hamzaonyeh ئ ARABIC LETTER YEH WITH HAMZA ABOVE */ + { 0x05C7, 0x0627 }, /* Arabic_alef ا ARABIC LETTER ALEF */ + { 0x05C8, 0x0628 }, /* Arabic_beh ب ARABIC LETTER BEH */ + { 0x05C9, 0x0629 }, /* Arabic_tehmarbuta ة ARABIC LETTER TEH MARBUTA */ + { 0x05CA, 0x062A }, /* Arabic_teh ت ARABIC LETTER TEH */ + { 0x05CB, 0x062B }, /* Arabic_theh ث ARABIC LETTER THEH */ + { 0x05CC, 0x062C }, /* Arabic_jeem ج ARABIC LETTER JEEM */ + { 0x05CD, 0x062D }, /* Arabic_hah ح ARABIC LETTER HAH */ + { 0x05CE, 0x062E }, /* Arabic_khah خ ARABIC LETTER KHAH */ + { 0x05CF, 0x062F }, /* Arabic_dal د ARABIC LETTER DAL */ + { 0x05D0, 0x0630 }, /* Arabic_thal ذ ARABIC LETTER THAL */ + { 0x05D1, 0x0631 }, /* Arabic_ra ر ARABIC LETTER REH */ + { 0x05D2, 0x0632 }, /* Arabic_zain ز ARABIC LETTER ZAIN */ + { 0x05D3, 0x0633 }, /* Arabic_seen س ARABIC LETTER SEEN */ + { 0x05D4, 0x0634 }, /* Arabic_sheen ش ARABIC LETTER SHEEN */ + { 0x05D5, 0x0635 }, /* Arabic_sad ص ARABIC LETTER SAD */ + { 0x05D6, 0x0636 }, /* Arabic_dad ض ARABIC LETTER DAD */ + { 0x05D7, 0x0637 }, /* Arabic_tah ط ARABIC LETTER TAH */ + { 0x05D8, 0x0638 }, /* Arabic_zah ظ ARABIC LETTER ZAH */ + { 0x05D9, 0x0639 }, /* Arabic_ain ع ARABIC LETTER AIN */ + { 0x05DA, 0x063A }, /* Arabic_ghain غ ARABIC LETTER GHAIN */ + { 0x05E0, 0x0640 }, /* Arabic_tatweel ـ ARABIC TATWEEL */ + { 0x05E1, 0x0641 }, /* Arabic_feh ف ARABIC LETTER FEH */ + { 0x05E2, 0x0642 }, /* Arabic_qaf ق ARABIC LETTER QAF */ + { 0x05E3, 0x0643 }, /* Arabic_kaf ك ARABIC LETTER KAF */ + { 0x05E4, 0x0644 }, /* Arabic_lam ل ARABIC LETTER LAM */ + { 0x05E5, 0x0645 }, /* Arabic_meem م ARABIC LETTER MEEM */ + { 0x05E6, 0x0646 }, /* Arabic_noon ن ARABIC LETTER NOON */ + { 0x05E7, 0x0647 }, /* Arabic_ha ه ARABIC LETTER HEH */ + { 0x05E8, 0x0648 }, /* Arabic_waw و ARABIC LETTER WAW */ + { 0x05E9, 0x0649 }, /* Arabic_alefmaksura ى ARABIC LETTER ALEF MAKSURA */ + { 0x05EA, 0x064A }, /* Arabic_yeh ي ARABIC LETTER YEH */ + { 0x05EB, 0x064B }, /* Arabic_fathatan ً ARABIC FATHATAN */ + { 0x05EC, 0x064C }, /* Arabic_dammatan ٌ ARABIC DAMMATAN */ + { 0x05ED, 0x064D }, /* Arabic_kasratan ٍ ARABIC KASRATAN */ + { 0x05EE, 0x064E }, /* Arabic_fatha َ ARABIC FATHA */ + { 0x05EF, 0x064F }, /* Arabic_damma ُ ARABIC DAMMA */ + { 0x05F0, 0x0650 }, /* Arabic_kasra ِ ARABIC KASRA */ + { 0x05F1, 0x0651 }, /* Arabic_shadda ّ ARABIC SHADDA */ + { 0x05F2, 0x0652 }, /* Arabic_sukun ْ ARABIC SUKUN */ + { 0x06A1, 0x0452 }, /* Serbian_dje ђ CYRILLIC SMALL LETTER DJE */ + { 0x06A2, 0x0453 }, /* Macedonia_gje ѓ CYRILLIC SMALL LETTER GJE */ + { 0x06A3, 0x0451 }, /* Cyrillic_io ё CYRILLIC SMALL LETTER IO */ + { 0x06A4, 0x0454 }, /* Ukrainian_ie є CYRILLIC SMALL LETTER UKRAINIAN IE */ + { 0x06A5, 0x0455 }, /* Macedonia_dse ѕ CYRILLIC SMALL LETTER DZE */ + { 0x06A6, 0x0456 }, /* Ukrainian_i і CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I */ + { 0x06A7, 0x0457 }, /* Ukrainian_yi ї CYRILLIC SMALL LETTER YI */ + { 0x06A8, 0x0458 }, /* Cyrillic_je ј CYRILLIC SMALL LETTER JE */ + { 0x06A9, 0x0459 }, /* Cyrillic_lje љ CYRILLIC SMALL LETTER LJE */ + { 0x06AA, 0x045A }, /* Cyrillic_nje њ CYRILLIC SMALL LETTER NJE */ + { 0x06AB, 0x045B }, /* Serbian_tshe ћ CYRILLIC SMALL LETTER TSHE */ + { 0x06AC, 0x045C }, /* Macedonia_kje ќ CYRILLIC SMALL LETTER KJE */ + { 0x06AE, 0x045E }, /* Byelorussian_shortu ў CYRILLIC SMALL LETTER SHORT U */ + { 0x06AF, 0x045F }, /* Cyrillic_dzhe џ CYRILLIC SMALL LETTER DZHE */ + { 0x06B0, 0x2116 }, /* numerosign № NUMERO SIGN */ + { 0x06B1, 0x0402 }, /* Serbian_DJE Ђ CYRILLIC CAPITAL LETTER DJE */ + { 0x06B2, 0x0403 }, /* Macedonia_GJE Ѓ CYRILLIC CAPITAL LETTER GJE */ + { 0x06B3, 0x0401 }, /* Cyrillic_IO Ё CYRILLIC CAPITAL LETTER IO */ + { 0x06B4, 0x0404 }, /* Ukrainian_IE Є CYRILLIC CAPITAL LETTER UKRAINIAN IE */ + { 0x06B5, 0x0405 }, /* Macedonia_DSE Ѕ CYRILLIC CAPITAL LETTER DZE */ + { 0x06B6, 0x0406 }, /* Ukrainian_I І CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I */ + { 0x06B7, 0x0407 }, /* Ukrainian_YI Ї CYRILLIC CAPITAL LETTER YI */ + { 0x06B8, 0x0408 }, /* Cyrillic_JE Ј CYRILLIC CAPITAL LETTER JE */ + { 0x06B9, 0x0409 }, /* Cyrillic_LJE Љ CYRILLIC CAPITAL LETTER LJE */ + { 0x06BA, 0x040A }, /* Cyrillic_NJE Њ CYRILLIC CAPITAL LETTER NJE */ + { 0x06BB, 0x040B }, /* Serbian_TSHE Ћ CYRILLIC CAPITAL LETTER TSHE */ + { 0x06BC, 0x040C }, /* Macedonia_KJE Ќ CYRILLIC CAPITAL LETTER KJE */ + { 0x06BE, 0x040E }, /* Byelorussian_SHORTU Ў CYRILLIC CAPITAL LETTER SHORT U */ + { 0x06BF, 0x040F }, /* Cyrillic_DZHE Џ CYRILLIC CAPITAL LETTER DZHE */ + { 0x06C0, 0x044E }, /* Cyrillic_yu ю CYRILLIC SMALL LETTER YU */ + { 0x06C1, 0x0430 }, /* Cyrillic_a а CYRILLIC SMALL LETTER A */ + { 0x06C2, 0x0431 }, /* Cyrillic_be б CYRILLIC SMALL LETTER BE */ + { 0x06C3, 0x0446 }, /* Cyrillic_tse ц CYRILLIC SMALL LETTER TSE */ + { 0x06C4, 0x0434 }, /* Cyrillic_de д CYRILLIC SMALL LETTER DE */ + { 0x06C5, 0x0435 }, /* Cyrillic_ie е CYRILLIC SMALL LETTER IE */ + { 0x06C6, 0x0444 }, /* Cyrillic_ef ф CYRILLIC SMALL LETTER EF */ + { 0x06C7, 0x0433 }, /* Cyrillic_ghe г CYRILLIC SMALL LETTER GHE */ + { 0x06C8, 0x0445 }, /* Cyrillic_ha х CYRILLIC SMALL LETTER HA */ + { 0x06C9, 0x0438 }, /* Cyrillic_i и CYRILLIC SMALL LETTER I */ + { 0x06CA, 0x0439 }, /* Cyrillic_shorti й CYRILLIC SMALL LETTER SHORT I */ + { 0x06CB, 0x043A }, /* Cyrillic_ka к CYRILLIC SMALL LETTER KA */ + { 0x06CC, 0x043B }, /* Cyrillic_el л CYRILLIC SMALL LETTER EL */ + { 0x06CD, 0x043C }, /* Cyrillic_em м CYRILLIC SMALL LETTER EM */ + { 0x06CE, 0x043D }, /* Cyrillic_en н CYRILLIC SMALL LETTER EN */ + { 0x06CF, 0x043E }, /* Cyrillic_o о CYRILLIC SMALL LETTER O */ + { 0x06D0, 0x043F }, /* Cyrillic_pe п CYRILLIC SMALL LETTER PE */ + { 0x06D1, 0x044F }, /* Cyrillic_ya я CYRILLIC SMALL LETTER YA */ + { 0x06D2, 0x0440 }, /* Cyrillic_er р CYRILLIC SMALL LETTER ER */ + { 0x06D3, 0x0441 }, /* Cyrillic_es с CYRILLIC SMALL LETTER ES */ + { 0x06D4, 0x0442 }, /* Cyrillic_te т CYRILLIC SMALL LETTER TE */ + { 0x06D5, 0x0443 }, /* Cyrillic_u у CYRILLIC SMALL LETTER U */ + { 0x06D6, 0x0436 }, /* Cyrillic_zhe ж CYRILLIC SMALL LETTER ZHE */ + { 0x06D7, 0x0432 }, /* Cyrillic_ve в CYRILLIC SMALL LETTER VE */ + { 0x06D8, 0x044C }, /* Cyrillic_softsign ь CYRILLIC SMALL LETTER SOFT SIGN */ + { 0x06D9, 0x044B }, /* Cyrillic_yeru ы CYRILLIC SMALL LETTER YERU */ + { 0x06DA, 0x0437 }, /* Cyrillic_ze з CYRILLIC SMALL LETTER ZE */ + { 0x06DB, 0x0448 }, /* Cyrillic_sha ш CYRILLIC SMALL LETTER SHA */ + { 0x06DC, 0x044D }, /* Cyrillic_e э CYRILLIC SMALL LETTER E */ + { 0x06DD, 0x0449 }, /* Cyrillic_shcha щ CYRILLIC SMALL LETTER SHCHA */ + { 0x06DE, 0x0447 }, /* Cyrillic_che ч CYRILLIC SMALL LETTER CHE */ + { 0x06DF, 0x044A }, /* Cyrillic_hardsign ъ CYRILLIC SMALL LETTER HARD SIGN */ + { 0x06E0, 0x042E }, /* Cyrillic_YU Ю CYRILLIC CAPITAL LETTER YU */ + { 0x06E1, 0x0410 }, /* Cyrillic_A А CYRILLIC CAPITAL LETTER A */ + { 0x06E2, 0x0411 }, /* Cyrillic_BE Б CYRILLIC CAPITAL LETTER BE */ + { 0x06E3, 0x0426 }, /* Cyrillic_TSE Ц CYRILLIC CAPITAL LETTER TSE */ + { 0x06E4, 0x0414 }, /* Cyrillic_DE Д CYRILLIC CAPITAL LETTER DE */ + { 0x06E5, 0x0415 }, /* Cyrillic_IE Е CYRILLIC CAPITAL LETTER IE */ + { 0x06E6, 0x0424 }, /* Cyrillic_EF Ф CYRILLIC CAPITAL LETTER EF */ + { 0x06E7, 0x0413 }, /* Cyrillic_GHE Г CYRILLIC CAPITAL LETTER GHE */ + { 0x06E8, 0x0425 }, /* Cyrillic_HA Х CYRILLIC CAPITAL LETTER HA */ + { 0x06E9, 0x0418 }, /* Cyrillic_I И CYRILLIC CAPITAL LETTER I */ + { 0x06EA, 0x0419 }, /* Cyrillic_SHORTI Й CYRILLIC CAPITAL LETTER SHORT I */ + { 0x06EB, 0x041A }, /* Cyrillic_KA К CYRILLIC CAPITAL LETTER KA */ + { 0x06EC, 0x041B }, /* Cyrillic_EL Л CYRILLIC CAPITAL LETTER EL */ + { 0x06ED, 0x041C }, /* Cyrillic_EM М CYRILLIC CAPITAL LETTER EM */ + { 0x06EE, 0x041D }, /* Cyrillic_EN Н CYRILLIC CAPITAL LETTER EN */ + { 0x06EF, 0x041E }, /* Cyrillic_O О CYRILLIC CAPITAL LETTER O */ + { 0x06F0, 0x041F }, /* Cyrillic_PE П CYRILLIC CAPITAL LETTER PE */ + { 0x06F1, 0x042F }, /* Cyrillic_YA Я CYRILLIC CAPITAL LETTER YA */ + { 0x06F2, 0x0420 }, /* Cyrillic_ER Р CYRILLIC CAPITAL LETTER ER */ + { 0x06F3, 0x0421 }, /* Cyrillic_ES С CYRILLIC CAPITAL LETTER ES */ + { 0x06F4, 0x0422 }, /* Cyrillic_TE Т CYRILLIC CAPITAL LETTER TE */ + { 0x06F5, 0x0423 }, /* Cyrillic_U У CYRILLIC CAPITAL LETTER U */ + { 0x06F6, 0x0416 }, /* Cyrillic_ZHE Ж CYRILLIC CAPITAL LETTER ZHE */ + { 0x06F7, 0x0412 }, /* Cyrillic_VE В CYRILLIC CAPITAL LETTER VE */ + { 0x06F8, 0x042C }, /* Cyrillic_SOFTSIGN Ь CYRILLIC CAPITAL LETTER SOFT SIGN */ + { 0x06F9, 0x042B }, /* Cyrillic_YERU Ы CYRILLIC CAPITAL LETTER YERU */ + { 0x06FA, 0x0417 }, /* Cyrillic_ZE З CYRILLIC CAPITAL LETTER ZE */ + { 0x06FB, 0x0428 }, /* Cyrillic_SHA Ш CYRILLIC CAPITAL LETTER SHA */ + { 0x06FC, 0x042D }, /* Cyrillic_E Э CYRILLIC CAPITAL LETTER E */ + { 0x06FD, 0x0429 }, /* Cyrillic_SHCHA Щ CYRILLIC CAPITAL LETTER SHCHA */ + { 0x06FE, 0x0427 }, /* Cyrillic_CHE Ч CYRILLIC CAPITAL LETTER CHE */ + { 0x06FF, 0x042A }, /* Cyrillic_HARDSIGN Ъ CYRILLIC CAPITAL LETTER HARD SIGN */ + { 0x07A1, 0x0386 }, /* Greek_ALPHAaccent Ά GREEK CAPITAL LETTER ALPHA WITH TONOS */ + { 0x07A2, 0x0388 }, /* Greek_EPSILONaccent Έ GREEK CAPITAL LETTER EPSILON WITH TONOS */ + { 0x07A3, 0x0389 }, /* Greek_ETAaccent Ή GREEK CAPITAL LETTER ETA WITH TONOS */ + { 0x07A4, 0x038A }, /* Greek_IOTAaccent Ί GREEK CAPITAL LETTER IOTA WITH TONOS */ + { 0x07A5, 0x03AA }, /* Greek_IOTAdiaeresis Ϊ GREEK CAPITAL LETTER IOTA WITH DIALYTIKA */ + { 0x07A7, 0x038C }, /* Greek_OMICRONaccent Ό GREEK CAPITAL LETTER OMICRON WITH TONOS */ + { 0x07A8, 0x038E }, /* Greek_UPSILONaccent Ύ GREEK CAPITAL LETTER UPSILON WITH TONOS */ + { 0x07A9, 0x03AB }, /* Greek_UPSILONdieresis Ϋ GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA */ + { 0x07AB, 0x038F }, /* Greek_OMEGAaccent Ώ GREEK CAPITAL LETTER OMEGA WITH TONOS */ + { 0x07AE, 0x0385 }, /* Greek_accentdieresis ΅ GREEK DIALYTIKA TONOS */ + { 0x07AF, 0x2015 }, /* Greek_horizbar ― HORIZONTAL BAR */ + { 0x07B1, 0x03AC }, /* Greek_alphaaccent ά GREEK SMALL LETTER ALPHA WITH TONOS */ + { 0x07B2, 0x03AD }, /* Greek_epsilonaccent έ GREEK SMALL LETTER EPSILON WITH TONOS */ + { 0x07B3, 0x03AE }, /* Greek_etaaccent ή GREEK SMALL LETTER ETA WITH TONOS */ + { 0x07B4, 0x03AF }, /* Greek_iotaaccent ί GREEK SMALL LETTER IOTA WITH TONOS */ + { 0x07B5, 0x03CA }, /* Greek_iotadieresis ϊ GREEK SMALL LETTER IOTA WITH DIALYTIKA */ + { 0x07B6, 0x0390 }, /* Greek_iotaaccentdieresis ΐ GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS */ + { 0x07B7, 0x03CC }, /* Greek_omicronaccent ό GREEK SMALL LETTER OMICRON WITH TONOS */ + { 0x07B8, 0x03CD }, /* Greek_upsilonaccent ύ GREEK SMALL LETTER UPSILON WITH TONOS */ + { 0x07B9, 0x03CB }, /* Greek_upsilondieresis ϋ GREEK SMALL LETTER UPSILON WITH DIALYTIKA */ + { 0x07BA, 0x03B0 }, /* Greek_upsilonaccentdieresis ΰ GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS */ + { 0x07BB, 0x03CE }, /* Greek_omegaaccent ώ GREEK SMALL LETTER OMEGA WITH TONOS */ + { 0x07C1, 0x0391 }, /* Greek_ALPHA Α GREEK CAPITAL LETTER ALPHA */ + { 0x07C2, 0x0392 }, /* Greek_BETA Β GREEK CAPITAL LETTER BETA */ + { 0x07C3, 0x0393 }, /* Greek_GAMMA Γ GREEK CAPITAL LETTER GAMMA */ + { 0x07C4, 0x0394 }, /* Greek_DELTA Δ GREEK CAPITAL LETTER DELTA */ + { 0x07C5, 0x0395 }, /* Greek_EPSILON Ε GREEK CAPITAL LETTER EPSILON */ + { 0x07C6, 0x0396 }, /* Greek_ZETA Ζ GREEK CAPITAL LETTER ZETA */ + { 0x07C7, 0x0397 }, /* Greek_ETA Η GREEK CAPITAL LETTER ETA */ + { 0x07C8, 0x0398 }, /* Greek_THETA Θ GREEK CAPITAL LETTER THETA */ + { 0x07C9, 0x0399 }, /* Greek_IOTA Ι GREEK CAPITAL LETTER IOTA */ + { 0x07CA, 0x039A }, /* Greek_KAPPA Κ GREEK CAPITAL LETTER KAPPA */ + { 0x07CB, 0x039B }, /* Greek_LAMBDA Λ GREEK CAPITAL LETTER LAMDA */ + { 0x07CC, 0x039C }, /* Greek_MU Μ GREEK CAPITAL LETTER MU */ + { 0x07CD, 0x039D }, /* Greek_NU Ν GREEK CAPITAL LETTER NU */ + { 0x07CE, 0x039E }, /* Greek_XI Ξ GREEK CAPITAL LETTER XI */ + { 0x07CF, 0x039F }, /* Greek_OMICRON Ο GREEK CAPITAL LETTER OMICRON */ + { 0x07D0, 0x03A0 }, /* Greek_PI Π GREEK CAPITAL LETTER PI */ + { 0x07D1, 0x03A1 }, /* Greek_RHO Ρ GREEK CAPITAL LETTER RHO */ + { 0x07D2, 0x03A3 }, /* Greek_SIGMA Σ GREEK CAPITAL LETTER SIGMA */ + { 0x07D4, 0x03A4 }, /* Greek_TAU Τ GREEK CAPITAL LETTER TAU */ + { 0x07D5, 0x03A5 }, /* Greek_UPSILON Υ GREEK CAPITAL LETTER UPSILON */ + { 0x07D6, 0x03A6 }, /* Greek_PHI Φ GREEK CAPITAL LETTER PHI */ + { 0x07D7, 0x03A7 }, /* Greek_CHI Χ GREEK CAPITAL LETTER CHI */ + { 0x07D8, 0x03A8 }, /* Greek_PSI Ψ GREEK CAPITAL LETTER PSI */ + { 0x07D9, 0x03A9 }, /* Greek_OMEGA Ω GREEK CAPITAL LETTER OMEGA */ + { 0x07E1, 0x03B1 }, /* Greek_alpha α GREEK SMALL LETTER ALPHA */ + { 0x07E2, 0x03B2 }, /* Greek_beta β GREEK SMALL LETTER BETA */ + { 0x07E3, 0x03B3 }, /* Greek_gamma γ GREEK SMALL LETTER GAMMA */ + { 0x07E4, 0x03B4 }, /* Greek_delta δ GREEK SMALL LETTER DELTA */ + { 0x07E5, 0x03B5 }, /* Greek_epsilon ε GREEK SMALL LETTER EPSILON */ + { 0x07E6, 0x03B6 }, /* Greek_zeta ζ GREEK SMALL LETTER ZETA */ + { 0x07E7, 0x03B7 }, /* Greek_eta η GREEK SMALL LETTER ETA */ + { 0x07E8, 0x03B8 }, /* Greek_theta θ GREEK SMALL LETTER THETA */ + { 0x07E9, 0x03B9 }, /* Greek_iota ι GREEK SMALL LETTER IOTA */ + { 0x07EA, 0x03BA }, /* Greek_kappa κ GREEK SMALL LETTER KAPPA */ + { 0x07EB, 0x03BB }, /* Greek_lambda λ GREEK SMALL LETTER LAMDA */ + { 0x07EC, 0x03BC }, /* Greek_mu μ GREEK SMALL LETTER MU */ + { 0x07ED, 0x03BD }, /* Greek_nu ν GREEK SMALL LETTER NU */ + { 0x07EE, 0x03BE }, /* Greek_xi ξ GREEK SMALL LETTER XI */ + { 0x07EF, 0x03BF }, /* Greek_omicron ο GREEK SMALL LETTER OMICRON */ + { 0x07F0, 0x03C0 }, /* Greek_pi π GREEK SMALL LETTER PI */ + { 0x07F1, 0x03C1 }, /* Greek_rho ρ GREEK SMALL LETTER RHO */ + { 0x07F2, 0x03C3 }, /* Greek_sigma σ GREEK SMALL LETTER SIGMA */ + { 0x07F3, 0x03C2 }, /* Greek_finalsmallsigma ς GREEK SMALL LETTER FINAL SIGMA */ + { 0x07F4, 0x03C4 }, /* Greek_tau τ GREEK SMALL LETTER TAU */ + { 0x07F5, 0x03C5 }, /* Greek_upsilon υ GREEK SMALL LETTER UPSILON */ + { 0x07F6, 0x03C6 }, /* Greek_phi φ GREEK SMALL LETTER PHI */ + { 0x07F7, 0x03C7 }, /* Greek_chi χ GREEK SMALL LETTER CHI */ + { 0x07F8, 0x03C8 }, /* Greek_psi ψ GREEK SMALL LETTER PSI */ + { 0x07F9, 0x03C9 }, /* Greek_omega ω GREEK SMALL LETTER OMEGA */ + { 0x08A1, 0x23B7 }, /* leftradical ⎷ ??? */ + { 0x08A2, 0x250C }, /* topleftradical ┌ BOX DRAWINGS LIGHT DOWN AND RIGHT */ + { 0x08A3, 0x2500 }, /* horizconnector ─ BOX DRAWINGS LIGHT HORIZONTAL */ + { 0x08A4, 0x2320 }, /* topintegral ⌠ TOP HALF INTEGRAL */ + { 0x08A5, 0x2321 }, /* botintegral ⌡ BOTTOM HALF INTEGRAL */ + { 0x08A6, 0x2502 }, /* vertconnector │ BOX DRAWINGS LIGHT VERTICAL */ + { 0x08A7, 0x23A1 }, /* topleftsqbracket ⎡ ??? */ + { 0x08A8, 0x23A3 }, /* botleftsqbracket ⎣ ??? */ + { 0x08A9, 0x23A4 }, /* toprightsqbracket ⎤ ??? */ + { 0x08AA, 0x23A6 }, /* botrightsqbracket ⎦ ??? */ + { 0x08AB, 0x239B }, /* topleftparens ⎛ ??? */ + { 0x08AC, 0x239D }, /* botleftparens ⎝ ??? */ + { 0x08AD, 0x239E }, /* toprightparens ⎞ ??? */ + { 0x08AE, 0x23A0 }, /* botrightparens ⎠ ??? */ + { 0x08AF, 0x23A8 }, /* leftmiddlecurlybrace ⎨ ??? */ + { 0x08B0, 0x23AC }, /* rightmiddlecurlybrace ⎬ ??? */ +/* 0x08B1 topleftsummation ? ??? */ +/* 0x08B2 botleftsummation ? ??? */ +/* 0x08B3 topvertsummationconnector ? ??? */ +/* 0x08B4 botvertsummationconnector ? ??? */ +/* 0x08B5 toprightsummation ? ??? */ +/* 0x08B6 botrightsummation ? ??? */ +/* 0x08B7 rightmiddlesummation ? ??? */ + { 0x08BC, 0x2264 }, /* lessthanequal ≤ LESS-THAN OR EQUAL TO */ + { 0x08BD, 0x2260 }, /* notequal ≠ NOT EQUAL TO */ + { 0x08BE, 0x2265 }, /* greaterthanequal ≥ GREATER-THAN OR EQUAL TO */ + { 0x08BF, 0x222B }, /* integral ∫ INTEGRAL */ + { 0x08C0, 0x2234 }, /* therefore ∴ THEREFORE */ + { 0x08C1, 0x221D }, /* variation ∝ PROPORTIONAL TO */ + { 0x08C2, 0x221E }, /* infinity ∞ INFINITY */ + { 0x08C5, 0x2207 }, /* nabla ∇ NABLA */ + { 0x08C8, 0x223C }, /* approximate ∼ TILDE OPERATOR */ + { 0x08C9, 0x2243 }, /* similarequal ≃ ASYMPTOTICALLY EQUAL TO */ + { 0x08CD, 0x21D4 }, /* ifonlyif ⇔ LEFT RIGHT DOUBLE ARROW */ + { 0x08CE, 0x21D2 }, /* implies ⇒ RIGHTWARDS DOUBLE ARROW */ + { 0x08CF, 0x2261 }, /* identical ≡ IDENTICAL TO */ + { 0x08D6, 0x221A }, /* radical √ SQUARE ROOT */ + { 0x08DA, 0x2282 }, /* includedin ⊂ SUBSET OF */ + { 0x08DB, 0x2283 }, /* includes ⊃ SUPERSET OF */ + { 0x08DC, 0x2229 }, /* intersection ∩ INTERSECTION */ + { 0x08DD, 0x222A }, /* union ∪ UNION */ + { 0x08DE, 0x2227 }, /* logicaland ∧ LOGICAL AND */ + { 0x08DF, 0x2228 }, /* logicalor ∨ LOGICAL OR */ + { 0x08EF, 0x2202 }, /* partialderivative ∂ PARTIAL DIFFERENTIAL */ + { 0x08F6, 0x0192 }, /* function ƒ LATIN SMALL LETTER F WITH HOOK */ + { 0x08FB, 0x2190 }, /* leftarrow ← LEFTWARDS ARROW */ + { 0x08FC, 0x2191 }, /* uparrow ↑ UPWARDS ARROW */ + { 0x08FD, 0x2192 }, /* rightarrow → RIGHTWARDS ARROW */ + { 0x08FE, 0x2193 }, /* downarrow ↓ DOWNWARDS ARROW */ +/* 0x09DF blank ? ??? */ + { 0x09E0, 0x25C6 }, /* soliddiamond ◆ BLACK DIAMOND */ + { 0x09E1, 0x2592 }, /* checkerboard ▒ MEDIUM SHADE */ + { 0x09E2, 0x2409 }, /* ht ␉ SYMBOL FOR HORIZONTAL TABULATION */ + { 0x09E3, 0x240C }, /* ff ␌ SYMBOL FOR FORM FEED */ + { 0x09E4, 0x240D }, /* cr ␍ SYMBOL FOR CARRIAGE RETURN */ + { 0x09E5, 0x240A }, /* lf ␊ SYMBOL FOR LINE FEED */ + { 0x09E8, 0x2424 }, /* nl ␤ SYMBOL FOR NEWLINE */ + { 0x09E9, 0x240B }, /* vt ␋ SYMBOL FOR VERTICAL TABULATION */ + { 0x09EA, 0x2518 }, /* lowrightcorner ┘ BOX DRAWINGS LIGHT UP AND LEFT */ + { 0x09EB, 0x2510 }, /* uprightcorner ┐ BOX DRAWINGS LIGHT DOWN AND LEFT */ + { 0x09EC, 0x250C }, /* upleftcorner ┌ BOX DRAWINGS LIGHT DOWN AND RIGHT */ + { 0x09ED, 0x2514 }, /* lowleftcorner └ BOX DRAWINGS LIGHT UP AND RIGHT */ + { 0x09EE, 0x253C }, /* crossinglines ┼ BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */ + { 0x09EF, 0x23BA }, /* horizlinescan1 ⎺ HORIZONTAL SCAN LINE-1 (Unicode 3.2 draft) */ + { 0x09F0, 0x23BB }, /* horizlinescan3 ⎻ HORIZONTAL SCAN LINE-3 (Unicode 3.2 draft) */ + { 0x09F1, 0x2500 }, /* horizlinescan5 ─ BOX DRAWINGS LIGHT HORIZONTAL */ + { 0x09F2, 0x23BC }, /* horizlinescan7 ⎼ HORIZONTAL SCAN LINE-7 (Unicode 3.2 draft) */ + { 0x09F3, 0x23BD }, /* horizlinescan9 ⎽ HORIZONTAL SCAN LINE-9 (Unicode 3.2 draft) */ + { 0x09F4, 0x251C }, /* leftt ├ BOX DRAWINGS LIGHT VERTICAL AND RIGHT */ + { 0x09F5, 0x2524 }, /* rightt ┤ BOX DRAWINGS LIGHT VERTICAL AND LEFT */ + { 0x09F6, 0x2534 }, /* bott ┴ BOX DRAWINGS LIGHT UP AND HORIZONTAL */ + { 0x09F7, 0x252C }, /* topt ┬ BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */ + { 0x09F8, 0x2502 }, /* vertbar │ BOX DRAWINGS LIGHT VERTICAL */ + { 0x0AA1, 0x2003 }, /* emspace   EM SPACE */ + { 0x0AA2, 0x2002 }, /* enspace   EN SPACE */ + { 0x0AA3, 0x2004 }, /* em3space   THREE-PER-EM SPACE */ + { 0x0AA4, 0x2005 }, /* em4space   FOUR-PER-EM SPACE */ + { 0x0AA5, 0x2007 }, /* digitspace   FIGURE SPACE */ + { 0x0AA6, 0x2008 }, /* punctspace   PUNCTUATION SPACE */ + { 0x0AA7, 0x2009 }, /* thinspace   THIN SPACE */ + { 0x0AA8, 0x200A }, /* hairspace   HAIR SPACE */ + { 0x0AA9, 0x2014 }, /* emdash — EM DASH */ + { 0x0AAA, 0x2013 }, /* endash – EN DASH */ +/* 0x0AAC signifblank ? ??? */ + { 0x0AAE, 0x2026 }, /* ellipsis … HORIZONTAL ELLIPSIS */ + { 0x0AAF, 0x2025 }, /* doubbaselinedot ‥ TWO DOT LEADER */ + { 0x0AB0, 0x2153 }, /* onethird ⅓ VULGAR FRACTION ONE THIRD */ + { 0x0AB1, 0x2154 }, /* twothirds ⅔ VULGAR FRACTION TWO THIRDS */ + { 0x0AB2, 0x2155 }, /* onefifth ⅕ VULGAR FRACTION ONE FIFTH */ + { 0x0AB3, 0x2156 }, /* twofifths ⅖ VULGAR FRACTION TWO FIFTHS */ + { 0x0AB4, 0x2157 }, /* threefifths ⅗ VULGAR FRACTION THREE FIFTHS */ + { 0x0AB5, 0x2158 }, /* fourfifths ⅘ VULGAR FRACTION FOUR FIFTHS */ + { 0x0AB6, 0x2159 }, /* onesixth ⅙ VULGAR FRACTION ONE SIXTH */ + { 0x0AB7, 0x215A }, /* fivesixths ⅚ VULGAR FRACTION FIVE SIXTHS */ + { 0x0AB8, 0x2105 }, /* careof ℅ CARE OF */ + { 0x0ABB, 0x2012 }, /* figdash ‒ FIGURE DASH */ + { 0x0ABC, 0x2329 }, /* leftanglebracket 〈 LEFT-POINTING ANGLE BRACKET */ +/* 0x0ABD decimalpoint ? ??? */ + { 0x0ABE, 0x232A }, /* rightanglebracket 〉 RIGHT-POINTING ANGLE BRACKET */ +/* 0x0ABF marker ? ??? */ + { 0x0AC3, 0x215B }, /* oneeighth ⅛ VULGAR FRACTION ONE EIGHTH */ + { 0x0AC4, 0x215C }, /* threeeighths ⅜ VULGAR FRACTION THREE EIGHTHS */ + { 0x0AC5, 0x215D }, /* fiveeighths ⅝ VULGAR FRACTION FIVE EIGHTHS */ + { 0x0AC6, 0x215E }, /* seveneighths ⅞ VULGAR FRACTION SEVEN EIGHTHS */ + { 0x0AC9, 0x2122 }, /* trademark ™ TRADE MARK SIGN */ + { 0x0ACA, 0x2613 }, /* signaturemark ☓ SALTIRE */ +/* 0x0ACB trademarkincircle ? ??? */ + { 0x0ACC, 0x25C1 }, /* leftopentriangle ◁ WHITE LEFT-POINTING TRIANGLE */ + { 0x0ACD, 0x25B7 }, /* rightopentriangle ▷ WHITE RIGHT-POINTING TRIANGLE */ + { 0x0ACE, 0x25CB }, /* emopencircle ○ WHITE CIRCLE */ + { 0x0ACF, 0x25AF }, /* emopenrectangle ▯ WHITE VERTICAL RECTANGLE */ + { 0x0AD0, 0x2018 }, /* leftsinglequotemark ‘ LEFT SINGLE QUOTATION MARK */ + { 0x0AD1, 0x2019 }, /* rightsinglequotemark ’ RIGHT SINGLE QUOTATION MARK */ + { 0x0AD2, 0x201C }, /* leftdoublequotemark “ LEFT DOUBLE QUOTATION MARK */ + { 0x0AD3, 0x201D }, /* rightdoublequotemark ” RIGHT DOUBLE QUOTATION MARK */ + { 0x0AD4, 0x211E }, /* prescription ℞ PRESCRIPTION TAKE */ + { 0x0AD6, 0x2032 }, /* minutes ′ PRIME */ + { 0x0AD7, 0x2033 }, /* seconds ″ DOUBLE PRIME */ + { 0x0AD9, 0x271D }, /* latincross ✝ LATIN CROSS */ +/* 0x0ADA hexagram ? ??? */ + { 0x0ADB, 0x25AC }, /* filledrectbullet ▬ BLACK RECTANGLE */ + { 0x0ADC, 0x25C0 }, /* filledlefttribullet ◀ BLACK LEFT-POINTING TRIANGLE */ + { 0x0ADD, 0x25B6 }, /* filledrighttribullet ▶ BLACK RIGHT-POINTING TRIANGLE */ + { 0x0ADE, 0x25CF }, /* emfilledcircle ● BLACK CIRCLE */ + { 0x0ADF, 0x25AE }, /* emfilledrect ▮ BLACK VERTICAL RECTANGLE */ + { 0x0AE0, 0x25E6 }, /* enopencircbullet ◦ WHITE BULLET */ + { 0x0AE1, 0x25AB }, /* enopensquarebullet ▫ WHITE SMALL SQUARE */ + { 0x0AE2, 0x25AD }, /* openrectbullet ▭ WHITE RECTANGLE */ + { 0x0AE3, 0x25B3 }, /* opentribulletup △ WHITE UP-POINTING TRIANGLE */ + { 0x0AE4, 0x25BD }, /* opentribulletdown ▽ WHITE DOWN-POINTING TRIANGLE */ + { 0x0AE5, 0x2606 }, /* openstar ☆ WHITE STAR */ + { 0x0AE6, 0x2022 }, /* enfilledcircbullet • BULLET */ + { 0x0AE7, 0x25AA }, /* enfilledsqbullet ▪ BLACK SMALL SQUARE */ + { 0x0AE8, 0x25B2 }, /* filledtribulletup ▲ BLACK UP-POINTING TRIANGLE */ + { 0x0AE9, 0x25BC }, /* filledtribulletdown ▼ BLACK DOWN-POINTING TRIANGLE */ + { 0x0AEA, 0x261C }, /* leftpointer ☜ WHITE LEFT POINTING INDEX */ + { 0x0AEB, 0x261E }, /* rightpointer ☞ WHITE RIGHT POINTING INDEX */ + { 0x0AEC, 0x2663 }, /* club ♣ BLACK CLUB SUIT */ + { 0x0AED, 0x2666 }, /* diamond ♦ BLACK DIAMOND SUIT */ + { 0x0AEE, 0x2665 }, /* heart ♥ BLACK HEART SUIT */ + { 0x0AF0, 0x2720 }, /* maltesecross ✠ MALTESE CROSS */ + { 0x0AF1, 0x2020 }, /* dagger † DAGGER */ + { 0x0AF2, 0x2021 }, /* doubledagger ‡ DOUBLE DAGGER */ + { 0x0AF3, 0x2713 }, /* checkmark ✓ CHECK MARK */ + { 0x0AF4, 0x2717 }, /* ballotcross ✗ BALLOT X */ + { 0x0AF5, 0x266F }, /* musicalsharp ♯ MUSIC SHARP SIGN */ + { 0x0AF6, 0x266D }, /* musicalflat ♭ MUSIC FLAT SIGN */ + { 0x0AF7, 0x2642 }, /* malesymbol ♂ MALE SIGN */ + { 0x0AF8, 0x2640 }, /* femalesymbol ♀ FEMALE SIGN */ + { 0x0AF9, 0x260E }, /* telephone ☎ BLACK TELEPHONE */ + { 0x0AFA, 0x2315 }, /* telephonerecorder ⌕ TELEPHONE RECORDER */ + { 0x0AFB, 0x2117 }, /* phonographcopyright ℗ SOUND RECORDING COPYRIGHT */ + { 0x0AFC, 0x2038 }, /* caret ‸ CARET */ + { 0x0AFD, 0x201A }, /* singlelowquotemark ‚ SINGLE LOW-9 QUOTATION MARK */ + { 0x0AFE, 0x201E }, /* doublelowquotemark „ DOUBLE LOW-9 QUOTATION MARK */ +/* 0x0AFF cursor ? ??? */ + { 0x0BA3, 0x003C }, /* leftcaret < LESS-THAN SIGN */ + { 0x0BA6, 0x003E }, /* rightcaret > GREATER-THAN SIGN */ + { 0x0BA8, 0x2228 }, /* downcaret ∨ LOGICAL OR */ + { 0x0BA9, 0x2227 }, /* upcaret ∧ LOGICAL AND */ + { 0x0BC0, 0x00AF }, /* overbar ¯ MACRON */ + { 0x0BC2, 0x22A5 }, /* downtack ⊥ UP TACK */ + { 0x0BC3, 0x2229 }, /* upshoe ∩ INTERSECTION */ + { 0x0BC4, 0x230A }, /* downstile ⌊ LEFT FLOOR */ + { 0x0BC6, 0x005F }, /* underbar _ LOW LINE */ + { 0x0BCA, 0x2218 }, /* jot ∘ RING OPERATOR */ + { 0x0BCC, 0x2395 }, /* quad ⎕ APL FUNCTIONAL SYMBOL QUAD */ + { 0x0BCE, 0x22A4 }, /* uptack ⊤ DOWN TACK */ + { 0x0BCF, 0x25CB }, /* circle ○ WHITE CIRCLE */ + { 0x0BD3, 0x2308 }, /* upstile ⌈ LEFT CEILING */ + { 0x0BD6, 0x222A }, /* downshoe ∪ UNION */ + { 0x0BD8, 0x2283 }, /* rightshoe ⊃ SUPERSET OF */ + { 0x0BDA, 0x2282 }, /* leftshoe ⊂ SUBSET OF */ + { 0x0BDC, 0x22A2 }, /* lefttack ⊢ RIGHT TACK */ + { 0x0BFC, 0x22A3 }, /* righttack ⊣ LEFT TACK */ + { 0x0CDF, 0x2017 }, /* hebrew_doublelowline ‗ DOUBLE LOW LINE */ + { 0x0CE0, 0x05D0 }, /* hebrew_aleph א HEBREW LETTER ALEF */ + { 0x0CE1, 0x05D1 }, /* hebrew_bet ב HEBREW LETTER BET */ + { 0x0CE2, 0x05D2 }, /* hebrew_gimel ג HEBREW LETTER GIMEL */ + { 0x0CE3, 0x05D3 }, /* hebrew_dalet ד HEBREW LETTER DALET */ + { 0x0CE4, 0x05D4 }, /* hebrew_he ה HEBREW LETTER HE */ + { 0x0CE5, 0x05D5 }, /* hebrew_waw ו HEBREW LETTER VAV */ + { 0x0CE6, 0x05D6 }, /* hebrew_zain ז HEBREW LETTER ZAYIN */ + { 0x0CE7, 0x05D7 }, /* hebrew_chet ח HEBREW LETTER HET */ + { 0x0CE8, 0x05D8 }, /* hebrew_tet ט HEBREW LETTER TET */ + { 0x0CE9, 0x05D9 }, /* hebrew_yod י HEBREW LETTER YOD */ + { 0x0CEA, 0x05DA }, /* hebrew_finalkaph ך HEBREW LETTER FINAL KAF */ + { 0x0CEB, 0x05DB }, /* hebrew_kaph כ HEBREW LETTER KAF */ + { 0x0CEC, 0x05DC }, /* hebrew_lamed ל HEBREW LETTER LAMED */ + { 0x0CED, 0x05DD }, /* hebrew_finalmem ם HEBREW LETTER FINAL MEM */ + { 0x0CEE, 0x05DE }, /* hebrew_mem מ HEBREW LETTER MEM */ + { 0x0CEF, 0x05DF }, /* hebrew_finalnun ן HEBREW LETTER FINAL NUN */ + { 0x0CF0, 0x05E0 }, /* hebrew_nun נ HEBREW LETTER NUN */ + { 0x0CF1, 0x05E1 }, /* hebrew_samech ס HEBREW LETTER SAMEKH */ + { 0x0CF2, 0x05E2 }, /* hebrew_ayin ע HEBREW LETTER AYIN */ + { 0x0CF3, 0x05E3 }, /* hebrew_finalpe ף HEBREW LETTER FINAL PE */ + { 0x0CF4, 0x05E4 }, /* hebrew_pe פ HEBREW LETTER PE */ + { 0x0CF5, 0x05E5 }, /* hebrew_finalzade ץ HEBREW LETTER FINAL TSADI */ + { 0x0CF6, 0x05E6 }, /* hebrew_zade צ HEBREW LETTER TSADI */ + { 0x0CF7, 0x05E7 }, /* hebrew_qoph ק HEBREW LETTER QOF */ + { 0x0CF8, 0x05E8 }, /* hebrew_resh ר HEBREW LETTER RESH */ + { 0x0CF9, 0x05E9 }, /* hebrew_shin ש HEBREW LETTER SHIN */ + { 0x0CFA, 0x05EA }, /* hebrew_taw ת HEBREW LETTER TAV */ + { 0x0DA1, 0x0E01 }, /* Thai_kokai ก THAI CHARACTER KO KAI */ + { 0x0DA2, 0x0E02 }, /* Thai_khokhai ข THAI CHARACTER KHO KHAI */ + { 0x0DA3, 0x0E03 }, /* Thai_khokhuat ฃ THAI CHARACTER KHO KHUAT */ + { 0x0DA4, 0x0E04 }, /* Thai_khokhwai ค THAI CHARACTER KHO KHWAI */ + { 0x0DA5, 0x0E05 }, /* Thai_khokhon ฅ THAI CHARACTER KHO KHON */ + { 0x0DA6, 0x0E06 }, /* Thai_khorakhang ฆ THAI CHARACTER KHO RAKHANG */ + { 0x0DA7, 0x0E07 }, /* Thai_ngongu ง THAI CHARACTER NGO NGU */ + { 0x0DA8, 0x0E08 }, /* Thai_chochan จ THAI CHARACTER CHO CHAN */ + { 0x0DA9, 0x0E09 }, /* Thai_choching ฉ THAI CHARACTER CHO CHING */ + { 0x0DAA, 0x0E0A }, /* Thai_chochang ช THAI CHARACTER CHO CHANG */ + { 0x0DAB, 0x0E0B }, /* Thai_soso ซ THAI CHARACTER SO SO */ + { 0x0DAC, 0x0E0C }, /* Thai_chochoe ฌ THAI CHARACTER CHO CHOE */ + { 0x0DAD, 0x0E0D }, /* Thai_yoying ญ THAI CHARACTER YO YING */ + { 0x0DAE, 0x0E0E }, /* Thai_dochada ฎ THAI CHARACTER DO CHADA */ + { 0x0DAF, 0x0E0F }, /* Thai_topatak ฏ THAI CHARACTER TO PATAK */ + { 0x0DB0, 0x0E10 }, /* Thai_thothan ฐ THAI CHARACTER THO THAN */ + { 0x0DB1, 0x0E11 }, /* Thai_thonangmontho ฑ THAI CHARACTER THO NANGMONTHO */ + { 0x0DB2, 0x0E12 }, /* Thai_thophuthao ฒ THAI CHARACTER THO PHUTHAO */ + { 0x0DB3, 0x0E13 }, /* Thai_nonen ณ THAI CHARACTER NO NEN */ + { 0x0DB4, 0x0E14 }, /* Thai_dodek ด THAI CHARACTER DO DEK */ + { 0x0DB5, 0x0E15 }, /* Thai_totao ต THAI CHARACTER TO TAO */ + { 0x0DB6, 0x0E16 }, /* Thai_thothung ถ THAI CHARACTER THO THUNG */ + { 0x0DB7, 0x0E17 }, /* Thai_thothahan ท THAI CHARACTER THO THAHAN */ + { 0x0DB8, 0x0E18 }, /* Thai_thothong ธ THAI CHARACTER THO THONG */ + { 0x0DB9, 0x0E19 }, /* Thai_nonu น THAI CHARACTER NO NU */ + { 0x0DBA, 0x0E1A }, /* Thai_bobaimai บ THAI CHARACTER BO BAIMAI */ + { 0x0DBB, 0x0E1B }, /* Thai_popla ป THAI CHARACTER PO PLA */ + { 0x0DBC, 0x0E1C }, /* Thai_phophung ผ THAI CHARACTER PHO PHUNG */ + { 0x0DBD, 0x0E1D }, /* Thai_fofa ฝ THAI CHARACTER FO FA */ + { 0x0DBE, 0x0E1E }, /* Thai_phophan พ THAI CHARACTER PHO PHAN */ + { 0x0DBF, 0x0E1F }, /* Thai_fofan ฟ THAI CHARACTER FO FAN */ + { 0x0DC0, 0x0E20 }, /* Thai_phosamphao ภ THAI CHARACTER PHO SAMPHAO */ + { 0x0DC1, 0x0E21 }, /* Thai_moma ม THAI CHARACTER MO MA */ + { 0x0DC2, 0x0E22 }, /* Thai_yoyak ย THAI CHARACTER YO YAK */ + { 0x0DC3, 0x0E23 }, /* Thai_rorua ร THAI CHARACTER RO RUA */ + { 0x0DC4, 0x0E24 }, /* Thai_ru ฤ THAI CHARACTER RU */ + { 0x0DC5, 0x0E25 }, /* Thai_loling ล THAI CHARACTER LO LING */ + { 0x0DC6, 0x0E26 }, /* Thai_lu ฦ THAI CHARACTER LU */ + { 0x0DC7, 0x0E27 }, /* Thai_wowaen ว THAI CHARACTER WO WAEN */ + { 0x0DC8, 0x0E28 }, /* Thai_sosala ศ THAI CHARACTER SO SALA */ + { 0x0DC9, 0x0E29 }, /* Thai_sorusi ษ THAI CHARACTER SO RUSI */ + { 0x0DCA, 0x0E2A }, /* Thai_sosua ส THAI CHARACTER SO SUA */ + { 0x0DCB, 0x0E2B }, /* Thai_hohip ห THAI CHARACTER HO HIP */ + { 0x0DCC, 0x0E2C }, /* Thai_lochula ฬ THAI CHARACTER LO CHULA */ + { 0x0DCD, 0x0E2D }, /* Thai_oang อ THAI CHARACTER O ANG */ + { 0x0DCE, 0x0E2E }, /* Thai_honokhuk ฮ THAI CHARACTER HO NOKHUK */ + { 0x0DCF, 0x0E2F }, /* Thai_paiyannoi ฯ THAI CHARACTER PAIYANNOI */ + { 0x0DD0, 0x0E30 }, /* Thai_saraa ะ THAI CHARACTER SARA A */ + { 0x0DD1, 0x0E31 }, /* Thai_maihanakat ั THAI CHARACTER MAI HAN-AKAT */ + { 0x0DD2, 0x0E32 }, /* Thai_saraaa า THAI CHARACTER SARA AA */ + { 0x0DD3, 0x0E33 }, /* Thai_saraam ำ THAI CHARACTER SARA AM */ + { 0x0DD4, 0x0E34 }, /* Thai_sarai ิ THAI CHARACTER SARA I */ + { 0x0DD5, 0x0E35 }, /* Thai_saraii ี THAI CHARACTER SARA II */ + { 0x0DD6, 0x0E36 }, /* Thai_saraue ึ THAI CHARACTER SARA UE */ + { 0x0DD7, 0x0E37 }, /* Thai_sarauee ื THAI CHARACTER SARA UEE */ + { 0x0DD8, 0x0E38 }, /* Thai_sarau ุ THAI CHARACTER SARA U */ + { 0x0DD9, 0x0E39 }, /* Thai_sarauu ู THAI CHARACTER SARA UU */ + { 0x0DDA, 0x0E3A }, /* Thai_phinthu ฺ THAI CHARACTER PHINTHU */ +/* 0x0DDE Thai_maihanakat_maitho ? ??? */ + { 0x0DDF, 0x0E3F }, /* Thai_baht ฿ THAI CURRENCY SYMBOL BAHT */ + { 0x0DE0, 0x0E40 }, /* Thai_sarae เ THAI CHARACTER SARA E */ + { 0x0DE1, 0x0E41 }, /* Thai_saraae แ THAI CHARACTER SARA AE */ + { 0x0DE2, 0x0E42 }, /* Thai_sarao โ THAI CHARACTER SARA O */ + { 0x0DE3, 0x0E43 }, /* Thai_saraaimaimuan ใ THAI CHARACTER SARA AI MAIMUAN */ + { 0x0DE4, 0x0E44 }, /* Thai_saraaimaimalai ไ THAI CHARACTER SARA AI MAIMALAI */ + { 0x0DE5, 0x0E45 }, /* Thai_lakkhangyao ๅ THAI CHARACTER LAKKHANGYAO */ + { 0x0DE6, 0x0E46 }, /* Thai_maiyamok ๆ THAI CHARACTER MAIYAMOK */ + { 0x0DE7, 0x0E47 }, /* Thai_maitaikhu ็ THAI CHARACTER MAITAIKHU */ + { 0x0DE8, 0x0E48 }, /* Thai_maiek ่ THAI CHARACTER MAI EK */ + { 0x0DE9, 0x0E49 }, /* Thai_maitho ้ THAI CHARACTER MAI THO */ + { 0x0DEA, 0x0E4A }, /* Thai_maitri ๊ THAI CHARACTER MAI TRI */ + { 0x0DEB, 0x0E4B }, /* Thai_maichattawa ๋ THAI CHARACTER MAI CHATTAWA */ + { 0x0DEC, 0x0E4C }, /* Thai_thanthakhat ์ THAI CHARACTER THANTHAKHAT */ + { 0x0DED, 0x0E4D }, /* Thai_nikhahit ํ THAI CHARACTER NIKHAHIT */ + { 0x0DF0, 0x0E50 }, /* Thai_leksun ๐ THAI DIGIT ZERO */ + { 0x0DF1, 0x0E51 }, /* Thai_leknung ๑ THAI DIGIT ONE */ + { 0x0DF2, 0x0E52 }, /* Thai_leksong ๒ THAI DIGIT TWO */ + { 0x0DF3, 0x0E53 }, /* Thai_leksam ๓ THAI DIGIT THREE */ + { 0x0DF4, 0x0E54 }, /* Thai_leksi ๔ THAI DIGIT FOUR */ + { 0x0DF5, 0x0E55 }, /* Thai_lekha ๕ THAI DIGIT FIVE */ + { 0x0DF6, 0x0E56 }, /* Thai_lekhok ๖ THAI DIGIT SIX */ + { 0x0DF7, 0x0E57 }, /* Thai_lekchet ๗ THAI DIGIT SEVEN */ + { 0x0DF8, 0x0E58 }, /* Thai_lekpaet ๘ THAI DIGIT EIGHT */ + { 0x0DF9, 0x0E59 }, /* Thai_lekkao ๙ THAI DIGIT NINE */ + { 0x0EA1, 0x3131 }, /* Hangul_Kiyeog ㄱ HANGUL LETTER KIYEOK */ + { 0x0EA2, 0x3132 }, /* Hangul_SsangKiyeog ㄲ HANGUL LETTER SSANGKIYEOK */ + { 0x0EA3, 0x3133 }, /* Hangul_KiyeogSios ㄳ HANGUL LETTER KIYEOK-SIOS */ + { 0x0EA4, 0x3134 }, /* Hangul_Nieun ㄴ HANGUL LETTER NIEUN */ + { 0x0EA5, 0x3135 }, /* Hangul_NieunJieuj ㄵ HANGUL LETTER NIEUN-CIEUC */ + { 0x0EA6, 0x3136 }, /* Hangul_NieunHieuh ㄶ HANGUL LETTER NIEUN-HIEUH */ + { 0x0EA7, 0x3137 }, /* Hangul_Dikeud ㄷ HANGUL LETTER TIKEUT */ + { 0x0EA8, 0x3138 }, /* Hangul_SsangDikeud ㄸ HANGUL LETTER SSANGTIKEUT */ + { 0x0EA9, 0x3139 }, /* Hangul_Rieul ㄹ HANGUL LETTER RIEUL */ + { 0x0EAA, 0x313A }, /* Hangul_RieulKiyeog ㄺ HANGUL LETTER RIEUL-KIYEOK */ + { 0x0EAB, 0x313B }, /* Hangul_RieulMieum ㄻ HANGUL LETTER RIEUL-MIEUM */ + { 0x0EAC, 0x313C }, /* Hangul_RieulPieub ㄼ HANGUL LETTER RIEUL-PIEUP */ + { 0x0EAD, 0x313D }, /* Hangul_RieulSios ㄽ HANGUL LETTER RIEUL-SIOS */ + { 0x0EAE, 0x313E }, /* Hangul_RieulTieut ㄾ HANGUL LETTER RIEUL-THIEUTH */ + { 0x0EAF, 0x313F }, /* Hangul_RieulPhieuf ㄿ HANGUL LETTER RIEUL-PHIEUPH */ + { 0x0EB0, 0x3140 }, /* Hangul_RieulHieuh ㅀ HANGUL LETTER RIEUL-HIEUH */ + { 0x0EB1, 0x3141 }, /* Hangul_Mieum ㅁ HANGUL LETTER MIEUM */ + { 0x0EB2, 0x3142 }, /* Hangul_Pieub ㅂ HANGUL LETTER PIEUP */ + { 0x0EB3, 0x3143 }, /* Hangul_SsangPieub ㅃ HANGUL LETTER SSANGPIEUP */ + { 0x0EB4, 0x3144 }, /* Hangul_PieubSios ㅄ HANGUL LETTER PIEUP-SIOS */ + { 0x0EB5, 0x3145 }, /* Hangul_Sios ㅅ HANGUL LETTER SIOS */ + { 0x0EB6, 0x3146 }, /* Hangul_SsangSios ㅆ HANGUL LETTER SSANGSIOS */ + { 0x0EB7, 0x3147 }, /* Hangul_Ieung ㅇ HANGUL LETTER IEUNG */ + { 0x0EB8, 0x3148 }, /* Hangul_Jieuj ㅈ HANGUL LETTER CIEUC */ + { 0x0EB9, 0x3149 }, /* Hangul_SsangJieuj ㅉ HANGUL LETTER SSANGCIEUC */ + { 0x0EBA, 0x314A }, /* Hangul_Cieuc ㅊ HANGUL LETTER CHIEUCH */ + { 0x0EBB, 0x314B }, /* Hangul_Khieuq ㅋ HANGUL LETTER KHIEUKH */ + { 0x0EBC, 0x314C }, /* Hangul_Tieut ㅌ HANGUL LETTER THIEUTH */ + { 0x0EBD, 0x314D }, /* Hangul_Phieuf ㅍ HANGUL LETTER PHIEUPH */ + { 0x0EBE, 0x314E }, /* Hangul_Hieuh ㅎ HANGUL LETTER HIEUH */ + { 0x0EBF, 0x314F }, /* Hangul_A ㅏ HANGUL LETTER A */ + { 0x0EC0, 0x3150 }, /* Hangul_AE ㅐ HANGUL LETTER AE */ + { 0x0EC1, 0x3151 }, /* Hangul_YA ㅑ HANGUL LETTER YA */ + { 0x0EC2, 0x3152 }, /* Hangul_YAE ㅒ HANGUL LETTER YAE */ + { 0x0EC3, 0x3153 }, /* Hangul_EO ㅓ HANGUL LETTER EO */ + { 0x0EC4, 0x3154 }, /* Hangul_E ㅔ HANGUL LETTER E */ + { 0x0EC5, 0x3155 }, /* Hangul_YEO ㅕ HANGUL LETTER YEO */ + { 0x0EC6, 0x3156 }, /* Hangul_YE ㅖ HANGUL LETTER YE */ + { 0x0EC7, 0x3157 }, /* Hangul_O ㅗ HANGUL LETTER O */ + { 0x0EC8, 0x3158 }, /* Hangul_WA ㅘ HANGUL LETTER WA */ + { 0x0EC9, 0x3159 }, /* Hangul_WAE ㅙ HANGUL LETTER WAE */ + { 0x0ECA, 0x315A }, /* Hangul_OE ㅚ HANGUL LETTER OE */ + { 0x0ECB, 0x315B }, /* Hangul_YO ㅛ HANGUL LETTER YO */ + { 0x0ECC, 0x315C }, /* Hangul_U ㅜ HANGUL LETTER U */ + { 0x0ECD, 0x315D }, /* Hangul_WEO ㅝ HANGUL LETTER WEO */ + { 0x0ECE, 0x315E }, /* Hangul_WE ㅞ HANGUL LETTER WE */ + { 0x0ECF, 0x315F }, /* Hangul_WI ㅟ HANGUL LETTER WI */ + { 0x0ED0, 0x3160 }, /* Hangul_YU ㅠ HANGUL LETTER YU */ + { 0x0ED1, 0x3161 }, /* Hangul_EU ㅡ HANGUL LETTER EU */ + { 0x0ED2, 0x3162 }, /* Hangul_YI ㅢ HANGUL LETTER YI */ + { 0x0ED3, 0x3163 }, /* Hangul_I ㅣ HANGUL LETTER I */ + { 0x0ED4, 0x11A8 }, /* Hangul_J_Kiyeog ᆨ HANGUL JONGSEONG KIYEOK */ + { 0x0ED5, 0x11A9 }, /* Hangul_J_SsangKiyeog ᆩ HANGUL JONGSEONG SSANGKIYEOK */ + { 0x0ED6, 0x11AA }, /* Hangul_J_KiyeogSios ᆪ HANGUL JONGSEONG KIYEOK-SIOS */ + { 0x0ED7, 0x11AB }, /* Hangul_J_Nieun ᆫ HANGUL JONGSEONG NIEUN */ + { 0x0ED8, 0x11AC }, /* Hangul_J_NieunJieuj ᆬ HANGUL JONGSEONG NIEUN-CIEUC */ + { 0x0ED9, 0x11AD }, /* Hangul_J_NieunHieuh ᆭ HANGUL JONGSEONG NIEUN-HIEUH */ + { 0x0EDA, 0x11AE }, /* Hangul_J_Dikeud ᆮ HANGUL JONGSEONG TIKEUT */ + { 0x0EDB, 0x11AF }, /* Hangul_J_Rieul ᆯ HANGUL JONGSEONG RIEUL */ + { 0x0EDC, 0x11B0 }, /* Hangul_J_RieulKiyeog ᆰ HANGUL JONGSEONG RIEUL-KIYEOK */ + { 0x0EDD, 0x11B1 }, /* Hangul_J_RieulMieum ᆱ HANGUL JONGSEONG RIEUL-MIEUM */ + { 0x0EDE, 0x11B2 }, /* Hangul_J_RieulPieub ᆲ HANGUL JONGSEONG RIEUL-PIEUP */ + { 0x0EDF, 0x11B3 }, /* Hangul_J_RieulSios ᆳ HANGUL JONGSEONG RIEUL-SIOS */ + { 0x0EE0, 0x11B4 }, /* Hangul_J_RieulTieut ᆴ HANGUL JONGSEONG RIEUL-THIEUTH */ + { 0x0EE1, 0x11B5 }, /* Hangul_J_RieulPhieuf ᆵ HANGUL JONGSEONG RIEUL-PHIEUPH */ + { 0x0EE2, 0x11B6 }, /* Hangul_J_RieulHieuh ᆶ HANGUL JONGSEONG RIEUL-HIEUH */ + { 0x0EE3, 0x11B7 }, /* Hangul_J_Mieum ᆷ HANGUL JONGSEONG MIEUM */ + { 0x0EE4, 0x11B8 }, /* Hangul_J_Pieub ᆸ HANGUL JONGSEONG PIEUP */ + { 0x0EE5, 0x11B9 }, /* Hangul_J_PieubSios ᆹ HANGUL JONGSEONG PIEUP-SIOS */ + { 0x0EE6, 0x11BA }, /* Hangul_J_Sios ᆺ HANGUL JONGSEONG SIOS */ + { 0x0EE7, 0x11BB }, /* Hangul_J_SsangSios ᆻ HANGUL JONGSEONG SSANGSIOS */ + { 0x0EE8, 0x11BC }, /* Hangul_J_Ieung ᆼ HANGUL JONGSEONG IEUNG */ + { 0x0EE9, 0x11BD }, /* Hangul_J_Jieuj ᆽ HANGUL JONGSEONG CIEUC */ + { 0x0EEA, 0x11BE }, /* Hangul_J_Cieuc ᆾ HANGUL JONGSEONG CHIEUCH */ + { 0x0EEB, 0x11BF }, /* Hangul_J_Khieuq ᆿ HANGUL JONGSEONG KHIEUKH */ + { 0x0EEC, 0x11C0 }, /* Hangul_J_Tieut ᇀ HANGUL JONGSEONG THIEUTH */ + { 0x0EED, 0x11C1 }, /* Hangul_J_Phieuf ᇁ HANGUL JONGSEONG PHIEUPH */ + { 0x0EEE, 0x11C2 }, /* Hangul_J_Hieuh ᇂ HANGUL JONGSEONG HIEUH */ + { 0x0EEF, 0x316D }, /* Hangul_RieulYeorinHieuh ㅭ HANGUL LETTER RIEUL-YEORINHIEUH */ + { 0x0EF0, 0x3171 }, /* Hangul_SunkyeongeumMieum ㅱ HANGUL LETTER KAPYEOUNMIEUM */ + { 0x0EF1, 0x3178 }, /* Hangul_SunkyeongeumPieub ㅸ HANGUL LETTER KAPYEOUNPIEUP */ + { 0x0EF2, 0x317F }, /* Hangul_PanSios ㅿ HANGUL LETTER PANSIOS */ + { 0x0EF3, 0x3181 }, /* Hangul_KkogjiDalrinIeung ㆁ HANGUL LETTER YESIEUNG */ + { 0x0EF4, 0x3184 }, /* Hangul_SunkyeongeumPhieuf ㆄ HANGUL LETTER KAPYEOUNPHIEUPH */ + { 0x0EF5, 0x3186 }, /* Hangul_YeorinHieuh ㆆ HANGUL LETTER YEORINHIEUH */ + { 0x0EF6, 0x318D }, /* Hangul_AraeA ㆍ HANGUL LETTER ARAEA */ + { 0x0EF7, 0x318E }, /* Hangul_AraeAE ㆎ HANGUL LETTER ARAEAE */ + { 0x0EF8, 0x11EB }, /* Hangul_J_PanSios ᇫ HANGUL JONGSEONG PANSIOS */ + { 0x0EF9, 0x11F0 }, /* Hangul_J_KkogjiDalrinIeung ᇰ HANGUL JONGSEONG YESIEUNG */ + { 0x0EFA, 0x11F9 }, /* Hangul_J_YeorinHieuh ᇹ HANGUL JONGSEONG YEORINHIEUH */ + { 0x0EFF, 0x20A9 }, /* Korean_Won ₩ WON SIGN */ + { 0x13A4, 0x20AC }, /* Euro € EURO SIGN */ + { 0x13BC, 0x0152 }, /* OE Œ LATIN CAPITAL LIGATURE OE */ + { 0x13BD, 0x0153 }, /* oe œ LATIN SMALL LIGATURE OE */ + { 0x13BE, 0x0178 }, /* Ydiaeresis Ÿ LATIN CAPITAL LETTER Y WITH DIAERESIS */ + { 0x20AC, 0x20AC }, /* EuroSign € EURO SIGN */ +}; + +/*********************************************************************** + * The following function converts ISO 10646-1 (UCS, Unicode) values to + * their corresponding KeySym values. + * + * The UTF-8 -> keysym conversion will hopefully one day be provided by + * Xlib via XmbLookupString() and should ideally not have to be + * done in X applications. But we are not there yet. + * + * Author: Markus G. Kuhn , University of Cambridge, + * June 1999 + * + * Special thanks to Richard Verhoeven for preparing + * an initial draft of the mapping table. + * + * This software is in the public domain. Share and enjoy! + ***********************************************************************/ +KeySym unicode_to_keysym(uint16_t unicode) { + int min = 0; + int max = sizeof(keysym_unicode_table) / sizeof(struct codepair) - 1; + int mid; + + #ifdef XK_LATIN1 + // First check for Latin-1 characters. (1:1 mapping) + if ((unicode >= 0x0020 && unicode <= 0x007E) || + (unicode >= 0x00A0 && unicode <= 0x00FF)) { + return unicode; + } + #endif + + // Binary search the lookup table. + while (max >= min) { + mid = (min + max) / 2; + if (keysym_unicode_table[mid].unicode < unicode) { + min = mid + 1; + } else if (keysym_unicode_table[mid].unicode > unicode) { + max = mid - 1; + } else { + // Found it. + return keysym_unicode_table[mid].keysym; + } + } + + // No matching KeySym value found, return UCS2 with bit set. + return unicode | 0x01000000; +} + + +/*********************************************************************** + * The following function converts KeySym values into the corresponding + * ISO 10646-1 (UCS, Unicode) values. + * + * The keysym -> UTF-8 conversion will hopefully one day be provided by + * Xlib via XLookupKeySym and should ideally not have to be done in X + * applications. But we are not there yet. + * + * Author: Markus G. Kuhn , University of Cambridge, + * June 1999 + * + * Special thanks to Richard Verhoeven for preparing + * an initial draft of the mapping table. + * + * This software is in the public domain. Share and enjoy! + ***********************************************************************/ +size_t keysym_to_unicode(KeySym keysym, uint16_t *buffer, size_t size) { + size_t count = 0; + + int min = 0; + int max = sizeof(keysym_unicode_table) / sizeof(struct codepair) - 1; + int mid; + + #ifdef XK_LATIN1 + // First check for Latin-1 characters. (1:1 mapping) + if ((keysym >= 0x0020 && keysym <= 0x007E) + || (keysym >= 0x00A0 && keysym <= 0x00FF)) { + + if (count < size) { + buffer[count++] = keysym; + } + + return count; + } + #endif + + // Also check for directly encoded 24-bit UCS characters. + #if defined(XK_LATIN8) || defined(XK_ARABIC) || defined(XK_CYRILLIC) || \ + defined(XK_ARMENIAN) || defined(XK_GEORGIAN) || defined(XK_CAUCASUS) || \ + defined(XK_VIETNAMESE) || defined(XK_CURRENCY) || \ + defined(XK_MATHEMATICAL) || defined(XK_BRAILLE) || defined(XK_SINHALA) + if ((keysym & 0xFF000000) == 0x01000000) { + if (count < size) { + buffer[count++] = keysym & 0x00FFFFFF; + } + + return count; + } + #endif + + // Binary search in table. + while (max >= min) { + mid = (min + max) / 2; + if (keysym_unicode_table[mid].keysym < keysym) { + min = mid + 1; + } else if (keysym_unicode_table[mid].keysym > keysym) { + max = mid - 1; + } else { + // Found it. + if (count < size) { + buffer[count++] = keysym_unicode_table[mid].unicode; + } + + return count; + } + } + + // No matching Unicode value found! + return count; +} + + +/* The following code is based on vncdisplaykeymap.c under the following terms: + * + * Copyright (C) 2008 Anthony Liguori + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2 as + * published by the Free Software Foundation. + */ +uint16_t keycode_to_scancode(KeyCode keycode) { + uint16_t scancode = VC_UNDEFINED; + + #if defined(USE_EVDEV) && defined(USE_XKB) + // Check to see if evdev is enabled. + if (is_evdev) { + unsigned short evdev_size = sizeof(evdev_scancode_table) / sizeof(evdev_scancode_table[0]); + + // NOTE scancodes < 97 appear to be identical between Evdev and XFree86. + if (keycode < evdev_size) { + // For scancode < 97, a simple scancode - 8 offest could be applied, + // but math is generally slower than memory and we cannot save any + // extra space in the lookup table due to binary padding. + scancode = evdev_scancode_table[keycode][0]; + } + } else { + // Evdev was disabled, fallback to XFree86. + #endif + unsigned short xfree86_size = sizeof(xfree86_scancode_table) / sizeof(xfree86_scancode_table[0]); + + // NOTE scancodes < 97 appear to be identical between Evdev and XFree86. + if (keycode < xfree86_size) { + // For scancode < 97, a simple scancode - 8 offest could be applied, + // but math is generally slower than memory and we cannot save any + // extra space in the lookup table due to binary padding. + scancode = xfree86_scancode_table[keycode][0]; + } + #if defined(USE_EVDEV) && defined(USE_XKB) + } + #endif + + return scancode; +} + +KeyCode scancode_to_keycode(uint16_t scancode) { + KeyCode keycode = 0x0000; + + #if defined(USE_EVDEV) && defined(USE_XKB) + // Check to see if Evdev is enabled. + if (is_evdev) { + unsigned short evdev_size = sizeof(evdev_scancode_table) / sizeof(evdev_scancode_table[0]); + + // NOTE scancodes < 97 appear to be identical between Evdev and XFree86. + if (scancode < 128) { + // For scancode < 97, a simple scancode + 8 offest could be applied, + // but math is generally slower than memory and we cannot save any + // extra space in the lookup table due to binary padding. + keycode = evdev_scancode_table[scancode][1]; + } else { + // Offset is the lower order bits + 128 + scancode = (scancode & 0x007F) | 0x80; + + if (scancode < evdev_size) { + keycode = evdev_scancode_table[scancode][1]; + } + } + } else { + // Evdev was disabled, fallback to XFree86. + #endif + unsigned short xfree86_size = sizeof(xfree86_scancode_table) / sizeof(xfree86_scancode_table[0]); + + // NOTE scancodes < 97 appear to be identical between Evdev and XFree86. + if (scancode < 128) { + // For scancode < 97, a simple scancode + 8 offest could be applied, + // but math is generally slower than memory and we cannot save any + // extra space in the lookup table due to binary padding. + keycode = xfree86_scancode_table[scancode][1]; + } else { + // Offset: lower order bits + 128 (If no size optimization!) + scancode = (scancode & 0x007F) | 0x80; + + if (scancode < xfree86_size) { + keycode = xfree86_scancode_table[scancode][1]; + } + } + #if defined(USE_EVDEV) && defined(USE_XKB) + } + #endif + + return keycode; +} + +#ifdef USE_XKBCOMMON +struct xkb_state * create_xkb_state(struct xkb_context *context, xcb_connection_t *connection) { + struct xkb_keymap *keymap; + struct xkb_state *state; + + int32_t device_id = xkb_x11_get_core_keyboard_device_id(connection); + if (device_id >= 0) { + keymap = xkb_x11_keymap_new_from_device(context, connection, device_id, XKB_KEYMAP_COMPILE_NO_FLAGS); + state = xkb_x11_state_new_from_device(keymap, connection, device_id); + } + #ifdef USE_XKBFILE + else { + // Evdev fallback, + logger(LOG_LEVEL_WARN, "%s [%u]: Unable to retrieve core keyboard device id! (%d)\n", + __FUNCTION__, __LINE__, device_id); + + keymap = xkb_keymap_new_from_names(context, &xkb_names, XKB_KEYMAP_COMPILE_NO_FLAGS); + state = xkb_state_new(keymap); + } + #endif + + xkb_map_unref(keymap); + return xkb_state_ref(state); +} + +void destroy_xkb_state(struct xkb_state* state) { + xkb_state_unref(state); +} + +size_t keycode_to_unicode(struct xkb_state* state, KeyCode keycode, uint16_t *buffer, size_t length) { + size_t count = 0; + + if (state != NULL) { + uint32_t unicode = xkb_state_key_get_utf32(state, keycode); + + if (unicode <= 0x10FFFF) { + if ((unicode <= 0xD7FF || (unicode >= 0xE000 && unicode <= 0xFFFF)) && length >= 1) { + buffer[0] = unicode; + count = 1; + } else if (unicode >= 0x10000) { + unsigned int code = (unicode - 0x10000); + buffer[0] = 0xD800 | (code >> 10); + buffer[1] = 0xDC00 | (code & 0x3FF); + count = 2; + } + } + } + + return count; +} +#else +// Faster more flexible alternative to XKeycodeToKeysym... +KeySym keycode_to_keysym(KeyCode keycode, unsigned int modifier_mask) { + KeySym keysym = NoSymbol; + + #ifdef USE_XKB + if (keyboard_map != NULL) { + // Get the range and number of symbols groups bound to the key. + unsigned char info = XkbKeyGroupInfo(keyboard_map, keycode); + unsigned int num_groups = XkbKeyNumGroups(keyboard_map, keycode); + + // Get the group. + unsigned int group = 0x0000; + switch (XkbOutOfRangeGroupAction(info)) { + case XkbRedirectIntoRange: + /* If the RedirectIntoRange flag is set, the four least significant + * bits of the groups wrap control specify the index of a group to + * which all illegal groups correspond. If the specified group is + * also out of range, all illegal groups map to Group1. + */ + group = XkbOutOfRangeGroupInfo(info); + if (group >= num_groups) { + group = 0; + } + break; + + case XkbClampIntoRange: + /* If the ClampIntoRange flag is set, out-of-range groups correspond + * to the nearest legal group. Effective groups larger than the + * highest supported group are mapped to the highest supported group; + * effective groups less than Group1 are mapped to Group1 . For + * example, a key with two groups of symbols uses Group2 type and + * symbols if the global effective group is either Group3 or Group4. + */ + group = num_groups - 1; + break; + + case XkbWrapIntoRange: + /* If neither flag is set, group is wrapped into range using integer + * modulus. For example, a key with two groups of symbols for which + * groups wrap uses Group1 symbols if the global effective group is + * Group3 or Group2 symbols if the global effective group is Group4. + */ + default: + if (num_groups != 0) { + group %= num_groups; + } + break; + } + + XkbKeyTypePtr key_type = XkbKeyKeyType(keyboard_map, keycode, group); + unsigned int active_mods = modifier_mask & key_type->mods.mask; + + int i, level = 0; + for (i = 0; i < key_type->map_count; i++) { + if (key_type->map[i].active && key_type->map[i].mods.mask == active_mods) { + level = key_type->map[i].level; + } + } + + keysym = XkbKeySymEntry(keyboard_map, keycode, level, group); + } + #else + if (keyboard_map != NULL) { + if (modifier_mask & Mod2Mask && + ((keyboard_map[keycode *keysym_per_keycode + 1] >= 0xFF80 && keyboard_map[keycode *keysym_per_keycode + 1] <= 0xFFBD) || + (keyboard_map[keycode *keysym_per_keycode + 1] >= 0x11000000 && keyboard_map[keycode *keysym_per_keycode + 1] <= 0x1100FFFF)) + ) { + + /* If the numlock modifier is on and the second KeySym is a keypad + * KeySym. In this case, if the Shift modifier is on, or if the + * Lock modifier is on and is interpreted as ShiftLock, then the + * first KeySym is used, otherwise the second KeySym is used. + * + * The standard KeySyms with the prefix ``XK_KP_'' in their name are + * called keypad KeySyms; these are KeySyms with numeric value in + * the hexadecimal range 0xFF80 to 0xFFBD inclusive. In addition, + * vendor-specific KeySyms in the hexadecimal range 0x11000000 to + * 0x1100FFFF are also keypad KeySyms. + */ + + + /* The numlock modifier is on and the second KeySym is a keypad + * KeySym. In this case, if the Shift modifier is on, or if the + * Lock modifier is on and is interpreted as ShiftLock, then the + * first KeySym is used, otherwise the second KeySym is used. + */ + if (modifier_mask & ShiftMask || (modifier_mask & LockMask && is_shift_lock)) { + // i = 0 + keysym = keyboard_map[keycode *keysym_per_keycode]; + } else { + // i = 1 + keysym = keyboard_map[keycode *keysym_per_keycode + 1]; + } + } else if (modifier_mask ^ ShiftMask && modifier_mask ^ LockMask) { + /* The Shift and Lock modifiers are both off. In this case, + * the first KeySym is used. + */ + // index = 0 + keysym = keyboard_map[keycode *keysym_per_keycode]; + } else if (modifier_mask ^ ShiftMask && modifier_mask & LockMask && is_caps_lock) { + /* The Shift modifier is off, and the Lock modifier is on + * and is interpreted as CapsLock. In this case, the first + * KeySym is used, but if that KeySym is lowercase + * alphabetic, then the corresponding uppercase KeySym is + * used instead. + */ + // index = 0; + keysym = keyboard_map[keycode *keysym_per_keycode]; + + if (keysym >= 'a' && keysym <= 'z') { + // keysym is an alpha char. + KeySym lower_keysym, upper_keysym; + XConvertCase(keysym, &lower_keysym, &upper_keysym); + keysym = upper_keysym; + } + } else if (modifier_mask & ShiftMask && modifier_mask & LockMask && is_caps_lock) { + /* The Shift modifier is on, and the Lock modifier is on and + * is interpreted as CapsLock. In this case, the second + * KeySym is used, but if that KeySym is lowercase + * alphabetic, then the corresponding uppercase KeySym is + * used instead. + */ + // index = 1 + keysym = keyboard_map[keycode *keysym_per_keycode + 1]; + + if (keysym >= 'A' && keysym <= 'Z') { + // keysym is an alpha char. + KeySym lower_keysym, upper_keysym; + XConvertCase(keysym, &lower_keysym, &upper_keysym); + keysym = lower_keysym; + } + } else if (modifier_mask & ShiftMask || (modifier_mask & LockMask && is_shift_lock) || modifier_mask & (ShiftMask + LockMask)) { + /* The Shift modifier is on, or the Lock modifier is on and + * is interpreted as ShiftLock, or both. In this case, the + * second KeySym is used. + */ + // index = 1 + keysym = keyboard_map[keycode *keysym_per_keycode + 1]; + } else { + logger(LOG_LEVEL_ERROR, "%s [%u]: Unable to determine the KeySym index!\n", + __FUNCTION__, __LINE__); + } + } + #endif + + return keysym; +} +#endif + +void load_input_helper(Display *disp) { + #ifdef USE_XKB + /* The following code block is based on vncdisplaykeymap.c under the terms: + * + * Copyright (C) 2008 Anthony Liguori + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2 as + * published by the Free Software Foundation. + */ + XkbDescPtr desc = XkbGetKeyboard(disp, XkbGBN_AllComponentsMask, XkbUseCoreKbd); + if (desc != NULL && desc->names != NULL) { + const char *layout_name = XGetAtomName(disp, desc->names->keycodes); + logger(LOG_LEVEL_DEBUG, + "%s [%u]: Found keycode atom '%s' (%i)!\n", + __FUNCTION__, __LINE__, layout_name, + (unsigned int) desc->names->keycodes); + + const char *prefix_xfree86 = "xfree86_"; + #if defined(USE_EVDEV) && defined(USE_XKB) + const char *prefix_evdev = "evdev_"; + if (strncmp(layout_name, prefix_evdev, strlen(prefix_evdev)) == 0) { + is_evdev = true; + } else + #endif + + if (strncmp(layout_name, prefix_xfree86, strlen(prefix_xfree86)) != 0) { + // logger(LOG_LEVEL_WARN, + // "%s [%u]: Unknown keycode name '%s', please file a bug report!\n", + // __FUNCTION__, __LINE__, layout_name); + } else if (layout_name == NULL) { + logger(LOG_LEVEL_ERROR, + "%s [%u]: X atom name failure for desc->names->keycodes!\n", + __FUNCTION__, __LINE__); + } + + XkbFreeClientMap(desc, XkbGBN_AllComponentsMask, True); + } else { + logger(LOG_LEVEL_WARN, + "%s [%u]: XkbGetKeyboard failed to locate a valid keyboard!\n", + __FUNCTION__, __LINE__); + } + + // Get the map. + keyboard_map = XkbGetMap(disp, XkbAllClientInfoMask, XkbUseCoreKbd); + #else + // No known alternative to determine scancode mapping, assume XFree86! + // printf("%s\n", "No known alternative to determine scancode mapping, assume XFree86!"); + // #pragma message("*** Warning: XKB support is required to accurately determine keyboard scancodes!") + // #pragma message("... Assuming XFree86 keyboard layout.") + + logger(LOG_LEVEL_WARN, "%s [%u]: Using XFree86 keyboard layout.\n", + __FUNCTION__, __LINE__); + logger(LOG_LEVEL_WARN, "%s [%u]: XKB support is required to accurately determine keyboard characters!\n", + __FUNCTION__, __LINE__); + + int minKeyCode, maxKeyCode; + XDisplayKeycodes(disp, &minKeyCode, &maxKeyCode); + + keyboard_map = XGetKeyboardMapping(disp, minKeyCode, (maxKeyCode - minKeyCode + 1), &keysym_per_keycode); + if (keyboard_map) { + XModifierKeymap *modifierMap = XGetModifierMapping(disp); + + if (modifierMap) { + /* The Lock modifier is interpreted as CapsLock when the KeySym + * named XK_Caps_Lock is attached to some KeyCode and that KeyCode + * is attached to the Lock modifier. The Lock modifier is + * interpreted as ShiftLock when the KeySym named XK_Shift_Lock is + * attached to some KeyCode and that KeyCode is attached to the Lock + * modifier. If the Lock modifier could be interpreted as both + * CapsLock and ShiftLock, the CapsLock interpretation is used. + */ + + KeyCode capsLock = XKeysymToKeycode(disp, XK_Caps_Lock); + KeyCode shiftLock = XKeysymToKeycode(disp, XK_Shift_Lock); + keysym_per_keycode--; + + // Loop over the modifier map to find out if/where shift and caps locks are set. + int i; + for (i = LockMapIndex; i < LockMapIndex + modifierMap->max_keypermod && !is_caps_lock; i++) { + if (capsLock != 0 && modifierMap->modifiermap[i] == capsLock) { + is_caps_lock = true; + is_shift_lock = false; + } else if (shiftLock != 0 && modifierMap->modifiermap[i] == shiftLock) { + is_shift_lock = true; + } + } + + XFree(modifierMap); + } else { + XFree(keyboard_map); + + logger(LOG_LEVEL_ERROR, + "%s [%u]: Unable to get modifier mapping table!\n", + __FUNCTION__, __LINE__); + } + } else { + logger(LOG_LEVEL_ERROR, + "%s [%u]: Unable to get keyboard mapping table!\n", + __FUNCTION__, __LINE__); + } + #endif +} + +void unload_input_helper() { + if (keyboard_map) { + #ifdef USE_XKB + XkbFreeClientMap(keyboard_map, XkbAllClientInfoMask, true); + #if defined(USE_EVDEV) && defined(USE_XKB) + is_evdev = false; + #endif + #else + XFree(keyboard_map); + #endif + } +} diff --git a/hook/x11/properties_c.h b/hook/x11/properties_c.h new file mode 100644 index 0000000..2c19b52 --- /dev/null +++ b/hook/x11/properties_c.h @@ -0,0 +1,492 @@ +/* Copyright (C) 2006-2017 Alexander Barker. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. +*/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#ifdef USE_XKB + #include +#endif +#ifdef USE_XF86MISC + #include + #include +#endif +#if defined(USE_XINERAMA) && !defined(USE_XRANDR) + #include +#elif defined(USE_XRANDR) +#include + #include +#endif +#ifdef USE_XT + #include + + static XtAppContext xt_context; + static Display *xt_disp; +#endif + +#include "../iohook.h" +#include "input.h" +// #include "../logger.h" + +Display *properties_disp; + +#ifdef USE_XRANDR +static pthread_mutex_t xrandr_mutex = PTHREAD_MUTEX_INITIALIZER; +static XRRScreenResources *xrandr_resources = NULL; + +static void settings_cleanup_proc(void *arg) { + if (pthread_mutex_trylock(&xrandr_mutex) == 0) { + if (xrandr_resources != NULL) { + XRRFreeScreenResources(xrandr_resources); + xrandr_resources = NULL; + } + + if (arg != NULL) { + XCloseDisplay((Display *) arg); + arg = NULL; + } + + pthread_mutex_unlock(&xrandr_mutex); + } +} + +static void *settings_thread_proc(void *arg) { + Display *settings_disp = XOpenDisplay(XDisplayName(NULL));; + if (settings_disp != NULL) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: %s\n", + __FUNCTION__, __LINE__, "XOpenDisplay success."); + + pthread_cleanup_push(settings_cleanup_proc, settings_disp); + + int event_base = 0; + int error_base = 0; + if (XRRQueryExtension(settings_disp, &event_base, &error_base)) { + Window root = XDefaultRootWindow(settings_disp); + unsigned long event_mask = RRScreenChangeNotifyMask; + XRRSelectInput(settings_disp, root, event_mask); + + XEvent ev; + + while(settings_disp != NULL) { + XNextEvent(settings_disp, &ev); + + if (ev.type == event_base + RRScreenChangeNotifyMask) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: Received XRRScreenChangeNotifyEvent.\n", + __FUNCTION__, __LINE__); + + pthread_mutex_lock(&xrandr_mutex); + if (xrandr_resources != NULL) { + XRRFreeScreenResources(xrandr_resources); + } + + xrandr_resources = XRRGetScreenResources(settings_disp, root); + if (xrandr_resources == NULL) { + logger(LOG_LEVEL_WARN, "%s [%u]: XRandR could not get screen resources!\n", + __FUNCTION__, __LINE__); + } + pthread_mutex_unlock(&xrandr_mutex); + } else { + logger(LOG_LEVEL_WARN, "%s [%u]: XRandR is not currently available!\n", + __FUNCTION__, __LINE__); + } + } + } + + // Execute the thread cleanup handler. + pthread_cleanup_pop(1); + + } else { + logger(LOG_LEVEL_ERROR, "%s [%u]: XOpenDisplay failure!\n", + __FUNCTION__, __LINE__); + } + + return NULL; +} +#endif + +IOHOOK_API screen_data* hook_create_screen_info(unsigned char *count) { + *count = 0; + screen_data *screens = NULL; + + #if defined(USE_XINERAMA) && !defined(USE_XRANDR) + if (XineramaIsActive(properties_disp)) { + int xine_count = 0; + XineramaScreenInfo *xine_info = XineramaQueryScreens(properties_disp, &xine_count); + + if (xine_info != NULL) { + if (xine_count > UINT8_MAX) { + *count = UINT8_MAX; + + logger(LOG_LEVEL_WARN, "%s [%u]: Screen count overflow detected!\n", + __FUNCTION__, __LINE__); + } else { + *count = (uint8_t) xine_count; + } + + screens = malloc(sizeof(screen_data) * xine_count); + + if (screens != NULL) { + int i; + for (i = 0; i < xine_count; i++) { + screens[i] = (screen_data) { + .number = xine_info[i].screen_number, + .x = xine_info[i].x_org, + .y = xine_info[i].y_org, + .width = xine_info[i].width, + .height = xine_info[i].height + }; + } + } + + XFree(xine_info); + } + } + #elif defined(USE_XRANDR) + pthread_mutex_lock(&xrandr_mutex); + if (xrandr_resources != NULL) { + int xrandr_count = xrandr_resources->ncrtc; + if (xrandr_count > UINT8_MAX) { + *count = UINT8_MAX; + + logger(LOG_LEVEL_WARN, "%s [%u]: Screen count overflow detected!\n", + __FUNCTION__, __LINE__); + } else { + *count = (uint8_t) xrandr_count; + } + + screens = malloc(sizeof(screen_data) * xrandr_count); + + if (screens != NULL) { + int i; + for (i = 0; i < xrandr_count; i++) { + XRRCrtcInfo *crtc_info = XRRGetCrtcInfo(properties_disp, xrandr_resources, xrandr_resources->crtcs[i]); + + if (crtc_info != NULL) { + screens[i] = (screen_data) { + .number = i + 1, + .x = crtc_info->x, + .y = crtc_info->y, + .width = crtc_info->width, + .height = crtc_info->height + }; + + XRRFreeCrtcInfo(crtc_info); + } else { + logger(LOG_LEVEL_WARN, "%s [%u]: XRandr failed to return crtc information! (%#X)\n", + __FUNCTION__, __LINE__, xrandr_resources->crtcs[i]); + } + } + } + } + pthread_mutex_unlock(&xrandr_mutex); + #else + Screen* default_screen = DefaultScreenOfDisplay(properties_disp); + + if (default_screen->width > 0 && default_screen->height > 0) { + screens = malloc(sizeof(screen_data)); + + if (screens != NULL) { + *count = 1; + screens[0] = (screen_data) { + .number = 1, + .x = 0, + .y = 0, + .width = default_screen->width, + .height = default_screen->height + }; + } + } + #endif + + return screens; +} + +IOHOOK_API long int hook_get_auto_repeat_rate() { + bool successful = false; + long int value = -1; + unsigned int delay = 0, rate = 0; + + // Check and make sure we could connect to the x server. + if (properties_disp != NULL) { + #ifdef USE_XKB + // Attempt to acquire the keyboard auto repeat rate using the XKB extension. + if (!successful) { + successful = XkbGetAutoRepeatRate(properties_disp, XkbUseCoreKbd, &delay, &rate); + + if (successful) { + logger(LOG_LEVEL_INFO, "%s [%u]: XkbGetAutoRepeatRate: %u.\n", + __FUNCTION__, __LINE__, rate); + } + } + #endif + + #ifdef USE_XF86MISC + // Fallback to the XF86 Misc extension if available and other efforts failed. + if (!successful) { + XF86MiscKbdSettings kb_info; + successful = (bool) XF86MiscGetKbdSettings(properties_disp, &kb_info); + if (successful) { + logger(LOG_LEVEL_INFO, "%s [%u]: XF86MiscGetKbdSettings: %i.\n", + __FUNCTION__, __LINE__, kbdinfo.rate); + + delay = (unsigned int) kbdinfo.delay; + rate = (unsigned int) kbdinfo.rate; + } + } + #endif + } else { + logger(LOG_LEVEL_ERROR, "%s [%u]: %s\n", + __FUNCTION__, __LINE__, "XOpenDisplay failure!"); + } + + if (successful) { + value = (long int) rate; + } + + return value; +} + +IOHOOK_API long int hook_get_auto_repeat_delay() { + bool successful = false; + long int value = -1; + unsigned int delay = 0, rate = 0; + + // Check and make sure we could connect to the x server. + if (properties_disp != NULL) { + #ifdef USE_XKB + // Attempt to acquire the keyboard auto repeat rate using the XKB extension. + if (!successful) { + successful = XkbGetAutoRepeatRate(properties_disp, XkbUseCoreKbd, &delay, &rate); + + if (successful) { + logger(LOG_LEVEL_INFO, "%s [%u]: XkbGetAutoRepeatRate: %u.\n", + __FUNCTION__, __LINE__, delay); + } + } + #endif + + #ifdef USE_XF86MISC + // Fallback to the XF86 Misc extension if available and other efforts failed. + if (!successful) { + XF86MiscKbdSettings kb_info; + successful = (bool) XF86MiscGetKbdSettings(properties_disp, &kb_info); + if (successful) { + logger(LOG_LEVEL_INFO, "%s [%u]: XF86MiscGetKbdSettings: %i.\n", + __FUNCTION__, __LINE__, kbdinfo.delay); + + delay = (unsigned int) kbdinfo.delay; + rate = (unsigned int) kbdinfo.rate; + } + } + #endif + } else { + logger(LOG_LEVEL_ERROR, "%s [%u]: %s\n", + __FUNCTION__, __LINE__, "XOpenDisplay failure!"); + } + + if (successful) { + value = (long int) delay; + } + + return value; +} + +IOHOOK_API long int hook_get_pointer_acceleration_multiplier() { + long int value = -1; + int accel_numerator, accel_denominator, threshold; + + // Check and make sure we could connect to the x server. + if (properties_disp != NULL) { + XGetPointerControl(properties_disp, &accel_numerator, &accel_denominator, &threshold); + if (accel_denominator >= 0) { + logger(LOG_LEVEL_INFO, "%s [%u]: XGetPointerControl: %i.\n", + __FUNCTION__, __LINE__, accel_denominator); + + value = (long int) accel_denominator; + } + } else { + logger(LOG_LEVEL_ERROR, "%s [%u]: %s\n", + __FUNCTION__, __LINE__, "XOpenDisplay failure!"); + } + + return value; +} + +IOHOOK_API long int hook_get_pointer_acceleration_threshold() { + long int value = -1; + int accel_numerator, accel_denominator, threshold; + + // Check and make sure we could connect to the x server. + if (properties_disp != NULL) { + XGetPointerControl(properties_disp, &accel_numerator, &accel_denominator, &threshold); + if (threshold >= 0) { + logger(LOG_LEVEL_INFO, "%s [%u]: XGetPointerControl: %i.\n", + __FUNCTION__, __LINE__, threshold); + + value = (long int) threshold; + } + } else { + logger(LOG_LEVEL_ERROR, "%s [%u]: %s\n", + __FUNCTION__, __LINE__, "XOpenDisplay failure!"); + } + + return value; +} + +IOHOOK_API long int hook_get_pointer_sensitivity() { + long int value = -1; + int accel_numerator, accel_denominator, threshold; + + // Check and make sure we could connect to the x server. + if (properties_disp != NULL) { + XGetPointerControl(properties_disp, &accel_numerator, &accel_denominator, &threshold); + if (accel_numerator >= 0) { + logger(LOG_LEVEL_INFO, "%s [%u]: XGetPointerControl: %i.\n", + __FUNCTION__, __LINE__, accel_numerator); + + value = (long int) accel_numerator; + } + } else { + logger(LOG_LEVEL_ERROR, "%s [%u]: %s\n", + __FUNCTION__, __LINE__, "XOpenDisplay failure!"); + } + + return value; +} + +IOHOOK_API long int hook_get_multi_click_time() { + long int value = 200; + int click_time; + bool successful = false; + + #ifdef USE_XT + // Check and make sure we could connect to the x server. + if (xt_disp != NULL) { + // Try and use the Xt extention to get the current multi-click. + if (!successful) { + // Fall back to the X Toolkit extension if available and other efforts failed. + click_time = XtGetMultiClickTime(xt_disp); + if (click_time >= 0) { + logger(LOG_LEVEL_INFO, "%s [%u]: XtGetMultiClickTime: %i.\n", + __FUNCTION__, __LINE__, click_time); + + successful = true; + } + } + } else { + logger(LOG_LEVEL_ERROR, "%s [%u]: %s\n", + __FUNCTION__, __LINE__, "XOpenDisplay failure!"); + } + #endif + + // Check and make sure we could connect to the x server. + if (properties_disp != NULL) { + // Try and acquire the multi-click time from the user defined X defaults. + if (!successful) { + char *xprop = XGetDefault(properties_disp, "*", "multiClickTime"); + if (xprop != NULL && sscanf(xprop, "%4i", &click_time) != EOF) { + logger(LOG_LEVEL_INFO, "%s [%u]: X default 'multiClickTime' property: %i.\n", + __FUNCTION__, __LINE__, click_time); + + successful = true; + } + } + + if (!successful) { + char *xprop = XGetDefault(properties_disp, "OpenWindows", "MultiClickTimeout"); + if (xprop != NULL && sscanf(xprop, "%4i", &click_time) != EOF) { + logger(LOG_LEVEL_INFO, "%s [%u]: X default 'MultiClickTimeout' property: %i.\n", + __FUNCTION__, __LINE__, click_time); + + successful = true; + } + } + } else { + logger(LOG_LEVEL_ERROR, "%s [%u]: %s\n", + __FUNCTION__, __LINE__, "XOpenDisplay failure!"); + } + + if (successful) { + value = (long int) click_time; + } + + return value; +} + +// Create a shared object constructor. +__attribute__ ((constructor)) +void on_library_load() { + // Make sure we are initialized for threading. + XInitThreads(); + + // Open local display. + properties_disp = XOpenDisplay(XDisplayName(NULL)); + if (properties_disp == NULL) { + logger(LOG_LEVEL_WARN, "%s [%u]: %s\n", + __FUNCTION__, __LINE__, "XOpenDisplay failure!"); + } else { + logger(LOG_LEVEL_DEBUG, "%s [%u]: %s\n", + __FUNCTION__, __LINE__, "XOpenDisplay success."); + } + + #ifdef USE_XRANDR + // Create the thread attribute. + pthread_attr_t settings_thread_attr; + pthread_attr_init(&settings_thread_attr); + + pthread_t settings_thread_id; + if (pthread_create(&settings_thread_id, &settings_thread_attr, settings_thread_proc, NULL) == 0) { + logger(LOG_LEVEL_DEBUG, "%s [%u]: Successfully created settings thread.\n", + __FUNCTION__, __LINE__); + } else { + logger(LOG_LEVEL_ERROR, "%s [%u]: Failed to create settings thread!\n", + __FUNCTION__, __LINE__); + } + + // Make sure the thread attribute is removed. + pthread_attr_destroy(&settings_thread_attr); + #endif + + #ifdef USE_XT + XtToolkitInitialize(); + xt_context = XtCreateApplicationContext(); + + int argc = 0; + char ** argv = { NULL }; + xt_disp = XtOpenDisplay(xt_context, NULL, "IOHook", "libIOhook", NULL, 0, &argc, argv); + #endif + + // Initialize. + load_input_helper(properties_disp); +} + +// Create a shared object destructor. +__attribute__ ((destructor)) +void on_library_unload() { + // Disable the event hook. + //hook_stop(); + + // Cleanup. + unload_input_helper(); + + #ifdef USE_XT + XtCloseDisplay(xt_disp); + XtDestroyApplicationContext(xt_context); + #endif + + // Destroy the native displays. + if (properties_disp != NULL) { + XCloseDisplay(properties_disp); + properties_disp = NULL; + } +} diff --git a/hook_test.go b/hook_test.go new file mode 100644 index 0000000..d9540d5 --- /dev/null +++ b/hook_test.go @@ -0,0 +1,23 @@ +package hook + +import ( + "fmt" + "testing" + + "github.com/vcaesar/tt" +) + +func TestAdd(t *testing.T) { + fmt.Println("hook test...") + + e := Start() + tt.NotNil(t, e) +} + +func TestKey(t *testing.T) { + k := RawcodetoKeychar(0) + tt.Equal(t, "error", k) + + r := KeychartoRawcode("error") + tt.Equal(t, 0, r) +} diff --git a/keycode.go b/keycode.go new file mode 100644 index 0000000..4224ad0 --- /dev/null +++ b/keycode.go @@ -0,0 +1,22 @@ +// Copyright 2016 The go-vgo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// https://github.com/go-vgo/robotgo/blob/master/LICENSE +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +package hook + +import "github.com/vcaesar/keycode" + +// MouseMap defines the robotgo hook mouse's code map +var MouseMap = keycode.MouseMap + +// Keycode defines the robotgo hook key's code map +var Keycode = keycode.Keycode + +// Special defines the special key map +var Special = keycode.Special diff --git a/tables.go b/tables.go new file mode 100644 index 0000000..0778dcd --- /dev/null +++ b/tables.go @@ -0,0 +1,354 @@ +package hook + +var ( + raw2key = map[uint16]string{ // https://github.com/wesbos/keycodes + 0: "error", + 3: "break", + 8: "backspace", + 9: "tab", + 12: "clear", + 13: "enter", + 16: "shift", + 17: "ctrl", + 18: "alt", + 19: "pause/break", + 20: "caps lock", + 21: "hangul", + 25: "hanja", + 27: "escape", + 28: "conversion", + 29: "non-conversion", + 32: "spacebar", + 33: "page up", + 34: "page down", + 35: "end", + 36: "home", + 37: "left arrow", + 38: "up arrow", + 39: "right arrow", + 40: "down arrow", + 41: "select", + 42: "print", + 43: "execute", + 44: "Print Screen", + 45: "insert", + 46: "delete", + 47: "help", + 48: "0", + 49: "1", + 50: "2", + 51: "3", + 52: "4", + 53: "5", + 54: "6", + 55: "7", + 56: "8", + 57: "9", + 58: ":", + 59: ";", + 60: "<", + 61: "=", + 63: "ß", + 64: "@", + 65: "a", + 66: "b", + 67: "c", + 68: "d", + 69: "e", + 70: "f", + 71: "g", + 72: "h", + 73: "i", + 74: "j", + 75: "k", + 76: "l", + 77: "m", + 78: "n", + 79: "o", + 80: "p", + 81: "q", + 82: "r", + 83: "s", + 84: "t", + 85: "u", + 86: "v", + 87: "w", + 88: "x", + 89: "y", + 90: "z", + 91: "l-super", + 92: "r-super", + 93: "apps", + 95: "sleep", + 96: "numpad 0", + 97: "numpad 1", + 98: "numpad 2", + 99: "numpad 3", + 100: "numpad 4", + 101: "numpad 5", + 102: "numpad 6", + 103: "numpad 7", + 104: "numpad 8", + 105: "numpad 9", + 106: "multiply", + 107: "add", + 108: "numpad period", + 109: "subtract", + 110: "decimal point", + 111: "divide", + 112: "f1", + 113: "f2", + 114: "f3", + 115: "f4", + 116: "f5", + 117: "f6", + 118: "f7", + 119: "f8", + 120: "f9", + 121: "f10", + 122: "f11", + 123: "f12", + 124: "f13", + 125: "f14", + 126: "f15", + 127: "f16", + 128: "f17", + 129: "f18", + 130: "f19", + 131: "f20", + 132: "f21", + 133: "f22", + 134: "f23", + 135: "f24", + 144: "num lock", + 145: "scroll lock", + 160: "^", + 161: "!", + 162: "؛", + 163: "#", + 164: "$", + 165: "ù", + 166: "page backward", + 167: "page forward", + 168: "refresh", + 169: "closing paren (AZERTY)", + 170: "*", + 171: "~ + * key", + 172: "home key", + 173: "minus (firefox), mute/unmute", + 174: "decrease volume level", + 175: "increase volume level", + 176: "next", + 177: "previous", + 178: "stop", + 179: "play/pause", + 180: "e-mail", + 181: "mute/unmute (firefox)", + 182: "decrease volume level (firefox)", + 183: "increase volume level (firefox)", + 186: "semi-colon / ñ", + 187: "equal sign", + 188: "comma", + 189: "dash", + 190: "period", + 191: "forward slash / ç", + 192: "grave accent / ñ / æ / ö", + 193: "?, / or °", + 194: "numpad period (chrome)", + 219: "open bracket", + 220: "back slash", + 221: "close bracket / å", + 222: "single quote / ø / ä", + 223: "`", + 224: "left or right ⌘ key (firefox)", + 225: "altgr", + 226: "< /git >, left back slash", + 230: "GNOME Compose Key", + 231: "ç", + 233: "XF86Forward", + 234: "XF86Back", + 235: "non-conversion", + 240: "alphanumeric", + 242: "hiragana/katakana", + 243: "half-width/full-width", + 244: "kanji", + 251: "unlock trackpad (Chrome/Edge)", + 255: "toggle touchpad", + 65517: "hyper", + } + + keytoraw = map[string]uint16{ + "error": 0, + "break": 3, + "backspace": 8, + "tab": 9, + "clear": 12, + "enter": 13, + "shift": 16, + "ctrl": 17, + "alt": 18, + "pause/break": 19, + "caps lock": 20, + "hangul": 21, + "hanja": 25, + "escape": 27, + "conversion": 28, + "non-conversion": 29, + "spacebar": 32, + "page up": 33, + "page down": 34, + "end": 35, + "home": 36, + "left arrow": 37, + "up arrow": 38, + "right arrow": 39, + "down arrow": 40, + "select": 41, + "print": 42, + "execute": 43, + "Print Screen": 44, + "insert": 45, + "delete": 46, + "help": 47, + "0": 48, + "1": 49, + "2": 50, + "3": 51, + "4": 52, + "5": 53, + "6": 54, + "7": 55, + "8": 56, + "9": 57, + ":": 58, + ";": 59, + "<": 60, + "=": 61, + "ß": 63, + "@": 64, + "a": 65, + "b": 66, + "c": 67, + "d": 68, + "e": 69, + "f": 70, + "g": 71, + "h": 72, + "i": 73, + "j": 74, + "k": 75, + "l": 76, + "m": 77, + "n": 78, + "o": 79, + "p": 80, + "q": 81, + "r": 82, + "s": 83, + "t": 84, + "u": 85, + "v": 86, + "w": 87, + "x": 88, + "y": 89, + "z": 90, + "l-super": 91, + "r-super": 92, + "apps": 93, + "sleep": 95, + "numpad 0": 96, + "numpad 1": 97, + "numpad 2": 98, + "numpad 3": 99, + "numpad 4": 100, + "numpad 5": 101, + "numpad 6": 102, + "numpad 7": 103, + "numpad 8": 104, + "numpad 9": 105, + "multiply": 106, + "add": 107, + "numpad period": 108, + "subtract": 109, + "decimal point": 110, + "divide": 111, + "f1": 112, + "f2": 113, + "f3": 114, + "f4": 115, + "f5": 116, + "f6": 117, + "f7": 118, + "f8": 119, + "f9": 120, + "f10": 121, + "f11": 122, + "f12": 123, + "f13": 124, + "f14": 125, + "f15": 126, + "f16": 127, + "f17": 128, + "f18": 129, + "f19": 130, + "f20": 131, + "f21": 132, + "f22": 133, + "f23": 134, + "f24": 135, + "num lock": 144, + "scroll lock": 145, + "^": 160, + "!": 161, + "؛": 162, + "#": 163, + "$": 164, + "ù": 165, + "page backward": 166, + "page forward": 167, + "refresh": 168, + "closing paren (AZERTY)": 169, + "*": 170, + "~ + * key": 171, + "home key": 172, + "minus (firefox), mute/unmute": 173, + "decrease volume level": 174, + "increase volume level": 175, + "next": 176, + "previous": 177, + "stop": 178, + "play/pause": 179, + "e-mail": 180, + "mute/unmute (firefox)": 181, + "decrease volume level (firefox)": 182, + "increase volume level (firefox)": 183, + "semi-colon / ñ": 186, + "equal sign": 187, + "comma": 188, + "dash": 189, + "period": 190, + "forward slash / ç": 191, + "grave accent / ñ / æ / ö": 192, + "?, / or °": 193, + "numpad period (chrome)": 194, + "open bracket": 219, + "back slash": 220, + "close bracket / å": 221, + "single quote / ø / ä": 222, + "`": 223, + "left or right ⌘ key (firefox)": 224, + "altgr": 225, + "< /git >, left back slash": 226, + "GNOME Compose Key": 230, + "ç": 231, + "XF86Forward": 233, + "XF86Back": 234, + "alphanumeric": 240, + "hiragana/katakana": 242, + "half-width/full-width": 243, + "kanji": 244, + "unlock trackpad (Chrome/Edge)": 251, + "toggle touchpad": 255, + "hyper": 65517, + } +) diff --git a/test/main.go b/test/main.go new file mode 100644 index 0000000..a0359d5 --- /dev/null +++ b/test/main.go @@ -0,0 +1,40 @@ +package main + +import ( + "fmt" + "time" + + "github.com/robotn/gohook" +) + +func main() { + s := hook.Start() + defer hook.End() + + tout := time.After(time.Hour * 2) + done := false + for !done { + select { + case i := <-s: + if i.Kind >= hook.KeyDown && i.Kind <= hook.KeyUp { + if i.Keychar == 'q' { + tout = time.After(0) + } + + fmt.Printf("%v key: %c:%v\n", i.Kind, i.Keychar, i.Rawcode) + } else if i.Kind >= hook.MouseDown && i.Kind < hook.MouseWheel { + //fmt.Printf("x: %v, y: %v, button: %v\n", i.X, i.Y, i.Button) + } else if i.Kind == hook.MouseWheel { + //fmt.Printf("x: %v, y: %v, button: %v, wheel: %v, rotation: %v\n", i.X, i.Y, i.Button,i.Amount,i.Rotation) + } else { + fmt.Printf("%+v\n", i) + } + + case <-tout: + fmt.Print("Done.") + done = true + break + } + } + +} From 933bb63b4e5115121e449860a0185b93e16a80b3 Mon Sep 17 00:00:00 2001 From: Evans Date: Mon, 7 Oct 2024 13:32:54 -0700 Subject: [PATCH 2/2] Update go.mod --- go.mod | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index dd2f07a..ff7abfe 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ -module gohook +module github.com/robotn/gohook -go 1.22 +go 1.17 require ( github.com/vcaesar/keycode v0.10.1