michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifdef WIN32_LEAN_AND_MEAN michael@0: #undef WIN32_LEAN_AND_MEAN michael@0: #endif michael@0: michael@0: #define NOMINMAX michael@0: michael@0: #include "crashreporter.h" michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include "resource.h" michael@0: #include "client/windows/sender/crash_report_sender.h" michael@0: #include "common/windows/string_utils-inl.h" michael@0: #include "mozilla/NullPtr.h" michael@0: michael@0: #define CRASH_REPORTER_VALUE L"Enabled" michael@0: #define SUBMIT_REPORT_VALUE L"SubmitCrashReport" michael@0: #define SUBMIT_REPORT_OLD L"SubmitReport" michael@0: #define INCLUDE_URL_VALUE L"IncludeURL" michael@0: #define EMAIL_ME_VALUE L"EmailMe" michael@0: #define EMAIL_VALUE L"Email" michael@0: #define MAX_EMAIL_LENGTH 1024 michael@0: michael@0: #define WM_UPLOADCOMPLETE WM_APP michael@0: michael@0: // Thanks, Windows.h :( michael@0: #undef min michael@0: #undef max michael@0: michael@0: using std::string; michael@0: using std::wstring; michael@0: using std::map; michael@0: using std::vector; michael@0: using std::set; michael@0: using std::ios; michael@0: using std::ifstream; michael@0: using std::ofstream; michael@0: michael@0: using namespace CrashReporter; michael@0: michael@0: typedef struct { michael@0: HWND hDlg; michael@0: wstring dumpFile; michael@0: map queryParameters; michael@0: wstring sendURL; michael@0: michael@0: wstring serverResponse; michael@0: } SendThreadData; michael@0: michael@0: /* michael@0: * Per http://msdn2.microsoft.com/en-us/library/ms645398(VS.85).aspx michael@0: * "The DLGTEMPLATEEX structure is not defined in any standard header file. michael@0: * The structure definition is provided here to explain the format of an michael@0: * extended template for a dialog box. michael@0: */ michael@0: typedef struct { michael@0: WORD dlgVer; michael@0: WORD signature; michael@0: DWORD helpID; michael@0: DWORD exStyle; michael@0: // There's more to this struct, but it has weird variable-length michael@0: // members, and I only actually need to touch exStyle on an existing michael@0: // instance, so I've omitted the rest. michael@0: } DLGTEMPLATEEX; michael@0: michael@0: static HANDLE gThreadHandle; michael@0: static SendThreadData gSendData = { 0, }; michael@0: static vector gRestartArgs; michael@0: static map gQueryParameters; michael@0: static wstring gCrashReporterKey(L"Software\\Mozilla\\Crash Reporter"); michael@0: static wstring gURLParameter; michael@0: static int gCheckboxPadding = 6; michael@0: static bool gRTLlayout = false; michael@0: michael@0: // When vertically resizing the dialog, these items should move down michael@0: static set gAttachedBottom; michael@0: michael@0: // Default set of items for gAttachedBottom michael@0: static const UINT kDefaultAttachedBottom[] = { michael@0: IDC_SUBMITREPORTCHECK, michael@0: IDC_VIEWREPORTBUTTON, michael@0: IDC_COMMENTTEXT, michael@0: IDC_INCLUDEURLCHECK, michael@0: IDC_EMAILMECHECK, michael@0: IDC_EMAILTEXT, michael@0: IDC_PROGRESSTEXT, michael@0: IDC_THROBBER, michael@0: IDC_CLOSEBUTTON, michael@0: IDC_RESTARTBUTTON, michael@0: }; michael@0: michael@0: static wstring UTF8ToWide(const string& utf8, bool *success = 0); michael@0: static DWORD WINAPI SendThreadProc(LPVOID param); michael@0: michael@0: static wstring Str(const char* key) michael@0: { michael@0: return UTF8ToWide(gStrings[key]); michael@0: } michael@0: michael@0: /* === win32 helper functions === */ michael@0: michael@0: static void DoInitCommonControls() michael@0: { michael@0: INITCOMMONCONTROLSEX ic; michael@0: ic.dwSize = sizeof(INITCOMMONCONTROLSEX); michael@0: ic.dwICC = ICC_PROGRESS_CLASS; michael@0: InitCommonControlsEx(&ic); michael@0: // also get the rich edit control michael@0: LoadLibrary(L"Msftedit.dll"); michael@0: } michael@0: michael@0: static bool GetBoolValue(HKEY hRegKey, LPCTSTR valueName, DWORD* value) michael@0: { michael@0: DWORD type, dataSize; michael@0: dataSize = sizeof(DWORD); michael@0: if (RegQueryValueEx(hRegKey, valueName, nullptr, michael@0: &type, (LPBYTE)value, &dataSize) == ERROR_SUCCESS && michael@0: type == REG_DWORD) michael@0: return true; michael@0: michael@0: return false; michael@0: } michael@0: michael@0: // Removes a value from HKEY_LOCAL_MACHINE and HKEY_CURRENT_USER, if it exists. michael@0: static void RemoveUnusedValues(const wchar_t* key, LPCTSTR valueName) michael@0: { michael@0: HKEY hRegKey; michael@0: michael@0: if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, key, 0, KEY_SET_VALUE, &hRegKey) michael@0: == ERROR_SUCCESS) { michael@0: RegDeleteValue(hRegKey, valueName); michael@0: RegCloseKey(hRegKey); michael@0: } michael@0: michael@0: if (RegOpenKeyEx(HKEY_CURRENT_USER, key, 0, KEY_SET_VALUE, &hRegKey) michael@0: == ERROR_SUCCESS) { michael@0: RegDeleteValue(hRegKey, valueName); michael@0: RegCloseKey(hRegKey); michael@0: } michael@0: } michael@0: michael@0: static bool CheckBoolKey(const wchar_t* key, michael@0: const wchar_t* valueName, michael@0: bool* enabled) michael@0: { michael@0: /* michael@0: * NOTE! This code needs to stay in sync with the preference checking michael@0: * code in in nsExceptionHandler.cpp. michael@0: */ michael@0: *enabled = false; michael@0: bool found = false; michael@0: HKEY hRegKey; michael@0: DWORD val; michael@0: // see if our reg key is set globally michael@0: if (RegOpenKey(HKEY_LOCAL_MACHINE, key, &hRegKey) == ERROR_SUCCESS) { michael@0: if (GetBoolValue(hRegKey, valueName, &val)) { michael@0: *enabled = (val == 1); michael@0: found = true; michael@0: } michael@0: RegCloseKey(hRegKey); michael@0: } else { michael@0: // look for it in user settings michael@0: if (RegOpenKey(HKEY_CURRENT_USER, key, &hRegKey) == ERROR_SUCCESS) { michael@0: if (GetBoolValue(hRegKey, valueName, &val)) { michael@0: *enabled = (val == 1); michael@0: found = true; michael@0: } michael@0: RegCloseKey(hRegKey); michael@0: } michael@0: } michael@0: michael@0: return found; michael@0: } michael@0: michael@0: static void SetBoolKey(const wchar_t* key, const wchar_t* value, bool enabled) michael@0: { michael@0: /* michael@0: * NOTE! This code needs to stay in sync with the preference setting michael@0: * code in in nsExceptionHandler.cpp. michael@0: */ michael@0: HKEY hRegKey; michael@0: michael@0: // remove the old value from the registry if it exists michael@0: RemoveUnusedValues(key, SUBMIT_REPORT_OLD); michael@0: michael@0: if (RegCreateKey(HKEY_CURRENT_USER, key, &hRegKey) == ERROR_SUCCESS) { michael@0: DWORD data = (enabled ? 1 : 0); michael@0: RegSetValueEx(hRegKey, value, 0, REG_DWORD, (LPBYTE)&data, sizeof(data)); michael@0: RegCloseKey(hRegKey); michael@0: } michael@0: } michael@0: michael@0: static bool GetStringValue(HKEY hRegKey, LPCTSTR valueName, wstring& value) michael@0: { michael@0: DWORD type, dataSize; michael@0: wchar_t buf[2048]; michael@0: dataSize = sizeof(buf); michael@0: if (RegQueryValueEx(hRegKey, valueName, nullptr, michael@0: &type, (LPBYTE)buf, &dataSize) == ERROR_SUCCESS && michael@0: type == REG_SZ) { michael@0: value = buf; michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: static bool GetStringKey(const wchar_t* key, michael@0: const wchar_t* valueName, michael@0: wstring& value) michael@0: { michael@0: value = L""; michael@0: bool found = false; michael@0: HKEY hRegKey; michael@0: // see if our reg key is set globally michael@0: if (RegOpenKey(HKEY_LOCAL_MACHINE, key, &hRegKey) == ERROR_SUCCESS) { michael@0: if (GetStringValue(hRegKey, valueName, value)) { michael@0: found = true; michael@0: } michael@0: RegCloseKey(hRegKey); michael@0: } else { michael@0: // look for it in user settings michael@0: if (RegOpenKey(HKEY_CURRENT_USER, key, &hRegKey) == ERROR_SUCCESS) { michael@0: if (GetStringValue(hRegKey, valueName, value)) { michael@0: found = true; michael@0: } michael@0: RegCloseKey(hRegKey); michael@0: } michael@0: } michael@0: michael@0: return found; michael@0: } michael@0: michael@0: static void SetStringKey(const wchar_t* key, michael@0: const wchar_t* valueName, michael@0: const wstring& value) michael@0: { michael@0: HKEY hRegKey; michael@0: if (RegCreateKey(HKEY_CURRENT_USER, key, &hRegKey) == ERROR_SUCCESS) { michael@0: RegSetValueEx(hRegKey, valueName, 0, REG_SZ, michael@0: (LPBYTE)value.c_str(), michael@0: (value.length() + 1) * sizeof(wchar_t)); michael@0: RegCloseKey(hRegKey); michael@0: } michael@0: } michael@0: michael@0: static string FormatLastError() michael@0: { michael@0: DWORD err = GetLastError(); michael@0: LPWSTR s; michael@0: string message = "Crash report submission failed: "; michael@0: // odds are it's a WinInet error michael@0: HANDLE hInetModule = GetModuleHandle(L"WinInet.dll"); michael@0: if(FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | michael@0: FORMAT_MESSAGE_FROM_SYSTEM | michael@0: FORMAT_MESSAGE_FROM_HMODULE, michael@0: hInetModule, michael@0: err, michael@0: 0, michael@0: (LPWSTR)&s, michael@0: 0, michael@0: nullptr) != 0) { michael@0: message += WideToUTF8(s, nullptr); michael@0: LocalFree(s); michael@0: // strip off any trailing newlines michael@0: string::size_type n = message.find_last_not_of("\r\n"); michael@0: if (n < message.size() - 1) { michael@0: message.erase(n+1); michael@0: } michael@0: } michael@0: else { michael@0: char buf[64]; michael@0: sprintf(buf, "Unknown error, error code: 0x%08x", err); michael@0: message += buf; michael@0: } michael@0: return message; michael@0: } michael@0: michael@0: #define TS_DRAW 2 michael@0: #define BP_CHECKBOX 3 michael@0: michael@0: typedef HANDLE (WINAPI*OpenThemeDataPtr)(HWND hwnd, LPCWSTR pszClassList); michael@0: typedef HRESULT (WINAPI*CloseThemeDataPtr)(HANDLE hTheme); michael@0: typedef HRESULT (WINAPI*GetThemePartSizePtr)(HANDLE hTheme, HDC hdc, int iPartId, michael@0: int iStateId, RECT* prc, int ts, michael@0: SIZE* psz); michael@0: typedef HRESULT (WINAPI*GetThemeContentRectPtr)(HANDLE hTheme, HDC hdc, int iPartId, michael@0: int iStateId, const RECT* pRect, michael@0: RECT* pContentRect); michael@0: michael@0: michael@0: static void GetThemeSizes(HWND hwnd) michael@0: { michael@0: HMODULE themeDLL = LoadLibrary(L"uxtheme.dll"); michael@0: michael@0: if (!themeDLL) michael@0: return; michael@0: michael@0: OpenThemeDataPtr openTheme = michael@0: (OpenThemeDataPtr)GetProcAddress(themeDLL, "OpenThemeData"); michael@0: CloseThemeDataPtr closeTheme = michael@0: (CloseThemeDataPtr)GetProcAddress(themeDLL, "CloseThemeData"); michael@0: GetThemePartSizePtr getThemePartSize = michael@0: (GetThemePartSizePtr)GetProcAddress(themeDLL, "GetThemePartSize"); michael@0: michael@0: if (!openTheme || !closeTheme || !getThemePartSize) { michael@0: FreeLibrary(themeDLL); michael@0: return; michael@0: } michael@0: michael@0: HANDLE buttonTheme = openTheme(hwnd, L"Button"); michael@0: if (!buttonTheme) { michael@0: FreeLibrary(themeDLL); michael@0: return; michael@0: } michael@0: HDC hdc = GetDC(hwnd); michael@0: SIZE s; michael@0: getThemePartSize(buttonTheme, hdc, BP_CHECKBOX, 0, nullptr, TS_DRAW, &s); michael@0: gCheckboxPadding = s.cx; michael@0: closeTheme(buttonTheme); michael@0: FreeLibrary(themeDLL); michael@0: } michael@0: michael@0: // Gets the position of a window relative to another window's client area michael@0: static void GetRelativeRect(HWND hwnd, HWND hwndParent, RECT* r) michael@0: { michael@0: GetWindowRect(hwnd, r); michael@0: MapWindowPoints(nullptr, hwndParent, (POINT*)r, 2); michael@0: } michael@0: michael@0: static void SetDlgItemVisible(HWND hwndDlg, UINT item, bool visible) michael@0: { michael@0: HWND hwnd = GetDlgItem(hwndDlg, item); michael@0: michael@0: ShowWindow(hwnd, visible ? SW_SHOW : SW_HIDE); michael@0: } michael@0: michael@0: static void SetDlgItemDisabled(HWND hwndDlg, UINT item, bool disabled) michael@0: { michael@0: HWND hwnd = GetDlgItem(hwndDlg, item); michael@0: LONG style = GetWindowLong(hwnd, GWL_STYLE); michael@0: if (!disabled) michael@0: style |= WS_DISABLED; michael@0: else michael@0: style &= ~WS_DISABLED; michael@0: michael@0: SetWindowLong(hwnd, GWL_STYLE, style); michael@0: } michael@0: michael@0: /* === Crash Reporting Dialog === */ michael@0: michael@0: static void StretchDialog(HWND hwndDlg, int ydiff) michael@0: { michael@0: RECT r; michael@0: GetWindowRect(hwndDlg, &r); michael@0: r.bottom += ydiff; michael@0: MoveWindow(hwndDlg, r.left, r.top, michael@0: r.right - r.left, r.bottom - r.top, TRUE); michael@0: } michael@0: michael@0: static void ReflowDialog(HWND hwndDlg, int ydiff) michael@0: { michael@0: // Move items attached to the bottom down/up by as much as michael@0: // the window resize michael@0: for (set::const_iterator item = gAttachedBottom.begin(); michael@0: item != gAttachedBottom.end(); michael@0: item++) { michael@0: RECT r; michael@0: HWND hwnd = GetDlgItem(hwndDlg, *item); michael@0: GetRelativeRect(hwnd, hwndDlg, &r); michael@0: r.top += ydiff; michael@0: r.bottom += ydiff; michael@0: MoveWindow(hwnd, r.left, r.top, michael@0: r.right - r.left, r.bottom - r.top, TRUE); michael@0: } michael@0: } michael@0: michael@0: static DWORD WINAPI SendThreadProc(LPVOID param) michael@0: { michael@0: bool finishedOk; michael@0: SendThreadData* td = (SendThreadData*)param; michael@0: michael@0: if (td->sendURL.empty()) { michael@0: finishedOk = false; michael@0: LogMessage("No server URL, not sending report"); michael@0: } else { michael@0: google_breakpad::CrashReportSender sender(L""); michael@0: finishedOk = (sender.SendCrashReport(td->sendURL, michael@0: td->queryParameters, michael@0: td->dumpFile, michael@0: &td->serverResponse) michael@0: == google_breakpad::RESULT_SUCCEEDED); michael@0: if (finishedOk) { michael@0: LogMessage("Crash report submitted successfully"); michael@0: } michael@0: else { michael@0: // get an error string and print it to the log michael@0: //XXX: would be nice to get the HTTP status code here, filed: michael@0: // http://code.google.com/p/google-breakpad/issues/detail?id=220 michael@0: LogMessage(FormatLastError()); michael@0: } michael@0: } michael@0: michael@0: PostMessage(td->hDlg, WM_UPLOADCOMPLETE, finishedOk ? 1 : 0, 0); michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: static void EndCrashReporterDialog(HWND hwndDlg, int code) michael@0: { michael@0: // Save the current values to the registry michael@0: wchar_t email[MAX_EMAIL_LENGTH]; michael@0: GetDlgItemTextW(hwndDlg, IDC_EMAILTEXT, email, michael@0: sizeof(email) / sizeof(email[0])); michael@0: SetStringKey(gCrashReporterKey.c_str(), EMAIL_VALUE, email); michael@0: michael@0: SetBoolKey(gCrashReporterKey.c_str(), INCLUDE_URL_VALUE, michael@0: IsDlgButtonChecked(hwndDlg, IDC_INCLUDEURLCHECK) != 0); michael@0: SetBoolKey(gCrashReporterKey.c_str(), EMAIL_ME_VALUE, michael@0: IsDlgButtonChecked(hwndDlg, IDC_EMAILMECHECK) != 0); michael@0: SetBoolKey(gCrashReporterKey.c_str(), SUBMIT_REPORT_VALUE, michael@0: IsDlgButtonChecked(hwndDlg, IDC_SUBMITREPORTCHECK) != 0); michael@0: michael@0: EndDialog(hwndDlg, code); michael@0: } michael@0: michael@0: static void MaybeResizeProgressText(HWND hwndDlg) michael@0: { michael@0: HWND hwndProgress = GetDlgItem(hwndDlg, IDC_PROGRESSTEXT); michael@0: HDC hdc = GetDC(hwndProgress); michael@0: HFONT hfont = (HFONT)SendMessage(hwndProgress, WM_GETFONT, 0, 0); michael@0: if (hfont) michael@0: SelectObject(hdc, hfont); michael@0: SIZE size; michael@0: RECT rect; michael@0: GetRelativeRect(hwndProgress, hwndDlg, &rect); michael@0: michael@0: wchar_t text[1024]; michael@0: GetWindowText(hwndProgress, text, 1024); michael@0: michael@0: if (!GetTextExtentPoint32(hdc, text, wcslen(text), &size)) michael@0: return; michael@0: michael@0: if (size.cx < (rect.right - rect.left)) michael@0: return; michael@0: michael@0: // Figure out how much we need to resize things vertically michael@0: // This is sort of a fudge, but it should be good enough. michael@0: int wantedHeight = size.cy * michael@0: (int)ceil((float)size.cx / (float)(rect.right - rect.left)); michael@0: int diff = wantedHeight - (rect.bottom - rect.top); michael@0: if (diff <= 0) michael@0: return; michael@0: michael@0: MoveWindow(hwndProgress, rect.left, rect.top, michael@0: rect.right - rect.left, michael@0: wantedHeight, michael@0: TRUE); michael@0: michael@0: gAttachedBottom.clear(); michael@0: gAttachedBottom.insert(IDC_CLOSEBUTTON); michael@0: gAttachedBottom.insert(IDC_RESTARTBUTTON); michael@0: michael@0: StretchDialog(hwndDlg, diff); michael@0: michael@0: for (int i = 0; i < sizeof(kDefaultAttachedBottom) / sizeof(UINT); i++) { michael@0: gAttachedBottom.insert(kDefaultAttachedBottom[i]); michael@0: } michael@0: } michael@0: michael@0: static void MaybeSendReport(HWND hwndDlg) michael@0: { michael@0: if (!IsDlgButtonChecked(hwndDlg, IDC_SUBMITREPORTCHECK)) { michael@0: EndCrashReporterDialog(hwndDlg, 0); michael@0: return; michael@0: } michael@0: michael@0: // disable all the form controls michael@0: EnableWindow(GetDlgItem(hwndDlg, IDC_SUBMITREPORTCHECK), false); michael@0: EnableWindow(GetDlgItem(hwndDlg, IDC_VIEWREPORTBUTTON), false); michael@0: EnableWindow(GetDlgItem(hwndDlg, IDC_COMMENTTEXT), false); michael@0: EnableWindow(GetDlgItem(hwndDlg, IDC_INCLUDEURLCHECK), false); michael@0: EnableWindow(GetDlgItem(hwndDlg, IDC_EMAILMECHECK), false); michael@0: EnableWindow(GetDlgItem(hwndDlg, IDC_EMAILTEXT), false); michael@0: EnableWindow(GetDlgItem(hwndDlg, IDC_CLOSEBUTTON), false); michael@0: EnableWindow(GetDlgItem(hwndDlg, IDC_RESTARTBUTTON), false); michael@0: michael@0: SetDlgItemText(hwndDlg, IDC_PROGRESSTEXT, Str(ST_REPORTDURINGSUBMIT).c_str()); michael@0: MaybeResizeProgressText(hwndDlg); michael@0: // start throbber michael@0: // play entire AVI, and loop michael@0: Animate_Play(GetDlgItem(hwndDlg, IDC_THROBBER), 0, -1, -1); michael@0: SetDlgItemVisible(hwndDlg, IDC_THROBBER, true); michael@0: gThreadHandle = nullptr; michael@0: gSendData.hDlg = hwndDlg; michael@0: gSendData.queryParameters = gQueryParameters; michael@0: michael@0: gThreadHandle = CreateThread(nullptr, 0, SendThreadProc, &gSendData, 0, michael@0: nullptr); michael@0: } michael@0: michael@0: static void RestartApplication() michael@0: { michael@0: wstring cmdLine; michael@0: michael@0: for (unsigned int i = 0; i < gRestartArgs.size(); i++) { michael@0: cmdLine += L"\"" + UTF8ToWide(gRestartArgs[i]) + L"\" "; michael@0: } michael@0: michael@0: STARTUPINFO si; michael@0: PROCESS_INFORMATION pi; michael@0: michael@0: ZeroMemory(&si, sizeof(si)); michael@0: si.cb = sizeof(si); michael@0: si.dwFlags = STARTF_USESHOWWINDOW; michael@0: si.wShowWindow = SW_SHOWNORMAL; michael@0: ZeroMemory(&pi, sizeof(pi)); michael@0: michael@0: if (CreateProcess(nullptr, (LPWSTR)cmdLine.c_str(), nullptr, nullptr, FALSE, michael@0: 0, nullptr, nullptr, &si, &pi)) { michael@0: CloseHandle(pi.hProcess); michael@0: CloseHandle(pi.hThread); michael@0: } michael@0: } michael@0: michael@0: static void ShowReportInfo(HWND hwndDlg) michael@0: { michael@0: wstring description; michael@0: michael@0: for (map::const_iterator i = gQueryParameters.begin(); michael@0: i != gQueryParameters.end(); michael@0: i++) { michael@0: description += i->first; michael@0: description += L": "; michael@0: description += i->second; michael@0: description += L"\n"; michael@0: } michael@0: michael@0: description += L"\n"; michael@0: description += Str(ST_EXTRAREPORTINFO); michael@0: michael@0: SetDlgItemText(hwndDlg, IDC_VIEWREPORTTEXT, description.c_str()); michael@0: } michael@0: michael@0: static void UpdateURL(HWND hwndDlg) michael@0: { michael@0: if (IsDlgButtonChecked(hwndDlg, IDC_INCLUDEURLCHECK)) { michael@0: gQueryParameters[L"URL"] = gURLParameter; michael@0: } else { michael@0: gQueryParameters.erase(L"URL"); michael@0: } michael@0: } michael@0: michael@0: static void UpdateEmail(HWND hwndDlg) michael@0: { michael@0: if (IsDlgButtonChecked(hwndDlg, IDC_EMAILMECHECK)) { michael@0: wchar_t email[MAX_EMAIL_LENGTH]; michael@0: GetDlgItemTextW(hwndDlg, IDC_EMAILTEXT, email, michael@0: sizeof(email) / sizeof(email[0])); michael@0: gQueryParameters[L"Email"] = email; michael@0: if (IsDlgButtonChecked(hwndDlg, IDC_SUBMITREPORTCHECK)) michael@0: EnableWindow(GetDlgItem(hwndDlg, IDC_EMAILTEXT), true); michael@0: } else { michael@0: gQueryParameters.erase(L"Email"); michael@0: EnableWindow(GetDlgItem(hwndDlg, IDC_EMAILTEXT), false); michael@0: } michael@0: } michael@0: michael@0: static void UpdateComment(HWND hwndDlg) michael@0: { michael@0: wchar_t comment[MAX_COMMENT_LENGTH + 1]; michael@0: GetDlgItemTextW(hwndDlg, IDC_COMMENTTEXT, comment, michael@0: sizeof(comment) / sizeof(comment[0])); michael@0: if (wcslen(comment) > 0) michael@0: gQueryParameters[L"Comments"] = comment; michael@0: else michael@0: gQueryParameters.erase(L"Comments"); michael@0: } michael@0: michael@0: /* michael@0: * Dialog procedure for the "view report" dialog. michael@0: */ michael@0: static BOOL CALLBACK ViewReportDialogProc(HWND hwndDlg, UINT message, michael@0: WPARAM wParam, LPARAM lParam) michael@0: { michael@0: switch (message) { michael@0: case WM_INITDIALOG: { michael@0: SetWindowText(hwndDlg, Str(ST_VIEWREPORTTITLE).c_str()); michael@0: SetDlgItemText(hwndDlg, IDOK, Str(ST_OK).c_str()); michael@0: SendDlgItemMessage(hwndDlg, IDC_VIEWREPORTTEXT, michael@0: EM_SETTARGETDEVICE, (WPARAM)nullptr, 0); michael@0: ShowReportInfo(hwndDlg); michael@0: SetFocus(GetDlgItem(hwndDlg, IDOK)); michael@0: return FALSE; michael@0: } michael@0: michael@0: case WM_COMMAND: { michael@0: if (HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == IDOK) michael@0: EndDialog(hwndDlg, 0); michael@0: return FALSE; michael@0: } michael@0: } michael@0: return FALSE; michael@0: } michael@0: michael@0: // Return the number of bytes this string will take encoded michael@0: // in UTF-8 michael@0: static inline int BytesInUTF8(wchar_t* str) michael@0: { michael@0: // Just count size of buffer for UTF-8, minus one michael@0: // (we don't need to count the null terminator) michael@0: return WideCharToMultiByte(CP_UTF8, 0, str, -1, michael@0: nullptr, 0, nullptr, nullptr) - 1; michael@0: } michael@0: michael@0: // Calculate the length of the text in this edit control (in bytes, michael@0: // in the UTF-8 encoding) after replacing the current selection michael@0: // with |insert|. michael@0: static int NewTextLength(HWND hwndEdit, wchar_t* insert) michael@0: { michael@0: wchar_t current[MAX_COMMENT_LENGTH + 1]; michael@0: michael@0: GetWindowText(hwndEdit, current, MAX_COMMENT_LENGTH + 1); michael@0: DWORD selStart, selEnd; michael@0: SendMessage(hwndEdit, EM_GETSEL, (WPARAM)&selStart, (LPARAM)&selEnd); michael@0: michael@0: int selectionLength = 0; michael@0: if (selEnd - selStart > 0) { michael@0: wchar_t selection[MAX_COMMENT_LENGTH + 1]; michael@0: google_breakpad::WindowsStringUtils::safe_wcsncpy(selection, michael@0: MAX_COMMENT_LENGTH + 1, michael@0: current + selStart, michael@0: selEnd - selStart); michael@0: selection[selEnd - selStart] = '\0'; michael@0: selectionLength = BytesInUTF8(selection); michael@0: } michael@0: michael@0: // current string length + replacement text length michael@0: // - replaced selection length michael@0: return BytesInUTF8(current) + BytesInUTF8(insert) - selectionLength; michael@0: } michael@0: michael@0: // Window procedure for subclassing edit controls michael@0: static LRESULT CALLBACK EditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, michael@0: LPARAM lParam) michael@0: { michael@0: static WNDPROC super = nullptr; michael@0: michael@0: if (super == nullptr) michael@0: super = (WNDPROC)GetWindowLongPtr(hwnd, GWLP_USERDATA); michael@0: michael@0: switch (uMsg) { michael@0: case WM_PAINT: { michael@0: HDC hdc; michael@0: PAINTSTRUCT ps; michael@0: RECT r; michael@0: wchar_t windowText[1024]; michael@0: michael@0: GetWindowText(hwnd, windowText, 1024); michael@0: // if the control contains text or is focused, draw it normally michael@0: if (GetFocus() == hwnd || windowText[0] != '\0') michael@0: return CallWindowProc(super, hwnd, uMsg, wParam, lParam); michael@0: michael@0: GetClientRect(hwnd, &r); michael@0: hdc = BeginPaint(hwnd, &ps); michael@0: FillRect(hdc, &r, GetSysColorBrush(IsWindowEnabled(hwnd) michael@0: ? COLOR_WINDOW : COLOR_BTNFACE)); michael@0: SetTextColor(hdc, GetSysColor(COLOR_GRAYTEXT)); michael@0: SelectObject(hdc, (HFONT)GetStockObject(DEFAULT_GUI_FONT)); michael@0: SetBkMode(hdc, TRANSPARENT); michael@0: wchar_t* txt = (wchar_t*)GetProp(hwnd, L"PROP_GRAYTEXT"); michael@0: // Get the actual edit control rect michael@0: CallWindowProc(super, hwnd, EM_GETRECT, 0, (LPARAM)&r); michael@0: UINT format = DT_EDITCONTROL | DT_NOPREFIX | DT_WORDBREAK | DT_INTERNAL; michael@0: if (gRTLlayout) michael@0: format |= DT_RIGHT; michael@0: if (txt) michael@0: DrawText(hdc, txt, wcslen(txt), &r, format); michael@0: EndPaint(hwnd, &ps); michael@0: return 0; michael@0: } michael@0: michael@0: // We handle WM_CHAR and WM_PASTE to limit the comment box to 500 michael@0: // bytes in UTF-8. michael@0: case WM_CHAR: { michael@0: // Leave accelerator keys and non-printing chars (except LF) alone michael@0: if (wParam & (1<<24) || wParam & (1<<29) || michael@0: (wParam < ' ' && wParam != '\n')) michael@0: break; michael@0: michael@0: wchar_t ch[2] = { (wchar_t)wParam, 0 }; michael@0: if (NewTextLength(hwnd, ch) > MAX_COMMENT_LENGTH) michael@0: return 0; michael@0: michael@0: break; michael@0: } michael@0: michael@0: case WM_PASTE: { michael@0: if (IsClipboardFormatAvailable(CF_UNICODETEXT) && michael@0: OpenClipboard(hwnd)) { michael@0: HGLOBAL hg = GetClipboardData(CF_UNICODETEXT); michael@0: wchar_t* pastedText = (wchar_t*)GlobalLock(hg); michael@0: int newSize = 0; michael@0: michael@0: if (pastedText) michael@0: newSize = NewTextLength(hwnd, pastedText); michael@0: michael@0: GlobalUnlock(hg); michael@0: CloseClipboard(); michael@0: michael@0: if (newSize > MAX_COMMENT_LENGTH) michael@0: return 0; michael@0: } michael@0: break; michael@0: } michael@0: michael@0: case WM_SETFOCUS: michael@0: case WM_KILLFOCUS: { michael@0: RECT r; michael@0: GetClientRect(hwnd, &r); michael@0: InvalidateRect(hwnd, &r, TRUE); michael@0: break; michael@0: } michael@0: michael@0: case WM_DESTROY: { michael@0: // cleanup our property michael@0: HGLOBAL hData = RemoveProp(hwnd, L"PROP_GRAYTEXT"); michael@0: if (hData) michael@0: GlobalFree(hData); michael@0: } michael@0: } michael@0: michael@0: return CallWindowProc(super, hwnd, uMsg, wParam, lParam); michael@0: } michael@0: michael@0: // Resize a control to fit this text michael@0: static int ResizeControl(HWND hwndButton, RECT& rect, wstring text, michael@0: bool shiftLeft, int userDefinedPadding) michael@0: { michael@0: HDC hdc = GetDC(hwndButton); michael@0: HFONT hfont = (HFONT)SendMessage(hwndButton, WM_GETFONT, 0, 0); michael@0: if (hfont) michael@0: SelectObject(hdc, hfont); michael@0: SIZE size, oldSize; michael@0: int sizeDiff = 0; michael@0: michael@0: wchar_t oldText[1024]; michael@0: GetWindowText(hwndButton, oldText, 1024); michael@0: michael@0: if (GetTextExtentPoint32(hdc, text.c_str(), text.length(), &size) michael@0: // default text on the button michael@0: && GetTextExtentPoint32(hdc, oldText, wcslen(oldText), &oldSize)) { michael@0: /* michael@0: Expand control widths to accomidate wider text strings. For most michael@0: controls (including buttons) the text padding is defined by the michael@0: dialog's rc file. Some controls (such as checkboxes) have padding michael@0: that extends to the end of the dialog, in which case we ignore the michael@0: rc padding and rely on a user defined value passed in through michael@0: userDefinedPadding. michael@0: */ michael@0: int textIncrease = size.cx - oldSize.cx; michael@0: if (textIncrease < 0) michael@0: return 0; michael@0: int existingTextPadding; michael@0: if (userDefinedPadding == 0) michael@0: existingTextPadding = (rect.right - rect.left) - oldSize.cx; michael@0: else michael@0: existingTextPadding = userDefinedPadding; michael@0: sizeDiff = textIncrease + existingTextPadding; michael@0: michael@0: if (shiftLeft) { michael@0: // shift left by the amount the button should grow michael@0: rect.left -= sizeDiff; michael@0: } michael@0: else { michael@0: // grow right instead michael@0: rect.right += sizeDiff; michael@0: } michael@0: MoveWindow(hwndButton, rect.left, rect.top, michael@0: rect.right - rect.left, michael@0: rect.bottom - rect.top, michael@0: TRUE); michael@0: } michael@0: return sizeDiff; michael@0: } michael@0: michael@0: // The window was resized horizontally, so widen some of our michael@0: // controls to make use of the space michael@0: static void StretchControlsToFit(HWND hwndDlg) michael@0: { michael@0: int controls[] = { michael@0: IDC_DESCRIPTIONTEXT, michael@0: IDC_SUBMITREPORTCHECK, michael@0: IDC_COMMENTTEXT, michael@0: IDC_INCLUDEURLCHECK, michael@0: IDC_EMAILMECHECK, michael@0: IDC_EMAILTEXT, michael@0: IDC_PROGRESSTEXT michael@0: }; michael@0: michael@0: RECT dlgRect; michael@0: GetClientRect(hwndDlg, &dlgRect); michael@0: michael@0: for (int i=0; iexStyle |= WS_EX_LAYOUTRTL; michael@0: michael@0: rv = DialogBoxIndirectParam(nullptr, (LPCDLGTEMPLATE)pMyDlgTemplate, michael@0: hwndParent, dlgProc, param); michael@0: GlobalUnlock(hMyDlgTemplate); michael@0: GlobalFree(hMyDlgTemplate); michael@0: } michael@0: else { michael@0: rv = DialogBoxParam(nullptr, MAKEINTRESOURCE(idd), hwndParent, michael@0: dlgProc, param); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: static BOOL CALLBACK CrashReporterDialogProc(HWND hwndDlg, UINT message, michael@0: WPARAM wParam, LPARAM lParam) michael@0: { michael@0: static int sHeight = 0; michael@0: michael@0: bool success; michael@0: bool enabled; michael@0: michael@0: switch (message) { michael@0: case WM_INITDIALOG: { michael@0: GetThemeSizes(hwndDlg); michael@0: RECT r; michael@0: GetClientRect(hwndDlg, &r); michael@0: sHeight = r.bottom - r.top; michael@0: michael@0: SetWindowText(hwndDlg, Str(ST_CRASHREPORTERTITLE).c_str()); michael@0: HICON hIcon = LoadIcon(GetModuleHandle(nullptr), michael@0: MAKEINTRESOURCE(IDI_MAINICON)); michael@0: SendMessage(hwndDlg, WM_SETICON, ICON_SMALL, (LPARAM)hIcon); michael@0: SendMessage(hwndDlg, WM_SETICON, ICON_BIG, (LPARAM)hIcon); michael@0: michael@0: // resize the "View Report" button based on the string length michael@0: RECT rect; michael@0: HWND hwnd = GetDlgItem(hwndDlg, IDC_VIEWREPORTBUTTON); michael@0: GetRelativeRect(hwnd, hwndDlg, &rect); michael@0: ResizeControl(hwnd, rect, Str(ST_VIEWREPORT), false, 0); michael@0: SetDlgItemText(hwndDlg, IDC_VIEWREPORTBUTTON, Str(ST_VIEWREPORT).c_str()); michael@0: michael@0: hwnd = GetDlgItem(hwndDlg, IDC_SUBMITREPORTCHECK); michael@0: GetRelativeRect(hwnd, hwndDlg, &rect); michael@0: long maxdiff = ResizeControl(hwnd, rect, Str(ST_CHECKSUBMIT), false, michael@0: gCheckboxPadding); michael@0: SetDlgItemText(hwndDlg, IDC_SUBMITREPORTCHECK, michael@0: Str(ST_CHECKSUBMIT).c_str()); michael@0: michael@0: if (!CheckBoolKey(gCrashReporterKey.c_str(), michael@0: SUBMIT_REPORT_VALUE, &enabled)) michael@0: enabled = ShouldEnableSending(); michael@0: michael@0: CheckDlgButton(hwndDlg, IDC_SUBMITREPORTCHECK, enabled ? BST_CHECKED michael@0: : BST_UNCHECKED); michael@0: SubmitReportChecked(hwndDlg); michael@0: michael@0: HWND hwndComment = GetDlgItem(hwndDlg, IDC_COMMENTTEXT); michael@0: WNDPROC OldWndProc = (WNDPROC)SetWindowLongPtr(hwndComment, michael@0: GWLP_WNDPROC, michael@0: (LONG_PTR)EditSubclassProc); michael@0: michael@0: // Subclass comment edit control to get placeholder text michael@0: SetWindowLongPtr(hwndComment, GWLP_USERDATA, (LONG_PTR)OldWndProc); michael@0: wstring commentGrayText = Str(ST_COMMENTGRAYTEXT); michael@0: wchar_t* hMem = (wchar_t*)GlobalAlloc(GPTR, (commentGrayText.length() + 1)*sizeof(wchar_t)); michael@0: wcscpy(hMem, commentGrayText.c_str()); michael@0: SetProp(hwndComment, L"PROP_GRAYTEXT", hMem); michael@0: michael@0: hwnd = GetDlgItem(hwndDlg, IDC_INCLUDEURLCHECK); michael@0: GetRelativeRect(hwnd, hwndDlg, &rect); michael@0: long diff = ResizeControl(hwnd, rect, Str(ST_CHECKURL), false, michael@0: gCheckboxPadding); michael@0: maxdiff = std::max(diff, maxdiff); michael@0: SetDlgItemText(hwndDlg, IDC_INCLUDEURLCHECK, Str(ST_CHECKURL).c_str()); michael@0: michael@0: // want this on by default michael@0: if (CheckBoolKey(gCrashReporterKey.c_str(), INCLUDE_URL_VALUE, &enabled) && michael@0: !enabled) { michael@0: CheckDlgButton(hwndDlg, IDC_INCLUDEURLCHECK, BST_UNCHECKED); michael@0: } else { michael@0: CheckDlgButton(hwndDlg, IDC_INCLUDEURLCHECK, BST_CHECKED); michael@0: } michael@0: michael@0: hwnd = GetDlgItem(hwndDlg, IDC_EMAILMECHECK); michael@0: GetRelativeRect(hwnd, hwndDlg, &rect); michael@0: diff = ResizeControl(hwnd, rect, Str(ST_CHECKEMAIL), false, michael@0: gCheckboxPadding); michael@0: maxdiff = std::max(diff, maxdiff); michael@0: SetDlgItemText(hwndDlg, IDC_EMAILMECHECK, Str(ST_CHECKEMAIL).c_str()); michael@0: michael@0: if (CheckBoolKey(gCrashReporterKey.c_str(), EMAIL_ME_VALUE, &enabled) && michael@0: enabled) { michael@0: CheckDlgButton(hwndDlg, IDC_EMAILMECHECK, BST_CHECKED); michael@0: } else { michael@0: CheckDlgButton(hwndDlg, IDC_EMAILMECHECK, BST_UNCHECKED); michael@0: } michael@0: michael@0: wstring email; michael@0: if (GetStringKey(gCrashReporterKey.c_str(), EMAIL_VALUE, email)) { michael@0: SetDlgItemText(hwndDlg, IDC_EMAILTEXT, email.c_str()); michael@0: } michael@0: michael@0: // Subclass email edit control to get placeholder text michael@0: HWND hwndEmail = GetDlgItem(hwndDlg, IDC_EMAILTEXT); michael@0: OldWndProc = (WNDPROC)SetWindowLongPtr(hwndEmail, michael@0: GWLP_WNDPROC, michael@0: (LONG_PTR)EditSubclassProc); michael@0: SetWindowLongPtr(hwndEmail, GWLP_USERDATA, (LONG_PTR)OldWndProc); michael@0: wstring emailGrayText = Str(ST_EMAILGRAYTEXT); michael@0: hMem = (wchar_t*)GlobalAlloc(GPTR, (emailGrayText.length() + 1)*sizeof(wchar_t)); michael@0: wcscpy(hMem, emailGrayText.c_str()); michael@0: SetProp(hwndEmail, L"PROP_GRAYTEXT", hMem); michael@0: michael@0: SetDlgItemText(hwndDlg, IDC_PROGRESSTEXT, Str(ST_REPORTPRESUBMIT).c_str()); michael@0: michael@0: RECT closeRect; michael@0: HWND hwndClose = GetDlgItem(hwndDlg, IDC_CLOSEBUTTON); michael@0: GetRelativeRect(hwndClose, hwndDlg, &closeRect); michael@0: michael@0: RECT restartRect; michael@0: HWND hwndRestart = GetDlgItem(hwndDlg, IDC_RESTARTBUTTON); michael@0: GetRelativeRect(hwndRestart, hwndDlg, &restartRect); michael@0: michael@0: // set the close button text and shift the buttons around michael@0: // since the size may need to change michael@0: int sizeDiff = ResizeControl(hwndClose, closeRect, Str(ST_QUIT), michael@0: true, 0); michael@0: restartRect.left -= sizeDiff; michael@0: restartRect.right -= sizeDiff; michael@0: SetDlgItemText(hwndDlg, IDC_CLOSEBUTTON, Str(ST_QUIT).c_str()); michael@0: michael@0: if (gRestartArgs.size() > 0) { michael@0: // Resize restart button to fit text michael@0: ResizeControl(hwndRestart, restartRect, Str(ST_RESTART), true, 0); michael@0: SetDlgItemText(hwndDlg, IDC_RESTARTBUTTON, Str(ST_RESTART).c_str()); michael@0: } else { michael@0: // No restart arguments, so just hide the restart button michael@0: SetDlgItemVisible(hwndDlg, IDC_RESTARTBUTTON, false); michael@0: } michael@0: // See if we need to widen the window michael@0: // Leave 6 pixels on either side + 6 pixels between the buttons michael@0: int neededSize = closeRect.right - closeRect.left + michael@0: restartRect.right - restartRect.left + 6 * 3; michael@0: GetClientRect(hwndDlg, &r); michael@0: // We may already have resized one of the checkboxes above michael@0: maxdiff = std::max(maxdiff, neededSize - (r.right - r.left)); michael@0: michael@0: if (maxdiff > 0) { michael@0: // widen window michael@0: GetWindowRect(hwndDlg, &r); michael@0: r.right += maxdiff; michael@0: MoveWindow(hwndDlg, r.left, r.top, michael@0: r.right - r.left, r.bottom - r.top, TRUE); michael@0: // shift both buttons right michael@0: if (restartRect.left + maxdiff < 6) michael@0: maxdiff += 6; michael@0: closeRect.left += maxdiff; michael@0: closeRect.right += maxdiff; michael@0: restartRect.left += maxdiff; michael@0: restartRect.right += maxdiff; michael@0: MoveWindow(hwndClose, closeRect.left, closeRect.top, michael@0: closeRect.right - closeRect.left, michael@0: closeRect.bottom - closeRect.top, michael@0: TRUE); michael@0: StretchControlsToFit(hwndDlg); michael@0: } michael@0: // need to move the restart button regardless michael@0: MoveWindow(hwndRestart, restartRect.left, restartRect.top, michael@0: restartRect.right - restartRect.left, michael@0: restartRect.bottom - restartRect.top, michael@0: TRUE); michael@0: michael@0: // Resize the description text last, in case the window was resized michael@0: // before this. michael@0: SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, michael@0: EM_SETEVENTMASK, (WPARAM)nullptr, michael@0: ENM_REQUESTRESIZE); michael@0: michael@0: wstring description = Str(ST_CRASHREPORTERHEADER); michael@0: description += L"\n\n"; michael@0: description += Str(ST_CRASHREPORTERDESCRIPTION); michael@0: SetDlgItemText(hwndDlg, IDC_DESCRIPTIONTEXT, description.c_str()); michael@0: michael@0: michael@0: // Make the title bold. michael@0: CHARFORMAT fmt = { 0, }; michael@0: fmt.cbSize = sizeof(fmt); michael@0: fmt.dwMask = CFM_BOLD; michael@0: fmt.dwEffects = CFE_BOLD; michael@0: SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, EM_SETSEL, michael@0: 0, Str(ST_CRASHREPORTERHEADER).length()); michael@0: SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, EM_SETCHARFORMAT, michael@0: SCF_SELECTION, (LPARAM)&fmt); michael@0: SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, EM_SETSEL, 0, 0); michael@0: // Force redraw. michael@0: SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, michael@0: EM_SETTARGETDEVICE, (WPARAM)nullptr, 0); michael@0: // Force resize. michael@0: SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, michael@0: EM_REQUESTRESIZE, 0, 0); michael@0: michael@0: // if no URL was given, hide the URL checkbox michael@0: if (gQueryParameters.find(L"URL") == gQueryParameters.end()) { michael@0: RECT urlCheckRect, emailCheckRect; michael@0: GetWindowRect(GetDlgItem(hwndDlg, IDC_INCLUDEURLCHECK), &urlCheckRect); michael@0: GetWindowRect(GetDlgItem(hwndDlg, IDC_EMAILMECHECK), &emailCheckRect); michael@0: michael@0: SetDlgItemVisible(hwndDlg, IDC_INCLUDEURLCHECK, false); michael@0: michael@0: gAttachedBottom.erase(IDC_VIEWREPORTBUTTON); michael@0: gAttachedBottom.erase(IDC_SUBMITREPORTCHECK); michael@0: gAttachedBottom.erase(IDC_COMMENTTEXT); michael@0: michael@0: StretchDialog(hwndDlg, urlCheckRect.top - emailCheckRect.top); michael@0: michael@0: gAttachedBottom.insert(IDC_VIEWREPORTBUTTON); michael@0: gAttachedBottom.insert(IDC_SUBMITREPORTCHECK); michael@0: gAttachedBottom.insert(IDC_COMMENTTEXT); michael@0: } michael@0: michael@0: MaybeResizeProgressText(hwndDlg); michael@0: michael@0: // Open the AVI resource for the throbber michael@0: Animate_Open(GetDlgItem(hwndDlg, IDC_THROBBER), michael@0: MAKEINTRESOURCE(IDR_THROBBER)); michael@0: michael@0: UpdateURL(hwndDlg); michael@0: UpdateEmail(hwndDlg); michael@0: michael@0: SetFocus(GetDlgItem(hwndDlg, IDC_SUBMITREPORTCHECK)); michael@0: return FALSE; michael@0: } michael@0: case WM_SIZE: { michael@0: ReflowDialog(hwndDlg, HIWORD(lParam) - sHeight); michael@0: sHeight = HIWORD(lParam); michael@0: InvalidateRect(hwndDlg, nullptr, TRUE); michael@0: return FALSE; michael@0: } michael@0: case WM_NOTIFY: { michael@0: NMHDR* notification = reinterpret_cast(lParam); michael@0: if (notification->code == EN_REQUESTRESIZE) { michael@0: // Resizing the rich edit control to fit the description text. michael@0: REQRESIZE* reqresize = reinterpret_cast(lParam); michael@0: RECT newSize = reqresize->rc; michael@0: RECT oldSize; michael@0: GetRelativeRect(notification->hwndFrom, hwndDlg, &oldSize); michael@0: michael@0: // resize the text box as requested michael@0: MoveWindow(notification->hwndFrom, newSize.left, newSize.top, michael@0: newSize.right - newSize.left, newSize.bottom - newSize.top, michael@0: TRUE); michael@0: michael@0: // Resize the dialog to fit (the WM_SIZE handler will move the controls) michael@0: StretchDialog(hwndDlg, newSize.bottom - oldSize.bottom); michael@0: } michael@0: return FALSE; michael@0: } michael@0: case WM_COMMAND: { michael@0: if (HIWORD(wParam) == BN_CLICKED) { michael@0: switch(LOWORD(wParam)) { michael@0: case IDC_VIEWREPORTBUTTON: michael@0: DialogBoxParamMaybeRTL(IDD_VIEWREPORTDIALOG, hwndDlg, michael@0: (DLGPROC)ViewReportDialogProc, 0); michael@0: break; michael@0: case IDC_SUBMITREPORTCHECK: michael@0: SubmitReportChecked(hwndDlg); michael@0: break; michael@0: case IDC_INCLUDEURLCHECK: michael@0: UpdateURL(hwndDlg); michael@0: break; michael@0: case IDC_EMAILMECHECK: michael@0: UpdateEmail(hwndDlg); michael@0: break; michael@0: case IDC_CLOSEBUTTON: michael@0: MaybeSendReport(hwndDlg); michael@0: break; michael@0: case IDC_RESTARTBUTTON: michael@0: RestartApplication(); michael@0: MaybeSendReport(hwndDlg); michael@0: break; michael@0: } michael@0: } else if (HIWORD(wParam) == EN_CHANGE) { michael@0: switch(LOWORD(wParam)) { michael@0: case IDC_EMAILTEXT: michael@0: UpdateEmail(hwndDlg); michael@0: break; michael@0: case IDC_COMMENTTEXT: michael@0: UpdateComment(hwndDlg); michael@0: } michael@0: } michael@0: michael@0: return FALSE; michael@0: } michael@0: case WM_UPLOADCOMPLETE: { michael@0: WaitForSingleObject(gThreadHandle, INFINITE); michael@0: success = (wParam == 1); michael@0: SendCompleted(success, WideToUTF8(gSendData.serverResponse)); michael@0: // hide throbber michael@0: Animate_Stop(GetDlgItem(hwndDlg, IDC_THROBBER)); michael@0: SetDlgItemVisible(hwndDlg, IDC_THROBBER, false); michael@0: michael@0: SetDlgItemText(hwndDlg, IDC_PROGRESSTEXT, michael@0: success ? michael@0: Str(ST_REPORTSUBMITSUCCESS).c_str() : michael@0: Str(ST_SUBMITFAILED).c_str()); michael@0: MaybeResizeProgressText(hwndDlg); michael@0: // close dialog after 5 seconds michael@0: SetTimer(hwndDlg, 0, 5000, nullptr); michael@0: // michael@0: return TRUE; michael@0: } michael@0: michael@0: case WM_LBUTTONDOWN: { michael@0: HWND hwndEmail = GetDlgItem(hwndDlg, IDC_EMAILTEXT); michael@0: POINT p = { LOWORD(lParam), HIWORD(lParam) }; michael@0: // if the email edit control is clicked, enable it, michael@0: // check the email checkbox, and focus the email edit control michael@0: if (ChildWindowFromPoint(hwndDlg, p) == hwndEmail && michael@0: IsWindowEnabled(GetDlgItem(hwndDlg, IDC_RESTARTBUTTON)) && michael@0: !IsWindowEnabled(hwndEmail) && michael@0: IsDlgButtonChecked(hwndDlg, IDC_SUBMITREPORTCHECK) != 0) { michael@0: CheckDlgButton(hwndDlg, IDC_EMAILMECHECK, BST_CHECKED); michael@0: UpdateEmail(hwndDlg); michael@0: SetFocus(hwndEmail); michael@0: } michael@0: break; michael@0: } michael@0: michael@0: case WM_TIMER: { michael@0: // The "1" gets used down in UIShowCrashUI to indicate that we at least michael@0: // tried to send the report. michael@0: EndCrashReporterDialog(hwndDlg, 1); michael@0: return FALSE; michael@0: } michael@0: michael@0: case WM_CLOSE: { michael@0: EndCrashReporterDialog(hwndDlg, 0); michael@0: return FALSE; michael@0: } michael@0: } michael@0: return FALSE; michael@0: } michael@0: michael@0: static wstring UTF8ToWide(const string& utf8, bool *success) michael@0: { michael@0: wchar_t* buffer = nullptr; michael@0: int buffer_size = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), michael@0: -1, nullptr, 0); michael@0: if(buffer_size == 0) { michael@0: if (success) michael@0: *success = false; michael@0: return L""; michael@0: } michael@0: michael@0: buffer = new wchar_t[buffer_size]; michael@0: if(buffer == nullptr) { michael@0: if (success) michael@0: *success = false; michael@0: return L""; michael@0: } michael@0: michael@0: MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), michael@0: -1, buffer, buffer_size); michael@0: wstring str = buffer; michael@0: delete [] buffer; michael@0: michael@0: if (success) michael@0: *success = true; michael@0: michael@0: return str; michael@0: } michael@0: michael@0: static string WideToMBCP(const wstring& wide, michael@0: unsigned int cp, michael@0: bool* success = nullptr) michael@0: { michael@0: char* buffer = nullptr; michael@0: int buffer_size = WideCharToMultiByte(cp, 0, wide.c_str(), michael@0: -1, nullptr, 0, nullptr, nullptr); michael@0: if(buffer_size == 0) { michael@0: if (success) michael@0: *success = false; michael@0: return ""; michael@0: } michael@0: michael@0: buffer = new char[buffer_size]; michael@0: if(buffer == nullptr) { michael@0: if (success) michael@0: *success = false; michael@0: return ""; michael@0: } michael@0: michael@0: WideCharToMultiByte(cp, 0, wide.c_str(), michael@0: -1, buffer, buffer_size, nullptr, nullptr); michael@0: string mb = buffer; michael@0: delete [] buffer; michael@0: michael@0: if (success) michael@0: *success = true; michael@0: michael@0: return mb; michael@0: } michael@0: michael@0: string WideToUTF8(const wstring& wide, bool* success) michael@0: { michael@0: return WideToMBCP(wide, CP_UTF8, success); michael@0: } michael@0: michael@0: /* === Crashreporter UI Functions === */ michael@0: michael@0: bool UIInit() michael@0: { michael@0: for (int i = 0; i < sizeof(kDefaultAttachedBottom) / sizeof(UINT); i++) { michael@0: gAttachedBottom.insert(kDefaultAttachedBottom[i]); michael@0: } michael@0: michael@0: DoInitCommonControls(); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void UIShutdown() michael@0: { michael@0: } michael@0: michael@0: void UIShowDefaultUI() michael@0: { michael@0: MessageBox(nullptr, Str(ST_CRASHREPORTERDEFAULT).c_str(), michael@0: L"Crash Reporter", michael@0: MB_OK | MB_ICONSTOP); michael@0: } michael@0: michael@0: bool UIShowCrashUI(const string& dumpFile, michael@0: const StringTable& queryParameters, michael@0: const string& sendURL, michael@0: const vector& restartArgs) michael@0: { michael@0: gSendData.hDlg = nullptr; michael@0: gSendData.dumpFile = UTF8ToWide(dumpFile); michael@0: gSendData.sendURL = UTF8ToWide(sendURL); michael@0: michael@0: for (StringTable::const_iterator i = queryParameters.begin(); michael@0: i != queryParameters.end(); michael@0: i++) { michael@0: gQueryParameters[UTF8ToWide(i->first)] = UTF8ToWide(i->second); michael@0: } michael@0: michael@0: if (gQueryParameters.find(L"Vendor") != gQueryParameters.end()) { michael@0: gCrashReporterKey = L"Software\\"; michael@0: if (!gQueryParameters[L"Vendor"].empty()) { michael@0: gCrashReporterKey += gQueryParameters[L"Vendor"] + L"\\"; michael@0: } michael@0: gCrashReporterKey += gQueryParameters[L"ProductName"] + L"\\Crash Reporter"; michael@0: } michael@0: michael@0: if (gQueryParameters.find(L"URL") != gQueryParameters.end()) michael@0: gURLParameter = gQueryParameters[L"URL"]; michael@0: michael@0: gRestartArgs = restartArgs; michael@0: michael@0: if (gStrings.find("isRTL") != gStrings.end() && michael@0: gStrings["isRTL"] == "yes") michael@0: gRTLlayout = true; michael@0: michael@0: return 1 == DialogBoxParamMaybeRTL(IDD_SENDDIALOG, nullptr, michael@0: (DLGPROC)CrashReporterDialogProc, 0); michael@0: } michael@0: michael@0: void UIError_impl(const string& message) michael@0: { michael@0: wstring title = Str(ST_CRASHREPORTERTITLE); michael@0: if (title.empty()) michael@0: title = L"Crash Reporter Error"; michael@0: michael@0: MessageBox(nullptr, UTF8ToWide(message).c_str(), title.c_str(), michael@0: MB_OK | MB_ICONSTOP); michael@0: } michael@0: michael@0: bool UIGetIniPath(string& path) michael@0: { michael@0: wchar_t fileName[MAX_PATH]; michael@0: if (GetModuleFileName(nullptr, fileName, MAX_PATH)) { michael@0: // get crashreporter ini michael@0: wchar_t* s = wcsrchr(fileName, '.'); michael@0: if (s) { michael@0: wcscpy(s, L".ini"); michael@0: path = WideToUTF8(fileName); michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: bool UIGetSettingsPath(const string& vendor, michael@0: const string& product, michael@0: string& settings_path) michael@0: { michael@0: wchar_t path[MAX_PATH]; michael@0: HRESULT hRes = SHGetFolderPath(nullptr, michael@0: CSIDL_APPDATA, michael@0: nullptr, michael@0: 0, michael@0: path); michael@0: if (FAILED(hRes)) { michael@0: // This provides a fallback for getting the path to APPDATA by querying the michael@0: // registry when the call to SHGetFolderPath is unable to provide this path michael@0: // (Bug 513958). michael@0: HKEY key; michael@0: DWORD type, size, dwRes; michael@0: dwRes = ::RegOpenKeyExW(HKEY_CURRENT_USER, michael@0: L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders", michael@0: 0, michael@0: KEY_READ, michael@0: &key); michael@0: if (dwRes != ERROR_SUCCESS) michael@0: return false; michael@0: michael@0: dwRes = RegQueryValueExW(key, michael@0: L"AppData", michael@0: nullptr, michael@0: &type, michael@0: (LPBYTE)&path, michael@0: &size); michael@0: ::RegCloseKey(key); michael@0: // The call to RegQueryValueExW must succeed, the type must be REG_SZ, the michael@0: // buffer size must not equal 0, and the buffer size be a multiple of 2. michael@0: if (dwRes != ERROR_SUCCESS || type != REG_SZ || size == 0 || size % 2 != 0) michael@0: return false; michael@0: } michael@0: michael@0: if (!vendor.empty()) { michael@0: PathAppend(path, UTF8ToWide(vendor).c_str()); michael@0: } michael@0: PathAppend(path, UTF8ToWide(product).c_str()); michael@0: PathAppend(path, L"Crash Reports"); michael@0: settings_path = WideToUTF8(path); michael@0: return true; michael@0: } michael@0: michael@0: bool UIEnsurePathExists(const string& path) michael@0: { michael@0: if (CreateDirectory(UTF8ToWide(path).c_str(), nullptr) == 0) { michael@0: if (GetLastError() != ERROR_ALREADY_EXISTS) michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool UIFileExists(const string& path) michael@0: { michael@0: DWORD attrs = GetFileAttributes(UTF8ToWide(path).c_str()); michael@0: return (attrs != INVALID_FILE_ATTRIBUTES); michael@0: } michael@0: michael@0: bool UIMoveFile(const string& oldfile, const string& newfile) michael@0: { michael@0: if (oldfile == newfile) michael@0: return true; michael@0: michael@0: return MoveFile(UTF8ToWide(oldfile).c_str(), UTF8ToWide(newfile).c_str()) michael@0: == TRUE; michael@0: } michael@0: michael@0: bool UIDeleteFile(const string& oldfile) michael@0: { michael@0: return DeleteFile(UTF8ToWide(oldfile).c_str()) == TRUE; michael@0: } michael@0: michael@0: ifstream* UIOpenRead(const string& filename) michael@0: { michael@0: // adapted from breakpad's src/common/windows/http_upload.cc michael@0: michael@0: // The "open" method on pre-MSVC8 ifstream implementations doesn't accept a michael@0: // wchar_t* filename, so use _wfopen directly in that case. For VC8 and michael@0: // later, _wfopen has been deprecated in favor of _wfopen_s, which does michael@0: // not exist in earlier versions, so let the ifstream open the file itself. michael@0: #if _MSC_VER >= 1400 // MSVC 2005/8 michael@0: ifstream* file = new ifstream(); michael@0: file->open(UTF8ToWide(filename).c_str(), ios::in); michael@0: #elif defined(_MSC_VER) michael@0: ifstream* file = new ifstream(_wfopen(UTF8ToWide(filename).c_str(), L"r")); michael@0: #else // GCC michael@0: ifstream* file = new ifstream(WideToMBCP(UTF8ToWide(filename), CP_ACP).c_str(), michael@0: ios::in); michael@0: #endif // _MSC_VER >= 1400 michael@0: michael@0: return file; michael@0: } michael@0: michael@0: ofstream* UIOpenWrite(const string& filename, bool append) // append=false michael@0: { michael@0: // adapted from breakpad's src/common/windows/http_upload.cc michael@0: michael@0: // The "open" method on pre-MSVC8 ifstream implementations doesn't accept a michael@0: // wchar_t* filename, so use _wfopen directly in that case. For VC8 and michael@0: // later, _wfopen has been deprecated in favor of _wfopen_s, which does michael@0: // not exist in earlier versions, so let the ifstream open the file itself. michael@0: #if _MSC_VER >= 1400 // MSVC 2005/8 michael@0: ofstream* file = new ofstream(); michael@0: file->open(UTF8ToWide(filename).c_str(), append ? ios::out | ios::app michael@0: : ios::out); michael@0: #elif defined(_MSC_VER) michael@0: ofstream* file = new ofstream(_wfopen(UTF8ToWide(filename).c_str(), michael@0: append ? L"a" : L"w")); michael@0: #else // GCC michael@0: ofstream* file = new ofstream(WideToMBCP(UTF8ToWide(filename), CP_ACP).c_str(), michael@0: append ? ios::out | ios::app : ios::out); michael@0: #endif // _MSC_VER >= 1400 michael@0: michael@0: return file; michael@0: } michael@0: michael@0: struct FileData michael@0: { michael@0: FILETIME timestamp; michael@0: wstring path; michael@0: }; michael@0: michael@0: static bool CompareFDTime(const FileData& fd1, const FileData& fd2) michael@0: { michael@0: return CompareFileTime(&fd1.timestamp, &fd2.timestamp) > 0; michael@0: } michael@0: michael@0: void UIPruneSavedDumps(const std::string& directory) michael@0: { michael@0: wstring wdirectory = UTF8ToWide(directory); michael@0: michael@0: WIN32_FIND_DATA fdata; michael@0: wstring findpath = wdirectory + L"\\*.dmp"; michael@0: HANDLE dirlist = FindFirstFile(findpath.c_str(), &fdata); michael@0: if (dirlist == INVALID_HANDLE_VALUE) michael@0: return; michael@0: michael@0: vector dumpfiles; michael@0: michael@0: for (BOOL ok = true; ok; ok = FindNextFile(dirlist, &fdata)) { michael@0: FileData fd = {fdata.ftLastWriteTime, wdirectory + L"\\" + fdata.cFileName}; michael@0: dumpfiles.push_back(fd); michael@0: } michael@0: michael@0: sort(dumpfiles.begin(), dumpfiles.end(), CompareFDTime); michael@0: michael@0: while (dumpfiles.size() > kSaveCount) { michael@0: // get the path of the oldest file michael@0: wstring path = (--dumpfiles.end())->path; michael@0: DeleteFile(path.c_str()); michael@0: michael@0: // s/.dmp/.extra/ michael@0: path.replace(path.size() - 4, 4, L".extra"); michael@0: DeleteFile(path.c_str()); michael@0: michael@0: dumpfiles.pop_back(); michael@0: } michael@0: }