Swift for Xojo™ Developers (cont’d) : SYSTEM

Here is a class the replicates most of the functionality of the Xojo™ SYSTEM module
it does NOT support these items at this time

  • keyChain
  • keyChainCount
  • isFunctionAvailable
  • penButtonPushed
  • penPressure
  • penType

and microSeconds and ticks report “time since last reboot”… Xojo reports only “Awake” time

import AppKit
import Foundation
import SystemConfiguration
import AVFoundation


public let system = ds$SYSTEM()

open class ds$SYSTEM : NSObject {
  //
  public enum LogType { // this is so main app doen't need to import 'os'
    case none
    case info
    case debug
    case error
    case fault
    var category : String  {
      switch self {
        case .none  : return ""
        case .info  : return "INFO : "
        case .debug : return "DEBUG : "
        case .error : return "ERROR : "
        case .fault : return "FAULT : "
      }
    }
    /*
     var realLogType : OSLogType {
     switch self {
     case .none : return .default
     case .info : return .info
     case .debug : return .debug
     case .error : return .error
     case .fault : return .fault
     }
     }
     */
  }

  //

  private var zInterfaces : [networkInterface] = []
  private var zLoopBack   : networkInterface?
  public var random       = dsRandom()

  public override init() {
    super.init()
    readNetworkInformation()
  }
  // MARK: PROPERTIES
  public var commandLine           : String { return Join(CommandLine.arguments," ") }
  public let cursors               = ds$CURSORS() // see below
  public var fontCount             : Int    { return NSFontManager.shared.availableFontFamilies.count }
  // keyChain                      : not supported at this time
  // keyChainCount                 : not supported at this time
  public var lastFontIndex         : Int    { fontCount-1 }
  public var mouseDown             : Bool   { return (NSEvent.pressedMouseButtons != 0 ) }
  public var mouseX                : Int    { return Int(NSEvent.mouseLocation.x) }
  public var mouseY                : Int    { return Int(NSEvent.mouseLocation.y) }
  // network                       : SEE BELOW
  // networkInterfaceCount         : SEE BELOW

  // MARK: METHODS
  public func Beep() { NSSound.beep() }

  public func debugLog(_ msg:String) { print(msg) } // goes to Xcode console only

  public func environmentVariable(_ name:String) -> String {
    if let value = ProcessInfo.processInfo.environment[name] { return value }
    return ""
  }

  public func fontAT(_ index:Int) -> String {
    if index >= 0 && index < fontCount { return NSFontManager.shared.availableFontFamilies[index] }
    fatalError("ERROR: Font(index:\(index)) is out of range.")
  }

  // isFunctionAvailable : not supported at this time

  // seems os_log isn't reliable :( so falling back to NSLOG
  // public func log(_ type:LogType,  _ msg : StaticString) { os_log(type.realLogType, msg) }
  // goes to Xcode console AND OS console
  public func log(_ type:LogType,  _ msg : String) { NSLog(type.category+msg) }

  // Note : Xojo™ returns "awake" time, this routine returns actual "Uptime" (ie. since reboot)
  public var microSeconds : Double {
    var temp = timespec()
    if 0 != clock_gettime(CLOCK_MONOTONIC_RAW, &temp) {
      fatalError("Could not execute clock_gettime, errno: \(errno)")
    }
    let upTime = (Double(temp.tv_sec)*1e6)+Double(temp.tv_nsec) / 1e6
    //let awake  = ProcessInfo.processInfo.systemUptime
    return Double(upTime)
  }

  public func getNetWorkInterface(_ index:Int) -> networkInterface{
    if index >= 0 && index < networkInterfaceCount {
      return zInterfaces[index]
    }
    fatalError("ERROR: Font(index:\(index)) is out of range.")
  }

  // penButtonPushed : not supported at this time
  // penPressure     : not supported at this time
  // penType         : not supported at this time

  // random          : SEE Class Header

  public func speak(_ value : String,_ interrupt:Bool=false) {
    let utterance   = AVSpeechUtterance(string: value)
    utterance.voice = AVSpeechSynthesisVoice(language: "en-US")
    let synthesizer = AVSpeechSynthesizer()
    synthesizer.speak(utterance)
  }

  public var ticks : Int { return Int(microSeconds * (1e6/60) ) }

  public let OSVersion = ds$Version()
  public class ds$Version : NSObject {
    public var majorVersion : Int    { return ProcessInfo.processInfo.operatingSystemVersion.majorVersion }
    public var minorVersion : Int    { return ProcessInfo.processInfo.operatingSystemVersion.minorVersion }
    public var patchVersion : Int    { return ProcessInfo.processInfo.operatingSystemVersion.patchVersion }
    public var toString     : String { return "\(majorVersion).\(minorVersion).\(patchVersion)" }
  }
  //
  // CURSOR METHODS
  //
  public class ds$CURSORS :  NSObject {
    public var arrow                        : NSCursor { return NSCursor.arrow }
    public var closedHand                   : NSCursor { return NSCursor.closedHand }
    public var contextualMenu               : NSCursor { return NSCursor.contextualMenu }
    public var crosshair                    : NSCursor { return NSCursor.crosshair }
    public var current                      : NSCursor { return NSCursor.current }
    public var currentSystem                : NSCursor { return NSCursor.currentSystem ?? NSCursor.arrow }
    public var disappearingItem             : NSCursor { return NSCursor.disappearingItem}
    public var dragCopy                     : NSCursor { return NSCursor.dragCopy }
    public var dragLink                     : NSCursor { return NSCursor.dragLink }
    public var iBeam                        : NSCursor { return NSCursor.iBeam }
    public var iBeamCursorForVerticalLayout : NSCursor { return NSCursor.iBeamCursorForVerticalLayout }
    public var openHand                     : NSCursor { return NSCursor.openHand }
    public var operationNotAllowed          : NSCursor { return NSCursor.operationNotAllowed}
    public var pointingHand                 : NSCursor { return NSCursor.pointingHand }
    public var resizeDown                   : NSCursor { return NSCursor.resizeDown }
    public var resizeLeft                   : NSCursor { return NSCursor.resizeLeft }
    public var resizeLeftRight              : NSCursor { return NSCursor.resizeLeftRight }
    public var resizeRight                  : NSCursor { return NSCursor.resizeRight }
    public var resizeUp                     : NSCursor { return NSCursor.resizeUp }
    public var resizeUpDown                 : NSCursor { return NSCursor.resizeUpDown }

