├── .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 |
--------------------------------------------------------------------------------