master uint/maus / source / maus_win.c
  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