    public func hide() { NSCursor.hide() }
    public func show() { NSCursor.unhide() }
  }
  //
  // NETWORK METHODS
  //
  public let network = ds$Network()

  public class ds$Network : NSObject {
    public var  isConnected : Bool  {
      var zeroAddress        = sockaddr_in()
      zeroAddress.sin_len    = UInt8(MemoryLayout<sockaddr_in>.size)
      zeroAddress.sin_family = sa_family_t(AF_INET)

      guard let defaultRouteReachability = withUnsafePointer(to: &zeroAddress, {
        $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
          SCNetworkReachabilityCreateWithAddress(nil, $0)
        }
      }) else {
        return false
      }
      var flags: SCNetworkReachabilityFlags = []
      if !SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags) {
        return false
      }
      let isReachable     = flags.contains(.reachable)
      let needsConnection = flags.contains(.connectionRequired)
      return (isReachable && !needsConnection)
    }

    public func lookupIPAddress(DNS : String)  -> [String] {
      var temp : String = DNS
      if Left(temp,4).uppercased() != "HTTP" { temp="http://\(temp)" }
      let url = URL(string:temp)
      var ipList: [String] = []
      guard let hostname = url?.host else { return ipList }
      guard let host     = hostname.withCString({gethostbyname($0)}) else { return ipList }
      guard host.pointee.h_length > 0 else { return ipList }

      var index = 0

      while host.pointee.h_addr_list[index] != nil {
        var addr: in_addr = in_addr()
        memcpy(&addr.s_addr, host.pointee.h_addr_list[index], Int(host.pointee.h_length))
        guard let remoteIPAsC = inet_ntoa(addr) else {
          return ipList
        }
        ipList.append(String.init(cString: remoteIPAsC))
        index += 1
      }
      return ipList
    }
  }

  public var networkInterfaceCount : Int { return zInterfaces.count }

  public struct networkInterface {
    let name       : String
    let ip         : String
    let netmask    : String
    let macAddress : String
  }

  public var loopBack : networkInterface? { return zLoopBack }

  private  func readNetworkInformation()  {
    // rds : 26Feb2022 - There must be a BETTER way to associate macAddress to IP Address
    guard let nic = SCNetworkInterfaceCopyAll() as? [SCNetworkInterface] else { return  }
    let listOfMacAddress = nic.map(SCNetworkInterfaceGetHardwareAddressString).compactMap { $0 as String?  }

    //--------------------------------------------------
    // Get list of all interfaces on the local machine:
    var ifaddr : UnsafeMutablePointer<ifaddrs>? = nil
    if getifaddrs(&ifaddr) == 0 {

      var ptr = ifaddr
      while( ptr != nil) { // For each interface ...
        let flags = Int32(ptr!.pointee.ifa_flags)
        var addr  = ptr!.pointee.ifa_addr.pointee

        // Check for running IPv4, IPv6 interfaces. Skip the loopback interface.
        //    if (flags & (IFF_UP|IFF_RUNNING|IFF_LOOPBACK)) == (IFF_UP|IFF_RUNNING) {
        if true {
          if addr.sa_family == UInt8(AF_INET) || addr.sa_family == UInt8(AF_INET6) {
            var mask = ptr!.pointee.ifa_netmask.pointee
            // Convert interface address to a human readable string:
            let zero     = CChar(0)
            var hostname = [CChar](repeating: zero, count: Int(NI_MAXHOST))
            var netmask  = [CChar](repeating: zero, count: Int(NI_MAXHOST))

            if (getnameinfo(&addr, socklen_t(addr.sa_len), &hostname, socklen_t(hostname.count),nil, socklen_t(0), NI_NUMERICHOST) == 0) {
              let address = String(cString: hostname)
              let name    = ptr!.pointee.ifa_name!
              let ifname  = String(cString: name)

              if (getnameinfo(&mask, socklen_t(mask.sa_len), &netmask, socklen_t(netmask.count), nil, socklen_t(0), NI_NUMERICHOST) == 0) {
                let netmaskIP = String(cString: netmask)

                // associate macAddress
                var macAddress : String = ""
                for i in(0...nic.count-1) {
                  let z = nic[i]
                  let nicName : String = SCNetworkInterfaceGetBSDName(z )! as String
                  if ifname==nicName {
                    macAddress = listOfMacAddress[i]
                    break
                  }
                }

                if Left(address,1) != ":" {
                  let info = networkInterface(name       : ifname,
                                ip         : address,
                                netmask    : netmaskIP,
                                macAddress : macAddress)

                  if flags & IFF_LOOPBACK > 0 {
                    zLoopBack = info
                  } else {
                    zInterfaces.append(info)
                  }
                }
              }
            }
          }
        }
        ptr = ptr!.pointee.ifa_next
      }
      freeifaddrs(ifaddr)
    }
  }
}