freeso/TSOClient/FSO.Unix/Program.cs
SegerEnd 35d0dd89a7
Add server connection, Archive maker and Keyboard navigation (#292)
* Server connection back

Get the server connection working again that was lost by the Archive
mode changes.

Key changes:
- Fix server connection bugs caused by the new Archive mode
- Update Microsoft.Data.Sqlite.Core to 10.0.2
- Add allOpenable config flag for free roam lot transitions for normal
servers.
- Add city and lot server configuration options
- Make UCP.InitArchive public for CoreGameScreenController
- Archive UI improvements (join dialog with direct server IP join)

* Keyboard navigation

Add full keyboard and Tab navigation support to the UI framework.

Key changes:
- Tab/Shift+Tab navigation between focusable UI elements
- Keyboard support for UIButton, UISlider, UIListBox, UICombobox
- UIRadioButton arrow key navigation within groups
- Mouse wheel scrolling for UIListBox
- UIContextMenu keyboard navigation (arrow keys, Enter, Escape)
- UIGridViewer keyboard support
- Focus management via InputManager with IFocusableUI interface

* FSO.Unix cross-platform support

Add FSO.Unix project for native Linux and macOS support.

Key changes:
- New FSO.Unix project with cross-platform entry point
- macOS .app bundle creation and deploy script for macOS/Linux
- Native dialogs via osascript (macOS) and zenity (Linux)
- ImageSharp-based bitmap/PNG handling instead of Windows drawing library
for non-Windows platforms
- GameLocator path fixes for TSO on macOS and Linux

* Docker containers

Add Docker configuration files for running a FreeSO server.

Key changes:
- Dockerfile and docker-compose.yml for server deployment
- Entrypoint script with secret generation
- OCI image labels for container metadata
- Default server configuration template (config.json)

* Spectator mode

Allow non-roommate visitors to open and explore lots in a read-only
spectator mode. Spectators can walk around and observe but cannot
modify the lot (build, buy, delete objects, or use most interactions).

Key features:
- Basic spectator mode for non-roommate lot opening
- Block VM commands, cross-room routing, and portal interactions for spectators
- Block pie menu on lot objects with error feedback
- Spectator-to-writable transition when a roommate/owner joins
- Reload lot save and transition back when lot owner joins
- Walls up with roof on spectator lot entry
- Spectator label shown in UI
- Admins excluded from spectator mode restrictions
- Chat event when lot transitions from spectator mode
- Allow spectators to use direct control
- No spectator lots in Archive mode

Also includes: cursor fixes for macOS/Linux, PriorityQueue pathfinder
optimization, UIListBox focus fix, CurLoader BMP header fix, API
config cleanup, and various other minor fixes.

* Spector mode improvements
2026-05-04 22:15:57 +01:00

137 lines
4.5 KiB
C#

using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using FSO.Client;
using FSO.Client.UI.Panels;
using FSO.Common;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Formats.Png;
namespace FSO.Unix
{
public static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
public static void Main(string[] args)
{
InitUnix();
var mgAssembly = typeof(Microsoft.Xna.Framework.Game).Assembly;
var platform = mgAssembly.GetType("MonoGame.Framework.Utilities.PlatformInfo");
var backend = platform?.GetProperty("GraphicsBackend")?.GetValue(null);
Console.WriteLine($"[FreeSO] MonoGame: {mgAssembly.GetName().Version} | Backend: {backend ?? "Unknown"}");
FSOEnvironment.Enable3D = true;
if ((new FSOProgram()).InitWithArguments(args))
{
var startProxy = new GameStartProxy();
startProxy.Start(false);
}
Environment.Exit(0);
}
public static void InitUnix()
{
FSO.Files.ImageLoaderHelpers.BitmapFunction = BitmapReader;
FSO.Files.ImageLoaderHelpers.SavePNGFunc = SavePNG;
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
FSOProgram.ShowDialog = ShowDialog;
}
public static void ShowDialog(string text)
{
ShowDialog(text, "FreeSO Message");
}
private static string Escape(string s) => s.Replace("\"", "\\\"");
private static void ShowDialog(string text, string title)
{
if (text.Length > 1500) text = text.Substring(0, 1500) + "...";
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
var psi = new ProcessStartInfo
{
FileName = "osascript",
Arguments = $"-e \"display alert \\\"{Escape(title)}\\\" message \\\"{Escape(text)}\\\" giving up after 15\"",
UseShellExecute = true
};
Process.Start(psi)?.WaitForExit();
}
else
{
try
{
var psi = new ProcessStartInfo
{
FileName = "zenity",
Arguments = $"--error --title=\"{Escape(title)}\" --text=\"{Escape(text)}\"",
UseShellExecute = false
};
Process.Start(psi)?.WaitForExit();
}
catch
{
Console.Error.WriteLine($"[{title}] {text}");
}
}
}
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
string title = e.ExceptionObject is OutOfMemoryException
? "Out of Memory! FreeSO needs to close."
: "A fatal error occured! Screenshot this dialog and post it on Discord.";
ShowDialog(e.ExceptionObject.ToString(), title);
Environment.Exit(1);
}
public static void SavePNG(byte[] data, int width, int height, Stream str)
{
using var image = new Image<Rgba32>(width, height);
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
int i = (y * width + x) * 4;
image[x, y] = new Rgba32(data[i], data[i + 1], data[i + 2], data[i + 3]);
}
}
image.Save(str, new PngEncoder());
}
public static Tuple<byte[], int, int> BitmapReader(Stream str)
{
using var image = Image.Load<Rgba32>(str);
int width = image.Width;
int height = image.Height;
var data = new byte[width * height * 4];
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
int i = (y * width + x) * 4;
Rgba32 px = image[x, y];
data[i] = px.R;
data[i + 1] = px.G;
data[i + 2] = px.B;
data[i + 3] = px.A;
}
}
return new Tuple<byte[], int, int>(data, width, height);
}
}
}