We've historically used CefSharp.Wpf
+ Touch keyboard sample to open the tablet keyboard on a WPF Kiosk that wraps CEF Sharp. Since it is using CefSharp.Wpf
- we are facing the relatively common GPU rendering issues on certain devices, as well as disappointment with the performance. Switching to CefSharp.Wpf.HwndHost
works to solve our GPU issues, but doesn't reliably trigger the touch keyboard.
How can I trigger the Windows 10 touch keyboard? (OSK / TabTip.exe)
Things that don't work:
disable-usb-keyboard-detect
does not by itself produce reliable SIP activationCefSharp.Wpf.HwndHost
by itself does not produce reliable SIP activation, but does sometimes work and does produce correct SIP type for numeric et al.CefSharp.Wpf
does not produce any SIP activation (in box), and produces GPU artifacts and performance issues on many machinesCefSharp.Wpf
+ Touch keyboard sample does produce reliable SIP activation, but does not control SIP keyboard type (e.g.: numeric pad not shown on <input type="number"
) and still suffers from GPU and performance issuesYou can use the same IInputPane2
interface to trigger the touch keyboard. CefSharp.Wpf.HwndHost
requires use of IRenderProcessMessageHandler
to receive focus changes, since the VirtualKeyboardRequested
event is specific to CefSharp.Wpf
.
Since the IRenderProcessMessageHandler.OnFocusedNodeChanged
event fires on a fairly high frequency and in cases where no OSK is desired, it is required to filter and debounce the event. It is trivial to do this using Rx.Net. A full example can be found on Github.
net6.0-windows10.0.19041
<PackageReference Include="CefSharp.Wpf.HwndHost" Version="98.1.210" />
<PackageReference Include="System.Reactive" Version="5.0.0" />
Cef.EnableHighDPISupport();
var settings = new CefSettings();
CefSharpSettings.FocusedNodeChangedEnabled = true;
settings.CefCommandLineArgs.Add("disable-usb-keyboard-detect", "1");
Cef.Initialize(settings);
public partial class MainWindow : Window
{
private Lazy<(IInputPaneInterop ipi, IInputPane2 ip)> sip;
public MainWindow()
{
InitializeComponent();
sip = new Lazy<(IInputPaneInterop ipi, IInputPane2 ip)>(() =>
{
var hwnd = new WindowInteropHelper(this).Handle;
var ipi = InputPane.As<IInputPaneInterop>();
var ip = ipi.GetForWindow(hwnd, typeof(IInputPane2).GUID);
return (ipi, ip);
});
var oskSubject = new Subject<bool>();
cwb.RenderProcessMessageHandler = new OskRenderProcessMessageHandler(oskSubject.OnNext);
oskSubject
.Throttle(TimeSpan.FromMilliseconds(200))
.ObserveOn(SynchronizationContext.Current ?? throw new InvalidOperationException("No syncctx"))
.Subscribe(PopOsk);
}
protected override void OnClosed(EventArgs e)
{
if (sip.IsValueCreated)
{
var (ipi, ip) = sip.Value;
Marshal.FinalReleaseComObject(ip);
Marshal.FinalReleaseComObject(ipi);
}
base.OnClosed(e);
}
private void PopOsk(bool shouldShow)
{
var (_, ip) = sip.Value;
if (shouldShow)
{
Debug.WriteLine($"Showing SIP");
ip.TryShow();
}
else
{
Debug.WriteLine($"Hiding SIP");
ip.TryHide();
}
}
}
internal class OskRenderProcessMessageHandler : IRenderProcessMessageHandler
{
private readonly Action<bool> SetOsk;
public OskRenderProcessMessageHandler(Action<bool> popOsk)
{
this.SetOsk = popOsk;
}
public void OnContextCreated(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame)
{
// nop
}
public void OnContextReleased(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame)
{
// nop
}
public void OnFocusedNodeChanged(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IDomNode? node)
{
SetOsk(node != null && "input".Equals(node.TagName, StringComparison.InvariantCultureIgnoreCase));
}
public void OnUncaughtException(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, JavascriptException exception)
{
// nop
}
}
[ComImport, Guid("75CF2C57-9195-4931-8332-F0B409E916AF"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IInputPaneInterop
{
void _VtblGap1_3();
IInputPane2 GetForWindow([In] IntPtr appWindow, [In] ref Guid riid);
}
[ComImport, Guid("8A6B3F26-7090-4793-944C-C3F2CDE26276"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IInputPane2
{
void _VtblGap1_3();
bool TryShow();
bool TryHide();
}
Error checking is elided for expository purposes.
General considerations:
BorderStyle