From a49ebf17bf659ffa8c1ed032152c81d6bd6001d4 Mon Sep 17 00:00:00 2001 From: Riley McArdle Date: Mon, 25 Mar 2013 19:29:20 -0500 Subject: [PATCH] Add PuTTY Session Settings command to the Config panel for PuTTY Saved Sessions. Fix handling of the plus (+) character in PuTTY session names. --- CHANGELOG.TXT | 2 + mRemoteV1/Config/PuttySessions.vb | 4 +- mRemoteV1/Connection/PuttySession.Info.vb | 25 ++- mRemoteV1/Forms/frmOptions.vb | 26 ++-- mRemoteV1/Language/Language.Designer.vb | 18 +++ mRemoteV1/Language/Language.resx | 6 + mRemoteV1/Tools/EnumWindows.vb | 40 +++++ mRemoteV1/Tools/ProcessController.vb | 181 ++++++++++++++++++++++ mRemoteV1/Tools/PuttyProcessController.vb | 17 ++ mRemoteV1/mRemoteV1.vbproj | 3 + 10 files changed, 294 insertions(+), 28 deletions(-) create mode 100644 mRemoteV1/Tools/EnumWindows.vb create mode 100644 mRemoteV1/Tools/ProcessController.vb create mode 100644 mRemoteV1/Tools/PuttyProcessController.vb diff --git a/CHANGELOG.TXT b/CHANGELOG.TXT index cf1e5fa13..8b018bf23 100644 --- a/CHANGELOG.TXT +++ b/CHANGELOG.TXT @@ -1,9 +1,11 @@ Fixed issue MR-392 - Sessions Panel - context menu entries need to be context aware Fixed issue MR-422 - Gives error Object reference not set to an instance of an object. Fixed issue MR-424 - Import of a few Linux SSH2 hosts discovered via the port scan tool results in a UE + Added PuTTY Session Settings command to the Config panel for PuTTY Saved Sessions. Fixed an exception or crash when choosing unnamed colors for themes. Fixed possible error "Control does not support transparent background colors" when modifying themes. Fixed changes to the active theme not being saved reliably. + Fixed handling of the plus (+) character in PuTTY session names. Changed Internet Explorer to no longer force IE7 compatibility mode. Changed the "Launch PuTTY" button in the "Options" dialog to open PuTTY from the path the user has currently set, instead of what was previously saved. diff --git a/mRemoteV1/Config/PuttySessions.vb b/mRemoteV1/Config/PuttySessions.vb index e7a9ab81c..0f22c10c5 100644 --- a/mRemoteV1/Config/PuttySessions.vb +++ b/mRemoteV1/Config/PuttySessions.vb @@ -65,7 +65,7 @@ Namespace Config If raw Then sessionNames.Add(sessionName) Else - sessionNames.Add(Web.HttpUtility.UrlDecode(sessionName)) + sessionNames.Add(Web.HttpUtility.UrlDecode(sessionName.Replace("+", "%2B"))) End If Next Return sessionNames.ToArray() @@ -89,7 +89,7 @@ Namespace Config Dim sessionKey As RegistryKey = sessionsKey.OpenSubKey(sessionName) If sessionKey Is Nothing Then Return Nothing - sessionName = Web.HttpUtility.UrlDecode(sessionName) + sessionName = Web.HttpUtility.UrlDecode(sessionName.Replace("+", "%2B")) Dim sessionInfo As New Connection.PuttySession.Info With sessionInfo diff --git a/mRemoteV1/Connection/PuttySession.Info.vb b/mRemoteV1/Connection/PuttySession.Info.vb index 465d61387..2acd6419e 100644 --- a/mRemoteV1/Connection/PuttySession.Info.vb +++ b/mRemoteV1/Connection/PuttySession.Info.vb @@ -13,22 +13,19 @@ Namespace Connection #Region "Commands" _ - Public Sub LaunchPutty() + LocalizedDisplayName("strPuttySessionSettings")> _ + Public Sub SessionSettings() Try - Dim process As New Process - With process.StartInfo - .UseShellExecute = False - If Settings.UseCustomPuttyPath Then - .FileName = Settings.CustomPuttyPath - Else - .FileName = App.Info.General.PuttyPath - End If - End With - process.Start() - process.WaitForExit() + Dim puttyProcess As New PuttyProcessController + If Not puttyProcess.Start() Then Return + If puttyProcess.SelectListBoxItem(PuttySession) Then + puttyProcess.ClickButton("&Load") + End If + puttyProcess.SetControlText("Button", "&Cancel", "&Close") + puttyProcess.SetControlVisible("Button", "&Open", False) + puttyProcess.WaitForExit() Catch ex As Exception - MessageCollector.AddMessage(MessageClass.ErrorMsg, Language.strPuttyStartFailed & vbNewLine & ex.Message, True) + MessageCollector.AddMessage(MessageClass.ErrorMsg, Language.strErrorCouldNotLaunchPutty & vbNewLine & ex.Message, False) End Try End Sub #End Region diff --git a/mRemoteV1/Forms/frmOptions.vb b/mRemoteV1/Forms/frmOptions.vb index 437bd5951..4d5b8c505 100644 --- a/mRemoteV1/Forms/frmOptions.vb +++ b/mRemoteV1/Forms/frmOptions.vb @@ -2,6 +2,8 @@ Imports System.ComponentModel Imports mRemoteNG.Messages Imports mRemoteNG.My +Imports mRemoteNG.Tools +Imports PSTaskDialog Imports WeifenLuo.WinFormsUI.Docking Imports mRemoteNG.App.Runtime Imports mRemoteNG.Themes @@ -2059,19 +2061,19 @@ Public Class frmOptions Private Sub btnLaunchPutty_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnLaunchPutty.Click Try - Dim process As New Process - With process.StartInfo - .UseShellExecute = False - If chkUseCustomPuttyPath.Checked Then - .FileName = txtCustomPuttyPath.Text - Else - .FileName = App.Info.General.PuttyPath - End If - End With - process.Start() - process.WaitForExit() + Dim puttyProcess As New PuttyProcessController + Dim fileName As String + If chkUseCustomPuttyPath.Checked Then + fileName = txtCustomPuttyPath.Text + Else + fileName = App.Info.General.PuttyPath + End If + puttyProcess.Start(fileName) + puttyProcess.SetControlText("Button", "&Cancel", "&Close") + puttyProcess.SetControlVisible("Button", "&Open", False) + puttyProcess.WaitForExit() Catch ex As Exception - MessageCollector.AddMessage(MessageClass.ErrorMsg, Language.strPuttyStartFailed & vbNewLine & ex.Message, True) + cTaskDialog.MessageBox(Application.Info.ProductName, Language.strErrorCouldNotLaunchPutty, "", ex.Message, "", "", eTaskDialogButtons.OK, eSysIcons.Error, Nothing) End Try End Sub diff --git a/mRemoteV1/Language/Language.Designer.vb b/mRemoteV1/Language/Language.Designer.vb index d87010b33..e8d677df4 100644 --- a/mRemoteV1/Language/Language.Designer.vb +++ b/mRemoteV1/Language/Language.Designer.vb @@ -1542,6 +1542,15 @@ Namespace My End Get End Property + ''' + ''' Looks up a localized string similar to PuTTY could not be launched.. + ''' + Friend Shared ReadOnly Property strErrorCouldNotLaunchPutty() As String + Get + Return ResourceManager.GetString("strErrorCouldNotLaunchPutty", resourceCulture) + End Get + End Property + ''' ''' Looks up a localized string similar to Decryption failed. {0}. ''' @@ -4394,6 +4403,15 @@ Namespace My End Get End Property + ''' + ''' Looks up a localized string similar to PuTTY Session Settings. + ''' + Friend Shared ReadOnly Property strPuttySessionSettings() As String + Get + Return ResourceManager.GetString("strPuttySessionSettings", resourceCulture) + End Get + End Property + ''' ''' Looks up a localized string similar to PuTTY Settings. ''' diff --git a/mRemoteV1/Language/Language.resx b/mRemoteV1/Language/Language.resx index 1820e4dbc..be3084cdf 100644 --- a/mRemoteV1/Language/Language.resx +++ b/mRemoteV1/Language/Language.resx @@ -2226,4 +2226,10 @@ mRemoteNG will now quit and begin with the installation. Retrieve + + PuTTY Session Settings + + + PuTTY could not be launched. + \ No newline at end of file diff --git a/mRemoteV1/Tools/EnumWindows.vb b/mRemoteV1/Tools/EnumWindows.vb new file mode 100644 index 000000000..a0ae28d17 --- /dev/null +++ b/mRemoteV1/Tools/EnumWindows.vb @@ -0,0 +1,40 @@ +Namespace Tools + Public Class EnumWindows + Public Shared Function EnumWindows() As List(Of IntPtr) + Dim handleList As New List(Of IntPtr) + + HandleLists.Add(handleList) + Dim handleIndex As Integer = HandleLists.IndexOf(handleList) + Win32.EnumWindows(AddressOf EnumCallback, handleIndex) + HandleLists.Remove(handleList) + + Return handleList + End Function + + Public Shared Function EnumChildWindows(ByVal hWndParent As IntPtr) As List(Of IntPtr) + Dim handleList As New List(Of IntPtr) + + HandleLists.Add(handleList) + Dim handleIndex As Integer = HandleLists.IndexOf(handleList) + Win32.EnumChildWindows(hWndParent, AddressOf EnumCallback, handleIndex) + HandleLists.Remove(handleList) + + Return handleList + End Function + + Private Shared ReadOnly HandleLists As New List(Of List(Of IntPtr)) + + Private Shared Function EnumCallback(hwnd As Integer, lParam As Integer) As Boolean + HandleLists(lParam).Add(hwnd) + Return True + End Function + + ' ReSharper disable ClassNeverInstantiated.Local + Private Class Win32 + ' ReSharper restore ClassNeverInstantiated.Local + Public Delegate Function EnumWindowsProc(ByVal hwnd As Integer, ByVal lParam As Integer) As Boolean + Declare Function EnumWindows Lib "user32" (ByVal lpEnumFunc As EnumWindowsProc, ByVal lParam As Integer) As Boolean + Declare Function EnumChildWindows Lib "user32" (ByVal hWndParent As IntPtr, ByVal lpEnumFunc As EnumWindowsProc, ByVal lParam As Integer) As Boolean + End Class + End Class +End Namespace diff --git a/mRemoteV1/Tools/ProcessController.vb b/mRemoteV1/Tools/ProcessController.vb new file mode 100644 index 000000000..d5fe6ddd5 --- /dev/null +++ b/mRemoteV1/Tools/ProcessController.vb @@ -0,0 +1,181 @@ +Imports System.Runtime.InteropServices +Imports System.Text +Imports mRemoteNG.My + +Namespace Tools + Public Class ProcessController +#Region "Public Methods" + Public Function Start(ByVal fileName As String, Optional ByVal arguments As CommandLineArguments = Nothing) As Boolean + With Process.StartInfo + .UseShellExecute = False + .FileName = fileName + If arguments IsNot Nothing Then .Arguments = arguments.ToString() + End With + + If Not Process.Start() Then Return False + GetMainWindowHandle() + + Return True + End Function + + Public Function SetControlVisible(ByVal className As String, ByVal text As String, Optional ByVal visible As Boolean = True) As Boolean + If Process Is Nothing OrElse Process.HasExited Then Return False + If Handle = IntPtr.Zero Then Return False + + Dim controlHandle As IntPtr = GetControlHandle(className, text) + If controlHandle = IntPtr.Zero Then Return False + + Dim nCmdShow As Integer + If visible Then + nCmdShow = Win32.SW_SHOW + Else + nCmdShow = Win32.SW_HIDE + End If + + Win32.ShowWindow(controlHandle, nCmdShow) + + Return True + End Function + + Public Function SetControlText(ByVal className As String, ByVal oldText As String, ByVal newText As String) As Boolean + If Process Is Nothing OrElse Process.HasExited Then Return False + If Handle = IntPtr.Zero Then Return False + + Dim controlHandle As IntPtr = GetControlHandle(className, oldText) + If controlHandle = IntPtr.Zero Then Return False + + Dim result As IntPtr = Win32.SendMessage(controlHandle, Win32.WM_SETTEXT, 0, New StringBuilder(newText)) + If Not result.ToInt32() = Win32.TRUE Then Return False + + Return True + End Function + + Public Function SelectListBoxItem(ByVal itemText As String) As Boolean + If Process Is Nothing OrElse Process.HasExited Then Return False + If Handle = IntPtr.Zero Then Return False + + Dim listBoxHandle As IntPtr = GetControlHandle("ListBox") + If listBoxHandle = IntPtr.Zero Then Return False + + Dim result As IntPtr = Win32.SendMessage(listBoxHandle, Win32.LB_SELECTSTRING, -1, New StringBuilder(itemText)) + If result.ToInt32() = Win32.LB_ERR Then Return False + + Return True + End Function + + Public Function ClickButton(ByVal text As String) As Boolean + If Process Is Nothing OrElse Process.HasExited Then Return False + If Handle = IntPtr.Zero Then Return False + + Dim buttonHandle As IntPtr = GetControlHandle("Button", text) + If buttonHandle = IntPtr.Zero Then Return False + + Dim buttonControlId As Integer = Win32.GetDlgCtrlID(buttonHandle) + Win32.SendMessage(Handle, Win32.WM_COMMAND, buttonControlId, buttonHandle) + + Return True + End Function + + Public Sub WaitForExit() + If Process Is Nothing OrElse Process.HasExited Then Return + Process.WaitForExit() + End Sub +#End Region + +#Region "Protected Fields" + Protected Process As New Process + Protected Handle As IntPtr = IntPtr.Zero + Protected Controls As New List(Of IntPtr) +#End Region + +#Region "Protected Methods" + Protected Function GetMainWindowHandle() As IntPtr + If Process Is Nothing OrElse Process.HasExited Then Return IntPtr.Zero + + Process.WaitForInputIdle(Settings.MaxPuttyWaitTime * 1000) + + Handle = IntPtr.Zero + Dim startTicks As Integer = Environment.TickCount + While Handle = IntPtr.Zero And Environment.TickCount < startTicks + (Settings.MaxPuttyWaitTime * 1000) + Process.Refresh() + Handle = Process.MainWindowHandle + If Handle = IntPtr.Zero Then Threading.Thread.Sleep(0) + End While + + Return Handle + End Function + + Protected Function GetControlHandle(ByVal className As String, Optional ByVal text As String = "") As IntPtr + If Process Is Nothing OrElse Process.HasExited Then Return IntPtr.Zero + If Handle = IntPtr.Zero Then Return IntPtr.Zero + + If Controls.Count = 0 Then + Controls = EnumWindows.EnumChildWindows(Handle) + End If + + Dim stringBuilder As New System.Text.StringBuilder + Dim controlHandle As IntPtr = IntPtr.Zero + For Each control As IntPtr In Controls + Win32.GetClassName(control, stringBuilder, stringBuilder.Capacity) + If (stringBuilder.ToString() = className) Then + If String.IsNullOrEmpty(text) Then + controlHandle = control + Exit For + Else + Win32.SendMessage(control, Win32.WM_GETTEXT, stringBuilder.Capacity, stringBuilder) + If (stringBuilder.ToString() = text) Then + controlHandle = control + Exit For + End If + End If + End If + Next + + Return controlHandle + End Function +#End Region + +#Region "Win32" + ' ReSharper disable ClassNeverInstantiated.Local + Private Class Win32 + ' ReSharper restore ClassNeverInstantiated.Local + ' ReSharper disable InconsistentNaming + ' ReSharper disable UnusedMethodReturnValue.Local + _ + Public Shared Sub GetClassName(ByVal hWnd As IntPtr, ByVal lpClassName As System.Text.StringBuilder, ByVal nMaxCount As Integer) + End Sub + + _ + Public Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal Msg As UInteger, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr + End Function + + _ + Public Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal Msg As UInteger, ByVal wParam As IntPtr, ByVal lParam As System.Text.StringBuilder) As IntPtr + End Function + + _ + Public Shared Function GetDlgCtrlID(ByVal hwndCtl As Integer) As Integer + End Function + + _ + Public Shared Function ShowWindow(ByVal hWnd As IntPtr, ByVal nCmdShow As Integer) As Boolean + End Function + + Public Const LB_ERR As Integer = -1 + Public Const LB_SELECTSTRING As Integer = &H18C + + Public Const WM_SETTEXT As Integer = &HC + Public Const WM_GETTEXT As Integer = &HD + Public Const WM_COMMAND As Integer = &H111 + + Public Const SW_HIDE As Integer = 0 + Public Const SW_SHOW As Integer = 5 + + Public Const [TRUE] As Integer = 1 + ' ReSharper restore UnusedMethodReturnValue.Local + ' ReSharper restore InconsistentNaming + End Class +#End Region + End Class +End Namespace + diff --git a/mRemoteV1/Tools/PuttyProcessController.vb b/mRemoteV1/Tools/PuttyProcessController.vb new file mode 100644 index 000000000..6b1757fb2 --- /dev/null +++ b/mRemoteV1/Tools/PuttyProcessController.vb @@ -0,0 +1,17 @@ +Imports mRemoteNG.My + +Namespace Tools + Public Class PuttyProcessController + Inherits ProcessController + Public Overloads Function Start(Optional ByVal arguments As CommandLineArguments = Nothing) As Boolean + Dim filename As String + If Settings.UseCustomPuttyPath Then + filename = Settings.CustomPuttyPath + Else + filename = App.Info.General.PuttyPath + End If + Return Start(filename, arguments) + End Function + End Class +End Namespace + diff --git a/mRemoteV1/mRemoteV1.vbproj b/mRemoteV1/mRemoteV1.vbproj index d787c053c..59441f56f 100644 --- a/mRemoteV1/mRemoteV1.vbproj +++ b/mRemoteV1/mRemoteV1.vbproj @@ -188,6 +188,7 @@ + @@ -272,6 +273,8 @@ + + ReconnectGroup.vb