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