2
0

Cloud and initial window improvements.

[ADDED]     Advanced option to mark self as corrupt.
[ADDED]     Tell user initial cloud sync can take a moment.
[IMPROVED]  Initial window now visible on full-screen spaces.
[FIXED]     User name label.
This commit is contained in:
Maarten Billemont 2014-02-22 18:27:14 -05:00
parent 3fa9843855
commit 658d710847
17 changed files with 163 additions and 97 deletions

View File

@ -7,7 +7,7 @@
<key>CFBundleExecutable</key>
<string>Crashlytics</string>
<key>CFBundleIdentifier</key>
<string>com.crashlytics.sdk.mac</string>
<string>com.crashlytics.ios</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
@ -15,16 +15,16 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>2.1.2</string>
<string>2.1.6</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>macosx</string>
<string>iPhoneOS</string>
</array>
<key>CFBundleVersion</key>
<string>9</string>
<string>21</string>
<key>DTPlatformName</key>
<string>macosx</string>
<string>iphoneos</string>
<key>MinimumOSVersion</key>
<string>10.6</string>
<string>4.0</string>
</dict>
</plist>

2
External/Pearl vendored

@ -1 +1 @@
Subproject commit 4a3c68dda957adba490aae9b56df018e935d6c84
Subproject commit 081c2dec20b3638694a5ad20cd2fddccdb298447

@ -1 +1 @@
Subproject commit a5ee62e1cca559e27aba92ac65ffc4da44827dfe
Subproject commit ef9aa1c29ed6e1729be4d6a3dd65e2c8a289bf4c

View File

