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