mirror of
https://github.com/Stone-Red-Code/RemSox.git
synced 2026-09-04 09:06:23 +02:00
Reorganize project structure
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
using RemSox.Kernel.Processing;
|
||||
using RemSox.Kernel.UI.CLI;
|
||||
|
||||
namespace RemSox.Kernel.Processes;
|
||||
|
||||
internal class CliProcess() : Process("Cli")
|
||||
{
|
||||
private string currentInput = string.Empty;
|
||||
|
||||
private int historyIndex = -1;
|
||||
|
||||
private bool commandRunning = false;
|
||||
|
||||
private CancellationTokenSource? commandCancellationTokenSource = null;
|
||||
|
||||
internal override void Start(string[] args)
|
||||
{
|
||||
Console.Clear();
|
||||
Console.WriteLine("Welcome RemSox!");
|
||||
Console.WriteLine("Type 'help' to see available commands.");
|
||||
Console.WriteLine(Environment.ProcessorCount + " CPU cores detected");
|
||||
Console.Write("> ");
|
||||
|
||||
historyIndex = CommandManager.GetCommandHistory().Count;
|
||||
}
|
||||
|
||||
internal override async void Tick()
|
||||
{
|
||||
while (Console.KeyAvailable)
|
||||
{
|
||||
ConsoleKeyInfo key = Console.ReadKey(intercept: true);
|
||||
|
||||
if (commandRunning)
|
||||
{
|
||||
if (key.Key == ConsoleKey.C && key.Modifiers.HasFlag(ConsoleModifiers.Control) && commandCancellationTokenSource is not null)
|
||||
{
|
||||
await commandCancellationTokenSource.CancelAsync();
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (key.Key)
|
||||
{
|
||||
case ConsoleKey.Enter:
|
||||
Console.WriteLine();
|
||||
|
||||
await HandleCommand(currentInput);
|
||||
|
||||
historyIndex = CommandManager.GetCommandHistory().Count;
|
||||
|
||||
currentInput = string.Empty;
|
||||
Console.Write("> ");
|
||||
break;
|
||||
|
||||
case ConsoleKey.Backspace:
|
||||
if (currentInput.Length > 0)
|
||||
{
|
||||
currentInput = currentInput[..^1];
|
||||
Console.Write("\b \b");
|
||||
}
|
||||
break;
|
||||
case ConsoleKey.UpArrow:
|
||||
if (CommandManager.GetCommandHistory().Count > 0)
|
||||
{
|
||||
historyIndex = Math.Max(historyIndex - 1, 0);
|
||||
currentInput = CommandManager.GetCommandHistory()[historyIndex];
|
||||
Console.Write("\r> " + currentInput + new string(' ', Console.WindowWidth - currentInput.Length - 2));
|
||||
Console.CursorLeft = 0;
|
||||
Console.CursorTop--;
|
||||
Console.Write("> " + currentInput);
|
||||
}
|
||||
break;
|
||||
case ConsoleKey.DownArrow:
|
||||
if (CommandManager.GetCommandHistory().Count > 0)
|
||||
{
|
||||
historyIndex = Math.Min(historyIndex + 1, CommandManager.GetCommandHistory().Count - 1);
|
||||
currentInput = CommandManager.GetCommandHistory()[historyIndex];
|
||||
Console.Write("\r> " + currentInput + new string(' ', Console.WindowWidth - currentInput.Length - 2));
|
||||
Console.CursorLeft = 0;
|
||||
Console.CursorTop--;
|
||||
Console.Write("> " + currentInput);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (!char.IsControl(key.KeyChar))
|
||||
{
|
||||
currentInput += key.KeyChar;
|
||||
Console.Write(key.KeyChar);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleCommand(string input)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(input))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
commandCancellationTokenSource = new CancellationTokenSource();
|
||||
commandRunning = true;
|
||||
|
||||
bool handled = await CommandManager.TryExecuteAsync(input, Console.WriteLine, commandCancellationTokenSource.Token);
|
||||
|
||||
if (!handled)
|
||||
{
|
||||
Console.WriteLine($"\"{input}\" is not a command");
|
||||
}
|
||||
|
||||
commandRunning = false;
|
||||
commandCancellationTokenSource.Dispose();
|
||||
commandCancellationTokenSource = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
using RemSox.Kernel.UI.GUI.Rendering;
|
||||
using Cosmos.Kernel.System.Graphics;
|
||||
using Cosmos.Kernel.System.Keyboard;
|
||||
using Cosmos.Kernel.System.Mouse;
|
||||
|
||||
using RemSox.Kernel.Processing;
|
||||
using RemSox.Shared.UI;
|
||||
using RemSox.Kernel.UI.GUI.Layout;
|
||||
using RemSox.Shared.UI.GUI.Rendering;
|
||||
using RemSox.Kernel.UI.GUI.UIEelements.Controls;
|
||||
using RemSox.Kernel.UI.GUI.Windows;
|
||||
|
||||
using System.Drawing;
|
||||
|
||||
namespace RemSox.Kernel.Processes;
|
||||
|
||||
internal class DesktopProcess() : Process("Desktop Manager")
|
||||
{
|
||||
private static bool isGraphicsInitialized = false;
|
||||
|
||||
private static readonly (string Name, Func<int> Spawn)[] AvailableApps = [
|
||||
("Terminal", () => ProcessManager.SpawnProcess<TerminalProcess>()),
|
||||
("Test Process",() => ProcessManager.SpawnProcess<TestProcess>()),
|
||||
("YesNt", () => ProcessManager.SpawnProcess<YesNtInterpreterProcess>()),
|
||||
];
|
||||
|
||||
private Window taskbar = null!;
|
||||
private Window? startMenu;
|
||||
private Button startButton = null!;
|
||||
|
||||
private readonly List<(int WindowId, Button Button)> windowButtons = [];
|
||||
private int tickCount;
|
||||
|
||||
internal override void Start(string[] args)
|
||||
{
|
||||
if (!isGraphicsInitialized)
|
||||
{
|
||||
WindowManager.AddRenderSource(new CanvasRenderSource());
|
||||
isGraphicsInitialized = true;
|
||||
}
|
||||
|
||||
WindowManager.InvalidateAll();
|
||||
CreateTaskbar();
|
||||
}
|
||||
|
||||
private void CreateTaskbar()
|
||||
{
|
||||
Canvas canvas = FullScreenCanvas.GetFullScreenCanvas();
|
||||
int screenW = (int)canvas.Mode.Width;
|
||||
int screenH = (int)canvas.Mode.Height;
|
||||
const int taskbarH = 36;
|
||||
|
||||
taskbar = WindowManager.CreateWindow(this, "Taskbar",
|
||||
new Size(screenW, taskbarH),
|
||||
new Point(0, screenH - taskbarH));
|
||||
taskbar.HasChrome = false;
|
||||
taskbar.IsResizable = false;
|
||||
taskbar.IsDraggable = false;
|
||||
taskbar.ZIndex = int.MaxValue;
|
||||
taskbar.AutoFlush = true;
|
||||
|
||||
startButton = taskbar.CreateUIElement<Button>(b =>
|
||||
{
|
||||
b.Position = new Point(2, 2);
|
||||
b.Size = new Size(65, taskbarH - 4);
|
||||
b.Text = "Start";
|
||||
b.BackgroundColor = Color.FromArgb(0, 100, 180);
|
||||
b.TextColor = Color.White;
|
||||
});
|
||||
|
||||
startButton.OnClick += (_, _) => ToggleStartMenu();
|
||||
}
|
||||
|
||||
private void ToggleStartMenu()
|
||||
{
|
||||
if (startMenu is not null)
|
||||
{
|
||||
WindowManager.CloseWindow(startMenu);
|
||||
startMenu = null;
|
||||
return;
|
||||
}
|
||||
|
||||
Canvas canvas = FullScreenCanvas.GetFullScreenCanvas();
|
||||
int screenH = (int)canvas.Mode.Height;
|
||||
int menuW = 150;
|
||||
int itemH = 22;
|
||||
int menuH = (AvailableApps.Length * itemH) + 10;
|
||||
int menuX = 2;
|
||||
int menuY = screenH - 36 - menuH;
|
||||
|
||||
startMenu = WindowManager.CreateWindow(this, "Start Menu",
|
||||
new Size(menuW, menuH),
|
||||
new Point(menuX, menuY));
|
||||
startMenu.HasChrome = false;
|
||||
startMenu.IsResizable = false;
|
||||
startMenu.IsDraggable = false;
|
||||
startMenu.ZIndex = int.MaxValue - 1;
|
||||
startMenu.AutoFlush = true;
|
||||
|
||||
StackLayout stack = startMenu.CreateStackLayout(5, 5, 2);
|
||||
stack.UniformWidth = menuW - 10;
|
||||
|
||||
foreach ((string? name, Func<int>? spawn) in AvailableApps)
|
||||
{
|
||||
Button appBtn = stack.Add<Button>(b =>
|
||||
{
|
||||
b.Size = new Size(menuW - 10, itemH);
|
||||
b.Text = name;
|
||||
b.BackgroundColor = Color.FromArgb(60, 60, 65);
|
||||
b.TextColor = Color.White;
|
||||
});
|
||||
|
||||
appBtn.OnClick += (_, _) =>
|
||||
{
|
||||
_ = spawn();
|
||||
WindowManager.CloseWindow(startMenu);
|
||||
startMenu = null;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private Point lastLocalMousePos = Point.Empty;
|
||||
private bool lastLocalLeft, lastLocalRight, lastLocalMiddle;
|
||||
|
||||
internal override void Tick()
|
||||
{
|
||||
PollLocalHardware();
|
||||
|
||||
WindowManager.Update();
|
||||
|
||||
if (tickCount % 5 == 0)
|
||||
{
|
||||
UpdateWindowButtons();
|
||||
}
|
||||
|
||||
tickCount++;
|
||||
|
||||
if (!ProcessManager.IsProcessRunning<TerminalProcess>())
|
||||
{
|
||||
_ = ProcessManager.SpawnProcess<TerminalProcess>();
|
||||
}
|
||||
}
|
||||
|
||||
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<Window> allWindows = WindowManager.GetAllWindows()
|
||||
.Where(w => w != taskbar && w != startMenu && w.HasChrome)
|
||||
.ToList();
|
||||
|
||||
// Build lookup of current window IDs
|
||||
HashSet<int> currentIds = [];
|
||||
foreach (Window w in allWindows)
|
||||
{
|
||||
_ = currentIds.Add(w.Id);
|
||||
}
|
||||
|
||||
// Remove buttons for windows that no longer exist
|
||||
for (int i = windowButtons.Count - 1; i >= 0; i--)
|
||||
{
|
||||
(int winId, Button btn) = windowButtons[i];
|
||||
if (!currentIds.Contains(winId))
|
||||
{
|
||||
taskbar.RemoveUIElement(btn.Id);
|
||||
windowButtons.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Build lookup of existing tracked windows
|
||||
Dictionary<int, Button> tracked = [];
|
||||
foreach ((int winId, Button? btn) in windowButtons)
|
||||
{
|
||||
tracked[winId] = btn;
|
||||
}
|
||||
|
||||
// Add new buttons and reposition everything in one pass
|
||||
int btnX = startButton.Size.Width + 6;
|
||||
windowButtons.Clear();
|
||||
foreach (Window win in allWindows)
|
||||
{
|
||||
if (tracked.TryGetValue(win.Id, out Button? existing))
|
||||
{
|
||||
// Reposition existing button
|
||||
existing.Position = new Point(btnX, 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create new button
|
||||
existing = taskbar.CreateUIElement<Button>(b =>
|
||||
{
|
||||
b.Position = new Point(btnX, 2);
|
||||
b.Size = new Size(100, 32);
|
||||
b.Text = TruncateTitle(win.Title, 12);
|
||||
b.BackgroundColor = Color.FromArgb(55, 55, 60);
|
||||
b.TextColor = Color.White;
|
||||
});
|
||||
|
||||
int capturedId = win.Id;
|
||||
existing.OnClick += (_, _) =>
|
||||
{
|
||||
Window? target = WindowManager.GetAllWindows().FirstOrDefault(w => w.Id == capturedId);
|
||||
if (target is not null)
|
||||
{
|
||||
WindowManager.FocusWindow(target);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Update highlight for focused window
|
||||
existing.BackgroundColor = win.IsFocused
|
||||
? Color.FromArgb(0, 90, 160)
|
||||
: Color.FromArgb(55, 55, 60);
|
||||
|
||||
windowButtons.Add((win.Id, existing));
|
||||
btnX += 104;
|
||||
}
|
||||
}
|
||||
|
||||
private static string TruncateTitle(string title, int maxLen)
|
||||
{
|
||||
return title.Length <= maxLen ? title : title[..(maxLen - 1)] + "\u2026";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
using Cosmos.Kernel.System.Graphics;
|
||||
using Cosmos.Kernel.System.Keyboard;
|
||||
|
||||
using RemSox.Shared.Networking;
|
||||
using RemSox.Kernel.Processing;
|
||||
using RemSox.Shared.UI;
|
||||
using RemSox.Shared.UI.GUI.Rendering;
|
||||
using RemSox.Kernel.UI.GUI.Rendering;
|
||||
using RemSox.Kernel.UI.GUI.Windows;
|
||||
|
||||
using System.Text;
|
||||
|
||||
namespace RemSox.Kernel.Processes;
|
||||
|
||||
internal sealed class RemoteDesktopProcess() : Process("Remote Desktop Server")
|
||||
{
|
||||
private TcpRpcServer? server;
|
||||
private NetworkRenderSource? networkSource;
|
||||
private CancellationTokenSource? cts;
|
||||
|
||||
public int Port { get; private set; }
|
||||
|
||||
internal override void Start(string[] args)
|
||||
{
|
||||
if (args.Length == 0 || !int.TryParse(args[0], out int port) || port <= 0 || port > 65535)
|
||||
{
|
||||
Logger.Log("Invalid port. Usage: rdp-start <port> (1-65535)", Logging.LogSeverity.Error);
|
||||
RequestStop();
|
||||
return;
|
||||
}
|
||||
|
||||
Port = port;
|
||||
cts = new CancellationTokenSource();
|
||||
server = new TcpRpcServer();
|
||||
networkSource = new NetworkRenderSource(server);
|
||||
|
||||
server.ListenTo("SyncRequest", async _ =>
|
||||
{
|
||||
Canvas canvas = FullScreenCanvas.GetFullScreenCanvas();
|
||||
RenderCommand screenInfo = new()
|
||||
{
|
||||
Type = RenderCommandType.ScreenInfo,
|
||||
WindowId = 0,
|
||||
ElementId = 0,
|
||||
Properties = new()
|
||||
{
|
||||
["Width"] = (int)canvas.Mode.Width,
|
||||
["Height"] = (int)canvas.Mode.Height,
|
||||
},
|
||||
};
|
||||
networkSource?.Render([screenInfo]);
|
||||
WindowManager.InvalidateAll();
|
||||
});
|
||||
|
||||
RegisterInputHandlers();
|
||||
|
||||
_ = Task.Run(() => server.StartAsync(port, cts.Token));
|
||||
|
||||
WindowManager.AddRenderSource(networkSource);
|
||||
|
||||
Logger.Log($"Remote desktop server started on port {port}.", Logging.LogSeverity.Info);
|
||||
}
|
||||
|
||||
internal override void Tick()
|
||||
{
|
||||
}
|
||||
|
||||
internal override void Stop()
|
||||
{
|
||||
cts?.Cancel();
|
||||
server?.Stop();
|
||||
|
||||
if (networkSource is not null)
|
||||
{
|
||||
WindowManager.RemoveRenderSource(networkSource);
|
||||
}
|
||||
|
||||
Logger.Log("Remote desktop server stopped.", Logging.LogSeverity.Info);
|
||||
}
|
||||
|
||||
private void RegisterInputHandlers()
|
||||
{
|
||||
if (server is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
server.ListenTo("MouseMove", async (payload) =>
|
||||
{
|
||||
(int X, int Y) = DeserializeMouseMove(payload);
|
||||
WindowManager.EnqueueMouseEvent(MouseEvent.Move(X, Y));
|
||||
});
|
||||
|
||||
server.ListenTo("MouseDown", async (payload) =>
|
||||
{
|
||||
(int X, int Y, string Button) = DeserializeMouseButton(payload);
|
||||
WindowManager.EnqueueMouseEvent(MouseEvent.ButtonDown(X, Y, ParseButton(Button)));
|
||||
});
|
||||
|
||||
server.ListenTo("MouseUp", async (payload) =>
|
||||
{
|
||||
(int X, int Y, string Button) = DeserializeMouseButton(payload);
|
||||
WindowManager.EnqueueMouseEvent(MouseEvent.ButtonUp(X, Y, ParseButton(Button)));
|
||||
});
|
||||
|
||||
server.ListenTo("MouseWheel", async (payload) =>
|
||||
{
|
||||
(int X, int Y, int Delta) = DeserializeMouseWheel(payload);
|
||||
WindowManager.EnqueueMouseEvent(MouseEvent.Wheel(X, Y, Delta));
|
||||
});
|
||||
|
||||
server.ListenTo("KeyEvent", async (payload) =>
|
||||
{
|
||||
(int Key, string KeyChar, bool Shift, bool Alt, bool Control, bool Pressed) = DeserializeKeyEvent(payload);
|
||||
ConsoleKeyEx key = (ConsoleKeyEx)Key;
|
||||
char keyChar = KeyChar.Length > 0 ? KeyChar[0] : '\0';
|
||||
|
||||
KeyEvent keyEvent = new(keyChar, key, Shift, Alt, Control, Pressed ? 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
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
using Cosmos.Kernel.System.Keyboard;
|
||||
|
||||
using RemSox.Kernel.Processing;
|
||||
using RemSox.Kernel.UI.CLI;
|
||||
using RemSox.Kernel.UI.GUI.UIEelements.Shapes;
|
||||
using RemSox.Kernel.UI.GUI.Windows;
|
||||
|
||||
using System.Drawing;
|
||||
|
||||
namespace RemSox.Kernel.Processes;
|
||||
|
||||
public class TerminalProcess() : Process("Terminal")
|
||||
{
|
||||
private Window window = null!;
|
||||
private readonly List<string> history = [];
|
||||
private int historyIndex = -1;
|
||||
private bool commandRunning = false;
|
||||
private CancellationTokenSource? commandCancellationTokenSource = null;
|
||||
private string currentInput = "";
|
||||
private readonly List<Text> textLines = [];
|
||||
private readonly Lock textLinesLock = new();
|
||||
private const int LineHeight = 30;
|
||||
private Size lastSize = new(-1, -1);
|
||||
|
||||
internal override void Start(string[] args)
|
||||
{
|
||||
window = CreateWindow("Terminal", new Size(400, 300));
|
||||
|
||||
PrintLine("RemSox GUI Terminal v1.0");
|
||||
PrintLine("Type 'help' for commands.");
|
||||
|
||||
historyIndex = CommandManager.GetCommandHistory().Count;
|
||||
|
||||
window.Flush();
|
||||
window.OnKeyEvent += HandleKey;
|
||||
}
|
||||
|
||||
internal override void Tick()
|
||||
{
|
||||
if (window.Size != lastSize && !window.IsResizing)
|
||||
{
|
||||
lastSize = window.Size;
|
||||
UpdateDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
internal override void Stop()
|
||||
{
|
||||
window.OnKeyEvent -= HandleKey;
|
||||
}
|
||||
|
||||
private async void HandleKey(KeyEvent keyEvent)
|
||||
{
|
||||
if (commandRunning)
|
||||
{
|
||||
if (keyEvent.Key == ConsoleKeyEx.C && keyEvent.Modifiers.HasFlag(ConsoleModifiers.Control) && commandCancellationTokenSource is not null)
|
||||
{
|
||||
await commandCancellationTokenSource.CancelAsync();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (keyEvent.Key == ConsoleKeyEx.Enter)
|
||||
{
|
||||
string cmd = currentInput;
|
||||
|
||||
PrintLine("> " + cmd);
|
||||
currentInput = "";
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(cmd))
|
||||
{
|
||||
if (cmd.Trim() == "exit")
|
||||
{
|
||||
RequestStop();
|
||||
}
|
||||
else if (cmd.Trim() == "clear")
|
||||
{
|
||||
lock (textLinesLock)
|
||||
{
|
||||
history.Clear();
|
||||
UpdateDisplay();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
commandCancellationTokenSource = new CancellationTokenSource();
|
||||
commandRunning = true;
|
||||
|
||||
bool handled = await CommandManager.TryExecuteAsync(cmd, PrintLine, commandCancellationTokenSource.Token);
|
||||
|
||||
if (!handled)
|
||||
{
|
||||
PrintLine($"\"{cmd}\" is not a command");
|
||||
}
|
||||
|
||||
commandRunning = false;
|
||||
commandCancellationTokenSource.Dispose();
|
||||
commandCancellationTokenSource = null;
|
||||
}
|
||||
|
||||
historyIndex = CommandManager.GetCommandHistory().Count;
|
||||
}
|
||||
UpdateDisplay();
|
||||
}
|
||||
else if (keyEvent.Key == ConsoleKeyEx.Backspace)
|
||||
{
|
||||
if (currentInput.Length > 0)
|
||||
{
|
||||
currentInput = currentInput[..^1];
|
||||
UpdateDisplay();
|
||||
}
|
||||
}
|
||||
else if (keyEvent.Key == ConsoleKeyEx.UpArrow)
|
||||
{
|
||||
if (CommandManager.GetCommandHistory().Count > 0)
|
||||
{
|
||||
historyIndex = Math.Max(historyIndex - 1, 0);
|
||||
currentInput = CommandManager.GetCommandHistory()[historyIndex];
|
||||
UpdateDisplay();
|
||||
}
|
||||
}
|
||||
else if (keyEvent.Key == ConsoleKeyEx.DownArrow)
|
||||
{
|
||||
if (CommandManager.GetCommandHistory().Count > 0)
|
||||
{
|
||||
historyIndex = Math.Min(historyIndex + 1, CommandManager.GetCommandHistory().Count - 1);
|
||||
currentInput = CommandManager.GetCommandHistory()[historyIndex];
|
||||
UpdateDisplay();
|
||||
}
|
||||
}
|
||||
else if (!char.IsControl(keyEvent.KeyChar))
|
||||
{
|
||||
currentInput += keyEvent.KeyChar;
|
||||
UpdateDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
private void PrintLine(string text)
|
||||
{
|
||||
history.Add(text);
|
||||
if (history.Count > 1000)
|
||||
{
|
||||
history.RemoveAt(0);
|
||||
}
|
||||
UpdateDisplay();
|
||||
}
|
||||
|
||||
private void UpdateDisplay()
|
||||
{
|
||||
if (window is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int availableHeight = window.Size.Height - 24; // 18 for title + 6 margin
|
||||
int maxLines = (availableHeight / LineHeight) - 1; // -1 for input line
|
||||
|
||||
if (maxLines < 1)
|
||||
{
|
||||
maxLines = 1;
|
||||
}
|
||||
|
||||
lock (textLinesLock)
|
||||
{
|
||||
// Ensure we have enough Text elements for maxLines + 1 (input line)
|
||||
while (textLines.Count <= maxLines)
|
||||
{
|
||||
Text textElement = window.CreateUIElement<Text>(t =>
|
||||
{
|
||||
t.Color = Color.LightGreen;
|
||||
t.Content = "";
|
||||
t.FontSize = LineHeight;
|
||||
});
|
||||
textLines.Add(textElement);
|
||||
}
|
||||
|
||||
// Calculate starting Y to align everything flush to the bottom margin
|
||||
int startY = window.Size.Height - ((maxLines + 1) * LineHeight) - 5;
|
||||
if (startY < 20)
|
||||
{
|
||||
startY = 20;
|
||||
}
|
||||
|
||||
for (int i = 0; i < maxLines; i++)
|
||||
{
|
||||
int historyIndex = history.Count - maxLines + i;
|
||||
string content = "";
|
||||
if (historyIndex >= 0 && historyIndex < history.Count)
|
||||
{
|
||||
content = history[historyIndex];
|
||||
}
|
||||
|
||||
textLines[i].Content = content;
|
||||
textLines[i].Position = new Point(5, startY + (i * LineHeight));
|
||||
}
|
||||
|
||||
// The last line is the input line
|
||||
textLines[maxLines].Content = "> " + currentInput + "_";
|
||||
textLines[maxLines].Position = new Point(5, startY + (maxLines * LineHeight));
|
||||
// Hide any extra text lines we don't need
|
||||
for (int i = maxLines + 1; i < textLines.Count; i++)
|
||||
{
|
||||
textLines[i].Content = "";
|
||||
}
|
||||
}
|
||||
|
||||
window.Flush();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using RemSox.Kernel.Processing;
|
||||
using RemSox.Kernel.Processing.IPC;
|
||||
using RemSox.Kernel.UI.GUI.UIEelements.Shapes;
|
||||
using RemSox.Kernel.UI.GUI.Windows;
|
||||
|
||||
using System.Drawing;
|
||||
|
||||
namespace RemSox.Kernel.Processes;
|
||||
|
||||
public class TestProcess() : Process("Test Process")
|
||||
{
|
||||
internal override void Start(string[] args)
|
||||
{
|
||||
Window window = WindowManager.CreateWindow(this, "Test Window", new Size(200, 150));
|
||||
window.AutoFlush = true;
|
||||
|
||||
Circle circle = window.CreateUIElement<Circle>(rect =>
|
||||
{
|
||||
rect.Position = new Point(10, 10);
|
||||
rect.Radius = 50;
|
||||
rect.Color = Color.Red;
|
||||
});
|
||||
|
||||
circle.Radius = 40;
|
||||
|
||||
SendMessageToAllProcesses(new TestMessage { SenderProcessId = Id });
|
||||
}
|
||||
|
||||
internal override void Tick()
|
||||
{
|
||||
// Example tick logic - could be used for animations, timed events, etc.
|
||||
}
|
||||
|
||||
internal override void HandleInterProcessMessage(Message message)
|
||||
{
|
||||
if (message is TestMessage testMessage && message.SenderProcessId != Id)
|
||||
{
|
||||
Console.WriteLine($"Received message in {Name} (ID: {Id}): {testMessage.MessageText}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class TestMessage : Message
|
||||
{
|
||||
public string MessageText { get; set; } = "Hello from TestMessage!";
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using RemSox.Kernel.Processing;
|
||||
using RemSox.Kernel.Utils;
|
||||
|
||||
using YesNt.Interpreter.Runtime;
|
||||
|
||||
namespace RemSox.Kernel.Processes;
|
||||
|
||||
public class YesNtInterpreterProcess() : Process("YesNtInterpreter")
|
||||
{
|
||||
private YesNtInterpreter interpreter = null!;
|
||||
|
||||
internal override void Start(string[] args)
|
||||
{
|
||||
// TODO: eventually we want to load these from a file instead of hardcoding them here
|
||||
args = [
|
||||
"win_create \"Test\" 320 240",
|
||||
"global winId = %win_last_id",
|
||||
"win_flush ${winId}",
|
||||
"print ${winId}",
|
||||
"label test:",
|
||||
"goto test"
|
||||
];
|
||||
interpreter = new();
|
||||
|
||||
new YesNtWindowStatements(this).Register(interpreter);
|
||||
|
||||
interpreter.Prepare([.. args]);
|
||||
}
|
||||
|
||||
internal override void Tick()
|
||||
{
|
||||
if (interpreter.IsRunning)
|
||||
{
|
||||
_ = interpreter.Step();
|
||||
}
|
||||
else
|
||||
{
|
||||
RequestStop();
|
||||
}
|
||||
}
|
||||
|
||||
internal override void Stop()
|
||||
{
|
||||
interpreter.Stop();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user