1//
2//  XBMCHelper.m
3//  xbmchelper
4//
5//  Created by Stephan Diederich on 11/12/08.
6//  Copyright 2008 University Heidelberg. All rights reserved.
7//
8
9#import "XBMCHelper.h"
10#import "XBMCDebugHelpers.h"
11
12//----------------------------------------------------------------------------
13//----------------------------------------------------------------------------
14@interface XBMCHelper (private)
15
16- (NSString *)buttonNameForButtonCode:(HIDRemoteButtonCode)buttonCode;
17- (void) checkAndLaunchApp;
18
19@end
20
21@implementation XBMCHelper
22- (id) init{
23  if( (self = [super init]) ){
24    if ((remote = [HIDRemote sharedHIDRemote]))
25    {
26      [remote setDelegate:self];
27      [remote setSimulateHoldEvents:NO];
28      //for now, we're using lending of exclusive lock
29      //kHIDRemoteModeExclusiveAuto isn't working, as we're a background daemon
30      //one possibility would be to know when XBMC is running. Once we know that,
31      //we could acquire exclusive lock when it's running, and release _exclusive_
32      //access once done
33      [remote setExclusiveLockLendingEnabled:YES];
34
35      if ([HIDRemote isCandelairInstallationRequiredForRemoteMode:kHIDRemoteModeExclusive])
36      {
37        //setup failed. user needs to install CandelaIR driver
38        NSLog(@"Error! Candelair driver installation necessary. XBMCHelper won't function properly!");
39        NSLog(@"Due to an issue in the OS version you are running, an additional driver needs to be installed before XBMC(Helper) can reliably access the remote.");
40        NSLog(@"See http://www.candelair.com/download/ for details");
41        return nil;
42      }
43      else
44      {
45        if ([remote startRemoteControl:kHIDRemoteModeExclusive])
46        {
47          DLOG(@"Driver has started successfully.");
48          if ([remote activeRemoteControlCount])
49            DLOG(@"Driver has found %d remotes.", [remote activeRemoteControlCount]);
50          else
51            ELOG(@"Driver has not found any remotes it could use. Will use remotes as they become available.");
52        }
53        else
54        {
55          ELOG(@"Failed to start remote control.");
56          //setup failed, cleanup
57          [remote setDelegate:nil];
58          return nil;
59        }
60      }
61    }
62  }
63  return self;
64}
65
66//----------------------------------------------------------------------------
67- (void) dealloc{
68  [remote stopRemoteControl];
69  if( [remote delegate] == self)
70    [remote setDelegate:nil];
71}
72
73//----------------------------------------------------------------------------
74- (void) connectToServer:(NSString*) fp_server onPort:(int) f_port withMode:(eRemoteMode) f_mode withTimeout:(double) f_timeout{
75  if(mp_wrapper)
76    [self disconnect];
77  mp_wrapper = [[XBMCClientWrapper alloc] initWithMode:f_mode serverAddress:fp_server port:f_port verbose:m_verbose];
78  [mp_wrapper setUniversalModeTimeout:f_timeout];
79}
80
81//----------------------------------------------------------------------------
82- (void) disconnect{
83  mp_wrapper = nil;
84}
85
86//----------------------------------------------------------------------------
87- (void) enableVerboseMode:(bool) f_really{
88  m_verbose = f_really;
89  [mp_wrapper enableVerboseMode:f_really];
90}
91
92//----------------------------------------------------------------------------
93- (void) setApplicationPath:(NSString*) fp_app_path{
94  if (mp_app_path != fp_app_path) {
95    mp_app_path = [fp_app_path stringByStandardizingPath];
96  }
97}
98
99//----------------------------------------------------------------------------
100- (void) setApplicationHome:(NSString*) fp_home_path{
101  if (mp_home_path != fp_home_path) {
102    mp_home_path = [fp_home_path stringByStandardizingPath];
103  }
104}
105
106#pragma mark -
107#pragma mark HIDRemote delegate methods
108
109// Notification of button events
110- (void)hidRemote:(HIDRemote *)hidRemote eventWithButton:(HIDRemoteButtonCode)buttonCode
111        isPressed:(BOOL)isPressed fromHardwareWithAttributes:(NSMutableDictionary *)attributes
112{
113  if(m_verbose){
114    NSLog(@"Received button '%@' %@ event", [self buttonNameForButtonCode:buttonCode], (isPressed)?@"press":@"release");
115  }
116  switch(buttonCode)
117  {
118    case kHIDRemoteButtonCodeUp:
119      if(isPressed)
120        [mp_wrapper handleEvent:ATV_BUTTON_UP];
121      else
122        [mp_wrapper handleEvent:ATV_BUTTON_UP_RELEASE];
123      break;
124    case kHIDRemoteButtonCodeDown:
125      if(isPressed)
126        [mp_wrapper handleEvent:ATV_BUTTON_DOWN];
127      else
128        [mp_wrapper handleEvent:ATV_BUTTON_DOWN_RELEASE];
129      break;
130    case kHIDRemoteButtonCodeLeft:
131      if(isPressed)
132        [mp_wrapper handleEvent:ATV_BUTTON_LEFT];
133      else
134        [mp_wrapper handleEvent:ATV_BUTTON_LEFT_RELEASE];
135      break;
136    case kHIDRemoteButtonCodeRight:
137      if(isPressed)
138        [mp_wrapper handleEvent:ATV_BUTTON_RIGHT];
139      else
140        [mp_wrapper handleEvent:ATV_BUTTON_RIGHT_RELEASE];
141      break;
142    case kHIDRemoteButtonCodeCenter:
143      if(isPressed) [mp_wrapper handleEvent:ATV_BUTTON_CENTER];
144      break;
145    case kHIDRemoteButtonCodeMenu:
146      if(isPressed){
147        [self checkAndLaunchApp]; //launch mp_app_path if it's not running
148        [mp_wrapper handleEvent:ATV_BUTTON_MENU];
149      }
150      break;
151    case kHIDRemoteButtonCodePlay: //aluminium remote
152      if(isPressed) {
153        [mp_wrapper handleEvent:ATV_BUTTON_PLAY];
154      }
155      break;
156//    case kHIDRemoteButtonCodeUpHold:
157//      //TODO
158//      break;
159//    case kHIDRemoteButtonCodeDownHold:
160//      //TODO
161      break;
162    case kHIDRemoteButtonCodeLeftHold:
163      if(isPressed)
164        [mp_wrapper handleEvent:ATV_BUTTON_LEFT_H];
165      else
166        [mp_wrapper handleEvent:ATV_BUTTON_LEFT_H_RELEASE];
167      break;
168    case kHIDRemoteButtonCodeRightHold:
169      if(isPressed)
170        [mp_wrapper handleEvent:ATV_BUTTON_RIGHT_H];
171      else
172        [mp_wrapper handleEvent:ATV_BUTTON_RIGHT_H_RELEASE];
173      break;
174    case kHIDRemoteButtonCodeCenterHold:
175      if(isPressed) [mp_wrapper handleEvent:ATV_BUTTON_CENTER_H];
176      break;
177    case kHIDRemoteButtonCodeMenuHold:
178      if(isPressed) {
179        [self checkAndLaunchApp]; //launch mp_app_path if it's not running
180        [mp_wrapper handleEvent:ATV_BUTTON_MENU_H];
181      }
182      break;
183    case kHIDRemoteButtonCodePlayHold: //aluminium remote
184      if(isPressed) {
185        [mp_wrapper handleEvent:ATV_BUTTON_PLAY_H];
186      }
187      break;
188    default:
189      NSLog(@"Oha, remote button not recognized %i pressed/released %i", buttonCode, isPressed);
190  }
191}
192
193
194// Notification of ID changes
195- (void)hidRemote:(HIDRemote *)hidRemote remoteIDChangedOldID:(SInt32)old
196            newID:(SInt32)newID forHardwareWithAttributes:(NSMutableDictionary *)attributes
197{
198  if(m_verbose)
199    NSLog(@"Change of remote ID from %d to %d", old, newID);
200  [mp_wrapper switchRemote: newID];
201
202}
203
204#pragma mark -
205#pragma mark Helper methods
206
207- (NSString *)buttonNameForButtonCode:(HIDRemoteButtonCode)buttonCode
208{
209	switch (buttonCode)
210	{
211		case kHIDRemoteButtonCodePlus:
212			return (@"Plus");
213      break;
214		case kHIDRemoteButtonCodeMinus:
215			return (@"Minus");
216      break;
217		case kHIDRemoteButtonCodeLeft:
218			return (@"Left");
219      break;
220		case kHIDRemoteButtonCodeRight:
221			return (@"Right");
222      break;
223		case kHIDRemoteButtonCodePlayPause:
224			return (@"Play/Pause");
225      break;
226		case kHIDRemoteButtonCodeMenu:
227			return (@"Menu");
228      break;
229		case kHIDRemoteButtonCodePlusHold:
230			return (@"Plus (hold)");
231      break;
232		case kHIDRemoteButtonCodeMinusHold:
233			return (@"Minus (hold)");
234      break;
235		case kHIDRemoteButtonCodeLeftHold:
236			return (@"Left (hold)");
237      break;
238		case kHIDRemoteButtonCodeRightHold:
239			return (@"Right (hold)");
240      break;
241		case kHIDRemoteButtonCodePlayPauseHold:
242			return (@"Play/Pause (hold)");
243      break;
244		case kHIDRemoteButtonCodeMenuHold:
245			return (@"Menu (hold)");
246      break;
247        case kHIDRemoteButtonCodePlay:
248            return (@"Play");
249      break;
250        default:
251      break;
252	}
253	return ([NSString stringWithFormat:@"Button %x", (int)buttonCode]);
254}
255
256//----------------------------------------------------------------------------
257- (void) checkAndLaunchApp
258{
259  if(!mp_app_path || ![mp_app_path length]){
260    ELOG(@"No executable set. Nothing to launch");
261    return;
262  }
263  NSFileManager *fileManager = [NSFileManager defaultManager];
264  if([fileManager fileExistsAtPath:mp_app_path]){
265    if(mp_home_path && [mp_home_path length])
266      setenv("KODI_HOME", [mp_home_path UTF8String], 1);
267    //launch or activate xbmc
268    if(![[NSWorkspace sharedWorkspace] launchApplication:mp_app_path])
269      ELOG(@"Error launching %@", mp_app_path);
270  } else
271    ELOG(@"Path does not exist: %@. Cannot launch executable", mp_app_path);
272}
273
274
275#pragma mark -
276#pragma mark Other (unused) HIDRemoteDelegate methods
277//- (BOOL)hidRemote:(HIDRemote *)aHidRemote
278//lendExclusiveLockToApplicationWithInfo:(NSDictionary *)applicationInfo
279//{
280//	NSLog(@"Lending exclusive lock to %@ (pid %@)", [applicationInfo objectForKey:(id)kCFBundleIdentifierKey], [applicationInfo objectForKey:kHIDRemoteDNStatusPIDKey]);
281//	return (YES);
282//}
283//
284//- (void)hidRemote:(HIDRemote *)aHidRemote
285//exclusiveLockReleasedByApplicationWithInfo:(NSDictionary *)applicationInfo
286//{
287//  NSLog(@"Exclusive lock released by %@ (pid %@)", [applicationInfo objectForKey:(id)kCFBundleIdentifierKey], [applicationInfo objectForKey:kHIDRemoteDNStatusPIDKey]);
288//	[aHidRemote startRemoteControl:kHIDRemoteModeExclusive];
289//}
290//
291//- (BOOL)hidRemote:(HIDRemote *)aHidRemote
292//shouldRetryExclusiveLockWithInfo:(NSDictionary *)applicationInfo
293//{
294//  NSLog(@"%@ (pid %@) says I should retry to acquire exclusive locks", [applicationInfo objectForKey:(id)kCFBundleIdentifierKey], [applicationInfo objectForKey:kHIDRemoteDNStatusPIDKey]);
295//	return (YES);
296//}
297//
298//
299//// Notification about hardware additions/removals
300//- (void)hidRemote:(HIDRemote *)aHidRemote foundNewHardwareWithAttributes:(NSMutableDictionary *)attributes
301//{
302//	NSLog(@"Found hardware: %@ by %@ (Transport: %@)", [attributes objectForKey:kHIDRemoteProduct], [attributes objectForKey:kHIDRemoteManufacturer], [attributes objectForKey:kHIDRemoteTransport]);
303//}
304//
305//- (void)hidRemote:(HIDRemote *)aHidRemote failedNewHardwareWithError:(NSError *)error
306//{
307//	NSLog(@"Initialization of hardware failed with error %@ (%@)", [error localizedDescription], [[error userInfo] objectForKey:@"InternalErrorCode"]);
308//}
309//
310//- (void)hidRemote:(HIDRemote *)aHidRemote releasedHardwareWithAttributes:(NSMutableDictionary *)attributes
311//{
312//	NSLog(@"Released hardware: %@ by %@ (Transport: %@)", [attributes objectForKey:kHIDRemoteProduct], [attributes objectForKey:kHIDRemoteManufacturer], [attributes objectForKey:kHIDRemoteTransport]);
313//}
314
315@end
316