main uint/cterm / libmaus / 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 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