11using CoreMeter ;
22using Newtonsoft . Json ;
3+ using Renci . SshNet ;
34using System ;
45using System . Collections . Generic ;
56using System . Collections . ObjectModel ;
67using System . Diagnostics ;
78using System . IO ;
9+ using System . Linq ;
810using System . Management ;
911using System . Text . RegularExpressions ;
1012using System . Threading . Tasks ;
@@ -18,29 +20,44 @@ namespace Background_Terminal
1820{
1921 public partial class MainWindow : Window
2022 {
23+ // Static Fields
2124 private static BrushConverter _brushConverter = new BrushConverter ( ) ;
2225
2326 private static DirectoryInfo _appDataDirectory = new DirectoryInfo ( System . IO . Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . LocalApplicationData ) , "BackgroundTerminal" ) ) ;
2427 private static string _configFile = "config.json" ;
2528 private static string _configPath = System . IO . Path . Combine ( _appDataDirectory . FullName , _configFile ) ;
2629
27- private CoreMeterUtility _coreMeterUtility ;
28-
30+ // TerminalWindow
2931 private TerminalWindow _terminalWindow ;
3032
33+ // Main Process
34+ private Process _process ;
35+
36+ // CoreMeter
37+ private CoreMeterUtility _coreMeterUtility ;
38+
39+ // Settings Container
3140 private BackgroundTerminalSettings _settings ;
3241
33- private Process _process ;
42+ // SSH Handling
43+ private SshClient _sshClient ;
44+ private bool _sshMode = false ;
45+ private string _sshServer = String . Empty ;
46+ private string _sshUsername = String . Empty ;
47+ private string _sshCurrentDirectory = String . Empty ;
3448
49+ // UI List Bindings
3550 private ObservableCollection < string > _terminalData = new ObservableCollection < string > ( ) ;
3651 public ObservableCollection < NewlineTrigger > NewlineTriggers { get ; set ; }
3752
38- private string _processPath ;
53+ // Newline State Handling
3954 private string _currentTrigger = null ;
4055 private string _newlineString = Environment . NewLine ;
4156
57+ // CMD Process ID
4258 private int _cmdProcessId ;
4359
60+ // TerminalWindow UI State Handling
4461 private bool _terminalWindowActive = false ;
4562 private bool _terminalWindowLocked = true ;
4663
@@ -50,12 +67,13 @@ public partial class MainWindow : Window
5067 private Key ? _key1 = null ;
5168 private Key ? _key2 = null ;
5269
70+ #region Constructor
5371 public MainWindow ( )
5472 {
5573 InitializeComponent ( ) ;
5674
5775 // Create TerminalWindow
58- _terminalWindow = new TerminalWindow ( SendCommand , KillChildren , TerminalWindowUpdate ) ;
76+ _terminalWindow = new TerminalWindow ( SendCommand , KillProcess , TerminalWindowUIUpdate ) ;
5977 _terminalWindow . Show ( ) ;
6078
6179 // Apply changes in terminal data to TerminalWindow
@@ -132,7 +150,46 @@ public MainWindow()
132150
133151 DataContext = this ;
134152 }
153+ #endregion
154+
155+ #region General Functions
156+ private void KillProcess ( )
157+ {
158+ if ( _sshMode )
159+ {
160+ _sshClient . Disconnect ( ) ;
161+ _sshClient . Dispose ( ) ;
162+
163+ _sshMode = false ;
164+
165+ _terminalData . Add ( "SSH Session Disconnected" ) ;
166+ }
167+ else
168+ {
169+ KillChildren ( ) ;
170+ }
171+ }
172+
173+ private void OutputSSHUsage ( )
174+ {
175+ _terminalData . Add ( "Background Terminal manually handles SSH connection. (Ctrl + C to quit)" ) ;
176+ _terminalData . Add ( "Usage: ssh <server>" ) ;
177+ _terminalData . Add ( "Note that SSH.net does not support change directory (cd), so you are required to prefix the " +
178+ "command with a (cd) call to the directory you want to be in. (cd /my/directory && mycommand)" ) ;
179+ _terminalData . Add ( "To get around this, I have implemented automated directory prefixing. If you call (cd) while in SSH mode, it will automatically prefix any " +
180+ "further commands with the directory you previously specified." ) ;
181+ }
182+
183+ private string DirectoryPrefixCommand ( string command )
184+ {
185+ if ( ! _sshCurrentDirectory . Equals ( String . Empty ) )
186+ return "cd " + _sshCurrentDirectory + " && " + command ;
187+
188+ return command ;
189+ }
190+ #endregion
135191
192+ #region UI State Functions
136193 private void ApplySettingsToTerminalWindow ( )
137194 {
138195 _terminalWindow . TerminalData_TextBox . FontSize = _settings . FontSize ;
@@ -143,10 +200,19 @@ private void ApplySettingsToTerminalWindow()
143200 _terminalWindow . Top = _settings . PosY ;
144201 _terminalWindow . Width = _settings . Width ;
145202 _terminalWindow . Height = _settings . Height ;
203+ }
204+
205+ private void TerminalWindowUIUpdate ( )
206+ {
207+ PosX_TextBox . Text = _terminalWindow . Left . ToString ( ) ;
208+ PosY_TextBox . Text = _terminalWindow . Top . ToString ( ) ;
146209
147- _terminalWindow . UpdateTerminalDataTextBoxMargin ( ) ;
210+ Width_TextBox . Text = _terminalWindow . Width . ToString ( ) ;
211+ Height_TextBox . Text = _terminalWindow . Height . ToString ( ) ;
148212 }
213+ #endregion
149214
215+ #region Process Helper Functions
150216 private List < Process > GetProcessChildren ( )
151217 {
152218 List < Process > children = new List < Process > ( ) ;
@@ -160,6 +226,20 @@ private List<Process> GetProcessChildren()
160226 return children ;
161227 }
162228
229+ private void KillChildren ( )
230+ {
231+ List < Process > children = GetProcessChildren ( ) ;
232+
233+ foreach ( Process child in children )
234+ {
235+ if ( ! child . Id . Equals ( _cmdProcessId ) )
236+ {
237+ child . Kill ( ) ;
238+ }
239+ }
240+ }
241+ #endregion
242+
163243 #region Terminal Data Handlers
164244 private async Task < int > RunTerminalProcessAsync ( )
165245 {
@@ -182,8 +262,8 @@ private async Task<int> RunTerminalProcessAsync()
182262 _process . StartInfo . RedirectStandardError = true ;
183263
184264 _process . EnableRaisingEvents = true ;
185- _process . OutputDataReceived += CMD_OutputDataReceived ;
186- _process . ErrorDataReceived += CMD_ErrorDataReceived ;
265+ _process . OutputDataReceived += OutputDataReceived ;
266+ _process . ErrorDataReceived += ErrorDataReceived ;
187267
188268 _process . Exited += new EventHandler ( ( sender , args ) =>
189269 {
@@ -216,33 +296,162 @@ private async Task<int> RunTerminalProcessAsync()
216296 return await taskCompletionSource . Task ;
217297 }
218298
219- private void CMD_OutputDataReceived ( object sender , DataReceivedEventArgs e )
299+ private void OutputDataReceived ( object sender , DataReceivedEventArgs e )
220300 {
221301 _terminalData . Add ( e . Data ) ;
222302 }
223303
224- private void CMD_ErrorDataReceived ( object sender , DataReceivedEventArgs e )
304+ private void ErrorDataReceived ( object sender , DataReceivedEventArgs e )
225305 {
226306 _terminalData . Add ( e . Data ) ;
227307 }
228308
229- private void SendCommand ( string command , bool output = true )
309+ private string SendCommandSSH ( string command , bool silent = false )
310+ {
311+ // Handle SSH login connection
312+ if ( _sshUsername . Equals ( String . Empty ) )
313+ {
314+ _sshUsername = command ;
315+ _terminalData . Add ( "Enter password:" ) ;
316+
317+ _terminalWindow . _passwordMode = true ;
318+ }
319+ else if ( _terminalWindow . _passwordMode )
320+ {
321+ _terminalData . Add ( "Connecting..." ) ;
322+
323+ // Attempt connection
324+ _sshClient = new SshClient ( _sshServer , _sshUsername , _terminalWindow . _password ) ;
325+ try
326+ {
327+ _sshClient . Connect ( ) ;
328+
329+ _terminalWindow . _passwordMode = false ;
330+ _terminalWindow . _password = String . Empty ;
331+
332+ if ( _sshClient . IsConnected )
333+ _terminalData . Add ( "Connected to " + _sshServer ) ;
334+ else
335+ {
336+ _terminalData . Add ( "There was a problem connecting." ) ;
337+
338+ _sshMode = false ;
339+ _sshUsername = String . Empty ;
340+ }
341+ }
342+ catch ( Exception e )
343+ {
344+ _terminalData . Add ( e . Message ) ;
345+ }
346+ }
347+
348+ // Handle SSH commands
349+ else
350+ {
351+ if ( _sshClient . IsConnected )
352+ {
353+ try
354+ {
355+ SshCommand sshCommand = _sshClient . CreateCommand ( command ) ;
356+ string result = sshCommand . Execute ( ) ;
357+
358+ StreamReader reader = new StreamReader ( sshCommand . ExtendedOutputStream ) ;
359+ string extendedResult = reader . ReadToEnd ( ) ;
360+
361+ if ( result . Length > 0 && ( result [ result . Length - 1 ] == '\n ' || result [ result . Length - 1 ] == '\r ' ) )
362+ result = result . Substring ( 0 , result . Length - 1 ) ;
363+
364+ // Handle silent calls to pwd maintain SSH current directory
365+ if ( silent )
366+ return result ;
367+
368+ if ( extendedResult . Length > 0 && ( extendedResult [ extendedResult . Length - 1 ] == '\n ' || extendedResult [ extendedResult . Length - 1 ] == '\r ' ) )
369+ extendedResult = extendedResult . Substring ( 0 , extendedResult . Length - 1 ) ;
370+
371+ if ( ! result . Equals ( String . Empty ) )
372+ _terminalData . Add ( result ) ;
373+
374+ if ( ! extendedResult . Equals ( String . Empty ) )
375+ _terminalData . Add ( extendedResult ) ;
376+
377+ }
378+ catch ( Exception e )
379+ {
380+ _terminalData . Add ( e . Message ) ;
381+ }
382+ }
383+ else
384+ {
385+ _terminalData . Add ( "You are no longer connected to SSH. Exiting." ) ;
386+
387+ _sshMode = false ;
388+ _sshUsername = String . Empty ;
389+ }
390+ }
391+
392+ return null ;
393+ }
394+
395+ private void SendCommandBGT ( string command )
230396 {
397+ string bgtCommand = command . Split ( ' ' ) [ 1 ] ;
398+ string [ ] parameters = command . Substring ( command . IndexOf ( bgtCommand ) + bgtCommand . Length + 1 ) . Split ( ' ' ) ;
399+
400+ if ( bgtCommand . Equals ( "newline" ) )
401+ {
402+ _newlineString = Regex . Unescape ( parameters [ 0 ] ) ;
403+ }
404+ }
405+
406+ private void SendCommand ( string command )
407+ {
408+
409+
410+ // Handle SSH mode
411+ if ( _sshMode )
412+ {
413+ _terminalData . Add ( DirectoryPrefixCommand ( command ) ) ;
414+ SendCommandSSH ( DirectoryPrefixCommand ( command ) ) ;
415+
416+ if ( command . ToLower ( ) . StartsWith ( "cd" ) )
417+ _sshCurrentDirectory = SendCommandSSH ( command + " && pwd" , true ) ;
418+ }
419+
231420 // Background-Terminal application commands
232- if ( command . StartsWith ( "bgt" ) )
421+ else if ( command . ToLower ( ) . StartsWith ( "bgt" ) )
233422 {
234- string bgtCommand = command . Split ( ' ' ) [ 1 ] ;
235- string [ ] parameters = command . Substring ( command . IndexOf ( bgtCommand ) + bgtCommand . Length + 1 ) . Split ( ' ' ) ;
423+ _terminalData . Add ( command ) ;
424+ SendCommandBGT ( command ) ;
425+ }
426+
427+ // Initialize SSH mode
428+ else if ( command . ToLower ( ) . StartsWith ( "ssh" ) )
429+ {
430+ _terminalData . Add ( command ) ;
236431
237- if ( bgtCommand . Equals ( "newline" ) )
432+ List < string > commandParams = command . Split ( ' ' ) . ToList ( ) ;
433+
434+ if ( commandParams . Count != 2 )
238435 {
239- _newlineString = Regex . Unescape ( parameters [ 0 ] ) ;
436+ OutputSSHUsage ( ) ;
437+ }
438+ else
439+ {
440+ _sshServer = commandParams [ 1 ] ;
441+
442+ OutputSSHUsage ( ) ;
443+
444+ _terminalData . Add ( "" ) ;
445+ _terminalData . Add ( "Enter username:" ) ;
446+
447+ _sshMode = true ;
240448 }
241449 }
450+
451+ // Standard command handling
242452 else
243453 {
244- if ( output )
245- _terminalData . Add ( command ) ;
454+ _terminalData . Add ( command ) ;
246455
247456 _process . StandardInput . NewLine = _newlineString ;
248457 _process . StandardInput . WriteLine ( command ) ;
@@ -265,28 +474,6 @@ private void SendCommand(string command, bool output = true)
265474 }
266475 }
267476 }
268-
269- private void KillChildren ( )
270- {
271- List < Process > children = GetProcessChildren ( ) ;
272-
273- foreach ( Process child in children )
274- {
275- if ( ! child . Id . Equals ( _cmdProcessId ) )
276- {
277- child . Kill ( ) ;
278- }
279- }
280- }
281-
282- private void TerminalWindowUpdate ( )
283- {
284- PosX_TextBox . Text = _terminalWindow . Left . ToString ( ) ;
285- PosY_TextBox . Text = _terminalWindow . Top . ToString ( ) ;
286-
287- Width_TextBox . Text = _terminalWindow . Width . ToString ( ) ;
288- Height_TextBox . Text = _terminalWindow . Height . ToString ( ) ;
289- }
290477 #endregion
291478
292479 #region Event Handlers
@@ -484,6 +671,10 @@ private void MinimizeButton_Click(object sender, RoutedEventArgs e)
484671
485672 private void ExitButton_Click ( object sender , RoutedEventArgs e )
486673 {
674+ _sshClient . Disconnect ( ) ;
675+ _sshClient . Dispose ( ) ;
676+ _process . Kill ( ) ;
677+
487678 Close ( ) ;
488679 }
489680
0 commit comments