1 /*
2 Copyright 2012-2020 David Robillard <d@drobilla.net>
3
4 Permission to use, copy, modify, and/or distribute this software for any
5 purpose with or without fee is hereby granted, provided that the above
6 copyright notice and this permission notice appear in all copies.
7
8 THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #include "win.h"
18
19 #include "implementation.h"
20 #include "stub.h"
21
22 #include "pugl/pugl.h"
23 #include "pugl/stub.h"
24
25 #include <windows.h>
26 #include <windowsx.h>
27
28 #include <math.h>
29 #include <stdbool.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <wctype.h>
33
34 #ifndef WM_MOUSEWHEEL
35 # define WM_MOUSEWHEEL 0x020A
36 #endif
37 #ifndef WM_MOUSEHWHEEL
38 # define WM_MOUSEHWHEEL 0x020E
39 #endif
40 #ifndef WHEEL_DELTA
41 # define WHEEL_DELTA 120
42 #endif
43 #ifndef GWLP_USERDATA
44 # define GWLP_USERDATA (-21)
45 #endif
46
47 #define PUGL_LOCAL_CLOSE_MSG (WM_USER + 50)
48 #define PUGL_LOCAL_MARK_MSG (WM_USER + 51)
49 #define PUGL_LOCAL_CLIENT_MSG (WM_USER + 52)
50 #define PUGL_USER_TIMER_MIN 9470
51
52 typedef BOOL(WINAPI* PFN_SetProcessDPIAware)(void);
53
54 LRESULT CALLBACK
55 wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
56
57 static wchar_t*
puglUtf8ToWideChar(const char * const utf8)58 puglUtf8ToWideChar(const char* const utf8)
59 {
60 const int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0);
61 if (len > 0) {
62 wchar_t* result = (wchar_t*)calloc((size_t)len, sizeof(wchar_t));
63 MultiByteToWideChar(CP_UTF8, 0, utf8, -1, result, len);
64 return result;
65 }
66
67 return NULL;
68 }
69
70 static char*
puglWideCharToUtf8(const wchar_t * const wstr,size_t * len)71 puglWideCharToUtf8(const wchar_t* const wstr, size_t* len)
72 {
73 int n = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL);
74 if (n > 0) {
75 char* result = (char*)calloc((size_t)n, sizeof(char));
76 WideCharToMultiByte(CP_UTF8, 0, wstr, -1, result, n, NULL, NULL);
77 *len = (size_t)n;
78 return result;
79 }
80
81 return NULL;
82 }
83
84 static bool
puglRegisterWindowClass(const char * name)85 puglRegisterWindowClass(const char* name)
86 {
87 WNDCLASSEX wc = {0};
88 if (GetClassInfoEx(GetModuleHandle(NULL), name, &wc)) {
89 return true; // Already registered
90 }
91
92 wc.cbSize = sizeof(wc);
93 wc.style = CS_OWNDC;
94 wc.lpfnWndProc = wndProc;
95 wc.hInstance = GetModuleHandle(NULL);
96 wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
97 wc.hCursor = LoadCursor(NULL, IDC_ARROW);
98 wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
99 wc.lpszClassName = name;
100
101 return RegisterClassEx(&wc);
102 }
103
104 PuglWorldInternals*
puglInitWorldInternals(PuglWorldType PUGL_UNUSED (type),PuglWorldFlags PUGL_UNUSED (flags))105 puglInitWorldInternals(PuglWorldType PUGL_UNUSED(type),
106 PuglWorldFlags PUGL_UNUSED(flags))
107 {
108 PuglWorldInternals* impl =
109 (PuglWorldInternals*)calloc(1, sizeof(PuglWorldInternals));
110 if (!impl) {
111 return NULL;
112 }
113
114 HMODULE user32 = LoadLibrary("user32.dll");
115 if (user32) {
116 PFN_SetProcessDPIAware SetProcessDPIAware =
117 (PFN_SetProcessDPIAware)GetProcAddress(user32, "SetProcessDPIAware");
118 if (SetProcessDPIAware) {
119 SetProcessDPIAware();
120 }
121
122 FreeLibrary(user32);
123 }
124
125 LARGE_INTEGER frequency;
126 QueryPerformanceFrequency(&frequency);
127 impl->timerFrequency = (double)frequency.QuadPart;
128
129 return impl;
130 }
131
132 void*
puglGetNativeWorld(PuglWorld * PUGL_UNUSED (world))133 puglGetNativeWorld(PuglWorld* PUGL_UNUSED(world))
134 {
135 return GetModuleHandle(NULL);
136 }
137
138 PuglInternals*
puglInitViewInternals(void)139 puglInitViewInternals(void)
140 {
141 return (PuglInternals*)calloc(1, sizeof(PuglInternals));
142 }
143
144 static PuglStatus
puglPollWinEvents(PuglWorld * world,const double timeout)145 puglPollWinEvents(PuglWorld* world, const double timeout)
146 {
147 (void)world;
148
149 if (timeout < 0) {
150 WaitMessage();
151 } else {
152 MsgWaitForMultipleObjects(
153 0, NULL, FALSE, (DWORD)(timeout * 1e3), QS_ALLEVENTS);
154 }
155 return PUGL_SUCCESS;
156 }
157
158 PuglStatus
puglRealize(PuglView * view)159 puglRealize(PuglView* view)
160 {
161 PuglInternals* impl = view->impl;
162 if (impl->hwnd) {
163 return PUGL_FAILURE;
164 }
165
166 // Getting depth from the display mode seems tedious, just set usual values
167 if (view->hints[PUGL_RED_BITS] == PUGL_DONT_CARE) {
168 view->hints[PUGL_RED_BITS] = 8;
169 }
170 if (view->hints[PUGL_BLUE_BITS] == PUGL_DONT_CARE) {
171 view->hints[PUGL_BLUE_BITS] = 8;
172 }
173 if (view->hints[PUGL_GREEN_BITS] == PUGL_DONT_CARE) {
174 view->hints[PUGL_GREEN_BITS] = 8;
175 }
176 if (view->hints[PUGL_ALPHA_BITS] == PUGL_DONT_CARE) {
177 view->hints[PUGL_ALPHA_BITS] = 8;
178 }
179
180 // Get refresh rate for resize draw timer
181 DEVMODEA devMode = {0};
182 EnumDisplaySettingsA(NULL, ENUM_CURRENT_SETTINGS, &devMode);
183 view->hints[PUGL_REFRESH_RATE] = (int)devMode.dmDisplayFrequency;
184
185 // Register window class if necessary
186 if (!puglRegisterWindowClass(view->world->className)) {
187 return PUGL_REGISTRATION_FAILED;
188 }
189
190 if (!view->backend || !view->backend->configure) {
191 return PUGL_BAD_BACKEND;
192 }
193
194 PuglStatus st = PUGL_SUCCESS;
195 if ((st = view->backend->configure(view)) ||
196 (st = view->backend->create(view))) {
197 return st;
198 }
199
200 if (view->title) {
201 puglSetWindowTitle(view, view->title);
202 }
203
204 view->impl->cursor = LoadCursor(NULL, IDC_ARROW);
205
206 puglSetFrame(view, view->frame);
207 SetWindowLongPtr(impl->hwnd, GWLP_USERDATA, (LONG_PTR)view);
208
209 puglDispatchSimpleEvent(view, PUGL_CREATE);
210
211 return PUGL_SUCCESS;
212 }
213
214 PuglStatus
puglShow(PuglView * view)215 puglShow(PuglView* view)
216 {
217 PuglInternals* impl = view->impl;
218
219 if (!impl->hwnd) {
220 const PuglStatus st = puglRealize(view);
221 if (st) {
222 return st;
223 }
224 }
225
226 ShowWindow(impl->hwnd, SW_SHOWNORMAL);
227 SetFocus(impl->hwnd);
228 return PUGL_SUCCESS;
229 }
230
231 PuglStatus
puglHide(PuglView * view)232 puglHide(PuglView* view)
233 {
234 PuglInternals* impl = view->impl;
235
236 ShowWindow(impl->hwnd, SW_HIDE);
237 return PUGL_SUCCESS;
238 }
239
240 void
puglFreeViewInternals(PuglView * view)241 puglFreeViewInternals(PuglView* view)
242 {
243 if (view) {
244 if (view->backend) {
245 view->backend->destroy(view);
246 }
247
248 ReleaseDC(view->impl->hwnd, view->impl->hdc);
249 DestroyWindow(view->impl->hwnd);
250 free(view->impl);
251 }
252 }
253
254 void
puglFreeWorldInternals(PuglWorld * world)255 puglFreeWorldInternals(PuglWorld* world)
256 {
257 UnregisterClass(world->className, NULL);
258 free(world->impl);
259 }
260
261 static PuglKey
keySymToSpecial(WPARAM sym)262 keySymToSpecial(WPARAM sym)
263 {
264 // clang-format off
265 switch (sym) {
266 case VK_F1: return PUGL_KEY_F1;
267 case VK_F2: return PUGL_KEY_F2;
268 case VK_F3: return PUGL_KEY_F3;
269 case VK_F4: return PUGL_KEY_F4;
270 case VK_F5: return PUGL_KEY_F5;
271 case VK_F6: return PUGL_KEY_F6;
272 case VK_F7: return PUGL_KEY_F7;
273 case VK_F8: return PUGL_KEY_F8;
274 case VK_F9: return PUGL_KEY_F9;
275 case VK_F10: return PUGL_KEY_F10;
276 case VK_F11: return PUGL_KEY_F11;
277 case VK_F12: return PUGL_KEY_F12;
278 case VK_BACK: return PUGL_KEY_BACKSPACE;
279 case VK_DELETE: return PUGL_KEY_DELETE;
280 case VK_LEFT: return PUGL_KEY_LEFT;
281 case VK_UP: return PUGL_KEY_UP;
282 case VK_RIGHT: return PUGL_KEY_RIGHT;
283 case VK_DOWN: return PUGL_KEY_DOWN;
284 case VK_PRIOR: return PUGL_KEY_PAGE_UP;
285 case VK_NEXT: return PUGL_KEY_PAGE_DOWN;
286 case VK_HOME: return PUGL_KEY_HOME;
287 case VK_END: return PUGL_KEY_END;
288 case VK_INSERT: return PUGL_KEY_INSERT;
289 case VK_SHIFT:
290 case VK_LSHIFT: return PUGL_KEY_SHIFT_L;
291 case VK_RSHIFT: return PUGL_KEY_SHIFT_R;
292 case VK_CONTROL:
293 case VK_LCONTROL: return PUGL_KEY_CTRL_L;
294 case VK_RCONTROL: return PUGL_KEY_CTRL_R;
295 case VK_MENU:
296 case VK_LMENU: return PUGL_KEY_ALT_L;
297 case VK_RMENU: return PUGL_KEY_ALT_R;
298 case VK_LWIN: return PUGL_KEY_SUPER_L;
299 case VK_RWIN: return PUGL_KEY_SUPER_R;
300 case VK_CAPITAL: return PUGL_KEY_CAPS_LOCK;
301 case VK_SCROLL: return PUGL_KEY_SCROLL_LOCK;
302 case VK_NUMLOCK: return PUGL_KEY_NUM_LOCK;
303 case VK_SNAPSHOT: return PUGL_KEY_PRINT_SCREEN;
304 case VK_PAUSE: return PUGL_KEY_PAUSE;
305 }
306 // clang-format on
307
308 return (PuglKey)0;
309 }
310
311 static uint32_t
getModifiers(void)312 getModifiers(void)
313 {
314 // clang-format off
315 return (((GetKeyState(VK_SHIFT) < 0) ? PUGL_MOD_SHIFT : 0u) |
316 ((GetKeyState(VK_CONTROL) < 0) ? PUGL_MOD_CTRL : 0u) |
317 ((GetKeyState(VK_MENU) < 0) ? PUGL_MOD_ALT : 0u) |
318 ((GetKeyState(VK_LWIN) < 0) ? PUGL_MOD_SUPER : 0u) |
319 ((GetKeyState(VK_RWIN) < 0) ? PUGL_MOD_SUPER : 0u));
320 // clang-format on
321 }
322
323 static void
initMouseEvent(PuglEvent * event,PuglView * view,int button,bool press,LPARAM lParam)324 initMouseEvent(PuglEvent* event,
325 PuglView* view,
326 int button,
327 bool press,
328 LPARAM lParam)
329 {
330 POINT pt = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
331 ClientToScreen(view->impl->hwnd, &pt);
332
333 if (press) {
334 SetCapture(view->impl->hwnd);
335 } else {
336 ReleaseCapture();
337 }
338
339 event->button.time = GetMessageTime() / 1e3;
340 event->button.type = press ? PUGL_BUTTON_PRESS : PUGL_BUTTON_RELEASE;
341 event->button.x = GET_X_LPARAM(lParam);
342 event->button.y = GET_Y_LPARAM(lParam);
343 event->button.xRoot = pt.x;
344 event->button.yRoot = pt.y;
345 event->button.state = getModifiers();
346 event->button.button = (uint32_t)button;
347 }
348
349 static void
initScrollEvent(PuglEvent * event,PuglView * view,LPARAM lParam)350 initScrollEvent(PuglEvent* event, PuglView* view, LPARAM lParam)
351 {
352 POINT pt = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
353 ScreenToClient(view->impl->hwnd, &pt);
354
355 event->scroll.time = GetMessageTime() / 1e3;
356 event->scroll.type = PUGL_SCROLL;
357 event->scroll.x = pt.x;
358 event->scroll.y = pt.y;
359 event->scroll.xRoot = GET_X_LPARAM(lParam);
360 event->scroll.yRoot = GET_Y_LPARAM(lParam);
361 event->scroll.state = getModifiers();
362 event->scroll.dx = 0;
363 event->scroll.dy = 0;
364 }
365
366 /// Return the code point for buf, or the replacement character on error
367 static uint32_t
puglDecodeUTF16(const wchar_t * buf,const int len)368 puglDecodeUTF16(const wchar_t* buf, const int len)
369 {
370 const uint32_t c0 = buf[0];
371 const uint32_t c1 = buf[0];
372 if (c0 >= 0xD800 && c0 < 0xDC00) {
373 if (len < 2) {
374 return 0xFFFD; // Surrogate, but length is only 1
375 } else if (c1 >= 0xDC00 && c1 <= 0xDFFF) {
376 return ((c0 & 0x03FF) << 10) + (c1 & 0x03FF) + 0x10000;
377 }
378
379 return 0xFFFD; // Unpaired surrogates
380 }
381
382 return c0;
383 }
384
385 static void
initKeyEvent(PuglEventKey * event,PuglView * view,bool press,WPARAM wParam,LPARAM lParam)386 initKeyEvent(PuglEventKey* event,
387 PuglView* view,
388 bool press,
389 WPARAM wParam,
390 LPARAM lParam)
391 {
392 POINT rpos = {0, 0};
393 GetCursorPos(&rpos);
394
395 POINT cpos = {rpos.x, rpos.y};
396 ScreenToClient(view->impl->hwnd, &rpos);
397
398 const unsigned scode = (uint32_t)((lParam & 0xFF0000) >> 16);
399 const unsigned vkey =
400 ((wParam == VK_SHIFT) ? MapVirtualKey(scode, MAPVK_VSC_TO_VK_EX)
401 : (unsigned)wParam);
402
403 const unsigned vcode = MapVirtualKey(vkey, MAPVK_VK_TO_VSC);
404 const unsigned kchar = MapVirtualKey(vkey, MAPVK_VK_TO_CHAR);
405 const bool dead = kchar >> (sizeof(UINT) * 8 - 1) & 1;
406 const bool ext = lParam & 0x01000000;
407
408 event->type = press ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE;
409 event->time = GetMessageTime() / 1e3;
410 event->state = getModifiers();
411 event->xRoot = rpos.x;
412 event->yRoot = rpos.y;
413 event->x = cpos.x;
414 event->y = cpos.y;
415 event->keycode = (uint32_t)((lParam & 0xFF0000) >> 16);
416 event->key = 0;
417
418 const PuglKey special = keySymToSpecial(vkey);
419 if (special) {
420 if (ext && (special == PUGL_KEY_CTRL || special == PUGL_KEY_ALT)) {
421 event->key = (uint32_t)special + 1u; // Right hand key
422 } else {
423 event->key = (uint32_t)special;
424 }
425 } else if (!dead) {
426 // Translate unshifted key
427 BYTE keyboardState[256] = {0};
428 wchar_t buf[5] = {0};
429
430 event->key = puglDecodeUTF16(
431 buf, ToUnicode(vkey, vcode, keyboardState, buf, 4, 1 << 2));
432 }
433 }
434
435 static void
initCharEvent(PuglEvent * event,PuglView * view,WPARAM wParam,LPARAM lParam)436 initCharEvent(PuglEvent* event, PuglView* view, WPARAM wParam, LPARAM lParam)
437 {
438 const wchar_t utf16[2] = {wParam & 0xFFFF,
439 (wchar_t)((wParam >> 16) & 0xFFFF)};
440
441 initKeyEvent(&event->key, view, true, wParam, lParam);
442 event->type = PUGL_TEXT;
443 event->text.character = puglDecodeUTF16(utf16, 2);
444
445 if (!WideCharToMultiByte(
446 CP_UTF8, 0, utf16, 2, event->text.string, 8, NULL, NULL)) {
447 memset(event->text.string, 0, 8);
448 }
449 }
450
451 static bool
ignoreKeyEvent(PuglView * view,LPARAM lParam)452 ignoreKeyEvent(PuglView* view, LPARAM lParam)
453 {
454 return view->hints[PUGL_IGNORE_KEY_REPEAT] && (lParam & (1 << 30));
455 }
456
457 static RECT
handleConfigure(PuglView * view,PuglEvent * event)458 handleConfigure(PuglView* view, PuglEvent* event)
459 {
460 RECT rect;
461 GetClientRect(view->impl->hwnd, &rect);
462 MapWindowPoints(view->impl->hwnd,
463 view->parent ? (HWND)view->parent : HWND_DESKTOP,
464 (LPPOINT)&rect,
465 2);
466
467 const LONG width = rect.right - rect.left;
468 const LONG height = rect.bottom - rect.top;
469
470 view->frame.x = rect.left;
471 view->frame.y = rect.top;
472
473 event->configure.type = PUGL_CONFIGURE;
474 event->configure.x = view->frame.x;
475 event->configure.y = view->frame.y;
476 event->configure.width = width;
477 event->configure.height = height;
478
479 if (view->frame.width != width || view->frame.height != height) {
480 view->frame.width = width;
481 view->frame.height = height;
482 }
483
484 return rect;
485 }
486
487 static void
handleCrossing(PuglView * view,const PuglEventType type,POINT pos)488 handleCrossing(PuglView* view, const PuglEventType type, POINT pos)
489 {
490 POINT root_pos = pos;
491 ClientToScreen(view->impl->hwnd, &root_pos);
492
493 const PuglEventCrossing ev = {
494 type,
495 0,
496 GetMessageTime() / 1e3,
497 (double)pos.x,
498 (double)pos.y,
499 (double)root_pos.x,
500 (double)root_pos.y,
501 getModifiers(),
502 PUGL_CROSSING_NORMAL,
503 };
504
505 puglDispatchEvent(view, (const PuglEvent*)&ev);
506 }
507
508 static void
constrainAspect(const PuglView * const view,RECT * const size,const WPARAM wParam)509 constrainAspect(const PuglView* const view,
510 RECT* const size,
511 const WPARAM wParam)
512 {
513 const float minA = (float)view->minAspectX / (float)view->minAspectY;
514 const float maxA = (float)view->maxAspectX / (float)view->maxAspectY;
515 const float w = (float)(size->right - size->left);
516 const float h = (float)(size->bottom - size->top);
517 const float a = w / h;
518
519 switch (wParam) {
520 case WMSZ_TOP:
521 size->top = (a < minA ? (LONG)((float)size->bottom - w * minA)
522 : a > maxA ? (LONG)((float)size->bottom - w * maxA)
523 : size->top);
524 break;
525 case WMSZ_TOPRIGHT:
526 case WMSZ_RIGHT:
527 case WMSZ_BOTTOMRIGHT:
528 size->right = (a < minA ? (LONG)((float)size->left + h * minA)
529 : a > maxA ? (LONG)((float)size->left + h * maxA)
530 : size->right);
531 break;
532 case WMSZ_BOTTOM:
533 size->bottom = (a < minA ? (LONG)((float)size->top + w * minA)
534 : a > maxA ? (LONG)((float)size->top + w * maxA)
535 : size->bottom);
536 break;
537 case WMSZ_BOTTOMLEFT:
538 case WMSZ_LEFT:
539 case WMSZ_TOPLEFT:
540 size->left = (a < minA ? (LONG)((float)size->right - h * minA)
541 : a > maxA ? (LONG)((float)size->right - h * maxA)
542 : size->left);
543 break;
544 }
545 }
546
547 static LRESULT
handleMessage(PuglView * view,UINT message,WPARAM wParam,LPARAM lParam)548 handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam)
549 {
550 PuglEvent event = {{PUGL_NOTHING, 0}};
551 RECT rect = {0, 0, 0, 0};
552 POINT pt = {0, 0};
553 MINMAXINFO* mmi = NULL;
554 void* dummy_ptr = NULL;
555
556 if (InSendMessageEx(dummy_ptr)) {
557 event.any.flags |= PUGL_IS_SEND_EVENT;
558 }
559
560 switch (message) {
561 case WM_SETCURSOR:
562 if (LOWORD(lParam) == HTCLIENT) {
563 SetCursor(view->impl->cursor);
564 } else {
565 return DefWindowProc(view->impl->hwnd, message, wParam, lParam);
566 }
567 break;
568 case WM_SHOWWINDOW:
569 if (wParam) {
570 handleConfigure(view, &event);
571 puglDispatchEvent(view, &event);
572 event.type = PUGL_NOTHING;
573
574 RedrawWindow(view->impl->hwnd,
575 NULL,
576 NULL,
577 RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_INTERNALPAINT);
578 }
579
580 if ((bool)wParam != view->visible) {
581 view->visible = wParam;
582 event.any.type = wParam ? PUGL_MAP : PUGL_UNMAP;
583 }
584 break;
585 case WM_SIZE:
586 handleConfigure(view, &event);
587 InvalidateRect(view->impl->hwnd, NULL, false);
588 break;
589 case WM_SIZING:
590 if (view->minAspectX) {
591 constrainAspect(view, (RECT*)lParam, wParam);
592 return TRUE;
593 }
594 break;
595 case WM_ENTERSIZEMOVE:
596 case WM_ENTERMENULOOP:
597 puglDispatchSimpleEvent(view, PUGL_LOOP_ENTER);
598 break;
599 case WM_TIMER:
600 if (wParam >= PUGL_USER_TIMER_MIN) {
601 PuglEvent ev = {{PUGL_TIMER, 0}};
602 ev.timer.id = wParam - PUGL_USER_TIMER_MIN;
603 puglDispatchEvent(view, &ev);
604 }
605 break;
606 case WM_EXITSIZEMOVE:
607 case WM_EXITMENULOOP:
608 puglDispatchSimpleEvent(view, PUGL_LOOP_LEAVE);
609 break;
610 case WM_GETMINMAXINFO:
611 mmi = (MINMAXINFO*)lParam;
612 mmi->ptMinTrackSize.x = view->minWidth;
613 mmi->ptMinTrackSize.y = view->minHeight;
614 if (view->maxWidth > 0 && view->maxHeight > 0) {
615 mmi->ptMaxTrackSize.x = view->maxWidth;
616 mmi->ptMaxTrackSize.y = view->maxHeight;
617 }
618 break;
619 case WM_PAINT:
620 GetUpdateRect(view->impl->hwnd, &rect, false);
621 event.expose.type = PUGL_EXPOSE;
622 event.expose.x = rect.left;
623 event.expose.y = rect.top;
624 event.expose.width = rect.right - rect.left;
625 event.expose.height = rect.bottom - rect.top;
626 break;
627 case WM_ERASEBKGND:
628 return true;
629 case WM_MOUSEMOVE:
630 pt.x = GET_X_LPARAM(lParam);
631 pt.y = GET_Y_LPARAM(lParam);
632
633 if (!view->impl->mouseTracked) {
634 TRACKMOUSEEVENT tme = {0};
635
636 tme.cbSize = sizeof(tme);
637 tme.dwFlags = TME_LEAVE;
638 tme.hwndTrack = view->impl->hwnd;
639 TrackMouseEvent(&tme);
640
641 handleCrossing(view, PUGL_POINTER_IN, pt);
642 view->impl->mouseTracked = true;
643 }
644
645 ClientToScreen(view->impl->hwnd, &pt);
646 event.motion.type = PUGL_MOTION;
647 event.motion.time = GetMessageTime() / 1e3;
648 event.motion.x = GET_X_LPARAM(lParam);
649 event.motion.y = GET_Y_LPARAM(lParam);
650 event.motion.xRoot = pt.x;
651 event.motion.yRoot = pt.y;
652 event.motion.state = getModifiers();
653 break;
654 case WM_MOUSELEAVE:
655 GetCursorPos(&pt);
656 ScreenToClient(view->impl->hwnd, &pt);
657 handleCrossing(view, PUGL_POINTER_OUT, pt);
658 view->impl->mouseTracked = false;
659 break;
660 case WM_LBUTTONDOWN:
661 initMouseEvent(&event, view, 1, true, lParam);
662 break;
663 case WM_MBUTTONDOWN:
664 initMouseEvent(&event, view, 2, true, lParam);
665 break;
666 case WM_RBUTTONDOWN:
667 initMouseEvent(&event, view, 3, true, lParam);
668 break;
669 case WM_LBUTTONUP:
670 initMouseEvent(&event, view, 1, false, lParam);
671 break;
672 case WM_MBUTTONUP:
673 initMouseEvent(&event, view, 2, false, lParam);
674 break;
675 case WM_RBUTTONUP:
676 initMouseEvent(&event, view, 3, false, lParam);
677 break;
678 case WM_MOUSEWHEEL:
679 initScrollEvent(&event, view, lParam);
680 event.scroll.dy = GET_WHEEL_DELTA_WPARAM(wParam) / (double)WHEEL_DELTA;
681 event.scroll.direction =
682 (event.scroll.dy > 0 ? PUGL_SCROLL_UP : PUGL_SCROLL_DOWN);
683 break;
684 case WM_MOUSEHWHEEL:
685 initScrollEvent(&event, view, lParam);
686 event.scroll.dx = GET_WHEEL_DELTA_WPARAM(wParam) / (double)WHEEL_DELTA;
687 event.scroll.direction =
688 (event.scroll.dx > 0 ? PUGL_SCROLL_RIGHT : PUGL_SCROLL_LEFT);
689 break;
690 case WM_KEYDOWN:
691 if (!ignoreKeyEvent(view, lParam)) {
692 initKeyEvent(&event.key, view, true, wParam, lParam);
693 }
694 break;
695 case WM_KEYUP:
696 initKeyEvent(&event.key, view, false, wParam, lParam);
697 break;
698 case WM_CHAR:
699 initCharEvent(&event, view, wParam, lParam);
700 break;
701 case WM_SETFOCUS:
702 event.type = PUGL_FOCUS_IN;
703 break;
704 case WM_KILLFOCUS:
705 event.type = PUGL_FOCUS_OUT;
706 break;
707 case WM_SYSKEYDOWN:
708 initKeyEvent(&event.key, view, true, wParam, lParam);
709 break;
710 case WM_SYSKEYUP:
711 initKeyEvent(&event.key, view, false, wParam, lParam);
712 break;
713 case WM_SYSCHAR:
714 return TRUE;
715 case PUGL_LOCAL_CLIENT_MSG:
716 event.client.type = PUGL_CLIENT;
717 event.client.data1 = (uintptr_t)wParam;
718 event.client.data2 = (uintptr_t)lParam;
719 break;
720 case WM_QUIT:
721 case PUGL_LOCAL_CLOSE_MSG:
722 event.any.type = PUGL_CLOSE;
723 break;
724 default:
725 return DefWindowProc(view->impl->hwnd, message, wParam, lParam);
726 }
727
728 puglDispatchEvent(view, &event);
729
730 return 0;
731 }
732
733 PuglStatus
puglGrabFocus(PuglView * view)734 puglGrabFocus(PuglView* view)
735 {
736 SetFocus(view->impl->hwnd);
737 return PUGL_SUCCESS;
738 }
739
740 bool
puglHasFocus(const PuglView * view)741 puglHasFocus(const PuglView* view)
742 {
743 return GetFocus() == view->impl->hwnd;
744 }
745
746 PuglStatus
puglRequestAttention(PuglView * view)747 puglRequestAttention(PuglView* view)
748 {
749 FLASHWINFO info = {
750 sizeof(FLASHWINFO), view->impl->hwnd, FLASHW_ALL | FLASHW_TIMERNOFG, 1, 0};
751
752 FlashWindowEx(&info);
753
754 return PUGL_SUCCESS;
755 }
756
757 PuglStatus
puglStartTimer(PuglView * view,uintptr_t id,double timeout)758 puglStartTimer(PuglView* view, uintptr_t id, double timeout)
759 {
760 const UINT msec = (UINT)floor(timeout * 1000.0);
761
762 return (SetTimer(view->impl->hwnd, PUGL_USER_TIMER_MIN + id, msec, NULL)
763 ? PUGL_SUCCESS
764 : PUGL_UNKNOWN_ERROR);
765 }
766
767 PuglStatus
puglStopTimer(PuglView * view,uintptr_t id)768 puglStopTimer(PuglView* view, uintptr_t id)
769 {
770 return (KillTimer(view->impl->hwnd, PUGL_USER_TIMER_MIN + id)
771 ? PUGL_SUCCESS
772 : PUGL_UNKNOWN_ERROR);
773 }
774
775 PuglStatus
puglSendEvent(PuglView * view,const PuglEvent * event)776 puglSendEvent(PuglView* view, const PuglEvent* event)
777 {
778 if (event->type == PUGL_CLIENT) {
779 PostMessage(view->impl->hwnd,
780 PUGL_LOCAL_CLIENT_MSG,
781 (WPARAM)event->client.data1,
782 (LPARAM)event->client.data2);
783
784 return PUGL_SUCCESS;
785 }
786
787 return PUGL_UNSUPPORTED_TYPE;
788 }
789
790 #ifndef PUGL_DISABLE_DEPRECATED
791 PuglStatus
puglWaitForEvent(PuglView * PUGL_UNUSED (view))792 puglWaitForEvent(PuglView* PUGL_UNUSED(view))
793 {
794 WaitMessage();
795 return PUGL_SUCCESS;
796 }
797 #endif
798
799 static PuglStatus
puglDispatchViewEvents(PuglView * view)800 puglDispatchViewEvents(PuglView* view)
801 {
802 /* Windows has no facility to process only currently queued messages, which
803 causes the event loop to run forever in cases like mouse movement where
804 the queue is constantly being filled with new messages. To work around
805 this, we post a message to ourselves before starting, record its time
806 when it is received, then break the loop on the first message that was
807 created afterwards. */
808
809 long markTime = 0;
810 MSG msg;
811 while (PeekMessage(&msg, view->impl->hwnd, 0, 0, PM_REMOVE)) {
812 if (msg.message == PUGL_LOCAL_MARK_MSG) {
813 markTime = GetMessageTime();
814 } else {
815 TranslateMessage(&msg);
816 DispatchMessage(&msg);
817 if (markTime != 0 && GetMessageTime() > markTime) {
818 break;
819 }
820 }
821 }
822
823 return PUGL_SUCCESS;
824 }
825
826 static PuglStatus
puglDispatchWinEvents(PuglWorld * world)827 puglDispatchWinEvents(PuglWorld* world)
828 {
829 for (size_t i = 0; i < world->numViews; ++i) {
830 PostMessage(world->views[i]->impl->hwnd, PUGL_LOCAL_MARK_MSG, 0, 0);
831 }
832
833 for (size_t i = 0; i < world->numViews; ++i) {
834 puglDispatchViewEvents(world->views[i]);
835 }
836
837 return PUGL_SUCCESS;
838 }
839
840 PuglStatus
puglUpdate(PuglWorld * world,double timeout)841 puglUpdate(PuglWorld* world, double timeout)
842 {
843 const double startTime = puglGetTime(world);
844 PuglStatus st = PUGL_SUCCESS;
845
846 if (timeout < 0.0) {
847 st = puglPollWinEvents(world, timeout);
848 st = st ? st : puglDispatchWinEvents(world);
849 } else if (timeout == 0.0) {
850 st = puglDispatchWinEvents(world);
851 } else {
852 const double endTime = startTime + timeout - 0.001;
853 for (double t = startTime; t < endTime; t = puglGetTime(world)) {
854 if ((st = puglPollWinEvents(world, endTime - t)) ||
855 (st = puglDispatchWinEvents(world))) {
856 break;
857 }
858 }
859 }
860
861 for (size_t i = 0; i < world->numViews; ++i) {
862 if (world->views[i]->visible) {
863 puglDispatchSimpleEvent(world->views[i], PUGL_UPDATE);
864 }
865
866 UpdateWindow(world->views[i]->impl->hwnd);
867 }
868
869 return st;
870 }
871
872 #ifndef PUGL_DISABLE_DEPRECATED
873 PuglStatus
puglProcessEvents(PuglView * view)874 puglProcessEvents(PuglView* view)
875 {
876 return puglUpdate(view->world, 0.0);
877 }
878 #endif
879
880 LRESULT CALLBACK
wndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)881 wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
882 {
883 PuglView* view = (PuglView*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
884
885 switch (message) {
886 case WM_CREATE:
887 PostMessage(hwnd, WM_SHOWWINDOW, TRUE, 0);
888 return 0;
889 case WM_CLOSE:
890 PostMessage(hwnd, PUGL_LOCAL_CLOSE_MSG, wParam, lParam);
891 return 0;
892 case WM_DESTROY:
893 return 0;
894 default:
895 if (view && hwnd == view->impl->hwnd) {
896 return handleMessage(view, message, wParam, lParam);
897 } else {
898 return DefWindowProc(hwnd, message, wParam, lParam);
899 }
900 }
901 }
902
903 double
puglGetTime(const PuglWorld * world)904 puglGetTime(const PuglWorld* world)
905 {
906 LARGE_INTEGER count;
907 QueryPerformanceCounter(&count);
908 return ((double)count.QuadPart / world->impl->timerFrequency -
909 world->startTime);
910 }
911
912 PuglStatus
puglPostRedisplay(PuglView * view)913 puglPostRedisplay(PuglView* view)
914 {
915 InvalidateRect(view->impl->hwnd, NULL, false);
916 return PUGL_SUCCESS;
917 }
918
919 PuglStatus
puglPostRedisplayRect(PuglView * view,const PuglRect rect)920 puglPostRedisplayRect(PuglView* view, const PuglRect rect)
921 {
922 const RECT r = {(long)floor(rect.x),
923 (long)floor(rect.y),
924 (long)ceil(rect.x + rect.width),
925 (long)ceil(rect.y + rect.height)};
926
927 InvalidateRect(view->impl->hwnd, &r, false);
928
929 return PUGL_SUCCESS;
930 }
931
932 PuglNativeView
puglGetNativeWindow(PuglView * view)933 puglGetNativeWindow(PuglView* view)
934 {
935 return (PuglNativeView)view->impl->hwnd;
936 }
937
938 PuglStatus
puglSetWindowTitle(PuglView * view,const char * title)939 puglSetWindowTitle(PuglView* view, const char* title)
940 {
941 puglSetString(&view->title, title);
942
943 if (view->impl->hwnd) {
944 wchar_t* wtitle = puglUtf8ToWideChar(title);
945 if (wtitle) {
946 SetWindowTextW(view->impl->hwnd, wtitle);
947 free(wtitle);
948 }
949 }
950
951 return PUGL_SUCCESS;
952 }
953
954 PuglStatus
puglSetFrame(PuglView * view,const PuglRect frame)955 puglSetFrame(PuglView* view, const PuglRect frame)
956 {
957 view->frame = frame;
958
959 if (view->impl->hwnd) {
960 RECT rect = {(long)frame.x,
961 (long)frame.y,
962 (long)frame.x + (long)frame.width,
963 (long)frame.y + (long)frame.height};
964
965 AdjustWindowRectEx(
966 &rect, puglWinGetWindowFlags(view), FALSE, puglWinGetWindowExFlags(view));
967
968 if (!SetWindowPos(view->impl->hwnd,
969 HWND_TOP,
970 rect.left,
971 rect.top,
972 rect.right - rect.left,
973 rect.bottom - rect.top,
974 SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER)) {
975 return PUGL_UNKNOWN_ERROR;
976 }
977 }
978
979 return PUGL_SUCCESS;
980 }
981
982 PuglStatus
puglSetDefaultSize(PuglView * const view,const int width,const int height)983 puglSetDefaultSize(PuglView* const view, const int width, const int height)
984 {
985 view->defaultWidth = width;
986 view->defaultHeight = height;
987 return PUGL_SUCCESS;
988 }
989
990 PuglStatus
puglSetMinSize(PuglView * const view,const int width,const int height)991 puglSetMinSize(PuglView* const view, const int width, const int height)
992 {
993 view->minWidth = width;
994 view->minHeight = height;
995 return PUGL_SUCCESS;
996 }
997
998 PuglStatus
puglSetMaxSize(PuglView * const view,const int width,const int height)999 puglSetMaxSize(PuglView* const view, const int width, const int height)
1000 {
1001 view->maxWidth = width;
1002 view->maxHeight = height;
1003 return PUGL_SUCCESS;
1004 }
1005
1006 PuglStatus
puglSetAspectRatio(PuglView * const view,const int minX,const int minY,const int maxX,const int maxY)1007 puglSetAspectRatio(PuglView* const view,
1008 const int minX,
1009 const int minY,
1010 const int maxX,
1011 const int maxY)
1012 {
1013 view->minAspectX = minX;
1014 view->minAspectY = minY;
1015 view->maxAspectX = maxX;
1016 view->maxAspectY = maxY;
1017 return PUGL_SUCCESS;
1018 }
1019
1020 PuglStatus
puglSetTransientFor(PuglView * view,PuglNativeView parent)1021 puglSetTransientFor(PuglView* view, PuglNativeView parent)
1022 {
1023 if (view->parent) {
1024 return PUGL_FAILURE;
1025 }
1026
1027 view->transientParent = parent;
1028
1029 if (view->impl->hwnd) {
1030 SetWindowLongPtr(view->impl->hwnd, GWLP_HWNDPARENT, (LONG_PTR)parent);
1031 return GetLastError() == NO_ERROR ? PUGL_SUCCESS : PUGL_FAILURE;
1032 }
1033
1034 return PUGL_SUCCESS;
1035 }
1036
1037 const void*
puglGetClipboard(PuglView * const view,const char ** const type,size_t * const len)1038 puglGetClipboard(PuglView* const view,
1039 const char** const type,
1040 size_t* const len)
1041 {
1042 PuglInternals* const impl = view->impl;
1043
1044 if (!IsClipboardFormatAvailable(CF_UNICODETEXT) ||
1045 !OpenClipboard(impl->hwnd)) {
1046 return NULL;
1047 }
1048
1049 HGLOBAL mem = GetClipboardData(CF_UNICODETEXT);
1050 wchar_t* wstr = mem ? (wchar_t*)GlobalLock(mem) : NULL;
1051 if (!wstr) {
1052 CloseClipboard();
1053 return NULL;
1054 }
1055
1056 free(view->clipboard.data);
1057 view->clipboard.data = puglWideCharToUtf8(wstr, &view->clipboard.len);
1058 GlobalUnlock(mem);
1059 CloseClipboard();
1060
1061 return puglGetInternalClipboard(view, type, len);
1062 }
1063
1064 PuglStatus
puglSetClipboard(PuglView * const view,const char * const type,const void * const data,const size_t len)1065 puglSetClipboard(PuglView* const view,
1066 const char* const type,
1067 const void* const data,
1068 const size_t len)
1069 {
1070 PuglInternals* const impl = view->impl;
1071
1072 PuglStatus st = puglSetInternalClipboard(view, type, data, len);
1073 if (st) {
1074 return st;
1075 } else if (!OpenClipboard(impl->hwnd)) {
1076 return PUGL_UNKNOWN_ERROR;
1077 }
1078
1079 // Measure string and allocate global memory for clipboard
1080 const char* str = (const char*)data;
1081 const int wlen = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
1082 HGLOBAL mem =
1083 GlobalAlloc(GMEM_MOVEABLE, (size_t)(wlen + 1) * sizeof(wchar_t));
1084 if (!mem) {
1085 CloseClipboard();
1086 return PUGL_UNKNOWN_ERROR;
1087 }
1088
1089 // Lock global memory
1090 wchar_t* wstr = (wchar_t*)GlobalLock(mem);
1091 if (!wstr) {
1092 GlobalFree(mem);
1093 CloseClipboard();
1094 return PUGL_UNKNOWN_ERROR;
1095 }
1096
1097 // Convert string into global memory and set it as clipboard data
1098 MultiByteToWideChar(CP_UTF8, 0, str, (int)len, wstr, wlen);
1099 wstr[wlen] = 0;
1100 GlobalUnlock(mem);
1101 SetClipboardData(CF_UNICODETEXT, mem);
1102 CloseClipboard();
1103 return PUGL_SUCCESS;
1104 }
1105
1106 static const char* const cursor_ids[] = {
1107 IDC_ARROW, // ARROW
1108 IDC_IBEAM, // CARET
1109 IDC_CROSS, // CROSSHAIR
1110 IDC_HAND, // HAND
1111 IDC_NO, // NO
1112 IDC_SIZEWE, // LEFT_RIGHT
1113 IDC_SIZENS, // UP_DOWN
1114 };
1115
1116 PuglStatus
puglSetCursor(PuglView * view,PuglCursor cursor)1117 puglSetCursor(PuglView* view, PuglCursor cursor)
1118 {
1119 PuglInternals* const impl = view->impl;
1120 const unsigned index = (unsigned)cursor;
1121 const unsigned count = sizeof(cursor_ids) / sizeof(cursor_ids[0]);
1122
1123 if (index >= count) {
1124 return PUGL_BAD_PARAMETER;
1125 }
1126
1127 const HCURSOR cur = LoadCursor(NULL, cursor_ids[index]);
1128 if (!cur) {
1129 return PUGL_FAILURE;
1130 }
1131
1132 impl->cursor = cur;
1133 if (impl->mouseTracked) {
1134 SetCursor(cur);
1135 }
1136
1137 return PUGL_SUCCESS;
1138 }
1139