55
66import AppKit
77import ApplicationServices
8+ import Carbon
89import Foundation
910
1011// MARK: - Data Types
@@ -24,10 +25,14 @@ public struct ScreenContext: Sendable, Codable {
2425 /// The accessibility tree of the focused window (nil if unavailable).
2526 public let focusedWindowAXTree : AXNode ?
2627
27- public init ( frontmostApp: AppInfo ? , visibleWindows: [ WindowInfo ] , focusedWindowAXTree: AXNode ? ) {
28+ /// The current keyboard input source / IME mode (nil if unavailable).
29+ public let keyboardInputSource : InputSourceInfo ?
30+
31+ public init ( frontmostApp: AppInfo ? , visibleWindows: [ WindowInfo ] , focusedWindowAXTree: AXNode ? , keyboardInputSource: InputSourceInfo ? = nil ) {
2832 self . frontmostApp = frontmostApp
2933 self . visibleWindows = visibleWindows
3034 self . focusedWindowAXTree = focusedWindowAXTree
35+ self . keyboardInputSource = keyboardInputSource
3136 }
3237}
3338
@@ -44,6 +49,20 @@ public struct AppInfo: Sendable, Codable {
4449 }
4550}
4651
52+ /// Information about the current keyboard input source (IME state).
53+ public struct InputSourceInfo : Sendable , Codable {
54+ /// The input source identifier (e.g., "com.apple.inputmethod.Japanese.RomajiTyping").
55+ public let id : String
56+
57+ /// The localized display name (e.g., "日本語ローマ字", "U.S.").
58+ public let localizedName : String
59+
60+ public init ( id: String , localizedName: String ) {
61+ self . id = id
62+ self . localizedName = localizedName
63+ }
64+ }
65+
4766/// Information about a visible window on screen.
4867public struct WindowInfo : Sendable , Codable {
4968 public let title : String ?
@@ -157,6 +176,7 @@ public struct ScreenContextProvider: Sendable {
157176 public static func gather( options: Options = Options ( ) ) -> ScreenContext {
158177 let frontmostApp = gatherFrontmostApp ( )
159178 let visibleWindows = gatherVisibleWindows ( )
179+ let inputSource = gatherKeyboardInputSource ( )
160180
161181 var axTree : AXNode ?
162182 if options. includeAXTree, let app = frontmostApp {
@@ -167,7 +187,8 @@ public struct ScreenContextProvider: Sendable {
167187 return ScreenContext (
168188 frontmostApp: frontmostApp,
169189 visibleWindows: visibleWindows,
170- focusedWindowAXTree: axTree
190+ focusedWindowAXTree: axTree,
191+ keyboardInputSource: inputSource
171192 )
172193 }
173194}
@@ -185,6 +206,26 @@ extension ScreenContextProvider {
185206 }
186207}
187208
209+ // MARK: - Gathering: Keyboard Input Source
210+
211+ extension ScreenContextProvider {
212+ private static func gatherKeyboardInputSource( ) -> InputSourceInfo ? {
213+ guard let source = TISCopyCurrentKeyboardInputSource ( ) ? . takeRetainedValue ( ) else {
214+ return nil
215+ }
216+
217+ let idPtr = TISGetInputSourceProperty ( source, kTISPropertyInputSourceID)
218+ let namePtr = TISGetInputSourceProperty ( source, kTISPropertyLocalizedName)
219+
220+ guard let idPtr, let namePtr else { return nil }
221+
222+ let id = Unmanaged < CFString > . fromOpaque ( idPtr) . takeUnretainedValue ( ) as String
223+ let localizedName = Unmanaged < CFString > . fromOpaque ( namePtr) . takeUnretainedValue ( ) as String
224+
225+ return InputSourceInfo ( id: id, localizedName: localizedName)
226+ }
227+ }
228+
188229// MARK: - Gathering: Visible Windows
189230
190231extension ScreenContextProvider {
@@ -378,6 +419,11 @@ extension ScreenContext {
378419 lines. append ( " Frontmost app: \( app. name) \( bundle) " )
379420 }
380421
422+ // Keyboard input source
423+ if let inputSource = keyboardInputSource {
424+ lines. append ( " Keyboard input source: \( inputSource. localizedName) ( \( inputSource. id) ) " )
425+ }
426+
381427 // Visible windows
382428 if !visibleWindows. isEmpty {
383429 lines. append ( " Visible windows: " )
0 commit comments