mirror of
https://github.com/yuzu-emu/breakpad.git
synced 2024-12-01 06:24:22 +01:00
Port fixes from internal Google Breakpad to SVN.
A=preston, nealsid R=Stuart, Preston git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@360 4c0a9323-5329-0410-9bdc-e9ce6186880e
This commit is contained in:
parent
23c364a2b4
commit
22734848ea
@ -88,6 +88,7 @@
|
|||||||
F93DE33F0F82C66B00608B94 /* string_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53820ECCE635009BE4BA /* string_utilities.cc */; };
|
F93DE33F0F82C66B00608B94 /* string_utilities.cc in Sources */ = {isa = PBXBuildFile; fileRef = F92C53820ECCE635009BE4BA /* string_utilities.cc */; };
|
||||||
F93DE3410F82C68300608B94 /* exception_handler_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F93DE3400F82C68300608B94 /* exception_handler_test.cc */; };
|
F93DE3410F82C68300608B94 /* exception_handler_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F93DE3400F82C68300608B94 /* exception_handler_test.cc */; };
|
||||||
F945849E0F280E3C009A47BF /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = F945849C0F280E3C009A47BF /* Localizable.strings */; };
|
F945849E0F280E3C009A47BF /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = F945849C0F280E3C009A47BF /* Localizable.strings */; };
|
||||||
|
F9B630A0100FF96B00D0F4AC /* goArrow.png in Resources */ = {isa = PBXBuildFile; fileRef = F9B6309F100FF96B00D0F4AC /* goArrow.png */; };
|
||||||
F9C44DB20EF07288003AEBAA /* Controller.m in Sources */ = {isa = PBXBuildFile; fileRef = F9C44DAC0EF07288003AEBAA /* Controller.m */; };
|
F9C44DB20EF07288003AEBAA /* Controller.m in Sources */ = {isa = PBXBuildFile; fileRef = F9C44DAC0EF07288003AEBAA /* Controller.m */; };
|
||||||
F9C44DB30EF07288003AEBAA /* crashduringload in Resources */ = {isa = PBXBuildFile; fileRef = F9C44DAD0EF07288003AEBAA /* crashduringload */; };
|
F9C44DB30EF07288003AEBAA /* crashduringload in Resources */ = {isa = PBXBuildFile; fileRef = F9C44DAD0EF07288003AEBAA /* crashduringload */; };
|
||||||
F9C44DB40EF07288003AEBAA /* crashInMain in Resources */ = {isa = PBXBuildFile; fileRef = F9C44DAE0EF07288003AEBAA /* crashInMain */; };
|
F9C44DB40EF07288003AEBAA /* crashInMain in Resources */ = {isa = PBXBuildFile; fileRef = F9C44DAE0EF07288003AEBAA /* crashInMain */; };
|
||||||
@ -288,6 +289,7 @@
|
|||||||
F93DE3400F82C68300608B94 /* exception_handler_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = exception_handler_test.cc; path = handler/exception_handler_test.cc; sourceTree = "<group>"; };
|
F93DE3400F82C68300608B94 /* exception_handler_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = exception_handler_test.cc; path = handler/exception_handler_test.cc; sourceTree = "<group>"; };
|
||||||
F945849D0F280E3C009A47BF /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = sender/English.lproj/Localizable.strings; sourceTree = "<group>"; };
|
F945849D0F280E3C009A47BF /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = sender/English.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
F945859D0F78241E009A47BF /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Framework/Info.plist; sourceTree = "<group>"; };
|
F945859D0F78241E009A47BF /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Framework/Info.plist; sourceTree = "<group>"; };
|
||||||
|
F9B6309F100FF96B00D0F4AC /* goArrow.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = goArrow.png; path = sender/goArrow.png; sourceTree = "<group>"; };
|
||||||
F9C44DA50EF060A8003AEBAA /* BreakpadTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BreakpadTest.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
F9C44DA50EF060A8003AEBAA /* BreakpadTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BreakpadTest.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
F9C44DAC0EF07288003AEBAA /* Controller.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Controller.m; path = testapp/Controller.m; sourceTree = "<group>"; };
|
F9C44DAC0EF07288003AEBAA /* Controller.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Controller.m; path = testapp/Controller.m; sourceTree = "<group>"; };
|
||||||
F9C44DAD0EF07288003AEBAA /* crashduringload */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; name = crashduringload; path = testapp/crashduringload; sourceTree = "<group>"; };
|
F9C44DAD0EF07288003AEBAA /* crashduringload */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; name = crashduringload; path = testapp/crashduringload; sourceTree = "<group>"; };
|
||||||
@ -535,6 +537,7 @@
|
|||||||
F92C56A60ECE04B6009BE4BA /* sender */ = {
|
F92C56A60ECE04B6009BE4BA /* sender */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
F9B6309F100FF96B00D0F4AC /* goArrow.png */,
|
||||||
F92C56A70ECE04C5009BE4BA /* crash_report_sender.h */,
|
F92C56A70ECE04C5009BE4BA /* crash_report_sender.h */,
|
||||||
F92C56A80ECE04C5009BE4BA /* crash_report_sender.m */,
|
F92C56A80ECE04C5009BE4BA /* crash_report_sender.m */,
|
||||||
F945849C0F280E3C009A47BF /* Localizable.strings */,
|
F945849C0F280E3C009A47BF /* Localizable.strings */,
|
||||||
@ -813,6 +816,7 @@
|
|||||||
4084699D0F5D9CF900FDCA37 /* crash_report_sender.icns in Resources */,
|
4084699D0F5D9CF900FDCA37 /* crash_report_sender.icns in Resources */,
|
||||||
33880C800F9E097100817F82 /* InfoPlist.strings in Resources */,
|
33880C800F9E097100817F82 /* InfoPlist.strings in Resources */,
|
||||||
3329D4ED0FA16D820007BBC5 /* Breakpad.nib in Resources */,
|
3329D4ED0FA16D820007BBC5 /* Breakpad.nib in Resources */,
|
||||||
|
F9B630A0100FF96B00D0F4AC /* goArrow.png in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -89,6 +89,7 @@ extern "C" {
|
|||||||
#define BREAKPAD_PROCESS_CRASH_TIME "BreakpadProcessCrashTime"
|
#define BREAKPAD_PROCESS_CRASH_TIME "BreakpadProcessCrashTime"
|
||||||
#define BREAKPAD_LOGFILE_KEY_PREFIX "BreakpadAppLogFile"
|
#define BREAKPAD_LOGFILE_KEY_PREFIX "BreakpadAppLogFile"
|
||||||
#define BREAKPAD_SERVER_PARAMETER_PREFIX "BreakpadServerParameterPrefix_"
|
#define BREAKPAD_SERVER_PARAMETER_PREFIX "BreakpadServerParameterPrefix_"
|
||||||
|
#define BREAKPAD_ON_DEMAND "BreakpadOnDemand"
|
||||||
|
|
||||||
// Optional user-defined function to dec to decide if we should handle
|
// Optional user-defined function to dec to decide if we should handle
|
||||||
// this crash or forward it along.
|
// this crash or forward it along.
|
||||||
@ -215,7 +216,7 @@ typedef bool (*BreakpadFilterCallback)(int exception_type,
|
|||||||
//=============================================================================
|
//=============================================================================
|
||||||
// The following are NOT user-supplied but are documented here for
|
// The following are NOT user-supplied but are documented here for
|
||||||
// completeness. They are calculated by Breakpad during initialization &
|
// completeness. They are calculated by Breakpad during initialization &
|
||||||
// crash-dump generation.
|
// crash-dump generation, or entered in by the user.
|
||||||
//
|
//
|
||||||
// BREAKPAD_PROCESS_START_TIME The time the process started.
|
// BREAKPAD_PROCESS_START_TIME The time the process started.
|
||||||
//
|
//
|
||||||
@ -242,6 +243,12 @@ typedef bool (*BreakpadFilterCallback)(int exception_type,
|
|||||||
// server without leaking Breakpad's
|
// server without leaking Breakpad's
|
||||||
// internal values.
|
// internal values.
|
||||||
//
|
//
|
||||||
|
// BREAKPAD_ON_DEMAND Used internally to indicate to the
|
||||||
|
// Reporter that we're sending on-demand,
|
||||||
|
// not as result of a crash.
|
||||||
|
//
|
||||||
|
// BREAKPAD_COMMENTS The text the user provided as comments.
|
||||||
|
// Only used in crash_report_sender.
|
||||||
|
|
||||||
// Returns a new BreakpadRef object on success, NULL otherwise.
|
// Returns a new BreakpadRef object on success, NULL otherwise.
|
||||||
BreakpadRef BreakpadCreate(NSDictionary *parameters);
|
BreakpadRef BreakpadCreate(NSDictionary *parameters);
|
||||||
@ -286,7 +293,7 @@ void BreakpadRemoveKeyValue(BreakpadRef ref, NSString *key);
|
|||||||
// necessary. Note that as mentioned above there are limits on both
|
// necessary. Note that as mentioned above there are limits on both
|
||||||
// the number of keys and their length.
|
// the number of keys and their length.
|
||||||
void BreakpadAddUploadParameter(BreakpadRef ref, NSString *key,
|
void BreakpadAddUploadParameter(BreakpadRef ref, NSString *key,
|
||||||
NSString *value);
|
NSString *value);
|
||||||
|
|
||||||
// This method will remove a previously-added parameter from the
|
// This method will remove a previously-added parameter from the
|
||||||
// upload parameter set.
|
// upload parameter set.
|
||||||
|
@ -409,7 +409,6 @@ bool Breakpad::ExtractParameters(NSDictionary *parameters) {
|
|||||||
NSString *timeout = [parameters objectForKey:@BREAKPAD_CONFIRM_TIMEOUT];
|
NSString *timeout = [parameters objectForKey:@BREAKPAD_CONFIRM_TIMEOUT];
|
||||||
NSArray *logFilePaths = [parameters objectForKey:@BREAKPAD_LOGFILES];
|
NSArray *logFilePaths = [parameters objectForKey:@BREAKPAD_LOGFILES];
|
||||||
NSString *logFileTailSize = [parameters objectForKey:@BREAKPAD_LOGFILE_UPLOAD_SIZE];
|
NSString *logFileTailSize = [parameters objectForKey:@BREAKPAD_LOGFILE_UPLOAD_SIZE];
|
||||||
NSString *reportEmail = [parameters objectForKey:@BREAKPAD_EMAIL];
|
|
||||||
NSString *requestUserText =
|
NSString *requestUserText =
|
||||||
[parameters objectForKey:@BREAKPAD_REQUEST_COMMENTS];
|
[parameters objectForKey:@BREAKPAD_REQUEST_COMMENTS];
|
||||||
NSString *requestEmail = [parameters objectForKey:@BREAKPAD_REQUEST_EMAIL];
|
NSString *requestEmail = [parameters objectForKey:@BREAKPAD_REQUEST_EMAIL];
|
||||||
@ -451,7 +450,7 @@ bool Breakpad::ExtractParameters(NSDictionary *parameters) {
|
|||||||
vendor = @"Vendor not specified";
|
vendor = @"Vendor not specified";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize the values
|
// Normalize the values.
|
||||||
if (skipConfirm) {
|
if (skipConfirm) {
|
||||||
skipConfirm = [skipConfirm uppercaseString];
|
skipConfirm = [skipConfirm uppercaseString];
|
||||||
|
|
||||||
@ -504,7 +503,7 @@ bool Breakpad::ExtractParameters(NSDictionary *parameters) {
|
|||||||
[resourcePath stringByAppendingPathComponent:@"Inspector"];
|
[resourcePath stringByAppendingPathComponent:@"Inspector"];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that there is an Inspector tool
|
// Verify that there is an Inspector tool.
|
||||||
if (![[NSFileManager defaultManager] fileExistsAtPath:inspectorPathString]) {
|
if (![[NSFileManager defaultManager] fileExistsAtPath:inspectorPathString]) {
|
||||||
DEBUGLOG(stderr, "Cannot find Inspector tool\n");
|
DEBUGLOG(stderr, "Cannot find Inspector tool\n");
|
||||||
return false;
|
return false;
|
||||||
@ -517,7 +516,7 @@ bool Breakpad::ExtractParameters(NSDictionary *parameters) {
|
|||||||
reporterPathString = [[NSBundle bundleWithPath:reporterPathString] executablePath];
|
reporterPathString = [[NSBundle bundleWithPath:reporterPathString] executablePath];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that there is a Reporter application
|
// Verify that there is a Reporter application.
|
||||||
if (![[NSFileManager defaultManager]
|
if (![[NSFileManager defaultManager]
|
||||||
fileExistsAtPath:reporterPathString]) {
|
fileExistsAtPath:reporterPathString]) {
|
||||||
DEBUGLOG(stderr, "Cannot find Reporter tool\n");
|
DEBUGLOG(stderr, "Cannot find Reporter tool\n");
|
||||||
@ -588,11 +587,6 @@ bool Breakpad::ExtractParameters(NSDictionary *parameters) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reportEmail) {
|
|
||||||
dictionary.SetKeyValue(BREAKPAD_EMAIL,
|
|
||||||
[reportEmail UTF8String]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (serverParameters) {
|
if (serverParameters) {
|
||||||
// For each key-value pair, call BreakpadAddUploadParameter()
|
// For each key-value pair, call BreakpadAddUploadParameter()
|
||||||
NSEnumerator *keyEnumerator = [serverParameters keyEnumerator];
|
NSEnumerator *keyEnumerator = [serverParameters keyEnumerator];
|
||||||
@ -633,7 +627,9 @@ void Breakpad::RemoveKeyValue(NSString *key) {
|
|||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
void Breakpad::GenerateAndSendReport() {
|
void Breakpad::GenerateAndSendReport() {
|
||||||
|
config_params_->SetKeyValue(BREAKPAD_ON_DEMAND, "YES");
|
||||||
HandleException(0, 0, 0, mach_thread_self());
|
HandleException(0, 0, 0, mach_thread_self());
|
||||||
|
config_params_->SetKeyValue(BREAKPAD_ON_DEMAND, "NO");
|
||||||
}
|
}
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
|
@ -319,6 +319,12 @@ kern_return_t Inspector::ReadMessages() {
|
|||||||
printf("parameter count = %d\n", info.parameter_count);
|
printf("parameter count = %d\n", info.parameter_count);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// In certain situations where multiple crash requests come
|
||||||
|
// through quickly, we can end up with the mach IPC messages not
|
||||||
|
// coming through correctly. Since we don't know what parameters
|
||||||
|
// we've missed, we can't do much besides abort the crash dump
|
||||||
|
// situation in this case.
|
||||||
|
unsigned int parameters_read = 0;
|
||||||
// The initial message contains the number of key value pairs that
|
// The initial message contains the number of key value pairs that
|
||||||
// we are expected to read.
|
// we are expected to read.
|
||||||
// Read each key/value pair, one mach message per key/value pair.
|
// Read each key/value pair, one mach message per key/value pair.
|
||||||
@ -329,6 +335,14 @@ kern_return_t Inspector::ReadMessages() {
|
|||||||
if(result == KERN_SUCCESS) {
|
if(result == KERN_SUCCESS) {
|
||||||
KeyValueMessageData &key_value_data =
|
KeyValueMessageData &key_value_data =
|
||||||
(KeyValueMessageData&)*message.GetData();
|
(KeyValueMessageData&)*message.GetData();
|
||||||
|
// If we get a blank key, make sure we don't increment the
|
||||||
|
// parameter count; in some cases (notably on-demand generation
|
||||||
|
// many times in a short period of time) caused the Mach IPC
|
||||||
|
// messages to not come through correctly.
|
||||||
|
if (strlen(key_value_data.key) == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
parameters_read++;
|
||||||
|
|
||||||
config_params_.SetKeyValue(key_value_data.key, key_value_data.value);
|
config_params_.SetKeyValue(key_value_data.key, key_value_data.value);
|
||||||
} else {
|
} else {
|
||||||
@ -336,6 +350,11 @@ kern_return_t Inspector::ReadMessages() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (parameters_read != info.parameter_count) {
|
||||||
|
DEBUGLOG(stderr, "Only read %d parameters instead of %d, aborting crash "
|
||||||
|
"dump generation.", parameters_read, info.parameter_count);
|
||||||
|
return KERN_FAILURE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@ -379,7 +398,7 @@ bool Inspector::InspectTask() {
|
|||||||
SetCrashTimeParameters();
|
SetCrashTimeParameters();
|
||||||
// If the client app has not specified a minidump directory,
|
// If the client app has not specified a minidump directory,
|
||||||
// use a default of Library/<kDefaultLibrarySubdirectory>/<Product Name>
|
// use a default of Library/<kDefaultLibrarySubdirectory>/<Product Name>
|
||||||
if (0 == strlen(minidumpDirectory)) {
|
if (!minidumpDirectory || 0 == strlen(minidumpDirectory)) {
|
||||||
NSArray *libraryDirectories =
|
NSArray *libraryDirectories =
|
||||||
NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
|
NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
|
||||||
NSUserDomainMask,
|
NSUserDomainMask,
|
||||||
|
14
src/client/mac/sender/Breakpad.nib/classes.nib
generated
14
src/client/mac/sender/Breakpad.nib/classes.nib
generated
@ -4,6 +4,14 @@
|
|||||||
<dict>
|
<dict>
|
||||||
<key>IBClasses</key>
|
<key>IBClasses</key>
|
||||||
<array>
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CLASS</key>
|
||||||
|
<string>LengthLimitingTextField</string>
|
||||||
|
<key>LANGUAGE</key>
|
||||||
|
<string>ObjC</string>
|
||||||
|
<key>SUPERCLASS</key>
|
||||||
|
<string>NSTextField</string>
|
||||||
|
</dict>
|
||||||
<dict>
|
<dict>
|
||||||
<key>ACTIONS</key>
|
<key>ACTIONS</key>
|
||||||
<dict>
|
<dict>
|
||||||
@ -26,10 +34,14 @@
|
|||||||
<string>NSButton</string>
|
<string>NSButton</string>
|
||||||
<key>commentMessage_</key>
|
<key>commentMessage_</key>
|
||||||
<string>NSTextField</string>
|
<string>NSTextField</string>
|
||||||
|
<key>commentsEntryField_</key>
|
||||||
|
<string>LengthLimitingTextField</string>
|
||||||
|
<key>countdownLabel_</key>
|
||||||
|
<string>NSTextField</string>
|
||||||
<key>dialogTitle_</key>
|
<key>dialogTitle_</key>
|
||||||
<string>NSTextField</string>
|
<string>NSTextField</string>
|
||||||
<key>emailEntryField_</key>
|
<key>emailEntryField_</key>
|
||||||
<string>NSView</string>
|
<string>LengthLimitingTextField</string>
|
||||||
<key>emailLabel_</key>
|
<key>emailLabel_</key>
|
||||||
<string>NSTextField</string>
|
<string>NSTextField</string>
|
||||||
<key>emailMessage_</key>
|
<key>emailMessage_</key>
|
||||||
|
6
src/client/mac/sender/Breakpad.nib/info.nib
generated
6
src/client/mac/sender/Breakpad.nib/info.nib
generated
@ -3,17 +3,17 @@
|
|||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>IBFramework Version</key>
|
<key>IBFramework Version</key>
|
||||||
<string>672</string>
|
<string>676</string>
|
||||||
<key>IBLastKnownRelativeProjectPath</key>
|
<key>IBLastKnownRelativeProjectPath</key>
|
||||||
<string>../Breakpad.xcodeproj</string>
|
<string>../Breakpad.xcodeproj</string>
|
||||||
<key>IBOldestOS</key>
|
<key>IBOldestOS</key>
|
||||||
<integer>5</integer>
|
<integer>5</integer>
|
||||||
<key>IBOpenObjects</key>
|
<key>IBOpenObjects</key>
|
||||||
<array>
|
<array>
|
||||||
<integer>2</integer>
|
<integer>132</integer>
|
||||||
</array>
|
</array>
|
||||||
<key>IBSystem Version</key>
|
<key>IBSystem Version</key>
|
||||||
<string>9G55</string>
|
<string>9J61</string>
|
||||||
<key>targetFramework</key>
|
<key>targetFramework</key>
|
||||||
<string>IBCocoaFramework</string>
|
<string>IBCocoaFramework</string>
|
||||||
</dict>
|
</dict>
|
||||||
|
BIN
src/client/mac/sender/Breakpad.nib/keyedobjects.nib
generated
BIN
src/client/mac/sender/Breakpad.nib/keyedobjects.nib
generated
Binary file not shown.
Binary file not shown.
@ -41,6 +41,24 @@
|
|||||||
extern NSString *const kGoogleServerType;
|
extern NSString *const kGoogleServerType;
|
||||||
extern NSString *const kSocorroServerType;
|
extern NSString *const kSocorroServerType;
|
||||||
extern NSString *const kDefaultServerType;
|
extern NSString *const kDefaultServerType;
|
||||||
|
|
||||||
|
// We're sublcassing NSTextField in order to override a particular
|
||||||
|
// method (see the implementation) that lets us reject changes if they
|
||||||
|
// are longer than a particular length. Bindings would normally solve
|
||||||
|
// this problem, but when we implemented a validation method, and
|
||||||
|
// returned NO for strings that were too long, the UI was not updated
|
||||||
|
// right away, which was a poor user experience. The UI would be
|
||||||
|
// updated as soon as the text field lost first responder status,
|
||||||
|
// which isn't soon enough. It is a known bug that the UI KVO didn't
|
||||||
|
// work in the middle of a validation.
|
||||||
|
@interface LengthLimitingTextField : NSTextField {
|
||||||
|
@private
|
||||||
|
unsigned int maximumLength_;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) setMaximumLength:(unsigned int)maxLength;
|
||||||
|
@end
|
||||||
|
|
||||||
@interface Reporter : NSObject {
|
@interface Reporter : NSObject {
|
||||||
@public
|
@public
|
||||||
IBOutlet NSWindow *alertWindow_; // The alert window
|
IBOutlet NSWindow *alertWindow_; // The alert window
|
||||||
@ -50,26 +68,34 @@ extern NSString *const kDefaultServerType;
|
|||||||
IBOutlet NSBox *preEmailBox_;
|
IBOutlet NSBox *preEmailBox_;
|
||||||
IBOutlet NSBox *emailSectionBox_;
|
IBOutlet NSBox *emailSectionBox_;
|
||||||
// Localized elements (or things that need to be moved during localization).
|
// Localized elements (or things that need to be moved during localization).
|
||||||
IBOutlet NSTextField *dialogTitle_;
|
IBOutlet NSTextField *dialogTitle_;
|
||||||
IBOutlet NSTextField *commentMessage_;
|
IBOutlet NSTextField *commentMessage_;
|
||||||
IBOutlet NSTextField *emailMessage_;
|
IBOutlet NSTextField *emailMessage_;
|
||||||
IBOutlet NSTextField *emailLabel_;
|
IBOutlet NSTextField *emailLabel_;
|
||||||
IBOutlet NSTextField *privacyLinkLabel_;
|
IBOutlet NSTextField *privacyLinkLabel_;
|
||||||
IBOutlet NSButton *sendButton_;
|
IBOutlet NSButton *sendButton_;
|
||||||
IBOutlet NSButton *cancelButton_;
|
IBOutlet NSButton *cancelButton_;
|
||||||
IBOutlet NSView *emailEntryField_;
|
IBOutlet LengthLimitingTextField *emailEntryField_;
|
||||||
IBOutlet NSView *privacyLinkArrow_;
|
IBOutlet LengthLimitingTextField *commentsEntryField_;
|
||||||
|
IBOutlet NSTextField *countdownLabel_;
|
||||||
|
IBOutlet NSView *privacyLinkArrow_;
|
||||||
|
|
||||||
// Text field bindings, for user input.
|
// Text field bindings, for user input.
|
||||||
NSString *commentsValue_; // Comments from the user
|
NSString *commentsValue_; // Comments from the user
|
||||||
NSString *emailValue_; // Email from the user
|
NSString *emailValue_; // Email from the user
|
||||||
|
NSString *countdownMessage_; // Message indicating time
|
||||||
|
// left for input.
|
||||||
@private
|
@private
|
||||||
int configFile_; // File descriptor for config file
|
int configFile_; // File descriptor for config file
|
||||||
NSMutableDictionary *parameters_; // Key value pairs of data (STRONG)
|
NSMutableDictionary *parameters_; // Key value pairs of data (STRONG)
|
||||||
NSData *minidumpContents_; // The data in the minidump (STRONG)
|
NSData *minidumpContents_; // The data in the minidump (STRONG)
|
||||||
NSData *logFileData_; // An NSdata for the tar,
|
NSData *logFileData_; // An NSdata for the tar,
|
||||||
// bz2'd log file.
|
// bz2'd log file.
|
||||||
|
NSTimeInterval remainingDialogTime_; // Keeps track of how long
|
||||||
|
// we have until we cancel
|
||||||
|
// the dialog
|
||||||
|
NSTimer *messageTimer_; // Timer we use to update
|
||||||
|
// the dialog
|
||||||
NSMutableDictionary *serverDictionary_; // The dictionary mapping a
|
NSMutableDictionary *serverDictionary_; // The dictionary mapping a
|
||||||
// server type name to a
|
// server type name to a
|
||||||
// dictionary of server
|
// dictionary of server
|
||||||
@ -107,4 +133,7 @@ extern NSString *const kDefaultServerType;
|
|||||||
- (NSString *)emailValue;
|
- (NSString *)emailValue;
|
||||||
- (void)setEmailValue:(NSString *)value;
|
- (void)setEmailValue:(NSString *)value;
|
||||||
|
|
||||||
|
- (NSString *)countdownMessage;
|
||||||
|
- (void)setCountdownMessage:(NSString *)value;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -42,8 +42,11 @@
|
|||||||
|
|
||||||
#define kLastSubmission @"LastSubmission"
|
#define kLastSubmission @"LastSubmission"
|
||||||
const int kMinidumpFileLengthLimit = 800000;
|
const int kMinidumpFileLengthLimit = 800000;
|
||||||
|
const int kUserCommentsMaxLength = 1500;
|
||||||
|
const int kEmailMaxLength = 64;
|
||||||
|
|
||||||
#define kApplePrefsSyncExcludeAllKey @"com.apple.PreferenceSync.ExcludeAllSyncKeys"
|
#define kApplePrefsSyncExcludeAllKey \
|
||||||
|
@"com.apple.PreferenceSync.ExcludeAllSyncKeys"
|
||||||
|
|
||||||
NSString *const kGoogleServerType = @"google";
|
NSString *const kGoogleServerType = @"google";
|
||||||
NSString *const kSocorroServerType = @"socorro";
|
NSString *const kSocorroServerType = @"socorro";
|
||||||
@ -174,6 +177,9 @@ NSString *const kDefaultServerType = @"google";
|
|||||||
// Returns YES if we should send the report without asking the user first.
|
// Returns YES if we should send the report without asking the user first.
|
||||||
- (BOOL)shouldSubmitSilently;
|
- (BOOL)shouldSubmitSilently;
|
||||||
|
|
||||||
|
// Returns YES if the minidump was generated on demand.
|
||||||
|
- (BOOL)isOnDemand;
|
||||||
|
|
||||||
// Returns YES if we should ask the user to provide comments.
|
// Returns YES if we should ask the user to provide comments.
|
||||||
- (BOOL)shouldRequestComments;
|
- (BOOL)shouldRequestComments;
|
||||||
|
|
||||||
@ -187,11 +193,11 @@ NSString *const kDefaultServerType = @"google";
|
|||||||
|
|
||||||
// Returns the short description of the crash, suitable for use as a dialog
|
// Returns the short description of the crash, suitable for use as a dialog
|
||||||
// title (e.g., "The application Foo has quit unexpectedly").
|
// title (e.g., "The application Foo has quit unexpectedly").
|
||||||
- (NSString*)shortCrashDialogMessage;
|
- (NSString*)shortDialogMessage;
|
||||||
|
|
||||||
// Return explanatory text about the crash and the reporter, suitable for the
|
// Return explanatory text about the crash and the reporter, suitable for the
|
||||||
// body text of a dialog.
|
// body text of a dialog.
|
||||||
- (NSString*)explanatoryCrashDialogText;
|
- (NSString*)explanatoryDialogText;
|
||||||
|
|
||||||
// Returns the amount of time the UI should be shown before timing out.
|
// Returns the amount of time the UI should be shown before timing out.
|
||||||
- (NSTimeInterval)messageTimeout;
|
- (NSTimeInterval)messageTimeout;
|
||||||
@ -207,7 +213,7 @@ NSString *const kDefaultServerType = @"google";
|
|||||||
- (void)removeEmailPrompt;
|
- (void)removeEmailPrompt;
|
||||||
|
|
||||||
// Run an alert window with the given timeout. Returns
|
// Run an alert window with the given timeout. Returns
|
||||||
// NSAlertButtonDefault if the timeout is exceeded. A timeout of 0
|
// NSRunStoppedResponse if the timeout is exceeded. A timeout of 0
|
||||||
// queues the message immediately in the modal run loop.
|
// queues the message immediately in the modal run loop.
|
||||||
- (int)runModalWindow:(NSWindow*)window withTimeout:(NSTimeInterval)timeout;
|
- (int)runModalWindow:(NSWindow*)window withTimeout:(NSTimeInterval)timeout;
|
||||||
|
|
||||||
@ -235,6 +241,16 @@ NSString *const kDefaultServerType = @"google";
|
|||||||
// will be uploaded to the crash server.
|
// will be uploaded to the crash server.
|
||||||
- (void)addServerParameter:(id)value forKey:(NSString *)key;
|
- (void)addServerParameter:(id)value forKey:(NSString *)key;
|
||||||
|
|
||||||
|
// This method is used to periodically update the UI with how many
|
||||||
|
// seconds are left in the dialog display.
|
||||||
|
- (void)updateSecondsLeftInDialogDisplay:(NSTimer*)theTimer;
|
||||||
|
|
||||||
|
// When we receive this notification, it means that the user has
|
||||||
|
// begun editing the email address or comments field, and we disable
|
||||||
|
// the timers so that the user has as long as they want to type
|
||||||
|
// in their comments/email.
|
||||||
|
- (void)controlTextDidBeginEditing:(NSNotification *)aNotification;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation Reporter
|
@implementation Reporter
|
||||||
@ -261,6 +277,7 @@ NSString *const kDefaultServerType = @"google";
|
|||||||
- (id)initWithConfigurationFD:(int)fd {
|
- (id)initWithConfigurationFD:(int)fd {
|
||||||
if ((self = [super init])) {
|
if ((self = [super init])) {
|
||||||
configFile_ = fd;
|
configFile_ = fd;
|
||||||
|
remainingDialogTime_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Because the reporter is embedded in the framework (and many copies
|
// Because the reporter is embedded in the framework (and many copies
|
||||||
@ -545,8 +562,8 @@ NSString *const kDefaultServerType = @"google";
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Create an alert panel to tell the user something happened
|
// Create an alert panel to tell the user something happened
|
||||||
NSPanel* alert = NSGetAlertPanel([self shortCrashDialogMessage],
|
NSPanel* alert = NSGetAlertPanel([self shortDialogMessage],
|
||||||
[self explanatoryCrashDialogText],
|
[self explanatoryDialogText],
|
||||||
NSLocalizedString(@"sendReportButton", @""),
|
NSLocalizedString(@"sendReportButton", @""),
|
||||||
NSLocalizedString(@"cancelButton", @""),
|
NSLocalizedString(@"cancelButton", @""),
|
||||||
nil);
|
nil);
|
||||||
@ -566,11 +583,11 @@ NSString *const kDefaultServerType = @"google";
|
|||||||
// "fall" as text areas are shrunk from their overly-large IB sizes.
|
// "fall" as text areas are shrunk from their overly-large IB sizes.
|
||||||
|
|
||||||
// Localize the header. No resizing needed, as it has plenty of room.
|
// Localize the header. No resizing needed, as it has plenty of room.
|
||||||
[dialogTitle_ setStringValue:[self shortCrashDialogMessage]];
|
[dialogTitle_ setStringValue:[self shortDialogMessage]];
|
||||||
|
|
||||||
// Localize the explanatory text field.
|
// Localize the explanatory text field.
|
||||||
[commentMessage_ setStringValue:[NSString stringWithFormat:@"%@\n\n%@",
|
[commentMessage_ setStringValue:[NSString stringWithFormat:@"%@\n\n%@",
|
||||||
[self explanatoryCrashDialogText],
|
[self explanatoryDialogText],
|
||||||
NSLocalizedString(@"commentsMsg", @"")]];
|
NSLocalizedString(@"commentsMsg", @"")]];
|
||||||
float commentHeightDelta = [commentMessage_ breakpad_adjustHeightToFit];
|
float commentHeightDelta = [commentMessage_ breakpad_adjustHeightToFit];
|
||||||
[headerBox_ breakpad_shiftVertically:commentHeightDelta];
|
[headerBox_ breakpad_shiftVertically:commentHeightDelta];
|
||||||
@ -615,9 +632,13 @@ NSString *const kDefaultServerType = @"google";
|
|||||||
- (int)runModalWindow:(NSWindow*)window withTimeout:(NSTimeInterval)timeout {
|
- (int)runModalWindow:(NSWindow*)window withTimeout:(NSTimeInterval)timeout {
|
||||||
// Queue a |stopModal| message to be performed in |timeout| seconds.
|
// Queue a |stopModal| message to be performed in |timeout| seconds.
|
||||||
if (timeout > 0.001) {
|
if (timeout > 0.001) {
|
||||||
[NSApp performSelector:@selector(stopModal)
|
remainingDialogTime_ = timeout;
|
||||||
withObject:nil
|
SEL updateSelector = @selector(updateSecondsLeftInDialogDisplay:);
|
||||||
afterDelay:timeout];
|
messageTimer_ = [NSTimer scheduledTimerWithTimeInterval:1.0
|
||||||
|
target:self
|
||||||
|
selector:updateSelector
|
||||||
|
userInfo:nil
|
||||||
|
repeats:YES];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the window modally and wait for either a |stopModal| message or a
|
// Run the window modally and wait for either a |stopModal| message or a
|
||||||
@ -625,12 +646,6 @@ NSString *const kDefaultServerType = @"google";
|
|||||||
[NSApp activateIgnoringOtherApps:YES];
|
[NSApp activateIgnoringOtherApps:YES];
|
||||||
int returnMethod = [NSApp runModalForWindow:window];
|
int returnMethod = [NSApp runModalForWindow:window];
|
||||||
|
|
||||||
// Cancel the pending |stopModal| message.
|
|
||||||
if (returnMethod != NSRunStoppedResponse) {
|
|
||||||
[NSObject cancelPreviousPerformRequestsWithTarget:NSApp
|
|
||||||
selector:@selector(stopModal)
|
|
||||||
object:nil];
|
|
||||||
}
|
|
||||||
return returnMethod;
|
return returnMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -667,8 +682,10 @@ NSString *const kDefaultServerType = @"google";
|
|||||||
textView:(NSTextView*)textView
|
textView:(NSTextView*)textView
|
||||||
doCommandBySelector:(SEL)commandSelector {
|
doCommandBySelector:(SEL)commandSelector {
|
||||||
BOOL result = NO;
|
BOOL result = NO;
|
||||||
// If the user has entered text, don't end editing on "return"
|
// If the user has entered text on the comment field, don't end
|
||||||
if (commandSelector == @selector(insertNewline:)
|
// editing on "return".
|
||||||
|
if (control == commentsEntryField_ &&
|
||||||
|
commandSelector == @selector(insertNewline:)
|
||||||
&& [[textView string] length] > 0) {
|
&& [[textView string] length] > 0) {
|
||||||
[textView insertNewlineIgnoringFieldEditor:self];
|
[textView insertNewlineIgnoringFieldEditor:self];
|
||||||
result = YES;
|
result = YES;
|
||||||
@ -676,6 +693,50 @@ doCommandBySelector:(SEL)commandSelector {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)controlTextDidBeginEditing:(NSNotification *)aNotification {
|
||||||
|
[messageTimer_ invalidate];
|
||||||
|
[self setCountdownMessage:@""];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)updateSecondsLeftInDialogDisplay:(NSTimer*)theTimer {
|
||||||
|
remainingDialogTime_ -= 1;
|
||||||
|
|
||||||
|
NSString *countdownMessage;
|
||||||
|
NSString *formatString;
|
||||||
|
|
||||||
|
int displayedTimeLeft; // This can be either minutes or seconds.
|
||||||
|
|
||||||
|
if (remainingDialogTime_ > 59) {
|
||||||
|
// calculate minutes remaining for UI purposes
|
||||||
|
displayedTimeLeft = (remainingDialogTime_ / 60);
|
||||||
|
|
||||||
|
if (displayedTimeLeft == 1) {
|
||||||
|
formatString = NSLocalizedString(@"countdownMsgMinuteSingular", @"");
|
||||||
|
} else {
|
||||||
|
formatString = NSLocalizedString(@"countdownMsgMinutesPlural", @"");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
displayedTimeLeft = remainingDialogTime_;
|
||||||
|
if (remainingDialogTime_ == 1) {
|
||||||
|
formatString = NSLocalizedString(@"countdownMsgSecondSingular", @"");
|
||||||
|
} else {
|
||||||
|
formatString = NSLocalizedString(@"countdownMsgSecondsPlural", @"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
countdownMessage = [NSString stringWithFormat:formatString,
|
||||||
|
displayedTimeLeft];
|
||||||
|
if (remainingDialogTime_ <= 30) {
|
||||||
|
[countdownLabel_ setTextColor:[NSColor redColor]];
|
||||||
|
}
|
||||||
|
[self setCountdownMessage:countdownMessage];
|
||||||
|
if (remainingDialogTime_ <= 0) {
|
||||||
|
[messageTimer_ invalidate];
|
||||||
|
[NSApp stopModal];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#pragma mark Accessors
|
#pragma mark Accessors
|
||||||
#pragma mark -
|
#pragma mark -
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
@ -702,6 +763,17 @@ doCommandBySelector:(SEL)commandSelector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (NSString *)countdownMessage {
|
||||||
|
return [[countdownMessage_ retain] autorelease];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setCountdownMessage:(NSString *)value {
|
||||||
|
if (countdownMessage_ != value) {
|
||||||
|
[countdownMessage_ release];
|
||||||
|
countdownMessage_ = [value copy];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark -
|
#pragma mark -
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
- (BOOL)reportIntervalElapsed {
|
- (BOOL)reportIntervalElapsed {
|
||||||
@ -730,6 +802,11 @@ doCommandBySelector:(SEL)commandSelector {
|
|||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (BOOL)isOnDemand {
|
||||||
|
return [[parameters_ objectForKey:@BREAKPAD_ON_DEMAND]
|
||||||
|
isEqualToString:@"YES"];
|
||||||
|
}
|
||||||
|
|
||||||
- (BOOL)shouldSubmitSilently {
|
- (BOOL)shouldSubmitSilently {
|
||||||
return [[parameters_ objectForKey:@BREAKPAD_SKIP_CONFIRM]
|
return [[parameters_ objectForKey:@BREAKPAD_SKIP_CONFIRM]
|
||||||
isEqualToString:@"YES"];
|
isEqualToString:@"YES"];
|
||||||
@ -745,21 +822,40 @@ doCommandBySelector:(SEL)commandSelector {
|
|||||||
isEqualToString:@"YES"];
|
isEqualToString:@"YES"];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString*)shortCrashDialogMessage {
|
- (NSString*)shortDialogMessage {
|
||||||
NSString *displayName = [parameters_ objectForKey:@BREAKPAD_PRODUCT_DISPLAY];
|
NSString *displayName = [parameters_ objectForKey:@BREAKPAD_PRODUCT_DISPLAY];
|
||||||
if (![displayName length])
|
if (![displayName length])
|
||||||
displayName = [parameters_ objectForKey:@BREAKPAD_PRODUCT];
|
displayName = [parameters_ objectForKey:@BREAKPAD_PRODUCT];
|
||||||
|
|
||||||
return [NSString stringWithFormat:NSLocalizedString(@"headerFmt", @""),
|
if ([self isOnDemand]) {
|
||||||
displayName];
|
return [NSString
|
||||||
|
stringWithFormat:NSLocalizedString(@"noCrashDialogHeader", @""),
|
||||||
|
displayName];
|
||||||
|
} else {
|
||||||
|
return [NSString
|
||||||
|
stringWithFormat:NSLocalizedString(@"crashDialogHeader", @""),
|
||||||
|
displayName];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSString*)explanatoryCrashDialogText {
|
- (NSString*)explanatoryDialogText {
|
||||||
|
NSString *displayName = [parameters_ objectForKey:@BREAKPAD_PRODUCT_DISPLAY];
|
||||||
|
if (![displayName length])
|
||||||
|
displayName = [parameters_ objectForKey:@BREAKPAD_PRODUCT];
|
||||||
|
|
||||||
NSString *vendor = [parameters_ objectForKey:@BREAKPAD_VENDOR];
|
NSString *vendor = [parameters_ objectForKey:@BREAKPAD_VENDOR];
|
||||||
if (![vendor length])
|
if (![vendor length])
|
||||||
vendor = @"unknown vendor";
|
vendor = @"unknown vendor";
|
||||||
|
|
||||||
return [NSString stringWithFormat:NSLocalizedString(@"msgFmt", @""), vendor];
|
if ([self isOnDemand]) {
|
||||||
|
return [NSString
|
||||||
|
stringWithFormat:NSLocalizedString(@"noCrashDialogMsg", @""),
|
||||||
|
vendor, displayName];
|
||||||
|
} else {
|
||||||
|
return [NSString
|
||||||
|
stringWithFormat:NSLocalizedString(@"crashDialogMsg", @""),
|
||||||
|
vendor];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSTimeInterval)messageTimeout {
|
- (NSTimeInterval)messageTimeout {
|
||||||
@ -940,6 +1036,77 @@ doCommandBySelector:(SEL)commandSelector {
|
|||||||
[super dealloc];
|
[super dealloc];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)awakeFromNib {
|
||||||
|
[emailEntryField_ setMaximumLength:kEmailMaxLength];
|
||||||
|
[commentsEntryField_ setMaximumLength:kUserCommentsMaxLength];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
@implementation LengthLimitingTextField
|
||||||
|
|
||||||
|
- (void) setMaximumLength:(unsigned int)maxLength {
|
||||||
|
maximumLength_ = maxLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the method we're overriding in NSTextField, which lets us
|
||||||
|
// limit the user's input if it makes the string too long.
|
||||||
|
- (BOOL) textView:(NSTextView *)textView
|
||||||
|
shouldChangeTextInRange:(NSRange)affectedCharRange
|
||||||
|
replacementString:(NSString *)replacementString {
|
||||||
|
|
||||||
|
// Sometimes the range comes in invalid, so reject if we can't
|
||||||
|
// figure out if the replacement text is too long.
|
||||||
|
if (affectedCharRange.location == NSNotFound) {
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
// Figure out what the new string length would be, taking into
|
||||||
|
// account user selections.
|
||||||
|
int newStringLength =
|
||||||
|
[[textView string] length] - affectedCharRange.length +
|
||||||
|
[replacementString length];
|
||||||
|
if (newStringLength > maximumLength_) {
|
||||||
|
return NO;
|
||||||
|
} else {
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cut, copy, and paste have to be caught specifically since there is no menu.
|
||||||
|
- (BOOL)performKeyEquivalent:(NSEvent*)event {
|
||||||
|
// Only handle the key equivalent if |self| is the text field with focus.
|
||||||
|
NSText* fieldEditor = [self currentEditor];
|
||||||
|
if (fieldEditor != nil) {
|
||||||
|
// Check for a single "Command" modifier
|
||||||
|
unsigned int modifiers = [event modifierFlags];
|
||||||
|
modifiers &= NSDeviceIndependentModifierFlagsMask;
|
||||||
|
if (modifiers == NSCommandKeyMask) {
|
||||||
|
// Now, check for Select All, Cut, Copy, or Paste key equivalents.
|
||||||
|
NSString* characters = [event characters];
|
||||||
|
// Select All is Command-A.
|
||||||
|
if ([characters isEqualToString:@"a"]) {
|
||||||
|
[fieldEditor selectAll:self];
|
||||||
|
return YES;
|
||||||
|
// Cut is Command-X.
|
||||||
|
} else if ([characters isEqualToString:@"x"]) {
|
||||||
|
[fieldEditor cut:self];
|
||||||
|
return YES;
|
||||||
|
// Copy is Command-C.
|
||||||
|
} else if ([characters isEqualToString:@"c"]) {
|
||||||
|
[fieldEditor copy:self];
|
||||||
|
return YES;
|
||||||
|
// Paste is Command-V.
|
||||||
|
} else if ([characters isEqualToString:@"v"]) {
|
||||||
|
[fieldEditor paste:self];
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Let the super class handle the rest (e.g. Command-Period will cancel).
|
||||||
|
return [super performKeyEquivalent:event];
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
|
BIN
src/client/mac/sender/goArrow.png
Normal file
BIN
src/client/mac/sender/goArrow.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
@ -209,8 +209,9 @@ const mach_port_t kNoLastExceptionThread = MACH_PORT_NULL;
|
|||||||
@"Last exception type is not 0 for on demand");
|
@"Last exception type is not 0 for on demand");
|
||||||
STAssertEquals(last_exception_code_, 0,
|
STAssertEquals(last_exception_code_, 0,
|
||||||
@"Last exception code is not 0 for on demand");
|
@"Last exception code is not 0 for on demand");
|
||||||
STAssertEquals(last_exception_thread_, (mach_port_t)0,
|
STAssertEquals(last_exception_thread_, mach_thread_self(),
|
||||||
@"Last exception thread is not 0 for on demand");
|
@"Last exception thread is not mach_thread_self() "
|
||||||
|
"for on demand");
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
Loading…
Reference in New Issue
Block a user