1 /*
2 Copyright 2012-2014 David Robillard <http://drobilla.net>
3 Copyright 2012-2019 Filipe Coelho <falktx@falktx.com>
4
5 Permission to use, copy, modify, and/or distribute this software for any
6 purpose with or without fee is hereby granted, provided that the above
7 copyright notice and this permission notice appear in all copies.
8
9 THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18 /**
19 @file pugl_win.cpp Windows/WGL Pugl Implementation.
20 */
21
22 #include <ctime>
23 #include <cstdio>
24 #include <cstdlib>
25
26 #include <winsock2.h>
27 #include <windows.h>
28 #include <windowsx.h>
29 #ifdef PUGL_CAIRO
30 #include <cairo/cairo.h>
31 #include <cairo/cairo-win32.h>
32 #endif
33 #ifdef PUGL_OPENGL
34 #include <GL/gl.h>
35 #endif
36
37 #include "pugl_internal.h"
38
39 #ifndef WM_MOUSEWHEEL
40 # define WM_MOUSEWHEEL 0x020A
41 #endif
42 #ifndef WM_MOUSEHWHEEL
43 # define WM_MOUSEHWHEEL 0x020E
44 #endif
45 #ifndef WHEEL_DELTA
46 # define WHEEL_DELTA 120
47 #endif
48 #ifndef GWLP_USERDATA
49 # define GWLP_USERDATA (-21)
50 #endif
51
52 #define PUGL_LOCAL_CLOSE_MSG (WM_USER + 50)
53
54 HINSTANCE hInstance = NULL;
55
56 struct PuglInternalsImpl {
57 HWND hwnd;
58 #ifdef PUGL_OPENGL
59 HDC hdc;
60 HGLRC hglrc;
61 #endif
62 #ifdef PUGL_CAIRO
63 cairo_t* buffer_cr;
64 cairo_surface_t* buffer_surface;
65 #endif
66 HDC paintHdc;
67 WNDCLASS wc;
68 };
69
70 LRESULT CALLBACK
71 wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
72
73 #if 0
74 extern "C" {
75 BOOL WINAPI
76 DllMain(HINSTANCE hInst, DWORD, LPVOID)
77 {
78 hInstance = hInst;
79 return 1;
80 }
81 } // extern "C"
82 #endif
83
84 PuglInternals*
puglInitInternals()85 puglInitInternals()
86 {
87 return (PuglInternals*)calloc(1, sizeof(PuglInternals));
88 }
89
90 void
puglEnterContext(PuglView * view)91 puglEnterContext(PuglView* view)
92 {
93 #ifdef PUGL_OPENGL
94 wglMakeCurrent(view->impl->hdc, view->impl->hglrc);
95 #endif
96 }
97
98 void
puglLeaveContext(PuglView * view,bool flush)99 puglLeaveContext(PuglView* view, bool flush)
100 {
101 #ifdef PUGL_OPENGL
102 if (flush) {
103 glFlush();
104 SwapBuffers(view->impl->hdc);
105 }
106 wglMakeCurrent(NULL, NULL);
107 #endif
108 }
109
110 int
puglCreateWindow(PuglView * view,const char * title)111 puglCreateWindow(PuglView* view, const char* title)
112 {
113 PuglInternals* impl = view->impl;
114
115 if (!title) {
116 title = "Window";
117 }
118
119 // FIXME: This is nasty, and pugl should not have static anything.
120 // Should class be a parameter? Does this make sense on other platforms?
121 static int wc_count = 0;
122 char classNameBuf[256];
123 std::srand((std::time(NULL)));
124 #ifdef __WINE__
125 std::snprintf(classNameBuf, sizeof(classNameBuf), "%s_%d-%d", title, std::rand(), ++wc_count);
126 #else
127 _snprintf(classNameBuf, sizeof(classNameBuf), "%s_%d-%d", title, std::rand(), ++wc_count);
128 #endif
129 classNameBuf[sizeof(classNameBuf)-1] = '\0';
130
131 impl->wc.style = CS_OWNDC;
132 impl->wc.lpfnWndProc = wndProc;
133 impl->wc.cbClsExtra = 0;
134 impl->wc.cbWndExtra = 0;
135 impl->wc.hInstance = hInstance;
136 impl->wc.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
137 impl->wc.hCursor = LoadCursor(hInstance, IDC_ARROW);
138 impl->wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
139 impl->wc.lpszMenuName = NULL;
140 impl->wc.lpszClassName = strdup(classNameBuf);
141
142 if (!RegisterClass(&impl->wc)) {
143 free((void*)impl->wc.lpszClassName);
144 free(impl);
145 return 1;
146 }
147
148 int winFlags = WS_POPUPWINDOW | WS_CAPTION;
149 if (view->user_resizable) {
150 winFlags |= WS_SIZEBOX;
151 if (view->min_width > 0 && view->min_height > 0) {
152 // Adjust the minimum window size to accomodate requested view size
153 RECT mr = { 0, 0, view->min_width, view->min_height };
154 AdjustWindowRectEx(&mr, view->parent ? WS_CHILD : winFlags, FALSE, WS_EX_TOPMOST);
155 view->min_width = mr.right - mr.left;
156 view->min_height = mr.bottom - mr.top;
157 }
158 }
159
160 // Adjust the window size to accomodate requested view size
161 RECT wr = { 0, 0, view->width, view->height };
162 AdjustWindowRectEx(&wr, view->parent ? WS_CHILD : winFlags, FALSE, WS_EX_TOPMOST);
163
164 impl->hwnd = CreateWindowEx(
165 WS_EX_TOPMOST,
166 classNameBuf, title,
167 view->parent ? (WS_CHILD | WS_VISIBLE) : winFlags,
168 CW_USEDEFAULT, CW_USEDEFAULT, wr.right-wr.left, wr.bottom-wr.top,
169 (HWND)view->parent, NULL, hInstance, NULL);
170
171 if (!impl->hwnd) {
172 UnregisterClass(impl->wc.lpszClassName, NULL);
173 free((void*)impl->wc.lpszClassName);
174 free(impl);
175 return 1;
176 }
177
178 SetWindowLongPtr(impl->hwnd, GWLP_USERDATA, (LONG_PTR)view);
179
180 #ifdef PUGL_OPENGL
181 impl->hdc = GetDC(impl->hwnd);
182
183 PIXELFORMATDESCRIPTOR pfd;
184 ZeroMemory(&pfd, sizeof(pfd));
185 pfd.nSize = sizeof(pfd);
186 pfd.nVersion = 1;
187 pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
188 pfd.iPixelType = PFD_TYPE_RGBA;
189 pfd.cColorBits = 24;
190 pfd.cDepthBits = 16;
191 pfd.iLayerType = PFD_MAIN_PLANE;
192
193 int format = ChoosePixelFormat(impl->hdc, &pfd);
194 SetPixelFormat(impl->hdc, format, &pfd);
195
196 impl->hglrc = wglCreateContext(impl->hdc);
197 if (!impl->hglrc) {
198 ReleaseDC (impl->hwnd, impl->hdc);
199 DestroyWindow (impl->hwnd);
200 UnregisterClass (impl->wc.lpszClassName, NULL);
201 free((void*)impl->wc.lpszClassName);
202 free(impl);
203 return 1;
204 }
205 #endif
206
207 return PUGL_SUCCESS;
208 }
209
210 void
puglShowWindow(PuglView * view)211 puglShowWindow(PuglView* view)
212 {
213 ShowWindow(view->impl->hwnd, SW_SHOWNORMAL);
214 }
215
216 void
puglHideWindow(PuglView * view)217 puglHideWindow(PuglView* view)
218 {
219 ShowWindow(view->impl->hwnd, SW_HIDE);
220 }
221
222 void
puglDestroy(PuglView * view)223 puglDestroy(PuglView* view)
224 {
225 if (!view) {
226 return;
227 }
228
229 PuglInternals* const impl = view->impl;
230
231 #ifdef PUGL_OPENGL
232 wglMakeCurrent(NULL, NULL);
233 wglDeleteContext(impl->hglrc);
234 ReleaseDC(impl->hwnd, impl->hdc);
235 #endif
236 #ifdef PUGL_CAIRO
237 cairo_destroy(impl->buffer_cr);
238 cairo_surface_destroy(impl->buffer_surface);
239 #endif
240 DestroyWindow(impl->hwnd);
241 UnregisterClass(impl->wc.lpszClassName, NULL);
242 free((void*)impl->wc.lpszClassName);
243 free(impl);
244 free(view);
245 }
246
247 static void
puglReshape(PuglView * view,int width,int height)248 puglReshape(PuglView* view, int width, int height)
249 {
250 puglEnterContext(view);
251
252 if (view->reshapeFunc) {
253 view->reshapeFunc(view, width, height);
254 } else {
255 puglDefaultReshape(width, height);
256 }
257
258 view->width = width;
259 view->height = height;
260 }
261
262 static void
puglDisplay(PuglView * view)263 puglDisplay(PuglView* view)
264 {
265 PuglInternals* impl = view->impl;
266 bool success = true;
267
268 puglEnterContext(view);
269
270 #ifdef PUGL_CAIRO
271 cairo_t *wc = NULL;
272 cairo_t *bc = NULL;
273 cairo_surface_t *ws = NULL;
274 cairo_surface_t *bs = NULL;
275
276 HDC hdc = impl->paintHdc;
277 bc = impl->buffer_cr;
278 bs = impl->buffer_surface;
279 int w = view->width;
280 int h = view->height;
281 int bw = bs ? cairo_image_surface_get_width(bs) : -1;
282 int bh = bs ? cairo_image_surface_get_height(bs) : -1;
283 ws = hdc ? cairo_win32_surface_create(hdc) : NULL;
284 wc = ws ? cairo_create(ws) : NULL;
285 if (wc && (!bc || bw != w || bh != h)) {
286 cairo_destroy(bc);
287 cairo_surface_destroy(bs);
288 bs = cairo_surface_create_similar_image(ws, CAIRO_FORMAT_ARGB32, w, h);
289 bc = bs ? cairo_create(bs) : NULL;
290 impl->buffer_cr = bc;
291 impl->buffer_surface = bs;
292 }
293 success = wc != NULL && bc != NULL;
294 #endif
295
296 if (success) {
297 view->redisplay = false;
298 if (view->displayFunc) {
299 view->displayFunc(view);
300 }
301 #ifdef PUGL_CAIRO
302 cairo_set_source_surface(wc, bs, 0, 0);
303 cairo_paint(wc);
304 #endif
305 }
306
307 puglLeaveContext(view, success);
308
309 #ifdef PUGL_CAIRO
310 cairo_destroy(wc);
311 cairo_surface_destroy(ws);
312 #endif
313
314 return;
315 (void)impl;
316 }
317
318 static PuglKey
keySymToSpecial(int sym)319 keySymToSpecial(int sym)
320 {
321 switch (sym) {
322 case VK_F1: return PUGL_KEY_F1;
323 case VK_F2: return PUGL_KEY_F2;
324 case VK_F3: return PUGL_KEY_F3;
325 case VK_F4: return PUGL_KEY_F4;
326 case VK_F5: return PUGL_KEY_F5;
327 case VK_F6: return PUGL_KEY_F6;
328 case VK_F7: return PUGL_KEY_F7;
329 case VK_F8: return PUGL_KEY_F8;
330 case VK_F9: return PUGL_KEY_F9;
331 case VK_F10: return PUGL_KEY_F10;
332 case VK_F11: return PUGL_KEY_F11;
333 case VK_F12: return PUGL_KEY_F12;
334 case VK_LEFT: return PUGL_KEY_LEFT;
335 case VK_UP: return PUGL_KEY_UP;
336 case VK_RIGHT: return PUGL_KEY_RIGHT;
337 case VK_DOWN: return PUGL_KEY_DOWN;
338 case VK_PRIOR: return PUGL_KEY_PAGE_UP;
339 case VK_NEXT: return PUGL_KEY_PAGE_DOWN;
340 case VK_HOME: return PUGL_KEY_HOME;
341 case VK_END: return PUGL_KEY_END;
342 case VK_INSERT: return PUGL_KEY_INSERT;
343 case VK_SHIFT: return PUGL_KEY_SHIFT;
344 case VK_CONTROL: return PUGL_KEY_CTRL;
345 case VK_MENU: return PUGL_KEY_ALT;
346 case VK_LWIN: return PUGL_KEY_SUPER;
347 case VK_RWIN: return PUGL_KEY_SUPER;
348 }
349 return (PuglKey)0;
350 }
351
352 static void
processMouseEvent(PuglView * view,int button,bool press,LPARAM lParam)353 processMouseEvent(PuglView* view, int button, bool press, LPARAM lParam)
354 {
355 view->event_timestamp_ms = GetMessageTime();
356 if (press) {
357 SetCapture(view->impl->hwnd);
358 } else {
359 ReleaseCapture();
360 }
361
362 if (view->mouseFunc) {
363 view->mouseFunc(view, button, press,
364 GET_X_LPARAM(lParam),
365 GET_Y_LPARAM(lParam));
366 }
367 }
368
369 static void
setModifiers(PuglView * view)370 setModifiers(PuglView* view)
371 {
372 view->mods = 0;
373 view->mods |= (GetKeyState(VK_SHIFT) < 0) ? PUGL_MOD_SHIFT : 0;
374 view->mods |= (GetKeyState(VK_CONTROL) < 0) ? PUGL_MOD_CTRL : 0;
375 view->mods |= (GetKeyState(VK_MENU) < 0) ? PUGL_MOD_ALT : 0;
376 view->mods |= (GetKeyState(VK_LWIN) < 0) ? PUGL_MOD_SUPER : 0;
377 view->mods |= (GetKeyState(VK_RWIN) < 0) ? PUGL_MOD_SUPER : 0;
378 }
379
380 static LRESULT
handleMessage(PuglView * view,UINT message,WPARAM wParam,LPARAM lParam)381 handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam)
382 {
383 PAINTSTRUCT ps;
384 PuglKey key;
385 RECT rect;
386 MINMAXINFO* mmi;
387
388 setModifiers(view);
389 switch (message) {
390 case WM_CREATE:
391 case WM_SHOWWINDOW:
392 case WM_SIZE:
393 GetClientRect(view->impl->hwnd, &rect);
394 puglReshape(view, rect.right, rect.bottom);
395 break;
396 case WM_GETMINMAXINFO:
397 mmi = (MINMAXINFO*)lParam;
398 mmi->ptMinTrackSize.x = view->min_width;
399 mmi->ptMinTrackSize.y = view->min_height;
400 break;
401 case WM_PAINT:
402 view->impl->paintHdc = BeginPaint(view->impl->hwnd, &ps);
403 puglDisplay(view);
404 view->impl->paintHdc = NULL;
405 EndPaint(view->impl->hwnd, &ps);
406 break;
407 case WM_MOUSEMOVE:
408 if (view->motionFunc) {
409 view->event_timestamp_ms = GetMessageTime();
410 view->motionFunc(view, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
411 }
412 break;
413 case WM_LBUTTONDOWN:
414 processMouseEvent(view, 1, true, lParam);
415 break;
416 case WM_MBUTTONDOWN:
417 processMouseEvent(view, 2, true, lParam);
418 break;
419 case WM_RBUTTONDOWN:
420 processMouseEvent(view, 3, true, lParam);
421 break;
422 case WM_LBUTTONUP:
423 processMouseEvent(view, 1, false, lParam);
424 break;
425 case WM_MBUTTONUP:
426 processMouseEvent(view, 2, false, lParam);
427 break;
428 case WM_RBUTTONUP:
429 processMouseEvent(view, 3, false, lParam);
430 break;
431 case WM_MOUSEWHEEL:
432 if (view->scrollFunc) {
433 view->event_timestamp_ms = GetMessageTime();
434 POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
435 ScreenToClient(view->impl->hwnd, &pt);
436 view->scrollFunc(
437 view, pt.x, pt.y,
438 0.0f, GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA);
439 }
440 break;
441 case WM_MOUSEHWHEEL:
442 if (view->scrollFunc) {
443 view->event_timestamp_ms = GetMessageTime();
444 POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
445 ScreenToClient(view->impl->hwnd, &pt);
446 view->scrollFunc(
447 view, pt.x, pt.y,
448 GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA, 0.0f);
449 }
450 break;
451 case WM_KEYDOWN:
452 if (view->ignoreKeyRepeat && (lParam & (1 << 30))) {
453 break;
454 } // else nobreak
455 case WM_KEYUP:
456 view->event_timestamp_ms = GetMessageTime();
457 if ((key = keySymToSpecial(wParam))) {
458 if (view->specialFunc) {
459 view->specialFunc(view, message == WM_KEYDOWN, key);
460 }
461 } else if (view->keyboardFunc) {
462 static BYTE kbs[256];
463 if (GetKeyboardState(kbs) != FALSE) {
464 char lb[2];
465 UINT scanCode = (lParam >> 8) & 0xFFFFFF00;
466 if ( 1 == ToAscii(wParam, scanCode, kbs, (LPWORD)lb, 0)) {
467 view->keyboardFunc(view, message == WM_KEYDOWN, (char)lb[0]);
468 }
469 }
470 }
471 break;
472 case WM_QUIT:
473 case PUGL_LOCAL_CLOSE_MSG:
474 if (view->closeFunc) {
475 view->closeFunc(view);
476 view->redisplay = false;
477 }
478 break;
479 default:
480 return DefWindowProc(
481 view->impl->hwnd, message, wParam, lParam);
482 }
483
484 return 0;
485 }
486
487 void
puglGrabFocus(PuglView *)488 puglGrabFocus(PuglView* /*view*/)
489 {
490 // TODO
491 }
492
493 PuglStatus
puglProcessEvents(PuglView * view)494 puglProcessEvents(PuglView* view)
495 {
496 MSG msg;
497 while (PeekMessage(&msg, view->impl->hwnd, 0, 0, PM_REMOVE)) {
498 handleMessage(view, msg.message, msg.wParam, msg.lParam);
499 }
500
501 if (view->redisplay) {
502 InvalidateRect(view->impl->hwnd, NULL, FALSE);
503 }
504
505 return PUGL_SUCCESS;
506 }
507
508 LRESULT CALLBACK
wndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)509 wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
510 {
511 PuglView* view = (PuglView*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
512
513 switch (message) {
514 case WM_CREATE:
515 PostMessage(hwnd, WM_SHOWWINDOW, TRUE, 0);
516 return 0;
517 case WM_CLOSE:
518 PostMessage(hwnd, PUGL_LOCAL_CLOSE_MSG, wParam, lParam);
519 return 0;
520 case WM_DESTROY:
521 return 0;
522 default:
523 if (view && hwnd == view->impl->hwnd) {
524 return handleMessage(view, message, wParam, lParam);
525 } else {
526 return DefWindowProc(hwnd, message, wParam, lParam);
527 }
528 }
529 }
530
531 void
puglPostRedisplay(PuglView * view)532 puglPostRedisplay(PuglView* view)
533 {
534 view->redisplay = true;
535 }
536
537 PuglNativeWindow
puglGetNativeWindow(PuglView * view)538 puglGetNativeWindow(PuglView* view)
539 {
540 return (PuglNativeWindow)view->impl->hwnd;
541 }
542
543 void*
puglGetContext(PuglView * view)544 puglGetContext(PuglView* view)
545 {
546 #ifdef PUGL_CAIRO
547 return view->impl->buffer_cr;
548 #endif
549 return NULL;
550
551 // may be unused
552 (void)view;
553 }
554
555 int
puglUpdateGeometryConstraints(PuglView * view,int min_width,int min_height,bool aspect)556 puglUpdateGeometryConstraints(PuglView* view, int min_width, int min_height, bool aspect)
557 {
558 // TODO
559 return 1;
560
561 (void)view;
562 (void)min_width;
563 (void)min_height;
564 (void)aspect;
565 }
566