1
1
using CoreMeter ;
2
2
using Newtonsoft . Json ;
3
+ using Renci . SshNet ;
3
4
using System ;
4
5
using System . Collections . Generic ;
5
6
using System . Collections . ObjectModel ;
6
7
using System . Diagnostics ;
7
8
using System . IO ;
9
+ using System . Linq ;
8
10
using System . Management ;
9
11
using System . Text . RegularExpressions ;
10
12
using System . Threading . Tasks ;
@@ -18,29 +20,44 @@ namespace Background_Terminal
18
20
{
19
21
public partial class MainWindow : Window
20
22
{
23
+ // Static Fields
21
24
private static BrushConverter _brushConverter = new BrushConverter ( ) ;
22
25
23
26
private static DirectoryInfo _appDataDirectory = new DirectoryInfo ( System . IO . Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . LocalApplicationData ) , "BackgroundTerminal" ) ) ;
24
27
private static string _configFile = "config.json" ;
25
28
private static string _configPath = System . IO . Path . Combine ( _appDataDirectory . FullName , _configFile ) ;
26
29
27
- private CoreMeterUtility _coreMeterUtility ;
28
-
30
+ // TerminalWindow
29
31
private TerminalWindow _terminalWindow ;
30
32
33
+ // Main Process
34
+ private Process _process ;
35
+
36
+ // CoreMeter
37
+ private CoreMeterUtility _coreMeterUtility ;
38
+
39
+ // Settings Container
31
40
private BackgroundTerminalSettings _settings ;
32
41
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 ;
34
48
49
+ // UI List Bindings
35
50
private ObservableCollection < string > _terminalData = new ObservableCollection < string > ( ) ;
36
51
public ObservableCollection < NewlineTrigger > NewlineTriggers { get ; set ; }
37
52
38
- private string _processPath ;
53
+ // Newline State Handling
39
54
private string _currentTrigger = null ;
40
55
private string _newlineString = Environment . NewLine ;
41
56
57
+ // CMD Process ID
42
58
private int _cmdProcessId ;
43
59
60
+ // TerminalWindow UI State Handling
44
61
private bool _terminalWindowActive = false ;
45
62
private bool _terminalWindowLocked = true ;
46
63
@@ -50,12 +67,13 @@ public partial class MainWindow : Window
50
67
private Key ? _key1 = null ;
51
68
private Key ? _key2 = null ;
52
69
70
+ #region Constructor
53
71
public MainWindow ( )
54
72
{
55
73
InitializeComponent ( ) ;
56
74
57
75
// Create TerminalWindow
58
- _terminalWindow = new TerminalWindow ( SendCommand , KillChildren , TerminalWindowUpdate ) ;
76
+ _terminalWindow = new TerminalWindow ( SendCommand , KillProcess , TerminalWindowUIUpdate ) ;
59
77
_terminalWindow . Show ( ) ;
60
78
61
79
// Apply changes in terminal data to TerminalWindow
@@ -132,7 +150,46 @@ public MainWindow()
132
150
133
151
DataContext = this ;
134
152
}
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
135
191
192
+ #region UI State Functions
136
193
private void ApplySettingsToTerminalWindow ( )
137
194
{
138
195
_terminalWindow . TerminalData_TextBox . FontSize = _settings . FontSize ;
@@ -143,10 +200,19 @@ private void ApplySettingsToTerminalWindow()
143
200
_terminalWindow . Top = _settings . PosY ;
144
201
_terminalWindow . Width = _settings . Width ;
145
202
_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 ( ) ;
146
209
147
- _terminalWindow . UpdateTerminalDataTextBoxMargin ( ) ;
210
+ Width_TextBox . Text = _terminalWindow . Width . ToString ( ) ;
211
+ Height_TextBox . Text = _terminalWindow . Height . ToString ( ) ;
148
212
}
213
+ #endregion
149
214
215
+ #region Process Helper Functions
150
216
private List < Process > GetProcessChildren ( )
151
217
{
152
218
List < Process > children = new List < Process > ( ) ;
@@ -160,6 +226,20 @@ private List<Process> GetProcessChildren()
160
226
return children ;
161
227
}
162
228
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
+
163
243
#region Terminal Data Handlers
164
244
private async Task < int > RunTerminalProcessAsync ( )
165
245
{
@@ -182,8 +262,8 @@ private async Task<int> RunTerminalProcessAsync()
182
262
_process . StartInfo . RedirectStandardError = true ;
183
263
184
264
_process . EnableRaisingEvents = true ;
185
- _process . OutputDataReceived += CMD_OutputDataReceived ;
186
- _process . ErrorDataReceived += CMD_ErrorDataReceived ;
265
+ _process . OutputDataReceived += OutputDataReceived ;
266
+ _process . ErrorDataReceived += ErrorDataReceived ;
187
267
188
268
_process . Exited += new EventHandler ( ( sender , args ) =>
189
269
{
@@ -216,33 +296,162 @@ private async Task<int> RunTerminalProcessAsync()
216
296
return await taskCompletionSource . Task ;
217
297
}
218
298
219
- private void CMD_OutputDataReceived ( object sender , DataReceivedEventArgs e )
299
+ private void OutputDataReceived ( object sender , DataReceivedEventArgs e )
220
300
{
221
301
_terminalData . Add ( e . Data ) ;
222
302
}
223
303
224
- private void CMD_ErrorDataReceived ( object sender , DataReceivedEventArgs e )
304
+ private void ErrorDataReceived ( object sender , DataReceivedEventArgs e )
225
305
{
226
306
_terminalData . Add ( e . Data ) ;
227
307
}
228
308
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 )
230
396
{
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
+
231
420
// Background-Terminal application commands
232
- if ( command . StartsWith ( "bgt" ) )
421
+ else if ( command . ToLower ( ) . StartsWith ( "bgt" ) )
233
422
{
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 ) ;
236
431
237
- if ( bgtCommand . Equals ( "newline" ) )
432
+ List < string > commandParams = command . Split ( ' ' ) . ToList ( ) ;
433
+
434
+ if ( commandParams . Count != 2 )
238
435
{
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 ;
240
448
}
241
449
}
450
+
451
+ // Standard command handling
242
452
else
243
453
{
244
- if ( output )
245
- _terminalData . Add ( command ) ;
454
+ _terminalData . Add ( command ) ;
246
455
247
456
_process . StandardInput . NewLine = _newlineString ;
248
457
_process . StandardInput . WriteLine ( command ) ;
@@ -265,28 +474,6 @@ private void SendCommand(string command, bool output = true)
265
474
}
266
475
}
267
476
}
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
- }
290
477
#endregion
291
478
292
479
#region Event Handlers
@@ -484,6 +671,10 @@ private void MinimizeButton_Click(object sender, RoutedEventArgs e)
484
671
485
672
private void ExitButton_Click ( object sender , RoutedEventArgs e )
486
673
{
674
+ _sshClient . Disconnect ( ) ;
675
+ _sshClient . Dispose ( ) ;
676
+ _process . Kill ( ) ;
677
+
487
678
Close ( ) ;
488
679
}
489
680
0 commit comments