Files
IT-Nexus/agent-cs/Services/RtcService.cs
Simon Grüssing 0a8b186585 Add Remote Desktop (JPEG-over-WebSocket), Agent v2.2.0
- RtcService.cs: Screen capture via Graphics.CopyFromScreen, ~8fps JPEG stream over WebSocket
- shellServer.js: Separate rdp-agent/rdp types with independent socket maps (no shell collision)
- AgentDetailPage: WebSocket-based RemoteDesktop component replaces WebRTC attempt
- setup.iss: Fixed filename to include 'v' prefix (IT-Nexus-Agent-Setup-v2.2.0.exe)
- Removed SIPSorcery dependencies, added System.Drawing.Common

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 14:13:18 +02:00

128 lines
4.5 KiB
C#

using System.Drawing;
using System.Drawing.Imaging;
using System.Net.WebSockets;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace ITNexusAgent.Services;
// Überträgt den Bildschirm via JPEG-Frames über WebSocket an den Browser
public class RtcService
{
private readonly string _serverUrl;
private readonly string _agentKey;
private readonly string _hostname;
private static readonly ImageCodecInfo JpegCodec =
ImageCodecInfo.GetImageEncoders().First(c => c.FormatID == ImageFormat.Jpeg.Guid);
public RtcService(string serverUrl, string agentKey, string hostname)
{
_serverUrl = serverUrl;
_agentKey = agentKey;
_hostname = hostname;
}
public async Task RunAsync(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
try { await ConnectAsync(ct); }
catch (Exception ex) { AgentWorker.Log($"RDP: {ex.Message}"); }
if (!ct.IsCancellationRequested)
await Task.Delay(TimeSpan.FromSeconds(15), ct).ContinueWith(_ => { });
}
}
private async Task ConnectAsync(CancellationToken ct)
{
var wsUrl = _serverUrl
.Replace("https://", "wss://")
.Replace("http://", "ws://")
+ $"/ws?type=rdp-agent&key={Uri.EscapeDataString(_agentKey)}&hostname={Uri.EscapeDataString(_hostname)}";
using var ws = new ClientWebSocket();
await ws.ConnectAsync(new Uri(wsUrl), ct);
AgentWorker.Log("RDP: Bereit");
CancellationTokenSource? captureCts = null;
var buf = new byte[4096];
while (ws.State == WebSocketState.Open && !ct.IsCancellationRequested)
{
WebSocketReceiveResult res;
try { res = await ws.ReceiveAsync(new ArraySegment<byte>(buf), ct); }
catch { break; }
if (res.MessageType == WebSocketMessageType.Close) break;
var raw = Encoding.UTF8.GetString(buf, 0, res.Count);
JObject? obj;
try { obj = JObject.Parse(raw); } catch { continue; }
var type = obj["type"]?.ToString();
if (type == "rdp_start" && captureCts == null)
{
captureCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
_ = CaptureLoopAsync(ws, captureCts.Token);
AgentWorker.Log("RDP: Screen-Capture gestartet");
}
else if (type == "rdp_stop" && captureCts != null)
{
captureCts.Cancel();
captureCts = null;
AgentWorker.Log("RDP: Screen-Capture gestoppt");
}
}
captureCts?.Cancel();
AgentWorker.Log("RDP: Getrennt");
}
private static async Task CaptureLoopAsync(ClientWebSocket ws, CancellationToken ct)
{
var encParams = new EncoderParameters(1);
encParams.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 60L);
while (!ct.IsCancellationRequested && ws.State == WebSocketState.Open)
{
try
{
var screen = System.Windows.Forms.Screen.PrimaryScreen;
if (screen == null) { await Task.Delay(500, ct).ContinueWith(_ => { }); continue; }
var bounds = screen.Bounds;
using var bmp = new Bitmap(bounds.Width, bounds.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
using (var g = Graphics.FromImage(bmp))
g.CopyFromScreen(bounds.X, bounds.Y, 0, 0, bounds.Size);
byte[] jpeg;
using (var ms = new MemoryStream())
{
bmp.Save(ms, JpegCodec, encParams);
jpeg = ms.ToArray();
}
var payload = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new
{
type = "rdp_frame",
data = Convert.ToBase64String(jpeg),
w = bounds.Width,
h = bounds.Height
}));
if (ws.State == WebSocketState.Open)
await ws.SendAsync(new ArraySegment<byte>(payload), WebSocketMessageType.Text, true, CancellationToken.None);
}
catch (OperationCanceledException) { break; }
catch (Exception ex) { AgentWorker.Log($"RDP: Capture-Fehler: {ex.Message}"); }
// ~8 fps
await Task.Delay(125, ct).ContinueWith(_ => { });
}
}
}