I'm developing a macOS application in .NET 7.0 that utilizes the Accessibility Framework (ApplicationServices -> HIServices) to access assistive features. When attempting to invoke the AXUIElementCopyAttributValues()
function, the application crashes with the EXC_BAD_ACCESS (SIGSEGV)
error.
Here is my code and a link to my test repository:
public partial class ProgramTest
{
public void MainTest()
{
AccessibilityHelper.RequestAccessibilityPermission();
string bundleIdentifier = "com.apple.Safari";
NSRunningApplication app = NSRunningApplication.GetRunningApplications(bundleIdentifier)?.First();
if (app != null)
{
int processId = app.ProcessIdentifier;
IntPtr appElement = AXUIElementCreateApplication((uint)processId);
IntPtr mainWindow;
if (AXUIElementCopyAttributeValue(appElement, kAXMainWindowAttribute, out mainWindow) == 0)
{
Console.WriteLine("Main Window Handle: " + mainWindow);
}
else
{
Console.WriteLine("Could not retrieve the main window.");
}
}
else
{
Console.WriteLine("Application not found.");
}
}
const string accessibilityFramework = "/System/Library/Frameworks/ApplicationServices.framework/Versions/Current/ApplicationServices";
[DllImport(accessibilityFramework)]
static extern IntPtr AXUIElementCreateApplication(uint pid);
[DllImport(accessibilityFramework)]
static extern int AXUIElementCopyAttributeValue(IntPtr element, string attribute, out IntPtr value);
const string kAXMainWindowAttribute = "kAXTitleAttribute";
}
public static class AccessibilityHelper
{
public static void RequestAccessibilityPermission()
{
NSString key = new NSString(AXTrustedCheckOptionPrompt);
NSDictionary options = NSDictionary.FromObjectAndKey(NSNumber.FromBoolean(true), key);
IntPtr optionsPtr = options.Handle;
bool isTrusted = AXIsProcessTrustedWithOptions(optionsPtr);
if (!isTrusted)
{
Console.WriteLine("User denied accessibility permission.");
}
}
const string accessibilityFramework = "/System/Library/Frameworks/ApplicationServices.framework/Versions/Current/ApplicationServices";
[DllImport(accessibilityFramework)]
static extern bool AXIsProcessTrustedWithOptions(IntPtr options);
const string AXTrustedCheckOptionPrompt = "AXTrustedCheckOptionPrompt";
}
Here are the steps I've taken to address the issue:
I've added the required keys for Accessibility Permissions in my Info.plist file:
<key>NSAppleEventsUsageDescription</key>
<string>We need accessibility permissions to interact with other applications.</string>
<key>NSAccessibilityUsageDescription</key>
<string>We need accessibility permissions to interact with other applications.</string>
I implemented a RequestAccessibilityPermission() method to request Accessibility Permissions at runtime.
public static void RequestAccessibilityPermission()
{
NSString key = new NSString(AXTrustedCheckOptionPrompt);
NSDictionary options = NSDictionary.FromObjectAndKey(NSNumber.FromBoolean(true), key);
IntPtr optionsPtr = options.Handle;
bool isTrusted = AXIsProcessTrustedWithOptions(optionsPtr);
if (!isTrusted)
{
Console.WriteLine("User denied accessibility permission.");
}
}
I've also created a custom Cocoa Library using Xcode with the methods AXUIElementCreateApplication(_:)
and AXUIElementCopyAttributeValue(_:_:_:)
. I intend to use these methods through P/Invoke in my .NET 7.0 macOS application. However, I'm encountering the same EXC_BAD_ACCESS (SIGSEGV)
error even when using this custom Cocoa Library.
Additionally, I have disabled the Sandbox in the entitlements.
<key>com.apple.security.app-sandbox</key>
<false/>
Here is the error report
-------------------------------------
Translated Report (Full Report Below)
-------------------------------------
Process: netMacOSTest [70764]
Path: /Users/USER/*/netMacOSTest.app/Contents/MacOS/netMacOSTest
Identifier: com.companyname.netMacOSTest
Version: 1.0 (1)
Code Type: X86-64 (Translated)
Parent Process: vsdbg-ui [70763]
Responsible: VisualStudio [491]
User ID: 501
Date/Time: 2023-12-07 15:44:18.6174 +0100
OS Version: macOS 14.0 (23A344)
Report Version: 12
Anonymous UUID: E66ED467-F839-D06D-428B-00BCDEEAD568
Time Awake Since Boot: 610000 seconds
System Integrity Protection: enabled
Notes:
PC register does not match crashing frame (0x0 vs 0x10344B8E8)
Crashed Thread: 0 Dispatch queue: com.apple.main-thread
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0x0000746954584180
Exception Codes: 0x0000000000000001, 0x0000746954584180
Termination Reason: Namespace SIGNAL, Code 11 Segmentation fault: 11
Terminating Process: exc handler [70764]
VM Region Info: 0x746954584180 is not in any region. Bytes after previous region: 22442082320769 Bytes before following region: 12514185690752
REGION TYPE START - END [ VSIZE] PRT/MAX SHRMOD REGION DETAIL
MALLOC_NANO 600000000000-600020000000 [512.0M] rw-/rwx SM=PRV
---> GAP OF 0x1fcae3f00000 BYTES
MALLOC_TINY 7fcb03f00000-7fcb04000000 [ 1024K] rw-/rwx SM=PRV
Error Formulating Crash Report:
PC register does not match crashing frame (0x0 vs 0x10344B8E8)
Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0 <translation info unavailable> 0x10344b8e8 ???
1 HIServices 0x7ff81c30f1e0 AXUIElementCopyAttributeValue + 95
2 ??? 0x116ecfdbe ???
3 ??? 0x116ecafd2 ???
4 ??? 0x116ecae4a ???
5 libcoreclr.dylib 0x10d478d89 CallDescrWorkerInternal + 124
6 libcoreclr.dylib 0x10d478d89 CallDescrWorkerInternal + 124
Does anyone have experience with this issue and can provide advice on how to resolve it? Are there additional steps I should take to identify and address the root cause of the error?
It looks like your method definition isn't entirely correct.
I just ran some test with the below import definitions which were successful. (note - you'll need to allow your code to execute unsafe code for these due to the pointer useage)
[DllImport("/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices")]
static extern IntPtr AXUIElementCreateSystemWide();
[DllImport("/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices")]
unsafe static extern int AXUIElementCopyAttributeNames(IntPtr element, IntPtr* names);
[DllImport("/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices")]
unsafe static extern int AXUIElementCopyAttributeValue(IntPtr element, IntPtr attribute, IntPtr* value);
These look to be working for me, see examples below. Note that the attribute names you should use don't match with the exact const definition in the header file but with the CopyAttributeNames you're able to validate what possible keys there are.
After changing the method definition I started getting correct error codes or results back instead of getting crashes on memory faults.
// get accessibility element
var element = AXUIElementCreateSystemWide();
// fetch the available attributes
IntPtr ptrAttributeNames;
var attributeNamesError = AXUIElementCopyAttributeNames(element, &ptrAttributeNames);
// convert response to a readable array
var attributeNames = CFArray.ArrayFromHandleFunc(ptrAttributeNames, CFString.FromHandle);
// fetch the value for an attribute
IntPtr ptrAttributeValue;
var attributeValueError = AXUIElementCopyAttributeValue(element, CFString.CreateNative("AXSelectedText"), &ptrAttributeValue);
var attributeValue = CFString.FromHandle(ptrAttributeValue);