@ -127,7 +127,7 @@
return @"Device Private Password";
}
Throw(@"Type not supported: %lu", type);
Throw(@"Type not supported: %lu", (long)type);
}
- (NSString *)shortNameOfType:(MPElementType)type {
@ -161,7 +161,7 @@
return @"Device";
}
Throw(@"Type not supported: %lu", type);
Throw(@"Type not supported: %lu", (long)type);
}
- (NSString *)classNameOfType:(MPElementType)type {
@ -200,7 +200,7 @@
return [MPElementStoredEntity class];
}
Throw(@"Type not supported: %lu", type);
Throw(@"Type not supported: %lu", (long)type);
}
- (MPElementType)nextType:(MPElementType)type {
@ -227,7 +227,7 @@
return MPElementTypeStoredPersonal;
}
Throw(@"Type not supported: %lu", type);
Throw(@"Type not supported: %lu", (long)type);
}
- (MPElementType)previousType:(MPElementType)type {
@ -300,13 +300,13 @@
case MPElementTypeGeneratedBasic:
case MPElementTypeGeneratedShort:
case MPElementTypeGeneratedPIN: {
NSAssert(NO, @"Cannot save content to element with generated type %lu.", element.type);
NSAssert(NO, @"Cannot save content to element with generated type %lu.", (long)element.type);
break;
}
case MPElementTypeStoredPersonal: {
NSAssert([element isKindOfClass:[MPElementStoredEntity class]],
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", element.type, [element class]);
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type, [element class]);
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
encryptWithSymmetricKey:[elementKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
@ -315,7 +315,7 @@
}
case MPElementTypeStoredDevicePrivate: {
NSAssert([element isKindOfClass:[MPElementStoredEntity class]],
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", element.type, [element class]);
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type, [element class]);
NSData *encryptedContent = [[clearContent dataUsingEncoding:NSUTF8StringEncoding]
encryptWithSymmetricKey:[elementKey subKeyOfLength:PearlCryptKeySize].keyData padding:YES];
@ -360,7 +360,7 @@
case MPElementTypeGeneratedShort:
case MPElementTypeGeneratedPIN: {
NSAssert([element isKindOfClass:[MPElementGeneratedEntity class]],
@"Element with generated type %lu is not an MPElementGeneratedEntity, but a %@.", element.type, [element class]);
@"Element with generated type %lu is not an MPElementGeneratedEntity, but a %@.", (long)element.type, [element class]);
NSString *name = element.name;
MPElementType type = element.type;
@ -382,7 +382,7 @@
case MPElementTypeStoredPersonal: {
NSAssert([element isKindOfClass:[MPElementStoredEntity class]],
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", element.type, [element class]);
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type, [element class]);
NSData *encryptedContent = ((MPElementStoredEntity *)element).contentObject;
@ -394,7 +394,7 @@
}
case MPElementTypeStoredDevicePrivate: {
NSAssert([element isKindOfClass:[MPElementStoredEntity class]],
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", element.type, [element class]);
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type, [element class]);
NSDictionary *elementQuery = [self queryForDevicePrivateElementNamed:element.name];
NSData *encryptedContent = [PearlKeyChain dataOfItemForQuery:elementQuery];
@ -423,7 +423,7 @@
case MPElementTypeStoredPersonal: {
NSAssert([element isKindOfClass:[MPElementStoredEntity class]],
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", element.type, [element class]);
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type, [element class]);
if ([importKey.keyID isEqualToData:elementKey.keyID])
((MPElementStoredEntity *)element).contentObject = [protectedContent decodeBase64];
@ -481,7 +481,7 @@
case MPElementTypeStoredPersonal: {
NSAssert([element isKindOfClass:[MPElementStoredEntity class]],
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", element.type, [element class]);
@"Element with stored type %lu is not an MPElementStoredEntity, but a %@.", (long)element.type, [element class]);
result = [((MPElementStoredEntity *)element).contentObject encodeBase64];
break;
}

View File

@ -75,6 +75,8 @@ static NSDictionary *keyQuery(MPUserEntity *user) {
if (password)
NSAssert(![NSThread isMainThread], @"Computing key must not happen from the main thread.");
if (!user)
return NO;
MPKey *tryKey = nil;

View File

@ -701,7 +701,7 @@ PearlAssociatedObjectProperty(NSManagedObjectContext*, MainManagedObjectContext,
[export appendFormat:@"%@ %8ld %8s %20s\t%@\n",
[[NSDateFormatter rfc3339DateFormatter] stringFromDate:lastUsed], (long)uses,
[PearlString( @"%lu:%lu", type, (unsigned long)version ) UTF8String], [name UTF8String], content
[PearlString( @"%lu:%lu", (long)type, (unsigned long)version ) UTF8String], [name UTF8String], content
? content: @""];
}

View File

@ -54,7 +54,7 @@
if (!aType || aType == (MPElementType)NSNotFound)
aType = MPElementTypeGeneratedLong;
if (![self isKindOfClass:[self.algorithm classOfType:aType]])
Throw(@"This object's class does not support the type: %lu", aType);
Throw(@"This object's class does not support the type: %lu", (long)aType);
self.type_ = @(aType);
}
@ -128,7 +128,7 @@
- (NSString *)debugDescription {
return PearlString( @"{%@: name=%@, user=%@, type=%lu, uses=%ld, lastUsed=%@, version=%ld, loginName=%@, requiresExplicitMigration=%d}",
NSStringFromClass( [self class] ), self.name, self.user.name, self.type, (long)self.uses, self.lastUsed, (long)self.version,
NSStringFromClass( [self class] ), self.name, self.user.name, (long)self.type, (long)self.uses, self.lastUsed, (long)self.version,
self.loginName, self.requiresExplicitMigration );
}

View File

@ -16,6 +16,7 @@
// Copyright, lhunath (Maarten Billemont) 2014. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "MPElementCollectionView.h"
#import "MPElementModel.h"
#import "MPMacAppDelegate.h"
@ -36,17 +37,24 @@
if (!(self = [super initWithCoder:coder]))
return nil;
__weak MPElementCollectionView *wSelf = self;
[self addObserverBlock:^(NSString *keyPath, id object, NSDictionary *change, void *context) {
dispatch_async( dispatch_get_main_queue(), ^{
wSelf.counterHidden = !(MPElementTypeClassGenerated & wSelf.representedObject.type);
wSelf.updateContentHidden = !(MPElementTypeClassStored & wSelf.representedObject.type);
} );
} forKeyPath:@"representedObject" options:0 context:nil];
[self addObserver:self forKeyPath:@"representedObject" options:0 context:nil];
return self;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.counterHidden = !(MPElementTypeClassGenerated & self.representedObject.type);
self.updateContentHidden = !(MPElementTypeClassStored & self.representedObject.type);
}];
}
- (void)dealloc {
[self removeObserver:self forKeyPath:@"representedObject"];
}
- (IBAction)toggleType:(id)sender {
id<MPAlgorithm> algorithm = self.representedObject.algorithm;

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="4514" systemVersion="13B3116" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment defaultVersion="1080" identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="4514"/>
</dependencies>
<objects>

View File

@ -34,6 +34,7 @@
- (IBAction)newUser:(NSMenuItem *)sender;
- (IBAction)lock:(id)sender;
- (IBAction)rebuildCloud:(id)sender;
- (IBAction)corruptCloud:(id)sender;
- (IBAction)terminate:(id)sender;
- (IBAction)iphoneAppStore:(id)sender;

View File

@ -14,6 +14,12 @@
#define LOGIN_HELPER_BUNDLE_ID @"com.lyndir.lhunath.MasterPassword.Mac.LoginHelper"
@interface UbiquityStoreManager (Private)
- (void)markCloudStoreCorrupted;
@end
@interface MPMacAppDelegate()
@property(nonatomic, strong) NSWindowController *initialWindow;
@ -129,16 +135,23 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
- (IBAction)togglePreference:(id)sender {
if (sender == self.enableCloudButton)
[self storeManager].cloudEnabled = (self.enableCloudButton.state == NSOnState);
if (sender == self.enableCloudButton) {
if (([self storeManager].cloudEnabled = self.enableCloudButton.state == NSOnState)) {
NSAlert *alert = [NSAlert new];
alert.messageText = @"iCloud Enabled";
alert.informativeText = @"If you already have a user on another iCloud-enabled device, "
@"it may take a moment for that user to sync down to this device.";
[alert runModal];
}
}
if (sender == self.useCloudItem)
[self storeManager].cloudEnabled = !(self.useCloudItem.state == NSOnState);
[self storeManager].cloudEnabled = self.useCloudItem.state != NSOnState;
if (sender == self.rememberPasswordItem)
[MPConfig get].rememberLogin = [NSNumber numberWithBool:![[MPConfig get].rememberLogin boolValue]];
if (sender == self.openAtLoginButton)
[self setLoginItemEnabled:(self.openAtLoginButton.state == NSOnState)];
[self setLoginItemEnabled:self.openAtLoginButton.state == NSOnState];
if (sender == self.openAtLoginItem)
[self setLoginItemEnabled:!(self.openAtLoginItem.state == NSOnState)];
[self setLoginItemEnabled:self.openAtLoginItem.state != NSOnState];
if (sender == self.savePasswordItem) {
[MPMacAppDelegate managedObjectContextPerformBlockAndWait:^(NSManagedObjectContext *context) {
MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserInContext:context];
@ -194,14 +207,24 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
- (IBAction)rebuildCloud:(id)sender {
if ([[NSAlert alertWithMessageText:@"iCloud Truth Sync" defaultButton:@"Continue"
if ([[NSAlert alertWithMessageText:@"iCloud Truth Push" defaultButton:@"Continue"
alternateButton:nil otherButton:@"Cancel"
informativeTextWithFormat:@"This action will force all your iCloud enabled devices to revert to this device's version of the truth."
informativeTextWithFormat:@"This action will force all your iCloud enabled devices to switch to this device's version of the truth."
@"\n\nThis is only necessary if you notice that your devices aren't syncing properly anymore. "
"Any data on other devices not available from here will be lost."] runModal] == NSAlertDefaultReturn)
[self.storeManager rebuildCloudContentFromCloudStoreOrLocalStore:NO];
}
- (IBAction)corruptCloud:(id)sender {
if ([[NSAlert alertWithMessageText:@"iCloud Truth Pull" defaultButton:@"Continue"
alternateButton:nil otherButton:@"Cancel"
informativeTextWithFormat:@"This action will force another iCloud enabled device to push their version of the truth on all."
@"\n\nThis is only necessary if you notice that your devices aren't syncing properly anymore. "
"Any data on this device not available from the other will be lost."] runModal] == NSAlertDefaultReturn)
[self.storeManager markCloudStoreCorrupted];
}
- (IBAction)terminate:(id)sender {
[self.passwordWindow close];
@ -301,8 +324,11 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
// Initial display.
[NSApp activateIgnoringOtherApps:YES];
if ([[MPMacConfig get].firstRun boolValue])
[self.initialWindow = [[NSWindowController alloc] initWithWindowNibName:@"MPInitialWindow" owner:self] showWindow:self];
if ([[MPMacConfig get].firstRun boolValue]) {
self.initialWindow = [[NSWindowController alloc] initWithWindowNibName:@"MPInitialWindow" owner:self];
[self.initialWindow.window setLevel:NSFloatingWindowLevel];
[self.initialWindow showWindow:self];
}
}
- (void)setActiveUser:(MPUserEntity *)activeUser {

View File

@ -44,7 +44,6 @@
self.backgroundQueue = [NSOperationQueue new];
self.backgroundQueue.maxConcurrentOperationCount = 1;
[self.userLabel setStringValue:PearlString( @"%@'s password for:", [[MPMacAppDelegate get] activeUserForMainThread].name )];
[[MPMacAppDelegate get] addObserverBlock:^(NSString *keyPath, id object, NSDictionary *change, void *context) {
// [MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *moc) {
// if (![MPAlgorithmDefault migrateUser:[[MPMacAppDelegate get] activeUserInContext:moc]])
@ -54,9 +53,9 @@
// @"their passwords to change. You'll need to update your profile for that site with the new password."];
// [moc saveToStore];
// }];
dispatch_async( dispatch_get_main_queue(), ^{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self ensureLoadedAndUnlockedOrCloseIfLoggedOut:YES];
} );
}];
} forKeyPath:@"key" options:NSKeyValueObservingOptionInitial context:nil];
[[NSNotificationCenter defaultCenter] addObserverForName:NSWindowDidBecomeKeyNotification object:self.window
queue:[NSOperationQueue mainQueue] usingBlock:
@ -77,11 +76,18 @@
[[NSNotificationCenter defaultCenter] addObserverForName:MPSignedOutNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:
^(NSNotification *note) {
self.userLabel.stringValue = @"";
self.siteField.stringValue = @"";
self.elements = nil;
[self ensureLoadedAndUnlockedOrCloseIfLoggedOut:YES];
}];
[[NSNotificationCenter defaultCenter] addObserverForName:MPSignedInNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:
^(NSNotification *note) {
self.userLabel.stringValue = PearlString( @"%@'s password for:",
[[MPMacAppDelegate get] activeUserForMainThread].name );
}];
[[NSNotificationCenter defaultCenter] addObserverForName:USMStoreDidChangeNotification object:nil
queue:[NSOperationQueue mainQueue] usingBlock:
^(NSNotification *note) {
@ -107,37 +113,7 @@
}
if (contextInfo == MPAlertUnlockMP) {
switch (returnCode) {
case NSAlertAlternateReturn: {
// "Change" button.
NSInteger returnCode_ = [[NSAlert
alertWithMessageText:@"Changing Master Password" defaultButton:nil
alternateButton:[PearlStrings get].commonButtonCancel otherButton:nil informativeTextWithFormat:
@"This will allow you to log in with a different master password.\n\n"
@"Note that you will only see the sites and passwords for the master password you log in with.\n"
@"If you log in with a different master password, your current sites will be unavailable.\n\n"
@"You can always change back to your current master password later.\n"
@"Your current sites and passwords will then become available again."]
runModal];
if (returnCode_ == NSAlertDefaultReturn) {
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserInContext:context];
activeUser.keyID = nil;
[[MPMacAppDelegate get] forgetSavedKeyFor:activeUser];
[[MPMacAppDelegate get] signOutAnimated:YES];
[context saveToStore];
}];
}
break;
}
case NSAlertOtherReturn: {
// "Cancel" button.
[self close];
break;
}
case NSAlertDefaultReturn: {
case NSAlertFirstButtonReturn: {
// "Unlock" button.
self.contentContainer.alphaValue = 0;
self.inProgress = YES;
@ -150,7 +126,7 @@
usingMasterPassword:password];
self.inProgress = NO;
dispatch_async( dispatch_get_current_queue(), ^{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
if (success)
self.contentContainer.alphaValue = 1;
else {
@ -159,11 +135,41 @@
}]] beginSheetModalForWindow:self.window modalDelegate:self
didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertIncorrectMP];
}
} );
}];
}];
break;
}
case NSAlertSecondButtonReturn: {
// "Change" button.
NSAlert *alert_ = [NSAlert new];
[alert_ addButtonWithTitle:@"Update"];
[alert_ addButtonWithTitle:@"Cancel"];
[alert_ setMessageText:@"Changing Master Password"];
[alert_ setInformativeText:@"This will allow you to log in with a different master password.\n\n"
@"Note that you will only see the sites and passwords for the master password you log in with.\n"
@"If you log in with a different master password, your current sites will be unavailable.\n\n"
@"You can always change back to your current master password later.\n"
@"Your current sites and passwords will then become available again."];
if ([alert_ runModal] == NSAlertFirstButtonReturn) {
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPUserEntity *activeUser = [[MPMacAppDelegate get] activeUserInContext:context];
activeUser.keyID = nil;
[[MPMacAppDelegate get] forgetSavedKeyFor:activeUser];
[[MPMacAppDelegate get] signOutAnimated:YES];
[context saveToStore];
}];
}
break;
}
case NSAlertThirdButtonReturn: {
// "Cancel" button.
[self close];
break;
}
default:
break;
}
@ -304,16 +310,19 @@
[self.siteField setStringValue:@""];
NSAlert *alert = [NSAlert alertWithMessageText:@"Master Password is locked."
defaultButton:@"Unlock" alternateButton:@"Change" otherButton:@"Cancel"
informativeTextWithFormat:@"The master password is required to unlock the application for:\n\n%@",
userName];
NSAlert *alert = [NSAlert new];
[alert addButtonWithTitle:@"Unlock"];
[alert addButtonWithTitle:@"Change"];
[alert addButtonWithTitle:@"Cancel"];
[alert setMessageText:@"Master Password is locked."];
[alert setInformativeText:PearlString( @"The master password is required to unlock the application for:\n\n%@", userName )];
NSSecureTextField *passwordField = [[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )];
[alert setAccessoryView:passwordField];
[alert layout];
[passwordField becomeFirstResponder];
[alert beginSheetModalForWindow:self.window modalDelegate:self
didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertUnlockMP];
[passwordField becomeFirstResponder];
}];
}];
@ -413,9 +422,11 @@
- (void)createNewSite:(NSString *)siteName {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSAlert *alert = [NSAlert alertWithMessageText:@"Create site?"
defaultButton:@"Create" alternateButton:nil otherButton:@"Cancel"
informativeTextWithFormat:@"Do you want to create a new site named:\n\n%@", siteName];
NSAlert *alert = [NSAlert new];
[alert addButtonWithTitle:@"Create"];
[alert addButtonWithTitle:@"Cancel"];
[alert setMessageText:@"Create site?"];
[alert setInformativeText:PearlString( @"Do you want to create a new site named:\n\n%@", siteName )];
[alert beginSheetModalForWindow:self.window modalDelegate:self
didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) contextInfo:MPAlertCreateSite];
}];

