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