From 4f635873ae001c68dccd50bef6fd919745cff282 Mon Sep 17 00:00:00 2001 From: mlch911 Date: Wed, 26 Jul 2023 14:13:08 +0800 Subject: [PATCH 01/10] Feature: Wireless Connection --- LookinServer.podspec | 4 +- LookinShared.podspec | 3 + .../Server/Connection/LKS_ConnectionManager.h | 6 + .../Server/Connection/LKS_ConnectionManager.m | 97 +++- .../Channel/Bonjour/ECONetServiceBrowser.h | 19 + .../Channel/Bonjour/ECONetServiceBrowser.m | 126 ++++ .../Channel/Bonjour/ECONetServicePublisher.h | 15 + .../Channel/Bonjour/ECONetServicePublisher.m | 50 ++ Src/Main/Shared/Channel/ECOBaseChannel.h | 57 ++ Src/Main/Shared/Channel/ECOBaseChannel.m | 33 ++ Src/Main/Shared/Channel/ECOChannelAppInfo.h | 27 + Src/Main/Shared/Channel/ECOChannelAppInfo.m | 85 +++ .../Shared/Channel/ECOChannelDeviceInfo.h | 48 ++ .../Shared/Channel/ECOChannelDeviceInfo.m | 205 +++++++ Src/Main/Shared/Channel/ECOChannelManager.h | 69 +++ Src/Main/Shared/Channel/ECOChannelManager.m | 116 ++++ Src/Main/Shared/Channel/ECOSocketChannel.h | 60 ++ Src/Main/Shared/Channel/ECOSocketChannel.m | 546 ++++++++++++++++++ Src/Main/Shared/Channel/ECOUSBChannel.h | 29 + Src/Main/Shared/Channel/ECOUSBChannel.m | 325 +++++++++++ Src/Main/Shared/LookinAppInfo.h | 2 + Src/Main/Shared/LookinAppInfo.m | 10 + Src/Main/Shared/LookinDefines.h | 9 + 23 files changed, 1937 insertions(+), 4 deletions(-) create mode 100644 Src/Main/Shared/Channel/Bonjour/ECONetServiceBrowser.h create mode 100644 Src/Main/Shared/Channel/Bonjour/ECONetServiceBrowser.m create mode 100644 Src/Main/Shared/Channel/Bonjour/ECONetServicePublisher.h create mode 100644 Src/Main/Shared/Channel/Bonjour/ECONetServicePublisher.m create mode 100644 Src/Main/Shared/Channel/ECOBaseChannel.h create mode 100644 Src/Main/Shared/Channel/ECOBaseChannel.m create mode 100644 Src/Main/Shared/Channel/ECOChannelAppInfo.h create mode 100644 Src/Main/Shared/Channel/ECOChannelAppInfo.m create mode 100644 Src/Main/Shared/Channel/ECOChannelDeviceInfo.h create mode 100644 Src/Main/Shared/Channel/ECOChannelDeviceInfo.m create mode 100644 Src/Main/Shared/Channel/ECOChannelManager.h create mode 100644 Src/Main/Shared/Channel/ECOChannelManager.m create mode 100644 Src/Main/Shared/Channel/ECOSocketChannel.h create mode 100644 Src/Main/Shared/Channel/ECOSocketChannel.m create mode 100644 Src/Main/Shared/Channel/ECOUSBChannel.h create mode 100644 Src/Main/Shared/Channel/ECOUSBChannel.m diff --git a/LookinServer.podspec b/LookinServer.podspec index 1c48481..2c7a4c1 100644 --- a/LookinServer.podspec +++ b/LookinServer.podspec @@ -12,7 +12,9 @@ Pod::Spec.new do |spec| spec.source = { :git => "https://github.com/QMUI/LookinServer.git", :tag => "1.1.7"} spec.framework = "UIKit" spec.requires_arc = true - + + spec.dependency 'CocoaAsyncSocket' + spec.subspec 'Core' do |ss| ss.source_files = ['Src/Main/**/*', 'Src/Base/**/*'] ss.pod_target_xcconfig = { diff --git a/LookinShared.podspec b/LookinShared.podspec index b4d48a9..a632a76 100644 --- a/LookinShared.podspec +++ b/LookinShared.podspec @@ -16,6 +16,9 @@ Pod::Spec.new do |spec| 'Src/Main/Shared/**/*', 'Src/Base/**/*' ] + + spec.dependency 'CocoaAsyncSocket' + spec.pod_target_xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) SHOULD_COMPILE_LOOKIN_SERVER=1' } diff --git a/Src/Main/Server/Connection/LKS_ConnectionManager.h b/Src/Main/Server/Connection/LKS_ConnectionManager.h index 0810566..fc71cf0 100644 --- a/Src/Main/Server/Connection/LKS_ConnectionManager.h +++ b/Src/Main/Server/Connection/LKS_ConnectionManager.h @@ -20,8 +20,14 @@ extern NSString *const LKS_ConnectionDidEndNotificationName; @property(nonatomic, assign) BOOL applicationIsActive; +- (void)startWirelessConnection; + +- (void)endWirelessConnection; + - (BOOL)isConnected; +- (BOOL)isWirelessConnnect; + - (void)respond:(LookinConnectionResponseAttachment *)data requestType:(uint32_t)requestType tag:(uint32_t)tag; - (void)pushData:(NSObject *)data type:(uint32_t)type; diff --git a/Src/Main/Server/Connection/LKS_ConnectionManager.m b/Src/Main/Server/Connection/LKS_ConnectionManager.m index 8fc5da4..b488b6c 100644 --- a/Src/Main/Server/Connection/LKS_ConnectionManager.m +++ b/Src/Main/Server/Connection/LKS_ConnectionManager.m @@ -16,6 +16,9 @@ #import "LKS_ExportManager.h" #import "LKS_PerspectiveManager.h" #import "LookinServerDefines.h" +#import "ECOChannelManager.h" + +@import CocoaAsyncSocket; NSString *const LKS_ConnectionDidEndNotificationName = @"LKS_ConnectionDidEndNotificationName"; @@ -25,6 +28,11 @@ @interface LKS_ConnectionManager () @property(nonatomic, strong) LKS_RequestHandler *requestHandler; +@property(nonatomic, strong) ECOChannelManager *wirelessChannel; +@property(nonatomic, strong) ECOChannelDeviceInfo *wirelessDevice; + +@property BOOL hasStartWirelessConnnection; + @end @implementation LKS_ConnectionManager @@ -52,6 +60,8 @@ - (instancetype)init { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleLocalInspectIn2D:) name:@"Lookin_2D" object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleLocalInspectIn3D:) name:@"Lookin_3D" object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(startWirelessConnection) name:@"Lookin_startWirelessConnection" object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(endWirelessConnection) name:@"Lookin_endWirelessConnection" object:nil]; [[NSNotificationCenter defaultCenter] addObserverForName:@"Lookin_Export" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) { [[LKS_ExportManager sharedInstance] exportAndShare]; }]; @@ -61,6 +71,81 @@ - (instancetype)init { return self; } +- (void)startWirelessConnection { + self.hasStartWirelessConnnection = YES; + if (!self.wirelessChannel) { +#if TARGET_OS_IPHONE + self.wirelessChannel = ECOChannelManager.new; + __weak __typeof(self) weakSelf = self; + // 接收到数据回调 + self.wirelessChannel.receivedBlock = ^(ECOChannelDeviceInfo *device, NSData *data, NSDictionary *extraInfo) { + NSLog(@"🚀 Lookin receivedBlock device:%@", device); + NSNumber *type = extraInfo[@"type"]; + NSNumber *tag = extraInfo[@"tag"]; + id object = nil; + id unarchivedObject = [NSKeyedUnarchiver unarchiveObjectWithData:data]; + if ([unarchivedObject isKindOfClass:[LookinConnectionAttachment class]]) { + LookinConnectionAttachment *attachment = (LookinConnectionAttachment *)unarchivedObject; + object = attachment.data; + } else { + object = unarchivedObject; + } + dispatch_async(dispatch_get_main_queue(), ^{ + [weakSelf.requestHandler handleRequestType:type.intValue tag:tag.intValue object:object]; + }); + }; + // 设备连接变更 + self.wirelessChannel.deviceBlock = ^(ECOChannelDeviceInfo *device, BOOL isConnected) { + NSLog(@"🚀 Lookin deviceBlock device:%@", device); + if ([device isEqual:weakSelf.wirelessDevice] && !isConnected) { + weakSelf.wirelessDevice = nil; + } + }; + // 授权状态变更回调 + self.wirelessChannel.authStateChangedBlock = ^(ECOChannelDeviceInfo *device, ECOAuthorizeResponseType authState) { + NSLog(@"🚀 Lookin authStateChangedBlock device:%@ authState:%ld", device, authState); + }; + // 请求授权状态认证回调 + self.wirelessChannel.requestAuthBlock = ^(ECOChannelDeviceInfo *device, ECOAuthorizeResponseType authState) { + NSLog(@"🚀 Lookin requestAuthBlock device:%@ authState:%ld", device, authState); + NSString *title = @"Lookin 连接请求"; + NSString *message = [NSString stringWithFormat:@"%@ 的Lookin想要连接你的设备,如果你想启用调试功能,请选择允许", device.hostName ?: device.ipAddress]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction *denyAction = [UIAlertAction actionWithTitle:@"拒绝" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) { + [weakSelf.wirelessChannel sendAuthorizationMessageToDevice:device state:ECOAuthorizeResponseType_Deny showAuthAlert:NO]; + }]; + UIAlertAction *allowOnceAction = [UIAlertAction actionWithTitle:@"允许一次" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + [weakSelf.wirelessChannel sendAuthorizationMessageToDevice:device state:ECOAuthorizeResponseType_AllowOnce showAuthAlert:NO]; + weakSelf.wirelessDevice = device; + }]; + UIAlertAction *allowAlwaysAction = [UIAlertAction actionWithTitle:@"始终允许" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + [weakSelf.wirelessChannel sendAuthorizationMessageToDevice:device state:ECOAuthorizeResponseType_AllowAlways showAuthAlert:NO]; + weakSelf.wirelessDevice = device; + }]; + [alertController addAction:denyAction]; + [alertController addAction:allowOnceAction]; + [alertController addAction:allowAlwaysAction]; + + dispatch_async(dispatch_get_main_queue(), ^{ + UIViewController *rootVC = [UIApplication sharedApplication].keyWindow.rootViewController; + [rootVC presentViewController:alertController animated:YES completion:nil]; + }); + }; +#endif + } +} + +- (void)endWirelessConnection { + self.hasStartWirelessConnnection = NO; + GCDAsyncSocket *asyncSocket = [self.wirelessChannel valueForKeyPath:@"socketChannel.cSocket"]; + if (asyncSocket) { + [asyncSocket setDelegate:nil]; + [asyncSocket disconnect]; + [self.wirelessChannel setValue:nil forKeyPath:@"socketChannel.cSocket"]; + } + self.wirelessChannel = nil; +} + - (void)_handleWillResignActiveNotification { self.applicationIsActive = NO; } @@ -133,7 +218,11 @@ - (void)dealloc { } - (BOOL)isConnected { - return self.peerChannel_ && self.peerChannel_.isConnected; + return self.isWirelessConnnect || (self.peerChannel_ && self.peerChannel_.isConnected); +} + +- (BOOL)isWirelessConnnect { + return self.wirelessChannel.isConnected; } - (void)respond:(LookinConnectionResponseAttachment *)data requestType:(uint32_t)requestType tag:(uint32_t)tag { @@ -145,15 +234,17 @@ - (void)pushData:(NSObject *)data type:(uint32_t)type { } - (void)_sendData:(NSObject *)data frameOfType:(uint32_t)frameOfType tag:(uint32_t)tag { + NSData *archivedData = [NSKeyedArchiver archivedDataWithRootObject:data]; if (self.peerChannel_) { - NSData *archivedData = [NSKeyedArchiver archivedDataWithRootObject:data]; dispatch_data_t payload = [archivedData createReferencingDispatchData]; [self.peerChannel_ sendFrameOfType:frameOfType tag:tag withPayload:payload callback:^(NSError *error) { if (error) { } }]; - } + } else if (self.wirelessDevice.isConnected) { + [self.wirelessChannel sendPacket:archivedData extraInfo:@{@"tag": @(tag), @"type": @(frameOfType)} toDevice:self.wirelessDevice]; + } } #pragma mark - Lookin_PTChannelDelegate diff --git a/Src/Main/Shared/Channel/Bonjour/ECONetServiceBrowser.h b/Src/Main/Shared/Channel/Bonjour/ECONetServiceBrowser.h new file mode 100644 index 0000000..fab2b28 --- /dev/null +++ b/Src/Main/Shared/Channel/Bonjour/ECONetServiceBrowser.h @@ -0,0 +1,19 @@ +// +// ECONetServiceBrowser.h +// Echo +// +// Created by 陈爱彬 on 2019/4/17. Maintain by 陈爱彬 +// Description +// + +#import + +typedef void(^ECONetServiceBrowserResolvedAddressesBlock)(NSArray *addresses, NSString *hostName); + +@interface ECONetServiceBrowser : NSObject + +@property (nonatomic, copy) ECONetServiceBrowserResolvedAddressesBlock addressesBlock; + +- (void)startBrowsing; + +@end diff --git a/Src/Main/Shared/Channel/Bonjour/ECONetServiceBrowser.m b/Src/Main/Shared/Channel/Bonjour/ECONetServiceBrowser.m new file mode 100644 index 0000000..98166d4 --- /dev/null +++ b/Src/Main/Shared/Channel/Bonjour/ECONetServiceBrowser.m @@ -0,0 +1,126 @@ +// +// ECONetServiceBrowser.m +// Echo +// +// Created by 陈爱彬 on 2019/4/17. Maintain by 陈爱彬 +// Description +// + +#import "ECONetServiceBrowser.h" +#import "LookinDefines.h" +#if TARGET_OS_IPHONE +#import +#endif + +@interface ECONetServiceBrowser() + + +@property (nonatomic, strong) NSMutableArray *services; +@property (nonatomic, strong) NSNetServiceBrowser *serviceBrowser; + +@end + +@implementation ECONetServiceBrowser + +- (instancetype)init { + self = [super init]; + if (self) { + + } + return self; +} +#pragma mark - Browser Services +//启动Bonjour服务搜索 +- (void)startBrowsing { + [self.services removeAllObjects]; + //创建Browser对象 + self.serviceBrowser = [[NSNetServiceBrowser alloc] init]; + self.serviceBrowser.includesPeerToPeer = YES; + [self.serviceBrowser setDelegate:self]; + [self.serviceBrowser searchForServicesOfType:LookinNetServiceType inDomain:LookinNetServiceDomain]; +} +//重置查找服务 +- (void)resetBrowserService { + [self.serviceBrowser stop]; + self.serviceBrowser = nil; + //重启查找 + [self startBrowsing]; +} +#pragma mark - NSNetServiceBrowserDelegate methods + +/* Sent to the NSNetServiceBrowser instance's delegate for each service discovered. If there are more services, moreComing will be YES. If for some reason handling discovered services requires significant processing, accumulating services until moreComing is NO and then doing the processing in bulk fashion may be desirable. + */ +- (void)netServiceBrowser:(NSNetServiceBrowser *)browser didFindService:(NSNetService *)service moreComing:(BOOL)moreComing { + NSLog(@"%s",__func__); + //解析服务 + [self.services addObject:service]; + service.delegate = self; + [service resolveWithTimeout:LookinNetServiceResolveAddressTimeout]; +} + +/* Sent to the NSNetServiceBrowser instance's delegate when a previously discovered service is no longer published. + */ +- (void)netServiceBrowser:(NSNetServiceBrowser *)browser didRemoveService:(NSNetService *)service moreComing:(BOOL)moreComing { + NSLog(@"%s",__func__); + //移除服务 + [self.services removeObject:service]; + service.delegate = nil; +} + +/* Sent to the NSNetServiceBrowser instance's delegate when an error in searching for domains or services has occurred. The error dictionary will contain two key/value pairs representing the error domain and code (see the NSNetServicesError enumeration above for error code constants). It is possible for an error to occur after a search has been started successfully. + */ +- (void)netServiceBrowser:(NSNetServiceBrowser *)browser didNotSearch:(NSDictionary *)errorDict { + NSLog(@"%s",__func__); + //重试 +#if TARGET_OS_IPHONE + if (@available(iOS 14.0, *)) { + NSNetServicesError errorCode = [errorDict[@"NSNetServicesErrorCode"] integerValue]; + if (errorCode == -72008) { + //iOS14新增本地网络隐私权限,提示用户如何设置并忽略 + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + NSString *title = @"Echo 连接提示"; + NSString *message = @"由于iOS14本地网络权限限制,请在Info.plist中设置NSLocalNetworkUsageDescription和NSBonjourServices,详细内容见:https://github.com/didi/echo"; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction *confirmAction = [UIAlertAction actionWithTitle:@"好的" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { + + }]; + [alertController addAction:confirmAction]; + UIViewController *rootVC = [UIApplication sharedApplication].keyWindow.rootViewController; + [rootVC presentViewController:alertController animated:YES completion:nil]; + }); + NSLog(@">>Echo Warning:Bonjour服务错误,由于iOS14本地网络权限限制,请在Info.plist中设置NSLocalNetworkUsageDescription和NSBonjourServices,详细内容见:https://github.com/didi/echo"); + return; + } + } +#endif + [self resetBrowserService]; +} +#pragma mark - NSNetServiceDelegate methods + +/* Sent to the NSNetService instance's delegate when one or more addresses have been resolved for an NSNetService instance. Some NSNetService methods will return different results before and after a successful resolution. An NSNetService instance may get resolved more than once; truly robust clients may wish to resolve again after an error, or to resolve more than once. + */ +- (void)netServiceDidResolveAddress:(NSNetService *)sender { + NSLog(@"%s",__func__); + //解析address地址 + NSString *name = [sender name]; + NSString *hostName = name ?: [sender hostName]; + NSArray *addresses = [[sender addresses] copy]; + !self.addressesBlock ?: self.addressesBlock(addresses, hostName ?: @""); +} + +/* Sent to the NSNetService instance's delegate when the instance's previously running publication or resolution request has stopped. + */ +- (void)netServiceDidStop:(NSNetService *)sender { + NSLog(@"%s",__func__); +} + +#pragma mark - getters +- (NSMutableArray *)services { + if (!_services) { + _services = [[NSMutableArray alloc] initWithCapacity:0]; + } + return _services; +} + +@end diff --git a/Src/Main/Shared/Channel/Bonjour/ECONetServicePublisher.h b/Src/Main/Shared/Channel/Bonjour/ECONetServicePublisher.h new file mode 100644 index 0000000..d6fe588 --- /dev/null +++ b/Src/Main/Shared/Channel/Bonjour/ECONetServicePublisher.h @@ -0,0 +1,15 @@ +// +// ECONetServicePublisher.h +// Echo +// +// Created by 陈爱彬 on 2019/4/17. Maintain by 陈爱彬 +// Description +// + +#import + +@interface ECONetServicePublisher : NSObject + +- (void)startPublish; + +@end diff --git a/Src/Main/Shared/Channel/Bonjour/ECONetServicePublisher.m b/Src/Main/Shared/Channel/Bonjour/ECONetServicePublisher.m new file mode 100644 index 0000000..fca7c16 --- /dev/null +++ b/Src/Main/Shared/Channel/Bonjour/ECONetServicePublisher.m @@ -0,0 +1,50 @@ +// +// ECONetServicePublisher.m +// Echo +// +// Created by 陈爱彬 on 2019/4/17. Maintain by 陈爱彬 +// Description +// + +#import "ECONetServicePublisher.h" +#import "LookinDefines.h" + +@interface ECONetServicePublisher() + + +@property (nonatomic, strong) NSNetService *netService; +@end + +@implementation ECONetServicePublisher + +- (instancetype)init { + self = [super init]; + if (self) { + + } + return self; +} +#pragma mark - Publish Service +- (void)startPublish { + if (self.netService) { + [self.netService stop]; + self.netService.delegate = nil; + self.netService = nil; + } + self.netService = [[NSNetService alloc] initWithDomain:LookinNetServiceDomain type:LookinNetServiceType name:LookinNetServiceName port:LookinNetServicePortNumber]; + self.netService.delegate = self; + [self.netService publish]; +} +#pragma mark - NSNetServiceDelegate methods +/* Sent to the NSNetService instance's delegate when the publication of the instance is complete and successful. + */ +- (void)netServiceDidPublish:(NSNetService *)sender { + +} +/* Sent to the NSNetService instance's delegate when an error in publishing the instance occurs. The error dictionary will contain two key/value pairs representing the error domain and code (see the NSNetServicesError enumeration above for error code constants). It is possible for an error to occur after a successful publication. + */ +- (void)netService:(NSNetService *)sender didNotPublish:(NSDictionary *)errorDict { + +} + +@end diff --git a/Src/Main/Shared/Channel/ECOBaseChannel.h b/Src/Main/Shared/Channel/ECOBaseChannel.h new file mode 100644 index 0000000..74738b7 --- /dev/null +++ b/Src/Main/Shared/Channel/ECOBaseChannel.h @@ -0,0 +1,57 @@ +// +// ECOBaseChannel.h +// Echo +// +// Created by 陈爱彬 on 2019/4/18. Maintain by 陈爱彬 +// Description +// + +#import +#import "ECOChannelDeviceInfo.h" + +// 通道优先级,usb > socket +typedef NS_ENUM(NSInteger, ECOChannelPriority) { + ECOChannelPriority_Socket = 0, + ECOChannelPriority_USB, +}; + +typedef NS_ENUM(NSInteger, ECOChannelConnectType) { + ECOChannelConnectType_Authorization = 0, //授权连接 + ECOChannelConnectType_Auto, //自动连接 +}; + +@class ECOBaseChannel; +@protocol ECOChannelConnectedDeviceProtocol + +@optional +//新的设备连接 +- (void)channel:(ECOBaseChannel *)channel didConnectedToDevice:(ECOChannelDeviceInfo *)device; +//设备已断开 +- (void)channel:(ECOBaseChannel *)channel didDisconnectWithDevice:(ECOChannelDeviceInfo *)device; +//接收到数据 +- (void)channel:(ECOBaseChannel *)channel didReceivedDevice:(ECOChannelDeviceInfo *)device andData:(NSData *)data extraInfo:(NSDictionary *)extraInfo; + +//设备变更了授权状态 +- (void)channel:(ECOBaseChannel *)channel device:(ECOChannelDeviceInfo *)device didChangedAuthState:(ECOAuthorizeResponseType)authorizedType; + +//设备要请求授权状态,弹窗展示 +- (void)channel:(ECOBaseChannel *)channel device:(ECOChannelDeviceInfo *)device willRequestAuthState:(ECOAuthorizeResponseType)authorizedType; + +@end + +@interface ECOBaseChannel : NSObject + +@property (nonatomic, assign) ECOChannelPriority priority; +@property (nonatomic, weak) id delegate; +@property (nonatomic, assign) ECOChannelConnectType connectType; + +- (instancetype)initWithDelegate:(id)delegate; +- (instancetype)init NS_UNAVAILABLE; + +//初始化Channel,供子类调用,该方法会在初始化后自动被调用 +- (void)setupChannel; + +// 是否已连接到Mac客户端 +- (BOOL)isConnected; + +@end diff --git a/Src/Main/Shared/Channel/ECOBaseChannel.m b/Src/Main/Shared/Channel/ECOBaseChannel.m new file mode 100644 index 0000000..caefdcf --- /dev/null +++ b/Src/Main/Shared/Channel/ECOBaseChannel.m @@ -0,0 +1,33 @@ +// +// ECOBaseChannel.m +// Echo +// +// Created by 陈爱彬 on 2019/4/18. Maintain by 陈爱彬 +// Description +// + +#import "ECOBaseChannel.h" + +@implementation ECOBaseChannel + +- (instancetype)initWithDelegate:(id)delegate { + self = [super init]; + if (self) { + self.delegate = delegate; + //目前使用授权连接机制,如果想自动连接已发现的设备,将该值改为ECOChannelConnectType_Auto + self.connectType = ECOChannelConnectType_Authorization; + [self setupChannel]; + } + return self; +} + +//初始化Channel,供子类调用,该方法会在初始化后自动被调用 +- (void)setupChannel { + +} + +// 是否已连接到Mac客户端 +- (BOOL)isConnected { + return NO; +} +@end diff --git a/Src/Main/Shared/Channel/ECOChannelAppInfo.h b/Src/Main/Shared/Channel/ECOChannelAppInfo.h new file mode 100644 index 0000000..ddaf933 --- /dev/null +++ b/Src/Main/Shared/Channel/ECOChannelAppInfo.h @@ -0,0 +1,27 @@ +// +// ECOChannelAppInfo.h +// EchoSDK +// +// Created by 陈爱彬 on 2019/10/30. Maintain by 陈爱彬 +// Description +// + +#import + +@interface ECOChannelAppInfo : NSObject + +@property (nonatomic, copy) NSString *appId; +@property (nonatomic, copy) NSString *appName; +@property (nonatomic, copy) NSString *appShortVersion; +@property (nonatomic, copy) NSString *appVersion; +@property (nonatomic, copy) NSString *appIcon; + +- (instancetype)initWithDictionary:(NSDictionary *)dict; + +- (NSDictionary *)toDictionary; + +//由外部设置通用的appid和appname ++ (void)setUniqueAppId:(NSString *)appId + appName:(NSString *)appName; + +@end diff --git a/Src/Main/Shared/Channel/ECOChannelAppInfo.m b/Src/Main/Shared/Channel/ECOChannelAppInfo.m new file mode 100644 index 0000000..2cd0c98 --- /dev/null +++ b/Src/Main/Shared/Channel/ECOChannelAppInfo.m @@ -0,0 +1,85 @@ +// +// ECOChannelAppInfo.m +// EchoSDK +// +// Created by 陈爱彬 on 2019/10/30. Maintain by 陈爱彬 +// Description +// + +#import "ECOChannelAppInfo.h" + +static NSString *_ecoUniqueAppId = nil; +static NSString *_ecoUniqueAppName = nil; + +@implementation ECOChannelAppInfo + +- (instancetype)init { + self = [super init]; + if (self) { + if (_ecoUniqueAppId) { + self.appId = _ecoUniqueAppId; + }else{ + self.appId = [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString*)kCFBundleIdentifierKey]; + } + if (_ecoUniqueAppName) { + self.appName = _ecoUniqueAppName; + }else{ + NSString *displayName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"]; + NSString *bundleName = [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString*)kCFBundleNameKey]; + self.appName = displayName ?: bundleName; + } + self.appShortVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"] ?: @""; + self.appVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"] ?: @""; +#if TARGET_OS_IPHONE + NSString *icon = [[[[NSBundle mainBundle] infoDictionary] valueForKeyPath:@"CFBundleIcons.CFBundlePrimaryIcon.CFBundleIconFiles"] lastObject]; + UIImage *appIcon = [UIImage imageNamed:icon]; + if (appIcon) { + NSData *iconData = UIImageJPEGRepresentation(appIcon, 1.0f); + NSString *encodedImageStr = [iconData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength]; + self.appIcon = encodedImageStr; + } +#endif + } + return self; +} + +- (instancetype)initWithDictionary:(NSDictionary *)dict { + self = [super init]; + if (self) { + self.appId = dict[@"appId"] ?: @""; + self.appName = dict[@"appName"] ?: @""; + self.appShortVersion = dict[@"sVer"] ?: @""; + self.appVersion = dict[@"ver"] ?: @""; + self.appIcon = dict[@"appIcon"] ?: @""; + } + return self; +} + +- (NSDictionary *)toDictionary { + return @{@"appId": self.appId ?: @"", + @"appName": self.appName ?: @"", + @"sVer": self.appShortVersion ?: @"", + @"ver": self.appVersion ?: @"", + @"appIcon": self.appIcon ?: @"" + }; +} + +- (BOOL)isEqual:(id)object { + if (![object isKindOfClass:[ECOChannelAppInfo class]]) { + return NO; + } + ECOChannelAppInfo *project = (ECOChannelAppInfo *)object; + if (![project.appId isEqual:self.appId]) { + return NO; + } + return YES; +} + +//由外部设置通用的appid和appname ++ (void)setUniqueAppId:(NSString *)appId + appName:(NSString *)appName { + _ecoUniqueAppId = appId; + _ecoUniqueAppName = appName; +} + +@end diff --git a/Src/Main/Shared/Channel/ECOChannelDeviceInfo.h b/Src/Main/Shared/Channel/ECOChannelDeviceInfo.h new file mode 100644 index 0000000..66ffb13 --- /dev/null +++ b/Src/Main/Shared/Channel/ECOChannelDeviceInfo.h @@ -0,0 +1,48 @@ +// +// ECOChannelDeviceInfo.h +// Echo +// +// Created by 陈爱彬 on 2019/4/17. Maintain by 陈爱彬 +// Description 设备信息 +// + +#import +#import "ECOChannelAppInfo.h" + +typedef NS_ENUM(NSInteger, ECODeviceType) { + ECODeviceType_Simulator = 0, //模拟器 + ECODeviceType_Device, //真机 + ECODeviceType_MacApp, //Mac客户端 +}; + +typedef NS_ENUM(NSInteger, ECOAuthorizeResponseType) { + ECOAuthorizeResponseType_Deny = 0, //拒绝连接 + ECOAuthorizeResponseType_AllowOnce, //允许本次 + ECOAuthorizeResponseType_AllowAlways, //始终允许 +}; + +@interface ECOChannelDeviceInfo : NSObject + +@property (nonatomic, copy) NSString *hostName; +@property (nonatomic, copy, readonly) NSString *ipAddress; +@property (nonatomic, copy, readonly) NSString *uuid; +@property (nonatomic, copy, readonly) NSString *deviceName; +@property (nonatomic, copy, readonly) NSString *systemVersion; +@property (nonatomic, copy, readonly) NSString *deviceModel; +@property (nonatomic, assign, readonly) ECODeviceType deviceType; +@property (nonatomic, assign) ECOAuthorizeResponseType authorizedType; +@property (nonatomic, assign) BOOL showAuthAlert; +@property (nonatomic, assign) BOOL isConnected; +@property (nonatomic, strong, readonly) ECOChannelAppInfo *appInfo; + +/** + 透传数据 + */ +@property (nonatomic, copy) NSDictionary *extraData; + +//解析网络信息传输过来的设备信息 +- (instancetype)initWithData:(NSData *)data; + +- (NSDictionary *)toJSONObject; + +@end diff --git a/Src/Main/Shared/Channel/ECOChannelDeviceInfo.m b/Src/Main/Shared/Channel/ECOChannelDeviceInfo.m new file mode 100644 index 0000000..b96bd3d --- /dev/null +++ b/Src/Main/Shared/Channel/ECOChannelDeviceInfo.m @@ -0,0 +1,205 @@ +// +// ECOChannelDeviceInfo.m +// Echo +// +// Created by 陈爱彬 on 2019/4/17. Maintain by 陈爱彬 +// Description +// + +#import "ECOChannelDeviceInfo.h" +#include +#include +#include +#include + +static NSInteger const ECOINET_ADDRSTRLEN = 16; +static NSInteger const ECOINET6_ADDRSTRLEN = 46; +static NSString *_macUUIDString = nil; + +@interface ECOChannelDeviceInfo() + +@property (nonatomic, readwrite) NSString *ipAddress; +@property (nonatomic, readwrite) NSString *uuid; +@property (nonatomic, readwrite) NSString *deviceName; +@property (nonatomic, readwrite) NSString *systemVersion; +@property (nonatomic, readwrite) NSString *deviceModel; +@property (nonatomic, readwrite) ECODeviceType deviceType; +@property (nonatomic, readwrite) ECOChannelAppInfo *appInfo; + +@end + +@implementation ECOChannelDeviceInfo + +//解析网络信息传输过来的设备信息 +- (instancetype)initWithData:(NSData *)data { + self = [super init]; + if (self) { + NSError *error = nil; + NSDictionary *deviceDict = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; + if (deviceDict) { + self.uuid = deviceDict[@"uuid"]; + self.ipAddress = deviceDict[@"ipAddress"]; + self.deviceName = deviceDict[@"deviceName"]; + self.deviceType = [deviceDict[@"deviceType"] integerValue]; + self.systemVersion = deviceDict[@"systemVersion"]; + self.deviceModel = deviceDict[@"model"]; + self.authorizedType = [deviceDict[@"authType"] integerValue]; + self.showAuthAlert = [deviceDict[@"showAuth"] boolValue]; + self.hostName = deviceDict[@"hostName"]; + + ECOChannelAppInfo *appInfo = [[ECOChannelAppInfo alloc] initWithDictionary:deviceDict[@"appInfo"]]; + self.appInfo = appInfo; + } + } + return self; +} + +- (id)copy { + ECOChannelDeviceInfo *deviceInfo = [ECOChannelDeviceInfo new]; + deviceInfo.hostName = self.hostName; + deviceInfo.ipAddress = self.ipAddress; + deviceInfo.uuid = self.uuid; + deviceInfo.deviceName = self.deviceName; + deviceInfo.systemVersion = self.systemVersion; + deviceInfo.deviceModel = self.deviceModel; + deviceInfo.deviceType = self.deviceType; + deviceInfo.authorizedType = self.authorizedType; + deviceInfo.showAuthAlert = self.showAuthAlert; + deviceInfo.appInfo = self.appInfo; + + return deviceInfo; +} + +- (instancetype)init { + self = [super init]; + if (self) { +#if TARGET_OS_OSX + [self setupMacDeviceInfo]; +#else + [self setupIOSDeviceInfo]; +#endif + //ipAddress + NSDictionary *addresses = [self getIPAddresses]; + //这里只取局域网的en0/ipv4地址,ipv6和pdp_ip0/ipv4暂时先忽略 + NSString *address = addresses[@"en0/ipv4"]; + self.ipAddress = address ?: @"0.0.0.0"; + } + return self; +} +- (void)setupMacDeviceInfo { + //uuid + if (!_macUUIDString) { + _macUUIDString = [[NSUUID UUID] UUIDString]; + } + self.uuid = _macUUIDString; + //设备 + self.deviceName = @"MacApp"; + self.deviceType = ECODeviceType_MacApp; +} +#if TARGET_OS_IPHONE +- (void)setupIOSDeviceInfo { + //设备名称 + UIDevice *device = [UIDevice currentDevice]; + self.deviceName = device.name; + self.deviceModel = device.model; + self.systemVersion = device.systemVersion; + //是否为模拟器 +#if TARGET_IPHONE_SIMULATOR + self.deviceType = ECODeviceType_Simulator; +#else + self.deviceType = ECODeviceType_Device; +#endif + //uuid + // self.uuid = [[NSUUID UUID] UUIDString]; + self.uuid = [[device identifierForVendor] UUIDString]; + + ECOChannelAppInfo *appInfo = [ECOChannelAppInfo new]; + self.appInfo = appInfo; +} +#endif +#pragma mark - IPAddress +//获取本机的ip地址表 +- (NSDictionary *)getIPAddresses { + NSMutableDictionary *addresses = [NSMutableDictionary dictionaryWithCapacity:8]; + + // retrieve the current interfaces - returns 0 on success + struct ifaddrs *interfaces; + if(!getifaddrs(&interfaces)) { + // Loop through linked list of interfaces + struct ifaddrs *interface; + for(interface=interfaces; interface; interface=interface->ifa_next) { + if(!(interface->ifa_flags & IFF_UP) /* || (interface->ifa_flags & IFF_LOOPBACK) */ ) { + continue; // deeply nested code harder to read + } + const struct sockaddr_in *addr = (const struct sockaddr_in*)interface->ifa_addr; + char addrBuf[ MAX(ECOINET_ADDRSTRLEN, ECOINET6_ADDRSTRLEN) ]; + if(addr && (addr->sin_family==AF_INET || addr->sin_family==AF_INET6)) { + NSString *name = [NSString stringWithUTF8String:interface->ifa_name]; + NSString *type; + if(addr->sin_family == AF_INET) { + if(inet_ntop(AF_INET, &addr->sin_addr, addrBuf, ECOINET_ADDRSTRLEN)) { + type = @"ipv4"; + } + } else { + const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6*)interface->ifa_addr; + if(inet_ntop(AF_INET6, &addr6->sin6_addr, addrBuf, ECOINET6_ADDRSTRLEN)) { + type = @"ipv6"; + } + } + if(type) { + NSString *key = [NSString stringWithFormat:@"%@/%@", name, type]; + addresses[key] = [NSString stringWithUTF8String:addrBuf]; + } + } + } + // Free memory + freeifaddrs(interfaces); + } + return [addresses count] ? addresses : nil; +} + +#pragma mark - Helper +- (NSDictionary *)toJSONObject { + NSMutableDictionary *json = [NSMutableDictionary dictionary]; + [json setValue:self.uuid ?: @"" forKey:@"uuid"]; + [json setValue:self.ipAddress ?: @"0.0.0.0" forKey:@"ipAddress"]; + [json setValue:self.deviceName ?: @"" forKey:@"deviceName"]; + [json setValue:self.deviceModel ?: @"" forKey:@"model"]; + [json setValue:@(self.deviceType) forKey:@"deviceType"]; + [json setValue:self.systemVersion ?: @"" forKey:@"systemVersion"]; + [json setValue:@(self.authorizedType) forKey:@"authType"]; + [json setValue:@(self.showAuthAlert) forKey:@"showAuth"]; + [json setValue:self.hostName ?: @"" forKey:@"hostName"]; + [json setValue:[self.appInfo toDictionary] forKey:@"appInfo"]; + + return [json copy]; +} +// 返回调试信息 +- (NSString *)description { + return [NSString stringWithFormat:@"%@", @{@"uuid": self.uuid ?: @"", + @"ipAddress": self.ipAddress ?: @"0.0.0.0", + @"deviceName": self.deviceName ?: @"", + @"deviceModel": self.deviceModel ?: @"", + @"deviceType": @(self.deviceType), + @"systemVersion": self.systemVersion ?: @"", + @"authType": @(self.authorizedType), + @"showAuth": @(self.showAuthAlert), + @"appId": self.appInfo.appId ?: @"", + @"appName": self.appInfo.appName ?: @"", + @"hostName": self.hostName ?: @"" + }]; +} + +- (BOOL)isEqual:(id)object { + if (![object isKindOfClass:[ECOChannelDeviceInfo class]]) { + return NO; + } + ECOChannelDeviceInfo *device = (ECOChannelDeviceInfo *)object; + if ([device.uuid isEqualToString:self.uuid] && + [device.appInfo.appId isEqualToString:self.appInfo.appId]) { + return YES; + } + return NO; +} + +@end diff --git a/Src/Main/Shared/Channel/ECOChannelManager.h b/Src/Main/Shared/Channel/ECOChannelManager.h new file mode 100644 index 0000000..f801d4a --- /dev/null +++ b/Src/Main/Shared/Channel/ECOChannelManager.h @@ -0,0 +1,69 @@ +// +// ECOChannelManager.h +// Echo +// +// Created by 陈爱彬 on 2019/4/16. Maintain by 陈爱彬 +// Description +// + +#import +#import "ECOChannelDeviceInfo.h" +#import "ECOUSBChannel.h" +#import "ECOSocketChannel.h" + +typedef void(^ECOChannelReceivedPacket)(ECOChannelDeviceInfo *device, NSData *data, NSDictionary *extraInfo); +typedef void(^ECOChannelDeviceConnectedStatusChanged)(ECOChannelDeviceInfo *device, BOOL isConnected); + +typedef void(^ECOChannelAuthStateChangedBlock)(ECOChannelDeviceInfo *device, ECOAuthorizeResponseType authState); +typedef void(^ECOChannelRequestAuthStateBlock)(ECOChannelDeviceInfo *device, ECOAuthorizeResponseType authState); + +@interface ECOChannelManager : NSObject + +/** + 接收到数据回调 + */ +@property (nonatomic, copy) ECOChannelReceivedPacket receivedBlock; + +/** + 设备连接变更 + */ +@property (nonatomic, copy) ECOChannelDeviceConnectedStatusChanged deviceBlock; + +/// 授权状态变更回调 +@property (nonatomic, copy) ECOChannelAuthStateChangedBlock authStateChangedBlock; + +/// 请求授权状态认证回调 +@property (nonatomic, copy) ECOChannelRequestAuthStateBlock requestAuthBlock; + +/// 设备白名单列表,记录始终允许的设备 +@property (nonatomic, strong, readonly) NSMutableArray *whitelistDevices; + +/** + 发送数据包 + + @param packet 数据包 + @param type 数据包类型,json或者普通数据包 + @param extraInfo 透传信息 + @param device 要接收消息的设备,如果传入nil,则对所有已授权连接的设备发送消息 + */ +- (void)sendPacket:(NSData *)packet +// type:(ECOPacketDataType)type + extraInfo:(NSDictionary *)extraInfo + toDevice:(ECOChannelDeviceInfo *)device; + +//发送授权数据 +- (void)sendAuthorizationMessageToDevice:(ECOChannelDeviceInfo *)device + state:(ECOAuthorizeResponseType)responseType + showAuthAlert:(BOOL)showAuthAlert; + +/** + 是否已连接到Mac客户端 + + @return 连接状态 + */ +- (BOOL)isConnected; + +//链接IP地址的主机 +- (void)connectToClientIPAddress:(NSString *)ipAddress; + +@end diff --git a/Src/Main/Shared/Channel/ECOChannelManager.m b/Src/Main/Shared/Channel/ECOChannelManager.m new file mode 100644 index 0000000..e11325d --- /dev/null +++ b/Src/Main/Shared/Channel/ECOChannelManager.m @@ -0,0 +1,116 @@ +// +// ECOChannelManager.m +// Echo +// +// Created by 陈爱彬 on 2019/4/16. Maintain by 陈爱彬 +// Description +// + +#import "ECOChannelManager.h" + +@interface ECOChannelManager() + + +@property (nonatomic, strong) ECOUSBChannel *ptChannel; +@property (nonatomic, strong) ECOSocketChannel *socketChannel; + +@end + +@implementation ECOChannelManager + +- (instancetype)init +{ + self = [super init]; + if (self) { + _socketChannel = [[ECOSocketChannel alloc] initWithDelegate:self]; + _ptChannel = [[ECOUSBChannel alloc] initWithDelegate:self]; +#if TARGET_OS_IPHONE + __weak typeof(self) weakSelf = self; + _ptChannel.attachBlock = ^(NSString *ipAddress) { + [weakSelf.socketChannel connectToIPAddress:ipAddress]; + }; +#endif + + } + return self; +} + +#pragma mark - 优先级管理 + +#pragma mark - 通道连接 +- (void)connectToClientIPAddress:(NSString *)ipAddress { + [self.socketChannel autoConnectToClientIPAddress:ipAddress]; +} +#pragma mark - 数据传输 +//发送数据 +- (void)sendPacket:(NSData *)packet +// type:(ECOPacketDataType)type + extraInfo:(NSDictionary *)extraInfo + toDevice:(ECOChannelDeviceInfo *)device { + if (!packet || + ![packet isKindOfClass:[NSData class]]) { + return; + } + [self.socketChannel sendPacket:packet extraInfo:extraInfo toDevice:device]; +} +//接收数据 +- (void)device:(ECOChannelDeviceInfo *)device receivePacket:(NSData *)packet extraInfo:(NSDictionary *)extraInfo { + if (!packet) { + return; + } + !self.receivedBlock ?: self.receivedBlock(device, packet, extraInfo); +} +//发送授权数据 +- (void)sendAuthorizationMessageToDevice:(ECOChannelDeviceInfo *)device + state:(ECOAuthorizeResponseType)responseType + showAuthAlert:(BOOL)showAuthAlert { + if (!device || + ![device isKindOfClass:[ECOChannelDeviceInfo class]]) { + return; + } + [self.socketChannel sendAuthorizationMessageToDevice:device state:responseType showAuthAlert:showAuthAlert]; +} +#pragma mark - 连接状态 +// 是否已连接到Mac客户端 +- (BOOL)isConnected { + BOOL isSocketConnected = [_socketChannel isConnected]; + return isSocketConnected; +} +#pragma mark - ECOChannelConnectedDeviceProtocol methods +- (void)channel:(ECOBaseChannel *)channel didConnectedToDevice:(ECOChannelDeviceInfo *)device { + NSLog(@">> [ECOChannelManager] did Connected to device:%@", [device description]); +// //状态回调 +// if (device.authorizedType != ECOAuthorizeResponseType_Deny) { +// !self.connectBlock ?: self.connectBlock([self isConnected]); +// } + //连接设备状态 + !self.deviceBlock ?: self.deviceBlock(device, YES); +} +- (void)channel:(ECOBaseChannel *)channel didDisconnectWithDevice:(ECOChannelDeviceInfo *)device { + NSLog(@">> [ECOChannelManager] did Disconnect to device:%@", [device description]); +// //状态回调 +// if (device.authorizedType != ECOAuthorizeResponseType_Deny) { +// !self.connectBlock ?: self.connectBlock([self isConnected]); +// } + //连接设备状态 + !self.deviceBlock ?: self.deviceBlock(device, NO); +} +- (void)channel:(ECOBaseChannel *)channel didReceivedDevice:(ECOChannelDeviceInfo *)device andData:(NSData *)data extraInfo:(NSDictionary *)extraInfo{ +// NSLog(@">> [ECOChannelManager] did Received data from device:%@", [device description]); + [self device:device receivePacket:data extraInfo:extraInfo]; +} + +- (void)channel:(ECOBaseChannel *)channel device:(ECOChannelDeviceInfo *)device didChangedAuthState:(ECOAuthorizeResponseType)authorizedType { + NSLog(@">> [ECOChannelManager] device did changed authState:%@", @(authorizedType)); + !self.authStateChangedBlock ?: self.authStateChangedBlock(device, authorizedType); +} +- (void)channel:(ECOBaseChannel *)channel device:(ECOChannelDeviceInfo *)device willRequestAuthState:(ECOAuthorizeResponseType)authorizedType { + NSLog(@">> [ECOChannelManager] device will request authState:%@", @(authorizedType)); + !self.requestAuthBlock ?: self.requestAuthBlock(device, authorizedType); +} + +- (NSMutableArray *)whitelistDevices { + return self.socketChannel.whitelistDevices; +} + +@end diff --git a/Src/Main/Shared/Channel/ECOSocketChannel.h b/Src/Main/Shared/Channel/ECOSocketChannel.h new file mode 100644 index 0000000..5811e21 --- /dev/null +++ b/Src/Main/Shared/Channel/ECOSocketChannel.h @@ -0,0 +1,60 @@ +// +// ECOSocketChannel.h +// Echo +// +// Created by 陈爱彬 on 2019/4/16. Maintain by 陈爱彬 +// Description +// + +#import "ECOBaseChannel.h" +//#import "ECOCoreDefines.h" + +typedef NS_ENUM(NSInteger, ECOSocketHeadTag) { + ECOSocketHeadTag_Data = 0, //普通数据 + ECOSocketHeadTag_Device = 1, //设备信息 + ECOSocketHeadTag_Authorization = 2, //授权连接 + ECOSocketHeadTag_ClientListen = 3, //Client监听Mac端的主动申请 +}; + +typedef NS_ENUM(NSInteger, ECOSocketDataTag) { + ECOSocketDataTag_Data = 10, //普通数据 + ECOSocketDataTag_Device = 11, //设备信息 + ECOSocketDataTag_Authorization = 12, //授权连接 + ECOSocketDataTag_ClientListen = 13, //Client监听Mac端的主动申请 +}; + +//版本号,版本不一致忽略该数据 +static const int ECOSocketProtocolVersion = 1; + +//包体主协议,1表示授权连接,2表示发送数据,3表示Client侧接收主动申请 +static const int ECOHeadMainType_Authorization = 1; +static const int ECOHeadMainType_Data = 2; +static const int ECOHeadMainType_ClientListen = 3; + +//包体子协议,0表示JSON数据,1表示普通NSData数据 +static const int ECOHeadSubType_JSON = 0; +static const int ECOHeadSubType_Data = 1; + +@interface ECOSocketChannel : ECOBaseChannel + +/// 设备白名单列表,记录始终允许的设备 +@property (nonatomic, strong) NSMutableArray *whitelistDevices; + +//连接到ip地址 +- (void)autoConnectToClientIPAddress:(NSString *)ipAddress; + +//Client侧连接Mac侧:连接到ip地址 +- (void)connectToIPAddress:(NSString *)ip; + +//发送数据 +- (void)sendPacket:(NSData *)packet +// type:(ECOPacketDataType)type + extraInfo:(NSDictionary *)extraInfo + toDevice:(ECOChannelDeviceInfo *)device; + +//发送授权数据 +- (void)sendAuthorizationMessageToDevice:(ECOChannelDeviceInfo *)device + state:(ECOAuthorizeResponseType)responseType + showAuthAlert:(BOOL)showAuthAlert; + +@end diff --git a/Src/Main/Shared/Channel/ECOSocketChannel.m b/Src/Main/Shared/Channel/ECOSocketChannel.m new file mode 100644 index 0000000..1980712 --- /dev/null +++ b/Src/Main/Shared/Channel/ECOSocketChannel.m @@ -0,0 +1,546 @@ +// +// ECOSocketChannel.m +// Echo +// +// Created by 陈爱彬 on 2019/4/16. Maintain by 陈爱彬 +// Description +// + +#import "ECOSocketChannel.h" +#import +#import +#include + +#import "ECONetServicePublisher.h" +#import "ECONetServiceBrowser.h" + +#import "LookinDefines.h" + +//static uint16_t const ECOClientSockeListenPortNumber = 23235; +//static uint16_t const ECOSocketAcceptPortNumber = 23234; +static CGFloat const ECOSocketRetryListenDelay = 1.f; +static NSInteger const ECOSocketHeadOffsetValue = 10; +static NSString *const ECHOAuthorizedDevicesKey = @"echoAuthorizedDevicesKey"; + +@interface ECOSocketChannel() + { + NSRecursiveLock *_socketLock; +} +@property (nonatomic, strong) NSMutableArray *sockets; +@property (nonatomic, strong) GCDAsyncSocket *mSocket; +@property (nonatomic, strong) ECONetServicePublisher *publisher; +@property (nonatomic, strong) ECONetServiceBrowser *browser; +//Client端主动连接的监听socket +@property (nonatomic, strong) GCDAsyncSocket *cSocket; +@property (nonatomic, strong) NSMutableArray *clientSockets; + +@end + +@implementation ECOSocketChannel + +- (void)dealloc { + NSLog(@"%s",__func__); +} +//初始化 +- (void)setupChannel { + [super setupChannel]; + _socketLock = [[NSRecursiveLock alloc] init]; + self.priority = ECOChannelPriority_Socket; + + //开启服务监听 +#if TARGET_OS_OSX + [self startListening]; +#else + [self setupClientListenSocket]; + [self.browser startBrowsing]; +#endif +} +//是否有socket连接 +- (BOOL)isConnected { + BOOL v = NO; + [self p_lock]; + if (self.connectType == ECOChannelConnectType_Authorization) { + for (GCDAsyncSocket *socket in self.sockets) { + ECOChannelDeviceInfo *deviceInfo = socket.userData; + if (deviceInfo.authorizedType != ECOAuthorizeResponseType_Deny) { + v = YES; + break; + } + } + }else{ + NSInteger count = [self.sockets count]; + v = count != 0; + } + [self p_unlock]; + return v; +} +//判断某个设备是否已连接 +- (BOOL)isEchoConnectedOfDevice:(ECOChannelDeviceInfo *)device { + BOOL v = NO; + [self p_lock]; + for (GCDAsyncSocket *socket in self.sockets) { + ECOChannelDeviceInfo *deviceInfo = socket.userData; + if ([device.ipAddress isEqualToString:deviceInfo.ipAddress]) { + v = YES; + break; + } + } + [self p_unlock]; + return v; +} +- (void)setupClientListenSocket { + self.cSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)]; + NSError *error = nil; + [self.cSocket acceptOnPort:LookinClientSockeListenPortNumber error:&error]; + if (error) { + NSLog(@"Socket Listen error:%@", error); + self.cSocket.delegate = nil; + self.cSocket = nil; + } +} +- (void)startListening { + [self p_lock]; + [self.sockets removeAllObjects]; + [self p_unlock]; + /* + https://developer.apple.com/library/archive/documentation/Networking/Conceptual/NSNetServiceProgGuide/Articles/PublishingServices.html + 创建listening socket for communication + */ + self.mSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)]; + NSError *error = nil; + BOOL isAccept = [self.mSocket acceptOnPort:LookinSocketAcceptPortNumber error:&error]; + if (error) { + NSLog(@"Socket Listen error:%@", error); + self.mSocket.delegate = nil; + self.mSocket = nil; + [self restartListening]; + return; + } + NSLog(@"Socket Listen:%@", isAccept ? @"Success" : @"Failure"); + //启动NetService + [self.publisher startPublish]; +} + +//重试监听 +- (void)restartListening { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(ECOSocketRetryListenDelay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [self startListening]; + }); +} +#pragma mark - Socket连接 +//Client侧连接Mac侧 +- (void)connectToAddresses:(NSArray *)addresses + hostName:(NSString *)hostName { + if (!addresses || ![addresses count]) { + return; + } + GCDAsyncSocket *socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)]; + NSData *address = [addresses objectAtIndex:0]; + NSError *error = nil; + BOOL connected = [socket connectToAddress:address error:&error]; + if (connected) { + [self p_lock]; + [self.sockets addObject:socket]; + [self p_unlock]; + //发送设备信息 + [self sendDeviceInfo:socket hostName:hostName]; + } + if (error) { + NSLog(@">> [ECOSocketChannel] connect to address failed:%@", error); + } +} +//Client侧连接Mac侧:连接到ip地址 +- (void)connectToIPAddress:(NSString *)ip { + if (!ip || ![ip length]) { + return; + } + GCDAsyncSocket *socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)]; + NSError *error = nil; + BOOL connected = [socket connectToHost:ip onPort:LookinSocketAcceptPortNumber error:&error]; + if (connected) { + [self p_lock]; + [self.sockets addObject:socket]; + [self p_unlock]; + //发送设备信息 + [self sendDeviceInfo:socket hostName:nil]; + } + if (error) { + NSLog(@">> [ECOSocketChannel] connect to address failed:%@", error); + } +} +//Mac侧连接Client侧 +- (void)autoConnectToClientIPAddress:(NSString *)ipAddress { + if (!ipAddress || ![ipAddress length]) { + return; + } + GCDAsyncSocket *socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)]; + NSError *error = nil; + BOOL connected = [socket connectToHost:ipAddress onPort:LookinClientSockeListenPortNumber error:&error]; + if (connected) { + [self.clientSockets addObject:socket]; + //发送本Mac侧的信息 + [self sendMacAutoConnectInfoInfo:socket]; + } + if (error) { + NSLog(@">> [ECOSocketChannel] autoConnectToClientIPAddress failed:%@", error); + } +} +#pragma mark - Socket通信 +//发送数据 +- (void)sendPacket:(NSData *)packet +// type:(ECOPacketDataType)type + extraInfo:(NSDictionary *)extraInfo + toDevice:(ECOChannelDeviceInfo *)device { + if (!packet) { + return; + } + + NSMutableData *buffer = [[NSMutableData alloc] init]; + NSMutableDictionary *header = [NSMutableDictionary dictionary]; + [header setValue:@(ECOSocketProtocolVersion) forKey:@"version"]; + [header setValue:@(ECOHeadMainType_Data) forKey:@"mType"]; +// [header setValue:@(type) forKey:@"sType"]; + [header setValue:@([packet length]) forKey:@"len"]; +// if (type == ECOPacketDataType_Data) { + [header setValue:extraInfo forKey:@"extra"]; +// } + NSData *headData = [NSJSONSerialization dataWithJSONObject:header options:0 error:nil]; + [buffer appendData:headData]; + [buffer appendData:[GCDAsyncSocket CRLFData]]; + [buffer appendBytes:[packet bytes] length:[packet length]]; + + [self p_lock]; + for (GCDAsyncSocket *socket in self.sockets) { + if (self.connectType == ECOChannelConnectType_Authorization) { + //授权机制 + ECOChannelDeviceInfo *deviceInfo = socket.userData; + if (deviceInfo.authorizedType != ECOAuthorizeResponseType_Deny) { + if (device != nil) { + //给单个设备发送消息 + if ([device isEqual:deviceInfo]) { + [socket writeData:buffer withTimeout:-1 tag:ECOSocketHeadTag_Data]; + } + }else{ + //给所有设备发送消息 + [socket writeData:buffer withTimeout:-1 tag:ECOSocketHeadTag_Data]; + } + } + }else{ + [socket writeData:buffer withTimeout:-1 tag:ECOSocketHeadTag_Data]; + } + } + [self p_unlock]; +} +//发送授权数据 +- (void)sendAuthorizationMessageToDevice:(ECOChannelDeviceInfo *)device + state:(ECOAuthorizeResponseType)responseType + showAuthAlert:(BOOL)showAuthAlert { + //修改数据 + ECOChannelDeviceInfo *authDevice = device; + device.showAuthAlert = showAuthAlert; +#if TARGET_OS_OSX + if (responseType != ECOAuthorizeResponseType_Deny) { + //允许需要等待对方确认,稍后修改device的内容 + authDevice = [device copy]; + } +#endif + authDevice.authorizedType = responseType; + NSError *error = nil; + NSData *packet = [NSJSONSerialization dataWithJSONObject:[authDevice toJSONObject] options:0 error:&error]; + if (error) { + NSLog(@">>[ECOCoreManager] send AuthorizationResponse failed:%@", error); + } + [self p_lock]; + for (GCDAsyncSocket *socket in self.sockets) { + ECOChannelDeviceInfo *deviceInfo = socket.userData; + if ([deviceInfo isEqual:authDevice]) { + NSMutableData *buffer = [[NSMutableData alloc] init]; + NSMutableDictionary *header = [NSMutableDictionary dictionary]; + [header setValue:@(ECOSocketProtocolVersion) forKey:@"version"]; + [header setValue:@(ECOHeadMainType_Authorization) forKey:@"mType"]; + [header setValue:@(ECOHeadSubType_JSON) forKey:@"sType"]; + [header setValue:@([packet length]) forKey:@"len"]; + NSData *headData = [NSJSONSerialization dataWithJSONObject:header options:0 error:nil]; + [buffer appendData:headData]; + [buffer appendData:[GCDAsyncSocket CRLFData]]; + [buffer appendBytes:[packet bytes] length:[packet length]]; + [socket writeData:buffer withTimeout:-1 tag:ECOSocketHeadTag_Data]; + } + } + [self p_unlock]; + +#if TARGET_OS_OSX + //授权连接回调,手动断开时主动回调,主动连接时等收到对方的消息再回调 + if (responseType == ECOAuthorizeResponseType_Deny) { + if ([self.delegate respondsToSelector:@selector(channel:device:didChangedAuthState:)]) { + [self.delegate channel:self device:device didChangedAuthState:NO]; + } + //修改授权白名单 + if (device.uuid.length > 0) { + NSString *uniId = [NSString stringWithFormat:@"%@_%@",device.uuid, device.appInfo.appId]; + if ([self.whitelistDevices containsObject:uniId]) { + [self.whitelistDevices removeObject:uniId]; + [self saveWhiteListDevices]; + } + } + } +#else + if ([self.delegate respondsToSelector:@selector(channel:device:didChangedAuthState:)]) { + [self.delegate channel:self device:device didChangedAuthState:responseType]; + } +#endif + +} +//发送设备信息 +- (void)sendDeviceInfo:(GCDAsyncSocket *)socket + hostName:(NSString *)hostName { + ECOChannelDeviceInfo *deviceInfo = [ECOChannelDeviceInfo new]; + deviceInfo.hostName = hostName; + NSData *packet = [NSJSONSerialization dataWithJSONObject:[deviceInfo toJSONObject] options:0 error:nil]; + if (!packet) { + return; + } + NSMutableData *buffer = [[NSMutableData alloc] init]; + NSMutableDictionary *header = [NSMutableDictionary dictionary]; + [header setValue:@(ECOSocketProtocolVersion) forKey:@"version"]; + [header setValue:@(ECOHeadMainType_Data) forKey:@"mType"]; + [header setValue:@(ECOHeadSubType_JSON) forKey:@"sType"]; + [header setValue:@([packet length]) forKey:@"len"]; + NSData *headData = [NSJSONSerialization dataWithJSONObject:header options:0 error:nil]; + [buffer appendData:headData]; + [buffer appendData:[GCDAsyncSocket CRLFData]]; + [buffer appendBytes:[packet bytes] length:[packet length]]; + [socket writeData:buffer withTimeout:-1 tag:ECOSocketHeadTag_Device]; +} +//发送给Client侧消息 +- (void)sendMacAutoConnectInfoInfo:(GCDAsyncSocket *)socket { + ECOChannelDeviceInfo *deviceInfo = [ECOChannelDeviceInfo new]; + NSData *packet = [NSJSONSerialization dataWithJSONObject:[deviceInfo toJSONObject] options:0 error:nil]; + if (!packet) { + return; + } + NSMutableData *buffer = [[NSMutableData alloc] init]; + NSMutableDictionary *header = [NSMutableDictionary dictionary]; + [header setValue:@(ECOSocketProtocolVersion) forKey:@"version"]; + [header setValue:@(ECOHeadMainType_ClientListen) forKey:@"mType"]; + [header setValue:@(ECOHeadSubType_JSON) forKey:@"sType"]; + [header setValue:@([packet length]) forKey:@"len"]; + NSData *headData = [NSJSONSerialization dataWithJSONObject:header options:0 error:nil]; + [buffer appendData:headData]; + [buffer appendData:[GCDAsyncSocket CRLFData]]; + [buffer appendBytes:[packet bytes] length:[packet length]]; + [socket writeData:buffer withTimeout:-1 tag:ECOSocketHeadTag_ClientListen]; +} +#pragma mark - GCDAsyncSocketDelegate methods +- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port { + // NSLog(@"%s",__func__); +#if TARGET_OS_IPHONE + //读取数据 + [sock readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1 tag:ECOSocketHeadTag_Device]; +#endif +} + +- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket { + // NSLog(@"%s",__func__); +#if TARGET_OS_OSX + [self p_lock]; + [self.sockets addObject:newSocket]; + newSocket.delegate = self; + [newSocket readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1 tag:ECOSocketHeadTag_Device]; + [self p_unlock]; + //发送设备信息 + [self sendDeviceInfo:newSocket hostName:nil]; +#else + [self.clientSockets addObject:newSocket]; + newSocket.delegate = self; + [newSocket readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1 tag:ECOSocketHeadTag_ClientListen]; +#endif +} + +- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag { +// NSLog(@"%s",__func__); + if (tag == ECOSocketHeadTag_Data || + tag == ECOSocketHeadTag_Device || + tag == ECOSocketHeadTag_ClientListen) { + NSData *headerData = [data subdataWithRange:NSMakeRange(0, data.length - [GCDAsyncSocket CRLFData].length)]; + NSDictionary *header = [NSJSONSerialization JSONObjectWithData:headerData options:NSJSONReadingAllowFragments error:nil]; + NSInteger version = [header[@"version"] integerValue]; + if (version < ECOSocketProtocolVersion) { + return; + } + NSInteger length = [header[@"len"] integerValue]; + NSInteger mainType = [header[@"mType"] integerValue]; + if (mainType == ECOHeadMainType_Authorization) { + [sock readDataToLength:length withTimeout:-1 tag:ECOSocketDataTag_Authorization]; + }else if(mainType == ECOHeadMainType_Data) { + ECOChannelDeviceInfo *deviceInfo = sock.userData; + NSDictionary *extraInfo = header[@"extra"]; + deviceInfo.extraData = extraInfo; + [sock readDataToLength:length withTimeout:-1 tag:tag + ECOSocketHeadOffsetValue]; + }else if (mainType == ECOHeadMainType_ClientListen) { + [sock readDataToLength:length withTimeout:-1 tag:tag + ECOSocketHeadOffsetValue]; + } + return; + } + if (tag == ECOSocketDataTag_Device) { + ECOChannelDeviceInfo *deviceInfo = [[ECOChannelDeviceInfo alloc] initWithData:data]; + sock.userData = deviceInfo; +#if TARGET_OS_OSX + NSString *uniId = [NSString stringWithFormat:@"%@_%@",deviceInfo.uuid, deviceInfo.appInfo.appId]; + if ([self.whitelistDevices containsObject:uniId]) { + //设置为已授权 + deviceInfo.authorizedType = ECOAuthorizeResponseType_AllowAlways; + [self sendAuthorizationMessageToDevice:deviceInfo state:ECOAuthorizeResponseType_AllowAlways showAuthAlert:NO]; + } +#endif + deviceInfo.isConnected = YES; + //连接到新设备 + if ([self.delegate respondsToSelector:@selector(channel:didConnectedToDevice:)]) { + [self.delegate channel:self didConnectedToDevice:deviceInfo]; + } + } + if (tag == ECOSocketDataTag_ClientListen) { + ECOChannelDeviceInfo *deviceInfo = [[ECOChannelDeviceInfo alloc] initWithData:data]; + BOOL isConnected = [self isEchoConnectedOfDevice:deviceInfo]; + if (!isConnected) { + [self connectToIPAddress:deviceInfo.ipAddress]; + }else{ + NSLog(@"当前Echo主机已连接:%@",deviceInfo.ipAddress); + } + [sock setDelegate:nil]; + [self.clientSockets removeObject:sock]; + } + if (tag == ECOSocketDataTag_Authorization) { + //授权信息 + ECOChannelDeviceInfo *deviceInfo = sock.userData; + ECOChannelDeviceInfo *tempDevice = [[ECOChannelDeviceInfo alloc] initWithData:data]; + BOOL isAlwaysAllow = tempDevice.authorizedType == ECOAuthorizeResponseType_AllowAlways; +#if TARGET_OS_IPHONE + deviceInfo.hostName = tempDevice.hostName; + if (tempDevice.showAuthAlert) { + //弹窗提示用户 + if ([self.delegate respondsToSelector:@selector(channel:device:willRequestAuthState:)]) { + [self.delegate channel:self device:deviceInfo willRequestAuthState:tempDevice.authorizedType]; + } + }else{ + deviceInfo.authorizedType = tempDevice.authorizedType; + //连接到新设备 + if ([self.delegate respondsToSelector:@selector(channel:device:didChangedAuthState:)]) { + [self.delegate channel:self device:deviceInfo didChangedAuthState:tempDevice.authorizedType]; + } + } +#endif +#if TARGET_OS_OSX + //修改设备的授权状态 + deviceInfo.authorizedType = tempDevice.authorizedType; + //重置标记位 + deviceInfo.showAuthAlert = NO; + //始终允许,加入白名单 + if (deviceInfo.uuid.length > 0) { + NSString *uniId = [NSString stringWithFormat:@"%@_%@",deviceInfo.uuid, deviceInfo.appInfo.appId]; + if (isAlwaysAllow && ![self.whitelistDevices containsObject:uniId]) { + [self.whitelistDevices addObject:uniId]; + [self saveWhiteListDevices]; + }else if (!isAlwaysAllow && [self.whitelistDevices containsObject:uniId]){ + [self.whitelistDevices removeObject:uniId]; + [self saveWhiteListDevices]; + } + } + //回调 + if ([self.delegate respondsToSelector:@selector(channel:device:didChangedAuthState:)]) { + [self.delegate channel:self device:deviceInfo didChangedAuthState:tempDevice.authorizedType]; + } +#endif + } + //传递数据给上层 + if (tag == ECOSocketDataTag_Data) { + if ([self.delegate respondsToSelector:@selector(channel:didReceivedDevice:andData:extraInfo:)]) { + ECOChannelDeviceInfo *deviceInfo = sock.userData; + if (deviceInfo.authorizedType != ECOAuthorizeResponseType_Deny) { + //未授权的通信忽略 + [self.delegate channel:self didReceivedDevice:deviceInfo andData:data extraInfo:deviceInfo.extraData]; + } + } + } + //读取下次发送的数据 + [sock readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1 tag:ECOSocketHeadTag_Data]; +} +- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(nullable NSError *)err { +// NSLog(@"%s",__func__); + [self p_lock]; + [sock setDelegate:nil]; + if ([self.sockets containsObject:sock]) { + if ([self.delegate respondsToSelector:@selector(channel:didDisconnectWithDevice:)]) { + ECOChannelDeviceInfo *deviceInfo = sock.userData; + deviceInfo.isConnected = NO; + [self.delegate channel:self didDisconnectWithDevice:deviceInfo]; + } + [self.sockets removeObject:sock]; + } + if ([self.clientSockets containsObject:sock]) { + [self.clientSockets removeObject:sock]; + } + [self p_unlock]; + //重启监听 + if (sock == self.mSocket) { + [self restartListening]; + } +} +#pragma mark - helper +- (void)p_lock { + [_socketLock lock]; +} +- (void)p_unlock { + [_socketLock unlock]; +} +- (void)saveWhiteListDevices { + NSArray *list = [self.whitelistDevices copy]; + [[NSUserDefaults standardUserDefaults] setObject:list forKey:ECHOAuthorizedDevicesKey]; + [[NSUserDefaults standardUserDefaults] synchronize]; +} +#pragma mark - getters +- (NSMutableArray *)sockets { + if (!_sockets) { + _sockets = [[NSMutableArray alloc] initWithCapacity:0]; + } + return _sockets; +} +- (NSMutableArray *)clientSockets { + if (!_clientSockets) { + _clientSockets = [[NSMutableArray alloc] initWithCapacity:0]; + } + return _clientSockets; +} + +#if TARGET_OS_OSX +- (ECONetServicePublisher *)publisher { + if (!_publisher) { + _publisher = [[ECONetServicePublisher alloc] init]; + } + return _publisher; +} +#endif + + +#if TARGET_OS_IPHONE +- (ECONetServiceBrowser *)browser { + if (!_browser) { + _browser = [[ECONetServiceBrowser alloc] init]; + __weak typeof(self) weakSelf = self; + _browser.addressesBlock = ^(NSArray *addresses, NSString *hostName) { + __strong typeof(weakSelf) strongSelf = weakSelf; + [strongSelf connectToAddresses:addresses hostName:hostName]; + }; + } + return _browser; +} +#endif + +- (NSMutableArray *)whitelistDevices { + if (!_whitelistDevices) { + NSArray *list = [[NSUserDefaults standardUserDefaults] objectForKey:ECHOAuthorizedDevicesKey]; + _whitelistDevices = [NSMutableArray arrayWithArray:list ?: @[]]; + } + return _whitelistDevices; +} +@end diff --git a/Src/Main/Shared/Channel/ECOUSBChannel.h b/Src/Main/Shared/Channel/ECOUSBChannel.h new file mode 100644 index 0000000..ca79458 --- /dev/null +++ b/Src/Main/Shared/Channel/ECOUSBChannel.h @@ -0,0 +1,29 @@ +// +// ECOUSBChannel.h +// Echo +// +// Created by 陈爱彬 on 2019/4/16. Maintain by 陈爱彬 +// Description +// + +#import "ECOBaseChannel.h" + +typedef NS_ENUM(NSInteger, ECOUSBChannelType) { + ECOUSBChannelType_Command = 100, + ECOUSBChannelType_TextMessage = 101, + ECOUSBChannelType_Ping = 102, + ECOUSBChannelType_Pong = 103, +}; + +typedef struct _ECOUSBChannelTextFrame { + uint32_t length; + uint8_t utf8text[0]; +} ECOUSBChannelTextFrame; + +typedef void(^ECOUSBChannelDidAttachBlock)(NSString *ipAddress); + +@interface ECOUSBChannel : ECOBaseChannel + +@property (nonatomic, copy) ECOUSBChannelDidAttachBlock attachBlock; + +@end diff --git a/Src/Main/Shared/Channel/ECOUSBChannel.m b/Src/Main/Shared/Channel/ECOUSBChannel.m new file mode 100644 index 0000000..9e59b75 --- /dev/null +++ b/Src/Main/Shared/Channel/ECOUSBChannel.m @@ -0,0 +1,325 @@ +// +// ECOUSBChannel.m +// Echo +// +// Created by 陈爱彬 on 2019/4/16. Maintain by 陈爱彬 +// Description +// + +#import "ECOUSBChannel.h" +#import "Lookin_PTChannel.h" +#import "ECOChannelDeviceInfo.h" + +#if TARGET_OS_OSX +#import +#endif + +static const int ECOUSBChannelIPv4PortNumber = 2333; +static const NSTimeInterval ECOUSBChannelReconnectDelay = 1.0; + +@interface ECOUSBChannel() + { + dispatch_queue_t _notConnectedQueue; +} + +@property (nonatomic, weak) Lookin_PTChannel *serverChannel; +@property (nonatomic, weak) Lookin_PTChannel *peerChannel; + +@property (nonatomic, strong) NSNumber *connectingToDeviceID; +@property (nonatomic, strong) NSNumber *connectedDeviceID; +@property (nonatomic, strong) NSDictionary *connectedDeviceProperties; +@property (nonatomic, strong) Lookin_PTChannel *connectedChannel; + +@end + +@implementation ECOUSBChannel + +#pragma mark - LifeCycle methods +- (void)dealloc { + NSLog(@"%s",__func__); + if (self.serverChannel) { + [self.serverChannel close]; + } + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} +//初始化 +- (void)setupChannel { + [super setupChannel]; + self.priority = ECOChannelPriority_USB; + +#if TARGET_OS_OSX + _notConnectedQueue = dispatch_queue_create("com.echo.notConnectedQueue", DISPATCH_QUEUE_SERIAL); + // Start listening for device attached/detached notifications + [self startListeningForDevices]; + // Start trying to connect to local IPv4 port (defined in PTExampleProtocol.h) + [self enqueueConnectToLocalIPv4Port]; + + [self ping]; +#else + [self setupPTChannel]; +#endif +} +//是否有usb连接 +- (BOOL)isConnected { +#if TARGET_OS_OSX + return self.connectedDeviceID != nil; +#else + return self.peerChannel != nil; +#endif +} +//创建channel +- (void)setupPTChannel { + Lookin_PTChannel *channel = [Lookin_PTChannel channelWithDelegate:self]; + [channel listenOnPort:ECOUSBChannelIPv4PortNumber IPv4Address:INADDR_LOOPBACK callback:^(NSError *error) { + if (error) { + NSLog(@">> [ECOUSBChannel] Failed to listen on 127.0.0.1:%d: %@", ECOUSBChannelIPv4PortNumber, error); + }else{ + NSLog(@">> [ECOUSBChannel] Listening on 127.0.0.1:%d", ECOUSBChannelIPv4PortNumber); + self.serverChannel = channel; + } + }]; +} + +#pragma mark - Wired device connections +- (void)startListeningForDevices { + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center addObserver:self selector:@selector(onUSBDeviceDidAttach:) name:Lookin_PTUSBDeviceDidAttachNotification object:Lookin_PTUSBHub.sharedHub]; + [center addObserver:self selector:@selector(onUSBDeviceDidDetach:) name:Lookin_PTUSBDeviceDidDetachNotification object:Lookin_PTUSBHub.sharedHub]; +} +- (void)onUSBDeviceDidAttach:(NSNotification *)note { + NSNumber *deviceID = [note.userInfo objectForKey:@"DeviceID"]; + NSLog(@"<< [ECOUSBChannel] PTUSBDeviceDidAttachNotification:%@", deviceID); + // [self showAlertWithMessage:[NSString stringWithFormat:@"usb设备连接:%@",deviceID]]; + + dispatch_async(_notConnectedQueue, ^{ + if (!self.connectingToDeviceID || + ![deviceID isEqualToNumber:self.connectingToDeviceID]) { + [self disconnectFromCurrentChannel]; + self.connectingToDeviceID = deviceID; + self.connectedDeviceProperties = [note.userInfo objectForKey:@"Properties"]; + [self enqueueConnectToUSBDevice]; + } + }); +} +- (void)onUSBDeviceDidDetach:(NSNotification *)note { + NSNumber *deviceID = [note.userInfo objectForKey:@"DeviceID"]; + NSLog(@"<< [ECOUSBChannel] PTUSBDeviceDidDetachNotification:%@", deviceID); + // [self showAlertWithMessage:[NSString stringWithFormat:@"usb设备断开:%@",deviceID]]; + + if ([self.connectingToDeviceID isEqualToNumber:deviceID]) { + self.connectedDeviceProperties = nil; + self.connectingToDeviceID = nil; + if (self.connectedChannel) { + [self.connectedChannel close]; + } + } +} + +- (void)enqueueConnectToLocalIPv4Port { + dispatch_async(_notConnectedQueue, ^{ + dispatch_async(dispatch_get_main_queue(), ^{ + [self connectToLocalIPv4Port]; + }); + }); +} + +- (void)connectToLocalIPv4Port { + Lookin_PTChannel *channel = [Lookin_PTChannel channelWithDelegate:self]; + channel.userInfo = [NSString stringWithFormat:@"127.0.0.1:%d", ECOUSBChannelIPv4PortNumber]; + [channel connectToPort:ECOUSBChannelIPv4PortNumber IPv4Address:INADDR_LOOPBACK callback:^(NSError *error, Lookin_PTAddress *address) { + if (error) { + if (error.domain == NSPOSIXErrorDomain && (error.code == ECONNREFUSED || error.code == ETIMEDOUT)) { + // this is an expected state + }else{ + NSLog(@"<< [ECOUSBChannel] Failed to connect to 127.0.0.1:%d: %@", ECOUSBChannelIPv4PortNumber, error); + } + [self performSelector:@selector(enqueueConnectToLocalIPv4Port) withObject:nil afterDelay:ECOUSBChannelReconnectDelay]; + }else{ + [self disconnectFromCurrentChannel]; + self.connectedChannel = channel; + channel.userInfo = address; + NSLog(@"<< [ECOUSBChannel] Connected to %@", address); + } + }]; +} + +- (void)enqueueConnectToUSBDevice { + dispatch_async(_notConnectedQueue, ^{ + dispatch_async(dispatch_get_main_queue(), ^{ + [self connectToUSBDevice]; + }); + }); +} + +- (void)connectToUSBDevice { + Lookin_PTChannel *channel = [Lookin_PTChannel channelWithDelegate:self]; + channel.userInfo = self.connectingToDeviceID; + channel.delegate = self; + [channel connectToPort:ECOUSBChannelIPv4PortNumber overUSBHub:Lookin_PTUSBHub.sharedHub deviceID:self.connectingToDeviceID callback:^(NSError *error) { + if (error) { + if (error.domain == Lookin_PTUSBHubErrorDomain && error.code == PTUSBHubErrorConnectionRefused) { + // NSLog(@"<< [ECOUSBChannel] Failed to connect to device #%@: %@", channel.userInfo, error); + }else{ + // NSLog(@"<< [ECOUSBChannel] Failed to connect to device #%@: %@", channel.userInfo, error); + } + if (channel.userInfo == self.connectingToDeviceID) { + //重试连接 + [self performSelector:@selector(enqueueConnectToUSBDevice) withObject:nil afterDelay:ECOUSBChannelReconnectDelay]; + } + }else{ + self.connectedDeviceID = self.connectingToDeviceID; + self.connectedChannel = channel; + NSLog(@"<< [ECOUSBChannel] Connect to device #%@\n%@", channel.userInfo, self.connectedDeviceProperties); + //发送ping信息 + [self ping]; + } + }]; +} + +- (void)disconnectFromCurrentChannel { + if (self.connectedDeviceID && self.connectedChannel) { + [self.connectedChannel close]; + self.connectedChannel = nil; + } +} + +- (void)didDisconnectFromDevice:(NSNumber*)deviceID { + NSLog(@"<< [ECOUSBChannel] Disconnected from device:%@", deviceID); + if ([self.connectedDeviceID isEqualToNumber:deviceID]) { + [self willChangeValueForKey:@"connectedDeviceID"]; + self.connectedDeviceID = nil; + [self didChangeValueForKey:@"connectedDeviceID"]; + } +} + +#pragma mark - Send Messages +- (void)ping { + if (!self.connectedChannel) { + [self performSelector:@selector(ping) withObject:nil afterDelay:1.f]; + return; + } + uint32_t tagno = [self.connectedChannel.protocol newTag]; + ECOChannelDeviceInfo *deviceInfo = [ECOChannelDeviceInfo new]; + dispatch_data_t payload = ECOUSBChannelDispatchDataWithPayload(deviceInfo.ipAddress); + [self.connectedChannel sendFrameOfType:ECOUSBChannelType_Ping tag:tagno withPayload:payload callback:^(NSError *error) { + // [self performSelector:@selector(ping) withObject:nil afterDelay:1.f]; + }]; +} +- (void)sendMessage:(NSString *)message { +#if TARGET_OS_OSX + if (self.connectedChannel) { + [self.connectedChannel sendFrameOfType:ECOUSBChannelType_TextMessage tag:PTFrameNoTag withPayload:ECOUSBChannelDispatchDataWithPayload(message) callback:^(NSError *error) { + if (error) { + NSLog(@">> [ECOUSBChannel] Failed to send message: %@", error); + } + NSLog(@">> [ECOUSBChannel] you: %@", message); + }]; + } +#else + if (self.peerChannel) { + [self.peerChannel sendFrameOfType:ECOUSBChannelType_TextMessage tag:PTFrameNoTag withPayload:ECOUSBChannelDispatchDataWithPayload(message) callback:^(NSError *error) { + if (error) { + NSLog(@">> [ECOUSBChannel] Failed to send message: %@", error); + } + NSLog(@">> [ECOUSBChannel] you: %@", message); + }]; + } +#endif +} +#pragma mark - dispatch_data_t +static dispatch_data_t ECOUSBChannelDispatchDataWithPayload(id payload) { + if ([payload isKindOfClass:[NSString class]]) { + //字符串 + NSString *message = (NSString *)payload; + const char *utf8text = [message cStringUsingEncoding:NSUTF8StringEncoding]; + size_t length = strlen(utf8text); + ECOUSBChannelTextFrame *textFrame = CFAllocatorAllocate(nil, sizeof(ECOUSBChannelTextFrame) + length, 0); + memcpy(textFrame->utf8text, utf8text, length); + textFrame->length = htonl(length); + + return dispatch_data_create((const void*)textFrame, sizeof(ECOUSBChannelTextFrame) + length, nil, ^{ + CFAllocatorDeallocate(nil, textFrame); + }); + }else if ([payload isKindOfClass:[NSDictionary class]]) { + //字典 + NSDictionary *info = (NSDictionary *)payload; + return [info createReferencingDispatchData]; + } + return nil; +} +#pragma mark - PTChannelDelegate methods +// Invoked to accept an incoming frame on a channel. Reply NO ignore the +// incoming frame. If not implemented by the delegate, all frames are accepted. +- (BOOL)ioFrameChannel:(Lookin_PTChannel*)channel shouldAcceptFrameOfType:(uint32_t)type tag:(uint32_t)tag payloadSize:(uint32_t)payloadSize { +// NSLog(@"%s",__func__); +#if TARGET_OS_OSX + if (channel != self.peerChannel) { + // A previous channel that has been canceled but not yet ended. Ignore. + return NO; + } +#endif + return YES; +} + +// Invoked when a new frame has arrived on a channel. +- (void)ioFrameChannel:(Lookin_PTChannel*)channel didReceiveFrameOfType:(uint32_t)type tag:(uint32_t)tag payload:(Lookin_PTData*)payload { +// NSLog(@"%s",__func__); + if (type == ECOUSBChannelType_TextMessage) { + ECOUSBChannelTextFrame *textFrame = (ECOUSBChannelTextFrame *)payload.data; + textFrame->length = ntohl(textFrame->length); + NSString *message = [[NSString alloc] initWithBytes:textFrame->utf8text length:textFrame->length encoding:NSUTF8StringEncoding]; +// NSLog(@">> [ECOUSBChannel] [%@]: %@", channel.userInfo, message); + // 测试:自动会话 + [self sendMessage:message]; + }else if (type == ECOUSBChannelType_Ping) { +// NSLog(@">> [ECOUSBChannel] received ping: %@", channel.userInfo); + ECOUSBChannelTextFrame *textFrame = (ECOUSBChannelTextFrame *)payload.data; + textFrame->length = ntohl(textFrame->length); + NSString *ipAddress = [[NSString alloc] initWithBytes:textFrame->utf8text length:textFrame->length encoding:NSUTF8StringEncoding]; +// NSLog(@">> [ECOUSBChannel] [%@]: %@", channel.userInfo, ipAddress); + !self.attachBlock ?: self.attachBlock(ipAddress); + +// if (self.peerChannel) { +// [self.peerChannel sendFrameOfType:ECOUSBChannelType_Pong tag:tag withPayload:nil callback:nil]; +// } + }else if (type == ECOUSBChannelType_Pong) { +// NSLog(@">> [ECOUSBChannel] receive pong: %@", channel.userInfo); + } +} + +// Invoked when the channel closed. If it closed because of an error, *error* is +// a non-nil NSError object. +- (void)ioFrameChannel:(Lookin_PTChannel*)channel didEndWithError:(NSError*)error { +// NSLog(@"%s",__func__); + if (error) { +// NSLog(@">> [ECOUSBChannel] %@ end with error:%@", channel, error); + }else{ +// NSLog(@">> [ECOUSBChannel] Disconnected from %@", channel.userInfo); + } +#if TARGET_OS_OSX + if (self.connectedDeviceID && [self.connectedDeviceID isEqualToNumber:channel.userInfo]) { + [self didDisconnectFromDevice:self.connectedDeviceID]; + } + if (self.connectedChannel == channel) { +// NSLog(@">> [ECOUSBChannel] Disconnected from: %@", channel.userInfo); + self.connectedChannel = nil; + } +#endif +} + +// For listening channels, this method is invoked when a new connection has been +// accepted. +- (void)ioFrameChannel:(Lookin_PTChannel*)channel didAcceptConnection:(Lookin_PTChannel*)otherChannel fromAddress:(Lookin_PTAddress*)address { +// NSLog(@"%s",__func__); + if (self.peerChannel) { + [self.peerChannel cancel]; + } + + self.peerChannel = otherChannel; + self.peerChannel.userInfo = address; +// NSLog(@">> [ECOUSBChannel] Connected to %@", address); + //测试,回复消息 +// [self sendMessage:@"你好"]; +} + +@end diff --git a/Src/Main/Shared/LookinAppInfo.h b/Src/Main/Shared/LookinAppInfo.h index 118edf9..ff71ebf 100644 --- a/Src/Main/Shared/LookinAppInfo.h +++ b/Src/Main/Shared/LookinAppInfo.h @@ -48,6 +48,8 @@ typedef NS_ENUM(NSInteger, LookinAppInfoDevice) { @property(nonatomic, assign) double screenHeight; /// 是几倍的屏幕 @property(nonatomic, assign) double screenScale; +/// 是否为无线连接 +@property(nonatomic, assign) BOOL isWireless; - (BOOL)isEqualToAppInfo:(LookinAppInfo *)info; diff --git a/Src/Main/Shared/LookinAppInfo.m b/Src/Main/Shared/LookinAppInfo.m index 0101b37..55bc704 100644 --- a/Src/Main/Shared/LookinAppInfo.m +++ b/Src/Main/Shared/LookinAppInfo.m @@ -11,6 +11,9 @@ #import "LookinAppInfo.h" +#if TARGET_OS_IPHONE +#import "LKS_ConnectionManager.h" +#endif static NSString * const CodingKey_AppIcon = @"1"; static NSString * const CodingKey_Screenshot = @"2"; @@ -35,6 +38,7 @@ - (id)copyWithZone:(NSZone *)zone { newAppInfo.screenHeight = self.screenHeight; newAppInfo.screenScale = self.screenScale; newAppInfo.appInfoIdentifier = self.appInfoIdentifier; + newAppInfo.isWireless = self.isWireless; return newAppInfo; } @@ -60,6 +64,7 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder { self.screenScale = [aDecoder decodeDoubleForKey:@"screenScale"]; self.appInfoIdentifier = [aDecoder decodeIntegerForKey:@"appInfoIdentifier"]; self.shouldUseCache = [aDecoder decodeBoolForKey:@"shouldUseCache"]; + self.isWireless = [aDecoder decodeBoolForKey:@"isWireless"]; } return self; } @@ -92,6 +97,7 @@ - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeDouble:self.screenScale forKey:@"screenScale"]; [aCoder encodeInteger:self.appInfoIdentifier forKey:@"appInfoIdentifier"]; [aCoder encodeBool:self.shouldUseCache forKey:@"shouldUseCache"]; + [aCoder encodeBool:self.isWireless forKey:@"isWireless"]; } + (BOOL)supportsSecureCoding { @@ -165,6 +171,10 @@ + (LookinAppInfo *)currentInfoWithScreenshot:(BOOL)hasScreenshot icon:(BOOL)hasI if (hasIcon) { info.appIcon = [self appIcon]; } + info.isWireless = LKS_ConnectionManager.sharedInstance.isWirelessConnnect; + if (info.isWireless) { + info.deviceDescription = [NSString stringWithFormat:@"ᯤ %@", info.deviceDescription]; + } return info; } diff --git a/Src/Main/Shared/LookinDefines.h b/Src/Main/Shared/LookinDefines.h index 2524eb4..a5a2060 100644 --- a/Src/Main/Shared/LookinDefines.h +++ b/Src/Main/Shared/LookinDefines.h @@ -49,6 +49,15 @@ static const int LookinUSBDeviceIPv4PortNumberEnd = 47179; static const int LookinSimulatorIPv4PortNumberStart = 47164; static const int LookinSimulatorIPv4PortNumberEnd = 47169; +static const int LookinClientSockeListenPortNumber = 47180; +static const int LookinSocketAcceptPortNumber = 47181; +static const int LookinNetServicePortNumber = LookinSocketAcceptPortNumber; + +static NSString *const LookinNetServiceDomain = @""; +static NSString *const LookinNetServiceType = @"_Lookin._tcp"; +static NSString *const LookinNetServiceName = @""; +static NSTimeInterval const LookinNetServiceResolveAddressTimeout = 30; + enum { /// 确认两端是否可以响应通讯 LookinRequestTypePing = 200, From f33b8b403de69616e2dc0e3303713c1ce6ebce5d Mon Sep 17 00:00:00 2001 From: mlch911 Date: Thu, 27 Jul 2023 12:42:18 +0800 Subject: [PATCH 02/10] Fix Bug: Wireless Connection --- Src/Main/Server/Connection/LKS_ConnectionManager.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Src/Main/Server/Connection/LKS_ConnectionManager.m b/Src/Main/Server/Connection/LKS_ConnectionManager.m index b488b6c..4f5381f 100644 --- a/Src/Main/Server/Connection/LKS_ConnectionManager.m +++ b/Src/Main/Server/Connection/LKS_ConnectionManager.m @@ -104,6 +104,9 @@ - (void)startWirelessConnection { // 授权状态变更回调 self.wirelessChannel.authStateChangedBlock = ^(ECOChannelDeviceInfo *device, ECOAuthorizeResponseType authState) { NSLog(@"🚀 Lookin authStateChangedBlock device:%@ authState:%ld", device, authState); + if (authState == ECOAuthorizeResponseType_AllowAlways) { + weakSelf.wirelessDevice = device; + } }; // 请求授权状态认证回调 self.wirelessChannel.requestAuthBlock = ^(ECOChannelDeviceInfo *device, ECOAuthorizeResponseType authState) { From 6000fe1f70520ff0a576d985ba64edef073d515d Mon Sep 17 00:00:00 2001 From: mlch911 Date: Mon, 25 Sep 2023 20:16:26 +0800 Subject: [PATCH 03/10] Add Wireless subspec --- LookinServer.podspec | 12 +++++++++--- LookinShared.podspec | 12 ++++++++++-- Src/Main/Server/Connection/LKS_ConnectionManager.h | 4 ++++ Src/Main/Server/Connection/LKS_ConnectionManager.m | 12 ++++++++++++ Src/Main/Shared/LookinAppInfo.h | 2 ++ Src/Main/Shared/LookinAppInfo.m | 8 ++++++++ Src/Main/Shared/LookinDefines.h | 2 ++ 7 files changed, 47 insertions(+), 5 deletions(-) diff --git a/LookinServer.podspec b/LookinServer.podspec index bda89f6..d9100f2 100644 --- a/LookinServer.podspec +++ b/LookinServer.podspec @@ -13,10 +13,9 @@ Pod::Spec.new do |spec| spec.framework = "UIKit" spec.requires_arc = true - spec.dependency 'CocoaAsyncSocket' - spec.subspec 'Core' do |ss| ss.source_files = ['Src/Main/**/*', 'Src/Base/**/*'] + ss.exclude_files = 'Src/Main/Shared/Channel/**/*' ss.pod_target_xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) SHOULD_COMPILE_LOOKIN_SERVER=1', 'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => '$(inherited) SHOULD_COMPILE_LOOKIN_SERVER' @@ -38,7 +37,7 @@ Pod::Spec.new do |spec| 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) LOOKIN_SERVER_DISABLE_HOOK=1', } end - + # CocoaPods 不支持多个 subspecs 和 configurations 并列 # "pod 'LookinServer', :subspecs => ['Swift', 'NoHook'], :configurations => ['Debug']" is not supported by CocoaPods # https://github.com/QMUI/LookinServer/issues/134 @@ -51,4 +50,11 @@ Pod::Spec.new do |spec| } end + spec.subspec 'Wireless' do |ss| + ss.dependency 'LookinShared/Wireless' + ss.pod_target_xcconfig = { + 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) LOOKIN_SERVER_WIRELESS=1' + } + end + end diff --git a/LookinShared.podspec b/LookinShared.podspec index b83c4b9..0be1ef1 100644 --- a/LookinShared.podspec +++ b/LookinShared.podspec @@ -16,10 +16,18 @@ Pod::Spec.new do |spec| 'Src/Main/Shared/**/*', 'Src/Base/**/*' ] - - spec.dependency 'CocoaAsyncSocket' + spec.exclude_files = 'Src/Main/Shared/Channel/**/*' + spec.default_subspec = :none spec.pod_target_xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) SHOULD_COMPILE_LOOKIN_SERVER=1' } + + spec.subspec 'Wireless' do |ss| + ss.source_files = 'Src/Main/Shared/Channel/**/*' + ss.dependency 'CocoaAsyncSocket' + ss.pod_target_xcconfig = { + 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) LOOKIN_SERVER_WIRELESS=1' + } + end end diff --git a/Src/Main/Server/Connection/LKS_ConnectionManager.h b/Src/Main/Server/Connection/LKS_ConnectionManager.h index fc71cf0..e8af56c 100644 --- a/Src/Main/Server/Connection/LKS_ConnectionManager.h +++ b/Src/Main/Server/Connection/LKS_ConnectionManager.h @@ -20,13 +20,17 @@ extern NSString *const LKS_ConnectionDidEndNotificationName; @property(nonatomic, assign) BOOL applicationIsActive; +#if LOOKIN_SERVER_WIRELESS - (void)startWirelessConnection; - (void)endWirelessConnection; +#endif - (BOOL)isConnected; +#if LOOKIN_SERVER_WIRELESS - (BOOL)isWirelessConnnect; +#endif - (void)respond:(LookinConnectionResponseAttachment *)data requestType:(uint32_t)requestType tag:(uint32_t)tag; diff --git a/Src/Main/Server/Connection/LKS_ConnectionManager.m b/Src/Main/Server/Connection/LKS_ConnectionManager.m index 5107721..c70b5d2 100644 --- a/Src/Main/Server/Connection/LKS_ConnectionManager.m +++ b/Src/Main/Server/Connection/LKS_ConnectionManager.m @@ -18,7 +18,9 @@ #import "LookinServerDefines.h" #import "ECOChannelManager.h" +#if LOOKIN_SERVER_WIRELESS @import CocoaAsyncSocket; +#endif NSString *const LKS_ConnectionDidEndNotificationName = @"LKS_ConnectionDidEndNotificationName"; @@ -60,8 +62,10 @@ - (instancetype)init { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleLocalInspectIn2D:) name:@"Lookin_2D" object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleLocalInspectIn3D:) name:@"Lookin_3D" object:nil]; +#if LOOKIN_SERVER_WIRELESS [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(startWirelessConnection) name:@"Lookin_startWirelessConnection" object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(endWirelessConnection) name:@"Lookin_endWirelessConnection" object:nil]; +#endif [[NSNotificationCenter defaultCenter] addObserverForName:@"Lookin_Export" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) { [[LKS_ExportManager sharedInstance] exportAndShare]; }]; @@ -71,6 +75,7 @@ - (instancetype)init { return self; } +#if LOOKIN_SERVER_WIRELESS - (void)startWirelessConnection { self.hasStartWirelessConnnection = YES; if (!self.wirelessChannel) { @@ -148,6 +153,7 @@ - (void)endWirelessConnection { } self.wirelessChannel = nil; } +#endif - (void)_handleWillResignActiveNotification { self.applicationIsActive = NO; @@ -218,12 +224,18 @@ - (void)dealloc { } - (BOOL)isConnected { +#if LOOKIN_SERVER_WIRELESS return self.isWirelessConnnect || (self.peerChannel_ && self.peerChannel_.isConnected); +#else + return self.peerChannel_ && self.peerChannel_.isConnected; +#endif } +#if LOOKIN_SERVER_WIRELESS - (BOOL)isWirelessConnnect { return self.wirelessChannel.isConnected; } +#endif - (void)respond:(LookinConnectionResponseAttachment *)data requestType:(uint32_t)requestType tag:(uint32_t)tag { [self _sendData:data frameOfType:requestType tag:tag]; diff --git a/Src/Main/Shared/LookinAppInfo.h b/Src/Main/Shared/LookinAppInfo.h index ff71ebf..28a9085 100644 --- a/Src/Main/Shared/LookinAppInfo.h +++ b/Src/Main/Shared/LookinAppInfo.h @@ -48,8 +48,10 @@ typedef NS_ENUM(NSInteger, LookinAppInfoDevice) { @property(nonatomic, assign) double screenHeight; /// 是几倍的屏幕 @property(nonatomic, assign) double screenScale; +#if LOOKIN_SERVER_WIRELESS /// 是否为无线连接 @property(nonatomic, assign) BOOL isWireless; +#endif - (BOOL)isEqualToAppInfo:(LookinAppInfo *)info; diff --git a/Src/Main/Shared/LookinAppInfo.m b/Src/Main/Shared/LookinAppInfo.m index 0e877e7..2e6f36d 100644 --- a/Src/Main/Shared/LookinAppInfo.m +++ b/Src/Main/Shared/LookinAppInfo.m @@ -38,7 +38,9 @@ - (id)copyWithZone:(NSZone *)zone { newAppInfo.screenHeight = self.screenHeight; newAppInfo.screenScale = self.screenScale; newAppInfo.appInfoIdentifier = self.appInfoIdentifier; +#if LOOKIN_SERVER_WIRELESS newAppInfo.isWireless = self.isWireless; +#endif return newAppInfo; } @@ -64,7 +66,9 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder { self.screenScale = [aDecoder decodeDoubleForKey:@"screenScale"]; self.appInfoIdentifier = [aDecoder decodeIntegerForKey:@"appInfoIdentifier"]; self.shouldUseCache = [aDecoder decodeBoolForKey:@"shouldUseCache"]; +#if LOOKIN_SERVER_WIRELESS self.isWireless = [aDecoder decodeBoolForKey:@"isWireless"]; +#endif } return self; } @@ -97,7 +101,9 @@ - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeDouble:self.screenScale forKey:@"screenScale"]; [aCoder encodeInteger:self.appInfoIdentifier forKey:@"appInfoIdentifier"]; [aCoder encodeBool:self.shouldUseCache forKey:@"shouldUseCache"]; +#if LOOKIN_SERVER_WIRELESS [aCoder encodeBool:self.isWireless forKey:@"isWireless"]; +#endif } + (BOOL)supportsSecureCoding { @@ -171,10 +177,12 @@ + (LookinAppInfo *)currentInfoWithScreenshot:(BOOL)hasScreenshot icon:(BOOL)hasI if (hasIcon) { info.appIcon = [self appIcon]; } +#if LOOKIN_SERVER_WIRELESS info.isWireless = LKS_ConnectionManager.sharedInstance.isWirelessConnnect; if (info.isWireless) { info.deviceDescription = [NSString stringWithFormat:@"ᯤ %@", info.deviceDescription]; } +#endif return info; } diff --git a/Src/Main/Shared/LookinDefines.h b/Src/Main/Shared/LookinDefines.h index d05f8cf..625e470 100644 --- a/Src/Main/Shared/LookinDefines.h +++ b/Src/Main/Shared/LookinDefines.h @@ -49,6 +49,7 @@ static const int LookinUSBDeviceIPv4PortNumberEnd = 47179; static const int LookinSimulatorIPv4PortNumberStart = 47164; static const int LookinSimulatorIPv4PortNumberEnd = 47169; +#if LOOKIN_SERVER_WIRELESS static const int LookinClientSockeListenPortNumber = 47180; static const int LookinSocketAcceptPortNumber = 47181; static const int LookinNetServicePortNumber = LookinSocketAcceptPortNumber; @@ -57,6 +58,7 @@ static NSString *const LookinNetServiceDomain = @""; static NSString *const LookinNetServiceType = @"_Lookin._tcp"; static NSString *const LookinNetServiceName = @""; static NSTimeInterval const LookinNetServiceResolveAddressTimeout = 30; +#endif enum { /// 确认两端是否可以响应通讯 From 5cb0ee75cc71f84aa45f9f2c41d38d28a6ab5881 Mon Sep 17 00:00:00 2001 From: mlch911 Date: Tue, 26 Sep 2023 11:44:09 +0800 Subject: [PATCH 04/10] Update README.md --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index d93eaf3..5e7f746 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,21 @@ Never integrate LookinServer in Release building configuration. `pod 'LookinServer', :subspecs => ['Swift'], :configurations => ['Debug']` ### Objective-C Project `pod 'LookinServer', :configurations => ['Debug']` + +### [Optianal] Wireless Connection +`pod 'LookinServer/Wireless', :configurations => ['Debug']` + +And you need to add follow content to Info.plist. The name of `NSBonjourServices` **MUST** be `_Lookin._tcp`. + +```plist +NSLocalNetworkUsageDescription +Local Network Usage Description +NSBonjourServices + + _Lookin._tcp + +``` + ## via Swift Package Manager: `https://github.com/QMUI/LookinServer/` From 6f526ddcc771db2ad2226f2817b797c0eb3ded6e Mon Sep 17 00:00:00 2001 From: mlch911 Date: Mon, 13 Nov 2023 14:13:50 +0800 Subject: [PATCH 05/10] Update podspec --- LookinServer.podspec | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/LookinServer.podspec b/LookinServer.podspec index 8f20a4e..81c97a2 100644 --- a/LookinServer.podspec +++ b/LookinServer.podspec @@ -50,7 +50,19 @@ Pod::Spec.new do |spec| } end + spec.subspec 'SwiftAndWireless' do |ss| + ss.dependency 'LookinShared/Wireless' + ss.dependency 'LookinServer/Core' + ss.source_files = 'Src/Swift/**/*' + ss.pod_target_xcconfig = { + 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) LOOKIN_SERVER_SWIFT_ENABLED=1 LOOKIN_SERVER_DISABLE_HOOK=1', + 'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => '$(inherited) LOOKIN_SERVER_SWIFT_ENABLED', + 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) LOOKIN_SERVER_WIRELESS=1' + } + end + spec.subspec 'Wireless' do |ss| + ss.dependency 'LookinServer/Core' ss.dependency 'LookinShared/Wireless' ss.pod_target_xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) LOOKIN_SERVER_WIRELESS=1' From 1d7a5ca905a4d5fb1327288538add0e7da963969 Mon Sep 17 00:00:00 2001 From: mlch911 Date: Thu, 14 Mar 2024 14:17:40 +0800 Subject: [PATCH 06/10] Fix Bug: podspec --- LookinServer.podspec | 3 +-- LookinShared.podspec | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/LookinServer.podspec b/LookinServer.podspec index 10a9e86..500d9e9 100644 --- a/LookinServer.podspec +++ b/LookinServer.podspec @@ -55,9 +55,8 @@ Pod::Spec.new do |spec| ss.dependency 'LookinServer/Core' ss.source_files = 'Src/Swift/**/*' ss.pod_target_xcconfig = { - 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) LOOKIN_SERVER_SWIFT_ENABLED=1 LOOKIN_SERVER_DISABLE_HOOK=1', + 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) LOOKIN_SERVER_SWIFT_ENABLED=1 LOOKIN_SERVER_DISABLE_HOOK=1 LOOKIN_SERVER_WIRELESS=1', 'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => '$(inherited) LOOKIN_SERVER_SWIFT_ENABLED', - 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) LOOKIN_SERVER_WIRELESS=1' } end diff --git a/LookinShared.podspec b/LookinShared.podspec index e86dc55..453e970 100644 --- a/LookinShared.podspec +++ b/LookinShared.podspec @@ -27,7 +27,7 @@ Pod::Spec.new do |spec| ss.source_files = 'Src/Main/Shared/Channel/**/*' ss.dependency 'CocoaAsyncSocket' ss.pod_target_xcconfig = { - 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) LOOKIN_SERVER_WIRELESS=1' + 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) SHOULD_COMPILE_LOOKIN_SERVER=1 LOOKIN_SERVER_WIRELESS=1' } end end From 8dabc32120f10d0244b5fcd4d6265862be6c4a6c Mon Sep 17 00:00:00 2001 From: mlch911 Date: Fri, 23 Aug 2024 15:34:55 +0800 Subject: [PATCH 07/10] =?UTF-8?q?=E6=94=AF=E6=8C=81=E6=97=A0=E7=BA=BF?= =?UTF-8?q?=E8=BF=9E=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LookinDemo/OC_Pod/LookinDemoOC/AppDelegate.m | 3 ++ LookinDemo/OC_Pod/LookinDemoOC/Info.plist | 4 ++ .../Server/Connection/LKS_ConnectionManager.h | 4 +- .../Server/Connection/LKS_ConnectionManager.m | 38 +++++++++++-------- .../Server/Connection/LKS_RequestHandler.h | 2 + .../Server/Connection/LKS_RequestHandler.m | 26 ++++++++----- .../Shared/Channel/ECOChannelDeviceInfo.h | 1 + .../Shared/Channel/ECOChannelDeviceInfo.m | 6 ++- 8 files changed, 56 insertions(+), 28 deletions(-) diff --git a/LookinDemo/OC_Pod/LookinDemoOC/AppDelegate.m b/LookinDemo/OC_Pod/LookinDemoOC/AppDelegate.m index 48c9c2f..d958b23 100644 --- a/LookinDemo/OC_Pod/LookinDemoOC/AppDelegate.m +++ b/LookinDemo/OC_Pod/LookinDemoOC/AppDelegate.m @@ -16,6 +16,9 @@ @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. +#if !SIMULATOR + [NSNotificationCenter.defaultCenter postNotificationName:@"Lookin_startWirelessConnection" object:nil]; +#endif return YES; } diff --git a/LookinDemo/OC_Pod/LookinDemoOC/Info.plist b/LookinDemo/OC_Pod/LookinDemoOC/Info.plist index 81ed29b..7d1fc3e 100644 --- a/LookinDemo/OC_Pod/LookinDemoOC/Info.plist +++ b/LookinDemo/OC_Pod/LookinDemoOC/Info.plist @@ -2,6 +2,10 @@ + NSBonjourServices + + _Lookin._tcp + UIApplicationSceneManifest UIApplicationSupportsMultipleScenes diff --git a/Src/Main/Server/Connection/LKS_ConnectionManager.h b/Src/Main/Server/Connection/LKS_ConnectionManager.h index e8af56c..2937484 100644 --- a/Src/Main/Server/Connection/LKS_ConnectionManager.h +++ b/Src/Main/Server/Connection/LKS_ConnectionManager.h @@ -32,9 +32,9 @@ extern NSString *const LKS_ConnectionDidEndNotificationName; - (BOOL)isWirelessConnnect; #endif -- (void)respond:(LookinConnectionResponseAttachment *)data requestType:(uint32_t)requestType tag:(uint32_t)tag; +- (void)respond:(LookinConnectionResponseAttachment *)data requestType:(uint32_t)requestType tag:(uint32_t)tag isWireless:(BOOL)isWireless; -- (void)pushData:(NSObject *)data type:(uint32_t)type; +- (void)pushData:(NSObject *)data type:(uint32_t)type isWireless:(BOOL)isWireless; @end diff --git a/Src/Main/Server/Connection/LKS_ConnectionManager.m b/Src/Main/Server/Connection/LKS_ConnectionManager.m index 1afb98c..de6deac 100644 --- a/Src/Main/Server/Connection/LKS_ConnectionManager.m +++ b/Src/Main/Server/Connection/LKS_ConnectionManager.m @@ -29,6 +29,7 @@ @interface LKS_ConnectionManager () @property(nonatomic, weak) Lookin_PTChannel *peerChannel_; @property(nonatomic, strong) LKS_RequestHandler *requestHandler; +@property(nonatomic, strong) LKS_RequestHandler *wirelessRequestHandler; @property(nonatomic, strong) ECOChannelManager *wirelessChannel; @property(nonatomic, strong) ECOChannelDeviceInfo *wirelessDevice; @@ -78,6 +79,7 @@ - (instancetype)init { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleGetLookinInfo:) name:@"GetLookinInfo" object:nil]; self.requestHandler = [LKS_RequestHandler new]; + self.wirelessRequestHandler = LKS_RequestHandler.wireless; } return self; } @@ -103,7 +105,7 @@ - (void)startWirelessConnection { object = unarchivedObject; } dispatch_async(dispatch_get_main_queue(), ^{ - [weakSelf.requestHandler handleRequestType:type.intValue tag:tag.intValue object:object]; + [weakSelf.wirelessRequestHandler handleRequestType:type.intValue tag:tag.intValue object:object]; }); }; // 设备连接变更 @@ -264,26 +266,30 @@ - (BOOL)isWirelessConnnect { } #endif -- (void)respond:(LookinConnectionResponseAttachment *)data requestType:(uint32_t)requestType tag:(uint32_t)tag { - [self _sendData:data frameOfType:requestType tag:tag]; +- (void)respond:(LookinConnectionResponseAttachment *)data requestType:(uint32_t)requestType tag:(uint32_t)tag isWireless:(BOOL)isWireless { + [self _sendData:data frameOfType:requestType tag:tag isWireless:isWireless]; } -- (void)pushData:(NSObject *)data type:(uint32_t)type { - [self _sendData:data frameOfType:type tag:0]; +- (void)pushData:(NSObject *)data type:(uint32_t)type isWireless:(BOOL)isWireless { + [self _sendData:data frameOfType:type tag:0 isWireless:isWireless]; } -- (void)_sendData:(NSObject *)data frameOfType:(uint32_t)frameOfType tag:(uint32_t)tag { +- (void)_sendData:(NSObject *)data frameOfType:(uint32_t)frameOfType tag:(uint32_t)tag isWireless:(BOOL)isWireless { NSData *archivedData = [NSKeyedArchiver archivedDataWithRootObject:data]; - if (self.peerChannel_) { - dispatch_data_t payload = [archivedData createReferencingDispatchData]; - - [self.peerChannel_ sendFrameOfType:frameOfType tag:tag withPayload:payload callback:^(NSError *error) { - if (error) { - } - }]; - } else if (self.wirelessDevice.isConnected) { - [self.wirelessChannel sendPacket:archivedData extraInfo:@{@"tag": @(tag), @"type": @(frameOfType)} toDevice:self.wirelessDevice]; - } + if (isWireless) { + if (self.wirelessDevice.isConnected) { + [self.wirelessChannel sendPacket:archivedData extraInfo:@{@"tag": @(tag), @"type": @(frameOfType)} toDevice:self.wirelessDevice]; + } + } else { + if (self.peerChannel_) { + dispatch_data_t payload = [archivedData createReferencingDispatchData]; + + [self.peerChannel_ sendFrameOfType:frameOfType tag:tag withPayload:payload callback:^(NSError *error) { + if (error) { + } + }]; + } + } } #pragma mark - Lookin_PTChannelDelegate diff --git a/Src/Main/Server/Connection/LKS_RequestHandler.h b/Src/Main/Server/Connection/LKS_RequestHandler.h index 52ebe71..e05345b 100644 --- a/Src/Main/Server/Connection/LKS_RequestHandler.h +++ b/Src/Main/Server/Connection/LKS_RequestHandler.h @@ -12,6 +12,8 @@ @interface LKS_RequestHandler : NSObject ++ (instancetype)wireless; + - (BOOL)canHandleRequestType:(uint32_t)requestType; - (void)handleRequestType:(uint32_t)requestType tag:(uint32_t)tag object:(id)object; diff --git a/Src/Main/Server/Connection/LKS_RequestHandler.m b/Src/Main/Server/Connection/LKS_RequestHandler.m index c9d1572..f1be19c 100644 --- a/Src/Main/Server/Connection/LKS_RequestHandler.m +++ b/Src/Main/Server/Connection/LKS_RequestHandler.m @@ -31,6 +31,8 @@ @interface LKS_RequestHandler () @property(nonatomic, strong) NSMutableSet *activeDetailHandlers; +@property(nonatomic, assign) BOOL isWireless; + @end @implementation LKS_RequestHandler { @@ -60,6 +62,12 @@ - (instancetype)init { return self; } ++ (instancetype)wireless { + LKS_RequestHandler *handler = self.new; + handler.isWireless = YES; + return handler; +} + - (BOOL)canHandleRequestType:(uint32_t)requestType { if ([_validRequestTypes containsObject:@(requestType)]) { return YES; @@ -74,7 +82,7 @@ - (void)handleRequestType:(uint32_t)requestType tag:(uint32_t)tag object:(id)obj if (![LKS_ConnectionManager sharedInstance].applicationIsActive) { responseAttachment.appIsInBackground = YES; } - [[LKS_ConnectionManager sharedInstance] respond:responseAttachment requestType:requestType tag:tag]; + [[LKS_ConnectionManager sharedInstance] respond:responseAttachment requestType:requestType tag:tag isWireless:self.isWireless]; } else if (requestType == LookinRequestTypeApp) { // 请求可用设备信息 @@ -90,7 +98,7 @@ - (void)handleRequestType:(uint32_t)requestType tag:(uint32_t)tag object:(id)obj LookinConnectionResponseAttachment *responseAttachment = [LookinConnectionResponseAttachment new]; responseAttachment.data = appInfo; - [[LKS_ConnectionManager sharedInstance] respond:responseAttachment requestType:requestType tag:tag]; + [[LKS_ConnectionManager sharedInstance] respond:responseAttachment requestType:requestType tag:tag isWireless:self.isWireless]; } else if (requestType == LookinRequestTypeHierarchy) { // 从 LookinClient 1.0.4 开始有这个参数,之前是 nil @@ -105,7 +113,7 @@ - (void)handleRequestType:(uint32_t)requestType tag:(uint32_t)tag object:(id)obj LookinConnectionResponseAttachment *responseAttachment = [LookinConnectionResponseAttachment new]; responseAttachment.data = [LookinHierarchyInfo staticInfoWithLookinVersion:clientVersion]; - [[LKS_ConnectionManager sharedInstance] respond:responseAttachment requestType:requestType tag:tag]; + [[LKS_ConnectionManager sharedInstance] respond:responseAttachment requestType:requestType tag:tag isWireless:self.isWireless]; } else if (requestType == LookinRequestTypeInbuiltAttrModification) { // 请求修改某个属性 @@ -116,7 +124,7 @@ - (void)handleRequestType:(uint32_t)requestType tag:(uint32_t)tag object:(id)obj } else { attachment.data = data; } - [[LKS_ConnectionManager sharedInstance] respond:attachment requestType:requestType tag:tag]; + [[LKS_ConnectionManager sharedInstance] respond:attachment requestType:requestType tag:tag isWireless:self.isWireless]; }]; } else if (requestType == LookinRequestTypeCustomAttrModification) { @@ -135,7 +143,7 @@ - (void)handleRequestType:(uint32_t)requestType tag:(uint32_t)tag object:(id)obj attrAttachment.data = data; attrAttachment.dataTotalCount = dataTotalCount; attrAttachment.currentDataCount = 1; - [[LKS_ConnectionManager sharedInstance] respond:attrAttachment requestType:LookinRequestTypeAttrModificationPatch tag:tag]; + [[LKS_ConnectionManager sharedInstance] respond:attrAttachment requestType:LookinRequestTypeAttrModificationPatch tag:tag isWireless:self.isWireless]; }]; } else if (requestType == LookinRequestTypeHierarchyDetails) { @@ -153,7 +161,7 @@ - (void)handleRequestType:(uint32_t)requestType tag:(uint32_t)tag object:(id)obj attachment.data = details; attachment.dataTotalCount = responsesDataTotalCount; attachment.currentDataCount = details.count; - [[LKS_ConnectionManager sharedInstance] respond:attachment requestType:LookinRequestTypeHierarchyDetails tag:tag]; + [[LKS_ConnectionManager sharedInstance] respond:attachment requestType:LookinRequestTypeHierarchyDetails tag:tag isWireless:self.isWireless]; } finishedBlock:^{ [self.activeDetailHandlers removeObject:handler]; @@ -166,7 +174,7 @@ - (void)handleRequestType:(uint32_t)requestType tag:(uint32_t)tag object:(id)obj LookinConnectionResponseAttachment *attach = [LookinConnectionResponseAttachment new]; attach.data = lookinObj; - [[LKS_ConnectionManager sharedInstance] respond:attach requestType:requestType tag:tag]; + [[LKS_ConnectionManager sharedInstance] respond:attach requestType:requestType tag:tag isWireless:self.isWireless]; } else if (requestType == LookinRequestTypeAllAttrGroups) { unsigned long oid = ((NSNumber *)object).unsignedLongValue; @@ -544,13 +552,13 @@ - (void)_handleInvokeWithObject:(NSObject *)obj selector:(SEL)selector resultDes - (void)_submitResponseWithError:(NSError *)error requestType:(uint32_t)requestType tag:(uint32_t)tag { LookinConnectionResponseAttachment *attachment = [LookinConnectionResponseAttachment new]; attachment.error = error; - [[LKS_ConnectionManager sharedInstance] respond:attachment requestType:requestType tag:tag]; + [[LKS_ConnectionManager sharedInstance] respond:attachment requestType:requestType tag:tag isWireless:self.isWireless]; } - (void)_submitResponseWithData:(NSObject *)data requestType:(uint32_t)requestType tag:(uint32_t)tag { LookinConnectionResponseAttachment *attachment = [LookinConnectionResponseAttachment new]; attachment.data = data; - [[LKS_ConnectionManager sharedInstance] respond:attachment requestType:requestType tag:tag]; + [[LKS_ConnectionManager sharedInstance] respond:attachment requestType:requestType tag:tag isWireless:self.isWireless]; } @end diff --git a/Src/Main/Shared/Channel/ECOChannelDeviceInfo.h b/Src/Main/Shared/Channel/ECOChannelDeviceInfo.h index 66ffb13..8d21f4c 100644 --- a/Src/Main/Shared/Channel/ECOChannelDeviceInfo.h +++ b/Src/Main/Shared/Channel/ECOChannelDeviceInfo.h @@ -12,6 +12,7 @@ typedef NS_ENUM(NSInteger, ECODeviceType) { ECODeviceType_Simulator = 0, //模拟器 ECODeviceType_Device, //真机 + ECODeviceType_iPad_Device, //iPad真机 ECODeviceType_MacApp, //Mac客户端 }; diff --git a/Src/Main/Shared/Channel/ECOChannelDeviceInfo.m b/Src/Main/Shared/Channel/ECOChannelDeviceInfo.m index b96bd3d..f503ef1 100644 --- a/Src/Main/Shared/Channel/ECOChannelDeviceInfo.m +++ b/Src/Main/Shared/Channel/ECOChannelDeviceInfo.m @@ -107,7 +107,11 @@ - (void)setupIOSDeviceInfo { #if TARGET_IPHONE_SIMULATOR self.deviceType = ECODeviceType_Simulator; #else - self.deviceType = ECODeviceType_Device; + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + self.deviceType = ECODeviceType_iPad_Device; + } else { + self.deviceType = ECODeviceType_Device; + } #endif //uuid // self.uuid = [[NSUUID UUID] UUIDString]; From eff8a851816f320deb69fa25d7a2565c62bef8fc Mon Sep 17 00:00:00 2001 From: mlch911 Date: Fri, 23 Aug 2024 15:59:36 +0800 Subject: [PATCH 08/10] Update README.md --- README.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4bd027b..b0d275a 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,8 @@ And you need to add follow content to Info.plist. The name of `NSBonjourServices _Lookin._tcp ``` +Also, the wireless ability does not start automatically, you need to send a notification to turn it on. And you can also send a notification to turn it off.
+Notification Names: `Lookin_startWirelessConnection` and `Lookin_endWirelessConnection` ## via Swift Package Manager: `https://github.com/QMUI/LookinServer/` @@ -59,7 +61,7 @@ Lookin 可以查看与修改 iOS App 里的 UI 对象,类似于 Xcode 自带 如果这是你的 iOS 项目第一次使用 Lookin,则需要先把 LookinServer 这款 iOS Framework 集成到你的 iOS 项目中。 > **Warning** -> +> > 1. 不要在 AppStore 模式下集成 LookinServer。 > 2. 不要使用早于 1.0.6 的版本,因为它包含一个严重 Bug,可能导致线上事故: https://qxh1ndiez2w.feishu.cn/wiki/Z9SpwT7zWiqvYvkBe7Lc6Disnab ## 通过 CocoaPods: @@ -68,6 +70,21 @@ Lookin 可以查看与修改 iOS App 里的 UI 对象,类似于 Xcode 自带 `pod 'LookinServer', :subspecs => ['Swift'], :configurations => ['Debug']` ### Objective-C 项目 `pod 'LookinServer', :configurations => ['Debug']` +### [可选] 无线连接功能 +`pod 'LookinServer/Wireless', :configurations => ['Debug']` + +你需要将下面的内容添加进你的`Info.plist`。 `NSBonjourServices`的值**必须是**`_Lookin._tcp`。 + +```plist +NSLocalNetworkUsageDescription +Local Network Usage Description +NSBonjourServices + + _Lookin._tcp + +``` +并且,无线功能不会自动启动,需要你发送通知来开启该功能,也支持发送通知来关闭它。
+通知名称`Lookin_startWirelessConnection`和`Lookin_endWirelessConnection` ## 通过 Swift Package Manager: `https://github.com/QMUI/LookinServer/` From 3266602e829c0444f3f053d19ed6221dd547a1ed Mon Sep 17 00:00:00 2001 From: mlch911 Date: Fri, 23 Aug 2024 16:24:28 +0800 Subject: [PATCH 09/10] =?UTF-8?q?Update:=20=E6=A8=A1=E6=8B=9F=E5=99=A8?= =?UTF-8?q?=E4=B8=8D=E5=BA=94=E8=AF=A5=E5=90=AF=E5=8A=A8=E6=97=A0=E7=BA=BF?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LookinDemo/OC_Pod/LookinDemoOC/AppDelegate.m | 2 +- Src/Main/Server/Connection/LKS_ConnectionManager.m | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/LookinDemo/OC_Pod/LookinDemoOC/AppDelegate.m b/LookinDemo/OC_Pod/LookinDemoOC/AppDelegate.m index d958b23..1714603 100644 --- a/LookinDemo/OC_Pod/LookinDemoOC/AppDelegate.m +++ b/LookinDemo/OC_Pod/LookinDemoOC/AppDelegate.m @@ -16,7 +16,7 @@ @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. -#if !SIMULATOR +#if !TARGET_OS_SIMULATOR [NSNotificationCenter.defaultCenter postNotificationName:@"Lookin_startWirelessConnection" object:nil]; #endif return YES; diff --git a/Src/Main/Server/Connection/LKS_ConnectionManager.m b/Src/Main/Server/Connection/LKS_ConnectionManager.m index de6deac..2bc15ad 100644 --- a/Src/Main/Server/Connection/LKS_ConnectionManager.m +++ b/Src/Main/Server/Connection/LKS_ConnectionManager.m @@ -86,6 +86,10 @@ - (instancetype)init { #if LOOKIN_SERVER_WIRELESS - (void)startWirelessConnection { +#if TARGET_OS_SIMULATOR + NSLog(@"LookinServer - warning: you should not start Wireless Connection on Simulator. We wouldn't start it."); + return; +#endif self.hasStartWirelessConnnection = YES; if (!self.wirelessChannel) { #if TARGET_OS_IPHONE From 92f398bcee84d7292027c000640081cb12b3d761 Mon Sep 17 00:00:00 2001 From: mlch911 Date: Tue, 3 Feb 2026 16:21:58 +0800 Subject: [PATCH 10/10] fix SPM --- Package.swift | 11 ++++++++-- Src/Main/Shared/Channel/ECOChannelAppInfo.m | 4 +++- .../Shared/Channel/ECOChannelDeviceInfo.m | 14 +++++++----- Src/Main/Shared/Channel/ECOSocketChannel.m | 4 +++- Src/Swift/LKS_SwiftTraceManager.swift | 22 +++++++++---------- 5 files changed, 34 insertions(+), 21 deletions(-) diff --git a/Package.swift b/Package.swift index b26b327..bb8fd4b 100644 --- a/Package.swift +++ b/Package.swift @@ -17,13 +17,17 @@ let package = Package( dependencies: [ // Dependencies declare other packages that this package depends on. // .package(url: /* package url */, from: "1.0.0"), + .package(url: "https://github.com/robbiehanson/CocoaAsyncSocket", from: "7.0.0"), ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. // Targets can depend on other targets in this package, and on products in packages this package depends on. .target( name: "LookinServer", - dependencies: [.target(name: "LookinServerSwift")], + dependencies: [ + .target(name: "LookinServerSwift"), + .product(name: "CocoaAsyncSocket", package: "CocoaAsyncSocket"), + ], path: "Src/Main", publicHeadersPath: "", cSettings: [ @@ -39,10 +43,13 @@ let package = Package( .headerSearchPath("Shared/Category"), .headerSearchPath("Shared/Message"), .headerSearchPath("Shared/Peertalk"), + .headerSearchPath("Shared/Channel"), + .headerSearchPath("Shared/Channel/Bonjour"), ], cxxSettings: [ .define("SHOULD_COMPILE_LOOKIN_SERVER", to: "1", .when(configuration: .debug)), - .define("SPM_LOOKIN_SERVER_ENABLED", to: "1", .when(configuration: .debug)) + .define("SPM_LOOKIN_SERVER_ENABLED", to: "1", .when(configuration: .debug)), + .define("LOOKIN_SERVER_WIRELESS", to: "1"), ] ), .target( diff --git a/Src/Main/Shared/Channel/ECOChannelAppInfo.m b/Src/Main/Shared/Channel/ECOChannelAppInfo.m index 2cd0c98..be8177e 100644 --- a/Src/Main/Shared/Channel/ECOChannelAppInfo.m +++ b/Src/Main/Shared/Channel/ECOChannelAppInfo.m @@ -3,11 +3,13 @@ // EchoSDK // // Created by 陈爱彬 on 2019/10/30. Maintain by 陈爱彬 -// Description +// Description // #import "ECOChannelAppInfo.h" +@import UIKit; + static NSString *_ecoUniqueAppId = nil; static NSString *_ecoUniqueAppName = nil; diff --git a/Src/Main/Shared/Channel/ECOChannelDeviceInfo.m b/Src/Main/Shared/Channel/ECOChannelDeviceInfo.m index f503ef1..4e027a5 100644 --- a/Src/Main/Shared/Channel/ECOChannelDeviceInfo.m +++ b/Src/Main/Shared/Channel/ECOChannelDeviceInfo.m @@ -3,7 +3,7 @@ // Echo // // Created by 陈爱彬 on 2019/4/17. Maintain by 陈爱彬 -// Description +// Description // #import "ECOChannelDeviceInfo.h" @@ -12,6 +12,8 @@ #include #include +@import UIKit; + static NSInteger const ECOINET_ADDRSTRLEN = 16; static NSInteger const ECOINET6_ADDRSTRLEN = 46; static NSString *_macUUIDString = nil; @@ -46,7 +48,7 @@ - (instancetype)initWithData:(NSData *)data { self.authorizedType = [deviceDict[@"authType"] integerValue]; self.showAuthAlert = [deviceDict[@"showAuth"] boolValue]; self.hostName = deviceDict[@"hostName"]; - + ECOChannelAppInfo *appInfo = [[ECOChannelAppInfo alloc] initWithDictionary:deviceDict[@"appInfo"]]; self.appInfo = appInfo; } @@ -66,7 +68,7 @@ - (id)copy { deviceInfo.authorizedType = self.authorizedType; deviceInfo.showAuthAlert = self.showAuthAlert; deviceInfo.appInfo = self.appInfo; - + return deviceInfo; } @@ -116,7 +118,7 @@ - (void)setupIOSDeviceInfo { //uuid // self.uuid = [[NSUUID UUID] UUIDString]; self.uuid = [[device identifierForVendor] UUIDString]; - + ECOChannelAppInfo *appInfo = [ECOChannelAppInfo new]; self.appInfo = appInfo; } @@ -125,7 +127,7 @@ - (void)setupIOSDeviceInfo { //获取本机的ip地址表 - (NSDictionary *)getIPAddresses { NSMutableDictionary *addresses = [NSMutableDictionary dictionaryWithCapacity:8]; - + // retrieve the current interfaces - returns 0 on success struct ifaddrs *interfaces; if(!getifaddrs(&interfaces)) { @@ -175,7 +177,7 @@ - (NSDictionary *)toJSONObject { [json setValue:@(self.showAuthAlert) forKey:@"showAuth"]; [json setValue:self.hostName ?: @"" forKey:@"hostName"]; [json setValue:[self.appInfo toDictionary] forKey:@"appInfo"]; - + return [json copy]; } // 返回调试信息 diff --git a/Src/Main/Shared/Channel/ECOSocketChannel.m b/Src/Main/Shared/Channel/ECOSocketChannel.m index 1980712..0f0e6af 100644 --- a/Src/Main/Shared/Channel/ECOSocketChannel.m +++ b/Src/Main/Shared/Channel/ECOSocketChannel.m @@ -7,7 +7,7 @@ // #import "ECOSocketChannel.h" -#import +//#import #import #include @@ -16,6 +16,8 @@ #import "LookinDefines.h" +@import CocoaAsyncSocket; + //static uint16_t const ECOClientSockeListenPortNumber = 23235; //static uint16_t const ECOSocketAcceptPortNumber = 23234; static CGFloat const ECOSocketRetryListenDelay = 1.f; diff --git a/Src/Swift/LKS_SwiftTraceManager.swift b/Src/Swift/LKS_SwiftTraceManager.swift index f1b5fdc..6e5fb63 100644 --- a/Src/Swift/LKS_SwiftTraceManager.swift +++ b/Src/Swift/LKS_SwiftTraceManager.swift @@ -18,28 +18,28 @@ public class LKS_SwiftTraceManager: NSObject { var mirror: Mirror? = Mirror(reflecting: hostObject) var currClass: AnyClass? = type(of: hostObject) let initialInClass: AnyClass? = currClass - + while let m = mirror, let unwrappedCurrClass = currClass { m.children.forEach { child in if let child = child as? (label: String?, value: NSObject) { let label: String? = child.label?.replacingOccurrences(of: "$__lazy_storage_$_", with: "") let value = child.value - + guard (value is UIView) || (value is CALayer) || (value is UIViewController) || (value is UIGestureRecognizer) else { return } - + guard let label = label, label.count > 0 else { return } - + let ivarTrace = LookinIvarTrace() ivarTrace.hostObject = hostObject - + ivarTrace.hostClassName = makeDisplayClassName(superClass: unwrappedCurrClass, childClass: initialInClass) - + ivarTrace.ivarName = label - + if (value === hostObject) { ivarTrace.relation = LookinIvarTraceRelationValue_Self } else if let hostView = hostObject as? UIView { @@ -60,11 +60,11 @@ public class LKS_SwiftTraceManager: NSObject { currClass = unwrappedCurrClass.superclass() } } - + // 比如 superClass 可能是 UIView,而 childClass 可能是 UIButton private static func makeDisplayClassName(superClass: AnyClass, childClass: AnyClass?) -> String { let superName = NSStringFromClass(superClass) - + guard let childClass = childClass else { return superName } @@ -81,7 +81,7 @@ public class LKS_SwiftTraceManager: NSObject { } return "\(childName) : \(superName)" } - + private static func queryModuleName(classname: String) -> String? { let parts = classname.components(separatedBy: ".") if parts.count != 2 { @@ -89,7 +89,7 @@ public class LKS_SwiftTraceManager: NSObject { } return parts[0] } - + /// 不包含 module name private static func queryShortName(classname: String) -> String { let parts = classname.components(separatedBy: ".")