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