using System.Runtime.InteropServices; using Avalonia.Controls; namespace DodoSSH.Client.App.Views; /// /// Moves the window's keyboard focus back out of the terminal's native child window. /// /// /// /// The two directions across this boundary are not symmetric, and only one of them needs anything /// like this. is Focusable by default and its OnGotFocus /// calls the adapter's Focus(), which on Windows is /// ICoreWebView2Controller::MoveFocus(PROGRAMMATIC) — so an ordinary Avalonia /// Focus() call on the control really does hand Win32 focus to WebView2. Measured: focus /// lands on the Chrome_WidgetWin_1 child and the page reports /// document.hasFocus() == true. Nothing custom is needed to give the terminal the keyboard. /// /// /// Coming back is where the package stops helping. OnLostFocus calls the adapter's /// ResignFocus(), and on Windows that method is empty — so moving Avalonia's focus to /// another control leaves Win32 focus on WebView2. Measured: after textBox.Focus() the /// focused element is the TextBox while GetFocus() is still the WebView2 child and the /// page still reports focus, which is a text box that shows a caret and silently receives nothing. /// Window.Activate() and Window.Focus() were both measured and neither recovers it. /// A real mouse click does, because Avalonia's window sets focus on pointer input — which is why the /// symptom is invisible to anyone who clicks before typing. /// /// /// So the hand-back has to be the Win32 call. This is safe to run at any time, including while the /// application is in the background: both windows live on this thread's message queue, and /// SetFocus confined to one queue changes which window receives keys without activating /// anything or taking focus from another application. /// /// internal static class NativeKeyboardFocus { /// /// Returns keyboard focus to 's own window, so that Avalonia's focused /// element receives keystrokes again. /// /// /// Focus an element afterwards. Collapsing the terminal leaves Avalonia with no focused element /// at all, so handing the keyboard back without also choosing a target means keystrokes reach /// the window and stop there. /// internal static void ReturnTo(Window window) { // Only Windows hosts the WebView in a child window today. Elsewhere this is either // unnecessary or wrong, and doing nothing is the honest option until a spike says otherwise // — see docs/platform-flags.md on the unproven Linux backend. if (!OperatingSystem.IsWindows()) { return; } if (window.TryGetPlatformHandle()?.Handle is { } handle && handle != IntPtr.Zero) { SetFocus(handle); } } /// /// DllImport rather than the source-generated LibraryImport, which requires /// AllowUnsafeBlocks for the whole project. The signature is blittable, so there is no /// marshalling stub for the generator to improve on, and turning unsafe code on across a client /// that handles key material to save nothing would be a poor trade. Hence the SYSLIB1054 /// suppression rather than a fix. /// #pragma warning disable SYSLIB1054 [DllImport("user32.dll")] private static extern IntPtr SetFocus(IntPtr window); #pragma warning restore SYSLIB1054 }