Created
November 15, 2024 20:52
-
-
Save anaisbetts/320e357f75a10fd3c94c0a81d456a6fb to your computer and use it in GitHub Desktop.
Accessibility APIs mapped for Xamarin .NET 8.0 macOS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Drawing; | |
using System.Linq; | |
using System.Runtime.InteropServices; | |
using CoreFoundation; | |
using CoreGraphics; | |
using Foundation; | |
using ObjCRuntime; | |
namespace HIServices | |
public static class AXAttributes | |
{ | |
// UI Element attributes | |
public static readonly NSString Size = new NSString("AXSize"); | |
public static readonly NSString Position = new NSString("AXPosition"); | |
public static readonly NSString Title = new NSString("AXTitle"); | |
public static readonly NSString Description = new NSString("AXDescription"); | |
public static readonly NSString Help = new NSString("AXHelp"); | |
// Window-specific attributes | |
public static readonly NSString Main = new NSString("AXMain"); | |
public static readonly NSString Minimized = new NSString("AXMinimized"); | |
public static readonly NSString Modal = new NSString("AXModal"); | |
public static readonly NSString Focused = new NSString("AXFocused"); | |
public static readonly NSString Parent = new NSString("AXParent"); | |
public static readonly NSString Children = new NSString("AXChildren"); | |
public static readonly NSString Window = new NSString("AXWindow"); | |
// Role and state | |
public static readonly NSString Role = new NSString("AXRole"); | |
public static readonly NSString RoleDescription = new NSString("AXRoleDescription"); | |
public static readonly NSString SubRole = new NSString("AXSubrole"); | |
public static readonly NSString Enabled = new NSString("AXEnabled"); | |
// Button attributes | |
public static readonly NSString CloseButton = new NSString("AXCloseButton"); | |
public static readonly NSString ZoomButton = new NSString("AXZoomButton"); | |
public static readonly NSString MinimizeButton = new NSString("AXMinimizeButton"); | |
public static readonly NSString ToolbarButton = new NSString("AXToolbarButton"); | |
// Focus notifications | |
public static readonly NSString FocusedWindowChanged = new NSString("AXFocusedWindowChanged"); | |
public static readonly NSString MainWindowChanged = new NSString("AXMainWindowChanged"); | |
public static readonly NSString FocusedUIElementChanged = new NSString("AXFocusedUIElementChanged"); | |
// Window state notifications | |
public static readonly NSString WindowCreated = new NSString("AXWindowCreated"); | |
public static readonly NSString WindowMoved = new NSString("AXWindowMoved"); | |
public static readonly NSString WindowResized = new NSString("AXWindowResized"); | |
public static readonly NSString WindowMiniaturized = new NSString("AXWindowMiniaturized"); | |
public static readonly NSString WindowDeminiaturized = new NSString("AXWindowDeminiaturized"); | |
// Element state notifications | |
public static readonly NSString ValueChanged = new NSString("AXValueChanged"); | |
public static readonly NSString UIElementDestroyed = new NSString("AXUIElementDestroyed"); | |
public static readonly NSString ElementCreated = new NSString("AXCreated"); | |
// Application notifications | |
public static readonly NSString ApplicationActivated = new NSString("AXApplicationActivated"); | |
public static readonly NSString ApplicationDeactivated = new NSString("AXApplicationDeactivated"); | |
public static readonly NSString ApplicationHidden = new NSString("AXApplicationHidden"); | |
public static readonly NSString ApplicationShown = new NSString("AXApplicationShown"); | |
// Value attributes | |
public static readonly NSString Value = new NSString("AXValue"); | |
public static readonly NSString MinValue = new NSString("AXMinValue"); | |
public static readonly NSString MaxValue = new NSString("AXMaxValue"); | |
public static readonly NSString ValueIncrement = new NSString("AXValueIncrement"); | |
public static readonly NSString ValueDescription = new NSString("AXValueDescription"); | |
// Additional UI attributes | |
public static readonly NSString Selected = new NSString("AXSelected"); | |
public static readonly NSString Expanded = new NSString("AXExpanded"); | |
public static readonly NSString Hidden = new NSString("AXHidden"); | |
public static readonly NSString TopLevelUIElement = new NSString("AXTopLevelUIElement"); | |
public static readonly NSString IsApplicationRunning = new NSString("AXIsApplicationRunning"); | |
} | |
// Additional enums for value types from the AXValue.h file | |
public enum AXValueType | |
{ | |
CGPoint = 1, | |
CGSize = 2, | |
CGRect = 3, | |
CFRange = 4, | |
AXError = 5, | |
IllegalType = 0 | |
} | |
public static class HIServices | |
{ | |
[DllImport("/System/Library/Frameworks/ApplicationServices.framework/Frameworks/HIServices.framework/HIServices")] | |
static extern bool AXIsProcessTrustedWithOptions(IntPtr options); | |
public static bool IsProcessTrustedWithOptions(bool promptUser = false) | |
{ | |
var dict = NSDictionary.FromObjectAndKey( | |
NSNumber.FromBoolean(promptUser), | |
new NSString("AXTrustedCheckOptionPrompt") | |
); | |
// Convert NSDictionary to CFDictionary handle | |
return AXIsProcessTrustedWithOptions(dict.Handle); | |
} | |
} | |
public class AXUIElement : NativeObject | |
{ | |
// Constructor for creating from native handle | |
[Preserve(Conditional = true)] | |
internal AXUIElement(NativeHandle handle, bool owns) : base(handle, owns) | |
{ | |
} | |
// Factory methods | |
[DllImport("/System/Library/Frameworks/ApplicationServices.framework/Frameworks/HIServices.framework/HIServices")] | |
static extern NativeHandle AXUIElementCreateApplication(int pid); | |
public static AXUIElement CreateApplication(int pid) | |
{ | |
return new AXUIElement(AXUIElementCreateApplication(pid), true); | |
} | |
[DllImport("/System/Library/Frameworks/ApplicationServices.framework/Frameworks/HIServices.framework/HIServices")] | |
static extern NativeHandle AXUIElementCreateSystemWide(); | |
public static AXUIElement CreateSystemWide() | |
{ | |
return new AXUIElement(AXUIElementCreateSystemWide(), true); | |
} | |
// Instance methods wrapping native functions | |
[DllImport("/System/Library/Frameworks/ApplicationServices.framework/Frameworks/HIServices.framework/HIServices")] | |
static extern int AXUIElementGetPid(NativeHandle element, out int pid); | |
public int GetPid() | |
{ | |
int pid; | |
var result = AXUIElementGetPid(Handle, out pid); | |
if (result != 0) | |
throw new Exception($"AXUIElementGetPid failed with code {result}"); | |
return pid; | |
} | |
[DllImport("/System/Library/Frameworks/ApplicationServices.framework/Frameworks/HIServices.framework/HIServices")] | |
static extern int AXUIElementCopyAttributeNames(IntPtr element, out IntPtr names); | |
public string[] GetAttributeNames() | |
{ | |
IntPtr names; | |
var result = AXUIElementCopyAttributeNames(Handle.Handle, out names); | |
if (result != 0) { | |
throw new Exception($"AXUIElementCopyAttributeNames failed with code {result}"); | |
} | |
return CFArray.StringArrayFromHandle(new NativeHandle(names)) ?? Array.Empty<string>(); | |
} | |
[DllImport("/System/Library/Frameworks/ApplicationServices.framework/Frameworks/HIServices.framework/HIServices")] | |
static extern int AXUIElementCopyAttributeValue(IntPtr element, IntPtr attribute, out IntPtr value); | |
public NativeObject? GetAttributeValue(string attributeName) | |
{ | |
var str = CFString.CreateNative(attributeName); | |
try { | |
IntPtr valueHandle; | |
var result = AXUIElementCopyAttributeValue(Handle, str, out valueHandle); | |
if (result != 0) | |
throw new Exception($"AXUIElementCopyAttributeValue failed with code {result}"); | |
// Note: Proper type determination and wrapping would be needed here | |
// This is a simplified version that returns the base NativeObject | |
return Runtime.GetINativeObject<NativeObject>(valueHandle, true); | |
} finally { | |
CFString.ReleaseNative(str); | |
} | |
} | |
[DllImport("/System/Library/Frameworks/ApplicationServices.framework/Frameworks/HIServices.framework/HIServices")] | |
static extern int AXUIElementSetAttributeValue(IntPtr element, IntPtr attribute, IntPtr value); | |
public void SetAttributeValue(string attributeName, INativeObject value) | |
{ | |
var str = CFString.CreateNative(attributeName); | |
try { | |
var result = AXUIElementSetAttributeValue(Handle, str, value.GetHandle()); | |
if (result != 0) | |
throw new Exception($"AXUIElementSetAttributeValue failed with code {result}"); | |
} finally { | |
CFString.ReleaseNative(str); | |
} | |
} | |
[DllImport("/System/Library/Frameworks/ApplicationServices.framework/Frameworks/HIServices.framework/HIServices")] | |
static extern int AXUIElementIsAttributeSettable(IntPtr element, IntPtr attribute, out byte settable); | |
public bool IsAttributeSettable(string attributeName) | |
{ | |
var str = CFString.CreateNative(attributeName); | |
try { | |
byte settable; | |
var result = AXUIElementIsAttributeSettable(Handle, str, out settable); | |
if (result != 0) | |
throw new Exception($"AXUIElementIsAttributeSettable failed with code {result}"); | |
return settable != 0; | |
} finally { | |
CFString.ReleaseNative(str); | |
} | |
} | |
[DllImport("/System/Library/Frameworks/ApplicationServices.framework/Frameworks/HIServices.framework/HIServices")] | |
static extern int AXUIElementPerformAction(IntPtr element, IntPtr action); | |
public void PerformAction(string actionName) | |
{ | |
var str = CFString.CreateNative(actionName); | |
try { | |
var result = AXUIElementPerformAction(Handle, str); | |
if (result != 0) | |
throw new Exception($"AXUIElementPerformAction failed with code {result}"); | |
} finally { | |
CFString.ReleaseNative(str); | |
} | |
} | |
[DllImport("/System/Library/Frameworks/ApplicationServices.framework/Frameworks/HIServices.framework/HIServices")] | |
static extern int AXUIElementCopyElementAtPosition(IntPtr application, float x, float y, out IntPtr element); | |
public static AXUIElement? CopyElementAtPosition(AXUIElement application, float x, float y) | |
{ | |
IntPtr elementHandle; | |
var result = AXUIElementCopyElementAtPosition(application.Handle, x, y, out elementHandle); | |
if (result != 0) | |
throw new Exception($"AXUIElementCopyElementAtPosition failed with code {result}"); | |
return elementHandle == IntPtr.Zero ? null : new AXUIElement(elementHandle, true); | |
} | |
[DllImport("/System/Library/Frameworks/ApplicationServices.framework/Frameworks/HIServices.framework/HIServices")] | |
static extern int AXUIElementSetMessagingTimeout(IntPtr element, float timeoutInSeconds); | |
public void SetMessagingTimeout(float timeoutInSeconds) | |
{ | |
var result = AXUIElementSetMessagingTimeout(Handle, timeoutInSeconds); | |
if (result != 0) | |
throw new Exception($"AXUIElementSetMessagingTimeout failed with code {result}"); | |
} | |
[DllImport("/System/Library/Frameworks/ApplicationServices.framework/Frameworks/HIServices.framework/HIServices")] | |
static extern int AXUIElementPostKeyboardEvent(IntPtr application, ushort keyChar, uint virtualKey, bool keyDown); | |
public void PostKeyboardEvent(ushort keyChar, uint virtualKey, bool keyDown) | |
{ | |
var result = AXUIElementPostKeyboardEvent(Handle, keyChar, virtualKey, keyDown); | |
if (result != 0) | |
throw new Exception($"AXUIElementPostKeyboardEvent failed with code {result}"); | |
} | |
[DllImport("/System/Library/Frameworks/ApplicationServices.framework/Frameworks/HIServices.framework/HIServices")] | |
static extern int _AXUIElementGetWindow(IntPtr element, out IntPtr windowId); | |
public IntPtr GetWindow() | |
{ | |
IntPtr windowId; | |
var result = _AXUIElementGetWindow(Handle, out windowId); | |
if (result != 0) | |
throw new Exception($"_AXUIElementGetWindow failed with code {result}"); | |
return windowId; | |
} | |
// System.Drawing convenience methods | |
public Point? GetPosition() | |
{ | |
var value = GetAttributeValue(AXAttributes.Position) as AXValue; | |
return value; // Uses implicit operator | |
} | |
public Size? GetSize() | |
{ | |
var value = GetAttributeValue(AXAttributes.Size) as AXValue; | |
return value; // Uses implicit operator | |
} | |
public Rectangle? GetBounds() | |
{ | |
var pos = GetPosition(); | |
var size = GetSize(); | |
if (pos == null || size == null) | |
return null; | |
return new Rectangle(pos.Value, size.Value); | |
} | |
} | |
public class AXValue : NativeObject | |
{ | |
[DllImport("/System/Library/Frameworks/ApplicationServices.framework/Frameworks/HIServices.framework/HIServices")] | |
static extern IntPtr AXValueCreate(AXValueType type, ref CGPoint point); | |
[DllImport("/System/Library/Frameworks/ApplicationServices.framework/Frameworks/HIServices.framework/HIServices")] | |
static extern IntPtr AXValueCreate(AXValueType type, ref CGSize size); | |
[DllImport("/System/Library/Frameworks/ApplicationServices.framework/Frameworks/HIServices.framework/HIServices")] | |
static extern IntPtr AXValueCreate(AXValueType type, ref CGRect rect); | |
[DllImport("/System/Library/Frameworks/ApplicationServices.framework/Frameworks/HIServices.framework/HIServices")] | |
static extern bool AXValueGetValue(IntPtr value, AXValueType type, out CGPoint point); | |
[DllImport("/System/Library/Frameworks/ApplicationServices.framework/Frameworks/HIServices.framework/HIServices")] | |
static extern bool AXValueGetValue(IntPtr value, AXValueType type, out CGSize size); | |
[DllImport("/System/Library/Frameworks/ApplicationServices.framework/Frameworks/HIServices.framework/HIServices")] | |
static extern bool AXValueGetValue(IntPtr value, AXValueType type, out CGRect rect); | |
internal AXValue(IntPtr handle, bool owns) : base(handle, owns) { } | |
// CoreGraphics type creation | |
public static AXValue CreateFromPoint(CGPoint point) | |
{ | |
var handle = AXValueCreate(AXValueType.CGPoint, ref point); | |
return new AXValue(handle, true); | |
} | |
public static AXValue CreateFromSize(CGSize size) | |
{ | |
var handle = AXValueCreate(AXValueType.CGSize, ref size); | |
return new AXValue(handle, true); | |
} | |
public static AXValue CreateFromRect(CGRect rect) | |
{ | |
var handle = AXValueCreate(AXValueType.CGRect, ref rect); | |
return new AXValue(handle, true); | |
} | |
// System.Drawing type creation | |
public static AXValue CreateFromPoint(Point point) | |
{ | |
var cgPoint = new CGPoint(point.X, point.Y); | |
return CreateFromPoint(cgPoint); | |
} | |
public static AXValue CreateFromSize(Size size) | |
{ | |
var cgSize = new CGSize(size.Width, size.Height); | |
return CreateFromSize(cgSize); | |
} | |
public static AXValue CreateFromRect(Rectangle rect) | |
{ | |
var cgRect = new CGRect(rect.X, rect.Y, rect.Width, rect.Height); | |
return CreateFromRect(cgRect); | |
} | |
// CoreGraphics type getters | |
public bool GetCGPoint(out CGPoint point) | |
{ | |
return AXValueGetValue(Handle, AXValueType.CGPoint, out point); | |
} | |
public bool GetCGSize(out CGSize size) | |
{ | |
return AXValueGetValue(Handle, AXValueType.CGSize, out size); | |
} | |
public bool GetCGRect(out CGRect rect) | |
{ | |
return AXValueGetValue(Handle, AXValueType.CGRect, out rect); | |
} | |
// System.Drawing type getters | |
public bool GetPoint(out Point point) | |
{ | |
var result = GetCGPoint(out var cgPoint); | |
point = new Point((int)cgPoint.X, (int)cgPoint.Y); | |
return result; | |
} | |
public bool GetSize(out Size size) | |
{ | |
var result = GetCGSize(out var cgSize); | |
size = new Size((int)cgSize.Width, (int)cgSize.Height); | |
return result; | |
} | |
public bool GetRect(out Rectangle rect) | |
{ | |
var result = GetCGRect(out var cgRect); | |
rect = new Rectangle((int)cgRect.X, (int)cgRect.Y, (int)cgRect.Width, (int)cgRect.Height); | |
return result; | |
} | |
// Convenience conversion methods | |
public static implicit operator CGPoint?(AXValue? value) | |
{ | |
if (value is null) return null; | |
return value.GetCGPoint(out var point) ? point : null; | |
} | |
public static implicit operator Point?(AXValue? value) | |
{ | |
if (value is null) return null; | |
return value.GetPoint(out var point) ? point : null; | |
} | |
public static implicit operator CGSize?(AXValue? value) | |
{ | |
if (value is null) return null; | |
return value.GetCGSize(out var size) ? size : null; | |
} | |
public static implicit operator Size?(AXValue? value) | |
{ | |
if (value is null) return null; | |
return value.GetSize(out var size) ? size : null; | |
} | |
public static implicit operator CGRect?(AXValue? value) | |
{ | |
if (value is null) return null; | |
return value.GetCGRect(out var rect) ? rect : null; | |
} | |
public static implicit operator Rectangle?(AXValue? value) | |
{ | |
if (value is null) return null; | |
return value.GetRect(out var rect) ? rect : null; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Runtime.InteropServices; | |
using CoreGraphics; | |
using ObjCRuntime; | |
using Foundation; | |
using System.Linq; | |
namespace QuartzServices; | |
/// <summary> | |
/// Provides bindings for the Quartz Window Services API, specifically for window listing and information retrieval. | |
/// </summary> | |
public static class CGWindow | |
{ | |
/// <summary> | |
/// Required window information dictionary keys | |
/// </summary> | |
public static class RequiredKeys | |
{ | |
public static NSString WindowNumber { get; } = new("kCGWindowNumber"); | |
public static NSString WindowStoreType { get; } = new("kCGWindowStoreType"); | |
public static NSString WindowLayer { get; } = new("kCGWindowLayer"); | |
public static NSString WindowBounds { get; } = new("kCGWindowBounds"); | |
public static NSString WindowSharingState { get; } = new("kCGWindowSharingState"); | |
public static NSString WindowAlpha { get; } = new("kCGWindowAlpha"); | |
public static NSString WindowOwnerPID { get; } = new("kCGWindowOwnerPID"); | |
public static NSString WindowMemoryUsage { get; } = new("kCGWindowMemoryUsage"); | |
} | |
/// <summary> | |
/// Optional window information dictionary keys | |
/// </summary> | |
public static class OptionalKeys | |
{ | |
public static NSString WindowOwnerName { get; } = new("kCGWindowOwnerName"); | |
public static NSString WindowName { get; } = new("kCGWindowName"); | |
public static NSString WindowIsOnscreen { get; } = new("kCGWindowIsOnscreen"); | |
} | |
/// <summary> | |
/// Represents a window's information as returned by the Quartz Window Services API | |
/// </summary> | |
public sealed record WindowInfo | |
{ | |
public required nint WindowNumber { get; init; } | |
public required string StoreType { get; init; } | |
public required nint Layer { get; init; } | |
public required CGRect Bounds { get; init; } | |
public required nint OwnerPID { get; init; } | |
// Optional properties | |
public string? OwnerName { get; init; } | |
public string? WindowName { get; init; } | |
public bool? IsOnscreen { get; init; } | |
/// <summary> | |
/// Creates a WindowInfo record from an NSDictionary | |
/// </summary> | |
internal static WindowInfo FromDictionary(NSDictionary dict) | |
{ | |
var WindowNumber = Runtime.GetNSObject<NSNumber>(dict[RequiredKeys.WindowNumber].Handle).NIntValue; | |
var StoreType = dict[RequiredKeys.WindowStoreType].ToString()!; | |
var Layer = Runtime.GetNSObject<NSNumber>(dict[RequiredKeys.WindowLayer].Handle).NIntValue; | |
CGRect.TryParse(Runtime.GetNSObject<NSDictionary>(dict[RequiredKeys.WindowBounds].Handle), out var Bounds); | |
var OwnerPID = Runtime.GetNSObject<NSNumber>(dict[RequiredKeys.WindowOwnerPID].Handle).NIntValue; | |
// Optional values - use null conditional operator to safely handle missing keys | |
var OwnerName = dict[OptionalKeys.WindowOwnerName]?.ToString(); | |
var WindowName = dict[OptionalKeys.WindowName]?.ToString(); | |
bool? IsOnscreen = dict[OptionalKeys.WindowIsOnscreen] is NSNumber n ? n.BoolValue : null; | |
WindowInfo ret = new() { | |
WindowNumber = WindowNumber, | |
StoreType = StoreType, | |
Layer = Layer, | |
Bounds = Bounds, | |
OwnerPID = OwnerPID, | |
OwnerName = OwnerName, | |
WindowName = WindowName, | |
IsOnscreen = IsOnscreen | |
}; | |
return ret; | |
} | |
} | |
/// <summary> | |
/// Native method import for window information retrieval | |
/// </summary> | |
[DllImport("/System/Library/Frameworks/Quartz.framework/Quartz")] | |
static extern IntPtr CGWindowListCopyWindowInfo( | |
CGWindowListOption option, | |
IntPtr relativeToWindow | |
); | |
/// <summary> | |
/// Retrieves information about windows in the system | |
/// </summary> | |
/// <param name="option">The window list options to apply</param> | |
/// <param name="relativeToWindow">The reference window ID, or IntPtr.Zero for none</param> | |
/// <returns>An array of window information records</returns> | |
public static WindowInfo[] GetWindowInformation( | |
CGWindowListOption option, | |
IntPtr relativeToWindow = default) | |
{ | |
var ptr = CGWindowListCopyWindowInfo(option, relativeToWindow); | |
ArgumentNullException.ThrowIfNull(ptr); | |
// Convert the unmanaged CFArrayRef to a managed NSArray | |
using var array = Runtime.GetNSObject<NSArray>(ptr)!; | |
// Use modern LINQ and collection expressions to transform the array | |
return Enumerable.Range(0, (int)array.Count) | |
.Select(i => Runtime.GetNSObject<NSDictionary>(array.ValueAt((nuint)i))) | |
.Select(WindowInfo.FromDictionary) | |
.ToArray(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment