From 8b42ed7eb4da99a7474fdc3ddce9a450da36cc61 Mon Sep 17 00:00:00 2001 From: Joe Wreschnig Date: Wed, 14 Aug 2013 21:27:49 +0200 Subject: [PATCH] Switch from NSTimer to a CVDisplayLink to handle continuous events. NSTimer was never great and scheduler coalescing in 10.9 will make it even less suitable for real-time use. --- Classes/NJInputController.m | 58 +++++++++++++++++++++++------ Enjoyable.xcodeproj/project.pbxproj | 4 ++ Info.plist | 2 +- 3 files changed, 51 insertions(+), 13 deletions(-) diff --git a/Classes/NJInputController.m b/Classes/NJInputController.m index cd665de..face5e7 100644 --- a/Classes/NJInputController.m +++ b/Classes/NJInputController.m @@ -11,13 +11,34 @@ #import "NJOutput.h" #import "NJEvents.h" +#import + +@interface NJInputController () + +- (void)updateContinuousOutputs; + +@end + +static CVReturn displayLink_update_cb(CVDisplayLinkRef displayLink, + const CVTimeStamp *inNow, + const CVTimeStamp *inOutputTime, + CVOptionFlags flagsIn, + CVOptionFlags *flagsOut, + void *ctxManager) { + NJInputController *manager = (__bridge NJInputController *)ctxManager; + [manager performSelectorOnMainThread:@selector(updateContinuousOutputs) + withObject:nil + waitUntilDone:NO]; + return kCVReturnSuccess; +} + @implementation NJInputController { NJHIDManager *_HIDManager; - NSTimer *_continuousOutputsTick; NSMutableArray *_continousOutputs; NSMutableArray *_devices; NSMutableArray *_mappings; NJMapping *_manualMapping; + CVDisplayLinkRef displayLink; } @@ -27,6 +48,17 @@ if ((self = [super init])) { _devices = [[NSMutableArray alloc] initWithCapacity:16]; _continousOutputs = [[NSMutableArray alloc] initWithCapacity:32]; + + CVReturn error = CVDisplayLinkCreateWithActiveCGDisplays(&displayLink); + if (error) { + [self.delegate inputController:self + didError:[NSError errorWithDomain:NSCocoaErrorDomain + code:error + userInfo:nil]]; + NSLog(@"DisplayLink failed creation with error: %d.", error); + displayLink = NULL; + } + CVDisplayLinkSetOutputCallback(displayLink, displayLink_update_cb, (__bridge void *)self); _HIDManager = [[NJHIDManager alloc] initWithCriteria:@[ @{ NSSTR(kIOHIDDeviceUsagePageKey) : @(kHIDPage_GenericDesktop), @@ -69,7 +101,10 @@ - (void)dealloc { [NSNotificationCenter.defaultCenter removeObserver:self]; - [_continuousOutputsTick invalidate]; + if (displayLink) { + CVDisplayLinkStop(displayLink); + CVDisplayLinkRelease(displayLink); + } } - (void)addRunningOutput:(NJOutput *)output { @@ -77,12 +112,8 @@ // re-adding them or they trigger multiple times each time. if (![_continousOutputs containsObject:output]) [_continousOutputs addObject:output]; - if (!_continuousOutputsTick) { - _continuousOutputsTick = [NSTimer scheduledTimerWithTimeInterval:1.0/60.0 - target:self - selector:@selector(updateContinuousOutputs:) - userInfo:nil - repeats:YES]; + if (displayLink && !CVDisplayLinkIsRunning(displayLink)) { + CVDisplayLinkStart(displayLink); } } @@ -157,22 +188,23 @@ } } -- (void)updateContinuousOutputs:(NSTimer *)timer { +- (void)updateContinuousOutputs { self.mouseLoc = [NSEvent mouseLocation]; for (NJOutput *output in [_continousOutputs copy]) { if (![output update:self]) { [_continousOutputs removeObject:output]; } } - if (!_continousOutputs.count) { - [_continuousOutputsTick invalidate]; - _continuousOutputsTick = nil; + if (!_continousOutputs.count && displayLink) { + CVDisplayLinkStop(displayLink); } } - (void)HIDManager:(NJHIDManager *)manager didError:(NSError *)error { [self.delegate inputController:self didError:error]; self.simulatingEvents = NO; + if (displayLink) + CVDisplayLinkStop(displayLink); } - (void)HIDManagerDidStart:(NJHIDManager *)manager { @@ -181,6 +213,8 @@ - (void)HIDManagerDidStop:(NJHIDManager *)manager { [_devices removeAllObjects]; + if (displayLink) + CVDisplayLinkStop(displayLink); [self.delegate inputControllerDidStopHID:self]; } diff --git a/Enjoyable.xcodeproj/project.pbxproj b/Enjoyable.xcodeproj/project.pbxproj index 8e9a213..c6b1cd9 100644 --- a/Enjoyable.xcodeproj/project.pbxproj +++ b/Enjoyable.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; }; D594BF000FAE7397007A85F2 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D594BEFF0FAE7397007A85F2 /* IOKit.framework */; }; EE1F3CEA16EF4182008C6426 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = EE1F3CE816EF4182008C6426 /* Localizable.strings */; }; + EE35A6E417BBCC9500413995 /* CoreVideo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE35A6E317BBCC9500413995 /* CoreVideo.framework */; }; EE3D897A16EA7EFC00596D1F /* Status Menu Icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = EE3D897916EA7EFC00596D1F /* Status Menu Icon@2x.png */; }; EE3D897C16EA806E00596D1F /* Status Menu Icon Disabled@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = EE3D897B16EA806E00596D1F /* Status Menu Icon Disabled@2x.png */; }; EE3D897F16EA817E00596D1F /* Status Menu Icon Disabled.png in Resources */ = {isa = PBXBuildFile; fileRef = EE3D897D16EA817E00596D1F /* Status Menu Icon Disabled.png */; }; @@ -77,6 +78,7 @@ 8D1107320486CEB800E47090 /* Enjoyable.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Enjoyable.app; sourceTree = BUILT_PRODUCTS_DIR; }; D594BEFF0FAE7397007A85F2 /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = /System/Library/Frameworks/IOKit.framework; sourceTree = ""; }; EE1F3CE916EF4182008C6426 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = Resources/English.lproj/Localizable.strings; sourceTree = ""; }; + EE35A6E317BBCC9500413995 /* CoreVideo.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreVideo.framework; path = System/Library/Frameworks/CoreVideo.framework; sourceTree = SDKROOT; }; EE3D897916EA7EFC00596D1F /* Status Menu Icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Status Menu Icon@2x.png"; path = "Resources/Status Menu Icon@2x.png"; sourceTree = ""; }; EE3D897B16EA806E00596D1F /* Status Menu Icon Disabled@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Status Menu Icon Disabled@2x.png"; path = "Resources/Status Menu Icon Disabled@2x.png"; sourceTree = ""; }; EE3D897D16EA817E00596D1F /* Status Menu Icon Disabled.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Status Menu Icon Disabled.png"; path = "Resources/Status Menu Icon Disabled.png"; sourceTree = ""; }; @@ -159,6 +161,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + EE35A6E417BBCC9500413995 /* CoreVideo.framework in Frameworks */, 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */, D594BF000FAE7397007A85F2 /* IOKit.framework in Frameworks */, EED4CE7716EE195100C65AA8 /* Sparkle.framework in Frameworks */, @@ -211,6 +214,7 @@ 29B97314FDCFA39411CA2CEA /* Enjoy */ = { isa = PBXGroup; children = ( + EE35A6E317BBCC9500413995 /* CoreVideo.framework */, 8D1107310486CEB800E47090 /* Info.plist */, 080E96DDFE201D6D7F000001 /* Classes */, EE1D5F8B16E403D600749C36 /* Categories */, diff --git a/Info.plist b/Info.plist index d480f07..9917f03 100644 --- a/Info.plist +++ b/Info.plist @@ -46,7 +46,7 @@ CFBundleSignature ???? CFBundleVersion - 583 + 598 LSApplicationCategoryType public.app-category.utilities NSHumanReadableCopyright -- 2.30.2