From 9e6401498c2db430b932f0d52805717b69cf5e1d Mon Sep 17 00:00:00 2001 From: Kristopher Johnson Date: Thu, 31 Oct 2019 18:52:55 -0400 Subject: [PATCH] Add Services Adds these services that can be invoked from any application's Services menu, and assigned keyboard shortcuts in the Keyboard panel of System Preferences: - Start Menubar Countdown - Stop Menubar Countdown - Pause Menubar Countdown - Resume Menubar Countdown --- MenubarCountdown.xcodeproj/project.pbxproj | 4 ++ MenubarCountdown/AppDelegate.swift | 52 ++++++++++------- MenubarCountdown/Info.plist | 55 ++++++++++++++++++ MenubarCountdown/Log.swift | 4 +- MenubarCountdown/ServicesProvider.swift | 66 ++++++++++++++++++++++ 5 files changed, 159 insertions(+), 22 deletions(-) create mode 100644 MenubarCountdown/ServicesProvider.swift diff --git a/MenubarCountdown.xcodeproj/project.pbxproj b/MenubarCountdown.xcodeproj/project.pbxproj index 9ac53b0..c7d94b1 100644 --- a/MenubarCountdown.xcodeproj/project.pbxproj +++ b/MenubarCountdown.xcodeproj/project.pbxproj @@ -25,6 +25,7 @@ 4EB2044E1BED908300D83EF3 /* Stopwatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB2044D1BED908300D83EF3 /* Stopwatch.swift */; }; 4EB204501BEE29D700D83EF3 /* AppUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB2044F1BEE29D700D83EF3 /* AppUserDefaults.swift */; }; 4EB204541BEE307900D83EF3 /* CALayerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB204531BEE307900D83EF3 /* CALayerExtensions.swift */; }; + 4EB58AAF236B881D00150BA5 /* ServicesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB58AAE236B881D00150BA5 /* ServicesProvider.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -70,6 +71,7 @@ 4EB2044D1BED908300D83EF3 /* Stopwatch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Stopwatch.swift; sourceTree = ""; }; 4EB2044F1BEE29D700D83EF3 /* AppUserDefaults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppUserDefaults.swift; sourceTree = ""; }; 4EB204531BEE307900D83EF3 /* CALayerExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CALayerExtensions.swift; sourceTree = ""; }; + 4EB58AAE236B881D00150BA5 /* ServicesProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServicesProvider.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -163,6 +165,7 @@ 4E4865AA1BEFA07E00C159BF /* Image Resources */, 4E4865B01BEFC63700C159BF /* MainMenu.xib */, 4E4D0E552367E5D9004B1404 /* MenubarCountdown.entitlements */, + 4EB58AAE236B881D00150BA5 /* ServicesProvider.swift */, 4E4865B11BEFC63700C159BF /* StartTimerDialog.xib */, 4E4865971BEE749E00C159BF /* StartTimerDialogController.swift */, 4EB2044D1BED908300D83EF3 /* Stopwatch.swift */, @@ -334,6 +337,7 @@ 4EB204501BEE29D700D83EF3 /* AppUserDefaults.swift in Sources */, 4EB2044E1BED908300D83EF3 /* Stopwatch.swift in Sources */, 4E48658E1BEE6CE500C159BF /* Log.swift in Sources */, + 4EB58AAF236B881D00150BA5 /* ServicesProvider.swift in Sources */, 4E4865AF1BEFA2B900C159BF /* StringExtensions.swift in Sources */, 4E48659C1BEE81C200C159BF /* TimerExpiredAlertController.swift in Sources */, 4EB204231BED89F900D83EF3 /* AppDelegate.swift in Sources */, diff --git a/MenubarCountdown/AppDelegate.swift b/MenubarCountdown/AppDelegate.swift index 2a75d2f..a9c88b2 100644 --- a/MenubarCountdown/AppDelegate.swift +++ b/MenubarCountdown/AppDelegate.swift @@ -81,7 +81,11 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele } func applicationDidFinishLaunching(_ notification: Notification) { - Log.debug("application did finish launching") + Log.debug("application did finish launching: \(notification)") + + UNUserNotificationCenter.current().delegate = self + + NSApp.servicesProvider = ServicesProvider(appDelegate: self) stopwatch.reset() @@ -90,8 +94,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele if UserDefaults.standard.bool(forKey: AppUserDefaults.showStartDialogOnLaunchKey) { showStartTimerDialog(self) } - - UNUserNotificationCenter.current().delegate = self } func applicationWillTerminate(_ notification: Notification) { @@ -374,7 +376,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele /** Start the timer. - Called when the user clicks the Start button in the StartTimerDialog. + Called when the user clicks the Start button in the StartTimerDialog + or invokes the Start Countdown service. If user selected "Show notification", then check for authorization and show the notification-authorization dialog if necessary before dismissing @@ -434,8 +437,9 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele /** Reset everything to a not-running state. - Called when the user clicks the Stop menu item or - clicks the OK button in the TimerExpiredAlert. + Called when the user clicks the Stop menu item, + clicks the OK button in the TimerExpiredAlert, + or invokes the Stop Countdown service. */ @IBAction func stopTimer(_ sender: AnyObject) { Log.debug("stop timer") @@ -451,35 +455,43 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele /** Pause the countdown timer. - Called when the user chooses the Pause menu item. + Called when the user chooses the Pause menu item or invokes the Pause Countdown service. */ @IBAction func pauseTimer(_ sender: AnyObject) { Log.debug("pause timer") - - isTimerRunning = false - canPause = false - canResume = true + if canPause { + isTimerRunning = false + canPause = false + canResume = true + } + else { + Log.error("can't resume in current state") + } } /** Resume the countdown timer. - Called when the user chooses the Resume menu item. + Called when the user chooses the Resume menu item or invokes the Resume Countdown service. */ @IBAction func resumeTimer(_ sender: AnyObject) { Log.debug("resume timer") + if canResume { + isTimerRunning = true + canPause = true + canResume = false - isTimerRunning = true - canPause = true - canResume = false - - timerSettingSeconds = secondsRemaining + timerSettingSeconds = secondsRemaining - stopwatch.reset() + stopwatch.reset() - updateStatusItemTitle(timeRemaining: timerSettingSeconds) + updateStatusItemTitle(timeRemaining: timerSettingSeconds) - waitForNextSecond() + waitForNextSecond() + } + else { + Log.error("can't resume in current state") + } } /** diff --git a/MenubarCountdown/Info.plist b/MenubarCountdown/Info.plist index 2527dd0..8bb34ff 100644 --- a/MenubarCountdown/Info.plist +++ b/MenubarCountdown/Info.plist @@ -34,5 +34,60 @@ MainMenu NSPrincipalClass NSApplication + NSServices + + + NSMessage + startCountdown + NSPortName + Menubar Countdown + NSServiceDescription + Show the Menubar Countdown start dialog + NSMenuItem + + default + Start Menubar Countdown + + + + NSMessage + stopCountdown + NSPortName + Menubar Countdown + NSServiceDescription + Stop Menubar Countdown timer + NSMenuItem + + default + Stop Menubar Countdown + + + + NSMessage + pauseCountdown + NSPortName + Menubar Countdown + NSServiceDescription + Pause the Menubar Countdown timer + NSMenuItem + + default + Pause Menubar Countdown + + + + NSMessage + resumeCountdown + NSPortName + Menubar Countdown + NSServiceDescription + Resume the Menubar Countdown timer when paused + NSMenuItem + + default + Resume Menubar Countdown + + + diff --git a/MenubarCountdown/Log.swift b/MenubarCountdown/Log.swift index cc9f182..3f05854 100644 --- a/MenubarCountdown/Log.swift +++ b/MenubarCountdown/Log.swift @@ -29,7 +29,7 @@ struct Log { let filename = file.lastPathComponent let msg = "ERROR: \(message) [\(function) \(filename):\(line)]" - os_log("%{private}@", type: .error, msg) + os_log("%{public}@", type: .error, msg) } /** @@ -53,7 +53,7 @@ struct Log { // Note: we log using default level rather than .debug so that the // messages always go into the log for a debug build - os_log("%{private}@", msg) + os_log("%{public}@", msg) #endif } } diff --git a/MenubarCountdown/ServicesProvider.swift b/MenubarCountdown/ServicesProvider.swift new file mode 100644 index 0000000..05b0e9b --- /dev/null +++ b/MenubarCountdown/ServicesProvider.swift @@ -0,0 +1,66 @@ +// +// ServicesProvider.swift +// Menubar Countdown +// +// Copyright © 2019 Kristopher Johnson. All rights reserved. +// + +import Foundation +import Cocoa + +/** + Implements the Services menu items for the application. + + The provided services are + + - Start Countdown: show the start dialog + - Stop Countdown: reset the timer + - Pause Countdown: pause the timer + - Resume Countdown: resume paused timer + + See also + + - The `NSServices` entries in `Info.plist` + - Construction and registration of the service provider in `AppDelegate.applicationDidFinishLaunching()` + + */ +@objc class ServicesProvider: NSObject { + + private var appDelegate: AppDelegate + + init(appDelegate: AppDelegate) { + self.appDelegate = appDelegate + } + + /** + Handle a Start Countdown service request. + */ + @objc func startCountdown(_ pboard: NSPasteboard, userData: String, error: NSErrorPointer) { + Log.debug("Start Countdown service was requested") + appDelegate.showStartTimerDialog(self) + } + + /** + Handle a Stop Countdown service request. + */ + @objc func stopCountdown(_ pboard: NSPasteboard, userData: String, error: NSErrorPointer) { + Log.debug("Stop Countdown service was requested") + appDelegate.stopTimer(self) + } + + /** + Handle a Pause Countdown service request. + */ + @objc func pauseCountdown(_ pboard: NSPasteboard, userData: String, error: NSErrorPointer) { + Log.debug("Pause Countdown service was requested") + appDelegate.pauseTimer(self) + } + + /** + Handle a Resume Countdown service request. + */ + @objc func resumeCountdown(_ pboard: NSPasteboard, userData: String, error: NSErrorPointer) { + Log.debug("Resume Countdown service was requested") + appDelegate.resumeTimer(self) + } +}