From aea2ce3b40677c4fcabea14fdc40352b650f458a Mon Sep 17 00:00:00 2001 From: Patrick Wardle Date: Sun, 23 Aug 2015 21:46:34 -1000 Subject: [PATCH] v1.1.0 commit finalized a global search window (tasks, dylibs, files) finalized a flagged items window for any flagged tasks or dylibs finalized XPC-helper's security improved by allowing only Objective-See binary to connect several UI fixes/improvements --- AppDelegate.h | 14 +- AppDelegate.m | 95 +++--- Consts.h | 6 +- Exception.m | 2 - FlaggedItems.m | 52 +++- ItemView.h | 2 +- ItemView.m | 47 ++- Items/File.m | 2 +- NSApplicationKeyEvents.h | 2 +- RequestRootWindowController.m | 3 +- SearchWindowController.h | 20 +- SearchWindowController.m | 277 ++++++++---------- Task.h | 2 +- Task.m | 92 +++--- TaskEnumerator.h | 4 - TaskEnumerator.m | 11 +- TaskExplorer-Info.plist | 4 +- .../UserInterfaceState.xcuserstate | Bin 33017 -> 42522 bytes .../xcdebugger/Breakpoints_v2.xcbkptlist | 126 ++++++-- .../xcschemes/TaskExplorer.xcscheme | 20 ++ TaskTableController.m | 2 + UI/AboutWindow.xib | 2 +- UI/FlaggedItems.xib | 2 +- UI/SearchWindow.xib | 19 +- Utilities.m | 2 +- VTInfoWindowController.m | 20 -- VirusTotal.m | 16 + en.lproj/MainMenu.xib | 54 ++-- remoteTaskService/Info.plist | 6 +- remoteTaskService/main.m | 8 +- remoteTaskService/remoteTaskService.h | 2 +- serviceInterface.h | 2 +- 32 files changed, 516 insertions(+), 400 deletions(-) diff --git a/AppDelegate.h b/AppDelegate.h index 9583022..35f5d91 100755 --- a/AppDelegate.h +++ b/AppDelegate.h @@ -32,6 +32,9 @@ } +//start time +@property NSTimeInterval startTime; + //connection flag @property BOOL isConnected; @@ -80,7 +83,7 @@ @property (weak) IBOutlet NSButton *logoButton; -@property (weak) IBOutlet NSButton *showPreferencesButton; +//@property (weak) IBOutlet NSButton *showPreferencesButton; //spinner @property (weak) IBOutlet NSProgressIndicator *progressIndicator; @@ -127,11 +130,14 @@ //'no items' found label for bottom pane @property (weak) IBOutlet NSTextField *noItemsLabel; +//refresh button +@property (weak) IBOutlet NSButton *refreshButton; + //search button @property (weak) IBOutlet NSButton *searchButton; -//refresh button -@property (weak) IBOutlet NSButton *refreshButton; +//save button +@property (weak) IBOutlet NSButton *saveButton; //flagged items button @property (weak) IBOutlet NSButton *flaggedButton; @@ -182,7 +188,7 @@ //-(void)applyPreferences; //button handler for when settings icon (gear) is clicked --(IBAction)showPreferences:(id)sender; +//-(IBAction)showPreferences:(id)sender; //button handler for logo -(IBAction)logoButtonHandler:(id)sender; diff --git a/AppDelegate.m b/AppDelegate.m index 96d5156..daefc65 100755 --- a/AppDelegate.m +++ b/AppDelegate.m @@ -16,13 +16,9 @@ #import "Task.h" -//TODO: path truncated in 'info' window (1password mini) - but weird when selected :/ -// resize text manually? http://stackoverflow.com/questions/6519995/modifying-an-nstextfields-font-size-according-to-content-length - //TODO: filter out dup'd networks (airportd 0:0..) -not sure want to do this -//TODO: 'flagged' items button? //TODO: add 'am i on main thread' guard and test -//TODO: filter dylibs, no first responder! + //TODO: autolayout vertically //TODO: filter VT results @@ -31,24 +27,24 @@ // see: https://mail.google.com/mail/u/0/#inbox/14eeb163d4dd2852 //TODO: show 'from where' via quarantine attrz //TODO: show user (after pid): -> (pid, user)? -//TODO: when filtering, and then refresh, doesn't go to row #0 :/ //TODO: check if VT can be reached! if not, error? or don't show '0 VT results detected' etc... //TODO: JavaW (iWorm) dylibs... + //TODO: remove task, remove from taskEnum's global list for executables, and dylibs, etc //TODO: also refresh!.... @implementation AppDelegate - @synthesize filterObj; +@synthesize startTime; @synthesize vtThreads; +@synthesize saveButton; @synthesize isConnected; @synthesize virusTotalObj; @synthesize taskTableController; @synthesize aboutWindowController; @synthesize prefsWindowController; -@synthesize showPreferencesButton; @synthesize resultsWindowController; @synthesize bottomPane; @synthesize bottomViewController; @@ -89,10 +85,9 @@ // ->main entry point -(void)applicationDidFinishLaunching:(NSNotification *)notification { - //TODO: re-enable for release! //first thing... // ->install exception handlers! - //installExceptionHandlers(); + installExceptionHandlers(); //init virus total object virusTotalObj = [[VirusTotal alloc] init]; @@ -103,6 +98,12 @@ //alloc flagged items flaggedItems = [NSMutableArray array]; + //set start time + self.startTime = [NSDate timeIntervalSinceReferenceDate]; + + //center + [self.window center]; + //no need to have a first responder [self.window makeFirstResponder:nil]; @@ -328,9 +329,8 @@ bail: // ->waits until window is non-nil dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - //TODO: re-enable //make modal - //makeModal(self.requestRootWindowController); + makeModal(self.requestRootWindowController); }); @@ -748,14 +748,6 @@ bail: //add tracking area to pref button [self.refreshButton addTrackingArea:trackingArea]; - - - //init tracking area - // ->for preference button - trackingArea = [[NSTrackingArea alloc] initWithRect:[self.showPreferencesButton bounds] options:(NSTrackingInVisibleRect|NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways) owner:self userInfo:@{@"tag":[NSNumber numberWithUnsignedInteger:self.showPreferencesButton.tag]}]; - - //add tracking area to pref button - [self.showPreferencesButton addTrackingArea:trackingArea]; //init tracking area // ->for search button @@ -764,6 +756,13 @@ bail: //add tracking area to search button [self.searchButton addTrackingArea:trackingArea]; + //init tracking area + // ->for save button + trackingArea = [[NSTrackingArea alloc] initWithRect:[self.saveButton bounds] options:(NSTrackingInVisibleRect|NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways) owner:self userInfo:@{@"tag":[NSNumber numberWithUnsignedInteger:self.saveButton.tag]}]; + + //add tracking area to search button + [self.saveButton addTrackingArea:trackingArea]; + //init tracking area // ->for logo button trackingArea = [[NSTrackingArea alloc] initWithRect:[self.logoButton bounds] options:(NSTrackingInVisibleRect|NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways) owner:self userInfo:@{@"tag":[NSNumber numberWithUnsignedInteger:self.logoButton.tag]}]; @@ -781,26 +780,6 @@ bail: return; } - -//TODO: don't need, and scannerThread can be removed!! and progressIndicator too! -//automatically invoked when window is un-minimized -// since the progress indicator is stopped (bug?), restart it --(void)windowDidDeminiaturize:(NSNotification *)notification -{ - //make sure scan is going on - // ->and then restart spinner - if(YES == [self.scannerThread isExecuting]) - { - //show - [self.progressIndicator setHidden:NO]; - - //start spinner - [self.progressIndicator startAnimation:nil]; - } - - return; -} - //automatically invoked when user clicks logo // ->load objective-see's html page -(IBAction)logoButtonHandler:(id)sender @@ -1145,7 +1124,6 @@ bail: bail: return; - } //automatically invoked when user clicks 'search' button @@ -1165,17 +1143,6 @@ bail: //show it [self.searchWindowController showWindow:self]; - /* - //invoke function in background that will make window modal - // ->waits until window is non-nil - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - - //make modal - //makeModal(self.searchWindowController); - - }); - */ - return; } @@ -1201,6 +1168,7 @@ bail: return; } +/* //automatically invoked when user clicks gear icon // ->show preferences @@ -1227,7 +1195,10 @@ bail: return; } + +*/ +/* //automatically invoked when menu is clicked // ->tell menu to disable 'Preferences' when scan is running -(BOOL)validateMenuItem:(NSMenuItem *)item @@ -1238,19 +1209,20 @@ bail: //check if item is 'Preferences' if(PREF_MENU_ITEM_TAG == item.tag) { - /* //unset enabled flag if scan is running if(YES != [[self.scanButtonLabel stringValue] isEqualToString:START_SCAN]) { //disable bEnabled = NO; } - */ + } return bEnabled; } +*/ + //automatically invoked when user clicks on Flat/Tree view // ->invoke helper function to change view @@ -1641,6 +1613,10 @@ bail: //scroll to top [self.taskTableController scrollToTop]; + //reload + // ->ensure that top row/task is correctly selected + //[self.taskTableController.itemView reloadData]; + //select top row [self.taskTableController.itemView selectRowIndexes:[NSIndexSet indexSetWithIndex:0] byExtendingSelection:NO]; @@ -1714,6 +1690,14 @@ bail: // ->also set text flagged items button label to red -(void)saveFlaggedBinary:(Binary*)binary { + //first check if item is already flagged + if(YES == [self.flaggedItems containsObject:binary]) + { + //no need to add + // ->so bail + goto bail; + } + //sync to save @synchronized(self.flaggedItems) { @@ -1729,6 +1713,9 @@ bail: self.flaggedLabel.textColor = [NSColor redColor]; } +//bail +bail: + return; } diff --git a/Consts.h b/Consts.h index 99b8e07..307b1e8 100644 --- a/Consts.h +++ b/Consts.h @@ -305,10 +305,14 @@ //sort by name #define SORT_BY_NAME 0x1 - //delta for pid tag #define PID_TAG_DELTA 1000 +//search wait time (from app's launch) +#define SEARCH_WAIT_TIME 30 + +//pls wait (search) message +#define PLS_WAIT_MESSAGE @"completing (intial) task/dylib/file enumeration please wait" diff --git a/Exception.m b/Exception.m index c34e107..d719de2 100755 --- a/Exception.m +++ b/Exception.m @@ -61,8 +61,6 @@ void showAlert() return; } - - //exception handler // will be invoked for Obj-C exceptions void exceptionHandler(NSException *exception) diff --git a/FlaggedItems.m b/FlaggedItems.m index 1c02455..bc0bf8e 100644 --- a/FlaggedItems.m +++ b/FlaggedItems.m @@ -25,14 +25,14 @@ @synthesize infoWindowController; //automatically called when nib is loaded -// ->center window +// ->first time (when outlets aren't nil), init UI -(void)awakeFromNib { //single time init if(YES != self.didInit) { - //center - [self.window center]; + //init UI + [self initUI]; //set flag self.didInit = YES; @@ -51,9 +51,17 @@ //init array for flagged items flaggedItems = [NSMutableArray array]; - //table reload w/ blank array - // ->make sure all is reset - [self.flaggedItemTable reloadData]; + //first time outlets are nil + // ->thus 'initUI' method called in 'awakeFromNib' + if(nil != self.window) + { + //can init UI + // ->center window, etc + [self initUI]; + + //set flag + self.didInit = YES; + } //populate flagged items array for(Binary* flaggedItem in ((AppDelegate*)[[NSApplication sharedApplication] delegate]).flaggedItems) @@ -65,16 +73,23 @@ //get all tasks instances flaggedTasks = [((AppDelegate*)[[NSApplication sharedApplication] delegate]).taskEnumerator tasksForBinary:flaggedItem]; - //save all tasks - [self.flaggedItems addObjectsFromArray:flaggedTasks]; + //sync to save + @synchronized(self.flaggedItems) + { + //save all tasks + [self.flaggedItems addObjectsFromArray:flaggedTasks]; + } } //when binary is dylib // ->just add, since will be processed as single item else { - //add - [self.flaggedItems addObject:flaggedItem]; - + //sync to save + @synchronized(self.flaggedItems) + { + //add + [self.flaggedItems addObject:flaggedItem]; + } } } @@ -85,6 +100,21 @@ } +//init the UI +// ->each time window is shown, reset, show spinner if needed, etc +-(void)initUI +{ + //center + [self.window center]; + + //table reload + // ->make sure all is reset + [self.flaggedItemTable reloadData]; + + return; +} + + //table delegate // ->return number of rows, which is just number of items in the currently selected plugin -(NSInteger)numberOfRowsInTableView:(NSTableView *)tableView diff --git a/ItemView.h b/ItemView.h index 429571b..4c32b43 100644 --- a/ItemView.h +++ b/ItemView.h @@ -3,7 +3,7 @@ // TaskExplorer // // Created by Patrick Wardle on 5/23/15. -// Copyright (c) 2015 Lucas Derraugh. All rights reserved. +// Copyright (c) 2015 Objective-See, LLC. All rights reserved. // #import "File.h" diff --git a/ItemView.m b/ItemView.m index 1b1f7a8..c4cb466 100644 --- a/ItemView.m +++ b/ItemView.m @@ -3,7 +3,7 @@ // TaskExplorer // // Created by Patrick Wardle on 5/23/15. -// Copyright (c) 2015 Lucas Derraugh. All rights reserved. +// Copyright (c) 2015 Objective-See, LLC. All rights reserved. // #import "Consts.h" @@ -38,7 +38,7 @@ NSTableCellView* createItemView(NSTableView* tableView, id owner, id item) //handle logic for search results // ->dylibs and files have the special global 'loaded in' views else if( (YES == [owner isKindOfClass:[SearchWindowController class]]) && - ( (YES == [item isKindOfClass:[Binary class]]) || (YES == [item isKindOfClass:[File class]]) ) ) + ( (YES == [item isKindOfClass:[Binary class]]) || (YES == [item isKindOfClass:[File class]]) ) ) { //create & config view itemCell = createLoadedItemView(tableView, owner, item); @@ -163,12 +163,25 @@ NSTableCellView* createLoadedItemView(NSTableView* tableView, id owner, id item) CGRect nameFrame = {0}; //get host tasks - // TODO: make this work for files too! + // ->works with dylibs or files + //TODO: make work w/ network connections tasks = [((AppDelegate*)[[NSApplication sharedApplication] delegate]).taskEnumerator loadedIn:item]; - - //start 'loaded in: ...' str - loadedIn = [NSMutableString stringWithFormat:@"(loaded in:"]; - + + //add dylib indicator + //-> '(dylib, loaded in: ... ' + if(YES == [item isKindOfClass:[Binary class]]) + { + //init + loadedIn = [NSMutableString stringWithFormat:@"(dylib, loaded in:"]; + } + //add file indicator + //-> '(file, loaded in: ... ' + else if(YES == [item isKindOfClass:[File class]]) + { + //init + loadedIn = [NSMutableString stringWithFormat:@"(file, loaded in:"]; + } + //add all tasks for(Task* task in tasks) { @@ -289,6 +302,9 @@ NSTableCellView* createTaskView(NSTableView* tableView, id owner, Task* task) //task's name frame CGRect nameFrame = {0}; + //string for pid + NSString* pidString = nil; + //sanity check if(nil == task.binary) { @@ -349,9 +365,24 @@ NSTableCellView* createTaskView(NSTableView* tableView, id owner, Task* task) // ->should now be exact size of text taskCell.textField.frame = nameFrame; + //init pid string + // ->search mode, show 'task,' to differential between files, etc + if(YES ==[owner isKindOfClass:[SearchWindowController class]]) + { + //init + pidString = [NSString stringWithFormat:@"(task: %@)", task.pid]; + } + //otherwise + // ->just set pid + else + { + //init + pidString = [NSString stringWithFormat:@"(%@)", task.pid]; + } + //set pid // ->immediately follows name - [((NSTextField*)[taskCell viewWithTag:TABLE_ROW_PID_LABEL]) setStringValue:[NSString stringWithFormat:@"(%@)", task.pid]]; + [((NSTextField*)[taskCell viewWithTag:TABLE_ROW_PID_LABEL]) setStringValue:pidString]; //set path [[taskCell viewWithTag:TABLE_ROW_SUB_TEXT_TAG] setStringValue:task.binary.path]; diff --git a/Items/File.m b/Items/File.m index 9e4e7bf..398f382 100644 --- a/Items/File.m +++ b/Items/File.m @@ -29,7 +29,7 @@ if(YES != [[NSFileManager defaultManager] fileExistsAtPath:params[KEY_RESULT_PATH]]) { //err msg - syslog(LOG_ERR, "OBJECTIVE-SEE ERROR: %s not found", [params[KEY_RESULT_PATH] UTF8String]); + //syslog(LOG_ERR, "OBJECTIVE-SEE ERROR: %s not found", [params[KEY_RESULT_PATH] UTF8String]); //set self to nil self = nil; diff --git a/NSApplicationKeyEvents.h b/NSApplicationKeyEvents.h index 88f315b..4a4d321 100644 --- a/NSApplicationKeyEvents.h +++ b/NSApplicationKeyEvents.h @@ -3,7 +3,7 @@ // KnockKnock // // Created by Patrick Wardle on 7/11/15. -// Copyright (c) 2015 Lucas Derraugh. All rights reserved. +// Copyright (c) 2015 Objective-See, LLC. All rights reserved. // #import diff --git a/RequestRootWindowController.m b/RequestRootWindowController.m index 4e23eaf..0f8386f 100644 --- a/RequestRootWindowController.m +++ b/RequestRootWindowController.m @@ -154,8 +154,9 @@ //2nd arg: permissions // ->4 at front is setuid + //TODO: CHANGE B4 RELEASE!! //TODO: make 4755 before deploy (for testing, 777 makes XCOde be able to del it during build!) - installArgs[1] = "4777"; + installArgs[1] = "4755"; //3rd arg: XPC service installArgs[2] = [xpcService UTF8String]; diff --git a/SearchWindowController.h b/SearchWindowController.h index bb378df..1ea844e 100644 --- a/SearchWindowController.h +++ b/SearchWindowController.h @@ -46,21 +46,15 @@ @property (nonatomic, retain)VTInfoWindowController* vtWindowController; //info window controller -@property(retain, nonatomic)InfoWindowController* infoWindowController; +@property(nonatomic, retain)InfoWindowController* infoWindowController; + +//'pls wait' label +@property(nonatomic, retain)NSString* plsWaitMessage; + +//overlay view +@property (weak) IBOutlet NSView *overlayView; -//TODO: remove! -//tasks -@property(nonatomic, retain)NSMutableDictionary* tasks; - -//dylibs -@property(nonatomic, retain)NSMutableDictionary* dylibs; - -//files -@property(nonatomic, retain)NSMutableDictionary* files; - -//connections -@property(nonatomic, retain)NSMutableDictionary* connections; /* METHODS */ diff --git a/SearchWindowController.m b/SearchWindowController.m index c58cc6b..163d2ff 100644 --- a/SearchWindowController.m +++ b/SearchWindowController.m @@ -16,31 +16,29 @@ #import "KKRow.h" #import "Filter.h" #import "Utilities.h" +#import "Consts.h" @implementation SearchWindowController -@synthesize tasks; -@synthesize dylibs; -@synthesize files; -@synthesize connections; @synthesize didInit; -@synthesize searchTable; @synthesize filterObj; -@synthesize searchResults; @synthesize searchBox; +@synthesize overlayView; +@synthesize searchTable; +@synthesize searchResults; +@synthesize plsWaitMessage; @synthesize vtWindowController; @synthesize infoWindowController; -/* + //automatically called when nib is loaded -// ->center window +// ->first time (when outlets aren't nil), init UI -(void)awakeFromNib { //single time init if(YES != self.didInit) { - //TODO: Flaggged Item window, use same logic! //init UI [self initUI]; @@ -50,42 +48,6 @@ return; } -*/ - -//TODO: center window each time? - -/* -//automatically invoked when window is loaded -// ->set to white --(void)windowDidLoad -{ - //super - //[super windowDidLoad]; - - - //TODO: reset searchResults when user clears search box~! - - - //init search data - // ->loads all current tasks, dylibs, files, network conns - //[self initSearchData]; - - //TODO: disable search box till done generating search dicts? - - return; -} -*/ - -//TODO: delete/remove -//automatically invoked when window is closing -// ->make ourselves unmodal --(void)windowWillClose:(NSNotification *)notification -{ - //make un-modal - //[[NSApplication sharedApplication] stopModal]; - - return; -} //init/prepare // ->make sure everything is cleanly init'd before displaying @@ -103,6 +65,38 @@ { //can init UI [self initUI]; + + //set flag + self.didInit = YES; + } + + return; +} + +//automatically invoked when window is un-minimized +// since the progress indicator is stopped/hidden (bug?), restart it +-(void)windowDidDeminiaturize:(NSNotification *)notification +{ + //current time + NSTimeInterval currentTime = 0.0f; + + //grab current time + currentTime = [NSDate timeIntervalSinceReferenceDate]; + + //make sure search is still waiting... + // ->and then restart spinner + if((currentTime - ((AppDelegate*)[[NSApplication sharedApplication] delegate]).startTime) < SEARCH_WAIT_TIME) + { + //update UI on main thread + dispatch_async(dispatch_get_main_queue(), ^{ + + //show + self.activityIndicator.hidden = NO; + + //start spinner + [self.activityIndicator startAnimation:nil]; + + }); } return; @@ -112,6 +106,12 @@ // ->each time window is shown, reset, show spinner if needed, etc -(void)initUI { + //current time + NSTimeInterval currentTime = 0.0f; + + //time left + NSTimeInterval timeRemaining = 0.0f; + //center [self.window center]; @@ -122,9 +122,40 @@ //reset search string [self.searchBox setStringValue:@""]; - //show activity indicator while tasks are still being enumerated - if(YES == ((AppDelegate*)[[NSApplication sharedApplication] delegate]).taskEnumerator.isEnumerating) + //grab current time + currentTime = [NSDate timeIntervalSinceReferenceDate]; + + //check if search timeout has been hit + // ->when this occur, no need to show activity indicator, etc + if((currentTime - ((AppDelegate*)[[NSApplication sharedApplication] delegate]).startTime) > SEARCH_WAIT_TIME) { + //make search box first responder + [self.window makeFirstResponder:self.searchBox]; + } + //show activity indicator while tasks are still being enumerated + else + { + //calc time remaining + timeRemaining = SEARCH_WAIT_TIME - (currentTime - ((AppDelegate*)[[NSApplication sharedApplication] delegate]).startTime); + + //pre-req + [self.overlayView setWantsLayer:YES]; + + //rounded corners + self.overlayView.layer.cornerRadius = 20.0; + + //maks + self.overlayView.layer.masksToBounds = YES; + + //set overlay's view color to black + self.overlayView.layer.backgroundColor = [NSColor blackColor].CGColor; + + //make it semi-transparent + self.overlayView.alphaValue = 0.20; + + //show overlay + self.overlayView.hidden = NO; + //disable search box self.searchBox.enabled = NO; @@ -137,18 +168,13 @@ //start spinner [self.activityIndicator startAnimation:nil]; + //update w/ time + self.activityIndicatorLabel.stringValue = [NSString stringWithFormat:@"%@ (%d)", PLS_WAIT_MESSAGE, (int)timeRemaining]; + //spin up thread to watch/wait until task enumeration is pau [NSThread detachNewThreadSelector:@selector(waitTillPau) toTarget:self withObject:nil]; } - //can search right now! - else - { - //make search box first responder - [self.window makeFirstResponder:self.searchBox]; - } - - return; } @@ -156,104 +182,54 @@ // ->wait until task enumeration is done, then allow user to search -(void)waitTillPau { + //time left + NSTimeInterval timeRemaining = 0.0f; + + //calc time remaining + timeRemaining = SEARCH_WAIT_TIME - ([NSDate timeIntervalSinceReferenceDate] - ((AppDelegate*)[[NSApplication sharedApplication] delegate]).startTime); + //nap/check/etc - while(YES == ((AppDelegate*)[[NSApplication sharedApplication] delegate]).taskEnumerator.isEnumerating) + while(timeRemaining > 0.0f) { //nap [NSThread sleepForTimeInterval:1.0f]; - } - - //ughh, don't know exactly how long q-processing will take - // ->so just nap a little longer - [NSThread sleepForTimeInterval:15.0f]; - - //yay all done - // ->hide activity indicator - self.activityIndicator.hidden = YES; - - //hide activity indicator label - self.activityIndicatorLabel.hidden = YES; - - //enable search box - self.searchBox.enabled = YES; - - //make search box first responder - [self.window makeFirstResponder:self.searchBox]; - - return; -} - - -/* - -//init search data -// ->load all tasks/dylibs/files/connections into dictionaries for easy/quick searching --(void)initSearchData:(NSString*)searchString -{ - //alloc task dictionary - tasks = ((AppDelegate*)[[NSApplication sharedApplication] delegate]).taskEnumerator.tasks; - - //alloc dylibs dictionary - dylibs = [NSMutableDictionary dictionary]; - - //alloc files dictionary - files = [NSMutableDictionary dictionary]; - - //alloc connections dictionary - connections = [NSMutableDictionary dictionary]; - - - /* - //'#' indicates a keyword search - // ->check for keyword match, then filter by keyword - if(YES == [search.string hasPrefix:@"#"]) - { - //ignore #search strings that don't match a keyword - if(YES != [filterObj isKeyword:search.string]) - { - //ignore - goto bail; - } - } - - //filter - [self.filterObj filterTasks:search.string items:self.taskEnumerator.tasks results:self.taskTableController.filteredItems]; - */ - - //process each - // ->add files, tasks, network connections -/* - for(NSNumber* taskPid in tasks) - { - //extract task - task = tasks[taskPid]; - // + //calc time remaining + timeRemaining = SEARCH_WAIT_TIME - ([NSDate timeIntervalSinceReferenceDate] - ((AppDelegate*)[[NSApplication sharedApplication] delegate]).startTime); - } - */ - /* - - return; -} -*/ -/* --(void)search:(NSString*)searchString -{ - //filter object - Filter* filterObj = nil; - - //task - Task* task = nil; - - //init filter obj - filterObj = [[Filter alloc] init]; - - - return; -} -*/ + //update UI on main thread + dispatch_async(dispatch_get_main_queue(), ^{ + + //update w/ time + self.activityIndicatorLabel.stringValue = [NSString stringWithFormat:@"%@ (%d)", PLS_WAIT_MESSAGE, (int)timeRemaining]; + + }); + } + + //update UI on main thread + dispatch_async(dispatch_get_main_queue(), ^{ + + //hide overlay + self.overlayView.hidden = YES; + + //yay all done + // ->hide activity indicator + self.activityIndicator.hidden = YES; + + //hide activity indicator label + self.activityIndicatorLabel.hidden = YES; + + //enable search box + self.searchBox.enabled = YES; + + //make search box first responder + [self.window makeFirstResponder:self.searchBox]; + + }); + + return; +} //table delegate // ->return number of rows @@ -399,10 +375,17 @@ bail: goto bail; } } - + //1st: search for all matching tasks + //sync + @synchronized(allTasks) + { + + //search for all matching tasks [self.filterObj filterTasks:searchString items:allTasks results:matchingTasks]; + }//sync + //add all tasks [self.searchResults addObjectsFromArray:matchingTasks]; diff --git a/Task.h b/Task.h index f4a9a19..958b8db 100644 --- a/Task.h +++ b/Task.h @@ -3,7 +3,7 @@ // TaskExplorer // // Created by Patrick Wardle on 5/2/15. -// Copyright (c) 2015 Lucas Derraugh. All rights reserved. +// Copyright (c) 2015 Objective-See, LLC. All rights reserved. // #import "Binary.h" diff --git a/Task.m b/Task.m index 76ec424..fc7e8d8 100644 --- a/Task.m +++ b/Task.m @@ -3,7 +3,7 @@ // TaskExplorer // // Created by Patrick Wardle on 5/2/15. -// Copyright (c) 2015 Lucas Derraugh. All rights reserved. +// Copyright (c) 2015 Objective-See, LLC. All rights reserved. // #import "Task.h" @@ -317,17 +317,17 @@ bail: //alloc array for new dylibs newDylibs = [NSMutableArray array]; - //sync - @synchronized(self.dylibs) - { - //reset existing dylibs - [self.dylibs removeAllObjects]; - } - //invoke XPC service (running as r00t) // ->will enumerate dylibs, then invoke reply block to save into iVar [[xpcConnection remoteObjectProxy] enumerateDylibs:self.pid withReply:^(NSMutableArray* dylibPaths) { + //sync + @synchronized(self.dylibs) + { + + //reset existing dylibs + [self.dylibs removeAllObjects]; + //add all dylibs for(NSString* dylibPath in dylibPaths) { @@ -381,30 +381,24 @@ bail: } } - //sync - @synchronized(self.dylibs) - { - //add to task's dylibs - [self.dylibs addObject:dylib]; - } - + //add to task's dylibs + [self.dylibs addObject:dylib]; + } //all dylibs - //sync to sort - @synchronized(self.dylibs) + //sort by name + self.dylibs = [[self.dylibs sortedArrayUsingComparator:^NSComparisonResult(id a, id b) { - //sort by name - self.dylibs = [[self.dylibs sortedArrayUsingComparator:^NSComparisonResult(id a, id b) - { - //sort - return [[(Binary*)a name] compare:[(Binary*)b name] options:NSCaseInsensitiveSearch]; - - }] mutableCopy]; - } + //sort + return [[(Binary*)a name] compare:[(Binary*)b name] options:NSCaseInsensitiveSearch]; + + }] mutableCopy]; //reload bottom pane now // ->this will only reload if new task is the currently selected one, etc [((AppDelegate*)[[NSApplication sharedApplication] delegate]) reloadBottomPane:self itemView:DYLIBS_VIEW]; + + }//sync //complete dylib processing for new dylib // ->get signing info, hash, etc, & save into global list @@ -445,17 +439,17 @@ bail: //alloc array for new files newFiles = [NSMutableArray array]; - //sync - @synchronized(self.files) - { - //reset existing files - [self.files removeAllObjects]; - } - //invoke XPC service (running as r00t) // ->will enumerate files, then invoke reply block so can save into iVar [[xpcConnection remoteObjectProxy] enumerateFiles:self.pid withReply:^(NSMutableArray* fileDescriptors) { + //sync + @synchronized(self.files) + { + + //reset existing files + [self.files removeAllObjects]; + //create/add all files for(NSMutableDictionary* fileDescriptor in fileDescriptors) { @@ -489,20 +483,18 @@ bail: } } - //sync to sort - @synchronized(self.files) + //sort by name + self.files = [[self.files sortedArrayUsingComparator:^NSComparisonResult(id a, id b) { - //sort by name - self.files = [[self.files sortedArrayUsingComparator:^NSComparisonResult(id a, id b) - { - //sort - return [[(File*)a name] compare:[(File*)b name] options:NSCaseInsensitiveSearch]; - - }] mutableCopy]; - } + //sort + return [[(File*)a name] compare:[(File*)b name] options:NSCaseInsensitiveSearch]; + + }] mutableCopy]; //reload bottom pane [((AppDelegate*)[[NSApplication sharedApplication] delegate]) reloadBottomPane:self itemView:FILES_VIEW]; + + }//sync //process all new files // ->calculate hash, etc & save into global list @@ -529,15 +521,16 @@ bail: //File object __block Connection* connection = nil; - //remove any existing enum'd networking sockets/connections - [self.connections removeAllObjects]; - - //dbg msg - //NSLog(@"invoking XPC to enumer networking"); - //invoke XPC service (running as r00t) // ->will enumerate network sockets/connections, then invoke reply block so can save into iVar [[xpcConnection remoteObjectProxy] enumerateNetwork:self.pid withReply:^(NSMutableArray* networkItems) { + + //sync + @synchronized(self.connections) + { + + //remove any existing enum'd networking sockets/connections + [self.connections removeAllObjects]; // //NSLog(@"found %d connections", networkItems.count); @@ -558,6 +551,9 @@ bail: //reload bottom pane [((AppDelegate*)[[NSApplication sharedApplication] delegate]) reloadBottomPane:self itemView:NETWORKING_VIEW]; + + }//sync + }]; return; diff --git a/TaskEnumerator.h b/TaskEnumerator.h index 3963a10..df6aa35 100644 --- a/TaskEnumerator.h +++ b/TaskEnumerator.h @@ -35,10 +35,6 @@ // ->contains binaries that should be processed @property (nonatomic, retain) Queue* binaryQueue; -//flag indicating tasks (and thus files/dylibs/etc) are being queried -@property BOOL isEnumerating; - - /* METHODS */ //enumerate all tasks diff --git a/TaskEnumerator.m b/TaskEnumerator.m index 1315546..548da4d 100644 --- a/TaskEnumerator.m +++ b/TaskEnumerator.m @@ -22,14 +22,11 @@ @implementation TaskEnumerator - @synthesize files; @synthesize tasks; @synthesize dylibs; @synthesize binaryQueue; @synthesize executables; -@synthesize isEnumerating; - //init -(id)init @@ -73,10 +70,7 @@ //xpc connection NSXPCConnection* xpcConnection = nil; - - //set flag - self.isEnumerating = YES; - + //determine if network is connected // ->sets 'isConnected' flag ((AppDelegate*)[[NSApplication sharedApplication] delegate]).isConnected = isNetworkConnected(); @@ -203,9 +197,6 @@ //TODO: add network connection filtering - //all done! - self.isEnumerating = NO; - return; } diff --git a/TaskExplorer-Info.plist b/TaskExplorer-Info.plist index 06c5280..1e89437 100755 --- a/TaskExplorer-Info.plist +++ b/TaskExplorer-Info.plist @@ -17,11 +17,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.0.1 + 1.1.0 CFBundleSignature ???? CFBundleVersion - 1.0.1 + 1.1.0 LSMinimumSystemVersion ${MACOSX_DEPLOYMENT_TARGET} NSHumanReadableCopyright diff --git a/TaskExplorer.xcodeproj/project.xcworkspace/xcuserdata/patrickw.xcuserdatad/UserInterfaceState.xcuserstate b/TaskExplorer.xcodeproj/project.xcworkspace/xcuserdata/patrickw.xcuserdatad/UserInterfaceState.xcuserstate index 68708620d0f23f4df1cfc3c6d055063be054b3f1..10eec8c3ed83731be7543f48baeca46d48cf4a6e 100644 GIT binary patch literal 42522 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 literal 33017 zcmdRX2YeLO^Z4%D-MiaOLN2{SdasvU3Is?`E&YwR{u`f9rxTl8?d#J9Vn)0QCT zt=(lqMG;a$M%WSdgaZ*k1QJ0+FcCt85@AF*5kX`US%iwnCUS^eB9F)?3W!3Yh$tr1 zLbHt~_dEx@`Iq?9#*j)fmP{bWl1XGLnL(<^O0tTqCTqxAvX0b{ z_2fiy64^?&k(0?T(m-~TJc-FU?wz zCy$Uv$v4R3kA#8c-u@LOP^Jy=W$yg=V9B5RWjLgYHESqKDAK zXeoLGEkn!E3bYn&MBC9*Xb0MZo<+~0{pbLC6&*&eqa)}jdI!CS-bWvx^XPMQ75zrh z6hlcUDJ7#=%8D96Ia4l_E9FLwqI@V{Dw2w#6jU@7Ln*0PYBV*58c(HD8B{J+OqEep zR5hic>ZvA5M@^ubsYz4^)k#gErc$%0`P5?S3F=8|8?~K!irPW#q4rTP5x1z9se{xZ z>L~RF^)~emb%OeUI!#@qKBvB*E>T}nKU2R@zfxDJ->4hZO`4)*w4An~ZE089jdrKK zX&*X}j-;b#1wEP`LnqQnbQWDqSI~8I13iJBNKc|W=x%x%J(K3?x%6uK3HnKT4ZW6L zNB7a|=?(NodK3LD{T%%~y_bH0-bWvzkJ4|@Z`1G4C+L&(Y5HUOQ~C?~EBb5tD*YRM zjsBg!PTyh(hGDFj5sWn>XKWZ3#+7knMlpVjKNG-2Fp*3YqhO+$cqWlq%B)~kGLJK> znYGM1W+StS*~V;Vb}>&g&oa+3`>WnRCo% z%thu)<}!1I`Ih;S`H8v8{Kovj{K?#s5E5F#NLUFc86mNj*h%aq&Jq`ihh(J0TjC=L zkOWFXC1H{%i9!-9iIa?xBubJb$&z$Qh9p~(BPoy+N=hW9l1fRHWP+qw(jw7IdL&aM zQzg?R(7VEv*Z`auac`$Qi`OMl$Oe+Hd0&ZNa-l4r_@UtCJmQH zNF$|DQiU{H8Y4}TCQDPK`O*Sup|nU^E3K1iq?4qr(l+UI=?rPFbiQ1OF3>9f-3q|ZwaN)Jg7OW%{8ke-x&Dm^c~ApK1Go%DO@57Hl{H>5YE ze@So2EM%54E7=H{tISR2F7uED$%17evQSyPEI~F}Hb#~y%aW;NrLrGQDh~Y?5rMY?^GkY=&%}Y`$!PY@uwqY=vy4>`~bU*+$tW*=E@u*|W0eWY5bE z$_~j6%U+kgBRe5GDLXCuPq#jvddc*lae3&1DPO61J4pvJGq_+r;YF32ZYviS1x# zv6!91-p@Y7E@PLo>)1YaJ-eCR&hBGhWcRZN*q7Ls*@NsM_8s;(`!4$)`#yVy{fNE9 ze#u^Dzhb{;zhSSiKd`@WBxk`{b9S6P=gE0--kd)dz=d!L+*ocLm&N6BgW#7M$_GvP!y6E1`+;YPR<9z4TKcquRA zS)Su9HWQ-=Pr?gOa3*{SKi-nJ!elXEBjH{61U{fn0IzRv>X@3*rESw`S%4%b!p-gBI6TM<1@li(=yV-Vqz3=VF}8}j4(yAB04QfnI5A|h}X!y06;-k zldenGG=R4m9D_z4IfXuDK~GQC0H&Frg-M231Ql!{rQp1o1A@o*6zshl;-#NkL{ z4B@|xh$0k3G!a86iC7|zh$j+=(fkPBnwRr7ye)6X+w%^*Bk#10NCYmABgPX+L^6>A zzo!xDyfd)cm3QL<`IUSh{JxRj#0O~Pj>!fCxH{VQ?(`;ocSn~YS3A90`tIq;T{^8A+^Fs{y>6-qZUB_< z>^A2(G{gaAf{zExMK?QW@sOOYS}ikx6I za*tvijHsczxTB-{_R$V+GqJjqC@1{a5oNslI--L25LjfBR#q}ViK<)IW;kioxEey! zN7NE^{7BxH_csC15>13{AJITG@}qdqK0-%K;JtWn-lrD843#H2Tnj|yX`9>i-91gZ zLZ2kQkP!YmNwg8|LH?!PYQn)l zOt}q*X@G+t@8gryzII3tGl^Ms`Rcr$ZY{`Ya#xpj`g&p}F`JkScEoroBEl0`Bexr9 zIN1Aof*1NXb$c(dSTF>0iTj9o#C&1_v5;8A2l2st2p`Ia@!@>LX5xP00b&X9An_3K zFtL=6V9Cv|sMPh)pam(IkW)x-vY zKTi-(5^IRH#5$snSkFiC3O<^T;gx(WAGevTiiyOr2R`10{a`{=@t=EuNqz#l>G z)2DT6+naPvMo|>)lE|I`f(5;ZylE zKAq3tGq(|Mfq{I7I8MAvyazvz1GnGjv%o-R^Z9%MUj#q%f!k_<-va9fWNU!GsW263 zI}N&~%&v}}&VH)yY^H7$=B&aFgI>^9jXY>rV1m>pcLNI>dO&DRymTDeHcyalb@y~( zA_*A!U;rbN%ole|Re@|w6NFlr-|j@JSl8JKllYxI3UmBGa{;N6>7CG@$I#mY{Rz+W zKvQ7?8Twq++}_cpOVt|SfuO_#@CIHqA)4FKs1>+kPBXEICtxghO1L$vvD_|%^{_;6| z-buLk9q~Q!LnSy%2BQe`xgc$V>HUfLStGX|=uUiTJ>34qXl=!7vFTOfH<UW z^~5#ecLBq@H_^y#!Q9>^0dnGf;!nWvGvWqullY6cMce|4Ok4v(a&_&^-7RtnUnnPy zn2;oih7jFHQpDsT*G49g3~_}o9-y6Mf#QBGA}vTO!gd{L$(O7nNARVBcW4bmSf*_S z6Pau%>C%}D8EH$}5&mn}3JUTsEvE^z6X`|xZzY{c7t)nwBmlkt z)d6rqQ@AHMe$W~Cinn6}V++p)+MDc!eQz)Lc#(p;r%)z@dd9{_&6;I2bYvVEzm{(l zM|3nf2DG7neip=?98D$?lS@I2b=_j~asAC91R#@*7E`>RHspS$@psc;WRpdN|0Xhr z%q8>4e6oNnyzCYlam35dIs;Ddbdg8tKAM=V$P}eB}o48T`mu z+}S`-eUtbPRsFf-ya6jQpP$A1 zm{`Av1m{n*3ICFS2gwzJ_&r2EOfDrK0gEt==XuP};qL`o(7TCT352YIzmJov`ME&M zgYfrZeyKo^FhiL}#pwJ9h=Bo~LPti?)0T`fNgeG`FqAr`_ z+<^xD7RE&JdUE3cD>m`>4YFb@xgBB%avMKy9r+YLpARq_Z`12flP`cbNbV+|A@`8a zlFyOPlY99E{6c;aznH(De}G>iJRI@|9uEBzP*_~5sx4Iw#;k@?BUj9Nt0?U1XaYA> zuv}fbwvKL{AbV{R2VsS#qv2ofA&2AwWqsEvyFPhw}}jl#eWM?T0G`5}K4fC5nv3PvF) zl;6y6;kWYJ`0e~t{0@F6zl(o*8wy7e0zEL>B%&CkM6oE2-;Kff5D0YUoiQ1J$uXEr z!({q^{u{Ft#`z;t*RJmYN(B#8WWVt?*n##YZ5Kp>a)AY+?bdY(+!Y2&xF@y-gHk3g z=7`too`yDXX{(07lH0)>X}Ss8p4M(7AOQ+!k_qTQS^%QJ{$_;&NP{rRfu{|#0Q@34 zx>c>l%*qgBiqcS~Fu3U`gMWtK(}%K~? zP_Y2J5Eb#y^LzV{8kO)b@SpJkmC1s`)D9C^NgjlMruLPnmatuqs!%nm;rH<`^844L zIvALG{s4@{c|lr(%&;)dfojMOwKp4o9Y_m6HU_3y^S1qt)a~~(bscTG?k+LnLlX$i zI@HX+G@!3&BI*Jgi6)^|)P~wo2kJzV`Iq@u_*ePY_=Ef*{_tjGK;5VZO+i!9G&G%m zoj<}K=Rf8@;m`8t!199e)}?8?wSuQsE#hRv7CgvgORg4-el zumuxL?+!lWPxJ5cAMph5Y_#)e9kIU;^`Z6rd;BRr(ZtG4XbVgVXfuCe9oovD6by%L zyD^E`Kb{&Pod$dyI;#v$qTwwjKs(XX0-#;|`|Hqd{sTUs3K%z#4j$8=6B4}92@p`H z^nh!q>k`sdqG3t}4QuY`njS7P6oewVr@NyKj6vfm^gJ+YFM0v(Lr+#W_2<(;Kq|XU zgUNr$pWz9OJbMBN@aTw$zhSHIMwc_A6Xf3EqiBc+l1n`P!i{WcL6uI}jR zYJ@yOlkOCH0~me`J&E2zF9?ey4UnR!hJU+stuP={bRblp@*j$H9!Kv&gaxC~hmI4I zMNvB;n5L8b1>WDxKUc;H!J8s_z&}T)(Z_d~(NDmPp5reH6PhLOBLciY>|cjI<3Asi z+b__!|JK%hhrUNYpdZQ0=x6i`{{?@E{|iG9zXFpSCM^U}_iOBE3)gmbwn7s1Hqnd@ zPwi;z(1us2QcNO#4c#PcH=^Isb@T`N6W!pyF&~*)Jr{Hs_QnIEubm@gGmYDh!9F9TV(#Xf;Y7O1}vu;L5m%67@ zj3e8d1?3QD$kNW{F0hT@xVCk6LnG68f?Xa^C3rXmv=LSU991c)+V&}0SZW#Cm66L7 z2VABs_;2~ds-pP#u$H{A^yu<_;wftivKZ?rIb}oH^5646@IS7n>?sGzk^hPRng2!L zrjshApr^YPlBDL)g;*p7@S1e{Z5M&Qoc4~X?L*!x*Ee;WtP15$d5A8x2@OvQk|RRk zKlnfSn_!r#Iy%}i zKz(49CKXJSuB)&^-wwGCCqW}1#_QgD6#uYV*h$7i&9b9n1rGe3X=>d5|u~g zL!5n?NTiCY1_iU!IHB+>1pPxs6{plxi5NEyS}!qJH#RNQ>bv@=Qo(!)vE^Ocm^yrh zs-TMag_xwF1G#PgZK_7V1CtVCzh*&Mzx}7Qg8j#&Of>cBf8W%Xsv?G3dm%QeYwz%c zF>fld zjVyI9aQi+?+6vr;l`awC1p>k_Fm?lGmb#x>_V3IrwVYZ(t)w2M9!1lr$1!P-Ne4{2 zVA37_=g(r&S0Kiv-<2DTni|6l#u>*%z#3}9a84byk=jJ~QCl$Sh)E|t0h7+KG6(*Z z-8ZK;eqPh7h)Re9U5km0npGyI35V*@PHLA)kGwJID(aC)?X!YbJV!lG?Zu=UCP!k( zu$r_2sM#+_Baq+$Qu=R~L;H1U5A`aD_99G<5=7h4B-)2X(e}E#Xde?r+eZ}bg1;}? z|2pCvBBIAc5$zxGlfsCf!leJd7ts$X$W3gZ&QKpwA5))TG7ytNm<+~b$Oh^h^(k3Q zUBF~0CZqTcOh)qo6}h?z#?|b~VtsRq@wX~bl*M$WF=K36@g9g4O~P=Qg6Vw&^%eCs z^$m3elVO+)$7BR1BZcH$qH(z|0jAmhg+8}3ZI@n%O^oxJF_tgZP8E~!8o8_aQ_~__ zV~4O}<7xI#n4#1iT@bz+8oTtJ!V*7Bnr2Om(1ifWgzGixx}dzjV^Xn>NCc!n+-+bQ zH*~c2r^^J2O|Ac;321H1!q8xhWBSuDG!5(HG($@;sl;UbKu|}swB^uz2yI1=z+@~Y z<9MF|7W*Xi^2VvBzZ2Syb~Jm*i3U9f)fvtE3=Rjp)oJ#Y2R#ZUXb5_ApIgJ{!ylIGT%bsbH|n!OcA$73=D zlc|CeC1h7kIRr87Q}e3X+hgf*;uQKmolK|Dse}cchMuG|=uC)ax}btV6g(lBkZ)Xu zb80qbLquc*k4rbLTYTc&DaMO;Ji=r?tn8B+m@GO?tLSX#BZtnV^PrzJx{$a<7ZF7j zP}*wDc$al*A@AK?-6$sT#WfPorvA`naEde!rK>x-grxvu0z~L5ACrZc%p79RXf<78 zvS)di%o<|P=t{6>bQN7q*I-hGNto+%?q<(SQ?Z%7>c2@hi`FXlKeJY5yr4UQ8&I1I z@hlk60)d7?9_lk7pHKIIsiA>;8U5?h^mKZLxF#^*wwP75m=9SW-77d4!gB6^(%k5` z(6i{-;u8A5d!>)QM}RJ@w*R}PA~6_Ba*N+i{$o= zN55fo9aAz)gJMa}#Jq5dt_6bpj;E?2Ge9{Es`fm6yM6#yK*;R6->90b z+-7M+@iN&R~I75Y{BHB3&zWGg1yz)c+V4(KDI zcQ8RCS4<0=CJ+4%v2kHBidaQha%37*P{yY#)0GJ+P^YLI3g|r%(8N0c zC8x%urbnkLp|UYHB`ii68yl7qql^!Wi;7dm#H1*aW22%>f^v#}Uz{IIH$J4#nBVw> z2Jc1)D@`rV)1L`*;RQ@~tfMbtar_6cY;(!n7aOkhIN_^ z^cDJB`aAl2OhQy(z$8QkJsapB>7VGI>0dB81(Q4`F(wxa!W?KOoc#hMy4SgoUJo}2 zj@A_LZJ^F&n5KnYIxq%3h7xU8v#!6iF|`#6nTBs}%4wKX#FP_*=3<7)qyZu#8nMTr zz)ht7LEjL#{3j--uA^^aSltwSr{O&gh6Im;K@5bi(=n{wnra9c3B!W#z(^SxCTC!> zw~yf%3rx<$BrLf8+d4uh#2RGOzzT(s$sUY}eUfxZLmdvrmT@relGw^P@uM+$k5Dx@ zzs1-Xe2+URH_ji7JIo&pL>6=S#P=Cb$ZbM~lksA_86U>C(qME@Ap?p@2+`(Z@?K0X z`tKAph@R$Mard@o`iFrDWI|x|g9&25ou7xv`F%_%10MGROfKXD%EVDD6UT2@qF~JK zSS>Y9!AuOJG}RNDgyMFoSkm69oy(Xfyp~#xl)>|1NnDGmaV0 zBr(ZM3X{sDQI9hjOeT}XsF-XfhskB~n0%&yDP)S6Vn)rBFr`cxQ_fT{l}r^=&D1cp zOdX?P>KQH5z%(*VjExNA>`bL$^DoF!~6;+!5kmL9=>CO;AAX<_utL}n7x%Cs@g!4x=kq1Ve82MrpfKf0;VHibWq{Jv5qeP6xW0ZnX21eN! zcMC_Mzb)&W;T^sF51*38hO&+x2b}{8X{7_Wq~z> zwjnbJ^Oy+sA&vZH6IgIJU|GrN+!*>(YLEnuFa`qPR7Io-=|6}LyCMo9;SFUE06I_G zSfH*5hjKvUjUjE+X*s6HQIX;BzoBVTgnBxxYPLn>>KmZ0bb5rbQh%yY0UnVdB$|wU zhffgd_l3I={YhR!L~2BqajDA?k*}^OOa+wM!QB9304ptn6E(9&BxYXC^ zM>X=qzYo5wB&Daj8yIZ{a*qh|v413#Vz#kZC$QUm{9X{juKq_$R!|xLZ&~q@*uj(k zb-c~kaZm)i_8;LrH2Es%nz%>BoH0km4*E3m^1nZXcT7SNcib`W?QOBQ4gW}B1d}7= zi_Afu5J7Iz$kYBl0iqQfqV#6WKP`gZqLGjO`=B$7m7nIgej)I)9r$E&TuYD@_>s1t%NZ!=e(^*%B%l%Nf|1D5+@NX6jh8hap*tycm}El&AIL_ z!aV$6Gyi62dWj&9{39C;h9Am{&DrTM0(|2i0n8AMz8QugBEUB_^31=_NMX1kP1_1} zBE!z#l1LHy+ZuWN--mvO!IvmSaL50V!rf*c%(yUG1pMAVimM@CGT~15*$n=85&p^l zHZGe%PZOcP|6f!FGux&TVV?d+BWE-NW+=jHiLe%aMkAkR5`_N+i`HZ4iqN+=w!#L{ z2*Y%!*6r$O)ab!mHdiXOh}_5jXu5>$M}zSr>{m#i0*f{VbIVd8c5qfB$NxvvL%eF- zZv%vj`Cv)4q#p8$cT`PEw2}tGPXY;Y7_Rfe0{aE1u7L%1cK^Q63pKsbF+$lE6tgI1 zRrSUwqLrbZ2@2C@Yso}O2LP2!lC(+lV4(}PzS(Y z3F*E6vx>}l7wP3Q-D`T4@i7Xh!;Dcz0zq-n%IHwfnAib=W=ZA<1kIM*BjF_&liy(S z3MRkB zUB$48003Vz0-jro=eHgQnGqlf^%<4Dv2ih>p3#G-?vOllSEzd=&jQrvF?k)6e+W?j z6ronlx0&>2O>b08f)QQ7u@Z_v;-TPba1akjUK^GL2PKC9CM4Q!V%W<7FmH)0V9#W< zd?7Lt>T@eV=0PYT2Z(=50vkJq;php;NuxX<5+fvlgSCHwqfnYMc8ytSib)4Ql6-ns zg3n7Xh|p+^m_cZBtKWG)e_5q4%1}ofHGtG*iBKXjyykr;`5x%}5hE#vZ5Ig`u|~x1 z+Zp@YqYI66CPc;noeE)W2I>4w^5?L0-jLh`aJMkBz{pYnXVuTPw>Mt>NdyPVRM8t1 z7c~T&l#z0G!%%7=wG@f9#z;O$?7a8hQAkpZAmbATq)%!mb-pVk7pW`ZCw0fj79%?W zIeU@T?asTn!y?;a5@J9|l(B-^4^S)hmIeS&sgKlG>L>Nb$Pptaj9}H(WrH+O8U#Q? zFmlDnO#tfte`>ns7G5Hw+xul8J|0*t*konANf@L`DdfM07Y6AVDQr-L0xC2Tqfvvb zzHi}Yaci%OFryL%*^(;F0#J8M6H=8l8(5NykrzhZ0!w^EA+TO_@2Nf2y>PY&2xwH4 zU_GKDqd^d&2gk2i3g<)&1G7R}DWc|wk^dlS^U1xM^%FqHqhgJr98D{5u^oRGrIw<177)C`V!~(|@al%FX zz`QD5EQNZ4Vd#8Vx)gve!$^Tqv;Y(eh>Rv@*~Vk1VX`*X78=p3?2U%WZKyszCS7w^ zs@F=_iB!j86gNoqecOK8+x>%)e{qAhN4iA{EAPW#w^Oo9lC6 zT{W0#5DP#q0kknrDGY7&z>w^f9vBwnOVXD`#KvMYZV<8g7N<@WdV|;*XMw2rLHT`M z3g;W#Wo+M)zAXYv!YFwV*nNwf4jo7oRUsO%GYQ)%DJ&D;1=L5=0s0waqWUMgvD})n( z26!tQDf0o4!j ze>z3tgKzW8cUAYs3mO5_P&BAVRI~!bL=ijS7|L2@lkYl+T`~g@1gn$n77#Re}-6G@%ZALDc3_~C| zCLkVyha43j4Htu87s(#HE7*r*4+GdoFq(o9oH>9Iluv*Ox^Krfi#ndG>5YwvgVBsq zDx+X%VOu<0L=OU9C42I&fY->@0^mN3W?*A?fxq z4DpyQ*?Fi;kzK&Bi8Aqh+2>+q3Xv$gB>Pf!S=>B4P^VT1ROR;w+nR(@#RoB3f)T8; z|2HuR1bNQyQI@K+#zGR=71_7q{*r<2Zzo{A>^s@_rd=p^ZS^n9NMt|Ceja4mFBri= z3dVvR*>3_{u3_|uIa~ge-H=@ts9%l|_+&u+KUnm;a|D6*dx;-*du{I@N|t1$Q0d4b zmSSm^VI>%?#Ap>pk7M)%Mr$^*GL~gI)`GRfXdOoTFnS%Ma~MIwWr(1$Hlm=x`uIOc z{le~<3|KoGTzWR1EihP%-uxTuwMSpV4JN#31=b+w;HPg^I&IJp02%T$Vm=tAR8`-Ll7IxhOnV5 zl;MD_UXRfRj5cDlX(JoKMiPl^7#oezW(@m!K|(iS^t71)`2S!>B0C1uj)lsiEyL}c zW0Tl4C~#$y*%TI>@NF1v$LOi`Y&x64g73Toqn#M`BZJZV@86in=CS!^1!rs#TP)tV zePbe9##Y00$dae&%Q(~IEmz@veXtuSKUBE76VH$o3qn9yy1>;}MH+`MVR zVGFy}9EYdaX9RuR!R};tu}`xwz(+7TiV=7f$1r+xBfE!v7WDCX7RDFci+2QlJdV-( z|KI83E5bN}v-S3{`goXqL(s?P*&{3v4+Or8(R=IJW9*wOP<;ZUlNg=)AJWGY>`C)6 z`G7rbl8b5LIiaTE`t>y}*9PUSvPV2)w3`F#@mY z?Eiv3La-Sr>W+cT+}U8+$|8jLzQT$6)j+=;AjRT^!IM z4snv(^^KEqG7duc^B95i2j`yj_sdywR%ZQja?ZxYSMv?noCD`NoW5~xoIB^iLGlsU z`UOUpFuFWM-yk|xf~y}E8x<>3&-noLoG(USf{>L&6^1n=gq6(5Fd-Dk1(_oh$|>%~ z2rin75g5V6Ve~adS42+y&~KHv(LzVSp>Lq0NvhPas;aQors<}R#&fB6a4C&T=Q1$* z7NhSl`hJj0DlXd`sXVTKdboWy;EFjR6?%tN;wrdGu8OP12(tD+Ve~UbSBG#3R#A*O z1Xy`9I;31ZoaVu4xdx2DP5qTmELJ3_qvK3F9=K+%#T=nYTpOG@#kFE|Z5`K+(eL6x zOu}g`;$Cj^E!U#8*Z!*^anM#_5utT#|IQs%(oT2ws1}JoB?1F7i)ot3w4#PBKg|9~L-87}U z$%r3pkZIJmhQUD+op2&)*J$HqO9!0WFfBe-6B|=l2P?5Du&t-Ct0O8hbYO&5axgos z=N{!&agSk&#T17ri}l=U?g^+4rz|lQ%r6maWh87$Gv8d*zYz;+g4I23!r3A;4^ZWxM? zf88(z&QMhAU`wS?<|Q5sYfgh1Sn4oA*w+(GUT zrW`Qkh$$yf#Uv3FQRG=_JiW$bCysIN4EO$V?p^LZOu1mn6;p1)`*IOL@#x|4IL$%0 zekWT$;m&gBFy(=%k(e4a#3c07bdtMBSa6?nUkC@aKwY`Ezy?iffckdh7gsu(TR6a2Ra=3Hfl+=D;DW1~{AElBX)wJ&lbz zU6Zcql#MNC=jbxb`TBkK4m`~>eNMvWNtU-L%U5SWB@wiQQ$IVp-uDE$k$_Eki@sY2G#ar|3+MQ!L=tj}=K!1{+JtVO zA{effvCwefJg@0}O5tXF0+^j4JQ5Chf!&vVql1UN4;v=))lf?U8!7w0k8@(|xDsLO zGEWjR!Unz+;6sEF3OLC=9lnE83}3xzAmA`@!a(#8_rL+-OW-RvtKq2ZwZwWj6?-$B zi2XEt-{v(q*7*c{+vYRk3po4tD>$`Q4kya`k|FTznmBSCnL(<_GO_}`RHGrA;R`h# zbB_OupHFuu+BbG-hbHFS*OL1IMolhls6*TVD~A zfzQH~NnO6-z>(Pm*5h05J8l>E1NWoRhInJj2h0ejV7Bnv#QjX|=YHj`!YK^3`8uI9klua=`4I_hC?zqP z7%$lf-Bo`zXC6^?Zr6XX~Um;DFrb^SLnXtN@Bh7;^$kf3K zbAwbTZIKG}ZA zL!Xqr58rHoq^s;Ke6{5Qe7EHbNSb~HxjTE-6TZRH$S!5qu+Ols!M9W(T48@?Z-FOH zgXawvmK((da$#Hq7X?0R5_p-JoQlhbFQ*i9C0v8i{kjLfm+}bs2U}s@-OC;3j&W~s z@4#13-setpXW$Dc-*DGK)MOSm76BIF7Go^3EpjdLEeb7)ElMoPEGjITE%X+XEZQtO zEGAnREP5=aTCB8q(c-wp_m)Cms={iv)qJZ3R*S3_TitK9+-il@qgLyzHd*bldc*2X ztGBI=TfJv>(&~My(^hA!KDIh*^^?^vR#&aASzWjK)9R+xtr6r1Y6LSvIwE{T$q0VL zsu71q{A|ry2U(|B8?0wqFSK54z07)x^)Bl}*2k?sw7y_{(fSMP@2sy`U$_24PRSkQ z?s7kQxI9~4ByW??kv?m2a2tknfVeAU`C3Lw-trR{p8{g8Xaw75TUF>+&1& zzidbw%7(FVvhlU?v+=hHw8^w7v1zu^+f1@)v+1yzY-6zLv6*T!-R6FqB{mP)EVWr? zv%=<4o5yTc+dOHr)@HxWn>L@?{A^3wy4Z%AWOvx^h}|1@Z`z%(J7xEQ-G_D`+5KeiXJ24nVP9=u zYp=0ywePiGYQM&Qi~TnHr|fsyKW)Fye!u-o_OIB#W`D^3yaVB2>oCf}+rif%z#+&X z#39}x!C|yRqJ!F@-l5H*!(p<6!J)@ts>5`Lc@7I47CGGSu*BgZhouh79JV{W>2TfA z!!g(~%rU}I;TYo>>zL}8;h5!^ zo#LHFJB@W3@09G6?^NWZb}DnKaH?`@cADta>eS&h*~#FcY49;HK#Y6-gJ7~>A2H-PUoC1I(^~vrPCFs@0@;c`pKDgwsLlL9_8%i z?Bg8h9PAwG9PXUxobFugT<%=yTzvm+ zzvle8OSsDzmqM3fmlBsUmkO6Eml~HkmwJ~5mnN6#F1;?Z;k?y3E^}Sxxh!k`-JUH7}b;rfp2 zyRIi(PrIIR{n+)a>zA%qTz_}H9*Fb&uxR-Cbuna+uZiK?RR_0?G?Az+zz?D?snAenA>T$i*7gFZQR4$bKJ|_ z_3l&L?{UZO_qyNbKHvQz_oeR3+*i0i>i(GfbM9x|zwxm3aP#o@i1bMJsPJg^nBlR) z<57>tJXU)=>9N*htH(}{r#+tWc-G@JkHa2EJdSz1)j> z(rctZq(yhEeyA+C1vLQRhZo8uint zUq)RWbAx_G*IDm=$~W_V_KW_#v(sy$0R%RQ?+Ydm$HEuIrSTRo?H zF7RCBdB5ip&xbshdM@)^;knUsv*%XN?VdY5pZ472`Ml=~o)H(e|ZsJ z$ji#h+RMhv&db5e$;-t{=~eEv+-s}XL9e&Gj(ff5b<*pc*Ee22dHv#b)$5wqU*3c_ z@}|8d-k#oZ-fHi9Z>@KOca!&I?=EkHcaQgM@5SEByjOTX>iwAaYVVESo4vPsZ};Be zz03PW?<3w9e55{dA2%Ogp8%gApAer|pH!bTpLCy0pBf*HPlHdB&jgs z;q$c59-rrY4*I;~bIRufpAUUL_W9fw`AU8LeWQJqzVW_ee8>7``{wx;_!j$Wd|Q1x zd?))FeE0jl>3h?U^Rx6D;b-IL>gVC->F4b?+Aq^D+b_?r&`<4G>Nm@8k>3M;5Be?j zTkf~gZ-d`Xzs;pGGIi2Jiso%F~B*%FCZ`= zI3O%wbU%X_O~5Y!zXeFflmhR5BxOnS`Zy1 z4dQ~Vf~vAY zK`Vn+2R#|IHfUqemZ0rHJA(EGy&CjR(0f6rf=&m06!b~Z4?({N-3%szsbD79HrOfH zCD<+4GuS8CKR7U05j-Y1D>yH>Ah;;FEVwebCb%xRGkALNyx{r43xXE~KOX#a@SDMx zg1-v>CiuJHA4Av>+Ypx!zmTkul8~~H%8;56O-Ogh)Q}k=Gec&F@F5FB9twFRWJSoT zkjF!|glrGl8L~U%<&alH4uu>EITdm`MYs623irXZY^$J>lmfoFbAUY9bztSRJuGVpGJ{h^HdBQHi?io6{8YvgZ{zeoNVl^InMRU1_w)fhD)N*~n~)e|)> zsyAwO6d$!c>SG0~P$K812l1}KA-VaiBlv=VBpl!?mm$`m;J zBukm2%vTmEOO)lxDrK#*UfHOepwug0R=%b@tURiGQ~8eaJ>@CoY2`=Cv&!?zi^@yN zuasAm-z$Go{;IsD{6l#&mWZWdC9!O*Wvq3qZLCAAbF5qJ$XKsf-`IfI;MlO($XGab zB{m^;Ol($cc5H5JLG04lHL>er*T-&(YmV!Vn-VuIt~c&j-08S8aUaK>iys*u5+4>H z0S6b)ieD6efBcg8hvVOiKNo*K{V)$N-zI#Y z@MFR+qmxGGjV>5nG`eK;&e8ivzcl)l(FeznV@8aTkFg!&Fs5dVe$1pXZDTse>=^Un zm;+;89`o9mn~7YaW#WiLo5Z@rNr`QV9f@6udlFwwJeYVm@#t8uvEgGQ$127u$4(tP zckH~e3&t)UduHsVv6shwJ@(si5#tiajT@IVE_K|3am&Z89JgxR>T#cryE5*(aX*av zd3@aXwDB3^v&QEnp(N`hnWLsghUx-%`we6%{9#-1kU$P6ySDnp)O zm*JQ(D#JU&FC!o$I3qM8Gow9YX2yn$9T_iXyq<9~1 z#-ABCGo3TXXXa)0WX{foV78Zp_@7xjXaO%;z)rW$w>BoOvSiZ06UQ zKWARe{5|u}%)hdvSzMM?mUWhGmVK5-R$!JQYkXE#R!&xaR#8?-R&7>&R%4bft0ik< zmLZGJT9maiYi(9v)`qN2SzEGpXFZ#>H)~(kfvlIaj%K}=^-~0YJ+N%YM1H-)oZHbst;8ksXkGiQ=M0RrMjZ}UiG8u7u8kOU)j=ZdA3Kk ze|AuIXm&)lB702sxa{QY)a;Dxtn8xfs_f?M&TK>WlYT z%ACh?p2*phvo+_boSiwlbN1vM$T^a8D(7s@r#TmLF6MlZ^Igu5Iltsw&G|j&k6bF( zI@dKfAU85sksFg6n;V~-oST-LnXAgp&CSm(&uz+`oI4|TX721=bgwqm3KPtOx|aC-{f7(C-PA~oiE9k<=f=j=R4)Q20B!6}On*6@}jrm*hcjrHwzc+th z{(=0L^N;4A$p19|a{f2@-{t?9|4aUj{96U6fG&_0um!dSqY8ox;tG-qQVTK)R0X*O zr3DoQ)djT$^#u(DlM1F4%q@7h;IV=y3f2~^FW6MDtKgY}=L+@~yjXCc;Prx&1?LOC zEx1~6t>AjWp9MDyC53FEW#NcIn?k!n_rjpU=)$BzRbftHUSUCDQDIeKZDD<3L!qv) zxv;ZvX5qZTM+#RLt|{y*+*r7!aChOeg?kJ46&@&jx$tP=`-PttUMc*w@LJ&?g*V}- zKeouS$hyd;$iB$2XjD;1QA|;CQFc*YQDKp~sH{j+)KH`=YA%{s)LPV2G`nbV(Xyfy zMUNIeR3lsZryq7GL_s-x9P^*D95xX>s5bL{aJOhnya>`wyYje?Nses9abG#9bFw;olu=ponD<)ol~7( zU07XR-CT{Uw^Z-0e!2Q+^_$i2RKHt&w)$N4r_~p#zpMVe`j6@x)qm9xHK@j_MqXoA z<51&N<67fh<5v?{6H*ga6JL{FlUtKtQ&>}6Q(03}qp4}AX{woA(^X@r>8Y7hb8pSu znt3%3)~v4CShKliYt8nW9W~F_?5jCY^GeOZn!`2k)SRjLtmccFFKfQ4xmxpE&9$2A zwUSz!TKih3TGv{STF=^$+VI+_+UVNY+JxFMwdJ+bYxmZ^Rr^KlceOv({!)9jj;OP$ zv#)cibFK5J^Q{Z03$6>Ri>Oo8)z{6eTUfWKZgJfMb&u6OUbnjL$-2#T&(^(E_iEjt zx+8VR>Q2^uPPcn%^|H>MiRX>YeIc>fP!+>iz43>O<=z>J{}d^<(Q*^~Lq2^%eD1^}715dP9A8 z{gnE9>zC9&RKK)-S^bLoN9)(uZ>rx~zrB7({nPc&)bFo8x~eot@5A=b`h|`RM#~;kqc@IQWubv945Cp{v$4=ybXk z-6Z(@))d_=-8|ir=9?`<3)|w)Saj63#@n4JoH|X3G JYyu>!7XWwg3G)B| diff --git a/TaskExplorer.xcodeproj/xcuserdata/patrickw.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/TaskExplorer.xcodeproj/xcuserdata/patrickw.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index e5da73d..ce427b5 100644 --- a/TaskExplorer.xcodeproj/xcuserdata/patrickw.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/TaskExplorer.xcodeproj/xcuserdata/patrickw.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -6,7 +6,7 @@ @@ -74,11 +74,11 @@ ignoreCount = "0" continueAfterRunningActions = "No" filePath = "ItemView.m" - timestampString = "461741382.460449" + timestampString = "462061810.500141" startingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807" - startingLineNumber = "191" - endingLineNumber = "191" + startingLineNumber = "267" + endingLineNumber = "267" landmarkName = "createLoadedItemView()" landmarkType = "7"> @@ -89,14 +89,14 @@ shouldBeEnabled = "No" ignoreCount = "0" continueAfterRunningActions = "No" - filePath = "ItemView.m" - timestampString = "461741382.460449" + filePath = "AppDelegate.m" + timestampString = "462091427.171131" startingColumnNumber = "9223372036854775807" endingColumnNumber = "9223372036854775807" - startingLineNumber = "213" - endingLineNumber = "213" - landmarkName = "createLoadedItemView()" - landmarkType = "7"> + startingLineNumber = "1593" + endingLineNumber = "1593" + landmarkName = "-refreshTasks:" + landmarkType = "5"> + startingLineNumber = "323" + endingLineNumber = "323" + landmarkName = "-refresh" + landmarkType = "5"> + + + + + + + + + + + + + + + + + + + + diff --git a/TaskExplorer.xcodeproj/xcuserdata/patrickw.xcuserdatad/xcschemes/TaskExplorer.xcscheme b/TaskExplorer.xcodeproj/xcuserdata/patrickw.xcuserdatad/xcschemes/TaskExplorer.xcscheme index ee52d29..8d3ff25 100644 --- a/TaskExplorer.xcodeproj/xcuserdata/patrickw.xcuserdatad/xcschemes/TaskExplorer.xcscheme +++ b/TaskExplorer.xcodeproj/xcuserdata/patrickw.xcuserdatad/xcschemes/TaskExplorer.xcscheme @@ -59,6 +59,26 @@ + + + + + + + + - + diff --git a/UI/FlaggedItems.xib b/UI/FlaggedItems.xib index 479a8f9..fd25648 100644 --- a/UI/FlaggedItems.xib +++ b/UI/FlaggedItems.xib @@ -17,7 +17,7 @@ - + diff --git a/UI/SearchWindow.xib b/UI/SearchWindow.xib index 5b4ad8b..020d610 100644 --- a/UI/SearchWindow.xib +++ b/UI/SearchWindow.xib @@ -9,6 +9,7 @@ + @@ -241,7 +242,7 @@ - + @@ -292,6 +293,7 @@ + @@ -401,13 +403,16 @@ + + + -