diff --git a/.gitignore b/.gitignore index 29a1493..3502223 100644 --- a/.gitignore +++ b/.gitignore @@ -54,7 +54,7 @@ playground.xcworkspace # Carthage # # Add this line if you want to avoid checking in source code from Carthage dependencies. -# Carthage/Checkouts +Carthage/Checkouts Carthage/Build diff --git a/Cartfile b/Cartfile new file mode 100644 index 0000000..ead8984 --- /dev/null +++ b/Cartfile @@ -0,0 +1 @@ +github "stephencelis/SQLite.swift" ~> 0.12.0 \ No newline at end of file diff --git a/Cartfile.resolved b/Cartfile.resolved new file mode 100644 index 0000000..fbbe6cf --- /dev/null +++ b/Cartfile.resolved @@ -0,0 +1 @@ +github "stephencelis/SQLite.swift" "0.12.2" diff --git a/MTMR.xcodeproj/project.pbxproj b/MTMR.xcodeproj/project.pbxproj index a720a6d..1918ae1 100644 --- a/MTMR.xcodeproj/project.pbxproj +++ b/MTMR.xcodeproj/project.pbxproj @@ -32,10 +32,14 @@ 607EEA4D2087A8DA009DA5F0 /* CurrencyBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607EEA4C2087A8DA009DA5F0 /* CurrencyBarItem.swift */; }; 60C44AFD20A373A100C0EC91 /* MusicBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C44AFC20A373A100C0EC91 /* MusicBarItem.swift */; }; 60F7D454208CC31400ABF5D2 /* InputSourceBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60F7D453208CC31400ABF5D2 /* InputSourceBarItem.swift */; }; + 8338290C2360CA31008981D7 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8338290B2360CA31008981D7 /* SQLite.framework */; }; + 8338290D2360CA3F008981D7 /* SQLite.framework in Copy Files */ = {isa = PBXBuildFile; fileRef = 8338290B2360CA31008981D7 /* SQLite.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 839ECD8523600BF500BE2DA5 /* NotificationTouchBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 839ECD8423600BF500BE2DA5 /* NotificationTouchBarItem.swift */; }; + 839ECD882360160100BE2DA5 /* Mail.scpt in Sources */ = {isa = PBXBuildFile; fileRef = 839ECD872360160100BE2DA5 /* Mail.scpt */; }; B0008E552080286C003AD4DD /* SupportHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0008E542080286C003AD4DD /* SupportHelpers.swift */; }; B002E642216C0E38002774BA /* CoreDisplay.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B002E641216C0E38002774BA /* CoreDisplay.framework */; }; B00D181D2152F4A5000806F4 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B00D181C2152F4A5000806F4 /* Sparkle.framework */; }; - B00D181F2152F521000806F4 /* Sparkle.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = B00D181C2152F4A5000806F4 /* Sparkle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + B00D181F2152F521000806F4 /* Sparkle.framework in Copy Files */ = {isa = PBXBuildFile; fileRef = B00D181C2152F4A5000806F4 /* Sparkle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; B04B7BB72087398C00C835D0 /* BatteryBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B04B7BB62087398C00C835D0 /* BatteryBarItem.swift */; }; B05600D32083E9BB00EB218D /* CustomSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B05600D22083E9BB00EB218D /* CustomSlider.swift */; }; B059D622205E03F5006E6B86 /* TouchBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B059D621205E03F5006E6B86 /* TouchBarController.swift */; }; @@ -70,14 +74,16 @@ /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ - B00D181E2152F507000806F4 /* CopyFiles */ = { + B00D181E2152F507000806F4 /* Copy Files */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 12; dstPath = ""; dstSubfolderSpec = 10; files = ( - B00D181F2152F521000806F4 /* Sparkle.framework in CopyFiles */, + B00D181F2152F521000806F4 /* Sparkle.framework in Copy Files */, + 8338290D2360CA3F008981D7 /* SQLite.framework in Copy Files */, ); + name = "Copy Files"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ @@ -112,6 +118,10 @@ 607EEA4C2087A8DA009DA5F0 /* CurrencyBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyBarItem.swift; sourceTree = ""; }; 60C44AFC20A373A100C0EC91 /* MusicBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicBarItem.swift; sourceTree = ""; }; 60F7D453208CC31400ABF5D2 /* InputSourceBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputSourceBarItem.swift; sourceTree = ""; }; + 8338290B2360CA31008981D7 /* SQLite.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SQLite.framework; path = Carthage/Build/Mac/SQLite.framework; sourceTree = ""; }; + 839ECD8423600BF500BE2DA5 /* NotificationTouchBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationTouchBarItem.swift; sourceTree = ""; }; + 839ECD86236015E300BE2DA5 /* Messages.scpt */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = Messages.scpt; sourceTree = ""; }; + 839ECD872360160100BE2DA5 /* Mail.scpt */ = {isa = PBXFileReference; lastKnownFileType = text; path = Mail.scpt; sourceTree = ""; }; B0008E542080286C003AD4DD /* SupportHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportHelpers.swift; sourceTree = ""; }; B002E641216C0E38002774BA /* CoreDisplay.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreDisplay.framework; path = ../../../../../System/Library/Frameworks/CoreDisplay.framework; sourceTree = ""; }; B00D181C2152F4A5000806F4 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Sparkle.framework; sourceTree = ""; }; @@ -164,6 +174,7 @@ files = ( B002E642216C0E38002774BA /* CoreDisplay.framework in Frameworks */, B081732A2135F354005D4908 /* CoreBrightness.framework in Frameworks */, + 8338290C2360CA31008981D7 /* SQLite.framework in Frameworks */, B059D62D205F11E8006E6B86 /* DFRFoundation.framework in Frameworks */, B09EB1E6207C0F8E00D5C1E0 /* MultitouchSupport.framework in Frameworks */, B00D181D2152F4A5000806F4 /* Sparkle.framework in Frameworks */, @@ -183,6 +194,7 @@ B059D62B205F11E8006E6B86 /* Frameworks */ = { isa = PBXGroup; children = ( + 8338290B2360CA31008981D7 /* SQLite.framework */, B002E641216C0E38002774BA /* CoreDisplay.framework */, B00D181C2152F4A5000806F4 /* Sparkle.framework */, B08173292135F354005D4908 /* CoreBrightness.framework */, @@ -268,6 +280,8 @@ B0B1742E207D6B590004B740 /* Vox.next.scpt */, B0B1742F207D6B590004B740 /* Vox.nowPlaying.scpt */, B0B17428207D6B580004B740 /* Weather.scpt */, + 839ECD86236015E300BE2DA5 /* Messages.scpt */, + 839ECD872360160100BE2DA5 /* Mail.scpt */, ); path = AppleScripts; sourceTree = ""; @@ -310,6 +324,7 @@ 4CFF5E5B22E623DD00BFB1EE /* YandexWeatherBarItem.swift */, B08126F0217BE19000A98970 /* WidgetProtocol.swift */, B0F54A792295AC7D00B4C509 /* DarkModeBarItem.swift */, + 839ECD8423600BF500BE2DA5 /* NotificationTouchBarItem.swift */, ); path = Widgets; sourceTree = ""; @@ -324,7 +339,7 @@ B082B24B205C7D8000BC04DC /* Sources */, B082B24C205C7D8000BC04DC /* Frameworks */, B082B24D205C7D8000BC04DC /* Resources */, - B00D181E2152F507000806F4 /* CopyFiles */, + B00D181E2152F507000806F4 /* Copy Files */, B0679BBF215AE085000FC6B4 /* ShellScript */, ); buildRules = ( @@ -479,8 +494,10 @@ B08173272135F02B005D4908 /* NightShiftBarItem.swift in Sources */, 36FEF872235A1CFC00A0ABCE /* AppSettings.swift in Sources */, 60F7D454208CC31400ABF5D2 /* InputSourceBarItem.swift in Sources */, + 839ECD882360160100BE2DA5 /* Mail.scpt in Sources */, 36A778BE20A6C27100B38714 /* GeneralExtensions.swift in Sources */, 60669B4320AD8FA80074E817 /* GroupBarItem.swift in Sources */, + 839ECD8523600BF500BE2DA5 /* NotificationTouchBarItem.swift in Sources */, 36C2ECDB207C3FE7003CDA33 /* ItemsParsing.swift in Sources */, B0A7E9AA205D6AA400EEF070 /* KeyPress.swift in Sources */, 36C2ECD7207B6DAE003CDA33 /* TimeTouchBarItem.swift in Sources */, @@ -637,9 +654,16 @@ "$(inherited)", "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", "$(PROJECT_DIR)", + "$(PROJECT_DIR)/Carthage/Build/Mac", ); INFOPLIST_FILE = MTMR/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.14; + OTHER_LDFLAGS = ( + "$(inherited)", + "-framework", + "\"SQLite\"", + ); PRODUCT_BUNDLE_IDENTIFIER = Toxblh.MTMR; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -660,9 +684,16 @@ "$(inherited)", "$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", "$(PROJECT_DIR)", + "$(PROJECT_DIR)/Carthage/Build/Mac", ); INFOPLIST_FILE = MTMR/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.14; + OTHER_LDFLAGS = ( + "$(inherited)", + "-framework", + "\"SQLite\"", + ); PRODUCT_BUNDLE_IDENTIFIER = Toxblh.MTMR; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/MTMR/AppleScripts/Mail.scpt b/MTMR/AppleScripts/Mail.scpt new file mode 100644 index 0000000..3c041cd --- /dev/null +++ b/MTMR/AppleScripts/Mail.scpt @@ -0,0 +1,10 @@ +if application "Mail" is running then + tell application "Mail" + if player state is playing then + return (get artist of current track) & " – " & (get name of current track) + else + return "" + end if + end tell +end if +return "" diff --git a/MTMR/AppleScripts/Messages.scpt b/MTMR/AppleScripts/Messages.scpt new file mode 100644 index 0000000..f0efadc --- /dev/null +++ b/MTMR/AppleScripts/Messages.scpt @@ -0,0 +1,10 @@ +if application "Messages" is running then + tell application "Messages" + if player state is playing then + return (get artist of current track) & " – " & (get name of current track) + else + return "" + end if + end tell +end if +return "" diff --git a/MTMR/ItemsParsing.swift b/MTMR/ItemsParsing.swift index 062409b..3990921 100644 --- a/MTMR/ItemsParsing.swift +++ b/MTMR/ItemsParsing.swift @@ -56,6 +56,13 @@ class SupportedTypesHolder { parameters: [.align: .align(.left)] ) }, + "reply": { _ in ( + item: .staticButton(title: "Reply"), + action: .shellScript(executable: "/usr/bin/open", parameters: ["sms://"]), + longAction: .none, + parameters: [.align: .align(.right)] + ) }, + "delete": { _ in ( item: .staticButton(title: "del"), action: .keyPress(keycode: 117), @@ -177,6 +184,27 @@ class SupportedTypesHolder { parameters: [:] ) }, + "notification": { decoder in + enum CodingKeys: String, CodingKey { case refreshInterval; case disableMarquee; case image } + let container = try decoder.container(keyedBy: CodingKeys.self) + let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) + let disableMarquee = try container.decodeIfPresent(Bool.self, forKey: .disableMarquee) + if var img = try container.decodeIfPresent(Source.self, forKey: .image) { + return ( + item: .notification(interval: interval ?? 5.0, disableMarquee: disableMarquee ?? false), + action: .shellScript(executable: "/usr/bin/open", parameters: ["sms://"]), + longAction: .none, + parameters: [.image: .image(source: img)] + ) + } else { + return ( + item: .notification(interval: interval ?? 5.0, disableMarquee: disableMarquee ?? false), + action: .shellScript(executable: "/usr/bin/open", parameters: ["sms://"]), + longAction: .none, + parameters: [:] + ) + } + }, ] static let sharedInstance = SupportedTypesHolder() @@ -220,6 +248,7 @@ enum ItemType: Decodable { case currency(interval: Double, from: String, to: String, full: Bool) case inputsource case music(interval: Double, disableMarquee: Bool) + case notification(interval: Double, disableMarquee: Bool) case group(items: [BarItemDefinition]) case nightShift case dnd @@ -267,6 +296,7 @@ enum ItemType: Decodable { case currency case inputsource case music + case notification case group case nightShift case dnd @@ -283,7 +313,7 @@ enum ItemType: Decodable { let source = try container.decode(Source.self, forKey: .source) let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0 self = .appleScriptTitledButton(source: source, refreshInterval: interval) - + case .shellScriptTitledButton: let source = try container.decode(Source.self, forKey: .source) let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0 @@ -320,7 +350,7 @@ enum ItemType: Decodable { let api_key = try container.decodeIfPresent(String.self, forKey: .api_key) ?? "32c4256d09a4c52b38aecddba7a078f6" let icon_type = try container.decodeIfPresent(String.self, forKey: .icon_type) ?? "text" self = .weather(interval: interval, units: units, api_key: api_key, icon_type: icon_type) - + case .yandexWeather: let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 1800.0 self = .yandexWeather(interval: interval) @@ -340,6 +370,11 @@ enum ItemType: Decodable { let disableMarquee = try container.decodeIfPresent(Bool.self, forKey: .disableMarquee) ?? false self = .music(interval: interval, disableMarquee: disableMarquee) + case .notification: + let interval = try container.decodeIfPresent(Double.self, forKey: .refreshInterval) ?? 5.0 + let disableMarquee = try container.decodeIfPresent(Bool.self, forKey: .disableMarquee) ?? false + self = .notification(interval: interval, disableMarquee: disableMarquee) + case .group: let items = try container.decode([BarItemDefinition].self, forKey: .items) self = .group(items: items) diff --git a/MTMR/TouchBarController.swift b/MTMR/TouchBarController.swift index 7c7b559..b71c066 100644 --- a/MTMR/TouchBarController.swift +++ b/MTMR/TouchBarController.swift @@ -45,6 +45,8 @@ extension ItemType { return "com.toxblh.mtmr.inputsource." case .music(interval: _): return "com.toxblh.mtmr.music." + case .notification(interval: _): + return "com.toxblh.mtmr.notification." case .group(items: _): return "com.toxblh.mtmr.groupBar." case .nightShift: @@ -98,7 +100,7 @@ class TouchBarController: NSObject, NSTouchBarDelegate { } blacklistAppIdentifiers = AppSettings.blacklistedAppIds - + NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didLaunchApplicationNotification, object: nil) NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didTerminateApplicationNotification, object: nil) NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(activeApplicationChanged), name: NSWorkspace.didActivateApplicationNotification, object: nil) @@ -258,7 +260,7 @@ class TouchBarController: NSObject, NSTouchBarDelegate { } else { barItem = AppScrubberTouchBarItem(identifier: identifier, autoResize: autoResize) } - case .volume: + case .volume: if case let .image(source)? = item.additionalParameters[.image] { barItem = VolumeViewController(identifier: identifier, image: source.image) } else { @@ -280,6 +282,12 @@ class TouchBarController: NSObject, NSTouchBarDelegate { barItem = InputSourceBarItem(identifier: identifier) case let .music(interval: interval, disableMarquee: disableMarquee): barItem = MusicBarItem(identifier: identifier, interval: interval, disableMarquee: disableMarquee) + case let .notification(interval: interval, disableMarquee: disableMarquee): + if case let .image(source)? = item.additionalParameters[.image] { + barItem = NotificationBarItem(identifier: identifier, interval: interval, disableMarquee: disableMarquee, image: source.image) + } else { + barItem = NotificationBarItem(identifier: identifier, interval: interval, disableMarquee: disableMarquee) + } case let .group(items: items): barItem = GroupBarItem(identifier: identifier, items: items) case .nightShift: diff --git a/MTMR/Widgets/NotificationTouchBarItem.swift b/MTMR/Widgets/NotificationTouchBarItem.swift new file mode 100644 index 0000000..e362d8e --- /dev/null +++ b/MTMR/Widgets/NotificationTouchBarItem.swift @@ -0,0 +1,566 @@ +// +// NotificationsTouchBarItem.swift +// MTMR +// +// Created by Matthew Cox on 10/22/19. +// Copyright © 2019 Anton Palgunov. All rights reserved. +// + +import Foundation +import Cocoa +import ScriptingBridge +import SQLite +import Contacts +import UserNotifications + +class NotificationBarItem: CustomButtonTouchBarItem { + public enum Message: String { + case Messages = "com.apple.iChat" + case Mail = "com.apple.mail" + } + + + private let notificationIdentifiers = [ + Message.Messages, + Message.Mail, + ] + + private let interval: TimeInterval + private let disableMarquee: Bool + private var messageText: String? + private var messageFrom: String? + private var timer: Timer? + private let iconSize = NSSize(width: 21, height: 21) + private let activity: NSBackgroundActivityScheduler + + init(identifier: NSTouchBarItem.Identifier, interval: TimeInterval, disableMarquee: Bool, image: NSImage? = nil) { + + self.interval = interval + self.disableMarquee = disableMarquee + //tapClosure = { [weak self] in self?.DarkModeToggle() } + if image == nil { + //sliderItem = CustomSlider() + } else { + // sliderItem = CustomSlider(knob: image!) + } + + + activity = NSBackgroundActivityScheduler(identifier: "\(identifier.rawValue).updatecheck") + activity.interval = interval + + super.init(identifier: identifier, title: "⏳") + //var systemBlue: NSColor { set } + isBordered = false + backgroundColor = NSColor.systemRed + //button = item.view as? NSButton else { continue } + + //let textRange = NSRange(location: 0, length: button.title.characters.count) + //let titleColor = useCustomColor.state == NSControl.StateValue.on ? NSColor.black : NSColor.white + //let newTitle = NSMutableAttributedString(string: button.title) + //newTitle.addAttribute(NSAttributedStringKey.foregroundColor, value: titleColor, range: textRange) + //newTitle.addAttribute(NSAttributedStringKey.font, value: button.font!, range: textRange) + //newTitle.setAlignment(.center, range: textRange) + //button.attributedTitle = newTitle + //attributedTitle = "test" + //image = NSImage(name: NSUserGroup) + //title = "\(Noti.0)" + // func getDeliveredNotifications(completionHandler: @escaping ([UNNotification]) -> Void) { + // print("") + // } + // let notificationCenter = UNUserNotificationCenter.current() + + // notificationCenter.requestAuthorization(options: [.alert, .badge, .provisional]) {} + + // UNUserNotificationCenter.current().getDeliveredNotifications { (notifications) in + // print("Noti") + // print(notifications) + + // } + + /* let center = UNUserNotificationCenter.current() + let options: UNAuthorizationOptions = [.alert, .badge, .sound] + center.requestAuthorization(options: options) { (granted, error) in + if granted { + application.registerForRemoteNotifications() + } + } + + func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { + let actionIdentifier = response.actionIdentifier + let content = response.notification.request.content + print("\(content)") + switch actionIdentifier { + case UNNotificationDismissActionIdentifier: // Notification was dismissed by user + // Do something + completionHandler() + case UNNotificationDefaultActionIdentifier: // App was opened from notification + // Do something + completionHandler() + case "com.usernotificationstutorial.reply": + if let textResponse = response as? UNTextInputNotificationResponse { + let reply = textResponse.userText + // Send reply message + completionHandler() + } + case "com.usernotificationstutorial.delete": + // Delete message + completionHandler() + default: + completionHandler() + } + + */ + /*let replyAction = UNTextInputNotificationAction(identifier: "com.usernotificationstutorial.reply", title: "Reply", options: [], textInputButtonTitle: "Send", textInputPlaceholder: "Type your message") + let deleteAction = UNNotificationAction(identifier: "com.usernotificationstutorial.delete", title: "Delete", options: [.authenticationRequired, .destructive]) + let category = UNNotificationCategory(identifier: "com.usernotificationstutorial.message", actions: [replyAction, deleteAction], intentIdentifiers: [], options: []) + center.setNotificationCategories([category]) + + } + + userNotificationCenter(center: center) + */ + tapClosure = { [weak self] in self?.playPause() } + longTapClosure = { [weak self] in self?.nextTrack() } + + refreshAndSchedule() + } + + @objc func marquee() { + let str = title + if str.count > 10 { + let indexFirst = str.index(str.startIndex, offsetBy: 0) + let indexSecond = str.index(str.startIndex, offsetBy: 1) + title = String(str.suffix(from: indexSecond)) + String(str[indexFirst]) + } + } + + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc func playPause() { + for ident in notificationIdentifiers { + if let messageApplication = SBApplication(bundleIdentifier: ident.rawValue) { + if messageApplication.isRunning { + if ident == .Messages { + let mp = (messageApplication as MessagesApplication) + mp.playpause!() + return + } else if ident == .Mail { + let mp = (messageApplication as MailApplication) + mp.playpause!() + return + } + break + } + } + } + } + + @objc func nextTrack() { + for ident in notificationIdentifiers { + if let messageApplication = SBApplication(bundleIdentifier: ident.rawValue) { + if messageApplication.isRunning { + if ident == .Messages { + let mp = (messageApplication as MessagesApplication) + mp.nextTrack!() + updateNotification() + return + } else if ident == .Mail { + let mp = (messageApplication as MailApplication) + mp.nextTrack!() + updateNotification() + return + } + } + } + } + } + + func refreshAndSchedule() { + DispatchQueue.main.async { + self.updateNotification() + DispatchQueue.main.asyncAfter(deadline: .now() + self.interval) { [weak self] in + self?.refreshAndSchedule() + } + } + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + func updateNotification() { + var iconUpdated = false + var titleUpdated = false + var notificationbuilder = "" + + let name = "" + let message = "" + let durl = "" + let person = "" + let dbpath = "" + let Noti = NotiBar().getAll(name: name, message: message, durl: durl, person: person, dbpath: dbpath) + //let lastNotificationItem = "" + + print("\(Noti.0) \(Noti.1) \(Noti.3)") + //icon \(Noti.0) + //Message \(Noti.1) + //button path \(Noti.2) + //Number \(Noti.3) + let fromBox = String(Noti.3) + let messageBox = String(Noti.1) + + if fromBox.isEmpty + { + notificationbuilder = "" + } + else + { + notificationbuilder = "\(fromBox) - \(messageBox)" + } + + let lastNotificationItem = "\(notificationbuilder)" + + for ident in notificationIdentifiers { + if let messageApplication = SBApplication(bundleIdentifier: ident.rawValue) { + if messageApplication.isRunning { + + var tempTitle = "" + if ident == .Messages { + tempTitle = lastNotificationItem + } else if ident == .Mail { + tempTitle = (messageApplication as MailApplication).title + } + + if tempTitle == self.messageText { + return + } else { + self.messageText = tempTitle + } + + if let messageText = self.messageText?.ifNotEmpty { + self.timer?.invalidate() + self.timer = nil + + if (disableMarquee) { + self.title = " " + messageText + } else { + self.title = " " + messageText + " " + self.timer = Timer.scheduledTimer(timeInterval: 0.25, target: self, selector: #selector(self.marquee), userInfo: nil, repeats: true) + } + + titleUpdated = true + } + if let _ = tempTitle.ifNotEmpty, + let appPath = NSWorkspace.shared.absolutePathForApplication(withBundleIdentifier: ident.rawValue) { + let image = NSWorkspace.shared.icon(forFile: appPath) + image.size = self.iconSize + self.image = image + iconUpdated = true + } + break + } + } + } + + DispatchQueue.main.async { + if !iconUpdated { + self.image = nil + } + + if !titleUpdated { + self.title = "" + } + } + } +} + + + + + +class NotiRun { + static func shell(launchPath path: String, arguments args: [String]) -> String { + let task = Process() + task.launchPath = path + task.arguments = args + + let pipe = Pipe() + task.standardOutput = pipe + task.standardError = pipe + task.launch() + + let data = pipe.fileHandleForReading.readDataToEndOfFile() + let output = String(data: data, encoding: .utf8) + task.waitUntilExit() + + return(output!) + } + +} + +class NotiBar +{ + + func getAll(name:String, message:String, durl: String, person:String, dbpath: String) -> (String, String, String, String, String) + { + + let res = NotiRun.shell(launchPath: "/usr/bin/getconf", arguments: ["DARWIN_USER_DIR"]) + let trimmedres = res.trimmingCharacters(in: .whitespacesAndNewlines) + let db = try! Connection("\(trimmedres)com.apple.notificationcenter/db2/db") + + let record = Table("record") + let app = Table("app") + let presented = Expression("presented") + let identifier = Expression("identifier") + let delivered_date = Expression("delivered_date") + let app_id = Expression("app_id") + // let app_id = Expression("app_id") + let data = Expression("data") + var name = "" + var message = "" + var durl = "" + var person = "" + var dbpath = "" + let timestamp = Int(NSDate().timeIntervalSinceReferenceDate - 86400) + //var image = "" + //var reply = "" + //var dismiss = "" + //var other = "" + let Messages = String("com.apple.iChat") + let Mail = String("com.apple.mail") + + for app in try! db.prepare(app.select(app[*])) + { + if ("\(app[identifier]!)" == Messages) + { + + } + if ("\(app[identifier]!)" == Mail) + { + + } + // Messages + // Mail + } + // print("\(timestamp)") + //for record in try! db.prepare(record.join(app, on: record[app_id] == app[app_id]).select(record[*]).filter(delivered_date > timestamp) ) { + //app_id == 4 || + for record in try! db.prepare(record.select(record[*]).filter(delivered_date > timestamp && app_id == 5) ) { + + guard let notifierout = try? PropertyListSerialization.propertyList(from: record[data], options: [], format: nil) else { + fatalError("failed to deserialize") + } + if let notiDescAppName = (notifierout as AnyObject)["app"]! as? String + { + if let ret = self.getPath(appNamedNew: "\(notiDescAppName)") { + let retfix = ("\(ret)") + let rettrimmed = retfix.trimmingCharacters(in: .whitespacesAndNewlines) + + let notifierbuilder = Bundle(url: URL(fileURLWithPath: "\(rettrimmed)"))?.infoDictionary?["CFBundleDisplayName"] as? String ?? Bundle(url: URL(fileURLWithPath: "\(rettrimmed)"))?.infoDictionary?["CFBundleName"] as? String + if notifierbuilder == nil + { + } + else + { + if (notifierbuilder == "Messages" || notifierbuilder == "Mail") + { + if (notifierbuilder == "Messages") + { + name = "💬" + } + else if notifierbuilder == "Mail" + { + name = "✉️" + } + + if let notiDescAppMessageTop = (notifierout as AnyObject)["req"]! as? AnyObject + { + if let notiDescAppMail = (notiDescAppMessageTop as AnyObject)["subt"]! as? String + { + message = notiDescAppMail + } + else if let notiDescAppMessage = (notiDescAppMessageTop as AnyObject)["body"]! as? String + { + message = notiDescAppMessage + } + } + if let notiDescAppURLTop = (notifierout as AnyObject)["req"]! as? AnyObject + { + if let notiDescAppURL = (notiDescAppURLTop as AnyObject)["durl"]! as? String + { + durl = notiDescAppURL + } + } + if let notiDescAppPersonTop = (notifierout as AnyObject)["req"]! as? AnyObject + { + if let notiDescAppPerson = (notiDescAppPersonTop as AnyObject)["titl"]! as? String + { + + person = notiDescAppPerson + } + // let imagee = getContactImage(imgu: "\(person)") + } + } + else + { + name = notifierbuilder! + } + } + } + } + + dbpath = "\(trimmedres)com.apple.notificationcenter/db2/db" + } + //print("\(name)-\(message)-\(durl)-\(person)-\(dbpath)") + return (name, message, durl, person, dbpath) + } + // static func getImage(image: String) -> String + //{ + + //} + //static func getOptionReply(reply: String) -> String + //{ + + //} + //static func getOptionDismiss(dismiss: String) -> String + //{ + + //} + //static func getOptionOther(otNSView()her: String) -> String + //{ + + //} + + func getPath(appNamedNew: String) -> String? { + + if let absopath = NSWorkspace.shared.absolutePathForApplication(withBundleIdentifier: appNamedNew) { + return "\(absopath)" + } else { + return "" + } + } + + func lastModified(path: String) -> NSDate? { + let fileUrl = NSURL(fileURLWithPath: path) + var modified: AnyObject? + do { + try fileUrl.getResourceValue(&modified, forKey: URLResourceKey.contentModificationDateKey) + return modified as? NSDate + } catch let error as NSError { + print("\(#function) Error: \(error)") + return nil + } + } + +} + +func getContactImage(imgu:String) -> NSImage? +{ + let store = CNContactStore() + //let contactStore = CNContactStore() + let keys = [CNContactPhoneNumbersKey, CNContactFamilyNameKey, CNContactGivenNameKey, CNContactNicknameKey, CNContactImageDataKey] as! CNKeyDescriptor + // let request = CNContactFetchRequest(keysToFetch: keys) + + // try? contactStore.enumerateContacts(with: request) { (contact, error) in + + // Do something with contact + + //} + + + do + { + let contacts = try! store.unifiedContacts(matching: CNContact.predicateForContacts(matchingName: imgu), keysToFetch:[keys]) + + if contacts.count > 0 + { + if let image = contacts[0].imageData + { + return NSImage.init(data: image) + } + } + + } + + return nil +} + + +@objc protocol MessagesApplication { + @objc optional var currentTrack: SpotifyTrack { get } + @objc optional func nextTrack() + @objc optional func previousTrack() + @objc optional func playpause() +} + +extension SBApplication: MessagesApplication {} + +@objc protocol MessagesTrack { + @objc optional var artist: String { get } + @objc optional var name: String { get } +} + +extension SBObject: MessagesTrack {} + +extension MessagesApplication { + var title: String { + guard let t = currentTrack else { return "" } + return (t.artist ?? "") + " — " + (t.name ?? "") + } +} + +@objc protocol MailApplication { + @objc optional var currentTrack: MailTrack { get } + @objc optional func playpause() + @objc optional func nextTrack() + @objc optional func previousTrack() +} + +extension SBApplication: MailApplication {} + +@objc protocol MailTrack { + @objc optional var artist: String { get } + @objc optional var name: String { get } +} + +extension SBObject: MailTrack {} + +extension MailApplication { + var title: String { + guard let t = currentTrack else { return "" } + return (t.artist ?? "") + " — " + (t.name ?? "") + } +}