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 
10 import std.exception;
11 import gfx.core.log : LogTag;
12 
13 enum gfxW32Tag = 0x0800_0000;
14 immutable gfxW32Log = LogTag("GFX-W32", gfxW32Tag);
15 
16 class Win32Display : Display
17 {
18     import gfx.core.rc : atomicRcCode;
19     import gfx.graal : Backend, Instance;
20 
21     mixin(atomicRcCode);
22 
23     private Win32Window[HWND] _windows;
24     private Window[] _iwindows;
25     private Instance _instance;
26 
27     this(in Backend[] loadOrder) {
28         assert(!g_dpy);
29         g_dpy = this;
30 
31         registerWindowClass();
32         assert(!_instance);
33 
34         foreach (b; loadOrder) {
35             final switch (b) {
36             case Backend.vulkan:
37                 try {
38                     gfxW32WndLog.trace("Attempting to instantiate Vulkan");
39                     import gfx.vulkan : createVulkanInstance, vulkanInit;
40                     vulkanInit();
41                     _instance = createVulkanInstance();
42                     gfxW32WndLog.info("Creating a Vulkan instance");
43                 }
44                 catch (Exception ex) {
45                     gfxW32WndLog.warningf("Vulkan is not available. %s", ex.msg);
46                 }
47                 break;
48             case Backend.gl3:
49                 try {
50                     import gfx.core.rc : makeRc;
51                     import gfx.gl3 : GlInstance;
52                     import gfx.gl3.context : GlAttribs;
53                     import gfx.window.win32.context : Win32GlContext;
54 
55                     gfxW32WndLog.trace("Attempting to instantiate OpenGL");
56                     auto w = new Win32Window(this, true);
57                     scope(exit) w.close();
58                     auto ctx = makeRc!Win32GlContext(GlAttribs.init, w.hWnd);
59                     gfxW32WndLog.trace("Creating an OpenGL instance");
60                     _instance = new GlInstance(ctx);
61                 }
62                 catch (Exception ex) {
63                     gfxW32WndLog.warningf("OpenGL is not available. %s", ex.msg);
64                 }
65                 break;
66             }
67             if (_instance) break;
68         }
69 
70         if (!_instance) {
71             throw new Exception("Could not instantiate a backend");
72         }
73     }
74 
75     override void dispose() {
76         auto openWindows = _iwindows;
77         foreach (w; openWindows) { // Window.close modifies _iwindows
78             w.close();
79         }
80         assert(!_iwindows.length);
81     }
82 
83     override @property Instance instance() {
84         return _instance;
85     }
86     override @property Window[] windows() {
87         return _iwindows;
88     }
89     override Window createWindow() {
90         return new Win32Window(this, false);
91     }
92     override void pollAndDispatch() {
93         MSG msg;
94         while (PeekMessageW(&msg, null, 0, 0, PM_REMOVE)) {
95             TranslateMessage(&msg);
96             DispatchMessageW(&msg);
97         }
98     }
99 
100 
101     private void registerWindow(HWND hWnd, Win32Window w)
102     {
103         _windows[hWnd] = w;
104         _iwindows ~= w;
105     }
106 
107     private void unregisterWindow(HWND hWnd)
108     {
109         import std.algorithm : remove;
110         _windows.remove(hWnd);
111         _iwindows = _iwindows.remove!((Window w) {
112             return (cast(Win32Window)w).hWnd == hWnd;
113         });
114     }
115 
116     private Win32Window findWithHWnd(HWND hWnd)
117     {
118         Win32Window *w = (hWnd in _windows);
119         return w ? *w : null;
120     }
121 
122     private bool wndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, out LRESULT res)
123     {
124         res = 0;
125 
126         Win32Window wnd = findWithHWnd(hWnd);
127         if (!wnd) {
128             return false;
129         }
130 
131         switch(msg)
132         {
133             case WM_CLOSE:
134                 if (wnd.closeHandler) {
135                     wnd._closeFlag = wnd.closeHandler();
136                 }
137                 else {
138                     wnd._closeFlag = true;
139                 }
140                 return true;
141             case WM_LBUTTONDOWN:
142             case WM_LBUTTONUP:
143             case WM_MBUTTONDOWN:
144             case WM_MBUTTONUP:
145             case WM_RBUTTONDOWN:
146             case WM_RBUTTONUP:
147             case WM_MOUSEMOVE:
148             case WM_MOUSELEAVE:
149                 return wnd.handleMouse(msg, wParam, lParam);
150             case WM_KEYDOWN:
151             case WM_KEYUP:
152             case WM_CHAR:
153                 return wnd.handleKey(msg, wParam, lParam);
154             default:
155                 return false;
156         }
157     }
158 
159 }
160 
161 class Win32Window : Window
162 {
163     import gfx.core.rc : Rc;
164 
165     private Win32Display dpy;
166     private HWND hWnd;
167     private Rc!Surface gfxSurface;
168 
169     private MouseHandler moveHandler;
170     private MouseHandler onHandler;
171     private MouseHandler offHandler;
172     private KeyHandler keyOnHandler;
173     private KeyHandler keyOffHandler;
174     private CloseHandler closeHandler;
175     private bool mouseOut;
176     private bool _closeFlag;
177     private bool dummy;
178 
179     this(Win32Display dpy, bool dummy=false)
180     {
181         import std.utf : toUTF16z;
182 
183         this.dpy = dpy;
184         this.dummy = dummy;
185 
186         HINSTANCE hInstance = GetModuleHandle(null);
187 
188         hWnd = enforce(
189             CreateWindowEx(
190                 WS_EX_CLIENTEDGE, // | WS_EX_LAYERED,
191                 wndClassName.toUTF16z,
192                 "null",
193                 WS_OVERLAPPEDWINDOW,
194                 CW_USEDEFAULT,
195                 CW_USEDEFAULT,
196                 CW_USEDEFAULT,
197                 CW_USEDEFAULT,
198                 null, null, hInstance, null
199             ),
200             "could not create win32 window"
201         );
202 
203         if (dummy) return;
204 
205         // What follow is a non-portable way to have alpha value of framebuffer used in desktop composition
206         // (by default composition makes window completely opaque)
207         // only works on Windows 7 and therefore disabled
208 
209         // DWM_BLURBEHIND bb;
210         // bb.dwFlags = DWM_BB_ENABLE;
211         // bb.fEnable = TRUE;
212         // bb.hRgnBlur = NULL;
213         // DwmEnableBlurBehindWindow(hWnd, &bb);
214         // MARGINS m = { -1 };
215         // DwmExtendFrameIntoClientArea(hWnd, &m);
216 
217         import gfx.graal : Backend;
218         final switch (dpy.instance.backend) {
219         case Backend.vulkan:
220             import gfx.vulkan.wsi : createVulkanWin32Surface;
221             gfxSurface = createVulkanWin32Surface(dpy.instance, GetModuleHandle(null), hWnd);
222             break;
223 
224         case Backend.gl3:
225             import gfx.gl3 : GlInstance;
226             import gfx.gl3.swapchain : GlSurface;
227             import gfx.window.win32.context : Win32GlContext;
228 
229             gfxSurface = new GlSurface(cast(size_t)hWnd);
230             auto glInst = cast(GlInstance)dpy.instance;
231             auto ctx = cast(Win32GlContext)glInst.ctx;
232             ctx.setPixelFormat(hWnd);
233             ctx.makeCurrent(cast(size_t)hWnd);
234             break;
235         }
236 
237         dpy.registerWindow(hWnd, this);
238     }
239 
240     override void show(uint width, uint height)
241     {
242         if (dummy) return;
243         RECT r;
244         r.right = width;
245         r.bottom = height;
246         AdjustWindowRectEx(&r, WS_BORDER | WS_CAPTION, FALSE, 0);
247         SetWindowPos(hWnd, HWND_TOP, 0, 0, r.right - r.left, r.bottom - r.top, SWP_SHOWWINDOW | SWP_NOMOVE);
248     }
249 
250     override void close() {
251 		DestroyWindow(hWnd);
252         if (dummy) return;
253         gfxSurface.unload();
254         dpy.unregisterWindow(hWnd);
255     }
256 
257     override @property bool closeFlag() const {
258         return _closeFlag;
259     }
260     override @property void closeFlag(in bool flag) {
261         _closeFlag = flag;
262     }
263 
264     override @property void onMouseMove(MouseHandler handler) {
265         moveHandler = handler;
266     }
267     override @property void onMouseOn(MouseHandler handler) {
268         onHandler = handler;
269     }
270     override @property void onMouseOff(MouseHandler handler) {
271         offHandler = handler;
272     }
273     override @property void onKeyOn(KeyHandler handler) {
274         keyOnHandler = handler;
275     }
276     override @property void onKeyOff(KeyHandler handler) {
277         keyOffHandler = handler;
278     }
279     override @property void onClose(CloseHandler handler) {
280         closeHandler = handler;
281     }
282 
283     override @property Surface surface() {
284         return gfxSurface.obj;
285     }
286 
287     private bool handleMouse(UINT msg, WPARAM wParam, LPARAM lParam) {
288         const x = GET_X_LPARAM(lParam);
289         const y = GET_Y_LPARAM(lParam);
290 
291         switch (msg) {
292         case WM_LBUTTONDOWN:
293         case WM_MBUTTONDOWN:
294         case WM_RBUTTONDOWN:
295             if (onHandler) {
296                 onHandler(x, y);
297                 return true;
298             }
299             break;
300         case WM_LBUTTONUP:
301         case WM_MBUTTONUP:
302         case WM_RBUTTONUP:
303             if (offHandler) {
304                 offHandler(x, y);
305                 return true;
306             }
307             break;
308         case WM_MOUSEMOVE:
309             if (mouseOut) {
310                 mouseOut = false;
311                 // mouse was out, deliver enter event (TODO)
312                 // and register for leave event
313                 TRACKMOUSEEVENT tm;
314                 tm.cbSize = TRACKMOUSEEVENT.sizeof;
315                 tm.dwFlags = TME_LEAVE;
316                 tm.hwndTrack = hWnd;
317                 tm.dwHoverTime = 0;
318                 TrackMouseEvent(&tm);
319             }
320             if (moveHandler) {
321                 moveHandler(x, y);
322                 return true;
323             }
324             break;
325         case WM_MOUSELEAVE:
326             mouseOut = true;
327             // TODO: deliver leave event
328             break;
329         default:
330             break;
331         }
332 
333         return false;
334     }
335 
336     private bool handleKey(UINT msg, WPARAM wParam, LPARAM lParam) {
337         KeyHandler handler;
338         if (msg == WM_KEYDOWN) {
339             handler = keyOnHandler;
340         }
341         else if (msg == WM_KEYUP) {
342             handler = keyOffHandler;
343         }
344 
345         if (handler) {
346             handler(cast(int)lParam); // TODO: sym, text, scancode and repeat from dgt
347             return true;
348         }
349         else {
350             return false;
351         }
352     }
353 }
354 
355 private __gshared Win32Display g_dpy;
356 package immutable wstring wndClassName = "GfxDWin32WindowClass"w;
357 
358 
359 package void registerWindowClass()
360 {
361     import std.utf : toUTF16z;
362 
363     static bool registered;
364     if (registered) return;
365     registered = true;
366 
367     WNDCLASSEX wc;
368     wc.cbSize        = WNDCLASSEX.sizeof;
369     wc.style         = CS_OWNDC;
370     wc.lpfnWndProc   = &win32WndProc;
371     wc.cbClsExtra    = 0;
372     wc.cbWndExtra    = 0;
373     wc.hInstance     = GetModuleHandle(null);
374     wc.hIcon         = LoadIcon(null, IDI_APPLICATION);
375     wc.hCursor       = LoadCursor(null, IDC_ARROW);
376     wc.hbrBackground = null;
377     wc.lpszMenuName  = null;
378     wc.lpszClassName = wndClassName.toUTF16z;
379     wc.hIconSm       = LoadIcon(null, IDI_APPLICATION);
380 
381     enforce(RegisterClassExW(&wc), "could not register win32 window class");
382 }
383 
384 package int GET_X_LPARAM(in LPARAM lp) pure
385 {
386     return cast(int)(lp & 0x0000ffff);
387 }
388 
389 package int GET_Y_LPARAM(in LPARAM lp) pure
390 {
391     return cast(int)((lp & 0xffff0000) >> 16);
392 }
393 
394 extern(Windows) nothrow
395 private LRESULT win32WndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
396 {
397     import std.exception : collectExceptionMsg;
398     LRESULT res;
399     try
400     {
401         if (!g_dpy || !g_dpy.wndProc(hwnd, msg, wParam, lParam, res))
402         {
403             res = DefWindowProc(hwnd, msg, wParam, lParam);
404         }
405     }
406     catch(Exception ex)
407     {
408         try { gfxW32WndLog.errorf("Win32 Proc exception: %s", ex.msg); }
409         catch(Exception) {}
410     }
411     return res;
412 }
413 
414 // a few missing bindings
415 
416 private:
417 
418 // see comment in Win32Window ctor
419 
420 // struct DWM_BLURBEHIND {
421 //     DWORD dwFlags;
422 //     BOOL  fEnable;
423 //     HRGN  hRgnBlur;
424 //     BOOL  fTransitionOnMaximized;
425 // }
426 
427 // struct MARGINS {
428 //     int left; int right; int top; int bottom;
429 // }
430 
431 // enum DWM_BB_ENABLE = 0x00000001;
432 
433 // extern(Windows) HRESULT DwmEnableBlurBehindWindow(
434 //     HWND hWnd,
435 //     const(DWM_BLURBEHIND)* pBlurBehind
436 // );
437 // extern(Windows) HRESULT DwmExtendFrameIntoClientArea(
438 //     HWND    hWnd,
439 //     const(MARGINS)* pMarInset
440 // );