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 // );