├── .appveyor.yml ├── .github └── workflows │ └── cppcmake.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── keyboard.png ├── win_keyboard.cpp └── win_mouse.cpp /.appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.13.0.{build} 2 | pull_requests: 3 | do_not_increment_build_number: true 4 | shallow_clone: true 5 | environment: 6 | lsltag: 1.13.0-b13 7 | lslversion: 1.13.0 8 | LSLDIST_URL: "https://github.com/sccn/liblsl/releases/download" 9 | matrix: 10 | - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 11 | install: 12 | - cmd: appveyor DownloadFile %LSLDIST_URL%/%lsltag%/liblsl-%lslversion%-Win64.7z -FileName liblsl_x64.7z 13 | - cmd: 7z x liblsl_x64.7z -oLSL 14 | build_script: 15 | - cmd: cmake -S . -B build -DLSL_INSTALL_ROOT=LSL/ 16 | - cd build 17 | - cmake --build . --config Release --target install 18 | - cpack -C Release 19 | artifacts: 20 | - path: 'build/*.7z' 21 | -------------------------------------------------------------------------------- /.github/workflows/cppcmake.yml: -------------------------------------------------------------------------------- 1 | name: C/C++ CI 2 | 3 | on: 4 | push: 5 | branches: ['*'] 6 | tags: 7 | - v*.* 8 | pull_request: 9 | branches: 10 | - master 11 | release: 12 | types: ['created'] 13 | 14 | env: 15 | LSL_RELEASE_URL: 'https://github.com/sccn/liblsl/releases/download/v1.15.2' 16 | LSL_RELEASE: '1.15.2' 17 | 18 | defaults: 19 | run: 20 | shell: bash 21 | 22 | # Check qt_ver on # https://download.qt.io/online/qtsdkrepository/ 23 | jobs: 24 | build: 25 | name: ${{ matrix.config.name }} 26 | runs-on: ${{ matrix.config.os }} 27 | strategy: 28 | fail-fast: false 29 | matrix: 30 | config: 31 | - name: "windows-x64" 32 | os: "windows-latest" 33 | cmake_extra: "-T v142,host=x86" 34 | arch: "amd64" 35 | steps: 36 | - uses: actions/checkout@v2 37 | 38 | - name: Download liblsl (Windows) 39 | if: matrix.config.os == 'windows-latest' 40 | run: | 41 | curl -L ${LSL_RELEASE_URL}/liblsl-${LSL_RELEASE}-Win_${{ matrix.config.arch}}.zip -o liblsl.zip 42 | 7z x liblsl.zip -oLSL 43 | 44 | - name: Download liblsl (macOS) 45 | if: startsWith(matrix.config.os, 'macos-') 46 | run: brew install labstreaminglayer/tap/lsl 47 | 48 | - name: Configure CMake 49 | run: | 50 | cmake --version 51 | cmake -S . -B build \ 52 | -DCMAKE_BUILD_TYPE=Release \ 53 | -DCMAKE_INSTALL_PREFIX=${PWD}/install \ 54 | -DCPACK_PACKAGE_DIRECTORY=${PWD}/package \ 55 | -DLSL_INSTALL_ROOT=$PWD/LSL/ \ 56 | -DCPACK_DEBIAN_PACKAGE_SHLIBDEPS=ON \ 57 | -DCPACK_DEBIAN_PACKAGE_DEPENDS=1 \ 58 | ${{ matrix.config.cmake_extra }} 59 | 60 | - name: make 61 | run: cmake --build build --config Release -j --target install 62 | 63 | - name: package 64 | run: | 65 | export LD_LIBRARY_PATH=$Qt5_DIR/lib:$Qt6_DIR/lib:$LD_LIBRARY_PATH 66 | cmake --build build --config Release -j --target package 67 | cmake -E remove_directory package/_CPack_Packages 68 | 69 | - name: Upload Artifacts 70 | uses: actions/upload-artifact@v2 71 | with: 72 | name: pkg-${{ matrix.config.name }} 73 | path: package 74 | 75 | - name: upload to release page 76 | if: github.event_name == 'release' 77 | env: 78 | TOKEN: "token ${{ secrets.GITHUB_TOKEN }}" 79 | UPLOAD_URL: ${{ github.event.release.upload_url }} 80 | run: | 81 | UPLOAD_URL=${UPLOAD_URL%\{*} # remove "{name,label}" suffix 82 | for pkg in package/*.*; do 83 | NAME=$(basename $pkg) 84 | MIME=$(file --mime-type $pkg|cut -d ' ' -f2) 85 | curl -X POST -H "Accept: application/vnd.github.v3+json" -H "Authorization: $TOKEN" -H "Content-Type: $MIME" --data-binary @$pkg $UPLOAD_URL?name=$NAME 86 | done 87 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ui_*.h 2 | /build*/ 3 | /CMakeLists.txt.user 4 | /CMakeLists.json 5 | .vs/ 6 | out/ 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(Input 3 | LANGUAGES CXX C 4 | VERSION 1.15.0) 5 | 6 | set(CMAKE_CXX_STANDARD 17) 7 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 8 | 9 | find_package(LSL REQUIRED 10 | HINTS ${LSL_INSTALL_ROOT} 11 | 12 | "${CMAKE_CURRENT_LIST_DIR}/../../LSL/liblsl/build/install" 13 | "${CMAKE_CURRENT_LIST_DIR}/../../LSL/liblsl/out/install/x64-Release" 14 | "${CMAKE_CURRENT_LIST_DIR}/../../LSL/liblsl/out/install/x64-Debug" 15 | "${CMAKE_CURRENT_LIST_DIR}/../../LSL/liblsl/out/build/x64-Release" 16 | "${CMAKE_CURRENT_LIST_DIR}/../../LSL/liblsl/out/build/x64-Debug" 17 | "${CMAKE_CURRENT_LIST_DIR}/../../LSL/liblsl/build/" 18 | PATH_SUFFIXES share/LSL) 19 | get_filename_component(LSL_PATH ${LSL_CONFIG} DIRECTORY) 20 | 21 | if(WIN32) 22 | add_executable(keyboard win_keyboard.cpp) 23 | target_link_libraries(keyboard PRIVATE LSL::lsl) 24 | add_executable(mouse win_mouse.cpp) 25 | target_link_libraries(mouse PRIVATE LSL::lsl) 26 | 27 | add_custom_command(TARGET keyboard POST_BUILD 28 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 29 | $ 30 | $) 31 | 32 | add_custom_command(TARGET mouse POST_BUILD 33 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 34 | $ 35 | $) 36 | 37 | installLSLApp(keyboard) 38 | installLSLApp(mouse) 39 | endif() 40 | LSLGenerateCPackConfig() 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Christian Kothe 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 | # Keyboard / Mouse connector apps 2 | 3 | These programs grab all input events 4 | (keystrokes and releases, mouse clicks, mouse movements) 5 | done at a computer to allow for 6 | recording input events for example while closed-source stimulus presentation 7 | software is running. 8 | 9 | Note that no passwords or personal information should be entered while this 10 | program is running. 11 | Also keep in mind that this program must run on the stimulus presentation PC. 12 | 13 | # Usage 14 | 15 | 1. Start the Keyboard / Mouse app. You should see a window like the following. 16 | ![keyboard.png](keyboard.png) 17 | 18 | 2. You should now have a stream on your lab network that has 19 | name "Keyboard" and type "Markers" (Keyboard) / 20 | two streams "MouseButtons" with type "Markers" and "MousePosition" (Mouse). 21 | The marker streams contain a single channel of irregular sampling rate with 22 | string-formatted values each time a key/mouse button is pressed or released. 23 | The "MousePosition" stream is an irregularly sampled streams with the X and 24 | Y positions. 25 | 26 | 27 | # Keyboard Event Names 28 | The following list contains the names assigned to the various key codes 29 | (corresponding to Windows virtual key codes in the same order). 30 | 31 | The events are of the form "KEYNAME pressed" or "KEYNAME released": 32 | 33 | ``` 34 | RESERVED00, LBUTTON, RBUTTON, CANCEL, MBUTTON, XBUTTON1, XBUTTON2, BELL, BACK, 35 | TAB, RESERVED0A, RESERVED0B, CLEAR, RETURN, RESERVED0E, RESERVED0F, SHIFT, 36 | CONTROL, MENU, PAUSE, CAPITAL, KANA, RESERVED16, JUNJA, FINAL, KANJI, 37 | UNASSIGNED1A, ESCAPE, CONVERT, NONCONVERT, ACCEPT, MODECHANGE, SPACE, PRIOR, 38 | NEXT, END, HOME, LEFT, UP, RIGHT, DOWN, SELECT, PRINT, EXECUTE, SNAPSHOT, 39 | INSERT, DELETE, HELP, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, UNASSIGNED3A, UNASSIGNED3B, 40 | UNASSIGNED3C, UNASSIGNED3D, UNASSIGNED3E, UNASSIGNED3F, UNASSIGNED40, A, B, C, 41 | D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, 42 | LWIN, RWIN, APPS, RESERVED5E, SLEEP, 43 | NUMPAD0, NUMPAD1, NUMPAD2, NUMPAD3, NUMPAD4, NUMPAD5, NUMPAD6, NUMPAD7, 44 | NUMPAD8, NUMPAD9, MULTIPLY, ADD, SEPARATOR, SUBTRACT, DECIMAL, DIVIDE, 45 | F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17, 46 | F18, F19, F20, F21, F22, F23, F24, 47 | UNASSIGNED88, UNASSIGNED89, UNASSIGNED8A, UNASSIGNED8B, UNASSIGNED8C, 48 | UNASSIGNED8D, UNASSIGNED8E, UNASSIGNED8F, NUMLOCK, SCROLL, OEM_FJ_JISHO, 49 | OEM_FJ_MASSHOU, OEM_FJ_TOUROKU, OEM_FJ_LOYA, OEM_FJ_ROYA, UNASSIGNED97, 50 | UNASSIGNED98, UNASSIGNED99, UNASSIGNED9A, UNASSIGNED9B, UNASSIGNED9C, 51 | UNASSIGNED9D, UNASSIGNED9E, UNASSIGNED9F, LSHIFT, RSHIFT, LCONTROL, RCONTROL, 52 | LMENU, RMENU, BROWSER_BACK, BROWSER_FORWARD, BROWSER_REFRESH, BROWSER_STOP, 53 | BROWSER_SEARCH, BROWSER_FAVORITES, BROWSER_HOME, VOLUME_MUTE, VOLUME_DOWN, 54 | VOLUME_UP, MEDIA_NEXT_TRACK, MEDIA_PREV_TRACK, MEDIA_STOP, MEDIA_PLAY_PAUSE, 55 | LAUNCH_MAIL, LAUNCH_MEDIA_SELECT, LAUNCH_APP1, LAUNCH_APP2, RESERVEDB8, 56 | RESERVEDB9, US_SEMICOLON, PLUS, COMMA, MINUS, PERIOD, US_SLASH, US_TILDE, 57 | RESERVEDC1, RESERVEDC2, RESERVEDC3, RESERVEDC4, RESERVEDC5, RESERVEDC6, 58 | RESERVEDC7, RESERVEDC8, RESERVEDC9, RESERVEDCA, RESERVEDCB, RESERVEDCC, 59 | RESERVEDCD, RESERVEDCE, RESERVEDCF, RESERVEDD0, RESERVEDD1, RESERVEDD2, 60 | RESERVEDD3, RESERVEDD4, RESERVEDD5, RESERVEDD6, RESERVEDD7, UNASSIGNEDD8, 61 | UNASSIGNEDD9, UNASSIGNEDDA, US_SQUARE_BRACKET_OPEN, US_BACKSLASH, 62 | US_SQUARE_BRACKET_CLOSE, US_QUOTE, OEM_8, RESERVEDE0, OEM_AX, OEM_102, 63 | ICO_HELP, ICO_00, PROCESSKEY, ICO_CLEAR, PACKET, RESERVEDE8, OEM_RESET, 64 | OEM_JUMP, OEM_PA1, OEM_PA2, OEM_PA3, OEM_WSCTRL, OEM_CUSEL, OEM_ATTN, 65 | OEM_FINISH, OEM_COPY, OEM_AUTO, OEM_ENLW, OEM_BACKTAB, ATTN, CRSEL, EXSEL, 66 | EREOF, PLAY, ZOOM, NONAME, PA1, OEM_CLEAR, RESERVEDFF 67 | ``` 68 | 69 | # Mouse event names 70 | 71 | The events are of the form "ButtonName pressed" or "ButtonName released": 72 | 73 | - MouseButtonLeft 74 | - MouseButtonRight 75 | - MouseButtonMiddle 76 | - MouseButtonX*n* where *n* is the index of the additional mouse button 77 | - MouseWheelUp*n* where *n* is the scroll distance (default 120) 78 | - MouseWheelLeft 79 | - MouseWheelRight 80 | -------------------------------------------------------------------------------- /keyboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labstreaminglayer/App-Input/9a4af08b889636f267cb6b42dd6b0f791299aa33/keyboard.png -------------------------------------------------------------------------------- /win_keyboard.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #define WIN32_LEAN_AND_MEAN 4 | #include 5 | 6 | // names for the Windows virtual-key codes 7 | const std::string key_names[256] = {"RESERVED00", "LBUTTON", "RBUTTON", "CANCEL", "MBUTTON", 8 | "XBUTTON1", "XBUTTON2", "BELL", "BACK", "TAB", "RESERVED0A", "RESERVED0B", "CLEAR", "RETURN", 9 | "RESERVED0E", "RESERVED0F", "SHIFT", "CONTROL", "MENU", "PAUSE", "CAPITAL", "KANA", 10 | "RESERVED16", "JUNJA", "FINAL", "KANJI", "UNASSIGNED1A", "ESCAPE", "CONVERT", "NONCONVERT", 11 | "ACCEPT", "MODECHANGE", "SPACE", "PRIOR", "NEXT", "END", "HOME", "LEFT", "UP", "RIGHT", "DOWN", 12 | "SELECT", "PRINT", "EXECUTE", "SNAPSHOT", "INSERT", "DELETE", "HELP", "0", "1", "2", "3", "4", 13 | "5", "6", "7", "8", "9", "UNASSIGNED3A", "UNASSIGNED3B", "UNASSIGNED3C", "UNASSIGNED3D", 14 | "UNASSIGNED3E", "UNASSIGNED3F", "UNASSIGNED40", "A", "B", "C", "D", "E", "F", "G", "H", "I", 15 | "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "LWIN", 16 | "RWIN", "APPS", "RESERVED5E", "SLEEP", "NUMPAD0", "NUMPAD1", "NUMPAD2", "NUMPAD3", "NUMPAD4", 17 | "NUMPAD5", "NUMPAD6", "NUMPAD7", "NUMPAD8", "NUMPAD9", "MULTIPLY", "ADD", "SEPARATOR", 18 | "SUBTRACT", "DECIMAL", "DIVIDE", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", 19 | "F11", "F12", "F13", "F14", "F15", "F16", "F17", "F18", "F19", "F20", "F21", "F22", "F23", 20 | "F24", "UNASSIGNED88", "UNASSIGNED89", "UNASSIGNED8A", "UNASSIGNED8B", "UNASSIGNED8C", 21 | "UNASSIGNED8D", "UNASSIGNED8E", "UNASSIGNED8F", "NUMLOCK", "SCROLL", "OEM_FJ_JISHO", 22 | "OEM_FJ_MASSHOU", "OEM_FJ_TOUROKU", "OEM_FJ_LOYA", "OEM_FJ_ROYA", "UNASSIGNED97", 23 | "UNASSIGNED98", "UNASSIGNED99", "UNASSIGNED9A", "UNASSIGNED9B", "UNASSIGNED9C", "UNASSIGNED9D", 24 | "UNASSIGNED9E", "UNASSIGNED9F", "LSHIFT", "RSHIFT", "LCONTROL", "RCONTROL", "LMENU", "RMENU", 25 | "BROWSER_BACK", "BROWSER_FORWARD", "BROWSER_REFRESH", "BROWSER_STOP", "BROWSER_SEARCH", 26 | "BROWSER_FAVORITES", "BROWSER_HOME", "VOLUME_MUTE", "VOLUME_DOWN", "VOLUME_UP", 27 | "MEDIA_NEXT_TRACK", "MEDIA_PREV_TRACK", "MEDIA_STOP", "MEDIA_PLAY_PAUSE", "LAUNCH_MAIL", 28 | "LAUNCH_MEDIA_SELECT", "LAUNCH_APP1", "LAUNCH_APP2", "RESERVEDB8", "RESERVEDB9", "US_SEMICOLON", 29 | "PLUS", "COMMA", "MINUS", "PERIOD", "US_SLASH", "US_TILDE", "RESERVEDC1", "RESERVEDC2", 30 | "RESERVEDC3", "RESERVEDC4", "RESERVEDC5", "RESERVEDC6", "RESERVEDC7", "RESERVEDC8", 31 | "RESERVEDC9", "RESERVEDCA", "RESERVEDCB", "RESERVEDCC", "RESERVEDCD", "RESERVEDCE", 32 | "RESERVEDCF", "RESERVEDD0", "RESERVEDD1", "RESERVEDD2", "RESERVEDD3", "RESERVEDD4", 33 | "RESERVEDD5", "RESERVEDD6", "RESERVEDD7", "UNASSIGNEDD8", "UNASSIGNEDD9", "UNASSIGNEDDA", 34 | "US_SQUARE_BRACKET_OPEN", "US_BACKSLASH", "US_SQUARE_BRACKET_CLOSE", "US_QUOTE", "OEM_8", 35 | "RESERVEDE0", "OEM_AX", "OEM_102", "ICO_HELP", "ICO_00", "PROCESSKEY", "ICO_CLEAR", "PACKET", 36 | "RESERVEDE8", "OEM_RESET", "OEM_JUMP", "OEM_PA1", "OEM_PA2", "OEM_PA3", "OEM_WSCTRL", 37 | "OEM_CUSEL", "OEM_ATTN", "OEM_FINISH", "OEM_COPY", "OEM_AUTO", "OEM_ENLW", "OEM_BACKTAB", 38 | "ATTN", "CRSEL", "EXSEL", "EREOF", "PLAY", "ZOOM", "NONAME", "PA1", "OEM_CLEAR", "RESERVEDFF"}; 39 | 40 | static lsl::stream_outlet* outlet = nullptr; 41 | static HHOOK kbdHook = nullptr; 42 | static bool isPressed[256] = {0}; 43 | 44 | LRESULT CALLBACK keyboard_callback(int code, WPARAM wParam, LPARAM lParam) { 45 | if (code >= 0 && outlet) { 46 | unsigned char key = 0; 47 | switch (wParam) { 48 | case WM_KEYDOWN: 49 | case WM_SYSKEYDOWN: 50 | key = ((KBDLLHOOKSTRUCT *)lParam)->vkCode & 0xFF; 51 | if (!isPressed[key]) { 52 | std::string evstr{key_names[key] + " pressed"}; 53 | outlet->push_sample(&evstr); 54 | std::cout << evstr << std::endl; 55 | isPressed[key] = true; 56 | } 57 | break; 58 | case WM_KEYUP: 59 | case WM_SYSKEYUP: 60 | key = ((KBDLLHOOKSTRUCT *)lParam)->vkCode & 0xFF; 61 | { 62 | std::string evstr{key_names[key] + " released"}; 63 | outlet->push_sample(&evstr); 64 | } 65 | isPressed[key] = false; 66 | break; 67 | default:; 68 | } 69 | } 70 | return CallNextHookEx(kbdHook, code, wParam, lParam); 71 | } 72 | 73 | int main(int argc, char *argv[]) { 74 | try { 75 | kbdHook = 76 | SetWindowsHookEx(WH_KEYBOARD_LL, (HOOKPROC)keyboard_callback, GetModuleHandle(nullptr), 0); 77 | if (!kbdHook) 78 | throw std::runtime_error( 79 | "Cannot install keyboard hook. Please make sure that this program has the " 80 | "permission to grab the Windows keyboard events."); 81 | std::cout << "Keyboard successfully hooked" << std::endl; 82 | // create streaminfo and outlet 83 | lsl::stream_info info("Keyboard", "Markers", 1, lsl::IRREGULAR_RATE, lsl::cf_string); 84 | outlet = new lsl::stream_outlet(info); 85 | std::cout << "Outlet created. Close this window to stop streaming." << std::endl; 86 | } catch (std::exception &e) { std::cerr << "Error: " << e.what() << std::endl; } 87 | MSG msg; 88 | while (GetMessage(&msg, NULL, 0, 0) > 0) { 89 | TranslateMessage(&msg); 90 | DispatchMessage(&msg); 91 | } 92 | if (kbdHook > 0) UnhookWindowsHookEx(kbdHook); 93 | if (outlet) delete outlet; 94 | } 95 | -------------------------------------------------------------------------------- /win_mouse.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #define WIN32_LEAN_AND_MEAN 6 | #include 7 | 8 | static lsl::stream_outlet *outletButtons{nullptr}, *outletPosition{nullptr}; 9 | static HHOOK mouseHook = nullptr; 10 | 11 | LRESULT CALLBACK mouse_callback(int code, WPARAM wParam, LPARAM lParam) { 12 | if (code >= 0 && outletButtons && outletPosition) { 13 | const auto *ev = (MSLLHOOKSTRUCT *)lParam; 14 | if (wParam == WM_MOUSEMOVE) { 15 | const int pos[] = {ev->pt.x, ev->pt.y}; 16 | outletPosition->push_sample(pos); 17 | return CallNextHookEx(mouseHook, code, wParam, lParam); 18 | } 19 | 20 | std::string evstr; 21 | const short mouseUpper = ev->mouseData >> 16; 22 | switch (wParam) { 23 | case WM_LBUTTONDOWN: evstr = "MouseButtonLeft pressed"; break; 24 | case WM_LBUTTONUP: evstr = "MouseButtonLeft released"; break; 25 | case WM_RBUTTONDOWN: evstr = "MouseButtonRight pressed"; break; 26 | case WM_RBUTTONUP: evstr = "MouseButtonRight released"; break; 27 | case WM_MBUTTONDOWN: evstr = "MouseButtonMiddle pressed"; break; 28 | case WM_MBUTTONUP: evstr = "MouseButtonMiddle released"; break; 29 | case WM_XBUTTONDOWN: 30 | evstr = "MouseButtonX" + std::to_string(ev->mouseData >> 16) + " pressed"; 31 | break; 32 | case WM_XBUTTONUP: 33 | evstr = "MouseButtonX" + std::to_string(ev->mouseData >> 16) + " released"; 34 | break; 35 | case WM_MOUSEWHEEL: 36 | if (mouseUpper > 0) 37 | evstr = "MouseWheelUp" + std::to_string(mouseUpper) + " pressed"; 38 | else 39 | evstr = "MouseWheelDown" + std::to_string(-mouseUpper) + " pressed"; 40 | break; 41 | case WM_MOUSEHWHEEL: 42 | if (mouseUpper < 0) 43 | evstr = "MouseWheelLeft" + std::to_string(-mouseUpper) + " pressed"; 44 | else 45 | evstr = "MouseWheelRight" + std::to_string(mouseUpper) + " pressed"; 46 | break; 47 | default: return CallNextHookEx(mouseHook, code, wParam, lParam); 48 | } 49 | outletButtons->push_sample(&evstr); 50 | } 51 | return CallNextHookEx(mouseHook, code, wParam, lParam); 52 | } 53 | 54 | int main(int argc, char *argv[]) { 55 | try { 56 | mouseHook = SetWindowsHookEx(WH_MOUSE_LL, mouse_callback, GetModuleHandle(nullptr), 0); 57 | if (!mouseHook) 58 | throw std::runtime_error( 59 | "Cannot install mouse hook. Please make sure that this program has the permission " 60 | "to grab the Windows mouse events."); 61 | std::cout << "Mouse successfully hooked" << std::endl; 62 | // create streaminfo and outlet for buttons 63 | lsl::stream_info infoButtons( 64 | "MouseButtons", "Markers", 1, lsl::IRREGULAR_RATE, lsl::cf_string); 65 | outletButtons = new lsl::stream_outlet(infoButtons); 66 | // create streaminfo and outlet for the position 67 | lsl::stream_info infoPosition( 68 | "MousePosition", "Position", 2, lsl::IRREGULAR_RATE, lsl::cf_int32); 69 | lsl::xml_element setup = infoPosition.desc().append_child("setup"); 70 | lsl::xml_element display = setup.append_child("display"); 71 | display.append_child_value("monitors", std::to_string(GetSystemMetrics(SM_CMONITORS))); 72 | display.append_child("resolution_primary") 73 | .append_child_value("X", std::to_string(GetSystemMetrics(SM_CXSCREEN))) 74 | .append_child_value("Y", std::to_string(GetSystemMetrics(SM_CYSCREEN))); 75 | display.append_child("resolution_virtual") 76 | .append_child_value("X", std::to_string(GetSystemMetrics(SM_XVIRTUALSCREEN))) 77 | .append_child_value("Y", std::to_string(GetSystemMetrics(SM_YVIRTUALSCREEN))); 78 | lsl::xml_element channels = infoPosition.desc().append_child("channels"); 79 | channels.append_child("channel") 80 | .append_child_value("label", "MouseX") 81 | .append_child_value("type", "PositionX") 82 | .append_child_value("unit", "pixels") 83 | .append_child_value("origin", "top"); 84 | channels.append_child("channel") 85 | .append_child_value("label", "MouseY") 86 | .append_child_value("type", "PositionY") 87 | .append_child_value("unit", "pixels") 88 | .append_child_value("origin", "left"); 89 | outletPosition = new lsl::stream_outlet(infoPosition); 90 | std::cout << "Started streaming data, close this window to stop." << std::endl; 91 | } catch (std::exception &e) { std::cerr << "Error: " << e.what() << std::endl; } 92 | MSG msg; 93 | while (GetMessage(&msg, nullptr, 0, 0) > 0) { 94 | TranslateMessage(&msg); 95 | DispatchMessage(&msg); 96 | } 97 | if (mouseHook > 0) UnhookWindowsHookEx(mouseHook); 98 | } 99 | --------------------------------------------------------------------------------