Swift: forward keystrokes to a different process - swift

I am trying to write an application that forwards the keypresses on one of its TextFields to a different process using its processID.
The code (below) that I have got works well in terms of listening to the key presses (right now, all key presses), but the trouble is coming from forwarding the events (keyDown/keyUp) to the other process.
Things I have tried:
CGEvent.postToPid: Does not work (nothing sent to the other process)
CGEvent.postToPSN: Could not get it to work as it requires the Process Serial Number as an UnsafeMutableRawPointer (not that I know how to get the PSN is in the first place)
Manually creating the CGEvent and then trying 1. and 2.
Reading documentation on the classes and their functions (they don't seem to be that popular)
Any help would be greatly appreciated.
import Cocoa
class ViewController: NSViewController {
var pid: Int32 = 12345
override func viewDidLoad() {
super.viewDidLoad()
NSEvent.addLocalMonitorForEvents(matching: .keyDown) {
(event) -> NSEvent? in
self.keyDown(with: event)
return event
}
// For some reason, keyUp is triggered twice
NSEvent.addLocalMonitorForEvents(matching: .keyUp) {
(event) -> NSEvent? in
self.keyUp(with: event)
return event
}
}
override func keyDown(with event: NSEvent) {
print("Down " + String(event.keyCode))
var cgEvent = event.cgEvent
cgEvent?.postToPid(pid)
}
override func keyUp(with event: NSEvent) {
print("Up " + String(event.keyCode))
var cgEvent = event.cgEvent
cgEvent?.postToPid(pid)
}
}

One of the comments suggested that permissions might be the cause, and it was.
My problem was resolved by going to the Capabilities section of my project and disabling App Sandboxing, which was enabled by default.
Keep in mind that App Sandboxing is a requirement for your app to be on the App Store (reference)

Related

IOBluetooth: deviceInquiryStarted and deviceInquiryComplete never called

I'm scanning for Bluetooth devices on macOS 13.1 using IOBluetoothDeviceInquiry. The scanning itself works, but only the deviceInquiryDeviceFound delegate method gets called. For the remaining methods:
deviceInquiryStarted never fires, even though the inquiry obviously starts;
deviceInquiryComplete never fires, and indeed the inquiry never seems to end (the program keeps outputting Found peer... console logs);
When updateNewDeviceNames is set to true, the delegate methods related to updating device names are nonetheless never called.
Setting inquiryLength has no effect. The documentation states:
if you have the inquiry object updating device names for you, the
whole inquiry process could be much longer than the specified length...
If you -must- have a strict inquiry length, disable name updates.
But scanning continues indefinitely even when I set updateNewDeviceNames to false.
Here's my code:
import IOBluetooth
class Inquiry: NSObject {
private lazy var inquiry: IOBluetoothDeviceInquiry = {
let inquiry: IOBluetoothDeviceInquiry = IOBluetoothDeviceInquiry()
inquiry.delegate = self
inquiry.updateNewDeviceNames = false
return inquiry
}()
lazy var foundDevices: [Any]! = self.inquiry.foundDevices()
private var continuation: CheckedContinuation<Void, Never>?
func start() async -> () {
await withCheckedContinuation { continuation in
self.continuation = continuation
self.inquiry.start()
}
}
}
extension Inquiry: IOBluetoothDeviceInquiryDelegate {
func deviceInquiryStarted(_ sender: IOBluetoothDeviceInquiry!) {
print("inquiry started")
}
func deviceInquiryComplete(_ sender: IOBluetoothDeviceInquiry!, error: IOReturn, aborted: Bool) {
print("inquiry complete")
continuation?.resume()
}
func deviceInquiryDeviceFound(_ sender: IOBluetoothDeviceInquiry!, device: IOBluetoothDevice!) {
print("device found: \(device.addressString!)")
}
}
let inquiry: Inquiry = Inquiry()
await inquiry.start()
print(inquiry.foundDevices!)
As I'm writing a command line tool, I wrap the start method in a continuation. Other than that it's pretty much identical to other examples of IOBluetoothDeviceInquiry usage I've found (e.g. this one, which has the same problems when I try running it).
I'm really at a loss as to why this isn't working, any help would be greatly appreciated!

iOS 14.5 trouble updating label text during networking loop

I have a loop that updates a remote database over the network. To keep track of the updates I want to use a counter to update the text of a label after each update. The problem is that the label text only updates after the loop completes. I have tried many combinations of DispatchQueue and DispatchGroup with no success.
The code below illustrates this problem. Thank you for your help.
import UIKit
class ViewController: UIViewController {
var counter = 0
#IBOutlet weak var countLBL: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func startClick(_ sender: Any) {
workloop()
}
func workloop() {
for _ in (0...3) {
networkTask()
counter += 1
countLBL.text = String(counter)
}
}
func networkTask() {
sleep(1)
}
}
Your basic assumptions and approach are wrong.
In all cases, if you execute a function, the UI changes you apply in that function will not be displayed to the screen until you exit the function and the app passes through the event loop.
Further, you should not be making synchronous network calls.
The idea that you'd have code
networkTask()
counter += 1
//update UI with info about completed network task
Is wrong. A function that performs a network task should be written to operate asynchrnously. (It will return immediately, go off and do the network task, and call a completion handler when the task is done. The code might look like this:
networkTask(completion: { results in
DispatchQueue.main.async {
// Code to display the results of the network operation to the UI
}
}
)
The idea of using async functions and completion handlers in Swift is widely covered by Apple, by lots and lots of development blogs, and by countless posts here on SO.

Get mouse clicks on NSTokenField

I would like to implement a NSTokenField that will show Tokens that - when hovering over the token - show a removal icon. Subsequently when I click on the icon I want the token to be removed.
After a lot of searching it seems that this is not possible with the standard NSTokenField. If someone knows how please let me know.
I have taken a look at https://github.com/octiplex/OEXTokenField and based on that code I have made a CustomTokenField implementation in Swift. So far so good I have a working CustomTokenField and when I hover over the token it shows a removal icon.
The next phase turns out to be a problem that I cannot figure out myself. How can I get a click on the token trigger a callback.?
The token class is derived from the NSTextAttachmentCell and the CustomTokenField is derived from the NStokenField:
class CustomTokenAttachmentCell: NSTextAttachmentCell {
. . .
}
class CustomTokenField: NSTokenField {
. . .
}
I have tried to approach this using two different angles:
Through the CustomTokenAttachmentCell
The NSTextAttachmentCell implements the NSTextAttachmentCellProtocol.
public protocol NSTextAttachmentCellProtocol : NSObjectProtocol {
. . .
public func wantsToTrackMouse() -> Bool
public func highlight(flag: Bool, withFrame cellFrame: NSRect, inView controlView: NSView?)
public func trackMouse(theEvent: NSEvent, inRect cellFrame: NSRect, ofView controlView: NSView?, untilMouseUp flag: Bool) -> Bool
. . .
}
This is hopeful. So I implemented these methods in CustomTokenAttachmentCell and wantsToTrackMouse() is actually being called. I have implemented that to return ‘true’.
override func trackMouse(theEvent: NSEvent, inRect cellFrame: NSRect, ofView controlView: NSView?, untilMouseUp flag: Bool) -> Bool {
Swift.print(“trackMouse”)
return true
}
override func highlight(flag: Bool, withFrame cellFrame: NSRect, inView controlView: NSView?) {
Swift.print("highlight")
}
override func wantsToTrackMouse() -> Bool {
Swift.print(“trackMouse”)
return true
}
The other two methods are never called. Is there something else that needs to be done to make that they are being called?
Through the CustomTokenField
I also tried to approach this from the CustomTokenField. It is possible to get mouse events using MouseDown(), however I could not
find a way to actually access the Tokens from the cells.
I have seen many posts here on StackOverflow and I have seen tips but none of them seems to point in the right direction.
Somehow I have come to the conclusion that you can only get mouse events in the case there is a NSControl in the hierarchy. For tokens that is not the case. An NSControl is part of the hierarchy for views hence my attempt to achieve this through the CustomTokenField but I run in a dead-end there as well.
E.g. this question Clicking token in NSTokenField is exactly the same but setting the action or target will generate a fatal error because the setAction and setTarget are stubs for the base class.
I am a beginning programmer on Coacoa, so hopefully this is just a matter of lack of knowledge.
Any advise would be appreciated.
Have you tried adding an NSButton on top of all of the whole CustomTokenAttachmentCell view? Then add an #IBOutlet action to the button for the click and pass that via delegation to the TokenField where you can control the tokens that are displayed.
I am also trying to implement this in my app, so if you are able to share any of the code it would be greatly appreciated. Thanks!

Piping NSEventMask together in 'addLocalMonitorForEventsMatchingMask'

I have the following code for listening for mouse events outside the view (subclass of NSView). This works great as it is, but I cannot figure out how to pipe the NSEventMasks together. I basically want to have the same event listener fire on NSEventMask.LeftMouseDownMask, NSEventMask.RightMouseDownMask and NSEventMask.OtherMouseDownMask.
Does anyone know how to pipe them together or if it's even possible in Swift?
Working code
localMouseEventListener = NSEvent.addLocalMonitorForEventsMatchingMask(NSEventMask.LeftMouseDownMask) { (event: NSEvent) -> NSEvent? in
// Event handling...
return event
}
This answer might help you.
In short you can now use arrays, so this seems to make the compiler happy:
localMouseEventListener = NSEvent.addLocalMonitorForEventsMatchingMask([.LeftMouseDownMask, .RightMouseDownMask, .OtherMouseDownMask]) { (event: NSEvent) -> NSEvent? in
// Event handling...
return event
}

Sync two web views

I have a need to sync two web views, so that anything that happens in one web view happens simultaneously in the other.
I have tried various ways, without success, and the more I try the more convoluted and likely bug ridden this is getting.
I feel there maybe a very simple way to do this, but can not figure it out.
One of the things that I note is not allowed is having the same NSTextField as the "takeStringURLFrom"
override func controlTextDidEndEditing(obj: NSNotification) {
webViewLeft.takeStringURLFrom(hiddenTextField)
webViewRight.takeStringURLFrom(urlField)
}
override func webView(sender: WebView!, didCommitLoadForFrame frame: WebFrame!) {
if frame == sender.mainFrame {
urlField.stringValue = sender.mainFrameURL
hiddenTextField.stringValue = sender.mainFrameURL
webViewRight.takeStringURLFrom(urlField)
webViewLeft.takeStringURLFrom(hiddenTextField)
printLn("realised just creating an infinite loop here")
}
}
I don't like this but it appears to work as needed. I think it needs some refinement. Using a hidden text field to mimic the url for the second web view and tie each web view to there respective text fields via the web view's referenced action "takeStringUrlFrom"
override func webView(sender: WebView!, didStartProvisionalLoadForFrame frame: WebFrame!) {
if frame == sender.mainFrame {
urlField.stringValue = sender.mainFrameURL
hiddenTextField.stringValue = sender.mainFrameURL
window.makeFirstResponder(nil)
window.makeFirstResponder(hiddenTextField)
window.makeFirstResponder(nil)
window.makeFirstResponder(urlField)
window.makeFirstResponder(nil)
}
}