Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 73 additions & 4 deletions PSReadLine/Accessibility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Copyright (c) Microsoft Corporation. All rights reserved.
--********************************************************************/

using System.Diagnostics;
using System.Runtime.InteropServices;

namespace Microsoft.PowerShell.Internal
Expand All @@ -10,14 +11,82 @@ internal class Accessibility
{
internal static bool IsScreenReaderActive()
{
bool returnValue = false;

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
PlatformWindows.SystemParametersInfo(PlatformWindows.SPI_GETSCREENREADER, 0, ref returnValue, 0);
return IsAnyWindowsScreenReaderEnabled();
}

if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return IsVoiceOverEnabled();
}

// TODO: Support Linux per https://code.visualstudio.com/docs/configure/accessibility/accessibility
return false;
}

private static bool IsAnyWindowsScreenReaderEnabled()
{
// The supposedly official way to check for a screen reader on
// Windows is SystemParametersInfo(SPI_GETSCREENREADER, ...) but it
// doesn't detect the in-box Windows Narrator and is otherwise known
// to be problematic.
//
// Unfortunately, the alternative method used by Electron and
// Chromium, where the relevant screen reader libraries (modules)
// are checked for does not work in the context of PowerShell
// because it relies on those applications injecting themselves into
// the app. Which they do not because PowerShell is not a windowed
// app, so we're stuck using the known-to-be-buggy way.
bool spiScreenReader = false;
PlatformWindows.SystemParametersInfo(PlatformWindows.SPI_GETSCREENREADER, 0, ref spiScreenReader, 0);
if (spiScreenReader)
{
return true;
}

// At least we can correctly check for Windows Narrator using the
// NarratorRunning mutex. Windows Narrator is mostly not broken with
// PSReadLine, not in the way that NVDA and VoiceOver are.
if (PlatformWindows.IsMutexPresent("NarratorRunning"))
{
return true;
}

return false;
}

private static bool IsVoiceOverEnabled()
{
try
{
// Use the 'defaults' command to check if VoiceOver is enabled
// This checks the com.apple.universalaccess preference for voiceOverOnOffKey
ProcessStartInfo startInfo = new()
{
FileName = "defaults",
Arguments = "read com.apple.universalaccess voiceOverOnOffKey",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};

using Process process = Process.Start(startInfo);
process.WaitForExit(250);
if (process.HasExited && process.ExitCode == 0)
{
string output = process.StandardOutput.ReadToEnd().Trim();
// VoiceOver is enabled if the value is 1
return output == "1";
}
}
catch
{
// If we can't determine the status, assume VoiceOver is not enabled
}

return returnValue;
return false;
}
}
}
4 changes: 2 additions & 2 deletions PSReadLine/BasicEditing.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public static void CancelLine(ConsoleKeyInfo? key = null, object arg = null)
_singleton._current = _singleton._buffer.Length;

using var _ = _singleton._prediction.DisableScoped();
_singleton.ForceRender();
_singleton.Render(force: true);

_singleton._console.Write("\x1b[91m^C\x1b[0m");

Expand Down Expand Up @@ -335,7 +335,7 @@ private bool AcceptLineImpl(bool validate)

if (renderNeeded)
{
ForceRender();
Render(force: true);
}

// Only run validation if we haven't before. If we have and status line shows an error,
Expand Down
17 changes: 12 additions & 5 deletions PSReadLine/Cmdlets.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.PowerShell.PSReadLine;
using Microsoft.PowerShell.Internal;
using AllowNull = System.Management.Automation.AllowNullAttribute;

namespace Microsoft.PowerShell
Expand Down Expand Up @@ -150,11 +151,6 @@ public class PSConsoleReadLineOptions

public const HistorySaveStyle DefaultHistorySaveStyle = HistorySaveStyle.SaveIncrementally;

/// <summary>
/// The predictive suggestion feature is disabled by default.
/// </summary>
public const PredictionSource DefaultPredictionSource = PredictionSource.None;

public const PredictionViewStyle DefaultPredictionViewStyle = PredictionViewStyle.InlineView;

/// <summary>
Expand Down Expand Up @@ -201,6 +197,7 @@ public PSConsoleReadLineOptions(string hostName, bool usingLegacyConsole)
{
ResetColors();
EditMode = DefaultEditMode;
ScreenReaderModeEnabled = Accessibility.IsScreenReaderActive();
ContinuationPrompt = DefaultContinuationPrompt;
ContinuationPromptColor = Console.ForegroundColor;
ExtraPromptLineCount = DefaultExtraPromptLineCount;
Expand Down Expand Up @@ -533,6 +530,8 @@ public object ListPredictionTooltipColor

public bool TerminateOrphanedConsoleApps { get; set; }

public bool ScreenReaderModeEnabled { get; set; }

internal string _defaultTokenColor;
internal string _commentColor;
internal string _keywordColor;
Expand Down Expand Up @@ -847,6 +846,14 @@ public SwitchParameter TerminateOrphanedConsoleApps
}
internal SwitchParameter? _terminateOrphanedConsoleApps;

[Parameter]
public SwitchParameter EnableScreenReaderMode
{
get => _enableScreenReaderMode.GetValueOrDefault();
set => _enableScreenReaderMode = value;
}
internal SwitchParameter? _enableScreenReaderMode;

[ExcludeFromCodeCoverage]
protected override void EndProcessing()
{
Expand Down
4 changes: 4 additions & 0 deletions PSReadLine/Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,10 @@ private void SetOptionsInternal(SetPSReadLineOption options)
nameof(Options.TerminateOrphanedConsoleApps)));
}
}
if (options._enableScreenReaderMode.HasValue)
{
Options.ScreenReaderModeEnabled = options.EnableScreenReaderMode;
}
}

private void SetKeyHandlerInternal(string[] keys, Action<ConsoleKeyInfo?, object> handler, string briefDescription, string longDescription, ScriptBlock scriptBlock)
Expand Down
15 changes: 15 additions & 0 deletions PSReadLine/PlatformWindows.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,21 @@ IntPtr templateFileWin32Handle
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern IntPtr GetStdHandle(uint handleId);

internal const int ERROR_ALREADY_EXISTS = 0xB7;

internal static bool IsMutexPresent(string name)
{
try
{
using var mutex = new System.Threading.Mutex(false, name);
return Marshal.GetLastWin32Error() == ERROR_ALREADY_EXISTS;
}
catch
{
return false;
}
}

[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool SetConsoleCtrlHandler(BreakHandler handlerRoutine, bool add);

Expand Down
Loading