Skip to content

Commit 24919d0

Browse files
committed
Improve screen reader detection on Windows by checking modules
Borrows the "better" algorithm from Electron, with attribution.
1 parent 2292525 commit 24919d0

File tree

2 files changed

+69
-6
lines changed

2 files changed

+69
-6
lines changed

PSReadLine/Accessibility.cs

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,43 @@ internal class Accessibility
1010
{
1111
internal static bool IsScreenReaderActive()
1212
{
13-
bool returnValue = false;
13+
// TODO: Support other platforms per https://code.visualstudio.com/docs/configure/accessibility/accessibility
14+
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
15+
return false;
1416

15-
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
17+
// The supposedly official way to check for a screen reader on
18+
// Windows is SystemParametersInfo(SPI_GETSCREENREADER, ...) but it
19+
// doesn't detect the in-box Windows Narrator and is otherwise known
20+
// to be problematic.
21+
//
22+
// The following is adapted from the Electron project, under the MIT License.
23+
// Hence this is also how VS Code detects screenreaders.
24+
// See: https://github.com/electron/electron/pull/39988
25+
26+
// Check for Windows Narrator using the NarratorRunning mutex
27+
if (PlatformWindows.IsMutexPresent("NarratorRunning"))
28+
return true;
29+
30+
// Check for various screen reader libraries
31+
string[] screenReaderLibraries = {
32+
// NVDA
33+
"nvdaHelperRemote.dll",
34+
// JAWS
35+
"jhook.dll",
36+
// Window-Eyes
37+
"gwhk64.dll",
38+
"gwmhook.dll",
39+
// ZoomText
40+
"AiSquared.Infuser.HookLib.dll"
41+
};
42+
43+
foreach (string library in screenReaderLibraries)
1644
{
17-
// NOTE: This API can detect if a third-party screen reader is active, such as NVDA, but not the in-box Windows Narrator.
18-
PlatformWindows.SystemParametersInfo(PlatformWindows.SPI_GETSCREENREADER, 0, ref returnValue, 0);
19-
} // TODO: Support other platforms per https://code.visualstudio.com/docs/configure/accessibility/accessibility
45+
if (PlatformWindows.IsLibraryLoaded(library))
46+
return true;
47+
}
2048

21-
return returnValue;
49+
return false;
2250
}
2351
}
2452
}

PSReadLine/PlatformWindows.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,41 @@ IntPtr templateFileWin32Handle
7979
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
8080
internal static extern IntPtr GetStdHandle(uint handleId);
8181

82+
internal static bool IsMutexPresent(string name)
83+
{
84+
try
85+
{
86+
using var mutex = new System.Threading.Mutex(false, name);
87+
return Marshal.GetLastWin32Error() == 183; // ERROR_ALREADY_EXISTS
88+
}
89+
catch
90+
{
91+
return false;
92+
}
93+
}
94+
95+
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
96+
[return: MarshalAs(UnmanagedType.Bool)]
97+
internal static extern bool GetModuleHandleEx(uint dwFlags, string lpModuleName, out IntPtr phModule);
98+
99+
internal static bool IsLibraryLoaded(string name)
100+
{
101+
// Prevents the reference count for the module being incremented.
102+
const uint GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT = 0x00000002;
103+
try
104+
{
105+
bool retValue = GetModuleHandleEx(
106+
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
107+
name,
108+
out IntPtr phModule);
109+
return phModule != IntPtr.Zero;
110+
}
111+
catch
112+
{
113+
return false;
114+
}
115+
}
116+
82117
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
83118
static extern bool SetConsoleCtrlHandler(BreakHandler handlerRoutine, bool add);
84119

0 commit comments

Comments
 (0)