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 }