├── .eslintrc.json ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── ci.yml │ └── lint.yml ├── .gitignore ├── .lintignore ├── .npmignore ├── .nvmrc ├── .prettierrc.json ├── CHANGELOG.md ├── CMakeLists.txt ├── LICENSE ├── README.md ├── _config.yml ├── build.js ├── build_def ├── darwin │ ├── binding.gyp │ └── uiohook.gyp ├── linux │ ├── binding.gyp │ └── uiohook.gyp └── win32 │ ├── binding.gyp │ └── uiohook.gyp ├── docs ├── .vuepress │ ├── config.js │ └── override.styl ├── README.md ├── faq.md ├── installation.md ├── manual-build.md ├── os-support.md └── usage.md ├── examples ├── electron-main │ ├── main.js │ ├── package-lock.json │ └── package.json ├── electron-renderer │ ├── index.html │ ├── main.js │ ├── package-lock.json │ └── package.json └── node │ ├── example.js │ ├── mouse-click-propagation.js │ └── package.json ├── helpers.js ├── index.d.ts ├── index.js ├── install.js ├── libuiohook ├── .gitignore ├── AUTHORS ├── CMakeLists.txt ├── COPYING.LESSER.md ├── COPYING.md ├── Makefile.am ├── README.md ├── bootstrap.sh ├── configure.ac ├── include │ └── uiohook.h ├── m4 │ └── ax_pthread.m4 ├── man │ ├── hook_get_auto_repeat_delay.man │ ├── hook_get_auto_repeat_rate.man │ ├── hook_get_multi_click_time.man │ ├── hook_get_pointer_acceleration_multiplier.man │ ├── hook_get_pointer_acceleration_threshold.man │ ├── hook_get_pointer_sensitivity.man │ ├── hook_run.man │ ├── hook_set_dispatch_proc.man │ ├── hook_set_logger_proc.man │ └── hook_stop.man ├── pc │ └── uiohook.pc.in ├── src │ ├── darwin │ │ ├── input_helper.c │ │ ├── input_helper.h │ │ ├── input_hook.c │ │ ├── post_event.c │ │ └── system_properties.c │ ├── demo_hook.c │ ├── demo_hook_async.c │ ├── demo_post.c │ ├── demo_properties.c │ ├── logger.c │ ├── logger.h │ ├── windows │ │ ├── input_helper.c │ │ ├── input_helper.h │ │ ├── input_hook.c │ │ ├── post_event.c │ │ └── system_properties.c │ └── x11 │ │ ├── input_helper.c │ │ ├── input_helper.h │ │ ├── input_hook.c │ │ ├── post_event.c │ │ └── system_properties.c └── test │ ├── input_helper_test.c │ ├── minunit.h │ ├── system_properties_test.c │ └── uiohook_test.c ├── package-lock.json ├── package.json ├── src ├── iohook.cc └── iohook.h └── test └── specs └── keyboard.spec.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true, 5 | "shared-node-browser": true, 6 | "commonjs": true, 7 | "es2021": true 8 | }, 9 | "parserOptions": { 10 | "sourceType": "module", 11 | "ecmaVersion": 12 12 | }, 13 | "extends": ["eslint:recommended", "prettier"], 14 | "plugins": ["only-warn", "prettier"], 15 | "reportUnusedDisableDirectives": true, 16 | "rules": { 17 | "prettier/prettier": "error" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # [djiit] 4 | patreon: djiit 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Expected Behavior 4 | 5 | 6 | 7 | 8 | ## Current Behavior 9 | 10 | 11 | 12 | 13 | ## Possible Solution 14 | 15 | 16 | 17 | 18 | ## Steps to Reproduce (for bugs) 19 | 20 | 21 | 22 | 23 | 1. 24 | 2. 25 | 3. 26 | 4. 27 | 28 | ## Context 29 | 30 | 31 | 32 | 33 | ## Your Environment 34 | 35 | 36 | 37 | - IOHook Version: 38 | - Environment name and version (e.g. Chrome 39, node.js 5.4, electron 11.2): 39 | - Operating System and version (desktop or mobile): 40 | - Optional Link to your project: 41 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | 6 | 7 | ## Motivation and Context 8 | 9 | 10 | 11 | 12 | ## How Has This Been Tested? 13 | 14 | 15 | 16 | 17 | 18 | ## Screenshots (if appropriate): 19 | 20 | ## Types of changes 21 | 22 | 23 | 24 | - [ ] Bug fix (non-breaking change which fixes an issue) 25 | - [ ] New feature (non-breaking change which adds functionality) 26 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 27 | 28 | ## Checklist: 29 | 30 | 31 | 32 | 33 | - [ ] My code follows the code style of this project. 34 | - [ ] My change requires a change to the documentation. 35 | - [ ] I have updated the documentation accordingly. 36 | - [ ] I have added tests to cover my changes. 37 | - [ ] All new and existing tests passed. 38 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | workflow_dispatch: 5 | release: 6 | push: 7 | pull_request: 8 | branches: [master] 9 | 10 | jobs: 11 | ci: 12 | strategy: 13 | matrix: 14 | os: [ubuntu-latest, macos-latest, windows-latest] 15 | arch: [x86_64] 16 | include: 17 | - os: windows-latest 18 | arch: i686 19 | runs-on: ${{ matrix.os }} 20 | name: ${{ matrix.os }} (${{matrix.arch}}) 21 | steps: 22 | - name: Checkout Code 23 | uses: actions/checkout@v2 24 | 25 | - name: Setup Node 26 | uses: actions/setup-node@v2 27 | with: 28 | # Set registry to NPM instead of github packages for publication 29 | registry-url: 'https://registry.npmjs.org' 30 | node-version: 14 31 | 32 | - name: Setup Linux Dependencies 33 | if: startsWith(matrix.os, 'ubuntu') 34 | run: | 35 | sudo apt-get install -y software-properties-common libx11-dev libxtst-dev libxt-dev libx11-xcb-dev libxkbcommon-dev libxkbcommon-x11-dev xorg-dev libxcb-xkb-dev libxkbfile-dev libxinerama-dev 36 | 37 | - name: Install Packages 38 | run: npm i --ignore-scripts 39 | 40 | # Build only if version tag is not present 41 | - name: Build 42 | run: npm run build -- --all --msvs_version=2019 43 | if: startsWith(github.ref, 'refs/tags/v') != true 44 | env: 45 | ARCH: ${{ matrix.arch }} 46 | 47 | # Build and publish if version tag is present 48 | - name: Build & Publish Prebuilds 49 | run: npm run build:ci -- --msvs_version=2019 50 | if: startsWith(github.ref, 'refs/tags/v') 51 | env: 52 | ARCH: ${{ matrix.arch }} 53 | GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} 54 | 55 | - name: Publish NPM Package 56 | if: startsWith(github.ref, 'refs/tags/v') && startsWith(matrix.os, 'ubuntu') 57 | run: npm publish --access public 58 | env: 59 | NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }} 60 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint Code Base 2 | 3 | on: [workflow_dispatch] 4 | 5 | jobs: 6 | super-lint: 7 | name: Lint code base 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - name: Checkout code 12 | uses: actions/checkout@v2 13 | with: 14 | # Full git history is needed to get a proper list of changed files within `super-linter` 15 | fetch-depth: 0 16 | 17 | - name: Lint Code Base 18 | uses: github/super-linter@v3 19 | env: 20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 21 | JAVASCRIPT_DEFAULT_STYLE: prettier 22 | VALIDATE_ALL_CODEBASE: true 23 | VALIDATE_JAVASCRIPT_ES: true 24 | VALIDATE_HTML: true 25 | VALIDATE_MARKDOWN: true 26 | LINTER_RULES_PATH: / 27 | JAVASCRIPT_ES_CONFIG_FILE: .eslintrc.json 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | .scratch 4 | build 5 | builds 6 | docs/.vuepress/dist 7 | logs 8 | node_modules 9 | prebuilds 10 | .env 11 | *.DS_Store 12 | *.iml 13 | *.log 14 | examples/**/.DS_Store 15 | npm-debug.log* 16 | stacktrace* 17 | binding.gyp 18 | uiohook.gyp 19 | 20 | libuiohook/nbproject/ 21 | libuiohook/build/ 22 | libuiohook/autom4te.cache/ 23 | libuiohook/config/ 24 | libuiohook/cmake/ 25 | libuiohook/include/config.h.in* 26 | libuiohook/m4/ltoptions.m4 27 | libuiohook/m4/ltversion.m4 28 | libuiohook/m4/libtool.m4 29 | libuiohook/m4/ltsugar.m4 30 | libuiohook/m4/lt~obsolete.m4 31 | libuiohook/Makefile.in 32 | libuiohook/configure 33 | libuiohook/aclocal.m4 34 | 35 | libuiohook/CMakeFiles/ 36 | libuiohook/demo_hook_async.dir/ 37 | libuiohook/demo_hook.dir/ 38 | libuiohook/demo_post.dir/ 39 | libuiohook/demo_properties.dir/ 40 | libuiohook/Release/ 41 | libuiohook/uiohook.dir/ 42 | libuiohook/*.vcxproj 43 | libuiohook/*.vcxproj.filters 44 | libuiohook/*.cmake 45 | libuiohook/*.sln 46 | -------------------------------------------------------------------------------- /.lintignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | .scratch 4 | build 5 | builds 6 | docs/.vuepress/dist 7 | logs 8 | node_modules 9 | prebuilds 10 | libuiohook 11 | .env 12 | *.DS_Store 13 | *.iml 14 | *.log 15 | examples/electron-example/.DS_Store 16 | npm-debug.log* 17 | stacktrace* 18 | binding.gyp 19 | uiohook.gyp 20 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | .scratch/ 4 | build/ 5 | builds/ 6 | docs/.vuepress/dist/ 7 | logs/ 8 | node_modules/ 9 | prebuilds/ 10 | .github/ 11 | CMakeFiles/ 12 | docs/ 13 | examples/ 14 | test/ 15 | .env 16 | *.DS_Store 17 | *.iml 18 | *.log 19 | npm-debug.log* 20 | stacktrace* 21 | /binding.gyp 22 | /uiohook.gyp 23 | .lintignore 24 | .eslintrc.json 25 | .prettierrc.json 26 | _config.yml 27 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 14 2 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "overrides": [ 3 | { 4 | "files": [".prettierrc", ".eslintrc"], 5 | "options": { 6 | "parser": "json" 7 | } 8 | } 9 | ], 10 | "semi": true, 11 | "singleQuote": true 12 | } 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## v0.7.2 4 | 5 | - fix: Fix macOS and linux prebuilt. 6 | 7 | ## v0.7.1 8 | 9 | - fix: add missing dependency. 10 | 11 | ## v0.7.0 12 | 13 | - feat: Add support for Electron 9+ and Node 12+. 14 | - fix: drop cmake-js for node-gyp. 15 | 16 | ## v0.6.6 17 | 18 | - feat: Add support for Electron 9. 19 | 20 | ## v0.6.5 21 | 22 | - feat: Add support for Electron 8. 23 | 24 | ## v0.6.4 25 | 26 | - feat: Add support for Node 13. 27 | - chore: Fix npm vulnerabilities with audit. 28 | 29 | ## v0.6.3 30 | 31 | - fix: depreacated use of objc_msgSend. 32 | - fix: Update start() types. 33 | - feat: improve x11 use. 34 | 35 | ## v0.6.2 36 | 37 | - feat: Add support for Electron 7. 38 | 39 | ## v0.6.1 40 | 41 | - fix: Update repo name. 42 | 43 | ## v0.6.0 44 | 45 | - feat: Add support for Electron 6. 46 | - deprecation: Drop support for electron < 4.X.X . 47 | 48 | ## v0.5.1 49 | 50 | - chore: Use SSL options when dealing with prebuilts. 51 | 52 | ## v0.5.0 53 | 54 | - deprecation: Drop support for node < 8, electron < 1.8 55 | 56 | ## v0.4.6 57 | 58 | - fix: fix Win32 builds 59 | 60 | ## v0.4.5 61 | 62 | - feat: add support for Electron 5, Node 12 63 | 64 | ## v0.4.4 65 | 66 | - hotfix: fix corrupted 0.4.3 release. 67 | 68 | ## v0.4.3 69 | 70 | - feat: ability to build for a single platform/target. 71 | 72 | ## v0.4.2 73 | 74 | - fix: use cmake-js fork to support Electron 4+ on Windows. 75 | 76 | ## v0.4.1 77 | 78 | - fix: use VS C++ for Electron >= v4. 79 | 80 | ## v0.4.0 81 | 82 | - feat: add support for Electron 4. 83 | 84 | ## v0.3.1 85 | 86 | - fix: allow use of iohook.registerShortcut without a 3rd releaseCallback argument. 87 | 88 | ## v0.3.0 89 | 90 | - feat: drop segfault-handler dependency. 91 | 92 | ## v0.2.6 93 | 94 | - feat: add support for Node 10 95 | 96 | ## v0.2.5 97 | 98 | - feat: git@github.com:wilix-team/iohook.git 99 | - fix: fix documentation link. 100 | - chore : repo transfered to wilix-team org. 101 | 102 | ## v0.2.4 103 | 104 | - feat: ability to listen for when a keyboard shortcut has been released 105 | - feat: ability to unregister a shortcut via the shortcut's keys 106 | 107 | ## v0.2.3 108 | 109 | - feat: Bump libuiohook version 110 | - chore: many travis build improvements 111 | 112 | ## v0.2.2 113 | 114 | - feat: Add support for Node 10 (for real this time...) 115 | 116 | ## v0.2.1 117 | 118 | - feat: Add support for Electron 2.0 119 | - feat: Drop support for node < 6, electron < 1.2 120 | 121 | ## v0.2.0 122 | 123 | - feat: Add modifier keys support 124 | - feat: Add online documentation 125 | - feat: Add tests (reverted for now) 126 | 127 | ## v0.1.16 128 | 129 | - feat: Add support for Electron 2.0 130 | - feat: Add support for NodeJS 10.x 131 | - deprecation: Drop support for Linux ia32 (like NodeJS itself) 132 | 133 | ## v0.1.15 134 | 135 | - feat: Bump libiohook to version 1.1 136 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | 3 | macro(use_c99) 4 | if (CMAKE_VERSION VERSION_LESS "3.1") 5 | if (CMAKE_C_COMPILER_ID STREQUAL "GNU") 6 | set (CMAKE_C_FLAGS "--std=gnu99 ${CMAKE_C_FLAGS}") 7 | endif () 8 | else () 9 | set (CMAKE_C_STANDARD 99) 10 | endif () 11 | endmacro(use_c99) 12 | 13 | use_c99() 14 | 15 | set (CMAKE_CXX_STANDARD 11) 16 | 17 | project(iohook) 18 | 19 | if(WIN32 OR WIN64) 20 | add_subdirectory(libuiohook ${CMAKE_CURRENT_SOURCE_DIR}/libuiohook) 21 | elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") 22 | 23 | #bootstrap and configure 24 | set(_config_headers "${CMAKE_CURRENT_SOURCE_DIR}/libuiohook/include/config.h") 25 | add_custom_target( "prepare_iuhook" 26 | COMMAND "./bootstrap.sh" 27 | COMMAND "./configure" 28 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/libuiohook") 29 | 30 | file(GLOB SOURCE_UIHOOK_FILES "${CMAKE_CURRENT_SOURCE_DIR}/libuiohook/src/logger.c" 31 | "${CMAKE_CURRENT_SOURCE_DIR}/libuiohook/src/logger.h" 32 | "${CMAKE_CURRENT_SOURCE_DIR}/libuiohook/src/x11/*.c" 33 | "${CMAKE_CURRENT_SOURCE_DIR}/libuiohook/src/x11/*.h" 34 | "${CMAKE_CURRENT_SOURCE_DIR}/libuiohook/include/config.h" ) 35 | 36 | add_library( "uiohook" STATIC ${SOURCE_UIHOOK_FILES} ) 37 | set_target_properties("uiohook" PROPERTIES COMPILE_FLAGS "-DHAVE_CONFIG_H=1 -fPIC") 38 | add_dependencies( "uiohook" "prepare_iuhook") 39 | target_include_directories("uiohook" PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/libuiohook/include/" "${CMAKE_CURRENT_SOURCE_DIR}/libuiohook/src" ${CMAKE_CURRENT_SOURCE_DIR}/libuiohook/src/x11 ) 40 | else() 41 | #bootstrap and configure 42 | set(_config_headers "${CMAKE_CURRENT_SOURCE_DIR}/libuiohook/include/config.h") 43 | add_custom_target( "prepare_iuhook" 44 | COMMAND "./bootstrap.sh" 45 | COMMAND "./configure" 46 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/libuiohook") 47 | 48 | file(GLOB SOURCE_UIHOOK_FILES "${CMAKE_CURRENT_SOURCE_DIR}/libuiohook/src/logger.c" 49 | "${CMAKE_CURRENT_SOURCE_DIR}/libuiohook/src/logger.h" 50 | "${CMAKE_CURRENT_SOURCE_DIR}/libuiohook/src/darwin/*.c" 51 | "${CMAKE_CURRENT_SOURCE_DIR}/libuiohook/src/darwin/*.h" 52 | "${CMAKE_CURRENT_SOURCE_DIR}/libuiohook/include/config.h" ) 53 | 54 | add_library( "uiohook" STATIC ${SOURCE_UIHOOK_FILES} ) 55 | set_target_properties("uiohook" PROPERTIES COMPILE_FLAGS "-DHAVE_CONFIG_H=1") 56 | add_dependencies( "uiohook" "prepare_iuhook") 57 | target_include_directories("uiohook" PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/libuiohook/include/" "${CMAKE_CURRENT_SOURCE_DIR}/libuiohook/src" ${CMAKE_CURRENT_SOURCE_DIR}/libuiohook/src/darwin ) 58 | 59 | endif() 60 | 61 | # Build a shared library named after the project from the files in `src/` 62 | file(GLOB SOURCE_FILES "src/*.cc" "src/*.h") 63 | add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES} ${CMAKE_JS_SRC}) 64 | 65 | # Gives our library file a .node extension without any "lib" prefix 66 | set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node") 67 | 68 | # Essential include files to build a node addon, 69 | # You should add this line in every CMake.js based project 70 | target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_JS_INC}) 71 | 72 | # Essential library files to link to a node addon 73 | # You should add this line in every CMake.js based project 74 | target_link_libraries(${PROJECT_NAME} ${CMAKE_JS_LIB} "uiohook") 75 | 76 | if("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") 77 | target_link_libraries(${PROJECT_NAME} ${CMAKE_JS_LIB} "uiohook" "xkbfile" "xkbcommon-x11" "xkbcommon" "X11-xcb" "xcb" "Xinerama" "Xt" "Xtst" "X11") 78 | endif() 79 | 80 | if(CMAKE_SYSTEM_NAME MATCHES "(Darwin)") 81 | find_library(FRAMEWORK_IOKIT IOKit) 82 | find_library(FRAMEWORK_Carbon Carbon) 83 | target_link_libraries(${PROJECT_NAME} ${FRAMEWORK_IOKIT} ${FRAMEWORK_Carbon}) 84 | endif() 85 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 wilix 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iohook 2 | 3 | [![NPM version](https://img.shields.io/npm/v/iohook?color=%230088FF)](https://www.npmjs.com/package/iohook) 4 | [![Release date](https://img.shields.io/github/release-date/wilix-team/iohook?color=%230088FF)](https://github.com/wilix-team/iohook/releases/latest) 5 | [![GitHub Super-Linter](https://github.com/wilix-team/iohook/workflows/Lint%20Code%20Base/badge.svg)](https://github.com/marketplace/actions/super-linter) 6 | [![CI](https://github.com/wilix-team/iohook/actions/workflows/ci.yml/badge.svg)](https://github.com/wilix-team/iohook/actions/workflows/ci.yml) 7 | [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?color=%23008880)](https://github.com/prettier/prettier) 8 | [![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/iohookjs/Lobby) 9 | [![Issues](https://img.shields.io/github/issues-raw/wilix-team/iohook)](https://github.com/wilix-team/iohook/issues) 10 | 11 | ## About 12 | 13 | Node.js global native keyboard and mouse listener. 14 | 15 | This module can handle keyboard and mouse events via native hooks inside and outside your JavaScript/TypeScript application. 16 | 17 | Found a bug? Have an idea? Feel free to post an [issue](https://github.com/wilix-team/iohook/issues) or submit a [PR](https://github.com/wilix-team/iohook/pulls). 18 | 19 | **Check out the [documentation](https://wilix-team.github.io/iohook).** 20 | 21 | ## Platform support 22 | 23 | - Versions >= 0.6.0 support only officially supported platforms versions. 24 | - Versions 0.5.X are the last to support Electron < 4.0.0 25 | - Versions 0.4.X are the last to support for Node < 8.0 and Electron < 2.0.0 26 | 27 | ## Installation 28 | 29 | iohook provides prebuilt version for a bunch of OSes and platforms. 30 | 31 | ### Linux (including WSL) 32 | 33 | ```bash 34 | # On Linux (including WSL) platform, you will need libxkbcommon-x11 installed 35 | sudo apt-get install -y libxkbcommon-x11-0 36 | ``` 37 | 38 | ### All platforms 39 | 40 | ```bash 41 | npm install iohook --save # or yarn add iohook 42 | ``` 43 | 44 | ## FAQ 45 | 46 | Q. _Does this module require Java ?_ 47 | 48 | A. No, this module doesn't require Java (like jnativehook) or any other runtimes. 49 | 50 | Q. _Is iohook compatible with Node/Electron version X.Y.Z ?_ 51 | 52 | A. We try to match the currently supported version of both [Node](https://nodejs.org/en/about/releases/) and [Electron](https://electronjs.org/docs/tutorial/support#currently-supported-versions). 53 | 54 | ## Apps 55 | 56 | Are you using iohook in your project ? Please tell us in a [PR](https://github.com/wilix-team/iohook/pulls) so we can add it to the list ! 57 | 58 | - [Cortex](https://crtx.gg/) 59 | - [Tracklify](https://tracklify.com/) 60 | - [CrewLink](https://github.com/ottomated/CrewLink) 61 | - [Runtime](https://github.com/yikuansun/desktopspeedruntools#runtime-speedrun-tools) 62 | 63 | ## Contributors 64 | 65 | Thanks to _kwhat_ for the [libuiohook](https://github.com/kwhat/libuiohook) project and [ayoubserti](https://github.com/ayoubserti) for the first iohook prototype. 66 | 67 | - [vespakoen](https://github.com/vespakoen) (prebuild system implementation) 68 | - [matthewshirley](https://github.com/matthewshirley) (Windows prebuild fix) 69 | - [djiit](https://github.com/djiit) (project & community help) 70 | - [ezain](https://github.com/eboukamza) (add feature enable/disable mouse click propagation) 71 | - [anoadragon453](https://github.com/anoadragon453) (electron 4+ support) 72 | - [ykhwong](https://github.com/ykhwong) (node-gyp usage, electron 9+ support) 73 | - All the other contributors. Feel free to extend this list ! 74 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-midnight 2 | -------------------------------------------------------------------------------- /build.js: -------------------------------------------------------------------------------- 1 | const spawn = require('child_process').spawn; 2 | const fs = require('fs-extra'); 3 | const path = require('path'); 4 | const tar = require('tar'); 5 | const argv = require('minimist')(process.argv.slice(2), { 6 | // Specify that these arguments should be a string 7 | string: ['version', 'runtime', 'abi'], 8 | }); 9 | const pkg = require('./package.json'); 10 | const nodeAbi = require('node-abi'); 11 | const { optionsFromPackage } = require('./helpers'); 12 | 13 | let arch = process.env.ARCH 14 | ? process.env.ARCH.replace('i686', 'ia32').replace('x86_64', 'x64') 15 | : process.arch; 16 | 17 | let gypJsPath = path.join( 18 | __dirname, 19 | 'node_modules', 20 | '.bin', 21 | process.platform === 'win32' ? 'node-gyp.cmd' : 'node-gyp' 22 | ); 23 | 24 | let files = []; 25 | let targets; 26 | let chain = Promise.resolve(); 27 | 28 | initBuild(); 29 | 30 | function initBuild() { 31 | // Check if a specific runtime has been specified from the command line 32 | if ('runtime' in argv && 'version' in argv && 'abi' in argv) { 33 | targets = [[argv['runtime'], argv['version'], argv['abi']]]; 34 | } else if ('all' in argv) { 35 | // If "--all", use those defined in package.json 36 | targets = require('./package.json').supportedTargets; 37 | } else { 38 | const options = optionsFromPackage(); 39 | if (process.env.npm_config_targets) { 40 | options.targets = options.targets.concat( 41 | process.env.npm_config_targets.split(',') 42 | ); 43 | } 44 | options.targets = options.targets.map((targetStr) => targetStr.split('-')); 45 | if (process.env.npm_config_targets === 'all') { 46 | options.targets = supportedTargets.map((arr) => [arr[0], arr[2]]); 47 | options.platforms = ['win32', 'darwin', 'linux']; 48 | options.arches = ['x64', 'ia32']; 49 | } 50 | if (process.env.npm_config_platforms) { 51 | options.platforms = options.platforms.concat( 52 | process.env.npm_config_platforms.split(',') 53 | ); 54 | } 55 | if (process.env.npm_config_arches) { 56 | options.arches = options.arches.concat( 57 | process.env.npm_config_arches.split(',') 58 | ); 59 | } 60 | 61 | if (options.targets.length > 0) { 62 | targets = options.targets.map((e) => [ 63 | e[0], 64 | nodeAbi.getTarget(e[1], e[0]), 65 | e[1], 66 | ]); 67 | } else { 68 | const runtime = process.versions['electron'] ? 'electron' : 'node'; 69 | const version = process.versions.node; 70 | const abi = process.versions.modules; 71 | targets = [[runtime, version, abi]]; 72 | } 73 | } 74 | 75 | targets.forEach((parts) => { 76 | let runtime = parts[0]; 77 | let version = parts[1]; 78 | let abi = parts[2]; 79 | chain = chain 80 | .then(function () { 81 | return build(runtime, version, abi); 82 | }) 83 | .then(function () { 84 | return tarGz(runtime, abi); 85 | }) 86 | .catch((err) => { 87 | console.error(err); 88 | process.exit(1); 89 | }); 90 | }); 91 | 92 | chain = chain.then(function () { 93 | if ('upload' in argv && argv['upload'] === 'false') { 94 | // If no upload has been specified, don't attempt to upload 95 | return; 96 | } 97 | 98 | return uploadFiles(files); 99 | }); 100 | 101 | cpGyp(); 102 | } 103 | 104 | function cpGyp() { 105 | try { 106 | fs.unlinkSync(path.join(__dirname, 'binding.gyp')); 107 | fs.unlinkSync(path.join(__dirname, 'uiohook.gyp')); 108 | } catch (e) {} 109 | switch (process.platform) { 110 | case 'win32': 111 | case 'darwin': 112 | fs.copySync( 113 | path.join(__dirname, 'build_def', process.platform, 'binding.gyp'), 114 | path.join(__dirname, 'binding.gyp') 115 | ); 116 | fs.copySync( 117 | path.join(__dirname, 'build_def', process.platform, 'uiohook.gyp'), 118 | path.join(__dirname, 'uiohook.gyp') 119 | ); 120 | break; 121 | default: 122 | fs.copySync( 123 | path.join(__dirname, 'build_def', 'linux', 'binding.gyp'), 124 | path.join(__dirname, 'binding.gyp') 125 | ); 126 | fs.copySync( 127 | path.join(__dirname, 'build_def', 'linux', 'uiohook.gyp'), 128 | path.join(__dirname, 'uiohook.gyp') 129 | ); 130 | break; 131 | } 132 | } 133 | 134 | function build(runtime, version, abi) { 135 | return new Promise(function (resolve, reject) { 136 | let args = [ 137 | 'configure', 138 | 'rebuild', 139 | '--target=' + version, 140 | '--arch=' + arch, 141 | ]; 142 | 143 | if (/^electron/i.test(runtime)) { 144 | args.push('--dist-url=https://atom.io/download/electron'); 145 | } 146 | 147 | if (parseInt(abi) >= 80) { 148 | if (arch === 'x64') { 149 | args.push('--v8_enable_pointer_compression=1'); 150 | } else { 151 | args.push('--v8_enable_pointer_compression=0'); 152 | args.push('--v8_enable_31bit_smis_on_64bit_arch=1'); 153 | } 154 | } 155 | if (process.platform !== 'win32') { 156 | if (parseInt(abi) >= 64) { 157 | args.push('--build_v8_with_gn=false'); 158 | } 159 | if (parseInt(abi) >= 67) { 160 | args.push('--enable_lto=false'); 161 | } 162 | } 163 | 164 | console.log('Building iohook for ' + runtime + ' v' + version + '>>>>'); 165 | if (process.platform === 'win32') { 166 | if (version.split('.')[0] >= 4) { 167 | process.env.msvs_toolset = 15; 168 | process.env.msvs_version = argv.msvs_version || 2017; 169 | } else { 170 | process.env.msvs_toolset = 12; 171 | process.env.msvs_version = 2013; 172 | } 173 | args.push('--msvs_version=' + process.env.msvs_version); 174 | } else { 175 | process.env.gyp_iohook_runtime = runtime; 176 | process.env.gyp_iohook_abi = abi; 177 | process.env.gyp_iohook_platform = process.platform; 178 | process.env.gyp_iohook_arch = arch; 179 | } 180 | 181 | let proc = spawn(gypJsPath, args, { 182 | env: process.env, 183 | }); 184 | proc.stdout.pipe(process.stdout); 185 | proc.stderr.pipe(process.stderr); 186 | proc.on('exit', function (code, sig) { 187 | if (code === 1) { 188 | return reject(new Error('Failed to build...')); 189 | } 190 | resolve(); 191 | }); 192 | }); 193 | } 194 | 195 | function tarGz(runtime, abi) { 196 | const FILES_TO_ARCHIVE = { 197 | win32: ['build/Release/iohook.node', 'build/Release/uiohook.dll'], 198 | linux: ['build/Release/iohook.node', 'build/Release/uiohook.so'], 199 | darwin: ['build/Release/iohook.node', 'build/Release/uiohook.dylib'], 200 | }; 201 | const tarPath = 202 | 'prebuilds/iohook-v' + 203 | pkg.version + 204 | '-' + 205 | runtime + 206 | '-v' + 207 | abi + 208 | '-' + 209 | process.platform + 210 | '-' + 211 | arch + 212 | '.tar.gz'; 213 | 214 | files.push(tarPath); 215 | 216 | if (!fs.existsSync(path.dirname(tarPath))) { 217 | fs.mkdirSync(path.dirname(tarPath)); 218 | } 219 | 220 | tar.c( 221 | { 222 | gzip: true, 223 | file: tarPath, 224 | sync: true, 225 | }, 226 | FILES_TO_ARCHIVE[process.platform] 227 | ); 228 | } 229 | 230 | function uploadFiles(files) { 231 | const upload = require('prebuild/upload'); 232 | return new Promise(function (resolve, reject) { 233 | console.log( 234 | 'Uploading ' + files.length + ' prebuilds(s) to Github releases' 235 | ); 236 | let opts = { 237 | pkg: pkg, 238 | files: files, 239 | 'tag-prefix': 'v', 240 | upload: process.env.GITHUB_ACCESS_TOKEN, 241 | }; 242 | upload(opts, function (err, result) { 243 | if (err) { 244 | return reject(err); 245 | } 246 | console.log('Found ' + result.old.length + ' prebuild(s) on Github'); 247 | if (result.old.length) { 248 | result.old.forEach(function (build) { 249 | console.log('-> ' + build); 250 | }); 251 | } 252 | console.log( 253 | 'Uploaded ' + result.new.length + ' new prebuild(s) to Github' 254 | ); 255 | if (result.new.length) { 256 | result.new.forEach(function (build) { 257 | console.log('-> ' + build); 258 | }); 259 | } 260 | resolve(); 261 | }); 262 | }); 263 | } 264 | -------------------------------------------------------------------------------- /build_def/darwin/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [{ 3 | "target_name": "iohook", 4 | "win_delay_load_hook": "true", 5 | "type": "loadable_module", 6 | "sources": [ 7 | "src/iohook.cc", 8 | "src/iohook.h" 9 | ], 10 | "dependencies": [ 11 | "./uiohook.gyp:uiohook" 12 | ], 13 | "cflags": [ 14 | "-std=c99" 15 | ], 16 | "link_settings": { 17 | "libraries": [ 18 | "-Wl,-rpath,@executable_path/.", 19 | "-Wl,-rpath,@loader_path/.", 20 | "-Wl,-rpath, { 65 | console.log(event); // { type: 'mousemove', x: 700, y: 400 } 66 | }); 67 | 68 | // Register and start hook 69 | ioHook.start(); 70 | 71 | // Alternatively, pass true to start in DEBUG mode. 72 | ioHook.start(true); 73 | 74 | // False to disable DEBUG. Cleaner terminal output. 75 | ioHook.start(false); 76 | ``` 77 | 78 | ## Available events 79 | 80 | ### keydown 81 | 82 | Triggered when user presses a key. 83 | 84 | ```js 85 | { 86 | keycode: 46, 87 | rawcode: 8, 88 | type: 'keydown', 89 | altKey: true, 90 | shiftKey: true, 91 | ctrlKey: false, 92 | metaKey: false 93 | } 94 | ``` 95 | 96 | ### keyup 97 | 98 | Triggered when user releases a key. 99 | 100 | ```js 101 | { 102 | keycode: 19, 103 | rawcode: 15, 104 | type: 'keyup', 105 | altKey: true, 106 | shiftKey: true, 107 | ctrlKey: false, 108 | metaKey: false 109 | } 110 | ``` 111 | 112 | ### mouseclick 113 | 114 | Triggered when user clicks a mouse button. 115 | 116 | ```js 117 | { button: 1, clicks: 1, x: 545, y: 696, type: 'mouseclick' } 118 | ``` 119 | 120 | ### mousedown 121 | 122 | Triggered when user clicks a mouse button. 123 | 124 | ```js 125 | { button: 1, clicks: 1, x: 545, y: 696, type: 'mousedown' } 126 | ``` 127 | 128 | ### mouseup 129 | 130 | Triggered when user releases a mouse button. 131 | 132 | ```js 133 | { button: 1, clicks: 1, x: 545, y: 696, type: 'mouseup' } 134 | ``` 135 | 136 | ### mousemove 137 | 138 | Triggered when user moves the mouse. 139 | 140 | ```js 141 | { button: 0, clicks: 0, x: 521, y: 737, type: 'mousemove' } 142 | ``` 143 | 144 | ### mousedrag 145 | 146 | Triggered when user clicks and drags something. 147 | 148 | ```js 149 | { button: 0, clicks: 0, x: 373, y: 683, type: 'mousedrag' } 150 | ``` 151 | 152 | ### mousewheel 153 | 154 | Triggered when user uses the mouse wheel. 155 | 156 | ```js 157 | { amount: 3, clicks: 1, direction: 3, rotation: 1, type: 'mousewheel', x: 466, y: 683 } 158 | ``` 159 | 160 | ## Shortcuts 161 | 162 | You can register global shortcuts. 163 | 164 | ::: tip NOTE 165 | When a shortcut is caught, keyup/keydown events still emit events. It means, that if you register a keyup AND shortcut for `ALT+T`, both events will be emited. 166 | ::: 167 | 168 | ### registerShortcut(keys, callback, releaseCallback?) 169 | 170 | In the next example we register CTRL+F7 shortcut (in MacOS. For other OSes, the keycodes could be different). 171 | 172 | ```js 173 | const id = ioHook.registerShortcut([29, 65], (keys) => { 174 | console.log('Shortcut called with keys:', keys); 175 | }); 176 | ``` 177 | 178 | We can also specify a callback to run when our shortcut has been released by specifying a third function argument. 179 | 180 | ```js 181 | const id = ioHook.registerShortcut( 182 | [29, 65], 183 | (keys) => { 184 | console.log('Shortcut called with keys:', keys); 185 | }, 186 | (keys) => { 187 | console.log('Shortcut has been released!'); 188 | } 189 | ); 190 | ``` 191 | 192 | ### unregisterShortcut(shortcutId) 193 | 194 | You can unregister shortcut by using shortcutId returned by `registerShortcut()`. 195 | 196 | ```js 197 | ioHook.unregisterShortcut(id); 198 | ``` 199 | 200 | ### unregisterShortcutByKeys(keys) 201 | 202 | You can unregister shortcut by using the keys codes passed to `registerShortcut()`. Passing codes in the same order as during registration is not required. 203 | 204 | ```js 205 | ioHook.unregisterShortcutByKeys(keys); 206 | ``` 207 | 208 | ### unregisterAllShortcuts() 209 | 210 | You can also unregister all shortcuts. 211 | 212 | ```js 213 | ioHook.unregisterAllShortcuts(); 214 | ``` 215 | 216 | ### useRawcode(using) 217 | 218 | Some libraries, such as [Mousetrap]() will emit keyboard events that contain 219 | a `rawcode` value. This is a separate, but equally valid, representation of 220 | the key that was pressed. However by default iohook instead uses an event's 221 | `keycode` field to determine which key was pressed. If these key codes do not 222 | line up, your shortcut will not be detected as pressed. 223 | 224 | To tell iohook to use the `rawcode` value instead, simply do so before 225 | starting iohook. 226 | 227 | ```js 228 | iohook.useRawcode(true); 229 | iohook.start(); 230 | ``` 231 | 232 | ### disableClickPropagation() 233 | 234 | You can disable mouse click event propagation. Click events are captured and emitted but not propagated to the window. 235 | 236 | ```js 237 | ioHook.disableClickPropagation(); 238 | ``` 239 | 240 | ### enableClickPropagation() 241 | 242 | You can enable mouse click event propagation if it's disabled. Click event are propagated by default. 243 | 244 | ```js 245 | ioHook.enableClickPropagation(); 246 | ``` 247 | -------------------------------------------------------------------------------- /examples/electron-main/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { app } = require('electron'); 4 | const ioHook = require('iohook'); 5 | 6 | function eventHandler(event) { 7 | console.log(event); 8 | } 9 | 10 | app.on('ready', () => { 11 | console.log( 12 | 'node: ' + 13 | process.versions.node + 14 | ', chromium: ' + 15 | process.versions.chrome + 16 | ', electron: ' + 17 | process.versions.electron 18 | ); 19 | ioHook.start(true); 20 | ioHook.on('mouseclick', eventHandler); 21 | ioHook.on('keydown', eventHandler); 22 | ioHook.on('mousewheel', eventHandler); 23 | ioHook.on('mousemove', eventHandler); 24 | console.log('Try move your mouse or press any key'); 25 | }); 26 | 27 | app.on('before-quit', () => { 28 | ioHook.unload(); 29 | ioHook.stop(); 30 | }); 31 | -------------------------------------------------------------------------------- /examples/electron-main/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iohook-electron-example", 3 | "version": "1.0.0", 4 | "description": "Show how to use this module with electron", 5 | "main": "main.js", 6 | "scripts": { 7 | "start": "electron ." 8 | }, 9 | "author": "Aloyan Dmitry", 10 | "license": "MIT", 11 | "dependencies": { 12 | "electron": ">=11.1.0", 13 | "iohook": "^0.9.2" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/electron-renderer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello World! 6 | 7 | 28 | 29 | 30 | 31 | We are using Node.js , Chromium 32 | , and Electron 33 | .

34 | Try to move your mouse or press any key! 35 |

36 | 37 | 38 | -------------------------------------------------------------------------------- /examples/electron-renderer/main.js: -------------------------------------------------------------------------------- 1 | // Modules to control application life and create native browser window 2 | const { app, BrowserWindow } = require('electron'); 3 | app.allowRendererProcessReuse = false; 4 | 5 | function createWindow() { 6 | // Create the browser window. 7 | const mainWindow = new BrowserWindow({ 8 | width: 1000, 9 | height: 600, 10 | webPreferences: { 11 | nodeIntegration: true, 12 | }, 13 | }); 14 | 15 | // and load the index.html of the app. 16 | mainWindow.loadFile('index.html'); 17 | 18 | // Open the DevTools. 19 | mainWindow.webContents.openDevTools(); 20 | } 21 | 22 | // This method will be called when Electron has finished 23 | // initialization and is ready to create browser windows. 24 | // Some APIs can only be used after this event occurs. 25 | app.whenReady().then(() => { 26 | createWindow(); 27 | }); 28 | 29 | // Quit when all windows are closed 30 | app.on('window-all-closed', function () { 31 | app.quit(); 32 | }); 33 | -------------------------------------------------------------------------------- /examples/electron-renderer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-quick-start", 3 | "version": "1.0.0", 4 | "description": "A minimal Electron application", 5 | "main": "main.js", 6 | "scripts": { 7 | "start": "electron ." 8 | }, 9 | "devDependencies": { 10 | "electron": ">=11.1.0", 11 | "iohook": "^0.9.2" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/node/example.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const ioHook = require('iohook'); 4 | 5 | ioHook.on('mousedown', function (msg) { 6 | console.log(msg); 7 | }); 8 | 9 | ioHook.on('keypress', function (msg) { 10 | console.log(msg); 11 | }); 12 | 13 | ioHook.on('keydown', function (msg) { 14 | console.log(msg); 15 | }); 16 | 17 | ioHook.on('keyup', function (msg) { 18 | console.log(msg); 19 | }); 20 | 21 | ioHook.on('mouseclick', function (msg) { 22 | console.log(msg); 23 | }); 24 | 25 | ioHook.on('mousewheel', function (msg) { 26 | console.log(msg); 27 | }); 28 | 29 | ioHook.on('mousemove', function (msg) { 30 | console.log(msg); 31 | }); 32 | 33 | ioHook.on('mousedrag', function (msg) { 34 | console.log(msg); 35 | }); 36 | 37 | //start ioHook 38 | ioHook.start(); 39 | // ioHook.setDebug(true); // Uncomment this line for see all debug information from iohook 40 | 41 | const CTRL = 29; 42 | const ALT = 56; 43 | const F7 = 65; 44 | 45 | ioHook.registerShortcut([CTRL, F7], (keys) => { 46 | console.log('Shortcut pressed with keys:', keys); 47 | }); 48 | 49 | let shId = ioHook.registerShortcut([ALT, F7], (keys) => { 50 | console.log('This shortcut will be called once. Keys:', keys); 51 | ioHook.unregisterShortcut(shId); 52 | }); 53 | 54 | console.log('Hook started. Try type something or move mouse'); 55 | -------------------------------------------------------------------------------- /examples/node/mouse-click-propagation.js: -------------------------------------------------------------------------------- 1 | const iohook = require('iohook'); 2 | 3 | iohook.on('mouseup', (event) => console.log(event)); 4 | 5 | let clickpropagation = true; 6 | 7 | iohook.on('keyup', (event) => { 8 | if (event.keycode !== 57) return; // space key 9 | 10 | clickpropagation 11 | ? iohook.disableClickPropagation() 12 | : iohook.enableClickPropagation(); 13 | clickpropagation = !clickpropagation; 14 | }); 15 | 16 | iohook.start(true); 17 | 18 | console.log( 19 | 'Hook started. Use space key to enable or disable click propagation' 20 | ); 21 | -------------------------------------------------------------------------------- /examples/node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-quick-start", 3 | "version": "1.0.0", 4 | "description": "A minimal Electron application", 5 | "main": "main.js", 6 | "scripts": { 7 | "start": "node example.js", 8 | "start:propagation": "node mouse-click-propagation.js" 9 | }, 10 | "devDependencies": { 11 | "iohook": "^0.9.2" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /helpers.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | 4 | /** 5 | * Return options for iohook from package.json 6 | * @return {Object} 7 | */ 8 | function optionsFromPackage(attempts) { 9 | attempts = attempts || 2; 10 | if (attempts > 5) { 11 | console.log("Can't resolve main package.json file"); 12 | return { 13 | targets: [], 14 | platforms: [process.platform], 15 | arches: [process.arch], 16 | }; 17 | } 18 | let mainPath = Array(attempts).join('../'); 19 | try { 20 | const content = fs.readFileSync( 21 | path.join(__dirname, mainPath, 'package.json'), 22 | 'utf-8' 23 | ); 24 | const packageJson = JSON.parse(content); 25 | const opts = packageJson.iohook || {}; 26 | if (!opts.targets) { 27 | opts.targets = []; 28 | } 29 | if (!opts.platforms) opts.platforms = [process.platform]; 30 | if (!opts.arches) opts.arches = [process.arch]; 31 | return opts; 32 | } catch (e) { 33 | return optionsFromPackage(attempts + 1); 34 | } 35 | } 36 | 37 | function printManualBuildParams() { 38 | const runtime = process.versions['electron'] ? 'electron' : 'node'; 39 | const essential = 40 | runtime + 41 | '-v' + 42 | process.versions.modules + 43 | '-' + 44 | process.platform + 45 | '-' + 46 | process.arch; 47 | const modulePath = path.join( 48 | __dirname, 49 | 'builds', 50 | essential, 51 | 'build', 52 | 'Release', 53 | 'iohook.node' 54 | ); 55 | console.info( 56 | `Runtime: ${runtime} ABI: ${process.versions.modules} Platform: ${process.platform} ARCH: ${process.arch}` 57 | ); 58 | console.info('The path is:', modulePath); 59 | } 60 | 61 | module.exports = { optionsFromPackage, printManualBuildParams }; 62 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'events'; 2 | 3 | /** 4 | * Native module for hooking keyboard and mouse events 5 | */ 6 | declare class IOHook extends EventEmitter { 7 | /** 8 | * Start hooking engine. Call it when you ready to receive events 9 | * @param {boolean} [enableLogger] If true, module will publish debug information to stdout 10 | */ 11 | start(enableLogger?: boolean): void; 12 | 13 | /** 14 | * Stop rising keyboard/mouse events 15 | */ 16 | stop(): void; 17 | 18 | /** 19 | * Manual native code load. Call this function only if unload called before 20 | */ 21 | load(): void; 22 | 23 | /** 24 | * Unload native code and free memory and system hooks 25 | */ 26 | unload(): void; 27 | 28 | /** 29 | * Enable/Disable stdout debug 30 | * @param {boolean} mode 31 | */ 32 | setDebug(mode: boolean): void; 33 | 34 | /** 35 | * Specify that key event's `rawcode` property should be used instead of 36 | * `keycode` when listening for key presses. 37 | * 38 | * This allows iohook to be used in conjunction with other programs that may 39 | * only provide a keycode. 40 | * @param {Boolean} using 41 | */ 42 | useRawcode(using: boolean): void; 43 | 44 | /** 45 | * Enable mouse click propagation (enabled by default). 46 | * The click event are emitted and propagated. 47 | */ 48 | enableClickPropagation(): void; 49 | 50 | /** 51 | * Disable mouse click propagation. 52 | * The click event are captured and the event emitted but not propagated to the window. 53 | */ 54 | disableClickPropagation(): void; 55 | 56 | /** 57 | * Register global shortcut. When all keys in keys array pressed, callback will be called 58 | * @param {Array} keys Array of keycodes 59 | * @param {Function} callback Callback for when shortcut pressed 60 | * @param {Function} [releaseCallback] Callback for when shortcut released 61 | * @return {number} ShortcutId for unregister 62 | */ 63 | registerShortcut( 64 | keys: Array, 65 | callback: Function, 66 | releaseCallback?: Function 67 | ): number; 68 | 69 | /** 70 | * Unregister shortcut by ShortcutId 71 | * @param {number} shortcutId 72 | */ 73 | unregisterShortcut(shortcutId: number): void; 74 | 75 | /** 76 | * Unregister shortcut via its key codes 77 | * @param {Array} keys 78 | */ 79 | unregisterShortcut(keys: Array): void; 80 | 81 | /** 82 | * Unregister all shortcuts 83 | */ 84 | unregisterAllShortcuts(): void; 85 | } 86 | 87 | declare interface IOHookEvent { 88 | type: string; 89 | keychar?: number; 90 | keycode?: number; 91 | rawcode?: number; 92 | button?: number; 93 | clicks?: number; 94 | x?: number; 95 | y?: number; 96 | } 97 | 98 | declare const iohook: IOHook; 99 | 100 | export = iohook; 101 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('events'); 2 | const path = require('path'); 3 | 4 | const runtime = process.versions['electron'] ? 'electron' : 'node'; 5 | const essential = 6 | runtime + 7 | '-v' + 8 | process.versions.modules + 9 | '-' + 10 | process.platform + 11 | '-' + 12 | process.arch; 13 | const modulePath = path.join( 14 | __dirname, 15 | 'builds', 16 | essential, 17 | 'build', 18 | 'Release', 19 | 'iohook.node' 20 | ); 21 | if (process.env.DEBUG) { 22 | console.info('Loading native binary:', modulePath); 23 | } 24 | let NodeHookAddon = require(modulePath); 25 | 26 | const events = { 27 | 3: 'keypress', 28 | 4: 'keydown', 29 | 5: 'keyup', 30 | 6: 'mouseclick', 31 | 7: 'mousedown', 32 | 8: 'mouseup', 33 | 9: 'mousemove', 34 | 10: 'mousedrag', 35 | 11: 'mousewheel', 36 | }; 37 | 38 | class IOHook extends EventEmitter { 39 | constructor() { 40 | super(); 41 | this.active = false; 42 | this.shortcuts = []; 43 | this.eventProperty = 'keycode'; 44 | this.activatedShortcuts = []; 45 | 46 | this.lastKeydownShift = false; 47 | this.lastKeydownAlt = false; 48 | this.lastKeydownCtrl = false; 49 | this.lastKeydownMeta = false; 50 | 51 | this.load(); 52 | this.setDebug(false); 53 | } 54 | 55 | /** 56 | * Start hook process 57 | * @param {boolean} [enableLogger] Turn on debug logging 58 | */ 59 | start(enableLogger) { 60 | if (!this.active) { 61 | this.active = true; 62 | this.setDebug(enableLogger); 63 | } 64 | } 65 | 66 | /** 67 | * Shutdown event hook 68 | */ 69 | stop() { 70 | if (this.active) { 71 | this.active = false; 72 | } 73 | } 74 | 75 | /** 76 | * Register global shortcut. When all keys in keys array pressed, callback will be called 77 | * @param {Array} keys Array of keycodes 78 | * @param {Function} callback Callback for when shortcut pressed 79 | * @param {Function} [releaseCallback] Callback for when shortcut has been released 80 | * @return {number} ShortcutId for unregister 81 | */ 82 | registerShortcut(keys, callback, releaseCallback) { 83 | let shortcut = {}; 84 | let shortcutId = Date.now() + Math.random(); 85 | keys.forEach((keyCode) => { 86 | shortcut[keyCode] = false; 87 | }); 88 | shortcut.id = shortcutId; 89 | shortcut.callback = callback; 90 | shortcut.releaseCallback = releaseCallback; 91 | this.shortcuts.push(shortcut); 92 | return shortcutId; 93 | } 94 | 95 | /** 96 | * Unregister shortcut by ShortcutId 97 | * @param shortcutId 98 | */ 99 | unregisterShortcut(shortcutId) { 100 | this.shortcuts.forEach((shortcut, i) => { 101 | if (shortcut.id === shortcutId) { 102 | this.shortcuts.splice(i, 1); 103 | } 104 | }); 105 | } 106 | 107 | /** 108 | * Unregister shortcut via its key codes 109 | * @param {string} keyCodes Keyboard keys matching the shortcut that should be unregistered 110 | */ 111 | unregisterShortcutByKeys(keyCodes) { 112 | // A traditional loop is used in order to access `this` from inside 113 | for (let i = 0; i < this.shortcuts.length; i++) { 114 | let shortcut = this.shortcuts[i]; 115 | 116 | // Convert any keycode numbers to strings 117 | keyCodes.forEach((key, index) => { 118 | if (typeof key !== 'string' && !(key instanceof String)) { 119 | // Convert to string 120 | keyCodes[index] = key.toString(); 121 | } 122 | }); 123 | 124 | // Check if this is our shortcut 125 | Object.keys(shortcut).every((key) => { 126 | if (key === 'callback' || key === 'id') return; 127 | 128 | // Remove all given keys from keyCodes 129 | // If any are not in this shortcut, then this shortcut does not match 130 | // If at the end we have eliminated all codes in keyCodes, then we have succeeded 131 | let index = keyCodes.indexOf(key); 132 | if (index === -1) return false; // break 133 | 134 | // Remove this key from the given keyCodes array 135 | keyCodes.splice(index, 1); 136 | return true; 137 | }); 138 | 139 | // Is this the shortcut we want to remove? 140 | if (keyCodes.length === 0) { 141 | // Unregister this shortcut 142 | this.shortcuts.splice(i, 1); 143 | return; 144 | } 145 | } 146 | } 147 | 148 | /** 149 | * Unregister all shortcuts 150 | */ 151 | unregisterAllShortcuts() { 152 | this.shortcuts.splice(0, this.shortcuts.length); 153 | } 154 | 155 | /** 156 | * Load native module 157 | */ 158 | load() { 159 | NodeHookAddon.startHook(this._handler.bind(this), this.debug || false); 160 | } 161 | 162 | /** 163 | * Unload native module and stop hook 164 | */ 165 | unload() { 166 | this.stop(); 167 | NodeHookAddon.stopHook(); 168 | } 169 | 170 | /** 171 | * Enable or disable native debug output 172 | * @param {Boolean} mode 173 | */ 174 | setDebug(mode) { 175 | NodeHookAddon.debugEnable(mode); 176 | } 177 | 178 | /** 179 | * Specify that key event's `rawcode` property should be used instead of 180 | * `keycode` when listening for key presses. 181 | * 182 | * This allows iohook to be used in conjunction with other programs that may 183 | * only provide a keycode. 184 | * @param {Boolean} using 185 | */ 186 | useRawcode(using) { 187 | // If true, use rawcode, otherwise use keycode 188 | this.eventProperty = using ? 'rawcode' : 'keycode'; 189 | } 190 | 191 | /** 192 | * Disable mouse click propagation. 193 | * The click event are captured and the event emitted but not propagated to the window. 194 | */ 195 | disableClickPropagation() { 196 | NodeHookAddon.grabMouseClick(true); 197 | } 198 | 199 | /** 200 | * Enable mouse click propagation (enabled by default). 201 | * The click event are emitted and propagated. 202 | */ 203 | enableClickPropagation() { 204 | NodeHookAddon.grabMouseClick(false); 205 | } 206 | 207 | /** 208 | * Local event handler. Don't use it in your code! 209 | * @param msg Raw event message 210 | * @private 211 | */ 212 | _handler(msg) { 213 | if (this.active === false || !msg) return; 214 | 215 | if (events[msg.type]) { 216 | const event = msg.mouse || msg.keyboard || msg.wheel; 217 | 218 | event.type = events[msg.type]; 219 | 220 | this._handleShift(event); 221 | this._handleAlt(event); 222 | this._handleCtrl(event); 223 | this._handleMeta(event); 224 | 225 | this.emit(events[msg.type], event); 226 | 227 | // If there is any registered shortcuts then handle them. 228 | if ( 229 | (event.type === 'keydown' || event.type === 'keyup') && 230 | iohook.shortcuts.length > 0 231 | ) { 232 | this._handleShortcut(event); 233 | } 234 | } 235 | } 236 | 237 | /** 238 | * Handles the shift key. Whenever shift is pressed, all future events would 239 | * contain { shiftKey: true } in its object, until the shift key is released. 240 | * @param event Event object 241 | * @private 242 | */ 243 | _handleShift(event) { 244 | if (event.type === 'keyup' && event.shiftKey) { 245 | this.lastKeydownShift = false; 246 | } 247 | 248 | if (event.type === 'keydown' && event.shiftKey) { 249 | this.lastKeydownShift = true; 250 | } 251 | 252 | if (this.lastKeydownShift) { 253 | event.shiftKey = true; 254 | } 255 | } 256 | 257 | /** 258 | * Handles the alt key. Whenever alt is pressed, all future events would 259 | * contain { altKey: true } in its object, until the alt key is released. 260 | * @param event Event object 261 | * @private 262 | */ 263 | _handleAlt(event) { 264 | if (event.type === 'keyup' && event.altKey) { 265 | this.lastKeydownAlt = false; 266 | } 267 | 268 | if (event.type === 'keydown' && event.altKey) { 269 | this.lastKeydownAlt = true; 270 | } 271 | 272 | if (this.lastKeydownAlt) { 273 | event.altKey = true; 274 | } 275 | } 276 | 277 | /** 278 | * Handles the ctrl key. Whenever ctrl is pressed, all future events would 279 | * contain { ctrlKey: true } in its object, until the ctrl key is released. 280 | * @param event Event object 281 | * @private 282 | */ 283 | _handleCtrl(event) { 284 | if (event.type === 'keyup' && event.ctrlKey) { 285 | this.lastKeydownCtrl = false; 286 | } 287 | 288 | if (event.type === 'keydown' && event.ctrlKey) { 289 | this.lastKeydownCtrl = true; 290 | } 291 | 292 | if (this.lastKeydownCtrl) { 293 | event.ctrlKey = true; 294 | } 295 | } 296 | 297 | /** 298 | * Handles the meta key. Whenever meta is pressed, all future events would 299 | * contain { metaKey: true } in its object, until the meta key is released. 300 | * @param event Event object 301 | * @private 302 | */ 303 | _handleMeta(event) { 304 | if (event.type === 'keyup' && event.metaKey) { 305 | this.lastKeydownMeta = false; 306 | } 307 | 308 | if (event.type === 'keydown' && event.metaKey) { 309 | this.lastKeydownMeta = true; 310 | } 311 | 312 | if (this.lastKeydownMeta) { 313 | event.metaKey = true; 314 | } 315 | } 316 | 317 | /** 318 | * Local shortcut event handler 319 | * @param event Event object 320 | * @private 321 | */ 322 | _handleShortcut(event) { 323 | if (this.active === false) { 324 | return; 325 | } 326 | 327 | // Keep track of shortcuts that are currently active 328 | let activatedShortcuts = this.activatedShortcuts; 329 | 330 | if (event.type === 'keydown') { 331 | this.shortcuts.forEach((shortcut) => { 332 | if (shortcut[event[this.eventProperty]] !== undefined) { 333 | // Mark this key as currently being pressed 334 | shortcut[event[this.eventProperty]] = true; 335 | 336 | let keysTmpArray = []; 337 | let callme = true; 338 | 339 | // Iterate through each keyboard key in this shortcut 340 | Object.keys(shortcut).forEach((key) => { 341 | if (key === 'callback' || key === 'releaseCallback' || key === 'id') 342 | return; 343 | 344 | // If one of the keys aren't pressed... 345 | if (shortcut[key] === false) { 346 | // Don't call the callback and empty our temp tracking array 347 | callme = false; 348 | keysTmpArray.splice(0, keysTmpArray.length); 349 | 350 | return; 351 | } 352 | 353 | // Otherwise, this key is being pressed. 354 | // Add it to the array of keyboard keys we will send as an argument 355 | // to our callback 356 | keysTmpArray.push(key); 357 | }); 358 | if (callme) { 359 | shortcut.callback(keysTmpArray); 360 | 361 | // Add this shortcut from our activate shortcuts array if not 362 | // already activated 363 | if (activatedShortcuts.indexOf(shortcut) === -1) { 364 | activatedShortcuts.push(shortcut); 365 | } 366 | } 367 | } 368 | }); 369 | } else if (event.type === 'keyup') { 370 | // Mark this key as currently not being pressed in all of our shortcuts 371 | this.shortcuts.forEach((shortcut) => { 372 | if (shortcut[event[this.eventProperty]] !== undefined) { 373 | shortcut[event[this.eventProperty]] = false; 374 | } 375 | }); 376 | 377 | // Check if any of our currently pressed shortcuts have been released 378 | // "released" means that all of the keys that the shortcut defines are no 379 | // longer being pressed 380 | this.activatedShortcuts.forEach((shortcut) => { 381 | if (shortcut[event[this.eventProperty]] === undefined) return; 382 | 383 | let shortcutReleased = true; 384 | let keysTmpArray = []; 385 | Object.keys(shortcut).forEach((key) => { 386 | if (key === 'callback' || key === 'releaseCallback' || key === 'id') 387 | return; 388 | keysTmpArray.push(key); 389 | 390 | // If any key is true, and thus still pressed, the shortcut is still 391 | // being held 392 | if (shortcut[key]) { 393 | shortcutReleased = false; 394 | } 395 | }); 396 | 397 | if (shortcutReleased) { 398 | // Call the released function handler 399 | if (shortcut.releaseCallback) { 400 | shortcut.releaseCallback(keysTmpArray); 401 | } 402 | 403 | // Remove this shortcut from our activate shortcuts array 404 | const index = this.activatedShortcuts.indexOf(shortcut); 405 | if (index !== -1) this.activatedShortcuts.splice(index, 1); 406 | } 407 | }); 408 | } 409 | } 410 | } 411 | 412 | const iohook = new IOHook(); 413 | 414 | module.exports = iohook; 415 | -------------------------------------------------------------------------------- /install.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const os = require('os'); 6 | const nugget = require('nugget'); 7 | const rc = require('rc'); 8 | const pump = require('pump'); 9 | const tfs = require('tar-fs'); 10 | const zlib = require('zlib'); 11 | const pkg = require('./package.json'); 12 | const supportedTargets = require('./package.json').supportedTargets; 13 | const { optionsFromPackage } = require('./helpers'); 14 | 15 | function onerror(err) { 16 | throw err; 17 | } 18 | 19 | /** 20 | * Download and Install prebuild 21 | * @param runtime 22 | * @param abi 23 | * @param platform 24 | * @param arch 25 | * @param cb Callback 26 | */ 27 | function install(runtime, abi, platform, arch, cb) { 28 | const essential = runtime + '-v' + abi + '-' + platform + '-' + arch; 29 | const pkgVersion = pkg.version; 30 | const currentPlatform = 'iohook-v' + pkgVersion + '-' + essential; 31 | 32 | console.log('Downloading prebuild for platform:', currentPlatform); 33 | let downloadUrl = 34 | 'https://github.com/wilix-team/iohook/releases/download/v' + 35 | pkgVersion + 36 | '/' + 37 | currentPlatform + 38 | '.tar.gz'; 39 | 40 | let nuggetOpts = { 41 | dir: os.tmpdir(), 42 | target: 'prebuild.tar.gz', 43 | strictSSL: true, 44 | }; 45 | 46 | let npmrc = {}; 47 | 48 | try { 49 | rc('npm', npmrc); 50 | } catch (error) { 51 | console.warn('Error reading npm configuration: ' + error.message); 52 | } 53 | 54 | if (npmrc && npmrc.proxy) { 55 | nuggetOpts.proxy = npmrc.proxy; 56 | } 57 | 58 | if (npmrc && npmrc['https-proxy']) { 59 | nuggetOpts.proxy = npmrc['https-proxy']; 60 | } 61 | 62 | if (npmrc && npmrc['strict-ssl'] === false) { 63 | nuggetOpts.strictSSL = false; 64 | } 65 | 66 | nugget(downloadUrl, nuggetOpts, function (errors) { 67 | if (errors) { 68 | const error = errors[0]; 69 | 70 | if (error.message.indexOf('404') === -1) { 71 | onerror(error); 72 | } else { 73 | console.error( 74 | 'Prebuild for current platform (' + currentPlatform + ') not found!' 75 | ); 76 | console.error('Try to build for your platform manually:'); 77 | console.error('# cd node_modules/iohook;'); 78 | console.error('# npm install'); 79 | console.error('# npm run build'); 80 | console.error(''); 81 | } 82 | } 83 | 84 | let options = { 85 | readable: true, 86 | writable: true, 87 | hardlinkAsFilesFallback: true, 88 | }; 89 | 90 | let binaryName; 91 | let updateName = function (entry) { 92 | if (/\.node$/i.test(entry.name)) binaryName = entry.name; 93 | }; 94 | let targetFile = path.join(__dirname, 'builds', essential); 95 | let extract = tfs.extract(targetFile, options).on('entry', updateName); 96 | pump( 97 | fs.createReadStream(path.join(nuggetOpts.dir, nuggetOpts.target)), 98 | zlib.createGunzip(), 99 | extract, 100 | function (err) { 101 | if (err) { 102 | return onerror(err); 103 | } 104 | cb(); 105 | } 106 | ); 107 | }); 108 | } 109 | 110 | const options = optionsFromPackage(); 111 | 112 | if (process.env.npm_config_targets) { 113 | options.targets = options.targets.concat( 114 | process.env.npm_config_targets.split(',') 115 | ); 116 | } 117 | if (process.env.npm_config_targets === 'all') { 118 | options.targets = supportedTargets.map((arr) => [arr[0], arr[2]]); 119 | options.platforms = ['win32', 'darwin', 'linux']; 120 | options.arches = ['x64', 'ia32']; 121 | } 122 | if (process.env.npm_config_platforms) { 123 | options.platforms = options.platforms.concat( 124 | process.env.npm_config_platforms.split(',') 125 | ); 126 | } 127 | if (process.env.npm_config_arches) { 128 | options.arches = options.arches.concat( 129 | process.env.npm_config_arches.split(',') 130 | ); 131 | } 132 | 133 | // Choice prebuilds for install 134 | if (options.targets.length > 0) { 135 | let chain = Promise.resolve(); 136 | options.targets.forEach(function (target) { 137 | if (typeof target === 'object') { 138 | chain = chain.then(function () { 139 | return new Promise(function (resolve) { 140 | console.log(target.runtime, target.abi, target.platform, target.arch); 141 | install( 142 | target.runtime, 143 | target.abi, 144 | target.platform, 145 | target.arch, 146 | resolve 147 | ); 148 | }); 149 | }); 150 | return; 151 | } 152 | let parts = target.split('-'); 153 | let runtime = parts[0]; 154 | let abi = parts[1]; 155 | options.platforms.forEach(function (platform) { 156 | options.arches.forEach(function (arch) { 157 | if (platform === 'darwin' && arch === 'ia32') { 158 | return; 159 | } 160 | chain = chain.then(function () { 161 | return new Promise(function (resolve) { 162 | console.log(runtime, abi, platform, arch); 163 | install(runtime, abi, platform, arch, resolve); 164 | }); 165 | }); 166 | }); 167 | }); 168 | }); 169 | } else { 170 | const runtime = process.versions['electron'] ? 'electron' : 'node'; 171 | const abi = process.versions.modules; 172 | const platform = process.platform; 173 | const arch = process.arch; 174 | install(runtime, abi, platform, arch, function () {}); 175 | } 176 | -------------------------------------------------------------------------------- /libuiohook/.gitignore: -------------------------------------------------------------------------------- 1 | /nbproject/ 2 | /build/ 3 | /autom4te.cache/ 4 | /config/ 5 | /cmake/ 6 | /include/config.h.in* 7 | /m4/ltoptions.m4 8 | /m4/ltversion.m4 9 | /m4/libtool.m4 10 | /m4/ltsugar.m4 11 | /m4/lt~obsolete.m4 12 | /Makefile.in 13 | /configure 14 | /aclocal.m4 15 | -------------------------------------------------------------------------------- /libuiohook/AUTHORS: -------------------------------------------------------------------------------- 1 | Alexander Barker 2 | 3 | Anthony Liguori 4 | original version of the keycode_to_scancode function in x11/input_helper.c 5 | 6 | Iván Munsuri Ibáñez 7 | contributed patches to include support for media keys and objective-c callbacks in x11/input_hook.c 8 | contributed hook_create_screen_info property for Windows and Darwin 9 | 10 | Fabrice Bellard 11 | xfree86_keycode_to_scancode_table lookup in x11/input_helper.c 12 | 13 | Marc-André Moreau 14 | original version of windows/input_helper.c 15 | original version of windows/input_helper.h 16 | 17 | Markus G. Kuhn 18 | keysym_unicode_table lookup table in x11/input_helper.c 19 | original version of the unicode_to_keysym function in x11/input_helper.c 20 | original version of the keysym_to_unicode function in x11/input_helper.c 21 | -------------------------------------------------------------------------------- /libuiohook/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | 3 | project(libuiohook) 4 | 5 | # libuihook sources. 6 | if(WIN32 OR WIN64) 7 | set(UIOHOOK_SRC 8 | "${CMAKE_CURRENT_SOURCE_DIR}/include/uiohook.h" 9 | "${CMAKE_CURRENT_SOURCE_DIR}/src/logger.c" 10 | "${CMAKE_CURRENT_SOURCE_DIR}/src/logger.h" 11 | "${CMAKE_CURRENT_SOURCE_DIR}/src/windows/input_helper.h" 12 | "${CMAKE_CURRENT_SOURCE_DIR}/src/windows/input_helper.c" 13 | "${CMAKE_CURRENT_SOURCE_DIR}/src/windows/input_hook.c" 14 | "${CMAKE_CURRENT_SOURCE_DIR}/src/windows/post_event.c" 15 | "${CMAKE_CURRENT_SOURCE_DIR}/src/windows/system_properties.c" 16 | ) 17 | elseif(LINUX) 18 | set(UIOHOOK_SRC 19 | "include/uiohook.h" 20 | "src/logger.c" 21 | "src/logger.h" 22 | "src/x11/*.h" 23 | "src/x11/*.c" 24 | ) 25 | elseif(APPLE) 26 | set(UIOHOOK_SRC 27 | "include/uiohook.h" 28 | "src/logger.c" 29 | "src/logger.h" 30 | "src/darwin/*.h" 31 | "src/darwin/*.c" 32 | ) 33 | else() 34 | error("unknown OS") 35 | return() 36 | 37 | endif() 38 | 39 | 40 | #library 41 | add_library("uiohook" SHARED ${UIOHOOK_SRC}) 42 | if(WIN32 OR WIN64) 43 | target_include_directories("uiohook" PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_SOURCE_DIR}/src" "${CMAKE_CURRENT_SOURCE_DIR}/windows") 44 | 45 | if(MSVC_VERSION LESS 1900) 46 | SET_TARGET_PROPERTIES("uiohook" PROPERTIES COMPILE_FLAGS "-Dinline=__inline -D_CRT_SECURE_NO_WARNINGS -Dsnprintf=_snprintf") 47 | else() 48 | SET_TARGET_PROPERTIES("uiohook" PROPERTIES COMPILE_FLAGS "-Dinline=__inline -D_CRT_SECURE_NO_WARNINGS") 49 | endif() 50 | endif() 51 | 52 | #demo_hook 53 | add_executable("demo_hook" "${CMAKE_CURRENT_SOURCE_DIR}/src/demo_hook.c") 54 | add_dependencies("demo_hook" "uiohook") 55 | target_link_libraries("demo_hook" "uiohook") 56 | if(WIN32 OR WIN64) 57 | if(MSVC_VERSION LESS 1900) 58 | SET_TARGET_PROPERTIES("uiohook" PROPERTIES COMPILE_FLAGS "-Dinline=__inline -D_CRT_SECURE_NO_WARNINGS -Dsnprintf=_snprintf") 59 | else() 60 | SET_TARGET_PROPERTIES("uiohook" PROPERTIES COMPILE_FLAGS "-Dinline=__inline -D_CRT_SECURE_NO_WARNINGS") 61 | endif() 62 | endif() 63 | 64 | #demo_hook_async 65 | add_executable("demo_hook_async" "${CMAKE_CURRENT_SOURCE_DIR}/src/demo_hook_async.c") 66 | add_dependencies("demo_hook_async" "uiohook") 67 | target_link_libraries("demo_hook_async" "uiohook") 68 | if(WIN32 OR WIN64) 69 | if(MSVC_VERSION LESS 1900) 70 | SET_TARGET_PROPERTIES("uiohook" PROPERTIES COMPILE_FLAGS "-Dinline=__inline -D_CRT_SECURE_NO_WARNINGS -Dsnprintf=_snprintf") 71 | else() 72 | SET_TARGET_PROPERTIES("uiohook" PROPERTIES COMPILE_FLAGS "-Dinline=__inline -D_CRT_SECURE_NO_WARNINGS") 73 | endif() 74 | endif() 75 | 76 | #demo_post 77 | add_executable("demo_post" "${CMAKE_CURRENT_SOURCE_DIR}/src/demo_post.c") 78 | add_dependencies("demo_post" "uiohook") 79 | target_link_libraries("demo_post" "uiohook") 80 | if(WIN32 OR WIN64) 81 | if(MSVC_VERSION LESS 1900) 82 | SET_TARGET_PROPERTIES("uiohook" PROPERTIES COMPILE_FLAGS "-Dinline=__inline -D_CRT_SECURE_NO_WARNINGS -Dsnprintf=_snprintf") 83 | else() 84 | SET_TARGET_PROPERTIES("uiohook" PROPERTIES COMPILE_FLAGS "-Dinline=__inline -D_CRT_SECURE_NO_WARNINGS") 85 | endif() 86 | endif() 87 | 88 | #demo_properties 89 | add_executable("demo_properties" "${CMAKE_CURRENT_SOURCE_DIR}/src/demo_properties.c") 90 | add_dependencies("demo_properties" "uiohook") 91 | target_link_libraries("demo_properties" "uiohook") 92 | if(WIN32 OR WIN64) 93 | if(MSVC_VERSION LESS 1900) 94 | SET_TARGET_PROPERTIES("uiohook" PROPERTIES COMPILE_FLAGS "-Dinline=__inline -D_CRT_SECURE_NO_WARNINGS -Dsnprintf=_snprintf") 95 | else() 96 | SET_TARGET_PROPERTIES("uiohook" PROPERTIES COMPILE_FLAGS "-Dinline=__inline -D_CRT_SECURE_NO_WARNINGS") 97 | endif() 98 | endif() 99 | 100 | 101 | #all demo 102 | add_custom_target("all_demo" 103 | DEPENDS 104 | "demo_hook" 105 | "demo_hook_async" 106 | "demo_post" 107 | "demo_properties" 108 | ) 109 | -------------------------------------------------------------------------------- /libuiohook/COPYING.LESSER.md: -------------------------------------------------------------------------------- 1 | GNU Lesser General Public License 2 | ================================= 3 | 4 | Version 3, 29 June 2007 5 | 6 | Copyright © 2007 Free Software Foundation, Inc. <> 7 | 8 | Everyone is permitted to copy and distribute verbatim copies 9 | of this license document, but changing it is not allowed. 10 | 11 | 12 | This version of the GNU Lesser General Public License incorporates 13 | the terms and conditions of version 3 of the GNU General Public 14 | License, supplemented by the additional permissions listed below. 15 | 16 | ### 0. Additional Definitions. 17 | 18 | As used herein, “this License” refers to version 3 of the GNU Lesser 19 | General Public License, and the “GNU GPL” refers to version 3 of the GNU 20 | General Public License. 21 | 22 | “The Library” refers to a covered work governed by this License, 23 | other than an Application or a Combined Work as defined below. 24 | 25 | An “Application” is any work that makes use of an interface provided 26 | by the Library, but which is not otherwise based on the Library. 27 | Defining a subclass of a class defined by the Library is deemed a mode 28 | of using an interface provided by the Library. 29 | 30 | A “Combined Work” is a work produced by combining or linking an 31 | Application with the Library. The particular version of the Library 32 | with which the Combined Work was made is also called the “Linked 33 | Version”. 34 | 35 | The “Minimal Corresponding Source” for a Combined Work means the 36 | Corresponding Source for the Combined Work, excluding any source code 37 | for portions of the Combined Work that, considered in isolation, are 38 | based on the Application, and not on the Linked Version. 39 | 40 | The “Corresponding Application Code” for a Combined Work means the 41 | object code and/or source code for the Application, including any data 42 | and utility programs needed for reproducing the Combined Work from the 43 | Application, but excluding the System Libraries of the Combined Work. 44 | 45 | ### 1. Exception to Section 3 of the GNU GPL. 46 | 47 | You may convey a covered work under sections 3 and 4 of this License 48 | without being bound by section 3 of the GNU GPL. 49 | 50 | ### 2. Conveying Modified Versions. 51 | 52 | If you modify a copy of the Library, and, in your modifications, a 53 | facility refers to a function or data to be supplied by an Application 54 | that uses the facility (other than as an argument passed when the 55 | facility is invoked), then you may convey a copy of the modified 56 | version: 57 | 58 | * a) under this License, provided that you make a good faith effort to 59 | ensure that, in the event an Application does not supply the 60 | function or data, the facility still operates, and performs 61 | whatever part of its purpose remains meaningful, or 62 | 63 | * b) under the GNU GPL, with none of the additional permissions of 64 | this License applicable to that copy. 65 | 66 | ### 3. Object Code Incorporating Material from Library Header Files. 67 | 68 | The object code form of an Application may incorporate material from 69 | a header file that is part of the Library. You may convey such object 70 | code under terms of your choice, provided that, if the incorporated 71 | material is not limited to numerical parameters, data structure 72 | layouts and accessors, or small macros, inline functions and templates 73 | (ten or fewer lines in length), you do both of the following: 74 | 75 | * a) Give prominent notice with each copy of the object code that the 76 | Library is used in it and that the Library and its use are 77 | covered by this License. 78 | * b) Accompany the object code with a copy of the GNU GPL and this license 79 | document. 80 | 81 | ### 4. Combined Works. 82 | 83 | You may convey a Combined Work under terms of your choice that, 84 | taken together, effectively do not restrict modification of the 85 | portions of the Library contained in the Combined Work and reverse 86 | engineering for debugging such modifications, if you also do each of 87 | the following: 88 | 89 | * a) Give prominent notice with each copy of the Combined Work that 90 | the Library is used in it and that the Library and its use are 91 | covered by this License. 92 | 93 | * b) Accompany the Combined Work with a copy of the GNU GPL and this license 94 | document. 95 | 96 | * c) For a Combined Work that displays copyright notices during 97 | execution, include the copyright notice for the Library among 98 | these notices, as well as a reference directing the user to the 99 | copies of the GNU GPL and this license document. 100 | 101 | * d) Do one of the following: 102 | - 0) Convey the Minimal Corresponding Source under the terms of this 103 | License, and the Corresponding Application Code in a form 104 | suitable for, and under terms that permit, the user to 105 | recombine or relink the Application with a modified version of 106 | the Linked Version to produce a modified Combined Work, in the 107 | manner specified by section 6 of the GNU GPL for conveying 108 | Corresponding Source. 109 | - 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | * e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | ### 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | * a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | * b) Give prominent notice with the combined library that part of it 140 | is a work based on the Library, and explaining where to find the 141 | accompanying uncombined form of the same work. 142 | 143 | ### 6. Revised Versions of the GNU Lesser General Public License. 144 | 145 | The Free Software Foundation may publish revised and/or new versions 146 | of the GNU Lesser General Public License from time to time. Such new 147 | versions will be similar in spirit to the present version, but may 148 | differ in detail to address new problems or concerns. 149 | 150 | Each version is given a distinguishing version number. If the 151 | Library as you received it specifies that a certain numbered version 152 | of the GNU Lesser General Public License “or any later version” 153 | applies to it, you have the option of following the terms and 154 | conditions either of that published version or of any later version 155 | published by the Free Software Foundation. If the Library as you 156 | received it does not specify a version number of the GNU Lesser 157 | General Public License, you may choose any version of the GNU Lesser 158 | General Public License ever published by the Free Software Foundation. 159 | 160 | If the Library as you received it specifies that a proxy can decide 161 | whether future versions of the GNU Lesser General Public License shall 162 | apply, that proxy's public statement of acceptance of any version is 163 | permanent authorization for you to choose that version for the 164 | Library. 165 | -------------------------------------------------------------------------------- /libuiohook/Makefile.am: -------------------------------------------------------------------------------- 1 | # libUIOHook: Cross-platfrom userland keyboard and mouse hooking. 2 | # Copyright (C) 2006-2017 Alexander Barker. All Rights Received. 3 | # https://github.com/kwhat/libuiohook/ 4 | # 5 | # libUIOHook is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU Lesser General Public License as published 7 | # by the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # libUIOHook is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | ACLOCAL_AMFLAGS = -I m4 20 | 21 | HOOK_SRC = src/logger.c 22 | TEST_SRC = test/input_helper_test.c test/system_properties_test.c test/uiohook_test.c 23 | MAN3_SRC = man/hook_run.man \ 24 | man/hook_stop.man \ 25 | man/hook_get_auto_repeat_delay.man \ 26 | man/hook_get_multi_click_time.man \ 27 | man/hook_get_pointer_acceleration_threshold.man \ 28 | man/hook_set_dispatch_proc.man \ 29 | man/hook_get_auto_repeat_rate.man \ 30 | man/hook_get_pointer_acceleration_multiplier.man \ 31 | man/hook_get_pointer_sensitivity.man \ 32 | man/hook_set_logger_proc.man 33 | 34 | if BUILD_DARWIN 35 | HOOK_SRC += src/darwin/input_helper.c \ 36 | src/darwin/input_hook.c \ 37 | src/darwin/post_event.c \ 38 | src/darwin/system_properties.c 39 | endif 40 | 41 | if BUILD_X11 42 | HOOK_SRC += src/x11/input_helper.c \ 43 | src/x11/input_hook.c \ 44 | src/x11/post_event.c \ 45 | src/x11/system_properties.c 46 | endif 47 | 48 | if BUILD_WINDOWS 49 | HOOK_SRC += src/windows/input_helper.c \ 50 | src/windows/input_hook.c \ 51 | src/windows/post_event.c \ 52 | src/windows/system_properties.c 53 | endif 54 | 55 | pkgconfigdir = $(libdir)/pkgconfig 56 | pkgconfig_DATA = $(top_builddir)/pc/uiohook.pc 57 | 58 | include_HEADERS = include/uiohook.h 59 | 60 | bin_PROGRAMS = 61 | lib_LTLIBRARIES = libuiohook.la 62 | 63 | 64 | libuiohook_la_CFLAGS = $(AM_CFLAGS) -Wall -Wextra -pedantic -Wno-unused-parameter -I$(top_srcdir)/include -I$(top_srcdir)/src/$(backend) -I$(top_srcdir)/src 65 | libuiohook_la_LDFLAGS = $(LTLDFLAGS) $(LDFLAGS) 66 | libuiohook_la_SOURCES = $(HOOK_SRC) 67 | 68 | 69 | if BUILD_DEMO 70 | bin_PROGRAMS += demohook demohookasync demopost demoprops 71 | 72 | demohook_SOURCES = src/demo_hook.c 73 | demohook_LDADD = $(top_builddir)/libuiohook.la 74 | demohook_CFLAGS = $(AM_CFLAGS) -Wall -Wextra -pedantic $(DEMO_CFLAGS) -I$(top_srcdir)/include 75 | demohook_LDFLAGS = $(LTLDFLAGS) $(DEMO_LIBS) 76 | 77 | demohookasync_SOURCES = src/demo_hook_async.c 78 | demohookasync_LDADD = $(top_builddir)/libuiohook.la 79 | demohookasync_CFLAGS = $(AM_CFLAGS) -Wall -Wextra -pedantic $(DEMO_CFLAGS) -I$(top_srcdir)/include 80 | demohookasync_LDFLAGS = $(LTLDFLAGS) $(DEMO_LIBS) 81 | 82 | demopost_SOURCES = src/demo_post.c 83 | demopost_LDADD = $(top_builddir)/libuiohook.la 84 | demopost_CFLAGS = $(AM_CFLAGS) -Wall -Wextra -pedantic $(DEMO_CFLAGS) -I$(top_srcdir)/include 85 | demopost_LDFLAGS = $(LTLDFLAGS) $(DEMO_LIBS) 86 | 87 | demoprops_SOURCES = src/demo_properties.c 88 | demoprops_LDADD = $(top_builddir)/libuiohook.la 89 | demoprops_CFLAGS = $(AM_CFLAGS) -Wall -Wextra -pedantic $(DEMO_CFLAGS) -I$(top_srcdir)/include 90 | demoprops_LDFLAGS = $(LTLDFLAGS) $(DEMO_LIBS) 91 | endif 92 | 93 | 94 | if BUILD_TEST 95 | bin_PROGRAMS += testhook 96 | 97 | testhook_SOURCES = $(TEST_SRC) 98 | testhook_LDADD = $(top_builddir)/libuiohook.la 99 | testhook_CFLAGS = $(AM_CFLAGS) -Wall -Wextra -pedantic $(TEST_CFLAGS) -I$(top_srcdir)/include -I$(top_srcdir)/test -I$(top_srcdir)/src/$(backend) -I$(top_srcdir)/src 100 | testhook_LDFLAGS = $(LTLDFLAGS) $(TEST_LIBS) 101 | endif 102 | 103 | man3_MANS = $(MAN3_SRC) 104 | 105 | EXTRA_DIST = COPYING.md COPYING.LESSER.md README.md 106 | -------------------------------------------------------------------------------- /libuiohook/README.md: -------------------------------------------------------------------------------- 1 | libuiohook 2 | ========== 3 | 4 | A multi-platform C library to provide global keyboard and mouse hooks from userland. 5 | 6 | ## Compiling 7 | Prerequisites: autotools, pkg-config, libtool, gcc, clang or msys2/mingw32 8 | 9 | ./bootstrap.sh 10 | ./configure 11 | make && make install 12 | 13 | ## Usage 14 | * [Hook Demo](https://github.com/kwhat/libuiohook/blob/master/src/demo_hook.c) 15 | * [Async Hook Demo](https://github.com/kwhat/libuiohook/blob/master/src/demo_hook_async.c) 16 | * [Event Post Demo](https://github.com/kwhat/libuiohook/blob/master/src/demo_post.c) 17 | * [Properties Demo](https://github.com/kwhat/libuiohook/blob/master/src/demo_properties.c) 18 | * [Public Interface](https://github.com/kwhat/libuiohook/blob/master/include/uiohook.h) 19 | * Please see the man pages for function documentation. 20 | -------------------------------------------------------------------------------- /libuiohook/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ "$(uname)" = "Darwin" ]; then 4 | include=" -I/opt/local/share/aclocal" 5 | fi 6 | 7 | autoreconf --install --verbose --force $include 8 | -------------------------------------------------------------------------------- /libuiohook/m4/ax_pthread.m4: -------------------------------------------------------------------------------- 1 | # =========================================================================== 2 | # http://www.gnu.org/software/autoconf-archive/ax_pthread.html 3 | # =========================================================================== 4 | # 5 | # SYNOPSIS 6 | # 7 | # AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) 8 | # 9 | # DESCRIPTION 10 | # 11 | # This macro figures out how to build C programs using POSIX threads. It 12 | # sets the PTHREAD_LIBS output variable to the threads library and linker 13 | # flags, and the PTHREAD_CFLAGS output variable to any special C compiler 14 | # flags that are needed. (The user can also force certain compiler 15 | # flags/libs to be tested by setting these environment variables.) 16 | # 17 | # Also sets PTHREAD_CC to any special C compiler that is needed for 18 | # multi-threaded programs (defaults to the value of CC otherwise). (This 19 | # is necessary on AIX to use the special cc_r compiler alias.) 20 | # 21 | # NOTE: You are assumed to not only compile your program with these flags, 22 | # but also link it with them as well. e.g. you should link with 23 | # $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS 24 | # 25 | # If you are only building threads programs, you may wish to use these 26 | # variables in your default LIBS, CFLAGS, and CC: 27 | # 28 | # LIBS="$PTHREAD_LIBS $LIBS" 29 | # CFLAGS="$CFLAGS $PTHREAD_CFLAGS" 30 | # CC="$PTHREAD_CC" 31 | # 32 | # In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant 33 | # has a nonstandard name, defines PTHREAD_CREATE_JOINABLE to that name 34 | # (e.g. PTHREAD_CREATE_UNDETACHED on AIX). 35 | # 36 | # Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the 37 | # PTHREAD_PRIO_INHERIT symbol is defined when compiling with 38 | # PTHREAD_CFLAGS. 39 | # 40 | # ACTION-IF-FOUND is a list of shell commands to run if a threads library 41 | # is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it 42 | # is not found. If ACTION-IF-FOUND is not specified, the default action 43 | # will define HAVE_PTHREAD. 44 | # 45 | # Please let the authors know if this macro fails on any platform, or if 46 | # you have any other suggestions or comments. This macro was based on work 47 | # by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help 48 | # from M. Frigo), as well as ac_pthread and hb_pthread macros posted by 49 | # Alejandro Forero Cuervo to the autoconf macro repository. We are also 50 | # grateful for the helpful feedback of numerous users. 51 | # 52 | # Updated for Autoconf 2.68 by Daniel Richard G. 53 | # 54 | # LICENSE 55 | # 56 | # Copyright (c) 2008 Steven G. Johnson 57 | # Copyright (c) 2011 Daniel Richard G. 58 | # 59 | # This program is free software: you can redistribute it and/or modify it 60 | # under the terms of the GNU General Public License as published by the 61 | # Free Software Foundation, either version 3 of the License, or (at your 62 | # option) any later version. 63 | # 64 | # This program is distributed in the hope that it will be useful, but 65 | # WITHOUT ANY WARRANTY; without even the implied warranty of 66 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 67 | # Public License for more details. 68 | # 69 | # You should have received a copy of the GNU General Public License along 70 | # with this program. If not, see . 71 | # 72 | # As a special exception, the respective Autoconf Macro's copyright owner 73 | # gives unlimited permission to copy, distribute and modify the configure 74 | # scripts that are the output of Autoconf when processing the Macro. You 75 | # need not follow the terms of the GNU General Public License when using 76 | # or distributing such scripts, even though portions of the text of the 77 | # Macro appear in them. The GNU General Public License (GPL) does govern 78 | # all other use of the material that constitutes the Autoconf Macro. 79 | # 80 | # This special exception to the GPL applies to versions of the Autoconf 81 | # Macro released by the Autoconf Archive. When you make and distribute a 82 | # modified version of the Autoconf Macro, you may extend this special 83 | # exception to the GPL to apply to your modified version as well. 84 | 85 | #serial 20 86 | 87 | AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD]) 88 | AC_DEFUN([AX_PTHREAD], [ 89 | AC_REQUIRE([AC_CANONICAL_HOST]) 90 | AC_LANG_PUSH([C]) 91 | ax_pthread_ok=no 92 | 93 | # We used to check for pthread.h first, but this fails if pthread.h 94 | # requires special compiler flags (e.g. on True64 or Sequent). 95 | # It gets checked for in the link test anyway. 96 | 97 | # First of all, check if the user has set any of the PTHREAD_LIBS, 98 | # etcetera environment variables, and if threads linking works using 99 | # them: 100 | if test x"$PTHREAD_LIBS$PTHREAD_CFLAGS" != x; then 101 | save_CFLAGS="$CFLAGS" 102 | CFLAGS="$CFLAGS $PTHREAD_CFLAGS" 103 | save_LIBS="$LIBS" 104 | LIBS="$PTHREAD_LIBS $LIBS" 105 | AC_MSG_CHECKING([for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS]) 106 | AC_TRY_LINK_FUNC(pthread_join, ax_pthread_ok=yes) 107 | AC_MSG_RESULT($ax_pthread_ok) 108 | if test x"$ax_pthread_ok" = xno; then 109 | PTHREAD_LIBS="" 110 | PTHREAD_CFLAGS="" 111 | fi 112 | LIBS="$save_LIBS" 113 | CFLAGS="$save_CFLAGS" 114 | fi 115 | 116 | # We must check for the threads library under a number of different 117 | # names; the ordering is very important because some systems 118 | # (e.g. DEC) have both -lpthread and -lpthreads, where one of the 119 | # libraries is broken (non-POSIX). 120 | 121 | # Create a list of thread flags to try. Items starting with a "-" are 122 | # C compiler flags, and other items are library names, except for "none" 123 | # which indicates that we try without any flags at all, and "pthread-config" 124 | # which is a program returning the flags for the Pth emulation library. 125 | 126 | ax_pthread_flags="pthreads none -Kthread -kthread lthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" 127 | 128 | # The ordering *is* (sometimes) important. Some notes on the 129 | # individual items follow: 130 | 131 | # pthreads: AIX (must check this before -lpthread) 132 | # none: in case threads are in libc; should be tried before -Kthread and 133 | # other compiler flags to prevent continual compiler warnings 134 | # -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) 135 | # -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) 136 | # lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) 137 | # -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads) 138 | # -pthreads: Solaris/gcc 139 | # -mthreads: Mingw32/gcc, Lynx/gcc 140 | # -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it 141 | # doesn't hurt to check since this sometimes defines pthreads too; 142 | # also defines -D_REENTRANT) 143 | # ... -mt is also the pthreads flag for HP/aCC 144 | # pthread: Linux, etcetera 145 | # --thread-safe: KAI C++ 146 | # pthread-config: use pthread-config program (for GNU Pth library) 147 | 148 | case ${host_os} in 149 | solaris*) 150 | 151 | # On Solaris (at least, for some versions), libc contains stubbed 152 | # (non-functional) versions of the pthreads routines, so link-based 153 | # tests will erroneously succeed. (We need to link with -pthreads/-mt/ 154 | # -lpthread.) (The stubs are missing pthread_cleanup_push, or rather 155 | # a function called by this macro, so we could check for that, but 156 | # who knows whether they'll stub that too in a future libc.) So, 157 | # we'll just look for -pthreads and -lpthread first: 158 | 159 | ax_pthread_flags="-pthreads pthread -mt -pthread $ax_pthread_flags" 160 | ;; 161 | 162 | darwin*) 163 | ax_pthread_flags="-pthread $ax_pthread_flags" 164 | ;; 165 | esac 166 | 167 | if test x"$ax_pthread_ok" = xno; then 168 | for flag in $ax_pthread_flags; do 169 | 170 | case $flag in 171 | none) 172 | AC_MSG_CHECKING([whether pthreads work without any flags]) 173 | ;; 174 | 175 | -*) 176 | AC_MSG_CHECKING([whether pthreads work with $flag]) 177 | PTHREAD_CFLAGS="$flag" 178 | ;; 179 | 180 | pthread-config) 181 | AC_CHECK_PROG(ax_pthread_config, pthread-config, yes, no) 182 | if test x"$ax_pthread_config" = xno; then continue; fi 183 | PTHREAD_CFLAGS="`pthread-config --cflags`" 184 | PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" 185 | ;; 186 | 187 | *) 188 | AC_MSG_CHECKING([for the pthreads library -l$flag]) 189 | PTHREAD_LIBS="-l$flag" 190 | ;; 191 | esac 192 | 193 | save_LIBS="$LIBS" 194 | save_CFLAGS="$CFLAGS" 195 | LIBS="$PTHREAD_LIBS $LIBS" 196 | CFLAGS="$CFLAGS $PTHREAD_CFLAGS" 197 | 198 | # Check for various functions. We must include pthread.h, 199 | # since some functions may be macros. (On the Sequent, we 200 | # need a special flag -Kthread to make this header compile.) 201 | # We check for pthread_join because it is in -lpthread on IRIX 202 | # while pthread_create is in libc. We check for pthread_attr_init 203 | # due to DEC craziness with -lpthreads. We check for 204 | # pthread_cleanup_push because it is one of the few pthread 205 | # functions on Solaris that doesn't have a non-functional libc stub. 206 | # We try pthread_create on general principles. 207 | AC_LINK_IFELSE([AC_LANG_PROGRAM([#include 208 | static void routine(void *a) { a = 0; } 209 | static void *start_routine(void *a) { return a; }], 210 | [pthread_t th; pthread_attr_t attr; 211 | pthread_create(&th, 0, start_routine, 0); 212 | pthread_join(th, 0); 213 | pthread_attr_init(&attr); 214 | pthread_cleanup_push(routine, 0); 215 | pthread_cleanup_pop(0) /* ; */])], 216 | [ax_pthread_ok=yes], 217 | []) 218 | 219 | LIBS="$save_LIBS" 220 | CFLAGS="$save_CFLAGS" 221 | 222 | AC_MSG_RESULT($ax_pthread_ok) 223 | if test "x$ax_pthread_ok" = xyes; then 224 | break; 225 | fi 226 | 227 | PTHREAD_LIBS="" 228 | PTHREAD_CFLAGS="" 229 | done 230 | fi 231 | 232 | # Various other checks: 233 | if test "x$ax_pthread_ok" = xyes; then 234 | save_LIBS="$LIBS" 235 | LIBS="$PTHREAD_LIBS $LIBS" 236 | save_CFLAGS="$CFLAGS" 237 | CFLAGS="$CFLAGS $PTHREAD_CFLAGS" 238 | 239 | # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. 240 | AC_MSG_CHECKING([for joinable pthread attribute]) 241 | attr_name=unknown 242 | for attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do 243 | AC_LINK_IFELSE([AC_LANG_PROGRAM([#include ], 244 | [int attr = $attr; return attr /* ; */])], 245 | [attr_name=$attr; break], 246 | []) 247 | done 248 | AC_MSG_RESULT($attr_name) 249 | if test "$attr_name" != PTHREAD_CREATE_JOINABLE; then 250 | AC_DEFINE_UNQUOTED(PTHREAD_CREATE_JOINABLE, $attr_name, 251 | [Define to necessary symbol if this constant 252 | uses a non-standard name on your system.]) 253 | fi 254 | 255 | AC_MSG_CHECKING([if more special flags are required for pthreads]) 256 | flag=no 257 | case ${host_os} in 258 | aix* | freebsd* | darwin*) flag="-D_THREAD_SAFE";; 259 | osf* | hpux*) flag="-D_REENTRANT";; 260 | solaris*) 261 | if test "$GCC" = "yes"; then 262 | flag="-D_REENTRANT" 263 | else 264 | flag="-mt -D_REENTRANT" 265 | fi 266 | ;; 267 | esac 268 | AC_MSG_RESULT(${flag}) 269 | if test "x$flag" != xno; then 270 | PTHREAD_CFLAGS="$flag $PTHREAD_CFLAGS" 271 | fi 272 | 273 | AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT], 274 | ax_cv_PTHREAD_PRIO_INHERIT, [ 275 | AC_LINK_IFELSE([ 276 | AC_LANG_PROGRAM([[#include ]], [[int i = PTHREAD_PRIO_INHERIT;]])], 277 | [ax_cv_PTHREAD_PRIO_INHERIT=yes], 278 | [ax_cv_PTHREAD_PRIO_INHERIT=no]) 279 | ]) 280 | AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes"], 281 | AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], 1, [Have PTHREAD_PRIO_INHERIT.])) 282 | 283 | LIBS="$save_LIBS" 284 | CFLAGS="$save_CFLAGS" 285 | 286 | # More AIX lossage: compile with *_r variant 287 | if test "x$GCC" != xyes; then 288 | case $host_os in 289 | aix*) 290 | AS_CASE(["x/$CC"], 291 | [x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6], 292 | [#handle absolute path differently from PATH based program lookup 293 | AS_CASE(["x$CC"], 294 | [x/*], 295 | [AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"])], 296 | [AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC])])]) 297 | ;; 298 | esac 299 | fi 300 | fi 301 | 302 | test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" 303 | 304 | AC_SUBST(PTHREAD_LIBS) 305 | AC_SUBST(PTHREAD_CFLAGS) 306 | AC_SUBST(PTHREAD_CC) 307 | 308 | # Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: 309 | if test x"$ax_pthread_ok" = xyes; then 310 | ifelse([$1],,AC_DEFINE(HAVE_PTHREAD,1,[Define if you have POSIX threads libraries and header files.]),[$1]) 311 | : 312 | else 313 | ax_pthread_ok=no 314 | $2 315 | fi 316 | AC_LANG_POP 317 | ])dnl AX_PTHREAD 318 | -------------------------------------------------------------------------------- /libuiohook/man/hook_get_auto_repeat_delay.man: -------------------------------------------------------------------------------- 1 | .so man3/hook_get_auto_repeat_rate.3 2 | -------------------------------------------------------------------------------- /libuiohook/man/hook_get_auto_repeat_rate.man: -------------------------------------------------------------------------------- 1 | .\" Copyright 2006-2017 Alexander Barker (alex@1stleg.com) 2 | .\" 3 | .\" %%%LICENSE_START(VERBATIM) 4 | .\" libUIOHook is free software: you can redistribute it and/or modify 5 | .\" it under the terms of the GNU Lesser General Public License as published 6 | .\" by the Free Software Foundation, either version 3 of the License, or 7 | .\" (at your option) any later version. 8 | .\" 9 | .\" libUIOHook is distributed in the hope that it will be useful, 10 | .\" but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | .\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | .\" GNU General Public License for more details. 13 | .\" 14 | .\" You should have received a copy of the GNU Lesser General Public License 15 | .\" along with this program. If not, see . 16 | .\" %%%LICENSE_END 17 | .\" 18 | .TH hook_get_auto_repeat_rate 3 "07 July 2014" "Version 1.0" "libUIOHook Programmer's Manual" 19 | .SH NAME 20 | hook_get_auto_repeat_rate, hook_get_auto_repeat_delay \- Keyboard auto\-repeat rate / delay 21 | .HP 22 | hook_get_pointer_acceleration_multiplier, hook_get_pointer_acceleration_threshold \- Pointer acceleration values 23 | .HP 24 | hook_get_pointer_sensitivity \- Pointer sensitivity 25 | .HP 26 | hook_get_multi_click_time \- Button multi-click timeout 27 | .SH SYNTAX 28 | #include 29 | .HP 30 | UIOHOOK_API long int hook_get_auto_repeat_rate\^(\fIvoid\fP\^); 31 | .HP 32 | UIOHOOK_API long int hook_get_auto_repeat_delay\^(\fIvoid\fP\^); 33 | .HP 34 | UIOHOOK_API long int hook_get_pointer_acceleration_multiplier\^(\fIvoid\fP\^); 35 | .HP 36 | UIOHOOK_API long int hook_get_pointer_acceleration_threshold\^(\fIvoid\fP\^); 37 | .HP 38 | UIOHOOK_API long int hook_get_pointer_sensitivity\^(\fIvoid\fP\^); 39 | .HP 40 | UIOHOOK_API long int hook_get_multi_click_time\^(\fIvoid\fP\^); 41 | .SH ARGUMENTS 42 | .IP \fIvoid\fP 1i 43 | .SH RETURN VALUE 44 | All functions return a value between zero and LONG_MAX inclusive for the 45 | requested property. If a system property could not be determined, due to error 46 | or omission, a value of -1 will be returned. 47 | .SH DESCRIPTION 48 | There is no guarantee that any of the above values will be available at any 49 | point during runtime. The return value should be checked for error after each 50 | call. 51 | -------------------------------------------------------------------------------- /libuiohook/man/hook_get_multi_click_time.man: -------------------------------------------------------------------------------- 1 | .so man3/hook_get_auto_repeat_rate.3 2 | -------------------------------------------------------------------------------- /libuiohook/man/hook_get_pointer_acceleration_multiplier.man: -------------------------------------------------------------------------------- 1 | .so man3/hook_get_auto_repeat_rate.3 2 | -------------------------------------------------------------------------------- /libuiohook/man/hook_get_pointer_acceleration_threshold.man: -------------------------------------------------------------------------------- 1 | .so man3/hook_get_auto_repeat_rate.3 2 | -------------------------------------------------------------------------------- /libuiohook/man/hook_get_pointer_sensitivity.man: -------------------------------------------------------------------------------- 1 | .so man3/hook_get_auto_repeat_rate.3 2 | -------------------------------------------------------------------------------- /libuiohook/man/hook_run.man: -------------------------------------------------------------------------------- 1 | .\" Copyright 2006-2017 Alexander Barker (alex@1stleg.com) 2 | .\" 3 | .\" %%%LICENSE_START(VERBATIM) 4 | .\" libUIOHook is free software: you can redistribute it and/or modify 5 | .\" it under the terms of the GNU Lesser General Public License as published 6 | .\" by the Free Software Foundation, either version 3 of the License, or 7 | .\" (at your option) any later version. 8 | .\" 9 | .\" libUIOHook is distributed in the hope that it will be useful, 10 | .\" but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | .\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | .\" GNU General Public License for more details. 13 | .\" 14 | .\" You should have received a copy of the GNU Lesser General Public License 15 | .\" along with this program. If not, see . 16 | .\" %%%LICENSE_END 17 | .\" 18 | .TH hook_run 3 "12 Dec 2014" "Version 1.0" "libUIOHook Programmer's Manual" 19 | .SH NAME 20 | hook_run, hook_disable \- Insert / Withdraw the native event hook 21 | .SH SYNTAX 22 | #include 23 | .HP 24 | UIOHOOK_API int hook_run\^(\fIvoid\fP\^); 25 | .HP 26 | UIOHOOK_API int hook_stop\^(\fIvoid\fP\^); 27 | .SH ARGUMENTS 28 | .IP \fIvoid\fP 1i 29 | 30 | .SH RETURN VALUE 31 | .IP \fIUIOHOOK_SUCCESS\fP li 32 | Returned on success. 33 | .IP \fIUIOHOOK_FAILURE\fP li 34 | General failure status. 35 | .IP \fIUIOHOOK_ERROR_OUT_OF_MEMORY\fP li 36 | Out of system memory. 37 | 38 | .IP \fIUIOHOOK_ERROR_X_OPEN_DISPLAY\fP li 39 | X11 specific error for XOpenDisplay\^(\^) failures. 40 | .IP \fIUIOHOOK_ERROR_X_RECORD_NOT_FOUND\fP li 41 | X11 specific error if XRecord is unavailable. 42 | .IP \fIUIOHOOK_ERROR_X_RECORD_ALLOC_RANGE\fP li 43 | X11 specific error for XRecordAllocRange\^(\^) failures. 44 | .IP \fIUIOHOOK_ERROR_X_RECORD_CREATE_CONTEXT\fP li 45 | X11 specific error for XRecordCreateContext\^(...\^) failures. 46 | .IP \fIUIOHOOK_ERROR_X_RECORD_ENABLE_CONTEXT\fP li 47 | X11 specific error for XRecordEnableContext\^(...\^) failures. 48 | 49 | .IP \fIUIOHOOK_ERROR_SET_WINDOWS_HOOK_EX\fP li 50 | Windows specific error for SetWindowsHookEx\^(...\^) failures. 51 | 52 | .IP \fIUIOHOOK_ERROR_AXAPI_DISABLED\fP li 53 | Darwin specific error if the Accessibility API for the calling application has not been enabled. 54 | .IP \fIUIOHOOK_ERROR_EVENT_PORT\fP li 55 | Darwin specfici error for CGEventTapCreate\^(...\^) failures. 56 | .IP \fIUIOHOOK_ERROR_CREATE_RUN_LOOP_SOURCE\fP li 57 | Darwin specfici error for CFMachPortCreateRunLoopSource\^(...\^) failures. 58 | .IP \fIUIOHOOK_ERROR_GET_RUNLOOP\fP li 59 | Darwin specfici error for CFRunLoopGetCurrent\^(\^) failures. 60 | .IP \fIUIOHOOK_ERROR_OBSERVER_CREATE\fP li 61 | Darwin specfici error for CFRunLoopObserverCreate\^(...\^) failures. 62 | 63 | .SH DESCRIPTION 64 | hook_run\^(\^) will block until either the hook has completed or an error 65 | occurred during the hook registration process. You may assume successful 66 | completion of hook_run() after receiving an event of EVENT_HOOK_ENABLED. 67 | 68 | The hook_stop\^(\^) function is asynchronous, and will only signal the running 69 | hook to stop. This function will return an error if signaling was not possible. 70 | -------------------------------------------------------------------------------- /libuiohook/man/hook_set_dispatch_proc.man: -------------------------------------------------------------------------------- 1 | .\" Copyright 2006-2017 Alexander Barker (alex@1stleg.com) 2 | .\" 3 | .\" %%%LICENSE_START(VERBATIM) 4 | .\" libUIOHook is free software: you can redistribute it and/or modify 5 | .\" it under the terms of the GNU Lesser General Public License as published 6 | .\" by the Free Software Foundation, either version 3 of the License, or 7 | .\" (at your option) any later version. 8 | .\" 9 | .\" libUIOHook is distributed in the hope that it will be useful, 10 | .\" but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | .\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | .\" GNU General Public License for more details. 13 | .\" 14 | .\" You should have received a copy of the GNU Lesser General Public License 15 | .\" along with this program. If not, see . 16 | .\" %%%LICENSE_END 17 | .\" 18 | .TH hook_set_dispatch_proc 3 "07 July 2014" "Version 1.0" "libUIOHook Programmer's Manual" 19 | .SH NAME 20 | hook_set_dispatch_proc \- Set the event callback function 21 | .SH SYNTAX 22 | #include 23 | .HP 24 | void dispatch_proc\^(\fIuiohook_event * const event\fP\^) { 25 | ... 26 | } 27 | .HP 28 | hook_set_dispatch_proc(&dispatch_proc); 29 | 30 | .SH ARGUMENTS 31 | .IP \fIdispatcher_t\fP 1i 32 | A function pointer to a matching dispatcher_t function. 33 | .SH RETURN VALUE 34 | .IP \fIvoid\fP li 35 | 36 | .SH DESCRIPTION 37 | Passing NULL to void dispatch_proc\^(\^) will remove the currently set callback. 38 | -------------------------------------------------------------------------------- /libuiohook/man/hook_set_logger_proc.man: -------------------------------------------------------------------------------- 1 | .\" Copyright 2006-2017 Alexander Barker (alex@1stleg.com) 2 | .\" 3 | .\" %%%LICENSE_START(VERBATIM) 4 | .\" libUIOHook is free software: you can redistribute it and/or modify 5 | .\" it under the terms of the GNU Lesser General Public License as published 6 | .\" by the Free Software Foundation, either version 3 of the License, or 7 | .\" (at your option) any later version. 8 | .\" 9 | .\" libUIOHook is distributed in the hope that it will be useful, 10 | .\" but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | .\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | .\" GNU General Public License for more details. 13 | .\" 14 | .\" You should have received a copy of the GNU Lesser General Public License 15 | .\" along with this program. If not, see . 16 | .\" %%%LICENSE_END 17 | .\" 18 | .TH hook_set_logger_proc 3 "07 July 2014" "Version 1.0" "libUIOHook Programmer's Manual" 19 | .SH NAME 20 | hook_set_logger_proc \- Set the event callback function 21 | .SH SYNTAX 22 | #include 23 | .HP 24 | void logger_proc\^(\fIunsigned int level, const char *format, ...\fP\^) { 25 | ... 26 | } 27 | .HP 28 | hook_set_logger_proc(&logger_proc); 29 | 30 | .SH ARGUMENTS 31 | .IP \fIlogger_t\fP 1i 32 | A function pointer to a matching logger_t function. 33 | .SH RETURN VALUE 34 | .IP \fIvoid\fP li 35 | 36 | .SH DESCRIPTION 37 | Passing NULL to void dispatch_proc\^(\^) will remove the currently set callback. 38 | -------------------------------------------------------------------------------- /libuiohook/man/hook_stop.man: -------------------------------------------------------------------------------- 1 | .so man3/hook_run.3 2 | -------------------------------------------------------------------------------- /libuiohook/pc/uiohook.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@prefix@ 2 | libdir=${prefix}/lib 3 | includedir=${prefix}/include 4 | 5 | Name: uiohook 6 | Description: uiohook library 7 | Version: @VERSION@ 8 | Libs: @LIBS@ -L${libdir} -luiohook 9 | Cflags: -I${includedir} 10 | Requires: @REQUIRE@ 11 | -------------------------------------------------------------------------------- /libuiohook/src/darwin/input_helper.h: -------------------------------------------------------------------------------- 1 | /* libUIOHook: Cross-platfrom userland keyboard and mouse hooking. 2 | * Copyright (C) 2006-2017 Alexander Barker. All Rights Received. 3 | * https://github.com/kwhat/libuiohook/ 4 | * 5 | * libUIOHook is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published 7 | * by the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * libUIOHook is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | // Reference: http://boredzo.org/blog/wp-content/uploads/2007/05/imtx-virtual-keycodes.png 20 | // Reference: https://svn.blender.org/svnroot/bf-blender/branches/render25/intern/ghost/intern/GHOST_SystemCocoa.mm 21 | // Reference: http://www.mactech.com/macintosh-c/chap02-1.html 22 | 23 | #ifndef _included_input_helper 24 | #define _included_input_helper 25 | 26 | #include 27 | #include // For HIToolbox kVK_ keycodes and TIS funcitons. 28 | #ifdef USE_IOKIT 29 | #include 30 | #endif 31 | #include 32 | 33 | 34 | #ifndef USE_IOKIT 35 | // Some of the system key codes that are needed from IOKit. 36 | #define NX_KEYTYPE_SOUND_UP 0x00 37 | #define NX_KEYTYPE_SOUND_DOWN 0x01 38 | #define NX_KEYTYPE_MUTE 0x07 39 | 40 | /* Display controls... 41 | #define NX_KEYTYPE_BRIGHTNESS_UP 0x02 42 | #define NX_KEYTYPE_BRIGHTNESS_DOWN 0x03 43 | #define NX_KEYTYPE_CONTRAST_UP 0x0B 44 | #define NX_KEYTYPE_CONTRAST_DOWN 0x0C 45 | #define NX_KEYTYPE_ILLUMINATION_UP 0x15 46 | #define NX_KEYTYPE_ILLUMINATION_DOWN 0x16 47 | #define NX_KEYTYPE_ILLUMINATION_TOGGLE 0x17 48 | */ 49 | 50 | #define NX_KEYTYPE_CAPS_LOCK 0x04 51 | //#define NX_KEYTYPE_HELP 0x05 52 | #define NX_POWER_KEY 0x06 53 | 54 | #define NX_KEYTYPE_EJECT 0x0E 55 | #define NX_KEYTYPE_PLAY 0x10 56 | #define NX_KEYTYPE_NEXT 0x12 57 | #define NX_KEYTYPE_PREVIOUS 0x13 58 | 59 | /* There is no official fast-forward or rewind scan code support. 60 | #define NX_KEYTYPE_FAST 0x14 61 | #define NX_KEYTYPE_REWIND 0x15 62 | */ 63 | #endif 64 | 65 | // These virtual key codes do not appear to be defined anywhere by Apple. 66 | #define kVK_NX_Power 0xE0 | NX_POWER_KEY /* 0xE6 */ 67 | #define kVK_NX_Eject 0xE0 | NX_KEYTYPE_EJECT /* 0xEE */ 68 | 69 | #define kVK_MEDIA_Play 0xE0 | NX_KEYTYPE_PLAY /* 0xF0 */ 70 | #define kVK_MEDIA_Next 0xE0 | NX_KEYTYPE_NEXT /* 0xF1 */ 71 | #define kVK_MEDIA_Previous 0xE0 | NX_KEYTYPE_PREVIOUS /* 0xF2 */ 72 | 73 | #define kVK_RightCommand 0x36 74 | #define kVK_ContextMenu 0x6E // AKA kMenuPowerGlyph 75 | #define kVK_Undefined 0xFF 76 | 77 | // These button codes do not appear to be defined anywhere by Apple. 78 | #define kVK_LBUTTON kCGMouseButtonLeft 79 | #define kVK_RBUTTON kCGMouseButtonRight 80 | #define kVK_MBUTTON kCGMouseButtonCenter 81 | #define kVK_XBUTTON1 3 82 | #define kVK_XBUTTON2 4 83 | 84 | // These button masks do not appear to be defined anywhere by Apple. 85 | #define kCGEventFlagMaskButtonLeft 1 << 0 86 | #define kCGEventFlagMaskButtonRight 1 << 1 87 | #define kCGEventFlagMaskButtonCenter 1 << 2 88 | #define kCGEventFlagMaskXButton1 1 << 3 89 | #define kCGEventFlagMaskXButton2 1 << 4 90 | 91 | 92 | /* Check for access to Apples accessibility API. 93 | */ 94 | extern bool is_accessibility_enabled(); 95 | 96 | /* Converts an OSX key code and event mask to the appropriate Unicode character 97 | * representation. 98 | */ 99 | extern UniCharCount keycode_to_unicode(CGEventRef event_ref, UniChar *buffer, UniCharCount size); 100 | 101 | /* Converts an OSX keycode to the appropriate UIOHook scancode constant. 102 | */ 103 | extern uint16_t keycode_to_scancode(UInt64 keycode); 104 | 105 | /* Converts a UIOHook scancode constant to the appropriate OSX keycode. 106 | */ 107 | extern UInt64 scancode_to_keycode(uint16_t keycode); 108 | 109 | 110 | /* Initialize items required for KeyCodeToKeySym() and KeySymToUnicode() 111 | * functionality. This method is called by OnLibraryLoad() and may need to be 112 | * called in combination with UnloadInputHelper() if the native keyboard layout 113 | * is changed. 114 | */ 115 | extern void load_input_helper(); 116 | 117 | /* De-initialize items required for KeyCodeToKeySym() and KeySymToUnicode() 118 | * functionality. This method is called by OnLibraryUnload() and may need to be 119 | * called in combination with LoadInputHelper() if the native keyboard layout 120 | * is changed. 121 | */ 122 | extern void unload_input_helper(); 123 | 124 | #endif 125 | -------------------------------------------------------------------------------- /libuiohook/src/darwin/post_event.c: -------------------------------------------------------------------------------- 1 | /* libUIOHook: Cross-platfrom userland keyboard and mouse hooking. 2 | * Copyright (C) 2006-2017 Alexander Barker. All Rights Received. 3 | * https://github.com/kwhat/libuiohook/ 4 | * 5 | * libUIOHook is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published 7 | * by the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * libUIOHook is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifdef HAVE_CONFIG_H 20 | #include 21 | #endif 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "input_helper.h" 30 | #include "logger.h" 31 | 32 | // TODO Possibly relocate to input helper. 33 | static inline CGEventFlags get_key_event_mask(uiohook_event * const event) { 34 | CGEventFlags native_mask = 0x00; 35 | 36 | if (event->mask & (MASK_SHIFT)) { native_mask |= kCGEventFlagMaskShift; } 37 | if (event->mask & (MASK_CTRL)) { native_mask |= kCGEventFlagMaskControl; } 38 | if (event->mask & (MASK_META)) { native_mask |= kCGEventFlagMaskCommand; } 39 | if (event->mask & (MASK_ALT)) { native_mask |= kCGEventFlagMaskAlternate; } 40 | 41 | if (event->type == EVENT_KEY_PRESSED || event->type == EVENT_KEY_RELEASED || event->type == EVENT_KEY_TYPED) { 42 | switch (event->data.keyboard.keycode) { 43 | case VC_KP_0: 44 | case VC_KP_1: 45 | case VC_KP_2: 46 | case VC_KP_3: 47 | case VC_KP_4: 48 | case VC_KP_5: 49 | case VC_KP_6: 50 | case VC_KP_7: 51 | case VC_KP_8: 52 | case VC_KP_9: 53 | 54 | case VC_NUM_LOCK: 55 | case VC_KP_ENTER: 56 | case VC_KP_MULTIPLY: 57 | case VC_KP_ADD: 58 | case VC_KP_SEPARATOR: 59 | case VC_KP_SUBTRACT: 60 | case VC_KP_DIVIDE: 61 | case VC_KP_COMMA: 62 | native_mask |= kCGEventFlagMaskNumericPad; 63 | break; 64 | } 65 | } 66 | 67 | return native_mask; 68 | } 69 | 70 | static inline void post_key_event(uiohook_event * const event) { 71 | bool is_pressed = event->type == EVENT_KEY_PRESSED; 72 | 73 | CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); 74 | CGEventRef cg_event = CGEventCreateKeyboardEvent(src, 75 | (CGKeyCode) scancode_to_keycode(event->data.keyboard.keycode), 76 | is_pressed); 77 | 78 | CGEventSetFlags(cg_event, get_key_event_mask(event)); 79 | CGEventPost(kCGHIDEventTap, cg_event); // kCGSessionEventTap also works. 80 | CFRelease(cg_event); 81 | CFRelease(src); 82 | } 83 | 84 | static inline void post_mouse_button_event(uiohook_event * const event, bool is_pressed) { 85 | CGMouseButton mouse_button; 86 | CGEventType mouse_type; 87 | if (event->data.mouse.button == MOUSE_BUTTON1) { 88 | if (is_pressed) { 89 | mouse_type = kCGEventLeftMouseDown; 90 | } 91 | else { 92 | mouse_type = kCGEventLeftMouseUp; 93 | } 94 | mouse_button = kCGMouseButtonLeft; 95 | } 96 | else if (event->data.mouse.button == MOUSE_BUTTON2) { 97 | if (is_pressed) { 98 | mouse_type = kCGEventRightMouseDown; 99 | } 100 | else { 101 | mouse_type = kCGEventRightMouseUp; 102 | } 103 | mouse_button = kCGMouseButtonRight; 104 | } 105 | else { 106 | if (is_pressed) { 107 | mouse_type = kCGEventOtherMouseDown; 108 | } 109 | else { 110 | mouse_type = kCGEventOtherMouseUp; 111 | } 112 | mouse_button = event->data.mouse.button - 1; 113 | } 114 | 115 | CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); 116 | CGEventRef cg_event = CGEventCreateMouseEvent(src, 117 | mouse_type, 118 | CGPointMake( 119 | (CGFloat) event->data.mouse.x, 120 | (CGFloat) event->data.mouse.y 121 | ), 122 | mouse_button 123 | ); 124 | CGEventPost(kCGHIDEventTap, cg_event); // kCGSessionEventTap also works. 125 | CFRelease(cg_event); 126 | CFRelease(src); 127 | } 128 | 129 | static inline void post_mouse_wheel_event(uiohook_event * const event) { 130 | // FIXME Should I create a source event with the coords? 131 | // It seems to automagically use the current location of the cursor. 132 | // Two options: Query the mouse, move it to x/y, scroll, then move back 133 | // OR disable x/y for scroll events on Windows & X11. 134 | CGScrollEventUnit scroll_unit; 135 | if (event->data.wheel.type == WHEEL_BLOCK_SCROLL) { 136 | // Scrolling data is line-based. 137 | scroll_unit = kCGScrollEventUnitLine; 138 | } 139 | else { 140 | // Scrolling data is pixel-based. 141 | scroll_unit = kCGScrollEventUnitPixel; 142 | } 143 | 144 | CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); 145 | CGEventRef cg_event = CGEventCreateScrollWheelEvent(src, 146 | kCGScrollEventUnitLine, 147 | // TODO Currently only support 1 wheel axis. 148 | (CGWheelCount) 1, // 1 for Y-only, 2 for Y-X, 3 for Y-X-Z 149 | event->data.wheel.amount * event->data.wheel.rotation); 150 | 151 | CGEventPost(kCGHIDEventTap, cg_event); // kCGSessionEventTap also works. 152 | CFRelease(cg_event); 153 | CFRelease(src); 154 | } 155 | 156 | static inline void post_mouse_motion_event(uiohook_event * const event) { 157 | CGEventSourceRef src = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); 158 | CGEventRef cg_event; 159 | if (event->mask >> 8 == 0x00) { 160 | // No mouse flags. 161 | cg_event = CGEventCreateMouseEvent(src, 162 | kCGEventMouseMoved, 163 | CGPointMake( 164 | (CGFloat) event->data.mouse.x, 165 | (CGFloat) event->data.mouse.y 166 | ), 167 | 0 168 | ); 169 | } 170 | else if (event->mask & MASK_BUTTON1) { 171 | cg_event = CGEventCreateMouseEvent(src, 172 | kCGEventLeftMouseDragged, 173 | CGPointMake( 174 | (CGFloat) event->data.mouse.x, 175 | (CGFloat) event->data.mouse.y 176 | ), 177 | kCGMouseButtonLeft 178 | ); 179 | } 180 | else if (event->mask & MASK_BUTTON2) { 181 | cg_event = CGEventCreateMouseEvent(src, 182 | kCGEventRightMouseDragged, 183 | CGPointMake( 184 | (CGFloat) event->data.mouse.x, 185 | (CGFloat) event->data.mouse.y 186 | ), 187 | kCGMouseButtonRight 188 | ); 189 | } 190 | else { 191 | cg_event = CGEventCreateMouseEvent(src, 192 | kCGEventOtherMouseDragged, 193 | CGPointMake( 194 | (CGFloat) event->data.mouse.x, 195 | (CGFloat) event->data.mouse.y 196 | ), 197 | (event->mask >> 8) - 1 198 | ); 199 | } 200 | 201 | // kCGSessionEventTap also works. 202 | CGEventPost(kCGHIDEventTap, cg_event); 203 | CFRelease(cg_event); 204 | CFRelease(src); 205 | } 206 | 207 | UIOHOOK_API void hook_post_event(uiohook_event * const event) { 208 | switch (event->type) { 209 | case EVENT_KEY_PRESSED: 210 | case EVENT_KEY_RELEASED: 211 | post_key_event(event); 212 | break; 213 | 214 | 215 | case EVENT_MOUSE_PRESSED: 216 | post_mouse_button_event(event, true); 217 | break; 218 | 219 | case EVENT_MOUSE_RELEASED: 220 | post_mouse_button_event(event, false); 221 | break; 222 | 223 | case EVENT_MOUSE_CLICKED: 224 | post_mouse_button_event(event, true); 225 | post_mouse_button_event(event, false); 226 | break; 227 | 228 | case EVENT_MOUSE_WHEEL: 229 | post_mouse_wheel_event(event); 230 | break; 231 | 232 | 233 | case EVENT_MOUSE_MOVED: 234 | case EVENT_MOUSE_DRAGGED: 235 | post_mouse_motion_event(event); 236 | break; 237 | 238 | 239 | case EVENT_KEY_TYPED: 240 | // FIXME Ignoreing EVENT_KEY_TYPED events. 241 | 242 | case EVENT_HOOK_ENABLED: 243 | case EVENT_HOOK_DISABLED: 244 | // Ignore hook enabled / disabled events. 245 | 246 | default: 247 | // Ignore any other garbage. 248 | logger(LOG_LEVEL_WARN, "%s [%u]: Ignoring post event type %#X\n", 249 | __FUNCTION__, __LINE__, event->type); 250 | break; 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /libuiohook/src/demo_hook.c: -------------------------------------------------------------------------------- 1 | /* libUIOHook: Cross-platfrom userland keyboard and mouse hooking. 2 | * Copyright (C) 2006-2017 Alexander Barker. All Rights Received. 3 | * https://github.com/kwhat/libuiohook/ 4 | * 5 | * libUIOHook is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published 7 | * by the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * libUIOHook is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifdef HAVE_CONFIG_H 20 | #include 21 | #endif 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | bool logger_proc(unsigned int level, const char *format, ...) { 31 | bool status = false; 32 | 33 | va_list args; 34 | switch (level) { 35 | #ifdef USE_DEBUG 36 | case LOG_LEVEL_DEBUG: 37 | case LOG_LEVEL_INFO: 38 | va_start(args, format); 39 | status = vfprintf(stdout, format, args) >= 0; 40 | va_end(args); 41 | break; 42 | #endif 43 | 44 | case LOG_LEVEL_WARN: 45 | case LOG_LEVEL_ERROR: 46 | va_start(args, format); 47 | status = vfprintf(stderr, format, args) >= 0; 48 | va_end(args); 49 | break; 50 | } 51 | 52 | return status; 53 | } 54 | 55 | // NOTE: The following callback executes on the same thread that hook_run() is called 56 | // from. This is important because hook_run() attaches to the operating systems 57 | // event dispatcher and may delay event delivery to the target application. 58 | // Furthermore, some operating systems may choose to disable your hook if it 59 | // takes to long to process. If you need to do any extended processing, please 60 | // do so by copying the event to your own queued dispatch thread. 61 | void dispatch_proc(uiohook_event * const event) { 62 | char buffer[256] = { 0 }; 63 | size_t length = snprintf(buffer, sizeof(buffer), 64 | "id=%i,when=%" PRIu64 ",mask=0x%X", 65 | event->type, event->time, event->mask); 66 | 67 | switch (event->type) { 68 | case EVENT_KEY_PRESSED: 69 | // If the escape key is pressed, naturally terminate the program. 70 | if (event->data.keyboard.keycode == VC_ESCAPE) { 71 | int status = hook_stop(); 72 | switch (status) { 73 | // System level errors. 74 | case UIOHOOK_ERROR_OUT_OF_MEMORY: 75 | logger_proc(LOG_LEVEL_ERROR, "Failed to allocate memory. (%#X)", status); 76 | break; 77 | 78 | case UIOHOOK_ERROR_X_RECORD_GET_CONTEXT: 79 | // NOTE This is the only platform specific error that occurs on hook_stop(). 80 | logger_proc(LOG_LEVEL_ERROR, "Failed to get XRecord context. (%#X)", status); 81 | break; 82 | 83 | // Default error. 84 | case UIOHOOK_FAILURE: 85 | default: 86 | logger_proc(LOG_LEVEL_ERROR, "An unknown hook error occurred. (%#X)", status); 87 | break; 88 | } 89 | } 90 | case EVENT_KEY_RELEASED: 91 | snprintf(buffer + length, sizeof(buffer) - length, 92 | ",keycode=%u,rawcode=0x%X", 93 | event->data.keyboard.keycode, event->data.keyboard.rawcode); 94 | break; 95 | 96 | case EVENT_KEY_TYPED: 97 | snprintf(buffer + length, sizeof(buffer) - length, 98 | ",keychar=%lc,rawcode=%u", 99 | (wint_t) event->data.keyboard.keychar, 100 | event->data.keyboard.rawcode); 101 | break; 102 | 103 | case EVENT_MOUSE_PRESSED: 104 | case EVENT_MOUSE_RELEASED: 105 | case EVENT_MOUSE_CLICKED: 106 | case EVENT_MOUSE_MOVED: 107 | case EVENT_MOUSE_DRAGGED: 108 | snprintf(buffer + length, sizeof(buffer) - length, 109 | ",x=%i,y=%i,button=%i,clicks=%i", 110 | event->data.mouse.x, event->data.mouse.y, 111 | event->data.mouse.button, event->data.mouse.clicks); 112 | break; 113 | 114 | case EVENT_MOUSE_WHEEL: 115 | snprintf(buffer + length, sizeof(buffer) - length, 116 | ",type=%i,amount=%i,rotation=%i", 117 | event->data.wheel.type, event->data.wheel.amount, 118 | event->data.wheel.rotation); 119 | break; 120 | 121 | default: 122 | break; 123 | } 124 | 125 | fprintf(stdout, "%s\n", buffer); 126 | } 127 | 128 | int main() { 129 | // Set the logger callback for library output. 130 | hook_set_logger_proc(&logger_proc); 131 | 132 | // Set the event callback for uiohook events. 133 | hook_set_dispatch_proc(&dispatch_proc); 134 | 135 | // Start the hook and block. 136 | // NOTE If EVENT_HOOK_ENABLED was delivered, the status will always succeed. 137 | int status = hook_run(); 138 | switch (status) { 139 | case UIOHOOK_SUCCESS: 140 | // Everything is ok. 141 | break; 142 | 143 | // System level errors. 144 | case UIOHOOK_ERROR_OUT_OF_MEMORY: 145 | logger_proc(LOG_LEVEL_ERROR, "Failed to allocate memory. (%#X)", status); 146 | break; 147 | 148 | 149 | // X11 specific errors. 150 | case UIOHOOK_ERROR_X_OPEN_DISPLAY: 151 | logger_proc(LOG_LEVEL_ERROR, "Failed to open X11 display. (%#X)", status); 152 | break; 153 | 154 | case UIOHOOK_ERROR_X_RECORD_NOT_FOUND: 155 | logger_proc(LOG_LEVEL_ERROR, "Unable to locate XRecord extension. (%#X)", status); 156 | break; 157 | 158 | case UIOHOOK_ERROR_X_RECORD_ALLOC_RANGE: 159 | logger_proc(LOG_LEVEL_ERROR, "Unable to allocate XRecord range. (%#X)", status); 160 | break; 161 | 162 | case UIOHOOK_ERROR_X_RECORD_CREATE_CONTEXT: 163 | logger_proc(LOG_LEVEL_ERROR, "Unable to allocate XRecord context. (%#X)", status); 164 | break; 165 | 166 | case UIOHOOK_ERROR_X_RECORD_ENABLE_CONTEXT: 167 | logger_proc(LOG_LEVEL_ERROR, "Failed to enable XRecord context. (%#X)", status); 168 | break; 169 | 170 | 171 | // Windows specific errors. 172 | case UIOHOOK_ERROR_SET_WINDOWS_HOOK_EX: 173 | logger_proc(LOG_LEVEL_ERROR, "Failed to register low level windows hook. (%#X)", status); 174 | break; 175 | 176 | 177 | // Darwin specific errors. 178 | case UIOHOOK_ERROR_AXAPI_DISABLED: 179 | logger_proc(LOG_LEVEL_ERROR, "Failed to enable access for assistive devices. (%#X)", status); 180 | break; 181 | 182 | case UIOHOOK_ERROR_CREATE_EVENT_PORT: 183 | logger_proc(LOG_LEVEL_ERROR, "Failed to create apple event port. (%#X)", status); 184 | break; 185 | 186 | case UIOHOOK_ERROR_CREATE_RUN_LOOP_SOURCE: 187 | logger_proc(LOG_LEVEL_ERROR, "Failed to create apple run loop source. (%#X)", status); 188 | break; 189 | 190 | case UIOHOOK_ERROR_GET_RUNLOOP: 191 | logger_proc(LOG_LEVEL_ERROR, "Failed to acquire apple run loop. (%#X)", status); 192 | break; 193 | 194 | case UIOHOOK_ERROR_CREATE_OBSERVER: 195 | logger_proc(LOG_LEVEL_ERROR, "Failed to create apple run loop observer. (%#X)", status); 196 | break; 197 | 198 | // Default error. 199 | case UIOHOOK_FAILURE: 200 | default: 201 | logger_proc(LOG_LEVEL_ERROR, "An unknown hook error occurred. (%#X)", status); 202 | break; 203 | } 204 | 205 | return status; 206 | } 207 | -------------------------------------------------------------------------------- /libuiohook/src/demo_post.c: -------------------------------------------------------------------------------- 1 | /* libUIOHook: Cross-platfrom userland keyboard and mouse hooking. 2 | * Copyright (C) 2006-2017 Alexander Barker. All Rights Received. 3 | * https://github.com/kwhat/libuiohook/ 4 | * 5 | * libUIOHook is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published 7 | * by the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * libUIOHook is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifdef HAVE_CONFIG_H 20 | #include 21 | #endif 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #ifdef _WIN32 28 | #include 29 | #endif 30 | 31 | // Virtual event pointers 32 | static uiohook_event *event = NULL; 33 | 34 | // TODO Implement CLI options. 35 | //int main(int argc, char *argv[]) { 36 | int main() { 37 | // Allocate memory for the virtual events only once. 38 | event = (uiohook_event *) malloc(sizeof(uiohook_event)); 39 | 40 | event->type = EVENT_KEY_PRESSED; 41 | event->mask = 0x00; 42 | 43 | event->data.keyboard.keycode = VC_A; 44 | event->data.keyboard.keychar = CHAR_UNDEFINED; 45 | 46 | hook_post_event(event); 47 | 48 | event->type = EVENT_KEY_RELEASED; 49 | 50 | hook_post_event(event); 51 | 52 | free(event); 53 | 54 | return 0; 55 | } 56 | -------------------------------------------------------------------------------- /libuiohook/src/demo_properties.c: -------------------------------------------------------------------------------- 1 | /* libUIOHook: Cross-platfrom userland keyboard and mouse hooking. 2 | * Copyright (C) 2006-2017 Alexander Barker. All Rights Received. 3 | * https://github.com/kwhat/libuiohook/ 4 | * 5 | * libUIOHook is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published 7 | * by the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * libUIOHook is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifdef HAVE_CONFIG_H 20 | #include 21 | #endif 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | bool logger_proc(unsigned int level, const char *format, ...) { 29 | bool status = false; 30 | 31 | va_list args; 32 | switch (level) { 33 | case LOG_LEVEL_WARN: 34 | case LOG_LEVEL_ERROR: 35 | va_start(args, format); 36 | status = vfprintf(stderr, format, args) >= 0; 37 | va_end(args); 38 | break; 39 | } 40 | 41 | return status; 42 | } 43 | 44 | int main() { 45 | // Disable the logger. 46 | hook_set_logger_proc(&logger_proc); 47 | 48 | // Retrieves the keyboard auto repeat rate. 49 | long int repeat_rate = hook_get_auto_repeat_rate(); 50 | if (repeat_rate >= 0) { 51 | fprintf(stdout, "Auto Repeat Rate:\t%ld\n", repeat_rate); 52 | } 53 | else { 54 | fprintf(stderr, "Failed to aquire keyboard auto repeat rate!\n"); 55 | } 56 | 57 | // Retrieves the keyboard auto repeat delay. 58 | long int repeat_delay = hook_get_auto_repeat_delay(); 59 | if (repeat_delay >= 0) { 60 | fprintf(stdout, "Auto Repeat Delay:\t%ld\n", repeat_delay); 61 | } 62 | else { 63 | fprintf(stderr, "Failed to acquire keyboard auto repeat delay!\n"); 64 | } 65 | 66 | // Retrieves the mouse acceleration multiplier. 67 | long int acceleration_multiplier = hook_get_pointer_acceleration_multiplier(); 68 | if (acceleration_multiplier >= 0) { 69 | fprintf(stdout, "Mouse Acceleration Multiplier:\t%ld\n", acceleration_multiplier); 70 | } 71 | else { 72 | fprintf(stderr, "Failed to acquire mouse acceleration multiplier!\n"); 73 | } 74 | 75 | // Retrieves the mouse acceleration threshold. 76 | long int acceleration_threshold = hook_get_pointer_acceleration_threshold(); 77 | if (acceleration_threshold >= 0) { 78 | fprintf(stdout, "Mouse Acceleration Threshold:\t%ld\n", acceleration_threshold); 79 | } 80 | else { 81 | fprintf(stderr, "Failed to acquire mouse acceleration threshold!\n"); 82 | } 83 | 84 | // Retrieves the mouse sensitivity. 85 | long int sensitivity = hook_get_pointer_sensitivity(); 86 | if (sensitivity >= 0) { 87 | fprintf(stdout, "Mouse Sensitivity:\t%ld\n", sensitivity); 88 | } 89 | else { 90 | fprintf(stderr, "Failed to acquire keyboard auto repeat rate!\n"); 91 | } 92 | 93 | // Retrieves the double/triple click interval. 94 | long int click_time = hook_get_multi_click_time(); 95 | if (click_time >= 0) { 96 | fprintf(stdout, "Multi-Click Time:\t%ld\n", click_time); 97 | } 98 | else { 99 | fprintf(stderr, "Failed to acquire keyboard auto repeat rate!\n"); 100 | } 101 | 102 | return EXIT_SUCCESS; 103 | } 104 | -------------------------------------------------------------------------------- /libuiohook/src/logger.c: -------------------------------------------------------------------------------- 1 | /* libUIOHook: Cross-platfrom userland keyboard and mouse hooking. 2 | * Copyright (C) 2006-2017 Alexander Barker. All Rights Received. 3 | * https://github.com/kwhat/libuiohook/ 4 | * 5 | * libUIOHook is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published 7 | * by the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * libUIOHook is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifdef HAVE_CONFIG_H 20 | #include 21 | #endif 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "logger.h" 30 | 31 | static bool default_logger(unsigned int level, const char *format, ...) { 32 | bool status = false; 33 | 34 | #ifndef USE_QUIET 35 | va_list args; 36 | switch (level) { 37 | #ifdef USE_DEBUG 38 | case LOG_LEVEL_DEBUG: 39 | #endif 40 | case LOG_LEVEL_INFO: 41 | va_start(args, format); 42 | status = vfprintf(stdout, format, args) >= 0; 43 | va_end(args); 44 | break; 45 | 46 | case LOG_LEVEL_WARN: 47 | case LOG_LEVEL_ERROR: 48 | va_start(args, format); 49 | status = vfprintf(stderr, format, args) >= 0; 50 | va_end(args); 51 | break; 52 | } 53 | #endif 54 | 55 | return status; 56 | } 57 | 58 | // Current logger function pointer, this should never be null. 59 | // FIXME This should be static and wrapped with a public facing function. 60 | logger_t logger = &default_logger; 61 | 62 | 63 | UIOHOOK_API void hook_set_logger_proc(logger_t logger_proc) { 64 | if (logger_proc == NULL) { 65 | logger = &default_logger; 66 | } 67 | else { 68 | logger = logger_proc; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /libuiohook/src/logger.h: -------------------------------------------------------------------------------- 1 | /* libUIOHook: Cross-platfrom userland keyboard and mouse hooking. 2 | * Copyright (C) 2006-2017 Alexander Barker. All Rights Received. 3 | * https://github.com/kwhat/libuiohook/ 4 | * 5 | * libUIOHook is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published 7 | * by the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * libUIOHook is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef _included_logger 20 | #define _included_logger 21 | 22 | #include 23 | #include 24 | 25 | #ifndef __FUNCTION__ 26 | #define __FUNCTION__ __func__ 27 | #endif 28 | 29 | // logger(level, message) 30 | extern logger_t logger; 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /libuiohook/src/windows/input_helper.h: -------------------------------------------------------------------------------- 1 | /* libUIOHook: Cross-platfrom userland keyboard and mouse hooking. 2 | * Copyright (C) 2006-2017 Alexander Barker. All Rights Received. 3 | * https://github.com/kwhat/libuiohook/ 4 | * 5 | * libUIOHook is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published 7 | * by the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * libUIOHook is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | /*********************************************************************** 20 | * The following code is based on code provided by Marc-André Moreau 21 | * to work around a failure to support dead keys in the ToUnicode() API. 22 | * According to the author some parts were taken directly from 23 | * Microsoft's kbd.h header file that is shipped with the Windows Driver 24 | * Development Kit. 25 | * 26 | * The original code was substantially modified to provide the following: 27 | * 1) More dynamic code structure. 28 | * 2) Support for compilers that do not implement _ptr64 (GCC / LLVM). 29 | * 3) Support for Wow64 at runtime via 32-bit binary. 30 | * 4) Support for contextual language switching. 31 | * 32 | * I have contacted Marc-André Moreau who has granted permission for 33 | * his original source code to be used under the Public Domain. Although 34 | * the libUIOHook library as a whole is currently covered under the LGPLv3, 35 | * please feel free to use and learn from the source code contained in the 36 | * following functions under the terms of the Public Domain. 37 | * 38 | * For further reading and the original code, please visit: 39 | * http://legacy.docdroppers.org/wiki/index.php?title=Writing_Keyloggers 40 | * http://www.techmantras.com/content/writing-keyloggers-full-length-tutorial 41 | * 42 | ***********************************************************************/ 43 | 44 | #ifndef _included_input_helper 45 | #define _included_input_helper 46 | 47 | #include 48 | #include 49 | 50 | #ifndef LPFN_ISWOW64PROCESS 51 | typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL); 52 | #endif 53 | 54 | typedef void* (CALLBACK *KbdLayerDescriptor) (VOID); 55 | 56 | #define CAPLOK 0x01 57 | #define WCH_NONE 0xF000 58 | #define WCH_DEAD 0xF001 59 | 60 | #ifndef WM_MOUSEHWHEEL 61 | #define WM_MOUSEHWHEEL 0x020E 62 | #endif 63 | 64 | typedef struct _VK_TO_WCHARS { 65 | BYTE VirtualKey; 66 | BYTE Attributes; 67 | WCHAR wch[]; 68 | } VK_TO_WCHARS, *PVK_TO_WCHARS; 69 | 70 | typedef struct _LIGATURE { 71 | BYTE VirtualKey; 72 | WORD ModificationNumber; 73 | WCHAR wch[]; 74 | } LIGATURE, *PLIGATURE; 75 | 76 | typedef struct _VK_TO_BIT { 77 | BYTE Vk; 78 | BYTE ModBits; 79 | } VK_TO_BIT, *PVK_TO_BIT; 80 | 81 | typedef struct _MODIFIERS { 82 | PVK_TO_BIT pVkToBit; // __ptr64 83 | WORD wMaxModBits; 84 | BYTE ModNumber[]; 85 | } MODIFIERS, *PMODIFIERS; 86 | 87 | typedef struct _VSC_VK { 88 | BYTE Vsc; 89 | USHORT Vk; 90 | } VSC_VK, *PVSC_VK; 91 | 92 | typedef struct _VK_TO_WCHAR_TABLE { 93 | PVK_TO_WCHARS pVkToWchars; // __ptr64 94 | BYTE nModifications; 95 | BYTE cbSize; 96 | } VK_TO_WCHAR_TABLE, *PVK_TO_WCHAR_TABLE; 97 | 98 | typedef struct _DEADKEY { 99 | DWORD dwBoth; 100 | WCHAR wchComposed; 101 | USHORT uFlags; 102 | } DEADKEY, *PDEADKEY; 103 | 104 | typedef struct _VSC_LPWSTR { 105 | BYTE vsc; 106 | WCHAR *pwsz; // __ptr64 107 | } VSC_LPWSTR, *PVSC_LPWSTR; 108 | 109 | typedef struct tagKbdLayer { 110 | PMODIFIERS pCharModifiers; // __ptr64 111 | PVK_TO_WCHAR_TABLE pVkToWcharTable; // __ptr64 112 | PDEADKEY pDeadKey; // __ptr64 113 | PVSC_LPWSTR pKeyNames; // __ptr64 114 | PVSC_LPWSTR pKeyNamesExt; // __ptr64 115 | WCHAR **pKeyNamesDead; // __ptr64 116 | USHORT *pusVSCtoVK; // __ptr64 117 | BYTE bMaxVSCtoVK; 118 | PVSC_VK pVSCtoVK_E0; // __ptr64 119 | PVSC_VK pVSCtoVK_E1; // __ptr64 120 | DWORD fLocaleFlags; 121 | BYTE nLgMax; 122 | BYTE cbLgEntry; 123 | PLIGATURE pLigature; // __ptr64 124 | DWORD dwType; 125 | DWORD dwSubType; 126 | } KBDTABLES, *PKBDTABLES; // __ptr64 127 | 128 | 129 | extern SIZE_T keycode_to_unicode(DWORD keycode, PWCHAR buffer, SIZE_T size); 130 | 131 | //extern DWORD unicode_to_keycode(wchar_t unicode); 132 | 133 | extern unsigned short keycode_to_scancode(DWORD vk_code, DWORD flags); 134 | 135 | extern DWORD scancode_to_keycode(unsigned short scancode); 136 | 137 | // Initialize the locale list and wow64 pointer size. 138 | extern int load_input_helper(); 139 | 140 | // Cleanup the initialized locales. 141 | extern int unload_input_helper(); 142 | 143 | #endif 144 | -------------------------------------------------------------------------------- /libuiohook/src/windows/post_event.c: -------------------------------------------------------------------------------- 1 | /* libUIOHook: Cross-platfrom userland keyboard and mouse hooking. 2 | * Copyright (C) 2006-2017 Alexander Barker. All Rights Received. 3 | * https://github.com/kwhat/libuiohook/ 4 | * 5 | * libUIOHook is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published 7 | * by the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * libUIOHook is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifdef HAVE_CONFIG_H 20 | #include 21 | #endif 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include "input_helper.h" 28 | #include "logger.h" 29 | 30 | // Some buggy versions of MinGW and MSys do not include these constants in winuser.h. 31 | #ifndef MAPVK_VK_TO_VSC 32 | #define MAPVK_VK_TO_VSC 0 33 | #define MAPVK_VSC_TO_VK 1 34 | #define MAPVK_VK_TO_CHAR 2 35 | #define MAPVK_VSC_TO_VK_EX 3 36 | #endif 37 | // Some buggy versions of MinGW and MSys only define this value for Windows 38 | // versions >= 0x0600 (Windows Vista) when it should be 0x0500 (Windows 2000). 39 | #ifndef MAPVK_VK_TO_VSC_EX 40 | #define MAPVK_VK_TO_VSC_EX 4 41 | #endif 42 | 43 | #ifndef KEYEVENTF_SCANCODE 44 | #define KEYEVENTF_EXTENDEDKEY 0x0001 45 | #define KEYEVENTF_KEYUP 0x0002 46 | #define KEYEVENTF_UNICODE 0x0004 47 | #define KEYEVENTF_SCANCODE 0x0008 48 | #endif 49 | 50 | #ifndef KEYEVENTF_KEYDOWN 51 | #define KEYEVENTF_KEYDOWN 0x0000 52 | #endif 53 | 54 | #define MAX_WINDOWS_COORD_VALUE 65535 55 | 56 | static UINT keymask_lookup[8] = { 57 | VK_LSHIFT, 58 | VK_LCONTROL, 59 | VK_LWIN, 60 | VK_LMENU, 61 | 62 | VK_RSHIFT, 63 | VK_RCONTROL, 64 | VK_RWIN, 65 | VK_RMENU 66 | }; 67 | 68 | UIOHOOK_API void hook_post_event(uiohook_event * const event) { 69 | //FIXME implement multiple monitor support 70 | uint16_t screen_width = GetSystemMetrics( SM_CXSCREEN ); 71 | uint16_t screen_height = GetSystemMetrics( SM_CYSCREEN ); 72 | 73 | unsigned char events_size = 0, events_max = 28; 74 | INPUT *events = malloc(sizeof(INPUT) * events_max); 75 | 76 | if (event->mask & (MASK_SHIFT | MASK_CTRL | MASK_META | MASK_ALT)) { 77 | for (unsigned int i = 0; i < sizeof(keymask_lookup) / sizeof(UINT); i++) { 78 | if (event->mask & 1 << i) { 79 | events[events_size].type = INPUT_KEYBOARD; 80 | events[events_size].ki.wVk = keymask_lookup[i]; 81 | events[events_size].ki.dwFlags = KEYEVENTF_KEYDOWN; 82 | events[events_size].ki.time = 0; // Use current system time. 83 | events_size++; 84 | } 85 | } 86 | } 87 | 88 | if (event->mask & (MASK_BUTTON1 | MASK_BUTTON2 | MASK_BUTTON3 | MASK_BUTTON4 | MASK_BUTTON5)) { 89 | events[events_size].type = INPUT_MOUSE; 90 | events[events_size].mi.dx = 0; // Relative mouse movement due to 91 | events[events_size].mi.dy = 0; // MOUSEEVENTF_ABSOLUTE not being set. 92 | events[events_size].mi.mouseData = 0x00; 93 | events[events_size].mi.time = 0; // Use current system time. 94 | 95 | if (event->mask & MASK_BUTTON1) { 96 | events[events_size].mi.mouseData |= MOUSEEVENTF_LEFTDOWN; 97 | } 98 | 99 | if (event->mask & MASK_BUTTON2) { 100 | events[events_size].mi.mouseData |= MOUSEEVENTF_RIGHTDOWN; 101 | } 102 | 103 | if (event->mask & MASK_BUTTON3) { 104 | events[events_size].mi.mouseData |= MOUSEEVENTF_MIDDLEDOWN; 105 | } 106 | 107 | if (event->mask & MASK_BUTTON4) { 108 | events[events_size].mi.mouseData = XBUTTON1; 109 | events[events_size].mi.mouseData |= MOUSEEVENTF_XDOWN; 110 | } 111 | 112 | if (event->mask & MASK_BUTTON5) { 113 | events[events_size].mi.mouseData = XBUTTON2; 114 | events[events_size].mi.dwFlags |= MOUSEEVENTF_XDOWN; 115 | } 116 | 117 | events_size++; 118 | } 119 | 120 | 121 | switch (event->type) { 122 | case EVENT_KEY_PRESSED: 123 | events[events_size].ki.wVk = scancode_to_keycode(event->data.keyboard.keycode); 124 | if (events[events_size].ki.wVk != 0x0000) { 125 | events[events_size].type = INPUT_KEYBOARD; 126 | events[events_size].ki.dwFlags = KEYEVENTF_KEYDOWN; // |= KEYEVENTF_SCANCODE; 127 | events[events_size].ki.wScan = 0; // event->data.keyboard.keycode; 128 | events[events_size].ki.time = 0; // GetSystemTime() 129 | events_size++; 130 | } 131 | else { 132 | logger(LOG_LEVEL_INFO, "%s [%u]: Unable to lookup scancode: %li\n", 133 | __FUNCTION__, __LINE__, 134 | event->data.keyboard.keycode); 135 | } 136 | break; 137 | 138 | case EVENT_KEY_RELEASED: 139 | events[events_size].ki.wVk = scancode_to_keycode(event->data.keyboard.keycode); 140 | if (events[events_size].ki.wVk != 0x0000) { 141 | events[events_size].type = INPUT_KEYBOARD; 142 | events[events_size].ki.dwFlags = KEYEVENTF_KEYUP; // |= KEYEVENTF_SCANCODE; 143 | events[events_size].ki.wVk = scancode_to_keycode(event->data.keyboard.keycode); 144 | events[events_size].ki.wScan = 0; // event->data.keyboard.keycode; 145 | events[events_size].ki.time = 0; // GetSystemTime() 146 | events_size++; 147 | } 148 | else { 149 | logger(LOG_LEVEL_INFO, "%s [%u]: Unable to lookup scancode: %li\n", 150 | __FUNCTION__, __LINE__, 151 | event->data.keyboard.keycode); 152 | } 153 | break; 154 | 155 | 156 | case EVENT_MOUSE_PRESSED: 157 | events[events_size].type = INPUT_MOUSE; 158 | events[events_size].mi.dwFlags = MOUSEEVENTF_XDOWN; 159 | 160 | switch (event->data.mouse.button) { 161 | case MOUSE_BUTTON1: 162 | events[events_size].mi.dwFlags = MOUSEEVENTF_LEFTDOWN; 163 | break; 164 | 165 | case MOUSE_BUTTON2: 166 | events[events_size].mi.dwFlags = MOUSEEVENTF_RIGHTDOWN; 167 | break; 168 | 169 | case MOUSE_BUTTON3: 170 | events[events_size].mi.dwFlags = MOUSEEVENTF_MIDDLEDOWN; 171 | break; 172 | 173 | case MOUSE_BUTTON4: 174 | events[events_size].mi.mouseData = XBUTTON1; 175 | break; 176 | 177 | case MOUSE_BUTTON5: 178 | events[events_size].mi.mouseData = XBUTTON2; 179 | break; 180 | 181 | default: 182 | // Extra buttons. 183 | if (event->data.mouse.button > 3) { 184 | events[events_size].mi.mouseData = event->data.mouse.button - 3; 185 | } 186 | } 187 | 188 | events[events_size].mi.dx = event->data.mouse.x * (MAX_WINDOWS_COORD_VALUE / screen_width) + 1; 189 | events[events_size].mi.dy = event->data.mouse.y * (MAX_WINDOWS_COORD_VALUE / screen_height) + 1; 190 | 191 | events[events_size].mi.dwFlags |= MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE; 192 | events[events_size].mi.time = 0; // GetSystemTime() 193 | 194 | events_size++; 195 | break; 196 | 197 | case EVENT_MOUSE_RELEASED: 198 | events[events_size].type = INPUT_MOUSE; 199 | events[events_size].mi.dwFlags = MOUSEEVENTF_XUP; 200 | 201 | switch (event->data.mouse.button) { 202 | case MOUSE_BUTTON1: 203 | events[events_size].mi.dwFlags = MOUSEEVENTF_LEFTUP; 204 | break; 205 | 206 | case MOUSE_BUTTON2: 207 | events[events_size].mi.dwFlags = MOUSEEVENTF_RIGHTUP; 208 | break; 209 | 210 | case MOUSE_BUTTON3: 211 | events[events_size].mi.dwFlags = MOUSEEVENTF_MIDDLEUP; 212 | break; 213 | 214 | case MOUSE_BUTTON4: 215 | events[events_size].mi.mouseData = XBUTTON1; 216 | break; 217 | 218 | case MOUSE_BUTTON5: 219 | events[events_size].mi.mouseData = XBUTTON2; 220 | break; 221 | 222 | default: 223 | // Extra buttons. 224 | if (event->data.mouse.button > 3) { 225 | events[events_size].mi.mouseData = event->data.mouse.button - 3; 226 | } 227 | } 228 | 229 | events[events_size].mi.dx = event->data.mouse.x * (MAX_WINDOWS_COORD_VALUE / screen_width) + 1; 230 | events[events_size].mi.dy = event->data.mouse.y * (MAX_WINDOWS_COORD_VALUE / screen_height) + 1; 231 | 232 | events[events_size].mi.dwFlags |= MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE; 233 | events[events_size].mi.time = 0; // GetSystemTime() 234 | events_size++; 235 | break; 236 | 237 | 238 | case EVENT_MOUSE_WHEEL: 239 | events[events_size].type = INPUT_MOUSE; 240 | events[events_size].mi.dwFlags = MOUSEEVENTF_WHEEL; 241 | 242 | // type, amount and rotation? 243 | events[events_size].mi.mouseData = event->data.wheel.amount * event->data.wheel.rotation * WHEEL_DELTA; 244 | 245 | events[events_size].mi.dx = event->data.wheel.x * (MAX_WINDOWS_COORD_VALUE / screen_width) + 1; 246 | events[events_size].mi.dy = event->data.wheel.y * (MAX_WINDOWS_COORD_VALUE / screen_height) + 1; 247 | 248 | events[events_size].mi.dwFlags |= MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE; 249 | events[events_size].mi.time = 0; // GetSystemTime() 250 | events_size++; 251 | break; 252 | 253 | 254 | case EVENT_MOUSE_DRAGGED: 255 | // The button masks are all applied with the modifier masks. 256 | 257 | case EVENT_MOUSE_MOVED: 258 | events[events_size].type = INPUT_MOUSE; 259 | events[events_size].mi.dwFlags = MOUSEEVENTF_MOVE; 260 | 261 | events[events_size].mi.dx = event->data.mouse.x * (MAX_WINDOWS_COORD_VALUE / screen_width) + 1; 262 | events[events_size].mi.dy = event->data.mouse.y * (MAX_WINDOWS_COORD_VALUE / screen_height) + 1; 263 | 264 | events[events_size].mi.dwFlags |= MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE; 265 | events[events_size].mi.time = 0; // GetSystemTime() 266 | events_size++; 267 | break; 268 | 269 | 270 | case EVENT_MOUSE_CLICKED: 271 | case EVENT_KEY_TYPED: 272 | // Ignore clicked and typed events. 273 | 274 | case EVENT_HOOK_ENABLED: 275 | case EVENT_HOOK_DISABLED: 276 | // Ignore hook enabled / disabled events. 277 | 278 | default: 279 | // Ignore any other garbage. 280 | logger(LOG_LEVEL_WARN, "%s [%u]: Ignoring post event type %#X\n", 281 | __FUNCTION__, __LINE__, event->type); 282 | break; 283 | } 284 | 285 | // Release the previously held modifier keys used to fake the event mask. 286 | if (event->mask & (MASK_SHIFT | MASK_CTRL | MASK_META | MASK_ALT)) { 287 | for (unsigned int i = 0; i < sizeof(keymask_lookup) / sizeof(UINT); i++) { 288 | if (event->mask & 1 << i) { 289 | events[events_size].type = INPUT_KEYBOARD; 290 | events[events_size].ki.wVk = keymask_lookup[i]; 291 | events[events_size].ki.dwFlags = KEYEVENTF_KEYUP; 292 | events[events_size].ki.time = 0; // Use current system time. 293 | events_size++; 294 | } 295 | } 296 | } 297 | 298 | if (event->mask & (MASK_BUTTON1 | MASK_BUTTON2 | MASK_BUTTON3 | MASK_BUTTON4 | MASK_BUTTON5)) { 299 | events[events_size].type = INPUT_MOUSE; 300 | events[events_size].mi.dx = 0; // Relative mouse movement due to 301 | events[events_size].mi.dy = 0; // MOUSEEVENTF_ABSOLUTE not being set. 302 | events[events_size].mi.mouseData = 0x00; 303 | events[events_size].mi.time = 0; // Use current system time. 304 | 305 | // If dwFlags does not contain MOUSEEVENTF_WHEEL, MOUSEEVENTF_XDOWN, or MOUSEEVENTF_XUP, 306 | // then mouseData should be zero. 307 | // http://msdn.microsoft.com/en-us/library/windows/desktop/ms646273%28v=vs.85%29.aspx 308 | if (event->mask & MASK_BUTTON1) { 309 | events[events_size].mi.dwFlags |= MOUSEEVENTF_LEFTUP; 310 | } 311 | 312 | if (event->mask & MASK_BUTTON2) { 313 | events[events_size].mi.dwFlags |= MOUSEEVENTF_RIGHTUP; 314 | } 315 | 316 | if (event->mask & MASK_BUTTON3) { 317 | events[events_size].mi.dwFlags |= MOUSEEVENTF_MIDDLEUP; 318 | } 319 | 320 | if (event->mask & MASK_BUTTON4) { 321 | events[events_size].mi.mouseData = XBUTTON1; 322 | events[events_size].mi.dwFlags |= MOUSEEVENTF_XUP; 323 | } 324 | 325 | if (event->mask & MASK_BUTTON5) { 326 | events[events_size].mi.mouseData = XBUTTON2; 327 | events[events_size].mi.dwFlags |= MOUSEEVENTF_XUP; 328 | } 329 | 330 | events_size++; 331 | } 332 | 333 | // Create the key release input 334 | // memcpy(key_events + 1, key_events, sizeof(INPUT)); 335 | // key_events[1].ki.dwFlags |= KEYEVENTF_KEYUP; 336 | 337 | if (! SendInput(events_size, events, sizeof(INPUT)) ) { 338 | logger(LOG_LEVEL_ERROR, "%s [%u]: SendInput() failed! (%#lX)\n", 339 | __FUNCTION__, __LINE__, (unsigned long) GetLastError()); 340 | } 341 | 342 | free(events); 343 | } 344 | -------------------------------------------------------------------------------- /libuiohook/src/windows/system_properties.c: -------------------------------------------------------------------------------- 1 | /* libUIOHook: Cross-platfrom userland keyboard and mouse hooking. 2 | * Copyright (C) 2006-2017 Alexander Barker. All Rights Received. 3 | * https://github.com/kwhat/libuiohook/ 4 | * 5 | * libUIOHook is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published 7 | * by the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * libUIOHook is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifdef HAVE_CONFIG_H 20 | #include 21 | #endif 22 | 23 | #include 24 | #include 25 | 26 | #include "logger.h" 27 | #include "input_helper.h" 28 | 29 | // The handle to the DLL module pulled in DllMain on DLL_PROCESS_ATTACH. 30 | HINSTANCE hInst; 31 | 32 | // input_hook.c 33 | extern void unregister_running_hooks(); 34 | 35 | 36 | // Structure for the monitor_enum_proc() callback so we can track the count. 37 | typedef struct _screen_info { 38 | uint8_t count; 39 | screen_data *data; 40 | } screen_info; 41 | 42 | /* The following function was contributed by Anthony Liguori Jan 14, 2015. 43 | * https://github.com/kwhat/libuiohook/pull/17 44 | * 45 | * callback function called by EnumDisplayMonitors for each enabled monitor 46 | * http://msdn.microsoft.com/en-us/library/windows/desktop/dd162610(v=vs.85).aspx 47 | * http://msdn.microsoft.com/en-us/library/dd162610%28VS.85%29.aspx 48 | * http://msdn.microsoft.com/en-us/library/dd145061%28VS.85%29.aspx 49 | * http://msdn.microsoft.com/en-us/library/dd144901(v=vs.85).aspx 50 | */ 51 | static BOOL CALLBACK monitor_enum_proc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { 52 | int width = lprcMonitor->right - lprcMonitor->left; 53 | int height = lprcMonitor->bottom - lprcMonitor->top; 54 | int origin_x = lprcMonitor->left; 55 | int origin_y = lprcMonitor->top; 56 | 57 | if (width > 0 && height > 0) { 58 | screen_info *screens = (screen_info *) dwData; 59 | 60 | if (screens->data == NULL) { 61 | screens->data = (screen_data *) malloc(sizeof(screen_data)); 62 | } 63 | else { 64 | screens->data = (screen_data *) realloc(screens, sizeof(screen_data) * screens->count); 65 | } 66 | 67 | screens->data[screens->count++] = (screen_data) { 68 | // Should monitor count start @ zero? Currently it starts at 1. 69 | .number = screens->count, 70 | .x = origin_x, 71 | .y = origin_y, 72 | .width = width, 73 | .height = height 74 | }; 75 | 76 | logger(LOG_LEVEL_INFO, "%s [%u]: Monitor %d: %ldx%ld (%ld, %ld)\n", 77 | __FUNCTION__, __LINE__, screens->count, width, height, origin_x, origin_y); 78 | } 79 | 80 | return TRUE; 81 | } 82 | 83 | /* The following function was contributed by Anthony Liguori Jan 14, 2015. 84 | * https://github.com/kwhat/libuiohook/pull/17 85 | */ 86 | UIOHOOK_API screen_data* hook_create_screen_info(unsigned char *count) { 87 | // Initialize count to zero. 88 | *count = 0; 89 | 90 | // Create a simple structure to make working with monitor_enum_proc easier. 91 | screen_info screens = { 92 | .count = 0, 93 | .data = NULL 94 | }; 95 | 96 | BOOL status = EnumDisplayMonitors(NULL, NULL, monitor_enum_proc, (LPARAM) &screens); 97 | 98 | if (!status || screens.count == 0) { 99 | // Fallback in case EnumDisplayMonitors fails. 100 | logger(LOG_LEVEL_INFO, "%s [%u]: EnumDisplayMonitors failed. Fallback.\n", 101 | __FUNCTION__, __LINE__); 102 | 103 | int width = GetSystemMetrics(SM_CXSCREEN); 104 | int height = GetSystemMetrics(SM_CYSCREEN); 105 | 106 | if (width > 0 && height > 0) { 107 | screens.data = (screen_data *) malloc(sizeof(screen_data)); 108 | 109 | if (screens.data != NULL) { 110 | *count = 1; 111 | screens.data[0] = (screen_data) { 112 | .number = 1, 113 | .x = 0, 114 | .y = 0, 115 | .width = width, 116 | .height = height 117 | }; 118 | } 119 | } 120 | } 121 | else { 122 | // Populate the count. 123 | *count = screens.count; 124 | } 125 | 126 | return screens.data; 127 | } 128 | 129 | UIOHOOK_API long int hook_get_auto_repeat_rate() { 130 | long int value = -1; 131 | long int rate; 132 | 133 | if (SystemParametersInfo(SPI_GETKEYBOARDSPEED, 0, &rate, 0)) { 134 | logger(LOG_LEVEL_INFO, "%s [%u]: SPI_GETKEYBOARDSPEED: %li.\n", 135 | __FUNCTION__, __LINE__, rate); 136 | 137 | value = rate; 138 | } 139 | 140 | return value; 141 | } 142 | 143 | UIOHOOK_API long int hook_get_auto_repeat_delay() { 144 | long int value = -1; 145 | long int delay; 146 | 147 | if (SystemParametersInfo(SPI_GETKEYBOARDDELAY, 0, &delay, 0)) { 148 | logger(LOG_LEVEL_INFO, "%s [%u]: SPI_GETKEYBOARDDELAY: %li.\n", 149 | __FUNCTION__, __LINE__, delay); 150 | 151 | value = delay; 152 | } 153 | 154 | return value; 155 | } 156 | 157 | UIOHOOK_API long int hook_get_pointer_acceleration_multiplier() { 158 | long int value = -1; 159 | int mouse[3]; // 0-Threshold X, 1-Threshold Y and 2-Speed. 160 | 161 | if (SystemParametersInfo(SPI_GETMOUSE, 0, &mouse, 0)) { 162 | logger(LOG_LEVEL_INFO, "%s [%u]: SPI_GETMOUSE[2]: %i.\n", 163 | __FUNCTION__, __LINE__, mouse[2]); 164 | 165 | value = mouse[2]; 166 | } 167 | 168 | return value; 169 | } 170 | 171 | UIOHOOK_API long int hook_get_pointer_acceleration_threshold() { 172 | long int value = -1; 173 | int mouse[3]; // 0-Threshold X, 1-Threshold Y and 2-Speed. 174 | 175 | if (SystemParametersInfo(SPI_GETMOUSE, 0, &mouse, 0)) { 176 | logger(LOG_LEVEL_INFO, "%s [%u]: SPI_GETMOUSE[0]: %i.\n", 177 | __FUNCTION__, __LINE__, mouse[0]); 178 | logger(LOG_LEVEL_INFO, "%s [%u]: SPI_GETMOUSE[1]: %i.\n", 179 | __FUNCTION__, __LINE__, mouse[1]); 180 | 181 | // Average the x and y thresholds. 182 | value = (mouse[0] + mouse[1]) / 2; 183 | } 184 | 185 | return value; 186 | } 187 | 188 | UIOHOOK_API long int hook_get_pointer_sensitivity() { 189 | long int value = -1; 190 | int sensitivity; 191 | 192 | if (SystemParametersInfo(SPI_GETMOUSESPEED, 0, &sensitivity, 0)) { 193 | logger(LOG_LEVEL_INFO, "%s [%u]: SPI_GETMOUSESPEED: %i.\n", 194 | __FUNCTION__, __LINE__, sensitivity); 195 | 196 | value = sensitivity; 197 | } 198 | 199 | return value; 200 | } 201 | 202 | UIOHOOK_API long int hook_get_multi_click_time() { 203 | long int value = -1; 204 | UINT clicktime; 205 | 206 | clicktime = GetDoubleClickTime(); 207 | logger(LOG_LEVEL_INFO, "%s [%u]: GetDoubleClickTime: %u.\n", 208 | __FUNCTION__, __LINE__, (unsigned int) clicktime); 209 | 210 | value = (long int) clicktime; 211 | 212 | return value; 213 | } 214 | 215 | // DLL Entry point. 216 | BOOL WINAPI DllMain(HINSTANCE hInstDLL, DWORD fdwReason, LPVOID lpReserved) { 217 | switch (fdwReason) { 218 | case DLL_PROCESS_ATTACH: 219 | // Save the DLL address. 220 | hInst = hInstDLL; 221 | 222 | // Initialize native input helper functions. 223 | load_input_helper(); 224 | break; 225 | 226 | case DLL_PROCESS_DETACH: 227 | // Unregister any hooks that may still be installed. 228 | unregister_running_hooks(); 229 | 230 | // Deinitialize native input helper functions. 231 | unload_input_helper(); 232 | break; 233 | 234 | case DLL_THREAD_ATTACH: 235 | case DLL_THREAD_DETACH: 236 | // Do Nothing. 237 | break; 238 | } 239 | 240 | return TRUE; 241 | } 242 | -------------------------------------------------------------------------------- /libuiohook/src/x11/input_helper.h: -------------------------------------------------------------------------------- 1 | /* libUIOHook: Cross-platfrom userland keyboard and mouse hooking. 2 | * Copyright (C) 2006-2017 Alexander Barker. All Rights Received. 3 | * https://github.com/kwhat/libuiohook/ 4 | * 5 | * libUIOHook is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published 7 | * by the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * libUIOHook is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef _included_input_helper 20 | #define _included_input_helper 21 | 22 | #include 23 | #include 24 | 25 | #ifdef USE_XKBCOMMON 26 | #include 27 | #include 28 | #include 29 | #endif 30 | 31 | 32 | // Virtual button codes that are not defined by X11. 33 | #define Button1 1 34 | #define Button2 2 35 | #define Button3 3 36 | #define WheelUp 4 37 | #define WheelDown 5 38 | #define WheelLeft 6 39 | #define WheelRight 7 40 | #define XButton1 8 41 | #define XButton2 9 42 | 43 | /* Converts an X11 key symbol to a single Unicode character. No direct X11 44 | * functionality exists to provide this information. 45 | */ 46 | extern size_t keysym_to_unicode(KeySym keysym, uint16_t *buffer, size_t size); 47 | 48 | /* Convert a single Unicode character to an X11 key symbol. This function 49 | * provides a better translation than XStringToKeysym() for Unicode characters. 50 | */ 51 | extern KeySym unicode_to_keysym(uint16_t unicode); 52 | 53 | /* Converts an X11 key code to the appropriate keyboard scan code. 54 | */ 55 | extern uint16_t keycode_to_scancode(KeyCode keycode); 56 | 57 | /* Converts a keyboard scan code to the appropriate X11 key code. 58 | */ 59 | extern KeyCode scancode_to_keycode(uint16_t scancode); 60 | 61 | 62 | #ifdef USE_XKBCOMMON 63 | 64 | /* Converts an X11 key code to a Unicode character sequence. libXKBCommon support 65 | * is required for this method. 66 | */ 67 | extern size_t keycode_to_unicode(struct xkb_state* state, KeyCode keycode, uint16_t *buffer, size_t size); 68 | 69 | /* Create a xkb_state structure and return a pointer to it. 70 | */ 71 | extern struct xkb_state * create_xkb_state(struct xkb_context *context, xcb_connection_t *connection); 72 | 73 | /* Release xkb_state structure created by create_xkb_state(). 74 | */ 75 | extern void destroy_xkb_state(struct xkb_state* state); 76 | 77 | #else 78 | 79 | /* Converts an X11 key code and event mask to the appropriate X11 key symbol. 80 | * This functions in much the same way as XKeycodeToKeysym() but allows for a 81 | * faster and more flexible lookup. 82 | */ 83 | extern KeySym keycode_to_keysym(KeyCode keycode, unsigned int modifier_mask); 84 | 85 | #endif 86 | 87 | /* Initialize items required for KeyCodeToKeySym() and KeySymToUnicode() 88 | * functionality. This method is called by OnLibraryLoad() and may need to be 89 | * called in combination with UnloadInputHelper() if the native keyboard layout 90 | * is changed. 91 | */ 92 | extern void load_input_helper(); 93 | 94 | /* De-initialize items required for KeyCodeToKeySym() and KeySymToUnicode() 95 | * functionality. This method is called by OnLibraryUnload() and may need to be 96 | * called in combination with LoadInputHelper() if the native keyboard layout 97 | * is changed. 98 | */ 99 | extern void unload_input_helper(); 100 | 101 | #endif 102 | -------------------------------------------------------------------------------- /libuiohook/src/x11/post_event.c: -------------------------------------------------------------------------------- 1 | /* libUIOHook: Cross-platfrom userland keyboard and mouse hooking. 2 | * Copyright (C) 2006-2017 Alexander Barker. All Rights Received. 3 | * https://github.com/kwhat/libuiohook/ 4 | * 5 | * libUIOHook is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published 7 | * by the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * libUIOHook is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifdef HAVE_CONFIG_H 20 | #include 21 | #endif 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #ifdef USE_XTEST 30 | #include 31 | #endif 32 | 33 | #include "input_helper.h" 34 | #include "logger.h" 35 | 36 | extern Display *properties_disp; 37 | 38 | // This lookup table must be in the same order the masks are defined. 39 | #ifdef USE_XTEST 40 | static KeySym keymask_lookup[8] = { 41 | XK_Shift_L, 42 | XK_Control_L, 43 | XK_Meta_L, 44 | XK_Alt_L, 45 | 46 | XK_Shift_R, 47 | XK_Control_R, 48 | XK_Meta_R, 49 | XK_Alt_R 50 | }; 51 | 52 | static unsigned int btnmask_lookup[5] = { 53 | MASK_BUTTON1, 54 | MASK_BUTTON2, 55 | MASK_BUTTON3, 56 | MASK_BUTTON4, 57 | MASK_BUTTON5 58 | }; 59 | #else 60 | // TODO Possibly relocate to input helper. 61 | static unsigned int convert_to_native_mask(unsigned int mask) { 62 | unsigned int native_mask = 0x00; 63 | 64 | if (mask & (MASK_SHIFT)) { native_mask |= ShiftMask; } 65 | if (mask & (MASK_CTRL)) { native_mask |= ControlMask; } 66 | if (mask & (MASK_META)) { native_mask |= Mod4Mask; } 67 | if (mask & (MASK_ALT)) { native_mask |= Mod1Mask; } 68 | 69 | if (mask & MASK_BUTTON1) { native_mask |= Button1Mask; } 70 | if (mask & MASK_BUTTON2) { native_mask |= Button2Mask; } 71 | if (mask & MASK_BUTTON3) { native_mask |= Button3Mask; } 72 | if (mask & MASK_BUTTON4) { native_mask |= Button4Mask; } 73 | if (mask & MASK_BUTTON5) { native_mask |= Button5Mask; } 74 | 75 | return native_mask; 76 | } 77 | #endif 78 | 79 | static inline void post_key_event(uiohook_event * const event) { 80 | #ifdef USE_XTEST 81 | // FIXME Currently ignoring EVENT_KEY_TYPED. 82 | if (event->type == EVENT_KEY_PRESSED) { 83 | XTestFakeKeyEvent( 84 | properties_disp, 85 | scancode_to_keycode(event->data.keyboard.keycode), 86 | True, 87 | 0); 88 | } 89 | else if (event->type == EVENT_KEY_RELEASED) { 90 | XTestFakeKeyEvent( 91 | properties_disp, 92 | scancode_to_keycode(event->data.keyboard.keycode), 93 | False, 94 | 0); 95 | } 96 | #else 97 | XKeyEvent key_event; 98 | 99 | key_event.serial = 0x00; 100 | key_event.send_event = False; 101 | key_event.display = properties_disp; 102 | key_event.time = CurrentTime; 103 | key_event.same_screen = True; 104 | 105 | unsigned int mask; 106 | 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)) { 107 | key_event.root = DefaultRootWindow(properties_disp); 108 | key_event.window = key_event.root; 109 | key_event.subwindow = None; 110 | 111 | key_event.x_root = 0; 112 | key_event.y_root = 0; 113 | key_event.x = 0; 114 | key_event.y = 0; 115 | } 116 | 117 | key_event.state = convert_to_native_mask(event->mask); 118 | key_event.keycode = XKeysymToKeycode(properties_disp, scancode_to_keycode(event->data.keyboard.keycode)); 119 | 120 | // FIXME Currently ignoring typed events. 121 | if (event->type == EVENT_KEY_PRESSED) { 122 | key_event.type = KeyPress; 123 | XSendEvent(properties_disp, InputFocus, False, KeyPressMask, (XEvent *) &key_event); 124 | } 125 | else if (event->type == EVENT_KEY_RELEASED) { 126 | key_event.type = KeyRelease; 127 | XSendEvent(properties_disp, InputFocus, False, KeyReleaseMask, (XEvent *) &key_event); 128 | } 129 | #endif 130 | } 131 | 132 | static inline void post_mouse_button_event(uiohook_event * const event) { 133 | #ifdef USE_XTEST 134 | Window ret_root; 135 | Window ret_child; 136 | int root_x; 137 | int root_y; 138 | int win_x; 139 | int win_y; 140 | unsigned int mask; 141 | 142 | Window win_root = XDefaultRootWindow(properties_disp); 143 | Bool query_status = XQueryPointer(properties_disp, win_root, &ret_root, &ret_child, &root_x, &root_y, &win_x, &win_y, &mask); 144 | if (query_status) { 145 | if (event->data.mouse.x != root_x || event->data.mouse.y != root_y) { 146 | // Move the pointer to the specified position. 147 | XTestFakeMotionEvent(properties_disp, -1, event->data.mouse.x, event->data.mouse.y, 0); 148 | } 149 | else { 150 | query_status = False; 151 | } 152 | } 153 | 154 | if (event->type == EVENT_MOUSE_WHEEL) { 155 | // Wheel events should be the same as click events on X11. 156 | // type, amount and rotation 157 | if (event->data.wheel.rotation < 0) { 158 | XTestFakeButtonEvent(properties_disp, WheelUp, True, 0); 159 | XTestFakeButtonEvent(properties_disp, WheelUp, False, 0); 160 | } 161 | else { 162 | XTestFakeButtonEvent(properties_disp, WheelDown, True, 0); 163 | XTestFakeButtonEvent(properties_disp, WheelDown, False, 0); 164 | } 165 | } 166 | else if (event->type == EVENT_MOUSE_PRESSED) { 167 | XTestFakeButtonEvent(properties_disp, event->data.mouse.button, True, 0); 168 | } 169 | else if (event->type == EVENT_MOUSE_RELEASED) { 170 | XTestFakeButtonEvent(properties_disp, event->data.mouse.button, False, 0); 171 | } 172 | else if (event->type == EVENT_MOUSE_CLICKED) { 173 | XTestFakeButtonEvent(properties_disp, event->data.mouse.button, True, 0); 174 | XTestFakeButtonEvent(properties_disp, event->data.mouse.button, False, 0); 175 | } 176 | 177 | if (query_status) { 178 | // Move the pointer back to the original position. 179 | XTestFakeMotionEvent(properties_disp, -1, root_x, root_y, 0); 180 | } 181 | #else 182 | XButtonEvent btn_event; 183 | 184 | btn_event.serial = 0x00; 185 | btn_event.send_event = False; 186 | btn_event.display = properties_disp; 187 | btn_event.time = CurrentTime; 188 | btn_event.same_screen = True; 189 | 190 | btn_event.root = DefaultRootWindow(properties_disp); 191 | btn_event.window = btn_event.root; 192 | btn_event.subwindow = None; 193 | 194 | btn_event.type = 0x00; 195 | btn_event.state = 0x00; 196 | btn_event.x_root = 0; 197 | btn_event.y_root = 0; 198 | btn_event.x = 0; 199 | btn_event.y = 0; 200 | btn_event.button = 0x00; 201 | 202 | btn_event.state = convert_to_native_mask(event->mask); 203 | 204 | btn_event.x = event->data.mouse.x; 205 | btn_event.y = event->data.mouse.y; 206 | 207 | #if defined(USE_XINERAMA) || defined(USE_XRANDR) 208 | uint8_t screen_count; 209 | screen_data *screens = hook_create_screen_info(&screen_count); 210 | if (screen_count > 1) { 211 | btn_event.x += screens[0].x; 212 | btn_event.y += screens[0].y; 213 | } 214 | 215 | if (screens != NULL) { 216 | free(screens); 217 | } 218 | #endif 219 | 220 | // These are the same because Window == Root Window. 221 | btn_event.x_root = btn_event.x; 222 | btn_event.y_root = btn_event.y; 223 | 224 | if (event->type == EVENT_MOUSE_WHEEL) { 225 | // type, amount and rotation 226 | if (event->data.wheel.rotation < 0) { 227 | btn_event.button = WheelUp; 228 | } 229 | else { 230 | btn_event.button = WheelDown; 231 | } 232 | } 233 | 234 | if (event->type != EVENT_MOUSE_RELEASED) { 235 | // FIXME Where do we set event->button? 236 | btn_event.type = ButtonPress; 237 | XSendEvent(properties_disp, InputFocus, False, ButtonPressMask, (XEvent *) &btn_event); 238 | } 239 | 240 | if (event->type != EVENT_MOUSE_PRESSED) { 241 | btn_event.type = ButtonRelease; 242 | XSendEvent(properties_disp, InputFocus, False, ButtonReleaseMask, (XEvent *) &btn_event); 243 | } 244 | #endif 245 | } 246 | 247 | static inline void post_mouse_motion_event(uiohook_event * const event) { 248 | #ifdef USE_XTEST 249 | XTestFakeMotionEvent(properties_disp, -1, event->data.mouse.x, event->data.mouse.y, 0); 250 | #else 251 | XMotionEvent mov_event; 252 | 253 | mov_event.serial = MotionNotify; 254 | mov_event.send_event = False; 255 | mov_event.display = properties_disp; 256 | mov_event.time = CurrentTime; 257 | mov_event.same_screen = True; 258 | mov_event.is_hint = NotifyNormal, 259 | mov_event.root = DefaultRootWindow(properties_disp); 260 | mov_event.window = mov_event.root; 261 | mov_event.subwindow = None; 262 | 263 | mov_event.type = 0x00; 264 | mov_event.state = 0x00; 265 | mov_event.x_root = 0; 266 | mov_event.y_root = 0; 267 | mov_event.x = 0; 268 | mov_event.y = 0; 269 | 270 | mov_event.state = convert_to_native_mask(event->mask); 271 | 272 | mov_event.x = event->data.mouse.x; 273 | mov_event.y = event->data.mouse.y; 274 | 275 | #if defined(USE_XINERAMA) || defined(USE_XRANDR) 276 | uint8_t screen_count; 277 | screen_data *screens = hook_create_screen_info(&screen_count); 278 | if (screen_count > 1) { 279 | mov_event.x += screens[0].x; 280 | mov_event.y += screens[0].y; 281 | } 282 | 283 | if (screens != NULL) { 284 | free(screens); 285 | } 286 | #endif 287 | 288 | // These are the same because Window == Root Window. 289 | mov_event.x_root = mov_event.x; 290 | mov_event.y_root = mov_event.y; 291 | 292 | long int event_mask = NoEventMask; 293 | if (event->type == EVENT_MOUSE_DRAGGED) { 294 | #if Button1Mask == Button1MotionMask && \ 295 | Button2Mask == Button2MotionMask && \ 296 | Button3Mask == Button3MotionMask && \ 297 | Button4Mask == Button4MotionMask && \ 298 | Button5Mask == Button5MotionMask 299 | // This little trick only works if Button#MotionMasks align with 300 | // the Button#Masks. 301 | event_mask = mov_event.state & 302 | (Button1MotionMask | Button2MotionMask | 303 | Button2MotionMask | Button3MotionMask | Button5MotionMask); 304 | #else 305 | // Fallback to some slightly larger... 306 | if (event->state & Button1Mask) { 307 | event_mask |= Button1MotionMask; 308 | } 309 | 310 | if (event->state & Button2Mask) { 311 | event_mask |= Button2MotionMask; 312 | } 313 | 314 | if (event->state & Button3Mask) { 315 | event_mask |= Button3MotionMask; 316 | } 317 | 318 | if (event->state & Button4Mask) { 319 | event_mask |= Button4MotionMask; 320 | } 321 | 322 | if (event->state & Button5Mask) { 323 | event_mask |= Button5MotionMask; 324 | } 325 | #endif 326 | } 327 | 328 | // NOTE x_mask = NoEventMask. 329 | XSendEvent(properties_disp, InputFocus, False, event_mask, (XEvent *) &mov_event); 330 | #endif 331 | } 332 | 333 | UIOHOOK_API void hook_post_event(uiohook_event * const event) { 334 | XLockDisplay(properties_disp); 335 | 336 | #ifdef USE_XTEST 337 | // XTest does not have modifier support, so we fake it by depressing the 338 | // appropriate modifier keys. 339 | unsigned int i = 0; 340 | for (i = 0; i < sizeof(keymask_lookup) / sizeof(KeySym); i++) { 341 | if (event->mask & 1 << i) { 342 | XTestFakeKeyEvent(properties_disp, XKeysymToKeycode(properties_disp, keymask_lookup[i]), True, 0); 343 | } 344 | } 345 | 346 | for (i = 0; i < sizeof(btnmask_lookup) / sizeof(unsigned int); i++) { 347 | if (event->mask & btnmask_lookup[i]) { 348 | XTestFakeButtonEvent(properties_disp, i + 1, True, 0); 349 | } 350 | } 351 | #endif 352 | 353 | switch (event->type) { 354 | case EVENT_KEY_PRESSED: 355 | case EVENT_KEY_RELEASED: 356 | case EVENT_KEY_TYPED: 357 | post_key_event(event); 358 | break; 359 | 360 | case EVENT_MOUSE_PRESSED: 361 | case EVENT_MOUSE_RELEASED: 362 | case EVENT_MOUSE_WHEEL: 363 | case EVENT_MOUSE_CLICKED: 364 | post_mouse_button_event(event); 365 | break; 366 | 367 | case EVENT_MOUSE_DRAGGED: 368 | case EVENT_MOUSE_MOVED: 369 | post_mouse_motion_event(event); 370 | break; 371 | 372 | case EVENT_HOOK_ENABLED: 373 | case EVENT_HOOK_DISABLED: 374 | // Ignore hook enabled / disabled events. 375 | 376 | default: 377 | // Ignore any other garbage. 378 | logger(LOG_LEVEL_WARN, "%s [%u]: Ignoring post event type %#X\n", 379 | __FUNCTION__, __LINE__, event->type); 380 | break; 381 | } 382 | 383 | #ifdef USE_XTEST 384 | // Release the previously held modifier keys used to fake the event mask. 385 | for (i = 0; i < sizeof(keymask_lookup) / sizeof(KeySym); i++) { 386 | if (event->mask & 1 << i) { 387 | XTestFakeKeyEvent(properties_disp, XKeysymToKeycode(properties_disp, keymask_lookup[i]), False, 0); 388 | } 389 | } 390 | 391 | for (i = 0; i < sizeof(btnmask_lookup) / sizeof(unsigned int); i++) { 392 | if (event->mask & btnmask_lookup[i]) { 393 | XTestFakeButtonEvent(properties_disp, i + 1, False, 0); 394 | } 395 | } 396 | #endif 397 | 398 | // Don't forget to flush! 399 | XSync(properties_disp, True); 400 | XUnlockDisplay(properties_disp); 401 | } 402 | -------------------------------------------------------------------------------- /libuiohook/test/input_helper_test.c: -------------------------------------------------------------------------------- 1 | /* libUIOHook: Cross-platfrom userland keyboard and mouse hooking. 2 | * Copyright (C) 2006-2017 Alexander Barker. All Rights Received. 3 | * https://github.com/kwhat/libuiohook/ 4 | * 5 | * libUIOHook is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published 7 | * by the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * libUIOHook is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | 22 | #include "input_helper.h" 23 | #include "minunit.h" 24 | #include "uiohook.h" 25 | 26 | /* Make sure all native keycodes map to virtual scancodes */ 27 | static char * test_bidirectional_keycode() { 28 | for (unsigned short i = 0; i < 256; i++) { 29 | printf("Testing keycode\t\t\t%3u\t[0x%04X]\n", i, i); 30 | 31 | #ifdef _WIN32 32 | if ((i > 6 && i < 16) || i > 18) { 33 | #endif 34 | // Lookup the virtual scancode... 35 | uint16_t scancode = keycode_to_scancode(i); 36 | printf("\tproduced scancode\t%3u\t[0x%04X]\n", scancode, scancode); 37 | 38 | // Lookup the native keycode... 39 | uint16_t keycode = (uint16_t) scancode_to_keycode(scancode); 40 | printf("\treproduced keycode\t%3u\t[0x%04X]\n", keycode, keycode); 41 | 42 | // If the returned virtual scancode > 127, we used an offset to 43 | // calculate the keycode index used above. 44 | if (scancode > 127) { 45 | printf("\t\tusing offset\t%3u\t[0x%04X]\n", (scancode & 0x7F) | 0x80, (scancode & 0x7F) | 0x80); 46 | } 47 | 48 | printf("\n"); 49 | 50 | if (scancode != VC_UNDEFINED) { 51 | mu_assert("error, scancode to keycode failed to convert back", i == keycode); 52 | } 53 | #ifdef _WIN32 54 | } 55 | #endif 56 | } 57 | 58 | return NULL; 59 | } 60 | 61 | /* Make sure all virtual scancodes map to native keycodes */ 62 | static char * test_bidirectional_scancode() { 63 | for (unsigned short i = 0; i < 256; i++) { 64 | printf("Testing scancode\t\t%3u\t[0x%04X]\n", i, i); 65 | 66 | // Lookup the native keycode... 67 | uint16_t keycode = (uint16_t) scancode_to_keycode(i); 68 | printf("\treproduced keycode\t%3u\t[0x%04X]\n", keycode, keycode); 69 | 70 | // Lookup the virtual scancode... 71 | uint16_t scancode = keycode_to_scancode(keycode); 72 | printf("\tproduced scancode\t%3u\t[0x%04X]\n", scancode, scancode); 73 | 74 | // If the returned virtual scancode > 127, we used an offset to 75 | // calculate the keycode index used above. 76 | if (scancode > 127) { 77 | // Fix the scancode for upper offsets. 78 | scancode = (scancode & 0x7F) | 0x80; 79 | printf("\t\tusing offset\t%3u\t[0x%04X]\n", scancode, scancode); 80 | } 81 | 82 | printf("\n"); 83 | 84 | #if defined(__APPLE__) && defined(__MACH__) 85 | if (keycode != 255) { 86 | #else 87 | if (keycode != 0x0000) { 88 | #endif 89 | mu_assert("error, scancode to keycode failed to convert back", i == scancode); 90 | } 91 | } 92 | 93 | return NULL; 94 | } 95 | 96 | char * input_helper_tests() { 97 | mu_run_test(test_bidirectional_keycode); 98 | mu_run_test(test_bidirectional_scancode); 99 | 100 | return NULL; 101 | } 102 | -------------------------------------------------------------------------------- /libuiohook/test/minunit.h: -------------------------------------------------------------------------------- 1 | // MinUnit -- A minimal unit testing framework for C 2 | #define mu_assert(message, test) do { if (!(test)) return message; } while (0) 3 | #define mu_run_test(test) do { char *message = test(); tests_run++; if (message) return message; } while (0) 4 | 5 | extern int tests_run; 6 | -------------------------------------------------------------------------------- /libuiohook/test/system_properties_test.c: -------------------------------------------------------------------------------- 1 | /* libUIOHook: Cross-platfrom userland keyboard and mouse hooking. 2 | * Copyright (C) 2006-2017 Alexander Barker. All Rights Received. 3 | * https://github.com/kwhat/libuiohook/ 4 | * 5 | * libUIOHook is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published 7 | * by the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * libUIOHook is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | #include "minunit.h" 24 | 25 | static char * test_auto_repeat_rate() { 26 | long int i = hook_get_auto_repeat_rate(); 27 | 28 | fprintf(stdout, "Auto repeat rate: %li\n", i); 29 | mu_assert("error, could not determine auto repeat rate", i >= 0); 30 | 31 | return NULL; 32 | } 33 | 34 | static char * test_auto_repeat_delay() { 35 | long int i = hook_get_auto_repeat_delay(); 36 | 37 | fprintf(stdout, "Auto repeat delay: %li\n", i); 38 | mu_assert("error, could not determine auto repeat delay", i >= 0); 39 | 40 | return NULL; 41 | } 42 | 43 | static char * test_pointer_acceleration_multiplier() { 44 | long int i = hook_get_pointer_acceleration_multiplier(); 45 | 46 | fprintf(stdout, "Pointer acceleration multiplier: %li\n", i); 47 | mu_assert("error, could not determine pointer acceleration multiplier", i >= 0); 48 | 49 | return NULL; 50 | } 51 | 52 | static char * test_pointer_acceleration_threshold() { 53 | long int i = hook_get_pointer_acceleration_threshold(); 54 | 55 | fprintf(stdout, "Pointer acceleration threshold: %li\n", i); 56 | mu_assert("error, could not determine pointer acceleration threshold", i >= 0); 57 | 58 | return NULL; 59 | } 60 | 61 | static char * test_pointer_sensitivity() { 62 | long int i = hook_get_pointer_sensitivity(); 63 | 64 | fprintf(stdout, "Pointer sensitivity: %li\n", i); 65 | mu_assert("error, could not determine pointer sensitivity", i >= 0); 66 | 67 | return NULL; 68 | } 69 | 70 | static char * test_multi_click_time() { 71 | long int i = hook_get_multi_click_time(); 72 | 73 | fprintf(stdout, "Multi click time: %li\n", i); 74 | mu_assert("error, could not determine multi click time", i >= 0); 75 | 76 | return NULL; 77 | } 78 | 79 | char * system_properties_tests() { 80 | mu_run_test(test_auto_repeat_rate); 81 | mu_run_test(test_auto_repeat_delay); 82 | 83 | mu_run_test(test_pointer_acceleration_multiplier); 84 | mu_run_test(test_pointer_acceleration_threshold); 85 | mu_run_test(test_pointer_sensitivity); 86 | 87 | mu_run_test(test_multi_click_time); 88 | 89 | return NULL; 90 | } 91 | -------------------------------------------------------------------------------- /libuiohook/test/uiohook_test.c: -------------------------------------------------------------------------------- 1 | /* libUIOHook: Cross-platfrom userland keyboard and mouse hooking. 2 | * Copyright (C) 2006-2017 Alexander Barker. All Rights Received. 3 | * https://github.com/kwhat/libuiohook/ 4 | * 5 | * libUIOHook is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published 7 | * by the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * libUIOHook is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU Lesser General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | 21 | #if !defined(__APPLE__) && !defined(__MACH__) && !defined(_WIN32) 22 | #include 23 | #endif 24 | 25 | #include "input_helper.h" 26 | #include "minunit.h" 27 | 28 | extern char * system_properties_tests(); 29 | extern char * input_helper_tests(); 30 | 31 | #if !defined(__APPLE__) && !defined(__MACH__) && !defined(_WIN32) 32 | static Display *disp; 33 | #endif 34 | 35 | int tests_run = 0; 36 | 37 | static char * init_tests() { 38 | #if !defined(__APPLE__) && !defined(__MACH__) && !defined(_WIN32) 39 | // TODO Create our own AC_DEFINE for this value. Currently defaults to X11 platforms. 40 | Display *disp = XOpenDisplay(XDisplayName(NULL)); 41 | mu_assert("error, could not open X display", disp != NULL); 42 | 43 | load_input_helper(disp); 44 | #else 45 | load_input_helper(); 46 | #endif 47 | 48 | return NULL; 49 | } 50 | 51 | static char * cleanup_tests() { 52 | #if !defined(__APPLE__) && !defined(__MACH__) && !defined(_WIN32) 53 | if (disp != NULL) { 54 | XCloseDisplay(disp); 55 | disp = NULL; 56 | } 57 | #else 58 | unload_input_helper(); 59 | #endif 60 | 61 | return NULL; 62 | } 63 | 64 | static char * all_tests() { 65 | mu_run_test(init_tests); 66 | 67 | mu_run_test(system_properties_tests); 68 | mu_run_test(input_helper_tests); 69 | 70 | mu_run_test(cleanup_tests); 71 | 72 | return NULL; 73 | } 74 | 75 | int main() { 76 | int status = 1; 77 | 78 | char *result = all_tests(); 79 | if (result != NULL) { 80 | status = 0; 81 | printf("%s\n", result); 82 | } 83 | else { 84 | printf("ALL TESTS PASSED\n"); 85 | } 86 | printf("Tests run: %d\n", tests_run); 87 | 88 | return status; 89 | } 90 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iohook", 3 | "version": "0.9.3", 4 | "description": "Node.js global keyboard and mouse hook", 5 | "author": "Aloyan Dmitry", 6 | "license": "MIT", 7 | "homepage": "https://github.com/wilix-team/iohook", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/wilix-team/iohook.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/wilix-team/iohook/issues" 14 | }, 15 | "main": "index.js", 16 | "types": "index.d.ts", 17 | "scripts": { 18 | "install": "node install.js", 19 | "build": "node build.js --upload=false", 20 | "build:ci": "node build.js --all", 21 | "build:print": "node -e 'require(\"./helpers\").printManualBuildParams()'", 22 | "test": "jest", 23 | "lint:dry": "eslint --ignore-path .lintignore .", 24 | "lint:fix": "eslint --ignore-path .lintignore --fix . && prettier --ignore-path .lintignore --write .", 25 | "docs:dev": "vuepress dev docs", 26 | "docs:build": "vuepress build docs", 27 | "docs:deploy": "npm run docs:build && gh-pages -d docs/.vuepress/dist/" 28 | }, 29 | "keywords": [ 30 | "hook", 31 | "electron", 32 | "nw.js", 33 | "listener", 34 | "mousemove", 35 | "keypress", 36 | "keyup", 37 | "global keypress", 38 | "shortcut" 39 | ], 40 | "lint-staged": { 41 | "examples/**/*.{js,jsx,ts,tsx}": [ 42 | "eslint --fix", 43 | "prettier --write" 44 | ], 45 | "docs/**/*.{js,jsx,ts,tsx}": [ 46 | "eslint --fix", 47 | "prettier --write" 48 | ], 49 | "test/**/*.{js,jsx,ts,tsx}": [ 50 | "eslint --fix", 51 | "prettier --write" 52 | ], 53 | "*.{js,jsx,ts,tsx}": [ 54 | "eslint --fix", 55 | "prettier --write" 56 | ] 57 | }, 58 | "husky": { 59 | "hooks": { 60 | "pre-commit": "lint-staged" 61 | } 62 | }, 63 | "dependencies": { 64 | "nugget": "^2.0.2", 65 | "pump": "^1.0.3", 66 | "rc": "^1.2.8", 67 | "tar-fs": "^1.16.3" 68 | }, 69 | "devDependencies": { 70 | "@types/node": "^7.0.62", 71 | "eslint": "^7.28.0", 72 | "eslint-config-prettier": "^8.3.0", 73 | "eslint-plugin-only-warn": "^1.0.2", 74 | "eslint-plugin-prettier": "^3.4.0", 75 | "fs-extra": "^9.0.1", 76 | "gh-pages": "^3.2.0", 77 | "htmlhint": "^0.15.1", 78 | "husky": "^6.0.0", 79 | "jest": "^26.6.3", 80 | "lint-staged": "^11.0.0", 81 | "minimist": "^1.2.5", 82 | "nan": "^2.15.0", 83 | "node-abi": "^2.19.3", 84 | "node-gyp": "^7.1.2", 85 | "prebuild": "^10.0.1", 86 | "prettier": "^2.3.1", 87 | "robotjs": "^0.6.0", 88 | "tar": "^6.0.5", 89 | "vuepress": "^1.7.1" 90 | }, 91 | "supportedTargets": [ 92 | [ 93 | "electron", 94 | "4.0.4", 95 | "69" 96 | ], 97 | [ 98 | "electron", 99 | "5.0.0", 100 | "70" 101 | ], 102 | [ 103 | "electron", 104 | "6.0.0", 105 | "73" 106 | ], 107 | [ 108 | "electron", 109 | "7.0.0", 110 | "75" 111 | ], 112 | [ 113 | "electron", 114 | "8.0.0", 115 | "76" 116 | ], 117 | [ 118 | "electron", 119 | "9.0.0", 120 | "80" 121 | ], 122 | [ 123 | "electron", 124 | "10.0.0", 125 | "82" 126 | ], 127 | [ 128 | "electron", 129 | "11.0.0", 130 | "85" 131 | ], 132 | [ 133 | "electron", 134 | "12.0.0", 135 | "87" 136 | ], 137 | [ 138 | "electron", 139 | "13.0.0", 140 | "89" 141 | ], 142 | [ 143 | "electron", 144 | "14.0.0", 145 | "89" 146 | ], 147 | [ 148 | "node", 149 | "10.0.0", 150 | "64" 151 | ], 152 | [ 153 | "node", 154 | "11.4.0", 155 | "67" 156 | ], 157 | [ 158 | "node", 159 | "12.0.0", 160 | "72" 161 | ], 162 | [ 163 | "node", 164 | "13.0.0", 165 | "79" 166 | ], 167 | [ 168 | "node", 169 | "14.0.0", 170 | "83" 171 | ], 172 | [ 173 | "node", 174 | "15.0.0", 175 | "88" 176 | ], 177 | [ 178 | "node", 179 | "16.0.0", 180 | "93" 181 | ] 182 | ] 183 | } 184 | -------------------------------------------------------------------------------- /src/iohook.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include "uiohook.h" 8 | 9 | class HookProcessWorker : public Nan::AsyncProgressWorkerBase 10 | { 11 | public: 12 | 13 | typedef Nan::AsyncProgressWorkerBase::ExecutionProgress HookExecution; 14 | 15 | HookProcessWorker(Nan::Callback * callback); 16 | 17 | void Execute(const ExecutionProgress& progress); 18 | 19 | void HandleProgressCallback(const uiohook_event *event, size_t size); 20 | 21 | void Stop(); 22 | 23 | const HookExecution* fHookExecution; 24 | }; -------------------------------------------------------------------------------- /test/specs/keyboard.spec.js: -------------------------------------------------------------------------------- 1 | const ioHook = require('../../index'); 2 | const robot = require('robotjs'); 3 | 4 | describe('Keyboard events', () => { 5 | afterEach(() => { 6 | ioHook.stop(); 7 | }); 8 | 9 | it('receives the text "hello world" on keyup event', (done) => { 10 | expect.assertions(22); 11 | 12 | const chars = [ 13 | { keycode: 35, value: 'h' }, 14 | { keycode: 18, value: 'e' }, 15 | { keycode: 38, value: 'l' }, 16 | { keycode: 38, value: 'l' }, 17 | { keycode: 24, value: 'o' }, 18 | { keycode: 57, value: ' ' }, 19 | { keycode: 17, value: 'w' }, 20 | { keycode: 24, value: 'o' }, 21 | { keycode: 19, value: 'r' }, 22 | { keycode: 38, value: 'l' }, 23 | { keycode: 32, value: 'd' }, 24 | ]; 25 | let i = 0; 26 | 27 | ioHook.on('keydown', (event) => { 28 | expect(event).toEqual({ 29 | keycode: chars[i].keycode, 30 | type: 'keydown', 31 | shiftKey: false, 32 | altKey: false, 33 | ctrlKey: false, 34 | metaKey: false, 35 | }); 36 | }); 37 | ioHook.on('keyup', (event) => { 38 | expect(event).toEqual({ 39 | keycode: chars[i].keycode, 40 | type: 'keydown', 41 | shiftKey: false, 42 | altKey: false, 43 | ctrlKey: false, 44 | metaKey: false, 45 | }); 46 | 47 | if (i === chars.length - 1) { 48 | done(); 49 | } 50 | 51 | i += 1; 52 | }); 53 | ioHook.start(); 54 | 55 | setTimeout(() => { 56 | // Make sure ioHook starts before anything gets typed 57 | for (const char of chars) { 58 | robot.keyTap(char.value); 59 | } 60 | }, 50); 61 | }); 62 | 63 | it('recognizes shift key being pressed', (done) => { 64 | expect.assertions(8); 65 | 66 | ioHook.on('keydown', (event) => { 67 | expect(event).toEqual({ 68 | type: 'keydown', 69 | shiftKey: true, 70 | altKey: false, 71 | ctrlKey: false, 72 | metaKey: false, 73 | }); 74 | }); 75 | ioHook.on('keyup', (event) => { 76 | expect(event).toEqual({ 77 | type: 'keydown', 78 | shiftKey: true, 79 | altKey: false, 80 | ctrlKey: false, 81 | metaKey: false, 82 | }); 83 | }); 84 | ioHook.start(); 85 | 86 | setTimeout(() => { 87 | // Make sure ioHook starts before anything gets typed 88 | robot.keyToggle('shift', 'down'); 89 | robot.keyTap('1'); 90 | robot.keyToggle('shift', 'up'); 91 | }, 50); 92 | }); 93 | 94 | it('recognizes alt key being pressed', (done) => { 95 | expect.assertions(8); 96 | 97 | ioHook.on('keydown', (event) => { 98 | expect(event).toEqual({ 99 | type: 'keydown', 100 | shiftKey: false, 101 | altKey: true, 102 | ctrlKey: false, 103 | metaKey: false, 104 | }); 105 | }); 106 | ioHook.on('keyup', (event) => { 107 | expect(event).toEqual({ 108 | type: 'keydown', 109 | shiftKey: false, 110 | altKey: true, 111 | ctrlKey: false, 112 | metaKey: false, 113 | }); 114 | }); 115 | ioHook.start(); 116 | 117 | setTimeout(() => { 118 | // Make sure ioHook starts before anything gets typed 119 | robot.keyToggle('alt', 'down'); 120 | robot.keyTap('1'); 121 | robot.keyToggle('alt', 'up'); 122 | }, 50); 123 | }); 124 | 125 | it('recognizes ctrl key being pressed', (done) => { 126 | expect.assertions(8); 127 | 128 | ioHook.on('keydown', (event) => { 129 | expect(event).toEqual({ 130 | type: 'keydown', 131 | shiftKey: false, 132 | altKey: false, 133 | ctrlKey: true, 134 | metaKey: false, 135 | }); 136 | }); 137 | ioHook.on('keyup', (event) => { 138 | expect(event).toEqual({ 139 | type: 'keydown', 140 | shiftKey: false, 141 | altKey: false, 142 | ctrlKey: true, 143 | metaKey: false, 144 | }); 145 | }); 146 | ioHook.start(); 147 | 148 | setTimeout(() => { 149 | // Make sure ioHook starts before anything gets typed 150 | robot.keyToggle('control', 'down'); 151 | robot.keyTap('1'); 152 | robot.keyToggle('control', 'up'); 153 | }, 50); 154 | }); 155 | 156 | it('recognizes meta key being pressed', (done) => { 157 | expect.assertions(8); 158 | 159 | ioHook.on('keydown', (event) => { 160 | expect(event).toEqual({ 161 | type: 'keydown', 162 | shiftKey: false, 163 | altKey: false, 164 | ctrlKey: false, 165 | metaKey: true, 166 | }); 167 | }); 168 | ioHook.on('keyup', (event) => { 169 | expect(event).toEqual({ 170 | type: 'keydown', 171 | shiftKey: false, 172 | altKey: false, 173 | ctrlKey: false, 174 | metaKey: true, 175 | }); 176 | }); 177 | ioHook.start(); 178 | 179 | setTimeout(() => { 180 | // Make sure ioHook starts before anything gets typed 181 | robot.keyToggle('command', 'down'); 182 | robot.keyTap('1'); 183 | robot.keyToggle('command', 'up'); 184 | }, 50); 185 | }); 186 | 187 | it('runs a callback when a shortcut has been released', (done) => { 188 | expect.assertions(2); 189 | 190 | let shortcut = [42, 30]; 191 | 192 | ioHook.registerShortcut( 193 | shortcut, 194 | (keys) => { 195 | expect(shortcut.sort()).toEqual(keys.sort()); 196 | }, 197 | (keys) => { 198 | expect(shortcut.sort()).toEqual(keys.sort()); 199 | } 200 | ); 201 | 202 | setTimeout(() => { 203 | // Make sure ioHook starts before anything gets typed 204 | robot.keyToggle('shift', 'down'); 205 | robot.keyTap('a'); 206 | robot.keyToggle('shift', 'up'); 207 | }, 50); 208 | }); 209 | 210 | it('can unregister a shortcut via its keycodes', (done) => { 211 | expect.assertions(0); 212 | 213 | let shortcut = [42, 30]; 214 | 215 | // Register the shortcut 216 | ioHook.registerShortcut(shortcut, (event) => { 217 | // We're unregistering this shortcut. It should not have been called 218 | expect.not.toHaveBeenCalled(); 219 | }); 220 | 221 | // Unregister the shortcut 222 | ioHook.unregisterShortcutByKeys(shortcut); 223 | 224 | ioHook.start(); 225 | 226 | setTimeout(() => { 227 | // Make sure ioHook starts before anything gets typed 228 | robot.keyToggle('shift', 'down'); 229 | robot.keyTap('a'); 230 | robot.keyToggle('shift', 'up'); 231 | }, 50); 232 | }); 233 | 234 | it('can use rawcode instead of keycode when detecting events', (done) => { 235 | expect.assertions(1); 236 | 237 | let rawCodeShortcut = [65505, 65]; // Shift + A in rawcode 238 | 239 | ioHook.registerShortcut(rawCodeShortcut, (event) => { 240 | expect.toHaveBeenCalled(); 241 | }); 242 | 243 | // Check rawcode detection works 244 | ioHook.useRawcode(true); 245 | ioHook.start(); 246 | 247 | setTimeout(() => { 248 | // Make sure ioHook starts before anything gets typed 249 | robot.keyToggle('shift', 'down'); 250 | robot.keyTap('a'); 251 | robot.keyToggle('shift', 'up'); 252 | }, 50); 253 | }); 254 | }); 255 | --------------------------------------------------------------------------------