/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Toolkit Crash Reporter * * The Initial Developer of the Original Code is * Ted Mielczarek * Portions created by the Initial Developer are Copyright (C) 2006 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Ted Mielczarek * Dave Camp * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #ifdef WIN32_LEAN_AND_MEAN #undef WIN32_LEAN_AND_MEAN #endif #include "crashreporter.h" #include #include #include #include #include #include #include #include #include "resource.h" #include "client/windows/sender/crash_report_sender.h" #include "common/windows/string_utils-inl.h" #define CRASH_REPORTER_VALUE L"Enabled" #define SUBMIT_REPORT_VALUE L"SubmitReport" #define INCLUDE_URL_VALUE L"IncludeURL" #define EMAIL_ME_VALUE L"EmailMe" #define EMAIL_VALUE L"Email" #define MAX_EMAIL_LENGTH 1024 #define WM_UPLOADCOMPLETE WM_APP using std::string; using std::wstring; using std::map; using std::vector; using std::set; using std::ios; using std::ifstream; using std::ofstream; using namespace CrashReporter; typedef struct { HWND hDlg; wstring dumpFile; map queryParameters; wstring sendURL; wstring serverResponse; } SendThreadData; static HANDLE gThreadHandle; static SendThreadData gSendData = { 0, }; static vector gRestartArgs; static map gQueryParameters; static wstring gCrashReporterKey(L"Software\\Mozilla\\Crash Reporter"); static wstring gURLParameter; static int gCheckboxPadding = 6; // 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_EMAILMECHECK, IDC_EMAILTEXT, 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"riched20.dll"); } static bool GetBoolValue(HKEY hRegKey, LPCTSTR valueName, DWORD* value) { DWORD type, dataSize; dataSize = sizeof(DWORD); if (RegQueryValueEx(hRegKey, valueName, NULL, &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) { *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) { 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 bool GetStringValue(HKEY hRegKey, LPCTSTR valueName, wstring& value) { DWORD type, dataSize; wchar_t buf[2048]; dataSize = sizeof(buf); if (RegQueryValueEx(hRegKey, valueName, NULL, &type, (LPBYTE)buf, &dataSize) == ERROR_SUCCESS && type == REG_SZ) { value = buf; return true; } return false; } static bool GetStringKey(const wchar_t* key, const wchar_t* valueName, wstring& value) { value = L""; bool found = false; HKEY hRegKey; // see if our reg key is set globally if (RegOpenKey(HKEY_LOCAL_MACHINE, key, &hRegKey) == ERROR_SUCCESS) { if (GetStringValue(hRegKey, valueName, value)) { found = true; } RegCloseKey(hRegKey); } else { // look for it in user settings if (RegOpenKey(HKEY_CURRENT_USER, key, &hRegKey) == ERROR_SUCCESS) { if (GetStringValue(hRegKey, valueName, value)) { found = true; } RegCloseKey(hRegKey); } } return found; } static void SetStringKey(const wchar_t* key, const wchar_t* valueName, const wstring& value) { HKEY hRegKey; if (RegCreateKey(HKEY_CURRENT_USER, key, &hRegKey) == ERROR_SUCCESS) { RegSetValueEx(hRegKey, valueName, 0, REG_SZ, (LPBYTE)value.c_str(), (value.length() + 1) * sizeof(wchar_t)); 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, NULL) != 0) { message += WideToUTF8(s, NULL); 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", 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); 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, NULL, 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(NULL, 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); } static void SetDlgItemDisabled(HWND hwndDlg, UINT item, bool disabled) { HWND hwnd = GetDlgItem(hwndDlg, item); LONG style = GetWindowLong(hwnd, GWL_STYLE); if (!disabled) style |= WS_DISABLED; else style &= ~WS_DISABLED; SetWindowLong(hwnd, GWL_STYLE, style); } /* === 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 { google_breakpad::CrashReportSender sender(L""); finishedOk = (sender.SendCrashReport(td->sendURL, td->queryParameters, td->dumpFile, &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()); } } 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 wchar_t email[MAX_EMAIL_LENGTH]; GetDlgItemText(hwndDlg, IDC_EMAILTEXT, email, sizeof(email)); SetStringKey(gCrashReporterKey.c_str(), EMAIL_VALUE, email); SetBoolKey(gCrashReporterKey.c_str(), INCLUDE_URL_VALUE, IsDlgButtonChecked(hwndDlg, IDC_INCLUDEURLCHECK) != 0); SetBoolKey(gCrashReporterKey.c_str(), EMAIL_ME_VALUE, IsDlgButtonChecked(hwndDlg, IDC_EMAILMECHECK) != 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 (int 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_EMAILMECHECK), false); EnableWindow(GetDlgItem(hwndDlg, IDC_EMAILTEXT), 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 = NULL; gSendData.hDlg = hwndDlg; gSendData.queryParameters = gQueryParameters; gThreadHandle = CreateThread(NULL, 0, SendThreadProc, &gSendData, 0, NULL); } 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(NULL, (LPWSTR)cmdLine.c_str(), NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) { CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } } static void ShowReportInfo(HWND hwndDlg) { wstring description; for (map::const_iterator i = gQueryParameters.begin(); i != gQueryParameters.end(); i++) { description += i->first; description += L": "; description += i->second; 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[L"URL"] = gURLParameter; } else { gQueryParameters.erase(L"URL"); } } static void UpdateEmail(HWND hwndDlg) { if (IsDlgButtonChecked(hwndDlg, IDC_EMAILMECHECK)) { wchar_t email[MAX_EMAIL_LENGTH]; GetDlgItemText(hwndDlg, IDC_EMAILTEXT, email, sizeof(email)); gQueryParameters[L"Email"] = email; if (IsDlgButtonChecked(hwndDlg, IDC_SUBMITREPORTCHECK)) EnableWindow(GetDlgItem(hwndDlg, IDC_EMAILTEXT), true); } else { gQueryParameters.erase(L"Email"); EnableWindow(GetDlgItem(hwndDlg, IDC_EMAILTEXT), false); } } static void UpdateComment(HWND hwndDlg) { wchar_t comment[MAX_COMMENT_LENGTH + 1]; GetDlgItemText(hwndDlg, IDC_COMMENTTEXT, comment, sizeof(comment)); if (wcslen(comment) > 0) gQueryParameters[L"Comments"] = comment; else gQueryParameters.erase(L"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)NULL, 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, NULL, 0, NULL, NULL) - 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 = NULL; if (super == NULL) 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); if (txt) DrawText(hdc, txt, wcslen(txt), &r, DT_EDITCONTROL | DT_NOPREFIX | DT_WORDBREAK | DT_INTERNAL); 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_EMAILMECHECK, IDC_EMAILTEXT, IDC_PROGRESSTEXT }; RECT dlgRect; GetClientRect(hwndDlg, &dlgRect); for (int i=0; i 0) { // set the restart button text and shift the buttons around // since the size may need to change int sizeDiff = ResizeControl(hwndRestart, restartRect, Str(ST_RESTART), true, 0); closeRect.left -= sizeDiff; closeRect.right -= sizeDiff; SetDlgItemText(hwndDlg, IDC_RESTARTBUTTON, Str(ST_RESTART).c_str()); } else { // No restart arguments, move the close button over to the side // and hide the restart button SetDlgItemVisible(hwndDlg, IDC_RESTARTBUTTON, false); int size = closeRect.right - closeRect.left; closeRect.right = restartRect.right; closeRect.left = closeRect.right - size; } // 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 = 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 (closeRect.left + maxdiff < 6) maxdiff += 6; closeRect.left += maxdiff; closeRect.right += maxdiff; restartRect.left += maxdiff; restartRect.right += maxdiff; MoveWindow(hwndRestart, restartRect.left, restartRect.top, restartRect.right - restartRect.left, restartRect.bottom - restartRect.top, TRUE); StretchControlsToFit(hwndDlg); } // need to move the close button regardless MoveWindow(hwndClose, closeRect.left, closeRect.top, closeRect.right - closeRect.left, closeRect.bottom - closeRect.top, TRUE); // Resize the description text last, in case the window was resized // before this. SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, EM_SETEVENTMASK, (WPARAM)NULL, 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); SendDlgItemMessage(hwndDlg, IDC_DESCRIPTIONTEXT, EM_SETTARGETDEVICE, (WPARAM)NULL, 0); // if no URL was given, hide the URL checkbox if (gQueryParameters.find(L"URL") == gQueryParameters.end()) { RECT urlCheckRect, emailCheckRect; GetWindowRect(GetDlgItem(hwndDlg, IDC_INCLUDEURLCHECK), &urlCheckRect); GetWindowRect(GetDlgItem(hwndDlg, IDC_EMAILMECHECK), &emailCheckRect); SetDlgItemVisible(hwndDlg, IDC_INCLUDEURLCHECK, false); gAttachedBottom.erase(IDC_VIEWREPORTBUTTON); gAttachedBottom.erase(IDC_SUBMITREPORTCHECK); gAttachedBottom.erase(IDC_COMMENTTEXT); StretchDialog(hwndDlg, urlCheckRect.top - emailCheckRect.top); 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); UpdateEmail(hwndDlg); SetFocus(GetDlgItem(hwndDlg, IDC_SUBMITREPORTCHECK)); return FALSE; } case WM_SIZE: { ReflowDialog(hwndDlg, HIWORD(lParam) - sHeight); sHeight = HIWORD(lParam); InvalidateRect(hwndDlg, NULL, 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: DialogBoxParam(NULL, MAKEINTRESOURCE(IDD_VIEWREPORTDIALOG), hwndDlg, (DLGPROC)ViewReportDialogProc, 0); break; case IDC_SUBMITREPORTCHECK: SubmitReportChecked(hwndDlg); break; case IDC_INCLUDEURLCHECK: UpdateURL(hwndDlg); break; case IDC_EMAILMECHECK: UpdateEmail(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_EMAILTEXT: UpdateEmail(hwndDlg); break; 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, NULL); // return TRUE; } case WM_LBUTTONDOWN: { HWND hwndEmail = GetDlgItem(hwndDlg, IDC_EMAILTEXT); POINT p = { LOWORD(lParam), HIWORD(lParam) }; // if the email edit control is clicked, enable it, // check the email checkbox, and focus the email edit control if (ChildWindowFromPoint(hwndDlg, p) == hwndEmail && !IsWindowEnabled(hwndEmail) && IsDlgButtonChecked(hwndDlg, IDC_SUBMITREPORTCHECK) != 0) { CheckDlgButton(hwndDlg, IDC_EMAILMECHECK, BST_CHECKED); UpdateEmail(hwndDlg); SetFocus(hwndEmail); } break; } 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 = NULL; int buffer_size = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, NULL, 0); if(buffer_size == 0) { if (success) *success = false; return L""; } buffer = new wchar_t[buffer_size]; if(buffer == NULL) { 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; } string WideToUTF8(const wstring& wide, bool* success) { char* buffer = NULL; int buffer_size = WideCharToMultiByte(CP_UTF8, 0, wide.c_str(), -1, NULL, 0, NULL, NULL); if(buffer_size == 0) { if (success) *success = false; return ""; } buffer = new char[buffer_size]; if(buffer == NULL) { if (success) *success = false; return ""; } WideCharToMultiByte(CP_UTF8, 0, wide.c_str(), -1, buffer, buffer_size, NULL, NULL); string utf8 = buffer; delete [] buffer; if (success) *success = true; return utf8; } /* === Crashreporter UI Functions === */ bool UIInit() { for (int i = 0; i < sizeof(kDefaultAttachedBottom) / sizeof(UINT); i++) { gAttachedBottom.insert(kDefaultAttachedBottom[i]); } DoInitCommonControls(); return true; } void UIShutdown() { } void UIShowDefaultUI() { MessageBox(NULL, Str(ST_CRASHREPORTERDEFAULT).c_str(), L"Crash Reporter", MB_OK | MB_ICONSTOP); } bool UIShowCrashUI(const string& dumpFile, const StringTable& queryParameters, const string& sendURL, const vector& restartArgs) { gSendData.hDlg = NULL; gSendData.dumpFile = UTF8ToWide(dumpFile); gSendData.sendURL = UTF8ToWide(sendURL); for (StringTable::const_iterator i = queryParameters.begin(); i != queryParameters.end(); i++) { gQueryParameters[UTF8ToWide(i->first)] = UTF8ToWide(i->second); } if (gQueryParameters.find(L"Vendor") != gQueryParameters.end()) { gCrashReporterKey = L"Software\\"; if (!gQueryParameters[L"Vendor"].empty()) { gCrashReporterKey += gQueryParameters[L"Vendor"] + L"\\"; } gCrashReporterKey += gQueryParameters[L"ProductName"] + L"\\Crash Reporter"; } if (gQueryParameters.find(L"URL") != gQueryParameters.end()) gURLParameter = gQueryParameters[L"URL"]; gRestartArgs = restartArgs; return DialogBoxParam(NULL, MAKEINTRESOURCE(IDD_SENDDIALOG), NULL, (DLGPROC)CrashReporterDialogProc, 0) == 1; } void UIError_impl(const string& message) { wstring title = Str(ST_CRASHREPORTERTITLE); if (title.empty()) title = L"Crash Reporter Error"; MessageBox(NULL, UTF8ToWide(message).c_str(), title.c_str(), MB_OK | MB_ICONSTOP); } bool UIGetIniPath(string& path) { wchar_t fileName[MAX_PATH]; if (GetModuleFileName(NULL, 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]; if(SUCCEEDED(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, path))) { 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; } return false; } bool UIEnsurePathExists(const string& path) { if (CreateDirectory(UTF8ToWide(path).c_str(), NULL) == 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) { // adapted from breakpad's src/common/windows/http_upload.cc // The "open" method on pre-MSVC8 ifstream implementations doesn't accept a // wchar_t* filename, so use _wfopen directly in that case. For VC8 and // later, _wfopen has been deprecated in favor of _wfopen_s, which does // not exist in earlier versions, so let the ifstream open the file itself. #if _MSC_VER >= 1400 // MSVC 2005/8 ifstream* file = new ifstream(); file->open(UTF8ToWide(filename).c_str(), ios::in); #else // _MSC_VER >= 1400 ifstream* file = new ifstream(_wfopen(UTF8ToWide(filename).c_str(), L"r")); #endif // _MSC_VER >= 1400 return file; } ofstream* UIOpenWrite(const string& filename, bool append) // append=false { // adapted from breakpad's src/common/windows/http_upload.cc // The "open" method on pre-MSVC8 ifstream implementations doesn't accept a // wchar_t* filename, so use _wfopen directly in that case. For VC8 and // later, _wfopen has been deprecated in favor of _wfopen_s, which does // not exist in earlier versions, so let the ifstream open the file itself. #if _MSC_VER >= 1400 // MSVC 2005/8 ofstream* file = new ofstream(); file->open(UTF8ToWide(filename).c_str(), append ? ios::out | ios::app : ios::out); #else // _MSC_VER >= 1400 ofstream* file = new ofstream(_wfopen(UTF8ToWide(filename).c_str(), append ? L"a" : L"w")); #endif // _MSC_VER >= 1400 return file; }