1#include <windows.h>
2#include <windowsx.h>
3
4#include "maus.h"
5#include "maus_input.h"
6#include "maus_windows.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 size_t i;
198
199 if (vk >= 'A' && vk <= 'Z')
200 return (MausKey)(MAUS_KEY_A + (vk - 'A'));
201 if (vk >= '0' && vk <= '9')
202 return (MausKey)(MAUS_KEY_0 + (vk - '0'));
203 if (vk >= VK_F1 && vk <= VK_F12)
204 return (MausKey)(MAUS_KEY_F1 + (vk - VK_F1));
205 if (vk >= VK_NUMPAD0 && vk <= VK_NUMPAD9)
206 return (MausKey)(MAUS_KEY_KP_0 + (vk - VK_NUMPAD0));
207
208 for (i = 0; i < sizeof(keymap) / sizeof(keymap[0]); i++) {
209 if (keymap[i].vk == vk)
210 return keymap[i].maus;
211 }
212
213 return MAUS_KEY_NONE;
214}
215
216static UINT resolve_lr(UINT vk, LPARAM lparam)
217{
218 UINT scan = (UINT)((lparam >> 16) & 0xFF);
219 int ext = (lparam & (1 << 24)) != 0;
220
221 if (vk == VK_SHIFT)
222 return MapVirtualKey(scan, MAPVK_VSC_TO_VK_EX);
223 if (vk == VK_CONTROL)
224 return ext ? VK_RCONTROL : VK_LCONTROL;
225 if (vk == VK_MENU)
226 return ext ? VK_RMENU : VK_LMENU;
227
228 return vk;
229}
230
231static char translate_text(UINT vk, UINT scan)
232{
233 BYTE state[256];
234 WCHAR buf[4];
235 int n;
236
237 n = ToUnicode(vk, scan, state, buf, 4, 0);
238
239 if (!GetKeyboardState(state))
240 return 0;
241 if (n == 1 && buf[0] < 0x80)
242 return (buf[0] == '\r') ? '\n' : (char)buf[0];
243
244 return 0;
245}
246
247static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
248{
249 Maus* mw;
250 MausBackend* be;
251
252 if (msg == WM_DESTROY) {
253 PostQuitMessage(EXIT_SUCCESS);
254 return 0;
255 }
256
257 if (msg == WM_SIZE) {
258 mw = (Maus*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
259 if (mw) {
260 be = &mw->backend;
261 be->resized = 1;
262 be->rw = LOWORD(lp);
263 be->rh = HIWORD(lp);
264 }
265 return 0;
266 }
267
268 return DefWindowProc(hwnd, msg, wp, lp);
269}
270
271void maus_clear(Maus* mw, MausColor col)
272{
273 uint32_t col_up = MAUS_UNPACK_COL(col);
274 uint32_t pxs = mw->height * mw->width;
275 uint32_t i;
276
277 for (i = 0; i < pxs; i++)
278 mw->bfb[i] = col_up;
279}
280
281void maus_clipboard_set_text(Maus* mw, const char* text)
282{
283 MausBackend* be = &mw->backend;
284 size_t len = strlen(text) + 1;
285 HGLOBAL hmem;
286
287 void* ptr;
288
289 if (!text || !OpenClipboard(be->hwnd))
290 return;
291
292 EmptyClipboard();
293 hmem = GlobalAlloc(GMEM_MOVEABLE, len);
294 if (hmem) {
295 ptr = GlobalLock(hmem);
296 if (ptr) {
297 memcpy(ptr, text, len);
298 GlobalUnlock(hmem);
299
300 if (!SetClipboardData(CF_TEXT, hmem))
301 GlobalFree(hmem); /* failed */
302 }
303 else {
304 GlobalFree(hmem);
305 }
306 }
307
308 CloseClipboard();
309}
310
311char* maus_clipboard_get_text(Maus* mw)
312{
313 MausBackend* be = &mw->backend;
314 HANDLE hmem;
315
316 const char* text;
317 size_t len;
318
319 if (!OpenClipboard(be->hwnd))
320 return NULL;
321
322 hmem = GetClipboardData(CF_TEXT);
323 if (!hmem) {
324 CloseClipboard();
325 return NULL;
326 }
327
328 text = GlobalLock(hmem);
329 if (text) {
330 if (mw->clipboard) {
331 free(mw->clipboard);
332 mw->clipboard = NULL;
333 }
334
335 len = strlen(text) + 1;
336 mw->clipboard = malloc(len);
337 if (mw->clipboard)
338 memcpy(mw->clipboard, text, len);
339
340 GlobalUnlock(hmem);
341 }
342
343 CloseClipboard();
344 return mw->clipboard;
345}
346
347void maus_close(Maus* mw)
348{
349 MausBackend* be = &mw->backend;
350
351 fb_destroy(mw);
352 if (mw->bfb) {
353 free(mw->bfb);
354 mw->bfb = NULL;
355 }
356 if (mw->clipboard) {
357 free(mw->clipboard);
358 mw->clipboard = NULL;
359 }
360 if (be->hwnd)
361 (void) maus_close_window(mw);
362}
363
364int8_t maus_close_window(Maus* mw)
365{
366 MausBackend* be = &mw->backend;
367
368 if (be->hwnd) {
369 DestroyWindow(be->hwnd);
370 be->hwnd = NULL;
371 return 1;
372 }
373
374 return 0;
375}
376
377
378int8_t maus_create_window(Maus* mw)
379{
380 MausBackend* be = &mw->backend;
381 be->hInst = GetModuleHandle(NULL);
382 WNDCLASS wc = {0};
383
384 RECT r = { 0, 0, mw->width, mw->height };
385
386 wc.lpfnWndProc = WndProc;
387 wc.hInstance = be->hInst;
388 wc.lpszClassName = "MausWindow";
389 wc.hCursor = LoadCursor(NULL, IDC_ARROW);
390 if (!RegisterClass(&wc))
391 return 0;
392
393 AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, FALSE);
394
395 be->hwnd = CreateWindowEx(
396 0, "MausWindow", mw->title,
397 WS_OVERLAPPEDWINDOW, mw->x, mw->y,
398 r.right - r.left, r.bottom - r.top,
399 NULL, NULL, be->hInst, NULL
400 );
401
402 if (!be->hwnd)
403 return 0;
404
405 SetWindowLongPtr(be->hwnd, GWLP_USERDATA, (LONG_PTR)mw);
406 ShowWindow(be->hwnd, SW_SHOW);
407 UpdateWindow(be->hwnd);
408 return 1;
409}
410
411Maus* maus_init(const char* title, int x, int y, int width, int height)
412{
413 Maus* mw = calloc(1, sizeof(Maus));
414 MausBackend* be = &mw->backend;
415
416 mw->frame_time_last = maus_get_time_ns();
417 mw->title = title;
418 mw->width = width;
419 mw->height = height;
420 mw->x = x;
421 mw->y = y;
422 be->hInst = GetModuleHandle(NULL);
423 be->hwnd = NULL;
424 be->hbm = NULL;
425
426 if (!fb_create(mw)) {
427 maus_log(stderr, "failed to create framebuffer");
428 free(mw);
429 return NULL;
430 }
431
432 mw->bfb = calloc(mw->stride * mw->height, sizeof(uint32_t));
433 if (!mw->bfb) {
434 maus_log(stderr, "failed to allocate back buffer");
435 maus_close(mw);
436 free(mw);
437 return NULL;
438 }
439
440 return mw;
441}
442
443int8_t maus_event_poll(Maus* mw, MausEvent* ev)
444{
445 MausBackend* be = &mw->backend;
446 MSG msg;
447
448 while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
449 TranslateMessage(&msg);
450 DispatchMessage(&msg);
451 ev->type = MAUS_EV_NONE;
452
453 if (handle_event(&msg, ev, mw))
454 return 1;
455 }
456
457 /* WM_SIZE doesnt is handled separately */
458 if (be->resized) {
459 be->resized = 0;
460 ev->type = MAUS_EV_RESIZE;
461 ev->resize.width = be->rw;
462 ev->resize.height = be->rh;
463 return 1;
464 }
465
466 return 0;
467}
468
469void maus_event_wait(Maus* mw, MausEvent* ev)
470{
471 MausBackend* be = &mw->backend;
472 MSG msg;
473
474 int r;
475
476 for (;;) {
477 r = GetMessage(&msg, NULL, 0, 0);
478 if (r <= 0) {
479 ev->type = MAUS_EV_CLOSE;
480 return;
481 }
482
483 TranslateMessage(&msg);
484 DispatchMessage(&msg);
485 ev->type = MAUS_EV_NONE;
486
487 /* WM_SIZE doesnt is handled separately */
488 if (be->resized) {
489 be->resized = 0;
490 ev->type = MAUS_EV_RESIZE;
491 ev->resize.width = be->rw;
492 ev->resize.height = be->rh;
493 return;
494 }
495
496 if (handle_event(&msg, ev, mw))
497 return;
498 }
499}
500
501void maus_present(Maus* mw)
502{
503 MausBackend* be = &mw->backend;
504 HDC wdc;
505
506 uint32_t bytes;
507
508 if (!be->hwnd || !be->memdc)
509 return;
510
511 wdc = GetDC(be->hwnd);
512
513 bytes = mw->stride * mw->height * sizeof(uint32_t);
514 memcpy(mw->fb, mw->bfb, bytes);
515
516 BitBlt(wdc, 0, 0, mw->width, mw->height, be->memdc, 0, 0, SRCCOPY);
517 ReleaseDC(be->hwnd, wdc);
518}
519
520int8_t maus_resize(Maus* mw, uint32_t width, uint32_t height)
521{
522 if (width == 0 || height == 0 ||
523 (mw->width == width && mw->height == height))
524 return 0;
525
526 fb_destroy(mw);
527 if (mw->bfb) {
528 free(mw->bfb);
529 mw->bfb = NULL;
530 }
531
532 mw->width = width;
533 mw->height = height;
534
535 if (!fb_create(mw))
536 return 0;
537
538 mw->bfb = calloc(mw->stride * mw->height, sizeof(uint32_t));
539 if (!mw->bfb)
540 return 0;
541
542 return 1;
543}
544
545void maus_cur_set_mode(Maus* mw, MausCursorState state)
546{
547 MausBackend* be = &mw->backend;
548 HWND hwnd = be->hwnd;
549
550 RECT r;
551
552 if (!hwnd) {
553 maus_log(stderr, "hwnd is NULL");
554 return;
555 }
556
557 /* show cursor */
558 if (state == MAUS_CURSOR_STATE_VISIBLE) {
559 while (ShowCursor(TRUE) < 0);
560 return;
561 }
562
563 /* hide cursor */
564 if (state == MAUS_CURSOR_STATE_HIDDEN) {
565 while (ShowCursor(FALSE) >= 0);
566 return;
567 }
568
569
570 /* lock cursor */
571 if (state == MAUS_CURSOR_STATE_LOCKED) {
572 GetClientRect(hwnd, &r);
573 MapWindowPoints(hwnd, NULL, (POINT*)&r, 2);
574
575 if (!ClipCursor(&r)) {
576 maus_log(stderr, "failed to grab mouse pointer");
577 return;
578 }
579
580 return;
581 }
582
583 /* unlock cursor */
584 if (state == MAUS_CURSOR_STATE_FREE) {
585 ClipCursor(NULL);
586 return;
587 }
588}
589