gecko/toolkit/crashreporter/client/crashreporter_win.cpp

1352 lines
41 KiB
C++
Raw Normal View History

/* -*- 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 <ted.mielczarek@gmail.com>
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Ted Mielczarek <ted.mielczarek@gmail.com>
* Dave Camp <dcamp@mozilla.com>
*
* 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 <windows.h>
#include <commctrl.h>
#include <richedit.h>
#include <shellapi.h>
#include <shlobj.h>
#include <shlwapi.h>
#include <math.h>
#include <set>
#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<wstring,wstring> queryParameters;
wstring sendURL;
wstring serverResponse;
} SendThreadData;
static HANDLE gThreadHandle;
static SendThreadData gSendData = { 0, };
static vector<string> gRestartArgs;
static map<wstring,wstring> gQueryParameters;
static wstring gCrashReporterKey(L"Software\\Mozilla\\Crash Reporter");
static wstring gURLParameter;
static int gCheckboxPadding = 0;
// When vertically resizing the dialog, these items should move down
static set<UINT> 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");
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<UINT>::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<wstring,wstring>::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;
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 extraPadding)
{
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)) {
// We want the change in the text size, minus the existing empty
// space on the control.
sizeDiff = (size.cx - oldSize.cx) -
((rect.right - rect.left) - extraPadding - oldSize.cx);
if (sizeDiff <= 0)
return 0;
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<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 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(NULL),
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);
int 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) {
CheckDlgButton(hwndDlg, IDC_SUBMITREPORTCHECK, BST_UNCHECKED);
EnableWindow(GetDlgItem(hwndDlg, IDC_VIEWREPORTBUTTON), enabled);
EnableWindow(GetDlgItem(hwndDlg, IDC_COMMENTTEXT), enabled);
EnableWindow(GetDlgItem(hwndDlg, IDC_INCLUDEURLCHECK), enabled);
EnableWindow(GetDlgItem(hwndDlg, IDC_EMAILMECHECK), enabled);
EnableWindow(GetDlgItem(hwndDlg, IDC_EMAILTEXT), enabled);
SetDlgItemVisible(hwndDlg, IDC_PROGRESSTEXT, enabled);
} else {
CheckDlgButton(hwndDlg, IDC_SUBMITREPORTCHECK, BST_CHECKED);
}
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);
int diff = ResizeControl(hwnd, rect, Str(ST_CHECKURL), false,
gCheckboxPadding);
maxdiff = 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);
}
hwnd = GetDlgItem(hwndDlg, IDC_EMAILMECHECK);
GetRelativeRect(hwnd, hwndDlg, &rect);
diff = ResizeControl(hwnd, rect, Str(ST_CHECKEMAIL), false,
gCheckboxPadding);
maxdiff = max(diff, maxdiff);
SetDlgItemText(hwndDlg, IDC_EMAILMECHECK, Str(ST_CHECKEMAIL).c_str());
if (CheckBoolKey(gCrashReporterKey.c_str(), EMAIL_ME_VALUE, &enabled) &&
enabled) {
CheckDlgButton(hwndDlg, IDC_EMAILMECHECK, BST_CHECKED);
} else {
CheckDlgButton(hwndDlg, IDC_EMAILMECHECK, BST_UNCHECKED);
}
wstring email;
if (GetStringKey(gCrashReporterKey.c_str(), EMAIL_VALUE, email)) {
SetDlgItemText(hwndDlg, IDC_EMAILTEXT, email.c_str());
}
// Subclass email edit control to get placeholder text
HWND hwndEmail = GetDlgItem(hwndDlg, IDC_EMAILTEXT);
OldWndProc = (WNDPROC)SetWindowLongPtr(hwndEmail,
GWLP_WNDPROC,
(LONG_PTR)EditSubclassProc);
SetWindowLongPtr(hwndEmail, GWLP_USERDATA, (LONG_PTR)OldWndProc);
wstring emailGrayText = Str(ST_EMAILGRAYTEXT);
hMem = (wchar_t*)GlobalAlloc(GPTR, (emailGrayText.length() + 1)*sizeof(wchar_t));
wcscpy(hMem, emailGrayText.c_str());
SetProp(hwndEmail, L"PROP_GRAYTEXT", hMem);
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);
// Resize close button to fit text
ResizeControl(hwndClose, closeRect, Str(ST_QUIT), true, 0);
SetDlgItemText(hwndDlg, IDC_CLOSEBUTTON, Str(ST_QUIT).c_str());
if (gRestartArgs.size() > 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<NMHDR*>(lParam);
if (notification->code == EN_REQUESTRESIZE) {
// Resizing the rich edit control to fit the description text.
REQRESIZE* reqresize = reinterpret_cast<REQRESIZE*>(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:
enabled = (IsDlgButtonChecked(hwndDlg, IDC_SUBMITREPORTCHECK) != 0);
EnableWindow(GetDlgItem(hwndDlg, IDC_VIEWREPORTBUTTON), enabled);
EnableWindow(GetDlgItem(hwndDlg, IDC_COMMENTTEXT), enabled);
EnableWindow(GetDlgItem(hwndDlg, IDC_INCLUDEURLCHECK), enabled);
EnableWindow(GetDlgItem(hwndDlg, IDC_EMAILMECHECK), enabled);
EnableWindow(GetDlgItem(hwndDlg, IDC_EMAILTEXT),
enabled && (IsDlgButtonChecked(hwndDlg, IDC_EMAILMECHECK)
!= 0));
SetDlgItemVisible(hwndDlg, IDC_PROGRESSTEXT, enabled);
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<string>& 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;
}