1 /** 2 * Application lifecycle: the singleton that owns process initialization and the 3 * Win32 message loop. 4 */ 5 module deft.app; 6 7 version (Windows): 8 9 import core.sys.windows.windows; 10 import core.sys.windows.commctrl; 11 import core.sys.windows.objbase; 12 13 import deft.platform.win32.init : ensureWindowClass; 14 import deft.window : activeWindowAcceleratorTable; 15 16 // SetProcessDpiAwarenessContext is Win10 1703+; resolved dynamically so the 17 // framework still loads on older systems (falling back to SetProcessDPIAware). 18 private alias DpiAwarenessContext = HANDLE; 19 private enum DpiAwarenessContext perMonitorAwareV2 = cast(HANDLE)-4; 20 private alias SetProcessDpiAwarenessContextFn = 21 extern (Windows) BOOL function(DpiAwarenessContext) nothrow; 22 private alias SetProcessDpiAwareFn = extern (Windows) BOOL function() nothrow; 23 24 /** 25 * Make the process DPI-aware so windows are rendered at native resolution 26 * instead of being bitmap-stretched by the OS. Bitmap stretching is what makes 27 * screen-reader cursors read the wrong screen location on high-DPI displays. 28 */ 29 private void enableDpiAwareness() nothrow 30 { 31 auto user32 = GetModuleHandleW("user32.dll"w.ptr); 32 if (user32 !is null) 33 { 34 auto setContext = cast(SetProcessDpiAwarenessContextFn) 35 GetProcAddress(user32, "SetProcessDpiAwarenessContext"); 36 if (setContext !is null && setContext(perMonitorAwareV2)) 37 return; 38 39 auto setAware = cast(SetProcessDpiAwareFn) 40 GetProcAddress(user32, "SetProcessDPIAware"); 41 if (setAware !is null) 42 setAware(); 43 } 44 } 45 46 /** 47 * The application object. Use `Application.instance` to obtain it, call 48 * `initialize()` once at startup, then `run()` to enter the message loop. 49 */ 50 class Application 51 { 52 private __gshared Application instance_; 53 private bool initialized_; 54 55 /// The process-wide application instance (created on first access). 56 static Application instance() 57 { 58 // Guard the lazy init so two threads racing for the first access cannot 59 // each construct an instance. (UI work is single-threaded, but the 60 // singleton may be touched from a worker thread via CommandQueue.) 61 synchronized (Application.classinfo) 62 { 63 if (instance_ is null) 64 instance_ = new Application(); 65 } 66 return instance_; 67 } 68 69 /** 70 * Initialize common controls and COM, and register the default window 71 * class. Idempotent; safe to call before creating any windows. 72 */ 73 void initialize() 74 { 75 if (initialized_) 76 return; 77 initialized_ = true; 78 79 // Must run before any window is created. 80 enableDpiAwareness(); 81 82 INITCOMMONCONTROLSEX icc; 83 icc.dwSize = INITCOMMONCONTROLSEX.sizeof; 84 icc.dwICC = ICC_LISTVIEW_CLASSES | ICC_TREEVIEW_CLASSES 85 | ICC_TAB_CLASSES | ICC_BAR_CLASSES; 86 InitCommonControlsEx(&icc); 87 88 // Apartment-threaded COM for shell and accessibility APIs. 89 CoInitializeEx(null, COINIT.COINIT_APARTMENTTHREADED); 90 91 ensureWindowClass(); 92 } 93 94 /** 95 * Run the Win32 message loop until `WM_QUIT`. Returns the exit code carried 96 * by the quit message. 97 */ 98 int run() 99 { 100 MSG msg; 101 while (GetMessageW(&msg, null, 0, 0) > 0) 102 { 103 HWND active = GetActiveWindow(); 104 105 // Keyboard accelerators (menu shortcuts) take priority: translating 106 // one dispatches the matching WM_COMMAND and consumes the message. The 107 // table is the active window's, so shortcuts are per-window. 108 HACCEL accel = activeWindowAcceleratorTable(active); 109 if (active !is null && accel !is null 110 && TranslateAcceleratorW(active, accel, &msg)) 111 continue; 112 113 // Then the dialog manager so Tab/Shift+Tab, arrow keys and 114 // default-button handling move focus between the active window's child 115 // controls. Without this, child controls are unreachable by keyboard. 116 if (active is null || !IsDialogMessageW(active, &msg)) 117 { 118 TranslateMessage(&msg); 119 DispatchMessageW(&msg); 120 } 121 } 122 return cast(int) msg.wParam; 123 } 124 125 /// Post a quit request to the message loop with the given exit code. 126 void quit(int exitCode = 0) 127 { 128 PostQuitMessage(exitCode); 129 } 130 }