From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- toolkit/crashreporter/client/crashreporter_win.cpp | 1295 ++++++++++++++++++++ 1 file changed, 1295 insertions(+) create mode 100644 toolkit/crashreporter/client/crashreporter_win.cpp (limited to 'toolkit/crashreporter/client/crashreporter_win.cpp') diff --git a/toolkit/crashreporter/client/crashreporter_win.cpp b/toolkit/crashreporter/client/crashreporter_win.cpp new file mode 100644 index 0000000000..e680aa37c9 --- /dev/null +++ b/toolkit/crashreporter/client/crashreporter_win.cpp @@ -0,0 +1,1295 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifdef WIN32_LEAN_AND_MEAN +# undef WIN32_LEAN_AND_MEAN +#endif + +#include "crashreporter.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "resource.h" +#include "windows/sender/crash_report_sender.h" +#include "common/windows/string_utils-inl.h" + +#define SUBMIT_REPORT_VALUE L"SubmitCrashReport" +#define INCLUDE_URL_VALUE L"IncludeURL" + +#define SENDURL_ORIGINAL L"https://crash-reports.mozilla.com/submit" +#define SENDURL_XPSP2 L"https://crash-reports-xpsp2.mozilla.com/submit" + +#define WM_UPLOADCOMPLETE WM_APP + +// Thanks, Windows.h :( +#undef min +#undef max + +using std::ifstream; +using std::ios; +using std::ios_base; +using std::map; +using std::ofstream; +using std::set; +using std::string; +using std::vector; +using std::wstring; + +using namespace CrashReporter; + +typedef struct { + HWND hDlg; + Json::Value queryParameters; + map files; + wstring sendURL; + + wstring serverResponse; +} SendThreadData; + +/* + * Per http://msdn2.microsoft.com/en-us/library/ms645398(VS.85).aspx + * "The DLGTEMPLATEEX structure is not defined in any standard header file. + * The structure definition is provided here to explain the format of an + * extended template for a dialog box. + */ +typedef struct { + WORD dlgVer; + WORD signature; + DWORD helpID; + DWORD exStyle; + // There's more to this struct, but it has weird variable-length + // members, and I only actually need to touch exStyle on an existing + // instance, so I've omitted the rest. +} DLGTEMPLATEEX; + +static HANDLE gThreadHandle; +static SendThreadData gSendData = { + 0, +}; +static vector gRestartArgs; +static Json::Value gQueryParameters; +static wstring gCrashReporterKey(L"Software\\Mozilla\\Crash Reporter"); +static string gURLParameter; +static int gCheckboxPadding = 6; +static bool gRTLlayout = false; + +// When vertically resizing the dialog, these items should move down +static set gAttachedBottom; + +// Default set of items for gAttachedBottom +static const UINT kDefaultAttachedBottom[] = { + IDC_SUBMITREPORTCHECK, IDC_VIEWREPORTBUTTON, IDC_COMMENTTEXT, + IDC_INCLUDEURLCHECK, IDC_PROGRESSTEXT, IDC_THROBBER, + IDC_CLOSEBUTTON, IDC_RESTARTBUTTON, +}; + +static wstring UTF8ToWide(const string& utf8, bool* success = 0); +static DWORD WINAPI SendThreadProc(LPVOID param); + +static wstring Str(const char* key) { return UTF8ToWide(gStrings[key]); } + +/* === win32 helper functions === */ + +static void DoInitCommonControls() { + INITCOMMONCONTROLSEX ic; + ic.dwSize = sizeof(INITCOMMONCONTROLSEX); + ic.dwICC = ICC_PROGRESS_CLASS; + InitCommonControlsEx(&ic); + // also get the rich edit control + LoadLibrary(L"Msftedit.dll"); +} + +static bool GetBoolValue(HKEY hRegKey, LPCTSTR valueName, DWORD* value) { + DWORD type, dataSize; + dataSize = sizeof(DWORD); + if (RegQueryValueEx(hRegKey, valueName, nullptr, &type, (LPBYTE)value, + &dataSize) == ERROR_SUCCESS && + type == REG_DWORD) + return true; + + return false; +} + +static bool CheckBoolKey(const wchar_t* key, const wchar_t* valueName, + bool* enabled) { + /* + * NOTE! This code needs to stay in sync with the preference checking + * code in in nsExceptionHandler.cpp. + */ + *enabled = false; + bool found = false; + HKEY hRegKey; + DWORD val; + // see if our reg key is set globally + if (RegOpenKey(HKEY_LOCAL_MACHINE, key, &hRegKey) == ERROR_SUCCESS) { + if (GetBoolValue(hRegKey, valueName, &val)) { + *enabled = (val == 1); + found = true; + } + RegCloseKey(hRegKey); + } else { + // look for it in user settings + if (RegOpenKey(HKEY_CURRENT_USER, key, &hRegKey) == ERROR_SUCCESS) { + if (GetBoolValue(hRegKey, valueName, &val)) { + *enabled = (val == 1); + found = true; + } + RegCloseKey(hRegKey); + } + } + + return found; +} + +static void SetBoolKey(const wchar_t* key, const wchar_t* value, bool enabled) { + /* + * NOTE! This code needs to stay in sync with the preference setting + * code in in nsExceptionHandler.cpp. + */ + HKEY hRegKey; + + if (RegCreateKey(HKEY_CURRENT_USER, key, &hRegKey) == ERROR_SUCCESS) { + DWORD data = (enabled ? 1 : 0); + RegSetValueEx(hRegKey, value, 0, REG_DWORD, (LPBYTE)&data, sizeof(data)); + RegCloseKey(hRegKey); + } +} + +static string FormatLastError() { + DWORD err = GetLastError(); + LPWSTR s; + string message = "Crash report submission failed: "; + // odds are it's a WinInet error + HANDLE hInetModule = GetModuleHandle(L"WinInet.dll"); + if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_FROM_HMODULE, + hInetModule, err, 0, (LPWSTR)&s, 0, nullptr) != 0) { + message += WideToUTF8(s, nullptr); + LocalFree(s); + // strip off any trailing newlines + string::size_type n = message.find_last_not_of("\r\n"); + if (n < message.size() - 1) { + message.erase(n + 1); + } + } else { + char buf[64]; + sprintf(buf, "Unknown error, error code: 0x%08x", + static_cast(err)); + message += buf; + } + return message; +} + +#define TS_DRAW 2 +#define BP_CHECKBOX 3 + +typedef HANDLE(WINAPI* OpenThemeDataPtr)(HWND hwnd, LPCWSTR pszClassList); +typedef HRESULT(WINAPI* CloseThemeDataPtr)(HANDLE hTheme); +typedef HRESULT(WINAPI* GetThemePartSizePtr)(HANDLE hTheme, HDC hdc, + int iPartId, int iStateId, + RECT* prc, int ts, SIZE* psz); +typedef HRESULT(WINAPI* GetThemeContentRectPtr)(HANDLE hTheme, HDC hdc, + int iPartId, int iStateId, + const RECT* pRect, + RECT* pContentRect); + +static void GetThemeSizes(HWND hwnd) { + HMODULE themeDLL = LoadLibrary(L"uxtheme.dll"); + + if (!themeDLL) return; + + OpenThemeDataPtr openTheme = + (OpenThemeDataPtr)GetProcAddress(themeDLL, "OpenThemeData"); + CloseThemeDataPtr closeTheme = + (CloseThemeDataPtr)GetProcAddress(themeDLL, "CloseThemeData"); + GetThemePartSizePtr getThemePartSize = + (GetThemePartSizePtr)GetProcAddress(themeDLL, "GetThemePartSize"); + + if (!openTheme || !closeTheme || !getThemePartSize) { + FreeLibrary(themeDLL); + return; + } + + HANDLE buttonTheme = openTheme(hwnd, L"Button"); + if (!buttonTheme) { + FreeLibrary(themeDLL); + return; + } + HDC hdc = GetDC(hwnd); + SIZE s; + getThemePartSize(buttonTheme, hdc, BP_CHECKBOX, 0, nullptr, TS_DRAW, &s); + gCheckboxPadding = s.cx; + closeTheme(buttonTheme); + FreeLibrary(themeDLL); +} + +// Gets the position of a window relative to another window's client area +static void GetRelativeRect(HWND hwnd, HWND hwndParent, RECT* r) { + GetWindowRect(hwnd, r); + MapWindowPoints(nullptr, hwndParent, (POINT*)r, 2); +} + +static void SetDlgItemVisible(HWND hwndDlg, UINT item, bool visible) { + HWND hwnd = GetDlgItem(hwndDlg, item); + + ShowWindow(hwnd, visible ? SW_SHOW : SW_HIDE); +} + +/* === Crash Reporting Dialog === */ + +static void StretchDialog(HWND hwndDlg, int ydiff) { + RECT r; + GetWindowRect(hwndDlg, &r); + r.bottom += ydiff; + MoveWindow(hwndDlg, r.left, r.top, r.right - r.left, r.bottom - r.top, TRUE); +} + +static void ReflowDialog(HWND hwndDlg, int ydiff) { + // Move items attached to the bottom down/up by as much as + // the window resize + for (set::const_iterator item = gAttachedBottom.begin(); + item != gAttachedBottom.end(); item++) { + RECT r; + HWND hwnd = GetDlgItem(hwndDlg, *item); + GetRelativeRect(hwnd, hwndDlg, &r); + r.top += ydiff; + r.bottom += ydiff; + MoveWindow(hwnd, r.left, r.top, r.right - r.left, r.bottom - r.top, TRUE); + } +} + +static DWORD WINAPI SendThreadProc(LPVOID param) { + bool finishedOk; + SendThreadData* td = (SendThreadData*)param; + + if (td->sendURL.empty()) { + finishedOk = false; + LogMessage("No server URL, not sending report"); + } else { + Json::StreamWriterBuilder builder; + builder["indentation"] = ""; + string parameters(Json::writeString(builder, td->queryParameters)); + google_breakpad::CrashReportSender sender(L""); + finishedOk = (sender.SendCrashReport(td->sendURL, parameters, td->files, + &td->serverResponse) == + google_breakpad::RESULT_SUCCEEDED); + if (finishedOk) { + LogMessage("Crash report submitted successfully"); + } else { + // get an error string and print it to the log + // XXX: would be nice to get the HTTP status code here, filed: + // http://code.google.com/p/google-breakpad/issues/detail?id=220 + LogMessage(FormatLastError()); + } + } + + if (gAutoSubmit) { + // Ordinarily this is done on the main thread in CrashReporterDialogProc, + // for auto submit we don't run that and it should be safe to finish up + // here as is done on other platforms. + SendCompleted(finishedOk, WideToUTF8(gSendData.serverResponse)); + } else { + PostMessage(td->hDlg, WM_UPLOADCOMPLETE, finishedOk ? 1 : 0, 0); + } + + return 0; +} + +static void EndCrashReporterDialog(HWND hwndDlg, int code) { + // Save the current values to the registry + SetBoolKey(gCrashReporterKey.c_str(), INCLUDE_URL_VALUE, + IsDlgButtonChecked(hwndDlg, IDC_INCLUDEURLCHECK) != 0); + SetBoolKey(gCrashReporterKey.c_str(), SUBMIT_REPORT_VALUE, + IsDlgButtonChecked(hwndDlg, IDC_SUBMITREPORTCHECK) != 0); + + EndDialog(hwndDlg, code); +} + +static void MaybeResizeProgressText(HWND hwndDlg) { + HWND hwndProgress = GetDlgItem(hwndDlg, IDC_PROGRESSTEXT); + HDC hdc = GetDC(hwndProgress); + HFONT hfont = (HFONT)SendMessage(hwndProgress, WM_GETFONT, 0, 0); + if (hfont) SelectObject(hdc, hfont); + SIZE size; + RECT rect; + GetRelativeRect(hwndProgress, hwndDlg, &rect); + + wchar_t text[1024]; + GetWindowText(hwndProgress, text, 1024); + + if (!GetTextExtentPoint32(hdc, text, wcslen(text), &size)) return; + + if (size.cx < (rect.right - rect.left)) return; + + // Figure out how much we need to resize things vertically + // This is sort of a fudge, but it should be good enough. + int wantedHeight = + size.cy * (int)ceil((float)size.cx / (float)(rect.right - rect.left)); + int diff = wantedHeight - (rect.bottom - rect.top); + if (diff <= 0) return; + + MoveWindow(hwndProgress, rect.left, rect.top, rect.right - rect.left, + wantedHeight, TRUE); + + gAttachedBottom.clear(); + gAttachedBottom.insert(IDC_CLOSEBUTTON); + gAttachedBottom.insert(IDC_RESTARTBUTTON); + + StretchDialog(hwndDlg, diff); + + for (size_t i = 0; i < sizeof(kDefaultAttachedBottom) / sizeof(UINT); i++) { + gAttachedBottom.insert(kDefaultAttachedBottom[i]); + } +} + +static void MaybeSendReport(HWND hwndDlg) { + if (!IsDlgButtonChecked(hwndDlg, IDC_SUBMITREPORTCHECK)) { + EndCrashReporterDialog(hwndDlg, 0); + return; + } + + // disable all the form controls + EnableWindow(GetDlgItem(hwndDlg, IDC_SUBMITREPORTCHECK), false); + EnableWindow(GetDlgItem(hwndDlg, IDC_VIEWREPORTBUTTON), false); + EnableWindow(GetDlgItem(hwndDlg, IDC_COMMENTTEXT), false); + EnableWindow(GetDlgItem(hwndDlg, IDC_INCLUDEURLCHECK), false); + EnableWindow(GetDlgItem(hwndDlg, IDC_CLOSEBUTTON), false); + EnableWindow(GetDlgItem(hwndDlg, IDC_RESTARTBUTTON), false); + + SetDlgItemText(hwndDlg, IDC_PROGRESSTEXT, Str(ST_REPORTDURINGSUBMIT).c_str()); + MaybeResizeProgressText(hwndDlg); + // start throbber + // play entire AVI, and loop + Animate_Play(GetDlgItem(hwndDlg, IDC_THROBBER), 0, -1, -1); + SetDlgItemVisible(hwndDlg, IDC_THROBBER, true); + gThreadHandle = nullptr; + gSendData.hDlg = hwndDlg; + gSendData.queryParameters = gQueryParameters; + + gThreadHandle = + CreateThread(nullptr, 0, SendThreadProc, &gSendData, 0, nullptr); +} + +static void RestartApplication() { + wstring cmdLine; + + for (unsigned int i = 0; i < gRestartArgs.size(); i++) { + cmdLine += L"\"" + UTF8ToWide(gRestartArgs[i]) + L"\" "; + } + + STARTUPINFO si; + PROCESS_INFORMATION pi; + + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + si.dwFlags = STARTF_USESHOWWINDOW; + si.wShowWindow = SW_SHOWNORMAL; + ZeroMemory(&pi, sizeof(pi)); + + if (CreateProcess(nullptr, (LPWSTR)cmdLine.c_str(), nullptr, nullptr, FALSE, + 0, nullptr, nullptr, &si, &pi)) { + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } +} + +static void ShowReportInfo(HWND hwndDlg) { + wstring description; + + for (Json::ValueConstIterator iter = gQueryParameters.begin(); + iter != gQueryParameters.end(); ++iter) { + description += UTF8ToWide(iter.name()); + description += L": "; + string value; + if (iter->isString()) { + value = iter->asString(); + } else { + Json::StreamWriterBuilder builder; + builder["indentation"] = ""; + value = Json::writeString(builder, *iter); + } + description += UTF8ToWide(value); + description += L"\n"; + } + + description += L"\n"; + description += Str(ST_EXTRAREPORTINFO); + + SetDlgItemText(hwndDlg, IDC_VIEWREPORTTEXT, description.c_str()); +} + +static void UpdateURL(HWND hwndDlg) { + if (IsDlgButtonChecked(hwndDlg, IDC_INCLUDEURLCHECK)) { + gQueryParameters["URL"] = gURLParameter; + } else { + gQueryParameters.removeMember("URL"); + } +} + +static void UpdateComment(HWND hwndDlg) { + wchar_t comment[MAX_COMMENT_LENGTH + 1]; + GetDlgItemTextW(hwndDlg, IDC_COMMENTTEXT, comment, + sizeof(comment) / sizeof(comment[0])); + if (wcslen(comment) > 0) + gQueryParameters["Comments"] = WideToUTF8(comment); + else + gQueryParameters.removeMember("Comments"); +} + +/* + * Dialog procedure for the "view report" dialog. + */ +static BOOL CALLBACK ViewReportDialogProc(HWND hwndDlg, UINT message, + WPARAM wParam, LPARAM lParam) { + switch (message) { + case WM_INITDIALOG: { + SetWindowText(hwndDlg, Str(ST_VIEWREPORTTITLE).c_str()); + SetDlgItemText(hwndDlg, IDOK, Str(ST_OK).c_str()); + SendDlgItemMessage(hwndDlg, IDC_VIEWREPORTTEXT, EM_SETTARGETDEVICE, + (WPARAM) nullptr, 0); + ShowReportInfo(hwndDlg); + SetFocus(GetDlgItem(hwndDlg, IDOK)); + return FALSE; + } + + case WM_COMMAND: { + if (HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == IDOK) + EndDialog(hwndDlg, 0); + return FALSE; + } + } + return FALSE; +} + +// Return the number of bytes this string will take encoded +// in UTF-8 +static inline int BytesInUTF8(wchar_t* str) { + // Just count size of buffer for UTF-8, minus one + // (we don't need to count the null terminator) + return WideCharToMultiByte(CP_UTF8, 0, str, -1, nullptr, 0, nullptr, + nullptr) - + 1; +} + +// Calculate the length of the text in this edit control (in bytes, +// in the UTF-8 encoding) after replacing the current selection +// with |insert|. +static int NewTextLength(HWND hwndEdit, wchar_t* insert) { + wchar_t current[MAX_COMMENT_LENGTH + 1]; + + GetWindowText(hwndEdit, current, MAX_COMMENT_LENGTH + 1); + DWORD selStart, selEnd; + SendMessage(hwndEdit, EM_GETSEL, (WPARAM)&selStart, (LPARAM)&selEnd); + + int selectionLength = 0; + if (selEnd - selStart > 0) { + wchar_t selection[MAX_COMMENT_LENGTH + 1]; + google_breakpad::WindowsStringUtils::safe_wcsncpy( + selection, MAX_COMMENT_LENGTH + 1, current + selStart, + selEnd - selStart); + selection[selEnd - selStart] = '\0'; + selectionLength = BytesInUTF8(selection); + } + + // current string length + replacement text length + // - replaced selection length + return BytesInUTF8(current) + BytesInUTF8(insert) - selectionLength; +} + +// Window procedure for subclassing edit controls +static LRESULT CALLBACK EditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, + LPARAM lParam) { + static WNDPROC super = nullptr; + + if (super == nullptr) super = (WNDPROC)GetWindowLongPtr(hwnd, GWLP_USERDATA); + + switch (uMsg) { + case WM_PAINT: { + HDC hdc; + PAINTSTRUCT ps; + RECT r; + wchar_t windowText[1024]; + + GetWindowText(hwnd, windowText, 1024); + // if the control contains text or is focused, draw it normally + if (GetFocus() == hwnd || windowText[0] != '\0') + return CallWindowProc(super, hwnd, uMsg, wParam, lParam); + + GetClientRect(hwnd, &r); + hdc = BeginPaint(hwnd, &ps); + FillRect(hdc, &r, + GetSysColorBrush(IsWindowEnabled(hwnd) ? COLOR_WINDOW + : COLOR_BTNFACE)); + SetTextColor(hdc, GetSysColor(COLOR_GRAYTEXT)); + SelectObject(hdc, (HFONT)GetStockObject(DEFAULT_GUI_FONT)); + SetBkMode(hdc, TRANSPARENT); + wchar_t* txt = (wchar_t*)GetProp(hwnd, L"PROP_GRAYTEXT"); + // Get the actual edit control rect + CallWindowProc(super, hwnd, EM_GETRECT, 0, (LPARAM)&r); + UINT format = DT_EDITCONTROL | DT_NOPREFIX | DT_WORDBREAK | DT_INTERNAL; + if (gRTLlayout) format |= DT_RIGHT; + if (txt) DrawText(hdc, txt, wcslen(txt), &r, format); + EndPaint(hwnd, &ps); + return 0; + } + + // We handle WM_CHAR and WM_PASTE to limit the comment box to 500 + // bytes in UTF-8. + case WM_CHAR: { + // Leave accelerator keys and non-printing chars (except LF) alone + if (wParam & (1 << 24) || wParam & (1 << 29) || + (wParam < ' ' && wParam != '\n')) + break; + + wchar_t ch[2] = {(wchar_t)wParam, 0}; + if (NewTextLength(hwnd, ch) > MAX_COMMENT_LENGTH) return 0; + + break; + } + + case WM_PASTE: { + if (IsClipboardFormatAvailable(CF_UNICODETEXT) && OpenClipboard(hwnd)) { + HGLOBAL hg = GetClipboardData(CF_UNICODETEXT); + wchar_t* pastedText = (wchar_t*)GlobalLock(hg); + int newSize = 0; + + if (pastedText) newSize = NewTextLength(hwnd, pastedText); + + GlobalUnlock(hg); + CloseClipboard(); + + if (newSize > MAX_COMMENT_LENGTH) return 0; + } + break; + } + + case WM_SETFOCUS: + case WM_KILLFOCUS: { + RECT r; + GetClientRect(hwnd, &r); + InvalidateRect(hwnd, &r, TRUE); + break; + } + + case WM_DESTROY: { + // cleanup our property + HGLOBAL hData = RemoveProp(hwnd, L"PROP_GRAYTEXT"); + if (hData) GlobalFree(hData); + } + } + + return CallWindowProc(super, hwnd, uMsg, wParam, lParam); +} + +// Resize a control to fit this text +static int ResizeControl(HWND hwndButton, RECT& rect, wstring text, + bool shiftLeft, int userDefinedPadding) { + HDC hdc = GetDC(hwndButton); + HFONT hfont = (HFONT)SendMessage(hwndButton, WM_GETFONT, 0, 0); + if (hfont) SelectObject(hdc, hfont); + SIZE size, oldSize; + int sizeDiff = 0; + + wchar_t oldText[1024]; + GetWindowText(hwndButton, oldText, 1024); + + if (GetTextExtentPoint32(hdc, text.c_str(), text.length(), &size) + // default text on the button + && GetTextExtentPoint32(hdc, oldText, wcslen(oldText), &oldSize)) { + /* + Expand control widths to accomidate wider text strings. For most + controls (including buttons) the text padding is defined by the + dialog's rc file. Some controls (such as checkboxes) have padding + that extends to the end of the dialog, in which case we ignore the + rc padding and rely on a user defined value passed in through + userDefinedPadding. + */ + int textIncrease = size.cx - oldSize.cx; + if (textIncrease < 0) return 0; + int existingTextPadding; + if (userDefinedPadding == 0) + existingTextPadding = (rect.right - rect.left) - oldSize.cx; + else + existingTextPadding = userDefinedPadding; + sizeDiff = textIncrease + existingTextPadding; + + if (shiftLeft) { + // shift left by the amount the button should grow + rect.left -= sizeDiff; + } else { + // grow right instead + rect.right += sizeDiff; + } + MoveWindow(hwndButton, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return sizeDiff; +} + +// The window was resized horizontally, so widen some of our +// controls to make use of the space +static void StretchControlsToFit(HWND hwndDlg) { + int controls[] = {IDC_DESCRIPTIONTEXT, IDC_SUBMITREPORTCHECK, IDC_COMMENTTEXT, + IDC_INCLUDEURLCHECK, IDC_PROGRESSTEXT}; + + RECT dlgRect; + GetClientRect(hwndDlg, &dlgRect); + + for (size_t i = 0; i < sizeof(controls) / sizeof(controls[0]); i++) { + RECT r; + HWND hwndControl = GetDlgItem(hwndDlg, controls[i]); + GetRelativeRect(hwndControl, hwndDlg, &r); + // 6 pixel spacing on the right + if (r.right + 6 != dlgRect.right) { + r.right = dlgRect.right - 6; + MoveWindow(hwndControl, r.left, r.top, r.right - r.left, r.bottom - r.top, + TRUE); + } + } +} + +static void SubmitReportChecked(HWND hwndDlg) { + bool enabled = (IsDlgButtonChecked(hwndDlg, IDC_SUBMITREPORTCHECK) != 0); + EnableWindow(GetDlgItem(hwndDlg, IDC_VIEWREPORTBUTTON), enabled); + EnableWindow(GetDlgItem(hwndDlg, IDC_COMMENTTEXT), enabled); + EnableWindow(GetDlgItem(hwndDlg, IDC_INCLUDEURLCHECK), enabled); + SetDlgItemVisible(hwndDlg, IDC_PROGRESSTEXT, enabled); +} + +static INT_PTR DialogBoxParamMaybeRTL(UINT idd, HWND hwndParent, + DLGPROC dlgProc, LPARAM param) { + INT_PTR rv = 0; + if (gRTLlayout) { + // We need to toggle the WS_EX_LAYOUTRTL style flag on the dialog + // template. + HRSRC hDialogRC = FindResource(nullptr, MAKEINTRESOURCE(idd), RT_DIALOG); + HGLOBAL hDlgTemplate = LoadResource(nullptr, hDialogRC); + DLGTEMPLATEEX* pDlgTemplate = (DLGTEMPLATEEX*)LockResource(hDlgTemplate); + unsigned long sizeDlg = SizeofResource(nullptr, hDialogRC); + HGLOBAL hMyDlgTemplate = GlobalAlloc(GPTR, sizeDlg); + DLGTEMPLATEEX* pMyDlgTemplate = (DLGTEMPLATEEX*)GlobalLock(hMyDlgTemplate); + memcpy(pMyDlgTemplate, pDlgTemplate, sizeDlg); + + pMyDlgTemplate->exStyle |= WS_EX_LAYOUTRTL; + + rv = DialogBoxIndirectParam(nullptr, (LPCDLGTEMPLATE)pMyDlgTemplate, + hwndParent, dlgProc, param); + GlobalUnlock(hMyDlgTemplate); + GlobalFree(hMyDlgTemplate); + } else { + rv = DialogBoxParam(nullptr, MAKEINTRESOURCE(idd), hwndParent, dlgProc, + param); + } + + return rv; +} + +static BOOL CALLBACK CrashReporterDialogProc(HWND hwndDlg, UINT message, + WPARAM wParam, LPARAM lParam) { + static int sHeight = 0; + + bool success; + bool enabled; + + switch (message) { + case WM_INITDIALOG: { + GetThemeSizes(hwndDlg); + RECT r; + GetClientRect(hwndDlg, &r); + sHeight = r.bottom - r.top; + + SetWindowText(hwndDlg, Str(ST_CRASHREPORTERTITLE).c_str()); + HICON hIcon = + LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(IDI_MAINICON)); + SendMessage(hwndDlg, WM_SETICON, ICON_SMALL, (LPARAM)hIcon); + SendMessage(hwndDlg, WM_SETICON, ICON_BIG, (LPARAM)hIcon); + + // resize the "View Report" button based on the string length + RECT rect; + HWND hwnd = GetDlgItem(hwndDlg, IDC_VIEWREPORTBUTTON); + GetRelativeRect(hwnd, hwndDlg, &rect); + ResizeControl(hwnd, rect, Str(ST_VIEWREPORT), false, 0); + SetDlgItemText(hwndDlg, IDC_VIEWREPORTBUTTON, Str(ST_VIEWREPORT).c_str()); + + hwnd = GetDlgItem(hwndDlg, IDC_SUBMITREPORTCHECK); + GetRelativeRect(hwnd, hwndDlg, &rect); + long maxdiff = ResizeControl(hwnd, rect, Str(ST_CHECKSUBMIT), false, + gCheckboxPadding); + SetDlgItemText(hwndDlg, IDC_SUBMITREPORTCHECK, + Str(ST_CHECKSUBMIT).c_str()); + + if (!CheckBoolKey(gCrashReporterKey.c_str(), SUBMIT_REPORT_VALUE, + &enabled)) + enabled = true; + + CheckDlgButton(hwndDlg, IDC_SUBMITREPORTCHECK, + enabled ? BST_CHECKED : BST_UNCHECKED); + SubmitReportChecked(hwndDlg); + + HWND hwndComment = GetDlgItem(hwndDlg, IDC_COMMENTTEXT); + WNDPROC OldWndProc = (WNDPROC)SetWindowLongPtr( + hwndComment, GWLP_WNDPROC, (LONG_PTR)EditSubclassProc); + + // Subclass comment edit control to get placeholder text + SetWindowLongPtr(hwndComment, GWLP_USERDATA, (LONG_PTR)OldWndProc); + wstring commentGrayText = Str(ST_COMMENTGRAYTEXT); + wchar_t* hMem = (wchar_t*)GlobalAlloc( + GPTR, (commentGrayText.length() + 1) * sizeof(wchar_t)); + wcscpy(hMem, commentGrayText.c_str()); + SetProp(hwndComment, L"PROP_GRAYTEXT", hMem); + + hwnd = GetDlgItem(hwndDlg, IDC_INCLUDEURLCHECK); + GetRelativeRect(hwnd, hwndDlg, &rect); + long diff = + ResizeControl(hwnd, rect, Str(ST_CHECKURL), false, gCheckboxPadding); + maxdiff = std::max(diff, maxdiff); + SetDlgItemText(hwndDlg, IDC_INCLUDEURLCHECK, Str(ST_CHECKURL).c_str()); + + // want this on by default + if (CheckBoolKey(gCrashReporterKey.c_str(), INCLUDE_URL_VALUE, + &enabled) && + !enabled) { + CheckDlgButton(hwndDlg, IDC_INCLUDEURLCHECK, BST_UNCHECKED); + } else { + CheckDlgButton(hwndDlg, IDC_INCLUDEURLCHECK, BST_CHECKED); + } + + SetDlgItemText(hwndDlg, IDC_PROGRESSTEXT, + Str(ST_REPORTPRESUBMIT).c_str()); + + RECT closeRect; + HWND hwndClose = GetDlgItem(hwndDlg, IDC_CLOSEBUTTON); + GetRelativeRect(hwndClose, hwndDlg, &closeRect); + + RECT restartRect; + HWND hwndRestart = GetDlgItem(hwndDlg, IDC_RESTARTBUTTON); + GetRelativeRect(hwndRestart, hwndDlg, &restartRect); + + // set the close button text and shift the buttons around + // since the size may need to change + int sizeDiff = ResizeControl(hwndClose, closeRect, Str(ST_QUIT), true, 0); + restartRect.left -= sizeDiff; + restartRect.right -= sizeDiff; + SetDlgItemText(hwndDlg, IDC_CLOSEBUTTON, Str(ST_QUIT).c_str()); + + if (gRestartArgs.size() > 0) { + // Resize restart button to fit text + ResizeControl(hwndRestart, restartRect, Str(ST_RESTART), true, 0); + SetDlgItemText(hwndDlg, IDC_RESTARTBUTTON, Str(ST_RESTART).c_str()); + } else { + // No restart arguments, so just hide the restart button + SetDlgItemVisible(hwndDlg, IDC_RESTARTBUTTON, false); + } + // See if we need to widen the window + // Leave 6 pixels on either side + 6 pixels between the buttons + int neededSize = closeRect.right - closeRect.left + restartRect.right - + restartRect.left + 6 * 3; + GetClientRect(hwndDlg, &r); + // We may already have resized one of the checkboxes above + maxdiff = std::max(maxdiff, neededSize - (r.right - r.left)); + + if (maxdiff > 0) { + // widen window + GetWindowRect(hwndDlg, &r); + r.right += maxdiff; + MoveWindow(hwndDlg, r.left, r.top, r.right - r.left, r.bottom - r.top, + TRUE); + // shift both buttons right + if (restartRect.left + maxdiff < 6) maxdiff += 6; + closeRect.left += maxdiff; + closeRect.right += maxdiff; + restartRect.left += maxdiff; + restartRect.right += maxdiff; + MoveWindow(hwndClose, closeRect.left, closeRect.top, + closeRect.right - closeRect.left, + closeRect.bottom - closeRect.top, TRUE); + StretchControlsToFit(hwndDlg); + } + // need to move the restart button regardless + MoveWindow(hwndRestart, restartRect.left, restartRect.top, + restartRect.right - restartRect.left, + restartRect.bottom - restartRect.top, TRUE); + + // Resize the description text last, in case the window was resized + // before this. + SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, EM_SETEVENTMASK, + (WPARAM) nullptr, ENM_REQUESTRESIZE); + + wstring description = Str(ST_CRASHREPORTERHEADER); + description += L"\n\n"; + description += Str(ST_CRASHREPORTERDESCRIPTION); + SetDlgItemText(hwndDlg, IDC_DESCRIPTIONTEXT, description.c_str()); + + // Make the title bold. + CHARFORMAT fmt = { + 0, + }; + fmt.cbSize = sizeof(fmt); + fmt.dwMask = CFM_BOLD; + fmt.dwEffects = CFE_BOLD; + SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, EM_SETSEL, 0, + Str(ST_CRASHREPORTERHEADER).length()); + SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, EM_SETCHARFORMAT, + SCF_SELECTION, (LPARAM)&fmt); + SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, EM_SETSEL, 0, 0); + // Force redraw. + SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, EM_SETTARGETDEVICE, + (WPARAM) nullptr, 0); + // Force resize. + SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, EM_REQUESTRESIZE, 0, 0); + + // if no URL was given, hide the URL checkbox + if (!gQueryParameters.isMember("URL")) { + RECT urlCheckRect; + GetWindowRect(GetDlgItem(hwndDlg, IDC_INCLUDEURLCHECK), &urlCheckRect); + + SetDlgItemVisible(hwndDlg, IDC_INCLUDEURLCHECK, false); + + gAttachedBottom.erase(IDC_VIEWREPORTBUTTON); + gAttachedBottom.erase(IDC_SUBMITREPORTCHECK); + gAttachedBottom.erase(IDC_COMMENTTEXT); + + StretchDialog(hwndDlg, urlCheckRect.top - urlCheckRect.bottom); + + gAttachedBottom.insert(IDC_VIEWREPORTBUTTON); + gAttachedBottom.insert(IDC_SUBMITREPORTCHECK); + gAttachedBottom.insert(IDC_COMMENTTEXT); + } + + MaybeResizeProgressText(hwndDlg); + + // Open the AVI resource for the throbber + Animate_Open(GetDlgItem(hwndDlg, IDC_THROBBER), + MAKEINTRESOURCE(IDR_THROBBER)); + + UpdateURL(hwndDlg); + + SetFocus(GetDlgItem(hwndDlg, IDC_SUBMITREPORTCHECK)); + return FALSE; + } + case WM_SIZE: { + ReflowDialog(hwndDlg, HIWORD(lParam) - sHeight); + sHeight = HIWORD(lParam); + InvalidateRect(hwndDlg, nullptr, TRUE); + return FALSE; + } + case WM_NOTIFY: { + NMHDR* notification = reinterpret_cast(lParam); + if (notification->code == EN_REQUESTRESIZE) { + // Resizing the rich edit control to fit the description text. + REQRESIZE* reqresize = reinterpret_cast(lParam); + RECT newSize = reqresize->rc; + RECT oldSize; + GetRelativeRect(notification->hwndFrom, hwndDlg, &oldSize); + + // resize the text box as requested + MoveWindow(notification->hwndFrom, newSize.left, newSize.top, + newSize.right - newSize.left, newSize.bottom - newSize.top, + TRUE); + + // Resize the dialog to fit (the WM_SIZE handler will move the controls) + StretchDialog(hwndDlg, newSize.bottom - oldSize.bottom); + } + return FALSE; + } + case WM_COMMAND: { + if (HIWORD(wParam) == BN_CLICKED) { + switch (LOWORD(wParam)) { + case IDC_VIEWREPORTBUTTON: + DialogBoxParamMaybeRTL(IDD_VIEWREPORTDIALOG, hwndDlg, + (DLGPROC)ViewReportDialogProc, 0); + break; + case IDC_SUBMITREPORTCHECK: + SubmitReportChecked(hwndDlg); + break; + case IDC_INCLUDEURLCHECK: + UpdateURL(hwndDlg); + break; + case IDC_CLOSEBUTTON: + MaybeSendReport(hwndDlg); + break; + case IDC_RESTARTBUTTON: + RestartApplication(); + MaybeSendReport(hwndDlg); + break; + } + } else if (HIWORD(wParam) == EN_CHANGE) { + switch (LOWORD(wParam)) { + case IDC_COMMENTTEXT: + UpdateComment(hwndDlg); + } + } + + return FALSE; + } + case WM_UPLOADCOMPLETE: { + WaitForSingleObject(gThreadHandle, INFINITE); + success = (wParam == 1); + SendCompleted(success, WideToUTF8(gSendData.serverResponse)); + // hide throbber + Animate_Stop(GetDlgItem(hwndDlg, IDC_THROBBER)); + SetDlgItemVisible(hwndDlg, IDC_THROBBER, false); + + SetDlgItemText(hwndDlg, IDC_PROGRESSTEXT, + success ? Str(ST_REPORTSUBMITSUCCESS).c_str() + : Str(ST_SUBMITFAILED).c_str()); + MaybeResizeProgressText(hwndDlg); + // close dialog after 5 seconds + SetTimer(hwndDlg, 0, 5000, nullptr); + // + return TRUE; + } + + case WM_TIMER: { + // The "1" gets used down in UIShowCrashUI to indicate that we at least + // tried to send the report. + EndCrashReporterDialog(hwndDlg, 1); + return FALSE; + } + + case WM_CLOSE: { + EndCrashReporterDialog(hwndDlg, 0); + return FALSE; + } + } + return FALSE; +} + +static wstring UTF8ToWide(const string& utf8, bool* success) { + wchar_t* buffer = nullptr; + int buffer_size = + MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, nullptr, 0); + if (buffer_size == 0) { + if (success) *success = false; + return L""; + } + + buffer = new wchar_t[buffer_size]; + if (buffer == nullptr) { + if (success) *success = false; + return L""; + } + + MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, buffer, buffer_size); + wstring str = buffer; + delete[] buffer; + + if (success) *success = true; + + return str; +} + +static string WideToMBCP(const wstring& wide, unsigned int cp, + bool* success = nullptr) { + char* buffer = nullptr; + int buffer_size = WideCharToMultiByte(cp, 0, wide.c_str(), -1, nullptr, 0, + nullptr, nullptr); + if (buffer_size == 0) { + if (success) *success = false; + return ""; + } + + buffer = new char[buffer_size]; + if (buffer == nullptr) { + if (success) *success = false; + return ""; + } + + WideCharToMultiByte(cp, 0, wide.c_str(), -1, buffer, buffer_size, nullptr, + nullptr); + string mb = buffer; + delete[] buffer; + + if (success) *success = true; + + return mb; +} + +string WideToUTF8(const wstring& wide, bool* success) { + return WideToMBCP(wide, CP_UTF8, success); +} + +/* === Crashreporter UI Functions === */ + +bool UIInit() { + for (size_t i = 0; i < sizeof(kDefaultAttachedBottom) / sizeof(UINT); i++) { + gAttachedBottom.insert(kDefaultAttachedBottom[i]); + } + + DoInitCommonControls(); + + return true; +} + +void UIShutdown() {} + +void UIShowDefaultUI() { + MessageBox(nullptr, Str(ST_CRASHREPORTERDEFAULT).c_str(), L"Crash Reporter", + MB_OK | MB_ICONSTOP); +} + +static bool CanUseMainCrashReportServer() { + // Any NT from 6.0 and above is fine. + if (IsWindowsVersionOrGreater(6, 0, 0)) { + return true; + } + + // On NT 5 servers, we need Server 2003 SP2. + if (IsWindowsServer()) { + return IsWindowsVersionOrGreater(5, 2, 2); + } + + // Otherwise we have an NT 5 client. + // We need exactly XP SP3 (version 5.1 SP3 but not version 5.2). + return (IsWindowsVersionOrGreater(5, 1, 3) && + !IsWindowsVersionOrGreater(5, 2, 0)); +} + +bool UIShowCrashUI(const StringTable& files, const Json::Value& queryParameters, + const string& sendURL, const vector& restartArgs) { + gSendData.hDlg = nullptr; + gSendData.sendURL = UTF8ToWide(sendURL); + + // Older Windows don't support the crash report server's crypto. + // This is a hack to use an alternate server. + if (!CanUseMainCrashReportServer() && + gSendData.sendURL.find(SENDURL_ORIGINAL) == 0) { + gSendData.sendURL.replace(0, ARRAYSIZE(SENDURL_ORIGINAL) - 1, + SENDURL_XPSP2); + } + + for (StringTable::const_iterator i = files.begin(); i != files.end(); i++) { + gSendData.files[UTF8ToWide(i->first)] = UTF8ToWide(i->second); + } + + gQueryParameters = queryParameters; + + if (gQueryParameters.isMember("Vendor")) { + gCrashReporterKey = L"Software\\"; + string vendor = gQueryParameters["Vendor"].asString(); + if (!vendor.empty()) { + gCrashReporterKey += UTF8ToWide(vendor) + L"\\"; + } + string productName = gQueryParameters["ProductName"].asString(); + gCrashReporterKey += UTF8ToWide(productName) + L"\\Crash Reporter"; + } + + if (gQueryParameters.isMember("URL")) { + gURLParameter = gQueryParameters["URL"].asString(); + } + + gRestartArgs = restartArgs; + + if (gStrings.find("isRTL") != gStrings.end() && gStrings["isRTL"] == "yes") + gRTLlayout = true; + + if (gAutoSubmit) { + gSendData.queryParameters = gQueryParameters; + + gThreadHandle = + CreateThread(nullptr, 0, SendThreadProc, &gSendData, 0, nullptr); + WaitForSingleObject(gThreadHandle, INFINITE); + // SendCompleted was called from SendThreadProc + return true; + } + + return 1 == DialogBoxParamMaybeRTL(IDD_SENDDIALOG, nullptr, + (DLGPROC)CrashReporterDialogProc, 0); +} + +void UIError_impl(const string& message) { + wstring title = Str(ST_CRASHREPORTERTITLE); + if (title.empty()) title = L"Crash Reporter Error"; + + MessageBox(nullptr, UTF8ToWide(message).c_str(), title.c_str(), + MB_OK | MB_ICONSTOP); +} + +bool UIGetIniPath(string& path) { + wchar_t fileName[MAX_PATH]; + if (GetModuleFileName(nullptr, fileName, MAX_PATH)) { + // get crashreporter ini + wchar_t* s = wcsrchr(fileName, '.'); + if (s) { + wcscpy(s, L".ini"); + path = WideToUTF8(fileName); + return true; + } + } + + return false; +} + +bool UIGetSettingsPath(const string& vendor, const string& product, + string& settings_path) { + wchar_t path[MAX_PATH] = {}; + HRESULT hRes = SHGetFolderPath(nullptr, CSIDL_APPDATA, nullptr, 0, path); + if (FAILED(hRes)) { + // This provides a fallback for getting the path to APPDATA by querying the + // registry when the call to SHGetFolderPath is unable to provide this path + // (Bug 513958). + HKEY key; + DWORD type, dwRes; + DWORD size = sizeof(path) - 1; + dwRes = ::RegOpenKeyExW(HKEY_CURRENT_USER, + L"Software\\Microsoft\\Windows\\CurrentVersion\\Exp" + L"lorer\\Shell Folders", + 0, KEY_READ, &key); + if (dwRes != ERROR_SUCCESS) return false; + + dwRes = + RegQueryValueExW(key, L"AppData", nullptr, &type, (LPBYTE)&path, &size); + ::RegCloseKey(key); + // The call to RegQueryValueExW must succeed, the type must be REG_SZ, the + // buffer size must not equal 0, and the buffer size be a multiple of 2. + if (dwRes != ERROR_SUCCESS || type != REG_SZ || size == 0 || size % 2 != 0) + return false; + } + + if (!vendor.empty()) { + PathAppend(path, UTF8ToWide(vendor).c_str()); + } + PathAppend(path, UTF8ToWide(product).c_str()); + PathAppend(path, L"Crash Reports"); + settings_path = WideToUTF8(path); + return true; +} + +bool UIEnsurePathExists(const string& path) { + if (CreateDirectory(UTF8ToWide(path).c_str(), nullptr) == 0) { + if (GetLastError() != ERROR_ALREADY_EXISTS) return false; + } + + return true; +} + +bool UIFileExists(const string& path) { + DWORD attrs = GetFileAttributes(UTF8ToWide(path).c_str()); + return (attrs != INVALID_FILE_ATTRIBUTES); +} + +bool UIMoveFile(const string& oldfile, const string& newfile) { + if (oldfile == newfile) return true; + + return MoveFile(UTF8ToWide(oldfile).c_str(), UTF8ToWide(newfile).c_str()) == + TRUE; +} + +bool UIDeleteFile(const string& oldfile) { + return DeleteFile(UTF8ToWide(oldfile).c_str()) == TRUE; +} + +ifstream* UIOpenRead(const string& filename, ios_base::openmode mode) { +#if defined(_MSC_VER) + ifstream* file = new ifstream(); + file->open(UTF8ToWide(filename).c_str(), mode); +#else // GCC + ifstream* file = + new ifstream(WideToMBCP(UTF8ToWide(filename), CP_ACP).c_str(), mode); +#endif // _MSC_VER + + return file; +} + +ofstream* UIOpenWrite(const string& filename, ios_base::openmode mode) { +#if defined(_MSC_VER) + ofstream* file = new ofstream(); + file->open(UTF8ToWide(filename).c_str(), mode); +#else // GCC + ofstream* file = + new ofstream(WideToMBCP(UTF8ToWide(filename), CP_ACP).c_str(), mode); +#endif // _MSC_VER + + return file; +} + +struct FileData { + FILETIME timestamp; + wstring path; +}; + +static bool CompareFDTime(const FileData& fd1, const FileData& fd2) { + return CompareFileTime(&fd1.timestamp, &fd2.timestamp) > 0; +} + +void UIPruneSavedDumps(const std::string& directory) { + wstring wdirectory = UTF8ToWide(directory); + + WIN32_FIND_DATA fdata; + wstring findpath = wdirectory + L"\\*.dmp"; + HANDLE dirlist = FindFirstFile(findpath.c_str(), &fdata); + if (dirlist == INVALID_HANDLE_VALUE) return; + + vector dumpfiles; + + for (BOOL ok = true; ok; ok = FindNextFile(dirlist, &fdata)) { + FileData fd = {fdata.ftLastWriteTime, wdirectory + L"\\" + fdata.cFileName}; + dumpfiles.push_back(fd); + } + + sort(dumpfiles.begin(), dumpfiles.end(), CompareFDTime); + + while (dumpfiles.size() > kSaveCount) { + // get the path of the oldest file + wstring path = (--dumpfiles.end())->path; + DeleteFile(path.c_str()); + + // s/.dmp/.extra/ + path.replace(path.size() - 4, 4, L".extra"); + DeleteFile(path.c_str()); + + dumpfiles.pop_back(); + } + FindClose(dirlist); +} + +bool UIRunProgram(const string& exename, const std::vector& args, + bool wait) { + wstring cmdLine = L"\"" + UTF8ToWide(exename) + L"\" "; + + for (auto arg : args) { + cmdLine += L"\"" + UTF8ToWide(arg) + L"\" "; + } + + STARTUPINFO si = {}; + si.cb = sizeof(si); + PROCESS_INFORMATION pi = {}; + + if (!CreateProcess(/* lpApplicationName */ nullptr, (LPWSTR)cmdLine.c_str(), + /* lpProcessAttributes */ nullptr, + /* lpThreadAttributes */ nullptr, + /* bInheritHandles */ false, + NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW, + /* lpEnvironment */ nullptr, + /* lpCurrentDirectory */ nullptr, &si, &pi)) { + return false; + } + + if (wait) { + WaitForSingleObject(pi.hProcess, INFINITE); + } + + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + return true; +} + +string UIGetEnv(const string& name) { + const wchar_t* var = _wgetenv(UTF8ToWide(name).c_str()); + if (var && *var) { + return WideToUTF8(var); + } + + return ""; +} -- cgit v1.2.3