1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include <stdio.h>
8 #include <windows.h>
9 #include <commctrl.h>
10 #include <process.h>
11 #include <io.h>
12 
13 #include "resource.h"
14 #include "progressui.h"
15 #include "readstrings.h"
16 #include "updatererrors.h"
17 
18 #define TIMER_ID 1
19 #define TIMER_INTERVAL 100
20 
21 #define RESIZE_WINDOW(hwnd, extrax, extray)                                  \
22   {                                                                          \
23     RECT windowSize;                                                         \
24     GetWindowRect(hwnd, &windowSize);                                        \
25     SetWindowPos(hwnd, 0, 0, 0, windowSize.right - windowSize.left + extrax, \
26                  windowSize.bottom - windowSize.top + extray,                \
27                  SWP_NOMOVE | SWP_NOZORDER);                                 \
28   }
29 
30 #define MOVE_WINDOW(hwnd, dx, dy)                     \
31   {                                                   \
32     RECT rc;                                          \
33     POINT pt;                                         \
34     GetWindowRect(hwnd, &rc);                         \
35     pt.x = rc.left;                                   \
36     pt.y = rc.top;                                    \
37     ScreenToClient(GetParent(hwnd), &pt);             \
38     SetWindowPos(hwnd, 0, pt.x + dx, pt.y + dy, 0, 0, \
39                  SWP_NOSIZE | SWP_NOZORDER);          \
40   }
41 
42 static float sProgress;  // between 0 and 100
43 static BOOL sQuit = FALSE;
44 static BOOL sIndeterminate = FALSE;
45 static StringTable sUIStrings;
46 
GetStringsFile(WCHAR filename[MAX_PATH])47 static BOOL GetStringsFile(WCHAR filename[MAX_PATH]) {
48   if (!GetModuleFileNameW(nullptr, filename, MAX_PATH)) {
49     return FALSE;
50   }
51 
52   WCHAR* dot = wcsrchr(filename, '.');
53   if (!dot || wcsicmp(dot + 1, L"exe")) {
54     return FALSE;
55   }
56 
57   wcscpy(dot + 1, L"ini");
58   return TRUE;
59 }
60 
UpdateDialog(HWND hDlg)61 static void UpdateDialog(HWND hDlg) {
62   int pos = int(sProgress + 0.5f);
63   HWND hWndPro = GetDlgItem(hDlg, IDC_PROGRESS);
64   SendMessage(hWndPro, PBM_SETPOS, pos, 0L);
65 }
66 
67 // The code in this function is from MSDN:
68 // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/windowing/dialogboxes/usingdialogboxes.asp
CenterDialog(HWND hDlg)69 static void CenterDialog(HWND hDlg) {
70   RECT rc, rcOwner, rcDlg;
71 
72   // Get the owner window and dialog box rectangles.
73   HWND desktop = GetDesktopWindow();
74 
75   GetWindowRect(desktop, &rcOwner);
76   GetWindowRect(hDlg, &rcDlg);
77   CopyRect(&rc, &rcOwner);
78 
79   // Offset the owner and dialog box rectangles so that
80   // right and bottom values represent the width and
81   // height, and then offset the owner again to discard
82   // space taken up by the dialog box.
83 
84   OffsetRect(&rcDlg, -rcDlg.left, -rcDlg.top);
85   OffsetRect(&rc, -rc.left, -rc.top);
86   OffsetRect(&rc, -rcDlg.right, -rcDlg.bottom);
87 
88   // The new position is the sum of half the remaining
89   // space and the owner's original position.
90 
91   SetWindowPos(hDlg, HWND_TOP, rcOwner.left + (rc.right / 2),
92                rcOwner.top + (rc.bottom / 2), 0, 0,  // ignores size arguments
93                SWP_NOSIZE);
94 }
95 
InitDialog(HWND hDlg)96 static void InitDialog(HWND hDlg) {
97   WCHAR szwTitle[MAX_TEXT_LEN];
98   WCHAR szwInfo[MAX_TEXT_LEN];
99 
100   MultiByteToWideChar(CP_UTF8, 0, sUIStrings.title, -1, szwTitle,
101                       sizeof(szwTitle) / sizeof(szwTitle[0]));
102   MultiByteToWideChar(CP_UTF8, 0, sUIStrings.info, -1, szwInfo,
103                       sizeof(szwInfo) / sizeof(szwInfo[0]));
104 
105   SetWindowTextW(hDlg, szwTitle);
106   SetWindowTextW(GetDlgItem(hDlg, IDC_INFO), szwInfo);
107 
108   // Set dialog icon
109   HICON hIcon = LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(IDI_DIALOG));
110   if (hIcon) {
111     SendMessage(hDlg, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
112   }
113 
114   HWND hWndPro = GetDlgItem(hDlg, IDC_PROGRESS);
115   SendMessage(hWndPro, PBM_SETRANGE, 0, MAKELPARAM(0, 100));
116   if (sIndeterminate) {
117     LONG_PTR val = GetWindowLongPtr(hWndPro, GWL_STYLE);
118     SetWindowLongPtr(hWndPro, GWL_STYLE, val | PBS_MARQUEE);
119     SendMessage(hWndPro, (UINT)PBM_SETMARQUEE, (WPARAM)TRUE, (LPARAM)50);
120   }
121 
122   // Resize the dialog to fit all of the text if necessary.
123   RECT infoSize, textSize;
124   HWND hWndInfo = GetDlgItem(hDlg, IDC_INFO);
125 
126   // Get the control's font for calculating the new size for the control
127   HDC hDCInfo = GetDC(hWndInfo);
128   HFONT hInfoFont, hOldFont = NULL;
129   hInfoFont = (HFONT)SendMessage(hWndInfo, WM_GETFONT, 0, 0);
130 
131   if (hInfoFont) {
132     hOldFont = (HFONT)SelectObject(hDCInfo, hInfoFont);
133   }
134 
135   // Measure the space needed for the text on a single line. DT_CALCRECT means
136   // nothing is drawn.
137   if (DrawText(hDCInfo, szwInfo, -1, &textSize,
138                DT_CALCRECT | DT_NOCLIP | DT_SINGLELINE)) {
139     GetClientRect(hWndInfo, &infoSize);
140     SIZE extra;
141     // Calculate the additional space needed for the text by subtracting from
142     // the rectangle returned by DrawText the existing client rectangle's width
143     // and height.
144     extra.cx =
145         (textSize.right - textSize.left) - (infoSize.right - infoSize.left);
146     extra.cy =
147         (textSize.bottom - textSize.top) - (infoSize.bottom - infoSize.top);
148     if (extra.cx < 0) {
149       extra.cx = 0;
150     }
151     if (extra.cy < 0) {
152       extra.cy = 0;
153     }
154     if ((extra.cx > 0) || (extra.cy > 0)) {
155       RESIZE_WINDOW(hDlg, extra.cx, extra.cy);
156       RESIZE_WINDOW(hWndInfo, extra.cx, extra.cy);
157       RESIZE_WINDOW(hWndPro, extra.cx, 0);
158       MOVE_WINDOW(hWndPro, 0, extra.cy);
159     }
160   }
161 
162   if (hOldFont) {
163     SelectObject(hDCInfo, hOldFont);
164   }
165 
166   ReleaseDC(hWndInfo, hDCInfo);
167 
168   CenterDialog(hDlg);  // make dialog appear in the center of the screen
169 
170   SetTimer(hDlg, TIMER_ID, TIMER_INTERVAL, nullptr);
171 }
172 
173 // Message handler for update dialog.
DialogProc(HWND hDlg,UINT message,WPARAM wParam,LPARAM lParam)174 static LRESULT CALLBACK DialogProc(HWND hDlg, UINT message, WPARAM wParam,
175                                    LPARAM lParam) {
176   switch (message) {
177     case WM_INITDIALOG:
178       InitDialog(hDlg);
179       return TRUE;
180 
181     case WM_TIMER:
182       if (sQuit) {
183         EndDialog(hDlg, 0);
184       } else {
185         UpdateDialog(hDlg);
186       }
187       return TRUE;
188 
189     case WM_COMMAND:
190       return TRUE;
191   }
192   return FALSE;
193 }
194 
InitProgressUI(int * argc,WCHAR *** argv)195 int InitProgressUI(int* argc, WCHAR*** argv) { return 0; }
196 
197 /**
198  * Initializes the progress UI strings
199  *
200  * @return 0 on success, -1 on error
201  */
InitProgressUIStrings()202 int InitProgressUIStrings() {
203   // If we do not have updater.ini, then we should not bother showing UI.
204   WCHAR filename[MAX_PATH];
205   if (!GetStringsFile(filename)) {
206     return -1;
207   }
208 
209   if (_waccess(filename, 04)) {
210     return -1;
211   }
212 
213   // If the updater.ini doesn't have the required strings, then we should not
214   // bother showing UI.
215   if (ReadStrings(filename, &sUIStrings) != OK) {
216     return -1;
217   }
218 
219   return 0;
220 }
221 
ShowProgressUI(bool indeterminate,bool initUIStrings)222 int ShowProgressUI(bool indeterminate, bool initUIStrings) {
223   sIndeterminate = indeterminate;
224   if (!indeterminate) {
225     // Only show the Progress UI if the process is taking a significant amount
226     // of time where a significant amount of time is defined as .5 seconds after
227     // ShowProgressUI is called sProgress is less than 70.
228     Sleep(500);
229 
230     if (sQuit || sProgress > 70.0f) {
231       return 0;
232     }
233   }
234 
235   // Don't load the UI if there's an <exe_name>.Local directory for redirection.
236   WCHAR appPath[MAX_PATH + 1] = {L'\0'};
237   if (!GetModuleFileNameW(nullptr, appPath, MAX_PATH)) {
238     return -1;
239   }
240 
241   if (wcslen(appPath) + wcslen(L".Local") >= MAX_PATH) {
242     return -1;
243   }
244 
245   wcscat(appPath, L".Local");
246 
247   if (!_waccess(appPath, 04)) {
248     return -1;
249   }
250 
251   // Don't load the UI if the strings for the UI are not provided.
252   if (initUIStrings && InitProgressUIStrings() == -1) {
253     return -1;
254   }
255 
256   if (!GetModuleFileNameW(nullptr, appPath, MAX_PATH)) {
257     return -1;
258   }
259 
260   // Use an activation context that supports visual styles for the controls.
261   ACTCTXW actx = {0};
262   actx.cbSize = sizeof(ACTCTXW);
263   actx.dwFlags = ACTCTX_FLAG_RESOURCE_NAME_VALID | ACTCTX_FLAG_HMODULE_VALID;
264   actx.hModule = GetModuleHandle(NULL);  // Use the embedded manifest
265   // This is needed only for Win XP but doesn't cause a problem with other
266   // versions of Windows.
267   actx.lpSource = appPath;
268   actx.lpResourceName = MAKEINTRESOURCE(IDR_COMCTL32_MANIFEST);
269 
270   HANDLE hactx = INVALID_HANDLE_VALUE;
271   hactx = CreateActCtxW(&actx);
272   ULONG_PTR actxCookie = NULL;
273   if (hactx != INVALID_HANDLE_VALUE) {
274     // Push the specified activation context to the top of the activation stack.
275     ActivateActCtx(hactx, &actxCookie);
276   }
277 
278   INITCOMMONCONTROLSEX icc = {sizeof(INITCOMMONCONTROLSEX), ICC_PROGRESS_CLASS};
279   InitCommonControlsEx(&icc);
280 
281   DialogBox(GetModuleHandle(nullptr), MAKEINTRESOURCE(IDD_DIALOG), nullptr,
282             (DLGPROC)DialogProc);
283 
284   if (hactx != INVALID_HANDLE_VALUE) {
285     // Deactivate the context now that the comctl32.dll is loaded.
286     DeactivateActCtx(0, actxCookie);
287   }
288 
289   return 0;
290 }
291 
QuitProgressUI()292 void QuitProgressUI() { sQuit = TRUE; }
293 
UpdateProgressUI(float progress)294 void UpdateProgressUI(float progress) {
295   sProgress = progress;  // 32-bit writes are atomic
296 }
297