diff --git a/Actions.xcodeproj/project.pbxproj b/Actions.xcodeproj/project.pbxproj index d23f301..bd6554a 100644 --- a/Actions.xcodeproj/project.pbxproj +++ b/Actions.xcodeproj/project.pbxproj @@ -224,6 +224,8 @@ E3D0656229A53EC60059A16E /* IsWebServerReachable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E31DE7F528D5E50400DE79EF /* IsWebServerReachable.swift */; }; E3D3A30C2A5BECED00F45541 /* GetDeviceDetailsExtended.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3D3A30B2A5BECED00F45541 /* GetDeviceDetailsExtended.swift */; }; E3D3A30D2A5BECED00F45541 /* GetDeviceDetailsExtended.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3D3A30B2A5BECED00F45541 /* GetDeviceDetailsExtended.swift */; }; + E3D53BF82A9385F5007A1340 /* GetModifierKeyState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3D53BF72A9385F5007A1340 /* GetModifierKeyState.swift */; }; + E3D53BF92A9385F5007A1340 /* GetModifierKeyState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3D53BF72A9385F5007A1340 /* GetModifierKeyState.swift */; }; E3DB884D28E6E3AD00FEE8D6 /* HapticFeedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3DB884B28E6E3AD00FEE8D6 /* HapticFeedback.swift */; }; E3DB885028E6EE7B00FEE8D6 /* HideShortcutsApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3DB884F28E6EE7B00FEE8D6 /* HideShortcutsApp.swift */; }; E3DB885228E6EFBB00FEE8D6 /* ScanDocuments.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3DB885128E6EFBB00FEE8D6 /* ScanDocuments.swift */; }; @@ -368,6 +370,7 @@ E3D514C528EEA6B9003F7C2E /* PlayAlertSound.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayAlertSound.swift; sourceTree = ""; }; E3D514C728EEA8F8003F7C2E /* FlashScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlashScreen.swift; sourceTree = ""; }; E3D514C928EEAAEC003F7C2E /* CalculateWithSoulver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalculateWithSoulver.swift; sourceTree = ""; }; + E3D53BF72A9385F5007A1340 /* GetModifierKeyState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetModifierKeyState.swift; sourceTree = ""; }; E3DB884B28E6E3AD00FEE8D6 /* HapticFeedback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HapticFeedback.swift; sourceTree = ""; }; E3DB884F28E6EE7B00FEE8D6 /* HideShortcutsApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HideShortcutsApp.swift; sourceTree = ""; }; E3DB885128E6EFBB00FEE8D6 /* ScanDocuments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanDocuments.swift; sourceTree = ""; }; @@ -534,6 +537,7 @@ E3EC2DD928D246BC00A88E25 /* GetFilePath.swift */, E31748A0291589B600F6319E /* GetHighResolutionTimestamp.swift */, E3B1C3C328F5D68D009BE0B7 /* GetIndexOfListItem.swift */, + E3D53BF72A9385F5007A1340 /* GetModifierKeyState.swift */, E3C33C9928D35F3D00386C59 /* GetMusicPlaylists.swift */, E31DE80728D61FE800DE79EF /* GetQueryItemsFromURL.swift */, E31DE80B28D62ACC00DE79EF /* GetQueryItemsFromURLAsDictionary.swift */, @@ -827,6 +831,7 @@ E31749482916A20C00F6319E /* GetQueryItemsFromURL.swift in Sources */, E31749162916A20C00F6319E /* SendFeedback.swift in Sources */, E365C11B2A4C683200A902D4 /* WaitMilliseconds.swift in Sources */, + E3D53BF82A9385F5007A1340 /* GetModifierKeyState.swift in Sources */, E317490A2916A20C00F6319E /* AddToList.swift in Sources */, E317491A2916A20C00F6319E /* IsConnectedToVPN.swift in Sources */, E31749402916A20C00F6319E /* CreateURL.swift in Sources */, @@ -919,6 +924,7 @@ E31749B82916A64100F6319E /* ConvertDateToUnixTime.swift in Sources */, E317497B2916A64100F6319E /* GetRandomColor.swift in Sources */, E31749B32916A64100F6319E /* RemoveDuplicateLines.swift in Sources */, + E3D53BF92A9385F5007A1340 /* GetModifierKeyState.swift in Sources */, E365C11C2A4C683200A902D4 /* WaitMilliseconds.swift in Sources */, E31749722916A64100F6319E /* RemoveEmptyLines.swift in Sources */, E317498F2916A64100F6319E /* ConvertUnixTimeToDate.swift in Sources */, diff --git a/Intents Extension/Actions/GetModifierKeyState.swift b/Intents Extension/Actions/GetModifierKeyState.swift new file mode 100644 index 0000000..79d3097 --- /dev/null +++ b/Intents Extension/Actions/GetModifierKeyState.swift @@ -0,0 +1,92 @@ +import AppIntents +import SwiftUI + +@available(iOS, unavailable) +struct GetModifierKeyState: AppIntent { + static let title: LocalizedStringResource = "Get Modifier Key State" + + static let description = IntentDescription( +""" +Returns which modifier keys are currently pressed. + +This can be useful to have alternative behavior when, for example, the user presses the Option key. + +Supported modifier keys: +- Shift +- Control +- Option +- Command +- Function +""", + categoryName: "Device", + searchKeywords: [ + "keyboard", + "shortcut", + "hotkey" + ] + ) + + static var parameterSummary: some ParameterSummary { + Summary("Get state of modifier keys") + } + + func perform() async throws -> some IntentResult & ReturnsValue { + .result(value: .init()) + } +} + +// Note: It should be possible to support iOS, but I don't have a physical keyboard to test with (doesn't work in the simulator), so I'm leaving it out for now. +// `GCKeyboard.coalesced?.keyboardInput?.button(forKeyCode: .leftShift)?.isPressed`s + +struct ModifierKeyState_AppEntity: TransientAppEntity { + static let typeDisplayRepresentation = TypeDisplayRepresentation(name: "Modifier Key State") + + @Property(title: "Shift") + var shift: Bool + + @Property(title: "Control") + var control: Bool + + @Property(title: "Option") + var option: Bool + + @Property(title: "Command") + var command: Bool + + @Property(title: "Function (Fn)") + var function: Bool + + var displayRepresentation: DisplayRepresentation { + .init( + title: "Modifier keys", + subtitle: + """ + Shift: \("\(shift)") + Control: \("\(control)") + Option: \("\(option)") + Command: \("\(command)") + Function: \("\(function)") + """ + ) + } + + init() { + #if os(macOS) + let flags = NSEvent.modifierFlags + + // I am intentionally excluding `.capsLock` because it's confusing since it works differently. It doesn't mean "pressed", it means "on". + + self.shift = flags.contains(.shift) + self.control = flags.contains(.control) + self.option = flags.contains(.option) + self.command = flags.contains(.command) + self.function = flags.contains(.function) + #else + self.shift = false + self.control = false + self.option = false + self.command = false + self.function = false + #endif + } +} diff --git a/app-store-description.txt b/app-store-description.txt index 87f0eb8..99f4be4 100644 --- a/app-store-description.txt +++ b/app-store-description.txt @@ -54,6 +54,7 @@ The app is free without ads because I love making apps. Consider leaving a nice - Get High-Resolution Timestamp - Get Index of List Item - Get Map Image of Location +- Get Modifier Key State - Get Music Playlists - Get Printers - Get Query Item Value from URL diff --git a/readme.md b/readme.md index 286fc4f..bf1b254 100644 --- a/readme.md +++ b/readme.md @@ -82,6 +82,7 @@ And for high-quality transcription, see my [Aiko](https://sindresorhus.com/aiko) - Get High-Resolution Timestamp - Get Index of List Item - Get Map Image of Location +- Get Modifier Key State (macOS-only) - Get Music Playlists (iOS-only) - Get Printers (macOS-only) - Get Query Item Value from URL