Files
UnrealEngineUWP/Engine/Source/Programs/IOS/UDKRemote/Classes/MainViewController.mm
Ben Marsh 7598af0532 Update copyright notices to 2019.
#rb none
#lockdown Nick.Penwarden

[CL 4662404 by Ben Marsh in Main branch]
2018-12-14 13:41:00 -05:00

924 lines
26 KiB
Plaintext

// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
//
// MainViewController.m
// UDKRemote
//
// Created by jadams on 7/28/10.
//
#import "MainViewController.h"
#import "UDKRemoteAppDelegate.h"
//#import <MediaPlayer/MediaPlayer.h>
#include <netdb.h> // getaddrinfo, struct addrinfo, AI_NUMERICHOST
/** Size of the ring buffer, MUST BE POWER OF TWO */
#define RING_BUFFER_NUM_ELEMENTS 1024
/** These data types must match up on the other side! */
enum EDataType
{
DT_TouchBegan=0,
DT_TouchMoved=1,
DT_TouchEnded=2,
DT_Tilt=3,
DT_Motion=4,
DT_Gyro=5,
DT_Ping=6,
};
// start all messages with this, the other side can use to help verify the data
#define MESSAGE_MAGIC_ID 0xAB
// version of messages being sent
#define MESSAGE_VERSION 1
/** A single event message to send over the network */
struct FMessage
{
unsigned char MagicTag; // verification byte
unsigned char MessageVersion; // version of the message, for compatibility going forward
unsigned short MessageID;
unsigned char DataType;
unsigned char Handle;
unsigned char DeviceOrientation;
unsigned char UIOrientation;
float Data[12]; // enough space for 4 vectors for CMMotion
};
struct FMessageQueue
{
/** The finger this queue is for */
unsigned char FinderIndex;
/** A queue of messages (0 is always a TouchBegan, queue moves up to a TouchEnd) */
FMessage Queue[64];
};
@interface MainViewController()
- (void)OnMotionTimer:(NSTimer*)Timer;
- (void)OnPingTimer:(NSTimer*)Timer;
- (void)PushData:(EDataType)DataType Data:(float*)Data DataSize:(int)DataSize UniqueTouchHandle:(unsigned char)Handle;
@end
@implementation MainViewController
@synthesize HostNameLabel;
@synthesize ResolvedNameLabel;
@synthesize HelpLabel;
@synthesize NavController;
@synthesize InfoButton;
@synthesize MotionManager;
@synthesize ReferenceAttitude;
@synthesize MotionTimer;
@synthesize PingTimer;
@synthesize ResolvedAddrString;
@synthesize ReceiveData;
@synthesize Background;
@synthesize FlipsidePopoverController;
/** Ring buffer of elements so that we can go back in time to resend events if needed */
static FMessage GRingBuffer[RING_BUFFER_NUM_ELEMENTS];
static void SocketDataCallback(CFSocketRef Socket, CFSocketCallBackType CallbackType, CFDataRef Addr, const void* Data, void* UserInfo);
/**
* Helper function to get the formatted port string
*/
-(NSString*) GetPortsString
{
NSString* OutPortsString = [[NSString alloc]initWithString:@""];
for( int i = 0; i < [AppDelegate.Ports count]; i++ )
{
OutPortsString = [NSString stringWithFormat:@"%@%@", OutPortsString, [AppDelegate.Ports objectAtIndex:i]];
if( i < [AppDelegate.Ports count]-1 )
{
OutPortsString = [NSString stringWithFormat:@"%@%@", OutPortsString, @", "];
}
}
return OutPortsString;
}
- (void)SpinUpThreads
{
// create the send socket
if (PushSocket != NULL)
{
CFSocketInvalidate(PushSocket);
CFRelease(PushSocket);
}
PushSocket = CFSocketCreate(NULL, 0, SOCK_DGRAM, 0, kCFSocketNoCallBack, NULL, NULL);
// make it so when a socket dies on device sleep, we get an error when using it instead of a SIGPIPE exception
int NoSigPipe = 1;
setsockopt(CFSocketGetNative(PushSocket), SOL_SOCKET, SO_NOSIGPIPE, &NoSigPipe, sizeof(NoSigPipe));
// create the listen socket
if (ReplySocket != NULL)
{
CFSocketInvalidate(ReplySocket);
CFRelease(ReplySocket);
}
CFSocketContext Context = { 0, (__bridge void*)self, NULL, NULL, NULL };
ReplySocket = CFSocketCreate(NULL, 0, SOCK_DGRAM, 0, kCFSocketDataCallBack, SocketDataCallback, &Context);
// bind to the port
sockaddr_in ServerAddress;
memset(&ServerAddress, 0, sizeof(ServerAddress));
ServerAddress.sin_len = sizeof(ServerAddress);
ServerAddress.sin_family = AF_INET;
// @todo: we should use port 0 and let it choose a port, then send it to the PC every second or something (using CFSocketCopyAddress to get the address that contains the port)
ServerAddress.sin_port = htons(41764);
ServerAddress.sin_addr.s_addr = htonl(INADDR_ANY);
NSData *AddressData = [NSData dataWithBytes:&ServerAddress length:sizeof(ServerAddress)];
if (CFSocketSetAddress(ReplySocket, (__bridge CFDataRef)AddressData) != kCFSocketSuccess)
{
NSLog(@"Failed to set addr");
}
// hook uip RunLoop source for our callbacks, etc
CFRunLoopRef RunLoop = CFRunLoopGetCurrent();
if (ReplySource != NULL)
{
CFRunLoopRemoveSource(RunLoop, ReplySource, kCFRunLoopCommonModes);
CFRelease(ReplySource);
}
ReplySource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, ReplySocket, 0);
CFRunLoopAddSource(RunLoop, ReplySource, kCFRunLoopCommonModes);
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]))
{
// initialization
SocketAddrData[0] = SocketAddrData[1] = SocketAddrData[2] = SocketAddrData[3] = SocketAddrData[4] = NULL;
AppDelegate = ((UDKRemoteAppDelegate*)[UIApplication sharedApplication].delegate);
// make sure arrays are NULLed out
bzero(AllTouches, sizeof(AllTouches));
// load the images and make views
for (int Index = 0; Index < ARRAY_COUNT(TouchImageViews); Index++)
{
// load the image
UIImage* Image = [UIImage imageNamed:[NSString stringWithFormat:@"Finger%d.png", Index]];
TouchImageViews[Index] = [[UIImageView alloc] initWithImage:Image];
// add it to as a subview
[self.view addSubview:TouchImageViews[Index]];
TouchImageViews[Index].hidden = YES;
}
PushSocket = ReplySocket = NULL;
ReplySource = NULL;
[self SpinUpThreads];
// start up a timer to ping other end
self.PingTimer = [NSTimer scheduledTimerWithTimeInterval:3.0f target:self selector:@selector(OnPingTimer:) userInfo:nil repeats:YES];
// gyro is now a required capability, so just use it
self.MotionManager = [[CMMotionManager alloc] init];
assert(MotionManager.deviceMotionAvailable);
MotionManager.deviceMotionUpdateInterval = 1.0 / 30.0;
[MotionManager startDeviceMotionUpdates];
// turn on the gyro if it's there
if (MotionManager.gyroAvailable)
{
[MotionManager startGyroUpdates];
}
// start up a timer to pull CM data from
self.MotionTimer = [NSTimer scheduledTimerWithTimeInterval:MotionManager.deviceMotionUpdateInterval target:self selector:@selector(OnMotionTimer:) userInfo:nil repeats:YES];
}
return self;
}
- (void)flipsideViewControllerDidFinish:(FlipsideViewController *)controller
{
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)
{
[self dismissViewControllerAnimated:YES completion:nil];
}
else
{
[self.FlipsidePopoverController dismissPopoverAnimated:YES];
}
// resolve any new addresses
[self UpdateSocketAddr];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// resolve any new addresses
[self UpdateSocketAddr];
//// self.view.contentInset = UIEdgeInsets(100,100,100,100);
// self.view.frame = CGRectMake(self.view.frame.origin.x + 40, self.view.frame.origin.y + 40, self.view.frame.size.width - 40, self.view.frame.size.height - 40);
}
-(void)FlipController:(BOOL)bIsAnimated sender:(id)sender
{
/*
NSURL* URL = [NSURL URLWithString:@"http://qthttp.akamai.com.edgesuite.net/iphone_demo/Video_Content/discovery/B02C36_12682301401197_Surrounded_By_Storms/hd/B02C36_12682301401197_Surrounded_By_Storms.mov"];
MPMoviePlayerController* MoviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:URL];
MoviePlayer.fullscreen = YES;
[[MoviePlayer view] setFrame:self.view.frame];
[self.view addSubview:[MoviePlayer view]];
[MoviePlayer play];
*/
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)
{
[self presentViewController:self.NavController animated:bIsAnimated completion:nil];
// UIViewController *controller = [[UIViewController alloc] initWithNibName:@"UIViewController" bundle:nil];
// [self presentModalViewController:controller animated:YES];
}
else
{
if (!self.FlipsidePopoverController)
{
// UIViewController *controller = [[UIViewController alloc] initWithNibName:@"Flipside" bundle:nil];
// self.flipsidePopoverController = [[UIPopoverController alloc] initWithContentViewController:controller];
self.FlipsidePopoverController = [[UIPopoverController alloc] initWithContentViewController:self.NavController];
self.FlipsidePopoverController.delegate = (FlipsideViewController*)self.NavController;
}
if ([self.FlipsidePopoverController isPopoverVisible])
{
[self.FlipsidePopoverController dismissPopoverAnimated:bIsAnimated];
}
else
{
[self.FlipsidePopoverController presentPopoverFromRect:[sender frame] inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:bIsAnimated];
}
}
// [self presentModalViewController:self.NavController animated:YES];
}
- (IBAction)showInfo:(id)sender
{
[self FlipController:YES sender:sender];
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc. that aren't in use.
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
// does user want to allow orientation changes?
if (AppDelegate.bLockOrientation)
{
// if so, return yes for the current orientation (caller wants at least one to return YES)
return 1 << AppDelegate.LockedOrientation;
}
return UIInterfaceOrientationMaskAll;
}
-(void)viewDidLoad
{
[super viewDidLoad];
}
-(void)MoveTopRightControl:(UIView*)Control OldInsets:(UIEdgeInsets)Old NewInsets:(UIEdgeInsets)New
{
CGRect Frame = Control.frame;
Frame.origin.x -= New.right - Old.right;
Frame.origin.y += New.top - Old.top;
Control.frame = Frame;
}
-(void)MoveBottomLeftControl:(UIView*)Control OldInsets:(UIEdgeInsets)Old NewInsets:(UIEdgeInsets)New
{
CGRect Frame = Control.frame;
Frame.origin.x += New.left - Old.left;
Frame.origin.y -= New.bottom - Old.bottom;
Control.frame = Frame;
}
UIEdgeInsets LastInsets = UIEdgeInsetsMake(0,0,0,0);
-(void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
if (@available(iOS 11, *))
{
UIEdgeInsets NewInsets = [self.view safeAreaInsets];
NSLog(@"NewInsets: %.2f, %.2f - %.2f, %.2f", NewInsets.left, NewInsets.top, NewInsets.right, NewInsets.bottom);
if (LastInsets.left != NewInsets.left || LastInsets.top != NewInsets.top ||
LastInsets.right != NewInsets.right || LastInsets.bottom != NewInsets.bottom)
{
NSLog(@"Info.top = %f", self.InfoButton.frame.origin.y);
[self MoveTopRightControl:self.InfoButton OldInsets:LastInsets NewInsets:NewInsets];
[self MoveBottomLeftControl:self.Text1 OldInsets:LastInsets NewInsets:NewInsets];
[self MoveBottomLeftControl:self.Text2 OldInsets:LastInsets NewInsets:NewInsets];
[self MoveBottomLeftControl:self.HelpLabel OldInsets:LastInsets NewInsets:NewInsets];
[self MoveBottomLeftControl:self.HostNameLabel OldInsets:LastInsets NewInsets:NewInsets];
[self MoveBottomLeftControl:self.ResolvedNameLabel OldInsets:LastInsets NewInsets:NewInsets];
CGRect BackgroundFrame = self.Background.frame;
BackgroundFrame.origin.x += NewInsets.left - LastInsets.left;
BackgroundFrame.origin.y += NewInsets.top - LastInsets.top;
BackgroundFrame.size.width += LastInsets.left + LastInsets.right - (NewInsets.left + NewInsets.right);
BackgroundFrame.size.height += LastInsets.top + LastInsets.bottom - (NewInsets.top + NewInsets.bottom);
self.Background.frame = BackgroundFrame;
// Frame = self.InfoButton.frame; Frame.origin.y += NewInsets.top - LastInsets.top; self.InfoButton.frame = Frame;
// Frame = self.Text1.frame; Frame.origin.y += NewInsets.bottom - LastInsets.bottom; self.InfoButton.frame = Frame;
NSLog(@"Info.top = %f", self.InfoButton.frame.origin.y);
LastInsets = NewInsets;
}
}
}
- (void)CleanupResolution
{
// make sure the host isn't scheduled anymore
CFHostUnscheduleFromRunLoop(ResolvingHost, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
CFHostSetClient(ResolvingHost, NULL, NULL);
CFRelease(ResolvingHost);
ResolvingHost = NULL;
}
/**
* Member function when resolution finishes
*/
- (void)OnResolveComplete:(const CFStreamError*) Error
{
// did it succeed?
if (Error->error == noErr)
{
// get the resolved addresses array
Boolean bWasResolved = NO;
CFArrayRef Addresses = CFHostGetAddressing(ResolvingHost, &bWasResolved);
if (bWasResolved)
{
int NumAddresses = (int)CFArrayGetCount(Addresses);
// look for the first IPv4 address
for (int AddressIndex = 0; AddressIndex < NumAddresses; AddressIndex++)
{
// just use the last address (it should be IPv4)
CFDataRef AddressData = (CFDataRef)CFArrayGetValueAtIndex(Addresses, AddressIndex);
// get the addr from the address list, and add a port number
sockaddr* ResolvedSockAddr = (struct sockaddr *)CFDataGetBytePtr(AddressData);
// is this IPv4?
if (ResolvedSockAddr->sa_family == AF_INET)
{
sockaddr_in ResolvedAddrWithPort;
memcpy(&ResolvedAddrWithPort, ResolvedSockAddr, ResolvedSockAddr->sa_len);
UInt8 PortCount = [AppDelegate.Ports count];
if( PortCount > 0 )
{
for( int i = 0; i < [AppDelegate.Ports count]; i++ )
{
short Port = [[AppDelegate.Ports objectAtIndex:i] intValue];
ResolvedAddrWithPort.sin_port = htons(Port);
// make a CFData object usable by CFSocket to send to
SocketAddrData[i] = CFDataCreate(NULL, (UInt8*)&ResolvedAddrWithPort, ResolvedAddrWithPort.sin_len);
}
}
// turn it into a string
char AddressBuffer[128];
getnameinfo((sockaddr*)&ResolvedAddrWithPort, ResolvedAddrWithPort.sin_len, AddressBuffer, 128, NULL, 0, NI_NUMERICHOST);
self.ResolvedAddrString = [NSString stringWithFormat:@"%s", AddressBuffer];
// put string into the label
self.ResolvedNameLabel.textColor = [UIColor yellowColor];
NSString* PortsString = [self GetPortsString];//[[NSString alloc] initWithString: @""];
self.ResolvedNameLabel.text = [NSString stringWithFormat:@"%@:%@ [Waiting for connection...]", self.ResolvedAddrString, PortsString];
self.HelpLabel.textColor = [UIColor cyanColor];
if (AppDelegate.bShouldIgnoreTilt && !AppDelegate.bShouldIgnoreTouch)
{
self.HelpLabel.text = @"Sending only TOUCH info...";
}
else if (!AppDelegate.bShouldIgnoreTilt && AppDelegate.bShouldIgnoreTouch)
{
self.HelpLabel.text = @"Sending only TILT info...";
}
else if (AppDelegate.bShouldIgnoreTilt && AppDelegate.bShouldIgnoreTouch)
{
self.HelpLabel.textColor = [UIColor yellowColor];
self.HelpLabel.text = @"Sending nothing, change settings...";
}
else
{
self.HelpLabel.text = @"Sending TOUCH and TILT info...";
}
// send a ping right away to get a reply
bIsConnected = NO;
[self PushData:DT_Ping Data:NULL DataSize:0 UniqueTouchHandle:0];
break;
}
}
}
}
else
{
// update label
self.ResolvedNameLabel.text = @"Failed to resolve!";
self.HelpLabel.text = @"Unable to send data...";
self.HelpLabel.textColor = [UIColor redColor];
}
[self CleanupResolution];
}
/**
* Callback function when resolution finishes
*/
static void MyCFHostClientCallBack(CFHostRef Host, CFHostInfoType TypeInfo, const CFStreamError *Error, void *UserInfo)
{
MainViewController* Controller = (__bridge MainViewController*)UserInfo;
[Controller OnResolveComplete:Error];
}
/**
* Resolve a network name to IP address
*/
- (BOOL)UpdateSocketAddr
{
// stop any inflight resolution
if (ResolvingHost)
{
CFHostCancelInfoResolution(ResolvingHost, kCFHostAddresses);
[self CleanupResolution];
}
// kill the old data
for (int i = 0; i < ARRAY_COUNT( SocketAddrData ); i++ )
{
SocketAddrData[i] = NULL;
}
// update the ip addr from the other view
NSString* HostName = AppDelegate.PCAddress;
// if it's the same do nothing
// if ([self.HostNameLabel.text isEqualToString:HostName])
// {
// return YES;
// }
if (!HostName)
{
// update the labels
self.HostNameLabel.text = @"None entered!";
self.ResolvedNameLabel.text = @"";
self.HelpLabel.text = @"Unable to send data...";
self.HelpLabel.textColor = [UIColor yellowColor];
return NO;
}
// update the labels
self.HostNameLabel.text = HostName;
self.ResolvedNameLabel.text = @"Resolving...";
self.HelpLabel.text = @"Unable to send data...";
self.HelpLabel.textColor = [UIColor yellowColor];
// make a new host object to resolve
ResolvingHost = CFHostCreateWithName(NULL, (__bridge CFStringRef)HostName);
// set up async resolution callback
CFHostClientContext Context = { 0, (__bridge void*)self, CFRetain, CFRelease, NULL };
if (!CFHostSetClient(ResolvingHost, MyCFHostClientCallBack, &Context))
{
self.ResolvedNameLabel.text = @"Failed to setup resolution...";
self.HelpLabel.text = @"Unable to send data...";
self.HelpLabel.textColor = [UIColor redColor];
[self CleanupResolution];
return NO;
}
// schedule the async operation
CFHostScheduleWithRunLoop(ResolvingHost, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
// perform asynchronous lookup
CFStreamError Error;
if (!CFHostStartInfoResolution(ResolvingHost, kCFHostAddresses, &Error))
{
self.ResolvedNameLabel.text = @"Failed to start resolution...";
self.HelpLabel.text = @"Unable to send data...";
self.HelpLabel.textColor = [UIColor redColor];
[self CleanupResolution];
return NO;
}
return YES;
}
/**
* Process received data
*/
-(void)OnReceivedData:(NSData*)NetData FromAddr:(const NSData*)Addr
{
// we have a connection if we got any data
bIsConnected = YES;
// reset how long it's been since we got a ping
PingsWithoutReply = 0;
if ([NetData length] == 5)
{
char AddressBuffer[128];
getnameinfo((sockaddr*)[Addr bytes], (int)[Addr length], AddressBuffer, 128, NULL, 0, NI_NUMERICHOST);
// put string into the label
self.ResolvedNameLabel.textColor = [UIColor cyanColor];
NSString* PortsString = [self GetPortsString];
self.ResolvedNameLabel.text = [NSString stringWithFormat:@"Connected to %s:%@", AddressBuffer, PortsString];
return;
}
static int NumChunksWaiting = 0;
int* IntData = (int*)[NetData bytes];
if ([NetData length] > 4 && IntData[0] == 0xFFEEDDCC)
{
NumChunksWaiting = IntData[1];
[self.ReceiveData setLength:0];
}
// get a chunk
else if (NumChunksWaiting)
{
NumChunksWaiting--;
if (self.ReceiveData == nil)
{
self.ReceiveData = [[NSMutableData alloc] init];
}
[self.ReceiveData appendData:NetData];
// did we get em all?
if (NumChunksWaiting == 0)
{
Background.image = [UIImage imageWithData:ReceiveData];
Background.contentMode = UIViewContentModeScaleToFill;
// Background.bounds = Background.superview.frame;
Background.opaque = YES;
}
}
}
/**
* Callback when data comes in from other end
*/
static void SocketDataCallback(CFSocketRef Socket, CFSocketCallBackType CallbackType, CFDataRef Addr, const void* Data, void* UserInfo)
{
MainViewController* Controller = (__bridge MainViewController*)UserInfo;
[Controller OnReceivedData:(__bridge NSData*)Data FromAddr:(__bridge NSData*)Addr];
}
/**
* Called every once in awhile to handle connections
*/
- (void)OnPingTimer:(NSTimer*)Timer
{
PingsWithoutReply++;
if (bIsConnected && PingsWithoutReply == 3)
{
bIsConnected = NO;
self.ResolvedNameLabel.textColor = [UIColor yellowColor];
NSString* PortsString = [self GetPortsString];
self.ResolvedNameLabel.text = [NSString stringWithFormat:@"%@:%@ [Waiting for connection...]", self.ResolvedAddrString, PortsString];
}
// send a ping to PC
[self PushData:DT_Ping Data:NULL DataSize:0 UniqueTouchHandle:0];
}
/**
* Send data over the network
*
* @param DataType What kind of data to send
* @param Data1 DataType-specific data
* @param Data2 DataType-specific data
*/
- (void)PushData:(EDataType)DataType Data:(float*)Data DataSize:(int)DataSize UniqueTouchHandle:(unsigned char)Handle
{
// can only send if we have a valid address
if (!bIsConnected && DataType != DT_Ping)
{
return;
}
static int RingBufferIndex = 0;
// next message to send
FMessage& Message = GRingBuffer[RingBufferIndex];
// move to next element for next time this function is called
RingBufferIndex = (RingBufferIndex + 1) & (RING_BUFFER_NUM_ELEMENTS - 1);
// setup the data to push
Message.MagicTag = MESSAGE_MAGIC_ID;
Message.MessageVersion = 1;
Message.MessageID = MessageID++;
Message.DataType = DataType;
Message.Handle = Handle;
Message.DeviceOrientation = (unsigned char)[[UIDevice currentDevice] orientation];
Message.UIOrientation = (unsigned char)[UIApplication sharedApplication].statusBarOrientation;
if (Data && DataSize)
{
memcpy(Message.Data, Data, DataSize);
}
// create NSData if needed
if (PushData == nil)
{
PushData = [[NSMutableData alloc] initWithBytes:&Message length:sizeof(Message)];
}
else
{
// replace the whole range
NSRange Range = { 0, sizeof(FMessage) };
[PushData replaceBytesInRange:Range withBytes:&Message];
}
// send over the network
int NumTimesToSend = 1;
// certain messages are very important, so we send them a few times
// and it's up to the listener to discard extra copies
if (DataType == DT_TouchBegan || DataType == DT_TouchEnded)
{
NumTimesToSend = 10;
}
// send as many times as we want
for (int SendIndex = 0; SendIndex < NumTimesToSend; SendIndex++)
{
for( int i = 0; i < ARRAY_COUNT( SocketAddrData ) && SocketAddrData[i] != NULL; i++ )
{
// in the case that the socket was killed (from sleep), this will return -1, so try to recreate it
if (CFSocketSendData(PushSocket, (CFDataRef)SocketAddrData[i], (__bridge CFDataRef)PushData, 0) == -1)
{
[self SpinUpThreads];
}
}
}
}
/**
* Returns the unique ID for the given touch
*/
-(unsigned char) GetTouchIndex:(UITouch*)Touch
{
// look for existing touch
for (int Index = 0; Index < ARRAY_COUNT(AllTouches); Index++)
{
if (AllTouches[Index] == Touch)
{
return Index;
}
}
// if we get here, it's a new touch, find a slot
for (int Index = 0; Index < ARRAY_COUNT(AllTouches); Index++)
{
if (AllTouches[Index] == nil)
{
AllTouches[Index] = Touch;
return Index;
}
}
// if we get here, that means we are trying to use more than 5 touches at once, which is an error
return 255;
}
/**
* Process a set of touches generically
*/
- (void)ProcessTouches:(NSSet*)Touches OfType:(EDataType)DataType
{
// send each touch
if (!AppDelegate.bShouldIgnoreTouch)
{
for (UITouch* Touch in Touches)
{
// send the touch coords in 0..1 range
CGPoint Loc = [Touch locationInView:self.Background];
float Data[2];
Data[0] = (float)Loc.x / (float)self.Background.bounds.size.width;
Data[1] = (float)Loc.y / (float)self.Background.bounds.size.height;
if (Data[0] < 0.0f) Data[0] = 0.0f;
if (Data[0] > 1.0f) Data[0] = 1.0f;
if (Data[1] < 0.0f) Data[1] = 0.0f;
if (Data[1] > 1.0f) Data[1] = 1.0f;
unsigned char TouchIndex = [self GetTouchIndex:Touch];
[self PushData:DataType Data:Data DataSize:sizeof(Data) UniqueTouchHandle:TouchIndex];
// draw the "fingerprints"
if (TouchIndex < ARRAY_COUNT(TouchImageViews))
{
UIImageView* TouchImageView = TouchImageViews[TouchIndex];
// put the image under the touch
TouchImageView.center = Loc;
// hide when user lets go
if (DataType == DT_TouchEnded)
{
TouchImageView.hidden = YES;
AllTouches[TouchIndex] = nil;
}
else
{
TouchImageView.hidden = NO;
}
}
}
}
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self ProcessTouches:touches OfType:DT_TouchBegan];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[self ProcessTouches:touches OfType:DT_TouchMoved];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[self ProcessTouches:touches OfType:DT_TouchEnded];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[self ProcessTouches:touches OfType:DT_TouchEnded];
}
/**
* Set the current tilt to be the "zero" rotation
*/
- (void)CalibrateTilt
{
if (self.MotionManager != nil)
{
self.ReferenceAttitude = MotionManager.deviceMotion.attitude;
}
else
{
bRecenterPitchAndRoll = YES;
}
}
- (void)OnMotionTimer:(NSTimer*)Timer
{
if (!AppDelegate.bShouldIgnoreTilt)
{
// get the CMMotion data
CMAttitude* CurrentAttitude = MotionManager.deviceMotion.attitude;
CMRotationRate CurrentRotationRate = MotionManager.deviceMotion.rotationRate;
CMAcceleration CurrentGravity = MotionManager.deviceMotion.gravity;
CMAcceleration CurrentUserAcceleration = MotionManager.deviceMotion.userAcceleration;
if (ReferenceAttitude)
{
[CurrentAttitude multiplyByInverseOfAttitude:ReferenceAttitude];
}
// setup the message
float Data[12];
Data[0 * 3 + 0] = (float)CurrentAttitude.pitch;
Data[0 * 3 + 1] = (float)CurrentAttitude.yaw;
Data[0 * 3 + 2] = (float)CurrentAttitude.roll;
Data[1 * 3 + 0] = (float)CurrentRotationRate.x;
Data[1 * 3 + 1] = (float)CurrentRotationRate.y;
Data[1 * 3 + 2] = (float)CurrentRotationRate.z;
Data[2 * 3 + 0] = (float)CurrentGravity.x;
Data[2 * 3 + 1] = (float)CurrentGravity.y;
Data[2 * 3 + 2] = (float)CurrentGravity.z;
Data[3 * 3 + 0] = (float)CurrentUserAcceleration.x;
Data[3 * 3 + 1] = (float)CurrentUserAcceleration.y;
Data[3 * 3 + 2] = (float)CurrentUserAcceleration.z;
// send it!
[self PushData:DT_Motion Data:Data DataSize:sizeof(Data) UniqueTouchHandle:0];
// setup gyro message
if (MotionManager.gyroAvailable)
{
CMRotationRate GyroData = MotionManager.gyroData.rotationRate;
float Gyro[3];
Gyro[0] = (float)GyroData.x;
Gyro[1] = (float)GyroData.y;
Gyro[2] = (float)GyroData.z;
// send it
[self PushData:DT_Gyro Data:Gyro DataSize:sizeof(Gyro) UniqueTouchHandle:0];
}
}
}
- (void)viewDidUnload
{
// Release any retained subviews of the main view.
self.HostNameLabel = nil;
self.ResolvedNameLabel = nil;
self.HelpLabel = nil;
self.FlipsidePopoverController = nil;
}
- (void)dealloc
{
[super dealloc];
for (int Index = 0; Index < ARRAY_COUNT(TouchImageViews); Index++)
{
[TouchImageViews[Index] release];
}
[PushData release];
CFRelease(PushSocket);
}
- (BOOL)prefersStatusBarHidden
{
return YES;
}
@end