1#include <windows.h>
2#include <windowsx.h>
3
4#include "maus.h"
5#include "maus_input.h"
6#include "maus_win.h"
7
8typedef struct {
9 UINT vk;
10 MausKey maus;
11} KeyMapEntry;
12
13static int8_t fb_create(Maus* mw);
14static void fb_destroy(Maus* mw);
15static int8_t handle_event(const MSG* msg, MausEvent* ev, Maus* mw);
16static MausKey vk_to_mauskey(UINT vk);
17static UINT resolve_lr(UINT vk, LPARAM lparam);
18static char translate_text(UINT vk, UINT scan);
19static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp);
20
21static const KeyMapEntry keymap[] = {
22 { VK_BACK, MAUS_KEY_BACKSPACE }, { VK_TAB, MAUS_KEY_TAB },
23 { VK_RETURN, MAUS_KEY_ENTER }, { VK_ESCAPE, MAUS_KEY_ESCAPE },
24 { VK_SPACE, MAUS_KEY_SPACE }, { VK_DELETE, MAUS_KEY_DELETE },
25 { VK_LEFT, MAUS_KEY_LEFT }, { VK_RIGHT, MAUS_KEY_RIGHT },
26 { VK_UP, MAUS_KEY_UP }, { VK_DOWN, MAUS_KEY_DOWN },
27 { VK_HOME, MAUS_KEY_HOME }, { VK_END, MAUS_KEY_END },
28 { VK_PRIOR, MAUS_KEY_PAGE_UP }, { VK_NEXT, MAUS_KEY_PAGE_DOWN },
29 { VK_INSERT, MAUS_KEY_INSERT }, { VK_CAPITAL, MAUS_KEY_CAPS_LOCK },
30 { VK_NUMLOCK, MAUS_KEY_NUM_LOCK }, { VK_SCROLL, MAUS_KEY_SCROLL_LOCK },
31 { VK_PAUSE, MAUS_KEY_PAUSE }, { VK_SNAPSHOT, MAUS_KEY_PRINT_SCREEN },
32 { VK_APPS, MAUS_KEY_MENU }, { VK_LSHIFT, MAUS_KEY_SHIFT_L },
33 { VK_RSHIFT, MAUS_KEY_SHIFT_R }, { VK_LCONTROL, MAUS_KEY_CONTROL_L },
34 { VK_RCONTROL, MAUS_KEY_CONTROL_R }, { VK_LMENU, MAUS_KEY_ALT_L },
35 { VK_RMENU, MAUS_KEY_ALT_R }, { VK_LWIN, MAUS_KEY_SUPER_L },
36 { VK_RWIN, MAUS_KEY_SUPER_R }, { VK_ADD, MAUS_KEY_KP_ADD },
37 { VK_SUBTRACT, MAUS_KEY_KP_SUBTRACT },{ VK_MULTIPLY, MAUS_KEY_KP_MULTIPLY },
38 { VK_DIVIDE, MAUS_KEY_KP_DIVIDE }, { VK_DECIMAL, MAUS_KEY_KP_DECIMAL },
39};
40
41static int8_t fb_create(Maus* mw)
42{
43 MausBackend* be = &mw->backend;
44 BITMAPINFO bmi = {0};
45 HDC dc = GetDC(NULL);
46
47 be->hbm = NULL;
48 mw->fb = NULL;
49 mw->stride = 0;
50
51 bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
52 bmi.bmiHeader.biWidth = mw->width;
53 bmi.bmiHeader.biHeight = -mw->height;
54 bmi.bmiHeader.biPlanes = 1;
55 bmi.bmiHeader.biBitCount = 32;
56 bmi.bmiHeader.biCompression = BI_RGB;
57
58 be->memdc = CreateCompatibleDC(dc);
59 be->hbm = CreateDIBSection(dc, &bmi, DIB_RGB_COLORS, (void**)&mw->fb, NULL, 0);
60 ReleaseDC(NULL, dc);
61
62 if (!be->hbm || !mw->fb) {
63 if (be->memdc)
64 DeleteDC(be->memdc);
65 maus_log(stderr, "CreateDIBSection failed");
66 return 0;
67 }
68
69 SelectObject(be->memdc, be->hbm);
70
71 mw->stride = mw->width;
72 return 1;
73}
74
75static void fb_destroy(Maus* mw)
76{
77 MausBackend* be = &mw->backend;
78
79 if (be->memdc) {
80 DeleteDC(be->memdc);
81 be->memdc = NULL;
82 }
83 if (be->hbm) {
84 DeleteObject(be->hbm);
85 be->hbm = NULL;
86 }
87
88 mw->fb = NULL;
89 mw->stride = 0;
90}
91
92static int8_t handle_event(const MSG* msg, MausEvent* ev, Maus* mw)
93{
94 UINT scan;
95 UINT vk;
96 int delta;
97 MausMouseButton mbtype;
98
99 switch (msg->message) {
100 case WM_QUIT:
101 ev->type = MAUS_EV_CLOSE;
102 return 1;
103
104 case WM_MOUSEMOVE:
105 ev->type = MAUS_EV_MOUSE_MOTION;
106 ev->mouse.motion.x = GET_X_LPARAM(msg->lParam);
107 ev->mouse.motion.y = GET_Y_LPARAM(msg->lParam);
108 return 1;
109
110 case WM_LBUTTONDOWN:
111 ev->type = MAUS_EV_MOUSE_BUTTON;
112 ev->mouse.button.button = MAUS_MOUSE_BUTTON_LEFT;
113 ev->mouse.button.pressed = 1;
114 mw->mouse_buttons[MAUS_MOUSE_BUTTON_LEFT] = 1;
115 return 1;
116 case WM_LBUTTONUP:
117 ev->type = MAUS_EV_MOUSE_BUTTON;
118 ev->mouse.button.button = MAUS_MOUSE_BUTTON_LEFT;
119 ev->mouse.button.pressed = 0;
120 mw->mouse_buttons[MAUS_MOUSE_BUTTON_LEFT] = 0;
121 return 1;
122
123 case WM_RBUTTONDOWN:
124 ev->type = MAUS_EV_MOUSE_BUTTON;
125 ev->mouse.button.button = MAUS_MOUSE_BUTTON_RIGHT;
126 ev->mouse.button.pressed = 1;
127 mw->mouse_buttons[MAUS_MOUSE_BUTTON_RIGHT] = 1;
128 return 1;
129 case WM_RBUTTONUP:
130 ev->type = MAUS_EV_MOUSE_BUTTON;
131 ev->mouse.button.button = MAUS_MOUSE_BUTTON_RIGHT;
132 ev->mouse.button.pressed = 0;
133 mw->mouse_buttons[MAUS_MOUSE_BUTTON_RIGHT] = 0;
134 return 1;
135
136 case WM_MBUTTONDOWN:
137 ev->type = MAUS_EV_MOUSE_BUTTON;
138 ev->mouse.button.button = MAUS_MOUSE_BUTTON_MIDDLE;
139 ev->mouse.button.pressed = 1;
140 mw->mouse_buttons[MAUS_MOUSE_BUTTON_MIDDLE] = 1;
141 return 1;
142 case WM_MBUTTONUP:
143 ev->type = MAUS_EV_MOUSE_BUTTON;
144 ev->mouse.button.button = MAUS_MOUSE_BUTTON_MIDDLE;
145 ev->mouse.button.pressed = 0;
146 mw->mouse_buttons[MAUS_MOUSE_BUTTON_MIDDLE] = 0;
147 return 1;
148
149 case WM_MOUSEWHEEL:
150 delta = GET_WHEEL_DELTA_WPARAM(msg->wParam);
151 mbtype = (delta > 0) ? MAUS_MOUSE_BUTTON_SCROLL_UP
152 : MAUS_MOUSE_BUTTON_SCROLL_DOWN;
153 ev->type = MAUS_EV_MOUSE_BUTTON;
154 ev->mouse.button.button = mbtype;
155 ev->mouse.button.pressed = 1;
156 return 1;
157
158 case WM_KEYDOWN:
159 case WM_SYSKEYDOWN:
160 scan = (UINT)((msg->lParam >> 16) & 0xFF);
161 vk = resolve_lr((UINT)msg->wParam, msg->lParam);
162
163 ev->type = MAUS_EV_KEY;
164 ev->key.code = (uint32_t)msg->wParam;
165 ev->key.pressed = 1;
166 ev->key.key = vk_to_mauskey(vk);
167 ev->key.text = translate_text((UINT)msg->wParam, scan);
168
169 if (ev->key.code < MAUS_KEYCODE_LAST)
170 mw->key_codes[ev->key.code] = 1;
171 if (ev->key.key != MAUS_KEY_NONE)
172 mw->key_syms[ev->key.key] = 1;
173 return 1;
174
175 case WM_KEYUP:
176 case WM_SYSKEYUP:
177 vk = resolve_lr((UINT)msg->wParam, msg->lParam);
178
179 ev->type = MAUS_EV_KEY;
180 ev->key.code = (uint32_t)msg->wParam;
181 ev->key.pressed = 0;
182 ev->key.key = vk_to_mauskey(vk);
183 ev->key.text = 0;
184
185 if (ev->key.code < MAUS_KEYCODE_LAST)
186 mw->key_codes[ev->key.code] = 0;
187 if (ev->key.key != MAUS_KEY_NONE)
188 mw->key_syms[ev->key.key] = 0;
189 return 1;
190 }
191
192 return 0;
193}
194
195static MausKey vk_to_mauskey(UINT vk)
196{
197 if (vk >= 'A' && vk <= 'Z')
198 return (MausKey)(MAUS_KEY_A + (vk - 'A'));
199 if (vk >= '0' && vk <= '9')
200 return (MausKey)(MAUS_KEY_0 + (vk - '0'));
201 if (vk >= VK_F1 && vk <= VK_F12)
202 return (MausKey)(MAUS_KEY_F1 + (vk - VK_F1));
203 if (vk >= VK_NUMPAD0 && vk <= VK_NUMPAD9)
204 return (MausKey)(MAUS_KEY_KP_0 + (vk - VK_NUMPAD0));
205
206 for (size_t i = 0; i < sizeof(keymap) / sizeof(keymap[0]); i++) {
207 if (keymap[i].vk == vk)
208 return keymap[i].maus;
209 }
210
211 return MAUS_KEY_NONE;
212}
213
214static UINT resolve_lr(UINT vk, LPARAM lparam)
215{
216 UINT scan = (UINT)((lparam >> 16) & 0xFF);
217 int ext = (lparam & (1 << 24)) != 0;
218
219 if (vk == VK_SHIFT)
220 return MapVirtualKey(scan, MAPVK_VSC_TO_VK_EX);
221 if (vk == VK_CONTROL)
222 return ext ? VK_RCONTROL : VK_LCONTROL;
223 if (vk == VK_MENU)
224 return ext ? VK_RMENU : VK_LMENU;
225
226 return vk;
227}
228
229static char translate_text(UINT vk, UINT scan)
230{
231 BYTE state[256];
232 WCHAR buf[4];
233 int n;
234
235 n = ToUnicode(vk, scan, state, buf, 4, 0);
236
237 if (!GetKeyboardState(state))
238 return 0;
239 if (n == 1 && buf[0] < 0x80)
240 return (buf[0] == '\r') ? '\n' : (char)buf[0];
241
242 return 0;
243}
244
245static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
246{
247 Maus* mw;
248 MausBackend* be;
249
250 if (msg == WM_DESTROY) {
251 PostQuitMessage(EXIT_SUCCESS);
252 return 0;
253 }
254
255 if (msg == WM_SIZE) {
256 mw = (Maus*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
257 if (mw) {
258 be = &mw->backend;
259 be->resized = 1;
260 be->rw = LOWORD(lp);
261 be->rh = HIWORD(lp);
262 }
263 return 0;
264 }
265
266 return DefWindowProc(hwnd, msg, wp, lp);
267}
268
269void maus_clear(Maus* mw, MausColor col)
270{
271 uint32_t col_up = MAUS_UNPACK_COL(col);
272 uint32_t pxs = mw->height * mw->width;
273 uint32_t i;
274
275 for (i = 0; i < pxs; i++)
276 mw->bfb[i] = col_up;
277}
278
279void maus_clipboard_set_text(Maus* mw, const char* text)
280{
281 MausBackend* be = &mw->backend;
282 size_t len = strlen(text) + 1;
283 HGLOBAL hmem;
284
285 void* ptr;
286
287 if (!text || !OpenClipboard(be->hwnd))
288 return;
289
290 EmptyClipboard();
291 hmem = GlobalAlloc(GMEM_MOVEABLE, len);
292 if (hmem) {
293 ptr = GlobalLock(hmem);
294 if (ptr) {
295 memcpy(ptr, text, len);
296 GlobalUnlock(hmem);
297
298 if (!SetClipboardData(CF_TEXT, hmem))
299 GlobalFree(hmem); /* failed */
300 }
301 else {
302 GlobalFree(hmem);
303 }
304 }
305
306 CloseClipboard();
307}
308
309char* maus_clipboard_get_text(Maus* mw)
310{
311 MausBackend* be = &mw->backend;
312 HANDLE hmem;
313
314 const char* text;
315 size_t len;
316
317 if (!OpenClipboard(be->hwnd))
318 return NULL;
319
320 hmem = GetClipboardData(CF_TEXT);
321 if (!hmem) {
322 CloseClipboard();
323 return NULL;
324 }
325
326 text = GlobalLock(hmem);
327 if (text) {
328 if (mw->clipboard) {
329 free(mw->clipboard);
330 mw->clipboard = NULL;
331 }
332
333 len = strlen(text) + 1;
334 mw->clipboard = malloc(len);
335 if (mw->clipboard)
336 memcpy(mw->clipboard, text, len);
337
338 GlobalUnlock(hmem);
339 }
340
341 CloseClipboard();
342 return mw->clipboard;
343}
344
345void maus_close(Maus* mw)
346{
347 MausBackend* be = &mw->backend;
348
349 fb_destroy(mw);
350 if (mw->bfb) {
351 free(mw->bfb);
352 mw->bfb = NULL;
353 }
354 if (mw->clipboard) {
355 free(mw->clipboard);
356 mw->clipboard = NULL;
357 }
358 if (be->hwnd)
359 (void) maus_close_window(mw);
360}
361
362int8_t maus_close_window(Maus* mw)
363{
364 MausBackend* be = &mw->backend;
365
366 if (be->hwnd) {
367 DestroyWindow(be->hwnd);
368 be->hwnd = NULL;
369 return 1;
370 }
371
372 return 0;
373}
374
375
376int8_t maus_create_window(Maus* mw)
377{
378 MausBackend* be = &mw->backend;
379 be->hInst = GetModuleHandle(NULL);
380 WNDCLASS wc = {0};
381
382 RECT r = { 0, 0, mw->width, mw->height };
383
384 wc.lpfnWndProc = WndProc;
385 wc.hInstance = be->hInst;
386 wc.lpszClassName = "MausWindow";
387 wc.hCursor = LoadCursor(NULL, IDC_ARROW);
388 if (!RegisterClass(&wc))
389 return 0;
390
391 AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, FALSE);
392
393 be->hwnd = CreateWindowEx(
394 0, "MausWindow", mw->title,
395 WS_OVERLAPPEDWINDOW, mw->x, mw->y,
396 r.right - r.left, r.bottom - r.top,
397 NULL, NULL, be->hInst, NULL
398 );
399
400 if (!be->hwnd)
401 return 0;
402
403 SetWindowLongPtr(be->hwnd, GWLP_USERDATA, (LONG_PTR)mw);
404 ShowWindow(be->hwnd, SW_SHOW);
405 UpdateWindow(be->hwnd);
406 return 1;
407}
408
409Maus* maus_init(const char* title, int x, int y, int width, int height)
410{
411 Maus* mw = calloc(1, sizeof(Maus));
412 MausBackend* be = &mw->backend;
413
414 mw->frame_time_last = maus_get_time_ns();
415 mw->title = title;
416 mw->width = width;
417 mw->height = height;
418 mw->x = x;
419 mw->y = y;
420 be->hInst = GetModuleHandle(NULL);
421 be->hwnd = NULL;
422 be->hbm = NULL;
423
424 if (!fb_create(mw)) {
425 maus_log(stderr, "failed to create framebuffer");
426 free(mw);
427 return NULL;
428 }
429
430 mw->bfb = calloc(mw->stride * mw->height, sizeof(uint32_t));
431 if (!mw->bfb) {
432 maus_log(stderr, "failed to allocate back buffer");
433 maus_close(mw);
434 free(mw);
435 return NULL;
436 }
437
438 return mw;
439}
440
441int8_t maus_event_poll(Maus* mw, MausEvent* ev)
442{
443 MausBackend* be = &mw->backend;
444 MSG msg;
445
446 while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
447 TranslateMessage(&msg);
448 DispatchMessage(&msg);
449 ev->type = MAUS_EV_NONE;
450
451 if (handle_event(&msg, ev, mw))
452 return 1;
453 }
454
455 /* WM_SIZE doesnt is handled separately */
456 if (be->resized) {
457 be->resized = 0;
458 ev->type = MAUS_EV_RESIZE;
459 ev->resize.width = be->rw;
460 ev->resize.height = be->rh;
461 return 1;
462 }
463
464 return 0;
465}
466
467void maus_event_wait(Maus* mw, MausEvent* ev)
468{
469 MausBackend* be = &mw->backend;
470 MSG msg;
471
472 int r;
473
474 for (;;) {
475 r = GetMessage(&msg, NULL, 0, 0);
476 if (r <= 0) {
477 ev->type = MAUS_EV_CLOSE;
478 return;
479 }
480
481 TranslateMessage(&msg);
482 DispatchMessage(&msg);
483 ev->type = MAUS_EV_NONE;
484
485 /* WM_SIZE doesnt is handled separately */
486 if (be->resized) {
487 be->resized = 0;
488 ev->type = MAUS_EV_RESIZE;
489 ev->resize.width = be->rw;
490 ev->resize.height = be->rh;
491 return;
492 }
493
494 if (handle_event(&msg, ev, mw))
495 return;
496 }
497}
498
499void maus_present(Maus* mw)
500{
501 MausBackend* be = &mw->backend;
502 HDC wdc;
503
504 uint32_t bytes;
505
506 if (!be->hwnd || !be->memdc)
507 return;
508
509 wdc = GetDC(be->hwnd);
510
511 bytes = mw->stride * mw->height * sizeof(uint32_t);
512 memcpy(mw->fb, mw->bfb, bytes);
513
514 BitBlt(wdc, 0, 0, mw->width, mw->height, be->memdc, 0, 0, SRCCOPY);
515 ReleaseDC(be->hwnd, wdc);
516}
517
518int8_t maus_resize(Maus* mw, uint32_t width, uint32_t height)
519{
520 if (width == 0 || height == 0 ||
521 (mw->width == width && mw->height == height))
522 return 0;
523
524 fb_destroy(mw);
525 if (mw->bfb) {
526 free(mw->bfb);
527 mw->bfb = NULL;
528 }
529
530 mw->width = width;
531 mw->height = height;
532
533 if (!fb_create(mw))
534 return 0;
535
536 mw->bfb = calloc(mw->stride * mw->height, sizeof(uint32_t));
537 if (!mw->bfb)
538 return 0;
539
540 return 1;
541}
542
543void maus_cur_set_mode(Maus* mw, MausCursorState state)
544{
545 MausBackend* be = &mw->backend;
546 HWND hwnd = be->hwnd;
547
548 RECT r;
549
550 if (!hwnd) {
551 maus_log(stderr, "hwnd is NULL");
552 return;
553 }
554
555 /* show cursor */
556 if (state == MAUS_CURSOR_STATE_VISIBLE) {
557 while (ShowCursor(TRUE) < 0);
558 return;
559 }
560
561 /* hide cursor */
562 if (state == MAUS_CURSOR_STATE_HIDDEN) {
563 while (ShowCursor(FALSE) >= 0);
564 return;
565 }
566
567
568 /* lock cursor */
569 if (state == MAUS_CURSOR_STATE_LOCKED) {
570 GetClientRect(hwnd, &r);
571 MapWindowPoints(hwnd, NULL, (POINT*)&r, 2);
572
573 if (!ClipCursor(&r)) {
574 maus_log(stderr, "failed to grab mouse pointer");
575 return;
576 }
577
578 return;
579 }
580
581 /* unlock cursor */
582 if (state == MAUS_CURSOR_STATE_FREE) {
583 ClipCursor(NULL);
584 return;
585 }
586}
587