summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/update/updater/progressui_win.cpp
blob: 51bd2d8cce5dfb66e6b33ff8962f399507adec1e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include <stdio.h>
#include <windows.h>
#include <commctrl.h>
#include <process.h>
#include <io.h>

#include "resource.h"
#include "progressui.h"
#include "readstrings.h"
#include "updatererrors.h"

#define TIMER_ID 1
#define TIMER_INTERVAL 100

#define RESIZE_WINDOW(hwnd, extrax, extray)                                  \
  {                                                                          \
    RECT windowSize;                                                         \
    GetWindowRect(hwnd, &windowSize);                                        \
    SetWindowPos(hwnd, 0, 0, 0, windowSize.right - windowSize.left + extrax, \
                 windowSize.bottom - windowSize.top + extray,                \
                 SWP_NOMOVE | SWP_NOZORDER);                                 \
  }

#define MOVE_WINDOW(hwnd, dx, dy)                     \
  {                                                   \
    RECT rc;                                          \
    POINT pt;                                         \
    GetWindowRect(hwnd, &rc);                         \
    pt.x = rc.left;                                   \
    pt.y = rc.top;                                    \
    ScreenToClient(GetParent(hwnd), &pt);             \
    SetWindowPos(hwnd, 0, pt.x + dx, pt.y + dy, 0, 0, \
                 SWP_NOSIZE | SWP_NOZORDER);          \
  }

static float sProgress;  // between 0 and 100
static BOOL sQuit = FALSE;
static BOOL sIndeterminate = FALSE;
static StringTable sUIStrings;

static BOOL GetStringsFile(WCHAR filename[MAX_PATH]) {
  if (!GetModuleFileNameW(nullptr, filename, MAX_PATH)) {
    return FALSE;
  }

  WCHAR* dot = wcsrchr(filename, '.');
  if (!dot || wcsicmp(dot + 1, L"exe")) {
    return FALSE;
  }

  wcscpy(dot + 1, L"ini");
  return TRUE;
}

static void UpdateDialog(HWND hDlg) {
  int pos = int(sProgress + 0.5f);
  HWND hWndPro = GetDlgItem(hDlg, IDC_PROGRESS);
  SendMessage(hWndPro, PBM_SETPOS, pos, 0L);
}

// The code in this function is from MSDN:
// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/windowing/dialogboxes/usingdialogboxes.asp
static void CenterDialog(HWND hDlg) {
  RECT rc, rcOwner, rcDlg;

  // Get the owner window and dialog box rectangles.
  HWND desktop = GetDesktopWindow();

  GetWindowRect(desktop, &rcOwner);
  GetWindowRect(hDlg, &rcDlg);
  CopyRect(&rc, &rcOwner);

  // Offset the owner and dialog box rectangles so that
  // right and bottom values represent the width and
  // height, and then offset the owner again to discard
  // space taken up by the dialog box.

  OffsetRect(&rcDlg, -rcDlg.left, -rcDlg.top);
  OffsetRect(&rc, -rc.left, -rc.top);
  OffsetRect(&rc, -rcDlg.right, -rcDlg.bottom);

  // The new position is the sum of half the remaining
  // space and the owner's original position.

  SetWindowPos(hDlg, HWND_TOP, rcOwner.left + (rc.right / 2),
               rcOwner.top + (rc.bottom / 2), 0, 0,  // ignores size arguments
               SWP_NOSIZE);
}

static void InitDialog(HWND hDlg) {
  mozilla::UniquePtr<WCHAR[]> szwTitle;
  mozilla::UniquePtr<WCHAR[]> szwInfo;

  int bufferSize =
      MultiByteToWideChar(CP_UTF8, 0, sUIStrings.title.get(), -1, nullptr, 0);
  szwTitle = mozilla::MakeUnique<WCHAR[]>(bufferSize);
  MultiByteToWideChar(CP_UTF8, 0, sUIStrings.title.get(), -1, szwTitle.get(),
                      bufferSize);
  bufferSize =
      MultiByteToWideChar(CP_UTF8, 0, sUIStrings.info.get(), -1, nullptr, 0);
  szwInfo = mozilla::MakeUnique<WCHAR[]>(bufferSize);
  MultiByteToWideChar(CP_UTF8, 0, sUIStrings.info.get(), -1, szwInfo.get(),
                      bufferSize);

  SetWindowTextW(hDlg, szwTitle.get());
  SetWindowTextW(GetDlgItem(hDlg, IDC_INFO), szwInfo.get());

  // Set dialog icon
  HICON hIcon = LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(IDI_DIALOG));
  if (hIcon) {
    SendMessage(hDlg, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
  }

  HWND hWndPro = GetDlgItem(hDlg, IDC_PROGRESS);
  SendMessage(hWndPro, PBM_SETRANGE, 0, MAKELPARAM(0, 100));
  if (sIndeterminate) {
    LONG_PTR val = GetWindowLongPtr(hWndPro, GWL_STYLE);
    SetWindowLongPtr(hWndPro, GWL_STYLE, val | PBS_MARQUEE);
    SendMessage(hWndPro, (UINT)PBM_SETMARQUEE, (WPARAM)TRUE, (LPARAM)50);
  }

  // Resize the dialog to fit all of the text if necessary.
  RECT infoSize, textSize;
  HWND hWndInfo = GetDlgItem(hDlg, IDC_INFO);

  // Get the control's font for calculating the new size for the control
  HDC hDCInfo = GetDC(hWndInfo);
  HFONT hInfoFont, hOldFont = NULL;
  hInfoFont = (HFONT)SendMessage(hWndInfo, WM_GETFONT, 0, 0);

  if (hInfoFont) {
    hOldFont = (HFONT)SelectObject(hDCInfo, hInfoFont);
  }

  // Measure the space needed for the text on a single line. DT_CALCRECT means
  // nothing is drawn.
  if (DrawText(hDCInfo, szwInfo.get(), -1, &textSize,
               DT_CALCRECT | DT_NOCLIP | DT_SINGLELINE)) {
    GetClientRect(hWndInfo, &infoSize);
    SIZE extra;
    // Calculate the additional space needed for the text by subtracting from
    // the rectangle returned by DrawText the existing client rectangle's width
    // and height.
    extra.cx =
        (textSize.right - textSize.left) - (infoSize.right - infoSize.left);
    extra.cy =
        (textSize.bottom - textSize.top) - (infoSize.bottom - infoSize.top);
    if (extra.cx < 0) {
      extra.cx = 0;
    }
    if (extra.cy < 0) {
      extra.cy = 0;
    }
    if ((extra.cx > 0) || (extra.cy > 0)) {
      RESIZE_WINDOW(hDlg, extra.cx, extra.cy);
      RESIZE_WINDOW(hWndInfo, extra.cx, extra.cy);
      RESIZE_WINDOW(hWndPro, extra.cx, 0);
      MOVE_WINDOW(hWndPro, 0, extra.cy);
    }
  }

  if (hOldFont) {
    SelectObject(hDCInfo, hOldFont);
  }

  ReleaseDC(hWndInfo, hDCInfo);

  CenterDialog(hDlg);  // make dialog appear in the center of the screen

  SetTimer(hDlg, TIMER_ID, TIMER_INTERVAL, nullptr);
}

