Skip to content

Instantly share code, notes, and snippets.

@anaisbetts
Created November 15, 2024 20:52
Show Gist options
  • Save anaisbetts/320e357f75a10fd3c94c0a81d456a6fb to your computer and use it in GitHub Desktop.
Save anaisbetts/320e357f75a10fd3c94c0a81d456a6fb to your computer and use it in GitHub Desktop.
Accessibility APIs mapped for Xamarin .NET 8.0 macOS
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;
}
}
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