mirror of
https://github.com/leonboe1/QuickDrop.git
synced 2026-11-11 02:51:11 +00:00
Prefer fixed port if possible
This commit is contained in:
1 parent
b546370e87
commit
cce84e9460
7 files changed
+133
-41
No files matched your search
@@ -21,6 +21,8 @@ import LUI
|
|||||||
|
|
||||||
public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearbyConnectionDelegate, OutboundNearbyConnectionDelegate, ObservableObject {
|
public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearbyConnectionDelegate, OutboundNearbyConnectionDelegate, ObservableObject {
|
||||||
|
|
||||||
|
// MARK: Private Properties
|
||||||
|
|
||||||
#if !EXTENSION
|
#if !EXTENSION
|
||||||
private let sleepManager = SleepManager.shared
|
private let sleepManager = SleepManager.shared
|
||||||
#endif
|
#endif
|
||||||
@@ -32,7 +34,7 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
|
|||||||
private var inboundAppDelegates: [InboundAppDelegate] = []
|
private var inboundAppDelegates: [InboundAppDelegate] = []
|
||||||
private var outgoingTransfers: [String: OutgoingTransferInfo] = [:]
|
private var outgoingTransfers: [String: OutgoingTransferInfo] = [:]
|
||||||
private var startedDeviceDiscovery = false
|
private var startedDeviceDiscovery = false
|
||||||
private var startedAdvertising = false
|
private var startedAdvertising = false { didSet { informAboutStatus() }}
|
||||||
private var browsers: [NWBrowser] = []
|
private var browsers: [NWBrowser] = []
|
||||||
private let serviceTypes = ["_FC9F5ED42C8A._tcp."]
|
private let serviceTypes = ["_FC9F5ED42C8A._tcp."]
|
||||||
private var qrCodePrivateKey: ECPrivateKey?
|
private var qrCodePrivateKey: ECPrivateKey?
|
||||||
@@ -42,24 +44,31 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
|
|||||||
private let connectionMonitorQueue = DispatchQueue(label: "NetworkConnectionMonitorQueue")
|
private let connectionMonitorQueue = DispatchQueue(label: "NetworkConnectionMonitorQueue")
|
||||||
private var securityScopeUrl: URL?
|
private var securityScopeUrl: URL?
|
||||||
private static let customDeviceNameKey = "com.leonboettger.quickdrop.deviceName"
|
private static let customDeviceNameKey = "com.leonboettger.quickdrop.deviceName"
|
||||||
|
private var defaultPort: NWEndpoint.Port = 50362
|
||||||
|
|
||||||
public let endpointID: [UInt8] = generateEndpointID()
|
|
||||||
|
// MARK: Shared Instance
|
||||||
|
|
||||||
public static let shared = NearbyConnectionManager()
|
public static let shared = NearbyConnectionManager()
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: Published State
|
||||||
|
|
||||||
@Published var attachments: AttachmentDetails? = nil
|
@Published var attachments: AttachmentDetails? = nil
|
||||||
@Published var hasLocalNetworkPermission = true
|
@Published var hasLocalNetworkPermission = true
|
||||||
@Published var isConnectedToLocalNetwork = true
|
@Published var isConnectedToLocalNetwork = true { didSet { informAboutStatus() }}
|
||||||
@Published var deviceInfo: EndpointInfo
|
@Published var deviceInfo: EndpointInfo
|
||||||
|
|
||||||
public var connectionUpdateCallback: (Bool) -> Void = { _ in } {
|
|
||||||
didSet {
|
|
||||||
connectionUpdateCallback(isConnectedToLocalNetwork)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// MARK: Public Properties
|
||||||
|
|
||||||
|
public var endpointID: [UInt8] = generateEndpointID(forceRegeneration: false)
|
||||||
|
public var connectionUpdateCallback: (Bool) -> Void = { _ in } { didSet { informAboutStatus() }}
|
||||||
public var changedDeviceNameCallback: () -> Void = { }
|
public var changedDeviceNameCallback: () -> Void = { }
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: Initializers
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
self.deviceInfo = Self.getEndpointInfo()
|
self.deviceInfo = Self.getEndpointInfo()
|
||||||
tcpListener = try! NWListener(using: NWParameters(tls: .none))
|
tcpListener = try! NWListener(using: NWParameters(tls: .none))
|
||||||
@@ -83,7 +92,6 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
|
|||||||
log("[NearbyConnectionManager] Local network lost.")
|
log("[NearbyConnectionManager] Local network lost.")
|
||||||
}
|
}
|
||||||
self.isConnectedToLocalNetwork = isConnected
|
self.isConnectedToLocalNetwork = isConnected
|
||||||
self.connectionUpdateCallback(isConnected)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,6 +121,8 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: Deinitializer
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
self.stopAccessingSaveDirectory()
|
self.stopAccessingSaveDirectory()
|
||||||
}
|
}
|
||||||
@@ -161,22 +171,73 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public func becomeVisible() {
|
public func becomeVisible(randomPort: Bool = false) {
|
||||||
if startedAdvertising {
|
if startedAdvertising {
|
||||||
log("[NearbyConnectionManager] Already advertising, skipping")
|
log("[NearbyConnectionManager] Already advertising, skipping")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log("[NearbyConnectionManager] Becoming visible")
|
log("[NearbyConnectionManager] Becoming visible")
|
||||||
|
let parameters = NWParameters(tls: .none)
|
||||||
|
|
||||||
tcpListener = try! NWListener(using: NWParameters(tls: .none))
|
do {
|
||||||
startedAdvertising = true
|
if randomPort {
|
||||||
|
self.tcpListener = try NWListener(using: parameters)
|
||||||
tcpListener.stateUpdateHandler = { (state: NWListener.State) in
|
} else {
|
||||||
if case .ready = state {
|
self.tcpListener = try NWListener(using: parameters, on: defaultPort)
|
||||||
self.initMDNS()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch {
|
||||||
|
log("[NearbyConnectionManager] Error starting TCP listener: \(error). Trying with random port.")
|
||||||
|
|
||||||
|
runAfter(seconds: 1) {
|
||||||
|
self.becomeVisible(randomPort: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
startedAdvertising = true
|
||||||
|
|
||||||
|
tcpListener.stateUpdateHandler = { state in
|
||||||
|
switch state {
|
||||||
|
case .ready:
|
||||||
|
log("[NearbyConnectionManager] Listener ready")
|
||||||
|
self.initMDNS(forceIDRegeneration: randomPort)
|
||||||
|
|
||||||
|
if let port = self.tcpListener.port, port != self.defaultPort {
|
||||||
|
log("[NearbyConnectionManager] Updated default port to \(port)")
|
||||||
|
self.defaultPort = port
|
||||||
|
}
|
||||||
|
|
||||||
|
case .failed(let error):
|
||||||
|
log("[NearbyConnectionManager] Listener failed: \(error)")
|
||||||
|
self.becomeInvisible()
|
||||||
|
|
||||||
|
runAfter(seconds: 1) {
|
||||||
|
if case .posix(let posixError) = error,
|
||||||
|
posixError == .EADDRINUSE {
|
||||||
|
self.becomeVisible(randomPort: true)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
self.becomeVisible()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case .cancelled:
|
||||||
|
log("[NearbyConnectionManager] Listener cancelled")
|
||||||
|
|
||||||
|
case .setup:
|
||||||
|
log("[NearbyConnectionManager] Listener setup")
|
||||||
|
|
||||||
|
case .waiting(let state):
|
||||||
|
log("[NearbyConnectionManager] Listener waiting: \(state)")
|
||||||
|
|
||||||
|
@unknown default:
|
||||||
|
log("[NearbyConnectionManager] Listener unknown state")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tcpListener.newConnectionHandler = { (connection: NWConnection) in
|
tcpListener.newConnectionHandler = { (connection: NWConnection) in
|
||||||
let id = UUID().uuidString
|
let id = UUID().uuidString
|
||||||
let conn = InboundNearbyConnection(connection: connection, id: id)
|
let conn = InboundNearbyConnection(connection: connection, id: id)
|
||||||
@@ -184,6 +245,7 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
|
|||||||
conn.delegate = self
|
conn.delegate = self
|
||||||
conn.start()
|
conn.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
tcpListener.start(queue: .global(qos: .utility))
|
tcpListener.start(queue: .global(qos: .utility))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,11 +264,13 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static func generateEndpointID() -> [UInt8] {
|
private static func generateEndpointID(forceRegeneration: Bool) -> [UInt8] {
|
||||||
|
|
||||||
// Try to retrieve from UserDefaults
|
// Try to retrieve from UserDefaults
|
||||||
if let savedString = Settings.shared.endpointID,
|
if !forceRegeneration,
|
||||||
|
let savedString = Settings.shared.endpointID,
|
||||||
let savedData = savedString.data(using: .utf8) {
|
let savedData = savedString.data(using: .utf8) {
|
||||||
|
log("[NearbyConnectionManager] Using cached endpoint ID: \(savedString)")
|
||||||
return [UInt8](savedData)
|
return [UInt8](savedData)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,12 +284,16 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
|
|||||||
// Save to UserDefaults as String
|
// Save to UserDefaults as String
|
||||||
let idString = String(bytes: id, encoding: .utf8) ?? ""
|
let idString = String(bytes: id, encoding: .utf8) ?? ""
|
||||||
Settings.shared.endpointID = idString
|
Settings.shared.endpointID = idString
|
||||||
|
log("[NearbyConnectionManager] Storing new endpoint ID: \(idString) ======================================")
|
||||||
|
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private func initMDNS() {
|
private func initMDNS(forceIDRegeneration: Bool) {
|
||||||
|
|
||||||
|
self.endpointID = Self.generateEndpointID(forceRegeneration: forceIDRegeneration)
|
||||||
|
|
||||||
let nameBytes: [UInt8] = [
|
let nameBytes: [UInt8] = [
|
||||||
0x23, // PCP
|
0x23, // PCP
|
||||||
endpointID[0], endpointID[1], endpointID[2], endpointID[3],
|
endpointID[0], endpointID[1], endpointID[2], endpointID[3],
|
||||||
@@ -721,6 +789,14 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: Callbacks
|
||||||
|
|
||||||
|
func informAboutStatus() {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.connectionUpdateCallback(self.isConnectedToLocalNetwork && self.startedAdvertising)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// -- MARK: - Internal Data Model
|
// -- MARK: - Internal Data Model
|
||||||
|
|
||||||
|
|||||||
@@ -1719,8 +1719,8 @@
|
|||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
SHARED_BUILD = 108;
|
SHARED_BUILD = 111;
|
||||||
SHARED_VERSION = 2.10.4;
|
SHARED_VERSION = 2.10.6;
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
@@ -1782,8 +1782,8 @@
|
|||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
SHARED_BUILD = 108;
|
SHARED_BUILD = 111;
|
||||||
SHARED_VERSION = 2.10.4;
|
SHARED_VERSION = 2.10.6;
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
SWIFT_COMPILATION_MODE = wholemodule;
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
"location" : "https://github.com/leonboe1/LUI.git",
|
"location" : "https://github.com/leonboe1/LUI.git",
|
||||||
"state" : {
|
"state" : {
|
||||||
"branch" : "main",
|
"branch" : "main",
|
||||||
"revision" : "170752316e3b728bd06f8fa4073fe18e9a99a5cd"
|
"revision" : "c26d4a19b175cf725deb00b412cf2f8c6df95826"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -337,7 +337,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
|
|||||||
}
|
}
|
||||||
}), hasSheet: false)
|
}), hasSheet: false)
|
||||||
|
|
||||||
let plusViewWidth: CGFloat = 550
|
let plusViewWidth: CGFloat = 750
|
||||||
let plusViewHeight: CGFloat = 750
|
let plusViewHeight: CGFloat = 750
|
||||||
|
|
||||||
// Create an NSWindow to host the SwiftUI view
|
// Create an NSWindow to host the SwiftUI view
|
||||||
|
|||||||
@@ -5,8 +5,9 @@
|
|||||||
// Created by Leon Böttger on 05.10.25.
|
// Created by Leon Böttger on 05.10.25.
|
||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import AppKit
|
import AppKit
|
||||||
|
import LUI
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
let toastViewSize = CGSize(width: 400, height: 120)
|
let toastViewSize = CGSize(width: 400, height: 120)
|
||||||
|
|
||||||
@@ -32,14 +33,12 @@ struct QuickDropToastView: View {
|
|||||||
let isDone = receiveModel.toastActions != nil
|
let isDone = receiveModel.toastActions != nil
|
||||||
|
|
||||||
VStack(spacing: 12) {
|
VStack(spacing: 12) {
|
||||||
HStack(spacing: 12) {
|
HStack(spacing: 10) {
|
||||||
ZStack(alignment: .bottomLeading) {
|
|
||||||
Image(.quickDropIcon)
|
let iconSize = 39.0
|
||||||
.resizable()
|
|
||||||
.scaledToFill()
|
AppIconView(hasPlusIcon: false, size: iconSize)
|
||||||
.frame(width: 44, height: 44)
|
.frame(width: iconSize, height: iconSize)
|
||||||
.clipShape(Circle())
|
|
||||||
}
|
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 2) {
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
Text(String("QuickDrop"))
|
Text(String("QuickDrop"))
|
||||||
@@ -118,7 +117,7 @@ struct QuickDropToastView: View {
|
|||||||
.padding(16)
|
.padding(16)
|
||||||
.background(
|
.background(
|
||||||
VisualEffectView(material: .hudWindow)
|
VisualEffectView(material: .hudWindow)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
|
.clipShape(RoundedRectangle(cornerRadius: 25, style: .continuous))
|
||||||
)
|
)
|
||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: 16, style: .continuous)
|
RoundedRectangle(cornerRadius: 16, style: .continuous)
|
||||||
@@ -143,6 +142,10 @@ fileprivate struct QuickDropToastViewButton: View {
|
|||||||
Color.gray.opacity(0.15)
|
Color.gray.opacity(0.15)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 8, style: .continuous))
|
.clipShape(RoundedRectangle(cornerRadius: 8, style: .continuous))
|
||||||
)
|
)
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 8, style: .continuous)
|
||||||
|
.stroke(Color.black.opacity(0.04), lineWidth: 1)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain) // prevents blue macOS button look
|
.buttonStyle(.plain) // prevents blue macOS button look
|
||||||
}
|
}
|
||||||
@@ -203,7 +206,7 @@ struct CapsuleProgress: View {
|
|||||||
struct QuickDropToastView_Previews: PreviewProvider {
|
struct QuickDropToastView_Previews: PreviewProvider {
|
||||||
struct Demo: View {
|
struct Demo: View {
|
||||||
@State var model = ReceiveModel(controlPlusScreen: { _ in })
|
@State var model = ReceiveModel(controlPlusScreen: { _ in })
|
||||||
@State var done = false
|
@State var done = true
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
QuickDropToastView(
|
QuickDropToastView(
|
||||||
@@ -215,7 +218,8 @@ struct QuickDropToastView_Previews: PreviewProvider {
|
|||||||
.padding(100)
|
.padding(100)
|
||||||
.background(Color.black.opacity(0.2))
|
.background(Color.black.opacity(0.2))
|
||||||
.onAppear {
|
.onAppear {
|
||||||
model.progress = 0.01
|
model.progress = 1
|
||||||
|
model.toastActions = .init(openFilesAction: {}, importPhotosAction: {}, closeToastAction: {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -355,8 +355,8 @@ class ReceiveModel: ObservableObject, InboundAppDelegate {
|
|||||||
let frame = screen.frame
|
let frame = screen.frame
|
||||||
let visible = screen.visibleFrame
|
let visible = screen.visibleFrame
|
||||||
|
|
||||||
let marginX: CGFloat = 24
|
let marginX: CGFloat = 20
|
||||||
let marginFromTop: CGFloat = 30
|
let marginFromTop: CGFloat = 20
|
||||||
|
|
||||||
// If the menu bar is auto-hidden, visible.maxY == frame.maxY; subtract the status bar thickness to stay clear
|
// If the menu bar is auto-hidden, visible.maxY == frame.maxY; subtract the status bar thickness to stay clear
|
||||||
let menuBarHidden = abs(frame.maxY - visible.maxY) < 0.5
|
let menuBarHidden = abs(frame.maxY - visible.maxY) < 0.5
|
||||||
|
|||||||
@@ -10,7 +10,19 @@
|
|||||||
"layers" : [
|
"layers" : [
|
||||||
{
|
{
|
||||||
"image-name" : "QuickDropArrows.png",
|
"image-name" : "QuickDropArrows.png",
|
||||||
"name" : "QuickDropArrows"
|
"name" : "QuickDropArrows",
|
||||||
|
"position-specializations" : [
|
||||||
|
{
|
||||||
|
"idiom" : "square",
|
||||||
|
"value" : {
|
||||||
|
"scale" : 1,
|
||||||
|
"translation-in-points" : [
|
||||||
|
0,
|
||||||
|
-5
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"image-name" : "QuickDropCircle.png",
|
"image-name" : "QuickDropCircle.png",
|
||||||
@@ -34,4 +46,4 @@
|
|||||||
],
|
],
|
||||||
"squares" : "shared"
|
"squares" : "shared"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user