// Message handler for update dialog.
static LRESULT CALLBACK DialogProc(HWND hDlg, UINT message, WPARAM wParam,
                                   LPARAM lParam) {
  switch (message) {
    case WM_INITDIALOG:
      InitDialog(hDlg);
      return TRUE;

    case WM_TIMER:
      if (sQuit) {
        EndDialog(hDlg, 0);
      } else {
        UpdateDialog(hDlg);
      }
      return TRUE;

    case WM_COMMAND:
      return TRUE;
  }
  return FALSE;
}

int InitProgressUI(int* argc, WCHAR*** argv) { return 0; }

/**
 * Initializes the progress UI strings
 *
 * @return 0 on success, -1 on error
 */
int InitProgressUIStrings() {
  // If we do not have updater.ini, then we should not bother showing UI.
  WCHAR filename[MAX_PATH];
  if (!GetStringsFile(filename)) {
    return -1;
  }

  if (_waccess(filename, 04)) {
    return -1;
  }

  // If the updater.ini doesn't have the required strings, then we should not
  // bother showing UI.
  if (ReadStrings(filename, &sUIStrings) != OK) {
    return -1;
  }

  return 0;
}

int ShowProgressUI(bool indeterminate, bool initUIStrings) {
  sIndeterminate = indeterminate;
  if (!indeterminate) {
    // Only show the Progress UI if the process is taking a significant amount
    // of time where a significant amount of time is defined as .5 seconds after
    // ShowProgressUI is called sProgress is less than 70.
    Sleep(500);

    if (sQuit || sProgress > 70.0f) {
      return 0;
    }
  }

  // Don't load the UI if there's an <exe_name>.Local directory for redirection.
  WCHAR appPath[MAX_PATH + 1] = {L'\0'};
  if (!GetModuleFileNameW(nullptr, appPath, MAX_PATH)) {
    return -1;
  }

  if (wcslen(appPath) + wcslen(L".Local") >= MAX_PATH) {
    return -1;
  }

  wcscat(appPath, L".Local");

  if (!_waccess(appPath, 04)) {
    return -1;
  }

  // Don't load the UI if the strings for the UI are not provided.
  if (initUIStrings && InitProgressUIStrings() == -1) {
    return -1;
  }

  if (!GetModuleFileNameW(nullptr, appPath, MAX_PATH)) {
    return -1;
  }

  // Use an activation context that supports visual styles for the controls.
  ACTCTXW actx = {0};
  actx.cbSize = sizeof(ACTCTXW);
  actx.dwFlags = ACTCTX_FLAG_RESOURCE_NAME_VALID | ACTCTX_FLAG_HMODULE_VALID;
  actx.hModule = GetModuleHandle(NULL);  // Use the embedded manifest
  // This is needed only for Win XP but doesn't cause a problem with other
  // versions of Windows.
  actx.lpSource = appPath;
  actx.lpResourceName = MAKEINTRESOURCE(IDR_COMCTL32_MANIFEST);

  HANDLE hactx = INVALID_HANDLE_VALUE;
  hactx = CreateActCtxW(&actx);
  ULONG_PTR actxCookie = NULL;
  if (hactx != INVALID_HANDLE_VALUE) {
    // Push the specified activation context to the top of the activation stack.
    ActivateActCtx(hactx, &actxCookie);
  }

  INITCOMMONCONTROLSEX icc = {sizeof(INITCOMMONCONTROLSEX), ICC_PROGRESS_CLASS};
  InitCommonControlsEx(&icc);

  DialogBox(GetModuleHandle(nullptr), MAKEINTRESOURCE(IDD_DIALOG), nullptr,
            (DLGPROC)DialogProc);

  if (hactx != INVALID_HANDLE_VALUE) {
    // Deactivate the context now that the comctl32.dll is loaded.
    DeactivateActCtx(0, actxCookie);
  }

  return 0;
}

void QuitProgressUI() { sQuit = TRUE; }

void UpdateProgressUI(float progress) {
  sProgress = progress;  // 32-bit writes are atomic
}