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