|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
//
//
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Interop;
namespace MS.Internal.WindowsRuntime
{
namespace Windows.UI.ViewManagement
{
/// <summary>
/// DevDiv:1193138
/// This class wraps the corresponding WinRT APIs for InputPane. This is used to show the touch keyboard.
/// Note that WinRT events belonging to this class are not included for simplicity.
///
/// This class uses RCWs for WinRT objects in order to properly cast from a WinRT IActivationFactory and
/// therefore implements IDisposable in order to allow for fast RCW cleanup.
/// </summary>
internal class InputPane : IDisposable
{
#region Fields
/// <summary>
/// Bool to check if the WinRT input pane is supported
/// </summary>
private static readonly bool _isSupported;
/// <summary>
/// Activation factory to instantiate InputPane RCWs
/// </summary>
private static object _winRtActivationFactory;
/// <summary>
/// The appropriate RCW for calling TryShow/Hide
/// </summary>
private InputPaneRcw.IInputPane2 _inputPane;
#endregion
#region Constructors
/// <summary>
/// Acquires the InputPane type from the winmd
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
static InputPane()
{
// We don't want to throw here - so wrap in try..catch
try
{
// If we cannot get a new activation factory, then we cannot support
// this platform. As such, null out the type to guard instantiations.
if (GetWinRtActivationFactory(forceInitialization: true) == null)
{
_isSupported = false;
}
_isSupported = true;
}
catch
{
_isSupported = false;
}
}
/// <summary>
/// Checks that the InputPane type was loaded and gets a new InputPane for the parent window.
/// </summary>
/// <exception cref="PlatformNotSupportedException"></exception>
private InputPane(IntPtr? hwnd)
{
if (!_isSupported)
{
throw new PlatformNotSupportedException();
}
try
{
if (hwnd.HasValue)
{
InputPaneRcw.IInputPaneInterop inputPaneInterop;
try
{
// Get the IActivationFactory and cast to IInputPaneInterop. The WinRT pattern for
// static factory types implements an activation factory that allows casting to one
// or more COM interfaces containing "static" methods to use as initialization. In
// the case of InputPane this is an interop class that contains an init function
// designed to take an HWND. This interface is cloaked and not part of the WinRT
// projections and therefore cannot be seen by reflecting on the type.
inputPaneInterop = GetWinRtActivationFactory() as InputPaneRcw.IInputPaneInterop;
}
catch (COMException)
{
// Do a fine grained catch here to detect the activation factory going stale.
// If this happens, we retry the cast querying a new factory. If this retry fails
// something else is going wrong, allow the error to be handled by the outer block.
inputPaneInterop = GetWinRtActivationFactory(forceInitialization: true) as InputPaneRcw.IInputPaneInterop;
}
_inputPane = inputPaneInterop?.GetForWindow(hwnd.Value, typeof(InputPaneRcw.IInputPane2).GUID);
}
}
catch (COMException)
{
// Something went wrong in acquiring/using one of the COM RCWs above.
// This is not a fatal error and just means that this particular attempt to initialize InputPane
// has failed or the platform does not support this access (this can be caught at the Type init
// but it is possible that is not the case).
}
if (_inputPane == null)
{
throw new PlatformNotSupportedException();
}
}
#endregion
#region Member Functions
/// <summary>
/// Wraps creation in a manner analagous to the WinRT interface to this class.
/// </summary>
/// <returns>A new InputPane</returns>
/// <exception cref="PlatformNotSupportedException"></exception>
internal static InputPane GetForWindow(HwndSource source)
{
return new InputPane(source?.Handle ?? null);
}
/// <summary>
/// Attempts to show the touch keyboard
/// </summary>
/// <returns>True if successful, false otherwise</returns>
internal bool TryShow()
{
bool result = false;
try
{
result = _inputPane?.TryShow() ?? false;
}
catch (COMException)
{
// It's possible that the IInputPane2 has gone stale for some reason
// in that case we should catch the exception here and simply return
// false indicating that the KB did not show.
}
return result;
}
/// <summary>
/// Attempts to hide the touch keyboard
/// </summary>
/// <returns>True if successful, false otherwise</returns>
internal bool TryHide()
{
bool result = false;
try
{
result = _inputPane?.TryHide() ?? false;
}
catch (COMException)
{
// It's possible that the IInputPane2 has gone stale for some reason
// in that case we should catch the exception here and simply return
// false indicating that the KB did not show.
}
return result;
}
/// <summary>
/// Creates, caches, and returns a WinRT activation factory for use with the InputPane runtime type.
/// </summary>
/// <param name="forceInitialization">If true, will create a new IActivationFactory. If false will
/// only create a new IActivationFactory if there is no valid cached instance available.</param>
/// <returns>An IActivationFactory of InputPane or null if it fails to instantiate.</returns>
private static object GetWinRtActivationFactory(bool forceInitialization = false)
{
if (forceInitialization || _winRtActivationFactory == null)
{
try
{
_winRtActivationFactory = InputPaneRcw.GetInputPaneActivationFactory();
}
catch (Exception e) when (e is TypeLoadException
|| e is FileNotFoundException
|| e is EntryPointNotFoundException
|| e is DllNotFoundException
|| e.HResult == NativeMethods.E_NOINTERFACE
|| e.HResult == NativeMethods.REGDB_E_CLASSNOTREG)
{
// Catch the set of exceptions that are considered activation exceptions,
// as well as exception with HResults that can be returned from DllGetActivationFactory when it fails.
// <see cref="https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.windowsruntime.windowsruntimemarshal.getactivationfactory(v=vs.110).aspx"/>
// On some Windows SKUs, notably ServerCore, a failing static dependency in InputPane seems to cause a
// FileNotFoundException during acquisition of the activation factory. We explicitly catch this exception
// here to alleviate this issue. This is not an ideal solution to the platform bug, but keeps WPF applications
// from being exposed to the issue.
// We also catch an EntryPointNotFoundException and DllNotFoundExceptions for when WinRT isn't supported on the platform.
_winRtActivationFactory = null;
}
}
return _winRtActivationFactory;
}
#endregion
#region IDisposable
bool _disposed = false;
~InputPane()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases the _inputPane RCW
/// </summary>
/// <param name="disposing">True if called from a Dispose() call, false when called from the finalizer</param>
private void Dispose(bool disposing)
{
if (!_disposed)
{
if (_inputPane != null)
{
try
{
// Release the input pane here
Marshal.ReleaseComObject(_inputPane);
}
catch
{
// Don't want to raise any exceptions in a finalizer, eat them here
}
_inputPane = null;
}
_disposed = true;
}
}
#endregion
}
}
}
|