mirror of
https://github.com/leonboe1/QuickDrop.git
synced 2026-11-11 19:11:10 +00:00
Merge branch 'master' into pt-BR-translation
This commit is contained in:
41 files changed
+2185
-235
No files matched your search
@@ -7,26 +7,63 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
698DFAE629E2F91A0064F247 /* NearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 698DFAE529E2F91A0064F247 /* NearbyConnection.swift */; };
|
||||
691F53BB2ABB70840089FD92 /* DeviceListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 691F53B92ABB70840089FD92 /* DeviceListCell.swift */; };
|
||||
691F53BC2ABB70840089FD92 /* DeviceListCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 691F53BA2ABB70840089FD92 /* DeviceListCell.xib */; };
|
||||
691F53BE2ABF03820089FD92 /* OutboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 691F53BD2ABF03820089FD92 /* OutboundNearbyConnection.swift */; };
|
||||
691F53C72AC2594E0089FD92 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 691F53C52AC2594E0089FD92 /* Localizable.strings */; };
|
||||
691F53CB2AC2599B0089FD92 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 691F53C92AC2599B0089FD92 /* Localizable.stringsdict */; };
|
||||
698DFB0329E362140064F247 /* NDNotificationCenterHackery.m in Sources */ = {isa = PBXBuildFile; fileRef = 698DFB0229E362140064F247 /* NDNotificationCenterHackery.m */; };
|
||||
699B03452AB5FBA300E0D718 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 699B03442AB5FBA300E0D718 /* Assets.xcassets */; };
|
||||
699DEBA62AB0573200115D22 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 699DEBA52AB0573200115D22 /* ShareViewController.swift */; };
|
||||
699DEBA92AB0573200115D22 /* ShareViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 699DEBA72AB0573200115D22 /* ShareViewController.xib */; };
|
||||
699DEBAE2AB0573200115D22 /* ShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 699DEBA12AB0573200115D22 /* ShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
69D2C32D29E77F2200EC7E30 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 69D2C32B29E77F2200EC7E30 /* Localizable.strings */; };
|
||||
69D2C32F29E7898C00EC7E30 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 69D2C32E29E7898C00EC7E30 /* MainMenu.xib */; };
|
||||
69D2C33829E78DF400EC7E30 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 69D2C33629E78DF400EC7E30 /* Localizable.stringsdict */; };
|
||||
69DA9A1229E0BF5100A442DA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A1129E0BF5100A442DA /* AppDelegate.swift */; };
|
||||
69DA9A1429E0BF5200A442DA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 69DA9A1329E0BF5200A442DA /* Assets.xcassets */; };
|
||||
69DA9A1F29E0C0B300A442DA /* NearbyConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */; };
|
||||
69DA9A2129E0CC4E00A442DA /* Data+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2029E0CC4E00A442DA /* Data+Extensions.swift */; };
|
||||
69DA9A2329E17F0400A442DA /* InboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */; };
|
||||
69DA9A2629E189EF00A442DA /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = 69DA9A2529E189EF00A442DA /* SwiftProtobuf */; };
|
||||
69DA9A2E29E18CB500A442DA /* ukey.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2829E18CB500A442DA /* ukey.pb.swift */; };
|
||||
69DA9A2F29E18CB500A442DA /* wire_format.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2929E18CB500A442DA /* wire_format.pb.swift */; };
|
||||
69DA9A3029E18CB500A442DA /* device_to_device_messages.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2A29E18CB500A442DA /* device_to_device_messages.pb.swift */; };
|
||||
69DA9A3129E18CB500A442DA /* offline_wire_formats.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2B29E18CB500A442DA /* offline_wire_formats.pb.swift */; };
|
||||
69DA9A3229E18CB500A442DA /* securegcm.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2C29E18CB500A442DA /* securegcm.pb.swift */; };
|
||||
69DA9A3329E18CB500A442DA /* securemessage.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2D29E18CB500A442DA /* securemessage.pb.swift */; };
|
||||
69DA9A3629E1994C00A442DA /* SwiftECC in Frameworks */ = {isa = PBXBuildFile; productRef = 69DA9A3529E1994C00A442DA /* SwiftECC */; };
|
||||
69DCF48A2AB70E8C00CBE2CC /* wire_format.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2929E18CB500A442DA /* wire_format.pb.swift */; };
|
||||
69DCF48B2AB70E8C00CBE2CC /* ukey.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2829E18CB500A442DA /* ukey.pb.swift */; };
|
||||
69DCF48C2AB70E8C00CBE2CC /* device_to_device_messages.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2A29E18CB500A442DA /* device_to_device_messages.pb.swift */; };
|
||||
69DCF48D2AB70E8C00CBE2CC /* offline_wire_formats.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2B29E18CB500A442DA /* offline_wire_formats.pb.swift */; };
|
||||
69DCF48E2AB70E8C00CBE2CC /* securegcm.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2C29E18CB500A442DA /* securegcm.pb.swift */; };
|
||||
69DCF48F2AB70E8C00CBE2CC /* securemessage.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2D29E18CB500A442DA /* securemessage.pb.swift */; };
|
||||
69DCF4902AB70E9700CBE2CC /* InboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */; };
|
||||
69DCF4912AB70E9700CBE2CC /* NearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 698DFAE529E2F91A0064F247 /* NearbyConnection.swift */; };
|
||||
69DCF4922AB70E9700CBE2CC /* NearbyConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */; };
|
||||
69DCF4932AB70E9700CBE2CC /* Data+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2029E0CC4E00A442DA /* Data+Extensions.swift */; };
|
||||
69DCF4942AB8BF7B00CBE2CC /* libNearbyShare.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 69DCF4812AB70D0600CBE2CC /* libNearbyShare.dylib */; };
|
||||
69DCF4952AB8BF7B00CBE2CC /* libNearbyShare.dylib in Embed Libraries */ = {isa = PBXBuildFile; fileRef = 69DCF4812AB70D0600CBE2CC /* libNearbyShare.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
69DCF49A2AB8C58500CBE2CC /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = 69DCF4992AB8C58500CBE2CC /* SwiftProtobuf */; };
|
||||
69DCF49C2AB8C58500CBE2CC /* SwiftECC in Frameworks */ = {isa = PBXBuildFile; productRef = 69DCF49B2AB8C58500CBE2CC /* SwiftECC */; };
|
||||
69DCF49D2AB8C9A500CBE2CC /* libNearbyShare.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 69DCF4812AB70D0600CBE2CC /* libNearbyShare.dylib */; };
|
||||
69DCF49E2AB8C9A500CBE2CC /* libNearbyShare.dylib in Embed Libraries */ = {isa = PBXBuildFile; fileRef = 69DCF4812AB70D0600CBE2CC /* libNearbyShare.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
699DEBAC2AB0573200115D22 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 69DA9A0629E0BF5100A442DA /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 699DEBA02AB0573200115D22;
|
||||
remoteInfo = ShareExtension;
|
||||
};
|
||||
69DCF4962AB8BF7B00CBE2CC /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 69DA9A0629E0BF5100A442DA /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 69DCF4802AB70D0600CBE2CC;
|
||||
remoteInfo = NearbyShare;
|
||||
};
|
||||
69DCF49F2AB8C9A500CBE2CC /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 69DA9A0629E0BF5100A442DA /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 69DCF4802AB70D0600CBE2CC;
|
||||
remoteInfo = NearbyShare;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
698DFAFF29E353220064F247 /* Embed Foundation Extensions */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
@@ -34,10 +71,43 @@
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
699DEBAE2AB0573200115D22 /* ShareExtension.appex in Embed Foundation Extensions */,
|
||||
);
|
||||
name = "Embed Foundation Extensions";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
699DEBDB2AB2828400115D22 /* Embed XPC Services */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "$(CONTENTS_FOLDER_PATH)/XPCServices";
|
||||
dstSubfolderSpec = 16;
|
||||
files = (
|
||||
);
|
||||
name = "Embed XPC Services";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
69DCF4982AB8BF7B00CBE2CC /* Embed Libraries */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
69DCF4952AB8BF7B00CBE2CC /* libNearbyShare.dylib in Embed Libraries */,
|
||||
);
|
||||
name = "Embed Libraries";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
69DCF4A12AB8C9A500CBE2CC /* Embed Libraries */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
69DCF49E2AB8C9A500CBE2CC /* libNearbyShare.dylib in Embed Libraries */,
|
||||
);
|
||||
name = "Embed Libraries";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
@@ -47,12 +117,26 @@
|
||||
3226184E2A51E10600B06FD1 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
322618502A51EB8A00B06FD1 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = es; path = es.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
322618512A51EB8A00B06FD1 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
691F53B92ABB70840089FD92 /* DeviceListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceListCell.swift; sourceTree = "<group>"; };
|
||||
691F53BA2ABB70840089FD92 /* DeviceListCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DeviceListCell.xib; sourceTree = "<group>"; };
|
||||
691F53BD2ABF03820089FD92 /* OutboundNearbyConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutboundNearbyConnection.swift; sourceTree = "<group>"; };
|
||||
691F53C42AC257A30089FD92 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/ShareViewController.strings; sourceTree = "<group>"; };
|
||||
691F53C62AC2594E0089FD92 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
691F53C82AC259630089FD92 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
691F53CA2AC2599B0089FD92 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = Base; path = Base.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
691F53CC2AC259A20089FD92 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ru; path = ru.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
698DFAE529E2F91A0064F247 /* NearbyConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearbyConnection.swift; sourceTree = "<group>"; };
|
||||
698DFAED29E353220064F247 /* UserNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotifications.framework; path = System/Library/Frameworks/UserNotifications.framework; sourceTree = SDKROOT; };
|
||||
698DFAEF29E353220064F247 /* UserNotificationsUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotificationsUI.framework; path = System/Library/Frameworks/UserNotificationsUI.framework; sourceTree = SDKROOT; };
|
||||
698DFB0029E362140064F247 /* NearDrop-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NearDrop-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
698DFB0129E362140064F247 /* NDNotificationCenterHackery.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NDNotificationCenterHackery.h; sourceTree = "<group>"; };
|
||||
698DFB0229E362140064F247 /* NDNotificationCenterHackery.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NDNotificationCenterHackery.m; sourceTree = "<group>"; };
|
||||
699B03442AB5FBA300E0D718 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
699DEBA12AB0573200115D22 /* ShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
699DEBA52AB0573200115D22 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; };
|
||||
699DEBA82AB0573200115D22 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/ShareViewController.xib; sourceTree = "<group>"; };
|
||||
699DEBAA2AB0573200115D22 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
699DEBAB2AB0573200115D22 /* ShareExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ShareExtension.entitlements; sourceTree = "<group>"; };
|
||||
69D2C32C29E77F2200EC7E30 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
69D2C32E29E7898C00EC7E30 /* MainMenu.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = "<group>"; };
|
||||
69D2C33029E789AF00EC7E30 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
@@ -71,19 +155,38 @@
|
||||
69DA9A2B29E18CB500A442DA /* offline_wire_formats.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = offline_wire_formats.pb.swift; sourceTree = "<group>"; };
|
||||
69DA9A2C29E18CB500A442DA /* securegcm.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = securegcm.pb.swift; sourceTree = "<group>"; };
|
||||
69DA9A2D29E18CB500A442DA /* securemessage.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = securemessage.pb.swift; sourceTree = "<group>"; };
|
||||
69DCF4812AB70D0600CBE2CC /* libNearbyShare.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libNearbyShare.dylib; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
A3930F0529EAB2D1008F891D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = it; path = it.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
A3930F0629EAB2D1008F891D /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
AE37AC272ABB62E400F0FF91 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = hu; path = hu.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
AE37AC282ABB62E400F0FF91 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
B670906E2A6D234D00DB8273 /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = th; path = th.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
B670906F2A6D237600DB8273 /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
699DEB9E2AB0573200115D22 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
69DCF49D2AB8C9A500CBE2CC /* libNearbyShare.dylib in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
69DA9A0B29E0BF5100A442DA /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
69DA9A3629E1994C00A442DA /* SwiftECC in Frameworks */,
|
||||
69DA9A2629E189EF00A442DA /* SwiftProtobuf in Frameworks */,
|
||||
69DCF4942AB8BF7B00CBE2CC /* libNearbyShare.dylib in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
69DCF47F2AB70D0600CBE2CC /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
69DCF49C2AB8C58500CBE2CC /* SwiftECC in Frameworks */,
|
||||
69DCF49A2AB8C58500CBE2CC /* SwiftProtobuf in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -99,10 +202,28 @@
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
699DEBA22AB0573200115D22 /* ShareExtension */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
699DEBA52AB0573200115D22 /* ShareViewController.swift */,
|
||||
691F53B92ABB70840089FD92 /* DeviceListCell.swift */,
|
||||
691F53BA2ABB70840089FD92 /* DeviceListCell.xib */,
|
||||
699DEBA72AB0573200115D22 /* ShareViewController.xib */,
|
||||
699B03442AB5FBA300E0D718 /* Assets.xcassets */,
|
||||
691F53C92AC2599B0089FD92 /* Localizable.stringsdict */,
|
||||
691F53C52AC2594E0089FD92 /* Localizable.strings */,
|
||||
699DEBAA2AB0573200115D22 /* Info.plist */,
|
||||
699DEBAB2AB0573200115D22 /* ShareExtension.entitlements */,
|
||||
);
|
||||
path = ShareExtension;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
69DA9A0529E0BF5100A442DA = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
69DA9A1029E0BF5100A442DA /* NearDrop */,
|
||||
699DEBA22AB0573200115D22 /* ShareExtension */,
|
||||
69DCF4822AB70D0600CBE2CC /* NearbyShare */,
|
||||
698DFAEC29E353220064F247 /* Frameworks */,
|
||||
69DA9A0F29E0BF5100A442DA /* Products */,
|
||||
);
|
||||
@@ -112,6 +233,8 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
69DA9A0E29E0BF5100A442DA /* NearDrop.app */,
|
||||
699DEBA12AB0573200115D22 /* ShareExtension.appex */,
|
||||
69DCF4812AB70D0600CBE2CC /* libNearbyShare.dylib */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -119,17 +242,12 @@
|
||||
69DA9A1029E0BF5100A442DA /* NearDrop */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
69DA9A2729E18CB500A442DA /* Protobuf */,
|
||||
69DA9A1129E0BF5100A442DA /* AppDelegate.swift */,
|
||||
69DA9A1329E0BF5200A442DA /* Assets.xcassets */,
|
||||
69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */,
|
||||
69D2C33629E78DF400EC7E30 /* Localizable.stringsdict */,
|
||||
69D2C32B29E77F2200EC7E30 /* Localizable.strings */,
|
||||
69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */,
|
||||
698DFB0129E362140064F247 /* NDNotificationCenterHackery.h */,
|
||||
698DFB0229E362140064F247 /* NDNotificationCenterHackery.m */,
|
||||
698DFAE529E2F91A0064F247 /* NearbyConnection.swift */,
|
||||
69DA9A2029E0CC4E00A442DA /* Data+Extensions.swift */,
|
||||
69DA9A1829E0BF5200A442DA /* NearDrop.entitlements */,
|
||||
698DFB0029E362140064F247 /* NearDrop-Bridging-Header.h */,
|
||||
69D2C32E29E7898C00EC7E30 /* MainMenu.xib */,
|
||||
@@ -150,9 +268,51 @@
|
||||
path = Protobuf;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
69DCF4822AB70D0600CBE2CC /* NearbyShare */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
69DA9A2729E18CB500A442DA /* Protobuf */,
|
||||
69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */,
|
||||
69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */,
|
||||
691F53BD2ABF03820089FD92 /* OutboundNearbyConnection.swift */,
|
||||
698DFAE529E2F91A0064F247 /* NearbyConnection.swift */,
|
||||
69DA9A2029E0CC4E00A442DA /* Data+Extensions.swift */,
|
||||
);
|
||||
path = NearbyShare;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
69DCF47D2AB70D0600CBE2CC /* Headers */ = {
|
||||
isa = PBXHeadersBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXHeadersBuildPhase section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
699DEBA02AB0573200115D22 /* ShareExtension */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 699DEBB12AB0573200115D22 /* Build configuration list for PBXNativeTarget "ShareExtension" */;
|
||||
buildPhases = (
|
||||
699DEB9D2AB0573200115D22 /* Sources */,
|
||||
699DEB9E2AB0573200115D22 /* Frameworks */,
|
||||
699DEB9F2AB0573200115D22 /* Resources */,
|
||||
69DCF4A12AB8C9A500CBE2CC /* Embed Libraries */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
69DCF4A02AB8C9A500CBE2CC /* PBXTargetDependency */,
|
||||
);
|
||||
name = ShareExtension;
|
||||
productName = ShareExtension;
|
||||
productReference = 699DEBA12AB0573200115D22 /* ShareExtension.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
69DA9A0D29E0BF5100A442DA /* NearDrop */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 69DA9A1B29E0BF5200A442DA /* Build configuration list for PBXNativeTarget "NearDrop" */;
|
||||
@@ -161,19 +321,42 @@
|
||||
69DA9A0B29E0BF5100A442DA /* Frameworks */,
|
||||
69DA9A0C29E0BF5100A442DA /* Resources */,
|
||||
698DFAFF29E353220064F247 /* Embed Foundation Extensions */,
|
||||
699DEBDB2AB2828400115D22 /* Embed XPC Services */,
|
||||
69DCF4982AB8BF7B00CBE2CC /* Embed Libraries */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
699DEBAD2AB0573200115D22 /* PBXTargetDependency */,
|
||||
69DCF4972AB8BF7B00CBE2CC /* PBXTargetDependency */,
|
||||
);
|
||||
name = NearDrop;
|
||||
packageProductDependencies = (
|
||||
);
|
||||
productName = NearDrop;
|
||||
productReference = 69DA9A0E29E0BF5100A442DA /* NearDrop.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
69DCF4802AB70D0600CBE2CC /* NearbyShare */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 69DCF4892AB70D0600CBE2CC /* Build configuration list for PBXNativeTarget "NearbyShare" */;
|
||||
buildPhases = (
|
||||
69DCF47D2AB70D0600CBE2CC /* Headers */,
|
||||
69DCF47E2AB70D0600CBE2CC /* Sources */,
|
||||
69DCF47F2AB70D0600CBE2CC /* Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = NearDrop;
|
||||
name = NearbyShare;
|
||||
packageProductDependencies = (
|
||||
69DA9A2529E189EF00A442DA /* SwiftProtobuf */,
|
||||
69DA9A3529E1994C00A442DA /* SwiftECC */,
|
||||
69DCF4992AB8C58500CBE2CC /* SwiftProtobuf */,
|
||||
69DCF49B2AB8C58500CBE2CC /* SwiftECC */,
|
||||
);
|
||||
productName = NearDrop;
|
||||
productReference = 69DA9A0E29E0BF5100A442DA /* NearDrop.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
productName = NearbyShare;
|
||||
productReference = 69DCF4812AB70D0600CBE2CC /* libNearbyShare.dylib */;
|
||||
productType = "com.apple.product-type.library.dynamic";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
@@ -185,10 +368,16 @@
|
||||
LastSwiftUpdateCheck = 1430;
|
||||
LastUpgradeCheck = 1430;
|
||||
TargetAttributes = {
|
||||
699DEBA02AB0573200115D22 = {
|
||||
CreatedOnToolsVersion = 14.3.1;
|
||||
};
|
||||
69DA9A0D29E0BF5100A442DA = {
|
||||
CreatedOnToolsVersion = 14.3;
|
||||
LastSwiftMigration = 1430;
|
||||
};
|
||||
69DCF4802AB70D0600CBE2CC = {
|
||||
CreatedOnToolsVersion = 14.3.1;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 69DA9A0929E0BF5100A442DA /* Build configuration list for PBXProject "NearDrop" */;
|
||||
@@ -204,6 +393,7 @@
|
||||
es,
|
||||
th,
|
||||
"pt-BR",
|
||||
hu,
|
||||
);
|
||||
mainGroup = 69DA9A0529E0BF5100A442DA;
|
||||
packageReferences = (
|
||||
@@ -215,11 +405,25 @@
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
69DA9A0D29E0BF5100A442DA /* NearDrop */,
|
||||
699DEBA02AB0573200115D22 /* ShareExtension */,
|
||||
69DCF4802AB70D0600CBE2CC /* NearbyShare */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
699DEB9F2AB0573200115D22 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
699B03452AB5FBA300E0D718 /* Assets.xcassets in Resources */,
|
||||
691F53C72AC2594E0089FD92 /* Localizable.strings in Resources */,
|
||||
691F53BC2ABB70840089FD92 /* DeviceListCell.xib in Resources */,
|
||||
691F53CB2AC2599B0089FD92 /* Localizable.stringsdict in Resources */,
|
||||
699DEBA92AB0573200115D22 /* ShareViewController.xib in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
69DA9A0C29E0BF5100A442DA /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@@ -234,28 +438,90 @@
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
699DEB9D2AB0573200115D22 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
699DEBA62AB0573200115D22 /* ShareViewController.swift in Sources */,
|
||||
691F53BB2ABB70840089FD92 /* DeviceListCell.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
69DA9A0A29E0BF5100A442DA /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
69DA9A2E29E18CB500A442DA /* ukey.pb.swift in Sources */,
|
||||
698DFB0329E362140064F247 /* NDNotificationCenterHackery.m in Sources */,
|
||||
69DA9A2329E17F0400A442DA /* InboundNearbyConnection.swift in Sources */,
|
||||
69DA9A1F29E0C0B300A442DA /* NearbyConnectionManager.swift in Sources */,
|
||||
69DA9A3029E18CB500A442DA /* device_to_device_messages.pb.swift in Sources */,
|
||||
69DA9A3129E18CB500A442DA /* offline_wire_formats.pb.swift in Sources */,
|
||||
69DA9A1229E0BF5100A442DA /* AppDelegate.swift in Sources */,
|
||||
69DA9A2F29E18CB500A442DA /* wire_format.pb.swift in Sources */,
|
||||
69DA9A3329E18CB500A442DA /* securemessage.pb.swift in Sources */,
|
||||
69DA9A2129E0CC4E00A442DA /* Data+Extensions.swift in Sources */,
|
||||
69DA9A3229E18CB500A442DA /* securegcm.pb.swift in Sources */,
|
||||
698DFAE629E2F91A0064F247 /* NearbyConnection.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
69DCF47E2AB70D0600CBE2CC /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
69DCF48D2AB70E8C00CBE2CC /* offline_wire_formats.pb.swift in Sources */,
|
||||
69DCF48F2AB70E8C00CBE2CC /* securemessage.pb.swift in Sources */,
|
||||
69DCF4912AB70E9700CBE2CC /* NearbyConnection.swift in Sources */,
|
||||
69DCF4922AB70E9700CBE2CC /* NearbyConnectionManager.swift in Sources */,
|
||||
69DCF48E2AB70E8C00CBE2CC /* securegcm.pb.swift in Sources */,
|
||||
691F53BE2ABF03820089FD92 /* OutboundNearbyConnection.swift in Sources */,
|
||||
69DCF48C2AB70E8C00CBE2CC /* device_to_device_messages.pb.swift in Sources */,
|
||||
69DCF4902AB70E9700CBE2CC /* InboundNearbyConnection.swift in Sources */,
|
||||
69DCF48B2AB70E8C00CBE2CC /* ukey.pb.swift in Sources */,
|
||||
69DCF48A2AB70E8C00CBE2CC /* wire_format.pb.swift in Sources */,
|
||||
69DCF4932AB70E9700CBE2CC /* Data+Extensions.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
699DEBAD2AB0573200115D22 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 699DEBA02AB0573200115D22 /* ShareExtension */;
|
||||
targetProxy = 699DEBAC2AB0573200115D22 /* PBXContainerItemProxy */;
|
||||
};
|
||||
69DCF4972AB8BF7B00CBE2CC /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 69DCF4802AB70D0600CBE2CC /* NearbyShare */;
|
||||
targetProxy = 69DCF4962AB8BF7B00CBE2CC /* PBXContainerItemProxy */;
|
||||
};
|
||||
69DCF4A02AB8C9A500CBE2CC /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 69DCF4802AB70D0600CBE2CC /* NearbyShare */;
|
||||
targetProxy = 69DCF49F2AB8C9A500CBE2CC /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
691F53C52AC2594E0089FD92 /* Localizable.strings */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
691F53C62AC2594E0089FD92 /* Base */,
|
||||
691F53C82AC259630089FD92 /* ru */,
|
||||
);
|
||||
name = Localizable.strings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
691F53C92AC2599B0089FD92 /* Localizable.stringsdict */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
691F53CA2AC2599B0089FD92 /* Base */,
|
||||
691F53CC2AC259A20089FD92 /* ru */,
|
||||
);
|
||||
name = Localizable.stringsdict;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
699DEBA72AB0573200115D22 /* ShareViewController.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
699DEBA82AB0573200115D22 /* Base */,
|
||||
691F53C42AC257A30089FD92 /* ru */,
|
||||
);
|
||||
name = ShareViewController.xib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
69D2C32B29E77F2200EC7E30 /* Localizable.strings */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
@@ -266,6 +532,7 @@
|
||||
322618512A51EB8A00B06FD1 /* es */,
|
||||
B670906F2A6D237600DB8273 /* th */,
|
||||
2C284C442AB9FFA200F8D624 /* pt-BR */,
|
||||
AE37AC282ABB62E400F0FF91 /* hu */,
|
||||
);
|
||||
name = Localizable.strings;
|
||||
sourceTree = "<group>";
|
||||
@@ -280,6 +547,7 @@
|
||||
322618502A51EB8A00B06FD1 /* es */,
|
||||
B670906E2A6D234D00DB8273 /* th */,
|
||||
2C284C432AB9FFA200F8D624 /* pt-BR */,
|
||||
AE37AC272ABB62E400F0FF91 /* hu */,
|
||||
);
|
||||
name = Localizable.stringsdict;
|
||||
sourceTree = "<group>";
|
||||
@@ -287,6 +555,58 @@
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
699DEBAF2AB0573200115D22 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = ShareExtension/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = NearDrop;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
"@executable_path/../../../../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = me.grishka.NearDrop.ShareExtension;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
699DEBB02AB0573200115D22 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = ShareExtension/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = NearDrop;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
"@executable_path/../../../../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = me.grishka.NearDrop.ShareExtension;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
69DA9A1929E0BF5200A442DA /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
@@ -338,7 +658,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.3;
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
@@ -393,7 +713,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.3;
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = macosx;
|
||||
@@ -405,6 +725,7 @@
|
||||
69DA9A1C29E0BF5200A442DA /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
@@ -436,6 +757,7 @@
|
||||
69DA9A1D29E0BF5200A442DA /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
@@ -463,9 +785,46 @@
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
69DCF4872AB70D0600CBE2CC /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
EXECUTABLE_PREFIX = lib;
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
69DCF4882AB70D0600CBE2CC /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
EXECUTABLE_PREFIX = lib;
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
699DEBB12AB0573200115D22 /* Build configuration list for PBXNativeTarget "ShareExtension" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
699DEBAF2AB0573200115D22 /* Debug */,
|
||||
699DEBB02AB0573200115D22 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
69DA9A0929E0BF5100A442DA /* Build configuration list for PBXProject "NearDrop" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
@@ -484,6 +843,15 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
69DCF4892AB70D0600CBE2CC /* Build configuration list for PBXNativeTarget "NearbyShare" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
69DCF4872AB70D0600CBE2CC /* Debug */,
|
||||
69DCF4882AB70D0600CBE2CC /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
@@ -506,12 +874,12 @@
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
69DA9A2529E189EF00A442DA /* SwiftProtobuf */ = {
|
||||
69DCF4992AB8C58500CBE2CC /* SwiftProtobuf */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 69DA9A2429E189EF00A442DA /* XCRemoteSwiftPackageReference "swift-protobuf" */;
|
||||
productName = SwiftProtobuf;
|
||||
};
|
||||
69DA9A3529E1994C00A442DA /* SwiftECC */ = {
|
||||
69DCF49B2AB8C58500CBE2CC /* SwiftECC */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 69DA9A3429E1994C00A442DA /* XCRemoteSwiftPackageReference "SwiftECC" */;
|
||||
productName = SwiftECC;
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1430"
|
||||
wasCreatedForAppExtension = "YES"
|
||||
version = "2.0">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "699DEBA02AB0573200115D22"
|
||||
BuildableName = "ShareExtension.appex"
|
||||
BlueprintName = "ShareExtension"
|
||||
ReferencedContainer = "container:NearDrop.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "69DA9A0D29E0BF5100A442DA"
|
||||
BuildableName = "NearDrop.app"
|
||||
BlueprintName = "NearDrop"
|
||||
ReferencedContainer = "container:NearDrop.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
shouldAutocreateTestPlan = "YES">
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = ""
|
||||
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
|
||||
launchStyle = "0"
|
||||
askForAppToLaunch = "Yes"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES"
|
||||
launchAutomaticallySubstyle = "2">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "69DA9A0D29E0BF5100A442DA"
|
||||
BuildableName = "NearDrop.app"
|
||||
BlueprintName = "NearDrop"
|
||||
ReferencedContainer = "container:NearDrop.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
askForAppToLaunch = "Yes"
|
||||
launchAutomaticallySubstyle = "2">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "69DA9A0D29E0BF5100A442DA"
|
||||
BuildableName = "NearDrop.app"
|
||||
BlueprintName = "NearDrop"
|
||||
ReferencedContainer = "container:NearDrop.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -7,12 +7,12 @@
|
||||
|
||||
import Cocoa
|
||||
import UserNotifications
|
||||
import NearbyShare
|
||||
|
||||
@main
|
||||
class AppDelegate: NSObject, NSApplicationDelegate{
|
||||
|
||||
private var connectionManager:NearbyConnectionManager?
|
||||
class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDelegate, MainAppDelegate{
|
||||
private var statusItem:NSStatusItem?
|
||||
private var activeIncomingTransfers:[String:TransferInfo]=[:]
|
||||
|
||||
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
||||
let menu=NSMenu()
|
||||
@@ -32,11 +32,13 @@ class AppDelegate: NSObject, NSApplicationDelegate{
|
||||
}
|
||||
}
|
||||
}
|
||||
nc.delegate=self
|
||||
let incomingTransfersCategory=NDNotificationCenterHackery.hackedNotificationCategory()
|
||||
let errorsCategory=UNNotificationCategory(identifier: "ERRORS", actions: [], intentIdentifiers: [])
|
||||
nc.setNotificationCategories([incomingTransfersCategory, errorsCategory])
|
||||
connectionManager=NearbyConnectionManager()
|
||||
}
|
||||
NearbyConnectionManager.shared.mainAppDelegate=self
|
||||
NearbyConnectionManager.shared.becomeVisible()
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_ aNotification: Notification) {
|
||||
UNUserNotificationCenter.current().removeAllDeliveredNotifications()
|
||||
@@ -60,5 +62,66 @@ class AppDelegate: NSObject, NSApplicationDelegate{
|
||||
NSApplication.shared.terminate(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
|
||||
let transferID=response.notification.request.content.userInfo["transferID"]! as! String
|
||||
NearbyConnectionManager.shared.submitUserConsent(transferID: transferID, accept: response.actionIdentifier=="ACCEPT")
|
||||
if response.actionIdentifier != "ACCEPT"{
|
||||
activeIncomingTransfers.removeValue(forKey: transferID)
|
||||
}
|
||||
completionHandler()
|
||||
}
|
||||
|
||||
func obtainUserConsent(for transfer: TransferMetadata, from device: RemoteDeviceInfo) {
|
||||
let notificationContent=UNMutableNotificationContent()
|
||||
notificationContent.title="NearDrop"
|
||||
notificationContent.subtitle=String(format:NSLocalizedString("PinCode", value: "PIN: %@", comment: ""), arguments: [transfer.pinCode!])
|
||||
let fileStr:String
|
||||
if transfer.files.count==1{
|
||||
fileStr=transfer.files[0].name
|
||||
}else{
|
||||
fileStr=String.localizedStringWithFormat(NSLocalizedString("NFiles", value: "%d files", comment: ""), transfer.files.count)
|
||||
}
|
||||
notificationContent.body=String(format: NSLocalizedString("DeviceSendingFiles", value: "%1$@ is sending you %2$@", comment: ""), arguments: [device.name, fileStr])
|
||||
notificationContent.sound = .default
|
||||
notificationContent.categoryIdentifier="INCOMING_TRANSFERS"
|
||||
notificationContent.userInfo=["transferID": transfer.id]
|
||||
NDNotificationCenterHackery.removeDefaultAction(notificationContent)
|
||||
let notificationReq=UNNotificationRequest(identifier: "transfer_"+transfer.id, content: notificationContent, trigger: nil)
|
||||
self.activeIncomingTransfers[transfer.id]=TransferInfo(device: device, transfer: transfer)
|
||||
UNUserNotificationCenter.current().add(notificationReq)
|
||||
}
|
||||
|
||||
func incomingTransfer(id: String, didFinishWith error: Error?) {
|
||||
guard let transfer=self.activeIncomingTransfers[id] else {return}
|
||||
if let error=error{
|
||||
let notificationContent=UNMutableNotificationContent()
|
||||
notificationContent.title=String(format: NSLocalizedString("TransferError", value: "Failed to receive files from %@", comment: ""), arguments: [transfer.device.name])
|
||||
if let ne=(error as? NearbyError){
|
||||
switch ne{
|
||||
case .inputOutput(let er):
|
||||
notificationContent.body=er.localizedDescription
|
||||
case .protocolError(_):
|
||||
notificationContent.body=NSLocalizedString("Error.Protocol", value: "Communication error", comment: "")
|
||||
case .requiredFieldMissing:
|
||||
notificationContent.body=NSLocalizedString("Error.Protocol", value: "Communication error", comment: "")
|
||||
case .ukey2:
|
||||
notificationContent.body=NSLocalizedString("Error.Crypto", value: "Encryption error", comment: "")
|
||||
case .canceled(reason: _):
|
||||
break; // can't happen for incoming transfers
|
||||
}
|
||||
}else{
|
||||
notificationContent.body=error.localizedDescription
|
||||
}
|
||||
notificationContent.categoryIdentifier="ERRORS"
|
||||
UNUserNotificationCenter.current().add(UNNotificationRequest(identifier: "transferError_"+id, content: notificationContent, trigger: nil))
|
||||
}
|
||||
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: ["transfer_"+id])
|
||||
self.activeIncomingTransfers.removeValue(forKey: id)
|
||||
}
|
||||
}
|
||||
|
||||
struct TransferInfo{
|
||||
let device:RemoteDeviceInfo
|
||||
let transfer:TransferMetadata
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
//
|
||||
// NearbyConnectionManager.swift
|
||||
// NearDrop
|
||||
//
|
||||
// Created by Grishka on 08.04.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Network
|
||||
import UserNotifications
|
||||
|
||||
class NearbyConnectionManager : NSObject, NetServiceDelegate, InboundNearbyConnectionDelegate, UNUserNotificationCenterDelegate{
|
||||
|
||||
private var tcpListener:NWListener;
|
||||
private let endpointID:[UInt8]=generateEndpointID()
|
||||
private var mdnsService:NetService?
|
||||
private var activeConnections:[String:InboundNearbyConnection]=[:]
|
||||
|
||||
override init() {
|
||||
tcpListener=try! NWListener(using: NWParameters(tls: .none))
|
||||
super.init()
|
||||
UNUserNotificationCenter.current().delegate=self
|
||||
startTCPListener()
|
||||
}
|
||||
|
||||
private func startTCPListener(){
|
||||
tcpListener.stateUpdateHandler={(state:NWListener.State) in
|
||||
if case .ready = state {
|
||||
self.initMDNS()
|
||||
}
|
||||
}
|
||||
tcpListener.newConnectionHandler={(connection:NWConnection) in
|
||||
let id=UUID().uuidString
|
||||
let conn=InboundNearbyConnection(connection: connection, id: id)
|
||||
self.activeConnections[id]=conn
|
||||
conn.delegate=self
|
||||
conn.start()
|
||||
}
|
||||
tcpListener.start(queue: .global(qos: .utility))
|
||||
}
|
||||
|
||||
private static func generateEndpointID()->[UInt8]{
|
||||
var id:[UInt8]=[]
|
||||
let alphabet="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".compactMap {UInt8($0.asciiValue!)}
|
||||
for _ in 0...3{
|
||||
id.append(alphabet[Int.random(in: 0..<alphabet.count)])
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
private func initMDNS(){
|
||||
let nameBytes:[UInt8]=[
|
||||
0x23, // PCP
|
||||
endpointID[0], endpointID[1], endpointID[2], endpointID[3],
|
||||
0xFC, 0x9F, 0x5E, // Service ID hash
|
||||
0, 0
|
||||
]
|
||||
let name=Data(nameBytes).urlSafeBase64EncodedString()
|
||||
// 1 byte: Version(3 bits)|Visibility(1 bit)|Device Type(3 bits)|Reserved(1 bits)
|
||||
// Device types: unknown=0, phone=1, tablet=2, laptop=3
|
||||
var endpointInfo:[UInt8]=[3 << 1]
|
||||
// 16 bytes: unknown random bytes
|
||||
for _ in 0...15{
|
||||
endpointInfo.append(UInt8.random(in: 0...255))
|
||||
}
|
||||
// Device name in UTF-8 prefixed with 1-byte length
|
||||
let hostName=Host.current().localizedName!
|
||||
let hostNameChars=hostName.utf8
|
||||
endpointInfo.append(UInt8(hostNameChars.count))
|
||||
for (i, ch) in hostNameChars.enumerated(){
|
||||
guard i<256 else {break}
|
||||
endpointInfo.append(UInt8(ch))
|
||||
}
|
||||
|
||||
let port:Int32=Int32(tcpListener.port!.rawValue)
|
||||
mdnsService=NetService(domain: "", type: "_FC9F5ED42C8A._tcp.", name: name, port: port)
|
||||
mdnsService?.delegate=self
|
||||
mdnsService?.includesPeerToPeer=true
|
||||
mdnsService?.setTXTRecord(NetService.data(fromTXTRecord: [
|
||||
"n": Data(endpointInfo).urlSafeBase64EncodedString().data(using: .utf8)!
|
||||
]))
|
||||
mdnsService?.publish()
|
||||
}
|
||||
|
||||
func obtainUserConsent(for transfer: TransferMetadata, from device: RemoteDeviceInfo, connection: InboundNearbyConnection) {
|
||||
let notificationContent=UNMutableNotificationContent()
|
||||
notificationContent.title="NearDrop"
|
||||
notificationContent.subtitle=String(format:NSLocalizedString("PinCode", value: "PIN: %@", comment: ""), arguments: [connection.pinCode!])
|
||||
let fileStr:String
|
||||
if transfer.files.count==1{
|
||||
fileStr=transfer.files[0].name
|
||||
}else{
|
||||
fileStr=String.localizedStringWithFormat(NSLocalizedString("NFiles", value: "%d files", comment: ""), transfer.files.count)
|
||||
}
|
||||
notificationContent.body=String(format: NSLocalizedString("DeviceSendingFiles", value: "%1$@ is sending you %2$@", comment: ""), arguments: [device.name, fileStr])
|
||||
notificationContent.sound = .default
|
||||
notificationContent.categoryIdentifier="INCOMING_TRANSFERS"
|
||||
notificationContent.userInfo=["transferID": connection.id]
|
||||
NDNotificationCenterHackery.removeDefaultAction(notificationContent)
|
||||
let notificationReq=UNNotificationRequest(identifier: "transfer_"+connection.id, content: notificationContent, trigger: nil)
|
||||
UNUserNotificationCenter.current().add(notificationReq)
|
||||
}
|
||||
|
||||
func connectionWasTerminated(connection:InboundNearbyConnection, error:Error?){
|
||||
activeConnections.removeValue(forKey: connection.id)
|
||||
if let error=error{
|
||||
let notificationContent=UNMutableNotificationContent()
|
||||
notificationContent.title=String(format: NSLocalizedString("TransferError", value: "Failed to receive files from %@", comment: ""), arguments: [connection.remoteDeviceInfo!.name])
|
||||
if let ne=(error as? NearbyError){
|
||||
switch ne{
|
||||
case .inputOutput(let er):
|
||||
notificationContent.body=er.localizedDescription
|
||||
case .protocolError(_):
|
||||
notificationContent.body=NSLocalizedString("Error.Protocol", value: "Communication error", comment: "")
|
||||
case .requiredFieldMissing:
|
||||
notificationContent.body=NSLocalizedString("Error.Protocol", value: "Communication error", comment: "")
|
||||
case .ukey2:
|
||||
notificationContent.body=NSLocalizedString("Error.Crypto", value: "Encryption error", comment: "")
|
||||
}
|
||||
}else{
|
||||
notificationContent.body=error.localizedDescription
|
||||
}
|
||||
notificationContent.categoryIdentifier="ERRORS"
|
||||
UNUserNotificationCenter.current().add(UNNotificationRequest(identifier: "transferError_"+connection.id, content: notificationContent, trigger: nil))
|
||||
}
|
||||
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: ["transfer_"+connection.id])
|
||||
}
|
||||
|
||||
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
|
||||
activeConnections[response.notification.request.content.userInfo["transferID"]! as! String]?.submitUserConsent(accepted: response.actionIdentifier=="ACCEPT")
|
||||
completionHandler()
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NFiles</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@files@</string>
|
||||
<key>files</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%d fájl</string>
|
||||
<key>other</key>
|
||||
<string>%d fájl</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -28,4 +28,20 @@ extension Data{
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
static func dataFromUrlSafeBase64(_ str:String)->Data?{
|
||||
var regularB64=String(str.map{
|
||||
if $0=="_"{
|
||||
return "/"
|
||||
}else if $0=="-"{
|
||||
return "+"
|
||||
}else{
|
||||
return $0
|
||||
}
|
||||
})
|
||||
while (regularB64.count%4) != 0{
|
||||
regularB64=regularB64+"="
|
||||
}
|
||||
return Data(base64Encoded: regularB64, options: .ignoreUnknownCharacters)
|
||||
}
|
||||
}
|
||||
File renamed without changes.
@@ -63,8 +63,10 @@ class InboundNearbyConnection: NearbyConnection{
|
||||
}
|
||||
}catch{
|
||||
lastError=error
|
||||
print("Deserialization error: \(error)")
|
||||
print("Deserialization error: \(error) in state \(currentState)")
|
||||
#if !DEBUG
|
||||
protocolError()
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,7 +281,7 @@ class InboundNearbyConnection: NearbyConnection{
|
||||
destinationURL: dest)
|
||||
transferredFiles[file.payloadID]=info
|
||||
}
|
||||
let metadata=TransferMetadata(files: transferredFiles.map({$0.value.meta}))
|
||||
let metadata=TransferMetadata(files: transferredFiles.map({$0.value.meta}), id: id, pinCode: pinCode)
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.obtainUserConsent(for: metadata, from: self.remoteDeviceInfo!, connection: self)
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import BigInt
|
||||
|
||||
class NearbyConnection{
|
||||
internal static let SANE_FRAME_LENGTH=5*1024*1024
|
||||
private static let dispatchQueue=DispatchQueue(label: "me.grishka.NearDrop.queue", qos: .utility) // FIFO (non-concurrent) queue to avoid those exciting concurrency bugs
|
||||
|
||||
internal let connection:NWConnection
|
||||
internal var remoteDeviceInfo:RemoteDeviceInfo?
|
||||
@@ -52,6 +53,7 @@ class NearbyConnection{
|
||||
func start(){
|
||||
connection.stateUpdateHandler={state in
|
||||
if case .ready = state {
|
||||
self.connectionReady()
|
||||
self.receiveFrameAsync()
|
||||
} else if case .failed(let err) = state {
|
||||
self.lastError=err
|
||||
@@ -59,9 +61,12 @@ class NearbyConnection{
|
||||
self.handleConnectionClosure()
|
||||
}
|
||||
}
|
||||
connection.start(queue: .global(qos: .utility))
|
||||
//connection.start(queue: .global(qos: .utility))
|
||||
connection.start(queue: NearbyConnection.dispatchQueue)
|
||||
}
|
||||
|
||||
func connectionReady(){}
|
||||
|
||||
internal func handleConnectionClosure(){
|
||||
print("Connection closed")
|
||||
}
|
||||
@@ -132,22 +137,27 @@ class NearbyConnection{
|
||||
}
|
||||
}
|
||||
|
||||
internal func sendFrameAsync(_ frame:Data){
|
||||
internal func sendFrameAsync(_ frame:Data, completion:(()->Void)?=nil){
|
||||
if connectionClosed{
|
||||
return
|
||||
}
|
||||
var lengthPrefixedData=Data(capacity: frame.count+4)
|
||||
let length:Int=frame.count
|
||||
lengthPrefixedData.append(contentsOf: [
|
||||
UInt8(length >> 24),
|
||||
UInt8(length >> 16),
|
||||
UInt8(length >> 8),
|
||||
UInt8(length)
|
||||
UInt8(truncatingIfNeeded: length >> 24),
|
||||
UInt8(truncatingIfNeeded: length >> 16),
|
||||
UInt8(truncatingIfNeeded: length >> 8),
|
||||
UInt8(truncatingIfNeeded: length)
|
||||
])
|
||||
lengthPrefixedData.append(frame)
|
||||
connection.send(content: lengthPrefixedData, completion: .contentProcessed({ error in
|
||||
|
||||
if let completion=completion{
|
||||
completion()
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
internal func encryptAndSendOfflineFrame(_ frame:Location_Nearby_Connections_OfflineFrame) throws{
|
||||
internal func encryptAndSendOfflineFrame(_ frame:Location_Nearby_Connections_OfflineFrame, completion:(()->Void)?=nil) throws{
|
||||
var d2dMsg=Securegcm_DeviceToDeviceMessage()
|
||||
serverSeq+=1
|
||||
d2dMsg.sequenceNumber=serverSeq
|
||||
@@ -185,7 +195,7 @@ class NearbyConnection{
|
||||
var smsg=Securemessage_SecureMessage()
|
||||
smsg.headerAndBody=try hb.serializedData()
|
||||
smsg.signature=Data(HMAC<SHA256>.authenticationCode(for: smsg.headerAndBody, using: sendHmacKey!))
|
||||
sendFrameAsync(try smsg.serializedData())
|
||||
sendFrameAsync(try smsg.serializedData(), completion: completion)
|
||||
}
|
||||
|
||||
internal func sendTransferSetupFrame(_ frame:Sharing_Nearby_Frame) throws{
|
||||
@@ -275,6 +285,9 @@ class NearbyConnection{
|
||||
try processFileChunk(frame: payloadTransfer)
|
||||
}
|
||||
}else if case .keepAlive = offlineFrame.v1.type{
|
||||
#if DEBUG
|
||||
print("Sent keep-alive")
|
||||
#endif
|
||||
sendKeepAlive(ack: true)
|
||||
}else{
|
||||
print("Unhandled offline frame encrypted: \(offlineFrame)")
|
||||
@@ -402,51 +415,6 @@ class NearbyConnection{
|
||||
}
|
||||
}
|
||||
|
||||
enum NearbyError:Error{
|
||||
case protocolError(_ message:String)
|
||||
case requiredFieldMissing
|
||||
case ukey2
|
||||
case inputOutput(cause:Errno)
|
||||
}
|
||||
|
||||
|
||||
struct RemoteDeviceInfo{
|
||||
let name:String
|
||||
let type:DeviceType
|
||||
|
||||
enum DeviceType{
|
||||
case unknown
|
||||
case phone
|
||||
case tablet
|
||||
case computer
|
||||
|
||||
static func fromRawValue(value:Int) -> DeviceType{
|
||||
switch value {
|
||||
case 0:
|
||||
return .unknown
|
||||
case 1:
|
||||
return .phone
|
||||
case 2:
|
||||
return .tablet
|
||||
case 3:
|
||||
return .computer
|
||||
default:
|
||||
return .unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TransferMetadata{
|
||||
let files:[FileMetadata]
|
||||
}
|
||||
|
||||
struct FileMetadata{
|
||||
let name:String
|
||||
let size:Int64
|
||||
let mimeType:String
|
||||
}
|
||||
|
||||
struct InternalFileInfo{
|
||||
let meta:FileMetadata
|
||||
let payloadID:Int64
|
||||
@@ -0,0 +1,374 @@
|
||||
//
|
||||
// NearbyConnectionManager.swift
|
||||
// NearDrop
|
||||
//
|
||||
// Created by Grishka on 08.04.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Network
|
||||
import System
|
||||
|
||||
public struct RemoteDeviceInfo{
|
||||
public let name:String
|
||||
public let type:DeviceType
|
||||
public var id:String?
|
||||
|
||||
init(name: String, type: DeviceType, id: String? = nil) {
|
||||
self.name = name
|
||||
self.type = type
|
||||
self.id = id
|
||||
}
|
||||
|
||||
init(info:EndpointInfo){
|
||||
self.name=info.name
|
||||
self.type=info.deviceType
|
||||
}
|
||||
|
||||
public enum DeviceType:Int32{
|
||||
case unknown=0
|
||||
case phone
|
||||
case tablet
|
||||
case computer
|
||||
|
||||
public static func fromRawValue(value:Int) -> DeviceType{
|
||||
switch value {
|
||||
case 0:
|
||||
return .unknown
|
||||
case 1:
|
||||
return .phone
|
||||
case 2:
|
||||
return .tablet
|
||||
case 3:
|
||||
return .computer
|
||||
default:
|
||||
return .unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public enum NearbyError:Error{
|
||||
case protocolError(_ message:String)
|
||||
case requiredFieldMissing
|
||||
case ukey2
|
||||
case inputOutput(cause:Errno)
|
||||
case canceled(reason:CancellationReason)
|
||||
|
||||
public enum CancellationReason{
|
||||
case userRejected, userCanceled, notEnoughSpace, unsupportedType, timedOut
|
||||
}
|
||||
}
|
||||
|
||||
public struct TransferMetadata{
|
||||
public let files:[FileMetadata]
|
||||
public let id:String
|
||||
public let pinCode:String?
|
||||
}
|
||||
|
||||
public struct FileMetadata{
|
||||
public let name:String
|
||||
public let size:Int64
|
||||
public let mimeType:String
|
||||
}
|
||||
|
||||
struct FoundServiceInfo{
|
||||
let service:NWBrowser.Result
|
||||
var device:RemoteDeviceInfo?
|
||||
}
|
||||
|
||||
struct OutgoingTransferInfo{
|
||||
let service:NWBrowser.Result
|
||||
let device:RemoteDeviceInfo
|
||||
let connection:OutboundNearbyConnection
|
||||
let delegate:ShareExtensionDelegate
|
||||
}
|
||||
|
||||
struct EndpointInfo{
|
||||
let name:String
|
||||
let deviceType:RemoteDeviceInfo.DeviceType
|
||||
|
||||
init(name: String, deviceType: RemoteDeviceInfo.DeviceType){
|
||||
self.name = name
|
||||
self.deviceType = deviceType
|
||||
}
|
||||
|
||||
init?(data:Data){
|
||||
guard data.count>17 else {return nil}
|
||||
let deviceNameLength=Int(data[17])
|
||||
guard data.count>=deviceNameLength+18 else {return nil}
|
||||
guard let deviceName=String(data: data[18..<(18+deviceNameLength)], encoding: .utf8) else {return nil}
|
||||
let rawDeviceType:Int=Int(data[0] & 7) >> 1
|
||||
self.name=deviceName
|
||||
self.deviceType=RemoteDeviceInfo.DeviceType.fromRawValue(value: rawDeviceType)
|
||||
}
|
||||
|
||||
func serialize()->Data{
|
||||
// 1 byte: Version(3 bits)|Visibility(1 bit)|Device Type(3 bits)|Reserved(1 bits)
|
||||
// Device types: unknown=0, phone=1, tablet=2, laptop=3
|
||||
var endpointInfo:[UInt8]=[UInt8(deviceType.rawValue << 1)]
|
||||
// 16 bytes: unknown random bytes
|
||||
for _ in 0...15{
|
||||
endpointInfo.append(UInt8.random(in: 0...255))
|
||||
}
|
||||
// Device name in UTF-8 prefixed with 1-byte length
|
||||
var nameChars=[UInt8](name.utf8)
|
||||
if nameChars.count>255{
|
||||
nameChars=[UInt8](nameChars[0..<255])
|
||||
}
|
||||
endpointInfo.append(UInt8(nameChars.count))
|
||||
for ch in nameChars{
|
||||
endpointInfo.append(UInt8(ch))
|
||||
}
|
||||
return Data(endpointInfo)
|
||||
}
|
||||
}
|
||||
|
||||
public protocol MainAppDelegate{
|
||||
func obtainUserConsent(for transfer:TransferMetadata, from device:RemoteDeviceInfo)
|
||||
func incomingTransfer(id:String, didFinishWith error:Error?)
|
||||
}
|
||||
|
||||
public protocol ShareExtensionDelegate:AnyObject{
|
||||
func addDevice(device:RemoteDeviceInfo)
|
||||
func removeDevice(id:String)
|
||||
func connectionWasEstablished(pinCode:String)
|
||||
func connectionFailed(with error:Error)
|
||||
func transferAccepted()
|
||||
func transferProgress(progress:Double)
|
||||
func transferFinished()
|
||||
}
|
||||
|
||||
public class NearbyConnectionManager : NSObject, NetServiceDelegate, InboundNearbyConnectionDelegate, OutboundNearbyConnectionDelegate{
|
||||
|
||||
private var tcpListener:NWListener;
|
||||
public let endpointID:[UInt8]=generateEndpointID()
|
||||
private var mdnsService:NetService?
|
||||
private var activeConnections:[String:InboundNearbyConnection]=[:]
|
||||
private var foundServices:[String:FoundServiceInfo]=[:]
|
||||
private var shareExtensionDelegates:[ShareExtensionDelegate]=[]
|
||||
private var outgoingTransfers:[String:OutgoingTransferInfo]=[:]
|
||||
public var mainAppDelegate:(any MainAppDelegate)?
|
||||
private var discoveryRefCount=0
|
||||
|
||||
private var browser:NWBrowser?
|
||||
|
||||
public static let shared=NearbyConnectionManager()
|
||||
|
||||
override init() {
|
||||
tcpListener=try! NWListener(using: NWParameters(tls: .none))
|
||||
super.init()
|
||||
}
|
||||
|
||||
public func becomeVisible(){
|
||||
startTCPListener()
|
||||
}
|
||||
|
||||
private func startTCPListener(){
|
||||
tcpListener.stateUpdateHandler={(state:NWListener.State) in
|
||||
if case .ready = state {
|
||||
self.initMDNS()
|
||||
}
|
||||
}
|
||||
tcpListener.newConnectionHandler={(connection:NWConnection) in
|
||||
let id=UUID().uuidString
|
||||
let conn=InboundNearbyConnection(connection: connection, id: id)
|
||||
self.activeConnections[id]=conn
|
||||
conn.delegate=self
|
||||
conn.start()
|
||||
}
|
||||
tcpListener.start(queue: .global(qos: .utility))
|
||||
}
|
||||
|
||||
private static func generateEndpointID()->[UInt8]{
|
||||
var id:[UInt8]=[]
|
||||
let alphabet="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".compactMap {UInt8($0.asciiValue!)}
|
||||
for _ in 0...3{
|
||||
id.append(alphabet[Int.random(in: 0..<alphabet.count)])
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
private func initMDNS(){
|
||||
let nameBytes:[UInt8]=[
|
||||
0x23, // PCP
|
||||
endpointID[0], endpointID[1], endpointID[2], endpointID[3],
|
||||
0xFC, 0x9F, 0x5E, // Service ID hash
|
||||
0, 0
|
||||
]
|
||||
let name=Data(nameBytes).urlSafeBase64EncodedString()
|
||||
let endpointInfo=EndpointInfo(name: Host.current().localizedName!, deviceType: .computer)
|
||||
|
||||
let port:Int32=Int32(tcpListener.port!.rawValue)
|
||||
mdnsService=NetService(domain: "", type: "_FC9F5ED42C8A._tcp.", name: name, port: port)
|
||||
mdnsService?.delegate=self
|
||||
mdnsService?.includesPeerToPeer=true
|
||||
mdnsService?.setTXTRecord(NetService.data(fromTXTRecord: [
|
||||
"n": endpointInfo.serialize().urlSafeBase64EncodedString().data(using: .utf8)!
|
||||
]))
|
||||
mdnsService?.publish()
|
||||
}
|
||||
|
||||
func obtainUserConsent(for transfer: TransferMetadata, from device: RemoteDeviceInfo, connection: InboundNearbyConnection) {
|
||||
guard let delegate=mainAppDelegate else {return}
|
||||
delegate.obtainUserConsent(for: transfer, from: device)
|
||||
}
|
||||
|
||||
func connectionWasTerminated(connection:InboundNearbyConnection, error:Error?){
|
||||
guard let delegate=mainAppDelegate else {return}
|
||||
delegate.incomingTransfer(id: connection.id, didFinishWith: error)
|
||||
activeConnections.removeValue(forKey: connection.id)
|
||||
}
|
||||
|
||||
public func submitUserConsent(transferID:String, accept:Bool){
|
||||
guard let conn=activeConnections[transferID] else {return}
|
||||
conn.submitUserConsent(accepted: accept)
|
||||
}
|
||||
|
||||
public func startDeviceDiscovery(){
|
||||
if discoveryRefCount==0{
|
||||
foundServices.removeAll()
|
||||
if browser==nil{
|
||||
browser=NWBrowser(for: .bonjourWithTXTRecord(type: "_FC9F5ED42C8A._tcp.", domain: nil), using: .tcp)
|
||||
browser?.browseResultsChangedHandler={newResults, changes in
|
||||
for change in changes{
|
||||
switch change{
|
||||
case let .added(res):
|
||||
self.maybeAddFoundDevice(service: res)
|
||||
case let .removed(res):
|
||||
self.maybeRemoveFoundDevice(service: res)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
browser?.start(queue: .main)
|
||||
}
|
||||
discoveryRefCount+=1
|
||||
}
|
||||
|
||||
public func stopDeviceDiscovery(){
|
||||
discoveryRefCount-=1
|
||||
assert(discoveryRefCount>=0)
|
||||
if discoveryRefCount==0{
|
||||
browser?.cancel()
|
||||
browser=nil
|
||||
}
|
||||
}
|
||||
|
||||
public func addShareExtensionDelegate(_ delegate:ShareExtensionDelegate){
|
||||
shareExtensionDelegates.append(delegate)
|
||||
for service in foundServices.values{
|
||||
guard let device=service.device else {continue}
|
||||
delegate.addDevice(device: device)
|
||||
}
|
||||
}
|
||||
|
||||
public func removeShareExtensionDelegate(_ delegate:ShareExtensionDelegate){
|
||||
shareExtensionDelegates.removeAll(where: {$0===delegate})
|
||||
}
|
||||
|
||||
public func cancelOutgoingTransfer(id:String){
|
||||
guard let transfer=outgoingTransfers[id] else {return}
|
||||
transfer.connection.cancel()
|
||||
}
|
||||
|
||||
private func endpointID(for service:NWBrowser.Result)->String?{
|
||||
guard case let NWEndpoint.service(name: serviceName, type: _, domain: _, interface: _)=service.endpoint else {return nil}
|
||||
guard let nameData=Data.dataFromUrlSafeBase64(serviceName) else {return nil}
|
||||
guard nameData.count>=10 else {return nil}
|
||||
let pcp=nameData[0]
|
||||
guard pcp==0x23 else {return nil}
|
||||
let endpointID=String(data: nameData.subdata(in: 1..<5), encoding: .ascii)!
|
||||
let serviceIDHash=nameData.subdata(in: 5..<8)
|
||||
guard serviceIDHash==Data([0xFC, 0x9F, 0x5E]) else {return nil}
|
||||
return endpointID
|
||||
}
|
||||
|
||||
private func maybeAddFoundDevice(service:NWBrowser.Result){
|
||||
#if DEBUG
|
||||
print("found service \(service)")
|
||||
#endif
|
||||
guard let endpointID=endpointID(for: service) else {return}
|
||||
#if DEBUG
|
||||
print("service name is valid, endpoint ID \(endpointID)")
|
||||
#endif
|
||||
var foundService=FoundServiceInfo(service: service)
|
||||
|
||||
guard case let NWBrowser.Result.Metadata.bonjour(txtRecord)=service.metadata else {return}
|
||||
guard let endpointInfoEncoded=txtRecord.dictionary["n"] else {return}
|
||||
guard let endpointInfo=Data.dataFromUrlSafeBase64(endpointInfoEncoded) else {return}
|
||||
guard endpointInfo.count>=19 else {return}
|
||||
let deviceType=RemoteDeviceInfo.DeviceType.fromRawValue(value: (Int(endpointInfo[0]) >> 1) & 7)
|
||||
let deviceNameLength=Int(endpointInfo[17])
|
||||
guard endpointInfo.count>=deviceNameLength+17 else {return}
|
||||
guard let deviceName=String(data: endpointInfo.subdata(in: 18..<(18+deviceNameLength)), encoding: .utf8) else {return}
|
||||
|
||||
let deviceInfo=RemoteDeviceInfo(name: deviceName, type: deviceType, id: endpointID)
|
||||
foundService.device=deviceInfo
|
||||
foundServices[endpointID]=foundService
|
||||
for delegate in shareExtensionDelegates{
|
||||
delegate.addDevice(device: deviceInfo)
|
||||
}
|
||||
}
|
||||
|
||||
private func maybeRemoveFoundDevice(service:NWBrowser.Result){
|
||||
guard let endpointID=endpointID(for: service) else {return}
|
||||
guard let _=foundServices.removeValue(forKey: endpointID) else {return}
|
||||
for delegate in shareExtensionDelegates {
|
||||
delegate.removeDevice(id: endpointID)
|
||||
}
|
||||
}
|
||||
|
||||
public func startOutgoingTransfer(deviceID:String, delegate:ShareExtensionDelegate, urls:[URL]){
|
||||
guard let info=foundServices[deviceID] else {return}
|
||||
let tcp=NWProtocolTCP.Options.init()
|
||||
tcp.noDelay=true
|
||||
let nwconn=NWConnection(to: info.service.endpoint, using: NWParameters(tls: .none, tcp: tcp))
|
||||
let conn=OutboundNearbyConnection(connection: nwconn, id: deviceID, urlsToSend: urls)
|
||||
conn.delegate=self
|
||||
let transfer=OutgoingTransferInfo(service: info.service, device: info.device!, connection: conn, delegate: delegate)
|
||||
outgoingTransfers[deviceID]=transfer
|
||||
conn.start()
|
||||
}
|
||||
|
||||
func outboundConnectionWasEstablished(connection: OutboundNearbyConnection) {
|
||||
guard let transfer=outgoingTransfers[connection.id] else {return}
|
||||
DispatchQueue.main.async {
|
||||
transfer.delegate.connectionWasEstablished(pinCode: connection.pinCode!)
|
||||
}
|
||||
}
|
||||
|
||||
func outboundConnectionTransferAccepted(connection: OutboundNearbyConnection) {
|
||||
guard let transfer=outgoingTransfers[connection.id] else {return}
|
||||
DispatchQueue.main.async {
|
||||
transfer.delegate.transferAccepted()
|
||||
}
|
||||
}
|
||||
|
||||
func outboundConnection(connection: OutboundNearbyConnection, transferProgress: Double) {
|
||||
guard let transfer=outgoingTransfers[connection.id] else {return}
|
||||
DispatchQueue.main.async {
|
||||
transfer.delegate.transferProgress(progress: transferProgress)
|
||||
}
|
||||
}
|
||||
|
||||
func outboundConnection(connection: OutboundNearbyConnection, failedWithError: Error) {
|
||||
guard let transfer=outgoingTransfers[connection.id] else {return}
|
||||
DispatchQueue.main.async {
|
||||
transfer.delegate.connectionFailed(with: failedWithError)
|
||||
}
|
||||
outgoingTransfers.removeValue(forKey: connection.id)
|
||||
}
|
||||
|
||||
func outboundConnectionTransferFinished(connection: OutboundNearbyConnection) {
|
||||
guard let transfer=outgoingTransfers[connection.id] else {return}
|
||||
DispatchQueue.main.async {
|
||||
transfer.delegate.transferFinished()
|
||||
}
|
||||
outgoingTransfers.removeValue(forKey: connection.id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,424 @@
|
||||
//
|
||||
// OutboundNearbyConnection.swift
|
||||
// NearbyShare
|
||||
//
|
||||
// Created by Grishka on 23.09.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Network
|
||||
import CryptoKit
|
||||
import CommonCrypto
|
||||
import System
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
import SwiftECC
|
||||
import BigInt
|
||||
|
||||
class OutboundNearbyConnection:NearbyConnection{
|
||||
private var currentState:State = .initial
|
||||
private let urlsToSend:[URL]
|
||||
private var ukeyClientFinishMsgData:Data?
|
||||
private var queue:[OutgoingFileTransfer]=[]
|
||||
private var currentTransfer:OutgoingFileTransfer?
|
||||
public var delegate:OutboundNearbyConnectionDelegate?
|
||||
private var totalBytesToSend:Int64=0
|
||||
private var totalBytesSent:Int64=0
|
||||
private var cancelled:Bool=false
|
||||
|
||||
enum State{
|
||||
case initial, sentUkeyClientInit, sentUkeyClientFinish, sentPairedKeyEncryption, sentPairedKeyResult, sentIntroduction, sendingFiles
|
||||
}
|
||||
|
||||
init(connection: NWConnection, id: String, urlsToSend:[URL]){
|
||||
self.urlsToSend=urlsToSend
|
||||
super.init(connection: connection, id: id)
|
||||
}
|
||||
|
||||
deinit {
|
||||
if let transfer=currentTransfer, let handle=transfer.handle{
|
||||
try? handle.close()
|
||||
}
|
||||
for transfer in queue{
|
||||
if let handle=transfer.handle{
|
||||
try? handle.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func cancel(){
|
||||
cancelled=true
|
||||
if encryptionDone{
|
||||
var cancel=Sharing_Nearby_Frame()
|
||||
cancel.version = .v1
|
||||
cancel.v1=Sharing_Nearby_V1Frame()
|
||||
cancel.v1.type = .cancel
|
||||
try? sendTransferSetupFrame(cancel)
|
||||
}
|
||||
try? sendDisconnectionAndDisconnect()
|
||||
}
|
||||
|
||||
override func connectionReady() {
|
||||
super.connectionReady()
|
||||
do{
|
||||
try sendConnectionRequest()
|
||||
try sendUkey2ClientInit()
|
||||
}catch{
|
||||
lastError=error
|
||||
protocolError()
|
||||
}
|
||||
}
|
||||
|
||||
override func isServer() -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
override func processReceivedFrame(frameData: Data) {
|
||||
do{
|
||||
#if DEBUG
|
||||
print("received \(frameData), state is \(currentState)")
|
||||
#endif
|
||||
switch currentState {
|
||||
case .initial:
|
||||
protocolError()
|
||||
case .sentUkeyClientInit:
|
||||
try processUkey2ServerInit(frame: try Securegcm_Ukey2Message(serializedData: frameData), raw: frameData)
|
||||
case .sentUkeyClientFinish:
|
||||
try processConnectionResponse(frame: try Location_Nearby_Connections_OfflineFrame(serializedData: frameData))
|
||||
default:
|
||||
let smsg=try Securemessage_SecureMessage(serializedData: frameData)
|
||||
try decryptAndProcessReceivedSecureMessage(smsg)
|
||||
}
|
||||
}catch{
|
||||
if case NearbyError.ukey2=error{
|
||||
}else if currentState == .sentUkeyClientInit{
|
||||
sendUkey2Alert(type: .badMessage)
|
||||
}
|
||||
lastError=error
|
||||
protocolError()
|
||||
}
|
||||
}
|
||||
|
||||
override func processTransferSetupFrame(_ frame: Sharing_Nearby_Frame) throws {
|
||||
if frame.hasV1 && frame.v1.hasType, case .cancel = frame.v1.type {
|
||||
print("Transfer canceled")
|
||||
try sendDisconnectionAndDisconnect()
|
||||
delegate?.outboundConnection(connection: self, failedWithError: NearbyError.canceled(reason: .userCanceled))
|
||||
return
|
||||
}
|
||||
print(frame)
|
||||
switch currentState{
|
||||
case .sentPairedKeyEncryption:
|
||||
try processPairedKeyEncryption(frame: frame)
|
||||
case .sentPairedKeyResult:
|
||||
try processPairedKeyResult(frame: frame)
|
||||
case .sentIntroduction:
|
||||
try processConsent(frame: frame)
|
||||
case .sendingFiles:
|
||||
break
|
||||
default:
|
||||
assertionFailure("Unexpected state \(currentState)")
|
||||
}
|
||||
}
|
||||
|
||||
override func protocolError() {
|
||||
super.protocolError()
|
||||
delegate?.outboundConnection(connection: self, failedWithError: lastError!)
|
||||
}
|
||||
|
||||
private func sendConnectionRequest() throws {
|
||||
var frame=Location_Nearby_Connections_OfflineFrame()
|
||||
frame.version = .v1
|
||||
frame.v1=Location_Nearby_Connections_V1Frame()
|
||||
frame.v1.type = .connectionRequest
|
||||
frame.v1.connectionRequest=Location_Nearby_Connections_ConnectionRequestFrame()
|
||||
frame.v1.connectionRequest.endpointID=String(bytes: NearbyConnectionManager.shared.endpointID, encoding: .ascii)!
|
||||
frame.v1.connectionRequest.endpointName=Host.current().localizedName!
|
||||
let endpointInfo=EndpointInfo(name: Host.current().localizedName!, deviceType: .computer)
|
||||
frame.v1.connectionRequest.endpointInfo=endpointInfo.serialize()
|
||||
frame.v1.connectionRequest.mediums=[.wifiLan]
|
||||
sendFrameAsync(try frame.serializedData())
|
||||
}
|
||||
|
||||
private func sendUkey2ClientInit() throws {
|
||||
let domain=Domain.instance(curve: .EC256r1)
|
||||
let (pubKey, privKey)=domain.makeKeyPair()
|
||||
publicKey=pubKey
|
||||
privateKey=privKey
|
||||
|
||||
var finishFrame=Securegcm_Ukey2Message()
|
||||
finishFrame.messageType = .clientFinish
|
||||
var finish=Securegcm_Ukey2ClientFinished()
|
||||
var pkey=Securemessage_GenericPublicKey()
|
||||
pkey.type = .ecP256
|
||||
pkey.ecP256PublicKey=Securemessage_EcP256PublicKey()
|
||||
pkey.ecP256PublicKey.x=Data(pubKey.w.x.asSignedBytes())
|
||||
pkey.ecP256PublicKey.y=Data(pubKey.w.y.asSignedBytes())
|
||||
finish.publicKey=try pkey.serializedData()
|
||||
finishFrame.messageData=try finish.serializedData()
|
||||
ukeyClientFinishMsgData=try finishFrame.serializedData()
|
||||
|
||||
var frame=Securegcm_Ukey2Message()
|
||||
frame.messageType = .clientInit
|
||||
|
||||
var clientInit=Securegcm_Ukey2ClientInit()
|
||||
clientInit.version=1
|
||||
clientInit.random=Data.randomData(length: 32)
|
||||
clientInit.nextProtocol="AES_256_CBC-HMAC_SHA256"
|
||||
var sha=SHA512()
|
||||
sha.update(data: ukeyClientFinishMsgData!)
|
||||
var commitment=Securegcm_Ukey2ClientInit.CipherCommitment()
|
||||
commitment.commitment=Data(sha.finalize())
|
||||
commitment.handshakeCipher = .p256Sha512
|
||||
clientInit.cipherCommitments.append(commitment)
|
||||
frame.messageData=try clientInit.serializedData()
|
||||
|
||||
ukeyClientInitMsgData=try frame.serializedData()
|
||||
sendFrameAsync(ukeyClientInitMsgData!)
|
||||
currentState = .sentUkeyClientInit
|
||||
}
|
||||
|
||||
private func processUkey2ServerInit(frame:Securegcm_Ukey2Message, raw:Data) throws{
|
||||
ukeyServerInitMsgData=raw
|
||||
guard frame.messageType == .serverInit else{
|
||||
sendUkey2Alert(type: .badMessageType)
|
||||
throw NearbyError.ukey2
|
||||
}
|
||||
let serverInit=try Securegcm_Ukey2ServerInit(serializedData: frame.messageData)
|
||||
guard serverInit.version==1 else{
|
||||
sendUkey2Alert(type: .badVersion)
|
||||
throw NearbyError.ukey2
|
||||
}
|
||||
guard serverInit.random.count==32 else{
|
||||
sendUkey2Alert(type: .badRandom)
|
||||
throw NearbyError.ukey2
|
||||
}
|
||||
guard serverInit.handshakeCipher == .p256Sha512 else{
|
||||
sendUkey2Alert(type: .badHandshakeCipher)
|
||||
throw NearbyError.ukey2
|
||||
}
|
||||
|
||||
let serverKey=try Securemessage_GenericPublicKey(serializedData: serverInit.publicKey)
|
||||
try finalizeKeyExchange(peerKey: serverKey)
|
||||
sendFrameAsync(ukeyClientFinishMsgData!)
|
||||
currentState = .sentUkeyClientFinish
|
||||
|
||||
var resp=Location_Nearby_Connections_OfflineFrame()
|
||||
resp.version = .v1
|
||||
resp.v1=Location_Nearby_Connections_V1Frame()
|
||||
resp.v1.type = .connectionResponse
|
||||
resp.v1.connectionResponse=Location_Nearby_Connections_ConnectionResponseFrame()
|
||||
resp.v1.connectionResponse.response = .accept
|
||||
resp.v1.connectionResponse.status=0
|
||||
resp.v1.connectionResponse.osInfo=Location_Nearby_Connections_OsInfo()
|
||||
resp.v1.connectionResponse.osInfo.type = .apple
|
||||
sendFrameAsync(try resp.serializedData())
|
||||
|
||||
encryptionDone=true
|
||||
delegate?.outboundConnectionWasEstablished(connection: self)
|
||||
}
|
||||
|
||||
private func processConnectionResponse(frame:Location_Nearby_Connections_OfflineFrame) throws{
|
||||
#if DEBUG
|
||||
print("connection response: \(frame)")
|
||||
#endif
|
||||
guard frame.version == .v1 else {throw NearbyError.protocolError("Unexpected offline frame version \(frame.version)")}
|
||||
guard frame.v1.type == .connectionResponse else {throw NearbyError.protocolError("Unexpected frame type \(frame.v1.type)")}
|
||||
guard frame.v1.connectionResponse.response == .accept else {throw NearbyError.protocolError("Connection was rejected by recipient")}
|
||||
|
||||
var pairedEncryption=Sharing_Nearby_Frame()
|
||||
pairedEncryption.version = .v1
|
||||
pairedEncryption.v1=Sharing_Nearby_V1Frame()
|
||||
pairedEncryption.v1.type = .pairedKeyEncryption
|
||||
pairedEncryption.v1.pairedKeyEncryption=Sharing_Nearby_PairedKeyEncryptionFrame()
|
||||
pairedEncryption.v1.pairedKeyEncryption.secretIDHash=Data.randomData(length: 6)
|
||||
pairedEncryption.v1.pairedKeyEncryption.signedData=Data.randomData(length: 72)
|
||||
try sendTransferSetupFrame(pairedEncryption)
|
||||
|
||||
currentState = .sentPairedKeyEncryption
|
||||
}
|
||||
|
||||
private func processPairedKeyEncryption(frame:Sharing_Nearby_Frame) throws{
|
||||
guard frame.hasV1, frame.v1.hasPairedKeyEncryption else { throw NearbyError.requiredFieldMissing }
|
||||
var pairedResult=Sharing_Nearby_Frame()
|
||||
pairedResult.version = .v1
|
||||
pairedResult.v1=Sharing_Nearby_V1Frame()
|
||||
pairedResult.v1.type = .pairedKeyResult
|
||||
pairedResult.v1.pairedKeyResult=Sharing_Nearby_PairedKeyResultFrame()
|
||||
pairedResult.v1.pairedKeyResult.status = .unable
|
||||
try sendTransferSetupFrame(pairedResult)
|
||||
currentState = .sentPairedKeyResult
|
||||
}
|
||||
|
||||
private func processPairedKeyResult(frame:Sharing_Nearby_Frame) throws{
|
||||
guard frame.hasV1, frame.v1.hasPairedKeyResult else { throw NearbyError.requiredFieldMissing }
|
||||
|
||||
var introduction=Sharing_Nearby_Frame()
|
||||
introduction.version = .v1
|
||||
introduction.v1.type = .introduction
|
||||
if urlsToSend.count==1 && urlsToSend[0].scheme != "file"{
|
||||
var meta=Sharing_Nearby_TextMetadata()
|
||||
meta.type = .url
|
||||
meta.textTitle=urlsToSend[0].host ?? "URL"
|
||||
meta.size=Int64(urlsToSend[0].absoluteString.utf8.count)
|
||||
meta.payloadID=Int64.random(in: Int64.min...Int64.max)
|
||||
introduction.v1.introduction.textMetadata.append(meta)
|
||||
}else{
|
||||
for url in urlsToSend{
|
||||
guard url.scheme=="file" else {continue}
|
||||
var meta=Sharing_Nearby_FileMetadata()
|
||||
meta.name=OutboundNearbyConnection.sanitizeFileName(name: url.lastPathComponent)
|
||||
let attrs=try FileManager.default.attributesOfItem(atPath: url.path)
|
||||
meta.size=(attrs[FileAttributeKey.size] as! NSNumber).int64Value
|
||||
let typeID=try? url.resourceValues(forKeys: [.typeIdentifierKey]).typeIdentifier
|
||||
meta.mimeType="application/octet-stream"
|
||||
if let typeID=typeID{
|
||||
let type=UTType(typeID)
|
||||
if let type=type, let mimeType=type.preferredMIMEType{
|
||||
meta.mimeType=mimeType
|
||||
}
|
||||
}
|
||||
if meta.mimeType.starts(with: "image/"){
|
||||
meta.type = .image
|
||||
}else if meta.mimeType.starts(with: "video/"){
|
||||
meta.type = .video
|
||||
}else if(meta.mimeType.starts(with: "audio/")){
|
||||
meta.type = .audio
|
||||
}else if(url.pathExtension.lowercased()=="apk"){
|
||||
meta.type = .app
|
||||
}else{
|
||||
meta.type = .unknown
|
||||
}
|
||||
meta.payloadID=Int64.random(in: Int64.min...Int64.max)
|
||||
queue.append(OutgoingFileTransfer(url: url, payloadID: meta.payloadID, handle: try FileHandle(forReadingFrom: url), totalBytes: meta.size, currentOffset: 0))
|
||||
introduction.v1.introduction.fileMetadata.append(meta)
|
||||
totalBytesToSend+=meta.size
|
||||
}
|
||||
}
|
||||
#if DEBUG
|
||||
print("sent introduction: \(introduction)")
|
||||
#endif
|
||||
try sendTransferSetupFrame(introduction)
|
||||
|
||||
currentState = .sentIntroduction
|
||||
}
|
||||
|
||||
private func processConsent(frame:Sharing_Nearby_Frame) throws{
|
||||
guard frame.version == .v1, frame.v1.type == .response else {throw NearbyError.requiredFieldMissing}
|
||||
switch frame.v1.connectionResponse.status{
|
||||
case .accept:
|
||||
currentState = .sendingFiles
|
||||
delegate?.outboundConnectionTransferAccepted(connection: self)
|
||||
try sendNextFileChunk()
|
||||
case .reject, .unknown:
|
||||
delegate?.outboundConnection(connection: self, failedWithError: NearbyError.canceled(reason: .userRejected))
|
||||
try sendDisconnectionAndDisconnect()
|
||||
case .notEnoughSpace:
|
||||
delegate?.outboundConnection(connection: self, failedWithError: NearbyError.canceled(reason: .notEnoughSpace))
|
||||
try sendDisconnectionAndDisconnect()
|
||||
case .timedOut:
|
||||
delegate?.outboundConnection(connection: self, failedWithError: NearbyError.canceled(reason: .timedOut))
|
||||
try sendDisconnectionAndDisconnect()
|
||||
case .unsupportedAttachmentType:
|
||||
delegate?.outboundConnection(connection: self, failedWithError: NearbyError.canceled(reason: .unsupportedType))
|
||||
try sendDisconnectionAndDisconnect()
|
||||
}
|
||||
}
|
||||
|
||||
private func sendNextFileChunk() throws{
|
||||
print("SEND NEXT: \(Thread.current)")
|
||||
if cancelled{
|
||||
return
|
||||
}
|
||||
if currentTransfer==nil || currentTransfer?.currentOffset==currentTransfer?.totalBytes{
|
||||
if currentTransfer != nil && currentTransfer?.handle != nil{
|
||||
try currentTransfer?.handle?.close()
|
||||
}
|
||||
if queue.isEmpty{
|
||||
#if DEBUG
|
||||
print("Disconnecting because all files have been transferred")
|
||||
#endif
|
||||
try sendDisconnectionAndDisconnect()
|
||||
delegate?.outboundConnectionTransferFinished(connection: self)
|
||||
return
|
||||
}
|
||||
currentTransfer=queue.removeFirst()
|
||||
}
|
||||
|
||||
guard let fileBuffer=try currentTransfer!.handle!.read(upToCount: 512*1024) else{
|
||||
throw NearbyError.inputOutput(cause: Errno.ioError)
|
||||
}
|
||||
|
||||
var transfer=Location_Nearby_Connections_PayloadTransferFrame()
|
||||
transfer.packetType = .data
|
||||
transfer.payloadChunk.offset=currentTransfer!.currentOffset
|
||||
transfer.payloadChunk.flags=0
|
||||
transfer.payloadChunk.body=fileBuffer
|
||||
transfer.payloadHeader.id=currentTransfer!.payloadID
|
||||
transfer.payloadHeader.type = .file
|
||||
transfer.payloadHeader.totalSize=Int64(currentTransfer!.totalBytes)
|
||||
transfer.payloadHeader.isSensitive=false
|
||||
currentTransfer!.currentOffset+=Int64(fileBuffer.count)
|
||||
|
||||
var wrapper=Location_Nearby_Connections_OfflineFrame()
|
||||
wrapper.version = .v1
|
||||
wrapper.v1=Location_Nearby_Connections_V1Frame()
|
||||
wrapper.v1.type = .payloadTransfer
|
||||
wrapper.v1.payloadTransfer=transfer
|
||||
try encryptAndSendOfflineFrame(wrapper, completion: {
|
||||
do{
|
||||
try self.sendNextFileChunk()
|
||||
}catch{
|
||||
self.lastError=error
|
||||
self.protocolError()
|
||||
}
|
||||
})
|
||||
#if DEBUG
|
||||
print("sent file chunk, current transfer: \(String(describing: currentTransfer))")
|
||||
#endif
|
||||
totalBytesSent+=Int64(fileBuffer.count)
|
||||
delegate?.outboundConnection(connection: self, transferProgress: Double(totalBytesSent)/Double(totalBytesToSend))
|
||||
|
||||
if currentTransfer!.currentOffset==currentTransfer!.totalBytes{
|
||||
// Signal end of file (yes, all this for one bit)
|
||||
var transfer=Location_Nearby_Connections_PayloadTransferFrame()
|
||||
transfer.packetType = .data
|
||||
transfer.payloadChunk.offset=currentTransfer!.currentOffset
|
||||
transfer.payloadChunk.flags=1 // <- this one here
|
||||
transfer.payloadHeader.id=currentTransfer!.payloadID
|
||||
transfer.payloadHeader.type = .file
|
||||
transfer.payloadHeader.totalSize=Int64(currentTransfer!.totalBytes)
|
||||
transfer.payloadHeader.isSensitive=false
|
||||
|
||||
var wrapper=Location_Nearby_Connections_OfflineFrame()
|
||||
wrapper.version = .v1
|
||||
wrapper.v1=Location_Nearby_Connections_V1Frame()
|
||||
wrapper.v1.type = .payloadTransfer
|
||||
wrapper.v1.payloadTransfer=transfer
|
||||
try encryptAndSendOfflineFrame(wrapper)
|
||||
#if DEBUG
|
||||
print("sent EOF, current transfer: \(String(describing: currentTransfer))")
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private static func sanitizeFileName(name:String)->String{
|
||||
return name.replacingOccurrences(of: "[\\/\\\\?%\\*:\\|\"<>=]", with: "_", options: .regularExpression)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate struct OutgoingFileTransfer{
|
||||
let url:URL
|
||||
let payloadID:Int64
|
||||
let handle:FileHandle?
|
||||
let totalBytes:Int64
|
||||
var currentOffset:Int64
|
||||
}
|
||||
|
||||
protocol OutboundNearbyConnectionDelegate{
|
||||
func outboundConnectionWasEstablished(connection:OutboundNearbyConnection)
|
||||
func outboundConnection(connection:OutboundNearbyConnection, transferProgress:Double)
|
||||
func outboundConnectionTransferAccepted(connection:OutboundNearbyConnection)
|
||||
func outboundConnection(connection:OutboundNearbyConnection, failedWithError:Error)
|
||||
func outboundConnectionTransferFinished(connection:OutboundNearbyConnection)
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+2
-1
@@ -59,6 +59,7 @@ sequenceDiagram
|
||||
Server->>Client: UKEY2 ServerInit
|
||||
Client->>Server: UKEY2 ClientFinish
|
||||
Server->>Client: Connection response
|
||||
Client->>Server: Connection response
|
||||
Note over Server, Client: All following packets are encrypted
|
||||
Server->>Client: Paired key encryption
|
||||
Client->>Server: Paired key encryption
|
||||
@@ -134,7 +135,7 @@ The **authentication string** is used for out-of-band key verification. Nearby S
|
||||
|
||||
#### Connection response
|
||||
|
||||
After the key exchange is complete, the server sends one last plaintext message to the client: a connection response. It's a subtype of offline frame saying that the server has accepted the connection. All the following communication is encrypted and wrapped in the payload layer.
|
||||
After the key exchange is complete, the server and client send each other one last plaintext message: a connection response. It's a subtype of offline frame saying that the other party has accepted the connection. All the following communication is encrypted and wrapped in the payload layer.
|
||||
|
||||
### The encryption layer
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1005 B |
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "16.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "32.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NFiles</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@files@</string>
|
||||
<key>files</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%d file</string>
|
||||
<key>other</key>
|
||||
<string>%d files</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,264 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="21701" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21701"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="ShareViewController" customModule="ShareExtension" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="contentWrap" destination="ACO-U2-AZj" id="PAE-Pw-Vyx"/>
|
||||
<outlet property="filesIcon" destination="afM-Om-nLQ" id="I58-a3-SBZ"/>
|
||||
<outlet property="filesLabel" destination="OEz-QK-nem" id="his-3f-6kx"/>
|
||||
<outlet property="largeProgress" destination="fCj-wF-rEj" id="jEg-Uc-Ur7"/>
|
||||
<outlet property="listView" destination="gZI-Zb-Blx" id="VOC-3j-Pwy"/>
|
||||
<outlet property="listViewWrapper" destination="A4d-Qt-PsT" id="8g5-VW-MIb"/>
|
||||
<outlet property="loadingOverlay" destination="fLZ-IA-bPS" id="84g-Ef-GzW"/>
|
||||
<outlet property="progressDeviceIcon" destination="84U-lB-SRB" id="LEL-BY-2FY"/>
|
||||
<outlet property="progressDeviceIconWrap" destination="jBg-Ih-Gac" id="69x-6y-z0c"/>
|
||||
<outlet property="progressDeviceName" destination="dJc-gw-4ux" id="ZE4-Kx-5hv"/>
|
||||
<outlet property="progressDeviceSecondaryIcon" destination="BEO-kU-a2Q" id="iks-QR-2Od"/>
|
||||
<outlet property="progressProgressBar" destination="J5x-hu-Kn5" id="vYH-DN-03b"/>
|
||||
<outlet property="progressState" destination="y8I-D3-scQ" id="Tj3-xa-8VG"/>
|
||||
<outlet property="progressView" destination="Q9K-dc-THx" id="LlE-4d-mI5"/>
|
||||
<outlet property="view" destination="1" id="2"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<customView translatesAutoresizingMaskIntoConstraints="NO" id="1">
|
||||
<rect key="frame" x="0.0" y="0.0" width="404" height="248"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="NVE-vN-dkz">
|
||||
<rect key="frame" x="325" y="3" width="76" height="32"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="60" id="cP1-hK-9ZX"/>
|
||||
</constraints>
|
||||
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="6Up-t3-mwm">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
Gw
|
||||
</string>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="cancel:" target="-2" id="Qav-AK-DGt"/>
|
||||
</connections>
|
||||
</button>
|
||||
<stackView distribution="fill" orientation="horizontal" alignment="top" spacing="5" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="1xF-vr-5sH">
|
||||
<rect key="frame" x="160" y="222" width="84" height="16"/>
|
||||
<subviews>
|
||||
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="4M6-D5-WIf">
|
||||
<rect key="frame" x="0.0" y="0.0" width="16" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="16" id="BOe-aZ-Njc"/>
|
||||
<constraint firstAttribute="height" constant="16" id="zLg-1a-wlZ"/>
|
||||
</constraints>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="NearDropIcon" id="q3u-Am-ZIA"/>
|
||||
</imageView>
|
||||
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="aNc-0i-CWK">
|
||||
<rect key="frame" x="19" y="0.0" width="67" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="left" title="NearDrop" allowsEditingTextAttributes="YES" id="0xp-rC-2gr">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<visibilityPriorities>
|
||||
<integer value="1000"/>
|
||||
<integer value="1000"/>
|
||||
</visibilityPriorities>
|
||||
<customSpacing>
|
||||
<real value="3.4028234663852886e+38"/>
|
||||
<real value="3.4028234663852886e+38"/>
|
||||
</customSpacing>
|
||||
</stackView>
|
||||
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="afM-Om-nLQ">
|
||||
<rect key="frame" x="10" y="196" width="16" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="16" id="3NE-mW-Jk0"/>
|
||||
<constraint firstAttribute="height" constant="16" id="koV-ob-gSn"/>
|
||||
</constraints>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="jOq-9r-V1W"/>
|
||||
</imageView>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="OEz-QK-nem">
|
||||
<rect key="frame" x="29" y="196" width="367" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingMiddle" truncatesLastVisibleLine="YES" id="opy-Bj-x6u">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<customView translatesAutoresizingMaskIntoConstraints="NO" id="ACO-U2-AZj">
|
||||
<rect key="frame" x="0.0" y="40" width="404" height="146"/>
|
||||
</customView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="OEz-QK-nem" firstAttribute="top" secondItem="1xF-vr-5sH" secondAttribute="bottom" constant="10" id="852-oh-vM0"/>
|
||||
<constraint firstItem="OEz-QK-nem" firstAttribute="leading" secondItem="afM-Om-nLQ" secondAttribute="trailing" constant="5" id="CIj-Dk-Md0"/>
|
||||
<constraint firstAttribute="trailing" secondItem="ACO-U2-AZj" secondAttribute="trailing" id="HIg-sX-IK0"/>
|
||||
<constraint firstItem="ACO-U2-AZj" firstAttribute="leading" secondItem="1" secondAttribute="leading" id="IGL-2g-wpe"/>
|
||||
<constraint firstItem="afM-Om-nLQ" firstAttribute="leading" secondItem="1" secondAttribute="leading" constant="10" id="LK2-cg-qcY"/>
|
||||
<constraint firstItem="NVE-vN-dkz" firstAttribute="top" secondItem="ACO-U2-AZj" secondAttribute="bottom" constant="10" id="RSD-PX-W0H"/>
|
||||
<constraint firstAttribute="bottom" secondItem="NVE-vN-dkz" secondAttribute="bottom" constant="10" id="USG-Gg-of3"/>
|
||||
<constraint firstAttribute="trailing" secondItem="NVE-vN-dkz" secondAttribute="trailing" constant="10" id="Z6U-4n-4tO"/>
|
||||
<constraint firstItem="afM-Om-nLQ" firstAttribute="top" secondItem="1" secondAttribute="top" constant="36" id="fUG-kT-wb0"/>
|
||||
<constraint firstItem="1xF-vr-5sH" firstAttribute="centerX" secondItem="1" secondAttribute="centerX" id="sCA-yf-S9L"/>
|
||||
<constraint firstItem="1xF-vr-5sH" firstAttribute="top" secondItem="1" secondAttribute="top" constant="10" id="tFn-vk-9Cq"/>
|
||||
<constraint firstAttribute="trailing" secondItem="OEz-QK-nem" secondAttribute="trailing" constant="10" id="vyd-BI-oDH"/>
|
||||
<constraint firstItem="ACO-U2-AZj" firstAttribute="top" secondItem="OEz-QK-nem" secondAttribute="bottom" constant="10" id="x5r-wc-s4x"/>
|
||||
</constraints>
|
||||
<point key="canvasLocation" x="140" y="-36"/>
|
||||
</customView>
|
||||
<stackView wantsLayer="YES" distribution="equalSpacing" orientation="vertical" alignment="centerX" spacing="0.0" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" id="fLZ-IA-bPS">
|
||||
<rect key="frame" x="0.0" y="0.0" width="521" height="100"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<subviews>
|
||||
<progressIndicator maxValue="100" indeterminate="YES" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="fCj-wF-rEj">
|
||||
<rect key="frame" x="245" y="68" width="32" height="32"/>
|
||||
</progressIndicator>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="27P-yf-gUd">
|
||||
<rect key="frame" x="189" y="42" width="144" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="Looking for devices..." id="NaJ-Wx-Pim">
|
||||
<font key="font" textStyle="headline" name=".SFNS-Bold"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="V5W-Rm-YVM">
|
||||
<rect key="frame" x="-2" y="0.0" width="525" height="32"/>
|
||||
<textFieldCell key="cell" alignment="center" title="If you don't see your device, open "Google Files" app and tap "Receive" on the Nearby Share tab." id="vla-gF-eJo">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="27P-yf-gUd" firstAttribute="top" secondItem="fCj-wF-rEj" secondAttribute="bottom" constant="10" id="87W-ec-fMh"/>
|
||||
<constraint firstItem="V5W-Rm-YVM" firstAttribute="top" secondItem="27P-yf-gUd" secondAttribute="bottom" constant="10" id="OSI-wI-LcY"/>
|
||||
</constraints>
|
||||
<visibilityPriorities>
|
||||
<integer value="1000"/>
|
||||
<integer value="1000"/>
|
||||
<integer value="1000"/>
|
||||
</visibilityPriorities>
|
||||
<customSpacing>
|
||||
<real value="3.4028234663852886e+38"/>
|
||||
<real value="3.4028234663852886e+38"/>
|
||||
<real value="3.4028234663852886e+38"/>
|
||||
</customSpacing>
|
||||
<point key="canvasLocation" x="660.5" y="-1.5"/>
|
||||
</stackView>
|
||||
<stackView wantsLayer="YES" distribution="equalSpacing" orientation="vertical" alignment="centerX" spacing="1" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" id="Q9K-dc-THx">
|
||||
<rect key="frame" x="0.0" y="0.0" width="384" height="130"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<customView translatesAutoresizingMaskIntoConstraints="NO" id="jBg-Ih-Gac">
|
||||
<rect key="frame" x="168" y="82" width="48" height="48"/>
|
||||
<subviews>
|
||||
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="84U-lB-SRB">
|
||||
<rect key="frame" x="0.0" y="0.0" width="48" height="48"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="48" id="9ld-k4-EkL"/>
|
||||
<constraint firstAttribute="height" constant="48" id="XA4-2k-hqR"/>
|
||||
</constraints>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="rb3-78-PXe"/>
|
||||
</imageView>
|
||||
<imageView hidden="YES" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="BEO-kU-a2Q">
|
||||
<rect key="frame" x="24" y="-8" width="32" height="32"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="32" id="05I-Ef-84Z"/>
|
||||
<constraint firstAttribute="width" constant="32" id="V0g-Fz-HWH"/>
|
||||
</constraints>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="NSCaution" id="k1c-Xl-fTQ"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="BEO-kU-a2Q" secondAttribute="trailing" constant="-8" id="59U-Zi-ESa"/>
|
||||
<constraint firstAttribute="bottom" secondItem="BEO-kU-a2Q" secondAttribute="bottom" constant="-8" id="8ty-pq-miR"/>
|
||||
<constraint firstItem="84U-lB-SRB" firstAttribute="top" secondItem="jBg-Ih-Gac" secondAttribute="top" id="MB9-UI-2mb"/>
|
||||
<constraint firstItem="84U-lB-SRB" firstAttribute="leading" secondItem="jBg-Ih-Gac" secondAttribute="leading" id="b23-hJ-aWT"/>
|
||||
<constraint firstAttribute="trailing" secondItem="84U-lB-SRB" secondAttribute="trailing" id="eKl-3V-K49"/>
|
||||
<constraint firstAttribute="bottom" secondItem="84U-lB-SRB" secondAttribute="bottom" id="uh8-T1-Vd8"/>
|
||||
</constraints>
|
||||
</customView>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="dJc-gw-4ux">
|
||||
<rect key="frame" x="-2" y="56" width="388" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" alignment="center" id="QzI-qQ-OBb">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<progressIndicator maxValue="100" doubleValue="50" indeterminate="YES" style="bar" translatesAutoresizingMaskIntoConstraints="NO" id="J5x-hu-Kn5">
|
||||
<rect key="frame" x="92" y="25" width="200" height="22"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="200" id="bM2-Ih-2UC"/>
|
||||
</constraints>
|
||||
</progressIndicator>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="y8I-D3-scQ">
|
||||
<rect key="frame" x="165" y="0.0" width="54" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" id="Iin-UY-IE5">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="y8I-D3-scQ" firstAttribute="top" secondItem="J5x-hu-Kn5" secondAttribute="bottom" constant="10" id="4EB-xc-Qni"/>
|
||||
<constraint firstItem="dJc-gw-4ux" firstAttribute="leading" secondItem="Q9K-dc-THx" secondAttribute="leading" id="IXP-am-A8h"/>
|
||||
<constraint firstItem="J5x-hu-Kn5" firstAttribute="top" secondItem="dJc-gw-4ux" secondAttribute="bottom" constant="10" id="Zav-Ho-QEJ"/>
|
||||
<constraint firstItem="dJc-gw-4ux" firstAttribute="top" secondItem="jBg-Ih-Gac" secondAttribute="bottom" constant="10" id="bIE-Es-ZbK"/>
|
||||
<constraint firstAttribute="trailing" secondItem="dJc-gw-4ux" secondAttribute="trailing" id="bKd-Vz-YA1"/>
|
||||
</constraints>
|
||||
<visibilityPriorities>
|
||||
<integer value="1000"/>
|
||||
<integer value="1000"/>
|
||||
<integer value="1000"/>
|
||||
<integer value="1000"/>
|
||||
</visibilityPriorities>
|
||||
<customSpacing>
|
||||
<real value="3.4028234663852886e+38"/>
|
||||
<real value="3.4028234663852886e+38"/>
|
||||
<real value="3.4028234663852886e+38"/>
|
||||
<real value="3.4028234663852886e+38"/>
|
||||
</customSpacing>
|
||||
<point key="canvasLocation" x="4" y="-288"/>
|
||||
</stackView>
|
||||
<scrollView wantsLayer="YES" autohidesScrollers="YES" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" id="A4d-Qt-PsT">
|
||||
<rect key="frame" x="0.0" y="0.0" width="271" height="172"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<clipView key="contentView" id="cCr-8P-Lya">
|
||||
<rect key="frame" x="1" y="1" width="269" height="170"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<collectionView id="gZI-Zb-Blx">
|
||||
<rect key="frame" x="0.0" y="0.0" width="269" height="170"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES"/>
|
||||
<collectionViewFlowLayout key="collectionViewLayout" minimumInteritemSpacing="10" minimumLineSpacing="10" id="ATS-Jd-odC">
|
||||
<size key="itemSize" width="50" height="50"/>
|
||||
</collectionViewFlowLayout>
|
||||
<color key="primaryBackgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</collectionView>
|
||||
</subviews>
|
||||
</clipView>
|
||||
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="3j2-z3-G1e">
|
||||
<rect key="frame" x="-100" y="-100" width="402" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="wty-KV-JAg">
|
||||
<rect key="frame" x="380" y="1" width="16" height="142"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<point key="canvasLocation" x="-397.5" y="30"/>
|
||||
</scrollView>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="NSCaution" width="32" height="32"/>
|
||||
<image name="NearDropIcon" width="16" height="16"/>
|
||||
</resources>
|
||||
</document>
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// DeviceListCell.swift
|
||||
// ShareExtension
|
||||
//
|
||||
// Created by Grishka on 20.09.2023.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
|
||||
class DeviceListCell:NSCollectionViewItem {
|
||||
public var clickHandler:(()->Void)?
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
let btn:NSButton=view as! NSButton
|
||||
btn.isEnabled=true
|
||||
btn.setButtonType(.momentaryPushIn)
|
||||
btn.action=#selector(onClick)
|
||||
btn.target=self
|
||||
}
|
||||
|
||||
@IBAction func onClick(_ sender:Any?){
|
||||
guard let handler=clickHandler else {return}
|
||||
handler()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="21701" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21701"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="DeviceListCell" customModule="ShareExtension" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="view" destination="Hz6-mo-xeY" id="gtq-ca-SBh"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<customView misplaced="YES" id="Hz6-mo-xeY" customClass="NSButton">
|
||||
<rect key="frame" x="0.0" y="0.0" width="206" height="82"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="QPM-sE-maP">
|
||||
<rect key="frame" x="-2" y="0.0" width="210" height="32"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="32" id="TBS-IE-2DT"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" truncatesLastVisibleLine="YES" alignment="center" title="Label" id="cXi-mf-qP0">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="O1J-am-CoK">
|
||||
<rect key="frame" x="79" y="42" width="48" height="48"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="48" id="KYr-2E-wCO"/>
|
||||
<constraint firstAttribute="height" constant="48" id="nik-I6-YgB"/>
|
||||
</constraints>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="QGU-rR-rJQ"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="O1J-am-CoK" firstAttribute="centerX" secondItem="Hz6-mo-xeY" secondAttribute="centerX" id="R76-2L-pXb"/>
|
||||
<constraint firstAttribute="bottom" secondItem="QPM-sE-maP" secondAttribute="bottom" id="Ryn-nY-yZp"/>
|
||||
<constraint firstItem="O1J-am-CoK" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" id="ZFA-S9-fpT"/>
|
||||
<constraint firstAttribute="trailing" secondItem="QPM-sE-maP" secondAttribute="trailing" id="eOl-Va-HXq"/>
|
||||
<constraint firstItem="QPM-sE-maP" firstAttribute="top" secondItem="O1J-am-CoK" secondAttribute="bottom" constant="10" id="leg-nD-mHB"/>
|
||||
<constraint firstItem="QPM-sE-maP" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" id="vvH-Hq-iUk"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<action selector="onClick:" target="-2" id="mve-gp-q4i"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="-60" y="150"/>
|
||||
</customView>
|
||||
<collectionViewItem id="1Lb-iJ-Nar" customClass="DeviceListCell" customModule="ShareExtension" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="imageView" destination="O1J-am-CoK" id="zQn-mV-bRy"/>
|
||||
<outlet property="textField" destination="QPM-sE-maP" id="s0j-Qq-7iA"/>
|
||||
<outlet property="view" destination="Hz6-mo-xeY" id="gZn-20-o77"/>
|
||||
</connections>
|
||||
</collectionViewItem>
|
||||
</objects>
|
||||
</document>
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionAttributes</key>
|
||||
<dict>
|
||||
<key>NSExtensionActivationRule</key>
|
||||
<string>SUBQUERY (
|
||||
extensionItems,
|
||||
$extensionItem,
|
||||
SUBQUERY (
|
||||
$extensionItem.attachments,
|
||||
$attachment,
|
||||
ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.file-url"
|
||||
AND NOT (ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.folder")
|
||||
).@count == $extensionItem.attachments.@count
|
||||
).@count > 0</string>
|
||||
</dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.share-services</string>
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).ShareViewController</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-only</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.server</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,271 @@
|
||||
//
|
||||
// ShareViewController.swift
|
||||
// ShareExtension
|
||||
//
|
||||
// Created by Grishka on 12.09.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Cocoa
|
||||
import NearbyShare
|
||||
|
||||
class ShareViewController: NSViewController, ShareExtensionDelegate{
|
||||
|
||||
private var urls:[URL]=[]
|
||||
private var foundDevices:[RemoteDeviceInfo]=[]
|
||||
private var chosenDevice:RemoteDeviceInfo?
|
||||
private var lastError:Error?
|
||||
|
||||
@IBOutlet var filesIcon:NSImageView?
|
||||
@IBOutlet var filesLabel:NSTextField?
|
||||
@IBOutlet var loadingOverlay:NSStackView?
|
||||
@IBOutlet var largeProgress:NSProgressIndicator?
|
||||
@IBOutlet var listView:NSCollectionView?
|
||||
@IBOutlet var listViewWrapper:NSView?
|
||||
@IBOutlet var contentWrap:NSView?
|
||||
@IBOutlet var progressView:NSView?
|
||||
@IBOutlet var progressDeviceIcon:NSImageView?
|
||||
@IBOutlet var progressDeviceName:NSTextField?
|
||||
@IBOutlet var progressProgressBar:NSProgressIndicator?
|
||||
@IBOutlet var progressState:NSTextField?
|
||||
@IBOutlet var progressDeviceIconWrap:NSView?
|
||||
@IBOutlet var progressDeviceSecondaryIcon:NSImageView?
|
||||
|
||||
override var nibName: NSNib.Name? {
|
||||
return NSNib.Name("ShareViewController")
|
||||
}
|
||||
|
||||
override func loadView() {
|
||||
super.loadView()
|
||||
|
||||
// Insert code here to customize the view
|
||||
let item = self.extensionContext!.inputItems[0] as! NSExtensionItem
|
||||
if let attachments = item.attachments {
|
||||
for attachment in attachments as NSArray{
|
||||
let provider=attachment as! NSItemProvider
|
||||
provider.loadItem(forTypeIdentifier: kUTTypeURL as String) { data, err in
|
||||
if let url=URL(dataRepresentation: data as! Data, relativeTo: nil, isAbsolute: false){
|
||||
self.urls.append(url)
|
||||
if self.urls.count==attachments.count{
|
||||
DispatchQueue.main.async {
|
||||
self.urlsReady()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let cancelError = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil)
|
||||
self.extensionContext!.cancelRequest(withError: cancelError)
|
||||
return
|
||||
}
|
||||
|
||||
contentWrap!.addSubview(listViewWrapper!)
|
||||
contentWrap!.addSubview(loadingOverlay!)
|
||||
contentWrap!.addSubview(progressView!)
|
||||
progressView!.isHidden=true
|
||||
|
||||
listViewWrapper!.translatesAutoresizingMaskIntoConstraints=false
|
||||
loadingOverlay!.translatesAutoresizingMaskIntoConstraints=false
|
||||
progressView!.translatesAutoresizingMaskIntoConstraints=false
|
||||
NSLayoutConstraint.activate([
|
||||
NSLayoutConstraint(item: listViewWrapper!, attribute: .width, relatedBy: .equal, toItem: contentWrap, attribute: .width, multiplier: 1, constant: 0),
|
||||
NSLayoutConstraint(item: listViewWrapper!, attribute: .height, relatedBy: .equal, toItem: contentWrap, attribute: .height, multiplier: 1, constant: 0),
|
||||
|
||||
NSLayoutConstraint(item: loadingOverlay!, attribute: .width, relatedBy: .equal, toItem: contentWrap, attribute: .width, multiplier: 1, constant: 0),
|
||||
NSLayoutConstraint(item: loadingOverlay!, attribute: .centerY, relatedBy: .equal, toItem: contentWrap, attribute: .centerY, multiplier: 1, constant: 0),
|
||||
|
||||
NSLayoutConstraint(item: progressView!, attribute: .width, relatedBy: .equal, toItem: contentWrap, attribute: .width, multiplier: 1, constant: 0),
|
||||
NSLayoutConstraint(item: progressView!, attribute: .centerY, relatedBy: .equal, toItem: contentWrap, attribute: .centerY, multiplier: 1, constant: 0)
|
||||
])
|
||||
|
||||
largeProgress!.startAnimation(nil)
|
||||
let flowLayout=NSCollectionViewFlowLayout()
|
||||
flowLayout.itemSize=NSSize(width: 75, height: 90)
|
||||
flowLayout.sectionInset=NSEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
|
||||
flowLayout.minimumInteritemSpacing=10
|
||||
flowLayout.minimumLineSpacing=10
|
||||
listView!.collectionViewLayout=flowLayout
|
||||
listView!.dataSource=self
|
||||
|
||||
progressDeviceIconWrap!.wantsLayer=true
|
||||
progressDeviceIconWrap!.layer!.masksToBounds=false
|
||||
}
|
||||
|
||||
override func viewDidLoad(){
|
||||
super.viewDidLoad()
|
||||
NearbyConnectionManager.shared.startDeviceDiscovery()
|
||||
NearbyConnectionManager.shared.addShareExtensionDelegate(self)
|
||||
}
|
||||
|
||||
override func viewWillDisappear() {
|
||||
if chosenDevice==nil{
|
||||
NearbyConnectionManager.shared.stopDeviceDiscovery()
|
||||
}
|
||||
NearbyConnectionManager.shared.removeShareExtensionDelegate(self)
|
||||
}
|
||||
|
||||
@IBAction func cancel(_ sender: AnyObject?) {
|
||||
if let device=chosenDevice{
|
||||
NearbyConnectionManager.shared.cancelOutgoingTransfer(id: device.id!)
|
||||
}
|
||||
let cancelError = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil)
|
||||
self.extensionContext!.cancelRequest(withError: cancelError)
|
||||
}
|
||||
|
||||
private func urlsReady(){
|
||||
for url in urls{
|
||||
if url.isFileURL{
|
||||
var isDirectory=UnsafeMutablePointer<ObjCBool>.allocate(capacity: 1)
|
||||
if FileManager.default.fileExists(atPath: url.path, isDirectory: isDirectory) && isDirectory.pointee.boolValue{
|
||||
print("Canceling share request because URL \(url) is a directory")
|
||||
let cancelError = NSError(domain: NSCocoaErrorDomain, code: NSUserCancelledError, userInfo: nil)
|
||||
self.extensionContext!.cancelRequest(withError: cancelError)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
if urls.count==1{
|
||||
if urls[0].isFileURL{
|
||||
filesLabel!.stringValue=urls[0].lastPathComponent
|
||||
filesIcon!.image=NSWorkspace.shared.icon(forFile: urls[0].path)
|
||||
}else if urls[0].scheme=="http" || urls[0].scheme=="https"{
|
||||
filesLabel!.stringValue=urls[0].absoluteString
|
||||
filesIcon!.image=NSImage(named: NSImage.networkName)
|
||||
}
|
||||
}else{
|
||||
filesLabel!.stringValue=String.localizedStringWithFormat(NSLocalizedString("NFiles", value: "%d files", comment: ""), urls.count)
|
||||
filesIcon!.image=NSImage(named: NSImage.multipleDocumentsName)
|
||||
}
|
||||
}
|
||||
|
||||
func addDevice(device: RemoteDeviceInfo) {
|
||||
if foundDevices.isEmpty{
|
||||
loadingOverlay?.animator().isHidden=true
|
||||
}
|
||||
foundDevices.append(device)
|
||||
listView?.animator().insertItems(at: [[0, foundDevices.count-1]])
|
||||
}
|
||||
|
||||
func removeDevice(id: String){
|
||||
if chosenDevice != nil{
|
||||
return
|
||||
}
|
||||
for i in foundDevices.indices{
|
||||
if foundDevices[i].id==id{
|
||||
foundDevices.remove(at: i)
|
||||
listView?.animator().deleteItems(at: [[0, i]])
|
||||
break
|
||||
}
|
||||
}
|
||||
if foundDevices.isEmpty{
|
||||
loadingOverlay?.animator().isHidden=false
|
||||
}
|
||||
}
|
||||
|
||||
func connectionWasEstablished(pinCode: String) {
|
||||
progressState?.stringValue=String(format:NSLocalizedString("PinCode", value: "PIN: %@", comment: ""), arguments: [pinCode])
|
||||
progressProgressBar?.isIndeterminate=false
|
||||
progressProgressBar?.maxValue=1000
|
||||
progressProgressBar?.doubleValue=0
|
||||
}
|
||||
|
||||
func connectionFailed(with error: Error) {
|
||||
progressProgressBar?.isIndeterminate=false
|
||||
progressProgressBar?.maxValue=1000
|
||||
progressProgressBar?.doubleValue=0
|
||||
lastError=error
|
||||
if let ne=(error as? NearbyError), case let .canceled(reason)=ne{
|
||||
switch reason{
|
||||
case .userRejected:
|
||||
progressState?.stringValue=NSLocalizedString("TransferDeclined", value: "Declined", comment: "")
|
||||
case .userCanceled:
|
||||
progressState?.stringValue=NSLocalizedString("TransferCanceled", value: "Canceled", comment: "")
|
||||
case .notEnoughSpace:
|
||||
progressState?.stringValue=NSLocalizedString("NotEnoughSpace", value: "Not enough disk space", comment: "")
|
||||
case .unsupportedType:
|
||||
progressState?.stringValue=NSLocalizedString("UnsupportedType", value: "Attachment type not supported", comment: "")
|
||||
case .timedOut:
|
||||
progressState?.stringValue=NSLocalizedString("TransferTimedOut", value: "Timed out", comment: "")
|
||||
}
|
||||
progressDeviceSecondaryIcon?.isHidden=false
|
||||
dismissDelayed()
|
||||
}else{
|
||||
let alert=NSAlert(error: error)
|
||||
alert.beginSheetModal(for: view.window!) { resp in
|
||||
self.extensionContext!.cancelRequest(withError: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func transferAccepted() {
|
||||
progressState?.stringValue=NSLocalizedString("Sending", value: "Sending...", comment: "")
|
||||
}
|
||||
|
||||
func transferProgress(progress: Double) {
|
||||
progressProgressBar!.doubleValue=progress*progressProgressBar!.maxValue
|
||||
}
|
||||
|
||||
func transferFinished() {
|
||||
progressState?.stringValue=NSLocalizedString("TransferFinished", value: "Transfer finished", comment: "")
|
||||
dismissDelayed()
|
||||
}
|
||||
|
||||
func selectDevice(device:RemoteDeviceInfo){
|
||||
NearbyConnectionManager.shared.stopDeviceDiscovery()
|
||||
listViewWrapper?.animator().isHidden=true
|
||||
progressView?.animator().isHidden=false
|
||||
progressDeviceName?.stringValue=device.name
|
||||
progressDeviceIcon?.image=imageForDeviceType(type: device.type)
|
||||
progressProgressBar?.startAnimation(nil)
|
||||
progressState?.stringValue=NSLocalizedString("Connecting", value: "Connecting...", comment: "")
|
||||
chosenDevice=device
|
||||
NearbyConnectionManager.shared.startOutgoingTransfer(deviceID: device.id!, delegate: self, urls: urls)
|
||||
}
|
||||
|
||||
private func dismissDelayed(){
|
||||
DispatchQueue.main.asyncAfter(deadline: .now()+2.0){
|
||||
if let error=self.lastError{
|
||||
self.extensionContext!.cancelRequest(withError: error)
|
||||
}else{
|
||||
self.extensionContext!.completeRequest(returningItems: nil, completionHandler: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func imageForDeviceType(type:RemoteDeviceInfo.DeviceType)->NSImage{
|
||||
let imageName:String
|
||||
switch type{
|
||||
case .tablet:
|
||||
imageName="com.apple.ipad"
|
||||
case .computer:
|
||||
imageName="com.apple.macbookpro-13-unibody"
|
||||
default: // also .phone
|
||||
imageName="com.apple.iphone"
|
||||
}
|
||||
return NSImage(contentsOfFile: "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/\(imageName).icns")!
|
||||
}
|
||||
|
||||
extension ShareViewController:NSCollectionViewDataSource{
|
||||
func numberOfSections(in collectionView: NSCollectionView) -> Int {
|
||||
return 1
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
return foundDevices.count
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
|
||||
let item=collectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "DeviceListCell"), for: indexPath)
|
||||
guard let collectionViewItem = item as? DeviceListCell else {return item}
|
||||
let device=foundDevices[indexPath[1]]
|
||||
collectionViewItem.textField?.stringValue=device.name
|
||||
collectionViewItem.imageView?.image=imageForDeviceType(type: device.type)
|
||||
// TODO maybe there's a better way to handle clicks on collection view items? I'm still new to Apple's way of doing UIs so I may do dumb shit occasionally
|
||||
collectionViewItem.clickHandler={
|
||||
self.selectDevice(device: device)
|
||||
}
|
||||
return collectionViewItem
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NFiles</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@files@</string>
|
||||
<key>files</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%d файл</string>
|
||||
<key>few</key>
|
||||
<string>%d файла</string>
|
||||
<key>many</key>
|
||||
<string>%d файлов</string>
|
||||
<key>other</key>
|
||||
<string>%d файлов</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
/* Class = "NSTextFieldCell"; title = "NearDrop"; ObjectID = "0xp-rC-2gr"; */
|
||||
"0xp-rC-2gr.title" = "NearDrop";
|
||||
|
||||
/* Class = "NSButtonCell"; title = "Cancel"; ObjectID = "6Up-t3-mwm"; */
|
||||
"6Up-t3-mwm.title" = "Отменить";
|
||||
|
||||
/* Class = "NSTextFieldCell"; title = "Looking for devices..."; ObjectID = "NaJ-Wx-Pim"; */
|
||||
"NaJ-Wx-Pim.title" = "Ищу устройства...";
|
||||
|
||||
/* Class = "NSTextFieldCell"; title = "If you don't see your device, open \"Files by Google\" app and tap \"Receive\" on the Nearby Share tab."; ObjectID = "vla-gF-eJo"; */
|
||||
"vla-gF-eJo.title" = "Если Вы не видите своё устройство, откройте приложение \"Google Files\" и нажмите \"Получить\" на вкладке Обмен.";
|
||||
Reference in new issue
Block a user