diff --git a/mRemoteV1/UI/FocusHelpers/ExternalProcessAltTabFocusHelper.cs b/mRemoteV1/UI/FocusHelpers/ExternalProcessAltTabFocusHelper.cs new file mode 100644 index 000000000..e38c5764b --- /dev/null +++ b/mRemoteV1/UI/FocusHelpers/ExternalProcessAltTabFocusHelper.cs @@ -0,0 +1,81 @@ +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/Forms/frmMain.cs b/mRemoteV1/UI/Forms/frmMain.cs index fdeadf3e0..1e2f7fa0a 100644 --- a/mRemoteV1/UI/Forms/frmMain.cs +++ b/mRemoteV1/UI/Forms/frmMain.cs @@ -28,6 +28,7 @@ using System.Runtime.InteropServices; using System.Text; using System.Windows.Forms; using mRemoteNG.Connection.Protocol; +using mRemoteNG.UI.FocusHelpers; using mRemoteNG.UI.Panels; using WeifenLuo.WinFormsUI.Docking; using Message = System.Windows.Forms.Message; @@ -53,14 +54,7 @@ namespace mRemoteNG.UI.Forms private readonly ThemeManager _themeManager; private readonly FileBackupPruner _backupPruner = new FileBackupPruner(); private readonly IConnectionInitiator _connectionInitiator = new ConnectionInitiator(); - private readonly SystemKeyboardHook _keyboardHook; - private bool _childProcessHeldLastFocus; - private bool _currentlyFixingAltTab; - - /// - /// TRUE if any part of mrng has focus - the main window or child processes - /// - private bool _mrngFocused; + private readonly ExternalProcessAltTabFocusHelper _altTabFocusHelper; internal FullscreenHandler Fullscreen { get; set; } @@ -79,7 +73,7 @@ namespace mRemoteNG.UI.Forms ApplyTheme(); _screenSystemMenu = new ScreenSelectionSystemMenu(this); - _keyboardHook = new SystemKeyboardHook(KeyboardHookCallback); + _altTabFocusHelper = new ExternalProcessAltTabFocusHelper(() => Focus(), ActivateConnection); } #region Properties @@ -389,7 +383,7 @@ namespace mRemoteNG.UI.Forms private void frmMain_FormClosing(object sender, FormClosingEventArgs e) { - _keyboardHook?.Dispose(); + _altTabFocusHelper?.Dispose(); if (!(Runtime.WindowList == null || Runtime.WindowList.Count == 0)) { @@ -519,25 +513,25 @@ namespace mRemoteNG.UI.Forms if (m.WParam.ToInt32() == 0) // mRemoteNG is being deactivated { var threadWhichIsActivating = m.LParam.ToInt32(); - _childProcessHeldLastFocus = _connectionInitiator + _altTabFocusHelper.ChildProcessHeldLastFocus = _connectionInitiator .ActiveConnections .OfType() .Any(proc => proc.ThreadId == threadWhichIsActivating); _inMouseActivate = false; - _mrngFocused = _childProcessHeldLastFocus; - Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, $"mRemoteNG main window lost focus (_childProcessHeldLastFocus={_childProcessHeldLastFocus})"); + _altTabFocusHelper.MrngFocused = _altTabFocusHelper.ChildProcessHeldLastFocus; + Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, $"mRemoteNG main window lost focus (_childProcessHeldLastFocus={_altTabFocusHelper.ChildProcessHeldLastFocus})"); break; } - if (_childProcessHeldLastFocus && !_mrngFocused) + if (_altTabFocusHelper.ChildProcessHeldLastFocus && !_altTabFocusHelper.MrngFocused) { ActivateConnection(); } - _childProcessHeldLastFocus = false; - _mrngFocused = true; - Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, $"mRemoteNG main window received focus (_childProcessHeldLastFocus={_childProcessHeldLastFocus})"); + _altTabFocusHelper.ChildProcessHeldLastFocus = false; + _altTabFocusHelper.MrngFocused = true; + Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, $"mRemoteNG main window received focus (_childProcessHeldLastFocus={_altTabFocusHelper.ChildProcessHeldLastFocus})"); //var candidateTabToFocus = FromChildHandle(NativeMethods.WindowFromPoint(MousePosition)) // ?? GetChildAtPoint(MousePosition); @@ -638,55 +632,6 @@ namespace mRemoteNG.UI.Forms base.WndProc(ref m); } - 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 (CPF={_childProcessHeldLastFocus}, MRNGF={_mrngFocused}, IMA={_inMouseActivate}, CFAT={_currentlyFixingAltTab})"); - if (_childProcessHeldLastFocus && _mrngFocused && !_inMouseActivate && !_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) - { - Focus(); // focus main window - return 1; - } - - // focus connection - ActivateConnection(); - return 1; - } - - return 0; - } - private void SimulateClick(Control control) { var clientMousePosition = control.PointToClient(MousePosition); diff --git a/mRemoteV1/mRemoteV1.csproj b/mRemoteV1/mRemoteV1.csproj index af39dc198..531ec0b67 100644 --- a/mRemoteV1/mRemoteV1.csproj +++ b/mRemoteV1/mRemoteV1.csproj @@ -538,6 +538,7 @@ +