Index: XeeTileImage.h =================================================================== --- XeeTileImage.h (revision 28) +++ XeeTileImage.h (working copy) @@ -2,8 +2,8 @@ #import "XeeBitmapTile.h" #import "XeeSampleSet.h" -#import -#import +#import +#import typedef void (*XeeReadPixelFunction)(uint8 *row,int x,int pixelsize,uint8 *dest); Index: XeeView.h =================================================================== --- XeeView.h (revision 28) +++ XeeView.h (working copy) @@ -1,7 +1,7 @@ #import "XeeTypes.h" -#import -#import +#import +#import @class XeeImage,XeeController,XeeTool; Index: XeeView.m =================================================================== --- XeeView.m (revision 28) +++ XeeView.m (working copy) @@ -5,8 +5,8 @@ #import "XeeGraphicsStuff.h" #import "CSKeyboardShortcuts.h" -#import -#import +#import +#import #import Index: XeeBitmapTile.h =================================================================== --- XeeBitmapTile.h (revision 28) +++ XeeBitmapTile.h (working copy) @@ -1,7 +1,7 @@ #include "XeeTypes.h" -#import -#import +#import +#import @interface XeeBitmapTile:NSObject { Index: Xee.xcodeproj/project.pbxproj =================================================================== --- Xee.xcodeproj/project.pbxproj (revision 28) +++ Xee.xcodeproj/project.pbxproj (working copy) @@ -294,6 +294,14 @@ 1BFD2D03098325D0000798A7 /* remove_idle.png in Resources */ = {isa = PBXBuildFile; fileRef = 1BFD2CFD098325D0000798A7 /* remove_idle.png */; }; 1BFD2D04098325D0000798A7 /* add_idle.png in Resources */ = {isa = PBXBuildFile; fileRef = 1BFD2CFE098325D0000798A7 /* add_idle.png */; }; 1BFF73290B6AF5C500E0900B /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1BFF73280B6AF5C500E0900B /* IOKit.framework */; }; + 1D262A520F20C264002FF19A /* AppleRemote.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D262A450F20C264002FF19A /* AppleRemote.m */; }; + 1D262A530F20C264002FF19A /* GlobalKeyboardDevice.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D262A470F20C264002FF19A /* GlobalKeyboardDevice.m */; }; + 1D262A540F20C264002FF19A /* HIDRemoteControlDevice.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D262A490F20C264002FF19A /* HIDRemoteControlDevice.m */; }; + 1D262A550F20C264002FF19A /* KeyspanFrontRowControl.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D262A4B0F20C264002FF19A /* KeyspanFrontRowControl.m */; }; + 1D262A560F20C264002FF19A /* MultiClickRemoteBehavior.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D262A4D0F20C264002FF19A /* MultiClickRemoteBehavior.m */; }; + 1D262A570F20C264002FF19A /* RemoteControl.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D262A4F0F20C264002FF19A /* RemoteControl.m */; }; + 1D262A580F20C264002FF19A /* RemoteControlContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D262A510F20C264002FF19A /* RemoteControlContainer.m */; }; + 1D262A5B0F20C294002FF19A /* XeeControllerRemoteActions.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D262A5A0F20C294002FF19A /* XeeControllerRemoteActions.m */; }; 8D15AC2D0486D014006FF6A4 /* MainMenu.nib in Resources */ = {isa = PBXBuildFile; fileRef = 2A37F4B6FDCFA73011CA2CEA /* MainMenu.nib */; }; 8D15AC2F0486D014006FF6A4 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165FFE840EACC02AAC07 /* InfoPlist.strings */; }; 8D15AC320486D014006FF6A4 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A37F4B0FDCFA73011CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; }; @@ -715,6 +723,24 @@ 1BFF74F90B6C2B2D00E0900B /* ru */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = ru; path = ru.lproj/PrefsWindow.nib; sourceTree = ""; }; 1BFF74FC0B6C2B3700E0900B /* Japanese */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = Japanese; path = Japanese.lproj/MainMenu.nib; sourceTree = ""; }; 1BFF74FD0B6C2B3A00E0900B /* ru */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = ru; path = ru.lproj/MainMenu.nib; sourceTree = ""; }; + 1D262A3A0F20C214002FF19A /* UniversalDetector.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniversalDetector.framework; path = "../Xee 2/UniversalDetector.framework"; sourceTree = SOURCE_ROOT; }; + 1D262A3B0F20C214002FF19A /* XADMaster.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XADMaster.framework; path = "../Xee 2/XADMaster.framework"; sourceTree = SOURCE_ROOT; }; + 1D262A440F20C264002FF19A /* AppleRemote.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppleRemote.h; path = RemoteControllerWrapper/AppleRemote.h; sourceTree = ""; }; + 1D262A450F20C264002FF19A /* AppleRemote.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppleRemote.m; path = RemoteControllerWrapper/AppleRemote.m; sourceTree = ""; }; + 1D262A460F20C264002FF19A /* GlobalKeyboardDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GlobalKeyboardDevice.h; path = RemoteControllerWrapper/GlobalKeyboardDevice.h; sourceTree = ""; }; + 1D262A470F20C264002FF19A /* GlobalKeyboardDevice.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GlobalKeyboardDevice.m; path = RemoteControllerWrapper/GlobalKeyboardDevice.m; sourceTree = ""; }; + 1D262A480F20C264002FF19A /* HIDRemoteControlDevice.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HIDRemoteControlDevice.h; path = RemoteControllerWrapper/HIDRemoteControlDevice.h; sourceTree = ""; }; + 1D262A490F20C264002FF19A /* HIDRemoteControlDevice.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = HIDRemoteControlDevice.m; path = RemoteControllerWrapper/HIDRemoteControlDevice.m; sourceTree = ""; }; + 1D262A4A0F20C264002FF19A /* KeyspanFrontRowControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = KeyspanFrontRowControl.h; path = RemoteControllerWrapper/KeyspanFrontRowControl.h; sourceTree = ""; }; + 1D262A4B0F20C264002FF19A /* KeyspanFrontRowControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = KeyspanFrontRowControl.m; path = RemoteControllerWrapper/KeyspanFrontRowControl.m; sourceTree = ""; }; + 1D262A4C0F20C264002FF19A /* MultiClickRemoteBehavior.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MultiClickRemoteBehavior.h; path = RemoteControllerWrapper/MultiClickRemoteBehavior.h; sourceTree = ""; }; + 1D262A4D0F20C264002FF19A /* MultiClickRemoteBehavior.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MultiClickRemoteBehavior.m; path = RemoteControllerWrapper/MultiClickRemoteBehavior.m; sourceTree = ""; }; + 1D262A4E0F20C264002FF19A /* RemoteControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RemoteControl.h; path = RemoteControllerWrapper/RemoteControl.h; sourceTree = ""; }; + 1D262A4F0F20C264002FF19A /* RemoteControl.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RemoteControl.m; path = RemoteControllerWrapper/RemoteControl.m; sourceTree = ""; }; + 1D262A500F20C264002FF19A /* RemoteControlContainer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RemoteControlContainer.h; path = RemoteControllerWrapper/RemoteControlContainer.h; sourceTree = ""; }; + 1D262A510F20C264002FF19A /* RemoteControlContainer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RemoteControlContainer.m; path = RemoteControllerWrapper/RemoteControlContainer.m; sourceTree = ""; }; + 1D262A590F20C294002FF19A /* XeeControllerRemoteActions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XeeControllerRemoteActions.h; sourceTree = ""; }; + 1D262A5A0F20C294002FF19A /* XeeControllerRemoteActions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XeeControllerRemoteActions.m; sourceTree = ""; }; 2A37F4B0FDCFA73011CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 2A37F4B7FDCFA73011CA2CEA /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = English.lproj/MainMenu.nib; sourceTree = ""; }; 32DBCF750370BD2300C91783 /* Xee_Prefix.pch */ = {isa = PBXFileReference; explicitFileType = sourcecode.c.h; fileEncoding = 4; path = Xee_Prefix.pch; sourceTree = ""; }; @@ -1253,10 +1279,32 @@ name = "Image Sources"; sourceTree = ""; }; + 1D262A400F20C21B002FF19A /* Remote Controller */ = { + isa = PBXGroup; + children = ( + 1D262A440F20C264002FF19A /* AppleRemote.h */, + 1D262A450F20C264002FF19A /* AppleRemote.m */, + 1D262A460F20C264002FF19A /* GlobalKeyboardDevice.h */, + 1D262A470F20C264002FF19A /* GlobalKeyboardDevice.m */, + 1D262A480F20C264002FF19A /* HIDRemoteControlDevice.h */, + 1D262A490F20C264002FF19A /* HIDRemoteControlDevice.m */, + 1D262A4A0F20C264002FF19A /* KeyspanFrontRowControl.h */, + 1D262A4B0F20C264002FF19A /* KeyspanFrontRowControl.m */, + 1D262A4C0F20C264002FF19A /* MultiClickRemoteBehavior.h */, + 1D262A4D0F20C264002FF19A /* MultiClickRemoteBehavior.m */, + 1D262A4E0F20C264002FF19A /* RemoteControl.h */, + 1D262A4F0F20C264002FF19A /* RemoteControl.m */, + 1D262A500F20C264002FF19A /* RemoteControlContainer.h */, + 1D262A510F20C264002FF19A /* RemoteControlContainer.m */, + ); + name = "Remote Controller"; + sourceTree = ""; + }; 2A37F4AAFDCFA73011CA2CEA /* Xee */ = { isa = PBXGroup; children = ( 2A37F4ABFDCFA73011CA2CEA /* Controllers */, + 1D262A400F20C21B002FF19A /* Remote Controller */, 1BE7D7C70B2F58D8009166F5 /* View Classes */, 1BE5E4B00A8249CF00967AD2 /* Other UI Elements */, 1BFB0E080B126FAA0088CDB4 /* Image Sources */, @@ -1278,6 +1326,8 @@ 2A37F4ABFDCFA73011CA2CEA /* Controllers */ = { isa = PBXGroup; children = ( + 1D262A590F20C294002FF19A /* XeeControllerRemoteActions.h */, + 1D262A5A0F20C294002FF19A /* XeeControllerRemoteActions.m */, 1B5D4917086DFD7F004480AB /* XeeDelegate.h */, 1B5D4918086DFD7F004480AB /* XeeDelegate.m */, 1B5D4904086DF3F8004480AB /* XeeController.h */, @@ -1329,6 +1379,8 @@ 2A37F4C3FDCFA73011CA2CEA /* Frameworks */ = { isa = PBXGroup; children = ( + 1D262A3A0F20C214002FF19A /* UniversalDetector.framework */, + 1D262A3B0F20C214002FF19A /* XADMaster.framework */, 1BCA0AA60E9EBBC50016D207 /* libcrypto.dylib */, 1BE4C13108E2303200859EE2 /* libz.dylib */, 1058C7A7FEA54F5311CA2CBB /* Cocoa.framework */, @@ -1715,6 +1767,14 @@ 1B4077A60EB928AC008D5F17 /* XeeXBMLoader.m in Sources */, 1B4077A90EB928B5008D5F17 /* XeeXPMLoader.m in Sources */, 1B4078930EBE658B008D5F17 /* XeeDreamcastLoader.m in Sources */, + 1D262A520F20C264002FF19A /* AppleRemote.m in Sources */, + 1D262A530F20C264002FF19A /* GlobalKeyboardDevice.m in Sources */, + 1D262A540F20C264002FF19A /* HIDRemoteControlDevice.m in Sources */, + 1D262A550F20C264002FF19A /* KeyspanFrontRowControl.m in Sources */, + 1D262A560F20C264002FF19A /* MultiClickRemoteBehavior.m in Sources */, + 1D262A570F20C264002FF19A /* RemoteControl.m in Sources */, + 1D262A580F20C264002FF19A /* RemoteControlContainer.m in Sources */, + 1D262A5B0F20C294002FF19A /* XeeControllerRemoteActions.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; Index: XeeControllerNavigationActions.m =================================================================== --- XeeControllerNavigationActions.m (revision 28) +++ XeeControllerNavigationActions.m (working copy) @@ -1,5 +1,6 @@ #import "XeeControllerNavigationActions.h" #import "XeeImageSource.h" +#import @implementation XeeController (NavigationActions) @@ -135,6 +136,8 @@ -(void)slideshowStep:(NSTimer *)timer { + //prevent sleeping + UpdateSystemActivity(UsrActivity); int slideshowdelay=[[NSUserDefaults standardUserDefaults] integerForKey:@"slideshowDelay"]; if(++slideshowcount>=slideshowdelay) { Index: RemoteControllerWrapper/KeyspanFrontRowControl.h =================================================================== --- RemoteControllerWrapper/KeyspanFrontRowControl.h (revision 0) +++ RemoteControllerWrapper/KeyspanFrontRowControl.h (revision 0) @@ -0,0 +1,39 @@ +/***************************************************************************** + * KeyspanFrontRowControl.h + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + *****************************************************************************/ + + +#import +#import "HIDRemoteControlDevice.h" + +/* Interacts with the Keyspan FrontRow Remote Control HID device + The class is not thread safe +*/ +@interface KeyspanFrontRowControl : HIDRemoteControlDevice { + +} + +@end Index: RemoteControllerWrapper/MultiClickRemoteBehavior.h =================================================================== --- RemoteControllerWrapper/MultiClickRemoteBehavior.h (revision 0) +++ RemoteControllerWrapper/MultiClickRemoteBehavior.h (revision 0) @@ -0,0 +1,90 @@ +/***************************************************************************** + * MultiClickRemoteBehavior.h + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + *****************************************************************************/ + + +#import +#import "RemoteControl.h" + +/** + A behavior that adds multiclick and hold events on top of a device. + Events are generated and send to a delegate + */ +@interface MultiClickRemoteBehavior : NSObject { + id delegate; + + // state for simulating plus/minus hold + BOOL simulateHoldEvents; + BOOL lastEventSimulatedHold; + RemoteControlEventIdentifier lastHoldEvent; + NSTimeInterval lastHoldEventTime; + + // state for multi click + unsigned int clickCountEnabledButtons; + NSTimeInterval maxClickTimeDifference; + NSTimeInterval lastClickCountEventTime; + RemoteControlEventIdentifier lastClickCountEvent; + unsigned int eventClickCount; +} + +- (id) init; + +// Delegates are not retained +- (void) setDelegate: (id) delegate; +- (id) delegate; + +// Simulating hold events does deactivate sending of individual requests for pressed down/released. +// Instead special hold events are being triggered when the user is pressing and holding a button for a small period. +// Simulation is activated only for those buttons and remote control that do not have a seperate event already +- (BOOL) simulateHoldEvent; +- (void) setSimulateHoldEvent: (BOOL) value; + +// click counting makes it possible to recognize if the user has pressed a button repeatedly +// click counting does delay each event as it has to wait if there is another event (second click) +// therefore there is a slight time difference (maximumClickCountTimeDifference) between a single click +// of the user and the call of your delegate method +// click counting can be enabled individually for specific buttons. Use the property clickCountEnableButtons to +// set the buttons for which click counting shall be enabled +- (BOOL) clickCountingEnabled; +- (void) setClickCountingEnabled: (BOOL) value; + +- (unsigned int) clickCountEnabledButtons; +- (void) setClickCountEnabledButtons: (unsigned int)value; + +// the maximum time difference till which clicks are recognized as multi clicks +- (NSTimeInterval) maximumClickCountTimeDifference; +- (void) setMaximumClickCountTimeDifference: (NSTimeInterval) timeDiff; + +@end + +/* + * Method definitions for the delegate of the MultiClickRemoteBehavior class + */ +@interface NSObject(MultiClickRemoteBehaviorDelegate) + +- (void) remoteButton: (RemoteControlEventIdentifier)buttonIdentifier pressedDown: (BOOL) pressedDown clickCount: (unsigned int) count; + +@end Index: RemoteControllerWrapper/RemoteControl.h =================================================================== --- RemoteControllerWrapper/RemoteControl.h (revision 0) +++ RemoteControllerWrapper/RemoteControl.h (revision 0) @@ -0,0 +1,102 @@ +/***************************************************************************** + * RemoteControl.h + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + *****************************************************************************/ + +#import + +// notifaction names that are being used to signal that an application wants to +// have access to the remote control device or if the application has finished +// using the remote control device +extern NSString* REQUEST_FOR_REMOTE_CONTROL_NOTIFCATION; +extern NSString* FINISHED_USING_REMOTE_CONTROL_NOTIFICATION; + +// keys used in user objects for distributed notifications +extern NSString* kRemoteControlDeviceName; +extern NSString* kApplicationIdentifier; +extern NSString* kTargetApplicationIdentifier; + +// we have a 6 bit offset to make a hold event out of a normal event +#define EVENT_TO_HOLD_EVENT_OFFSET 6 + +@class RemoteControl; + +typedef enum _RemoteControlEventIdentifier { + // normal events + kRemoteButtonPlus =1<<1, + kRemoteButtonMinus =1<<2, + kRemoteButtonMenu =1<<3, + kRemoteButtonPlay =1<<4, + kRemoteButtonRight =1<<5, + kRemoteButtonLeft =1<<6, + + // hold events + kRemoteButtonPlus_Hold =1<<7, + kRemoteButtonMinus_Hold =1<<8, + kRemoteButtonMenu_Hold =1<<9, + kRemoteButtonPlay_Hold =1<<10, + kRemoteButtonRight_Hold =1<<11, + kRemoteButtonLeft_Hold =1<<12, + + // special events (not supported by all devices) + kRemoteControl_Switched =1<<13, +} RemoteControlEventIdentifier; + +@interface NSObject(RemoteControlDelegate) + +- (void) sendRemoteButtonEvent: (RemoteControlEventIdentifier) event pressedDown: (BOOL) pressedDown remoteControl: (RemoteControl*) remoteControl; + +@end + +/* + Base Interface for Remote Control devices +*/ +@interface RemoteControl : NSObject { + id delegate; +} + +// returns nil if the remote control device is not available +- (id) initWithDelegate: (id) remoteControlDelegate; + +- (void) setListeningToRemote: (BOOL) value; +- (BOOL) isListeningToRemote; + +- (BOOL) isOpenInExclusiveMode; +- (void) setOpenInExclusiveMode: (BOOL) value; + +- (IBAction) startListening: (id) sender; +- (IBAction) stopListening: (id) sender; + +// is this remote control sending the given event? +- (BOOL) sendsEventForButtonIdentifier: (RemoteControlEventIdentifier) identifier; + +// sending of notifications between applications ++ (void) sendFinishedNotifcationForAppIdentifier: (NSString*) identifier; ++ (void) sendRequestForRemoteControlNotification; + +// name of the device ++ (const char*) remoteControlDeviceName; + +@end Index: RemoteControllerWrapper/AppleRemote.h =================================================================== --- RemoteControllerWrapper/AppleRemote.h (revision 0) +++ RemoteControllerWrapper/AppleRemote.h (revision 0) @@ -0,0 +1,37 @@ +/***************************************************************************** + * RemoteControlWrapper.h + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED ‚ÄúAS IS‚Äù, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + *****************************************************************************/ + +#import +#import "HIDRemoteControlDevice.h" + +/* Interacts with the Apple Remote Control HID device + The class is not thread safe +*/ +@interface AppleRemote : HIDRemoteControlDevice { +} + +@end Index: RemoteControllerWrapper/RemoteControlContainer.h =================================================================== --- RemoteControllerWrapper/RemoteControlContainer.h (revision 0) +++ RemoteControllerWrapper/RemoteControlContainer.h (revision 0) @@ -0,0 +1,38 @@ +/***************************************************************************** + * RemoteControlContainer.h + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + *****************************************************************************/ + +#import +#import "RemoteControl.h" + +@interface RemoteControlContainer : RemoteControl { + NSMutableArray* remoteControls; +} + +- (BOOL) instantiateAndAddRemoteControlDeviceWithClass: (Class) clazz; +- (unsigned int) count; + +@end Index: RemoteControllerWrapper/GlobalKeyboardDevice.h =================================================================== --- RemoteControllerWrapper/GlobalKeyboardDevice.h (revision 0) +++ RemoteControllerWrapper/GlobalKeyboardDevice.h (revision 0) @@ -0,0 +1,49 @@ +/***************************************************************************** + * GlobalKeyboardDevice.h + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + *****************************************************************************/ + +#import +#import + +#import "RemoteControl.h" + +/* + This class registers for a number of global keyboard shortcuts to simulate a remote control + */ +@interface GlobalKeyboardDevice : RemoteControl { + + NSMutableDictionary* hotKeyRemoteEventMapping; + EventHandlerRef eventHandlerRef; + +} + +- (void) mapRemoteButton: (RemoteControlEventIdentifier) remoteButtonIdentifier defaultKeycode: (unsigned int) defaultKeycode defaultModifiers: (unsigned int) defaultModifiers; + +- (BOOL)registerHotKeyCode: (unsigned int) keycode modifiers: (unsigned int) modifiers remoteEventIdentifier: (RemoteControlEventIdentifier) identifier; + +static OSStatus hotKeyEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEvent, void* refCon ); + +@end Index: RemoteControllerWrapper/HIDRemoteControlDevice.h =================================================================== --- RemoteControllerWrapper/HIDRemoteControlDevice.h (revision 0) +++ RemoteControllerWrapper/HIDRemoteControlDevice.h (revision 0) @@ -0,0 +1,64 @@ +/***************************************************************************** + * HIDRemoteControlDevice.h + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED ‚ÄúAS IS‚Äù, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + *****************************************************************************/ + +#import +#import + +#import "RemoteControl.h" + +/* + Base class for HID based remote control devices + */ +@interface HIDRemoteControlDevice : RemoteControl { + IOHIDDeviceInterface** hidDeviceInterface; + IOHIDQueueInterface** queue; + NSMutableArray* allCookies; + NSMutableDictionary* cookieToButtonMapping; + CFRunLoopSourceRef eventSource; + + BOOL fixSecureEventInputBug; + BOOL openInExclusiveMode; + BOOL processesBacklog; + + int supportedButtonEvents; +} + +// When your application needs to much time on the main thread when processing an event other events +// may already be received which are put on a backlog. As soon as your main thread +// has some spare time this backlog is processed and may flood your delegate with calls. +// Backlog processing is turned off by default. +- (BOOL) processesBacklog; +- (void) setProcessesBacklog: (BOOL) value; + +// methods that should be overwritten by subclasses +- (void) setCookieMappingInDictionary: (NSMutableDictionary*) cookieToButtonMapping; + +- (void) sendRemoteButtonEvent: (RemoteControlEventIdentifier) event pressedDown: (BOOL) pressedDown; + ++ (BOOL) isRemoteAvailable; + +@end Index: RemoteControllerWrapper/KeyspanFrontRowControl.m =================================================================== --- RemoteControllerWrapper/KeyspanFrontRowControl.m (revision 0) +++ RemoteControllerWrapper/KeyspanFrontRowControl.m (revision 0) @@ -0,0 +1,87 @@ +/***************************************************************************** + * KeyspanFrontRowControl.m + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + *****************************************************************************/ + +#import "KeyspanFrontRowControl.h" +#import +#import +#import +#import +#import + +@implementation KeyspanFrontRowControl + +- (void) setCookieMappingInDictionary: (NSMutableDictionary*) _cookieToButtonMapping { + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlus] forKey:@"11_18_99_10_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMinus] forKey:@"11_18_98_10_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu] forKey:@"11_18_58_10_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay] forKey:@"11_18_61_10_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight] forKey:@"11_18_96_10_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft] forKey:@"11_18_97_10_"]; + /* hold events are not being send by this device + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight_Hold] forKey:@"14_6_4_2_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft_Hold] forKey:@"14_6_3_2_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu_Hold] forKey:@"14_6_14_6_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay_Sleep] forKey:@"18_14_6_18_14_6_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteControl_Switched] forKey:@"19_"]; + */ +} + ++ (io_object_t) findRemoteDevice { + CFMutableDictionaryRef hidMatchDictionary = NULL; + IOReturn ioReturnValue = kIOReturnSuccess; + io_iterator_t hidObjectIterator = 0; + io_object_t hidDevice = 0; + SInt32 idVendor = 1741; + SInt32 idProduct = 0x420; + + // Set up a matching dictionary to search the I/O Registry by class + // name for all HID class devices + hidMatchDictionary = IOServiceMatching(kIOHIDDeviceKey); + + CFNumberRef numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &idVendor); + CFDictionaryAddValue(hidMatchDictionary, CFSTR(kIOHIDVendorIDKey), numberRef); + CFRelease(numberRef); + + numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &idProduct); + CFDictionaryAddValue(hidMatchDictionary, CFSTR(kIOHIDProductIDKey), numberRef); + CFRelease(numberRef); + + // Now search I/O Registry for matching devices. + ioReturnValue = IOServiceGetMatchingServices(kIOMasterPortDefault, hidMatchDictionary, &hidObjectIterator); + + if ((ioReturnValue == kIOReturnSuccess) && (hidObjectIterator != 0)) { + hidDevice = IOIteratorNext(hidObjectIterator); + } + + // release the iterator + IOObjectRelease(hidObjectIterator); + + return hidDevice; + +} + +@end Index: RemoteControllerWrapper/MultiClickRemoteBehavior.m =================================================================== --- RemoteControllerWrapper/MultiClickRemoteBehavior.m (revision 0) +++ RemoteControllerWrapper/MultiClickRemoteBehavior.m (revision 0) @@ -0,0 +1,210 @@ +/***************************************************************************** + * MultiClickRemoteBehavior.m + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + *****************************************************************************/ + +#import "MultiClickRemoteBehavior.h" + +const NSTimeInterval DEFAULT_MAXIMUM_CLICK_TIME_DIFFERENCE=0.35; +const NSTimeInterval HOLD_RECOGNITION_TIME_INTERVAL=0.4; + +@implementation MultiClickRemoteBehavior + +- (id) init { + if (self = [super init]) { + maxClickTimeDifference = DEFAULT_MAXIMUM_CLICK_TIME_DIFFERENCE; + } + return self; +} + +// Delegates are not retained! +// http://developer.apple.com/documentation/Cocoa/Conceptual/CocoaFundamentals/CommunicatingWithObjects/chapter_6_section_4.html +// Delegating objects do not (and should not) retain their delegates. +// However, clients of delegating objects (applications, usually) are responsible for ensuring that their delegates are around +// to receive delegation messages. To do this, they may have to retain the delegate. +- (void) setDelegate: (id) _delegate { + if (_delegate && [_delegate respondsToSelector:@selector(remoteButton:pressedDown:clickCount:)]==NO) return; + + delegate = _delegate; +} +- (id) delegate { + return delegate; +} + +- (BOOL) simulateHoldEvent { + return simulateHoldEvents; +} +- (void) setSimulateHoldEvent: (BOOL) value { + simulateHoldEvents = value; +} + +- (BOOL) simulatesHoldForButtonIdentifier: (RemoteControlEventIdentifier) identifier remoteControl: (RemoteControl*) remoteControl { + // we do that check only for the normal button identifiers as we would check for hold support for hold events instead + if (identifier > (1 << EVENT_TO_HOLD_EVENT_OFFSET)) return NO; + + return [self simulateHoldEvent] && [remoteControl sendsEventForButtonIdentifier: (identifier << EVENT_TO_HOLD_EVENT_OFFSET)]==NO; +} + +- (BOOL) clickCountingEnabled { + return clickCountEnabledButtons != 0; +} +- (void) setClickCountingEnabled: (BOOL) value { + if (value) { + [self setClickCountEnabledButtons: kRemoteButtonPlus | kRemoteButtonMinus | kRemoteButtonPlay | kRemoteButtonLeft | kRemoteButtonRight | kRemoteButtonMenu]; + } else { + [self setClickCountEnabledButtons: 0]; + } +} + +- (unsigned int) clickCountEnabledButtons { + return clickCountEnabledButtons; +} +- (void) setClickCountEnabledButtons: (unsigned int)value { + clickCountEnabledButtons = value; +} + +- (NSTimeInterval) maximumClickCountTimeDifference { + return maxClickTimeDifference; +} +- (void) setMaximumClickCountTimeDifference: (NSTimeInterval) timeDiff { + maxClickTimeDifference = timeDiff; +} + +- (void) sendSimulatedHoldEvent: (id) time { + BOOL startSimulateHold = NO; + RemoteControlEventIdentifier event = lastHoldEvent; + @synchronized(self) { + startSimulateHold = (lastHoldEvent>0 && lastHoldEventTime == [time doubleValue]); + } + if (startSimulateHold) { + lastEventSimulatedHold = YES; + event = (event << EVENT_TO_HOLD_EVENT_OFFSET); + [delegate remoteButton:event pressedDown: YES clickCount: 1]; + } +} + +- (void) executeClickCountEvent: (NSArray*) values { + RemoteControlEventIdentifier event = [[values objectAtIndex: 0] unsignedIntValue]; + NSTimeInterval eventTimePoint = [[values objectAtIndex: 1] doubleValue]; + + BOOL finishedClicking = NO; + int finalClickCount = eventClickCount; + + @synchronized(self) { + finishedClicking = (event != lastClickCountEvent || eventTimePoint == lastClickCountEventTime); + if (finishedClicking) { + eventClickCount = 0; + lastClickCountEvent = 0; + lastClickCountEventTime = 0; + } + } + + if (finishedClicking) { + [delegate remoteButton:event pressedDown: YES clickCount:finalClickCount]; + // trigger a button release event, too + [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow:0.1]]; + [delegate remoteButton:event pressedDown: NO clickCount:finalClickCount]; + } +} + +- (void) sendRemoteButtonEvent: (RemoteControlEventIdentifier) event pressedDown: (BOOL) pressedDown remoteControl: (RemoteControl*) remoteControl { + if (!delegate) return; + + BOOL clickCountingForEvent = ([self clickCountEnabledButtons] & event) == event; + + if ([self simulatesHoldForButtonIdentifier: event remoteControl: remoteControl] && lastClickCountEvent==0) { + if (pressedDown) { + // wait to see if it is a hold + lastHoldEvent = event; + lastHoldEventTime = [NSDate timeIntervalSinceReferenceDate]; + [self performSelector:@selector(sendSimulatedHoldEvent:) + withObject:[NSNumber numberWithDouble:lastHoldEventTime] + afterDelay:HOLD_RECOGNITION_TIME_INTERVAL]; + return; + } else { + if (lastEventSimulatedHold) { + // it was a hold + // send an event for "hold release" + event = (event << EVENT_TO_HOLD_EVENT_OFFSET); + lastHoldEvent = 0; + lastEventSimulatedHold = NO; + + [delegate remoteButton:event pressedDown: pressedDown clickCount:1]; + return; + } else { + RemoteControlEventIdentifier previousEvent = lastHoldEvent; + @synchronized(self) { + lastHoldEvent = 0; + } + + // in case click counting is enabled we have to setup the state for that, too + if (clickCountingForEvent) { + lastClickCountEvent = previousEvent; + lastClickCountEventTime = lastHoldEventTime; + NSNumber* eventNumber; + NSNumber* timeNumber; + eventClickCount = 1; + timeNumber = [NSNumber numberWithDouble:lastClickCountEventTime]; + eventNumber= [NSNumber numberWithUnsignedInt:previousEvent]; + NSTimeInterval diffTime = maxClickTimeDifference-([NSDate timeIntervalSinceReferenceDate]-lastHoldEventTime); + [self performSelector: @selector(executeClickCountEvent:) + withObject: [NSArray arrayWithObjects:eventNumber, timeNumber, nil] + afterDelay: diffTime]; + // we do not return here because we are still in the press-release event + // that will be consumed below + } else { + // trigger the pressed down event that we consumed first + [delegate remoteButton:event pressedDown: YES clickCount:1]; + } + } + } + } + + if (clickCountingForEvent) { + if (pressedDown == NO) return; + + NSNumber* eventNumber; + NSNumber* timeNumber; + @synchronized(self) { + lastClickCountEventTime = [NSDate timeIntervalSinceReferenceDate]; + if (lastClickCountEvent == event) { + eventClickCount = eventClickCount + 1; + } else { + eventClickCount = 1; + } + lastClickCountEvent = event; + timeNumber = [NSNumber numberWithDouble:lastClickCountEventTime]; + eventNumber= [NSNumber numberWithUnsignedInt:event]; + } + [self performSelector: @selector(executeClickCountEvent:) + withObject: [NSArray arrayWithObjects:eventNumber, timeNumber, nil] + afterDelay: maxClickTimeDifference]; + } else { + [delegate remoteButton:event pressedDown: pressedDown clickCount:1]; + } + +} + +@end Index: RemoteControllerWrapper/RemoteControl.m =================================================================== --- RemoteControllerWrapper/RemoteControl.m (revision 0) +++ RemoteControllerWrapper/RemoteControl.m (revision 0) @@ -0,0 +1,103 @@ +/***************************************************************************** + * RemoteControl.m + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + *****************************************************************************/ + +#import "RemoteControl.h" + +// notifaction names that are being used to signal that an application wants to +// have access to the remote control device or if the application has finished +// using the remote control device +NSString* REQUEST_FOR_REMOTE_CONTROL_NOTIFCATION = @"mac.remotecontrols.RequestForRemoteControl"; +NSString* FINISHED_USING_REMOTE_CONTROL_NOTIFICATION = @"mac.remotecontrols.FinishedUsingRemoteControl"; + +// keys used in user objects for distributed notifications +NSString* kRemoteControlDeviceName = @"RemoteControlDeviceName"; +NSString* kApplicationIdentifier = @"CFBundleIdentifier"; +// bundle identifier of the application that should get access to the remote control +// this key is being used in the FINISHED notification only +NSString* kTargetApplicationIdentifier = @"TargetBundleIdentifier"; + + +@implementation RemoteControl + +// returns nil if the remote control device is not available +- (id) initWithDelegate: (id) _remoteControlDelegate { + if (self = [super init]) { + delegate = [_remoteControlDelegate retain]; + } + return self; +} + +- (void) dealloc { + [delegate release]; + [super dealloc]; +} + +- (void) setListeningToRemote: (BOOL) value { +} +- (BOOL) isListeningToRemote { + return NO; +} + +- (IBAction) startListening: (id) sender { +} +- (IBAction) stopListening: (id) sender { + +} + +- (BOOL) isOpenInExclusiveMode { + return YES; +} +- (void) setOpenInExclusiveMode: (BOOL) value { +} + +- (BOOL) sendsEventForButtonIdentifier: (RemoteControlEventIdentifier) identifier { + return YES; +} + ++ (void) sendDistributedNotification: (NSString*) notificationName targetBundleIdentifier: (NSString*) targetIdentifier { + NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys: [NSString stringWithCString:[self remoteControlDeviceName] encoding:NSASCIIStringEncoding], + kRemoteControlDeviceName, [[NSBundle mainBundle] bundleIdentifier], kApplicationIdentifier, + targetIdentifier, kTargetApplicationIdentifier, nil]; + + [[NSDistributedNotificationCenter defaultCenter] postNotificationName:notificationName + object:nil + userInfo:userInfo + deliverImmediately:YES]; +} + ++ (void) sendFinishedNotifcationForAppIdentifier: (NSString*) identifier { + [self sendDistributedNotification:FINISHED_USING_REMOTE_CONTROL_NOTIFICATION targetBundleIdentifier:identifier]; +} ++ (void) sendRequestForRemoteControlNotification { + [self sendDistributedNotification:REQUEST_FOR_REMOTE_CONTROL_NOTIFCATION targetBundleIdentifier:nil]; +} + ++ (const char*) remoteControlDeviceName { + return NULL; +} + +@end Index: RemoteControllerWrapper/AppleRemote.m =================================================================== --- RemoteControllerWrapper/AppleRemote.m (revision 0) +++ RemoteControllerWrapper/AppleRemote.m (revision 0) @@ -0,0 +1,93 @@ +/***************************************************************************** + * RemoteControlWrapper.m + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + *****************************************************************************/ + +#import "AppleRemote.h" + +#import +#import +#import +#import +#import + +const char* AppleRemoteDeviceName = "AppleIRController"; + +// the WWDC 07 Leopard Build is missing the constant +#ifndef NSAppKitVersionNumber10_4 + #define NSAppKitVersionNumber10_4 824 +#endif + +@implementation AppleRemote + ++ (const char*) remoteControlDeviceName { + return AppleRemoteDeviceName; +} + +- (void) setCookieMappingInDictionary: (NSMutableDictionary*) _cookieToButtonMapping { + if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_4) { + // 10.4.x Tiger + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlus] forKey:@"14_12_11_6_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMinus] forKey:@"14_13_11_6_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu] forKey:@"14_7_6_14_7_6_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay] forKey:@"14_8_6_14_8_6_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight] forKey:@"14_9_6_14_9_6_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft] forKey:@"14_10_6_14_10_6_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight_Hold] forKey:@"14_6_4_2_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft_Hold] forKey:@"14_6_3_2_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu_Hold] forKey:@"14_6_14_6_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay_Hold] forKey:@"18_14_6_18_14_6_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteControl_Switched] forKey:@"19_"]; + } else { + // 10.5.x Leopard + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlus] forKey:@"31_29_28_19_18_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMinus] forKey:@"31_30_28_19_18_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu] forKey:@"31_20_19_18_31_20_19_18_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay] forKey:@"31_21_19_18_31_21_19_18_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight] forKey:@"31_22_19_18_31_22_19_18_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft] forKey:@"31_23_19_18_31_23_19_18_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight_Hold] forKey:@"31_19_18_4_2_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft_Hold] forKey:@"31_19_18_3_2_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu_Hold] forKey:@"31_19_18_31_19_18_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay_Hold] forKey:@"35_31_19_18_35_31_19_18_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteControl_Switched] forKey:@"19_"]; + } +} + +- (void) sendRemoteButtonEvent: (RemoteControlEventIdentifier) event pressedDown: (BOOL) pressedDown { + if (pressedDown == NO && event == kRemoteButtonMenu_Hold) { + // There is no seperate event for pressed down on menu hold. We are simulating that event here + [super sendRemoteButtonEvent:event pressedDown:YES]; + } + + [super sendRemoteButtonEvent:event pressedDown:pressedDown]; + + if (pressedDown && (event == kRemoteButtonRight || event == kRemoteButtonLeft || event == kRemoteButtonPlay || event == kRemoteButtonMenu || event == kRemoteButtonPlay_Hold)) { + // There is no seperate event when the button is being released. We are simulating that event here + [super sendRemoteButtonEvent:event pressedDown:NO]; + } +} + +@end Index: RemoteControllerWrapper/RemoteControlContainer.m =================================================================== --- RemoteControllerWrapper/RemoteControlContainer.m (revision 0) +++ RemoteControllerWrapper/RemoteControlContainer.m (revision 0) @@ -0,0 +1,114 @@ +/***************************************************************************** + * RemoteControlContainer.m + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + *****************************************************************************/ + +#import "RemoteControlContainer.h" + + +@implementation RemoteControlContainer + +- (id) initWithDelegate: (id) _remoteControlDelegate { + if (self = [super initWithDelegate:_remoteControlDelegate]) { + remoteControls = [[NSMutableArray alloc] init]; + } + return self; +} + +- (void) dealloc { + [self stopListening: self]; + [remoteControls release]; + [super dealloc]; +} + +- (BOOL) instantiateAndAddRemoteControlDeviceWithClass: (Class) clazz { + RemoteControl* remoteControl = [[clazz alloc] initWithDelegate: delegate]; + if (remoteControl) { + [remoteControls addObject: remoteControl]; + [remoteControl addObserver: self forKeyPath:@"listeningToRemote" options:NSKeyValueObservingOptionNew context:nil]; + return YES; + } + return NO; +} + +- (unsigned int) count { + return [remoteControls count]; +} + +- (void) reset { + [self willChangeValueForKey:@"listeningToRemote"]; + [self didChangeValueForKey:@"listeningToRemote"]; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + [self reset]; +} + +- (void) setListeningToRemote: (BOOL) value { + int i; + for(i=0; i < [remoteControls count]; i++) { + [[remoteControls objectAtIndex: i] setListeningToRemote: value]; + } + if (value && value != [self isListeningToRemote]) [self performSelector:@selector(reset) withObject:nil afterDelay:0.01]; +} +- (BOOL) isListeningToRemote { + int i; + for(i=0; i < [remoteControls count]; i++) { + if ([[remoteControls objectAtIndex: i] isListeningToRemote]) { + return YES; + } + } + return NO; +} + +- (IBAction) startListening: (id) sender { + int i; + for(i=0; i < [remoteControls count]; i++) { + [[remoteControls objectAtIndex: i] startListening: sender]; + } +} +- (IBAction) stopListening: (id) sender { + int i; + for(i=0; i < [remoteControls count]; i++) { + [[remoteControls objectAtIndex: i] stopListening: sender]; + } +} + +- (BOOL) isOpenInExclusiveMode { + BOOL mode = YES; + int i; + for(i=0; i < [remoteControls count]; i++) { + mode = mode && ([[remoteControls objectAtIndex: i] isOpenInExclusiveMode]); + } + return mode; +} +- (void) setOpenInExclusiveMode: (BOOL) value { + int i; + for(i=0; i < [remoteControls count]; i++) { + [[remoteControls objectAtIndex: i] setOpenInExclusiveMode:value]; + } +} + +@end Index: RemoteControllerWrapper/GlobalKeyboardDevice.m =================================================================== --- RemoteControllerWrapper/GlobalKeyboardDevice.m (revision 0) +++ RemoteControllerWrapper/GlobalKeyboardDevice.m (revision 0) @@ -0,0 +1,241 @@ +/***************************************************************************** + * GlobalKeyboardDevice.m + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + *****************************************************************************/ + + +#import "GlobalKeyboardDevice.h" + +#define F1 122 +#define F2 120 +#define F3 99 +#define F4 118 +#define F5 96 +#define F6 97 +#define F7 98 + +/* + the following default keys are read and shall be used to change the keyboard mapping + + mac.remotecontrols.GlobalKeyboardDevice.plus_modifiers + mac.remotecontrols.GlobalKeyboardDevice.plus_keycode + mac.remotecontrols.GlobalKeyboardDevice.minus_modifiers + mac.remotecontrols.GlobalKeyboardDevice.minus_keycode + mac.remotecontrols.GlobalKeyboardDevice.play_modifiers + mac.remotecontrols.GlobalKeyboardDevice.play_keycode + mac.remotecontrols.GlobalKeyboardDevice.left_modifiers + mac.remotecontrols.GlobalKeyboardDevice.left_keycode + mac.remotecontrols.GlobalKeyboardDevice.right_modifiers + mac.remotecontrols.GlobalKeyboardDevice.right_keycode + mac.remotecontrols.GlobalKeyboardDevice.menu_modifiers + mac.remotecontrols.GlobalKeyboardDevice.menu_keycode + mac.remotecontrols.GlobalKeyboardDevice.playhold_modifiers + mac.remotecontrols.GlobalKeyboardDevice.playhold_keycode + */ + + +@implementation GlobalKeyboardDevice + +- (id) initWithDelegate: (id) _remoteControlDelegate { + if (self = [super initWithDelegate: _remoteControlDelegate]) { + hotKeyRemoteEventMapping = [[NSMutableDictionary alloc] init]; + + unsigned int modifiers = cmdKey + shiftKey /*+ optionKey*/ + controlKey; + + [self mapRemoteButton:kRemoteButtonPlus defaultKeycode:F1 defaultModifiers:modifiers]; + [self mapRemoteButton:kRemoteButtonMinus defaultKeycode:F2 defaultModifiers:modifiers]; + [self mapRemoteButton:kRemoteButtonPlay defaultKeycode:F3 defaultModifiers:modifiers]; + [self mapRemoteButton:kRemoteButtonLeft defaultKeycode:F4 defaultModifiers:modifiers]; + [self mapRemoteButton:kRemoteButtonRight defaultKeycode:F5 defaultModifiers:modifiers]; + [self mapRemoteButton:kRemoteButtonMenu defaultKeycode:F6 defaultModifiers:modifiers]; + [self mapRemoteButton:kRemoteButtonPlay_Hold defaultKeycode:F7 defaultModifiers:modifiers]; + } + return self; +} + +- (void) dealloc { + [hotKeyRemoteEventMapping release]; + [super dealloc]; +} + +- (void) mapRemoteButton: (RemoteControlEventIdentifier) remoteButtonIdentifier defaultKeycode: (unsigned int) defaultKeycode defaultModifiers: (unsigned int) defaultModifiers { + NSString* defaultsKey; + + switch(remoteButtonIdentifier) { + case kRemoteButtonPlus: + defaultsKey = @"plus"; + break; + case kRemoteButtonMinus: + defaultsKey = @"minus"; + break; + case kRemoteButtonMenu: + defaultsKey = @"menu"; + break; + case kRemoteButtonPlay: + defaultsKey = @"play"; + break; + case kRemoteButtonRight: + defaultsKey = @"right"; + break; + case kRemoteButtonLeft: + defaultsKey = @"left"; + break; + case kRemoteButtonPlay_Hold: + defaultsKey = @"playhold"; + break; + default: + NSLog(@"Unknown global keyboard defaults key for remote button identifier %d", remoteButtonIdentifier); + } + + NSNumber* modifiersCfg = [[NSUserDefaults standardUserDefaults] objectForKey: [NSString stringWithFormat: @"mac.remotecontrols.GlobalKeyboardDevice.%@_modifiers", defaultsKey]]; + NSNumber* keycodeCfg = [[NSUserDefaults standardUserDefaults] objectForKey: [NSString stringWithFormat: @"mac.remotecontrols.GlobalKeyboardDevice.%@_keycode", defaultsKey]]; + + unsigned int modifiers = defaultModifiers; + if (modifiersCfg) modifiers = [modifiersCfg unsignedIntValue]; + + unsigned int keycode = defaultKeycode; + if (keycodeCfg) keycode = [keycodeCfg unsignedIntValue]; + + [self registerHotKeyCode: keycode modifiers: modifiers remoteEventIdentifier: remoteButtonIdentifier]; +} + +- (void) setListeningToRemote: (BOOL) value { + if (value == [self isListeningToRemote]) return; + if (value) { + [self startListening: self]; + } else { + [self stopListening: self]; + } +} +- (BOOL) isListeningToRemote { + return (eventHandlerRef!=NULL); +} + +- (IBAction) startListening: (id) sender { + + if (eventHandlerRef) return; + + EventTypeSpec eventSpec[2] = { + { kEventClassKeyboard, kEventHotKeyPressed }, + { kEventClassKeyboard, kEventHotKeyReleased } + }; + + InstallEventHandler( GetEventDispatcherTarget(), + (EventHandlerProcPtr)hotKeyEventHandler, + 2, eventSpec, self, &eventHandlerRef); +} +- (IBAction) stopListening: (id) sender { + RemoveEventHandler(eventHandlerRef); + eventHandlerRef = NULL; +} + +- (BOOL) sendsEventForButtonIdentifier: (RemoteControlEventIdentifier) identifier { + NSEnumerator* values = [hotKeyRemoteEventMapping objectEnumerator]; + NSNumber* remoteIdentifier; + while( (remoteIdentifier = [values nextObject]) ) { + if ([remoteIdentifier unsignedIntValue] == identifier) return YES; + } + return NO; +} + ++ (const char*) remoteControlDeviceName { + return "Keyboard"; +} + +- (BOOL)registerHotKeyCode: (unsigned int) keycode modifiers: (unsigned int) modifiers remoteEventIdentifier: (RemoteControlEventIdentifier) identifier { + OSStatus err; + EventHotKeyID hotKeyID; + EventHotKeyRef carbonHotKey; + + hotKeyID.signature = 'PTHk'; + hotKeyID.id = (long)keycode; + + err = RegisterEventHotKey(keycode, modifiers, hotKeyID, GetEventDispatcherTarget(), nil, &carbonHotKey ); + + if( err ) + return NO; + + [hotKeyRemoteEventMapping setObject: [NSNumber numberWithInt:identifier] forKey: [NSNumber numberWithUnsignedInt: hotKeyID.id]]; + + return YES; +} +/* +- (void)unregisterHotKey: (PTHotKey*)hotKey +{ + OSStatus err; + EventHotKeyRef carbonHotKey; + NSValue* key; + + if( [[self allHotKeys] containsObject: hotKey] == NO ) + return; + + carbonHotKey = [self _carbonHotKeyForHotKey: hotKey]; + NSAssert( carbonHotKey != nil, @"" ); + + err = UnregisterEventHotKey( carbonHotKey ); + //Watch as we ignore 'err': + + key = [NSValue valueWithPointer: carbonHotKey]; + [mHotKeys removeObjectForKey: key]; + + [self _updateEventHandler]; + + //See that? Completely ignored +} +*/ + +- (RemoteControlEventIdentifier) remoteControlEventIdentifierForID: (unsigned int) id { + NSNumber* remoteEventIdentifier = [hotKeyRemoteEventMapping objectForKey:[NSNumber numberWithUnsignedInt: id]]; + return [remoteEventIdentifier unsignedIntValue]; +} + +- (void) sendRemoteButtonEvent: (RemoteControlEventIdentifier) event pressedDown: (BOOL) pressedDown { + [delegate sendRemoteButtonEvent: event pressedDown: pressedDown remoteControl:self]; +} + +static RemoteControlEventIdentifier lastEvent; + +static OSStatus hotKeyEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEvent, void* userData ) +{ + GlobalKeyboardDevice* keyboardDevice = (GlobalKeyboardDevice*) userData; + EventHotKeyID hkCom; + GetEventParameter(inEvent,kEventParamDirectObject,typeEventHotKeyID,NULL,sizeof(hkCom),NULL,&hkCom); + + RemoteControlEventIdentifier identifier = [keyboardDevice remoteControlEventIdentifierForID:hkCom.id]; + if (identifier == 0) return noErr; + + BOOL pressedDown = YES; + if (identifier != lastEvent) { + lastEvent = identifier; + } else { + lastEvent = 0; + pressedDown = NO; + } + [keyboardDevice sendRemoteButtonEvent: identifier pressedDown: pressedDown]; + + return noErr; +} + +@end Index: RemoteControllerWrapper/HIDRemoteControlDevice.m =================================================================== --- RemoteControllerWrapper/HIDRemoteControlDevice.m (revision 0) +++ RemoteControllerWrapper/HIDRemoteControlDevice.m (revision 0) @@ -0,0 +1,515 @@ +/***************************************************************************** + * HIDRemoteControlDevice.m + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + *****************************************************************************/ + +#import "HIDRemoteControlDevice.h" + +#import +#import +#import +#import +#import +#import + +@interface HIDRemoteControlDevice (PrivateMethods) +- (NSDictionary*) cookieToButtonMapping; +- (IOHIDQueueInterface**) queue; +- (IOHIDDeviceInterface**) hidDeviceInterface; +- (void) handleEventWithCookieString: (NSString*) cookieString sumOfValues: (SInt32) sumOfValues; +- (void) removeNotifcationObserver; +- (void) remoteControlAvailable:(NSNotification *)notification; + +@end + +@interface HIDRemoteControlDevice (IOKitMethods) ++ (io_object_t) findRemoteDevice; +- (IOHIDDeviceInterface**) createInterfaceForDevice: (io_object_t) hidDevice; +- (BOOL) initializeCookies; +- (BOOL) openDevice; +@end + +@implementation HIDRemoteControlDevice + ++ (const char*) remoteControlDeviceName { + return ""; +} + ++ (BOOL) isRemoteAvailable { + io_object_t hidDevice = [self findRemoteDevice]; + if (hidDevice != 0) { + IOObjectRelease(hidDevice); + return YES; + } else { + return NO; + } +} + +- (id) initWithDelegate: (id) _remoteControlDelegate { + if ([[self class] isRemoteAvailable] == NO) return nil; + + if ( self = [super initWithDelegate: _remoteControlDelegate] ) { + openInExclusiveMode = YES; + queue = NULL; + hidDeviceInterface = NULL; + cookieToButtonMapping = [[NSMutableDictionary alloc] init]; + + [self setCookieMappingInDictionary: cookieToButtonMapping]; + + NSEnumerator* enumerator = [cookieToButtonMapping objectEnumerator]; + NSNumber* identifier; + supportedButtonEvents = 0; + while(identifier = [enumerator nextObject]) { + supportedButtonEvents |= [identifier intValue]; + } + + fixSecureEventInputBug = [[NSUserDefaults standardUserDefaults] boolForKey: @"remoteControlWrapperFixSecureEventInputBug"]; + } + + return self; +} + +- (void) dealloc { + [self removeNotifcationObserver]; + [self stopListening:self]; + [cookieToButtonMapping release]; + [super dealloc]; +} + +- (void) sendRemoteButtonEvent: (RemoteControlEventIdentifier) event pressedDown: (BOOL) pressedDown { + [delegate sendRemoteButtonEvent: event pressedDown: pressedDown remoteControl:self]; +} + +- (void) setCookieMappingInDictionary: (NSMutableDictionary*) cookieToButtonMapping { +} +- (int) remoteIdSwitchCookie { + return 0; +} + +- (BOOL) sendsEventForButtonIdentifier: (RemoteControlEventIdentifier) identifier { + return (supportedButtonEvents & identifier) == identifier; +} + +- (BOOL) isListeningToRemote { + return (hidDeviceInterface != NULL && allCookies != NULL && queue != NULL); +} + +- (void) setListeningToRemote: (BOOL) value { + if (value == NO) { + [self stopListening:self]; + } else { + [self startListening:self]; + } +} + +- (BOOL) isOpenInExclusiveMode { + return openInExclusiveMode; +} +- (void) setOpenInExclusiveMode: (BOOL) value { + openInExclusiveMode = value; +} + +- (BOOL) processesBacklog { + return processesBacklog; +} +- (void) setProcessesBacklog: (BOOL) value { + processesBacklog = value; +} + +- (IBAction) startListening: (id) sender { + if ([self isListeningToRemote]) return; + + // 4th July 2007 + // + // A security update in february of 2007 introduced an odd behavior. + // Whenever SecureEventInput is activated or deactivated the exclusive access + // to the remote control device is lost. This leads to very strange behavior where + // a press on the Menu button activates FrontRow while your app still gets the event. + // A great number of people have complained about this. + // + // Enabling the SecureEventInput and keeping it enabled does the trick. + // + // I'm pretty sure this is a kind of bug at Apple and I'm in contact with the responsible + // Apple Engineer. This solution is not a perfect one - I know. + // One of the side effects is that applications that listen for special global keyboard shortcuts (like Quicksilver) + // may get into problems as they no longer get the events. + // As there is no official Apple Remote API from Apple I also failed to open a technical incident on this. + // + // Note that there is a corresponding DisableSecureEventInput in the stopListening method below. + // + if ([self isOpenInExclusiveMode] && fixSecureEventInputBug) EnableSecureEventInput(); + + [self removeNotifcationObserver]; + + io_object_t hidDevice = [[self class] findRemoteDevice]; + if (hidDevice == 0) return; + + if ([self createInterfaceForDevice:hidDevice] == NULL) { + goto error; + } + + if ([self initializeCookies]==NO) { + goto error; + } + + if ([self openDevice]==NO) { + goto error; + } + // be KVO friendly + [self willChangeValueForKey:@"listeningToRemote"]; + [self didChangeValueForKey:@"listeningToRemote"]; + goto cleanup; + +error: + [self stopListening:self]; + DisableSecureEventInput(); + +cleanup: + IOObjectRelease(hidDevice); +} + +- (IBAction) stopListening: (id) sender { + if ([self isListeningToRemote]==NO) return; + + BOOL sendNotification = NO; + + if (eventSource != NULL) { + CFRunLoopRemoveSource(CFRunLoopGetCurrent(), eventSource, kCFRunLoopDefaultMode); + CFRelease(eventSource); + eventSource = NULL; + } + if (queue != NULL) { + (*queue)->stop(queue); + + //dispose of queue + (*queue)->dispose(queue); + + //release the queue we allocated + (*queue)->Release(queue); + + queue = NULL; + + sendNotification = YES; + } + + if (allCookies != nil) { + [allCookies autorelease]; + allCookies = nil; + } + + if (hidDeviceInterface != NULL) { + //close the device + (*hidDeviceInterface)->close(hidDeviceInterface); + + //release the interface + (*hidDeviceInterface)->Release(hidDeviceInterface); + + hidDeviceInterface = NULL; + } + + if ([self isOpenInExclusiveMode] && fixSecureEventInputBug) DisableSecureEventInput(); + + if ([self isOpenInExclusiveMode] && sendNotification) { + [[self class] sendFinishedNotifcationForAppIdentifier: nil]; + } + // be KVO friendly + [self willChangeValueForKey:@"listeningToRemote"]; + [self didChangeValueForKey:@"listeningToRemote"]; +} + +@end + +@implementation HIDRemoteControlDevice (PrivateMethods) + +- (IOHIDQueueInterface**) queue { + return queue; +} + +- (IOHIDDeviceInterface**) hidDeviceInterface { + return hidDeviceInterface; +} + + +- (NSDictionary*) cookieToButtonMapping { + return cookieToButtonMapping; +} + +- (NSString*) validCookieSubstring: (NSString*) cookieString { + if (cookieString == nil || [cookieString length] == 0) return nil; + NSEnumerator* keyEnum = [[self cookieToButtonMapping] keyEnumerator]; + NSString* key; + while(key = [keyEnum nextObject]) { + NSRange range = [cookieString rangeOfString:key]; + if (range.location == 0) return key; + } + return nil; +} + +- (void) handleEventWithCookieString: (NSString*) cookieString sumOfValues: (SInt32) sumOfValues { + /* + if (previousRemainingCookieString) { + cookieString = [previousRemainingCookieString stringByAppendingString: cookieString]; + NSLog(@"New cookie string is %@", cookieString); + [previousRemainingCookieString release], previousRemainingCookieString=nil; + }*/ + if (cookieString == nil || [cookieString length] == 0) return; + + NSNumber* buttonId = [[self cookieToButtonMapping] objectForKey: cookieString]; + if (buttonId != nil) { + [self sendRemoteButtonEvent: [buttonId intValue] pressedDown: (sumOfValues>0)]; + } else { + // let's see if a number of events are stored in the cookie string. this does + // happen when the main thread is too busy to handle all incoming events in time. + NSString* subCookieString; + NSString* lastSubCookieString=nil; + while(subCookieString = [self validCookieSubstring: cookieString]) { + cookieString = [cookieString substringFromIndex: [subCookieString length]]; + lastSubCookieString = subCookieString; + if (processesBacklog) [self handleEventWithCookieString: subCookieString sumOfValues:sumOfValues]; + } + if (processesBacklog == NO && lastSubCookieString != nil) { + // process the last event of the backlog and assume that the button is not pressed down any longer. + // The events in the backlog do not seem to be in order and therefore (in rare cases) the last event might be + // a button pressed down event while in reality the user has released it. + // NSLog(@"processing last event of backlog"); + [self handleEventWithCookieString: lastSubCookieString sumOfValues:0]; + } + if ([cookieString length] > 0) { + NSLog(@"Unknown button for cookiestring %@", cookieString); + } + } +} + +- (void) removeNotifcationObserver { + [[NSDistributedNotificationCenter defaultCenter] removeObserver:self name:FINISHED_USING_REMOTE_CONTROL_NOTIFICATION object:nil]; +} + +- (void) remoteControlAvailable:(NSNotification *)notification { + [self removeNotifcationObserver]; + [self startListening: self]; +} + +@end + +/* Callback method for the device queue +Will be called for any event of any type (cookie) to which we subscribe +*/ +static void QueueCallbackFunction(void* target, IOReturn result, void* refcon, void* sender) { + if (target < 0) { + NSLog(@"QueueCallbackFunction called with invalid target!"); + return; + } + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + + HIDRemoteControlDevice* remote = (HIDRemoteControlDevice*)target; + IOHIDEventStruct event; + AbsoluteTime zeroTime = {0,0}; + NSMutableString* cookieString = [NSMutableString string]; + SInt32 sumOfValues = 0; + while (result == kIOReturnSuccess) + { + result = (*[remote queue])->getNextEvent([remote queue], &event, zeroTime, 0); + if ( result != kIOReturnSuccess ) + continue; + + //printf("%d %d %d\n", event.elementCookie, event.value, event.longValue); + + if (((int)event.elementCookie)!=5) { + sumOfValues+=event.value; + [cookieString appendString:[NSString stringWithFormat:@"%d_", event.elementCookie]]; + } + } + [remote handleEventWithCookieString: cookieString sumOfValues: sumOfValues]; + + [pool release]; +} + +@implementation HIDRemoteControlDevice (IOKitMethods) + +- (IOHIDDeviceInterface**) createInterfaceForDevice: (io_object_t) hidDevice { + io_name_t className; + IOCFPlugInInterface** plugInInterface = NULL; + HRESULT plugInResult = S_OK; + SInt32 score = 0; + IOReturn ioReturnValue = kIOReturnSuccess; + + hidDeviceInterface = NULL; + + ioReturnValue = IOObjectGetClass(hidDevice, className); + + if (ioReturnValue != kIOReturnSuccess) { + NSLog(@"Error: Failed to get class name."); + return NULL; + } + + ioReturnValue = IOCreatePlugInInterfaceForService(hidDevice, + kIOHIDDeviceUserClientTypeID, + kIOCFPlugInInterfaceID, + &plugInInterface, + &score); + if (ioReturnValue == kIOReturnSuccess) + { + //Call a method of the intermediate plug-in to create the device interface + plugInResult = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID), (LPVOID) &hidDeviceInterface); + + if (plugInResult != S_OK) { + NSLog(@"Error: Couldn't create HID class device interface"); + } + // Release + if (plugInInterface) (*plugInInterface)->Release(plugInInterface); + } + return hidDeviceInterface; +} + +- (BOOL) initializeCookies { + IOHIDDeviceInterface122** handle = (IOHIDDeviceInterface122**)hidDeviceInterface; + IOHIDElementCookie cookie; + long usage; + long usagePage; + id object; + NSArray* elements = nil; + NSDictionary* element; + IOReturn success; + + if (!handle || !(*handle)) return NO; + + // Copy all elements, since we're grabbing most of the elements + // for this device anyway, and thus, it's faster to iterate them + // ourselves. When grabbing only one or two elements, a matching + // dictionary should be passed in here instead of NULL. + success = (*handle)->copyMatchingElements(handle, NULL, (CFArrayRef*)&elements); + + if (success == kIOReturnSuccess) { + + [elements autorelease]; + /* + cookies = calloc(NUMBER_OF_APPLE_REMOTE_ACTIONS, sizeof(IOHIDElementCookie)); + memset(cookies, 0, sizeof(IOHIDElementCookie) * NUMBER_OF_APPLE_REMOTE_ACTIONS); + */ + allCookies = [[NSMutableArray alloc] init]; + + NSEnumerator *elementsEnumerator = [elements objectEnumerator]; + + while (element = [elementsEnumerator nextObject]) { + //Get cookie + object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementCookieKey) ]; + if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue; + if (object == 0 || CFGetTypeID(object) != CFNumberGetTypeID()) continue; + cookie = (IOHIDElementCookie) [object longValue]; + + //Get usage + object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementUsageKey) ]; + if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue; + usage = [object longValue]; + + //Get usage page + object = [element valueForKey: (NSString*)CFSTR(kIOHIDElementUsagePageKey) ]; + if (object == nil || ![object isKindOfClass:[NSNumber class]]) continue; + usagePage = [object longValue]; + + [allCookies addObject: [NSNumber numberWithInt:(int)cookie]]; + } + } else { + return NO; + } + + return YES; +} + +- (BOOL) openDevice { + HRESULT result; + + IOHIDOptionsType openMode = kIOHIDOptionsTypeNone; + if ([self isOpenInExclusiveMode]) openMode = kIOHIDOptionsTypeSeizeDevice; + IOReturn ioReturnValue = (*hidDeviceInterface)->open(hidDeviceInterface, openMode); + + if (ioReturnValue == KERN_SUCCESS) { + queue = (*hidDeviceInterface)->allocQueue(hidDeviceInterface); + if (queue) { + result = (*queue)->create(queue, 0, 12); //depth: maximum number of elements in queue before oldest elements in queue begin to be lost. + + IOHIDElementCookie cookie; + NSEnumerator *allCookiesEnumerator = [allCookies objectEnumerator]; + + while (cookie = (IOHIDElementCookie)[[allCookiesEnumerator nextObject] intValue]) { + (*queue)->addElement(queue, cookie, 0); + } + + // add callback for async events + ioReturnValue = (*queue)->createAsyncEventSource(queue, &eventSource); + if (ioReturnValue == KERN_SUCCESS) { + ioReturnValue = (*queue)->setEventCallout(queue,QueueCallbackFunction, self, NULL); + if (ioReturnValue == KERN_SUCCESS) { + CFRunLoopAddSource(CFRunLoopGetCurrent(), eventSource, kCFRunLoopDefaultMode); + + //start data delivery to queue + (*queue)->start(queue); + return YES; + } else { + NSLog(@"Error when setting event callback"); + } + } else { + NSLog(@"Error when creating async event source"); + } + } else { + NSLog(@"Error when opening device"); + } + } else if (ioReturnValue == kIOReturnExclusiveAccess) { + // the device is used exclusive by another application + + // 1. we register for the FINISHED_USING_REMOTE_CONTROL_NOTIFICATION notification + [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(remoteControlAvailable:) name:FINISHED_USING_REMOTE_CONTROL_NOTIFICATION object:nil]; + + // 2. send a distributed notification that we wanted to use the remote control + [[self class] sendRequestForRemoteControlNotification]; + } + return NO; +} + ++ (io_object_t) findRemoteDevice { + CFMutableDictionaryRef hidMatchDictionary = NULL; + IOReturn ioReturnValue = kIOReturnSuccess; + io_iterator_t hidObjectIterator = 0; + io_object_t hidDevice = 0; + + // Set up a matching dictionary to search the I/O Registry by class + // name for all HID class devices + hidMatchDictionary = IOServiceMatching([self remoteControlDeviceName]); + + // Now search I/O Registry for matching devices. + ioReturnValue = IOServiceGetMatchingServices(kIOMasterPortDefault, hidMatchDictionary, &hidObjectIterator); + + if ((ioReturnValue == kIOReturnSuccess) && (hidObjectIterator != 0)) { + hidDevice = IOIteratorNext(hidObjectIterator); + } + + // release the iterator + IOObjectRelease(hidObjectIterator); + + return hidDevice; +} + +@end + Index: XeeController.h =================================================================== --- XeeController.h (revision 28) +++ XeeController.h (working copy) @@ -9,9 +9,9 @@ @class XeeImage,XeeImageSource,XeeView,XeeDisplayWindow,XeeFullScreenWindow; @class XeeCollisionPanel,XeeRenamePanel,XeeStatusBar,XeeDestinationView; @class XeeMoveTool,XeeCropTool; +@class RemoteControl,MultiClickRemoteBehavior; - @interface XeeController:NSObject { XeeImageSource *source; @@ -39,6 +39,9 @@ CGImageRef copiedcgimage; NSMutableArray *tasks; + + RemoteControl* remoteControl; + MultiClickRemoteBehavior* remoteControlBehavior; IBOutlet NSWindow *window; IBOutlet XeeView *imageview; Index: XeeController.m =================================================================== --- XeeController.m (revision 28) +++ XeeController.m (working copy) @@ -11,6 +11,13 @@ #import "XeeMoveTool.h" #import "XeeCropTool.h" +//add for remote controller +#import "AppleRemote.h" +#import "KeyspanFrontRowControl.h" +#import "GlobalKeyboardDevice.h" +#import "RemoteControlContainer.h" +#import "MultiClickRemoteBehavior.h" + #import extern float XeeZoomLevels[]; @@ -52,6 +59,18 @@ renamepanel=nil; collisionpanel=nil; delaypanel=nil; + + //setup remote controller event handler + remoteControlBehavior = [[MultiClickRemoteBehavior alloc] init]; + [remoteControlBehavior setDelegate: self]; + //[remoteControlBehavior setSimulateHoldEvent:YES]; + [remoteControlBehavior setClickCountingEnabled:YES]; + RemoteControlContainer* container = [[RemoteControlContainer alloc] initWithDelegate: remoteControlBehavior]; + [container instantiateAndAddRemoteControlDeviceWithClass: [AppleRemote class]]; + [container instantiateAndAddRemoteControlDeviceWithClass: [KeyspanFrontRowControl class]]; + [container instantiateAndAddRemoteControlDeviceWithClass: [GlobalKeyboardDevice class]]; + [container startListening: self]; + remoteControl = container; [controllers addObject:self]; } @@ -60,6 +79,9 @@ -(void)dealloc { + [remoteControl release]; + [remoteControlBehavior release]; + [source release]; [currimage release]; @@ -146,6 +168,7 @@ { [[NSNotificationCenter defaultCenter] postNotificationName:@"XeeFrontImageDidChangeNotification" object:currimage]; [statusbar setNeedsDisplay:YES]; + [remoteControl startListening: self]; } -(void)windowDidResignMain:(NSNotification *)notification @@ -153,6 +176,7 @@ if(fullscreenwindow) return; // ignore messages when switching to and from the fullscreen window [[NSNotificationCenter defaultCenter] postNotificationName:@"XeeFrontImageDidChangeNotification" object:nil]; [statusbar setNeedsDisplay:YES]; + [remoteControl stopListening: self]; } -(void)windowDidMove:(NSNotification *)notification Index: XeeControllerRemoteActions.h =================================================================== --- XeeControllerRemoteActions.h (revision 0) +++ XeeControllerRemoteActions.h (revision 0) @@ -0,0 +1,16 @@ +// +// XeeControllerRemoteAction.h +// Xee +// +// Created by Poison on 1/16/09. +// Copyright 2009 HUST. All rights reserved. +// + +#import "XeeController.h" +#import "RemoteControl.h" + +@interface XeeController (NavigationActions) + +- (void) remoteButton: (RemoteControlEventIdentifier)buttonIdentifier pressedDown: (BOOL) pressedDown clickCount: (unsigned int)clickCount; + +@end Index: XeeControllerRemoteActions.m =================================================================== --- XeeControllerRemoteActions.m (revision 0) +++ XeeControllerRemoteActions.m (revision 0) @@ -0,0 +1,146 @@ +// +// XeeControllerRemoteAction.m +// Xee +// +// Created by Poison on 1/16/09. +// Copyright 2009 HUST. All rights reserved. +// + +#import "XeeControllerRemoteActions.h" +#import "XeeImageSource.h" + + +@implementation XeeController (RemoteActions) + +- (void) runSlideshow +{ + if(slideshowtimer) + { + [slideshowtimer invalidate]; + [slideshowtimer release]; + slideshowtimer=nil; + } + else + { + slideshowtimer=[[NSTimer scheduledTimerWithTimeInterval:1 + target:self + selector:@selector(slideshowStep:) + userInfo:nil + repeats:YES] retain]; + } +} + +- (void) setDelay: (int)delay +{ + [[NSUserDefaults standardUserDefaults] setInteger:delay forKey:@"slideshowDelay"]; +} + +- (void) addSlideshowDelay: (int)sec +{ + int delay = [[NSUserDefaults standardUserDefaults] integerForKey:@"slideshowDelay"]; + delay += sec; + if (delay < 1) { + delay = 1; + } + [self setDelay:delay]; +} + +- (void) remoteButton: (RemoteControlEventIdentifier)buttonIdentifier pressedDown: (BOOL) pressedDown clickCount: (unsigned int)clickCount { + NSString* buttonName=nil; + NSString* pressed=@""; + + if (pressedDown) pressed = @"(pressed)"; else pressed = @"(released)"; + int step = 1; + if (clickCount == 2) { + step = 5; + } + else if (clickCount == 3) { + step = 20; + } + else if (clickCount > 3) { + step = 100; + } + + switch(buttonIdentifier) { + case kRemoteButtonPlus: + buttonName = @"Volume up"; + if (!pressedDown) { + [self addSlideshowDelay:step]; + } + break; + case kRemoteButtonMinus: + buttonName = @"Volume down"; + if (!pressedDown) { + [self addSlideshowDelay:-step]; + } + break; + case kRemoteButtonMenu: + buttonName = @"Menu"; + if (!pressedDown) { + [self fullScreen:nil]; + } + break; + case kRemoteButtonPlay: + buttonName = @"Play"; + if (!pressedDown) { + [self runSlideshow]; + } + break; + case kRemoteButtonRight: + buttonName = @"Right"; + if (!pressedDown) { + [source skip:step]; + } + break; + case kRemoteButtonLeft: + buttonName = @"Left"; + if (!pressedDown) { + [source skip:-step]; + } + break; + case kRemoteButtonRight_Hold: + buttonName = @"Right holding"; + if (pressedDown) { + [source pickLastImage]; + } + break; + case kRemoteButtonLeft_Hold: + buttonName = @"Left holding"; + if (pressedDown) { + [source pickFirstImage]; + } + break; + case kRemoteButtonPlus_Hold: + buttonName = @"Volume up holding"; + if (pressedDown) { + [self addSlideshowDelay:10]; + } + break; + case kRemoteButtonMinus_Hold: + buttonName = @"Volume down holding"; + if (pressedDown) { + [self setDelay:1]; + } + break; + case kRemoteButtonPlay_Hold: + buttonName = @"Play (sleep mode)"; + break; + case kRemoteButtonMenu_Hold: + buttonName = @"Menu (long)"; + break; + case kRemoteControl_Switched: + buttonName = @"Remote Control Switched"; + break; + default: + NSLog(@"Unmapped event for button %d", buttonIdentifier); + break; + } + /*NSString* clickCountString = @""; + if (clickCount > 1) clickCountString = [NSString stringWithFormat: @"%d clicks", clickCount]; + NSString* feedbackString = [NSString stringWithFormat:@"%@ %@ %@", buttonName, pressed, clickCountString]; + + NSLog(@"%@", feedbackString); + if (pressedDown == NO) printf("\n");*/ +} + +@end Index: XeeCropTool.m =================================================================== --- XeeCropTool.m (revision 28) +++ XeeCropTool.m (working copy) @@ -4,7 +4,7 @@ #import "XeeGraphicsStuff.h" #import "XeeController.h" -#import +#import