Skip to content

Commit 4baae08

Browse files
committed
Improve screen reader detection on Windows by checking modules
Borrows the "better" algorithm from Electron, with attribution.
1 parent 06c70c8 commit 4baae08

File tree

2 files changed

+71
-6
lines changed

2 files changed

+71
-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: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,43 @@ IntPtr templateFileWin32Handle
7979
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
8080
internal static extern IntPtr GetStdHandle(uint handleId);
8181

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

0 commit comments

Comments
 (0)