Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
30 changed files
with
4,903 additions
and
0 deletions.
There are no files selected for viewing
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
#import "PTChannel.h" | ||
|
||
static const NSTimeInterval PTAppReconnectDelay = 1.0; | ||
|
||
@interface PTAppDelegate : NSObject <NSApplicationDelegate, PTChannelDelegate> | ||
|
||
@property (assign) IBOutlet NSWindow *window; | ||
@property (assign) IBOutlet NSTextField *inputTextField; | ||
@property (assign) IBOutlet NSTextView *outputTextView; | ||
|
||
- (IBAction)sendMessage:(id)sender; | ||
|
||
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,293 @@ | ||
#import "PTAppDelegate.h" | ||
#import "PTUSBHub.h" | ||
#import "PTExampleProtocol.h" | ||
#import <QuartzCore/QuartzCore.h> | ||
|
||
@interface PTAppDelegate () { | ||
// If the remote connection is over USB transport... | ||
NSNumber *connectingToDeviceID_; | ||
NSNumber *connectedDeviceID_; | ||
NSDictionary *connectedDeviceProperties_; | ||
NSDictionary *remoteDeviceInfo_; | ||
dispatch_queue_t notConnectedQueue_; | ||
BOOL notConnectedQueueSuspended_; | ||
PTChannel *connectedChannel_; | ||
NSDictionary *consoleTextAttributes_; | ||
NSDictionary *consoleStatusTextAttributes_; | ||
} | ||
|
||
@property (readonly) NSNumber *connectedDeviceID; | ||
@property PTChannel *connectedChannel; | ||
|
||
- (void)presentMessage:(NSString*)message isStatus:(BOOL)isStatus; | ||
- (void)startListeningForDevices; | ||
- (void)didDisconnectFromDevice:(NSNumber*)deviceID; | ||
- (void)disconnectFromCurrentChannel; | ||
- (void)enqueueConnectToLocalIPv4Port; | ||
- (void)connectToLocalIPv4Port; | ||
- (void)connectToUSBDevice; | ||
|
||
@end | ||
|
||
|
||
@implementation PTAppDelegate | ||
|
||
@synthesize window = window_; | ||
@synthesize inputTextField = inputTextField_; | ||
@synthesize outputTextView = outputTextView_; | ||
@synthesize connectedDeviceID = connectedDeviceID_; | ||
|
||
|
||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { | ||
// We use a serial queue that we toggle depending on if we are connected or | ||
// not. When we are not connected to a peer, the queue is running to handle | ||
// "connect" tries. When we are connected to a peer, the queue is suspended | ||
// thus no longer trying to connect. | ||
notConnectedQueue_ = dispatch_queue_create("PTExample.notConnectedQueue", DISPATCH_QUEUE_SERIAL); | ||
|
||
// Configure the output NSTextView we use for UI feedback | ||
outputTextView_.textContainerInset = NSMakeSize(15.0, 10.0); | ||
consoleTextAttributes_ = [NSDictionary dictionaryWithObjectsAndKeys: | ||
[NSFont fontWithName:@"helvetica" size:16.0], NSFontAttributeName, | ||
[NSColor lightGrayColor], NSForegroundColorAttributeName, | ||
nil]; | ||
consoleStatusTextAttributes_ = [NSDictionary dictionaryWithObjectsAndKeys: | ||
[NSFont fontWithName:@"menlo" size:11.0], NSFontAttributeName, | ||
[NSColor darkGrayColor], NSForegroundColorAttributeName, | ||
nil]; | ||
|
||
// Configure the input NSTextField we use for UI input | ||
[inputTextField_ setFont:[NSFont fontWithDescriptor:[[consoleTextAttributes_ objectForKey:NSFontAttributeName] fontDescriptor] size:14.0]]; | ||
[self.window makeFirstResponder:inputTextField_]; | ||
|
||
// Start listening for device attached/detached notifications | ||
[self startListeningForDevices]; | ||
|
||
// Start trying to connect to local IPv4 port (defined in PTExampleProtocol.h) | ||
[self enqueueConnectToLocalIPv4Port]; | ||
|
||
// Put a little message in the UI | ||
[self presentMessage:@"Ready for action — connecting at will." isStatus:YES]; | ||
} | ||
|
||
|
||
- (IBAction)sendMessage:(id)sender { | ||
if (connectedChannel_) { | ||
NSString *message = self.inputTextField.stringValue; | ||
dispatch_data_t payload = [[message dataUsingEncoding:NSUTF8StringEncoding] createReferencingDispatchData]; | ||
[connectedChannel_ sendFrameOfType:PTExampleFrameTypeTextMessage tag:PTFrameNoTag withPayload:payload callback:^(NSError *error) { | ||
if (error) { | ||
NSLog(@"Failed to send message: %@", error); | ||
} | ||
}]; | ||
[self presentMessage:[NSString stringWithFormat:@"[you]: %@", message] isStatus:NO]; | ||
self.inputTextField.stringValue = @""; | ||
} | ||
} | ||
|
||
|
||
- (void)presentMessage:(NSString*)message isStatus:(BOOL)isStatus { | ||
NSLog(@">> %@", message); | ||
[self.outputTextView.textStorage beginEditing]; | ||
if (self.outputTextView.textStorage.length > 0) { | ||
message = [@"\n" stringByAppendingString:message]; | ||
} | ||
[self.outputTextView.textStorage appendAttributedString:[[NSAttributedString alloc] initWithString:message attributes:isStatus ? consoleStatusTextAttributes_ : consoleTextAttributes_]]; | ||
[self.outputTextView.textStorage endEditing]; | ||
|
||
[NSAnimationContext beginGrouping]; | ||
[NSAnimationContext currentContext].duration = 0.15; | ||
[NSAnimationContext currentContext].timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; | ||
NSClipView* clipView = [[self.outputTextView enclosingScrollView] contentView]; | ||
NSPoint newOrigin = clipView.bounds.origin; | ||
newOrigin.y += 5.0; // hack A 1/2 | ||
[clipView setBoundsOrigin:newOrigin]; // hack A 2/2 | ||
newOrigin.y += 1000.0; | ||
newOrigin = [clipView constrainScrollPoint:newOrigin]; | ||
[clipView.animator setBoundsOrigin:newOrigin]; | ||
[NSAnimationContext endGrouping]; | ||
|
||
// Scrolling w/o animation: | ||
//[self.outputTextView scrollToEndOfDocument:self]; | ||
} | ||
|
||
|
||
- (PTChannel*)connectedChannel { | ||
return connectedChannel_; | ||
} | ||
|
||
- (void)setConnectedChannel:(PTChannel*)connectedChannel { | ||
connectedChannel_ = connectedChannel; | ||
|
||
// Toggle the notConnectedQueue_ depending on if we are connected or not | ||
if (!connectedChannel_ && notConnectedQueueSuspended_) { | ||
dispatch_resume(notConnectedQueue_); | ||
notConnectedQueueSuspended_ = NO; | ||
} else if (connectedChannel_ && !notConnectedQueueSuspended_) { | ||
dispatch_suspend(notConnectedQueue_); | ||
notConnectedQueueSuspended_ = YES; | ||
} | ||
|
||
if (!connectedChannel_ && connectingToDeviceID_) { | ||
[self enqueueConnectToUSBDevice]; | ||
} | ||
} | ||
|
||
|
||
#pragma mark - PTChannelDelegate | ||
|
||
|
||
- (BOOL)ioFrameChannel:(PTChannel*)channel shouldAcceptFrameOfType:(uint32_t)type tag:(uint32_t)tag payloadSize:(uint32_t)payloadSize { | ||
if ( type != PTExampleFrameTypeDeviceInfo | ||
&& type != PTExampleFrameTypeTextMessage | ||
&& type != PTFrameTypeEndOfStream) { | ||
NSLog(@"Unexpected frame of type %u", type); | ||
[channel close]; | ||
return NO; | ||
} else { | ||
return YES; | ||
} | ||
} | ||
|
||
- (void)ioFrameChannel:(PTChannel*)channel didReceiveFrameOfType:(uint32_t)type tag:(uint32_t)tag payload:(PTData*)payload { | ||
//NSLog(@"received %@, %u, %u, %@", channel, type, tag, payload); | ||
if (type == PTExampleFrameTypeDeviceInfo) { | ||
NSDictionary *deviceInfo = [NSDictionary dictionaryWithContentsOfDispatchData:payload.dispatchData]; | ||
[self presentMessage:[NSString stringWithFormat:@"Connected to %@", deviceInfo.description] isStatus:YES]; | ||
} else if (type == PTExampleFrameTypeTextMessage) { | ||
NSString *message = [[NSString alloc] initWithBytes:payload.data length:payload.length encoding:NSUTF8StringEncoding]; | ||
[self presentMessage:[NSString stringWithFormat:@"[%@]: %@", channel.userInfo, message] isStatus:NO]; | ||
} | ||
} | ||
|
||
- (void)ioFrameChannel:(PTChannel*)channel didEndWithError:(NSError*)error { | ||
if (connectedDeviceID_ && [connectedDeviceID_ isEqualToNumber:channel.userInfo]) { | ||
[self didDisconnectFromDevice:connectedDeviceID_]; | ||
} | ||
|
||
if (connectedChannel_ == channel) { | ||
[self presentMessage:[NSString stringWithFormat:@"Disconnected from %@", channel.userInfo] isStatus:YES]; | ||
self.connectedChannel = nil; | ||
} | ||
} | ||
|
||
|
||
#pragma mark - Wired device connections | ||
|
||
|
||
- (void)startListeningForDevices { | ||
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; | ||
|
||
[nc addObserverForName:PTUSBDeviceDidAttachNotification object:PTUSBHub.sharedHub queue:nil usingBlock:^(NSNotification *note) { | ||
NSNumber *deviceID = [note.userInfo objectForKey:@"DeviceID"]; | ||
//NSLog(@"PTUSBDeviceDidAttachNotification: %@", note.userInfo); | ||
NSLog(@"PTUSBDeviceDidAttachNotification: %@", deviceID); | ||
|
||
dispatch_async(notConnectedQueue_, ^{ | ||
if (!connectingToDeviceID_ || ![deviceID isEqualToNumber:connectingToDeviceID_]) { | ||
[self disconnectFromCurrentChannel]; | ||
connectingToDeviceID_ = deviceID; | ||
connectedDeviceProperties_ = [note.userInfo objectForKey:@"Properties"]; | ||
[self enqueueConnectToUSBDevice]; | ||
} | ||
}); | ||
}]; | ||
|
||
[nc addObserverForName:PTUSBDeviceDidDetachNotification object:PTUSBHub.sharedHub queue:nil usingBlock:^(NSNotification *note) { | ||
NSNumber *deviceID = [note.userInfo objectForKey:@"DeviceID"]; | ||
//NSLog(@"PTUSBDeviceDidDetachNotification: %@", note.userInfo); | ||
NSLog(@"PTUSBDeviceDidDetachNotification: %@", deviceID); | ||
|
||
if ([connectingToDeviceID_ isEqualToNumber:deviceID]) { | ||
connectedDeviceProperties_ = nil; | ||
connectingToDeviceID_ = nil; | ||
if (connectedChannel_) { | ||
[connectedChannel_ close]; | ||
} | ||
} | ||
}]; | ||
} | ||
|
||
|
||
- (void)didDisconnectFromDevice:(NSNumber*)deviceID { | ||
NSLog(@"Disconnected from device"); | ||
if ([connectedDeviceID_ isEqualToNumber:deviceID]) { | ||
[self willChangeValueForKey:@"connectedDeviceID"]; | ||
connectedDeviceID_ = nil; | ||
[self didChangeValueForKey:@"connectedDeviceID"]; | ||
} | ||
} | ||
|
||
|
||
- (void)disconnectFromCurrentChannel { | ||
if (connectedDeviceID_ && connectedChannel_) { | ||
[connectedChannel_ close]; | ||
self.connectedChannel = nil; | ||
} | ||
} | ||
|
||
|
||
- (void)enqueueConnectToLocalIPv4Port { | ||
dispatch_async(notConnectedQueue_, ^{ | ||
dispatch_async(dispatch_get_main_queue(), ^{ | ||
[self connectToLocalIPv4Port]; | ||
}); | ||
}); | ||
} | ||
|
||
|
||
- (void)connectToLocalIPv4Port { | ||
PTChannel *channel = [PTChannel channelWithDelegate:self]; | ||
channel.userInfo = [NSString stringWithFormat:@"127.0.0.1:%d", PTExampleProtocolIPv4PortNumber]; | ||
[channel connectToPort:PTExampleProtocolIPv4PortNumber IPv4Address:INADDR_LOOPBACK callback:^(NSError *error, PTAddress *address) { | ||
if (error) { | ||
if (error.domain == NSPOSIXErrorDomain && (error.code == ECONNREFUSED || error.code == ETIMEDOUT)) { | ||
// this is an expected state | ||
} else { | ||
NSLog(@"Failed to connect to 127.0.0.1:%d: %@", PTExampleProtocolIPv4PortNumber, error); | ||
} | ||
} else { | ||
[self disconnectFromCurrentChannel]; | ||
self.connectedChannel = channel; | ||
channel.userInfo = address; | ||
NSLog(@"Connected to %@", address); | ||
} | ||
[self performSelector:@selector(enqueueConnectToLocalIPv4Port) withObject:nil afterDelay:PTAppReconnectDelay]; | ||
}]; | ||
} | ||
|
||
|
||
- (void)enqueueConnectToUSBDevice { | ||
dispatch_async(notConnectedQueue_, ^{ | ||
dispatch_async(dispatch_get_main_queue(), ^{ | ||
[self connectToUSBDevice]; | ||
}); | ||
}); | ||
} | ||
|
||
|
||
- (void)connectToUSBDevice { | ||
PTChannel *channel = [PTChannel channelWithDelegate:self]; | ||
channel.userInfo = connectingToDeviceID_; | ||
channel.delegate = self; | ||
|
||
[channel connectToPort:PTExampleProtocolIPv4PortNumber overUSBHub:PTUSBHub.sharedHub deviceID:connectingToDeviceID_ callback:^(NSError *error) { | ||
if (error) { | ||
if (error.domain == PTUSBHubErrorDomain && error.code == PTUSBHubErrorConnectionRefused) { | ||
NSLog(@"Failed to connect to device #%@: %@", channel.userInfo, error); | ||
} else { | ||
NSLog(@"Failed to connect to device #%@: %@", channel.userInfo, error); | ||
} | ||
if (channel.userInfo == connectingToDeviceID_) { | ||
[self performSelector:@selector(enqueueConnectToUSBDevice) withObject:nil afterDelay:PTAppReconnectDelay]; | ||
} | ||
} else { | ||
connectedDeviceID_ = connectingToDeviceID_; | ||
self.connectedChannel = channel; | ||
//NSLog(@"Connected to device #%@\n%@", connectingToDeviceID_, connectedDeviceProperties_); | ||
//infoTextField_.stringValue = [NSString stringWithFormat:@"Connected to device #%@\n%@", deviceID, connectedDeviceProperties_]; | ||
} | ||
}]; | ||
} | ||
|
||
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
#ifndef peertalk_PTExampleProtocol_h | ||
#define peertalk_PTExampleProtocol_h | ||
|
||
#import <Foundation/Foundation.h> | ||
#include <stdint.h> | ||
|
||
static const int PTExampleProtocolIPv4PortNumber = 2345; | ||
|
||
enum { | ||
PTExampleFrameTypeDeviceInfo = 100, | ||
PTExampleFrameTypeTextMessage = 101, | ||
}; | ||
|
||
typedef struct _PTExampleTextFrame { | ||
const uint8_t *utf8text; | ||
} PTExampleTextFrame; | ||
|
||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
<plist version="1.0"> | ||
<dict> | ||
<key>CFBundleDevelopmentRegion</key> | ||
<string>en</string> | ||
<key>CFBundleExecutable</key> | ||
<string>${EXECUTABLE_NAME}</string> | ||
<key>CFBundleIconFile</key> | ||
<string>Icon</string> | ||
<key>CFBundleIdentifier</key> | ||
<string>me.rsms.peertalk.${PRODUCT_NAME:rfc1034identifier}</string> | ||
<key>CFBundleInfoDictionaryVersion</key> | ||
<string>6.0</string> | ||
<key>CFBundleName</key> | ||
<string>${PRODUCT_NAME}</string> | ||
<key>CFBundlePackageType</key> | ||
<string>APPL</string> | ||
<key>CFBundleShortVersionString</key> | ||
<string>1.0</string> | ||
<key>CFBundleSignature</key> | ||
<string>????</string> | ||
<key>CFBundleVersion</key> | ||
<string>1</string> | ||
<key>LSMinimumSystemVersion</key> | ||
<string>${MACOSX_DEPLOYMENT_TARGET}</string> | ||
<key>NSHumanReadableCopyright</key> | ||
<string>Copyright © 2012 __MyCompanyName__. All rights reserved.</string> | ||
<key>NSMainNibFile</key> | ||
<string>MainMenu</string> | ||
<key>NSPrincipalClass</key> | ||
<string>NSApplication</string> | ||
</dict> | ||
</plist> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
// | ||
// Prefix header for all source files of the 'Peertalk Example' target in the 'Peertalk Example' project | ||
// | ||
|
||
#ifdef __OBJC__ | ||
#import <Cocoa/Cocoa.h> | ||
#endif |
Oops, something went wrong.