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