diff --git a/dcbattwebhook-swift.xcodeproj/project.pbxproj b/dcbattwebhook-swift.xcodeproj/project.pbxproj index 9a33455..9df008d 100644 --- a/dcbattwebhook-swift.xcodeproj/project.pbxproj +++ b/dcbattwebhook-swift.xcodeproj/project.pbxproj @@ -594,7 +594,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = "dcbattwebhook-swift/dcbattwebhook_swift.entitlements"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; - CURRENT_PROJECT_VERSION = 48; + CURRENT_PROJECT_VERSION = 50; DEVELOPMENT_ASSET_PATHS = "\"dcbattwebhook-swift/Preview Content\""; ENABLE_HARDENED_RUNTIME = NO; "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES; @@ -641,7 +641,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = "dcbattwebhook-swift/dcbattwebhook_swift.entitlements"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; - CURRENT_PROJECT_VERSION = 48; + CURRENT_PROJECT_VERSION = 50; DEVELOPMENT_ASSET_PATHS = "\"dcbattwebhook-swift/Preview Content\""; ENABLE_HARDENED_RUNTIME = NO; "ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES; diff --git a/dcbattwebhook-swift.xcodeproj/project.xcworkspace/xcuserdata/Stella.xcuserdatad/UserInterfaceState.xcuserstate b/dcbattwebhook-swift.xcodeproj/project.xcworkspace/xcuserdata/Stella.xcuserdatad/UserInterfaceState.xcuserstate index fa77891..5829a4a 100644 Binary files a/dcbattwebhook-swift.xcodeproj/project.xcworkspace/xcuserdata/Stella.xcuserdatad/UserInterfaceState.xcuserstate and b/dcbattwebhook-swift.xcodeproj/project.xcworkspace/xcuserdata/Stella.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/dcbattwebhook-swift/AutomationsView.swift b/dcbattwebhook-swift/AutomationsView.swift new file mode 100644 index 0000000..9396127 --- /dev/null +++ b/dcbattwebhook-swift/AutomationsView.swift @@ -0,0 +1,115 @@ +// +// AutomationsView.swift +// dcbattwebhook-swift +// +// Created by Stella Luna on 11/22/23. +// + +import SwiftUI + +struct AutomationsView: View { + @State private var macSendOnPluggedIn = false + @State private var macSendOnUnplugged = false + @State private var macSendOnHitFullCharge = false + + private var macSendSettings: [Bool] {[ + macSendOnPluggedIn, + macSendOnUnplugged, + macSendOnHitFullCharge + ]} + + let defaults = UserDefaults.standard + + var body: some View { + VStack { + #if os(macOS) + Form { + Section(header: Text("Events"), footer: Text("You can choose to automatically send battery info when one or more of these things happen.\nConfigure Display name, pronoun, etc. in Settings.")) { + Text("Send battery info automatically when...") + Toggle(isOn: $macSendOnPluggedIn) { + Text("Device is plugged in") + } + Toggle(isOn: $macSendOnUnplugged) { + Text("Device is unplugged") + } + Toggle(isOn: $macSendOnHitFullCharge) { + Text("Device finishes charging") + }.disabled(true) + } + }.formStyle(.grouped) + .onAppear() { + if defaults.object(forKey: "MacSendOnPluggedIn") == nil { + macSendOnPluggedIn = false + } else { macSendOnPluggedIn = defaults.bool(forKey: "MacSendOnPluggedIn") } + if defaults.object(forKey: "MacSendOnUnplugged") == nil { + macSendOnUnplugged = false + } else { macSendOnUnplugged = defaults.bool(forKey: "MacSendOnUnplugged") } + if defaults.object(forKey: "MacSendOnHitFullCharge") == nil { + macSendOnHitFullCharge = false + } else { macSendOnHitFullCharge = defaults.bool(forKey: "MacSendOnHitFullCharge") } + } + .onDisappear() { + defaults.set(macSendOnPluggedIn, forKey: "MacSendOnPluggedIn") + defaults.set(macSendOnUnplugged, forKey: "MacSendOnUnplugged") + defaults.set(macSendOnHitFullCharge, forKey: "MacSendOnHitFullCharge") + }.onChange(of: macSendSettings) {_ in + defaults.set(macSendOnPluggedIn, forKey: "MacSendOnPluggedIn") + defaults.set(macSendOnUnplugged, forKey: "MacSendOnUnplugged") + defaults.set(macSendOnHitFullCharge, forKey: "MacSendOnHitFullCharge") + } + #elseif os(watchOS) + AutomationsViewNotEligibleView() + #elseif os(visionOS) + AutomationsViewNotEligibleView() + #elseif os(tvOS) + AutomationsViewNotEligibleView() + #elseif os(iOS) + AutomationsViewRequiresShortcutsView() + #endif + } + .navigationTitle("Automations") + .onAppear() { + } + + } +} + +struct AutomationsViewNotEligibleView: View { + + var body: some View { + VStack { + Form { + VStack{ + Image(systemName: "exclamationmark.triangle").foregroundStyle(.red).font(.system(size: 30)).padding() + Text("This device does not support automations.") + }.multilineTextAlignment(.center) + } + } + .onAppear() { + } + + } +} + +struct AutomationsViewRequiresShortcutsView: View { + + var body: some View { + VStack { + Form { + VStack{ + Image(systemName: "info.circle").foregroundStyle(.yellow).font(.system(size: 30)).padding() + Text("This device does not support in-app automations, but it can use Shortcuts Automations.\n\nPlease see the Help for more details about Shortcuts Automations.") + } + } + } + .onAppear() { + } + + } +} + +struct AutomationsView_Previews: PreviewProvider { + static var previews: some View { + AutomationsView() + } +} diff --git a/dcbattwebhook-swift/ContentView.swift b/dcbattwebhook-swift/ContentView.swift index 4724342..af66f71 100644 --- a/dcbattwebhook-swift/ContentView.swift +++ b/dcbattwebhook-swift/ContentView.swift @@ -29,6 +29,10 @@ struct ContentView: View { .tabItem { Label("Settings", systemImage: "gearshape") } + AutomationsView() + .tabItem { + Label("Automations", systemImage: "gearshape.2") + } HelpView() .tabItem { Label("Help", systemImage: "questionmark.circle") @@ -47,6 +51,9 @@ struct ContentView: View { NavigationLink(destination: SettingsView(), isActive: $isShowingSettings, label: { Label("Settings", systemImage: "gearshape") }) + NavigationLink{AutomationsView()} label: { + Label("Automations", systemImage: "gearshape.2") + } NavigationLink(destination: HelpView(), isActive: $isShowingHelp, label: { Label("Help", systemImage: "questionmark.circle") }) diff --git a/dcbattwebhook-swift/DefaultsUsed.md b/dcbattwebhook-swift/DefaultsUsed.md index 28bc2a2..2b1fe43 100644 --- a/dcbattwebhook-swift/DefaultsUsed.md +++ b/dcbattwebhook-swift/DefaultsUsed.md @@ -20,6 +20,14 @@ - SelectedServiceType - String, stores the user's preference on which service (discord, telegram, etc) they want to use. Must be selected from a predefined list. See serviceTypes array in WebhookSettingsView.swift +### Automations +- MacSendOnPluggedIn + - Bool, stores whether the user has chosen to send battery info when plugging in their Mac (false by default) +- MacSendOnUnplugged + - Bool, stores whether the user has chosen to send battery info when unolugging their Mac (false by default) +- MacSendOnHitFullCharge + - Bool, stores whether the user has chosen to send battery info when their Mac hits 100% charge (false by default as not yet implemented) + ### Service Specific Settings The way these are constructed is as follows: ```swift diff --git a/dcbattwebhook-swift/MacSupport/MacAppDelegate.swift b/dcbattwebhook-swift/MacSupport/MacAppDelegate.swift index 4b614a6..dfa14bf 100644 --- a/dcbattwebhook-swift/MacSupport/MacAppDelegate.swift +++ b/dcbattwebhook-swift/MacSupport/MacAppDelegate.swift @@ -15,7 +15,10 @@ class AppDelegate: NSObject, NSApplicationDelegate { let source = IOPSNotificationCreateRunLoopSource( { _ in - print(getPowerSource()) + if macAutomationsSavedPowerSource != GetMacPowerSource() { + HandleMacPowerStateChange() + macAutomationsSavedPowerSource = GetMacPowerSource() + } },nil).takeRetainedValue() diff --git a/dcbattwebhook-swift/MacSupport/MacIOKit.swift b/dcbattwebhook-swift/MacSupport/MacIOKit.swift index ae1fc14..c5039b8 100644 --- a/dcbattwebhook-swift/MacSupport/MacIOKit.swift +++ b/dcbattwebhook-swift/MacSupport/MacIOKit.swift @@ -10,6 +10,51 @@ import Foundation import IOKit import IOKit.ps +func HandleMacPowerStateChange() { + switch GetMacPowerSource() { + case "Battery Power": + if (GetMacAutomationSetting(automationSetting: "MacSendOnUnplugged")) { + let isSettingsValid = ValidateSettings() + + if (isSettingsValid.err == true) { + // config error + } + else { + let ResultsVar = sendInfo(isCurrentlyCharging: false, didGetPluggedIn: false, didGetUnplugged: true, didHitFullCharge: false) + SaveAutomationCurrentDate() + if (ResultsVar.err) { + // network error + } + else { + // success + } + + } + } + case "AC Power": + if (GetMacAutomationSetting(automationSetting: "MacSendOnPluggedIn")) { + let isSettingsValid = ValidateSettings() + + if (isSettingsValid.err == true) { + // config error + } + else { + let ResultsVar = sendInfo(isCurrentlyCharging: false, didGetPluggedIn: true, didGetUnplugged: false, didHitFullCharge: false) + SaveAutomationCurrentDate() + if (ResultsVar.err) { + // network error + } + else { + // success + } + + } + } + default: + print("Unknown") + } +} + /** Returns the current power source as string. @@ -18,7 +63,7 @@ import IOKit.ps - Warning: Only available on macOS */ -func getPowerSource() -> String { +func GetMacPowerSource() -> String { guard let snapshot = IOPSCopyPowerSourcesInfo()?.takeRetainedValue() else { return "Unknown" } guard let sources: NSArray = IOPSCopyPowerSourcesList(snapshot)?.takeRetainedValue() else { return "Unknown" } for ps in sources { @@ -31,4 +76,26 @@ func getPowerSource() -> String { return "Unknown" } + +/** + Returns whether or not the Mac is charging + + - Returns: + it's a bool + + - Warning: Only available on macOS + */ +func GetMacIsCharging() -> Bool { + guard let snapshot = IOPSCopyPowerSourcesInfo()?.takeRetainedValue() else { return false } + guard let sources: NSArray = IOPSCopyPowerSourcesList(snapshot)?.takeRetainedValue() else { return false } + for ps in sources { + guard let info: NSDictionary = IOPSGetPowerSourceDescription(snapshot, ps as CFTypeRef)?.takeUnretainedValue() else { return false } + + if let isCharging = info[kIOPSIsChargingKey] as? Bool { + return isCharging + } + } + + return false +} #endif diff --git a/dcbattwebhook-swift/MacSupport/MacSettingsUtils.swift b/dcbattwebhook-swift/MacSupport/MacSettingsUtils.swift new file mode 100644 index 0000000..663a874 --- /dev/null +++ b/dcbattwebhook-swift/MacSupport/MacSettingsUtils.swift @@ -0,0 +1,58 @@ +// +// MacSettingsUtils.swift +// Battery Webhook +// +// Created by Stella Luna on 11/21/23. +// + +#if os(macOS) +import Foundation + +/** + Gets a Mac Automation Setting and returns it + + - Parameters: + - automationSetting: a valid setting to retrieve as described below + + A valid setting is one of the following strings: + ``` + MacSendOnPluggedIn + MacSendOnUnplugged + MacSendOnHitFullCharge + ``` + + - Returns: + Value of the Mac Automation Setting as Bool + + - Warning: Only available on macOS, and returns `false` if an invalid setting is specified + */ +func GetMacAutomationSetting(automationSetting: String) -> Bool { + let defaults = UserDefaults.standard + + var macSendOnPluggedIn = false + var macSendOnUnplugged = false + var macSendOnHitFullCharge = false + + if defaults.object(forKey: "MacSendOnPluggedIn") == nil { + macSendOnPluggedIn = false + } else { macSendOnPluggedIn = defaults.bool(forKey: "MacSendOnPluggedIn") } + if defaults.object(forKey: "MacSendOnUnplugged") == nil { + macSendOnUnplugged = false + } else { macSendOnUnplugged = defaults.bool(forKey: "MacSendOnUnplugged") } + if defaults.object(forKey: "MacSendOnHitFullCharge") == nil { + macSendOnHitFullCharge = false + } else { macSendOnHitFullCharge = defaults.bool(forKey: "MacSendOnHitFullCharge") } + + switch automationSetting { + case "MacSendOnPluggedIn": + return macSendOnPluggedIn + case "MacSendOnUnplugged": + return macSendOnUnplugged + case "MacSendOnHitFullCharge": + return macSendOnHitFullCharge + default: + return false + } +} + +#endif diff --git a/dcbattwebhook-swift/MenuBarExtraView.swift b/dcbattwebhook-swift/MacSupport/MenuBarExtraView.swift similarity index 100% rename from dcbattwebhook-swift/MenuBarExtraView.swift rename to dcbattwebhook-swift/MacSupport/MenuBarExtraView.swift diff --git a/dcbattwebhook-swift/dcbattwebhook_swiftApp.swift b/dcbattwebhook-swift/dcbattwebhook_swiftApp.swift index f9f9d17..37a7a17 100644 --- a/dcbattwebhook-swift/dcbattwebhook_swiftApp.swift +++ b/dcbattwebhook-swift/dcbattwebhook_swiftApp.swift @@ -15,10 +15,11 @@ public let prodName = "Battery Webhook" /// Base version of the app, use `version` if you want the running OS as well let versionNum = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "unknown" let versionBuild = Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "unknown" -let versionType = "dev" +let versionType = "RC" public let versionBase = "\(versionNum)(\(versionBuild)) \(versionType)" #if os(macOS) +public var macAutomationsSavedPowerSource = GetMacPowerSource() public let version = "\(versionBase) on macOS" #elseif os(watchOS) public let version = "\(versionBase) on watchOS"