window-frame.cpp 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. // Keeps the DWM frame and shadow while the NSIS page owns the entire client area.
  2. #define WIN32_LEAN_AND_MEAN
  3. #define UNICODE
  4. #include <windows.h>
  5. #include <tlhelp32.h>
  6. #include <objidl.h>
  7. #include <commctrl.h>
  8. #include <dwmapi.h>
  9. #include <gdiplus.h>
  10. #include <new>
  11. using namespace Gdiplus;
  12. // Match the affected executable, not another user's or directory's same-named application.
  13. // Returns 0 while running, 1 when absent, and -1 if the process list cannot be read.
  14. extern "C" __declspec(dllexport) int __cdecl InstallerFindProcess(LPCWSTR executable) {
  15. WCHAR target[32768];
  16. DWORD length = GetLongPathNameW(executable, target, ARRAYSIZE(target));
  17. LPCWSTR expected = length > 0 && length < ARRAYSIZE(target) ? target : executable;
  18. LPCWSTR filename = wcsrchr(expected, L'\\');
  19. filename = filename ? filename + 1 : expected;
  20. HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  21. if (snapshot == INVALID_HANDLE_VALUE) return -1;
  22. PROCESSENTRY32W entry = {};
  23. entry.dwSize = sizeof(entry);
  24. int result = 1;
  25. BOOL present = Process32FirstW(snapshot, &entry);
  26. while (present) {
  27. if (_wcsicmp(entry.szExeFile, filename) == 0) {
  28. HANDLE process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, entry.th32ProcessID);
  29. if (process) {
  30. WCHAR path[32768];
  31. DWORD count = ARRAYSIZE(path);
  32. if (QueryFullProcessImageNameW(process, 0, path, &count) && _wcsicmp(path, expected) == 0) result = 0;
  33. CloseHandle(process);
  34. if (result == 0) break;
  35. }
  36. }
  37. present = Process32NextW(snapshot, &entry);
  38. }
  39. if (!present && GetLastError() != ERROR_NO_MORE_FILES) result = -1;
  40. CloseHandle(snapshot);
  41. return result;
  42. }
  43. struct ProgressPage {
  44. HWND source;
  45. bool dark;
  46. UINT dpi;
  47. ULONG_PTR gdiplus;
  48. Image* brand;
  49. WCHAR caption[128];
  50. };
  51. // NSIS shows its page after MUI's SHOW callback; keep its controls off screen.
  52. static LRESULT CALLBACK HiddenPageProc(HWND window, UINT message, WPARAM wparam,
  53. LPARAM lparam, UINT_PTR id, DWORD_PTR) {
  54. if (message == WM_WINDOWPOSCHANGING) {
  55. auto* position = reinterpret_cast<WINDOWPOS*>(lparam);
  56. position->flags = (position->flags & ~SWP_SHOWWINDOW) | SWP_HIDEWINDOW;
  57. }
  58. if (message == WM_NCDESTROY) RemoveWindowSubclass(window, HiddenPageProc, id);
  59. return DefSubclassProc(window, message, wparam, lparam);
  60. }
  61. static void FillProgress(Graphics& graphics, Brush& brush, REAL width) {
  62. GraphicsPath path;
  63. path.AddArc(64.0f, 482.0f, 4.0f, 4.0f, 180.0f, 90.0f);
  64. path.AddArc(64.0f + width - 4, 482.0f, 4.0f, 4.0f, 270.0f, 90.0f);
  65. path.AddArc(64.0f + width - 4, 484.0f, 4.0f, 4.0f, 0.0f, 90.0f);
  66. path.AddArc(64.0f, 484.0f, 4.0f, 4.0f, 90.0f, 90.0f);
  67. path.CloseFigure();
  68. graphics.FillPath(&brush, &path);
  69. }
  70. static LRESULT CALLBACK ProgressProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) {
  71. auto* page = reinterpret_cast<ProgressPage*>(GetWindowLongPtrW(window, GWLP_USERDATA));
  72. if (message == WM_CREATE) {
  73. page = static_cast<ProgressPage*>(reinterpret_cast<CREATESTRUCTW*>(lparam)->lpCreateParams);
  74. SetWindowLongPtrW(window, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(page));
  75. SetTimer(window, 1, 100, nullptr);
  76. }
  77. if (message == WM_ERASEBKGND) return 1;
  78. if (message == WM_TIMER) { InvalidateRect(window, nullptr, FALSE); return 0; }
  79. if (message == WM_LBUTTONDOWN && page) {
  80. const int x = LOWORD(lparam) * 96 / page->dpi;
  81. const int y = HIWORD(lparam) * 96 / page->dpi;
  82. if (y < 48) {
  83. HWND parent = GetParent(window);
  84. if (x >= 548) PostMessageW(parent, WM_CLOSE, 0, 0);
  85. else if (x >= 504) ShowWindow(parent, SW_MINIMIZE);
  86. else { ReleaseCapture(); SendMessageW(parent, WM_NCLBUTTONDOWN, HTCAPTION, 0); }
  87. }
  88. return 0;
  89. }
  90. if (message == WM_PAINT && page) {
  91. PAINTSTRUCT paint;
  92. HDC dc = BeginPaint(window, &paint);
  93. {
  94. Bitmap buffer(MulDiv(600, page->dpi, 96), MulDiv(600, page->dpi, 96), PixelFormat32bppPARGB);
  95. Graphics graphics(&buffer);
  96. graphics.ScaleTransform(page->dpi / 96.0f, page->dpi / 96.0f);
  97. graphics.Clear(page->dark ? Color(255, 21, 21, 23) : Color(255, 255, 255, 255));
  98. graphics.SetSmoothingMode(SmoothingModeAntiAlias);
  99. graphics.DrawImage(page->brand, Rect(0, 174, 600, 196));
  100. const LRESULT maximum = SendMessageW(page->source, PBM_GETRANGE, FALSE, 0);
  101. const LRESULT position = SendMessageW(page->source, PBM_GETPOS, 0, 0);
  102. const int percent = maximum > 0 ? 20 + static_cast<int>(75 * position / maximum) : 20;
  103. SolidBrush track(page->dark ? Color(255, 97, 102, 107) : Color(255, 233, 236, 242));
  104. SolidBrush ink(page->dark ? Color(255, 255, 255, 255) : Color(255, 15, 17, 21));
  105. FillProgress(graphics, track, 472.0f);
  106. FillProgress(graphics, ink, 472.0f * percent / 100);
  107. FontFamily family(L"Microsoft YaHei UI");
  108. Font font(&family, 14, FontStyleRegular, UnitPixel);
  109. StringFormat centered;
  110. centered.SetAlignment(StringAlignmentCenter);
  111. centered.SetLineAlignment(StringAlignmentCenter);
  112. WCHAR caption[160];
  113. wsprintfW(caption, page->caption, percent);
  114. graphics.DrawString(caption, -1, &font, RectF(48, 512, 504, 22), &centered, &ink);
  115. Font controls(&family, 16, FontStyleRegular, UnitPixel);
  116. graphics.DrawString(L"\x2212", -1, &controls, RectF(504, 8, 40, 32), &centered, &ink);
  117. graphics.DrawString(L"\x00d7", -1, &controls, RectF(548, 8, 40, 32), &centered, &ink);
  118. Graphics screen(dc);
  119. screen.DrawImage(&buffer, 0, 0);
  120. }
  121. EndPaint(window, &paint);
  122. return 0;
  123. }
  124. if (message == WM_NCDESTROY && page) {
  125. KillTimer(window, 1);
  126. delete page->brand;
  127. GdiplusShutdown(page->gdiplus);
  128. delete page;
  129. }
  130. return DefWindowProcW(window, message, wparam, lparam);
  131. }
  132. // Runs on the NSIS UI thread; the stock installation section runs on its worker.
  133. extern "C" __declspec(dllexport) HWND __cdecl InstallerShowProgress(HWND parent, HWND source,
  134. BOOL dark, UINT dpi, const WCHAR* brand, const WCHAR* caption) {
  135. HINSTANCE module = GetModuleHandleW(nullptr);
  136. WNDCLASSW type = {};
  137. type.lpfnWndProc = ProgressProc;
  138. type.hInstance = module;
  139. type.lpszClassName = L"HarnessInstallerProgress";
  140. type.hCursor = LoadCursorW(nullptr, IDC_ARROW);
  141. RegisterClassW(&type);
  142. auto* page = new (std::nothrow) ProgressPage{};
  143. if (!page) return nullptr;
  144. GdiplusStartupInput startup;
  145. if (GdiplusStartup(&page->gdiplus, &startup, nullptr) != Ok) { delete page; return nullptr; }
  146. page->source = source;
  147. HWND stockPage = GetParent(source);
  148. if (!SetWindowSubclass(stockPage, HiddenPageProc, 1, 0)) {
  149. GdiplusShutdown(page->gdiplus); delete page; return nullptr;
  150. }
  151. ShowWindow(stockPage, SW_HIDE);
  152. page->dark = dark != FALSE;
  153. page->dpi = dpi;
  154. lstrcpynW(page->caption, caption, 128);
  155. page->brand = new Image(brand);
  156. if (page->brand->GetLastStatus() != Ok) {
  157. delete page->brand; GdiplusShutdown(page->gdiplus); delete page; return nullptr;
  158. }
  159. HWND window = CreateWindowExW(0, type.lpszClassName, L"", WS_CHILD | WS_VISIBLE,
  160. 0, 0, MulDiv(600, dpi, 96), MulDiv(600, dpi, 96), parent, nullptr, module, page);
  161. if (!window) { delete page->brand; GdiplusShutdown(page->gdiplus); delete page; }
  162. return window;
  163. }
  164. static LRESULT CALLBACK FrameProc(HWND window, UINT message, WPARAM wparam,
  165. LPARAM lparam, UINT_PTR id, DWORD_PTR) {
  166. if (message == WM_NCCALCSIZE && wparam) return 0;
  167. if (message == WM_NCHITTEST) {
  168. const LRESULT hit = DefSubclassProc(window, message, wparam, lparam);
  169. // The installer has a fixed size; its page provides the drag area.
  170. return hit >= HTLEFT && hit <= HTBOTTOMRIGHT ? HTCLIENT : hit;
  171. }
  172. if (message == WM_NCDESTROY) RemoveWindowSubclass(window, FrameProc, id);
  173. return DefSubclassProc(window, message, wparam, lparam);
  174. }
  175. extern "C" __declspec(dllexport) HRESULT __cdecl InstallerApplyFrame(HWND window) {
  176. // NSIS may release its DLL reference before the window receives WM_NCDESTROY.
  177. HMODULE module = nullptr;
  178. if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_PIN | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
  179. reinterpret_cast<LPCWSTR>(&FrameProc), &module)) return E_FAIL;
  180. if (!SetWindowSubclass(window, FrameProc, 1, 0)) return E_FAIL;
  181. const int stockControls[] = {1, 2, 3, 1028, 1256, 1034, 1035, 1036, 1037, 1038, 1039};
  182. for (int id : stockControls) {
  183. HWND child = GetDlgItem(window, id);
  184. if (child) {
  185. if (!SetWindowSubclass(child, HiddenPageProc, 1, 0)) return E_FAIL;
  186. ShowWindow(child, SW_HIDE);
  187. }
  188. }
  189. SetWindowLongW(window, GWL_STYLE, GetWindowLongW(window, GWL_STYLE) | WS_THICKFRAME);
  190. const DWMNCRENDERINGPOLICY policy = DWMNCRP_ENABLED;
  191. HRESULT result = DwmSetWindowAttribute(window, DWMWA_NCRENDERING_POLICY, &policy, sizeof(policy));
  192. if (FAILED(result)) return result;
  193. const DWORD rounded = 2;
  194. // Windows 10 does not support the Windows 11 corner preference.
  195. DwmSetWindowAttribute(window, 33, &rounded, sizeof(rounded));
  196. const MARGINS margins = {1, 1, 1, 1};
  197. result = DwmExtendFrameIntoClientArea(window, &margins);
  198. SetWindowPos(window, nullptr, 0, 0, 0, 0,
  199. SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
  200. return result;
  201. }