pEp macOS Desktop adapter
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

239 lines
9.3 KiB

  1. //
  2. // AppDelegate.swift
  3. // pEpNotifications
  4. //
  5. // Created by Volker Birk on 28.05.20.
  6. // Copyleft © 2020 pp foundation.
  7. // This file is under GNU General Public License 3.0
  8. //
  9. import Cocoa
  10. import SwiftUI
  11. enum DNType : Int { case ready = 0, downloading, downloadArrived, noDownloadAvailable }
  12. @objc(pEpNotificationProtocol) protocol pEpNotificationProtocol {
  13. func notifyDownload(_ type: Int, withName: NSString, withFilename: NSString)
  14. }
  15. @objc(pEpMacOSAdapterProtocol) protocol pEpMacOSAdapterProtocol {
  16. func subscribeForUpdate(_ endpoint: NSXPCListenerEndpoint?)
  17. func unsubscribeForUpdate()
  18. func updateNow()
  19. func scheduleUpdates()
  20. func stopUpdates()
  21. }
  22. @objc class pEpNotification : NSObject, pEpNotificationProtocol {
  23. var delegate: pEpNotificationProtocol!
  24. func notifyDownload(_ type: Int, withName: NSString, withFilename: NSString) {
  25. NSLog("notifyDownload");
  26. delegate?.notifyDownload(type, withName: withName, withFilename: withFilename)
  27. }
  28. }
  29. @NSApplicationMain
  30. class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDelegate, NSXPCListenerDelegate, pEpNotificationProtocol {
  31. @IBOutlet weak var pEpMenu: NSMenu!
  32. @IBOutlet weak var statusText: NSMenuItem!
  33. @IBOutlet weak var _updateNow: NSMenuItem!
  34. @IBOutlet weak var _scheduleUpdates: NSMenuItem!
  35. @IBOutlet weak var _alwaysShowThisMenu: NSMenuItem!
  36. var statusBarItem: NSStatusItem? = nil
  37. var connection: NSXPCConnection!
  38. var service: pEpMacOSAdapterProtocol!
  39. var nc = NSUserNotificationCenter.default
  40. var clientListener: NSXPCListener!
  41. var receiver: pEpNotification!
  42. @objc func installMenuExtra() {
  43. if statusBarItem == nil {
  44. statusBarItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
  45. statusBarItem?.button?.title = "p≡p"
  46. statusBarItem?.menu = NSApp.menu?.item(at: 0)?.submenu
  47. _updateNow.action = #selector(updateNow)
  48. }
  49. }
  50. func uninstallMenuExtra() {
  51. if (statusBarItem != nil && !UserDefaults.standard.bool(forKey: "AlwaysShowThisMenu")) {
  52. NSStatusBar.system.removeStatusItem(statusBarItem!)
  53. statusBarItem = nil
  54. }
  55. }
  56. func setDownloadState(_ text: String, _ product: Dictionary<String, Any>? = nil) {
  57. if product == nil {
  58. statusText.title = NSLocalizedString(text, comment: "")
  59. statusText.isEnabled = false
  60. }
  61. else {
  62. let name = product?["name"] as! String
  63. statusText.title = NSString.localizedStringWithFormat(NSLocalizedString(text, comment: "") as NSString, name) as String
  64. statusText.isEnabled = true
  65. }
  66. statusText.representedObject = product
  67. }
  68. @IBAction func scheduleUpdates(_ sender: NSMenuItem) {
  69. if sender.state == NSControl.StateValue.on {
  70. sender.state = NSControl.StateValue.off
  71. UserDefaults.standard.set(false, forKey: "ScheduleUpdates")
  72. service.stopUpdates()
  73. }
  74. else {
  75. sender.state = NSControl.StateValue.on
  76. UserDefaults.standard.set(true, forKey: "ScheduleUpdates")
  77. service.scheduleUpdates()
  78. }
  79. }
  80. @IBAction func alwaysShowThisMenu(_ sender: NSMenuItem) {
  81. if sender.state == NSControl.StateValue.on {
  82. sender.state = NSControl.StateValue.off
  83. UserDefaults.standard.set(false, forKey: "AlwaysShowThisMenu")
  84. if statusText.representedObject == nil {
  85. uninstallMenuExtra()
  86. }
  87. }
  88. else {
  89. sender.state = NSControl.StateValue.on
  90. UserDefaults.standard.set(true, forKey: "AlwaysShowThisMenu")
  91. }
  92. }
  93. @IBAction func installNow(_ sender: NSMenuItem) {
  94. uninstallMenuExtra()
  95. let product = sender.representedObject as! Dictionary<String, Any>
  96. let un = product["notification"] as! NSUserNotification
  97. nc.removeDeliveredNotification(un)
  98. NSLog("pEpNotifications: installNow clicked for %@", product["name"] as! String)
  99. NSWorkspace.shared.openFile(product["filename"] as! String)
  100. sender.representedObject = nil
  101. setDownloadState("Connected.")
  102. }
  103. func notifyDownload(_ type: Int, withName: NSString, withFilename: NSString)
  104. {
  105. let _type = DNType.init(rawValue: type)
  106. switch _type {
  107. case .downloading:
  108. NSLog("pEpNotifications: downloading")
  109. setDownloadState(String(format: NSLocalizedString("Downloading update for %@…", comment: ""), withName))
  110. self.performSelector(onMainThread: #selector(installMenuExtra), with:nil, waitUntilDone: false)
  111. case .downloadArrived:
  112. let un = NSUserNotification()
  113. un.title = NSLocalizedString("Update available", comment: "")
  114. un.informativeText = String(format: NSLocalizedString("A new update for %@ is ready to be installed.", comment: ""), withName)
  115. un.actionButtonTitle = NSLocalizedString("Install", comment: "")
  116. un.hasActionButton = true
  117. un.otherButtonTitle = NSLocalizedString("Not now", comment: "")
  118. un.soundName = NSUserNotificationDefaultSoundName
  119. let installTitle : String = NSLocalizedString("Install", comment: "")
  120. un.additionalActions = [NSUserNotificationAction(identifier: "install", title: installTitle)]
  121. un.setValue(true, forKey: "_showsButtons")
  122. un.userInfo = ["name": withName, "filename": withFilename]
  123. nc.deliver(un)
  124. setDownloadState("New version of %@ available", ["name": withName, "filename": withFilename, "notification": un])
  125. case .noDownloadAvailable:
  126. NSLog("pEpNotifications: no download available")
  127. setDownloadState("The software is up to date.")
  128. case .ready:
  129. NSLog("pEpNotifications: ready.")
  130. setDownloadState("Connected.")
  131. case .none:
  132. break;
  133. }
  134. }
  135. @objc func updateNow() {
  136. nc.removeAllDeliveredNotifications()
  137. service.updateNow()
  138. }
  139. @objc func userNotificationCenter(_ : NSUserNotificationCenter, didActivate: NSUserNotification) {
  140. if didActivate.activationType == NSUserNotification.ActivationType.actionButtonClicked {
  141. uninstallMenuExtra()
  142. let filename : String = didActivate.userInfo?["filename"] as! String;
  143. NSLog("pEpNotifications: actionButtonClicked for %@", filename)
  144. NSWorkspace.shared.openFile(filename)
  145. setDownloadState("Connected.")
  146. }
  147. }
  148. func proxyErrorHandler(err: Error) -> Void {
  149. NSLog("%@", err.localizedDescription)
  150. setDownloadState("Connection failed")
  151. }
  152. @objc func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool {
  153. newConnection.exportedInterface = NSXPCInterface.init(with: pEpNotificationProtocol.self)
  154. let obj = pEpNotification()
  155. obj.delegate = self
  156. newConnection.exportedObject = obj;
  157. newConnection.resume()
  158. return true
  159. }
  160. func applicationDidFinishLaunching(_ aNotification: Notification) {
  161. // preference defaults
  162. let appDefaults = ["ScheduleUpdates": true, "AlwaysShowThisMenu": true]
  163. UserDefaults.standard.register(defaults: appDefaults)
  164. if UserDefaults.standard.bool(forKey: "AlwaysShowThisMenu") {
  165. _alwaysShowThisMenu.state = NSControl.StateValue.on
  166. installMenuExtra()
  167. }
  168. else {
  169. _alwaysShowThisMenu.state = NSControl.StateValue.off
  170. }
  171. setDownloadState("Connecting…")
  172. // connect to XPC service
  173. // connection = NSXPCConnection.init(machServiceName: "pEp.foundation.pEpMacOSAdapter") // Commented for hacky TB installer using this branch for building upater app and master for xcpservice. Needs "foundation.pEp.adapter.macOS"
  174. connection = NSXPCConnection.init(machServiceName: "foundation.pEp.adapter.macOS")
  175. if connection != nil {
  176. connection.remoteObjectInterface = NSXPCInterface.init(with: pEpMacOSAdapterProtocol.self)
  177. connection.resume()
  178. service = connection.remoteObjectProxyWithErrorHandler(proxyErrorHandler) as? pEpMacOSAdapterProtocol
  179. // init client callback service
  180. clientListener = NSXPCListener.anonymous()
  181. clientListener.delegate = self
  182. clientListener.resume()
  183. // subscribe and schedule updates
  184. service.subscribeForUpdate(clientListener.endpoint)
  185. if UserDefaults.standard.bool(forKey: "ScheduleUpdates") {
  186. service.scheduleUpdates()
  187. _scheduleUpdates.state = NSControl.StateValue.on
  188. }
  189. else {
  190. service.stopUpdates()
  191. _scheduleUpdates.state = NSControl.StateValue.off
  192. }
  193. service.updateNow()
  194. }
  195. else {
  196. NSLog("pEpNotifications: %@", "cannot connect to pEp.foundation.adapter.macOS")
  197. }
  198. nc.removeAllDeliveredNotifications()
  199. nc.delegate = self
  200. }
  201. func applicationWillTerminate(_ aNotification: Notification) {
  202. service.unsubscribeForUpdate()
  203. connection?.invalidate()
  204. }
  205. }