View File

@ -10,8 +10,8 @@
<string>Master Password</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key>
<string>MasterPassword</string>
<key>CFBundleIconFile</key>
<string>MasterPassword</string>
<key>CFBundleIdentifier</key>
<string>com.lyndir.lhunath.MasterPassword.Mac</string>
<key>CFBundleInfoDictionaryVersion</key>

View File

@ -1982,7 +1982,7 @@
COMBINE_HIDPI_IMAGES = YES;
GCC_PREFIX_HEADER = "MasterPassword-Prefix.pch";
INFOPLIST_FILE = "MasterPassword-Info.plist";
PROVISIONING_PROFILE = "";
PROVISIONING_PROFILE = "2A46D0C9-E5F0-4C52-BCC6-96434A0D1C1B";
SKIP_INSTALL = NO;
WRAPPER_NAME = "Master Password.${WRAPPER_EXTENSION}";
};

View File

@ -140,13 +140,30 @@
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Advanced" id="777">
<items>
<menuItem title="iCloud Truth Sync" id="778">
<menuItem title="iCloud Truth Push" id="778">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="rebuildCloud:" target="494" id="780"/>
</connections>
</menuItem>
<menuItem title="Force this device's version of the truth upon all others." enabled="NO" id="779">
<menuItem title="Force our version of the truth upon all other devices." enabled="NO" id="779">
<attributedString key="attributedTitle">
<fragment content="Force this device's version of the truth upon all others.">
<attributes>
<font key="NSFont" size="12" name="Helvetica"/>
<paragraphStyle key="NSParagraphStyle" alignment="natural" lineBreakMode="wordWrapping" baseWritingDirection="natural"/>
</attributes>
</fragment>
</attributedString>
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem title="iCloud Truth Pull" id="cLQ-kc-cYN">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="corruptCloud:" target="494" id="asr-sb-Zkz"/>
</connections>
</menuItem>
<menuItem title="Mark ourselves as corrupt and pull the truth from another." enabled="NO" id="6NL-ki-Jff">
<attributedString key="attributedTitle">
<fragment content="Force this device's version of the truth upon all others.">
<attributes>
@ -164,7 +181,7 @@
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="718"/>
<menuItem title="Show" keyEquivalent="p" id="719">
<menuItem title="Open" keyEquivalent="p" id="719">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="showPasswordWindow:" target="494" id="782"/>

