Add PuTTY Session Settings command to the Config panel for PuTTY Saved Sessions.

Fix handling of the plus (+) character in PuTTY session names.
This commit is contained in:
Riley McArdle
2013-03-25 19:29:20 -05:00
parent 903a9dd5cc
commit a49ebf17bf
10 changed files with 294 additions and 28 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -13,22 +13,19 @@ Namespace Connection
#Region "Commands"
<Command(),
DisplayName("Edit in PuTTY")> _
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

View File

@@ -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

View File

@@ -1542,6 +1542,15 @@ Namespace My
End Get
End Property
'''<summary>
''' Looks up a localized string similar to PuTTY could not be launched..
'''</summary>
Friend Shared ReadOnly Property strErrorCouldNotLaunchPutty() As String
Get
Return ResourceManager.GetString("strErrorCouldNotLaunchPutty", resourceCulture)
End Get
End Property
'''<summary>
''' Looks up a localized string similar to Decryption failed. {0}.
'''</summary>
@@ -4394,6 +4403,15 @@ Namespace My
End Get
End Property
'''<summary>
''' Looks up a localized string similar to PuTTY Session Settings.
'''</summary>
Friend Shared ReadOnly Property strPuttySessionSettings() As String
Get
Return ResourceManager.GetString("strPuttySessionSettings", resourceCulture)
End Get
End Property
'''<summary>
''' Looks up a localized string similar to PuTTY Settings.
'''</summary>

View File

@@ -2226,4 +2226,10 @@ mRemoteNG will now quit and begin with the installation.</value>
<data name="strMenuSessionRetrieve" xml:space="preserve">
<value>Retrieve</value>
</data>
<data name="strPuttySessionSettings" xml:space="preserve">
<value>PuTTY Session Settings</value>
</data>
<data name="strErrorCouldNotLaunchPutty" xml:space="preserve">
<value>PuTTY could not be launched.</value>
</data>
</root>

View File

@@ -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

View File

@@ -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
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Public Shared Sub GetClassName(ByVal hWnd As IntPtr, ByVal lpClassName As System.Text.StringBuilder, ByVal nMaxCount As Integer)
End Sub
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Public Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal Msg As UInteger, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr
End Function
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
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
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Public Shared Function GetDlgCtrlID(ByVal hwndCtl As Integer) As Integer
End Function
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
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

View File

@@ -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

View File

@@ -188,6 +188,7 @@
<Compile Include="Config\Config.Settings.Save.vb" />
<Compile Include="Config\ConfirmClose.vb" />
<Compile Include="Config\PuttySessions.vb" />
<Compile Include="Tools\EnumWindows.vb" />
<Compile Include="Tools\PropertyGridCommandSite.vb" />
<Compile Include="Connection\PuttySession.Info.vb" />
<Compile Include="Root\PuttySessions.Info.vb" />
@@ -272,6 +273,8 @@
<Compile Include="Security\Security.Impersonator.vb" />
<Compile Include="Security\Security.Save.vb" />
<Compile Include="Tools\IeBrowserEmulation.vb" />
<Compile Include="Tools\ProcessController.vb" />
<Compile Include="Tools\PuttyProcessController.vb" />
<Compile Include="Tools\ReconnectGroup.Designer.vb">
<DependentUpon>ReconnectGroup.vb</DependentUpon>
</Compile>