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: #import michael@0: #import michael@0: #include "crashreporter.h" michael@0: #include "crashreporter_osx.h" michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: using std::string; michael@0: using std::vector; michael@0: using std::ostringstream; michael@0: michael@0: using namespace CrashReporter; michael@0: michael@0: static NSAutoreleasePool* gMainPool; michael@0: static CrashReporterUI* gUI = 0; michael@0: static string gDumpFile; michael@0: static StringTable gQueryParameters; michael@0: static string gURLParameter; michael@0: static string gSendURL; michael@0: static vector gRestartArgs; michael@0: static bool gDidTrySend = false; michael@0: static bool gRTLlayout = false; michael@0: michael@0: static cpu_type_t pref_cpu_types[2] = { michael@0: #if defined(__i386__) michael@0: CPU_TYPE_X86, michael@0: #elif defined(__x86_64__) michael@0: CPU_TYPE_X86_64, michael@0: #elif defined(__ppc__) michael@0: CPU_TYPE_POWERPC, michael@0: #endif michael@0: CPU_TYPE_ANY }; michael@0: michael@0: #define NSSTR(s) [NSString stringWithUTF8String:(s).c_str()] michael@0: michael@0: static NSString* Str(const char* aName) michael@0: { michael@0: string str = gStrings[aName]; michael@0: if (str.empty()) str = "?"; michael@0: return NSSTR(str); michael@0: } michael@0: michael@0: static bool RestartApplication() michael@0: { michael@0: vector argv(gRestartArgs.size() + 1); michael@0: michael@0: posix_spawnattr_t spawnattr; michael@0: if (posix_spawnattr_init(&spawnattr) != 0) { michael@0: return false; michael@0: } michael@0: michael@0: // Set spawn attributes. michael@0: size_t attr_count = sizeof(pref_cpu_types) / sizeof(pref_cpu_types[0]); michael@0: size_t attr_ocount = 0; michael@0: if (posix_spawnattr_setbinpref_np(&spawnattr, michael@0: attr_count, michael@0: pref_cpu_types, michael@0: &attr_ocount) != 0 || michael@0: attr_ocount != attr_count) { michael@0: posix_spawnattr_destroy(&spawnattr); michael@0: return false; michael@0: } michael@0: michael@0: unsigned int i; michael@0: for (i = 0; i < gRestartArgs.size(); i++) { michael@0: argv[i] = (char*)gRestartArgs[i].c_str(); michael@0: } michael@0: argv[i] = 0; michael@0: michael@0: char **env = NULL; michael@0: char ***nsEnv = _NSGetEnviron(); michael@0: if (nsEnv) michael@0: env = *nsEnv; michael@0: int result = posix_spawnp(NULL, michael@0: argv[0], michael@0: NULL, michael@0: &spawnattr, michael@0: &argv[0], michael@0: env); michael@0: michael@0: posix_spawnattr_destroy(&spawnattr); michael@0: michael@0: return result == 0; michael@0: } michael@0: michael@0: @implementation CrashReporterUI michael@0: michael@0: -(void)awakeFromNib michael@0: { michael@0: gUI = self; michael@0: [mWindow center]; michael@0: michael@0: [mWindow setTitle:[[NSBundle mainBundle] michael@0: objectForInfoDictionaryKey:@"CFBundleName"]]; michael@0: } michael@0: michael@0: -(void)showCrashUI:(const string&)dumpfile michael@0: queryParameters:(const StringTable&)queryParameters michael@0: sendURL:(const string&)sendURL michael@0: { michael@0: gDumpFile = dumpfile; michael@0: gQueryParameters = queryParameters; michael@0: gSendURL = sendURL; michael@0: michael@0: [mWindow setTitle:Str(ST_CRASHREPORTERTITLE)]; michael@0: [mHeaderLabel setStringValue:Str(ST_CRASHREPORTERHEADER)]; michael@0: michael@0: NSRect viewReportFrame = [mViewReportButton frame]; michael@0: [mViewReportButton setTitle:Str(ST_VIEWREPORT)]; michael@0: [mViewReportButton sizeToFit]; michael@0: if (gRTLlayout) { michael@0: // sizeToFit will keep the left side fixed, so realign michael@0: float oldWidth = viewReportFrame.size.width; michael@0: viewReportFrame = [mViewReportButton frame]; michael@0: viewReportFrame.origin.x += oldWidth - viewReportFrame.size.width; michael@0: [mViewReportButton setFrame: viewReportFrame]; michael@0: } michael@0: michael@0: [mSubmitReportButton setTitle:Str(ST_CHECKSUBMIT)]; michael@0: [mIncludeURLButton setTitle:Str(ST_CHECKURL)]; michael@0: [mEmailMeButton setTitle:Str(ST_CHECKEMAIL)]; michael@0: [mViewReportOkButton setTitle:Str(ST_OK)]; michael@0: michael@0: [mCommentText setPlaceholder:Str(ST_COMMENTGRAYTEXT)]; michael@0: if (gRTLlayout) michael@0: [mCommentText toggleBaseWritingDirection:self]; michael@0: [[mEmailText cell] setPlaceholderString:Str(ST_EMAILGRAYTEXT)]; michael@0: michael@0: if (gQueryParameters.find("URL") != gQueryParameters.end()) { michael@0: // save the URL value in case the checkbox gets unchecked michael@0: gURLParameter = gQueryParameters["URL"]; michael@0: } michael@0: else { michael@0: // no URL specified, hide checkbox michael@0: [mIncludeURLButton removeFromSuperview]; michael@0: // shrink window to fit michael@0: NSRect frame = [mWindow frame]; michael@0: NSRect includeURLFrame = [mIncludeURLButton frame]; michael@0: NSRect emailFrame = [mEmailMeButton frame]; michael@0: int buttonMask = [mViewReportButton autoresizingMask]; michael@0: int checkMask = [mSubmitReportButton autoresizingMask]; michael@0: int commentScrollMask = [mCommentScrollView autoresizingMask]; michael@0: michael@0: [mViewReportButton setAutoresizingMask:NSViewMinYMargin]; michael@0: [mSubmitReportButton setAutoresizingMask:NSViewMinYMargin]; michael@0: [mCommentScrollView setAutoresizingMask:NSViewMinYMargin]; michael@0: michael@0: // remove all the space in between michael@0: frame.size.height -= includeURLFrame.origin.y - emailFrame.origin.y; michael@0: [mWindow setFrame:frame display: true animate:NO]; michael@0: michael@0: [mViewReportButton setAutoresizingMask:buttonMask]; michael@0: [mSubmitReportButton setAutoresizingMask:checkMask]; michael@0: [mCommentScrollView setAutoresizingMask:commentScrollMask]; michael@0: } michael@0: michael@0: // resize some buttons horizontally and possibly some controls vertically michael@0: [self doInitialResizing]; michael@0: michael@0: // load default state of submit checkbox michael@0: // we don't just do this via IB because we want the default to be michael@0: // off a certain percentage of the time michael@0: BOOL submitChecked = NO; michael@0: NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults]; michael@0: if (nil != [userDefaults objectForKey:@"submitReport"]) { michael@0: submitChecked = [userDefaults boolForKey:@"submitReport"]; michael@0: } michael@0: else { michael@0: // use compile-time specified enable percentage michael@0: submitChecked = ShouldEnableSending(); michael@0: [userDefaults setBool:submitChecked forKey:@"submitReport"]; michael@0: } michael@0: [mSubmitReportButton setState:(submitChecked ? NSOnState : NSOffState)]; michael@0: michael@0: [self updateSubmit]; michael@0: [self updateURL]; michael@0: [self updateEmail]; michael@0: michael@0: [mWindow makeKeyAndOrderFront:nil]; michael@0: } michael@0: michael@0: -(void)showErrorUI:(const string&)message michael@0: { michael@0: [self setView: mErrorView animate: NO]; michael@0: michael@0: [mErrorHeaderLabel setStringValue:Str(ST_CRASHREPORTERHEADER)]; michael@0: [self setStringFitVertically:mErrorLabel michael@0: string:NSSTR(message) michael@0: resizeWindow:YES]; michael@0: [mErrorCloseButton setTitle:Str(ST_OK)]; michael@0: michael@0: [mErrorCloseButton setKeyEquivalent:@"\r"]; michael@0: [mWindow makeFirstResponder:mErrorCloseButton]; michael@0: [mWindow makeKeyAndOrderFront:nil]; michael@0: } michael@0: michael@0: -(void)showReportInfo michael@0: { michael@0: NSDictionary* boldAttr = [NSDictionary michael@0: dictionaryWithObject: michael@0: [NSFont boldSystemFontOfSize: michael@0: [NSFont smallSystemFontSize]] michael@0: forKey:NSFontAttributeName]; michael@0: NSDictionary* normalAttr = [NSDictionary michael@0: dictionaryWithObject: michael@0: [NSFont systemFontOfSize: michael@0: [NSFont smallSystemFontSize]] michael@0: forKey:NSFontAttributeName]; michael@0: michael@0: [mViewReportTextView setString:@""]; michael@0: for (StringTable::iterator iter = gQueryParameters.begin(); michael@0: iter != gQueryParameters.end(); michael@0: iter++) { michael@0: NSAttributedString* key = [[NSAttributedString alloc] michael@0: initWithString:NSSTR(iter->first + ": ") michael@0: attributes:boldAttr]; michael@0: NSAttributedString* value = [[NSAttributedString alloc] michael@0: initWithString:NSSTR(iter->second + "\n") michael@0: attributes:normalAttr]; michael@0: [[mViewReportTextView textStorage] appendAttributedString: key]; michael@0: [[mViewReportTextView textStorage] appendAttributedString: value]; michael@0: [key release]; michael@0: [value release]; michael@0: } michael@0: michael@0: NSAttributedString* extra = [[NSAttributedString alloc] michael@0: initWithString:NSSTR("\n" + gStrings[ST_EXTRAREPORTINFO]) michael@0: attributes:normalAttr]; michael@0: [[mViewReportTextView textStorage] appendAttributedString: extra]; michael@0: [extra release]; michael@0: } michael@0: michael@0: - (void)maybeSubmitReport michael@0: { michael@0: if ([mSubmitReportButton state] == NSOnState) { michael@0: [self setStringFitVertically:mProgressText michael@0: string:Str(ST_REPORTDURINGSUBMIT) michael@0: resizeWindow:YES]; michael@0: // disable all the controls michael@0: [self enableControls:NO]; michael@0: [mSubmitReportButton setEnabled:NO]; michael@0: [mRestartButton setEnabled:NO]; michael@0: [mCloseButton setEnabled:NO]; michael@0: [mProgressIndicator startAnimation:self]; michael@0: gDidTrySend = true; michael@0: [self sendReport]; michael@0: } else { michael@0: [NSApp terminate:self]; michael@0: } michael@0: } michael@0: michael@0: - (void)closeMeDown:(id)unused michael@0: { michael@0: [NSApp terminate:self]; michael@0: } michael@0: michael@0: -(IBAction)submitReportClicked:(id)sender michael@0: { michael@0: [self updateSubmit]; michael@0: NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults]; michael@0: [userDefaults setBool:([mSubmitReportButton state] == NSOnState) michael@0: forKey:@"submitReport"]; michael@0: [userDefaults synchronize]; michael@0: } michael@0: michael@0: -(IBAction)viewReportClicked:(id)sender michael@0: { michael@0: [self showReportInfo]; michael@0: [NSApp beginSheet:mViewReportWindow modalForWindow:mWindow michael@0: modalDelegate:nil didEndSelector:nil contextInfo:nil]; michael@0: } michael@0: michael@0: - (IBAction)viewReportOkClicked:(id)sender; michael@0: { michael@0: [mViewReportWindow orderOut:nil]; michael@0: [NSApp endSheet:mViewReportWindow]; michael@0: } michael@0: michael@0: -(IBAction)closeClicked:(id)sender michael@0: { michael@0: [self maybeSubmitReport]; michael@0: } michael@0: michael@0: -(IBAction)restartClicked:(id)sender michael@0: { michael@0: RestartApplication(); michael@0: [self maybeSubmitReport]; michael@0: } michael@0: michael@0: - (IBAction)includeURLClicked:(id)sender michael@0: { michael@0: [self updateURL]; michael@0: } michael@0: michael@0: -(IBAction)emailMeClicked:(id)sender michael@0: { michael@0: [self updateEmail]; michael@0: } michael@0: michael@0: -(void)controlTextDidChange:(NSNotification *)note michael@0: { michael@0: [self updateEmail]; michael@0: } michael@0: michael@0: - (void)textDidChange:(NSNotification *)aNotification michael@0: { michael@0: // update comment parameter michael@0: if ([[[mCommentText textStorage] mutableString] length] > 0) michael@0: gQueryParameters["Comments"] = [[[mCommentText textStorage] mutableString] michael@0: UTF8String]; michael@0: else michael@0: gQueryParameters.erase("Comments"); michael@0: } michael@0: michael@0: // Limit the comment field to 500 bytes in UTF-8 michael@0: - (BOOL)textView:(NSTextView *)aTextView shouldChangeTextInRange:(NSRange)affectedCharRange replacementString:(NSString *)replacementString michael@0: { michael@0: // current string length + replacement text length - replaced range length michael@0: if (([[aTextView string] michael@0: lengthOfBytesUsingEncoding:NSUTF8StringEncoding] michael@0: + [replacementString lengthOfBytesUsingEncoding:NSUTF8StringEncoding] michael@0: - [[[aTextView string] substringWithRange:affectedCharRange] michael@0: lengthOfBytesUsingEncoding:NSUTF8StringEncoding]) michael@0: > MAX_COMMENT_LENGTH) { michael@0: return NO; michael@0: } michael@0: return YES; michael@0: } michael@0: michael@0: - (void)doInitialResizing michael@0: { michael@0: NSRect windowFrame = [mWindow frame]; michael@0: NSRect restartFrame = [mRestartButton frame]; michael@0: NSRect closeFrame = [mCloseButton frame]; michael@0: // resize close button to fit text michael@0: float oldCloseWidth = closeFrame.size.width; michael@0: [mCloseButton setTitle:Str(ST_QUIT)]; michael@0: [mCloseButton sizeToFit]; michael@0: closeFrame = [mCloseButton frame]; michael@0: // move close button left if it grew michael@0: if (!gRTLlayout) { michael@0: closeFrame.origin.x -= closeFrame.size.width - oldCloseWidth; michael@0: } michael@0: michael@0: if (gRestartArgs.size() == 0) { michael@0: [mRestartButton removeFromSuperview]; michael@0: if (!gRTLlayout) { michael@0: closeFrame.origin.x = restartFrame.origin.x + michael@0: (restartFrame.size.width - closeFrame.size.width); michael@0: } michael@0: else { michael@0: closeFrame.origin.x = restartFrame.origin.x; michael@0: } michael@0: [mCloseButton setFrame: closeFrame]; michael@0: [mCloseButton setKeyEquivalent:@"\r"]; michael@0: } else { michael@0: [mRestartButton setTitle:Str(ST_RESTART)]; michael@0: // resize "restart" button michael@0: float oldRestartWidth = restartFrame.size.width; michael@0: [mRestartButton sizeToFit]; michael@0: restartFrame = [mRestartButton frame]; michael@0: if (!gRTLlayout) { michael@0: // move left by the amount that the button grew michael@0: restartFrame.origin.x -= restartFrame.size.width - oldRestartWidth; michael@0: closeFrame.origin.x -= restartFrame.size.width - oldRestartWidth; michael@0: } michael@0: else { michael@0: // shift the close button right in RTL michael@0: closeFrame.origin.x += restartFrame.size.width - oldRestartWidth; michael@0: } michael@0: [mRestartButton setFrame: restartFrame]; michael@0: [mCloseButton setFrame: closeFrame]; michael@0: // possibly resize window if both buttons no longer fit michael@0: // leave 20 px from either side of the window, and 12 px michael@0: // between the buttons michael@0: float neededWidth = closeFrame.size.width + restartFrame.size.width + michael@0: 2*20 + 12; michael@0: michael@0: if (neededWidth > windowFrame.size.width) { michael@0: windowFrame.size.width = neededWidth; michael@0: [mWindow setFrame:windowFrame display: true animate: NO]; michael@0: } michael@0: [mRestartButton setKeyEquivalent:@"\r"]; michael@0: } michael@0: michael@0: NSButton *checkboxes[] = { michael@0: mSubmitReportButton, michael@0: mIncludeURLButton, michael@0: mEmailMeButton michael@0: }; michael@0: michael@0: for (int i=0; i<3; i++) { michael@0: NSRect frame = [checkboxes[i] frame]; michael@0: [checkboxes[i] sizeToFit]; michael@0: if (gRTLlayout) { michael@0: // sizeToFit will keep the left side fixed, so realign michael@0: float oldWidth = frame.size.width; michael@0: frame = [checkboxes[i] frame]; michael@0: frame.origin.x += oldWidth - frame.size.width; michael@0: [checkboxes[i] setFrame: frame]; michael@0: } michael@0: // keep existing spacing on left side, + 20 px spare on right michael@0: float neededWidth = frame.origin.x + frame.size.width + 20; michael@0: if (neededWidth > windowFrame.size.width) { michael@0: windowFrame.size.width = neededWidth; michael@0: [mWindow setFrame:windowFrame display: true animate: NO]; michael@0: } michael@0: } michael@0: michael@0: // do this down here because we may have made the window wider michael@0: // up above michael@0: [self setStringFitVertically:mDescriptionLabel michael@0: string:Str(ST_CRASHREPORTERDESCRIPTION) michael@0: resizeWindow:YES]; michael@0: michael@0: // now pin all the controls (except quit/submit) in place, michael@0: // if we lengthen the window after this, it's just to lengthen michael@0: // the progress text, so nothing above that text should move. michael@0: NSView* views[] = { michael@0: mSubmitReportButton, michael@0: mViewReportButton, michael@0: mCommentScrollView, michael@0: mIncludeURLButton, michael@0: mEmailMeButton, michael@0: mEmailText, michael@0: mProgressIndicator, michael@0: mProgressText michael@0: }; michael@0: for (unsigned int i=0; ifirst); michael@0: NSString* value = NSSTR(i->second); michael@0: [parameters setObject: value forKey: key]; michael@0: } michael@0: michael@0: [mPost addFileAtPath: NSSTR(gDumpFile) name: @"upload_file_minidump"]; michael@0: [mPost setParameters: parameters]; michael@0: [parameters release]; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: -(void)uploadComplete:(NSData*)data michael@0: { michael@0: NSHTTPURLResponse* response = [mPost response]; michael@0: [mPost release]; michael@0: michael@0: bool success; michael@0: string reply; michael@0: if (!data || !response || [response statusCode] != 200) { michael@0: success = false; michael@0: reply = ""; michael@0: michael@0: // if data is nil, we probably logged an error in uploadThread michael@0: if (data != nil && response != nil) { michael@0: ostringstream message; michael@0: message << "Crash report submission failed: server returned status " michael@0: << [response statusCode]; michael@0: LogMessage(message.str()); michael@0: } michael@0: } else { michael@0: success = true; michael@0: LogMessage("Crash report submitted successfully"); michael@0: michael@0: NSString* encodingName = [response textEncodingName]; michael@0: NSStringEncoding encoding; michael@0: if (encodingName) { michael@0: encoding = CFStringConvertEncodingToNSStringEncoding( michael@0: CFStringConvertIANACharSetNameToEncoding((CFStringRef)encodingName)); michael@0: } else { michael@0: encoding = NSISOLatin1StringEncoding; michael@0: } michael@0: NSString* r = [[NSString alloc] initWithData: data encoding: encoding]; michael@0: reply = [r UTF8String]; michael@0: [r release]; michael@0: } michael@0: michael@0: SendCompleted(success, reply); michael@0: michael@0: [mProgressIndicator stopAnimation:self]; michael@0: if (success) { michael@0: [self setStringFitVertically:mProgressText michael@0: string:Str(ST_REPORTSUBMITSUCCESS) michael@0: resizeWindow:YES]; michael@0: } else { michael@0: [self setStringFitVertically:mProgressText michael@0: string:Str(ST_SUBMITFAILED) michael@0: resizeWindow:YES]; michael@0: } michael@0: // quit after 5 seconds michael@0: [self performSelector:@selector(closeMeDown:) withObject:nil michael@0: afterDelay:5.0]; michael@0: } michael@0: michael@0: -(void)uploadThread:(HTTPMultipartUpload*)post michael@0: { michael@0: NSAutoreleasePool* autoreleasepool = [[NSAutoreleasePool alloc] init]; michael@0: NSError* error = nil; michael@0: NSData* data = [post send: &error]; michael@0: if (error) { michael@0: data = nil; michael@0: NSString* errorDesc = [error localizedDescription]; michael@0: string message = [errorDesc UTF8String]; michael@0: LogMessage("Crash report submission failed: " + message); michael@0: } michael@0: michael@0: [self performSelectorOnMainThread: @selector(uploadComplete:) michael@0: withObject: data michael@0: waitUntilDone: YES]; michael@0: michael@0: [autoreleasepool release]; michael@0: } michael@0: michael@0: // to get auto-quit when we close the window michael@0: -(BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)theApplication michael@0: { michael@0: return YES; michael@0: } michael@0: michael@0: -(void)applicationWillTerminate:(NSNotification *)aNotification michael@0: { michael@0: // since we use [NSApp terminate:] we never return to main, michael@0: // so do our cleanup here michael@0: if (!gDidTrySend) michael@0: DeleteDump(); michael@0: } michael@0: michael@0: @end michael@0: michael@0: @implementation TextViewWithPlaceHolder michael@0: michael@0: - (BOOL)becomeFirstResponder michael@0: { michael@0: [self setNeedsDisplay:YES]; michael@0: return [super becomeFirstResponder]; michael@0: } michael@0: michael@0: - (void)drawRect:(NSRect)rect michael@0: { michael@0: [super drawRect:rect]; michael@0: if (mPlaceHolderString && [[self string] isEqualToString:@""] && michael@0: self != [[self window] firstResponder]) michael@0: [mPlaceHolderString drawInRect:[self frame]]; michael@0: } michael@0: michael@0: - (BOOL)resignFirstResponder michael@0: { michael@0: [self setNeedsDisplay:YES]; michael@0: return [super resignFirstResponder]; michael@0: } michael@0: michael@0: - (void)setPlaceholder:(NSString*)placeholder michael@0: { michael@0: NSColor* txtColor = [NSColor disabledControlTextColor]; michael@0: NSDictionary* txtDict = [NSDictionary michael@0: dictionaryWithObjectsAndKeys:txtColor, michael@0: NSForegroundColorAttributeName, nil]; michael@0: mPlaceHolderString = [[NSMutableAttributedString alloc] michael@0: initWithString:placeholder attributes:txtDict]; michael@0: if (gRTLlayout) michael@0: [mPlaceHolderString setAlignment:NSRightTextAlignment michael@0: range:NSMakeRange(0, [placeholder length])]; michael@0: michael@0: } michael@0: michael@0: - (void)insertTab:(id)sender michael@0: { michael@0: // don't actually want to insert tabs, just tab to next control michael@0: [[self window] selectNextKeyView:sender]; michael@0: } michael@0: michael@0: - (void)insertBacktab:(id)sender michael@0: { michael@0: [[self window] selectPreviousKeyView:sender]; michael@0: } michael@0: michael@0: - (void)setEnabled:(BOOL)enabled michael@0: { michael@0: [self setSelectable:enabled]; michael@0: [self setEditable:enabled]; michael@0: if (![[self string] isEqualToString:@""]) { michael@0: NSAttributedString* colorString; michael@0: NSColor* txtColor; michael@0: if (enabled) michael@0: txtColor = [NSColor textColor]; michael@0: else michael@0: txtColor = [NSColor disabledControlTextColor]; michael@0: NSDictionary *txtDict = [NSDictionary michael@0: dictionaryWithObjectsAndKeys:txtColor, michael@0: NSForegroundColorAttributeName, nil]; michael@0: colorString = [[NSAttributedString alloc] michael@0: initWithString:[self string] michael@0: attributes:txtDict]; michael@0: [[self textStorage] setAttributedString: colorString]; michael@0: [self setInsertionPointColor:txtColor]; michael@0: [colorString release]; michael@0: } michael@0: } michael@0: michael@0: - (void)dealloc michael@0: { michael@0: [mPlaceHolderString release]; michael@0: [super dealloc]; michael@0: } michael@0: michael@0: @end michael@0: michael@0: /* === Crashreporter UI Functions === */ michael@0: michael@0: bool UIInit() michael@0: { michael@0: gMainPool = [[NSAutoreleasePool alloc] init]; michael@0: [NSApplication sharedApplication]; michael@0: michael@0: if (gStrings.find("isRTL") != gStrings.end() && michael@0: gStrings["isRTL"] == "yes") michael@0: gRTLlayout = true; michael@0: michael@0: [NSBundle loadNibNamed:(gRTLlayout ? @"MainMenuRTL" : @"MainMenu") michael@0: owner:NSApp]; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void UIShutdown() michael@0: { michael@0: [gMainPool release]; michael@0: } michael@0: michael@0: void UIShowDefaultUI() michael@0: { michael@0: [gUI showErrorUI: gStrings[ST_CRASHREPORTERDEFAULT]]; michael@0: [NSApp run]; 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: gRestartArgs = restartArgs; michael@0: michael@0: [gUI showCrashUI: dumpfile michael@0: queryParameters: queryParameters michael@0: sendURL: sendURL]; michael@0: [NSApp run]; michael@0: michael@0: return gDidTrySend; michael@0: } michael@0: michael@0: void UIError_impl(const string& message) michael@0: { michael@0: if (!gUI) { michael@0: // UI failed to initialize, printing is the best we can do michael@0: printf("Error: %s\n", message.c_str()); michael@0: return; michael@0: } michael@0: michael@0: [gUI showErrorUI: message]; michael@0: [NSApp run]; michael@0: } michael@0: michael@0: bool UIGetIniPath(string& path) michael@0: { michael@0: path = gArgv[0]; michael@0: path.append(".ini"); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool UIGetSettingsPath(const string& vendor, michael@0: const string& product, michael@0: string& settingsPath) michael@0: { michael@0: FSRef foundRef; michael@0: OSErr err = FSFindFolder(kUserDomain, kApplicationSupportFolderType, michael@0: kCreateFolder, &foundRef); michael@0: if (err != noErr) michael@0: return false; michael@0: michael@0: unsigned char path[PATH_MAX]; michael@0: FSRefMakePath(&foundRef, path, sizeof(path)); michael@0: NSString* destPath = [NSString stringWithUTF8String:reinterpret_cast(path)]; michael@0: michael@0: // Note that MacOS ignores the vendor when creating the profile hierarchy - michael@0: // all application preferences directories live alongside one another in michael@0: // ~/Library/Application Support/ michael@0: destPath = [destPath stringByAppendingPathComponent: NSSTR(product)]; michael@0: // Thunderbird stores its profile in ~/Library/Thunderbird, michael@0: // but we're going to put stuff in ~/Library/Application Support/Thunderbird michael@0: // anyway, so we have to ensure that path exists. michael@0: string tempPath = [destPath UTF8String]; michael@0: if (!UIEnsurePathExists(tempPath)) michael@0: return false; michael@0: michael@0: destPath = [destPath stringByAppendingPathComponent: @"Crash Reports"]; michael@0: michael@0: settingsPath = [destPath UTF8String]; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool UIEnsurePathExists(const string& path) michael@0: { michael@0: int ret = mkdir(path.c_str(), S_IRWXU); michael@0: int e = errno; michael@0: if (ret == -1 && e != EEXIST) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool UIFileExists(const string& path) michael@0: { michael@0: struct stat sb; michael@0: int ret = stat(path.c_str(), &sb); michael@0: if (ret == -1 || !(sb.st_mode & S_IFREG)) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool UIMoveFile(const string& file, const string& newfile) michael@0: { michael@0: if (!rename(file.c_str(), newfile.c_str())) michael@0: return true; michael@0: if (errno != EXDEV) michael@0: return false; michael@0: michael@0: NSFileManager *fileManager = [NSFileManager defaultManager]; michael@0: NSString *source = [fileManager stringWithFileSystemRepresentation:file.c_str() length:file.length()]; michael@0: NSString *dest = [fileManager stringWithFileSystemRepresentation:newfile.c_str() length:newfile.length()]; michael@0: if (!source || !dest) michael@0: return false; michael@0: michael@0: [fileManager moveItemAtPath:source toPath:dest error:NULL]; michael@0: return UIFileExists(newfile); michael@0: } michael@0: michael@0: bool UIDeleteFile(const string& file) michael@0: { michael@0: return (unlink(file.c_str()) != -1); michael@0: } michael@0: michael@0: std::ifstream* UIOpenRead(const string& filename) michael@0: { michael@0: return new std::ifstream(filename.c_str(), std::ios::in); michael@0: } michael@0: michael@0: std::ofstream* UIOpenWrite(const string& filename, bool append) // append=false michael@0: { michael@0: return new std::ofstream(filename.c_str(), michael@0: append ? std::ios::out | std::ios::app michael@0: : std::ios::out); michael@0: }