|
6 | 6 | //
|
7 | 7 |
|
8 | 8 | import AppKit
|
| 9 | +import Darwin |
9 | 10 | import Foundation
|
10 | 11 | import IOKit.pwr_mgt // Asertions
|
11 | 12 | import SwiftUI
|
12 | 13 |
|
| 14 | +struct ProcessInfoStruct { |
| 15 | + var pid: Int32 |
| 16 | + var command: String |
| 17 | + var arguments: [String] |
| 18 | +} |
| 19 | + |
13 | 20 | func initialLaunchLogic() {
|
14 | 21 | guard !CommandLineUtilities().unitTestingEnabled() else {
|
15 | 22 | LogManager.debug("App being ran in test mode", logger: uiLog)
|
@@ -177,6 +184,88 @@ private func logDeferralStates() {
|
177 | 184 | LoggerUtilities().logUserDeferrals()
|
178 | 185 | }
|
179 | 186 |
|
| 187 | +func getAllProcesses() -> [ProcessInfoStruct] { |
| 188 | + var processes = [ProcessInfoStruct]() |
| 189 | + |
| 190 | + // Get the number of processes |
| 191 | + var mib: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_ALL] |
| 192 | + var size = 0 |
| 193 | + sysctl(&mib, u_int(mib.count), nil, &size, nil, 0) |
| 194 | + |
| 195 | + let processCount = size / MemoryLayout<kinfo_proc>.size |
| 196 | + var processList = [kinfo_proc](repeating: kinfo_proc(), count: processCount) |
| 197 | + |
| 198 | + // Get the list of processes |
| 199 | + sysctl(&mib, u_int(mib.count), &processList, &size, nil, 0) |
| 200 | + |
| 201 | + // Extract process info |
| 202 | + for process in processList { |
| 203 | + let command = withUnsafePointer(to: process.kp_proc.p_comm) { |
| 204 | + $0.withMemoryRebound(to: CChar.self, capacity: Int(MAXCOMLEN)) { |
| 205 | + String(cString: $0) |
| 206 | + } |
| 207 | + } |
| 208 | + let pid = process.kp_proc.p_pid |
| 209 | + let arguments = getArgumentsForPID(pid: pid) |
| 210 | + processes.append(ProcessInfoStruct(pid: pid, command: command, arguments: arguments)) |
| 211 | + } |
| 212 | + |
| 213 | + return processes |
| 214 | +} |
| 215 | + |
| 216 | +func getArgumentsForPID(pid: Int32) -> [String] { |
| 217 | + var args = [String]() |
| 218 | + |
| 219 | + var mib: [Int32] = [CTL_KERN, KERN_PROCARGS2, pid] |
| 220 | + var size = 0 |
| 221 | + sysctl(&mib, u_int(mib.count), nil, &size, nil, 0) |
| 222 | + |
| 223 | + var buffer = [CChar](repeating: 0, count: size) |
| 224 | + sysctl(&mib, u_int(mib.count), &buffer, &size, nil, 0) |
| 225 | + |
| 226 | + // Convert buffer to a string with proper bounds checking |
| 227 | + let bufferString = String(bytesNoCopy: &buffer, length: size, encoding: .ascii, freeWhenDone: false) |
| 228 | + |
| 229 | + // Split the string into arguments |
| 230 | + if let bufferString = bufferString { |
| 231 | + args = bufferString.split(separator: "\0").map { String($0) } |
| 232 | + } |
| 233 | + |
| 234 | + // Drop the first element which is the full path to the executable |
| 235 | + if !args.isEmpty { |
| 236 | + args.removeFirst() |
| 237 | + } |
| 238 | + |
| 239 | + return args |
| 240 | +} |
| 241 | + |
| 242 | +func isAnyProcessRunning(commandsWithArgs: [(commandPattern: String, arguments: [String]?)]) -> Bool { |
| 243 | + let processes = getAllProcesses() |
| 244 | + for (commandPattern, arguments) in commandsWithArgs { |
| 245 | + let matchingProcesses = processes.filter { process in |
| 246 | + fnmatch(commandPattern, process.command, 0) == 0 && |
| 247 | + (arguments == nil || arguments!.allSatisfy { arg in |
| 248 | + process.arguments.contains(where: { $0.contains(arg) }) |
| 249 | + }) |
| 250 | + } |
| 251 | + if !matchingProcesses.isEmpty { |
| 252 | + return true |
| 253 | + } |
| 254 | + } |
| 255 | + return false |
| 256 | +} |
| 257 | + |
| 258 | +func isDownloadingOrPreparingSoftwareUpdate() -> Bool { |
| 259 | + let commandsWithArgs: [(commandPattern: String, arguments: [String]?)] = [ |
| 260 | + ("softwareupdated", ["/System/Library/PrivateFrameworks/MobileSoftwareUpdate.framework/Support/softwareupdated"]), // When downloading a minor update, this process is running. |
| 261 | + ("installcoordinationd", ["/System/Library/PrivateFrameworks/InstallCoordination.framework/Support/installcoordinationd"]), // When preparing a minor update, this process is running. Unfortunately, after preparing the update, this process appears to stay running. |
| 262 | + ("softwareupdate", ["/usr/bin/softwareupdate", "--fetch-full-installer"]), // When downloading a major upgrade via SoftwareUpdate prefpane, it triggers a --fetch-full-installer run. Nudge also performs this method. |
| 263 | + ("osinstallersetupd" ,["/Applications/*Install macOS *.app/Contents/Frameworks/OSInstallerSetup.framework/Resources/osinstallersetupd"]), // When installing a major upgrade, this process is running. |
| 264 | + // /System/Library/PrivateFrameworks/PackageKit.framework/Resources/installd||system_installd - system_installd may be interesting, but I think installd is being used for any package |
| 265 | + ] |
| 266 | + return isAnyProcessRunning(commandsWithArgs: commandsWithArgs) |
| 267 | +} |
| 268 | + |
180 | 269 | func needToActivateNudge() -> Bool {
|
181 | 270 | if NSApplication.shared.isActive && nudgeLogState.afterFirstLaunch {
|
182 | 271 | LogManager.notice("Nudge is currently the frontmostApplication", logger: uiLog)
|
@@ -265,6 +354,7 @@ private func shouldBailOutEarly() -> Bool {
|
265 | 354 | /// 6. Acceptable Assertions are on
|
266 | 355 | /// 7. Acceptable Apps are in front
|
267 | 356 | /// 8. Refresh Timer hasn't been met
|
| 357 | + /// 9. macOS Updates are downloading or preparing for installation |
268 | 358 | let frontmostApplication = NSWorkspace.shared.frontmostApplication
|
269 | 359 | let pastRequiredInstallationDate = DateManager().pastRequiredInstallationDate()
|
270 | 360 |
|
@@ -313,6 +403,12 @@ private func shouldBailOutEarly() -> Bool {
|
313 | 403 | if isRefreshTimerPassedThreshold() {
|
314 | 404 | return true
|
315 | 405 | }
|
| 406 | + |
| 407 | + // Check if downloading or preparing updates |
| 408 | + if OptionalFeatureVariables.acceptableUpdatePreparingUsage && isDownloadingOrPreparingSoftwareUpdate() { |
| 409 | + LogManager.info("Ignoring Nudge activation - macOS is currently downloading or preparing an update", logger: uiLog) |
| 410 | + return true |
| 411 | + } |
316 | 412 |
|
317 | 413 | return false
|
318 | 414 | }
|
|
0 commit comments