Convert desktop to EchoType Bridge: pipe injection via TIP

- Add EchoType to TextInjectionMode enum (becomes default)
- Add WriteToEchoTypePipe(): write [UINT32 cch][WCHAR text] to
  \.\pipe\EchoType\pid_<PID> via NamedPipeClientStream
- InsertText(): route to EchoType pipe when mode is EchoType
- TrayApplicationContext: add 'EchoType (recommended)' to input mode menu
- Update tray branding: 'EchoType Bridge' in tooltips and balloon tips
- AppLogger: log to %LocalAppData%\EchoType\bridge.log

The bridge keeps WebSocket server + UDP discovery + audio control
unchanged. Text injection now goes through the EchoType TIP's
composition-commit mechanism for atomic insertion, falling back to
clipboard and SendInput as secondary options.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Misaka_Company
2026-08-12 15:06:30 +08:00
parent 30da1680f2
commit e0a66c7a8c
3 changed files with 78 additions and 12 deletions

View File

@@ -12,6 +12,7 @@ public sealed class TrayApplicationContext : ApplicationContext
private readonly Icon connectedIcon;
private readonly Icon waitIcon;
private readonly SynchronizationContext uiContext;
private ToolStripMenuItem? echoTypeMenuItem;
private ToolStripMenuItem? clipboardPasteMenuItem;
private ToolStripMenuItem? sendInputTypingMenuItem;
private bool? lastConnectionState;
@@ -34,7 +35,7 @@ public sealed class TrayApplicationContext : ApplicationContext
notifyIcon = new NotifyIcon
{
Icon = waitIcon,
Text = "WirelessTextSyncer starting...",
Text = "EchoType Bridge starting...",
Visible = true,
ContextMenuStrip = BuildMenu()
};
@@ -49,7 +50,7 @@ public sealed class TrayApplicationContext : ApplicationContext
catch (Exception ex)
{
AppLogger.Error("Failed to start tray application.", ex);
notifyIcon.BalloonTipTitle = "WirelessTextSyncer";
notifyIcon.BalloonTipTitle = "EchoType Bridge";
notifyIcon.BalloonTipText = $"Failed to start: {ex.Message}";
notifyIcon.ShowBalloonTip(5000);
}
@@ -83,6 +84,10 @@ public sealed class TrayApplicationContext : ApplicationContext
private ToolStripMenuItem BuildInputModeMenu()
{
var inputModeMenu = new ToolStripMenuItem("Input mode");
echoTypeMenuItem = new ToolStripMenuItem("EchoType (recommended)")
{
CheckOnClick = false
};
clipboardPasteMenuItem = new ToolStripMenuItem("Clipboard paste")
{
CheckOnClick = false
@@ -92,8 +97,10 @@ public sealed class TrayApplicationContext : ApplicationContext
CheckOnClick = false
};
echoTypeMenuItem.Click += (_, _) => SetInputMode(TextInjectionMode.EchoType);
clipboardPasteMenuItem.Click += (_, _) => SetInputMode(TextInjectionMode.ClipboardPaste);
sendInputTypingMenuItem.Click += (_, _) => SetInputMode(TextInjectionMode.SendInputTyping);
inputModeMenu.DropDownItems.Add(echoTypeMenuItem);
inputModeMenu.DropDownItems.Add(clipboardPasteMenuItem);
inputModeMenu.DropDownItems.Add(sendInputTypingMenuItem);
UpdateInputModeMenuChecks();
@@ -106,13 +113,18 @@ public sealed class TrayApplicationContext : ApplicationContext
AppLogger.Info($"Text injection mode changed to {mode}.");
UpdateInputModeMenuChecks();
notifyIcon.BalloonTipTitle = "WirelessTextSyncer";
notifyIcon.BalloonTipTitle = "EchoType Bridge";
notifyIcon.BalloonTipText = $"Input mode: {GetInputModeLabel(mode)}";
notifyIcon.ShowBalloonTip(1500);
}
private void UpdateInputModeMenuChecks()
{
if (echoTypeMenuItem is not null)
{
echoTypeMenuItem.Checked = keyboard.Mode == TextInjectionMode.EchoType;
}
if (clipboardPasteMenuItem is not null)
{
clipboardPasteMenuItem.Checked = keyboard.Mode == TextInjectionMode.ClipboardPaste;
@@ -126,7 +138,9 @@ public sealed class TrayApplicationContext : ApplicationContext
private static string GetInputModeLabel(TextInjectionMode mode)
{
return mode == TextInjectionMode.ClipboardPaste ? "Clipboard paste" : "SendInput typing";
return mode == TextInjectionMode.EchoType ? "EchoType"
: mode == TextInjectionMode.ClipboardPaste ? "Clipboard paste"
: "SendInput typing";
}
private void OnTrayIconClicked(object? sender, MouseEventArgs e)
@@ -140,7 +154,7 @@ public sealed class TrayApplicationContext : ApplicationContext
private void CopyConnectionAddress()
{
Clipboard.SetText($"ws://{server.LocalIpAddress}:{server.Port}");
notifyIcon.BalloonTipTitle = "WirelessTextSyncer";
notifyIcon.BalloonTipTitle = "EchoType Bridge";
notifyIcon.BalloonTipText = "Connection address copied.";
notifyIcon.ShowBalloonTip(1500);
}
@@ -154,7 +168,7 @@ public sealed class TrayApplicationContext : ApplicationContext
var connected = server.HasClient;
var status = connected ? "connected" : "waiting";
var text = $"WirelessTextSyncer {server.LocalIpAddress}:{server.Port} ({status})";
var text = $"EchoType Bridge {server.LocalIpAddress}:{server.Port} ({status})";
notifyIcon.Icon = connected ? connectedIcon : waitIcon;
notifyIcon.Text = text.Length > 63 ? text[..63] : text;
@@ -168,7 +182,7 @@ public sealed class TrayApplicationContext : ApplicationContext
private void ShowConnectionToast(bool connected)
{
notifyIcon.BalloonTipTitle = "WirelessTextSyncer";
notifyIcon.BalloonTipTitle = "EchoType Bridge";
notifyIcon.BalloonTipText = connected
? $"Device connected to {server.LocalIpAddress}:{server.Port}."
: "Device disconnected. Waiting for connection.";