diff --git a/.github/workflows/on_push.yml b/.github/workflows/on_push.yml index 8e2e62b..ac258bb 100644 --- a/.github/workflows/on_push.yml +++ b/.github/workflows/on_push.yml @@ -229,7 +229,7 @@ jobs: id: prepare run: | mkdir release - mv ./WakaTime.zip release/macos-wakatime-${{ needs.version.outputs.semver_tag }}.zip + mv ./WakaTime.zip release/macos-wakatime.zip - name: "Create release" uses: softprops/action-gh-release@master @@ -241,7 +241,7 @@ jobs: target_commitish: ${{ github.sha }} draft: false files: | - ./release/macos-wakatime-${{ needs.version.outputs.semver_tag }}.zip + ./release/macos-wakatime.zip env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - diff --git a/README.md b/README.md index f19ab55..8e63430 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Mac system tray app for automatic time tracking and metrics generated from your ## Install -1. Download the [latest release](https://github.com/wakatime/macos-wakatime/releases/latest/download/WakaTime.zip). +1. Download the [latest release](https://github.com/wakatime/macos-wakatime/releases/latest/download/macos-wakatime.zip). 2. Move `WakaTime.app` into your `Applications` folder, and run `WakaTime.app`. 3. Enter your [WakaTime API Key][api key], then press `Save`. 4. Use Xcode like normal and your coding activity will be displayed on your [WakaTime dashboard][dashboard] diff --git a/WakaTime/AppDelegate.swift b/WakaTime/AppDelegate.swift index a985339..4aa3757 100644 --- a/WakaTime/AppDelegate.swift +++ b/WakaTime/AppDelegate.swift @@ -2,9 +2,11 @@ import AppUpdater import Cocoa import UserNotifications -class AppDelegate: NSObject, NSApplicationDelegate { +class AppDelegate: NSObject, NSApplicationDelegate, StatusBarDelegate { var window: NSWindow! var statusBarItem: NSStatusItem! + var statusBarA11yItem: NSMenuItem! + var statusBarA11yStatus: Bool = true var settingsWindowController = SettingsWindowController() var monitoredAppsWindowController = MonitoredAppsWindowController() var wakaTime: WakaTime? @@ -12,9 +14,6 @@ class AppDelegate: NSObject, NSApplicationDelegate { let updater = AppUpdater(owner: "wakatime", repo: "macos-wakatime") func applicationDidFinishLaunching(_ aNotification: Notification) { - - wakaTime = WakaTime() - // Handle deep links let eventManager = NSAppleEventManager.shared() eventManager.setEventHandler( @@ -29,6 +28,12 @@ class AppDelegate: NSObject, NSApplicationDelegate { let menu = NSMenu() + statusBarA11yItem = NSMenuItem( + title: "* A11y permission needed *", + action: #selector(AppDelegate.a11yClicked(_:)), + keyEquivalent: "") + statusBarA11yItem.isHidden = true + menu.addItem(statusBarA11yItem) menu.addItem(withTitle: "Dashboard", action: #selector(AppDelegate.dashboardClicked(_:)), keyEquivalent: "") menu.addItem(withTitle: "Settings", action: #selector(AppDelegate.settingsClicked(_:)), keyEquivalent: "") menu.addItem(withTitle: "Monitored Apps", action: #selector(AppDelegate.monitoredAppsClicked(_:)), keyEquivalent: "") @@ -38,6 +43,8 @@ class AppDelegate: NSObject, NSApplicationDelegate { menu.addItem(withTitle: "Quit", action: #selector(AppDelegate.quitClicked(_:)), keyEquivalent: "") statusBarItem.menu = menu + + wakaTime = WakaTime(self) } @objc func handleGetURL(_ event: NSAppleEventDescriptor, withReplyEvent replyEvent: NSAppleEventDescriptor) { @@ -71,7 +78,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { if error.isCancelled { let alert = NSAlert() alert.messageText = "Up to date" - alert.informativeText = "You have the latest version." + alert.informativeText = "You have the latest version (\(Bundle.main.version))." alert.alertStyle = NSAlert.Style.warning alert.addButton(withTitle: "OK") alert.runModal() @@ -80,7 +87,12 @@ class AppDelegate: NSObject, NSApplicationDelegate { NSLog(String(describing: error)) let alert = NSAlert() alert.messageText = "Error" - alert.informativeText = error.localizedDescription + let max = 200 + if error.localizedDescription.count <= max { + alert.informativeText = error.localizedDescription + } else { + alert.informativeText = String(error.localizedDescription.prefix(max).appending("…")) + } alert.alertStyle = NSAlert.Style.warning alert.addButton(withTitle: "OK") alert.runModal() @@ -88,10 +100,26 @@ class AppDelegate: NSObject, NSApplicationDelegate { } } + @objc func a11yClicked(_ sender: AnyObject) { + a11yStatusChanged(Accessibility.requestA11yPermission()) + } + @objc func quitClicked(_ sender: AnyObject) { NSApplication.shared.terminate(self) } + func a11yStatusChanged(_ hasPermission: Bool) { + guard statusBarA11yStatus != hasPermission else { return } + + statusBarA11yStatus = hasPermission + if hasPermission { + statusBarItem.button?.image = NSImage(named: NSImage.Name("WakaTime")) + } else { + statusBarItem.button?.image = NSImage(named: NSImage.Name("WakaTimeDisabled")) + } + statusBarA11yItem.isHidden = hasPermission + } + private func showSettings() { NSApp.activate(ignoringOtherApps: true) settingsWindowController.showWindow(self) diff --git a/WakaTime/Helpers/Accessibility.swift b/WakaTime/Helpers/Accessibility.swift index bae9ea5..ddf9c5b 100644 --- a/WakaTime/Helpers/Accessibility.swift +++ b/WakaTime/Helpers/Accessibility.swift @@ -1,12 +1,10 @@ import AppKit class Accessibility { - public static func requestA11yPermission() { + public static func requestA11yPermission() -> Bool { let prompt = kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String let options: NSDictionary = [prompt: true] let appHasPermission = AXIsProcessTrustedWithOptions(options) - if appHasPermission { - // print("has a11y permission") - } + return appHasPermission } } diff --git a/WakaTime/Resources/Assets.xcassets/WakaTimeDisabled.imageset/32.png b/WakaTime/Resources/Assets.xcassets/WakaTimeDisabled.imageset/32.png new file mode 100644 index 0000000..f26081d Binary files /dev/null and b/WakaTime/Resources/Assets.xcassets/WakaTimeDisabled.imageset/32.png differ diff --git a/WakaTime/Resources/Assets.xcassets/WakaTimeDisabled.imageset/64.png b/WakaTime/Resources/Assets.xcassets/WakaTimeDisabled.imageset/64.png new file mode 100644 index 0000000..ccf7e5c Binary files /dev/null and b/WakaTime/Resources/Assets.xcassets/WakaTimeDisabled.imageset/64.png differ diff --git a/WakaTime/Resources/Assets.xcassets/WakaTimeDisabled.imageset/Contents.json b/WakaTime/Resources/Assets.xcassets/WakaTimeDisabled.imageset/Contents.json new file mode 100644 index 0000000..dc72a07 --- /dev/null +++ b/WakaTime/Resources/Assets.xcassets/WakaTimeDisabled.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "32.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "64.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/WakaTime/WakaTime.swift b/WakaTime/WakaTime.swift index 22df292..e0271de 100644 --- a/WakaTime/WakaTime.swift +++ b/WakaTime/WakaTime.swift @@ -6,6 +6,7 @@ class WakaTime { // MARK: Watcher let watcher = Watcher() + let delegate: StatusBarDelegate // MARK: Watcher State @@ -24,14 +25,19 @@ class WakaTime { // MARK: Initialization and Setup - init() { + init(_ delegate: StatusBarDelegate) { + self.delegate = delegate + Dependencies.installDependencies() if SettingsManager.shouldRegisterAsLoginItem() { SettingsManager.registerAsLoginItem() } - Accessibility.requestA11yPermission() + if !Accessibility.requestA11yPermission() { + delegate.a11yStatusChanged(false) + } configureFirebase() checkForApiKey() watcher.eventHandler = handleEvent + watcher.statusBarDelegate = delegate } private func configureFirebase() { @@ -115,3 +121,7 @@ class WakaTime { } } } + +protocol StatusBarDelegate { + func a11yStatusChanged(_ hasPermission: Bool) -> Void +} diff --git a/WakaTime/Watcher.swift b/WakaTime/Watcher.swift index c0ce747..78aff59 100644 --- a/WakaTime/Watcher.swift +++ b/WakaTime/Watcher.swift @@ -8,6 +8,7 @@ class Watcher: NSObject { var appVersions: [String: String] = [:] var eventHandler: ((_ app: NSRunningApplication, _ path: URL, _ isWrite: Bool, _ isBuilding: Bool) -> Void)? + var statusBarDelegate: StatusBarDelegate? var isBuilding = false var activeApp: NSRunningApplication? private var observer: AXObserver? @@ -117,8 +118,10 @@ class Watcher: NSObject { observeActivityText(activeWindow: activeWindow) } // NSLog("Watching for file changes on \(app.localizedName ?? "nil")") + self.statusBarDelegate?.a11yStatusChanged(true) } catch { NSLog("Failed to setup AXObserver: \(error.localizedDescription)") + self.statusBarDelegate?.a11yStatusChanged(false) } }