1 /*
2 Copyright 2012 David Robillard <http://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 /**
18 @file pugl_win.cpp Windows/WGL Pugl Implementation.
19 */
20
21 #include <windows.h>
22 #include <windowsx.h>
23 #include <GL/gl.h>
24
25 #include <stdio.h>
26 #include <stdlib.h>
27
28 #include "pugl_internal.h"
29
30 #ifndef WM_MOUSEWHEEL
31 # define WM_MOUSEWHEEL 0x020A
32 #endif
33 #ifndef WM_MOUSEHWHEEL
34 # define WM_MOUSEHWHEEL 0x020E
35 #endif
36 #ifndef WHEEL_DELTA
37 # define WHEEL_DELTA 120
38 #endif
39 #ifndef GWL_USERDATA
40 # define GWL_USERDATA (-21)
41 #endif
42
43 const int LOCAL_CLOSE_MSG = WM_USER + 50;
44
45 struct PuglInternalsImpl {
46 HWND hwnd;
47 HDC hdc;
48 HGLRC hglrc;
49 WNDCLASS wc;
50 bool keep_aspect;
51 int win_flags;
52 };
53
54 LRESULT CALLBACK
55 wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
56
57 PuglView*
puglCreate(PuglNativeWindow parent,const char * title,int min_width,int min_height,int width,int height,bool resizable,bool ontop,unsigned long transientId)58 puglCreate(PuglNativeWindow parent,
59 const char* title,
60 int min_width,
61 int min_height,
62 int width,
63 int height,
64 bool resizable,
65 bool ontop,
66 unsigned long transientId)
67 {
68 PuglView* view = (PuglView*)calloc(1, sizeof(PuglView));
69 PuglInternals* impl = (PuglInternals*)calloc(1, sizeof(PuglInternals));
70 if (!view || !impl) {
71 return NULL;
72 }
73
74 view->impl = impl;
75 view->width = width;
76 view->height = height;
77 view->ontop = ontop;
78 view->user_resizable = resizable && !parent;
79 view->impl->keep_aspect = min_width != width;
80
81 // FIXME: This is nasty, and pugl should not have static anything.
82 // Should class be a parameter? Does this make sense on other platforms?
83 static int wc_count = 0;
84 int retry = 99;
85 char classNameBuf[256];
86 while (true) {
87 _snprintf(classNameBuf, sizeof(classNameBuf), "x%d%s", wc_count++, title);
88 classNameBuf[sizeof(classNameBuf)-1] = '\0';
89
90 impl->wc.style = CS_OWNDC;
91 impl->wc.lpfnWndProc = wndProc;
92 impl->wc.cbClsExtra = 0;
93 impl->wc.cbWndExtra = 0;
94 impl->wc.hInstance = 0;
95 impl->wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
96 impl->wc.hCursor = LoadCursor(NULL, IDC_ARROW);
97 impl->wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
98 impl->wc.lpszMenuName = NULL;
99 impl->wc.lpszClassName = strdup(classNameBuf);
100
101 if (RegisterClass(&impl->wc)) {
102 break;
103 }
104 if (--retry > 0) {
105 free((void*)impl->wc.lpszClassName);
106 continue;
107 }
108 /* fail */
109 free((void*)impl->wc.lpszClassName);
110 free(impl);
111 free(view);
112 return NULL;
113 }
114
115 if (parent) {
116 view->impl->win_flags = WS_CHILD;
117 } else {
118 view->impl->win_flags = WS_POPUPWINDOW | WS_CAPTION | (view->user_resizable ? WS_SIZEBOX : 0);
119 }
120
121 // Adjust the overall window size to accomodate our requested client size
122 RECT wr = { 0, 0, width, height };
123 AdjustWindowRectEx(&wr, view->impl->win_flags, FALSE, WS_EX_TOPMOST);
124
125 RECT mr = { 0, 0, min_width, min_height };
126 AdjustWindowRectEx(&mr, view->impl->win_flags, FALSE, WS_EX_TOPMOST);
127 view->min_width = mr.right - mr.left;
128 view->min_height = mr.bottom - mr.top;
129
130 impl->hwnd = CreateWindowEx (
131 WS_EX_TOPMOST,
132 classNameBuf, title, (view->user_resizable ? WS_SIZEBOX : 0) |
133 (parent ? (WS_CHILD | WS_VISIBLE) : (WS_POPUPWINDOW | WS_CAPTION)),
134 0, 0, wr.right - wr.left, wr.bottom - wr.top,
135 (HWND)parent, NULL, NULL, NULL);
136
137 if (!impl->hwnd) {
138 free((void*)impl->wc.lpszClassName);
139 free(impl);
140 free(view);
141 return NULL;
142 }
143
144 SetWindowLongPtr(impl->hwnd, GWL_USERDATA, (LONG_PTR)view);
145
146 SetWindowPos (impl->hwnd,
147 ontop ? HWND_TOPMOST : HWND_TOP,
148 0, 0, 0, 0, (ontop ? 0 : SWP_NOACTIVATE) | SWP_ASYNCWINDOWPOS | SWP_NOMOVE | SWP_NOSIZE);
149
150 impl->hdc = GetDC(impl->hwnd);
151
152 PIXELFORMATDESCRIPTOR pfd;
153 ZeroMemory(&pfd, sizeof(pfd));
154 pfd.nSize = sizeof(pfd);
155 pfd.nVersion = 1;
156 pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
157 pfd.iPixelType = PFD_TYPE_RGBA;
158 pfd.cColorBits = 24;
159 pfd.cDepthBits = 16;
160 pfd.iLayerType = PFD_MAIN_PLANE;
161
162 int format = ChoosePixelFormat(impl->hdc, &pfd);
163 SetPixelFormat(impl->hdc, format, &pfd);
164
165 impl->hglrc = wglCreateContext(impl->hdc);
166 if (!impl->hglrc) {
167 ReleaseDC (impl->hwnd, impl->hdc);
168 DestroyWindow (impl->hwnd);
169 UnregisterClass (impl->wc.lpszClassName, NULL);
170 free((void*)impl->wc.lpszClassName);
171 free(impl);
172 free(view);
173 return NULL;
174 }
175 wglMakeCurrent(impl->hdc, impl->hglrc);
176
177 view->width = width;
178 view->height = height;
179
180 return view;
181 }
182
183 void
puglDestroy(PuglView * view)184 puglDestroy(PuglView* view)
185 {
186 if (!view) {
187 return;
188 }
189 wglMakeCurrent(NULL, NULL);
190 wglDeleteContext(view->impl->hglrc);
191 ReleaseDC(view->impl->hwnd, view->impl->hdc);
192 DestroyWindow(view->impl->hwnd);
193 UnregisterClass(view->impl->wc.lpszClassName, NULL);
194 free((void*)view->impl->wc.lpszClassName);
195 free(view->impl);
196 free(view);
197 }
198
199 PUGL_API void
puglShowWindow(PuglView * view)200 puglShowWindow(PuglView* view) {
201 ShowWindow(view->impl->hwnd, WS_VISIBLE);
202 ShowWindow(view->impl->hwnd, SW_RESTORE);
203 //SetForegroundWindow(view->impl->hwnd);
204 UpdateWindow(view->impl->hwnd);
205 }
206
207 PUGL_API void
puglHideWindow(PuglView * view)208 puglHideWindow(PuglView* view) {
209 ShowWindow(view->impl->hwnd, SW_HIDE);
210 UpdateWindow(view->impl->hwnd);
211 }
212
213 static void
puglReshape(PuglView * view,int width,int height)214 puglReshape(PuglView* view, int width, int height)
215 {
216 wglMakeCurrent(view->impl->hdc, view->impl->hglrc);
217
218 if (view->reshapeFunc) {
219 view->reshapeFunc(view, width, height);
220 } else {
221 puglDefaultReshape(view, width, height);
222 }
223
224 wglMakeCurrent(NULL, NULL);
225
226 view->width = width;
227 view->height = height;
228 }
229
230 static void
puglDisplay(PuglView * view)231 puglDisplay(PuglView* view)
232 {
233 wglMakeCurrent(view->impl->hdc, view->impl->hglrc);
234 #if 0
235 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
236 glLoadIdentity();
237 #endif
238
239 view->redisplay = false;
240 if (view->displayFunc) {
241 view->displayFunc(view);
242 }
243
244 glFlush();
245 SwapBuffers(view->impl->hdc);
246 wglMakeCurrent(NULL, NULL);
247 }
248
249 static void
puglResize(PuglView * view)250 puglResize(PuglView* view)
251 {
252 int set_hints; // ignored for now
253 view->resize = false;
254 if (!view->resizeFunc) { return; }
255 /* ask the plugin about the new size */
256 view->resizeFunc(view, &view->width, &view->height, &set_hints);
257
258 HWND parent = GetParent (view->impl->hwnd);
259 if (parent) {
260 puglReshape(view, view->width, view->height);
261 SetWindowPos (view->impl->hwnd, HWND_TOP,
262 0, 0, view->width, view->height,
263 SWP_NOZORDER | SWP_NOMOVE);
264 return;
265 }
266
267 RECT wr = { 0, 0, (long)view->width, (long)view->height };
268 AdjustWindowRectEx(&wr, view->impl->win_flags, FALSE, WS_EX_TOPMOST);
269 SetWindowPos (view->impl->hwnd,
270 view->ontop ? HWND_TOPMOST : HWND_NOTOPMOST,
271 0, 0, wr.right-wr.left, wr.bottom-wr.top,
272 SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOOWNERZORDER /*|SWP_NOZORDER*/);
273 UpdateWindow(view->impl->hwnd);
274
275 /* and call Reshape in GL context */
276 puglReshape(view, view->width, view->height);
277 }
278
279 static PuglKey
keySymToSpecial(int sym)280 keySymToSpecial(int sym)
281 {
282 switch (sym) {
283 case VK_F1: return PUGL_KEY_F1;
284 case VK_F2: return PUGL_KEY_F2;
285 case VK_F3: return PUGL_KEY_F3;
286 case VK_F4: return PUGL_KEY_F4;
287 case VK_F5: return PUGL_KEY_F5;
288 case VK_F6: return PUGL_KEY_F6;
289 case VK_F7: return PUGL_KEY_F7;
290 case VK_F8: return PUGL_KEY_F8;
291 case VK_F9: return PUGL_KEY_F9;
292 case VK_F10: return PUGL_KEY_F10;
293 case VK_F11: return PUGL_KEY_F11;
294 case VK_F12: return PUGL_KEY_F12;
295 case VK_LEFT: return PUGL_KEY_LEFT;
296 case VK_UP: return PUGL_KEY_UP;
297 case VK_RIGHT: return PUGL_KEY_RIGHT;
298 case VK_DOWN: return PUGL_KEY_DOWN;
299 case VK_PRIOR: return PUGL_KEY_PAGE_UP;
300 case VK_NEXT: return PUGL_KEY_PAGE_DOWN;
301 case VK_HOME: return PUGL_KEY_HOME;
302 case VK_END: return PUGL_KEY_END;
303 case VK_INSERT: return PUGL_KEY_INSERT;
304 case VK_SHIFT: return PUGL_KEY_SHIFT;
305 case VK_CONTROL: return PUGL_KEY_CTRL;
306 case VK_MENU: return PUGL_KEY_ALT;
307 case VK_LWIN: return PUGL_KEY_SUPER;
308 case VK_RWIN: return PUGL_KEY_SUPER;
309 }
310 return (PuglKey)0;
311 }
312
313 static void
processMouseEvent(PuglView * view,int button,bool press,LPARAM lParam)314 processMouseEvent(PuglView* view, int button, bool press, LPARAM lParam)
315 {
316 view->event_timestamp_ms = GetMessageTime();
317 if (GetFocus() != view->impl->hwnd) {
318 // focus is needed to receive mouse-wheel events
319 SetFocus (view->impl->hwnd);
320 }
321
322 if (press) {
323 SetCapture(view->impl->hwnd);
324 } else {
325 ReleaseCapture();
326 }
327
328 if (view->mouseFunc) {
329 view->mouseFunc(view, button, press,
330 GET_X_LPARAM(lParam),
331 GET_Y_LPARAM(lParam));
332 }
333 }
334
335 static void
setModifiers(PuglView * view)336 setModifiers(PuglView* view)
337 {
338 view->mods = 0;
339 view->mods |= (GetKeyState(VK_SHIFT) < 0) ? PUGL_MOD_SHIFT : 0;
340 view->mods |= (GetKeyState(VK_CONTROL) < 0) ? PUGL_MOD_CTRL : 0;
341 view->mods |= (GetKeyState(VK_MENU) < 0) ? PUGL_MOD_ALT : 0;
342 view->mods |= (GetKeyState(VK_LWIN) < 0) ? PUGL_MOD_SUPER : 0;
343 view->mods |= (GetKeyState(VK_RWIN) < 0) ? PUGL_MOD_SUPER : 0;
344 }
345
346 static LRESULT
handleMessage(PuglView * view,UINT message,WPARAM wParam,LPARAM lParam)347 handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam)
348 {
349 PAINTSTRUCT ps;
350 PuglKey key;
351
352 setModifiers(view);
353 switch (message) {
354 case WM_CREATE:
355 case WM_SHOWWINDOW:
356 case WM_SIZE:
357 {
358 RECT rect;
359 GetClientRect(view->impl->hwnd, &rect);
360 puglReshape(view, rect.right, rect.bottom);
361 view->width = rect.right;
362 view->height = rect.bottom;
363 }
364 break;
365 case WM_SIZING:
366 if (view->impl->keep_aspect) {
367 float aspect = view->min_width / (float)view->min_height;
368 RECT* rect = (RECT*)lParam;
369 switch ((int)wParam) {
370 case WMSZ_LEFT:
371 case WMSZ_RIGHT:
372 case WMSZ_BOTTOMLEFT:
373 case WMSZ_BOTTOMRIGHT:
374 rect->bottom = rect->top + (rect->right - rect->left) / aspect;
375 break;
376 case WMSZ_TOP:
377 case WMSZ_BOTTOM:
378 case WMSZ_TOPRIGHT:
379 rect->right = rect->left + (rect->bottom - rect->top) * aspect;
380 break;
381 case WMSZ_TOPLEFT:
382 rect->left = rect->right - (rect->bottom - rect->top) * aspect;
383 break;
384 }
385 return TRUE;
386 }
387 break;
388 case WM_GETMINMAXINFO:
389 {
390 MINMAXINFO *mmi = (MINMAXINFO*)lParam;
391 mmi->ptMinTrackSize.x = view->min_width;
392 mmi->ptMinTrackSize.y = view->min_height;
393 }
394 break;
395 case WM_PAINT:
396 BeginPaint(view->impl->hwnd, &ps);
397 puglDisplay(view);
398 EndPaint(view->impl->hwnd, &ps);
399 break;
400 case WM_MOUSEMOVE:
401 if (view->motionFunc) {
402 view->motionFunc(view, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
403 }
404 break;
405 case WM_LBUTTONDOWN:
406 processMouseEvent(view, 1, true, lParam);
407 break;
408 case WM_MBUTTONDOWN:
409 processMouseEvent(view, 2, true, lParam);
410 break;
411 case WM_RBUTTONDOWN:
412 processMouseEvent(view, 3, true, lParam);
413 break;
414 case WM_LBUTTONUP:
415 processMouseEvent(view, 1, false, lParam);
416 break;
417 case WM_MBUTTONUP:
418 processMouseEvent(view, 2, false, lParam);
419 break;
420 case WM_RBUTTONUP:
421 processMouseEvent(view, 3, false, lParam);
422 break;
423 case WM_MOUSEWHEEL:
424 if (view->scrollFunc) {
425 view->event_timestamp_ms = GetMessageTime();
426 POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
427 ScreenToClient(view->impl->hwnd, &pt);
428 view->scrollFunc(
429 view, pt.x, pt.y,
430 0, GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA);
431 }
432 break;
433 case WM_MOUSEHWHEEL:
434 if (view->scrollFunc) {
435 view->event_timestamp_ms = GetMessageTime();
436 POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
437 ScreenToClient(view->impl->hwnd, &pt);
438 view->scrollFunc(
439 view, pt.x, pt.y,
440 GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA, 0);
441 }
442 break;
443 case WM_KEYDOWN:
444 if (view->ignoreKeyRepeat && (lParam & (1 << 30))) {
445 break;
446 } // else nobreak
447 case WM_KEYUP:
448 view->event_timestamp_ms = GetMessageTime();
449 if ((key = keySymToSpecial(wParam))) {
450 if (view->specialFunc) {
451 view->specialFunc(view, message == WM_KEYDOWN, key);
452 }
453 } else if (view->keyboardFunc) {
454 static BYTE kbs[256];
455 if (GetKeyboardState(kbs) != FALSE) {
456 char lb[2];
457 UINT scanCode = (lParam >> 8) & 0xFFFFFF00;
458 if ( 1 == ToAscii(wParam, scanCode, kbs, (LPWORD)lb, 0)) {
459 view->keyboardFunc(view, message == WM_KEYDOWN, (char)lb[0]);
460 }
461 }
462 }
463 break;
464 case WM_SETFOCUS:
465 if (view->focusFunc) {
466 view->focusFunc(view, TRUE);
467 }
468 return DefWindowProc(view->impl->hwnd, message, wParam, lParam);
469 case WM_KILLFOCUS:
470 if (view->focusFunc) {
471 view->focusFunc(view, FALSE);
472 }
473 return DefWindowProc(view->impl->hwnd, message, wParam, lParam);
474 case WM_QUIT:
475 case LOCAL_CLOSE_MSG:
476 if (view->closeFunc) {
477 view->closeFunc(view);
478 }
479 break;
480 default:
481 return DefWindowProc(
482 view->impl->hwnd, message, wParam, lParam);
483 }
484
485 return 0;
486 }
487
488 PuglStatus
puglProcessEvents(PuglView * view)489 puglProcessEvents(PuglView* view)
490 {
491 MSG msg;
492 while (PeekMessage(&msg, view->impl->hwnd, 0, 0, PM_REMOVE)) {
493 handleMessage(view, msg.message, msg.wParam, msg.lParam);
494 }
495
496 if (view->resize) {
497 puglResize(view);
498 }
499
500 if (view->redisplay) {
501 InvalidateRect(view->impl->hwnd, NULL, FALSE);
502 }
503
504 return PUGL_SUCCESS;
505 }
506
507 LRESULT CALLBACK
wndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)508 wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
509 {
510 PuglView* view = (PuglView*)GetWindowLongPtr(hwnd, GWL_USERDATA);
511 switch (message) {
512 case WM_CREATE:
513 PostMessage(hwnd, WM_SHOWWINDOW, TRUE, 0);
514 return 0;
515 case WM_CLOSE:
516 PostMessage(hwnd, LOCAL_CLOSE_MSG, wParam, lParam);
517 return 0;
518 case WM_DESTROY:
519 return 0;
520 default:
521 if (view && hwnd == view->impl->hwnd) {
522 return handleMessage(view, message, wParam, lParam);
523 } else {
524 return DefWindowProc(hwnd, message, wParam, lParam);
525 }
526 }
527 }
528
529 void
puglPostRedisplay(PuglView * view)530 puglPostRedisplay(PuglView* view)
531 {
532 view->redisplay = true;
533 }
534
535 void
puglPostResize(PuglView * view)536 puglPostResize(PuglView* view)
537 {
538 view->resize = true;
539 }
540
541 PuglNativeWindow
puglGetNativeWindow(PuglView * view)542 puglGetNativeWindow(PuglView* view)
543 {
544 return (PuglNativeWindow)view->impl->hwnd;
545 }
546
547 int
puglOpenFileDialog(PuglView * view,const char * title)548 puglOpenFileDialog(PuglView* view, const char *title)
549 {
550 char fn[1024] = "";
551 OPENFILENAME ofn;
552 ZeroMemory(&ofn, sizeof(OPENFILENAME));
553 ofn.lStructSize = sizeof(OPENFILENAME);
554 ofn.lpstrFile = fn;
555 ofn.nMaxFile = 1024;
556 ofn.lpstrTitle = title;
557 ofn.lpstrFilter = "All\0*.*\0";
558 ofn.nFilterIndex = 0;
559 ofn.lpstrInitialDir = 0;
560 ofn.Flags = OFN_ENABLESIZING | OFN_FILEMUSTEXIST | OFN_NONETWORKBUTTON | OFN_HIDEREADONLY | OFN_READONLY;
561
562 // TODO look into async ofn.lpfnHook, OFN_ENABLEHOOK
563 // UINT_PTR CALLBACK openFileHook(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
564 // yet it seems GetOpenFIleName itself won't return anyway.
565
566 ofn.hwndOwner = view->impl->hwnd; // modal
567
568 if (GetOpenFileName (&ofn)) {
569 if (view->fileSelectedFunc) {
570 view->fileSelectedFunc(view, fn);
571 }
572 } else {
573 if (view->fileSelectedFunc) {
574 view->fileSelectedFunc(view, NULL);
575 }
576 }
577 return 0;
578 }
579
580 int
puglUpdateGeometryConstraints(PuglView * view,int min_width,int min_height,bool aspect)581 puglUpdateGeometryConstraints(PuglView* view, int min_width, int min_height, bool aspect)
582 {
583 view->min_width = min_width;
584 view->min_height = min_height;
585 if (view->width < min_width || view->height < min_height) {
586 if (!view->resize) {
587 if (view->width < min_width) view->width = min_width;
588 if (view->height < min_height) view->height = min_height;
589 view->resize = true;
590 // TODO: trigger resize
591 // (not always needed since this fn is usually called in response to a resize)
592 }
593 }
594 return 0;
595 }
596