diff options
Diffstat (limited to 'apple_remote')
-rw-r--r-- | apple_remote/Library_AppleRemote.mk | 31 | ||||
-rw-r--r-- | apple_remote/Makefile | 7 | ||||
-rw-r--r-- | apple_remote/Module_apple_remote.mk | 21 | ||||
-rw-r--r-- | apple_remote/README | 12 | ||||
-rw-r--r-- | apple_remote/source/AppleRemote.h | 44 | ||||
-rw-r--r-- | apple_remote/source/AppleRemote.m | 87 | ||||
-rw-r--r-- | apple_remote/source/GlobalKeyboardDevice.h | 56 | ||||
-rw-r--r-- | apple_remote/source/GlobalKeyboardDevice.m | 256 | ||||
-rw-r--r-- | apple_remote/source/HIDRemoteControlDevice.h | 69 | ||||
-rw-r--r-- | apple_remote/source/HIDRemoteControlDevice.m | 538 | ||||
-rw-r--r-- | apple_remote/source/KeyspanFrontRowControl.h | 45 | ||||
-rw-r--r-- | apple_remote/source/KeyspanFrontRowControl.m | 100 | ||||
-rw-r--r-- | apple_remote/source/MultiClickRemoteBehavior.h | 95 | ||||
-rw-r--r-- | apple_remote/source/MultiClickRemoteBehavior.m | 216 | ||||
-rw-r--r-- | apple_remote/source/RemoteControl.m | 154 | ||||
-rw-r--r-- | apple_remote/source/RemoteControlContainer.h | 43 | ||||
-rw-r--r-- | apple_remote/source/RemoteControlContainer.m | 141 | ||||
-rw-r--r-- | apple_remote/source/RemoteMainController.m | 177 |
18 files changed, 2092 insertions, 0 deletions
diff --git a/apple_remote/Library_AppleRemote.mk b/apple_remote/Library_AppleRemote.mk new file mode 100644 index 000000000..cf485533c --- /dev/null +++ b/apple_remote/Library_AppleRemote.mk @@ -0,0 +1,31 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# + +$(eval $(call gb_Library_Library,AppleRemote)) + +$(eval $(call gb_Library_use_system_darwin_frameworks,AppleRemote,\ + Cocoa \ + Carbon \ + IOKit \ +)) + +$(eval $(call gb_Library_add_objcobjects,AppleRemote,\ + apple_remote/source/KeyspanFrontRowControl \ + apple_remote/source/AppleRemote \ + apple_remote/source/RemoteControl \ + apple_remote/source/RemoteControlContainer \ + apple_remote/source/GlobalKeyboardDevice \ + apple_remote/source/HIDRemoteControlDevice \ + apple_remote/source/MultiClickRemoteBehavior \ + apple_remote/source/RemoteMainController \ +)) + +# vim: set noet sw=4 ts=4: diff --git a/apple_remote/Makefile b/apple_remote/Makefile new file mode 100644 index 000000000..ccb1c85a0 --- /dev/null +++ b/apple_remote/Makefile @@ -0,0 +1,7 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- + +module_directory:=$(dir $(realpath $(firstword $(MAKEFILE_LIST)))) + +include $(module_directory)/../solenv/gbuild/partial_build.mk + +# vim: set noet sw=4 ts=4: diff --git a/apple_remote/Module_apple_remote.mk b/apple_remote/Module_apple_remote.mk new file mode 100644 index 000000000..235b31bdf --- /dev/null +++ b/apple_remote/Module_apple_remote.mk @@ -0,0 +1,21 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# + +$(eval $(call gb_Module_Module,apple_remote)) + +ifeq ($(OS),MACOSX) +ifneq ($(ENABLE_MACOSX_SANDBOX),TRUE) +$(eval $(call gb_Module_add_targets,apple_remote,\ + Library_AppleRemote \ +)) +endif +endif + + +# vim: set noet sw=4 ts=4: diff --git a/apple_remote/README b/apple_remote/README new file mode 100644 index 000000000..cf099f284 --- /dev/null +++ b/apple_remote/README @@ -0,0 +1,12 @@ +Library to interact with the Apple Remote Control on Mac + +This is an early version of Martin Kahr's Remote Control Wrapper +library +(http://martinkahr.com/2007/07/26/remote-control-wrapper-20/index.html +) with modifications by Eric Bachard. Unfortunately the exact extent +of (and rationale behind) the modifications done is unknown, at least +until the original upstream source version it is based on is +found. Version control of this just starts with the monolithic commit +of the appleremote01 CWS. Some technical detail can be found in the +OOo wiki: +http://wiki.openoffice.org/wiki/Mac_OS_X_Porting_-_Apple_Remote_implementation diff --git a/apple_remote/source/AppleRemote.h b/apple_remote/source/AppleRemote.h new file mode 100644 index 000000000..f80431bfa --- /dev/null +++ b/apple_remote/source/AppleRemote.h @@ -0,0 +1,44 @@ +/* -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/***************************************************************************** + * RemoteControlWrapper.h + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Code modified and adapted to OpenOffice.org + * by Eric Bachard on 11.08.2008 under the same license + * + * 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 <Cocoa/Cocoa.h> + +#import "HIDRemoteControlDevice.h" + +/* Interacts with the Apple Remote Control HID device + The class is not thread safe +*/ +@interface AppleRemote : HIDRemoteControlDevice { +} + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/apple_remote/source/AppleRemote.m b/apple_remote/source/AppleRemote.m new file mode 100644 index 000000000..fe50af4cc --- /dev/null +++ b/apple_remote/source/AppleRemote.m @@ -0,0 +1,87 @@ +/* -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/***************************************************************************** + * RemoteControlWrapper.m + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Code modified and adapted to OpenOffice.org + * by Eric Bachard on 11.08.2008 under the same license + * + * 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 <mach/mach.h> +#import <mach/mach_error.h> +#import <IOKit/IOKitLib.h> +#import <IOKit/IOCFPlugIn.h> +#import <IOKit/hid/IOHIDKeys.h> + +static const char* AppleRemoteDeviceName = "AppleIRController"; + +@implementation AppleRemote + ++ (const char*) remoteControlDeviceName { + return AppleRemoteDeviceName; +} + +- (void) setCookieMappingInDictionary: (NSMutableDictionary*) _cookieToButtonMapping { + + // TODO : avoid such magics +#ifdef DEBUG + NSLog( @"Apple Remote: setting 10.6 cookies" ); +#endif + // 10.6.x Snow Leopard + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlus] forKey:@"33_31_30_21_20_2_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMinus] forKey:@"33_32_30_21_20_2_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu] forKey:@"33_22_21_20_2_33_22_21_20_2_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay] forKey:@"33_23_21_20_2_33_23_21_20_2_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight] forKey:@"33_24_21_20_2_33_24_21_20_2_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft] forKey:@"33_25_21_20_2_33_25_21_20_2_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonRight_Hold] forKey:@"33_21_20_14_12_2_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonLeft_Hold] forKey:@"33_21_20_13_12_2_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonMenu_Hold] forKey:@"33_21_20_2_33_21_20_2_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteButtonPlay_Hold] forKey:@"37_33_21_20_2_37_33_21_20_2_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteControl_Switched] forKey:@"19_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kMetallicRemote2009ButtonPlay] forKey:@"33_21_20_8_2_33_21_20_8_2_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kMetallicRemote2009ButtonMiddlePlay] forKey:@"33_21_20_3_2_33_21_20_3_2_"]; + [_cookieToButtonMapping setObject:[NSNumber numberWithInt:kRemoteControl_Switched] forKey:@"19_"]; +} + +- (void) sendRemoteButtonEvent: (RemoteControlEventIdentifier) event pressedDown: (BOOL) pressedDown { + if (pressedDown == NO && event == kRemoteButtonMenu_Hold) { + // There is no separate 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 separate event when the button is being released. We are simulating that event here + [super sendRemoteButtonEvent:event pressedDown:NO]; + } +} + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/apple_remote/source/GlobalKeyboardDevice.h b/apple_remote/source/GlobalKeyboardDevice.h new file mode 100644 index 000000000..752b1eecf --- /dev/null +++ b/apple_remote/source/GlobalKeyboardDevice.h @@ -0,0 +1,56 @@ +/* -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/***************************************************************************** + * GlobalKeyboardDevice.h + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Code modified and adapted to OpenOffice.org + * by Eric Bachard on 11.08.2008 under the same license + * + * 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 <apple_remote/RemoteControl.h> + +#import <Carbon/Carbon.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; + + + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/apple_remote/source/GlobalKeyboardDevice.m b/apple_remote/source/GlobalKeyboardDevice.m new file mode 100644 index 000000000..cbd78f5b0 --- /dev/null +++ b/apple_remote/source/GlobalKeyboardDevice.m @@ -0,0 +1,256 @@ +/* -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/***************************************************************************** + * GlobalKeyboardDevice.m + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Code modified and adapted to OpenOffice.org + * by Eric Bachard on 11.08.2008 under the same license + * + * 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 + */ + +static OSStatus hotKeyEventHandler(EventHandlerCallRef, EventRef, void*); + +@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 = NULL; + + 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: +#ifdef DEBUG + NSLog( @"Apple Remote: Unknown global keyboard defaults key for button identifier %d", remoteButtonIdentifier); +#endif + break; + } + + 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); +} + +- (void) startListening: (id) sender { + + if (eventHandlerRef) return; + + EventTypeSpec const eventSpec[2] = { + { kEventClassKeyboard, kEventHotKeyPressed }, + { kEventClassKeyboard, kEventHotKeyReleased } + }; + + InstallEventHandler( GetEventDispatcherTarget(), + (EventHandlerProcPtr)hotKeyEventHandler, + 2, eventSpec, self, &eventHandlerRef); + (void)sender; +} + +- (void) stopListening: (id) sender { + RemoveEventHandler(eventHandlerRef); + eventHandlerRef = NULL; + (void)sender; +} + +- (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(), 0, &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 ) +{ + (void)inHandlerRef; + 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 + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/apple_remote/source/HIDRemoteControlDevice.h b/apple_remote/source/HIDRemoteControlDevice.h new file mode 100644 index 000000000..3f5687057 --- /dev/null +++ b/apple_remote/source/HIDRemoteControlDevice.h @@ -0,0 +1,69 @@ +/* -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/***************************************************************************** + * HIDRemoteControlDevice.h + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Code modified and adapted to OpenOffice.org + * by Eric Bachard on 11.08.2008 under the same license + * + * 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 <apple_remote/RemoteControl.h> + +#import <IOKit/hid/IOHIDLib.h> + +/* + Base class for HID based remote control devices + */ +@interface HIDRemoteControlDevice : RemoteControl { + IOHIDDeviceInterface** hidDeviceInterface; // see IOKit/hid/IOHIDLib.h + IOHIDQueueInterface** queue; // IOKit/hid/IOHIDLib.h + NSMutableArray* allCookies; + NSMutableDictionary* cookieToButtonMapping; + CFRunLoopSourceRef eventSource; + + BOOL fixSecureEventInputBug; + BOOL openInExclusiveMode; + BOOL processesBacklog; + + int supportedButtonEvents; +} + +// When your application needs too 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 + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/apple_remote/source/HIDRemoteControlDevice.m b/apple_remote/source/HIDRemoteControlDevice.m new file mode 100644 index 000000000..9a875d191 --- /dev/null +++ b/apple_remote/source/HIDRemoteControlDevice.m @@ -0,0 +1,538 @@ +/* -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/***************************************************************************** + * HIDRemoteControlDevice.m + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Code modified and adapted to OpenOffice.org + * by Eric Bachard on 11.08.2008 under the same license + * + * 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 <mach/mach.h> +#import <mach/mach_error.h> +#import <IOKit/IOKitLib.h> +#import <IOKit/IOCFPlugIn.h> +#import <IOKit/hid/IOHIDKeys.h> +#import <Carbon/Carbon.h> + +@interface HIDRemoteControlDevice (PrivateMethods) +- (NSDictionary*) cookieToButtonMapping; // Creates the dictionary using the magics, depending on the remote +- (IOHIDQueueInterface**) queue; +- (IOHIDDeviceInterface**) hidDeviceInterface; +- (void) handleEventWithCookieString: (NSString*) cookieString sumOfValues: (SInt32) sumOfValues; +- (void) removeNotificationObserver; +- (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 removeNotificationObserver]; + [self stopListening:self]; + [cookieToButtonMapping release]; + [super dealloc]; +} + +- (void) sendRemoteButtonEvent: (RemoteControlEventIdentifier) event pressedDown: (BOOL) pressedDown { + [delegate sendRemoteButtonEvent: event pressedDown: pressedDown remoteControl:self]; +} + +- (void) setCookieMappingInDictionary: (NSMutableDictionary*) cookieToButtonMap { + (void)cookieToButtonMap; +} + +- (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; +} + +- (void) startListening: (id) sender { + (void)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 removeNotificationObserver]; + + 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); +} + +- (void) stopListening: (id) sender { + (void)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] sendFinishedNotificationForAppIdentifier: 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( @"Apple Remote: 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) { + switch ( [buttonId intValue] ) + { + case kMetallicRemote2009ButtonPlay: + case kMetallicRemote2009ButtonMiddlePlay: + buttonId = [NSNumber numberWithInt:kRemoteButtonPlay]; + break; + default: + break; + } + [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( @"Apple Remote: Unknown button for cookiestring %@", cookieString); + } + } +} + +- (void) removeNotificationObserver { + [[NSDistributedNotificationCenter defaultCenter] removeObserver:self name:FINISHED_USING_REMOTE_CONTROL_NOTIFICATION object:nil]; +} + +- (void) remoteControlAvailable:(NSNotification *)notification { + (void)notification; + [self removeNotificationObserver]; + [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) { + (void)refcon; + (void)sender; + if ((intptr_t)target < 0) { + NSLog( @"Apple Remote: QueueCallbackFunction called with invalid target!"); + return; + } + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + + HIDRemoteControlDevice* remote = (HIDRemoteControlDevice*)target; + IOHIDEventStruct event; + AbsoluteTime const 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:@"%lld_", (long long) (intptr_t) 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( @"Apple Remote: Error: Failed to get RemoteControlDevice 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( @"Apple Remote: 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) { + + /* + 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 == NULL || 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]]; + } + CFRelease(elements); + elements=nil; + } 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( @"Apple Remote: Error when setting event callback"); + } + } else { + NSLog( @"Apple Remote: Error when creating async event source"); + } + } else { + NSLog( @"Apple Remote: 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 + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/apple_remote/source/KeyspanFrontRowControl.h b/apple_remote/source/KeyspanFrontRowControl.h new file mode 100644 index 000000000..c75e86b08 --- /dev/null +++ b/apple_remote/source/KeyspanFrontRowControl.h @@ -0,0 +1,45 @@ +/* -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/***************************************************************************** + * KeyspanFrontRowControl.h + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Code modified and adapted to OpenOffice.org + * by Eric Bachard on 11.08.2008 under the same License + * + * 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 <Cocoa/Cocoa.h> + +#import "HIDRemoteControlDevice.h" + +/* Interacts with the Keyspan FrontRow Remote Control HID device + The class is not thread safe +*/ +@interface KeyspanFrontRowControl : HIDRemoteControlDevice { + +} + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/apple_remote/source/KeyspanFrontRowControl.m b/apple_remote/source/KeyspanFrontRowControl.m new file mode 100644 index 000000000..a337c4978 --- /dev/null +++ b/apple_remote/source/KeyspanFrontRowControl.m @@ -0,0 +1,100 @@ +/* -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/***************************************************************************** + * KeyspanFrontRowControl.m + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Code modified and adapted to OpenOffice.org + * by Eric Bachard on 11.08.2008 under the same License + * + * 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 <mach/mach.h> +#import <mach/mach_error.h> +#import <IOKit/IOKitLib.h> +#import <IOKit/IOCFPlugIn.h> +#import <IOKit/hid/IOHIDKeys.h> + +@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 sent 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 numberRefVendor = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &idVendor); + if ( numberRefVendor ) + { + CFDictionaryAddValue(hidMatchDictionary, CFSTR(kIOHIDVendorIDKey), numberRefVendor); + CFRelease(numberRefVendor); + } + + CFNumberRef numberRefProduct = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &idProduct); + if ( numberRefProduct ) + { + CFDictionaryAddValue(hidMatchDictionary, CFSTR(kIOHIDProductIDKey), numberRefProduct); + CFRelease(numberRefProduct); + } + + // Now search I/O Registry for matching devices. + ioReturnValue = IOServiceGetMatchingServices(kIOMasterPortDefault, hidMatchDictionary, &hidObjectIterator); + + if ((ioReturnValue == kIOReturnSuccess) && (hidObjectIterator != 0)) { + hidDevice = IOIteratorNext(hidObjectIterator); + } + + // release the iterator + if ( hidObjectIterator ) + IOObjectRelease(hidObjectIterator); + + return hidDevice; + +} + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/apple_remote/source/MultiClickRemoteBehavior.h b/apple_remote/source/MultiClickRemoteBehavior.h new file mode 100644 index 000000000..6a59c1f61 --- /dev/null +++ b/apple_remote/source/MultiClickRemoteBehavior.h @@ -0,0 +1,95 @@ +/* -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/***************************************************************************** + * MultiClickRemoteBehavior.h + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Code modified and adapted to OpenOffice.org + * by Eric Bachard on 11.08.2008 under the same License + * + * 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 <apple_remote/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 separate 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 + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/apple_remote/source/MultiClickRemoteBehavior.m b/apple_remote/source/MultiClickRemoteBehavior.m new file mode 100644 index 000000000..f69f13a98 --- /dev/null +++ b/apple_remote/source/MultiClickRemoteBehavior.m @@ -0,0 +1,216 @@ +/* -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/***************************************************************************** + * MultiClickRemoteBehavior.m + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Code modified and adapted to OpenOffice.org + * by Eric Bachard on 11.08.2008 under the same License + * + * 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" + +static const NSTimeInterval DEFAULT_MAXIMUM_CLICK_TIME_DIFFERENCE = 0.35; +static 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; // return what ? + + 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 | kMetallicRemote2009ButtonPlay | kMetallicRemote2009ButtonMiddlePlay]; + } 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 + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/apple_remote/source/RemoteControl.m b/apple_remote/source/RemoteControl.m new file mode 100644 index 000000000..2d4021a91 --- /dev/null +++ b/apple_remote/source/RemoteControl.m @@ -0,0 +1,154 @@ +/* -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/***************************************************************************** + * RemoteControl.m + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Code modified and adapted to OpenOffice.org + * by Eric Bachard on 11.08.2008 under the same License + * + * 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 <apple_remote/RemoteControl.h> + +// notification 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_NOTIFICATION = @"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]; +#ifdef DEBUG + NSLog( @"Apple RemoteControl initWithDelegate ok"); +#endif + } + return self; +} + +- (void) dealloc { + [delegate release]; + [super dealloc]; +} + +- (void) setListeningToRemote: (BOOL) value { +#ifdef DEBUG + NSLog( @"Apple RemoteControl setListeningToRemote ok"); +#endif + (void)value; +} +- (BOOL) isListeningToRemote { + return NO; +} + +- (void) startListening: (id) sender { +#ifdef DEBUG + NSLog( @"Apple RemoteControl startListening ok"); +#endif + (void)sender; +} +- (void) stopListening: (id) sender { +#ifdef DEBUG + NSLog( @"Apple RemoteControl stopListening ok"); +#endif + (void)sender; +} + +- (BOOL) isOpenInExclusiveMode { + return YES; +} +- (void) setOpenInExclusiveMode: (BOOL) value { + (void)value; +} + +- (BOOL) sendsEventForButtonIdentifier: (RemoteControlEventIdentifier) identifier { +#ifdef DEBUG + NSLog( @"Apple RemoteControl: sending event for button identifier\n"); +#endif + (void)identifier; + return YES; +} + ++ (void) sendDistributedNotification: (NSString*) notificationName targetBundleIdentifier: (NSString*) targetIdentifier +{ + NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys: [NSString stringWithCString:[self remoteControlDeviceName] encoding:NSASCIIStringEncoding], + kRemoteControlDeviceName /* key = RemoteControlDeviceName -> OK */, + [[NSBundle mainBundle] bundleIdentifier] /* value = org.openoffice.script -> OK */, + kApplicationIdentifier/* key = CFBundleIdentifier -> OK */, + targetIdentifier /*value = AppleIRController -> OK */, + kTargetApplicationIdentifier /*targetBundleIdentifier -> does not appear, since the peer is nil*/, + nil]; +#ifdef DEBUG + NSLog( @"Apple Remote: sendDistributedNotification ..."); + // Debug purpose: returns all the existing dictionary keys. + NSEnumerator* itKey = [userInfo keyEnumerator]; + NSEnumerator* itVal = [userInfo objectEnumerator]; + for(;;) { + NSString* sKey = [itKey nextObject]; + NSString* sVal = [itVal nextObject]; + if( !sKey && !sVal) + break; + if( !sKey) sKey = @"nil"; + if( !sVal) sVal = @"nil"; + NSLog( @"\tARdict[\"%@\"] = \"%@\"",sKey,sVal); + } +#endif + + [[NSDistributedNotificationCenter defaultCenter] postNotificationName:notificationName + object:nil + userInfo:userInfo + deliverImmediately:YES]; +} + ++ (void) sendFinishedNotificationForAppIdentifier: (NSString*) identifier { + [self sendDistributedNotification:FINISHED_USING_REMOTE_CONTROL_NOTIFICATION targetBundleIdentifier:identifier]; +#ifdef DEBUG + NSLog( @"Apple RemoteControl: sendFinishedNotificationForAppIdentifier ..."); +#endif +} ++ (void) sendRequestForRemoteControlNotification { + [self sendDistributedNotification:REQUEST_FOR_REMOTE_CONTROL_NOTIFICATION targetBundleIdentifier:nil]; +#ifdef DEBUG + NSLog( @"Apple RemoteControl: sendRequestForRemoteControlNotification ..."); +#endif +} + ++ (const char*) remoteControlDeviceName { + return NULL; +} + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/apple_remote/source/RemoteControlContainer.h b/apple_remote/source/RemoteControlContainer.h new file mode 100644 index 000000000..c3361f45e --- /dev/null +++ b/apple_remote/source/RemoteControlContainer.h @@ -0,0 +1,43 @@ +/* -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/***************************************************************************** + * RemoteControlContainer.h + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Code modified and adapted to OpenOffice.org + * by Eric Bachard on 11.08.2008 under the same License + * + * 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 <apple_remote/RemoteControl.h> + +@interface RemoteControlContainer : RemoteControl { + NSMutableArray* remoteControls; +} + +- (BOOL) instantiateAndAddRemoteControlDeviceWithClass: (Class) clazz; +- (unsigned int) count; + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/apple_remote/source/RemoteControlContainer.m b/apple_remote/source/RemoteControlContainer.m new file mode 100644 index 000000000..14ecb36f1 --- /dev/null +++ b/apple_remote/source/RemoteControlContainer.m @@ -0,0 +1,141 @@ +/* -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/***************************************************************************** + * RemoteControlContainer.m + * RemoteControlWrapper + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Code modified and adapted to OpenOffice.org + * by Eric Bachard on 11.08.2008 under the same License + * + * 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]; +#ifdef DEBUG + NSLog( @"Apple Remote: ControlContainer initWithDelegate ok"); + } + else { + NSLog( @"Apple Remote: RemoteControlContainer initWithDelegate failed"); +#endif + } + + return self; +} + +- (void) dealloc { + [self stopListening: self]; + [remoteControls release]; + [super dealloc]; +} + +- (BOOL) instantiateAndAddRemoteControlDeviceWithClass: (Class) clazz { + BOOL toReturn = NO; + RemoteControl* remoteControl = [[clazz alloc] initWithDelegate: delegate]; + if (remoteControl) { + [remoteControls addObject: remoteControl]; + [remoteControl addObserver: self forKeyPath:@"listeningToRemote" options:NSKeyValueObservingOptionNew context:nil]; + toReturn = YES; + } +#ifdef DEBUG + else { + NSLog( @"Apple Remote: ControlContainer instantiateAndAddRemoteControlDeviceWithClass failed"); + toReturn = NO; + } +#endif + return toReturn; +} + +- (unsigned int) count { + return [remoteControls count]; +} + +- (void) reset { + [self willChangeValueForKey:@"listeningToRemote"]; + [self didChangeValueForKey:@"listeningToRemote"]; +#ifdef DEBUG + // debug purpose + NSLog( @"Apple Remote: reset... (after listening)"); +#endif +} + +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + [self reset]; + (void)keyPath; + (void)object; + (void)change; + (void)context; +} + +- (void) setListeningToRemote: (BOOL) value { + for(NSUInteger 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 { + for(NSUInteger i=0; i < [remoteControls count]; i++) { + if ([[remoteControls objectAtIndex: i] isListeningToRemote]) { + return YES; + } + } + return NO; +} + +- (void) startListening: (id) sender { +#ifdef DEBUG + NSLog(@"Apple Remote: start listening to events... "); +#endif + for(NSUInteger i=0; i < [remoteControls count]; i++) { + [[remoteControls objectAtIndex: i] startListening: sender]; + } +} +- (void) stopListening: (id) sender { +#ifdef DEBUG + NSLog(@"Apple Remote: stopListening to events... "); +#endif + for(NSUInteger i=0; i < [remoteControls count]; i++) { + [[remoteControls objectAtIndex: i] stopListening: sender]; + } +} + +- (BOOL) isOpenInExclusiveMode { + BOOL mode = YES; + for(NSUInteger i=0; i < [remoteControls count]; i++) { + mode = mode && ([[remoteControls objectAtIndex: i] isOpenInExclusiveMode]); + } + return mode; +} +- (void) setOpenInExclusiveMode: (BOOL) value { + for(NSUInteger i=0; i < [remoteControls count]; i++) { + [[remoteControls objectAtIndex: i] setOpenInExclusiveMode:value]; + } +} + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/apple_remote/source/RemoteMainController.m b/apple_remote/source/RemoteMainController.m new file mode 100644 index 000000000..9e933a302 --- /dev/null +++ b/apple_remote/source/RemoteMainController.m @@ -0,0 +1,177 @@ +/* -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/***************************************************************************** + * RemoteMainController.m + * + * Created by Martin Kahr on 11.03.06 under a MIT-style license. + * Copyright (c) 2006 martinkahr.com. All rights reserved. + * + * Code modified and adapted to OpenOffice.org + * by Eric Bachard on 11.08.2008 under the same License + * + * 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 <apple_remote/RemoteMainController.h> +#import "AppleRemote.h" +#import "KeyspanFrontRowControl.h" +#import "GlobalKeyboardDevice.h" +#import "RemoteControlContainer.h" +#import "MultiClickRemoteBehavior.h" + + +// Sample Code 3: Multi Click Behavior and Hold Event Simulation + + +@implementation AppleRemoteMainController + +- (id) init { + self = [super init]; // because we redefined our own init instead of use the fu..nny awakeFromNib + if (self != nil) { + + // 1. instantiate the desired behavior for the remote control device + remoteControlBehavior = [[MultiClickRemoteBehavior alloc] init]; + + // 2. configure the behavior + [remoteControlBehavior setDelegate: self]; + + // 3. a Remote Control Container manages a number of devices and conforms to the RemoteControl interface + // Therefore you can enable or disable all the devices of the container with a single "startListening:" call. + RemoteControlContainer* container = [[RemoteControlContainer alloc] initWithDelegate: remoteControlBehavior]; + + if ( [container instantiateAndAddRemoteControlDeviceWithClass: [AppleRemote class]] != 0 ) { +#ifdef DEBUG + NSLog(@"[container instantiateAndAddRemoteControlDeviceWithClass: [AppleRemote class]] successful"); + } + else { + NSLog(@"[container instantiateAndAddRemoteControlDeviceWithClass: [AppleRemote class]] failed"); +#endif + } + + if ( [container instantiateAndAddRemoteControlDeviceWithClass: [GlobalKeyboardDevice class]] != 0 ) { +#ifdef DEBUG + NSLog(@"[container instantiateAndAddRemoteControlDeviceWithClass: [GlobalKeyboardDevice class]] successful"); + } + else { + NSLog(@"[container instantiateAndAddRemoteControlDeviceWithClass: [GlobalKeyboardDevice class]] failed"); +#endif + } + // to give the binding mechanism a chance to see the change of the attribute + [self setValue: container forKey: @"remoteControl"]; +#ifdef DEBUG + NSLog(@"AppleRemoteMainController init done"); +#endif + } + else + NSLog(@"AppleRemoteMainController init failed"); + return self; +} + +- (void) postTheEvent: (short int)buttonIdentifier modifierFlags:(int)modifierFlags +{ +SAL_WNODEPRECATED_DECLARATIONS_PUSH + // 'NSApplicationDefined' is deprecated: first deprecated in macOS 10.12 + [NSApp postEvent: + [NSEvent otherEventWithType:NSApplicationDefined + location:NSZeroPoint + modifierFlags:modifierFlags + timestamp: 0 + windowNumber:[[NSApp keyWindow] windowNumber] + context:nil + subtype:AppleRemoteControlEvent + data1: buttonIdentifier + data2: 0] + atStart: NO]; +SAL_WNODEPRECATED_DECLARATIONS_POP +} + + +- (void) remoteButton: (RemoteControlEventIdentifier)buttonIdentifier pressedDown: (BOOL) pressedDown clickCount: (unsigned int)clickCount +{ + (void)clickCount; +#ifdef DEBUG + NSString* pressed = @""; + NSString* buttonName = nil; +#endif + if (pressedDown) + { +#ifdef DEBUG + pressed = @"(AppleRemoteMainController: button pressed)"; + + switch(buttonIdentifier) + { + case kRemoteButtonPlus: buttonName = @"Volume up"; break; // MEDIA_COMMAND_VOLUME_UP ( see include/vcl/commandevent.hxx ) + case kRemoteButtonMinus: buttonName = @"Volume down"; break; // MEDIA_COMMAND_VOLUME_DOWN + case kRemoteButtonMenu: buttonName = @"Menu"; break; // MEDIA_COMMAND_MENU + case kRemoteButtonPlay: buttonName = @"Play"; break; // MEDIA_COMMAND_PLAY + case kRemoteButtonRight: buttonName = @"Next slide"; break; // MEDIA_COMMAND_NEXTTRACK + case kRemoteButtonLeft: buttonName = @"Left"; break; // MEDIA_COMMAND_PREVIOUSTRACK + case kRemoteButtonRight_Hold: buttonName = @"Last slide"; break; // MEDIA_COMMAND_NEXTTRACK_HOLD + case kRemoteButtonLeft_Hold: buttonName = @"First slide"; break; // MEDIA_COMMAND_PREVIOUSTRACK_HOLD + case kRemoteButtonPlus_Hold: buttonName = @"Volume up holding"; break; + case kRemoteButtonMinus_Hold: buttonName = @"Volume down holding"; break; + case kRemoteButtonPlay_Hold: buttonName = @"Play (sleep mode)"; break; // MEDIA_COMMAND_PLAY_HOLD + case kRemoteButtonMenu_Hold: buttonName = @"Menu (long)"; break; // MEDIA_COMMAND_MENU_HOLD + case kRemoteControl_Switched: buttonName = @"Remote Control Switched";break; + + default: NSLog( @"AppleRemoteMainController: Unmapped event for button %d", buttonIdentifier); break; + } +#endif + [ self postTheEvent:buttonIdentifier modifierFlags: 0 ]; + } + else // not pressed + { +#ifdef DEBUG + pressed = @"(AppleRemoteMainController: button released)"; +#endif + } + +#ifdef DEBUG + //NSLog(@"Button %@ pressed %@", buttonName, pressed); + NSString* clickCountString = @""; + if (clickCount > 1) clickCountString = [NSString stringWithFormat: @"%d clicks", clickCount]; + NSString* feedbackString = [NSString stringWithFormat:@"(Value:%4d) %@ %@ %@", buttonIdentifier, buttonName, pressed, clickCountString]; + + // print out events + NSLog(@"%@", feedbackString); + + if (pressedDown == NO) printf("\n"); + // simulate slow processing of events + // [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.5]]; +#endif +} + +- (void) dealloc { + [ remoteControl release ]; remoteControl = nil; + [ remoteControlBehavior release ]; remoteControlBehavior = nil; + [super dealloc]; +} + +// for bindings access +- (RemoteControl*) remoteControl { + return remoteControl; +} + +- (MultiClickRemoteBehavior*) remoteBehavior { + return remoteControlBehavior; +} + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |