From 26d9e3c2ff3a2aea516731d9c4f9b80242cd2d44 Mon Sep 17 00:00:00 2001 From: David Sparer Date: Sun, 25 Aug 2019 10:39:27 -0500 Subject: [PATCH] debug putty focus issues --- mRemoteV1/App/NativeMethods.cs | 177 ++++++++- mRemoteV1/Connection/InterfaceControl.cs | 14 +- .../Protocol/ExternalProcessProtocolBase.cs | 101 +++++ .../Connection/Protocol/IntegratedProgram.cs | 4 +- mRemoteV1/Connection/Protocol/ProtocolBase.cs | 6 + mRemoteV1/Connection/Protocol/PuttyBase.cs | 243 ++++++------ .../Connection/Protocol/RDP/RdpProtocol6.cs | 49 ++- .../DebugConsoleMessageWriter.cs | 2 +- mRemoteV1/Themes/ThemeInfo.cs | 2 +- mRemoteV1/Tools/MultiSSHController.cs | 2 +- mRemoteV1/UI/Forms/frmMain.Designer.cs | 364 +++++++++--------- mRemoteV1/UI/Forms/frmMain.cs | 200 +++++++--- mRemoteV1/UI/Tabs/ConnectionTab.cs | 27 +- mRemoteV1/UI/Tabs/DockPaneStripNG.cs | 9 + mRemoteV1/UI/Tabs/TabHelper.cs | 45 ++- mRemoteV1/UI/Window/ConnectionWindow.cs | 2 + mRemoteV1/mRemoteV1.csproj | 4 + 17 files changed, 890 insertions(+), 361 deletions(-) create mode 100644 mRemoteV1/Connection/Protocol/ExternalProcessProtocolBase.cs diff --git a/mRemoteV1/App/NativeMethods.cs b/mRemoteV1/App/NativeMethods.cs index 0e15954b..293379b0 100644 --- a/mRemoteV1/App/NativeMethods.cs +++ b/mRemoteV1/App/NativeMethods.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Drawing; using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; @@ -41,6 +41,12 @@ namespace mRemoteNG.App [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)] internal static extern int IsIconic(IntPtr hWnd); + + internal delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool EnumChildWindows(IntPtr hwndParent, EnumWindowsProc lpEnumFunc, IntPtr lParam); + [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)] internal static extern bool MoveWindow(IntPtr hWnd, int x, int y, int cx, int cy, bool repaint); @@ -74,6 +80,9 @@ namespace mRemoteNG.App [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)] internal static extern bool SetForegroundWindow(IntPtr hWnd); + [DllImport("user32.dll", SetLastError = true)] + internal static extern IntPtr SetFocus(IntPtr hWnd); + [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)] internal static extern bool SetMenuItemBitmaps(IntPtr hMenu, int uPosition, @@ -109,6 +118,16 @@ namespace mRemoteNG.App [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] internal static extern bool CloseHandle(IntPtr handle); + [DllImport("user32.dll")] + internal static extern IntPtr DefWindowProc(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam); + + [DllImport("user32.dll", SetLastError = true)] + internal static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); + + // When you don't want the ProcessId, use this overload and pass IntPtr.Zero for the second parameter + [DllImport("user32.dll")] + internal static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr processId); + #endregion #region Structures @@ -408,6 +427,11 @@ namespace mRemoteNG.App /// public const int WM_WINDOWPOSCHANGED = 0x47; + /// + /// Sent to a window when its nonclient area needs to be changed to indicate an active or inactive state. + /// + public const int WM_NCACTIVATE = 0x86; + /// /// Posted to the window with the keyboard focus when a nonsystem key is pressed. A nonsystem key is a key that is pressed when the ALT key is not pressed. /// @@ -423,6 +447,25 @@ namespace mRemoteNG.App /// public const int WM_CHAR = 0x102; + /// + /// Posted to the window with the keyboard focus when the user presses the F10 key + /// (which activates the menu bar) or holds down the ALT key and then presses another + /// key. It also occurs when no window currently has the keyboard focus; in this case, + /// the WM_SYSKEYDOWN message is sent to the active window. The window that receives the + /// message can distinguish between these two contexts by checking the context code in + /// the lParam parameter. + /// + public const int WM_SYSKEYDOWN = 0x104; + + /// + /// Posted to the window with the keyboard focus when the user releases a key that was + /// pressed while the ALT key was held down. It also occurs when no window currently + /// has the keyboard focus; in this case, the WM_SYSKEYUP message is sent to the active + /// window. The window that receives the message can distinguish between these two + /// contexts by checking the context code in the lParam parameter. + /// + public const int WM_SYSKEYUP = 0x105; + /// /// Sent when the user selects a command item from a menu, when a control sends a notification message to its parent window, or when an accelerator keystroke is translated. /// @@ -542,6 +585,138 @@ namespace mRemoteNG.App #endregion + #region WinEvent + /// + /// An application-defined callback (or hook) function that the system calls in + /// response to events generated by an accessible object. The hook function processes + /// the event notifications as required. Clients install the hook function and + /// request specific types of event notifications by calling SetWinEventHook. + /// + /// + /// Handle to an event hook function. This value is returned by + /// SetWinEventHook when the hook function is installed and + /// is specific to each instance of the hook function. + /// + /// + /// Specifies the event that occurred. This value is one of the event constants. + /// + /// + /// Handle to the window that generates the event, or NULL if no window is + /// associated with the event. For example, the mouse pointer is not associated + /// with a window. + /// + /// + /// Identifies the object associated with the event. This is one of the object + /// identifiers or a custom object ID. + /// + /// + /// Identifies whether the event was triggered by an object or a child element + /// of the object. If this value is CHILDID_SELF, the event was triggered by + /// the object; otherwise, this value is the child ID of the element that + /// triggered the event. + /// + /// + /// + /// Specifies the time, in milliseconds, that the event was generated. + /// + public delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, + int idObject, int idChild, uint dwEventThread, uint dwmsEventTime); + + /// + /// Sets an event hook function for a range of events. + /// + /// + /// Specifies the event constant for the lowest event value in the range of + /// events that are handled by the hook function. This parameter can be set + /// to EVENT_MIN to indicate the lowest possible event value. + /// + /// + /// Specifies the event constant for the highest event value in the range + /// of events that are handled by the hook function. This parameter can be + /// set to EVENT_MAX to indicate the highest possible event value. + /// + /// + /// Handle to the DLL that contains the hook function at lpfnWinEventProc, + /// if the WINEVENT_INCONTEXT flag is specified in the dwFlags parameter. + /// If the hook function is not located in a DLL, or if the WINEVENT_OUTOFCONTEXT + /// flag is specified, this parameter is NULL. + /// + /// + /// Pointer to the event hook function. For more information about this + /// function, see WinEventProc. + /// + /// + /// Specifies the ID of the process from which the hook function receives + /// events. Specify zero (0) to receive events from all processes on the + /// current desktop. + /// + /// + /// Specifies the ID of the thread from which the hook function receives + /// events. If this parameter is zero, the hook function is associated + /// with all existing threads on the current desktop. + /// + /// + /// Flag values that specify the location of the hook function and of + /// the events to be skipped. Valid values are: WINEVENT_INCONTEXT, + /// WINEVENT_OUTOFCONTEXT, WINEVENT_SKIPOWNPROCESS, WINEVENT_SKIPOWNTHREAD + /// + /// + /// If successful, returns an HWINEVENTHOOK value that identifies this + /// event hook instance. Applications save this return value to use it + /// with the UnhookWinEvent function. If unsuccessful, returns zero. + /// + [DllImport("user32.dll")] + public static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, + WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags); + + /// + /// Removes an event hook function created by a previous call to + /// SetWinEventHook. + /// + /// + /// Handle to the event hook returned in the previous call to + /// SetWinEventHook. + /// + /// + [DllImport("user32.dll")] + public static extern bool UnhookWinEvent(IntPtr hWinEventHook); + + /// + /// The foreground window has changed. The system sends + /// this event even if the foreground window has changed + /// to another window in the same thread. Server applications + /// never send this event. For this event, the WinEventProc + /// callback function's hwnd parameter is the handle to the + /// window that is in the foreground, the idObject parameter + /// is OBJID_WINDOW, and the idChild parameter is CHILDID_SELF. + /// + public const uint EVENT_SYSTEM_FOREGROUND = 0x0003; + + /// + /// The user has released ALT+TAB. This event is sent by the + /// system, never by servers. The hwnd parameter of the WinEventProc + /// callback function identifies the window to which the user + /// has switched. If only one application is running when the + /// user presses ALT+TAB, the system sends this event without + /// a corresponding EVENT_SYSTEM_SWITCHSTART event. + /// + public const uint EVENT_SYSTEM_SWITCHSTART = 0x0014; + + /// + /// The user has pressed ALT+TAB, which activates the switch + /// window. This event is sent by the system, never by servers. + /// The hwnd parameter of the WinEventProc callback function + /// identifies the window to which the user is switching. If + /// only one application is running when the user presses + /// ALT+TAB, the system sends an EVENT_SYSTEM_SWITCHEND event + /// without a corresponding EVENT_SYSTEM_SWITCHSTART event. + /// + public const uint EVENT_SYSTEM_SWITCHEND = 0x0015; + + public const uint WINEVENT_OUTOFCONTEXT = 0; + + #endregion + #endregion } } \ No newline at end of file diff --git a/mRemoteV1/Connection/InterfaceControl.cs b/mRemoteV1/Connection/InterfaceControl.cs index b21c31b8..714e9fdf 100644 --- a/mRemoteV1/Connection/InterfaceControl.cs +++ b/mRemoteV1/Connection/InterfaceControl.cs @@ -1,4 +1,4 @@ -using mRemoteNG.App; +using mRemoteNG.App; using mRemoteNG.Connection.Protocol; using System; using System.Drawing; @@ -25,6 +25,8 @@ namespace mRemoteNG.Connection Location = new Point(0, 0); Size = Parent.Size; Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top; + GotFocus += OnGotFocus; + LostFocus += OnLostFocus; InitializeComponent(); } catch (Exception ex) @@ -35,6 +37,16 @@ namespace mRemoteNG.Connection } } + private void OnLostFocus(object sender, EventArgs e) + { + Runtime.MessageCollector.AddMessage(Messages.MessageClass.DebugMsg, $"InterfaceControl lost focus '{Info.Name}'"); + } + + private void OnGotFocus(object sender, EventArgs e) + { + Runtime.MessageCollector.AddMessage(Messages.MessageClass.DebugMsg, $"InterfaceControl gained focus '{Info.Name}'"); + } + public static InterfaceControl FindInterfaceControl(DockPanel DockPnl) { if (!(DockPnl.ActiveDocument is ConnectionTab ct)) return null; diff --git a/mRemoteV1/Connection/Protocol/ExternalProcessProtocolBase.cs b/mRemoteV1/Connection/Protocol/ExternalProcessProtocolBase.cs new file mode 100644 index 00000000..0b720e2d --- /dev/null +++ b/mRemoteV1/Connection/Protocol/ExternalProcessProtocolBase.cs @@ -0,0 +1,101 @@ +using System; +using System.Diagnostics; +using mRemoteNG.App; +using mRemoteNG.Messages; + +namespace mRemoteNG.Connection.Protocol +{ + public class ExternalProcessProtocolBase : ProtocolBase + { + private IntPtr _winEventHook; + private NativeMethods.WinEventDelegate _setForegroundDelegate; + + public override bool IsExternalProcess { get; } = true; + + protected Process ProtocolProcess { get; set; } + + protected IntPtr ProcessHandle { get; set; } + + public int ThreadId => (int)NativeMethods.GetWindowThreadProcessId(ProcessHandle, IntPtr.Zero); + + + public override bool Connect() + { + _setForegroundDelegate = OnWinEventSetForeground; + _winEventHook = NativeMethods.SetWinEventHook( + NativeMethods.EVENT_SYSTEM_FOREGROUND, + NativeMethods.EVENT_SYSTEM_FOREGROUND, + IntPtr.Zero, + _setForegroundDelegate, + Convert.ToUInt32(ProtocolProcess.Id), + 0, + NativeMethods.WINEVENT_OUTOFCONTEXT); + + return base.Connect(); + } + + public override void Close() + { + if (NativeMethods.UnhookWinEvent(_winEventHook)) + { + Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, $"Successfully unhooked WinEvent listener from '{InterfaceControl.Info.Name}'"); + } + else + { + Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, $"Failed to unhook WinEvent listener from '{InterfaceControl.Info.Name}'"); + } + + base.Close(); + } + + public override void Focus() + { + FocusChildProcessWindow(); + } + + private void FocusChildProcessWindow() + { + if (NativeMethods.GetForegroundWindow() == ProcessHandle) + { + Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, "Process already focused - do nothing"); + return; + } + + 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}"); + } + + /// + /// This callback will be called when the external process window managed by + /// this protocol is brought to the foreground. + /// + /// + /// + /// + /// + /// + /// + /// + void OnWinEventSetForeground(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) + { + if (hwnd != ProtocolProcess.MainWindowHandle) + return; + + //Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, + // "Exernal protocol window set to foreground. " + + // $"protocol:{InterfaceControl.Info.Protocol} " + + // $"pid:{ProtocolProcess.Id}, " + + // $"hwnd:{ProtocolProcess.MainWindowHandle}"); + } + } +} diff --git a/mRemoteV1/Connection/Protocol/IntegratedProgram.cs b/mRemoteV1/Connection/Protocol/IntegratedProgram.cs index 2b296074..0ec4cc40 100644 --- a/mRemoteV1/Connection/Protocol/IntegratedProgram.cs +++ b/mRemoteV1/Connection/Protocol/IntegratedProgram.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Drawing; using System.Threading; @@ -9,7 +9,7 @@ using mRemoteNG.Tools; namespace mRemoteNG.Connection.Protocol { - public class IntegratedProgram : ProtocolBase + public class IntegratedProgram : ExternalProcessProtocolBase { #region Private Fields diff --git a/mRemoteV1/Connection/Protocol/ProtocolBase.cs b/mRemoteV1/Connection/Protocol/ProtocolBase.cs index 3b673f2d..b9e7ba6d 100644 --- a/mRemoteV1/Connection/Protocol/ProtocolBase.cs +++ b/mRemoteV1/Connection/Protocol/ProtocolBase.cs @@ -63,6 +63,12 @@ namespace mRemoteNG.Connection.Protocol public readonly System.Timers.Timer tmrReconnect = new System.Timers.Timer(2000); protected ReconnectGroup ReconnectGroup; + /// + /// Whether this protocol runs as a thread within the main process or if + /// it is an external process that is running as a child process. + /// + public virtual bool IsExternalProcess { get; } = false; + protected ProtocolBase(string name) { Name = name; diff --git a/mRemoteV1/Connection/Protocol/PuttyBase.cs b/mRemoteV1/Connection/Protocol/PuttyBase.cs index 3897542d..97d61665 100644 --- a/mRemoteV1/Connection/Protocol/PuttyBase.cs +++ b/mRemoteV1/Connection/Protocol/PuttyBase.cs @@ -1,4 +1,4 @@ -using mRemoteNG.App; +using mRemoteNG.App; using mRemoteNG.Messages; using mRemoteNG.Security.SymmetricEncryption; using mRemoteNG.Tools; @@ -14,27 +14,22 @@ using System.Windows.Forms; namespace mRemoteNG.Connection.Protocol { - public class PuttyBase : ProtocolBase + public class PuttyBase : ExternalProcessProtocolBase { private const int IDM_RECONF = 0x50; // PuTTY Settings Menu ID private bool _isPuttyNg; private readonly DisplayProperties _display = new DisplayProperties(); #region Public Properties - protected Putty_Protocol PuttyProtocol { private get; set; } protected Putty_SSHVersion PuttySSHVersion { private get; set; } - public IntPtr PuttyHandle { get; set; } - - private Process PuttyProcess { get; set; } - public static string PuttyPath { get; set; } public bool Focused { - get { return NativeMethods.GetForegroundWindow() == PuttyHandle; } + get { return NativeMethods.GetForegroundWindow() == ProcessHandle; } } #endregion @@ -54,111 +49,39 @@ namespace mRemoteNG.Connection.Protocol { try { - _isPuttyNg = PuttyTypeDetector.GetPuttyType() == PuttyTypeDetector.PuttyType.PuttyNg; + var arguments = BuildPuttyCommandLineArguments(InterfaceControl.Info); - PuttyProcess = new Process + ProtocolProcess = new Process { StartInfo = { UseShellExecute = false, - FileName = PuttyPath - } + FileName = PuttyPath, + Arguments = arguments.ToString() + }, + EnableRaisingEvents = true }; - var arguments = new CommandLineArguments {EscapeForShell = false}; + ProtocolProcess.Exited += ProcessExited; - arguments.Add("-load", InterfaceControl.Info.PuttySession); - - if (!(InterfaceControl.Info is PuttySessionInfo)) - { - arguments.Add("-" + PuttyProtocol); - - if (PuttyProtocol == Putty_Protocol.ssh) - { - var username = ""; - var password = ""; - - if (!string.IsNullOrEmpty(InterfaceControl.Info?.Username)) - { - username = InterfaceControl.Info.Username; - } - else - { - // ReSharper disable once SwitchStatementMissingSomeCases - switch (Settings.Default.EmptyCredentials) - { - case "windows": - username = Environment.UserName; - break; - case "custom": - username = Settings.Default.DefaultUsername; - break; - } - } - - if (!string.IsNullOrEmpty(InterfaceControl.Info?.Password)) - { - password = InterfaceControl.Info.Password; - } - else - { - if (Settings.Default.EmptyCredentials == "custom") - { - var cryptographyProvider = new LegacyRijndaelCryptographyProvider(); - password = cryptographyProvider.Decrypt(Settings.Default.DefaultPassword, - Runtime.EncryptionKey); - } - } - - arguments.Add("-" + (int)PuttySSHVersion); - - if (!Force.HasFlag(ConnectionInfo.Force.NoCredentials)) - { - if (!string.IsNullOrEmpty(username)) - { - arguments.Add("-l", username); - } - - if (!string.IsNullOrEmpty(password)) - { - arguments.Add("-pw", password); - } - } - } - - arguments.Add("-P", InterfaceControl.Info.Port.ToString()); - arguments.Add(InterfaceControl.Info.Hostname); - } - - if (_isPuttyNg) - { - arguments.Add("-hwndparent", InterfaceControl.Handle.ToString()); - } - - PuttyProcess.StartInfo.Arguments = arguments.ToString(); - - PuttyProcess.EnableRaisingEvents = true; - PuttyProcess.Exited += ProcessExited; - - PuttyProcess.Start(); - PuttyProcess.WaitForInputIdle(Settings.Default.MaxPuttyWaitTime * 1000); + ProtocolProcess.Start(); + ProtocolProcess.WaitForInputIdle(Settings.Default.MaxPuttyWaitTime * 1000); var startTicks = Environment.TickCount; - while (PuttyHandle.ToInt32() == 0 & + while (ProcessHandle.ToInt32() == 0 & Environment.TickCount < startTicks + Settings.Default.MaxPuttyWaitTime * 1000) { if (_isPuttyNg) { - PuttyHandle = NativeMethods.FindWindowEx( - InterfaceControl.Handle, new IntPtr(0), null, null); + ProcessHandle = NativeMethods.FindWindowEx(InterfaceControl.Handle, new IntPtr(0), null, null); } else { - PuttyProcess.Refresh(); - PuttyHandle = PuttyProcess.MainWindowHandle; + ProtocolProcess.Refresh(); + ProcessHandle = ProtocolProcess.MainWindowHandle; } - if (PuttyHandle.ToInt32() == 0) + if (ProcessHandle.ToInt32() == 0) { Thread.Sleep(0); } @@ -166,14 +89,14 @@ namespace mRemoteNG.Connection.Protocol if (!_isPuttyNg) { - NativeMethods.SetParent(PuttyHandle, InterfaceControl.Handle); + NativeMethods.SetParent(ProcessHandle, InterfaceControl.Handle); } Runtime.MessageCollector.AddMessage(MessageClass.InformationMsg, Language.strPuttyStuff, true); Runtime.MessageCollector.AddMessage(MessageClass.InformationMsg, - string.Format(Language.strPuttyHandle, PuttyHandle), true); + string.Format(Language.strPuttyHandle, ProcessHandle), true); Runtime.MessageCollector.AddMessage(MessageClass.InformationMsg, - string.Format(Language.strPuttyTitle, PuttyProcess.MainWindowTitle), + string.Format(Language.strPuttyTitle, ProtocolProcess.MainWindowTitle), true); Runtime.MessageCollector.AddMessage(MessageClass.InformationMsg, string.Format(Language.strPuttyParentHandle, @@ -192,18 +115,102 @@ namespace mRemoteNG.Connection.Protocol } } + private CommandLineArguments BuildPuttyCommandLineArguments(AbstractConnectionRecord connectionInfo) + { + var arguments = new CommandLineArguments { EscapeForShell = false }; + + arguments.Add("-load", connectionInfo.PuttySession); + + if (!(connectionInfo is PuttySessionInfo)) + { + arguments.Add("-" + PuttyProtocol); + + if (PuttyProtocol == Putty_Protocol.ssh) + { + var username = ""; + var password = ""; + + if (!string.IsNullOrEmpty(connectionInfo.Username)) + { + username = connectionInfo.Username; + } + else + { + // ReSharper disable once SwitchStatementMissingSomeCases + switch (Settings.Default.EmptyCredentials) + { + case "windows": + username = Environment.UserName; + break; + case "custom": + username = Settings.Default.DefaultUsername; + break; + } + } + + if (!string.IsNullOrEmpty(connectionInfo.Password)) + { + password = connectionInfo.Password; + } + else + { + if (Settings.Default.EmptyCredentials == "custom") + { + var cryptographyProvider = new LegacyRijndaelCryptographyProvider(); + password = cryptographyProvider.Decrypt(Settings.Default.DefaultPassword, + Runtime.EncryptionKey); + } + } + + arguments.Add("-" + (int)PuttySSHVersion); + + if (!Force.HasFlag(ConnectionInfo.Force.NoCredentials)) + { + if (!string.IsNullOrEmpty(username)) + { + arguments.Add("-l", username); + } + + if (!string.IsNullOrEmpty(password)) + { + arguments.Add("-pw", password); + } + } + } + + arguments.Add("-P", connectionInfo.Port.ToString()); + arguments.Add(connectionInfo.Hostname); + } + + _isPuttyNg = PuttyTypeDetector.GetPuttyType() == PuttyTypeDetector.PuttyType.PuttyNg; + if (_isPuttyNg) + { + arguments.Add("-hwndparent", InterfaceControl.Handle.ToString()); + } + + return arguments; + } + public override void Focus() { - try - { - NativeMethods.SetForegroundWindow(PuttyHandle); - } - catch (Exception ex) - { - Runtime.MessageCollector.AddMessage(MessageClass.ErrorMsg, - Language.strPuttyFocusFailed + Environment.NewLine + ex.Message, - true); - } + //try + //{ + // if (NativeMethods.GetForegroundWindow() == PuttyHandle) + // { + // Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, $"Putty window already focused, ignoring focus request '{InterfaceControl.Info.Name}' (pid:{PuttyProcess.Id})"); + // return; + // } + + // NativeMethods.SetForegroundWindow(PuttyHandle); + // Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, $"Putty window focused '{InterfaceControl.Info.Name}' (pid:{PuttyProcess.Id})"); + //} + //catch (Exception ex) + //{ + // Runtime.MessageCollector.AddMessage(MessageClass.ErrorMsg, + // Language.strPuttyFocusFailed + Environment.NewLine + ex.Message, + // true); + //} + base.Focus(); } public override void Resize(object sender, EventArgs e) @@ -216,14 +223,14 @@ namespace mRemoteNG.Connection.Protocol if (_isPuttyNg) { // PuTTYNG 0.70.0.1 and later doesn't have any window borders - NativeMethods.MoveWindow(PuttyHandle, 0, 0, InterfaceControl.Width, InterfaceControl.Height, true); + NativeMethods.MoveWindow(ProcessHandle, 0, 0, InterfaceControl.Width, InterfaceControl.Height, true); } else { var scaledFrameBorderHeight = _display.ScaleHeight(SystemInformation.FrameBorderSize.Height); var scaledFrameBorderWidth = _display.ScaleWidth(SystemInformation.FrameBorderSize.Width); - NativeMethods.MoveWindow(PuttyHandle, -scaledFrameBorderWidth, + NativeMethods.MoveWindow(ProcessHandle, -scaledFrameBorderWidth, -(SystemInformation.CaptionHeight + scaledFrameBorderHeight), InterfaceControl.Width + scaledFrameBorderWidth * 2, InterfaceControl.Height + SystemInformation.CaptionHeight + @@ -243,9 +250,9 @@ namespace mRemoteNG.Connection.Protocol { try { - if (PuttyProcess.HasExited == false) + if (ProtocolProcess.HasExited == false) { - PuttyProcess.Kill(); + ProtocolProcess.Kill(); } } catch (Exception ex) @@ -257,7 +264,7 @@ namespace mRemoteNG.Connection.Protocol try { - PuttyProcess.Dispose(); + ProtocolProcess.Dispose(); } catch (Exception ex) { @@ -273,8 +280,8 @@ namespace mRemoteNG.Connection.Protocol { try { - NativeMethods.PostMessage(PuttyHandle, NativeMethods.WM_SYSCOMMAND, (IntPtr)IDM_RECONF, (IntPtr)0); - NativeMethods.SetForegroundWindow(PuttyHandle); + NativeMethods.PostMessage(ProcessHandle, NativeMethods.WM_SYSCOMMAND, (IntPtr)IDM_RECONF, IntPtr.Zero); + Focus(); } catch (Exception ex) { @@ -284,6 +291,14 @@ namespace mRemoteNG.Connection.Protocol } } + /// + /// Sends an individual key stroke to this PuTTY session. + /// + public void SendKeyStroke(int keyType, int keyData) + { + NativeMethods.PostMessage(ProcessHandle, keyType, new IntPtr(keyData), new IntPtr(0)); + } + #endregion #region Enums diff --git a/mRemoteV1/Connection/Protocol/RDP/RdpProtocol6.cs b/mRemoteV1/Connection/Protocol/RDP/RdpProtocol6.cs index ca53457a..60021fab 100644 --- a/mRemoteV1/Connection/Protocol/RDP/RdpProtocol6.cs +++ b/mRemoteV1/Connection/Protocol/RDP/RdpProtocol6.cs @@ -1,6 +1,9 @@ using System; +using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Runtime.InteropServices; +using System.Text; using System.Threading; using System.Timers; using System.Windows.Forms; @@ -35,6 +38,7 @@ namespace mRemoteNG.Connection.Protocol.RDP private readonly FrmMain _frmMain = FrmMain.Default; protected virtual RdpVersion RdpProtocolVersion => RdpVersion.Rdc6; private AxHost AxHost => (AxHost)Control; + private readonly NativeMethods.EnumWindowsProc _enumWindowsProc; #region Properties @@ -101,6 +105,7 @@ namespace mRemoteNG.Connection.Protocol.RDP { _displayProperties = new DisplayProperties(); tmrReconnect.Elapsed += tmrReconnect_Elapsed; + _enumWindowsProc = LpEnumFunc; } #endregion @@ -243,17 +248,54 @@ namespace mRemoteNG.Connection.Protocol.RDP public override void Focus() { + var result = new List(); + var listHandle = GCHandle.Alloc(result); try { if (Control.ContainsFocus == false) { - Control.Focus(); + //AxHost.Focus(); + //AxHost.Select(); + //AxHost.DoVerb(-1); + //AxHost.DoVerb(-4); + //AxHost.DoVerb(-5); + + NativeMethods.EnumChildWindows(AxHost.Handle, _enumWindowsProc, GCHandle.ToIntPtr(listHandle)); + if (result.Any()) + { + NativeMethods.SetFocus(result[0]); + Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, $"RDP connection focused '{connectionInfo.Name}'"); + } } } catch (Exception ex) { Runtime.MessageCollector.AddExceptionStackTrace(Language.strRdpFocusFailed, ex); } + finally + { + if (listHandle.IsAllocated) + listHandle.Free(); + } + } + + private bool LpEnumFunc(IntPtr hwnd, IntPtr lparam) + { + var gch = GCHandle.FromIntPtr(lparam); + var list = gch.Target as List; + if (list == null) + throw new InvalidCastException("GCHandle Target could not be cast as List"); + + var sb = new StringBuilder(); + NativeMethods.GetClassName(hwnd, sb, 64); + + if (sb.ToString().Equals("IHWindowClass")) + { + list.Add(hwnd); + return false; + } + + return true; } /// @@ -672,6 +714,9 @@ namespace mRemoteNG.Connection.Protocol.RDP _rdpClient.OnDisconnected += RDPEvent_OnDisconnected; _rdpClient.OnLeaveFullScreenMode += RDPEvent_OnLeaveFullscreenMode; _rdpClient.OnIdleTimeoutNotification += RDPEvent_OnIdleTimeoutNotification; + //_rdpClient.OnFocusReleased += direction => Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, $"RDP control '{connectionInfo.Name}' released focus."); + //AxHost.GotFocus += (sender, args) => Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, $"RDP control '{connectionInfo.Name}' received focus."); + //AxHost.LostFocus += (sender, args) => Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, $"RDP control '{connectionInfo.Name}' lost focus."); } catch (Exception ex) { @@ -748,7 +793,7 @@ namespace mRemoteNG.Connection.Protocol.RDP private void RdpClient_GotFocus(object sender, EventArgs e) { - ((ConnectionTab)Control.Parent.Parent).Focus(); + //((ConnectionTab)Control.Parent.Parent).Focus(); } #endregion diff --git a/mRemoteV1/Messages/MessageWriters/DebugConsoleMessageWriter.cs b/mRemoteV1/Messages/MessageWriters/DebugConsoleMessageWriter.cs index c168e39f..1ab4387b 100644 --- a/mRemoteV1/Messages/MessageWriters/DebugConsoleMessageWriter.cs +++ b/mRemoteV1/Messages/MessageWriters/DebugConsoleMessageWriter.cs @@ -6,7 +6,7 @@ namespace mRemoteNG.Messages.MessageWriters { public void Write(IMessage message) { - var textToPrint = $"{message.Class}: {message.Text}"; + var textToPrint = $"[{message.Date.ToString("O")}] {message.Class}: {message.Text}"; Debug.Print(textToPrint); } } diff --git a/mRemoteV1/Themes/ThemeInfo.cs b/mRemoteV1/Themes/ThemeInfo.cs index eb0f2445..1cb9c4e0 100644 --- a/mRemoteV1/Themes/ThemeInfo.cs +++ b/mRemoteV1/Themes/ThemeInfo.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel; using System.Linq; using WeifenLuo.WinFormsUI.Docking; diff --git a/mRemoteV1/Tools/MultiSSHController.cs b/mRemoteV1/Tools/MultiSSHController.cs index d2dc91b8..162f4678 100644 --- a/mRemoteV1/Tools/MultiSSHController.cs +++ b/mRemoteV1/Tools/MultiSSHController.cs @@ -63,7 +63,7 @@ namespace mRemoteNG.Tools foreach (PuttyBase proc in processHandlers) { - NativeMethods.PostMessage(proc.PuttyHandle, keyType, new IntPtr(keyData), new IntPtr(0)); + proc.SendKeyStroke(keyType, keyData); } } diff --git a/mRemoteV1/UI/Forms/frmMain.Designer.cs b/mRemoteV1/UI/Forms/frmMain.Designer.cs index dc8c75a7..de63b224 100644 --- a/mRemoteV1/UI/Forms/frmMain.Designer.cs +++ b/mRemoteV1/UI/Forms/frmMain.Designer.cs @@ -31,193 +31,191 @@ [System.Diagnostics.DebuggerStepThrough()] private void InitializeComponent() { - this.components = new System.ComponentModel.Container(); - mRemoteNG.Connection.ConnectionInitiator connectionInitiator1 = new mRemoteNG.Connection.ConnectionInitiator(); - System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FrmMain)); - this.pnlDock = new WeifenLuo.WinFormsUI.Docking.DockPanel(); - this.msMain = new System.Windows.Forms.MenuStrip(); - this.fileMenu = new mRemoteNG.UI.Menu.MainFileMenu(); - this.viewMenu = new mRemoteNG.UI.Menu.ViewMenu(); - this.toolsMenu = new mRemoteNG.UI.Menu.ToolsMenu(); - this.helpMenu = new mRemoteNG.UI.Menu.HelpMenu(); - this.mMenSep3 = new System.Windows.Forms.ToolStripSeparator(); - this.tsContainer = new System.Windows.Forms.ToolStripContainer(); - this._quickConnectToolStrip = new mRemoteNG.UI.Controls.QuickConnectToolStrip(); - this._multiSshToolStrip = new mRemoteNG.UI.Controls.MultiSshToolStrip(); - this._externalToolsToolStrip = new mRemoteNG.UI.Controls.ExternalToolsToolStrip(); - this.tmrAutoSave = new System.Windows.Forms.Timer(this.components); - this.vsToolStripExtender = new WeifenLuo.WinFormsUI.Docking.VisualStudioToolStripExtender(this.components); - this.msMain.SuspendLayout(); - this.tsContainer.ContentPanel.SuspendLayout(); - this.tsContainer.TopToolStripPanel.SuspendLayout(); - this.tsContainer.SuspendLayout(); - this.SuspendLayout(); - // - // pnlDock - // - this.pnlDock.Dock = System.Windows.Forms.DockStyle.Fill; - this.pnlDock.DockBackColor = System.Drawing.SystemColors.Control; - this.pnlDock.DockLeftPortion = 230D; - this.pnlDock.DockRightPortion = 230D; - this.pnlDock.DocumentStyle = WeifenLuo.WinFormsUI.Docking.DocumentStyle.DockingSdi; - this.pnlDock.Location = new System.Drawing.Point(0, 0); - this.pnlDock.Name = "pnlDock"; - this.pnlDock.Size = new System.Drawing.Size(1129, 471); - this.pnlDock.TabIndex = 13; - this.pnlDock.ActiveDocumentChanged += new System.EventHandler(this.pnlDock_ActiveDocumentChanged); - // - // msMain - // - this.msMain.Anchor = System.Windows.Forms.AnchorStyles.Left; - this.msMain.Dock = System.Windows.Forms.DockStyle.None; - this.msMain.GripMargin = new System.Windows.Forms.Padding(2); - this.msMain.GripStyle = System.Windows.Forms.ToolStripGripStyle.Visible; - this.msMain.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.components = new System.ComponentModel.Container(); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FrmMain)); + this.pnlDock = new WeifenLuo.WinFormsUI.Docking.DockPanel(); + this.msMain = new System.Windows.Forms.MenuStrip(); + this.fileMenu = new mRemoteNG.UI.Menu.MainFileMenu(); + this.viewMenu = new mRemoteNG.UI.Menu.ViewMenu(); + this.toolsMenu = new mRemoteNG.UI.Menu.ToolsMenu(); + this.helpMenu = new mRemoteNG.UI.Menu.HelpMenu(); + this.mMenSep3 = new System.Windows.Forms.ToolStripSeparator(); + this.tsContainer = new System.Windows.Forms.ToolStripContainer(); + this._quickConnectToolStrip = new mRemoteNG.UI.Controls.QuickConnectToolStrip(); + this._multiSshToolStrip = new mRemoteNG.UI.Controls.MultiSshToolStrip(); + this._externalToolsToolStrip = new mRemoteNG.UI.Controls.ExternalToolsToolStrip(); + this.tmrAutoSave = new System.Windows.Forms.Timer(this.components); + this.vsToolStripExtender = new WeifenLuo.WinFormsUI.Docking.VisualStudioToolStripExtender(this.components); + this.msMain.SuspendLayout(); + this.tsContainer.ContentPanel.SuspendLayout(); + this.tsContainer.TopToolStripPanel.SuspendLayout(); + this.tsContainer.SuspendLayout(); + this.SuspendLayout(); + // + // pnlDock + // + this.pnlDock.Dock = System.Windows.Forms.DockStyle.Fill; + this.pnlDock.DockBackColor = System.Drawing.SystemColors.Control; + this.pnlDock.DockLeftPortion = 230D; + this.pnlDock.DockRightPortion = 230D; + this.pnlDock.DocumentStyle = WeifenLuo.WinFormsUI.Docking.DocumentStyle.DockingSdi; + this.pnlDock.Location = new System.Drawing.Point(0, 0); + this.pnlDock.Name = "pnlDock"; + this.pnlDock.Size = new System.Drawing.Size(1129, 471); + this.pnlDock.TabIndex = 13; + this.pnlDock.ActiveDocumentChanged += new System.EventHandler(this.pnlDock_ActiveDocumentChanged); + // + // msMain + // + this.msMain.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.msMain.Dock = System.Windows.Forms.DockStyle.None; + this.msMain.GripMargin = new System.Windows.Forms.Padding(2); + this.msMain.GripStyle = System.Windows.Forms.ToolStripGripStyle.Visible; + this.msMain.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { this.fileMenu, this.viewMenu, this.toolsMenu, this.helpMenu}); - this.msMain.Location = new System.Drawing.Point(3, 50); - this.msMain.Name = "msMain"; - this.msMain.Padding = new System.Windows.Forms.Padding(0, 0, 1, 0); - this.msMain.Size = new System.Drawing.Size(184, 25); - this.msMain.Stretch = false; - this.msMain.TabIndex = 0; - this.msMain.Text = "Main Toolbar"; - // - // fileMenu - // - this.fileMenu.ConnectionInitiator = null; - this.fileMenu.Margin = new System.Windows.Forms.Padding(0, 3, 0, 3); - this.fileMenu.Name = "mMenFile"; - this.fileMenu.Size = new System.Drawing.Size(37, 19); - this.fileMenu.Text = "&File"; - this.fileMenu.TreeWindow = null; - this.fileMenu.DropDownOpening += new System.EventHandler(this.mainFileMenu1_DropDownOpening); - // - // viewMenu - // - this.viewMenu.FullscreenHandler = null; - this.viewMenu.MainForm = null; - this.viewMenu.Margin = new System.Windows.Forms.Padding(0, 3, 0, 3); - this.viewMenu.Name = "mMenView"; - this.viewMenu.Size = new System.Drawing.Size(44, 19); - this.viewMenu.Text = "&View"; - this.viewMenu.TsExternalTools = null; - this.viewMenu.TsMultiSsh = null; - this.viewMenu.TsQuickConnect = null; - this.viewMenu.DropDownOpening += new System.EventHandler(this.ViewMenu_Opening); - // - // toolsMenu - // - this.toolsMenu.CredentialProviderCatalog = null; - this.toolsMenu.MainForm = null; - this.toolsMenu.Margin = new System.Windows.Forms.Padding(0, 3, 0, 3); - this.toolsMenu.Name = "mMenTools"; - this.toolsMenu.Size = new System.Drawing.Size(47, 19); - this.toolsMenu.Text = "&Tools"; - // - // helpMenu - // - this.helpMenu.Margin = new System.Windows.Forms.Padding(0, 3, 0, 3); - this.helpMenu.Name = "mMenInfo"; - this.helpMenu.Size = new System.Drawing.Size(44, 19); - this.helpMenu.Text = "&Help"; - this.helpMenu.TextDirection = System.Windows.Forms.ToolStripTextDirection.Horizontal; - // - // mMenSep3 - // - this.mMenSep3.Name = "mMenSep3"; - this.mMenSep3.Size = new System.Drawing.Size(211, 6); - // - // tsContainer - // - // - // tsContainer.ContentPanel - // - this.tsContainer.ContentPanel.Controls.Add(this.pnlDock); - this.tsContainer.ContentPanel.Size = new System.Drawing.Size(1129, 471); - this.tsContainer.Dock = System.Windows.Forms.DockStyle.Fill; - this.tsContainer.Location = new System.Drawing.Point(0, 0); - this.tsContainer.Name = "tsContainer"; - this.tsContainer.Size = new System.Drawing.Size(1129, 571); - this.tsContainer.TabIndex = 17; - this.tsContainer.Text = "ToolStripContainer1"; - // - // tsContainer.TopToolStripPanel - // - this.tsContainer.TopToolStripPanel.Controls.Add(this._quickConnectToolStrip); - this.tsContainer.TopToolStripPanel.Controls.Add(this._multiSshToolStrip); - this.tsContainer.TopToolStripPanel.Controls.Add(this.msMain); - this.tsContainer.TopToolStripPanel.Controls.Add(this._externalToolsToolStrip); - this.tsContainer.TopToolStripPanel.RenderMode = System.Windows.Forms.ToolStripRenderMode.Professional; - // - // _quickConnectToolStrip - // - this._quickConnectToolStrip.BackColor = System.Drawing.SystemColors.Control; - this._quickConnectToolStrip.ConnectionInitiator = connectionInitiator1; - this._quickConnectToolStrip.Dock = System.Windows.Forms.DockStyle.None; - this._quickConnectToolStrip.ForeColor = System.Drawing.SystemColors.ControlText; - this._quickConnectToolStrip.Location = new System.Drawing.Point(3, 0); - this._quickConnectToolStrip.Name = "_quickConnectToolStrip"; - this._quickConnectToolStrip.Size = new System.Drawing.Size(364, 25); - this._quickConnectToolStrip.TabIndex = 18; - // - // _multiSshToolStrip - // - this._multiSshToolStrip.Dock = System.Windows.Forms.DockStyle.None; - this._multiSshToolStrip.Location = new System.Drawing.Point(3, 25); - this._multiSshToolStrip.MinimumSize = new System.Drawing.Size(300, 0); - this._multiSshToolStrip.Name = "_multiSshToolStrip"; - this._multiSshToolStrip.Size = new System.Drawing.Size(376, 25); - this._multiSshToolStrip.TabIndex = 1; - // - // _externalToolsToolStrip - // - this._externalToolsToolStrip.BackColor = System.Drawing.SystemColors.Control; - this._externalToolsToolStrip.Dock = System.Windows.Forms.DockStyle.None; - this._externalToolsToolStrip.ForeColor = System.Drawing.SystemColors.ControlText; - this._externalToolsToolStrip.Location = new System.Drawing.Point(3, 75); - this._externalToolsToolStrip.Name = "_externalToolsToolStrip"; - this._externalToolsToolStrip.Size = new System.Drawing.Size(111, 25); - this._externalToolsToolStrip.TabIndex = 17; - // - // tmrAutoSave - // - this.tmrAutoSave.Interval = 10000; - this.tmrAutoSave.Tick += new System.EventHandler(this.tmrAutoSave_Tick); - // - // vsToolStripExtender - // - this.vsToolStripExtender.DefaultRenderer = null; - // - // FrmMain - // - this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; - this.ClientSize = new System.Drawing.Size(1129, 571); - this.Controls.Add(this.tsContainer); - this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); - this.MainMenuStrip = this.msMain; - this.MinimumSize = new System.Drawing.Size(400, 400); - this.Name = "FrmMain"; - this.Opacity = 0D; - this.Text = " "; - this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.frmMain_FormClosing); - this.Load += new System.EventHandler(this.frmMain_Load); - this.Shown += new System.EventHandler(this.frmMain_Shown); - this.ResizeBegin += new System.EventHandler(this.frmMain_ResizeBegin); - this.ResizeEnd += new System.EventHandler(this.frmMain_ResizeEnd); - this.Resize += new System.EventHandler(this.frmMain_Resize); - this.msMain.ResumeLayout(false); - this.msMain.PerformLayout(); - this.tsContainer.ContentPanel.ResumeLayout(false); - this.tsContainer.TopToolStripPanel.ResumeLayout(false); - this.tsContainer.TopToolStripPanel.PerformLayout(); - this.tsContainer.ResumeLayout(false); - this.tsContainer.PerformLayout(); - this.ResumeLayout(false); + this.msMain.Location = new System.Drawing.Point(3, 50); + this.msMain.Name = "msMain"; + this.msMain.Padding = new System.Windows.Forms.Padding(0, 0, 1, 0); + this.msMain.Size = new System.Drawing.Size(184, 25); + this.msMain.Stretch = false; + this.msMain.TabIndex = 0; + this.msMain.Text = "Main Toolbar"; + // + // fileMenu + // + this.fileMenu.ConnectionInitiator = null; + this.fileMenu.Margin = new System.Windows.Forms.Padding(0, 3, 0, 3); + this.fileMenu.Name = "mMenFile"; + this.fileMenu.Size = new System.Drawing.Size(37, 19); + this.fileMenu.Text = "&File"; + this.fileMenu.TreeWindow = null; + this.fileMenu.DropDownOpening += new System.EventHandler(this.mainFileMenu1_DropDownOpening); + // + // viewMenu + // + this.viewMenu.FullscreenHandler = null; + this.viewMenu.MainForm = null; + this.viewMenu.Margin = new System.Windows.Forms.Padding(0, 3, 0, 3); + this.viewMenu.Name = "mMenView"; + this.viewMenu.Size = new System.Drawing.Size(44, 19); + this.viewMenu.Text = "&View"; + this.viewMenu.TsExternalTools = null; + this.viewMenu.TsMultiSsh = null; + this.viewMenu.TsQuickConnect = null; + this.viewMenu.DropDownOpening += new System.EventHandler(this.ViewMenu_Opening); + // + // toolsMenu + // + this.toolsMenu.CredentialProviderCatalog = null; + this.toolsMenu.MainForm = null; + this.toolsMenu.Margin = new System.Windows.Forms.Padding(0, 3, 0, 3); + this.toolsMenu.Name = "mMenTools"; + this.toolsMenu.Size = new System.Drawing.Size(47, 19); + this.toolsMenu.Text = "&Tools"; + // + // helpMenu + // + this.helpMenu.Margin = new System.Windows.Forms.Padding(0, 3, 0, 3); + this.helpMenu.Name = "mMenInfo"; + this.helpMenu.Size = new System.Drawing.Size(44, 19); + this.helpMenu.Text = "&Help"; + this.helpMenu.TextDirection = System.Windows.Forms.ToolStripTextDirection.Horizontal; + // + // mMenSep3 + // + this.mMenSep3.Name = "mMenSep3"; + this.mMenSep3.Size = new System.Drawing.Size(211, 6); + // + // tsContainer + // + // + // tsContainer.ContentPanel + // + this.tsContainer.ContentPanel.Controls.Add(this.pnlDock); + this.tsContainer.ContentPanel.Size = new System.Drawing.Size(1129, 471); + this.tsContainer.Dock = System.Windows.Forms.DockStyle.Fill; + this.tsContainer.Location = new System.Drawing.Point(0, 0); + this.tsContainer.Name = "tsContainer"; + this.tsContainer.Size = new System.Drawing.Size(1129, 571); + this.tsContainer.TabIndex = 17; + this.tsContainer.Text = "ToolStripContainer1"; + // + // tsContainer.TopToolStripPanel + // + this.tsContainer.TopToolStripPanel.Controls.Add(this._quickConnectToolStrip); + this.tsContainer.TopToolStripPanel.Controls.Add(this._multiSshToolStrip); + this.tsContainer.TopToolStripPanel.Controls.Add(this.msMain); + this.tsContainer.TopToolStripPanel.Controls.Add(this._externalToolsToolStrip); + this.tsContainer.TopToolStripPanel.RenderMode = System.Windows.Forms.ToolStripRenderMode.Professional; + // + // _quickConnectToolStrip + // + this._quickConnectToolStrip.BackColor = System.Drawing.SystemColors.Control; + this._quickConnectToolStrip.Dock = System.Windows.Forms.DockStyle.None; + this._quickConnectToolStrip.ForeColor = System.Drawing.SystemColors.ControlText; + this._quickConnectToolStrip.Location = new System.Drawing.Point(3, 0); + this._quickConnectToolStrip.Name = "_quickConnectToolStrip"; + this._quickConnectToolStrip.Size = new System.Drawing.Size(364, 25); + this._quickConnectToolStrip.TabIndex = 18; + // + // _multiSshToolStrip + // + this._multiSshToolStrip.Dock = System.Windows.Forms.DockStyle.None; + this._multiSshToolStrip.Location = new System.Drawing.Point(3, 25); + this._multiSshToolStrip.MinimumSize = new System.Drawing.Size(300, 0); + this._multiSshToolStrip.Name = "_multiSshToolStrip"; + this._multiSshToolStrip.Size = new System.Drawing.Size(376, 25); + this._multiSshToolStrip.TabIndex = 1; + // + // _externalToolsToolStrip + // + this._externalToolsToolStrip.BackColor = System.Drawing.SystemColors.Control; + this._externalToolsToolStrip.Dock = System.Windows.Forms.DockStyle.None; + this._externalToolsToolStrip.ForeColor = System.Drawing.SystemColors.ControlText; + this._externalToolsToolStrip.Location = new System.Drawing.Point(3, 75); + this._externalToolsToolStrip.Name = "_externalToolsToolStrip"; + this._externalToolsToolStrip.Size = new System.Drawing.Size(111, 25); + this._externalToolsToolStrip.TabIndex = 17; + // + // tmrAutoSave + // + this.tmrAutoSave.Interval = 10000; + this.tmrAutoSave.Tick += new System.EventHandler(this.tmrAutoSave_Tick); + // + // vsToolStripExtender + // + this.vsToolStripExtender.DefaultRenderer = null; + // + // FrmMain + // + this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.ClientSize = new System.Drawing.Size(1129, 571); + this.Controls.Add(this.tsContainer); + this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.MainMenuStrip = this.msMain; + this.MinimumSize = new System.Drawing.Size(400, 400); + this.Name = "FrmMain"; + this.Opacity = 0D; + this.Text = " "; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.frmMain_FormClosing); + this.Load += new System.EventHandler(this.frmMain_Load); + this.Shown += new System.EventHandler(this.frmMain_Shown); + this.ResizeBegin += new System.EventHandler(this.frmMain_ResizeBegin); + this.ResizeEnd += new System.EventHandler(this.frmMain_ResizeEnd); + this.Resize += new System.EventHandler(this.frmMain_Resize); + this.msMain.ResumeLayout(false); + this.msMain.PerformLayout(); + this.tsContainer.ContentPanel.ResumeLayout(false); + this.tsContainer.TopToolStripPanel.ResumeLayout(false); + this.tsContainer.TopToolStripPanel.PerformLayout(); + this.tsContainer.ResumeLayout(false); + this.tsContainer.PerformLayout(); + this.ResumeLayout(false); } internal WeifenLuo.WinFormsUI.Docking.DockPanel pnlDock; diff --git a/mRemoteV1/UI/Forms/frmMain.cs b/mRemoteV1/UI/Forms/frmMain.cs index 4ec22b45..0474b08d 100644 --- a/mRemoteV1/UI/Forms/frmMain.cs +++ b/mRemoteV1/UI/Forms/frmMain.cs @@ -1,4 +1,4 @@ -using Microsoft.Win32; +using Microsoft.Win32; using mRemoteNG.App; using mRemoteNG.App.Info; using mRemoteNG.App.Initialization; @@ -23,11 +23,14 @@ using System.Diagnostics; using System.Drawing; using System.Globalization; using System.IO; +using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Windows.Forms; +using mRemoteNG.Connection.Protocol; using mRemoteNG.UI.Panels; using WeifenLuo.WinFormsUI.Docking; +using Message = System.Windows.Forms.Message; // ReSharper disable MemberCanBePrivate.Global @@ -206,6 +209,18 @@ namespace mRemoteNG.UI.Forms panelAdder.AddPanel(panelName); } + TabHelper.Instance.ActiveConnectionTabChanged += OnActiveConnectionTabChanged; + TabHelper.Instance.TabClicked += OnTabClicked; + } + + private void OnTabClicked(object sender, EventArgs e) + { + ActivateConnection(); + } + + private void OnActiveConnectionTabChanged(object sender, EventArgs e) + { + ActivateConnection(); } private void ApplyLanguage() @@ -437,6 +452,7 @@ namespace mRemoteNG.UI.Forms private void frmMain_ResizeBegin(object sender, EventArgs e) { _inSizeMove = true; + Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, "Begin app window move/resize"); } private void frmMain_Resize(object sender, EventArgs e) @@ -460,12 +476,17 @@ namespace mRemoteNG.UI.Forms private void frmMain_ResizeEnd(object sender, EventArgs e) { _inSizeMove = false; + Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, "End app window move/resize"); // This handles activations from clicks that started a size/move operation ActivateConnection(); } + + // Maybe after starting putty, remove its ability to show up in alt-tab? + // SetWindowLong(this.Handle, GWL_EXSTYLE, (GetWindowLong(this.Handle,GWL_EXSTYLE) | WS_EX_TOOLWINDOW) & ~WS_EX_APPWINDOW); protected override void WndProc(ref System.Windows.Forms.Message m) { + const int tabKey = 0x09; // Listen for and handle operating system messages try { @@ -474,63 +495,124 @@ namespace mRemoteNG.UI.Forms { case NativeMethods.WM_MOUSEACTIVATE: _inMouseActivate = true; + Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, $"_inMouseActivate = {_inMouseActivate}"); + + var controlThatWasClicked2 = FromChildHandle(NativeMethods.WindowFromPoint(MousePosition)) + ?? GetChildAtPoint(MousePosition); + + if (controlThatWasClicked2 == null) + break; + + Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, $"Clicked control: {controlThatWasClicked2}"); + break; + case NativeMethods.WM_KEYDOWN: + case NativeMethods.WM_SYSKEYDOWN: + if (m.WParam.ToInt32() != tabKey) + break; + + if ((m.LParam.ToInt32() & 0b00100000000000000000000000000000) == 0) // 29th bit ON means ALT key down + break; + + Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, "ALT-TAB PRESSED"); + + break; + case NativeMethods.WM_SYSKEYUP: + if (m.WParam.ToInt32() != tabKey) + break; + + if ((m.LParam.ToInt32() & 0b00100000000000000000000000000000) == 0) // 29th bit ON means ALT key down + break; + + Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, "ALT-TAB RELEASED"); + break; case NativeMethods.WM_ACTIVATEAPP: - var candidateTabToFocus = FromChildHandle(NativeMethods.WindowFromPoint(MousePosition)) - ?? GetChildAtPoint(MousePosition); - - if (candidateTabToFocus is InterfaceControl) + if (m.WParam.ToInt32() == 0) // mRemoteNG is being deactivated { - candidateTabToFocus.Parent.Focus(); + //var threadWhichIsActivating = m.LParam.ToInt32(); + //var activatingChildProcessWindow = _connectionInitiator.ActiveConnections + // .OfType() + // .Any(prot => prot.ThreadId == threadWhichIsActivating); + + _inMouseActivate = false; + Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, "mRemoteNG main window lost focus"); + break; } - _inMouseActivate = false; + Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, "mRemoteNG main window received focus"); + + //var candidateTabToFocus = FromChildHandle(NativeMethods.WindowFromPoint(MousePosition)) + // ?? GetChildAtPoint(MousePosition); + //if (candidateTabToFocus is InterfaceControl) + //{ + // candidateTabToFocus.Parent.Focus(); + //} + break; case NativeMethods.WM_ACTIVATE: - // Only handle this msg if it was triggered by a click - if (NativeMethods.LOWORD(m.WParam) == NativeMethods.WA_CLICKACTIVE) + if (NativeMethods.LOWORD(m.WParam) == NativeMethods.WA_ACTIVE) { - var controlThatWasClicked = FromChildHandle(NativeMethods.WindowFromPoint(MousePosition)) - ?? GetChildAtPoint(MousePosition); - if (controlThatWasClicked != null) - { - if (controlThatWasClicked is TreeView || - controlThatWasClicked is ComboBox || - controlThatWasClicked is TextBox || - controlThatWasClicked is FrmMain) - { - controlThatWasClicked.Focus(); - } - else if (controlThatWasClicked.CanSelect || - controlThatWasClicked is MenuStrip || - controlThatWasClicked is ToolStrip) - { - // Simulate a mouse event since one wasn't generated by Windows - SimulateClick(controlThatWasClicked); - controlThatWasClicked.Focus(); - } - else if (controlThatWasClicked is AutoHideStripBase) - { - // only focus the autohide toolstrip - controlThatWasClicked.Focus(); - } - else - { - // This handles activations from clicks that did not start a size/move operation - ActivateConnection(); - } - } + Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, "KB ACTIVATE"); } + // Only handle this msg if it was triggered by a click + if (NativeMethods.LOWORD(m.WParam) != NativeMethods.WA_CLICKACTIVE) + return; + + var controlThatWasClicked = FromChildHandle(NativeMethods.WindowFromPoint(MousePosition)) + ?? GetChildAtPoint(MousePosition); + + if (controlThatWasClicked == null) + break; + + Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, $"Click activate: {controlThatWasClicked}"); + + if (controlThatWasClicked is TreeView || + controlThatWasClicked is ComboBox || + controlThatWasClicked is TextBox || + controlThatWasClicked is FrmMain || + controlThatWasClicked is AutoHideStripBase) + { + controlThatWasClicked.Focus(); + } + else if (controlThatWasClicked.CanSelect || + controlThatWasClicked is MenuStrip || + controlThatWasClicked is ToolStrip) + { + // Simulate a mouse event since one wasn't generated by Windows + SimulateClick(controlThatWasClicked); + controlThatWasClicked.Focus(); + } + //else + //{ + // // This handles activations from clicks that did not start a size/move operation + // ActivateConnection(); + //} + 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 - var windowPos = - (NativeMethods.WINDOWPOS)Marshal.PtrToStructure(m.LParam, typeof(NativeMethods.WINDOWPOS)); + if (!_inMouseActivate) + break; + + var windowPos = (NativeMethods.WINDOWPOS)Marshal.PtrToStructure(m.LParam, typeof(NativeMethods.WINDOWPOS)); if ((windowPos.flags & NativeMethods.SWP_NOACTIVATE) == 0) { if (!_inMouseActivate && !_inSizeMove) + { + Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, "WM_WINDOWPOSCHANGED DONE"); ActivateConnection(); + } } break; @@ -558,6 +640,18 @@ namespace mRemoteNG.UI.Forms base.WndProc(ref m); } + protected override bool ProcessKeyMessage(ref Message m) + { + if (m.WParam.ToInt32() != 0x09) + return false; + + if ((m.LParam.ToInt32() & 0b00100000000000000000000000000000) == 0) // 29th bit ON means ALT key down + return false; + + Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, "ALT-TAB PRESSED"); + return base.ProcessKeyMessage(ref m); + } + private void SimulateClick(Control control) { var clientMousePosition = control.PointToClient(MousePosition); @@ -571,12 +665,24 @@ namespace mRemoteNG.UI.Forms private void ActivateConnection() { - var cw = pnlDock.ActiveDocument as ConnectionWindow; - var dp = cw?.ActiveControl as DockPane; + 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)) return; - var ifc = InterfaceControl.FindInterfaceControl(tab); - if (ifc == null) return; + //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; + 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(); @@ -585,7 +691,7 @@ namespace mRemoteNG.UI.Forms private void pnlDock_ActiveDocumentChanged(object sender, EventArgs e) { - ActivateConnection(); + //ActivateConnection(); } internal void UpdateWindowTitle() diff --git a/mRemoteV1/UI/Tabs/ConnectionTab.cs b/mRemoteV1/UI/Tabs/ConnectionTab.cs index 5788efb0..73139113 100644 --- a/mRemoteV1/UI/Tabs/ConnectionTab.cs +++ b/mRemoteV1/UI/Tabs/ConnectionTab.cs @@ -7,6 +7,7 @@ using mRemoteNG.Config; using mRemoteNG.Connection; using mRemoteNG.Connection.Protocol; using mRemoteNG.Connection.Protocol.VNC; +using mRemoteNG.Messages; using mRemoteNG.UI.TaskDialog; using WeifenLuo.WinFormsUI.Docking; @@ -14,7 +15,7 @@ namespace mRemoteNG.UI.Tabs { public partial class ConnectionTab : DockContent { - private InterfaceControl InterfaceControl => Tag as InterfaceControl; + public InterfaceControl InterfaceControl => Tag as InterfaceControl; /// ///Silent close ignores the popup asking for confirmation @@ -30,11 +31,33 @@ namespace mRemoteNG.UI.Tabs { InitializeComponent(); GotFocus += ConnectionTab_GotFocus; + Activated += OnActivated; + Click += OnClick; + } + + private void OnClick(object sender, EventArgs e) + { + Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, $"Tab clicked: '{TabText}'"); + } + + private void OnActivated(object sender, EventArgs e) + { + Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, $"Tab activated: '{TabText}'"); } private void ConnectionTab_GotFocus(object sender, EventArgs e) { - TabHelper.Instance.CurrentTab = this; + 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"); + //} } protected override void OnFormClosing(FormClosingEventArgs e) diff --git a/mRemoteV1/UI/Tabs/DockPaneStripNG.cs b/mRemoteV1/UI/Tabs/DockPaneStripNG.cs index 4e234dfe..b4588835 100644 --- a/mRemoteV1/UI/Tabs/DockPaneStripNG.cs +++ b/mRemoteV1/UI/Tabs/DockPaneStripNG.cs @@ -1088,11 +1088,19 @@ namespace mRemoteNG.UI.Tabs protected override void OnMouseDown(MouseEventArgs e) { + App.Runtime.MessageCollector.AddMessage(Messages.MessageClass.DebugMsg, "Mouse down"); base.OnMouseDown(e); // suspend drag if mouse is down on active close button. m_suspendDrag = ActiveCloseHitTest(e.Location); if (!IsMouseDown) IsMouseDown = true; + + var tabIndex = HitTest(e.Location); + var tab = Tabs[tabIndex].Content as ConnectionTab; + if (tab == null) + return; + TabHelper.Instance.CurrentTab = tab; + TabHelper.Instance.RaiseTabClickedEvent(); } protected override void OnMouseMove(MouseEventArgs e) @@ -1149,6 +1157,7 @@ namespace mRemoteNG.UI.Tabs protected override void OnMouseClick(MouseEventArgs e) { + App.Runtime.MessageCollector.AddMessage(Messages.MessageClass.DebugMsg, "Mouse click"); base.OnMouseClick(e); if (e.Button != MouseButtons.Left || Appearance != DockPane.AppearanceStyle.Document) return; diff --git a/mRemoteV1/UI/Tabs/TabHelper.cs b/mRemoteV1/UI/Tabs/TabHelper.cs index 62e102d8..0c1bee82 100644 --- a/mRemoteV1/UI/Tabs/TabHelper.cs +++ b/mRemoteV1/UI/Tabs/TabHelper.cs @@ -4,12 +4,18 @@ using System; namespace mRemoteNG.UI.Tabs { - class TabHelper + public class TabHelper { private static readonly Lazy lazyHelper = new Lazy(() => new TabHelper()); public static TabHelper Instance => lazyHelper.Value; + /// + /// Should focus events on a connection tab automatically focus + /// its child connection? + /// + public bool FocusConnection { get; set; } = true; + private TabHelper() { } @@ -21,10 +27,16 @@ namespace mRemoteNG.UI.Tabs get => currentTab; set { + if (currentTab == value) + { + Runtime.MessageCollector.AddMessage(Messages.MessageClass.DebugMsg, $"Tab already current: '{currentTab.TabText}'"); + return; + } + currentTab = value; findCurrentPanel(); - Runtime.MessageCollector.AddMessage(Messages.MessageClass.DebugMsg, - "Tab got focused: " + currentTab.TabText); + Runtime.MessageCollector.AddMessage(Messages.MessageClass.DebugMsg, $"Current tab changed: '{currentTab.TabText}'"); + RaiseActiveConnectionTabChangedEvent(); } } @@ -36,7 +48,7 @@ namespace mRemoteNG.UI.Tabs currentForm = currentForm.Parent; } - if (currentForm != null) + if (currentForm != null && CurrentPanel != currentForm) CurrentPanel = (ConnectionWindow)currentForm; } @@ -47,10 +59,31 @@ namespace mRemoteNG.UI.Tabs get => currentPanel; set { + if (currentPanel == value) + return; + currentPanel = value; - Runtime.MessageCollector.AddMessage(Messages.MessageClass.DebugMsg, - "Panel got focused: " + currentPanel.TabText); + Runtime.MessageCollector.AddMessage(Messages.MessageClass.DebugMsg, $"Current panel changed: '{currentPanel.TabText}'"); + RaiseActivePanelChangedEvent(); } } + + public event EventHandler ActivePanelChanged; + protected virtual void RaiseActivePanelChangedEvent() + { + ActivePanelChanged?.Invoke(this, EventArgs.Empty); + } + + public event EventHandler ActiveConnectionTabChanged; + protected virtual void RaiseActiveConnectionTabChangedEvent() + { + ActiveConnectionTabChanged?.Invoke(this, EventArgs.Empty); + } + + public event EventHandler TabClicked; + public void RaiseTabClickedEvent() + { + TabClicked?.Invoke(this, EventArgs.Empty); + } } } \ No newline at end of file diff --git a/mRemoteV1/UI/Window/ConnectionWindow.cs b/mRemoteV1/UI/Window/ConnectionWindow.cs index 2e759ed7..d7a67337 100644 --- a/mRemoteV1/UI/Window/ConnectionWindow.cs +++ b/mRemoteV1/UI/Window/ConnectionWindow.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Diagnostics; using System.Linq; using System.Windows.Forms; using mRemoteNG.App; @@ -97,6 +98,7 @@ namespace mRemoteNG.UI.Window private void ConnectionWindow_GotFocus(object sender, EventArgs e) { TabHelper.Instance.CurrentPanel = this; + Runtime.MessageCollector.AddMessage(MessageClass.DebugMsg, $"Connection window focused: '{TabText}'"); } public ConnectionTab AddConnectionTab(ConnectionInfo connectionInfo) diff --git a/mRemoteV1/mRemoteV1.csproj b/mRemoteV1/mRemoteV1.csproj index 7dc17adb..36d5dd3d 100644 --- a/mRemoteV1/mRemoteV1.csproj +++ b/mRemoteV1/mRemoteV1.csproj @@ -96,6 +96,9 @@ + + ..\packages\Utf8Json.1.3.7\lib\net45\Utf8Json.dll + False References\VncSharp.dll @@ -255,6 +258,7 @@ +