2
2
using npcook . Terminal . Controls ;
3
3
using Renci . SshNet ;
4
4
using System ;
5
+ using System . Collections . Concurrent ;
5
6
using System . Collections . Generic ;
6
7
using System . ComponentModel ;
7
8
using System . IO ;
8
9
using System . Linq ;
9
10
using System . Runtime . CompilerServices ;
10
11
using System . Text ;
12
+ using System . Threading ;
11
13
using System . Threading . Tasks ;
12
14
using System . Windows ;
13
15
using System . Windows . Input ;
@@ -26,6 +28,12 @@ public ErrorEventArgs(string message)
26
28
}
27
29
}
28
30
31
+ interface IWindowStreamNotifier : IStreamNotifier
32
+ {
33
+ void Start ( ) ;
34
+ void Stop ( ) ;
35
+ }
36
+
29
37
#if USE_LIBSSHNET
30
38
class LibSshNetStreamNotifier : IStreamNotifier
31
39
{
@@ -52,33 +60,174 @@ private void Stream_DataReceived(object sender, EventArgs e)
52
60
}
53
61
}
54
62
#else
55
- class ShellStreamNotifier : IStreamNotifier
63
+ class ShellStreamNotifier : IWindowStreamNotifier
56
64
{
57
- TerminalControl terminal ;
65
+ readonly TerminalControl terminal ;
66
+ readonly ShellStream stream ;
67
+ readonly SemaphoreSlim received = new SemaphoreSlim ( 0 ) ;
68
+ bool stopped ;
58
69
59
70
public Stream Stream
60
- { get ; }
71
+ { get { return stream ; } }
61
72
62
- public event EventHandler DataAvailable ;
73
+ public event EventHandler < DataAvailableEventArgs > DataAvailable ;
63
74
64
75
public ShellStreamNotifier ( TerminalControl terminal , ShellStream stream )
65
76
{
66
77
this . terminal = terminal ;
67
- Stream = stream ;
78
+ this . stream = stream ;
79
+ }
80
+
81
+ public void Start ( )
82
+ {
83
+ stopped = false ;
68
84
stream . DataReceived += Stream_DataReceived ;
85
+ Task . Run ( ( ) =>
86
+ {
87
+ while ( true )
88
+ {
89
+ received . Wait ( ) ;
90
+ if ( stopped )
91
+ break ;
92
+
93
+ while ( stream . DataAvailable )
94
+ {
95
+ terminal . BeginChange ( ) ;
96
+ if ( DataAvailable != null )
97
+ {
98
+ bool done = false ;
99
+ Task . Run ( ( ) =>
100
+ {
101
+ while ( ! done )
102
+ {
103
+ Thread . Sleep ( 50 ) ;
104
+ if ( done )
105
+ break ;
106
+ terminal . CycleChange ( ) ;
107
+ }
108
+ } ) ;
109
+ DataAvailable ( this , new DataAvailableEventArgs ( 0 ) ) ;
110
+ done = true ;
111
+ }
112
+ else
113
+ throw new InvalidOperationException ( "Data was received but no one was listening." ) ;
114
+ terminal . EndChange ( ) ;
115
+ }
116
+ }
117
+ } ) ;
118
+ received . Release ( ) ;
119
+ }
120
+
121
+ public void Stop ( )
122
+ {
123
+ stopped = true ;
124
+ stream . Close ( ) ;
125
+ received . Release ( ) ;
126
+ }
127
+
128
+ private void Stream_DataReceived ( object sender , Renci . SshNet . Common . ShellDataEventArgs e )
129
+ {
130
+ received . Release ( ) ;
131
+ }
132
+ }
133
+
134
+ class SavingShellStreamNotifier : IWindowStreamNotifier
135
+ {
136
+ readonly TerminalControl terminal ;
137
+ readonly ShellStream input ;
138
+ readonly Stream output ;
139
+ readonly ProxyStream middle ;
140
+ readonly ConcurrentQueue < byte [ ] > outputQueue = new ConcurrentQueue < byte [ ] > ( ) ;
141
+ readonly EventWaitHandle outputWait = new EventWaitHandle ( false , EventResetMode . AutoReset ) ;
142
+ readonly Task outputTask ;
143
+
144
+ public Stream Stream
145
+ { get { return middle ; } }
146
+
147
+ public event EventHandler < DataAvailableEventArgs > DataAvailable ;
148
+
149
+ public SavingShellStreamNotifier ( TerminalControl terminal , ShellStream stream , Stream output )
150
+ {
151
+ this . terminal = terminal ;
152
+ input = stream ;
153
+ this . output = output ;
154
+ middle = new ProxyStream ( input ) ;
155
+ stream . DataReceived += Stream_DataReceived ;
156
+ outputTask = Task . Run ( ( ) =>
157
+ {
158
+ do
159
+ {
160
+ outputWait . WaitOne ( ) ;
161
+ if ( outputQueue . IsEmpty )
162
+ break ;
163
+
164
+ byte [ ] result ;
165
+ while ( ! outputQueue . TryDequeue ( out result ) ) ;
166
+
167
+ output . Write ( result , 0 , result . Length ) ;
168
+ } while ( true ) ;
169
+ } ) ;
69
170
}
70
171
71
172
public void Start ( )
72
173
{
73
- if ( ( Stream as ShellStream ) . DataAvailable && DataAvailable != null )
174
+ if ( input . DataAvailable && DataAvailable != null )
74
175
Stream_DataReceived ( this , null ) ;
75
176
}
76
177
178
+ public void Stop ( )
179
+ {
180
+ output . Close ( ) ;
181
+ }
182
+
77
183
private void Stream_DataReceived ( object sender , Renci . SshNet . Common . ShellDataEventArgs e )
78
184
{
79
185
terminal . BeginChange ( ) ;
80
186
if ( DataAvailable != null )
81
- DataAvailable ( this , EventArgs . Empty ) ;
187
+ {
188
+ DataAvailable ( this , new DataAvailableEventArgs ( e . Data . Length ) ) ;
189
+ outputQueue . Enqueue ( middle . GetCopy ( ) ) ;
190
+ outputWait . Set ( ) ;
191
+ }
192
+ else
193
+ throw new InvalidOperationException ( "Data was received but no one was listening." ) ;
194
+ terminal . EndChange ( ) ;
195
+ }
196
+ }
197
+
198
+ class LoadingShellStreamNotifier : IWindowStreamNotifier
199
+ {
200
+ TerminalControl terminal ;
201
+
202
+ public Stream Stream
203
+ { get ; }
204
+
205
+ public event EventHandler < DataAvailableEventArgs > DataAvailable ;
206
+
207
+ public LoadingShellStreamNotifier ( TerminalControl terminal , Stream input )
208
+ {
209
+ this . terminal = terminal ;
210
+ Stream = new ProxyStream ( input , false , true ) ;
211
+ }
212
+
213
+ public void Start ( )
214
+ {
215
+ if ( DataAvailable != null )
216
+ {
217
+ Stream_DataReceived ( this , null ) ;
218
+ }
219
+ }
220
+
221
+ public void Stop ( )
222
+ {
223
+ Stream . Close ( ) ;
224
+ }
225
+
226
+ private void Stream_DataReceived ( object sender , Renci . SshNet . Common . ShellDataEventArgs e )
227
+ {
228
+ terminal . BeginChange ( ) ;
229
+ if ( DataAvailable != null )
230
+ DataAvailable ( this , new DataAvailableEventArgs ( e . Data . Length ) ) ;
82
231
else
83
232
throw new InvalidOperationException ( "Data was received but no one was listening." ) ;
84
233
terminal . EndChange ( ) ;
@@ -101,9 +250,7 @@ protected void notifyPropertyChanged([CallerMemberName] string memberName = null
101
250
{
102
251
App . Current . Dispatcher . BeginInvoke ( new Action ( ( ) =>
103
252
{
104
- var handler = PropertyChanged ;
105
- if ( handler != null )
106
- handler ( this , new PropertyChangedEventArgs ( memberName ) ) ;
253
+ PropertyChanged ? . Invoke ( this , new PropertyChangedEventArgs ( memberName ) ) ;
107
254
} ) ) ;
108
255
}
109
256
@@ -181,9 +328,7 @@ async void onReopenSession(object _)
181
328
}
182
329
catch ( ConnectException ex )
183
330
{
184
- var handler = Error ;
185
- if ( handler != null )
186
- handler ( this , new ErrorEventArgs ( ex . Message ) ) ;
331
+ Error ? . Invoke ( this , new ErrorEventArgs ( ex . Message ) ) ;
187
332
}
188
333
}
189
334
@@ -202,10 +347,10 @@ void onOptions(object _)
202
347
203
348
}
204
349
205
- public void Connect ( IStreamNotifier notifier , ConnectionSettings settings )
350
+ public void Connect ( IStreamNotifier notifier , ShellStream stream , ConnectionSettings settings )
206
351
{
207
352
this . settings = settings ;
208
- stream = notifier . Stream as ShellStream ;
353
+ this . stream = stream ;
209
354
var terminal = new XtermTerminal ( notifier )
210
355
{
211
356
Size = new Terminal . Point ( App . DefaultTerminalCols , App . DefaultTerminalRows ) ,
@@ -236,17 +381,15 @@ private void Terminal_StreamException(object sender, StreamExceptionEventArgs e)
236
381
else
237
382
throw new Exception ( "An unidentified error occurred." , e . Exception ) ;
238
383
239
- var handler = Error ;
240
- if ( handler != null )
241
- handler ( this , new ErrorEventArgs ( message ) ) ;
384
+ Error ? . Invoke ( this , new ErrorEventArgs ( message ) ) ;
242
385
}
243
386
244
387
public void ChangeSize ( int cols , int rows )
245
388
{
246
389
if ( cols != terminal . Size . Col || rows != terminal . Size . Row )
247
390
{
248
391
terminal . Size = new Terminal . Point ( cols , rows ) ;
249
- stream . Channel . SendWindowChangeRequest ( ( uint ) cols , ( uint ) rows , 0 , 0 ) ;
392
+ // stream.Channel.SendWindowChangeRequest((uint) cols, (uint) rows, 0, 0);
250
393
}
251
394
}
252
395
}
0 commit comments