From e9838960faba45d2ebd57134974254fec1bd67f3 Mon Sep 17 00:00:00 2001 From: David Sparer Date: Sun, 1 Sep 2019 12:30:11 -0500 Subject: [PATCH] improved ability to determine if external proc has focus --- .../Protocol/IntegratedProgramTests.cs | 4 +- .../UI/Forms/OptionsFormSetupAndTeardown.cs | 3 +- .../UI/Window/ConnectionTreeWindowTests.cs | 3 +- mRemoteV1/Connection/ConnectionInitiator.cs | 28 ++- .../Connection/ConnectionStartingEvent.cs | 18 ++ mRemoteV1/Connection/IConnectionInitiator.cs | 5 +- .../Protocol/ExternalProcessProtocolBase.cs | 57 +++-- mRemoteV1/Connection/Protocol/IFocusable.cs | 11 + mRemoteV1/Connection/Protocol/ProtocolBase.cs | 3 +- .../ExternalProcessAltTabFocusHelper.cs | 81 ------- .../ExternalProcessFocusHelper.cs | 199 ++++++++++++++++++ mRemoteV1/UI/Forms/frmMain.cs | 78 ++----- mRemoteV1/UI/Tabs/ConnectionTab.cs | 11 +- mRemoteV1/UI/Window/ConnectionWindow.cs | 2 +- mRemoteV1/mRemoteV1.csproj | 4 +- 15 files changed, 321 insertions(+), 186 deletions(-) create mode 100644 mRemoteV1/Connection/ConnectionStartingEvent.cs create mode 100644 mRemoteV1/Connection/Protocol/IFocusable.cs delete mode 100644 mRemoteV1/UI/FocusHelpers/ExternalProcessAltTabFocusHelper.cs create mode 100644 mRemoteV1/UI/FocusHelpers/ExternalProcessFocusHelper.cs diff --git a/mRemoteNGTests/Connection/Protocol/IntegratedProgramTests.cs b/mRemoteNGTests/Connection/Protocol/IntegratedProgramTests.cs index 254b5a018..046fa1f4c 100644 --- a/mRemoteNGTests/Connection/Protocol/IntegratedProgramTests.cs +++ b/mRemoteNGTests/Connection/Protocol/IntegratedProgramTests.cs @@ -12,7 +12,7 @@ namespace mRemoteNGTests.Connection.Protocol { public class IntegratedProgramTests { - private readonly ExternalTool _extTool = new ExternalTool(new ConnectionInitiator()) + private readonly ExternalTool _extTool = new ExternalTool(new ConnectionInitiator(new ProtocolFactory())) { DisplayName = "notepad", FileName = @"%windir%\system32\notepad.exe", @@ -50,7 +50,7 @@ namespace mRemoteNGTests.Connection.Protocol private InterfaceControl BuildInterfaceControl(string extAppName, ProtocolBase sut) { - var connectionWindow = new ConnectionWindow(new DockContent(), new ConnectionInitiator()); + var connectionWindow = new ConnectionWindow(new DockContent(), new ConnectionInitiator(new ProtocolFactory())); var connectionInfo = new ConnectionInfo {ExtApp = extAppName, Protocol = ProtocolType.IntApp}; return new InterfaceControl(connectionWindow, sut, connectionInfo); } diff --git a/mRemoteNGTests/UI/Forms/OptionsFormSetupAndTeardown.cs b/mRemoteNGTests/UI/Forms/OptionsFormSetupAndTeardown.cs index 2c7ac28d0..20f30e47d 100644 --- a/mRemoteNGTests/UI/Forms/OptionsFormSetupAndTeardown.cs +++ b/mRemoteNGTests/UI/Forms/OptionsFormSetupAndTeardown.cs @@ -1,4 +1,5 @@ using mRemoteNG.Connection; +using mRemoteNG.Connection.Protocol; using NUnit.Framework; using mRemoteNG.UI.Forms; @@ -16,7 +17,7 @@ namespace mRemoteNGTests.UI.Forms [SetUp] public void Setup() { - _optionsForm = new FrmOptions(new ConnectionInitiator()); + _optionsForm = new FrmOptions(new ConnectionInitiator(new ProtocolFactory())); _optionsForm.Show(); } diff --git a/mRemoteNGTests/UI/Window/ConnectionTreeWindowTests.cs b/mRemoteNGTests/UI/Window/ConnectionTreeWindowTests.cs index 320a0e00f..cfd9478fa 100644 --- a/mRemoteNGTests/UI/Window/ConnectionTreeWindowTests.cs +++ b/mRemoteNGTests/UI/Window/ConnectionTreeWindowTests.cs @@ -1,5 +1,6 @@ using System.Threading; using mRemoteNG.Connection; +using mRemoteNG.Connection.Protocol; using mRemoteNG.UI.Window; using NUnit.Framework; using WeifenLuo.WinFormsUI.Docking; @@ -14,7 +15,7 @@ namespace mRemoteNGTests.UI.Window [SetUp] public void Setup() { - _connectionTreeWindow = new ConnectionTreeWindow(new DockContent(), new ConnectionInitiator()); + _connectionTreeWindow = new ConnectionTreeWindow(new DockContent(), new ConnectionInitiator(new ProtocolFactory())); } [TearDown] diff --git a/mRemoteV1/Connection/ConnectionInitiator.cs b/mRemoteV1/Connection/ConnectionInitiator.cs index 4665c4fe8..7447a6123 100644 --- a/mRemoteV1/Connection/ConnectionInitiator.cs +++ b/mRemoteV1/Connection/ConnectionInitiator.cs @@ -16,12 +16,14 @@ namespace mRemoteNG.Connection { public class ConnectionInitiator : IConnectionInitiator { + private readonly ProtocolFactory _protocolFactory; private readonly List _activeConnections = new List(); public IEnumerable ActiveConnections => _activeConnections; - public ConnectionInitiator() + public ConnectionInitiator(ProtocolFactory protocolFactory) { + _protocolFactory = protocolFactory; } public bool SwitchToOpenConnection(ConnectionInfo connectionInfo) @@ -35,6 +37,8 @@ namespace mRemoteNG.Connection return true; } + + public void OpenConnection( ContainerInfo containerInfo, ConnectionInfo.Force force = ConnectionInfo.Force.None, @@ -77,8 +81,7 @@ namespace mRemoteNG.Connection return; } - var protocolFactory = new ProtocolFactory(); - var newProtocol = protocolFactory.CreateProtocol(connectionInfo); + var newProtocol = _protocolFactory.CreateProtocol(connectionInfo); var connectionPanel = SetConnectionPanel(connectionInfo, force); if (string.IsNullOrEmpty(connectionPanel)) return; @@ -96,6 +99,7 @@ namespace mRemoteNG.Connection return; } + OnConnectionStarting(newProtocol.InterfaceControl.Info, newProtocol); if (newProtocol.Connect() == false) { newProtocol.Close(); @@ -225,11 +229,11 @@ namespace mRemoteNG.Connection } Runtime.MessageCollector.AddMessage(msgClass, - string.Format( - Language.strProtocolEventDisconnected, - disconnectedMessage, - prot.InterfaceControl.Info.Hostname, - prot.InterfaceControl.Info.Protocol.ToString())); + string.Format( + Language.strProtocolEventDisconnected, + disconnectedMessage, + prot.InterfaceControl.Info.Hostname, + prot.InterfaceControl.Info.Protocol.ToString())); } catch (Exception ex) { @@ -271,7 +275,7 @@ namespace mRemoteNG.Connection } } - private static void Prot_Event_Connected(object sender) + private void Prot_Event_Connected(object sender) { var prot = (ProtocolBase)sender; Runtime.MessageCollector.AddMessage(MessageClass.InformationMsg, Language.strConnectionEventConnected, @@ -304,5 +308,11 @@ namespace mRemoteNG.Connection } #endregion + + public event EventHandler ConnectionStarting; + protected virtual void OnConnectionStarting(ConnectionInfo connectionInfo, ProtocolBase protocolBase) + { + ConnectionStarting?.Invoke(this, new ConnectionStartingEvent(connectionInfo, protocolBase)); + } } } \ No newline at end of file diff --git a/mRemoteV1/Connection/ConnectionStartingEvent.cs b/mRemoteV1/Connection/ConnectionStartingEvent.cs new file mode 100644 index 000000000..a2e76b6fc --- /dev/null +++ b/mRemoteV1/Connection/ConnectionStartingEvent.cs @@ -0,0 +1,18 @@ +using System; +using mRemoteNG.Connection.Protocol; + +namespace mRemoteNG.Connection +{ + [Serializable] + public class ConnectionStartingEvent : EventArgs + { + public ConnectionInfo ConnectionInfo { get; } + public ProtocolBase Protocol { get; } + + public ConnectionStartingEvent(ConnectionInfo connectionInfo, ProtocolBase protocol) + { + ConnectionInfo = connectionInfo; + Protocol = protocol; + } + } +} diff --git a/mRemoteV1/Connection/IConnectionInitiator.cs b/mRemoteV1/Connection/IConnectionInitiator.cs index b1105677a..abca657e3 100644 --- a/mRemoteV1/Connection/IConnectionInitiator.cs +++ b/mRemoteV1/Connection/IConnectionInitiator.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using mRemoteNG.Connection.Protocol; using mRemoteNG.Container; using mRemoteNG.UI.Window; @@ -20,5 +21,7 @@ namespace mRemoteNG.Connection ConnectionWindow conForm = null); bool SwitchToOpenConnection(ConnectionInfo connectionInfo); + + event EventHandler ConnectionStarting; } } \ No newline at end of file diff --git a/mRemoteV1/Connection/Protocol/ExternalProcessProtocolBase.cs b/mRemoteV1/Connection/Protocol/ExternalProcessProtocolBase.cs index 0b720e2d4..d2695371a 100644 --- a/mRemoteV1/Connection/Protocol/ExternalProcessProtocolBase.cs +++ b/mRemoteV1/Connection/Protocol/ExternalProcessProtocolBase.cs @@ -5,10 +5,11 @@ using mRemoteNG.Messages; namespace mRemoteNG.Connection.Protocol { - public class ExternalProcessProtocolBase : ProtocolBase + public abstract class ExternalProcessProtocolBase : ProtocolBase, IFocusable { private IntPtr _winEventHook; private NativeMethods.WinEventDelegate _setForegroundDelegate; + private bool _hasFocus; public override bool IsExternalProcess { get; } = true; @@ -16,6 +17,26 @@ namespace mRemoteNG.Connection.Protocol protected IntPtr ProcessHandle { get; set; } + public bool HasFocus + { + get => _hasFocus; + private set + { + if (_hasFocus == value) + return; + + _hasFocus = value; + Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, + string.Format("External protocol window set to {0}. name:'{1}', protocol:'{2}', pid:{3}, hwnd:{4}", + _hasFocus ? "foreground" : "background", + InterfaceControl.Info.Name, + InterfaceControl.Info.Protocol, + ProtocolProcess.Id, + ProcessHandle)); + OnFocusChanged(); + } + } + public int ThreadId => (int)NativeMethods.GetWindowThreadProcessId(ProcessHandle, IntPtr.Zero); @@ -27,7 +48,7 @@ namespace mRemoteNG.Connection.Protocol NativeMethods.EVENT_SYSTEM_FOREGROUND, IntPtr.Zero, _setForegroundDelegate, - Convert.ToUInt32(ProtocolProcess.Id), + /*Convert.ToUInt32(ProtocolProcess.Id)*/0, 0, NativeMethods.WINEVENT_OUTOFCONTEXT); @@ -63,16 +84,15 @@ namespace mRemoteNG.Connection.Protocol var setForegroundSuccessful = NativeMethods.SetForegroundWindow(ProcessHandle); - var logMsg = setForegroundSuccessful - ? "External protocol window set to foreground. " - : "Failed to set external protocol window to foreground. "; - - Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, - logMsg + - $"name:'{InterfaceControl.Info.Name}', " + - $"protocol:'{InterfaceControl.Info.Protocol}', " + - $"pid:{ProtocolProcess.Id}, " + - $"hwnd:{ProcessHandle}"); + if (!setForegroundSuccessful) + { + Runtime.MessageCollector.AddMessage(MessageClass.WarningMsg, + "Failed to set external protocol window to foreground. " + + $"name:'{InterfaceControl.Info.Name}', " + + $"protocol:'{InterfaceControl.Info.Protocol}', " + + $"pid:{ProtocolProcess.Id}, " + + $"hwnd:{ProcessHandle}"); + } } /// @@ -88,14 +108,13 @@ namespace mRemoteNG.Connection.Protocol /// void OnWinEventSetForeground(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) { - if (hwnd != ProtocolProcess.MainWindowHandle) - return; + HasFocus = hwnd == ProcessHandle; + } - //Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, - // "Exernal protocol window set to foreground. " + - // $"protocol:{InterfaceControl.Info.Protocol} " + - // $"pid:{ProtocolProcess.Id}, " + - // $"hwnd:{ProtocolProcess.MainWindowHandle}"); + public event EventHandler FocusChanged; + protected virtual void OnFocusChanged() + { + FocusChanged?.Invoke(this, EventArgs.Empty); } } } diff --git a/mRemoteV1/Connection/Protocol/IFocusable.cs b/mRemoteV1/Connection/Protocol/IFocusable.cs new file mode 100644 index 000000000..e50ccfa9c --- /dev/null +++ b/mRemoteV1/Connection/Protocol/IFocusable.cs @@ -0,0 +1,11 @@ +using System; + +namespace mRemoteNG.Connection.Protocol +{ + public interface IFocusable + { + bool HasFocus { get; } + void Focus(); + event EventHandler FocusChanged; + } +} diff --git a/mRemoteV1/Connection/Protocol/ProtocolBase.cs b/mRemoteV1/Connection/Protocol/ProtocolBase.cs index b9e7ba6db..13f2cc361 100644 --- a/mRemoteV1/Connection/Protocol/ProtocolBase.cs +++ b/mRemoteV1/Connection/Protocol/ProtocolBase.cs @@ -341,8 +341,7 @@ namespace mRemoteNG.Connection.Protocol { if (!disposing) return; - if(tmrReconnect != null) - tmrReconnect.Dispose(); + tmrReconnect?.Dispose(); } public void Dispose() diff --git a/mRemoteV1/UI/FocusHelpers/ExternalProcessAltTabFocusHelper.cs b/mRemoteV1/UI/FocusHelpers/ExternalProcessAltTabFocusHelper.cs deleted file mode 100644 index e38c5764b..000000000 --- a/mRemoteV1/UI/FocusHelpers/ExternalProcessAltTabFocusHelper.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using mRemoteNG.App; -using mRemoteNG.Messages; -using System.Windows.Forms; - -namespace mRemoteNG.UI.FocusHelpers -{ - public class ExternalProcessAltTabFocusHelper : IDisposable - { - private readonly SystemKeyboardHook _keyboardHook; - private bool _currentlyFixingAltTab; - - /// - /// TRUE if any part of mrng has focus - the main window or child processes - /// - public bool MrngFocused { get; set; } - public bool ChildProcessHeldLastFocus { get; set; } - public Action FocusMainWindowAction { get; } - public Action FocusConnectionAction { get; } - - public ExternalProcessAltTabFocusHelper(Action focusMainWindowAction, Action focusConnectionAction) - { - FocusMainWindowAction = focusMainWindowAction; - FocusConnectionAction = focusConnectionAction; - _keyboardHook = new SystemKeyboardHook(KeyboardHookCallback); - } - - public int KeyboardHookCallback(int msg, NativeMethods.KBDLLHOOKSTRUCT kbd) - { - var key = (Keys)kbd.vkCode; - if (key.HasFlag(Keys.Tab) && kbd.flags.HasFlag(NativeMethods.KBDLLHOOKSTRUCTFlags.LLKHF_ALTDOWN)) - { - if (msg == NativeMethods.WM_SYSKEYDOWN || msg == NativeMethods.WM_KEYDOWN) - { - Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, $"ALT-TAB PRESSED (CHILDPROC_FOCUSED={ChildProcessHeldLastFocus}, MRNG_FOCUSED={MrngFocused}, CURR_FIXING={_currentlyFixingAltTab})"); - if (ChildProcessHeldLastFocus && MrngFocused && !_currentlyFixingAltTab) - { - Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, "FIXING ALT-TAB FOR EXTAPP"); - _currentlyFixingAltTab = true; - - // simulate an extra TAB key press. This skips focus of the mrng main window. - NativeMethods.keybd_event((byte)Keys.Tab, 0, (uint)NativeMethods.KEYEVENTF.KEYUP, 0); - NativeMethods.keybd_event((byte)Keys.Tab, 0, 0, 0); - - // WndProc will never get an event when we switch from a child proc to a completely different program since the main mrng window never had focus to begin with. - // Assume mrng as a whole will lose focus, even though the user could choose to retain focus on us. When Alt-tab completes, the mrng main window will - // receive the focus event and we will handle the child process focusing as necessary. - MrngFocused = false; - _currentlyFixingAltTab = false; - } - } - } - - // alt + right-shift - if (key.HasFlag(Keys.RShiftKey) && kbd.flags.HasFlag(NativeMethods.KBDLLHOOKSTRUCTFlags.LLKHF_ALTDOWN)) - { - if (msg != NativeMethods.WM_SYSKEYUP && msg != NativeMethods.WM_KEYUP) - return 0; - - if (!MrngFocused) - return 0; - - if (ChildProcessHeldLastFocus) - { - FocusMainWindowAction(); - return 1; - } - - FocusConnectionAction(); - return 1; - } - - return 0; - } - - public void Dispose() - { - _keyboardHook?.Dispose(); - } - } -} diff --git a/mRemoteV1/UI/FocusHelpers/ExternalProcessFocusHelper.cs b/mRemoteV1/UI/FocusHelpers/ExternalProcessFocusHelper.cs new file mode 100644 index 000000000..3ea1614f7 --- /dev/null +++ b/mRemoteV1/UI/FocusHelpers/ExternalProcessFocusHelper.cs @@ -0,0 +1,199 @@ +using System; +using System.Windows.Forms; +using mRemoteNG.App; +using mRemoteNG.Connection; +using mRemoteNG.Connection.Protocol; +using mRemoteNG.Messages; +using mRemoteNG.UI.Tabs; + +namespace mRemoteNG.UI.FocusHelpers +{ + public class ExternalProcessFocusHelper : IDisposable + { + private readonly IntPtr _mainWindowHandle; + private int _extFocusCount; + private readonly SystemKeyboardHook _keyboardHook; + private bool _currentlyFixingAltTab; + private bool _childProcessHeldLastFocus; + private bool _mainWindowFocused; + private bool _connectionReleasingFocus; + + /// + /// TRUE if any part of mrng has focus - the main window or child processes + /// + public bool MrngFocused => MainWindowFocused || ChildProcessFocused; + + public bool FixingMainWindowFocus { get; private set; } + + public bool MainWindowFocused + { + get => _mainWindowFocused; + set + { + if (_mainWindowFocused == value) + return; + + _mainWindowFocused = value; + // main window is receiving focus + if (ChildProcessHeldLastFocus && _mainWindowFocused && !_connectionReleasingFocus) + ActivateConnection(); + } + } + + public bool ChildProcessFocused => _extFocusCount > 0; + + public bool ChildProcessHeldLastFocus + { + get => _childProcessHeldLastFocus; + private set + { + _childProcessHeldLastFocus = value; + Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, $"_childProcessHeldLastFocus={_childProcessHeldLastFocus}"); + } + } + + public ExternalProcessFocusHelper( + IConnectionInitiator connectionInitiator, + IntPtr mainWindowHandle) + { + connectionInitiator.ConnectionStarting += ConnectionInitiatorOnConnectionStarting; + _mainWindowHandle = mainWindowHandle; + _keyboardHook = new SystemKeyboardHook(KeyboardHookCallback); + } + + private void ConnectionInitiatorOnConnectionStarting(object sender, ConnectionStartingEvent e) + { + if (!(e.Protocol is ExternalProcessProtocolBase extProc)) + return; + + extProc.FocusChanged += ExtProcOnFocusChanged; + extProc.Disconnected += ProtocolOnDisconnected; + } + + private void ExtProcOnFocusChanged(object sender, EventArgs e) + { + if (!(sender is IFocusable extProc)) + return; + + if (extProc.HasFocus) + ExternalWindowFocused(); + else + ExternalWindowDefocused(); + } + + private void ProtocolOnDisconnected(object sender, string disconnectedmessage, int? reasoncode) + { + if (!(sender is ExternalProcessProtocolBase prot)) + return; + + prot.FocusChanged -= ExtProcOnFocusChanged; + prot.Disconnected -= ProtocolOnDisconnected; + } + + public void ActivateConnection() + { + Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, "Performing special connection focus logic"); + + //var cw = pnlDock.ActiveDocument as ConnectionWindow; + //var dp = cw?.ActiveControl as DockPane; + + //if (!(dp?.ActiveContent is ConnectionTab tab)) + //{ + // Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, "Active content is not a tab. We won't focus a specific connection."); + // return; + //} + + //var ifc = InterfaceControl.FindInterfaceControl(tab); + var tab = TabHelper.Instance.CurrentTab; + if (tab == null) + return; + + var ifc = tab.InterfaceControl; + if (ifc == null) + { + Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, + $"InterfaceControl for tab '{tab.Name}' was not found. We won't focus that connection."); + return; + } + + //FixingMainWindowFocus = true; + //_focusMainWindowAction(); + //FixingMainWindowFocus = false; + + ifc.Protocol.Focus(); + //var conFormWindow = ifc.FindForm(); + //((ConnectionTab) conFormWindow)?.RefreshInterfaceController(); + } + + public int KeyboardHookCallback(int msg, NativeMethods.KBDLLHOOKSTRUCT kbd) + { + var key = (Keys)kbd.vkCode; + // Alt + Tab + if (key.HasFlag(Keys.Tab) && kbd.flags.HasFlag(NativeMethods.KBDLLHOOKSTRUCTFlags.LLKHF_ALTDOWN)) + { + if (msg == NativeMethods.WM_SYSKEYDOWN || msg == NativeMethods.WM_KEYDOWN) + { + Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, $"ALT-TAB PRESSED (CHILDPROC_FOCUSED={ChildProcessFocused}, CHILDPROC_LAST_FOCUSED={ChildProcessHeldLastFocus}, MRNG_FOCUSED={MrngFocused}, CURR_FIXING={_currentlyFixingAltTab})"); + if (ChildProcessHeldLastFocus && MrngFocused && !_currentlyFixingAltTab) + { + FixExternalAppAltTab(); + } + } + } + + // Alt + ` + if (key.HasFlag(Keys.Oem3) && kbd.flags.HasFlag(NativeMethods.KBDLLHOOKSTRUCTFlags.LLKHF_ALTDOWN)) + { + if (msg != NativeMethods.WM_SYSKEYUP && msg != NativeMethods.WM_KEYUP) + return 0; + + if (ChildProcessFocused) + { + _connectionReleasingFocus = true; + var focused = NativeMethods.SetForegroundWindow(_mainWindowHandle); + _connectionReleasingFocus = false; + return 1; + } + + if (!MainWindowFocused) + return 0; + ActivateConnection(); + return 1; + } + + return 0; + } + + private void FixExternalAppAltTab() + { + Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, "FIXING ALT-TAB FOR EXTAPP"); + _currentlyFixingAltTab = true; + + // simulate an extra TAB key press. This skips focus of the mrng main window. + NativeMethods.keybd_event((byte) Keys.Tab, 0, (uint) NativeMethods.KEYEVENTF.KEYUP, 0); + NativeMethods.keybd_event((byte) Keys.Tab, 0, 0, 0); + + // WndProc will never get an event when we switch from a child proc to a completely different program since the main mrng window never had focus to begin with. + // Assume mrng as a whole will lose focus, even though the user could choose to retain focus on us. When Alt-tab completes, the mrng main window will + // receive the focus event and we will handle the child process focusing as necessary. + MainWindowFocused = false; + _currentlyFixingAltTab = false; + } + + public void Dispose() + { + _keyboardHook?.Dispose(); + } + + private void ExternalWindowFocused() + { + _extFocusCount++; + } + + private void ExternalWindowDefocused() + { + _extFocusCount--; + ChildProcessHeldLastFocus = !MainWindowFocused && !ChildProcessFocused; + } + } +} diff --git a/mRemoteV1/UI/Forms/frmMain.cs b/mRemoteV1/UI/Forms/frmMain.cs index 1e2f7fa0a..5e5c8a566 100644 --- a/mRemoteV1/UI/Forms/frmMain.cs +++ b/mRemoteV1/UI/Forms/frmMain.cs @@ -53,8 +53,8 @@ namespace mRemoteNG.UI.Forms private readonly IList _messageWriters = new List(); private readonly ThemeManager _themeManager; private readonly FileBackupPruner _backupPruner = new FileBackupPruner(); - private readonly IConnectionInitiator _connectionInitiator = new ConnectionInitiator(); - private readonly ExternalProcessAltTabFocusHelper _altTabFocusHelper; + private readonly IConnectionInitiator _connectionInitiator; + private readonly ExternalProcessFocusHelper _focusHelper; internal FullscreenHandler Fullscreen { get; set; } @@ -73,7 +73,11 @@ namespace mRemoteNG.UI.Forms ApplyTheme(); _screenSystemMenu = new ScreenSelectionSystemMenu(this); - _altTabFocusHelper = new ExternalProcessAltTabFocusHelper(() => Focus(), ActivateConnection); + var protocolFactory = new ProtocolFactory(); + _connectionInitiator = new ConnectionInitiator(protocolFactory); + + Debug.Assert(IsHandleCreated, "Expected main window handle to be created by now"); + _focusHelper = new ExternalProcessFocusHelper(_connectionInitiator, Handle); } #region Properties @@ -218,12 +222,12 @@ namespace mRemoteNG.UI.Forms private void OnTabClicked(object sender, EventArgs e) { - ActivateConnection(); + _focusHelper.ActivateConnection(); } private void OnActiveConnectionTabChanged(object sender, EventArgs e) { - ActivateConnection(); + _focusHelper.ActivateConnection(); } private void ApplyLanguage() @@ -383,7 +387,7 @@ namespace mRemoteNG.UI.Forms private void frmMain_FormClosing(object sender, FormClosingEventArgs e) { - _altTabFocusHelper?.Dispose(); + _focusHelper?.Dispose(); if (!(Runtime.WindowList == null || Runtime.WindowList.Count == 0)) { @@ -483,7 +487,7 @@ namespace mRemoteNG.UI.Forms _inSizeMove = false; Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, "End app window move/resize"); // This handles activations from clicks that started a size/move operation - ActivateConnection(); + _focusHelper.ActivateConnection(); } @@ -510,28 +514,19 @@ namespace mRemoteNG.UI.Forms Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, $"Clicked control: {controlThatWasClicked2}"); break; case NativeMethods.WM_ACTIVATEAPP: + if (_focusHelper.FixingMainWindowFocus) + break; + if (m.WParam.ToInt32() == 0) // mRemoteNG is being deactivated { - var threadWhichIsActivating = m.LParam.ToInt32(); - _altTabFocusHelper.ChildProcessHeldLastFocus = _connectionInitiator - .ActiveConnections - .OfType() - .Any(proc => proc.ThreadId == threadWhichIsActivating); - _inMouseActivate = false; - _altTabFocusHelper.MrngFocused = _altTabFocusHelper.ChildProcessHeldLastFocus; - Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, $"mRemoteNG main window lost focus (_childProcessHeldLastFocus={_altTabFocusHelper.ChildProcessHeldLastFocus})"); + _focusHelper.MainWindowFocused = false; + Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, $"mRemoteNG main window lost focus (_childProcessHeldLastFocus={_focusHelper.ChildProcessHeldLastFocus})"); break; } - if (_altTabFocusHelper.ChildProcessHeldLastFocus && !_altTabFocusHelper.MrngFocused) - { - ActivateConnection(); - } - - _altTabFocusHelper.ChildProcessHeldLastFocus = false; - _altTabFocusHelper.MrngFocused = true; - Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, $"mRemoteNG main window received focus (_childProcessHeldLastFocus={_altTabFocusHelper.ChildProcessHeldLastFocus})"); + _focusHelper.MainWindowFocused = true; + Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, $"mRemoteNG main window received focus (_childProcessHeldLastFocus={_focusHelper.ChildProcessHeldLastFocus})"); //var candidateTabToFocus = FromChildHandle(NativeMethods.WindowFromPoint(MousePosition)) // ?? GetChildAtPoint(MousePosition); @@ -583,14 +578,10 @@ namespace mRemoteNG.UI.Forms break; case NativeMethods.WM_NCACTIVATE: - //if (m.WParam.ToInt32() == 1) - // break; - //// Never allow the mRemoteNG window to display itself as inactive. By doing this, //// we ensure focus events can propagate to child connection windows NativeMethods.DefWindowProc(Handle, Convert.ToUInt32(m.Msg), (IntPtr)1, m.LParam); m.Result = (IntPtr)1; - Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, "Fixed main app NCACTIVATE"); return; case NativeMethods.WM_WINDOWPOSCHANGED: // Ignore this message if the window wasn't activated @@ -603,7 +594,7 @@ namespace mRemoteNG.UI.Forms if (!_inMouseActivate && !_inSizeMove) { Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, "WM_WINDOWPOSCHANGED DONE"); - ActivateConnection(); + _focusHelper.ActivateConnection(); } } @@ -643,38 +634,9 @@ namespace mRemoteNG.UI.Forms clientMousePosition.Y = temp_wHigh; } - private void ActivateConnection() - { - Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, "Performing special connection focus logic"); - //var cw = pnlDock.ActiveDocument as ConnectionWindow; - //var dp = cw?.ActiveControl as DockPane; - - //if (!(dp?.ActiveContent is ConnectionTab tab)) - //{ - // Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, "Active content is not a tab. We won't focus a specific connection."); - // return; - //} - - //var ifc = InterfaceControl.FindInterfaceControl(tab); - var tab = TabHelper.Instance.CurrentTab; - if (tab == null) - return; - - var ifc = tab.InterfaceControl; - if (ifc == null) - { - Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, $"InterfaceControl for tab '{tab.Name}' was not found. We won't focus that connection."); - return; - } - - ifc.Protocol.Focus(); - var conFormWindow = ifc.FindForm(); - ((ConnectionTab)conFormWindow)?.RefreshInterfaceController(); - } - private void pnlDock_ActiveDocumentChanged(object sender, EventArgs e) { - //ActivateConnection(); + //_focusHelper.ActivateConnection(); } internal void UpdateWindowTitle() diff --git a/mRemoteV1/UI/Tabs/ConnectionTab.cs b/mRemoteV1/UI/Tabs/ConnectionTab.cs index 731391132..0d86fc21d 100644 --- a/mRemoteV1/UI/Tabs/ConnectionTab.cs +++ b/mRemoteV1/UI/Tabs/ConnectionTab.cs @@ -48,16 +48,7 @@ namespace mRemoteNG.UI.Tabs private void ConnectionTab_GotFocus(object sender, EventArgs e) { Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, $"Tab received focused: '{TabText}'"); - //TabHelper.Instance.CurrentTab = this; - //if (TabHelper.Instance.FocusConnection) - //{ - // Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, $"Focusing connection in tab: '{TabText}'"); - // InterfaceControl?.Protocol.Focus(); - //} - //else - //{ - // Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, "Dont focus connection"); - //} + TabHelper.Instance.CurrentTab = this; } protected override void OnFormClosing(FormClosingEventArgs e) diff --git a/mRemoteV1/UI/Window/ConnectionWindow.cs b/mRemoteV1/UI/Window/ConnectionWindow.cs index d7a673378..e28d98b26 100644 --- a/mRemoteV1/UI/Window/ConnectionWindow.cs +++ b/mRemoteV1/UI/Window/ConnectionWindow.cs @@ -134,7 +134,7 @@ namespace mRemoteNG.UI.Window var conTab = new ConnectionTab { - Tag = connectionInfo, + Tag = connectionInfo, // BUG: the Tag gets set to an InterfaceControl later on. Is this right? DockAreas = DockAreas.Document | DockAreas.Float, Icon = ConnectionIcon.FromString(connectionInfo.Icon), TabText = titleText, diff --git a/mRemoteV1/mRemoteV1.csproj b/mRemoteV1/mRemoteV1.csproj index 531ec0b67..ca7e38a0e 100644 --- a/mRemoteV1/mRemoteV1.csproj +++ b/mRemoteV1/mRemoteV1.csproj @@ -252,6 +252,7 @@ + @@ -260,6 +261,7 @@ + @@ -538,7 +540,7 @@ - +