App with sandbox TCC deny IOHIDDeviceOpen

Hi, I am developing macOS app with sandbox. I am implementing HID Device functionality in macOS app which shows error.

0x1000638ee: TCC deny IOHIDDeviceOpen

Could not open HID manager

In entitlements : com.apple.security.device.usb is set to true

Answered by ForumsContributor in

What are you trying to do with the HID device?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Hi, I am counting number of keys pressed by the user. like calculating typing speed of the user.

I am counting number of keys pressed by the user.

You don’t need HID for that. Rather, check out CGEventTap. A good place to start is here.

This API requires the System Settings > Privacy & Security > Input Monitoring privilege. CG has some little known but super useful routines to help with that: CGPreflightListenEventAccess and CGRequestListenEventAccess. And because this requires explicit user approval, it’s supported even in sandboxed apps.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Following code i tried to implement in my MacOS App, but it can't able to create CGEvent.tapCreate

import Foundation
import CoreGraphics


func myCGEventCallback(proxy : CGEventTapProxy, type : CGEventType, event : CGEvent, refcon : UnsafeMutableRawPointer?) -> Unmanaged<CGEvent>? {
  if type == .keyDown || type == .keyUp || type == .flagsChanged {
    
    let keyCode = event.getIntegerValueField(.keyboardEventKeycode)
    print(keyCode)
  }
  return Unmanaged.passRetained(event)
}

func KeyCounterEvent() {
  let eventMask = (1 << CGEventType.keyDown.rawValue) | (1 << CGEventType.keyUp.rawValue) | (1 << CGEventType.flagsChanged.rawValue)
  guard let eventTap = CGEvent.tapCreate(tap: .cgSessionEventTap, place: .headInsertEventTap, options: .defaultTap, eventsOfInterest: CGEventMask(eventMask), callback: myCGEventCallback, userInfo: nil) else {
    debugPrint("Failed to create event tap")
    exit(1)
  }
  let runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0)
  CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .commonModes)
  CGEvent.tapEnable(tap: eventTap, enable: true)
  CFRunLoopRun()
}

Note: tried this code in separate console app works fine, but problem occurs when creating it in MacOS App with SandBox

it can't able to create CGEvent.tapCreate(…)

So it returns nil, resulting in your code running the else case in the guard statement?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

what will be the suggested solution for macOS App with SandBoxed

what will be the suggested solution for macOS App with SandBoxed

I’m not sure how to interpret this. I asked:

So it returns nil, resulting in your code running the else case in the guard statement?

and I was expecting an answer to that.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Yes code running the else case in the guard statement in my masOS App.

Note: tried this code in separate console app works fine, but problem occurs when creating it in MacOS App with SandBox

Accepted Answer

problem occurs when creating it in MacOS App with SandBox

This is working for me. I have a test project I use for exploring various TCC options. It can build either with or without the sandbox. I ran the sandboxed version on my Mac (macOS 13.2.1) and started the event tap test. I saw this alert:

Once I approved this in System Settings my event tap started to work.

The specific code I’m using is pasted in below.

If you can’t get things working based on this, I recommend that you open a DTS tech support incident and I can look at your issue in more depth.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"


os_log(.debug, log: self.log, "will create tap")
let info = Unmanaged.passRetained(self).toOpaque()
let mask = CGEventMask(1 << CGEventType.keyDown.rawValue)
guard let port = CGEvent.tapCreate(
    tap: .cgSessionEventTap,
    place: .headInsertEventTap,
    options: .listenOnly,
    eventsOfInterest: mask,
    callback: { (proxy, type, event, info) -> Unmanaged<CGEvent>? in
        let obj = Unmanaged<CGEventTapAction>.fromOpaque(info!).takeUnretainedValue()
        obj.didReceiveEvent(event)
        // We don’t replace the event, so the new event is the same as
        // the old event, so we return it unretained.
        return Unmanaged.passUnretained(event)
    },
    userInfo: info
) else {
    os_log(.debug, log: self.log, "did not create tap")
    // We retained `self` above, but the event tap didn’t get created so
    // we need to clean up.
    Unmanaged<CGEventTapAction>.fromOpaque(info).release()
    setStatus("Failed to create event tap.")
    return
}
let rls = CFMachPortCreateRunLoopSource(nil, port, 0)!
CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, .defaultMode)
self.runState = RunState(port: port, setStatus: setStatus)
os_log(.debug, log: self.log, "did create tap")

Quinn’s sample works perfectly for capturing key up/down events. Thanks for that!

I need to get the characters associated with key-up or key-down events. Using the NSEvent’s characters property, I wrote this in the didReceiveEvent callback:

let nsEvent = NSEvent(cgEvent: event)
print("Characters: \(event.characters)")

However, this crashes in a SwiftUI or AppKit app with a failing assertion in isIGetInputSourceRef while processing the raw key code.

Running event.characters on the main thread seems to be a workaround. There’s no documentation suggesting this, and the CGEvent comes in on a background thread through the tap. Other CGEvent and NSEvent properties work fine from any thread.

Is it expected that event.characters must be called from the main thread? Is it safe to dispatch it to the main queue, or could it fail if the main queue is handling other events? What’s the recommended approach?

Additionally, in my command-line executable without a UI, accessing characters from a background thread works fine. Why does this fail in a UI app? Is accessing it on the main thread a requirement?

It would be very valuable if someone could shed some light on this.

App with sandbox TCC deny IOHIDDeviceOpen
 
 
Q