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