Implement desktop sync protocol handling

This commit is contained in:
Misaka
2026-05-15 21:19:27 +08:00
parent 498251ad79
commit 4c314c510b
9 changed files with 253 additions and 14 deletions

View File

@@ -1,5 +1,8 @@
namespace WirelessTextSyncer.Windows.Models;
using System.Text.Json.Serialization;
[JsonConverter(typeof(SyncActionJsonConverter))]
public enum SyncAction
{
InsertText,

View File

@@ -0,0 +1,41 @@
using System.Text.Json;
using System.Text.Json.Serialization;
namespace WirelessTextSyncer.Windows.Models;
public sealed class SyncActionJsonConverter : JsonConverter<SyncAction>
{
public override SyncAction Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
var value = reader.GetString();
return value switch
{
"insertText" => SyncAction.InsertText,
"backspace" => SyncAction.Backspace,
"enter" => SyncAction.Enter,
"ping" => SyncAction.Ping,
_ => throw new JsonException($"Unsupported sync action: {value}")
};
}
public override void Write(
Utf8JsonWriter writer,
SyncAction value,
JsonSerializerOptions options)
{
var action = value switch
{
SyncAction.InsertText => "insertText",
SyncAction.Backspace => "backspace",
SyncAction.Enter => "enter",
SyncAction.Ping => "ping",
_ => throw new JsonException($"Unsupported sync action: {value}")
};
writer.WriteStringValue(action);
}
}

View File

@@ -1,7 +1,15 @@
namespace WirelessTextSyncer.Windows.Services;
using System.Runtime.InteropServices;
public sealed class KeyboardInjectionService : IKeyboardInjectionService
{
private const uint InputKeyboard = 1;
private const uint KeyEventFKeyUp = 0x0002;
private const uint KeyEventFUnicode = 0x0004;
private const ushort VirtualKeyBack = 0x08;
private const ushort VirtualKeyReturn = 0x0D;
public void InsertText(string text)
{
if (string.IsNullOrEmpty(text))
@@ -9,16 +17,103 @@ public sealed class KeyboardInjectionService : IKeyboardInjectionService
return;
}
SendKeys.SendWait(text);
foreach (var character in text)
{
SendUnicode(character);
}
}
public void Backspace()
{
SendKeys.SendWait("{BACKSPACE}");
SendVirtualKey(VirtualKeyBack);
}
public void Enter()
{
SendKeys.SendWait("{ENTER}");
SendVirtualKey(VirtualKeyReturn);
}
private static void SendUnicode(char character)
{
Send([
CreateUnicodeInput(character, 0),
CreateUnicodeInput(character, KeyEventFKeyUp)
]);
}
private static void SendVirtualKey(ushort virtualKey)
{
Send([
CreateVirtualKeyInput(virtualKey, 0),
CreateVirtualKeyInput(virtualKey, KeyEventFKeyUp)
]);
}
private static void Send(INPUT[] inputs)
{
var sent = SendInput((uint)inputs.Length, inputs, Marshal.SizeOf<INPUT>());
if (sent != inputs.Length)
{
throw new InvalidOperationException("Windows did not accept all keyboard input events.");
}
}
private static INPUT CreateUnicodeInput(char character, uint flags)
{
return new INPUT
{
type = InputKeyboard,
U = new InputUnion
{
ki = new KEYBDINPUT
{
wScan = character,
dwFlags = KeyEventFUnicode | flags
}
}
};
}
private static INPUT CreateVirtualKeyInput(ushort virtualKey, uint flags)
{
return new INPUT
{
type = InputKeyboard,
U = new InputUnion
{
ki = new KEYBDINPUT
{
wVk = virtualKey,
dwFlags = flags
}
}
};
}
[DllImport("user32.dll", SetLastError = true)]
private static extern uint SendInput(uint numberOfInputs, INPUT[] inputs, int sizeOfInputStructure);
[StructLayout(LayoutKind.Sequential)]
private struct INPUT
{
public uint type;
public InputUnion U;
}
[StructLayout(LayoutKind.Explicit)]
private struct InputUnion
{
[FieldOffset(0)]
public KEYBDINPUT ki;
}
[StructLayout(LayoutKind.Sequential)]
private struct KEYBDINPUT
{
public ushort wVk;
public ushort wScan;
public uint dwFlags;
public uint time;
public UIntPtr dwExtraInfo;
}
}

View File

@@ -8,8 +8,14 @@ namespace WirelessTextSyncer.Windows.Services;
public sealed class WebSocketServerService : IDisposable
{
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
{
PropertyNameCaseInsensitive = true
};
private readonly SyncMessageHandler messageHandler;
private readonly List<IWebSocketConnection> clients = [];
private readonly object clientsLock = new();
private WebSocketServer? server;
public WebSocketServerService(SyncMessageHandler messageHandler)
@@ -21,7 +27,16 @@ public sealed class WebSocketServerService : IDisposable
public string LocalIpAddress { get; private set; } = "127.0.0.1";
public bool HasClient => clients.Count > 0;
public bool HasClient
{
get
{
lock (clientsLock)
{
return clients.Count > 0;
}
}
}
public event EventHandler? StatusChanged;
@@ -35,12 +50,20 @@ public sealed class WebSocketServerService : IDisposable
{
socket.OnOpen = () =>
{
clients.Add(socket);
lock (clientsLock)
{
clients.Add(socket);
}
StatusChanged?.Invoke(this, EventArgs.Empty);
};
socket.OnClose = () =>
{
clients.Remove(socket);
lock (clientsLock)
{
clients.Remove(socket);
}
StatusChanged?.Invoke(this, EventArgs.Empty);
};
socket.OnMessage = HandleRawMessage;
@@ -49,24 +72,37 @@ public sealed class WebSocketServerService : IDisposable
public void Dispose()
{
foreach (var client in clients.ToArray())
IWebSocketConnection[] currentClients;
lock (clientsLock)
{
currentClients = clients.ToArray();
clients.Clear();
}
foreach (var client in currentClients)
{
client.Close();
}
clients.Clear();
server?.Dispose();
}
private void HandleRawMessage(string rawMessage)
{
var message = JsonSerializer.Deserialize<SyncMessage>(rawMessage);
if (message is null)
try
{
var message = JsonSerializer.Deserialize<SyncMessage>(rawMessage, JsonOptions);
if (message is null)
{
return;
}
messageHandler.Handle(message);
}
catch (JsonException)
{
return;
}
messageHandler.Handle(message);
}
private static string ResolveLocalIpAddress()

View File

@@ -75,6 +75,7 @@ public sealed class TrayApplicationContext : ApplicationContext
private void UpdateTrayText()
{
var status = server.HasClient ? "connected" : "waiting";
notifyIcon.Text = $"WirelessTextSyncer {server.LocalIpAddress}:{server.Port} ({status})";
var text = $"WirelessTextSyncer {server.LocalIpAddress}:{server.Port} ({status})";
notifyIcon.Text = text.Length > 63 ? text[..63] : text;
}
}

View File

@@ -10,7 +10,6 @@
<ItemGroup>
<PackageReference Include="Fleck" Version="1.2.0" />
<PackageReference Include="InputSimulatorPlus" Version="1.0.7" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.8" />
</ItemGroup>