From ba5d670d1aeb6e919c1e2a7e12c0049e2e43deb4 Mon Sep 17 00:00:00 2001
From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com>
Date: Thu, 18 Jun 2026 23:54:23 +0200
Subject: [PATCH] Convert TCP networking to sync and binary, add DHCP and IP
stack init on boot
---
Kernel.cs | 59 ++++++-
Networking/TcpConnection.cs | 3 +-
Networking/TcpRpcBase.cs | 194 ++++++++++++++++++------
Networking/TcpRpcClient.cs | 33 ++--
Networking/TcpRpcServer.cs | 72 +++------
Processes/RemoteDesktopProcess.cs | 122 ++++++++++++---
UI/GUI/Rendering/NetworkRenderSource.cs | 2 +-
build-and-run.ps1 | 3 +-
8 files changed, 345 insertions(+), 143 deletions(-)
diff --git a/Kernel.cs b/Kernel.cs
index a1c986d..7cc52b1 100644
--- a/Kernel.cs
+++ b/Kernel.cs
@@ -1,5 +1,11 @@
global using Sys = Cosmos.Kernel.System;
+using Cosmos.Kernel.HAL.Interfaces.Devices;
+using Cosmos.Kernel.System.Network;
+using Cosmos.Kernel.System.Network.Config;
+using Cosmos.Kernel.System.Network.IPv4.UDP.DHCP;
+using Cosmos.Kernel.System.Timer;
+
using RemSox.Cryptography;
using RemSox.Processes;
using RemSox.Processing;
@@ -11,7 +17,7 @@ namespace RemSox;
///
/// Main kernel class - inherits from Cosmos.Kernel.System.Kernel.
///
-public class Kernel : Sys.Kernel
+public partial class Kernel : Sys.Kernel
{
protected override void BeforeRun()
{
@@ -31,6 +37,57 @@ public class Kernel : Sys.Kernel
new StopRemoteDesktopCommand()
]);
+ // Xml serializer test
+ Console.WriteLine("[Kernel] Testing XML serialization...");
+ Thread.Sleep(1000);
+ System.Drawing.Point point = new(10, 20);
+
+ string xml = $"{point.X}{point.Y}";
+ Console.WriteLine("[Kernel] XML Serialization Test: " + xml);
+
+ Console.WriteLine("[TCP Server] Starting...");
+
+ INetworkDevice? device = NetworkManager.PrimaryDevice;
+ if (device == null)
+ {
+ Console.WriteLine("[ERROR] No network device.");
+ return;
+ }
+
+ Console.WriteLine("[TCP Server] Waiting for link...");
+ int attempts = 0;
+ while (!device.LinkUp && attempts < 30)
+ {
+ TimerManager.Wait(100);
+ attempts++;
+ }
+
+ if (!device.Ready)
+ {
+ Console.WriteLine("[ERROR] Device not ready.");
+ return;
+ }
+
+ Console.WriteLine("[TCP Server] Initializing network stack...");
+ NetworkStack.Initialize();
+
+ Console.WriteLine("[TCP Server] Running DHCP...");
+ DHCPClient dhcp = new DHCPClient();
+ if (dhcp.SendDiscoverPacket() == -1)
+ {
+ Console.WriteLine("[ERROR] DHCP failed.");
+ return;
+ }
+
+ IPConfig? config = NetworkConfigManager.Get(device);
+ if (config?.IPAddress == null)
+ {
+ Console.WriteLine("[ERROR] No IP from DHCP.");
+ return;
+ }
+
+ Console.WriteLine("[TCP Server] IP: " + config.IPAddress);
+
Sys.Graphics.Canvas canvas = Sys.Graphics.FullScreenCanvas.GetFullScreenCanvas();
Sys.Mouse.MouseManager.Initialize();
diff --git a/Networking/TcpConnection.cs b/Networking/TcpConnection.cs
index 8a28cb0..4287569 100644
--- a/Networking/TcpConnection.cs
+++ b/Networking/TcpConnection.cs
@@ -7,12 +7,11 @@ public class TcpConnection(TcpClient client) : IDisposable
{
public TcpClient Client { get; } = client;
public NetworkStream Stream { get; } = client.GetStream();
- public SemaphoreSlim SendLock { get; } = new(1, 1);
+ public object SendLock { get; } = new();
public void Dispose()
{
Stream.Dispose();
Client.Dispose();
- SendLock.Dispose();
}
}
\ No newline at end of file
diff --git a/Networking/TcpRpcBase.cs b/Networking/TcpRpcBase.cs
index 818578c..cb41baf 100644
--- a/Networking/TcpRpcBase.cs
+++ b/Networking/TcpRpcBase.cs
@@ -2,9 +2,8 @@ using RemSox.Networking;
using System.Buffers.Binary;
using System.Collections.Concurrent;
-using System.Diagnostics.CodeAnalysis;
using System.Net.Sockets;
-using System.Text.Json;
+using System.Text;
public abstract class TcpRpcBase(IPacketCrypto? crypto = null)
{
@@ -15,57 +14,65 @@ public abstract class TcpRpcBase(IPacketCrypto? crypto = null)
// Security Guard: Prevent OOM/DoS via oversized length headers (Default: 32MB)
protected const int MaxMessageSize = 32 * 1024 * 1024;
- ///
- /// Base message registration for handlers tracking the sending connection.
- ///
- [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Handled via library constraints")]
- public void ListenTo(string type, Func handler)
+ public void ListenTo(string type, Func handler)
+ {
+ handlers[type] = async (conn, msg) => await handler(conn, msg.Payload);
+ }
+
+ public void ListenTo(string type, Func handler)
+ {
+ handlers[type] = async (conn, msg) => await handler(msg.Payload);
+ }
+
+ public void RespondTo(string type, Func> handler)
{
handlers[type] = async (conn, msg) =>
{
- T data = JsonSerializer.Deserialize(msg.Payload)!;
- await handler(conn, data);
+ byte[] res = await handler(msg.Payload);
+ SendRaw(conn, new TcpMessage
+ {
+ Type = type,
+ RequestId = msg.RequestId,
+ Payload = res
+ });
};
}
- ///
- /// Overloaded listener where connection mapping can be ignored (Convenient for Clients).
- ///
- public void ListenTo(string type, Func handler)
+ protected void SendRaw(TcpConnection conn, TcpMessage msg)
{
- ListenTo(type, async (_, data) => await handler(data));
- }
-
- [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Handled via library constraints")]
- protected async Task SendRaw(TcpConnection conn, TcpMessage msg)
- {
- byte[] data = JsonSerializer.SerializeToUtf8Bytes(msg);
+ byte[] rawData = SerializeMessage(msg);
if (crypto is not null)
{
- data = crypto.Encrypt(data);
+ rawData = crypto.Encrypt(rawData);
}
- // Allocate a single contiguous frame buffer to prevent inter-thread fragmentation
- // and eliminate redundant Socket Write Syscalls.
- byte[] packet = new byte[4 + data.Length];
- BinaryPrimitives.WriteInt32LittleEndian(packet, data.Length);
- Array.Copy(data, 0, packet, 4, data.Length);
+ byte[] packet = new byte[4 + rawData.Length];
+ BinaryPrimitives.WriteInt32LittleEndian(packet, rawData.Length);
+ Array.Copy(rawData, 0, packet, 4, rawData.Length);
- // Enforce sequence safety across multiple threads pushing data out of a singular stream
- await conn.SendLock.WaitAsync();
- try
+ lock (conn.SendLock)
{
- await conn.Stream.WriteAsync(packet);
- }
- finally
- {
- _ = conn.SendLock.Release();
+ conn.Stream.Write(packet, 0, packet.Length);
}
}
- [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Handled via library constraints")]
- protected async Task HandleConnection(TcpConnection conn, CancellationToken token = default)
+ private static void ReadExact(NetworkStream stream, byte[] buffer, int offset, int count)
+ {
+ int totalRead = 0;
+ while (totalRead < count)
+ {
+ int read = stream.Read(buffer, offset + totalRead, count - totalRead);
+ if (read == 0)
+ {
+ Thread.Sleep(10);
+ continue;
+ }
+ totalRead += read;
+ }
+ }
+
+ protected void HandleConnection(TcpConnection conn, CancellationToken token = default)
{
NetworkStream stream = conn.Stream;
byte[] lengthBytes = new byte[4];
@@ -74,32 +81,41 @@ public abstract class TcpRpcBase(IPacketCrypto? crypto = null)
{
while (!token.IsCancellationRequested && conn.Client.Connected)
{
- // 1. Frame Length Read
- await stream.ReadExactlyAsync(lengthBytes, token);
+ Console.WriteLine("[TcpRpc] Waiting for incoming packet...");
+
+ ReadExact(stream, lengthBytes, 0, 4);
int length = BinaryPrimitives.ReadInt32LittleEndian(lengthBytes);
- // DoS Payload Protection Check
if (length is <= 0 or > MaxMessageSize)
{
- throw new InvalidDataException($"Protocol violation: Received packet length of {length} bytes exceeds limits.");
+ Console.WriteLine($"[TcpRpc] Invalid packet length: {length}");
+ Thread.Sleep(10);
+ continue;
}
- // 2. Body Payload Read
+ Console.WriteLine($"[TcpRpc] Incoming packet length: {length} bytes");
+
byte[] buffer = new byte[length];
- await stream.ReadExactlyAsync(buffer, token);
+ ReadExact(stream, buffer, 0, length);
+
+ Console.WriteLine($"[TcpRpc] Incoming packet payload: {BitConverter.ToString(buffer)}");
if (crypto is not null)
{
buffer = crypto.Decrypt(buffer);
}
- TcpMessage? msg = JsonSerializer.Deserialize(buffer);
+ Console.WriteLine($"[TcpRpc] Decrypted packet payload: {BitConverter.ToString(buffer)}");
+
+ TcpMessage? msg = DeserializeMessage(buffer);
if (msg is null)
{
+ Console.WriteLine("[TcpRpc] Failed to deserialize incoming message.");
continue;
}
- // 3. Response Handler Check
+ Console.WriteLine($"[TcpRpc] Incoming message type: {msg.Type}, requestId: {msg.RequestId}, payload length: {msg.Payload.Length} bytes");
+
if (pendingRequests.TryGetValue(msg.RequestId, out TaskCompletionSource? tcs))
{
_ = tcs.TrySetResult(msg.Payload);
@@ -107,11 +123,8 @@ public abstract class TcpRpcBase(IPacketCrypto? crypto = null)
continue;
}
- // 4. Inbound Router Handler
if (handlers.TryGetValue(msg.Type, out Func? handler))
{
- // Decouple handler execution to the ThreadPool so slow business logic
- // doesn't bottleneck packet processing from the network stream interface.
_ = Task.Run(async () =>
{
try
@@ -120,7 +133,6 @@ public abstract class TcpRpcBase(IPacketCrypto? crypto = null)
}
catch
{
- // Operational log placement here for user exceptions inside delegates
}
}, token);
}
@@ -128,7 +140,6 @@ public abstract class TcpRpcBase(IPacketCrypto? crypto = null)
}
catch
{
- // Explicitly handles natural dropping scenarios cleanly
}
finally
{
@@ -137,5 +148,90 @@ public abstract class TcpRpcBase(IPacketCrypto? crypto = null)
}
}
+ private static byte[] SerializeMessage(TcpMessage msg)
+ {
+ byte[] typeBytes = Encoding.UTF8.GetBytes(msg.Type);
+ byte[] requestIdBytes = Encoding.UTF8.GetBytes(msg.RequestId);
+
+ byte[] data = new byte[4 + typeBytes.Length + 4 + requestIdBytes.Length + 4 + msg.Payload.Length];
+ int offset = 0;
+
+ WriteInt32(data, ref offset, typeBytes.Length);
+ typeBytes.CopyTo(data, offset);
+ offset += typeBytes.Length;
+
+ WriteInt32(data, ref offset, requestIdBytes.Length);
+ requestIdBytes.CopyTo(data, offset);
+ offset += requestIdBytes.Length;
+
+ WriteInt32(data, ref offset, msg.Payload.Length);
+ msg.Payload.CopyTo(data, offset);
+
+ return data;
+ }
+
+ private static TcpMessage? DeserializeMessage(byte[] data)
+ {
+ int offset = 0;
+ if (offset + 4 > data.Length)
+ {
+ return null;
+ }
+
+ int typeLen = ReadInt32(data, ref offset);
+ if (offset + typeLen > data.Length)
+ {
+ return null;
+ }
+
+ string type = Encoding.UTF8.GetString(data, offset, typeLen);
+ offset += typeLen;
+
+ if (offset + 4 > data.Length)
+ {
+ return null;
+ }
+
+ int requestIdLen = ReadInt32(data, ref offset);
+ if (offset + requestIdLen > data.Length)
+ {
+ return null;
+ }
+
+ string requestId = Encoding.UTF8.GetString(data, offset, requestIdLen);
+ offset += requestIdLen;
+
+ if (offset + 4 > data.Length)
+ {
+ return null;
+ }
+
+ int payloadLen = ReadInt32(data, ref offset);
+ if (offset + payloadLen > data.Length)
+ {
+ return null;
+ }
+
+ byte[] payload = new byte[payloadLen];
+ Array.Copy(data, offset, payload, 0, payloadLen);
+
+ return new TcpMessage { Type = type, RequestId = requestId, Payload = payload };
+ }
+
+ private static void WriteInt32(byte[] data, ref int offset, int value)
+ {
+ data[offset++] = (byte)(value & 0xFF);
+ data[offset++] = (byte)((value >> 8) & 0xFF);
+ data[offset++] = (byte)((value >> 16) & 0xFF);
+ data[offset++] = (byte)((value >> 24) & 0xFF);
+ }
+
+ private static int ReadInt32(byte[] data, ref int offset)
+ {
+ int val = data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) | (data[offset + 3] << 24);
+ offset += 4;
+ return val;
+ }
+
protected abstract void OnConnectionClosed(TcpConnection conn);
}
\ No newline at end of file
diff --git a/Networking/TcpRpcClient.cs b/Networking/TcpRpcClient.cs
index c2f02de..3126905 100644
--- a/Networking/TcpRpcClient.cs
+++ b/Networking/TcpRpcClient.cs
@@ -1,6 +1,5 @@
-using System.Diagnostics.CodeAnalysis;
+using System.Net;
using System.Net.Sockets;
-using System.Text.Json;
namespace RemSox.Networking;
@@ -8,19 +7,17 @@ public class TcpRpcClient(IPacketCrypto? crypto = null) : TcpRpcBase(crypto)
{
private TcpConnection? connection;
- public async Task ConnectAsync(string host, int port)
+ public void Connect(IPAddress ipAddress, int port)
{
TcpClient client = new();
- await client.ConnectAsync(host, port);
+ client.Connect(ipAddress, port);
connection = new TcpConnection(client);
- // Run network monitoring task loop background-detached
- _ = HandleConnection(connection);
+ _ = Task.Run(() => HandleConnection(connection));
}
- [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Handled via library constraints")]
- public async Task RequestAsync(string type, TReq request, TimeSpan timeout = default)
+ public async Task RequestAsync(string type, byte[] request, TimeSpan timeout = default)
{
if (connection is null)
{
@@ -29,7 +26,7 @@ public class TcpRpcClient(IPacketCrypto? crypto = null) : TcpRpcBase(crypto)
if (timeout == default)
{
- timeout = TimeSpan.FromSeconds(30); // Default fallback timeout
+ timeout = TimeSpan.FromSeconds(30);
}
string requestId = Guid.NewGuid().ToString();
@@ -38,19 +35,17 @@ public class TcpRpcClient(IPacketCrypto? crypto = null) : TcpRpcBase(crypto)
try
{
- await SendRaw(connection, new TcpMessage
+ SendRaw(connection, new TcpMessage
{
Type = type,
RequestId = requestId,
- Payload = JsonSerializer.SerializeToUtf8Bytes(request)
+ Payload = request
});
- // Enforce async timeout safety to prevent permanent dictionary leaks on dropped calls
using CancellationTokenSource timeoutCts = new(timeout);
- await using (timeoutCts.Token.Register(() => tcs.TrySetCanceled()))
+ using (timeoutCts.Token.Register(() => tcs.TrySetCanceled()))
{
- byte[] responseBytes = await tcs.Task;
- return JsonSerializer.Deserialize(responseBytes)!;
+ return await tcs.Task;
}
}
finally
@@ -59,19 +54,18 @@ public class TcpRpcClient(IPacketCrypto? crypto = null) : TcpRpcBase(crypto)
}
}
- [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Handled via library constraints")]
- public Task SendAsync(string type, T data)
+ public void Send(string type, byte[] data)
{
if (connection is null)
{
throw new InvalidOperationException("Client not connected.");
}
- return SendRaw(connection, new TcpMessage
+ SendRaw(connection, new TcpMessage
{
Type = type,
RequestId = Guid.NewGuid().ToString(),
- Payload = JsonSerializer.SerializeToUtf8Bytes(data)
+ Payload = data
});
}
@@ -82,7 +76,6 @@ public class TcpRpcClient(IPacketCrypto? crypto = null) : TcpRpcBase(crypto)
connection = null;
}
- // Fail-fast all lingering tasks waiting on an dead connection loop
foreach (TaskCompletionSource req in pendingRequests.Values)
{
_ = req.TrySetException(new SocketException((int)SocketError.ConnectionReset));
diff --git a/Networking/TcpRpcServer.cs b/Networking/TcpRpcServer.cs
index 6e25b23..2d9f41a 100644
--- a/Networking/TcpRpcServer.cs
+++ b/Networking/TcpRpcServer.cs
@@ -1,19 +1,17 @@
using System.Collections.Concurrent;
-using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Net.Sockets;
-using System.Text.Json;
namespace RemSox.Networking;
public class TcpRpcServer(IPacketCrypto? crypto = null) : TcpRpcBase(crypto)
{
- // Fix: Using ConcurrentDictionary to prevent collection errors during structural additions/prunings
private readonly ConcurrentDictionary connections = new();
+ private readonly ConcurrentDictionary activeEndpoints = new();
private TcpListener? listener;
private CancellationTokenSource? cts;
- public async Task StartAsync(int port, CancellationToken token = default)
+ public void StartAsync(int port, CancellationToken token = default)
{
cts = CancellationTokenSource.CreateLinkedTokenSource(token);
@@ -24,17 +22,25 @@ public class TcpRpcServer(IPacketCrypto? crypto = null) : TcpRpcBase(crypto)
{
while (!cts.IsCancellationRequested)
{
- TcpClient client = await listener.AcceptTcpClientAsync(cts.Token);
+ TcpClient client = listener.AcceptTcpClient();
+ string ep = client.Client.RemoteEndPoint?.ToString() ?? "";
+ if (!activeEndpoints.TryAdd(ep, 0))
+ {
+ client.Close();
+ Thread.Sleep(10);
+ continue;
+ }
+
+ Console.WriteLine($"Client connected: {ep}");
TcpConnection conn = new(client);
_ = connections.TryAdd(conn, 0);
- _ = HandleConnection(conn, cts.Token);
+ _ = Task.Run(() => HandleConnection(conn, cts.Token));
}
}
catch (OperationCanceledException)
{
- // Expected shutdown scenario
}
}
@@ -51,62 +57,26 @@ public class TcpRpcServer(IPacketCrypto? crypto = null) : TcpRpcBase(crypto)
connections.Clear();
}
- [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Handled via library constraints")]
- public void RespondTo(string type, Func> handler)
+ public void SendRawToAll(string type, byte[] payload)
{
- handlers[type] = async (conn, msg) =>
- {
- TReq req = JsonSerializer.Deserialize(msg.Payload)!;
- TRes res = await handler(req);
-
- await SendRaw(conn, new TcpMessage
- {
- Type = type,
- RequestId = msg.RequestId,
- Payload = JsonSerializer.SerializeToUtf8Bytes(res)
- });
- };
- }
-
- [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Handled via library constraints")]
- public Task SendToAll(string type, T data)
- {
- byte[] payloadBytes = JsonSerializer.SerializeToUtf8Bytes(data);
- List sendTasks = [];
-
foreach (TcpConnection conn in connections.Keys)
{
- sendTasks.Add(SendRaw(conn, new TcpMessage
- {
- Type = type,
- RequestId = Guid.NewGuid().ToString(),
- Payload = payloadBytes
- }));
- }
-
- return Task.WhenAll(sendTasks);
- }
-
- /// Broadcasts raw binary payload to all connected clients.
- public Task SendRawToAll(string type, byte[] payload)
- {
- List sendTasks = [];
-
- foreach (TcpConnection conn in connections.Keys)
- {
- sendTasks.Add(SendRaw(conn, new TcpMessage
+ SendRaw(conn, new TcpMessage
{
Type = type,
RequestId = Guid.NewGuid().ToString(),
Payload = payload
- }));
+ });
}
-
- return Task.WhenAll(sendTasks);
}
protected override void OnConnectionClosed(TcpConnection conn)
{
_ = connections.TryRemove(conn, out _);
+
+ string ep = conn.Client.Client.RemoteEndPoint?.ToString() ?? "";
+ _ = activeEndpoints.TryRemove(ep, out _);
+
+ Console.WriteLine($"Client disconnected: {ep}");
}
}
\ No newline at end of file
diff --git a/Processes/RemoteDesktopProcess.cs b/Processes/RemoteDesktopProcess.cs
index 559a840..10d8daa 100644
--- a/Processes/RemoteDesktopProcess.cs
+++ b/Processes/RemoteDesktopProcess.cs
@@ -7,6 +7,8 @@ using RemSox.UI;
using RemSox.UI.GUI.Rendering;
using RemSox.UI.GUI.Windows;
+using System.Text;
+
namespace RemSox.Processes;
internal sealed class RemoteDesktopProcess() : Process("Remote Desktop Server")
@@ -17,12 +19,6 @@ internal sealed class RemoteDesktopProcess() : Process("Remote Desktop Server")
public int Port { get; private set; }
- // Remote input message record types (JSON-serialized over TCP)
- private sealed record MouseMoveMsg(int X, int Y);
- private sealed record MouseButtonMsg(int X, int Y, string Button);
- private sealed record MouseWheelMsg(int X, int Y, int Delta);
- private sealed record KeyEventMsg(int Key, string KeyChar, bool Shift, bool Alt, bool Control, bool Pressed);
-
internal override void Start(string[] args)
{
if (args.Length == 0 || !int.TryParse(args[0], out int port) || port <= 0 || port > 65535)
@@ -37,7 +33,7 @@ internal sealed class RemoteDesktopProcess() : Process("Remote Desktop Server")
server = new TcpRpcServer();
networkSource = new NetworkRenderSource(server);
- server.ListenTo("SyncRequest", async _ =>
+ server.ListenTo("SyncRequest", async _ =>
{
Canvas canvas = FullScreenCanvas.GetFullScreenCanvas();
RenderCommand screenInfo = new()
@@ -88,35 +84,37 @@ internal sealed class RemoteDesktopProcess() : Process("Remote Desktop Server")
return;
}
- server.ListenTo("MouseMove", async (msg) =>
+ server.ListenTo("MouseMove", async (payload) =>
{
+ var msg = DeserializeMouseMove(payload);
WindowManager.EnqueueMouseEvent(MouseEvent.Move(msg.X, msg.Y));
});
- server.ListenTo("MouseDown", async (msg) =>
+ server.ListenTo("MouseDown", async (payload) =>
{
- MouseButton button = ParseButton(msg.Button);
- WindowManager.EnqueueMouseEvent(MouseEvent.ButtonDown(msg.X, msg.Y, button));
+ var msg = DeserializeMouseButton(payload);
+ WindowManager.EnqueueMouseEvent(MouseEvent.ButtonDown(msg.X, msg.Y, ParseButton(msg.Button)));
});
- server.ListenTo("MouseUp", async (msg) =>
+ server.ListenTo("MouseUp", async (payload) =>
{
- MouseButton button = ParseButton(msg.Button);
- WindowManager.EnqueueMouseEvent(MouseEvent.ButtonUp(msg.X, msg.Y, button));
+ var msg = DeserializeMouseButton(payload);
+ WindowManager.EnqueueMouseEvent(MouseEvent.ButtonUp(msg.X, msg.Y, ParseButton(msg.Button)));
});
- server.ListenTo("MouseWheel", async (msg) =>
+ server.ListenTo("MouseWheel", async (payload) =>
{
+ var msg = DeserializeMouseWheel(payload);
WindowManager.EnqueueMouseEvent(MouseEvent.Wheel(msg.X, msg.Y, msg.Delta));
});
- server.ListenTo("KeyEvent", async (msg) =>
+ server.ListenTo("KeyEvent", async (payload) =>
{
+ var msg = DeserializeKeyEvent(payload);
ConsoleKeyEx key = (ConsoleKeyEx)msg.Key;
char keyChar = msg.KeyChar.Length > 0 ? msg.KeyChar[0] : '\0';
- bool isPressed = msg.Pressed;
- KeyEvent keyEvent = new(keyChar, key, msg.Shift, msg.Alt, msg.Control, isPressed ? KeyEvent.KeyEventType.Make : KeyEvent.KeyEventType.Break);
+ KeyEvent keyEvent = new(keyChar, key, msg.Shift, msg.Alt, msg.Control, msg.Pressed ? KeyEvent.KeyEventType.Make : KeyEvent.KeyEventType.Break);
WindowManager.EnqueueKeyEvent(keyEvent);
});
}
@@ -131,4 +129,92 @@ internal sealed class RemoteDesktopProcess() : Process("Remote Desktop Server")
_ => MouseButton.None
};
}
+
+ private static byte[] SerializeMouseMove(int x, int y)
+ {
+ byte[] data = new byte[8];
+ WriteInt32(data, 0, x);
+ WriteInt32(data, 4, y);
+ return data;
+ }
+
+ private static (int X, int Y) DeserializeMouseMove(byte[] data)
+ {
+ return (ReadInt32(data, 0), ReadInt32(data, 4));
+ }
+
+ private static byte[] SerializeMouseButton(int x, int y, string button)
+ {
+ byte[] buttonBytes = Encoding.UTF8.GetBytes(button);
+ byte[] data = new byte[8 + 4 + buttonBytes.Length];
+ WriteInt32(data, 0, x);
+ WriteInt32(data, 4, y);
+ WriteInt32(data, 8, buttonBytes.Length);
+ buttonBytes.CopyTo(data, 12);
+ return data;
+ }
+
+ private static (int X, int Y, string Button) DeserializeMouseButton(byte[] data)
+ {
+ int x = ReadInt32(data, 0);
+ int y = ReadInt32(data, 4);
+ int len = ReadInt32(data, 8);
+ string button = Encoding.UTF8.GetString(data, 12, len);
+ return (x, y, button);
+ }
+
+ private static byte[] SerializeMouseWheel(int x, int y, int delta)
+ {
+ byte[] data = new byte[12];
+ WriteInt32(data, 0, x);
+ WriteInt32(data, 4, y);
+ WriteInt32(data, 8, delta);
+ return data;
+ }
+
+ private static (int X, int Y, int Delta) DeserializeMouseWheel(byte[] data)
+ {
+ return (ReadInt32(data, 0), ReadInt32(data, 4), ReadInt32(data, 8));
+ }
+
+ private static byte[] SerializeKeyEvent(int key, string keyChar, bool shift, bool alt, bool control, bool pressed)
+ {
+ byte[] charBytes = Encoding.UTF8.GetBytes(keyChar);
+ byte[] data = new byte[4 + 4 + charBytes.Length + 4];
+ WriteInt32(data, 0, key);
+ WriteInt32(data, 4, charBytes.Length);
+ charBytes.CopyTo(data, 8);
+ int offset = 8 + charBytes.Length;
+ data[offset++] = shift ? (byte)1 : (byte)0;
+ data[offset++] = alt ? (byte)1 : (byte)0;
+ data[offset++] = control ? (byte)1 : (byte)0;
+ data[offset] = pressed ? (byte)1 : (byte)0;
+ return data;
+ }
+
+ private static (int Key, string KeyChar, bool Shift, bool Alt, bool Control, bool Pressed) DeserializeKeyEvent(byte[] data)
+ {
+ int key = ReadInt32(data, 0);
+ int charLen = ReadInt32(data, 4);
+ string keyChar = Encoding.UTF8.GetString(data, 8, charLen);
+ int offset = 8 + charLen;
+ bool shift = data[offset] != 0;
+ bool alt = data[offset + 1] != 0;
+ bool control = data[offset + 2] != 0;
+ bool pressed = data[offset + 3] != 0;
+ return (key, keyChar, shift, alt, control, pressed);
+ }
+
+ private static void WriteInt32(byte[] data, int offset, int value)
+ {
+ data[offset] = (byte)(value & 0xFF);
+ data[offset + 1] = (byte)((value >> 8) & 0xFF);
+ data[offset + 2] = (byte)((value >> 16) & 0xFF);
+ data[offset + 3] = (byte)((value >> 24) & 0xFF);
+ }
+
+ private static int ReadInt32(byte[] data, int offset)
+ {
+ return data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) | (data[offset + 3] << 24);
+ }
}
diff --git a/UI/GUI/Rendering/NetworkRenderSource.cs b/UI/GUI/Rendering/NetworkRenderSource.cs
index 663bf42..3ebd4da 100644
--- a/UI/GUI/Rendering/NetworkRenderSource.cs
+++ b/UI/GUI/Rendering/NetworkRenderSource.cs
@@ -11,7 +11,7 @@ public sealed class NetworkRenderSource(TcpRpcServer server) : IRenderSource
foreach (RenderCommand cmd in commands)
{
byte[] data = cmd.ToBytes();
- _ = server.SendRawToAll(MessageType, data);
+ server.SendRawToAll(MessageType, data);
}
}
diff --git a/build-and-run.ps1 b/build-and-run.ps1
index cb4723a..04bb896 100644
--- a/build-and-run.ps1
+++ b/build-and-run.ps1
@@ -3,4 +3,5 @@ Set-Location $PSScriptRoot
cosmos build
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
-cosmos run
\ No newline at end of file
+#cosmos run
+bash -c "qemu-system-x86_64 -M q35 -cpu max -m 512M -serial stdio -cdrom ./output-x64/RemSox.iso -vga std -nic user,hostfwd=tcp::9999-:9999"
\ No newline at end of file