Narrator doesn't properly set the system screen reader flag. How do I check if it's running?

Published: 2024-03-22

Narrator, the screen reader that is built-in to Windows and made by Microsoft, doesn't set the system screen reader flag, despite Microsoft themselves using this flag to detect screen readers in apps like Visual Studio Code, and recommending its usage.

At least they now mention that Narrator doesn't set the flag on the official documentation page, but don't tell you why, or how to query it. According to a former Narrator developer I spoke to, this is because internal people at Microsoft didn't think the screen reader flag was needed, and accessibility should be built-in, not reliant on the flag. Note that I'm very roughly paraphrasing, but for whatever it's worth I agree. However, as even Microsoft's newer products showcase, like the aformerly mentioned Visual Studio Code, and the new PowerShell, screen reader-specific modes/features/tweaks are very often needed. Not very many people use Narrator, but it annoyed me to a great degree that I couldn't detect it. I'm off this week, so had some extra time to sit down and attack the internet with my query. I managed to find a few things:

  1. The PureBasic developers detect Narrator for their new screen reader mode. The IDE is open source, and you can find the function in PureBasicIDE/WindowsMisc.pb in their GitHub repository.
  2. This Stack Overflow answer about the exact same issue.

The TL;DR is that Narrator creates a mutex when it runs, called "NarratorRunning". After about 20 minutes of hacking, I came up with this small C program to detect if a screen reader is active:

#include <windows.h>
#include <stdio.h>

BOOL IsScreenReaderRunning();

int main() {
	printf("%s", (IsScreenReaderRunning() ? "Running" : "Not running"));
	return 0;
}

BOOL IsScreenReaderRunning() {
	BOOL bIsActive = FALSE;
	if (SystemParametersInfo(SPI_GETSCREENREADER, 0, &bIsActive, 0)) {
		if (bIsActive)
			return TRUE;
	}
	HANDLE hMutex = OpenMutex(SYNCHRONIZE, FALSE, "NarratorRunning");
	if (hMutex) {
		CloseHandle(hMutex);
		return TRUE;
	}
	return FALSE;
}

Compile this with your favorite Windows C compiler, and link against User32.lib. If I run this program with either NVDA or Narrator active, I see "Running". With no reader, I see "Not running".

An effective, albeit pretty dumb, hack.