aboutsummaryrefslogtreecommitdiff
path: root/DWA140Menu
diff options
context:
space:
mode:
Diffstat (limited to 'DWA140Menu')
-rw-r--r--DWA140Menu/AppDelegate.swift177
-rw-r--r--DWA140Menu/Assets.xcassets/CreditsPNG.imageset/Contents.json13
-rw-r--r--DWA140Menu/Assets.xcassets/CreditsPNG.imageset/CreditsPNG.pngbin0 -> 119225 bytes
-rw-r--r--DWA140Menu/Assets.xcassets/WifiConnected.imageset/Contents.json24
-rw-r--r--DWA140Menu/Assets.xcassets/WifiConnected.imageset/WifiConnected.pngbin0 -> 3688 bytes
-rw-r--r--DWA140Menu/Assets.xcassets/wifierror.imageset/AirPortError-1_55A54008AD1BA589AA210D2629C1DF41_0.pngbin0 -> 743 bytes
-rw-r--r--DWA140Menu/Assets.xcassets/wifierror.imageset/Contents.json24
-rw-r--r--DWA140Menu/Base.lproj/Main.storyboard54
-rw-r--r--DWA140Menu/DWA140Menu-Bridging-Header.h2
-rw-r--r--DWA140Menu/Info.plist2
-rwxr-xr-xDWA140Menu/Reachability.swift316
11 files changed, 583 insertions, 29 deletions
diff --git a/DWA140Menu/AppDelegate.swift b/DWA140Menu/AppDelegate.swift
index 0454a77..cd78f1a 100644
--- a/DWA140Menu/AppDelegate.swift
+++ b/DWA140Menu/AppDelegate.swift
@@ -7,20 +7,183 @@
//
import Cocoa
-
+import Foundation
+struct Networking {
+
+ enum NetworkInterfaceType: String, CustomStringConvertible {
+
+ case Ethernet = "en2"
+ case Unknown = ""
+
+ var description: String {
+ switch self {
+ case .Ethernet:
+ return "Ethernet"
+ case .Unknown:
+ return "Unknown"
+ }
+ }
+ }
+
+ static var networkInterfaceType: NetworkInterfaceType {
+ if let name = Networking().getInterfaces().first?.name, let type = NetworkInterfaceType(rawValue: name) {
+ return type
+ }
+
+ return .Unknown
+ }
+
+ static var isConnectedByEthernet: Bool {
+ let networking = Networking()
+ for addr in networking.getInterfaces() {
+
+ if addr.name == NetworkInterfaceType.Ethernet.rawValue {
+ return true
+ }
+ }
+ return false
+ }
+
+
+ // Credit to Martin R http://stackoverflow.com/a/34016247/600753 for this lovely code
+ // New Swift 3 implementation needed upated to replace unsafepointer calls with .withMemoryRebound
+ func getInterfaces() -> [(name : String, addr: String, mac : String)] {
+
+ var addresses = [(name : String, addr: String, mac : String)]()
+ var nameToMac = [ String: String ]()
+
+ // Get list of all interfaces on the local machine:
+ var ifaddr : UnsafeMutablePointer<ifaddrs>?
+ guard getifaddrs(&ifaddr) == 0 else { return [] }
+ guard let firstAddr = ifaddr else { return [] }
+
+ // For each interface ...
+ for ptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) {
+ let flags = Int32(ptr.pointee.ifa_flags)
+ if var addr = ptr.pointee.ifa_addr {
+ let name = String(cString: ptr.pointee.ifa_name)
+
+ // Check for running IPv4, IPv6 interfaces. Skip the loopback interface.
+ if (flags & (IFF_UP|IFF_RUNNING|IFF_LOOPBACK)) == (IFF_UP|IFF_RUNNING) {
+ switch Int32(addr.pointee.sa_family) {
+ case AF_LINK:
+ nameToMac[name] = withUnsafePointer(to: &addr) { unsafeAddr in
+ unsafeAddr.withMemoryRebound(to: sockaddr_dl.self, capacity: 1) { dl in
+ dl.withMemoryRebound(to: Int8.self, capacity: 1) { dll in
+ let lladdr = UnsafeRawBufferPointer(start: dll + 8 + Int(dl.pointee.sdl_nlen), count: Int(dl.pointee.sdl_alen))
+
+ if lladdr.count == 6 {
+ return lladdr.map { String(format:"%02hhx", $0)}.joined(separator: ":")
+ } else {
+ return nil
+ }
+ }
+ }
+ }
+
+ case AF_INET, AF_INET6:
+ // Convert interface address to a human readable string:
+ var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
+ if (getnameinfo(addr, socklen_t(addr.pointee.sa_len),
+ &hostname, socklen_t(hostname.count),
+ nil, socklen_t(0), NI_NUMERICHOST) == 0) {
+ let address = String(cString: hostname)
+ addresses.append( (name: name, addr: address, mac : "") )
+ }
+ default:
+ break
+ }
+ }
+ }
+ }
+
+ freeifaddrs(ifaddr)
+
+ // Now add the mac address to the tuples:
+ for (i, addr) in addresses.enumerated() {
+ if let mac = nameToMac[addr.name] {
+ addresses[i] = (name: addr.name, addr: addr.addr, mac : mac)
+ }
+ }
+
+ return addresses
+ }
+}
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
-
-
-
+
+ @objc func openCredits() {
+ let myWindowController = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"), bundle: nil).instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "credits")) as! NSWindowController
+ myWindowController.showWindow(self)
+ }
+
+ @objc func changeIcon() {
+ if let button = statusItem.button {
+ if Networking.isConnectedByEthernet {
+ button.image = NSImage(named: NSImage.Name(rawValue: "WifiConnected"))
+
+ } else {
+ button.image = NSImage(named: NSImage.Name(rawValue: "wifierror"))
+ }
+ }
+
+ }
+ @objc func openNetwork() {
+ NSWorkspace.shared.open(URL(fileURLWithPath: "/System/Library/PreferencePanes/Network.prefPane"))
+ }
+ @objc func openDWA() {
+ let fileman = FileManager.default
+ let alluserspath = "/Library/PreferencePanes/DWA-140WirelessUtility.prefPane"
+ let currentuserpath = "~/Library/PreferencePanes/DWA-140WirelessUtility.prefPane"
+ if fileman.fileExists(atPath: alluserspath) {
+ NSWorkspace.shared.open(URL(fileURLWithPath: alluserspath)) }
+ else {
+ NSWorkspace.shared.open(URL(fileURLWithPath: currentuserpath))
+ }
+ }
+ @objc func restartApp() {
+ let url = URL(fileURLWithPath: Bundle.main.resourcePath!)
+ let path = url.deletingLastPathComponent().deletingLastPathComponent().absoluteString
+ let task = Process()
+ task.launchPath = "/usr/bin/open"
+ task.arguments = [path]
+ task.launch()
+ exit(0)
+ }
+ @objc func ethStatus() -> String {
+ if Networking.isConnectedByEthernet {
+ return "USB WiFi is connected. (en4)"
+ } else {
+ return "USB WiFi is disconnected."
+ }
+ }
+
+
+ let statusItem = NSStatusBar.system.statusItem(withLength:NSStatusItem.squareLength)
+
func applicationDidFinishLaunching(_ aNotification: Notification) {
- // Insert code here to initialize your application
+
+ changeIcon()
+
+constructMenu()
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
-
+ func constructMenu() {
+ let menu = NSMenu()
+ menu.addItem(NSMenuItem(title: "DWA-140 Shortcut", action: #selector(openCredits), keyEquivalent: ""))
+ menu.addItem(NSMenuItem.separator())
+ menu.addItem(NSMenuItem(title: ethStatus(), action: nil, keyEquivalent: ""))
+ menu.addItem(NSMenuItem(title: "Refresh Status", action: #selector(restartApp), keyEquivalent: ""))
+ menu.addItem(NSMenuItem(title: "Open DWA-140" , action: #selector(openDWA), keyEquivalent: ""))
+ menu.addItem(NSMenuItem(title: "Network Preferences" , action: #selector(openNetwork), keyEquivalent: ""))
+ menu.addItem(NSMenuItem.separator())
+ menu.addItem(NSMenuItem(title: "Quit", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q"))
+
+ statusItem.menu = menu
+
+ }
}
-
diff --git a/DWA140Menu/Assets.xcassets/CreditsPNG.imageset/Contents.json b/DWA140Menu/Assets.xcassets/CreditsPNG.imageset/Contents.json
new file mode 100644
index 0000000..03a16f6
--- /dev/null
+++ b/DWA140Menu/Assets.xcassets/CreditsPNG.imageset/Contents.json
@@ -0,0 +1,13 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "CreditsPNG.png",
+ "scale" : "1x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/DWA140Menu/Assets.xcassets/CreditsPNG.imageset/CreditsPNG.png b/DWA140Menu/Assets.xcassets/CreditsPNG.imageset/CreditsPNG.png
new file mode 100644
index 0000000..3819bdb
--- /dev/null
+++ b/DWA140Menu/Assets.xcassets/CreditsPNG.imageset/CreditsPNG.png
Binary files differ
diff --git a/DWA140Menu/Assets.xcassets/WifiConnected.imageset/Contents.json b/DWA140Menu/Assets.xcassets/WifiConnected.imageset/Contents.json
new file mode 100644
index 0000000..60f1a5a
--- /dev/null
+++ b/DWA140Menu/Assets.xcassets/WifiConnected.imageset/Contents.json
@@ -0,0 +1,24 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "WifiConnected.png",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ },
+ "properties" : {
+ "template-rendering-intent" : "template"
+ }
+} \ No newline at end of file
diff --git a/DWA140Menu/Assets.xcassets/WifiConnected.imageset/WifiConnected.png b/DWA140Menu/Assets.xcassets/WifiConnected.imageset/WifiConnected.png
new file mode 100644
index 0000000..11ec726
--- /dev/null
+++ b/DWA140Menu/Assets.xcassets/WifiConnected.imageset/WifiConnected.png
Binary files differ
diff --git a/DWA140Menu/Assets.xcassets/wifierror.imageset/AirPortError-1_55A54008AD1BA589AA210D2629C1DF41_0.png b/DWA140Menu/Assets.xcassets/wifierror.imageset/AirPortError-1_55A54008AD1BA589AA210D2629C1DF41_0.png
new file mode 100644
index 0000000..039d66f
--- /dev/null
+++ b/DWA140Menu/Assets.xcassets/wifierror.imageset/AirPortError-1_55A54008AD1BA589AA210D2629C1DF41_0.png
Binary files differ
diff --git a/DWA140Menu/Assets.xcassets/wifierror.imageset/Contents.json b/DWA140Menu/Assets.xcassets/wifierror.imageset/Contents.json
new file mode 100644
index 0000000..3f18ed4
--- /dev/null
+++ b/DWA140Menu/Assets.xcassets/wifierror.imageset/Contents.json
@@ -0,0 +1,24 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "AirPortError-1_55A54008AD1BA589AA210D2629C1DF41_0.png",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ },
+ "properties" : {
+ "template-rendering-intent" : "template"
+ }
+} \ No newline at end of file
diff --git a/DWA140Menu/Base.lproj/Main.storyboard b/DWA140Menu/Base.lproj/Main.storyboard
index c25c019..42bdba8 100644
--- a/DWA140Menu/Base.lproj/Main.storyboard
+++ b/DWA140Menu/Base.lproj/Main.storyboard
@@ -1,7 +1,8 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="11134" systemVersion="15F34" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
- <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11134"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
+ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Application-->
@@ -673,45 +674,54 @@
<outlet property="delegate" destination="Voe-Tx-rLC" id="PrD-fu-P6m"/>
</connections>
</application>
- <customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModuleProvider="target"/>
+ <customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="DWA140Menu" customModuleProvider="target"/>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="75" y="0.0"/>
</scene>
<!--Window Controller-->
- <scene sceneID="R2V-B0-nI4">
+ <scene sceneID="Qjy-2V-LMd">
<objects>
- <windowController id="B8D-0N-5wS" sceneMemberID="viewController">
- <window key="window" title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="IQv-IB-iLA">
- <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
- <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
- <rect key="contentRect" x="196" y="240" width="480" height="270"/>
- <rect key="screenRect" x="0.0" y="0.0" width="1680" height="1027"/>
- <connections>
- <outlet property="delegate" destination="B8D-0N-5wS" id="98r-iN-zZc"/>
+ <windowController storyboardIdentifier="credits" showSeguePresentationStyle="single" id="nRX-BA-ezW" sceneMemberID="viewController">
+ <window key="window" title="Credits" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" titlebarAppearsTransparent="YES" id="rZh-6e-Tf1">
+ <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES"/>
+ <rect key="contentRect" x="735" y="354" width="475" height="195"/>
+ <rect key="screenRect" x="0.0" y="0.0" width="1920" height="1058"/>
+ <connections>
+ <outlet property="delegate" destination="nRX-BA-ezW" id="gZ6-jx-7xG"/>
</connections>
</window>
<connections>
- <segue destination="XfG-lQ-9wD" kind="relationship" relationship="window.shadowedContentViewController" id="cq2-FE-JQM"/>
+ <segue destination="0aO-16-CgC" kind="relationship" relationship="window.shadowedContentViewController" id="pKN-ne-ylG"/>
</connections>
</windowController>
- <customObject id="Oky-zY-oP4" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
+ <customObject id="ggv-1R-9Ps" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
- <point key="canvasLocation" x="75" y="250"/>
+ <point key="canvasLocation" x="-40" y="580"/>
</scene>
<!--View Controller-->
- <scene sceneID="hIz-AP-VOD">
+ <scene sceneID="Mzy-En-CiB">
<objects>
- <viewController id="XfG-lQ-9wD" customClass="ViewController" customModuleProvider="target" sceneMemberID="viewController">
- <view key="view" wantsLayer="YES" id="m2S-Jp-Qdl">
- <rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
+ <viewController id="0aO-16-CgC" sceneMemberID="viewController">
+ <view key="view" id="bth-bX-hYV">
+ <rect key="frame" x="0.0" y="0.0" width="475" height="195"/>
<autoresizingMask key="autoresizingMask"/>
+ <subviews>
+ <imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="RcP-h0-2uh">
+ <rect key="frame" x="-3" y="-3" width="480" height="200"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+ <imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="CreditsPNG" id="Xsm-jF-xX3"/>
+ </imageView>
+ </subviews>
</view>
</viewController>
- <customObject id="rPt-NT-nkU" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
+ <customObject id="zDu-Zs-kKh" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
- <point key="canvasLocation" x="75" y="655"/>
+ <point key="canvasLocation" x="666.5" y="554.5"/>
</scene>
</scenes>
+ <resources>
+ <image name="CreditsPNG" width="480" height="200"/>
+ </resources>
</document>
diff --git a/DWA140Menu/DWA140Menu-Bridging-Header.h b/DWA140Menu/DWA140Menu-Bridging-Header.h
new file mode 100644
index 0000000..824bab7
--- /dev/null
+++ b/DWA140Menu/DWA140Menu-Bridging-Header.h
@@ -0,0 +1,2 @@
+#include <ifaddrs.h>
+#include <net/if_dl.h>
diff --git a/DWA140Menu/Info.plist b/DWA140Menu/Info.plist
index 027b744..e28296a 100644
--- a/DWA140Menu/Info.plist
+++ b/DWA140Menu/Info.plist
@@ -26,6 +26,8 @@
<string>Copyright © 2018 Dylan Bolger. All rights reserved.</string>
<key>NSMainStoryboardFile</key>
<string>Main</string>
+ <key>LSUIElement</key>
+ <true/>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
diff --git a/DWA140Menu/Reachability.swift b/DWA140Menu/Reachability.swift
new file mode 100755
index 0000000..8711a71
--- /dev/null
+++ b/DWA140Menu/Reachability.swift
@@ -0,0 +1,316 @@
+/*
+Copyright (c) 2014, Ashley Mills
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+*/
+
+import SystemConfiguration
+import Foundation
+
+public enum ReachabilityError: Error {
+ case FailedToCreateWithAddress(sockaddr_in)
+ case FailedToCreateWithHostname(String)
+ case UnableToSetCallback
+ case UnableToSetDispatchQueue
+ case UnableToGetInitialFlags
+}
+
+@available(*, unavailable, renamed: "Notification.Name.reachabilityChanged")
+public let ReachabilityChangedNotification = NSNotification.Name("ReachabilityChangedNotification")
+
+public extension Notification.Name {
+ public static let reachabilityChanged = Notification.Name("reachabilityChanged")
+}
+
+public class Reachability {
+
+ public typealias NetworkReachable = (Reachability) -> ()
+ public typealias NetworkUnreachable = (Reachability) -> ()
+
+ @available(*, unavailable, renamed: "Connection")
+ public enum NetworkStatus: CustomStringConvertible {
+ case notReachable, reachableViaWiFi, reachableViaWWAN
+ public var description: String {
+ switch self {
+ case .reachableViaWWAN: return "Cellular"
+ case .reachableViaWiFi: return "WiFi"
+ case .notReachable: return "No Connection"
+ }
+ }
+ }
+
+ public enum Connection: CustomStringConvertible {
+ case none, wifi, cellular
+ public var description: String {
+ switch self {
+ case .cellular: return "Cellular"
+ case .wifi: return "WiFi"
+ case .none: return "No Connection"
+ }
+ }
+ }
+
+ public var whenReachable: NetworkReachable?
+ public var whenUnreachable: NetworkUnreachable?
+
+ @available(*, deprecated: 4.0, renamed: "allowsCellularConnection")
+ public let reachableOnWWAN: Bool = true
+
+ /// Set to `false` to force Reachability.connection to .none when on cellular connection (default value `true`)
+ public var allowsCellularConnection: Bool
+
+ // The notification center on which "reachability changed" events are being posted
+ public var notificationCenter: NotificationCenter = NotificationCenter.default
+
+ @available(*, deprecated: 4.0, renamed: "connection.description")
+ public var currentReachabilityString: String {
+ return "\(connection)"
+ }
+
+ @available(*, unavailable, renamed: "connection")
+ public var currentReachabilityStatus: Connection {
+ return connection
+ }
+
+ public var connection: Connection {
+ if flags == nil {
+ try? setReachabilityFlags()
+ }
+
+ switch flags?.connection {
+ case .none?, nil: return .none
+ case .cellular?: return allowsCellularConnection ? .cellular : .none
+ case .wifi?: return .wifi
+ }
+ }
+
+ fileprivate var isRunningOnDevice: Bool = {
+ #if targetEnvironment(simulator)
+ return false
+ #else
+ return true
+ #endif
+ }()
+
+ fileprivate var notifierRunning = false
+ fileprivate let reachabilityRef: SCNetworkReachability
+ fileprivate let reachabilitySerialQueue: DispatchQueue
+ fileprivate(set) var flags: SCNetworkReachabilityFlags? {
+ didSet {
+ guard flags != oldValue else { return }
+ reachabilityChanged()
+ }
+ }
+
+ required public init(reachabilityRef: SCNetworkReachability, queueQoS: DispatchQoS = .default, targetQueue: DispatchQueue? = nil) {
+ self.allowsCellularConnection = true
+ self.reachabilityRef = reachabilityRef
+ self.reachabilitySerialQueue = DispatchQueue(label: "uk.co.ashleymills.reachability", qos: queueQoS, target: targetQueue)
+ }
+
+ public convenience init?(hostname: String, queueQoS: DispatchQoS = .default, targetQueue: DispatchQueue? = nil) {
+ guard let ref = SCNetworkReachabilityCreateWithName(nil, hostname) else { return nil }
+ self.init(reachabilityRef: ref, queueQoS: queueQoS, targetQueue: targetQueue)
+ }
+
+ public convenience init?(queueQoS: DispatchQoS = .default, targetQueue: DispatchQueue? = nil) {
+ var zeroAddress = sockaddr()
+ zeroAddress.sa_len = UInt8(MemoryLayout<sockaddr>.size)
+ zeroAddress.sa_family = sa_family_t(AF_INET)
+
+ guard let ref = SCNetworkReachabilityCreateWithAddress(nil, &zeroAddress) else { return nil }
+
+ self.init(reachabilityRef: ref, queueQoS: queueQoS, targetQueue: targetQueue)
+ }
+
+ deinit {
+ stopNotifier()
+ }
+}
+
+public extension Reachability {
+
+ // MARK: - *** Notifier methods ***
+ func startNotifier() throws {
+ guard !notifierRunning else { return }
+
+ let callback: SCNetworkReachabilityCallBack = { (reachability, flags, info) in
+ guard let info = info else { return }
+
+ let reachability = Unmanaged<Reachability>.fromOpaque(info).takeUnretainedValue()
+ reachability.flags = flags
+ }
+
+ var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
+ context.info = UnsafeMutableRawPointer(Unmanaged<Reachability>.passUnretained(self).toOpaque())
+ if !SCNetworkReachabilitySetCallback(reachabilityRef, callback, &context) {
+ stopNotifier()
+ throw ReachabilityError.UnableToSetCallback
+ }
+
+ if !SCNetworkReachabilitySetDispatchQueue(reachabilityRef, reachabilitySerialQueue) {
+ stopNotifier()
+ throw ReachabilityError.UnableToSetDispatchQueue
+ }
+
+ // Perform an initial check
+ try setReachabilityFlags()
+
+ notifierRunning = true
+ }
+
+ func stopNotifier() {
+ defer { notifierRunning = false }
+
+ SCNetworkReachabilitySetCallback(reachabilityRef, nil, nil)
+ SCNetworkReachabilitySetDispatchQueue(reachabilityRef, nil)
+ }
+
+ // MARK: - *** Connection test methods ***
+ @available(*, deprecated: 4.0, message: "Please use `connection != .none`")
+ var isReachable: Bool {
+ return connection != .none
+ }
+
+ @available(*, deprecated: 4.0, message: "Please use `connection == .cellular`")
+ var isReachableViaWWAN: Bool {
+ // Check we're not on the simulator, we're REACHABLE and check we're on WWAN
+ return connection == .cellular
+ }
+
+ @available(*, deprecated: 4.0, message: "Please use `connection == .wifi`")
+ var isReachableViaWiFi: Bool {
+ return connection == .wifi
+ }
+
+ var description: String {
+ guard let flags = flags else { return "unavailable flags" }
+ let W = isRunningOnDevice ? (flags.isOnWWANFlagSet ? "W" : "-") : "X"
+ let R = flags.isReachableFlagSet ? "R" : "-"
+ let c = flags.isConnectionRequiredFlagSet ? "c" : "-"
+ let t = flags.isTransientConnectionFlagSet ? "t" : "-"
+ let i = flags.isInterventionRequiredFlagSet ? "i" : "-"
+ let C = flags.isConnectionOnTrafficFlagSet ? "C" : "-"
+ let D = flags.isConnectionOnDemandFlagSet ? "D" : "-"
+ let l = flags.isLocalAddressFlagSet ? "l" : "-"
+ let d = flags.isDirectFlagSet ? "d" : "-"
+
+ return "\(W)\(R) \(c)\(t)\(i)\(C)\(D)\(l)\(d)"
+ }
+}
+
+fileprivate extension Reachability {
+
+ func setReachabilityFlags() throws {
+ try reachabilitySerialQueue.sync { [unowned self] in
+ var flags = SCNetworkReachabilityFlags()
+ if !SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags) {
+ self.stopNotifier()
+ throw ReachabilityError.UnableToGetInitialFlags
+ }
+
+ self.flags = flags
+ }
+ }
+
+ func reachabilityChanged() {
+ let block = connection != .none ? whenReachable : whenUnreachable
+
+ DispatchQueue.main.async { [weak self] in
+ guard let strongSelf = self else { return }
+ block?(strongSelf)
+ strongSelf.notificationCenter.post(name: .reachabilityChanged, object: strongSelf)
+ }
+ }
+}
+
+extension SCNetworkReachabilityFlags {
+
+ typealias Connection = Reachability.Connection
+
+ var connection: Connection {
+ guard isReachableFlagSet else { return .none }
+
+ // If we're reachable, but not on an iOS device (i.e. simulator), we must be on WiFi
+ #if targetEnvironment(simulator)
+ return .wifi
+ #else
+ var connection = Connection.none
+
+ if !isConnectionRequiredFlagSet {
+ connection = .wifi
+ }
+
+ if isConnectionOnTrafficOrDemandFlagSet {
+ if !isInterventionRequiredFlagSet {
+ connection = .wifi
+ }
+ }
+
+ if isOnWWANFlagSet {
+ connection = .cellular
+ }
+
+ return connection
+ #endif
+ }
+
+ var isOnWWANFlagSet: Bool {
+ #if os(iOS)
+ return contains(.isWWAN)
+ #else
+ return false
+ #endif
+ }
+ var isReachableFlagSet: Bool {
+ return contains(.reachable)
+ }
+ var isConnectionRequiredFlagSet: Bool {
+ return contains(.connectionRequired)
+ }
+ var isInterventionRequiredFlagSet: Bool {
+ return contains(.interventionRequired)
+ }
+ var isConnectionOnTrafficFlagSet: Bool {
+ return contains(.connectionOnTraffic)
+ }
+ var isConnectionOnDemandFlagSet: Bool {
+ return contains(.connectionOnDemand)
+ }
+ var isConnectionOnTrafficOrDemandFlagSet: Bool {
+ return !intersection([.connectionOnTraffic, .connectionOnDemand]).isEmpty
+ }
+ var isTransientConnectionFlagSet: Bool {
+ return contains(.transientConnection)
+ }
+ var isLocalAddressFlagSet: Bool {
+ return contains(.isLocalAddress)
+ }
+ var isDirectFlagSet: Bool {
+ return contains(.isDirect)
+ }
+ var isConnectionRequiredAndTransientFlagSet: Bool {
+ return intersection([.connectionRequired, .transientConnection]) == [.connectionRequired, .transientConnection]
+ }
+}