merge batch 3: MacSyncApp menu (reminders+notes+contacts), Info.plist; deploy/entitlements kept apricot

This commit is contained in:
quinn 2026-05-15 18:08:15 -07:00
parent bf3879feba
commit c420741292
2 changed files with 72 additions and 5 deletions

View file

@ -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)

View file

@ -19,10 +19,10 @@
<key>NSContactsUsageDescription</key>
<string>MacSync syncs your contacts to your personal server.</string>
<key>NSCalendarsUsageDescription</key>
<string>MacSync syncs your calendar events to your personal server.</string>
<string>MacSync reads and writes calendar events so changes made on the web sync back to Calendar.app.</string>
<key>NSRemindersUsageDescription</key>
<string>MacSync syncs your reminders to your personal server.</string>
<string>MacSync reads and writes Reminders so changes made on the web sync back to Reminders.app.</string>
<key>NSAppleEventsUsageDescription</key>
<string>MacSync reads your Mail.app messages to sync email to your personal server.</string>
<string>MacSync controls Messages.app, Mail.app, and Notes.app via AppleScript to sync messages, email, and notes to and from your personal server.</string>
</dict>
</plist>