Skip to content

Commit

Permalink
v2.0: Chrome Support
Browse files Browse the repository at this point in the history
  • Loading branch information
Alex Vallat committed Dec 26, 2020
1 parent fc7c32b commit 14c8f3c
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 7 deletions.
14 changes: 14 additions & 0 deletions KeyLayoutAutoSwitch/AccessibleObjectHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ namespace KeyLayoutAutoSwitch
{
internal static class AccessibleObjectHelper
{
[DllImport("oleacc.dll", ExactSpelling = true, PreserveSig = false)]
[return: MarshalAs(UnmanagedType.Interface)]
private static extern object AccessibleObjectFromWindow(
IntPtr hwnd,
uint dwObjectID,
[In, MarshalAs(UnmanagedType.LPStruct)] Guid riid);

private static readonly Guid IID_IAccessible = new Guid("{618736E0-3C3D-11CF-810C-00AA00389B71}");

[DllImport("oleacc.dll")]
private static extern uint AccessibleChildren(IAccessible paccContainer, int iChildStart, int cChildren, [Out] object[] rgvarChildren, out int pcObtained);
private const int NAVDIR_FIRSTCHILD = 7;
Expand Down Expand Up @@ -115,5 +124,10 @@ public static AccessibleRole GetRole(IAccessible accessibleObject)
return AccessibleRole.None;
}
}

public static IAccessible GetAccessibleObjectFromWindow(IntPtr hwnd, uint objectID = 0)
{
return (IAccessible)AccessibleObjectFromWindow(hwnd, objectID, IID_IAccessible);
}
}
}
62 changes: 62 additions & 0 deletions KeyLayoutAutoSwitch/ChromeAccessibilityWinEventHook.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using System;
using System.Runtime.InteropServices;

namespace KeyLayoutAutoSwitch
{
/// <summary>
/// Handles accessibility events as if a screen reader were running. Chrome detects this to automatically enable accessibility features.
/// </summary>
internal class ChromeAccessibilityWinEventHook : IDisposable
{
private delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);

[DllImport("user32.dll")]
private static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);

[DllImport("user32.dll")]
private static extern bool UnhookWinEvent(IntPtr hWinEventHook);