View File

@ -15,11 +15,11 @@
93D395F08A087F8A24689347 /* NSArray+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39067C0AFDC581794E2B8 /* NSArray+Indexing.m */; };
93D396AA30690B256F30378A /* PearlNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3956915634581E737B38C /* PearlNavigationController.m */; };
93D396BA1C74C4A06FD86437 /* PearlOverlay.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D3942A356B639724157982 /* PearlOverlay.h */; };
93D397952F5635C793C24DF1 /* NSError+MPFullDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39F9106F2CCFB94283188 /* NSError+MPFullDescription.m */; };
93D397952F5635C793C24DF1 /* NSError+PearlFullDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39F9106F2CCFB94283188 /* NSError+PearlFullDescription.m */; };
93D3992FA1546E01F498F665 /* PearlNavigationController.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D398567FD02DB2647B8CF3 /* PearlNavigationController.h */; };
93D399433EA75E50656040CB /* Twitter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93D394077F8FAB8167647187 /* Twitter.framework */; };
93D399BBC0A7EC746CB1B19B /* MPLogsViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D391943675426839501BB8 /* MPLogsViewController.h */; };
93D39B842AB9A5D072810D76 /* NSError+MPFullDescription.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D398C95847261903D781D3 /* NSError+MPFullDescription.h */; };
93D39B842AB9A5D072810D76 /* NSError+PearlFullDescription.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D398C95847261903D781D3 /* NSError+PearlFullDescription.h */; };
93D39C34FE35830EF5BE1D2A /* NSArray+Indexing.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D396D04E57792A54D437AC /* NSArray+Indexing.h */; };
93D39C8AD8EAB747856B3A8C /* LLModel.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3923B42DA2DA18F287092 /* LLModel.m */; };
93D39E281E3658B30550CB55 /* NSDictionary+Indexing.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */; };
@ -499,7 +499,7 @@
93D3979190DACEBD1F6AE9F4 /* MPLogsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPLogsViewController.m; sourceTree = "<group>"; };
93D3983278751A530262F64E /* LLConfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LLConfig.h; sourceTree = "<group>"; };
93D398567FD02DB2647B8CF3 /* PearlNavigationController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlNavigationController.h; sourceTree = "<group>"; };
93D398C95847261903D781D3 /* NSError+MPFullDescription.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSError+MPFullDescription.h"; sourceTree = "<group>"; };
93D398C95847261903D781D3 /* NSError+PearlFullDescription.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSError+PearlFullDescription.h"; sourceTree = "<group>"; };
93D39A28369954D147E239BA /* MPSetupViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSetupViewController.m; sourceTree = "<group>"; };
93D39A3CC4D8330831FC8CB4 /* LLToggleViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LLToggleViewController.h; sourceTree = "<group>"; };
93D39AA1EE2E1E7B81372240 /* NSDictionary+Indexing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+Indexing.m"; sourceTree = "<group>"; };
@ -507,7 +507,7 @@
93D39BF6BCBDFFE844E7D34C /* LLButtonView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LLButtonView.m; sourceTree = "<group>"; };
93D39C8E26B06F01566785B7 /* LLToggleViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LLToggleViewController.m; sourceTree = "<group>"; };
93D39F7C9F47BF6387FBC5C3 /* PearlEMail.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PearlEMail.h; sourceTree = "<group>"; };
93D39F9106F2CCFB94283188 /* NSError+MPFullDescription.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSError+MPFullDescription.m"; sourceTree = "<group>"; };
93D39F9106F2CCFB94283188 /* NSError+PearlFullDescription.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSError+PearlFullDescription.m"; sourceTree = "<group>"; };
DA04E33D14B1E70400ECA4F3 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; };
DA30E9CB15722ECA00A68B4C /* NSBundle+PearlMutableInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSBundle+PearlMutableInfo.h"; sourceTree = "<group>"; };
DA30E9CC15722ECA00A68B4C /* NSBundle+PearlMutableInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSBundle+PearlMutableInfo.m"; sourceTree = "<group>"; };
@ -2937,8 +2937,8 @@
DAFE45F515039823003ABA7C /* PearlStringUtils.m */,
DAFE45F815039823003ABA7C /* README */,
DAFE45F915039823003ABA7C /* Resources */,
93D39F9106F2CCFB94283188 /* NSError+MPFullDescription.m */,
93D398C95847261903D781D3 /* NSError+MPFullDescription.h */,
93D39F9106F2CCFB94283188 /* NSError+PearlFullDescription.m */,
93D398C95847261903D781D3 /* NSError+PearlFullDescription.h */,
);
path = Pearl;
sourceTree = "<group>";
@ -3202,7 +3202,7 @@
93D396BA1C74C4A06FD86437 /* PearlOverlay.h in Headers */,
DAEB937518AA537D000490CC /* ssl.h in Headers */,
93D3992FA1546E01F498F665 /* PearlNavigationController.h in Headers */,
93D39B842AB9A5D072810D76 /* NSError+MPFullDescription.h in Headers */,
93D39B842AB9A5D072810D76 /* NSError+PearlFullDescription.h in Headers */,
DAEB936218AA537D000490CC /* obj_mac.h in Headers */,
DAEB934218AA537D000490CC /* bn.h in Headers */,
);
@ -3854,7 +3854,7 @@
DA3509FF15F101A500C14A8E /* PearlQueue.m in Sources */,
93D3922A53E41A54832E90D9 /* PearlOverlay.m in Sources */,
93D396AA30690B256F30378A /* PearlNavigationController.m in Sources */,
93D397952F5635C793C24DF1 /* NSError+MPFullDescription.m in Sources */,
93D397952F5635C793C24DF1 /* NSError+PearlFullDescription.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};