1 /// Win32 Window interface for Gfx-d
2 module gfx.window.win32;
3
4 version(Windows):
5
6 import core.sys.windows.windows;
7 import gfx.graal.presentation;
8 import gfx.window;
9 import gfx.window.keys;
10
11 import std.exception;
12 import gfx.core.log : LogTag;
13
14 enum gfxW32Tag = 0x0800_0000;
15 immutable gfxW32Log = LogTag("GFX-W32", gfxW32Tag);
16
17 class Win32Display : Display
18 {
19 import gfx.core.rc : atomicRcCode;
20 import gfx.graal : Backend, Instance;
21
22 mixin(atomicRcCode);
23
24 private Win32Window[HWND] _windows;
25 private Window[] _iwindows;
26 private Instance _instance;
27
28 this(DisplayCreateInfo createInfo) {
29 assert(!g_dpy);
30 g_dpy = this;
31
32 registerWindowClass();
33 assert(!_instance);
34
35 foreach (b; createInfo.backendCreateOrder) {
36 final switch (b) {
37 case Backend.vulkan:
38 try {
39 import gfx.vulkan : createVulkanInstance, debugReportInstanceExtensions,
40 lunarGValidationLayers, VulkanCreateInfo, vulkanInit;
41 import gfx.vulkan.wsi : win32SurfaceInstanceExtensions;
42
43 gfxW32Log.trace("Attempting to instantiate Vulkan");
44 vulkanInit();
45 VulkanCreateInfo vci;
46 vci.mandatoryExtensions = win32SurfaceInstanceExtensions;
47 vci.optionalExtensions = createInfo.debugCallbackEnabled ?
48 debugReportInstanceExtensions : [];
49 vci.optionalLayers = createInfo.validationEnabled ?
50 lunarGValidationLayers : [];
51 _instance = createVulkanInstance(vci);
52 gfxW32Log.info("Creating a Vulkan instance");
53 }
54 catch (Exception ex) {
55 gfxW32Log.warningf("Vulkan is not available. %s", ex.msg);
56 }
57 break;
58 case Backend.gl3:
59 try {
60 import gfx.core.rc : makeRc;
61 import gfx.gl3 : GlInstance;
62 import gfx.gl3.context : GlAttribs;
63 import gfx.window.win32.context : Win32GlContext;
64
65 gfxW32Log.trace("Attempting to instantiate OpenGL");
66 auto w = new Win32Window(this, "", true);
67 scope(exit) w.close();
68 auto ctx = makeRc!Win32GlContext(GlAttribs.init, w.hWnd);
69 gfxW32Log.trace("Creating an OpenGL instance");
70 _instance = new GlInstance(ctx);
71 }
72 catch (Exception ex) {
73 gfxW32Log.warningf("OpenGL is not available. %s", ex.msg);
74 }
75 break;
76 }
77 if (_instance) break;
78 }
79
80 if (!_instance) {
81 throw new Exception("Could not instantiate a backend");
82 }
83 }
84
85 override void dispose() {
86 auto openWindows = _iwindows;
87 foreach (w; openWindows) { // Window.close modifies _iwindows
88 w.close();
89 }
90 assert(!_iwindows.length);
91 }
92
93 override @property Instance instance() {
94 return _instance;
95 }
96 override @property Window[] windows() {
97 return _iwindows;
98 }
99 override Window createWindow(string title) {
100 return new Win32Window(this, title, false);
101 }
102 override void pollAndDispatch() {
103 MSG msg;
104 while (PeekMessageW(&msg, null, 0, 0, PM_REMOVE)) {
105 TranslateMessage(&msg);
106 DispatchMessageW(&msg);
107 }
108 }
109
110
111 private void registerWindow(HWND hWnd, Win32Window w)
112 {
113 _windows[hWnd] = w;
114 _iwindows ~= w;
115 }
116
117 private void unregisterWindow(HWND hWnd)
118 {
119 import std.algorithm : remove;
120 _windows.remove(hWnd);
121 _iwindows = _iwindows.remove!((Window w) {
122 return (cast(Win32Window)w).hWnd == hWnd;
123 });
124 }
125
126 private Win32Window findWithHWnd(HWND hWnd)
127 {
128 Win32Window *w = (hWnd in _windows);
129 return w ? *w : null;
130 }
131
132 private bool wndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, out LRESULT res)
133 {
134 res = 0;
135
136 Win32Window wnd = findWithHWnd(hWnd);
137 if (!wnd) {
138 return false;
139 }
140
141 switch(msg)
142 {
143 case WM_CLOSE:
144 if (wnd.closeHandler) {
145 wnd._closeFlag = wnd.closeHandler();
146 }
147 else {
148 wnd._closeFlag = true;
149 }
150 return true;
151 case WM_SIZE:
152 return wnd.handleResize(wParam, lParam);
153 case WM_LBUTTONDOWN:
154 case WM_LBUTTONUP:
155 case WM_MBUTTONDOWN:
156 case WM_MBUTTONUP:
157 case WM_RBUTTONDOWN:
158 case WM_RBUTTONUP:
159 case WM_MOUSEMOVE:
160 case WM_MOUSELEAVE:
161 return wnd.handleMouse(msg, wParam, lParam);
162 case WM_KEYDOWN:
163 case WM_KEYUP:
164 case WM_CHAR:
165 return wnd.handleKey(msg, wParam, lParam);
166 default:
167 return false;
168 }
169 }
170
171 }
172
173 class Win32Window : Window
174 {
175 import gfx.core.rc : Rc;
176
177 private Win32Display dpy;
178 private HWND hWnd;
179 private Rc!Surface gfxSurface;
180
181 private ResizeHandler resizeHandler;
182 private MouseHandler moveHandler;
183 private MouseHandler onHandler;
184 private MouseHandler offHandler;
185 private KeyHandler keyOnHandler;
186 private KeyHandler keyOffHandler;
187 private CloseHandler closeHandler;
188 private bool mouseOut;
189 private string tit;
190 private uint width;
191 private uint height;
192 private bool _closeFlag;
193 private bool dummy;
194
195 this(Win32Display dpy, string title, bool dummy=false)
196 {
197 import std.utf : toUTF16z;
198
199 this.dpy = dpy;
200 this.tit = title;
201 this.dummy = dummy;
202
203 HINSTANCE hInstance = GetModuleHandle(null);
204
205 hWnd = enforce(
206 CreateWindowEx(
207 WS_EX_CLIENTEDGE, // | WS_EX_LAYERED,
208 wndClassName.toUTF16z,
209 "null",
210 WS_OVERLAPPEDWINDOW,
211 CW_USEDEFAULT,
212 CW_USEDEFAULT,
213 CW_USEDEFAULT,
214 CW_USEDEFAULT,
215 null, null, hInstance, null
216 ),
217 "could not create win32 window"
218 );
219
220 if (dummy) return;
221
222 SetWindowTextW(hWnd, tit.toUTF16z);
223
224 // What follow is a non-portable way to have alpha value of framebuffer used in desktop composition
225 // (by default composition makes window completely opaque)
226 // only works on Windows 7 and therefore disabled
227
228 // DWM_BLURBEHIND bb;
229 // bb.dwFlags = DWM_BB_ENABLE;
230 // bb.fEnable = TRUE;
231 // bb.hRgnBlur = NULL;
232 // DwmEnableBlurBehindWindow(hWnd, &bb);
233 // MARGINS m = { -1 };
234 // DwmExtendFrameIntoClientArea(hWnd, &m);
235
236 import gfx.graal : Backend;
237 final switch (dpy.instance.backend) {
238 case Backend.vulkan:
239 import gfx.vulkan.wsi : createVulkanWin32Surface;
240 gfxSurface = createVulkanWin32Surface(dpy.instance, GetModuleHandle(null), hWnd);
241 break;
242
243 case Backend.gl3:
244 import gfx.gl3 : GlInstance;
245 import gfx.gl3.swapchain : GlSurface;
246 import gfx.window.win32.context : Win32GlContext;
247
248 gfxSurface = new GlSurface(cast(size_t)hWnd);
249 auto glInst = cast(GlInstance)dpy.instance;
250 auto ctx = cast(Win32GlContext)glInst.ctx;
251 ctx.setPixelFormat(hWnd);
252 ctx.makeCurrent(cast(size_t)hWnd);
253 break;
254 }
255
256 dpy.registerWindow(hWnd, this);
257 }
258
259 override void show(uint width, uint height)
260 {
261 if (dummy) return;
262 RECT r;
263 r.right = width;
264 r.bottom = height;
265 AdjustWindowRectEx(&r, WS_BORDER | WS_CAPTION, FALSE, 0);
266 SetWindowPos(hWnd, HWND_TOP, 0, 0, r.right - r.left, r.bottom - r.top, SWP_SHOWWINDOW | SWP_NOMOVE);
267 }
268
269 override void close() {
270 DestroyWindow(hWnd);
271 if (dummy) return;
272 gfxSurface.unload();
273 dpy.unregisterWindow(hWnd);
274 }
275
276 override @property bool closeFlag() const {
277 return _closeFlag;
278 }
279 override @property void closeFlag(in bool flag) {
280 _closeFlag = flag;
281 }
282
283 override @property string title()
284 {
285 return tit;
286 }
287
288 override void setTitle(string title)
289 {
290 import std.utf : toUTF16z;
291
292 tit = title;
293 SetWindowTextW(hWnd, tit.toUTF16z);
294 }
295
296 override @property void onResize(ResizeHandler handler) {
297 resizeHandler = handler;
298 }
299 override @property void onMouseMove(MouseHandler handler) {
300 moveHandler = handler;
301 }
302 override @property void onMouseOn(MouseHandler handler) {
303 onHandler = handler;
304 }
305 override @property void onMouseOff(MouseHandler handler) {
306 offHandler = handler;
307 }
308 override @property void onKeyOn(KeyHandler handler) {
309 keyOnHandler = handler;
310 }
311 override @property void onKeyOff(KeyHandler handler) {
312 keyOffHandler = handler;
313 }
314 override @property void onClose(CloseHandler handler) {
315 closeHandler = handler;
316 }
317
318 override @property Surface surface() {
319 return gfxSurface.obj;
320 }
321
322 bool handleResize(WPARAM wParam, LPARAM lParam)
323 {
324 switch (wParam)
325 {
326 case SIZE_MAXSHOW:
327 case SIZE_MAXHIDE:
328 case SIZE_MINIMIZED:
329 return false;
330 case SIZE_MAXIMIZED:
331 case SIZE_RESTORED:
332 const w = GET_X_LPARAM(lParam);
333 const h = GET_Y_LPARAM(lParam);
334 if (w != width || h != height) {
335 width = w;
336 height = h;
337 if (resizeHandler) resizeHandler(width, height);
338 }
339 return true;
340 default:
341 return false;
342 }
343 }
344
345 private bool handleMouse(UINT msg, WPARAM wParam, LPARAM lParam)
346 {
347 const x = GET_X_LPARAM(lParam);
348 const y = GET_Y_LPARAM(lParam);
349 const mods = keyMods;
350
351 switch (msg) {
352 case WM_LBUTTONDOWN:
353 case WM_MBUTTONDOWN:
354 case WM_RBUTTONDOWN:
355 if (onHandler) {
356 onHandler(MouseEvent(x, y, mods));
357 return true;
358 }
359 break;
360 case WM_LBUTTONUP:
361 case WM_MBUTTONUP:
362 case WM_RBUTTONUP:
363 if (offHandler) {
364 offHandler(MouseEvent(x, y, mods));
365 return true;
366 }
367 break;
368 case WM_MOUSEMOVE:
369 if (mouseOut) {
370 mouseOut = false;
371 // mouse was out, deliver enter event (TODO)
372 // and register for leave event
373 TRACKMOUSEEVENT tm;
374 tm.cbSize = TRACKMOUSEEVENT.sizeof;
375 tm.dwFlags = TME_LEAVE;
376 tm.hwndTrack = hWnd;
377 tm.dwHoverTime = 0;
378 TrackMouseEvent(&tm);
379 }
380 if (moveHandler) {
381 moveHandler(MouseEvent(x, y, mods));
382 return true;
383 }
384 break;
385 case WM_MOUSELEAVE:
386 mouseOut = true;
387 // TODO: deliver leave event
388 break;
389 default:
390 break;
391 }
392
393 return false;
394 }
395
396 private bool handleKey(UINT msg, WPARAM wParam, LPARAM lParam)
397 {
398 import gfx.window.win32.keymap : getKeysym, getKeycode;
399 import std.conv : to;
400
401 assert(msg != WM_CHAR, "WM_CHAR must be intercepted before delivery!");
402 assert(msg == WM_KEYDOWN || msg == WM_KEYUP, "Wrong delivery");
403
404 if (wParam < 0 || wParam >= 256) {
405 gfxW32Log.warningf("key %s received a virtual key out of byte boundary: %s",
406 msg == WM_KEYDOWN?"down":"up", wParam);
407 return false;
408 }
409
410 const sym = getKeysym(wParam);
411 const scancode = cast(ubyte)((lParam & scanCodeMask) >> 16);
412 const code = getKeycode(scancode);
413
414 KeyHandler handler;
415 string text;
416
417 switch (msg) {
418 case WM_KEYDOWN:
419 text = peekCharMsg().to!string;
420 // const repeat = ((lParam & previousStateMask) != 0);
421 // const repeatCount = lParam & repeatCountMask;
422 handler = keyOnHandler;
423 break;
424 case WM_KEYUP:
425 handler = keyOffHandler;
426 break;
427 default:
428 break;
429 }
430
431 if (handler) {
432 handler(KeyEvent(sym, code, keyMods, text));
433 return true;
434 }
435 else {
436 return false;
437 }
438 }
439
440 wstring peekCharMsg()
441 {
442 MSG msg;
443 if (PeekMessage(&msg, hWnd, WM_CHAR, WM_CHAR, PM_REMOVE))
444 {
445 immutable auto count = msg.lParam & repeatCountMask;
446 auto str = new wchar[count];
447 str[] = cast(wchar)msg.wParam;
448
449 import std.exception : assumeUnique;
450 return assumeUnique(str);
451 }
452 return "";
453 }
454
455 }
456
457 private __gshared Win32Display g_dpy;
458 package immutable wstring wndClassName = "GfxDWin32WindowClass"w;
459
460
461 package void registerWindowClass()
462 {
463 import std.utf : toUTF16z;
464
465 static bool registered;
466 if (registered) return;
467 registered = true;
468
469 WNDCLASSEX wc;
470 wc.cbSize = WNDCLASSEX.sizeof;
471 wc.style = CS_OWNDC;
472 wc.lpfnWndProc = &win32WndProc;
473 wc.cbClsExtra = 0;
474 wc.cbWndExtra = 0;
475 wc.hInstance = GetModuleHandle(null);
476 wc.hIcon = LoadIcon(null, IDI_APPLICATION);
477 wc.hCursor = LoadCursor(null, IDC_ARROW);
478 wc.hbrBackground = null;
479 wc.lpszMenuName = null;
480 wc.lpszClassName = wndClassName.toUTF16z;
481 wc.hIconSm = LoadIcon(null, IDI_APPLICATION);
482
483 enforce(RegisterClassExW(&wc), "could not register win32 window class");
484 }
485
486 private enum uint previousStateMask = 0x40000000;
487 private enum uint repeatCountMask = 0x0000ffff;
488 private enum uint scanCodeMask = 0x00ff0000;
489
490 private int GET_X_LPARAM(in LPARAM lp) pure
491 {
492 return cast(int)(lp & 0x0000ffff);
493 }
494
495 private int GET_Y_LPARAM(in LPARAM lp) pure
496 {
497 return cast(int)((lp & 0xffff0000) >> 16);
498 }
499
500 extern(Windows) nothrow
501 private LRESULT win32WndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
502 {
503 import std.exception : collectExceptionMsg;
504 LRESULT res;
505 try
506 {
507 if (!g_dpy || !g_dpy.wndProc(hwnd, msg, wParam, lParam, res))
508 {
509 res = DefWindowProc(hwnd, msg, wParam, lParam);
510 }
511 }
512 catch(Exception ex)
513 {
514 try { gfxW32Log.errorf("Win32 Proc exception: %s", ex.msg); }
515 catch(Exception) {}
516 }
517 return res;
518 }
519
520 @property KeyMods keyMods()
521 {
522 KeyMods mods = KeyMods.none;
523
524 if (GetKeyState(VK_LSHIFT) & 0x8000) mods |= KeyMods.leftShift;
525 if (GetKeyState(VK_LCONTROL) & 0x8000) mods |= KeyMods.leftCtrl;
526 if (GetKeyState(VK_LMENU) & 0x8000) mods |= KeyMods.leftAlt;
527 if (GetKeyState(VK_LWIN) & 0x8000) mods |= KeyMods.leftSuper;
528
529 if (GetKeyState(VK_RSHIFT) & 0x8000) mods |= KeyMods.rightShift;
530 if (GetKeyState(VK_RCONTROL) & 0x8000) mods |= KeyMods.rightCtrl;
531 if (GetKeyState(VK_RMENU) & 0x8000) mods |= KeyMods.rightAlt;
532 if (GetKeyState(VK_RWIN) & 0x8000) mods |= KeyMods.rightSuper;
533
534 return mods;
535 }
536 // a few missing bindings
537
538 private:
539
540 // see comment in Win32Window ctor
541
542 // struct DWM_BLURBEHIND {
543 // DWORD dwFlags;
544 // BOOL fEnable;
545 // HRGN hRgnBlur;
546 // BOOL fTransitionOnMaximized;
547 // }
548
549 // struct MARGINS {
550 // int left; int right; int top; int bottom;
551 // }
552
553 // enum DWM_BB_ENABLE = 0x00000001;
554
555 // extern(Windows) HRESULT DwmEnableBlurBehindWindow(
556 // HWND hWnd,
557 // const(DWM_BLURBEHIND)* pBlurBehind
558 // );
559 // extern(Windows) HRESULT DwmExtendFrameIntoClientArea(
560 // HWND hWnd,
561 // const(MARGINS)* pMarInset
562 // );