[DllImport("User32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, uint nMsg, int wParam, int lParam);

private const uint WINEVENT_OUTOFCONTEXT = 0;
private const uint EVENT_SYSTEM_ALERT = 0x0002;

private const uint WM_GETOBJECT = 0x3D;

// ref: http://www.chromium.org/developers/design-documents/accessibility
private const int GOOGLE_CHROME_ACCESSIBILITY_OBJECT_ID = 1;

private readonly IntPtr mWinEventHook;
private readonly WinEventDelegate mEventDelegate;

public ChromeAccessibilityWinEventHook()
{
mEventDelegate = OnEventReceived;
mWinEventHook = SetWinEventHook(EVENT_SYSTEM_ALERT, EVENT_SYSTEM_ALERT, IntPtr.Zero, mEventDelegate, 0, 0, WINEVENT_OUTOFCONTEXT);
}

private void OnEventReceived(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
{
if (idObject == GOOGLE_CHROME_ACCESSIBILITY_OBJECT_ID)
{
EventReceived = true;
SendMessage(hwnd, WM_GETOBJECT, 0, idObject);

// Chrome only enables accessibility if it gets a top-level IAccessible request, so let's make one first
var _ = AccessibleObjectHelper.GetAccessibleObjectFromWindow(hwnd).accName;
}
}

public void Dispose()
{
UnhookWinEvent(mWinEventHook);
}

/// <summary>
/// This flag is set true whenever an event is received.
/// Set it false before the period of interest.
/// </summary>
public bool EventReceived { get; set; }
}
}
1 change: 1 addition & 0 deletions KeyLayoutAutoSwitch/KeyLayoutAutoSwitch.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
<ItemGroup>
<Compile Include="AccessibleObjectHelper.cs" />
<Compile Include="Browser.cs" />
<Compile Include="ChromeAccessibilityWinEventHook.cs" />
<Compile Include="ChromeWidgets.cs" />
<Compile Include="ExtensionMethods.cs" />
<Compile Include="Chrome.cs" />
Expand Down
12 changes: 9 additions & 3 deletions KeyLayoutAutoSwitch/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,12 @@ internal class BackgroundApplicationContext : ApplicationContext
{
private readonly NotifyIcon mNotifyIcon;
private readonly IDisposable mFocusEventHook;
private readonly ChromeAccessibilityWinEventHook mChromeAccessibilityWinEventHook;

public BackgroundApplicationContext(bool showInitialConfigDialog)
{
mFocusEventHook = NativeMethods.AddFocusEventHook(OnFocusChanged);
mChromeAccessibilityWinEventHook = new ChromeAccessibilityWinEventHook();

Application.ApplicationExit += OnApplicationExit;

Expand Down Expand Up @@ -241,7 +243,7 @@ private void OnFocusChanged(IntPtr hwnd, uint idObject, uint idChild)
{
var className = NativeMethods.GetWindowClassName(hwnd);

Debug.WriteLine($"Focus changed to window: {hwnd} ({className}), object {idObject}, child {idChild}, layout {NativeMethods.GetKeyboardLayout(hwnd)}");
//Debug.WriteLine($"Focus changed to window: {hwnd} ({className}), object {idObject}, child {idChild}, layout {NativeMethods.GetKeyboardLayout(hwnd)}");

Browser browser = null;
switch (className)
Expand All @@ -252,8 +254,11 @@ private void OnFocusChanged(IntPtr hwnd, uint idObject, uint idChild)
case "Chrome_RenderWidgetHostHWND":
browser = new Chrome();
break;
case "Chrome_WidgetWin_1":
browser = new ChromeWidgets();
default:
if (className.StartsWith("Chrome_WidgetWin_"))
{
browser = new ChromeWidgets();
}
break;
}
if (browser != null)
Expand Down Expand Up @@ -338,6 +343,7 @@ private void OnApplicationExit(object sender, EventArgs eventArgs)
{
mFocusEventHook.Dispose();
mNotifyIcon.Dispose();
mChromeAccessibilityWinEventHook.Dispose();
}
}
}
4 changes: 2 additions & 2 deletions KeyLayoutAutoSwitch/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,5 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.2.0.0")]
[assembly: AssemblyFileVersion("1.2.0.0")]
[assembly: AssemblyVersion("2.0.0.0")]
[assembly: AssemblyFileVersion("2.0.0.0")]
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,14 @@ Note that the visited URLs are stored hashed, and in memory only. They are never

To exit KeyLayoutAutoSwitch entirely, right click on the notification area icon and choose "Exit". KeyLayoutAutoSwitch will no longer be running in the background, and will not change keyboard layouts until it is next run.

## Chrome Support
KeyLayoutAutoSwitch also supports Chrome and most Chromium-based browsers, however the Find in Page rule "Match web page" will not work (the URL is not know for the Find in Page popup). Instead, use the "Do not change" rule, which is similar.

If Chrome is not automatically enabling accessibility then this can be forced by adding the "--force-renderer-accessibility" flag on the command line. This will not generally be required, but if you are finding that the site rules are having no effect then it is worth trying.

## Limitations
KeyLayoutAutoSwitch uses the Accessibility interfaces to obtain information from the web browser about which element is focused. If Accessibility services have been disabled in the browser it will not be able to function properly.

At present only Firefox is supported, although it is anticipated that support for other browsers may be added in the future.

KeyLayoutAutoSwitch is Windows only. Linux support would be nice to have, but is unlikely to happen soon, unless someone with knowledge of Linux accessibility services and Mono is willing to contribute development work to the project.

## Translation
Expand All @@ -53,6 +56,9 @@ KeyLayoutAutoSwitch will use the default windows locale, but this may be overrid
Donations are very welcome, and may be made through PayPal by using the [Donate](http://keylayoutautoswitch.byalexv.co.uk/donate) link. Or you can send directly to me in any currency using <https://www.paypal.me/toalexv>.

## Changelog
v2.0:
* Added support for Chrome and Chromium-based browsers

v1.3:
* Added new option to Previously visited web pages rule, to apply the last used keyboard layout for the whole site (domain)
* Added context menu command to clear the previously visited web pages, forgetting all last used layouts
Expand Down

0 comments on commit 14c8f3c

Please sign in to comment.