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