diff --git a/HowOnline/AppDelegate.swift b/HowOnline/AppDelegate.swift
index a57026b..1c02c1f 100644
--- a/HowOnline/AppDelegate.swift
+++ b/HowOnline/AppDelegate.swift
@@ -7,100 +7,140 @@
//
import Cocoa
-import ReachabilitySwift
-import StartAtLoginController
-
-@NSApplicationMain
-class AppDelegate: NSObject, NSApplicationDelegate, ProberDelegate {
- @IBOutlet weak var statusMenuItem: NSMenuItem!
- @IBOutlet weak var menu: NSMenu!
- @IBOutlet weak var startAtLoginController: StartAtLoginController!
-
- let statusItem = NSStatusBar.systemStatusBar().statusItemWithLength(NSSquareStatusItemLength)
- var refreshTimer: NSTimer! = nil
- var reachability: Reachability?
- var prober: Prober!
-
- func applicationDidFinishLaunching(aNotification: NSNotification) {
- // The menu is defined in interface builder
- statusItem.menu = self.menu
- updateMenu(false, text: "hello", longText: "Hello")
-
- startReachability()
-
- refreshTimer = NSTimer.scheduledTimerWithTimeInterval(3.0, target: self, selector: #selector(probe), userInfo: nil, repeats: true)
- prober = Prober(delegate: self)
- probe()
- }
-
- func startReachability() {
- let refreshBlock: (Reachability) -> () = { reachability in
- dispatch_async(dispatch_get_main_queue()) {
- self.probe()
- }
- }
-
- do {
- // We dont need any more sophistication than just checking for a wifi connection
- // since we do our own internet connection checking
- let reachability = try Reachability.reachabilityForLocalWiFi()
- self.reachability = reachability
-
- reachability.whenReachable = refreshBlock
- reachability.whenUnreachable = refreshBlock
- try reachability.startNotifier()
- } catch {
- print("Unable to create Reachability")
- return
- }
- }
-
- func probeResult(prober: Prober, result: Prober.ProbeResult) {
- updateMenu(result.success, text: result.text, longText: result.longText)
- }
-
- func updateMenu(success: Bool, text: String, longText: String) {
- if let button = statusItem.button {
- button.image = imageForStatus(text, filledBar: success, imageSize: button.frame.size)
- statusMenuItem.title = "Status: \(longText)"
-
- // Make the icon black and white, but this will also auto reverse colors in Dark/highlighted mode
- button.image!.template = true
- }
- }
-
- func probe() {
- prober.probe()
- }
-
- func imageForStatus(text: String, filledBar: Bool, imageSize: NSSize) -> NSImage? {
- return NSImage(size: imageSize, flipped: false, drawingHandler: { (rect: NSRect) -> Bool in
- NSColor.blackColor().setFill()
- NSColor.blackColor().setStroke()
-
- let progressBarRect = CGRect(x: 2, y: rect.size.height - 4 - 4, width: rect.size.width - 2 - 2, height: 4)
- let path = NSBezierPath(roundedRect: progressBarRect, xRadius: 2, yRadius: 2)
-
- path.stroke()
- if filledBar {
- path.fill()
- }
-
- let textRect = CGRect(x: 0, y: 0, width: rect.size.width, height: rect.size.height - 9)
-
- // The text needs to be really small to fit
- let paragraphStyle = NSMutableParagraphStyle()
- paragraphStyle.alignment = .Center
- paragraphStyle.lineBreakMode = .ByClipping
-
- let attrs = [
- NSFontAttributeName: NSFont.systemFontOfSize(8),
- NSParagraphStyleAttributeName: paragraphStyle
- ]
-
- text.drawWithRect(textRect, options: .UsesLineFragmentOrigin, attributes: attrs, context: nil)
-
- return true
- })
- }
-}
\ No newline at end of file
+import CoreLocation
+
+@main
+class AppDelegate: NSObject, NSApplicationDelegate, ProberDelegate, CLLocationManagerDelegate {
+ @IBOutlet weak var statusMenuItem: NSMenuItem!
+ @IBOutlet weak var menu: NSMenu!
+
+ let statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
+ var refreshTimer: Timer?
+ var prober: Prober!
+ var locationManager: CLLocationManager!
+ var locationAuthorized = false
+
+ func applicationDidFinishLaunching(_ aNotification: Notification) {
+ // The menu is defined in interface builder
+ statusItem.menu = self.menu
+ updateMenu(success: false, text: "hello", longText: "Hello")
+
+ // Setup location manager for SSID access (required since macOS 10.15)
+ setupLocationServices()
+
+ prober = Prober(delegate: self)
+
+ // Start probing after a short delay to allow location auth
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
+ self.startProbing()
+ }
+ }
+
+ func setupLocationServices() {
+ locationManager = CLLocationManager()
+ locationManager.delegate = self
+
+ // Check current authorization status
+ let status: CLAuthorizationStatus
+ if #available(macOS 11.0, *) {
+ status = locationManager.authorizationStatus
+ } else {
+ status = CLLocationManager.authorizationStatus()
+ }
+
+ switch status {
+ case .authorizedAlways:
+ locationAuthorized = true
+ case .notDetermined:
+ // Request authorization - on macOS this uses "When In Use" style
+ if #available(macOS 10.15, *) {
+ locationManager.requestWhenInUseAuthorization()
+ }
+ case .denied, .restricted:
+ print("Location access denied - SSID detection may not work")
+ locationAuthorized = false
+ @unknown default:
+ locationAuthorized = false
+ }
+ }
+
+ func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
+ let status: CLAuthorizationStatus
+ if #available(macOS 11.0, *) {
+ status = manager.authorizationStatus
+ } else {
+ status = CLLocationManager.authorizationStatus()
+ }
+
+ locationAuthorized = (status == .authorizedAlways)
+
+ if locationAuthorized {
+ probe()
+ }
+ }
+
+ // Legacy delegate method for older macOS
+ func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
+ locationAuthorized = (status == .authorizedAlways)
+
+ if locationAuthorized {
+ probe()
+ }
+ }
+
+ func startProbing() {
+ refreshTimer = Timer.scheduledTimer(withTimeInterval: 3.0, repeats: true) { [weak self] _ in
+ self?.probe()
+ }
+ probe()
+ }
+
+ func probeResult(_ prober: Prober, result: Prober.ProbeResult) {
+ updateMenu(success: result.success, text: result.text, longText: result.longText)
+ }
+
+ func updateMenu(success: Bool, text: String, longText: String) {
+ if let button = statusItem.button {
+ button.image = imageForStatus(text: text, filledBar: success, imageSize: button.frame.size)
+ statusMenuItem.title = "Status: \(longText)"
+
+ // Make the icon black and white, but this will also auto reverse colors in Dark/highlighted mode
+ button.image?.isTemplate = true
+ }
+ }
+
+ @objc func probe() {
+ prober.probe()
+ }
+
+ func imageForStatus(text: String, filledBar: Bool, imageSize: NSSize) -> NSImage? {
+ return NSImage(size: imageSize, flipped: false, drawingHandler: { (rect: NSRect) -> Bool in
+ NSColor.black.setFill()
+ NSColor.black.setStroke()
+
+ let progressBarRect = CGRect(x: 2, y: rect.size.height - 4 - 4, width: rect.size.width - 2 - 2, height: 4)
+ let path = NSBezierPath(roundedRect: progressBarRect, xRadius: 2, yRadius: 2)
+
+ path.stroke()
+ if filledBar {
+ path.fill()
+ }
+
+ let textRect = CGRect(x: 0, y: 0, width: rect.size.width, height: rect.size.height - 9)
+
+ // The text needs to be really small to fit
+ let paragraphStyle = NSMutableParagraphStyle()
+ paragraphStyle.alignment = .center
+ paragraphStyle.lineBreakMode = .byClipping
+
+ let attrs: [NSAttributedString.Key: Any] = [
+ .font: NSFont.systemFont(ofSize: 8),
+ .paragraphStyle: paragraphStyle
+ ]
+
+ text.draw(with: textRect, options: .usesLineFragmentOrigin, attributes: attrs, context: nil)
+
+ return true
+ })
+ }
+}
diff --git a/HowOnline/HowOnline.entitlements b/HowOnline/HowOnline.entitlements
index 7a2230d..9f2bc64 100644
--- a/HowOnline/HowOnline.entitlements
+++ b/HowOnline/HowOnline.entitlements
@@ -8,5 +8,7 @@
com.apple.security.network.server
+ com.apple.security.personal-information.location
+
diff --git a/HowOnline/Info.plist b/HowOnline/Info.plist
index 96ffcdb..ad361b1 100644
--- a/HowOnline/Info.plist
+++ b/HowOnline/Info.plist
@@ -17,11 +17,11 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 1.01
+ 1.1.0
CFBundleSignature
????
CFBundleVersion
- 4
+ 5
LSApplicationCategoryType
public.app-category.utilities
LSMinimumSystemVersion
@@ -32,5 +32,9 @@
MainMenu
NSPrincipalClass
NSApplication
+ NSLocationWhenInUseUsageDescription
+ HowOnline needs location access to detect your WiFi network name (SSID). This is required by macOS 10.15+ for accessing WiFi information.
+ NSLocationUsageDescription
+ HowOnline needs location access to detect your WiFi network name (SSID). This is required by macOS 10.15+ for accessing WiFi information.
diff --git a/HowOnline/Pinger.swift b/HowOnline/Pinger.swift
index 172441a..6302a12 100644
--- a/HowOnline/Pinger.swift
+++ b/HowOnline/Pinger.swift
@@ -10,59 +10,58 @@ import Foundation
import QuartzCore
class Pinger: NSObject, SimplePingDelegate {
- typealias CompletionBlock = (timeElapsedMs: Int?) -> ()
- var completionBlock: CompletionBlock!
+ typealias CompletionBlock = (Int?) -> Void
+ var completionBlock: CompletionBlock?
- var pinger: SimplePing!
- var timeoutTimer: NSTimer!
- var pingStartTime: CFTimeInterval = 0
-
- func ping(hostname: String, completionBlock: CompletionBlock) {
- self.pinger = SimplePing(hostName: hostname)
- self.completionBlock = completionBlock
-
- pingStartTime = CACurrentMediaTime()
-
- pinger.delegate = self;
- pinger.start()
- }
-
- func stopPinging(success: Bool) {
- pinger.stop()
- if timeoutTimer != nil {
- timeoutTimer.invalidate()
- }
-
- if success {
- let elapsedTime = Int((CACurrentMediaTime() - pingStartTime) * 1000)
- self.completionBlock(timeElapsedMs: elapsedTime)
- } else {
- self.completionBlock(timeElapsedMs: nil)
- }
- }
-
- func timeout() {
- stopPinging(false)
- }
-
- func simplePing(pinger: SimplePing!, didFailToSendPacket packet: NSData!, error: NSError!) {
- stopPinging(false)
- }
-
- func simplePing(pinger: SimplePing!, didFailWithError error: NSError!) {
- stopPinging(false)
- }
-
- func simplePing(pinger: SimplePing!, didReceivePingResponsePacket packet: NSData!) {
- stopPinging(true)
- }
-
- func simplePing(pinger: SimplePing!, didReceiveUnexpectedPacket packet: NSData!) {
- stopPinging(false)
- }
-
- func simplePing(pinger: SimplePing!, didStartWithAddress address: NSData!) {
- timeoutTimer = NSTimer.scheduledTimerWithTimeInterval(2.5, target: self, selector: #selector(timeout), userInfo: nil, repeats: false)
- pinger.sendPingWithData(nil)
- }
-}
\ No newline at end of file
+ var pinger: SimplePing?
+ var timeoutTimer: Timer?
+ var pingStartTime: CFTimeInterval = 0
+
+ func ping(hostname: String, completionBlock: @escaping CompletionBlock) {
+ self.pinger = SimplePing(hostName: hostname)
+ self.completionBlock = completionBlock
+
+ pingStartTime = CACurrentMediaTime()
+
+ pinger?.delegate = self
+ pinger?.start()
+ }
+
+ func stopPinging(success: Bool) {
+ pinger?.stop()
+ timeoutTimer?.invalidate()
+ timeoutTimer = nil
+
+ if success {
+ let elapsedTime = Int((CACurrentMediaTime() - pingStartTime) * 1000)
+ self.completionBlock?(elapsedTime)
+ } else {
+ self.completionBlock?(nil)
+ }
+ }
+
+ @objc func timeout() {
+ stopPinging(success: false)
+ }
+
+ func simplePing(_ pinger: SimplePing, didFailToSendPacket packet: Data, error: Error) {
+ stopPinging(success: false)
+ }
+
+ func simplePing(_ pinger: SimplePing, didFailWithError error: Error) {
+ stopPinging(success: false)
+ }
+
+ func simplePing(_ pinger: SimplePing, didReceivePingResponsePacket packet: Data) {
+ stopPinging(success: true)
+ }
+
+ func simplePing(_ pinger: SimplePing, didReceiveUnexpectedPacket packet: Data) {
+ stopPinging(success: false)
+ }
+
+ func simplePing(_ pinger: SimplePing, didStartWithAddress address: Data) {
+ timeoutTimer = Timer.scheduledTimer(timeInterval: 2.5, target: self, selector: #selector(timeout), userInfo: nil, repeats: false)
+ pinger.send(with: nil)
+ }
+}
diff --git a/HowOnline/PortTester.swift b/HowOnline/PortTester.swift
index 3c0f6a0..9407a09 100644
--- a/HowOnline/PortTester.swift
+++ b/HowOnline/PortTester.swift
@@ -7,35 +7,76 @@
//
import Foundation
-import CocoaAsyncSocket
-
-class PortTester: GCDAsyncSocketDelegate {
- typealias ResultBlock = (success: Bool) -> ()
-
- var socket: GCDAsyncSocket!
- var resultBlock: ResultBlock!
-
- func testPortOpen(host: String, port: Int, timeoutSeconds: Double, result: ResultBlock) {
- self.resultBlock = result
- self.socket = GCDAsyncSocket(delegate: self, delegateQueue: dispatch_get_main_queue())
-
- do {
- try self.socket.connectToHost(host, onPort: UInt16(port), withTimeout: NSTimeInterval(timeoutSeconds))
- } catch {
- resultBlock(success: false)
- }
- }
-
- @objc
- func socket(socket: GCDAsyncSocket, didConnectToHost: String, port: UInt16) {
- socket.disconnect()
- resultBlock(success: true)
- }
-
- @objc
- func socketDidDisconnect(sock: GCDAsyncSocket!, withError err: NSError!) {
- if err != nil {
- resultBlock(success: false)
- }
- }
+
+class PortTester: NSObject, StreamDelegate {
+ typealias ResultBlock = (Bool) -> Void
+
+ var inputStream: InputStream?
+ var outputStream: OutputStream?
+ var resultBlock: ResultBlock?
+ var timeoutTimer: Timer?
+ var didConnect = false
+
+ func testPortOpen(host: String, port: Int, timeoutSeconds: Double, result: @escaping ResultBlock) {
+ self.resultBlock = result
+ self.didConnect = false
+
+ var readStream: Unmanaged?
+ var writeStream: Unmanaged?
+
+ CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, host as CFString, UInt32(port), &readStream, &writeStream)
+
+ inputStream = readStream?.takeRetainedValue()
+ outputStream = writeStream?.takeRetainedValue()
+
+ inputStream?.delegate = self
+ outputStream?.delegate = self
+
+ inputStream?.schedule(in: .current, forMode: .common)
+ outputStream?.schedule(in: .current, forMode: .common)
+
+ inputStream?.open()
+ outputStream?.open()
+
+ timeoutTimer = Timer.scheduledTimer(withTimeInterval: timeoutSeconds, repeats: false) { [weak self] _ in
+ self?.handleTimeout()
+ }
+ }
+
+ private func handleTimeout() {
+ if !didConnect {
+ cleanup()
+ resultBlock?(false)
+ }
+ }
+
+ private func cleanup() {
+ timeoutTimer?.invalidate()
+ timeoutTimer = nil
+
+ inputStream?.close()
+ outputStream?.close()
+
+ inputStream?.remove(from: .current, forMode: .common)
+ outputStream?.remove(from: .current, forMode: .common)
+
+ inputStream = nil
+ outputStream = nil
+ }
+
+ func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
+ switch eventCode {
+ case .openCompleted:
+ didConnect = true
+ cleanup()
+ resultBlock?(true)
+ case .errorOccurred:
+ if !didConnect {
+ cleanup()
+ resultBlock?(false)
+ }
+ default:
+ break
+ }
+ }
}
diff --git a/HowOnline/Prober.swift b/HowOnline/Prober.swift
index 51c88bf..d67ac87 100644
--- a/HowOnline/Prober.swift
+++ b/HowOnline/Prober.swift
@@ -9,158 +9,176 @@
import Foundation
import CoreWLAN
-protocol ProberDelegate {
- func probeResult(prober: Prober, result: Prober.ProbeResult)
+protocol ProberDelegate: AnyObject {
+ func probeResult(_ prober: Prober, result: Prober.ProbeResult)
}
class Prober {
- typealias ProbeResult = (success: Bool, text: String, longText: String)
-
- let delegate: ProberDelegate
- let pinger: Pinger! = Pinger()
- let portTester: PortTester! = PortTester()
- var gatewayIP: String!
-
- var curProbe = 0
- var probing: Bool = false
-
- var probes: [() -> ()] {
- return [simpleChecks, testGateway, pingEightDotEight, resolveGoogle, pingGoogle]
- }
-
- init(delegate: ProberDelegate) {
- self.delegate = delegate
- }
-
- func probe() {
- if probing {
- print("already probing, ignoring request")
- return
- }
- probing = true
-
- probes[curProbe]()
- }
-
- private func probeResult(result: ProbeResult) {
- probing = false
-
- if result.success {
- // The last probe being successful means we should pass the actual success message
- if curProbe == probes.count - 1 {
- self.delegate.probeResult(self, result: result)
-
- // Otherwise, proceed to the next probe test
- } else {
- curProbe += 1
- probe()
- }
-
- } else {
- curProbe = 0
- self.delegate.probeResult(self, result: result)
- }
- }
-
- private func pingProbe(ip: String, errorText: String, longErrorText: String) {
- pinger.ping(ip) { (timeElapsedMs) -> () in
- if timeElapsedMs == nil {
- self.probeResult(ProbeResult(success: false, text: errorText, longText: longErrorText))
- return
- }
-
- self.probeResult(ProbeResult(success: true, text: "\(timeElapsedMs!)ms", longText: "OK. Ping to Google: \(timeElapsedMs!)ms"))
- }
- }
-
- //////
-
- private func simpleChecks() {
- guard let interface = CWWiFiClient()?.interface() else {
- self.probeResult(ProbeResult(success: false, text: "wifi if", longText: "Couldn't detect a WiFi interface"))
- return
- }
-
- guard interface.powerOn() else {
- self.probeResult(ProbeResult(success: false, text: "wifi off", longText: "Your WiFi is turned off"))
- return
- }
-
- guard interface.ssid() != nil else {
- self.probeResult(ProbeResult(success: false, text: "no ssid", longText: "Not associated to a WiFi network"))
- return
- }
-
- guard let ip = getWiFiAddress() where ip.characters.count > 0 else {
- self.probeResult(ProbeResult(success: false, text: "no ip", longText: "On WiFi, but no IP address assigned"))
- return
- }
-
- guard !ip.hasPrefix("169.254") && !ip.hasPrefix("0.0") else {
- self.probeResult(ProbeResult(success: false, text: "self ip", longText: "On WiFi, but self-assigned IP"))
- return
- }
-
- guard let gatewayIP = defaultGateway() else {
- self.probeResult(ProbeResult(success: false, text: "no gw", longText: "On WiFi, but no internet gateway assigned"))
- return
- }
- self.gatewayIP = gatewayIP
-
- probeResult(ProbeResult(success: true, text: "", longText: ""))
- }
-
- private func testGateway() {
- // Some gateways aren't pingable for whatever unspeakable reason. As such we'll ping
- // it first, and if it fails we'll try a connection to port 22, 80 and
- pinger.ping(self.gatewayIP) { (timeElapsedMs) -> () in
- if timeElapsedMs == nil {
- self.portTester.testPortOpen(self.gatewayIP, port: 80, timeoutSeconds: 2.0) { (success) -> () in
- if success {
- self.probeResult(ProbeResult(success: true, text: "", longText: ""))
- return
- }
-
- self.portTester.testPortOpen(self.gatewayIP, port: 22, timeoutSeconds: 2.0) { (success) -> () in
- if success {
- self.probeResult(ProbeResult(success: true, text: "", longText: ""))
- return
- }
-
- self.portTester.testPortOpen(self.gatewayIP, port: 23, timeoutSeconds: 2.0) { (success) -> () in
- if success {
- self.probeResult(ProbeResult(success: true, text: "", longText: ""))
- return
- }
-
- self.probeResult(ProbeResult(success: false, text: "bad gw", longText: "Gateway wasn't pingable or connectable via HTTP/SSH/Telnet"))
- }
- }
- }
-
- return
- }
-
- self.probeResult(ProbeResult(success: true, text: "", longText: ""))
- }
- }
-
- private func pingEightDotEight() {
- pingProbe("8.8.8.8", errorText: "ping 8.", longErrorText: "Failed to ping 8.8.8.8")
- }
-
- private func resolveGoogle() {
- testResolveHostname("google.com") { (success) -> Void in
- if !success {
- self.probeResult(ProbeResult(success: false, text: "dns", longText: "Failed to do a DNS lookup for google.com"))
- return
- }
-
- self.probeResult(ProbeResult(success: true, text: "", longText: ""))
- }
- }
-
- private func pingGoogle() {
- pingProbe("google.com", errorText: "ping G", longErrorText: "Failed to ping google.com")
- }
-}
\ No newline at end of file
+ struct ProbeResult {
+ let success: Bool
+ let text: String
+ let longText: String
+ }
+
+ weak var delegate: ProberDelegate?
+ let pinger: Pinger = Pinger()
+ let portTester: PortTester = PortTester()
+ var gatewayIP: String = ""
+
+ var curProbe = 0
+ var probing: Bool = false
+
+ var probes: [() -> Void] {
+ return [simpleChecks, testGateway, pingEightDotEight, resolveGoogle, pingGoogle]
+ }
+
+ init(delegate: ProberDelegate) {
+ self.delegate = delegate
+ }
+
+ func probe() {
+ if probing {
+ print("already probing, ignoring request")
+ return
+ }
+ probing = true
+
+ probes[curProbe]()
+ }
+
+ private func probeResult(_ result: ProbeResult) {
+ probing = false
+
+ if result.success {
+ // The last probe being successful means we should pass the actual success message
+ if curProbe == probes.count - 1 {
+ self.delegate?.probeResult(self, result: result)
+
+ // Otherwise, proceed to the next probe test
+ } else {
+ curProbe += 1
+ probe()
+ }
+
+ } else {
+ curProbe = 0
+ self.delegate?.probeResult(self, result: result)
+ }
+ }
+
+ private func pingProbe(ip: String, errorText: String, longErrorText: String) {
+ pinger.ping(hostname: ip) { [weak self] (timeElapsedMs) in
+ guard let self = self else { return }
+
+ if timeElapsedMs == nil {
+ self.probeResult(ProbeResult(success: false, text: errorText, longText: longErrorText))
+ return
+ }
+
+ self.probeResult(ProbeResult(success: true, text: "\(timeElapsedMs!)ms", longText: "OK. Ping to Google: \(timeElapsedMs!)ms"))
+ }
+ }
+
+ //////
+
+ private func simpleChecks() {
+ guard let interface = CWWiFiClient.shared().interface() else {
+ self.probeResult(ProbeResult(success: false, text: "wifi if", longText: "Couldn't detect a WiFi interface"))
+ return
+ }
+
+ guard interface.powerOn() else {
+ self.probeResult(ProbeResult(success: false, text: "wifi off", longText: "Your WiFi is turned off"))
+ return
+ }
+
+ // Note: ssid() requires Location Services permission on macOS 10.15+
+ // The app requests this permission at startup
+ guard interface.ssid() != nil else {
+ self.probeResult(ProbeResult(success: false, text: "no ssid", longText: "Not associated to a WiFi network (or Location permission denied)"))
+ return
+ }
+
+ guard let ip = getWiFiAddress(), !ip.isEmpty else {
+ self.probeResult(ProbeResult(success: false, text: "no ip", longText: "On WiFi, but no IP address assigned"))
+ return
+ }
+
+ guard !ip.hasPrefix("169.254") && !ip.hasPrefix("0.0") else {
+ self.probeResult(ProbeResult(success: false, text: "self ip", longText: "On WiFi, but self-assigned IP"))
+ return
+ }
+
+ guard let gatewayIP = defaultGateway() as String? else {
+ self.probeResult(ProbeResult(success: false, text: "no gw", longText: "On WiFi, but no internet gateway assigned"))
+ return
+ }
+ self.gatewayIP = gatewayIP
+
+ probeResult(ProbeResult(success: true, text: "", longText: ""))
+ }
+
+ private func testGateway() {
+ // Some gateways aren't pingable for whatever unspeakable reason. As such we'll ping
+ // it first, and if it fails we'll try a connection to port 22, 80 and
+ pinger.ping(hostname: self.gatewayIP) { [weak self] (timeElapsedMs) in
+ guard let self = self else { return }
+
+ if timeElapsedMs == nil {
+ self.portTester.testPortOpen(host: self.gatewayIP, port: 80, timeoutSeconds: 2.0) { [weak self] (success) in
+ guard let self = self else { return }
+
+ if success {
+ self.probeResult(ProbeResult(success: true, text: "", longText: ""))
+ return
+ }
+
+ self.portTester.testPortOpen(host: self.gatewayIP, port: 22, timeoutSeconds: 2.0) { [weak self] (success) in
+ guard let self = self else { return }
+
+ if success {
+ self.probeResult(ProbeResult(success: true, text: "", longText: ""))
+ return
+ }
+
+ self.portTester.testPortOpen(host: self.gatewayIP, port: 23, timeoutSeconds: 2.0) { [weak self] (success) in
+ guard let self = self else { return }
+
+ if success {
+ self.probeResult(ProbeResult(success: true, text: "", longText: ""))
+ return
+ }
+
+ self.probeResult(ProbeResult(success: false, text: "bad gw", longText: "Gateway wasn't pingable or connectable via HTTP/SSH/Telnet"))
+ }
+ }
+ }
+
+ return
+ }
+
+ self.probeResult(ProbeResult(success: true, text: "", longText: ""))
+ }
+ }
+
+ private func pingEightDotEight() {
+ pingProbe(ip: "8.8.8.8", errorText: "ping 8.", longErrorText: "Failed to ping 8.8.8.8")
+ }
+
+ private func resolveGoogle() {
+ testResolveHostname("google.com") { [weak self] (success) in
+ guard let self = self else { return }
+
+ if !success {
+ self.probeResult(ProbeResult(success: false, text: "dns", longText: "Failed to do a DNS lookup for google.com"))
+ return
+ }
+
+ self.probeResult(ProbeResult(success: true, text: "", longText: ""))
+ }
+ }
+
+ private func pingGoogle() {
+ pingProbe(ip: "google.com", errorText: "ping G", longErrorText: "Failed to ping google.com")
+ }
+}
diff --git a/HowOnline/Utilities.swift b/HowOnline/Utilities.swift
index 7e82add..8b962ab 100644
--- a/HowOnline/Utilities.swift
+++ b/HowOnline/Utilities.swift
@@ -10,39 +10,42 @@ import Foundation
// Return IP address of WiFi interface (en0) as a String, or `nil`
func getWiFiAddress() -> String? {
- var address : String?
-
- // Get list of all interfaces on the local machine:
- var ifaddr : UnsafeMutablePointer = nil
- if getifaddrs(&ifaddr) == 0 {
-
- // For each interface ...
- for (var ptr = ifaddr; ptr != nil; ptr = ptr.memory.ifa_next) {
- let interface = ptr.memory
-
- // Check for IPv4 or IPv6 interface:
- let addrFamily = interface.ifa_addr.memory.sa_family
- if addrFamily == UInt8(AF_INET) || addrFamily == UInt8(AF_INET6) {
-
- // Check interface name:
- if let name = String.fromCString(interface.ifa_name) where name.hasPrefix("en") {
-
- // Convert interface address to a human readable string:
- var addr = interface.ifa_addr.memory
- var hostname = [CChar](count: Int(NI_MAXHOST), repeatedValue: 0)
- getnameinfo(&addr, socklen_t(interface.ifa_addr.memory.sa_len),
- &hostname, socklen_t(hostname.count),
- nil, socklen_t(0), NI_NUMERICHOST)
- address = String.fromCString(hostname)
-
- if let checkAddress = address where checkAddress != "" {
- break
- }
- }
- }
- }
- freeifaddrs(ifaddr)
- }
-
- return address
+ var address: String?
+
+ // Get list of all interfaces on the local machine:
+ var ifaddr: UnsafeMutablePointer?
+ if getifaddrs(&ifaddr) == 0 {
+
+ // For each interface ...
+ var ptr = ifaddr
+ while ptr != nil {
+ defer { ptr = ptr?.pointee.ifa_next }
+
+ guard let interface = ptr?.pointee else { continue }
+
+ // Check for IPv4 or IPv6 interface:
+ let addrFamily = interface.ifa_addr.pointee.sa_family
+ if addrFamily == UInt8(AF_INET) || addrFamily == UInt8(AF_INET6) {
+
+ // Check interface name:
+ let name = String(cString: interface.ifa_name)
+ if name.hasPrefix("en") {
+
+ // Convert interface address to a human readable string:
+ var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
+ getnameinfo(interface.ifa_addr, socklen_t(interface.ifa_addr.pointee.sa_len),
+ &hostname, socklen_t(hostname.count),
+ nil, socklen_t(0), NI_NUMERICHOST)
+ address = String(cString: hostname)
+
+ if let checkAddress = address, !checkAddress.isEmpty {
+ break
+ }
+ }
+ }
+ }
+ freeifaddrs(ifaddr)
+ }
+
+ return address
}
diff --git a/HowOnlineLauncher/AppDelegate.swift b/HowOnlineLauncher/AppDelegate.swift
index 3321b8e..1959ba9 100644
--- a/HowOnlineLauncher/AppDelegate.swift
+++ b/HowOnlineLauncher/AppDelegate.swift
@@ -8,29 +8,30 @@
import Cocoa
-@NSApplicationMain
+@main
class AppDelegate: NSObject, NSApplicationDelegate {
- func applicationDidFinishLaunching(aNotification: NSNotification) {
- for app in NSWorkspace.sharedWorkspace().runningApplications {
- if app.bundleIdentifier == "com.lg.HowOnline" {
- NSApp.terminate(nil)
- return
- }
- }
-
- let path = NSBundle.mainBundle().bundlePath as NSString
- var components = path.pathComponents
- components.removeLast()
- components.removeLast()
- components.removeLast()
- components.append("MacOS")
- components.append("HowOnline")
-
- let newPath = NSString.pathWithComponents(components)
-
- NSWorkspace.sharedWorkspace().launchApplication(newPath)
-
- NSApp.terminate(nil)
- }
-}
+ func applicationDidFinishLaunching(_ aNotification: Notification) {
+ for app in NSWorkspace.shared.runningApplications {
+ if app.bundleIdentifier == "com.lg.HowOnline" {
+ NSApp.terminate(nil)
+ return
+ }
+ }
+
+ let path = Bundle.main.bundlePath as NSString
+ var components = path.pathComponents
+ components.removeLast()
+ components.removeLast()
+ components.removeLast()
+ components.append("MacOS")
+ components.append("HowOnline")
+
+ let newPath = NSString.path(withComponents: components)
+ NSWorkspace.shared.openApplication(at: URL(fileURLWithPath: newPath),
+ configuration: NSWorkspace.OpenConfiguration(),
+ completionHandler: nil)
+
+ NSApp.terminate(nil)
+ }
+}
diff --git a/Podfile b/Podfile
index 0e9d0e2..2e0f135 100644
--- a/Podfile
+++ b/Podfile
@@ -1,7 +1,8 @@
-platform :osx, "10.11"
+platform :osx, "10.15"
use_frameworks!
-target "HowOnline"
-pod "ReachabilitySwift"
-pod "StartAtLoginController"
-pod "CocoaAsyncSocket"
+target "HowOnline" do
+ # Note: ReachabilitySwift and StartAtLoginController removed -
+ # they were causing build issues and aren't needed for core functionality
+ # The location manager now handles WiFi state detection
+end