Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
RemoteDesktopView.xaml.cs
1using System;
2using System.Collections.Generic;
3using System.Drawing;
4using System.Drawing.Imaging;
5using System.IO;
6using System.Threading.Tasks;
7using System.Windows;
8using System.Windows.Controls;
9using System.Windows.Input;
10using System.Windows.Media;
11using System.Windows.Media.Imaging;
13using Waher.Events;
17
19{
23 public partial class RemoteDesktopView : UserControl, ITabView
24 {
25 private readonly LinkedList<(Pending, int, int)> queue = new LinkedList<(Pending, int, int)>();
26 private readonly XmppContact node;
27 private readonly XmppClient client;
28 private readonly RemoteDesktopClient rdpClient;
29 private readonly object synchObj = new object();
30 private RemoteDesktopSession session;
31 private Pending[,] pendingTiles = null;
32 private WriteableBitmap desktop = null;
33 private DateTime updateScreenTimer;
34 private int columns;
35 private int rows;
36 private bool drawing = false;
37 private bool disposeRdpClient;
38
39 private class Pending
40 {
41 public string Base64;
42 public byte[] Bin;
43
44 public Pending(string Base64)
45 {
46 this.Base64 = Base64;
47 }
48
49 public Pending(byte[] Bin)
50 {
51 this.Bin = Bin;
52 }
53 }
54
55 public RemoteDesktopView(XmppContact Node, XmppClient Client, RemoteDesktopClient RdpClient, bool DisposeRdpClient)
56 {
57 this.node = Node;
58 this.client = Client;
59 this.rdpClient = RdpClient;
60 this.disposeRdpClient = DisposeRdpClient;
61
63
64 this.Focusable = true;
65 Keyboard.Focus(this);
66 }
67
68 public RemoteDesktopSession Session
69 {
70 get => this.session;
71 internal set
72 {
73 if (!(this.session is null))
74 {
75 this.session.StateChanged -= this.Session_StateChanged;
76 this.session.TileUpdated -= this.Session_TileUpdated;
77 this.session.ScanComplete -= this.Session_ScanComplete;
78 }
79
80 this.session = value;
81
82 this.session.StateChanged += this.Session_StateChanged;
83 this.session.TileUpdated += this.Session_TileUpdated;
84 this.session.ScanComplete += this.Session_ScanComplete;
85 }
86 }
87
88 private Task Session_StateChanged(object Sender, EventArgs e)
89 {
90 if (this.session.State == RemoteDesktopSessionState.Started && this.desktop is null)
91 {
92 int ScreenWidth = this.session.Width;
93 int ScreenHeight = this.session.Height;
94 this.columns = (ScreenWidth + this.session.TileSize - 1) / this.session.TileSize;
95 this.rows = (ScreenHeight + this.session.TileSize - 1) / this.session.TileSize;
96
97 lock (this.synchObj)
98 {
99 this.pendingTiles = new Pending[this.rows, this.columns];
100
101 foreach ((Pending Tile, int X, int Y) in this.queue)
102 this.pendingTiles[Y, X] = Tile;
103
104 this.queue.Clear();
105 }
106 }
107
108 return Task.CompletedTask;
109 }
110
111 private void Session_TileUpdated(object Sender, TileEventArgs e)
112 {
113 lock (this.synchObj)
114 {
115 if (this.pendingTiles is null)
116 this.queue.AddLast((new Pending(e.TileBase64), e.X, e.Y));
117 else
118 this.pendingTiles[e.Y, e.X] = new Pending(e.TileBase64);
119
120 if (this.updateScreenTimer == DateTime.MinValue)
121 this.updateScreenTimer = MainWindow.Scheduler.Add(DateTime.Now.AddMilliseconds(250), this.UpdateScreen, null);
122 }
123 }
124
125 private Task Session_ScanComplete(object Sender, EventArgs e)
126 {
127 this.UpdateScreen(null);
128 return Task.CompletedTask;
129 }
130
131 private byte[] buffer;
132 private byte[] block;
133 private int blockState = 0;
134 private int blockLen = 0;
135 private int blockLeft = 0;
136 private int blockPos = 0;
137 private int state = 0;
138 private byte command = 0;
139 private int len = 0;
140 private int left = 0;
141 private int pos = 0;
142 private int x = 0;
143 private int y = 0;
144
145 internal Task Socks5DataReceived(object Sender, DataReceivedEventArgs e)
146 {
147 byte[] Data = e.Buffer;
148 int i = e.Offset;
149 int c = e.Count;
150
151 while (c > 0)
152 {
153 switch (this.blockState)
154 {
155 case 0:
156 this.blockLen = Data[i++];
157 this.blockState++;
158 c--;
159 break;
160
161 case 1:
162 this.blockLen <<= 8;
163 this.blockLen |= Data[i++];
164 this.blockLeft = this.blockLen;
165 if ((this.block?.Length ?? 0) != this.blockLen)
166 this.block = new byte[this.blockLen];
167 this.blockPos = 0;
168 this.blockState++;
169 c--;
170 break;
171
172 case 2:
173 int j = Math.Min(this.blockLeft, c);
174 Array.Copy(Data, i, this.block, this.blockPos, j);
175 this.blockPos += j;
176 this.blockLeft -= j;
177 i += j;
178 c -= j;
179
180 if (this.blockLeft == 0)
181 {
182 this.BlockReceived(this.block);
183 this.blockState = 0;
184 }
185 break;
186 }
187 }
188
189 return Task.CompletedTask;
190 }
191
192 private void BlockReceived(byte[] Data)
193 {
194 int i = 0;
195 int c = Data.Length;
196 int j;
197
198 while (c > 0)
199 {
200 switch (this.state)
201 {
202 case 0:
203 this.command = Data[i++];
204 c--;
205 this.state++;
206 break;
207
208 case 1:
209 this.len = Data[i++];
210 c--;
211 this.state++;
212 break;
213
214 case 2:
215 this.len |= Data[i++] << 8;
216 c--;
217 this.state++;
218 break;
219
220 case 3:
221 this.len |= Data[i++] << 16;
222 c--;
223 this.left = this.len;
224 this.buffer = new byte[this.len];
225 this.pos = 0;
226 this.state++;
227 break;
228
229 case 4:
230 this.x = Data[i++];
231 c--;
232 this.state++;
233 break;
234
235 case 5:
236 this.x |= Data[i++] << 8;
237 c--;
238 this.state++;
239 break;
240
241 case 6:
242 this.y = Data[i++];
243 c--;
244 this.state++;
245 break;
246
247 case 7:
248 this.y |= Data[i++] << 8;
249 c--;
250
251 if (this.left > 0)
252 this.state++;
253 else
254 {
255 this.ProcessCommand();
256 this.state = 0;
257 }
258 break;
259
260 case 8:
261 j = Math.Min(this.left, c);
262 Array.Copy(Data, i, this.buffer, this.pos, j);
263 this.pos += j;
264 this.left -= j;
265 i += j;
266 c -= j;
267
268 if (this.left == 0)
269 {
270 this.ProcessCommand();
271 this.state = 0;
272 }
273 break;
274
275 default:
276 c = 0;
277 break;
278 }
279 }
280 }
281
282 private void ProcessCommand()
283 {
284 switch (this.command)
285 {
286 case 0:
287 lock (this.synchObj)
288 {
289 if (this.pendingTiles is null)
290 this.queue.AddLast((new Pending(this.buffer), this.x, this.y));
291 else
292 this.pendingTiles[this.y, this.x] = new Pending(this.buffer);
293
294 if (this.updateScreenTimer == DateTime.MinValue)
295 this.updateScreenTimer = MainWindow.Scheduler.Add(DateTime.Now.AddMilliseconds(250), this.UpdateScreen, null);
296 }
297 break;
298
299 case 1:
300 this.UpdateScreen(null);
301 break;
302 }
303 }
304
305 internal Task Socks5StreamClosed(object Sender, StreamEventArgs e)
306 {
307 return Task.CompletedTask; // TODO
308 }
309
310 private void UpdateScreen(object _)
311 {
312 this.updateScreenTimer = DateTime.MinValue;
313 MainWindow.UpdateGui(this.UpdateScreenGuiThread);
314 }
315
316 private Task UpdateScreenGuiThread()
317 {
318 MemoryStream ms = null;
319 Bitmap Tile = null;
320 BitmapData Data = null;
321 Pending PendingTile;
322 bool Locked = false;
323 int Size = this.session.TileSize;
324 int x, y;
325
326 try
327 {
328 if (this.drawing)
329 return Task.CompletedTask;
330
331 this.drawing = true;
332
333 for (y = 0; y < this.rows; y++)
334 {
335 for (x = 0; x < this.columns; x++)
336 {
337 lock (this.synchObj)
338 {
339 PendingTile = this.pendingTiles[y, x];
340 if (PendingTile is null)
341 continue;
342
343 this.pendingTiles[y, x] = null;
344 }
345
346 if (this.desktop is null)
347 {
348 this.desktop = new WriteableBitmap(this.session.Width, this.session.Height, 96, 96, PixelFormats.Bgra32, null);
349 this.DesktopImage.Source = this.desktop;
350 }
351
352 ms?.Dispose();
353 ms = null;
354 ms = new MemoryStream(PendingTile.Bin ?? Convert.FromBase64String(PendingTile.Base64));
355
356 Tile = (Bitmap)Bitmap.FromStream(ms);
357 Data = Tile.LockBits(new Rectangle(0, 0, Tile.Width, Tile.Height), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
358
359 if (!Locked)
360 {
361 this.desktop.Lock();
362 Locked = true;
363 }
364
365 this.desktop.WritePixels(new Int32Rect(0, 0, Tile.Width, Tile.Height), Data.Scan0, Data.Stride * Data.Height,
366 Data.Stride, x * Size, y * Size);
367
368 Tile.UnlockBits(Data);
369 Data = null;
370
371 Tile.Dispose();
372 Tile = null;
373 }
374 }
375 }
376 finally
377 {
378 this.drawing = false;
379
380 if (Locked)
381 this.desktop.Unlock();
382
383 if (!(Data is null))
384 Tile.UnlockBits(Data);
385
386 ms?.Dispose();
387 Tile?.Dispose();
388 }
389
390 return Task.CompletedTask;
391 }
392
393 public async void Dispose()
394 {
395 try
396 {
397 this.node?.XmppAccountNode?.UnregisterView(this);
398
399 if (this.updateScreenTimer > DateTime.MinValue)
400 {
401 MainWindow.Scheduler?.Remove(this.updateScreenTimer);
402 this.updateScreenTimer = DateTime.MinValue;
403 }
404
405 if (!(this.session is null) &&
406 this.session.State != RemoteDesktopSessionState.Stopped &&
407 this.session.State != RemoteDesktopSessionState.Stopping)
408 {
409 await this.rdpClient.StopSessionAsync(this.session.RemoteJid, this.session.SessionId);
410 }
411
412 if (this.disposeRdpClient)
413 {
414 this.rdpClient.Dispose();
415 this.disposeRdpClient = false;
416 }
417 }
418 catch (Exception ex)
419 {
420 Log.Exception(ex);
421 }
422
423 this.node?.ViewClosed();
424 }
425
426 public XmppContact Node => this.node;
427 public XmppClient Client => this.client;
428 public RemoteDesktopClient RdpClient => this.rdpClient;
429
430 private void UserControl_MouseMove(object Sender, MouseEventArgs e)
431 {
432 if (!(this.session is null))
433 {
434 this.GetPosition(e, out int X, out int Y);
435 this.session.MouseMoved(X, Y);
436 e.Handled = true;
437 }
438 }
439
440 private void GetPosition(MouseEventArgs e, out int X, out int Y)
441 {
442 System.Windows.Point P = e.GetPosition(this.DesktopImage);
443
444 X = (int)(this.session.Width * P.X / this.DesktopImage.ActualWidth + 0.5);
445 Y = (int)(this.session.Height * P.Y / this.DesktopImage.ActualHeight + 0.5);
446 }
447
448 private void UserControl_MouseDown(object Sender, MouseButtonEventArgs e)
449 {
450 if (!(this.session is null))
451 {
452 this.GetPosition(e, out int X, out int Y);
453
454 switch (e.ChangedButton)
455 {
456 case System.Windows.Input.MouseButton.Left:
457 this.session.MouseDown(X, Y, Networking.XMPP.RDP.MouseButton.Left);
458 e.Handled = true;
459 break;
460
461 case System.Windows.Input.MouseButton.Middle:
462 this.session.MouseDown(X, Y, Networking.XMPP.RDP.MouseButton.Middle);
463 e.Handled = true;
464 break;
465
466 case System.Windows.Input.MouseButton.Right:
467 this.session.MouseDown(X, Y, Networking.XMPP.RDP.MouseButton.Right);
468 e.Handled = true;
469 break;
470
471 default:
472 e.Handled = false;
473 break;
474 }
475 }
476 }
477
478 private void UserControl_MouseUp(object Sender, MouseButtonEventArgs e)
479 {
480 if (!(this.session is null))
481 {
482 this.GetPosition(e, out int X, out int Y);
483
484 switch (e.ChangedButton)
485 {
486 case System.Windows.Input.MouseButton.Left:
487 this.session.MouseUp(X, Y, Networking.XMPP.RDP.MouseButton.Left);
488 e.Handled = true;
489 break;
490
491 case System.Windows.Input.MouseButton.Middle:
492 this.session.MouseUp(X, Y, Networking.XMPP.RDP.MouseButton.Middle);
493 e.Handled = true;
494 break;
495
496 case System.Windows.Input.MouseButton.Right:
497 this.session.MouseUp(X, Y, Networking.XMPP.RDP.MouseButton.Right);
498 e.Handled = true;
499 break;
500
501 default:
502 e.Handled = false;
503 break;
504 }
505 }
506 }
507
508 private void UserControl_MouseWheel(object Sender, MouseWheelEventArgs e)
509 {
510 if (!(this.session is null))
511 {
512 this.GetPosition(e, out int X, out int Y);
513 this.session.MouseWheel(X, Y, e.Delta);
514 e.Handled = true;
515 }
516 }
517
518 private void UserControl_KeyDown(object Sender, KeyEventArgs e)
519 {
520 if (!(this.session is null))
521 {
522 int KeyCode = KeyInterop.VirtualKeyFromKey(e.Key);
523 this.session.KeyDown(KeyCode);
524 e.Handled = true;
525 }
526 }
527
528 private void UserControl_KeyUp(object Sender, KeyEventArgs e)
529 {
530 if (!(this.session is null))
531 {
532 int KeyCode = KeyInterop.VirtualKeyFromKey(e.Key);
533 this.session.KeyUp(KeyCode);
534 e.Handled = true;
535 }
536 }
537
538 private void UserControl_IsVisibleChanged(object Sender, DependencyPropertyChangedEventArgs e)
539 {
540 if (this.IsVisible)
541 Keyboard.Focus(this);
542 }
543
544 public void SaveButton_Click(object Sender, RoutedEventArgs e)
545 {
546 // TODO: screen capture?
547 }
548
549 public void SaveAsButton_Click(object Sender, RoutedEventArgs e)
550 {
551 // TODO: screen capture?
552 }
553
554 public void NewButton_Click(object Sender, RoutedEventArgs e)
555 {
556 // TODO: Refresh screen?
557 }
558
559 public void OpenButton_Click(object Sender, RoutedEventArgs e)
560 {
561 // TODO: ?
562 }
563 }
564}
Interaction logic for RemoteDesktopView.xaml
Interaction logic for xaml
Represents an XMPP contact whose capabilities have not been measured.
Definition: XmppContact.cs:24
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:13
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
Definition: Log.cs:1647
Event arguments for data reception events.
Event arguments for stream callbacks.
Maintains the client-side state of a Remote Desktop Session.
RemoteDesktopSessionState State
Session state changed.
void KeyDown(int KeyCode)
Reports a key having been pressed.
void MouseDown(int X, int Y, MouseButton Button)
Reports the mouse having been pressed down.
void MouseUp(int X, int Y, MouseButton Button)
Reports the mouse having been released up.
void MouseMoved(int X, int Y)
Reports the mouse having moved to a given position.
void MouseWheel(int X, int Y, int Delta)
Reports the mouse wheel having been turned.
void KeyUp(int KeyCode)
Reports a key having been released.
Event arguments for tile events.
Definition: TileEventArgs.cs:9
int X
Tile X-coordinate of remote desktop screen.
string TileBase64
PNG of tile being updated, base64-encoded.
int Y
Tile Y-coordinate of remote desktop screen.
Manages an XMPP client connection. Implements XMPP, as defined in https://tools.ietf....
Definition: XmppClient.cs:59
Interface for tab view user controls in the client.
Definition: ITabView.cs:10
RemoteDesktopSessionState
State of a Remote Desktop Session