diff --git a/agent-cs/AgentWorker.cs b/agent-cs/AgentWorker.cs
index 4125c54..736529b 100644
--- a/agent-cs/AgentWorker.cs
+++ b/agent-cs/AgentWorker.cs
@@ -6,7 +6,7 @@ namespace ITNexusAgent;
public class AgentWorker
{
- private const string Version = "2.2.0";
+ private const string Version = "2.3.0";
private const string DataDir = @"C:\ProgramData\IT Nexus Agent";
private const string ConfigPath = @"C:\ProgramData\IT Nexus Agent\config.json";
private const string StatusPath = @"C:\ProgramData\IT Nexus Agent\status.json";
diff --git a/agent-cs/CaptureModeRunner.cs b/agent-cs/CaptureModeRunner.cs
index 1e9dc20..a1c598a 100644
--- a/agent-cs/CaptureModeRunner.cs
+++ b/agent-cs/CaptureModeRunner.cs
@@ -24,7 +24,6 @@ public static class CaptureModeRunner
var encParams = new EncoderParameters(1);
encParams.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 60L);
- // Capture-Region bestimmen: 0 = alle Screens, 1-N = spezifischer Screen
var allScreens = System.Windows.Forms.Screen.AllScreens;
Rectangle captureRect;
if (screenIdx <= 0 || screenIdx > allScreens.Length)
@@ -40,14 +39,27 @@ public static class CaptureModeRunner
captureRect = allScreens[screenIdx - 1].Bounds;
}
+ uint lastHash = 0;
+ var sw = System.Diagnostics.Stopwatch.StartNew();
+
while (tcp.Connected)
{
+ sw.Restart();
try
{
using var bmp = new Bitmap(captureRect.Width, captureRect.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
using (var g = Graphics.FromImage(bmp))
g.CopyFromScreen(captureRect.X, captureRect.Y, 0, 0, new Size(captureRect.Width, captureRect.Height));
+ var hash = SampleHash(bmp);
+ if (hash == lastHash)
+ {
+ var elapsed = (int)sw.ElapsedMilliseconds;
+ if (elapsed < 16) Thread.Sleep(16 - elapsed);
+ continue;
+ }
+ lastHash = hash;
+
byte[] jpeg;
using (var ms = new MemoryStream())
{
@@ -55,16 +67,32 @@ public static class CaptureModeRunner
jpeg = ms.ToArray();
}
- // 4-Byte Länge (LE) + JPEG-Daten
stream.Write(BitConverter.GetBytes(jpeg.Length));
stream.Write(jpeg);
stream.Flush();
}
catch { break; }
- Thread.Sleep(150); // ~6-7 fps
+ var frameMs = (int)sw.ElapsedMilliseconds;
+ if (frameMs < 16) Thread.Sleep(16 - frameMs); // cap at ~60 fps
}
}
catch { /* Verbindung fehlgeschlagen → Exit */ }
}
+
+ private static uint SampleHash(Bitmap bmp)
+ {
+ // Sample 64 evenly-distributed pixels — fast change detection, O(1)
+ uint h = 2166136261u;
+ int w = bmp.Width, ht = bmp.Height;
+ int stepX = Math.Max(1, w / 8);
+ int stepY = Math.Max(1, ht / 8);
+ for (int y = 0; y < ht; y += stepY)
+ for (int x = 0; x < w; x += stepX)
+ {
+ var c = bmp.GetPixel(x, y);
+ h = (h ^ (uint)c.ToArgb()) * 16777619u;
+ }
+ return h;
+ }
}
diff --git a/agent-cs/IT-Nexus-Agent.csproj b/agent-cs/IT-Nexus-Agent.csproj
index ffb64ef..c483d07 100644
--- a/agent-cs/IT-Nexus-Agent.csproj
+++ b/agent-cs/IT-Nexus-Agent.csproj
@@ -7,8 +7,8 @@
true
IT-Nexus-Agent
ITNexusAgent
- 2.0.0
- 2.0.0.0
+ 2.3.0
+ 2.3.0.0
enable
enable
false
diff --git a/agent-cs/Services/RtcService.cs b/agent-cs/Services/RtcService.cs
index 76d5808..e6172e5 100644
--- a/agent-cs/Services/RtcService.cs
+++ b/agent-cs/Services/RtcService.cs
@@ -70,6 +70,15 @@ public class RtcService
_ = ConsentAndCaptureAsync(ws, screenIdx, captureCts.Token);
AgentWorker.Log($"RDP: Consent angefordert (screen={screenIdx})");
}
+ else if (type == "rdp_switch_screen" && captureCts != null)
+ {
+ // Consent bereits erteilt — nur Screen wechseln, kein erneuter Dialog
+ var newScreen = obj["screen"]?.ToObject() ?? 0;
+ captureCts.Cancel();
+ captureCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
+ _ = CapturePipeLoopAsync(ws, newScreen, captureCts.Token);
+ AgentWorker.Log($"RDP: Screen gewechselt zu {newScreen}");
+ }
else if (type == "rdp_stop" && captureCts != null)
{
captureCts.Cancel();
@@ -93,8 +102,10 @@ public class RtcService
if (!SpawnConsentHelper(exePath, consentPort.ToString()))
{
consentListener.Stop();
- AgentWorker.Log("RDP: Consent-Helper fehlgeschlagen, starte ohne Consent");
- await CapturePipeLoopAsync(ws, screenIdx, ct);
+ AgentWorker.Log("RDP: Consent-Helper fehlgeschlagen, verweigere Zugriff");
+ var failed = Encoding.UTF8.GetBytes("{\"type\":\"rdp_denied\",\"reason\":\"consent_spawn_failed\"}");
+ if (ws.State == WebSocketState.Open)
+ await ws.SendAsync(new ArraySegment(failed), WebSocketMessageType.Text, true, CancellationToken.None);
return;
}
diff --git a/agent-cs/Services/ShellService.cs b/agent-cs/Services/ShellService.cs
index 1a37c52..84afbd5 100644
--- a/agent-cs/Services/ShellService.cs
+++ b/agent-cs/Services/ShellService.cs
@@ -40,7 +40,7 @@ public class ShellService
+ $"/ws?type=agent&key={Uri.EscapeDataString(_agentKey)}&hostname={Uri.EscapeDataString(_hostname)}";
using var ws = new ClientWebSocket();
- ws.Options.SetRequestHeader("User-Agent", "IT-Nexus-Agent/2.1.2");
+ ws.Options.SetRequestHeader("User-Agent", "IT-Nexus-Agent/2.3.0");
await ws.ConnectAsync(new Uri(wsUrl), ct);
AgentWorker.Log("SHELL: WebSocket verbunden");
diff --git a/agent-cs/UI/RdpConsentWindow.xaml b/agent-cs/UI/RdpConsentWindow.xaml
index b61689a..d41fd42 100644
--- a/agent-cs/UI/RdpConsentWindow.xaml
+++ b/agent-cs/UI/RdpConsentWindow.xaml
@@ -1,15 +1,16 @@
diff --git a/agent-cs/setup.iss b/agent-cs/setup.iss
index 319688d..c6cc89e 100644
--- a/agent-cs/setup.iss
+++ b/agent-cs/setup.iss
@@ -1,5 +1,5 @@
#define MyAppName "IT Nexus Agent"
-#define MyAppVersion "2.2.0"
+#define MyAppVersion "2.3.0"
#define MyAppPublisher "Cereda Systems GmbH"
#define MyAppURL "https://it-nexus.cereda-systems.de"
#define MyAppExeName "IT-Nexus-Agent.exe"
diff --git a/frontend/src/components/common/RemoteDesktopPanel.jsx b/frontend/src/components/common/RemoteDesktopPanel.jsx
index e19acfc..c7bc910 100644
--- a/frontend/src/components/common/RemoteDesktopPanel.jsx
+++ b/frontend/src/components/common/RemoteDesktopPanel.jsx
@@ -61,7 +61,9 @@ export default function RemoteDesktopPanel({ agentId, agentHostname, autoConnect
img.src = 'data:image/jpeg;base64,' + msg.data;
} else if (msg.type === 'rdp_denied') {
setStatus('error');
- setError('Zugriff wurde vom Benutzer abgelehnt');
+ setError(msg.reason === 'consent_spawn_failed'
+ ? 'Consent-Dialog konnte nicht geöffnet werden (schtasks-Fehler)'
+ : 'Zugriff wurde vom Benutzer abgelehnt');
ws.close();
} else if (msg.type === 'rdp_disconnected') {
setStatus('idle');
@@ -119,8 +121,14 @@ export default function RemoteDesktopPanel({ agentId, agentHostname, autoConnect
{/* Screen-Selektor */}