mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-14 19:29:36 +01:00
Initial macOS WebView support
This is a gigantic kerfuffle because Chromium expects a very specific directory & app bundle layout. Have to change a bunch of resource loading code to account for content development being launched from an app bundle, and also had to make automatic MSBuild tooling & a python script to generate such an app bundle
This commit is contained in:
24
MSBuild/MacOSAppBundle.targets
Normal file
24
MSBuild/MacOSAppBundle.targets
Normal file
@@ -0,0 +1,24 @@
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<!--
|
||||
Depend on this in your client project (e.g. Content.Client) to generate a development app bundle for macOS.
|
||||
This is required for WebView.
|
||||
-->
|
||||
|
||||
<PropertyGroup>
|
||||
<RTMakeAppBundle Condition="'$(TargetOS)' == 'MacOS' And '$(RTMakeAppBundle)' == '' And '$(FullRelease)' != 'True'">True</RTMakeAppBundle>
|
||||
<RTAppBundleName Condition="'$(RTAppBundleName)' == ''">RobustToolbox Project</RTAppBundleName>
|
||||
<RTAppBundleIdentifier Condition="'$(RTAppBundleIdentifier)' == ''">org.robusttoolbox.project</RTAppBundleIdentifier>
|
||||
<!-- RTAppBundleIcon controls icon -->
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<_RTMacOSAppBundle_targets_imported>True</_RTMacOSAppBundle_targets_imported>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="RTMakeAppBundleAfterBuild" Condition="'$(RTMakeAppBundle)' == 'True'" AfterTargets="AfterBuild">
|
||||
<PropertyGroup>
|
||||
<_RTMacOSAppBundle_icon Condition="'$(RTAppBundleIcon)' != ''">--icon "$(RTAppBundleIcon)"</_RTMacOSAppBundle_icon>
|
||||
</PropertyGroup>
|
||||
<Exec Command="$(MSBuildThisFileDirectory)/../Tools/macos_make_appbundle.py $(_RTMacOSAppBundle_for_webview) --name "$(RTAppBundleName)" --directory "$(OutputPath)" --apphost "$(AssemblyName)" --identifier "$(RTAppBundleIdentifier)" $(_RTMacOSAppBundle_icon)" />
|
||||
</Target>
|
||||
</Project>
|
||||
14
MSBuild/WebView.targets
Normal file
14
MSBuild/WebView.targets
Normal file
@@ -0,0 +1,14 @@
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<!-- If you are using Robust.Client.WebView, import this to depend on it. -->
|
||||
|
||||
<Import Condition="'$(_RTMacOSAppBundle_targets_imported)' != 'True'"
|
||||
Project="$(MSBuildThisFileDirectory)\MacOSAppBundle.targets" />
|
||||
|
||||
<PropertyGroup>
|
||||
<_RTMacOSAppBundle_for_webview>--webview</_RTMacOSAppBundle_for_webview>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\Robust.Client.WebView\Robust.Client.WebView.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.WebView.Cef
|
||||
@@ -20,6 +22,20 @@ namespace Robust.Client.WebView.Cef
|
||||
argv[0] = "-";
|
||||
}
|
||||
|
||||
#if MACOS
|
||||
NativeLibrary.SetDllImportResolver(typeof(CefSettings).Assembly,
|
||||
(name, assembly, path) =>
|
||||
{
|
||||
if (name == "libcef")
|
||||
{
|
||||
var libPath = PathHelpers.ExecutableRelativeFile("../../../../Frameworks/Chromium Embedded Framework.framework/Chromium Embedded Framework");
|
||||
return NativeLibrary.Load(libPath, assembly, path);
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
#endif
|
||||
|
||||
var mainArgs = new CefMainArgs(argv);
|
||||
|
||||
StartWatchThread();
|
||||
|
||||
@@ -17,11 +17,13 @@ namespace Robust.Client.WebView.Cef
|
||||
{
|
||||
internal partial class WebViewManagerCef
|
||||
{
|
||||
private readonly List<ControlImpl> _activeControls = new();
|
||||
|
||||
public IWebViewControlImpl MakeControlImpl(WebViewControl owner)
|
||||
{
|
||||
var shader = _prototypeManager.Index<ShaderPrototype>("bgra");
|
||||
var shaderInstance = shader.Instance();
|
||||
var impl = new ControlImpl(owner, shaderInstance);
|
||||
var impl = new ControlImpl(this, owner, shaderInstance);
|
||||
_dependencyCollection.InjectDependencies(impl);
|
||||
return impl;
|
||||
}
|
||||
@@ -133,11 +135,13 @@ namespace Robust.Client.WebView.Cef
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
[Dependency] private readonly IInputManager _inputMgr = default!;
|
||||
|
||||
private readonly WebViewManagerCef _manager;
|
||||
public readonly WebViewControl Owner;
|
||||
private readonly ShaderInstance _shaderInstance;
|
||||
|
||||
public ControlImpl(WebViewControl owner, ShaderInstance shaderInstance)
|
||||
public ControlImpl(WebViewManagerCef manager, WebViewControl owner, ShaderInstance shaderInstance)
|
||||
{
|
||||
_manager = manager;
|
||||
Owner = owner;
|
||||
_shaderInstance = shaderInstance;
|
||||
}
|
||||
@@ -194,6 +198,7 @@ namespace Robust.Client.WebView.Cef
|
||||
var texture = _clyde.CreateBlankTexture<Rgba32>(Vector2i.One);
|
||||
|
||||
_data = new LiveData(texture, client, browser, renderer);
|
||||
_manager._activeControls.Add(this);
|
||||
}
|
||||
|
||||
public void CloseBrowser()
|
||||
@@ -203,6 +208,8 @@ namespace Robust.Client.WebView.Cef
|
||||
_data!.Texture.Dispose();
|
||||
_data.Browser.GetHost().CloseBrowser(true);
|
||||
_data = null;
|
||||
|
||||
_manager._activeControls.Remove(this);
|
||||
}
|
||||
|
||||
public void MouseMove(GUIMouseMoveEventArgs args)
|
||||
@@ -279,6 +286,7 @@ namespace Robust.Client.WebView.Cef
|
||||
|
||||
// Logger.Debug($"{guiRawEvent.Action} {guiRawEvent.Key} {guiRawEvent.ScanCode} {vkKey}");
|
||||
|
||||
#if !MACOS
|
||||
var lParam = 0;
|
||||
lParam |= (guiRawEvent.ScanCode & 0xFF) << 16;
|
||||
if (guiRawEvent.Action != RawKeyAction.Down)
|
||||
@@ -286,7 +294,9 @@ namespace Robust.Client.WebView.Cef
|
||||
|
||||
if (guiRawEvent.Action == RawKeyAction.Up)
|
||||
lParam |= 1 << 31;
|
||||
|
||||
#else
|
||||
var lParam = guiRawEvent.RawCode;
|
||||
#endif
|
||||
var modifiers = CalcModifiers(guiRawEvent.Key);
|
||||
|
||||
host.SendKeyEvent(new CefKeyEvent
|
||||
@@ -307,7 +317,7 @@ namespace Robust.Client.WebView.Cef
|
||||
host.SendKeyEvent(new CefKeyEvent
|
||||
{
|
||||
EventType = CefKeyEventType.Char,
|
||||
WindowsKeyCode = '\r',
|
||||
WindowsKeyCode = '\b',
|
||||
NativeKeyCode = lParam,
|
||||
Modifiers = modifiers
|
||||
});
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -47,11 +48,12 @@ namespace Robust.Client.WebView.Cef
|
||||
string subProcessName;
|
||||
if (OperatingSystem.IsWindows())
|
||||
subProcessName = "Robust.Client.WebView.exe";
|
||||
else if (OperatingSystem.IsLinux())
|
||||
else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
subProcessName = "Robust.Client.WebView";
|
||||
else
|
||||
throw new NotSupportedException("Unsupported platform for CEF!");
|
||||
|
||||
#if !MACOS
|
||||
var subProcessPath = Path.Combine(BasePath, subProcessName);
|
||||
var cefResourcesPath = LocateCefResources();
|
||||
_sawmill.Debug($"Subprocess path: {subProcessPath}, resources: {cefResourcesPath}");
|
||||
@@ -60,19 +62,36 @@ namespace Robust.Client.WebView.Cef
|
||||
|
||||
if (cefResourcesPath == null)
|
||||
throw new InvalidOperationException("Unable to locate cef_resources directory!");
|
||||
#endif
|
||||
|
||||
var remoteDebugPort = _cfg.GetCVar(WCVars.WebRemoteDebugPort);
|
||||
|
||||
var cachePath = FindAndLockCacheDirectory();
|
||||
|
||||
#if MACOS
|
||||
NativeLibrary.SetDllImportResolver(typeof(CefSettings).Assembly,
|
||||
(name, assembly, path) =>
|
||||
{
|
||||
if (name == "libcef")
|
||||
{
|
||||
var libPath = PathHelpers.ExecutableRelativeFile("../Frameworks/Chromium Embedded Framework.framework/Chromium Embedded Framework");
|
||||
return NativeLibrary.Load(libPath, assembly, path);
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
#endif
|
||||
|
||||
var settings = new CefSettings()
|
||||
{
|
||||
WindowlessRenderingEnabled = true, // So we can render to our UI controls.
|
||||
ExternalMessagePump = false, // Unsure, honestly. TODO CEF: Research this?
|
||||
NoSandbox = true, // Not disabling the sandbox crashes CEF.
|
||||
#if !MACOS
|
||||
BrowserSubprocessPath = subProcessPath,
|
||||
LocalesDirPath = Path.Combine(cefResourcesPath, "locales"),
|
||||
ResourcesDirPath = cefResourcesPath,
|
||||
#endif
|
||||
RemoteDebuggingPort = remoteDebugPort,
|
||||
CookieableSchemesList = "usr,res",
|
||||
CachePath = cachePath,
|
||||
@@ -113,7 +132,6 @@ namespace Robust.Client.WebView.Cef
|
||||
if (ProbeDir(BasePath, out var path))
|
||||
return path;
|
||||
|
||||
|
||||
foreach (var searchDir in NativeDllSearchDirectories())
|
||||
{
|
||||
if (ProbeDir(searchDir, out path))
|
||||
@@ -147,6 +165,16 @@ namespace Robust.Client.WebView.Cef
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
foreach (var control in _activeControls.ToArray())
|
||||
{
|
||||
control.CloseBrowser();
|
||||
}
|
||||
|
||||
foreach (var window in _browserWindows.ToArray())
|
||||
{
|
||||
window.Dispose();
|
||||
}
|
||||
|
||||
CefRuntime.Shutdown();
|
||||
}
|
||||
|
||||
|
||||
@@ -8,10 +8,10 @@ internal sealed class TestBrowseWindow : DefaultWindow
|
||||
{
|
||||
protected override Vector2 ContentsMinimumSize => new Vector2(640, 480);
|
||||
|
||||
public TestBrowseWindow()
|
||||
public TestBrowseWindow(string url)
|
||||
{
|
||||
var wv = new WebViewControl();
|
||||
wv.Url = "https://spacestation14.io";
|
||||
wv.Url = url;
|
||||
|
||||
Contents.AddChild(wv);
|
||||
}
|
||||
@@ -23,6 +23,15 @@ internal sealed class TestBrowseWindowCommand : LocalizedCommands
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
new TestBrowseWindow().Open();
|
||||
var url = args.Length > 0 ? args[0] : "https://spacestation14.com";
|
||||
new TestBrowseWindow(url).Open();
|
||||
}
|
||||
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length == 1)
|
||||
return CompletionResult.FromHint("<url>");
|
||||
|
||||
return CompletionResult.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +105,7 @@ namespace Robust.Client
|
||||
private IMainArgs? _loaderArgs;
|
||||
|
||||
public bool ContentStart { get; set; } = false;
|
||||
public StartType StartTypeValue { get; private set; }
|
||||
public GameControllerOptions Options { get; private set; } = new();
|
||||
public InitialLaunchState LaunchState { get; private set; } = default!;
|
||||
|
||||
@@ -398,9 +399,18 @@ namespace Robust.Client
|
||||
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions)
|
||||
: Options.MountOptions;
|
||||
|
||||
StartTypeValue = ContentStart ? StartType.Content : StartType.Engine;
|
||||
#if FULL_RELEASE
|
||||
if (_loaderArgs != null || Options.ResourceMountDisabled)
|
||||
StartTypeValue = StartType.Loader;
|
||||
#else
|
||||
if (StartTypeValue == StartType.Content && Path.GetFileName(PathHelpers.GetExecutableDirectory()) == "MacOS")
|
||||
StartTypeValue = StartType.ContentAppBundle;
|
||||
#endif
|
||||
|
||||
ProgramShared.DoMounts(_resManager, mountOptions, Options.ContentBuildDirectory,
|
||||
Options.AssemblyDirectory,
|
||||
Options.LoadContentResources, _loaderArgs != null && !Options.ResourceMountDisabled, ContentStart);
|
||||
Options.LoadContentResources, StartTypeValue);
|
||||
|
||||
if (_loaderArgs != null)
|
||||
{
|
||||
|
||||
@@ -163,7 +163,7 @@ internal partial class Clyde
|
||||
|
||||
var button = ConvertSdl3Button(ev.Button);
|
||||
var key = Mouse.MouseButtonToKey(button);
|
||||
EmitKeyEvent(key, ev.Type, false, ev.Mods, 0);
|
||||
EmitKeyEvent(key, ev.Type, false, ev.Mods, 0, 0);
|
||||
}
|
||||
|
||||
private void ProcessEventMouseMotion(EventMouseMotion ev)
|
||||
@@ -227,10 +227,10 @@ internal partial class Clyde
|
||||
|
||||
private void ProcessEventKey(EventKey ev)
|
||||
{
|
||||
EmitKeyEvent(ConvertSdl3Scancode(ev.Scancode), ev.Type, ev.Repeat, ev.Mods, ev.Scancode);
|
||||
EmitKeyEvent(ConvertSdl3Scancode(ev.Scancode), ev.Type, ev.Repeat, ev.Mods, ev.Scancode, ev.Raw);
|
||||
}
|
||||
|
||||
private void EmitKeyEvent(Key key, ET type, bool repeat, SDL.SDL_Keymod mods, SDL.SDL_Scancode scancode)
|
||||
private void EmitKeyEvent(Key key, ET type, bool repeat, SDL.SDL_Keymod mods, SDL.SDL_Scancode scancode, ushort rawCode)
|
||||
{
|
||||
var shift = (mods & SDL_Keymod.SDL_KMOD_SHIFT) != 0;
|
||||
var alt = (mods & SDL_Keymod.SDL_KMOD_ALT) != 0;
|
||||
@@ -241,7 +241,8 @@ internal partial class Clyde
|
||||
key,
|
||||
repeat,
|
||||
alt, control, shift, system,
|
||||
(int)scancode);
|
||||
(int)scancode,
|
||||
rawCode);
|
||||
|
||||
switch (type)
|
||||
{
|
||||
|
||||
@@ -140,6 +140,7 @@ internal partial class Clyde
|
||||
{
|
||||
WindowId = ev.windowID,
|
||||
Scancode = ev.scancode,
|
||||
Raw = ev.raw,
|
||||
Type = ev.type,
|
||||
Repeat = ev.repeat,
|
||||
Mods = ev.mod,
|
||||
@@ -194,6 +195,7 @@ internal partial class Clyde
|
||||
{
|
||||
public uint WindowId;
|
||||
public SDL.SDL_Scancode Scancode;
|
||||
public ushort Raw;
|
||||
public ET Type;
|
||||
public bool Repeat;
|
||||
public SDL.SDL_Keymod Mods;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
@@ -9,6 +10,7 @@ namespace Robust.Client
|
||||
{
|
||||
GameControllerOptions Options { get; }
|
||||
bool ContentStart { get; set; }
|
||||
StartType StartTypeValue { get; }
|
||||
void SetCommandLineArgs(CommandLineArgs args);
|
||||
void Run(GameController.DisplayMode mode, GameControllerOptions options, Func<ILogHandler>? logHandlerFactory = null);
|
||||
void KeyDown(KeyEventArgs keyEvent);
|
||||
|
||||
@@ -106,17 +106,20 @@ namespace Robust.Client.Input
|
||||
public bool IsRepeat { get; }
|
||||
|
||||
public int ScanCode { get; }
|
||||
internal ushort RawCode { get; }
|
||||
|
||||
public KeyEventArgs(
|
||||
Keyboard.Key key,
|
||||
bool repeat,
|
||||
bool alt, bool control, bool shift, bool system,
|
||||
int scanCode)
|
||||
int scanCode,
|
||||
ushort rawCode=0)
|
||||
: base(alt, control, shift, system)
|
||||
{
|
||||
Key = key;
|
||||
IsRepeat = repeat;
|
||||
ScanCode = scanCode;
|
||||
RawCode = rawCode;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -299,7 +299,8 @@ namespace Robust.Client.Input
|
||||
args.Key,
|
||||
args.ScanCode,
|
||||
action,
|
||||
(Vector2i) (mousePos ?? Vector2.Zero));
|
||||
(Vector2i) (mousePos ?? Vector2.Zero),
|
||||
args.RawCode);
|
||||
|
||||
var block = rawInput.RawKeyEvent(keyEvent);
|
||||
return block;
|
||||
|
||||
@@ -35,13 +35,15 @@ namespace Robust.Client.UserInterface
|
||||
public readonly int ScanCode;
|
||||
public readonly RawKeyAction Action;
|
||||
public readonly Vector2i MouseRelative;
|
||||
public readonly ushort RawCode;
|
||||
|
||||
public GuiRawKeyEvent(Keyboard.Key key, int scanCode, RawKeyAction action, Vector2i mouseRelative)
|
||||
public GuiRawKeyEvent(Keyboard.Key key, int scanCode, RawKeyAction action, Vector2i mouseRelative, ushort rawCode)
|
||||
{
|
||||
Key = key;
|
||||
ScanCode = scanCode;
|
||||
Action = action;
|
||||
MouseRelative = mouseRelative;
|
||||
RawCode = rawCode;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -302,8 +302,14 @@ namespace Robust.Server
|
||||
var mountOptions = _commandLineArgs != null
|
||||
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions) : Options.MountOptions;
|
||||
|
||||
var startType = ContentStart ? StartType.Content : StartType.Engine;
|
||||
#if FULL_RELEASE
|
||||
if (Options.ResourceMountDisabled)
|
||||
startType = StartType.Loader;
|
||||
#endif
|
||||
|
||||
ProgramShared.DoMounts(_resources, mountOptions, Options.ContentBuildDirectory, Options.AssemblyDirectory,
|
||||
Options.LoadContentResources, Options.ResourceMountDisabled, ContentStart);
|
||||
Options.LoadContentResources, startType);
|
||||
|
||||
// When the game is ran with the startup executable being content,
|
||||
// we have to disable the separate load context.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -18,8 +19,18 @@ namespace Robust.Shared.ContentPack
|
||||
{
|
||||
// TODO: remove this shitty hack, either through making it less hardcoded into shared,
|
||||
// or by making our file structure less spaghetti somehow.
|
||||
var assembly = typeof(PathHelpers).Assembly;
|
||||
var location = assembly.Location;
|
||||
string location;
|
||||
if (Process.GetCurrentProcess().MainModule is { } mod)
|
||||
{
|
||||
location = mod.FileName;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback in case the above doesn't work ig?
|
||||
var assembly = typeof(PathHelpers).Assembly;
|
||||
location = assembly.Location;
|
||||
}
|
||||
|
||||
if (location == string.Empty)
|
||||
{
|
||||
// See https://docs.microsoft.com/en-us/dotnet/api/system.reflection.assembly.location?view=net-5.0#remarks
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared.Configuration;
|
||||
@@ -28,14 +29,30 @@ internal static class ProgramShared
|
||||
}
|
||||
|
||||
#if !FULL_RELEASE
|
||||
private static string FindContentRootDir(bool contentStart)
|
||||
private static string FindContentRootDir(StartType startType)
|
||||
{
|
||||
return PathOffset + (contentStart ? "../../" : "../../../");
|
||||
var relative = startType switch
|
||||
{
|
||||
StartType.Engine => "../../../",
|
||||
StartType.Content => "../../",
|
||||
StartType.Loader => throw new InvalidOperationException(),
|
||||
StartType.ContentAppBundle => "../../../../../",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(startType), startType, null)
|
||||
};
|
||||
return PathOffset + relative;
|
||||
}
|
||||
|
||||
private static string FindEngineRootDir(bool contentStart)
|
||||
private static string FindEngineRootDir(StartType startType)
|
||||
{
|
||||
return PathOffset + (contentStart ? "../../RobustToolbox/" : "../../");
|
||||
var relative = startType switch
|
||||
{
|
||||
StartType.Engine => "../../",
|
||||
StartType.Content => "../../RobustToolbox/",
|
||||
StartType.Loader => throw new InvalidOperationException(),
|
||||
StartType.ContentAppBundle => "../../../../../RobustToolbox/",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(startType), startType, null)
|
||||
};
|
||||
return PathOffset + relative;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -47,19 +64,35 @@ internal static class ProgramShared
|
||||
}
|
||||
}
|
||||
|
||||
internal static void DoMounts(IResourceManagerInternal res, MountOptions? options, string contentBuildDir, ResPath assembliesPath, bool loadContentResources = true,
|
||||
bool loader = false, bool contentStart = false)
|
||||
internal static void DoMounts(
|
||||
IResourceManagerInternal res,
|
||||
MountOptions? options,
|
||||
string contentBuildDir,
|
||||
ResPath assembliesPath,
|
||||
bool loadContentResources = true,
|
||||
StartType startType = StartType.Engine)
|
||||
{
|
||||
#if FULL_RELEASE
|
||||
if (!loader)
|
||||
res.MountContentDirectory(@"Resources/");
|
||||
if (startType != StartType.Loader)
|
||||
res.MountContentDirectory(@"Resources/");
|
||||
#else
|
||||
res.MountContentDirectory($@"{FindEngineRootDir(contentStart)}Resources/");
|
||||
var engineRoot = FindEngineRootDir(startType);
|
||||
// System.Console.WriteLine($"ENGINE DIR IS {engineRoot}");
|
||||
res.MountContentDirectory($@"{engineRoot}Resources/");
|
||||
|
||||
if (loadContentResources)
|
||||
{
|
||||
var contentRootDir = FindContentRootDir(contentStart);
|
||||
res.MountContentDirectory($@"{contentRootDir}bin/{contentBuildDir}/", assembliesPath);
|
||||
var contentRootDir = FindContentRootDir(startType);
|
||||
// System.Console.WriteLine($"CONTENT DIR IS {Path.GetFullPath(contentRootDir)}");
|
||||
if (startType == StartType.ContentAppBundle)
|
||||
{
|
||||
res.MountContentDirectory("./", assembliesPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
res.MountContentDirectory($@"{contentRootDir}bin/{contentBuildDir}/", assembliesPath);
|
||||
}
|
||||
|
||||
res.MountContentDirectory($@"{contentRootDir}Resources/");
|
||||
}
|
||||
#endif
|
||||
@@ -103,3 +136,27 @@ internal static class ProgramShared
|
||||
task.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
internal enum StartType
|
||||
{
|
||||
/// <summary>
|
||||
/// We've been started from <c>RobustToolbox/bin/Client/Robust.Client</c>
|
||||
/// </summary>
|
||||
Engine,
|
||||
|
||||
/// <summary>
|
||||
/// We've been started from e.g. <c>bin/Content.Client/Content.Client</c>
|
||||
/// </summary>
|
||||
Content,
|
||||
|
||||
/// <summary>
|
||||
/// We've been started from the launcher loader.
|
||||
/// </summary>
|
||||
Loader,
|
||||
|
||||
/// <summary>
|
||||
/// (macOS only)
|
||||
/// We've been started from e.g. <c>bin/Content.Client/Space Station 14.app/Contents/MacOS/Content.Client</c>
|
||||
/// </summary>
|
||||
ContentAppBundle
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using Robust.Client;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
@@ -11,6 +12,7 @@ namespace Robust.UnitTesting
|
||||
public InitialLaunchState LaunchState { get; } = new(false, null, null, null);
|
||||
public GameControllerOptions Options { get; } = new();
|
||||
public bool ContentStart { get; set; }
|
||||
public StartType StartTypeValue => ContentStart ? StartType.Content : StartType.Engine;
|
||||
|
||||
public event Action<FrameEventArgs>? TickUpdateOverride { add { } remove { } }
|
||||
|
||||
|
||||
106
Tools/macos_make_appbundle.py
Executable file
106
Tools/macos_make_appbundle.py
Executable file
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import plistlib
|
||||
|
||||
p = os.path.join
|
||||
|
||||
symlinkable_re = re.compile(r"(?:runtimes|.+\.(?:dll|pdb|json))$", re.IGNORECASE)
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--webview", action="store_true")
|
||||
parser.add_argument("--name", required=True)
|
||||
parser.add_argument("--directory", required=True)
|
||||
parser.add_argument("--apphost", required=True)
|
||||
parser.add_argument("--identifier", required=True)
|
||||
parser.add_argument("--icon")
|
||||
|
||||
args = parser.parse_args()
|
||||
dir: str = args.directory
|
||||
name: str = args.name
|
||||
|
||||
# Create base app directory structure.
|
||||
os.makedirs(p(dir, f"{name}.app", "Contents", "MacOS"), exist_ok=True)
|
||||
os.makedirs(p(dir, f"{name}.app", "Contents", "Resources"), exist_ok=True)
|
||||
os.makedirs(p(dir, f"{name}.app", "Contents", "Frameworks"), exist_ok=True)
|
||||
|
||||
# Copy apphost
|
||||
dest_apphost = p(dir, f"{name}.app", "Contents", "MacOS", name)
|
||||
shutil.copy(p(dir, args.apphost), dest_apphost)
|
||||
|
||||
# Symlink most files in the bin dir.
|
||||
symlink_files(args.directory, p(dir, f"{name}.app", "Contents", "MacOS"), "")
|
||||
|
||||
# Copy icon
|
||||
if args.icon:
|
||||
shutil.copy(args.icon, p(dir, f"{name}.app", "Contents", "Resources", "icon.icns"))
|
||||
|
||||
# Write plist
|
||||
plist_dat = {
|
||||
"CFBundleName": name,
|
||||
"CFBundleDisplayName": name,
|
||||
"CFBundleIdentifier": args.identifier,
|
||||
"CFBundleIconFile": "icon",
|
||||
"CFBundleExecutable": name,
|
||||
"LSApplicationCategoryType": "public.app-category.games"
|
||||
}
|
||||
|
||||
with open(p(dir, f"{name}.app", "Contents", "Info.plist"), "wb") as f:
|
||||
plistlib.dump(plist_dat, f)
|
||||
|
||||
if args.webview:
|
||||
chromium_framework_path = p(dir, f"{name}.app", "Contents", "Frameworks", "Chromium Embedded Framework.framework")
|
||||
if not os.path.exists(chromium_framework_path):
|
||||
os.symlink("../../../Chromium Embedded Framework.framework", chromium_framework_path)
|
||||
|
||||
create_webview_helper(dir, name, args.identifier, None, None)
|
||||
create_webview_helper(dir, name, args.identifier, "GPU", "gpu")
|
||||
create_webview_helper(dir, name, args.identifier, "Renderer", "renderer")
|
||||
create_webview_helper(dir, name, args.identifier, "Alerts", "alerts")
|
||||
|
||||
def create_webview_helper(dir: str, name: str, identifier: str, suffix: str | None, identifier_suffix: str | None):
|
||||
helper_name = f"{name} helper"
|
||||
if suffix is not None:
|
||||
helper_name += f" ({suffix})"
|
||||
|
||||
sub_app_path = p(dir, f"{name}.app", "Contents", "Frameworks", f"{helper_name}.app")
|
||||
|
||||
os.makedirs(p(sub_app_path, "Contents", "MacOS"), exist_ok=True)
|
||||
os.makedirs(p(sub_app_path, "Contents", "Resources"), exist_ok=True)
|
||||
|
||||
# Copy apphost for Robust.Client.WebView
|
||||
shutil.copy(p(dir, "Robust.Client.WebView"), p(sub_app_path, "Contents", "MacOS", helper_name))
|
||||
|
||||
# Symlink files
|
||||
symlink_files(dir, p(sub_app_path, "Contents", "MacOS"), "../../../")
|
||||
|
||||
helper_identifier = f"{identifier}.cef.{identifier_suffix}"
|
||||
|
||||
if identifier_suffix is not None:
|
||||
helper_identifier += "." + identifier_suffix
|
||||
|
||||
plist_dat = {
|
||||
"CFBundleName": f"{name} helper",
|
||||
"CFBundleDisplayName": f"{name} helper",
|
||||
"CFBundleIdentifier": f"{identifier}.cef.{identifier_suffix}",
|
||||
"CFBundleExecutable": helper_name
|
||||
}
|
||||
|
||||
with open(p(sub_app_path, "Contents", "Info.plist"), "wb") as f:
|
||||
plistlib.dump(plist_dat, f)
|
||||
|
||||
def symlink_files(src_dir: str, dest_dir: str, relative: str):
|
||||
for file in os.listdir(src_dir):
|
||||
if not symlinkable_re.match(file):
|
||||
continue
|
||||
|
||||
dest_symlink = p(dest_dir, file)
|
||||
if not os.path.islink(dest_symlink):
|
||||
os.symlink(f"../../../{relative}{file}", dest_symlink)
|
||||
|
||||
|
||||
main()
|
||||
Reference in New Issue
Block a user