diff --git a/src/client/MacSyncApp.swift b/src/client/MacSyncApp.swift
index 7b11713..23ca572 100644
--- a/src/client/MacSyncApp.swift
+++ b/src/client/MacSyncApp.swift
@@ -4,7 +4,9 @@ import Foundation
import ICalSync
import IMailSync
import IMessageSync
+import INoteSync
import IPhotoSync
+import IReminderSync
import MacSyncShared
import Photos
@@ -27,11 +29,15 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
private let iPhotoSync = IPhotoSync.SyncManager.shared
private let iMailSync = IMailSync.SyncManager.shared
private let iCalSync = ICalSync.SyncManager.shared
+ private let iReminderSync = IReminderSync.SyncManager.shared
+ private let iNoteSync = INoteSync.SyncManager.shared
private let webServer = LocalWebServer()
private var syncStatusItem: NSMenuItem?
private var iphotoStatusItem: NSMenuItem?
private var imailStatusItem: NSMenuItem?
private var icalStatusItem: NSMenuItem?
+ private var ireminderStatusItem: NSMenuItem?
+ private var inoteStatusItem: NSMenuItem?
private var contactRenderStatusItem: NSMenuItem?
func applicationDidFinishLaunching(_ notification: Notification) {
@@ -62,6 +68,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
iPhotoSync.startSync()
iMailSync.startSync()
iCalSync.startSync()
+ iReminderSync.startSync()
+ iNoteSync.startSync()
ContactRenderPoller.shared.start()
} else {
let existingDeviceId = try? macSyncSharedKeychain.loadString(key: "deviceId")
@@ -96,6 +104,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
self.iPhotoSync.startSync()
self.iMailSync.startSync()
self.iCalSync.startSync()
+ self.iReminderSync.startSync()
+ self.iNoteSync.startSync()
ContactRenderPoller.shared.start()
}
} catch {
@@ -134,6 +144,16 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
menu.addItem(iCalItem)
icalStatusItem = iCalItem
+ let iReminderItem = NSMenuItem(title: "Reminders: idle", action: nil, keyEquivalent: "")
+ iReminderItem.isEnabled = false
+ menu.addItem(iReminderItem)
+ ireminderStatusItem = iReminderItem
+
+ let iNoteItem = NSMenuItem(title: "Notes: idle", action: nil, keyEquivalent: "")
+ iNoteItem.isEnabled = false
+ menu.addItem(iNoteItem)
+ inoteStatusItem = iNoteItem
+
let contactRenderItem = NSMenuItem(title: "Contacts: idle", action: nil, keyEquivalent: "")
contactRenderItem.isEnabled = false
menu.addItem(contactRenderItem)
@@ -223,6 +243,32 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
icalStatusItem?.title = "iCal: idle"
}
+ // Reminders
+ if iReminderSync.isSyncing {
+ ireminderStatusItem?.title = "Reminders: \(iReminderSync.currentOperation)"
+ } else if let last = iReminderSync.lastSyncCompletedAt {
+ let total = iReminderSync.stats.reminderCount
+ let suffix = total > 0 ? " (\(total) reminders)" : ""
+ ireminderStatusItem?.title = "Reminders: \(relFormatter.localizedString(for: last, relativeTo: Date()))\(suffix)"
+ } else if iReminderSync.syncError != .none {
+ ireminderStatusItem?.title = "Reminders: \(iReminderSync.syncError.message)"
+ } else {
+ ireminderStatusItem?.title = "Reminders: idle"
+ }
+
+ // Notes
+ if iNoteSync.isSyncing {
+ inoteStatusItem?.title = "Notes: \(iNoteSync.currentOperation)"
+ } else if let last = iNoteSync.lastSyncCompletedAt {
+ let total = iNoteSync.stats.noteCount
+ let suffix = total > 0 ? " (\(total) notes)" : ""
+ inoteStatusItem?.title = "Notes: \(relFormatter.localizedString(for: last, relativeTo: Date()))\(suffix)"
+ } else if iNoteSync.syncError != .none {
+ inoteStatusItem?.title = "Notes: \(iNoteSync.syncError.message)"
+ } else {
+ inoteStatusItem?.title = "Notes: idle"
+ }
+
// Contact render
let poller = ContactRenderPoller.shared
if !poller.lastError.isEmpty {
@@ -242,6 +288,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
iPhotoSync.syncNow()
iMailSync.syncNow()
iCalSync.syncNow()
+ iReminderSync.syncNow()
+ iNoteSync.syncNow()
}
@objc private func openSettings() {
@@ -280,7 +328,11 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
calendars.target = self
submenu.addItem(calendars)
- let automation = NSMenuItem(title: "Grant Messages + Mail Automation…", action: #selector(requestAutomation), keyEquivalent: "")
+ let reminders = NSMenuItem(title: "Grant Reminders…", action: #selector(requestReminders), keyEquivalent: "")
+ reminders.target = self
+ submenu.addItem(reminders)
+
+ let automation = NSMenuItem(title: "Grant Messages + Mail + Notes Automation…", action: #selector(requestAutomation), keyEquivalent: "")
automation.target = self
submenu.addItem(automation)
@@ -372,13 +424,28 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
}
}
+ @objc private func requestReminders() {
+ NSLog("MacSync: requesting reminders permission")
+ Task {
+ let granted = await iReminderSync.reader.requestAuthorization()
+ if !granted {
+ await MainActor.run {
+ NSWorkspace.shared.open(
+ URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Reminders")!
+ )
+ }
+ }
+ }
+ }
+
@objc private func requestAutomation() {
- NSLog("MacSync: priming Messages + Mail automation TCC")
+ NSLog("MacSync: priming Messages + Mail + Notes automation TCC")
// Sending a harmless AppleScript to each app registers MacSync in the Automation
// pane and surfaces the first-time "Allow" prompt.
let probe = """
tell application \"Messages\" to get name
tell application \"Mail\" to get name
+ tell application \"Notes\" to get name
"""
var errorInfo: NSDictionary?
_ = NSAppleScript(source: probe)?.executeAndReturnError(&errorInfo)
diff --git a/src/client/Resources/Info.plist.template b/src/client/Resources/Info.plist.template
index 38ccb10..17b957c 100644
--- a/src/client/Resources/Info.plist.template
+++ b/src/client/Resources/Info.plist.template
@@ -19,10 +19,10 @@
NSContactsUsageDescription
MacSync syncs your contacts to your personal server.
NSCalendarsUsageDescription
- MacSync syncs your calendar events to your personal server.
+ MacSync reads and writes calendar events so changes made on the web sync back to Calendar.app.
NSRemindersUsageDescription
- MacSync syncs your reminders to your personal server.
+ MacSync reads and writes Reminders so changes made on the web sync back to Reminders.app.
NSAppleEventsUsageDescription
- MacSync reads your Mail.app messages to sync email to your personal server.
+ MacSync controls Messages.app, Mail.app, and Notes.app via AppleScript to sync messages, email, and notes to and from your personal server.