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