├── Telepath ├── en.lproj │ ├── InfoPlist.strings │ └── Credits.rtf ├── listen.wav ├── fez-chime.mp3 ├── Fonts │ ├── OpenSans-Light.ttf │ ├── OpenSans-Regular.ttf │ └── OpenSans-Semibold.ttf ├── Images.xcassets │ ├── WebcamDefaultImage.imageset │ │ ├── webcam.png │ │ └── Contents.json │ └── AppIcon.appiconset │ │ └── Contents.json ├── Telepath-Prefix.pch ├── TPWindow.h ├── TPTrackerLight.h ├── main.m ├── Telepath.xcdatamodeld │ ├── .xccurrentversion │ └── Telepath.xcdatamodel │ │ └── contents ├── TPHUDWindowController.h ├── TPTrackerMouse.h ├── TPTrackerWindow.h ├── TPTrackerBrunchBuilds.h ├── TPTrackerWorkHours.h ├── TPTrackerKeyboard.h ├── TPTrackerHappiness.h ├── TPTrackerEmail.h ├── TPTrackerScreen.h ├── TPAppDelegate.h ├── TPWindow.m ├── TPUtilities.h ├── TPTrackerCamera.h ├── TPLightSensor.h ├── TPTrackerGitHub.h ├── TPTracker.h ├── TPTrackerLight.m ├── Telepath-Info.plist ├── TPTrackerTrello.h ├── TPTrackerBrunchBuilds.m ├── ImageSnap.h ├── TPUtilities.m ├── TPTrackerHappiness.m ├── TPTrackerMouse.m ├── TPTrackerWorkHours.m ├── TPTrackerEmail.m ├── TPTrackerScreen.m ├── TPTrackerTrello.m ├── TPLightSensor.m ├── TPTrackerWindow.m ├── TPTrackerCamera.m ├── TPTrackerGitHub.m ├── TPAppDelegate.m ├── TPTrackerKeyboard.m ├── TPTracker.m ├── TPHUDWindowController.m └── ImageSnap.m ├── TelepathTests ├── en.lproj │ └── InfoPlist.strings ├── TelepathTests-Info.plist └── TelepathTests.m ├── run-it-forever.sh ├── .gitignore ├── LICENSE ├── README.md └── Telepath.xcodeproj └── project.pbxproj /Telepath/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /TelepathTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Telepath/listen.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nwinter/telepath-logger/HEAD/Telepath/listen.wav -------------------------------------------------------------------------------- /Telepath/fez-chime.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nwinter/telepath-logger/HEAD/Telepath/fez-chime.mp3 -------------------------------------------------------------------------------- /Telepath/Fonts/OpenSans-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nwinter/telepath-logger/HEAD/Telepath/Fonts/OpenSans-Light.ttf -------------------------------------------------------------------------------- /Telepath/Fonts/OpenSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nwinter/telepath-logger/HEAD/Telepath/Fonts/OpenSans-Regular.ttf -------------------------------------------------------------------------------- /Telepath/Fonts/OpenSans-Semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nwinter/telepath-logger/HEAD/Telepath/Fonts/OpenSans-Semibold.ttf -------------------------------------------------------------------------------- /Telepath/Images.xcassets/WebcamDefaultImage.imageset/webcam.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nwinter/telepath-logger/HEAD/Telepath/Images.xcassets/WebcamDefaultImage.imageset/webcam.png -------------------------------------------------------------------------------- /run-it-forever.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | until /Applications/Telepath.app/Contents/MacOS/Telepath; do 3 | echo "Telepath crashed with exit code $?. Respawning.." >&2 4 | sleep 1 5 | done 6 | -------------------------------------------------------------------------------- /Telepath/Telepath-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | #import 9 | #endif 10 | -------------------------------------------------------------------------------- /Telepath/TPWindow.h: -------------------------------------------------------------------------------- 1 | // 2 | // TPWindow.h 3 | // Telepath 4 | // 5 | // Created by Nick Winter on 9/7/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TPWindow : NSWindow 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Telepath/TPTrackerLight.h: -------------------------------------------------------------------------------- 1 | // 2 | // TPTrackerLight.h 3 | // Telepath 4 | // 5 | // Created by Nick Winter on 8/31/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TPTrackerLight : NSObject 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Telepath/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Telepath 4 | // 5 | // Created by Nick Winter on 8/30/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, const char * argv[]) 12 | { 13 | return NSApplicationMain(argc, argv); 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | *.xcworkspace 13 | !default.xcworkspace 14 | xcuserdata 15 | profile 16 | *.moved-aside 17 | DerivedData 18 | .idea/ 19 | -------------------------------------------------------------------------------- /Telepath/Telepath.xcdatamodeld/.xccurrentversion: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _XCCurrentVersionName 6 | Telepath.xcdatamodel 7 | 8 | 9 | -------------------------------------------------------------------------------- /Telepath/TPHUDWindowController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TPHUDWindowController.h 3 | // Telepath 4 | // 5 | // Created by Nick Winter on 8/30/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TPHUDWindowController : NSWindowController 12 | 13 | @end -------------------------------------------------------------------------------- /Telepath/Images.xcassets/WebcamDefaultImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "webcam.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | } 17 | } -------------------------------------------------------------------------------- /Telepath/TPTrackerMouse.h: -------------------------------------------------------------------------------- 1 | // 2 | // TPTrackerMouse.h 3 | // Telepath 4 | // 5 | // Created by Nick Winter on 8/31/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TPTrackerMouse : NSObject 12 | @property (readonly) NSInteger currentEvents; 13 | @property (readonly) NSInteger totalEvents; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Telepath/TPTrackerWindow.h: -------------------------------------------------------------------------------- 1 | // 2 | // TPTrackerWindow.h 3 | // Telepath 4 | // 5 | // Created by Nick Winter on 8/31/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TPTrackerWindow : NSObject 12 | @property (readonly) NSInteger currentEvents; 13 | @property (readonly) NSInteger totalEvents; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Telepath/Telepath.xcdatamodeld/Telepath.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Telepath/TPTrackerBrunchBuilds.h: -------------------------------------------------------------------------------- 1 | // 2 | // TPTrackerBrunchBuilds.h 3 | // Telepath 4 | // 5 | // Created by Nick Winter on 9/8/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TPTrackerBrunchBuilds : NSObject 12 | @property (readonly) NSInteger currentEvents; 13 | @property (readonly) NSInteger totalEvents; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Telepath/TPTrackerWorkHours.h: -------------------------------------------------------------------------------- 1 | // 2 | // TPTrackerWorkHours.h 3 | // Telepath 4 | // 5 | // Created by Nick Winter on 9/9/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TPTrackerWorkHours : NSObject 12 | @property (readonly) float sessionHours; 13 | @property (readonly) float dayHours; 14 | @property (readonly) float weekHours; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /Telepath/TPTrackerKeyboard.h: -------------------------------------------------------------------------------- 1 | // 2 | // TPTrackerKeyboard.h 3 | // Telepath 4 | // 5 | // Created by Nick Winter on 8/31/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TPTrackerKeyboard : NSObject 12 | @property (nonatomic, strong) NSArray *modifierKeys; 13 | @property (readonly) NSInteger currentEvents; 14 | @property (readonly) NSInteger totalEvents; 15 | @end 16 | -------------------------------------------------------------------------------- /Telepath/TPTrackerHappiness.h: -------------------------------------------------------------------------------- 1 | // 2 | // TPTrackerHappiness.h 3 | // Telepath 4 | // 5 | // Created by Nick Winter on 11/8/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TPTrackerHappiness : NSObject 12 | /// We'll randomly ping somewhen around this interval (seconds). 13 | @property (nonatomic) NSTimeInterval pingInterval; 14 | 15 | - (id)initWithPingInterval:(NSTimeInterval)pingInterval; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /Telepath/TPTrackerEmail.h: -------------------------------------------------------------------------------- 1 | // 2 | // TPTrackerEmail.h 3 | // Telepath 4 | // 5 | // Created by Nick Winter on 9/8/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | /* 10 | Oh whatever, someone else can mod this one if they want to use it. I tried to use PubSub framework, but it is buggy and didn't work. 11 | */ 12 | 13 | #import 14 | 15 | @interface TPTrackerEmail : NSObject 16 | @property (readonly) NSInteger unreadEmails; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Telepath/TPTrackerScreen.h: -------------------------------------------------------------------------------- 1 | // 2 | // TPTrackerScreen.h 3 | // Telepath 4 | // 5 | // Created by Nick Winter on 9/16/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TPTrackerScreen : NSObject 12 | /// We'll take a camera screenshot at this interval (seconds). Set at same time as Camera's recording interval is set to be in sync. 13 | @property (nonatomic) NSTimeInterval recordingInterval; 14 | 15 | - (id)initWithRecordingInterval:(NSTimeInterval)recordingInterval; 16 | 17 | @end 18 | 19 | 20 | -------------------------------------------------------------------------------- /Telepath/en.lproj/Credits.rtf: -------------------------------------------------------------------------------- 1 | {\rtf0\ansi{\fonttbl\f0\fswiss Helvetica;} 2 | {\colortbl;\red255\green255\blue255;} 3 | \paperw9840\paperh8400 4 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural 5 | 6 | \f0\b\fs24 \cf0 Engineering: 7 | \b0 \ 8 | Some people\ 9 | \ 10 | 11 | \b Human Interface Design: 12 | \b0 \ 13 | Some other people\ 14 | \ 15 | 16 | \b Testing: 17 | \b0 \ 18 | Hopefully not nobody\ 19 | \ 20 | 21 | \b Documentation: 22 | \b0 \ 23 | Whoever\ 24 | \ 25 | 26 | \b With special thanks to: 27 | \b0 \ 28 | Mom\ 29 | } 30 | -------------------------------------------------------------------------------- /Telepath/TPAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // TPAppDelegate.h 3 | // Telepath 4 | // 5 | // Created by Nick Winter on 8/30/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TPAppDelegate : NSObject 12 | 13 | @property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator; 14 | @property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel; 15 | @property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext; 16 | 17 | - (IBAction)saveAction:(id)sender; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Telepath/TPWindow.m: -------------------------------------------------------------------------------- 1 | // 2 | // TPWindow.m 3 | // Telepath 4 | // 5 | // Created by Nick Winter on 9/7/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | #import "TPWindow.h" 10 | 11 | @implementation TPWindow 12 | 13 | - (id)init 14 | { 15 | self = [super init]; 16 | if (self) { 17 | [self setContentMaxSize:NSMakeSize(2560, 240)]; 18 | } 19 | return self; 20 | } 21 | 22 | - (BOOL)canBecomeKeyWindow { 23 | return YES; 24 | } 25 | 26 | - (BOOL)canBecomeMainWindow { 27 | return YES; 28 | } 29 | 30 | - (BOOL)isMovableByWindowBackground { 31 | return YES; 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /Telepath/TPUtilities.h: -------------------------------------------------------------------------------- 1 | // 2 | // TPUtilities.h 3 | // Telepath 4 | // 5 | // Created by Nick Winter on 8/31/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /// Shorthand: seconds since epoch. 12 | double now(void); 13 | 14 | /// Shorthand: serialize object to JSON. 15 | NSString *JSONRepresentation(id object); 16 | 17 | /// Shorthand: deserialize dictionary from JSON. 18 | NSDictionary *dictionaryFromJSON(NSData *json); 19 | 20 | /// Shorthand: deserialize array to JSON. 21 | NSArray *arrayFromJSON(NSData *json); 22 | 23 | /// Could've sworn this was built in somewhere just a second ago. 24 | NSString *base64ForData(NSData *theData); -------------------------------------------------------------------------------- /Telepath/TPTrackerCamera.h: -------------------------------------------------------------------------------- 1 | // 2 | // TPTrackerCamera.h 3 | // Telepath 4 | // 5 | // Created by Nick Winter on 8/31/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @interface TPTrackerCamera : NSObject 13 | /// We'll take a camera screenshot at this interval (seconds). 14 | @property (nonatomic) NSTimeInterval recordingInterval; 15 | 16 | /// We'll update the camera preview at this interval (seconds). 17 | @property (nonatomic) NSTimeInterval previewInterval; 18 | 19 | /// Our camera images will be cropped to this much. 1.0 for no crop. Default 0.6. 20 | @property float cropRatio; 21 | 22 | - (id)initWithRecordingInterval:(NSTimeInterval)recordingInterval andPreviewInterval:(NSTimeInterval)previewInterval; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /TelepathTests/TelepathTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | net.nickwinter.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /TelepathTests/TelepathTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // TelepathTests.m 3 | // TelepathTests 4 | // 5 | // Created by Nick Winter on 8/30/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TelepathTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation TelepathTests 16 | 17 | - (void)setUp 18 | { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown 24 | { 25 | // Put teardown code here. This method is called after the invocation of each test method in the class. 26 | [super tearDown]; 27 | } 28 | 29 | - (void)testExample 30 | { 31 | XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__); 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /Telepath/TPLightSensor.h: -------------------------------------------------------------------------------- 1 | // 2 | // TPLightSensor.h 3 | // Telepath 4 | // 5 | // Created by Nick Winter on 8/30/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #define TP_NOTIFICATION_LIGHT_CHANGED @"tp_light_changed" 12 | 13 | @interface TPLightSensor : NSObject 14 | @property (readonly) BOOL started; 15 | @property (readonly) float left; 16 | @property (readonly) float right; 17 | @property (readonly) float brightness; // dB 0-100 (100 is shining flashlight into sensor or more) 18 | @property (readonly) float typicalBrightness; /// Indoors, indirect partly cloudy 19 | @property (readonly) float lastBrightness; // brightness level before the last change 20 | @property (nonatomic) double updateInterval; /// Default: 0.1, which is twice as fast as the sensor can update on Nick's 2010 MBP 21 | 22 | - (BOOL)start; 23 | - (void)stop; 24 | 25 | @end 26 | 27 | extern NSString * const TPLightChanged; -------------------------------------------------------------------------------- /Telepath/TPTrackerGitHub.h: -------------------------------------------------------------------------------- 1 | // 2 | // TPTrackerGitHub.h 3 | // Telepath 4 | // 5 | // Created by Nick Winter on 9/8/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | /* 10 | In order to use GitHub tracking (single-repo only for now), create a file called ~/Dropbox/code/telepath_github_token.txt with five lines: 11 | gitHubUserName (ex: nwinter) 12 | gitHubRepo (ex: aether) 13 | gitHubToken (ex: 4d712b826661a8c8e3280da981e99e8f30285eaf) 14 | 15 | To get a GitHub authorization token, just do this: https://help.github.com/articles/creating-an-access-token-for-command-line-use 16 | */ 17 | 18 | #import 19 | 20 | @interface TPTrackerGitHub : NSObject 21 | @property (readonly) NSInteger currentCommits; 22 | @property (readonly) NSInteger totalCommits; 23 | @property (readonly) NSInteger currentAdditions; 24 | @property (readonly) NSInteger totalAdditions; 25 | @property (readonly) NSInteger currentDeletions; 26 | @property (readonly) NSInteger totalDeletions; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Nick Winter 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Telepath/TPTracker.h: -------------------------------------------------------------------------------- 1 | // 2 | // TPTracker.h 3 | // Telepath 4 | // 5 | // Created by Nick Winter on 8/31/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | extern NSString * const TPActivityClearTotals; 12 | extern NSString * const TPActivityAny; 13 | extern NSString * const TPActivityKeyboard; 14 | extern NSString * const TPActivityKeyboardVeryBad; 15 | extern NSString * const TPActivityMouse; 16 | extern NSString * const TPActivityWindow; 17 | extern NSString * const TPActivityLight; 18 | extern NSString * const TPActivityCamera; 19 | extern NSString * const TPActivityGitHub; 20 | extern NSString * const TPActivityTrello; 21 | extern NSString * const TPActivityBrunchBuild; 22 | extern NSString * const TPActivityEmail; 23 | extern NSString * const TPActivityWorkHours; 24 | extern NSString * const TPActivityScreen; 25 | extern NSString * const TPActivityHappiness; 26 | 27 | @interface TPTracker : NSObject 28 | 29 | @property (readonly) NSInteger currentEvents; 30 | @property (readonly) NSInteger totalEvents; 31 | @property NSTimeInterval cameraRecordingInterval; 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /Telepath/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /Telepath/TPTrackerLight.m: -------------------------------------------------------------------------------- 1 | // 2 | // TPTrackerLight.m 3 | // Telepath 4 | // 5 | // Created by Nick Winter on 8/31/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | #import "TPTrackerLight.h" 10 | #import "TPUtilities.h" 11 | #import "TPLightSensor.h" 12 | #import "TPTracker.h" 13 | 14 | @interface TPTrackerLight () 15 | 16 | @property TPLightSensor *lightSensor; 17 | @property NSMutableArray *events; 18 | 19 | @end 20 | 21 | @implementation TPTrackerLight 22 | 23 | - (id)init 24 | { 25 | self = [super init]; 26 | if (self) { 27 | self.lightSensor = [TPLightSensor new]; 28 | [self.lightSensor start]; 29 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onLightChanged:) name:TPLightChanged object:self.lightSensor]; 30 | } 31 | return self; 32 | } 33 | 34 | - (void)dealloc 35 | { 36 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 37 | } 38 | 39 | - (void)onLightChanged:(NSNotification *)note { 40 | NSMutableArray *event = [NSMutableArray array]; 41 | [event addObject:@(now())]; 42 | [event addObject:@"lightChanged"]; 43 | [event addObject:[NSNumber numberWithFloat:self.lightSensor.brightness]]; 44 | [[NSNotificationCenter defaultCenter] postNotificationName:TPActivityLight object:self userInfo:@{@"light": @(self.lightSensor.brightness), @"event": event}]; 45 | } 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /Telepath/Telepath-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | net.nickwinter.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 0.0.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSApplicationCategoryType 26 | public.app-category.productivity 27 | LSMinimumSystemVersion 28 | ${MACOSX_DEPLOYMENT_TARGET} 29 | NSHumanReadableCopyright 30 | Copyright © 2013 Nick Winter. All rights reserved. 31 | NSMainNibFile 32 | MainMenu 33 | ATSApplicationFontsPath 34 | Fonts 35 | NSPrincipalClass 36 | NSApplication 37 | 38 | 39 | -------------------------------------------------------------------------------- /Telepath/TPTrackerTrello.h: -------------------------------------------------------------------------------- 1 | // 2 | // TPTrackerTrello.h 3 | // Telepath 4 | // 5 | // Created by Nick Winter on 9/8/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | /* 10 | In order to use Trello tracking, create a file called ~/Dropbox/code/telepath_trello_token.txt with six lines: 11 | trelloUserName (ex: nwinter) 12 | trelloBoardIDs, separated, by, commaSpace (ex: 4e3f29a024e0c004633abdc3, c8e3280da981e99e8f30285e) 13 | trelloDoneListIDs, separated, by, commaSpace 14 | trelloApplicationKey (ex: 4d712b826661a8c8e3280da981e99e8f) 15 | trelloApplicationSecret (ex: 4d712b826661a8c8e3280da981e99e8f30285eaf4d712b826661a8c8e3280da981) 16 | trelloToken (ex: 4d712b826661a8c8e3280da981e99e8f30285eaf4d712b826661a8c8e3280da981) 17 | 18 | To get the first two, sign into Trello to see your username, and also grab the board ID (like 4e3f29a024e0c004633abdc3) for each board you care about. They said it should be part of the URL, but it wasn't for me (I got some shorter base64 string instead of hex), so I used https://trello.com/1/members/my/boards?key=substitutewithyourapplicationkey&token=substitutewithyourapplicationtoken 19 | 20 | Then to get the lists which represent "Done" items for your boards, for each board, check https://trello.com/1/boards/substitutewithsomeboardid/lists?key=substitutewithyourapplicationkey&token=substitutewithyourapplicationtoken 21 | 22 | To get the application key and application secret, go to https://trello.com/1/appKey/generate 23 | 24 | To get an indefinite read-only authorization token, go to https://trello.com/1/authorize?key=substitutewithyourapplicationkey&name=Telepath&expiration=never&response_type=token 25 | */ 26 | 27 | #import 28 | 29 | @interface TPTrackerTrello : NSObject 30 | @property (readonly) NSInteger currentTrellosSlain; 31 | @property (readonly) NSInteger totalTrellosSlain; 32 | @property (readonly) NSInteger trellosAlive; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | telepath-logger 2 | =============== 3 | 4 | A happy Mac keylogger for Quantified Self purposes. It also now serves as a time lapse heads-up-display thing. Really only I use this, so you'll want to either just use its code as inspiration or heavily tweak it for your purposes. 5 | 6 | ![Time lapse HUD screengrab example.](https://dl.dropboxusercontent.com/u/138899/GitHub%20Wikis/telepath-logger-example-screencrab.jpg) 7 | 8 | See also these blog posts: 9 | 10 | [Telepath Logger Now Open Source](http://blog.nickwinter.net/telepath-logger-now-open-source) 11 | 12 | [Upcoming Maniac Week](http://blog.nickwinter.net/upcoming-maniac-week) 13 | 14 | [The 120-Hour Workweek - Epic Coding Time-Lapse](http://blog.nickwinter.net/the-120-hour-workweek-epic-coding-time-lapse) 15 | 16 | ============== 17 | In case you do want to try running this yourself, here are some quickly-thrown-together instructions. 18 | 19 | 1. Clone the Telepath repository. 20 | 1. Open Telepath.xcodeproj in Xcode 21 | 1. Hit Run. It should open up a Telepath window that's way too big and in the wrong place. 22 | 1. Change the 2560 size to the width of the screen you want to use [here](https://github.com/nwinter/telepath-logger/blob/master/Telepath/TPWindow.m#L17-L17). 23 | 1. Find the main window in the Interface Builder for TPHUDWindowController.xib, select the Window, turn on the righthand sidebar, switch to the size tab, and update its size and position to be the place that you want. You might also want to set it to be a Textured Window so that you can drag it around. 24 | 1. Comment in or out the TPTracker subclasses that you do/don't want to use [here](https://github.com/nwinter/telepath-logger/blob/master/Telepath/TPTracker.m#L87-L106). Some of these require additional configuration, so you'll probably want to turn some of them off if they don't apply to you. 25 | 26 | If it seems to run okay and records what you want it to record, then you can build it into an app and set that app to autostart when you start OS X. -------------------------------------------------------------------------------- /Telepath/TPTrackerBrunchBuilds.m: -------------------------------------------------------------------------------- 1 | // 2 | // TPTrackerBrunchBuilds.m 3 | // Telepath 4 | // 5 | // Created by Nick Winter on 9/8/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | #import "TPTrackerBrunchBuilds.h" 10 | #import "TPTracker.h" 11 | 12 | @interface TPTrackerBrunchBuilds () 13 | @property NSInteger previousEvents; 14 | @property (readwrite) NSInteger totalEvents; 15 | 16 | @end 17 | 18 | @implementation TPTrackerBrunchBuilds 19 | 20 | - (id)init 21 | { 22 | self = [super init]; 23 | if (self) { 24 | self.previousEvents = [[NSUserDefaults standardUserDefaults] integerForKey:@"previousBrunchBuildEvents"]; 25 | self.totalEvents = [[NSUserDefaults standardUserDefaults] integerForKey:@"totalBrunchBuildEvents"]; 26 | [[NSNotificationCenter defaultCenter] addObserverForName:TPActivityClearTotals object:nil queue:nil usingBlock:^(NSNotification *note) { 27 | [[NSUserDefaults standardUserDefaults] setObject:@(self.totalEvents) forKey:@"previousBrunchBuildEvents"]; 28 | self.previousEvents = self.totalEvents; 29 | }]; 30 | [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(onBrunchBuild:) name:@"net.nickwinter.Telepath.BrunchBuild" object:nil]; 31 | [self onBrunchBuild:nil]; 32 | } 33 | return self; 34 | } 35 | 36 | - (NSInteger)currentEvents { 37 | return self.totalEvents - self.previousEvents; 38 | } 39 | 40 | - (void)dealloc 41 | { 42 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 43 | [[NSDistributedNotificationCenter defaultCenter] removeObserver:self]; 44 | } 45 | 46 | - (void)onBrunchBuild:(NSNotification *)note { 47 | if(note) 48 | ++self.totalEvents; 49 | [[NSNotificationCenter defaultCenter] postNotificationName:TPActivityBrunchBuild object:self userInfo:@{@"totalEvents": @(self.totalEvents), @"currentEvents": @(self.currentEvents)}]; 50 | [[NSUserDefaults standardUserDefaults] setObject:@(self.totalEvents) forKey:@"totalBrunchBuildEvents"]; 51 | } 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /Telepath/ImageSnap.h: -------------------------------------------------------------------------------- 1 | // 2 | // ImageSnap.h 3 | // ImageSnap 4 | // 5 | // Created by Robert Harder on 9/10/09. 6 | // 7 | #import 8 | #import 9 | #include "ImageSnap.h" 10 | 11 | 12 | @interface ImageSnap : NSObject { 13 | 14 | QTCaptureSession *mCaptureSession; 15 | QTCaptureDeviceInput *mCaptureDeviceInput; 16 | QTCaptureDecompressedVideoOutput *mCaptureDecompressedVideoOutput; 17 | QTCaptureVideoPreviewOutput *mCaptureVideoPreviewOutput; 18 | CVImageBufferRef mCurrentImageBuffer; 19 | } 20 | 21 | 22 | /** 23 | * Returns all attached QTCaptureDevice objects that have video. 24 | * This includes video-only devices (QTMediaTypeVideo) and 25 | * audio/video devices (QTMediaTypeMuxed). 26 | * 27 | * @return autoreleased array of video devices 28 | */ 29 | +(NSArray *)videoDevices; 30 | 31 | /** 32 | * Returns the default QTCaptureDevice object for video 33 | * or nil if none is found. 34 | */ 35 | +(QTCaptureDevice *)defaultVideoDevice; 36 | 37 | /** 38 | * Returns the QTCaptureDevice with the given name 39 | * or nil if the device cannot be found. 40 | */ 41 | +(QTCaptureDevice *)deviceNamed:(NSString *)name; 42 | 43 | /** 44 | * Writes an NSImage to disk, formatting it according 45 | * to the file extension. If path is "-" (a dash), then 46 | * an jpeg representation is written to standard out. 47 | */ 48 | + (BOOL) saveImage:(NSImage *)image toPath: (NSString*)path; 49 | 50 | /** 51 | * Converts an NSImage to raw NSData according to a given 52 | * format. A simple string search is performed for such 53 | * characters as jpeg, tiff, png, and so forth. 54 | */ 55 | +(NSData *)dataFrom:(NSImage *)image asType:(NSString *)format; 56 | 57 | 58 | 59 | /** 60 | * Primary one-stop-shopping message for capturing an image. 61 | * Activates the video source, saves a frame, stops the source, 62 | * and saves the file. 63 | */ 64 | +(BOOL)saveSingleSnapshotFrom:(QTCaptureDevice *)device toFile:(NSString *)path; 65 | +(BOOL)saveSingleSnapshotFrom:(QTCaptureDevice *)device toFile:(NSString *)path withWarmup:(NSNumber *)warmup; 66 | +(BOOL)saveSingleSnapshotFrom:(QTCaptureDevice *)device toFile:(NSString *)path withWarmup:(NSNumber *)warmup withTimelapse:(NSNumber *)timelapse; 67 | 68 | -(id)init; 69 | -(void)dealloc; 70 | 71 | 72 | -(BOOL)startSession:(QTCaptureDevice *)device; 73 | -(NSImage *)snapshot; 74 | -(void)stopSession; 75 | 76 | 77 | @end 78 | -------------------------------------------------------------------------------- /Telepath/TPUtilities.m: -------------------------------------------------------------------------------- 1 | // 2 | // TPUtilities.m 3 | // Telepath 4 | // 5 | // Created by Nick Winter on 8/31/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | #import "TPUtilities.h" 10 | 11 | double now(void) { 12 | return [[NSDate date] timeIntervalSince1970]; 13 | } 14 | 15 | NSString *JSONRepresentation(id object) { 16 | NSError *error; 17 | NSString *json = [[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:object options:0 error:&error] encoding:NSUTF8StringEncoding]; 18 | if(error) 19 | NSLog(@"Error converting %@ to JSON: %@", object, [error localizedDescription]); 20 | return json; 21 | } 22 | 23 | NSDictionary *dictionaryFromJSON(NSData *json) { 24 | NSError *error; 25 | NSDictionary *d = [NSJSONSerialization JSONObjectWithData:json options:kNilOptions error:&error]; 26 | if(error) 27 | NSLog(@"Error converting %@ from JSON: %@", json, [error localizedDescription]); 28 | return d; 29 | } 30 | 31 | NSArray *arrayFromJSON(NSData *json) { 32 | NSError *error; 33 | NSArray *a = [NSJSONSerialization JSONObjectWithData:json options:kNilOptions error:&error]; 34 | if(error) 35 | NSLog(@"Error converting %@ from JSON: %@", json, [error localizedDescription]); 36 | return a; 37 | } 38 | 39 | //from: http://cocoadev.com/BaseSixtyFour 40 | NSString *base64ForData(NSData *theData) { 41 | const uint8_t* input = (const uint8_t*)[theData bytes]; 42 | NSInteger length = [theData length]; 43 | 44 | static char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 45 | 46 | NSMutableData* data = [NSMutableData dataWithLength:((length + 2) / 3) * 4]; 47 | uint8_t* output = (uint8_t*)data.mutableBytes; 48 | 49 | NSInteger i; 50 | for (i=0; i < length; i += 3) { 51 | NSInteger value = 0; 52 | NSInteger j; 53 | for (j = i; j < (i + 3); j++) { 54 | value <<= 8; 55 | 56 | if (j < length) { 57 | value |= (0xFF & input[j]); 58 | } 59 | } 60 | 61 | NSInteger theIndex = (i / 3) * 4; 62 | output[theIndex + 0] = table[(value >> 18) & 0x3F]; 63 | output[theIndex + 1] = table[(value >> 12) & 0x3F]; 64 | output[theIndex + 2] = (i + 1) < length ? table[(value >> 6) & 0x3F] : '='; 65 | output[theIndex + 3] = (i + 2) < length ? table[(value >> 0) & 0x3F] : '='; 66 | } 67 | 68 | return [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]; 69 | } -------------------------------------------------------------------------------- /Telepath/TPTrackerHappiness.m: -------------------------------------------------------------------------------- 1 | // 2 | // TPTrackerHappiness.m 3 | // Telepath 4 | // 5 | // Created by Nick Winter on 11/8/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | #import "TPTrackerHappiness.h" 10 | #import "TPTracker.h" 11 | 12 | @interface TPTrackerHappiness () 13 | @property NSTimer *pingTimer; 14 | @property BOOL started; 15 | 16 | /// Whether we've in screensaver (and might not want to play a noise if it's late at night). 17 | @property BOOL screensaver; 18 | 19 | @end 20 | 21 | @implementation TPTrackerHappiness 22 | 23 | - (id)initWithPingInterval:(NSTimeInterval)pingInterval { 24 | self = [super init]; 25 | if(self) { 26 | srand48(time(0)); 27 | self.pingInterval = pingInterval; // Starts ping. 28 | [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(onScreensaverStart:) name:@"com.apple.screensaver.didstart" object:nil]; 29 | [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(onScreensaverStop:) name:@"com.apple.screensaver.didstop" object:nil]; 30 | } 31 | return self; 32 | } 33 | 34 | - (void)setPingInterval:(NSTimeInterval)pingInterval { 35 | if(pingInterval == _pingInterval) return; 36 | _pingInterval = pingInterval; 37 | [self start]; 38 | } 39 | 40 | - (void)start { 41 | [self.pingTimer invalidate]; 42 | NSTimeInterval interval = (1 + 0.5 - drand48()) * self.pingInterval; // 0.5 - 1.5x the interval 43 | if(!self.started) 44 | interval -= 0.5 * self.pingInterval; // start early on first run, since it may have been a while since last ping 45 | self.pingTimer = [NSTimer scheduledTimerWithTimeInterval:interval target:self selector:@selector(ping:) userInfo:nil repeats:YES]; 46 | self.started = YES; 47 | } 48 | 49 | - (void)stop { 50 | [self.pingTimer invalidate]; 51 | self.pingTimer= nil; 52 | self.started = NO; 53 | } 54 | 55 | - (void)ping:(NSTimer *)timer { 56 | NSDateFormatter *timeOfDay = [[NSDateFormatter alloc] init]; 57 | [timeOfDay setDateFormat:@"HHmm"]; 58 | int currentTime = [[timeOfDay stringFromDate:[NSDate date]] intValue]; 59 | if(!self.screensaver || (830 < currentTime && currentTime < 2300)) { 60 | NSSound *chime = [NSSound soundNamed:@"fez-chime"]; 61 | [chime play]; 62 | } 63 | [[NSNotificationCenter defaultCenter] postNotificationName:TPActivityHappiness object:self userInfo:@{}]; 64 | [self start]; 65 | } 66 | 67 | - (void)onScreensaverStart:(NSNotification *)note { 68 | NSLog(@"Screensaver Start"); 69 | self.screensaver = YES; 70 | } 71 | 72 | - (void)onScreensaverStop:(NSNotification *)note { 73 | NSLog(@"Screensaver Stop"); 74 | self.screensaver = NO; 75 | } 76 | 77 | @end 78 | -------------------------------------------------------------------------------- /Telepath/TPTrackerMouse.m: -------------------------------------------------------------------------------- 1 | // 2 | // TPTrackerMouse.m 3 | // Telepath 4 | // 5 | // Created by Nick Winter on 8/31/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | #import "TPTrackerMouse.h" 10 | #import "TPUtilities.h" 11 | #import "TPTracker.h" 12 | 13 | @interface TPTrackerMouse () 14 | 15 | @property NSMutableArray *events; 16 | @property id eventMonitor; 17 | @property NSInteger previousEvents; 18 | @property (readwrite) NSInteger totalEvents; 19 | 20 | @end 21 | 22 | @implementation TPTrackerMouse 23 | 24 | - (id)init 25 | { 26 | self = [super init]; 27 | if (self) { 28 | NSInteger logMask = (NSLeftMouseDraggedMask|NSMouseMovedMask|NSLeftMouseDownMask|NSRightMouseDownMask|NSLeftMouseUpMask|NSRightMouseUpMask); 29 | self.eventMonitor = [NSEvent addGlobalMonitorForEventsMatchingMask:logMask handler:^(NSEvent *e) { [self onInputEvent:e]; }]; 30 | self.previousEvents = [[NSUserDefaults standardUserDefaults] integerForKey:@"previousMouseEvents"]; 31 | self.totalEvents = [[NSUserDefaults standardUserDefaults] integerForKey:@"totalMouseEvents"]; 32 | [[NSNotificationCenter defaultCenter] addObserverForName:TPActivityClearTotals object:nil queue:nil usingBlock:^(NSNotification *note) { 33 | [[NSUserDefaults standardUserDefaults] setObject:@(self.totalEvents) forKey:@"previousMouseEvents"]; 34 | self.previousEvents = self.totalEvents; 35 | }]; 36 | } 37 | return self; 38 | } 39 | 40 | - (NSInteger)currentEvents { 41 | return self.totalEvents - self.previousEvents; 42 | } 43 | 44 | - (void)dealloc 45 | { 46 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 47 | } 48 | 49 | -(void)onInputEvent:(NSEvent *)e { 50 | NSEventType t = [e type]; 51 | if(t == NSMouseMoved) 52 | [self onMouseEvent:e type:@"mouseMoved"]; 53 | else if(t == NSLeftMouseDown) 54 | [self onMouseEvent:e type:@"leftMouseDown"]; 55 | else if(t == NSLeftMouseUp) 56 | [self onMouseEvent:e type:@"leftMouseUp"]; 57 | else if(t == NSRightMouseDown) 58 | [self onMouseEvent:e type:@"rightMouseDown"]; 59 | else if(t == NSRightMouseUp) 60 | [self onMouseEvent:e type:@"rightMouseUp"]; 61 | else if(t == NSLeftMouseDragged) 62 | [self onMouseEvent:e type:@"leftMouseDragged"]; 63 | } 64 | 65 | - (void)onMouseEvent:(NSEvent *)e type:(NSString *)type { 66 | NSPoint p = [NSEvent mouseLocation]; 67 | NSMutableArray *event = [NSMutableArray array]; 68 | [event addObject:@(now())]; 69 | [event addObject:type]; 70 | [event addObject:@(p.x)]; 71 | [event addObject:@(p.y)]; 72 | [[NSNotificationCenter defaultCenter] postNotificationName:TPActivityMouse object:self userInfo:@{@"event": event, @"totalEvents": @(++self.totalEvents), @"currentEvents": @(self.currentEvents)}]; 73 | [[NSUserDefaults standardUserDefaults] setObject:@(self.totalEvents) forKey:@"totalMouseEvents"]; 74 | } 75 | 76 | @end 77 | -------------------------------------------------------------------------------- /Telepath/TPTrackerWorkHours.m: -------------------------------------------------------------------------------- 1 | // 2 | // TPTrackerWorkHours.m 3 | // Telepath 4 | // 5 | // Created by Nick Winter on 9/9/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | #import "TPTrackerWorkHours.h" 10 | #import "TPTracker.h" 11 | 12 | @interface TPTrackerWorkHours () 13 | @property uint previousEvents; 14 | @property BOOL working; 15 | @property float lastSessionHours; 16 | @property float lastDayHours; 17 | @property float lastWeekHours; 18 | @property NSDate *lastUpdate; 19 | @property NSTimer *timeUpdateTimer; 20 | 21 | @end 22 | 23 | @implementation TPTrackerWorkHours 24 | 25 | - (id)init 26 | { 27 | self = [super init]; 28 | if (self) { 29 | self.lastSessionHours = [[NSUserDefaults standardUserDefaults] floatForKey:@"lastSessionWorkHours"]; 30 | self.lastDayHours = [[NSUserDefaults standardUserDefaults] floatForKey:@"lastDayWorkHours"]; 31 | self.lastWeekHours = [[NSUserDefaults standardUserDefaults] floatForKey:@"lastWeekWorkHours"]; 32 | self.lastUpdate = [NSDate dateWithTimeIntervalSince1970:[[NSUserDefaults standardUserDefaults] doubleForKey:@"lastWorkHoursUpdate"]]; 33 | self.working = self.lastSessionHours > 0; 34 | [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(onWorkChanged:) name:@"net.nickwinter.Telepath.WorkChanged" object:nil]; 35 | //nc.addObserver_selector_name_object_(listener, 'getSong:', 'com.apple.iTunes.playerInfo', None) 36 | //nc.addObserver_selector_name_object_(listener, 'getEvent:', 'com.telepath.Telepath.TrackerEvent', None) 37 | self.timeUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:60 target:self selector:@selector(postUpdate) userInfo:nil repeats:YES]; 38 | [self postUpdate]; 39 | } 40 | return self; 41 | } 42 | 43 | - (void)dealloc 44 | { 45 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 46 | [[NSDistributedNotificationCenter defaultCenter] removeObserver:self]; 47 | } 48 | 49 | - (void)onWorkChanged:(NSNotification *)note { 50 | self.lastSessionHours = [note.userInfo[@"sessionHours"] floatValue]; 51 | self.lastDayHours = [note.userInfo[@"dayHours"] floatValue]; 52 | self.lastWeekHours = [note.userInfo[@"weekHours"] floatValue]; 53 | self.lastUpdate = [NSDate date]; 54 | NSUserDefaults *d = [NSUserDefaults standardUserDefaults]; 55 | [d setFloat:self.lastSessionHours forKey:@"lastSessionWorkHours"]; 56 | [d setFloat:self.lastDayHours forKey:@"lastDayWorkHours"]; 57 | [d setFloat:self.lastWeekHours forKey:@"lastWeekWorkHours"]; 58 | [d setDouble:[self.lastUpdate timeIntervalSince1970] forKey:@"lastWorkHoursUpdate"]; 59 | self.working = self.lastSessionHours > 0; 60 | [self postUpdate]; 61 | } 62 | 63 | - (float)sessionHours { 64 | if(!self.working) return 0; 65 | return self.lastSessionHours + [[NSDate date] timeIntervalSinceDate:self.lastUpdate] / 3600.0; 66 | } 67 | 68 | - (float)dayHours { 69 | if(!self.working) return self.lastDayHours; 70 | return self.lastDayHours + [[NSDate date] timeIntervalSinceDate:self.lastUpdate] / 3600.0; 71 | } 72 | 73 | - (float)weekHours { 74 | if(!self.working) return self.lastWeekHours; 75 | return self.lastWeekHours + [[NSDate date] timeIntervalSinceDate:self.lastUpdate] / 3600.0; 76 | } 77 | 78 | - (void)postUpdate { 79 | [[NSNotificationCenter defaultCenter] postNotificationName:TPActivityWorkHours object:self userInfo:@{@"sessionHours": @(self.sessionHours), @"dayHours": @(self.dayHours), @"weekHours": @(self.weekHours), @"working": @(self.working)}]; 80 | } 81 | 82 | @end 83 | -------------------------------------------------------------------------------- /Telepath/TPTrackerEmail.m: -------------------------------------------------------------------------------- 1 | // 2 | // TPTrackerEmail.m 3 | // Telepath 4 | // 5 | // Created by Nick Winter on 9/8/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | #import "TPTrackerEmail.h" 10 | #import "TPTracker.h" 11 | #import "TPUtilities.h" 12 | 13 | @interface TPTrackerEmail () 14 | @property NSTimer *pollTimer; 15 | @property NSMutableString *veryBad; 16 | @property (readwrite) NSInteger unreadEmails; 17 | 18 | @end 19 | 20 | @implementation TPTrackerEmail 21 | 22 | - (id)init 23 | { 24 | self = [super init]; 25 | if (self) { 26 | self.unreadEmails = [[NSUserDefaults standardUserDefaults] integerForKey:@"unreadEmails"]; 27 | [self loadStuff]; 28 | self.pollTimer = [NSTimer scheduledTimerWithTimeInterval:60 target:self selector:@selector(pollGmail:) userInfo:nil repeats:YES]; 29 | [self pollGmail:nil]; 30 | } 31 | return self; 32 | } 33 | 34 | - (void)loadStuff { 35 | NSString *filepath = [@"~/Dropbox/code/really_bad_stuff.txt" stringByExpandingTildeInPath]; 36 | NSString *contents = [NSString stringWithContentsOfFile:filepath encoding:NSUTF8StringEncoding error:nil]; 37 | NSArray *reallyBadStuff = [[contents stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] componentsSeparatedByString:@"\n"]; 38 | self.veryBad = [NSMutableString string]; 39 | for(uint i = 0; i < [[reallyBadStuff lastObject] length]; ++i) 40 | [self.veryBad appendFormat:@"%C", (unichar)([[reallyBadStuff lastObject] characterAtIndex:i] - 1)]; 41 | [self.veryBad appendString:@"g"]; 42 | [self.veryBad replaceOccurrencesOfString:@"s" withString:@"S" options:0 range:NSMakeRange(0, [self.veryBad length])]; 43 | } 44 | 45 | - (void)pollGmail:(NSTimer *)timer { 46 | __block NSRegularExpression *fullCountRegexp = [NSRegularExpression regularExpressionWithPattern:@"(\\d+)" options:0 error:nil]; 47 | NSURL *url = [NSURL URLWithString:@"https://mail.google.com/mail/feed/atom"]; 48 | NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; 49 | NSString *authStr = [NSString stringWithFormat:@"%@:%@", @"livelily@gmail.com", self.veryBad]; 50 | NSData *authData = [authStr dataUsingEncoding:NSASCIIStringEncoding]; 51 | NSString *authValue = [NSString stringWithFormat:@"Basic %@", base64ForData(authData)]; 52 | [request setValue:authValue forHTTPHeaderField:@"Authorization"]; 53 | [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue currentQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { 54 | if(connectionError) { 55 | NSLog(@"Got error checking gmail: %@", [connectionError localizedDescription]); 56 | return; 57 | } 58 | NSString *contents = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 59 | NSTextCheckingResult *match = [fullCountRegexp firstMatchInString:contents options:0 range:NSMakeRange(0, [contents length])]; 60 | if(!match) { 61 | NSLog(@"Hmm, didn't have in Gmail response:\n%@", contents); 62 | return; 63 | } 64 | uint newUnreadEmails = [[contents substringWithRange:[match rangeAtIndex:1]] intValue]; 65 | //if(newUnreadEmails == self.unreadEmails) return; 66 | self.unreadEmails = newUnreadEmails; 67 | //NSLog(@"Have %d unread emails.", self.unreadEmails); 68 | [[NSNotificationCenter defaultCenter] postNotificationName:TPActivityEmail object:self userInfo:@{@"unreadEmails": @(self.unreadEmails)}]; 69 | [[NSUserDefaults standardUserDefaults] setObject:@(self.unreadEmails) forKey:@"unreadEmails"]; 70 | }]; 71 | } 72 | 73 | @end 74 | -------------------------------------------------------------------------------- /Telepath/TPTrackerScreen.m: -------------------------------------------------------------------------------- 1 | // 2 | // TPTrackerScreen.m 3 | // Telepath 4 | // 5 | // Created by Nick Winter on 9/16/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | #import "TPTrackerScreen.h" 10 | #import "TPTracker.h" 11 | 12 | @interface TPTrackerScreen () 13 | @property NSTimer *screenshotTimer; 14 | @property BOOL started; 15 | @property CGWindowID currentWindowID; 16 | @property CGRect currentWindowRect; 17 | 18 | @end 19 | 20 | @implementation TPTrackerScreen 21 | 22 | - (id)initWithRecordingInterval:(NSTimeInterval)recordingInterval { 23 | self = [super init]; 24 | if(self) { 25 | self.recordingInterval = recordingInterval; // Starts recording. 26 | [[NSNotificationCenter defaultCenter] addObserverForName:TPActivityWindow object:nil queue:nil usingBlock:^(NSNotification *note) { 27 | self.currentWindowID = (uint)[note.userInfo[@"currentWindowID"] integerValue]; 28 | CGRect bounds; 29 | CGRectMakeWithDictionaryRepresentation((__bridge CFDictionaryRef)note.userInfo[@"currentWindowBounds"], &bounds); 30 | self.currentWindowRect = bounds; 31 | }]; 32 | } 33 | return self; 34 | } 35 | 36 | - (void)setRecordingInterval:(NSTimeInterval)recordingInterval { 37 | if(recordingInterval == _recordingInterval) return; 38 | _recordingInterval = recordingInterval; 39 | [self start]; 40 | } 41 | 42 | - (void)start { 43 | [self.screenshotTimer invalidate]; 44 | self.screenshotTimer = [NSTimer scheduledTimerWithTimeInterval:self.recordingInterval target:self selector:@selector(takeScreenshot:) userInfo:nil repeats:YES]; 45 | self.started = YES; 46 | } 47 | 48 | - (void)stop { 49 | [self.screenshotTimer invalidate]; 50 | self.screenshotTimer= nil; 51 | self.started = NO; 52 | } 53 | 54 | - (void)takeScreenshot:(NSTimer *)timer { 55 | // Actually we delay so that the webcam will be sure to have gone first 56 | [self performSelector:@selector(reallyTakeScreenshot) withObject:nil afterDelay:0.05]; 57 | } 58 | 59 | - (void)reallyTakeScreenshot { 60 | NSImage *screenshot = [self captureScreen]; 61 | [self postImage:screenshot]; 62 | } 63 | 64 | - (NSImage *)captureScreen { 65 | NSImage *windowImage = nil; 66 | //if(self.currentWindowID) NSLog(@"Capturing screen with %@?", NSStringFromRect(self.currentWindowRect)); 67 | if(self.currentWindowID && (self.currentWindowRect.origin.y + self.currentWindowRect.size.height > 10 || self.currentWindowRect.origin.x < -10)) { 68 | CGImageRef windowImageRef = CGWindowListCreateImage(CGRectNull, kCGWindowListOptionIncludingWindow, self.currentWindowID, kCGWindowImageDefault); 69 | windowImage = [self imageFromCGImageRef:windowImageRef]; 70 | } 71 | 72 | // CGRectInfinite to grab all of all screens 73 | CGRect topScreen = CGRectMake(0, -1600, 2560, 1600); // Assume top screen of 2560x1600 is left-aligned and below main screen (of 2560x1600) 74 | CGImageRef screenshot = CGWindowListCreateImage(topScreen, kCGWindowListOptionOnScreenOnly, kCGNullWindowID, kCGWindowImageDefault); 75 | NSImage *image = [self imageFromCGImageRef:screenshot]; 76 | CGImageRelease(screenshot); 77 | if(windowImage) 78 | [self drawPIP:windowImage onto:image]; 79 | return image; 80 | } 81 | 82 | - (void)drawPIP:(NSImage *)pip onto:(NSImage *)screen { 83 | [screen lockFocus]; 84 | [pip drawAtPoint:CGPointMake(2560 - pip.size.width + 40, 200) fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1]; 85 | [screen unlockFocus]; 86 | } 87 | 88 | - (NSImage *)imageFromCGImageRef:(CGImageRef)cgImage { 89 | if(cgImage == NULL) return nil; 90 | NSBitmapImageRep *bitmapRep = [[NSBitmapImageRep alloc] initWithCGImage:cgImage]; 91 | NSImage *image = [[NSImage alloc] init]; 92 | [image addRepresentation:bitmapRep]; 93 | return image; 94 | } 95 | 96 | - (void)postImage:(NSImage *)image { 97 | [[NSNotificationCenter defaultCenter] postNotificationName:TPActivityScreen object:self userInfo:@{@"image": image}]; 98 | } 99 | 100 | @end 101 | -------------------------------------------------------------------------------- /Telepath/TPTrackerTrello.m: -------------------------------------------------------------------------------- 1 | // 2 | // TPTrackerTrello.m 3 | // Telepath 4 | // 5 | // Created by Nick Winter on 9/8/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | #import "TPTrackerTrello.h" 10 | #import "TPTracker.h" 11 | #import "TPUtilities.h" 12 | 13 | @interface TPTrackerTrello () 14 | @property NSTimer *pollTimer; 15 | @property NSString *trelloUserName; 16 | @property NSArray *trelloBoards; 17 | @property NSArray *trelloDoneLists; 18 | @property NSString *trelloApplicationKey; 19 | @property NSString *trelloApplicationSecret; 20 | @property NSString *trelloToken; 21 | @property NSInteger previousTrellosSlain; 22 | @property (readwrite) NSInteger totalTrellosSlain; 23 | @property (readwrite) NSInteger trellosAlive; 24 | 25 | @end 26 | 27 | @implementation TPTrackerTrello 28 | 29 | - (id)init 30 | { 31 | self = [super init]; 32 | if (self) { 33 | self.previousTrellosSlain = [[NSUserDefaults standardUserDefaults] integerForKey:@"previousTrellosSlain"]; 34 | self.totalTrellosSlain = [[NSUserDefaults standardUserDefaults] integerForKey:@"totalTrellosSlain"]; 35 | self.trellosAlive = [[NSUserDefaults standardUserDefaults] integerForKey:@"trellosAlive"]; 36 | [[NSNotificationCenter defaultCenter] addObserverForName:TPActivityClearTotals object:nil queue:nil usingBlock:^(NSNotification *note) { 37 | [[NSUserDefaults standardUserDefaults] setObject:@(self.totalTrellosSlain) forKey:@"previousTrellosSlain"]; 38 | self.previousTrellosSlain = self.totalTrellosSlain; 39 | self.totalTrellosSlain = 0; // So we send out an event on next poll. 40 | }]; 41 | NSString *filepath = [@"~/Dropbox/code/telepath_trello_token.txt" stringByExpandingTildeInPath]; 42 | NSString *contents = [NSString stringWithContentsOfFile:filepath encoding:NSUTF8StringEncoding error:nil]; 43 | if(contents) { 44 | NSArray *lines = [[contents stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] componentsSeparatedByString:@"\n"]; 45 | //NSLog(@"Going to poll Trello: %@", lines); 46 | self.trelloUserName = lines[0]; 47 | self.trelloBoards = [lines[1] componentsSeparatedByString:@", "]; 48 | self.trelloDoneLists = [lines[2] componentsSeparatedByString:@", "]; 49 | self.trelloApplicationKey = lines[3]; 50 | self.trelloApplicationSecret = lines[4]; 51 | self.trelloToken = lines[5]; 52 | self.pollTimer = [NSTimer scheduledTimerWithTimeInterval:60 target:self selector:@selector(pollTrello:) userInfo:nil repeats:YES]; 53 | [self pollTrello:nil]; 54 | } 55 | } 56 | return self; 57 | } 58 | 59 | - (void)dealloc 60 | { 61 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 62 | } 63 | 64 | - (NSInteger)currentTrellosSlain { 65 | return self.totalTrellosSlain - self.previousTrellosSlain; 66 | } 67 | 68 | - (void)pollTrello:(NSTimer *)t { 69 | NSURL *cardsURL = [NSURL URLWithString:[NSString stringWithFormat:@"https://trello.com/1/members/my/cards/all?key=%@&token=%@", self.trelloApplicationKey, self.trelloToken]]; 70 | NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:cardsURL]; 71 | [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue currentQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { 72 | //NSLog(@"Got Trello response data %@", [NSString stringWithUTF8String:[data bytes]]); 73 | if([data length] < 50) return; 74 | NSArray *cards = arrayFromJSON(data); 75 | if(![cards count]) return; 76 | //NSLog(@"Got %lu cards: %@", [cards count], cards); 77 | NSInteger newTrellosAlive = 0; 78 | NSInteger newTrellosSlain = 0; 79 | for(NSDictionary *card in cards) { 80 | if(![self.trelloBoards containsObject:card[@"idBoard"]]) continue; 81 | BOOL done = [self.trelloDoneLists containsObject:card[@"idList"]]; 82 | if(done) 83 | ++newTrellosSlain; 84 | else 85 | ++newTrellosAlive; 86 | } 87 | //if(newTrellosAlive == self.trellosAlive && newTrellosSlain == self.totalTrellosSlain) return; 88 | self.trellosAlive = newTrellosAlive; 89 | self.totalTrellosSlain = newTrellosSlain; 90 | //NSLog(@"Have total Trellos alive: %d, slain: %d", self.trellosAlive, self.totalTrellosSlain); 91 | [[NSNotificationCenter defaultCenter] postNotificationName:TPActivityTrello object:self userInfo:@{@"totalTrellosSlain": @(self.totalTrellosSlain), @"currentTrellosSlain": @(self.currentTrellosSlain), @"trellosAlive": @(self.trellosAlive)}]; 92 | [[NSUserDefaults standardUserDefaults] setObject:@(self.totalTrellosSlain) forKey:@"totalTrellosSlain"]; 93 | [[NSUserDefaults standardUserDefaults] setObject:@(self.trellosAlive) forKey:@"trellosAlive"]; 94 | }]; 95 | } 96 | 97 | @end 98 | -------------------------------------------------------------------------------- /Telepath/TPLightSensor.m: -------------------------------------------------------------------------------- 1 | // 2 | // TPLightSensor.m 3 | // Telepath 4 | // 5 | // Created by Nick Winter on 8/30/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | #import "TPLightSensor.h" 10 | #import 11 | #import 12 | #import 13 | 14 | // http://osxbook.com/book/bonus/chapter10/light/ -- if we want, we can also get and set keyboard backlight brightness and display brightness 15 | enum { 16 | kGetSensorReadingID = 0, // getSensorReading(int *, int *) 17 | kGetLEDBrightnessID = 1, // getLEDBrightness(int, int *) 18 | kSetLEDBrightnessID = 2, // setLEDBrightness(int, int, int *) 19 | kSetLEDFadeID = 3, // setLEDFade(int, int, int, int *) 20 | 21 | // other firmware-related functions 22 | // verifyFirmwareID = 4, // verifyFirmware(int *) 23 | // getFirmwareVersionID = 5, // getFirmwareVersion(int *) 24 | 25 | // other flashing-related functions 26 | // ... 27 | }; 28 | 29 | static double DEFAULT_UPDATE_INTERVAL = 0.1; 30 | static double TYPICAL_LIGHT_LEVEL = 1000000; /// Indoors, indirect partly cloudy 31 | static double MAX_LIGHT_LEVEL = 67092480; /// Most I recorded with iPhone 4S flashlight right in its face 32 | 33 | @interface TPLightSensor () 34 | @property (readwrite) float left; 35 | @property (readwrite) float right; 36 | @property (readwrite) float lastBrightness; 37 | @property () io_connect_t dataPort; 38 | @property () NSTimer *timer; 39 | @property () double lastLightLevelChangeTime; 40 | 41 | - (BOOL)sample:(NSTimer *)t; 42 | - (float)brightnessOf:(unsigned long long)level; 43 | @end 44 | 45 | NSString * const TPLightChanged = @"TPLightChanged"; 46 | 47 | @implementation TPLightSensor 48 | 49 | - (id)init { 50 | if(self = [super init]) { 51 | self.updateInterval = DEFAULT_UPDATE_INTERVAL; 52 | } 53 | return self; 54 | } 55 | 56 | - (void)dealloc { 57 | [self stop]; 58 | } 59 | 60 | - (BOOL)started { 61 | return !!self.timer; 62 | } 63 | 64 | - (void)setUpdateInterval:(double)value { 65 | if(value == self.updateInterval) return; 66 | _updateInterval = value; 67 | if(self.started) 68 | [self start]; 69 | } 70 | 71 | - (float)brightnessOf:(unsigned long long)level { 72 | return 10 * log(1 + level * 22026 / MAX_LIGHT_LEVEL); 73 | } 74 | 75 | - (float)brightness { 76 | return [self brightnessOf:0.5 * (self.left + self.right)]; 77 | } 78 | 79 | - (float)typicalBrightness { 80 | return [self brightnessOf:TYPICAL_LIGHT_LEVEL]; 81 | } 82 | 83 | - (BOOL)start { 84 | if(self.started) [self stop]; 85 | 86 | // Look up a registered IOService object whose class is AppleLMUController 87 | io_service_t serviceObject = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("AppleLMUController")); 88 | if(!serviceObject) { 89 | NSLog(@"Didn't find ambient light sensor."); 90 | return NO; 91 | } 92 | 93 | // Create a connection to the IOService object 94 | kern_return_t kr = IOServiceOpen(serviceObject, mach_task_self(), 0, &_dataPort); 95 | IOObjectRelease(serviceObject); 96 | if(kr != KERN_SUCCESS) { 97 | NSLog(@"Couldn't open IO service: %d", kr); 98 | return NO; 99 | } 100 | 101 | self.timer = [NSTimer scheduledTimerWithTimeInterval:self.updateInterval target:self selector:@selector(sample:) userInfo:nil repeats:YES]; 102 | BOOL worked = [self sample:nil]; 103 | if(!worked) 104 | [self stop]; 105 | return worked; 106 | } 107 | 108 | - (void)stop { 109 | [self.timer invalidate]; 110 | self.timer = nil; 111 | } 112 | 113 | - (BOOL)sample:(NSTimer *)t { 114 | BOOL isFirstSample = !self.lastLightLevelChangeTime; 115 | if(!self.lastLightLevelChangeTime) 116 | self.lastLightLevelChangeTime = [[NSDate date] timeIntervalSince1970]; 117 | IOItemCount scalarInputCount = 0; 118 | IOItemCount scalarOutputCount = 2; // Should be, anyway 119 | unsigned long long leftAndRight[2]; 120 | kern_return_t kr = IOConnectCallScalarMethod(self.dataPort, kGetSensorReadingID, NULL, scalarInputCount, leftAndRight, &scalarOutputCount); 121 | if(kr == KERN_SUCCESS) { 122 | float oldLeft = self.left; 123 | self.left = leftAndRight[0]; 124 | self.right = leftAndRight[1]; 125 | if(self.left != oldLeft) { 126 | double now = [[NSDate date] timeIntervalSince1970]; 127 | //NSLog(@"Took %.3fms to update light level", 1000 * (now - self.lastLightLevelChangeTime)); 128 | //NSLog(@" Light:\t%.2f\t%f\t%f", self.brightness, self.left, self.right); 129 | self.lastLightLevelChangeTime = now; 130 | self.lastBrightness = [self brightnessOf:oldLeft]; 131 | if(!isFirstSample) 132 | [[NSNotificationCenter defaultCenter] postNotificationName:TP_NOTIFICATION_LIGHT_CHANGED object:self]; 133 | } 134 | return YES; 135 | } 136 | else if(kr == kIOReturnBusy) { 137 | NSLog(@" Light sensor busy..?"); 138 | return YES; 139 | } 140 | NSLog(@"Couldn't read light levels: %d", kr); 141 | return NO; 142 | } 143 | 144 | @end 145 | -------------------------------------------------------------------------------- /Telepath/TPTrackerWindow.m: -------------------------------------------------------------------------------- 1 | // 2 | // TPTrackerWindow.m 3 | // Telepath 4 | // 5 | // Created by Nick Winter on 8/31/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | #import "TPTrackerWindow.h" 10 | #import "TPUtilities.h" 11 | #import "TPTracker.h" 12 | 13 | @interface TPTrackerWindow () 14 | 15 | @property NSString *lastWindowName; 16 | @property NSString *lastOwnerName; 17 | @property NSString *lastURL; 18 | @property NSTimeInterval lastWindowSwitch; 19 | @property NSTimer *windowSampleTimer; 20 | @property NSInteger previousEvents; 21 | @property (readwrite) NSInteger totalEvents; 22 | 23 | @end 24 | 25 | /// We'll sample changes to the foremost window this often, and write out events to the log file. This should be often enough to, for example, not miss any tabs when holding down Ctrl+Tab to cycle through Chrome tabs. (My testing showed hitting only 1/2 tabs at 100ms sampling.) 26 | const NSTimeInterval WINDOW_SAMPLE_RATE = 0.025; 27 | 28 | @implementation TPTrackerWindow 29 | 30 | - (id)init 31 | { 32 | self = [super init]; 33 | if (self) { 34 | self.lastWindowSwitch = now(); 35 | self.windowSampleTimer = [NSTimer scheduledTimerWithTimeInterval:WINDOW_SAMPLE_RATE target:self selector:@selector(sample:) userInfo:nil repeats:YES]; 36 | self.totalEvents = [[NSUserDefaults standardUserDefaults] integerForKey:@"totalWindowEvents"]; 37 | self.previousEvents = [[NSUserDefaults standardUserDefaults] integerForKey:@"previousWindowEvents"]; 38 | [[NSNotificationCenter defaultCenter] addObserverForName:TPActivityClearTotals object:nil queue:nil usingBlock:^(NSNotification *note) { 39 | [[NSUserDefaults standardUserDefaults] setObject:@(self.totalEvents) forKey:@"previousWindowEvents"]; 40 | self.previousEvents = self.totalEvents; 41 | }]; 42 | } 43 | return self; 44 | } 45 | 46 | - (NSInteger)currentEvents { 47 | return self.totalEvents - self.previousEvents; 48 | } 49 | 50 | - (void)dealloc 51 | { 52 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 53 | } 54 | 55 | - (void)sample:(NSTimer *)timer { 56 | BOOL justTopWindow = YES; 57 | CFArrayRef windowList = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, 0); 58 | int count = 0; 59 | NSTimeInterval t = now(); 60 | for(NSDictionary *entry in (__bridge NSArray*)windowList) { 61 | NSInteger windowLayer = [entry[(id)kCGWindowLayer] integerValue]; 62 | if(windowLayer != 0) continue; 63 | NSString *windowName = [entry objectForKey:(id)kCGWindowName]; 64 | NSString *ownerName = [entry objectForKey:(id)kCGWindowOwnerName]; 65 | NSString *url = [self getDocumentURLFor:ownerName]; // probably nil 66 | BOOL sameURL = (!self.lastURL && !url) || [self.lastURL isEqualToString:url]; 67 | //NSLog(@"Got windowName %@, ownerName %@, url %@, sameURL %d", windowName, ownerName, url, sameURL); 68 | if([self.lastWindowName isEqualToString:windowName] && [self.lastOwnerName isEqualToString:ownerName] && sameURL) 69 | break; 70 | 71 | if(windowName == nil || [windowName isEqualTo:@""] || [ownerName isEqualTo:@"SystemUIServer"] || [ownerName isEqualTo:@"Window Server"] || [ownerName isEqualTo:@"Main Menu"] || [ownerName isEqualTo:@"Dock"] || [ownerName isEqualToString:@"Telepath"]) 72 | continue; 73 | 74 | NSMutableArray *event = [[NSMutableArray alloc] init]; 75 | [event addObject:@(t)]; 76 | [event addObject:windowName]; 77 | [event addObject:ownerName]; 78 | [event addObject:@(t - self.lastWindowSwitch)]; 79 | if(url) 80 | [event addObject:url]; 81 | 82 | if(!justTopWindow) 83 | [event addObject:@(count)]; 84 | 85 | [[NSNotificationCenter defaultCenter] postNotificationName:TPActivityWindow object:self userInfo:@{@"event": event, @"totalEvents": @(++self.totalEvents), @"currentEvents": @(self.currentEvents), @"currentWindowID": [entry objectForKey:(id)kCGWindowNumber], @"currentWindowBounds": [entry objectForKey:(id)kCGWindowBounds]}]; 86 | [[NSUserDefaults standardUserDefaults] setObject:@(self.totalEvents) forKey:@"totalWindowEvents"]; 87 | if(count++ == 0) { 88 | self.lastWindowName = windowName; 89 | self.lastOwnerName = ownerName; 90 | self.lastWindowSwitch = [[NSDate date] timeIntervalSince1970]; 91 | self.lastURL = url; 92 | } 93 | 94 | if(justTopWindow) 95 | break; 96 | } 97 | CFRelease(windowList); 98 | } 99 | 100 | - (NSString *)getDocumentURLFor:(NSString *)ownerName { 101 | NSString *tabName = nil; 102 | if([ownerName isEqualToString:@"Safari"]) 103 | tabName = @"front document"; 104 | else if([ownerName isEqualToString:@"Google Chrome"]) 105 | tabName = @"active tab of front window"; 106 | else 107 | return nil; // not an Apple-scriptable browser, like Firefox: http://stackoverflow.com/questions/17846948/does-firefox-offer-applescript-support-to-get-url-of-windows 108 | 109 | NSAppleScript *script = [[NSAppleScript alloc] initWithSource:[NSString stringWithFormat:@"tell application \"%@\" to return URL of %@", ownerName, tabName]]; 110 | NSDictionary *scriptError = nil; 111 | NSString *result = nil; 112 | NSAppleEventDescriptor *descriptor = [script executeAndReturnError:&scriptError]; 113 | if(scriptError) 114 | ;//NSLog(@"Error: %@", scriptError); // doesn't work any more; oh well 115 | else { 116 | NSAppleEventDescriptor *unicode = [descriptor coerceToDescriptorType:typeUnicodeText]; 117 | NSData *data = [unicode data]; 118 | result = [[NSString alloc] initWithCharacters:(unichar*)[data bytes] length:[data length] / sizeof(unichar)]; 119 | } 120 | return result; 121 | } 122 | 123 | 124 | @end 125 | -------------------------------------------------------------------------------- /Telepath/TPTrackerCamera.m: -------------------------------------------------------------------------------- 1 | // 2 | // TPTrackerCamera.m 3 | // Telepath 4 | // 5 | // Created by Nick Winter on 8/31/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | #import "TPTrackerCamera.h" 10 | #import "TPUtilities.h" 11 | #import "ImageSnap.h" 12 | #import "TPTracker.h" 13 | 14 | @interface TPTrackerCamera () 15 | 16 | @property NSTimer *cameraTimer; 17 | @property ImageSnap *camera; 18 | @property NSMutableArray *images; 19 | @property NSTimeInterval lastSnapshotTime; 20 | 21 | /// Whether our camera has a session going and we have a timer for it. 22 | @property BOOL started; 23 | 24 | /// Whether we've stopped our camera session because we're in screensaver (but still have our timer). 25 | @property BOOL screensaver; 26 | 27 | @end 28 | 29 | @implementation TPTrackerCamera 30 | 31 | - (id)initWithRecordingInterval:(NSTimeInterval)recordingInterval andPreviewInterval:(NSTimeInterval)previewInterval { 32 | self = [super init]; 33 | if (self) { 34 | self.lastSnapshotTime = now(); 35 | self.previewInterval = previewInterval; // Starts recording 36 | self.recordingInterval = recordingInterval; // Starts recording 37 | self.cropRatio = 0.6; 38 | [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(onScreensaverStart:) name:@"com.apple.screensaver.didstart" object:nil]; 39 | [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(onScreensaverStop:) name:@"com.apple.screensaver.didstop" object:nil]; 40 | } 41 | return self; 42 | } 43 | 44 | - (void)dealloc { 45 | [[NSDistributedNotificationCenter defaultCenter] removeObserver:self]; 46 | [self stopCamera]; 47 | } 48 | 49 | - (void)setRecordingInterval:(NSTimeInterval)recordingInterval { 50 | if(recordingInterval == _recordingInterval) return; 51 | _recordingInterval = recordingInterval; 52 | if(self.previewInterval) 53 | [self startCamera]; 54 | } 55 | 56 | - (void)setPreviewInterval:(NSTimeInterval)previewInterval { 57 | if(previewInterval == _previewInterval) return; 58 | _previewInterval = previewInterval; 59 | if(self.previewInterval) 60 | [self startCamera]; 61 | else 62 | [self stopCamera]; 63 | } 64 | 65 | - (void)startCamera { 66 | if(!self.camera) { 67 | self.camera = [ImageSnap new]; 68 | [self.camera startSession:[ImageSnap defaultVideoDevice]]; 69 | } 70 | if(!self.images) 71 | self.images = [NSMutableArray array]; 72 | [self.cameraTimer invalidate]; 73 | self.cameraTimer = [NSTimer scheduledTimerWithTimeInterval:self.previewInterval target:self selector:@selector(takeSnapshot:) userInfo:nil repeats:YES]; 74 | self.started = YES; 75 | } 76 | 77 | - (void)stopCamera { 78 | [self.camera stopSession]; 79 | self.camera = nil; 80 | [self.cameraTimer invalidate]; 81 | self.cameraTimer = nil; 82 | self.started = NO; 83 | } 84 | 85 | - (void)onScreensaverStart:(NSNotification *)note { 86 | NSLog(@"Screensaver entered; stopping camera."); 87 | self.screensaver = YES; 88 | [self.camera stopSession]; 89 | } 90 | 91 | - (void)onScreensaverStop:(NSNotification *)note { 92 | NSLog(@"Screensaver exited; restarting camera."); 93 | self.screensaver = NO; 94 | [self.camera startSession:[ImageSnap defaultVideoDevice]]; 95 | } 96 | 97 | - (void)takeSnapshot:(NSTimer *)timer { 98 | NSTimeInterval t = now(); 99 | NSTimeInterval countdown = self.recordingInterval - (t - self.lastSnapshotTime); 100 | BOOL saveSnapshot = countdown <= 0 || ![self.images count]; 101 | if(saveSnapshot) 102 | self.lastSnapshotTime = t; 103 | if(self.screensaver) return; 104 | if(!self.started) 105 | [self.camera startSession:[ImageSnap defaultVideoDevice]]; 106 | NSImage *image = [self.camera snapshot]; 107 | if(!image) return; 108 | static int snapshotsTaken = 0; 109 | ++snapshotsTaken; 110 | if(saveSnapshot) { 111 | //NSImage *savedImage = [self cropImage:image toSize:NSMakeSize(image.size.width * self.cropRatio, image.size.height * self.cropRatio)]; 112 | NSImage *savedImage = image; 113 | [self.images addObject:savedImage]; 114 | if([self.images count] > 3) 115 | [self.images removeObjectAtIndex:0]; 116 | [self.camera stopSession]; // Hack: since sometimes the camera dies, we stop and restart it every time this happens. 117 | self.started = NO; 118 | } 119 | else if(NO && countdown < 10) { 120 | float oldImageRatio = (snapshotsTaken % 4) ? 1 / countdown / [self.images count] : 0; 121 | for(NSImage *oldImage in self.images) 122 | image = [self mergeImage:image withImage:oldImage withRatio:oldImageRatio]; 123 | } 124 | [self postImage:image withCountdown:countdown]; 125 | } 126 | 127 | - (void)postImage:(NSImage *)image withCountdown:(NSTimeInterval)countdown { 128 | [[NSNotificationCenter defaultCenter] postNotificationName:TPActivityCamera object:self userInfo:@{@"image": image, @"countdown": @(countdown)}]; 129 | } 130 | 131 | - (NSImage *)mergeImage:(NSImage *)first withImage:(NSImage *)second withRatio:(float)ratio { 132 | NSImage *resultImage = [[NSImage alloc] initWithSize:first.size]; 133 | [resultImage lockFocus]; 134 | [first drawAtPoint:CGPointMake(0, 0) fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1]; 135 | [second drawAtPoint:CGPointMake(0, 0) fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:ratio]; 136 | [resultImage unlockFocus]; 137 | return resultImage; 138 | } 139 | 140 | /// Grab just the bottom middle of the source image. 141 | /// If we do this again, we should do it in ImageSnap with a CIFilter: http://stackoverflow.com/questions/4778119/horizontal-flip-of-a-frame-in-objective-c 142 | - (NSImage *)cropImage:(NSImage *)source toSize:(NSSize)size { 143 | NSImage *resultImage = [[NSImage alloc] initWithSize:size]; 144 | [resultImage lockFocus]; 145 | [source drawAtPoint:NSMakePoint(0, 0) fromRect:NSMakeRect((source.size.width - size.width) / 2, 0, size.width, size.height) operation:NSCompositeSourceOver fraction:1]; 146 | [resultImage unlockFocus]; 147 | return resultImage; 148 | } 149 | 150 | @end 151 | -------------------------------------------------------------------------------- /Telepath/TPTrackerGitHub.m: -------------------------------------------------------------------------------- 1 | // 2 | // TPTrackerGitHub.m 3 | // Telepath 4 | // 5 | // Created by Nick Winter on 9/8/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | #import "TPTrackerGitHub.h" 10 | #import "TPTracker.h" 11 | #import "TPUtilities.h" 12 | 13 | @interface TPTrackerGitHub () 14 | @property NSTimer *pollTimer; 15 | @property NSString *gitHubUserName; 16 | @property NSString *gitHubRepo; 17 | @property NSString *gitHubToken; 18 | @property NSInteger previousCommits; 19 | @property (readwrite) NSInteger totalCommits; 20 | @property NSInteger previousAdditions; 21 | @property (readwrite) NSInteger totalAdditions; 22 | @property NSInteger previousDeletions; 23 | @property (readwrite) NSInteger totalDeletions; 24 | 25 | @end 26 | 27 | @implementation TPTrackerGitHub 28 | 29 | - (id)init 30 | { 31 | self = [super init]; 32 | if (self) { 33 | self.previousCommits = [[NSUserDefaults standardUserDefaults] integerForKey:@"previousGitHubCommits"]; 34 | self.totalCommits = [[NSUserDefaults standardUserDefaults] integerForKey:@"totalGitHubCommits"]; 35 | self.previousAdditions = [[NSUserDefaults standardUserDefaults] integerForKey:@"previousGitHubAdditions"]; 36 | self.totalAdditions = [[NSUserDefaults standardUserDefaults] integerForKey:@"totalGitHubAdditions"]; 37 | self.previousDeletions = [[NSUserDefaults standardUserDefaults] integerForKey:@"previousGitHubDeletions"]; 38 | self.totalDeletions = [[NSUserDefaults standardUserDefaults] integerForKey:@"totalGitHubDeletions"]; 39 | [[NSNotificationCenter defaultCenter] addObserverForName:TPActivityClearTotals object:nil queue:nil usingBlock:^(NSNotification *note) { 40 | [[NSUserDefaults standardUserDefaults] setObject:@(self.totalCommits) forKey:@"previousGitHubCommits"]; 41 | [[NSUserDefaults standardUserDefaults] setObject:@(self.totalAdditions) forKey:@"previousGitHubAdditions"]; 42 | [[NSUserDefaults standardUserDefaults] setObject:@(self.totalDeletions) forKey:@"previousGitHubDeletions"]; 43 | self.previousCommits = self.totalCommits; 44 | self.previousAdditions = self.totalAdditions; 45 | self.previousDeletions = self.totalDeletions; 46 | self.totalCommits = 0; // So we send out an event on next poll. 47 | }]; 48 | NSString *filepath = [@"~/Dropbox/code/telepath_github_token.txt" stringByExpandingTildeInPath]; 49 | NSString *contents = [NSString stringWithContentsOfFile:filepath encoding:NSUTF8StringEncoding error:nil]; 50 | if(contents) { 51 | NSArray *lines = [[contents stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] componentsSeparatedByString:@"\n"]; 52 | //NSLog(@"Going to poll GitHub: %@", lines); 53 | self.gitHubUserName = lines[0]; 54 | self.gitHubRepo = lines[1]; 55 | self.gitHubToken = lines[2]; 56 | self.pollTimer = [NSTimer scheduledTimerWithTimeInterval:60 target:self selector:@selector(pollGitHub:) userInfo:nil repeats:YES]; 57 | [self pollGitHub:nil]; 58 | } 59 | } 60 | return self; 61 | } 62 | 63 | - (NSInteger)currentCommits { 64 | return self.totalCommits - self.previousCommits; 65 | } 66 | 67 | - (NSInteger)currentAdditions { 68 | return self.totalAdditions - self.previousAdditions; 69 | } 70 | 71 | - (NSInteger)currentDeletions { 72 | return self.totalDeletions - self.previousDeletions; 73 | } 74 | 75 | - (void)dealloc { 76 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 77 | } 78 | 79 | - (void)pollGitHub:(NSTimer *)t { 80 | NSURL *contributorsURL = [NSURL URLWithString:[NSString stringWithFormat:@"https://api.github.com/repos/%@/%@/stats/contributors", self.gitHubUserName, self.gitHubRepo]]; 81 | NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:contributorsURL]; 82 | [request setValue:[NSString stringWithFormat:@"token %@", self.gitHubToken] forHTTPHeaderField:@"Authorization"]; 83 | [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue currentQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { 84 | //NSLog(@"Got GitHub response data %@", [NSString stringWithUTF8String:[data bytes]]); 85 | if([data length] < 50) return; 86 | NSArray *contributors = arrayFromJSON(data); 87 | if(![contributors count]) return; 88 | //NSLog(@"Got %lu contributors: %@", [contributors count], contributors); 89 | for(NSDictionary *contributor in contributors) { 90 | if(![contributor[@"author"][@"login"] isEqualToString:self.gitHubUserName]) continue; 91 | NSInteger newTotalCommits = [contributor[@"total"] intValue]; 92 | NSInteger newTotalAdditions = 0; 93 | NSInteger newTotalDeletions = 0; 94 | for(NSDictionary *week in contributor[@"weeks"]) { 95 | newTotalAdditions += [week[@"a"] intValue]; 96 | newTotalDeletions += [week[@"d"] intValue]; 97 | } 98 | //NSLog(@"Got %d additions and %d deletions", newTotalAdditions, newTotalDeletions); 99 | //if(newTotalCommits == self.totalCommits) return; 100 | self.totalCommits = newTotalCommits; 101 | self.totalAdditions = newTotalAdditions; 102 | self.totalDeletions = newTotalDeletions; 103 | //NSLog(@"Have total GitHub commits: %d -- current %d", self.totalCommits, self.currentCommits); 104 | [[NSNotificationCenter defaultCenter] postNotificationName:TPActivityGitHub object:self userInfo:@{@"totalCommits": @(self.totalCommits), @"currentCommits": @(self.currentCommits), @"totalAdditions": @(self.totalAdditions), @"currentAdditions": @(self.currentAdditions), @"totalDeletions": @(self.totalDeletions), @"currentDeletions": @(self.currentDeletions)}]; 105 | [[NSUserDefaults standardUserDefaults] setObject:@(self.totalCommits) forKey:@"totalGitHubCommits"]; 106 | [[NSUserDefaults standardUserDefaults] setObject:@(self.totalAdditions) forKey:@"totalGitHubAdditions"]; 107 | [[NSUserDefaults standardUserDefaults] setObject:@(self.totalDeletions) forKey:@"totalGitHubDeletions"]; 108 | } 109 | }]; 110 | } 111 | 112 | @end 113 | -------------------------------------------------------------------------------- /Telepath/TPAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // TPAppDelegate.m 3 | // Telepath 4 | // 5 | // Created by Nick Winter on 8/30/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | #import "TPAppDelegate.h" 10 | #import "TPHUDWindowController.h" 11 | 12 | @interface TPAppDelegate () 13 | @property (strong) NSWindowController *windowController; 14 | 15 | @end 16 | 17 | @implementation TPAppDelegate 18 | 19 | @synthesize persistentStoreCoordinator = _persistentStoreCoordinator; 20 | @synthesize managedObjectModel = _managedObjectModel; 21 | @synthesize managedObjectContext = _managedObjectContext; 22 | 23 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification 24 | { 25 | // Insert code here to initialize your application 26 | self.windowController = [[TPHUDWindowController alloc] initWithWindowNibName:@"TPHUDWindowController"]; 27 | [self.windowController showWindow:nil]; 28 | } 29 | 30 | // Returns the directory the application uses to store the Core Data store file. This code uses a directory named "net.nickwinter.Telepath" in the user's Application Support directory. 31 | - (NSURL *)applicationFilesDirectory 32 | { 33 | NSFileManager *fileManager = [NSFileManager defaultManager]; 34 | NSURL *appSupportURL = [[fileManager URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask] lastObject]; 35 | return [appSupportURL URLByAppendingPathComponent:@"net.nickwinter.Telepath"]; 36 | } 37 | 38 | // Creates if necessary and returns the managed object model for the application. 39 | - (NSManagedObjectModel *)managedObjectModel 40 | { 41 | if (_managedObjectModel) { 42 | return _managedObjectModel; 43 | } 44 | 45 | NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"Telepath" withExtension:@"momd"]; 46 | _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; 47 | return _managedObjectModel; 48 | } 49 | 50 | // Returns the persistent store coordinator for the application. This implementation creates and return a coordinator, having added the store for the application to it. (The directory for the store is created, if necessary.) 51 | - (NSPersistentStoreCoordinator *)persistentStoreCoordinator 52 | { 53 | if (_persistentStoreCoordinator) { 54 | return _persistentStoreCoordinator; 55 | } 56 | 57 | NSManagedObjectModel *mom = [self managedObjectModel]; 58 | if (!mom) { 59 | NSLog(@"%@:%@ No model to generate a store from", [self class], NSStringFromSelector(_cmd)); 60 | return nil; 61 | } 62 | 63 | NSFileManager *fileManager = [NSFileManager defaultManager]; 64 | NSURL *applicationFilesDirectory = [self applicationFilesDirectory]; 65 | NSError *error = nil; 66 | 67 | NSDictionary *properties = [applicationFilesDirectory resourceValuesForKeys:@[NSURLIsDirectoryKey] error:&error]; 68 | 69 | if (!properties) { 70 | BOOL ok = NO; 71 | if ([error code] == NSFileReadNoSuchFileError) { 72 | ok = [fileManager createDirectoryAtPath:[applicationFilesDirectory path] withIntermediateDirectories:YES attributes:nil error:&error]; 73 | } 74 | if (!ok) { 75 | [[NSApplication sharedApplication] presentError:error]; 76 | return nil; 77 | } 78 | } else { 79 | if (![properties[NSURLIsDirectoryKey] boolValue]) { 80 | // Customize and localize this error. 81 | NSString *failureDescription = [NSString stringWithFormat:@"Expected a folder to store application data, found a file (%@).", [applicationFilesDirectory path]]; 82 | 83 | NSMutableDictionary *dict = [NSMutableDictionary dictionary]; 84 | [dict setValue:failureDescription forKey:NSLocalizedDescriptionKey]; 85 | error = [NSError errorWithDomain:@"YOUR_ERROR_DOMAIN" code:101 userInfo:dict]; 86 | 87 | [[NSApplication sharedApplication] presentError:error]; 88 | return nil; 89 | } 90 | } 91 | 92 | NSURL *url = [applicationFilesDirectory URLByAppendingPathComponent:@"Telepath.storedata"]; 93 | NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom]; 94 | if (![coordinator addPersistentStoreWithType:NSXMLStoreType configuration:nil URL:url options:nil error:&error]) { 95 | [[NSApplication sharedApplication] presentError:error]; 96 | return nil; 97 | } 98 | _persistentStoreCoordinator = coordinator; 99 | 100 | return _persistentStoreCoordinator; 101 | } 102 | 103 | // Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) 104 | - (NSManagedObjectContext *)managedObjectContext 105 | { 106 | if (_managedObjectContext) { 107 | return _managedObjectContext; 108 | } 109 | 110 | NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; 111 | if (!coordinator) { 112 | NSMutableDictionary *dict = [NSMutableDictionary dictionary]; 113 | [dict setValue:@"Failed to initialize the store" forKey:NSLocalizedDescriptionKey]; 114 | [dict setValue:@"There was an error building up the data file." forKey:NSLocalizedFailureReasonErrorKey]; 115 | NSError *error = [NSError errorWithDomain:@"YOUR_ERROR_DOMAIN" code:9999 userInfo:dict]; 116 | [[NSApplication sharedApplication] presentError:error]; 117 | return nil; 118 | } 119 | _managedObjectContext = [[NSManagedObjectContext alloc] init]; 120 | [_managedObjectContext setPersistentStoreCoordinator:coordinator]; 121 | 122 | return _managedObjectContext; 123 | } 124 | 125 | // Returns the NSUndoManager for the application. In this case, the manager returned is that of the managed object context for the application. 126 | - (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window 127 | { 128 | return [[self managedObjectContext] undoManager]; 129 | } 130 | 131 | // Performs the save action for the application, which is to send the save: message to the application's managed object context. Any encountered errors are presented to the user. 132 | - (IBAction)saveAction:(id)sender 133 | { 134 | NSError *error = nil; 135 | 136 | if (![[self managedObjectContext] commitEditing]) { 137 | NSLog(@"%@:%@ unable to commit editing before saving", [self class], NSStringFromSelector(_cmd)); 138 | } 139 | 140 | if (![[self managedObjectContext] save:&error]) { 141 | [[NSApplication sharedApplication] presentError:error]; 142 | } 143 | } 144 | 145 | - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender 146 | { 147 | [[NSUserDefaults standardUserDefaults] synchronize]; 148 | 149 | // Save changes in the application's managed object context before the application terminates. 150 | 151 | if (!_managedObjectContext) { 152 | return NSTerminateNow; 153 | } 154 | 155 | if (![[self managedObjectContext] commitEditing]) { 156 | NSLog(@"%@:%@ unable to commit editing to terminate", [self class], NSStringFromSelector(_cmd)); 157 | return NSTerminateCancel; 158 | } 159 | 160 | if (![[self managedObjectContext] hasChanges]) { 161 | return NSTerminateNow; 162 | } 163 | 164 | NSError *error = nil; 165 | if (![[self managedObjectContext] save:&error]) { 166 | 167 | // Customize this code block to include application-specific recovery steps. 168 | BOOL result = [sender presentError:error]; 169 | if (result) { 170 | return NSTerminateCancel; 171 | } 172 | 173 | NSString *question = NSLocalizedString(@"Could not save changes while quitting. Quit anyway?", @"Quit without saves error question message"); 174 | NSString *info = NSLocalizedString(@"Quitting now will lose any changes you have made since the last successful save", @"Quit without saves error question info"); 175 | NSString *quitButton = NSLocalizedString(@"Quit anyway", @"Quit anyway button title"); 176 | NSString *cancelButton = NSLocalizedString(@"Cancel", @"Cancel button title"); 177 | NSAlert *alert = [[NSAlert alloc] init]; 178 | [alert setMessageText:question]; 179 | [alert setInformativeText:info]; 180 | [alert addButtonWithTitle:quitButton]; 181 | [alert addButtonWithTitle:cancelButton]; 182 | 183 | NSInteger answer = [alert runModal]; 184 | 185 | if (answer == NSAlertAlternateReturn) { 186 | return NSTerminateCancel; 187 | } 188 | } 189 | 190 | return NSTerminateNow; 191 | } 192 | 193 | @end 194 | -------------------------------------------------------------------------------- /Telepath/TPTrackerKeyboard.m: -------------------------------------------------------------------------------- 1 | // 2 | // TPTrackerKeyboard.m 3 | // Telepath 4 | // 5 | // Created by Nick Winter on 8/31/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | #import "TPTrackerKeyboard.h" 10 | #import "TPUtilities.h" 11 | #import "TPTracker.h" 12 | #include 13 | 14 | @interface TPTrackerKeyboard () 15 | @property NSMutableString *recentCharacters; 16 | @property NSArray *reallyBadStuff; 17 | @property NSArray *punctuation; 18 | @property NSMutableString *veryBad; 19 | @property id eventMonitor; 20 | @property NSInteger previousEvents; 21 | @property (readwrite) NSInteger totalEvents; 22 | 23 | @end 24 | 25 | // TODO: Look at how Selfspy does it, since it probably handles all the modifier flags and special keys and such better. 26 | 27 | @implementation TPTrackerKeyboard 28 | 29 | - (id)init 30 | { 31 | self = [super init]; 32 | if (self) { 33 | self.recentCharacters = [NSMutableString new]; 34 | 35 | [self loadReallyBadStuff]; 36 | 37 | NSDictionary *options = @{(__bridge id)kAXTrustedCheckOptionPrompt: @YES}; 38 | BOOL accessibilityEnabled = AXIsProcessTrustedWithOptions((__bridge CFDictionaryRef)options); 39 | NSLog(@"Accessibility is enabled? %d", accessibilityEnabled); 40 | 41 | NSInteger logMask = (NSKeyDownMask|NSKeyUpMask|NSFlagsChangedMask); 42 | self.eventMonitor = [NSEvent addGlobalMonitorForEventsMatchingMask:logMask handler:^(NSEvent *e) { [self onInputEvent:e]; }]; 43 | self.previousEvents = [[NSUserDefaults standardUserDefaults] integerForKey:@"previousKeyboardEvents"]; 44 | self.totalEvents = [[NSUserDefaults standardUserDefaults] integerForKey:@"totalKeyboardEvents"]; 45 | [[NSNotificationCenter defaultCenter] addObserverForName:TPActivityClearTotals object:nil queue:nil usingBlock:^(NSNotification *note) { 46 | [[NSUserDefaults standardUserDefaults] setObject:@(self.totalEvents) forKey:@"previousKeyboardEvents"]; 47 | self.previousEvents = self.totalEvents; 48 | }]; 49 | } 50 | return self; 51 | } 52 | 53 | - (NSInteger)currentEvents { 54 | return self.totalEvents - self.previousEvents; 55 | } 56 | 57 | - (void)dealloc 58 | { 59 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 60 | } 61 | 62 | - (void)loadReallyBadStuff { 63 | // Reading list of terrible words, upon typing of which we shall beep castigatorally! 64 | NSString *filepath = [@"~/Dropbox/code/really_bad_stuff.txt" stringByExpandingTildeInPath]; 65 | NSString *contents = [NSString stringWithContentsOfFile:filepath encoding:NSUTF8StringEncoding error:nil]; 66 | self.reallyBadStuff = [[contents stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] componentsSeparatedByString:@"\n"]; 67 | self.punctuation = @[@" ", @",", @".", @";", @"?", @"!", @"'", @"\"", @"-", @"/", @"(", @")", @"[", @"]", @"\n"]; 68 | self.veryBad = [NSMutableString string]; 69 | for(NSInteger i = 0; i < [[self.reallyBadStuff lastObject] length]; ++i) 70 | [self.veryBad appendFormat:@"%C", (unichar)([[self.reallyBadStuff lastObject] characterAtIndex:i] - 1)]; 71 | } 72 | 73 | - (NSArray *) modifierKeys { 74 | if (!_modifierKeys) { 75 | _modifierKeys = @[@[@"⇪", @(NSAlphaShiftKeyMask), @(NO)].mutableCopy, 76 | @[@"⇧", @(NSShiftKeyMask), @(NO)].mutableCopy, 77 | @[@"⌃", @(NSControlKeyMask), @(NO)].mutableCopy, 78 | @[@"⌥", @(NSAlternateKeyMask), @(NO)].mutableCopy, 79 | @[@"⌘", @(NSCommandKeyMask), @(NO)].mutableCopy, 80 | @[@"", @(NSNumericPadKeyMask), @(NO)].mutableCopy, 81 | @[@"", @(NSHelpKeyMask), @(NO)].mutableCopy, 82 | @[@"", @(NSFunctionKeyMask), @(NO)].mutableCopy]; 83 | } 84 | return _modifierKeys; 85 | } 86 | 87 | - (void)checkReallyBadStuff { 88 | if(!self.reallyBadStuff) return; 89 | // Warn the user if she types despicable words 90 | for(NSString *thing in self.reallyBadStuff) 91 | for(NSString *punct in self.punctuation) { 92 | if([thing length] && [self.recentCharacters rangeOfString:[NSString stringWithFormat:@"%@%@", thing, punct] options:NSCaseInsensitiveSearch].location != NSNotFound) { 93 | NSLog(@"You shouldn't use %@!", thing); 94 | [self.recentCharacters deleteCharactersInRange:NSMakeRange(0, [self.recentCharacters length])]; 95 | NSBeep(); 96 | } 97 | } 98 | if([self.recentCharacters rangeOfString:self.veryBad].location != NSNotFound) { 99 | [self.recentCharacters deleteCharactersInRange:NSMakeRange(0, [self.recentCharacters length])]; 100 | [[NSNotificationCenter defaultCenter] postNotificationName:TPActivityKeyboardVeryBad object:self userInfo:@{@"badLength": @([self.veryBad length])}]; 101 | } 102 | } 103 | 104 | - (void)onInputEvent:(NSEvent *)e { 105 | NSEventType t = [e type]; 106 | if (t == NSKeyDown) 107 | [self onKeyEvent:e up:NO]; 108 | else if(t == NSKeyUp) 109 | [self onKeyEvent:e up:YES]; 110 | else if(t == NSFlagsChanged) 111 | [self onFlagsChangedEvent:e]; 112 | } 113 | 114 | - (void)onKeyEvent:(NSEvent *)e up:(BOOL)keyUp { 115 | NSMutableArray *event = [NSMutableArray array]; 116 | [event addObject:@(now())]; 117 | [event addObject:keyUp ? @"keyUp" : @"keyDown"]; 118 | [event addObject:[self eventCharacters: e]]; 119 | 120 | NSNumber *isText = @([event.lastObject isEqualToString:e.characters]); 121 | 122 | [[NSNotificationCenter defaultCenter] postNotificationName:TPActivityKeyboard object:self 123 | userInfo:@{@"event": event, 124 | @"totalEvents": @(++self.totalEvents), 125 | @"currentEvents": @(self.currentEvents), 126 | @"isText": isText}]; 127 | 128 | [[NSUserDefaults standardUserDefaults] setObject:@(self.totalEvents) forKey:@"totalKeyboardEvents"]; 129 | 130 | if(keyUp) { 131 | [self.recentCharacters appendString:[e characters]]; 132 | if([self.recentCharacters length] > 100) 133 | [self.recentCharacters deleteCharactersInRange:NSMakeRange(0, 1)]; 134 | [self checkReallyBadStuff]; 135 | } 136 | } 137 | 138 | /** 139 | * http://blog.elliottcable.name/posts/useful_unicode.xhtml 140 | */ 141 | 142 | - (NSString *) eventCharacters: (NSEvent *) e { 143 | switch (e.keyCode) { 144 | case 36: return @"↩"; 145 | case 48: return @"⇥"; 146 | case 49: return @" "; 147 | case 51: return @"⌫"; 148 | case 53: return @"␛"; 149 | case 123: return @"←"; 150 | case 124: return @"→"; 151 | case 125: return @"↓"; 152 | case 126: return @"↑"; 153 | default: return e.characters; 154 | } 155 | } 156 | 157 | - (void)onFlagsChangedEvent:(NSEvent *)e { 158 | NSMutableArray *event = [NSMutableArray array]; 159 | [event addObject:@(now())]; 160 | 161 | unsigned long flags = [e modifierFlags]; 162 | for(NSMutableArray *modifierKey in self.modifierKeys) { 163 | BOOL oldState = !![[modifierKey objectAtIndex:2] boolValue]; 164 | BOOL newState = !!(flags & [[modifierKey objectAtIndex:1] unsignedIntValue]); 165 | //NSLog(@"modifierKey: %@, oldState: %d, newState: %d", modifierKey, oldState, newState); 166 | if(oldState == newState) continue; 167 | [modifierKey replaceObjectAtIndex:2 withObject:[NSNumber numberWithBool:newState]]; 168 | [event addObject:newState ? @"keyDown" : @"keyUp"]; 169 | [event addObject:[modifierKey objectAtIndex:0]]; 170 | break; 171 | } 172 | if([event count] == 1) 173 | ;//NSLog(@"Hmm; modifier flags changed, but we didn't match any of the flags...? %d", flags); // happens with left/right mods 174 | else 175 | [[NSNotificationCenter defaultCenter] postNotificationName:TPActivityKeyboard object:self userInfo:@{@"event": event, @"totalEvents": @(++self.totalEvents), @"currentEvents": @(self.currentEvents), @"isText": @(NO)}]; 176 | 177 | /* 178 | Later we could make this fancier to get left/right modifier keys by using this: 179 | (Found from Googling at http://www.libsdl.org/tmp/SDL/src/video/cocoa/SDL_cocoakeyboard.m ) 180 | #ifndef NX_DEVICERCTLKEYMASK 181 | #define NX_DEVICELCTLKEYMASK 0x00000001 182 | #endif 183 | #ifndef NX_DEVICELSHIFTKEYMASK 184 | #define NX_DEVICELSHIFTKEYMASK 0x00000002 185 | #endif 186 | #ifndef NX_DEVICERSHIFTKEYMASK 187 | #define NX_DEVICERSHIFTKEYMASK 0x00000004 188 | #endif 189 | #ifndef NX_DEVICELCMDKEYMASK 190 | #define NX_DEVICELCMDKEYMASK 0x00000008 191 | #endif 192 | #ifndef NX_DEVICERCMDKEYMASK 193 | #define NX_DEVICERCMDKEYMASK 0x00000010 194 | #endif 195 | #ifndef NX_DEVICELALTKEYMASK 196 | #define NX_DEVICELALTKEYMASK 0x00000020 197 | #endif 198 | #ifndef NX_DEVICERALTKEYMASK 199 | #define NX_DEVICERALTKEYMASK 0x00000040 200 | #endif 201 | #ifndef NX_DEVICERCTLKEYMASK 202 | #define NX_DEVICERCTLKEYMASK 0x00002000 203 | #endif 204 | */ 205 | } 206 | 207 | @end 208 | -------------------------------------------------------------------------------- /Telepath/TPTracker.m: -------------------------------------------------------------------------------- 1 | // 2 | // TPTracker.m 3 | // Telepath 4 | // 5 | // Created by Nick Winter on 8/31/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | #import "TPTracker.h" 10 | #import "TPUtilities.h" 11 | #import "TPTrackerKeyboard.h" 12 | #import "TPTrackerMouse.h" 13 | #import "TPTrackerWindow.h" 14 | #import "TPTrackerLight.h" 15 | #import "TPTrackerCamera.h" 16 | #import "TPTrackerGitHub.h" 17 | #import "TPTrackerTrello.h" 18 | #import "TPTrackerBrunchBuilds.h" 19 | #import "TPTrackerEmail.h" 20 | #import "TPTrackerWorkHours.h" 21 | #import "TPTrackerScreen.h" 22 | #import "TPTrackerHappiness.h" 23 | #import "ImageSnap.h" 24 | 25 | @interface TPTracker () 26 | 27 | /* Trackers */ 28 | @property TPTrackerKeyboard *trackerKeyboard; 29 | @property TPTrackerMouse *trackerMouse; 30 | @property TPTrackerWindow *trackerWindow; 31 | @property TPTrackerLight *trackerLight; 32 | @property TPTrackerCamera *trackerCamera; 33 | @property TPTrackerGitHub *trackerGitHub; 34 | @property TPTrackerTrello *trackerTrello; 35 | @property TPTrackerBrunchBuilds *trackerBrunchBuilds; 36 | @property TPTrackerEmail *trackerEmail; 37 | @property TPTrackerWorkHours *trackerWorkHours; 38 | @property TPTrackerScreen *trackerScreen; 39 | @property TPTrackerHappiness *trackerHappiness; 40 | 41 | /* Event logging */ 42 | @property NSInteger previousEvents; 43 | @property (readwrite) NSInteger totalEvents; 44 | @property NSMutableArray *eventsToLog; 45 | 46 | /* File state */ 47 | @property NSString *outputDir; 48 | @property NSFileHandle *logFile; 49 | @property NSTimer *logFileSwitchTimer; 50 | @property NSTimer *logFileWriteTimer; 51 | 52 | @end 53 | 54 | NSString * const TPActivityClearTotals = @"TPActivityClearTotals"; 55 | NSString * const TPActivityAny = @"TPActivityAny"; 56 | NSString * const TPActivityKeyboard = @"TPActivityKeyboard"; 57 | NSString * const TPActivityKeyboardVeryBad = @"TPActivityKeyboardVeryBad"; 58 | NSString * const TPActivityMouse = @"TPActivityMouse"; 59 | NSString * const TPActivityWindow = @"TPActivityWindow"; 60 | NSString * const TPActivityLight = @"TPActivityLight"; 61 | NSString * const TPActivityCamera = @"TPActivityCamera"; 62 | NSString * const TPActivityGitHub = @"TPActivityGitHub"; 63 | NSString * const TPActivityTrello = @"TPActivityTrello"; 64 | NSString * const TPActivityBrunchBuild = @"TPActivityBrunchBuild"; 65 | NSString * const TPActivityEmail = @"TPActivityEmail"; 66 | NSString * const TPActivityWorkHours = @"TPActivityWorkHours"; 67 | NSString * const TPActivityScreen = @"TPActivityScreen"; 68 | NSString * const TPActivityHappiness = @"TPActivityHappiness"; 69 | 70 | /// We'll switch log files this often. 71 | const double FILE_SWITCH_INTERVAL = 1 * 24 * 60 * 60; 72 | 73 | /// We'll write accumulated events to a log file this often. 74 | const double FILE_WRITE_INTERVAL = 1; 75 | 76 | @implementation TPTracker 77 | 78 | - (id)init 79 | { 80 | self = [super init]; 81 | if (self) { 82 | self.eventsToLog = [NSMutableArray array]; 83 | self.logFileSwitchTimer = [NSTimer scheduledTimerWithTimeInterval:FILE_SWITCH_INTERVAL target:self selector:@selector(openNewFile:) userInfo:nil repeats:YES]; 84 | self.logFileWriteTimer = [NSTimer scheduledTimerWithTimeInterval:FILE_WRITE_INTERVAL target:self selector:@selector(writeLogs:) userInfo:nil repeats:YES]; 85 | NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; 86 | self.trackerKeyboard = [TPTrackerKeyboard new]; 87 | self.trackerMouse = [TPTrackerMouse new]; 88 | self.trackerWindow = [TPTrackerWindow new]; 89 | self.trackerLight = [TPTrackerLight new]; 90 | //NSTimeInterval cameraRecordingInterval = 86400.0 / 30.0 / 60.0; // Default: one minute per day at 30 FPS (1 shot every 48s) 91 | //NSTimeInterval cameraRecordingInterval = 60; // Once a minute 92 | NSTimeInterval targetLength = 367; // Song length 6:07, leads to ~55s 93 | NSTimeInterval cameraRecordingInterval = 7 * 86400 / 30.0 / targetLength; 94 | //cameraRecordingInterval = 10; // Testing: every 10s. 95 | //NSTimeInterval cameraPreviewInterval = 1 / 10.0; 96 | //NSTimeInterval cameraPreviewInterval = 1 / 3.0; 97 | NSTimeInterval cameraPreviewInterval = 1; // / 3.0; 98 | //NSLog(@"Would set up screen recording with recording interval %f and preview interval %f, but not recording screen right now", cameraRecordingInterval, cameraPreviewInterval); 99 | self.trackerCamera = [[TPTrackerCamera alloc] initWithRecordingInterval:cameraRecordingInterval andPreviewInterval:cameraPreviewInterval]; 100 | self.trackerGitHub = [TPTrackerGitHub new]; 101 | self.trackerTrello = [TPTrackerTrello new]; 102 | self.trackerBrunchBuilds = [TPTrackerBrunchBuilds new]; 103 | self.trackerEmail = [TPTrackerEmail new]; 104 | self.trackerWorkHours = [TPTrackerWorkHours new]; 105 | self.trackerScreen = [[TPTrackerScreen alloc] initWithRecordingInterval:cameraRecordingInterval]; 106 | self.trackerHappiness = [[TPTrackerHappiness alloc] initWithPingInterval:3 * 60 * 60]; 107 | self.previousEvents = [[NSUserDefaults standardUserDefaults] integerForKey:@"previousEvents"]; 108 | self.totalEvents = [[NSUserDefaults standardUserDefaults] integerForKey:@"totalEvents"]; 109 | [nc addObserverForName:TPActivityClearTotals object:nil queue:nil usingBlock:^(NSNotification *note) { 110 | [[NSUserDefaults standardUserDefaults] setObject:@(self.totalEvents) forKey:@"previousEvents"]; 111 | self.previousEvents = self.totalEvents; 112 | }]; 113 | [nc addObserver:self selector:@selector(onActivityKeyboard:) name:TPActivityKeyboard object:self.trackerKeyboard]; 114 | [nc addObserver:self selector:@selector(onActivityMouse:) name:TPActivityMouse object:self.trackerMouse]; 115 | [nc addObserver:self selector:@selector(onActivityWindow:) name:TPActivityWindow object:self.trackerWindow]; 116 | [nc addObserver:self selector:@selector(onActivityLight:) name:TPActivityLight object:self.trackerLight]; 117 | [nc addObserver:self selector:@selector(onActivityCamera:) name:TPActivityCamera object:self.trackerCamera]; 118 | [nc addObserver:self selector:@selector(onActivityScreen:) name:TPActivityScreen object:self.trackerScreen]; 119 | } 120 | return self; 121 | } 122 | 123 | - (NSInteger)currentEvents { 124 | return self.totalEvents - self.previousEvents; 125 | } 126 | 127 | - (NSTimeInterval)cameraRecordingInterval { 128 | return self.trackerCamera.recordingInterval; 129 | } 130 | 131 | - (void)setCameraRecordingInterval:(NSTimeInterval)cameraRecordingInterval { 132 | self.trackerCamera.recordingInterval = cameraRecordingInterval; 133 | } 134 | 135 | - (void)dealloc 136 | { 137 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 138 | [self.logFile closeFile]; 139 | } 140 | 141 | #pragma mark - Activity 142 | 143 | - (void)logEvent:(NSArray *)event { 144 | [self.eventsToLog addObject:JSONRepresentation(event)]; 145 | [[NSUserDefaults standardUserDefaults] setObject:@(++self.totalEvents) forKey:@"totalEvents"]; 146 | [[NSNotificationCenter defaultCenter] postNotificationName:TPActivityAny object:self]; 147 | // Distributed notifications got really slow in 10.9? 148 | //[[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"net.nickwinter.Telepath.TrackerEvent" object:nil userInfo:[NSDictionary dictionaryWithObjectsAndKeys:event, @"event", nil]]; 149 | } 150 | 151 | - (void)onActivityKeyboard:(NSNotification *)note { 152 | [self logEvent:note.userInfo[@"event"]]; 153 | } 154 | 155 | - (void)onActivityMouse:(NSNotification *)note { 156 | [self logEvent:note.userInfo[@"event"]]; 157 | } 158 | 159 | - (void)onActivityWindow:(NSNotification *)note { 160 | [self logEvent:note.userInfo[@"event"]]; 161 | } 162 | 163 | - (void)onActivityLight:(NSNotification *)note { 164 | [self logEvent:note.userInfo[@"event"]]; 165 | } 166 | 167 | - (void)onActivityCamera:(NSNotification *)note { 168 | return; // Don't save camera images for now 169 | [self saveImage:note.userInfo[@"image"] withExtension:@"jpg"]; 170 | } 171 | 172 | - (void)onActivityScreen:(NSNotification *)note { 173 | [self saveImage:note.userInfo[@"image"] withExtension:@"jpg"]; 174 | } 175 | 176 | #pragma mark - File writing 177 | 178 | - (void)ensureOutputDirExists { 179 | NSString *path = [@"~/Desktop/Hog/Storage/Telepath/winter/Telepath" stringByExpandingTildeInPath]; 180 | if([[NSFileManager defaultManager] fileExistsAtPath:path]) 181 | self.outputDir = path; 182 | else 183 | self.outputDir = [@"~/Library/Application Support/Telepath/" stringByExpandingTildeInPath]; // If you're not me. 184 | [[NSFileManager defaultManager] createDirectoryAtPath:self.outputDir withIntermediateDirectories:YES attributes:nil error:nil]; 185 | } 186 | 187 | - (void)openNewFile:(NSTimer *)t { 188 | [self.logFile closeFile]; 189 | [self ensureOutputDirExists]; 190 | NSDate *date = [NSDate date]; 191 | unsigned long long ms = (long long)([date timeIntervalSince1970] * 1000); 192 | NSError *err; 193 | NSString *path = [NSString stringWithFormat:@"%@/%@-%.3d-Z.log", self.outputDir, [date descriptionWithCalendarFormat:@"%Y-%m-%d-%H-%M-%S" timeZone:[NSTimeZone timeZoneForSecondsFromGMT:0] locale:[[NSUserDefaults standardUserDefaults] dictionaryRepresentation]], (int)(ms % 1000)]; 194 | 195 | if(![[NSFileManager defaultManager] createFileAtPath:path contents:[NSData data] attributes:nil]) { 196 | NSLog(@"FAIL creating file"); 197 | } 198 | 199 | self.logFile = [NSFileHandle fileHandleForWritingToURL:[NSURL fileURLWithPath:path] error:&err]; 200 | if(!self.logFile) { 201 | NSLog(@"FAIL opening file %@", err); 202 | } 203 | } 204 | 205 | - (void)writeLogs:(NSTimer *)timer { 206 | if(![self.eventsToLog count]) return; 207 | if(!self.logFile) 208 | [self openNewFile:nil]; 209 | NSString *logEntries = [[self.eventsToLog componentsJoinedByString:@"\n"] stringByAppendingString:@"\n"]; 210 | NSData *logEntriesData = [logEntries dataUsingEncoding:NSUTF8StringEncoding]; 211 | [self.logFile writeData:logEntriesData]; 212 | [self.eventsToLog removeAllObjects]; 213 | } 214 | 215 | - (void)saveImage:(NSImage *)image withExtension:(NSString *)extension { 216 | if(!image) return; 217 | [self ensureOutputDirExists]; 218 | NSDate *date = [NSDate date]; 219 | unsigned long long ms = (long long)([date timeIntervalSince1970] * 1000); 220 | NSString *filename = [NSString stringWithFormat:@"%@/%@-%.3d-Z.%@", self.outputDir, [date descriptionWithCalendarFormat:@"%Y-%m-%d-%H-%M-%S" timeZone:[NSTimeZone timeZoneForSecondsFromGMT:0] locale:[[NSUserDefaults standardUserDefaults] dictionaryRepresentation]], (int)(ms % 1000), extension]; 221 | [ImageSnap saveImage:image toPath:filename]; 222 | } 223 | 224 | @end 225 | -------------------------------------------------------------------------------- /Telepath/TPHUDWindowController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TPHUDWindowController.m 3 | // Telepath 4 | // 5 | // Created by Nick Winter on 8/30/13. 6 | // Copyright (c) 2013 Nick Winter. All rights reserved. 7 | // 8 | 9 | #import "TPHUDWindowController.h" 10 | #import "ImageSnap.h" 11 | #import "TPLightSensor.h" 12 | #import "TPTracker.h" 13 | #import 14 | 15 | @interface TPHUDWindowController () 16 | // Camera Area 17 | @property (unsafe_unretained) IBOutlet NSImageView *cameraImage; 18 | @property (weak) IBOutlet NSTextField *cameraTimerLabel; 19 | 20 | // Time Area 21 | @property (weak) IBOutlet NSTextFieldCell *timeLabel; 22 | @property (weak) IBOutlet NSTextFieldCell *dayLabel; 23 | @property (weak) IBOutlet NSTextField *sessionHoursLabel; 24 | @property (weak) IBOutlet NSTextField *dayHoursLabel; 25 | @property (weak) IBOutlet NSTextField *weekHoursLabel; 26 | 27 | // Random Stats Area 28 | @property (weak) IBOutlet NSTextField *keystrokesField; 29 | @property (weak) IBOutlet NSTextField *mouseMovementsField; 30 | @property (weak) IBOutlet NSTextField *windowSwitchesField; 31 | @property (weak) IBOutlet NSTextField *commitsField; 32 | @property (weak) IBOutlet NSTextField *additionsField; 33 | @property (weak) IBOutlet NSTextField *deletionsField; 34 | @property (weak) IBOutlet NSTextField *trellosSlainField; 35 | @property (weak) IBOutlet NSTextField *trellosRemainingField; 36 | @property (weak) IBOutlet NSTextField *buildsField; 37 | @property (weak) IBOutlet NSTextField *unreadEmailsField; 38 | 39 | // Keyboard Area 40 | @property (weak) IBOutlet NSTextField *currentWindowField; 41 | @property (weak) IBOutlet NSTextField *currentDocumentField; 42 | @property (unsafe_unretained) IBOutlet NSTextView *recentKeysView; 43 | 44 | // Activity Area 45 | @property (weak) IBOutlet NSComboBox *activityBox; 46 | @property (weak) IBOutlet NSTextField *activityDetailField; 47 | 48 | // Affect Area 49 | @property (weak) IBOutlet NSTextField *artistField; 50 | @property (weak) IBOutlet NSTextField *songField; 51 | @property (weak) IBOutlet NSTextField *happinessField; 52 | @property (weak) IBOutlet NSTextField *energyField; 53 | @property (weak) IBOutlet NSTextField *healthField; 54 | @property (weak) IBOutlet NSTextField *happinessLabel; 55 | @property (weak) IBOutlet NSTextField *energyLabel; 56 | @property (weak) IBOutlet NSTextField *healthLabel; 57 | 58 | // Timelapse Progress Area (or is it Percentile Feedback Area?) 59 | @property (weak) IBOutlet WebView *percentileFeedbackView; 60 | 61 | 62 | @property TPTracker *tracker; 63 | @property NSTimer *timeUpdateTimer; 64 | @property NSString *currentActivity; 65 | @property BOOL isWorking; 66 | 67 | @end 68 | 69 | @implementation TPHUDWindowController 70 | 71 | - (id)initWithWindow:(NSWindow *)window { 72 | self = [super initWithWindow:window]; 73 | if (self) { 74 | [self.window setContentBorderThickness:0.0f forEdge:NSMinYEdge]; 75 | [self.window setContentBorderThickness:0.0f forEdge:NSMaxYEdge]; 76 | } 77 | return self; 78 | } 79 | 80 | - (void)windowDidLoad { 81 | [super windowDidLoad]; 82 | [self.window setLevel:NSFloatingWindowLevel]; 83 | [self.window setCollectionBehavior:NSWindowCollectionBehaviorStationary|NSWindowCollectionBehaviorCanJoinAllSpaces|NSWindowCollectionBehaviorFullScreenAuxiliary]; 84 | [self.window becomeKeyWindow]; 85 | [self.window setOpaque:NO]; 86 | [self.window setBackgroundColor:[[self.window backgroundColor] colorWithAlphaComponent:0.75]]; 87 | 88 | NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; 89 | [nc addObserver:self selector:@selector(onActivityKeyboard:) name:TPActivityKeyboard object:nil]; 90 | [nc addObserver:self selector:@selector(onActivityKeyboardVeryBad:) name:TPActivityKeyboardVeryBad object:nil]; 91 | [nc addObserver:self selector:@selector(onActivityMouse:) name:TPActivityMouse object:nil]; 92 | [nc addObserver:self selector:@selector(onActivityWindow:) name:TPActivityWindow object:nil]; 93 | [nc addObserver:self selector:@selector(onActivityLight:) name:TPActivityLight object:nil]; 94 | [nc addObserver:self selector:@selector(onActivityCamera:) name:TPActivityCamera object:nil]; 95 | [nc addObserver:self selector:@selector(onActivityGitHub:) name:TPActivityGitHub object:nil]; 96 | [nc addObserver:self selector:@selector(onActivityTrello:) name:TPActivityTrello object:nil]; 97 | [nc addObserver:self selector:@selector(onActivityBrunchBuild:) name:TPActivityBrunchBuild object:nil]; 98 | [nc addObserver:self selector:@selector(onActivityEmail:) name:TPActivityEmail object:nil]; 99 | [nc addObserver:self selector:@selector(onActivityWorkHours:) name:TPActivityWorkHours object:nil]; 100 | [nc addObserver:self selector:@selector(onActivityHappiness:) name:TPActivityHappiness object:nil]; 101 | [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(onWorkChanged:) name:@"net.nickwinter.Telepath.WorkChanged" object:nil]; 102 | [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(onActivityHappiness:) name:@"net.nickwinter.Telepath.HappinessChanged" object:nil]; 103 | [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(onSongChanged:) name:@"com.apple.iTunes.playerInfo" object:nil]; 104 | [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(onSongChanged:) name:@"com.spotify.client.PlaybackStateChanged" object:nil]; 105 | //[[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(onAnyDistributedNotification:) name:nil object:nil]; 106 | 107 | self.timeUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(updateTime:) userInfo:nil repeats:YES]; 108 | 109 | [self setUpActivityBox]; 110 | self.tracker = [TPTracker new]; 111 | [self.percentileFeedbackView setMainFrameURL:@"http://www.nickwinter.net/codecombat-stats?just_graph=1"]; 112 | NSView *clipView = [[[self.percentileFeedbackView.mainFrame frameView] documentView] superview]; 113 | [clipView scaleUnitSquareToSize:NSMakeSize(0.586, 0.586)]; 114 | [clipView setNeedsDisplay:YES]; 115 | [self setFonts]; 116 | [self.artistField setHidden:YES]; 117 | [self.songField setHidden:YES]; 118 | [self onActivityHappiness:nil]; // Hide these fields until a ping happens 119 | } 120 | 121 | - (NSArray *)allSubviewsOfView:(NSView *)view { 122 | NSMutableArray *allSubviews = [NSMutableArray arrayWithObject:view]; 123 | NSArray *subviews = [view subviews]; 124 | for (NSView *subview in subviews) 125 | [allSubviews addObjectsFromArray:[self allSubviewsOfView:subview]]; 126 | return [allSubviews copy]; 127 | } 128 | 129 | - (void)setFonts { 130 | NSColor *textColor = [NSColor colorWithWhite:0.35 alpha:1.0]; 131 | for(id view in [self allSubviewsOfView:self.window.contentView]) { 132 | if(![view respondsToSelector:@selector(setFont:)]) continue; 133 | NSFont *font = (NSFont *)[view font]; 134 | font = [NSFont fontWithName:@"OpenSans-Light" size:font.pointSize]; 135 | if([view respondsToSelector:@selector(textColor)]) { 136 | NSColor *color = ((NSTextField *)view).textColor; 137 | if([color isEqual:[NSColor keyboardFocusIndicatorColor]]) 138 | [view setTextColor:textColor]; 139 | if(font.pointSize < 24 && ![color isEqual:[NSColor controlTextColor]]) 140 | font = [NSFont fontWithName:@"OpenSans-Semibold" size:font.pointSize]; 141 | } 142 | [view setFont:(id)font]; 143 | } 144 | } 145 | 146 | - (void)dealloc { 147 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 148 | } 149 | 150 | - (NSImage *)cropImage:(NSImage *)source toSize:(NSSize)size { 151 | NSImage *resultImage = [[NSImage alloc] initWithSize:size]; 152 | [resultImage lockFocus]; 153 | [source drawAtPoint:NSMakePoint(0, 0) fromRect:NSMakeRect((source.size.width - size.width) / 2, 0, size.width, size.height) operation:NSCompositeSourceOver fraction:1]; 154 | [resultImage unlockFocus]; 155 | return resultImage; 156 | } 157 | 158 | 159 | - (void)onActivityKeyboard:(NSNotification *)note { 160 | [self.keystrokesField setStringValue:[NSString stringWithFormat:@"%@", note.userInfo[@"currentEvents"]]]; 161 | NSArray *event = note.userInfo[@"event"]; 162 | BOOL down = [event[1] isEqualToString:@"keyDown"]; 163 | if(down) { 164 | NSInteger fontSize = [note.userInfo[@"isText"] boolValue] ? 24 : 13; 165 | NSAttributedString *newText = [[NSAttributedString alloc] initWithString:event[2] attributes:@{NSFontAttributeName: [NSFont fontWithName:@"OpenSans-Semibold" size:fontSize], NSForegroundColorAttributeName: [NSColor colorWithWhite:0.35 alpha:1.0]}]; 166 | [self.recentKeysView.textStorage appendAttributedString:newText]; 167 | NSInteger length = [self.recentKeysView.textStorage length]; 168 | if(length > 500) 169 | [self.recentKeysView.textStorage replaceCharactersInRange:NSMakeRange(0, length - 500) withString:@""]; 170 | [self.recentKeysView scrollToEndOfDocument:nil]; 171 | } 172 | } 173 | 174 | - (void)onActivityKeyboardVeryBad:(NSNotification *)note { 175 | [self.recentKeysView.textStorage replaceCharactersInRange:NSMakeRange([self.recentKeysView.textStorage length] - [note.userInfo[@"badLength"] intValue], [note.userInfo[@"badLength"] intValue]) withString:@""]; 176 | } 177 | 178 | - (void)onActivityMouse:(NSNotification *)note { 179 | [self.mouseMovementsField setStringValue:[NSString stringWithFormat:@"%@", note.userInfo[@"currentEvents"]]]; 180 | } 181 | 182 | - (void)onActivityWindow:(NSNotification *)note { 183 | [self.windowSwitchesField setStringValue:[NSString stringWithFormat:@"%@", note.userInfo[@"currentEvents"]]]; 184 | NSArray *event = note.userInfo[@"event"]; 185 | [self.currentWindowField setStringValue:event[2]]; 186 | [self.currentDocumentField setStringValue:event[1]]; 187 | } 188 | 189 | - (void)onActivityLight:(NSNotification *)note { 190 | 191 | } 192 | 193 | - (void)onActivityCamera:(NSNotification *)note { 194 | self.cameraImage.image = note.userInfo[@"image"]; 195 | NSTimeInterval countdown = [note.userInfo[@"countdown"] doubleValue]; 196 | self.cameraTimerLabel.alphaValue = countdown < 0.5 ? 0 : countdown < 5 ? 1.0 : countdown < 10 ? 0.5 : 0.2; 197 | [self.cameraTimerLabel setStringValue:[NSString stringWithFormat:@"%.0f", countdown]]; 198 | } 199 | 200 | - (void)onActivityGitHub:(NSNotification *)note { 201 | [self.commitsField setStringValue:[NSString stringWithFormat:@"%@", note.userInfo[@"currentCommits"]]]; 202 | [self.additionsField setStringValue:[NSString stringWithFormat:@"%@++", note.userInfo[@"currentAdditions"]]]; 203 | [self.deletionsField setStringValue:[NSString stringWithFormat:@"%@--", note.userInfo[@"currentDeletions"]]]; 204 | //[self.additionsField setStringValue:[NSString stringWithFormat:@"%@++", @(57824)]]; 205 | //[self.deletionsField setStringValue:[NSString stringWithFormat:@"%@--", @(13800)]]; 206 | } 207 | 208 | - (void)onActivityTrello:(NSNotification *)note { 209 | [self.trellosSlainField setStringValue:[NSString stringWithFormat:@"%@", note.userInfo[@"currentTrellosSlain"]]]; 210 | [self.trellosRemainingField setStringValue:[NSString stringWithFormat:@"%@", note.userInfo[@"trellosAlive"]]]; 211 | } 212 | 213 | - (void)onActivityBrunchBuild:(NSNotification *)note { 214 | [self.buildsField setStringValue:[NSString stringWithFormat:@"%@", note.userInfo[@"currentEvents"]]]; 215 | } 216 | 217 | - (void)onActivityEmail:(NSNotification *)note { 218 | [self.unreadEmailsField setStringValue:[NSString stringWithFormat:@"%@", note.userInfo[@"unreadEmails"]]]; 219 | } 220 | 221 | - (void)onActivityWorkHours:(NSNotification *)note { 222 | float sessionHours = [note.userInfo[@"sessionHours"] floatValue]; 223 | float dayHours = [note.userInfo[@"dayHours"] floatValue]; 224 | float weekHours = [note.userInfo[@"weekHours"] floatValue]; 225 | [self.sessionHoursLabel setStringValue:[NSString stringWithFormat:@"%d:%02d", (int)sessionHours, (int)(60 * sessionHours) % 60]]; 226 | [self.dayHoursLabel setStringValue:[NSString stringWithFormat:@"%d:%02d", (int)dayHours, (int)(60 * dayHours) % 60]]; 227 | [self.weekHoursLabel setStringValue:[NSString stringWithFormat:@"%d:%02d", (int)weekHours, (int)(60 * weekHours) % 60]]; 228 | BOOL working = [note.userInfo[@"working"] boolValue]; 229 | if(self.isWorking != working) { 230 | self.isWorking = working; 231 | NSSound *listen = [NSSound soundNamed:@"listen"]; 232 | [listen play]; 233 | } 234 | } 235 | 236 | - (void)onActivityHappiness:(NSNotification *)note { 237 | self.happinessField.alphaValue = self.energyField.alphaValue = self.healthField.alphaValue = 1.0; 238 | self.happinessLabel.alphaValue = self.energyLabel.alphaValue = self.healthLabel.alphaValue = 1.0; 239 | NSTimeInterval fadeDuration = 10 * 60; 240 | if(!note) 241 | fadeDuration = 1.0; 242 | else if(note.userInfo[@"happiness"]) { 243 | [self.happinessField setStringValue:[NSString stringWithFormat:@"%@", note.userInfo[@"happiness"]]]; 244 | [self.energyField setStringValue:[NSString stringWithFormat:@"%@", note.userInfo[@"energy"]]]; 245 | [self.healthField setStringValue:[NSString stringWithFormat:@"%@", note.userInfo[@"health"]]]; 246 | fadeDuration = 1.5 * 60 * 60; 247 | } 248 | else { 249 | [self.happinessField setStringValue:@"?"]; 250 | [self.energyField setStringValue:@"?"]; 251 | [self.healthField setStringValue:@"?"]; 252 | } 253 | [NSAnimationContext beginGrouping]; 254 | [[NSAnimationContext currentContext] setDuration:fadeDuration]; 255 | [[self.happinessField animator] setAlphaValue:0.0]; 256 | [[self.energyField animator] setAlphaValue:0.0]; 257 | [[self.healthField animator] setAlphaValue:0.0]; 258 | [[self.happinessLabel animator] setAlphaValue:0.0]; 259 | [[self.energyLabel animator] setAlphaValue:0.0]; 260 | [[self.healthLabel animator] setAlphaValue:0.0]; 261 | [NSAnimationContext endGrouping]; 262 | } 263 | 264 | - (void)onWorkChanged:(NSNotification *)note { 265 | [self.percentileFeedbackView reload:nil]; 266 | } 267 | 268 | - (void)onSongChanged:(NSNotification *)note { 269 | NSString *artist = note.userInfo[@"Artist"]; 270 | NSString *song = note.userInfo[@"Name"]; 271 | BOOL playing = [note.userInfo[@"Player State"] isEqualToString:@"Playing"]; 272 | self.songField.hidden = self.artistField.hidden = !playing; 273 | [self.artistField setStringValue:artist]; 274 | [self.songField setStringValue:song]; 275 | } 276 | 277 | - (void)onAnyDistributedNotification:(NSNotification *)note { 278 | NSLog(@"<%p>%s: object: %@ name: %@ userInfo: %@", self, __PRETTY_FUNCTION__, note.object, note.name, note.userInfo); 279 | } 280 | 281 | - (void)updateTime:(NSTimer *)timer { 282 | NSDate *date = [NSDate date]; 283 | NSDateFormatter *dayFormat = [NSDateFormatter new]; 284 | [dayFormat setDateFormat:@"EEEE"]; 285 | NSDateFormatter *timeFormat = [NSDateFormatter new]; 286 | [timeFormat setDateFormat:@"HH:mm"]; 287 | [self.dayLabel setStringValue:[dayFormat stringFromDate:date]]; 288 | [self.timeLabel setStringValue:[timeFormat stringFromDate:date]]; 289 | //[self.dayLabel setStringValue:@"Wednesday"]; 290 | //[self.timeLabel setStringValue:@"10:32"]; 291 | } 292 | 293 | - (void)setUpActivityBox { 294 | NSArray *pastActivities = [[NSUserDefaults standardUserDefaults] arrayForKey:@"activities"]; 295 | NSMutableArray *activities = [NSMutableArray arrayWithArray:pastActivities ? pastActivities : @[@"Coding", @"Sleeping", @"Eating"]]; 296 | self.currentActivity = [[NSUserDefaults standardUserDefaults] stringForKey:@"currentActivity"]; 297 | if(self.currentActivity && ![activities containsObject:self.currentActivity]) 298 | [activities addObject:self.currentActivity]; 299 | [self.activityBox addItemsWithObjectValues:activities]; 300 | if(self.currentActivity) 301 | [self.activityBox selectItemWithObjectValue:self.currentActivity]; 302 | void (^onChangedBlock)(NSNotification *note) = ^(NSNotification *note) { 303 | if([note.name isEqualToString:NSComboBoxSelectionDidChangeNotification]) 304 | self.currentActivity = [self.activityBox objectValueOfSelectedItem]; 305 | else 306 | self.currentActivity = [self.activityBox stringValue]; 307 | [[NSUserDefaults standardUserDefaults] setObject:self.currentActivity forKey:@"currentActivity"]; 308 | if(![activities containsObject:self.currentActivity]) { 309 | [activities addObject:self.currentActivity]; 310 | [self.activityBox addItemWithObjectValue:self.currentActivity]; 311 | [[NSUserDefaults standardUserDefaults] setObject:activities forKey:@"activities"]; 312 | } 313 | [[NSUserDefaults standardUserDefaults] synchronize]; 314 | }; 315 | [[NSNotificationCenter defaultCenter] addObserverForName:NSComboBoxSelectionDidChangeNotification object:self.activityBox queue:nil usingBlock:onChangedBlock]; 316 | [[NSNotificationCenter defaultCenter] addObserverForName:NSControlTextDidEndEditingNotification object:self.activityBox queue:nil usingBlock:onChangedBlock]; 317 | 318 | NSString *currentActivityDetail = [[NSUserDefaults standardUserDefaults] stringForKey:@"currentActivityDetail"]; 319 | if(currentActivityDetail) 320 | self.activityDetailField.stringValue = currentActivityDetail; 321 | } 322 | 323 | - (IBAction)onActivityDetailChanged:(id)sender { 324 | NSString *detail = self.activityDetailField.stringValue; 325 | [[NSUserDefaults standardUserDefaults] setObject:detail forKey:@"currentActivityDetail"]; 326 | [[NSUserDefaults standardUserDefaults] synchronize]; 327 | 328 | //NSURL *url = [NSURL URLWithString:@"https://www.leftronic.com/customSend/"]; 329 | //NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; 330 | //request.HTTPMethod = @"POST"; 331 | //request.HTTPBody = [[NSString stringWithFormat:@"{\"accessKey\": \"some-access-key-string\", \"streamName\": \"some-stream-name\", \"point\": {\"label\": \"Nick: %@\"}}", detail] dataUsingEncoding:NSUTF8StringEncoding]; 332 | //NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:nil startImmediately:YES]; 333 | //if(!connection) NSLog(@"pfft"); 334 | } 335 | 336 | - (IBAction)onRandomStatsClearClicked:(id)sender { 337 | NSLog(@"Clear totals!"); 338 | [[NSNotificationCenter defaultCenter] postNotificationName:TPActivityClearTotals object:self userInfo:nil]; 339 | } 340 | 341 | @end 342 | -------------------------------------------------------------------------------- /Telepath/ImageSnap.m: -------------------------------------------------------------------------------- 1 | // 2 | // ImageSnap.m 3 | // ImageSnap 4 | // 5 | // Created by Robert Harder on 9/10/09. 6 | // 7 | 8 | #import "ImageSnap.h" 9 | 10 | 11 | #define error(...) fprintf(stderr, __VA_ARGS__) 12 | #define console(...) (!g_quiet && printf(__VA_ARGS__)) 13 | #define verbose(...) (g_verbose && !g_quiet && fprintf(stderr, __VA_ARGS__)) 14 | 15 | BOOL g_verbose = NO; 16 | BOOL g_quiet = NO; 17 | //double g_timelapse = -1; 18 | NSString *VERSION = @"0.2.5"; 19 | 20 | 21 | @interface ImageSnap() 22 | 23 | 24 | - (void)captureOutput:(QTCaptureOutput *)captureOutput 25 | didOutputVideoFrame:(CVImageBufferRef)videoFrame 26 | withSampleBuffer:(QTSampleBuffer *)sampleBuffer 27 | fromConnection:(QTCaptureConnection *)connection; 28 | 29 | @end 30 | 31 | 32 | @implementation ImageSnap 33 | 34 | 35 | 36 | - (id)init{ 37 | self = [super init]; 38 | mCaptureSession = nil; 39 | mCaptureDeviceInput = nil; 40 | mCaptureDecompressedVideoOutput = nil; 41 | mCaptureVideoPreviewOutput = nil; 42 | mCurrentImageBuffer = nil; 43 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onDeviceDisconnected:) name:QTCaptureDeviceWasDisconnectedNotification object:nil]; 44 | return self; 45 | } 46 | 47 | - (void)dealloc{ 48 | 49 | if( mCaptureSession ) [mCaptureSession release]; 50 | if( mCaptureDeviceInput ) [mCaptureDeviceInput release]; 51 | if( mCaptureDecompressedVideoOutput ) [mCaptureDecompressedVideoOutput release]; 52 | if( mCaptureVideoPreviewOutput ) [mCaptureVideoPreviewOutput release]; 53 | CVBufferRelease(mCurrentImageBuffer); 54 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 55 | 56 | [super dealloc]; 57 | } 58 | 59 | 60 | // Returns an array of video devices attached to this computer. 61 | + (NSArray *)videoDevices{ 62 | NSMutableArray *results = [NSMutableArray arrayWithCapacity:3]; 63 | [results addObjectsFromArray:[QTCaptureDevice inputDevicesWithMediaType:QTMediaTypeVideo]]; 64 | [results addObjectsFromArray:[QTCaptureDevice inputDevicesWithMediaType:QTMediaTypeMuxed]]; 65 | return results; 66 | } 67 | 68 | // Returns the default video device or nil if none found. 69 | + (QTCaptureDevice *)defaultVideoDevice{ 70 | QTCaptureDevice *device = nil; 71 | 72 | device = [QTCaptureDevice defaultInputDeviceWithMediaType:QTMediaTypeVideo]; 73 | if( device == nil ){ 74 | device = [QTCaptureDevice defaultInputDeviceWithMediaType:QTMediaTypeMuxed]; 75 | } 76 | return device; 77 | } 78 | 79 | // Returns the named capture device or nil if not found. 80 | +(QTCaptureDevice *)deviceNamed:(NSString *)name{ 81 | QTCaptureDevice *result = nil; 82 | 83 | NSArray *devices = [ImageSnap videoDevices]; 84 | for( QTCaptureDevice *device in devices ){ 85 | if ( [name isEqualToString:[device description]] ){ 86 | result = device; 87 | } // end if: match 88 | } // end for: each device 89 | 90 | return result; 91 | } // end 92 | 93 | 94 | // Saves an image to a file or standard out if path is nil or "-" (hyphen). 95 | + (BOOL) saveImage:(NSImage *)image toPath: (NSString*)path{ 96 | 97 | NSString *ext = [path pathExtension]; 98 | NSData *photoData = [ImageSnap dataFrom:image asType:ext]; 99 | 100 | // If path is a dash, that means write to standard out 101 | if( path == nil || [@"-" isEqualToString:path] ){ 102 | NSUInteger length = [photoData length]; 103 | NSUInteger i; 104 | char *start = (char *)[photoData bytes]; 105 | for( i = 0; i < length; ++i ){ 106 | putc( start[i], stdout ); 107 | } // end for: write out 108 | return YES; 109 | } else { 110 | return [photoData writeToFile:path atomically:NO]; 111 | } 112 | 113 | 114 | return NO; 115 | } 116 | 117 | 118 | /** 119 | * Converts an NSImage into NSData. Defaults to jpeg if 120 | * format cannot be determined. 121 | */ 122 | +(NSData *)dataFrom:(NSImage *)image asType:(NSString *)format{ 123 | 124 | NSData *tiffData = [image TIFFRepresentation]; 125 | 126 | NSBitmapImageFileType imageType = NSJPEGFileType; 127 | NSDictionary *imageProps = nil; 128 | 129 | 130 | // TIFF. Special case. Can save immediately. 131 | if( [@"tif" rangeOfString:format options:NSCaseInsensitiveSearch].location != NSNotFound || 132 | [@"tiff" rangeOfString:format options:NSCaseInsensitiveSearch].location != NSNotFound ){ 133 | return tiffData; 134 | } 135 | 136 | // JPEG 137 | else if( [@"jpg" rangeOfString:format options:NSCaseInsensitiveSearch].location != NSNotFound || 138 | [@"jpeg" rangeOfString:format options:NSCaseInsensitiveSearch].location != NSNotFound ){ 139 | imageType = NSJPEGFileType; 140 | imageProps = [NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:0.9] forKey:NSImageCompressionFactor]; 141 | 142 | } 143 | 144 | // PNG 145 | else if( [@"png" rangeOfString:format options:NSCaseInsensitiveSearch].location != NSNotFound ){ 146 | imageType = NSPNGFileType; 147 | } 148 | 149 | // BMP 150 | else if( [@"bmp" rangeOfString:format options:NSCaseInsensitiveSearch].location != NSNotFound ){ 151 | imageType = NSBMPFileType; 152 | } 153 | 154 | // GIF 155 | else if( [@"gif" rangeOfString:format options:NSCaseInsensitiveSearch].location != NSNotFound ){ 156 | imageType = NSGIFFileType; 157 | } 158 | 159 | NSBitmapImageRep *imageRep = [NSBitmapImageRep imageRepWithData:tiffData]; 160 | NSData *photoData = [imageRep representationUsingType:imageType properties:imageProps]; 161 | 162 | return photoData; 163 | } // end dataFrom 164 | 165 | 166 | 167 | /** 168 | * Primary one-stop-shopping message for capturing an image. 169 | * Activates the video source, saves a frame, stops the source, 170 | * and saves the file. 171 | */ 172 | 173 | +(BOOL)saveSingleSnapshotFrom:(QTCaptureDevice *)device toFile:(NSString *)path{ 174 | return [self saveSingleSnapshotFrom:device toFile:path withWarmup:nil]; 175 | } 176 | 177 | +(BOOL)saveSingleSnapshotFrom:(QTCaptureDevice *)device toFile:(NSString *)path withWarmup:(NSNumber *)warmup{ 178 | return [self saveSingleSnapshotFrom:device toFile:path withWarmup:warmup withTimelapse:nil]; 179 | } 180 | 181 | +(BOOL)saveSingleSnapshotFrom:(QTCaptureDevice *)device 182 | toFile:(NSString *)path 183 | withWarmup:(NSNumber *)warmup 184 | withTimelapse:(NSNumber *)timelapse{ 185 | ImageSnap *snap; 186 | NSImage *image = nil; 187 | double interval = timelapse == nil ? -1 : [timelapse doubleValue]; 188 | 189 | snap = [[ImageSnap alloc] init]; // Instance of this ImageSnap class 190 | verbose("Starting device..."); 191 | if( [snap startSession:device] ){ // Try starting session 192 | verbose("Device started.\n"); 193 | 194 | if( warmup == nil ){ 195 | // Skip warmup 196 | verbose("Skipping warmup period.\n"); 197 | } else { 198 | double delay = [warmup doubleValue]; 199 | verbose("Delaying %.2lf seconds for warmup...",delay); 200 | NSDate *now = [[NSDate alloc] init]; 201 | [[NSRunLoop currentRunLoop] runUntilDate:[now dateByAddingTimeInterval: [warmup doubleValue]]]; 202 | [now release]; 203 | verbose("Warmup complete.\n"); 204 | } 205 | 206 | if ( interval > 0 ) { 207 | 208 | verbose("Time lapse: snapping every %.2lf seconds to current directory.\n", interval); 209 | 210 | NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; 211 | [dateFormatter setDateFormat:@"yyyy-MM-dd_HH-mm-ss.SSS"]; 212 | 213 | // wait a bit to make sure the camera is initialized 214 | //[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow: 1.0]]; 215 | 216 | for (unsigned long seq=0; ; seq++) 217 | { 218 | NSDate *now = [[NSDate alloc] init]; 219 | NSString *nowstr = [dateFormatter stringFromDate:now]; 220 | 221 | verbose(" - Snapshot %5lu", seq); 222 | verbose(" (%s)\n", [nowstr UTF8String]); 223 | 224 | // create filename 225 | NSString *filename = [NSString stringWithFormat:@"snapshot-%05ld-%s.jpg", seq, [nowstr UTF8String]]; 226 | 227 | // capture and write 228 | image = [snap snapshot]; // Capture a frame 229 | if (image != nil) { 230 | [ImageSnap saveImage:image toPath:filename]; 231 | console( "%s\n", [filename UTF8String]); 232 | } else { 233 | error( "Image capture failed.\n" ); 234 | } 235 | 236 | // sleep 237 | [[NSRunLoop currentRunLoop] runUntilDate:[now dateByAddingTimeInterval: interval]]; 238 | 239 | [now release]; 240 | } 241 | 242 | } else { 243 | image = [snap snapshot]; // Capture a frame 244 | 245 | } 246 | //NSLog(@"Stopping..."); 247 | [snap stopSession]; // Stop session 248 | //NSLog(@"Stopped."); 249 | } // end if: able to start session 250 | 251 | [snap release]; 252 | 253 | if ( interval > 0 ){ 254 | return YES; 255 | } else { 256 | return image == nil ? NO : [ImageSnap saveImage:image toPath:path]; 257 | } 258 | } // end 259 | 260 | 261 | /** 262 | * Returns current snapshot or nil if there is a problem 263 | * or session is not started. 264 | */ 265 | -(NSImage *)snapshot{ 266 | verbose( "Taking snapshot...\n"); 267 | 268 | CVImageBufferRef frame = nil; // Hold frame we find 269 | while( frame == nil ){ // While waiting for a frame 270 | 271 | //verbose( "\tEntering synchronized block to see if frame is captured yet..."); 272 | @synchronized(self){ // Lock since capture is on another thread 273 | frame = mCurrentImageBuffer; // Hold current frame 274 | CVBufferRetain(frame); // Retain it (OK if nil) 275 | } // end sync: self 276 | //verbose( "Done.\n" ); 277 | 278 | if( frame == nil ){ // Still no frame? Wait a little while. 279 | [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow: 0.1]]; 280 | } // end if: still nothing, wait 281 | 282 | } // end while: no frame yet 283 | 284 | // Convert frame to an NSImage 285 | CIImage *flippedImage = [self flipImage:[CIImage imageWithCVImageBuffer:frame]]; 286 | NSCIImageRep *imageRep = [NSCIImageRep imageRepWithCIImage:flippedImage]; 287 | NSImage *image = [[[NSImage alloc] initWithSize:[imageRep size]] autorelease]; 288 | [image addRepresentation:imageRep]; 289 | CVBufferRelease(frame); // Nick's addition. Maybe horribly unsafe or something, but was leaking crazy memory without this. 290 | verbose( "Snapshot taken.\n" ); 291 | 292 | return image; 293 | } 294 | 295 | - (CIImage *)flipImage:(CIImage *)image { 296 | CIImage *resultImage = image; 297 | CIFilter *flipFilter = [CIFilter filterWithName:@"CIAffineTransform"]; 298 | [flipFilter setValue:resultImage forKey:@"inputImage"]; 299 | NSAffineTransform *flipTransform = [NSAffineTransform transform]; 300 | [flipTransform scaleXBy:-1 yBy:1.0]; // horizontal flip 301 | [flipTransform translateXBy:-1280 yBy:0]; 302 | [flipFilter setValue:flipTransform forKey:@"inputTransform"]; 303 | resultImage = [flipFilter valueForKey:@"outputImage"]; 304 | return resultImage; 305 | } 306 | 307 | 308 | /** 309 | * Blocks until session is stopped. 310 | */ 311 | -(void)stopSession{ 312 | verbose("Stopping session...\n" ); 313 | 314 | // Make sure we've stopped 315 | while( mCaptureSession != nil ){ 316 | verbose("\tCaptureSession != nil\n"); 317 | 318 | verbose("\tStopping CaptureSession..."); 319 | [mCaptureSession stopRunning]; 320 | verbose("Done.\n"); 321 | 322 | if( [mCaptureSession isRunning] ){ 323 | verbose( "[mCaptureSession isRunning]"); 324 | [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow: 0.1]]; 325 | }else { 326 | verbose( "\tShutting down 'stopSession(..)'" ); 327 | if( mCaptureSession ) [mCaptureSession release]; 328 | if( mCaptureDeviceInput ) [mCaptureDeviceInput release]; 329 | if( mCaptureDecompressedVideoOutput ) [mCaptureDecompressedVideoOutput release]; 330 | if( mCaptureVideoPreviewOutput ) [mCaptureVideoPreviewOutput release]; 331 | 332 | mCaptureSession = nil; 333 | mCaptureDeviceInput = nil; 334 | mCaptureDecompressedVideoOutput = nil; 335 | mCaptureVideoPreviewOutput = nil; 336 | } // end if: stopped 337 | 338 | } // end while: not stopped 339 | } 340 | 341 | 342 | /** 343 | * Begins the capture session. Frames begin coming in. 344 | */ 345 | -(BOOL)startSession:(QTCaptureDevice *)device{ 346 | 347 | verbose( "Starting capture session...\n" ); 348 | 349 | if( device == nil ) { 350 | verbose( "\tCannot start session: no device provided.\n" ); 351 | return NO; 352 | } 353 | 354 | NSError *error = nil; 355 | 356 | // If we've already started with this device, return 357 | if( [device isEqual:[mCaptureDeviceInput device]] && 358 | mCaptureSession != nil && 359 | [mCaptureSession isRunning] ){ 360 | return YES; 361 | } // end if: already running 362 | 363 | else if( mCaptureSession != nil ){ 364 | verbose( "\tStopping previous session.\n" ); 365 | [self stopSession]; 366 | } // end if: else stop session 367 | 368 | 369 | // Create the capture session 370 | verbose( "\tCreating QTCaptureSession..." ); 371 | mCaptureSession = [[QTCaptureSession alloc] init]; 372 | verbose( "Done.\n"); 373 | if( ![device open:&error] ){ 374 | error( "\tCould not create capture session.\n" ); 375 | [mCaptureSession release]; 376 | mCaptureSession = nil; 377 | return NO; 378 | } 379 | 380 | 381 | // Create input object from the device 382 | verbose( "\tCreating QTCaptureDeviceInput with %s...", [[device description] UTF8String] ); 383 | mCaptureDeviceInput = [[QTCaptureDeviceInput alloc] initWithDevice:device]; 384 | verbose( "Done.\n"); 385 | if (![mCaptureSession addInput:mCaptureDeviceInput error:&error]) { 386 | error( "\tCould not convert device to input device.\n"); 387 | [mCaptureSession release]; 388 | [mCaptureDeviceInput release]; 389 | mCaptureSession = nil; 390 | mCaptureDeviceInput = nil; 391 | return NO; 392 | } 393 | 394 | 395 | // // Decompressed video output 396 | // verbose( "\tCreating QTCaptureDecompressedVideoOutput..."); 397 | // mCaptureDecompressedVideoOutput = [[QTCaptureDecompressedVideoOutput alloc] init]; 398 | // [mCaptureDecompressedVideoOutput setDelegate:self]; 399 | // verbose( "Done.\n" ); 400 | // if (![mCaptureSession addOutput:mCaptureDecompressedVideoOutput error:&error]) { 401 | // error( "\tCould not create decompressed output.\n"); 402 | // [mCaptureSession release]; 403 | // [mCaptureDeviceInput release]; 404 | // [mCaptureDecompressedVideoOutput release]; 405 | // mCaptureSession = nil; 406 | // mCaptureDeviceInput = nil; 407 | // mCaptureDecompressedVideoOutput = nil; 408 | // return NO; 409 | // } 410 | 411 | // Preview video output 412 | verbose( "\tCreating QTCaptureVideoPreviewOutput..."); 413 | mCaptureVideoPreviewOutput = [[QTCaptureVideoPreviewOutput alloc] init]; 414 | [mCaptureVideoPreviewOutput setDelegate:self]; 415 | verbose( "Done.\n" ); 416 | if (![mCaptureSession addOutput:mCaptureVideoPreviewOutput error:&error]) { 417 | error( "\tCould not create preview output.\n"); 418 | [mCaptureSession release]; 419 | [mCaptureDeviceInput release]; 420 | [mCaptureVideoPreviewOutput release]; 421 | mCaptureSession = nil; 422 | mCaptureDeviceInput = nil; 423 | mCaptureVideoPreviewOutput = nil; 424 | return NO; 425 | } 426 | 427 | // Clear old image? 428 | verbose("\tEntering synchronized block to clear memory..."); 429 | @synchronized(self){ 430 | if( mCurrentImageBuffer != nil ){ 431 | CVBufferRelease(mCurrentImageBuffer); 432 | mCurrentImageBuffer = nil; 433 | } // end if: clear old image 434 | } // end sync: self 435 | verbose( "Done.\n"); 436 | 437 | [mCaptureSession startRunning]; 438 | verbose("Session started.\n"); 439 | 440 | return YES; 441 | } // end startSession 442 | 443 | 444 | 445 | // This delegate method is called whenever the QTCaptureDecompressedVideoOutput receives a frame 446 | - (void)captureOutput:(QTCaptureOutput *)captureOutput 447 | didOutputVideoFrame:(CVImageBufferRef)videoFrame 448 | withSampleBuffer:(QTSampleBuffer *)sampleBuffer 449 | fromConnection:(QTCaptureConnection *)connection 450 | { 451 | verbose( "." ); 452 | if (videoFrame == nil ) { 453 | verbose( "'nil' Frame captured.\n" ); 454 | return; 455 | } 456 | 457 | // Swap out old frame for new one 458 | CVImageBufferRef imageBufferToRelease; 459 | CVBufferRetain(videoFrame); 460 | 461 | @synchronized(self){ 462 | imageBufferToRelease = mCurrentImageBuffer; 463 | mCurrentImageBuffer = videoFrame; 464 | } // end sync 465 | CVBufferRelease(imageBufferToRelease); 466 | 467 | } 468 | 469 | - (void)onDeviceDisconnected:(NSNotification *)note { 470 | return; // This didn't work 471 | [self stopSession]; 472 | [self startSession:[ImageSnap defaultVideoDevice]]; 473 | } 474 | 475 | @end 476 | 477 | 478 | // ////////////////////////////////////////////////////////// 479 | // 480 | // //////// B E G I N C - L E V E L M A I N //////// // 481 | // 482 | // ////////////////////////////////////////////////////////// 483 | 484 | int processArguments(int argc, const char * argv[]); 485 | void printUsage(int argc, const char * argv[]); 486 | int listDevices(); 487 | NSString *generateFilename(); 488 | QTCaptureDevice *getDefaultDevice(); 489 | 490 | 491 | // Main entry point. Since we're using Cocoa and all kinds of fancy 492 | // classes, we have to set up appropriate pools and loops. 493 | // Thanks to the example http://lists.apple.com/archives/cocoa-dev/2003/Apr/msg01638.html 494 | // for reminding me how to do it. 495 | int mainRemnant (int argc, const char * argv[]) { 496 | NSApplicationLoad(); // May be necessary for 10.5 not to crash. 497 | 498 | NSAutoreleasePool *pool; 499 | pool = [[NSAutoreleasePool alloc] init]; 500 | [NSApplication sharedApplication]; 501 | 502 | int result = processArguments(argc, argv); 503 | 504 | // [pool release]; 505 | [pool drain]; 506 | return result; 507 | } 508 | 509 | 510 | 511 | /** 512 | * Process command line arguments and execute program. 513 | */ 514 | int processArguments(int argc, const char * argv[] ){ 515 | 516 | NSString *filename = nil; 517 | QTCaptureDevice *device = nil; 518 | NSNumber *warmup = nil; 519 | NSNumber *timelapse = nil; 520 | 521 | 522 | int i; 523 | for( i = 1; i < argc; ++i ){ 524 | 525 | // Handle command line switches 526 | if (argv[i][0] == '-') { 527 | 528 | // Dash only? Means write image to stdout 529 | if( argv[i][1] == 0 ){ 530 | filename = @"-"; 531 | g_quiet = YES; 532 | } else { 533 | 534 | // Which switch was given 535 | switch (argv[i][1]) { 536 | 537 | // Help 538 | case '?': 539 | case 'h': 540 | printUsage( argc, argv ); 541 | return 0; 542 | break; 543 | 544 | 545 | // Verbose 546 | case 'v': 547 | g_verbose = YES; 548 | break; 549 | 550 | case 'q': 551 | g_quiet = YES; 552 | break; 553 | 554 | 555 | // List devices 556 | case 'l': 557 | listDevices(); 558 | return 0; 559 | break; 560 | 561 | // Specify device 562 | case 'd': 563 | if( i+1 < argc ){ 564 | device = [ImageSnap deviceNamed:[NSString stringWithUTF8String:argv[i+1]]]; 565 | if( device == nil ){ 566 | error( "Device \"%s\" not found.\n", argv[i+1] ); 567 | return 11; 568 | } // end if: not found 569 | ++i; // Account for "follow on" argument 570 | } else { 571 | error( "Not enough arguments given with 'd' flag.\n" ); 572 | return (int)'d'; 573 | } 574 | break; 575 | 576 | // Specify a warmup period before picture snaps 577 | case 'w': 578 | if( i+1 < argc ){ 579 | warmup = [NSNumber numberWithFloat:[[NSString stringWithUTF8String:argv[i+1]] floatValue]]; 580 | ++i; // Account for "follow on" argument 581 | } else { 582 | error( "Not enough arguments given with 'w' flag.\n" ); 583 | return (int)'w'; 584 | } 585 | break; 586 | 587 | // Timelapse 588 | case 't': 589 | if( i+1 < argc ){ 590 | timelapse = [NSNumber numberWithDouble:[[NSString stringWithUTF8String:argv[i+1]] doubleValue]]; 591 | //g_timelapse = [timelapse doubleValue]; 592 | ++i; // Account for "follow on" argument 593 | } else { 594 | error( "Not enough arguments given with 't' flag.\n" ); 595 | return (int)'t'; 596 | } 597 | break; 598 | 599 | 600 | 601 | } // end switch: flag value 602 | } // end else: not dash only 603 | } // end if: '-' 604 | 605 | // Else assume it's a filename 606 | else { 607 | filename = [NSString stringWithUTF8String:argv[i]]; 608 | } 609 | 610 | } // end for: each command line argument 611 | 612 | 613 | // Make sure we have a filename 614 | if( filename == nil ){ 615 | filename = generateFilename(); 616 | verbose( "No filename specified. Using %s\n", [filename UTF8String] ); 617 | } // end if: no filename given 618 | 619 | if( filename == nil ){ 620 | error( "No suitable filename could be determined.\n" ); 621 | return 1; 622 | } 623 | 624 | 625 | // Make sure we have a device 626 | if( device == nil ){ 627 | device = getDefaultDevice(); 628 | verbose( "No device specified. Using %s\n", [[device description] UTF8String] ); 629 | } // end if: no device given 630 | 631 | if( device == nil ){ 632 | error( "No video devices found.\n" ); 633 | return 2; 634 | } else { 635 | console( "Capturing image from device \"%s\"...", [[device description] UTF8String] ); 636 | } 637 | 638 | 639 | // Image capture 640 | if( [ImageSnap saveSingleSnapshotFrom:device toFile:filename withWarmup:warmup withTimelapse:timelapse] ){ 641 | console( "%s\n", [filename UTF8String] ); 642 | } else { 643 | error( "Error.\n" ); 644 | } // end else 645 | 646 | return 0; 647 | } 648 | 649 | 650 | 651 | void printUsage(int argc, const char * argv[]){ 652 | printf( "USAGE: %s [options] [filename]\n", argv[0] ); 653 | printf( "Version: %s\n", [VERSION UTF8String] ); 654 | printf( "Captures an image from a video device and saves it in a file.\n" ); 655 | printf( "If no device is specified, the system default will be used.\n" ); 656 | printf( "If no filename is specfied, snapshot.jpg will be used.\n" ); 657 | printf( "Supported image types: JPEG, TIFF, PNG, GIF, BMP\n" ); 658 | printf( " -h This help message\n" ); 659 | printf( " -v Verbose mode\n"); 660 | printf( " -l List available video devices\n" ); 661 | printf( " -t x.xx Take a picture every x.xx seconds\n" ); 662 | printf( " -q Quiet mode. Do not output any text\n"); 663 | printf( " -w x.xx Warmup. Delay snapshot x.xx seconds after turning on camera\n" ); 664 | printf( " -d device Use named video device\n" ); 665 | } 666 | 667 | 668 | 669 | 670 | 671 | /** 672 | * Prints a list of video capture devices to standard out. 673 | */ 674 | int listDevices(){ 675 | NSArray *devices = [ImageSnap videoDevices]; 676 | 677 | [devices count] > 0 678 | ? printf("Video Devices:\n") 679 | : printf("No video devices found.\n"); 680 | 681 | for( QTCaptureDevice *device in devices ){ 682 | printf( "%s\n", [[device description] UTF8String] ); 683 | } // end for: each device 684 | return (int)[devices count]; 685 | } 686 | 687 | /** 688 | * Generates a filename for saving the image, presumably 689 | * because the user didn't specify a filename. 690 | * Currently returns snapshot.tiff. 691 | */ 692 | NSString *generateFilename(){ 693 | NSString *result = @"snapshot.jpg"; 694 | return result; 695 | } // end 696 | 697 | 698 | /** 699 | * Gets a default video device, or nil if none is found. 700 | * For now, simply queries ImageSnap. May be fancier 701 | * in the future. 702 | */ 703 | QTCaptureDevice *getDefaultDevice(){ 704 | return [ImageSnap defaultVideoDevice]; 705 | } // end 706 | 707 | 708 | 709 | -------------------------------------------------------------------------------- /Telepath.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C83B4D1817E11B57009F5DCC /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C83B4D1717E11B57009F5DCC /* QuartzCore.framework */; }; 11 | C83B4D1A17E11B81009F5DCC /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C83B4D1917E11B81009F5DCC /* IOKit.framework */; }; 12 | C85A13B2182D4A2200FF3EDA /* Fonts in Resources */ = {isa = PBXBuildFile; fileRef = C85A13B1182D4A2200FF3EDA /* Fonts */; }; 13 | C85A13B4182D7EB100FF3EDA /* fez-chime.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = C85A13B3182D7EB100FF3EDA /* fez-chime.mp3 */; }; 14 | C85A13B7182D80E100FF3EDA /* TPTrackerHappiness.m in Sources */ = {isa = PBXBuildFile; fileRef = C85A13B6182D80E100FF3EDA /* TPTrackerHappiness.m */; }; 15 | C8A1948E17DCE37700C0F7FC /* TPTrackerGitHub.m in Sources */ = {isa = PBXBuildFile; fileRef = C8A1948D17DCE37700C0F7FC /* TPTrackerGitHub.m */; }; 16 | C8A1949117DCF84100C0F7FC /* TPTrackerTrello.m in Sources */ = {isa = PBXBuildFile; fileRef = C8A1949017DCF84100C0F7FC /* TPTrackerTrello.m */; }; 17 | C8A1949417DD145100C0F7FC /* TPTrackerBrunchBuilds.m in Sources */ = {isa = PBXBuildFile; fileRef = C8A1949317DD145100C0F7FC /* TPTrackerBrunchBuilds.m */; }; 18 | C8A1949717DD272E00C0F7FC /* TPTrackerEmail.m in Sources */ = {isa = PBXBuildFile; fileRef = C8A1949617DD272E00C0F7FC /* TPTrackerEmail.m */; }; 19 | C8A1949C17DEDDCC00C0F7FC /* TPTrackerWorkHours.m in Sources */ = {isa = PBXBuildFile; fileRef = C8A1949B17DEDDCC00C0F7FC /* TPTrackerWorkHours.m */; }; 20 | C8AF4BF518221E870007835F /* ApplicationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C8AF4BF418221E870007835F /* ApplicationServices.framework */; }; 21 | C8E5F62717E8186E00129E0D /* TPTrackerScreen.m in Sources */ = {isa = PBXBuildFile; fileRef = C8E5F62617E8186E00129E0D /* TPTrackerScreen.m */; }; 22 | C8E9BEE417EEA27100049672 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C8E9BEE317EEA27100049672 /* WebKit.framework */; }; 23 | C8EBF4B6182BF43D00D33029 /* listen.wav in Resources */ = {isa = PBXBuildFile; fileRef = C8EBF4B5182BF43D00D33029 /* listen.wav */; }; 24 | C8F6325917D1053800330DFD /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C8F6325817D1053800330DFD /* Cocoa.framework */; }; 25 | C8F6326317D1053800330DFD /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C8F6326117D1053800330DFD /* InfoPlist.strings */; }; 26 | C8F6326517D1053800330DFD /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = C8F6326417D1053800330DFD /* main.m */; }; 27 | C8F6326917D1053800330DFD /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = C8F6326717D1053800330DFD /* Credits.rtf */; }; 28 | C8F6326C17D1053800330DFD /* TPAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = C8F6326B17D1053800330DFD /* TPAppDelegate.m */; }; 29 | C8F6326F17D1053800330DFD /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = C8F6326D17D1053800330DFD /* MainMenu.xib */; }; 30 | C8F6327217D1053800330DFD /* Telepath.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = C8F6327017D1053800330DFD /* Telepath.xcdatamodeld */; }; 31 | C8F6327417D1053800330DFD /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C8F6327317D1053800330DFD /* Images.xcassets */; }; 32 | C8F6327B17D1053800330DFD /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C8F6327A17D1053800330DFD /* XCTest.framework */; }; 33 | C8F6327C17D1053800330DFD /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C8F6325817D1053800330DFD /* Cocoa.framework */; }; 34 | C8F6328417D1053800330DFD /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C8F6328217D1053800330DFD /* InfoPlist.strings */; }; 35 | C8F6328617D1053800330DFD /* TelepathTests.m in Sources */ = {isa = PBXBuildFile; fileRef = C8F6328517D1053800330DFD /* TelepathTests.m */; }; 36 | C8F6329017D1057D00330DFD /* QTKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C8F6328F17D1057D00330DFD /* QTKit.framework */; }; 37 | C8F6329417D1074200330DFD /* TPHUDWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = C8F6329217D1074200330DFD /* TPHUDWindowController.m */; }; 38 | C8F6329517D1074200330DFD /* TPHUDWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = C8F6329317D1074200330DFD /* TPHUDWindowController.xib */; }; 39 | C8F6329817D109C200330DFD /* TPLightSensor.m in Sources */ = {isa = PBXBuildFile; fileRef = C8F6329717D109C200330DFD /* TPLightSensor.m */; }; 40 | C8F6329C17D10C3C00330DFD /* ImageSnap.m in Sources */ = {isa = PBXBuildFile; fileRef = C8F6329B17D10C3C00330DFD /* ImageSnap.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 41 | C8F632A517D26AD600330DFD /* TPTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = C8F632A417D26AD600330DFD /* TPTracker.m */; }; 42 | C8F632A817D26B8700330DFD /* TPTrackerKeyboard.m in Sources */ = {isa = PBXBuildFile; fileRef = C8F632A717D26B8700330DFD /* TPTrackerKeyboard.m */; }; 43 | C8F632AB17D26B9000330DFD /* TPTrackerMouse.m in Sources */ = {isa = PBXBuildFile; fileRef = C8F632AA17D26B9000330DFD /* TPTrackerMouse.m */; }; 44 | C8F632AE17D26B9A00330DFD /* TPTrackerWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = C8F632AD17D26B9A00330DFD /* TPTrackerWindow.m */; }; 45 | C8F632B117D26BA800330DFD /* TPTrackerLight.m in Sources */ = {isa = PBXBuildFile; fileRef = C8F632B017D26BA800330DFD /* TPTrackerLight.m */; }; 46 | C8F632B417D26BC400330DFD /* TPTrackerCamera.m in Sources */ = {isa = PBXBuildFile; fileRef = C8F632B317D26BC400330DFD /* TPTrackerCamera.m */; }; 47 | C8F632B817D26D8B00330DFD /* TPUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = C8F632B717D26D8B00330DFD /* TPUtilities.m */; }; 48 | C8FACFAF17DBCCE0005182E6 /* TPWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = C8FACFAE17DBCCE0005182E6 /* TPWindow.m */; }; 49 | /* End PBXBuildFile section */ 50 | 51 | /* Begin PBXContainerItemProxy section */ 52 | C8F6327D17D1053800330DFD /* PBXContainerItemProxy */ = { 53 | isa = PBXContainerItemProxy; 54 | containerPortal = C8F6324D17D1053700330DFD /* Project object */; 55 | proxyType = 1; 56 | remoteGlobalIDString = C8F6325417D1053800330DFD; 57 | remoteInfo = Telepath; 58 | }; 59 | /* End PBXContainerItemProxy section */ 60 | 61 | /* Begin PBXFileReference section */ 62 | C83B4D1717E11B57009F5DCC /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 63 | C83B4D1917E11B81009F5DCC /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; }; 64 | C85A13B1182D4A2200FF3EDA /* Fonts */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Fonts; sourceTree = ""; }; 65 | C85A13B3182D7EB100FF3EDA /* fez-chime.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = "fez-chime.mp3"; sourceTree = ""; }; 66 | C85A13B5182D80E100FF3EDA /* TPTrackerHappiness.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TPTrackerHappiness.h; sourceTree = ""; }; 67 | C85A13B6182D80E100FF3EDA /* TPTrackerHappiness.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TPTrackerHappiness.m; sourceTree = ""; }; 68 | C8A1948C17DCE37700C0F7FC /* TPTrackerGitHub.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TPTrackerGitHub.h; sourceTree = ""; }; 69 | C8A1948D17DCE37700C0F7FC /* TPTrackerGitHub.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TPTrackerGitHub.m; sourceTree = ""; }; 70 | C8A1948F17DCF84100C0F7FC /* TPTrackerTrello.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TPTrackerTrello.h; sourceTree = ""; }; 71 | C8A1949017DCF84100C0F7FC /* TPTrackerTrello.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TPTrackerTrello.m; sourceTree = ""; }; 72 | C8A1949217DD145100C0F7FC /* TPTrackerBrunchBuilds.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TPTrackerBrunchBuilds.h; sourceTree = ""; }; 73 | C8A1949317DD145100C0F7FC /* TPTrackerBrunchBuilds.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TPTrackerBrunchBuilds.m; sourceTree = ""; }; 74 | C8A1949517DD272E00C0F7FC /* TPTrackerEmail.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TPTrackerEmail.h; sourceTree = ""; }; 75 | C8A1949617DD272E00C0F7FC /* TPTrackerEmail.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TPTrackerEmail.m; sourceTree = ""; }; 76 | C8A1949A17DEDDCC00C0F7FC /* TPTrackerWorkHours.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TPTrackerWorkHours.h; sourceTree = ""; }; 77 | C8A1949B17DEDDCC00C0F7FC /* TPTrackerWorkHours.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TPTrackerWorkHours.m; sourceTree = ""; }; 78 | C8AF4BF418221E870007835F /* ApplicationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ApplicationServices.framework; path = System/Library/Frameworks/ApplicationServices.framework; sourceTree = SDKROOT; }; 79 | C8E5F62517E8186E00129E0D /* TPTrackerScreen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TPTrackerScreen.h; sourceTree = ""; }; 80 | C8E5F62617E8186E00129E0D /* TPTrackerScreen.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TPTrackerScreen.m; sourceTree = ""; }; 81 | C8E9BEE317EEA27100049672 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; 82 | C8EBF4B5182BF43D00D33029 /* listen.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = listen.wav; sourceTree = ""; }; 83 | C8F6325517D1053800330DFD /* Telepath.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Telepath.app; sourceTree = BUILT_PRODUCTS_DIR; }; 84 | C8F6325817D1053800330DFD /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 85 | C8F6325B17D1053800330DFD /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 86 | C8F6325C17D1053800330DFD /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 87 | C8F6325D17D1053800330DFD /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 88 | C8F6326017D1053800330DFD /* Telepath-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Telepath-Info.plist"; sourceTree = ""; }; 89 | C8F6326217D1053800330DFD /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 90 | C8F6326417D1053800330DFD /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 91 | C8F6326617D1053800330DFD /* Telepath-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Telepath-Prefix.pch"; sourceTree = ""; }; 92 | C8F6326817D1053800330DFD /* en */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; name = en; path = en.lproj/Credits.rtf; sourceTree = ""; }; 93 | C8F6326A17D1053800330DFD /* TPAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TPAppDelegate.h; sourceTree = ""; }; 94 | C8F6326B17D1053800330DFD /* TPAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TPAppDelegate.m; sourceTree = ""; }; 95 | C8F6326E17D1053800330DFD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 96 | C8F6327117D1053800330DFD /* Telepath.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Telepath.xcdatamodel; sourceTree = ""; }; 97 | C8F6327317D1053800330DFD /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 98 | C8F6327917D1053800330DFD /* TelepathTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TelepathTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 99 | C8F6327A17D1053800330DFD /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 100 | C8F6328117D1053800330DFD /* TelepathTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "TelepathTests-Info.plist"; sourceTree = ""; }; 101 | C8F6328317D1053800330DFD /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 102 | C8F6328517D1053800330DFD /* TelepathTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TelepathTests.m; sourceTree = ""; }; 103 | C8F6328F17D1057D00330DFD /* QTKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QTKit.framework; path = System/Library/Frameworks/QTKit.framework; sourceTree = SDKROOT; }; 104 | C8F6329117D1074200330DFD /* TPHUDWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TPHUDWindowController.h; sourceTree = ""; }; 105 | C8F6329217D1074200330DFD /* TPHUDWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TPHUDWindowController.m; sourceTree = ""; }; 106 | C8F6329317D1074200330DFD /* TPHUDWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TPHUDWindowController.xib; sourceTree = ""; }; 107 | C8F6329617D109C200330DFD /* TPLightSensor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TPLightSensor.h; sourceTree = ""; }; 108 | C8F6329717D109C200330DFD /* TPLightSensor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TPLightSensor.m; sourceTree = ""; }; 109 | C8F6329A17D10C3C00330DFD /* ImageSnap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ImageSnap.h; sourceTree = ""; }; 110 | C8F6329B17D10C3C00330DFD /* ImageSnap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ImageSnap.m; sourceTree = ""; }; 111 | C8F632A317D26AD600330DFD /* TPTracker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TPTracker.h; sourceTree = ""; }; 112 | C8F632A417D26AD600330DFD /* TPTracker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TPTracker.m; sourceTree = ""; }; 113 | C8F632A617D26B8700330DFD /* TPTrackerKeyboard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TPTrackerKeyboard.h; sourceTree = ""; }; 114 | C8F632A717D26B8700330DFD /* TPTrackerKeyboard.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TPTrackerKeyboard.m; sourceTree = ""; }; 115 | C8F632A917D26B9000330DFD /* TPTrackerMouse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TPTrackerMouse.h; sourceTree = ""; }; 116 | C8F632AA17D26B9000330DFD /* TPTrackerMouse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TPTrackerMouse.m; sourceTree = ""; }; 117 | C8F632AC17D26B9A00330DFD /* TPTrackerWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TPTrackerWindow.h; sourceTree = ""; }; 118 | C8F632AD17D26B9A00330DFD /* TPTrackerWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TPTrackerWindow.m; sourceTree = ""; }; 119 | C8F632AF17D26BA800330DFD /* TPTrackerLight.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TPTrackerLight.h; sourceTree = ""; }; 120 | C8F632B017D26BA800330DFD /* TPTrackerLight.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TPTrackerLight.m; sourceTree = ""; }; 121 | C8F632B217D26BC400330DFD /* TPTrackerCamera.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TPTrackerCamera.h; sourceTree = ""; }; 122 | C8F632B317D26BC400330DFD /* TPTrackerCamera.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TPTrackerCamera.m; sourceTree = ""; }; 123 | C8F632B617D26D8B00330DFD /* TPUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TPUtilities.h; sourceTree = ""; }; 124 | C8F632B717D26D8B00330DFD /* TPUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TPUtilities.m; sourceTree = ""; }; 125 | C8FACFAD17DBCCE0005182E6 /* TPWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TPWindow.h; sourceTree = ""; }; 126 | C8FACFAE17DBCCE0005182E6 /* TPWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TPWindow.m; sourceTree = ""; }; 127 | /* End PBXFileReference section */ 128 | 129 | /* Begin PBXFrameworksBuildPhase section */ 130 | C8F6325217D1053800330DFD /* Frameworks */ = { 131 | isa = PBXFrameworksBuildPhase; 132 | buildActionMask = 2147483647; 133 | files = ( 134 | C8AF4BF518221E870007835F /* ApplicationServices.framework in Frameworks */, 135 | C8E9BEE417EEA27100049672 /* WebKit.framework in Frameworks */, 136 | C83B4D1A17E11B81009F5DCC /* IOKit.framework in Frameworks */, 137 | C83B4D1817E11B57009F5DCC /* QuartzCore.framework in Frameworks */, 138 | C8F6329017D1057D00330DFD /* QTKit.framework in Frameworks */, 139 | C8F6325917D1053800330DFD /* Cocoa.framework in Frameworks */, 140 | ); 141 | runOnlyForDeploymentPostprocessing = 0; 142 | }; 143 | C8F6327617D1053800330DFD /* Frameworks */ = { 144 | isa = PBXFrameworksBuildPhase; 145 | buildActionMask = 2147483647; 146 | files = ( 147 | C8F6327C17D1053800330DFD /* Cocoa.framework in Frameworks */, 148 | C8F6327B17D1053800330DFD /* XCTest.framework in Frameworks */, 149 | ); 150 | runOnlyForDeploymentPostprocessing = 0; 151 | }; 152 | /* End PBXFrameworksBuildPhase section */ 153 | 154 | /* Begin PBXGroup section */ 155 | C8EBF4B7182BF44D00D33029 /* Sounds */ = { 156 | isa = PBXGroup; 157 | children = ( 158 | C85A13B3182D7EB100FF3EDA /* fez-chime.mp3 */, 159 | C8EBF4B5182BF43D00D33029 /* listen.wav */, 160 | ); 161 | name = Sounds; 162 | sourceTree = ""; 163 | }; 164 | C8F6324C17D1053700330DFD = { 165 | isa = PBXGroup; 166 | children = ( 167 | C8F6325E17D1053800330DFD /* Telepath */, 168 | C8F6327F17D1053800330DFD /* TelepathTests */, 169 | C8F6325717D1053800330DFD /* Frameworks */, 170 | C8F6325617D1053800330DFD /* Products */, 171 | ); 172 | sourceTree = ""; 173 | }; 174 | C8F6325617D1053800330DFD /* Products */ = { 175 | isa = PBXGroup; 176 | children = ( 177 | C8F6325517D1053800330DFD /* Telepath.app */, 178 | C8F6327917D1053800330DFD /* TelepathTests.xctest */, 179 | ); 180 | name = Products; 181 | sourceTree = ""; 182 | }; 183 | C8F6325717D1053800330DFD /* Frameworks */ = { 184 | isa = PBXGroup; 185 | children = ( 186 | C8AF4BF418221E870007835F /* ApplicationServices.framework */, 187 | C8E9BEE317EEA27100049672 /* WebKit.framework */, 188 | C83B4D1917E11B81009F5DCC /* IOKit.framework */, 189 | C83B4D1717E11B57009F5DCC /* QuartzCore.framework */, 190 | C8F6328F17D1057D00330DFD /* QTKit.framework */, 191 | C8F6325817D1053800330DFD /* Cocoa.framework */, 192 | C8F6327A17D1053800330DFD /* XCTest.framework */, 193 | C8F6325A17D1053800330DFD /* Other Frameworks */, 194 | ); 195 | name = Frameworks; 196 | sourceTree = ""; 197 | }; 198 | C8F6325A17D1053800330DFD /* Other Frameworks */ = { 199 | isa = PBXGroup; 200 | children = ( 201 | C8F6325B17D1053800330DFD /* AppKit.framework */, 202 | C8F6325C17D1053800330DFD /* CoreData.framework */, 203 | C8F6325D17D1053800330DFD /* Foundation.framework */, 204 | ); 205 | name = "Other Frameworks"; 206 | sourceTree = ""; 207 | }; 208 | C8F6325E17D1053800330DFD /* Telepath */ = { 209 | isa = PBXGroup; 210 | children = ( 211 | C8F6329917D10C2B00330DFD /* Third Party */, 212 | C8F632B517D26BDA00330DFD /* Trackers */, 213 | C8F6326A17D1053800330DFD /* TPAppDelegate.h */, 214 | C8F6326B17D1053800330DFD /* TPAppDelegate.m */, 215 | C8F6326D17D1053800330DFD /* MainMenu.xib */, 216 | C8F6329117D1074200330DFD /* TPHUDWindowController.h */, 217 | C8F6329217D1074200330DFD /* TPHUDWindowController.m */, 218 | C8F6329317D1074200330DFD /* TPHUDWindowController.xib */, 219 | C8FACFAD17DBCCE0005182E6 /* TPWindow.h */, 220 | C8FACFAE17DBCCE0005182E6 /* TPWindow.m */, 221 | C8F6329617D109C200330DFD /* TPLightSensor.h */, 222 | C8F6329717D109C200330DFD /* TPLightSensor.m */, 223 | C8F632B617D26D8B00330DFD /* TPUtilities.h */, 224 | C8F632B717D26D8B00330DFD /* TPUtilities.m */, 225 | C8F6327317D1053800330DFD /* Images.xcassets */, 226 | C8EBF4B7182BF44D00D33029 /* Sounds */, 227 | C85A13B1182D4A2200FF3EDA /* Fonts */, 228 | C8F6327017D1053800330DFD /* Telepath.xcdatamodeld */, 229 | C8F6325F17D1053800330DFD /* Supporting Files */, 230 | ); 231 | path = Telepath; 232 | sourceTree = ""; 233 | }; 234 | C8F6325F17D1053800330DFD /* Supporting Files */ = { 235 | isa = PBXGroup; 236 | children = ( 237 | C8F6326017D1053800330DFD /* Telepath-Info.plist */, 238 | C8F6326117D1053800330DFD /* InfoPlist.strings */, 239 | C8F6326417D1053800330DFD /* main.m */, 240 | C8F6326617D1053800330DFD /* Telepath-Prefix.pch */, 241 | C8F6326717D1053800330DFD /* Credits.rtf */, 242 | ); 243 | name = "Supporting Files"; 244 | sourceTree = ""; 245 | }; 246 | C8F6327F17D1053800330DFD /* TelepathTests */ = { 247 | isa = PBXGroup; 248 | children = ( 249 | C8F6328517D1053800330DFD /* TelepathTests.m */, 250 | C8F6328017D1053800330DFD /* Supporting Files */, 251 | ); 252 | path = TelepathTests; 253 | sourceTree = ""; 254 | }; 255 | C8F6328017D1053800330DFD /* Supporting Files */ = { 256 | isa = PBXGroup; 257 | children = ( 258 | C8F6328117D1053800330DFD /* TelepathTests-Info.plist */, 259 | C8F6328217D1053800330DFD /* InfoPlist.strings */, 260 | ); 261 | name = "Supporting Files"; 262 | sourceTree = ""; 263 | }; 264 | C8F6329917D10C2B00330DFD /* Third Party */ = { 265 | isa = PBXGroup; 266 | children = ( 267 | C8F6329A17D10C3C00330DFD /* ImageSnap.h */, 268 | C8F6329B17D10C3C00330DFD /* ImageSnap.m */, 269 | ); 270 | name = "Third Party"; 271 | sourceTree = ""; 272 | }; 273 | C8F632B517D26BDA00330DFD /* Trackers */ = { 274 | isa = PBXGroup; 275 | children = ( 276 | C8F632A317D26AD600330DFD /* TPTracker.h */, 277 | C8F632A417D26AD600330DFD /* TPTracker.m */, 278 | C8F632A617D26B8700330DFD /* TPTrackerKeyboard.h */, 279 | C8F632A717D26B8700330DFD /* TPTrackerKeyboard.m */, 280 | C8F632A917D26B9000330DFD /* TPTrackerMouse.h */, 281 | C8F632AA17D26B9000330DFD /* TPTrackerMouse.m */, 282 | C8F632AC17D26B9A00330DFD /* TPTrackerWindow.h */, 283 | C8F632AD17D26B9A00330DFD /* TPTrackerWindow.m */, 284 | C8F632AF17D26BA800330DFD /* TPTrackerLight.h */, 285 | C8F632B017D26BA800330DFD /* TPTrackerLight.m */, 286 | C8F632B217D26BC400330DFD /* TPTrackerCamera.h */, 287 | C8F632B317D26BC400330DFD /* TPTrackerCamera.m */, 288 | C8A1948C17DCE37700C0F7FC /* TPTrackerGitHub.h */, 289 | C8A1948D17DCE37700C0F7FC /* TPTrackerGitHub.m */, 290 | C8A1948F17DCF84100C0F7FC /* TPTrackerTrello.h */, 291 | C8A1949017DCF84100C0F7FC /* TPTrackerTrello.m */, 292 | C8A1949217DD145100C0F7FC /* TPTrackerBrunchBuilds.h */, 293 | C8A1949317DD145100C0F7FC /* TPTrackerBrunchBuilds.m */, 294 | C8A1949517DD272E00C0F7FC /* TPTrackerEmail.h */, 295 | C8A1949617DD272E00C0F7FC /* TPTrackerEmail.m */, 296 | C8A1949A17DEDDCC00C0F7FC /* TPTrackerWorkHours.h */, 297 | C8A1949B17DEDDCC00C0F7FC /* TPTrackerWorkHours.m */, 298 | C8E5F62517E8186E00129E0D /* TPTrackerScreen.h */, 299 | C8E5F62617E8186E00129E0D /* TPTrackerScreen.m */, 300 | C85A13B5182D80E100FF3EDA /* TPTrackerHappiness.h */, 301 | C85A13B6182D80E100FF3EDA /* TPTrackerHappiness.m */, 302 | ); 303 | name = Trackers; 304 | sourceTree = ""; 305 | }; 306 | /* End PBXGroup section */ 307 | 308 | /* Begin PBXNativeTarget section */ 309 | C8F6325417D1053800330DFD /* Telepath */ = { 310 | isa = PBXNativeTarget; 311 | buildConfigurationList = C8F6328917D1053800330DFD /* Build configuration list for PBXNativeTarget "Telepath" */; 312 | buildPhases = ( 313 | C8F6325117D1053800330DFD /* Sources */, 314 | C8F6325217D1053800330DFD /* Frameworks */, 315 | C8F6325317D1053800330DFD /* Resources */, 316 | ); 317 | buildRules = ( 318 | ); 319 | dependencies = ( 320 | ); 321 | name = Telepath; 322 | productName = Telepath; 323 | productReference = C8F6325517D1053800330DFD /* Telepath.app */; 324 | productType = "com.apple.product-type.application"; 325 | }; 326 | C8F6327817D1053800330DFD /* TelepathTests */ = { 327 | isa = PBXNativeTarget; 328 | buildConfigurationList = C8F6328C17D1053800330DFD /* Build configuration list for PBXNativeTarget "TelepathTests" */; 329 | buildPhases = ( 330 | C8F6327517D1053800330DFD /* Sources */, 331 | C8F6327617D1053800330DFD /* Frameworks */, 332 | C8F6327717D1053800330DFD /* Resources */, 333 | ); 334 | buildRules = ( 335 | ); 336 | dependencies = ( 337 | C8F6327E17D1053800330DFD /* PBXTargetDependency */, 338 | ); 339 | name = TelepathTests; 340 | productName = TelepathTests; 341 | productReference = C8F6327917D1053800330DFD /* TelepathTests.xctest */; 342 | productType = "com.apple.product-type.bundle.unit-test"; 343 | }; 344 | /* End PBXNativeTarget section */ 345 | 346 | /* Begin PBXProject section */ 347 | C8F6324D17D1053700330DFD /* Project object */ = { 348 | isa = PBXProject; 349 | attributes = { 350 | CLASSPREFIX = TP; 351 | LastUpgradeCheck = 0500; 352 | ORGANIZATIONNAME = "Nick Winter"; 353 | TargetAttributes = { 354 | C8F6327817D1053800330DFD = { 355 | TestTargetID = C8F6325417D1053800330DFD; 356 | }; 357 | }; 358 | }; 359 | buildConfigurationList = C8F6325017D1053700330DFD /* Build configuration list for PBXProject "Telepath" */; 360 | compatibilityVersion = "Xcode 3.2"; 361 | developmentRegion = English; 362 | hasScannedForEncodings = 0; 363 | knownRegions = ( 364 | en, 365 | Base, 366 | ); 367 | mainGroup = C8F6324C17D1053700330DFD; 368 | productRefGroup = C8F6325617D1053800330DFD /* Products */; 369 | projectDirPath = ""; 370 | projectRoot = ""; 371 | targets = ( 372 | C8F6325417D1053800330DFD /* Telepath */, 373 | C8F6327817D1053800330DFD /* TelepathTests */, 374 | ); 375 | }; 376 | /* End PBXProject section */ 377 | 378 | /* Begin PBXResourcesBuildPhase section */ 379 | C8F6325317D1053800330DFD /* Resources */ = { 380 | isa = PBXResourcesBuildPhase; 381 | buildActionMask = 2147483647; 382 | files = ( 383 | C8F6326317D1053800330DFD /* InfoPlist.strings in Resources */, 384 | C8EBF4B6182BF43D00D33029 /* listen.wav in Resources */, 385 | C8F6327417D1053800330DFD /* Images.xcassets in Resources */, 386 | C85A13B4182D7EB100FF3EDA /* fez-chime.mp3 in Resources */, 387 | C8F6329517D1074200330DFD /* TPHUDWindowController.xib in Resources */, 388 | C8F6326917D1053800330DFD /* Credits.rtf in Resources */, 389 | C85A13B2182D4A2200FF3EDA /* Fonts in Resources */, 390 | C8F6326F17D1053800330DFD /* MainMenu.xib in Resources */, 391 | ); 392 | runOnlyForDeploymentPostprocessing = 0; 393 | }; 394 | C8F6327717D1053800330DFD /* Resources */ = { 395 | isa = PBXResourcesBuildPhase; 396 | buildActionMask = 2147483647; 397 | files = ( 398 | C8F6328417D1053800330DFD /* InfoPlist.strings in Resources */, 399 | ); 400 | runOnlyForDeploymentPostprocessing = 0; 401 | }; 402 | /* End PBXResourcesBuildPhase section */ 403 | 404 | /* Begin PBXSourcesBuildPhase section */ 405 | C8F6325117D1053800330DFD /* Sources */ = { 406 | isa = PBXSourcesBuildPhase; 407 | buildActionMask = 2147483647; 408 | files = ( 409 | C8F6326C17D1053800330DFD /* TPAppDelegate.m in Sources */, 410 | C8F6329C17D10C3C00330DFD /* ImageSnap.m in Sources */, 411 | C8F6327217D1053800330DFD /* Telepath.xcdatamodeld in Sources */, 412 | C8F632A517D26AD600330DFD /* TPTracker.m in Sources */, 413 | C8F6326517D1053800330DFD /* main.m in Sources */, 414 | C8A1949717DD272E00C0F7FC /* TPTrackerEmail.m in Sources */, 415 | C8A1949C17DEDDCC00C0F7FC /* TPTrackerWorkHours.m in Sources */, 416 | C85A13B7182D80E100FF3EDA /* TPTrackerHappiness.m in Sources */, 417 | C8F632B417D26BC400330DFD /* TPTrackerCamera.m in Sources */, 418 | C8F632B817D26D8B00330DFD /* TPUtilities.m in Sources */, 419 | C8A1948E17DCE37700C0F7FC /* TPTrackerGitHub.m in Sources */, 420 | C8F6329417D1074200330DFD /* TPHUDWindowController.m in Sources */, 421 | C8A1949417DD145100C0F7FC /* TPTrackerBrunchBuilds.m in Sources */, 422 | C8E5F62717E8186E00129E0D /* TPTrackerScreen.m in Sources */, 423 | C8F632AE17D26B9A00330DFD /* TPTrackerWindow.m in Sources */, 424 | C8F632B117D26BA800330DFD /* TPTrackerLight.m in Sources */, 425 | C8F6329817D109C200330DFD /* TPLightSensor.m in Sources */, 426 | C8FACFAF17DBCCE0005182E6 /* TPWindow.m in Sources */, 427 | C8F632A817D26B8700330DFD /* TPTrackerKeyboard.m in Sources */, 428 | C8A1949117DCF84100C0F7FC /* TPTrackerTrello.m in Sources */, 429 | C8F632AB17D26B9000330DFD /* TPTrackerMouse.m in Sources */, 430 | ); 431 | runOnlyForDeploymentPostprocessing = 0; 432 | }; 433 | C8F6327517D1053800330DFD /* Sources */ = { 434 | isa = PBXSourcesBuildPhase; 435 | buildActionMask = 2147483647; 436 | files = ( 437 | C8F6328617D1053800330DFD /* TelepathTests.m in Sources */, 438 | ); 439 | runOnlyForDeploymentPostprocessing = 0; 440 | }; 441 | /* End PBXSourcesBuildPhase section */ 442 | 443 | /* Begin PBXTargetDependency section */ 444 | C8F6327E17D1053800330DFD /* PBXTargetDependency */ = { 445 | isa = PBXTargetDependency; 446 | target = C8F6325417D1053800330DFD /* Telepath */; 447 | targetProxy = C8F6327D17D1053800330DFD /* PBXContainerItemProxy */; 448 | }; 449 | /* End PBXTargetDependency section */ 450 | 451 | /* Begin PBXVariantGroup section */ 452 | C8F6326117D1053800330DFD /* InfoPlist.strings */ = { 453 | isa = PBXVariantGroup; 454 | children = ( 455 | C8F6326217D1053800330DFD /* en */, 456 | ); 457 | name = InfoPlist.strings; 458 | sourceTree = ""; 459 | }; 460 | C8F6326717D1053800330DFD /* Credits.rtf */ = { 461 | isa = PBXVariantGroup; 462 | children = ( 463 | C8F6326817D1053800330DFD /* en */, 464 | ); 465 | name = Credits.rtf; 466 | sourceTree = ""; 467 | }; 468 | C8F6326D17D1053800330DFD /* MainMenu.xib */ = { 469 | isa = PBXVariantGroup; 470 | children = ( 471 | C8F6326E17D1053800330DFD /* Base */, 472 | ); 473 | name = MainMenu.xib; 474 | sourceTree = ""; 475 | }; 476 | C8F6328217D1053800330DFD /* InfoPlist.strings */ = { 477 | isa = PBXVariantGroup; 478 | children = ( 479 | C8F6328317D1053800330DFD /* en */, 480 | ); 481 | name = InfoPlist.strings; 482 | sourceTree = ""; 483 | }; 484 | /* End PBXVariantGroup section */ 485 | 486 | /* Begin XCBuildConfiguration section */ 487 | C8F6328717D1053800330DFD /* Debug */ = { 488 | isa = XCBuildConfiguration; 489 | buildSettings = { 490 | ALWAYS_SEARCH_USER_PATHS = NO; 491 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 492 | CLANG_CXX_LIBRARY = "libc++"; 493 | CLANG_ENABLE_MODULES = YES; 494 | CLANG_ENABLE_OBJC_ARC = YES; 495 | CLANG_WARN_BOOL_CONVERSION = YES; 496 | CLANG_WARN_CONSTANT_CONVERSION = YES; 497 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 498 | CLANG_WARN_EMPTY_BODY = YES; 499 | CLANG_WARN_ENUM_CONVERSION = YES; 500 | CLANG_WARN_INT_CONVERSION = YES; 501 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 502 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 503 | COPY_PHASE_STRIP = NO; 504 | GCC_C_LANGUAGE_STANDARD = gnu99; 505 | GCC_DYNAMIC_NO_PIC = NO; 506 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 507 | GCC_OPTIMIZATION_LEVEL = 0; 508 | GCC_PREPROCESSOR_DEFINITIONS = ( 509 | "DEBUG=1", 510 | "$(inherited)", 511 | ); 512 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 513 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 514 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 515 | GCC_WARN_UNDECLARED_SELECTOR = YES; 516 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 517 | GCC_WARN_UNUSED_FUNCTION = YES; 518 | GCC_WARN_UNUSED_VARIABLE = YES; 519 | MACOSX_DEPLOYMENT_TARGET = 10.8; 520 | ONLY_ACTIVE_ARCH = YES; 521 | SDKROOT = macosx; 522 | }; 523 | name = Debug; 524 | }; 525 | C8F6328817D1053800330DFD /* Release */ = { 526 | isa = XCBuildConfiguration; 527 | buildSettings = { 528 | ALWAYS_SEARCH_USER_PATHS = NO; 529 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 530 | CLANG_CXX_LIBRARY = "libc++"; 531 | CLANG_ENABLE_MODULES = YES; 532 | CLANG_ENABLE_OBJC_ARC = YES; 533 | CLANG_WARN_BOOL_CONVERSION = YES; 534 | CLANG_WARN_CONSTANT_CONVERSION = YES; 535 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 536 | CLANG_WARN_EMPTY_BODY = YES; 537 | CLANG_WARN_ENUM_CONVERSION = YES; 538 | CLANG_WARN_INT_CONVERSION = YES; 539 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 540 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 541 | COPY_PHASE_STRIP = YES; 542 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 543 | ENABLE_NS_ASSERTIONS = NO; 544 | GCC_C_LANGUAGE_STANDARD = gnu99; 545 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 546 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 547 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 548 | GCC_WARN_UNDECLARED_SELECTOR = YES; 549 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 550 | GCC_WARN_UNUSED_FUNCTION = YES; 551 | GCC_WARN_UNUSED_VARIABLE = YES; 552 | MACOSX_DEPLOYMENT_TARGET = 10.8; 553 | SDKROOT = macosx; 554 | }; 555 | name = Release; 556 | }; 557 | C8F6328A17D1053800330DFD /* Debug */ = { 558 | isa = XCBuildConfiguration; 559 | buildSettings = { 560 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 561 | COMBINE_HIDPI_IMAGES = YES; 562 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 563 | GCC_PREFIX_HEADER = "Telepath/Telepath-Prefix.pch"; 564 | INFOPLIST_FILE = "Telepath/Telepath-Info.plist"; 565 | PRODUCT_NAME = "$(TARGET_NAME)"; 566 | WRAPPER_EXTENSION = app; 567 | }; 568 | name = Debug; 569 | }; 570 | C8F6328B17D1053800330DFD /* Release */ = { 571 | isa = XCBuildConfiguration; 572 | buildSettings = { 573 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 574 | COMBINE_HIDPI_IMAGES = YES; 575 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 576 | GCC_PREFIX_HEADER = "Telepath/Telepath-Prefix.pch"; 577 | INFOPLIST_FILE = "Telepath/Telepath-Info.plist"; 578 | PRODUCT_NAME = "$(TARGET_NAME)"; 579 | WRAPPER_EXTENSION = app; 580 | }; 581 | name = Release; 582 | }; 583 | C8F6328D17D1053800330DFD /* Debug */ = { 584 | isa = XCBuildConfiguration; 585 | buildSettings = { 586 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/Telepath.app/Contents/MacOS/Telepath"; 587 | COMBINE_HIDPI_IMAGES = YES; 588 | FRAMEWORK_SEARCH_PATHS = ( 589 | "$(DEVELOPER_FRAMEWORKS_DIR)", 590 | "$(inherited)", 591 | ); 592 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 593 | GCC_PREFIX_HEADER = "Telepath/Telepath-Prefix.pch"; 594 | GCC_PREPROCESSOR_DEFINITIONS = ( 595 | "DEBUG=1", 596 | "$(inherited)", 597 | ); 598 | INFOPLIST_FILE = "TelepathTests/TelepathTests-Info.plist"; 599 | PRODUCT_NAME = "$(TARGET_NAME)"; 600 | TEST_HOST = "$(BUNDLE_LOADER)"; 601 | WRAPPER_EXTENSION = xctest; 602 | }; 603 | name = Debug; 604 | }; 605 | C8F6328E17D1053800330DFD /* Release */ = { 606 | isa = XCBuildConfiguration; 607 | buildSettings = { 608 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/Telepath.app/Contents/MacOS/Telepath"; 609 | COMBINE_HIDPI_IMAGES = YES; 610 | FRAMEWORK_SEARCH_PATHS = ( 611 | "$(DEVELOPER_FRAMEWORKS_DIR)", 612 | "$(inherited)", 613 | ); 614 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 615 | GCC_PREFIX_HEADER = "Telepath/Telepath-Prefix.pch"; 616 | INFOPLIST_FILE = "TelepathTests/TelepathTests-Info.plist"; 617 | PRODUCT_NAME = "$(TARGET_NAME)"; 618 | TEST_HOST = "$(BUNDLE_LOADER)"; 619 | WRAPPER_EXTENSION = xctest; 620 | }; 621 | name = Release; 622 | }; 623 | /* End XCBuildConfiguration section */ 624 | 625 | /* Begin XCConfigurationList section */ 626 | C8F6325017D1053700330DFD /* Build configuration list for PBXProject "Telepath" */ = { 627 | isa = XCConfigurationList; 628 | buildConfigurations = ( 629 | C8F6328717D1053800330DFD /* Debug */, 630 | C8F6328817D1053800330DFD /* Release */, 631 | ); 632 | defaultConfigurationIsVisible = 0; 633 | defaultConfigurationName = Release; 634 | }; 635 | C8F6328917D1053800330DFD /* Build configuration list for PBXNativeTarget "Telepath" */ = { 636 | isa = XCConfigurationList; 637 | buildConfigurations = ( 638 | C8F6328A17D1053800330DFD /* Debug */, 639 | C8F6328B17D1053800330DFD /* Release */, 640 | ); 641 | defaultConfigurationIsVisible = 0; 642 | defaultConfigurationName = Release; 643 | }; 644 | C8F6328C17D1053800330DFD /* Build configuration list for PBXNativeTarget "TelepathTests" */ = { 645 | isa = XCConfigurationList; 646 | buildConfigurations = ( 647 | C8F6328D17D1053800330DFD /* Debug */, 648 | C8F6328E17D1053800330DFD /* Release */, 649 | ); 650 | defaultConfigurationIsVisible = 0; 651 | defaultConfigurationName = Release; 652 | }; 653 | /* End XCConfigurationList section */ 654 | 655 | /* Begin XCVersionGroup section */ 656 | C8F6327017D1053800330DFD /* Telepath.xcdatamodeld */ = { 657 | isa = XCVersionGroup; 658 | children = ( 659 | C8F6327117D1053800330DFD /* Telepath.xcdatamodel */, 660 | ); 661 | currentVersion = C8F6327117D1053800330DFD /* Telepath.xcdatamodel */; 662 | path = Telepath.xcdatamodeld; 663 | sourceTree = ""; 664 | versionGroupType = wrapper.xcdatamodel; 665 | }; 666 | /* End XCVersionGroup section */ 667 | }; 668 | rootObject = C8F6324D17D1053700330DFD /* Project object */; 669 | } 670 | --------------------------------------------------------------------------------