diff --git a/Sparkle/updates/appcast.xml b/Sparkle/updates/appcast.xml index 4b450ee..082545a 100644 --- a/Sparkle/updates/appcast.xml +++ b/Sparkle/updates/appcast.xml @@ -81,5 +81,22 @@ + + 2.0.6 + + https://iglance.github.io/release-notes.html + + Mon, 20 Apr 2020 19:00:38 +0200 + 10.12 + + + + + + + + + + diff --git a/Sparkle/updates/iGlance2.0.6-2.0.1.delta b/Sparkle/updates/iGlance2.0.6-2.0.1.delta new file mode 100644 index 0000000..d136e38 Binary files /dev/null and b/Sparkle/updates/iGlance2.0.6-2.0.1.delta differ diff --git a/Sparkle/updates/iGlance2.0.6-2.0.2.delta b/Sparkle/updates/iGlance2.0.6-2.0.2.delta new file mode 100644 index 0000000..0a9f038 Binary files /dev/null and b/Sparkle/updates/iGlance2.0.6-2.0.2.delta differ diff --git a/Sparkle/updates/iGlance2.0.6-2.0.3.delta b/Sparkle/updates/iGlance2.0.6-2.0.3.delta new file mode 100644 index 0000000..5db2c9b Binary files /dev/null and b/Sparkle/updates/iGlance2.0.6-2.0.3.delta differ diff --git a/Sparkle/updates/iGlance2.0.6-2.0.4.delta b/Sparkle/updates/iGlance2.0.6-2.0.4.delta new file mode 100644 index 0000000..19810ac Binary files /dev/null and b/Sparkle/updates/iGlance2.0.6-2.0.4.delta differ diff --git a/Sparkle/updates/iGlance2.0.6-2.0.5.delta b/Sparkle/updates/iGlance2.0.6-2.0.5.delta new file mode 100644 index 0000000..0aada3c Binary files /dev/null and b/Sparkle/updates/iGlance2.0.6-2.0.5.delta differ diff --git a/Sparkle/updates/iGlance2.0.6-2.0.delta b/Sparkle/updates/iGlance2.0.6-2.0.delta new file mode 100644 index 0000000..681cb95 Binary files /dev/null and b/Sparkle/updates/iGlance2.0.6-2.0.delta differ diff --git a/iGlance/iGlance/iGlance.xcodeproj/project.pbxproj b/iGlance/iGlance/iGlance.xcodeproj/project.pbxproj index bf851a0..827d135 100644 --- a/iGlance/iGlance/iGlance.xcodeproj/project.pbxproj +++ b/iGlance/iGlance/iGlance.xcodeproj/project.pbxproj @@ -44,6 +44,7 @@ B472EC20243101DB002F7504 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = B472EC1F243101DB002F7504 /* Logger.swift */; }; B47D3C8D23ABEDF100DE911F /* CustomDashboardBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = B47D3C8C23ABEDF100DE911F /* CustomDashboardBox.swift */; }; B48BD19123E73F8E00152931 /* CpuUsageMenuBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B48BD19023E73F8E00152931 /* CpuUsageMenuBarItem.swift */; }; + B4924F09244A2492000D8BDD /* RepeatingTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4924F08244A2492000D8BDD /* RepeatingTimer.swift */; }; B49CD58F23EB17D9009711E6 /* CodableColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B49CD58E23EB17D9009711E6 /* CodableColor.swift */; }; B49CDFF523E1E18A0070D664 /* AppMover.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B49CDFF223E1DB9B0070D664 /* AppMover.framework */; }; B49CDFF623E1E18A0070D664 /* AppMover.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B49CDFF223E1DB9B0070D664 /* AppMover.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -61,6 +62,7 @@ B4C37E6D23A8D811008C7FC0 /* ContentManagerViewControllerHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4C37E6C23A8D811008C7FC0 /* ContentManagerViewControllerHolder.swift */; }; B4C37E7023A91F3C008C7FC0 /* ThemeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4C37E6F23A91F3C008C7FC0 /* ThemeManager.swift */; }; B4C8DD2523AA3DB8008DAE45 /* ContentManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4C8DD2423AA3DB8008DAE45 /* ContentManagerView.swift */; }; + B4C985E3244CB694002F8D3F /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4C985E2244CB694002F8D3F /* Utils.swift */; }; B4CF2F7823C4F5CD00544511 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4CF2F7723C4F5CD00544511 /* AppDelegate.swift */; }; B4CF2F7C23C4F5CE00544511 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B4CF2F7B23C4F5CE00544511 /* Assets.xcassets */; }; B4CF2F7F23C4F5CE00544511 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B4CF2F7D23C4F5CE00544511 /* Main.storyboard */; }; @@ -161,6 +163,7 @@ B472EC1F243101DB002F7504 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; B47D3C8C23ABEDF100DE911F /* CustomDashboardBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDashboardBox.swift; sourceTree = ""; }; B48BD19023E73F8E00152931 /* CpuUsageMenuBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CpuUsageMenuBarItem.swift; sourceTree = ""; }; + B4924F08244A2492000D8BDD /* RepeatingTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepeatingTimer.swift; sourceTree = ""; }; B49CD58E23EB17D9009711E6 /* CodableColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodableColor.swift; sourceTree = ""; }; B49CDFF223E1DB9B0070D664 /* AppMover.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = AppMover.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B49CDFF723E1E1950070D664 /* SMCKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SMCKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -176,6 +179,7 @@ B4C37E6C23A8D811008C7FC0 /* ContentManagerViewControllerHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentManagerViewControllerHolder.swift; sourceTree = ""; }; B4C37E6F23A91F3C008C7FC0 /* ThemeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = ""; }; B4C8DD2423AA3DB8008DAE45 /* ContentManagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentManagerView.swift; sourceTree = ""; }; + B4C985E2244CB694002F8D3F /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = ""; }; B4CF2F7523C4F5CD00544511 /* iGlanceLauncher.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iGlanceLauncher.app; sourceTree = BUILT_PRODUCTS_DIR; }; B4CF2F7723C4F5CD00544511 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; B4CF2F7B23C4F5CE00544511 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -305,6 +309,7 @@ B4DE727F23C2644A0001CF46 /* BatteryInfo.swift */, B415FF2B23F45F6000ED6567 /* MemoryInfo.swift */, B4F4AF3A23FAFBD60033B5CE /* FanInfo.swift */, + B4C985E2244CB694002F8D3F /* Utils.swift */, ); path = SystemInfo; sourceTree = ""; @@ -392,6 +397,7 @@ B4C37E6E23A91F2F008C7FC0 /* Theme */, B4FE0DAD23EF1BCB00AE94E6 /* Utils */, B46789B523A69ECA00CE572D /* AppDelegate.swift */, + B4924F08244A2492000D8BDD /* RepeatingTimer.swift */, B472EC1F243101DB002F7504 /* Logger.swift */, B4DE725E23C257610001CF46 /* Extensions.swift */, B49F5D5623C5BCAF009E9BE6 /* UserSettings.swift */, @@ -718,9 +724,11 @@ B4E909112427B33F00260479 /* BatteryMenuBarItem.swift in Sources */, B4C37E6D23A8D811008C7FC0 /* ContentManagerViewControllerHolder.swift in Sources */, B48BD19123E73F8E00152931 /* CpuUsageMenuBarItem.swift in Sources */, + B4C985E3244CB694002F8D3F /* Utils.swift in Sources */, B4CFC47623FC393300A0ED15 /* FanViewController.swift in Sources */, B4A0D37A242E5FD500690098 /* NetworkStatisticsMenuView.swift in Sources */, B4E909132427B5EA00260479 /* BatteryViewController.swift in Sources */, + B4924F09244A2492000D8BDD /* RepeatingTimer.swift in Sources */, B4FE0DB123EF1E0100AE94E6 /* LineGraph.swift in Sources */, B4FE0DB323EF210500AE94E6 /* Graph.swift in Sources */, B43ABF2A23AA32FA007F93BA /* SidebarViewController.swift in Sources */, @@ -908,7 +916,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 2.0.5; + CURRENT_PROJECT_VERSION = 2.0.6; DEVELOPMENT_TEAM = J6GXEPK4NG; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = iGlance/Info.plist; @@ -917,7 +925,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.12; - MARKETING_VERSION = 2.0.5; + MARKETING_VERSION = 2.0.6; PRODUCT_BUNDLE_IDENTIFIER = io.github.iglance.iGlance; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -935,7 +943,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 2.0.5; + CURRENT_PROJECT_VERSION = 2.0.6; DEVELOPMENT_TEAM = J6GXEPK4NG; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = iGlance/Info.plist; @@ -944,7 +952,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.12; - MARKETING_VERSION = 2.0.5; + MARKETING_VERSION = 2.0.6; PRODUCT_BUNDLE_IDENTIFIER = io.github.iglance.iGlance; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/iGlance/iGlance/iGlance/AppDelegate.swift b/iGlance/iGlance/iGlance/AppDelegate.swift index 10be5a7..a1eb6af 100644 --- a/iGlance/iGlance/iGlance/AppDelegate.swift +++ b/iGlance/iGlance/iGlance/AppDelegate.swift @@ -35,7 +35,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { var mainWindow: MainWindowController = NSStoryboard(name: "Main", bundle: nil).instantiateController(withIdentifier: "MainWindowController") as! MainWindowController - var currentUpdateLoopTimer: Timer! + var currentUpdateLoopTimer: RepeatingTimer! // MARK: - // MARK: Lifecycle Functions @@ -92,8 +92,8 @@ class AppDelegate: NSObject, NSApplicationDelegate { */ @objc private func onWakeUp(notification: NSNotification) { - // invalidate the old timer and start a new timer - changeUpdateLoopTimeInterval(interval: AppDelegate.userSettings.settings.updateInterval) + // resume the timer + currentUpdateLoopTimer.resume() DDLogInfo("Create a new timer after waking up from sleep") } @@ -102,8 +102,8 @@ class AppDelegate: NSObject, NSApplicationDelegate { */ @objc private func onSleep(notification: NSNotification) { - // invalidate currently used timer - currentUpdateLoopTimer.invalidate() + // suspend the current timer + currentUpdateLoopTimer.suspend() DDLogInfo("Invalidated the current timer") } @@ -214,8 +214,10 @@ class AppDelegate: NSObject, NSApplicationDelegate { * * - Returns: The newly created timer. */ - func createUpdateLoopTimer(interval: Double) -> Timer { - let timer = Timer.scheduledTimer(timeInterval: interval, target: self, selector: #selector(updateLoop), userInfo: nil, repeats: true) + func createUpdateLoopTimer(interval: Double) -> RepeatingTimer { + let timer = RepeatingTimer(timeInterval: interval) + timer.eventHandler = updateLoop + timer.resume() return timer } @@ -223,23 +225,20 @@ class AppDelegate: NSObject, NSApplicationDelegate { /** * The main update loop for the whole app. This function is called every user defined time interval. */ - @objc func updateLoop() { - AppDelegate.menuBarItemManager.updateMenuBarItems() + DispatchQueue.main.async { + AppDelegate.menuBarItemManager.updateMenuBarItems() + } } /** * Changes the update interval of the main loop to the given time interval. */ func changeUpdateLoopTimeInterval(interval: Double) { - // invalidate the currently used timer to stop it - currentUpdateLoopTimer.invalidate() - - // create a new timer object - let timer = createUpdateLoopTimer(interval: interval) - RunLoop.current.add(timer, forMode: RunLoop.Mode.common) - - currentUpdateLoopTimer = timer + // create a new timer instance + let timer = RepeatingTimer(timeInterval: interval) + timer.eventHandler = updateLoop + timer.resume() } // MARK: - diff --git a/iGlance/iGlance/iGlance/Graphs/BarGraph.swift b/iGlance/iGlance/iGlance/Graphs/BarGraph.swift index 77c11c6..5751765 100644 --- a/iGlance/iGlance/iGlance/Graphs/BarGraph.swift +++ b/iGlance/iGlance/iGlance/Graphs/BarGraph.swift @@ -104,13 +104,16 @@ class BarGraph: Graph { * Takes an image and draws the bar on the given image */ private func drawBarGraph(image: inout NSImage, currentValue: Double, barColor: NSColor, gradientColor: NSColor?) { + // if the current value is zero we don't have to draw anything and can return immediately + if currentValue == 0 { + return + } + // lock the focus on the image in order to draw on it image.lockFocus() // get the height of the bar - // prevent a value of zero since this would cause a bug when drawing the bar - let value = (currentValue == 0 ? 0.1 : currentValue) - let barHeight = Double((maxBarHeight / self.maxValue) * value) + let barHeight = Double((maxBarHeight / self.maxValue) * currentValue) // draw the gradient if necessary if gradientColor != nil { diff --git a/iGlance/iGlance/iGlance/Graphs/LineGraph.swift b/iGlance/iGlance/iGlance/Graphs/LineGraph.swift index c58a51f..fd84bc9 100644 --- a/iGlance/iGlance/iGlance/Graphs/LineGraph.swift +++ b/iGlance/iGlance/iGlance/Graphs/LineGraph.swift @@ -84,6 +84,11 @@ class LineGraph: Graph { // iterate the values and draw a bar for each value on the correct position var nextValuePosition = self.imageSize.width - self.borderWidth - 1 for value in valueHistory.makeIterator().reversed() { + // if the value is zero we don't have to draw anything and can continue with the loop + if value == 0 { + continue + } + // calculate the height of the bar let barHeight = Double((self.maxbarHeight / self.maxValue) * value) diff --git a/iGlance/iGlance/iGlance/Logger.swift b/iGlance/iGlance/iGlance/Logger.swift index 207ff0f..e02758c 100644 --- a/iGlance/iGlance/iGlance/Logger.swift +++ b/iGlance/iGlance/iGlance/Logger.swift @@ -56,6 +56,7 @@ class Logger { let savePanel = NSSavePanel() savePanel.nameFieldStringValue = mostRecentLogFileUrl.lastPathComponent + DDLogInfo("Saving the most recent log file") savePanel.begin { result in if result == .OK { var success = false diff --git a/iGlance/iGlance/iGlance/MainWindow/MainViews/NetworkViewController.swift b/iGlance/iGlance/iGlance/MainWindow/MainViews/NetworkViewController.swift index 6c1155a..6f70718 100644 --- a/iGlance/iGlance/iGlance/MainWindow/MainViews/NetworkViewController.swift +++ b/iGlance/iGlance/iGlance/MainWindow/MainViews/NetworkViewController.swift @@ -19,7 +19,11 @@ import CocoaLumberjack class NetworkViewController: MainViewViewController { // MARK: - // MARK: Outlets - @IBOutlet private var networkUsageCheckbox: NSButton! + @IBOutlet private var networkUsageCheckbox: NSButton! { + didSet { + networkUsageCheckbox.state = AppDelegate.userSettings.settings.network.showBandwidth ? .on : .off + } + } // MARK: - // MARK: Actions diff --git a/iGlance/iGlance/iGlance/MainWindow/MainWindowController.swift b/iGlance/iGlance/iGlance/MainWindow/MainWindowController.swift index 3f1ed3a..d3d4424 100644 --- a/iGlance/iGlance/iGlance/MainWindow/MainWindowController.swift +++ b/iGlance/iGlance/iGlance/MainWindow/MainWindowController.swift @@ -34,6 +34,13 @@ class MainWindowController: NSWindowController { required init?(coder: NSCoder) { super.init(coder: coder) + + // show the dock icon if no menu bar item is visible + // this prevents that the app is running but the user has no means to interact with the app + if !AppDelegate.menuBarItemManager.menuBarItems.contains(where: { $0.statusItem.isVisible == true }) { + NSApp.setActivationPolicy(.regular) + DDLogInfo("Dock icon is shown because no menu bar icon is visible") + } } // MARK: - diff --git a/iGlance/iGlance/iGlance/Modals/PreferenceModal/PreferenceModalViewController.swift b/iGlance/iGlance/iGlance/Modals/PreferenceModal/PreferenceModalViewController.swift index 800edf1..40de421 100644 --- a/iGlance/iGlance/iGlance/Modals/PreferenceModal/PreferenceModalViewController.swift +++ b/iGlance/iGlance/iGlance/Modals/PreferenceModal/PreferenceModalViewController.swift @@ -16,7 +16,6 @@ import Cocoa import ServiceManagement import CocoaLumberjack -import Sparkle class PreferenceModalViewController: ModalViewController { // MARK: - @@ -153,6 +152,8 @@ class PreferenceModalViewController: ModalViewController { AppDelegate.userSettings.settings.updateInterval = 2.0 } + DDLogInfo("Set the update interval to \(updateIntervalSelector.indexOfSelectedItem + 1) seconds") + // update the update loop timer guard let appDelegate = AppDelegate.getInstance() else { DDLogError("Could not retrieve the App Delegate Instance") diff --git a/iGlance/iGlance/iGlance/RepeatingTimer.swift b/iGlance/iGlance/iGlance/RepeatingTimer.swift new file mode 100644 index 0000000..11c4ad3 --- /dev/null +++ b/iGlance/iGlance/iGlance/RepeatingTimer.swift @@ -0,0 +1,76 @@ +// Copyright (C) 2020 D0miH & Contributors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import Foundation + + +/** + * This class is a timer that is reaping a given event handler every time interval. + * + * This class is taken from https://gist.github.com/danielgalasko/1da90276f23ea24cb3467c33d2c05768#file-repeatingtimer-swift. + * Huge thanks to Daniel Galasko for providing this code. + */ +class RepeatingTimer { + let timeInterval: TimeInterval + + init(timeInterval: TimeInterval) { + self.timeInterval = timeInterval + } + + private lazy var timer: DispatchSourceTimer = { + let timer = DispatchSource.makeTimerSource() + timer.schedule(deadline: .now() + self.timeInterval, repeating: self.timeInterval) + timer.setEventHandler { [weak self] in + self?.eventHandler?() + } + return timer + }() + + var eventHandler: (() -> Void)? + + private enum State { + case suspended + case resumed + } + + private var state: State = .suspended + + deinit { + timer.setEventHandler {} + timer.cancel() + /* + If the timer is suspended, calling cancel without resuming + triggers a crash. This is documented here https://forums.developer.apple.com/thread/15902 + */ + resume() + eventHandler = nil + } + + func resume() { + if state == .resumed { + return + } + state = .resumed + timer.resume() + } + + func suspend() { + if state == .suspended { + return + } + state = .suspended + timer.suspend() + } +} diff --git a/iGlance/iGlance/iGlance/SystemInfo/DiskInfo.swift b/iGlance/iGlance/iGlance/SystemInfo/DiskInfo.swift index dbc1106..bbab8d4 100644 --- a/iGlance/iGlance/iGlance/SystemInfo/DiskInfo.swift +++ b/iGlance/iGlance/iGlance/SystemInfo/DiskInfo.swift @@ -22,17 +22,9 @@ class DiskInfo { * disk and the second element beeing the unit (e.g. MB, GB, TB...). */ func getInternalDiskSize() -> (Int, String) { - let task = Process() - let outputPipe = Pipe() + guard let output = executeCommand(launchPath: "/usr/sbin/system_profiler", arguments: ["SPNVMeDataType", "SPSerialATADataType"]) else { + DDLogError("An error occurred while executing the system_profiler command") - // execute the system_profiler command - task.launchPath = "/usr/sbin/system_profiler" - task.arguments = ["SPNVMeDataType", "SPSerialATADataType"] - task.standardOutput = outputPipe - task.launch() - let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile() - guard let output = String(data: outputData, encoding: String.Encoding.utf8) else { - DDLogError("An error occurred while casting the command output to a string") return (0, "") } diff --git a/iGlance/iGlance/iGlance/SystemInfo/GpuInfo.swift b/iGlance/iGlance/iGlance/SystemInfo/GpuInfo.swift index f2982a1..375dde9 100644 --- a/iGlance/iGlance/iGlance/SystemInfo/GpuInfo.swift +++ b/iGlance/iGlance/iGlance/SystemInfo/GpuInfo.swift @@ -29,17 +29,8 @@ class GpuInfo { */ func getGpuName() -> String { // pretty similar to https://github.com/sebhildebrandt/systeminformation/blob/master/lib/graphics.js - let task = Process() - let outputPipe = Pipe() - - // execute the system_profiler command - task.launchPath = "/usr/sbin/system_profiler" - task.arguments = ["SPDisplaysDataType"] - task.standardOutput = outputPipe - task.launch() - let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile() - guard let output = String(data: outputData, encoding: String.Encoding.utf8) else { - DDLogError("An error occurred while casting the command output to a string") + guard let output = executeCommand(launchPath: "/usr/sbin/system_profiler", arguments: ["SPDisplaysDataType"]) else { + DDLogError("An error occurred while executing the system_profiler command") return "" } diff --git a/iGlance/iGlance/iGlance/SystemInfo/NetworkInfo.swift b/iGlance/iGlance/iGlance/SystemInfo/NetworkInfo.swift index 2444fe8..19da449 100644 --- a/iGlance/iGlance/iGlance/SystemInfo/NetworkInfo.swift +++ b/iGlance/iGlance/iGlance/SystemInfo/NetworkInfo.swift @@ -62,19 +62,8 @@ class NetworkInfo { */ func getTotalTransmittedBytesOf(interface: String) -> (up: UInt64, down: UInt64) { // create the process to call the netstat commandline tool - let process = Process() - process.launchPath = "/usr/bin/env" - process.arguments = ["netstat", "-bdnI", interface] - - // create a pipe to get the output of the command - let pipe = Pipe() - process.standardOutput = pipe - process.launch() - - // get the output as a string - let data = pipe.fileHandleForReading.readDataToEndOfFile() - guard let commandOutput = String(data: data, encoding: String.Encoding.utf8) else { - DDLogError("An error occurred while casting the command output to a string") + guard let commandOutput = executeCommand(launchPath: "/usr/bin/env", arguments: ["netstat", "-bdnI", interface]) else { + DDLogError("An error occurred while executing the netstat command") return (up: 0, down: 0) } @@ -115,30 +104,61 @@ class NetworkInfo { * Returns the name of the currently used network interface as a string. If something went wrong the default network interface "en0" is returned. */ func getCurrentlyUsedInterface() -> String { - // create the process for the command - let process = Process() - process.launchPath = "/bin/bash" - process.arguments = ["-c", "route get 0.0.0.0 2>/dev/null | grep interface: | awk '{print $2}'"] - - // create the pipe for the output - let pipe = Pipe() - process.standardOutput = pipe - process.launch() - - // get the command output - let commandOutput = pipe.fileHandleForReading.readDataToEndOfFile() - - DDLogInfo("Output of the network interface command: \n\(commandOutput)") - - // get the currently used interface - guard let commandString = String(data: commandOutput, encoding: String.Encoding.utf8) else { - DDLogError("Something went wrong while casting the command output to a string") + // idea is from https://apple.stackexchange.com/a/223446 + // get the srvice list + let arguments = ["networksetup", "-listallhardwareports"] + guard let netCmdOutput = executeCommand(launchPath: "/usr/bin/env", arguments: arguments) else { + DDLogError("Something went wrong while executing the networksetup command") return "en0" } + let interfaceList = netCmdOutput.split(separator: "\n") + .filter { $0.contains("Device:") } + .map { $0.replacingOccurrences(of: "Device: ", with: "") } - // get the interface name - let interfaceName = commandString.trimmingCharacters(in: .whitespacesAndNewlines) + // the default interface is en0 + var activeInterfaces = ["en0"] + + // get all the network interfaces of ifconfig + guard let ifconfigInterfacesCommand = executeCommand(launchPath: "/usr/bin/env", arguments: ["ifconfig", "-lu"]) else { + DDLogError("Something went wrong while executing the ifconfig command to retrieve all interfaces") + return activeInterfaces[0] + } + let ifconfigInterfacesList = ifconfigInterfacesCommand + .replacingOccurrences(of: "\n", with: "") + .split(separator: " ") + .map { String($0) } + + // iterate the list from top to bottom + for interfaceName in interfaceList where ifconfigInterfacesList.contains(interfaceName) { + // get more info about the current network interface + guard let ifconfOutput = executeCommand(launchPath: "/usr/bin/env", arguments: ["ifconfig", interfaceName]) else { + DDLogError("Something went wrong while executing the ifconfig command for interface \(interfaceName)") + continue + } + + // get the status line + let statusString = ifconfOutput.split(separator: "\n") + .filter { $0.contains("status:") } + .map { $0.replacingOccurrences(of: "\tstatus: ", with: "") } + + // check if the string array is empty + if statusString.isEmpty { + DDLogInfo("Could not find a status string for interface \(interfaceName)") + continue + } + + // if we got more values log it, but proceed and check if one of them is active + if statusString.count > 1 { + DDLogError("Read more than one status string for interface \(interfaceName)") + } + + if statusString.contains("active") && !activeInterfaces.contains(interfaceName) { + // add the active interface to the list + activeInterfaces.insert(interfaceName, at: 0) + } + } - return interfaceName.isEmpty ? "en0" : interfaceName + // return the first interface in the list + return activeInterfaces[0] } } diff --git a/iGlance/iGlance/iGlance/SystemInfo/Utils.swift b/iGlance/iGlance/iGlance/SystemInfo/Utils.swift new file mode 100644 index 0000000..3269ed2 --- /dev/null +++ b/iGlance/iGlance/iGlance/SystemInfo/Utils.swift @@ -0,0 +1,33 @@ +// +// Utils.swift +// iGlance +// +// Created by Dominik on 19.04.20. +// Copyright © 2020 D0miH. All rights reserved. +// + +import Foundation +import CocoaLumberjack + +/** + * Executes the program that is located at the given launch path with the given arguments. + * + * - Returns: The output of the command as a string. + */ +func executeCommand(launchPath: String, arguments: [String]) -> String? { + let task = Process() + let outputPipe = Pipe() + + // execute the command + task.launchPath = launchPath + task.arguments = arguments + task.standardOutput = outputPipe + task.launch() + let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile() + guard let output = String(data: outputData, encoding: String.Encoding.utf8) else { + DDLogError("An error occurred while casting the command output to a string") + return nil + } + + return output +}