22Copyright (c) Microsoft Corporation. All rights reserved.
33--********************************************************************/
44
5+ using System . Diagnostics ;
56using System . Runtime . InteropServices ;
67
78namespace Microsoft . PowerShell . Internal
@@ -10,14 +11,82 @@ internal class Accessibility
1011 {
1112 internal static bool IsScreenReaderActive ( )
1213 {
13- bool returnValue = false ;
14-
1514 if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
1615 {
17- PlatformWindows . SystemParametersInfo ( PlatformWindows . SPI_GETSCREENREADER , 0 , ref returnValue , 0 ) ;
16+ return IsAnyWindowsScreenReaderEnabled ( ) ;
17+ }
18+
19+ if ( RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) )
20+ {
21+ return IsVoiceOverEnabled ( ) ;
22+ }
23+
24+ // TODO: Support Linux per https://code.visualstudio.com/docs/configure/accessibility/accessibility
25+ return false ;
26+ }
27+
28+ private static bool IsAnyWindowsScreenReaderEnabled ( )
29+ {
30+ // The supposedly official way to check for a screen reader on
31+ // Windows is SystemParametersInfo(SPI_GETSCREENREADER, ...) but it
32+ // doesn't detect the in-box Windows Narrator and is otherwise known
33+ // to be problematic.
34+ //
35+ // Unfortunately, the alternative method used by Electron and
36+ // Chromium, where the relevant screen reader libraries (modules)
37+ // are checked for does not work in the context of PowerShell
38+ // because it relies on those applications injecting themselves into
39+ // the app. Which they do not because PowerShell is not a windowed
40+ // app, so we're stuck using the known-to-be-buggy way.
41+ bool spiScreenReader = false ;
42+ PlatformWindows . SystemParametersInfo ( PlatformWindows . SPI_GETSCREENREADER , 0 , ref spiScreenReader , 0 ) ;
43+ if ( spiScreenReader )
44+ {
45+ return true ;
46+ }
47+
48+ // At least we can correctly check for Windows Narrator using the
49+ // NarratorRunning mutex. Windows Narrator is mostly not broken with
50+ // PSReadLine, not in the way that NVDA and VoiceOver are.
51+ if ( PlatformWindows . IsMutexPresent ( "NarratorRunning" ) )
52+ {
53+ return true ;
54+ }
55+
56+ return false ;
57+ }
58+
59+ private static bool IsVoiceOverEnabled ( )
60+ {
61+ try
62+ {
63+ // Use the 'defaults' command to check if VoiceOver is enabled
64+ // This checks the com.apple.universalaccess preference for voiceOverOnOffKey
65+ ProcessStartInfo startInfo = new ( )
66+ {
67+ FileName = "defaults" ,
68+ Arguments = "read com.apple.universalaccess voiceOverOnOffKey" ,
69+ UseShellExecute = false ,
70+ RedirectStandardOutput = true ,
71+ RedirectStandardError = true ,
72+ CreateNoWindow = true
73+ } ;
74+
75+ using Process process = Process . Start ( startInfo ) ;
76+ process . WaitForExit ( 250 ) ;
77+ if ( process . HasExited && process . ExitCode == 0 )
78+ {
79+ string output = process . StandardOutput . ReadToEnd ( ) . Trim ( ) ;
80+ // VoiceOver is enabled if the value is 1
81+ return output == "1" ;
82+ }
83+ }
84+ catch
85+ {
86+ // If we can't determine the status, assume VoiceOver is not enabled
1887 }
1988
20- return returnValue ;
89+ return false ;
2190 }
2291 }
2392}
0 commit comments