diff --git a/Nudge/UI/Defaults.swift b/Nudge/UI/Defaults.swift index f42c7f92..11314570 100644 --- a/Nudge/UI/Defaults.swift +++ b/Nudge/UI/Defaults.swift @@ -18,6 +18,7 @@ var nudgeLogState = LogState() struct Globals { static let bundle = Bundle.main static let bundleID = bundle.bundleIdentifier ?? "com.github.macadmins.Nudge" + static let dnc = DistributedNotificationCenter.default() static let nc = NotificationCenter.default static let snc = NSWorkspace.shared.notificationCenter // Preferences diff --git a/Nudge/UI/Main.swift b/Nudge/UI/Main.swift index 46264dc6..fe157d8d 100644 --- a/Nudge/UI/Main.swift +++ b/Nudge/UI/Main.swift @@ -64,6 +64,7 @@ struct ContentView: View { .edgesIgnoringSafeArea(.all) .onAppear { initialLaunchLogic() + handleNudgeActivation() updateUI() } .onReceive(Intervals.nudgeRefreshCycleTimer) { _ in @@ -87,7 +88,6 @@ struct ContentView: View { window?.isMovable = false window?.collectionBehavior = [.fullScreenAuxiliary] window?.delegate = UIConstants.windowDelegate - // _ = needToActivateNudge() } private func handleNudgeActivation() { @@ -177,6 +177,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { handleCommandLineArguments() applyGracePeriodLogic() applyRandomDelayIfNecessary() + updateNudgeState() handleSoftwareUpdateRequirements() } @@ -210,12 +211,6 @@ class AppDelegate: NSObject, NSApplicationDelegate { } } - // Observe screen locking. Maybe useful later - @objc func screenLocked(_ notification: Notification) { - nudgePrimaryState.screenCurrentlyLocked = true - LogManager.info("Screen was locked", logger: utilsLog) - } - @objc func screenParametersChanged(_ notification: Notification) { LogManager.info("Screen parameters changed - Notification Center", logger: utilsLog) UIUtilities().centerNudge() @@ -226,11 +221,6 @@ class AppDelegate: NSObject, NSApplicationDelegate { UIUtilities().centerNudge() } - @objc func screenUnlocked(_ notification: Notification) { - nudgePrimaryState.screenCurrentlyLocked = false - LogManager.info("Screen was unlocked", logger: utilsLog) - } - @objc func spacesStateChanged(_ notification: Notification) { UIUtilities().centerNudge() LogManager.info("Spaces state changed", logger: utilsLog) @@ -291,9 +281,13 @@ class AppDelegate: NSObject, NSApplicationDelegate { private func detectBannedShortcutKeys(with event: NSEvent) -> Bool { guard NSApplication.shared.isActive else { return false } switch event.modifierFlags.intersection(.deviceIndependentFlagsMask) { - // Disable CMD + W - closes the Nudge window and breaks it - case [.command] where event.charactersIgnoringModifiers == "w": - LogManager.warning("Nudge detected an attempt to close the application via CMD + W shortcut key.", logger: utilsLog) + // Disable CMD + H - Hides Nudge + case [.command] where event.charactersIgnoringModifiers == "h": + LogManager.warning("Nudge detected an attempt to hide the application via CMD + H shortcut key.", logger: utilsLog) + return true + // Disable CMD + M - Minimizes Nudge + case [.command] where event.charactersIgnoringModifiers == "m": + LogManager.warning("Nudge detected an attempt to minimize the application via CMD + M shortcut key.", logger: utilsLog) return true // Disable CMD + N - closes the Nudge window and breaks it case [.command] where event.charactersIgnoringModifiers == "n": @@ -303,26 +297,27 @@ class AppDelegate: NSObject, NSApplicationDelegate { case [.command] where event.charactersIgnoringModifiers == "q": LogManager.warning("Nudge detected an attempt to quit the application via CMD + Q shortcut key.", logger: utilsLog) return true - // Disable CMD + M - Minimizes Nudge - case [.command] where event.charactersIgnoringModifiers == "m": - LogManager.warning("Nudge detected an attempt to minimize the application via CMD + M shortcut key.", logger: utilsLog) - return true - // Disable CMD + H - Hides Nudge - case [.command] where event.charactersIgnoringModifiers == "h": - LogManager.warning("Nudge detected an attempt to hide the application via CMD + H shortcut key.", logger: utilsLog) - return true - // Disable CMD + Option + Esc (Force Quit Applications) - case [.command, .option] where event.charactersIgnoringModifiers == "\u{1b}": // Escape key - LogManager.warning("Nudge detected an attempt to open Force Quit Applications via CMD + Option + Esc.", logger: utilsLog) + // Disable CMD + W - closes the Nudge window and breaks it + case [.command] where event.charactersIgnoringModifiers == "w": + LogManager.warning("Nudge detected an attempt to close the application via CMD + W shortcut key.", logger: utilsLog) return true // Disable CMD + Option + M - Minimizes Nudge - case [.command, .option] where event.charactersIgnoringModifiers == "ยต": + case [.command, .option] where event.charactersIgnoringModifiers == "m": LogManager.warning("Nudge detected an attempt to minimise the application via CMD + Option + M shortcut key.", logger: utilsLog) return true // Disable CMD + Option + N - Add tabs to Nudge window - case [.command, .option] where event.charactersIgnoringModifiers == "~": + case [.command, .option] where event.charactersIgnoringModifiers == "n": LogManager.warning("Nudge detected an attempt to add tabs to the application via CMD + Option + N shortcut key.", logger: utilsLog) return true + // Disable CMD + Option + W - Close Window + case [.command, .option] where event.charactersIgnoringModifiers == "w": + LogManager.warning("Nudge detected an attempt to add tabs to the application via CMD + Option + W shortcut key.", logger: utilsLog) + return true + // Disable CMD + Option + Esc (Force Quit Applications) + case [.command, .option] where event.charactersIgnoringModifiers == "\u{1b}": // Escape key + // This doesn't work since Apple allows that shortcut to bypass the application's memory. + LogManager.warning("Nudge detected an attempt to open Force Quit Applications via CMD + Option + Esc.", logger: utilsLog) + return true default: // Don't care about any other shortcut keys return false @@ -335,7 +330,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { if !nudgeLogState.afterFirstLaunch && OptionalFeatureVariables.terminateApplicationsOnLaunch { terminateApplications() } - Globals.nc.addObserver( + Globals.snc.addObserver( self, selector: #selector(terminateApplicationSender(_:)), name: NSWorkspace.didLaunchApplicationNotification, @@ -446,8 +441,8 @@ class AppDelegate: NSObject, NSApplicationDelegate { private func setupNotificationObservers() { setupNotificationCenterObservers() - setupScreenLockObservers() setupScreenChangeObservers() + setupScreenLockObservers() setupWorkspaceNotificationCenterObservers() } @@ -478,19 +473,20 @@ class AppDelegate: NSObject, NSApplicationDelegate { } private func setupScreenLockObservers() { - Globals.nc.addObserver( - self, - selector: #selector(screenLocked), - name: NSNotification.Name("com.apple.screenIsLocked"), - object: nil - ) - - Globals.nc.addObserver( - self, - selector: #selector(screenUnlocked), - name: NSNotification.Name("com.apple.screenIsUnlocked"), - object: nil - ) + Globals.dnc.addObserver( + forName: NSNotification.Name("com.apple.screenIsLocked"), + object: nil, + queue: .main) { _ in + nudgePrimaryState.screenCurrentlyLocked = true + utilsLog.info("Screen was locked") + } + Globals.dnc.addObserver( + forName: NSNotification.Name("com.apple.screenIsUnlocked"), + object: nil, + queue: .main) { _ in + nudgePrimaryState.screenCurrentlyLocked = false + utilsLog.info("Screen was unlocked") + } } private func setupWorkspaceNotificationCenterObservers() { @@ -541,11 +537,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { return } - let shouldRunAsynchronously = OptionalFeatureVariables.asynchronousSoftwareUpdate && - !AppStateManager().requireMajorUpgrade() && - !DateManager().pastRequiredInstallationDate() - - if shouldRunAsynchronously { + if OptionalFeatureVariables.asynchronousSoftwareUpdate { runUpdateAsynchronously() } else { SoftwareUpdate().download() diff --git a/Nudge/Utilities/UILogic.swift b/Nudge/Utilities/UILogic.swift index 788713cc..29e48116 100644 --- a/Nudge/Utilities/UILogic.swift +++ b/Nudge/Utilities/UILogic.swift @@ -191,7 +191,7 @@ private func logUserSessionDeferrals(resetCount: Bool = false) { } func needToActivateNudge() -> Bool { - if NSApplication.shared.isActive { + if NSApplication.shared.isActive && nudgeLogState.afterFirstLaunch { LogManager.notice("Nudge is currently the frontmostApplication", logger: uiLog) return false } @@ -354,7 +354,7 @@ private func updateDualQuitButtonRequirement() { nudgePrimaryState.requireDualQuitButtons = AppStateManager().requireDualQuitButtons() || nudgePrimaryState.userDeferrals > deferralThreshold } -private func updateNudgeState() { +func updateNudgeState() { nudgePrimaryState.deferralCountPastThreshold = nudgePrimaryState.userDeferrals > UserExperienceVariables.allowedDeferrals if nudgePrimaryState.userDeferrals > UserExperienceVariables.allowedDeferralsUntilForcedSecondaryQuitButton { @@ -363,7 +363,7 @@ private func updateNudgeState() { if nudgePrimaryState.deferralCountPastThreshold { if !nudgePrimaryState.hasLoggedDeferralCountPastThreshold { - LogManager.notice("allowedDeferrals has been passed", logger: uiLog) + LogManager.notice("allowedDeferrals has been passed: \(UserExperienceVariables.allowedDeferrals)", logger: uiLog) nudgePrimaryState.hasLoggedDeferralCountPastThreshold = true } } diff --git a/Nudge/Utilities/Utils.swift b/Nudge/Utilities/Utils.swift index adb62028..08d2c8e9 100644 --- a/Nudge/Utilities/Utils.swift +++ b/Nudge/Utilities/Utils.swift @@ -49,17 +49,24 @@ struct AppStateManager { } private func applyBackgroundBlur(to window: NSWindow) { - // load the blur background and send it to the back if we are past the required install date - LogManager.info("Enabling blurred background", logger: uiLog) - nudgePrimaryState.backgroundBlur.removeAll() + // Figure out all the screens upon Nudge launching UIConstants.screens.forEach { screen in - let blurWindowController = BackgroundBlurWindowController() - nudgePrimaryState.backgroundBlur.append(blurWindowController) - blurWindowController.close() - blurWindowController.loadWindow() - blurWindowController.showWindow(nil) + loopedScreen = screen + } + // load the blur background and send it to the back if we are past the required install date + if nudgePrimaryState.backgroundBlur.isEmpty { + LogManager.info("Enabling blurred background", logger: uiLog) + UIConstants.screens.forEach { screen in + let blurWindowController = BackgroundBlurWindowController() + blurWindowController.loadWindow() + blurWindowController.showWindow(nil) + loopedScreen = screen + nudgePrimaryState.backgroundBlur.append(blurWindowController) + } + window.level = NSWindow.Level(rawValue: Int(CGWindowLevelForKey(.maximumWindow) + 1)) + } else { + LogManager.info("Background blur currently set", logger: uiLog) } - window.level = .floating } private func calculateNewRequiredInstallationDateIfNeeded(currentDate: Date, gracePeriodPathCreationDate: Date) -> Date { @@ -774,8 +781,8 @@ struct UIUtilities { private func determineUpdateURL() -> URL? { if let actionButtonPath = FeatureVariables.actionButtonPath { if actionButtonPath.isEmpty { - LogManager.warning("actionButtonPath is set but contains an empty string. No action will be triggered.", logger: utilsLog) - return nil + LogManager.warning("actionButtonPath is set but contains an empty string. Defaulting to out of box behavior.", logger: utilsLog) + return URL(fileURLWithPath: "/System/Library/CoreServices/Software Update.app") } // Check if it's a shell command @@ -804,7 +811,7 @@ struct UIUtilities { return URL(fileURLWithPath: "/System/Library/CoreServices/Software Update.app") } - func executeShellCommand(command: String, userClicked: Bool, configuration: NSWorkspace.OpenConfiguration) { + func executeShellCommand(command: String, userClicked: Bool) { let cmds = command.components(separatedBy: " ") guard let launchPath = cmds.first, let argument = cmds.last else { LogManager.error("Invalid shell command format", logger: uiLog) @@ -849,10 +856,14 @@ struct UIUtilities { if userClicked { LogManager.notice("User clicked updateDevice", logger: uiLog) // Remove forced blur and reset window level - nudgePrimaryState.backgroundBlur.forEach { blurWindowController in - blurWindowController.close() + if !nudgePrimaryState.backgroundBlur.isEmpty { + nudgePrimaryState.backgroundBlur.forEach { blurWindowController in + uiLog.notice("\("Attempting to remove forced blur", privacy: .public)") + blurWindowController.close() + nudgePrimaryState.backgroundBlur.removeAll() + } + NSApp.windows.first?.level = .normal } - NSApp.windows.first?.level = .normal } else { LogManager.notice("Synthetically clicked updateDevice due to allowedDeferral count", logger: uiLog) } @@ -872,22 +883,27 @@ struct UIUtilities { let configuration = NSWorkspace.OpenConfiguration() configuration.activates = true - guard let url = determineUpdateURL() else { - return - } + if let url = determineUpdateURL() { + let openAction = { + if url.isFileURL { + NSWorkspace.shared.openApplication(at: url, configuration: configuration) + } else { + NSWorkspace.shared.open(url) + } + } - let openAction = { - if url.isFileURL { - NSWorkspace.shared.openApplication(at: url, configuration: configuration) + // Execute the action immediately or with a delay based on user interaction + if userClicked { + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: openAction) } else { - NSWorkspace.shared.open(url) + openAction() } - } - - if userClicked { - DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: openAction) } else { - openAction() + if let actionButtonPath = FeatureVariables.actionButtonPath { + executeShellCommand(command: actionButtonPath, userClicked: userClicked) + } else { + LogManager.error("actionButtonPath is nil.", logger: uiLog) + } } postUpdateDeviceActions(userClicked: userClicked)