Welcome to mirror list, hosted at ThFree Co, Russian Federation.

AccerssibilityHelper.cs « Xwt.Mac « Xwt.XamMac - github.com/mono/xwt.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: ee448efec4088278121d461ed5d42ed10ad2fa2e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
using System;
using System.Runtime.InteropServices;
using AppKit;
using CoreFoundation;
using Foundation;
using ObjCRuntime;

namespace Xwt.Mac
{
	public class AccerssibilityHelper
	{
		public event EventHandler AccessibilityInUseChanged;

		static bool a11yHelperInitialized;
		static AccerssibilityHelper a11yHelperInstance;
		public void InitializeAccessibilityHelper()
		{
			// Don't swizzle twice if we have both XamMac and GtkMac running
			if (a11yHelperInstance != null)
				return;

			a11yHelperInstance = this;
			// TODO: Test if this works with VSM swizzle
			SwizzleNSApplicationAccessibilitySetter();
		}

		[DllImport("/usr/lib/libobjc.dylib")]
		private static extern IntPtr class_getInstanceMethod(IntPtr classHandle, IntPtr Selector);

		[DllImport("/usr/lib/libobjc.dylib")]
		private static extern IntPtr method_getImplementation(IntPtr method);

		[DllImport("/usr/lib/libobjc.dylib")]
		private static extern IntPtr imp_implementationWithBlock(ref BlockLiteral block);

		[DllImport("/usr/lib/libobjc.dylib")]
		private static extern void method_setImplementation(IntPtr method, IntPtr imp);

		[MonoNativeFunctionWrapper]
		delegate void AccessibilitySetValueForAttributeDelegate(IntPtr self, IntPtr selector, IntPtr valueHandle, IntPtr attributeHandle);
		delegate void SwizzledAccessibilitySetValueForAttributeDelegate(IntPtr block, IntPtr self, IntPtr valueHandle, IntPtr attributeHandle);

		static IntPtr originalAccessibilitySetValueForAttributeMethod;
		void SwizzleNSApplicationAccessibilitySetter()
		{
			// Swizzle accessibilitySetValue:forAttribute: so that we can detect when VoiceOver gets enabled
			var nsApplicationClassHandle = Class.GetHandle("NSApplication");

			// This happens if GtkMac is loaded before XamMac
			if (nsApplicationClassHandle == IntPtr.Zero)
				return;

			var accessibilitySetValueForAttributeSelector = Selector.GetHandle("accessibilitySetValue:forAttribute:");

			var accessibilitySetValueForAttributeMethod = class_getInstanceMethod(nsApplicationClassHandle, accessibilitySetValueForAttributeSelector);
			originalAccessibilitySetValueForAttributeMethod = method_getImplementation(accessibilitySetValueForAttributeMethod);

			var block = new BlockLiteral();

			SwizzledAccessibilitySetValueForAttributeDelegate d = accessibilitySetValueForAttribute;
			block.SetupBlock(d, null);
			var imp = imp_implementationWithBlock(ref block);
			method_setImplementation(accessibilitySetValueForAttributeMethod, imp);

			accessibilityInUse = CFPreferences.GetAppBooleanValue("voiceOverOnOffKey", "com.apple.universalaccess");
			a11yHelperInitialized = true;
		}

		[MonoPInvokeCallback(typeof(SwizzledAccessibilitySetValueForAttributeDelegate))]
		static void accessibilitySetValueForAttribute(IntPtr block, IntPtr self, IntPtr valueHandle, IntPtr attributeHandle)
		{
			var d = Marshal.GetDelegateForFunctionPointer(originalAccessibilitySetValueForAttributeMethod, typeof(AccessibilitySetValueForAttributeDelegate));
			d.DynamicInvoke(self, Selector.GetHandle("accessibilitySetValue:forAttribute:"), valueHandle, attributeHandle);

			var val = (NSNumber)ObjCRuntime.Runtime.GetNSObject(valueHandle);

			bool previousValue = accessibilityInUse;
			accessibilityInUse = val.BoolValue;
			if (accessibilityInUse != previousValue)
				a11yHelperInstance.OnAccessibilityInUseChanged(a11yHelperInstance, EventArgs.Empty);
		}

		void OnAccessibilityInUseChanged(object sender, EventArgs eventArgs)
		{
			AccessibilityInUseChanged?.Invoke(sender, eventArgs);
		}

		static bool accessibilityInUse;
		public bool AccessibilityInUse
		{
			get
			{
				if (!a11yHelperInitialized)
					InitializeAccessibilityHelper();

				return accessibilityInUse;
			}
		}

		public void MakeAnnouncement(string message, bool polite = false)
		{
			if (!a11yHelperInitialized)
				return;

			var nsObject = NSApplication.SharedApplication?.AccessibilityFocusedWindow;
			if (nsObject == null)
				return;
			using (var msg = new NSString(message))
			using (var dictionary = new NSDictionary(NSAccessibilityNotificationUserInfoKeys.AnnouncementKey, msg,
					NSAccessibilityNotificationUserInfoKeys.PriorityKey, polite ? NSAccessibilityPriorityLevel.Medium : NSAccessibilityPriorityLevel.High))
			{
				NSAccessibility.PostNotification(nsObject, NSView.AnnouncementRequestedNotification, dictionary);
			}
		}
	}
}