From 633ded2d62a2e911e3c64c003c070e40bae5c1a7 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Wed, 17 Jun 2026 00:50:05 +0200 Subject: [PATCH] Add unified input event queue and cursor support for window manager --- Processes/DesktopProcess.cs | 57 ++++++++++ Processes/RemoteDesktopProcess.cs | 62 +++++++++++ UI/GUI/Rendering/CanvasRenderSource.cs | 10 +- UI/GUI/Rendering/RenderCommand.cs | 9 ++ UI/GUI/Rendering/RenderCommandType.cs | 2 + UI/GUI/Windows/Window.cs | 1 + UI/GUI/Windows/WindowManager.cs | 139 ++++++++++++++++++++----- 7 files changed, 253 insertions(+), 27 deletions(-) diff --git a/Processes/DesktopProcess.cs b/Processes/DesktopProcess.cs index d8b2b19..791c692 100644 --- a/Processes/DesktopProcess.cs +++ b/Processes/DesktopProcess.cs @@ -1,6 +1,9 @@ using Cosmos.Kernel.System.Graphics; +using Cosmos.Kernel.System.Keyboard; +using Cosmos.Kernel.System.Mouse; using RemSox.Processing; +using RemSox.UI; using RemSox.UI.GUI.Layout; using RemSox.UI.GUI.Rendering; using RemSox.UI.GUI.UIEelements.Controls; @@ -115,8 +118,13 @@ internal class DesktopProcess() : Process("Desktop Manager") } } + private Point lastLocalMousePos = Point.Empty; + private bool lastLocalLeft, lastLocalRight, lastLocalMiddle; + internal override void Tick() { + PollLocalHardware(); + WindowManager.Update(); if (tickCount % 5 == 0) @@ -132,6 +140,55 @@ internal class DesktopProcess() : Process("Desktop Manager") } } + private void PollLocalHardware() + { + Point pos = new(MouseManager.X, MouseManager.Y); + + if (pos != lastLocalMousePos) + { + WindowManager.EnqueueMouseEvent(MouseEvent.Move(pos.X, pos.Y)); + lastLocalMousePos = pos; + } + + bool left = MouseManager.LeftButton; + if (left != lastLocalLeft) + { + WindowManager.EnqueueMouseEvent(left + ? MouseEvent.ButtonDown(pos.X, pos.Y, MouseButton.Left) + : MouseEvent.ButtonUp(pos.X, pos.Y, MouseButton.Left)); + lastLocalLeft = left; + } + + bool right = MouseManager.RightButton; + if (right != lastLocalRight) + { + WindowManager.EnqueueMouseEvent(right + ? MouseEvent.ButtonDown(pos.X, pos.Y, MouseButton.Right) + : MouseEvent.ButtonUp(pos.X, pos.Y, MouseButton.Right)); + lastLocalRight = right; + } + + bool middle = MouseManager.MiddleButton; + if (middle != lastLocalMiddle) + { + WindowManager.EnqueueMouseEvent(middle + ? MouseEvent.ButtonDown(pos.X, pos.Y, MouseButton.Middle) + : MouseEvent.ButtonUp(pos.X, pos.Y, MouseButton.Middle)); + lastLocalMiddle = middle; + } + + int scroll = MouseManager.ScrollDelta; + if (scroll != 0) + { + WindowManager.EnqueueMouseEvent(MouseEvent.Wheel(pos.X, pos.Y, scroll)); + } + + while (KeyboardManager.TryReadKey(out KeyEvent? keyEvent) && keyEvent is not null) + { + WindowManager.EnqueueKeyEvent(keyEvent); + } + } + private void UpdateWindowButtons() { List allWindows = WindowManager.GetAllWindows() diff --git a/Processes/RemoteDesktopProcess.cs b/Processes/RemoteDesktopProcess.cs index b646958..d1ff162 100644 --- a/Processes/RemoteDesktopProcess.cs +++ b/Processes/RemoteDesktopProcess.cs @@ -1,5 +1,8 @@ +using Cosmos.Kernel.System.Keyboard; + using RemSox.Networking; using RemSox.Processing; +using RemSox.UI; using RemSox.UI.GUI.Rendering; using RemSox.UI.GUI.Windows; @@ -13,6 +16,12 @@ 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) @@ -32,6 +41,8 @@ internal sealed class RemoteDesktopProcess() : Process("Remote Desktop Server") WindowManager.InvalidateAll(); }); + RegisterInputHandlers(); + _ = Task.Run(() => server.StartAsync(port, cts.Token)); WindowManager.AddRenderSource(networkSource); @@ -55,4 +66,55 @@ internal sealed class RemoteDesktopProcess() : Process("Remote Desktop Server") Logger.Log("Remote desktop server stopped.", Logging.LogSeverity.Info); } + + private void RegisterInputHandlers() + { + if (server is null) + { + return; + } + + server.ListenTo("MouseMove", async (msg) => + { + WindowManager.EnqueueMouseEvent(MouseEvent.Move(msg.X, msg.Y)); + }); + + server.ListenTo("MouseDown", async (msg) => + { + MouseButton button = ParseButton(msg.Button); + WindowManager.EnqueueMouseEvent(MouseEvent.ButtonDown(msg.X, msg.Y, button)); + }); + + server.ListenTo("MouseUp", async (msg) => + { + MouseButton button = ParseButton(msg.Button); + WindowManager.EnqueueMouseEvent(MouseEvent.ButtonUp(msg.X, msg.Y, button)); + }); + + server.ListenTo("MouseWheel", async (msg) => + { + WindowManager.EnqueueMouseEvent(MouseEvent.Wheel(msg.X, msg.Y, msg.Delta)); + }); + + server.ListenTo("KeyEvent", async (msg) => + { + 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); + WindowManager.EnqueueKeyEvent(keyEvent); + }); + } + + private static MouseButton ParseButton(string button) + { + return button switch + { + "Left" => MouseButton.Left, + "Right" => MouseButton.Right, + "Middle" => MouseButton.Middle, + _ => MouseButton.None + }; + } } diff --git a/UI/GUI/Rendering/CanvasRenderSource.cs b/UI/GUI/Rendering/CanvasRenderSource.cs index 0975b48..b9fdde4 100644 --- a/UI/GUI/Rendering/CanvasRenderSource.cs +++ b/UI/GUI/Rendering/CanvasRenderSource.cs @@ -1,6 +1,5 @@ using Cosmos.Kernel.System.Graphics; using Cosmos.Kernel.System.Graphics.Fonts; -using Cosmos.Kernel.System.Mouse; using RemSox.UI.GUI.UIEelements; using RemSox.Utils; @@ -26,6 +25,8 @@ public sealed class CanvasRenderSource : IRenderSource private static bool isPositionDirty = true; private static Point lastPointerPosition = new(-1, -1); + private static Point cursorPosition; + private static readonly Lock renderLock = new(); public void Render(IEnumerable commands) @@ -56,6 +57,11 @@ public sealed class CanvasRenderSource : IRenderSource RemovePrimitives(command.WindowId, command.ElementId); break; + case RenderCommandType.SetCursor: + cursorPosition = command.Position; + isPositionDirty = true; + break; + default: if (windowCanvases.ContainsKey(command.WindowId)) { @@ -70,7 +76,7 @@ public sealed class CanvasRenderSource : IRenderSource public void Composite() { Canvas screenCanvas = FullScreenCanvas.GetFullScreenCanvas(); - Point pointerPosition = new(MouseManager.X, MouseManager.Y); + Point pointerPosition = cursorPosition; lock (renderLock) { diff --git a/UI/GUI/Rendering/RenderCommand.cs b/UI/GUI/Rendering/RenderCommand.cs index 5a25f5d..27d05b9 100644 --- a/UI/GUI/Rendering/RenderCommand.cs +++ b/UI/GUI/Rendering/RenderCommand.cs @@ -90,6 +90,11 @@ public class RenderCommand case RenderCommandType.RemovePrimitives: break; + + case RenderCommandType.SetCursor: + WriteInt16(s, Position.X); + WriteInt16(s, Position.Y); + break; } } @@ -154,6 +159,10 @@ public class RenderCommand case RenderCommandType.RemovePrimitives: break; + + case RenderCommandType.SetCursor: + pos = new Point(ReadInt16(data, ref offset), ReadInt16(data, ref offset)); + break; } return new RenderCommand diff --git a/UI/GUI/Rendering/RenderCommandType.cs b/UI/GUI/Rendering/RenderCommandType.cs index cf1b8eb..7a9caca 100644 --- a/UI/GUI/Rendering/RenderCommandType.cs +++ b/UI/GUI/Rendering/RenderCommandType.cs @@ -15,4 +15,6 @@ public enum RenderCommandType : byte DrawPoint = 0x16, RemovePrimitives = 0x20, + + SetCursor = 0x30, } diff --git a/UI/GUI/Windows/Window.cs b/UI/GUI/Windows/Window.cs index f20f5a7..de59568 100644 --- a/UI/GUI/Windows/Window.cs +++ b/UI/GUI/Windows/Window.cs @@ -254,6 +254,7 @@ public sealed class Window(string title, int processId, int id, IRenderSource re } // Remove old primitives for this element, then emit new ones + // TODO: optimize by diffing properties and only updating what changed instead of full remove+add commands.Add(new RenderCommand { WindowId = Id, diff --git a/UI/GUI/Windows/WindowManager.cs b/UI/GUI/Windows/WindowManager.cs index 962ae9f..29fb25a 100644 --- a/UI/GUI/Windows/WindowManager.cs +++ b/UI/GUI/Windows/WindowManager.cs @@ -1,6 +1,5 @@ using Cosmos.Kernel.System.Graphics; using Cosmos.Kernel.System.Keyboard; -using Cosmos.Kernel.System.Mouse; using RemSox.Processing; using RemSox.UI.GUI.Rendering; @@ -31,15 +30,39 @@ public static class WindowManager private static readonly MuliRenderSource renderSource = new([]); + private static readonly Queue mouseQueue = []; + private static readonly Queue keyQueue = []; + private static readonly Lock inputLock = new(); + + /// Enqueues a mouse event from any input source. + public static void EnqueueMouseEvent(MouseEvent mouseEvent) + { + lock (inputLock) + { + mouseQueue.Enqueue(mouseEvent); + } + } + + /// Enqueues a keyboard event from any input source. + public static void EnqueueKeyEvent(KeyEvent keyEvent) + { + lock (inputLock) + { + keyQueue.Enqueue(keyEvent); + } + } /// - /// Processes input, updates interaction state, and triggers rendering of all windows. + /// Processes queued input and updates interaction state, + /// and triggers rendering. /// internal static void Update() { - Point pointerPosition = new(MouseManager.X, MouseManager.Y); - bool leftButtonDown = MouseManager.LeftButton; - bool rightButtonDown = MouseManager.RightButton; - bool middleButtonDown = MouseManager.MiddleButton; + InputState input = DrainInput(); + + Point pointerPosition = input.Position; + bool leftButtonDown = input.LeftButton; + bool rightButtonDown = input.RightButton; + bool middleButtonDown = input.MiddleButton; Canvas canvas = FullScreenCanvas.GetFullScreenCanvas(); @@ -57,20 +80,31 @@ public static class WindowManager activeInteractWindow = null; } - while (KeyboardManager.TryReadKey(out KeyEvent? keyEvent) && keyEvent is not null) + foreach (KeyEvent keyEvent in input.KeyEvents) { focusedWindow?.HandleKeyEvent(keyEvent); } if (focusedWindow is not null) { - DispatchMouseEvents(focusedWindow, pointerPosition); + DispatchMouseEvents(focusedWindow, pointerPosition, + leftButtonDown, wasLeftButtonDown, + rightButtonDown, wasRightButtonDown, + middleButtonDown, wasMiddleButtonDown, + input.ScrollDelta); } wasLeftButtonDown = leftButtonDown; wasRightButtonDown = rightButtonDown; wasMiddleButtonDown = middleButtonDown; + renderSource.Render([new RenderCommand + { + Type = RenderCommandType.SetCursor, + WindowId = 0, + ElementId = 0, + Position = pointerPosition, + }]); renderSource.Composite(); lastPointerPosition = pointerPosition; @@ -382,45 +416,91 @@ public static class WindowManager return null; } - private static void DispatchMouseEvents(Window window, Point pos) + private static InputState DrainInput() { - int delta = MouseManager.ScrollDelta; + List mouseEvents; + List keyEvents; + lock (inputLock) + { + mouseEvents = [.. mouseQueue]; + mouseQueue.Clear(); + keyEvents = [.. keyQueue]; + keyQueue.Clear(); + } + + Point pos = lastPointerPosition; + bool left = wasLeftButtonDown, right = wasRightButtonDown, middle = wasMiddleButtonDown; + int scroll = 0; + + foreach (MouseEvent e in mouseEvents) + { + switch (e.Type) + { + case MouseEventType.Move: + pos = new Point(e.X, e.Y); + break; + case MouseEventType.ButtonDown: + if (e.Button == MouseButton.Left) { left = true; } + else if (e.Button == MouseButton.Right) { right = true; } + else if (e.Button == MouseButton.Middle) { middle = true; } + break; + case MouseEventType.ButtonUp: + if (e.Button == MouseButton.Left) { left = false; } + else if (e.Button == MouseButton.Right) { right = false; } + else if (e.Button == MouseButton.Middle) { middle = false; } + break; + case MouseEventType.Wheel: + scroll += e.Delta; + break; + } + } + + return new InputState(pos, left, right, middle, scroll, keyEvents); + } + + private static void DispatchMouseEvents( + Window window, Point pos, + bool leftDown, bool wasLeftDown, + bool rightDown, bool wasRightDown, + bool middleDown, bool wasMiddleDown, + int scrollDelta) + { if (pos != lastPointerPosition) { - window.HandleMouseEvent(new MouseEvent(MouseEventType.Move, pos.X, pos.Y, MouseButton.None, 0)); + window.HandleMouseEvent(MouseEvent.Move(pos.X, pos.Y)); } - if (MouseManager.LeftButton && !wasLeftButtonDown) + if (leftDown && !wasLeftDown) { - window.HandleMouseEvent(new MouseEvent(MouseEventType.ButtonDown, pos.X, pos.Y, MouseButton.Left, 0)); + window.HandleMouseEvent(MouseEvent.ButtonDown(pos.X, pos.Y, MouseButton.Left)); } - else if (!MouseManager.LeftButton && wasLeftButtonDown) + else if (!leftDown && wasLeftDown) { - window.HandleMouseEvent(new MouseEvent(MouseEventType.ButtonUp, pos.X, pos.Y, MouseButton.Left, 0)); + window.HandleMouseEvent(MouseEvent.ButtonUp(pos.X, pos.Y, MouseButton.Left)); } - if (MouseManager.RightButton && !wasRightButtonDown) + if (rightDown && !wasRightDown) { - window.HandleMouseEvent(new MouseEvent(MouseEventType.ButtonDown, pos.X, pos.Y, MouseButton.Right, 0)); + window.HandleMouseEvent(MouseEvent.ButtonDown(pos.X, pos.Y, MouseButton.Right)); } - else if (!MouseManager.RightButton && wasRightButtonDown) + else if (!rightDown && wasRightDown) { - window.HandleMouseEvent(new MouseEvent(MouseEventType.ButtonUp, pos.X, pos.Y, MouseButton.Right, 0)); + window.HandleMouseEvent(MouseEvent.ButtonUp(pos.X, pos.Y, MouseButton.Right)); } - if (MouseManager.MiddleButton && !wasMiddleButtonDown) + if (middleDown && !wasMiddleDown) { - window.HandleMouseEvent(new MouseEvent(MouseEventType.ButtonDown, pos.X, pos.Y, MouseButton.Middle, 0)); + window.HandleMouseEvent(MouseEvent.ButtonDown(pos.X, pos.Y, MouseButton.Middle)); } - else if (!MouseManager.MiddleButton && wasMiddleButtonDown) + else if (!middleDown && wasMiddleDown) { - window.HandleMouseEvent(new MouseEvent(MouseEventType.ButtonUp, pos.X, pos.Y, MouseButton.Middle, 0)); + window.HandleMouseEvent(MouseEvent.ButtonUp(pos.X, pos.Y, MouseButton.Middle)); } - if (delta != 0) + if (scrollDelta != 0) { - window.HandleMouseEvent(new MouseEvent(MouseEventType.Wheel, pos.X, pos.Y, MouseButton.None, delta)); + window.HandleMouseEvent(MouseEvent.Wheel(pos.X, pos.Y, scrollDelta)); } } @@ -429,6 +509,15 @@ public static class WindowManager return nextWindowId++; } + private readonly record struct InputState( + Point Position, + bool LeftButton, + bool RightButton, + bool MiddleButton, + int ScrollDelta, + List KeyEvents + ); + private sealed class MuliRenderSource(List sources) : IRenderSource { private readonly Lock sourcesLock = new();