Add support for binary JPEG rects, as well as some other minor tweaks

This commit is contained in:
Elijah R 2024-06-25 21:52:21 -04:00
parent 59fa954e11
commit 473756d8b4
5 changed files with 92 additions and 20 deletions

View file

@ -14,6 +14,8 @@ using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CollabVMSharp.Protocol;
using MsgPack.Serialization;
using Timer = System.Timers.Timer; using Timer = System.Timers.Timer;
// ReSharper disable FieldCanBeMadeReadOnly.Local // ReSharper disable FieldCanBeMadeReadOnly.Local
// ReSharper disable ArrangeObjectCreationWhenTypeNotEvident // ReSharper disable ArrangeObjectCreationWhenTypeNotEvident
@ -21,7 +23,9 @@ using Timer = System.Timers.Timer;
// ReSharper disable ArrangeObjectCreationWhenTypeEvident // ReSharper disable ArrangeObjectCreationWhenTypeEvident
namespace CollabVMSharp; namespace CollabVMSharp;
public class CollabVMClient { public class CollabVMClient
{
private static readonly string[] SupportedCapabilities = { "bin" };
// Fields // Fields
private Uri url; private Uri url;
private string? username; private string? username;
@ -43,6 +47,7 @@ public class CollabVMClient {
private Dictionary<string, Action<string, string>> commandsOneArg; private Dictionary<string, Action<string, string>> commandsOneArg;
private bool _usesAccountAuth; private bool _usesAccountAuth;
private Dictionary<string,string> _headers; private Dictionary<string,string> _headers;
private MessagePackSerializer<CollabVMProtocolMessage> _msgpack;
// Tasks and related // Tasks and related
private TaskCompletionSource<Node[]> GotNodeList; private TaskCompletionSource<Node[]> GotNodeList;
private TaskCompletionSource<bool> GotConnectionToNode; private TaskCompletionSource<bool> GotConnectionToNode;
@ -152,6 +157,7 @@ public class CollabVMClient {
this.GotIPTasks = new(); this.GotIPTasks = new();
this.QEMUMonitorResult = new(); this.QEMUMonitorResult = new();
this.QEMUMonitorSemaphore = new(1, 1); this.QEMUMonitorSemaphore = new(1, 1);
this._msgpack = MessagePackSerializer.Get<CollabVMProtocolMessage>();
// Assign empty handlers to prevent exception // Assign empty handlers to prevent exception
Chat += delegate { }; Chat += delegate { };
ChatHistory += delegate { }; ChatHistory += delegate { };
@ -188,6 +194,8 @@ public class CollabVMClient {
this.SendMsg(Guacutils.Encode("rename", this.username)); this.SendMsg(Guacutils.Encode("rename", this.username));
else else
this.SendMsg(Guacutils.Encode("rename")); this.SendMsg(Guacutils.Encode("rename"));
if (SupportedCapabilities.Length > 0)
this.SendMsg(Guacutils.Encode(SupportedCapabilities.Prepend("cap").ToArray()));
this.NOPRecieve.Start(); this.NOPRecieve.Start();
this.WebSocketLoop(); this.WebSocketLoop();
if (this.node == null) return; if (this.node == null) return;
@ -199,15 +207,13 @@ public class CollabVMClient {
private async void WebSocketLoop() { private async void WebSocketLoop() {
ArraySegment<byte> receivebuffer = new ArraySegment<byte>(new byte[8192]); ArraySegment<byte> receivebuffer = new ArraySegment<byte>(new byte[8192]);
do { do {
MemoryStream ms = new(); using var ms = new MemoryStream();
WebSocketReceiveResult res; WebSocketReceiveResult res;
do { do {
try { try {
res = await socket.ReceiveAsync(receivebuffer, CancellationToken.None); res = await socket.ReceiveAsync(receivebuffer, CancellationToken.None);
} catch (WebSocketException e) { } catch (WebSocketException e) {
#if DEBUG Error.Invoke(this, $"Got {e.Message} while reading from WebSocket, closing connection");
Console.Error.WriteLine($"Got {e.Message} while reading from WebSocket, closing connection");
#endif
Cleanup(true); Cleanup(true);
return; return;
} }
@ -218,28 +224,72 @@ public class CollabVMClient {
} }
await ms.WriteAsync(receivebuffer.Array, 0, res.Count); await ms.WriteAsync(receivebuffer.Array, 0, res.Count);
} while (!res.EndOfMessage); } while (!res.EndOfMessage);
string msg; if (res.MessageType == WebSocketMessageType.Text)
try { {
msg = Encoding.UTF8.GetString(ms.ToArray()); string msg;
} catch (Exception e) { try {
#if DEBUG msg = Encoding.UTF8.GetString(ms.ToArray());
await Console.Error.WriteLineAsync($"Failed to read message from socket: {e.Message}"); } catch (Exception e) {
#endif Error.Invoke(this, $"Failed to read message from socket: {e.Message}");
continue; continue;
} finally {ms.Dispose();} }
this.ProcessMessage(msg); this.ProcessMessage(msg);
} else if (res.MessageType == WebSocketMessageType.Binary)
{
ms.Position = 0;
await this.ProcessBinaryMessage(ms);
}
} while (socket.State == WebSocketState.Open); } while (socket.State == WebSocketState.Open);
this.Cleanup(); this.Cleanup();
} }
private async Task ProcessBinaryMessage(MemoryStream data)
{
CollabVMProtocolMessage msg;
try
{
msg = await this._msgpack.UnpackAsync(data);
}
catch (Exception ex)
{
Error.Invoke(this, $"Failed to unpack binary message: {ex.Message}");
return;
}
switch (msg.type)
{
case CollabVMProtocolMessageType.rect:
{
if (msg.rect == null || msg.rect!.data == null) return;
Image rect;
try
{
rect = Image.Load(msg.rect.data);
}
catch (Exception ex)
{
Error.Invoke(this, "Server sent an invalid screen rect");
return;
}
framebuffer.Mutate(f => f.DrawImage(rect, new Point(msg.rect.x, msg.rect.y), 1));
this.Rect.Invoke(this, new RectEventArgs
{
X = msg.rect.x,
Y = msg.rect.y,
Data = rect,
});
break;
}
}
}
private async void ProcessMessage(string msg) { private async void ProcessMessage(string msg) {
string[] msgArr; string[] msgArr;
try { try {
msgArr = Guacutils.Decode(msg); msgArr = Guacutils.Decode(msg);
} catch (Exception e) { } catch (Exception e) {
#if DEBUG Error.Invoke(this, $"Failed to decode incoming message: {e.Message}");
await Console.Error.WriteLineAsync($"Failed to decode incoming message: {e.Message}");
#endif
return; return;
} }
@ -327,7 +377,7 @@ public class CollabVMClient {
{ {
rect = Image.Load(Convert.FromBase64String(msgArr[5])); rect = Image.Load(Convert.FromBase64String(msgArr[5]));
} }
catch (FormatException ex) catch (Exception ex)
{ {
Error.Invoke(this, "Server sent an invalid screen rect"); Error.Invoke(this, "Server sent an invalid screen rect");
return; return;

View file

@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<LangVersion>10</LangVersion> <LangVersion>10</LangVersion>
<Version>2.7.0</Version> <Version>2.7.1</Version>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild> <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<PackageLicenseExpression>GPL-3.0-or-later</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-or-later</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>
@ -11,6 +11,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="MsgPack.Cli" Version="1.0.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.3" /> <PackageReference Include="SixLabors.ImageSharp" Version="3.1.3" />
</ItemGroup> </ItemGroup>

View file

@ -0,0 +1,7 @@
namespace CollabVMSharp.Protocol;
public class CollabVMProtocolMessage
{
public CollabVMProtocolMessageType type { get; set; }
public CollabVMRectMessage? rect { get; set; }
}

View file

@ -0,0 +1,6 @@
namespace CollabVMSharp.Protocol;
public enum CollabVMProtocolMessageType
{
rect = 0
}

View file

@ -0,0 +1,8 @@
namespace CollabVMSharp.Protocol;
public class CollabVMRectMessage
{
public int x { get; set; }
public int y { get; set; }
public byte[] data { get; set; }
}