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