├── .gitignore ├── 3rdparty ├── README.txt └── d2d1.h ├── UNLICENSE ├── chuniio ├── chuniio.c ├── chuniio.def ├── chuniio.h └── meson.build ├── create-touch-window ├── create-touch-window.c ├── create-touch-window.h ├── dllmain.c └── meson.build ├── cross-mingw-32.txt ├── cross-mingw-64.txt ├── leap-configurator ├── leap-configurator.c └── meson.build ├── leapio ├── leapio.c ├── leapio.h └── meson.build ├── log.h ├── meson.build ├── precompiled.h ├── readme.md └── subprojects └── capnhook.wrap /.gitignore: -------------------------------------------------------------------------------- 1 | build32 2 | build64 3 | .vscode 4 | LeapSDK 5 | -------------------------------------------------------------------------------- /3rdparty/README.txt: -------------------------------------------------------------------------------- 1 | Download the Leap Motion Orion SDK package from https://developer.leapmotion.com and copy LeapSDK folder here. 2 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /chuniio/chuniio.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "chuniio.h" 7 | #include "leapio/leapio.h" 8 | #include "log.h" 9 | 10 | // FIXME: free d2d on exit 11 | 12 | #define CHUNI_WINPROC CallWindowProc(chuni_wndproc, hwnd, msg, w_param, l_param) 13 | #define DEF_WINPROC DefWindowProc(hwnd, msg, w_param, l_param) 14 | #define MAXFINGERS 10 15 | #define CONFIG L".\\chunitouch.ini" 16 | 17 | extern IMAGE_DOS_HEADER __ImageBase; 18 | #define M_HINST ((HINSTANCE) &__ImageBase) 19 | 20 | #define CSRC_TOUCH 0 21 | #define CSRC_LEAP 1 22 | 23 | #define LEAP_X 0 24 | #define LEAP_Y 1 25 | #define LEAP_Z 2 26 | 27 | static BOOL separate_control = FALSE; 28 | static LONG chuni_ir_trigger_threshold = 7000; 29 | static LONG chuni_ir_height = 5000; 30 | static UINT chuni_ir_leap_trigger = 500; 31 | static UINT chuni_ir_leap_step = 300; 32 | static uint8_t leap_orientation = LEAP_Y; 33 | static BOOL leap_inverted = FALSE; 34 | 35 | static LONG chuni_key_start = 31800; 36 | static LONG chuni_key_width = 4000; 37 | 38 | static LONG chuni_key_end = 0; //chuni_key_start + 32 * chuni_key_width; 39 | 40 | static bool raw_input = false; 41 | static uint8_t ir_control_source = CSRC_TOUCH; 42 | static bool ir_keep_slider = false; 43 | 44 | static unsigned int __stdcall chuni_io_slider_thread_proc(void* ctx); 45 | 46 | static bool chuni_coin_pending = true; 47 | static uint16_t chuni_coins = 0; 48 | static uint8_t chuni_ir_sensor_map = 0; 49 | static HANDLE chuni_io_slider_thread; 50 | static bool chuni_io_slider_stop_flag; 51 | static uint8_t chuni_sliders[32]; 52 | static WNDPROC chuni_wndproc; 53 | 54 | static LONG start_locations[MAXFINGERS]; 55 | static LONG finger_ids[MAXFINGERS]; 56 | 57 | static D2D1_SIZE_U canvas_sz; 58 | static ID2D1HwndRenderTarget *target = NULL; 59 | static ID2D1SolidColorBrush* brushes[32]; 60 | 61 | static int get_slider_from_pos(LONG x, LONG y) { 62 | if (x < chuni_key_start || x > chuni_key_end) return -1; 63 | return 31 - ((x - chuni_key_start) / chuni_key_width); 64 | } 65 | 66 | static void chuni_io_ir(uint8_t *bitmap, int8_t sensor_id, bool set) { 67 | if (sensor_id > 5) sensor_id = 5; 68 | if (sensor_id < 0) sensor_id = 0; 69 | if (sensor_id % 2 == 0) sensor_id++; 70 | else sensor_id--; 71 | if (set) *bitmap |= 1 << sensor_id; 72 | else *bitmap &= ~(1 << sensor_id); 73 | } 74 | 75 | static int get_finger_index(DWORD id) { 76 | int avail_indx = -1; 77 | for (int i = 0; i < MAXFINGERS; i++) { 78 | if (finger_ids[i] > 0 && (DWORD) finger_ids[i] == id) return i; 79 | if (avail_indx == -1 && finger_ids[i] == -1) avail_indx = i; 80 | } 81 | 82 | if (avail_indx == -1) return -1; 83 | finger_ids[avail_indx] = id; 84 | return avail_indx; 85 | } 86 | 87 | LRESULT CALLBACK winproc(HWND hwnd, UINT msg, WPARAM w_param, LPARAM l_param) { 88 | if (msg != WM_TOUCH) return separate_control ? DEF_WINPROC : CHUNI_WINPROC; 89 | 90 | UINT fingers = LOWORD(w_param); 91 | if (fingers <= 0) return separate_control ? DEF_WINPROC : CHUNI_WINPROC; 92 | POINT local_point; 93 | TOUCHINPUT inputs[MAXFINGERS]; 94 | static uint8_t clicked_sliders[32]; 95 | 96 | memset(clicked_sliders, 0, 32); 97 | uint8_t chuni_ir_map_local = 0; 98 | 99 | if (GetTouchInputInfo((HTOUCHINPUT)l_param, fingers, inputs, sizeof(TOUCHINPUT))) { 100 | for (UINT i = 0; i < fingers; i++) { 101 | TOUCHINPUT p = inputs[i]; 102 | local_point.x = TOUCH_COORD_TO_PIXEL(p.x); 103 | local_point.y = TOUCH_COORD_TO_PIXEL(p.y); 104 | int fid = get_finger_index(p.dwID); 105 | if (fid < 0) { 106 | log_error("too many fingers.\n"); 107 | continue; 108 | } 109 | if (p.dwFlags & TOUCHEVENTF_UP) { 110 | finger_ids[fid] = -1; 111 | continue; 112 | } 113 | if (!raw_input) { 114 | if (ScreenToClient(hwnd, &local_point) == 0) { 115 | log_error("screen-to-client mapping failed"); 116 | } 117 | } 118 | if (p.dwFlags & TOUCHEVENTF_DOWN) start_locations[fid] = local_point.y; 119 | LONG x_diff = start_locations[fid] - local_point.y; 120 | if (ir_control_source == CSRC_TOUCH && x_diff > chuni_ir_trigger_threshold) { 121 | int8_t ir_id = (x_diff / chuni_ir_height) - 1; 122 | chuni_io_ir(&chuni_ir_map_local, ir_id, true); 123 | } 124 | if (ir_control_source == CSRC_LEAP || x_diff <= chuni_ir_trigger_threshold || ir_keep_slider) { 125 | int slider_id = get_slider_from_pos(local_point.x, local_point.y); 126 | if (slider_id >= 0 && slider_id < 32) clicked_sliders[slider_id] = 128; 127 | } 128 | } 129 | } 130 | 131 | CloseTouchInputHandle((HTOUCHINPUT)l_param); 132 | 133 | memcpy(chuni_sliders, clicked_sliders, 32); 134 | chuni_ir_sensor_map = chuni_ir_map_local; 135 | return separate_control ? DEF_WINPROC : CHUNI_WINPROC; 136 | } 137 | 138 | static void render() { 139 | if (!target) return; 140 | ID2D1HwndRenderTarget_BeginDraw(target); 141 | 142 | float step = canvas_sz.width/32.; 143 | 144 | for (int i = 0; i < 32; i++) { 145 | D2D1_RECT_F r = { step * i, 0, step * (i+1), canvas_sz.height }; 146 | ID2D1HwndRenderTarget_FillRectangle(target, &r, (ID2D1Brush *) brushes[i]); 147 | } 148 | 149 | if (ID2D1HwndRenderTarget_EndDraw(target, NULL, NULL) < 0) { // fixme: read tag 150 | log_fatal("render failed.\n"); 151 | } 152 | } 153 | 154 | static void make_control_window() { 155 | if (target) return; 156 | ID2D1Factory* d2df = NULL; 157 | D2D1_FACTORY_OPTIONS opt = { D2D1_DEBUG_LEVEL_INFORMATION }; 158 | if (D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &IID_ID2D1Factory, &opt, (void **) &d2df) != S_OK) { 159 | log_fatal("can't create d2d factoy.\n"); 160 | return; 161 | } 162 | const char *name = "chuni-controller"; 163 | 164 | WNDCLASS c = { CS_NOCLOSE, winproc, 0, 0, M_HINST, NULL, LoadCursor(0, IDC_ARROW), NULL, NULL, name }; 165 | RegisterClass(&c); 166 | HWND hwnd = CreateWindowEx( 167 | 0, name, name, 168 | WS_OVERLAPPED | WS_CAPTION, 169 | CW_USEDEFAULT, CW_USEDEFAULT, (32 * chuni_key_width), 170 | (chuni_ir_height * 12 + chuni_ir_trigger_threshold), NULL, NULL, M_HINST, NULL 171 | ); 172 | 173 | if (!hwnd) { 174 | log_fatal("can't create control window.\n"); 175 | return; 176 | } 177 | 178 | RECT rc; 179 | GetClientRect(hwnd, &rc); 180 | 181 | canvas_sz.height = rc.bottom - rc.top; 182 | canvas_sz.width = rc.right - rc.left; 183 | 184 | D2D1_RENDER_TARGET_PROPERTIES rtp; 185 | rtp.type = D2D1_RENDER_TARGET_TYPE_DEFAULT; 186 | rtp.pixelFormat.alphaMode = D2D1_ALPHA_MODE_IGNORE; 187 | rtp.pixelFormat.format = DXGI_FORMAT_B8G8R8A8_UNORM; 188 | rtp.dpiX = rtp.dpiY = 0; 189 | rtp.usage = D2D1_RENDER_TARGET_USAGE_NONE; 190 | rtp.minLevel = D2D1_FEATURE_LEVEL_DEFAULT; 191 | 192 | D2D1_HWND_RENDER_TARGET_PROPERTIES hrtp; 193 | hrtp.hwnd = hwnd; 194 | hrtp.pixelSize = canvas_sz; 195 | hrtp.presentOptions = D2D1_PRESENT_OPTIONS_RETAIN_CONTENTS; 196 | 197 | if (ID2D1Factory_CreateHwndRenderTarget(d2df, &rtp, &hrtp, &target) < 0) { 198 | log_fatal("can't create d2d render target.\n"); 199 | return; 200 | } 201 | 202 | for (int i = 0; i < 32; i++) { 203 | D2D1_COLOR_F color = { i/32., i/32., i/32., 1. }; 204 | if (ID2D1HwndRenderTarget_CreateSolidColorBrush(target, &color, NULL, &brushes[i]) < 0) { 205 | log_fatal("d2d brush creation failed.\n"); 206 | return; 207 | } 208 | } 209 | 210 | ShowWindow(hwnd, SW_SHOWNORMAL); 211 | } 212 | 213 | void leap_handler(const LEAP_TRACKING_EVENT *ev) { 214 | uint8_t chuni_ir_map_local = 0; 215 | 216 | for(uint32_t h = 0; h < ev->nHands; h++) { 217 | const LEAP_HAND* hand = &(ev->pHands[h]); 218 | float pos = 0; 219 | if (leap_orientation == LEAP_X) pos = hand->palm.position.x; 220 | if (leap_orientation == LEAP_Y) pos = hand->palm.position.y; 221 | if (leap_orientation == LEAP_Z) pos = hand->palm.position.z; 222 | 223 | if ((!leap_inverted && pos > chuni_ir_leap_trigger) || (leap_inverted && chuni_ir_leap_trigger > pos)) { 224 | int8_t ir_id = (leap_inverted ? -1 : 1) * (pos - chuni_ir_leap_trigger) / chuni_ir_leap_step + 1; 225 | if (ir_id > 5) ir_id = 5; 226 | if (ir_id < 0) ir_id = 0; 227 | chuni_io_ir(&chuni_ir_map_local, ir_id, true); 228 | } 229 | } 230 | 231 | chuni_ir_sensor_map = chuni_ir_map_local; 232 | } 233 | 234 | HRESULT chuni_io_jvs_init(void) { 235 | // hook winproc 236 | HWND hwnd = FindWindowA(NULL, "teaGfx DirectX Release"); 237 | 238 | // alloc console for debug output 239 | AllocConsole(); 240 | SetConsoleTitle("chuni-touch"); 241 | FILE* fp; 242 | freopen_s(&fp, "CONOUT$", "w", stdout); 243 | log_info("allocated debug console.\n"); 244 | 245 | WCHAR str_control_src[16]; 246 | WCHAR str_leap_orientation[16]; 247 | 248 | separate_control = GetPrivateProfileIntW(L"options", L"separate_control", FALSE, CONFIG); 249 | chuni_ir_height = GetPrivateProfileIntW(L"ir", L"touch_height", 50, CONFIG); 250 | chuni_ir_trigger_threshold = GetPrivateProfileIntW(L"ir", L"touch_trigger", 70, CONFIG); 251 | chuni_ir_leap_trigger = GetPrivateProfileIntW(L"ir", L"leap_trigger", 50, CONFIG); 252 | chuni_ir_leap_step = GetPrivateProfileIntW(L"ir", L"leap_step", 30, CONFIG); 253 | chuni_key_start = GetPrivateProfileIntW(L"slider", L"offset", 318, CONFIG); 254 | chuni_key_width = GetPrivateProfileIntW(L"slider", L"width", 40, CONFIG); 255 | raw_input = GetPrivateProfileIntW(L"io", L"raw_input", 0, CONFIG); 256 | ir_keep_slider = GetPrivateProfileIntW(L"misc", L"ir_keep_slider", 0, CONFIG); 257 | 258 | if (separate_control) { 259 | chuni_key_start = 0; 260 | log_info("ignoring slider.offset in separate_control mode.\n"); 261 | } 262 | 263 | if (hwnd == NULL) log_error("can't get window handle for chuni.\n"); 264 | else if (!separate_control) { 265 | ULONG flags; 266 | if (!IsTouchWindow(hwnd, &flags)) log_warn("IsTouchWindow() returned false, touch might not work.\n"); 267 | #ifdef _WIN64 268 | chuni_wndproc = (WNDPROC)SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)&winproc); 269 | #else 270 | chuni_wndproc = (WNDPROC)SetWindowLongPtr(hwnd, GWL_WNDPROC, (LONG_PTR)&winproc); 271 | #endif 272 | 273 | log_info("hooked WNDPROC.\n"); 274 | } 275 | 276 | GetPrivateProfileStringW(L"ir", L"control_source", L"touch", str_control_src, 16, CONFIG); 277 | GetPrivateProfileStringW(L"ir", L"leap_orientation", L"y", str_leap_orientation, 16, CONFIG); 278 | 279 | chuni_key_end = chuni_key_start + 32 * chuni_key_width; 280 | ir_control_source = (wcscmp(str_control_src, L"leap") == 0) ? CSRC_LEAP : CSRC_TOUCH; 281 | /**/ if (wcscmp(str_leap_orientation, L"x") == 0) leap_orientation = LEAP_X; 282 | else if (wcscmp(str_leap_orientation, L"y") == 0) leap_orientation = LEAP_Y; 283 | else if (wcscmp(str_leap_orientation, L"z") == 0) leap_orientation = LEAP_Z; 284 | else if (wcscmp(str_leap_orientation, L"-x") == 0) { leap_orientation = LEAP_X; leap_inverted = TRUE; } 285 | else if (wcscmp(str_leap_orientation, L"-y") == 0) { leap_orientation = LEAP_Y; leap_inverted = TRUE; } 286 | else if (wcscmp(str_leap_orientation, L"-z") == 0) { leap_orientation = LEAP_Z; leap_inverted = TRUE; } 287 | 288 | for(int i = 0; i < MAXFINGERS; i++) finger_ids[i] = -1; 289 | 290 | if (ir_control_source == CSRC_LEAP) { 291 | log_info("connecting to leap service...\n"); 292 | leap_connect(NULL); 293 | leap_set_tracking_handler(leap_handler); 294 | while (!leap_is_connected()) { 295 | Sleep(10); 296 | } 297 | log_info("connected to leap service.\n"); 298 | } 299 | 300 | 301 | log_info("raw_input: %s\n", raw_input ? "enabled" : "disabled"); 302 | log_info("separate_control: %s\n", separate_control ? "enabled" : "disabled"); 303 | log_info("ir_keep_slider: %s\n", ir_keep_slider ? "enabled" : "disabled"); 304 | log_info("key: start: %ld, width: %ld, end: %ld\n", chuni_key_start, chuni_key_width, chuni_key_end); 305 | 306 | if (ir_control_source == CSRC_TOUCH) { 307 | log_info("ir: touch mode, trigger_threshold: %ld, height: %ld\n", chuni_ir_trigger_threshold, chuni_ir_height); 308 | } else { 309 | log_info("ir: leap mode, axis: %u, trigger_threshold: %u, step: %u\n", leap_orientation, chuni_ir_leap_trigger, chuni_ir_leap_step); 310 | } 311 | 312 | if (separate_control) { 313 | log_info("creating separated control window...\n"); 314 | make_control_window(); 315 | render(); 316 | } 317 | 318 | return S_OK; 319 | } 320 | 321 | void chuni_io_jvs_read_coin_counter(uint16_t* out) { 322 | if (out == NULL) { 323 | return; 324 | } 325 | 326 | if (GetAsyncKeyState(VK_F3)) { 327 | if (chuni_coin_pending) { 328 | chuni_coins++; 329 | chuni_coin_pending = false; 330 | } 331 | } else chuni_coin_pending = true; 332 | 333 | *out = chuni_coins; 334 | } 335 | 336 | void chuni_io_jvs_poll(uint8_t* opbtn, uint8_t* beams) { 337 | *beams = chuni_ir_sensor_map; 338 | } 339 | 340 | void chuni_io_jvs_set_coin_blocker(bool open) { 341 | if (open) log_info("coin blocker disabled"); 342 | else log_info("coin blocker enabled."); 343 | 344 | } 345 | 346 | HRESULT chuni_io_slider_init(void) { 347 | log_info("init slider...\n"); 348 | return S_OK; 349 | } 350 | 351 | void chuni_io_slider_start(chuni_io_slider_callback_t callback) { 352 | log_info("starting slider...\n"); 353 | if (chuni_io_slider_thread != NULL) { 354 | return; 355 | } 356 | 357 | chuni_io_slider_thread = (HANDLE)_beginthreadex( 358 | NULL, 359 | 0, 360 | chuni_io_slider_thread_proc, 361 | callback, 362 | 0, 363 | NULL); 364 | } 365 | 366 | void chuni_io_slider_stop(void) { 367 | log_info("stopping slider...\n"); 368 | if (chuni_io_slider_thread == NULL) { 369 | return; 370 | } 371 | 372 | chuni_io_slider_stop_flag = true; 373 | 374 | WaitForSingleObject(chuni_io_slider_thread, INFINITE); 375 | CloseHandle(chuni_io_slider_thread); 376 | chuni_io_slider_thread = NULL; 377 | chuni_io_slider_stop_flag = false; 378 | } 379 | 380 | void chuni_io_slider_set_leds(const uint8_t* brg) { 381 | if (separate_control) { 382 | for (int i = 31, ii = 0; i >= 0; i--, ii += 3) { 383 | D2D1_COLOR_F c = { brg[ii+1]/255., brg[ii+2]/255., brg[ii]/255., 1. }; 384 | ID2D1SolidColorBrush_SetColor(brushes[i], &c); 385 | } 386 | D2D1_COLOR_F c = { brg[91]/255., brg[92]/255., brg[90]/255., 1. }; 387 | ID2D1SolidColorBrush_SetColor(brushes[0], &c); // hmm... 388 | render(); 389 | } 390 | } 391 | 392 | static unsigned int __stdcall chuni_io_slider_thread_proc(void* ctx) { 393 | chuni_io_slider_callback_t callback; 394 | 395 | for (size_t i = 0; i < _countof(chuni_sliders); i++) chuni_sliders[i] = 0; 396 | 397 | callback = (chuni_io_slider_callback_t)ctx; 398 | 399 | while (!chuni_io_slider_stop_flag) { 400 | callback(chuni_sliders); 401 | Sleep(1); 402 | } 403 | 404 | return 0; 405 | } 406 | -------------------------------------------------------------------------------- /chuniio/chuniio.def: -------------------------------------------------------------------------------- 1 | LIBRARY chuniio 2 | 3 | EXPORTS 4 | chuni_io_jvs_init 5 | chuni_io_jvs_poll 6 | chuni_io_jvs_read_coin_counter 7 | chuni_io_jvs_set_coin_blocker 8 | chuni_io_slider_init 9 | chuni_io_slider_set_leds 10 | chuni_io_slider_start 11 | chuni_io_slider_stop 12 | 13 | -------------------------------------------------------------------------------- /chuniio/chuniio.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef WINVER 3 | #define WINVER 0x0602 4 | #endif 5 | #ifndef _WIN32_WINNT 6 | #define _WIN32_WINNT 0x0602 7 | #endif 8 | #ifndef _WIN32_WINDOWS 9 | #define _WIN32_WINDOWS 0x0410 10 | #endif 11 | #ifndef _WIN32_IE 12 | #define _WIN32_IE 0x0700 13 | #endif 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #define D2D_USE_C_DEFINITIONS 21 | #ifdef __MINGW32__ 22 | #include 23 | #else 24 | #ifdef _MSC_VER 25 | #include "3rdparty/d2d1.h" 26 | #else 27 | #error "don't know what to do with d2d1." 28 | #endif 29 | #endif 30 | #pragma comment(lib, "d2d1.lib") 31 | 32 | typedef void (*chuni_io_slider_callback_t)(const uint8_t *state); 33 | HRESULT chuni_io_jvs_init(void); 34 | void chuni_io_jvs_poll(uint8_t *opbtn, uint8_t *beams); 35 | void chuni_io_jvs_read_coin_counter(uint16_t *total); 36 | void chuni_io_jvs_set_coin_blocker(bool open); 37 | HRESULT chuni_io_slider_init(void); 38 | void chuni_io_slider_start(chuni_io_slider_callback_t callback); 39 | void chuni_io_slider_set_leds(const uint8_t *rgb); 40 | void chuni_io_slider_stop(void); 41 | -------------------------------------------------------------------------------- /chuniio/meson.build: -------------------------------------------------------------------------------- 1 | chuniio_dll = shared_library( 2 | 'chuniio', 3 | name_prefix : '', 4 | include_directories : [inc, leap_inc], 5 | implicit_include_directories : false, 6 | c_pch : '../precompiled.h', 7 | link_with: [leapio_lib], 8 | dependencies: [leap_lib, d2d1_lib], 9 | vs_module_defs : 'chuniio.def', 10 | sources : [ 11 | 'chuniio.c', 12 | 'chuniio.h' 13 | ], 14 | ) 15 | -------------------------------------------------------------------------------- /create-touch-window/create-touch-window.c: -------------------------------------------------------------------------------- 1 | #include "create-touch-window.h" 2 | #include "hook/table.h" 3 | #include "hook/com-proxy.h" 4 | #include 5 | #include 6 | 7 | #define CONFIG L".\\chunitouch.ini" 8 | 9 | static BOOL touch_feedback = FALSE; 10 | 11 | void make_touchable (HWND h) { 12 | const BOOL enabled = FALSE; 13 | if(!touch_feedback) { 14 | SetWindowFeedbackSetting(h, FEEDBACK_TOUCH_CONTACTVISUALIZATION, 0, sizeof(BOOL), &enabled); 15 | SetWindowFeedbackSetting(h, FEEDBACK_PEN_BARRELVISUALIZATION, 0, sizeof(BOOL), &enabled); 16 | SetWindowFeedbackSetting(h, FEEDBACK_PEN_TAP, 0, sizeof(BOOL), &enabled); 17 | SetWindowFeedbackSetting(h, FEEDBACK_PEN_DOUBLETAP, 0, sizeof(BOOL), &enabled); 18 | SetWindowFeedbackSetting(h, FEEDBACK_PEN_PRESSANDHOLD, 0, sizeof(BOOL), &enabled); 19 | SetWindowFeedbackSetting(h, FEEDBACK_PEN_RIGHTTAP, 0, sizeof(BOOL), &enabled); 20 | SetWindowFeedbackSetting(h, FEEDBACK_TOUCH_TAP, 0, sizeof(BOOL), &enabled); 21 | SetWindowFeedbackSetting(h, FEEDBACK_TOUCH_DOUBLETAP, 0, sizeof(BOOL), &enabled); 22 | SetWindowFeedbackSetting(h, FEEDBACK_TOUCH_PRESSANDHOLD, 0, sizeof(BOOL), &enabled); 23 | SetWindowFeedbackSetting(h, FEEDBACK_TOUCH_RIGHTTAP, 0, sizeof(BOOL), &enabled); 24 | SetWindowFeedbackSetting(h, FEEDBACK_GESTURE_PRESSANDTAP, 0, sizeof(BOOL), &enabled); 25 | } 26 | RegisterTouchWindow(h, TWF_FINETOUCH | TWF_WANTPALM); 27 | } 28 | 29 | static HWND(WINAPI* n_CreateWindowExW)( 30 | DWORD dwExStyle, 31 | LPCWSTR lpClassName, 32 | LPCWSTR lpWindowName, 33 | DWORD dwStyle, 34 | int X, 35 | int Y, 36 | int nWidth, 37 | int nHeight, 38 | HWND hWndParent, 39 | HMENU hMenu, 40 | HINSTANCE hInstance, 41 | LPVOID lpParam 42 | ); 43 | 44 | static HWND(WINAPI* n_CreateWindowExA)( 45 | DWORD dwExStyle, 46 | LPCSTR lpClassName, 47 | LPCSTR lpWindowName, 48 | DWORD dwStyle, 49 | int X, 50 | int Y, 51 | int nWidth, 52 | int nHeight, 53 | HWND hWndParent, 54 | HMENU hMenu, 55 | HINSTANCE hInstance, 56 | LPVOID lpParam 57 | ); 58 | 59 | HWND WINAPI m_CreateWindowExW( 60 | DWORD dwExStyle, 61 | LPCWSTR lpClassName, 62 | LPCWSTR lpWindowName, 63 | DWORD dwStyle, 64 | int X, 65 | int Y, 66 | int nWidth, 67 | int nHeight, 68 | HWND hWndParent, 69 | HMENU hMenu, 70 | HINSTANCE hInstance, 71 | LPVOID lpParam 72 | ) { 73 | HWND h = n_CreateWindowExW(dwExStyle, lpClassName, lpWindowName, dwStyle, X, Y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam); 74 | make_touchable(h); 75 | return h; 76 | } 77 | 78 | HWND WINAPI m_CreateWindowExA( 79 | DWORD dwExStyle, 80 | LPCSTR lpClassName, 81 | LPCSTR lpWindowName, 82 | DWORD dwStyle, 83 | int X, 84 | int Y, 85 | int nWidth, 86 | int nHeight, 87 | HWND hWndParent, 88 | HMENU hMenu, 89 | HINSTANCE hInstance, 90 | LPVOID lpParam 91 | ) { 92 | HWND h = n_CreateWindowExA(dwExStyle, lpClassName, lpWindowName, dwStyle, X, Y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam); 93 | make_touchable(h); 94 | return h; 95 | } 96 | 97 | static const struct hook_symbol cw_hooks[] = { 98 | { "CreateWindowExW", 0, m_CreateWindowExW, (void**)&n_CreateWindowExW }, 99 | { "CreateWindowExA", 0, m_CreateWindowExA, (void**)&n_CreateWindowExA } 100 | }; 101 | 102 | void do_hook() { 103 | touch_feedback = GetPrivateProfileIntW(L"io", L"touch_feedback", 0, CONFIG); 104 | hook_table_apply(NULL, "user32.dll", cw_hooks, _countof(cw_hooks)); 105 | } 106 | -------------------------------------------------------------------------------- /create-touch-window/create-touch-window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef WINVER 3 | #define WINVER 0x0602 4 | #endif 5 | #ifndef _WIN32_WINNT 6 | #define _WIN32_WINNT 0x0602 7 | #endif 8 | #ifndef _WIN32_WINDOWS 9 | #define _WIN32_WINDOWS 0x0410 10 | #endif 11 | #ifndef _WIN32_IE 12 | #define _WIN32_IE 0x0700 13 | #endif 14 | 15 | void do_hook(); 16 | -------------------------------------------------------------------------------- /create-touch-window/dllmain.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "create-touch-window.h" 3 | 4 | BOOL WINAPI DllMain(HMODULE mod, DWORD cause, void *ctx) { 5 | do_hook(); 6 | return TRUE; 7 | } 8 | -------------------------------------------------------------------------------- /create-touch-window/meson.build: -------------------------------------------------------------------------------- 1 | chuniio_dll = shared_library( 2 | 'ctw', 3 | name_prefix : '', 4 | include_directories : inc, 5 | implicit_include_directories : false, 6 | c_pch : '../precompiled.h', 7 | dependencies: capnhook.get_variable('hook_dep'), 8 | sources : [ 9 | 'dllmain.c', 10 | 'create-touch-window.c', 11 | 'create-touch-window.h' 12 | ], 13 | ) 14 | -------------------------------------------------------------------------------- /cross-mingw-32.txt: -------------------------------------------------------------------------------- 1 | [binaries] 2 | c = 'i686-w64-mingw32-gcc' 3 | ar = 'i686-w64-mingw32-ar' 4 | strip = 'i686-w64-mingw32-strip' 5 | 6 | [host_machine] 7 | system = 'windows' 8 | cpu_family = 'x86' 9 | cpu = 'i686' 10 | endian = 'little' 11 | -------------------------------------------------------------------------------- /cross-mingw-64.txt: -------------------------------------------------------------------------------- 1 | [binaries] 2 | c = 'x86_64-w64-mingw32-gcc' 3 | ar = 'x86_64-w64-mingw32-ar' 4 | strip = 'x86_64-w64-mingw32-strip' 5 | 6 | [host_machine] 7 | system = 'windows' 8 | cpu_family = 'x86_64' 9 | cpu = 'x86_64' 10 | endian = 'little' 11 | -------------------------------------------------------------------------------- /leap-configurator/leap-configurator.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "leapio/leapio.h" 3 | #include "log.h" 4 | 5 | #define CONFIG L".\\chunitouch.ini" 6 | 7 | static BOOL _test_mode = FALSE; 8 | static uint32_t _n_hands = 0; 9 | static float _x, _y, _z; 10 | static BOOL use_leap; 11 | static UINT leap_trigger; 12 | static UINT leap_step; 13 | static UINT leap_orientation; 14 | static BOOL leap_inverted; 15 | 16 | #define LEAP_X 0 17 | #define LEAP_Y 1 18 | #define LEAP_Z 2 19 | 20 | void handle_track(const LEAP_TRACKING_EVENT *ev) { 21 | // log_debug("saw %u hands.\n", ev->nHands); 22 | static int8_t last_id = -1; 23 | _n_hands = ev->nHands; 24 | for(uint32_t h = 0; h < ev->nHands; h++) { 25 | const LEAP_HAND* hand = &(ev->pHands[h]); 26 | _x = hand->palm.position.x; 27 | _y = hand->palm.position.y; 28 | _z = hand->palm.position.z; 29 | 30 | if (_test_mode) { 31 | int8_t id = -1; 32 | float pos = 0; 33 | if (leap_orientation == LEAP_X) pos = _x; 34 | if (leap_orientation == LEAP_Y) pos = _y; 35 | if (leap_orientation == LEAP_Z) pos = _z; 36 | if ((!leap_inverted && pos > leap_trigger) || (leap_inverted && leap_trigger > pos)) { 37 | id = (leap_inverted ? -1 : 1) * (pos - leap_trigger) / leap_step - 1; 38 | if (id > 5) id = 5; 39 | if (id < 0) id = 0; 40 | } 41 | 42 | if (last_id != id) { 43 | if (id >= 0) log_info("IR %d triggered.\n", id + 1); 44 | else log_info("No IR triggered.\n"); 45 | last_id = id; 46 | } 47 | } 48 | 49 | //log_debug("hand %u is a %s hand. location (%f, %f, %f).\n", 50 | // hand->id, hand->type == eLeapHandType_Left ? "left" : "right", 51 | // hand->palm.position.x, hand->palm.position.y, hand->palm.position.z); 52 | } 53 | } 54 | 55 | char prompt(const char *p, const char *s, uint8_t n) { 56 | if (p != NULL) printf("%s ", p); 57 | if (n > 0 && s != NULL) { 58 | printf("[%c", s[0]); 59 | for (uint8_t i = 1; i < n; i ++) printf("/%c", s[i]); 60 | printf("] "); 61 | } 62 | char c = getchar(); 63 | 64 | if (c == '\n') { 65 | if (s != NULL) return s[0]; 66 | } else (void) getchar(); // eats "\n" 67 | return c; 68 | } 69 | 70 | void configure() { 71 | float low_x, low_y, low_z, high_x, high_y, high_z; 72 | 73 | while (TRUE) { 74 | prompt("Put your hand at the location you want the bottommost sensor to be activated, press [ENTER] when you are ready.", NULL, 0); 75 | if (_n_hands == 0) { 76 | printf("I can't see any hands.\n"); 77 | continue; 78 | } 79 | if (_n_hands > 1) { 80 | printf("I saw more than one hand.\n"); 81 | continue; 82 | } 83 | low_x = _x; 84 | low_y = _y; 85 | low_z = _z; 86 | break; 87 | } 88 | 89 | while (TRUE) { 90 | prompt("Put your hand at the location you want the topmost sensor to be activated, press [ENTER] when you are ready.", NULL, 0); 91 | if (_n_hands == 0) { 92 | printf("I can't see any hands.\n"); 93 | continue; 94 | } 95 | if (_n_hands > 1) { 96 | printf("I saw more than one hand.\n"); 97 | continue; 98 | } 99 | high_x = _x; 100 | high_y = _y; 101 | high_z = _z; 102 | break; 103 | } 104 | 105 | log_info("low: (%f, %f, %f), high: (%f, %f, %f).\n", low_x, low_y, low_z, high_x, high_y, high_z); 106 | float dx = high_x - low_x; 107 | float dy = high_y - low_y; 108 | float dz = high_z - low_z; 109 | float dmax = max(max(fabs(dx), fabs(dy)), fabs(dz)); 110 | WCHAR leap_orientation_char; 111 | 112 | if (dmax == fabs(dx)) { 113 | leap_orientation_char = 'x'; 114 | leap_orientation = LEAP_X; 115 | leap_trigger = low_x; 116 | } 117 | if (dmax == fabs(dy)) { 118 | leap_orientation_char = 'y'; 119 | leap_orientation = LEAP_Y; 120 | leap_trigger = low_y; 121 | } 122 | if (dmax == fabs(dz)) { 123 | leap_orientation_char = 'z'; 124 | leap_orientation = LEAP_Z; 125 | leap_trigger = low_z; 126 | } 127 | if (leap_orientation == LEAP_X && dx < 0) { 128 | leap_inverted = TRUE; 129 | } 130 | if (leap_orientation == LEAP_Y && dy < 0) { 131 | leap_inverted = TRUE; 132 | } 133 | if (leap_orientation == LEAP_Z && dz < 0) { 134 | leap_inverted = TRUE; 135 | } 136 | leap_step = dmax/6; 137 | use_leap = TRUE; 138 | 139 | WCHAR leap_trigger_str[16]; 140 | WCHAR leap_step_str[16]; 141 | WCHAR leap_orientation_str[16]; 142 | 143 | swprintf_s(leap_trigger_str, 16, L"%d", leap_trigger); 144 | swprintf_s(leap_step_str, 16, L"%d", leap_step); 145 | swprintf_s(leap_orientation_str, 16, L"%s%c", leap_inverted ? L"-" : L"", leap_orientation_char); 146 | 147 | WritePrivateProfileStringW(L"ir", L"leap_trigger", leap_trigger_str, CONFIG); 148 | WritePrivateProfileStringW(L"ir", L"leap_step", leap_step_str, CONFIG); 149 | WritePrivateProfileStringW(L"ir", L"leap_orientation", leap_orientation_str, CONFIG); 150 | } 151 | 152 | void test() { 153 | printf("Move your hand around. Configurator will print out which IR sensor is being activated.\n"); 154 | prompt("Press [ENTER] to begin test, press [ENTER] again to end.", NULL, 0); 155 | _test_mode = true; 156 | (void) getchar(); 157 | _test_mode = false; 158 | } 159 | 160 | int main () { 161 | leap_trigger = GetPrivateProfileIntW(L"ir", L"leap_trigger", 50, CONFIG); 162 | leap_step = GetPrivateProfileIntW(L"ir", L"leap_step", 30, CONFIG); 163 | 164 | WCHAR str_control_src[16]; 165 | WCHAR str_leap_orientation[16]; 166 | 167 | GetPrivateProfileStringW(L"ir", L"control_source", L"touch", str_control_src, 16, CONFIG); 168 | GetPrivateProfileStringW(L"ir", L"leap_orientation", L"y", str_leap_orientation, 16, CONFIG); 169 | use_leap = wcscmp(str_control_src, L"leap") == 0; 170 | 171 | /**/ if (wcscmp(str_leap_orientation, L"x") == 0) leap_orientation = LEAP_X; 172 | else if (wcscmp(str_leap_orientation, L"y") == 0) leap_orientation = LEAP_Y; 173 | else if (wcscmp(str_leap_orientation, L"z") == 0) leap_orientation = LEAP_Z; 174 | else if (wcscmp(str_leap_orientation, L"-x") == 0) { leap_orientation = LEAP_X; leap_inverted = TRUE; } 175 | else if (wcscmp(str_leap_orientation, L"-y") == 0) { leap_orientation = LEAP_Y; leap_inverted = TRUE; } 176 | else if (wcscmp(str_leap_orientation, L"-z") == 0) { leap_orientation = LEAP_Z; leap_inverted = TRUE; } 177 | 178 | log_info("connecting to leap service...\n"); 179 | leap_set_tracking_handler(handle_track); // debug 180 | 181 | leap_connect(NULL); 182 | while (!leap_is_connected()) { 183 | Sleep(10); 184 | } 185 | log_info("connected to leap service.\n"); 186 | 187 | while (TRUE) { 188 | printf("chuni-touch: leap configurator\n"); 189 | printf("current configured values: enabled: %s, trigger: %d, step: %d, orientation: %s%d.\n", use_leap ? "true" : "false", leap_trigger, leap_step, leap_inverted ? "-" : "", leap_orientation); 190 | printf(" c) configure\n"); 191 | printf(" t) test\n"); 192 | printf("\n"); 193 | printf(" q) quit\n"); 194 | char a = prompt("action", "Ctq", 3); 195 | switch (a) { 196 | case 'C': 197 | case 'c': 198 | configure(); 199 | break; 200 | case 't': 201 | test(); 202 | break; 203 | case 'q': 204 | return 0; 205 | default: 206 | printf("bad selection: %c.\n", a); 207 | } 208 | } 209 | 210 | leap_join_thread(); 211 | 212 | // TODO 213 | 214 | log_info("disconnecting from leap service...\n"); 215 | leap_disconnect(); 216 | log_info("disconnected.\n"); 217 | return 0; 218 | } 219 | 220 | -------------------------------------------------------------------------------- /leap-configurator/meson.build: -------------------------------------------------------------------------------- 1 | leap_configurator = executable( 2 | 'leapconfig', 3 | name_prefix : '', 4 | include_directories : [inc, leap_inc], 5 | implicit_include_directories : false, 6 | c_pch : '../precompiled.h', 7 | link_with: [leapio_lib], 8 | dependencies: leap_lib, 9 | sources : [ 10 | 'leap-configurator.c' 11 | ] 12 | ) 13 | -------------------------------------------------------------------------------- /leapio/leapio.c: -------------------------------------------------------------------------------- 1 | #include "leapio.h" 2 | #include "log.h" 3 | #include 4 | 5 | static LEAP_CONNECTION _connection = NULL; 6 | static leap_connect_callback_t _conn_cb = NULL; 7 | static leap_connect_callback_t _devconn_cb = NULL; 8 | static leap_tracking_callback_t _track_cb = NULL; 9 | static BOOL _running = FALSE; 10 | static BOOL _connected = FALSE; 11 | static BOOL _device_connected = FALSE; 12 | static HANDLE _polling_thread = NULL; 13 | 14 | static const char* leap_result_string(eLeapRS r) { 15 | switch(r){ 16 | case eLeapRS_Success: return "Success"; 17 | case eLeapRS_UnknownError: return "Unknown Error"; 18 | case eLeapRS_InvalidArgument: return "Invalid Argument"; 19 | case eLeapRS_InsufficientResources: return "Insufficient Resources"; 20 | case eLeapRS_InsufficientBuffer: return "Insufficient Buffer"; 21 | case eLeapRS_Timeout: return "Timeout"; 22 | case eLeapRS_NotConnected: return "Not Connected"; 23 | case eLeapRS_HandshakeIncomplete: return "Handshake Incomplete"; 24 | case eLeapRS_BufferSizeOverflow: return "Buffer Size Overflow"; 25 | case eLeapRS_ProtocolError: return "Protocol Error"; 26 | case eLeapRS_InvalidClientID: return "Invalid Client ID"; 27 | case eLeapRS_UnexpectedClosed: return "Unexpected Closed"; 28 | case eLeapRS_UnknownImageFrameRequest: return "Unknown Image Frame Request"; 29 | case eLeapRS_UnknownTrackingFrameID: return "Unknown Tracking Frame ID"; 30 | case eLeapRS_RoutineIsNotSeer: return "Routine Is Not Seer"; 31 | case eLeapRS_TimestampTooEarly: return "Timestamp Too Early"; 32 | case eLeapRS_ConcurrentPoll: return "Concurrent Poll"; 33 | case eLeapRS_NotAvailable: return "Not Available"; 34 | case eLeapRS_NotStreaming: return "Not Streaming"; 35 | case eLeapRS_CannotOpenDevice: return "Cannot Open Device"; 36 | default: return "Internal Error"; 37 | } 38 | } 39 | 40 | static void leap_log(const LEAP_LOG_EVENT* e) { 41 | switch(e->severity) { 42 | case eLeapLogSeverity_Unknown: log_notice("%s.\n", e->message); break; 43 | case eLeapLogSeverity_Critical: log_fatal("%s.\n", e->message); break; 44 | case eLeapLogSeverity_Warning: log_warn("%s.\n", e->message); break; 45 | // case eLeapLogSeverity_Information: log_info("%s.\n", e->message); break; 46 | } 47 | } 48 | 49 | static void leap_event_loop(void *_) { 50 | log_debug("spinned up leap event loop.\n"); 51 | eLeapRS rslt; 52 | LEAP_CONNECTION_MESSAGE msg; 53 | 54 | while (_running) { 55 | UINT timeout = 1000; 56 | rslt = LeapPollConnection(_connection, timeout, &msg); 57 | 58 | if (rslt != eLeapRS_Success) { 59 | log_warn("LeapPollConnection: %s.\n", leap_result_string(rslt)); 60 | continue; 61 | } 62 | 63 | switch (msg.type){ 64 | case eLeapEventType_Connection: 65 | _connected = TRUE; 66 | if (_conn_cb != NULL) _conn_cb(TRUE); 67 | break; 68 | case eLeapEventType_ConnectionLost: 69 | _connected = FALSE; 70 | if (_conn_cb != NULL) _conn_cb(FALSE); 71 | break; 72 | case eLeapEventType_Device: { 73 | _device_connected = TRUE; 74 | if (_devconn_cb != NULL) _devconn_cb(TRUE); 75 | LEAP_DEVICE_INFO device_info; 76 | LEAP_DEVICE device; 77 | 78 | rslt = LeapOpenDevice(msg.device_event->device, &device); 79 | 80 | if (rslt != eLeapRS_Success) { 81 | log_error("LeapOpenDevice: %s\n", leap_result_string(rslt)); 82 | goto device_handle_end; 83 | } 84 | 85 | device_info.size = sizeof(LEAP_DEVICE_INFO); 86 | device_info.serial_length = 1; 87 | device_info.serial = malloc(1); 88 | 89 | rslt = LeapGetDeviceInfo(device, &device_info); 90 | 91 | if (rslt == eLeapRS_Success) log_info("leap device %s connected.\n", device_info.serial); 92 | else if (rslt == eLeapRS_InsufficientBuffer) { 93 | device_info.serial = realloc(device_info.serial, device_info.serial_length); 94 | rslt = LeapGetDeviceInfo(device, &device_info); 95 | 96 | if (rslt != eLeapRS_Success) { 97 | log_error("LeapGetDeviceInfo: %s\n", leap_result_string(rslt)); 98 | goto device_handle_end; 99 | } 100 | } 101 | device_handle_end: 102 | free(device_info.serial); 103 | LeapCloseDevice(device); // this closes the handler for device, not the device itself. 104 | break; 105 | } 106 | case eLeapEventType_DeviceLost: 107 | _device_connected = FALSE; 108 | if (_devconn_cb != NULL) _devconn_cb(FALSE); 109 | log_warn("leap device lost.\n"); 110 | break; 111 | case eLeapEventType_DeviceFailure: 112 | _device_connected = FALSE; 113 | if (_devconn_cb != NULL) _devconn_cb(FALSE); 114 | log_warn("leap device failure.\n"); 115 | break; 116 | case eLeapEventType_Tracking: 117 | if (_track_cb != NULL) _track_cb(msg.tracking_event); 118 | break; 119 | case eLeapEventType_ImageComplete: 120 | break; 121 | case eLeapEventType_ImageRequestError: 122 | break; 123 | case eLeapEventType_LogEvent: 124 | leap_log(msg.log_event); 125 | break; 126 | case eLeapEventType_Policy: 127 | break; 128 | case eLeapEventType_ConfigChange: 129 | break; 130 | case eLeapEventType_ConfigResponse: 131 | break; 132 | case eLeapEventType_Image: 133 | break; 134 | case eLeapEventType_PointMappingChange: 135 | break; 136 | case eLeapEventType_LogEvents: 137 | for (uint32_t i = 0; i < msg.log_events->nEvents; i++) { 138 | leap_log(&(msg.log_events->events[i])); 139 | } 140 | break; 141 | case eLeapEventType_HeadPose: 142 | break; 143 | default: 144 | break; 145 | } //switch msg.type 146 | 147 | } // while _running 148 | 149 | log_debug("leap event loop stopped.\n"); 150 | } 151 | 152 | BOOL leap_is_connected() { 153 | return _connected; 154 | } 155 | 156 | BOOL leap_is_device_connected() { 157 | return _device_connected; 158 | } 159 | 160 | BOOL leap_connect(leap_connect_callback_t cb) { 161 | if (_running || _connection != NULL) return FALSE; 162 | _running = TRUE; 163 | _conn_cb = cb; 164 | eLeapRS rslt; 165 | 166 | rslt = LeapCreateConnection(NULL, &_connection); 167 | if (rslt != eLeapRS_Success) { 168 | log_error("LeapCreateConnection: %s\n", leap_result_string(rslt)); 169 | goto fail; 170 | } 171 | 172 | rslt = LeapOpenConnection(_connection); 173 | if (rslt != eLeapRS_Success) { 174 | log_error("LeapOpenConnection: %s\n", leap_result_string(rslt)); 175 | goto fail; 176 | } 177 | 178 | _polling_thread = (HANDLE) _beginthread(leap_event_loop, 0, NULL); 179 | return TRUE; 180 | 181 | fail: 182 | LeapDestroyConnection(_connection); 183 | _connection = NULL; 184 | _running = FALSE; 185 | return FALSE; 186 | } 187 | 188 | BOOL leap_disconnect() { 189 | if (!_running || _connection == NULL) return FALSE; 190 | 191 | _running = FALSE; 192 | WaitForSingleObject(_polling_thread, INFINITE); 193 | CloseHandle(_polling_thread); 194 | LeapCloseConnection(_connection); 195 | LeapDestroyConnection(_connection); 196 | _connection = NULL; 197 | 198 | return TRUE; 199 | } 200 | 201 | void leap_set_tracking_handler(leap_tracking_callback_t cb) { 202 | _track_cb = cb; 203 | } 204 | 205 | void leap_unset_tracking_handler() { 206 | _track_cb = NULL; 207 | } 208 | 209 | void leap_join_thread() { 210 | WaitForSingleObject(_polling_thread, INFINITE); 211 | } -------------------------------------------------------------------------------- /leapio/leapio.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "LeapC.h" 5 | 6 | typedef void (*leap_connect_callback_t)(BOOL connected); 7 | typedef void (*leap_tracking_callback_t)(const LEAP_TRACKING_EVENT *ev); 8 | 9 | /** 10 | * @brief check if connection to leap service is established. 11 | * 12 | * @return BOOL 13 | */ 14 | BOOL leap_is_connected(); 15 | 16 | /** 17 | * @brief check if leap device is connected. 18 | * 19 | * @return BOOL 20 | */ 21 | BOOL leap_is_device_connected(); 22 | 23 | /** 24 | * @brief connects to a leap service. 25 | * 26 | * @param cb callback on device connect/disconnect. 27 | * 28 | * Note that successfully establishing a connection with leap service does not 29 | * necessarily mean that we are ready to get hand tracking data from the leap 30 | * controller. In fact, there might not even be any leap controller attached to 31 | * the device. So, NO tracking data will be sent before device_connect callback. 32 | * 33 | * leapio will try to re-connect if the disconnect was not caused by a 34 | * leap_disconnect call. 35 | * @return BOOL TRUE on success, FALSE otherwise. 36 | */ 37 | BOOL leap_connect(leap_connect_callback_t cb); 38 | 39 | /** 40 | * @brief disconnect from the leap service. 41 | * 42 | * @return BOOL TRUE on success, FALSE otherwise. 43 | */ 44 | BOOL leap_disconnect(); 45 | 46 | /** 47 | * @brief set/update callback for tracking events. 48 | * 49 | * This call only registers the callback. You may call this before leap_connect. 50 | * You may set callback to NULL. 51 | * 52 | * @param cb tracking event callback. 53 | */ 54 | void leap_set_tracking_handler(leap_tracking_callback_t cb); 55 | 56 | /** 57 | * @brief set/update callback for device connect/disconnect. 58 | * 59 | * @param cb 60 | */ 61 | void leap_set_device_ready_handler(leap_connect_callback_t cb); 62 | 63 | /** 64 | * @brief join the event loop thread. 65 | * 66 | */ 67 | void leap_join_thread(); -------------------------------------------------------------------------------- /leapio/meson.build: -------------------------------------------------------------------------------- 1 | leapio_lib = static_library( 2 | 'leapio', 3 | name_prefix : '', 4 | include_directories : [inc, leap_inc], 5 | implicit_include_directories : false, 6 | c_pch : '../precompiled.h', 7 | dependencies: leap_lib, 8 | sources : [ 9 | 'leapio.c', 10 | 'leapio.h' 11 | ], 12 | ) 13 | -------------------------------------------------------------------------------- /log.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #define log_debug(fmt, ...) __log("DEBUG", fmt, ## __VA_ARGS__) 4 | #define log_info(fmt, ...) __log("INFO ", fmt, ## __VA_ARGS__) 5 | #define log_notice(fmt, ...) __log("NOTE ", fmt, ## __VA_ARGS__) 6 | #define log_warn(fmt, ...) __log("WARN ", fmt, ## __VA_ARGS__) 7 | #define log_error(fmt, ...) __log("ERROR", fmt, ## __VA_ARGS__) 8 | #define log_fatal(fmt, ...) __log("FATAL", fmt, ## __VA_ARGS__) 9 | #define __log(log_level, fmt, ...) printf("[" log_level "] %s: " fmt, __FUNCTION__, ## __VA_ARGS__) 10 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('chunithm-touch', 'c', version: '0.4.0', default_options: ['warning_level=2']) 2 | 3 | add_project_arguments( 4 | '-DCOBJMACROS', 5 | '-DDIRECTINPUT_VERSION=0x0800', 6 | '-DWIN32_LEAN_AND_MEAN', 7 | '-D_WIN32_WINNT=_WIN32_WINNT_WIN8', 8 | '-DMINGW_HAS_SECURE_API=1', 9 | language: 'c', 10 | ) 11 | 12 | if meson.get_compiler('c').get_id() != 'msvc' 13 | add_project_arguments( 14 | '-ffunction-sections', 15 | '-fdata-sections', 16 | language: 'c', 17 | ) 18 | 19 | add_project_link_arguments( 20 | '-Wl,--exclude-all-symbols', 21 | '-Wl,--gc-sections', 22 | '-static-libgcc', 23 | language: 'c', 24 | ) 25 | endif 26 | 27 | cc = meson.get_compiler('c') 28 | 29 | inc = include_directories('.') 30 | 31 | leap_inc = include_directories('3rdparty/LeapSDK/include') 32 | arch = 'x86' 33 | if host_machine.cpu_family() == 'x86_64' 34 | arch = 'x64' 35 | endif 36 | leap_lib_path = meson.source_root() + '/3rdparty/LeapSDK/lib/' + arch 37 | leap_lib = cc.find_library('LeapC', dirs: leap_lib_path) 38 | d2d1_lib = cc.find_library('d2d1') 39 | 40 | capnhook = subproject('capnhook') 41 | 42 | subdir('leapio') 43 | subdir('leap-configurator') 44 | subdir('chuniio') 45 | subdir('create-touch-window') 46 | -------------------------------------------------------------------------------- /precompiled.h: -------------------------------------------------------------------------------- 1 | // Note: Copy-Paste from segatools. 2 | 3 | /* Win32 user-mode API */ 4 | #define WIN32_NO_STATUS 5 | #include 6 | #undef WIN32_NO_STATUS 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | /* Win32 kernel-mode definitions */ 17 | #ifdef __GNUC__ 18 | /* MinGW needs to include this for PHYSICAL_ADDRESS to be defined. 19 | The MS SDK throws a bunch of duplicate symbol errors instead. */ 20 | #include 21 | #else 22 | #include 23 | #endif 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | chuni-touch 2 | --- 3 | 4 | `chuni-touch` adds touchscreen support and Leap Motion for Chunithm. For touch, It works by hooking `CreateWindowExA()` and `CreateWindowExW()` and do `RegisterTouchWindow()` on every window created by Chunithm (with `ctw.dll`). It then takes over Chunithm's `WindowProc` and handles the touch input there (with `chuniio.dll`). 5 | 6 | ### Installation 7 | 8 | `chuni-touch` requires `segatools` to work. If you are using some other tools to launch Chunithm, you are on your own. 9 | 10 | If you are using `segatools`: 11 | 12 | 1. Download `chuni-touch.zip` from the [release](https://github.com/Nat-Lab/chunithm-touch/releases) page. 13 | 2. Unzip `chuni-touch.zip`, copy everything in it to the `bin` folder of your game. Override any file that already exists. You may want to make a backup of your `bin` folder. 14 | 3. (Optional) If you plan to use a Leap Motion for AIR and AIR-actions, run `leapconfig.exe` to configure your Leap Motion controller. You may configure the controller manually in `chunitouch.ini` too. 15 | 16 | ### Usage 17 | 18 | Just tap/slide on the screen. Slide up to simulate the IR sensor if you are using touch-bashed IR simulation. Raise your hand as if you were playing on the real arcade to simulate the IR sensor if you are using a Leap controller. A video demo of how touch controls work is available [here](https://youtu.be/Uknwet_-wWw). Use F1, F2, and F3 for test, service, and to insert coin. 19 | 20 | ### Configuration 21 | 22 | Settings will be read from `chunitouch.ini`. Here's a list of configurable options: 23 | 24 | ``` 25 | [options] 26 | ; use a separate window for touch input. slider.offset will be ignore if 27 | ; enabled. 28 | separate_control = 0 29 | 30 | [ir] 31 | ; source of control. 'touch' for touchscreen and 'leap' for leap motion. 32 | control_source = touch 33 | ; height of each touch IR sensor (unit: pixel) 34 | touch_height = 50 35 | ; touch IR trigger threshold (number of pixels required to move up for a move to 36 | ; be registered as air) 37 | touch_trigger = 70 38 | ; specifies the axis to track hands on. x, y, z, -x, -y, or -z. 39 | leap_orientation = y 40 | ; the minimum height of your hand(s) need to be for it to be registered as air 41 | ; (unit: millimeters) 42 | leap_trigger = 100 43 | ; the height of each virtual IR sensor (unit: millimeters) 44 | leap_step = 30 45 | 46 | [slider] 47 | ; slider's width (unit: pixel) 48 | width = 40 49 | ; slider's x-offset (pixels from the left of the screen) 50 | offset = 318 51 | 52 | [io] 53 | ; use raw input 54 | raw_input = 0 55 | ; show Windows touch feedback 56 | touch_feedback = 0 57 | 58 | [misc] 59 | ; keep slider(s) holded while on air-action 60 | ir_keep_slider = 0 61 | ``` 62 | 63 | ### Building 64 | 65 | To build chuni-touch, you will need to get the Leap Motion standard Orion SDK package from [Leap Motion developer site](http://developer.leapmotion.com). Unzip the SDK package and copy the `LeapSDK` folder to `3rdparty/`. 66 | 67 | You may build `chuni-touch` on with any operating system that can run MinGW-w64. On Windows: 68 | 69 | ``` 70 | > meson build 71 | > ninja -C build 72 | ``` 73 | 74 | On Unix-like: 75 | 76 | ``` 77 | $ meson --cross cross-build-32.txt build32 78 | $ ninja -C build32 79 | $ meson --cross cross-build-64.txt build64 80 | $ ninja -C build64 81 | ``` 82 | 83 | Or, if you are using Windows and have Visual Studio installed, you may build it with Visual Studio: 84 | 85 | ``` 86 | > meson --backend vs build 87 | > msbuild build\chunithm-touch.sln 88 | ``` 89 | 90 | ### License 91 | UNLICENSE -------------------------------------------------------------------------------- /subprojects/capnhook.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | directory = capnhook 3 | url = https://github.com/decafcode/capnhook 4 | revision = 69f7e3b48c2e0ff5be1d7a83cdcc2597a458357b 5 | --------------------------------------------------------------------------------