Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Sources/SendKeysLib/Configuration/SendConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ struct SendConfig: Codable {
var remap: [String: String]?
var targeted: Bool?
var terminateCommand: String?
var window: String?

init(
activate: Bool? = nil, animationInterval: Double? = nil, delay: Double? = nil, initialDelay: Double? = nil,
keyboardLayout: KeyMappings.Layouts? = nil, remap: [String: String]? = nil, targeted: Bool? = nil,
terminateCommand: String? = nil
terminateCommand: String? = nil, window: String? = nil
) {
self.activate = activate
self.animationInterval = animationInterval
Expand All @@ -21,6 +22,7 @@ struct SendConfig: Codable {
self.remap = remap
self.targeted = targeted
self.terminateCommand = terminateCommand
self.window = window
}

func merge(with other: SendConfig?) -> SendConfig {
Expand Down
28 changes: 28 additions & 0 deletions Sources/SendKeysLib/Sender.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ public struct Sender: ParsableCommand {
help: "Process id of a running application to send keys to.")
var processId: Int?

@Option(
name: NameSpecification([.customLong("window")]), help: "Name of the window to send keystrokes to.")
var targetWindow: String?

@Flag(
name: .long, inversion: FlagInversion.prefixedNo,
help: "Activate the specified app or process before sending commands.")
Expand Down Expand Up @@ -106,6 +110,30 @@ public struct Sender: ParsableCommand {
if app == nil {
throw RuntimeError("Application could not be found.")
}
if targetWindow != nil {
let pid = app!.processIdentifier
let appRef = AXUIElementCreateApplication(pid)
var windowList: CFTypeRef?
let result = AXUIElementCopyAttributeValue(appRef, kAXWindowsAttribute as CFString, &windowList)
guard result == .success, let windows = windowList as? [AXUIElement] else {
throw RuntimeError("Failed to retrieve windows.")
}
var foundWindow: Bool = false
for window in windows {
var title: CFTypeRef?
AXUIElementCopyAttributeValue(window, kAXTitleAttribute as CFString, &title)

if let titleString = title as? String, titleString.starts(with: targetWindow ?? "") {
AXUIElementSetAttributeValue(window, kAXMainAttribute as CFString, kCFBooleanTrue)
AXUIElementSetAttributeValue(window, kAXFocusedAttribute as CFString, kCFBooleanTrue)
foundWindow = true
break
}
}
if !foundWindow {
throw RuntimeError("Target window could not be found.")
}
}
keyPresser = KeyPresser(app: app)
} else {
keyPresser = KeyPresser(app: nil)
Expand Down