/*
* wiiuse
*
* Written By:
* Michael Laforest < para >
* Email: < thepara (--AT--) g m a i l [--DOT--] com >
*
* Copyright 2006-2007
*
* This file is part of wiiuse.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* $Header$
*
*/
/**
* @file
* @brief Handles device I/O for Mac OS X.
*/
#ifdef __APPLE__
#import "os_mac.h"
#import "../events.h"
#import
#import
#import
#import
#import
@implementation WiiuseWiimote
#pragma mark init, dealloc
- (id) initWithPtr: (wiimote*) wm_ device:(IOBluetoothDevice *)device_ {
self = [super init];
if(self) {
wm = wm_;
device = [device_ retain];
controlChannel = nil;
interruptChannel = nil;
disconnectNotification = nil;
receivedData = [[NSMutableArray alloc] initWithCapacity: 2];
receivedDataLock = [[NSLock alloc] init];
}
return self;
}
- (void) dealloc {
wm = NULL;
[interruptChannel release];
[controlChannel release];
[device release];
[disconnectNotification unregister];
[disconnectNotification release];
[receivedData release];
[super dealloc];
}
#pragma mark connect, disconnect
- (BOOL) connectChannel: (IOBluetoothL2CAPChannel**) pChannel PSM: (BluetoothL2CAPPSM) psm {
if ([device openL2CAPChannelSync:pChannel withPSM:psm delegate:self] != kIOReturnSuccess) {
WIIUSE_ERROR("Unable to open L2CAP channel [id %i].", wm->unid);
*pChannel = nil;
return NO;
} else {
[*pChannel retain];
return YES;
}
}
- (IOReturn) connect {
if(!device) {
WIIUSE_ERROR("Missing device.");
return kIOReturnBadArgument;
}
// open channels
if(![self connectChannel:&controlChannel PSM:kBluetoothL2CAPPSMHIDControl]) {
[self disconnect];
return kIOReturnNotOpen;
} else if(![self connectChannel:&interruptChannel PSM:kBluetoothL2CAPPSMHIDInterrupt]) {
[self disconnect];
return kIOReturnNotOpen;
}
// register for device disconnection
disconnectNotification = [device registerForDisconnectNotification:self selector:@selector(disconnected:fromDevice:)];
if(!disconnectNotification) {
WIIUSE_ERROR("Unable to register disconnection handler [id %i].", wm->unid);
[self disconnect];
return kIOReturnNotOpen;
}
return kIOReturnSuccess;
}
- (void) disconnectChannel: (IOBluetoothL2CAPChannel**) pChannel {
if(!pChannel) return;
if([*pChannel closeChannel] != kIOReturnSuccess)
WIIUSE_ERROR("Unable to close channel [id %i].", wm ? wm->unid : -1);
[*pChannel release];
*pChannel = nil;
}
- (void) disconnect {
// channels
[self disconnectChannel:&interruptChannel];
[self disconnectChannel:&controlChannel];
// device
if([device closeConnection] != kIOReturnSuccess)
WIIUSE_ERROR("Unable to close the device connection [id %i].", wm ? wm->unid : -1);
[device release];
device = nil;
}
- (void) disconnected:(IOBluetoothUserNotification*) notification fromDevice:(IOBluetoothDevice*) device {
WiiuseDisconnectionMessage* message = [[WiiuseDisconnectionMessage alloc] init];
[receivedDataLock lock];
[receivedData addObject:message];
[receivedDataLock unlock];
[message release];
}
#pragma mark read, write
// <0: nothing received, else: length of data received (can be 0 in case of disconnection message)
- (int) checkForAvailableDataForBuffer: (byte*) buffer length: (NSUInteger) bufferLength {
int result = -1;
[receivedDataLock lock];
if([receivedData count]) {
// look at first item in queue
NSObject* firstMessage = [receivedData objectAtIndex:0];
result = [firstMessage applyToStruct:wm buffer: buffer length: bufferLength];
if(result >= 0)
[receivedData removeObjectAtIndex:0];
}
[receivedDataLock unlock];
return result;
}
- (void) waitForIncomingData: (NSTimeInterval) duration {
NSDate* timeoutDate = [NSDate dateWithTimeIntervalSinceNow: duration];
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (true) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // This is used for fast release of NSDate, otherwise it leaks
if(![theRL runMode:NSDefaultRunLoopMode beforeDate:timeoutDate]) {
WIIUSE_ERROR("Could not start run loop while waiting for read [id %i].", wm->unid);
break;
}
[pool drain];
[receivedDataLock lock];
NSUInteger count = [receivedData count];
[receivedDataLock unlock];
if(count) {
// received some data, stop waiting
break;
}
if([timeoutDate isLessThanOrEqualTo:[NSDate date]]) {
// timeout
break;
}
}
}
// result = length of data copied to event buffer
- (int) readBuffer:(byte *)buffer length:(NSUInteger)bufferLength {
// is there already some data to read?
int result = [self checkForAvailableDataForBuffer: buffer length: bufferLength];
if(result < 0) {
// wait a short amount of time, until data becomes available or a timeout is reached
[self waitForIncomingData:1];
// check again
result = [self checkForAvailableDataForBuffer: buffer length: bufferLength];
}
return result >= 0 ? result : 0;
}
- (int) writeReport: (byte) report_type buffer: (byte*) buffer length: (NSUInteger) length {
if(interruptChannel == nil) {
WIIUSE_ERROR("Attempted to write to nil interrupt channel [id %i].", wm->unid);
return 0;
}
byte write_buffer[MAX_PAYLOAD];
write_buffer[0] = WM_SET_DATA | WM_BT_OUTPUT;
write_buffer[1] = report_type;
memcpy(write_buffer+2, buffer, length);
IOReturn error = [interruptChannel writeSync:write_buffer length:length+2];
if (error != kIOReturnSuccess) {
WIIUSE_ERROR("Error writing to interrupt channel [id %i].", wm->unid);
WIIUSE_DEBUG("Attempting to reopen the interrupt channel [id %i].", wm->unid);
[self disconnectChannel:&interruptChannel];
[self connectChannel:&interruptChannel PSM:kBluetoothL2CAPPSMHIDInterrupt];
if(!interruptChannel) {
WIIUSE_ERROR("Error reopening the interrupt channel [id %i].", wm->unid);
[self disconnect];
} else {
WIIUSE_DEBUG("Attempting to write again to the interrupt channel [id %i].", wm->unid);
error = [interruptChannel writeSync:write_buffer length:length+2];
if (error != kIOReturnSuccess)
WIIUSE_ERROR("Unable to write again to the interrupt channel [id %i].", wm->unid);
}
}
return (error == kIOReturnSuccess) ? length : 0;
}
#pragma mark IOBluetoothL2CAPChannelDelegate
- (void) l2capChannelData:(IOBluetoothL2CAPChannel*)channel data:(void*)data_ length:(NSUInteger)length {
byte* data = (byte*) data_;
// This is done in case the control channel woke up this handler
#if WIIUSE_MAC_OS_X_VERSION_10_7_OR_ABOVE
BluetoothL2CAPPSM psm = channel.PSM;
#else
BluetoothL2CAPPSM psm = [channel getPSM];
#endif
if(!data || (psm == kBluetoothL2CAPPSMHIDControl)) {
return;
}
// copy the data into the buffer
// on Mac, we ignore the first byte
WiiuseReceivedData* newData = [[WiiuseReceivedData alloc] initWithBytes: data+1 length: length-1];
[receivedDataLock lock];
[receivedData addObject: newData];
[receivedDataLock unlock];
[newData release];
}
#if !WIIUSE_MAC_OS_X_VERSION_10_7_OR_ABOVE
// the following delegate methods were required on 10.6. They are here to get rid of 10.6 compiler warnings.
- (void)l2capChannelOpenComplete:(IOBluetoothL2CAPChannel*)l2capChannel status:(IOReturn)error {
/* no-op */
}
- (void)l2capChannelClosed:(IOBluetoothL2CAPChannel*)l2capChannel {
/* no-op */
}
- (void)l2capChannelReconfigured:(IOBluetoothL2CAPChannel*)l2capChannel {
/* no-op */
}
- (void)l2capChannelWriteComplete:(IOBluetoothL2CAPChannel*)l2capChannel refcon:(void*)refcon status:(IOReturn)error {
/* no-op */
}
- (void)l2capChannelQueueSpaceAvailable:(IOBluetoothL2CAPChannel*)l2capChannel {
/* no-op */
}
#endif
@end
#pragma mark -
#pragma mark WiiuseReceivedMessage
@implementation WiiuseReceivedData
- (id) initWithData:(NSData *)data_ {
self = [super init];
if (self) {
data = [data_ retain];
}
return self;
}
- (id) initWithBytes: (void*) bytes length: (NSUInteger) length {
NSData* data_ = [[NSData alloc] initWithBytes:bytes length:length];
id result = [self initWithData: data_];
[data_ release];
return result;
}
- (void) dealloc {
[data release];
[super dealloc];
}
- (int) applyToStruct:(wiimote *)wm buffer:(byte *)buffer length:(NSUInteger)bufferLength {
byte* bytes = (byte*) [data bytes];
NSUInteger length = [data length];
if(length > bufferLength) {
WIIUSE_WARNING("Received data was longer than event buffer. Dropping excess bytes.");
length = bufferLength;
}
// log the received data
#ifdef WITH_WIIUSE_DEBUG
{
printf("[DEBUG] (id %i) RECV: (%.2x) ", wm->unid, bytes[0]);
int x;
for (x = 1; x < length; ++x)
printf("%.2x ", bytes[x]);
printf("\n");
}
#endif
// copy to struct
memcpy(buffer, bytes, length);
return length;
}
@end
@implementation WiiuseDisconnectionMessage
- (int) applyToStruct:(wiimote *)wm buffer:(byte *)buffer length:(NSUInteger)bufferLength {
wiiuse_disconnected(wm);
return 0;
}
@end
#endif // __APPLE__